commit cd823a2d9e7344392b71e9513a5be2b352fdf56b Author: rohit Date: Sat Jul 5 23:59:03 2025 +0530 automaton layer diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 0000000..91e91e4 --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,1901 @@ +{ + "files": [ + "README.md" + ], + "imageSize": 100, + "commit": false, + "contributorsPerLine": 7, + "projectName": "activepieces", + "projectOwner": "activepieces", + "repoType": "github", + "repoHost": "https://github.com", + "skipCi": true, + "commitConvention": "angular", + "contributors": [ + { + "login": "ShahedAlMashni", + "name": "ShahedAlMashni", + "avatar_url": "https://avatars.githubusercontent.com/u/41443850?v=4", + "profile": "https://github.com/ShahedAlMashni", + "contributions": [ + "plugin" + ] + }, + { + "login": "AbdulTheActivePiecer", + "name": "AbdulTheActivePiecer", + "avatar_url": "https://avatars.githubusercontent.com/u/106555838?v=4", + "profile": "https://github.com/AbdulTheActivePiecer", + "contributions": [ + "maintenance" + ] + }, + { + "login": "khaledmashaly", + "name": "Khaled Mashaly", + "avatar_url": "https://avatars.githubusercontent.com/u/61781545?v=4", + "profile": "https://github.com/khaledmashaly", + "contributions": [ + "maintenance" + ] + }, + { + "login": "abuaboud", + "name": "Mohammed Abu Aboud", + "avatar_url": "https://avatars.githubusercontent.com/u/1812998?v=4", + "profile": "https://github.com/abuaboud", + "contributions": [ + "maintenance" + ] + }, + { + "login": "aboudzein", + "name": "Abdulrahman Zeineddin", + "avatar_url": "https://avatars.githubusercontent.com/u/12976630?v=4", + "profile": "https://aboudzein.github.io", + "contributions": [ + "plugin" + ] + }, + { + "login": "creed983", + "name": "ahmad jaber", + "avatar_url": "https://avatars.githubusercontent.com/u/62152944?v=4", + "profile": "https://github.com/creed983", + "contributions": [ + "plugin" + ] + }, + { + "login": "ashrafsamhouri", + "name": "ashrafsamhouri", + "avatar_url": "https://avatars.githubusercontent.com/u/97393596?v=4", + "profile": "https://github.com/ashrafsamhouri", + "contributions": [ + "plugin" + ] + }, + { + "login": "mabumusa1", + "name": "Mohammad Abu Musa", + "avatar_url": "https://avatars.githubusercontent.com/u/12627658?v=4", + "profile": "https://steercampaign.com", + "contributions": [ + "projectManagement" + ] + }, + { + "login": "kanarelo", + "name": "Mukewa Wekalao", + "avatar_url": "https://avatars.githubusercontent.com/u/393261?v=4", + "profile": "https://github.com/kanarelo", + "contributions": [ + "plugin" + ] + }, + { + "login": "OsamaHaikal", + "name": "Osama Abdallah Essa Haikal", + "avatar_url": "https://avatars.githubusercontent.com/u/72370395?v=4", + "profile": "https://osamahaikal.me/", + "contributions": [ + "plugin" + ] + }, + { + "login": "M-Arman", + "name": "Arman", + "avatar_url": "https://avatars.githubusercontent.com/u/54455592?v=4", + "profile": "https://github.com/M-Arman", + "contributions": [ + "security" + ] + }, + { + "login": "oskarkraemer", + "name": "Oskar Krämer", + "avatar_url": "https://avatars.githubusercontent.com/u/42745862?v=4", + "profile": "https://github.com/oskarkraemer", + "contributions": [ + "doc" + ] + }, + { + "login": "tpatel", + "name": "Thibaut Patel", + "avatar_url": "https://avatars.githubusercontent.com/u/494686?v=4", + "profile": "https://thibpat.com", + "contributions": [ + "ideas", + "plugin" + ] + }, + { + "login": "Applesaucesomer", + "name": "Applesaucesomer", + "avatar_url": "https://avatars.githubusercontent.com/u/18318905?v=4", + "profile": "https://github.com/Applesaucesomer", + "contributions": [ + "ideas" + ] + }, + { + "login": "crazyTweek", + "name": "crazyTweek", + "avatar_url": "https://avatars.githubusercontent.com/u/6828237?v=4", + "profile": "https://github.com/crazyTweek", + "contributions": [ + "ideas" + ] + }, + { + "login": "m-tabaza", + "name": "Muhammad Tabaza", + "avatar_url": "https://avatars.githubusercontent.com/u/23503983?v=4", + "profile": "https://linkedin.com/in/muhammad-tabaza", + "contributions": [ + "plugin" + ] + }, + { + "login": "ShayPunter", + "name": "Shay Punter", + "avatar_url": "https://avatars.githubusercontent.com/u/18310437?v=4", + "profile": "https://shaypunter.co.uk", + "contributions": [ + "doc", + "plugin" + ] + }, + { + "login": "abaza738", + "name": "abaza738", + "avatar_url": "https://avatars.githubusercontent.com/u/50132270?v=4", + "profile": "https://github.com/abaza738", + "contributions": [ + "plugin" + ] + }, + { + "login": "jonaboe", + "name": "Jona Boeddinghaus", + "avatar_url": "https://avatars.githubusercontent.com/u/51358680?v=4", + "profile": "https://github.com/jonaboe", + "contributions": [ + "plugin" + ] + }, + { + "login": "fomojola", + "name": "fomojola", + "avatar_url": "https://avatars.githubusercontent.com/u/264253?v=4", + "profile": "https://github.com/fomojola", + "contributions": [ + "code" + ] + }, + { + "login": "astorozhevsky", + "name": "Alexander Storozhevsky", + "avatar_url": "https://avatars.githubusercontent.com/u/11055414?v=4", + "profile": "https://github.com/astorozhevsky", + "contributions": [ + "code" + ] + }, + { + "login": "J0LGER", + "name": "J0LGER", + "avatar_url": "https://avatars.githubusercontent.com/u/54769522?v=4", + "profile": "https://github.com/J0LGER", + "contributions": [ + "security" + ] + }, + { + "login": "veverkap", + "name": "Patrick Veverka", + "avatar_url": "https://avatars.githubusercontent.com/u/22348?v=4", + "profile": "https://about.me/veverkap", + "contributions": [ + "bug" + ] + }, + { + "login": "berksmbl", + "name": "Berk Sümbül", + "avatar_url": "https://avatars.githubusercontent.com/u/10000339?v=4", + "profile": "http://berksmbl.com", + "contributions": [ + "doc" + ] + }, + { + "login": "Willianwg", + "name": "Willian Guedes", + "avatar_url": "https://avatars.githubusercontent.com/u/51550522?v=4", + "profile": "https://github.com/Willianwg", + "contributions": [ + "plugin" + ] + }, + { + "login": "abdullahranginwala", + "name": "Abdullah Ranginwala", + "avatar_url": "https://avatars.githubusercontent.com/u/19731056?v=4", + "profile": "https://github.com/abdullahranginwala", + "contributions": [ + "code" + ] + }, + { + "login": "dentych", + "name": "Dennis Tychsen", + "avatar_url": "https://avatars.githubusercontent.com/u/2256372?v=4", + "profile": "https://github.com/dentych", + "contributions": [ + "plugin" + ] + }, + { + "login": "MyWay", + "name": "MyWay", + "avatar_url": "https://avatars.githubusercontent.com/u/1765284?v=4", + "profile": "https://github.com/MyWay", + "contributions": [ + "plugin" + ] + }, + { + "login": "bibhuty-did-this", + "name": "Bibhuti Bhusan Panda", + "avatar_url": "https://avatars.githubusercontent.com/u/28416188?v=4", + "profile": "https://github.com/bibhuty-did-this", + "contributions": [ + "plugin" + ] + }, + { + "login": "tarunsamanta2k20", + "name": "Tarun Samanta", + "avatar_url": "https://avatars.githubusercontent.com/u/55488549?v=4", + "profile": "https://github.com/tarunsamanta2k20", + "contributions": [ + "bug" + ] + }, + { + "login": "HKudria", + "name": "Herman Kudria", + "avatar_url": "https://avatars.githubusercontent.com/u/9007211?v=4", + "profile": "https://www.linkedin.com/in/herman-kudria-10868b207/", + "contributions": [ + "plugin" + ] + }, + { + "login": "Abdallah-Alwarawreh", + "name": "[NULL] Dev", + "avatar_url": "https://avatars.githubusercontent.com/u/66683380?v=4", + "profile": "http://nulldev.imagefoo.com/", + "contributions": [ + "plugin" + ] + }, + { + "login": "JanHolger", + "name": "Jan Bebendorf", + "avatar_url": "https://avatars.githubusercontent.com/u/25184957?v=4", + "profile": "https://github.com/JanHolger", + "contributions": [ + "plugin" + ] + }, + { + "login": "nileshtrivedi", + "name": "Nilesh", + "avatar_url": "https://avatars.githubusercontent.com/u/19304?v=4", + "profile": "https://blog.nileshtrivedi.com", + "contributions": [ + "plugin" + ] + }, + { + "login": "VrajGohil", + "name": "Vraj Gohil", + "avatar_url": "https://avatars.githubusercontent.com/u/40790016?v=4", + "profile": "https://certopus.com", + "contributions": [ + "plugin" + ] + }, + { + "login": "BastienMe", + "name": "BastienMe", + "avatar_url": "https://avatars.githubusercontent.com/u/71411115?v=4", + "profile": "https://github.com/BastienMe", + "contributions": [ + "plugin" + ] + }, + { + "login": "SFoskett", + "name": "Stephen Foskett", + "avatar_url": "https://avatars.githubusercontent.com/u/8627862?v=4", + "profile": "http://blog.fosketts.net", + "contributions": [ + "doc" + ] + }, + { + "login": "asuri0n", + "name": "Nathan", + "avatar_url": "https://avatars.githubusercontent.com/u/15729117?v=4", + "profile": "http://ganapati.fr", + "contributions": [ + "doc" + ] + }, + { + "login": "mnatanek", + "name": "Marcin Natanek", + "avatar_url": "https://avatars.githubusercontent.com/u/4056319?v=4", + "profile": "https://www.n-soft.pl", + "contributions": [ + "plugin" + ] + }, + { + "login": "buttonsbond", + "name": "Mark van Bellen", + "avatar_url": "https://avatars.githubusercontent.com/u/23551912?v=4", + "profile": "http://all-tech-plus.com", + "contributions": [ + "plugin" + ] + }, + { + "login": "olivierguzzi", + "name": "Olivier Guzzi", + "avatar_url": "https://avatars.githubusercontent.com/u/13715916?v=4", + "profile": "http://guzguz.fr", + "contributions": [ + "plugin" + ] + }, + { + "login": "Ozak93", + "name": "Osama Zakarneh", + "avatar_url": "https://avatars.githubusercontent.com/u/31257994?v=4", + "profile": "https://github.com/Ozak93", + "contributions": [ + "plugin" + ] + }, + { + "login": "phestvik", + "name": "phestvik", + "avatar_url": "https://avatars.githubusercontent.com/u/88210985?v=4", + "profile": "https://github.com/phestvik", + "contributions": [ + "ideas" + ] + }, + { + "login": "Rajdeep1311", + "name": "Rajdeep Pal", + "avatar_url": "https://avatars.githubusercontent.com/u/113296626?v=4", + "profile": "http://website-portfolio-bucket.s3-website-ap-northeast-1.amazonaws.com/", + "contributions": [ + "doc" + ] + }, + { + "login": "camilou", + "name": "Camilo Usuga", + "avatar_url": "https://avatars.githubusercontent.com/u/40870?v=4", + "profile": "http://www.tepote.com", + "contributions": [ + "plugin" + ] + }, + { + "login": "kishanprmr", + "name": "Kishan Parmar", + "avatar_url": "https://avatars.githubusercontent.com/u/135701940?v=4", + "profile": "https://github.com/kishanprmr", + "contributions": [ + "doc", + "plugin" + ] + }, + { + "login": "BBND", + "name": "BBND", + "avatar_url": "https://avatars.githubusercontent.com/u/42919338?v=4", + "profile": "https://github.com/BBND", + "contributions": [ + "plugin" + ] + }, + { + "login": "haseebrehmanpc", + "name": "Haseeb Rehman", + "avatar_url": "https://avatars.githubusercontent.com/u/37938986?v=4", + "profile": "https://github.com/haseebrehmanpc", + "contributions": [ + "plugin" + ] + }, + { + "login": "rita-gorokhod", + "name": "Rita Gorokhod", + "avatar_url": "https://avatars.githubusercontent.com/u/60586879?v=4", + "profile": "https://www.linkedin.com/in/ritagorokhod/", + "contributions": [ + "plugin" + ] + }, + { + "login": "facferreira", + "name": "Fábio Ferreira", + "avatar_url": "https://avatars.githubusercontent.com/u/487349?v=4", + "profile": "https://github.com/facferreira", + "contributions": [ + "plugin" + ] + }, + { + "login": "FlorinBuffet", + "name": "Florin Buffet", + "avatar_url": "https://avatars.githubusercontent.com/u/73933252?v=4", + "profile": "https://buffetitservices.ch", + "contributions": [ + "doc" + ] + }, + { + "login": "Owlcept", + "name": "Drew Lewis", + "avatar_url": "https://avatars.githubusercontent.com/u/67299472?v=4", + "profile": "https://github.com/Owlcept", + "contributions": [ + "plugin" + ] + }, + { + "login": "bendersej", + "name": "Benjamin André-Micolon", + "avatar_url": "https://avatars.githubusercontent.com/u/10613140?v=4", + "profile": "https://bendersej.com", + "contributions": [ + "plugin" + ] + }, + { + "login": "DGurskij", + "name": "Denis Gurskij", + "avatar_url": "https://avatars.githubusercontent.com/u/26856659?v=4", + "profile": "https://github.com/DGurskij", + "contributions": [ + "plugin" + ] + }, + { + "login": "thatguynef", + "name": "Nefer Lopez", + "avatar_url": "https://avatars.githubusercontent.com/u/11466949?v=4", + "profile": "https://neferlopez.com", + "contributions": [ + "doc" + ] + }, + { + "login": "fardeenpanjwani-codeglo", + "name": "fardeenpanjwani-codeglo", + "avatar_url": "https://avatars.githubusercontent.com/u/141914308?v=4", + "profile": "https://github.com/fardeenpanjwani-codeglo", + "contributions": [ + "doc" + ] + }, + { + "login": "landonmoir", + "name": "Landon Moir", + "avatar_url": "https://avatars.githubusercontent.com/u/29764668?v=4", + "profile": "https://github.com/landonmoir", + "contributions": [ + "plugin" + ] + }, + { + "login": "lldiegon", + "name": "Diego Nijboer", + "avatar_url": "https://avatars.githubusercontent.com/u/22002313?v=4", + "profile": "https://lightspeed-it.nl/", + "contributions": [ + "plugin" + ] + }, + { + "login": "tanoggy", + "name": "Tân Một Nắng", + "avatar_url": "https://avatars.githubusercontent.com/u/24206229?v=4", + "profile": "https://ductan.me/", + "contributions": [ + "plugin" + ] + }, + { + "login": "GFoley83", + "name": "Gavin Foley", + "avatar_url": "https://avatars.githubusercontent.com/u/838788?v=4", + "profile": "https://geteduca.com", + "contributions": [ + "doc" + ] + }, + { + "login": "dennis-tra", + "name": "Dennis Trautwein", + "avatar_url": "https://avatars.githubusercontent.com/u/11836793?v=4", + "profile": "https://dtrautwein.eu", + "contributions": [ + "bug" + ] + }, + { + "login": "inspiredclick", + "name": "Andrew Rosenblatt", + "avatar_url": "https://avatars.githubusercontent.com/u/1548613?v=4", + "profile": "https://github.com/inspiredclick", + "contributions": [ + "bug" + ] + }, + { + "login": "w95", + "name": "rika", + "avatar_url": "https://avatars.githubusercontent.com/u/6433752?v=4", + "profile": "https://github.com/w95", + "contributions": [ + "plugin" + ] + }, + { + "login": "cyrilselasi", + "name": "Cyril Selasi", + "avatar_url": "https://avatars.githubusercontent.com/u/7190330?v=4", + "profile": "https://github.com/cyrilselasi", + "contributions": [ + "plugin" + ] + }, + { + "login": "nijfranck", + "name": "Franck Nijimbere", + "avatar_url": "https://avatars.githubusercontent.com/u/9940307?v=4", + "profile": "http://nijfranck.github.io", + "contributions": [ + "plugin" + ] + }, + { + "login": "alerdenisov", + "name": "Aleksandr Denisov", + "avatar_url": "https://avatars.githubusercontent.com/u/3899837?v=4", + "profile": "https://github.com/alerdenisov", + "contributions": [ + "plugin" + ] + }, + { + "login": "rbnswartz", + "name": "Reuben Swartz", + "avatar_url": "https://avatars.githubusercontent.com/u/724704?v=4", + "profile": "https://github.com/rbnswartz", + "contributions": [ + "doc" + ] + }, + { + "login": "joselupianez", + "name": "joselupianez", + "avatar_url": "https://avatars.githubusercontent.com/u/4380557?v=4", + "profile": "http://lupianezjose.com", + "contributions": [ + "plugin" + ] + }, + { + "login": "awais000", + "name": "Awais Manzoor", + "avatar_url": "https://avatars.githubusercontent.com/u/24081860?v=4", + "profile": "http://www.zidoary.com", + "contributions": [ + "bug", + "code" + ] + }, + { + "login": "andchir", + "name": "Andrei", + "avatar_url": "https://avatars.githubusercontent.com/u/6392311?v=4", + "profile": "https://github.com/andchir", + "contributions": [ + "bug" + ] + }, + { + "login": "derbbre", + "name": "derbbre", + "avatar_url": "https://avatars.githubusercontent.com/u/281843?v=4", + "profile": "https://github.com/derbbre", + "contributions": [ + "doc" + ] + }, + { + "login": "maor-rozenfeld", + "name": "Maor Rozenfeld", + "avatar_url": "https://avatars.githubusercontent.com/u/49363375?v=4", + "profile": "https://github.com/maor-rozenfeld", + "contributions": [ + "code" + ] + }, + { + "login": "miqh", + "name": "Michael Huynh", + "avatar_url": "https://avatars.githubusercontent.com/u/43751307?v=4", + "profile": "https://github.com/miqh", + "contributions": [ + "doc" + ] + }, + { + "login": "fdundjer", + "name": "Filip Dunđer", + "avatar_url": "https://avatars.githubusercontent.com/u/17405319?v=4", + "profile": "https://github.com/fdundjer", + "contributions": [ + "code" + ] + }, + { + "login": "donthorp", + "name": "Don Thorp", + "avatar_url": "https://avatars.githubusercontent.com/u/8629?v=4", + "profile": "http://donthorp.net", + "contributions": [ + "doc" + ] + }, + { + "login": "joeworkman", + "name": "Joe Workman", + "avatar_url": "https://avatars.githubusercontent.com/u/225628?v=4", + "profile": "https://joeworkman.net", + "contributions": [ + "plugin" + ] + }, + { + "login": "Autumnlight02", + "name": "Aykut Akgün", + "avatar_url": "https://avatars.githubusercontent.com/u/68244453?v=4", + "profile": "https://github.com/Autumnlight02", + "contributions": [ + "code" + ] + }, + { + "login": "yann120", + "name": "Yann Petitjean", + "avatar_url": "https://avatars.githubusercontent.com/u/10012140?v=4", + "profile": "https://github.com/yann120", + "contributions": [ + "plugin", + "bug" + ] + }, + { + "login": "pfernandez98", + "name": "pfernandez98", + "avatar_url": "https://avatars.githubusercontent.com/u/54374282?v=4", + "profile": "https://github.com/pfernandez98", + "contributions": [ + "plugin" + ] + }, + { + "login": "denieler", + "name": "Daniel O.", + "avatar_url": "https://avatars.githubusercontent.com/u/2836281?v=4", + "profile": "http://denieler.com", + "contributions": [ + "plugin" + ] + }, + { + "login": "MrMYHuang", + "name": "Meng-Yuan Huang", + "avatar_url": "https://avatars.githubusercontent.com/u/12458706?v=4", + "profile": "http://myh.tw", + "contributions": [ + "doc" + ] + }, + { + "login": "bigfluffycookie", + "name": "Leyla", + "avatar_url": "https://avatars.githubusercontent.com/u/54935347?v=4", + "profile": "https://github.com/bigfluffycookie", + "contributions": [ + "bug" + ] + }, + { + "login": "i-nithin", + "name": "i-nithin", + "avatar_url": "https://avatars.githubusercontent.com/u/97078688?v=4", + "profile": "https://i-nithin.netlify.app/", + "contributions": [ + "plugin" + ] + }, + { + "login": "la3rence", + "name": "la3rence", + "avatar_url": "https://avatars.githubusercontent.com/u/24540598?v=4", + "profile": "https://lawrenceli.me", + "contributions": [ + "plugin" + ] + }, + { + "login": "dennisrongo", + "name": "Dennis Rongo", + "avatar_url": "https://avatars.githubusercontent.com/u/51771021?v=4", + "profile": "http://dennisrongo.com", + "contributions": [ + "bug", + "plugin" + ] + }, + { + "login": "kartikmehta8", + "name": "Kartik Mehta", + "avatar_url": "https://avatars.githubusercontent.com/u/77505989?v=4", + "profile": "https://github.com/kartikmehta8", + "contributions": [ + "doc", + "code" + ] + }, + { + "login": "zaaakher", + "name": "Zakher Masri", + "avatar_url": "https://avatars.githubusercontent.com/u/46135573?v=4", + "profile": "https://zakher.me", + "contributions": [ + "doc", + "code" + ] + }, + { + "login": "AbdullahBitar", + "name": "AbdullahBitar", + "avatar_url": "https://avatars.githubusercontent.com/u/122645579?v=4", + "profile": "https://github.com/AbdullahBitar", + "contributions": [ + "plugin" + ] + }, + { + "login": "mariomeyer", + "name": "Mario Meyer", + "avatar_url": "https://avatars.githubusercontent.com/u/867650?v=4", + "profile": "https://github.com/mariomeyer", + "contributions": [ + "plugin" + ] + }, + { + "login": "karimkhaleel", + "name": "Karim Khaleel", + "avatar_url": "https://avatars.githubusercontent.com/u/94621779?v=4", + "profile": "https://github.com/karimkhaleel", + "contributions": [ + "plugin" + ] + }, + { + "login": "CPonchet", + "name": "CPonchet", + "avatar_url": "https://avatars.githubusercontent.com/u/40756925?v=4", + "profile": "https://github.com/CPonchet", + "contributions": [ + "bug" + ] + }, + { + "login": "AdamSelene", + "name": "Olivier Sambourg", + "avatar_url": "https://avatars.githubusercontent.com/u/79495?v=4", + "profile": "https://github.com/AdamSelene", + "contributions": [ + "plugin" + ] + }, + { + "login": "Verlich", + "name": "Ahmad(Ed)", + "avatar_url": "https://avatars.githubusercontent.com/u/30838131?v=4", + "profile": "https://github.com/Verlich", + "contributions": [ + "plugin" + ] + }, + { + "login": "leenmashni", + "name": "leenmashni", + "avatar_url": "https://avatars.githubusercontent.com/u/102361544?v=4", + "profile": "https://github.com/leenmashni", + "contributions": [ + "plugin" + ] + }, + { + "login": "AliasKingsWorth", + "name": "M Abdul Rauf", + "avatar_url": "https://avatars.githubusercontent.com/u/47811610?v=4", + "profile": "https://github.com/AliasKingsWorth", + "contributions": [ + "doc" + ] + }, + { + "login": "vbarrier", + "name": "Vincent Barrier", + "avatar_url": "https://avatars.githubusercontent.com/u/446808?v=4", + "profile": "https://github.com/vbarrier", + "contributions": [ + "plugin" + ] + }, + { + "login": "jmgb27", + "name": "John", + "avatar_url": "https://avatars.githubusercontent.com/u/65794951?v=4", + "profile": "http://johnmark.dev", + "contributions": [ + "code", + "plugin" + ] + }, + { + "login": "jdevalk", + "name": "Joost de Valk", + "avatar_url": "https://avatars.githubusercontent.com/u/487629?v=4", + "profile": "https://joost.blog/", + "contributions": [ + "plugin" + ] + }, + { + "login": "nyamkamunhjin", + "name": "MJ", + "avatar_url": "https://avatars.githubusercontent.com/u/44439626?v=4", + "profile": "https://www.linkedin.com/in/nyamkamunhjin/", + "contributions": [ + "plugin" + ] + }, + { + "login": "shravankshenoy", + "name": "ShravanShenoy", + "avatar_url": "https://avatars.githubusercontent.com/u/29670290?v=4", + "profile": "https://github.com/shravankshenoy", + "contributions": [ + "code" + ] + }, + { + "login": "jonkristian", + "name": "Jon Kristian", + "avatar_url": "https://avatars.githubusercontent.com/u/13219?v=4", + "profile": "http://jonkristian.no", + "contributions": [ + "doc" + ] + }, + { + "login": "cr0fters", + "name": "cr0fters", + "avatar_url": "https://avatars.githubusercontent.com/u/1754858?v=4", + "profile": "https://github.com/cr0fters", + "contributions": [ + "bug" + ] + }, + { + "login": "bimsina", + "name": "Bibek Timsina", + "avatar_url": "https://avatars.githubusercontent.com/u/29589003?v=4", + "profile": "https://bibek-timsina.com.np/", + "contributions": [ + "bug" + ] + }, + { + "login": "szepeviktor", + "name": "Viktor Szépe", + "avatar_url": "https://avatars.githubusercontent.com/u/952007?v=4", + "profile": "https://github.com/szepeviktor/debian-server-tools/blob/master/CV.md", + "contributions": [ + "code" + ] + }, + { + "login": "rendyt1", + "name": "Rendy Tan", + "avatar_url": "https://avatars.githubusercontent.com/u/38492810?v=4", + "profile": "https://github.com/rendyt1", + "contributions": [ + "doc", + "plugin" + ] + }, + { + "login": "islamaf", + "name": "Islam Abdelfattah", + "avatar_url": "https://avatars.githubusercontent.com/u/44944648?v=4", + "profile": "http://islamaf.github.io", + "contributions": [ + "bug" + ] + }, + { + "login": "uniqueeest", + "name": "Yoonjae Choi", + "avatar_url": "https://avatars.githubusercontent.com/u/123538138?v=4", + "profile": "https://github.com/uniqueeest", + "contributions": [ + "code" + ] + }, + { + "login": "javix64", + "name": "Javier HM", + "avatar_url": "https://avatars.githubusercontent.com/u/58471170?v=4", + "profile": "http://javix64.com", + "contributions": [ + "plugin" + ] + }, + { + "login": "MohamedHassan499", + "name": "Mohamed Hassan", + "avatar_url": "https://avatars.githubusercontent.com/u/50884619?v=4", + "profile": "http://farag.tech", + "contributions": [ + "bug" + ] + }, + { + "login": "christian-schab", + "name": "Christian Schab", + "avatar_url": "https://avatars.githubusercontent.com/u/17610709?v=4", + "profile": "https://www.coasy.com/", + "contributions": [ + "plugin" + ] + }, + { + "login": "thirstycode", + "name": "Pratik Kinage", + "avatar_url": "https://avatars.githubusercontent.com/u/37847256?v=4", + "profile": "https://www.gamespecifications.com/", + "contributions": [ + "plugin" + ] + }, + { + "login": "LevwTech", + "name": "Abdelrahman Mostafa ", + "avatar_url": "https://avatars.githubusercontent.com/u/69399787?v=4", + "profile": "https://github.com/LevwTech", + "contributions": [ + "plugin" + ] + }, + { + "login": "HamzaZagha", + "name": "Hamza Zagha", + "avatar_url": "https://avatars.githubusercontent.com/u/45468866?v=4", + "profile": "https://github.com/HamzaZagha", + "contributions": [ + "bug" + ] + }, + { + "login": "founderblocks-sils", + "name": "Lasse Schuirmann", + "avatar_url": "https://avatars.githubusercontent.com/u/88160672?v=4", + "profile": "https://founderblocks.io/", + "contributions": [ + "plugin" + ] + }, + { + "login": "Startouf", + "name": "Cyril Duchon-Doris", + "avatar_url": "https://avatars.githubusercontent.com/u/7388889?v=4", + "profile": "https://about.me/cyril_duchon_doris", + "contributions": [ + "plugin" + ] + }, + { + "login": "Javiink", + "name": "Javiink", + "avatar_url": "https://avatars.githubusercontent.com/u/43996484?v=4", + "profile": "https://github.com/Javiink", + "contributions": [ + "plugin" + ] + }, + { + "login": "hharchani", + "name": "Harshit Harchani", + "avatar_url": "https://avatars.githubusercontent.com/u/6430611?v=4", + "profile": "https://github.com/hharchani", + "contributions": [ + "plugin" + ] + }, + { + "login": "MrAkber", + "name": "MrAkber", + "avatar_url": "https://avatars.githubusercontent.com/u/170118042?v=4", + "profile": "https://github.com/MrAkber", + "contributions": [ + "doc" + ] + }, + { + "login": "marek-slavicek", + "name": "marek-slavicek", + "avatar_url": "https://avatars.githubusercontent.com/u/136325104?v=4", + "profile": "https://github.com/marek-slavicek", + "contributions": [ + "plugin" + ] + }, + { + "login": "hugh-codes", + "name": "hugh-codes", + "avatar_url": "https://avatars.githubusercontent.com/u/166336705?v=4", + "profile": "https://github.com/hugh-codes", + "contributions": [ + "plugin" + ] + }, + { + "login": "alewis001", + "name": "Alex Lewis", + "avatar_url": "https://avatars.githubusercontent.com/u/3482446?v=4", + "profile": "https://github.com/alewis001", + "contributions": [ + "bug" + ] + }, + { + "login": "yuaanlin", + "name": "Yuanlin Lin", + "avatar_url": "https://avatars.githubusercontent.com/u/21105863?v=4", + "profile": "https://yual.in", + "contributions": [ + "doc" + ] + }, + { + "login": "AlaShibanAtKlo", + "name": "Ala Shiban", + "avatar_url": "https://avatars.githubusercontent.com/u/96867907?v=4", + "profile": "https://klo.dev", + "contributions": [ + "doc" + ] + }, + { + "login": "hamedsh", + "name": "hamsh", + "avatar_url": "https://avatars.githubusercontent.com/u/6043214?v=4", + "profile": "http://hamedsh.medium.com", + "contributions": [ + "code" + ] + }, + { + "login": "AnneMariel95", + "name": "Anne Mariel Catapang", + "avatar_url": "https://avatars.githubusercontent.com/u/77142075?v=4", + "profile": "https://www.anne-mariel.com/", + "contributions": [ + "plugin" + ] + }, + { + "login": "codegino", + "name": "Carlo Gino Catapang", + "avatar_url": "https://avatars.githubusercontent.com/u/19299524?v=4", + "profile": "https://hi.carlogino.com", + "contributions": [ + "plugin" + ] + }, + { + "login": "drona2938", + "name": "Aditya Rathore", + "avatar_url": "https://avatars.githubusercontent.com/u/34496554?v=4", + "profile": "https://github.com/drona2938", + "contributions": [ + "plugin" + ] + }, + { + "login": "coderbob2", + "name": "coderbob2", + "avatar_url": "https://avatars.githubusercontent.com/u/47177246?v=4", + "profile": "https://github.com/coderbob2", + "contributions": [ + "plugin" + ] + }, + { + "login": "Raamyy", + "name": "Ramy Gamal", + "avatar_url": "https://avatars.githubusercontent.com/u/29176293?v=4", + "profile": "https://raamyy.netlify.app", + "contributions": [ + "plugin" + ] + }, + { + "login": "alexandrudanpop", + "name": "Alexandru-Dan Pop", + "avatar_url": "https://avatars.githubusercontent.com/u/15979292?v=4", + "profile": "https://alexandrudanpop.dev/", + "contributions": [ + "code" + ] + }, + { + "login": "Trayshmhirk", + "name": "Frank Micheal ", + "avatar_url": "https://avatars.githubusercontent.com/u/112286458?v=4", + "profile": "https://github.com/Trayshmhirk", + "contributions": [ + "plugin" + ] + }, + { + "login": "emmanuel-ferdman", + "name": "Emmanuel Ferdman", + "avatar_url": "https://avatars.githubusercontent.com/u/35470921?v=4", + "profile": "https://github.com/emmanuel-ferdman", + "contributions": [ + "doc" + ] + }, + { + "login": "sany2407", + "name": "Sany A", + "avatar_url": "https://avatars.githubusercontent.com/u/179091674?v=4", + "profile": "https://github.com/sany2407", + "contributions": [ + "plugin" + ] + }, + { + "login": "Swimburger", + "name": "Niels Swimberghe", + "avatar_url": "https://avatars.githubusercontent.com/u/3382717?v=4", + "profile": "https://swimburger.net", + "contributions": [ + "bug" + ] + }, + { + "login": "lostinbug", + "name": "lostinbug", + "avatar_url": "https://avatars.githubusercontent.com/u/157452389?v=4", + "profile": "https://github.com/lostinbug", + "contributions": [ + "plugin" + ] + }, + { + "login": "gushkool", + "name": "gushkool", + "avatar_url": "https://avatars.githubusercontent.com/u/64713308?v=4", + "profile": "https://github.com/gushkool", + "contributions": [ + "plugin" + ] + }, + { + "login": "OmarSayed", + "name": "Omar Sayed", + "avatar_url": "https://avatars.githubusercontent.com/u/3813045?v=4", + "profile": "http://www.linkedin.com/in/omarsayed", + "contributions": [ + "plugin" + ] + }, + { + "login": "rSnapkoOpenOps", + "name": "rSnapkoOpenOps", + "avatar_url": "https://avatars.githubusercontent.com/u/179845343?v=4", + "profile": "https://github.com/rSnapkoOpenOps", + "contributions": [ + "bug" + ] + }, + { + "login": "ahronshor", + "name": "ahronshor", + "avatar_url": "https://avatars.githubusercontent.com/u/25138831?v=4", + "profile": "https://github.com/ahronshor", + "contributions": [ + "plugin" + ] + }, + { + "login": "cezudas", + "name": "Cezar", + "avatar_url": "https://avatars.githubusercontent.com/u/3786138?v=4", + "profile": "https://github.com/cezudas", + "contributions": [ + "bug" + ] + }, + { + "login": "geekyme-fsmk", + "name": "Shawn Lim", + "avatar_url": "https://avatars.githubusercontent.com/u/100678833?v=4", + "profile": "https://github.com/geekyme-fsmk", + "contributions": [ + "plugin" + ] + }, + { + "login": "geekyme", + "name": "Shawn Lim", + "avatar_url": "https://avatars.githubusercontent.com/u/977460?v=4", + "profile": "https://shawn.storyline.io/", + "contributions": [ + "plugin" + ] + }, + { + "login": "pavloDeshko", + "name": "pavloDeshko", + "avatar_url": "https://avatars.githubusercontent.com/u/27104046?v=4", + "profile": "https://github.com/pavloDeshko", + "contributions": [ + "bug" + ] + }, + { + "login": "liuhuapiaoyuan", + "name": "abc", + "avatar_url": "https://avatars.githubusercontent.com/u/8020726?v=4", + "profile": "https://github.com/liuhuapiaoyuan", + "contributions": [ + "code" + ] + }, + { + "login": "manojkum-d", + "name": "manoj kumar d", + "avatar_url": "https://avatars.githubusercontent.com/u/141437046?v=4", + "profile": "https://github.com/manojkum-d", + "contributions": [ + "plugin" + ] + }, + { + "login": "felifluid", + "name": "Feli", + "avatar_url": "https://avatars.githubusercontent.com/u/59516203?v=4", + "profile": "https://github.com/felifluid", + "contributions": [ + "plugin" + ] + }, + { + "login": "mordonez", + "name": "Miguel", + "avatar_url": "https://avatars.githubusercontent.com/u/293837?v=4", + "profile": "https://github.com/mordonez", + "contributions": [ + "plugin" + ] + }, + { + "login": "dev-instasent", + "name": "Instasent DEV", + "avatar_url": "https://avatars.githubusercontent.com/u/116744368?v=4", + "profile": "https://github.com/dev-instasent", + "contributions": [ + "plugin" + ] + }, + { + "login": "matthieu-lombard", + "name": "Matthieu Lombard", + "avatar_url": "https://avatars.githubusercontent.com/u/33624489?v=4", + "profile": "https://github.com/matthieu-lombard", + "contributions": [ + "plugin" + ] + }, + { + "login": "beyondlevi", + "name": "beyondlevi", + "avatar_url": "https://avatars.githubusercontent.com/u/57486338?v=4", + "profile": "https://github.com/beyondlevi", + "contributions": [ + "plugin" + ] + }, + { + "login": "rafalzawadzki", + "name": "Rafal Zawadzki", + "avatar_url": "https://avatars.githubusercontent.com/u/10667346?v=4", + "profile": "https://rafal.fyi", + "contributions": [ + "plugin" + ] + }, + { + "login": "simonc", + "name": "Simon Courtois", + "avatar_url": "https://avatars.githubusercontent.com/u/119303?v=4", + "profile": "https://www.pdfmonkey.io/", + "contributions": [ + "plugin" + ] + }, + { + "login": "alegria-solutions", + "name": "alegria-solutions", + "avatar_url": "https://avatars.githubusercontent.com/u/124846022?v=4", + "profile": "https://github.com/alegria-solutions", + "contributions": [ + "plugin" + ] + }, + { + "login": "D-Rowe-FS", + "name": "D-Rowe-FS", + "avatar_url": "https://avatars.githubusercontent.com/u/142934784?v=4", + "profile": "https://github.com/D-Rowe-FS", + "contributions": [ + "plugin" + ] + }, + { + "login": "ChineseHamberger", + "name": "张晟杰", + "avatar_url": "https://avatars.githubusercontent.com/u/101547635?v=4", + "profile": "https://github.com/ChineseHamberger", + "contributions": [ + "plugin" + ] + }, + { + "login": "AshotZaqoyan", + "name": "Ashot", + "avatar_url": "https://avatars.githubusercontent.com/u/72438085?v=4", + "profile": "https://codesign.rf.gd", + "contributions": [ + "plugin" + ] + }, + { + "login": "amrabuaza", + "name": "Amr Abu Aza", + "avatar_url": "https://avatars.githubusercontent.com/u/30035105?v=4", + "profile": "https://github.com/amrabuaza", + "contributions": [ + "plugin" + ] + }, + { + "login": "jerboa88", + "name": "John Goodliff", + "avatar_url": "https://avatars.githubusercontent.com/u/9030780?v=4", + "profile": "http://johng.io", + "contributions": [ + "plugin" + ] + }, + { + "login": "DiwashDev", + "name": "Diwash Dev", + "avatar_url": "https://avatars.githubusercontent.com/u/182864159?v=4", + "profile": "https://github.com/DiwashDev", + "contributions": [ + "plugin" + ] + }, + { + "login": "matthiez", + "name": "André", + "avatar_url": "https://avatars.githubusercontent.com/u/12965261?v=4", + "profile": "https://www.seven.io", + "contributions": [ + "plugin" + ] + }, + { + "login": "loudotdigital", + "name": "Lou | Digital Marketing", + "avatar_url": "https://avatars.githubusercontent.com/u/7611772?v=4", + "profile": "https://github.com/loudotdigital", + "contributions": [ + "plugin" + ] + }, + { + "login": "maarteNNNN", + "name": "Maarten Coppens", + "avatar_url": "https://avatars.githubusercontent.com/u/14275291?v=4", + "profile": "https://github.com/maarteNNNN", + "contributions": [ + "plugin" + ] + }, + { + "login": "mahmuthamet", + "name": "Mahmoud Hamed", + "avatar_url": "https://avatars.githubusercontent.com/u/90776946?v=4", + "profile": "https://github.com/mahmuthamet", + "contributions": [ + "plugin" + ] + }, + { + "login": "Blightwidow", + "name": "Theo Dammaretz", + "avatar_url": "https://avatars.githubusercontent.com/u/14098167?v=4", + "profile": "https://dammaretz.com", + "contributions": [ + "plugin" + ] + }, + { + "login": "s31w4n", + "name": "s31w4n", + "avatar_url": "https://avatars.githubusercontent.com/u/63353528?v=4", + "profile": "https://github.com/s31w4n", + "contributions": [ + "doc", + "code", + "plugin" + ] + }, + { + "login": "abdulrahmanmajid", + "name": "Abdul Rahman", + "avatar_url": "https://avatars.githubusercontent.com/u/94991678?v=4", + "profile": "https://kallabot.com", + "contributions": [ + "plugin" + ] + }, + { + "login": "coat", + "name": "Kent Smith", + "avatar_url": "https://avatars.githubusercontent.com/u/1661?v=4", + "profile": "https://github.com/coat", + "contributions": [ + "plugin" + ] + }, + { + "login": "ArvindEnvoy", + "name": "Arvind Ramesh", + "avatar_url": "https://avatars.githubusercontent.com/u/25014185?v=4", + "profile": "https://github.com/ArvindEnvoy", + "contributions": [ + "code" + ] + }, + { + "login": "valentin-mourtialon", + "name": "valentin-mourtialon", + "avatar_url": "https://avatars.githubusercontent.com/u/88686764?v=4", + "profile": "https://github.com/valentin-mourtialon", + "contributions": [ + "plugin" + ] + }, + { + "login": "psgpsg16", + "name": "psgpsg16", + "avatar_url": "https://avatars.githubusercontent.com/u/188385621?v=4", + "profile": "https://github.com/psgpsg16", + "contributions": [ + "plugin" + ] + }, + { + "login": "mariiawidrpay", + "name": "Mariia Shyn", + "avatar_url": "https://avatars.githubusercontent.com/u/110456120?v=4", + "profile": "https://github.com/mariiawidrpay", + "contributions": [ + "plugin" + ] + }, + { + "login": "joshuaheslin", + "name": "Joshua Heslin", + "avatar_url": "https://avatars.githubusercontent.com/u/48037470?v=4", + "profile": "https://github.com/joshuaheslin", + "contributions": [ + "plugin" + ] + }, + { + "login": "ahmad-swanblocks", + "name": "Ahmad", + "avatar_url": "https://avatars.githubusercontent.com/u/165162455?v=4", + "profile": "https://github.com/ahmad-swanblocks", + "contributions": [ + "plugin" + ] + }, + { + "login": "danielpoonwj", + "name": "Daniel Poon", + "avatar_url": "https://avatars.githubusercontent.com/u/17039704?v=4", + "profile": "https://github.com/danielpoonwj", + "contributions": [ + "code" + ] + }, + { + "login": "Kevinyu-alan", + "name": "Kévin Yu", + "avatar_url": "https://avatars.githubusercontent.com/u/198612963?v=4", + "profile": "https://github.com/Kevinyu-alan", + "contributions": [ + "plugin" + ] + }, + { + "login": "flex-yeongeun", + "name": "노영은", + "avatar_url": "https://avatars.githubusercontent.com/u/186537288?v=4", + "profile": "https://github.com/flex-yeongeun", + "contributions": [ + "plugin" + ] + }, + { + "login": "reemayoush", + "name": "reemayoush", + "avatar_url": "https://avatars.githubusercontent.com/u/168414383?v=4", + "profile": "https://github.com/reemayoush", + "contributions": [ + "plugin" + ] + }, + { + "login": "briceflaceliere", + "name": "Brice", + "avatar_url": "https://avatars.githubusercontent.com/u/5811531?v=4", + "profile": "https://github.com/briceflaceliere", + "contributions": [ + "security" + ] + }, + { + "login": "mg-wunna", + "name": "Mg Wunna", + "avatar_url": "https://avatars.githubusercontent.com/u/63114419?v=4", + "profile": "https://mg-wunna.github.io/mg-wunna/", + "contributions": [ + "plugin" + ] + }, + { + "login": "hakrsh", + "name": "Harikrishnan U M", + "avatar_url": "https://avatars.githubusercontent.com/u/61736905?v=4", + "profile": "https://www.linkedin.com/in/harikrishnanum/", + "contributions": [ + "bug" + ] + }, + { + "login": "perrine-pullicino-alan", + "name": "perrine-pullicino-alan", + "avatar_url": "https://avatars.githubusercontent.com/u/143406842?v=4", + "profile": "https://github.com/perrine-pullicino-alan", + "contributions": [ + "plugin" + ] + }, + { + "login": "kaovilai", + "name": "Tiger Kaovilai", + "avatar_url": "https://avatars.githubusercontent.com/u/11228024?v=4", + "profile": "http://kaovilai.pw", + "contributions": [ + "code" + ] + }, + { + "login": "CarefulGuru", + "name": "CarefulGuru", + "avatar_url": "https://avatars.githubusercontent.com/u/141072854?v=4", + "profile": "https://github.com/CarefulGuru", + "contributions": [ + "plugin" + ] + }, + { + "login": "AnkitSharmaOnGithub", + "name": "Ankit Kumar Sharma", + "avatar_url": "https://avatars.githubusercontent.com/u/53289186?v=4", + "profile": "https://github.com/AnkitSharmaOnGithub", + "contributions": [ + "plugin" + ] + }, + { + "login": "nhnansari", + "name": "Naeem Hassan", + "avatar_url": "https://avatars.githubusercontent.com/u/116841234?v=4", + "profile": "https://github.com/nhnansari", + "contributions": [ + "plugin" + ] + }, + { + "login": "TimPetricola", + "name": "Tim Petricola", + "avatar_url": "https://avatars.githubusercontent.com/u/674084?v=4", + "profile": "http://timpetricola.com", + "contributions": [ + "code" + ] + }, + { + "login": "amaan-ai20", + "name": "Amaan", + "avatar_url": "https://avatars.githubusercontent.com/u/188329978?v=4", + "profile": "https://github.com/amaan-ai20", + "contributions": [ + "plugin" + ] + }, + { + "login": "mllopart", + "name": "Marc Llopart", + "avatar_url": "https://avatars.githubusercontent.com/u/1257083?v=4", + "profile": "https://www.marcllopartriera.com", + "contributions": [ + "plugin" + ] + }, + { + "login": "onyedikachi-david", + "name": "David Anyatonwu", + "avatar_url": "https://avatars.githubusercontent.com/u/51977119?v=4", + "profile": "https://github.com/onyedikachi-david", + "contributions": [ + "plugin" + ] + }, + { + "login": "neo773", + "name": "neo773", + "avatar_url": "https://avatars.githubusercontent.com/u/62795688?v=4", + "profile": "https://huzef.com", + "contributions": [ + "plugin" + ] + }, + { + "login": "Daniel-Klippa", + "name": "Daniel-Klippa", + "avatar_url": "https://avatars.githubusercontent.com/u/207180643?v=4", + "profile": "https://github.com/Daniel-Klippa", + "contributions": [ + "code" + ] + }, + { + "login": "krushnarout", + "name": "Krushna Kanta Rout", + "avatar_url": "https://avatars.githubusercontent.com/u/129386740?v=4", + "profile": "https://github.com/krushnarout", + "contributions": [ + "plugin" + ] + }, + { + "login": "nish17", + "name": "Nimesh Solanki", + "avatar_url": "https://avatars.githubusercontent.com/u/12984120?v=4", + "profile": "https://www.snimesh.com", + "contributions": [ + "code" + ] + }, + { + "login": "rimjhimyadav", + "name": "Rimjhim Yadav", + "avatar_url": "https://avatars.githubusercontent.com/u/187646079?v=4", + "profile": "https://github.com/rimjhimyadav", + "contributions": [ + "plugin" + ] + }, + { + "login": "gs03-dev", + "name": "gs03", + "avatar_url": "https://avatars.githubusercontent.com/u/70076620?v=4", + "profile": "https://github.com/gs03-dev", + "contributions": [ + "plugin" + ] + }, + { + "login": "Kunal-Darekar", + "name": "Kunal Darekar", + "avatar_url": "https://avatars.githubusercontent.com/u/150500530?v=4", + "profile": "https://github.com/Kunal-Darekar", + "contributions": [ + "plugin" + ] + }, + { + "login": "Sanket6652", + "name": "Sanket6652", + "avatar_url": "https://avatars.githubusercontent.com/u/119039046?v=4", + "profile": "https://github.com/Sanket6652", + "contributions": [ + "plugin" + ] + }, + { + "login": "Ani-4x", + "name": "Animesh", + "avatar_url": "https://avatars.githubusercontent.com/u/174266491?v=4", + "profile": "https://github.com/Ani-4x", + "contributions": [ + "plugin" + ] + }, + { + "login": "DerekJDev", + "name": "Derek Johnson", + "avatar_url": "https://avatars.githubusercontent.com/u/13419128?v=4", + "profile": "https://tarvent.com", + "contributions": [ + "plugin" + ] + }, + { + "login": "mamiefurax", + "name": "MamieFurax", + "avatar_url": "https://avatars.githubusercontent.com/u/3955802?v=4", + "profile": "https://github.com/mamiefurax", + "contributions": [ + "plugin" + ] + }, + { + "login": "jadhavharshh", + "name": "Harsh Jadhav", + "avatar_url": "https://avatars.githubusercontent.com/u/182950168?v=4", + "profile": "https://github.com/jadhavharshh", + "contributions": [ + "code" + ] + }, + { + "login": "lucaslimasouza", + "name": "Lucas Lima de Souza", + "avatar_url": "https://avatars.githubusercontent.com/u/1576846?v=4", + "profile": "http://twitter.com/lucaslima_souza", + "contributions": [ + "plugin" + ] + }, + { + "login": "BenjaminAlan", + "name": "Benjamin Benouarka", + "avatar_url": "https://avatars.githubusercontent.com/u/42831606?v=4", + "profile": "https://github.com/BenjaminAlan", + "contributions": [ + "code" + ] + }, + { + "login": "cjwooo", + "name": "Chris Wu", + "avatar_url": "https://avatars.githubusercontent.com/u/4491213?v=4", + "profile": "https://www.linkedin.com/in/chris-wu/", + "contributions": [ + "code" + ] + }, + { + "login": "prasanna2000-max", + "name": "Prasanna R", + "avatar_url": "https://avatars.githubusercontent.com/u/61037314?v=4", + "profile": "https://github.com/prasanna2000-max", + "contributions": [ + "code" + ] + }, + { + "login": "AdminCraftHD", + "name": "AdminCraftHD", + "avatar_url": "https://avatars.githubusercontent.com/u/33310274?v=4", + "profile": "https://github.com/AdminCraftHD", + "contributions": [ + "code" + ] + }, + { + "login": "tumrabert", + "name": "Tanakit Phentun", + "avatar_url": "https://avatars.githubusercontent.com/u/38305310?v=4", + "profile": "https://tumrabertworld.vercel.app/resume", + "contributions": [ + "code" + ] + }, + { + "login": "marapper", + "name": "Sergey Bondar", + "avatar_url": "https://avatars.githubusercontent.com/u/1397054?v=4", + "profile": "https://github.com/marapper", + "contributions": [ + "plugin" + ] + } + ], + "commitType": "docs" +} diff --git a/.cursor/mcp.json b/.cursor/mcp.json new file mode 100644 index 0000000..ba9d53a --- /dev/null +++ b/.cursor/mcp.json @@ -0,0 +1,7 @@ +{ + "mcpServers": { + "nx-mcp": { + "url": "http://localhost:9536/sse" + } + } +} \ No newline at end of file diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..9623556 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,43 @@ +# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 20, 18, 16, 14, 20-bullseye, 18-bullseye, 16-bullseye, 14-bullseye, 20-buster, 18-buster, 16-buster, 14-buster +ARG VARIANT=20-bullseye +FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment if you want to install an additional version of node using nvm +# ARG EXTRA_NODE_VERSION=10 +# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" + +# [Optional] Uncomment if you want to install more global node modules +# RUN su node -c "npm install -g " + +RUN npm install -g nx +RUN apt-get update && apt-get install -y git + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + locales \ + locales-all \ + libcap-dev \ + && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y poppler-utils poppler-data + +# Set the locale +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +COPY default.cf /usr/local/etc/isolate + +RUN npm i -g npm@9.9.3 +RUN npm i -g pnpm@9.15.0 +RUN npm i -g cross-env@7.0.3 + +RUN pnpm config set store-dir /root/.local/share/pnpm/store + +# Update to use Node.js 20 packages +RUN pnpm store add @tsconfig/node20@20.1.4 +RUN pnpm store add @types/node@20.14.8 +RUN pnpm store add typescript@4.9.4 diff --git a/.devcontainer/codespaces.sh b/.devcontainer/codespaces.sh new file mode 100644 index 0000000..1d8f4b7 --- /dev/null +++ b/.devcontainer/codespaces.sh @@ -0,0 +1,13 @@ +echo "Running Setup for Codespaces" + +type -p curl >/dev/null || sudo apt install curl -y +curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \ +&& sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \ +&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ +&& sudo apt update \ +&& sudo apt install gh -y + +gh codespace ports visibility 3000:public -c $CODESPACE_NAME +gh codespace ports visibility 4200:public -c $CODESPACE_NAME +export BACKEND_URL=$(gh codespace ports -c $CODESPACE_NAME --json sourcePort,browseUrl | jq -r '.[] | select(.sourcePort == 3000) | .browseUrl') +sed -i "s|apiUrl: 'http://localhost:3000/v1'|apiUrl: '${BACKEND_URL}/v1'|g" /workspace/packages/ui/common/src/lib/environments/environment.ts diff --git a/.devcontainer/default.cf b/.devcontainer/default.cf new file mode 100644 index 0000000..eebd421 --- /dev/null +++ b/.devcontainer/default.cf @@ -0,0 +1,24 @@ +# This is a configuration file for Isolate + +# All sandboxes are created under this directory. +# To avoid symlink attacks, this directory and all its ancestors +# must be writeable only to root. +box_root = /var/local/lib/isolate + +# Root of the control group hierarchy +cg_root = /sys/fs/cgroup + +# If the following variable is defined, the per-box cgroups +# are created as sub-groups of the named cgroup +#cg_parent = boxes + +# Block of UIDs and GIDs reserved for sandboxes +first_uid = 60000 +first_gid = 60000 +num_boxes = 1000 + +# Per-box settings of the set of allowed CPUs and NUMA nodes +# (see linux/Documentation/cgroups/cpusets.txt for precise syntax) + +#box0.cpus = 4-7 +#box0.mems = 1 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..d282b72 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,33 @@ +// Update the VARIANT arg in docker-compose.yml to pick a Node.js version +{ + "name": "Activepieces Dev", + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspace", + + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "cipchk.cssrem", + "huizhou.githd" + ] + } + }, + "forwardPorts": [3000, 4200, 5432], + "postAttachCommand": "/bin/bash .devcontainer/setup.sh", + "hostRequirements": { + "cpus": 4, + "memory": "8gb" + }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // This can be used to network with other containers or with the host. + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "yarn install", + "remoteUser": "root", + "postCreateCommand": "npm ci" +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..9b51471 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,54 @@ +version: '3.8' + +networks: + activepieces: + driver: bridge + +services: + app: + privileged: true + build: + context: . + dockerfile: Dockerfile + + volumes: + - ..:/workspace:cached + + # Connect the app to the same network as db and redis + networks: + - activepieces + + # Overrides default command so things don't shut down after the process ends. + command: sleep infinity + + # Uncomment the next line to use a non-root user for all processes. + # user: node + + # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. + # (Adding the "ports" property to this file will not forward from a Codespace.) + + db: + image: postgres:14.4 + environment: + POSTGRES_DB: activepieces + POSTGRES_USER: postgres + POSTGRES_PASSWORD: A79Vm5D4p2VQHOp2gd5 + networks: + - activepieces + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + + redis: + image: redis:7.0.7 + networks: + - activepieces + volumes: + - redis_data:/data + ports: + - "6379:6379" + +volumes: + postgres_data: + redis_data: diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh new file mode 100644 index 0000000..15e3da0 --- /dev/null +++ b/.devcontainer/setup.sh @@ -0,0 +1,8 @@ +# exit this file if we are not in Codespaces +if [ -z "${CODESPACES}" ]; then + exit 0 +fi + +echo "Running Setup for Codespaces" + +sh .devcontainer/codespaces.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2db1b5a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +.angular +.dockerignore +.env +.git +.gitattributes +.github +.history +.idea +*.log +*.md +.vscode +builds +deploy +Dockerfile +dist +docs +node_modules diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..22b69fa --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ceb1dee --- /dev/null +++ b/.env.example @@ -0,0 +1,28 @@ +## It's advisable to consult the documentation and use the tools/deploy.sh to generate the passwords, keys, instead of manually filling them. + +AP_ENGINE_EXECUTABLE_PATH=dist/packages/engine/main.js + +## Random Long Password (Optional for community edition) +AP_API_KEY= + +## 256 bit encryption key, 32 hex character +AP_ENCRYPTION_KEY= + +## JWT Secret +AP_JWT_SECRET= + +AP_ENVIRONMENT=prod +AP_FRONTEND_URL=http://localhost:8080 +AP_WEBHOOK_TIMEOUT_SECONDS=30 +AP_TRIGGER_DEFAULT_POLL_INTERVAL=5 +AP_POSTGRES_DATABASE=activepieces +AP_POSTGRES_HOST=postgres +AP_POSTGRES_PORT=5432 +AP_POSTGRES_USERNAME=postgres +AP_POSTGRES_PASSWORD= +AP_EXECUTION_MODE=UNSANDBOXED +AP_REDIS_HOST=redis +AP_REDIS_PORT=6379 +AP_FLOW_TIMEOUT_SECONDS=600 +AP_TELEMETRY_ENABLED=true +AP_TEMPLATES_SOURCE_URL="https://cloud.activepieces.com/api/v1/flow-templates" diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..6375042 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +node_modules +deploy \ No newline at end of file diff --git a/.eslintrc.base.json b/.eslintrc.base.json new file mode 100644 index 0000000..9ca2e83 --- /dev/null +++ b/.eslintrc.base.json @@ -0,0 +1,35 @@ +{ + "root": true, + "ignorePatterns": ["**/*"], + "plugins": ["@nx"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": { + "@nx/enforce-module-boundaries": [ + "error", + { + "enforceBuildableLibDependency": true, + "allow": [], + "depConstraints": [ + { + "sourceTag": "*", + "onlyDependOnLibsWithTags": ["*"] + } + ] + } + ] + } + }, + { + "files": ["*.ts", "*.tsx"], + "extends": ["plugin:@nx/typescript"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "extends": ["plugin:@nx/javascript"], + "rules": {} + } + ] +} diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..2f0b960 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,54 @@ +{ + "root": true, + "ignorePatterns": ["**/*", "deploy/**/*"], + "plugins": ["@nx"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": { + "@nx/enforce-module-boundaries": [ + "error", + { + "enforceBuildableLibDependency": true, + "allow": [], + "depConstraints": [ + { + "sourceTag": "*", + "onlyDependOnLibsWithTags": ["*"] + } + ] + } + ], + "no-restricted-imports": [ + "error", + { + "patterns": ["lodash", "lodash/*"] + } + ] + } + }, + { + "files": ["*.ts", "*.tsx"], + "extends": ["plugin:@nx/typescript"], + "rules": { + "@typescript-eslint/no-extra-semi": "error", + "no-extra-semi": "off" + } + }, + { + "files": ["*.js", "*.jsx"], + "extends": ["plugin:@nx/javascript"], + "rules": { + "@typescript-eslint/no-extra-semi": "error", + "no-extra-semi": "off" + } + }, + { + "files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"], + "env": { + "jest": true + }, + "rules": {} + } + ] +} diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..56a73ea --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,103 @@ +The primary source of the Code of Conduct is here. + +## 1. Purpose + +A primary goal of the Activepieces community is to support you and your business in the development, use and implementation of Activepieces. It’s to be inclusive and add value to the largest number of participants, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all. + +This code of conduct outlines our expectations for all those who participate in our community, whether in-person or online, as well as the consequences for unacceptable behavior. + +Your participation is contingent upon following these guidelines in all Activepieces activities, including but not limited to: + +* Using Activepieces community resources. +* Working with other Activepiecesians and other Activepieces community participants whether virtually or co-located. +* Representing Activepieces at public events. +* Representing Activepieces in social media (official accounts, personal accounts, Facebook pages and groups). +* Participating in Activepieces sprints and training events. +* Participating in Activepieces-related forums, mailing lists, wikis, websites, chat channels, bugs, group or person-to-person meetings, and Activepieces-related correspondence. + +We invite all those who participate in Activepieces activities online to help us create safe and positive experiences for everyone, everywhere. + + +## 2. Open Source & Culture Citizenship + +A supplemental goal of this Code of Conduct is to increase open source and culture citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community. + +Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society. + +If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, please recognize their efforts. + +## 3. Welcoming to all +We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience or job role, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, national origin, citizenship and immigration status, neurodiversity, mental health or socio-economic status. + + +## 4. Expected Behavior + +The following behaviors are expected and requested of all community members: + +* Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community. +* Exercise consideration and respect in your speech and actions. +* Attempt collaboration before conflict. +* Guide conversations toward issue resolution. +* Refrain from demeaning, discriminatory, or harassing behavior and speech. + +Alert Activepieces team members if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential. + +## 5. Unacceptable Behavior + +The following behaviors are considered harassment and are unacceptable within our community: + +* **Violence and Threats of Violence** are not acceptable - online or offline. This includes incitement of violence toward any individual, including encouraging a person to commit self-harm. This also includes posting or threatening to post other people’s personally identifying information (“doxxing”) online. +* **Public or private harassment** is never acceptable in any form. +* **Personal Attacks** Conflicts will inevitably arise, but frustration should never turn into a personal attack. It is not okay to insult, demean or belittle others. Attacking someone for their opinions, beliefs and ideas is not acceptable. It is important to speak directly when we disagree and when we think we need to improve, but such discussions must be conducted respectfully and professionally, remaining focused on the issue at hand. +* **Derogatory Language** Hurtful or harmful language is never acceptable in any context related to: background, family status, gender, gender identity or expression, marital status, sex, sexual orientation, personal appearance, body size, native language, age, ability, neurodiversity, mental health, race and/or ethnicity, national origin, citizenship and immigration status, socioeconomic status, religion, geographic location. +* **Unwelcome Sexual Attention or Physical Contact** Unwelcome sexual attention or unwelcome physical contact is not acceptable. This includes sexualized comments, jokes or imagery in interactions, communications or presentation materials, as well as inappropriate touching, groping, or sexual advances. This includes touching a person without permission, including sensitive areas such as their hair, pregnant stomach, mobility device (wheelchair, scooter, etc) or tattoos. This also includes physically blocking or intimidating another person. Physical contact or simulated physical contact (such as emojis like “kiss”) without affirmative consent is not acceptable. This includes sharing or distribution of sexualized images or text. +* **Disruptive Behavior** Sustained disruption of events, forums, or meetings, including talks and presentations, will not be tolerated. This includes spamming community discussions with the solicitation of unwanted products or services. +* **Influencing Disruptive Behavior** We will treat influencing or leading such activities the same way we treat the activities themselves, and thus the same consequences apply. +* **Corporate Promotions** Sharing of demo/trial/landing page links and other corporate promotions are never permitted unless explicitly requested by a community member. The only exceptions are that the moderated [Commercial forum category](https://forum.Activepieces.org/c/commercial) may be used to promote opportunities which may be relevant for members of the community (for example job opportunities, freelance gigs) and Activepieces Community Partners may promote their products and services on their partners page. +* **Scraping contacts** by name or any other personally identifiable information for unsolicited communication is never acceptable in any form. + +## 6. Consequences of Unacceptable Behavior + +Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated. + +Anyone asked to stop unacceptable behavior is expected to comply immediately. + +If a community member engages in unacceptable behavior, we may take any action deemed appropriate, up to and including a temporary ban or permanent expulsion from the community without warning. Examples of sanctions which may be applied include but is not limited to: +* Verbal warnings. +* Written warnings. +* Temporary absence from participation. +* Long-term absence from participation. +* Being required to follow a conduct agreement that dictates the process of returning to the community. + + +## 7. Reporting Guidelines +If you are subject to or witness unacceptable behavior, or have any other concerns, please notify us as soon as possible by emailing info@activepieces.com, or contacting a Activepieces team member on the specific platform. + +Processes for dealing with breaches of the Code of Conduct can be found [here][coc-breaches]. + +## 8. Addressing Grievances +Only permanent resolutions (such as bans) may be appealed. To appeal a decision, contact the Activepieces team at info@activepieces.com with your appeal and the team will review the situation. + +## 9. Scope +We expect all community participants (contributors, moderators and other guests) to abide by this Code of Conduct in all community venues–online and in-person–as well as in all one-on-one communications pertaining to community affairs. + +While this code of conduct is specifically aimed at Activepieces’s official resources and community, we recognize that it is possible for actions taken outside of Activepieces’s official online or in person spaces to have a deep impact on community health. + +Resources or incidents which break this code of conduct for any reason in a non-Activepieces community location will be considered in the same way as resources or incidents from owned channels, and subject to the same sanctions. + +## 10. Contact info +For more information, please contact info@activepieces.com. + +## 11. License and attribution +This Code of Conduct is directly adapted from the Stumptown Syndicate and distributed under a [Creative Commons Attribution-ShareAlike license][cc-by-sa]. + +Additional text from [Mozilla Community Participation Guidelines][mozilla-guidelines] distributed under a [Creative Commons Attribution-ShareAlike license][cc-by-sa]. + +Reviewed and updated using the [Mozilla Code of Conduct Assessment Tool][mozilla-tool]. + +[coc-breaches]: +[mozilla-guidelines]: +[cc-by-sa]: +[mozilla-tool]: + +(Code of Conduct is subject to change without notice). diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..01c4392 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]: the code piece fails" +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..11fc491 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/mcp_bounty.md b/.github/ISSUE_TEMPLATE/mcp_bounty.md new file mode 100644 index 0000000..c16060b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/mcp_bounty.md @@ -0,0 +1,60 @@ +--- +name: MCP Bounty +about: Request a new Piece for Activepieces +title: '[Piece Request] ' +labels: ['pieces'] +assignees: '' + +--- + +(Replace everything other than the titles that start with ##) + +## 🧩 Product Overview +What does your product do? Who uses it? + +--- + +## ⚙️ Actions +These are the things you want AI agents to be able to do with your product. +They’ll also be available for automations in Activepieces. + +Examples: +- Create Contact +- Send Message +- Update Deal + +--- + +## ⏱️ Triggers +Triggers are only used in automation. +What events in your product should start a workflow? + +Examples: +- New Contact Created +- Ticket Closed + +--- + +## 📚 API Reference +Link to your public API docs or developer portal. + +--- + +## 🧪 Test Account Access +How can contributors test your API? (Free trial, sandbox credentials, etc.) + +--- + +## 💡 Extra Notes +Anything else worth mentioning? (Edge cases, beta features, known limitations) + +--- + +## 🔄 Alternatives Explored (Optional) +Have you used other platforms or approaches for integration? + +--- + +## 📬 Contact for Contributors (Optional) +If you’re not actively monitoring this issue, how can contributors reach you? +Example: yourname@yourcompany.com diff --git a/.github/ISSUE_TEMPLATE/piece-request.md b/.github/ISSUE_TEMPLATE/piece-request.md new file mode 100644 index 0000000..416bfeb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/piece-request.md @@ -0,0 +1,14 @@ +--- +name: Piece Request +about: Request new Action / Trigger +title: '' +labels: pieces +assignees: '' + +--- + +**Describe your usecase.** +A clear and concise description how exactly you want to use the piece. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions you've considered. diff --git a/.github/pre-release-drafter.yml b/.github/pre-release-drafter.yml new file mode 100644 index 0000000..046c94e --- /dev/null +++ b/.github/pre-release-drafter.yml @@ -0,0 +1,37 @@ +include-pre-releases: true +exclude-labels: + - 'skip-changelog' + - 'release' + - 'pre-release' +categories: + - title: "⛓️‍💥 Breaking Changes" + labels: + - "⛓️‍💥 breaking-change" + - title: "✨ Exciting New Features" + labels: + - "🌟 feature" + - title: "🧩 Pieces" + labels: + - "🔌 pieces" + - title: "🛠️ Piece Framework" + labels: + - "🛠️ piece-framework" + - title: "🐞 Bug Fixes" + labels: + - "🐛 bug" + - title: "🎨 Enhancements & Polish" + labels: + - "✨ polishing" + - title: "📚 Documentation" + labels: + - "📚 documentation" + - title: "🧹 Maintenance" + labels: + - "🧹 clean up" + +template: | + + $CHANGES + + ## Thanks ❤️ + $CONTRIBUTORS \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..771e4e6 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,17 @@ +## What does this PR do? + + + + +### Explain How the Feature Works + + + +### Relevant User Scenarios + + + + + + +Fixes # (issue) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..c60f415 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,40 @@ +include-pre-releases: false +exclude-labels: + - 'skip-changelog' + - 'release' + - 'pre-release' +categories: + - title: "⛓️‍💥 Breaking Changes" + labels: + - "⛓️‍💥 breaking-change" + - title: "✨ Exciting New Features" + labels: + - "🌟 feature" + - title: "🧩 Pieces" + labels: + - "🔌 pieces" + - title: "🛠️ Piece Framework" + labels: + - "🛠️ piece-framework" + - title: "🐞 Bug Fixes" + labels: + - "🐛 bug" + - title: "🎨 Enhancements & Polish" + labels: + - "✨ polishing" + - title: "📚 Documentation" + labels: + - "📚 documentation" + - title: "🧹 Maintenance & Dev Experience" + labels: + - "🧹 clean up" + - title: "Other Changes" + labels: + - "*" + +template: | + + $CHANGES + + ## Thanks ❤️ + $CONTRIBUTORS \ No newline at end of file diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..c467e79 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,17 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 10 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 5 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security +# Label to use when marking an issue as stale +staleLabel: Automatically Closed +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: true diff --git a/.github/workflows/automate-deploy.yml b/.github/workflows/automate-deploy.yml new file mode 100644 index 0000000..011e666 --- /dev/null +++ b/.github/workflows/automate-deploy.yml @@ -0,0 +1,69 @@ +name: Automate Deploy + +on: + workflow_dispatch: + pull_request: + types: [merged] + branches: + - main + merged: true + +jobs: + Release: + if: contains(github.event.pull_request.labels.*.name, 'auto-deploy') || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Set RELEASE env var from package.json + run: echo RELEASE=$(node --print "require('./package.json').version") >> $GITHUB_ENV + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Depot CLI + uses: depot/setup-action@v1 + + - name: Build and push + uses: depot/build-push-action@v1 + with: + project: du7O4b0e8P + token: ${{ secrets.DEPOT_PROJECT_TOKEN }} + context: . + file: ./Dockerfile + platforms: | + linux/amd64 + push: true + tags: | + ghcr.io/activepieces/activepieces-cloud:${{ env.RELEASE }}.${{ github.sha }}.beta + + - name: Configure SSH + run: | + mkdir -p ~/.ssh/ + echo "$SSH_KEY" > ~/.ssh/ops.key + chmod 600 ~/.ssh/ops.key + cat >>~/.ssh/config <> $GITHUB_ENV + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile + platforms: | + linux/amd64 + push: true + tags: | + ghcr.io/activepieces/activepieces-cloud:${{ env.RELEASE }}.${{ github.sha }} diff --git a/.github/workflows/build-cloud-nx.yml b/.github/workflows/build-cloud-nx.yml new file mode 100644 index 0000000..ca8f743 --- /dev/null +++ b/.github/workflows/build-cloud-nx.yml @@ -0,0 +1,79 @@ +name: CI +on: + pull_request: + +permissions: + actions: read + contents: read + +jobs: + main: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-node@v3 + with: + node-version: 20 + cache: 'npm' + + - run: npx nx-cloud start-ci-run --distribute-on="3 linux-large-js" --agents + + - run: npm ci + + - run: npx nx reset + + - uses: nrwl/nx-set-shas@v4 + + - name: List all nx targets + run: npx nx show projects --all + + - name: Get changed files + id: changed-files + run: echo "files=$(git diff --name-only HEAD origin/main | tr '\n' ' ')" >> $GITHUB_OUTPUT + + - name: Check if framework or common pieces are changed + id: check-framework-common + run: | + CHANGED_FILES="${{ steps.changed-files.outputs.files }}" + if echo "$CHANGED_FILES" | grep -q "community/framework\|community/common"; then + echo "framework_or_common_changed=true" >> $GITHUB_OUTPUT + else + echo "framework_or_common_changed=false" >> $GITHUB_OUTPUT + fi + + - name: Extract pieces projects from changed files + id: extract-pieces + run: | + PIECES=$(echo "${{ steps.changed-files.outputs.files }}" | grep -o "packages/pieces/[^/]*/[^/]*/" | awk -F'/' '{print "pieces-" $4}' | sort -u | tr '\n' ',' | sed 's/,$//') + echo "pieces_projects=$PIECES" >> $GITHUB_OUTPUT + + - name: Lint affected projects excluding pieces + run: npx nx affected --target=lint --exclude="pieces-*" --agents --parallel + - name: Lint changed pieces projects + if: steps.extract-pieces.outputs.pieces_projects != '' && steps.check-framework-common.outputs.framework_or_common_changed == 'false' + run: npx nx run-many --target=lint --projects="${{ steps.extract-pieces.outputs.pieces_projects }}" --agents --parallel + + - name: Lint all pieces projects + if: steps.check-framework-common.outputs.framework_or_common_changed == 'true' + run: npx nx run-many --target=lint --projects="pieces-*" --agents --parallel + + - name: Build affected projects excluding pieces + run: npx nx affected --target=build -c production --exclude="pieces-*" --agents --parallel + + - name: Build changed pieces projects + if: steps.extract-pieces.outputs.pieces_projects != '' && steps.check-framework-common.outputs.framework_or_common_changed == 'false' + run: npx nx run-many --target=build -c production --projects="${{ steps.extract-pieces.outputs.pieces_projects }}" --agents --parallel + + - name: Build all pieces projects + if: steps.check-framework-common.outputs.framework_or_common_changed == 'true' + run: npx nx run-many --target=build -c production --projects="pieces-*" --agents --parallel + + - name: Run all tests in parallel + run: | + npx nx run-many --target=test --projects=engine,shared --agents --parallel & + npx nx run server-api:test-ce & + npx nx run server-api:test-ee & + npx nx run server-api:test-cloud & + wait diff --git a/.github/workflows/closed-issue-reply.yaml b/.github/workflows/closed-issue-reply.yaml new file mode 100644 index 0000000..799f66d --- /dev/null +++ b/.github/workflows/closed-issue-reply.yaml @@ -0,0 +1,21 @@ +name: Closed Issue Message +on: + issues: + types: [closed] +permissions: {} +jobs: + auto_comment: + permissions: + issues: write # to comment on issues (aws-actions/closed-issue-message) + + runs-on: ubuntu-latest + if: github.repository_owner == 'activepieces' + steps: + - uses: aws-actions/closed-issue-message@v1 + with: + # These inputs are both required + repo-token: "${{ secrets.GITHUB_TOKEN }}" + message: | + ### ⚠️COMMENT VISIBILITY WARNING⚠️ + Comments on closed issues are hard for our team to see. + If this issue is continuing with the latest stable version of Activepieces, please open a new issue that references this one. \ No newline at end of file diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml new file mode 100644 index 0000000..f1a7c13 --- /dev/null +++ b/.github/workflows/crowdin.yml @@ -0,0 +1,37 @@ +name: Crowdin Action + +on: + push: + branches: [ main ] + workflow_dispatch: + +jobs: + synchronize-with-crowdin: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: crowdin action + uses: crowdin/github-action@v2 + with: + # Configuration file + config: 'crowdin.yml' + upload_sources: true + upload_translations: false + download_translations: true + localization_branch_name: l10n_crowdin_translations + create_pull_request: true + pull_request_title: 'New Crowdin Translations' + pull_request_body: 'New Crowdin translations by [Crowdin GH Action](https://github.com/crowdin/github-action)' + pull_request_base_branch_name: 'main' + env: + # A classic GitHub Personal Access Token with the 'repo' scope selected (the user should have write access to the repository). + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # A numeric ID, found at https://crowdin.com/project//tools/api + CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} + + # Visit https://crowdin.com/settings#api-key to create this token + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/pre-release-changelog.yml b/.github/workflows/pre-release-changelog.yml new file mode 100644 index 0000000..c60c6e8 --- /dev/null +++ b/.github/workflows/pre-release-changelog.yml @@ -0,0 +1,33 @@ +name: Write Pre Release notes + +on: + pull_request: + types: [opened, reopened, synchronize, edited, closed, labeled] + +permissions: + contents: write + pull-requests: write + +jobs: + Release: + if: contains(github.event.pull_request.labels.*.name, 'pre-release') + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Set RELEASE env var from package.json + run: echo RELEASE=$(node --print "require('./package.json').rcVersion") >> $GITHUB_ENV + + - name: Create release notes + uses: release-drafter/release-drafter@v5 + with: + config-name: pre-release-drafter.yml + commitish: main + prerelease: true + tag: ${{ env.RELEASE }} + name: ${{ env.RELEASE }} + version: ${{ env.RELEASE }} + latest: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-ap-base.yml b/.github/workflows/release-ap-base.yml new file mode 100644 index 0000000..d1947c9 --- /dev/null +++ b/.github/workflows/release-ap-base.yml @@ -0,0 +1,43 @@ +name: Release AP base + +on: + workflow_dispatch: + inputs: + tag: + description: 'image tag' + required: true + +jobs: + Release-AP-Base: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Fail if tag already exists + run: "! docker manifest inspect activepieces/ap-base:${{ inputs.tag }}" + + - name: Set up Depot CLI + uses: depot/setup-action@v1 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push + uses: depot/build-push-action@v1 + with: + project: du7O4b0e8P + token: ${{ secrets.DEPOT_PROJECT_TOKEN }} + context: . + file: ./ap-base.dockerfile + platforms: | + linux/amd64 + linux/arm64 + linux/arm/v7 + push: true + tags: | + activepieces/ap-base:${{ inputs.tag }} + activepieces/ap-base:latest diff --git a/.github/workflows/release-changelog.yml b/.github/workflows/release-changelog.yml new file mode 100644 index 0000000..4df5e31 --- /dev/null +++ b/.github/workflows/release-changelog.yml @@ -0,0 +1,32 @@ +name: Write release notes + +on: + pull_request: + types: [opened, reopened, synchronize, edited, closed, labeled] + +permissions: + contents: write + pull-requests: write + +jobs: + Release: + if: contains(github.event.pull_request.labels.*.name, 'release') + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Set RELEASE env var from package.json + run: echo RELEASE=$(node --print "require('./package.json').version") >> $GITHUB_ENV + + - name: Create release notes + uses: release-drafter/release-drafter@v5 + with: + commitish: main + prerelease: false + tag: ${{ env.RELEASE }} + name: ${{ env.RELEASE }} + version: ${{ env.RELEASE }} + latest: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-pieces.yml b/.github/workflows/release-pieces.yml new file mode 100644 index 0000000..f98a8f8 --- /dev/null +++ b/.github/workflows/release-pieces.yml @@ -0,0 +1,61 @@ +name: Release Pieces + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - 'packages/pieces/**' + - 'packages/shared/**' + +jobs: + Release-Pieces: + if: github.repository == 'activepieces/activepieces' + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Cache dependencies + uses: actions/cache@v3 + with: + path: ~/.npm + key: npm-${{ hashFiles('package-lock.json') }} + restore-keys: npm- + + - name: Install dependencies + run: npm ci --ignore-scripts + + - name: build packages + run: npx nx run-many --target=build + + - name: copy project .npmrc to user level + run: cp .npmrc $HOME/.npmrc + + - name: publish shared package + run: npx ts-node -r tsconfig-paths/register -P packages/engine/tsconfig.lib.json tools/scripts/utils/publish-nx-project.ts packages/shared + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: publish pieces-common package + run: npx ts-node -r tsconfig-paths/register -P packages/engine/tsconfig.lib.json tools/scripts/utils/publish-nx-project.ts packages/pieces/community/common + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: publish pieces-framework package + run: npx ts-node -r tsconfig-paths/register -P packages/engine/tsconfig.lib.json tools/scripts/utils/publish-nx-project.ts packages/pieces/community/framework + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: publish pieces packages + run: npx ts-node -r tsconfig-paths/register -P packages/engine/tsconfig.lib.json tools/scripts/pieces/publish-pieces-to-npm.ts + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: update pieces metadata + run: npx ts-node -r tsconfig-paths/register -P packages/engine/tsconfig.lib.json tools/scripts/pieces/update-pieces-metadata.ts packages/pieces/community/framework + env: + AP_CLOUD_API_KEY: ${{ secrets.AP_CLOUD_API_KEY }} diff --git a/.github/workflows/release-rc.yml b/.github/workflows/release-rc.yml new file mode 100644 index 0000000..f5e7d81 --- /dev/null +++ b/.github/workflows/release-rc.yml @@ -0,0 +1,51 @@ +name: Release RC + +on: + workflow_dispatch: + +jobs: + Release: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Set RELEASE env var from package.json + run: echo RELEASE=$(node --print "require('./package.json').rcVersion") >> $GITHUB_ENV + + - name: Set CLOUD_RELEASE env var from package.json + run: echo CLOUD_RELEASE=$(node --print "require('./package.json').rcVersion.replace(/-/g, '')") >> $GITHUB_ENV + + - name: Fail if tag already exists + run: '! docker manifest inspect activepieces/activepieces:${{ env.RELEASE }}' + + - name: Set up Depot CLI + uses: depot/setup-action@v1 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: depot/build-push-action@v1 + with: + project: du7O4b0e8P + token: ${{ secrets.DEPOT_PROJECT_TOKEN }} + context: . + file: ./Dockerfile + platforms: | + linux/amd64 + linux/arm64 + push: true + tags: | + ghcr.io/activepieces/activepieces:${{ env.RELEASE }} + ghcr.io/activepieces/activepieces-cloud:${{ env.CLOUD_RELEASE }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b96d684 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,50 @@ +name: Release Everything + +on: + workflow_dispatch: + +jobs: + Release: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Set RELEASE env var from package.json + run: echo RELEASE=$(node --print "require('./package.json').version") >> $GITHUB_ENV + + - name: Fail if tag already exists + run: '! docker manifest inspect activepieces/activepieces:${{ env.RELEASE }}' + + - name: Set up Depot CLI + uses: depot/setup-action@v1 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: depot/build-push-action@v1 + with: + project: du7O4b0e8P + token: ${{ secrets.DEPOT_PROJECT_TOKEN }} + context: . + file: ./Dockerfile + platforms: | + linux/amd64 + linux/arm64 + push: true + tags: | + activepieces/activepieces:${{ env.RELEASE }} + activepieces/activepieces:latest + ghcr.io/activepieces/activepieces:${{ env.RELEASE }} + ghcr.io/activepieces/activepieces:latest diff --git a/.github/workflows/validate-issue-linking.yml b/.github/workflows/validate-issue-linking.yml new file mode 100644 index 0000000..f77e509 --- /dev/null +++ b/.github/workflows/validate-issue-linking.yml @@ -0,0 +1,29 @@ +name: "Issue Linking - Require Issue Reference" + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +permissions: + pull-requests: read + issues: read + +jobs: + validate-issue-linking: + name: Check Issue Linking + runs-on: ubuntu-latest + steps: + - name: Check Issue Linking + uses: actions/github-script@v7 + with: + script: | + const body = context.payload.pull_request.body || ''; + const issuePattern = /(?:closes|fixes|resolves)\s+#(\d+)/i; + const linkedIssue = body.match(issuePattern); + + if (!linkedIssue) { + core.setFailed('Pull request must be linked to an issue using "closes #issue_number", "fixes #issue_number", or "resolves #issue_number"'); + } \ No newline at end of file diff --git a/.github/workflows/validate-pr-labels.yml b/.github/workflows/validate-pr-labels.yml new file mode 100644 index 0000000..25795b6 --- /dev/null +++ b/.github/workflows/validate-pr-labels.yml @@ -0,0 +1,25 @@ +name: "Pull Request Labels - Require At Least One Label" + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +permissions: + pull-requests: read + +jobs: + validate-labels: + name: Check PR Labels + runs-on: ubuntu-latest + steps: + - name: Check PR Labels + uses: actions/github-script@v7 + with: + script: | + const labels = context.payload.pull_request.labels; + if (!labels || labels.length === 0) { + core.setFailed('Pull request must have at least one label'); + } \ No newline at end of file diff --git a/.github/workflows/validate-pr-title.yml b/.github/workflows/validate-pr-title.yml new file mode 100644 index 0000000..4cab9c0 --- /dev/null +++ b/.github/workflows/validate-pr-title.yml @@ -0,0 +1,20 @@ +name: "Lint PR" + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +permissions: + pull-requests: read + +jobs: + main: + name: Validate PR title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5.2.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/validate-publishable-packages.yml b/.github/workflows/validate-publishable-packages.yml new file mode 100644 index 0000000..317b832 --- /dev/null +++ b/.github/workflows/validate-publishable-packages.yml @@ -0,0 +1,37 @@ +name: Validate publishable packages + +on: + workflow_dispatch: + pull_request: + branches: + - main + paths: + - 'packages/pieces/**' + +jobs: + validate-publishable-packages: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Cache dependencies + id: cache-npm + uses: actions/cache@v3 + with: + path: ~/.npm + key: npm-${{ hashFiles('package-lock.json') }} + restore-keys: npm- + + - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} + name: List the state of node modules + continue-on-error: true + run: npm list + + - name: Install dependencies + run: npm ci --ignore-scripts + + - name: validate publishable packages + run: npx ts-node -r tsconfig-paths/register -P packages/engine/tsconfig.lib.json tools/scripts/validate-publishable-packages.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f46951 --- /dev/null +++ b/.gitignore @@ -0,0 +1,67 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +dist +tmp +.nx/ +/out-tsc +firebase-admin-sdk.json +dev +/cache +.env + +# SDK Build +builds + +# dependencies +node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace +/cache +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings +.nx/ +# System Files +.DS_Store +Thumbs.db + + +.angular +activepieces-engine.js +.history/ + +# produced by unsandboxed engine execution +.pnpm-store + +# produced by backend tests +.npm-cache + +# scratch pad +scratch.md + +# environment variables +.env + +.cursor/rules/nx-rules.mdc +.github/instructions/nx.instructions.md diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 0000000..5098393 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,10 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +# Check if the commit includes changes to the backend's .env file +if git diff --cached --name-only -- packages/server/api/.env | grep -q '^packages/server/api/.env$'; then + echo "Error: You're attempting to commit the backend's .env file. Please avoid committing this file." + exit 1 +fi + +npx --no -- commitlint --edit ${1} diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..1525982 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..f1befed --- /dev/null +++ b/.npmrc @@ -0,0 +1,4 @@ +@activepieces:registry=https://registry.npmjs.org/ +//registry.npmjs.org/:_authToken=${NPM_TOKEN} +legacy-peer-deps=true +save-exact=true diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..3462e8c --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v18.19.0 \ No newline at end of file diff --git a/.nxignore b/.nxignore new file mode 100644 index 0000000..c9748e1 --- /dev/null +++ b/.nxignore @@ -0,0 +1,4 @@ +deploy/ +dist/ +cache/ +dev/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..a4684ea --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +# Add files here to ignore them from prettier formatting + +/dist +/coverage + +.angular + +/.nx/cache +/.nx/workspace-data \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..92cde39 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} \ No newline at end of file diff --git a/.typos.toml b/.typos.toml new file mode 100644 index 0000000..ca85468 --- /dev/null +++ b/.typos.toml @@ -0,0 +1,27 @@ +[files] +extend-exclude = [ + ".git/", + "**/database/**", + "packages/ui/core/src/locale/", + # French + "packages/pieces/community/wedof/src/", +] +ignore-hidden = false + +[default] +extend-ignore-re = [ + "[0-9A-Za-z]{34}", + "name: 'referal'", + "getRepository\\('referal'\\)", + "label: 'FO Language', value: 'fo'", + "649c83111c9cbe6ba1d4cabe", + "hYy9pRFVxpDsO1FB05SunFWUe9JZY", + "lod6JEdKyPlvrnErdnrGa", +] + +[default.extend-identifiers] +"crazyTweek" = "crazyTweek" +"optin_ip" = "optin_ip" + +# Typos +"Github" = "GitHub" diff --git a/.verdaccio/config.yml b/.verdaccio/config.yml new file mode 100644 index 0000000..a007fe8 --- /dev/null +++ b/.verdaccio/config.yml @@ -0,0 +1,28 @@ +# path to a directory with all packages +storage: ../tmp/local-registry/storage + +# a list of other known repositories we can talk to +uplinks: + npmjs: + url: https://registry.npmjs.org/ + maxage: 60m + +packages: + '**': + # give all users (including non-authenticated users) full access + # because it is a local registry + access: $all + publish: $all + unpublish: $all + + # if package is not available locally, proxy requests to npm registry + proxy: npmjs + +# log settings +logs: + type: stdout + format: pretty + level: warn + +publish: + allow_offline: true # set offline to true to allow publish offline diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..109c930 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "nrwl.angular-console", + "esbenp.prettier-vscode", + "firsttris.vscode-jest-runner", + "rvest.vs-code-prettier-eslint" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..64f1cde --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "backend", + "port": 9229, + "request": "attach", + "skipFiles": [ + "/**", + "**/node_modules/**" + ], + "type": "node", + "localRoot": "${workspaceFolder}/packages/server/api", + "remoteRoot": "/usr/src/app", + "restart": true, + "autoAttachChildProcesses": false + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5f6644b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "cSpell.words": [ + "Activepieces", + "Fastify", + "mpim", + "sendgrid" + ], + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "editor.formatOnSave": false, + "typescript.tsdk": "node_modules/typescript/lib", + "javascript.preferences.importModuleSpecifier": "relative", + "nxConsole.generateAiAgentRules": true, +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f687217 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,13 @@ + +# Contributing to Activepieces + +First off, thanks for taking the time to contribute! ❤️ + +All types of contributions are encouraged and valued. See the [Contributing Guide](https://www.activepieces.com/docs/contributing/overview) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉 + +> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: +> - Star the project +> - Tweet about it +> - Refer this project in your project's readme +> - Mention the project at local meetups and tell your friends/colleagues + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e86379e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,97 @@ +FROM node:18.20.5-bullseye-slim AS base + +# Use a cache mount for apt to speed up the process +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + openssh-client \ + python3 \ + g++ \ + build-essential \ + git \ + poppler-utils \ + poppler-data \ + procps && \ + yarn config set python /usr/bin/python3 && \ + npm install -g node-gyp +RUN npm i -g npm@9.9.3 pnpm@9.15.0 + +# Set the locale +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 +ENV NX_DAEMON=false + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + locales \ + locales-all \ + libcap-dev \ + && rm -rf /var/lib/apt/lists/* + +# install isolated-vm in a parent directory to avoid linking the package in every sandbox +RUN cd /usr/src && npm i isolated-vm@5.0.1 + +RUN pnpm store add @tsconfig/node18@1.0.0 +RUN pnpm store add @types/node@18.17.1 + +RUN pnpm store add typescript@4.9.4 + +### STAGE 1: Build ### +FROM base AS build + +# Set up backend +WORKDIR /usr/src/app + +COPY .npmrc package.json package-lock.json ./ +RUN npm ci + +COPY . . + +RUN npx nx run-many --target=build --projects=server-api --configuration production +RUN npx nx run-many --target=build --projects=react-ui + +# Install backend production dependencies +RUN cd dist/packages/server/api && npm install --production --force + +### STAGE 2: Run ### +FROM base AS run + +# Set up backend +WORKDIR /usr/src/app + +COPY packages/server/api/src/assets/default.cf /usr/local/etc/isolate + +# Install Nginx and gettext for envsubst +RUN apt-get update && apt-get install -y nginx gettext + +# Copy Nginx configuration template +COPY nginx.react.conf /etc/nginx/nginx.conf + +COPY --from=build /usr/src/app/LICENSE . + +RUN mkdir -p /usr/src/app/dist/packages/server/ +RUN mkdir -p /usr/src/app/dist/packages/engine/ +RUN mkdir -p /usr/src/app/dist/packages/shared/ + +# Copy Output files to appropriate directory from build stage +COPY --from=build /usr/src/app/dist/packages/engine/ /usr/src/app/dist/packages/engine/ +COPY --from=build /usr/src/app/dist/packages/server/ /usr/src/app/dist/packages/server/ +COPY --from=build /usr/src/app/dist/packages/shared/ /usr/src/app/dist/packages/shared/ + +RUN cd /usr/src/app/dist/packages/server/api/ && npm install --production --force + +# Copy Output files to appropriate directory from build stage +COPY --from=build /usr/src/app/packages packages +# Copy frontend files to Nginx document root directory from build stage +COPY --from=build /usr/src/app/dist/packages/react-ui /usr/share/nginx/html/ + +LABEL service=activepieces + +# Set up entrypoint script +COPY docker-entrypoint.sh . +RUN chmod +x docker-entrypoint.sh +ENTRYPOINT ["./docker-entrypoint.sh"] + +EXPOSE 80 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..73e0a2f --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2020-2024 Activepieces Inc. + +Portions of this software are licensed as follows: + +* All content that resides under the "packages/ee/" and "packages/server/api/src/app/ee" directory of this repository, if that directory exists, is licensed under the license defined in packages/ee/LICENSE +* All third party components incorporated into the Activepieces Inc Software are licensed under the original license provided by the owner of the applicable component. +* Content outside of the above mentioned directories or restrictions above is available under the "MIT Expat" license as defined below. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..691d096 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,39 @@ +# Security + +Contact: security@activepieces.com + +Based on [https://supabase.com/.well-known/security.txt](https://supabase.com/.well-known/security.txt) + +At Activepieces.com, we consider the security of our systems a top priority. But no matter how much effort we put into system security, there can still be vulnerabilities present. + +If you discover a vulnerability, we would like to know about it so we can take steps to address it as quickly as possible. We would like to ask you to help us better protect our clients and our systems. + +## Out of scope vulnerabilities: + +- Clickjacking on pages with no sensitive actions. +- Unauthenticated/logout/login CSRF. +- Attacks requiring MITM or physical access to a user's device. +- Any activity that could lead to the disruption of our service (DoS). +- Content spoofing and text injection issues without showing an attack vector/without being able to modify HTML/CSS. +- Email spoofing +- Missing DNSSEC, CAA, CSP headers +- Lack of Secure or HTTP only flag on non-sensitive cookies +- Deadlinks + +## Please do the following: + +- E-mail your findings to [security@activepieces.com](mailto:security@activepieces.com). +- Do not run automated scanners on our infrastructure or dashboard. If you wish to do this, contact us and we will set up a sandbox for you. +- Do not take advantage of the vulnerability or problem you have discovered, for example by downloading more data than necessary to demonstrate the vulnerability or deleting or modifying other people's data, +- Do not reveal the problem to others until it has been resolved, +- Do not use attacks on physical security, social engineering, distributed denial of service, spam or applications of third parties, +- Do provide sufficient information to reproduce the problem, so we will be able to resolve it as quickly as possible. Usually, the IP address or the URL of the affected system and a description of the vulnerability will be sufficient, but complex vulnerabilities may require further explanation. + +## What we promise: + +- We will respond to your report within 3 business days with our evaluation of the report and an expected resolution date, +- If you have followed the instructions above, we will not take any legal action against you in regard to the report, +- We will handle your report with strict confidentiality, and not pass on your personal details to third parties without your permission, +- We will keep you informed of the progress towards resolving the problem, +- In the public information concerning the problem reported, we will give your name as the discoverer of the problem (unless you desire otherwise), and +- We strive to resolve all problems as quickly as possible, and we would like to play an active role in the ultimate publication on the problem after it is resolved. diff --git a/assets/ap-logo.png b/assets/ap-logo.png new file mode 100755 index 0000000..bf13eb2 Binary files /dev/null and b/assets/ap-logo.png differ diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..422b194 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1 @@ +module.exports = { extends: ['@commitlint/config-conventional'] }; diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 0000000..2e24d8f --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,12 @@ +project_id_env: CROWDIN_PROJECT_ID +api_token_env: CROWDIN_PERSONAL_TOKEN +base_path: . +base_url: 'https://api.crowdin.com' +preserve_hierarchy: 1 +files: + - type: i18next_json + source: packages/react-ui/public/locales/en/translation.json + translation: /packages/react-ui/public/locales/%two_letters_code%/translation.json + - type: json + source: packages/pieces/**/**/src/i18n/translation.json + translation: /packages/pieces/**/**/src/i18n/%two_letters_code%.json diff --git a/deploy/pulumi/.gitignore b/deploy/pulumi/.gitignore new file mode 100644 index 0000000..dd1912c --- /dev/null +++ b/deploy/pulumi/.gitignore @@ -0,0 +1,33 @@ +/.pulumi/ +/.vscode/ +/.vs/ +bin/ +build/ +node_modules/ +*.pyc +.Python +venv/ +include/ +lib/ +yarn.lock +package-lock.json +# https://www.pulumi.com/blog/iac-recommended-practices-developer-stacks-git-branches/#using-developer-stacks +# Pulumi.*.yaml +# Pulumi.*dev*.yaml +.idea/ +.ionide/ +*.iml +key.rsa* +obj/ +vendor +Gopkg.lock +**/.DS_Store + +**/ci-scripts + +# Java app +.gradle/ +.settings/ +.project +.classpath +target/ \ No newline at end of file diff --git a/deploy/pulumi/Pulumi.activepieces-dev.yaml b/deploy/pulumi/Pulumi.activepieces-dev.yaml new file mode 100644 index 0000000..01809f2 --- /dev/null +++ b/deploy/pulumi/Pulumi.activepieces-dev.yaml @@ -0,0 +1,22 @@ +encryptionsalt: v1:wHCVNl3bj/g=:v1:mxogi9ZeBjIcxNZC:q+bjpLv9rnJnu8qq7xwKGLd/GAZOqA== +config: + activepieces:environment: "dev" + activepieces:apEncryptionKey: + activepieces:apJwtSecret: + activepieces:deployLocalBuild: "false" + activepieces:repoName: + activepieces:containerCpu: "256" + activepieces:containerMemory: "512" + activepieces:containerInstances: "1" + activepieces:usePostgres: "false" + activepieces:dbInstanceClass: "db.t3.small" + activepieces:dbUsername: "postgres" + activepieces:dbIsPublic: "false" + activepieces:dbPassword: + secure: v1:MXNSOcqZCp10X2PX:mU2iTrcETjdisk8FkD5yHLJYUxRei/9l + activepieces:addIpToPostgresSecurityGroup: + activepieces:useRedis: "false" + activepieces:redisNodeType: "cache.t3.small" + activepieces:domain: + activepieces:subDomain: + aws:region: "us-east-1" diff --git a/deploy/pulumi/Pulumi.activepieces-prod.yaml b/deploy/pulumi/Pulumi.activepieces-prod.yaml new file mode 100644 index 0000000..ca1b070 --- /dev/null +++ b/deploy/pulumi/Pulumi.activepieces-prod.yaml @@ -0,0 +1,19 @@ +encryptionsalt: v1:icXg2cmIvSc=:v1:y8+4YhdMCPPDY26J:5cNYmimH353n8sjUDDc6srvcPgb+8Q== +config: + activepieces:environment: "prod" + activepieces:apEncryptionKey: + activepieces:apJwtSecret: + activepieces:deployLocalBuild: "true" + activepieces:repoName: "activepieces-prod-repo" + activepieces:containerCpu: "512" + activepieces:containerMemory: "1024" + activepieces:containerInstances: "1" + activepieces:usePostgres: "true" + activepieces:dbInstanceClass: "db.t3.small" + activepieces:dbIsPublic: "false" + activepieces:dbPassword: + secure: v1:MXNSOcqZCp10X2PX:mU2iTrcETjdisk8FkD5yHLJYUxRei/9l + activepieces:dbUsername: "postgres" + activepieces:useRedis: "true" + activepieces:redisNodeType: "cache.t3.small" + aws:region: "us-east-1" diff --git a/deploy/pulumi/Pulumi.yaml b/deploy/pulumi/Pulumi.yaml new file mode 100644 index 0000000..a406b85 --- /dev/null +++ b/deploy/pulumi/Pulumi.yaml @@ -0,0 +1,56 @@ +runtime: nodejs +name: activepieces +description: A Pulumi template to deploy Activepieces in a development or production configuration. +stack: activepieces-dev +template: + description: Deploy Activepieces into into an ECS Fargate instance & optionally add Postgres, Redis and a DNS registration with SSL. + config: + aws:region: + description: The AWS region to deploy into + default: us-west-2 + environment: + description: Environment + default: prod + containerCpu: + description: The amount of CPU to allocate for the container + default: 256 + containerMemory: + description: The amount of memory to allocate for the container + default: 512 + containerInstances: + description: Number of running containers behind load balancer + default: 1 + usePostgres: + description: Add Postgres for storage or use SQLite3 locally + default: true + dbIsPublic: + description: Should Db be publicly reachable. Ignored if usePostgres is false. + default: false + dbUsername: + description: Default username for the Postgres. Ignored if usePostgres is false + default: postgres + dbPassword: + description: Defaults to "postgres". Ignored if usePostgres is false + default: postgres + secret: true + dbInstanceClass: + description: The size of the RDS instance + default: db.t3.micro + useRedis: + description: Use a single node Redis cluster or in-memory + default: true + redisNodeType: + description: Node type for the Redis 7 cluster + default: cache.t3.micro + domain: + description: Optional - E.g. "yourdomain.com". Hosted zone must already exist in Route 53. Creates SSL cert + subDomain: + description: Optional - E.g. "activepieces". "domain" must be set + addIpToPostgresSecurityGroup: + description: Optional - An IP address to add to the allowed inbound traffic for the Postgres + apEncryptionKey: + description: Optional - Run 'openssl rand -hex 16' locally to generate or leave blank to auto-generate + secret: true + apJwtSecret: + description: Optional - Run 'openssl rand -hex 32' locally to generate or leave blank to auto-generate + secret: true diff --git a/deploy/pulumi/README.md b/deploy/pulumi/README.md new file mode 100644 index 0000000..ec8609a --- /dev/null +++ b/deploy/pulumi/README.md @@ -0,0 +1,3 @@ +# Getting Started + +See instruction on https://www.activepieces.com/docs/install/options/aws diff --git a/deploy/pulumi/autotag.ts b/deploy/pulumi/autotag.ts new file mode 100644 index 0000000..42bb106 --- /dev/null +++ b/deploy/pulumi/autotag.ts @@ -0,0 +1,16 @@ +import * as pulumi from "@pulumi/pulumi"; +import { isTaggable } from "./taggable"; + +/** + * registerAutoTags registers a global stack transformation that merges a set + * of tags with whatever was also explicitly added to the resource definition. + */ +export function registerAutoTags(autoTags: Record): void { + pulumi.runtime.registerStackTransformation((args) => { + if (isTaggable(args.type)) { + args.props["tags"] = { ...args.props["tags"], ...autoTags }; + return { props: args.props, opts: args.opts }; + } + return undefined; + }); +} \ No newline at end of file diff --git a/deploy/pulumi/index.ts b/deploy/pulumi/index.ts new file mode 100644 index 0000000..cb7f71c --- /dev/null +++ b/deploy/pulumi/index.ts @@ -0,0 +1,466 @@ +import * as aws from "@pulumi/aws"; +import * as docker from "@pulumi/docker"; +import * as pulumi from "@pulumi/pulumi"; +import * as awsx from "@pulumi/awsx"; +import { ApplicationLoadBalancer } from "@pulumi/awsx/lb/applicationLoadBalancer"; +import { registerAutoTags } from './autotag'; +import * as child_process from "child_process"; + +const stack = pulumi.getStack(); +const config = new pulumi.Config(); + +const apEncryptionKey = config.getSecret("apEncryptionKey")?.apply(secretValue => { + return secretValue || child_process.execSync("openssl rand -hex 16").toString().trim(); +}); +const apJwtSecret = config.getSecret("apJwtSecret")?.apply(secretValue => { + return secretValue || child_process.execSync("openssl rand -hex 32").toString().trim(); +}); +const containerCpu = config.requireNumber("containerCpu"); +const containerMemory = config.requireNumber("containerMemory"); +const containerInstances = config.requireNumber("containerInstances"); +const addIpToPostgresSecurityGroup = config.get("addIpToPostgresSecurityGroup"); +const domain = config.get("domain"); +const subDomain = config.get("subDomain"); +const usePostgres = config.requireBoolean("usePostgres"); +const useRedis = config.requireBoolean("useRedis"); +const redisNodeType = config.require("redisNodeType"); +const dbIsPublic = config.getBoolean("dbIsPublic"); +const dbUsername = config.get("dbUsername"); +const dbPassword = config.getSecret("dbPassword"); +const dbInstanceClass = config.require("dbInstanceClass"); + +// Add tags for every resource that allows them, with the following properties. +// Useful to know who or what created the resource/service +registerAutoTags({ + "pulumi:Project": pulumi.getProject(), + "pulumi:Stack": pulumi.getStack(), + "Created by": config.get("author") || child_process.execSync("pulumi whoami").toString().trim().replace('\\', '/') +}); + +let imageName; + +// Check if we're deploying a local build or direct from Docker Hub +if (config.getBoolean("deployLocalBuild")) { + + const repoName = config.require("repoName"); + + const repo = new aws.ecr.Repository(repoName, { + name: repoName // https://www.pulumi.com/docs/intro/concepts/resources/names/#autonaming + }); // Create a private ECR repository + + const repoUrl = pulumi.interpolate`${repo.repositoryUrl}`; // Get registry info (creds and endpoint) + const name = pulumi.interpolate`${repoUrl}:latest`; + + // Get the repository credentials we use to push the image to the repository + const repoCreds = repo.registryId.apply(async (registryId) => { + const credentials = await aws.ecr.getCredentials({ + registryId: registryId, + }); + const decodedCredentials = Buffer.from(credentials.authorizationToken, "base64").toString(); + const [username, password] = decodedCredentials.split(":"); + return { + server: credentials.proxyEndpoint, + username, + password + }; + }); + + // Build and publish the container image. + const image = new docker.Image(stack, { + build: { + context: `../../`, + dockerfile: `../../Dockerfile`, + builderVersion: "BuilderBuildKit", + args: { + "BUILDKIT_INLINE_CACHE": "1" + }, + }, + skipPush: pulumi.runtime.isDryRun(), + imageName: name, + registry: repoCreds + }); + + imageName = image.imageName; + + pulumi.log.info(`Finished pushing image to ECR`, image); +} else { + imageName = process.env.IMAGE_NAME || config.get("imageName") || "activepieces/activepieces:latest"; +} + +const containerEnvironmentVars: awsx.types.input.ecs.TaskDefinitionKeyValuePairArgs[] = []; + +// Allocate a new VPC with the default settings: +const vpc = new awsx.ec2.Vpc(`${stack}-vpc`, { + numberOfAvailabilityZones: 2, + natGateways: { + strategy: "Single" + }, + tags: { + // For some reason, this is how you name a VPC with AWS: + // https://github.com/pulumi/pulumi-terraform/issues/38#issue-262186406 + Name: `${stack}-vpc` + }, + enableDnsHostnames: true, + enableDnsSupport: true +}); + +const albSecGroup = new aws.ec2.SecurityGroup(`${stack}-alb-sg`, { + name: `${stack}-alb-sg`, + vpcId: vpc.vpcId, + ingress: [{ // Allow only http & https traffic + protocol: "tcp", + fromPort: 443, + toPort: 443, + cidrBlocks: ["0.0.0.0/0"] + }, + { + protocol: "tcp", + fromPort: 80, + toPort: 80, + cidrBlocks: ["0.0.0.0/0"] + }], + egress: [{ + protocol: "-1", + fromPort: 0, + toPort: 0, + cidrBlocks: ["0.0.0.0/0"] + }] +}) + +const fargateSecGroup = new aws.ec2.SecurityGroup(`${stack}-fargate-sg`, { + name: `${stack}-fargate-sg`, + vpcId: vpc.vpcId, + ingress: [ + { + protocol: "tcp", + fromPort: 80, + toPort: 80, + securityGroups: [albSecGroup.id] + } + ], + egress: [ // allow all outbound traffic + { + protocol: "-1", + fromPort: 0, + toPort: 0, + cidrBlocks: ["0.0.0.0/0"] + } + ] +}); + +if (usePostgres) { + const rdsSecurityGroupArgs: aws.ec2.SecurityGroupArgs = { + name: `${stack}-db-sg`, + vpcId: vpc.vpcId, + ingress: [{ + protocol: "tcp", + fromPort: 5432, + toPort: 5432, + securityGroups: [fargateSecGroup.id] // The id of the Fargate security group + }], + egress: [ // allow all outbound traffic + { + protocol: "-1", + fromPort: 0, + toPort: 0, + cidrBlocks: ["0.0.0.0/0"] + } + ] + }; + + // Optionally add the current outgoing public IP address to the CIDR block + // so that they can connect directly to the Db during development + if (addIpToPostgresSecurityGroup) { + + // @ts-ignore + rdsSecurityGroupArgs.ingress.push({ + protocol: "tcp", + fromPort: 5432, + toPort: 5432, + cidrBlocks: [`${addIpToPostgresSecurityGroup}/32`], + description: `Public IP for local connection` + }); + } + + const rdsSecurityGroup = new aws.ec2.SecurityGroup(`${stack}-db-sg`, rdsSecurityGroupArgs); + + const rdsSubnets = new aws.rds.SubnetGroup(`${stack}-db-subnet-group`, { + name: `${stack}-db-subnet-group`, + subnetIds: dbIsPublic ? vpc.publicSubnetIds : vpc.privateSubnetIds + }); + + const db = new aws.rds.Instance(stack, { + allocatedStorage: 10, + engine: "postgres", + engineVersion: "14.9", + identifier: stack, // In RDS + dbName: "postgres", // When connected to the DB host + instanceClass: dbInstanceClass, + port: 5432, + publiclyAccessible: dbIsPublic, + skipFinalSnapshot: true, + storageType: "gp2", + username: dbUsername, + password: dbPassword, + dbSubnetGroupName: rdsSubnets.id, + vpcSecurityGroupIds: [rdsSecurityGroup.id], + backupRetentionPeriod: 0, + applyImmediately: true, + allowMajorVersionUpgrade: true, + autoMinorVersionUpgrade: true + }, { + protect: dbIsPublic === false, + deleteBeforeReplace: true + }); + + containerEnvironmentVars.push( + { + name: "AP_POSTGRES_DATABASE", + value: db.dbName + }, + { + name: "AP_POSTGRES_HOST", + value: db.address + }, + { + name: "AP_POSTGRES_PORT", + value: pulumi.interpolate`${db.port}` + }, + { + name: "AP_POSTGRES_USERNAME", + value: db.username + }, + { + name: "AP_POSTGRES_PASSWORD", + value: config.requireSecret("dbPassword") + }, + { + name: "AP_POSTGRES_USE_SSL", + value: "false" + }); + +} else { + containerEnvironmentVars.push( + { + name: "AP_DB_TYPE", + value: "SQLITE3" + }); +} + +if (useRedis) { + + const redisCluster = new aws.elasticache.Cluster(`${stack}-redis-cluster`, { + clusterId: `${stack}-redis-cluster`, + engine: "redis", + engineVersion: '7.0', + nodeType: redisNodeType, + numCacheNodes: 1, + parameterGroupName: "default.redis7", + port: 6379, + subnetGroupName: new aws.elasticache.SubnetGroup(`${stack}-redis-subnet-group`, { + name: `${stack}-redis-subnet-group`, + subnetIds: vpc.privateSubnetIds + }).id, + securityGroupIds: [ + new aws.ec2.SecurityGroup(`${stack}-redis-sg`, { + name: `${stack}-redis-sg`, + vpcId: vpc.vpcId, + ingress: [{ + protocol: "tcp", + fromPort: 6379, // The standard port for Redis + toPort: 6379, + securityGroups: [fargateSecGroup.id] + }], + egress: [{ + protocol: "-1", + fromPort: 0, + toPort: 0, + cidrBlocks: ["0.0.0.0/0"] + }] + }).id + ] + }); + + const redisUrl = pulumi.interpolate`${redisCluster.cacheNodes[0].address}:${redisCluster.cacheNodes[0].port}`; + containerEnvironmentVars.push( + { + name: "AP_REDIS_URL", + value: redisUrl + }); + +} else { + containerEnvironmentVars.push( + { + name: "AP_QUEUE_MODE", + value: "MEMORY" + }); +} + +let alb: ApplicationLoadBalancer; +// Export the URL so we can easily access it. +let frontendUrl; + +if (subDomain && domain) { + const fullDomain = `${subDomain}.${domain}`; + + const exampleCertificate = new aws.acm.Certificate(`${stack}-cert`, { + domainName: fullDomain, + validationMethod: "DNS", + }); + + const hostedZoneId = aws.route53.getZone({ name: domain }, { async: true }).then(zone => zone.zoneId); + + // DNS records to verify SSL Certificate + const certificateValidationDomain = new aws.route53.Record(`${fullDomain}-validation`, { + name: exampleCertificate.domainValidationOptions[0].resourceRecordName, + zoneId: hostedZoneId, + type: exampleCertificate.domainValidationOptions[0].resourceRecordType, + records: [exampleCertificate.domainValidationOptions[0].resourceRecordValue], + ttl: 600, + }); + + const certificateValidation = new aws.acm.CertificateValidation(`${fullDomain}-cert-validation`, { + certificateArn: exampleCertificate.arn, + validationRecordFqdns: [certificateValidationDomain.fqdn], + }); + + // Creates an ALB associated with our custom VPC. + alb = new awsx.lb.ApplicationLoadBalancer(`${stack}-alb`, { + securityGroups: [albSecGroup.id], + name: `${stack}-alb`, + subnetIds: vpc.publicSubnetIds, + listeners: [{ + port: 80, // port on the docker container + protocol: "HTTP", + defaultActions: [{ + type: "redirect", + redirect: { + protocol: "HTTPS", + port: "443", + statusCode: "HTTP_301", + }, + }] + }, + { + protocol: "HTTPS", + port: 443, + certificateArn: certificateValidation.certificateArn + }], + defaultTargetGroup: { + name: `${stack}-alb-tg`, + port: 80 // port on the docker container , + } + }); + + // Create a DNS record for the load balancer + const albDomain = new aws.route53.Record(fullDomain, { + name: fullDomain, + zoneId: hostedZoneId, + type: "CNAME", + records: [alb.loadBalancer.dnsName], + ttl: 600, + }); + + frontendUrl = pulumi.interpolate`https://${subDomain}.${domain}`; + +} else { + + // Creates an ALB associated with our custom VPC. + alb = new awsx.lb.ApplicationLoadBalancer(`${stack}-alb`, { + securityGroups: [albSecGroup.id], + name: `${stack}-alb`, + subnetIds: vpc.publicSubnetIds, + listeners: [{ + port: 80, // exposed port from the docker file + protocol: "HTTP" + }], + defaultTargetGroup: { + name: `${stack}-alb-tg`, + port: 80, // port on the docker container + protocol: "HTTP" + } + }); + + frontendUrl = pulumi.interpolate`http://${alb.loadBalancer.dnsName}`; +} + +const environmentVariables = [ + ...containerEnvironmentVars, + { + name: "AP_ENGINE_EXECUTABLE_PATH", + value: "dist/packages/engine/main.js" + }, + { + name: "AP_ENCRYPTION_KEY", + value: apEncryptionKey + }, + { + name: "AP_JWT_SECRET", + value: apJwtSecret + }, + { + name: "AP_ENVIRONMENT", + value: "prod" + }, + { + name: "AP_FRONTEND_URL", + value: frontendUrl + }, + { + name: "AP_TRIGGER_DEFAULT_POLL_INTERVAL", + value: "5" + }, + { + name: "AP_EXECUTION_MODE", + value: "UNSANDBOXED" + }, + { + name: "AP_REDIS_USE_SSL", + value: "false" + }, + { + name: "AP_SANDBOX_RUN_TIME_SECONDS", + value: "600" + }, + { + name: "AP_TELEMETRY_ENABLED", + value: "true" + }, + { + name: "AP_TEMPLATES_SOURCE_URL", + value: "https://cloud.activepieces.com/api/v1/flow-templates" + } +]; + +const fargateService = new awsx.ecs.FargateService(`${stack}-fg`, { + name: `${stack}-fg`, + cluster: (new aws.ecs.Cluster(`${stack}-cluster`, { + name: `${stack}-cluster` + })).arn, + networkConfiguration: { + subnets: vpc.publicSubnetIds, + securityGroups: [fargateSecGroup.id], + assignPublicIp: true + }, + desiredCount: containerInstances, + taskDefinitionArgs: { + family: `${stack}-fg-task-definition`, + container: { + name: "activepieces", + image: imageName, + cpu: containerCpu, + memory: containerMemory, + portMappings: [{ + targetGroup: alb.defaultTargetGroup, + }], + environment: environmentVariables + } + } +}); + +pulumi.log.info("Finished running Pulumi"); + +export const _ = { + activePiecesUrl: frontendUrl, + activepiecesEnv: environmentVariables +}; diff --git a/deploy/pulumi/package.json b/deploy/pulumi/package.json new file mode 100644 index 0000000..e8d55a8 --- /dev/null +++ b/deploy/pulumi/package.json @@ -0,0 +1,13 @@ +{ + "name": "pulumi", + "main": "index.ts", + "devDependencies": { + "@types/node": "^18" + }, + "dependencies": { + "@pulumi/pulumi": "^3.0.0", + "@pulumi/aws": "^6.0.0", + "@pulumi/awsx": "^1.0.0", + "@pulumi/docker": "^4.4.0" + } +} \ No newline at end of file diff --git a/deploy/pulumi/taggable.ts b/deploy/pulumi/taggable.ts new file mode 100644 index 0000000..3067b45 --- /dev/null +++ b/deploy/pulumi/taggable.ts @@ -0,0 +1,237 @@ +/** + * isTaggable returns true if the given resource type is an AWS resource that supports tags. + */ + export function isTaggable(t: string): boolean { + return (taggableResourceTypes.indexOf(t) !== -1); +} + +// taggableResourceTypes is a list of known AWS type tokens that are taggable. +const taggableResourceTypes = [ + "aws:accessanalyzer/analyzer:Analyzer", + "aws:acm/certificate:Certificate", + "aws:acmpca/certificateAuthority:CertificateAuthority", + "aws:alb/loadBalancer:LoadBalancer", + "aws:alb/targetGroup:TargetGroup", + "aws:apigateway/apiKey:ApiKey", + "aws:apigateway/clientCertificate:ClientCertificate", + "aws:apigateway/domainName:DomainName", + "aws:apigateway/restApi:RestApi", + "aws:apigateway/stage:Stage", + "aws:apigateway/usagePlan:UsagePlan", + "aws:apigateway/vpcLink:VpcLink", + "aws:applicationloadbalancing/loadBalancer:LoadBalancer", + "aws:applicationloadbalancing/targetGroup:TargetGroup", + "aws:appmesh/mesh:Mesh", + "aws:appmesh/route:Route", + "aws:appmesh/virtualNode:VirtualNode", + "aws:appmesh/virtualRouter:VirtualRouter", + "aws:appmesh/virtualService:VirtualService", + "aws:appsync/graphQLApi:GraphQLApi", + "aws:athena/workgroup:Workgroup", + "aws:autoscaling/group:Group", + "aws:backup/plan:Plan", + "aws:backup/vault:Vault", + "aws:cfg/aggregateAuthorization:AggregateAuthorization", + "aws:cfg/configurationAggregator:ConfigurationAggregator", + "aws:cfg/rule:Rule", + "aws:cloudformation/stack:Stack", + "aws:cloudformation/stackSet:StackSet", + "aws:cloudfront/distribution:Distribution", + "aws:cloudhsmv2/cluster:Cluster", + "aws:cloudtrail/trail:Trail", + "aws:cloudwatch/eventRule:EventRule", + "aws:cloudwatch/logGroup:LogGroup", + "aws:cloudwatch/metricAlarm:MetricAlarm", + "aws:codebuild/project:Project", + "aws:codecommit/repository:Repository", + "aws:codepipeline/pipeline:Pipeline", + "aws:codepipeline/webhook:Webhook", + "aws:codestarnotifications/notificationRule:NotificationRule", + "aws:cognito/identityPool:IdentityPool", + "aws:cognito/userPool:UserPool", + "aws:datapipeline/pipeline:Pipeline", + "aws:datasync/agent:Agent", + "aws:datasync/efsLocation:EfsLocation", + "aws:datasync/locationSmb:LocationSmb", + "aws:datasync/nfsLocation:NfsLocation", + "aws:datasync/s3Location:S3Location", + "aws:datasync/task:Task", + "aws:dax/cluster:Cluster", + "aws:directconnect/connection:Connection", + "aws:directconnect/hostedPrivateVirtualInterfaceAccepter:HostedPrivateVirtualInterfaceAccepter", + "aws:directconnect/hostedPublicVirtualInterfaceAccepter:HostedPublicVirtualInterfaceAccepter", + "aws:directconnect/hostedTransitVirtualInterfaceAcceptor:HostedTransitVirtualInterfaceAcceptor", + "aws:directconnect/linkAggregationGroup:LinkAggregationGroup", + "aws:directconnect/privateVirtualInterface:PrivateVirtualInterface", + "aws:directconnect/publicVirtualInterface:PublicVirtualInterface", + "aws:directconnect/transitVirtualInterface:TransitVirtualInterface", + "aws:directoryservice/directory:Directory", + "aws:dlm/lifecyclePolicy:LifecyclePolicy", + "aws:dms/endpoint:Endpoint", + "aws:dms/replicationInstance:ReplicationInstance", + "aws:dms/replicationSubnetGroup:ReplicationSubnetGroup", + "aws:dms/replicationTask:ReplicationTask", + "aws:docdb/cluster:Cluster", + "aws:docdb/clusterInstance:ClusterInstance", + "aws:docdb/clusterParameterGroup:ClusterParameterGroup", + "aws:docdb/subnetGroup:SubnetGroup", + "aws:dynamodb/table:Table", + "aws:ebs/snapshot:Snapshot", + "aws:ebs/snapshotCopy:SnapshotCopy", + "aws:ebs/volume:Volume", + "aws:ec2/ami:Ami", + "aws:ec2/amiCopy:AmiCopy", + "aws:ec2/amiFromInstance:AmiFromInstance", + "aws:ec2/capacityReservation:CapacityReservation", + "aws:ec2/customerGateway:CustomerGateway", + "aws:ec2/defaultNetworkAcl:DefaultNetworkAcl", + "aws:ec2/defaultRouteTable:DefaultRouteTable", + "aws:ec2/defaultSecurityGroup:DefaultSecurityGroup", + "aws:ec2/defaultSubnet:DefaultSubnet", + "aws:ec2/defaultVpc:DefaultVpc", + "aws:ec2/defaultVpcDhcpOptions:DefaultVpcDhcpOptions", + "aws:ec2/eip:Eip", + "aws:ec2/fleet:Fleet", + "aws:ec2/instance:Instance", + "aws:ec2/internetGateway:InternetGateway", + "aws:ec2/keyPair:KeyPair", + "aws:ec2/launchTemplate:LaunchTemplate", + "aws:ec2/natGateway:NatGateway", + "aws:ec2/networkAcl:NetworkAcl", + "aws:ec2/networkInterface:NetworkInterface", + "aws:ec2/placementGroup:PlacementGroup", + "aws:ec2/routeTable:RouteTable", + "aws:ec2/securityGroup:SecurityGroup", + "aws:ec2/spotInstanceRequest:SpotInstanceRequest", + "aws:ec2/subnet:Subnet", + "aws:ec2/vpc:Vpc", + "aws:ec2/vpcDhcpOptions:VpcDhcpOptions", + "aws:ec2/vpcEndpoint:VpcEndpoint", + "aws:ec2/vpcEndpointService:VpcEndpointService", + "aws:ec2/vpcPeeringConnection:VpcPeeringConnection", + "aws:ec2/vpcPeeringConnectionAccepter:VpcPeeringConnectionAccepter", + "aws:ec2/vpnConnection:VpnConnection", + "aws:ec2/vpnGateway:VpnGateway", + "aws:ec2clientvpn/endpoint:Endpoint", + "aws:ec2transitgateway/routeTable:RouteTable", + "aws:ec2transitgateway/transitGateway:TransitGateway", + "aws:ec2transitgateway/vpcAttachment:VpcAttachment", + "aws:ec2transitgateway/vpcAttachmentAccepter:VpcAttachmentAccepter", + "aws:ecr/repository:Repository", + "aws:ecs/capacityProvider:CapacityProvider", + "aws:ecs/cluster:Cluster", + "aws:ecs/service:Service", + "aws:ecs/taskDefinition:TaskDefinition", + "aws:efs/fileSystem:FileSystem", + "aws:eks/cluster:Cluster", + "aws:eks/fargateProfile:FargateProfile", + "aws:eks/nodeGroup:NodeGroup", + "aws:elasticache/cluster:Cluster", + "aws:elasticache/replicationGroup:ReplicationGroup", + "aws:elasticbeanstalk/application:Application", + "aws:elasticbeanstalk/applicationVersion:ApplicationVersion", + "aws:elasticbeanstalk/environment:Environment", + "aws:elasticloadbalancing/loadBalancer:LoadBalancer", + "aws:elasticloadbalancingv2/loadBalancer:LoadBalancer", + "aws:elasticloadbalancingv2/targetGroup:TargetGroup", + "aws:elasticsearch/domain:Domain", + "aws:elb/loadBalancer:LoadBalancer", + "aws:emr/cluster:Cluster", + "aws:fsx/lustreFileSystem:LustreFileSystem", + "aws:fsx/windowsFileSystem:WindowsFileSystem", + "aws:gamelift/alias:Alias", + "aws:gamelift/build:Build", + "aws:gamelift/fleet:Fleet", + "aws:gamelift/gameSessionQueue:GameSessionQueue", + "aws:glacier/vault:Vault", + "aws:glue/crawler:Crawler", + "aws:glue/job:Job", + "aws:glue/trigger:Trigger", + "aws:iam/role:Role", + "aws:iam/user:User", + "aws:inspector/resourceGroup:ResourceGroup", + "aws:kinesis/analyticsApplication:AnalyticsApplication", + "aws:kinesis/firehoseDeliveryStream:FirehoseDeliveryStream", + "aws:kinesis/stream:Stream", + "aws:kms/externalKey:ExternalKey", + "aws:kms/key:Key", + "aws:lambda/function:Function", + "aws:lb/loadBalancer:LoadBalancer", + "aws:lb/targetGroup:TargetGroup", + "aws:licensemanager/licenseConfiguration:LicenseConfiguration", + "aws:lightsail/instance:Instance", + "aws:mediaconvert/queue:Queue", + "aws:mediapackage/channel:Channel", + "aws:mediastore/container:Container", + "aws:mq/broker:Broker", + "aws:mq/configuration:Configuration", + "aws:msk/cluster:Cluster", + "aws:neptune/cluster:Cluster", + "aws:neptune/clusterInstance:ClusterInstance", + "aws:neptune/clusterParameterGroup:ClusterParameterGroup", + "aws:neptune/eventSubscription:EventSubscription", + "aws:neptune/parameterGroup:ParameterGroup", + "aws:neptune/subnetGroup:SubnetGroup", + "aws:opsworks/stack:Stack", + "aws:organizations/account:Account", + "aws:pinpoint/app:App", + "aws:qldb/ledger:Ledger", + "aws:ram/resourceShare:ResourceShare", + "aws:rds/cluster:Cluster", + "aws:rds/clusterEndpoint:ClusterEndpoint", + "aws:rds/clusterInstance:ClusterInstance", + "aws:rds/clusterParameterGroup:ClusterParameterGroup", + "aws:rds/clusterSnapshot:ClusterSnapshot", + "aws:rds/eventSubscription:EventSubscription", + "aws:rds/instance:Instance", + "aws:rds/optionGroup:OptionGroup", + "aws:rds/parameterGroup:ParameterGroup", + "aws:rds/securityGroup:SecurityGroup", + "aws:rds/snapshot:Snapshot", + "aws:rds/subnetGroup:SubnetGroup", + "aws:redshift/cluster:Cluster", + "aws:redshift/eventSubscription:EventSubscription", + "aws:redshift/parameterGroup:ParameterGroup", + "aws:redshift/snapshotCopyGrant:SnapshotCopyGrant", + "aws:redshift/snapshotSchedule:SnapshotSchedule", + "aws:redshift/subnetGroup:SubnetGroup", + "aws:resourcegroups/group:Group", + "aws:route53/healthCheck:HealthCheck", + "aws:route53/resolverEndpoint:ResolverEndpoint", + "aws:route53/resolverRule:ResolverRule", + "aws:route53/zone:Zone", + "aws:s3/bucket:Bucket", + "aws:s3/bucketObject:BucketObject", + "aws:sagemaker/endpoint:Endpoint", + "aws:sagemaker/endpointConfiguration:EndpointConfiguration", + "aws:sagemaker/model:Model", + "aws:sagemaker/notebookInstance:NotebookInstance", + "aws:secretsmanager/secret:Secret", + "aws:servicecatalog/portfolio:Portfolio", + "aws:sfn/activity:Activity", + "aws:sfn/stateMachine:StateMachine", + "aws:sns/topic:Topic", + "aws:sqs/queue:Queue", + "aws:ssm/activation:Activation", + "aws:ssm/document:Document", + "aws:ssm/maintenanceWindow:MaintenanceWindow", + "aws:ssm/parameter:Parameter", + "aws:ssm/patchBaseline:PatchBaseline", + "aws:storagegateway/cachesIscsiVolume:CachesIscsiVolume", + "aws:storagegateway/gateway:Gateway", + "aws:storagegateway/nfsFileShare:NfsFileShare", + "aws:storagegateway/smbFileShare:SmbFileShare", + "aws:swf/domain:Domain", + "aws:transfer/server:Server", + "aws:transfer/user:User", + "aws:waf/rateBasedRule:RateBasedRule", + "aws:waf/rule:Rule", + "aws:waf/ruleGroup:RuleGroup", + "aws:waf/webAcl:WebAcl", + "aws:wafregional/rateBasedRule:RateBasedRule", + "aws:wafregional/rule:Rule", + "aws:wafregional/ruleGroup:RuleGroup", + "aws:wafregional/webAcl:WebAcl", + "aws:workspaces/directory:Directory", + "aws:workspaces/ipGroup:IpGroup", +]; diff --git a/deploy/pulumi/tsconfig.json b/deploy/pulumi/tsconfig.json new file mode 100644 index 0000000..d721ec9 --- /dev/null +++ b/deploy/pulumi/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "strict": true, + "outDir": "bin", + "target": "es2016", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "experimentalDecorators": true, + "pretty": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true, + "noImplicitAny": false, + "types": ["node"] + }, + "files": [ + "index.ts" + ] +} diff --git a/depot.json b/depot.json new file mode 100644 index 0000000..75167fc --- /dev/null +++ b/depot.json @@ -0,0 +1 @@ +{"id":"du7O4b0e8P"} diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..9a17eb7 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,23 @@ +services: + db: + image: postgres:14.4 + environment: + POSTGRES_DB: activepieces + POSTGRES_USER: postgres + POSTGRES_PASSWORD: A79Vm5D4p2VQHOp2gd5 + + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + + redis: + image: redis:7.0.7 + volumes: + - redis_data:/data + ports: + - "6379:6379" + +volumes: + postgres_data: + redis_data: \ No newline at end of file diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 0000000..63fcf37 --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,30 @@ +version: '3.0' + +services: + app: + extends: + file: docker-compose.dev.yml + service: app + user: "${UID}:${GID}" + command: /bin/sh -c "npm_config_cache=/usr/src/app/.npm-cache npx nx run-tests backend" + + postgres: + extends: + file: docker-compose.dev.yml + service: postgres + ports: + - "5432:5432" + + redis: + extends: + file: docker-compose.dev.yml + service: redis + ports: + - "6379:6379" + +volumes: + postgres_data_dev: + redis_data_dev: + +networks: + activepieces_dev: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..36c23b6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,43 @@ +version: '3.0' +services: + activepieces: + image: ghcr.io/activepieces/activepieces:0.64.2 + container_name: activepieces + restart: unless-stopped + ## Enable the following line if you already use AP_EXECUTION_MODE with SANDBOXED or old activepieces, checking the breaking change documentation for more info. + ## privileged: true + ports: + - '8080:80' + depends_on: + - postgres + - redis + env_file: .env + volumes: + - ./cache:/usr/src/app/cache + networks: + - activepieces + postgres: + image: 'postgres:14.4' + container_name: postgres + restart: unless-stopped + environment: + - 'POSTGRES_DB=${AP_POSTGRES_DATABASE}' + - 'POSTGRES_PASSWORD=${AP_POSTGRES_PASSWORD}' + - 'POSTGRES_USER=${AP_POSTGRES_USERNAME}' + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - activepieces + redis: + image: 'redis:7.0.7' + container_name: redis + restart: unless-stopped + volumes: + - 'redis_data:/data' + networks: + - activepieces +volumes: + postgres_data: + redis_data: +networks: + activepieces: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..e09ada0 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +# Start Nginx server +nginx -g "daemon off;" & + +# Start backend server +node --enable-source-maps dist/packages/server/api/main.js diff --git a/docs/_snippets/enterprise-feature.mdx b/docs/_snippets/enterprise-feature.mdx new file mode 100644 index 0000000..84c2733 --- /dev/null +++ b/docs/_snippets/enterprise-feature.mdx @@ -0,0 +1,3 @@ + +This feature is available in our paid editions. Contact us [here](https://www.activepieces.com/sales), and we'll be delighted to assist you! + \ No newline at end of file diff --git a/docs/_snippets/execution-mode.mdx b/docs/_snippets/execution-mode.mdx new file mode 100644 index 0000000..083a1ea --- /dev/null +++ b/docs/_snippets/execution-mode.mdx @@ -0,0 +1,5 @@ +| Name | Supports NPM in Code Piece | Requires Docker to be Privileged | Performance | Secure for Multi Tenant | Environment Variable | +|-------------------------------|----------------------------|----------------------------------|-----------------------|-------------------------|-------------------------------------------| +| V8/Code Sandboxing | ❌ | No | Fast & Lightweight | ✅ | Set `AP_EXECUTION_MODE` to `SANDBOX_CODE_ONLY` | +| No Sandboxin | ✅ | No | Fast & Lightweight | ❌ | Set `AP_EXECUTION_MODE` to `UNSANDBOXED` | +| Kernel Namespaces Sandboxing | ✅ | Yes | Slow & CPU Intensive | ✅ | Set `AP_EXECUTION_MODE` to `SANDBOXED` | diff --git a/docs/_snippets/profile/abdulyki.mdx b/docs/_snippets/profile/abdulyki.mdx new file mode 100644 index 0000000..23b976e --- /dev/null +++ b/docs/_snippets/profile/abdulyki.mdx @@ -0,0 +1,5 @@ + + Product Engineer + +
A stoic software engineer, looking to make the world a better place
+
diff --git a/docs/_snippets/profile/abood.mdx b/docs/_snippets/profile/abood.mdx new file mode 100644 index 0000000..6d368fd --- /dev/null +++ b/docs/_snippets/profile/abood.mdx @@ -0,0 +1,5 @@ + + **👋 Former** Product Engineer (Intern) + +
Speed isn't everything... But my code compiles before I finish my coffee
+
diff --git a/docs/_snippets/profile/aboodzein.mdx b/docs/_snippets/profile/aboodzein.mdx new file mode 100644 index 0000000..15fe1c1 --- /dev/null +++ b/docs/_snippets/profile/aboodzein.mdx @@ -0,0 +1,5 @@ + + Product Engineer (Part Time) + +
Quiet but lethal, building in stealth like an Arctic fox in the snow
+
diff --git a/docs/_snippets/profile/amr.mdx b/docs/_snippets/profile/amr.mdx new file mode 100644 index 0000000..fd5a62a --- /dev/null +++ b/docs/_snippets/profile/amr.mdx @@ -0,0 +1,5 @@ + + Product Engineer + +
Mo calls me Amr Database... He doesn't know that after one byte, I'm already full!
+
diff --git a/docs/_snippets/profile/ash.mdx b/docs/_snippets/profile/ash.mdx new file mode 100644 index 0000000..58f8cc3 --- /dev/null +++ b/docs/_snippets/profile/ash.mdx @@ -0,0 +1,5 @@ + + CEO + +
On a mission to democratize automation for everyone.⚡
+
diff --git a/docs/_snippets/profile/ginika.mdx b/docs/_snippets/profile/ginika.mdx new file mode 100644 index 0000000..40e5d51 --- /dev/null +++ b/docs/_snippets/profile/ginika.mdx @@ -0,0 +1,5 @@ + + Content Marketing + +
Coming soon!
+
diff --git a/docs/_snippets/profile/hazem.mdx b/docs/_snippets/profile/hazem.mdx new file mode 100644 index 0000000..8211a12 --- /dev/null +++ b/docs/_snippets/profile/hazem.mdx @@ -0,0 +1,5 @@ + + Product Engineer + +
Building the future of automation, one piece at a time 🚀
+
diff --git a/docs/_snippets/profile/issa.mdx b/docs/_snippets/profile/issa.mdx new file mode 100644 index 0000000..4caada7 --- /dev/null +++ b/docs/_snippets/profile/issa.mdx @@ -0,0 +1,5 @@ + + Product Designer (Part Time) + +
Coming soon!
+
diff --git a/docs/_snippets/profile/kareem.mdx b/docs/_snippets/profile/kareem.mdx new file mode 100644 index 0000000..add75fc --- /dev/null +++ b/docs/_snippets/profile/kareem.mdx @@ -0,0 +1,5 @@ + + Content + +
Coming soon!
+
diff --git a/docs/_snippets/profile/kishan.mdx b/docs/_snippets/profile/kishan.mdx new file mode 100644 index 0000000..3bc425a --- /dev/null +++ b/docs/_snippets/profile/kishan.mdx @@ -0,0 +1,5 @@ + + Community & Piece Manager + +
Coming soon!
+
diff --git a/docs/_snippets/profile/mo.mdx b/docs/_snippets/profile/mo.mdx new file mode 100644 index 0000000..55f8e2d --- /dev/null +++ b/docs/_snippets/profile/mo.mdx @@ -0,0 +1,5 @@ + + Product Engineer (CTO) + +
Former beaver, now human (major career change). Still can't resist building things, but traded wood for code 🌳 💻
+
diff --git a/docs/_snippets/profile/sanad.mdx b/docs/_snippets/profile/sanad.mdx new file mode 100644 index 0000000..1484510 --- /dev/null +++ b/docs/_snippets/profile/sanad.mdx @@ -0,0 +1,5 @@ + + Content + +
Fox of all trades, adaptable, and always with a paw in every task. If it smells like a problem, you can bet he'll sneak in and figure it out!
+
diff --git a/docs/_snippets/replace-oauth2-apps.mdx b/docs/_snippets/replace-oauth2-apps.mdx new file mode 100644 index 0000000..184fedd --- /dev/null +++ b/docs/_snippets/replace-oauth2-apps.mdx @@ -0,0 +1,3 @@ + +If you would like your users to use your own OAuth2 apps, we recommend you check [this](/admin-console/manage-oauth2). + \ No newline at end of file diff --git a/docs/about/breaking-changes.mdx b/docs/about/breaking-changes.mdx new file mode 100644 index 0000000..ea64d1d --- /dev/null +++ b/docs/about/breaking-changes.mdx @@ -0,0 +1,105 @@ +--- +title: "Breaking Changes" +description: "This list shows all versions that include breaking changes and how to upgrade." +icon: "hammer" +--- + +## 0.64.0 + +### What has changed? + +- MCP management is removed from the embedding SDK. + + +## 0.63.0 + +### What has changed? + +- Replicate provider's text models have been removed. + +### When is action necessary? + +- If you are using one of Replicate's text models, you should replace it with another model from another provider. + +## 0.46.0 + +### What has changed? + +- The UI for "Array of Properties" inputs in the pieces has been updated, particularly affecting the "Dynamic Value" toggle functionality. + +### When is action necessary? + +- No action is required for this change. +- Your published flows will continue to work without interruption. +- When editing existing flows that use the "Dynamic Value" toggle on "Array of Properties" inputs (such as the "files" parameter in the "Extract Structured Data" action of the "Utility AI" piece), the end user will need to remap the values again. +- For details on the new UI implementation, refer to this [announcement](https://community.activepieces.com/t/inline-items/8964). + +## 0.38.6 + +### What has changed? + +- Workers no longer rely on the `AP_FLOW_WORKER_CONCURRENCY` and `AP_SCHEDULED_WORKER_CONCURRENCY` environment variables. These values are now retrieved from the app server. + +### When is action necessary? + +- If `AP_CONTAINER_TYPE` is set to `WORKER` on the worker machine, and `AP_SCHEDULED_WORKER_CONCURRENCY` or `AP_FLOW_WORKER_CONCURRENCY` are set to zero on the app server, workers will stop processing the queues. To fix this, check the [Separate Worker from App](https://www.activepieces.com/docs/install/configuration/separate-workers) documentation and set the `AP_CONTAINER_TYPE` to fetch the necessary values from the app server. If no container type is set on the worker machine, this is not a breaking change. + +## 0.35.1 + +### What has changed? + +- The 'name' attribute has been renamed to 'externalId' in the `AppConnection` entity. +- The 'displayName' attribute has been added to the `AppConnection` entity. + +### When is action necessary? +- If you are using the connections API, you should update the `name` attribute to `externalId` and add the `displayName` attribute. + +## 0.35.0 + +### What has changed? + +- All branches are now converted to routers, and downgrade is not supported. + +## 0.33.0 + +### What has changed? + +- Files from actions or triggers are now stored in the database / S3 to support retries from certain steps, and the size of files from actions is now subject to the limit of `AP_MAX_FILE_SIZE_MB`. +- Files in triggers were previously passed as base64 encoded strings; now they are passed as file paths in the database / S3. Paused flows that have triggers from version 0.29.0 or earlier will no longer work. + +### When is action necessary? +- If you are dealing with large files in the actions, consider increasing the `AP_MAX_FILE_SIZE_MB` to a higher value, and make sure the storage system (database/S3) has enough capacity for the files. + + +## 0.30.0 + +### What has changed? + +- `AP_SANDBOX_RUN_TIME_SECONDS` is now deprecated and replaced with `AP_FLOW_TIMEOUT_SECONDS` +- `AP_CODE_SANDBOX_TYPE` is now deprecated and replaced with new mode in `AP_EXECUTION_MODE` + +### When is action necessary? + +- If you are using `AP_CODE_SANDBOX_TYPE` to `V8_ISOLATE`, you should switch to `AP_EXECUTION_MODE` to `SANDBOX_CODE_ONLY` +- If you are using `AP_SANDBOX_RUN_TIME_SECONDS` to set the sandbox run time limit, you should switch to `AP_FLOW_TIMEOUT_SECONDS` + +## 0.28.0 + +### What has changed? + +- **Project Members:** + - The `EXTERNAL_CUSTOMER` role has been deprecated and replaced with the `OPERATOR` role. Please check the permissions page for more details. + - All pending invitations will be removed. + - The User Invitation entity has been introduced to send invitations. You can still use the Project Member API to add roles for the user, but it requires the user to exist. If you want to send an email, use the User Invitation, and later a record in the project member will be created after the user accepts and registers an account. +- **Authentication:** + - The `SIGN_UP_ENABLED` environment variable, which allowed multiple users to sign up for different platforms/projects, has been removed. It has been replaced with inviting users to the same platform/project. All old users should continue to work normally. + +### When is action necessary? + +- **Project Members:** + +If you use the embedding SDK or the create project member API with the `EXTERNAL_CUSTOMER` role, you should start using the `OPERATOR` role instead. + +- **Authentication:** + +Multiple platforms/projects are no longer supported in the community edition. Technically, everything is still there, but you have to hack using the API as the authentication system has now changed. If you have already created the users/platforms, they should continue to work, and no action is required. diff --git a/docs/about/changelog.mdx b/docs/about/changelog.mdx new file mode 100755 index 0000000..7ae8bf8 --- /dev/null +++ b/docs/about/changelog.mdx @@ -0,0 +1,6 @@ +--- +title: "Changelog" +description: "A log of all notable changes to Activepieces" +icon: "code-commit" +url: "https://github.com/activepieces/activepieces/releases" +--- diff --git a/docs/about/editions.mdx b/docs/about/editions.mdx new file mode 100644 index 0000000..79a136b --- /dev/null +++ b/docs/about/editions.mdx @@ -0,0 +1,43 @@ +--- +title: "Editions" +description: "" +icon: "code-compare" +--- + +Activepieces operates on an open-core model, providing a core software platform as open source licensed under the permissive **MIT** license while offering additional features as proprietary add-ons in the cloud. + +### Community / Open Source Edition + +The Community edition is free and open source. It has all the pieces and features to build and run flows without any limitations. + +### Commercial Editions + +Learn more at: [https://www.activepieces.com/pricing](https://www.activepieces.com/pricing) + +## Feature Comparison + +| Feature | Community | Enterprise | Embed | +| ----------------------- | ----------- | -------- | -------| +| Flow History | ✅ | ✅ | ✅ | +| All Pieces | ✅ | ✅ | ✅ | +| Flow Runs | ✅ | ✅ | ✅ | +| Unlimited Flows | ✅ | ✅ | ✅ | +| Unlimited Connections | ✅ | ✅ | ✅ | +| Unlimited Flow steps | ✅ | ✅ | ✅ | +| Custom Pieces | ✅ | ✅ | ✅ | +| On Premise | ✅ | ✅ | ✅ | +| Cloud | ❌ | ✅ | ✅ | +| Project Team Members | ❌ | ✅ | ✅ | +| Manage Multiple Projects| ❌ | ✅ | ✅ | +| Limits Per Project | ❌ | ✅ | ✅ | +| Pieces Management | ❌ | ✅ | ✅ | +| Templates Management | ❌ | ✅ | ✅ | +| Custom Domain | ❌ | ✅ | ✅ | +| All Languages | ✅ | ✅ | ✅ | +| JWT Single Sign On | ❌ | ❌ | ✅ | +| Embed SDK | ❌ | ❌ | ✅ | +| Audit Logs | ❌ | ✅ | ❌ | +| Git Sync | ❌ | ✅ | ❌ | +| Private Pieces | ❌ | 5| 2| +| Custom Email Branding | ❌ | ✅ | ✅ | +| Custom Branding | ❌ | ✅ | ✅ | diff --git a/docs/about/i18n.mdx b/docs/about/i18n.mdx new file mode 100644 index 0000000..69079af --- /dev/null +++ b/docs/about/i18n.mdx @@ -0,0 +1,29 @@ +--- +title: "i18n Translations" +description: "" +icon: "language" +--- + + +This guide helps you understand how to change or add new translations. + +Activepieces uses Crowdin because it helps translators who don't know how to code. It also makes the approval process easier. Activepieces automatically sync new text from the code and translations back into the code. + +## Contribute to existing translations + +1. Create Crowdin account +2. Join the project https://crowdin.com/project/activepieces + +![Join Project](/resources/crowdin.png) + +3. Click on the language you want to translate + +4. Click on "Translate All" + +![Translate All](/resources/crowdin-translate-all.png) + +5. Select Strings you want to translate and click on "Save" button + + +## Adding a new language +- Please contact us (support@activepieces.com) if you want to add a new language. We will add it to the project and you can start translating. diff --git a/docs/about/license.mdx b/docs/about/license.mdx new file mode 100755 index 0000000..8190b05 --- /dev/null +++ b/docs/about/license.mdx @@ -0,0 +1,22 @@ +--- +title: "License" +description: "" +icon: 'file-contract' +--- + +Activepieces' **core** is released as open source under the [MIT license](https://github.com/activepieces/activepieces/blob/main/LICENSE) and enterprise / cloud editions features are released under [Commercial License](https://github.com/activepieces/activepieces/blob/main/packages/ee/LICENSE) + +The MIT license is a permissive license that grants users the freedom to use, modify, or distribute the software without any significant restrictions. The only requirement is that you include the license notice along with the software when distributing it. + +Using the enterprise features (under the packages/ee and packages/server/api/src/app/ee folder) with a self-hosted instance requires an Activepieces license. If you are looking for these features, contact us at [sales@activepieces.com](mailto:sales@activepieces.com). + +**Benefits of Dual Licensing Repo** + +- **Transparency** - Everyone can see what we are doing and contribute to the project. +- **Clarity** - Everyone can see what the difference is between the open source and commercial versions of our software. +- **Audit** - Everyone can audit our code and see what we are doing. +- **Faster Development** - We can develop faster and more efficiently. + + +If you are still confused or have feedback, please open an issue on GitHub or send a message in the #contribution channel on Discord. + diff --git a/docs/about/telemetry.mdx b/docs/about/telemetry.mdx new file mode 100644 index 0000000..5ff8929 --- /dev/null +++ b/docs/about/telemetry.mdx @@ -0,0 +1,32 @@ +--- +title: "Telemetry" +description: "" +icon: 'calculator' +--- + +# Why Does Activepieces need data? + +As a self-hosted product, gathering usage metrics and insights can be difficult for us. However, these analytics are essential in helping us understand key behaviors and delivering a higher quality experience that meets your needs. + +To ensure we can continue to improve our product, we have decided to track certain basic behaviors and metrics that are vital for understanding the usage of Activepieces. + +We have implemented a minimal tracking plan and provide a detailed list of the metrics collected in a separate section. + + +# What Does Activepieces Collect? + +We value transparency in data collection and assure you that we do not collect any personal information. The following events are currently being collected: + +[Exact Code](https://github.com/activepieces/activepieces/blob/main/packages/shared/src/lib/common/telemetry.ts) + +1. `flow.published`: Event fired when a flow is published +2. `signed.up`: Event fired when a user signs up +3. `flow.test`: Event fired when a flow is tested +4. `flow.created`: Event fired when a flow is created +5. `start.building`: Event fired when a user starts building +6. `demo.imported`: Event fired when a demo is imported +7. `flow.imported`: Event fired when a flow template is imported + +# Opting out? + +To opt out, set the environment variable `AP_TELEMETRY_ENABLED=false` \ No newline at end of file diff --git a/docs/admin-console/appearance.mdx b/docs/admin-console/appearance.mdx new file mode 100644 index 0000000..d7665e2 --- /dev/null +++ b/docs/admin-console/appearance.mdx @@ -0,0 +1,19 @@ +--- +title: "Appearance" +description: "" +icon: "palette" +--- + + + +Customize the brand by going to the **Platform Admin -> Setup -> Branding**. Here, you can customize: + +- Logo / FavIcon +- Primary color +- Platform Name + +![Branding Platform](/resources/screenshots/branding.png) + + + diff --git a/docs/admin-console/custom-domain.mdx b/docs/admin-console/custom-domain.mdx new file mode 100644 index 0000000..6445955 --- /dev/null +++ b/docs/admin-console/custom-domain.mdx @@ -0,0 +1,13 @@ +--- +title: "Custom Domains" +description: "" +icon: "globe" +--- + + + +You can set up a unique domain for your platform, like app.example.com.

+This is also used to determine the theme and branding on the authentication pages when a user is not logged in. + +**Platform Admin -> Setup -> Branding** +![Manage Projects](/resources/screenshots/custom-domain.png) diff --git a/docs/admin-console/customize-emails.mdx b/docs/admin-console/customize-emails.mdx new file mode 100644 index 0000000..508c39e --- /dev/null +++ b/docs/admin-console/customize-emails.mdx @@ -0,0 +1,11 @@ +--- +title: "Customize Emails" +description: "" +icon: "envelope" +--- + + + +You can add your own mail server to Activepieces, or override it if it's in the cloud. From the platform, all email templates are automatically whitelabeled according to the [appearance settings](./appearance). + +![Manage SMTP](/resources/screenshots/manage-smtp.png) diff --git a/docs/admin-console/manage-ai-providers.mdx b/docs/admin-console/manage-ai-providers.mdx new file mode 100644 index 0000000..4041f9f --- /dev/null +++ b/docs/admin-console/manage-ai-providers.mdx @@ -0,0 +1,35 @@ +--- +title: "Manage AI Providers" +description: "" +icon: "sparkles" +--- + +Set your AI providers so your users enjoy a seamless building experience with our universal AI pieces like [Text AI](https://www.activepieces.com/pieces/text-ai). + +## Manage AI Providers + +You can manage the AI providers that you want to use in your flows. To do this, go to the **AI** page in the **Admin Console**. + +You can define the provider's base URL and the API key. + +These settings will be used for all the projects for every request to the AI provider. + +![Manage AI Providers](/resources/screenshots/configure-ai-provider.png) + +## Configure AI Credits Limits Per Project + +You can configure the token limits per project. To do this, go to the project general settings and change the **AI Credits** field to the desired value. + + + This limit is per project and is an accumulation of all the reported usage by the AI piece in the project. + Since only the AI piece goes through the Activepieces API, + using any other piece like the standalone OpenAI, Anthropic or Perplexity pieces will not count towards or respect this limit. + + +![Manage AI Providers](/resources/screenshots/ai-credits-limit.png) + +### AI Credits Explained + +AI credits are the number tasks that can be run by any of our universal AI pieces. + +So if you have a flow run that contains 5 universal AI pieces steps, the AI credits consumed will be 5. diff --git a/docs/admin-console/manage-oauth2.mdx b/docs/admin-console/manage-oauth2.mdx new file mode 100644 index 0000000..4970882 --- /dev/null +++ b/docs/admin-console/manage-oauth2.mdx @@ -0,0 +1,12 @@ +--- +title: "Replace OAuth2 Apps" +description: "" +icon: "key" +--- + + + +Your project automatically uses Activepieces OAuth2 Apps as the default setting.

+If you prefer to use your own OAuth2 Apps, Go to **Platform Admin -> Setup -> Pieces** then choose a piece that uses OAuth2 like Google Sheets and click the open lock icon to configure your own OAuth2 app. + +![Manage Oauth2 apps](/resources/screenshots/manage-oauth2.png) \ No newline at end of file diff --git a/docs/admin-console/manage-pieces.mdx b/docs/admin-console/manage-pieces.mdx new file mode 100644 index 0000000..0d17416 --- /dev/null +++ b/docs/admin-console/manage-pieces.mdx @@ -0,0 +1,21 @@ +--- +title: "Manage Pieces" +description: "" +icon: "puzzle-piece" +--- + + + +## Show Specific Pieces in Project + +If you go to **Pieces Settings** in your project, you can manage which pieces you would like to be available to your users. + +![Manage Pieces](/resources/screenshots/manage-pieces.png) +![Manage Pieces](/resources/screenshots/manage-pieces-2.png) +## Install Piece + +- Go to **Platform Admin -> Setup -> Pieces** and hit Install pieces. +- You can choose to install a piece from NPM or upload a tar file directly for private pieces. +- You can check the [Sharing Pieces Doc](/developers/sharing-pieces/overview) for more info. + +![Manage Projects](/resources/screenshots/install-piece.png) diff --git a/docs/admin-console/manage-projects.mdx b/docs/admin-console/manage-projects.mdx new file mode 100644 index 0000000..d633e46 --- /dev/null +++ b/docs/admin-console/manage-projects.mdx @@ -0,0 +1,14 @@ +--- +title: "Managed Projects" +description: "" +icon: "building" +--- + + + +This feature helps you unlock these use cases: +1. Set up projects for different teams inside the company. +2. Set up projects automatically using the embedding feature for your SaaS customers. + + +You can **create** new projects and sets **limits** on the number of tasks for each project. diff --git a/docs/admin-console/manage-templates.mdx b/docs/admin-console/manage-templates.mdx new file mode 100644 index 0000000..df5fc29 --- /dev/null +++ b/docs/admin-console/manage-templates.mdx @@ -0,0 +1,13 @@ +--- +title: "Manage Templates" +description: "" +icon: "star" +--- + + + +You can create custom templates for your users within the Platform dashboard's. + + + diff --git a/docs/admin-console/overview.mdx b/docs/admin-console/overview.mdx new file mode 100644 index 0000000..cf424b6 --- /dev/null +++ b/docs/admin-console/overview.mdx @@ -0,0 +1,21 @@ +--- +title: "Overview" +description: "" +icon: "cube" +--- + + + +The platform is the admin panel for managing your instance. It's suitable for SaaS, Embed, or agencies that want to white-label Activepieces and offer it to their customers. With this platform, you can: + +1. **Custom Branding:** Tailor the appearance of the software to align with your brand's identity by selecting your own branding colors and fonts. + +2. **Projects Management:** Manage your projects, including creating, editing, and deleting projects. + +3. **Piece Management:** Take full control over Activepieces pieces. You can show or hide existing pieces and create your own unique pieces to customize the platform according to your specific needs. + +4. **User Authentication Management:** adding and removing users, and assigning roles to users. + +5. **Template Management:** Control prebuilt templates and add your own unique templates to meet the requirements of your users. + +6. **AI Provider Management:** Manage the AI providers that you want to use in your flows. diff --git a/docs/ai/mcp.mdx b/docs/ai/mcp.mdx new file mode 100644 index 0000000..539a90f --- /dev/null +++ b/docs/ai/mcp.mdx @@ -0,0 +1,51 @@ +--- +title: "MCP" +icon: 'brain' +description: "Give AI access to your tools through Activepieces" +--- + +## What is an MCP? + +LLMs produce text by default, but they're evolving to be able to use tools too. Say you want to ask Claude what meetings you have tomorrow, it can happen if you give it access to your calendar. + +**These tools live in an MCP Server that has a URL**. You provide your LLM (or MCP Client) with this URL so it can access your tools. + +There are many [MCP clients](https://github.com/punkpeye/awesome-mcp-clients) you can use for this purpose, and the most popular ones today are Claude Desktop, Cursor and Windsurf. + +**Official docs:** [https://modelcontextprotocol.io/introduction](https://modelcontextprotocol.io/introduction) + +## MCPs on Activepieces + +To use MCPs on Activepieces, we'll let you connect any of our [open source MCP tools](https://www.activepieces.com/mcp), and give you an MCP Server URL. Then, you'll configure your LLM to work with it. + +## Use Activepieces MCP Server + +1. **You need to run Activepieces.** It can run on our cloud or you can self-host it in your machine or infrastructure. + + ***Both options are for free, and all our MCP tools are open source.*** + + + +Use our cloud to run your MCP tools, or to just give it a test drive + + + + Deploy Activepieces using Docker or one of the other methods + + + + +2. **Connect your tools.** Go to AI → MCP in your Activepieces Dashboard, and start connecting the tools that you want to give AI access to. + +3. **Follow the instructions.** Click on your choice of MCP client (Claude Desktop, Cursor or Windsurf) and follow the instructions. + +4. **Chat with your LLM with superpowers 🚀** + + +## Things to try out with the MCP + +- Cancel all my meetings for tomorrow +- What tasks do I have to do today? +- Tweet this idea for me + +And many more! diff --git a/docs/developers/building-pieces/create-action.mdx b/docs/developers/building-pieces/create-action.mdx new file mode 100755 index 0000000..a1693ec --- /dev/null +++ b/docs/developers/building-pieces/create-action.mdx @@ -0,0 +1,109 @@ +--- +title: 'Create Action' +icon: 'circle-5' +description: '' +--- + +## Action Definition + +Now let's create first action which fetch random ice-cream flavor. + +```bash +npm run cli actions create +``` + +You will be asked three questions to define your new piece: + +1. `Piece Folder Name`: This is the name associated with the folder where the action resides. It helps organize and categorize actions within the piece. +2. `Action Display Name`: The name users see in the interface, conveying the action's purpose clearly. +3. `Action Description`: A brief, informative text in the UI, guiding users about the action's function and purpose. + Next, Let's create the action file: + +**Example:** + +```bash +npm run cli actions create + +? Enter the piece folder name : gelato +? Enter the action display name : get icecream flavor +? Enter the action description : fetches random icecream flavor. +``` + +This will create a new TypeScript file named `get-icecream-flavor.ts` in the `packages/pieces/community/gelato/src/lib/actions` directory. + +Inside this file, paste the following code: + +```typescript +import { + createAction, + Property, + PieceAuth, +} from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { gelatoAuth } from '../..'; + +export const getIcecreamFlavor = createAction({ + name: 'get_icecream_flavor', // Must be a unique across the piece, this shouldn't be changed. + auth: gelatoAuth, + displayName: 'Get Icecream Flavor', + description: 'Fetches random icecream flavor', + props: {}, + async run(context) { + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://cloud.activepieces.com/api/v1/webhooks/RGjv57ex3RAHOgs0YK6Ja/sync', + headers: { + Authorization: context.auth, // Pass API key in headers + }, + }); + return res.body; + }, +}); +``` + +The createAction function takes an object with several properties, including the `name`, `displayName`, `description`, `props`, and `run` function of the action. + +The `name` property is a unique identifier for the action. The `displayName` and `description` properties are used to provide a human-readable name and description for the action. + +The `props` property is an object that defines the properties that the action requires from the user. In this case, the action doesn't require any properties. + +The `run` function is the function that is called when the action is executed. It takes a single argument, context, which contains the values of the action's properties. + +The `run` function utilizes the httpClient.sendRequest function to make a GET request, fetching a random ice cream flavor. It incorporates API key authentication in the request headers. Finally, it returns the response body. + +## Expose The Definition + +To make the action readable by Activepieces, add it to the array of actions in the piece definition. + +```typescript +import { createPiece } from '@activepieces/pieces-framework'; +// Don't forget to add the following import. +import { getIcecreamFlavor } from './lib/actions/get-icecream-flavor'; + +export const gelato = createPiece({ + displayName: 'Gelato', + logoUrl: 'https://cdn.activepieces.com/pieces/gelato.png', + authors: [], + auth: gelatoAuth, + // Add the action here. + actions: [getIcecreamFlavor], // <-------- + triggers: [], +}); +``` + +# Testing + +By default, the development setup only builds specific components. Open the file `packages/server/api/.env` and include "gelato" in the `AP_DEV_PIECES`. + +For more details, check out the [Piece Development](../development-setup/getting-started) section. + +Once you edit the environment variable, restart the backend. The piece will be rebuilt. After this process, you'll need to **refresh** the frontend to see the changes. + + +If the build fails, try debugging by running `npx nx run-many -t build --projects=gelato`. +It will display any errors in your code. + + +To test the action, use the flow builder in Activepieces. It should function as shown in the screenshot. + +![Gelato Action](/resources/screenshots/gelato-action.png) diff --git a/docs/developers/building-pieces/create-trigger.mdx b/docs/developers/building-pieces/create-trigger.mdx new file mode 100644 index 0000000..ad2ee6e --- /dev/null +++ b/docs/developers/building-pieces/create-trigger.mdx @@ -0,0 +1,137 @@ +--- +title: 'Create Trigger' +icon: 'circle-6' +description: '' +--- + +This tutorial will guide you through the process of creating trigger for a Gelato piece that fetches new icecream flavor. + +## Trigger Definition + +To create trigger run the following command, + +```bash +npm run cli triggers create +``` + +1. `Piece Folder Name`: This is the name associated with the folder where the trigger resides. It helps organize and categorize triggers within the piece. +2. `Trigger Display Name`: The name users see in the interface, conveying the trigger's purpose clearly. +3. `Trigger Description`: A brief, informative text in the UI, guiding users about the trigger's function and purpose. +4. `Trigger Technique`: Specifies the trigger type - either [polling](../piece-reference/triggers/polling-trigger) or [webhook](../piece-reference/triggers/webhook-trigger). + +**Example:** + +```bash +npm run cli triggers create + +? Enter the piece folder name : gelato +? Enter the trigger display name : new flavor created +? Enter the trigger description : triggers when a new icecream flavor is created. +? Select the trigger technique: polling +``` + +This will create a new TypeScript file at `packages/pieces/community/gelato/src/lib/triggers` named `new-flavor-created.ts`. + +Inside this file, paste the following code: + +```ts +import { gelatoAuth } from '../../'; +import { + DedupeStrategy, + HttpMethod, + HttpRequest, + Polling, + httpClient, + pollingHelper, +} from '@activepieces/pieces-common'; +import { + PiecePropValueSchema, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; + +const polling: Polling< + PiecePropValueSchema, + Record +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + const request: HttpRequest = { + method: HttpMethod.GET, + url: 'https://cloud.activepieces.com/api/v1/webhooks/aHlEaNLc6vcF1nY2XJ2ed/sync', + headers: { + authorization: auth, + }, + }; + const res = await httpClient.sendRequest(request); + return res.body['flavors'].map((flavor: string) => ({ + epochMilliSeconds: dayjs().valueOf(), + data: flavor, + })); + }, +}; + +export const newFlavorCreated = createTrigger({ + auth: gelatoAuth, + name: 'newFlavorCreated', + displayName: 'new flavor created', + description: 'triggers when a new icecream flavor is created.', + props: {}, + sampleData: {}, + type: TriggerStrategy.POLLING, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async onEnable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onEnable(polling, { store, auth, propsValue }); + }, + + async onDisable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onDisable(polling, { store, auth, propsValue }); + }, + + async run(context) { + return await pollingHelper.poll(polling, context); + }, +}); +``` + +The way polling triggers usually work is as follows: + +`Run`:The run method executes every 5 minutes, fetching data from the endpoint within a specified timestamp range or continuing until it identifies the last item ID. It then returns the new items as an array. In this example, the httpClient.sendRequest method is utilized to retrieve new flavors, which are subsequently stored in the store along with a timestamp. + +## Expose The Definition + +To make the trigger readable by Activepieces, add it to the array of triggers in the piece definition. + +```typescript +import { createPiece } from '@activepieces/pieces-framework'; +import { getIcecreamFlavor } from './lib/actions/get-icecream-flavor'; +// Don't forget to add the following import. +import { newFlavorCreated } from './lib/triggers/new-flavor-created'; + +export const gelato = createPiece({ + displayName: 'Gelato Tutorial', + logoUrl: 'https://cdn.activepieces.com/pieces/gelato.png', + authors: [], + auth: gelatoAuth, + actions: [getIcecreamFlavor], + // Add the trigger here. + triggers: [newFlavorCreated], // <-------- +}); +``` + +# Testing + +By default, the development setup only builds specific components. Open the file `packages/server/api/.env` and include "gelato" in the `AP_DEV_PIECES`. + +For more details, check out the [Piece Development](../development-setup/getting-started) section. + +Once you edit the environment variable, restart the backend. The piece will be rebuilt. After this process, you'll need to **refresh** the frontend to see the changes. + +To test the trigger, use the load sample data from flow builder in Activepieces. It should function as shown in the screenshot. + +![Gelato Action](/resources/screenshots/gelato-trigger.png) \ No newline at end of file diff --git a/docs/developers/building-pieces/overview.mdx b/docs/developers/building-pieces/overview.mdx new file mode 100755 index 0000000..5d0d946 --- /dev/null +++ b/docs/developers/building-pieces/overview.mdx @@ -0,0 +1,34 @@ +--- +title: 'Overview' +description: 'This section helps developers build and contribute pieces.' +icon: 'hand-wave' +--- + +Building pieces is fun and important; it allows you to customize Activepieces for your own needs. + + + We love contributions! In fact, most of the pieces are contributed by the community. Feel free to open a pull request. + + +**Friendly Tip:** +For the fastest support, we recommend joining our Discord community. We are dedicated to addressing every question and concern raised there. + + + + + + Build pieces using TypeScript for a more powerful and flexible development process. + + + See your changes in the browser within 7 seconds. + + + Work within the open-source environment, explore, and contribute to other pieces. + + + Join our large community, where you can ask questions, share ideas, and develop alongside others. + + + Use the Universal SDK to quickly build AI-powered pieces that support multiple AI providers. + + diff --git a/docs/developers/building-pieces/piece-authentication.mdx b/docs/developers/building-pieces/piece-authentication.mdx new file mode 100644 index 0000000..76d64d0 --- /dev/null +++ b/docs/developers/building-pieces/piece-authentication.mdx @@ -0,0 +1,38 @@ +--- +title: 'Add Piece Authentication' +icon: 'circle-4' +description: '' +--- + + +### Piece Authentication + +Activepieces supports multiple forms of authentication, you can check those forms [here](../piece-reference/authentication) + +Now, let's establish authentication for this piece, which necessitates the inclusion of an API Key in the headers. + +Modify `src/index.ts` file to add authentication, + +```ts +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; + +export const gelatoAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'Please use **test-key** as value for API Key', +}); + +export const gelato = createPiece({ + displayName: 'Gelato', + logoUrl: 'https://cdn.activepieces.com/pieces/gelato.png', + auth: gelatoAuth, + authors: [], + actions: [], + triggers: [], +}); +``` + + + Use the value **test-key** as the API key when testing actions or triggers for + Gelato. + \ No newline at end of file diff --git a/docs/developers/building-pieces/piece-definition.mdx b/docs/developers/building-pieces/piece-definition.mdx new file mode 100755 index 0000000..17dcbef --- /dev/null +++ b/docs/developers/building-pieces/piece-definition.mdx @@ -0,0 +1,51 @@ +--- +title: 'Create Piece Definition' +icon: 'circle-3' +description: '' +--- + + +This tutorial will guide you through the process of creating a Gelato piece with an action that fetches random icecream flavor and trigger that fetches new icecream flavor created. It assumes that you are familiar with the following: + +- [Activepieces Local development](../development-setup/local) Or [GitHub Codespaces](../development-setup/codespaces). +- TypeScript syntax. + +## Piece Definition + +To get started, let's generate a new piece for Gelato + +```bash +npm run cli pieces create +``` + +You will be asked three questions to define your new piece: + +1. `Piece Name`: Specify a name for your piece. This name uniquely identifies your piece within the ActivePieces ecosystem. +2. `Package Name`: Optionally, you can enter a name for the npm package associated with your piece. If left blank, the default name will be used. +3. `Piece Type`: Choose the piece type based on your intention. It can be either "custom" if it's a tailored solution for your needs, or "community" if it's designed to be shared and used by the broader community. + +**Example:** + +```bash +npm run cli pieces create + +? Enter the piece name: gelato +? Enter the package name: @activepieces/piece-gelato +? Select the piece type: community +``` + +The piece will be generated at `packages/pieces/community/gelato/`, +the `src/index.ts` file should contain the following code + +```ts +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; + +export const gelato = createPiece({ + displayName: 'Gelato', + logoUrl: 'https://cdn.activepieces.com/pieces/gelato.png', + auth: PieceAuth.None(), + authors: [], + actions: [], + triggers: [], +}); +``` diff --git a/docs/developers/building-pieces/setup-fork.mdx b/docs/developers/building-pieces/setup-fork.mdx new file mode 100644 index 0000000..55bcb1c --- /dev/null +++ b/docs/developers/building-pieces/setup-fork.mdx @@ -0,0 +1,18 @@ +--- +title: 'Fork Repository' +icon: "circle-1" +--- + +To start building pieces, we need to fork the repository that contains the framework library and the development environment. Later, we will publish these pieces as `npm` artifacts. + +Follow these steps to fork the repository: + +1. Go to the repository page at https://github.com/activepieces/activepieces. +2. Click the `Fork` button located in the top right corner of the page. + +![Fork Repository](/resources/screenshots/fork-repository.jpg) + + + +If you are an enterprise customer and want to use the private pieces feature, you can refer to the tutorial on how to set up a [private fork](../misc/private-fork). + \ No newline at end of file diff --git a/docs/developers/building-pieces/start-building.mdx b/docs/developers/building-pieces/start-building.mdx new file mode 100644 index 0000000..318aa76 --- /dev/null +++ b/docs/developers/building-pieces/start-building.mdx @@ -0,0 +1,41 @@ +--- +title: 'Start Building' +icon: 'hammer' +description: '' +--- +This section guides you in creating a Gelato piece, from setting up your development environment to contributing the piece. By the end of this tutorial, you will have a piece with an action that fetches a random ice cream flavor and a trigger that fetches newly created ice cream flavors. + + +These are the next sections. In each step, we will do one small thing. This tutorial should take around 30 minutes. + + +## Steps Overview + + + + Fork the repository to create your own copy of the codebase. + + + Set up your development environment with the necessary tools and dependencies. + + + Define the structure and behavior of your Gelato piece. + + + Implement authentication mechanisms for your Gelato piece. + + + Create an action that fetches a random ice cream flavor. + + + Create a trigger that fetches newly created ice cream flavors. + + + Share your Gelato piece with others. + + + + + + Contribute a piece to our repo and receive +1,400 tasks/month on [Activepieces Cloud](https://cloud.activepieces.com). + diff --git a/docs/developers/development-setup/codespaces.mdx b/docs/developers/development-setup/codespaces.mdx new file mode 100755 index 0000000..165ab10 --- /dev/null +++ b/docs/developers/development-setup/codespaces.mdx @@ -0,0 +1,30 @@ +--- +title: 'GitHub Codespaces' +description: '' +--- + +GitHub Codespaces is a cloud development platform that enables developers to write, run, and debug code directly in their browsers, seamlessly integrated with GitHub. + +### Steps to setup Codespaces + +1. Go to [Activepieces repo](https://github.com/activepieces/activepieces). + +2. Click Code `<>`, then under codespaces click create codespace on main. + +![Create Codespace](/resources/screenshots/development-setup_codespaces.png) + + + By default, the development setup only builds specific pieces.Open the file + `packages/server/api/.env` and add comma-separated list of pieces to make + available. + +For more details, check out the [Piece Development](/developers/development-setup/getting-started) section. + + + +3. Open the terminal and run `npm start` + +4. Access the frontend URL by opening port 4200 and signing in with these details: + +Email: `dev@ap.com` +Password: `12345678` diff --git a/docs/developers/development-setup/dev-container.mdx b/docs/developers/development-setup/dev-container.mdx new file mode 100755 index 0000000..172b133 --- /dev/null +++ b/docs/developers/development-setup/dev-container.mdx @@ -0,0 +1,57 @@ +--- +title: 'Dev Containers' +description: '' +--- + +## Using Dev Containers in Visual Studio Code + +The project includes a dev container configuration that allows you to use Visual Studio Code's [Remote Development](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack) extension to develop the project in a consistent environment. This can be especially helpful if you are new to the project or if you have a different environment setup on your local machine. + +## Prerequisites + +Before you can use the dev container, you will need to install the following: + +- [Visual Studio Code](https://code.visualstudio.com/). +- The [Remote Development](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack) extension for Visual Studio Code. +- [Docker](https://www.docker.com/). + +## Using the Dev Container + +To use the dev container for the Activepieces project, follow these steps: + +1. Clone the Activepieces repository to your local machine. +2. Open the project in Visual Studio Code. +3. Press `Ctrl+Shift+P` and type `> Dev Containers: Reopen in Container`. +4. Run `npm start`. +5. The backend will run at `localhost:3000` and the frontend will run at `localhost:4200`. + + + By default, the development setup only builds specific pieces.Open the file + `packages/server/api/.env` and add comma-separated list of pieces to make + available. + +For more details, check out the [Piece Development](/developers/development-setup/getting-started) section. + + + +The login credentials are: +Email: `dev@ap.com` +Password: `12345678` + +## Exiting the Dev Container + +To exit the dev container and return to your local environment, follow these steps: + +1. In the bottom left corner of Visual Studio Code, click the `Remote-Containers: Reopen folder locally` button. +2. Visual Studio Code will close the connection to the dev container and reopen the project in your local environment. + +## Troubleshoot + +One of the best trouble shoot after an error occur is to reset the dev container. + +1. Exit the dev container +2. Run the following + ```sh + sh tools/reset-dev.sh + ``` +3. Rebuild the dev container from above steps diff --git a/docs/developers/development-setup/getting-started.mdx b/docs/developers/development-setup/getting-started.mdx new file mode 100644 index 0000000..a4b0781 --- /dev/null +++ b/docs/developers/development-setup/getting-started.mdx @@ -0,0 +1,25 @@ +--- +title: 'Getting Started' +description: '' +--- + +## Development Setup + +To set up the development environment, you can choose one of the following methods: + +- **Codespaces**: This is the quickest way to set up the development environment. Follow the [Codespaces](./codespaces) guide. +- **Local Environment**: It is recommended for local development. Follow the [Local Environment](./local) guide. +- **Dev Container**: This method is suitable for remote development on another machine. Follow the [Dev Container](./dev-container) guide. + + +## Pieces Development + +To avoid making the dev environment slow, not all pieces are functional during development at first. By default, only these pieces are functional at first, as specified in `AP_DEV_PIECES`. + +https://github.com/activepieces/activepieces/blob/main/packages/server/api/.env#L4 + +To override the default list available at first, define an `AP_DEV_PIECES` environment variable with a comma-separated list of pieces to make available. For example, to make `google-sheets` and `cal-com` available, you can use: + +```sh +AP_DEV_PIECES=google-sheets,cal-com npm start +``` diff --git a/docs/developers/development-setup/local.mdx b/docs/developers/development-setup/local.mdx new file mode 100755 index 0000000..70b2fce --- /dev/null +++ b/docs/developers/development-setup/local.mdx @@ -0,0 +1,39 @@ +--- +title: 'Local Dev Environment' +description: '' +--- + +## Prerequisites + +- Node.js v18+ +- npm v9+ + +## Instructions + +1. Setup the environment + +```bash +node tools/setup-dev.js +``` + +2. Start the environment + +This command will start activepieces with sqlite3 and in memory queue. + +```bash +npm start +``` + + + By default, the development setup only builds specific pieces.Open the file + `packages/server/api/.env` and add comma-separated list of pieces to make + available. + +For more details, check out the [Piece Development](/developers/development-setup/getting-started) section. + + + +3. Go to **_localhost:4200_** on your web browser and sign in with these details: + +Email: `dev@ap.com` +Password: `12345678` diff --git a/docs/developers/misc/build-piece.mdx b/docs/developers/misc/build-piece.mdx new file mode 100644 index 0000000..4525421 --- /dev/null +++ b/docs/developers/misc/build-piece.mdx @@ -0,0 +1,33 @@ +--- +title: 'Build Custom Pieces' +icon: 'box' +--- + +You can use the CLI to build custom pieces for the platform. This process compiles the pieces and exports them as a `.tgz` packed archive. + +### How It Works + +The CLI scans the `packages/pieces/` directory for the specified piece. It checks the **name** in the `package.json` file. If the piece is found, it builds and packages it into a `.tgz` archive. + +### Usage + +To build a piece, follow these steps: + +1. Ensure you have the CLI installed by cloning the repository. +2. Run the following command: + +```bash +npm run build-piece +``` + +You will be prompted to enter the name of the piece you want to build. For example: + +```bash +? Enter the piece folder name : google-drive +``` + +The CLI will build the piece and you will be given the path to the archive. For example: + +```bash +Piece 'google-drive' built and packed successfully at dist/packages/pieces/community/google-drive +``` \ No newline at end of file diff --git a/docs/developers/misc/create-new-ai-provider.mdx b/docs/developers/misc/create-new-ai-provider.mdx new file mode 100644 index 0000000..9398b32 --- /dev/null +++ b/docs/developers/misc/create-new-ai-provider.mdx @@ -0,0 +1,101 @@ +--- +title: 'Create New AI Provider' +icon: 'sparkles' +--- + +ActivePieces currently supports the following AI providers: + +- OpenAI +- Anthropic + +To create a new AI provider, you need to follow these steps: + +## Implement the AI Interface + +Create a new factory that returns an instance of the `AI` interface in the `packages/pieces/community/common/src/lib/ai/providers/your-ai-provider.ts` file. + +```typescript +export const yourAiProvider = ({ + serverUrl, + engineToken, +}: { serverUrl: string, engineToken: string }): AI => { + const impl = new YourAiProviderSDK(serverUrl, engineToken); + return { + provider: "YOUR_AI_PROVIDER" as const, + chat: { + text: async (params) => { + try { + const response = await impl.chat.text(params); + return response; + } catch (e: any) { + if (e?.error?.error) { + throw e.error.error; + } + throw e; + } + } + }, + }; +}; +``` + +## Register the AI Provider + +Add the new AI provider to the `AiProviders` enum in `packages/pieces/community/common/src/lib/ai/providers/index.ts` file. + +```diff +export const AiProviders = [ ++ { ++ logoUrl: 'https://cdn.activepieces.com/pieces/openai.png', ++ defaultBaseUrl: 'https://api.your-ai-provider.com', ++ label: 'Your AI Provider' as const, ++ value: 'your-ai-provider' as const, ++ models: [ ++ { label: 'model-1', value: 'model-1' }, ++ { label: 'model-2', value: 'model-2' }, ++ { label: 'model-3', value: 'model-3' }, ++ ], ++ factory: yourAiProvider, ++ }, +... +] +``` + +## Define Authentication Header + +Now we need to tell ActivePieces how to authenticate to your AI provider. You can do this by adding an `auth` property to the `AiProvider` object. + +The `auth` property is an object that defines the authentication mechanism for your AI provider. It consists of two properties: `name` and `mapper`. The `name` property specifies the name of the header that will be used to authenticate with your AI provider, and the `mapper` property defines a function that maps the value of the header to the format that your AI provider expects. + +Here's an example of how to define the authentication header for a bearer token: + +```diff +export const AiProviders = [ + { + logoUrl: 'https://cdn.activepieces.com/pieces/openai.png', + defaultBaseUrl: 'https://api.your-ai-provider.com', + label: 'Your AI Provider' as const, + value: 'your-ai-provider' as const, + models: [ + { label: 'model-1', value: 'model-1' }, + { label: 'model-2', value: 'model-2' }, + { label: 'model-3', value: 'model-3' }, + ], ++ auth: authHeader({ bearer: true }), // or authHeader({ name: 'x-api-key', bearer: false }) + factory: yourAiProvider, + }, +... +] +``` + +## Test the AI Provider + +To test the AI provider, you can use a **universal AI** piece in a flow. Follow these steps: + +- Add the required headers from the admin console for the newly created AI provider. These headers will be used to authenticate the requests to the AI provider. + +![Configure AI Provider](/resources/screenshots/configure-ai-provider.png) + +- Create a flow that uses our **universal AI** pieces. And select **"Your AI Provider"** as the AI provider in the **Ask AI** action settings. + +![Configure AI Provider](/resources/screenshots/use-ai-provider.png) diff --git a/docs/developers/misc/pieces-ci-cd.mdx b/docs/developers/misc/pieces-ci-cd.mdx new file mode 100644 index 0000000..ee9bd89 --- /dev/null +++ b/docs/developers/misc/pieces-ci-cd.mdx @@ -0,0 +1,82 @@ +--- +title: 'Custom Pieces CI/CD' +icon: 'hammer' +--- + +You can use the CLI to sync custom pieces. There is no need to rebuild the Docker image as they are loaded directly from npm. + +### How It Works + +Use the CLI to sync items from `packages/pieces/custom/` to instances. In production, Activepieces acts as an npm registry, storing all piece versions. + +The CLI scans the directory for `package.json` files, checking the **name** and **version** of each piece. If a piece isn't uploaded, it packages and uploads it via the API. + +### Usage + +To use the CLI, follow these steps: + +1. Generate an API Key from the Admin Interface. Go to Settings and generate the API Key. +2. Install the CLI by cloning the repository. +3. Run the following command, replacing `API_KEY` with your generated API Key and `INSTANCE_URL` with your instance URL: + + +```bash +AP_API_KEY=your_api_key_here npm run sync-pieces -- --apiUrl https://INSTANCE_URL/api +``` + +### Developer Workflow + +1. Developers create and modify the pieces offline. +2. Increment the piece version in their corresponding `package.json`. For more information, refer to the [piece versioning](../../developers/piece-reference/piece-versioning) documentation. +3. Open a pull request towards the main branch. +4. Once the pull request is merged to the main branch, manually run the CLI or use a GitHub/GitLab Action to trigger the synchronization process. + +### GitHub Action + +```yaml +name: Sync Custom Pieces + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + sync-pieces: + runs-on: ubuntu-latest + + steps: + # Step 1: Check out the repository code with full history + - name: Check out repository code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + # Step 2: Cache Node.js dependencies + - name: Cache Node.js dependencies + uses: actions/cache@v3 + with: + path: ~/.npm + key: npm-${{ hashFiles('package-lock.json') }} + restore-keys: | + npm- + + # Step 3: Set up Node.js + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' # Use Node.js version 20 + cache: 'npm' + + # Step 4: Install dependencies using npm ci + - name: Install dependencies + run: npm ci --ignore-scripts + + # Step 6: Sync Custom Pieces + - name: Sync Custom Pieces + env: + AP_API_KEY: ${{ secrets.AP_API_KEY }} + run: npm run sync-pieces -- --apiUrl ${{ secrets.INSTANCE_URL }}/api + +``` diff --git a/docs/developers/misc/private-fork.mdx b/docs/developers/misc/private-fork.mdx new file mode 100644 index 0000000..aeedfcb --- /dev/null +++ b/docs/developers/misc/private-fork.mdx @@ -0,0 +1,84 @@ +--- +title: 'Setup Private Fork' +icon: "code-branch" +--- + + +**Friendly Tip #1:** If you want to experiment, you can fork or clone the public repository. + + + +For private piece installation, you will need the paid edition. However, you can still develop pieces, contribute them back, **OR** publish them to the public npm registry and use them in your own instance or project. + + +## Create a Private Fork (Private Pieces) + +By following these steps, you can create a private fork on GitHub, GitLab or another platform and configure the "activepieces" repository as the upstream source, allowing you to incorporate changes from the "activepieces" repository. + +1. **Clone the Repository:** + +Begin by creating a bare clone of the repository. Remember that this is a temporary step and will be deleted later. + +```bash +git clone --bare git@github.com:activepieces/activepieces.git + ``` + +2. **Create a Private Git Repository** + +Generate a new private repository on GitHub or your chosen platform. When initializing the new repository, do not include a README, license, or gitignore files. This precaution is essential to avoid merge conflicts when synchronizing your fork with the original repository. + +3. **Mirror-Push to the Private Repository:** + +Mirror-push the bare clone you created earlier to your newly created "activepieces" repository. Make sure to replace `` in the URL below with your actual GitHub username. + +```bash +cd activepieces.git +git push --mirror git@github.com:/activepieces.git +``` + +4. **Remove the Temporary Local Repository:** + +```bash +cd .. +rm -rf activepieces.git +``` + +5. **Clone Your Private Repository:** + +Now, you can clone your "activepieces" repository onto your local machine into your desired directory. + +```bash +cd ~/path/to/directory +git clone git@github.com:/activepieces.git +``` + +6. **Add the Original Repository as a Remote:** + +If desired, you can add the original repository as a remote to fetch potential future changes. However, remember to disable push operations for this remote, as you are not permitted to push changes to it. + +```bash +git remote add upstream git@github.com:activepieces/activepieces.git +git remote set-url --push upstream DISABLE +``` + +You can view a list of all your remotes using `git remote -v`. It should resemble the following: + +``` +origin git@github.com:/activepieces.git (fetch) +origin git@github.com:/activepieces.git (push) +upstream git@github.com:activepieces/activepieces.git (fetch) +upstream DISABLE (push) +``` + +> When pushing changes, always use `git push origin`. + +### Sync Your Fork + +To retrieve changes from the "upstream" repository, fetch the remote and rebase your work on top of it. + +```bash +git fetch upstream +git merge upstream/main +``` + +Conflict resolution should not be necessary since you've only added pieces to your repository. \ No newline at end of file diff --git a/docs/developers/misc/publish-piece.mdx b/docs/developers/misc/publish-piece.mdx new file mode 100644 index 0000000..ffd25d2 --- /dev/null +++ b/docs/developers/misc/publish-piece.mdx @@ -0,0 +1,57 @@ +--- +title: 'Publish Custom Pieces' +icon: 'upload' +--- + +You can use the CLI to publish custom pieces to the platform. This process packages the pieces and uploads them to the specified API endpoint. + +### How It Works + +The CLI scans the `packages/pieces/` directory for the specified piece. It checks the **name** and **version** in the `package.json` file. If the piece is not already published, it builds, packages, and uploads it to the platform using the API. + +### Usage + +To publish a piece, follow these steps: + +1. Ensure you have an API Key. Generate it from the Admin Interface by navigating to Settings. +2. Install the CLI by cloning the repository. +3. Run the following command: + +```bash +npm run publish-piece-to-api +``` + +4. You will be asked three questions to publish your piece: + + - `Piece Folder Name`: This is the name associated with the folder where the action resides. It helps organize and categorize actions within the piece. + + - `API URL`: This is the URL of the API endpoint where the piece will be published (ex: https://cloud.activepieces.com/api). + + - `API Key Source`: This is the source of the API key. It can be either `Env Variable (AP_API_KEY)` or `Manually`. + + +In case you choose `Env Variable (AP_API_KEY)`, the CLI will use the API key from the `.env` file in the `packages/server/api` directory. + +In case you choose `Manually`, you will be asked to enter the API key. + + +Examples: + +```bash +npm run publish-piece-to-api + +? Enter the piece folder name : google-drive +? Enter the API URL : https://cloud.activepieces.com/api +? Enter the API Key Source : Env Variable (AP_API_KEY) + +``` + +```bash +npm run publish-piece-to-api + +? Enter the piece folder name : google-drive +? Enter the API URL : https://cloud.activepieces.com/api +? Enter the API Key Source : Manually +? Enter the API Key : ap_1234567890abcdef1234567890abcdef + +``` \ No newline at end of file diff --git a/docs/developers/piece-reference/ai-providers.mdx b/docs/developers/piece-reference/ai-providers.mdx new file mode 100644 index 0000000..9cdbb5e --- /dev/null +++ b/docs/developers/piece-reference/ai-providers.mdx @@ -0,0 +1,300 @@ +--- +title: 'AI SDK & Providers' +icon: 'brain' +description: 'The AI Toolkit to build AI pieces tailored for specific use cases that work with many AI providers using the AI SDK' +--- + +**What it provides:** + +- 🔐 **Centralized Credentials Management**: Admin manages credentials, end users use without hassle. +- 🌐 **Support for Multiple AI Providers**: OpenAI, Anthropic, and many open-source models. +- 💬 **Support for Various AI Capabilities**: Chat, Image, Agents, and more. + +## Using the AI SDK + +Activepieces integrates with the [AI SDK](https://ai-sdk.dev/) to provide a unified interface for calling LLMs across multiple AI providers. Here's an example on how to use the AI SDK's `generateText` function to call an LLM in your actions. + +```typescript +import { aiProps } from '@activepieces/pieces-common'; +import { SUPPORTED_AI_PROVIDERS, createAIProvider } from '@activepieces/shared'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { LanguageModel, generateText } from 'ai'; + +export const askAI = createAction({ + name: 'askAi', + displayName: 'Ask AI', + description: 'Generate text using AI providers', + props: { + // AI provider selection (OpenAI, Anthropic, etc.) + provider: aiProps({ modelType: 'language' }).provider, + // Model selection within the chosen provider + model: aiProps({ modelType: 'language' }).model, + prompt: Property.LongText({ + displayName: 'Prompt', + required: true, + }), + creativity: Property.Number({ + displayName: 'Creativity', + required: false, + defaultValue: 100, + description: 'Controls the creativity of the AI response (0-100)', + }), + maxTokens: Property.Number({ + displayName: 'Max Tokens', + required: false, + defaultValue: 2000, + }), + }, + async run(context) { + const providerName = context.propsValue.provider as string; + const modelInstance = context.propsValue.model as LanguageModel; + + // The `createAIProvider` function creates a standardized AI provider instance compatible with the AI SDK: + const baseURL = `${context.server.apiUrl}v1/ai-providers/proxy/${providerName}`; + const engineToken = context.server.token; + const provider = createAIProvider({ + providerName, // Provider name (e.g., 'openai', 'anthropic') + modelInstance, // Model instance with configuration + apiKey: engineToken, // Authentication token + baseURL, // Proxy URL for API requests + }); + + // Generate text using the AI SDK + const response = await generateText({ + model: provider, // AI provider instance + messages: [ + { + role: 'user', + content: context.propsValue.prompt, + }, + ], + maxTokens: context.propsValue.maxTokens, // Limit response length + temperature: (context.propsValue.creativity ?? 100) / 100, // Control randomness (0-1) + headers: { + 'Authorization': `Bearer ${engineToken}`, // Required for proxy authentication + }, + }); + + return response.text ?? ''; + }, +}); +``` + +## AI Properties Helper + +Use `aiProps` to create consistent AI-related properties: + +```typescript +import { aiProps } from '@activepieces/pieces-common'; + +// For language models (text generation) +props: { + provider: aiProps({ modelType: 'language' }).provider, + model: aiProps({ modelType: 'language' }).model, +} + +// For image models (image generation) +props: { + provider: aiProps({ modelType: 'image' }).provider, + model: aiProps({ modelType: 'image' }).model, + advancedOptions: aiProps({ modelType: 'image' }).advancedOptions, +} + +// For function calling support +props: { + provider: aiProps({ modelType: 'language', functionCalling: true }).provider, + model: aiProps({ modelType: 'language', functionCalling: true }).model, +} +``` + +### Advanced Options + +The `aiProps` helper includes an `advancedOptions` property that provides provider-specific configuration options. These options are dynamically generated based on the selected provider and model. + +To add advanced options for your new provider, update the `advancedOptions` property in `packages/pieces/community/common/src/lib/ai/index.ts`: + +```typescript +// In packages/pieces/community/common/src/lib/ai/index.ts +advancedOptions: Property.DynamicProperties({ + displayName: 'Advanced Options', + required: false, + refreshers: ['provider', 'model'], + props: async (propsValue): Promise => { + const provider = propsValue['provider'] as unknown as string; + + const providerMetadata = SUPPORTED_AI_PROVIDERS.find(p => p.provider === provider); + if (isNil(providerMetadata)) { + return {}; + } + + if (modelType === 'image') { + // Existing OpenAI options + if (provider === 'openai') { + return { + quality: Property.StaticDropdown({ + options: { + options: [ + { label: 'Standard', value: 'standard' }, + { label: 'HD', value: 'hd' }, + ], + disabled: false, + placeholder: 'Select Image Quality', + }, + defaultValue: 'standard', + description: 'Standard images are less detailed and faster to generate.', + displayName: 'Image Quality', + required: true, + }), + }; + } + + return {}; + }, +}) +``` + +The advanced options automatically update when users change their provider or model selection, ensuring only relevant options are shown. + +## Adding a New AI Provider + +To add support for a new AI provider, you need to update several files in the Activepieces codebase. Here's a complete guide: + +Before starting, check the [Vercel AI SDK Providers](https://ai-sdk.dev/providers/ai-sdk-providers) documentation to see all available providers and their capabilities. + +### 1. Install Required Dependencies + +First, add the AI SDK for your provider to the project dependencies: + +```bash +npm install @ai-sdk/openai +``` + +### 2. Update SUPPORTED_AI_PROVIDERS Array + +First, add your new provider to the `SUPPORTED_AI_PROVIDERS` array in `packages/shared/src/lib/ai/supported-ai-providers.ts`: + +```typescript +import { openai } from '@ai-sdk/openai' // Import the OpenAI SDK + +export const SUPPORTED_AI_PROVIDERS: SupportedAIProvider[] = [ + // ... existing providers + { + provider: 'openai', // Unique provider identifier + baseUrl: 'https://api.openai.com', // OpenAI's API base URL + displayName: 'OpenAI', // Display name in UI + markdown: `Follow these instructions to get your OpenAI API Key: + +1. Visit: https://platform.openai.com/account/api-keys +2. Create a new API key for Activepieces integration. +3. Add your credit card and upgrade to paid plan to avoid rate limits. +`, // Instructions for users + logoUrl: 'https://cdn.activepieces.com/pieces/openai.png', // OpenAI logo + auth: { + headerName: 'Authorization', // HTTP header name for auth + bearer: true, // Whether to use "Bearer" prefix + }, + languageModels: [ // Available language models + { + displayName: 'GPT-4o', + instance: openai('gpt-4o'), // Model instance from AI SDK + functionCalling: true, // Whether model supports function calling + }, + { + displayName: 'GPT-4o Mini', + instance: openai('gpt-4o-mini'), + functionCalling: true, + }, + ], + imageModels: [ // Available image models + { + displayName: 'DALL-E 3', + instance: openai.image('dall-e-3'), // Image model instance + }, + { + displayName: 'DALL-E 2', + instance: openai.image('dall-e-2'), + }, + ], + }, +]; +``` + +### 3. Update createAIProvider Function + +Add a case for your provider in the `createAIProvider` function in `packages/shared/src/lib/ai/ai-sdk.ts`: + +```typescript +import { createOpenAI } from '@ai-sdk/openai' // Import the OpenAI SDK + +export function createAIProvider({ + providerName, + modelInstance, + apiKey, + baseURL, +}: CreateAIProviderParams): T { + const isImageModel = SUPPORTED_AI_PROVIDERS + .flatMap(provider => provider.imageModels) + .some(model => model.instance.modelId === modelInstance.modelId) + + switch (providerName) { + // ... existing cases + case 'openai': { + const openaiVersion = 'v1' // OpenAI API version + const provider = createOpenAI({ + apiKey, // OpenAI API key + baseURL: `${baseURL}/${openaiVersion}`, // Full API URL + }) + + if (isImageModel) { + return provider.imageModel(modelInstance.modelId) as T + } + return provider(modelInstance.modelId) as T + } + default: + throw new Error(`Provider ${providerName} is not supported`) + } +} +``` + +### 4. Handle Provider-Specific Requirements + +OpenAI supports both language and image models, but some providers may have specific requirements or limitations: + +```typescript +// Example: Anthropic only supports language models +case 'anthropic': { + const provider = createAnthropic({ + apiKey, + baseURL: `${baseURL}/v1`, + }) + + if (isImageModel) { + throw new Error(`Provider ${providerName} does not support image models`) + } + return provider(modelInstance.modelId) as T +} + +// Example: Replicate primarily supports image models +case 'replicate': { + const provider = createReplicate({ + apiToken: apiKey, // Note: Replicate uses 'apiToken' instead of 'apiKey' + baseURL: `${baseURL}/v1`, + }) + + if (!isImageModel) { + throw new Error(`Provider ${providerName} does not support language models`) + } + return provider.imageModel(modelInstance.modelId) as unknown as T +} +``` + +### 5. Test Your Integration + +After adding the provider, test it by: + +1. **Configure credentials** in the Admin panel for your new provider +2. **Create a test action** using `aiProps` to select your provider and models +3. **Verify functionality** by running a flow with your new provider + +Once these changes are made, your new AI provider will be available in the `aiProps` dropdowns and can be used with `generateText` and other [AI SDK functions](https://ai-sdk.dev/docs/introduction) throughout Activepieces. + diff --git a/docs/developers/piece-reference/authentication.mdx b/docs/developers/piece-reference/authentication.mdx new file mode 100644 index 0000000..ff7f719 --- /dev/null +++ b/docs/developers/piece-reference/authentication.mdx @@ -0,0 +1,132 @@ +--- +title: "Piece Auth" +description: "Learn about piece authentication" +icon: 'key' +--- + +Piece authentication is used to gather user credentials and securely store them for future use in different flows. The authentication must be defined as the `auth` parameter in the `createPiece`, `createTrigger`, and `createAction` functions. + +This requirement ensures that the type of authentication can be inferred correctly in triggers and actions. + + +Friendly Tip: Only at most one authentication is allowed per piece. + + +### Secret Text + +This authentication collects sensitive information, such as passwords or API keys. It is displayed as a masked input field. + +**Example:** + +```typescript +PieceAuth.SecretText({ + displayName: 'API Key', + description: 'Enter your API key', + required: true, + // Optional Validation + validate: async ({auth}) => { + if(auth.startsWith('sk_')){ + return { + valid: true, + } + } + return { + valid: false, + error: 'Invalid Api Key' + } + } +}) +``` + +### Username and Password + +This authentication collects a username and password as separate fields. + +**Example:** + +```typescript +PieceAuth.BasicAuth({ + displayName: 'Credentials', + description: 'Enter your username and password', + required: true, + username: { + displayName: 'Username', + description: 'Enter your username', + }, + password: { + displayName: 'Password', + description: 'Enter your password', + }, + // Optional Validation + validate: async ({auth}) => { + if(auth){ + return { + valid: true, + } + } + return { + valid: false, + error: 'Invalid Api Key' + } + } +}) +``` + +### Custom + +This authentication allows for custom authentication by collecting specific properties, such as a base URL and access token. + +**Example:** + +```typescript +PieceAuth.CustomAuth({ + displayName: 'Custom Authentication', + description: 'Enter custom authentication details', + props: { + base_url: Property.ShortText({ + displayName: 'Base URL', + description: 'Enter the base URL', + required: true, + }), + access_token: PieceAuth.SecretText({ + displayName: 'Access Token', + description: 'Enter the access token', + required: true + }) + }, + // Optional Validation + validate: async ({auth}) => { + if(auth){ + return { + valid: true, + } + } + return { + valid: false, + error: 'Invalid Api Key' + } + }, + required: true +}) +``` + +### OAuth2 + +This authentication collects OAuth2 authentication details, including the authentication URL, token URL, and scope. + +**Example:** + +```typescript +PieceAuth.OAuth2({ + displayName: 'OAuth2 Authentication', + grantType: OAuth2GrantType.AUTHORIZATION_CODE, + required: true, + authUrl: 'https://example.com/auth', + tokenUrl: 'https://example.com/token', + scope: ['read', 'write'] +}) +``` + + +Please note `OAuth2GrantType.CLIENT_CREDENTIALS` is also supported for service-based authentication. + \ No newline at end of file diff --git a/docs/developers/piece-reference/custom-api-calls.mdx b/docs/developers/piece-reference/custom-api-calls.mdx new file mode 100644 index 0000000..7cd9818 --- /dev/null +++ b/docs/developers/piece-reference/custom-api-calls.mdx @@ -0,0 +1,55 @@ +--- +title: "Enable Custom API Calls" +description: "Learn how to enable custom API calls for your pieces" +icon: 'webhook' +--- + +Custom API Calls allow the user to send a request to a specific endpoint if no action has been implemented for it. + +This will show in the actions list of the piece as `Custom API Call`, to enable this action for a piece, you need to call the `createCustomApiCallAction` in your actions array. + +## Basic Example + +The example below implements the action for the OpenAI piece. The OpenAI piece uses a `Bearer token` authorization header to identify the user sending the request. + +```typescript +actions: [ + ...yourActions, + createCustomApiCallAction({ + // The auth object defined in the piece + auth: openaiAuth, + // The base URL for the API + baseUrl: () => { + 'https://api.openai.com/v1' + }, + // Mapping the auth object to the needed authorization headers + authMapping: async (auth) => { + return { + 'Authorization': `Bearer ${auth}` + } + } + }) +] +``` + +## Dynamic Base URL and Basic Auth Example + +The example below implements the action for the Jira Cloud piece. The Jira Cloud piece uses a dynamic base URL for it's actions, where the base URL changes based on the values the user authenticated with. We will also implement a Basic authentication header. + +```typescript +actions: [ + ...yourActions, + createCustomApiCallAction({ + baseUrl: (auth) => { + return `${(auth as JiraAuth).instanceUrl}/rest/api/3` + }, + auth: jiraCloudAuth, + authMapping: async (auth) => { + const typedAuth = auth as JiraAuth + return { + 'Authorization': `Basic ${typedAuth.email}:${typedAuth.apiToken}` + } + } + }) +] +``` \ No newline at end of file diff --git a/docs/developers/piece-reference/examples.mdx b/docs/developers/piece-reference/examples.mdx new file mode 100755 index 0000000..1039f9c --- /dev/null +++ b/docs/developers/piece-reference/examples.mdx @@ -0,0 +1,31 @@ +--- +title: "Piece Examples" +description: "Explore a collection of example triggers and actions" +icon: 'brackets-curly' +--- + +To get the full benefit, it is recommended to read the tutorial first. + +## Triggers: + +**Webhooks:** +- [New Form Submission on Typeform](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/typeform/src/lib/trigger/new-submission.ts) + +**Polling:** +- [New Completed Task On Todoist](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/todoist/src/lib/triggers/task-completed-trigger.ts) + +## Actions: +- [Send a message On Discord](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/discord/src/lib/actions/send-message-webhook.ts) +- [Send an mail On Gmail](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/gmail/src/lib/actions/send-email-action.ts) + +## Authentication + +**OAuth2:** +- [Slack](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/slack/src/index.ts) +- [Gmail](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/gmail/src/index.ts) + +**API Key:** +- [Sendgrid](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/sendgrid/src/index.ts) + +**Basic Authentication:** +- [Twilio](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/twilio/src/index.ts) diff --git a/docs/developers/piece-reference/external-libraries.mdx b/docs/developers/piece-reference/external-libraries.mdx new file mode 100644 index 0000000..9f4c773 --- /dev/null +++ b/docs/developers/piece-reference/external-libraries.mdx @@ -0,0 +1,25 @@ +--- +title: "External Libraries" +icon: 'npm' +description: "Learn how to install and use external libraries." +--- + +The Activepieces repository is structured as a monorepo, employing Nx as its build tool. + +To use an external library in your project, you can simply add it to the main `package.json` file and then use it in any part of your project. + +Nx will automatically detect where you're using the library and include it in the build. + +Here's how to install and use an external library: + +- Install the library using: + +```bash +npm install --save +``` + +- Import the library into your piece. + +Guidelines: +- Make sure you are using well-maintained libraries. +- Ensure that the library size is not too large to avoid bloating the bundle size; this will make the piece load faster in the sandbox. \ No newline at end of file diff --git a/docs/developers/piece-reference/files.mdx b/docs/developers/piece-reference/files.mdx new file mode 100644 index 0000000..df65a38 --- /dev/null +++ b/docs/developers/piece-reference/files.mdx @@ -0,0 +1,25 @@ +--- +title: "Files" +icon: 'file' +description: "Learn how to use files object to create file references." +--- + +The `ctx.files` object allow you to store files in local storage or in a remote storage depending on the run environment. + +## Write + +You can use the `write` method to write a file to the storage, It returns a string that can be used in other actions or triggers properties to reference the file. + +**Example:** +```ts +const fileReference = await files.write({ + fileName: 'file.txt', + data: Buffer.from('text') +}); +``` + + +This code will store the file in the database If the run environment is testing mode since it will be required to test other steps, other wise it will store it in the local temporary directory. + + +For Reading the file If you are using the file property in a trigger or action, It will be automatically parsed and you can use it directly, please refer to `Property.File` in the [properties](./properties#file) section. \ No newline at end of file diff --git a/docs/developers/piece-reference/flow-control.mdx b/docs/developers/piece-reference/flow-control.mdx new file mode 100644 index 0000000..71b2d02 --- /dev/null +++ b/docs/developers/piece-reference/flow-control.mdx @@ -0,0 +1,65 @@ +--- +title: 'Flow Control' +icon: 'Joystick' +description: 'Learn How to Control Flow from Inside the Piece' +--- + +Flow Controls provide the ability to control the flow of execution from inside a piece. By using the `ctx` parameter in the `run` method of actions, you can perform various operations to control the flow. + +## Stop Flow + +You can stop the flow and provide a response to the webhook trigger. This can be useful when you want to terminate the execution of the piece and send a specific response back. + +**Example with Response:** + +```typescript +context.run.stop({ + response: { + status: context.propsValue.status ?? StatusCodes.OK, + body: context.propsValue.body, + headers: (context.propsValue.headers as Record) ?? {}, + }, +}); +``` + +**Example without Response:** + +```typescript +context.run.stop(); +``` + +## Pause Flow and Wait for Webhook + +You can pause flow and return HTTP response, also provide a callback to URL that you can call with certain payload and continue the flow. + +**Example:** + +```typescript +ctx.run.pause({ + pauseMetadata: { + type: PauseType.WEBHOOK, + response: { + callbackUrl: context.generateResumeUrl({ + queryParams: {}, + }), + }, + }, +}); +``` + +## Pause Flow and Delay + +You can pause or delay the flow until a specific timestamp. Currently, the only supported type of pause is a delay based on a future timestamp. + +**Example:** + +```typescript +ctx.run.pause({ + pauseMetadata: { + type: PauseType.DELAY, + resumeDateTime: futureTime.toUTCString() + } +}); +``` + +These flow hooks give you control over the execution of the piece by allowing you to stop the flow or pause it until a certain condition is met. You can use these hooks to customize the behavior and flow of your actions. \ No newline at end of file diff --git a/docs/developers/piece-reference/i18n.mdx b/docs/developers/piece-reference/i18n.mdx new file mode 100644 index 0000000..22f2844 --- /dev/null +++ b/docs/developers/piece-reference/i18n.mdx @@ -0,0 +1,36 @@ +--- +title: 'Piece i18n' +description: 'Learn about translating pieces to multiple locales' +icon: 'globe' +--- + + + + Translation files are created automatically while you develop your piece. + + Add your piece to [AP_DEV_PIECES](/developers/development-setup/local) and run: + ```bash + npm start + ``` + + Each time you make changes to your piece, a translation file will be generated. + Look for this message in your terminal: `Translation file for piece created in /src/i18n/translation.json` + + + Create a file named `/src/i18n/.json` and translate the values from the translation file. + + For open source pieces, you can use the [Crowdin project](https://crowdin.com/project/activepieces) to translate to different languages. These translations will automatically sync back to your code. + + + + + Go to your dashboard: Project Settings -> Appearance -> Languages + ![Languages](/resources/i18n-pieces.png) + + Create or edit the locale file (like fr.json) in your piece directory. Refresh the UI to see your changes. + + Your piece will now appear in the translated language: + ![French Webhooks](/resources/french-webhooks.png) + + + \ No newline at end of file diff --git a/docs/developers/piece-reference/persistent-storage.mdx b/docs/developers/piece-reference/persistent-storage.mdx new file mode 100644 index 0000000..0897bd6 --- /dev/null +++ b/docs/developers/piece-reference/persistent-storage.mdx @@ -0,0 +1,45 @@ +--- +title: "Persistent Storage" +icon: 'database' +description: "Learn how to store and retrieve data from a key-value store" +--- + +The `ctx` parameter inside triggers and actions provides a simple key/value storage mechanism. The storage is persistent, meaning that the stored values are retained even after the execution of the piece. + +By default, the storage operates at the flow level, but it can also be configured to store values at the project level. + + +The storage scope is completely isolated. If a key is stored in a different scope, it will not be fetched when requested in different scope. + + +## Put + +You can store a value with a specified key in the storage. + +**Example:** + +```typescript +ctx.store.put('KEY', 'VALUE', StoreScope.PROJECT); +``` + +## Get + +You can retrieve the value associated with a specific key from the storage. + +**Example:** + +```typescript +const value = ctx.store.get('KEY', StoreScope.PROJECT); +``` + +## Delete + +You can delete a key-value pair from the storage. + +**Example:** + +```typescript +ctx.store.delete('KEY', StoreScope.PROJECT); +``` + +These storage operations allow you to store, retrieve, and delete key-value pairs in the persistent storage. You can use this storage mechanism to store and retrieve data as needed within your triggers and actions. \ No newline at end of file diff --git a/docs/developers/piece-reference/piece-versioning.mdx b/docs/developers/piece-reference/piece-versioning.mdx new file mode 100644 index 0000000..a3bf428 --- /dev/null +++ b/docs/developers/piece-reference/piece-versioning.mdx @@ -0,0 +1,43 @@ +--- +title: 'Piece Versioning' +icon: 'code-compare' +description: 'Learn how to version your pieces' +--- + +Pieces are npm packages and follows **semantic versioning**. + +## Semantic Versioning + +The version number consists of three numbers: `MAJOR.MINOR.PATCH`, where: + +- **MAJOR** It should be incremented when there are breaking changes to the piece. +- **MINOR** It should be incremented for new features or functionality that is compatible with the previous version, unless the major version is less than 1.0, in which case it can be a breaking change. +- **PATCH** It should be incremented for bug fixes and small changes that do not introduce new features or break backward compatibility. + +## Engine +The engine will use the most up-to-date compatible version for a given piece version during the **DRAFT** flow versions. Once the flow is published, all pieces will be locked to a specific version. + +**Case 1: Piece Version is Less Than 1.0**: +The engine will select the latest **patch** version that shares the same **minor** version number. + +**Case 2: Piece Version Reaches Version 1.0**: +The engine will select the latest **minor** version that shares the same **major** version number. + +## Examples + +when you make a change, remember to increment the version accordingly. + + +### Breaking changes +- Remove an existing action. +- Add a required `action` prop. +- Remove an existing action prop, whether required or optional. +- Remove an attribute from an action output. +- Change the existing behavior of an action/trigger. + +### Non-breaking changes +- Add a new action. +- Add an optional `action` prop. +- Add an attribute to an action output. + +i.e., any removal is breaking, any required addition is breaking, everything else is not breaking. \ No newline at end of file diff --git a/docs/developers/piece-reference/properties-validation.mdx b/docs/developers/piece-reference/properties-validation.mdx new file mode 100644 index 0000000..189c793 --- /dev/null +++ b/docs/developers/piece-reference/properties-validation.mdx @@ -0,0 +1,78 @@ +--- +title: "Props Validation" +description: "Learn about different types of properties validation " +icon: 'magnifying-glass' +--- + +Activepieces uses Zod for runtime validation of piece properties. Zod provides a powerful schema validation system that helps ensure your piece receives valid inputs. + +To use Zod validation in your piece, first import the validation helper and Zod: + + +Please make sure the `minimumSupportedRelease` is set to at least `0.36.1` for the validation to work. + + +```typescript +import { createAction, Property } from '@activepieces/pieces-framework'; +import { propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const getIcecreamFlavor = createAction({ + name: 'get_icecream_flavor', // Unique name for the action. + displayName: 'Get Ice Cream Flavor', + description: 'Fetches a random ice cream flavor based on user preferences.', + props: { + sweetnessLevel: Property.Number({ + displayName: 'Sweetness Level', + required: true, + description: 'Specify the sweetness level (0 to 10).', + }), + includeToppings: Property.Checkbox({ + displayName: 'Include Toppings', + required: false, + description: 'Should the flavor include toppings?', + defaultValue: true, + }), + numberOfFlavors: Property.Number({ + displayName: 'Number of Flavors', + required: true, + description: 'How many flavors do you want to fetch? (1-5)', + defaultValue: 1, + }), + }, + async run({ propsValue }) { + // Validate the input properties using Zod + await propsValidation.validateZod(propsValue, { + sweetnessLevel: z.number().min(0).max(10, 'Sweetness level must be between 0 and 10.'), + numberOfFlavors: z.number().min(1).max(5, 'You can fetch between 1 and 5 flavors.'), + }); + + // Action logic + const sweetnessLevel = propsValue.sweetnessLevel; + const includeToppings = propsValue.includeToppings ?? true; // Default to true + const numberOfFlavors = propsValue.numberOfFlavors; + + // Simulate fetching random ice cream flavors + const allFlavors = [ + 'Vanilla', + 'Chocolate', + 'Strawberry', + 'Mint', + 'Cookie Dough', + 'Pistachio', + 'Mango', + 'Coffee', + 'Salted Caramel', + 'Blackberry', + ]; + const selectedFlavors = allFlavors.slice(0, numberOfFlavors); + + return { + message: `Here are your ${numberOfFlavors} flavors: ${selectedFlavors.join(', ')}`, + sweetnessLevel: sweetnessLevel, + includeToppings: includeToppings, + }; + }, +}); + +``` diff --git a/docs/developers/piece-reference/properties.mdx b/docs/developers/piece-reference/properties.mdx new file mode 100644 index 0000000..f0afa14 --- /dev/null +++ b/docs/developers/piece-reference/properties.mdx @@ -0,0 +1,446 @@ +--- +title: 'Props' +description: 'Learn about different types of properties used in triggers / actions' +icon: 'input-pipe' +--- + +Properties are used in actions and triggers to collect information from the user. They are also displayed to the user for input. Here are some commonly used properties: + +## Basic Properties + +These properties collect basic information from the user. + +### Short Text + +This property collects a short text input from the user. + +**Example:** + +```typescript +Property.ShortText({ + displayName: 'Name', + description: 'Enter your name', + required: true, + defaultValue: 'John Doe', +}); +``` + +### Long Text + +This property collects a long text input from the user. + +**Example:** + +```typescript +Property.LongText({ + displayName: 'Description', + description: 'Enter a description', + required: false, +}); +``` + +### Checkbox + +This property presents a checkbox for the user to select or deselect. + +**Example:** + +```typescript +Property.Checkbox({ + displayName: 'Agree to Terms', + description: 'Check this box to agree to the terms', + required: true, + defaultValue: false, +}); +``` + +### Markdown + +This property displays a markdown snippet to the user, useful for documentation or instructions. It includes a `variant` option to style the markdown, using the `MarkdownVariant` enum: + +- **BORDERLESS**: For a minimalistic, no-border layout. +- **INFO**: Displays informational messages. +- **WARNING**: Alerts the user to cautionary information. +- **TIP**: Highlights helpful tips or suggestions. + +The default value for `variant` is **INFO**. + +**Example:** + +```typescript +Property.MarkDown({ + value: '## This is a markdown snippet', + variant: MarkdownVariant.WARNING, +}), +``` + + + If you want to show a webhook url to the user, use `{{ webhookUrl }}` in the + markdown snippet. + + +### DateTime + +This property collects a date and time from the user. + +**Example:** + +```typescript +Property.DateTime({ + displayName: 'Date and Time', + description: 'Select a date and time', + required: true, + defaultValue: '2023-06-09T12:00:00Z', +}); +``` + +### Number + +This property collects a numeric input from the user. + +**Example:** + +```typescript +Property.Number({ + displayName: 'Quantity', + description: 'Enter a number', + required: true, +}); +``` + +### Static Dropdown + +This property presents a dropdown menu with predefined options. + +**Example:** + +```typescript +Property.StaticDropdown({ + displayName: 'Country', + description: 'Select your country', + required: true, + options: { + options: [ + { + label: 'Option One', + + value: '1', + }, + { + label: 'Option Two', + value: '2', + }, + ], + }, +}); +``` + +### Static Multiple Dropdown + +This property presents a dropdown menu with multiple selection options. + +**Example:** + +```typescript +Property.StaticMultiSelectDropdown({ + displayName: 'Colors', + description: 'Select one or more colors', + required: true, + options: { + options: [ + { + label: 'Red', + value: 'red', + }, + { + label: 'Green', + value: 'green', + }, + { + label: 'Blue', + value: 'blue', + }, + ], + }, +}); +``` + +### JSON + +This property collects JSON data from the user. + +**Example:** + +```typescript +Property.Json({ + displayName: 'Data', + description: 'Enter JSON data', + required: true, + defaultValue: { key: 'value' }, +}); +``` + +### Dictionary + +This property collects key-value pairs from the user. + +**Example:** + +```typescript +Property.Object({ + displayName: 'Options', + description: 'Enter key-value pairs', + required: true, + defaultValue: { + key1: 'value1', + key2: 'value2', + }, +}); +``` + +### File + +This property collects a file from the user, either by providing a URL or uploading a file. + +**Example:** + +```typescript +Property.File({ + displayName: 'File', + description: 'Upload a file', + required: true, +}); +``` + +### Array of Strings + +This property collects an array of strings from the user. + +**Example:** + +```typescript +Property.Array({ + displayName: 'Tags', + description: 'Enter tags', + required: false, + defaultValue: ['tag1', 'tag2'], +}); +``` + +### Array of Fields + +This property collects an array of objects from the user. + +**Example:** + +```typescript +Property.Array({ + displayName: 'Fields', + description: 'Enter fields', + properties: { + fieldName: Property.ShortText({ + displayName: 'Field Name', + required: true, + }), + fieldType: Property.StaticDropdown({ + displayName: 'Field Type', + required: true, + options: { + options: [ + { label: 'TEXT', value: 'TEXT' }, + { label: 'NUMBER', value: 'NUMBER' }, + ], + }, + }), + }, + required: false, + defaultValue: [], +}); +``` + +## Dynamic Data Properties + +These properties provide more advanced options for collecting user input. + +### Dropdown + +This property allows for dynamically loaded options based on the user's input. + +**Example:** + +```typescript +Property.Dropdown({ + displayName: 'Options', + description: 'Select an option', + required: true, + refreshers: ['auth'], + refreshOnSearch: false, + options: async ({ auth }, { searchValue }) => { + // Search value only works when refreshOnSearch is true + if (!auth) { + return { + disabled: true, + }; + } + return { + options: [ + { + label: 'Option One', + value: '1', + }, + { + label: 'Option Two', + value: '2', + }, + ], + }; + }, +}); +``` + + + When accessing the Piece auth, be sure to use exactly `auth` as it is + hardcoded. However, for other properties, use their respective names. + + +### Multi-Select Dropdown + +This property allows for multiple selections from dynamically loaded options. + +**Example:** + +```typescript +Property.MultiSelectDropdown({ + displayName: 'Options', + description: 'Select one or more options', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + }; + } + return { + options: [ + { + label: 'Option One', + value: '1', + }, + { + label: 'Option Two', + value: '2', + }, + ], + }; + }, +}); +``` + + + When accessing the Piece auth, be sure to use exactly `auth` as it is + hardcoded. However, for other properties, use their respective names. + + +### Dynamic Properties + +This property is used to construct forms dynamically based on API responses or user input. + +**Example:** + +```typescript + +import { + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; + + +Property.DynamicProperties({ + description: 'Dynamic Form', + displayName: 'Dynamic Form', + required: true, + refreshers: ['authentication'], + props: async (propsValue) => { + const authentication = propsValue['authentication']; + const apiEndpoint = 'https://someapi.com'; + const response = await httpClient.sendRequest<{ values: [string[]][] }>({ + method: HttpMethod.GET, + url: apiEndpoint + }); + + const properties = { + prop1: Property.ShortText({ + displayName: 'Property 1', + description: 'Enter property 1', + required: true, + }), + prop2: Property.Number({ + displayName: 'Property 2', + description: 'Enter property 2', + required: false, + }), + }; + + return properties; + }, +}); +``` + + + +### Custom Property (BETA) + + + This feature is still in BETA and not fully released yet, please let us know if you use it and face any issues and consider it a possibility could have breaking changes in the future + +This is a property that lets you inject JS code into the frontend and manipulate the DOM of this content however you like, it is extremely useful in case you are [embedding](/embedding/overview) Activepieces and want to have a way to communicate with the SaaS embedding it. +It has a `code` property which is a function that takes in an object parameter which will have the following schema: + + +| Parameter Name | Type | Description | +| --- | --- | --- | +| onChange | `(value:unknown)=>void` | A callback you call to set the value of your input (only call this inside event handlers)| +| value | `unknown` | Whatever the type of the value you pass to onChange| +| containerId | `string` | The ID of an HTML element in which you can modify the DOM however you like | +| isEmbedded | `boolean` | The flag that tells you if the code is running inside an [embedded instance](/embedding/overview) of Activepieces | +| projectId | `string` | The project ID of the flow the step that contains this property is in | +| disabled | `boolean` | The flag that tells you whether or not the property is disabled | +| property | `{ displayName:string, description?: string, required: boolean}` | The current property information| + +- You can return a clean up function at the end of the `code` property function to remove any listeners or HTML elements you inserted (this is important for development mode, the component gets [mounted twice](https://react.dev/reference/react/useEffect#my-effect-runs-twice-when-the-component-mounts)). +- This function must be pure without any imports from external packages or variables outside the function scope. +- **Must** mark your piece `minimumSupportedRelease` property to be at least `0.58.0` after introducing this property to it. + +Here is how to define such a property: +```typescript + Property.Custom({ + code:(({value,onChange,containerId})=>{ + const container = document.getElementById(containerId); + const input = document.createElement('input'); + input.classList.add(...['border','border-solid', 'border-border', 'rounded-md']) + input.type = 'text'; + input.value = `${value}`; + input.oninput = (e: Event) => { + const value = (e.target as HTMLInputElement).value; + onChange(value); + } + container!.appendChild(input); + const windowCallback = (e:MessageEvent<{type:string,value:string,propertyName:string}>) => { + if(e.data.type === 'updateInput' && e.data.propertyName === 'YOUR_PROPERTY_NAME'){ + input.value= e.data.value; + onChange(e.data.value); + } + } + window.addEventListener('message', windowCallback); + return ()=>{ + window.removeEventListener('message', windowCallback); + container!.removeChild(input); + } + }), + displayName: 'Custom Property', + required: true + }) + ``` + +- If you would like to know more about how to setup communication between Activepieces and the SaaS that's embedding it, check the [window postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). diff --git a/docs/developers/piece-reference/triggers/overview.mdx b/docs/developers/piece-reference/triggers/overview.mdx new file mode 100755 index 0000000..c71ced7 --- /dev/null +++ b/docs/developers/piece-reference/triggers/overview.mdx @@ -0,0 +1,76 @@ +--- +title: 'Overview' +description: '' +--- + +This tutorial explains three techniques for creating triggers: + +- `Polling`: Periodically call endpoints to check for changes. +- `Webhooks`: Listen to user events through a single URL. +- `App Webhooks (Subscriptions)`: Use a developer app (using OAuth2) to receive all authorized user events at a single URL (Not Supported). + +to create new trigger run following command, + +```bash +npm run cli triggers create +``` + +1. `Piece Folder Name`: This is the name associated with the folder where the trigger resides. It helps organize and categorize triggers within the piece. +2. `Trigger Display Name`: The name users see in the interface, conveying the trigger's purpose clearly. +3. `Trigger Description`: A brief, informative text in the UI, guiding users about the trigger's function and purpose. +4. `Trigger Technique`: Specifies the trigger type - either polling or webhook. + +# Trigger Structure + +```typescript +export const createNewIssue = createTrigger({ + auth: PieceAuth | undefined + name: string, // Unique name across the piece. + displayName: string, // Display name on the interface. + description: string, // Description for the action + triggerType: POLLING | WEBHOOK, + + props: {}; // Required properties from the user. + // Run when the user enable or publish the flow. + + onEnable: (ctx) => {}; + // Run when the user disable the flow or + // the old flow is deleted after new one is published. + onDisable: (ctx) => {}; + + // Trigger implementation, It takes context as parameter. + // should returns an array of payload, each payload considered + // a separate flow run. + run: async run(ctx): unknown[] => {} +}) +``` + + + It's important to note that the `run` method returns an array. The reason for + this is that a single polling can contain multiple triggers, so each item in + the array will trigger the flow to run. + + +## Context Object + +The Context object contains multiple helpful pieces of information and tools that can be useful while developing. + +```typescript +// Store: A simple, lightweight key-value store that is helpful when you are developing triggers that persist between runs, used to store information like the last polling date. +await context.store.put('_lastFetchedDate', new Date()); +const lastFetchedData = await context.store.get('_lastFetchedDate', new Date()); + +// Webhook URL: A unique, auto-generated URL that will trigger the flow. Useful when you need to develop a trigger based on webhooks. +context.webhookUrl; + +// Payload: Contains information about the HTTP request sent by the third party. It has three properties: status, headers, and body. +context.payload; + +// PropsValue: Contains the information filled by the user in defined properties. +context.propsValue; +``` + +**App Webhooks (Not Supported)** + +Certain services, such as `Slack` and `Square`, only support webhooks at the developer app level. +This means that all authorized users for the app will be sent to the same endpoint. While this technique will be supported soon, for now, a workaround is to perform polling on the endpoint. diff --git a/docs/developers/piece-reference/triggers/polling-trigger.mdx b/docs/developers/piece-reference/triggers/polling-trigger.mdx new file mode 100644 index 0000000..36e690f --- /dev/null +++ b/docs/developers/piece-reference/triggers/polling-trigger.mdx @@ -0,0 +1,111 @@ +--- +title: "Polling Trigger" +description: "Periodically call endpoints to check for changes" +--- + + + +The way polling triggers usually work is as follows: + +**On Enable:** +Store the last timestamp or most recent item id using the context store property. + +**Run:** +This method runs every **5 minutes**, fetches the endpoint between a certain timestamp or traverses until it finds the last item id, and returns the new items as an array. + +**Testing:** +You can implement a test function which should return some of the most recent items. It's recommended to limit this to five. + +**Examples:** +- [New Record Airtable](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/airtable/src/lib/trigger/new-record.trigger.ts) +- [New Updated Item Salesforce](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/salesforce/src/lib/trigger/new-updated-record.ts) + +# Polling library + +There multiple strategy to implement polling triggers, and we have created a library to help you with that. + +## Strategies + +**Timebased:** + +This strategy fetches new items using a timestamp. You need to implement the items method, which should return the most recent items. +The library will detect new items based on the timestamp. + +The polling object's generic type consists of the props value and the object type. + +```typescript +const polling: Polling<{ authentication: OAuth2PropertyValue, object: string }> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ propsValue, lastFetchEpochMS }) => { + // Todo implement the logic to fetch the items + const items = [ {id: 1, created_date: '2021-01-01T00:00:00Z'}, {id: 2, created_date: '2021-01-01T00:00:00Z'}]; + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.created_date).valueOf(), + data: item, + })); + } +} +``` + +**Last ID Strategy:** + +This strategy fetches new items based on the last item ID. To use this strategy, you need to implement the items method, which should return the most recent items. +The library will detect new items after the last item ID. + +The polling object's generic type consists of the props value and the object type + +```typescript +const polling: Polling<{ authentication: AuthProps}> = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ propsValue }) => { + // Implement the logic to fetch the items + const items = [{ id: 1 }, { id: 2 }]; + return items.map((item) => ({ + id: item.id, + data: item, + })); + } +} +``` + +## Trigger Implementation + +After implementing the polling object, you can use the polling helper to implement the trigger. + +```typescript +export const newTicketInView = createTrigger({ + name: 'new_ticket_in_view', + displayName: 'New ticket in view', + description: 'Triggers when a new ticket is created in a view', + type: TriggerStrategy.POLLING, + props: { + authentication: Property.SecretText({ + displayName: 'Authentication', + description: markdownProperty, + required: true, + }), + }, + sampleData: {}, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + store: context.store, + propsValue: context.propsValue, + auth: context.auth + }) + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + store: context.store, + propsValue: context.propsValue, + auth: context.auth + + }) + }, + run: async (context) => { + return await pollingHelper.poll(polling, context); + }, + test: async (context) => { + return await pollingHelper.test(polling, context); + } +}); +``` diff --git a/docs/developers/piece-reference/triggers/webhook-trigger.mdx b/docs/developers/piece-reference/triggers/webhook-trigger.mdx new file mode 100644 index 0000000..634b2fb --- /dev/null +++ b/docs/developers/piece-reference/triggers/webhook-trigger.mdx @@ -0,0 +1,37 @@ +--- +title: 'Webhook Trigger' +description: 'Listen to user events through a single URL' +--- + +The way webhook triggers usually work is as follows: + +**On Enable:** +Use `context.webhookUrl` to perform an HTTP request to register the webhook in a third-party app, and store the webhook Id in the `store`. + +**On Handshake:** +Some services require a successful handshake request usually consisting of some challenge. It works similar to a normal run except that you return the correct challenge response. This is optional and in order to enable the handshake you need to configure one of the available handshake strategies in the `handshakeConfiguration` option. + +**Run:** +You can find the HTTP body inside `context.payload.body`. If needed, alter the body; otherwise, return an array with a single item `context.payload.body`. + +**Disable:** +Using the `context.store`, fetch the webhook ID from the enable step and delete the webhook on the third-party app. + +**Testing:** +You cannot test it with Test Flow, as it uses static sample data provided in the piece. +To test the trigger, publish the flow, perform the event. Then check the flow runs from the main dashboard. + +**Examples:** + +- [New Form Submission on Typeform](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/typeform/src/lib/trigger/new-submission.ts) + + +To make your webhook accessible from the internet, you need to configure the backend URL. Follow these steps: + +1. Install ngrok. +2. Run the command `ngrok http 4200`. +3. Replace the `AP_FRONTEND_URL` environment variable in `packages/server/api/.env` with the ngrok URL. + +Once you have completed these configurations, you are good to go! + + diff --git a/docs/developers/sharing-pieces/community.mdx b/docs/developers/sharing-pieces/community.mdx new file mode 100644 index 0000000..2ae7df3 --- /dev/null +++ b/docs/developers/sharing-pieces/community.mdx @@ -0,0 +1,32 @@ +--- +title: "Community (Public NPM)" +description: "Learn how to publish your piece to the community." +--- + +You can publish your pieces to the npm registry and share them with the community. Users can install your piece from Settings -> My Pieces -> Install Piece -> type in the name of your piece package. + + + + Make sure you are logged in to npm. If not, please run: + ```bash + npm login + ``` + + + Rename the piece name in `package.json` to something unique or related to your organization's scope (e.g., `@my-org/piece-PIECE_NAME`). You can find it at `packages/pieces/PIECE_NAME/package.json`. + + Don't forget to increase the version number in `package.json` for each new release. + + + + + Replace `PIECE_FOLDER_NAME` with the name of the folder. + + Run the following command: + ```bash + npm run publish-piece PIECE_FOLDER_NAME + ``` + + + +**Congratulations! You can now import the piece from the settings page.** diff --git a/docs/developers/sharing-pieces/contribute.mdx b/docs/developers/sharing-pieces/contribute.mdx new file mode 100644 index 0000000..5012159 --- /dev/null +++ b/docs/developers/sharing-pieces/contribute.mdx @@ -0,0 +1,17 @@ +--- +title: "Contribute" +description: "Learn how to contribute a piece to the main repository." +--- + + + + - Build and test your piece. + - Open a pull request from your repository to the main fork. + - A maintainer will review your work closely. + + + - Once the pull request is approved, it will be merged into the main branch. + - Your piece will be available within a few minutes. + - An automatic GitHub action will package it and create an npm package on npmjs.com. + + diff --git a/docs/developers/sharing-pieces/overview.mdx b/docs/developers/sharing-pieces/overview.mdx new file mode 100644 index 0000000..d89ff7a --- /dev/null +++ b/docs/developers/sharing-pieces/overview.mdx @@ -0,0 +1,9 @@ +--- +title: "Overview" +description: "Learn the different ways to publish your own piece on activepieces." +--- + +## Methods +- [Contribute Back](/developers/sharing-pieces/contribute): Publish your piece by contributing back your piece to main repository. +- [Community](/developers/sharing-pieces/community): Publish your piece on npm directly and share it with the community. +- [Private](/developers/sharing-pieces/private): Publish your piece on activepieces privately. diff --git a/docs/developers/sharing-pieces/private.mdx b/docs/developers/sharing-pieces/private.mdx new file mode 100644 index 0000000..b2f9856 --- /dev/null +++ b/docs/developers/sharing-pieces/private.mdx @@ -0,0 +1,34 @@ +--- +title: "Private" +description: "Learn how to share your pieces privately." +--- + + + +This guide assumes you have already created a piece and created a private fork of our repository, and you would like to package it as a file and upload it. + + +Friendly Tip: There is a CLI to easily upload it to your platform. Please check out [Publish Custom Pieces](../misc/publish-piece). + + + + + + + Build the piece using the following command. Make sure to replace `${name}` with your piece name. + + ```bash + npm run pieces -- build --name=${name} + ``` + + + More information about building pieces can be found [here](../misc/build-piece). + + + + Upload the generated tarball inside `dist/packages/pieces/${name}`from Activepieces Platform Admin -> Pieces + + ![Manage Pieces](/resources/screenshots/install-piece.png) + + + diff --git a/docs/embedding/customize-pieces.mdx b/docs/embedding/customize-pieces.mdx new file mode 100644 index 0000000..4b24a2f --- /dev/null +++ b/docs/embedding/customize-pieces.mdx @@ -0,0 +1,32 @@ +--- +title: "Show/Hide Pieces" +description: "" +icon: "puzzle" +--- + + + +If you would like to only show specific pieces to your embedding users, we recommend you do the following: + + + + Tag the pieces you would like to show to your user by going to **Platform Admin -> Setup -> Pieces**, selecting the pieces you would like to tag and hit **Apply Tags** + + ![Bulk Tag](../resources/screenshots/tag-pieces.png) + + + You need to specify the tags of pieces in the token, check how to generate token in [provisioning users](./provision-users). + + You should specify the `pieces` claim like this: + ```json + { + /// Other claims + "piecesFilterType": "ALLOWED", + "piecesTags": [ "free" ] + } + ``` + + Each time the token is used by the embedding SDK, it will sync all pieces with these tags to the token's project. + The project will only contain the pieces that contain these tags. + + diff --git a/docs/embedding/embed-builder.mdx b/docs/embedding/embed-builder.mdx new file mode 100644 index 0000000..a4a684a --- /dev/null +++ b/docs/embedding/embed-builder.mdx @@ -0,0 +1,90 @@ +--- +title: "Embed Builder" +description: "" +icon: "wrench" +--- + + + +This documentation explains how to embed the Activepieces iframe inside your application and customize it. + +## Configure SDK + +Adding the embedding SDK script will initialize an object in your window called `activepieces`, which has a method called `configure` that you should call after the container has been rendered. + +The following scripts shouldn't contain the `async` or `defer` attributes. + + + + +These steps assume you have already generated a JWT token from the backend. If not, please check the [provision-users](./provision-users) page. + + +```html + + +``` + +`configure` returns a promise which is resolved after authentication is done. + + + + +Please check the [navigation](./navigation) section, as it's very important to understand how navigation works and how to supply an auto-sync experience. + + +**Configure Parameters:** + +| Parameter Name | Required | Type | Description | +| --- | --- | --- | --- | +| instanceUrl| ✅ | string | The url of the instance hosting Activepieces, could be https://cloud.activepieces.com if you are a cloud user. | +| jwtToken | ✅ | string | The jwt token you generated to authenticate your users to Activepieces. | +| prefix | ❌ | string | Some customers have an embedding prefix, like this `/`. For example if the prefix is `/automation` and the Activepieces url is `/flows` the full url would be `/automation/flows`. | +| embedding.containerId | ❌ | string | The html element's id that is going to be containing Activepieces's iframe. | +| embedding.builder.disableNavigation | ❌ | boolean \| "keep_home_button_only" | Hides the folder name, home button (if not set to ["keep_home_button_only"](./sdk-changelog#20%2F05%2F2025-0-4-0)) and delete option in the builder, by default it is false. | +| embedding.builder.hideFlowName | ❌ | boolean | Hides the flow name and flow actions dropdown in the builder's header, by default it is false. | +| embedding.builder.homeButtonClickedHandler | ❌ | ()=>void | Callback that stops home button from navigating to dashboard and overrides it with this handler (added in [0.4.0](./sdk-changelog#20%2F05%2F2025-0-4-0)) +| embedding.builder.homeButtonIcon | ❌ | 'logo' \| 'back' | if set to **'back'** the tooltip shown on hovering the home button is removed (added in [0.5.0](./sdk-changelog#03%2F07%2F2025-0-5-0)) +| embedding.dashboard.hideSidebar | ❌ | boolean | Controls the visibility of the sidebar in the dashboard, by default it is false. | +| embedding.hideFolders | ❌ | boolean | Hides all things related to folders in both the flows table and builder by default it is false. | +| embedding.styling.fontUrl | ❌ | string | The url of the font to be used in the embedding, by default it is `https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap`. | +| embedding.styling.fontFamily | ❌ | string | The font family to be used in the embedding, by default it is `Roboto`. | +| embedding.styling.mode | ❌ | 'light' \| 'dark' | Controls light/dark mode (added in [0.5.0](./sdk-changelog#03%2F07%2F2025-0-5-0))| +| embedding.hideExportAndImportFlow | ❌ | boolean | Hides the option to export or import flows (added in [0.4.0](./sdk-changelog#20%2F05%2F2025-0-4-0)) | +| embedding.hideDuplicateFlow | ❌ | boolean | Hides the option to duplicate a flow (added in [0.5.0](./sdk-changelog#03%2F07%2F2025-0-5-0))| +| embedding.locale | ❌ | 'en' \| 'nl' \| 'it' \| 'de' \| 'fr' \| 'bg' \| 'uk' \| 'hu' \| 'es' \| 'ja' \| 'id' \| 'vi' \| 'zh' \| 'pt' | it takes [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) locale codes (added in [0.5.0](./sdk-changelog#03%2F07%2F2025-0-5-0))| +| navigation.handler | ❌ | `({route:string}) => void` | This callback will be triggered each time a route in Activepieces changes, you can read more about it [here](/embedding/navigation) | + + + For the font to be loaded, you need to set both the `fontUrl` and `fontFamily` properties. + If you only set one of them, the default font will be used. + The default font is `Roboto`. + The font weights we use are the default font-weights from [tailwind](https://tailwindcss.com/docs/font-weight). + diff --git a/docs/embedding/embed-connections.mdx b/docs/embedding/embed-connections.mdx new file mode 100644 index 0000000..fc09d50 --- /dev/null +++ b/docs/embedding/embed-connections.mdx @@ -0,0 +1,74 @@ +--- +title: "Create/Update Connections" +description: "" +icon: "cards" +--- + + +**Requirements:** +* Activepieces version 0.34.5 or higher +* SDK version 0.3.2 or higher + + + + + "connectionName" is the externalId of the connection (you can get it by hovering the connection name in the connections table).
+ We kept the same parameter name for backward compatibility, anyone upgrading their instance from < 0.35.1, will not face issues in that regard. +
+ + +**Breaking Change:**

If your Activepieces instance version is < 0.45.0 and (you are using the connect method from the embed sdk, and need the connection externalId to be returned after the user creates it OR if you want to reconnect a specific connection with an externalId), you must upgrade your instance to >= 0.45.0 +
+ +- You can use the embedded SDK in your SaaS to allow your users to create connections and store them in Activepieces. + + + + Follow the instructions in the [Embed Builder](./embed-builder). + + + After initializing the SDK, you will have access to a property called `activepieces` inside your `window` object. Call its `connect` method to open a new connection dialog as follows. + + ```html + + ``` + + **Connect Parameters:** + + | Parameter Name | Required | Type | Description | + | -------------- | -------- | ------ | ------------------------------------------- | + | pieceName | ✅ | string | The name of the piece you want to create a connection for. + | connectionName | ❌ | string | The external Id of the connection (you can get it by hovering the connection name in the connections table), when provided the connection created/upserted will use this as the external Id and display name. + | newWindow | ❌ | \{ width?: number, height?: number, top?: number, left?: number \} | If set the connection dialog will be opened in a new window instead of an iframe taking the full page. + + **Connect Result** + + The `connect` method returns a `promise` that resolves to the following: + + ```ts + { + connection?: { + id: string, + name: string + } + } + ``` + + `name` is the externalId of the connection. + `connection` is undefined if the user closes the dialog and doesn't create a connection. + + + + + + + + + You can use the `connections` piece in the builder to retrieve the created connection using its name. + ![Connections in Builder](/resources/screenshots/connections-piece.png) + ![Connections in Builder](/resources/screenshots/connections-piece-usage.png) + + + diff --git a/docs/embedding/navigation.mdx b/docs/embedding/navigation.mdx new file mode 100644 index 0000000..20a4541 --- /dev/null +++ b/docs/embedding/navigation.mdx @@ -0,0 +1,87 @@ +--- +title: "Navigation" +description: "" +icon: "signs-post" +--- + +By default, navigating within your embedded instance of Activepieces doesn't affect the client's browser history or viewed URL. Activepieces only provide a **handler**, that trigger on every route change in the **iframe**. + +## Automatically Sync URL + +You can use the following snippet when configuring the SDK, which will implement a handler that syncs the Activepieces iframe with your browser: + + +The following snippet listens when the user clicks backward, so it syncs the route back to the iframe using `activepieces.navigate` and in the handler, it updates the URL of the browser. + + +```js +const instanceUrl = 'YOUR_INSTANCE_URL'; +const jwtToken = 'YOUR_GENERATED_JWT_TOKEN'; +const containerId = 'YOUR_CONTAINER_ID'; +activepieces.configure({ + instanceUrl, + jwtToken, + embedding: { + containerId, + builder: { + disableNavigation: false, + hideFlowName: false + }, + dashboard: { + hideSidebar: false + }, + hideFolders: false, + navigation: { + handler: ({ route }) => { + //route can include search params at the end of it + if (!window.location.href.endsWith(route)) { + window.history.pushState({}, "", window.location.origin + route); + } + } + } + }, +}); + +window.addEventListener("popstate", () => { + const route = activepieces.extractActivepiecesRouteFromUrl({ vendorUrl: window.location.href }); + activepieces.navigate({ route }); +}); +``` + +## Navigate Method + +If you use `activepieces.navigate({ route: '/flows' })` this will tell the embedded sdk where to navigate to. + +Here is the list for routes the sdk can navigate to: + +| Route | Description | +| --- | --- | +| `/flows` | Flows table +| `/flows/{flowId}` | Opens up a flow in the builder +| `/runs` | Runs table +| `/runs/{runId}` | Opens up a run in the builder +| `/connections` | Connections table +| `/tables` | Tables table +| `/tables/{tableId}` | Opens up a table +| `todos` | Todos table +| `todos/{todoId}` | Opens up a todo + + +## Navigate to Initial Route + +You can call the `navigate` method after initializing the sdk using the `configure` sdk: + +```js +const flowId = '1234'; +const instanceUrl = 'YOUR_INSTANCE_URL'; +const jwtToken = 'YOUR_GENERATED_JWT_TOKEN'; +activepieces.configure({ + instanceUrl, + jwtToken, +}).then(() => { + activepieces.navigate({ + route: `/flows/${flowId}` + }) +}); +``` + diff --git a/docs/embedding/overview.mdx b/docs/embedding/overview.mdx new file mode 100644 index 0000000..3c0cc20 --- /dev/null +++ b/docs/embedding/overview.mdx @@ -0,0 +1,33 @@ +--- +title: 'Overview' +description: 'Understanding how embedding works' +icon: "cube" +--- + + + +This section provides an overview of how to embed the Activepieces builder in your application and automatically provision the user. + +The embedding process involves the following steps: + + + + Generate a JSON Web Token (JWT) to identify your customer and pass it to the SDK, read more [here](./provision-users). + + + You can use the SDK to embed and customize Activepieces in your SaaS, read more [here](./embed-builder). + + + + + Here is an example of how it looks like in one of the SaaS that embed Activepieces: + ![Embedding Example](../resources/screenshots/embedding-example.png) + + +In case, you need to gather connections from your users in your SaaS. You can do this with the SDK. Find more info [here](./embed-connections). + + + +If you are looking for a way to communicate between Activpieces and the SaaS embedding it through a piece, we recommend you check the [custom property doc](/developers/piece-reference/properties#custom-property-beta) + + diff --git a/docs/embedding/predefined-connection.mdx b/docs/embedding/predefined-connection.mdx new file mode 100644 index 0000000..75c3956 --- /dev/null +++ b/docs/embedding/predefined-connection.mdx @@ -0,0 +1,257 @@ +--- +title: "Predefined Connection" +icon: "Link" +--- + +Use predefined connections to allow users to access your piece in the embedded app without re-entering authentication credentials. + +The high-level steps are: +- Create a global connection for a project using the API in the platform admin. Only platform admins can edit or delete global connections. +- (Optional) Hide the connections dropdown in the piece settings. + +### Prerequisites + +- [Run the Enterprise Edition](/handbook/engineering/playbooks/run-ee) +- [Create your piece](/developers/building-pieces/overview). Later we will customize the piece logic to use predefined connections. + +### Create a Predefined Connection + + + + Go to **Platform Admin → Security → API Keys** and create an API key. Save it for use in the next step. + ![Create API Key](/resources/screenshots/create-api-key.png) + + + Add the following snippet to your backend to create a global connection each time you generate the JWT token. + + The snippet does the following: + - Create Project If it doesn't exist. + - Create a global connection for the project with certain naming convention. +```js +const apiKey = 'YOUR_API_KEY'; +const instanceUrl = 'https://cloud.activepieces.com'; + +// The name of the user / organization in your SAAS +const externalProjectId = 'org_1234'; +const pieceName = '@activepieces/piece-gelato'; +// This will depend on what your piece auth type is, can be one of this ['PLATFORM_OAUTH2','SECRET_TEXT','BASIC_AUTH','CUSTOM_AUTH'] +const pieceAuthType = "CUSTOM_AUTH" +const connectionProps = { + // Fill in the props required by your piece's auth +} +const { id: projectId, externalId } = await getOrCreateProject({ + projectExternalId: externalProjectId, + apiKey, + instanceUrl, +}); + +await createGlobalConnection({ + projectId, + externalProjectId, + apiKey, + instanceUrl, + pieceName, + props, + pieceAuthType +}); + +``` +Implementation: + +```js +async function getOrCreateProject({ + projectExternalId, + apiKey, + instanceUrl, +}: { + projectExternalId: string, + apiKey: string, + instanceUrl: string +}): Promise<{ id: string, externalId: string }> { + + const projects = await fetch(`${instanceUrl}/api/v1/projects?externalId=${projectExternalId}`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + }, + }) + .then(response => response.json()) + .then(data => data.data) + .catch(err => { + console.error('Error fetching projects:', err); + return []; + }); + + if (projects.length > 0) { + return { + id: projects[0].id, + externalId: projects[0].externalId + }; + } + + const newProject = await fetch(`${instanceUrl}/api/v1/projects`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + displayName: projectExternalId, + metadata: {}, + externalId: projectExternalId + }) + }) + .then(response => response.json()) + .catch(err => { + console.error('Error creating project:', err); + throw err; + }); + + return { + id: newProject.id, + externalId: newProject.externalId + }; +} + +async function createGlobalConnection({ + projectId, + externalProjectId, + apiKey, + instanceUrl, + pieceName, + props, + pieceAuthType +}: { + projectId: string, + externalProjectId: string, + apiKey: string, + instanceUrl: string, + pieceName: string, + props: Record, + pieceAuthType +}) { + + const displayName = 'Gelato Connection'; + const connectionExternalId = 'gelato_' + externalProjectId; + + return fetch(`${instanceUrl}/api/v1/global-connections`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + displayName, + pieceName, + metadata: {}, + type: pieceAuthType, + value: { + type: pieceAuthType, + props + }, + scope: 'PLATFORM', + projectIds: [projectId], + externalId: connectionExternalId + }) + }); +} +``` + + + + +### Hide the Connections Dropdown (Optional) + + + + Wherever you call `createTrigger` or `createAction` set `requireAuth` to `false`, this will hide the connections dropdown in the piece settings in the builder, + next we need to fetch it based on a naming convention. + + + Here is example how you can fetch the connection value based on naming convention, make sure this naming convention is followed when creating a global connection. + +```js +import { + ConnectionsManager, + Property, + TriggerStrategy +} from "@activepieces/pieces-framework"; +import { + createTrigger +} from "@activepieces/pieces-framework"; +import { + isNil +} from "@activepieces/shared"; +// Add this import from the index.ts file, where it contains the definition of the auth object. +import { auth } from '../..'; + +const fetchConnection = async ( + connections: ConnectionsManager, + projectExternalId: string | undefined, +): Promise> => { + if (isNil(projectExternalId)) { + throw new Error('This project is missing an external id'); + } + // the naming convention here is gelato_projectExternalId + const connection = await connections.get(`gelato_${projectExternalId}`); + if (isNil(connection)) { + throw new Error(`Connection not found for project ${projectExternalId}`); + } + + return connection as PiecePropValueSchema; +}; + + +export const newFlavorCreated = createTrigger({ + requireAuth: false, + name: 'newFlavorCreated', + displayName: 'new flavor created', + description: 'triggers when a new icecream flavor is created.', + props: { + dropdown: Property.Dropdown({ + displayName: 'Dropdown', + required: true, + refreshers: [], + options: async (_, { + connections, + project + }) => { + const connection = await fetchConnection(connections, (await project.externalId())); + // your logic + return { + options: [{ + label: 'test', + value: 'test' + }] + } + } + }) + }, + sampleData: {}, + type: TriggerStrategy.POLLING, + async test({connections,project}) { + const connection = await fetchConnection(connections, (await project.externalId())); + // use the connection with your own logic + return [] + }, + async onEnable({connections,project}) { + const connection = await fetchConnection(connections, (await project.externalId())); + // use the connection with your own logic + }, + + async onDisable({connections,project}) { + const connection = await fetchConnection(connections, (await project.externalId())); + // use the connection with your own logic + }, + + async run({connections,project}) { + const connection = await fetchConnection(connections, (await project.externalId())); + // use the connection with your own logic + return [] + }, +}); +``` + + + diff --git a/docs/embedding/provision-users.mdx b/docs/embedding/provision-users.mdx new file mode 100644 index 0000000..bc5c923 --- /dev/null +++ b/docs/embedding/provision-users.mdx @@ -0,0 +1,112 @@ +--- +title: "Provision Users" +description: "Automatically authenticate your SaaS users to your Activepieces instance" +icon: 'user' +--- + + + +## Overview + +In Activepieces, there are **Projects** and **Users**. Each project is provisioned with their corresponding workspace, project, or team in your SaaS. The users are then mapped to the respective users in Activepieces. + +To achieve this, the backend will generate a signed token that contains all the necessary information to automatically create a user and project. If the user or project already exists, it will skip the creation and log in the user directly. + + + + You can generate a signing key by going to **Platform Settings -> Signing Keys -> Generate Signing Key**. + + This will generate a public and private key pair. The public key will be used by Activepieces to verify the signature of the JWT tokens you send. The private key will be used by you to sign the JWT tokens. + + ![Create Signing Key](../resources/screenshots/create-signing-key.png) + + Please store your private key in a safe place, as it will not be stored in Activepieces. + + + + + The signing key will be used to generate JWT tokens for the currently logged-in user on your website, which will then be sent to the Activepieces Iframe as a query parameter to authenticate the user and exchange the token for a longer lived token. + + To generate these tokens, you will need to add code in your backend to generate the token using the RS256 algorithm, so the JWT header would look like this: + + + To obtain the `SIGNING_KEY_ID`, refer to the signing key table and locate the value in the first column. + ![Signing Key ID](../resources/screenshots/signing-key-id.png) + + + ```json + { + "alg": "RS256", + "typ": "JWT", + "kid": "SIGNING_KEY_ID" + } + ``` + + The signed tokens must include these claims in the payload: + ```json + { + "version": "v3", + "externalUserId": "user_id", + "externalProjectId": "user_project_id", + "firstName": "John", + "lastName": "Doe", + "role": "EDITOR", + "piecesFilterType": "NONE", + "exp": 1856563200, + "tasks": 50000, + "aiCredits": 250 + } + ``` + + | Claim | Description | + |-------------------|-------------------------------------------------| + | externalUserId | Unique identification of the user in **your** software | + | externalProjectId | Unique identification of the user's project in **your** software | + | firstName | First name of the user | + | lastName | Last name of the user | + | role | Role of the user in the Activepieces project (e.g., **EDITOR**, **VIEWER**, **ADMIN**) | + | exp | Expiry timestamp for the token (Unix timestamp) | + | piecesFilterType | Customize the project pieces, check [customize pieces](/embedding/customize-pieces) | + | piecesTags | Customize the project pieces, check [customize pieces](/embedding/customize-pieces) | + | tasks | Customize the tasks limit for your user's project | + | aiCredits | Customize the ai credits limit for your user's project | + + You can use any JWT library to generate the token. Here is an example using the jsonwebtoken library in Node.js: + + + **Friendly Tip #1**: You can also use this [tool](https://dinochiesa.github.io/jwt/) to generate a quick example. + + + + **Friendly Tip #2**: Make sure the expiry time is very short, as it's a temporary token and will be exchanged for a longer-lived token. + + + ```javascript Node.js + const jwt = require('jsonwebtoken'); + + // JWT NumericDates specified in seconds: + const currentTime = Math.floor(Date.now() / 1000); + let token = jwt.sign( + { + version: "v3", + externalUserId: "user_id", + externalProjectId: "user_project_id", + firstName: "John", + lastName: "Doe", + role: "EDITOR", + piecesFilterType: "NONE", + exp: currentTime + (60 * 60), // 1 hour from now + }, + process.env.ACTIVEPIECES_SIGNING_KEY, + { + algorithm: "RS256", + header: { + kid: signingKeyID, // Include the "kid" in the header + }, + } + ); + ``` + + Once you have generated the token, please check the embedding docs to know how to embed the token in the iframe. + + diff --git a/docs/embedding/sdk-changelog.mdx b/docs/embedding/sdk-changelog.mdx new file mode 100644 index 0000000..3f70e92 --- /dev/null +++ b/docs/embedding/sdk-changelog.mdx @@ -0,0 +1,88 @@ +--- +title: "SDK Changelog" +description: "A log of all notable changes to Activepieces SDK" +icon: "code-commit" +--- + + + + +**Breaking Change:**



If your Activepieces image version is < 0.45.0 and (you are using the connect method from the embed SDk, and need the connection externalId to be returned after the user creates it OR if you want to reconnect a specific connection with an externalId), you must upgrade your Activepieces image to >= 0.45.0 +
+ + + Between Acitvepieces image version 0.32.1 and 0.46.4 the navigation handler was including the project id in the path, this might have broken implementation logic for people using the navigation handler, this has been fixed from 0.46.5 and onwards, the handler won't show the project id prepended to routes. + + +Change log format: DD/MM/YYYY (version) + +### 04/12/2024 (0.3.0) + + +- add custom navigation handler ([#4500](https://github.com/activepieces/activepieces/pull/4500)) +- allow passing a predefined name for connection in connect method ([#4485](https://github.com/activepieces/activepieces/pull/4485)) +- add changelog ([#4503](https://github.com/activepieces/activepieces/pull/4503)) + + +### 26/01/2025 (0.3.3) + +- This version requires you to update Activepieces to 0.39.8 +- activepieces.configure method was being resolved before the user was authenticated, this is fixed now, so you can use activepieces.navigate method to navigate to your desired initial route. + + +### 04/02/2025 (0.3.4) + +- This version requires you to update Activepieces to 0.41.0 +- Adds the ability to pass font family name and font url to the embed sdk + + +### 24/2/2025 (0.3.5) +- Added a new parameter to the connect method to make the connection dialog a popup instead of an iframe taking the full page. +- Fixed a bug where the returned promise from the connect method was always resolved to \{connection: undefined\} +- Now when you use the connect method with the "connectionName" parameter, the user will reconnect to the connection with the matching externalId instead of creating a new one. + + +### 16/04/2025 (0.3.6) +- Added the [request](./sdk-server-requests) method which allows you to call our backend API. + +### 17/04/2025 (0.3.7) +- Added [MCP methods](./mcps) to update MCP configurations. + + +### 20/05/2025 (0.4.0) + +-- Note: we didn't consider adding optional new parameters as a breaking change so we were bumping the patch version, but that was wrong and we will begin bumping the minor version for those changes from now on, and patch version will only get bumped for bug fixes. +- This version requires you to update Activepieces to 0.56.0 +- Added `embedding.hideExportAndImportFlow` parameter to the [configure](./embed-builder#configure-parameters) method. +- Added new possible value to the configure method param `embed.builder.disableNavigation` which is "keep_home_button_only" that keeps only the home button and hides the folder name with the delete flow action. +- Added new param to the configure method `embed.builder.homeButtonClickedHandler`, that overrides the navigation behaviour on clicking the home button. + + +### 26/05/2025 (0.4.1) +- Fixed an issue where sometimes the embed HTML file was getting cached. + + +### 16/06/2025 (0.5.0-rc.0) +- SDK URL: https://cdn.activepieces.com/sdk/embed/0.5.0-rc.0.js +- This version requires you to **upgrade Activepieces to [0.64.0-rc.0](https://github.com/activepieces/activepieces/pkgs/container/activepieces/438888138?tag=0.64.0-rc.0)** +- Added `embedding.hideDuplicateFlow` parameter to the [configure](./embed-builder#configure-parameters) method **(value: true | false)**. +- Added `embedding.builder.homeButtonIcon` parameter to the [configure](./embed-builder#configure-parameters) method **(value: 'logo' | 'back')**, if set to **'back'** the tooltip shown on hovering the home button is removed. +- Added `embedding.locale` parameter to the [configure](./embed-builder#configure-parameters) method, it takes [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) locale codes, here are the ones supported: **('en' | 'nl' | 'it' | 'de' | 'fr' | 'bg' | 'uk' | 'hu' | 'es' | 'ja' | 'id' | 'vi' | 'zh' | 'pt')** +- Added `embedding.styling.mode` parameter to [configure](./embed-builder#configure-parameters) method **(value: 'light' | 'dark')** +- **(Breaking Change)** Removed `prefix` parameter from the [configure](./embed-builder#configure-parameters) method. +- **(Breaking Change)** Removed `embedding.builder.hideLogo` parameter from the [configure](./embed-builder#configure-parameters) method. + +### 17/06/2025 (0.5.0-rc.1) +- SDK URL: https://cdn.activepieces.com/sdk/embed/0.5.0-rc.1.js +- This version requires you to **upgrade Activepieces to [0.64.0-rc.0](https://github.com/activepieces/activepieces/pkgs/container/activepieces/438888138?tag=0.64.0-rc.0)** +- Revert back the `prefix` parameter from the [configure](./embed-builder#configure-parameters) method. + + +### 03/07/2025 (0.5.0) +- SDK URL: https://cdn.activepieces.com/sdk/embed/0.5.0.js +- This version requires you to **upgrade Activepieces to [0.64.2](https://github.com/activepieces/activepieces/releases/tag/0.64.2)** +- Added `embedding.hideDuplicateFlow` parameter to the [configure](./embed-builder#configure-parameters) method **(value: true | false)**. +- Added `embedding.builder.homeButtonIcon` parameter to the [configure](./embed-builder#configure-parameters) method **(value: 'logo' | 'back')**, if set to **'back'** the tooltip shown on hovering the home button is removed. +- Added `embedding.locale` parameter to the [configure](./embed-builder#configure-parameters) method, it takes [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) locale codes, here are the ones supported: **('en' | 'nl' | 'it' | 'de' | 'fr' | 'bg' | 'uk' | 'hu' | 'es' | 'ja' | 'id' | 'vi' | 'zh' | 'pt')** +- Added `embedding.styling.mode` parameter to [configure](./embed-builder#configure-parameters) method **(value: 'light' | 'dark')** +- **(Breaking Change)** Removed `embedding.builder.hideLogo` parameter from the [configure](./embed-builder#configure-parameters) method. \ No newline at end of file diff --git a/docs/embedding/sdk-server-requests.mdx b/docs/embedding/sdk-server-requests.mdx new file mode 100644 index 0000000..9a46e7b --- /dev/null +++ b/docs/embedding/sdk-server-requests.mdx @@ -0,0 +1,46 @@ +--- +title: "API Requests" +description: "Send requests to your Activepieces instance from the embedded app" +icon: "server" +--- + + +**Requirements:** +* Activepieces version 0.34.5 or higher +* SDK version 0.3.6 or higher + + + + +You can use the embedded SDK to send requests to your instance and retrieve data. + + + + + Follow the instructions in the [Embed Builder](./embed-builder) to initialize the SDK. + + + + + + ```html + + ``` + + **Request Parameters:** + + | Parameter Name | Required | Type | Description | + | -------------- | -------- | ------ | ------------------------------------------- | + | path | ✅ | string | The path within your instance you want to hit (we prepend the path with your_instance_url/api/v1) + | method | ✅ | string | The http method to use 'GET', 'POST','PUT', 'DELETE', 'OPTIONS', 'PATCH' and 'HEAD + | body | ❌ | JSON object | The json body of your request + | queryParams | ❌ | Record\ | The query params to include in your request + + + + + + + diff --git a/docs/endpoints/connections/delete.mdx b/docs/endpoints/connections/delete.mdx new file mode 100644 index 0000000..fa708b5 --- /dev/null +++ b/docs/endpoints/connections/delete.mdx @@ -0,0 +1,4 @@ +--- +title: 'Delete Connection' +openapi: DELETE /v1/app-connections/{id} +--- \ No newline at end of file diff --git a/docs/endpoints/connections/list.mdx b/docs/endpoints/connections/list.mdx new file mode 100644 index 0000000..e88223e --- /dev/null +++ b/docs/endpoints/connections/list.mdx @@ -0,0 +1,4 @@ +--- +title: 'List Connections' +openapi: GET /v1/app-connections/ +--- \ No newline at end of file diff --git a/docs/endpoints/connections/schema.mdx b/docs/endpoints/connections/schema.mdx new file mode 100644 index 0000000..7ca8984 --- /dev/null +++ b/docs/endpoints/connections/schema.mdx @@ -0,0 +1,5 @@ +--- +title: 'Connection Schema' +openapi-schema: app-connection +icon: cube +--- \ No newline at end of file diff --git a/docs/endpoints/connections/upsert.mdx b/docs/endpoints/connections/upsert.mdx new file mode 100644 index 0000000..48a184e --- /dev/null +++ b/docs/endpoints/connections/upsert.mdx @@ -0,0 +1,4 @@ +--- +title: 'Upsert Connection' +openapi: POST /v1/app-connections +--- \ No newline at end of file diff --git a/docs/endpoints/flow-runs/get.mdx b/docs/endpoints/flow-runs/get.mdx new file mode 100644 index 0000000..022a76d --- /dev/null +++ b/docs/endpoints/flow-runs/get.mdx @@ -0,0 +1,4 @@ +--- +title: 'Get Flow Run' +openapi: GET /v1/flow-runs/{id} +--- \ No newline at end of file diff --git a/docs/endpoints/flow-runs/list.mdx b/docs/endpoints/flow-runs/list.mdx new file mode 100644 index 0000000..bf82a8d --- /dev/null +++ b/docs/endpoints/flow-runs/list.mdx @@ -0,0 +1,4 @@ +--- +title: 'List Flows Runs' +openapi: GET /v1/flow-runs +--- \ No newline at end of file diff --git a/docs/endpoints/flow-runs/schema.mdx b/docs/endpoints/flow-runs/schema.mdx new file mode 100644 index 0000000..4b411c7 --- /dev/null +++ b/docs/endpoints/flow-runs/schema.mdx @@ -0,0 +1,5 @@ +--- +title: 'Flow Run Schema' +openapi-schema: flow-run +icon: cube +--- \ No newline at end of file diff --git a/docs/endpoints/flow-templates/create.mdx b/docs/endpoints/flow-templates/create.mdx new file mode 100644 index 0000000..766f278 --- /dev/null +++ b/docs/endpoints/flow-templates/create.mdx @@ -0,0 +1,4 @@ +--- +title: 'Create Flow Template' +openapi: POST /v1/flow-templates +--- \ No newline at end of file diff --git a/docs/endpoints/flow-templates/delete.mdx b/docs/endpoints/flow-templates/delete.mdx new file mode 100644 index 0000000..898455c --- /dev/null +++ b/docs/endpoints/flow-templates/delete.mdx @@ -0,0 +1,4 @@ +--- +title: 'Delete Flow Template' +openapi: DELETE /v1/flow-templates/{id} +--- \ No newline at end of file diff --git a/docs/endpoints/flow-templates/get.mdx b/docs/endpoints/flow-templates/get.mdx new file mode 100644 index 0000000..d8b34b8 --- /dev/null +++ b/docs/endpoints/flow-templates/get.mdx @@ -0,0 +1,4 @@ +--- +title: 'Get Flow Template' +openapi: GET /v1/flow-templates/{id} +--- \ No newline at end of file diff --git a/docs/endpoints/flow-templates/list.mdx b/docs/endpoints/flow-templates/list.mdx new file mode 100644 index 0000000..cf5c7fb --- /dev/null +++ b/docs/endpoints/flow-templates/list.mdx @@ -0,0 +1,4 @@ +--- +title: 'List Flow Templates' +openapi: GET /v1/flow-templates +--- \ No newline at end of file diff --git a/docs/endpoints/flow-templates/schema.mdx b/docs/endpoints/flow-templates/schema.mdx new file mode 100644 index 0000000..20b4992 --- /dev/null +++ b/docs/endpoints/flow-templates/schema.mdx @@ -0,0 +1,5 @@ +--- +title: 'Flow Template Schema' +openapi-schema: flow-template +icon: cube +--- \ No newline at end of file diff --git a/docs/endpoints/flows/create.mdx b/docs/endpoints/flows/create.mdx new file mode 100644 index 0000000..8a78d1f --- /dev/null +++ b/docs/endpoints/flows/create.mdx @@ -0,0 +1,4 @@ +--- +title: 'Create Flow' +openapi: POST /v1/flows +--- \ No newline at end of file diff --git a/docs/endpoints/flows/delete.mdx b/docs/endpoints/flows/delete.mdx new file mode 100644 index 0000000..760dee0 --- /dev/null +++ b/docs/endpoints/flows/delete.mdx @@ -0,0 +1,4 @@ +--- +title: 'Delete Flow' +openapi: DELETE /v1/flows/{id} +--- \ No newline at end of file diff --git a/docs/endpoints/flows/get.mdx b/docs/endpoints/flows/get.mdx new file mode 100644 index 0000000..08da511 --- /dev/null +++ b/docs/endpoints/flows/get.mdx @@ -0,0 +1,4 @@ +--- +title: 'Get Flow' +openapi: GET /v1/flows/{id} +--- \ No newline at end of file diff --git a/docs/endpoints/flows/list.mdx b/docs/endpoints/flows/list.mdx new file mode 100644 index 0000000..5819126 --- /dev/null +++ b/docs/endpoints/flows/list.mdx @@ -0,0 +1,4 @@ +--- +title: 'List Flows' +openapi: GET /v1/flows +--- \ No newline at end of file diff --git a/docs/endpoints/flows/schema.mdx b/docs/endpoints/flows/schema.mdx new file mode 100644 index 0000000..ac5cd35 --- /dev/null +++ b/docs/endpoints/flows/schema.mdx @@ -0,0 +1,5 @@ +--- +title: 'Flow Schema' +openapi-schema: flow +icon: cube +--- \ No newline at end of file diff --git a/docs/endpoints/flows/update.mdx b/docs/endpoints/flows/update.mdx new file mode 100644 index 0000000..ab641be --- /dev/null +++ b/docs/endpoints/flows/update.mdx @@ -0,0 +1,4 @@ +--- +title: 'Apply Flow Operation' +openapi: POST /v1/flows/{id} +--- \ No newline at end of file diff --git a/docs/endpoints/folders/create.mdx b/docs/endpoints/folders/create.mdx new file mode 100644 index 0000000..33740d8 --- /dev/null +++ b/docs/endpoints/folders/create.mdx @@ -0,0 +1,4 @@ +--- +title: 'Create Folder' +openapi: POST /v1/folders +--- \ No newline at end of file diff --git a/docs/endpoints/folders/delete.mdx b/docs/endpoints/folders/delete.mdx new file mode 100644 index 0000000..bb6c1e7 --- /dev/null +++ b/docs/endpoints/folders/delete.mdx @@ -0,0 +1,4 @@ +--- +title: 'Delete Folder' +openapi: DELETE /v1/folders/{id} +--- \ No newline at end of file diff --git a/docs/endpoints/folders/get.mdx b/docs/endpoints/folders/get.mdx new file mode 100644 index 0000000..f208284 --- /dev/null +++ b/docs/endpoints/folders/get.mdx @@ -0,0 +1,4 @@ +--- +title: 'Get Folder' +openapi: GET /v1/folders/{id} +--- \ No newline at end of file diff --git a/docs/endpoints/folders/list.mdx b/docs/endpoints/folders/list.mdx new file mode 100644 index 0000000..e1f4dba --- /dev/null +++ b/docs/endpoints/folders/list.mdx @@ -0,0 +1,4 @@ +--- +title: 'List Folders' +openapi: GET /v1/folders +--- \ No newline at end of file diff --git a/docs/endpoints/folders/schema.mdx b/docs/endpoints/folders/schema.mdx new file mode 100644 index 0000000..586ffc6 --- /dev/null +++ b/docs/endpoints/folders/schema.mdx @@ -0,0 +1,5 @@ +--- +title: 'Folder Schema' +openapi-schema: folder +icon: cube +--- \ No newline at end of file diff --git a/docs/endpoints/folders/update.mdx b/docs/endpoints/folders/update.mdx new file mode 100644 index 0000000..edfaa3e --- /dev/null +++ b/docs/endpoints/folders/update.mdx @@ -0,0 +1,4 @@ +--- +title: 'Update Folder' +openapi: POST /v1/folders/{id} +--- \ No newline at end of file diff --git a/docs/endpoints/git-repos/configure.mdx b/docs/endpoints/git-repos/configure.mdx new file mode 100644 index 0000000..9579cca --- /dev/null +++ b/docs/endpoints/git-repos/configure.mdx @@ -0,0 +1,4 @@ +--- +title: 'Configure' +openapi: POST /v1/git-repos +--- diff --git a/docs/endpoints/git-repos/schema.mdx b/docs/endpoints/git-repos/schema.mdx new file mode 100644 index 0000000..62bb937 --- /dev/null +++ b/docs/endpoints/git-repos/schema.mdx @@ -0,0 +1,5 @@ +--- +title: 'Git Repos Schema' +openapi-schema: git-repo +icon: cube +--- \ No newline at end of file diff --git a/docs/endpoints/global-connections/delete.mdx b/docs/endpoints/global-connections/delete.mdx new file mode 100644 index 0000000..73aa531 --- /dev/null +++ b/docs/endpoints/global-connections/delete.mdx @@ -0,0 +1,4 @@ +--- +title: 'Delete Global Connection' +openapi: DELETE /v1/global-connections/{id} +--- \ No newline at end of file diff --git a/docs/endpoints/global-connections/list.mdx b/docs/endpoints/global-connections/list.mdx new file mode 100644 index 0000000..11b6095 --- /dev/null +++ b/docs/endpoints/global-connections/list.mdx @@ -0,0 +1,5 @@ +--- +title: 'List Global Connections' +openapi: GET /v1/global-connections +--- + diff --git a/docs/endpoints/global-connections/schema.mdx b/docs/endpoints/global-connections/schema.mdx new file mode 100644 index 0000000..da16fb9 --- /dev/null +++ b/docs/endpoints/global-connections/schema.mdx @@ -0,0 +1,5 @@ +--- +title: 'Global Connection Schema' +openapi-schema: global-connection +icon: cube +--- \ No newline at end of file diff --git a/docs/endpoints/global-connections/update.mdx b/docs/endpoints/global-connections/update.mdx new file mode 100644 index 0000000..47f8244 --- /dev/null +++ b/docs/endpoints/global-connections/update.mdx @@ -0,0 +1,5 @@ +--- +title: 'Update Global Connection' +openapi: POST /v1/global-connections/{id} +--- + diff --git a/docs/endpoints/global-connections/upsert.mdx b/docs/endpoints/global-connections/upsert.mdx new file mode 100644 index 0000000..378d321 --- /dev/null +++ b/docs/endpoints/global-connections/upsert.mdx @@ -0,0 +1,5 @@ +--- +title: 'Upsert Global Connection' +openapi: POST /v1/global-connections +--- + diff --git a/docs/endpoints/mcp-servers/list.mdx b/docs/endpoints/mcp-servers/list.mdx new file mode 100644 index 0000000..5852c6f --- /dev/null +++ b/docs/endpoints/mcp-servers/list.mdx @@ -0,0 +1,4 @@ +--- +title: 'List MCP servers' +openapi: GET /v1/mcp-servers +--- \ No newline at end of file diff --git a/docs/endpoints/mcp-servers/rotate.mdx b/docs/endpoints/mcp-servers/rotate.mdx new file mode 100644 index 0000000..6d48a08 --- /dev/null +++ b/docs/endpoints/mcp-servers/rotate.mdx @@ -0,0 +1,4 @@ +--- +title: 'Rotate MCP server token' +openapi: POST /v1/mcp-servers/{id}/rotate +--- \ No newline at end of file diff --git a/docs/endpoints/mcp-servers/schema.mdx b/docs/endpoints/mcp-servers/schema.mdx new file mode 100644 index 0000000..f5a57b5 --- /dev/null +++ b/docs/endpoints/mcp-servers/schema.mdx @@ -0,0 +1,5 @@ +--- +title: 'MCP Server Schema' +openapi-schema: mcp +icon: Cube +--- \ No newline at end of file diff --git a/docs/endpoints/mcp-servers/update.mdx b/docs/endpoints/mcp-servers/update.mdx new file mode 100644 index 0000000..66557c1 --- /dev/null +++ b/docs/endpoints/mcp-servers/update.mdx @@ -0,0 +1,4 @@ +--- +title: 'Update MCP Server' +openapi: POST /v1/mcp-servers/{id} +--- \ No newline at end of file diff --git a/docs/endpoints/overview.mdx b/docs/endpoints/overview.mdx new file mode 100644 index 0000000..a511c95 --- /dev/null +++ b/docs/endpoints/overview.mdx @@ -0,0 +1,36 @@ +--- +title: "Overview" +icon: "webhook" +--- + + +API keys are generated under the platform dashboard at this moment to manage multiple projects, which is only available in the Platform and Enterprise editions, +Please contact sales@activepieces.com for more information. + + +### Authentication: + +The API uses "API keys" to authenticate requests. You can view and manage your API keys from the Platform Dashboard. After creating the API keys, you can pass the API key as a Bearer token in the header. + +Example: +`Authorization: Bearer {API_KEY}` + +### Pagination + +All endpoints use seek pagination, to paginate through the results, you can provide the `limit` and `cursor` as query parameters. + +The API response will have the following structure: +```json +{ + "data": [], + "next": "string", + "previous": "string" +} +``` + +- **`data`**: Holds the requested results or data. + +- **`next`**: Provides a starting cursor for the next set of results, if available. + +- **`previous`**: Provides a starting cursor for the previous set of results, if applicable. + diff --git a/docs/endpoints/pieces/install.mdx b/docs/endpoints/pieces/install.mdx new file mode 100644 index 0000000..3f7ac74 --- /dev/null +++ b/docs/endpoints/pieces/install.mdx @@ -0,0 +1,4 @@ +--- +title: 'Install Piece' +openapi: POST /v1/pieces +--- \ No newline at end of file diff --git a/docs/endpoints/pieces/schema.mdx b/docs/endpoints/pieces/schema.mdx new file mode 100644 index 0000000..6a15054 --- /dev/null +++ b/docs/endpoints/pieces/schema.mdx @@ -0,0 +1,5 @@ +--- +title: 'Piece Schema' +openapi-schema: piece +icon: cube +--- \ No newline at end of file diff --git a/docs/endpoints/project-members/delete.mdx b/docs/endpoints/project-members/delete.mdx new file mode 100644 index 0000000..a598dd8 --- /dev/null +++ b/docs/endpoints/project-members/delete.mdx @@ -0,0 +1,4 @@ +--- +title: 'Delete Project Member' +openapi: DELETE /v1/project-members/{id} +--- \ No newline at end of file diff --git a/docs/endpoints/project-members/list.mdx b/docs/endpoints/project-members/list.mdx new file mode 100644 index 0000000..5fe2786 --- /dev/null +++ b/docs/endpoints/project-members/list.mdx @@ -0,0 +1,4 @@ +--- +title: 'List Project Member' +openapi: GET /v1/project-members +--- \ No newline at end of file diff --git a/docs/endpoints/project-members/schema.mdx b/docs/endpoints/project-members/schema.mdx new file mode 100644 index 0000000..95580bc --- /dev/null +++ b/docs/endpoints/project-members/schema.mdx @@ -0,0 +1,5 @@ +--- +title: 'Project Member Schema' +openapi-schema: project-member +icon: cube +--- \ No newline at end of file diff --git a/docs/endpoints/project-releases/create.mdx b/docs/endpoints/project-releases/create.mdx new file mode 100644 index 0000000..7d30aeb --- /dev/null +++ b/docs/endpoints/project-releases/create.mdx @@ -0,0 +1,4 @@ +--- +title: 'Create Project Release' +openapi: POST /v1/project-releases +--- \ No newline at end of file diff --git a/docs/endpoints/project-releases/schema.mdx b/docs/endpoints/project-releases/schema.mdx new file mode 100644 index 0000000..2ad0555 --- /dev/null +++ b/docs/endpoints/project-releases/schema.mdx @@ -0,0 +1,5 @@ +--- +title: 'Project Release Schema' +openapi-schema: project-release +icon: cube +--- \ No newline at end of file diff --git a/docs/endpoints/projects/create.mdx b/docs/endpoints/projects/create.mdx new file mode 100644 index 0000000..ccc24db --- /dev/null +++ b/docs/endpoints/projects/create.mdx @@ -0,0 +1,4 @@ +--- +title: 'Create Project' +openapi: POST /v1/projects +--- \ No newline at end of file diff --git a/docs/endpoints/projects/list.mdx b/docs/endpoints/projects/list.mdx new file mode 100644 index 0000000..e4708db --- /dev/null +++ b/docs/endpoints/projects/list.mdx @@ -0,0 +1,4 @@ +--- +title: 'List Projects' +openapi: GET /v1/projects +--- \ No newline at end of file diff --git a/docs/endpoints/projects/schema.mdx b/docs/endpoints/projects/schema.mdx new file mode 100644 index 0000000..a55d91b --- /dev/null +++ b/docs/endpoints/projects/schema.mdx @@ -0,0 +1,5 @@ +--- +title: 'Project Schema' +openapi-schema: project +icon: cube +--- \ No newline at end of file diff --git a/docs/endpoints/projects/update.mdx b/docs/endpoints/projects/update.mdx new file mode 100644 index 0000000..47bb3c8 --- /dev/null +++ b/docs/endpoints/projects/update.mdx @@ -0,0 +1,4 @@ +--- +title: 'Update Project' +openapi: POST /v1/projects/{id} +--- \ No newline at end of file diff --git a/docs/endpoints/sample-data/get.mdx b/docs/endpoints/sample-data/get.mdx new file mode 100644 index 0000000..d06d8b2 --- /dev/null +++ b/docs/endpoints/sample-data/get.mdx @@ -0,0 +1,7 @@ +--- +title: 'Get Sample Data' +openapi: GET /v1/sample-data +--- + + + diff --git a/docs/endpoints/user-invitations/delete.mdx b/docs/endpoints/user-invitations/delete.mdx new file mode 100644 index 0000000..45801a6 --- /dev/null +++ b/docs/endpoints/user-invitations/delete.mdx @@ -0,0 +1,4 @@ +--- +title: 'Delete User Invitation' +openapi: DELETE /v1/user-invitations/{id} +--- \ No newline at end of file diff --git a/docs/endpoints/user-invitations/list.mdx b/docs/endpoints/user-invitations/list.mdx new file mode 100644 index 0000000..80cef4b --- /dev/null +++ b/docs/endpoints/user-invitations/list.mdx @@ -0,0 +1,4 @@ +--- +title: 'List User Invitations' +openapi: GET /v1/user-invitations +--- \ No newline at end of file diff --git a/docs/endpoints/user-invitations/schema.mdx b/docs/endpoints/user-invitations/schema.mdx new file mode 100644 index 0000000..af64b50 --- /dev/null +++ b/docs/endpoints/user-invitations/schema.mdx @@ -0,0 +1,5 @@ +--- +title: 'User Invitation Schema' +openapi-schema: user-invitation +icon: cube +--- \ No newline at end of file diff --git a/docs/endpoints/user-invitations/upsert.mdx b/docs/endpoints/user-invitations/upsert.mdx new file mode 100644 index 0000000..a9eb0b2 --- /dev/null +++ b/docs/endpoints/user-invitations/upsert.mdx @@ -0,0 +1,4 @@ +--- +title: 'Send User Invitation (Upsert)' +openapi: POST /v1/user-invitations +--- \ No newline at end of file diff --git a/docs/endpoints/users/list.mdx b/docs/endpoints/users/list.mdx new file mode 100644 index 0000000..02e0e34 --- /dev/null +++ b/docs/endpoints/users/list.mdx @@ -0,0 +1,5 @@ +--- +title: 'List Users' +openapi: GET /v1/users +icon: user +--- \ No newline at end of file diff --git a/docs/endpoints/users/schema.mdx b/docs/endpoints/users/schema.mdx new file mode 100644 index 0000000..3a7a0a1 --- /dev/null +++ b/docs/endpoints/users/schema.mdx @@ -0,0 +1,5 @@ +--- +title: 'User Schema' +openapi-schema: user +icon: user +--- \ No newline at end of file diff --git a/docs/favicon.png b/docs/favicon.png new file mode 100644 index 0000000..9ae61ee Binary files /dev/null and b/docs/favicon.png differ diff --git a/docs/favicon.svg b/docs/favicon.svg new file mode 100755 index 0000000..b069197 --- /dev/null +++ b/docs/favicon.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/flows/building-flows.mdx b/docs/flows/building-flows.mdx new file mode 100644 index 0000000..139b3d6 --- /dev/null +++ b/docs/flows/building-flows.mdx @@ -0,0 +1,16 @@ +--- +title: "Building Flows" +icon: 'hammer' +description: "Flow consists of two parts, trigger and actions" +--- +## Trigger + +The flow's starting point determines its frequency of execution. There are various types of triggers available, such as Schedule Trigger, Webhook Trigger, or Event Trigger based on specific service. + +## Action + +Actions come after the flow and control what occurs when the flow is activated, like running code or communicating with other services. + +In real-life scenario: + +![Flow Parts](/resources/flow-parts.png) \ No newline at end of file diff --git a/docs/flows/debugging-runs.mdx b/docs/flows/debugging-runs.mdx new file mode 100755 index 0000000..bbb3fdc --- /dev/null +++ b/docs/flows/debugging-runs.mdx @@ -0,0 +1,15 @@ +--- +title: "Debugging Runs" +icon: 'bug' +description: "Ensuring your business automations are running properly" +--- + +You can monitor each run that results from an enabled flow: + +1. Go to the Dashboard, click on **Runs**. +2. Find the run that you're looking for, and click on it. +3. You will see the builder in a view-only mode, each step will show a ✅ or a ❌ to indicate its execution status. +4. Click on any of these steps, you will see the **input** and **output** in the **Run Details** panel. + +The debugging experience looks like this: +![Debugging Business Automations](/resources/screenshots/using-activepieces-debugging.png) \ No newline at end of file diff --git a/docs/flows/known-limits.mdx b/docs/flows/known-limits.mdx new file mode 100644 index 0000000..1564719 --- /dev/null +++ b/docs/flows/known-limits.mdx @@ -0,0 +1,42 @@ +--- +title: "Technical limits" +icon: 'ban' +description: "technical limits for Activepieces execution" +--- + +### Overview + + +This Limits applies for the **Activepieces Cloud**, and can be configured via environment variables for self-hosted instances. + + +### Flow Limits + +- **Execution Time**: Each flow has a maximum execution time of **600 seconds (10 minutes)**. Flows exceeding this limit will be marked as a timeout. +- **Memory Usage**: During execution, a flow should not use more than **128 MB of RAM**. + + +**Friendly Tip #1:** Flow run in a paused state, such as Wait for Approval or Delay, do not count toward the 600 seconds. + + + +**Friendly Tip #2:** The execution time limit can be worked around by splitting the flows into multiple ones, such as by having one flow call another flow using a webhook, or by having each flow process a small batch of items. + + +### File Storage Limits + +The files from actions or triggers are stored in the database / S3 to support retries from certain steps. + + +- **Maximum File Size**: 10 MB + +### Data Storage Limits + +Some pieces utilize the built-in Activepieces key store, such as the Store Piece and Queue Piece. + +The storage limits are as follows: + +- **Maximum Key Length**: 128 characters +- **Maximum Value Size**: 512 KB + + diff --git a/docs/flows/passing-data.mdx b/docs/flows/passing-data.mdx new file mode 100755 index 0000000..d011e8e --- /dev/null +++ b/docs/flows/passing-data.mdx @@ -0,0 +1,106 @@ +--- +title: "Passing Data" +icon: 'wand-sparkles' +description: "Using data from previous steps in the current one" +--- + +## Data flow + +Any Activepieces flow is a vertical diagram that **starts with a trigger step** followed by **any number of action steps**. + +Steps are connected vertically. Data flows from parent steps to the children. Children steps have access to the output data of the parent steps. + +## Example Steps + + +This flow has 3 steps, they can access data as follows: + +- **Step 1** is the main data producer to be used in the next steps. Data produced by Step 1 will be accessible in Steps 2 and 3. Some triggers don't produce data though, like Schedules. + +- **Step 2** can access data produced by Step 1. After execution, this step will also produce data to be used in the next step(s). + +- **Step 3** can access data produced by Steps 1 and 2 as they're its parent steps. This step can produce data but since it's the last step in the flow, it can't be used by other ones. + +## Data to Insert Panel + +In order to use data from a previous step in your current step, place your cursor in any input, the **Data to Insert** panel will pop up. + + + +This panel shows the accessible steps and their data. You can expand the data items to view their content, and you can click the items to insert them in your current settings input. + +If an item in this panel has a caret (⌄) to the right, it means you can click on the item to expand its child properties. You can select the parent item or its properties as you need. + +When you insert data from this panel, it gets inserted at the cursor's position in the input. This means you can combine static text and dynamic data in any field. + + + +We generally recommend that you expand the items before inserting them to understand the type of data they contain and whether they're the right fit to the input you're filling. + +## Testing Steps to Generate Data + +We require you to test steps before accessing their data. This approach protects you from selecting the wrong data and breaking your flows after publishing them. + +If a step is not tested and you try to access its data, you will see the following message: +Test your automation step first + +To fix this, go to the step and use the Generate Sample Data panel to test it. Steps use different approaches for testing. These are the common ones: + +- **Load Data:** Some triggers will let you load data from your connected account without having to perform any action in that account. +- **Test Trigger:** Some triggers will require you to head to your connected account and fire the trigger in order to generate sample data. +- **Send Data:** Webhooks require you to send a sample request to the webhook URL to generate sample data. +- **Test Action:** Action steps will let you run the action in order to generate sample data. + +Follow the instructions in the Generate Sample Data panel to know how your step should be tested. Some triggers will also let you Use Mock Data, which will generate static sample data from the piece. We recommend that you test the step instead of using mock data. + +This is an example for generating sample data for a trigger using the **Load Data** button: + + + +## Advanced Tips + +### Switching to Dynamic Values + +Dropdowns and some other input types don't let you select data from previous steps. If you'd like to bypass this and use data from previous steps instead, switch the input into a dynamic one using this button: + + + +### Accessing data by path + +If you can't find the data you're looking for in the **Data to Insert** panel but you'd like to use it, you can write a JSON path instead. + +Use the following syntax to write JSON paths: + +`{{step_slug.path.to.property}}` + +The `step_slug` can be found by moving your cursor over any of your flow steps, it will show to the right of the step. \ No newline at end of file diff --git a/docs/flows/publishing-flows.mdx b/docs/flows/publishing-flows.mdx new file mode 100644 index 0000000..ce5ea22 --- /dev/null +++ b/docs/flows/publishing-flows.mdx @@ -0,0 +1,9 @@ +--- +title: "Publishing Flows" +icon: 'bolt' +description: "Make your flow work by publishing your updates" +--- + +The changes you make won't work right away to avoid disrupting the flow that's already published. To enable your changes, simply click on the publish button once you're done with your changes. + +![Flow Parts](/resources/publish-flow.png) \ No newline at end of file diff --git a/docs/flows/versioning.mdx b/docs/flows/versioning.mdx new file mode 100644 index 0000000..e497d95 --- /dev/null +++ b/docs/flows/versioning.mdx @@ -0,0 +1,18 @@ +--- +title: "Version History" +icon: 'clock' +description: "Learn how flow versioning works in Activepieces" +--- + +Activepieces keeps track of all published flows and their versions. Here’s how it works: + +1. You can edit a flow as many times as you want in **draft** mode. +2. Once you're done with your changes, you can publish it. +3. The published flow will be **immutable** and cannot be edited. +4. If you try to edit a published flow, Activepieces will create a new **draft** if there is none and copy the **published** version to the new version. + +This means you can always go back to a previous version and edit the flow in draft mode without affecting the published version. + +![Flow History](/resources/flow-history.png) + +As you can see in the following screenshot, the yellow dot refers to DRAFT and the green dot refers to PUBLISHED. diff --git a/docs/getting-started/introduction.mdx b/docs/getting-started/introduction.mdx new file mode 100755 index 0000000..0a559db --- /dev/null +++ b/docs/getting-started/introduction.mdx @@ -0,0 +1,66 @@ +--- +sidebarTitle: "What is Activepieces?" +title: "🥳 Welcome to Activepieces" +icon: 'hand-wave' +description: "Your friendliest open source all-in-one automation tool, designed to be extensible." +--- + + + Learn how to work with Activepieces + + + Browse available pieces + + + Learn how to install Activepieces + + + How to Build Pieces and Contribute + + + +# 🔥 Why Activepieces is Different: + +- **💖 Loved by Everyone**: Intuitive interface and great experience for both technical and non-technical users with a quick learning curve. + + +![](/resources/templates.gif) +- **🌐 Open Ecosystem:** All pieces are open source and available on npmjs.com, **60% of the pieces are contributed by the community**. + +- **🛠️ Pieces are written in Typescript**: Pieces are npm packages in TypeScript, offering full customization with the best developer experience, including **hot reloading** for **local** piece development on your machine. 😎 + + +![](/resources/create-action.png) + + +- **🤖 AI-Ready**: Native AI pieces let you experiment with various providers, or create your own agents using our AI SDK, and there is Copilot to help you build flows inside the builder. + +- **🏢 Enterprise-Ready**: Developers set up the tools, and anyone in the organization can use the no-code builder. Full customization from branding to control. + +- **🔒 Secure by Design**: Self-hosted and network-gapped for maximum security and control over your data. + +- **🧠 Human in Loop**: Delay execution for a period of time or require approval. These are just pieces built on top of the piece framework, and you can build many pieces like that. 🎨 + +- **💻 Human Input Interfaces**: Built-in support for human input triggers like "Chat Interface" 💬 and "Form Interface" 📝 + + diff --git a/docs/getting-started/principles.mdx b/docs/getting-started/principles.mdx new file mode 100644 index 0000000..b796a40 --- /dev/null +++ b/docs/getting-started/principles.mdx @@ -0,0 +1,21 @@ +--- +title: "Product Principles" +sidebarTitle: "Product Principles" +icon: "pen-nib" +--- + +## 🌟 Keep It Simple + +- Design the product to be accessible for everyone, regardless of their background and technical expertise. + +- The code is in a monorepository under one service, making it easy to develop, maintain, and scale. + +- Keep the technology stack simple to achieve massive adoption. + +- Keep the software unopinionated and unlock niche use cases by making it extensible through pieces. + +## 🧩 Keep It Extensible + +- Automation pieces framework has minimal abstraction and allow you to extend for any usecase. + +- All contributions are welcome. The core is open source, and commercial code is available. diff --git a/docs/handbook/customer-support/handle-requests.mdx b/docs/handbook/customer-support/handle-requests.mdx new file mode 100644 index 0000000..0bd34da --- /dev/null +++ b/docs/handbook/customer-support/handle-requests.mdx @@ -0,0 +1,94 @@ +--- +title: "How to handle Requests" +icon: "ticket" +--- + +As a support engineer, you should: + +* Fix the urgent issues (please see the definition below) +* Open tickets for all non-urgent issues. **(DO NOT INCLUDE ANY SENSITIVE INFO IN ISSUE)** +* Keep customers updated +* Write clear ticket descriptions +* Help the team prioritize work +* Route issues to the right people + +### Ticket fields + +When handling support tickets, ensure you set the appropriate status and priority to help with ticket management and response time: + +**Status Field**: + +These status fields help both our team and customers understand whether an issue will be addressed in future development sprints or requires immediate attention. + +- **Backlog**: Issues planned for future development sprints that don't require immediate attention +- **Prioritized**: High-priority issues requiring immediate team focus and resolution + +**Priority Levels**: + + +Make sure when opening a ticket on Linear to match the priority you have in Pylon. We have a view for immediate tickets (High + Medium priority) to be considered in the next sprint planning. +[View Immediate Tickets](https://linear.app/activepieces/team/AP/view/immediate-f6fa2e7fcaed) + + +During sprint planning, we filter and prioritize customer requests with Medium priority or higher to identify which tickets need immediate attention. This helps us focus our development efforts on the most impactful customer issues. + +- **Urgent (P0)**: Emergency issues requiring immediate on-call response + - Critical system outages + - Security vulnerabilities + - Major functionality breakdowns affecting multiple customers + +- **High (P1)**: Critical features or blockers + - Core functionality issues + - Features essential for customer operations + - Significant customer-impacting bugs + +- **Medium (P2)**: Important but non-critical issues + - Feature enhancements blocking specific workflows + - Performance improvements + - UX improvements affecting usability + +- **Low (P3)**: Non-urgent improvements + - Minor enhancements + - UI polish + - Nice-to-have features + + +### Requests + +### Type 1: Quick Fixes & Urgent Issues + +* Understand the issue and how urgent it is. +* If the issue is important/urgent and easy to fix, handle it yourself and open a PR right away. This leaves a great impression! + +### Type 2: Complex Technical Issues + +* Always create a GitHub issue for the feature request, and send it to the customer. +* Assess the issue and determine its urgency. +* Leave a comment on the GitHub issue with an estimated completion time. + +### Type 3: Feature Enhancement Requests + +* Always create a GitHub issue for the feature request and send it to the customer. +* Evaluate the request and dig deeper into what the customer is trying to solve, then either evaluate and open a new ticket or append to an existing ticket in the backlog. +* Add it to our roadmap and discuss it with the team. + + +New features will always have the status "Backlog". Please make sure to communicate that we will discuss and address it in future production cycles so the customer doesn't expect immediate action. + + +### Frequently Asked Questions + + + + If you don't understand the feature or issue, reach out to the customer for clarification. It's important to fully grasp the problem before proceeding. You can also consult with your team for additional insights. + + + + When faced with multiple urgent issues, assess the impact of each on the customer and the system. Prioritize based on severity, number of affected users, and potential risks. Communicate with your team to ensure alignment on priorities. + + + + If you encounter an abusive or rude customer, escalate the issue to Mohammad AbuAboud or Ashraf Samhouri. It's important to handle such situations with care and ensure that the customer feels heard while maintaining a respectful and professional demeanor. + + + diff --git a/docs/handbook/customer-support/overview.mdx b/docs/handbook/customer-support/overview.mdx new file mode 100644 index 0000000..ce7008d --- /dev/null +++ b/docs/handbook/customer-support/overview.mdx @@ -0,0 +1,30 @@ +--- +title: "Overview" +icon: "cube" +--- + +At Activepieces, we take a unique approach to customer support. Instead of having dedicated support staff, our full-time engineers handle support requests on rotation. This ensures you get expert technical help from the people who build the product. + +### Support Schedule + +Our on-call engineer handles customer support as part of their rotation. For more details about how this works, check out our on-call documentation. + + +### Support Channels + +- Community Support + - GitHub Issues: We actively monitor and respond to issues on our [GitHub repository](https://github.com/activepieces/activepieces) + - Community Forum: We engage with users on our [Community Platform](https://community.activepieces.com/) to provide help and gather feedback + - Email: only for account related issues, delete account request or billing issues. + +- Enterprise Support + - Enterprise customers receive dedicated support through Slack + - We use [Pylon](https://usepylon.com) to manage support tickets and customer channels efficiently + - For detailed information on using Pylon, see our [Pylon Guide](/docs/handbook/customer-support/pylon) + + +### Support Hours & SLA: + + +Work in progress—coming soon! + \ No newline at end of file diff --git a/docs/handbook/customer-support/pylon.mdx b/docs/handbook/customer-support/pylon.mdx new file mode 100644 index 0000000..8e9a9ea --- /dev/null +++ b/docs/handbook/customer-support/pylon.mdx @@ -0,0 +1,50 @@ +--- +title: "How to use Pylon" +description: "Guide for using Pylon to manage customer support tickets" +icon: "hexagon" +--- + +At Activepieces, we use Pylon to manage Slack-based customer support requests through a Kanban board. + +Learn more about Pylon's features: https://docs.usepylon.com/pylon-docs + +![Pylon board showing different columns for ticket management](/resources/pylon-board.png) + +### New Column +Contains new support requests that haven't been reviewed yet +- Action Items: + - Respond fast even if you don't have an answer, the important thing here is to reply that you will take a look into it, the key to winning the customer's heart. + + +### On You Column +Contains active tickets that require your attention and response. These tickets need immediate review and action. + +- Action items: + - Set ticket fields (status and priority) according to the guide below + - Check the [handle request page](./handle-requests) on how to handle tickets + + +The goal as a support engineer is to keep the "New" and "On You" columns empty. + + + +### On Hold + +Contains only tickets that have a linked Linear issue. + +- Place tickets here after: + - You have identified the customer's issue + - You have created a Linear issue (if one doesn't exist - avoid duplicates!) + - You have linked the issue in Pylon + - You have assigned it to a team member (for urgent cases only) + + +Please do not place tickets on hold without a ticket. + + + +Tickets will automatically move back to the "On You" column when the linked GitHub issue is closed. + + +### Closed Column +This means you did awesome job and the ticket reached it's Final destination for resolved tickets and no further attention required. diff --git a/docs/handbook/customer-support/tone.mdx b/docs/handbook/customer-support/tone.mdx new file mode 100644 index 0000000..b3e3bb4 --- /dev/null +++ b/docs/handbook/customer-support/tone.mdx @@ -0,0 +1,38 @@ +--- +title: "Tone & Communication" +icon: "comments" +--- + + +Our customers are fellow engineers and great people to work with. This guide will help you understand the tone and communication style that reflects Activepieces' culture in customer support. + +#### Casual + +Chat with them like you're talking to a friend. There's no need to sound like a robot. For example: +* ✅ "Hey there! How can I help you today?" +* ❌ "Greetings. How may I assist you with your inquiry?" +* ✅ "No worries, we'll get this sorted out together!" +* ❌ "Please hold while I process your request." + +#### Fast +Reply quickly! People love fast responses. Even if you don't know the answer right away, let them know you'll get back to them with the information. This is the fastest way to make customers happy; everyone likes to be heard. + +#### Honest + +Explain the issue clearly, don't be defensive, and be honest. We're all about open source and transparency here – it's part of our culture. For example: +* ✅ "I'm sorry, I forgot to follow up on this. Let's get it sorted out now." +* ❌ "I apologize for the delay; there were unforeseen circumstances." + +### Always Communicate the Next Step + +Always clarify the next step, such as whether the ticket will receive an immediate response or be added to the backlog for team discussion. + +#### Use "we," not "I" +* ✅ "We made a mistake here. We'll fix that for you." +* ❌ "I'll look into this for you." +* You're speaking on behalf of the company in every email you send. +* Use "we" to show customers they have the whole team's support. + + +Customers are real people who want to talk to real people. Be yourself, be helpful, and focus on solving their problems! + \ No newline at end of file diff --git a/docs/handbook/customer-support/trial.mdx b/docs/handbook/customer-support/trial.mdx new file mode 100644 index 0000000..a971a4e --- /dev/null +++ b/docs/handbook/customer-support/trial.mdx @@ -0,0 +1,9 @@ +--- +title: "Trial Key Management" +description: "Description of your new file." +icon: 'key' +--- + +Please read more how to create development / production keys for the customer in the following document. + +* [Trial Key Management Guide](https://docs.google.com/document/d/1k4-_ZCgyejS9UKA7AwkSB-l2TEZcnK2454o2joIgm4k/edit?tab=t.0#heading=h.ziaohggn8z8d): Includes detailed instructions on generating and extending 14-day trial keys. \ No newline at end of file diff --git a/docs/handbook/engineering/onboarding/downtime-incident.mdx b/docs/handbook/engineering/onboarding/downtime-incident.mdx new file mode 100644 index 0000000..c685255 --- /dev/null +++ b/docs/handbook/engineering/onboarding/downtime-incident.mdx @@ -0,0 +1,97 @@ +--- +title: Handling Downtime +icon: turn-down +--- + +![Downtime Incident](https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExdTZnbGxjc3k5d3NxeXQwcmhxeTRsbnNybnd4NG41ZnkwaDdsa3MzeSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/2UCt7zbmsLoCXybx6t/giphy.gif) + +## 📋 What You Need Before Starting + +Make sure these are ready: +- **[Incident.io Setup](../playbooks/setup-incident-io)**: For managing incidents. +- **Grafana & Loki**: For checking logs and errors. +- **Checkly Debugging**: For testing and monitoring. + +--- + +## 🚨 Stay Calm and Take Action + + + Don’t panic! Follow these steps to fix the issue. + + +1. **Tell Your Users**: + - Let your users know there’s an issue. Post on [Community](https://community.activepieces.com) and Discord. + - Example message: *“We’re looking into a problem with our services. Thanks for your patience!”* + +2. **Find Out What’s Wrong**: + - Gather details. What’s not working? When did it start? + +3. **Update the Status Page**: + - Use [Incident.io](https://incident.io) to update the status page. Set it to *“Investigating”* or *“Partial Outage”*. + +--- + +## 🔍 Check for Infrastructure Problems + +1. **Look at DigitalOcean**: + - Check if the CPU, memory, or disk usage is too high. + - If it is: + - **Increase the machine size** temporarily to fix the issue. + - Keep looking for the root cause. + +--- + +## 📜 Check Logs and Errors + +1. **Use Grafana & Loki**: + - Search for recent errors in the logs. + - Look for anything unusual or repeating. + +2. **Check Sentry**: + - Look for grouped errors (errors that happen a lot). + - Try to **reproduce the error** and fix it if possible. + +--- + +## 🛠️ Debugging with Checkly + +1. **Check Checkly Logs**: + - Watch the **video recordings** of failed checks to see what went wrong. + - If the issue is a **timeout**, it might mean there’s a bigger performance problem. + - If it's an E2E test failure due to UI changes, it's likely not urgent. + - Fix the test and the issue will go away. + +--- + +## 🚨 When Should You Ask for Help? + +Ask for help right away if: +- Flows are failing. +- The whole platform is down. +- There's a lot of data loss or corruption. +- You're not sure what is causing the issue. +- You've spent **more than 5 minutes** and still don't know what's wrong. + +💡 **How to Ask for Help**: +- Use **Incident.io** to create a **critical alert**. +- Go to the **Slack incident channel** and escalate the issue to the engineering team. + + + If you’re unsure, **ask for help!** It’s better to be safe than sorry. + + +--- + +## 💡 Helpful Tips + +1. **Stay Organized**: + - Keep a list of steps to follow during downtime. + - Write down everything you do so you can refer to it later. + +2. **Communicate Clearly**: + - Keep your team and users updated. + - Use simple language in your updates. + +3. **Take Care of Yourself**: + - If you feel stressed, take a short break. Grab a coffee ☕, take a deep breath, and tackle the problem step by step. diff --git a/docs/handbook/engineering/onboarding/how-we-work.mdx b/docs/handbook/engineering/onboarding/how-we-work.mdx new file mode 100644 index 0000000..11dac14 --- /dev/null +++ b/docs/handbook/engineering/onboarding/how-we-work.mdx @@ -0,0 +1,31 @@ +--- +title: "Engineering Workflow" +icon: 'lightbulb' +--- + +Activepieces work is based on one-week sprints, as priorities change fast, the sprint has to be short to adapt. + +## Sprints + +Sprints are shared publicly on our GitHub account. This would give everyone visibility into what we are working on. + +* There should be a GitHub issue for the sprint set up in advance that outlines the changes. +* Each _individual_ should come prepared with specific suggestions for what they will work on over the next sprint. **if you're in an engineering role, no one will dictate to you what to build – it is up to you to drive this.** +* Teams generally meet once a week to pick the **priorities** together. +* Everyone in the team should attend the sprint planning. +* Anyone can comment on the sprint issue before or after the sprint. + +## Pull Requests + +When it comes to code review, we have a few guidelines to ensure efficiency: + +- Create a pull request in draft state as soon as possible. +- Be proactive and review other people’s pull requests. Don’t wait for someone to ask for your review; it’s your responsibility. +- Assign only one reviewer to your pull request. +- Add the PR to the current project (sprint) so we can keep track of unmerged PRs at the end of each sprint. +- It is the **responsibility** of the **PR owner** to draft the test scenarios within the PR description. Upon review, the reviewer may assume that these scenarios have been tested and provide additional suggestions for scenarios. +- Large, incomplete features should be broken down into smaller tasks and continuously merged into the main branch. + +## Planning is everyone's job. + +Every engineer is responsible for discovering bugs/opportunities and bringing them up in the sprint to convert them into actionable tasks. diff --git a/docs/handbook/engineering/onboarding/on-call.mdx b/docs/handbook/engineering/onboarding/on-call.mdx new file mode 100644 index 0000000..43ee9c2 --- /dev/null +++ b/docs/handbook/engineering/onboarding/on-call.mdx @@ -0,0 +1,64 @@ +--- +title: 'On-Call' +icon: 'phone' +--- + +## Prerequisites: +- [Setup Incident IO](../playbooks/setup-incident-io) + +## Why On-Call? + +We need to ensure there is **exactly one person** at the same time who is the main point of contact for the users and the **first responder** for the issues. It's also a great way to learn about the product and the users and have some fun. + + + You can listen to [Queen - Under Pressure](https://www.youtube.com/watch?v=a01QQZyl-_I) while on-call, it's fun and motivating. + + + + If you ever feel burn out in middle of your rotation, please reach out to the team and we will help you with the rotation or take over the responsibility. + + +## On-Call Schedule + +The on-call rotation is managed through Incident.io, with each engineer taking a one-week shift. You can: +- View the current schedule and upcoming rotations on [Incident.io On-Call Schedule](https://app.incident.io/activepieces/on-call/schedules) +- Add the schedule to your Google Calendar using [this link](https://calendar.google.com/calendar/r?cid=webcal://app.incident.io/api/schedule_feeds/cc024d13704b618cbec9e2c4b2415666dfc8b1efdc190659ebc5886dfe2a1e4b) + + +Make sure to update the on-call schedule in Incident.io if you cannot be available during your assigned rotation. This ensures alerts are routed to the correct person and maintains our incident response coverage. + +To modify the schedule: +1. Go to [Incident.io On-Call Schedule](https://app.incident.io/activepieces/on-call/schedules) +2. Find your rotation slot +3. Click "Override schedule" to mark your unavailability +4. Coordinate with the team to find coverage for your slot + + + +## What it means to be on-call + +The primary objective of being on-call is to triage issues and assist users. It is not about fixing the issues or coding missing features. Delegation is key whenever possible. + +You are responsible for the following: + +* Respond to Slack messages as soon as possible, referring to the [customer support guidelines](/handbook/customer-support/overview). + +* Check [community.activepieces.com](https://community.activepieces.com) for any new issues or to learn about existing issues. + +* Monitor your Incident.io notifications and respond promptly when paged. + + + **Friendly Tip #1**: always escalate to the team if you are unsure what to do. + + +## How do you get paged? + +Monitor and respond to incidents that come through these channels: + +#### Slack Fire Emoji (🔥) +When a customer reports an issue in Slack and someone reacts with 🔥, you'll be automatically paged and a dedicated incident channel will be created. + +#### Automated Alerts +Watch for notifications from: + - Digital Ocean about CPU, Memory, or Disk outages + - Checkly about e2e test failures or website downtime diff --git a/docs/handbook/engineering/onboarding/onboarding-check-list.mdx b/docs/handbook/engineering/onboarding/onboarding-check-list.mdx new file mode 100644 index 0000000..04ad360 --- /dev/null +++ b/docs/handbook/engineering/onboarding/onboarding-check-list.mdx @@ -0,0 +1,52 @@ +--- +title: "Onboarding Check List" +icon: 'lightbulb' +--- + +🎉 Welcome to Activepieces! + +This guide provides a checklist for the new hire onboarding process. + +--- + +## 📧 Essentials + +- [ ] Set up your @activepieces.com email account and setup 2FA +- [ ] Confirm access to out private Discord server. +- [ ] Get Invited to the Activepieces Github Organization and Setup 2FA +- [ ] Get Assigned to a buddy who will be your onboarding buddy. + + + +During your first two months, we'll schedule 1:1 meetings every two weeks to ensure you're progressing well and to maintain open communication in both directions. +After two months, we will decrease the frequency of the 1:1 to once a month. + + + + +If you don't setup the 2FA, We will be alerted from security perspective. + + +--- + + +### Engineering Checklist + +- [ ] Setup your development environment using our setup guide +- [ ] Learn the repository structure and our tech stack (Fastify, React, PostgreSQL, SQLite, Redis) +- [ ] Understand the key database tables (Platform, Projects, Flows, Connections, Users) +- [ ] Complete your first "warmup" task within your first day (it's our tradition!) + + +--- +## 🌟 Tips for Success + +- Don't hesitate to ask questions—the team is especially helpful during your first days +- Take time to understand the product from a business perspective +- Work closely with your onboarding buddy to get up to speed quickly +- Review our documentation, explore the codebase, and check out community resources, outside your scope. +- Provide your ideas and feedback regularly + +--- + +Welcome again to the team. We can't wait to see the impact you'll make at Activepieces! 😉 diff --git a/docs/handbook/engineering/overview.mdx b/docs/handbook/engineering/overview.mdx new file mode 100644 index 0000000..c035fdf --- /dev/null +++ b/docs/handbook/engineering/overview.mdx @@ -0,0 +1,8 @@ +--- +title: "Overview" +icon: "code" +--- + +Welcome to the engineering team! This section contains essential information to help you get started, including our development processes, guidelines, and practices. We're excited to have you on board. + + diff --git a/docs/handbook/engineering/playbooks/bullboard.mdx b/docs/handbook/engineering/playbooks/bullboard.mdx new file mode 100644 index 0000000..c615886 --- /dev/null +++ b/docs/handbook/engineering/playbooks/bullboard.mdx @@ -0,0 +1,91 @@ +--- +title: "Queues Dashboard" +icon: "gauge-high" +--- + +The Bull Board is a tool that allows you to check issues with scheduling and internal flow runs issues. + +![BullBoard Overview](https://raw.githubusercontent.com/felixmosh/bull-board/master/screenshots/overview.png) + +## Setup BullBoard + +To enable the Bull Board UI in your self-hosted installation: + +1. Define these environment variables: + - `AP_QUEUE_UI_ENABLED`: Set to `true` + - `AP_QUEUE_UI_USERNAME`: Set your desired username + - `AP_QUEUE_UI_PASSWORD`: Set your desired password + +2. Access the UI at `/api/ui` + + + +For cloud installations, please ask your team for access to the internal documentation that explains how to access BullBoard. + + +## Common Issues + +### Scheduling Issues + +If a scheduled flow is not triggering as expected: + +1. Check the `repeatableJobs` queue in BullBoard to verify the job exists +2. Verify the job status is not "failed" or "delayed" +3. Check that the cron expression or interval is configured correctly +4. Look for any error messages in the job details + +### Flow Stuck in "Running" State + +If a flow appears stuck in the running state: + +1. Check the `oneTimeJobs` queue for the corresponding job +2. Look for: + - Jobs in "delayed" state (indicates retry attempts) + - Jobs in "failed" state (indicates execution errors) +3. Review the job logs for error messages or timeouts +4. If needed, you can manually remove stuck jobs through the BullBoard UI + +## Queue Overview + +We maintain four main queues in our system: + +#### Scheduled Queue (`repeatableJobs`) + +Contains both polling and delayed jobs. + + +Failed jobs are not normal and need to be checked right away to find and fix what's causing them. + + + +Delayed jobs represent either paused flows scheduled for future execution or upcoming polling job iterations. + + +#### One-Time Queue (`oneTimeJobs`) +Handles immediate flow executions that run only once + + +- Delayed jobs indicate an internal system error occurred and the job will be retried automatically according to the backoff policy +- Failed jobs require immediate investigation as they represent executions that failed for unknown reasons that could indicate system issues + + +#### Webhook Queue (`webhookJobs`) + +Handles incoming webhook triggers + + +- Delayed jobs indicate an internal system error occurred and the job will be retried automatically according to the backoff policy +- Failed jobs require immediate investigation as they represent executions that failed for unknown reasons that could indicate system issues + + +#### Users Interaction Queue (`usersInteractionJobs`) + +Handles operations that are directly initiated by users, including: +• Installing pieces +• Testing flows +• Loading dropdown options +• Executing triggers +• Executing actions + +Failed jobs in this queue are not retried since they represent real-time user actions that should either succeed or fail immediately + diff --git a/docs/handbook/engineering/playbooks/database-migration.mdx b/docs/handbook/engineering/playbooks/database-migration.mdx new file mode 100644 index 0000000..7cc945b --- /dev/null +++ b/docs/handbook/engineering/playbooks/database-migration.mdx @@ -0,0 +1,94 @@ +--- +title: "Database Migrations" +description: "Guide for creating database migrations in Activepieces" +icon: "database" +--- + +Activepieces uses TypeORM as its database driver in Node.js. We support two database types across different editions of our platform. + +The database migration files contain both what to do to migrate (up method) and what to do when rolling back (down method). + + +Read more about TypeORM migrations here: +https://orkhan.gitbook.io/typeorm/docs/migrations + + +## Database Support + +- PostgreSQL +- SQLite + + +**Why Do we have SQLite?** +We support SQLite to simplify development and self-hosting. It's particularly helpful for: + +- Developers creating pieces who want a quick setup +- Self-hosters using platforms to manage docker images but doesn't support docker compose. + + +## Editions + +- **Enterprise & Cloud Edition** (Must use PostgreSQL) +- **Community Edition** (Can use PostgreSQL or SQLite) + + +If you are generating a migration for an entity that will only be used in Cloud & Enterprise editions, you only need to create the PostgreSQL migration file. You can skip generating the SQLite migration. + + + +### How To Generate + + + + Uncomment the following line in `packages/server/api/src/app/database/database-connection.ts`: + ```typescript + export const exportedConnection = databaseConnection() + ``` + + + + Edit your `.env` file to set the database type: + + ```env + # For SQLite migrations (default) + AP_DATABASE_TYPE=SQLITE + ``` + + For PostgreSQL migrations: + ```env + AP_DATABASE_TYPE=POSTGRES + AP_POSTGRES_DATABASE=activepieces + AP_POSTGRES_HOST=db + AP_POSTGRES_PORT=5432 + AP_POSTGRES_USERNAME=postgres + AP_POSTGRES_PASSWORD=password + ``` + + + + Run the migration generation command: + ```bash + nx db-migration server-api name= + ``` + Replace `` with a descriptive name for your migration. + + + + The command will generate a new migration file in `packages/server/api/src/app/database/migrations`. + Review the generated file and: + + - For PostgreSQL migrations: Move it to `postgres-connection.ts` + - For SQLite migrations: Move it to `sqlite-connection.ts` + + + + After moving the file, remember to re-comment the line from step 1: + ```typescript + // export const exportedConnection = databaseConnection() + ``` + + + + +Always test your migrations by running them both up and down to ensure they work as expected. + diff --git a/docs/handbook/engineering/playbooks/frontend-best-practices.mdx b/docs/handbook/engineering/playbooks/frontend-best-practices.mdx new file mode 100644 index 0000000..de9c16e --- /dev/null +++ b/docs/handbook/engineering/playbooks/frontend-best-practices.mdx @@ -0,0 +1,253 @@ +--- +title: "Frontend Best Practices" +icon: 'lightbulb' +--- + +## Overview + +Our frontend codebase is large and constantly growing, with multiple developers contributing to it. Establishing consistent rules across key areas like data fetching and state management will make the code easier to follow, refactor, and test. It will also help newcomers understand existing patterns and adopt them quickly. + +## Data Fetching with React Query + +### Hook Organization + +All `useMutation` and `useQuery` hooks should be grouped by domain/feature in a single location: `features/lib/feature-hooks.ts`. Never call data fetching hooks directly from component bodies. + +**Benefits:** +- Easier refactoring and testing +- Simplified mocking for tests +- Cleaner components focused on UI logic +- Reduced clutter in `.tsx` files + +#### ❌ Don't do +```tsx +// UserProfile.tsx +import { useMutation, useQuery } from '@tanstack/react-query'; +import { updateUser, getUser } from '../api/users'; + +function UserProfile({ userId }) { + const { data: user } = useQuery({ + queryKey: ['user', userId], + queryFn: () => getUser(userId) + }); + + const updateUserMutation = useMutation({ + mutationFn: updateUser, + onSuccess: () => { + // refetch logic here + } + }); + + return ( +
+ {/* UI logic */} +
+ ); +} +``` + +#### ✅ Do +```tsx +// features/users/lib/user-hooks.ts +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { updateUser, getUser } from '../api/users'; +import { userKeys } from './user-keys'; + +export function useUser(userId: string) { + return useQuery({ + queryKey: userKeys.detail(userId), + queryFn: () => getUser(userId) + }); +} + +export function useUpdateUser() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: updateUser, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: userKeys.all }); + } + }); +} + +// UserProfile.tsx +import { useUser, useUpdateUser } from '../lib/user-hooks'; + +function UserProfile({ userId }) { + const { data: user } = useUser(userId); + const updateUserMutation = useUpdateUser(); + + return ( +
+ {/* Clean UI logic only */} +
+ ); +} +``` + +### Query Keys Management + +Query keys should be unique identifiers for specific queries. Avoid using boolean values, empty strings, or inconsistent patterns. + +**Best Practice:** Group all query keys in one centralized location (inside the hooks file) for easy management and refactoring. + +```tsx +// features/users/lib/user-hooks.ts +export const userKeys = { + all: ['users'] as const, + lists: () => [...userKeys.all, 'list'] as const, + list: (filters: string) => [...userKeys.lists(), { filters }] as const, + details: () => [...userKeys.all, 'detail'] as const, + detail: (id: string) => [...userKeys.details(), id] as const, + preferences: (id: string) => [...userKeys.detail(id), 'preferences'] as const, +}; + +// Usage examples: +// userKeys.all // ['users'] +// userKeys.list('active') // ['users', 'list', { filters: 'active' }] +// userKeys.detail('123') // ['users', 'detail', '123'] +``` + +**Benefits:** +- Easy key renaming and refactoring +- Consistent key structure across the app +- Better query specificity control +- Centralized key management + +### Refetch vs Query Invalidation + +Prefer using `invalidateQueries` over passing `refetch` functions between components. This approach is more maintainable and easier to understand. + +#### ❌ Don't do +```tsx +function UserList() { + const { data: users, refetch } = useUsers(); + + return ( +
+ + + {/* Passing refetch everywhere */} +
+ ); +} +``` + +#### ✅ Do +```tsx +// In your mutation hooks +export function useCreateUser() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: createUser, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: userKeys.lists() }); + } + }); +} + +// Components don't need to handle refetching +function UserList() { + const { data: users } = useUsers(); + + return ( +
+ {/* Handles its own invalidation */} + {/* Handles its own invalidation */} +
+ ); +} +``` + +## Dialog State Management + +Use a centralized store or context to manage all dialog states in one place. This eliminates the need to pass local state between different components and provides global access to dialog controls. + +### Implementation Example + +```tsx +// stores/dialog-store.ts +import { create } from 'zustand'; +import { immer } from 'zustand/middleware/immer'; + +interface DialogState { + createUser: boolean; + editUser: boolean; + deleteConfirmation: boolean; + // Add more dialogs as needed +} + +interface DialogStore { + dialogs: DialogState; + setDialog: (dialog: keyof DialogState, isOpen: boolean) => void; +} + +export const useDialogStore = create()( + immer((set) => ({ + dialogs: { + createUser: false, + editUser: false, + deleteConfirmation: false, + }, + setDialog: (dialog, isOpen) => + set((state) => { + state.dialogs[dialog] = isOpen; + }), + })) +); + +// Usage in components +function UserManagement() { + const { dialogs, setDialog } = useDialogStore(); + + return ( +
+ + + setDialog('createUser', false)} + /> + + setDialog('editUser', false)} + /> +
+ ); +} + +// Any component can control dialogs - no provider needed +function Sidebar() { + const setDialog = useDialogStore((state) => state.setDialog); + + return ( + + ); +} + +// You can also use selectors for better performance +function UserDialog() { + const isOpen = useDialogStore((state) => state.dialogs.createUser); + const setDialog = useDialogStore((state) => state.setDialog); + + return ( + setDialog('createUser', false)} + /> + ); +} +``` +**Benefits:** +- Centralized dialog state management +- No prop drilling of dialog states +- Easy to open/close dialogs from anywhere in the app +- Consistent dialog behavior across the application +- Simplified component logic \ No newline at end of file diff --git a/docs/handbook/engineering/playbooks/infrastructure.mdx b/docs/handbook/engineering/playbooks/infrastructure.mdx new file mode 100644 index 0000000..eb6aa51 --- /dev/null +++ b/docs/handbook/engineering/playbooks/infrastructure.mdx @@ -0,0 +1,26 @@ +--- +title: "Cloud Infrastructure" +icon: "server" +--- + + + The playbooks are private, Please ask your team for an access. + + + +Our infrastructure stack consists of several key components that help us monitor, deploy, and manage our services effectively. + +## Hosting Providers + +We use two main hosting providers: + +- **DigitalOcean**: Hosts our databases including Redis and PostgreSQL +- **Hetzner**: Provides the machines that run our services + +## Grafana (Loki) for Logs + +We use Grafana Loki to collect and search through logs from all our services in one centralized place. + +## Kamal for Deployment + +Kamal is a deployment tool that helps us deploy our Docker containers to production with zero downtime. diff --git a/docs/handbook/engineering/playbooks/product-announcement.mdx b/docs/handbook/engineering/playbooks/product-announcement.mdx new file mode 100644 index 0000000..f3f43a9 --- /dev/null +++ b/docs/handbook/engineering/playbooks/product-announcement.mdx @@ -0,0 +1,41 @@ +--- +title: "Feature Announcement" +icon: "bullhorn" +--- + +When we develop new features, our marketing team handles the public announcements. As engineers, we need to clearly communicate: + +1. The problem the feature solves +2. The benefit to our users +3. How it integrates with our product + +### Handoff to Marketing Team + +There is an integration between GitHub and Linear, that automatically open a ticket for the marketing team after 5 minutes of issue get closed.\ +\ +Please make sure of the following: + +- Github Pull Request is linked to an issue. +- The pull request must have one of these labels: **"Pieces"**, **"Polishing"**, or **"Feature"**. + - If none of these labels are added, the PR will not be merged. + - You can also add any other relevant label. +- The GitHub issue must include the correct template (see "Ticket templates" below). + + + Bonus: Please include a video showing the marketing team on how to use this feature so they can create a demo video and market it correctly. + + +Ticket templates: + +``` +### What Problem Does This Feature Solve? + +### Explain How the Feature Works +[Insert the video link here] + +### Target Audience +Enterprise / Everyone + +### Relevant User Scenarios +[Insert Pylon tickets or community posts here] +``` \ No newline at end of file diff --git a/docs/handbook/engineering/playbooks/releases.mdx b/docs/handbook/engineering/playbooks/releases.mdx new file mode 100644 index 0000000..eda2dde --- /dev/null +++ b/docs/handbook/engineering/playbooks/releases.mdx @@ -0,0 +1,29 @@ +--- +title: 'How to create Release' +icon: 'flask' +--- + +Pre-releases are versions of the software that are released before the final version. They are used to test new features and bug fixes before they are released to the public. Pre-releases are typically labeled with a version number that includes a pre-release identifier, such as `official` or `rc`. + +## Types of Releases + +There are several types of releases that can be used to indicate the stability of the software: + +- **Official**: Official releases are considered to be stable and are close to the final release. +- **Release Candidate (RC)**: Release candidates are versions of the software that are feature-complete and have been tested by a larger group of users. They are considered to be stable and are close to the final release. They are typically used for final testing before the final release. + +## Why Use Pre-Releases + +We do pre-release when we release hot-fixes / bug fixes / small and beta features. + +## How to Release a Pre-Release + +To release a pre-release version of the software, follow these steps: + +1. **Create a new branch**: Create a new branch from the `main` branch. The branch name should be `release/vX.Y.Z` where `X.Y.Z` is the version number. +2. **Increase the version number**: Update the `package.json` file with the new version number. +3. **Open a Pull Request**: Open a pull request from the new branch to the `main` branch. Assign the `pre-release` label to the pull request. +4. **Check the Changelog**: Check the [Activepieces Releases](https://github.com/activepieces/activepieces/releases) page to see if there are any new features or bug fixes that need to be included in the pre-release. Make sure all PRs are labeled correctly so they show in the correct auto-generated changelog. If not, assign the labels and rerun the changelog by removing the "pre-release" label and adding it again to the PR. +5. Go to https://github.com/activepieces/activepieces/actions/workflows/release-rc.yml and run it on the release branch to build the rc image. +6. **Merge the Pull Request**: Merge the pull request to the `main` branch. +7. **Release the Notes**: Release the notes for the new version. diff --git a/docs/handbook/engineering/playbooks/run-ee.mdx b/docs/handbook/engineering/playbooks/run-ee.mdx new file mode 100644 index 0000000..f318c95 --- /dev/null +++ b/docs/handbook/engineering/playbooks/run-ee.mdx @@ -0,0 +1,51 @@ +--- +title: "Run Enterprise Edition" +icon: "building" +--- + +The enterprise edition requires a postgres and redis instance to run, and a license key to activate. + + + + +Follow the instructions [here](/developers/development-setup/dev-container) to run the dev container. + + + + +Pase the following env variables in `server/api/.env` + +```bash +## these variables are set to align with the .devcontainer/docker-compose.yml file +AP_DB_TYPE=POSTGRES +AP_DEV_PIECES="your_piece_name" +AP_ENVIRONMENT="dev" +AP_EDITION=ee +AP_EXECUTION_MODE=SANDBOXED +AP_FRONTEND_URL="http://localhost:4200" +AP_WEBHOOK_URL="http://localhost:3000" +AP_PIECES_SOURCE='FILE' +AP_PIECES_SYNC_MODE='NONE' +AP_LOG_LEVEL=debug +AP_LOG_PRETTY=true +AP_QUEUE_MODE=REDIS +AP_REDIS_HOST="redis" +AP_REDIS_PORT="6379" +AP_TRIGGER_DEFAULT_POLL_INTERVAL=1 +AP_CACHE_PATH=/workspace/cache +AP_POSTGRES_DATABASE=activepieces +AP_POSTGRES_HOST=db +AP_POSTGRES_PORT=5432 +AP_POSTGRES_USERNAME=postgres +AP_POSTGRES_PASSWORD=A79Vm5D4p2VQHOp2gd5 +AP_ENCRYPTION_KEY=427a130d9ffab21dc07bcd549fcf0966 +AP_JWT_SECRET=secret +``` + + + + After signing in, activate the license key by going to **Platform Admin -> Setup -> License Keys** + ![Activation License Key](/resources/screenshots/activation-license-key-settings.png) + + + \ No newline at end of file diff --git a/docs/handbook/engineering/playbooks/setup-incident-io.mdx b/docs/handbook/engineering/playbooks/setup-incident-io.mdx new file mode 100644 index 0000000..3ced699 --- /dev/null +++ b/docs/handbook/engineering/playbooks/setup-incident-io.mdx @@ -0,0 +1,30 @@ +--- +title: "Setup Incident.io" +icon: 'bell-ring' +--- + +Incident.io is our primary tool for managing and responding to urgent issues and service disruptions. +This guide explains how we use Incident.io to coordinate our on-call rotations and emergency response procedures. + +## Setup and Notifications + +### Personal Setup + +1. Download the Incident.io mobile app from your device's app store +2. Ask your team to add you to the Incident.io workspace +3. Configure your notification preferences: + - Phone calls for critical incidents + - Push notifications for high-priority issues + - Slack notifications for standard updates + +### On-Call Rotations + +Our team operates on a weekly rotation schedule through Incident.io, where every team member participates. When you're on-call: +- You'll receive priority notifications for all urgent issues +- Phone calls will be placed for critical service disruptions +- Rotations change every week, with handoffs occurring on Monday mornings +- Response is expected within 15 minutes for critical incidents + + + If you are unable to respond to an incident, please escalate to the engineering team. + diff --git a/docs/handbook/hiring/compensation.mdx b/docs/handbook/hiring/compensation.mdx new file mode 100644 index 0000000..d4bf5b2 --- /dev/null +++ b/docs/handbook/hiring/compensation.mdx @@ -0,0 +1,14 @@ +--- +title: "Our Compensation" +icon: 'money-bill-wave' +--- + +The packages include three factors for the salary: + +- **Role**: The specific position and responsibilities of the employee. +- **Location**: The geographical area where the employee is based. +- **Level**: The seniority and experience level of the employee. + +Salaries are fixed and based on levels and seniority, not negotiation. This ensures fair pay for everyone. + +Salaries are updated based on market trends and the company's performance. It's easier to justify raises when the business is great. \ No newline at end of file diff --git a/docs/handbook/hiring/hiring.mdx b/docs/handbook/hiring/hiring.mdx new file mode 100644 index 0000000..93082b7 --- /dev/null +++ b/docs/handbook/hiring/hiring.mdx @@ -0,0 +1,26 @@ +--- +title: "Our Hiring Process" +icon: 'user-plus' +--- + +Engineers are the majority of the Activepieces team, and we are always looking for highly talented product engineers. + + + + Here, you'll face a real challenge from Activepieces. We'll guide you through it to see how you solve problems. + + + We'll chat about your past experiences and how you design products. It's like having a friendly conversation where we reflect on what you've done before. + + + You'll do open source task for one day. This open source contribution task help us understand how well we work together. + + + +## Interviewing Tips + +Every interview should make us say **HELL YES**. If not, we'll kindly pass. + +**Avoid Bias:** Get opinions from others to make fair decisions. + +**Speak Up Early:** If you're unsure about something, ask or test it right away. diff --git a/docs/handbook/hiring/levels.mdx b/docs/handbook/hiring/levels.mdx new file mode 100644 index 0000000..1d75e2a --- /dev/null +++ b/docs/handbook/hiring/levels.mdx @@ -0,0 +1,43 @@ +--- +title: "Our Roles & Levels" +icon: 'layer-group' +--- + +**Product Engineers** are full stack engineers who handle both the engineering and product side, delivering features end-to-end. + +### Our Levels + +We break out seniority into three levels, **L1 to L3**. + +### L1 Product Engineers + +They tend to be early-career. + +- They get more management support than folks at other levels. +- They focus on continuously absorbing new information about our users and how to be effective at **Activepieces**. +- They aim to be increasingly autonomous as they gain more experience here. + +### L2 Product Engineers + +They are generally responsible for running a project start-to-finish. + +- They independently decide on the implementation details. +- They work with **Stakeholders** / **teammates** / **L3s** on the plan. +- They have personal responsibility for the **“how”** of what they’re working on, but share responsibility for the **“what”** and **“why”**. +- They make consistent progress on their work by continuously defining the scope, incorporating feedback, trying different approaches and solutions, and deciding what will deliver the most value for users. + +### L3 Product Engineers +Their scope is bigger than coding, they lead a product area, make key product decisions and guide the team with strong leadership skills. + +- **Planning**: They help **L2s** figure out what the next priority things to focus on and guide **L1s** in determining the right sequence of work to get a project done. +- **Day-to-Day Work**: They might be hands-on with the day-to-day work of the team, providing support and resources to their teammates as needed. +- **Customer Communication**: They handle direct communication with customers regarding planning and product direction, ensuring that customer needs and feedback are incorporated into the development process. + +### How to Level Up + +There is no formal process, but it happens at the end of **each year** and is based on two things: + +1. **Manager Review**: Managers look at how well the engineer has performed and grown over the year. +2. **Peer Review**: Colleagues give feedback on how well the engineer has worked with the team. + +This helps make sure promotions are fair and based on merit. \ No newline at end of file diff --git a/docs/handbook/hiring/team.mdx b/docs/handbook/hiring/team.mdx new file mode 100644 index 0000000..226c70b --- /dev/null +++ b/docs/handbook/hiring/team.mdx @@ -0,0 +1,28 @@ +--- +title: "Our Team Structure" +icon: 'users' +--- + +We are big believers in small teams with 10x engineers who would outperform other team types. + +## No product management by default + +Engineers decide what to build. If you need help, feel free to reach out to the team for other opinions or help. + +## No Process by default + +We trust the engineers' judgment to make the call whether this code is risky and requires external approval or if it's a fix that can be easily reversed or fixed with no big impact on the end user. + +## They Love Users + +When the engineer loves the users, that means they would ship fast, they wouldn't over-engineer because they understand the requirements very well, they usually have empathy which means they don't complicate everyone else. + +## Pragmatic & Speed + +Engineering planning sometimes seems sexy from a technical perspective, but being pragmatic means you would take decisions in a timely manner, taking them in baby steps and iterating faster rather than planning for the long run, and it's easy to reverse wrong decisions early on without investing too much time. + +## Starts With Hiring + +We hire very **slowly**. We are always looking for highly talented engineers. We love to hire people with a broader skill set and flexibility, low egos, and who are builders at heart. + +We found that working with strong engineers is one of the strongest reasons to retain employees, and this would allow everyone to be free and have less process. diff --git a/docs/handbook/overview.mdx b/docs/handbook/overview.mdx new file mode 100644 index 0000000..00d5607 --- /dev/null +++ b/docs/handbook/overview.mdx @@ -0,0 +1,8 @@ +--- +title: "Activepieces Handbook" +icon: 'book' +--- + +Welcome to the Activepieces Handbook! + +This guide serves as a complete resource for understanding our organization. Inside, you'll find detailed sections covering various aspects of our internal processes and policies. \ No newline at end of file diff --git a/docs/handbook/people/time.mdx b/docs/handbook/people/time.mdx new file mode 100644 index 0000000..9291b0a --- /dev/null +++ b/docs/handbook/people/time.mdx @@ -0,0 +1,16 @@ +--- +title: "Time Off & Working Hours" +--- + +We offer the team unlimited PTO **with the expectation that you take at least the minimum time off listed in your contract**, this is to ensure people are taking days off without feeling guilty. + + +## No Permission Required + +We don't require permission for you to take time off, but please make sure you communicate that with the team and ensure they can move forward without you in your absence. + +## Flexible Working + +We don't measure the amount of time you work, but rather the impact. If you have an appointment or need to do a quick household chore, please do so without permission, but please make sure it's added to the calendar. + +**NOTE:** When choosing your working hours, make sure they are **predictable** for your team and that you can overlap with them in case they want to reach out. diff --git a/docs/handbook/product/interface-design.mdx b/docs/handbook/product/interface-design.mdx new file mode 100644 index 0000000..2c1c426 --- /dev/null +++ b/docs/handbook/product/interface-design.mdx @@ -0,0 +1,30 @@ +--- +title: "Interface Design" +icon: "palette" +--- + +This page is a collection of resources for interface design. It's a work in progress and will be updated as we go. + +## Color Palette + +![Color Palette](/resources/color-palette.png) + +The palette includes: + +- Primary colors for main actions and branding +- Secondary colors for supporting elements +- Semantic colors for status and feedback (success, warning, destructive) + + +## Tech Stack + +Our frontend is built with: + +- **React** - Core UI framework +- **Shadcn UI** - Component library +- **Tailwind CSS** - Utility-first styling + + +## Learning Resources + +- [Interface Design (Chapters 46-53)](https://basecamp.com/gettingreal/09.1-interface-first) from Getting Real by Basecamp diff --git a/docs/handbook/teams/ai.mdx b/docs/handbook/teams/ai.mdx new file mode 100644 index 0000000..e12f1c5 --- /dev/null +++ b/docs/handbook/teams/ai.mdx @@ -0,0 +1,22 @@ +--- +title: "AI Agent" +icon: "robot" +--- + +### Mission Statement + +We use AI to help you build workflows quickly and easily, turning your ideas into working automations in minutes. + +### People + + + + + + + + + +### Roadmap + +https://linear.app/activepieces/project/copilot-1f9e2549f61c/issues \ No newline at end of file diff --git a/docs/handbook/teams/content.mdx b/docs/handbook/teams/content.mdx new file mode 100644 index 0000000..23c11e1 --- /dev/null +++ b/docs/handbook/teams/content.mdx @@ -0,0 +1,18 @@ +--- +title: "Marketing & Content" +icon: "pencil" +--- + +### Mission Statement + +We aim to share and teach Activepieces' vision of democratized automation, helping users discover and learn how to unlock the full potential of our platform while building a vibrant community of automation enthusiasts. + +### People + + + + + + + + diff --git a/docs/handbook/teams/developer-experience.mdx b/docs/handbook/teams/developer-experience.mdx new file mode 100644 index 0000000..081336a --- /dev/null +++ b/docs/handbook/teams/developer-experience.mdx @@ -0,0 +1,21 @@ +--- +title: "Developer Experience & Infrastructure" +icon: "code-branch" +--- + +### Mission Statement + +We build and maintain developer tools, infrastructure, and documentation to improve the productivity and satisfaction of developers working with our platform. We also ensure Activepieces is easy to self-host by providing clear documentation, deployment guides, and infrastructure tooling. + +### People + + + + + + + + +### Roadmap + +https://linear.app/activepieces/project/self-hosting-devxp-infrastructure-cc6611474f1f/overview \ No newline at end of file diff --git a/docs/handbook/teams/embed-sdk.mdx b/docs/handbook/teams/embed-sdk.mdx new file mode 100644 index 0000000..0628a35 --- /dev/null +++ b/docs/handbook/teams/embed-sdk.mdx @@ -0,0 +1,21 @@ +--- +title: "Embedding" +icon: "code" +--- + + +### Mission Statement + +We build a robust SDK that makes it simple for developers to embed Activepieces automation capabilities into any application. + +### People + + + + + + + +### Roadmap + +https://linear.app/activepieces/project/embedding-085e6ea3fef0/overview \ No newline at end of file diff --git a/docs/handbook/teams/flow-builder.mdx b/docs/handbook/teams/flow-builder.mdx new file mode 100644 index 0000000..03cf119 --- /dev/null +++ b/docs/handbook/teams/flow-builder.mdx @@ -0,0 +1,21 @@ +--- +title: "Flow Editor & Dashboard" +icon: "diagram-project" +--- + + +### Mission Statement + +We aim to build a simple yet powerful tool that helps people automate tasks without coding. Our goal is to make it easy for anyone to use. We build and maintain the flow editor that enables users to create and manage automated workflows through an intuitive interface. + + +### People + + + + + + +### Roadmap + +https://linear.app/activepieces/project/flow-editor-and-execution-bd53ec32d508/overview \ No newline at end of file diff --git a/docs/handbook/teams/human-in-loop.mdx b/docs/handbook/teams/human-in-loop.mdx new file mode 100644 index 0000000..7416346 --- /dev/null +++ b/docs/handbook/teams/human-in-loop.mdx @@ -0,0 +1,18 @@ +--- +title: "Human in the Loop" +icon: "user-check" +--- + +### Mission Statement + +We build and maintain features that enable human interaction within automated workflows, including forms, approvals, and chat interfaces. + +### People + + + + + +### Roadmap + +https://linear.app/activepieces/project/human-in-the-loop-8eb571776a92/overview \ No newline at end of file diff --git a/docs/handbook/teams/management-features.mdx b/docs/handbook/teams/management-features.mdx new file mode 100644 index 0000000..a533fc2 --- /dev/null +++ b/docs/handbook/teams/management-features.mdx @@ -0,0 +1,19 @@ +--- +title: "Dashboard & Platform Admin" +icon: "shield" +--- + +### Mission Statement + +We build and maintain the platform administration capabilities and dashboard features, ensuring secure and efficient management of users, organizations, and system resources. + +### People + + + + + + +### Roadmap + +https://linear.app/activepieces/project/management-features-0e61486373e7/overview \ No newline at end of file diff --git a/docs/handbook/teams/overview.mdx b/docs/handbook/teams/overview.mdx new file mode 100644 index 0000000..b84ce3d --- /dev/null +++ b/docs/handbook/teams/overview.mdx @@ -0,0 +1,55 @@ +--- +title: "Overview" +icon: "cube" +--- + + + + Leverage artificial intelligence capabilities across the platform + + + + Build tools and infrastructure to improve developer productivity and satisfaction + + + Integrate and embed platform functionality into your applications + + + Run and monitor automated workflows with high performance and reliability + + + Design and implement human review and approval processes + + + Build and maintain platform administration capabilities and dashboard features + + + Create and manage educational content, documentation, and marketing copy + + + Build and manage integration pieces to connect with external services + + + Grow revenue by selling Activepieces to businesses + + + Create and manage data tables + + + +### People + + + + + + + + + + + + + + + diff --git a/docs/handbook/teams/pieces.mdx b/docs/handbook/teams/pieces.mdx new file mode 100644 index 0000000..f4ebcc0 --- /dev/null +++ b/docs/handbook/teams/pieces.mdx @@ -0,0 +1,26 @@ +--- +title: "Pieces" +icon: "puzzle-piece" +--- + +### Mission Statement + +We build and maintain integration pieces that enable users to connect and automate across different services and platforms. + +### People + + + + + + +### Roadmap + +#### Third Party Pieces +https://linear.app/activepieces/project/third-party-pieces-38b9d73a164c/issues + +#### Core Pieces +https://linear.app/activepieces/project/core-pieces-3419406029ca/issues + +#### Universal AI Pieces +https://linear.app/activepieces/project/universal-ai-pieces-92ed6f9cd12b/issues \ No newline at end of file diff --git a/docs/handbook/teams/sales.mdx b/docs/handbook/teams/sales.mdx new file mode 100644 index 0000000..4e04ce8 --- /dev/null +++ b/docs/handbook/teams/sales.mdx @@ -0,0 +1,14 @@ +--- +title: "Sales" +icon: "handshake" +--- + +### Mission Statement + +We grow revenue by selling Activepieces to businesses. + +### People + + + + diff --git a/docs/handbook/teams/tables.mdx b/docs/handbook/teams/tables.mdx new file mode 100644 index 0000000..690163e --- /dev/null +++ b/docs/handbook/teams/tables.mdx @@ -0,0 +1,18 @@ +--- +title: "Tables" +icon: "table" +--- + +### Mission Statement + +We build powerful yet simple data table capabilities that allow users to store, manage and manipulate their data within their automation workflows. + +### People + + + + + +### Roadmap + +https://linear.app/activepieces/project/data-tables-files-81237f412ac5/issues \ No newline at end of file diff --git a/docs/install/architecture/engine.mdx b/docs/install/architecture/engine.mdx new file mode 100644 index 0000000..b9b1765 --- /dev/null +++ b/docs/install/architecture/engine.mdx @@ -0,0 +1,18 @@ +--- +title: "Engine" +icon: "brain" +--- + +The Engine file contains the following types of operations: + +- **Extract Piece Metadata**: Extracts metadata when installing new pieces. +- **Execute Step**: Executes a single test step. +- **Execute Flow**: Executes a flow. +- **Execute Property**: Executes dynamic dropdowns or dynamic properties. +- **Execute Trigger Hook**: Executes actions such as OnEnable, OnDisable, or extracting payloads. +- **Execute Auth Validation**: Validates the authentication of the connection. + +The engine takes the flow JSON with an engine token scoped to this project and implements the API provided for the piece framework, such as: +- Storage Service: A simple key/value persistent store for the piece framework. +- File Service: A helper to store files either locally or in a database, such as for testing steps. +- Fetch Metadata: Retrieves metadata of the current running project. \ No newline at end of file diff --git a/docs/install/architecture/overview.mdx b/docs/install/architecture/overview.mdx new file mode 100644 index 0000000..fb5614c --- /dev/null +++ b/docs/install/architecture/overview.mdx @@ -0,0 +1,67 @@ +--- +title: "Overview" +description: "" +icon: "cube" +--- + +This page focuses on describing the main components of Activepieces and focus mainly on workflow executions. + +## Components + +![Architecture](/resources/architecture.png) + +**Activepieces:** + +- **App**: The main application that organizes everything from APIs to scheduled jobs. +- **Worker**: Polls for new jobs and executes the flows with the engine, ensuring proper sandboxing, and sends results back to the app through the API. +- **Engine**: TypeScript code that parses flow JSON and executes it. It is compiled into a single JS file. +- **UI**: Frontend written in React. + +**Third Party**: +- **Postgres**: The main database for Activepieces. +- **Redis**: This is used to power the queue using [BullMQ](https://docs.bullmq.io/). + +## Reliability & Scalability + + +Postgres and Redis availability is outside the scope of this documentation, as many cloud providers already implement best practices to ensure their availability. + + +- **Webhooks**: + All webhooks are sent to the Activepieces app, which performs basic validation and adds them to the queue. In case of a spike, webhooks will be added to the queue. + +- **Polling Trigger**: + All recurring jobs are added to Redis. In case of a failure, the missed jobs will be executed again. + +- **Flow Execution**: + Workers poll jobs from the queue. In the event of a spike, the flow execution will still work but may be delayed depending on the size of the spike. + +To scale Activepieces, you typically need to increase the replicas of either workers, the app, or the Postgres database. A small Redis instance is sufficient as it can handle thousands of jobs per second and rarely acts as a bottleneck. + +## Repository Structure + + +The repository is structured as a monorepo using the NX build system, with TypeScript as the primary language. It is divided into several packages: + +``` +. +├── packages +│ ├── react-ui +│ ├── server +| |── api +| |── worker +| |── shared +| ├── ee +│ ├── engine +│ ├── pieces +│ ├── shared +``` + +- `react-ui`: This package contains the user interface, implemented using the React framework. +- `server-api`: This package contains the main application written in TypeScript with the Fastify framework. +- `server-worker`: This package contains the logic of accepting flow jobs and executing them using the engine. +- `server-shared`: this package contains the shared logic between worker and app. +- `engine`: This package contains the logic for flow execution within the sandbox. +- `pieces`: This package contains the implementation of triggers and actions for third-party apps. +- `shared`: This package contains shared data models and helper functions used by the other packages. +- `ee`: This package contains features that are only available in the paid edition. diff --git a/docs/install/architecture/performance.mdx b/docs/install/architecture/performance.mdx new file mode 100644 index 0000000..c185478 --- /dev/null +++ b/docs/install/architecture/performance.mdx @@ -0,0 +1,107 @@ +--- +title: "Performance & Benchmarking" +icon: "chart-line" +--- + +## Performance + +On average, Activepieces (self-hosted) can handle 95 flow executions per second on a single instance (including PostgreSQL and Redis) with under 300ms latency.\ +It can scale up much more with increasing instance resources and/or adding more instances.\ +\ +The result of **5000** requests with concurrency of **25** + +``` +This is ApacheBench, Version 2.3 <$Revision: 1913912 $> +Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ +Licensed to The Apache Software Foundation, http://www.apache.org/ + +Benchmarking localhost (be patient) +Completed 500 requests +Completed 1000 requests +Completed 1500 requests +Completed 2000 requests +Completed 2500 requests +Completed 3000 requests +Completed 3500 requests +Completed 4000 requests +Completed 4500 requests +Completed 5000 requests +Finished 5000 requests + + +Server Software: +Server Hostname: localhost +Server Port: 4200 + +Document Path: /api/v1/webhooks/GMtpNwDsy4mbJe3369yzy/sync +Document Length: 16 bytes + +Concurrency Level: 25 +Time taken for tests: 52.087 seconds +Complete requests: 5000 +Failed requests: 0 +Total transferred: 1375000 bytes +HTML transferred: 80000 bytes +Requests per second: 95.99 [#/sec] (mean) +Time per request: 260.436 [ms] (mean) +Time per request: 10.417 [ms] (mean, across all concurrent requests) +Transfer rate: 25.78 [Kbytes/sec] received + +Connection Times (ms) + min mean[+/-sd] median max +Connect: 0 0 0.0 0 1 +Processing: 32 260 23.8 254 756 +Waiting: 31 260 23.8 254 756 +Total: 32 260 23.8 254 756 + +Percentage of the requests served within a certain time (ms) + 50% 254 + 66% 261 + 75% 267 + 80% 272 + 90% 289 + 95% 306 + 98% 327 + 99% 337 + 100% 756 (longest request) +``` + +#### Benchmarking + +Here is how to reproduce the benchmark: + +1. Run Activepieces with PostgreSQL and Redis with the following environment variables: + +```env +AP_EXECUTION_MODE=SANDBOX_CODE_ONLY +AP_FLOW_WORKER_CONCURRENCY=25 +``` + +2. Create a flow with a Catch Webhook trigger and a webhook Return Response action. + + + ![Simple Webhook Flow](/resources/screenshots/simple-webhook-flow.png) +3. Get the webhook URL from the webhook trigger and append `/sync` to it. +4. Install a benchmark tool like [ab](https://httpd.apache.org/docs/2.4/programs/ab.html): + +```bash +sudo apt-get install apache2-utils +``` + +5. Run the benchmark: + +```bash +ab -c 25 -n 5000 http://localhost:4200/api/v1/webhooks/GMtpNwDsy4mbJe3369yzy/sync +``` + +6. Check the results: + +Instance specs used to get the above results: + +- 16GB RAM +- AMD Ryzen 7 8845HS (8 cores, 16 threads) +- Ubuntu 24.04 LTS + + + These benchmarks are based on running Activepieces in `SANDBOX_CODE_ONLY` mode. This does **not** represent the performance of Activepieces Cloud, which uses a different sandboxing mechanism to support multi-tenancy. For more information, see [Sandboxing](/install/architecture/workers#sandboxing). + \ No newline at end of file diff --git a/docs/install/architecture/stack.mdx b/docs/install/architecture/stack.mdx new file mode 100644 index 0000000..29fafd8 --- /dev/null +++ b/docs/install/architecture/stack.mdx @@ -0,0 +1,51 @@ +--- +title: "Stack & Tools" +icon: "hammer" +--- + +## Language + +Activepieces uses **Typescript** as its one and only language. +The reason behind unifying the language is the ability for it to break data models and features into packages, which can be shared across its components (worker / frontend / backend). + +This enables it to focus on learning fewer tooling options and perfect them across all its packages. + +## Frontend + +- Web framework/library: [React](https://reactjs.org/) +- Layout/components: [shadcn](https://shadcn.com/) / Tailwind + +## Backend + +- Framework: [Fastify](https://www.fastify.io/) +- Database: [PostgreSQL](https://www.postgresql.org/) +- Task Queuing: [Redis](https://redis.io/) +- Task Worker: [BullMQ](https://github.com/taskforcesh/bullmq) + +## Testing + +- Unit & Integration Tests: [Jest](https://jestjs.io/) +- E2E Test: [Playwright](https://playwright.dev/) + +## Additional Tools + +- Application monitoring: [Sentry](https://sentry.io/welcome/) +- CI/CD: [GitHub Actions](https://github.com/features/actions) / [Depot](https://depot.dev/) / [Kamal](https://kamal-deploy.org/) +- Containerization: [Docker](https://www.docker.com/) +- Linter: [ESLint](https://eslint.org/) +- Logging: [Loki](https://grafana.com/) +- Building: [NX Monorepo](https://nx.dev/) + +## Adding New Tool + +Adding a new tool isn't a simple choice. A simple choice is one that's easy to do or undo, or one that only affects your work and not others'. + +We avoid adding new stuff to increase the ease of setup, which increases adoption. Having more dependencies means more moving parts and support. + +If you're thinking about a new tool, ask yourself these: + +- Is this tool open source? How can we give it to customers who use their own servers? +- What does it fix, and why do we need it now? +- Can we use what we already have instead? + +These questions only apply to required services for everyone. If this tool speeds up your own work, we don't need to think so hard. diff --git a/docs/install/architecture/workers.mdx b/docs/install/architecture/workers.mdx new file mode 100644 index 0000000..ef49ea2 --- /dev/null +++ b/docs/install/architecture/workers.mdx @@ -0,0 +1,98 @@ +--- +title: "Workers & Sandboxing" +icon: "gears" +--- + +This component is responsible for polling jobs from the app, preparing the sandbox, and executing them with the engine. + +## Jobs + +There are three types of jobs: + +- **Recurring Jobs**: Polling/schedule triggers jobs for active flows. +- **Flow Jobs**: Flows that are currently being executed. +- **Webhook Jobs**: Webhooks that still need to be ingested, as third-party webhooks can map to multiple flows or need mapping. + + +This documentation will not discuss how the engine works other than stating that it takes the jobs and produces the output. Please refer to [engine](./engine) for more information. + + +## Sandboxing + +Sandbox in Activepieces means in which environment the engine will execute the flow. There are three types of sandboxes, each with different trade-offs: + + + + + + +### No Sandboxing & V8 Sandboxing + +The difference between the two modes is in the execution of code pieces. For V8 Sandboxing, we use [isolated-vm](https://www.npmjs.com/package/isolated-vm), which relies on V8 isolation to isolate code pieces. + +These are the steps that are used to execute the flow: + + + + If the code doesn't exist, it will be compiled using TypeScript Compiler (tsc) and the necessary npm packages will be prepared, if possible. + + + Pieces are npm packages, we perform a simple check. If they don't exist, we use `pnpm` to install the pieces. + + + There is a pool of worker threads kept warm and the engine stays running and listening. Each thread executes one engine operation and sends back the result upon completion. + + + + +#### Security: +In a self-hosted environment, all piece installations are done by the **platform admin**. It is assumed that the pieces are secure, as they have full access to the machine. + +Code pieces provided by the end user are isolated using V8, which restricts the user to browser JavaScript instead of Node.js with npm. + +#### Performance +The flow execution is fast as the javascript can be, although there is overhead in polling from queue and prepare the files first time the flow get executed. + +#### Benchmark + +TBD + +### Kernel Namespaces Sandboxing + +This consists of two steps: the first one is preparing the sandbox, and the other one is the execution part. + +#### Prepare the folder + +Each flow will have a folder with everything required to execute this flows, which means the **engine**, **code pieces** and **npms** + + + +If the code doesn't exist, it will be compiled using TypeScript Compiler (tsc) and the necessary npm packages will be prepared, if possible. + + + Pieces are npm packages, we perform simple check If they don't exist we use `pnpm` to install the pieces. + + + +#### Execute Flow using Sandbox + +In this mode, we use kernel namespaces to isolate everything (file system, memory, CPU). The folder prepared earlier will be bound as a **Read Only** Directory. + +Then we use the command line and to spin up the isolation with new node process, something like that. +```bash +./isolate node path/to/flow.js --- rest of args +``` + +#### Security + +The flow execution is isolated in their own namespaces, which means pieces are isolated in different process and namespaces, So the user can run bash scripts and use the file system safely as It's limited and will be removed after the execution, in this mode the user can use any **NPM package** in their code piece. + +#### Performance + +This mode is **Slow** and **CPU Intensive**. The reason behind this is the **cold boot** of Node.js, since each flow execution will require a new **Node.js** process. The Node.js process consumes a lot of resources and takes some time to compile the code and start executing. + + +#### Benchmark + + +TBD diff --git a/docs/install/configuration/environment-variables.mdx b/docs/install/configuration/environment-variables.mdx new file mode 100644 index 0000000..a759129 --- /dev/null +++ b/docs/install/configuration/environment-variables.mdx @@ -0,0 +1,130 @@ +--- +title: 'Environment Variables' +description: '' +icon: 'gear' +--- + +To configure activepieces, you will need to set some environment variables, There is file called `.env` at the root directory for our main repo. + + When you execute the [tools/deploy.sh](https://github.com/activepieces/activepieces/blob/main/tools/deploy.sh) script in the Docker installation tutorial, +it will produce these values. + +## Environment Variables + +| Variable | Description | Default Value | Example | +| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------- | +| `AP_CONFIG_PATH` | Optional parameter for specifying the path to store SQLite3 and local settings. | `~/.activepieces` | | +| `AP_CLOUD_AUTH_ENABLED` | Turn off the utilization of Activepieces oauth2 applications | `false` | | +| `AP_DB_TYPE` | The type of database to use. (POSTGRES / SQLITE3) | `SQLITE3` | | +| `AP_EXECUTION_MODE` | You can choose between 'SANDBOXED', 'UNSANDBOXED', 'SANDBOX_CODE_ONLY' as possible values. If you decide to change this, make sure to carefully read https://www.activepieces.com/docs/install/architecture/workers | `UNSANDBOXED` | | +| `AP_FLOW_WORKER_CONCURRENCY` | The number of different flows can be processed in same time | `10` | +| `AP_SCHEDULED_WORKER_CONCURRENCY` | The number of different scheduled flows can be processed in same time | `10` | +| `AP_ENCRYPTION_KEY` | ❗️ Encryption key used for connections is a 32-character (16 bytes) hexadecimal key. You can generate one using the following command: `openssl rand -hex 16`. | `None` | +| `AP_EXECUTION_DATA_RETENTION_DAYS` | The number of days to retain execution data, logs and events. | `30` | | +| `AP_FRONTEND_URL` | ❗️ Url that will be used to specify redirect url and webhook url. +| `AP_INTERNAL_URL` | (BETA) Used to specify the SSO authentication URL. | `None` | [https://demo.activepieces.com/api](https://demo.activepieces.com/api) | +| `AP_JWT_SECRET` | ❗️ Encryption key used for generating JWT tokens is a 32-character hexadecimal key. You can generate one using the following command: `openssl rand -hex 32`. | `None` | [https://demo.activepieces.com](https://demo.activepieces.com) | +| `AP_QUEUE_MODE` | The queue mode to use. (MEMORY / REDIS) | `MEMORY` | | +| `AP_QUEUE_UI_ENABLED` | Enable the queue UI (only works with redis) | `true` | | +| `AP_QUEUE_UI_USERNAME` | The username for the queue UI. This is required if `AP_QUEUE_UI_ENABLED` is set to `true`. | None | | +| `AP_QUEUE_UI_PASSWORD` | The password for the queue UI. This is required if `AP_QUEUE_UI_ENABLED` is set to `true`. | None | | +| `AP_REDIS_FAILED_JOB_RETENTION_DAYS` | The number of days to retain failed jobs in Redis. | `30` | | +| `AP_REDIS_FAILED_JOB_RETENTION_MAX_COUNT` | The maximum number of failed jobs to retain in Redis. | `2000` | | +| `AP_TRIGGER_DEFAULT_POLL_INTERVAL` | The default polling interval determines how frequently the system checks for new data updates for pieces with scheduled triggers, such as new Google Contacts. | `5` | | +| `AP_PIECES_SOURCE` | `AP_PIECES_SOURCE`: `FILE` for local development, `DB` for database. You can find more information about it in [Setting Piece Source](#setting-piece-source) section. | `CLOUD_AND_DB` | | +| `AP_PIECES_SYNC_MODE` | `AP_PIECES_SYNC_MODE`: `NONE` for no metadata syncing / 'OFFICIAL_AUTO' for automatic syncing for pieces metadata from cloud | `OFFICIAL_AUTO` | +| `AP_POSTGRES_DATABASE` | ❗️ The name of the PostgreSQL database | `None` | | +| `AP_POSTGRES_HOST` | ❗️ The hostname or IP address of the PostgreSQL server | `None` | | +| `AP_POSTGRES_PASSWORD` | ❗️ The password for the PostgreSQL, you can generate a 32-character hexadecimal key using the following command: `openssl rand -hex 32`. | `None` | | +| `AP_POSTGRES_PORT` | ❗️ The port number for the PostgreSQL server | `None` | | +| `AP_POSTGRES_USERNAME` | ❗️ The username for the PostgreSQL user | `None` | | +| `AP_POSTGRES_USE_SSL` | Use SSL to connect the postgres database | `false` | | +| `AP_POSTGRES_SSL_CA` | Use SSL Certificate to connect to the postgres database | +| `AP_POSTGRES_URL` | Alternatively, you can specify only the connection string (e.g postgres://user:password@host:5432/database) instead of providing the database, host, port, username, and password. | `None` | | +| `AP_REDIS_TYPE` | Type of Redis, Possible values are `DEFAULT` or `SENTINEL`. | `DEFAULT` | +| `AP_REDIS_URL` | If a Redis connection URL is specified, all other Redis properties will be ignored. | `None` | | +| `AP_REDIS_USER` | ❗️ Username to use when connect to redis | `None` | | +| `AP_REDIS_PASSWORD` | ❗️ Password to use when connect to redis | `None` | | +| `AP_REDIS_HOST` | ❗️ The hostname or IP address of the Redis server | `None` | | +| `AP_REDIS_PORT` | ❗️ The port number for the Redis server | `None` | | +| `AP_REDIS_DB` | The Redis database index to use | `0` | | +| `AP_REDIS_USE_SSL` | Connect to Redis with SSL | `false` | | +| `AP_REDIS_SSL_CA_FILE` | The path to the CA file for the Redis server. | `None` | | +| `AP_REDIS_SENTINEL_HOSTS` | If specified, this should be a comma-separated list of `host:port` pairs for Redis Sentinels. Make sure to set `AP_REDIS_CONNECTION_MODE` to `SENTINEL` | `None` | `sentinel-host-1:26379,sentinel-host-2:26379,sentinel-host-3:26379` | +| `AP_REDIS_SENTINEL_NAME` | The name of the master node monitored by the sentinels. | `None` | `sentinel-host-1` | +| `AP_REDIS_SENTINEL_ROLE` | The role to connect to, either `master` or `slave`. | `None` | `master` | +| `AP_TRIGGER_TIMEOUT_SECONDS` | Maximum allowed runtime for a trigger to perform polling in seconds | `60` | | +| `AP_FLOW_TIMEOUT_SECONDS` | Maximum allowed runtime for a flow to run in seconds | `600` | | +| `AP_SANDBOX_MEMORY_LIMIT` | The maximum amount of memory (in kilobytes) that a single sandboxed worker process can use. This helps prevent runaway memory usage in custom code or pieces. If not set, the default is 524288 KB (512 MB). | `524288` | `1048576` | +| `AP_SANDBOX_PROPAGATED_ENV_VARS` | Environment variables that will be propagated to the sandboxed code. If you are using it for pieces, we strongly suggests keeping everything in the authentication object to make sure it works across AP instances. | `None` | | +| `AP_TELEMETRY_ENABLED` | Collect telemetry information. | `true` | | +| `AP_TEMPLATES_SOURCE_URL` | This is the endpoint we query for templates, remove it and templates will be removed from UI | `https://cloud.activepieces.com/api/v1/flow-templates` | | +| `AP_WEBHOOK_TIMEOUT_SECONDS` | The default timeout for webhooks. The maximum allowed is 15 minutes. Please note that Cloudflare limits it to 30 seconds. If you are using a reverse proxy for SSL, make sure it's configured correctly. | `30` | | +| `AP_TRIGGER_FAILURE_THRESHOLD` | The maximum number of consecutive trigger failures is 576 by default, which is equivalent to approximately 2 days. | `30` | +| `AP_PROJECT_RATE_LIMITER_ENABLED` | Enforce rate limits and prevent excessive usage by a single project. | `true` | | +| `AP_MAX_CONCURRENT_JOBS_PER_PROJECT`| The maximum number of active runs a project can have. This is used to enforce rate limits and prevent excessive usage by a single project. | `100` | | +| `AP_S3_ACCESS_KEY_ID` | The access key ID for your S3-compatible storage service. Not required if `AP_S3_USE_IRSA` is `true`. | `None` | | +| `AP_S3_SECRET_ACCESS_KEY` | The secret access key for your S3-compatible storage service. Not required if `AP_S3_USE_IRSA` is `true`. | `None` | | +| `AP_S3_BUCKET` | The name of the S3 bucket to use for file storage. | `None` | | +| `AP_S3_ENDPOINT` | The endpoint URL for your S3-compatible storage service. Not required if `AWS_ENDPOINT_URL` is set. | `None` | `https://s3.amazonaws.com` | +| `AP_S3_REGION` | The region where your S3 bucket is located. Not required if `AWS_REGION` is set. | `None` | `us-east-1` | +| `AP_S3_USE_SIGNED_URLS` | It is used to route traffic to S3 directly. It should be enabled if the S3 bucket is public. | `None` | | +| `AP_S3_USE_IRSA` | Use IAM Role for Service Accounts (IRSA) to connect to S3. When `true`, `AP_S3_ACCESS_KEY_ID` and `AP_S3_ACCESS_KEY_ID` are not required. | `None` | `true` | +| `AP_MAX_FILE_SIZE_MB` | The maximum allowed file size in megabytes for uploads including logs of flow runs. If logs exceed this size, they will be truncated which may cause flow execution issues. | `10` | `10` | +| `AP_FILE_STORAGE_LOCATION` | The location to store files. Possible values are `DB` for storing files in the database or `S3` for storing files in an S3-compatible storage service. | `DB` | | +| `AP_PAUSED_FLOW_TIMEOUT_DAYS` | The maximum allowed pause duration in days for a paused flow, please note it can not exceed `AP_EXECUTION_DATA_RETENTION_DAYS` | `30` | +| `AP_MAX_RECORDS_PER_TABLE` | The maximum allowed number of records per table | `1500` | `1500` +| `AP_MAX_FIELDS_PER_TABLE` | The maximum allowed number of fields per table | `15` | `15` +| `AP_MAX_TABLES_PER_PROJECT` | The maximum allowed number of tables per project | `20` | `20` +| `AP_MAX_MCPS_PER_PROJECT` | The maximum allowed number of mcp per project | `20` | `20` +| `AP_ENABLE_FLOW_ON_PUBLISH` | Whether publishing a new flow version should automatically enable the flow | `true` | `false` +| `AP_ISSUE_ARCHIVE_DAYS` | Controls the automatic archival of issues in the system. Issues that have not been updated for this many days will be automatically moved to an archived state.| `14` | `1` + + + The frontend URL is essential for webhooks and app triggers to work. It must + be accessible to third parties to send data. + + + +### Setting Webhook (Frontend URL): + +The default URL is set to the machine's IP address. To ensure proper operation, ensure that this address is accessible or specify an `AP_FRONTEND_URL` environment variable. + +One possible solution for this is using a service like ngrok ([https://ngrok.com/](https://ngrok.com/)), which can be used to expose the frontend port (4200) to the internet. + +### Setting Piece Source + +These are the different options for the `AP_PIECES_SOURCE` environment variable: + +1. `FILE`: **Only for Local Development**, this option loads pieces directly from local files. For Production, please consider using other options, as this one only supports a single version per piece. + +2. `DB`: This option will only load pieces that are manually installed in the database from "My Pieces" or the Admin Console in the EE Edition. Pieces are loaded from npm, which provides multiple versions per piece, making it suitable for production. + +You can also set AP_PIECES_SYNC_MODE to `OFFICIAL_AUTO`, where it will update the metadata of pieces periodically. + +### Redis Configuration + +Set the `AP_REDIS_URL` environment variable to the connection URL of your Redis server. + +Please note that if a Redis connection URL is specified, all other **Redis properties** will be ignored. + + +If you don't have the Redis URL, you can use the following command to get it. You can use the following variables: + +- `REDIS_USER`: The username to use when connecting to Redis. +- `REDIS_PASSWORD`: The password to use when connecting to Redis. +- `REDIS_HOST`: The hostname or IP address of the Redis server. +- `REDIS_PORT`: The port number for the Redis server. +- `REDIS_DB`: The Redis database index to use. +- `REDIS_USE_SSL`: Connect to Redis with SSL. + + + If you are using **Redis Sentinel**, you can set the following environment variables: + - `AP_REDIS_TYPE`: Set this to `SENTINEL`. + - `AP_REDIS_SENTINEL_HOSTS`: A comma-separated list of `host:port` pairs for Redis Sentinels. When set, all other Redis properties will be ignored. + - `AP_REDIS_SENTINEL_NAME`: The name of the master node monitored by the sentinels. + - `AP_REDIS_SENTINEL_ROLE`: The role to connect to, either `master` or `slave`. + - `AP_REDIS_PASSWORD`: The password to use when connecting to Redis. + - `AP_REDIS_USE_SSL`: Connect to Redis with SSL. + - `AP_REDIS_SSL_CA_FILE`: The path to the CA file for the Redis server. + diff --git a/docs/install/configuration/hardware.mdx b/docs/install/configuration/hardware.mdx new file mode 100644 index 0000000..90d456e --- /dev/null +++ b/docs/install/configuration/hardware.mdx @@ -0,0 +1,52 @@ +--- +title: "Hardware Requirements" +icon: "server" +description: "Specifications for hosting Activepieces" +--- + +More information about architecture please visit our [architecture](../architecture/overview) page. + +### Technical Specifications + +Activepieces is designed to be memory-intensive rather than CPU-intensive. A modest instance will suffice for most scenarios, but requirements can vary based on specific use cases. + +| Component | Memory (RAM) | CPU Cores | Notes | +|---------------------|--------------|-----------|-----------------------------------------------------------------------| +| PostgreSQL | 1 GB | 1 | | +| Redis | 1 GB | 1 | | +| Activepieces | 8 GB | 2 | For high availability, consider deploying across multiple machines. Set `FLOW_WORKER_CONCURRENCY` to `25` for optimal performance. | + + +The above recommendations are designed to meet the needs of the majority of use cases. + + +## Scaling Factors + +### Redis + +Redis requires minimal scaling as it primarily stores jobs during processing. Activepieces leverages BullMQ, capable of handling a substantial number of jobs per second. + +### PostgreSQL + + +**Scaling Tip:** Since files are stored in the database, you can alleviate the load by configuring S3 storage for file management. + + +PostgreSQL is typically not the system's bottleneck. + +### Activepieces Container + + +**Scaling Tip:** The Activepieces container is stateless, allowing for seamless horizontal scaling. + + +- `FLOW_WORKER_CONCURRENCY` and `SCHEDULED_WORKER_CONCURRENCY` dictate the number of concurrent jobs processed for flows and scheduled flows, respectively. By default, these are set to 20 and 10. + +## Expected Performance + +Activepieces ensures no request is lost; all requests are queued. In the event of a spike, requests will be processed later, which is acceptable as most flows are asynchronous, with synchronous flows being prioritized. + +It's hard to predict exact performance because flows can be very different. But running a flow doesn't slow things down, as it runs as fast as regular JavaScript. +(Note: This applies to `SANDBOXED_CODE_ONLY` and `UNSANDBOXED` execution modes, which are recommended and used in self-hosted setups.) + +You can anticipate handling over **20 million executions** monthly with this setup. diff --git a/docs/install/configuration/overview.mdx b/docs/install/configuration/overview.mdx new file mode 100644 index 0000000..2dc1396 --- /dev/null +++ b/docs/install/configuration/overview.mdx @@ -0,0 +1,85 @@ +--- +title: "Deployment Checklist" +description: "Checklist to follow after deploying Activepieces" +icon: "list" +--- + + +This tutorial assumes you have already followed the quick start guide using one of the installation methods listed in [Install Overview](../overview). + + +In this section, we will go through the checklist after using one of the installation methods and ensure that your deployment is production-ready. + + + + +You should decide on the sandboxing mode for your deployment based on your use case and whether it is multi-tenant or not. Here is a simplified way to decide: + + +**Friendly Tip #1**: For multi-tenant setups, use V8/Code Sandboxing. + +It is secure and does not require privileged Docker access in Kubernetes. +Privileged Docker is usually not allowed to prevent root escalation threats. + + + +**Friendly Tip #2**: For single-tenant setups, use No Sandboxing. It is faster and does not require privileged Docker access. + + + + +More Information at [Sandboxing & Workers](../architecture/workers#sandboxing) + + + +For licensing inquiries regarding the self-hosted enterprise edition, please reach out to `sales@activepieces.com`, as the code and Docker image are not covered by the MIT license. + + +You can request a trial key from within the app or in the cloud by filling out the form. Alternatively, you can contact sales at [https://www.activepieces.com/sales](https://www.activepieces.com/sales).

Please know that when your trial runs out, all enterprise [features](/about/editions#feature-comparison) will be shut down meaning any user other than the platform admin will be deactivated, and your private pieces will be deleted, which could result in flows using them to fail.
+ + +Enterprise Edition only works on Fresh Installation as the database migration scripts are different from the community edition. + + + +Enterprise edition must use `PostgreSQL` as the database backend and `Redis` as the Queue System. + + +## Installation + +1. Set the `AP_EDITION` environment variable to `ee`. +2. Set the `AP_EXECUTION_MODE` to anything other than `UNSANDBOXED`, check the above section. +3. Once your instance is up, activate the license key by going to **Platform Admin -> Setup -> License Keys**. + +![Activation License Key](/resources/screenshots/activation-license-key-settings.png) + +
+ +Setting up HTTPS is highly recommended because many services require webhook URLs to be secure (HTTPS). This helps prevent potential errors. + +To set up SSL, you can use any reverse proxy. For a step-by-step guide, check out our example using [Nginx](./setup-ssl). + + +Run logs and files are stored in the database by default, but you can switch to S3 later without any migration; for most cases, the database is enough. + +It's recommended to start with the database and switch to S3 if needed. After switching, expired files in the database will be deleted, and everything will be stored in S3. No manual migration is needed. + +Configure the following environment variables: + +- `AP_S3_ACCESS_KEY_ID` +- `AP_S3_SECRET_ACCESS_KEY` +- `AP_S3_ENDPOINT` +- `AP_S3_BUCKET` +- `AP_S3_REGION` +- `AP_MAX_FILE_SIZE_MB` +- `AP_FILE_STORAGE_LOCATION` (set to `S3`) +- `AP_S3_USE_SIGNED_URLS` + +**Friendly Tip #1**: If the S3 bucket supports signed URLs but needs to be accessible over a public network, you can set `AP_S3_USE_SIGNED_URLS` to `true` to route traffic directly to S3 and reduce heavy traffic on your API server. + + + + + If you encounter any issues, check out our [Troubleshooting](./troubleshooting) guide. + +
diff --git a/docs/install/configuration/separate-workers.mdx b/docs/install/configuration/separate-workers.mdx new file mode 100644 index 0000000..c0b1728 --- /dev/null +++ b/docs/install/configuration/separate-workers.mdx @@ -0,0 +1,47 @@ +--- +title: 'Separate Workers from App' +description: '' +icon: 'robot' +--- + +Benefits of separating workers from the main application (APP): + +- **Availability**: The application remains lightweight, allowing workers to be scaled independently. +- **Security**: Workers lack direct access to Redis and the database, minimizing impact in case of a security breach. + + + + + To create a worker token, use the local CLI command to generate the JWT and sign it with your `AP_JWT_SECRET` used for the app server. Follow these steps: + 1. Open your terminal and navigate to the root of the repository. + 2. Run the command: `npm run workers token`. + 3. When prompted, enter the JWT secret (this should be the same as the `AP_JWT_SECRET` used for the app server). + 4. The generated token will be displayed in your terminal, copy it and use it in the next step. + ![Workers Token](../../resources/worker-token.png) + + + Define the following environment variables in the `.env` file on the worker machine: + - Set `AP_CONTAINER_TYPE` to `WORKER` + - Specify `AP_FRONTEND_URL` + - Provide `AP_WORKER_TOKEN` + + + Configure a persistent volume for the worker to cache flows and pieces. This is important as first uncached execution of pieces and flows are very slow. Having a persistent volume significantly improves execution speed. + + Add the following volume mapping to your docker configuration: + ```yaml + volumes: + - :/usr/src/app/cache + ``` + + + Launch the worker machine and supply it with the generated token. + + + Verify that the workers are visible in the Platform Admin Console under Infra -> Workers. + ![Workers Infrastructure](../../resources/workers.png) + + + On the APP machine, set `AP_CONTAINER_TYPE` to `APP`. + + diff --git a/docs/install/configuration/setup-app-webhooks.mdx b/docs/install/configuration/setup-app-webhooks.mdx new file mode 100644 index 0000000..5993271 --- /dev/null +++ b/docs/install/configuration/setup-app-webhooks.mdx @@ -0,0 +1,33 @@ +--- +title: "Setup App Webhooks" +description: "" +icon: 'webhook' +--- + +Certain apps like Slack and Square only support one webhook per OAuth2 app. This means that manual configuration is required in their developer portal, and it cannot be automated. + +## Slack + +**Configure Webhook Secret** + +1. Visit the "Basic Information" section of your Slack OAuth settings. +2. Copy the "Signing Secret" and save it. +3. Set the following environment variable in your activepieces environment: + ``` + AP_APP_WEBHOOK_SECRETS={"@activepieces/piece-slack": {"webhookSecret": "SIGNING_SECRET"}} + ``` +4. Restart your application instance. + + +**Configure Webhook URL** + +1. Go to the "Event Subscription" settings in the Slack OAuth2 developer platform. +2. The URL format should be: `https://YOUR_AP_INSTANCE/api/v1/app-events/slack`. +3. When connecting to Slack, use your OAuth2 credentials or update the OAuth2 app details from the admin console (in platform plans). +4. Add the following events to the app: + - `message.channels` + - `reaction_added` + - `message.im` + - `message.groups` + - `message.mpim` + - `app_mention` diff --git a/docs/install/configuration/setup-ssl.mdx b/docs/install/configuration/setup-ssl.mdx new file mode 100644 index 0000000..a063067 --- /dev/null +++ b/docs/install/configuration/setup-ssl.mdx @@ -0,0 +1,73 @@ +--- +title: "Setup HTTPS" +description: "" +icon: "shield" +--- + +To enable SSL, you can use a reverse proxy. In this case, we will use Nginx as the reverse proxy. + +## Install Nginx + +```bash +sudo apt-get install nginx +``` + + +## Create Certificate + +To proceed with this documentation, it is assumed that you already have a certificate for your domain. + + +You have the option to use Cloudflare or generate a certificate using Let's Encrypt or Certbot. + + + +Add the certificate to the following paths: `/etc/key.pem` and `/etc/cert.pem` + + +## Setup Nginx + +```bash +sudo nano /etc/nginx/sites-available/default +``` + + +```bash +server { + listen 80; + listen [::]:80; + + server_name example.com www.example.com; + + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + + server_name example.com www.example.com; + + ssl_certificate /etc/cert.pem; + ssl_certificate_key /etc/key.pem; + + location / { + proxy_pass http://localhost:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } +} +``` + +## Restart Nginx + +```bash +sudo systemctl restart nginx +``` + +## Test + +Visit your domain and you should see your application running with SSL. \ No newline at end of file diff --git a/docs/install/configuration/troubleshooting.mdx b/docs/install/configuration/troubleshooting.mdx new file mode 100644 index 0000000..ef9a50b --- /dev/null +++ b/docs/install/configuration/troubleshooting.mdx @@ -0,0 +1,89 @@ +--- +title: "Troubleshooting" +description: "" +icon: "wrench" +--- +### Websocket Connection Issues + +If you're experiencing issues with websocket connections, it's likely due to incorrect proxy configuration. Common symptoms include: + +- Test Flow button not working +- Test step in flows not working +- Copilot features not working +- Real-time updates not showing + +To resolve these issues: + +1. Ensure your reverse proxy is properly configured for websocket connections +2. Check our [Setup HTTPS](./setup-ssl) guide for correct configuration examples +3. Some browser block http websocket connections, please setup ssl to resolve this issue. + +### Runs with Internal Errors or Scheduling Issues + +If you're experiencing issues with flow runs showing internal errors or scheduling problems: + +[BullBoard dashboard](/handbook/engineering/playbooks/bullboard) + +### Truncated logs + +If you see `(truncated)` in the flow run logs in your flow runs, it means that the logs have exceeded the maximum allowed file size. You can increase the `AP_MAX_FILE_SIZE_MB` environment variable to a higher value to resolve this issue. + +### Reset Password + +If you forgot your password on self hosted instance, you can reset it using the following steps: + +**Postgres** + +1. **Locate PostgreSQL Docker Container**: + - Use a command like `docker ps` to find the PostgreSQL container. + +2. **Access the Container**: + - Use SSH to access the PostgreSQL Docker container. + ```bash + docker exec -it POSTGRES_CONTAINER_ID /bin/bash + ``` + +3. **Open the PostgreSQL Console**: + - Inside the container, open the PostgreSQL console with the `psql` command. + ```bash + psql -U postgres + ``` + +4. **Connect to the ActivePieces Database**: + - Connect to the ActivePieces database. + ```sql + \c activepieces + ``` + +5. **Create a Secure Password**: + - Use a tool like [bcrypt-generator.com](https://bcrypt-generator.com/) to generate a new secure password, number of rounds is 10. + +6. **Update Your Password**: + - Run the following SQL query within the PostgreSQL console, replacing `HASH_PASSWORD` with your new password and `YOUR_EMAIL_ADDRESS` with your email. + ```sql + UPDATE public.user_identity SET password='HASH_PASSWORD' WHERE email='YOUR_EMAIL_ADDRESS'; + ``` + +**SQLite3** + +1. **Open the SQLite3 Shell**: + - Access the SQLite3 database by opening the SQLite3 shell. Replace "database.db" with the actual name of your SQLite3 database file if it's different. + ```bash + sqlite3 ~/.activepieces/database.sqlite + ``` + +2. **Create a Secure Password**: + - Use a tool like [bcrypt-generator.com](https://bcrypt-generator.com/) to generate a new secure password, number of rounds is 10. + +3. **Reset Your Password**: + - Once inside the SQLite3 shell, you can update your password with an SQL query. Replace `HASH_PASSWORD` with your new password and `YOUR_EMAIL_ADDRESS` with your email. + ```sql + UPDATE user_identity SET password = 'HASH_PASSWORD' WHERE email = 'YOUR_EMAIL_ADDRESS'; + ``` + +4. **Exit the SQLite3 Shell**: + - After making the changes, exit the SQLite3 shell by typing: + ```bash + .exit + ``` + diff --git a/docs/install/options/aws.mdx b/docs/install/options/aws.mdx new file mode 100644 index 0000000..5494563 --- /dev/null +++ b/docs/install/options/aws.mdx @@ -0,0 +1,139 @@ +--- +title: "AWS (Pulumi)" +description: "Get Activepieces up & running on AWS with Pulumi for IaC" +--- + +# Infrastructure-as-Code (IaC) with Pulumi + +Pulumi is an IaC solution akin to Terraform or CloudFormation that lets you deploy & manage your infrastructure using popular programming languages e.g. Typescipt (which we'll use), C#, Go etc. + +## Deploy from Pulumi Cloud + +If you're already familiar with Pulumi Cloud and have [integrated their services with your AWS account](https://www.pulumi.com/docs/pulumi-cloud/deployments/oidc/aws/#configuring-openid-connect-for-aws), you can use the button below to deploy Activepieces in a few clicks. +The template will deploy the latest Activepieces image that's available on [Docker Hub](https://hub.docker.com/r/activepieces/activepieces). + +[![Deploy with Pulumi](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new?template=https://github.com/activepieces/activepieces/tree/main/deploy/pulumi) + +## Deploy from a local environment + +Or, if you're currently using an S3 bucket to maintain your Pulumi state, you can scaffold and deploy Activepieces direct from Docker Hub using the template below in just few commands: + +```bash +$ mkdir deploy-activepieces && cd deploy-activepieces +$ pulumi new https://github.com/activepieces/activepieces/tree/main/deploy/pulumi +$ pulumi up +``` + +## What's Deployed? + +The template is setup to be somewhat flexible, supporting what could be a development or more production-ready configuration. +The configuration options that are presented during stack configuration will allow you to optionally add any or all of: + +* PostgreSQL RDS instance. Opting out of this will use a local SQLite3 Db. +* Single node Redis 7 cluster. Opting out of this will mean using an in-memory cache. +* Fully qualified domain name with SSL. Note that the hosted zone must already be configured in Route 53. +Opting out of this will mean relying on using the application load balancer's url over standard HTTP to access your Activepieces deployment. + +For a full list of all the currently available configuration options, take a look at the [Activepieces Pulumi template file on GitHub](https://github.com/activepieces/activepieces/tree/main/deploy/pulumi/Pulumi.yaml). + +## Setting up Pulumi for the first time + +If you're new to Pulumi then read on to get your local dev environment setup to be able to deploy Activepieces. + +### Prerequisites + +1. Make sure you have [Node](https://nodejs.org/en/download) and [Pulumi](https://www.pulumi.com/docs/install/) installed. +2. [Install and configure the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). +3. [Install and configure Pulumi](https://www.pulumi.com/docs/clouds/aws/get-started/begin/). +4. Create an S3 bucket which we'll use to maintain the state of all the various service we'll provision for our Activepieces deployment: + +```bash +aws s3api create-bucket --bucket pulumi-state --region us-east-1 +``` + + + Note: [Pulumi supports to two different state management approaches](https://www.pulumi.com/docs/concepts/state/#deciding-on-a-state-backend). + If you'd rather use Pulumi Cloud instead of S3 then feel free to skip this step and setup an account with Pulumi. + + +5. Login to the Pulumi backend: + +```bash +pulumi login s3://pulumi-state?region=us-east-1 +``` +6. Next we're going to use the Activepieces Pulumi deploy template to create a new project, a stack in that project and then kick off the deploy: + +```bash +$ mkdir deploy-activepieces && cd deploy-activepieces +$ pulumi new https://github.com/activepieces/activepieces/tree/main/deploy/pulumi +``` + +This step will prompt you to create you stack and to populate a series of config options, such as whether or not to provision a PostgreSQL RDS instance or use SQLite3. + + + Note: When choosing a stack name, use something descriptive like `activepieces-dev`, `ap-prod` etc. + This solution uses the stack name as a prefix for every AWS service created + e.g. your VPC will be named `-vpc`. + + +7. Nothing left to do now but kick off the deploy: + +```bash +pulumi up +``` + +8. Now choose `yes` when prompted. Once the deployment has finished, you should see a bunch of Pulumi output variables that look like the following: +```json + _: { + activePiecesUrl: "http://.us-east-1.elb.amazonaws.com" + activepiecesEnv: [ + . . . . + ] + } +``` + +The config value of interest here is the `activePiecesUrl` as that is the URL for our Activepieces deployment. +If you chose to add a fully qualified domain during your stack configuration, that will be displayed here. +Otherwise you'll see the URL to the application load balancer. And that's it. + +Congratulations! You have successfully deployed Activepieces to AWS. + +## Deploy a locally built Activepieces Docker image + +To deploy a locally built image instead of using the official Docker Hub image, read on. + +1. Clone the Activepieces repo locally: + +```bash +git clone https://github.com/activepieces/activepieces +``` +2. Move into the `deploy/pulumi` folder & install the necessary npm packages: + +```bash +cd deploy/pulumi && npm i +``` +3. This folder already has two Pulumi stack configuration files reday to go: `Pulumi.activepieces-dev.yaml` and `Pulumi.activepieces-prod.yaml`. +These files already contain all the configurations we need to create our environments. Feel free to have a look & edit the values as you see fit. +Lets continue by creating a development stack that uses the existing `Pulumi.activepieces-dev.yaml` file & kick off the deploy. + +```bash +pulumi stack init activepieces-dev && pulumi up +``` + + + Note: Using `activepieces-dev` or `activepieces-prod` for the `pulumi stack init` command is required here as the stack name needs to match the existing stack file name in the folder. + + +4. You should now see a preview in the terminal of all the services that will be provisioned, before you continue. +Once you choose `yes`, a new image will be built based on the `Dockerfile` in the root of the solution (make sure Docker Desktop is running) and then pushed up to a new ECR, along with provisioning all the other AWS services for the stack. + +Congratulations! You have successfully deployed Activepieces into AWS using a locally built Docker image. + +## Customising the deploy + +All of the current configuration options, as well as the low-level details associated with the provisioned services are fully customisable, as you would expect from any IaC. +For example, if you'd like to have three availability zones instead of two for the VPC, use an older version of Redis or add some additional security group rules for PostgreSQL, you can update all of these and more in the `index.ts` file inside the `deploy` folder. + +Or maybe you'd still like to deploy the official Activepieces Docker image instead of a local build, but would like to change some of the services. Simply set the `deployLocalBuild` config option in the stack file to `false` and make whatever changes you'd like to the `index.ts` file. + +Checking out the [Pulumi docs](https://www.pulumi.com/docs/clouds/aws/) before doing so is highly encouraged. diff --git a/docs/install/options/docker-compose.mdx b/docs/install/options/docker-compose.mdx new file mode 100755 index 0000000..a927842 --- /dev/null +++ b/docs/install/options/docker-compose.mdx @@ -0,0 +1,122 @@ +--- +title: "Docker Compose" +description: "" +icon: "book" +--- + +To get up and running quickly with Activepieces, we will use the Activepieces Docker image. Follow these steps: + +## Prerequisites + +You need to have [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and [Docker](https://docs.docker.com/get-docker/) installed on your machine in order to set up Activepieces via Docker Compose. + +## Installing + +**1. Clone Activepieces repository.** + +Use the command line to clone Activepieces repository: + +```bash +git clone https://github.com/activepieces/activepieces.git +``` + +**2. Go to the repository folder.** + +```bash +cd activepieces +``` + +**3.Generate Environment variable** + +Run the following command from the command prompt / terminal + +```bash +sh tools/deploy.sh +``` + + +If none of the above methods work, you can rename the .env.example file in the root directory to .env and fill in the necessary information within the file. + + +**4. Run Activepieces.** + + +Please note that "docker-compose" (with a dash) is an outdated version of Docker Compose and it will not work properly. We strongly recommend downloading and installing version 2 from the [here](https://docs.docker.com/compose/install/) to use Docker Compose. + + +```bash +docker compose -p activepieces up +``` + +## 4. Configure Webhook URL (Important for Triggers, Optional If you have public IP) + +**Note:** By default, Activepieces will try to use your public IP for webhooks. If you are self-hosting on a personal machine, you must configure the frontend URL so that the webhook is accessible from the internet. + +**Optional:** The easiest way to expose your webhook URL on localhost is by using a service like ngrok. However, it is not suitable for production use. + +1. Install ngrok +2. Run the following command: +```bash +ngrok http 8080 +``` +3. Replace `AP_FRONTEND_URL` environment variable in `.env` with the ngrok url. + +![Ngrok](../../resources/screenshots/docker-ngrok.png) + + +When deploying for production, ensure that you update the database credentials and properly set the environment variables. + +Review the [configurations guide](/install/configuration/environment-variables) to make any necessary adjustments. + + +## Upgrading + +To upgrade to new versions, which are installed using docker compose, perform the following steps. First, open a terminal in the activepieces repository directory and run the following commands. + +### Automatic Pull + +**1. Run the update script** + +```bash +sh tools/update.sh +``` + +### Manually Pull + +**1. Pull the new docker compose file** +```bash +git pull +``` + +**2. Pull the new images** +```bash +docker compose pull +``` + +**3. Review changelog for breaking changes** + + +Please review breaking changes in the [changelog](../../about/breaking-changes). + + +**4. Run the updated docker images** +``` +docker compose up -d --remove-orphans +``` + +Congratulations! You have now successfully updated the version. + +## Deleting + +The following command is capable of deleting all Docker containers and associated data, and therefore should be used with caution: + +``` +sh tools/reset.sh +``` + + +Executing this command will result in the removal of all Docker containers and the data stored within them. It is important to be aware of the potentially hazardous nature of this command before proceeding. + + + + diff --git a/docs/install/options/docker.mdx b/docs/install/options/docker.mdx new file mode 100755 index 0000000..7327410 --- /dev/null +++ b/docs/install/options/docker.mdx @@ -0,0 +1,87 @@ +--- +title: "Docker" +description: "Single docker image deployment with SQLite3 and Memory Queue" +icon: "docker" +--- + + +Set up Activepieces using Docker Compose for easy deployment - Ideal for personal and testing with SQLite3 and in-memory queue. +For production (companies), use PostgreSQL and Redis, Refer to docker compose setup. + + +To get up and running quickly with Activepieces, we will use the Activepieces Docker image. Follow these steps: + +## Prerequisites + +You need to have [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and [Docker](https://docs.docker.com/get-docker/) installed on your machine in order to set up Activepieces via Docker Compose. + +## Install + +### Pull Image and Run Docker image + +Pull the Activepieces Docker image and run the container with the following command: + +```bash +docker run -d -p 8080:80 -v ~/.activepieces:/root/.activepieces -e AP_QUEUE_MODE=MEMORY -e AP_DB_TYPE=SQLITE3 -e AP_FRONTEND_URL="http://localhost:8080" activepieces/activepieces:latest +``` + +### Configure Webhook URL (Important for Triggers, Optional If you have public IP) + +**Note:** By default, Activepieces will try to use your public IP for webhooks. If you are self-hosting on a personal machine, you must configure the frontend URL so that the webhook is accessible from the internet. + +**Optional:** The easiest way to expose your webhook URL on localhost is by using a service like ngrok. However, it is not suitable for production use. + +1. Install ngrok +2. Run the following command: +```bash +ngrok http 8080 +``` +3. Replace `AP_FRONTEND_URL` environment variable in the command line above. + +![Ngrok](../../resources/screenshots/docker-ngrok.png) + + + +## Upgrade + +Please follow the steps below: + +### Step 1: Back Up Your Data (Recommended) + +Before proceeding with the upgrade, it is always a good practice to back up your Activepieces data to avoid any potential data loss during the update process. + +1. **Stop the Current Activepieces Container:** If your Activepieces container is running, stop it using the following command: + ```bash + docker stop activepieces_container_name + ``` + +2. **Backup Activepieces Data Directory:** By default, Activepieces data is stored in the `~/.activepieces` directory on your host machine. Create a backup of this directory to a safe location using the following command: + ```bash + cp -r ~/.activepieces ~/.activepieces_backup + ``` + +### Step 2: Update the Docker Image + +1. **Pull the Latest Activepieces Docker Image:** Run the following command to pull the latest Activepieces Docker image from Docker Hub: + ```bash + docker pull activepieces/activepieces:latest + ``` + +### Step 3: Remove the Existing Activepieces Container + +1. **Stop and Remove the Current Activepieces Container:** If your Activepieces container is running, stop and remove it using the following commands: + ```bash + docker stop activepieces_container_name + docker rm activepieces_container_name + ``` + +### Step 4: Run the Updated Activepieces Container + +Now, run the updated Activepieces container with the latest image using the same command you used during the initial setup. Be sure to replace `activepieces_container_name` with the desired name for your new container. + +```bash +docker run -d -p 8080:80 -v ~/.activepieces:/root/.activepieces -e AP_QUEUE_MODE=MEMORY -e AP_DB_TYPE=SQLITE3 -e AP_FRONTEND_URL="http://localhost:8080" --name activepieces_container_name activepieces/activepieces:latest +``` + + +Congratulations! You have successfully upgraded your Activepieces Docker deployment diff --git a/docs/install/options/easypanel.mdx b/docs/install/options/easypanel.mdx new file mode 100755 index 0000000..70248c2 --- /dev/null +++ b/docs/install/options/easypanel.mdx @@ -0,0 +1,15 @@ +--- +title: "Easypanel" +description: "Run Activepieces with Easypanel 1-Click Install" +--- + +Easypanel is a modern server control panel. If you [run Easypanel](https://easypanel.io/docs) on your server, you can deploy Activepieces with 1 click on it. + +![Deploy to Easypanel](https://easypanel.io/img/deploy-on-easypanel-40.svg) + +## Instructions + +1. Create a VM that runs Ubuntu on your cloud provider. +2. Install Easypanel using the instructions from the website. +3. Create a new project. +4. Install Activepieces using the dedicated template. diff --git a/docs/install/options/elestio.mdx b/docs/install/options/elestio.mdx new file mode 100644 index 0000000..21c2fe0 --- /dev/null +++ b/docs/install/options/elestio.mdx @@ -0,0 +1,8 @@ +--- +title: "Elestio" +description: "Run Activepieces with Elestio 1-Click Install" +--- + +You can deploy Activepieces on Elestio using one-click deployment. Elestio handles version updates, maintenance, security, backups, etc. So go ahead and click below to deploy and start using. + +[![Deploy on Elestio](https://elest.io/images/logos/deploy-to-elestio-btn.png)](https://elest.io/open-source/activepieces) diff --git a/docs/install/options/gcp.mdx b/docs/install/options/gcp.mdx new file mode 100644 index 0000000..98f36c2 --- /dev/null +++ b/docs/install/options/gcp.mdx @@ -0,0 +1,29 @@ +--- +title: "GCP" +description: "" +--- + +This documentation is to deploy activepieces on VM Instance or VM Instance Group, we should first create VM template + +## Create VM Template + +First choose machine type (e.g e2-medium) + +After configuring the VM Template, you can proceed to click on "Deploy Container" and specify the following container-specific settings: + +- Image: activepieces/activepieces +- Run as a privileged container: true +- Environment Variables: + - `AP_QUEUE_MODE`: MEMORY + - `AP_DB_TYPE`: SQLITE3 + - `AP_FRONTEND_URL`: http://localhost:80 + - `AP_EXECUTION_MODE`: SANDBOXED +- Firewall: Allow HTTP traffic (for testing purposes only) + +Once these details are entered, click on the "Deploy" button and patiently wait for the container deployment process to complete.\ + +After a successful deployment, you can access the ActivePieces application by visiting the external IP address of the VM on GCP. + +## Production Deployment + +Please visit [ActivePieces](/install/configuration/environment-variables) for more details on how to customize the application. \ No newline at end of file diff --git a/docs/install/overview.mdx b/docs/install/overview.mdx new file mode 100755 index 0000000..85199bb --- /dev/null +++ b/docs/install/overview.mdx @@ -0,0 +1,94 @@ +--- +title: "Overview" +icon: "hand-wave" +description: "Introduction to the different ways to install Activepieces" +--- + +Activepieces Community Edition can be deployed using **Docker**, **Docker Compose**, and **Kubernetes**. + + +Community Edition is **free** and **open source**. + +You can read the difference between the editions [here](../about/editions). + + +## Recommended Options + + + +Deploy Activepieces as a single Docker container using the SQLite database. + + + + Deploy Activepieces with **Redis** and **PostgreSQL** setup. + + + + +## Other Options + + + + + + + + + + + + + + + + + + } href="./options/easypanel"> + 1-Click Install with Easypanel template, maintained by the community. + + + + 1-Click Install on Elestio. + + + + Install on AWS with Pulumi. + + + + Install on GCP as a VM template. + + + + + + } href="https://www.pikapods.com/pods?run=activepieces"> + Instantly run on PikaPods from $2.9/month. + + + + Easily install on RepoCloud using this template, maintained by the community. + + + + + + + + } href="https://zeabur.com/templates/LNTQDF"> + 1-Click Install on Zeabur. + + + + +## Cloud Edition + + + + This is the fastest option. + + diff --git a/docs/mint.json b/docs/mint.json new file mode 100644 index 0000000..77b0a39 --- /dev/null +++ b/docs/mint.json @@ -0,0 +1,547 @@ +{ + "name": "Activepieces", + "logo": { + "light": "./resources/logo/light.svg", + "dark": "./resources/logo/dark.svg", + "href": "https://www.activepieces.com" + }, + "favicon": "/favicon.svg", + "colors": { + "primary": "#6e41e2", + "dark": "#6838e0", + "light": "#c5b3f3", + "background": { + "dark": "#121212" + } + }, + "openapi": [ + "/openapi.json" + ], + "metadata": { + "description": "Open source no-code business automation· Zapier open source alternative" + }, + "topbarCtaButton": { + "name": "Get Started", + "url": "https://www.activepieces.com/plans" + }, + "feedback": { + "suggestEdit": true, + "raiseIssue": true + }, + "topbarLinks": [ + { + "name": "GitHub", + "url": "https://github.com/activepieces/activepieces" + }, + { + "name": "Pieces", + "url": "https://www.activepieces.com/pieces" + } + ], + "tabs": [ + { + "name": "Develop Pieces", + "url": "developers" + }, + { + "name": "Embedding", + "url": "embedding" + }, + { + "name": "API Reference", + "url": "endpoints" + }, + { + "name": "Deploy", + "url": "install" + }, + { + "name": "Handbook", + "url": "handbook" + } + ], + "navigation": [ + { + "group": "Getting Started", + "pages": [ + "getting-started/introduction", + "getting-started/principles" + ] + }, + { + "group": "Get Started", + "pages": [ + "endpoints/overview" + ] + }, + { + "group": "Handbook", + "pages": [ + "handbook/overview" + ] + }, + { + "group": "Teams", + "icon": "users", + "pages": [ + "handbook/teams/overview", + "handbook/teams/ai", + "handbook/teams/content", + "handbook/teams/developer-experience", + "handbook/teams/embed-sdk", + "handbook/teams/flow-builder", + "handbook/teams/management-features", + "handbook/teams/human-in-loop", + "handbook/teams/pieces", + "handbook/teams/sales", + "handbook/teams/tables" + ] + }, + { + "group": "Hiring", + "icon": "user", + "pages": [ + "handbook/hiring/hiring", + "handbook/hiring/levels", + "handbook/hiring/team", + "handbook/hiring/compensation" + ] + }, + { + "group": "Customer Support", + "icon": "hero", + "pages": [ + "handbook/customer-support/overview", + "handbook/customer-support/tone", + "handbook/customer-support/pylon", + "handbook/customer-support/handle-requests", + "handbook/customer-support/trial" + ] + }, + { + "group": "Engineering Onboarding", + "icon": "code", + "pages": [ + "handbook/engineering/overview", + "handbook/engineering/onboarding/onboarding-check-list", + "handbook/engineering/onboarding/how-we-work", + "handbook/engineering/onboarding/on-call", + "handbook/engineering/onboarding/downtime-incident" + ] + }, + { + "group": "Engineering Playbooks", + "icon": "code", + "pages": [ + "handbook/engineering/playbooks/run-ee", + "handbook/engineering/playbooks/setup-incident-io", + "handbook/engineering/playbooks/releases", + "handbook/engineering/playbooks/infrastructure", + "handbook/engineering/playbooks/bullboard", + "handbook/engineering/playbooks/database-migration", + "handbook/engineering/playbooks/product-announcement", + "handbook/engineering/playbooks/frontend-best-practices" + ] + }, + { + "group": "Product", + "icon": "tool", + "pages": [ + "handbook/product/interface-design" + ] + }, + { + "group": "Endpoints", + "pages": [ + { + "group": "Projects", + "icon": "building", + "pages": [ + "endpoints/projects/schema", + "endpoints/projects/create", + "endpoints/projects/update", + "endpoints/projects/list" + ] + }, + { + "group": "Users", + "icon": "user", + "pages": [ + "endpoints/users/schema", + "endpoints/users/list" + ] + }, + { + "group": "User Invitations", + "icon": "paper-plane", + "pages": [ + "endpoints/user-invitations/schema", + "endpoints/user-invitations/upsert", + "endpoints/user-invitations/list", + "endpoints/user-invitations/delete" + ] + }, + { + "group": "Project Members", + "icon": "user", + "pages": [ + "endpoints/project-members/schema", + "endpoints/project-members/list", + "endpoints/project-members/delete" + ] + }, + { + "group": "Connections", + "icon": "link", + "pages": [ + "endpoints/connections/schema", + "endpoints/connections/upsert", + "endpoints/connections/list", + "endpoints/connections/delete" + ] + }, + { + "group": "Flows", + "icon": "bolt", + "pages": [ + "endpoints/flows/schema", + "endpoints/flows/create", + "endpoints/flows/update", + "endpoints/flows/get", + "endpoints/flows/list", + "endpoints/flows/delete" + ] + }, + { + "group": "Flow Runs", + "icon": "play", + "pages": [ + "endpoints/flow-runs/schema", + "endpoints/flow-runs/get", + "endpoints/flow-runs/list" + ] + }, + { + "group": "Sample Data", + "icon": "database", + "pages": [ + "endpoints/sample-data/get" + ] + }, + { + "group": "Pieces", + "icon": "plug", + "pages": [ + "endpoints/pieces/schema", + "endpoints/pieces/install" + ] + }, + { + "group": "Project Releases", + "icon": "cube", + "pages": [ + "endpoints/project-releases/schema", + "endpoints/project-releases/create" + ] + }, + { + "group": "Global Connections", + "icon": "globe", + "pages": [ + "endpoints/global-connections/schema", + "endpoints/global-connections/upsert", + "endpoints/global-connections/update", + "endpoints/global-connections/list", + "endpoints/global-connections/delete" + ] + }, + { + "group": "Git Sync", + "icon": "git", + "pages": [ + "endpoints/git-repos/schema", + "endpoints/git-repos/configure" + ] + }, + { + "group": "Folders", + "icon": "folder", + "pages": [ + "endpoints/folders/schema", + "endpoints/folders/create", + "endpoints/folders/update", + "endpoints/folders/get", + "endpoints/folders/list", + "endpoints/folders/delete" + ] + }, + { + "group": "Flow Templates", + "icon": "copy", + "pages": [ + "endpoints/flow-templates/schema", + "endpoints/flow-templates/create", + "endpoints/flow-templates/delete", + "endpoints/flow-templates/get", + "endpoints/flow-templates/list" + ] + }, + { + "group": "MCP Servers", + "icon": "server", + "pages": [ + "endpoints/mcp-servers/schema", + "endpoints/mcp-servers/list", + "endpoints/mcp-servers/rotate", + "endpoints/mcp-servers/update" + ] + } + ] + }, + { + "group": "AI", + "pages": [ + "ai/mcp" + ] + }, + { + "group": "Flows", + "pages": [ + "flows/building-flows", + "flows/passing-data", + "flows/publishing-flows", + "flows/debugging-runs", + "flows/versioning", + "flows/known-limits" + ] + }, + { + "group": "Get Started", + "pages": [ + "install/overview", + "install/options/docker", + "install/options/docker-compose", + { + "group": "Others Options", + "pages": [ + "install/options/easypanel", + "install/options/aws", + "install/options/gcp", + "install/options/elestio" + ] + } + ] + }, + { + "group": "Configuration", + "pages": [ + "install/configuration/overview", + "install/configuration/environment-variables", + "install/configuration/separate-workers", + "install/configuration/setup-app-webhooks", + "install/configuration/hardware", + "install/configuration/setup-ssl", + "install/configuration/troubleshooting" + ] + }, + { + "group": "Architecture", + "pages": [ + "install/architecture/overview", + "install/architecture/workers", + "install/architecture/engine", + "install/architecture/stack", + "install/architecture/performance" + ] + }, + { + "group": "Build a Piece", + "pages": [ + "developers/building-pieces/overview", + "developers/building-pieces/start-building", + "developers/building-pieces/setup-fork", + { + "group": "Development Environment", + "icon": "circle-2", + "pages": [ + "developers/development-setup/getting-started", + "developers/development-setup/local", + "developers/development-setup/dev-container", + "developers/development-setup/codespaces" + ] + }, + "developers/building-pieces/piece-definition", + "developers/building-pieces/piece-authentication", + "developers/building-pieces/create-action", + "developers/building-pieces/create-trigger", + { + "group": "Sharing Pieces", + "icon": "circle-7", + "pages": [ + "developers/sharing-pieces/overview", + "developers/sharing-pieces/contribute", + "developers/sharing-pieces/community", + "developers/sharing-pieces/private" + ] + } + ] + }, + { + "group": "Piece Reference", + "pages": [ + "developers/piece-reference/authentication", + { + "group": "Triggers", + "icon": "bolt", + "pages": [ + "developers/piece-reference/triggers/overview", + "developers/piece-reference/triggers/polling-trigger", + "developers/piece-reference/triggers/webhook-trigger" + ] + }, + "developers/piece-reference/properties", + "developers/piece-reference/properties-validation", + "developers/piece-reference/flow-control", + "developers/piece-reference/persistent-storage", + "developers/piece-reference/files", + "developers/piece-reference/external-libraries", + "developers/piece-reference/piece-versioning", + "developers/piece-reference/examples", + "developers/piece-reference/custom-api-calls", + "developers/piece-reference/i18n", + "developers/piece-reference/ai-providers" + ] + }, + { + "group": "Misc", + "pages": [ + "developers/misc/build-piece", + "developers/misc/publish-piece", + "developers/misc/pieces-ci-cd", + "developers/misc/create-new-ai-provider", + "developers/misc/private-fork" + ] + }, + { + "group": "Essentials", + "pages": [ + "embedding/overview", + "embedding/provision-users", + "embedding/embed-builder" + ] + }, + + { + "group": "Misc", + "pages": [ + "embedding/customize-pieces", + "embedding/embed-connections", + "embedding/navigation", + "embedding/predefined-connection", + "embedding/sdk-changelog", + "embedding/sdk-server-requests" + ] + }, + { + "group": "Platform Admin", + "pages": [ + "admin-console/overview", + "admin-console/appearance", + "admin-console/manage-projects", + "admin-console/custom-domain", + "admin-console/manage-templates", + "admin-console/manage-pieces", + "admin-console/manage-oauth2", + "admin-console/customize-emails", + "admin-console/manage-ai-providers" + ] + }, + { + "group": "Operations", + "pages": [ + "operations/git-sync", + { + "group": "Audit Logs", + "icon": "book", + "pages": [ + "operations/audit-logs/overview", + "operations/audit-logs/flow-created", + "operations/audit-logs/flow-updated", + "operations/audit-logs/flow-deleted", + "operations/audit-logs/connection-upserted", + "operations/audit-logs/connection-deleted", + "operations/audit-logs/flow-run-started", + "operations/audit-logs/flow-run-finished", + "operations/audit-logs/folder-created", + "operations/audit-logs/folder-updated", + "operations/audit-logs/folder-deleted", + "operations/audit-logs/user-signed-in", + "operations/audit-logs/user-signed-up", + "operations/audit-logs/user-email-verified", + "operations/audit-logs/user-password-reset", + "operations/audit-logs/signing-key-created" + ] + } + ] + }, + { + "group": "Security", + "pages": [ + "security/practices", + "security/permissions", + "security/sso" + ] + }, + { + "group": "About", + "pages": [ + "about/i18n", + "about/editions", + "about/breaking-changes", + "about/changelog", + "about/telemetry", + "about/license" + ] + } + ], + "analytics": { + "posthog": { + "apiKey": "phc_7F92HoXJPeGnTKmYv0eOw62FurPMRW9Aqr0TPrDzvHh" + } + }, + "footerSocials": { + "website": "https://www.activepieces.com", + "github": "https://github.com/activepieces/activepieces", + "discord": "https://discord.gg/2jUXBKDdP8" + }, + "redirects": [ + { + "source": "/embedding/connection-cards/overview", + "destination": "/embedding/connections/create-connection" + }, + { + "source": "/embedding/connection-cards/user-authentication", + "destination": "/embedding/configuration/generate-jwt" + }, + { + "source": "/embedding/connection-cards/frontend-sdk", + "destination": "/embedding/configuration/initialize-embed" + }, + { + "source": "/embedding/connection-cards/user-flows", + "destination": "/embedding/configuration/generate-jwt" + }, + { + "source": "/embedding/embed-builder/generate-jwt", + "destination": "/embedding/configuration/generate-jwt" + }, + { + "source": "/embedding/embed-builder/embed-iframe", + "destination": "/embedding/configuration/initialize-embed" + }, + { + "source": "/embedding/predefined-connection/overview", + "destination": "/embedding/predefined-connection" + } + ] +} \ No newline at end of file diff --git a/docs/openapi.json b/docs/openapi.json new file mode 100644 index 0000000..a161f3b --- /dev/null +++ b/docs/openapi.json @@ -0,0 +1,21909 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Activepieces Documentation", + "version": "0.0.0" + }, + "components": { + "securitySchemes": { + "apiKey": { + "type": "http", + "description": "Use your api key generated from the admin console", + "scheme": "bearer" + } + }, + "schemas": { + "flow.created": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectDisplayName": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "action": { + "type": "string", + "enum": [ + "flow.created" + ] + }, + "data": { + "type": "object", + "properties": { + "flow": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "created", + "updated" + ] + }, + "project": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + } + }, + "required": [ + "displayName" + ] + } + }, + "required": [ + "flow" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "action", + "data" + ] + }, + "flow.deleted": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectDisplayName": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "action": { + "type": "string", + "enum": [ + "flow.deleted" + ] + }, + "data": { + "type": "object", + "properties": { + "flow": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "created", + "updated" + ] + }, + "flowVersion": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "flowId": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "displayName", + "flowId", + "created", + "updated" + ] + }, + "project": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + } + }, + "required": [ + "displayName" + ] + } + }, + "required": [ + "flow", + "flowVersion" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "action", + "data" + ] + }, + "connection.upserted": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectDisplayName": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "action": { + "anyOf": [ + { + "type": "string", + "enum": [ + "connection.deleted" + ] + }, + { + "type": "string", + "enum": [ + "connection.upserted" + ] + } + ] + }, + "data": { + "type": "object", + "properties": { + "connection": { + "description": "App connection is a connection to an external app.", + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "pieceName": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "MISSING" + ] + }, + { + "type": "string", + "enum": [ + "ERROR" + ] + } + ] + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "PLATFORM_OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "CLOUD_OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "SECRET_TEXT" + ] + }, + { + "type": "string", + "enum": [ + "BASIC_AUTH" + ] + }, + { + "type": "string", + "enum": [ + "CUSTOM_AUTH" + ] + }, + { + "type": "string", + "enum": [ + "NO_AUTH" + ] + } + ] + }, + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "displayName", + "externalId", + "pieceName", + "status", + "type", + "id", + "created", + "updated" + ] + }, + "project": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + } + }, + "required": [ + "displayName" + ] + } + }, + "required": [ + "connection" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "action", + "data" + ] + }, + "connection.deleted": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectDisplayName": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "action": { + "anyOf": [ + { + "type": "string", + "enum": [ + "connection.deleted" + ] + }, + { + "type": "string", + "enum": [ + "connection.upserted" + ] + } + ] + }, + "data": { + "type": "object", + "properties": { + "connection": { + "description": "App connection is a connection to an external app.", + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "pieceName": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "MISSING" + ] + }, + { + "type": "string", + "enum": [ + "ERROR" + ] + } + ] + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "PLATFORM_OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "CLOUD_OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "SECRET_TEXT" + ] + }, + { + "type": "string", + "enum": [ + "BASIC_AUTH" + ] + }, + { + "type": "string", + "enum": [ + "CUSTOM_AUTH" + ] + }, + { + "type": "string", + "enum": [ + "NO_AUTH" + ] + } + ] + }, + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "displayName", + "externalId", + "pieceName", + "status", + "type", + "id", + "created", + "updated" + ] + }, + "project": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + } + }, + "required": [ + "displayName" + ] + } + }, + "required": [ + "connection" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "action", + "data" + ] + }, + "folder.created": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectDisplayName": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "action": { + "anyOf": [ + { + "type": "string", + "enum": [ + "folder.updated" + ] + }, + { + "type": "string", + "enum": [ + "folder.created" + ] + }, + { + "type": "string", + "enum": [ + "folder.deleted" + ] + } + ] + }, + "data": { + "type": "object", + "properties": { + "folder": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "displayName", + "created", + "updated" + ] + }, + "project": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + } + }, + "required": [ + "displayName" + ] + } + }, + "required": [ + "folder" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "action", + "data" + ] + }, + "folder.updated": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectDisplayName": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "action": { + "anyOf": [ + { + "type": "string", + "enum": [ + "folder.updated" + ] + }, + { + "type": "string", + "enum": [ + "folder.created" + ] + }, + { + "type": "string", + "enum": [ + "folder.deleted" + ] + } + ] + }, + "data": { + "type": "object", + "properties": { + "folder": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "displayName", + "created", + "updated" + ] + }, + "project": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + } + }, + "required": [ + "displayName" + ] + } + }, + "required": [ + "folder" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "action", + "data" + ] + }, + "folder.deleted": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectDisplayName": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "action": { + "anyOf": [ + { + "type": "string", + "enum": [ + "folder.updated" + ] + }, + { + "type": "string", + "enum": [ + "folder.created" + ] + }, + { + "type": "string", + "enum": [ + "folder.deleted" + ] + } + ] + }, + "data": { + "type": "object", + "properties": { + "folder": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "displayName", + "created", + "updated" + ] + }, + "project": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + } + }, + "required": [ + "displayName" + ] + } + }, + "required": [ + "folder" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "action", + "data" + ] + }, + "flow.run.started": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectDisplayName": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "action": { + "anyOf": [ + { + "type": "string", + "enum": [ + "flow.run.started" + ] + }, + { + "type": "string", + "enum": [ + "flow.run.finished" + ] + } + ] + }, + "data": { + "type": "object", + "properties": { + "flowRun": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "startTime": { + "type": "string" + }, + "finishTime": { + "type": "string" + }, + "duration": { + "type": "number" + }, + "environment": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PRODUCTION" + ] + }, + { + "type": "string", + "enum": [ + "TESTING" + ] + } + ] + }, + "flowId": { + "type": "string" + }, + "flowVersionId": { + "type": "string" + }, + "flowDisplayName": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "FAILED" + ] + }, + { + "type": "string", + "enum": [ + "QUOTA_EXCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "INTERNAL_ERROR" + ] + }, + { + "type": "string", + "enum": [ + "PAUSED" + ] + }, + { + "type": "string", + "enum": [ + "RUNNING" + ] + }, + { + "type": "string", + "enum": [ + "STOPPED" + ] + }, + { + "type": "string", + "enum": [ + "SUCCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "MEMORY_LIMIT_EXCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "TIMEOUT" + ] + } + ] + } + }, + "required": [ + "id", + "startTime", + "environment", + "flowId", + "flowVersionId", + "flowDisplayName", + "status" + ] + }, + "project": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + } + }, + "required": [ + "displayName" + ] + } + }, + "required": [ + "flowRun" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "action", + "data" + ] + }, + "flow.run.finished": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectDisplayName": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "action": { + "anyOf": [ + { + "type": "string", + "enum": [ + "flow.run.started" + ] + }, + { + "type": "string", + "enum": [ + "flow.run.finished" + ] + } + ] + }, + "data": { + "type": "object", + "properties": { + "flowRun": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "startTime": { + "type": "string" + }, + "finishTime": { + "type": "string" + }, + "duration": { + "type": "number" + }, + "environment": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PRODUCTION" + ] + }, + { + "type": "string", + "enum": [ + "TESTING" + ] + } + ] + }, + "flowId": { + "type": "string" + }, + "flowVersionId": { + "type": "string" + }, + "flowDisplayName": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "FAILED" + ] + }, + { + "type": "string", + "enum": [ + "QUOTA_EXCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "INTERNAL_ERROR" + ] + }, + { + "type": "string", + "enum": [ + "PAUSED" + ] + }, + { + "type": "string", + "enum": [ + "RUNNING" + ] + }, + { + "type": "string", + "enum": [ + "STOPPED" + ] + }, + { + "type": "string", + "enum": [ + "SUCCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "MEMORY_LIMIT_EXCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "TIMEOUT" + ] + } + ] + } + }, + "required": [ + "id", + "startTime", + "environment", + "flowId", + "flowVersionId", + "flowDisplayName", + "status" + ] + }, + "project": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + } + }, + "required": [ + "displayName" + ] + } + }, + "required": [ + "flowRun" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "action", + "data" + ] + }, + "user.signed.up": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectDisplayName": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "action": { + "type": "string", + "enum": [ + "user.signed.up" + ] + }, + "data": { + "type": "object", + "properties": { + "source": { + "anyOf": [ + { + "type": "string", + "enum": [ + "credentials" + ] + }, + { + "type": "string", + "enum": [ + "sso" + ] + }, + { + "type": "string", + "enum": [ + "managed" + ] + } + ] + }, + "user": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + } + }, + "required": [ + "source" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "action", + "data" + ] + }, + "user.signed.in": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectDisplayName": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "action": { + "anyOf": [ + { + "type": "string", + "enum": [ + "user.signed.in" + ] + }, + { + "type": "string", + "enum": [ + "user.password.reset" + ] + }, + { + "type": "string", + "enum": [ + "user.email.verified" + ] + } + ] + }, + "data": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + } + } + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "action", + "data" + ] + }, + "user.password.reset": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectDisplayName": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "action": { + "anyOf": [ + { + "type": "string", + "enum": [ + "user.signed.in" + ] + }, + { + "type": "string", + "enum": [ + "user.password.reset" + ] + }, + { + "type": "string", + "enum": [ + "user.email.verified" + ] + } + ] + }, + "data": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + } + } + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "action", + "data" + ] + }, + "user.email.verified": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectDisplayName": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "action": { + "anyOf": [ + { + "type": "string", + "enum": [ + "user.signed.in" + ] + }, + { + "type": "string", + "enum": [ + "user.password.reset" + ] + }, + { + "type": "string", + "enum": [ + "user.email.verified" + ] + } + ] + }, + "data": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + } + } + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "action", + "data" + ] + }, + "signing.key.created": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectDisplayName": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "action": { + "type": "string", + "enum": [ + "signing.key.created" + ] + }, + "data": { + "type": "object", + "properties": { + "signingKey": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "displayName": { + "type": "string" + } + }, + "required": [ + "id", + "created", + "updated", + "displayName" + ] + } + }, + "required": [ + "signingKey" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "action", + "data" + ] + }, + "project.role.created": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectDisplayName": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "action": { + "anyOf": [ + { + "type": "string", + "enum": [ + "project.role.created" + ] + }, + { + "type": "string", + "enum": [ + "project.role.updated" + ] + }, + { + "type": "string", + "enum": [ + "project.role.deleted" + ] + } + ] + }, + "data": { + "type": "object", + "properties": { + "projectRole": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "name": { + "type": "string" + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "platformId": { + "type": "string" + } + }, + "required": [ + "id", + "created", + "updated", + "name", + "permissions" + ] + } + }, + "required": [ + "projectRole" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "action", + "data" + ] + }, + "project.release.created": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectDisplayName": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "action": { + "type": "string", + "enum": [ + "project.release.created" + ] + }, + "data": { + "type": "object", + "properties": { + "release": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "GIT" + ] + }, + { + "type": "string", + "enum": [ + "PROJECT" + ] + }, + { + "type": "string", + "enum": [ + "ROLLBACK" + ] + } + ] + }, + "projectId": { + "type": "string" + }, + "importedByUser": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "INACTIVE" + ] + } + ] + }, + "externalId": { + "type": "string", + "nullable": true + }, + "lastChangelogDismissed": { + "type": "string", + "nullable": true + }, + "platformId": { + "type": "string", + "nullable": true + }, + "platformRole": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ADMIN" + ] + }, + { + "type": "string", + "enum": [ + "MEMBER" + ] + } + ] + }, + "lastName": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "email", + "firstName", + "status", + "platformRole", + "lastName", + "created", + "updated" + ] + } + }, + "required": [ + "name", + "type", + "projectId" + ] + } + }, + "required": [ + "release" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "action", + "data" + ] + }, + "flow-template": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PLATFORM" + ] + }, + { + "type": "string", + "enum": [ + "PROJECT" + ] + } + ] + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "pieces": { + "type": "array", + "items": { + "type": "string" + } + }, + "schemaVersion": { + "type": "string", + "nullable": true + }, + "blogUrl": { + "type": "string" + }, + "template": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "trigger": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "PIECE_TRIGGER" + ] + }, + "settings": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "pattern": "^([~^])?[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "pieceType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "CUSTOM" + ] + }, + { + "type": "string", + "enum": [ + "OFFICIAL" + ] + } + ] + }, + "packageType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + { + "type": "string", + "enum": [ + "REGISTRY" + ] + } + ] + }, + "triggerName": { + "type": "string" + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "pieceName", + "pieceVersion", + "pieceType", + "packageType", + "input", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "EMPTY" + ] + }, + "settings": {} + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + } + ] + }, + "valid": { + "type": "boolean" + }, + "schemaVersion": { + "type": "string", + "nullable": true + }, + "connectionIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "displayName", + "trigger", + "valid", + "connectionIds" + ] + }, + "projectId": { + "type": "string" + }, + "platformId": { + "type": "string" + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + } + }, + "required": [ + "id", + "created", + "updated", + "name", + "description", + "type", + "tags", + "pieces", + "template", + "platformId" + ] + }, + "folder": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "displayOrder": { + "type": "number" + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "displayName", + "displayOrder" + ] + }, + "user": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "INACTIVE" + ] + } + ] + }, + "externalId": { + "type": "string", + "nullable": true + }, + "lastChangelogDismissed": { + "type": "string", + "nullable": true + }, + "platformId": { + "type": "string", + "nullable": true + }, + "platformRole": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ADMIN" + ] + }, + { + "type": "string", + "enum": [ + "MEMBER" + ] + } + ] + }, + "lastName": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "email", + "firstName", + "status", + "platformRole", + "lastName", + "created", + "updated" + ] + }, + "user-invitation": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "email": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PENDING" + ] + }, + { + "type": "string", + "enum": [ + "ACCEPTED" + ] + } + ] + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PLATFORM" + ] + }, + { + "type": "string", + "enum": [ + "PROJECT" + ] + } + ] + }, + "platformId": { + "type": "string" + }, + "platformRole": { + "type": "string", + "enum": [ + "ADMIN", + "MEMBER" + ], + "nullable": true + }, + "projectId": { + "type": "string", + "nullable": true + }, + "projectRoleId": { + "type": "string", + "nullable": true + }, + "projectRole": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "name": { + "type": "string" + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "platformId": { + "type": "string" + }, + "type": { + "type": "string" + }, + "userCount": { + "type": "number" + } + }, + "required": [ + "id", + "created", + "updated", + "name", + "permissions", + "type" + ], + "nullable": true + } + }, + "required": [ + "id", + "created", + "updated", + "email", + "status", + "type", + "platformId" + ] + }, + "project-member": { + "description": "Project member is which user is assigned to a project.", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "userId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectRoleId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "userId", + "projectId", + "projectRoleId" + ] + }, + "project": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "notifyStatus": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NEVER" + ] + }, + { + "type": "string", + "enum": [ + "ALWAYS" + ] + }, + { + "type": "string", + "enum": [ + "NEW_ISSUE" + ] + } + ] + }, + "platformId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "externalId": { + "type": "string" + }, + "releasesEnabled": { + "type": "boolean" + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "usage": { + "type": "object", + "properties": { + "tasks": { + "type": "number" + }, + "aiCredits": { + "type": "number" + }, + "nextLimitResetDate": { + "type": "string" + } + }, + "required": [ + "tasks", + "aiCredits", + "nextLimitResetDate" + ] + }, + "plan": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "piecesFilterType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NONE" + ] + }, + { + "type": "string", + "enum": [ + "ALLOWED" + ] + } + ] + }, + "pieces": { + "type": "array", + "items": { + "type": "string" + } + }, + "tasks": { + "type": "number", + "nullable": true + }, + "aiCredits": { + "type": "number", + "nullable": true + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "name", + "piecesFilterType", + "pieces" + ] + }, + "analytics": { + "type": "object", + "properties": { + "totalUsers": { + "type": "number" + }, + "activeUsers": { + "type": "number" + }, + "totalFlows": { + "type": "number" + }, + "activeFlows": { + "type": "number" + } + }, + "required": [ + "totalUsers", + "activeUsers", + "totalFlows", + "activeFlows" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "ownerId", + "displayName", + "notifyStatus", + "platformId", + "releasesEnabled", + "usage", + "plan", + "analytics" + ] + }, + "flow": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "folderId": { + "type": "string", + "nullable": true + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ENABLED" + ] + }, + { + "type": "string", + "enum": [ + "DISABLED" + ] + } + ] + }, + "schedule": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "CRON_EXPRESSION" + ] + }, + "cronExpression": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "failureCount": { + "type": "number" + } + }, + "required": [ + "type", + "cronExpression", + "timezone" + ], + "nullable": true + }, + "handshakeConfiguration": { + "type": "object", + "properties": { + "strategy": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NONE" + ] + }, + { + "type": "string", + "enum": [ + "HEADER_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "QUERY_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "BODY_PARAM_PRESENT" + ] + } + ] + }, + "paramName": { + "type": "string" + } + }, + "required": [ + "strategy" + ], + "nullable": true + }, + "publishedVersionId": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "externalId", + "status" + ] + }, + "flow-run": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "flowId": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "flowVersionId": { + "type": "string" + }, + "flowDisplayName": { + "type": "string" + }, + "terminationReason": { + "type": "string" + }, + "logsFileId": { + "type": "string", + "nullable": true + }, + "tasks": { + "type": "number" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "FAILED" + ] + }, + { + "type": "string", + "enum": [ + "QUOTA_EXCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "INTERNAL_ERROR" + ] + }, + { + "type": "string", + "enum": [ + "PAUSED" + ] + }, + { + "type": "string", + "enum": [ + "RUNNING" + ] + }, + { + "type": "string", + "enum": [ + "STOPPED" + ] + }, + { + "type": "string", + "enum": [ + "SUCCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "MEMORY_LIMIT_EXCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "TIMEOUT" + ] + } + ] + }, + "duration": { + "type": "number" + }, + "startTime": { + "type": "string" + }, + "finishTime": { + "type": "string" + }, + "environment": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PRODUCTION" + ] + }, + { + "type": "string", + "enum": [ + "TESTING" + ] + } + ] + }, + "pauseMetadata": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "DELAY" + ] + }, + "resumeDateTime": { + "type": "string" + }, + "handlerId": { + "type": "string" + }, + "progressUpdateType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "WEBHOOK_RESPONSE" + ] + }, + { + "type": "string", + "enum": [ + "TEST_FLOW" + ] + }, + { + "type": "string", + "enum": [ + "NONE" + ] + } + ] + } + }, + "required": [ + "type", + "resumeDateTime" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "WEBHOOK" + ] + }, + "requestId": { + "type": "string" + }, + "response": { + "type": "object", + "properties": { + "status": { + "type": "number" + }, + "body": {}, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "handlerId": { + "type": "string" + }, + "progressUpdateType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "WEBHOOK_RESPONSE" + ] + }, + { + "type": "string", + "enum": [ + "TEST_FLOW" + ] + }, + { + "type": "string", + "enum": [ + "NONE" + ] + } + ] + } + }, + "required": [ + "type", + "requestId", + "response" + ] + } + ] + }, + "steps": { + "type": "object", + "additionalProperties": {} + }, + "failedStepName": { + "type": "string" + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "flowId", + "flowVersionId", + "flowDisplayName", + "status", + "startTime", + "environment", + "steps" + ] + }, + "app-connection": { + "description": "App connection is a connection to an external app.", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "PLATFORM_OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "CLOUD_OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "SECRET_TEXT" + ] + }, + { + "type": "string", + "enum": [ + "BASIC_AUTH" + ] + }, + { + "type": "string", + "enum": [ + "CUSTOM_AUTH" + ] + }, + { + "type": "string", + "enum": [ + "NO_AUTH" + ] + } + ] + }, + "pieceName": { + "type": "string" + }, + "projectIds": { + "type": "array", + "items": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + } + }, + "platformId": { + "type": "string", + "nullable": true + }, + "scope": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PROJECT" + ] + }, + { + "type": "string", + "enum": [ + "PLATFORM" + ] + } + ] + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "MISSING" + ] + }, + { + "type": "string", + "enum": [ + "ERROR" + ] + } + ] + }, + "ownerId": { + "type": "string", + "nullable": true + }, + "owner": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "INACTIVE" + ] + } + ] + }, + "externalId": { + "type": "string", + "nullable": true + }, + "lastChangelogDismissed": { + "type": "string", + "nullable": true + }, + "platformId": { + "type": "string", + "nullable": true + }, + "platformRole": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ADMIN" + ] + }, + { + "type": "string", + "enum": [ + "MEMBER" + ] + } + ] + }, + "lastName": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "email", + "firstName", + "status", + "platformRole", + "lastName", + "created", + "updated" + ], + "nullable": true + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "flowIds": { + "type": "array", + "items": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "nullable": true + } + }, + "required": [ + "id", + "created", + "updated", + "externalId", + "displayName", + "type", + "pieceName", + "projectIds", + "scope", + "status" + ] + }, + "piece": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "logoUrl": { + "type": "string" + }, + "description": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "authors": { + "type": "array", + "items": { + "type": "string" + } + }, + "platformId": { + "type": "string" + }, + "directoryPath": { + "type": "string" + }, + "auth": { + "anyOf": [ + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "username": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "displayName" + ] + }, + "password": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "displayName" + ] + }, + "type": { + "type": "string", + "enum": [ + "BASIC_AUTH" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "username", + "password", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "props": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "SHORT_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "LONG_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "NUMBER" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "CHECKBOX" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "placeholder": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": {} + }, + "required": [ + "label", + "value" + ] + } + } + }, + "required": [ + "options" + ] + }, + "type": { + "type": "string", + "enum": [ + "STATIC_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "options", + "type", + "required" + ] + } + ] + } + }, + "type": { + "type": "string", + "enum": [ + "CUSTOM_AUTH" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "props", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "props": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "SHORT_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "SECRET_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "placeholder": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": {} + }, + "required": [ + "label", + "value" + ] + } + } + }, + "required": [ + "options" + ] + }, + "type": { + "type": "string", + "enum": [ + "STATIC_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "options", + "type", + "required" + ] + } + ] + } + }, + "authUrl": { + "type": "string" + }, + "tokenUrl": { + "type": "string" + }, + "scope": { + "type": "array", + "items": { + "type": "string" + } + }, + "pkce": { + "type": "boolean" + }, + "authorizationMethod": { + "anyOf": [ + { + "type": "string", + "enum": [ + "HEADER" + ] + }, + { + "type": "string", + "enum": [ + "BODY" + ] + } + ] + }, + "grantType": { + "anyOf": [ + { + "anyOf": [ + { + "type": "string", + "enum": [ + "authorization_code" + ] + }, + { + "type": "string", + "enum": [ + "client_credentials" + ] + } + ] + }, + { + "type": "string", + "enum": [ + "both_client_credentials_and_authorization_code" + ] + } + ] + }, + "extra": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "type": { + "type": "string", + "enum": [ + "OAUTH2" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "authUrl", + "tokenUrl", + "scope", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "SECRET_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + } + ] + }, + "version": { + "type": "string" + }, + "categories": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARTIFICIAL_INTELLIGENCE" + ] + }, + { + "type": "string", + "enum": [ + "COMMUNICATION" + ] + }, + { + "type": "string", + "enum": [ + "COMMERCE" + ] + }, + { + "type": "string", + "enum": [ + "CORE" + ] + }, + { + "type": "string", + "enum": [ + "UNIVERSAL_AI" + ] + }, + { + "type": "string", + "enum": [ + "FLOW_CONTROL" + ] + }, + { + "type": "string", + "enum": [ + "BUSINESS_INTELLIGENCE" + ] + }, + { + "type": "string", + "enum": [ + "ACCOUNTING" + ] + }, + { + "type": "string", + "enum": [ + "PRODUCTIVITY" + ] + }, + { + "type": "string", + "enum": [ + "CONTENT_AND_FILES" + ] + }, + { + "type": "string", + "enum": [ + "DEVELOPER_TOOLS" + ] + }, + { + "type": "string", + "enum": [ + "CUSTOMER_SUPPORT" + ] + }, + { + "type": "string", + "enum": [ + "FORMS_AND_SURVEYS" + ] + }, + { + "type": "string", + "enum": [ + "HUMAN_RESOURCES" + ] + }, + { + "type": "string", + "enum": [ + "PAYMENT_PROCESSING" + ] + }, + { + "type": "string", + "enum": [ + "MARKETING" + ] + }, + { + "type": "string", + "enum": [ + "SALES_AND_CRM" + ] + } + ] + } + }, + "minimumSupportedRelease": { + "type": "string" + }, + "maximumSupportedRelease": { + "type": "string" + }, + "i18n": { + "type": "object", + "properties": { + "nl": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "en": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "de": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "it": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "fr": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "bg": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "uk": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "hu": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "es": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "ja": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "id": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "vi": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "zh": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "pt": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "actions": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "props": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "SHORT_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "LONG_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "MARKDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "CHECKBOX" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "placeholder": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": {} + }, + "required": [ + "label", + "value" + ] + } + } + }, + "required": [ + "options" + ] + }, + "type": { + "type": "string", + "enum": [ + "STATIC_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "options", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "placeholder": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": {} + }, + "required": [ + "label", + "value" + ] + } + } + }, + "required": [ + "options" + ] + }, + "type": { + "type": "string", + "enum": [ + "STATIC_MULTI_SELECT_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "options", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {}, + "refreshers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "displayName", + "type", + "required", + "refreshers" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "MULTI_SELECT_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {}, + "refreshers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "displayName", + "type", + "required", + "refreshers" + ] + }, + { + "type": "object", + "properties": { + "refreshers": { + "type": "array", + "items": { + "type": "string" + } + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "DYNAMIC" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "refreshers", + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "NUMBER" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "properties": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "SHORT_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "LONG_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "placeholder": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": {} + }, + "required": [ + "label", + "value" + ] + } + } + }, + "required": [ + "options" + ] + }, + "type": { + "type": "string", + "enum": [ + "STATIC_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "options", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "MULTI_SELECT_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {}, + "refreshers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "displayName", + "type", + "required", + "refreshers" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "placeholder": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": {} + }, + "required": [ + "label", + "value" + ] + } + } + }, + "required": [ + "options" + ] + }, + "type": { + "type": "string", + "enum": [ + "STATIC_MULTI_SELECT_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "options", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "CHECKBOX" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "NUMBER" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "FILE" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + } + ] + } + }, + "type": { + "type": "string", + "enum": [ + "ARRAY" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "properties", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "OBJECT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "JSON" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "DATE_TIME" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "FILE" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "COLOR" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + } + ] + }, + { + "anyOf": [ + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "username": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "displayName" + ] + }, + "password": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "displayName" + ] + }, + "type": { + "type": "string", + "enum": [ + "BASIC_AUTH" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "username", + "password", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "props": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "SHORT_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "LONG_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "NUMBER" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "CHECKBOX" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "placeholder": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": {} + }, + "required": [ + "label", + "value" + ] + } + } + }, + "required": [ + "options" + ] + }, + "type": { + "type": "string", + "enum": [ + "STATIC_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "options", + "type", + "required" + ] + } + ] + } + }, + "type": { + "type": "string", + "enum": [ + "CUSTOM_AUTH" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "props", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "props": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "SHORT_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "SECRET_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "placeholder": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": {} + }, + "required": [ + "label", + "value" + ] + } + } + }, + "required": [ + "options" + ] + }, + "type": { + "type": "string", + "enum": [ + "STATIC_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "options", + "type", + "required" + ] + } + ] + } + }, + "authUrl": { + "type": "string" + }, + "tokenUrl": { + "type": "string" + }, + "scope": { + "type": "array", + "items": { + "type": "string" + } + }, + "pkce": { + "type": "boolean" + }, + "authorizationMethod": { + "anyOf": [ + { + "type": "string", + "enum": [ + "HEADER" + ] + }, + { + "type": "string", + "enum": [ + "BODY" + ] + } + ] + }, + "grantType": { + "anyOf": [ + { + "anyOf": [ + { + "type": "string", + "enum": [ + "authorization_code" + ] + }, + { + "type": "string", + "enum": [ + "client_credentials" + ] + } + ] + }, + { + "type": "string", + "enum": [ + "both_client_credentials_and_authorization_code" + ] + } + ] + }, + "extra": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "type": { + "type": "string", + "enum": [ + "OAUTH2" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "authUrl", + "tokenUrl", + "scope", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "SECRET_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + } + ] + } + ] + } + }, + "requireAuth": { + "type": "boolean" + }, + "errorHandlingOptions": { + "type": "object", + "properties": { + "retryOnFailure": { + "type": "object", + "properties": { + "defaultValue": { + "type": "boolean" + }, + "hide": { + "type": "boolean" + } + } + }, + "continueOnFailure": { + "type": "object", + "properties": { + "defaultValue": { + "type": "boolean" + }, + "hide": { + "type": "boolean" + } + } + } + }, + "required": [ + "retryOnFailure", + "continueOnFailure" + ] + } + }, + "required": [ + "name", + "displayName", + "description", + "props", + "requireAuth" + ] + } + }, + "triggers": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "props": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "SHORT_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "LONG_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "MARKDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "CHECKBOX" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "placeholder": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": {} + }, + "required": [ + "label", + "value" + ] + } + } + }, + "required": [ + "options" + ] + }, + "type": { + "type": "string", + "enum": [ + "STATIC_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "options", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "placeholder": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": {} + }, + "required": [ + "label", + "value" + ] + } + } + }, + "required": [ + "options" + ] + }, + "type": { + "type": "string", + "enum": [ + "STATIC_MULTI_SELECT_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "options", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {}, + "refreshers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "displayName", + "type", + "required", + "refreshers" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "MULTI_SELECT_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {}, + "refreshers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "displayName", + "type", + "required", + "refreshers" + ] + }, + { + "type": "object", + "properties": { + "refreshers": { + "type": "array", + "items": { + "type": "string" + } + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "DYNAMIC" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "refreshers", + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "NUMBER" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "properties": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "SHORT_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "LONG_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "placeholder": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": {} + }, + "required": [ + "label", + "value" + ] + } + } + }, + "required": [ + "options" + ] + }, + "type": { + "type": "string", + "enum": [ + "STATIC_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "options", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "MULTI_SELECT_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {}, + "refreshers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "displayName", + "type", + "required", + "refreshers" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "placeholder": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": {} + }, + "required": [ + "label", + "value" + ] + } + } + }, + "required": [ + "options" + ] + }, + "type": { + "type": "string", + "enum": [ + "STATIC_MULTI_SELECT_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "options", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "CHECKBOX" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "NUMBER" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "FILE" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + } + ] + } + }, + "type": { + "type": "string", + "enum": [ + "ARRAY" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "properties", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "OBJECT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "JSON" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "DATE_TIME" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "FILE" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "COLOR" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + } + ] + }, + { + "anyOf": [ + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "username": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "displayName" + ] + }, + "password": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "displayName" + ] + }, + "type": { + "type": "string", + "enum": [ + "BASIC_AUTH" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "username", + "password", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "props": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "SHORT_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "LONG_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "NUMBER" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "CHECKBOX" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "placeholder": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": {} + }, + "required": [ + "label", + "value" + ] + } + } + }, + "required": [ + "options" + ] + }, + "type": { + "type": "string", + "enum": [ + "STATIC_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "options", + "type", + "required" + ] + } + ] + } + }, + "type": { + "type": "string", + "enum": [ + "CUSTOM_AUTH" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "props", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "props": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "SHORT_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "SECRET_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "placeholder": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": {} + }, + "required": [ + "label", + "value" + ] + } + } + }, + "required": [ + "options" + ] + }, + "type": { + "type": "string", + "enum": [ + "STATIC_DROPDOWN" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "options", + "type", + "required" + ] + } + ] + } + }, + "authUrl": { + "type": "string" + }, + "tokenUrl": { + "type": "string" + }, + "scope": { + "type": "array", + "items": { + "type": "string" + } + }, + "pkce": { + "type": "boolean" + }, + "authorizationMethod": { + "anyOf": [ + { + "type": "string", + "enum": [ + "HEADER" + ] + }, + { + "type": "string", + "enum": [ + "BODY" + ] + } + ] + }, + "grantType": { + "anyOf": [ + { + "anyOf": [ + { + "type": "string", + "enum": [ + "authorization_code" + ] + }, + { + "type": "string", + "enum": [ + "client_credentials" + ] + } + ] + }, + { + "type": "string", + "enum": [ + "both_client_credentials_and_authorization_code" + ] + } + ] + }, + "extra": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "type": { + "type": "string", + "enum": [ + "OAUTH2" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "authUrl", + "tokenUrl", + "scope", + "type", + "required" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "SECRET_TEXT" + ] + }, + "required": { + "type": "boolean" + }, + "defaultValue": {} + }, + "required": [ + "displayName", + "type", + "required" + ] + } + ] + } + ] + } + }, + "errorHandlingOptions": { + "type": "object", + "properties": { + "retryOnFailure": { + "type": "object", + "properties": { + "defaultValue": { + "type": "boolean" + }, + "hide": { + "type": "boolean" + } + } + }, + "continueOnFailure": { + "type": "object", + "properties": { + "defaultValue": { + "type": "boolean" + }, + "hide": { + "type": "boolean" + } + } + } + }, + "required": [ + "retryOnFailure", + "continueOnFailure" + ] + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "POLLING" + ] + }, + { + "type": "string", + "enum": [ + "WEBHOOK" + ] + }, + { + "type": "string", + "enum": [ + "APP_WEBHOOK" + ] + } + ] + }, + "sampleData": {}, + "handshakeConfiguration": { + "type": "object", + "properties": { + "strategy": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NONE" + ] + }, + { + "type": "string", + "enum": [ + "HEADER_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "QUERY_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "BODY_PARAM_PRESENT" + ] + } + ] + }, + "paramName": { + "type": "string" + } + }, + "required": [ + "strategy" + ] + }, + "renewConfiguration": { + "anyOf": [ + { + "type": "object", + "properties": { + "strategy": { + "type": "string", + "enum": [ + "CRON" + ] + }, + "cronExpression": { + "type": "string" + } + }, + "required": [ + "strategy", + "cronExpression" + ] + }, + { + "type": "object", + "properties": { + "strategy": { + "type": "string", + "enum": [ + "NONE" + ] + } + }, + "required": [ + "strategy" + ] + } + ] + }, + "testStrategy": { + "anyOf": [ + { + "type": "string", + "enum": [ + "SIMULATION" + ] + }, + { + "type": "string", + "enum": [ + "TEST_FUNCTION" + ] + } + ] + } + }, + "required": [ + "name", + "displayName", + "description", + "props", + "type", + "sampleData", + "testStrategy" + ] + } + } + }, + "required": [ + "name", + "displayName", + "logoUrl", + "description", + "authors", + "version", + "actions", + "triggers" + ] + }, + "git-repo": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "remoteUrl": { + "type": "string" + }, + "branch": { + "type": "string" + }, + "branchType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PRODUCTION" + ] + }, + { + "type": "string", + "enum": [ + "DEVELOPMENT" + ] + } + ] + }, + "projectId": { + "type": "string" + }, + "slug": { + "type": "string" + } + }, + "required": [ + "id", + "created", + "updated", + "remoteUrl", + "branch", + "branchType", + "projectId", + "slug" + ] + }, + "project-release": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "importedBy": { + "type": "string", + "nullable": true + }, + "fileId": { + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "GIT" + ] + }, + { + "type": "string", + "enum": [ + "PROJECT" + ] + }, + { + "type": "string", + "enum": [ + "ROLLBACK" + ] + } + ] + }, + "importedByUser": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "INACTIVE" + ] + } + ] + }, + "externalId": { + "type": "string", + "nullable": true + }, + "lastChangelogDismissed": { + "type": "string", + "nullable": true + }, + "platformId": { + "type": "string", + "nullable": true + }, + "platformRole": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ADMIN" + ] + }, + { + "type": "string", + "enum": [ + "MEMBER" + ] + } + ] + }, + "lastName": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "email", + "firstName", + "status", + "platformRole", + "lastName", + "created", + "updated" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "name", + "fileId", + "type" + ] + }, + "global-connection": { + "description": "App connection is a connection to an external app.", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "PLATFORM_OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "CLOUD_OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "SECRET_TEXT" + ] + }, + { + "type": "string", + "enum": [ + "BASIC_AUTH" + ] + }, + { + "type": "string", + "enum": [ + "CUSTOM_AUTH" + ] + }, + { + "type": "string", + "enum": [ + "NO_AUTH" + ] + } + ] + }, + "pieceName": { + "type": "string" + }, + "projectIds": { + "type": "array", + "items": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + } + }, + "platformId": { + "type": "string", + "nullable": true + }, + "scope": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PROJECT" + ] + }, + { + "type": "string", + "enum": [ + "PLATFORM" + ] + } + ] + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "MISSING" + ] + }, + { + "type": "string", + "enum": [ + "ERROR" + ] + } + ] + }, + "ownerId": { + "type": "string", + "nullable": true + }, + "owner": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "INACTIVE" + ] + } + ] + }, + "externalId": { + "type": "string", + "nullable": true + }, + "lastChangelogDismissed": { + "type": "string", + "nullable": true + }, + "platformId": { + "type": "string", + "nullable": true + }, + "platformRole": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ADMIN" + ] + }, + { + "type": "string", + "enum": [ + "MEMBER" + ] + } + ] + }, + "lastName": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "email", + "firstName", + "status", + "platformRole", + "lastName", + "created", + "updated" + ], + "nullable": true + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "flowIds": { + "type": "array", + "items": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "nullable": true + } + }, + "required": [ + "id", + "created", + "updated", + "externalId", + "displayName", + "type", + "pieceName", + "projectIds", + "scope", + "status" + ] + }, + "mcp": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "name": { + "type": "string" + }, + "projectId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "token": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "agentId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "tools": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PIECE" + ] + }, + { + "type": "string", + "enum": [ + "FLOW" + ] + } + ] + }, + "mcpId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "pieceMetadata": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "type": "string" + }, + "actionNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "logoUrl": { + "type": "string" + }, + "connectionExternalId": { + "type": "string" + } + }, + "required": [ + "pieceName", + "pieceVersion", + "actionNames", + "logoUrl" + ] + }, + "flowId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "flow": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "folderId": { + "type": "string", + "nullable": true + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ENABLED" + ] + }, + { + "type": "string", + "enum": [ + "DISABLED" + ] + } + ] + }, + "schedule": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "CRON_EXPRESSION" + ] + }, + "cronExpression": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "failureCount": { + "type": "number" + } + }, + "required": [ + "type", + "cronExpression", + "timezone" + ], + "nullable": true + }, + "handshakeConfiguration": { + "type": "object", + "properties": { + "strategy": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NONE" + ] + }, + { + "type": "string", + "enum": [ + "HEADER_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "QUERY_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "BODY_PARAM_PRESENT" + ] + } + ] + }, + "paramName": { + "type": "string" + } + }, + "required": [ + "strategy" + ], + "nullable": true + }, + "publishedVersionId": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "version": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "flowId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "trigger": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "PIECE_TRIGGER" + ] + }, + "settings": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "pattern": "^([~^])?[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "pieceType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "CUSTOM" + ] + }, + { + "type": "string", + "enum": [ + "OFFICIAL" + ] + } + ] + }, + "packageType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + { + "type": "string", + "enum": [ + "REGISTRY" + ] + } + ] + }, + "triggerName": { + "type": "string" + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "pieceName", + "pieceVersion", + "pieceType", + "packageType", + "input", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "EMPTY" + ] + }, + "settings": {} + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + } + ] + }, + "updatedBy": { + "type": "string", + "nullable": true + }, + "valid": { + "type": "boolean" + }, + "schemaVersion": { + "type": "string", + "nullable": true + }, + "state": { + "anyOf": [ + { + "type": "string", + "enum": [ + "LOCKED" + ] + }, + { + "type": "string", + "enum": [ + "DRAFT" + ] + } + ] + }, + "connectionIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "created", + "updated", + "flowId", + "displayName", + "trigger", + "valid", + "state", + "connectionIds" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "externalId", + "status", + "version" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "type", + "mcpId" + ] + } + } + }, + "required": [ + "id", + "created", + "updated", + "name", + "projectId", + "token", + "tools" + ] + } + } + }, + "paths": { + "/v1/folders": { + "post": { + "tags": [ + "folders" + ], + "description": "Create a new folder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "projectId": { + "type": "string" + } + }, + "required": [ + "displayName", + "projectId" + ] + } + } + }, + "required": true + }, + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + }, + "get": { + "tags": [ + "folders" + ], + "description": "List folders", + "parameters": [ + { + "schema": { + "type": "number" + }, + "in": "query", + "name": "limit", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursor", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + } + }, + "/v1/folders/{id}": { + "post": { + "tags": [ + "folders" + ], + "description": "Update an existing folder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + } + }, + "required": [ + "displayName" + ] + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + }, + "get": { + "tags": [ + "folders" + ], + "description": "Get a folder by id", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + }, + "delete": { + "tags": [ + "folders" + ], + "description": "Delete a folder", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + } + }, + "/v1/flows": { + "post": { + "tags": [ + "flows" + ], + "description": "Create a flow", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "folderId": { + "type": "string" + }, + "folderName": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "displayName", + "projectId" + ] + } + } + }, + "required": true + }, + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "201": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "folderId": { + "type": "string", + "nullable": true + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ENABLED" + ] + }, + { + "type": "string", + "enum": [ + "DISABLED" + ] + } + ] + }, + "schedule": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "CRON_EXPRESSION" + ] + }, + "cronExpression": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "failureCount": { + "type": "number" + } + }, + "required": [ + "type", + "cronExpression", + "timezone" + ], + "nullable": true + }, + "handshakeConfiguration": { + "type": "object", + "properties": { + "strategy": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NONE" + ] + }, + { + "type": "string", + "enum": [ + "HEADER_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "QUERY_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "BODY_PARAM_PRESENT" + ] + } + ] + }, + "paramName": { + "type": "string" + } + }, + "required": [ + "strategy" + ], + "nullable": true + }, + "publishedVersionId": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "version": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "flowId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "trigger": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "PIECE_TRIGGER" + ] + }, + "settings": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "pattern": "^([~^])?[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "pieceType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "CUSTOM" + ] + }, + { + "type": "string", + "enum": [ + "OFFICIAL" + ] + } + ] + }, + "packageType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + { + "type": "string", + "enum": [ + "REGISTRY" + ] + } + ] + }, + "triggerName": { + "type": "string" + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "pieceName", + "pieceVersion", + "pieceType", + "packageType", + "input", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "EMPTY" + ] + }, + "settings": {} + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + } + ] + }, + "updatedBy": { + "type": "string", + "nullable": true + }, + "valid": { + "type": "boolean" + }, + "schemaVersion": { + "type": "string", + "nullable": true + }, + "state": { + "anyOf": [ + { + "type": "string", + "enum": [ + "LOCKED" + ] + }, + { + "type": "string", + "enum": [ + "DRAFT" + ] + } + ] + }, + "connectionIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "created", + "updated", + "flowId", + "displayName", + "trigger", + "valid", + "state", + "connectionIds" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "externalId", + "status", + "version" + ] + } + } + } + } + } + }, + "get": { + "tags": [ + "flows" + ], + "description": "List flows", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "folderId", + "required": false + }, + { + "schema": { + "type": "number" + }, + "in": "query", + "name": "limit", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursor", + "required": false + }, + { + "schema": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ENABLED" + ] + }, + { + "type": "string", + "enum": [ + "DISABLED" + ] + } + ] + } + }, + "in": "query", + "name": "status", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "name", + "required": false + }, + { + "schema": { + "anyOf": [ + { + "type": "string", + "enum": [ + "LOCKED" + ] + }, + { + "type": "string", + "enum": [ + "DRAFT" + ] + } + ] + }, + "in": "query", + "name": "versionState", + "required": false + }, + { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query", + "name": "connectionExternalIds", + "required": false + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "folderId": { + "type": "string", + "nullable": true + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ENABLED" + ] + }, + { + "type": "string", + "enum": [ + "DISABLED" + ] + } + ] + }, + "schedule": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "CRON_EXPRESSION" + ] + }, + "cronExpression": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "failureCount": { + "type": "number" + } + }, + "required": [ + "type", + "cronExpression", + "timezone" + ], + "nullable": true + }, + "handshakeConfiguration": { + "type": "object", + "properties": { + "strategy": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NONE" + ] + }, + { + "type": "string", + "enum": [ + "HEADER_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "QUERY_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "BODY_PARAM_PRESENT" + ] + } + ] + }, + "paramName": { + "type": "string" + } + }, + "required": [ + "strategy" + ], + "nullable": true + }, + "publishedVersionId": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "version": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "flowId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "trigger": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "PIECE_TRIGGER" + ] + }, + "settings": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "pattern": "^([~^])?[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "pieceType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "CUSTOM" + ] + }, + { + "type": "string", + "enum": [ + "OFFICIAL" + ] + } + ] + }, + "packageType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + { + "type": "string", + "enum": [ + "REGISTRY" + ] + } + ] + }, + "triggerName": { + "type": "string" + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "pieceName", + "pieceVersion", + "pieceType", + "packageType", + "input", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "EMPTY" + ] + }, + "settings": {} + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + } + ] + }, + "updatedBy": { + "type": "string", + "nullable": true + }, + "valid": { + "type": "boolean" + }, + "schemaVersion": { + "type": "string", + "nullable": true + }, + "state": { + "anyOf": [ + { + "type": "string", + "enum": [ + "LOCKED" + ] + }, + { + "type": "string", + "enum": [ + "DRAFT" + ] + } + ] + }, + "connectionIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "created", + "updated", + "flowId", + "displayName", + "trigger", + "valid", + "state", + "connectionIds" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "externalId", + "status", + "version" + ] + } + }, + "next": { + "description": "Cursor to the next page", + "type": "string", + "nullable": true + }, + "previous": { + "description": "Cursor to the previous page", + "type": "string", + "nullable": true + } + }, + "required": [ + "data" + ] + } + } + } + } + } + } + }, + "/v1/flows/{id}": { + "post": { + "tags": [ + "flows" + ], + "description": "Apply an operation to a flow", + "requestBody": { + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "title": "Move Action", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "MOVE_ACTION" + ] + }, + "request": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "newParentStep": { + "type": "string" + }, + "stepLocationRelativeToNewParent": { + "anyOf": [ + { + "type": "string", + "enum": [ + "AFTER" + ] + }, + { + "type": "string", + "enum": [ + "INSIDE_LOOP" + ] + }, + { + "type": "string", + "enum": [ + "INSIDE_BRANCH" + ] + } + ] + }, + "branchIndex": { + "type": "number" + } + }, + "required": [ + "name", + "newParentStep" + ] + } + }, + "required": [ + "type", + "request" + ] + }, + { + "title": "Change Status", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "CHANGE_STATUS" + ] + }, + "request": { + "type": "object", + "properties": { + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ENABLED" + ] + }, + { + "type": "string", + "enum": [ + "DISABLED" + ] + } + ] + } + }, + "required": [ + "status" + ] + } + }, + "required": [ + "type", + "request" + ] + }, + { + "title": "Lock and Publish", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "LOCK_AND_PUBLISH" + ] + }, + "request": { + "type": "object", + "properties": { + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ENABLED" + ] + }, + { + "type": "string", + "enum": [ + "DISABLED" + ] + } + ] + } + } + } + }, + "required": [ + "type", + "request" + ] + }, + { + "title": "Copy as Draft", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "USE_AS_DRAFT" + ] + }, + "request": { + "type": "object", + "properties": { + "versionId": { + "type": "string" + } + }, + "required": [ + "versionId" + ] + } + }, + "required": [ + "type", + "request" + ] + }, + { + "title": "Lock Flow", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "LOCK_FLOW" + ] + }, + "request": { + "type": "object", + "properties": {} + } + }, + "required": [ + "type", + "request" + ] + }, + { + "title": "Import Flow", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "IMPORT_FLOW" + ] + }, + "request": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "trigger": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "PIECE_TRIGGER" + ] + }, + "settings": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "pattern": "^([~^])?[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "pieceType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "CUSTOM" + ] + }, + { + "type": "string", + "enum": [ + "OFFICIAL" + ] + } + ] + }, + "packageType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + { + "type": "string", + "enum": [ + "REGISTRY" + ] + } + ] + }, + "triggerName": { + "type": "string" + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "pieceName", + "pieceVersion", + "pieceType", + "packageType", + "input", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "EMPTY" + ] + }, + "settings": {} + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + } + ] + }, + "schemaVersion": { + "type": "string", + "nullable": true + } + }, + "required": [ + "displayName", + "trigger" + ] + } + }, + "required": [ + "type", + "request" + ] + }, + { + "title": "Change Name", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "CHANGE_NAME" + ] + }, + "request": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + } + }, + "required": [ + "displayName" + ] + } + }, + "required": [ + "type", + "request" + ] + }, + { + "title": "Delete Action", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "DELETE_ACTION" + ] + }, + "request": { + "type": "object", + "properties": { + "names": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "names" + ] + } + }, + "required": [ + "type", + "request" + ] + }, + { + "title": "Update Action", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "UPDATE_ACTION" + ] + }, + "request": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "skip": { + "type": "boolean" + }, + "customLogoUrl": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "CODE" + ] + }, + "settings": { + "type": "object", + "properties": { + "sourceCode": { + "type": "object", + "properties": { + "packageJson": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "packageJson", + "code" + ] + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + }, + "errorHandlingOptions": { + "type": "object", + "properties": { + "continueOnFailure": { + "type": "object", + "properties": { + "value": { + "type": "boolean" + } + }, + "required": [ + "value" + ] + }, + "retryOnFailure": { + "type": "object", + "properties": { + "value": { + "type": "boolean" + } + }, + "required": [ + "value" + ] + } + } + } + }, + "required": [ + "sourceCode", + "input" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "skip": { + "type": "boolean" + }, + "customLogoUrl": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "LOOP_ON_ITEMS" + ] + }, + "settings": { + "type": "object", + "properties": { + "items": { + "type": "string" + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "items", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "skip": { + "type": "boolean" + }, + "customLogoUrl": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "PIECE" + ] + }, + "settings": { + "type": "object", + "properties": { + "packageType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + { + "type": "string", + "enum": [ + "REGISTRY" + ] + } + ] + }, + "pieceType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "CUSTOM" + ] + }, + { + "type": "string", + "enum": [ + "OFFICIAL" + ] + } + ] + }, + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "pattern": "^([~^])?[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "actionName": { + "type": "string" + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + }, + "errorHandlingOptions": { + "type": "object", + "properties": { + "continueOnFailure": { + "type": "object", + "properties": { + "value": { + "type": "boolean" + } + }, + "required": [ + "value" + ] + }, + "retryOnFailure": { + "type": "object", + "properties": { + "value": { + "type": "boolean" + } + }, + "required": [ + "value" + ] + } + } + } + }, + "required": [ + "packageType", + "pieceType", + "pieceName", + "pieceVersion", + "input", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "skip": { + "type": "boolean" + }, + "customLogoUrl": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "ROUTER" + ] + }, + "settings": { + "type": "object", + "properties": { + "branches": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "conditions": { + "type": "array", + "items": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "firstValue": { + "type": "string" + }, + "secondValue": { + "type": "string" + }, + "caseSensitive": { + "type": "boolean" + }, + "operator": { + "anyOf": [ + { + "type": "string", + "enum": [ + "TEXT_CONTAINS" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_DOES_NOT_CONTAIN" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_EXACTLY_MATCHES" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_DOES_NOT_EXACTLY_MATCH" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_START_WITH" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_DOES_NOT_START_WITH" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_ENDS_WITH" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_DOES_NOT_END_WITH" + ] + }, + { + "type": "string", + "enum": [ + "LIST_CONTAINS" + ] + }, + { + "type": "string", + "enum": [ + "LIST_DOES_NOT_CONTAIN" + ] + } + ] + } + }, + "required": [ + "firstValue", + "secondValue" + ] + }, + { + "type": "object", + "properties": { + "firstValue": { + "type": "string" + }, + "secondValue": { + "type": "string" + }, + "operator": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NUMBER_IS_GREATER_THAN" + ] + }, + { + "type": "string", + "enum": [ + "NUMBER_IS_LESS_THAN" + ] + }, + { + "type": "string", + "enum": [ + "NUMBER_IS_EQUAL_TO" + ] + } + ] + } + }, + "required": [ + "firstValue", + "secondValue" + ] + }, + { + "type": "object", + "properties": { + "firstValue": { + "type": "string" + }, + "secondValue": { + "type": "string" + }, + "operator": { + "anyOf": [ + { + "type": "string", + "enum": [ + "DATE_IS_BEFORE" + ] + }, + { + "type": "string", + "enum": [ + "DATE_IS_EQUAL" + ] + }, + { + "type": "string", + "enum": [ + "DATE_IS_AFTER" + ] + } + ] + } + }, + "required": [ + "firstValue", + "secondValue" + ] + }, + { + "type": "object", + "properties": { + "firstValue": { + "type": "string" + }, + "operator": { + "anyOf": [ + { + "type": "string", + "enum": [ + "EXISTS" + ] + }, + { + "type": "string", + "enum": [ + "DOES_NOT_EXIST" + ] + }, + { + "type": "string", + "enum": [ + "BOOLEAN_IS_TRUE" + ] + }, + { + "type": "string", + "enum": [ + "BOOLEAN_IS_FALSE" + ] + }, + { + "type": "string", + "enum": [ + "LIST_IS_EMPTY" + ] + }, + { + "type": "string", + "enum": [ + "LIST_IS_NOT_EMPTY" + ] + } + ] + } + }, + "required": [ + "firstValue" + ] + } + ] + } + } + }, + "branchType": { + "type": "string", + "enum": [ + "CONDITION" + ] + }, + "branchName": { + "type": "string" + } + }, + "required": [ + "conditions", + "branchType", + "branchName" + ] + }, + { + "type": "object", + "properties": { + "branchType": { + "type": "string", + "enum": [ + "FALLBACK" + ] + }, + "branchName": { + "type": "string" + } + }, + "required": [ + "branchType", + "branchName" + ] + } + ] + } + }, + "executionType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "EXECUTE_ALL_MATCH" + ] + }, + { + "type": "string", + "enum": [ + "EXECUTE_FIRST_MATCH" + ] + } + ] + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "branches", + "executionType", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + } + ] + } + }, + "required": [ + "type", + "request" + ] + }, + { + "title": "Add Action", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "ADD_ACTION" + ] + }, + "request": { + "type": "object", + "properties": { + "parentStep": { + "type": "string" + }, + "stepLocationRelativeToParent": { + "anyOf": [ + { + "type": "string", + "enum": [ + "AFTER" + ] + }, + { + "type": "string", + "enum": [ + "INSIDE_LOOP" + ] + }, + { + "type": "string", + "enum": [ + "INSIDE_BRANCH" + ] + } + ] + }, + "branchIndex": { + "type": "number" + }, + "action": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "skip": { + "type": "boolean" + }, + "customLogoUrl": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "CODE" + ] + }, + "settings": { + "type": "object", + "properties": { + "sourceCode": { + "type": "object", + "properties": { + "packageJson": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "packageJson", + "code" + ] + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + }, + "errorHandlingOptions": { + "type": "object", + "properties": { + "continueOnFailure": { + "type": "object", + "properties": { + "value": { + "type": "boolean" + } + }, + "required": [ + "value" + ] + }, + "retryOnFailure": { + "type": "object", + "properties": { + "value": { + "type": "boolean" + } + }, + "required": [ + "value" + ] + } + } + } + }, + "required": [ + "sourceCode", + "input" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "skip": { + "type": "boolean" + }, + "customLogoUrl": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "LOOP_ON_ITEMS" + ] + }, + "settings": { + "type": "object", + "properties": { + "items": { + "type": "string" + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "items", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "skip": { + "type": "boolean" + }, + "customLogoUrl": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "PIECE" + ] + }, + "settings": { + "type": "object", + "properties": { + "packageType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + { + "type": "string", + "enum": [ + "REGISTRY" + ] + } + ] + }, + "pieceType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "CUSTOM" + ] + }, + { + "type": "string", + "enum": [ + "OFFICIAL" + ] + } + ] + }, + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "pattern": "^([~^])?[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "actionName": { + "type": "string" + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + }, + "errorHandlingOptions": { + "type": "object", + "properties": { + "continueOnFailure": { + "type": "object", + "properties": { + "value": { + "type": "boolean" + } + }, + "required": [ + "value" + ] + }, + "retryOnFailure": { + "type": "object", + "properties": { + "value": { + "type": "boolean" + } + }, + "required": [ + "value" + ] + } + } + } + }, + "required": [ + "packageType", + "pieceType", + "pieceName", + "pieceVersion", + "input", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "skip": { + "type": "boolean" + }, + "customLogoUrl": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "ROUTER" + ] + }, + "settings": { + "type": "object", + "properties": { + "branches": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "conditions": { + "type": "array", + "items": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "firstValue": { + "type": "string" + }, + "secondValue": { + "type": "string" + }, + "caseSensitive": { + "type": "boolean" + }, + "operator": { + "anyOf": [ + { + "type": "string", + "enum": [ + "TEXT_CONTAINS" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_DOES_NOT_CONTAIN" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_EXACTLY_MATCHES" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_DOES_NOT_EXACTLY_MATCH" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_START_WITH" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_DOES_NOT_START_WITH" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_ENDS_WITH" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_DOES_NOT_END_WITH" + ] + }, + { + "type": "string", + "enum": [ + "LIST_CONTAINS" + ] + }, + { + "type": "string", + "enum": [ + "LIST_DOES_NOT_CONTAIN" + ] + } + ] + } + }, + "required": [ + "firstValue", + "secondValue" + ] + }, + { + "type": "object", + "properties": { + "firstValue": { + "type": "string" + }, + "secondValue": { + "type": "string" + }, + "operator": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NUMBER_IS_GREATER_THAN" + ] + }, + { + "type": "string", + "enum": [ + "NUMBER_IS_LESS_THAN" + ] + }, + { + "type": "string", + "enum": [ + "NUMBER_IS_EQUAL_TO" + ] + } + ] + } + }, + "required": [ + "firstValue", + "secondValue" + ] + }, + { + "type": "object", + "properties": { + "firstValue": { + "type": "string" + }, + "secondValue": { + "type": "string" + }, + "operator": { + "anyOf": [ + { + "type": "string", + "enum": [ + "DATE_IS_BEFORE" + ] + }, + { + "type": "string", + "enum": [ + "DATE_IS_EQUAL" + ] + }, + { + "type": "string", + "enum": [ + "DATE_IS_AFTER" + ] + } + ] + } + }, + "required": [ + "firstValue", + "secondValue" + ] + }, + { + "type": "object", + "properties": { + "firstValue": { + "type": "string" + }, + "operator": { + "anyOf": [ + { + "type": "string", + "enum": [ + "EXISTS" + ] + }, + { + "type": "string", + "enum": [ + "DOES_NOT_EXIST" + ] + }, + { + "type": "string", + "enum": [ + "BOOLEAN_IS_TRUE" + ] + }, + { + "type": "string", + "enum": [ + "BOOLEAN_IS_FALSE" + ] + }, + { + "type": "string", + "enum": [ + "LIST_IS_EMPTY" + ] + }, + { + "type": "string", + "enum": [ + "LIST_IS_NOT_EMPTY" + ] + } + ] + } + }, + "required": [ + "firstValue" + ] + } + ] + } + } + }, + "branchType": { + "type": "string", + "enum": [ + "CONDITION" + ] + }, + "branchName": { + "type": "string" + } + }, + "required": [ + "conditions", + "branchType", + "branchName" + ] + }, + { + "type": "object", + "properties": { + "branchType": { + "type": "string", + "enum": [ + "FALLBACK" + ] + }, + "branchName": { + "type": "string" + } + }, + "required": [ + "branchType", + "branchName" + ] + } + ] + } + }, + "executionType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "EXECUTE_ALL_MATCH" + ] + }, + { + "type": "string", + "enum": [ + "EXECUTE_FIRST_MATCH" + ] + } + ] + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "branches", + "executionType", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + } + ] + } + }, + "required": [ + "parentStep", + "action" + ] + } + }, + "required": [ + "type", + "request" + ] + }, + { + "title": "Update Trigger", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "UPDATE_TRIGGER" + ] + }, + "request": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "EMPTY" + ] + }, + "settings": {} + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "PIECE_TRIGGER" + ] + }, + "settings": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "pattern": "^([~^])?[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "pieceType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "CUSTOM" + ] + }, + { + "type": "string", + "enum": [ + "OFFICIAL" + ] + } + ] + }, + "packageType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + { + "type": "string", + "enum": [ + "REGISTRY" + ] + } + ] + }, + "triggerName": { + "type": "string" + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "pieceName", + "pieceVersion", + "pieceType", + "packageType", + "input", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + } + ] + } + }, + "required": [ + "type", + "request" + ] + }, + { + "title": "Change Folder", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "CHANGE_FOLDER" + ] + }, + "request": { + "type": "object", + "properties": { + "folderId": { + "type": "string", + "nullable": true + } + } + } + }, + "required": [ + "type", + "request" + ] + }, + { + "title": "Duplicate Action", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "DUPLICATE_ACTION" + ] + }, + "request": { + "type": "object", + "properties": { + "stepName": { + "type": "string" + } + }, + "required": [ + "stepName" + ] + } + }, + "required": [ + "type", + "request" + ] + }, + { + "title": "Delete Branch", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "DELETE_BRANCH" + ] + }, + "request": { + "type": "object", + "properties": { + "branchIndex": { + "type": "number" + }, + "stepName": { + "type": "string" + } + }, + "required": [ + "branchIndex", + "stepName" + ] + } + }, + "required": [ + "type", + "request" + ] + }, + { + "title": "Add Branch", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "ADD_BRANCH" + ] + }, + "request": { + "type": "object", + "properties": { + "branchIndex": { + "type": "number" + }, + "stepName": { + "type": "string" + }, + "conditions": { + "type": "array", + "items": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "firstValue": { + "type": "string" + }, + "secondValue": { + "type": "string" + }, + "caseSensitive": { + "type": "boolean" + }, + "operator": { + "anyOf": [ + { + "type": "string", + "enum": [ + "TEXT_CONTAINS" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_DOES_NOT_CONTAIN" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_EXACTLY_MATCHES" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_DOES_NOT_EXACTLY_MATCH" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_START_WITH" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_DOES_NOT_START_WITH" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_ENDS_WITH" + ] + }, + { + "type": "string", + "enum": [ + "TEXT_DOES_NOT_END_WITH" + ] + }, + { + "type": "string", + "enum": [ + "LIST_CONTAINS" + ] + }, + { + "type": "string", + "enum": [ + "LIST_DOES_NOT_CONTAIN" + ] + } + ] + } + }, + "required": [ + "firstValue", + "secondValue" + ] + }, + { + "type": "object", + "properties": { + "firstValue": { + "type": "string" + }, + "secondValue": { + "type": "string" + }, + "operator": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NUMBER_IS_GREATER_THAN" + ] + }, + { + "type": "string", + "enum": [ + "NUMBER_IS_LESS_THAN" + ] + }, + { + "type": "string", + "enum": [ + "NUMBER_IS_EQUAL_TO" + ] + } + ] + } + }, + "required": [ + "firstValue", + "secondValue" + ] + }, + { + "type": "object", + "properties": { + "firstValue": { + "type": "string" + }, + "secondValue": { + "type": "string" + }, + "operator": { + "anyOf": [ + { + "type": "string", + "enum": [ + "DATE_IS_BEFORE" + ] + }, + { + "type": "string", + "enum": [ + "DATE_IS_EQUAL" + ] + }, + { + "type": "string", + "enum": [ + "DATE_IS_AFTER" + ] + } + ] + } + }, + "required": [ + "firstValue", + "secondValue" + ] + }, + { + "type": "object", + "properties": { + "firstValue": { + "type": "string" + }, + "operator": { + "anyOf": [ + { + "type": "string", + "enum": [ + "EXISTS" + ] + }, + { + "type": "string", + "enum": [ + "DOES_NOT_EXIST" + ] + }, + { + "type": "string", + "enum": [ + "BOOLEAN_IS_TRUE" + ] + }, + { + "type": "string", + "enum": [ + "BOOLEAN_IS_FALSE" + ] + }, + { + "type": "string", + "enum": [ + "LIST_IS_EMPTY" + ] + }, + { + "type": "string", + "enum": [ + "LIST_IS_NOT_EMPTY" + ] + } + ] + } + }, + "required": [ + "firstValue" + ] + } + ] + } + } + }, + "branchName": { + "type": "string" + } + }, + "required": [ + "branchIndex", + "stepName", + "branchName" + ] + } + }, + "required": [ + "type", + "request" + ] + }, + { + "title": "Duplicate Branch", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "DUPLICATE_BRANCH" + ] + }, + "request": { + "type": "object", + "properties": { + "branchIndex": { + "type": "number" + }, + "stepName": { + "type": "string" + } + }, + "required": [ + "branchIndex", + "stepName" + ] + } + }, + "required": [ + "type", + "request" + ] + }, + { + "title": "Skip Action", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "SET_SKIP_ACTION" + ] + }, + "request": { + "type": "object", + "properties": { + "names": { + "type": "array", + "items": { + "type": "string" + } + }, + "skip": { + "type": "boolean" + } + }, + "required": [ + "names", + "skip" + ] + } + }, + "required": [ + "type", + "request" + ] + }, + { + "title": "Update Metadata", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "UPDATE_METADATA" + ] + }, + "request": { + "type": "object", + "properties": { + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + } + } + } + }, + "required": [ + "type", + "request" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "MOVE_BRANCH" + ] + }, + "request": { + "type": "object", + "properties": { + "sourceBranchIndex": { + "type": "number" + }, + "targetBranchIndex": { + "type": "number" + }, + "stepName": { + "type": "string" + } + }, + "required": [ + "sourceBranchIndex", + "targetBranchIndex", + "stepName" + ] + } + }, + "required": [ + "type", + "request" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "SAVE_SAMPLE_DATA" + ] + }, + "request": { + "type": "object", + "properties": { + "stepName": { + "type": "string" + }, + "payload": {}, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "INPUT" + ] + }, + { + "type": "string", + "enum": [ + "OUTPUT" + ] + } + ] + } + }, + "required": [ + "stepName", + "payload", + "type" + ] + } + }, + "required": [ + "type", + "request" + ] + } + ] + } + } + } + }, + "parameters": [ + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + }, + "get": { + "tags": [ + "flows" + ], + "description": "Get a flow by id", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "versionId", + "required": false + }, + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "folderId": { + "type": "string", + "nullable": true + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ENABLED" + ] + }, + { + "type": "string", + "enum": [ + "DISABLED" + ] + } + ] + }, + "schedule": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "CRON_EXPRESSION" + ] + }, + "cronExpression": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "failureCount": { + "type": "number" + } + }, + "required": [ + "type", + "cronExpression", + "timezone" + ], + "nullable": true + }, + "handshakeConfiguration": { + "type": "object", + "properties": { + "strategy": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NONE" + ] + }, + { + "type": "string", + "enum": [ + "HEADER_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "QUERY_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "BODY_PARAM_PRESENT" + ] + } + ] + }, + "paramName": { + "type": "string" + } + }, + "required": [ + "strategy" + ], + "nullable": true + }, + "publishedVersionId": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "version": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "flowId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "trigger": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "PIECE_TRIGGER" + ] + }, + "settings": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "pattern": "^([~^])?[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "pieceType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "CUSTOM" + ] + }, + { + "type": "string", + "enum": [ + "OFFICIAL" + ] + } + ] + }, + "packageType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + { + "type": "string", + "enum": [ + "REGISTRY" + ] + } + ] + }, + "triggerName": { + "type": "string" + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "pieceName", + "pieceVersion", + "pieceType", + "packageType", + "input", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "EMPTY" + ] + }, + "settings": {} + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + } + ] + }, + "updatedBy": { + "type": "string", + "nullable": true + }, + "valid": { + "type": "boolean" + }, + "schemaVersion": { + "type": "string", + "nullable": true + }, + "state": { + "anyOf": [ + { + "type": "string", + "enum": [ + "LOCKED" + ] + }, + { + "type": "string", + "enum": [ + "DRAFT" + ] + } + ] + }, + "connectionIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "created", + "updated", + "flowId", + "displayName", + "trigger", + "valid", + "state", + "connectionIds" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "externalId", + "status", + "version" + ] + } + } + } + } + } + }, + "delete": { + "tags": [ + "flows" + ], + "description": "Delete a flow", + "parameters": [ + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "204": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "not": {} + } + } + } + } + } + } + }, + "/v1/flows/{id}/template": { + "get": { + "tags": [ + "flows" + ], + "description": "Export flow as template", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "versionId", + "required": false + }, + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "pieces": { + "type": "array", + "items": { + "type": "string" + } + }, + "schemaVersion": { + "type": "string", + "nullable": true + }, + "blogUrl": { + "type": "string" + }, + "template": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "trigger": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "PIECE_TRIGGER" + ] + }, + "settings": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "pattern": "^([~^])?[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "pieceType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "CUSTOM" + ] + }, + { + "type": "string", + "enum": [ + "OFFICIAL" + ] + } + ] + }, + "packageType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + { + "type": "string", + "enum": [ + "REGISTRY" + ] + } + ] + }, + "triggerName": { + "type": "string" + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "pieceName", + "pieceVersion", + "pieceType", + "packageType", + "input", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "EMPTY" + ] + }, + "settings": {} + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + } + ] + }, + "valid": { + "type": "boolean" + }, + "schemaVersion": { + "type": "string", + "nullable": true + }, + "connectionIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "displayName", + "trigger", + "valid", + "connectionIds" + ] + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + } + }, + "required": [ + "created", + "updated", + "name", + "description", + "tags", + "pieces", + "template" + ] + } + } + } + } + } + } + }, + "/v1/sample-data": { + "get": { + "tags": [ + "sample-data" + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "flowId", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "flowVersionId", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "stepName", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": true + }, + { + "schema": { + "anyOf": [ + { + "type": "string", + "enum": [ + "INPUT" + ] + }, + { + "type": "string", + "enum": [ + "OUTPUT" + ] + } + ] + }, + "in": "query", + "name": "type", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + } + }, + "/v1/flow-runs": { + "get": { + "tags": [ + "flow-runs" + ], + "description": "List Flow Runs", + "parameters": [ + { + "schema": { + "type": "array", + "items": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + } + }, + "in": "query", + "name": "flowId", + "required": false + }, + { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query", + "name": "tags", + "required": false + }, + { + "schema": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string", + "enum": [ + "FAILED" + ] + }, + { + "type": "string", + "enum": [ + "QUOTA_EXCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "INTERNAL_ERROR" + ] + }, + { + "type": "string", + "enum": [ + "PAUSED" + ] + }, + { + "type": "string", + "enum": [ + "RUNNING" + ] + }, + { + "type": "string", + "enum": [ + "STOPPED" + ] + }, + { + "type": "string", + "enum": [ + "SUCCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "MEMORY_LIMIT_EXCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "TIMEOUT" + ] + } + ] + } + }, + "in": "query", + "name": "status", + "required": false + }, + { + "schema": { + "type": "number" + }, + "in": "query", + "name": "limit", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursor", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "createdAfter", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "createdBefore", + "required": false + }, + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "failedStepName", + "required": false + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "flowId": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "flowVersionId": { + "type": "string" + }, + "flowDisplayName": { + "type": "string" + }, + "logsFileId": { + "type": "string", + "nullable": true + }, + "tasks": { + "type": "number" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "FAILED" + ] + }, + { + "type": "string", + "enum": [ + "QUOTA_EXCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "INTERNAL_ERROR" + ] + }, + { + "type": "string", + "enum": [ + "PAUSED" + ] + }, + { + "type": "string", + "enum": [ + "RUNNING" + ] + }, + { + "type": "string", + "enum": [ + "STOPPED" + ] + }, + { + "type": "string", + "enum": [ + "SUCCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "MEMORY_LIMIT_EXCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "TIMEOUT" + ] + } + ] + }, + "duration": { + "type": "number" + }, + "startTime": { + "type": "string" + }, + "finishTime": { + "type": "string" + }, + "environment": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PRODUCTION" + ] + }, + { + "type": "string", + "enum": [ + "TESTING" + ] + } + ] + }, + "failedStepName": { + "type": "string" + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "flowId", + "flowVersionId", + "flowDisplayName", + "status", + "startTime", + "environment" + ] + } + }, + "next": { + "description": "Cursor to the next page", + "type": "string", + "nullable": true + }, + "previous": { + "description": "Cursor to the previous page", + "type": "string", + "nullable": true + } + }, + "required": [ + "data" + ] + } + } + } + } + } + } + }, + "/v1/flow-runs/{id}": { + "get": { + "tags": [ + "flow-runs" + ], + "description": "Get Flow Run", + "parameters": [ + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "flowId": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "flowVersionId": { + "type": "string" + }, + "flowDisplayName": { + "type": "string" + }, + "logsFileId": { + "type": "string", + "nullable": true + }, + "tasks": { + "type": "number" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "FAILED" + ] + }, + { + "type": "string", + "enum": [ + "QUOTA_EXCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "INTERNAL_ERROR" + ] + }, + { + "type": "string", + "enum": [ + "PAUSED" + ] + }, + { + "type": "string", + "enum": [ + "RUNNING" + ] + }, + { + "type": "string", + "enum": [ + "STOPPED" + ] + }, + { + "type": "string", + "enum": [ + "SUCCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "MEMORY_LIMIT_EXCEEDED" + ] + }, + { + "type": "string", + "enum": [ + "TIMEOUT" + ] + } + ] + }, + "duration": { + "type": "number" + }, + "startTime": { + "type": "string" + }, + "finishTime": { + "type": "string" + }, + "environment": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PRODUCTION" + ] + }, + { + "type": "string", + "enum": [ + "TESTING" + ] + } + ] + }, + "steps": { + "type": "object", + "additionalProperties": {} + }, + "failedStepName": { + "type": "string" + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "flowId", + "flowVersionId", + "flowDisplayName", + "status", + "startTime", + "environment", + "steps" + ] + } + } + } + } + } + } + }, + "/v1/app-connections": { + "post": { + "tags": [ + "app-connections" + ], + "description": "Upsert an app connection based on the app name", + "requestBody": { + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "title": "Secret Text", + "description": "Secret Text", + "type": "object", + "properties": { + "externalId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "pieceName": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": {} + }, + "type": { + "type": "string", + "enum": [ + "SECRET_TEXT" + ] + }, + "value": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "SECRET_TEXT" + ] + }, + "secret_text": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "type", + "secret_text" + ] + } + }, + "required": [ + "externalId", + "displayName", + "pieceName", + "projectId", + "type", + "value" + ] + }, + { + "title": "OAuth2", + "description": "OAuth2", + "type": "object", + "properties": { + "externalId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "pieceName": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": {} + }, + "type": { + "type": "string", + "enum": [ + "OAUTH2" + ] + }, + "value": { + "type": "object", + "properties": { + "client_id": { + "minLength": 1, + "type": "string" + }, + "code": { + "minLength": 1, + "type": "string" + }, + "code_challenge": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "authorization_method": { + "anyOf": [ + { + "type": "string", + "enum": [ + "HEADER" + ] + }, + { + "type": "string", + "enum": [ + "BODY" + ] + } + ] + }, + "client_secret": { + "minLength": 1, + "type": "string" + }, + "grant_type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "authorization_code" + ] + }, + { + "type": "string", + "enum": [ + "client_credentials" + ] + } + ] + }, + "props": { + "type": "object", + "additionalProperties": {} + }, + "redirect_url": { + "minLength": 1, + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "OAUTH2" + ] + } + }, + "required": [ + "client_id", + "code", + "scope", + "client_secret", + "redirect_url", + "type" + ] + } + }, + "required": [ + "externalId", + "displayName", + "pieceName", + "projectId", + "type", + "value" + ] + }, + { + "title": "Cloud OAuth2", + "description": "Cloud OAuth2", + "type": "object", + "properties": { + "externalId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "pieceName": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": {} + }, + "type": { + "type": "string", + "enum": [ + "CLOUD_OAUTH2" + ] + }, + "value": { + "type": "object", + "properties": { + "client_id": { + "minLength": 1, + "type": "string" + }, + "code": { + "minLength": 1, + "type": "string" + }, + "code_challenge": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "authorization_method": { + "anyOf": [ + { + "type": "string", + "enum": [ + "HEADER" + ] + }, + { + "type": "string", + "enum": [ + "BODY" + ] + } + ] + }, + "props": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "type": { + "type": "string", + "enum": [ + "CLOUD_OAUTH2" + ] + } + }, + "required": [ + "client_id", + "code", + "scope", + "type" + ] + } + }, + "required": [ + "externalId", + "displayName", + "pieceName", + "projectId", + "type", + "value" + ] + }, + { + "title": "Platform OAuth2", + "description": "Platform OAuth2", + "type": "object", + "properties": { + "externalId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "pieceName": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": {} + }, + "type": { + "type": "string", + "enum": [ + "PLATFORM_OAUTH2" + ] + }, + "value": { + "type": "object", + "properties": { + "client_id": { + "minLength": 1, + "type": "string" + }, + "code": { + "minLength": 1, + "type": "string" + }, + "code_challenge": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "authorization_method": { + "anyOf": [ + { + "type": "string", + "enum": [ + "HEADER" + ] + }, + { + "type": "string", + "enum": [ + "BODY" + ] + } + ] + }, + "props": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "type": { + "type": "string", + "enum": [ + "PLATFORM_OAUTH2" + ] + }, + "redirect_url": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "client_id", + "code", + "scope", + "type", + "redirect_url" + ] + } + }, + "required": [ + "externalId", + "displayName", + "pieceName", + "projectId", + "type", + "value" + ] + }, + { + "title": "Basic Auth", + "description": "Basic Auth", + "type": "object", + "properties": { + "externalId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "pieceName": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": {} + }, + "type": { + "type": "string", + "enum": [ + "BASIC_AUTH" + ] + }, + "value": { + "type": "object", + "properties": { + "username": { + "minLength": 1, + "type": "string" + }, + "password": { + "minLength": 1, + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "BASIC_AUTH" + ] + } + }, + "required": [ + "username", + "password", + "type" + ] + } + }, + "required": [ + "externalId", + "displayName", + "pieceName", + "projectId", + "type", + "value" + ] + }, + { + "title": "Custom Auth", + "description": "Custom Auth", + "type": "object", + "properties": { + "externalId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "pieceName": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": {} + }, + "type": { + "type": "string", + "enum": [ + "CUSTOM_AUTH" + ] + }, + "value": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "CUSTOM_AUTH" + ] + }, + "props": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "type", + "props" + ] + } + }, + "required": [ + "externalId", + "displayName", + "pieceName", + "projectId", + "type", + "value" + ] + }, + { + "title": "No Auth", + "description": "No Auth", + "type": "object", + "properties": { + "externalId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "pieceName": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": {} + }, + "type": { + "type": "string", + "enum": [ + "NO_AUTH" + ] + }, + "value": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "NO_AUTH" + ] + } + }, + "required": [ + "type" + ] + } + }, + "required": [ + "externalId", + "displayName", + "pieceName", + "projectId", + "type", + "value" + ] + } + ] + } + } + } + }, + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + }, + "get": { + "tags": [ + "app-connections" + ], + "description": "List app connections", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursor", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": true + }, + { + "schema": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PROJECT" + ] + }, + { + "type": "string", + "enum": [ + "PLATFORM" + ] + } + ] + }, + "in": "query", + "name": "scope", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "pieceName", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "displayName", + "required": false + }, + { + "schema": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "MISSING" + ] + }, + { + "type": "string", + "enum": [ + "ERROR" + ] + } + ] + } + }, + "in": "query", + "name": "status", + "required": false + }, + { + "schema": { + "type": "number" + }, + "in": "query", + "name": "limit", + "required": false + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "description": "App connection is a connection to an external app.", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "PLATFORM_OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "CLOUD_OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "SECRET_TEXT" + ] + }, + { + "type": "string", + "enum": [ + "BASIC_AUTH" + ] + }, + { + "type": "string", + "enum": [ + "CUSTOM_AUTH" + ] + }, + { + "type": "string", + "enum": [ + "NO_AUTH" + ] + } + ] + }, + "pieceName": { + "type": "string" + }, + "projectIds": { + "type": "array", + "items": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + } + }, + "platformId": { + "type": "string", + "nullable": true + }, + "scope": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PROJECT" + ] + }, + { + "type": "string", + "enum": [ + "PLATFORM" + ] + } + ] + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "MISSING" + ] + }, + { + "type": "string", + "enum": [ + "ERROR" + ] + } + ] + }, + "ownerId": { + "type": "string", + "nullable": true + }, + "owner": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "INACTIVE" + ] + } + ] + }, + "externalId": { + "type": "string", + "nullable": true + }, + "lastChangelogDismissed": { + "type": "string", + "nullable": true + }, + "platformId": { + "type": "string", + "nullable": true + }, + "platformRole": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ADMIN" + ] + }, + { + "type": "string", + "enum": [ + "MEMBER" + ] + } + ] + }, + "lastName": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "email", + "firstName", + "status", + "platformRole", + "lastName", + "created", + "updated" + ], + "nullable": true + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "flowIds": { + "type": "array", + "items": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "nullable": true + } + }, + "required": [ + "id", + "created", + "updated", + "externalId", + "displayName", + "type", + "pieceName", + "projectIds", + "scope", + "status" + ] + } + }, + "next": { + "description": "Cursor to the next page", + "type": "string", + "nullable": true + }, + "previous": { + "description": "Cursor to the previous page", + "type": "string", + "nullable": true + } + }, + "required": [ + "data" + ] + } + } + } + } + } + } + }, + "/v1/app-connections/{id}": { + "post": { + "tags": [ + "app-connections" + ], + "description": "Update an app connection value", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "minLength": 1, + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "displayName" + ] + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + }, + "delete": { + "tags": [ + "app-connections" + ], + "description": "Delete an app connection", + "parameters": [ + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "204": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "not": {} + } + } + } + } + } + } + }, + "/v1/app-connections/owners": { + "get": { + "tags": [ + "app-connections" + ], + "description": "List app connection owners", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + } + }, + "required": [ + "firstName", + "lastName", + "email" + ] + } + }, + "next": { + "description": "Cursor to the next page", + "type": "string", + "nullable": true + }, + "previous": { + "description": "Cursor to the previous page", + "type": "string", + "nullable": true + } + }, + "required": [ + "data" + ] + } + } + } + } + } + } + }, + "/v1/app-connections/replace": { + "post": { + "tags": [ + "app-connections" + ], + "description": "Replace app connections", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sourceAppConnectionId": { + "type": "string" + }, + "targetAppConnectionId": { + "type": "string" + }, + "projectId": { + "type": "string" + } + }, + "required": [ + "sourceAppConnectionId", + "targetAppConnectionId", + "projectId" + ] + } + } + }, + "required": true + }, + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "204": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "not": {} + } + } + } + } + } + } + }, + "/v1/platforms/{id}": { + "get": { + "tags": [ + "platforms" + ], + "description": "Get a platform by id", + "parameters": [ + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "federatedAuthProviders": { + "type": "object", + "properties": { + "google": { + "type": "object", + "properties": { + "clientId": { + "type": "string" + } + }, + "required": [ + "clientId" + ], + "nullable": true + }, + "github": { + "type": "object", + "properties": { + "clientId": { + "type": "string" + } + }, + "required": [ + "clientId" + ], + "nullable": true + }, + "saml": { + "type": "object", + "properties": {}, + "nullable": true + } + }, + "nullable": true + }, + "copilotSettings": { + "type": "object", + "properties": { + "providers": { + "type": "object", + "properties": { + "openai": { + "type": "object", + "properties": {} + }, + "azureOpenai": { + "type": "object", + "properties": {} + } + } + } + }, + "required": [ + "providers" + ] + }, + "smtp": { + "type": "object", + "properties": {}, + "nullable": true + }, + "plan": { + "type": "object", + "properties": { + "includedTasks": { + "type": "number" + }, + "includedAiCredits": { + "type": "number" + }, + "tasksLimit": { + "type": "number" + }, + "aiCreditsLimit": { + "type": "number" + }, + "environmentsEnabled": { + "type": "boolean" + }, + "analyticsEnabled": { + "type": "boolean" + }, + "showPoweredBy": { + "type": "boolean" + }, + "auditLogEnabled": { + "type": "boolean" + }, + "embeddingEnabled": { + "type": "boolean" + }, + "managePiecesEnabled": { + "type": "boolean" + }, + "manageTemplatesEnabled": { + "type": "boolean" + }, + "customAppearanceEnabled": { + "type": "boolean" + }, + "manageProjectsEnabled": { + "type": "boolean" + }, + "projectRolesEnabled": { + "type": "boolean" + }, + "customDomainsEnabled": { + "type": "boolean" + }, + "globalConnectionsEnabled": { + "type": "boolean" + }, + "customRolesEnabled": { + "type": "boolean" + }, + "apiKeysEnabled": { + "type": "boolean" + }, + "tablesEnabled": { + "type": "boolean" + }, + "todosEnabled": { + "type": "boolean" + }, + "alertsEnabled": { + "type": "boolean" + }, + "ssoEnabled": { + "type": "boolean" + }, + "licenseKey": { + "type": "string" + }, + "licenseExpiresAt": { + "type": "string" + }, + "stripeCustomerId": { + "type": "string" + }, + "stripeSubscriptionId": { + "type": "string" + }, + "stripeSubscriptionStatus": { + "type": "string" + }, + "userSeatsLimit": { + "type": "number" + }, + "projectsLimit": { + "type": "number" + }, + "tablesLimit": { + "type": "number" + }, + "mcpLimit": { + "type": "number" + }, + "activeFlowsLimit": { + "type": "number" + }, + "agentsLimit": { + "type": "number" + } + }, + "required": [ + "includedTasks", + "includedAiCredits", + "environmentsEnabled", + "analyticsEnabled", + "showPoweredBy", + "auditLogEnabled", + "embeddingEnabled", + "managePiecesEnabled", + "manageTemplatesEnabled", + "customAppearanceEnabled", + "manageProjectsEnabled", + "projectRolesEnabled", + "customDomainsEnabled", + "globalConnectionsEnabled", + "customRolesEnabled", + "apiKeysEnabled", + "tablesEnabled", + "todosEnabled", + "alertsEnabled", + "ssoEnabled" + ] + }, + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "ownerId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "name": { + "type": "string" + }, + "primaryColor": { + "type": "string" + }, + "logoIconUrl": { + "type": "string" + }, + "fullLogoUrl": { + "type": "string" + }, + "favIconUrl": { + "type": "string" + }, + "filteredPieceNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "filteredPieceBehavior": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ALLOWED" + ] + }, + { + "type": "string", + "enum": [ + "BLOCKED" + ] + } + ] + }, + "cloudAuthEnabled": { + "type": "boolean" + }, + "enforceAllowedAuthDomains": { + "type": "boolean" + }, + "allowedAuthDomains": { + "type": "array", + "items": { + "type": "string" + } + }, + "emailAuthEnabled": { + "type": "boolean" + }, + "pinnedPieces": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "plan", + "id", + "created", + "updated", + "ownerId", + "name", + "primaryColor", + "logoIconUrl", + "fullLogoUrl", + "favIconUrl", + "filteredPieceNames", + "filteredPieceBehavior", + "cloudAuthEnabled", + "enforceAllowedAuthDomains", + "allowedAuthDomains", + "emailAuthEnabled", + "pinnedPieces" + ] + } + } + } + } + } + } + }, + "/v1/mcp-servers": { + "post": { + "tags": [ + "mcp" + ], + "description": "Create a new MCP server", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "projectId": { + "type": "string" + } + }, + "required": [ + "name", + "projectId" + ] + } + } + }, + "required": true + }, + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "name": { + "type": "string" + }, + "projectId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "token": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "agentId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "tools": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PIECE" + ] + }, + { + "type": "string", + "enum": [ + "FLOW" + ] + } + ] + }, + "mcpId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "pieceMetadata": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "type": "string" + }, + "actionNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "logoUrl": { + "type": "string" + }, + "connectionExternalId": { + "type": "string" + } + }, + "required": [ + "pieceName", + "pieceVersion", + "actionNames", + "logoUrl" + ] + }, + "flowId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "flow": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "folderId": { + "type": "string", + "nullable": true + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ENABLED" + ] + }, + { + "type": "string", + "enum": [ + "DISABLED" + ] + } + ] + }, + "schedule": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "CRON_EXPRESSION" + ] + }, + "cronExpression": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "failureCount": { + "type": "number" + } + }, + "required": [ + "type", + "cronExpression", + "timezone" + ], + "nullable": true + }, + "handshakeConfiguration": { + "type": "object", + "properties": { + "strategy": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NONE" + ] + }, + { + "type": "string", + "enum": [ + "HEADER_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "QUERY_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "BODY_PARAM_PRESENT" + ] + } + ] + }, + "paramName": { + "type": "string" + } + }, + "required": [ + "strategy" + ], + "nullable": true + }, + "publishedVersionId": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "version": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "flowId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "trigger": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "PIECE_TRIGGER" + ] + }, + "settings": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "pattern": "^([~^])?[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "pieceType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "CUSTOM" + ] + }, + { + "type": "string", + "enum": [ + "OFFICIAL" + ] + } + ] + }, + "packageType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + { + "type": "string", + "enum": [ + "REGISTRY" + ] + } + ] + }, + "triggerName": { + "type": "string" + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "pieceName", + "pieceVersion", + "pieceType", + "packageType", + "input", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "EMPTY" + ] + }, + "settings": {} + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + } + ] + }, + "updatedBy": { + "type": "string", + "nullable": true + }, + "valid": { + "type": "boolean" + }, + "schemaVersion": { + "type": "string", + "nullable": true + }, + "state": { + "anyOf": [ + { + "type": "string", + "enum": [ + "LOCKED" + ] + }, + { + "type": "string", + "enum": [ + "DRAFT" + ] + } + ] + }, + "connectionIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "created", + "updated", + "flowId", + "displayName", + "trigger", + "valid", + "state", + "connectionIds" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "externalId", + "status", + "version" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "type", + "mcpId" + ] + } + } + }, + "required": [ + "id", + "created", + "updated", + "name", + "projectId", + "token", + "tools" + ], + "nullable": true + } + } + } + } + } + }, + "get": { + "tags": [ + "mcp" + ], + "description": "List MCP servers", + "parameters": [ + { + "schema": { + "type": "number" + }, + "in": "query", + "name": "limit", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursor", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "name", + "required": false + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "name": { + "type": "string" + }, + "projectId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "token": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "agentId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "tools": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PIECE" + ] + }, + { + "type": "string", + "enum": [ + "FLOW" + ] + } + ] + }, + "mcpId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "pieceMetadata": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "type": "string" + }, + "actionNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "logoUrl": { + "type": "string" + }, + "connectionExternalId": { + "type": "string" + } + }, + "required": [ + "pieceName", + "pieceVersion", + "actionNames", + "logoUrl" + ] + }, + "flowId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "flow": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "folderId": { + "type": "string", + "nullable": true + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ENABLED" + ] + }, + { + "type": "string", + "enum": [ + "DISABLED" + ] + } + ] + }, + "schedule": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "CRON_EXPRESSION" + ] + }, + "cronExpression": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "failureCount": { + "type": "number" + } + }, + "required": [ + "type", + "cronExpression", + "timezone" + ], + "nullable": true + }, + "handshakeConfiguration": { + "type": "object", + "properties": { + "strategy": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NONE" + ] + }, + { + "type": "string", + "enum": [ + "HEADER_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "QUERY_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "BODY_PARAM_PRESENT" + ] + } + ] + }, + "paramName": { + "type": "string" + } + }, + "required": [ + "strategy" + ], + "nullable": true + }, + "publishedVersionId": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "version": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "flowId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "trigger": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "PIECE_TRIGGER" + ] + }, + "settings": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "pattern": "^([~^])?[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "pieceType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "CUSTOM" + ] + }, + { + "type": "string", + "enum": [ + "OFFICIAL" + ] + } + ] + }, + "packageType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + { + "type": "string", + "enum": [ + "REGISTRY" + ] + } + ] + }, + "triggerName": { + "type": "string" + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "pieceName", + "pieceVersion", + "pieceType", + "packageType", + "input", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "EMPTY" + ] + }, + "settings": {} + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + } + ] + }, + "updatedBy": { + "type": "string", + "nullable": true + }, + "valid": { + "type": "boolean" + }, + "schemaVersion": { + "type": "string", + "nullable": true + }, + "state": { + "anyOf": [ + { + "type": "string", + "enum": [ + "LOCKED" + ] + }, + { + "type": "string", + "enum": [ + "DRAFT" + ] + } + ] + }, + "connectionIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "created", + "updated", + "flowId", + "displayName", + "trigger", + "valid", + "state", + "connectionIds" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "externalId", + "status", + "version" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "type", + "mcpId" + ] + } + } + }, + "required": [ + "id", + "created", + "updated", + "name", + "projectId", + "token", + "tools" + ] + } + }, + "next": { + "description": "Cursor to the next page", + "type": "string", + "nullable": true + }, + "previous": { + "description": "Cursor to the previous page", + "type": "string", + "nullable": true + } + }, + "required": [ + "data" + ] + } + } + } + } + } + } + }, + "/v1/mcp-servers/{id}": { + "get": { + "tags": [ + "mcp" + ], + "description": "Get an MCP server by ID", + "parameters": [ + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "name": { + "type": "string" + }, + "projectId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "token": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "agentId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "tools": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PIECE" + ] + }, + { + "type": "string", + "enum": [ + "FLOW" + ] + } + ] + }, + "mcpId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "pieceMetadata": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "type": "string" + }, + "actionNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "logoUrl": { + "type": "string" + }, + "connectionExternalId": { + "type": "string" + } + }, + "required": [ + "pieceName", + "pieceVersion", + "actionNames", + "logoUrl" + ] + }, + "flowId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "flow": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "folderId": { + "type": "string", + "nullable": true + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ENABLED" + ] + }, + { + "type": "string", + "enum": [ + "DISABLED" + ] + } + ] + }, + "schedule": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "CRON_EXPRESSION" + ] + }, + "cronExpression": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "failureCount": { + "type": "number" + } + }, + "required": [ + "type", + "cronExpression", + "timezone" + ], + "nullable": true + }, + "handshakeConfiguration": { + "type": "object", + "properties": { + "strategy": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NONE" + ] + }, + { + "type": "string", + "enum": [ + "HEADER_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "QUERY_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "BODY_PARAM_PRESENT" + ] + } + ] + }, + "paramName": { + "type": "string" + } + }, + "required": [ + "strategy" + ], + "nullable": true + }, + "publishedVersionId": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "version": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "flowId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "trigger": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "PIECE_TRIGGER" + ] + }, + "settings": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "pattern": "^([~^])?[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "pieceType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "CUSTOM" + ] + }, + { + "type": "string", + "enum": [ + "OFFICIAL" + ] + } + ] + }, + "packageType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + { + "type": "string", + "enum": [ + "REGISTRY" + ] + } + ] + }, + "triggerName": { + "type": "string" + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "pieceName", + "pieceVersion", + "pieceType", + "packageType", + "input", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "EMPTY" + ] + }, + "settings": {} + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + } + ] + }, + "updatedBy": { + "type": "string", + "nullable": true + }, + "valid": { + "type": "boolean" + }, + "schemaVersion": { + "type": "string", + "nullable": true + }, + "state": { + "anyOf": [ + { + "type": "string", + "enum": [ + "LOCKED" + ] + }, + { + "type": "string", + "enum": [ + "DRAFT" + ] + } + ] + }, + "connectionIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "created", + "updated", + "flowId", + "displayName", + "trigger", + "valid", + "state", + "connectionIds" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "externalId", + "status", + "version" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "type", + "mcpId" + ] + } + } + }, + "required": [ + "id", + "created", + "updated", + "name", + "projectId", + "token", + "tools" + ] + } + } + } + } + } + }, + "post": { + "tags": [ + "mcp" + ], + "description": "Update the project MCP server configuration", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "tools": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PIECE" + ] + }, + { + "type": "string", + "enum": [ + "FLOW" + ] + } + ] + }, + "mcpId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "pieceMetadata": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "type": "string" + }, + "actionNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "logoUrl": { + "type": "string" + }, + "connectionExternalId": { + "type": "string" + } + }, + "required": [ + "pieceName", + "pieceVersion", + "actionNames", + "logoUrl" + ] + }, + "flowId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "flow": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "folderId": { + "type": "string", + "nullable": true + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ENABLED" + ] + }, + { + "type": "string", + "enum": [ + "DISABLED" + ] + } + ] + }, + "schedule": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "CRON_EXPRESSION" + ] + }, + "cronExpression": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "failureCount": { + "type": "number" + } + }, + "required": [ + "type", + "cronExpression", + "timezone" + ], + "nullable": true + }, + "handshakeConfiguration": { + "type": "object", + "properties": { + "strategy": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NONE" + ] + }, + { + "type": "string", + "enum": [ + "HEADER_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "QUERY_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "BODY_PARAM_PRESENT" + ] + } + ] + }, + "paramName": { + "type": "string" + } + }, + "required": [ + "strategy" + ], + "nullable": true + }, + "publishedVersionId": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "version": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "flowId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "trigger": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "PIECE_TRIGGER" + ] + }, + "settings": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "pattern": "^([~^])?[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "pieceType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "CUSTOM" + ] + }, + { + "type": "string", + "enum": [ + "OFFICIAL" + ] + } + ] + }, + "packageType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + { + "type": "string", + "enum": [ + "REGISTRY" + ] + } + ] + }, + "triggerName": { + "type": "string" + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "pieceName", + "pieceVersion", + "pieceType", + "packageType", + "input", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "EMPTY" + ] + }, + "settings": {} + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + } + ] + }, + "updatedBy": { + "type": "string", + "nullable": true + }, + "valid": { + "type": "boolean" + }, + "schemaVersion": { + "type": "string", + "nullable": true + }, + "state": { + "anyOf": [ + { + "type": "string", + "enum": [ + "LOCKED" + ] + }, + { + "type": "string", + "enum": [ + "DRAFT" + ] + } + ] + }, + "connectionIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "created", + "updated", + "flowId", + "displayName", + "trigger", + "valid", + "state", + "connectionIds" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "externalId", + "status", + "version" + ] + } + }, + "required": [ + "type", + "mcpId" + ] + } + } + } + } + } + } + }, + "parameters": [ + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "name": { + "type": "string" + }, + "projectId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "token": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "agentId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "tools": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PIECE" + ] + }, + { + "type": "string", + "enum": [ + "FLOW" + ] + } + ] + }, + "mcpId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "pieceMetadata": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "type": "string" + }, + "actionNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "logoUrl": { + "type": "string" + }, + "connectionExternalId": { + "type": "string" + } + }, + "required": [ + "pieceName", + "pieceVersion", + "actionNames", + "logoUrl" + ] + }, + "flowId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "flow": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "folderId": { + "type": "string", + "nullable": true + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ENABLED" + ] + }, + { + "type": "string", + "enum": [ + "DISABLED" + ] + } + ] + }, + "schedule": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "CRON_EXPRESSION" + ] + }, + "cronExpression": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "failureCount": { + "type": "number" + } + }, + "required": [ + "type", + "cronExpression", + "timezone" + ], + "nullable": true + }, + "handshakeConfiguration": { + "type": "object", + "properties": { + "strategy": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NONE" + ] + }, + { + "type": "string", + "enum": [ + "HEADER_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "QUERY_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "BODY_PARAM_PRESENT" + ] + } + ] + }, + "paramName": { + "type": "string" + } + }, + "required": [ + "strategy" + ], + "nullable": true + }, + "publishedVersionId": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "version": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "flowId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "trigger": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "PIECE_TRIGGER" + ] + }, + "settings": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "pattern": "^([~^])?[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "pieceType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "CUSTOM" + ] + }, + { + "type": "string", + "enum": [ + "OFFICIAL" + ] + } + ] + }, + "packageType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + { + "type": "string", + "enum": [ + "REGISTRY" + ] + } + ] + }, + "triggerName": { + "type": "string" + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "pieceName", + "pieceVersion", + "pieceType", + "packageType", + "input", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "EMPTY" + ] + }, + "settings": {} + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + } + ] + }, + "updatedBy": { + "type": "string", + "nullable": true + }, + "valid": { + "type": "boolean" + }, + "schemaVersion": { + "type": "string", + "nullable": true + }, + "state": { + "anyOf": [ + { + "type": "string", + "enum": [ + "LOCKED" + ] + }, + { + "type": "string", + "enum": [ + "DRAFT" + ] + } + ] + }, + "connectionIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "created", + "updated", + "flowId", + "displayName", + "trigger", + "valid", + "state", + "connectionIds" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "externalId", + "status", + "version" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "type", + "mcpId" + ] + } + } + }, + "required": [ + "id", + "created", + "updated", + "name", + "projectId", + "token", + "tools" + ] + } + } + } + } + } + }, + "delete": { + "tags": [ + "mcp" + ], + "description": "Delete an MCP server by ID", + "parameters": [ + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "204": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "not": {} + } + } + } + } + } + } + }, + "/v1/mcp-servers/{id}/rotate": { + "post": { + "tags": [ + "mcp" + ], + "description": "Rotate the MCP token", + "parameters": [ + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "name": { + "type": "string" + }, + "projectId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "token": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "agentId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "tools": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PIECE" + ] + }, + { + "type": "string", + "enum": [ + "FLOW" + ] + } + ] + }, + "mcpId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "pieceMetadata": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "type": "string" + }, + "actionNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "logoUrl": { + "type": "string" + }, + "connectionExternalId": { + "type": "string" + } + }, + "required": [ + "pieceName", + "pieceVersion", + "actionNames", + "logoUrl" + ] + }, + "flowId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "flow": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "folderId": { + "type": "string", + "nullable": true + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ENABLED" + ] + }, + { + "type": "string", + "enum": [ + "DISABLED" + ] + } + ] + }, + "schedule": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "CRON_EXPRESSION" + ] + }, + "cronExpression": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "failureCount": { + "type": "number" + } + }, + "required": [ + "type", + "cronExpression", + "timezone" + ], + "nullable": true + }, + "handshakeConfiguration": { + "type": "object", + "properties": { + "strategy": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NONE" + ] + }, + { + "type": "string", + "enum": [ + "HEADER_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "QUERY_PRESENT" + ] + }, + { + "type": "string", + "enum": [ + "BODY_PARAM_PRESENT" + ] + } + ] + }, + "paramName": { + "type": "string" + } + }, + "required": [ + "strategy" + ], + "nullable": true + }, + "publishedVersionId": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "version": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "flowId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "trigger": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "PIECE_TRIGGER" + ] + }, + "settings": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "pattern": "^([~^])?[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "pieceType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "CUSTOM" + ] + }, + { + "type": "string", + "enum": [ + "OFFICIAL" + ] + } + ] + }, + "packageType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + { + "type": "string", + "enum": [ + "REGISTRY" + ] + } + ] + }, + "triggerName": { + "type": "string" + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "pieceName", + "pieceVersion", + "pieceType", + "packageType", + "input", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "EMPTY" + ] + }, + "settings": {} + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + } + ] + }, + "updatedBy": { + "type": "string", + "nullable": true + }, + "valid": { + "type": "boolean" + }, + "schemaVersion": { + "type": "string", + "nullable": true + }, + "state": { + "anyOf": [ + { + "type": "string", + "enum": [ + "LOCKED" + ] + }, + { + "type": "string", + "enum": [ + "DRAFT" + ] + } + ] + }, + "connectionIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "created", + "updated", + "flowId", + "displayName", + "trigger", + "valid", + "state", + "connectionIds" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "externalId", + "status", + "version" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "type", + "mcpId" + ] + } + } + }, + "required": [ + "id", + "created", + "updated", + "name", + "projectId", + "token", + "tools" + ] + } + } + } + } + } + } + }, + "/v1/mcp-runs": { + "get": { + "tags": [ + "mcp-run" + ], + "description": "Get MCP run", + "parameters": [ + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": true + }, + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "query", + "name": "mcpId", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursorRequest", + "required": false + }, + { + "schema": { + "type": "number" + }, + "in": "query", + "name": "limit", + "required": false + }, + { + "schema": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string", + "enum": [ + "Success" + ] + }, + { + "type": "string", + "enum": [ + "Failed" + ] + } + ] + } + }, + "in": "query", + "name": "status", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "metadata", + "required": false + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "mcpId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "projectId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "toolId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "metadata": { + "anyOf": [ + { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "type": "string" + }, + "actionName": { + "type": "string" + } + }, + "required": [ + "pieceName", + "pieceVersion", + "actionName" + ] + }, + { + "type": "object", + "properties": { + "flowId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "flowVersionId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "flowId", + "flowVersionId", + "name" + ] + } + ] + }, + "input": {}, + "output": {}, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "Success" + ] + }, + { + "type": "string", + "enum": [ + "Failed" + ] + } + ] + } + }, + "required": [ + "id", + "created", + "updated", + "mcpId", + "projectId", + "toolId", + "metadata", + "input", + "output", + "status" + ] + } + }, + "next": { + "description": "Cursor to the next page", + "type": "string", + "nullable": true + }, + "previous": { + "description": "Cursor to the previous page", + "type": "string", + "nullable": true + } + }, + "required": [ + "data" + ] + } + } + } + } + } + } + }, + "/v1/users": { + "get": { + "tags": [ + "users" + ], + "description": "List users", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursor", + "required": false + }, + { + "schema": { + "type": "number" + }, + "in": "query", + "name": "limit", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "externalId", + "required": false + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "INACTIVE" + ] + } + ] + }, + "externalId": { + "type": "string", + "nullable": true + }, + "lastChangelogDismissed": { + "type": "string", + "nullable": true + }, + "platformId": { + "type": "string", + "nullable": true + }, + "platformRole": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ADMIN" + ] + }, + { + "type": "string", + "enum": [ + "MEMBER" + ] + } + ] + }, + "lastName": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "email", + "firstName", + "status", + "platformRole", + "lastName", + "created", + "updated" + ] + } + }, + "next": { + "description": "Cursor to the next page", + "type": "string", + "nullable": true + }, + "previous": { + "description": "Cursor to the previous page", + "type": "string", + "nullable": true + } + }, + "required": [ + "data" + ] + } + } + } + } + } + } + }, + "/v1/user-invitations": { + "post": { + "tags": [ + "user-invitations" + ], + "description": "Send a user invitation to a user. If the user already has an invitation, the invitation will be updated.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "PROJECT" + ] + }, + "email": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectRole": { + "type": "string" + } + }, + "required": [ + "type", + "email", + "projectId", + "projectRole" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "PLATFORM" + ] + }, + "email": { + "type": "string" + }, + "platformRole": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ADMIN" + ] + }, + { + "type": "string", + "enum": [ + "MEMBER" + ] + } + ] + } + }, + "required": [ + "type", + "email", + "platformRole" + ] + } + ] + } + } + } + }, + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "201": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "email": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PENDING" + ] + }, + { + "type": "string", + "enum": [ + "ACCEPTED" + ] + } + ] + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PLATFORM" + ] + }, + { + "type": "string", + "enum": [ + "PROJECT" + ] + } + ] + }, + "platformId": { + "type": "string" + }, + "platformRole": { + "type": "string", + "enum": [ + "ADMIN", + "MEMBER" + ], + "nullable": true + }, + "projectId": { + "type": "string", + "nullable": true + }, + "projectRoleId": { + "type": "string", + "nullable": true + }, + "projectRole": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "name": { + "type": "string" + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "platformId": { + "type": "string" + }, + "type": { + "type": "string" + }, + "userCount": { + "type": "number" + } + }, + "required": [ + "id", + "created", + "updated", + "name", + "permissions", + "type" + ], + "nullable": true + }, + "link": { + "type": "string" + } + }, + "required": [ + "id", + "created", + "updated", + "email", + "status", + "type", + "platformId" + ] + } + } + } + } + } + }, + "get": { + "tags": [ + "user-invitations" + ], + "parameters": [ + { + "schema": { + "type": "number" + }, + "in": "query", + "name": "limit", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursor", + "required": false + }, + { + "schema": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PLATFORM" + ] + }, + { + "type": "string", + "enum": [ + "PROJECT" + ] + } + ] + }, + "in": "query", + "name": "type", + "required": true + }, + { + "schema": { + "type": "string", + "nullable": true + }, + "in": "query", + "name": "projectId", + "required": false + }, + { + "schema": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PENDING" + ] + }, + { + "type": "string", + "enum": [ + "ACCEPTED" + ] + } + ] + }, + "in": "query", + "name": "status", + "required": false + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "email": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PENDING" + ] + }, + { + "type": "string", + "enum": [ + "ACCEPTED" + ] + } + ] + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PLATFORM" + ] + }, + { + "type": "string", + "enum": [ + "PROJECT" + ] + } + ] + }, + "platformId": { + "type": "string" + }, + "platformRole": { + "type": "string", + "enum": [ + "ADMIN", + "MEMBER" + ], + "nullable": true + }, + "projectId": { + "type": "string", + "nullable": true + }, + "projectRoleId": { + "type": "string", + "nullable": true + }, + "projectRole": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "name": { + "type": "string" + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "platformId": { + "type": "string" + }, + "type": { + "type": "string" + }, + "userCount": { + "type": "number" + } + }, + "required": [ + "id", + "created", + "updated", + "name", + "permissions", + "type" + ], + "nullable": true + } + }, + "required": [ + "id", + "created", + "updated", + "email", + "status", + "type", + "platformId" + ] + } + }, + "next": { + "description": "Cursor to the next page", + "type": "string", + "nullable": true + }, + "previous": { + "description": "Cursor to the previous page", + "type": "string", + "nullable": true + } + }, + "required": [ + "data" + ] + } + } + } + } + } + } + }, + "/v1/user-invitations/{id}": { + "delete": { + "tags": [ + "user-invitations" + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "204": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "not": {} + } + } + } + } + } + } + }, + "/v1/tables/{id}": { + "post": { + "tags": [ + "tables" + ], + "description": "Update a table", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ] + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + }, + "delete": { + "tags": [ + "tables" + ], + "description": "Delete a table", + "parameters": [ + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "204": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "not": {} + } + } + } + } + } + }, + "get": { + "tags": [ + "tables" + ], + "description": "Get a table by id", + "parameters": [ + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "name": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "externalId": { + "type": "string" + } + }, + "required": [ + "id", + "created", + "updated", + "name", + "projectId", + "externalId" + ] + } + } + } + } + } + } + }, + "/v1/tables": { + "get": { + "tags": [ + "tables" + ], + "description": "List tables", + "parameters": [ + { + "schema": { + "type": "number" + }, + "in": "query", + "name": "limit", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursor", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "name", + "required": false + }, + { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query", + "name": "externalIds", + "required": false + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "name": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "externalId": { + "type": "string" + } + }, + "required": [ + "id", + "created", + "updated", + "name", + "projectId", + "externalId" + ] + } + }, + "next": { + "description": "Cursor to the next page", + "type": "string", + "nullable": true + }, + "previous": { + "description": "Cursor to the previous page", + "type": "string", + "nullable": true + } + }, + "required": [ + "data" + ] + } + } + } + } + } + } + }, + "/v1/tables/{id}/export": { + "get": { + "tags": [ + "tables" + ], + "description": "Export a table", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + } + }, + "rows": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "name": { + "type": "string" + } + }, + "required": [ + "fields", + "rows", + "name" + ] + } + } + } + } + } + } + }, + "/v1/tables/{id}/webhooks": { + "post": { + "tags": [ + "tables" + ], + "description": "Create a table webhook", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "events": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string", + "enum": [ + "RECORD_CREATED" + ] + }, + { + "type": "string", + "enum": [ + "RECORD_UPDATED" + ] + }, + { + "type": "string", + "enum": [ + "RECORD_DELETED" + ] + } + ] + } + }, + "webhookUrl": { + "type": "string" + }, + "flowId": { + "type": "string" + } + }, + "required": [ + "events", + "webhookUrl", + "flowId" + ] + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + } + }, + "/v1/tables/{id}/webhooks/{webhookId}": { + "delete": { + "tags": [ + "tables" + ], + "description": "Delete a table webhook", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "webhookId", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + } + }, + "/v1/records/{id}": { + "post": { + "tags": [ + "records" + ], + "description": "Update a record", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "cells": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fieldId": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "fieldId", + "value" + ] + } + }, + "tableId": { + "type": "string" + } + }, + "required": [ + "tableId" + ] + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "tableId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "cells": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "updated": { + "type": "string" + }, + "created": { + "type": "string" + }, + "value": {}, + "fieldName": { + "type": "string" + } + }, + "required": [ + "updated", + "created", + "value", + "fieldName" + ] + } + } + }, + "required": [ + "id", + "created", + "updated", + "tableId", + "projectId", + "cells" + ] + } + } + } + } + } + } + }, + "/v1/records": { + "delete": { + "tags": [ + "records" + ], + "description": "Delete records", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "ids" + ] + } + } + }, + "required": true + }, + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "tableId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "cells": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "updated": { + "type": "string" + }, + "created": { + "type": "string" + }, + "value": {}, + "fieldName": { + "type": "string" + } + }, + "required": [ + "updated", + "created", + "value", + "fieldName" + ] + } + } + }, + "required": [ + "id", + "created", + "updated", + "tableId", + "projectId", + "cells" + ] + } + } + } + } + } + } + }, + "get": { + "tags": [ + "records" + ], + "description": "List records", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "tableId", + "required": true + }, + { + "schema": { + "type": "number" + }, + "in": "query", + "name": "limit", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursor", + "required": false + }, + { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fieldId": { + "type": "string" + }, + "value": { + "type": "string" + }, + "operator": { + "anyOf": [ + { + "type": "string", + "enum": [ + "eq" + ] + }, + { + "type": "string", + "enum": [ + "neq" + ] + }, + { + "type": "string", + "enum": [ + "gt" + ] + }, + { + "type": "string", + "enum": [ + "gte" + ] + }, + { + "type": "string", + "enum": [ + "lt" + ] + }, + { + "type": "string", + "enum": [ + "lte" + ] + }, + { + "type": "string", + "enum": [ + "co" + ] + } + ] + } + }, + "required": [ + "fieldId", + "value" + ] + } + }, + "in": "query", + "name": "filters", + "required": false + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "tableId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "cells": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "updated": { + "type": "string" + }, + "created": { + "type": "string" + }, + "value": {}, + "fieldName": { + "type": "string" + } + }, + "required": [ + "updated", + "created", + "value", + "fieldName" + ] + } + } + }, + "required": [ + "id", + "created", + "updated", + "tableId", + "projectId", + "cells" + ] + } + }, + "next": { + "description": "Cursor to the next page", + "type": "string", + "nullable": true + }, + "previous": { + "description": "Cursor to the previous page", + "type": "string", + "nullable": true + } + }, + "required": [ + "data" + ] + } + } + } + } + } + } + }, + "/v1/projects": { + "post": { + "tags": [ + "projects" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "pattern": "^[^./]+$", + "type": "string" + }, + "externalId": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + } + }, + "required": [ + "displayName" + ] + } + } + }, + "required": true + }, + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "201": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "notifyStatus": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NEVER" + ] + }, + { + "type": "string", + "enum": [ + "ALWAYS" + ] + }, + { + "type": "string", + "enum": [ + "NEW_ISSUE" + ] + } + ] + }, + "platformId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "externalId": { + "type": "string" + }, + "releasesEnabled": { + "type": "boolean" + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "usage": { + "type": "object", + "properties": { + "tasks": { + "type": "number" + }, + "aiCredits": { + "type": "number" + }, + "nextLimitResetDate": { + "type": "string" + } + }, + "required": [ + "tasks", + "aiCredits", + "nextLimitResetDate" + ] + }, + "plan": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "piecesFilterType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NONE" + ] + }, + { + "type": "string", + "enum": [ + "ALLOWED" + ] + } + ] + }, + "pieces": { + "type": "array", + "items": { + "type": "string" + } + }, + "tasks": { + "type": "number", + "nullable": true + }, + "aiCredits": { + "type": "number", + "nullable": true + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "name", + "piecesFilterType", + "pieces" + ] + }, + "analytics": { + "type": "object", + "properties": { + "totalUsers": { + "type": "number" + }, + "activeUsers": { + "type": "number" + }, + "totalFlows": { + "type": "number" + }, + "activeFlows": { + "type": "number" + } + }, + "required": [ + "totalUsers", + "activeUsers", + "totalFlows", + "activeFlows" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "ownerId", + "displayName", + "notifyStatus", + "platformId", + "releasesEnabled", + "usage", + "plan", + "analytics" + ] + } + } + } + } + } + }, + "get": { + "tags": [ + "projects" + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "externalId", + "required": false + }, + { + "schema": { + "type": "number" + }, + "in": "query", + "name": "limit", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursor", + "required": false + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "notifyStatus": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NEVER" + ] + }, + { + "type": "string", + "enum": [ + "ALWAYS" + ] + }, + { + "type": "string", + "enum": [ + "NEW_ISSUE" + ] + } + ] + }, + "platformId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "externalId": { + "type": "string" + }, + "releasesEnabled": { + "type": "boolean" + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "usage": { + "type": "object", + "properties": { + "tasks": { + "type": "number" + }, + "aiCredits": { + "type": "number" + }, + "nextLimitResetDate": { + "type": "string" + } + }, + "required": [ + "tasks", + "aiCredits", + "nextLimitResetDate" + ] + }, + "plan": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "piecesFilterType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NONE" + ] + }, + { + "type": "string", + "enum": [ + "ALLOWED" + ] + } + ] + }, + "pieces": { + "type": "array", + "items": { + "type": "string" + } + }, + "tasks": { + "type": "number", + "nullable": true + }, + "aiCredits": { + "type": "number", + "nullable": true + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "name", + "piecesFilterType", + "pieces" + ] + }, + "analytics": { + "type": "object", + "properties": { + "totalUsers": { + "type": "number" + }, + "activeUsers": { + "type": "number" + }, + "totalFlows": { + "type": "number" + }, + "activeFlows": { + "type": "number" + } + }, + "required": [ + "totalUsers", + "activeUsers", + "totalFlows", + "activeFlows" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "ownerId", + "displayName", + "notifyStatus", + "platformId", + "releasesEnabled", + "usage", + "plan", + "analytics" + ] + } + }, + "next": { + "description": "Cursor to the next page", + "type": "string", + "nullable": true + }, + "previous": { + "description": "Cursor to the previous page", + "type": "string", + "nullable": true + } + }, + "required": [ + "data" + ] + } + } + } + } + } + } + }, + "/v1/projects/{id}": { + "post": { + "tags": [ + "projects" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "notifyStatus": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NEVER" + ] + }, + { + "type": "string", + "enum": [ + "ALWAYS" + ] + }, + { + "type": "string", + "enum": [ + "NEW_ISSUE" + ] + } + ] + }, + "releasesEnabled": { + "type": "boolean" + }, + "displayName": { + "pattern": "^[^./]+$", + "type": "string" + }, + "externalId": { + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": {} + }, + "plan": { + "type": "object", + "properties": { + "tasks": { + "type": "number" + }, + "pieces": { + "type": "array", + "items": { + "type": "string" + } + }, + "piecesFilterType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NONE" + ] + }, + { + "type": "string", + "enum": [ + "ALLOWED" + ] + } + ] + }, + "aiCredits": { + "type": "number" + } + } + } + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "notifyStatus": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NEVER" + ] + }, + { + "type": "string", + "enum": [ + "ALWAYS" + ] + }, + { + "type": "string", + "enum": [ + "NEW_ISSUE" + ] + } + ] + }, + "platformId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "externalId": { + "type": "string" + }, + "releasesEnabled": { + "type": "boolean" + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "usage": { + "type": "object", + "properties": { + "tasks": { + "type": "number" + }, + "aiCredits": { + "type": "number" + }, + "nextLimitResetDate": { + "type": "string" + } + }, + "required": [ + "tasks", + "aiCredits", + "nextLimitResetDate" + ] + }, + "plan": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "piecesFilterType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "NONE" + ] + }, + { + "type": "string", + "enum": [ + "ALLOWED" + ] + } + ] + }, + "pieces": { + "type": "array", + "items": { + "type": "string" + } + }, + "tasks": { + "type": "number", + "nullable": true + }, + "aiCredits": { + "type": "number", + "nullable": true + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "name", + "piecesFilterType", + "pieces" + ] + }, + "analytics": { + "type": "object", + "properties": { + "totalUsers": { + "type": "number" + }, + "activeUsers": { + "type": "number" + }, + "totalFlows": { + "type": "number" + }, + "activeFlows": { + "type": "number" + } + }, + "required": [ + "totalUsers", + "activeUsers", + "totalFlows", + "activeFlows" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "ownerId", + "displayName", + "notifyStatus", + "platformId", + "releasesEnabled", + "usage", + "plan", + "analytics" + ] + } + } + } + } + } + }, + "delete": { + "tags": [ + "projects" + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + } + }, + "/v1/project-members": { + "get": { + "tags": [ + "project-members" + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectId", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "projectRoleId", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursor", + "required": false + }, + { + "schema": { + "type": "number" + }, + "in": "query", + "name": "limit", + "required": false + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "userId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectRoleId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "user": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "INACTIVE" + ] + } + ] + }, + "externalId": { + "type": "string", + "nullable": true + }, + "lastChangelogDismissed": { + "type": "string", + "nullable": true + }, + "platformId": { + "type": "string", + "nullable": true + }, + "platformRole": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ADMIN" + ] + }, + { + "type": "string", + "enum": [ + "MEMBER" + ] + } + ] + }, + "lastName": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "email", + "firstName", + "status", + "platformRole", + "lastName", + "created", + "updated" + ] + }, + "projectRole": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "name": { + "type": "string" + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "platformId": { + "type": "string" + }, + "type": { + "type": "string" + }, + "userCount": { + "type": "number" + } + }, + "required": [ + "id", + "created", + "updated", + "name", + "permissions", + "type" + ] + }, + "project": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "displayName": { + "type": "string" + } + }, + "required": [ + "id", + "displayName" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "userId", + "projectId", + "projectRoleId", + "user", + "projectRole", + "project" + ] + } + }, + "next": { + "description": "Cursor to the next page", + "type": "string", + "nullable": true + }, + "previous": { + "description": "Cursor to the previous page", + "type": "string", + "nullable": true + } + }, + "required": [ + "data" + ] + } + } + } + } + } + } + }, + "/v1/project-members/{id}": { + "delete": { + "tags": [ + "project-members" + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "204": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "not": {} + } + } + } + } + } + } + }, + "/v1/pieces": { + "post": { + "summary": "Add a piece to a platform", + "tags": [ + "pieces" + ], + "description": "Add a piece to a platform", + "requestBody": { + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "title": "Private Piece", + "type": "object", + "properties": { + "packageType": { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + "scope": { + "type": "string", + "enum": [ + "PLATFORM" + ] + }, + "pieceName": { + "minLength": 1, + "type": "string" + }, + "pieceVersion": { + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "pieceArchive": { + "type": "object", + "properties": { + "filename": { + "type": "string" + }, + "data": {}, + "type": { + "type": "string", + "enum": [ + "file" + ] + } + }, + "required": [ + "filename", + "data", + "type" + ] + } + }, + "required": [ + "packageType", + "scope", + "pieceName", + "pieceVersion", + "pieceArchive" + ] + }, + { + "title": "NPM Piece", + "type": "object", + "properties": { + "packageType": { + "type": "string", + "enum": [ + "REGISTRY" + ] + }, + "scope": { + "type": "string", + "enum": [ + "PLATFORM" + ] + }, + "pieceName": { + "minLength": 1, + "type": "string" + }, + "pieceVersion": { + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + } + }, + "required": [ + "packageType", + "scope", + "pieceName", + "pieceVersion" + ] + } + ] + } + } + } + }, + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "201": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + } + } + } + }, + "/v1/flow-templates/{id}": { + "get": { + "tags": [ + "flow-templates" + ], + "description": "Get a flow template", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + }, + "delete": { + "tags": [ + "flow-templates" + ], + "description": "Delete a flow template", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + } + }, + "/v1/flow-templates": { + "get": { + "tags": [ + "flow-templates" + ], + "description": "List flow templates", + "parameters": [ + { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query", + "name": "pieces", + "required": false + }, + { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query", + "name": "tags", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "search", + "required": false + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + }, + "post": { + "tags": [ + "flow-templates" + ], + "description": "Create a flow template", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "template": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "trigger": { + "anyOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "PIECE_TRIGGER" + ] + }, + "settings": { + "type": "object", + "properties": { + "pieceName": { + "type": "string" + }, + "pieceVersion": { + "pattern": "^([~^])?[0-9]+\\.[0-9]+\\.[0-9]+$", + "type": "string" + }, + "pieceType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "CUSTOM" + ] + }, + { + "type": "string", + "enum": [ + "OFFICIAL" + ] + } + ] + }, + "packageType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ARCHIVE" + ] + }, + { + "type": "string", + "enum": [ + "REGISTRY" + ] + } + ] + }, + "triggerName": { + "type": "string" + }, + "input": { + "type": "object", + "additionalProperties": {} + }, + "inputUiInfo": { + "additionalProperties": true, + "type": "object", + "properties": { + "sampleDataFileId": { + "type": "string" + }, + "sampleDataInputFileId": { + "type": "string" + }, + "lastTestDate": { + "type": "string" + }, + "customizedInputs": { + "type": "object", + "additionalProperties": {} + }, + "schema": { + "type": "object", + "additionalProperties": {} + }, + "currentSelectedData": {} + } + } + }, + "required": [ + "pieceName", + "pieceVersion", + "pieceType", + "packageType", + "input", + "inputUiInfo" + ] + } + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "nextAction": {}, + "type": { + "type": "string", + "enum": [ + "EMPTY" + ] + }, + "settings": {} + }, + "required": [ + "name", + "valid", + "displayName", + "type", + "settings" + ] + } + ] + }, + "valid": { + "type": "boolean" + }, + "schemaVersion": { + "type": "string", + "nullable": true + }, + "connectionIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "displayName", + "trigger", + "valid", + "connectionIds" + ] + }, + "blogUrl": { + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PLATFORM" + ] + }, + { + "type": "string", + "enum": [ + "PROJECT" + ] + } + ] + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "type": "string" + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + } + }, + "required": [ + "template", + "type" + ] + } + } + }, + "required": true + }, + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + } + }, + "/v1/git-repos": { + "post": { + "tags": [ + "git-repos" + ], + "description": "Upsert a git repository information for a project.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "projectId": { + "minLength": 1, + "type": "string" + }, + "remoteUrl": { + "pattern": "^git@", + "type": "string" + }, + "branch": { + "minLength": 1, + "type": "string" + }, + "branchType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PRODUCTION" + ] + }, + { + "type": "string", + "enum": [ + "DEVELOPMENT" + ] + } + ] + }, + "sshPrivateKey": { + "minLength": 1, + "type": "string" + }, + "slug": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "projectId", + "remoteUrl", + "branch", + "branchType", + "sshPrivateKey", + "slug" + ] + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "remoteUrl": { + "type": "string" + }, + "branch": { + "type": "string" + }, + "branchType": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PRODUCTION" + ] + }, + { + "type": "string", + "enum": [ + "DEVELOPMENT" + ] + } + ] + }, + "projectId": { + "type": "string" + }, + "slug": { + "type": "string" + } + }, + "required": [ + "id", + "created", + "updated", + "remoteUrl", + "branch", + "branchType", + "projectId", + "slug" + ] + } + } + } + } + } + } + }, + "/v1/project-roles/{id}/project-members": { + "get": { + "tags": [ + "project-members" + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursor", + "required": false + }, + { + "schema": { + "type": "number" + }, + "in": "query", + "name": "limit", + "required": false + }, + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "platformId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "userId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "projectId": { + "type": "string" + }, + "projectRoleId": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "user": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "INACTIVE" + ] + } + ] + }, + "externalId": { + "type": "string", + "nullable": true + }, + "lastChangelogDismissed": { + "type": "string", + "nullable": true + }, + "platformId": { + "type": "string", + "nullable": true + }, + "platformRole": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ADMIN" + ] + }, + { + "type": "string", + "enum": [ + "MEMBER" + ] + } + ] + }, + "lastName": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "email", + "firstName", + "status", + "platformRole", + "lastName", + "created", + "updated" + ] + }, + "projectRole": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "name": { + "type": "string" + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "platformId": { + "type": "string" + }, + "type": { + "type": "string" + }, + "userCount": { + "type": "number" + } + }, + "required": [ + "id", + "created", + "updated", + "name", + "permissions", + "type" + ] + }, + "project": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "displayName": { + "type": "string" + } + }, + "required": [ + "id", + "displayName" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "platformId", + "userId", + "projectId", + "projectRoleId", + "user", + "projectRole", + "project" + ] + } + }, + "next": { + "description": "Cursor to the next page", + "type": "string", + "nullable": true + }, + "previous": { + "description": "Cursor to the previous page", + "type": "string", + "nullable": true + } + }, + "required": [ + "data" + ] + } + } + } + } + } + } + }, + "/v1/project-releases": { + "post": { + "tags": [ + "project-releases" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "ROLLBACK" + ] + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "selectedFlowsIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "projectId": { + "type": "string" + }, + "projectReleaseId": { + "type": "string" + } + }, + "required": [ + "type", + "name", + "projectId", + "projectReleaseId" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "PROJECT" + ] + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "selectedFlowsIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "projectId": { + "type": "string" + }, + "targetProjectId": { + "type": "string" + } + }, + "required": [ + "type", + "name", + "projectId", + "targetProjectId" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "GIT" + ] + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "selectedFlowsIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "projectId": { + "type": "string" + } + }, + "required": [ + "type", + "name", + "projectId" + ] + } + ], + "discriminator": { + "propertyName": "type" + } + } + } + } + }, + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "201": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "importedBy": { + "type": "string", + "nullable": true + }, + "fileId": { + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "GIT" + ] + }, + { + "type": "string", + "enum": [ + "PROJECT" + ] + }, + { + "type": "string", + "enum": [ + "ROLLBACK" + ] + } + ] + }, + "importedByUser": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "INACTIVE" + ] + } + ] + }, + "externalId": { + "type": "string", + "nullable": true + }, + "lastChangelogDismissed": { + "type": "string", + "nullable": true + }, + "platformId": { + "type": "string", + "nullable": true + }, + "platformRole": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ADMIN" + ] + }, + { + "type": "string", + "enum": [ + "MEMBER" + ] + } + ] + }, + "lastName": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "email", + "firstName", + "status", + "platformRole", + "lastName", + "created", + "updated" + ] + } + }, + "required": [ + "id", + "created", + "updated", + "projectId", + "name", + "fileId", + "type" + ] + } + } + } + } + } + } + }, + "/v1/global-connections": { + "post": { + "tags": [ + "global-connections" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "pieceName": { + "type": "string" + }, + "metadata": { + "allOf": [ + { + "type": "object", + "additionalProperties": {} + }, + { + "type": "object", + "additionalProperties": {} + } + ] + }, + "type": { + "type": "string", + "enum": [ + "SECRET_TEXT" + ] + }, + "value": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "SECRET_TEXT" + ] + }, + "secret_text": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "type", + "secret_text" + ] + }, + "scope": { + "type": "string", + "enum": [ + "PLATFORM" + ] + }, + "projectIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "externalId": { + "type": "string" + } + }, + "required": [ + "displayName", + "pieceName", + "type", + "value", + "scope", + "projectIds" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "pieceName": { + "type": "string" + }, + "metadata": { + "allOf": [ + { + "type": "object", + "additionalProperties": {} + }, + { + "type": "object", + "additionalProperties": {} + } + ] + }, + "type": { + "type": "string", + "enum": [ + "OAUTH2" + ] + }, + "value": { + "type": "object", + "properties": { + "client_id": { + "minLength": 1, + "type": "string" + }, + "code": { + "minLength": 1, + "type": "string" + }, + "code_challenge": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "authorization_method": { + "anyOf": [ + { + "type": "string", + "enum": [ + "HEADER" + ] + }, + { + "type": "string", + "enum": [ + "BODY" + ] + } + ] + }, + "client_secret": { + "minLength": 1, + "type": "string" + }, + "grant_type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "authorization_code" + ] + }, + { + "type": "string", + "enum": [ + "client_credentials" + ] + } + ] + }, + "props": { + "type": "object", + "additionalProperties": {} + }, + "redirect_url": { + "minLength": 1, + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "OAUTH2" + ] + } + }, + "required": [ + "client_id", + "code", + "scope", + "client_secret", + "redirect_url", + "type" + ] + }, + "scope": { + "type": "string", + "enum": [ + "PLATFORM" + ] + }, + "projectIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "externalId": { + "type": "string" + } + }, + "required": [ + "displayName", + "pieceName", + "type", + "value", + "scope", + "projectIds" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "pieceName": { + "type": "string" + }, + "metadata": { + "allOf": [ + { + "type": "object", + "additionalProperties": {} + }, + { + "type": "object", + "additionalProperties": {} + } + ] + }, + "type": { + "type": "string", + "enum": [ + "CLOUD_OAUTH2" + ] + }, + "value": { + "type": "object", + "properties": { + "client_id": { + "minLength": 1, + "type": "string" + }, + "code": { + "minLength": 1, + "type": "string" + }, + "code_challenge": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "authorization_method": { + "anyOf": [ + { + "type": "string", + "enum": [ + "HEADER" + ] + }, + { + "type": "string", + "enum": [ + "BODY" + ] + } + ] + }, + "props": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "type": { + "type": "string", + "enum": [ + "CLOUD_OAUTH2" + ] + } + }, + "required": [ + "client_id", + "code", + "scope", + "type" + ] + }, + "scope": { + "type": "string", + "enum": [ + "PLATFORM" + ] + }, + "projectIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "externalId": { + "type": "string" + } + }, + "required": [ + "displayName", + "pieceName", + "type", + "value", + "scope", + "projectIds" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "pieceName": { + "type": "string" + }, + "metadata": { + "allOf": [ + { + "type": "object", + "additionalProperties": {} + }, + { + "type": "object", + "additionalProperties": {} + } + ] + }, + "type": { + "type": "string", + "enum": [ + "PLATFORM_OAUTH2" + ] + }, + "value": { + "type": "object", + "properties": { + "client_id": { + "minLength": 1, + "type": "string" + }, + "code": { + "minLength": 1, + "type": "string" + }, + "code_challenge": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "authorization_method": { + "anyOf": [ + { + "type": "string", + "enum": [ + "HEADER" + ] + }, + { + "type": "string", + "enum": [ + "BODY" + ] + } + ] + }, + "props": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "type": { + "type": "string", + "enum": [ + "PLATFORM_OAUTH2" + ] + }, + "redirect_url": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "client_id", + "code", + "scope", + "type", + "redirect_url" + ] + }, + "scope": { + "type": "string", + "enum": [ + "PLATFORM" + ] + }, + "projectIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "externalId": { + "type": "string" + } + }, + "required": [ + "displayName", + "pieceName", + "type", + "value", + "scope", + "projectIds" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "pieceName": { + "type": "string" + }, + "metadata": { + "allOf": [ + { + "type": "object", + "additionalProperties": {} + }, + { + "type": "object", + "additionalProperties": {} + } + ] + }, + "type": { + "type": "string", + "enum": [ + "BASIC_AUTH" + ] + }, + "value": { + "type": "object", + "properties": { + "username": { + "minLength": 1, + "type": "string" + }, + "password": { + "minLength": 1, + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "BASIC_AUTH" + ] + } + }, + "required": [ + "username", + "password", + "type" + ] + }, + "scope": { + "type": "string", + "enum": [ + "PLATFORM" + ] + }, + "projectIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "externalId": { + "type": "string" + } + }, + "required": [ + "displayName", + "pieceName", + "type", + "value", + "scope", + "projectIds" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "pieceName": { + "type": "string" + }, + "metadata": { + "allOf": [ + { + "type": "object", + "additionalProperties": {} + }, + { + "type": "object", + "additionalProperties": {} + } + ] + }, + "type": { + "type": "string", + "enum": [ + "CUSTOM_AUTH" + ] + }, + "value": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "CUSTOM_AUTH" + ] + }, + "props": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "type", + "props" + ] + }, + "scope": { + "type": "string", + "enum": [ + "PLATFORM" + ] + }, + "projectIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "externalId": { + "type": "string" + } + }, + "required": [ + "displayName", + "pieceName", + "type", + "value", + "scope", + "projectIds" + ] + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "pieceName": { + "type": "string" + }, + "metadata": { + "allOf": [ + { + "type": "object", + "additionalProperties": {} + }, + { + "type": "object", + "additionalProperties": {} + } + ] + }, + "type": { + "type": "string", + "enum": [ + "NO_AUTH" + ] + }, + "value": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "NO_AUTH" + ] + } + }, + "required": [ + "type" + ] + }, + "scope": { + "type": "string", + "enum": [ + "PLATFORM" + ] + }, + "projectIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "externalId": { + "type": "string" + } + }, + "required": [ + "displayName", + "pieceName", + "type", + "value", + "scope", + "projectIds" + ] + } + ] + } + } + } + }, + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "201": { + "description": "App connection is a connection to an external app.", + "content": { + "application/json": { + "schema": { + "description": "App connection is a connection to an external app.", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "PLATFORM_OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "CLOUD_OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "SECRET_TEXT" + ] + }, + { + "type": "string", + "enum": [ + "BASIC_AUTH" + ] + }, + { + "type": "string", + "enum": [ + "CUSTOM_AUTH" + ] + }, + { + "type": "string", + "enum": [ + "NO_AUTH" + ] + } + ] + }, + "pieceName": { + "type": "string" + }, + "projectIds": { + "type": "array", + "items": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + } + }, + "platformId": { + "type": "string", + "nullable": true + }, + "scope": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PROJECT" + ] + }, + { + "type": "string", + "enum": [ + "PLATFORM" + ] + } + ] + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "MISSING" + ] + }, + { + "type": "string", + "enum": [ + "ERROR" + ] + } + ] + }, + "ownerId": { + "type": "string", + "nullable": true + }, + "owner": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "INACTIVE" + ] + } + ] + }, + "externalId": { + "type": "string", + "nullable": true + }, + "lastChangelogDismissed": { + "type": "string", + "nullable": true + }, + "platformId": { + "type": "string", + "nullable": true + }, + "platformRole": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ADMIN" + ] + }, + { + "type": "string", + "enum": [ + "MEMBER" + ] + } + ] + }, + "lastName": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "email", + "firstName", + "status", + "platformRole", + "lastName", + "created", + "updated" + ], + "nullable": true + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "flowIds": { + "type": "array", + "items": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "nullable": true + } + }, + "required": [ + "id", + "created", + "updated", + "externalId", + "displayName", + "type", + "pieceName", + "projectIds", + "scope", + "status" + ] + } + } + } + } + } + }, + "get": { + "tags": [ + "global-connections" + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "cursor", + "required": false + }, + { + "schema": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PROJECT" + ] + }, + { + "type": "string", + "enum": [ + "PLATFORM" + ] + } + ] + }, + "in": "query", + "name": "scope", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "pieceName", + "required": false + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "displayName", + "required": false + }, + { + "schema": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "MISSING" + ] + }, + { + "type": "string", + "enum": [ + "ERROR" + ] + } + ] + } + }, + "in": "query", + "name": "status", + "required": false + }, + { + "schema": { + "type": "number" + }, + "in": "query", + "name": "limit", + "required": false + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "description": "App connection is a connection to an external app.", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "PLATFORM_OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "CLOUD_OAUTH2" + ] + }, + { + "type": "string", + "enum": [ + "SECRET_TEXT" + ] + }, + { + "type": "string", + "enum": [ + "BASIC_AUTH" + ] + }, + { + "type": "string", + "enum": [ + "CUSTOM_AUTH" + ] + }, + { + "type": "string", + "enum": [ + "NO_AUTH" + ] + } + ] + }, + "pieceName": { + "type": "string" + }, + "projectIds": { + "type": "array", + "items": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + } + }, + "platformId": { + "type": "string", + "nullable": true + }, + "scope": { + "anyOf": [ + { + "type": "string", + "enum": [ + "PROJECT" + ] + }, + { + "type": "string", + "enum": [ + "PLATFORM" + ] + } + ] + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "MISSING" + ] + }, + { + "type": "string", + "enum": [ + "ERROR" + ] + } + ] + }, + "ownerId": { + "type": "string", + "nullable": true + }, + "owner": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "status": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ACTIVE" + ] + }, + { + "type": "string", + "enum": [ + "INACTIVE" + ] + } + ] + }, + "externalId": { + "type": "string", + "nullable": true + }, + "lastChangelogDismissed": { + "type": "string", + "nullable": true + }, + "platformId": { + "type": "string", + "nullable": true + }, + "platformRole": { + "anyOf": [ + { + "type": "string", + "enum": [ + "ADMIN" + ] + }, + { + "type": "string", + "enum": [ + "MEMBER" + ] + } + ] + }, + "lastName": { + "type": "string" + }, + "created": { + "type": "string" + }, + "updated": { + "type": "string" + } + }, + "required": [ + "id", + "email", + "firstName", + "status", + "platformRole", + "lastName", + "created", + "updated" + ], + "nullable": true + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": {} + }, + "flowIds": { + "type": "array", + "items": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "nullable": true + } + }, + "required": [ + "id", + "created", + "updated", + "externalId", + "displayName", + "type", + "pieceName", + "projectIds", + "scope", + "status" + ] + } + }, + "next": { + "description": "Cursor to the next page", + "type": "string", + "nullable": true + }, + "previous": { + "description": "Cursor to the previous page", + "type": "string", + "nullable": true + } + }, + "required": [ + "data" + ] + } + } + } + } + } + } + }, + "/v1/global-connections/{id}": { + "post": { + "tags": [ + "global-connections" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "minLength": 1, + "type": "string" + }, + "projectIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "metadata": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "displayName" + ] + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "200": { + "description": "Default Response" + } + } + }, + "delete": { + "tags": [ + "global-connections" + ], + "parameters": [ + { + "schema": { + "pattern": "^[0-9a-zA-Z]{21}$", + "type": "string" + }, + "in": "path", + "name": "id", + "required": true + } + ], + "security": [ + { + "apiKey": [] + } + ], + "responses": { + "204": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "not": {} + } + } + } + } + } + } + } + }, + "servers": [ + { + "url": "https://cloud.activepieces.com/api", + "description": "Production Server" + } + ], + "externalDocs": { + "url": "https://www.activepieces.com/docs", + "description": "Find more info here" + } +} \ No newline at end of file diff --git a/docs/operations/audit-logs/connection-deleted.mdx b/docs/operations/audit-logs/connection-deleted.mdx new file mode 100644 index 0000000..f6b747c --- /dev/null +++ b/docs/operations/audit-logs/connection-deleted.mdx @@ -0,0 +1,5 @@ +--- +title: 'Connection Deleted' +openapi-schema: connection.deleted +icon: link +--- \ No newline at end of file diff --git a/docs/operations/audit-logs/connection-upserted.mdx b/docs/operations/audit-logs/connection-upserted.mdx new file mode 100644 index 0000000..1443265 --- /dev/null +++ b/docs/operations/audit-logs/connection-upserted.mdx @@ -0,0 +1,5 @@ +--- +title: 'Connection Upserted' +openapi-schema: connection.upserted +icon: link +--- \ No newline at end of file diff --git a/docs/operations/audit-logs/flow-created.mdx b/docs/operations/audit-logs/flow-created.mdx new file mode 100644 index 0000000..43557f3 --- /dev/null +++ b/docs/operations/audit-logs/flow-created.mdx @@ -0,0 +1,5 @@ +--- +title: 'Flow Created' +openapi-schema: flow.created +icon: bolt +--- \ No newline at end of file diff --git a/docs/operations/audit-logs/flow-deleted.mdx b/docs/operations/audit-logs/flow-deleted.mdx new file mode 100644 index 0000000..aa454c4 --- /dev/null +++ b/docs/operations/audit-logs/flow-deleted.mdx @@ -0,0 +1,5 @@ +--- +title: 'Flow Deleted' +openapi-schema: flow.deleted +icon: bolt +--- \ No newline at end of file diff --git a/docs/operations/audit-logs/flow-run-finished.mdx b/docs/operations/audit-logs/flow-run-finished.mdx new file mode 100644 index 0000000..0da63ad --- /dev/null +++ b/docs/operations/audit-logs/flow-run-finished.mdx @@ -0,0 +1,5 @@ +--- +title: 'Flow Run Finished' +openapi-schema: flow.run.finished +icon: play +--- \ No newline at end of file diff --git a/docs/operations/audit-logs/flow-run-started.mdx b/docs/operations/audit-logs/flow-run-started.mdx new file mode 100644 index 0000000..a584335 --- /dev/null +++ b/docs/operations/audit-logs/flow-run-started.mdx @@ -0,0 +1,5 @@ +--- +title: 'Flow Run Started' +openapi-schema: flow.run.started +icon: play +--- \ No newline at end of file diff --git a/docs/operations/audit-logs/flow-updated.mdx b/docs/operations/audit-logs/flow-updated.mdx new file mode 100644 index 0000000..270aec0 --- /dev/null +++ b/docs/operations/audit-logs/flow-updated.mdx @@ -0,0 +1,5 @@ +--- +title: 'Flow Updated' +openapi-schema: flow.updated +icon: bolt +--- \ No newline at end of file diff --git a/docs/operations/audit-logs/folder-created.mdx b/docs/operations/audit-logs/folder-created.mdx new file mode 100644 index 0000000..5c24a79 --- /dev/null +++ b/docs/operations/audit-logs/folder-created.mdx @@ -0,0 +1,5 @@ +--- +title: 'Folder Created' +openapi-schema: folder.created +icon: folder +--- \ No newline at end of file diff --git a/docs/operations/audit-logs/folder-deleted.mdx b/docs/operations/audit-logs/folder-deleted.mdx new file mode 100644 index 0000000..4991aa6 --- /dev/null +++ b/docs/operations/audit-logs/folder-deleted.mdx @@ -0,0 +1,5 @@ +--- +title: 'Folder Deleted' +openapi-schema: folder.deleted +icon: folder +--- \ No newline at end of file diff --git a/docs/operations/audit-logs/folder-updated.mdx b/docs/operations/audit-logs/folder-updated.mdx new file mode 100644 index 0000000..4b773d9 --- /dev/null +++ b/docs/operations/audit-logs/folder-updated.mdx @@ -0,0 +1,5 @@ +--- +title: 'Folder Updated' +openapi-schema: folder.updated +icon: folder +--- \ No newline at end of file diff --git a/docs/operations/audit-logs/overview.mdx b/docs/operations/audit-logs/overview.mdx new file mode 100644 index 0000000..149df5b --- /dev/null +++ b/docs/operations/audit-logs/overview.mdx @@ -0,0 +1,10 @@ +--- +title: "Overview" +description: "" +--- + + + +This table in admin console contains all application events. We are constantly adding new events, so there is no better place to see the events defined in the code than [here](https://github.com/activepieces/activepieces/blob/main/packages/ee/shared/src/lib/audit-events/index.ts). + +![Audit Logs](/resources/screenshots/audit-logs.png) diff --git a/docs/operations/audit-logs/signing-key-created.mdx b/docs/operations/audit-logs/signing-key-created.mdx new file mode 100644 index 0000000..3e4c4f2 --- /dev/null +++ b/docs/operations/audit-logs/signing-key-created.mdx @@ -0,0 +1,5 @@ +--- +title: 'Signing Key Created' +openapi-schema: signing.key.created +icon: key +--- \ No newline at end of file diff --git a/docs/operations/audit-logs/user-email-verified.mdx b/docs/operations/audit-logs/user-email-verified.mdx new file mode 100644 index 0000000..834eb6a --- /dev/null +++ b/docs/operations/audit-logs/user-email-verified.mdx @@ -0,0 +1,5 @@ +--- +title: 'User Email Verified' +openapi-schema: user.email.verified +icon: lock +--- \ No newline at end of file diff --git a/docs/operations/audit-logs/user-password-reset.mdx b/docs/operations/audit-logs/user-password-reset.mdx new file mode 100644 index 0000000..f464751 --- /dev/null +++ b/docs/operations/audit-logs/user-password-reset.mdx @@ -0,0 +1,5 @@ +--- +title: 'User Password Reset' +openapi-schema: user.password.reset +icon: lock +--- \ No newline at end of file diff --git a/docs/operations/audit-logs/user-signed-in.mdx b/docs/operations/audit-logs/user-signed-in.mdx new file mode 100644 index 0000000..4621424 --- /dev/null +++ b/docs/operations/audit-logs/user-signed-in.mdx @@ -0,0 +1,5 @@ +--- +title: 'User Signed In' +openapi-schema: user.signed.in +icon: lock +--- \ No newline at end of file diff --git a/docs/operations/audit-logs/user-signed-up.mdx b/docs/operations/audit-logs/user-signed-up.mdx new file mode 100644 index 0000000..ad24891 --- /dev/null +++ b/docs/operations/audit-logs/user-signed-up.mdx @@ -0,0 +1,5 @@ +--- +title: 'User Signed Up' +openapi-schema: user.signed.up +icon: lock +--- \ No newline at end of file diff --git a/docs/operations/git-sync.mdx b/docs/operations/git-sync.mdx new file mode 100644 index 0000000..202e53f --- /dev/null +++ b/docs/operations/git-sync.mdx @@ -0,0 +1,105 @@ +--- +title: "Environments & Releases" +description: "" +icon: "git-alt" +--- + + + +The Project Releases feature allows for the creation of an **external backup**, **environments**, and maintaining a **version history** from the Git Repository or an existing project. + + +### How It Works + +This example explains how to set up development and production environments using either Git repositories or existing projects as sources. The setup can be extended to include multiple environments, Git branches, or projects based on your needs. + +### Requirements + +You have to enable the project releases feature in the Settings -> Environments. + +## Git-Sync + +**Requirements** +- Empty Git Repository +- Two Projects in Activepieces: one for Development and one for Production + +### 1. Push Flow to Repository +After making changes in the flow: +1. Click the 3-dot menu near the flow name +2. Select "Push to Git" +3. Add commit message and push + +### 2. Deleting Flows +When you delete a flow from a project configured with Git sync (Release from Git), it will automatically delete the flow from the repository. + +## Project-Sync + +### 1. **Initialize Projects** + - Create a source project (e.g., Development) + - Create a target project (e.g., Production) + +### 2. **Develop** + - Build and test your flows in the source project + - When ready, sync changes to the target project using releases + +## Creating a Release + + +Credentials are not synced automatically. Create identical credentials with the same names in both environments manually. + + +You can create a release in two ways: + +1. **From Git Repository**: + - Click "Create Release" and select "From Git" + +2. **From Existing Project**: + - Click "Create Release" and select "From Project" + - Choose the source project to sync from + +For both methods: +- Review the changes between environments +- Choose the operations you want to perform: + - **Update Existing Flows**: Synchronize flows that exist in both environments + - **Delete Missing Flows**: Remove flows that are no longer present in the source + - **Create New Flows**: Add new flows found in the source +- Confirm to create the release + +### Important Notes +- Enabled flows will be updated and republished (failed republishes become drafts) +- New flows start in a disabled state + +### Approval Workflow (Optional) + +To manage your approval workflow, you can use Git by creating two branches: development and production. Then, you can use standard pull requests as the approval step. + +### GitHub action +This GitHub action can be used to automatically pull changes upon merging. + + +Don't forget to replace `INSTANCE_URL` and `PROJECT_ID`, and add `ACTIVEPIECES_API_KEY` to the secrets. + + + + +```yml +name: Auto Deploy +on: + workflow_dispatch: + push: + branches: [ "main" ] +jobs: + run-pull: + runs-on: ubuntu-latest + steps: + - name: deploy + # Use GitHub secrets + run: | + curl --request POST \ + --url {INSTANCE_URL}/api/v1/git-repos/pull \ + --header 'Authorization: Bearer ${{ secrets.ACTIVEPIECES_API_KEY }}' \ + --header 'Content-Type: application/json' \ + --data '{ + "projectId": "{PROJECT_ID}" + }' +``` \ No newline at end of file diff --git a/docs/resources/architecture.png b/docs/resources/architecture.png new file mode 100644 index 0000000..09aebcd Binary files /dev/null and b/docs/resources/architecture.png differ diff --git a/docs/resources/banner.png b/docs/resources/banner.png new file mode 100644 index 0000000..2960cac Binary files /dev/null and b/docs/resources/banner.png differ diff --git a/docs/resources/beavers/pupil.png b/docs/resources/beavers/pupil.png new file mode 100644 index 0000000..f43fcd9 Binary files /dev/null and b/docs/resources/beavers/pupil.png differ diff --git a/docs/resources/color-palette.png b/docs/resources/color-palette.png new file mode 100644 index 0000000..6148fea Binary files /dev/null and b/docs/resources/color-palette.png differ diff --git a/docs/resources/create-action.png b/docs/resources/create-action.png new file mode 100644 index 0000000..f90e993 Binary files /dev/null and b/docs/resources/create-action.png differ diff --git a/docs/resources/crowdin-translate-all.png b/docs/resources/crowdin-translate-all.png new file mode 100644 index 0000000..505dd74 Binary files /dev/null and b/docs/resources/crowdin-translate-all.png differ diff --git a/docs/resources/crowdin.png b/docs/resources/crowdin.png new file mode 100644 index 0000000..2991d2d Binary files /dev/null and b/docs/resources/crowdin.png differ diff --git a/docs/resources/flow-history.png b/docs/resources/flow-history.png new file mode 100644 index 0000000..854e06a Binary files /dev/null and b/docs/resources/flow-history.png differ diff --git a/docs/resources/flow-parts.png b/docs/resources/flow-parts.png new file mode 100644 index 0000000..ba4a086 Binary files /dev/null and b/docs/resources/flow-parts.png differ diff --git a/docs/resources/french-webhooks.png b/docs/resources/french-webhooks.png new file mode 100644 index 0000000..0b27857 Binary files /dev/null and b/docs/resources/french-webhooks.png differ diff --git a/docs/resources/i18n-pieces.png b/docs/resources/i18n-pieces.png new file mode 100644 index 0000000..3d8110b Binary files /dev/null and b/docs/resources/i18n-pieces.png differ diff --git a/docs/resources/logo/dark.svg b/docs/resources/logo/dark.svg new file mode 100755 index 0000000..7a5ae90 --- /dev/null +++ b/docs/resources/logo/dark.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/docs/resources/logo/light.svg b/docs/resources/logo/light.svg new file mode 100755 index 0000000..fb017e6 --- /dev/null +++ b/docs/resources/logo/light.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/docs/resources/passing-data-3steps.mp4 b/docs/resources/passing-data-3steps.mp4 new file mode 100644 index 0000000..e5b409b Binary files /dev/null and b/docs/resources/passing-data-3steps.mp4 differ diff --git a/docs/resources/passing-data-data-to-insert-panel.mp4 b/docs/resources/passing-data-data-to-insert-panel.mp4 new file mode 100644 index 0000000..d03ad16 Binary files /dev/null and b/docs/resources/passing-data-data-to-insert-panel.mp4 differ diff --git a/docs/resources/passing-data-dynamic-value.mp4 b/docs/resources/passing-data-dynamic-value.mp4 new file mode 100644 index 0000000..4b4089a Binary files /dev/null and b/docs/resources/passing-data-dynamic-value.mp4 differ diff --git a/docs/resources/passing-data-load-data.mp4 b/docs/resources/passing-data-load-data.mp4 new file mode 100644 index 0000000..bea1bef Binary files /dev/null and b/docs/resources/passing-data-load-data.mp4 differ diff --git a/docs/resources/passing-data-main-insert-data-example.mp4 b/docs/resources/passing-data-main-insert-data-example.mp4 new file mode 100644 index 0000000..28d8762 Binary files /dev/null and b/docs/resources/passing-data-main-insert-data-example.mp4 differ diff --git a/docs/resources/passing-data-test-step-first.png b/docs/resources/passing-data-test-step-first.png new file mode 100644 index 0000000..1fb57db Binary files /dev/null and b/docs/resources/passing-data-test-step-first.png differ diff --git a/docs/resources/passing-data.gif b/docs/resources/passing-data.gif new file mode 100644 index 0000000..7f29a42 Binary files /dev/null and b/docs/resources/passing-data.gif differ diff --git a/docs/resources/profile/abdulyki.png b/docs/resources/profile/abdulyki.png new file mode 100644 index 0000000..6227971 Binary files /dev/null and b/docs/resources/profile/abdulyki.png differ diff --git a/docs/resources/profile/abood.webp b/docs/resources/profile/abood.webp new file mode 100644 index 0000000..3f842a6 Binary files /dev/null and b/docs/resources/profile/abood.webp differ diff --git a/docs/resources/profile/aboodzein.png b/docs/resources/profile/aboodzein.png new file mode 100644 index 0000000..d75a21f Binary files /dev/null and b/docs/resources/profile/aboodzein.png differ diff --git a/docs/resources/profile/amr.png b/docs/resources/profile/amr.png new file mode 100644 index 0000000..2c4a646 Binary files /dev/null and b/docs/resources/profile/amr.png differ diff --git a/docs/resources/profile/amro.png b/docs/resources/profile/amro.png new file mode 100644 index 0000000..d743fdb Binary files /dev/null and b/docs/resources/profile/amro.png differ diff --git a/docs/resources/profile/ash.png b/docs/resources/profile/ash.png new file mode 100644 index 0000000..f3c7bb8 Binary files /dev/null and b/docs/resources/profile/ash.png differ diff --git a/docs/resources/profile/ginika.png b/docs/resources/profile/ginika.png new file mode 100644 index 0000000..ad426bc Binary files /dev/null and b/docs/resources/profile/ginika.png differ diff --git a/docs/resources/profile/hazem.jpg b/docs/resources/profile/hazem.jpg new file mode 100644 index 0000000..d28ae65 Binary files /dev/null and b/docs/resources/profile/hazem.jpg differ diff --git a/docs/resources/profile/issa.png b/docs/resources/profile/issa.png new file mode 100644 index 0000000..9782722 Binary files /dev/null and b/docs/resources/profile/issa.png differ diff --git a/docs/resources/profile/kareem.png b/docs/resources/profile/kareem.png new file mode 100644 index 0000000..db06884 Binary files /dev/null and b/docs/resources/profile/kareem.png differ diff --git a/docs/resources/profile/khaled.png b/docs/resources/profile/khaled.png new file mode 100644 index 0000000..619ec51 Binary files /dev/null and b/docs/resources/profile/khaled.png differ diff --git a/docs/resources/profile/kishan.png b/docs/resources/profile/kishan.png new file mode 100644 index 0000000..61465af Binary files /dev/null and b/docs/resources/profile/kishan.png differ diff --git a/docs/resources/profile/mo.png b/docs/resources/profile/mo.png new file mode 100644 index 0000000..ee3da90 Binary files /dev/null and b/docs/resources/profile/mo.png differ diff --git a/docs/resources/profile/sanad.png b/docs/resources/profile/sanad.png new file mode 100644 index 0000000..921fabc Binary files /dev/null and b/docs/resources/profile/sanad.png differ diff --git a/docs/resources/publish-flow.png b/docs/resources/publish-flow.png new file mode 100644 index 0000000..632e4e1 Binary files /dev/null and b/docs/resources/publish-flow.png differ diff --git a/docs/resources/pylon-board.png b/docs/resources/pylon-board.png new file mode 100644 index 0000000..4467db0 Binary files /dev/null and b/docs/resources/pylon-board.png differ diff --git a/docs/resources/scale-pieces-cli.gif b/docs/resources/scale-pieces-cli.gif new file mode 100644 index 0000000..9125a06 Binary files /dev/null and b/docs/resources/scale-pieces-cli.gif differ diff --git a/docs/resources/screenshots/activation-license-key-settings.png b/docs/resources/screenshots/activation-license-key-settings.png new file mode 100644 index 0000000..508ff32 Binary files /dev/null and b/docs/resources/screenshots/activation-license-key-settings.png differ diff --git a/docs/resources/screenshots/activity-finished.png b/docs/resources/screenshots/activity-finished.png new file mode 100644 index 0000000..9193f99 Binary files /dev/null and b/docs/resources/screenshots/activity-finished.png differ diff --git a/docs/resources/screenshots/activity-started.png b/docs/resources/screenshots/activity-started.png new file mode 100644 index 0000000..7752361 Binary files /dev/null and b/docs/resources/screenshots/activity-started.png differ diff --git a/docs/resources/screenshots/ai-credits-limit.png b/docs/resources/screenshots/ai-credits-limit.png new file mode 100644 index 0000000..7014b82 Binary files /dev/null and b/docs/resources/screenshots/ai-credits-limit.png differ diff --git a/docs/resources/screenshots/appearance.png b/docs/resources/screenshots/appearance.png new file mode 100644 index 0000000..e38dd71 Binary files /dev/null and b/docs/resources/screenshots/appearance.png differ diff --git a/docs/resources/screenshots/audit-logs.png b/docs/resources/screenshots/audit-logs.png new file mode 100644 index 0000000..631517c Binary files /dev/null and b/docs/resources/screenshots/audit-logs.png differ diff --git a/docs/resources/screenshots/branch-settings.png b/docs/resources/screenshots/branch-settings.png new file mode 100644 index 0000000..b3c11e2 Binary files /dev/null and b/docs/resources/screenshots/branch-settings.png differ diff --git a/docs/resources/screenshots/branch.png b/docs/resources/screenshots/branch.png new file mode 100644 index 0000000..52dfca6 Binary files /dev/null and b/docs/resources/screenshots/branch.png differ diff --git a/docs/resources/screenshots/branding.png b/docs/resources/screenshots/branding.png new file mode 100644 index 0000000..7cea6d1 Binary files /dev/null and b/docs/resources/screenshots/branding.png differ diff --git a/docs/resources/screenshots/bullboard-ui.png b/docs/resources/screenshots/bullboard-ui.png new file mode 100644 index 0000000..daf18c7 Binary files /dev/null and b/docs/resources/screenshots/bullboard-ui.png differ diff --git a/docs/resources/screenshots/code-fullscreen.png b/docs/resources/screenshots/code-fullscreen.png new file mode 100644 index 0000000..6e5f99d Binary files /dev/null and b/docs/resources/screenshots/code-fullscreen.png differ diff --git a/docs/resources/screenshots/code-settings.png b/docs/resources/screenshots/code-settings.png new file mode 100644 index 0000000..68102de Binary files /dev/null and b/docs/resources/screenshots/code-settings.png differ diff --git a/docs/resources/screenshots/configure-ai-provider.png b/docs/resources/screenshots/configure-ai-provider.png new file mode 100644 index 0000000..d85478d Binary files /dev/null and b/docs/resources/screenshots/configure-ai-provider.png differ diff --git a/docs/resources/screenshots/connection-dialog-builder.png b/docs/resources/screenshots/connection-dialog-builder.png new file mode 100644 index 0000000..1f35703 Binary files /dev/null and b/docs/resources/screenshots/connection-dialog-builder.png differ diff --git a/docs/resources/screenshots/connection-dialog-sdk.png b/docs/resources/screenshots/connection-dialog-sdk.png new file mode 100644 index 0000000..7c1df13 Binary files /dev/null and b/docs/resources/screenshots/connection-dialog-sdk.png differ diff --git a/docs/resources/screenshots/connections-piece-usage.png b/docs/resources/screenshots/connections-piece-usage.png new file mode 100644 index 0000000..04d2156 Binary files /dev/null and b/docs/resources/screenshots/connections-piece-usage.png differ diff --git a/docs/resources/screenshots/connections-piece.png b/docs/resources/screenshots/connections-piece.png new file mode 100644 index 0000000..1f64460 Binary files /dev/null and b/docs/resources/screenshots/connections-piece.png differ diff --git a/docs/resources/screenshots/connections-table.png b/docs/resources/screenshots/connections-table.png new file mode 100644 index 0000000..19558a3 Binary files /dev/null and b/docs/resources/screenshots/connections-table.png differ diff --git a/docs/resources/screenshots/contributing-first-connector.png b/docs/resources/screenshots/contributing-first-connector.png new file mode 100755 index 0000000..afe3514 Binary files /dev/null and b/docs/resources/screenshots/contributing-first-connector.png differ diff --git a/docs/resources/screenshots/create-api-key.png b/docs/resources/screenshots/create-api-key.png new file mode 100644 index 0000000..a8cc167 Binary files /dev/null and b/docs/resources/screenshots/create-api-key.png differ diff --git a/docs/resources/screenshots/create-global-connection.png b/docs/resources/screenshots/create-global-connection.png new file mode 100644 index 0000000..e68806c Binary files /dev/null and b/docs/resources/screenshots/create-global-connection.png differ diff --git a/docs/resources/screenshots/create-signing-key.png b/docs/resources/screenshots/create-signing-key.png new file mode 100644 index 0000000..48409a9 Binary files /dev/null and b/docs/resources/screenshots/create-signing-key.png differ diff --git a/docs/resources/screenshots/custom-domain.png b/docs/resources/screenshots/custom-domain.png new file mode 100644 index 0000000..46d30f5 Binary files /dev/null and b/docs/resources/screenshots/custom-domain.png differ diff --git a/docs/resources/screenshots/data-to-insert.png b/docs/resources/screenshots/data-to-insert.png new file mode 100644 index 0000000..51215db Binary files /dev/null and b/docs/resources/screenshots/data-to-insert.png differ diff --git a/docs/resources/screenshots/development-setup_codespaces.png b/docs/resources/screenshots/development-setup_codespaces.png new file mode 100644 index 0000000..27e6a2c Binary files /dev/null and b/docs/resources/screenshots/development-setup_codespaces.png differ diff --git a/docs/resources/screenshots/docker-ngrok.png b/docs/resources/screenshots/docker-ngrok.png new file mode 100644 index 0000000..e612102 Binary files /dev/null and b/docs/resources/screenshots/docker-ngrok.png differ diff --git a/docs/resources/screenshots/dynamic-value.png b/docs/resources/screenshots/dynamic-value.png new file mode 100644 index 0000000..ca9035c Binary files /dev/null and b/docs/resources/screenshots/dynamic-value.png differ diff --git a/docs/resources/screenshots/embedding-example.png b/docs/resources/screenshots/embedding-example.png new file mode 100644 index 0000000..418ee41 Binary files /dev/null and b/docs/resources/screenshots/embedding-example.png differ diff --git a/docs/resources/screenshots/fork-repository.jpg b/docs/resources/screenshots/fork-repository.jpg new file mode 100644 index 0000000..e7f755f Binary files /dev/null and b/docs/resources/screenshots/fork-repository.jpg differ diff --git a/docs/resources/screenshots/gelato-action.png b/docs/resources/screenshots/gelato-action.png new file mode 100644 index 0000000..8b3088d Binary files /dev/null and b/docs/resources/screenshots/gelato-action.png differ diff --git a/docs/resources/screenshots/gelato-trigger.png b/docs/resources/screenshots/gelato-trigger.png new file mode 100644 index 0000000..87ba19a Binary files /dev/null and b/docs/resources/screenshots/gelato-trigger.png differ diff --git a/docs/resources/screenshots/http-request-settings.png b/docs/resources/screenshots/http-request-settings.png new file mode 100755 index 0000000..7706c51 Binary files /dev/null and b/docs/resources/screenshots/http-request-settings.png differ diff --git a/docs/resources/screenshots/inside-loop-settings.png b/docs/resources/screenshots/inside-loop-settings.png new file mode 100644 index 0000000..b36ba5d Binary files /dev/null and b/docs/resources/screenshots/inside-loop-settings.png differ diff --git a/docs/resources/screenshots/install-piece.png b/docs/resources/screenshots/install-piece.png new file mode 100644 index 0000000..46ccb0a Binary files /dev/null and b/docs/resources/screenshots/install-piece.png differ diff --git a/docs/resources/screenshots/invite-customer.png b/docs/resources/screenshots/invite-customer.png new file mode 100644 index 0000000..85c728b Binary files /dev/null and b/docs/resources/screenshots/invite-customer.png differ diff --git a/docs/resources/screenshots/jumpcloud/acl-url.png b/docs/resources/screenshots/jumpcloud/acl-url.png new file mode 100644 index 0000000..3d8eb95 Binary files /dev/null and b/docs/resources/screenshots/jumpcloud/acl-url.png differ diff --git a/docs/resources/screenshots/jumpcloud/declare-login.png b/docs/resources/screenshots/jumpcloud/declare-login.png new file mode 100644 index 0000000..3dd52e7 Binary files /dev/null and b/docs/resources/screenshots/jumpcloud/declare-login.png differ diff --git a/docs/resources/screenshots/jumpcloud/export-metadata.png b/docs/resources/screenshots/jumpcloud/export-metadata.png new file mode 100644 index 0000000..3a73ffa Binary files /dev/null and b/docs/resources/screenshots/jumpcloud/export-metadata.png differ diff --git a/docs/resources/screenshots/jumpcloud/user-attribute.png b/docs/resources/screenshots/jumpcloud/user-attribute.png new file mode 100644 index 0000000..fe3fe55 Binary files /dev/null and b/docs/resources/screenshots/jumpcloud/user-attribute.png differ diff --git a/docs/resources/screenshots/jumpcloud/user-groups.png b/docs/resources/screenshots/jumpcloud/user-groups.png new file mode 100644 index 0000000..6900f46 Binary files /dev/null and b/docs/resources/screenshots/jumpcloud/user-groups.png differ diff --git a/docs/resources/screenshots/loop-custom-path.png b/docs/resources/screenshots/loop-custom-path.png new file mode 100644 index 0000000..3c87d22 Binary files /dev/null and b/docs/resources/screenshots/loop-custom-path.png differ diff --git a/docs/resources/screenshots/loop-settings.png b/docs/resources/screenshots/loop-settings.png new file mode 100644 index 0000000..6a442a8 Binary files /dev/null and b/docs/resources/screenshots/loop-settings.png differ diff --git a/docs/resources/screenshots/loops.png b/docs/resources/screenshots/loops.png new file mode 100644 index 0000000..07a5e1f Binary files /dev/null and b/docs/resources/screenshots/loops.png differ diff --git a/docs/resources/screenshots/manage-oauth2.png b/docs/resources/screenshots/manage-oauth2.png new file mode 100644 index 0000000..c87d970 Binary files /dev/null and b/docs/resources/screenshots/manage-oauth2.png differ diff --git a/docs/resources/screenshots/manage-pieces-2.png b/docs/resources/screenshots/manage-pieces-2.png new file mode 100644 index 0000000..46c47ae Binary files /dev/null and b/docs/resources/screenshots/manage-pieces-2.png differ diff --git a/docs/resources/screenshots/manage-pieces.png b/docs/resources/screenshots/manage-pieces.png new file mode 100644 index 0000000..5f42e6b Binary files /dev/null and b/docs/resources/screenshots/manage-pieces.png differ diff --git a/docs/resources/screenshots/manage-projects.png b/docs/resources/screenshots/manage-projects.png new file mode 100644 index 0000000..efb6667 Binary files /dev/null and b/docs/resources/screenshots/manage-projects.png differ diff --git a/docs/resources/screenshots/manage-smtp.png b/docs/resources/screenshots/manage-smtp.png new file mode 100644 index 0000000..5f3471f Binary files /dev/null and b/docs/resources/screenshots/manage-smtp.png differ diff --git a/docs/resources/screenshots/manage-templates.png b/docs/resources/screenshots/manage-templates.png new file mode 100644 index 0000000..c95648b Binary files /dev/null and b/docs/resources/screenshots/manage-templates.png differ diff --git a/docs/resources/screenshots/multi-tenancy-connection-success.png b/docs/resources/screenshots/multi-tenancy-connection-success.png new file mode 100644 index 0000000..b9a91ad Binary files /dev/null and b/docs/resources/screenshots/multi-tenancy-connection-success.png differ diff --git a/docs/resources/screenshots/new-connection.png b/docs/resources/screenshots/new-connection.png new file mode 100644 index 0000000..77c4fe9 Binary files /dev/null and b/docs/resources/screenshots/new-connection.png differ diff --git a/docs/resources/screenshots/piece-version-screenshot.png b/docs/resources/screenshots/piece-version-screenshot.png new file mode 100644 index 0000000..3d91c78 Binary files /dev/null and b/docs/resources/screenshots/piece-version-screenshot.png differ diff --git a/docs/resources/screenshots/project-settings.png b/docs/resources/screenshots/project-settings.png new file mode 100644 index 0000000..0465e01 Binary files /dev/null and b/docs/resources/screenshots/project-settings.png differ diff --git a/docs/resources/screenshots/signing-key-id.png b/docs/resources/screenshots/signing-key-id.png new file mode 100644 index 0000000..609d78f Binary files /dev/null and b/docs/resources/screenshots/signing-key-id.png differ diff --git a/docs/resources/screenshots/simple-webhook-flow.png b/docs/resources/screenshots/simple-webhook-flow.png new file mode 100644 index 0000000..7566cd3 Binary files /dev/null and b/docs/resources/screenshots/simple-webhook-flow.png differ diff --git a/docs/resources/screenshots/sso.png b/docs/resources/screenshots/sso.png new file mode 100644 index 0000000..c675a6c Binary files /dev/null and b/docs/resources/screenshots/sso.png differ diff --git a/docs/resources/screenshots/tag-pieces.png b/docs/resources/screenshots/tag-pieces.png new file mode 100644 index 0000000..d277892 Binary files /dev/null and b/docs/resources/screenshots/tag-pieces.png differ diff --git a/docs/resources/screenshots/team-screenshot.png b/docs/resources/screenshots/team-screenshot.png new file mode 100644 index 0000000..f647d56 Binary files /dev/null and b/docs/resources/screenshots/team-screenshot.png differ diff --git a/docs/resources/screenshots/testing-predefined-connections-creation.png b/docs/resources/screenshots/testing-predefined-connections-creation.png new file mode 100644 index 0000000..be72111 Binary files /dev/null and b/docs/resources/screenshots/testing-predefined-connections-creation.png differ diff --git a/docs/resources/screenshots/use-ai-provider.png b/docs/resources/screenshots/use-ai-provider.png new file mode 100644 index 0000000..64f33cc Binary files /dev/null and b/docs/resources/screenshots/use-ai-provider.png differ diff --git a/docs/resources/screenshots/using-activepieces-code-output.png b/docs/resources/screenshots/using-activepieces-code-output.png new file mode 100644 index 0000000..e5c0cdd Binary files /dev/null and b/docs/resources/screenshots/using-activepieces-code-output.png differ diff --git a/docs/resources/screenshots/using-activepieces-code-params.png b/docs/resources/screenshots/using-activepieces-code-params.png new file mode 100644 index 0000000..04cd78d Binary files /dev/null and b/docs/resources/screenshots/using-activepieces-code-params.png differ diff --git a/docs/resources/screenshots/using-activepieces-debugging.png b/docs/resources/screenshots/using-activepieces-debugging.png new file mode 100755 index 0000000..6d64521 Binary files /dev/null and b/docs/resources/screenshots/using-activepieces-debugging.png differ diff --git a/docs/resources/screenshots/using-activepieces-json-input.png b/docs/resources/screenshots/using-activepieces-json-input.png new file mode 100644 index 0000000..74262df Binary files /dev/null and b/docs/resources/screenshots/using-activepieces-json-input.png differ diff --git a/docs/resources/screenshots/using-activepieces-overview-choose-trigger.png b/docs/resources/screenshots/using-activepieces-overview-choose-trigger.png new file mode 100755 index 0000000..d53659e Binary files /dev/null and b/docs/resources/screenshots/using-activepieces-overview-choose-trigger.png differ diff --git a/docs/resources/screenshots/using-activepieces-overview-invalid-settings.png b/docs/resources/screenshots/using-activepieces-overview-invalid-settings.png new file mode 100755 index 0000000..eb3f1d5 Binary files /dev/null and b/docs/resources/screenshots/using-activepieces-overview-invalid-settings.png differ diff --git a/docs/resources/screenshots/using-activepieces-overview-main-screen.png b/docs/resources/screenshots/using-activepieces-overview-main-screen.png new file mode 100755 index 0000000..ce425ec Binary files /dev/null and b/docs/resources/screenshots/using-activepieces-overview-main-screen.png differ diff --git a/docs/resources/screenshots/using-activepieces-overview-piece-settings.png b/docs/resources/screenshots/using-activepieces-overview-piece-settings.png new file mode 100755 index 0000000..f201d30 Binary files /dev/null and b/docs/resources/screenshots/using-activepieces-overview-piece-settings.png differ diff --git a/docs/resources/screenshots/using-activepieces-overview-pieces-panel.png b/docs/resources/screenshots/using-activepieces-overview-pieces-panel.png new file mode 100755 index 0000000..1813aed Binary files /dev/null and b/docs/resources/screenshots/using-activepieces-overview-pieces-panel.png differ diff --git a/docs/resources/screenshots/using-activepieces-step-name.png b/docs/resources/screenshots/using-activepieces-step-name.png new file mode 100755 index 0000000..7dd14bd Binary files /dev/null and b/docs/resources/screenshots/using-activepieces-step-name.png differ diff --git a/docs/resources/screenshots/using-activepieces-triggers-schedule.png b/docs/resources/screenshots/using-activepieces-triggers-schedule.png new file mode 100755 index 0000000..9918fa5 Binary files /dev/null and b/docs/resources/screenshots/using-activepieces-triggers-schedule.png differ diff --git a/docs/resources/screenshots/using-activepieces-triggers-webhook.png b/docs/resources/screenshots/using-activepieces-triggers-webhook.png new file mode 100755 index 0000000..336c001 Binary files /dev/null and b/docs/resources/screenshots/using-activepieces-triggers-webhook.png differ diff --git a/docs/resources/templates.gif b/docs/resources/templates.gif new file mode 100644 index 0000000..ee4f024 Binary files /dev/null and b/docs/resources/templates.gif differ diff --git a/docs/resources/unified-ai.png b/docs/resources/unified-ai.png new file mode 100644 index 0000000..15932f2 Binary files /dev/null and b/docs/resources/unified-ai.png differ diff --git a/docs/resources/visual-builder.gif b/docs/resources/visual-builder.gif new file mode 100644 index 0000000..ab5d816 Binary files /dev/null and b/docs/resources/visual-builder.gif differ diff --git a/docs/resources/worker-token.png b/docs/resources/worker-token.png new file mode 100644 index 0000000..83f552c Binary files /dev/null and b/docs/resources/worker-token.png differ diff --git a/docs/resources/workers.png b/docs/resources/workers.png new file mode 100644 index 0000000..99004ac Binary files /dev/null and b/docs/resources/workers.png differ diff --git a/docs/script.js b/docs/script.js new file mode 100644 index 0000000..5b7de8f --- /dev/null +++ b/docs/script.js @@ -0,0 +1,114 @@ +"use client"; + +// Get the Mintlify search containers, going to reuse them as the triggers for Inkeep +const searchButtonContainerIds = [ + "search-bar-entry", + "search-bar-entry-mobile", +]; + +// Clone and replace, needed to remove existing event listeners +const clonedSearchButtonContainers = searchButtonContainerIds.map((id) => { + const originalElement = document.getElementById(id); + const clonedElement = originalElement.cloneNode(true); + + originalElement.parentNode.replaceChild(clonedElement, originalElement); + + return clonedElement; +}); + +// Load the Inkeep script +const inkeepScript = document.createElement("script"); +inkeepScript.type = "module"; +inkeepScript.src = + "https://unpkg.com/@inkeep/widgets-embed@0.2.262/dist/embed.js"; +document.body.appendChild(inkeepScript); + +// Once the Inkeep script has loaded, load the Inkeep chat components +inkeepScript.addEventListener("load", function () { + // Customization settings + const sharedConfig = { + baseSettings: { + apiKey: "9b77dccb6cacb770614645da24b68c168dc213967b015a6f", + integrationId: "clt6g42dh0008hl8duk922fjd", + organizationId: "org_hCC6MgTLBCh3juv4", + primaryBrandColor: "#8143E3", + }, + aiChatSettings: { + chatSubjectName: "Activepieces", + botAvatarSrcUrl: + "https://storage.googleapis.com/organization-image-assets/activepieces-botAvatarSrcUrl-1709136841325.svg", + botAvatarDarkSrcUrl: + "https://storage.googleapis.com/organization-image-assets/activepieces-botAvatarDarkSrcUrl-1709136840372.svg", + getHelpCallToActions: [ + { + name: "Discord", + url: "https://discord.gg/2jUXBKDdP8", + icon: { + builtIn: "FaDiscord", + }, + }, + { + name: "Community", + url: "https://community.activepieces.com/", + icon: { + builtIn: "IoPeopleOutline", + }, + }, + { + name: "GitHub", + url: "https://github.com/activepieces/activepieces", + icon: { + builtIn: "FaGithub", + }, + }, + ], + quickQuestions: [ + "How do I conditionally change a flow?", + "Can I incorporate custom external APIs?", + "How do I monitor usage?", + ], + }, + }; + + // for syncing with dark mode + const colorModeSettings = { + observedElement: document.documentElement, + isDarkModeCallback: (el) => { + return el.classList.contains("dark"); + }, + colorModeAttribute: "class", + }; + + // add the "Ask AI" pill chat button + Inkeep().embed({ + componentType: "ChatButton", + colorModeSync: colorModeSettings, + properties: sharedConfig, + }); + + // instantiate Inkeep "custom trigger" component + const inkeepSearchModal = Inkeep({ + ...sharedConfig.baseSettings, + }).embed({ + componentType: "CustomTrigger", + colorModeSync: colorModeSettings, + properties: { + ...sharedConfig, + isOpen: false, + onClose: () => { + inkeepSearchModal.render({ + isOpen: false, + }); + }, + }, + }); + + // When the Mintlify search bar clone is clicked, open the Inkeep search modal + clonedSearchButtonContainers.forEach((trigger) => { + trigger.addEventListener("click", function () { + inkeepSearchModal.render({ + isOpen: true, + }); + }); + }); +}); diff --git a/docs/security/permissions.mdx b/docs/security/permissions.mdx new file mode 100644 index 0000000..6af3541 --- /dev/null +++ b/docs/security/permissions.mdx @@ -0,0 +1,52 @@ +--- +title: "Project Permissions" +description: "Documentation on project permissions in Activepieces" +icon: 'user' +--- + +Activepieces utilizes Role-Based Access Control (RBAC) for managing permissions within projects. Each project consists of multiple flows and users, with each user assigned specific roles that define their actions within the project. + +The supported roles in Activepieces are: + +- **Admin:** + - View Flows + - Edit Flows + - Publish/Turn On and Off Flows + - View Runs + - Retry Runs + - View Issues + - Resolve Issues + - View Connections + - Edit Connections + - View Project Members + - Add/Remove Project Members + - Configure Git Repo to Sync Flows With + - Push/Pull Flows to/from Git Repo + +- **Editor:** + - View Flows + - Edit Flows + - Publish/Turn On and Off Flows + - View Runs + - Retry Runs + - View Connections + - Edit Connections + - View Issues + - Resolve Issues + - View Project Members + +- **Operator:** + - Publish/Turn On and Off Flows + - View Runs + - Retry Runs + - View Issues + - View Connections + - Edit Connections + - View Project Members + +- **Viewer:** + - View Flows + - View Runs + - View Connections + - View Project Members + - View Issues \ No newline at end of file diff --git a/docs/security/practices.mdx b/docs/security/practices.mdx new file mode 100644 index 0000000..4dd2754 --- /dev/null +++ b/docs/security/practices.mdx @@ -0,0 +1,56 @@ +--- +title: "Security & Data Practices" +description: "We prioritize security and follow these practices to keep information safe." +icon: 'lock' +--- + +## External Systems Credentials + +**Storing Credentials** + +All credentials are stored with 256-bit encryption keys, and there is no API to retrieve them for the user. They are sent only during processing, after which access is revoked from the engine. + +**Data Masking** + +We implement a robust data masking mechanism where third-party credentials or any sensitive information are systematically censored within the logs, guaranteeing that sensitive information is never stored or documented. + +**OAuth2** + +Integrations with third parties are always done using OAuth2, with a limited number of scopes when third-party support allows. + +## Vulnerability Disclosure + +Activepieces is an open-source project that welcomes contributors to test and report security issues. + +For detailed information about our security policy, please refer to our GitHub Security Policy at: [https://github.com/activepieces/activepieces/security/policy](https://github.com/activepieces/activepieces/security/policy) + +## Access and Authentication + +**Role-Based Access Control (RBAC)** + +To manage user access, we utilize Role-Based Access Control (RBAC). Team admins assign roles to users, granting them specific permissions to access and interact with projects, folders, and resources. RBAC allows for fine-grained control, enabling administrators to define and enforce access policies based on user roles. + +**Single Sign-On (SSO)** + +Implementing Single Sign-On (SSO) serves as a pivotal component of our security strategy. SSO streamlines user authentication by allowing them to access Activepieces with a single set of credentials. This not only enhances user convenience but also strengthens security by reducing the potential attack surface associated with managing multiple login credentials. + +**Audit Logs** + +We maintain comprehensive audit logs to track and monitor all access activities within Activepieces. This includes user interactions, system changes, and other relevant events. Our meticulous logging helps identify security threats and ensures transparency and accountability in our security measures. + +**Password Policy Enforcement** + +Users log in to Activepieces using a password known only to them. Activepieces enforces password length and complexity standards. Passwords are not stored; instead, only a secure hash of the password is stored in the database. For more information. + +## Privacy & Data + +**Supported Cloud Regions** + +Presently, our cloud services are available in Germany as the supported data region. + +We have plans to expand to additional regions in the near future. +If you opt for **self-hosting**, the available regions will depend on where you choose to host. + +**Policy** + +To better understand how we handle your data and prioritize your privacy, please take a moment to review our [Privacy Policy](https://www.activepieces.com/privacy). This document outlines in detail the measures we take to safeguard your information and the principles guiding our approach to privacy and data protection. diff --git a/docs/security/sso.mdx b/docs/security/sso.mdx new file mode 100644 index 0000000..57117c2 --- /dev/null +++ b/docs/security/sso.mdx @@ -0,0 +1,101 @@ +--- +title: "Single Sign-On" +description: "" +icon: 'key' +--- + + + + +## Enforcing SSO + +You can enforce SSO by specifying the domain. As part of the SSO configuration, you have the option to disable email and user login. This ensures that all authentication is routed through the designated SSO provider. + + +![SSO](/resources/screenshots/sso.png) + + +## Supported SSO Providers + +You can enable various SSO providers, including Google and GitHub, to integrate with your system by configuring SSO. + +### Google: + + + + + + + + + +### GitHub: + + + + + + + + + + + + +### SAML with OKTA: + + + + + + + + + + + + + + + +### SAML with JumpCloud: + + + + + + ![JumpCloud ACS URL](/resources/screenshots/jumpcloud/acl-url.png) + + + + ![JumpCloud User Attributes](/resources/screenshots/jumpcloud/user-attribute.png) + + + JumpCloud does not provide the `HTTP-Redirect` binding by default. You need to tick this box. + ![JumpCloud Redirect Binding](/resources/screenshots/jumpcloud/declare-login.png) + + Make sure you press `Save` and then Refresh the Page and Click on `Export Metadata` + + ![JumpCloud Export Metadata](/resources/screenshots/jumpcloud/export-metadata.png) + + + Please Verify ` Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"` inside the xml. + + + After you export the metadata, paste it in the `Idp Metadata` field. + + + + Find the `` element in the IDP metadata and copy its value. Paste it between these lines: + + ``` + -----BEGIN CERTIFICATE----- + [PASTE THE VALUE FROM IDP METADATA] + -----END CERTIFICATE----- + ``` + + + ![JumpCloud Assign App](/resources/screenshots/jumpcloud/user-groups.png) + + + \ No newline at end of file diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 0000000..6b3f2d6 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,5 @@ +import { getJestProjectsAsync } from '@nx/jest'; + +export default async () => ({ + projects: await getJestProjectsAsync(), +}); diff --git a/jest.preset.js b/jest.preset.js new file mode 100644 index 0000000..950792c --- /dev/null +++ b/jest.preset.js @@ -0,0 +1,15 @@ +const nxPreset = require('@nx/jest/preset').default; + +module.exports = { + ...nxPreset, + /* TODO: Update to latest Jest snapshotFormat + * By default Nx has kept the older style of Jest Snapshot formats + * to prevent breaking of any existing tests with snapshots. + * It's recommend you update to the latest format. + * You can do this by removing snapshotFormat property + * and running tests with --update-snapshot flag. + * Example: "nx affected --targets=test,run-tests --update-snapshot" + * More info: https://jestjs.io/docs/upgrading-to-jest29#snapshot-format + */ + snapshotFormat: { escapeString: true, printBasicPrototype: true }, +}; diff --git a/migrations.json b/migrations.json new file mode 100644 index 0000000..dde590a --- /dev/null +++ b/migrations.json @@ -0,0 +1,126 @@ +{ + "migrations": [ + { + "version": "21.0.0-beta.8", + "description": "Removes the legacy cache configuration from nx.json", + "implementation": "./src/migrations/update-21-0-0/remove-legacy-cache", + "package": "nx", + "name": "remove-legacy-cache" + }, + { + "version": "21.0.0-beta.8", + "description": "Removes the legacy cache configuration from nx.json", + "implementation": "./src/migrations/update-21-0-0/remove-custom-tasks-runner", + "package": "nx", + "name": "remove-custom-tasks-runner" + }, + { + "version": "21.0.0-beta.11", + "description": "Updates release version config based on the breaking changes in Nx v21", + "implementation": "./src/migrations/update-21-0-0/release-version-config-changes", + "package": "nx", + "name": "release-version-config-changes" + }, + { + "version": "21.0.0-beta.11", + "description": "Updates release changelog config based on the breaking changes in Nx v21", + "implementation": "./src/migrations/update-21-0-0/release-changelog-config-changes", + "package": "nx", + "name": "release-changelog-config-changes" + }, + { + "version": "21.1.0-beta.2", + "description": "Adds **/nx-rules.mdc and **/nx.instructions.md to .gitignore if not present", + "implementation": "./src/migrations/update-21-1-0/add-gitignore-entry", + "package": "nx", + "name": "21-1-0-add-ignore-entries-for-nx-rule-files" + }, + { + "version": "20.2.0-beta.5", + "description": "Update TypeScript ESLint packages to v8.13.0 if they are already on v8", + "implementation": "./src/migrations/update-20-2-0/update-typescript-eslint-v8-13-0", + "package": "@nx/eslint", + "name": "update-typescript-eslint-v8.13.0" + }, + { + "version": "20.3.0-beta.1", + "description": "Update ESLint flat config to include .cjs, .mjs, .cts, and .mts files in overrides (if needed)", + "implementation": "./src/migrations/update-20-3-0/add-file-extensions-to-overrides", + "package": "@nx/eslint", + "name": "add-file-extensions-to-overrides" + }, + { + "cli": "nx", + "version": "21.0.0-beta.9", + "description": "Replace usage of `getJestProjects` with `getJestProjectsAsync`.", + "implementation": "./src/migrations/update-21-0-0/replace-getJestProjects-with-getJestProjectsAsync", + "package": "@nx/jest", + "name": "replace-getJestProjects-with-getJestProjectsAsync-v21" + }, + { + "version": "21.0.0-beta.10", + "description": "Remove the previously deprecated and unused `tsConfig` option from the `@nx/jest:jest` executor.", + "implementation": "./src/migrations/update-21-0-0/remove-tsconfig-option-from-jest-executor", + "package": "@nx/jest", + "name": "remove-tsconfig-option-from-jest-executor" + }, + { + "version": "20.0.4-beta.0", + "description": "Add gitignore entry for temporary vite config files.", + "implementation": "./src/migrations/update-20-0-4/add-vite-temp-files-to-git-ignore", + "package": "@nx/vite", + "name": "update-20-0-4" + }, + { + "version": "20.0.6-beta.0", + "description": "Add gitignore entry for temporary vite config files and remove previous incorrect glob.", + "implementation": "./src/migrations/update-20-0-4/add-vite-temp-files-to-git-ignore", + "package": "@nx/vite", + "name": "update-20-0-6" + }, + { + "version": "20.3.0-beta.2", + "description": "Add gitignore entry for temporary vitest config files.", + "implementation": "./src/migrations/update-20-3-0/add-vitest-temp-files-to-git-ignore", + "package": "@nx/vite", + "name": "update-20-3-0" + }, + { + "version": "20.5.0-beta.2", + "description": "Install jiti as a devDependency to allow vite to parse TS postcss files.", + "implementation": "./src/migrations/update-20-5-0/install-jiti", + "package": "@nx/vite", + "name": "update-20-5-0-install-jiti" + }, + { + "version": "20.5.0-beta.3", + "description": "Update resolve.conditions to include defaults that are no longer provided by Vite.", + "implementation": "./src/migrations/update-20-5-0/update-resolve-conditions", + "package": "@nx/vite", + "name": "update-20-5-0-update-resolve-conditions" + }, + { + "version": "20.5.0-beta.3", + "description": "Add vite config temporary files to the ESLint configuration ignore patterns if ESLint is used.", + "implementation": "./src/migrations/update-20-5-0/eslint-ignore-vite-temp-files", + "package": "@nx/vite", + "name": "eslint-ignore-vite-temp-files" + }, + { + "cli": "nx", + "version": "21.0.0-beta.11", + "description": "Replaces `classProperties.loose` option with `loose`.", + "factory": "./src/migrations/update-21-0-0/update-babel-loose", + "package": "@nx/react", + "name": "update-21-0-0-update-babel-loose" + }, + { + "cli": "nx", + "version": "21.0.0-beta.11", + "description": "Remove isolatedConfig option for @nx/webpack:webpack", + "implementation": "./src/migrations/update-21-0-0/remove-isolated-config", + "package": "@nx/webpack", + "name": "update-21-0-0-remove-isolated-config" + } + ] +} diff --git a/nginx.react.conf b/nginx.react.conf new file mode 100644 index 0000000..12866f6 --- /dev/null +++ b/nginx.react.conf @@ -0,0 +1,59 @@ +events{} +http { + include /etc/nginx/mime.types; + client_max_body_size 100m; + + server_tokens off; + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; + add_header X-Content-Type-Options "nosniff" always; + + server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + error_page 404 /404.html; + location = /404.html { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html; + } + + location /socket.io { + proxy_pass http://localhost:3000/socket.io; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + proxy_read_timeout 900s; + proxy_send_timeout 900s; + } + + location ~* ^/(?!api/).*.(css|js|jpg|jpeg|png|gif|ico|svg)$ { + root /usr/share/nginx/html; + add_header Expires "0"; + add_header Cache-Control "public, max-age=31536000, immutable"; + } + + # Use the default language for the root of the application + location / { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html?$args; + } + + location /api/ { + proxy_pass http://localhost:3000/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + proxy_read_timeout 900s; + proxy_send_timeout 900s; + + # SSE specific settings + proxy_buffering off; + proxy_cache off; + } + } +} diff --git a/nx.json b/nx.json new file mode 100644 index 0000000..415a1f2 --- /dev/null +++ b/nx.json @@ -0,0 +1,124 @@ +{ + "$schema": "./node_modules/nx/schemas/nx-schema.json", + "targetDefaults": { + "build": { + "dependsOn": ["^build"], + "inputs": ["production", "^production"], + "cache": true + }, + "lint": { + "inputs": ["default", "{workspaceRoot}/.eslintrc.json"], + "cache": true + }, + "test": { + "inputs": [ + "default", + "^production", + "{workspaceRoot}/jest.preset.js" + ], + "cache": true + }, + "@nx/jest:jest": { + "cache": true, + "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"], + "options": { + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + }, + "@nx/eslint:lint": { + "inputs": ["default", "{workspaceRoot}/.eslintrc.json"], + "cache": true + }, + "@nx/js:tsc": { + "cache": true, + "dependsOn": ["^build"], + "inputs": ["production", "^production"] + } + }, + "namedInputs": { + "default": ["{projectRoot}/**/*", "sharedGlobals"], + "production": [ + "default", + "!{projectRoot}/.eslintrc.json", + "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", + "!{projectRoot}/tsconfig.spec.json", + "!{projectRoot}/jest.config.[jt]s", + "!{projectRoot}/**/*.spec.[jt]s", + "!{projectRoot}/src/test-setup.[jt]s" + ], + "sharedGlobals": [] + }, + "workspaceLayout": { + "appsDir": "packages", + "libsDir": "packages" + }, + "generators": { + "@nx/workspace:move": { + "projectNameAndRootFormat": "as-provided" + }, + "@nx/react": { + "application": { + "babel": true, + "style": "tailwind", + "linter": "eslint", + "bundler": "vite" + }, + "component": { + "style": "tailwind" + }, + "library": { + "style": "tailwind", + "linter": "eslint" + } + } + }, + "nxCloudAccessToken": "Y2Y3YzhhNjYtZWI4OC00M2Y0LWIwY2ItMWJhNTBjYzA3ZDA0fHJlYWQtb25seQ==", + "plugins": [ + { + "plugin": "@nx/eslint/plugin", + "options": { + "targetName": "lint" + } + }, + { + "plugin": "@nx/vite/plugin", + "options": { + "buildTargetName": "build", + "testTargetName": "test", + "serveTargetName": "serve", + "previewTargetName": "preview", + "serveStaticTargetName": "serve-static" + } + }, + { + "plugin": "@nx/jest/plugin", + "options": { + "targetName": "test" + } + } + ], + "tasksRunnerOptions": { + "default": { + "runner": "nx/tasks-runners/default", + "options": { + "cacheableOperations": ["lint", "test", "e2e"], + "parallel": 3, + "useDaemonProcess": false, + "cacheDirectory": ".nx/cache", + "runtimeCacheInputs": ["node -v"] + } + } + }, + "release": { + "version": { + "preVersionCommand": "npx nx run-many -t build" + } + }, + "useLegacyCache": true +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3918d62 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,58859 @@ +{ + "name": "activepieces", + "version": "0.64.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "activepieces", + "version": "0.64.2", + "dependencies": { + "@activepieces/import-fresh-webpack": "3.3.0", + "@ai-sdk/anthropic": "1.2.12", + "@ai-sdk/azure": "1.0.10", + "@ai-sdk/google": "1.2.19", + "@ai-sdk/openai": "1.3.22", + "@ai-sdk/replicate": "0.2.8", + "@anthropic-ai/sdk": "0.39.0", + "@authenio/samlify-node-xmllint": "2.0.0", + "@aws-sdk/client-s3": "3.637.0", + "@aws-sdk/s3-request-presigner": "3.658.1", + "@azure/communication-email": "1.0.0", + "@azure/openai": "1.0.0-beta.11", + "@babel/runtime": "7.26.10", + "@bull-board/api": "6.10.1", + "@bull-board/fastify": "6.10.1", + "@codemirror/lang-javascript": "6.2.2", + "@codemirror/lang-json": "6.0.1", + "@datastructures-js/queue": "4.2.3", + "@dnd-kit/core": "6.1.0", + "@dnd-kit/modifiers": "7.0.0", + "@dnd-kit/sortable": "8.0.0", + "@fastify/basic-auth": "6.2.0", + "@fastify/cors": "11.0.1", + "@fastify/formbody": "8.0.2", + "@fastify/http-proxy": "11.1.2", + "@fastify/multipart": "9.0.3", + "@fastify/rate-limit": "10.3.0", + "@fastify/swagger": "9.5.1", + "@fastify/type-provider-typebox": "5.1.0", + "@google/generative-ai": "0.21.0", + "@hookform/resolvers": "3.9.0", + "@mailchimp/mailchimp_marketing": "3.0.80", + "@mailerlite/mailerlite-nodejs": "1.1.0", + "@microsoft/microsoft-graph-client": "3.0.7", + "@microsoft/microsoft-graph-types": "2.40.0", + "@modelcontextprotocol/sdk": "1.11.0", + "@notionhq/client": "2.2.11", + "@nx/devkit": "21.1.2", + "@octokit/rest": "21.1.1", + "@onfleet/node-onfleet": "1.3.3", + "@qdrant/js-client-rest": "1.7.0", + "@radix-ui/react-accordion": "1.2.4", + "@radix-ui/react-avatar": "1.1.0", + "@radix-ui/react-checkbox": "1.1.1", + "@radix-ui/react-collapsible": "1.1.0", + "@radix-ui/react-context-menu": "2.2.2", + "@radix-ui/react-dialog": "1.1.1", + "@radix-ui/react-dropdown-menu": "2.1.1", + "@radix-ui/react-icons": "1.3.0", + "@radix-ui/react-label": "2.1.0", + "@radix-ui/react-popover": "1.1.1", + "@radix-ui/react-progress": "1.1.0", + "@radix-ui/react-radio-group": "1.2.0", + "@radix-ui/react-scroll-area": "1.1.0", + "@radix-ui/react-select": "2.1.1", + "@radix-ui/react-separator": "1.1.0", + "@radix-ui/react-slider": "1.3.5", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-switch": "1.1.0", + "@radix-ui/react-tabs": "1.1.0", + "@radix-ui/react-toast": "1.2.1", + "@radix-ui/react-toggle": "1.1.0", + "@radix-ui/react-tooltip": "1.1.2", + "@rollup/wasm-node": "4.21.2", + "@segment/analytics-next": "1.72.0", + "@segment/analytics-node": "2.2.0", + "@sendgrid/mail": "8.0.0", + "@sentry/node": "7.120.0", + "@sinclair/typebox": "0.34.11", + "@slack/web-api": "7.9.0", + "@socket.io/redis-adapter": "8.2.1", + "@supabase/supabase-js": "2.49.9", + "@tanstack/react-query": "5.51.1", + "@tanstack/react-table": "8.19.2", + "@tanstack/react-virtual": "3.13.11", + "@tiptap/extension-mention": "2.5.4", + "@tiptap/extension-placeholder": "2.5.5", + "@tiptap/pm": "2.5.4", + "@tiptap/react": "2.5.4", + "@tiptap/starter-kit": "2.5.4", + "@tiptap/suggestion": "2.5.4", + "@tryfabric/martian": "1.2.0", + "@types/amqplib": "0.10.5", + "@types/docusign-esign": "5.19.9", + "@types/imapflow": "1.0.18", + "@types/js-yaml": "4.0.9", + "@types/pg-format": "1.0.5", + "@types/showdown": "2.0.6", + "@uiw/codemirror-theme-github": "4.23.0", + "@uiw/react-codemirror": "4.23.0", + "@xyflow/react": "12.3.5", + "ai": "4.3.16", + "airtable": "0.11.6", + "ajv": "8.12.0", + "amqplib": "0.10.4", + "assemblyai": "4.7.0", + "async-mutex": "0.4.0", + "axios": "1.8.3", + "axios-retry": "4.4.1", + "basic-ftp": "5.0.5", + "bcrypt": "6.0.0", + "boring-avatars": "1.11.2", + "buffer": "6.0.3", + "bullmq": "5.28.1", + "cheerio": "1.0.0-rc.12", + "chokidar": "3.6.0", + "class-variance-authority": "0.7.0", + "clear-module": "4.1.2", + "cli-table3": "0.6.3", + "clipboard": "2.0.11", + "clsx": "2.1.1", + "cmdk": "^1.1.1", + "codemirror": "5.65.14", + "color": "4.2.3", + "commander": "11.1.0", + "compare-versions": "6.1.0", + "concat": "1.0.3", + "contrast-color": "1.0.1", + "cron-validator": "1.3.1", + "cronstrue": "2.31.0", + "cross-spawn": "7.0.6", + "crypto-js": "4.2.0", + "csv-parse": "5.6.0", + "csv-reader": "1.0.12", + "csv-stringify": "6.5.2", + "date-fns": "3.6.0", + "dayjs": "1.11.9", + "decompress": "4.2.1", + "deep-equal": "2.2.2", + "deepmerge-ts": "7.1.0", + "docusign-esign": "8.1.0", + "drip-nodejs": "3.1.2", + "elevenlabs": "0.2.2", + "embla-carousel-react": "8.1.8", + "eventsource-parser": "3.0.2", + "exifreader": "4.20.0", + "fast-glob": "3.3.3", + "fastify": "5.4.0", + "fastify-favicon": "5.0.0", + "fastify-plugin": "5.0.1", + "fastify-raw-body": "5.0.0", + "fastify-socket": "5.1.2", + "fastify-xml-body-parser": "2.2.0", + "feedparser": "2.2.10", + "fetch-retry": "6.0.0", + "firebase-scrypt": "2.2.0", + "flowtoken": "1.0.40", + "font-awesome": "4.7.0", + "form-data": "4.0.0", + "framer-motion": "12.15.0", + "frimousse": "^0.2.0", + "fs-extra": "11.2.0", + "fuse.js": "7.0.0", + "google-auth-library": "8.9.0", + "googleapis": "129.0.0", + "http-status-codes": "2.2.0", + "https-proxy-agent": "7.0.4", + "i18next": "23.13.0", + "i18next-browser-languagedetector": "8.0.0", + "i18next-http-backend": "2.5.2", + "i18next-icu": "2.3.0", + "imapflow": "1.0.152", + "intercom-client": "6.0.0", + "intl-messageformat": "10.5.14", + "ioredis": "5.4.1", + "is-base64": "1.1.0", + "isolated-vm": "5.0.1", + "js-yaml": "4.1.0", + "jsdom": "23.0.1", + "jshint": "2.13.6", + "json-server": "1.0.0-beta.0", + "json-to-pretty-yaml": "1.2.2", + "json2xml": "0.1.3", + "jsoneditor": "10.0.3", + "jsonlint-mod": "1.7.6", + "jsonrepair": "3.2.0", + "jsonwebtoken": "9.0.1", + "jszip": "3.10.1", + "jwks-rsa": "3.1.0", + "jwt-decode": "4.0.0", + "lottie-web": "5.12.2", + "lucide-react": "0.407.0", + "mailparser": "3.6.7", + "marked": "4.3.0", + "mime": "4.0.1", + "mime-types": "2.1.35", + "mintlify": "4.0.395", + "monday-sdk-js": "0.5.2", + "mongodb": "6.15.0", + "motion": "12.16.0", + "mustache": "4.2.0", + "nanoid": "3.3.8", + "next-themes": "0.4.6", + "node-cron": "3.0.3", + "nodemailer": "6.9.9", + "notion-to-md": "3.1.1", + "nx-cloud": "19.1.0", + "object-sizeof": "2.6.3", + "openai": "4.67.1", + "p-limit": "2.3.0", + "pako": "2.1.0", + "papaparse": "5.5.3", + "pg": "8.11.3", + "pg-format": "1.0.4", + "pickleparser": "0.1.0", + "pino-loki": "2.1.3", + "playwright": "1.52.0", + "posthog-js": "1.195.0", + "priority-queue-typescript": "1.0.1", + "prismjs": "1.30.0", + "promise-mysql": "5.2.0", + "qrcode": "1.5.4", + "qs": "6.11.2", + "react": "18.3.1", + "react-colorful": "5.6.1", + "react-data-grid": "7.0.0-beta.47", + "react-day-picker": "8.10.1", + "react-dom": "18.3.1", + "react-error-boundary": "5.0.0", + "react-hook-form": "7.52.2", + "react-i18next": "15.0.1", + "react-json-view": "1.21.3", + "react-lottie": "1.2.4", + "react-markdown": "9.0.1", + "react-resizable-panels": "2.0.20", + "react-ripples": "2.2.1", + "react-router-dom": "6.11.2", + "react-syntax-highlighter": "15.4.2", + "react-textarea-autosize": "8.5.5", + "react-use": "17.5.1", + "recharts": "2.12.7", + "redlock": "5.0.0-beta.2", + "remark-gfm": "4.0.0", + "replicate": "0.34.1", + "rollup": "4.22.5", + "rss-parser": "3.13.0", + "safe-flat": "2.1.0", + "samlify": "2.10.0", + "semver": "7.6.0", + "showdown": "2.1.0", + "simple-git": "3.21.0", + "slackify-markdown": "4.4.0", + "slugify": "1.6.6", + "snowflake-sdk": "1.9.3", + "soap": "1.1.10", + "socket.io": "4.8.1", + "socket.io-client": "4.7.5", + "sonner": "2.0.3", + "sqlite3": "5.1.7", + "sqlstring": "2.3.3", + "ssh2-sftp-client": "9.1.0", + "string-replace-async": "3.0.2", + "string-strip-html": "8.5.0", + "stripe": "18.2.1", + "tailwind-merge": "2.4.0", + "tailwind-scrollbar": "4.0.2", + "tailwindcss-animate": "1.0.7", + "tinycolor2": "1.6.0", + "tree-kill": "1.2.2", + "tsconfig-paths": "4.2.0", + "tslib": "2.6.2", + "turndown": "7.2.0", + "twitter-api-v2": "1.15.1", + "typeorm": "0.3.18", + "url": "0.11.3", + "use-debounce": "10.0.1", + "use-deep-compare-effect": "1.8.1", + "use-ripple-hook": "1.0.24", + "usehooks-ts": "3.1.0", + "vaul": "1.1.2", + "write-file-atomic": "5.0.1", + "xml2js": "0.6.2", + "xmlrpc": "1.3.2", + "yaml": "2.4.1", + "zone.js": "0.14.4", + "zustand": "4.5.4" + }, + "devDependencies": { + "@commitlint/cli": "17.7.1", + "@commitlint/config-conventional": "17.7.0", + "@faker-js/faker": "8.2.0", + "@nx/esbuild": "21.1.2", + "@nx/eslint": "21.1.2", + "@nx/eslint-plugin": "21.1.2", + "@nx/jest": "21.1.2", + "@nx/js": "21.1.2", + "@nx/node": "21.1.2", + "@nx/react": "21.1.2", + "@nx/vite": "21.1.2", + "@nx/web": "21.1.2", + "@nx/webpack": "21.1.2", + "@nx/workspace": "21.1.2", + "@playwright/test": "1.37.1", + "@swc-node/register": "1.9.2", + "@swc/cli": "0.6.0", + "@swc/core": "1.5.7", + "@swc/helpers": "0.5.11", + "@testing-library/react": "15.0.6", + "@types/bcrypt": "5.0.0", + "@types/color": "3.0.6", + "@types/contrast-color": "1.0.3", + "@types/crypto-js": "4.1.1", + "@types/decompress": "4.2.4", + "@types/deep-equal": "1.0.1", + "@types/feedparser": "2.2.5", + "@types/imap": "0.8.37", + "@types/is-base64": "1.1.1", + "@types/jest": "29.5.13", + "@types/json2xml": "0.1.1", + "@types/mailchimp__mailchimp_marketing": "3.0.10", + "@types/mailparser": "3.4.0", + "@types/marked": "4.3.2", + "@types/mime-types": "2.1.1", + "@types/mustache": "4.2.4", + "@types/node": "^18.16.9", + "@types/node-cron": "3.0.11", + "@types/nodemailer": "6.4.9", + "@types/onfleet__node-onfleet": "1.3.7", + "@types/pako": "2.0.3", + "@types/papaparse": "5.3.8", + "@types/pg": "8.10.2", + "@types/prismjs": "1.26.0", + "@types/qrcode": "1.5.5", + "@types/qs": "6.9.7", + "@types/react": "18.3.1", + "@types/react-dom": "18.3.0", + "@types/react-lottie": "1.2.10", + "@types/recharts": "1.8.29", + "@types/semver": "7.5.6", + "@types/snowflake-sdk": "1.6.20", + "@types/sqlstring": "2.3.2", + "@types/ssh2-sftp-client": "9.0.0", + "@types/tinycolor2": "1.4.5", + "@types/turndown": "5.0.4", + "@types/write-file-atomic": "4.0.3", + "@types/xml2js": "0.4.14", + "@types/xmlrpc": "1.3.10", + "@typescript-eslint/eslint-plugin": "7.18.0", + "@typescript-eslint/parser": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@vitejs/plugin-react": "^4.2.0", + "@vitest/ui": "^1.3.1", + "autoprefixer": "10.4.15", + "babel-jest": "29.7.0", + "chalk": "4.1.2", + "concurrently": "8.2.1", + "esbuild": "0.25.0", + "eslint": "8.57.0", + "eslint-config-prettier": "10.1.5", + "eslint-import-resolver-alias": "1.1.2", + "eslint-import-resolver-typescript": "3.6.1", + "eslint-plugin-import": "2.31.0", + "eslint-plugin-import-x": "0.5.2", + "eslint-plugin-jest-dom": "5.4.0", + "eslint-plugin-jsx-a11y": "6.7.1", + "eslint-plugin-prettier": "4.2.1", + "eslint-plugin-react": "7.32.2", + "eslint-plugin-react-hooks": "4.6.0", + "eslint-plugin-tailwindcss": "3.17.4", + "eslint-plugin-testing-library": "6.2.2", + "eslint-plugin-vitest": "0.5.4", + "husky": "8.0.3", + "i18next-parser": "9.3.0", + "inquirer": "8.2.6", + "jest": "29.7.0", + "jest-environment-jsdom": "29.7.0", + "jest-environment-node": "^29.4.1", + "jsonc-eslint-parser": "^2.1.0", + "lint-staged": "15.2.9", + "nx": "21.1.2", + "pino-pretty": "9.4.1", + "pnpm": "9.15.0", + "postcss": "8.4.38", + "postcss-import": "14.1.0", + "postcss-preset-env": "7.5.0", + "postcss-url": "10.1.3", + "prettier": "2.8.4", + "tailwindcss": "3.4.3", + "ts-jest": "29.1.1", + "ts-node": "10.9.1", + "typescript": "5.5.4", + "verdaccio": "6.1.2", + "vite": "6.3.5", + "vite-plugin-checker": "0.7.2", + "vitest": "3.0.8", + "wait-on": "7.2.0", + "webpack": "5.98.0", + "webpack-cli": "5.1.4", + "webpack-ignore-dynamic-require": "1.0.0" + }, + "optionalDependencies": { + "@nx/nx-darwin-arm64": "21.1.2", + "@nx/nx-darwin-x64": "18.0.4", + "@nx/nx-linux-arm-gnueabihf": "18.0.4", + "@nx/nx-linux-x64-gnu": "18.0.4", + "@nx/nx-win32-x64-msvc": "18.0.4", + "@rollup/rollup-darwin-arm64": "4.20.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.200", + "@rollup/rollup-linux-arm64-gnu": "4.20.0" + } + }, + "node_modules/@activepieces/import-fresh-webpack": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@activepieces/import-fresh-webpack/-/import-fresh-webpack-3.3.0.tgz", + "integrity": "sha512-lSNLON5thmhQfBt1Fw57wkux4MkMF+7GJszq8NaT+fsz2jKtWLIp156bOYgjGK/6y4AAp23RvXAlv4sPxmF3ow==", + "dependencies": { + "parent-module": "^2.0.0", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@acuminous/bitsyntax": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz", + "integrity": "sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==", + "dependencies": { + "buffer-more-ints": "~1.0.0", + "debug": "^4.3.4", + "safe-buffer": "~5.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.3.tgz", + "integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==", + "dev": true + }, + "node_modules/@ai-sdk/anthropic": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-1.2.12.tgz", + "integrity": "sha512-YSzjlko7JvuiyQFmI9RN1tNZdEiZxc+6xld/0tq/VkJaHpEzGAb1yiNxxvmYVcjvfu/PcvCxAAYXmTYQQ63IHQ==", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@ai-sdk/azure": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@ai-sdk/azure/-/azure-1.0.10.tgz", + "integrity": "sha512-drbmzYS0iPRU/I3xnzphxNsYvSMjYhoq8gK34zShjeJGpPbewZPsXbPrncX6gdrkD4JR021yCahPc9E6RpXr5Q==", + "dependencies": { + "@ai-sdk/openai": "1.0.8", + "@ai-sdk/provider": "1.0.2", + "@ai-sdk/provider-utils": "2.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@ai-sdk/azure/node_modules/@ai-sdk/openai": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-1.0.8.tgz", + "integrity": "sha512-wcTHM9qgRWGYVO3WxPSTN/RwnZ9R5/17xyo61iUCCSCZaAuJyh6fKddO0/oamwDp3BG7g+4wbfAyuTo32H+fHw==", + "dependencies": { + "@ai-sdk/provider": "1.0.2", + "@ai-sdk/provider-utils": "2.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@ai-sdk/azure/node_modules/@ai-sdk/provider": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.0.2.tgz", + "integrity": "sha512-YYtP6xWQyaAf5LiWLJ+ycGTOeBLWrED7LUrvc+SQIWhGaneylqbaGsyQL7VouQUeQ4JZ1qKYZuhmi3W56HADPA==", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/azure/node_modules/@ai-sdk/provider-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.0.4.tgz", + "integrity": "sha512-GMhcQCZbwM6RoZCri0MWeEWXRt/T+uCxsmHEsTwNvEH3GDjNzchfX25C8ftry2MeEOOn6KfqCLSKomcgK6RoOg==", + "dependencies": { + "@ai-sdk/provider": "1.0.2", + "eventsource-parser": "^3.0.0", + "nanoid": "^3.3.8", + "secure-json-parse": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/google": { + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-1.2.19.tgz", + "integrity": "sha512-Xgl6eftIRQ4srUdCzxM112JuewVMij5q4JLcNmHcB68Bxn9dpr3MVUSPlJwmameuiQuISIA8lMB+iRiRbFsaqA==", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@ai-sdk/openai": { + "version": "1.3.22", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-1.3.22.tgz", + "integrity": "sha512-QwA+2EkG0QyjVR+7h6FE7iOu2ivNqAVMm9UJZkVxxTk5OIq5fFJDTEI/zICEMuHImTTXR2JjsL6EirJ28Jc4cw==", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.3.tgz", + "integrity": "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz", + "integrity": "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "nanoid": "^3.3.8", + "secure-json-parse": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.23.8" + } + }, + "node_modules/@ai-sdk/react": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.12.tgz", + "integrity": "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==", + "dependencies": { + "@ai-sdk/provider-utils": "2.2.8", + "@ai-sdk/ui-utils": "1.2.11", + "swr": "^2.2.5", + "throttleit": "2.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/replicate": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/replicate/-/replicate-0.2.8.tgz", + "integrity": "sha512-l9t4+RzbAn8osstkbWs6l++Nava+4LO4dsaddnE0GQM5E0BEIgMTJ14hoyfE02Ep0rJZ0M2HlXGqv5heW47P8A==", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@ai-sdk/ui-utils": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz", + "integrity": "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.23.8" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.39.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.39.0.tgz", + "integrity": "sha512-eMyDIPRZbt1CCLErRCi3exlAvNkBtRe+kW5vvJyef93PmNr/clstYgHhtvmkxN82nlKgzyGPCyGxrm0JQ1ZIdg==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/@authenio/samlify-node-xmllint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@authenio/samlify-node-xmllint/-/samlify-node-xmllint-2.0.0.tgz", + "integrity": "sha512-V9cQ0CHqu3JwOmbSecGPUnzIES5kHxD00FEZKnWh90ksQUJG5/TscV2r9XLbKp7MlRMOSUfWxecM35xPSLFdSg==", + "dependencies": { + "node-xmllint": "^1.0.0" + }, + "peerDependencies": { + "samlify": ">= 2.6.0" + } + }, + "node_modules/@authenio/xml-encryption": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@authenio/xml-encryption/-/xml-encryption-2.0.2.tgz", + "integrity": "sha512-cTlrKttbrRHEw3W+0/I609A2Matj5JQaRvfLtEIGZvlN0RaPi+3ANsMeqAyCAVlH/lUIW2tmtBlSMni74lcXeg==", + "dependencies": { + "@xmldom/xmldom": "^0.8.6", + "escape-html": "^1.0.3", + "xpath": "0.0.32" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.637.0.tgz", + "integrity": "sha512-y6UC94fsMvhKbf0dzfnjVP1HePeGjplfcYfilZU1COIJLyTkMcUv4XcT4I407CGIrvgEafONHkiC09ygqUauNA==", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/client-sts": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-bucket-endpoint": "3.620.0", + "@aws-sdk/middleware-expect-continue": "3.620.0", + "@aws-sdk/middleware-flexible-checksums": "3.620.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-location-constraint": "3.609.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-sdk-s3": "3.635.0", + "@aws-sdk/middleware-ssec": "3.609.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/signature-v4-multi-region": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@aws-sdk/xml-builder": "3.609.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/eventstream-serde-browser": "^3.0.6", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.5", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-blob-browser": "^3.1.2", + "@smithy/hash-node": "^3.0.3", + "@smithy/hash-stream-node": "^3.1.2", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/md5-js": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.637.0.tgz", + "integrity": "sha512-+KjLvgX5yJYROWo3TQuwBJlHCY0zz9PsLuEolmXQn0BVK1L/m9GteZHtd+rEdAoDGBpE0Xqjy1oz5+SmtsaRUw==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.637.0.tgz", + "integrity": "sha512-27bHALN6Qb6m6KZmPvRieJ/QRlj1lyac/GT2Rn5kJpre8Mpp+yxrtvp3h9PjNBty4lCeFEENfY4dGNSozBuBcw==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.637.0" + } + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.637.0.tgz", + "integrity": "sha512-xUi7x4qDubtA8QREtlblPuAcn91GS/09YVEY/RwU7xCY0aqGuFwgszAANlha4OUIqva8oVj2WO4gJuG+iaSnhw==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.635.0.tgz", + "integrity": "sha512-i1x/E/sgA+liUE1XJ7rj1dhyXpAKO1UKFUcTTHXok2ARjWTvszHnSXMOsB77aPbmn0fUp1JTx2kHUAZ1LVt5Bg==", + "dependencies": { + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", + "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.635.0.tgz", + "integrity": "sha512-iJyRgEjOCQlBMXqtwPLIKYc7Bsc6nqjrZybdMDenPDa+kmLg7xh8LxHsu9088e+2/wtLicE34FsJJIfzu3L82g==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.637.0.tgz", + "integrity": "sha512-h+PFCWfZ0Q3Dx84SppET/TFpcQHmxFW8/oV9ArEvMilw4EBN+IlxgbL0CnHwjHW64szcmrM0mbebjEfHf4FXmw==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.637.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.637.0.tgz", + "integrity": "sha512-yoEhoxJJfs7sPVQ6Is939BDQJZpZCoUgKr/ySse4YKOZ24t4VqgHA6+wV7rYh+7IW24Rd91UTvEzSuHYTlxlNA==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-ini": "3.637.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", + "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.637.0.tgz", + "integrity": "sha512-Mvz+h+e62/tl+dVikLafhv+qkZJ9RUb8l2YN/LeKMWkxQylPT83CPk9aimVhCV89zth1zpREArl97+3xsfgQvA==", + "dependencies": { + "@aws-sdk/client-sso": "3.637.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", + "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.620.0.tgz", + "integrity": "sha512-eGLL0W6L3HDb3OACyetZYOWpHJ+gLo0TehQKeQyy2G8vTYXqNTeqYhuI6up9HVjBzU9eQiULVQETmgQs7TFaRg==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.620.0.tgz", + "integrity": "sha512-QXeRFMLfyQ31nAHLbiTLtk0oHzG9QLMaof5jIfqcUwnOkO8YnQdeqzakrg1Alpy/VQ7aqzIi8qypkBe2KXZz0A==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.620.0.tgz", + "integrity": "sha512-ftz+NW7qka2sVuwnnO1IzBku5ccP+s5qZGeRTPgrKB7OzRW85gthvIo1vQR2w+OwHFk7WJbbhhWwbCbktnP4UA==", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-sdk/types": "3.609.0", + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", + "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.609.0.tgz", + "integrity": "sha512-xzsdoTkszGVqGVPjUmgoP7TORiByLueMHieI1fhQL888WPdqctwAx3ES6d/bA9Q/i8jnc6hs+Fjhy8UvBTkE9A==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", + "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", + "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.635.0.tgz", + "integrity": "sha512-RLdYJPEV4JL/7NBoFUs7VlP90X++5FlJdxHz0DzCjmiD3qCviKy+Cym3qg1gBgHwucs5XisuClxDrGokhAdTQw==", + "dependencies": { + "@aws-sdk/core": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.609.0.tgz", + "integrity": "sha512-GZSD1s7+JswWOTamVap79QiDaIV7byJFssBW68GYjyRS5EBjNfwA/8s+6uE6g39R3ojyTbYOmvcANoZEhSULXg==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.637.0.tgz", + "integrity": "sha512-EYo0NE9/da/OY8STDsK2LvM4kNa79DBsf4YVtaG4P5pZ615IeFsD8xOHZeuJmUrSMlVQ8ywPRX7WMucUybsKug==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", + "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner": { + "version": "3.658.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.658.1.tgz", + "integrity": "sha512-FQsECwePc34AAZU2mt0GUOppUIwOCLdsBkDQdCDyLDuWMN1+caYVzSAu++pJpkA+1MDdAKp4AiJyNiWbe/uI5g==", + "dependencies": { + "@aws-sdk/signature-v4-multi-region": "3.658.1", + "@aws-sdk/types": "3.654.0", + "@aws-sdk/util-format-url": "3.654.0", + "@smithy/middleware-endpoint": "^3.1.3", + "@smithy/protocol-http": "^4.1.3", + "@smithy/smithy-client": "^3.3.5", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/core": { + "version": "3.658.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.658.1.tgz", + "integrity": "sha512-vJVMoMcSKXK2gBRSu9Ywwv6wQ7tXH8VL1fqB1uVxgCqBZ3IHfqNn4zvpMPWrwgO2/3wv7XFyikGQ5ypPTCw4jA==", + "dependencies": { + "@smithy/core": "^2.4.6", + "@smithy/node-config-provider": "^3.1.7", + "@smithy/property-provider": "^3.1.6", + "@smithy/protocol-http": "^4.1.3", + "@smithy/signature-v4": "^4.1.4", + "@smithy/smithy-client": "^3.3.5", + "@smithy/types": "^3.4.2", + "@smithy/util-middleware": "^3.0.6", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.658.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.658.1.tgz", + "integrity": "sha512-UdiwCY4Eg7e1ZbseKvBr83SARukcqS5R9R3bnx4sb3cEK0wFDXWrlhRMgK94jr8IJeskV1ySyxozdb1XOzOU3w==", + "dependencies": { + "@aws-sdk/core": "3.658.1", + "@aws-sdk/types": "3.654.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/core": "^2.4.6", + "@smithy/node-config-provider": "^3.1.7", + "@smithy/protocol-http": "^4.1.3", + "@smithy/signature-v4": "^4.1.4", + "@smithy/smithy-client": "^3.3.5", + "@smithy/types": "^3.4.2", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.6", + "@smithy/util-stream": "^3.1.8", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.658.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.658.1.tgz", + "integrity": "sha512-gad2cOtmwLuiR096PB1vJsv2+KYwI+eN5D+eLaRLCTD9MMGvVWB5xkIXXGmn99ks4gAgtSpzZp8RD6viBj0gIw==", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.658.1", + "@aws-sdk/types": "3.654.0", + "@smithy/protocol-http": "^4.1.3", + "@smithy/signature-v4": "^4.1.4", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/types": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.654.0.tgz", + "integrity": "sha512-VWvbED3SV+10QJIcmU/PKjsKilsTV16d1I7/on4bvD/jo1qGeMXqLDBSen3ks/tuvXZF/mFc7ZW/W2DiLVtO7A==", + "dependencies": { + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.635.0.tgz", + "integrity": "sha512-J6QY4/invOkpogCHjSaDON1hF03viPpOnsrzVuCvJMmclS/iG62R4EY0wq1alYll0YmSdmKlpJwHMWwGtqK63Q==", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", + "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.614.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.568.0.tgz", + "integrity": "sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.637.0.tgz", + "integrity": "sha512-pAqOKUHeVWHEXXDIp/qoMk/6jyxIb6GGjnK1/f8dKHtKIEs4tKsnnL563gceEvdad53OPXIt86uoevCcCzmBnw==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.654.0.tgz", + "integrity": "sha512-2yAlJ/l1uTJhS52iu4+/EvdIyQhDBL+nATY8rEjFI0H+BHGVrJIH2CL4DByhvi2yvYwsqQX0HYah6pF/yoXukA==", + "dependencies": { + "@aws-sdk/types": "3.654.0", + "@smithy/querystring-builder": "^3.0.6", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url/node_modules/@aws-sdk/types": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.654.0.tgz", + "integrity": "sha512-VWvbED3SV+10QJIcmU/PKjsKilsTV16d1I7/on4bvD/jo1qGeMXqLDBSen3ks/tuvXZF/mFc7ZW/W2DiLVtO7A==", + "dependencies": { + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.804.0.tgz", + "integrity": "sha512-zVoRfpmBVPodYlnMjgVjfGoEZagyRF5IPn3Uo6ZvOZp24chnW/FRstH7ESDHDDRga4z3V+ElUQHKpFDXWyBW5A==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", + "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", + "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.609.0.tgz", + "integrity": "sha512-l9XxNcA4HX98rwCC2/KoiWcmEiRfZe4G+mYwDbCFT87JIMj6GBhLDkAzr/W8KAaA2IDr8Vc6J8fZPgVulxxfMA==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@azure-rest/core-client": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.4.0.tgz", + "integrity": "sha512-CjMFBcmnt0YNdRcxSSoZbtZNXudLlicdml7UrPsV03nHiWB+Bq5cu5ctieyaCuRtU7jm7+SOFtiE/g4pBFPKKA==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-rest-pipeline": "^1.5.0", + "@azure/core-tracing": "^1.0.1", + "@typespec/ts-http-runtime": "^0.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/communication-common": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@azure/communication-common/-/communication-common-2.4.0.tgz", + "integrity": "sha512-wwn4AoOgTgoA9OZkO34SKBpQg7/kfcABnzbaYEbc+9bCkBtwwjgMEk6xM+XLEE/uuODZ8q8jidUoNcZHQyP5AQ==", + "dependencies": { + "@azure-rest/core-client": "^2.3.3", + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.9.0", + "@azure/core-rest-pipeline": "^1.17.0", + "@azure/core-tracing": "^1.2.0", + "@azure/core-util": "^1.11.0", + "events": "^3.3.0", + "jwt-decode": "^4.0.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/communication-common/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@azure/communication-email": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@azure/communication-email/-/communication-email-1.0.0.tgz", + "integrity": "sha512-aY/qE3u4gadd6I895WOJPXrbKaPqeFDxGOK5xgAAqHkqNadI+hCp/D59q5Kfcj5Qcxal6mLm1GwZ1Cka0x4KZw==", + "dependencies": { + "@azure/communication-common": "^2.2.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.3.2", + "@azure/core-lro": "^2.5.0", + "@azure/core-rest-pipeline": "^1.8.0", + "@azure/logger": "^1.0.0", + "tslib": "^1.9.3", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/communication-email/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@azure/core-auth": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.9.0.tgz", + "integrity": "sha512-FPwHpZywuyasDSLMqJ6fhbOK3TqUdviZNF8OqRGA4W5Ewib2lEEZ+pBsYcBa88B2NGO/SEnYPGhyBqNlE8ilSw==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.4.tgz", + "integrity": "sha512-f7IxTD15Qdux30s2qFARH+JxgwxWLG2Rlr4oSkPGuLWm+1p5y1+C04XGLA0vmX6EtqfutmjvpNmAfgwVIS5hpw==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.20.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.6.1", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-http-compat": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.3.0.tgz", + "integrity": "sha512-qLQujmUypBBG0gxHd0j6/Jdmul6ttl24c8WGiLXIk7IHXdBlfoBqW27hyz3Xn6xbfdyVSarl1Ttbk0AwnZBYCw==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-client": "^1.3.0", + "@azure/core-rest-pipeline": "^1.20.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-lro": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz", + "integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.2.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-paging": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.6.2.tgz", + "integrity": "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.21.0.tgz", + "integrity": "sha512-a4MBwe/5WKbq9MIxikzgxLBbruC5qlkFYlBdI7Ev50Y7ib5Vo/Jvt5jnJo7NaWeJ908LCHL0S1Us4UMf1VoTfg==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.8.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@typespec/ts-http-runtime": "^0.2.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-sse": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@azure/core-sse/-/core-sse-2.2.0.tgz", + "integrity": "sha512-6Xg/CeW0jRyMoWt+puw2x6Qqkml3tr76Cn/oA9goIcUXtsi3ngmTwCVbwqkUWfhsOfo4F+78LGgiswSxTHN0sg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.2.0.tgz", + "integrity": "sha512-UKTiEJPkWcESPYJz3X5uKRYyOcJD+4nYph+KpfdPRnQJVrZfk0KJgdnaAWKfhsBBtAf/D58Az4AvCJEmWgIBAg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.12.0.tgz", + "integrity": "sha512-13IyjTQgABPARvG90+N2dXpC+hwp466XCdQXPCRlbWHgd3SJd5Q1VvaBGv6k1BIa4MQm6hAF1UBU1m8QUxV8sQ==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@typespec/ts-http-runtime": "^0.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-xml": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@azure/core-xml/-/core-xml-1.4.5.tgz", + "integrity": "sha512-gT4H8mTaSXRz7eGTuQyq1aIJnJqeXzpOe9Ay7Z3FrCouer14CbV3VzjnJrNrQfbBpGBLO9oy8BmrY75A0p53cA==", + "dependencies": { + "fast-xml-parser": "^5.0.7", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-xml/node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@azure/core-xml/node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, + "node_modules/@azure/core-xml/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@azure/logger": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.2.0.tgz", + "integrity": "sha512-0hKEzLhpw+ZTAfNJyRrn6s+V0nDWzXk9OjBr2TiGIu0OfMr5s2V4FpKLTAK3Ca5r5OKLbf4hkOGDPyiRjie/jA==", + "dependencies": { + "@typespec/ts-http-runtime": "^0.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/openai": { + "version": "1.0.0-beta.11", + "resolved": "https://registry.npmjs.org/@azure/openai/-/openai-1.0.0-beta.11.tgz", + "integrity": "sha512-OXS27xkG1abiGf5VZUKnkJKr1VCo8+6EUrTGW5aSVjc5COqX8jAUqVAOZsQVCHBdtWYSBULlZkc0ncKMTRQAiQ==", + "deprecated": "The Azure OpenAI client library for JavaScript beta has been retired. Please migrate to the stable OpenAI SDK for JavaScript using the migration guide: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/openai/openai/MIGRATION.md.", + "dependencies": { + "@azure-rest/core-client": "^1.1.7", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.13.0", + "@azure/core-sse": "^2.0.0", + "@azure/core-util": "^1.4.0", + "@azure/logger": "^1.0.3", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/openai/node_modules/@azure-rest/core-client": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-1.4.0.tgz", + "integrity": "sha512-ozTDPBVUDR5eOnMIwhggbnVmOrka4fXCs8n8mvUo4WLLc38kki6bAOByDoVZZPz/pZy2jMt2kwfpvy/UjALj6w==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-rest-pipeline": "^1.5.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/storage-blob": { + "version": "12.27.0", + "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.27.0.tgz", + "integrity": "sha512-IQjj9RIzAKatmNca3D6bT0qJ+Pkox1WZGOg2esJF2YLHb45pQKOwGPIAV+w3rfgkj7zV3RMxpn/c6iftzSOZJQ==", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.4.0", + "@azure/core-client": "^1.6.2", + "@azure/core-http-compat": "^2.0.0", + "@azure/core-lro": "^2.2.0", + "@azure/core-paging": "^1.1.1", + "@azure/core-rest-pipeline": "^1.10.1", + "@azure/core-tracing": "^1.1.2", + "@azure/core-util": "^1.6.1", + "@azure/core-xml": "^1.4.3", + "@azure/logger": "^1.0.0", + "events": "^3.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.7.tgz", + "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz", + "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.7", + "@babel/types": "^7.27.7", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", + "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.27.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.10" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", + "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "dev": true, + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", + "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.27.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", + "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.27.1.tgz", + "integrity": "sha512-DTxe4LBPrtFdsWzgpmbBKevg3e9PBy+dXRt19kSbucbZvL2uqtdqwwpluL1jfxYE0wIDTFp1nTy/q6gNLsxXrg==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-decorators": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", + "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.27.1.tgz", + "integrity": "sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.5.tgz", + "integrity": "sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", + "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.7.tgz", + "integrity": "sha512-CuLkokN1PEZ0Fsjtq+001aog/C2drDK9nTfK/NRK0n6rBin6cBrvM+zfQjDE+UllhR6/J4a6w8Xq9i4yi3mQrw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.27.7", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.7.tgz", + "integrity": "sha512-pg3ZLdIKWCP0CrJm0O4jYjVthyBeioVfvz9nwt6o5paUxsgJ/8GucSMAIaj6M7xA4WY+SrvtGu2LijzkdyecWQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.7.tgz", + "integrity": "sha512-201B1kFTWhckclcXpWHc8uUpYziDX/Pl4rxl0ZX0DiCZ3jknwfSUALL3QCYeeXXB37yWxJbo+g+Vfq8pAaHi3w==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.7", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.27.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.27.1.tgz", + "integrity": "sha512-edoidOjl/ZxvYo4lSBOQGDSyToYVkTAwyVoa2tkuYTSmjrB1+uAedoL5iROVLXkxH+vRgA7uP4tMg2pUJpZ3Ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.27.1.tgz", + "integrity": "sha512-p9+Vl3yuHPmkirRrg021XiP+EETmPMQTLr6Ayjj85RLNEbb3Eya/4VI0vAdzQG9SEAl2Lnt7fy5lZyMzjYoZQQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", + "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "dev": true, + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.5.tgz", + "integrity": "sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.27.4.tgz", + "integrity": "sha512-D68nR5zxU64EUzV8i7T3R5XP0Xhrou/amNnddsRQssx6GrTLdZl1rLxyjtVZBd+v/NVX4AbTPOB5aU8thAZV1A==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", + "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.2.tgz", + "integrity": "sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.27.1", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.27.1", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.27.1", + "@babel/plugin-transform-classes": "^7.27.1", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.1", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.27.2", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.1", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.27.1", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.40.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz", + "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.27.1", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", + "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz", + "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", + "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.6.0.tgz", + "integrity": "sha512-6cuonJVNOIL7lTj5zgo/Rc2bKAo4/GvN+rKCrUj7GdEHRzCk8zKOfFwUsL9nAVk5rSIsRmlgcpLzTRysopEeeg==", + "dev": true + }, + "node_modules/@bull-board/api": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/@bull-board/api/-/api-6.10.1.tgz", + "integrity": "sha512-VPkZa2XZI2Wk2MqK1XyiiS+tOhNan54mnm2fpv2KA0fdZ92mQqNjhKkOpsykhQv9XUEc8cCRlZqGxf67YCMJbQ==", + "dependencies": { + "redis-info": "^3.1.0" + }, + "peerDependencies": { + "@bull-board/ui": "6.10.1" + } + }, + "node_modules/@bull-board/fastify": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/@bull-board/fastify/-/fastify-6.10.1.tgz", + "integrity": "sha512-l9+KjDxPTtyfSnS/lhzp48G24lZs3A+9lmh+zRi+SBY+jpMribDpAq6pvAK96Tkm9fmMgcewXB4i0t2rlhrZbw==", + "dependencies": { + "@bull-board/api": "6.10.1", + "@bull-board/ui": "6.10.1", + "@fastify/static": "^8.2.0", + "@fastify/view": "^11.1.0", + "ejs": "^3.1.10" + } + }, + "node_modules/@bull-board/ui": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/@bull-board/ui/-/ui-6.10.1.tgz", + "integrity": "sha512-b6z6MBid/0DEShAMFPjPVZoPSoWqRBHCvTknyaxr/m8gL2/C+QP7jlCXut+L7uTFbCj9qs+CreAP0x/VdLI/Ig==", + "dependencies": { + "@bull-board/api": "6.10.1" + } + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.18.6", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz", + "integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz", + "integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz", + "integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", + "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.2.tgz", + "integrity": "sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.8.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz", + "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.11", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", + "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", + "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/theme-one-dark": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", + "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.38.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.0.tgz", + "integrity": "sha512-yvSchUwHOdupXkd7xJ0ob36jdsSR/I+/C+VbY0ffBiL5NiSTEBDfB1ZGWbbIlDd5xgdUkody+lukAdOxYrOBeg==", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@commitlint/cli": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-17.7.1.tgz", + "integrity": "sha512-BCm/AT06SNCQtvFv921iNhudOHuY16LswT0R3OeolVGLk8oP+Rk9TfQfgjH7QPMjhvp76bNqGFEcpKojxUNW1g==", + "dev": true, + "dependencies": { + "@commitlint/format": "^17.4.4", + "@commitlint/lint": "^17.7.0", + "@commitlint/load": "^17.7.1", + "@commitlint/read": "^17.5.1", + "@commitlint/types": "^17.4.4", + "execa": "^5.0.0", + "lodash.isfunction": "^3.0.9", + "resolve-from": "5.0.0", + "resolve-global": "1.0.0", + "yargs": "^17.0.0" + }, + "bin": { + "commitlint": "cli.js" + }, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/config-conventional": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-17.7.0.tgz", + "integrity": "sha512-iicqh2o6et+9kWaqsQiEYZzfLbtoWv9uZl8kbI8EGfnc0HeGafQBF7AJ0ylN9D/2kj6txltsdyQs8+2fTMwWEw==", + "dev": true, + "dependencies": { + "conventional-changelog-conventionalcommits": "^6.1.0" + }, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/config-validator": { + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-17.8.1.tgz", + "integrity": "sha512-UUgUC+sNiiMwkyiuIFR7JG2cfd9t/7MV8VB4TZ+q02ZFkHoduUS4tJGsCBWvBOGD9Btev6IecPMvlWUfJorkEA==", + "dev": true, + "dependencies": { + "@commitlint/types": "^17.8.1", + "ajv": "^8.11.0" + }, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/ensure": { + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-17.8.1.tgz", + "integrity": "sha512-xjafwKxid8s1K23NFpL8JNo6JnY/ysetKo8kegVM7c8vs+kWLP8VrQq+NbhgVlmCojhEDbzQKp4eRXSjVOGsow==", + "dev": true, + "dependencies": { + "@commitlint/types": "^17.8.1", + "lodash.camelcase": "^4.3.0", + "lodash.kebabcase": "^4.1.1", + "lodash.snakecase": "^4.1.1", + "lodash.startcase": "^4.4.0", + "lodash.upperfirst": "^4.3.1" + }, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/execute-rule": { + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-17.8.1.tgz", + "integrity": "sha512-JHVupQeSdNI6xzA9SqMF+p/JjrHTcrJdI02PwesQIDCIGUrv04hicJgCcws5nzaoZbROapPs0s6zeVHoxpMwFQ==", + "dev": true, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/format": { + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-17.8.1.tgz", + "integrity": "sha512-f3oMTyZ84M9ht7fb93wbCKmWxO5/kKSbwuYvS867duVomoOsgrgljkGGIztmT/srZnaiGbaK8+Wf8Ik2tSr5eg==", + "dev": true, + "dependencies": { + "@commitlint/types": "^17.8.1", + "chalk": "^4.1.0" + }, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/is-ignored": { + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-17.8.1.tgz", + "integrity": "sha512-UshMi4Ltb4ZlNn4F7WtSEugFDZmctzFpmbqvpyxD3la510J+PLcnyhf9chs7EryaRFJMdAKwsEKfNK0jL/QM4g==", + "dev": true, + "dependencies": { + "@commitlint/types": "^17.8.1", + "semver": "7.5.4" + }, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/is-ignored/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@commitlint/is-ignored/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@commitlint/is-ignored/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@commitlint/lint": { + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-17.8.1.tgz", + "integrity": "sha512-aQUlwIR1/VMv2D4GXSk7PfL5hIaFSfy6hSHV94O8Y27T5q+DlDEgd/cZ4KmVI+MWKzFfCTiTuWqjfRSfdRllCA==", + "dev": true, + "dependencies": { + "@commitlint/is-ignored": "^17.8.1", + "@commitlint/parse": "^17.8.1", + "@commitlint/rules": "^17.8.1", + "@commitlint/types": "^17.8.1" + }, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/load": { + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-17.8.1.tgz", + "integrity": "sha512-iF4CL7KDFstP1kpVUkT8K2Wl17h2yx9VaR1ztTc8vzByWWcbO/WaKwxsnCOqow9tVAlzPfo1ywk9m2oJ9ucMqA==", + "dev": true, + "dependencies": { + "@commitlint/config-validator": "^17.8.1", + "@commitlint/execute-rule": "^17.8.1", + "@commitlint/resolve-extends": "^17.8.1", + "@commitlint/types": "^17.8.1", + "@types/node": "20.5.1", + "chalk": "^4.1.0", + "cosmiconfig": "^8.0.0", + "cosmiconfig-typescript-loader": "^4.0.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0", + "resolve-from": "^5.0.0", + "ts-node": "^10.8.1", + "typescript": "^4.6.4 || ^5.2.2" + }, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/load/node_modules/@types/node": { + "version": "20.5.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz", + "integrity": "sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==", + "dev": true + }, + "node_modules/@commitlint/message": { + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-17.8.1.tgz", + "integrity": "sha512-6bYL1GUQsD6bLhTH3QQty8pVFoETfFQlMn2Nzmz3AOLqRVfNNtXBaSY0dhZ0dM6A2MEq4+2d7L/2LP8TjqGRkA==", + "dev": true, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/parse": { + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-17.8.1.tgz", + "integrity": "sha512-/wLUickTo0rNpQgWwLPavTm7WbwkZoBy3X8PpkUmlSmQJyWQTj0m6bDjiykMaDt41qcUbfeFfaCvXfiR4EGnfw==", + "dev": true, + "dependencies": { + "@commitlint/types": "^17.8.1", + "conventional-changelog-angular": "^6.0.0", + "conventional-commits-parser": "^4.0.0" + }, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/read": { + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-17.8.1.tgz", + "integrity": "sha512-Fd55Oaz9irzBESPCdMd8vWWgxsW3OWR99wOntBDHgf9h7Y6OOHjWEdS9Xzen1GFndqgyoaFplQS5y7KZe0kO2w==", + "dev": true, + "dependencies": { + "@commitlint/top-level": "^17.8.1", + "@commitlint/types": "^17.8.1", + "fs-extra": "^11.0.0", + "git-raw-commits": "^2.0.11", + "minimist": "^1.2.6" + }, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/resolve-extends": { + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-17.8.1.tgz", + "integrity": "sha512-W/ryRoQ0TSVXqJrx5SGkaYuAaE/BUontL1j1HsKckvM6e5ZaG0M9126zcwL6peKSuIetJi7E87PRQF8O86EW0Q==", + "dev": true, + "dependencies": { + "@commitlint/config-validator": "^17.8.1", + "@commitlint/types": "^17.8.1", + "import-fresh": "^3.0.0", + "lodash.mergewith": "^4.6.2", + "resolve-from": "^5.0.0", + "resolve-global": "^1.0.0" + }, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/rules": { + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-17.8.1.tgz", + "integrity": "sha512-2b7OdVbN7MTAt9U0vKOYKCDsOvESVXxQmrvuVUZ0rGFMCrCPJWWP1GJ7f0lAypbDAhaGb8zqtdOr47192LBrIA==", + "dev": true, + "dependencies": { + "@commitlint/ensure": "^17.8.1", + "@commitlint/message": "^17.8.1", + "@commitlint/to-lines": "^17.8.1", + "@commitlint/types": "^17.8.1", + "execa": "^5.0.0" + }, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/to-lines": { + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-17.8.1.tgz", + "integrity": "sha512-LE0jb8CuR/mj6xJyrIk8VLz03OEzXFgLdivBytoooKO5xLt5yalc8Ma5guTWobw998sbR3ogDd+2jed03CFmJA==", + "dev": true, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/top-level": { + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-17.8.1.tgz", + "integrity": "sha512-l6+Z6rrNf5p333SHfEte6r+WkOxGlWK4bLuZKbtf/2TXRN+qhrvn1XE63VhD8Oe9oIHQ7F7W1nG2k/TJFhx2yA==", + "dev": true, + "dependencies": { + "find-up": "^5.0.0" + }, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@commitlint/types": { + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-17.8.1.tgz", + "integrity": "sha512-PXDQXkAmiMEG162Bqdh9ChML/GJZo6vU+7F03ALKDK8zYc6SuAr47LjG7hGYRqUOz+WK0dU7bQ0xzuqFMdxzeQ==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0" + }, + "engines": { + "node": ">=v14" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", + "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", + "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", + "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", + "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", + "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", + "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", + "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", + "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", + "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", + "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", + "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.10" + } + }, + "node_modules/@cypress/request": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.8.tgz", + "integrity": "sha512-h0NFgh1mJmm1nr4jCwkGHwKneVYKghUyWe6TMNrk0B9zsjAJxpg8C4/+BAcmLgCPa1vj1V8rNUaILl+zYRUWBQ==", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~4.0.0", + "http-signature": "~1.4.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "6.14.0", + "safe-buffer": "^5.1.2", + "tough-cookie": "^5.0.0", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/request/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@cypress/request/node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@datastructures-js/queue": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@datastructures-js/queue/-/queue-4.2.3.tgz", + "integrity": "sha512-GWVMorC/xi2V2ta+Z/CPgPGHL2ZJozcj48g7y2nIX5GIGZGRrbShSHgvMViJwHJurUzJYOdIdRZnWDRrROFwJA==" + }, + "node_modules/@devhigley/parse-proxy": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@devhigley/parse-proxy/-/parse-proxy-1.0.3.tgz", + "integrity": "sha512-ozRQ9CgWF4JXNNae1zUEpb2fbqH61oxtZz2sdR7a0ci5mi9pSP3EvoU7g4idZYi+CXP32gsvH7kTYZJCGW3DKQ==" + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.1.0.tgz", + "integrity": "sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.0", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/modifiers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-7.0.0.tgz", + "integrity": "sha512-BG/ETy3eBjFap7+zIti53f0PCLGDzNXyTmn6fSdrudORf+OH04MxrW4p5+mPu4mgMk9kM41iYONjc3DOUWTcfg==", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.1.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-8.0.0.tgz", + "integrity": "sha512-U3jk5ebVXe1Lr7c2wU7SBZjcWdQP+j7peHJfCspnA81enlu88Mgd7CC8Q+pub9ubP7eKVETzJW+IBAhsqbSu/g==", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.1.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", + "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", + "dev": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "devOptional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", + "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", + "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", + "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", + "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", + "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", + "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", + "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", + "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", + "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", + "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", + "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", + "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", + "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", + "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", + "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", + "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", + "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", + "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", + "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", + "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", + "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", + "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", + "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", + "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", + "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", + "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@faker-js/faker": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.2.0.tgz", + "integrity": "sha512-VacmzZqVxdWdf9y64lDOMZNDMM/FQdtM9IsaOPKOm2suYwEatb8VkdHqOzXcDnZbk7YDE2BmsJmy/2Hmkn563g==", + "deprecated": "Please update to a newer version", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" + } + }, + "node_modules/@fastify/accept-negotiator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.1.tgz", + "integrity": "sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.2.tgz", + "integrity": "sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" + } + }, + "node_modules/@fastify/basic-auth": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@fastify/basic-auth/-/basic-auth-6.2.0.tgz", + "integrity": "sha512-Ao9Jf8TyW8v7p3CPy++c+E3qcCDeWfAlSIfFo0CsKrfvm81i0OCpnobIMwaSSkg/At0rzsLzbJPDWrgNru0G1w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "@fastify/error": "^4.0.0", + "fastify-plugin": "^5.0.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.1.tgz", + "integrity": "sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw==" + }, + "node_modules/@fastify/cors": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-11.0.1.tgz", + "integrity": "sha512-dmZaE7M1f4SM8ZZuk5RhSsDJ+ezTgI7v3HHRj8Ow9CneczsPLZV6+2j2uwdaSLn8zhTv6QV0F4ZRcqdalGx1pQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "fastify-plugin": "^5.0.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/@fastify/deepmerge": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-2.0.2.tgz", + "integrity": "sha512-3wuLdX5iiiYeZWP6bQrjqhrcvBIf0NHbQH1Ur1WbHvoiuTYUEItgygea3zs8aHpiitn0lOB8gX20u1qO+FDm7Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz", + "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "fast-json-stringify": "^6.0.0" + } + }, + "node_modules/@fastify/formbody": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@fastify/formbody/-/formbody-8.0.2.tgz", + "integrity": "sha512-84v5J2KrkXzjgBpYnaNRPqwgMsmY7ZDjuj0YVuMR3NXCJRCgKEZy/taSP1wUYGn0onfxJpLyRGDLa+NMaDJtnA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "fast-querystring": "^1.1.2", + "fastify-plugin": "^5.0.0" + } + }, + "node_modules/@fastify/forwarded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.0.tgz", + "integrity": "sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==" + }, + "node_modules/@fastify/http-proxy": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@fastify/http-proxy/-/http-proxy-11.1.2.tgz", + "integrity": "sha512-DDceCz1DIEa4vUsQ0DhMqwiZdZUCG0RByY51k0BndBLo7bWpO3QuzLpZTS7Hr6Bp6r82SRmAinM0uPpQse1zqA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "@fastify/reply-from": "^12.0.2", + "fast-querystring": "^1.1.2", + "fastify-plugin": "^5.0.1", + "ws": "^8.18.0" + } + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@fastify/multipart": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-9.0.3.tgz", + "integrity": "sha512-pJogxQCrT12/6I5Fh6jr3narwcymA0pv4B0jbC7c6Bl9wnrxomEUnV0d26w6gUls7gSXmhG8JGRMmHFIPsxt1g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "@fastify/busboy": "^3.0.0", + "@fastify/deepmerge": "^2.0.0", + "@fastify/error": "^4.0.0", + "fastify-plugin": "^5.0.0", + "secure-json-parse": "^3.0.0" + } + }, + "node_modules/@fastify/multipart/node_modules/secure-json-parse": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-3.0.2.tgz", + "integrity": "sha512-H6nS2o8bWfpFEV6U38sOSjS7bTbdgbCGU9wEM6W14P5H0QOsz94KCusifV44GpHDTu2nqZbuDNhTzu+mjDSw1w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/@fastify/proxy-addr": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.0.0.tgz", + "integrity": "sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, + "node_modules/@fastify/rate-limit": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@fastify/rate-limit/-/rate-limit-10.3.0.tgz", + "integrity": "sha512-eIGkG9XKQs0nyynatApA3EVrojHOuq4l6fhB4eeCk4PIOeadvOJz9/4w3vGI44Go17uaXOWEcPkaD8kuKm7g6Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "@lukeed/ms": "^2.0.2", + "fastify-plugin": "^5.0.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/@fastify/reply-from": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@fastify/reply-from/-/reply-from-12.1.0.tgz", + "integrity": "sha512-5SvMj0NnAAhno/MIv+bErCfzYxcqTueqUxCyub/i+68/CqYWroYg0/p8D0ZhrSQcDigowaKk+MEQSoLjbGwZ+g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "@fastify/error": "^4.0.0", + "end-of-stream": "^1.4.4", + "fast-content-type-parse": "^2.0.0", + "fast-querystring": "^1.1.2", + "fastify-plugin": "^5.0.1", + "toad-cache": "^3.7.0", + "undici": "^7.0.0" + } + }, + "node_modules/@fastify/send": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@fastify/send/-/send-4.1.0.tgz", + "integrity": "sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "@lukeed/ms": "^2.0.2", + "escape-html": "~1.0.3", + "fast-decode-uri-component": "^1.0.1", + "http-errors": "^2.0.0", + "mime": "^3" + } + }, + "node_modules/@fastify/send/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@fastify/static": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-8.2.0.tgz", + "integrity": "sha512-PejC/DtT7p1yo3p+W7LiUtLMsV8fEvxAK15sozHy9t8kwo5r0uLYmhV/inURmGz1SkHZFz/8CNtHLPyhKcx4SQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "@fastify/accept-negotiator": "^2.0.0", + "@fastify/send": "^4.0.0", + "content-disposition": "^0.5.4", + "fastify-plugin": "^5.0.0", + "fastq": "^1.17.1", + "glob": "^11.0.0" + } + }, + "node_modules/@fastify/swagger": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-9.5.1.tgz", + "integrity": "sha512-EGjYLA7vDmCPK7XViAYMF6y4+K3XUy5soVTVxsyXolNe/Svb4nFQxvtuQvvoQb2Gzc9pxiF3+ZQN/iZDHhKtTg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "fastify-plugin": "^5.0.0", + "json-schema-resolver": "^3.0.0", + "openapi-types": "^12.1.3", + "rfdc": "^1.3.1", + "yaml": "^2.4.2" + } + }, + "node_modules/@fastify/swagger/node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/@fastify/type-provider-typebox": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/type-provider-typebox/-/type-provider-typebox-5.1.0.tgz", + "integrity": "sha512-F1AQHeLiKp1hu6GMWm5W6fZ6zXQ0mTV+qHOzrptAie9AYewvFr5Q3blfy8Qmx9gUgwA3Yj+CWvQQJeTwDgTnIg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "peerDependencies": { + "@sinclair/typebox": ">=0.26 <=0.34" + } + }, + "node_modules/@fastify/view": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@fastify/view/-/view-11.1.0.tgz", + "integrity": "sha512-ab8LG5V8srCdnknYlufaSusrjkYLYf5TUWm3DTdPFr5m1C9tI7KNl78UCcP0BJiaGaZj1udsyzAC67JCf5Ol/g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "fastify-plugin": "^5.0.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz", + "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz", + "integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==", + "dependencies": { + "@floating-ui/core": "^1.7.2", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.4.tgz", + "integrity": "sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw==", + "dependencies": { + "@floating-ui/dom": "^1.7.2" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==" + }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz", + "integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==", + "dependencies": { + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz", + "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.7.8", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.8.tgz", + "integrity": "sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/icu-skeleton-parser": "1.8.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.2.tgz", + "integrity": "sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "optional": true + }, + "node_modules/@google-cloud/paginator": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", + "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", + "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", + "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/@google-cloud/storage": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.12.0.tgz", + "integrity": "sha512-78nNAY7iiZ4O/BouWMWTD/oSF2YtYgYB3GZirn0To6eBOugjXVoK+GXgUXOl+HlqbAOyHxAVXOlsj3snfbQ1dw==", + "dependencies": { + "@google-cloud/paginator": "^3.0.7", + "@google-cloud/projectify": "^3.0.0", + "@google-cloud/promisify": "^3.0.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "compressible": "^2.0.12", + "duplexify": "^4.0.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "fast-xml-parser": "^4.2.2", + "gaxios": "^5.0.0", + "google-auth-library": "^8.0.1", + "mime": "^3.0.0", + "mime-types": "^2.0.8", + "p-limit": "^3.0.1", + "retry-request": "^5.0.0", + "teeny-request": "^8.0.0", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@google-cloud/storage/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@google-cloud/storage/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@google/generative-ai": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.21.0.tgz", + "integrity": "sha512-7XhUbtnlkSEZK15kN3t+tzIMxsbKm/dSkKBFalj+20NvPKe1kBY7mR2P7vuijEn+f06z5+A8bVGKO0v39cr6Wg==", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@gulpjs/to-absolute-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", + "integrity": "sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==", + "dev": true, + "dependencies": { + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@hookform/resolvers": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.0.tgz", + "integrity": "sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.8.tgz", + "integrity": "sha512-d/QAsnwuHX2OPolxvYcgSj7A9DO9H6gVOy2DvBTx+P2LH2iRTo/RSGV3iwCzW024nP9hw98KIuDmdyhZQj1UQg==", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/figures": "^1.0.12", + "@inquirer/type": "^3.0.7", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.12", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.12.tgz", + "integrity": "sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.1.13", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.13.tgz", + "integrity": "sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==", + "dependencies": { + "@inquirer/figures": "^1.0.12", + "@inquirer/type": "^3.0.7", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@inquirer/core/node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.13.tgz", + "integrity": "sha512-WbicD9SUQt/K8O5Vyk9iC2ojq5RHoCLK6itpp2fHsWe44VxxcA9z3GTWlvjSTGmMQpZr+lbVmrxdHcumJoLbMA==", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.15.tgz", + "integrity": "sha512-4Y+pbr/U9Qcvf+N/goHzPEXiHH8680lM3Dr3Y9h9FFw4gHS+zVpbj8LfbKWIb/jayIB4aSO4pWiBTrBYWkvi5A==", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz", + "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.12.tgz", + "integrity": "sha512-xJ6PFZpDjC+tC1P8ImGprgcsrzQRsUh9aH3IZixm1lAZFK49UGHxM3ltFfuInN2kPYNfyoPRh+tU4ftsjPLKqQ==", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.15.tgz", + "integrity": "sha512-xWg+iYfqdhRiM55MvqiTCleHzszpoigUpN5+t1OMcRkJrUrw7va3AzXaxvS+Ak7Gny0j2mFSTv2JJj8sMtbV2g==", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.15.tgz", + "integrity": "sha512-75CT2p43DGEnfGTaqFpbDC2p2EEMrq0S+IRrf9iJvYreMy5mAWj087+mdKyLHapUEPLjN10mNvABpGbk8Wdraw==", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.5.3.tgz", + "integrity": "sha512-8YL0WiV7J86hVAxrh3fE5mDCzcTDe1670unmJRz6ArDgN+DBK1a0+rbnNWp4DUB5rPMwqD5ZP6YHl9KK1mbZRg==", + "dependencies": { + "@inquirer/checkbox": "^4.1.8", + "@inquirer/confirm": "^5.1.12", + "@inquirer/editor": "^4.2.13", + "@inquirer/expand": "^4.0.15", + "@inquirer/input": "^4.1.12", + "@inquirer/number": "^3.0.15", + "@inquirer/password": "^4.0.15", + "@inquirer/rawlist": "^4.1.3", + "@inquirer/search": "^3.0.15", + "@inquirer/select": "^4.2.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.3.tgz", + "integrity": "sha512-7XrV//6kwYumNDSsvJIPeAqa8+p7GJh7H5kRuxirct2cgOcSWwwNGoXDRgpNFbY/MG2vQ4ccIWCi8+IXXyFMZA==", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.15.tgz", + "integrity": "sha512-YBMwPxYBrADqyvP4nNItpwkBnGGglAvCLVW8u4pRmmvOsHUtCAUIMbUrLX5B3tFL1/WsLGdQ2HNzkqswMs5Uaw==", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/figures": "^1.0.12", + "@inquirer/type": "^3.0.7", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.2.3.tgz", + "integrity": "sha512-OAGhXU0Cvh0PhLz9xTF/kx6g6x+sP+PcyTiLvCrewI99P3BBeexD+VbuwkNDvqGkk3y2h5ZiWLeRP7BFlhkUDg==", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/figures": "^1.0.12", + "@inquirer/type": "^3.0.7", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", + "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/schemas/node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.11.tgz", + "integrity": "sha512-C512c1ytBTio4MrpWKlJpyFHT6+qfFL8SZ58zBzJ1OOzUEjHeF1BtjY2fH7n4x/g2OV/KiiMLAivOp1DXmiMMw==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.9.tgz", + "integrity": "sha512-amBU75CKOOkcQLfyM6J+DnWwz41yTsWI7o8MQ003LwUIWb4NYX/evAblTx1oBBYJySqL/zHPxHXDw5ewpQaUFw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.3.tgz", + "integrity": "sha512-AiR5uKpFxP3PjO4R19kQGIMwxyRyPuXmKEEy301V1C0+1rVjS94EZQXf1QKZYN8Q0YM+estSPhmx5JwNftv6nw==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.28", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.28.tgz", + "integrity": "sha512-KNNHHwW3EIp4EDYOvYFGyIFfx36R2dNJYH4knnZlF8T5jdbD5Wx8xmSaQ2gP9URkJ04LGEtlcCtwArKcmFcwKw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz", + "integrity": "sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==", + "dev": true, + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.6.0.tgz", + "integrity": "sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==" + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" + }, + "node_modules/@lezer/common": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", + "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz", + "integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@lukeed/uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@lukeed/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==", + "dependencies": { + "@lukeed/csprng": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@mailchimp/mailchimp_marketing": { + "version": "3.0.80", + "resolved": "https://registry.npmjs.org/@mailchimp/mailchimp_marketing/-/mailchimp_marketing-3.0.80.tgz", + "integrity": "sha512-Cgz0xPb+1DUjmrl5whAsmqfAChBko+Wf4/PLQE4RvwfPlcq2agfHr1QFiXEhZ8e+GQwQ3hZQn9iLGXwIXwxUCg==", + "dependencies": { + "dotenv": "^8.2.0", + "superagent": "3.8.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@mailerlite/mailerlite-nodejs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mailerlite/mailerlite-nodejs/-/mailerlite-nodejs-1.1.0.tgz", + "integrity": "sha512-XwjO7mQJEF97v6cxjPmjmCeOtNC1XSODUlP5DhUyFmeKHWINbDHP3gcmyQ6U1xUHmQiNHSn2Vdy5tUUD3pgdOg==", + "dependencies": { + "axios": "^1.2.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==" + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz", + "integrity": "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/mdx/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@mdx-js/mdx/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mdx-js/mdx/node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/mdx/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@mdx-js/mdx/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/react": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz", + "integrity": "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==", + "dependencies": { + "@types/mdx": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, + "node_modules/@microsoft/microsoft-graph-client": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@microsoft/microsoft-graph-client/-/microsoft-graph-client-3.0.7.tgz", + "integrity": "sha512-/AazAV/F+HK4LIywF9C+NYHcJo038zEnWkteilcxC1FM/uK/4NVGDKGrxx7nNq1ybspAroRKT4I1FHfxQzxkUw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependenciesMeta": { + "@azure/identity": { + "optional": true + }, + "@azure/msal-browser": { + "optional": true + }, + "buffer": { + "optional": true + }, + "stream-browserify": { + "optional": true + } + } + }, + "node_modules/@microsoft/microsoft-graph-types": { + "version": "2.40.0", + "resolved": "https://registry.npmjs.org/@microsoft/microsoft-graph-types/-/microsoft-graph-types-2.40.0.tgz", + "integrity": "sha512-1fcPVrB/NkbNcGNfCy+Cgnvwxt6/sbIEEFgZHFBJ670zYLegENYJF8qMo7x3LqBjWX2/Eneq5BVVRCLTmlJN+g==" + }, + "node_modules/@mintlify/cli": { + "version": "4.0.395", + "resolved": "https://registry.npmjs.org/@mintlify/cli/-/cli-4.0.395.tgz", + "integrity": "sha512-DKEvNKm8V0Oofhs8DMNXGw1mouNAz2PsQA5clO2OR19usTmf5Cwq6rVDTvBt4RXmLUMhb5nhYlooX6rVSKsgbA==", + "dependencies": { + "@mintlify/common": "1.0.279", + "@mintlify/link-rot": "3.0.377", + "@mintlify/models": "0.0.174", + "@mintlify/prebuild": "1.0.374", + "@mintlify/previewing": "4.0.387", + "@mintlify/validation": "0.1.299", + "chalk": "^5.2.0", + "detect-port": "^1.5.1", + "fs-extra": "^11.2.0", + "inquirer": "^12.3.0", + "js-yaml": "^4.1.0", + "ora": "^6.1.2", + "yargs": "^17.6.0" + }, + "bin": { + "mintlify": "bin/index.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@mintlify/cli/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@mintlify/cli/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@mintlify/cli/node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mintlify/cli/node_modules/inquirer": { + "version": "12.6.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.6.3.tgz", + "integrity": "sha512-eX9beYAjr1MqYsIjx1vAheXsRk1jbZRvHLcBu5nA9wX0rXR1IfCZLnVLp4Ym4mrhqmh7AuANwcdtgQ291fZDfQ==", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/prompts": "^7.5.3", + "@inquirer/type": "^3.0.7", + "ansi-escapes": "^4.3.2", + "mute-stream": "^2.0.0", + "run-async": "^3.0.0", + "rxjs": "^7.8.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@mintlify/cli/node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mintlify/cli/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mintlify/cli/node_modules/log-symbols": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "dependencies": { + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mintlify/cli/node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@mintlify/cli/node_modules/ora": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-6.3.1.tgz", + "integrity": "sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==", + "dependencies": { + "chalk": "^5.0.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.6.1", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.1.0", + "log-symbols": "^5.1.0", + "stdin-discarder": "^0.1.0", + "strip-ansi": "^7.0.1", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mintlify/cli/node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mintlify/cli/node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/@mintlify/cli/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@mintlify/common": { + "version": "1.0.279", + "resolved": "https://registry.npmjs.org/@mintlify/common/-/common-1.0.279.tgz", + "integrity": "sha512-sHCNrDXQO1pKQ5EQxfD/4oCrUfZnV3NyBXk6/0IY+wd70m5V+R+rtaT/+VxsFvuCHUk9JMZzuHA1CdfBV+XmYA==", + "dependencies": { + "@mintlify/mdx": "^1.0.1", + "@mintlify/models": "0.0.174", + "@mintlify/openapi-parser": "^0.0.7", + "@mintlify/validation": "0.1.299", + "@sindresorhus/slugify": "^2.1.1", + "acorn": "^8.11.2", + "estree-util-to-js": "^2.0.0", + "estree-walker": "^3.0.3", + "gray-matter": "^4.0.3", + "hast-util-from-html": "^2.0.3", + "hast-util-to-html": "^9.0.4", + "hast-util-to-text": "^4.0.2", + "is-absolute-url": "^4.0.1", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "mdast-util-from-markdown": "^2.0.2", + "mdast-util-mdx": "^3.0.0", + "mdast-util-mdx-jsx": "^3.1.3", + "micromark-extension-mdx-jsx": "^3.0.1", + "openapi-types": "^12.0.0", + "remark": "^15.0.1", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.0", + "remark-math": "^6.0.0", + "remark-mdx": "^3.1.0", + "unified": "^11.0.5", + "unist-builder": "^4.0.0", + "unist-util-map": "^4.0.0", + "unist-util-remove": "^4.0.0", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1", + "vfile": "^6.0.3" + } + }, + "node_modules/@mintlify/common/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@mintlify/common/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mintlify/common/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@mintlify/common/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mintlify/link-rot": { + "version": "3.0.377", + "resolved": "https://registry.npmjs.org/@mintlify/link-rot/-/link-rot-3.0.377.tgz", + "integrity": "sha512-5Fksi9gp1yUPCQjBnggm0fir/hEQPaZBXWyclzaVy1rRhtGgn/QYcMyAHdJFbaM/NYrlCnk5PUfs6myslhsWCQ==", + "dependencies": { + "@mintlify/common": "1.0.279", + "@mintlify/prebuild": "1.0.374", + "fs-extra": "^11.1.0", + "is-absolute-url": "^4.0.1", + "unist-util-visit": "^4.1.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@mintlify/link-rot/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/@mintlify/link-rot/node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mintlify/link-rot/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mintlify/link-rot/node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mintlify/mdx": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mintlify/mdx/-/mdx-1.1.0.tgz", + "integrity": "sha512-Jkefouzft4opHtEdWqzHUMqrJ3IQ6Zp2f1afXL22yAPshPGzifo1yG1v7/yIS2VPjM1Tk23hekJFaGsSQscF+A==", + "dependencies": { + "hast-util-to-string": "^3.0.1", + "next-mdx-remote-client": "^1.0.3", + "rehype-katex": "^7.0.1", + "remark-gfm": "^4.0.0", + "remark-math": "^6.0.0", + "remark-smartypants": "^3.0.2", + "shiki": "^3.6.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0" + }, + "peerDependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + } + }, + "node_modules/@mintlify/mdx/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@mintlify/mdx/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mintlify/mdx/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@mintlify/mdx/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mintlify/models": { + "version": "0.0.174", + "resolved": "https://registry.npmjs.org/@mintlify/models/-/models-0.0.174.tgz", + "integrity": "sha512-TFbzkAuPgC0HVeLDHFRukCaVYwNlvj7oUXdeGyo3n+XWZfU7WNMjPZgpIr3MgTI5gg+Z1/VLG2eXp6DGZlGbYw==", + "dependencies": { + "axios": "^1.4.0", + "openapi-types": "^12.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@mintlify/openapi-parser": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@mintlify/openapi-parser/-/openapi-parser-0.0.7.tgz", + "integrity": "sha512-3ecbkzPbsnkKVZJypVL0H5pCTR7a4iLv4cP7zbffzAwy+vpH70JmPxNVpPPP62yLrdZlfNcMxu5xKeT7fllgMg==", + "dependencies": { + "ajv": "^8.17.1", + "ajv-draft-04": "^1.0.0", + "ajv-formats": "^3.0.1", + "jsonpointer": "^5.0.1", + "leven": "^4.0.0", + "yaml": "^2.4.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@mintlify/openapi-parser/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@mintlify/openapi-parser/node_modules/leven": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-4.0.0.tgz", + "integrity": "sha512-puehA3YKku3osqPlNuzGDUHq8WpwXupUg1V6NXdV38G+gr+gkBwFC8g1b/+YcIvp8gnqVIus+eJCH/eGsRmJNw==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mintlify/openapi-parser/node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/@mintlify/prebuild": { + "version": "1.0.374", + "resolved": "https://registry.npmjs.org/@mintlify/prebuild/-/prebuild-1.0.374.tgz", + "integrity": "sha512-MfoCpMqjcKihPNpYW4eGWMzFGGsqnl4x159gNaQd+ckid5wO2XTUNrvsjdNHKtCu8sMgvuThLuutdCtjuatjYA==", + "dependencies": { + "@mintlify/common": "1.0.279", + "@mintlify/openapi-parser": "^0.0.7", + "@mintlify/scraping": "4.0.125", + "@mintlify/validation": "0.1.299", + "axios": "^1.6.2", + "chalk": "^5.3.0", + "favicons": "^7.0.2", + "fs-extra": "^11.1.0", + "gray-matter": "^4.0.3", + "is-absolute-url": "^4.0.1", + "js-yaml": "^4.1.0", + "openapi-types": "^12.0.0", + "unist-util-visit": "^4.1.1" + } + }, + "node_modules/@mintlify/prebuild/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/@mintlify/prebuild/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@mintlify/prebuild/node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mintlify/prebuild/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mintlify/prebuild/node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mintlify/previewing": { + "version": "4.0.387", + "resolved": "https://registry.npmjs.org/@mintlify/previewing/-/previewing-4.0.387.tgz", + "integrity": "sha512-y8I2vSVjhIqQ1meJ8LFmSAg89Ab4hsOeF9Nhs5o00TCOAy9+Tp2g/VVTtWyEoZH1eA5B7RxGqmL0Vb9DIux3iw==", + "dependencies": { + "@mintlify/common": "1.0.279", + "@mintlify/prebuild": "1.0.374", + "@mintlify/validation": "0.1.299", + "better-opn": "^3.0.2", + "chalk": "^5.1.0", + "chokidar": "^3.5.3", + "express": "^4.18.2", + "fs-extra": "^11.1.0", + "got": "^13.0.0", + "gray-matter": "^4.0.3", + "is-absolute-url": "^4.0.1", + "is-online": "^10.0.0", + "js-yaml": "^4.1.0", + "openapi-types": "^12.0.0", + "ora": "^6.1.2", + "socket.io": "^4.7.2", + "tar": "^6.1.15", + "unist-util-visit": "^4.1.1", + "yargs": "^17.6.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@mintlify/previewing/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/@mintlify/previewing/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mintlify/previewing/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@mintlify/previewing/node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/@mintlify/previewing/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@mintlify/previewing/node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mintlify/previewing/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mintlify/previewing/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/@mintlify/previewing/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@mintlify/previewing/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/@mintlify/previewing/node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@mintlify/previewing/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@mintlify/previewing/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mintlify/previewing/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@mintlify/previewing/node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mintlify/previewing/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mintlify/previewing/node_modules/log-symbols": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "dependencies": { + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mintlify/previewing/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mintlify/previewing/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mintlify/previewing/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@mintlify/previewing/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mintlify/previewing/node_modules/ora": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-6.3.1.tgz", + "integrity": "sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==", + "dependencies": { + "chalk": "^5.0.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.6.1", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.1.0", + "log-symbols": "^5.1.0", + "stdin-discarder": "^0.1.0", + "strip-ansi": "^7.0.1", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mintlify/previewing/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "node_modules/@mintlify/previewing/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@mintlify/previewing/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@mintlify/previewing/node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mintlify/previewing/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/@mintlify/previewing/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@mintlify/previewing/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@mintlify/previewing/node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@mintlify/previewing/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@mintlify/previewing/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@mintlify/previewing/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mintlify/previewing/node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mintlify/previewing/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mintlify/previewing/node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mintlify/scraping": { + "version": "4.0.125", + "resolved": "https://registry.npmjs.org/@mintlify/scraping/-/scraping-4.0.125.tgz", + "integrity": "sha512-o234LtHeixho2xSIXuUpVeWvH5DqtbYL7DoZk8Y8SM5fPK0TbdFD74rMrAdn8jsHxGkkGOFJPFXMwJkl5Dz9wA==", + "dependencies": { + "@mintlify/common": "1.0.279", + "@mintlify/openapi-parser": "^0.0.7", + "fs-extra": "^11.1.1", + "hast-util-to-mdast": "^10.1.0", + "js-yaml": "^4.1.0", + "mdast-util-mdx-jsx": "^3.1.3", + "puppeteer": "^22.14.0", + "rehype-parse": "^9.0.0", + "remark-gfm": "^4.0.0", + "remark-mdx": "^3.0.1", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "traverse": "^0.6.10", + "unified": "^11.0.5", + "unist-util-visit": "^5.0.0", + "yargs": "^17.6.0", + "zod": "^3.20.6" + }, + "bin": { + "mintlify-scrape": "bin/cli.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@mintlify/scraping/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@mintlify/scraping/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mintlify/scraping/node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mintlify/scraping/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@mintlify/scraping/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mintlify/validation": { + "version": "0.1.299", + "resolved": "https://registry.npmjs.org/@mintlify/validation/-/validation-0.1.299.tgz", + "integrity": "sha512-uRLpKwmPs+6Ep0YF+kahptKyiXSmnqXiAxZFkPRx68+CTjr7ln7SrtjOtIt8F/NrM9kMtmKbR2UEoB29G8DpFg==", + "dependencies": { + "@mintlify/models": "0.0.174", + "is-absolute-url": "^4.0.1", + "lcm": "^0.0.3", + "lodash": "^4.17.21", + "openapi-types": "^12.0.0", + "zod": "^3.20.6", + "zod-to-json-schema": "^3.20.3" + } + }, + "node_modules/@mixmark-io/domino": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz", + "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==" + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.0.tgz", + "integrity": "sha512-k/1pb70eD638anoi0e8wUGAlbMJXyvdV4p62Ko+EZ7eBe1xMx8Uhak1R5DgfoofsK5IBBnRwsYGTaLZl+6/+RQ==", + "dependencies": { + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.3", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modern-js/node-bundle-require": { + "version": "2.67.6", + "resolved": "https://registry.npmjs.org/@modern-js/node-bundle-require/-/node-bundle-require-2.67.6.tgz", + "integrity": "sha512-rRiDQkrm3kgn0E/GNrcvqo4c71PaUs2R8Xmpv6GUKbEr6lz7VNgfZmAhdAQPtNfRfiBe+1sFLzEcwfEdDo/dTA==", + "dev": true, + "dependencies": { + "@modern-js/utils": "2.67.6", + "@swc/helpers": "^0.5.17", + "esbuild": "0.17.19" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "dev": true, + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "node_modules/@modern-js/node-bundle-require/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "node_modules/@modern-js/utils": { + "version": "2.67.6", + "resolved": "https://registry.npmjs.org/@modern-js/utils/-/utils-2.67.6.tgz", + "integrity": "sha512-cxY7HsSH0jIN3rlL6RZ0tgzC1tH0gHW++8X6h7sXCNCylhUdbGZI9yTGbpAS8bU7c97NmPaTKg+/ILt00Kju1Q==", + "dev": true, + "dependencies": { + "@swc/helpers": "^0.5.17", + "caniuse-lite": "^1.0.30001520", + "lodash": "^4.17.21", + "rslog": "^1.1.0" + } + }, + "node_modules/@modern-js/utils/node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "dev": true, + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@modern-js/utils/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "node_modules/@module-federation/bridge-react-webpack-plugin": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@module-federation/bridge-react-webpack-plugin/-/bridge-react-webpack-plugin-0.9.1.tgz", + "integrity": "sha512-znN/Qm6M0U1t3iF10gu1hSxDkk18yz78yvk+AMB34UDzpXHiC1zbpIeV2CQNV5GCeafmCICmcn9y1qh7F54KTg==", + "dev": true, + "dependencies": { + "@module-federation/sdk": "0.9.1", + "@types/semver": "7.5.8", + "semver": "7.6.3" + } + }, + "node_modules/@module-federation/bridge-react-webpack-plugin/node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@module-federation/bridge-react-webpack-plugin/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@module-federation/cli": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/cli/-/cli-0.15.0.tgz", + "integrity": "sha512-ZFQ7TA7vwSro4n21/+9cGxVkeRU9IcXcQGs1GIToz/JFvomTHbGN33iplR3GNMhuMNyXQ/wxe2gWkEmIBCzW2w==", + "dev": true, + "dependencies": { + "@modern-js/node-bundle-require": "2.67.6", + "@module-federation/dts-plugin": "0.15.0", + "@module-federation/sdk": "0.15.0", + "chalk": "3.0.0", + "commander": "11.1.0" + }, + "bin": { + "mf": "bin/mf.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@module-federation/cli/node_modules/@module-federation/dts-plugin": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/dts-plugin/-/dts-plugin-0.15.0.tgz", + "integrity": "sha512-UztaFAhpCpsy+EUOP1BiqlYpRdD4h2TUITphCmThO1grOCqU7dYYwGjWNy37NtJeykRRznH3FU0+iGBG3Oiw6w==", + "dev": true, + "dependencies": { + "@module-federation/error-codes": "0.15.0", + "@module-federation/managers": "0.15.0", + "@module-federation/sdk": "0.15.0", + "@module-federation/third-party-dts-extractor": "0.15.0", + "adm-zip": "^0.5.10", + "ansi-colors": "^4.1.3", + "axios": "^1.8.2", + "chalk": "3.0.0", + "fs-extra": "9.1.0", + "isomorphic-ws": "5.0.0", + "koa": "2.16.1", + "lodash.clonedeepwith": "4.5.0", + "log4js": "6.9.1", + "node-schedule": "2.1.1", + "rambda": "^9.1.0", + "ws": "8.18.0" + }, + "peerDependencies": { + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24" + }, + "peerDependenciesMeta": { + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/@module-federation/cli/node_modules/@module-federation/error-codes": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.15.0.tgz", + "integrity": "sha512-CFJSF+XKwTcy0PFZ2l/fSUpR4z247+Uwzp1sXVkdIfJ/ATsnqf0Q01f51qqSEA6MYdQi6FKos9FIcu3dCpQNdg==", + "dev": true + }, + "node_modules/@module-federation/cli/node_modules/@module-federation/managers": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/managers/-/managers-0.15.0.tgz", + "integrity": "sha512-YMIiFRgMHtuMcLBgOYyfkFpwU9vo6l0VjOZE5Wdr33DltQBUgp9Lo8+2AkyZ4TTkelqjvUWSNKKYV3MV4GL7gw==", + "dev": true, + "dependencies": { + "@module-federation/sdk": "0.15.0", + "find-pkg": "2.0.0", + "fs-extra": "9.1.0" + } + }, + "node_modules/@module-federation/cli/node_modules/@module-federation/sdk": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.15.0.tgz", + "integrity": "sha512-PWiYbGcJrKUD6JZiEPihrXhV3bgXdll4bV7rU+opV7tHaun+Z0CdcawjZ82Xnpb8MCPGmqHwa1MPFeUs66zksw==", + "dev": true + }, + "node_modules/@module-federation/cli/node_modules/@module-federation/third-party-dts-extractor": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/third-party-dts-extractor/-/third-party-dts-extractor-0.15.0.tgz", + "integrity": "sha512-rML74G1NB9wtHubXP+ZTMI5HZkYypN/E93w8Zkwr6rc/k1eoZZza2lghw2znCNeu3lDlhvI9i4iaVsJQrX4oQA==", + "dev": true, + "dependencies": { + "find-pkg": "2.0.0", + "fs-extra": "9.1.0", + "resolve": "1.22.8" + } + }, + "node_modules/@module-federation/cli/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@module-federation/cli/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@module-federation/cli/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@module-federation/cli/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@module-federation/cli/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@module-federation/cli/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@module-federation/cli/node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@module-federation/cli/node_modules/koa": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.1.tgz", + "integrity": "sha512-umfX9d3iuSxTQP4pnzLOz0HKnPg0FaUUIKcye2lOiz3KPu1Y3M3xlz76dISdFPQs37P9eJz1wUpcTS6KDPn9fA==", + "dev": true, + "dependencies": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.9.0", + "debug": "^4.3.2", + "delegates": "^1.0.0", + "depd": "^2.0.0", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^2.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "engines": { + "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + } + }, + "node_modules/@module-federation/cli/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@module-federation/cli/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@module-federation/cli/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@module-federation/cli/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@module-federation/cli/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@module-federation/data-prefetch": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@module-federation/data-prefetch/-/data-prefetch-0.9.1.tgz", + "integrity": "sha512-rS1AsgRvIMAWK8oMprEBF0YQ3WvsqnumjinvAZU1Dqut5DICmpQMTPEO1OrAKyjO+PQgEhmq13HggzN6ebGLrQ==", + "dev": true, + "dependencies": { + "@module-federation/runtime": "0.9.1", + "@module-federation/sdk": "0.9.1", + "fs-extra": "9.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@module-federation/data-prefetch/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@module-federation/dts-plugin": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@module-federation/dts-plugin/-/dts-plugin-0.9.1.tgz", + "integrity": "sha512-DezBrFaIKfDcEY7UhqyO1WbYocERYsR/CDN8AV6OvMnRlQ8u0rgM8qBUJwx0s+K59f+CFQFKEN4C8p7naCiHrw==", + "dev": true, + "dependencies": { + "@module-federation/error-codes": "0.9.1", + "@module-federation/managers": "0.9.1", + "@module-federation/sdk": "0.9.1", + "@module-federation/third-party-dts-extractor": "0.9.1", + "adm-zip": "^0.5.10", + "ansi-colors": "^4.1.3", + "axios": "^1.7.4", + "chalk": "3.0.0", + "fs-extra": "9.1.0", + "isomorphic-ws": "5.0.0", + "koa": "2.15.4", + "lodash.clonedeepwith": "4.5.0", + "log4js": "6.9.1", + "node-schedule": "2.1.1", + "rambda": "^9.1.0", + "ws": "8.18.0" + }, + "peerDependencies": { + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24" + }, + "peerDependenciesMeta": { + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/@module-federation/dts-plugin/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@module-federation/dts-plugin/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@module-federation/dts-plugin/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@module-federation/enhanced": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@module-federation/enhanced/-/enhanced-0.9.1.tgz", + "integrity": "sha512-c9siKVjcgT2gtDdOTqEr+GaP2X/PWAS0OV424ljKLstFL1lcS/BIsxWFDmxPPl5hDByAH+1q4YhC1LWY4LNDQw==", + "dev": true, + "dependencies": { + "@module-federation/bridge-react-webpack-plugin": "0.9.1", + "@module-federation/data-prefetch": "0.9.1", + "@module-federation/dts-plugin": "0.9.1", + "@module-federation/error-codes": "0.9.1", + "@module-federation/inject-external-runtime-core-plugin": "0.9.1", + "@module-federation/managers": "0.9.1", + "@module-federation/manifest": "0.9.1", + "@module-federation/rspack": "0.9.1", + "@module-federation/runtime-tools": "0.9.1", + "@module-federation/sdk": "0.9.1", + "btoa": "^1.2.1", + "upath": "2.0.1" + }, + "peerDependencies": { + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue-tsc": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/@module-federation/error-codes": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.9.1.tgz", + "integrity": "sha512-q8spCvlwUzW42iX1irnlBTcwcZftRNHyGdlaoFO1z/fW4iphnBIfijzkigWQzOMhdPgzqN/up7XN+g5hjBGBtw==", + "dev": true + }, + "node_modules/@module-federation/inject-external-runtime-core-plugin": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@module-federation/inject-external-runtime-core-plugin/-/inject-external-runtime-core-plugin-0.9.1.tgz", + "integrity": "sha512-BPfzu1cqDU5BhM493enVF1VfxJWmruen0ktlHrWdJJlcddhZzyFBGaLAGoGc+83fS75aEllvJTEthw4kMViMQQ==", + "dev": true, + "peerDependencies": { + "@module-federation/runtime-tools": "0.9.1" + } + }, + "node_modules/@module-federation/managers": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@module-federation/managers/-/managers-0.9.1.tgz", + "integrity": "sha512-8hpIrvGfiODxS1qelTd7eaLRVF7jrp17RWgeH1DWoprxELANxm5IVvqUryB+7j+BhoQzamog9DL5q4MuNfGgIA==", + "dev": true, + "dependencies": { + "@module-federation/sdk": "0.9.1", + "find-pkg": "2.0.0", + "fs-extra": "9.1.0" + } + }, + "node_modules/@module-federation/managers/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@module-federation/manifest": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@module-federation/manifest/-/manifest-0.9.1.tgz", + "integrity": "sha512-+GteKBXrAUkq49i2CSyWZXM4vYa+mEVXxR9Du71R55nXXxgbzAIoZj9gxjRunj9pcE8+YpAOyfHxLEdWngxWdg==", + "dev": true, + "dependencies": { + "@module-federation/dts-plugin": "0.9.1", + "@module-federation/managers": "0.9.1", + "@module-federation/sdk": "0.9.1", + "chalk": "3.0.0", + "find-pkg": "2.0.0" + } + }, + "node_modules/@module-federation/manifest/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@module-federation/node": { + "version": "2.7.7", + "resolved": "https://registry.npmjs.org/@module-federation/node/-/node-2.7.7.tgz", + "integrity": "sha512-8NaByOBkbTkv25k2iBgaEFvjzLPAQKjlFBtR1JYdMXMyeouzzsDi9G7S0Hblc5td8ZKe7PDP/+KA3+uS35jMcQ==", + "dev": true, + "dependencies": { + "@module-federation/enhanced": "0.15.0", + "@module-federation/runtime": "0.15.0", + "@module-federation/sdk": "0.15.0", + "btoa": "1.2.1", + "encoding": "^0.1.13", + "node-fetch": "2.7.0" + }, + "peerDependencies": { + "react": "^16||^17||^18||^19", + "react-dom": "^16||^17||^18||^19", + "webpack": "^5.40.0" + }, + "peerDependenciesMeta": { + "next": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/bridge-react-webpack-plugin": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/bridge-react-webpack-plugin/-/bridge-react-webpack-plugin-0.15.0.tgz", + "integrity": "sha512-bbinV0gC82x0JGrT6kNV1tQHi4UBxqY79mZJKWVbGpSMPM+nifC9y/nQCYhZZajT7D/5zIHNkP0BKrQmPA7ArA==", + "dev": true, + "dependencies": { + "@module-federation/sdk": "0.15.0", + "@types/semver": "7.5.8", + "semver": "7.6.3" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/data-prefetch": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/data-prefetch/-/data-prefetch-0.15.0.tgz", + "integrity": "sha512-ivAnthD4SbBoT3590qLzCyKELGyfa7nj8BEjWjb6BNrP5Eu8sHX3Q2wHf76QsYfuwErtjaMU87N7dTe2ELZPVg==", + "dev": true, + "dependencies": { + "@module-federation/runtime": "0.15.0", + "@module-federation/sdk": "0.15.0", + "fs-extra": "9.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/dts-plugin": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/dts-plugin/-/dts-plugin-0.15.0.tgz", + "integrity": "sha512-UztaFAhpCpsy+EUOP1BiqlYpRdD4h2TUITphCmThO1grOCqU7dYYwGjWNy37NtJeykRRznH3FU0+iGBG3Oiw6w==", + "dev": true, + "dependencies": { + "@module-federation/error-codes": "0.15.0", + "@module-federation/managers": "0.15.0", + "@module-federation/sdk": "0.15.0", + "@module-federation/third-party-dts-extractor": "0.15.0", + "adm-zip": "^0.5.10", + "ansi-colors": "^4.1.3", + "axios": "^1.8.2", + "chalk": "3.0.0", + "fs-extra": "9.1.0", + "isomorphic-ws": "5.0.0", + "koa": "2.16.1", + "lodash.clonedeepwith": "4.5.0", + "log4js": "6.9.1", + "node-schedule": "2.1.1", + "rambda": "^9.1.0", + "ws": "8.18.0" + }, + "peerDependencies": { + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24" + }, + "peerDependenciesMeta": { + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/enhanced": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/enhanced/-/enhanced-0.15.0.tgz", + "integrity": "sha512-YzGcjdggtR+VrNdIgT1nvhT+V6I+LnrdsLV3YfOB0iVkOe4+YFbDLZJK16CuYRSm/HTR38LVbziE/6tWcibKYw==", + "dev": true, + "dependencies": { + "@module-federation/bridge-react-webpack-plugin": "0.15.0", + "@module-federation/cli": "0.15.0", + "@module-federation/data-prefetch": "0.15.0", + "@module-federation/dts-plugin": "0.15.0", + "@module-federation/error-codes": "0.15.0", + "@module-federation/inject-external-runtime-core-plugin": "0.15.0", + "@module-federation/managers": "0.15.0", + "@module-federation/manifest": "0.15.0", + "@module-federation/rspack": "0.15.0", + "@module-federation/runtime-tools": "0.15.0", + "@module-federation/sdk": "0.15.0", + "btoa": "^1.2.1", + "schema-utils": "^4.3.0", + "upath": "2.0.1" + }, + "bin": { + "mf": "bin/mf.js" + }, + "peerDependencies": { + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue-tsc": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/error-codes": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.15.0.tgz", + "integrity": "sha512-CFJSF+XKwTcy0PFZ2l/fSUpR4z247+Uwzp1sXVkdIfJ/ATsnqf0Q01f51qqSEA6MYdQi6FKos9FIcu3dCpQNdg==", + "dev": true + }, + "node_modules/@module-federation/node/node_modules/@module-federation/inject-external-runtime-core-plugin": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/inject-external-runtime-core-plugin/-/inject-external-runtime-core-plugin-0.15.0.tgz", + "integrity": "sha512-D6+FO2oj2Gr6QpfWv3i9RI9VJM2IFCMiFQKg5zOpKw1qdrPRWb35fiXAXGjw9RrVgrZz0Z1b9OP4zC9hfbpnQQ==", + "dev": true, + "peerDependencies": { + "@module-federation/runtime-tools": "0.15.0" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/managers": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/managers/-/managers-0.15.0.tgz", + "integrity": "sha512-YMIiFRgMHtuMcLBgOYyfkFpwU9vo6l0VjOZE5Wdr33DltQBUgp9Lo8+2AkyZ4TTkelqjvUWSNKKYV3MV4GL7gw==", + "dev": true, + "dependencies": { + "@module-federation/sdk": "0.15.0", + "find-pkg": "2.0.0", + "fs-extra": "9.1.0" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/manifest": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/manifest/-/manifest-0.15.0.tgz", + "integrity": "sha512-x+UVFkdoKiNZhpUO8H/9jlM3nmC5bIApZvbC2TQuNva+ElCPotdhEO8jduiVkBnc2lr8D9qnFm8U5Kx/aFnGlA==", + "dev": true, + "dependencies": { + "@module-federation/dts-plugin": "0.15.0", + "@module-federation/managers": "0.15.0", + "@module-federation/sdk": "0.15.0", + "chalk": "3.0.0", + "find-pkg": "2.0.0" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/rspack": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/rspack/-/rspack-0.15.0.tgz", + "integrity": "sha512-nRz0JHcoTz+M5A+wXCG3981lmPeEm91EZe4q5GVfbVhvlAf/Ctd26qSz4lXuyUA1Ar5afBTxKvqWy7xh4wcg2A==", + "dev": true, + "dependencies": { + "@module-federation/bridge-react-webpack-plugin": "0.15.0", + "@module-federation/dts-plugin": "0.15.0", + "@module-federation/inject-external-runtime-core-plugin": "0.15.0", + "@module-federation/managers": "0.15.0", + "@module-federation/manifest": "0.15.0", + "@module-federation/runtime-tools": "0.15.0", + "@module-federation/sdk": "0.15.0", + "btoa": "1.2.1" + }, + "peerDependencies": { + "@rspack/core": ">=0.7", + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/runtime": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.15.0.tgz", + "integrity": "sha512-dTPsCNum9Bhu3yPOcrPYq0YnM9eCMMMNB1wuiqf1+sFbQlNApF0vfZxooqz3ln0/MpgE0jerVvFsLVGfqvC9Ug==", + "dev": true, + "dependencies": { + "@module-federation/error-codes": "0.15.0", + "@module-federation/runtime-core": "0.15.0", + "@module-federation/sdk": "0.15.0" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/runtime-core": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.15.0.tgz", + "integrity": "sha512-RYzI61fRDrhyhaEOXH3AgIGlHiot0wPFXu7F43cr+ZnTi+VlSYWLdlZ4NBuT9uV6JSmH54/c+tEZm5SXgKR2sQ==", + "dev": true, + "dependencies": { + "@module-federation/error-codes": "0.15.0", + "@module-federation/sdk": "0.15.0" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/runtime-tools": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.15.0.tgz", + "integrity": "sha512-kzFn3ObUeBp5vaEtN1WMxhTYBuYEErxugu1RzFUERD21X3BZ+b4cWwdFJuBDlsmVjctIg/QSOoZoPXRKAO0foA==", + "dev": true, + "dependencies": { + "@module-federation/runtime": "0.15.0", + "@module-federation/webpack-bundler-runtime": "0.15.0" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/sdk": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.15.0.tgz", + "integrity": "sha512-PWiYbGcJrKUD6JZiEPihrXhV3bgXdll4bV7rU+opV7tHaun+Z0CdcawjZ82Xnpb8MCPGmqHwa1MPFeUs66zksw==", + "dev": true + }, + "node_modules/@module-federation/node/node_modules/@module-federation/third-party-dts-extractor": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/third-party-dts-extractor/-/third-party-dts-extractor-0.15.0.tgz", + "integrity": "sha512-rML74G1NB9wtHubXP+ZTMI5HZkYypN/E93w8Zkwr6rc/k1eoZZza2lghw2znCNeu3lDlhvI9i4iaVsJQrX4oQA==", + "dev": true, + "dependencies": { + "find-pkg": "2.0.0", + "fs-extra": "9.1.0", + "resolve": "1.22.8" + } + }, + "node_modules/@module-federation/node/node_modules/@module-federation/webpack-bundler-runtime": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.15.0.tgz", + "integrity": "sha512-i+3wu2Ljh2TmuUpsnjwZVupOVqV50jP0ndA8PSP4gwMKlgdGeaZ4VH5KkHAXGr2eiYUxYLMrJXz1+eILJqeGDg==", + "dev": true, + "dependencies": { + "@module-federation/runtime": "0.15.0", + "@module-federation/sdk": "0.15.0" + } + }, + "node_modules/@module-federation/node/node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@module-federation/node/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@module-federation/node/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@module-federation/node/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@module-federation/node/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@module-federation/node/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@module-federation/node/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@module-federation/node/node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@module-federation/node/node_modules/koa": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.1.tgz", + "integrity": "sha512-umfX9d3iuSxTQP4pnzLOz0HKnPg0FaUUIKcye2lOiz3KPu1Y3M3xlz76dISdFPQs37P9eJz1wUpcTS6KDPn9fA==", + "dev": true, + "dependencies": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.9.0", + "debug": "^4.3.2", + "delegates": "^1.0.0", + "depd": "^2.0.0", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^2.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "engines": { + "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + } + }, + "node_modules/@module-federation/node/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@module-federation/node/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@module-federation/node/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@module-federation/node/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@module-federation/node/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@module-federation/node/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@module-federation/rspack": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@module-federation/rspack/-/rspack-0.9.1.tgz", + "integrity": "sha512-ZJqG75dWHhyTMa9I0YPJEV2XRt0MFxnDiuMOpI92esdmwWY633CBKyNh1XxcLd629YVeTv03+whr+Fz/f91JEw==", + "dev": true, + "dependencies": { + "@module-federation/bridge-react-webpack-plugin": "0.9.1", + "@module-federation/dts-plugin": "0.9.1", + "@module-federation/inject-external-runtime-core-plugin": "0.9.1", + "@module-federation/managers": "0.9.1", + "@module-federation/manifest": "0.9.1", + "@module-federation/runtime-tools": "0.9.1", + "@module-federation/sdk": "0.9.1" + }, + "peerDependencies": { + "@rspack/core": ">=0.7", + "typescript": "^4.9.0 || ^5.0.0", + "vue-tsc": ">=1.0.24" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/@module-federation/runtime": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.9.1.tgz", + "integrity": "sha512-jp7K06weabM5BF5sruHr/VLyalO+cilvRDy7vdEBqq88O9mjc0RserD8J+AP4WTl3ZzU7/GRqwRsiwjjN913dA==", + "dev": true, + "dependencies": { + "@module-federation/error-codes": "0.9.1", + "@module-federation/runtime-core": "0.9.1", + "@module-federation/sdk": "0.9.1" + } + }, + "node_modules/@module-federation/runtime-core": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.9.1.tgz", + "integrity": "sha512-r61ufhKt5pjl81v7TkmhzeIoSPOaNtLynW6+aCy3KZMa3RfRevFxmygJqv4Nug1L0NhqUeWtdLejh4VIglNy5Q==", + "dev": true, + "dependencies": { + "@module-federation/error-codes": "0.9.1", + "@module-federation/sdk": "0.9.1" + } + }, + "node_modules/@module-federation/runtime-tools": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.9.1.tgz", + "integrity": "sha512-JQZ//ab+lEXoU2DHAH+JtYASGzxEjXB0s4rU+6VJXc8c+oUPxH3kWIwzjdncg2mcWBmC1140DCk+K+kDfOZ5CQ==", + "dev": true, + "dependencies": { + "@module-federation/runtime": "0.9.1", + "@module-federation/webpack-bundler-runtime": "0.9.1" + } + }, + "node_modules/@module-federation/sdk": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.9.1.tgz", + "integrity": "sha512-YQonPTImgnCqZjE/A+3N2g3J5ypR6kx1tbBzc9toUANKr/dw/S63qlh/zHKzWQzxjjNNVMdXRtTMp07g3kgEWg==", + "dev": true + }, + "node_modules/@module-federation/third-party-dts-extractor": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@module-federation/third-party-dts-extractor/-/third-party-dts-extractor-0.9.1.tgz", + "integrity": "sha512-KeIByP718hHyq+Mc53enZ419pZZ1fh9Ns6+/bYLkc3iCoJr/EDBeiLzkbMwh2AS4Qk57WW0yNC82xzf7r0Zrrw==", + "dev": true, + "dependencies": { + "find-pkg": "2.0.0", + "fs-extra": "9.1.0", + "resolve": "1.22.8" + } + }, + "node_modules/@module-federation/third-party-dts-extractor/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@module-federation/webpack-bundler-runtime": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.9.1.tgz", + "integrity": "sha512-CxySX01gT8cBowKl9xZh+voiHvThMZ471icasWnlDIZb14KasZoX1eCh9wpGvwoOdIk9rIRT7h70UvW9nmop6w==", + "dev": true, + "dependencies": { + "@module-federation/runtime": "0.9.1", + "@module-federation/sdk": "0.9.1" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", + "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@napi-rs/nice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.1.tgz", + "integrity": "sha512-zM0mVWSXE0a0h9aKACLwKmD6nHcRiKrPpCfvaKqG1CqDEyjEawId0ocXxVzPMCAm6kkWr2P025msfxXEnt8UGQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.0.1", + "@napi-rs/nice-android-arm64": "1.0.1", + "@napi-rs/nice-darwin-arm64": "1.0.1", + "@napi-rs/nice-darwin-x64": "1.0.1", + "@napi-rs/nice-freebsd-x64": "1.0.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.0.1", + "@napi-rs/nice-linux-arm64-gnu": "1.0.1", + "@napi-rs/nice-linux-arm64-musl": "1.0.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.0.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.0.1", + "@napi-rs/nice-linux-s390x-gnu": "1.0.1", + "@napi-rs/nice-linux-x64-gnu": "1.0.1", + "@napi-rs/nice-linux-x64-musl": "1.0.1", + "@napi-rs/nice-win32-arm64-msvc": "1.0.1", + "@napi-rs/nice-win32-ia32-msvc": "1.0.1", + "@napi-rs/nice-win32-x64-msvc": "1.0.1" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.1.tgz", + "integrity": "sha512-5qpvOu5IGwDo7MEKVqqyAxF90I6aLj4n07OzpARdgDRfz8UbBztTByBp0RC59r3J1Ij8uzYi6jI7r5Lws7nn6w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.0.1.tgz", + "integrity": "sha512-GqvXL0P8fZ+mQqG1g0o4AO9hJjQaeYG84FRfZaYjyJtZZZcMjXW5TwkL8Y8UApheJgyE13TQ4YNUssQaTgTyvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-91k3HEqUl2fsrz/sKkuEkscj6EAj3/eZNCLqzD2AA0TtVbkQi8nqxZCZDMkfklULmxLkMxuUdKe7RvG/T6s2AA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.0.1.tgz", + "integrity": "sha512-jXnMleYSIR/+TAN/p5u+NkCA7yidgswx5ftqzXdD5wgy/hNR92oerTXHc0jrlBisbd7DpzoaGY4cFD7Sm5GlgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.0.1.tgz", + "integrity": "sha512-j+iJ/ezONXRQsVIB/FJfwjeQXX7A2tf3gEXs4WUGFrJjpe/z2KB7sOv6zpkm08PofF36C9S7wTNuzHZ/Iiccfw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.0.1.tgz", + "integrity": "sha512-G8RgJ8FYXYkkSGQwywAUh84m946UTn6l03/vmEXBYNJxQJcD+I3B3k5jmjFG/OPiU8DfvxutOP8bi+F89MCV7Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.0.1.tgz", + "integrity": "sha512-IMDak59/W5JSab1oZvmNbrms3mHqcreaCeClUjwlwDr0m3BoR09ZiN8cKFBzuSlXgRdZ4PNqCYNeGQv7YMTjuA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.0.1.tgz", + "integrity": "sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.0.1.tgz", + "integrity": "sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.0.1.tgz", + "integrity": "sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.0.1.tgz", + "integrity": "sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.0.1.tgz", + "integrity": "sha512-XQAJs7DRN2GpLN6Fb+ZdGFeYZDdGl2Fn3TmFlqEL5JorgWKrQGRUrpGKbgZ25UeZPILuTKJ+OowG2avN8mThBA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.0.1.tgz", + "integrity": "sha512-/rodHpRSgiI9o1faq9SZOp/o2QkKQg7T+DK0R5AkbnI/YxvAIEHf2cngjYzLMQSQgUhxym+LFr+UGZx4vK4QdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.0.1.tgz", + "integrity": "sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.0.1.tgz", + "integrity": "sha512-t7eBAyPUrWL8su3gDxw9xxxqNwZzAqKo0Szv3IjVQd1GpXXVkb6vBBQUuxfIYaXMzZLwlxRQ7uzM2vdUE9ULGw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.0.1.tgz", + "integrity": "sha512-JlF+uDcatt3St2ntBG8H02F1mM45i5SF9W+bIKiReVE6wiy3o16oBP/yxt+RZ+N6LbCImJXJ6bXNO2kn9AXicg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", + "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.9.0" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@notionhq/client": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@notionhq/client/-/client-2.2.11.tgz", + "integrity": "sha512-yTKeVu0fB9PtlK9eQwN6y+MEq0J0vu1MPP7j5bme9+KTenH4OTcOAx/70+LgvN4Z4RSsymQxdXrtcLHMFN9ZuA==", + "dependencies": { + "@types/node-fetch": "^2.5.10", + "node-fetch": "^2.6.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nrwl/nx-cloud": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/@nrwl/nx-cloud/-/nx-cloud-19.1.0.tgz", + "integrity": "sha512-krngXVPfX0Zf6+zJDtcI59/Pt3JfcMPMZ9C/+/x6rvz4WGgyv1s0MI4crEUM0Lx5ZpS4QI0WNDCFVQSfGEBXUg==", + "dependencies": { + "nx-cloud": "19.1.0" + } + }, + "node_modules/@nx/devkit": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-21.1.2.tgz", + "integrity": "sha512-1dgjwSsNDdp/VXydZnSfzfVwySEB3C9yjzeIw6+3+nRvZfH16a7ggZE7MF5sJTq4d+01hAgIDz3KyvGa6Jf73g==", + "dependencies": { + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "minimatch": "9.0.3", + "semver": "^7.5.3", + "tmp": "~0.2.1", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": "21.1.2" + } + }, + "node_modules/@nx/esbuild": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/esbuild/-/esbuild-21.1.2.tgz", + "integrity": "sha512-6h3f8mC/5e2JxFAJaE4kLALkaoAs0nVB3aFBV+nd3+0mwywbcnMQ+dibvGCrBz2EPYlWczo43upAFEvvqpdUag==", + "dev": true, + "dependencies": { + "@nx/devkit": "21.1.2", + "@nx/js": "21.1.2", + "picocolors": "^1.1.0", + "tinyglobby": "^0.2.12", + "tsconfig-paths": "^4.1.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "esbuild": ">=0.19.2 <1.0.0" + }, + "peerDependenciesMeta": { + "esbuild": { + "optional": true + } + } + }, + "node_modules/@nx/eslint": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/eslint/-/eslint-21.1.2.tgz", + "integrity": "sha512-Mp8u0RlkhxYtZ47d2ou6t8XIpRy7N/n23OzikqMro4Wt/DK1irGyShSoNIqdGdwalAE5MG1OFXspttXB+y/wOQ==", + "dev": true, + "dependencies": { + "@nx/devkit": "21.1.2", + "@nx/js": "21.1.2", + "semver": "^7.5.3", + "tslib": "^2.3.0", + "typescript": "~5.7.2" + }, + "peerDependencies": { + "@zkochan/js-yaml": "0.0.7", + "eslint": "^8.0.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "@zkochan/js-yaml": { + "optional": true + } + } + }, + "node_modules/@nx/eslint-plugin": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/eslint-plugin/-/eslint-plugin-21.1.2.tgz", + "integrity": "sha512-kwhwe6e8dZ0pf5CYPq4OBck15NEJrfuivCEGRTIDZWu3WDYJIw7OvhfyCdGuoZLeHGoCVRjIU6xV5hOzkD9RSw==", + "dev": true, + "dependencies": { + "@nx/devkit": "21.1.2", + "@nx/js": "21.1.2", + "@typescript-eslint/type-utils": "^8.0.0", + "@typescript-eslint/utils": "^8.0.0", + "chalk": "^4.1.0", + "confusing-browser-globals": "^1.0.9", + "globals": "^15.9.0", + "jsonc-eslint-parser": "^2.1.0", + "semver": "^7.5.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.13.2 || ^7.0.0 || ^8.0.0", + "eslint-config-prettier": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/@nx/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.1.tgz", + "integrity": "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.35.1", + "@typescript-eslint/visitor-keys": "8.35.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@nx/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.1.tgz", + "integrity": "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@nx/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.1.tgz", + "integrity": "sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==", + "dev": true, + "dependencies": { + "@typescript-eslint/project-service": "8.35.1", + "@typescript-eslint/tsconfig-utils": "8.35.1", + "@typescript-eslint/types": "8.35.1", + "@typescript-eslint/visitor-keys": "8.35.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@nx/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.1.tgz", + "integrity": "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.35.1", + "@typescript-eslint/types": "8.35.1", + "@typescript-eslint/typescript-estree": "8.35.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@nx/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.1.tgz", + "integrity": "sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.35.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@nx/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@nx/eslint-plugin/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nx/eslint-plugin/node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/@nx/eslint/node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@nx/jest": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/jest/-/jest-21.1.2.tgz", + "integrity": "sha512-y4VZita9LFb6XajulRIwjMcqHU6/f73C4SNSH6IM5BYmkN68ovICmzTGvoaL7wGTaYrA4Moh/WoKwEwQWKxRPQ==", + "dev": true, + "dependencies": { + "@jest/reporters": "^29.4.1", + "@jest/test-result": "^29.4.1", + "@nx/devkit": "21.1.2", + "@nx/js": "21.1.2", + "@phenomnomnominal/tsquery": "~5.0.1", + "identity-obj-proxy": "3.0.0", + "jest-config": "^29.4.1", + "jest-resolve": "^29.4.1", + "jest-util": "^29.4.1", + "minimatch": "9.0.3", + "picocolors": "^1.1.0", + "resolve.exports": "2.0.3", + "semver": "^7.5.3", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + } + }, + "node_modules/@nx/js": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/js/-/js-21.1.2.tgz", + "integrity": "sha512-ZF6Zf4Ys+RBvH0GoQHio94C/0N07Px/trAvseMuQ8PKc0tSkXycu/EBc1uAZQvgJThR5o3diAKtIQug77pPYMQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.2", + "@babel/plugin-proposal-decorators": "^7.22.7", + "@babel/plugin-transform-class-properties": "^7.22.5", + "@babel/plugin-transform-runtime": "^7.23.2", + "@babel/preset-env": "^7.23.2", + "@babel/preset-typescript": "^7.22.5", + "@babel/runtime": "^7.22.6", + "@nx/devkit": "21.1.2", + "@nx/workspace": "21.1.2", + "@zkochan/js-yaml": "0.0.7", + "babel-plugin-const-enum": "^1.0.1", + "babel-plugin-macros": "^3.1.0", + "babel-plugin-transform-typescript-metadata": "^0.3.1", + "chalk": "^4.1.0", + "columnify": "^1.6.0", + "detect-port": "^1.5.1", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "js-tokens": "^4.0.0", + "jsonc-parser": "3.2.0", + "npm-package-arg": "11.0.1", + "npm-run-path": "^4.0.1", + "ora": "5.3.0", + "picocolors": "^1.1.0", + "picomatch": "4.0.2", + "semver": "^7.5.3", + "source-map-support": "0.5.19", + "tinyglobby": "^0.2.12", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "verdaccio": "^6.0.5" + }, + "peerDependenciesMeta": { + "verdaccio": { + "optional": true + } + } + }, + "node_modules/@nx/module-federation": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/module-federation/-/module-federation-21.1.2.tgz", + "integrity": "sha512-19aodN8uh3fEI2ifXYDXTV4hpQMk5Ko3UAFW+x0dQwlzSUIObt2mBMHL0PIkFXlT1fyz/rH+tkx67/DAAUxVZw==", + "dev": true, + "dependencies": { + "@module-federation/enhanced": "^0.9.0", + "@module-federation/node": "^2.6.26", + "@module-federation/sdk": "^0.9.0", + "@nx/devkit": "21.1.2", + "@nx/js": "21.1.2", + "@nx/web": "21.1.2", + "@rspack/core": "^1.3.8", + "express": "^4.21.2", + "http-proxy-middleware": "^3.0.3", + "picocolors": "^1.1.0", + "tslib": "^2.3.0", + "webpack": "^5.88.0" + } + }, + "node_modules/@nx/module-federation/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nx/module-federation/node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/@nx/module-federation/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nx/module-federation/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/@nx/module-federation/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@nx/module-federation/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/@nx/module-federation/node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@nx/module-federation/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nx/module-federation/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nx/module-federation/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@nx/module-federation/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nx/module-federation/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nx/module-federation/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@nx/module-federation/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nx/module-federation/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true + }, + "node_modules/@nx/module-federation/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@nx/module-federation/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nx/module-federation/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/@nx/module-federation/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@nx/module-federation/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nx/module-federation/node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@nx/module-federation/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nx/module-federation/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nx/node": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/node/-/node-21.1.2.tgz", + "integrity": "sha512-BCKooOKT04MJDzLy6U4w3mFWhHCsuoMXqUjcd5g/3zf4bFXOK3ooklvVkxjHUQxRXVG/uPJ+ZcgTC1SE0vpS6g==", + "dev": true, + "dependencies": { + "@nx/devkit": "21.1.2", + "@nx/eslint": "21.1.2", + "@nx/jest": "21.1.2", + "@nx/js": "21.1.2", + "kill-port": "^1.6.1", + "tcp-port-used": "^1.0.2", + "tslib": "^2.3.0" + } + }, + "node_modules/@nx/nx-darwin-arm64": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-21.1.2.tgz", + "integrity": "sha512-9dO32jd+h7SrvQafJph6b7Bsmp2IotTE0w7dAGb4MGBQni3JWCXaxlMMpWUZXWW1pM5uIkFJO5AASW4UOI7w2w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@nx/nx-darwin-x64": { + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-18.0.4.tgz", + "integrity": "sha512-rFKHjeU0Ngz1R7UJAsbncpqwuFDjUdpcvI783r6s2eP7JoiiwtDBXvDcHiy8Odk0lPYmwDELaFZBhvdENqaDNA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-freebsd-x64": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-21.1.2.tgz", + "integrity": "sha512-E5HR44fimXlQuAgn/tP9esmvxbzt/92AIl0PBT6L3Juh/xYiXKWhda63H4+UNT8AcLRxVXwfZrGPuGCDs+7y/Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@nx/nx-linux-arm-gnueabihf": { + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-18.0.4.tgz", + "integrity": "sha512-C3qWbFhEMIdTzvAHlIUHecZN3YBu7bx3S0p3gPNGmEMUMbYHP2zMlimBrZIbAxzntyGqWCqhXiFB21QhJ0t1Dw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-linux-arm64-gnu": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-21.1.2.tgz", + "integrity": "sha512-NFhsp27O+mS3r7PWLmJgyZy42WQ72c2pTQSpYfhaBbZPTI5DqBHdANa0sEPmV+ON24qkl5CZKvsmhzjsNmyW6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-linux-arm64-musl": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-21.1.2.tgz", + "integrity": "sha512-BgS9npARwcnw+hoaRsbas6vdBAJRBAj5qSeL57LO8Dva+e/6PYqoNyVJ0BgJ98xPXDpzM/NnpeRsndQGpLyhDw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-linux-x64-gnu": { + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-18.0.4.tgz", + "integrity": "sha512-BVLkegIwxHnEB64VBraBxyC01D3C3dVNxq2b4iNaqr4mpWNmos+G/mvcTU3NS7W8ZjpBjlXgdEkpgkl2hMKTEA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-linux-x64-musl": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-21.1.2.tgz", + "integrity": "sha512-+0V0YAOWMh1wvpQZuayQ7y+sj2MhE3l7z0JMD9SX/4xv9zLOWGv+EiUmN/fGoU/mwsSkH2wTCo6G6quKF1E8jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-win32-arm64-msvc": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-21.1.2.tgz", + "integrity": "sha512-E+ECMQIMJ6R47BMW5YpDyOhTqczvFaL8k24umRkcvlRh3SraczyxBVPkYHDukDp7tCeIszc5EvdWc83C3W8U4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@nx/nx-win32-x64-msvc": { + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-18.0.4.tgz", + "integrity": "sha512-FdAdl5buvtUXp8hZVRkK0AZeiCu35l0u+yHsulNViYdh3OXRT1hYJ0CeqpxlLfvbHqB9JzDPtJtG0dpKHH/O0Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/react": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/react/-/react-21.1.2.tgz", + "integrity": "sha512-9uvH7fHJ2Qm//7fpwPDLncaBG3QrtQiaS8JmOlsFmOv9vmznXIgfpIi9Ce1ZgfgX41E568E23aI77Hz9i/72zw==", + "dev": true, + "dependencies": { + "@nx/devkit": "21.1.2", + "@nx/eslint": "21.1.2", + "@nx/js": "21.1.2", + "@nx/module-federation": "21.1.2", + "@nx/web": "21.1.2", + "@phenomnomnominal/tsquery": "~5.0.1", + "@svgr/webpack": "^8.0.1", + "express": "^4.21.2", + "file-loader": "^6.2.0", + "http-proxy-middleware": "^3.0.3", + "minimatch": "9.0.3", + "picocolors": "^1.1.0", + "semver": "^7.6.3", + "tslib": "^2.3.0" + } + }, + "node_modules/@nx/react/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nx/react/node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/@nx/react/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nx/react/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/@nx/react/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@nx/react/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/@nx/react/node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@nx/react/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nx/react/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nx/react/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@nx/react/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nx/react/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nx/react/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@nx/react/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nx/react/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true + }, + "node_modules/@nx/react/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@nx/react/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nx/react/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/@nx/react/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nx/react/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@nx/react/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nx/react/node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@nx/react/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nx/react/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nx/vite": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/vite/-/vite-21.1.2.tgz", + "integrity": "sha512-qKb3CTPtcs3MsDebNW7PUS10IDB1+w//iXKFobwmclH4uW/HFUMRcdUrIsdcQfdmQPjGNTTM2fwmbgWJC4qmAw==", + "dev": true, + "dependencies": { + "@nx/devkit": "21.1.2", + "@nx/js": "21.1.2", + "@phenomnomnominal/tsquery": "~5.0.1", + "@swc/helpers": "~0.5.0", + "ajv": "^8.0.0", + "enquirer": "~2.3.6", + "picomatch": "4.0.2", + "semver": "^7.6.3", + "tsconfig-paths": "^4.1.2" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vitest": "^1.3.1 || ^2.0.0 || ^3.0.0" + } + }, + "node_modules/@nx/vite/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nx/web": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/web/-/web-21.1.2.tgz", + "integrity": "sha512-ONw3bEO6rc9DqM9Jnt6Rc5xkSBMzruWA2KvHVlU4qaoUs1VKbnmJ28dM72lFMn8wbOOeq+RG7GC2nBpifBPLHw==", + "dev": true, + "dependencies": { + "@nx/devkit": "21.1.2", + "@nx/js": "21.1.2", + "detect-port": "^1.5.1", + "http-server": "^14.1.0", + "picocolors": "^1.1.0", + "tslib": "^2.3.0" + } + }, + "node_modules/@nx/webpack": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/webpack/-/webpack-21.1.2.tgz", + "integrity": "sha512-MtTXjxT8HB47uaMi4Hw6VGnyycQ5gm7Trazk4Fq7mr4ReIrP0vp1BGnNgho2d1dFONY0eA6MeBf9j/5jOI+MBA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.2", + "@nx/devkit": "21.1.2", + "@nx/js": "21.1.2", + "@phenomnomnominal/tsquery": "~5.0.1", + "ajv": "^8.12.0", + "autoprefixer": "^10.4.9", + "babel-loader": "^9.1.2", + "browserslist": "^4.21.4", + "copy-webpack-plugin": "^10.2.4", + "css-loader": "^6.4.0", + "css-minimizer-webpack-plugin": "^5.0.0", + "fork-ts-checker-webpack-plugin": "7.2.13", + "less": "4.1.3", + "less-loader": "11.1.0", + "license-webpack-plugin": "^4.0.2", + "loader-utils": "^2.0.3", + "mini-css-extract-plugin": "~2.4.7", + "parse5": "4.0.0", + "picocolors": "^1.1.0", + "postcss": "^8.4.38", + "postcss-import": "~14.1.0", + "postcss-loader": "^6.1.1", + "rxjs": "^7.8.0", + "sass": "^1.85.0", + "sass-embedded": "^1.83.4", + "sass-loader": "^16.0.4", + "source-map-loader": "^5.0.0", + "style-loader": "^3.3.0", + "stylus": "^0.64.0", + "stylus-loader": "^7.1.0", + "terser-webpack-plugin": "^5.3.3", + "ts-loader": "^9.3.1", + "tsconfig-paths-webpack-plugin": "4.0.0", + "tslib": "^2.3.0", + "webpack": "5.98.0", + "webpack-dev-server": "^5.2.1", + "webpack-node-externals": "^3.0.0", + "webpack-subresource-integrity": "^5.1.0" + } + }, + "node_modules/@nx/workspace": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-21.1.2.tgz", + "integrity": "sha512-I4e/X/GN0Vx3FDZv/7bFYmXfOPmcMI3cDO/rg+TqudsuxVM7tJ7+8jtwdpU4I2IEpI6oU9FZ7Fu9R2uNqL5rrQ==", + "dev": true, + "dependencies": { + "@nx/devkit": "21.1.2", + "@zkochan/js-yaml": "0.0.7", + "chalk": "^4.1.0", + "enquirer": "~2.3.6", + "nx": "21.1.2", + "picomatch": "4.0.2", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + } + }, + "node_modules/@octokit/auth-token": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", + "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.5.tgz", + "integrity": "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg==", + "dependencies": { + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.2.2", + "@octokit/request": "^9.2.3", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", + "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.2.tgz", + "integrity": "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==", + "dependencies": { + "@octokit/request": "^9.2.3", + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.6.0.tgz", + "integrity": "sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==", + "dependencies": { + "@octokit/types": "^13.10.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz", + "integrity": "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.5.0.tgz", + "integrity": "sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw==", + "dependencies": { + "@octokit/types": "^13.10.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==" + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/request": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz", + "integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==", + "dependencies": { + "@octokit/endpoint": "^10.1.4", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", + "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.1.1.tgz", + "integrity": "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==", + "dependencies": { + "@octokit/core": "^6.1.4", + "@octokit/plugin-paginate-rest": "^11.4.2", + "@octokit/plugin-request-log": "^5.3.1", + "@octokit/plugin-rest-endpoint-methods": "^13.3.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "dependencies": { + "@octokit/openapi-types": "^25.1.0" + } + }, + "node_modules/@onfleet/node-onfleet": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@onfleet/node-onfleet/-/node-onfleet-1.3.3.tgz", + "integrity": "sha512-0pcg+J9pdNEMAt/GYq96l4rmBsrsDwnTCWrjZhRCKmTlZq0QHBN/8u8smR7RPubg+DsMc5MOSPXXoLsfn03YIg==", + "dependencies": { + "bottleneck": "^2.19.5", + "node-fetch": "^2.6.7" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", + "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/@parcel/watcher/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "optional": true + }, + "node_modules/@phenomnomnominal/tsquery": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@phenomnomnominal/tsquery/-/tsquery-5.0.1.tgz", + "integrity": "sha512-3nVv+e2FQwsW8Aw6qTU6f+1rfcJ3hrcnvH/mu9i8YhxO+9sqbOfpL8m6PbET5+xKOlz/VSbp0RoYWYCtIsnmuA==", + "dev": true, + "dependencies": { + "esquery": "^1.4.0" + }, + "peerDependencies": { + "typescript": "^3 || ^4 || ^5" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@playwright/test": { + "version": "1.37.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.1.tgz", + "integrity": "sha512-bq9zTli3vWJo8S3LwB91U0qDNQDpEXnw7knhxLM0nwDvexQAwx9tO8iKDZSqqneVq+URd/WIoz+BALMqUTgdSg==", + "deprecated": "Please update to the latest version of Playwright to test up-to-date browsers.", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.37.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==" + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", + "dependencies": { + "debug": "^4.3.5", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@puppeteer/browsers/node_modules/tar-fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz", + "integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/@qdrant/js-client-rest": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@qdrant/js-client-rest/-/js-client-rest-1.7.0.tgz", + "integrity": "sha512-16O0EQfrrybcPVipodxykr6dMUlBzKW7a63cSDUFVgc5a1AWESwERykwjuvW5KqvKdkPcxZ2NssrvgUO1W3MgA==", + "dependencies": { + "@qdrant/openapi-typescript-fetch": "^1.2.1", + "@sevinf/maybe": "^0.5.0", + "undici": "^5.26.2" + }, + "engines": { + "node": ">=18.0.0", + "pnpm": ">=8" + }, + "peerDependencies": { + "typescript": ">=4.1" + } + }, + "node_modules/@qdrant/js-client-rest/node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@qdrant/js-client-rest/node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/@qdrant/openapi-typescript-fetch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@qdrant/openapi-typescript-fetch/-/openapi-typescript-fetch-1.2.6.tgz", + "integrity": "sha512-oQG/FejNpItrxRHoyctYvT3rwGZOnK4jr3JdppO/c78ktDvkWiPXPHNsrDf33K9sZdRb6PR7gi4noIapu5q4HA==", + "engines": { + "node": ">=18.0.0", + "pnpm": ">=8" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", + "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==" + }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.4.tgz", + "integrity": "sha512-SGCxlSBaMvEzDROzyZjsVNzu9XY5E28B3k8jOENyrz6csOv/pG1eHyYfLJai1n9tRjwG61coXDhfpgtxKxUv5g==", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collapsible": "1.1.4", + "@radix-ui/react-collection": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.0.3", + "@radix-ui/react-use-controllable-state": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-accordion/node_modules/@radix-ui/react-collapsible": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.4.tgz", + "integrity": "sha512-u7LCw1EYInQtBNLGjm9nZ89S/4GcvX1UR5XbekEgnQae2Hkpq39ycJ1OhdeN1/JDfVNG91kWaWoest127TaEKQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.3", + "@radix-ui/react-primitive": "2.0.3", + "@radix-ui/react-use-controllable-state": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-accordion/node_modules/@radix-ui/react-presence": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.3.tgz", + "integrity": "sha512-IrVLIhskYhH3nLvtcBLQFZr61tBG7wx7O3kEmdzcYwRGAEBmBicGGL7ATzNgruYJ3xBTbuzEEq9OXJM3PAX3tA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-accordion/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", + "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.0.tgz", + "integrity": "sha512-Q/PbuSMk/vyAd/UoIShVGZ7StHHeRFYU7wXmi5GV+8cLXflZAEpHL/F697H1klrzxKXNtZ97vWiC0q3RKUH8UA==", + "dependencies": { + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.1.tgz", + "integrity": "sha512-0i/EKJ222Afa1FE0C6pNJxDq1itzcl3HChE9DwskA4th4KRse8ojx8a1nVcOjwJdbpDLcz7uol77yYnQNMHdKw==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.0.tgz", + "integrity": "sha512-zQY7Epa8sTL0mq4ajSJpjgn2YmCgyrG7RsQgLp3C0LQVkG7+Tf6Pv1CeNWZLyqMjhdPkBa5Lx7wYBeSu7uCSTA==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.3.tgz", + "integrity": "sha512-mM2pxoQw5HJ49rkzwOs7Y6J4oYH22wS8BfK2/bBxROlI4xuR0c4jEenQP63LlTlDkO6Buj2Vt+QYAYcOgqtrXA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.0.3", + "@radix-ui/react-slot": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", + "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.2.tgz", + "integrity": "sha512-99EatSTpW+hRYHt7m8wdDlLtkmTovEe8Z/hnxUPV+SKuuNL5HWNhQI4QSdjZqNSgXHay2z4M3Dym73j9p2Gx5Q==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-menu": "2.1.2", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-context-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz", + "integrity": "sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz", + "integrity": "sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.1.tgz", + "integrity": "sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-menu": "2.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-collection": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", + "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-menu": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.1.tgz", + "integrity": "sha512-oa3mXRRVjHi6DZu/ghuzdylyjaMXLymx83irM7hTxutQbD+7IhPKdMdRHD26Rm+kHRrWcrUkkRPv5pd47a2xFQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-roving-focus": "1.1.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz", + "integrity": "sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", + "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz", + "integrity": "sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x" + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", + "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.2.tgz", + "integrity": "sha512-lZ0R4qR2Al6fZ4yCCZzu/ReTFrylHFxIqy7OezIpWF4bL0o9biKo0pFIvkaew3TyZ9Fy5gYVrR5zCGZBVbO1zg==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-roving-focus": "1.1.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-collection": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", + "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", + "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", + "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", + "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/react-remove-scroll": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", + "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.6", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.1.tgz", + "integrity": "sha512-3y1A3isulwnWhvTTwmIreiB8CF4L+qRjZnK1wYLO7pplddzXKby/GnZ2M7OZY3qgnl6p9AodUIHRYGXNah8Y7g==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", + "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", + "integrity": "sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz", + "integrity": "sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.3.tgz", + "integrity": "sha512-Pf/t/GkndH7CQ8wE2hbkXA+WyZ83fhQQn5DDmwDiDo6AwN/fhaH8oqZ0jRjMrO2iaMhDi6P1HRx6AZwyMinY1g==", + "dependencies": { + "@radix-ui/react-slot": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", + "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.0.tgz", + "integrity": "sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg==", + "dependencies": { + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.2.0.tgz", + "integrity": "sha512-yv+oiLaicYMBpqgfpSPw6q+RyXlLdIpQWDHZbUKURxe+nEh53hFXPPlfhfQQtYkS5MMK/5IWIa76SksleQZSzw==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-roving-focus": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", + "integrity": "sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-collection": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", + "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.1.0.tgz", + "integrity": "sha512-9ArIZ9HWhsrfqS765h+GZuLoxaRHD/j0ZWOWilsCvYTpYJp8XwCqNG7Dt9Nu/TItKOdgLGkOPCodQvDc+UMwYg==", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.1.tgz", + "integrity": "sha512-8iRDfyLtzxlprOo9IicnzvpsO1wNCkuwzzCM+Z5Rb5tNOpCdMvcc2AkzX0Fz+Tz9v6NJ5B/7EEgyZveo4FBRfQ==", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-collection": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", + "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.0.tgz", + "integrity": "sha512-3uBAs+egzvJBDZAzvb/n4NxxOYpnspmWxO2u5NbZ8Y6FM/NdrGSF9bop3Cf6F6C71z1rTSn8KV0Fo2ZVd79lGA==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.5.tgz", + "integrity": "sha512-rkfe2pU2NBAYfGaxa3Mqosi7VZEWX5CxKaanRv0vZd4Zhl9fvQrg0VM93dv3xGLGfrHuoTRF3JXH8nb9g+B3fw==", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider/node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==" + }, + "node_modules/@radix-ui/react-slider/node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider/node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider/node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.0.tgz", + "integrity": "sha512-OBzy5WAj641k0AOSpKQtreDMe+isX0MQJ1IVyF03ucdF3DunOnROVrjWs8zsXUxC3zfZ6JL9HFVCUlMghz9dJw==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.0.tgz", + "integrity": "sha512-bZgOKB/LtZIij75FSuPzyEti/XBhJH52ExgtdVqjCIh+Nx/FW+LhnbXtbCzIi34ccyMsyOja8T0thCzoHFXNKA==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-roving-focus": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.1.tgz", + "integrity": "sha512-5trl7piMXcZiCq7MW6r8YYmu0bK5qDpTWz+FdEPdKyft2UixkspheYbjbrLXVN5NGKHFbOP7lm8eD0biiSqZqg==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-collection": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", + "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.0.tgz", + "integrity": "sha512-gwoxaKZ0oJ4vIgzsfESBuSgJNdc0rv12VhHgcqN0TEJmmZixXG/2XpsLK8kzNWYcnaoRIEEQc0bEi3dIvdUpjw==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.2.tgz", + "integrity": "sha512-9XRsLwe6Yb9B/tlnYCPVUd/TFS4J7HuOZW345DCeC6vKIxQGMZdx21RK4VoZauPD5frgkXTYVS5y90L+3YBn4w==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.1.tgz", + "integrity": "sha512-YnEXIy8/ga01Y1PN0VfaNH//MhA91JlEGVBDxDzROqwrAtG5Yr2QGEPz8A/rJA3C7ZAHryOYGaUv8fLSW2H/mg==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", + "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", + "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", + "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", + "dependencies": { + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", + "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz", + "integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", + "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==" + }, + "node_modules/@remirror/core-constants": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-2.0.2.tgz", + "integrity": "sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==" + }, + "node_modules/@remix-run/router": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.2.tgz", + "integrity": "sha512-LzqpSrMK/3JBAVBI9u3NWtOhWNw5AMQfrUFYB0+bDHTSw17z++WJLsPsxAuK+oSddsxk4d7F/JcdDPM1M5YAhA==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.19", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz", + "integrity": "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==", + "dev": true + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz", + "integrity": "sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.5.tgz", + "integrity": "sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", + "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.5.tgz", + "integrity": "sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.5.tgz", + "integrity": "sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", + "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.5.tgz", + "integrity": "sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.5.tgz", + "integrity": "sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.5.tgz", + "integrity": "sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.5.tgz", + "integrity": "sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz", + "integrity": "sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz", + "integrity": "sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.5.tgz", + "integrity": "sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.5.tgz", + "integrity": "sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz", + "integrity": "sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/wasm-node": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/wasm-node/-/wasm-node-4.21.2.tgz", + "integrity": "sha512-AJCfdXkpe5EX+jfWOMYuFl3ZomTQyfx4V4geRmChdTwAo05FdpnobwqtYn0mo7Mf1qVN7mniI7kdG98vKDVd2g==", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/@rspack/binding": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.4.1.tgz", + "integrity": "sha512-zYgOmI+LC2zxB/LIcnaeK66ElFHaPChdWzRruTT1LAFFwpgGkBGAwFoP27PDnxQW0Aejci21Ld8X9tyxm08QFw==", + "dev": true, + "optionalDependencies": { + "@rspack/binding-darwin-arm64": "1.4.1", + "@rspack/binding-darwin-x64": "1.4.1", + "@rspack/binding-linux-arm64-gnu": "1.4.1", + "@rspack/binding-linux-arm64-musl": "1.4.1", + "@rspack/binding-linux-x64-gnu": "1.4.1", + "@rspack/binding-linux-x64-musl": "1.4.1", + "@rspack/binding-wasm32-wasi": "1.4.1", + "@rspack/binding-win32-arm64-msvc": "1.4.1", + "@rspack/binding-win32-ia32-msvc": "1.4.1", + "@rspack/binding-win32-x64-msvc": "1.4.1" + } + }, + "node_modules/@rspack/binding-darwin-arm64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.4.1.tgz", + "integrity": "sha512-enh5DYbpaexdEmjbcxj3BJDauP3w+20jFKWvKROtAQV350PUw0bf2b4WOgngIH9hBzlfjpXNYAk6T5AhVAlY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rspack/binding-darwin-x64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.4.1.tgz", + "integrity": "sha512-KoehyhBji4TLXhn4mqOUw6xsQNRzNVA9XcCm1Jx+M1Qb0dhMTNfduvBSyXuRV5+/QaRbk7+4UJbyRNFUtt96kA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rspack/binding-linux-arm64-gnu": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.4.1.tgz", + "integrity": "sha512-PJ5cHqvrj1bK7jH5DVrdKoR8Fy+p6l9baxXajq/6xWTxP+4YTdEtLsRZnpLMS1Ho2RRpkxDWJn+gdlKuleNioQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-linux-arm64-musl": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.4.1.tgz", + "integrity": "sha512-cpDz+z3FwVQfK6VYfXQEb0ym6fFIVmvK4y3R/2VAbVGWYVxZB5I6AcSdOWdDnpppHmcHpf+qQFlwhHvbpMMJNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-linux-x64-gnu": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.4.1.tgz", + "integrity": "sha512-jjTx53CpiYWK7fAv5qS8xHEytFK6gLfZRk+0kt2YII6uqez/xQ3SRcboreH8XbJcBoxINBzMNMf5/SeMBZ939A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-linux-x64-musl": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.4.1.tgz", + "integrity": "sha512-FAyR3Og81Smtr/CnsuTiW4ZCYAPCqeV73lzMKZ9xdVUgM9324ryEgqgX38GZLB5Mo7cvQhv7/fpMeHQo16XQCw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-wasm32-wasi": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.4.1.tgz", + "integrity": "sha512-3Q1VICIQP4GsaTJEmmwfowQ48NvhlL0CKH88l5+mbji2rBkGx7yR67pPdfCVNjXcCtFoemTYw98eaumJTjC++g==", + "cpu": [ + "wasm32" + ], + "dev": true, + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + } + }, + "node_modules/@rspack/binding-win32-arm64-msvc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.4.1.tgz", + "integrity": "sha512-DdLPOy1J98kn45uEhiEqlBKgMvet+AxOzX2OcrnU0wQXthGM9gty1YXYNryOhlK+X+eOcwcP3GbnDOAKi8nKqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rspack/binding-win32-ia32-msvc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.4.1.tgz", + "integrity": "sha512-13s8fYtyC9DyvKosD2Kvzd6fVZDZZyPp91L4TEXWaO0CFhaCbtLTYIntExq9MwtKHYKKx7bchIFw93o0xjKjUg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rspack/binding-win32-x64-msvc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.4.1.tgz", + "integrity": "sha512-ubQW8FcLnwljDanwTzkC9Abyo59gmX8m9uVr1GHOEuEU9Cua0KMijX2j/MYfiziz4nuQgv1saobY7K1I5nE3YA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rspack/core": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.4.1.tgz", + "integrity": "sha512-UTRCTQk2G8YiPBiMvfn8FcysxeO4Muek6a/Z39Cw2r4ZI8k5iPnKiyZboTJLS7120PwWBw2SO+QQje35Z44x0g==", + "dev": true, + "dependencies": { + "@module-federation/runtime-tools": "0.15.0", + "@rspack/binding": "1.4.1", + "@rspack/lite-tapable": "1.0.1" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.1" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@rspack/core/node_modules/@module-federation/error-codes": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.15.0.tgz", + "integrity": "sha512-CFJSF+XKwTcy0PFZ2l/fSUpR4z247+Uwzp1sXVkdIfJ/ATsnqf0Q01f51qqSEA6MYdQi6FKos9FIcu3dCpQNdg==", + "dev": true + }, + "node_modules/@rspack/core/node_modules/@module-federation/runtime": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.15.0.tgz", + "integrity": "sha512-dTPsCNum9Bhu3yPOcrPYq0YnM9eCMMMNB1wuiqf1+sFbQlNApF0vfZxooqz3ln0/MpgE0jerVvFsLVGfqvC9Ug==", + "dev": true, + "dependencies": { + "@module-federation/error-codes": "0.15.0", + "@module-federation/runtime-core": "0.15.0", + "@module-federation/sdk": "0.15.0" + } + }, + "node_modules/@rspack/core/node_modules/@module-federation/runtime-core": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.15.0.tgz", + "integrity": "sha512-RYzI61fRDrhyhaEOXH3AgIGlHiot0wPFXu7F43cr+ZnTi+VlSYWLdlZ4NBuT9uV6JSmH54/c+tEZm5SXgKR2sQ==", + "dev": true, + "dependencies": { + "@module-federation/error-codes": "0.15.0", + "@module-federation/sdk": "0.15.0" + } + }, + "node_modules/@rspack/core/node_modules/@module-federation/runtime-tools": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.15.0.tgz", + "integrity": "sha512-kzFn3ObUeBp5vaEtN1WMxhTYBuYEErxugu1RzFUERD21X3BZ+b4cWwdFJuBDlsmVjctIg/QSOoZoPXRKAO0foA==", + "dev": true, + "dependencies": { + "@module-federation/runtime": "0.15.0", + "@module-federation/webpack-bundler-runtime": "0.15.0" + } + }, + "node_modules/@rspack/core/node_modules/@module-federation/sdk": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.15.0.tgz", + "integrity": "sha512-PWiYbGcJrKUD6JZiEPihrXhV3bgXdll4bV7rU+opV7tHaun+Z0CdcawjZ82Xnpb8MCPGmqHwa1MPFeUs66zksw==", + "dev": true + }, + "node_modules/@rspack/core/node_modules/@module-federation/webpack-bundler-runtime": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.15.0.tgz", + "integrity": "sha512-i+3wu2Ljh2TmuUpsnjwZVupOVqV50jP0ndA8PSP4gwMKlgdGeaZ4VH5KkHAXGr2eiYUxYLMrJXz1+eILJqeGDg==", + "dev": true, + "dependencies": { + "@module-federation/runtime": "0.15.0", + "@module-federation/sdk": "0.15.0" + } + }, + "node_modules/@rspack/lite-tapable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.0.1.tgz", + "integrity": "sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true + }, + "node_modules/@segment/analytics-core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-core/-/analytics-core-1.6.0.tgz", + "integrity": "sha512-bn9X++IScUfpT7aJGjKU/yJAu/Ko2sYD6HsKA70Z2560E89x30pqgqboVKY8kootvQnT4UKCJiUr5NDMgjmWdQ==", + "dependencies": { + "@lukeed/uuid": "^2.0.0", + "@segment/analytics-generic-utils": "1.2.0", + "dset": "^3.1.2", + "tslib": "^2.4.1" + } + }, + "node_modules/@segment/analytics-generic-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-generic-utils/-/analytics-generic-utils-1.2.0.tgz", + "integrity": "sha512-DfnW6mW3YQOLlDQQdR89k4EqfHb0g/3XvBXkovH1FstUN93eL1kfW9CsDcVQyH3bAC5ZsFyjA/o/1Q2j0QeoWw==", + "dependencies": { + "tslib": "^2.4.1" + } + }, + "node_modules/@segment/analytics-next": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-next/-/analytics-next-1.72.0.tgz", + "integrity": "sha512-NqJ8819q1DJ2nWo71XnJhkdBYIh0oYOP8yBqJhcyqsQYiSo3Eg4NGLm+7ubMcFzB/1YRM005medyvokEmDocuQ==", + "dependencies": { + "@lukeed/uuid": "^2.0.0", + "@segment/analytics-core": "1.6.0", + "@segment/analytics-generic-utils": "1.2.0", + "@segment/analytics.js-video-plugins": "^0.2.1", + "@segment/facade": "^3.4.9", + "dset": "^3.1.2", + "js-cookie": "3.0.1", + "node-fetch": "^2.6.7", + "tslib": "^2.4.1", + "unfetch": "^4.1.0" + } + }, + "node_modules/@segment/analytics-node": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-node/-/analytics-node-2.2.0.tgz", + "integrity": "sha512-mPFTSBr9CrkFBdgr7KU/YD8V/25P8vPb/hVvVHYKwEdHRovlizZ34ENgQlvqeRuamQiXD3RLM8pcWX+WxPz3lQ==", + "dependencies": { + "@lukeed/uuid": "^2.0.0", + "@segment/analytics-core": "1.8.0", + "@segment/analytics-generic-utils": "1.2.0", + "buffer": "^6.0.3", + "jose": "^5.1.0", + "node-fetch": "^2.6.7", + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@segment/analytics-node/node_modules/@segment/analytics-core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-core/-/analytics-core-1.8.0.tgz", + "integrity": "sha512-6CrccsYRY33I3mONN2ZW8SdBpbLtu1Ict3xR+n0FemYF5RB/jG7pW6jOvDXULR8kuYMzMmGOP4HvlyUmf3qLpg==", + "dependencies": { + "@lukeed/uuid": "^2.0.0", + "@segment/analytics-generic-utils": "1.2.0", + "dset": "^3.1.4", + "tslib": "^2.4.1" + } + }, + "node_modules/@segment/analytics.js-video-plugins": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@segment/analytics.js-video-plugins/-/analytics.js-video-plugins-0.2.1.tgz", + "integrity": "sha512-lZwCyEXT4aaHBLNK433okEKdxGAuyrVmop4BpQqQSJuRz0DglPZgd9B/XjiiWs1UyOankg2aNYMN3VcS8t4eSQ==", + "dependencies": { + "unfetch": "^3.1.1" + } + }, + "node_modules/@segment/analytics.js-video-plugins/node_modules/unfetch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-3.1.2.tgz", + "integrity": "sha512-L0qrK7ZeAudGiKYw6nzFjnJ2D5WHblUBwmHIqtPS6oKUd+Hcpk7/hKsSmcHsTlpd1TbTNsiRBUKRq3bHLNIqIw==" + }, + "node_modules/@segment/facade": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/@segment/facade/-/facade-3.4.10.tgz", + "integrity": "sha512-xVQBbB/lNvk/u8+ey0kC/+g8pT3l0gCT8O2y9Z+StMMn3KAFAQ9w8xfgef67tJybktOKKU7pQGRPolRM1i1pdA==", + "dependencies": { + "@segment/isodate-traverse": "^1.1.1", + "inherits": "^2.0.4", + "new-date": "^1.0.3", + "obj-case": "0.2.1" + } + }, + "node_modules/@segment/isodate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@segment/isodate/-/isodate-1.0.3.tgz", + "integrity": "sha512-BtanDuvJqnACFkeeYje7pWULVv8RgZaqKHWwGFnL/g/TH/CcZjkIVTfGDp/MAxmilYHUkrX70SqwnYSTNEaN7A==" + }, + "node_modules/@segment/isodate-traverse": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@segment/isodate-traverse/-/isodate-traverse-1.1.1.tgz", + "integrity": "sha512-+G6e1SgAUkcq0EDMi+SRLfT48TNlLPF3QnSgFGVs0V9F3o3fq/woQ2rHFlW20W0yy5NnCUH0QGU3Am2rZy/E3w==", + "dependencies": { + "@segment/isodate": "^1.0.3" + } + }, + "node_modules/@selderee/plugin-htmlparser2": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", + "integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==", + "dependencies": { + "domhandler": "^5.0.3", + "selderee": "^0.11.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/@sendgrid/client": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-8.1.5.tgz", + "integrity": "sha512-Jqt8aAuGIpWGa15ZorTWI46q9gbaIdQFA21HIPQQl60rCjzAko75l3D1z7EyjFrNr4MfQ0StusivWh8Rjh10Cg==", + "dependencies": { + "@sendgrid/helpers": "^8.0.0", + "axios": "^1.8.2" + }, + "engines": { + "node": ">=12.*" + } + }, + "node_modules/@sendgrid/helpers": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-8.0.0.tgz", + "integrity": "sha512-Ze7WuW2Xzy5GT5WRx+yEv89fsg/pgy3T1E3FS0QEx0/VvRmigMZ5qyVGhJz4SxomegDkzXv/i0aFPpHKN8qdAA==", + "dependencies": { + "deepmerge": "^4.2.2" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/@sendgrid/mail": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-8.0.0.tgz", + "integrity": "sha512-ePir+LT6kzJ7yaWAFuo8R+N9Pjm3KQMV5NpJ9XuTaLwwuXrUBiPzFo953Qc7slsYf7AKhMurF4w+ta4v1nsJ7A==", + "dependencies": { + "@sendgrid/client": "^8.0.0", + "@sendgrid/helpers": "^8.0.0" + }, + "engines": { + "node": ">=12.*" + } + }, + "node_modules/@sentry-internal/tracing": { + "version": "7.120.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.120.0.tgz", + "integrity": "sha512-VymJoIGMV0PcTJyshka9uJ1sKpR7bHooqW5jTEr6g0dYAwB723fPXHjVW+7SETF7i5+yr2KMprYKreqRidKyKA==", + "dependencies": { + "@sentry/core": "7.120.0", + "@sentry/types": "7.120.0", + "@sentry/utils": "7.120.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/core": { + "version": "7.120.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.120.0.tgz", + "integrity": "sha512-uTc2sUQ0heZrMI31oFOHGxjKgw16MbV3C2mcT7qcrb6UmSGR9WqPOXZhnVVuzPWCnQ8B5IPPVdynK//J+9/m6g==", + "dependencies": { + "@sentry/types": "7.120.0", + "@sentry/utils": "7.120.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/integrations": { + "version": "7.120.0", + "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.120.0.tgz", + "integrity": "sha512-/Hs9MgSmG4JFNyeQkJ+MWh/fxO/U38Pz0VSH3hDrfyCjI8vH9Vz9inGEQXgB9Ke4eH8XnhsQ7xPnM27lWJts6g==", + "dependencies": { + "@sentry/core": "7.120.0", + "@sentry/types": "7.120.0", + "@sentry/utils": "7.120.0", + "localforage": "^1.8.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node": { + "version": "7.120.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.120.0.tgz", + "integrity": "sha512-GAyuNd8WUznsiOyDq2QUwR/aVnMmItUc4tgZQxhH1R+n4Adx3cAhnpq3zEuzsIAC5+/7ut+4Q4B3akh6SDZd4w==", + "dependencies": { + "@sentry-internal/tracing": "7.120.0", + "@sentry/core": "7.120.0", + "@sentry/integrations": "7.120.0", + "@sentry/types": "7.120.0", + "@sentry/utils": "7.120.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/types": { + "version": "7.120.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.120.0.tgz", + "integrity": "sha512-3mvELhBQBo6EljcRrJzfpGJYHKIZuBXmqh0y8prh03SWE62pwRL614GIYtd4YOC6OP1gfPn8S8h9w3dD5bF5HA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/utils": { + "version": "7.120.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.120.0.tgz", + "integrity": "sha512-XZsPcBHoYu4+HYn14IOnhabUZgCF99Xn4IdWn8Hjs/c+VPtuAVDhRTsfPyPrpY3OcN8DgO5fZX4qcv/6kNbX1A==", + "dependencies": { + "@sentry/types": "7.120.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sevinf/maybe": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@sevinf/maybe/-/maybe-0.5.0.tgz", + "integrity": "sha512-ARhyoYDnY1LES3vYI0fiG6e9esWfTNcXcO6+MPJJXcnyMV3bim4lnFt45VXouV7y82F4x3YH8nOQ6VztuvUiWg==" + }, + "node_modules/@shikijs/core": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.7.0.tgz", + "integrity": "sha512-yilc0S9HvTPyahHpcum8eonYrQtmGTU0lbtwxhA6jHv4Bm1cAdlPFRCJX4AHebkCm75aKTjjRAW+DezqD1b/cg==", + "dependencies": { + "@shikijs/types": "3.7.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.7.0.tgz", + "integrity": "sha512-0t17s03Cbv+ZcUvv+y33GtX75WBLQELgNdVghnsdhTgU3hVcWcMsoP6Lb0nDTl95ZJfbP1mVMO0p3byVh3uuzA==", + "dependencies": { + "@shikijs/types": "3.7.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.3" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.7.0.tgz", + "integrity": "sha512-5BxcD6LjVWsGu4xyaBC5bu8LdNgPCVBnAkWTtOCs/CZxcB22L8rcoWfv7Hh/3WooVjBZmFtyxhgvkQFedPGnFw==", + "dependencies": { + "@shikijs/types": "3.7.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.7.0.tgz", + "integrity": "sha512-1zYtdfXLr9xDKLTGy5kb7O0zDQsxXiIsw1iIBcNOO8Yi5/Y1qDbJ+0VsFoqTlzdmneO8Ij35g7QKF8kcLyznCQ==", + "dependencies": { + "@shikijs/types": "3.7.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.7.0.tgz", + "integrity": "sha512-VJx8497iZPy5zLiiCTSIaOChIcKQwR0FebwE9S3rcN0+J/GTWwQ1v/bqhTbpbY3zybPKeO8wdammqkpXc4NVjQ==", + "dependencies": { + "@shikijs/types": "3.7.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.7.0.tgz", + "integrity": "sha512-MGaLeaRlSWpnP0XSAum3kP3a8vtcTsITqoEPYdt3lQG3YCdQH4DnEhodkYcNMcU0uW0RffhoD1O3e0vG5eSBBg==", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==" + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.11", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.11.tgz", + "integrity": "sha512-zE9pWGVSG82z+sFO+oUmqmqRVm8Wg5sVhmljYi1fDhLOSphBBy939QmC/qXcKFWqTiRJ6keyG4y75bIoTPRBAw==" + }, + "node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/slugify": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz", + "integrity": "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==", + "dependencies": { + "@sindresorhus/transliterate": "^1.0.0", + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sindresorhus/slugify/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sindresorhus/transliterate": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-1.6.0.tgz", + "integrity": "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==", + "dependencies": { + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sindresorhus/transliterate/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@slack/logger": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.0.tgz", + "integrity": "sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==", + "dependencies": { + "@types/node": ">=18.0.0" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@slack/types": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.14.0.tgz", + "integrity": "sha512-n0EGm7ENQRxlXbgKSrQZL69grzg1gHLAVd+GlRVQJ1NSORo0FrApR7wql/gaKdu2n4TO83Sq/AmeUOqD60aXUA==", + "engines": { + "node": ">= 12.13.0", + "npm": ">= 6.12.0" + } + }, + "node_modules/@slack/web-api": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.9.0.tgz", + "integrity": "sha512-PEvscTsHj4pLQr6g/0OwPEFDN9ElJMdba9uYvhTPjC2yGQGzjB4YmqilXaDX0Lm3IBEcLtJNRAbsfQp+x3X3Qg==", + "dependencies": { + "@slack/logger": "^4.0.0", + "@slack/types": "^2.9.0", + "@types/node": ">=18.0.0", + "@types/retry": "0.12.0", + "axios": "^1.8.3", + "eventemitter3": "^5.0.1", + "form-data": "^4.0.0", + "is-electron": "2.2.2", + "is-stream": "^2", + "p-queue": "^6", + "p-retry": "^4", + "retry": "^0.13.1" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.9.tgz", + "integrity": "sha512-yiW0WI30zj8ZKoSYNx90no7ugVn3khlyH/z5W8qtKBtVE6awRALbhSG+2SAHA1r6bO/6M9utxYKVZ3PCJ1rWxw==", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-4.0.0.tgz", + "integrity": "sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.1.tgz", + "integrity": "sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ==", + "dependencies": { + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.13.tgz", + "integrity": "sha512-Gr/qwzyPaTL1tZcq8WQyHhTZREER5R1Wytmz4WnVGL4onA3dNk6Btll55c8Vr58pLdvWZmtG8oZxJTw3t3q7Jg==", + "dependencies": { + "@smithy/node-config-provider": "^3.1.12", + "@smithy/types": "^3.7.2", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.5.7.tgz", + "integrity": "sha512-8olpW6mKCa0v+ibCjoCzgZHQx1SQmZuW/WkrdZo73wiTprTH6qhmskT60QLFdT9DRa5mXxjz89kQPZ7ZSsoqqg==", + "dependencies": { + "@smithy/middleware-serde": "^3.0.11", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-stream": "^3.3.4", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.8.tgz", + "integrity": "sha512-ZCY2yD0BY+K9iMXkkbnjo+08T2h8/34oHd0Jmh6BZUSZwaaGlGCyBT/3wnS7u7Xl33/EEfN4B6nQr3Gx5bYxgw==", + "dependencies": { + "@smithy/node-config-provider": "^3.1.12", + "@smithy/property-provider": "^3.1.11", + "@smithy/types": "^3.7.2", + "@smithy/url-parser": "^3.0.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.10.tgz", + "integrity": "sha512-323B8YckSbUH0nMIpXn7HZsAVKHYHFUODa8gG9cHo0ySvA1fr5iWaNT+iIL0UCqUzG6QPHA3BSsBtRQou4mMqQ==", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^3.7.2", + "@smithy/util-hex-encoding": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "3.0.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.14.tgz", + "integrity": "sha512-kbrt0vjOIihW3V7Cqj1SXQvAI5BR8SnyQYsandva0AOR307cXAc+IhPngxIPslxTLfxwDpNu0HzCAq6g42kCPg==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^3.0.13", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.11.tgz", + "integrity": "sha512-P2pnEp4n75O+QHjyO7cbw/vsw5l93K/8EWyjNCAAybYwUmj3M+hjSQZ9P5TVdUgEG08ueMAP5R4FkuSkElZ5tQ==", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.13.tgz", + "integrity": "sha512-zqy/9iwbj8Wysmvi7Lq7XFLeDgjRpTbCfwBhJa8WbrylTAHiAu6oQTwdY7iu2lxigbc9YYr9vPv5SzYny5tCXQ==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^3.0.13", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.13.tgz", + "integrity": "sha512-L1Ib66+gg9uTnqp/18Gz4MDpJPKRE44geOjOQ2SVc0eiaO5l255ADziATZgjQjqumC7yPtp1XnjHlF1srcwjKw==", + "dependencies": { + "@smithy/eventstream-codec": "^3.1.10", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.9.tgz", + "integrity": "sha512-hYNVQOqhFQ6vOpenifFME546f0GfJn2OiQ3M0FDmuUu8V/Uiwy2wej7ZXxFBNqdx0R5DZAqWM1l6VRhGz8oE6A==", + "dependencies": { + "@smithy/protocol-http": "^4.1.4", + "@smithy/querystring-builder": "^3.0.7", + "@smithy/types": "^3.5.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.10.tgz", + "integrity": "sha512-elwslXOoNunmfS0fh55jHggyhccobFkexLYC1ZeZ1xP2BTSrcIBaHV2b4xUQOdctrSNOpMqOZH1r2XzWTEhyfA==", + "dependencies": { + "@smithy/chunked-blob-reader": "^4.0.0", + "@smithy/chunked-blob-reader-native": "^3.0.1", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/hash-node": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.11.tgz", + "integrity": "sha512-emP23rwYyZhQBvklqTtwetkQlqbNYirDiEEwXl2v0GYWMnCzxst7ZaRAnWuy28njp5kAH54lvkdG37MblZzaHA==", + "dependencies": { + "@smithy/types": "^3.7.2", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.10.tgz", + "integrity": "sha512-olomK/jZQ93OMayW1zfTHwcbwBdhcZOHsyWyiZ9h9IXvc1mCD/VuvzbLb3Gy/qNJwI4MANPLctTp2BucV2oU/Q==", + "dependencies": { + "@smithy/types": "^3.7.2", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.11.tgz", + "integrity": "sha512-NuQmVPEJjUX6c+UELyVz8kUx8Q539EDeNwbRyu4IIF8MeV7hUtq1FB3SHVyki2u++5XLMFqngeMKk7ccspnNyQ==", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.11.tgz", + "integrity": "sha512-3NM0L3i2Zm4bbgG6Ymi9NBcxXhryi3uE8fIfHJZIOfZVxOkGdjdgjR9A06SFIZCfnEIWKXZdm6Yq5/aPXFFhsQ==", + "dependencies": { + "@smithy/types": "^3.7.2", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.13.tgz", + "integrity": "sha512-zfMhzojhFpIX3P5ug7jxTjfUcIPcGjcQYzB9t+rv0g1TX7B0QdwONW+ATouaLoD7h7LOw/ZlXfkq4xJ/g2TrIw==", + "dependencies": { + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.8.tgz", + "integrity": "sha512-OEJZKVUEhMOqMs3ktrTWp7UvvluMJEvD5XgQwRePSbDg1VvBaL8pX8mwPltFn6wk1GySbcVwwyldL8S+iqnrEQ==", + "dependencies": { + "@smithy/core": "^2.5.7", + "@smithy/middleware-serde": "^3.0.11", + "@smithy/node-config-provider": "^3.1.12", + "@smithy/shared-ini-file-loader": "^3.1.12", + "@smithy/types": "^3.7.2", + "@smithy/url-parser": "^3.0.11", + "@smithy/util-middleware": "^3.0.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "3.0.34", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.34.tgz", + "integrity": "sha512-yVRr/AAtPZlUvwEkrq7S3x7Z8/xCd97m2hLDaqdz6ucP2RKHsBjEqaUA2ebNv2SsZoPEi+ZD0dZbOB1u37tGCA==", + "dependencies": { + "@smithy/node-config-provider": "^3.1.12", + "@smithy/protocol-http": "^4.1.8", + "@smithy/service-error-classification": "^3.0.11", + "@smithy/smithy-client": "^3.7.0", + "@smithy/types": "^3.7.2", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-retry": "^3.0.11", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-retry/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.11.tgz", + "integrity": "sha512-KzPAeySp/fOoQA82TpnwItvX8BBURecpx6ZMu75EZDkAcnPtO6vf7q4aH5QHs/F1s3/snQaSFbbUMcFFZ086Mw==", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.11.tgz", + "integrity": "sha512-1HGo9a6/ikgOMrTrWL/WiN9N8GSVYpuRQO5kjstAq4CvV59bjqnh7TbdXGQ4vxLD3xlSjfBjq5t1SOELePsLnA==", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.12.tgz", + "integrity": "sha512-O9LVEu5J/u/FuNlZs+L7Ikn3lz7VB9hb0GtPT9MQeiBmtK8RSY3ULmsZgXhe6VAlgTw0YO+paQx4p8xdbs43vQ==", + "dependencies": { + "@smithy/property-provider": "^3.1.11", + "@smithy/shared-ini-file-loader": "^3.1.12", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.3.3.tgz", + "integrity": "sha512-BrpZOaZ4RCbcJ2igiSNG16S+kgAc65l/2hmxWdmhyoGWHTLlzQzr06PXavJp9OBlPEG/sHlqdxjWmjzV66+BSQ==", + "dependencies": { + "@smithy/abort-controller": "^3.1.9", + "@smithy/protocol-http": "^4.1.8", + "@smithy/querystring-builder": "^3.0.11", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.11.tgz", + "integrity": "sha512-I/+TMc4XTQ3QAjXfOcUWbSS073oOEAxgx4aZy8jHaf8JQnRkq2SZWw8+PfDtBvLUjcGMdxl+YwtzWe6i5uhL/A==", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", + "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.11.tgz", + "integrity": "sha512-u+5HV/9uJaeLj5XTb6+IEF/dokWWkEqJ0XiaRRogyREmKGUgZnNecLucADLdauWFKUNbQfulHFEZEdjwEBjXRg==", + "dependencies": { + "@smithy/types": "^3.7.2", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.11.tgz", + "integrity": "sha512-Je3kFvCsFMnso1ilPwA7GtlbPaTixa3WwC+K21kmMZHsBEOZYQaqxcMqeFFoU7/slFjKDIpiiPydvdJm8Q/MCw==", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.11.tgz", + "integrity": "sha512-QnYDPkyewrJzCyaeI2Rmp7pDwbUETe+hU8ADkXmgNusO1bgHBH7ovXJiYmba8t0fNfJx75fE8dlM6SEmZxheog==", + "dependencies": { + "@smithy/types": "^3.7.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.12.tgz", + "integrity": "sha512-1xKSGI+U9KKdbG2qDvIR9dGrw3CNx+baqJfyr0igKEpjbHL5stsqAesYBzHChYHlelWtb87VnLWlhvfCz13H8Q==", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.4.tgz", + "integrity": "sha512-5JWeMQYg81TgU4cG+OexAWdvDTs5JDdbEZx+Qr1iPbvo91QFGzjy0IkXAKaXUHqmKUJgSHK0ZxnCkgZpzkeNTA==", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.7.0.tgz", + "integrity": "sha512-9wYrjAZFlqWhgVo3C4y/9kpc68jgiSsKUnsFPzr/MSiRL93+QRDafGTfhhKAb2wsr69Ru87WTiqSfQusSmWipA==", + "dependencies": { + "@smithy/core": "^2.5.7", + "@smithy/middleware-endpoint": "^3.2.8", + "@smithy/middleware-stack": "^3.0.11", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "@smithy/util-stream": "^3.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.11.tgz", + "integrity": "sha512-TmlqXkSk8ZPhfc+SQutjmFr5FjC0av3GZP4B/10caK1SbRwe/v+Wzu/R6xEKxoNqL+8nY18s1byiy6HqPG37Aw==", + "dependencies": { + "@smithy/querystring-parser": "^3.0.11", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", + "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", + "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", + "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "3.0.34", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.34.tgz", + "integrity": "sha512-FumjjF631lR521cX+svMLBj3SwSDh9VdtyynTYDAiBDEf8YPP5xORNXKQ9j0105o5+ARAGnOOP/RqSl40uXddA==", + "dependencies": { + "@smithy/property-provider": "^3.1.11", + "@smithy/smithy-client": "^3.7.0", + "@smithy/types": "^3.7.2", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "3.0.34", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.34.tgz", + "integrity": "sha512-vN6aHfzW9dVVzkI0wcZoUXvfjkl4CSbM9nE//08lmUMyf00S75uuCpTrqF9uD4bD9eldIXlt53colrlwKAT8Gw==", + "dependencies": { + "@smithy/config-resolver": "^3.0.13", + "@smithy/credential-provider-imds": "^3.2.8", + "@smithy/node-config-provider": "^3.1.12", + "@smithy/property-provider": "^3.1.11", + "@smithy/smithy-client": "^3.7.0", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.7.tgz", + "integrity": "sha512-tSfcqKcN/Oo2STEYCABVuKgJ76nyyr6skGl9t15hs+YaiU06sgMkN7QYjo0BbVw+KT26zok3IzbdSOksQ4YzVw==", + "dependencies": { + "@smithy/node-config-provider": "^3.1.12", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.11.tgz", + "integrity": "sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow==", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.11.tgz", + "integrity": "sha512-hJUC6W7A3DQgaee3Hp9ZFcOxVDZzmBIRBPlUAk8/fSOEl7pE/aX7Dci0JycNOnm9Mfr0KV2XjIlUOcGWXQUdVQ==", + "dependencies": { + "@smithy/service-error-classification": "^3.0.11", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.3.4.tgz", + "integrity": "sha512-SGhGBG/KupieJvJSZp/rfHHka8BFgj56eek9px4pp7lZbOF+fRiVr4U7A3y3zJD8uGhxq32C5D96HxsTC9BckQ==", + "dependencies": { + "@smithy/fetch-http-handler": "^4.1.3", + "@smithy/node-http-handler": "^3.3.3", + "@smithy/types": "^3.7.2", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-stream/node_modules/@smithy/fetch-http-handler": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.1.3.tgz", + "integrity": "sha512-6SxNltSncI8s689nvnzZQc/dPXcpHQ34KUj6gR/HBroytKOd/isMG3gJF/zBE1TBmTT18TXyzhg3O3SOOqGEhA==", + "dependencies": { + "@smithy/protocol-http": "^4.1.8", + "@smithy/querystring-builder": "^3.0.11", + "@smithy/types": "^3.7.2", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.2.0.tgz", + "integrity": "sha512-PpjSboaDUE6yl+1qlg3Si57++e84oXdWGbuFUSAciXsVfEZJJJupR2Nb0QuXHiunt2vGR+1PTizOMvnUPaG2Qg==", + "dependencies": { + "@smithy/abort-controller": "^3.1.9", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "node_modules/@socket.io/redis-adapter": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@socket.io/redis-adapter/-/redis-adapter-8.2.1.tgz", + "integrity": "sha512-6Dt7EZgGSBP0qvXeOKGx7NnSr2tPMbVDfDyL97zerZo+v69hMfL99skMCL3RKZlWVqLyRme2T0wcy3udHhtOsg==", + "dependencies": { + "debug": "~4.3.1", + "notepack.io": "~3.0.1", + "uid2": "1.0.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "socket.io-adapter": "^2.4.0" + } + }, + "node_modules/@socket.io/redis-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@sphinxxxx/color-conversion": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@sphinxxxx/color-conversion/-/color-conversion-2.2.2.tgz", + "integrity": "sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw==" + }, + "node_modules/@sqltools/formatter": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", + "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" + }, + "node_modules/@supabase/auth-js": { + "version": "2.69.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.69.1.tgz", + "integrity": "sha512-FILtt5WjCNzmReeRLq5wRs3iShwmnWgBvxHfqapC/VoljJl+W8hDAyFmf1NVw3zH+ZjZ05AKxiKxVeb0HNWRMQ==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.4.tgz", + "integrity": "sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.4.tgz", + "integrity": "sha512-O4soKqKtZIW3olqmbXXbKugUtByD2jPa8kL2m2c1oozAO11uCcGrRhkZL0kVxjBLrXHE0mdSkFsMj7jDSfyNpw==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.11.9", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.11.9.tgz", + "integrity": "sha512-fLseWq8tEPCO85x3TrV9Hqvk7H4SGOqnFQ223NPJSsxjSYn0EmzU1lvYO6wbA0fc8DE94beCAiiWvGvo4g33lQ==", + "dependencies": { + "@supabase/node-fetch": "^2.6.13", + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "ws": "^8.18.2" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz", + "integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.49.9", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.49.9.tgz", + "integrity": "sha512-lB2A2X8k1aWAqvlpO4uZOdfvSuZ2s0fCMwJ1Vq6tjWsi3F+au5lMbVVn92G0pG8gfmis33d64Plkm6eSDs6jRA==", + "dependencies": { + "@supabase/auth-js": "2.69.1", + "@supabase/functions-js": "2.4.4", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.19.4", + "@supabase/realtime-js": "2.11.9", + "@supabase/storage-js": "2.7.1" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "dev": true, + "dependencies": { + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/webpack": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", + "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@babel/plugin-transform-react-constant-elements": "^7.21.3", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@svgr/core": "8.1.0", + "@svgr/plugin-jsx": "8.1.0", + "@svgr/plugin-svgo": "8.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@swc-node/core": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@swc-node/core/-/core-1.13.3.tgz", + "integrity": "sha512-OGsvXIid2Go21kiNqeTIn79jcaX4l0G93X2rAnas4LFoDyA9wAwVK7xZdm+QsKoMn5Mus2yFLCc4OtX2dD/PWA==", + "dev": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@swc/core": ">= 1.4.13", + "@swc/types": ">= 0.1" + } + }, + "node_modules/@swc-node/register": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@swc-node/register/-/register-1.9.2.tgz", + "integrity": "sha512-BBjg0QNuEEmJSoU/++JOXhrjWdu3PTyYeJWsvchsI0Aqtj8ICkz/DqlwtXbmZVZ5vuDPpTfFlwDBZe81zgShMA==", + "dev": true, + "dependencies": { + "@swc-node/core": "^1.13.1", + "@swc-node/sourcemap-support": "^0.5.0", + "colorette": "^2.0.20", + "debug": "^4.3.4", + "pirates": "^4.0.6", + "tslib": "^2.6.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@swc/core": ">= 1.4.13", + "typescript": ">= 4.3" + } + }, + "node_modules/@swc-node/sourcemap-support": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@swc-node/sourcemap-support/-/sourcemap-support-0.5.1.tgz", + "integrity": "sha512-JxIvIo/Hrpv0JCHSyRpetAdQ6lB27oFYhv0PKCNf1g2gUXOjpeR1exrXccRxLMuAV5WAmGFBwRnNOJqN38+qtg==", + "dev": true, + "dependencies": { + "source-map-support": "^0.5.21", + "tslib": "^2.6.3" + } + }, + "node_modules/@swc-node/sourcemap-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@swc-node/sourcemap-support/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/@swc-node/sourcemap-support/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "node_modules/@swc/cli": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.6.0.tgz", + "integrity": "sha512-Q5FsI3Cw0fGMXhmsg7c08i4EmXCrcl+WnAxb6LYOLHw4JFFC3yzmx9LaXZ7QMbA+JZXbigU2TirI7RAfO0Qlnw==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3", + "@xhmikosr/bin-wrapper": "^13.0.5", + "commander": "^8.3.0", + "fast-glob": "^3.2.5", + "minimatch": "^9.0.3", + "piscina": "^4.3.1", + "semver": "^7.3.8", + "slash": "3.0.0", + "source-map": "^0.7.3" + }, + "bin": { + "spack": "bin/spack.js", + "swc": "bin/swc.js", + "swcx": "bin/swcx.js" + }, + "engines": { + "node": ">= 16.14.0" + }, + "peerDependencies": { + "@swc/core": "^1.2.66", + "chokidar": "^4.0.1" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@swc/cli/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@swc/core": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.5.7.tgz", + "integrity": "sha512-U4qJRBefIJNJDRCCiVtkfa/hpiZ7w0R6kASea+/KLp+vkus3zcLSB8Ub8SvKgTIxjWpwsKcZlPf5nrv4ls46SQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.2", + "@swc/types": "0.1.7" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.5.7", + "@swc/core-darwin-x64": "1.5.7", + "@swc/core-linux-arm-gnueabihf": "1.5.7", + "@swc/core-linux-arm64-gnu": "1.5.7", + "@swc/core-linux-arm64-musl": "1.5.7", + "@swc/core-linux-x64-gnu": "1.5.7", + "@swc/core-linux-x64-musl": "1.5.7", + "@swc/core-win32-arm64-msvc": "1.5.7", + "@swc/core-win32-ia32-msvc": "1.5.7", + "@swc/core-win32-x64-msvc": "1.5.7" + }, + "peerDependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.5.7.tgz", + "integrity": "sha512-bZLVHPTpH3h6yhwVl395k0Mtx8v6CGhq5r4KQdAoPbADU974Mauz1b6ViHAJ74O0IVE5vyy7tD3OpkQxL/vMDQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.5.7.tgz", + "integrity": "sha512-RpUyu2GsviwTc2qVajPL0l8nf2vKj5wzO3WkLSHAHEJbiUZk83NJrZd1RVbEknIMO7+Uyjh54hEh8R26jSByaw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.5.7.tgz", + "integrity": "sha512-cTZWTnCXLABOuvWiv6nQQM0hP6ZWEkzdgDvztgHI/+u/MvtzJBN5lBQ2lue/9sSFYLMqzqff5EHKlFtrJCA9dQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.5.7.tgz", + "integrity": "sha512-hoeTJFBiE/IJP30Be7djWF8Q5KVgkbDtjySmvYLg9P94bHg9TJPSQoC72tXx/oXOgXvElDe/GMybru0UxhKx4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.5.7.tgz", + "integrity": "sha512-+NDhK+IFTiVK1/o7EXdCeF2hEzCiaRSrb9zD7X2Z7inwWlxAntcSuzZW7Y6BRqGQH89KA91qYgwbnjgTQ22PiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.5.7.tgz", + "integrity": "sha512-25GXpJmeFxKB+7pbY7YQLhWWjkYlR+kHz5I3j9WRl3Lp4v4UD67OGXwPe+DIcHqcouA1fhLhsgHJWtsaNOMBNg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.5.7.tgz", + "integrity": "sha512-0VN9Y5EAPBESmSPPsCJzplZHV26akC0sIgd3Hc/7S/1GkSMoeuVL+V9vt+F/cCuzr4VidzSkqftdP3qEIsXSpg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.5.7.tgz", + "integrity": "sha512-RtoNnstBwy5VloNCvmvYNApkTmuCe4sNcoYWpmY7C1+bPR+6SOo8im1G6/FpNem8AR5fcZCmXHWQ+EUmRWJyuA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.5.7.tgz", + "integrity": "sha512-Xm0TfvcmmspvQg1s4+USL3x8D+YPAfX2JHygvxAnCJ0EHun8cm2zvfNBcsTlnwYb0ybFWXXY129aq1wgFC9TpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.5.7.tgz", + "integrity": "sha512-tp43WfJLCsKLQKBmjmY/0vv1slVywR5Q4qKjF5OIY8QijaEW7/8VwPyUyVoJZEnDgv9jKtUTG5PzqtIYPZGnyg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true + }, + "node_modules/@swc/helpers": { + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.11.tgz", + "integrity": "sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@swc/types": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.7.tgz", + "integrity": "sha512-scHWahbHF0eyj3JsxG9CFJgFdFNaVQCNAimBlT6PzS3n/HptxqREjsm4OH6AN3lYcffZYSPxXW8ua2BEHp0lJQ==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.51.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.51.1.tgz", + "integrity": "sha512-fJBMQMpo8/KSsWW5ratJR5+IFr7YNJ3K2kfP9l5XObYHsgfVy1w3FJUWU4FT2fj7+JMaEg33zOcNDBo0LMwHnw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.51.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.51.1.tgz", + "integrity": "sha512-s47HKFnQ4HOJAHoIiXcpna/roMMPZJPy6fJ6p4ZNVn8+/onlLBEDd1+xc8OnDuwgvecqkZD7Z2mnSRbcWefrKw==", + "dependencies": { + "@tanstack/query-core": "5.51.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@tanstack/react-table": { + "version": "8.19.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.19.2.tgz", + "integrity": "sha512-itoSIAkA/Vsg+bjY23FSemcTyPhc5/1YjYyaMsr9QSH/cdbZnQxHVWrpWn0Sp2BWN71qkzR7e5ye8WuMmwyOjg==", + "dependencies": { + "@tanstack/table-core": "8.19.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.13.11", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.11.tgz", + "integrity": "sha512-u5EaOSJOq08T9NXFuDopMdxZBNDFuEMohIFFU45fBYDXXh9SjYdbpNq1OLFSOpQnDRPjqgmY96ipZTkzom9t9Q==", + "dependencies": { + "@tanstack/virtual-core": "3.13.11" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.19.2", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.19.2.tgz", + "integrity": "sha512-KpRjhgehIhbfH78ARm/GJDXGnpdw4bCg3qas6yjWSi7czJhI/J6pWln7NHtmBkGE9ZbohiiNtLqwGzKmBfixig==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.13.11", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.11.tgz", + "integrity": "sha512-ORL6UyuZJ0D9X33LDR4TcgcM+K2YiS2j4xbvH1vnhhObwR1Z4dKwPTL/c0kj2Yeb4Yp2lBv1wpyVaqlohk8zpg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@techteamer/ocsp": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@techteamer/ocsp/-/ocsp-1.0.1.tgz", + "integrity": "sha512-q4pW5wAC6Pc3JI8UePwE37CkLQ5gDGZMgjSX4MEEm4D4Di59auDQ8UNIDzC4gRnPNmmcwjpPxozq8p5pjiOmOw==", + "dependencies": { + "asn1.js": "^5.4.1", + "asn1.js-rfc2560": "^5.0.1", + "asn1.js-rfc5280": "^3.0.0", + "async": "^3.2.4", + "simple-lru-cache": "^0.0.2" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/react": { + "version": "15.0.6", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-15.0.6.tgz", + "integrity": "sha512-UlbazRtEpQClFOiYp+1BapMT+xyqWMnE+hh9tn5DQ6gmlE7AIZWcGpzZukmDZuFk3By01oiqOf8lRedLS4k6xQ==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^10.0.0", + "@types/react-dom": "^18.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/react": "^18.0.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@tinyhttp/accepts": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@tinyhttp/accepts/-/accepts-2.2.3.tgz", + "integrity": "sha512-9pQN6pJAJOU3McmdJWTcyq7LLFW8Lj5q+DadyKcvp+sxMkEpktKX5sbfJgJuOvjk6+1xWl7pe0YL1US1vaO/1w==", + "dependencies": { + "mime": "4.0.4", + "negotiator": "^0.6.3" + }, + "engines": { + "node": ">=12.20.0" + }, + "funding": { + "type": "individual", + "url": "https://github.com/tinyhttp/tinyhttp?sponsor=1" + } + }, + "node_modules/@tinyhttp/accepts/node_modules/mime": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz", + "integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@tinyhttp/accepts/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tinyhttp/app": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@tinyhttp/app/-/app-2.5.2.tgz", + "integrity": "sha512-DcB3Y8GQppLQlO2VxRYF7LzTEAoZb+VRQXuIsErcu2fNaM1xdx6NQZDso5rlZUiaeg6KYYRfU34N4XkZbv6jSA==", + "dependencies": { + "@tinyhttp/cookie": "2.1.1", + "@tinyhttp/proxy-addr": "2.2.1", + "@tinyhttp/req": "2.2.5", + "@tinyhttp/res": "2.2.5", + "@tinyhttp/router": "2.2.3", + "header-range-parser": "1.1.3", + "regexparam": "^2.0.2" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "individual", + "url": "https://github.com/tinyhttp/tinyhttp?sponsor=1" + } + }, + "node_modules/@tinyhttp/content-disposition": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@tinyhttp/content-disposition/-/content-disposition-2.2.2.tgz", + "integrity": "sha512-crXw1txzrS36huQOyQGYFvhTeLeG0Si1xu+/l6kXUVYpE0TjFjEZRqTbuadQLfKGZ0jaI+jJoRyqaWwxOSHW2g==", + "engines": { + "node": ">=12.20.0" + }, + "funding": { + "type": "individual", + "url": "https://github.com/tinyhttp/tinyhttp?sponsor=1" + } + }, + "node_modules/@tinyhttp/content-type": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@tinyhttp/content-type/-/content-type-0.1.4.tgz", + "integrity": "sha512-dl6f3SHIJPYbhsW1oXdrqOmLSQF/Ctlv3JnNfXAE22kIP7FosqJHxkz/qj2gv465prG8ODKH5KEyhBkvwrueKQ==", + "engines": { + "node": ">=12.4" + } + }, + "node_modules/@tinyhttp/cookie": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@tinyhttp/cookie/-/cookie-2.1.1.tgz", + "integrity": "sha512-h/kL9jY0e0Dvad+/QU3efKZww0aTvZJslaHj3JTPmIPC9Oan9+kYqmh3M6L5JUQRuTJYFK2nzgL2iJtH2S+6dA==", + "engines": { + "node": ">=12.20.0" + }, + "funding": { + "type": "individual", + "url": "https://github.com/tinyhttp/tinyhttp?sponsor=1" + } + }, + "node_modules/@tinyhttp/cookie-signature": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@tinyhttp/cookie-signature/-/cookie-signature-2.1.1.tgz", + "integrity": "sha512-VDsSMY5OJfQJIAtUgeQYhqMPSZptehFSfvEEtxr+4nldPA8IImlp3QVcOVuK985g4AFR4Hl1sCbWCXoqBnVWnw==", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/cors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tinyhttp/cors/-/cors-2.0.1.tgz", + "integrity": "sha512-qrmo6WJuaiCzKWagv2yA/kw6hIISfF/hOqPWwmI6w0o8apeTMmRN3DoCFvQ/wNVuWVdU5J4KU7OX8aaSOEq51A==", + "dependencies": { + "@tinyhttp/vary": "^0.1.3" + }, + "engines": { + "node": ">=12.20 || 14.x || >=16" + } + }, + "node_modules/@tinyhttp/encode-url": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@tinyhttp/encode-url/-/encode-url-2.1.1.tgz", + "integrity": "sha512-AhY+JqdZ56qV77tzrBm0qThXORbsVjs/IOPgGCS7x/wWnsa/Bx30zDUU/jPAUcSzNOzt860x9fhdGpzdqbUeUw==", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/etag": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@tinyhttp/etag/-/etag-2.1.2.tgz", + "integrity": "sha512-j80fPKimGqdmMh6962y+BtQsnYPVCzZfJw0HXjyH70VaJBHLKGF+iYhcKqzI3yef6QBNa8DKIPsbEYpuwApXTw==", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/forwarded": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@tinyhttp/forwarded/-/forwarded-2.1.2.tgz", + "integrity": "sha512-9H/eulJ68ElY/+zYpTpNhZ7vxGV+cnwaR6+oQSm7bVgZMyuQfgROW/qvZuhmgDTIxnGMXst+Ba4ij6w6Krcs3w==", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/proxy-addr": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@tinyhttp/proxy-addr/-/proxy-addr-2.2.1.tgz", + "integrity": "sha512-BicqMqVI91hHq2BQmnqJUh0FQUnx7DncwSGgu2ghlh+JZG2rHK2ZN/rXkfhrx1rrUw6hnd0L36O8GPMh01+dDQ==", + "dependencies": { + "@tinyhttp/forwarded": "2.1.2", + "ipaddr.js": "^2.2.0" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/req": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@tinyhttp/req/-/req-2.2.5.tgz", + "integrity": "sha512-trfsXwtmsNjMcGKcLJ+45h912kLRqBQCQD06ams3Tq0kf4gHLxjHjoYOC1Z9yGjOn81XllRx8wqvnvr+Kbe3gw==", + "dependencies": { + "@tinyhttp/accepts": "2.2.3", + "@tinyhttp/type-is": "2.2.4", + "@tinyhttp/url": "2.1.1", + "header-range-parser": "^1.1.3" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/res": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@tinyhttp/res/-/res-2.2.5.tgz", + "integrity": "sha512-yBsqjWygpuKAVz4moWlP4hqzwiDDqfrn2mA0wviJAcgvGiyOErtlQwXY7aj3aPiCpURvxvEFO//Gdy6yV+xEpA==", + "dependencies": { + "@tinyhttp/content-disposition": "2.2.2", + "@tinyhttp/cookie": "2.1.1", + "@tinyhttp/cookie-signature": "2.1.1", + "@tinyhttp/encode-url": "2.1.1", + "@tinyhttp/req": "2.2.5", + "@tinyhttp/send": "2.2.3", + "@tinyhttp/vary": "^0.1.3", + "es-escape-html": "^0.1.1", + "mime": "4.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/res/node_modules/mime": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz", + "integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@tinyhttp/router": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@tinyhttp/router/-/router-2.2.3.tgz", + "integrity": "sha512-O0MQqWV3Vpg/uXsMYg19XsIgOhwjyhTYWh51Qng7bxqXixxx2PEvZWnFjP7c84K7kU/nUX41KpkEBTLnznk9/Q==", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/send": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@tinyhttp/send/-/send-2.2.3.tgz", + "integrity": "sha512-o4cVHHGQ8WjVBS8UT0EE/2WnjoybrfXikHwsRoNlG1pfrC/Sd01u1N4Te8cOd/9aNGLr4mGxWb5qTm2RRtEi7g==", + "dependencies": { + "@tinyhttp/content-type": "^0.1.4", + "@tinyhttp/etag": "2.1.2", + "mime": "4.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/send/node_modules/mime": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz", + "integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@tinyhttp/type-is": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@tinyhttp/type-is/-/type-is-2.2.4.tgz", + "integrity": "sha512-7F328NheridwjIfefBB2j1PEcKKABpADgv7aCJaE8x8EON77ZFrAkI3Rir7pGjopV7V9MBmW88xUQigBEX2rmQ==", + "dependencies": { + "@tinyhttp/content-type": "^0.1.4", + "mime": "4.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/type-is/node_modules/mime": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz", + "integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@tinyhttp/url": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@tinyhttp/url/-/url-2.1.1.tgz", + "integrity": "sha512-POJeq2GQ5jI7Zrdmj22JqOijB5/GeX+LEX7DUdml1hUnGbJOTWDx7zf2b5cCERj7RoXL67zTgyzVblBJC+NJWg==", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/vary": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@tinyhttp/vary/-/vary-0.1.3.tgz", + "integrity": "sha512-SoL83sQXAGiHN1jm2VwLUWQSQeDAAl1ywOm6T0b0Cg1CZhVsjoiZadmjhxF6FHCCY7OHHVaLnTgSMxTPIDLxMg==", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@tiptap/core": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.23.0.tgz", + "integrity": "sha512-Cdfhd0Po1cKMYqHtyv/3XATXpf2Kjo8fuau/QJwrml0NpM18/XX9mAgp2NJ/QaiQ3vi8vDandg7RmZ5OrApglQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-blockquote": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.23.0.tgz", + "integrity": "sha512-EBWzvqM39K07oAtxD/bZr/TKqTvMZ9pQtBkimyFgHBhkd/PsQvu0r0k1wmheSsizlwDVkO4O8St0zkUimqyevQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-bold": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.23.0.tgz", + "integrity": "sha512-OY1xlt1yXpj9+Mzdv+YC6a3okr39xDkeCJyvmLG5OShYUx6fxTT19uXbKF7Y3aAS0BHet5rcrTXEMRlp7N3Qaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.23.0.tgz", + "integrity": "sha512-4CZxcVj/0ZetEiWgiP31xTHgaQ7Hr3Ad36cAEza/nGYifaztuPjLO2Y9qdnC1iJHIxttnX6GVRnCMRmZMfhgHg==", + "dependencies": { + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-bullet-list": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.23.0.tgz", + "integrity": "sha512-YrmH5AVSkpCQ7k1Orm8hlzDeUO7rxpQkS51sr2w+ruruKIun/X6V0phuLee+f7DBrzHenBcuU6gBtU6vgGPYFw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-code": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.23.0.tgz", + "integrity": "sha512-Ip/5+kNoqrxYPHLnZMf7i6wfjjRuR5QgfC3IR3Mk1WQM1JGXCLL+uUjTUxKXFUj28hjSJfsmVbTUhoVvgZEWfw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-code-block": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.23.0.tgz", + "integrity": "sha512-p8iizp5nQBBhYPrIgBVwEqcDnc2fFRAZCXy/xjmAk2kKOhB7NYe3+1yrbFcQKVAdaUFxG+BRj2WxNDeeRt5tJA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-document": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.23.0.tgz", + "integrity": "sha512-kuRPqH0UdjZ4RcnpPELsu1N8LqeixEin+mv5eaQJI/aZ6rFq+kcY4UZF3C7q56Rat5r9CgHBiZbD0t5l6E3gdA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-dropcursor": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.23.0.tgz", + "integrity": "sha512-m2LzkJpipHLPEllD3MXZQMssu7Xng7YJOJ8ZNDkF0uUkXljwh7G0ROjGNKUlV8/dqoCVmJIZIyF6t9saQwTTbA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-floating-menu": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.23.0.tgz", + "integrity": "sha512-MvwDMhO5o5NciE+wc6B9dQgTFzmPjtB1o3S+HTdlGzGFGgx9PsNikK5BkqMit9j2NnrqyHnOf88QK/wZR5fqGA==", + "dependencies": { + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-gapcursor": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.23.0.tgz", + "integrity": "sha512-SpYsDtMiVwqcSB84g714PrnHo985R5UiIaGngef6iMNy/0xjKcO0tj/feu0WwJDuSj22Opzlnb/Ld/D4Va27Ng==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-hard-break": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.23.0.tgz", + "integrity": "sha512-OpNBEYv9HDUPo8SgvmI5oPd0b+xmdadtFyL7t4lxhYar8n5NDYubaXYgbKcdJfXvUxEeGwdc3ePnTFpsF0mrYw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-heading": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.23.0.tgz", + "integrity": "sha512-ZbombU/zc42QiqIBVq5bn/I0Y+eiie/0Nax/bdFCDPIKLp8GCp2BDRg46e3kcCanTyZMXw2HmkWrkG3sQNHLWw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-history": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.23.0.tgz", + "integrity": "sha512-W+2bZ/02nm56g/wmEaSx9QcdZ8mHjoFyc8MKf54Mrzi+nIdNjsNreKrn1yCp683CGEPd8DLadDFkz0o13N+rxA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.23.0.tgz", + "integrity": "sha512-i/gml9PMQ6uNeq2CCNIWkkYDbafx6XMH4xPSHW4SAG02Exa64iIZLWy57Vb4MR5INSZ6lM/OzU7sdfzHSOb44g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-italic": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.23.0.tgz", + "integrity": "sha512-hX3oacTUloWM8Xu8IapcU2onMWmSkJi8mNAJiIFMiAYcERfTfxPsT3u2yO2gvpoh1iqtZWFM2gc+3x6BnXek8g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-list-item": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.23.0.tgz", + "integrity": "sha512-tYhLqCaQRjX2S6ICt8FJ+eCAxBMVtXWth6dWt3w7wpkoCVU6n0Dva/2Z3x5lNJPZxUKrsqXc1oYOgvY1pUYyAA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-mention": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-mention/-/extension-mention-2.5.4.tgz", + "integrity": "sha512-U5Kqjhs7FraJzopZydy14/v0+X6unmfYYt42QHhVeSEdZ8y7QtyFigJktJUBzE12CpwGkyh8e3xI9Ozi7lFb0w==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.5.4", + "@tiptap/pm": "^2.5.4", + "@tiptap/suggestion": "^2.5.4" + } + }, + "node_modules/@tiptap/extension-ordered-list": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.23.0.tgz", + "integrity": "sha512-IMlPpAPuiFl5L5QwP0aFb8jmJtOceNy4E4tUZulvqARnrzFv//wSuHBZKJziygvm/XK7VcV/clk4fCk/ca5r4g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-paragraph": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.23.0.tgz", + "integrity": "sha512-MXhRkb741UOcJp2evG/H0MY3WJQnX7z8PsejmJbJXOHBrS/Esxq0AlrDAjuFhbfAnJwYiWQ1lk6ucvKV6DhFuQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-placeholder": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.5.5.tgz", + "integrity": "sha512-SwWLYdyrMeoVUQdivkIJ4kkAcb38pykxSetlrXitfUmnkwv0/fi+p76Rickf+roudWPsfzqvgvJ4gT6OAOJrGA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.5.5", + "@tiptap/pm": "^2.5.5" + } + }, + "node_modules/@tiptap/extension-strike": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.23.0.tgz", + "integrity": "sha512-zdYO4xdg15BE8gmPYFgA5Xn5+hPA6NAiDBWxv5KNWD9cJ5OhsJx2OsfSCWc0CxYQaIIbHhGM9EGzqH5lF+UnwQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-text": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.23.0.tgz", + "integrity": "sha512-hF+CU1H4B4UgqjBXXPPaACVZdSGuMH0TDYTd7h403qUAIBKkYbjuan7laQpiT0qnF0Dg+sGgvmGcd4H1tTBM8g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/pm": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.5.4.tgz", + "integrity": "sha512-oFIsuniptdUXn93x4aM2sVN3hYKo9Fj55zAkYrWhwxFYUYcPxd5ibra2we+wRK5TaiPu098wpC+yMSTZ/KKMpA==", + "dependencies": { + "prosemirror-changeset": "^2.2.1", + "prosemirror-collab": "^1.3.1", + "prosemirror-commands": "^1.5.2", + "prosemirror-dropcursor": "^1.8.1", + "prosemirror-gapcursor": "^1.3.2", + "prosemirror-history": "^1.4.1", + "prosemirror-inputrules": "^1.4.0", + "prosemirror-keymap": "^1.2.2", + "prosemirror-markdown": "^1.13.0", + "prosemirror-menu": "^1.2.4", + "prosemirror-model": "^1.22.1", + "prosemirror-schema-basic": "^1.2.3", + "prosemirror-schema-list": "^1.4.1", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.3.7", + "prosemirror-trailing-node": "^2.0.8", + "prosemirror-transform": "^1.9.0", + "prosemirror-view": "^1.33.8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/react": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.5.4.tgz", + "integrity": "sha512-2HPHt2lEK6Z4jOV3HHVTee8hD4NS6eEj0zRZWSFjt1zDzXtFqX8VIv7qC1iDYsQgyiFnFnOucOQtAlDewBb23A==", + "dependencies": { + "@tiptap/extension-bubble-menu": "^2.5.4", + "@tiptap/extension-floating-menu": "^2.5.4", + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.5.4", + "@tiptap/pm": "^2.5.4", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@tiptap/starter-kit": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.5.4.tgz", + "integrity": "sha512-IYnSETtBUSsy+Ece4kfVyzew+zyj7W9rP2Ronx0CbjeWQarfCAGxjuZ6uGLPB+tC5ZuMVt68Gyqb2y8GFes2Yw==", + "dependencies": { + "@tiptap/core": "^2.5.4", + "@tiptap/extension-blockquote": "^2.5.4", + "@tiptap/extension-bold": "^2.5.4", + "@tiptap/extension-bullet-list": "^2.5.4", + "@tiptap/extension-code": "^2.5.4", + "@tiptap/extension-code-block": "^2.5.4", + "@tiptap/extension-document": "^2.5.4", + "@tiptap/extension-dropcursor": "^2.5.4", + "@tiptap/extension-gapcursor": "^2.5.4", + "@tiptap/extension-hard-break": "^2.5.4", + "@tiptap/extension-heading": "^2.5.4", + "@tiptap/extension-history": "^2.5.4", + "@tiptap/extension-horizontal-rule": "^2.5.4", + "@tiptap/extension-italic": "^2.5.4", + "@tiptap/extension-list-item": "^2.5.4", + "@tiptap/extension-ordered-list": "^2.5.4", + "@tiptap/extension-paragraph": "^2.5.4", + "@tiptap/extension-strike": "^2.5.4", + "@tiptap/extension-text": "^2.5.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/suggestion": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-2.5.4.tgz", + "integrity": "sha512-mf0gC237PFz5l/hFRIetZoXemLMUXtmTPRbHTgBzqkTfaiJhfWsZZ3VeQNh4hoQ5AGYxRHWb9+zgRNGsH4jAEw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.5.4", + "@tiptap/pm": "^2.5.4" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" + }, + "node_modules/@tryfabric/martian": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@tryfabric/martian/-/martian-1.2.0.tgz", + "integrity": "sha512-q3grnGgwfujNZelpK6uMswObYKSy1dY+yKypgjl7EPxpSvSHlJb1f0gIfghGUWf1gVxZfHfSZDe+k9KCzaLbwQ==", + "dependencies": { + "@notionhq/client": "^1.0.4", + "remark-gfm": "^1.0.0", + "remark-parse": "^9.0.0", + "unified": "^9.2.1" + }, + "engines": { + "node": ">=15" + } + }, + "node_modules/@tryfabric/martian/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/@tryfabric/martian/node_modules/ccount": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", + "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@tryfabric/martian/node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@tryfabric/martian/node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@tryfabric/martian/node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@tryfabric/martian/node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@tryfabric/martian/node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@tryfabric/martian/node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@tryfabric/martian/node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@tryfabric/martian/node_modules/longest-streak": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", + "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@tryfabric/martian/node_modules/mdast-util-find-and-replace": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-1.1.1.tgz", + "integrity": "sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==", + "dependencies": { + "escape-string-regexp": "^4.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@tryfabric/martian/node_modules/mdast-util-gfm": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-0.1.2.tgz", + "integrity": "sha512-NNkhDx/qYcuOWB7xHUGWZYVXvjPFFd6afg6/e2g+SV4r9q5XUcCbV4Wfa3DLYIiD+xAEZc6K4MGaE/m0KDcPwQ==", + "dependencies": { + "mdast-util-gfm-autolink-literal": "^0.1.0", + "mdast-util-gfm-strikethrough": "^0.2.0", + "mdast-util-gfm-table": "^0.1.0", + "mdast-util-gfm-task-list-item": "^0.1.0", + "mdast-util-to-markdown": "^0.6.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@tryfabric/martian/node_modules/mdast-util-gfm-autolink-literal": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-0.1.3.tgz", + "integrity": "sha512-GjmLjWrXg1wqMIO9+ZsRik/s7PLwTaeCHVB7vRxUwLntZc8mzmTsLVr6HW1yLokcnhfURsn5zmSVdi3/xWWu1A==", + "dependencies": { + "ccount": "^1.0.0", + "mdast-util-find-and-replace": "^1.1.0", + "micromark": "^2.11.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@tryfabric/martian/node_modules/mdast-util-gfm-strikethrough": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-0.2.3.tgz", + "integrity": "sha512-5OQLXpt6qdbttcDG/UxYY7Yjj3e8P7X16LzvpX8pIQPYJ/C2Z1qFGMmcw+1PZMUM3Z8wt8NRfYTvCni93mgsgA==", + "dependencies": { + "mdast-util-to-markdown": "^0.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@tryfabric/martian/node_modules/mdast-util-gfm-table": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-0.1.6.tgz", + "integrity": "sha512-j4yDxQ66AJSBwGkbpFEp9uG/LS1tZV3P33fN1gkyRB2LoRL+RR3f76m0HPHaby6F4Z5xr9Fv1URmATlRRUIpRQ==", + "dependencies": { + "markdown-table": "^2.0.0", + "mdast-util-to-markdown": "~0.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@tryfabric/martian/node_modules/mdast-util-gfm-task-list-item": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-0.1.6.tgz", + "integrity": "sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==", + "dependencies": { + "mdast-util-to-markdown": "~0.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@tryfabric/martian/node_modules/mdast-util-to-markdown": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz", + "integrity": "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==", + "dependencies": { + "@types/unist": "^2.0.0", + "longest-streak": "^2.0.0", + "mdast-util-to-string": "^2.0.0", + "parse-entities": "^2.0.0", + "repeat-string": "^1.0.0", + "zwitch": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@tryfabric/martian/node_modules/mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@tryfabric/martian/node_modules/micromark": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", + "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "debug": "^4.0.0", + "parse-entities": "^2.0.0" + } + }, + "node_modules/@tryfabric/martian/node_modules/micromark-extension-gfm": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-0.3.3.tgz", + "integrity": "sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==", + "dependencies": { + "micromark": "~2.11.0", + "micromark-extension-gfm-autolink-literal": "~0.5.0", + "micromark-extension-gfm-strikethrough": "~0.6.5", + "micromark-extension-gfm-table": "~0.4.0", + "micromark-extension-gfm-tagfilter": "~0.3.0", + "micromark-extension-gfm-task-list-item": "~0.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@tryfabric/martian/node_modules/micromark-extension-gfm-autolink-literal": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-0.5.7.tgz", + "integrity": "sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==", + "dependencies": { + "micromark": "~2.11.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@tryfabric/martian/node_modules/micromark-extension-gfm-strikethrough": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-0.6.5.tgz", + "integrity": "sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==", + "dependencies": { + "micromark": "~2.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@tryfabric/martian/node_modules/micromark-extension-gfm-table": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-0.4.3.tgz", + "integrity": "sha512-hVGvESPq0fk6ALWtomcwmgLvH8ZSVpcPjzi0AjPclB9FsVRgMtGZkUcpE0zgjOCFAznKepF4z3hX8z6e3HODdA==", + "dependencies": { + "micromark": "~2.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@tryfabric/martian/node_modules/micromark-extension-gfm-tagfilter": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-0.3.0.tgz", + "integrity": "sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@tryfabric/martian/node_modules/micromark-extension-gfm-task-list-item": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-0.3.3.tgz", + "integrity": "sha512-0zvM5iSLKrc/NQl84pZSjGo66aTGd57C1idmlWmE87lkMcXrTxg1uXa/nXomxJytoje9trP0NDLvw4bZ/Z/XCQ==", + "dependencies": { + "micromark": "~2.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@tryfabric/martian/node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@tryfabric/martian/node_modules/remark-gfm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-1.0.0.tgz", + "integrity": "sha512-KfexHJCiqvrdBZVbQ6RopMZGwaXz6wFJEfByIuEwGf0arvITHjiKKZ1dpXujjH9KZdm1//XJQwgfnJ3lmXaDPA==", + "dependencies": { + "mdast-util-gfm": "^0.1.0", + "micromark-extension-gfm": "^0.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@tryfabric/martian/node_modules/unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@tryfabric/martian/node_modules/unist-util-visit-parents": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@tryfabric/martian/node_modules/zwitch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", + "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/amqplib": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@types/amqplib/-/amqplib-0.10.5.tgz", + "integrity": "sha512-/cSykxROY7BWwDoi4Y4/jLAuZTshZxd8Ey1QYa/VaXriMotBDoou7V/twJiOSHzU6t1Kp1AHAUXGCgqq+6DNeg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/bcrypt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz", + "integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/bluebird": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.42.tgz", + "integrity": "sha512-Jhy+MWRlro6UjVi578V/4ZGNfeCOcNCp0YaFNIUGFKlImowqwb1O/22wDVk3FDGMLqxdpOV3qQHD5fPEH4hK6A==" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/color": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.6.tgz", + "integrity": "sha512-NMiNcZFRUAiUUCCf7zkAelY8eV3aKqfbzyFQlXpPIEeoNDbsEHGpb854V3gzTsGKYj830I5zPuOwU/TP5/cW6A==", + "dev": true, + "dependencies": { + "@types/color-convert": "*" + } + }, + "node_modules/@types/color-convert": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.4.tgz", + "integrity": "sha512-Ub1MmDdyZ7mX//g25uBAoH/mWGd9swVbt8BseymnaE18SU4po/PjmCrHxqIIRjBo3hV/vh1KGr0eMxUhp+t+dQ==", + "dev": true, + "dependencies": { + "@types/color-name": "^1.1.0" + } + }, + "node_modules/@types/color-name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.5.tgz", + "integrity": "sha512-j2K5UJqGTxeesj6oQuGpMgifpT5k9HprgQd8D1Y0lOFqKHl3PJu5GMeS4Y5EgjS55AE6OQxf8mPED9uaGbf4Cg==", + "dev": true + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/contrast-color": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/contrast-color/-/contrast-color-1.0.3.tgz", + "integrity": "sha512-D4xFbzgt7p6o2I6e6vxxbSuDw8VbTDb6XGurz2MFi39WmmlDRmPvEcATqsfLShLM8GxrhZ0gWT+9VdGnC1DCTA==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==", + "dev": true + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.11.tgz", + "integrity": "sha512-4pQMp8ldf7UaB/gR8Fvvy69psNHkTpD/pVw3vmEi8iZAB9EPMBruB1JvHO4BIq9QkUUd2lV1F5YXpMNj7JPBpw==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==" + }, + "node_modules/@types/d3-shape": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.12.tgz", + "integrity": "sha512-8oMzcd4+poSLGgV0R1Q1rOlx/xdmozS4Xab7np0eamFFUYq71AU9pOCJEFnkXW2aI/oXdVYJzw6pssbSut7Z9Q==", + "dev": true, + "dependencies": { + "@types/d3-path": "^1" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/decompress": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@types/decompress/-/decompress-4.2.4.tgz", + "integrity": "sha512-/C8kTMRTNiNuWGl5nEyKbPiMv6HA+0RbEXzFhFBEzASM6+oa4tJro9b8nj7eRlOFfuLdzUU+DS/GPDlvvzMOhA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-mMUu4nWHLBlHtxXY17Fg6+ucS/MnndyOWyOe7MmwkoMYxvfQU2ajtRaEvqSUv+aVkMqH/C0NCI8UoVfRNQ10yg==", + "dev": true + }, + "node_modules/@types/diff-match-patch": { + "version": "1.0.36", + "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", + "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==" + }, + "node_modules/@types/docusign-esign": { + "version": "5.19.9", + "resolved": "https://registry.npmjs.org/@types/docusign-esign/-/docusign-esign-5.19.9.tgz", + "integrity": "sha512-AZNfmxucaY5IGU0j3LH/3A9iFbdY39+wlUnmGfeN0AihCspNC1H08xlQ8j8th5Rl2y9TsgDXcrmKF1ncYVGYWw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/feedparser": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@types/feedparser/-/feedparser-2.2.5.tgz", + "integrity": "sha512-+IILu+/iCBJxMt3s8xcXR8KH8g7Y3q1UbnxijMFcJp9bVuIWmH3Z6/4WkFvUyIfVZvYWI/Gz78QGHQMLHBUQgA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/sax": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.16", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", + "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/imap": { + "version": "0.8.37", + "resolved": "https://registry.npmjs.org/@types/imap/-/imap-0.8.37.tgz", + "integrity": "sha512-cEjr3G2TmnOeYGG47BEshEmXsZpOaK33TlbxrtB2fzFXvaHvrKfK0kqhMxteKWWtbeGPPxKwyvT+ePz4Pv/45A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/imapflow": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/@types/imapflow/-/imapflow-1.0.18.tgz", + "integrity": "sha512-BoWZUoMktji2YJmkRY8z0KsjvyDNpBzeC/rLVMFKcHkPxaKp+SHBFfx/kj7ltKh3l010Lc9RZqnJs8KUMNhf6Q==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/is-base64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/is-base64/-/is-base64-1.1.1.tgz", + "integrity": "sha512-JgnGhP+MeSHEQmvxcobcwPEP4Ew56voiq9/0hmP/41lyQ/3gBw/ZCIRy2v+QkEOdeCl58lRcrf6+Y6WMlJGETA==", + "dev": true + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.13", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.13.tgz", + "integrity": "sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/jest/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@types/jest/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@types/jest/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/@types/js-cookie": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.7.tgz", + "integrity": "sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==" + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==" + }, + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/jsdom/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@types/jsdom/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/json2xml": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@types/json2xml/-/json2xml-0.1.1.tgz", + "integrity": "sha512-9t//j15vXZnE6gSSMAlSA/uhqXk30PXaW6KoPYhUXgW5O9UlGpzYt5gmy+c52jumJJooh9IifNrXK3M/1Oo2ig==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" + }, + "node_modules/@types/mailchimp__mailchimp_marketing": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/mailchimp__mailchimp_marketing/-/mailchimp__mailchimp_marketing-3.0.10.tgz", + "integrity": "sha512-UMeHXH1XQUuj6PBWQzripZhH7ENEFMHiH55aGXKKuqhM29kF//xHWwqazY/zwT8TWKHvXAmIg/MKT8ZM1pFWrw==", + "dev": true + }, + "node_modules/@types/mailparser": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@types/mailparser/-/mailparser-3.4.0.tgz", + "integrity": "sha512-MotFinA1sT2nPFtQw1WpaF3X6I1OdbEloaixMmk924BOYqwHmlZkoi7XcVUXHI+7i0to8JguHqYj5k/E6c9Chw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "iconv-lite": "^0.6.3" + } + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/marked": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.3.2.tgz", + "integrity": "sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w==", + "dev": true + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" + }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + }, + "node_modules/@types/mime-types": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz", + "integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "node_modules/@types/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", + "dev": true + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" + }, + "node_modules/@types/mustache": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@types/mustache/-/mustache-4.2.4.tgz", + "integrity": "sha512-5DK8oX+pyEJm8Arm57Ut2R4KCeDuNQhLAuU04IgaKB7nYsFYzhpWqSoFnp7kCtVG7wXKftnaEJIyUFTVvSkkzw==", + "dev": true + }, + "node_modules/@types/mysql": { + "version": "2.15.27", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.27.tgz", + "integrity": "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "18.19.113", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.113.tgz", + "integrity": "sha512-TmSTE9vyebJ9vSEiU+P+0Sp4F5tMgjiEOZaQUW6wA3ODvi6uBgkHQ+EsIu0pbiKvf9QHEvyRCiaz03rV0b+IaA==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-cron": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.11.tgz", + "integrity": "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==", + "dev": true + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/nodemailer": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.9.tgz", + "integrity": "sha512-XYG8Gv+sHjaOtUpiuytahMy2mM3rectgroNbs6R3djZEKmPNiIJwe9KqOJBGzKKnNZNKvnuvmugBgpq3w/S0ig==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true + }, + "node_modules/@types/onfleet__node-onfleet": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@types/onfleet__node-onfleet/-/onfleet__node-onfleet-1.3.7.tgz", + "integrity": "sha512-P6rfCK7t19hHGNqJ2xBh17VplPjgk0J8aktx+544wJHfK3ZRjTEAPmPsCa1NRWhbheAHoPQ8nFZSzylWsXzopQ==", + "dev": true + }, + "node_modules/@types/pako": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.3.tgz", + "integrity": "sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==", + "dev": true + }, + "node_modules/@types/papaparse": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.8.tgz", + "integrity": "sha512-ArKIEOOWULbhi53wkAiRy1ze4wvrTfhpAj7Yfzva+EkmX2sV8PpFB+xqzJfzXNzK4me95FJH9QZt5NXFVGzOoQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "dev": true + }, + "node_modules/@types/pg": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.10.2.tgz", + "integrity": "sha512-MKFs9P6nJ+LAeHLU3V0cODEOgyThJ3OAnmOlsZsxux6sfQs3HRXR5bBn7xG5DjckEFhTAxsXi7k7cd0pCMxpJw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^4.0.1" + } + }, + "node_modules/@types/pg-format": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/pg-format/-/pg-format-1.0.5.tgz", + "integrity": "sha512-i+oEEJEC+1I3XAhgqtVp45Faj8MBbV0Aoq4rHsHD7avgLjyDkaWKObd514g0Q/DOUkdxU0P4CQ0iq2KR4SoJcw==" + }, + "node_modules/@types/phoenix": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==" + }, + "node_modules/@types/prismjs": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.0.tgz", + "integrity": "sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true + }, + "node_modules/@types/qrcode": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.5.tgz", + "integrity": "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + }, + "node_modules/@types/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.1.tgz", + "integrity": "sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-lottie": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@types/react-lottie/-/react-lottie-1.2.10.tgz", + "integrity": "sha512-rCd1p3US4ELKJlqwVnP0h5b24zt5p9OCvKUoNpYExLqwbFZMWEiJ6EGLMmH7nmq5V7KomBIbWO2X/XRFsL0vCA==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/recharts": { + "version": "1.8.29", + "resolved": "https://registry.npmjs.org/@types/recharts/-/recharts-1.8.29.tgz", + "integrity": "sha512-ulKklaVsnFIIhTQsQw226TnOibrddW1qUQNFVhoQEyY1Z7FRQrNecFCGt7msRuJseudzE9czVawZb17dK/aPXw==", + "dev": true, + "dependencies": { + "@types/d3-shape": "^1", + "@types/react": "*" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/showdown": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.6.tgz", + "integrity": "sha512-pTvD/0CIeqe4x23+YJWlX2gArHa8G0J0Oh6GKaVXV7TAeickpkkZiNOgFcFcmLQ5lB/K0qBJL1FtRYltBfbGCQ==" + }, + "node_modules/@types/snowflake-sdk": { + "version": "1.6.20", + "resolved": "https://registry.npmjs.org/@types/snowflake-sdk/-/snowflake-sdk-1.6.20.tgz", + "integrity": "sha512-Dr7oIXrWthlk9wVWpZgpm49BT8cFFXz43u7SkJKyiZK3WHiHQo4b+m2/p3WIpkYzZCcOZZ/t1B09XMd7+u1Wjw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "generic-pool": "^3.9.0" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/sqlstring": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@types/sqlstring/-/sqlstring-2.3.2.tgz", + "integrity": "sha512-lVRe4Iz9UNgiHelKVo8QlC8fb5nfY8+p+jNQNE+UVsuuVlQnWhyWmQ/wF5pE8Ys6TdjfVpqTG9O9i2vi6E0+Sg==", + "dev": true + }, + "node_modules/@types/ssh2": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.5.tgz", + "integrity": "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==", + "dev": true, + "dependencies": { + "@types/node": "^18.11.18" + } + }, + "node_modules/@types/ssh2-sftp-client": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@types/ssh2-sftp-client/-/ssh2-sftp-client-9.0.0.tgz", + "integrity": "sha512-TLrSS/GoU9UwGx9WqTPY/1zQL0TITLw+lmjcT+xGHGjozT+dS/ptwhh/FF1+rY0XJ2P715WDkL/e8yEOnoCR/g==", + "dev": true, + "dependencies": { + "@types/ssh2": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/symlink-or-copy": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/symlink-or-copy/-/symlink-or-copy-1.2.2.tgz", + "integrity": "sha512-MQ1AnmTLOncwEf9IVU+B2e4Hchrku5N67NkgcAHW0p3sdzPe0FNMANxEm6OJUzPniEQGkeT3OROLlCwZJLWFZA==", + "dev": true + }, + "node_modules/@types/tinycolor2": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.5.tgz", + "integrity": "sha512-uLJijDHN5E6j5n1qefF9oaeplgszXglWXWTviMoFr/YxgvbyrkFil20yDT7ljhCiTQ/BfCYtxfJS81LdTro5DQ==", + "dev": true + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + }, + "node_modules/@types/turndown": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.4.tgz", + "integrity": "sha512-28GI33lCCkU4SGH1GvjDhFgOVr+Tym4PXGBIU1buJUa6xQolniPArtUT+kv42RR2N9MsMLInkr904Aq+ESHBJg==", + "dev": true + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==" + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/@types/write-file-atomic": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/write-file-atomic/-/write-file-atomic-4.0.3.tgz", + "integrity": "sha512-qdo+vZRchyJIHNeuI1nrpsLw+hnkgqP/8mlaN6Wle/NKhydHmUN9l4p3ZE8yP90AJNJW4uB8HQhedb4f1vNayQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/xml2js": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz", + "integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/xmlrpc": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@types/xmlrpc/-/xmlrpc-1.3.10.tgz", + "integrity": "sha512-0jU+htwq8NGHqcz9pZzD76/Kpe1dpkDGFH696UoTONkwteRHA/2nzBAanqN7EppdkO+DwYYZd9M8IaOcnDqeVQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.1.tgz", + "integrity": "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.35.1", + "@typescript-eslint/types": "^8.35.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.1.tgz", + "integrity": "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.1.tgz", + "integrity": "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.1.tgz", + "integrity": "sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.35.1", + "@typescript-eslint/utils": "8.35.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.1.tgz", + "integrity": "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.35.1", + "@typescript-eslint/visitor-keys": "8.35.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.1.tgz", + "integrity": "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.1.tgz", + "integrity": "sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==", + "dev": true, + "dependencies": { + "@typescript-eslint/project-service": "8.35.1", + "@typescript-eslint/tsconfig-utils": "8.35.1", + "@typescript-eslint/types": "8.35.1", + "@typescript-eslint/visitor-keys": "8.35.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.1.tgz", + "integrity": "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.35.1", + "@typescript-eslint/types": "8.35.1", + "@typescript-eslint/typescript-estree": "8.35.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.1.tgz", + "integrity": "sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.35.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.2.3.tgz", + "integrity": "sha512-oRhjSzcVjX8ExyaF8hC0zzTqxlVuRlgMHL/Bh4w3xB9+wjbm0FpXylVU/lBrn+kgphwYTrOk3tp+AVShGmlYCg==", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@uiw/codemirror-extensions-basic-setup": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.23.0.tgz", + "integrity": "sha512-+k5nkRpUWGaHr1JWT8jcKsVewlXw5qBgSopm9LW8fZ6KnSNZBycz8kHxh0+WSvckmXEESGptkIsb7dlkmJT/hQ==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@codemirror/autocomplete": ">=6.0.0", + "@codemirror/commands": ">=6.0.0", + "@codemirror/language": ">=6.0.0", + "@codemirror/lint": ">=6.0.0", + "@codemirror/search": ">=6.0.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/view": ">=6.0.0" + } + }, + "node_modules/@uiw/codemirror-theme-github": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-github/-/codemirror-theme-github-4.23.0.tgz", + "integrity": "sha512-1pJ9V7LQXoojfgYXgI4yn8CfaYBm9HS919xC32/rs81Wl1lhYEOhiYRmNcpnJQDu9ZMgO8ebPMgAVU21z/C76g==", + "dependencies": { + "@uiw/codemirror-themes": "4.23.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-themes": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.23.0.tgz", + "integrity": "sha512-9fiji9xooZyBQozR1i6iTr56YP7j/Dr/VgsNWbqf5Szv+g+4WM1iZuiDGwNXmFMWX8gbkDzp6ASE21VCPSofWw==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@codemirror/language": ">=6.0.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/view": ">=6.0.0" + } + }, + "node_modules/@uiw/react-codemirror": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.23.0.tgz", + "integrity": "sha512-MnqTXfgeLA3fsUUQjqjJgemEuNyoGALgsExVm0NQAllAAi1wfj+IoKFeK+h3XXMlTFRCFYOUh4AHDv0YXJLsOg==", + "dependencies": { + "@babel/runtime": "^7.18.6", + "@codemirror/commands": "^6.1.0", + "@codemirror/state": "^6.1.1", + "@codemirror/theme-one-dark": "^6.0.0", + "@uiw/codemirror-extensions-basic-setup": "4.23.0", + "codemirror": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.11.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/theme-one-dark": ">=6.0.0", + "@codemirror/view": ">=6.0.0", + "codemirror": ">=6.0.0", + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@uiw/react-codemirror/node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" + }, + "node_modules/@verdaccio/auth": { + "version": "8.0.0-next-8.15", + "resolved": "https://registry.npmjs.org/@verdaccio/auth/-/auth-8.0.0-next-8.15.tgz", + "integrity": "sha512-vAfzGOHbPcPXMCI90jqm/qSZ1OUBnOGzudZA3+YtherncdwADekvXbdJlZVclcfmZ0sRbfVG5Xpf88aETiwfcw==", + "dev": true, + "dependencies": { + "@verdaccio/config": "8.0.0-next-8.15", + "@verdaccio/core": "8.0.0-next-8.15", + "@verdaccio/loaders": "8.0.0-next-8.6", + "@verdaccio/signature": "8.0.0-next-8.7", + "@verdaccio/utils": "8.1.0-next-8.15", + "debug": "4.4.0", + "lodash": "4.17.21", + "verdaccio-htpasswd": "13.0.0-next-8.15" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/@verdaccio/auth/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@verdaccio/commons-api": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@verdaccio/commons-api/-/commons-api-10.2.0.tgz", + "integrity": "sha512-F/YZANu4DmpcEV0jronzI7v2fGVWkQ5Mwi+bVmV+ACJ+EzR0c9Jbhtbe5QyLUuzR97t8R5E/Xe53O0cc2LukdQ==", + "dev": true, + "dependencies": { + "http-errors": "2.0.0", + "http-status-codes": "2.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/@verdaccio/config": { + "version": "8.0.0-next-8.15", + "resolved": "https://registry.npmjs.org/@verdaccio/config/-/config-8.0.0-next-8.15.tgz", + "integrity": "sha512-oEzQB+xeqaFAy54veMshqpt1hlZCYNkqoKuwkt7O8J43Fo/beiLluKUVneXckzi+pg1yvvGT7lNCbvuUQrxxQg==", + "dev": true, + "dependencies": { + "@verdaccio/core": "8.0.0-next-8.15", + "@verdaccio/utils": "8.1.0-next-8.15", + "debug": "4.4.0", + "js-yaml": "4.1.0", + "lodash": "4.17.21", + "minimatch": "7.4.6" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/@verdaccio/config/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@verdaccio/config/node_modules/minimatch": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", + "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@verdaccio/core": { + "version": "8.0.0-next-8.15", + "resolved": "https://registry.npmjs.org/@verdaccio/core/-/core-8.0.0-next-8.15.tgz", + "integrity": "sha512-d5r/ZSkCri7s1hvV35enptquV5LJ81NqMYJnsjuryIUnvwn1yaqLlcdd6zIL08unzCSr7qDdUAdwGRRm6PKzng==", + "dev": true, + "dependencies": { + "ajv": "8.17.1", + "core-js": "3.40.0", + "http-errors": "2.0.0", + "http-status-codes": "2.3.0", + "process-warning": "1.0.0", + "semver": "7.7.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/@verdaccio/core/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@verdaccio/core/node_modules/core-js": { + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz", + "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/@verdaccio/core/node_modules/http-status-codes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", + "dev": true + }, + "node_modules/@verdaccio/core/node_modules/process-warning": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", + "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==", + "dev": true + }, + "node_modules/@verdaccio/core/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@verdaccio/file-locking": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@verdaccio/file-locking/-/file-locking-10.3.1.tgz", + "integrity": "sha512-oqYLfv3Yg3mAgw9qhASBpjD50osj2AX4IwbkUtyuhhKGyoFU9eZdrbeW6tpnqUnj6yBMtAPm2eGD4BwQuX400g==", + "dev": true, + "dependencies": { + "lockfile": "1.0.4" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/@verdaccio/loaders": { + "version": "8.0.0-next-8.6", + "resolved": "https://registry.npmjs.org/@verdaccio/loaders/-/loaders-8.0.0-next-8.6.tgz", + "integrity": "sha512-yuqD8uAZJcgzuNHjV6C438UNT5r2Ai9+SnUlO34AHZdWSYcluO3Zj5R3p5uf+C7YPCE31pUD27wBU74xVbUoBw==", + "dev": true, + "dependencies": { + "debug": "4.4.0", + "lodash": "4.17.21" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/@verdaccio/loaders/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@verdaccio/local-storage-legacy": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@verdaccio/local-storage-legacy/-/local-storage-legacy-11.0.2.tgz", + "integrity": "sha512-7AXG7qlcVFmF+Nue2oKaraprGRtaBvrQIOvc/E89+7hAe399V01KnZI6E/ET56u7U9fq0MSlp92HBcdotlpUXg==", + "dev": true, + "dependencies": { + "@verdaccio/commons-api": "10.2.0", + "@verdaccio/file-locking": "10.3.1", + "@verdaccio/streams": "10.2.1", + "async": "3.2.4", + "debug": "4.3.4", + "lodash": "4.17.21", + "lowdb": "1.0.0", + "mkdirp": "1.0.4" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/@verdaccio/local-storage-legacy/node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "node_modules/@verdaccio/local-storage-legacy/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@verdaccio/local-storage-legacy/node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true + }, + "node_modules/@verdaccio/local-storage-legacy/node_modules/lowdb": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-1.0.0.tgz", + "integrity": "sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.3", + "is-promise": "^2.1.0", + "lodash": "4", + "pify": "^3.0.0", + "steno": "^0.4.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@verdaccio/local-storage-legacy/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@verdaccio/local-storage-legacy/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@verdaccio/local-storage-legacy/node_modules/steno": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/steno/-/steno-0.4.4.tgz", + "integrity": "sha512-EEHMVYHNXFHfGtgjNITnka0aHhiAlo93F7z2/Pwd+g0teG9CnM3JIINM7hVVB5/rhw9voufD7Wukwgtw2uqh6w==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.3" + } + }, + "node_modules/@verdaccio/logger": { + "version": "8.0.0-next-8.15", + "resolved": "https://registry.npmjs.org/@verdaccio/logger/-/logger-8.0.0-next-8.15.tgz", + "integrity": "sha512-3gjhqvB87JUNDHFMN3YG4IweS9EgbCpAWZatNYzcoIWOoGiEaFQQBSM592CaFiI0yf8acyqWkNa1V95L1NMbRg==", + "dev": true, + "dependencies": { + "@verdaccio/logger-commons": "8.0.0-next-8.15", + "pino": "9.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/@verdaccio/logger-commons": { + "version": "8.0.0-next-8.15", + "resolved": "https://registry.npmjs.org/@verdaccio/logger-commons/-/logger-commons-8.0.0-next-8.15.tgz", + "integrity": "sha512-nF7VgBC2cl5ufv+mZEwBHHyZFb1F0+kVkuRMf3Tyk+Qp4lXilC9MRZ0oc+RnzsDbNmJ6IZHgHNbs6aJrNfaRGg==", + "dev": true, + "dependencies": { + "@verdaccio/core": "8.0.0-next-8.15", + "@verdaccio/logger-prettify": "8.0.0-next-8.2", + "colorette": "2.0.20", + "debug": "4.4.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/@verdaccio/logger-commons/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@verdaccio/logger-prettify": { + "version": "8.0.0-next-8.2", + "resolved": "https://registry.npmjs.org/@verdaccio/logger-prettify/-/logger-prettify-8.0.0-next-8.2.tgz", + "integrity": "sha512-WMXnZPLw5W7GSIQE8UOTp6kRIwiTmnnoJbMmyMlGiNrsRaFKTqk09R5tKUgOyGgd4Lu6yncLbmdm5UjAuwHf1Q==", + "dev": true, + "dependencies": { + "colorette": "2.0.20", + "dayjs": "1.11.13", + "lodash": "4.17.21", + "pino-abstract-transport": "1.2.0", + "sonic-boom": "3.8.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/@verdaccio/logger-prettify/node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "dev": true + }, + "node_modules/@verdaccio/logger-prettify/node_modules/pino-abstract-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "dev": true, + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/@verdaccio/logger-prettify/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@verdaccio/logger-prettify/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/@verdaccio/logger-prettify/node_modules/sonic-boom": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", + "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", + "dev": true, + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/@verdaccio/logger-prettify/node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/@verdaccio/logger-prettify/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/@verdaccio/logger/node_modules/pino": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.6.0.tgz", + "integrity": "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==", + "dev": true, + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^4.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/@verdaccio/logger/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/@verdaccio/middleware": { + "version": "8.0.0-next-8.15", + "resolved": "https://registry.npmjs.org/@verdaccio/middleware/-/middleware-8.0.0-next-8.15.tgz", + "integrity": "sha512-xsCLGbnhqcYwE8g/u9wxNLfDcESpr9ptEZ8Ce7frVTphU7kYIL48QCDPMzug7U+AguNtCq4v4zcoY1PaOQ8mgw==", + "dev": true, + "dependencies": { + "@verdaccio/config": "8.0.0-next-8.15", + "@verdaccio/core": "8.0.0-next-8.15", + "@verdaccio/url": "13.0.0-next-8.15", + "@verdaccio/utils": "8.1.0-next-8.15", + "debug": "4.4.0", + "express": "4.21.2", + "express-rate-limit": "5.5.1", + "lodash": "4.17.21", + "lru-cache": "7.18.3", + "mime": "2.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/@verdaccio/middleware/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@verdaccio/middleware/node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/@verdaccio/middleware/node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@verdaccio/middleware/node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/@verdaccio/middleware/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@verdaccio/middleware/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/@verdaccio/middleware/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@verdaccio/middleware/node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@verdaccio/middleware/node_modules/express-rate-limit": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.5.1.tgz", + "integrity": "sha512-MTjE2eIbHv5DyfuFz4zLYWxpqVhEhkTiwFGuB74Q9CSou2WHO52nlE5y3Zlg6SIsiYUIPj6ifFxnkPz6O3sIUg==", + "dev": true + }, + "node_modules/@verdaccio/middleware/node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@verdaccio/middleware/node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/@verdaccio/middleware/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@verdaccio/middleware/node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@verdaccio/middleware/node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/@verdaccio/middleware/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@verdaccio/middleware/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@verdaccio/middleware/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@verdaccio/middleware/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@verdaccio/middleware/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@verdaccio/middleware/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@verdaccio/middleware/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@verdaccio/middleware/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true + }, + "node_modules/@verdaccio/middleware/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@verdaccio/middleware/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@verdaccio/middleware/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/@verdaccio/middleware/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@verdaccio/middleware/node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@verdaccio/middleware/node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/@verdaccio/middleware/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@verdaccio/middleware/node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@verdaccio/middleware/node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@verdaccio/middleware/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@verdaccio/middleware/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@verdaccio/search-indexer": { + "version": "8.0.0-next-8.4", + "resolved": "https://registry.npmjs.org/@verdaccio/search-indexer/-/search-indexer-8.0.0-next-8.4.tgz", + "integrity": "sha512-Oea9m9VDqdlDPyQ9+fpcxZk0sIYH2twVK+YbykHpSYpjZRzz9hJfIr/uUwAgpWq83zAl2YDbz4zR3TjzjrWQig==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/@verdaccio/signature": { + "version": "8.0.0-next-8.7", + "resolved": "https://registry.npmjs.org/@verdaccio/signature/-/signature-8.0.0-next-8.7.tgz", + "integrity": "sha512-sqP+tNzUtVIwUtt1ZHwYoxsO3roDLK7GW8c8Hj0SNaON+9ele9z4NBhaor+g95zRuLy6xtw/RgOvpyLon/vPrA==", + "dev": true, + "dependencies": { + "@verdaccio/config": "8.0.0-next-8.15", + "debug": "4.4.0", + "jsonwebtoken": "9.0.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/@verdaccio/signature/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@verdaccio/signature/node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dev": true, + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/@verdaccio/signature/node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "dev": true, + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@verdaccio/signature/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dev": true, + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@verdaccio/streams": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@verdaccio/streams/-/streams-10.2.1.tgz", + "integrity": "sha512-OojIG/f7UYKxC4dYX8x5ax8QhRx1b8OYUAMz82rUottCuzrssX/4nn5QE7Ank0DUSX3C9l/HPthc4d9uKRJqJQ==", + "dev": true, + "engines": { + "node": ">=12", + "npm": ">=5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/@verdaccio/tarball": { + "version": "13.0.0-next-8.15", + "resolved": "https://registry.npmjs.org/@verdaccio/tarball/-/tarball-13.0.0-next-8.15.tgz", + "integrity": "sha512-oSNmq7zD/iPIC5HpJbOJjW/lb0JV9k3jLwI6sG7kPgm+UIxVAOV4fKQOAD18HpHl/WjkF247NA6zGlAB94Habw==", + "dev": true, + "dependencies": { + "@verdaccio/core": "8.0.0-next-8.15", + "@verdaccio/url": "13.0.0-next-8.15", + "@verdaccio/utils": "8.1.0-next-8.15", + "debug": "4.4.0", + "gunzip-maybe": "^1.4.2", + "lodash": "4.17.21", + "tar-stream": "^3.1.7" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/@verdaccio/tarball/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@verdaccio/ui-theme": { + "version": "8.0.0-next-8.15", + "resolved": "https://registry.npmjs.org/@verdaccio/ui-theme/-/ui-theme-8.0.0-next-8.15.tgz", + "integrity": "sha512-k9BAM7rvbUqB2JPReNgXKUVTzBkdmIrNw0f6/7uyO+9cp7eVuarrPBnVF0oMc7jzVNBZRCpUksrhMZ0KwDZTpw==", + "dev": true + }, + "node_modules/@verdaccio/url": { + "version": "13.0.0-next-8.15", + "resolved": "https://registry.npmjs.org/@verdaccio/url/-/url-13.0.0-next-8.15.tgz", + "integrity": "sha512-1N/dGhw7cZMhupf/Xlm73beiL3oCaAiyo9DTumjF3aTcJnipVcT1hoj6CSj9RIX54824rUK9WVmo83dk0KPnjw==", + "dev": true, + "dependencies": { + "@verdaccio/core": "8.0.0-next-8.15", + "debug": "4.4.0", + "lodash": "4.17.21", + "validator": "13.12.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/@verdaccio/url/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@verdaccio/utils": { + "version": "8.1.0-next-8.15", + "resolved": "https://registry.npmjs.org/@verdaccio/utils/-/utils-8.1.0-next-8.15.tgz", + "integrity": "sha512-efg/bunOUMVXV+MlljJCrpuT+OQRrQS4wJyGL92B3epUGlgZ8DXs+nxN5v59v1a6AocAdSKwHgZS0g9txmBhOg==", + "dev": true, + "dependencies": { + "@verdaccio/core": "8.0.0-next-8.15", + "lodash": "4.17.21", + "minimatch": "7.4.6", + "semver": "7.7.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/@verdaccio/utils/node_modules/minimatch": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", + "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@verdaccio/utils/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.6.0.tgz", + "integrity": "sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.19", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + } + }, + "node_modules/@vitest/expect": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.8.tgz", + "integrity": "sha512-Xu6TTIavTvSSS6LZaA3EebWFr6tsoXPetOWNMOlc7LO88QVVBwq2oQWBoDiLCN6YTvNYsGSjqOO8CAdjom5DCQ==", + "dev": true, + "dependencies": { + "@vitest/spy": "3.0.8", + "@vitest/utils": "3.0.8", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/@vitest/pretty-format": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.8.tgz", + "integrity": "sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==", + "dev": true, + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/@vitest/utils": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.8.tgz", + "integrity": "sha512-nkBC3aEhfX2PdtQI/QwAWp8qZWwzASsU4Npbcd5RdMPBSSLCpkZp52P3xku3s3uA0HIEhGvEcF8rNkBsz9dQ4Q==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "3.0.8", + "loupe": "^3.1.3", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/loupe": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", + "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", + "dev": true + }, + "node_modules/@vitest/mocker": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.8.tgz", + "integrity": "sha512-n3LjS7fcW1BCoF+zWZxG7/5XvuYH+lsFg+BDwwAz0arIwHQJFUEsKBQ0BLU49fCxuM/2HSeBPHQD8WjgrxMfow==", + "dev": true, + "dependencies": { + "@vitest/spy": "3.0.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/runner": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.8.tgz", + "integrity": "sha512-c7UUw6gEcOzI8fih+uaAXS5DwjlBaCJUo7KJ4VvJcjL95+DSR1kova2hFuRt3w41KZEFcOEiq098KkyrjXeM5w==", + "dev": true, + "dependencies": { + "@vitest/utils": "3.0.8", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/@vitest/pretty-format": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.8.tgz", + "integrity": "sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==", + "dev": true, + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/@vitest/utils": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.8.tgz", + "integrity": "sha512-nkBC3aEhfX2PdtQI/QwAWp8qZWwzASsU4Npbcd5RdMPBSSLCpkZp52P3xku3s3uA0HIEhGvEcF8rNkBsz9dQ4Q==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "3.0.8", + "loupe": "^3.1.3", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/loupe": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", + "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", + "dev": true + }, + "node_modules/@vitest/runner/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "node_modules/@vitest/snapshot": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.8.tgz", + "integrity": "sha512-x8IlMGSEMugakInj44nUrLSILh/zy1f2/BgH0UeHpNyOocG18M9CWVIFBaXPt8TrqVZWmcPjwfG/ht5tnpba8A==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "3.0.8", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/@vitest/pretty-format": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.8.tgz", + "integrity": "sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==", + "dev": true, + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "node_modules/@vitest/spy": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.8.tgz", + "integrity": "sha512-MR+PzJa+22vFKYb934CejhR4BeRpMSoxkvNoDit68GQxRLSf11aT6CTj3XaqUU9rxgWJFnqicN/wxw6yBRkI1Q==", + "dev": true, + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/ui": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-1.6.1.tgz", + "integrity": "sha512-xa57bCPGuzEFqGjPs3vVLyqareG8DX0uMkr5U/v5vLv5/ZUrBrPL7gzxzTJedEyZxFMfsozwTIbbYfEQVo3kgg==", + "dev": true, + "dependencies": { + "@vitest/utils": "1.6.1", + "fast-glob": "^3.3.2", + "fflate": "^0.8.1", + "flatted": "^3.2.9", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "sirv": "^2.0.4" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "1.6.1" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vitest/utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/utils/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xhmikosr/archive-type": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/archive-type/-/archive-type-7.0.0.tgz", + "integrity": "sha512-sIm84ZneCOJuiy3PpWR5bxkx3HaNt1pqaN+vncUBZIlPZCq8ASZH+hBVdu5H8znR7qYC6sKwx+ie2Q7qztJTxA==", + "dev": true, + "dependencies": { + "file-type": "^19.0.0" + }, + "engines": { + "node": "^14.14.0 || >=16.0.0" + } + }, + "node_modules/@xhmikosr/bin-check": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@xhmikosr/bin-check/-/bin-check-7.0.3.tgz", + "integrity": "sha512-4UnCLCs8DB+itHJVkqFp9Zjg+w/205/J2j2wNBsCEAm/BuBmtua2hhUOdAMQE47b1c7P9Xmddj0p+X1XVsfHsA==", + "dev": true, + "dependencies": { + "execa": "^5.1.1", + "isexe": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/bin-wrapper": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@xhmikosr/bin-wrapper/-/bin-wrapper-13.0.5.tgz", + "integrity": "sha512-DT2SAuHDeOw0G5bs7wZbQTbf4hd8pJ14tO0i4cWhRkIJfgRdKmMfkDilpaJ8uZyPA0NVRwasCNAmMJcWA67osw==", + "dev": true, + "dependencies": { + "@xhmikosr/bin-check": "^7.0.3", + "@xhmikosr/downloader": "^15.0.1", + "@xhmikosr/os-filter-obj": "^3.0.0", + "bin-version-check": "^5.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress/-/decompress-10.0.1.tgz", + "integrity": "sha512-6uHnEEt5jv9ro0CDzqWlFgPycdE+H+kbJnwyxgZregIMLQ7unQSCNVsYG255FoqU8cP46DyggI7F7LohzEl8Ag==", + "dev": true, + "dependencies": { + "@xhmikosr/decompress-tar": "^8.0.1", + "@xhmikosr/decompress-tarbz2": "^8.0.1", + "@xhmikosr/decompress-targz": "^8.0.1", + "@xhmikosr/decompress-unzip": "^7.0.0", + "graceful-fs": "^4.2.11", + "make-dir": "^4.0.0", + "strip-dirs": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-tar": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tar/-/decompress-tar-8.0.1.tgz", + "integrity": "sha512-dpEgs0cQKJ2xpIaGSO0hrzz3Kt8TQHYdizHsgDtLorWajuHJqxzot9Hbi0huRxJuAGG2qiHSQkwyvHHQtlE+fg==", + "dev": true, + "dependencies": { + "file-type": "^19.0.0", + "is-stream": "^2.0.1", + "tar-stream": "^3.1.7" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-tarbz2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tarbz2/-/decompress-tarbz2-8.0.2.tgz", + "integrity": "sha512-p5A2r/AVynTQSsF34Pig6olt9CvRj6J5ikIhzUd3b57pUXyFDGtmBstcw+xXza0QFUh93zJsmY3zGeNDlR2AQQ==", + "dev": true, + "dependencies": { + "@xhmikosr/decompress-tar": "^8.0.1", + "file-type": "^19.6.0", + "is-stream": "^2.0.1", + "seek-bzip": "^2.0.0", + "unbzip2-stream": "^1.4.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-targz": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-targz/-/decompress-targz-8.0.1.tgz", + "integrity": "sha512-mvy5AIDIZjQ2IagMI/wvauEiSNHhu/g65qpdM4EVoYHUJBAmkQWqcPJa8Xzi1aKVTmOA5xLJeDk7dqSjlHq8Mg==", + "dev": true, + "dependencies": { + "@xhmikosr/decompress-tar": "^8.0.1", + "file-type": "^19.0.0", + "is-stream": "^2.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-unzip": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-unzip/-/decompress-unzip-7.0.0.tgz", + "integrity": "sha512-GQMpzIpWTsNr6UZbISawsGI0hJ4KA/mz5nFq+cEoPs12UybAqZWKbyIaZZyLbJebKl5FkLpsGBkrplJdjvUoSQ==", + "dev": true, + "dependencies": { + "file-type": "^19.0.0", + "get-stream": "^6.0.1", + "yauzl": "^3.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/downloader": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@xhmikosr/downloader/-/downloader-15.0.1.tgz", + "integrity": "sha512-fiuFHf3Dt6pkX8HQrVBsK0uXtkgkVlhrZEh8b7VgoDqFf+zrgFBPyrwCqE/3nDwn3hLeNz+BsrS7q3mu13Lp1g==", + "dev": true, + "dependencies": { + "@xhmikosr/archive-type": "^7.0.0", + "@xhmikosr/decompress": "^10.0.1", + "content-disposition": "^0.5.4", + "defaults": "^3.0.0", + "ext-name": "^5.0.0", + "file-type": "^19.0.0", + "filenamify": "^6.0.0", + "get-stream": "^6.0.1", + "got": "^13.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/os-filter-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/os-filter-obj/-/os-filter-obj-3.0.0.tgz", + "integrity": "sha512-siPY6BD5dQ2SZPl3I0OZBHL27ZqZvLEosObsZRQ1NUB8qcxegwt0T9eKtV96JMFQpIz1elhkzqOg4c/Ri6Dp9A==", + "dev": true, + "dependencies": { + "arch": "^3.0.0" + }, + "engines": { + "node": "^14.14.0 || >=16.0.0" + } + }, + "node_modules/@xmldom/is-dom-node": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@xmldom/is-dom-node/-/is-dom-node-1.0.1.tgz", + "integrity": "sha512-CJDxIgE5I0FH+ttq/Fxy6nRpxP70+e2O048EPe85J2use3XKdatVM7dDVvFNjQudd9B49NPoZ+8PG49zj4Er8Q==", + "engines": { + "node": ">= 16" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@xobotyi/scrollbar-width": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz", + "integrity": "sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==" + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/@xyflow/react": { + "version": "12.3.5", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.3.5.tgz", + "integrity": "sha512-wAYqpicdrVo1rxCu0X3M9s3YIF45Agqfabw0IBryTGqjWvr2NyfciI8gIP4MB+NKpWWN5kxZ9tiZ9u8lwC7iAg==", + "dependencies": { + "@xyflow/system": "0.0.46", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.46", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.46.tgz", + "integrity": "sha512-bmFXvboVdiydIFZmDCjrbBCYgB0d5pYdkcZPWbAxGmhMRUZ+kW3CksYgYxWabrw51rwpWitLEadvLrivG0mVfA==", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "node_modules/@yarnpkg/parsers": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.2.tgz", + "integrity": "sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==", + "dev": true, + "dependencies": { + "js-yaml": "^3.10.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@yarnpkg/parsers/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@yarnpkg/parsers/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@yarnpkg/parsers/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/@zkochan/js-yaml": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.7.tgz", + "integrity": "sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abortcontroller-polyfill": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.8.tgz", + "integrity": "sha512-9f1iZ2uWh92VcrU9Y8x+LdM4DLj75VE0MJB8zuF1iUnroEptStw+DQ8EQPMUdfe5k+PkB1uUfDQfWbhstH8LrQ==" + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ace-builds": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.43.0.tgz", + "integrity": "sha512-iBkvY7owAPCquKCenPCEl4YVDOo9YPRfAZbOuzGcyJlMYhiA5aIEjFPZsYZvX1ZQ1Rq4cfYRhJjixSYcpDPOoQ==" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/addressparser": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz", + "integrity": "sha512-aQX7AISOMM7HFE0iZ3+YnD07oIeJqWGVnJ+ZIKaBZAk03ftmVYVqsGas/rbXKR21n4D/hKCSHypvcyOkds/xzg==" + }, + "node_modules/adm-zip": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", + "dev": true, + "engines": { + "node": ">=12.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz", + "integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==", + "dependencies": { + "clean-stack": "^4.0.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ai": { + "version": "4.3.16", + "resolved": "https://registry.npmjs.org/ai/-/ai-4.3.16.tgz", + "integrity": "sha512-KUDwlThJ5tr2Vw0A1ZkbDKNME3wzWhuVfAOwIvFUzl1TPVDFAXDFTXio3p+jaKneB+dKNCvFFlolYmmgHttG1g==", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8", + "@ai-sdk/react": "1.2.12", + "@ai-sdk/ui-utils": "1.2.11", + "@opentelemetry/api": "1.9.0", + "jsondiffpatch": "0.6.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/airtable": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/airtable/-/airtable-0.11.6.tgz", + "integrity": "sha512-Na67L2TO1DflIJ1yOGhQG5ilMfL2beHpsR+NW/jhaYOa4QcoxZOtDFs08cpSd1tBMsLpz5/rrz/VMX/pGL/now==", + "dependencies": { + "@types/node": ">=8.0.0 <15", + "abort-controller": "^3.0.0", + "abortcontroller-polyfill": "^1.4.0", + "lodash": "^4.17.21", + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/airtable/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/amqplib": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.4.tgz", + "integrity": "sha512-DMZ4eCEjAVdX1II2TfIUpJhfKAuoCeDIo/YyETbfAqehHTXxxs7WOOd+N1Xxr4cKhx12y23zk8/os98FxlZHrw==", + "dependencies": { + "@acuminous/bitsyntax": "^0.1.2", + "buffer-more-ints": "~1.0.0", + "readable-stream": "1.x >=1.1.9", + "url-parse": "~1.5.10" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/apache-md5": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/apache-md5/-/apache-md5-1.1.8.tgz", + "integrity": "sha512-FCAJojipPn0bXjuEpjOOOMN8FZDkxfWWp4JGN9mifU2IhxvKyXZYqpzPHdnTSUpmPDy+tsslB6Z1g+Vg6nVbYA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/app-root-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "optional": true + }, + "node_modules/arch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-3.0.0.tgz", + "integrity": "sha512-AmIAC+Wtm2AU8lGfTtHsw0Y9Qtftx2YXEEtiBP10xFUtMOA+sHHx6OAddyL52mUKh1vsXQ6/w1mVDptZCyUt4Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/are-we-there-yet/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/are-we-there-yet/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-indexofobject": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/array-indexofobject/-/array-indexofobject-0.0.1.tgz", + "integrity": "sha512-tpdPBIBm4TMNxSp8O3pZgC7mF4+wn9SmJlhE+7bi5so6x39PvzUqChQMbv93R5ilYGZ1HV+Neki4IH/i+87AoQ==" + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "engines": { + "node": ">=8" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/asn1.js-rfc2560": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/asn1.js-rfc2560/-/asn1.js-rfc2560-5.0.1.tgz", + "integrity": "sha512-1PrVg6kuBziDN3PGFmRk3QrjpKvP9h/Hv5yMrFZvC1kpzP6dQRzf5BpKstANqHBkaOUmTpakJWhicTATOA/SbA==", + "dependencies": { + "asn1.js-rfc5280": "^3.0.0" + }, + "peerDependencies": { + "asn1.js": "^5.0.0" + } + }, + "node_modules/asn1.js-rfc5280": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/asn1.js-rfc5280/-/asn1.js-rfc5280-3.0.0.tgz", + "integrity": "sha512-Y2LZPOWeZ6qehv698ZgOGGCZXBQShObWnGthTrIFlIQjuV1gg2B8QOhWFRExq/MR1VnPpIIe7P9vX2vElxv+Pg==", + "dependencies": { + "asn1.js": "^5.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==" + }, + "node_modules/assemblyai": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/assemblyai/-/assemblyai-4.7.0.tgz", + "integrity": "sha512-m0GGgb9StJMYJns9bUrMtz+WlrcM8jNiYnhlSalz5tGgu9XUThxjOs6bWXxPNsD/0BjXp4VijMEvwmue3Q85mQ==", + "dependencies": { + "ws": "^8.17.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "dev": true + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/async-mutex": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz", + "integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.15", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz", + "integrity": "sha512-KCuPB8ZCIqFdA4HwKXsvz7j6gvSDNhDP7WnUjBleRkKjPdvCmHFuQ77ocavI8FT6NdvlBnE2UFr2H4Mycn8Vew==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.21.10", + "caniuse-lite": "^1.0.30001520", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/avvio": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.1.0.tgz", + "integrity": "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true + }, + "node_modules/axe-core": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/axios": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz", + "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios-ntlm": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/axios-ntlm/-/axios-ntlm-1.4.4.tgz", + "integrity": "sha512-kpCRdzMfL8gi0Z0o96P3QPAK4XuC8iciGgxGXe+PeQ4oyjI2LZN8WSOKbu0Y9Jo3T/A7pB81n6jYVPIpglEuRA==", + "dependencies": { + "axios": "^1.8.4", + "des.js": "^1.1.0", + "dev-null": "^0.1.1", + "js-md4": "^0.3.2" + } + }, + "node_modules/axios-ntlm/node_modules/axios": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios-retry": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.4.1.tgz", + "integrity": "sha512-JGzNoglDHtHWIEvvAampB0P7jxQ/sT4COmW0FgSQkVg6o4KqNjNMBI6uFVOq517hkw/OAYYAG08ADtBlV8lvmQ==", + "dependencies": { + "is-retry-allowed": "^2.2.0" + }, + "peerDependencies": { + "axios": "0.x || 1.x" + } + }, + "node_modules/axobject-query": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.4.tgz", + "integrity": "sha512-aPTElBrbifBU1krmZxGZOlBkslORe7Ll7+BDnI50Wy4LgOt69luMgevkDfTq1O/ZgprooPCtWpjCwKSZw/iZ4A==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==" + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-loader": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", + "dev": true, + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-const-enum": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-const-enum/-/babel-plugin-const-enum-1.2.0.tgz", + "integrity": "sha512-o1m/6iyyFnp9MRsK1dHF3bneqyf3AlM2q3A/YbgQr2pCat6B6XJVDv2TXqzfY2RYUi4mak6WAksSBPlyYGx9dg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-typescript": "^7.3.3", + "@babel/traverse": "^7.16.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/babel-plugin-macros/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-transform-typescript-metadata": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-typescript-metadata/-/babel-plugin-transform-typescript-metadata-0.3.2.tgz", + "integrity": "sha512-mWEvCQTgXQf48yDqgN7CH50waTyYBeP2Lpqx4nNWab9sxEpdXVeKgfj1qYI2/TgUPQtNFZ85i3PemRtnXVYYJg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "node_modules/bail": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bare-events": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "optional": true + }, + "node_modules/bare-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.5.tgz", + "integrity": "sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", + "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", + "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/base16": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", + "integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "dev": true + }, + "node_modules/before-after-hook": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==" + }, + "node_modules/better-opn": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz", + "integrity": "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==", + "dependencies": { + "open": "^8.0.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.0.tgz", + "integrity": "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==", + "engines": { + "node": "*" + } + }, + "node_modules/bin-version": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-6.0.0.tgz", + "integrity": "sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "find-versions": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bin-version-check": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-5.1.0.tgz", + "integrity": "sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==", + "dev": true, + "dependencies": { + "bin-version": "^6.0.0", + "semver": "^7.5.3", + "semver-truncate": "^3.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binascii": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/binascii/-/binascii-0.0.2.tgz", + "integrity": "sha512-rA2CrUl1+6yKrn+XgLs8Hdy18OER1UW146nM+ixzhQXDY+Bd3ySkyIJGwF2a4I45JwbvF1mDL/nWkqBwpOcdBA==" + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bl/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bl/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/bn.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==" + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/boring-avatars": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/boring-avatars/-/boring-avatars-1.11.2.tgz", + "integrity": "sha512-3+wkwPeObwS4R37FGXMYViqc4iTrIRj5yzfX9Qy4mnpZ26sX41dGMhsAgmKks1r/uufY1pl4vpgzMWHYfJRb2A==" + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" + }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/broccoli-node-api": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/broccoli-node-api/-/broccoli-node-api-1.7.0.tgz", + "integrity": "sha512-QIqLSVJWJUVOhclmkmypJJH9u9s/aWH4+FH6Q6Ju5l+Io4dtwqdPUNmDfw40o6sxhbZHhqGujDJuHTML1wG8Yw==", + "dev": true + }, + "node_modules/broccoli-node-info": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/broccoli-node-info/-/broccoli-node-info-2.2.0.tgz", + "integrity": "sha512-VabSGRpKIzpmC+r+tJueCE5h8k6vON7EIMMWu6d/FyPdtijwLQ7QvzShEw+m3mHoDzUaj/kiZsDYrS8X2adsBg==", + "dev": true, + "engines": { + "node": "8.* || >= 10.*" + } + }, + "node_modules/broccoli-output-wrapper": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/broccoli-output-wrapper/-/broccoli-output-wrapper-3.2.5.tgz", + "integrity": "sha512-bQAtwjSrF4Nu0CK0JOy5OZqw9t5U0zzv2555EA/cF8/a8SLDTIetk9UgrtMVw7qKLKdSpOZ2liZNeZZDaKgayw==", + "dev": true, + "dependencies": { + "fs-extra": "^8.1.0", + "heimdalljs-logger": "^0.1.10", + "symlink-or-copy": "^1.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + } + }, + "node_modules/broccoli-output-wrapper/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/broccoli-output-wrapper/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/broccoli-output-wrapper/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/broccoli-plugin": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/broccoli-plugin/-/broccoli-plugin-4.0.7.tgz", + "integrity": "sha512-a4zUsWtA1uns1K7p9rExYVYG99rdKeGRymW0qOCNkvDPHQxVi3yVyJHhQbM3EZwdt2E0mnhr5e0c/bPpJ7p3Wg==", + "dev": true, + "dependencies": { + "broccoli-node-api": "^1.7.0", + "broccoli-output-wrapper": "^3.2.5", + "fs-merger": "^3.2.1", + "promise-map-series": "^0.3.0", + "quick-temp": "^0.1.8", + "rimraf": "^3.0.2", + "symlink-or-copy": "^1.3.1" + }, + "engines": { + "node": "10.* || >= 12.*" + } + }, + "node_modules/browser-request": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/browser-request/-/browser-request-0.3.3.tgz", + "integrity": "sha512-YyNI4qJJ+piQG6MMEuo7J3Bzaqssufx04zpEKYfSrl/1Op59HWali9zMtBpXnkmqMcOuWJPZvudrm9wISmnCbg==", + "engines": [ + "node" + ] + }, + "node_modules/browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==", + "dev": true, + "dependencies": { + "pako": "~0.2.0" + } + }, + "node_modules/browserify-zlib/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "dev": true, + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, + "node_modules/buffer-builder": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", + "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==", + "dev": true + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/buffer-more-ints": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", + "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" + }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/buildcheck": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", + "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/bullmq": { + "version": "5.28.1", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.28.1.tgz", + "integrity": "sha512-iSoqziPLKH//mmoc4Aj3/opTmk1PgFdITwUrx/wDqrTxfBRjnTGInsu129LCEY6d+SmhZWnA9PYU6ciX+NT64A==", + "dependencies": { + "cron-parser": "^4.6.0", + "ioredis": "^5.4.1", + "msgpackr": "^1.11.2", + "node-abort-controller": "^3.1.1", + "semver": "^7.5.4", + "tslib": "^2.0.0", + "uuid": "^9.0.0" + } + }, + "node_modules/bullmq/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/cacache/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dev": true, + "dependencies": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "dependencies": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-keys/node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chai/node_modules/loupe": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", + "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", + "dev": true + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cheerio/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/chromium-bidi": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.3.tgz", + "integrity": "sha512-qXlsCmpCZJAnoTYI83Iu6EdYQpMYdVkCfq08KDh2pmlVqK5t5IA9mGs4/LwCwp4fqisSOMXZxP3HIh8w8aRn0A==", + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/chromium-bidi/node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true + }, + "node_modules/class-variance-authority": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", + "integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==", + "dependencies": { + "clsx": "2.0.0" + }, + "funding": { + "url": "https://joebell.co.uk" + } + }, + "node_modules/class-variance-authority/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==" + }, + "node_modules/clean-stack": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz", + "integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==", + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clean-stack/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clear-module": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/clear-module/-/clear-module-4.1.2.tgz", + "integrity": "sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==", + "dependencies": { + "parent-module": "^2.0.0", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", + "integrity": "sha512-41U72MB56TfUMGndAKK8vJ78eooOD4Z5NOL4xEfjc0c23s+6EYKXlXsmACBVclLP1yOfWCgEganVzddVrSNoTg==", + "dependencies": { + "exit": "0.1.2", + "glob": "^7.1.1" + }, + "engines": { + "node": ">=0.2.5" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + }, + "node_modules/cli-highlight/node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/cli-highlight/node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "node_modules/cli-highlight/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-spinners": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", + "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cli/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/cli/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cli/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/clipanion": { + "version": "4.0.0-rc.4", + "resolved": "https://registry.npmjs.org/clipanion/-/clipanion-4.0.0-rc.4.tgz", + "integrity": "sha512-CXkMQxU6s9GklO/1f714dkKBMu1lopS1WFF0B8o4AxPykR1hpozxSiUZ5ZUeBjfPgCWqbcNOtZVFhB8Lkfp1+Q==", + "dev": true, + "dependencies": { + "typanion": "^3.8.0" + }, + "peerDependencies": { + "typanion": "*" + } + }, + "node_modules/clipboard": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", + "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", + "dependencies": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clone-response/node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-dialog": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz", + "integrity": "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", + "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz", + "integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-presence": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", + "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/react-remove-scroll": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", + "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/codemirror": { + "version": "5.65.14", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.14.tgz", + "integrity": "sha512-VSNugIBDGt0OU9gDjeVr6fNkoFQznrWEUdAApMlXQNbfE8gGO19776D6MwSqF/V/w/sDwonsQ0z7KmmI9guScg==" + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/colorspace/node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/colorspace/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/colorspace/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/columnify": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz", + "integrity": "sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==", + "dev": true, + "dependencies": { + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==" + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true + }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "node_modules/compare-versions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.0.tgz", + "integrity": "sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==" + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", + "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.0.2", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/concat": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/concat/-/concat-1.0.3.tgz", + "integrity": "sha512-f/ZaH1aLe64qHgTILdldbvyfGiGF4uzeo9IuXUloIOLQzFmIPloy9QbZadNsuVv0j5qbKQvQb/H/UYf2UsKTpw==", + "dependencies": { + "commander": "^2.9.0" + }, + "bin": { + "concat": "bin/concat" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/concat/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/concurrently": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.1.tgz", + "integrity": "sha512-nVraf3aXOpIcNud5pB9M82p1tynmZkrSGQ1p6X/VY8cJ+2LMVqAgXsJxYYefACSHbTYlm92O1xuhdGTjwoEvbQ==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha512-duS7VP5pvfsNLDvL1O4VOEbw37AI3A4ZUQYemvDlnpGrNu9tprR7BYWpDYwC0Xia0Zxz5ZupdiIrUp0GH1aXfg==", + "dependencies": { + "date-now": "^0.1.4" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "optional": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/contrast-color": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/contrast-color/-/contrast-color-1.0.1.tgz", + "integrity": "sha512-XeTV/LiyWrf/OWnODTqve2YGBfg32N6zlLqQjJKmEY+ffDqIfecgdmluVz7tky1D4VEaweZgoeRJJT87gDSDCQ==", + "engines": { + "npm": ">= 4.0.0" + } + }, + "node_modules/conventional-changelog-angular": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-6.0.0.tgz", + "integrity": "sha512-6qLgrBF4gueoC7AFVHu51nHL9pF9FRjXrH+ceVf7WmAfH3gs+gEYOkvxhjMPjZu57I4AGUGoNTY8V7Hrgf1uqg==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-6.1.0.tgz", + "integrity": "sha512-3cS3GEtR78zTfMzk0AizXKKIdN4OvSh7ibNz6/DPbhWWQu7LqE/8+/GqSodV+sywUR2gpJAdP/1JFf4XtN7Zpw==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/conventional-commits-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-4.0.0.tgz", + "integrity": "sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg==", + "dev": true, + "dependencies": { + "is-text-path": "^1.0.1", + "JSONStream": "^1.3.5", + "meow": "^8.1.2", + "split2": "^3.2.2" + }, + "bin": { + "conventional-commits-parser": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" + }, + "node_modules/cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", + "dev": true, + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz", + "integrity": "sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.7", + "glob-parent": "^6.0.1", + "globby": "^12.0.2", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 12.20.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/array-union": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", + "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", + "integrity": "sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==", + "dev": true, + "dependencies": { + "array-union": "^3.0.1", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.7", + "ignore": "^5.1.9", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true + }, + "node_modules/core-js-compat": { + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz", + "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==", + "dev": true, + "dependencies": { + "browserslist": "^4.25.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.4.0.tgz", + "integrity": "sha512-BabizFdC3wBHhbI4kJh0VkQP9GkBfoHPydD0COMce1nJ1kJAB3F2TmJ/I7diULBKtmEWSwEbuN/KDtgnmUUVmw==", + "dev": true, + "engines": { + "node": ">=v14.21.3" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=7", + "ts-node": ">=10", + "typescript": ">=4" + } + }, + "node_modules/cpu-features": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.19.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/cron-validator": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/cron-validator/-/cron-validator-1.3.1.tgz", + "integrity": "sha512-C1HsxuPCY/5opR55G5/WNzyEGDWFVG+6GLrA+fW/sCTcP6A6NTjUP2AK7B8n2PyFs90kDG2qzwm8LMheADku6A==" + }, + "node_modules/cronstrue": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/cronstrue/-/cronstrue-2.31.0.tgz", + "integrity": "sha512-A1cyfGyLSRdvT9MNn/pggoCTlvxnJyiCUItU9XHSXk89ZyK2YY9q9q7Rf4j8NhV9YwN1BjB0wimZlvKYb/9Bwg==", + "bin": { + "cronstrue": "bin/cli.js" + } + }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, + "node_modules/css-blank-pseudo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", + "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-blank-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-declaration-sorter": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", + "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-has-pseudo": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", + "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-has-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-in-js-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz", + "integrity": "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==", + "dependencies": { + "hyphenate-style-name": "^1.0.3" + } + }, + "node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", + "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "cssnano": "^6.0.1", + "jest-worker": "^29.4.3", + "postcss": "^8.4.24", + "schema-utils": "^4.0.1", + "serialize-javascript": "^6.0.1" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "lightningcss": { + "optional": true + } + } + }, + "node_modules/css-prefers-color-scheme": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", + "dev": true, + "bin": { + "css-prefers-color-scheme": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssdb": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-6.6.3.tgz", + "integrity": "sha512-7GDvDSmE+20+WcSMhP17Q1EVWUrLlbxxpMDqG731n8P99JhnQZHR9YvtjPvEHfjFUjvQJvdpKCjlKOX+xe4UVA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", + "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "dev": true, + "dependencies": { + "cssnano-preset-default": "^6.1.2", + "lilconfig": "^3.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-preset-default": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", + "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^4.0.2", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.1.0", + "postcss-convert-values": "^6.1.0", + "postcss-discard-comments": "^6.0.2", + "postcss-discard-duplicates": "^6.0.3", + "postcss-discard-empty": "^6.0.3", + "postcss-discard-overridden": "^6.0.2", + "postcss-merge-longhand": "^6.0.5", + "postcss-merge-rules": "^6.1.1", + "postcss-minify-font-values": "^6.1.0", + "postcss-minify-gradients": "^6.0.3", + "postcss-minify-params": "^6.1.0", + "postcss-minify-selectors": "^6.0.4", + "postcss-normalize-charset": "^6.0.2", + "postcss-normalize-display-values": "^6.0.2", + "postcss-normalize-positions": "^6.0.2", + "postcss-normalize-repeat-style": "^6.0.2", + "postcss-normalize-string": "^6.0.2", + "postcss-normalize-timing-functions": "^6.0.2", + "postcss-normalize-unicode": "^6.1.0", + "postcss-normalize-url": "^6.0.2", + "postcss-normalize-whitespace": "^6.0.2", + "postcss-ordered-values": "^6.0.2", + "postcss-reduce-initial": "^6.1.0", + "postcss-reduce-transforms": "^6.0.2", + "postcss-svgo": "^6.0.3", + "postcss-unique-selectors": "^6.0.4" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-utils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", + "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/csv-parse": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.6.0.tgz", + "integrity": "sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==" + }, + "node_modules/csv-reader": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/csv-reader/-/csv-reader-1.0.12.tgz", + "integrity": "sha512-0AAgazKJUywtjvZbclNuovIiQY/WyvojWw15Y2k3kPixE+pDiOFnfg5FcH3CfDqqnrB2f3p5oPAc446EXD01Tw==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csv-stringify": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.5.2.tgz", + "integrity": "sha512-RFPahj0sXcmUyjrObAK+DOWtMvMIFV328n4qZJhgX3x2RqkQgOTU2mCUmiFR0CzM6AzChlRSUErjiJeEt8BaQA==" + }, + "node_modules/cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==", + "dev": true + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "node_modules/dargs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", + "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha512-AsElvov3LoNB7tf5k37H2jYSB+ZZPMT5sG2QjJCcdlV5chIv6htBUBUui2IKRjgtKAKtCBN7Zbwa+MtwLjSeNw==" + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/dayjs": { + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", + "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decimal.js": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==" + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", + "dependencies": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "dependencies": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar/node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/decompress-tar/node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-tar/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/decompress-tar/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/decompress-tar/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/decompress-tar/node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "dependencies": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/decompress-tarbz2/node_modules/file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-tarbz2/node_modules/seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", + "dependencies": { + "commander": "^2.8.1" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "dependencies": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz/node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", + "dependencies": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip/node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip/node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip/node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/decompress/node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress/node_modules/make-dir/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress/node_modules/strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "dependencies": { + "is-natural-number": "^4.0.1" + } + }, + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-equal": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz", + "integrity": "sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.1", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.0", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-3.0.0.tgz", + "integrity": "sha512-RsqXDEAALjfRTro+IFNKpcPCt0/Cy2FqHSIlnomiJp9YGadpQnrtbRpSgN2+np21qHcIKiva4fiOQGjS9/qR/A==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "devOptional": true + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, + "node_modules/detect-port": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.6.1.tgz", + "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", + "dependencies": { + "address": "^1.0.1", + "debug": "4" + }, + "bin": { + "detect": "bin/detect-port.js", + "detect-port": "bin/detect-port.js" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/dev-null": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dev-null/-/dev-null-0.1.1.tgz", + "integrity": "sha512-nMNZG0zfMgmdv8S5O0TM5cpwNbGKRGPCxVsr0SmA3NZZy9CYBbuNLL0PD3Acx9e5LIUgwONXtM9kM6RlawPxEQ==" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==" + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dns-socket": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/dns-socket/-/dns-socket-4.2.2.tgz", + "integrity": "sha512-BDeBd8najI4/lS00HSKpdFia+OvUMytaVjfzR9n5Lq8MlZRSvtbI+uLtx1+XmQFls5wFU9dssccTmQQ6nfpjdg==", + "dependencies": { + "dns-packet": "^5.2.4" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/docusign-esign": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/docusign-esign/-/docusign-esign-8.1.0.tgz", + "integrity": "sha512-p+YgSlAv5OspREJT6NvgEZMR5L8j1SGMZRUM2NEVvdcW35dmklB3Kx2ia8SrKfq5/U9sTSEiSQJiphKQGBgU0w==", + "dependencies": { + "@devhigley/parse-proxy": "^1.0.3", + "axios": "^1.6.8", + "csv-stringify": "^1.0.0", + "jsonwebtoken": "^9.0.0", + "passport-oauth2": "^1.6.1", + "safe-buffer": "^5.1.2" + }, + "engines": { + "node": ">=2.2.1" + } + }, + "node_modules/docusign-esign/node_modules/csv-stringify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-1.1.2.tgz", + "integrity": "sha512-3NmNhhd+AkYs5YtM1GEh01VR6PKj6qch2ayfQaltx5xpcAdThjnbbI5eT8CzRVpXfGKAxnmrSYLsNl/4f3eWiw==", + "dependencies": { + "lodash.get": "~4.4.2" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/drip-nodejs": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/drip-nodejs/-/drip-nodejs-3.1.2.tgz", + "integrity": "sha512-XlLZcdMWp9uI5Owx/2u3F4ECYhWKtxOw+j4DTUKiwbRHjotLQy3m0SnGWJ1jLBMuT6R31ytmUrFHXG7cAbAJDQ==", + "dependencies": { + "axios": "^1.7.7" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/duplexify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/duplexify/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/duplexify/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ecc-jsbn/node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.178", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.178.tgz", + "integrity": "sha512-wObbz/ar3Bc6e4X5vf0iO8xTN8YAjN/tgiAOJLr7yjYFtP9wAjq8Mb5h0yn6kResir+VYx2DXBj9NNobs0ETSA==", + "dev": true + }, + "node_modules/elevenlabs": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/elevenlabs/-/elevenlabs-0.2.2.tgz", + "integrity": "sha512-e1Jud6QtilavgYny4w4RAD6bQFMk4v2vXsePZolyiEpKqtQXfq6Fz7LW6tJEg41R3oEacQGnqOWrKjlhFkjJkg==", + "deprecated": "This package has moved to @elevenlabs/elevenlabs-js", + "dependencies": { + "command-exists": "^1.2.9", + "execa": "^5.1.1", + "form-data": "4.0.0", + "node-fetch": "2.7.0", + "qs": "6.11.2", + "url-join": "4.0.1" + } + }, + "node_modules/embla-carousel": { + "version": "8.1.8", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.1.8.tgz", + "integrity": "sha512-KuHPA8qcAts6YE6ELtt38XYAb26hnKw8Ga0lSXmrhm1oI97t6oACFkqSsy33dfeZQEhaZB6VwWvaWQJRJVgSgA==" + }, + "node_modules/embla-carousel-react": { + "version": "8.1.8", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.1.8.tgz", + "integrity": "sha512-b8DcmC+j1vqVWSM6rU/GYGyY6Kp9LX8OoikZPBKmV6qL8s94sSPGl6jtDLLUtV8TTIQGMYOlOKUgoMAt/0TwOQ==", + "dependencies": { + "embla-carousel": "8.1.8", + "embla-carousel-reactive-utils": "8.1.8" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0" + } + }, + "node_modules/embla-carousel-reactive-utils": { + "version": "8.1.8", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.1.8.tgz", + "integrity": "sha512-bwV/23WD3Ecm0YuQ4I6Syzs3tdVJw0Oj3VCZlEODv1kH8LZ5kNDLgX2Uvx5brvoe2hpifBHPBQ8gYlxNL5kMPA==", + "peerDependencies": { + "embla-carousel": "8.1.8" + } + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "devOptional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding-japanese": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.0.0.tgz", + "integrity": "sha512-++P0RhebUC8MJAwJOsT93dT+5oc5oPImp1HubZpAuCZ5kTLnhuuBhKHj2jJeO/Gj93idPBWmIuQ9QWMe5rX3pQ==", + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz", + "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", + "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ensure-posix-path": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz", + "integrity": "sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw==", + "dev": true + }, + "node_modules/ent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.2.tgz", + "integrity": "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "punycode": "^1.4.1", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/envinfo": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", + "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eol": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/eol/-/eol-0.9.1.tgz", + "integrity": "sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==", + "dev": true + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-escape-html": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/es-escape-html/-/es-escape-html-0.1.1.tgz", + "integrity": "sha512-yUx1o+8RsG7UlszmYPtks+dm6Lho2m8lgHMOsLJQsFI0R8XwUJwiMhM1M4E/S8QLeGyf6MkDV/pWgjQ0tdTSyQ==", + "engines": { + "node": ">=12.x" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esbuild": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", + "@esbuild/darwin-x64": "0.25.0", + "@esbuild/freebsd-arm64": "0.25.0", + "@esbuild/freebsd-x64": "0.25.0", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz", + "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-alias": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-alias/-/eslint-import-resolver-alias-1.1.2.tgz", + "integrity": "sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==", + "dev": true, + "engines": { + "node": ">= 4" + }, + "peerDependencies": { + "eslint-plugin-import": ">=1.4.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", + "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "fast-glob": "^3.3.1", + "get-tsconfig": "^4.5.0", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import-x": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-0.5.2.tgz", + "integrity": "sha512-6f1YMmg3PdLwfiJDYnCRPfh67zJKbwbOKL99l6xGZDmIFkMht/4xyudafGEcDOmDlgp36e41W4RXDfOn7+pGRg==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^7.4.0", + "debug": "^4.3.4", + "doctrine": "^3.0.0", + "eslint-import-resolver-node": "^0.3.9", + "get-tsconfig": "^4.7.3", + "is-glob": "^4.0.3", + "minimatch": "^9.0.3", + "semver": "^7.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "eslint": "^8.56.0 || ^9.0.0-0" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-import/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import/node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/eslint-plugin-jest-dom": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest-dom/-/eslint-plugin-jest-dom-5.4.0.tgz", + "integrity": "sha512-yBqvFsnpS5Sybjoq61cJiUsenRkC9K32hYQBFS9doBR7nbQZZ5FyO+X7MlmfM1C48Ejx/qTuOCgukDUNyzKZ7A==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.16.3", + "requireindex": "^1.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0", + "npm": ">=6", + "yarn": ">=1" + }, + "peerDependencies": { + "@testing-library/dom": "^8.0.0 || ^9.0.0 || ^10.0.0", + "eslint": "^6.8.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "@testing-library/dom": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", + "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.20.7", + "aria-query": "^5.1.3", + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.6.2", + "axobject-query": "^3.1.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.3", + "language-tags": "=1.0.5", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.32.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", + "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-tailwindcss": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.17.4.tgz", + "integrity": "sha512-gJAEHmCq2XFfUP/+vwEfEJ9igrPeZFg+skeMtsxquSQdxba9XRk5bn0Bp9jxG1VV9/wwPKi1g3ZjItu6MIjhNg==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.5", + "postcss": "^8.4.4" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "tailwindcss": "^3.4.0" + } + }, + "node_modules/eslint-plugin-testing-library": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-6.2.2.tgz", + "integrity": "sha512-1E94YOTUDnOjSLyvOwmbVDzQi/WkKm3WVrMXu6SmBr6DN95xTGZmI6HJ/eOkSXh/DlheRsxaPsJvZByDBhWLVQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^5.58.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0", + "npm": ">=6" + }, + "peerDependencies": { + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-plugin-vitest": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.5.4.tgz", + "integrity": "sha512-um+odCkccAHU53WdKAw39MY61+1x990uXjSPguUCq3VcEHdqJrOb8OTMrbYlY6f9jAKx7x98kLVlIe3RJeJqoQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^7.7.1" + }, + "engines": { + "node": "^18.0.0 || >= 20.0.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "vitest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eta": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-3.5.0.tgz", + "integrity": "sha512-e3x3FBvGzeCIHhF+zhK8FZA2vC5uFn6b4HJjegUbIWrDb4mJ7JjTGMJY9VGIbRVpmSwHopNiaJibhjIr+HfLug==", + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "url": "https://github.com/eta-dev/eta?sponsor=1" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.2.tgz", + "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exifreader": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/exifreader/-/exifreader-4.20.0.tgz", + "integrity": "sha512-C28BhOHe5svd0Jj/5DGSIXD3PnPp46gfvHN4OkRfvHYZHkcJMhxeUxlwsgJ6Yl62zlZRtmfN+9suZFg0fv4hgg==", + "hasInstallScript": true, + "optionalDependencies": { + "@xmldom/xmldom": "^0.8.10" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ext-list": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "dev": true, + "dependencies": { + "mime-db": "^1.28.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "dev": true, + "dependencies": { + "ext-list": "^2.0.0", + "sort-keys-length": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/external-editor/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/external-editor/node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/extract-zip/node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-content-type-parse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", + "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "dev": true + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-equals": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", + "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-json-stringify": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.0.1.tgz", + "integrity": "sha512-s7SJE83QKBZwg54dIbD5rCtzOBVD43V1ReWXXYqBgwCwHLYAAT0RQc/FmrQglXqWPpz6omtryJQOau5jI4Nrvg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0", + "json-schema-ref-resolver": "^2.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, + "node_modules/fast-shallow-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz", + "integrity": "sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==" + }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastest-stable-stringify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz", + "integrity": "sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==" + }, + "node_modules/fastify": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.4.0.tgz", + "integrity": "sha512-I4dVlUe+WNQAhKSyv15w+dwUh2EPiEl4X2lGYMmNSgF83WzTMAPKGdWEv5tPsCQOb+SOZwz8Vlta2vF+OeDgRw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "@fastify/ajv-compiler": "^4.0.0", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^6.0.0", + "find-my-way": "^9.0.0", + "light-my-request": "^6.0.0", + "pino": "^9.0.0", + "process-warning": "^5.0.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastify-favicon": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fastify-favicon/-/fastify-favicon-5.0.0.tgz", + "integrity": "sha512-t+elngZ+o/Q3mR8btRqqGbCYNck5BmmW49iUCOw5ya0senmXBsSTKQKfrJlijG9e9YQi0VX2hlhWDYDRBUca+w==", + "dependencies": { + "fastify-plugin": "^5.0.0" + }, + "engines": { + "node": ">=20.9.0" + } + }, + "node_modules/fastify-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.0.1.tgz", + "integrity": "sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==" + }, + "node_modules/fastify-raw-body": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fastify-raw-body/-/fastify-raw-body-5.0.0.tgz", + "integrity": "sha512-2qfoaQ3BQDhZ1gtbkKZd6n0kKxJISJGM6u/skD9ljdWItAscjXrtZ1lnjr7PavmXX9j4EyCPmBDiIsLn07d5vA==", + "dependencies": { + "fastify-plugin": "^5.0.0", + "raw-body": "^3.0.0", + "secure-json-parse": "^2.4.0" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "url": "https://github.com/Eomm/fastify-raw-body?sponsor=1" + } + }, + "node_modules/fastify-socket": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/fastify-socket/-/fastify-socket-5.1.2.tgz", + "integrity": "sha512-eJf6Qu49rSLdEvOR2CtSh6XVLJMh+JaPqWcOqJmMKLlBYRzpToTfaTqkZaE95pa7/kRZWOibq8XJMn3NNwf3eg==", + "hasInstallScript": true, + "dependencies": { + "fastify-plugin": "^5.0.1", + "tslib": "^2.8.1" + }, + "peerDependencies": { + "fastify": ">=4", + "socket.io": ">=4" + } + }, + "node_modules/fastify-socket/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/fastify-xml-body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/fastify-xml-body-parser/-/fastify-xml-body-parser-2.2.0.tgz", + "integrity": "sha512-Jxltec0Iin4QX+DEQoYCyGmU5cTRtI0x22mRT/3FBQMhTEn7KNTHnnEtbyN3+6SLgW8cSirnOe1t8vqn77vR+Q==", + "dependencies": { + "fast-xml-parser": "^4.1.2", + "fastify-plugin": "^3.0.0" + } + }, + "node_modules/fastify-xml-body-parser/node_modules/fastify-plugin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.1.tgz", + "integrity": "sha512-qKcDXmuZadJqdTm6vlCqioEbyewF60b/0LOFCcYN1B6BIZGlYJumWWOYs70SFYLDAH4YqdE1cxH/RKMG7rFxgA==" + }, + "node_modules/fastify/node_modules/secure-json-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.0.0.tgz", + "integrity": "sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/favicons": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/favicons/-/favicons-7.2.0.tgz", + "integrity": "sha512-k/2rVBRIRzOeom3wI9jBPaSEvoTSQEW4iM0EveBmBBKFxO8mSyyRWtDlfC3VnEfu0avmjrMzy8/ZFPSe6F71Hw==", + "dependencies": { + "escape-html": "^1.0.3", + "sharp": "^0.33.1", + "xml2js": "^0.6.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fbemitter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-3.0.0.tgz", + "integrity": "sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==", + "dependencies": { + "fbjs": "^3.0.0" + } + }, + "node_modules/fbjs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", + "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", + "dependencies": { + "cross-fetch": "^3.1.5", + "fbjs-css-vars": "^1.0.0", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^1.0.35" + } + }, + "node_modules/fbjs-css-vars": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", + "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==" + }, + "node_modules/fbjs/node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, + "node_modules/feedparser": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/feedparser/-/feedparser-2.2.10.tgz", + "integrity": "sha512-WoAOooa61V8/xuKMi2pEtK86qQ3ZH/M72EEGdqlOTxxb3m6ve1NPvZcmPFs3wEDfcBbFLId2GqZ4YjsYi+h1xA==", + "dependencies": { + "addressparser": "^1.0.1", + "array-indexofobject": "~0.0.1", + "lodash.assign": "^4.2.0", + "lodash.get": "^4.4.2", + "lodash.has": "^4.5.2", + "lodash.uniq": "^4.5.0", + "mri": "^1.1.5", + "readable-stream": "^2.3.7", + "sax": "^1.2.4" + }, + "bin": { + "feedparser": "bin/feedparser.js" + }, + "engines": { + "node": ">= 10.18.1" + } + }, + "node_modules/feedparser/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/feedparser/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/feedparser/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/fetch-retry": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-6.0.0.tgz", + "integrity": "sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag==" + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/file-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/file-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/file-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/file-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/file-type": { + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-19.6.0.tgz", + "integrity": "sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==", + "dev": true, + "dependencies": { + "get-stream": "^9.0.1", + "strtok3": "^9.0.1", + "token-types": "^6.0.0", + "uint8array-extras": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/file-type/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-type/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/filename-reserved-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz", + "integrity": "sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/filenamify": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-6.0.0.tgz", + "integrity": "sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==", + "dev": true, + "dependencies": { + "filename-reserved-regex": "^3.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "dev": true, + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-file-up": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-2.0.1.tgz", + "integrity": "sha512-qVdaUhYO39zmh28/JLQM5CoYN9byEOKEH4qfa8K1eNV17W0UUMJ9WgbR/hHFH+t5rcl+6RTb5UC7ck/I+uRkpQ==", + "dev": true, + "dependencies": { + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-my-way": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.3.0.tgz", + "integrity": "sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/find-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-2.0.0.tgz", + "integrity": "sha512-WgZ+nKbELDa6N3i/9nrHeNznm+lY3z4YfhDDWgW+5P0pdmMj26bxaxU11ookgY3NyP9GC7HvZ9etp0jRFqGEeQ==", + "dev": true, + "dependencies": { + "find-file-up": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-versions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-5.1.0.tgz", + "integrity": "sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==", + "dev": true, + "dependencies": { + "semver-regex": "^4.0.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/firebase-scrypt": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/firebase-scrypt/-/firebase-scrypt-2.2.0.tgz", + "integrity": "sha512-36vJZVPFepErsNw+nBjb9cpM9wYPtcxk1bKN//vLdVkNPhaw1cogzwxtMs0s+dYg1gvBDakg2Q4ch8zAWAvnxA==", + "dependencies": { + "babel-runtime": "^6.26.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true + }, + "node_modules/flowtoken": { + "version": "1.0.40", + "resolved": "https://registry.npmjs.org/flowtoken/-/flowtoken-1.0.40.tgz", + "integrity": "sha512-DTntSiqdvMcEqg6dHni0kLcfmtJ6tYgm/Qembc1kJ4eEBDve75U2+lsxf4EwY8/hToAgt0zZ9i2fiKaKkxsX9w==", + "dependencies": { + "react-markdown": "^9.0.1", + "react-syntax-highlighter": "^15.5.0", + "regexp-tree": "^0.1.27", + "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.0" + } + }, + "node_modules/flowtoken/node_modules/react-syntax-highlighter": { + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz", + "integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "highlightjs-vue": "^1.0.0", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, + "node_modules/flux": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/flux/-/flux-4.0.4.tgz", + "integrity": "sha512-NCj3XlayA2UsapRpM7va6wU1+9rE5FIL7qoMcmxWHRzbp0yujihMBm9BBHZ1MDIk5h5o2Bl6eGiCe8rYELAmYw==", + "dependencies": { + "fbemitter": "^3.0.0", + "fbjs": "^3.0.1" + }, + "peerDependencies": { + "react": "^15.0.2 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/font-awesome": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", + "integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==", + "engines": { + "node": ">=0.10.3" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "7.2.13", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.13.tgz", + "integrity": "sha512-fR3WRkOb4bQdWB/y7ssDUlVdrclvwtyCUIHCfivAoYxq9dF7XfrDKbMdZIfwJ7hxIAqkYSGeU7lLJE6xrxIBdg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cosmiconfig": "^7.0.1", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">=12.13.0", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "typescript": ">3.6.0", + "vue-template-compiler": "*", + "webpack": "^5.11.0" + }, + "peerDependenciesMeta": { + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "12.15.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.15.0.tgz", + "integrity": "sha512-XKg/LnKExdLGugZrDILV7jZjI599785lDIJZLxMiiIFidCsy0a4R2ZEf+Izm67zyOuJgQYTHOmodi7igQsw3vg==", + "dependencies": { + "motion-dom": "^12.15.0", + "motion-utils": "^12.12.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/frimousse": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/frimousse/-/frimousse-0.2.0.tgz", + "integrity": "sha512-viSrsVQWKR4Q7xzC0lkx3Wu9i1+IHrth0QXn0nlIIJXpltwUnjkGXSTuoW7WHI5aJ4z49WR8E/pyQizFjlNtTA==", + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/front-matter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", + "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==", + "dev": true, + "dependencies": { + "js-yaml": "^3.13.1" + } + }, + "node_modules/front-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/front-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/front-matter/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-merger": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/fs-merger/-/fs-merger-3.2.1.tgz", + "integrity": "sha512-AN6sX12liy0JE7C2evclwoo0aCG3PFulLjrTLsJpWh/2mM+DinhpSGqYLbHBBbIW1PLRNcFhJG8Axtz8mQW3ug==", + "dev": true, + "dependencies": { + "broccoli-node-api": "^1.7.0", + "broccoli-node-info": "^2.1.0", + "fs-extra": "^8.0.1", + "fs-tree-diff": "^2.0.1", + "walk-sync": "^2.2.0" + } + }, + "node_modules/fs-merger/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-merger/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/fs-merger/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/fs-mkdirp-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", + "integrity": "sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.8", + "streamx": "^2.12.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", + "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", + "dev": true + }, + "node_modules/fs-tree-diff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fs-tree-diff/-/fs-tree-diff-2.0.1.tgz", + "integrity": "sha512-x+CfAZ/lJHQqwlD64pYM5QxWjzWhSjroaVsr8PW831zOApL55qPibed0c+xebaLWVr2BnHFoHdrwOv8pzt8R5A==", + "dev": true, + "dependencies": { + "@types/symlink-or-copy": "^1.2.0", + "heimdalljs-logger": "^0.1.7", + "object-assign": "^4.1.0", + "path-posix": "^1.0.0", + "symlink-or-copy": "^1.1.8" + }, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fuse.js": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz", + "integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==", + "engines": { + "node": ">=10" + } + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/gaxios": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gaxios/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gcd": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/gcd/-/gcd-0.0.1.tgz", + "integrity": "sha512-VNx3UEGr+ILJTiMs1+xc5SX1cMgJCrXezKPa003APUWNqQqaF6n25W8VcR7nHN6yRWbvvUTwCpZCFJeWC2kXlw==" + }, + "node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-them-args": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/get-them-args/-/get-them-args-1.3.2.tgz", + "integrity": "sha512-LRn8Jlk+DwZE4GTlDbT3Hikd1wSHgLMme/+7ddlqKd7ldwR6LjJgTVWzBnR01wnYGe4KgrXjg287RaI22UHmAw==", + "dev": true + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/get-uri": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/git-raw-commits": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", + "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", + "dev": true, + "dependencies": { + "dargs": "^7.0.0", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0" + }, + "bin": { + "git-raw-commits": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "node_modules/glob": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-stream": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-8.0.3.tgz", + "integrity": "sha512-fqZVj22LtFJkHODT+M4N1RJQ3TjnnQhfE9GwZI8qXscYarnhpip70poMldRnP8ipQ/w0B621kOhfc53/J9bd/A==", + "dev": true, + "dependencies": { + "@gulpjs/to-absolute-glob": "^4.0.0", + "anymatch": "^3.1.3", + "fastq": "^1.13.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "is-negated-glob": "^1.0.0", + "normalize-path": "^3.0.0", + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-stream/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==", + "dev": true, + "dependencies": { + "ini": "^1.3.4" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/global-dirs/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", + "dependencies": { + "delegate": "^3.1.2" + } + }, + "node_modules/google-auth-library": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz", + "integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.3.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-auth-library/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-auth-library/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "engines": { + "node": ">=14" + } + }, + "node_modules/google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "deprecated": "Package is no longer maintained", + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis": { + "version": "129.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-129.0.0.tgz", + "integrity": "sha512-gFatrzby+oh/GxEeMhJOKzgs9eG7yksRcTon9b+kPie4ZnDSgGQ85JgtUaBtLSBkcKpUKukdSP6Km1aCjs4y4Q==", + "dependencies": { + "google-auth-library": "^9.0.0", + "googleapis-common": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz", + "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.7.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common/node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis-common/node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis-common/node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis-common/node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/googleapis/node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis/node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis/node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis/node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", + "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/got/node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/gray-matter/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/gulp-sort": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gulp-sort/-/gulp-sort-2.0.0.tgz", + "integrity": "sha512-MyTel3FXOdh1qhw1yKhpimQrAmur9q1X0ZigLmCOxouQD+BD3za9/89O+HfbgBQvvh4igEbp0/PUWO+VqGYG1g==", + "dev": true, + "dependencies": { + "through2": "^2.0.1" + } + }, + "node_modules/gulp-sort/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/gulp-sort/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-sort/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-sort/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gunzip-maybe": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz", + "integrity": "sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==", + "dev": true, + "dependencies": { + "browserify-zlib": "^0.1.4", + "is-deflate": "^1.0.0", + "is-gzip": "^1.0.0", + "peek-stream": "^1.1.0", + "pumpify": "^1.3.3", + "through2": "^2.0.3" + }, + "bin": { + "gunzip-maybe": "bin.js" + } + }, + "node_modules/gunzip-maybe/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/gunzip-maybe/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gunzip-maybe/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gunzip-maybe/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/harmony-reflect": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", + "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "optional": true + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-dom": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", + "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==", + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^9.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-html": "^2.0.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/hast-util-from-html/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/hast-util-raw/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-mdast": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/hast-util-to-mdast/-/hast-util-to-mdast-10.1.2.tgz", + "integrity": "sha512-FiCRI7NmOvM4y+f5w32jPRzcxDIz+PUqDwEqn1A+1q2cdp3B8Gx7aVrXORdOKjMNDQsD1ogOr896+0jJHW1EFQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "hast-util-to-text": "^4.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "mdast-util-to-string": "^4.0.0", + "rehype-minify-whitespace": "^6.0.0", + "trim-trailing-lines": "^2.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5/node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/header-range-parser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/header-range-parser/-/header-range-parser-1.1.3.tgz", + "integrity": "sha512-B9zCFt3jH8g09LR1vHL4pcAn8yMEtlSlOUdQemzHMRKMImNIhhszdeosYFfNW0WXKQtXIlWB+O4owHJKvEJYaA==", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/heimdalljs": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/heimdalljs/-/heimdalljs-0.2.6.tgz", + "integrity": "sha512-o9bd30+5vLBvBtzCPwwGqpry2+n0Hi6H1+qwt6y+0kwRHGGF8TFIhJPmnuM0xO97zaKrDZMwO/V56fAnn8m/tA==", + "dev": true, + "dependencies": { + "rsvp": "~3.2.1" + } + }, + "node_modules/heimdalljs-logger": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/heimdalljs-logger/-/heimdalljs-logger-0.1.10.tgz", + "integrity": "sha512-pO++cJbhIufVI/fmB/u2Yty3KJD0TqNPecehFae0/eps0hkZ3b4Zc/PezUMOpYuHFQbA7FxHZxa305EhmjLj4g==", + "dev": true, + "dependencies": { + "debug": "^2.2.0", + "heimdalljs": "^0.2.6" + } + }, + "node_modules/heimdalljs-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/heimdalljs-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/heimdalljs/node_modules/rsvp": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.2.1.tgz", + "integrity": "sha512-Rf4YVNYpKjZ6ASAmibcwTNciQ5Co5Ztq6iZPEykHpkoflnD/K5ryE/rHehFsTm4NJj8nKDhbi3eKBWGogmNnkg==", + "dev": true + }, + "node_modules/help-me": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-4.2.0.tgz", + "integrity": "sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==", + "dev": true, + "dependencies": { + "glob": "^8.0.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/help-me/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/help-me/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/help-me/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/help-me/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/help-me/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "engines": { + "node": "*" + } + }, + "node_modules/highlightjs-vue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", + "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==" + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/html-to-text": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz", + "integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==", + "dependencies": { + "@selderee/plugin-htmlparser2": "^0.11.0", + "deepmerge": "^4.3.1", + "dom-serializer": "^2.0.0", + "htmlparser2": "^8.0.2", + "selderee": "^0.11.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", + "dev": true, + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-assert/node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", + "dev": true + }, + "node_modules/http-assert/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==" + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-middleware": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", + "integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.15", + "debug": "^4.3.6", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.3", + "is-plain-object": "^5.0.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/http-proxy/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-server/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/http-signature": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.18.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/http-status-codes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.2.0.tgz", + "integrity": "sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng==" + }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/husky": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "dev": true, + "bin": { + "husky": "lib/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "engines": { + "node": ">=10.18" + } + }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==" + }, + "node_modules/i18next": { + "version": "23.13.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.13.0.tgz", + "integrity": "sha512-B+g0/KTKmN3+NeMKPljQxdrih6Q6lyDF5O2e/Ofd0JQsTLojJD/BSTTN04iw6OVc0yBiHeypu5hoBNV6ag44Zw==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz", + "integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-http-backend": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.5.2.tgz", + "integrity": "sha512-+K8HbDfrvc1/2X8jpb7RLhI9ZxBDpx3xogYkQwGKlWAUXLSEGXzgdt3EcUjLlBCdMwdQY+K+EUF6oh8oB6rwHw==", + "dependencies": { + "cross-fetch": "4.0.0" + } + }, + "node_modules/i18next-icu": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/i18next-icu/-/i18next-icu-2.3.0.tgz", + "integrity": "sha512-x+j7kd5nDJCfbU53uwsMfXD7ALPu5uv0bqjAMQ5nVvXRoj1L7gkmswKtM3XDWYo4YUHf1jznlhSdPyy0xEwU+Q==", + "peerDependencies": { + "intl-messageformat": "^10.3.3" + } + }, + "node_modules/i18next-parser": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/i18next-parser/-/i18next-parser-9.3.0.tgz", + "integrity": "sha512-VaQqk/6nLzTFx1MDiCZFtzZXKKyBV6Dv0cJMFM/hOt4/BWHWRgYafzYfVQRUzotwUwjqeNCprWnutzD/YAGczg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.25.0", + "broccoli-plugin": "^4.0.7", + "cheerio": "^1.0.0", + "colors": "^1.4.0", + "commander": "^12.1.0", + "eol": "^0.9.1", + "esbuild": "^0.25.0", + "fs-extra": "^11.2.0", + "gulp-sort": "^2.0.0", + "i18next": "^23.5.1 || ^24.2.0", + "js-yaml": "^4.1.0", + "lilconfig": "^3.1.3", + "rsvp": "^4.8.5", + "sort-keys": "^5.0.0", + "typescript": "^5.0.4", + "vinyl": "^3.0.0", + "vinyl-fs": "^4.0.0" + }, + "bin": { + "i18next": "bin/cli.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || ^22.0.0", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/i18next-parser/node_modules/cheerio": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.0.tgz", + "integrity": "sha512-+0hMx9eYhJvWbgpKV9hN7jg0JcwydpopZE4hgi+KvQtByZXPp04NiCWU0LzcAbP63abZckIHkTQaXVF52mX3xQ==", + "dev": true, + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^10.0.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.10.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=18.17" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/i18next-parser/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/i18next-parser/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/i18next-parser/node_modules/htmlparser2": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, + "node_modules/i18next-parser/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/identity-obj-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", + "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", + "dev": true, + "dependencies": { + "harmony-reflect": "^1.4.6" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/imapflow": { + "version": "1.0.152", + "resolved": "https://registry.npmjs.org/imapflow/-/imapflow-1.0.152.tgz", + "integrity": "sha512-FzGKOUhf486/luvdCi10PrhiszQTTw/3LDFoqfVNK1XvXmju5JKt17iCi9D1baDTM1rd3CqWmGBCes569XRJOw==", + "dependencies": { + "encoding-japanese": "2.0.0", + "iconv-lite": "0.6.3", + "libbase64": "1.2.1", + "libmime": "5.2.1", + "libqp": "2.0.1", + "mailsplit": "5.4.0", + "nodemailer": "6.9.9", + "pino": "8.18.0", + "socks": "2.7.3" + } + }, + "node_modules/imapflow/node_modules/pino": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.18.0.tgz", + "integrity": "sha512-Mz/gKiRyuXu4HnpHgi1YWdHQCoWMufapzooisvFn78zl4dZciAxS+YeRkUxXl1ee/SzU80YCz1zpECCh4oC6Aw==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "v1.1.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^3.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.7.0", + "thread-stream": "^2.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/imapflow/node_modules/pino-abstract-transport": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz", + "integrity": "sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/imapflow/node_modules/pino-std-serializers": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" + }, + "node_modules/imapflow/node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" + }, + "node_modules/imapflow/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/imapflow/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/imapflow/node_modules/sonic-boom": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", + "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/imapflow/node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/imapflow/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/imapflow/node_modules/thread-stream": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", + "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, + "node_modules/immutable": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", + "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "optional": true + }, + "node_modules/inflection": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-3.0.2.tgz", + "integrity": "sha512-+Bg3+kg+J6JUWn8J6bzFmOWkTQ6L/NHfDRSYU+EVvuKHDxUDHAXgqixHfVlzuBQaPOTac8hn43aPhMNk6rMe3g==", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==" + }, + "node_modules/inline-style-prefixer": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz", + "integrity": "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==", + "dependencies": { + "css-in-js-utils": "^3.1.0" + } + }, + "node_modules/inquirer": { + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", + "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inspect-with-kind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/inspect-with-kind/-/inspect-with-kind-1.0.5.tgz", + "integrity": "sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + } + }, + "node_modules/intercom-client": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/intercom-client/-/intercom-client-6.0.0.tgz", + "integrity": "sha512-gd1msd9cmg56Fz4HN1A/G4gD7czBlR6CV8YXPJbTPkB+kkifKKCwzZNyBgYnFD5KCY124jTToFiGrKmnO8gG1w==", + "dependencies": { + "form-data": "^4.0.0", + "formdata-node": "^6.0.3", + "js-base64": "3.7.2", + "node-fetch": "2.7.0", + "qs": "6.11.2", + "readable-stream": "^4.5.2", + "url-join": "4.0.1" + } + }, + "node_modules/intercom-client/node_modules/formdata-node": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-6.0.3.tgz", + "integrity": "sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/intercom-client/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/intercom-client/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/intercom-client/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/intl-messageformat": { + "version": "10.5.14", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.14.tgz", + "integrity": "sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/fast-memoize": "2.2.0", + "@formatjs/icu-messageformat-parser": "2.7.8", + "tslib": "^2.4.0" + } + }, + "node_modules/ioredis": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz", + "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==", + "dependencies": { + "@ioredis/commands": "^1.1.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-absolute-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-4.0.1.tgz", + "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-base64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-base64/-/is-base64-1.1.0.tgz", + "integrity": "sha512-Nlhg7Z2dVC4/PTvIFkgVVNvPHSO2eR/Yd0XzhGiXCXEvWnptXlXa/clQ8aePPiMuxEGcWfzWbGw2Fe3d+Y3v1g==", + "bin": { + "is_base64": "bin/is-base64", + "is-base64": "bin/is-base64" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-deflate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-deflate/-/is-deflate-1.0.0.tgz", + "integrity": "sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==", + "dev": true + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==" + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-gzip": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz", + "integrity": "sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-ip": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", + "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", + "dependencies": { + "ip-regex": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "optional": true + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==" + }, + "node_modules/is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-online": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/is-online/-/is-online-10.0.0.tgz", + "integrity": "sha512-WCPdKwNDjXJJmUubf2VHLMDBkUZEtuOvpXUfUnUFbEnM6In9ByiScL4f4jKACz/fsb2qDkesFerW3snf/AYz3A==", + "dependencies": { + "got": "^12.1.0", + "p-any": "^4.0.0", + "p-timeout": "^5.1.0", + "public-ip": "^5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-online/node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/is-online/node_modules/got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-retry-allowed": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-text-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", + "dev": true, + "dependencies": { + "text-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true + }, + "node_modules/is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is2": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", + "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "ip-regex": "^4.1.0", + "is-url": "^1.2.4" + }, + "engines": { + "node": ">=v0.10.0" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isolated-vm": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/isolated-vm/-/isolated-vm-5.0.1.tgz", + "integrity": "sha512-hs7+ff59Z2zDvavfcjuot/r1gm6Bmpt+GoZxmVfxUmXaX5scOvUq/Rnme+mUtSh5lW41hH8gAuvk/yTJDYO8Fg==", + "hasInstallScript": true, + "dependencies": { + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "dev": true, + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==" + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-circus/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-config/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jest-config/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-config/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-environment-jsdom": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", + "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0", + "jsdom": "^20.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-jsdom/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-jsdom/node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "node_modules/jest-environment-jsdom/node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-environment-jsdom/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/jest-environment-jsdom/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-environment-jsdom/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-environment-jsdom/node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-jsdom/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/jest-environment-jsdom/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/jest-environment-jsdom/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-environment-jsdom/node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jest-environment-jsdom/node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-environment-jsdom/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-environment-jsdom/node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-leak-detector/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-runner/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/jose": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-base64": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz", + "integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==" + }, + "node_modules/js-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz", + "integrity": "sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/js-md4": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "node_modules/jsdom": { + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-23.0.1.tgz", + "integrity": "sha512-2i27vgvlUsGEBO9+/kJQRbtqtm+191b5zAZrU/UezVmnC2dlDAFLgDYJvAEi94T4kjsRKkezEtLQTgsNEsW2lQ==", + "dependencies": { + "cssstyle": "^3.0.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.7", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.3", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.14.2", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsdom/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsdom/node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jshint": { + "version": "2.13.6", + "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.6.tgz", + "integrity": "sha512-IVdB4G0NTTeQZrBoM8C5JFVLjV2KtZ9APgybDA1MK73xb09qFs0jCXyQLnCOp1cSZZZbvhq/6mfXHUTaDkffuQ==", + "dependencies": { + "cli": "~1.0.0", + "console-browserify": "1.1.x", + "exit": "0.1.x", + "htmlparser2": "3.8.x", + "lodash": "~4.17.21", + "minimatch": "~3.0.2", + "strip-json-comments": "1.0.x" + }, + "bin": { + "jshint": "bin/jshint" + } + }, + "node_modules/jshint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jshint/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/jshint/node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/jshint/node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/jshint/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "node_modules/jshint/node_modules/domhandler": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha512-q9bUwjfp7Eif8jWxxxPSykdRZAb6GkguBGSgvvCrhI9wB71W2K/Kvv4E61CF/mcCfnVJDeDWx/Vb/uAqbDj6UQ==", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/jshint/node_modules/domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/jshint/node_modules/entities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ==" + }, + "node_modules/jshint/node_modules/htmlparser2": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha512-hBxEg3CYXe+rPIua8ETe7tmG3XDn9B0edOE/e9wH2nLczxzgdu0m0aNHY+5wFZiviLWLdANPJTssa92dMcXQ5Q==", + "dependencies": { + "domelementtype": "1", + "domhandler": "2.3", + "domutils": "1.5", + "entities": "1.0", + "readable-stream": "1.1" + } + }, + "node_modules/jshint/node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jshint/node_modules/strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha512-AOPG8EBc5wAikaG1/7uFCNFJwnKOuQwFTpYBdTW6OvWHeZBQBrAA/amefHGrEiOnCPcLFZK6FUPtWVKpQVIRgg==", + "bin": { + "strip-json-comments": "cli.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-ref-resolver": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-2.0.1.tgz", + "integrity": "sha512-HG0SIB9X4J8bwbxCbnd5FfPEbcXAJYTi1pBJeP/QPON+w8ovSME8iRG+ElHNxZNX2Qh6eYn1GdzJFS4cDFfx0Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/json-schema-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-resolver/-/json-schema-resolver-3.0.0.tgz", + "integrity": "sha512-HqMnbz0tz2DaEJ3ntsqtx3ezzZyDE7G56A/pPY/NGmrPu76UzsWquOpHFRAf5beTNXoH2LU5cQePVvRli1nchA==", + "dependencies": { + "debug": "^4.1.1", + "fast-uri": "^3.0.5", + "rfdc": "^1.1.4" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/Eomm/json-schema-resolver?sponsor=1" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/json-server": { + "version": "1.0.0-beta.0", + "resolved": "https://registry.npmjs.org/json-server/-/json-server-1.0.0-beta.0.tgz", + "integrity": "sha512-6LMP/lfigyAigG4NG8GM5Wf+OmCv/5RpyBJLOPEfODB1z+VeHNsnJQvkXz+E6w3j6cbYFo+uSwO2nXXXCqZubQ==", + "dependencies": { + "@tinyhttp/app": "^2.2.3", + "@tinyhttp/cors": "^2.0.0", + "chalk": "^5.3.0", + "chokidar": "^3.5.3", + "dot-prop": "^8.0.2", + "eta": "^3.2.0", + "inflection": "^3.0.0", + "json5": "^2.2.3", + "lowdb": "^7.0.1", + "milliparsec": "^2.3.0", + "sirv": "^2.0.4", + "sort-on": "^6.0.0" + }, + "bin": { + "json-server": "lib/bin.js" + }, + "engines": { + "node": ">=18.3" + } + }, + "node_modules/json-server/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/json-server/node_modules/dot-prop": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-8.0.2.tgz", + "integrity": "sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ==", + "dependencies": { + "type-fest": "^3.8.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/json-server/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/json-source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/json-source-map/-/json-source-map-0.6.1.tgz", + "integrity": "sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "node_modules/json-to-pretty-yaml": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/json-to-pretty-yaml/-/json-to-pretty-yaml-1.2.2.tgz", + "integrity": "sha512-rvm6hunfCcqegwYaG5T4yKJWxc9FXFgBVrcTZ4XfSVRwa5HA/Xs+vB/Eo9treYYHCeNM0nrSUr82V/M31Urc7A==", + "dependencies": { + "remedial": "^1.0.7", + "remove-trailing-spaces": "^1.0.6" + }, + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/json2xml": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/json2xml/-/json2xml-0.1.3.tgz", + "integrity": "sha512-yfTe9HnbrE3oRUEQL9mn7BueLd7RCTb7ig/mAFI6xY4RNYOEXF26x0qHFR7mb8ZrKgfE57wxkq0N3TboyFm6UA==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-eslint-parser": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.0.tgz", + "integrity": "sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==", + "dev": true, + "dependencies": { + "acorn": "^8.5.0", + "eslint-visitor-keys": "^3.0.0", + "espree": "^9.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/jsondiffpatch": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz", + "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==", + "dependencies": { + "@types/diff-match-patch": "^1.0.36", + "chalk": "^5.3.0", + "diff-match-patch": "^1.0.5" + }, + "bin": { + "jsondiffpatch": "bin/jsondiffpatch.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/jsondiffpatch/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jsoneditor": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/jsoneditor/-/jsoneditor-10.0.3.tgz", + "integrity": "sha512-dCEtrms+zsIQjDZC5Yo6FBWNvNlXSR2nZ5aprEjawAD689CI8/qqv/Ytj0wPqcz6H1p1ebFfM/wkf8L1wrWk5w==", + "dependencies": { + "ace-builds": "^1.33.1", + "ajv": "^6.12.6", + "javascript-natural-sort": "^0.7.1", + "jmespath": "^0.16.0", + "json-source-map": "^0.6.1", + "jsonrepair": "^3.7.0", + "mobius1-selectr": "^2.4.13", + "picomodal": "^3.0.0", + "vanilla-picker": "^2.12.3" + } + }, + "node_modules/jsoneditor/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/jsoneditor/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/jsoneditor/node_modules/jsonrepair": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.12.0.tgz", + "integrity": "sha512-SWfjz8SuQ0wZjwsxtSJ3Zy8vvLg6aO/kxcp9TWNPGwJKgTZVfhNEQBMk/vPOpYCDFWRxD6QWuI6IHR1t615f0w==", + "bin": { + "jsonrepair": "bin/cli.js" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonlint-mod": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/jsonlint-mod/-/jsonlint-mod-1.7.6.tgz", + "integrity": "sha512-oGuk6E1ehmIpw0w9ttgb2KsDQQgGXBzZczREW8OfxEm9eCQYL9/LCexSnh++0z3AiYGcXpBgqDSx9AAgzl/Bvg==", + "dependencies": { + "chalk": "^2.4.2", + "JSV": "^4.0.2", + "underscore": "^1.9.1" + }, + "bin": { + "jsonlint": "lib/cli.js" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/jsonlint-mod/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jsonlint-mod/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jsonlint-mod/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/jsonlint-mod/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/jsonlint-mod/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/jsonlint-mod/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/jsonlint-mod/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jsonrepair": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.2.0.tgz", + "integrity": "sha512-6eHBc2z5vipym4S8rzTcCXQBLWpkSzi9bk7I3xTdUxRzXyYvfjoVZzJ97N4C/9vcKI9NgNp3slPwHufDr0rFYw==", + "bin": { + "jsonrepair": "bin/cli.js" + } + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", + "integrity": "sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==", + "dependencies": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "node_modules/JSV": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", + "integrity": "sha512-ZJ6wx9xaKJ3yFUhq5/sk82PJMuUyLk277I8mQeyDgCTjGdjWJIvPfaU5LIXaMuaN2UO1X3kZH4+lgphublZUHw==", + "engines": { + "node": "*" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/jszip/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.1.0.tgz", + "integrity": "sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==", + "dependencies": { + "@types/express": "^4.17.17", + "@types/jsonwebtoken": "^9.0.2", + "debug": "^4.3.4", + "jose": "^4.14.6", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jwks-rsa/node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, + "node_modules/katex": { + "version": "0.16.22", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", + "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dev": true, + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kill-port": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/kill-port/-/kill-port-1.6.1.tgz", + "integrity": "sha512-un0Y55cOM7JKGaLnGja28T38tDDop0AQ8N0KlAdyh+B1nmMoX8AnNmqPNZbS3mUMgiST51DCVqmbFT1gNJpVNw==", + "dev": true, + "dependencies": { + "get-them-args": "1.3.2", + "shell-exec": "1.0.2" + }, + "bin": { + "kill-port": "cli.js" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/koa": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.4.tgz", + "integrity": "sha512-7fNBIdrU2PEgLljXoPWoyY4r1e+ToWCmzS/wwMPbUNs7X+5MMET1ObhJBlUkF5uZG9B6QhM2zS1TsH6adegkiQ==", + "dev": true, + "dependencies": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.9.0", + "debug": "^4.3.2", + "delegates": "^1.0.0", + "depd": "^2.0.0", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^2.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "engines": { + "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + } + }, + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true + }, + "node_modules/koa-convert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", + "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", + "dev": true, + "dependencies": { + "co": "^4.6.0", + "koa-compose": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/koa/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/koa/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "dev": true, + "dependencies": { + "language-subtag-registry": "~0.3.2" + } + }, + "node_modules/launch-editor": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", + "integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/lcm": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/lcm/-/lcm-0.0.3.tgz", + "integrity": "sha512-TB+ZjoillV6B26Vspf9l2L/vKaRY/4ep3hahcyVkCGFgsTNRUQdc24bQeNFiZeoxH0vr5+7SfNRMQuPHv/1IrQ==", + "dependencies": { + "gcd": "^0.0.1" + } + }, + "node_modules/leac": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz", + "integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==", + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/lead": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-4.0.0.tgz", + "integrity": "sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/less": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", + "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less-loader": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.1.0.tgz", + "integrity": "sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug==", + "dev": true, + "dependencies": { + "klona": "^2.0.4" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + } + }, + "node_modules/less/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/less/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/less/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/libbase64": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.2.1.tgz", + "integrity": "sha512-l+nePcPbIG1fNlqMzrh68MLkX/gTxk/+vdvAb388Ssi7UuUN31MI44w4Yf33mM3Cm4xDfw48mdf3rkdHszLNew==" + }, + "node_modules/libmime": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.2.1.tgz", + "integrity": "sha512-A0z9O4+5q+ZTj7QwNe/Juy1KARNb4WaviO4mYeFC4b8dBT2EEqK2pkM+GC8MVnkOjqhl5nYQxRgnPYRRTNmuSQ==", + "dependencies": { + "encoding-japanese": "2.0.0", + "iconv-lite": "0.6.3", + "libbase64": "1.2.1", + "libqp": "2.0.1" + } + }, + "node_modules/libqp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/libqp/-/libqp-2.0.1.tgz", + "integrity": "sha512-Ka0eC5LkF3IPNQHJmYBWljJsw0UvM6j+QdKRbWyCdTmYwvIDE6a7bCm0UkTAL/K+3KXK5qXT/ClcInU01OpdLg==" + }, + "node_modules/license-webpack-plugin": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", + "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "dev": true, + "dependencies": { + "webpack-sources": "^3.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/light-my-request/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "engines": { + "node": ">=18" + } + }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/lines-and-columns": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz", + "integrity": "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/lint-staged": { + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.9.tgz", + "integrity": "sha512-BZAt8Lk3sEnxw7tfxM7jeZlPRuT4M68O0/CwZhhaw6eeWu0Lz5eERE3m386InivXB64fp/mDID452h48tvKlRQ==", + "dev": true, + "dependencies": { + "chalk": "~5.3.0", + "commander": "~12.1.0", + "debug": "~4.3.6", + "execa": "~8.0.1", + "lilconfig": "~3.1.2", + "listr2": "~8.2.4", + "micromatch": "~4.0.7", + "pidtree": "~0.6.0", + "string-argv": "~0.3.2", + "yaml": "~2.5.0" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/lint-staged/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/lint-staged/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/lint-staged/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/lint-staged/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/yaml": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/listr2": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", + "integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "dependencies": { + "lie": "3.1.1" + } + }, + "node_modules/localforage/node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lockfile": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", + "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", + "dev": true, + "dependencies": { + "signal-exit": "^3.0.2" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw==" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lodash.clonedeepwith": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeepwith/-/lodash.clonedeepwith-4.5.0.tgz", + "integrity": "sha512-QRBRSxhbtsX1nc0baxSkkK5WlVTTm/s48DSukcGcWZwIyI8Zz+lB+kFiELJXtzfH4Aj6kMWQ1VWW4U5uUDgZMA==", + "dev": true + }, + "node_modules/lodash.curry": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", + "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, + "node_modules/lodash.flow": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", + "integrity": "sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead." + }, + "node_modules/lodash.has": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", + "integrity": "sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g==" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "dev": true + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true + }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "dev": true + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "dev": true + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "dev": true + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + }, + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==", + "dev": true + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lottie-web": { + "version": "5.12.2", + "resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.12.2.tgz", + "integrity": "sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg==" + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lowdb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-7.0.1.tgz", + "integrity": "sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==", + "dependencies": { + "steno": "^4.0.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lru-memoizer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/lucide-react": { + "version": "0.407.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.407.0.tgz", + "integrity": "sha512-+dRIu9Sry+E8wPF9+sY5eKld2omrU4X5IKXxrgqBt+o11IIHVU0QOfNoVWFuj0ZRDrxr4Wci26o2mKZqLGE0lA==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/luxon": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz", + "integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/mailparser": { + "version": "3.6.7", + "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.6.7.tgz", + "integrity": "sha512-/3x8HW70DNehw+3vdOPKdlLuxOHoWcGB5jfx5vJ5XUbY9/2jUJbrrhda5Si8Dj/3w08U0y5uGAkqs5+SPTPKoA==", + "dependencies": { + "encoding-japanese": "2.0.0", + "he": "1.2.0", + "html-to-text": "9.0.5", + "iconv-lite": "0.6.3", + "libmime": "5.2.1", + "linkify-it": "5.0.0", + "mailsplit": "5.4.0", + "nodemailer": "6.9.9", + "tlds": "1.248.0" + } + }, + "node_modules/mailsplit": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/mailsplit/-/mailsplit-5.4.0.tgz", + "integrity": "sha512-wnYxX5D5qymGIPYLwnp6h8n1+6P6vz/MJn5AzGjZ8pwICWssL+CCQjWBIToOVHASmATot4ktvlLo6CyLfOXWYA==", + "dependencies": { + "libbase64": "1.2.1", + "libmime": "5.2.0", + "libqp": "2.0.1" + } + }, + "node_modules/mailsplit/node_modules/libmime": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.2.0.tgz", + "integrity": "sha512-X2U5Wx0YmK0rXFbk67ASMeqYIkZ6E5vY7pNWRKtnNzqjvdYYG8xtPDpCnuUEnPU9vlgNev+JoSrcaKSUaNvfsw==", + "dependencies": { + "encoding-japanese": "2.0.0", + "iconv-lite": "0.6.3", + "libbase64": "1.2.1", + "libqp": "2.0.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/make-fetch-happen/node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "dependencies": { + "repeat-string": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/matcher-collection": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/matcher-collection/-/matcher-collection-2.0.1.tgz", + "integrity": "sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ==", + "dev": true, + "dependencies": { + "@types/minimatch": "^3.0.3", + "minimatch": "^3.0.2" + }, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/matcher-collection/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/matcher-collection/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-frontmatter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", + "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "escape-string-regexp": "^5.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "longest-streak": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, + "node_modules/meow": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-frontmatter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "dependencies": { + "fault": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-frontmatter/node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/milliparsec": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/milliparsec/-/milliparsec-2.3.0.tgz", + "integrity": "sha512-b+6KYJw+DwQjk24qCUuq+lZvRXDpXJ02qsllKgKaDurHpQ0v7D5op9VAkdYM/pXRhFeh7uLYHmnwFnYvdXGa3A==", + "engines": { + "node": ">=12.4" + } + }, + "node_modules/mime": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.1.tgz", + "integrity": "sha512-5lZ5tyrIfliMXzFtkYyekWbtRXObT9OWa8IwQ5uxTBDHucNNwniRqo0yInflj+iYi5CBa6qxadGzGarDfuEOxA==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.4.7.tgz", + "integrity": "sha512-euWmddf0sk9Nv1O0gfeeUAvAkoSlWncNLF77C0TP2+WoPvy8mAHKOzMajcCz2dzvyt3CNgxb1obIEVFIRxaipg==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/minimist-options/node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-fetch/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/mintlify": { + "version": "4.0.395", + "resolved": "https://registry.npmjs.org/mintlify/-/mintlify-4.0.395.tgz", + "integrity": "sha512-gFReDFjWgTIWTw55Naefoe7r802uaPIytJWXoIhfFG/zsji1LD3FYWLIhTO+/5V6ioTEIN7WVLgdzl+YcDkNCg==", + "dependencies": { + "@mintlify/cli": "4.0.395" + }, + "bin": { + "mintlify": "index.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/mktemp": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/mktemp/-/mktemp-0.4.0.tgz", + "integrity": "sha512-IXnMcJ6ZyTuhRmJSjzvHSRhlVPiN9Jwc6e59V0bEJ0ba6OBeX2L0E+mRN1QseeOF4mM+F1Rit6Nh7o+rl2Yn/A==", + "dev": true, + "engines": { + "node": ">0.9" + } + }, + "node_modules/mobius1-selectr": { + "version": "2.4.13", + "resolved": "https://registry.npmjs.org/mobius1-selectr/-/mobius1-selectr-2.4.13.tgz", + "integrity": "sha512-Mk9qDrvU44UUL0EBhbAA1phfQZ7aMZPjwtL7wkpiBzGh8dETGqfsh50mWoX9EkjDlkONlErWXArHCKfoxVg0Bw==" + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.48", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", + "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/monday-sdk-js": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/monday-sdk-js/-/monday-sdk-js-0.5.2.tgz", + "integrity": "sha512-ZxlfnuogPN966e3OcRuZmML/jedhLZTW0h6G00alXejTzKS7avR6z9npco8b++up6Cx0gJfyaQ9cXTTM6+sU1Q==", + "dependencies": { + "node-fetch": "^2.6.0" + } + }, + "node_modules/mongodb": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.15.0.tgz", + "integrity": "sha512-ifBhQ0rRzHDzqp9jAQP6OwHSH7dbYIQjD3SbJs9YYk9AikKEettW/9s/tbSFDTpXcRbF+u1aLrhHxDFaYtZpFQ==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.3", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/motion": { + "version": "12.16.0", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.16.0.tgz", + "integrity": "sha512-P3HA83fnPMEGBLfKdD5vDdjH1Aa3wM3jT3+HX3fCVpy/4/lJiqvABajLgZenBu+rzkFzmeaPkvT7ouf9Tq5tVQ==", + "dependencies": { + "framer-motion": "^12.16.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "12.20.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.20.1.tgz", + "integrity": "sha512-XyveLJ9dmQTmaEsP9RlcuoNFxWlRIGdasdPJBB4aOwPr8bRcJdhltudAbiEjRQBmsGD30sjJdaEjhkHsAHapLQ==", + "dependencies": { + "motion-utils": "^12.19.0" + } + }, + "node_modules/motion-utils": { + "version": "12.19.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.19.0.tgz", + "integrity": "sha512-BuFTHINYmV07pdWs6lj6aI63vr2N4dg0vR+td0rtrdpWOhBzIkEklZyLcvKBoEtwSqx8Jg06vUB5RS0xDiUybw==" + }, + "node_modules/motion/node_modules/framer-motion": { + "version": "12.20.1", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.20.1.tgz", + "integrity": "sha512-NW2t2GHQcNvLHq18JyNVY15VKrwru+nkNyhLdqf4MbxbGhxZcSDi68iNcAy6O1nG0yYAQJbLioBIH1Kmg8Xr1g==", + "dependencies": { + "motion-dom": "^12.20.1", + "motion-utils": "^12.19.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/msgpackr": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.4.tgz", + "integrity": "sha512-uaff7RG9VIC4jacFW9xzL3jc0iM32DNHe4jYVycBcjUePT/Klnfj7pqtWJt9khvDFizmjN2TlYniYmSS2LIaZg==", + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node_modules/mysql": { + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", + "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "dependencies": { + "bignumber.js": "9.0.0", + "readable-stream": "2.3.7", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mysql/node_modules/bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", + "engines": { + "node": "*" + } + }, + "node_modules/mysql/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/mysql/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/mysql/node_modules/sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mysql/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nan": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", + "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==", + "optional": true + }, + "node_modules/nano-css": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/nano-css/-/nano-css-5.6.2.tgz", + "integrity": "sha512-+6bHaC8dSDGALM1HJjOHVXpuastdu2xFoZlC77Jh4cg+33Zcgm+Gxd+1xsnpZK14eyHObSp82+ll5y3SX75liw==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "css-tree": "^1.1.2", + "csstype": "^3.1.2", + "fastest-stable-stringify": "^2.0.2", + "inline-style-prefixer": "^7.0.1", + "rtl-css-js": "^1.16.1", + "stacktrace-js": "^2.0.2", + "stylis": "^4.3.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/new-date": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/new-date/-/new-date-1.0.3.tgz", + "integrity": "sha512-0fsVvQPbo2I18DT2zVHpezmeeNYV2JaJSrseiHLc17GNOxJzUdx5mvSigPu8LtIfZSij5i1wXnXFspEs2CD6hA==", + "dependencies": { + "@segment/isodate": "1.0.3" + } + }, + "node_modules/next-mdx-remote-client": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/next-mdx-remote-client/-/next-mdx-remote-client-1.1.1.tgz", + "integrity": "sha512-cJnJGaRiHc1gn4aCzDmY9zmcCjEw+zMCpCYIy45Kjs8HzeQpdGcaO5GrgIcX/DFkuCVrrzc69wi2gGnExXbv/A==", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@mdx-js/mdx": "^3.1.0", + "@mdx-js/react": "^3.1.0", + "remark-mdx-remove-esm": "^1.1.0", + "serialize-error": "^12.0.0", + "vfile": "^6.0.3", + "vfile-matter": "^5.0.1" + }, + "engines": { + "node": ">=18.18.0" + }, + "peerDependencies": { + "react": ">= 18.3.0 < 19.0.0", + "react-dom": ">= 18.3.0 < 19.0.0" + } + }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", + "dependencies": { + "@types/nlcst": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-abi": { + "version": "3.75.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", + "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" + }, + "node_modules/node-addon-api": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.4.0.tgz", + "integrity": "sha512-D9DI/gXHvVmjHS08SVch0Em8G5S1P+QWtU31appcKT/8wFSPRcdHadIFSAntdMMVM5zz+/DL+bL/gz3UDppqtg==", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-gyp/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-machine-id": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", + "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true + }, + "node_modules/node-rsa": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz", + "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==", + "dependencies": { + "asn1": "^0.2.4" + } + }, + "node_modules/node-schedule": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", + "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "dev": true, + "dependencies": { + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/node-xmllint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-xmllint/-/node-xmllint-1.0.0.tgz", + "integrity": "sha512-71UV2HRUP+djvHpdyatiuv+Y1o8hI4ZI7bMfuuoACMLR1JJCErM4WXAclNeHd6BgHXkqeqnnAk3wpDkSQWmFXw==" + }, + "node_modules/nodemailer": { + "version": "6.9.9", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.9.tgz", + "integrity": "sha512-dexTll8zqQoVJEZPwQAKzxxtFn0qTnjdQTchoU6Re9BUUGBJiOy3YMn/0ShTW6J5M0dfQ1NeDeRTTl4oIWgQMA==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.2.tgz", + "integrity": "sha512-Ee/R3SyN4BuynXcnTaekmaVdbDAEiNrHqjQIA37mHU8G9pf7aaAD4ZX3XjBLo6rsdcxA/gtkcNYZLt30ACgynw==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/notepack.io": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-3.0.1.tgz", + "integrity": "sha512-TKC/8zH5pXIAMVQio2TvVDTtPRX+DJPHDqjRbxogtFiByHyzKmy96RA0JtCQJ+WouyyL4A10xomQzgbUT+1jCg==" + }, + "node_modules/notion-to-md": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/notion-to-md/-/notion-to-md-3.1.1.tgz", + "integrity": "sha512-Zaa2P1B9Rx99bevFYTGuUMYbbfdHn2G1AZMsytYGDWIJjr6Ie1qp/8CorpwVUh1qrquES/V2PkEREqCuTu1zKA==", + "dependencies": { + "markdown-table": "^2.0.0", + "node-fetch": "2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/now-and-later": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", + "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.1.tgz", + "integrity": "sha512-M7s1BD4NxdAvBKUPqqRW957Xwcl/4Zvo8Aj+ANrzvIPzGJZElrH7Z//rSaec2ORcND6FHHLnZeY8qgTpXDMFQQ==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", + "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==" + }, + "node_modules/nx": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/nx/-/nx-21.1.2.tgz", + "integrity": "sha512-oczAEOOkQHElxCXs2g2jXDRabDRsmub/h5SAgqAUDSJ2CRnYGVVlgZX7l+o+A9kSqfONyLy5FlJ1pSWlvPuG4w==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@napi-rs/wasm-runtime": "0.2.4", + "@yarnpkg/lockfile": "^1.1.0", + "@yarnpkg/parsers": "3.0.2", + "@zkochan/js-yaml": "0.0.7", + "axios": "^1.8.3", + "chalk": "^4.1.0", + "cli-cursor": "3.1.0", + "cli-spinners": "2.6.1", + "cliui": "^8.0.1", + "dotenv": "~16.4.5", + "dotenv-expand": "~11.0.6", + "enquirer": "~2.3.6", + "figures": "3.2.0", + "flat": "^5.0.2", + "front-matter": "^4.0.2", + "ignore": "^5.0.4", + "jest-diff": "^29.4.1", + "jsonc-parser": "3.2.0", + "lines-and-columns": "2.0.3", + "minimatch": "9.0.3", + "node-machine-id": "1.1.12", + "npm-run-path": "^4.0.1", + "open": "^8.4.0", + "ora": "5.3.0", + "resolve.exports": "2.0.3", + "semver": "^7.5.3", + "string-width": "^4.2.3", + "tar-stream": "~2.2.0", + "tmp": "~0.2.1", + "tree-kill": "^1.2.2", + "tsconfig-paths": "^4.1.2", + "tslib": "^2.3.0", + "yaml": "^2.6.0", + "yargs": "^17.6.2", + "yargs-parser": "21.1.1" + }, + "bin": { + "nx": "bin/nx.js", + "nx-cloud": "bin/nx-cloud.js" + }, + "optionalDependencies": { + "@nx/nx-darwin-arm64": "21.1.2", + "@nx/nx-darwin-x64": "21.1.2", + "@nx/nx-freebsd-x64": "21.1.2", + "@nx/nx-linux-arm-gnueabihf": "21.1.2", + "@nx/nx-linux-arm64-gnu": "21.1.2", + "@nx/nx-linux-arm64-musl": "21.1.2", + "@nx/nx-linux-x64-gnu": "21.1.2", + "@nx/nx-linux-x64-musl": "21.1.2", + "@nx/nx-win32-arm64-msvc": "21.1.2", + "@nx/nx-win32-x64-msvc": "21.1.2" + }, + "peerDependencies": { + "@swc-node/register": "^1.8.0", + "@swc/core": "^1.3.85" + }, + "peerDependenciesMeta": { + "@swc-node/register": { + "optional": true + }, + "@swc/core": { + "optional": true + } + } + }, + "node_modules/nx-cloud": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/nx-cloud/-/nx-cloud-19.1.0.tgz", + "integrity": "sha512-f24vd5/57/MFSXNMfkerdDiK0EvScGOKO71iOWgJNgI1xVweDRmOA/EfjnPMRd5m+pnoPs/4A7DzuwSW0jZVyw==", + "dependencies": { + "@nrwl/nx-cloud": "19.1.0", + "axios": "^1.6.0", + "chalk": "^4.1.0", + "dotenv": "~10.0.0", + "fs-extra": "^11.1.0", + "ini": "4.1.3", + "node-machine-id": "^1.1.12", + "open": "~8.4.0", + "tar": "6.2.1", + "yargs-parser": ">=21.1.1" + }, + "bin": { + "nx-cloud": "bin/nx-cloud.js" + } + }, + "node_modules/nx-cloud/node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "engines": { + "node": ">=10" + } + }, + "node_modules/nx/node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.4.tgz", + "integrity": "sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==", + "dev": true, + "dependencies": { + "@emnapi/core": "^1.1.0", + "@emnapi/runtime": "^1.1.0", + "@tybys/wasm-util": "^0.9.0" + } + }, + "node_modules/nx/node_modules/@nx/nx-darwin-x64": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-21.1.2.tgz", + "integrity": "sha512-5sf+4PRVg9pDVgD53NE1hoPz4lC8Ni34UovQsOrZgDvwU5mqPbIhTzVYRDH86i/086AcCvjT5tEt7rEcuRwlKw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/nx/node_modules/@nx/nx-linux-arm-gnueabihf": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-21.1.2.tgz", + "integrity": "sha512-V4n6DE+r12gwJHFjZs+e2GmWYZdhpgA2DYWbsYWRYb1XQCNUg4vPzt+YFzWZ+K2o91k93EBnlLfrag7CqxUslw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/nx/node_modules/@nx/nx-linux-x64-gnu": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-21.1.2.tgz", + "integrity": "sha512-tjBINbymQgxnIlNK/m6B0P5eiGRSHSYPNkFdh3+sra80AP/ymHGLRxxZy702Ga2xg8RVr9zEvuXYHI+QBa1YmA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/nx/node_modules/@nx/nx-win32-x64-msvc": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-21.1.2.tgz", + "integrity": "sha512-J9rNTBOS7Ld6CybU/cou1Fg52AHSYsiwpZISM2RNM0XIoVSDk3Jsvh4OJgS2rvV0Sp/cgDg3ieOMAreekH+TKw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/nx/node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/nx/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/nx/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/nx/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/nx/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/nx/node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/oauth": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz", + "integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==" + }, + "node_modules/obj-case": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/obj-case/-/obj-case-0.2.1.tgz", + "integrity": "sha512-PquYBBTy+Y6Ob/O2574XHhDtHJlV1cJHMCgW+rDRc9J5hhmRelJB3k5dTK/3cVmFVtzvAKuENeuLpoyTzMzkOg==" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-sizeof": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/object-sizeof/-/object-sizeof-2.6.3.tgz", + "integrity": "sha512-GNkVRrLh11Qr5BGr73dwwPE200/78QG2rbx30cnXPnMvt7UuttH4Dup5t+LtcQhARkg8Hbr0c8Kiz52+CFxYmw==", + "dependencies": { + "buffer": "^6.0.3" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.hasown": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz", + "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.3.tgz", + "integrity": "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==", + "dev": true + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openai": { + "version": "4.67.1", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.67.1.tgz", + "integrity": "sha512-2YbRFy6qaYRJabK2zLMn4txrB2xBy0KP5g/eoqeSPTT31mIJMnkT75toagvfE555IKa2RdrzJrZwdDsUipsAMw==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==" + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz", + "integrity": "sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "log-symbols": "^4.0.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==" + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-any": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-any/-/p-any-4.0.0.tgz", + "integrity": "sha512-S/B50s+pAVe0wmEZHmBs/9yJXeZ5KhHzOsgKzt0hRdgkoR3DxW9ts46fcsWi/r3VnzsnkKS7q4uimze+zjdryw==", + "dependencies": { + "p-cancelable": "^3.0.0", + "p-some": "^6.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map/node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "optional": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map/node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-map/node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/p-queue/node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-some": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-some/-/p-some-6.0.0.tgz", + "integrity": "sha512-CJbQCKdfSX3fIh8/QKgS+9rjm7OBNUTmwWswAFQAhc8j1NR1dsEDETUEuVUtQHZpV+J03LqWBEwvu0g1Yn+TYg==", + "dependencies": { + "aggregate-error": "^4.0.0", + "p-cancelable": "^3.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz", + "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, + "node_modules/papaparse": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz", + "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==" + }, + "node_modules/parent-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", + "integrity": "sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==", + "dependencies": { + "callsites": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/parse-latin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", + "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", + "dependencies": { + "@types/nlcst": "^2.0.0", + "@types/unist": "^3.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0", + "unist-util-visit-children": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parse5-parser-stream/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseley": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", + "integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==", + "dependencies": { + "leac": "^0.6.0", + "peberminta": "^0.9.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/passport-oauth2": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz", + "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==", + "dependencies": { + "base64url": "3.x.x", + "oauth": "0.10.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x", + "utils-merge": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-oauth2/node_modules/uid2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-posix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-posix/-/path-posix-1.0.0.tgz", + "integrity": "sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/peberminta": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz", + "integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==", + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/peek-readable": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.4.2.tgz", + "integrity": "sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/peek-stream": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz", + "integrity": "sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "duplexify": "^3.5.0", + "through2": "^2.0.3" + } + }, + "node_modules/peek-stream/node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/peek-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/peek-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/peek-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/peek-stream/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true + }, + "node_modules/pg": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz", + "integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.6.2", + "pg-pool": "^3.6.1", + "pg-protocol": "^1.6.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.1.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==" + }, + "node_modules/pg-format": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pg-format/-/pg-format-1.0.4.tgz", + "integrity": "sha512-YyKEF78pEA6wwTAqOUaHIN/rWpfzzIuMh9KdAhc3rSLQ/7zkRFcCgYBAEGatDstLyZw4g0s9SNICmaTGnBVeyw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==" + }, + "node_modules/pg-types": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.2.tgz", + "integrity": "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==", + "dev": true, + "dependencies": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~3.0.1", + "postgres-bytea": "~3.0.0", + "postgres-date": "~2.1.0", + "postgres-interval": "^3.0.0", + "postgres-range": "^1.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pg/node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pg/node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/pg/node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pg/node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pg/node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/pgpass/node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/pickleparser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/pickleparser/-/pickleparser-0.1.0.tgz", + "integrity": "sha512-L81sYaXj6JlEOtVPwhXsEjlDQ0fjxPGf9ay4uBfQnsrR87mZ8SFQR/OD8kdqjQdeyMFcWyCMhx7WKZT1yZK7FQ==", + "bin": { + "pickleparser": "bin/pickletojson.js", + "pickletojson": "bin/pickletojson.js" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/picomodal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/picomodal/-/picomodal-3.0.0.tgz", + "integrity": "sha512-FoR3TDfuLlqUvcEeK5ifpKSVVns6B4BQvc8SDF6THVMuadya6LLtji0QgUDSStw0ZR2J7I6UGi5V2V23rnPWTw==" + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pino": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.7.0.tgz", + "integrity": "sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-abstract-transport/node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/pino-loki": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/pino-loki/-/pino-loki-2.1.3.tgz", + "integrity": "sha512-CRJh2u3/5JYYLEspjBRiXxKmR759Qo/yylz/efVFLr2CWERBlsndZHscDtfCzYDxT0P47fVLnbAvIAro0PEjag==", + "dependencies": { + "commander": "^10.0.1", + "got": "^11.8.6", + "pino-abstract-transport": "^1.0.0", + "pump": "^3.0.0" + }, + "bin": { + "pino-loki": "dist/cli.cjs" + }, + "funding": { + "url": "https://github.com/sponsors/Julien-R44" + } + }, + "node_modules/pino-loki/node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/pino-loki/node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pino-loki/node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/pino-loki/node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pino-loki/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "engines": { + "node": ">=14" + } + }, + "node_modules/pino-loki/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pino-loki/node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/pino-loki/node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/pino-loki/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/pino-loki/node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pino-loki/node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/pino-loki/node_modules/pino-abstract-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-loki/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-loki/node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pino-loki/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/pino-loki/node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/pino-loki/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/pino-pretty": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-9.4.1.tgz", + "integrity": "sha512-loWr5SNawVycvY//hamIzyz3Fh5OSpvkcO13MwdDW+eKIGylobPLqnVGTDwDXkdmpJd1BhEG+qhDw09h6SqJiQ==", + "dev": true, + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^4.0.1", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-pretty/node_modules/pino-abstract-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "dev": true, + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-pretty/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/pino-pretty/node_modules/sonic-boom": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", + "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", + "dev": true, + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/pino-pretty/node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/pino-pretty/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/piscina": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.9.2.tgz", + "integrity": "sha512-Fq0FERJWFEUpB4eSY59wSNwXD4RYqR+nR/WiEVcZW8IWfVBxJJafcgTEZDQo8k3w0sUarJ8RyVbbUF4GQ2LGbQ==", + "dev": true, + "optionalDependencies": { + "@napi-rs/nice": "^1.0.1" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dev": true, + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/pkg-dir/node_modules/yocto-queue": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkginfo": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.1.tgz", + "integrity": "sha512-8xCNE/aT/EXKenuMDZ+xTVwkT8gsoHN2z/Q29l80u0ppGEXVvsKRzNMbtKhg8LS8k1tJLAHHylf6p4VFmP6XUQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/playwright": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", + "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", + "dependencies": { + "playwright-core": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.37.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.1.tgz", + "integrity": "sha512-17EuQxlSIYCmEMwzMqusJ2ztDgJePjrbttaefgdsiqeLWidjYz9BxXaTaZWxH1J95SHGk6tjE+dwgWILJoUZfA==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/playwright/node_modules/playwright-core": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", + "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/pnpm": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/pnpm/-/pnpm-9.15.0.tgz", + "integrity": "sha512-duI3l2CkMo7EQVgVvNZije5yevN3mqpMkU45RBVsQpmSGon5djge4QfUHxLPpLZmgcqccY8GaPoIMe1MbYulbA==", + "dev": true, + "bin": { + "pnpm": "bin/pnpm.cjs", + "pnpx": "bin/pnpx.cjs" + }, + "engines": { + "node": ">=18.12" + }, + "funding": { + "url": "https://opencollective.com/pnpm" + } + }, + "node_modules/portfinder": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.37.tgz", + "integrity": "sha512-yuGIEjDAYnnOex9ddMnKZEMFE0CcGo6zbfzDklkmT1m5z734ss6JMzN9rNB3+RR7iS+F10D4/BVIaXOyh8PQKw==", + "dev": true, + "dependencies": { + "async": "^3.2.6", + "debug": "^4.3.6" + }, + "engines": { + "node": ">= 10.12" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", + "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-calc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", + "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", + "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", + "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", + "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-colormin": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-convert-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-custom-media": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", + "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-custom-properties": { + "version": "12.1.11", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.11.tgz", + "integrity": "sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", + "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-discard-comments": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-empty": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-double-position-gradients": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", + "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-env-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", + "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", + "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", + "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "dev": true, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-gap-properties": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", + "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-image-set-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", + "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-import": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", + "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-initial": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", + "dev": true, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-lab-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", + "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-loader": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", + "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "dev": true, + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-loader/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postcss-loader/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-media-minmax": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", + "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^6.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-rules": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", + "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^4.0.2", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", + "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", + "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "dev": true, + "dependencies": { + "colord": "^2.9.3", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-params": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", + "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", + "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nesting": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", + "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", + "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", + "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", + "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", + "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-string": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", + "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", + "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz", + "integrity": "sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==", + "dev": true, + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-ordered-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "dev": true, + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", + "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "dev": true, + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", + "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-preset-env": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.5.0.tgz", + "integrity": "sha512-0BJzWEfCdTtK2R3EiKKSdkE51/DI/BwnhlnicSW482Ym6/DGHud8K0wGLcdjip1epVX0HKo4c8zzTeV/SkiejQ==", + "dev": true, + "dependencies": { + "@csstools/postcss-color-function": "^1.1.0", + "@csstools/postcss-font-format-keywords": "^1.0.0", + "@csstools/postcss-hwb-function": "^1.0.0", + "@csstools/postcss-ic-unit": "^1.0.0", + "@csstools/postcss-is-pseudo-class": "^2.0.2", + "@csstools/postcss-normalize-display-values": "^1.0.0", + "@csstools/postcss-oklab-function": "^1.1.0", + "@csstools/postcss-progressive-custom-properties": "^1.3.0", + "@csstools/postcss-stepped-value-functions": "^1.0.0", + "@csstools/postcss-unset-value": "^1.0.0", + "autoprefixer": "^10.4.6", + "browserslist": "^4.20.3", + "css-blank-pseudo": "^3.0.3", + "css-has-pseudo": "^3.0.4", + "css-prefers-color-scheme": "^6.0.3", + "cssdb": "^6.6.1", + "postcss-attribute-case-insensitive": "^5.0.0", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^4.2.2", + "postcss-color-hex-alpha": "^8.0.3", + "postcss-color-rebeccapurple": "^7.0.2", + "postcss-custom-media": "^8.0.0", + "postcss-custom-properties": "^12.1.7", + "postcss-custom-selectors": "^6.0.0", + "postcss-dir-pseudo-class": "^6.0.4", + "postcss-double-position-gradients": "^3.1.1", + "postcss-env-function": "^4.0.6", + "postcss-focus-visible": "^6.0.4", + "postcss-focus-within": "^5.0.4", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^3.0.3", + "postcss-image-set-function": "^4.0.6", + "postcss-initial": "^4.0.1", + "postcss-lab-function": "^4.2.0", + "postcss-logical": "^5.0.4", + "postcss-media-minmax": "^5.0.0", + "postcss-nesting": "^10.1.4", + "postcss-opacity-percentage": "^1.1.2", + "postcss-overflow-shorthand": "^3.0.3", + "postcss-page-break": "^3.0.4", + "postcss-place": "^7.0.4", + "postcss-pseudo-class-any-link": "^7.1.2", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^5.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", + "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", + "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", + "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "dev": true, + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-5.0.0.tgz", + "integrity": "sha512-/2K3A4TCP9orP4TNS7u3tGdRFVKqz/E6pX3aGnriPG0jU78of8wsUcqE4QAhWEU0d+WnMSF93Ah3F//vUtK+iQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", + "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^3.2.0" + }, + "engines": { + "node": "^14 || ^16 || >= 18" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", + "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-url": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/postcss-url/-/postcss-url-10.1.3.tgz", + "integrity": "sha512-FUzyxfI5l2tKmXdYc6VTu3TWZsInayEKPbiyW+P6vmmIrrb4I6CGX0BFoewgYHLK+oIL5FECEK02REYRpBvUCw==", + "dev": true, + "dependencies": { + "make-dir": "~3.1.0", + "mime": "~2.5.2", + "minimatch": "~3.0.4", + "xxhashjs": "~0.2.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-url/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/postcss-url/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/postcss-url/node_modules/mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/postcss-url/node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/postcss-url/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/postgres-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "dev": true, + "dependencies": { + "obuf": "~1.1.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postgres-date": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz", + "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-interval": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-range": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz", + "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==", + "dev": true + }, + "node_modules/posthog-js": { + "version": "1.195.0", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.195.0.tgz", + "integrity": "sha512-ja+d/uogH9IPPnnM512uL5a9Igzz4K1OvBJNybCSbt6hqjSC+c5XaY3XH8t4D3RFz7aU5Di3trsrh/YNkSbF6A==", + "dependencies": { + "core-js": "^3.38.1", + "fflate": "^0.4.8", + "preact": "^10.19.3", + "web-vitals": "^4.2.0" + } + }, + "node_modules/posthog-js/node_modules/core-js": { + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.43.0.tgz", + "integrity": "sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/posthog-js/node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, + "node_modules/preact": { + "version": "10.26.9", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.9.tgz", + "integrity": "sha512-SSjF9vcnF27mJK1XyFMNJzFd5u3pQiATFqoaDy03XuN00u4ziveVVEGt5RKJrDR8MHE/wJo9Nnad56RLzS2RMA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", + "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/priority-queue-typescript": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/priority-queue-typescript/-/priority-queue-typescript-1.0.1.tgz", + "integrity": "sha512-Apycf6CgjdPRNfpZ8qgQeHVRe4B6R51QAd94k6fE13tGUpW1RLv8+ZeJipl2f0j/No3nt2Gk7dF2f+i1Au/I2w==" + }, + "node_modules/prism-react-renderer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", + "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", + "dependencies": { + "@types/prismjs": "^1.26.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "optional": true + }, + "node_modules/promise-map-series": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/promise-map-series/-/promise-map-series-0.3.0.tgz", + "integrity": "sha512-3npG2NGhTc8BWBolLLf8l/92OxMGaRLbqvIh9wjCHhDXNvk4zsxaTaCpiCunW09qWPrN2zeNSNwRLVBrQQtutA==", + "dev": true, + "engines": { + "node": "10.* || >= 12.*" + } + }, + "node_modules/promise-mysql": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/promise-mysql/-/promise-mysql-5.2.0.tgz", + "integrity": "sha512-IKkBe7OukgCpy5U5EZPlgH6BRvnngmP+HwD6PoMNzvGXBYVZkiJ5nx6SY7bo+sgwXsMOVE7zQf6CfS9qaFs2pw==", + "dependencies": { + "@types/bluebird": "^3.5.26", + "@types/mysql": "^2.15.2", + "bluebird": "^3.5.1", + "mysql": "^2.18.1" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/prosemirror-changeset": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.3.1.tgz", + "integrity": "sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==", + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-collab": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", + "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", + "dependencies": { + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", + "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz", + "integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz", + "integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.0.tgz", + "integrity": "sha512-K0xJRCmt+uSw7xesnHmcn72yBGTbY45vm8gXI4LZXbx2Z0jwh5aF9xrGQgrVPu0WbyFVFF3E/o9VhJYz6SQWnA==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-markdown": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.2.tgz", + "integrity": "sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==", + "dependencies": { + "@types/markdown-it": "^14.0.0", + "markdown-it": "^14.0.0", + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-menu": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.5.tgz", + "integrity": "sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==", + "dependencies": { + "crelt": "^1.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.1.tgz", + "integrity": "sha512-AUvbm7qqmpZa5d9fPKMvH1Q5bqYQvAZWOGRvxsB6iFLyycvC9MwNemNVjHVrWgjaoxAfY8XVg7DbvQ/qxvI9Eg==", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-basic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz", + "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==", + "dependencies": { + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", + "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.7.1.tgz", + "integrity": "sha512-eRQ97Bf+i9Eby99QbyAiyov43iOKgWa7QCGly+lrDt7efZ1v8NWolhXiB43hSDGIXT1UXgbs4KJN3a06FGpr1Q==", + "dependencies": { + "prosemirror-keymap": "^1.2.2", + "prosemirror-model": "^1.25.0", + "prosemirror-state": "^1.4.3", + "prosemirror-transform": "^1.10.3", + "prosemirror-view": "^1.39.1" + } + }, + "node_modules/prosemirror-trailing-node": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-2.0.9.tgz", + "integrity": "sha512-YvyIn3/UaLFlFKrlJB6cObvUhmwFNZVhy1Q8OpW/avoTbD/Y7H5EcjK4AZFKhmuS6/N6WkGgt7gWtBWDnmFvHg==", + "dependencies": { + "@remirror/core-constants": "^2.0.2", + "escape-string-regexp": "^4.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1.22.1", + "prosemirror-state": "^1.4.2", + "prosemirror-view": "^1.33.8" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.4.tgz", + "integrity": "sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.40.0.tgz", + "integrity": "sha512-2G3svX0Cr1sJjkD/DYWSe3cfV5VPVTBOxI9XQEGWJDFEpsZb/gh4MV29ctv+OJx2RFX4BLt09i+6zaGM/ldkCw==", + "dependencies": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/psl/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/public-ip": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/public-ip/-/public-ip-5.0.0.tgz", + "integrity": "sha512-xaH3pZMni/R2BG7ZXXaWS9Wc9wFlhyDVJF47IJ+3ali0TGv+2PsckKxbmo+rnx3ZxiV2wblVhtdS3bohAP6GGw==", + "dependencies": { + "dns-socket": "^4.2.2", + "got": "^12.0.0", + "is-ip": "^3.1.0" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/public-ip/node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/public-ip/node_modules/got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/pumpify/node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/pumpify/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/pumpify/node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/pumpify/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.15.0.tgz", + "integrity": "sha512-XjCY1SiSEi1T7iSYuxS82ft85kwDJUS7wj1Z0eGVXKdtr5g4xnVcbjwxhq5xBnpK/E7x1VZZoJDxpjAOasHT4Q==", + "hasInstallScript": true, + "dependencies": { + "@puppeteer/browsers": "2.3.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1312386", + "puppeteer-core": "22.15.0" + }, + "bin": { + "puppeteer": "lib/esm/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", + "dependencies": { + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer/node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pure-color": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", + "integrity": "sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==" + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/python-struct": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/python-struct/-/python-struct-1.1.3.tgz", + "integrity": "sha512-UsI/mNvk25jRpGKYI38Nfbv84z48oiIWwG67DLVvjRhy8B/0aIK+5Ju5WOHgw/o9rnEmbAS00v4rgKFQeC332Q==", + "dependencies": { + "long": "^4.0.0" + } + }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/qrcode/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qrcode/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/qrcode/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "node_modules/qrcode/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/quick-temp": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/quick-temp/-/quick-temp-0.1.8.tgz", + "integrity": "sha512-YsmIFfD9j2zaFwJkzI6eMG7y0lQP7YeWzgtFgNl38pGWZBSXJooZbOWwkcRot7Vt0Fg9L23pX0tqWU3VvLDsiA==", + "dev": true, + "dependencies": { + "mktemp": "~0.4.0", + "rimraf": "^2.5.4", + "underscore.string": "~3.3.4" + } + }, + "node_modules/quick-temp/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/quick-temp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/quick-temp/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/quick-temp/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rambda": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/rambda/-/rambda-9.4.2.tgz", + "integrity": "sha512-++euMfxnl7OgaEKwXh9QqThOjMeta2HH001N1v4mYQzBjJBnmXBh2BCK6dZAbICFVXOFUVD3xFG0R3ZPU0mxXw==", + "dev": true + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-base16-styling": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.6.0.tgz", + "integrity": "sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ==", + "dependencies": { + "base16": "^1.0.0", + "lodash.curry": "^4.0.1", + "lodash.flow": "^3.3.0", + "pure-color": "^1.2.0" + } + }, + "node_modules/react-colorful": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", + "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/react-data-grid": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/react-data-grid/-/react-data-grid-7.0.0-beta.47.tgz", + "integrity": "sha512-28kjsmwQGD/9RXYC50zn5Zv/SQMhBBoSvG5seq0fM8XXi9TZ0zr9Z5T3YJqLwcEtoNzTOq3y0njkmdujGkIwQQ==", + "dependencies": { + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0", + "react-dom": "^18.0 || ^19.0" + } + }, + "node_modules/react-day-picker": { + "version": "8.10.1", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz", + "integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "date-fns": "^2.28.0 || ^3.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-error-boundary": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-5.0.0.tgz", + "integrity": "sha512-tnjAxG+IkpLephNcePNA7v6F/QpWLH8He65+DmedchDwg162JZqx4NmbXj0mlAYVVEd81OW7aFhmbsScYfiAFQ==", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, + "node_modules/react-hook-form": { + "version": "7.52.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.52.2.tgz", + "integrity": "sha512-pqfPEbERnxxiNMPd0bzmt1tuaPcVccywFDpyk2uV5xCIBphHV5T8SVnX9/o3kplPE1zzKt77+YIoq+EMwJp56A==", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/react-i18next": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.1.tgz", + "integrity": "sha512-NwxLqNM6CLbeGA9xPsjits0EnXdKgCRSS6cgkgOdNcPXqL+1fYNl8fBg1wmnnHvFy812Bt4IWTPE9zjoPmFj3w==", + "dependencies": { + "@babel/runtime": "^7.24.8", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/react-json-view": { + "version": "1.21.3", + "resolved": "https://registry.npmjs.org/react-json-view/-/react-json-view-1.21.3.tgz", + "integrity": "sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==", + "dependencies": { + "flux": "^4.0.1", + "react-base16-styling": "^0.6.0", + "react-lifecycles-compat": "^3.0.4", + "react-textarea-autosize": "^8.3.2" + }, + "peerDependencies": { + "react": "^17.0.0 || ^16.3.0 || ^15.5.4", + "react-dom": "^17.0.0 || ^16.3.0 || ^15.5.4" + } + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-lottie": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/react-lottie/-/react-lottie-1.2.4.tgz", + "integrity": "sha512-kBGxI+MIZGBf4wZhNCWwHkMcVP+kbpmrLWH/SkO0qCKc7D7eSPcxQbfpsmsCo8v2KCBYjuGSou+xTqK44D/jMg==", + "dependencies": { + "babel-runtime": "^6.26.0", + "lottie-web": "^5.1.3" + }, + "engines": { + "npm": "^3.0.0" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, + "node_modules/react-markdown": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz", + "integrity": "sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-markdown/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/react-markdown/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-markdown/node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/react-markdown/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", + "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.4", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-resizable-panels": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.0.20.tgz", + "integrity": "sha512-aMbK3VF8U+VBICG+rwhE0Rr/eFZaRzmNq3akBRL1TrayIpLXz7Rbok0//kYeWj6SQRsjcQ3f4eRplJicM+oL6w==", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-ripples": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-ripples/-/react-ripples-2.2.1.tgz", + "integrity": "sha512-TfLjgZICQlsYCN2iX4fdeZNjoDzhJVC7r+R3irqyaNvCtWezouS9j+sE+nwSk747irtV8RKRDHmwMmsNBOw8Qg==" + }, + "node_modules/react-router": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.11.2.tgz", + "integrity": "sha512-74z9xUSaSX07t3LM+pS6Un0T55ibUE/79CzfZpy5wsPDZaea1F8QkrsiyRnA2YQ7LwE/umaydzXZV80iDCPkMg==", + "dependencies": { + "@remix-run/router": "1.6.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.11.2.tgz", + "integrity": "sha512-JNbKtAeh1VSJQnH6RvBDNhxNwemRj7KxCzc5jb7zvDSKRnPWIFj9pO+eXqjM69gQJ0r46hSz1x4l9y0651DKWw==", + "dependencies": { + "@remix-run/router": "1.6.2", + "react-router": "6.11.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-syntax-highlighter": { + "version": "15.4.2", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.4.2.tgz", + "integrity": "sha512-7Ymlzv7bS/7gU11iEXTCrZfevhZvfQ1U67uPZDudOfNixkLrPMjqVnxSZ2dcKmnqlqBhsYm/kiPmOoVcvD//VA==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.1.1", + "lowlight": "^1.17.0", + "prismjs": "^1.22.0", + "refractor": "^3.2.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, + "node_modules/react-textarea-autosize": { + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.5.tgz", + "integrity": "sha512-CVA94zmfp8m4bSHtWwmANaBR8EPsKy2aZ7KwqhoS4Ftib87F9Kvi7XQhOixypPLMc6kVYgOXvKFuuzZDpHGRPg==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/react-universal-interface": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz", + "integrity": "sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==", + "peerDependencies": { + "react": "*", + "tslib": "*" + } + }, + "node_modules/react-use": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/react-use/-/react-use-17.5.1.tgz", + "integrity": "sha512-LG/uPEVRflLWMwi3j/sZqR00nF6JGqTTDblkXK2nzXsIvij06hXl1V/MZIlwj1OKIQUtlh1l9jK8gLsRyCQxMg==", + "dependencies": { + "@types/js-cookie": "^2.2.6", + "@xobotyi/scrollbar-width": "^1.9.5", + "copy-to-clipboard": "^3.3.1", + "fast-deep-equal": "^3.1.3", + "fast-shallow-equal": "^1.0.0", + "js-cookie": "^2.2.1", + "nano-css": "^5.6.2", + "react-universal-interface": "^0.6.2", + "resize-observer-polyfill": "^1.5.1", + "screenfull": "^5.1.0", + "set-harmonic-interval": "^1.0.1", + "throttle-debounce": "^3.0.1", + "ts-easing": "^0.2.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/react-use/node_modules/js-cookie": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", + "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/recharts": { + "version": "2.12.7", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz", + "integrity": "sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^16.10.2", + "react-smooth": "^4.0.0", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/recharts/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.0.tgz", + "integrity": "sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/recma-jsx/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/recma-jsx/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/recma-jsx/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-parse/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/recma-parse/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/recma-parse/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/recma-parse/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/recma-stringify/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/recma-stringify/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/recma-stringify/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redent/node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-info": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redis-info/-/redis-info-3.1.0.tgz", + "integrity": "sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg==", + "dependencies": { + "lodash": "^4.17.11" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/redlock": { + "version": "5.0.0-beta.2", + "resolved": "https://registry.npmjs.org/redlock/-/redlock-5.0.0-beta.2.tgz", + "integrity": "sha512-2RDWXg5jgRptDrB1w9O/JgSZC0j7y4SlaXnor93H/UJm/QyDiFgBKNtrh0TI6oCXqYSaSoXxFh6Sd3VtYfhRXw==", + "dependencies": { + "node-abort-controller": "^3.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/refractor/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/refractor/node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/refractor/node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/refractor/node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/refractor/node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", + "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==" + }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexparam": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.2.tgz", + "integrity": "sha512-A1PeDEYMrkLrfyOwv2jwihXbo9qxdGD3atBYQA9JJgreAx8/7rC6IUkWOw2NQlOxLp2wL0ifQbh1HuidDfYA6w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/regexpu-core": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "dev": true, + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/rehype-katex": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz", + "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/katex": "^0.16.0", + "hast-util-from-html-isomorphic": "^2.0.0", + "hast-util-to-text": "^4.0.0", + "katex": "^0.16.0", + "unist-util-visit-parents": "^6.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-minify-whitespace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/rehype-minify-whitespace/-/rehype-minify-whitespace-6.0.2.tgz", + "integrity": "sha512-Zk0pyQ06A3Lyxhe9vGtOtzz3Z0+qZ5+7icZ/PL/2x1SHPbKao5oB/g/rlc6BCTajqBb33JcOe71Ye1oFsuYbnw==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/rehype-parse/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rehype-parse/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/rehype-parse/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz", + "integrity": "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==", + "dependencies": { + "@types/mdast": "^4.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-frontmatter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", + "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-frontmatter": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-frontmatter/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-frontmatter/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/remark-frontmatter/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-frontmatter/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz", + "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-gfm/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/remark-gfm/node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-gfm/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-math": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", + "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-math": "^3.0.0", + "micromark-extension-math": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-math/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-math/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/remark-math/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-math/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz", + "integrity": "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx-remove-esm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remark-mdx-remove-esm/-/remark-mdx-remove-esm-1.2.0.tgz", + "integrity": "sha512-BOZDeA9EuHDxQsvX7y4ovdlP8dk2/ToDGjOTrT5gs57OqTZuH4J1Tn8XjUFa221xvfXxiKaWrKT04waQ+tYydg==", + "dependencies": { + "@types/mdast": "^4.0.4", + "mdast-util-mdxjs-esm": "^2.0.1", + "unist-util-remove": "^4.0.0" + }, + "peerDependencies": { + "unified": "^11" + } + }, + "node_modules/remark-parse": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz", + "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==", + "dependencies": { + "mdast-util-from-markdown": "^0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/remark-parse/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/remark-parse/node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-parse/node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-parse/node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-parse/node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-parse/node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-parse/node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-parse/node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-parse/node_modules/mdast-util-from-markdown": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", + "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-string": "^2.0.0", + "micromark": "~2.11.0", + "parse-entities": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse/node_modules/mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse/node_modules/micromark": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", + "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "debug": "^4.0.0", + "parse-entities": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-parse/node_modules/unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dependencies": { + "@types/unist": "^2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-rehype/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/remark-rehype/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-rehype/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", + "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", + "dependencies": { + "retext": "^9.0.0", + "retext-smartypants": "^6.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/remark-smartypants/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-smartypants/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/remark-smartypants/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-smartypants/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-stringify/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/remark-stringify/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-stringify/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/remark/node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remedial": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/remedial/-/remedial-1.0.8.tgz", + "integrity": "sha512-/62tYiOe6DzS5BqVsNpH/nkGlX45C/Sp6V+NtiN6JQNS1Viay7cWkazmRkrQrdFj2eshDe96SIQNIoMxqhzBOg==", + "engines": { + "node": "*" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true + }, + "node_modules/remove-trailing-spaces": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/remove-trailing-spaces/-/remove-trailing-spaces-1.0.9.tgz", + "integrity": "sha512-xzG7w5IRijvIkHIjDk65URsJJ7k4J95wmcArY5PRcmjldIOl7oTvG8+X2Ag690R7SfwiOcHrWZKVc1Pp5WIOzA==" + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/replicate": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/replicate/-/replicate-0.34.1.tgz", + "integrity": "sha512-kQ5ULqowkZsx34WdUhlAtp9IcpalIfkaSRrFPUGP3gEpXouCxGsjXpn57e3Ic7K3mNw74cLkIrtAgcrlP+pzvg==", + "engines": { + "git": ">=2.11.0", + "node": ">=18.0.0", + "npm": ">=7.19.0", + "yarn": ">=1.7.0" + }, + "optionalDependencies": { + "readable-stream": ">=4.0.0" + } + }, + "node_modules/replicate/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "optional": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/replicate/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/replicate/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "node_modules/requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true, + "engines": { + "node": ">=0.10.5" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-global": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-global/-/resolve-global-1.0.0.tgz", + "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==", + "dev": true, + "dependencies": { + "global-dirs": "^0.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-2.0.0.tgz", + "integrity": "sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A==", + "dev": true, + "dependencies": { + "value-or-function": "^4.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/retext": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "dependencies": { + "@types/nlcst": "^2.0.0", + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", + "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", + "dependencies": { + "@types/nlcst": "^2.0.0", + "parse-latin": "^7.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/retext-latin/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retext-latin/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/retext-latin/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", + "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/retext-stringify/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retext-stringify/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/retext-stringify/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/retext/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retext/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/retext/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-request": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", + "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", + "dependencies": { + "debug": "^4.1.1", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "devOptional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "devOptional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "devOptional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "devOptional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.5.tgz", + "integrity": "sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.22.5", + "@rollup/rollup-android-arm64": "4.22.5", + "@rollup/rollup-darwin-arm64": "4.22.5", + "@rollup/rollup-darwin-x64": "4.22.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.5", + "@rollup/rollup-linux-arm-musleabihf": "4.22.5", + "@rollup/rollup-linux-arm64-gnu": "4.22.5", + "@rollup/rollup-linux-arm64-musl": "4.22.5", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.5", + "@rollup/rollup-linux-riscv64-gnu": "4.22.5", + "@rollup/rollup-linux-s390x-gnu": "4.22.5", + "@rollup/rollup-linux-x64-gnu": "4.22.5", + "@rollup/rollup-linux-x64-musl": "4.22.5", + "@rollup/rollup-win32-arm64-msvc": "4.22.5", + "@rollup/rollup-win32-ia32-msvc": "4.22.5", + "@rollup/rollup-win32-x64-msvc": "4.22.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.5.tgz", + "integrity": "sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.5.tgz", + "integrity": "sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" + }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==" + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==" + }, + "node_modules/rslog": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/rslog/-/rslog-1.2.9.tgz", + "integrity": "sha512-KSjM8jJKYYaKgI4jUGZZ4kdTBTM/EIGH1JnoB0ptMkzcyWaHeXW9w6JVLCYs37gh8sFZkLLqAyBb2sT02bqpcQ==", + "dev": true + }, + "node_modules/rss-parser": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/rss-parser/-/rss-parser-3.13.0.tgz", + "integrity": "sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w==", + "dependencies": { + "entities": "^2.0.3", + "xml2js": "^0.5.0" + } + }, + "node_modules/rss-parser/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/rss-parser/node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/rsvp": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", + "dev": true, + "engines": { + "node": "6.* || >= 7.*" + } + }, + "node_modules/rtl-css-js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz", + "integrity": "sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==", + "dependencies": { + "@babel/runtime": "^7.1.2" + } + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safe-flat": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/safe-flat/-/safe-flat-2.1.0.tgz", + "integrity": "sha512-qr5iVWMYuN21dkijya23k6apc2BV1hiCG75vjToKDTzWlbR4SLbLbCnowPJ2pngnwGT2nMEeZKOglBE4pksj6g==" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex2": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", + "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "ret": "~0.5.0" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/samlify": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/samlify/-/samlify-2.10.0.tgz", + "integrity": "sha512-IIFg193YPn9IpTd2jCWVvLLC9xdWz/eLn1rtF9YMSwK/B1rt2OM2zAuP99cw3MPYyYsm+I9rlvYgq9FuJ9JqSA==", + "dependencies": { + "@authenio/xml-encryption": "^2.0.2", + "@xmldom/xmldom": "^0.8.6", + "camelcase": "^6.2.0", + "node-forge": "^1.3.0", + "node-rsa": "^1.1.1", + "pako": "^1.0.10", + "uuid": "^8.3.2", + "xml": "^1.0.1", + "xml-crypto": "^6.1.0", + "xml-escape": "^1.1.0", + "xpath": "^0.0.32" + } + }, + "node_modules/samlify/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/sass": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.2.tgz", + "integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==", + "dev": true, + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-embedded": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.89.2.tgz", + "integrity": "sha512-Ack2K8rc57kCFcYlf3HXpZEJFNUX8xd8DILldksREmYXQkRHI879yy8q4mRDJgrojkySMZqmmmW1NxrFxMsYaA==", + "dev": true, + "dependencies": { + "@bufbuild/protobuf": "^2.5.0", + "buffer-builder": "^0.2.0", + "colorjs.io": "^0.5.0", + "immutable": "^5.0.2", + "rxjs": "^7.4.0", + "supports-color": "^8.1.1", + "sync-child-process": "^1.0.2", + "varint": "^6.0.0" + }, + "bin": { + "sass": "dist/bin/sass.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "sass-embedded-android-arm": "1.89.2", + "sass-embedded-android-arm64": "1.89.2", + "sass-embedded-android-riscv64": "1.89.2", + "sass-embedded-android-x64": "1.89.2", + "sass-embedded-darwin-arm64": "1.89.2", + "sass-embedded-darwin-x64": "1.89.2", + "sass-embedded-linux-arm": "1.89.2", + "sass-embedded-linux-arm64": "1.89.2", + "sass-embedded-linux-musl-arm": "1.89.2", + "sass-embedded-linux-musl-arm64": "1.89.2", + "sass-embedded-linux-musl-riscv64": "1.89.2", + "sass-embedded-linux-musl-x64": "1.89.2", + "sass-embedded-linux-riscv64": "1.89.2", + "sass-embedded-linux-x64": "1.89.2", + "sass-embedded-win32-arm64": "1.89.2", + "sass-embedded-win32-x64": "1.89.2" + } + }, + "node_modules/sass-embedded-android-arm": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.89.2.tgz", + "integrity": "sha512-oHAPTboBHRZlDBhyRB6dvDKh4KvFs+DZibDHXbkSI6dBZxMTT+Yb2ivocHnctVGucKTLQeT7+OM5DjWHyynL/A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-arm64": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.89.2.tgz", + "integrity": "sha512-+pq7a7AUpItNyPu61sRlP6G2A8pSPpyazASb+8AK2pVlFayCSPAEgpwpCE9A2/Xj86xJZeMizzKUHxM2CBCUxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-riscv64": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.89.2.tgz", + "integrity": "sha512-HfJJWp/S6XSYvlGAqNdakeEMPOdhBkj2s2lN6SHnON54rahKem+z9pUbCriUJfM65Z90lakdGuOfidY61R9TYg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-x64": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.89.2.tgz", + "integrity": "sha512-BGPzq53VH5z5HN8de6jfMqJjnRe1E6sfnCWFd4pK+CAiuM7iw5Fx6BQZu3ikfI1l2GY0y6pRXzsVLdp/j4EKEA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-arm64": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.89.2.tgz", + "integrity": "sha512-UCm3RL/tzMpG7DsubARsvGUNXC5pgfQvP+RRFJo9XPIi6elopY5B6H4m9dRYDpHA+scjVthdiDwkPYr9+S/KGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-x64": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.89.2.tgz", + "integrity": "sha512-D9WxtDY5VYtMApXRuhQK9VkPHB8R79NIIR6xxVlN2MIdEid/TZWi1MHNweieETXhWGrKhRKglwnHxxyKdJYMnA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.89.2.tgz", + "integrity": "sha512-leP0t5U4r95dc90o8TCWfxNXwMAsQhpWxTkdtySDpngoqtTy3miMd7EYNYd1znI0FN1CBaUvbdCMbnbPwygDlA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm64": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.89.2.tgz", + "integrity": "sha512-2N4WW5LLsbtrWUJ7iTpjvhajGIbmDR18ZzYRywHdMLpfdPApuHPMDF5CYzHbS+LLx2UAx7CFKBnj5LLjY6eFgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.89.2.tgz", + "integrity": "sha512-Z6gG2FiVEEdxYHRi2sS5VIYBmp17351bWtOCUZ/thBM66+e70yiN6Eyqjz80DjL8haRUegNQgy9ZJqsLAAmr9g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm64": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.89.2.tgz", + "integrity": "sha512-nTyuaBX6U1A/cG7WJh0pKD1gY8hbg1m2SnzsyoFG+exQ0lBX/lwTLHq3nyhF+0atv7YYhYKbmfz+sjPP8CZ9lw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-riscv64": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.89.2.tgz", + "integrity": "sha512-N6oul+qALO0SwGY8JW7H/Vs0oZIMrRMBM4GqX3AjM/6y8JsJRxkAwnfd0fDyK+aICMFarDqQonQNIx99gdTZqw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-x64": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.89.2.tgz", + "integrity": "sha512-K+FmWcdj/uyP8GiG9foxOCPfb5OAZG0uSVq80DKgVSC0U44AdGjvAvVZkrgFEcZ6cCqlNC2JfYmslB5iqdL7tg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-riscv64": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.89.2.tgz", + "integrity": "sha512-g9nTbnD/3yhOaskeqeBQETbtfDQWRgsjHok6bn7DdAuwBsyrR3JlSFyqKc46pn9Xxd9SQQZU8AzM4IR+sY0A0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.89.2.tgz", + "integrity": "sha512-Ax7dKvzncyQzIl4r7012KCMBvJzOz4uwSNoyoM5IV6y5I1f5hEwI25+U4WfuTqdkv42taCMgpjZbh9ERr6JVMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-arm64": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.89.2.tgz", + "integrity": "sha512-j96iJni50ZUsfD6tRxDQE2QSYQ2WrfHxeiyAXf41Kw0V4w5KYR/Sf6rCZQLMTUOHnD16qTMVpQi20LQSqf4WGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-x64": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.89.2.tgz", + "integrity": "sha512-cS2j5ljdkQsb4PaORiClaVYynE9OAPZG/XjbOMxpQmjRIf7UroY4PEIH+Waf+y47PfXFX9SyxhYuw2NIKGbEng==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/sass-loader": { + "version": "16.0.5", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.5.tgz", + "integrity": "sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==", + "dev": true, + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/sass/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/schema-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/screenfull": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz", + "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, + "node_modules/seek-bzip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-2.0.0.tgz", + "integrity": "sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==", + "dev": true, + "dependencies": { + "commander": "^6.0.0" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/seek-bzip/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/selderee": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz", + "integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==", + "dependencies": { + "parseley": "^0.12.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==" + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-regex": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", + "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver-truncate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-3.0.0.tgz", + "integrity": "sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serialize-error": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-12.0.0.tgz", + "integrity": "sha512-ZYkZLAvKTKQXWuh5XpBw7CdbSzagarX39WyZ2H07CDLC5/KfsRGlIXV8d4+tfqX1M7916mRqR1QfNHSij+c9Pw==", + "dependencies": { + "type-fest": "^4.31.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/serve-index/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-harmonic-interval": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz", + "integrity": "sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==", + "engines": { + "node": ">=6.9" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-exec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/shell-exec/-/shell-exec-1.0.2.tgz", + "integrity": "sha512-jyVd+kU2X+mWKMmGhx4fpWbPsjvD53k9ivqetutVW/BQ+WIZoDoP4d8vUMGezV6saZsiNoW2f9GIhg9Dondohg==", + "dev": true + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shiki": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.7.0.tgz", + "integrity": "sha512-ZcI4UT9n6N2pDuM2n3Jbk0sR4Swzq43nLPgS/4h0E3B/NrFn2HKElrDtceSf8Zx/OWYOo7G1SAtBLypCp+YXqg==", + "dependencies": { + "@shikijs/core": "3.7.0", + "@shikijs/engine-javascript": "3.7.0", + "@shikijs/engine-oniguruma": "3.7.0", + "@shikijs/langs": "3.7.0", + "@shikijs/themes": "3.7.0", + "@shikijs/types": "3.7.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/showdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", + "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==", + "dependencies": { + "commander": "^9.0.0" + }, + "bin": { + "showdown": "bin/showdown.js" + }, + "funding": { + "type": "individual", + "url": "https://www.paypal.me/tiviesantos" + } + }, + "node_modules/showdown/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-git": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.21.0.tgz", + "integrity": "sha512-oTzw9248AF5bDTMk9MrxsRzEzivMlY+DWH0yWS4VYpMhNLhDWnN06pCtaUyPnqv/FpsdeNmRqmZugMABHRPdDA==", + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, + "node_modules/simple-lru-cache": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/simple-lru-cache/-/simple-lru-cache-0.0.2.tgz", + "integrity": "sha512-uEv/AFO0ADI7d99OHDmh1QfYzQk/izT1vCmu/riQfh7qjBVUUgRT87E5s5h7CxWCA/+YoZerykpEthzVrW3LIw==" + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slackify-markdown": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/slackify-markdown/-/slackify-markdown-4.4.0.tgz", + "integrity": "sha512-a2b0Rh4aPi3PYt23N0vxPn7emkQtShewhLX8uIiXOMlPBAXRki+/9kEXJztZr1Oo9rDb1YxScGuZ0D2ubLPhvQ==", + "dependencies": { + "mdast-util-to-markdown": "^0.6.2", + "remark-gfm": "^1.0.0", + "remark-parse": "^9.0.0", + "remark-stringify": "^9.0.1", + "unified": "^9.0.0", + "unist-util-remove": "^2.0.1", + "unist-util-visit": "^2.0.3" + } + }, + "node_modules/slackify-markdown/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/slackify-markdown/node_modules/ccount": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", + "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/slackify-markdown/node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/slackify-markdown/node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/slackify-markdown/node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/slackify-markdown/node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/slackify-markdown/node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/slackify-markdown/node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/slackify-markdown/node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/slackify-markdown/node_modules/longest-streak": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", + "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/slackify-markdown/node_modules/mdast-util-find-and-replace": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-1.1.1.tgz", + "integrity": "sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==", + "dependencies": { + "escape-string-regexp": "^4.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/mdast-util-gfm": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-0.1.2.tgz", + "integrity": "sha512-NNkhDx/qYcuOWB7xHUGWZYVXvjPFFd6afg6/e2g+SV4r9q5XUcCbV4Wfa3DLYIiD+xAEZc6K4MGaE/m0KDcPwQ==", + "dependencies": { + "mdast-util-gfm-autolink-literal": "^0.1.0", + "mdast-util-gfm-strikethrough": "^0.2.0", + "mdast-util-gfm-table": "^0.1.0", + "mdast-util-gfm-task-list-item": "^0.1.0", + "mdast-util-to-markdown": "^0.6.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/mdast-util-gfm-autolink-literal": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-0.1.3.tgz", + "integrity": "sha512-GjmLjWrXg1wqMIO9+ZsRik/s7PLwTaeCHVB7vRxUwLntZc8mzmTsLVr6HW1yLokcnhfURsn5zmSVdi3/xWWu1A==", + "dependencies": { + "ccount": "^1.0.0", + "mdast-util-find-and-replace": "^1.1.0", + "micromark": "^2.11.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/mdast-util-gfm-strikethrough": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-0.2.3.tgz", + "integrity": "sha512-5OQLXpt6qdbttcDG/UxYY7Yjj3e8P7X16LzvpX8pIQPYJ/C2Z1qFGMmcw+1PZMUM3Z8wt8NRfYTvCni93mgsgA==", + "dependencies": { + "mdast-util-to-markdown": "^0.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/mdast-util-gfm-table": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-0.1.6.tgz", + "integrity": "sha512-j4yDxQ66AJSBwGkbpFEp9uG/LS1tZV3P33fN1gkyRB2LoRL+RR3f76m0HPHaby6F4Z5xr9Fv1URmATlRRUIpRQ==", + "dependencies": { + "markdown-table": "^2.0.0", + "mdast-util-to-markdown": "~0.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/mdast-util-gfm-task-list-item": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-0.1.6.tgz", + "integrity": "sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==", + "dependencies": { + "mdast-util-to-markdown": "~0.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/mdast-util-to-markdown": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz", + "integrity": "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==", + "dependencies": { + "@types/unist": "^2.0.0", + "longest-streak": "^2.0.0", + "mdast-util-to-string": "^2.0.0", + "parse-entities": "^2.0.0", + "repeat-string": "^1.0.0", + "zwitch": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/micromark": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", + "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "debug": "^4.0.0", + "parse-entities": "^2.0.0" + } + }, + "node_modules/slackify-markdown/node_modules/micromark-extension-gfm": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-0.3.3.tgz", + "integrity": "sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==", + "dependencies": { + "micromark": "~2.11.0", + "micromark-extension-gfm-autolink-literal": "~0.5.0", + "micromark-extension-gfm-strikethrough": "~0.6.5", + "micromark-extension-gfm-table": "~0.4.0", + "micromark-extension-gfm-tagfilter": "~0.3.0", + "micromark-extension-gfm-task-list-item": "~0.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/micromark-extension-gfm-autolink-literal": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-0.5.7.tgz", + "integrity": "sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==", + "dependencies": { + "micromark": "~2.11.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/micromark-extension-gfm-strikethrough": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-0.6.5.tgz", + "integrity": "sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==", + "dependencies": { + "micromark": "~2.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/micromark-extension-gfm-table": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-0.4.3.tgz", + "integrity": "sha512-hVGvESPq0fk6ALWtomcwmgLvH8ZSVpcPjzi0AjPclB9FsVRgMtGZkUcpE0zgjOCFAznKepF4z3hX8z6e3HODdA==", + "dependencies": { + "micromark": "~2.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/micromark-extension-gfm-tagfilter": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-0.3.0.tgz", + "integrity": "sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/micromark-extension-gfm-task-list-item": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-0.3.3.tgz", + "integrity": "sha512-0zvM5iSLKrc/NQl84pZSjGo66aTGd57C1idmlWmE87lkMcXrTxg1uXa/nXomxJytoje9trP0NDLvw4bZ/Z/XCQ==", + "dependencies": { + "micromark": "~2.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/slackify-markdown/node_modules/remark-gfm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-1.0.0.tgz", + "integrity": "sha512-KfexHJCiqvrdBZVbQ6RopMZGwaXz6wFJEfByIuEwGf0arvITHjiKKZ1dpXujjH9KZdm1//XJQwgfnJ3lmXaDPA==", + "dependencies": { + "mdast-util-gfm": "^0.1.0", + "micromark-extension-gfm": "^0.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/remark-stringify": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-9.0.1.tgz", + "integrity": "sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==", + "dependencies": { + "mdast-util-to-markdown": "^0.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/unist-util-remove": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-2.1.0.tgz", + "integrity": "sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q==", + "dependencies": { + "unist-util-is": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/unist-util-visit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/unist-util-visit-parents": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/slackify-markdown/node_modules/zwitch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", + "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slugify": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", + "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/snowflake-sdk": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/snowflake-sdk/-/snowflake-sdk-1.9.3.tgz", + "integrity": "sha512-smPj3pLwK3MCdPMqdjdKdmZcCWFioYM3eCvf5R4p+qk+n7vfUChTL40ZXy8DE6e44c3UY50fp9AQeBiNT8rw6A==", + "dependencies": { + "@aws-sdk/client-s3": "^3.388.0", + "@azure/storage-blob": "^12.11.0", + "@google-cloud/storage": "^6.9.3", + "@techteamer/ocsp": "1.0.1", + "agent-base": "^6.0.2", + "asn1.js-rfc2560": "^5.0.0", + "asn1.js-rfc5280": "^3.0.0", + "axios": "^1.6.5", + "big-integer": "^1.6.43", + "bignumber.js": "^9.1.2", + "binascii": "0.0.2", + "bn.js": "^5.2.1", + "browser-request": "^0.3.3", + "debug": "^3.2.6", + "expand-tilde": "^2.0.2", + "extend": "^3.0.2", + "fast-xml-parser": "^4.2.5", + "fastest-levenshtein": "^1.0.16", + "generic-pool": "^3.8.2", + "glob": "^9.0.0", + "https-proxy-agent": "^7.0.2", + "jsonwebtoken": "^9.0.0", + "mime-types": "^2.1.29", + "mkdirp": "^1.0.3", + "moment": "^2.29.4", + "moment-timezone": "^0.5.15", + "open": "^7.3.1", + "python-struct": "^1.1.3", + "simple-lru-cache": "^0.0.2", + "uuid": "^8.3.2", + "winston": "^3.1.0" + }, + "peerDependencies": { + "asn1.js": "^5.4.1" + } + }, + "node_modules/snowflake-sdk/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/snowflake-sdk/node_modules/agent-base/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/snowflake-sdk/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/snowflake-sdk/node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/snowflake-sdk/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, + "node_modules/snowflake-sdk/node_modules/minimatch": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", + "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/snowflake-sdk/node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/snowflake-sdk/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/snowflake-sdk/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/snowflake-sdk/node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/soap": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/soap/-/soap-1.1.10.tgz", + "integrity": "sha512-dqfX9qHhXup3ZLWsI5of6xJIJKeBCPnn3tTu9sKtASm2A53Zk6/u3drygLiUy+H1mmjRBptXfVkjY6pt8nhOjA==", + "dependencies": { + "axios": "^1.8.3", + "axios-ntlm": "^1.4.3", + "debug": "^4.4.0", + "formidable": "^3.5.2", + "get-stream": "^6.0.1", + "lodash": "^4.17.21", + "sax": "^1.4.1", + "strip-bom": "^3.0.0", + "whatwg-mimetype": "4.0.0", + "xml-crypto": "^6.0.1" + }, + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/soap/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-client": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/socks": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.3.tgz", + "integrity": "sha512-vfuYK48HXCTFD03G/1/zkIls3Ebr2YNa4qU9gHDZdblHLiqhJrJGkY3+0Nx0JpN9qBhJbVObc1CNciT1bIZJxw==", + "deprecated": "please use 2.7.4 or 2.8.1 to fix package-lock issue", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/socks": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", + "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/sonner": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.3.tgz", + "integrity": "sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/sort-keys": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-5.1.0.tgz", + "integrity": "sha512-aSbHV0DaBcr7u0PVHXzM6NbZNAtrr9sF6+Qfs9UUVG7Ll3jQ6hHi8F/xqIIcn2rvIVbr0v/2zyjSdwSV47AgLQ==", + "dev": true, + "dependencies": { + "is-plain-obj": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sort-keys-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", + "dev": true, + "dependencies": { + "sort-keys": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys-length/node_modules/sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "dev": true, + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sort-on": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/sort-on/-/sort-on-6.1.0.tgz", + "integrity": "sha512-WTECP0nYNWO1n2g5bpsV0yZN9cBmZsF8ThHFbOqVN0HBFRoaQZLLEMvMmJlKHNPYQeVngeI5+jJzIfFqOIo1OA==", + "dependencies": { + "dot-prop": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sort-on/node_modules/dot-prop": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", + "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", + "dependencies": { + "type-fest": "^4.18.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sort-on/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "dev": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "dev": true + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/spdy-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/spdy-transport/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/spdy-transport/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dev": true, + "dependencies": { + "readable-stream": "^3.0.0" + } + }, + "node_modules/split2/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/split2/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/split2/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, + "node_modules/sqlite3": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", + "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/sqlite3/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==" + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ssh2": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz", + "integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==", + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.10", + "nan": "^2.20.0" + } + }, + "node_modules/ssh2-sftp-client": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/ssh2-sftp-client/-/ssh2-sftp-client-9.1.0.tgz", + "integrity": "sha512-Hzdr9OE6GxZjcmyM9tgBSIFVyrHAp9c6U2Y4yBkmYOHoQvZ7pIm27dmltvcmRfxcWiIcg8HBvG5iAikDf+ZuzQ==", + "dependencies": { + "concat-stream": "^2.0.0", + "promise-retry": "^2.0.1", + "ssh2": "^1.12.0" + }, + "engines": { + "node": ">=10.24.1" + } + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "dev": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sshpk/node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ssri/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ssri/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/stack-generator": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz", + "integrity": "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" + }, + "node_modules/stacktrace-gps": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz", + "integrity": "sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==", + "dependencies": { + "source-map": "0.5.6", + "stackframe": "^1.3.4" + } + }, + "node_modules/stacktrace-gps/node_modules/source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stacktrace-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz", + "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", + "dependencies": { + "error-stack-parser": "^2.0.6", + "stack-generator": "^2.0.5", + "stacktrace-gps": "^3.0.4" + } + }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true + }, + "node_modules/stdin-discarder": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", + "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", + "dependencies": { + "bl": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stdin-discarder/node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/stdin-discarder/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/stdin-discarder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/stdin-discarder/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/steno": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/steno/-/steno-4.0.2.tgz", + "integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/stream-composer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", + "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", + "dev": true, + "dependencies": { + "streamx": "^2.13.2" + } + }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" + }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/streamroller/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/streamroller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/streamroller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-replace-async": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/string-replace-async/-/string-replace-async-3.0.2.tgz", + "integrity": "sha512-s6hDtXJ7FKyRap/amefqrOMpkEQvxUDueyvJygQeHxCK5Za90dOMgdibCCrPdfdAYAkr8imrZ1PPXW7DOf0RzQ==", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/string-strip-html": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/string-strip-html/-/string-strip-html-8.5.0.tgz", + "integrity": "sha512-5ICsK1B1j0A3AF1d45m0sqQCcmi1Q+t1QpF+b794LO5FTHV+ITkGR5C+UCDJQZgs5LMuRruqr6j48PxQVIurJQ==", + "engines": { + "node": ">=14" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-3.0.0.tgz", + "integrity": "sha512-I0sdgcFTfKQlUPZyAqPJmSG3HLO9rWDFnxonnIbskYNM3DwFOeTNB5KzVq3dA1GdRAc/25b5Y7UO2TQfKWw4aQ==", + "dev": true, + "dependencies": { + "inspect-with-kind": "^1.0.5", + "is-plain-obj": "^1.1.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stripe": { + "version": "18.2.1", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-18.2.1.tgz", + "integrity": "sha512-GwB1B7WSwEBzW4dilgyJruUYhbGMscrwuyHsPUmSRKrGHZ5poSh2oU9XKdii5BFVJzXHn35geRvGJ6R8bYcp8w==", + "dependencies": { + "qs": "^6.11.0" + }, + "engines": { + "node": ">=12.*" + }, + "peerDependencies": { + "@types/node": ">=12.x.x" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, + "node_modules/strtok3": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-9.1.1.tgz", + "integrity": "sha512-FhwotcEqjr241ZbjFzjlIYg6c5/L/s4yBGWSMvJ9UoExiSqL+FnFA/CaeZx17WGaZMS/4SOZp8wH18jSS4R4lw==", + "dev": true, + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^5.3.1" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==" + }, + "node_modules/style-loader": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==" + }, + "node_modules/style-to-js": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz", + "integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==", + "dependencies": { + "style-to-object": "1.0.9" + } + }, + "node_modules/style-to-object": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz", + "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, + "node_modules/stylehacks": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", + "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==" + }, + "node_modules/stylus": { + "version": "0.64.0", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.64.0.tgz", + "integrity": "sha512-ZIdT8eUv8tegmqy1tTIdJv9We2DumkNZFdCF5mz/Kpq3OcTaxSuCAYZge6HKK2CmNC02G1eJig2RV7XTw5hQrA==", + "dev": true, + "dependencies": { + "@adobe/css-tools": "~4.3.3", + "debug": "^4.3.2", + "glob": "^10.4.5", + "sax": "~1.4.1", + "source-map": "^0.7.3" + }, + "bin": { + "stylus": "bin/stylus" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://opencollective.com/stylus" + } + }, + "node_modules/stylus-loader": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-7.1.3.tgz", + "integrity": "sha512-TY0SKwiY7D2kMd3UxaWKSf3xHF0FFN/FAfsSqfrhxRT/koXTwffq2cgEWDkLQz7VojMu7qEEHt5TlMjkPx9UDw==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.12", + "normalize-path": "^3.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "stylus": ">=0.52.4", + "webpack": "^5.0.0" + } + }, + "node_modules/stylus/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/stylus/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/stylus/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/stylus/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/stylus/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/sucrase/node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/sucrase/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/superagent": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.1.tgz", + "integrity": "sha512-VMBFLYgFuRdfeNQSMLbxGSLfmXL/xc+OO+BZp41Za/NRDBet/BNbkRJrYzCUu0u4GU0i/ml2dtT8b9qgkw9z6Q==", + "deprecated": "Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net", + "dependencies": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.1.1", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/superagent/node_modules/form-data": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.3.tgz", + "integrity": "sha512-XHIrMD0NpDrNM/Ckf7XJiBbLl57KEhT3+i3yY+eWm+cqYZJQTZrKo8Y8AWKnuV5GT4scfuUGt9LzNoIx3dU1nQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/superagent/node_modules/form-data/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/superagent/node_modules/formidable": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", + "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", + "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/superagent/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/superagent/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/superagent/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/superagent/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true + }, + "node_modules/svgo": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "dev": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/svgo/node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/svgo/node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true + }, + "node_modules/swr": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.3.tgz", + "integrity": "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "node_modules/symlink-or-copy": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/symlink-or-copy/-/symlink-or-copy-1.3.1.tgz", + "integrity": "sha512-0K91MEXFpBUaywiwSSkmKjnGcasG/rVBXFLJz5DrgGabpYD6N+3yZrfD6uUIfpuTu65DZLHi7N8CizHc07BPZA==", + "dev": true + }, + "node_modules/sync-child-process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", + "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", + "dev": true, + "dependencies": { + "sync-message-port": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/sync-message-port": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz", + "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/tailwind-merge": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.4.0.tgz", + "integrity": "sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwind-scrollbar": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/tailwind-scrollbar/-/tailwind-scrollbar-4.0.2.tgz", + "integrity": "sha512-wAQiIxAPqk0MNTPptVe/xoyWi27y+NRGnTwvn4PQnbvB9kp8QUBiGl/wsfoVBHnQxTmhXJSNt9NHTmcz9EivFA==", + "dependencies": { + "prism-react-renderer": "^2.4.1" + }, + "engines": { + "node": ">=12.13.0" + }, + "peerDependencies": { + "tailwindcss": "4.x" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", + "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/tailwindcss/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tailwindcss/node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/tailwindcss/node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/tapable": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", + "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/tar-fs/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tar-fs/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/tar-fs/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/tar-fs/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/tcp-port-used": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", + "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", + "dev": true, + "dependencies": { + "debug": "4.3.1", + "is2": "^2.0.6" + } + }, + "node_modules/tcp-port-used/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/tcp-port-used/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/teeny-request": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.3.tgz", + "integrity": "sha512-jJZpA5He2y52yUhA7pyAGZlgQpcB+xLjcN0eUFxr9c8hP/H7uOXbBNVo/O0C/xVfJLJs680jvkFgVJEEvk9+ww==", + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/teeny-request/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/teeny-request/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/teeny-request/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/teeny-request/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/terser": { + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/terser/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/text-extensions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", + "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/throttle-debounce": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", + "integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==", + "engines": { + "node": ">=10" + } + }, + "node_modules/throttleit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", + "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, + "node_modules/tlds": { + "version": "1.248.0", + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.248.0.tgz", + "integrity": "sha512-noj0KdpWTBhwsKxMOXk0rN9otg4kTgLm4WohERRHbJ9IY+kSDKr3RmjitaQ3JFzny+DyvBOQKlFZhp0G0qNSfg==", + "bin": { + "tlds": "bin.js" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true + }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-buffer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", + "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/to-through": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-3.0.0.tgz", + "integrity": "sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==", + "dev": true, + "dependencies": { + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/token-types": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.3.tgz", + "integrity": "sha512-IKJ6EzuPPWtKtEIEPpIdXv9j5j2LGJEYk0CKY2efgKoYKLBiZdh6iQkLVBow/CB3phyWAWCyk+bZeaimJn6uRQ==", + "dev": true, + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/traverse": { + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.11.tgz", + "integrity": "sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==", + "dependencies": { + "gopd": "^1.2.0", + "typedarray.prototype.slice": "^1.0.5", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tree-dump": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.3.tgz", + "integrity": "sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/trim-trailing-lines": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-2.1.0.tgz", + "integrity": "sha512-5UR5Biq4VlVOtzqkm2AZlgvSlDJtME46uV0br0gENbwN4l5+mMKT4b9gJKqWtuL2zAIqajGJGuvbCbcAJUZqBg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/trough": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-easing": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz", + "integrity": "sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==" + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "node_modules/ts-jest": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-loader": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz", + "integrity": "sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig-paths-webpack-plugin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.0.0.tgz", + "integrity": "sha512-fw/7265mIWukrSHd0i+wSwx64kYUSAKPfxRDksjKIYTxSAp9W9/xcZVBF4Kl0eqQd5eBpAQ/oQrc5RyM/0c1GQ==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tsconfig-paths": "^4.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "dev": true, + "engines": { + "node": ">=0.6.x" + } + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/turndown": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz", + "integrity": "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==", + "dependencies": { + "@mixmark-io/domino": "^2.2.0" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, + "node_modules/twitter-api-v2": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/twitter-api-v2/-/twitter-api-v2-1.15.1.tgz", + "integrity": "sha512-KNxoJL+sldWMI3AooPGcNkbP8awQai93d9xxsTurVPuUo/qnOUR3iO0XZTGC5sezdejHHqNyTwBAgGGw948MDg==" + }, + "node_modules/typanion": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/typanion/-/typanion-3.14.0.tgz", + "integrity": "sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-assert": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", + "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "dev": true + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, + "node_modules/typedarray.prototype.slice": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.5.tgz", + "integrity": "sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "math-intrinsics": "^1.1.0", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-offset": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typeorm": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.18.tgz", + "integrity": "sha512-St/k5Rdk3Qq0aiYiUw/FRq5i+VqPrqn4ld6m1fqfYJ6uIIK26KFFU9eE0Vl1zdLHkCxTW6Sj4JAL5I3B5N1/5g==", + "dependencies": { + "@sqltools/formatter": "^1.2.5", + "app-root-path": "^3.1.0", + "buffer": "^6.0.3", + "chalk": "^4.1.2", + "cli-highlight": "^2.1.11", + "dayjs": "^1.11.9", + "debug": "^4.3.4", + "dotenv": "^16.0.3", + "glob": "^10.3.10", + "mkdirp": "^2.1.3", + "reflect-metadata": "^0.1.13", + "sha.js": "^2.4.11", + "tslib": "^2.5.0", + "uuid": "^9.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "typeorm": "cli.js", + "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", + "typeorm-ts-node-esm": "cli-ts-node-esm.js" + }, + "engines": { + "node": ">= 12.9.0" + }, + "funding": { + "url": "https://opencollective.com/typeorm" + }, + "peerDependencies": { + "@google-cloud/spanner": "^5.18.0", + "@sap/hana-client": "^2.12.25", + "better-sqlite3": "^7.1.2 || ^8.0.0 || ^9.0.0", + "hdb-pool": "^0.1.6", + "ioredis": "^5.0.4", + "mongodb": "^5.8.0", + "mssql": "^9.1.1 || ^10.0.1", + "mysql2": "^2.2.5 || ^3.0.1", + "oracledb": "^6.3.0", + "pg": "^8.5.1", + "pg-native": "^3.0.0", + "pg-query-stream": "^4.0.0", + "redis": "^3.1.1 || ^4.0.0", + "sql.js": "^1.4.0", + "sqlite3": "^5.0.3", + "ts-node": "^10.7.0", + "typeorm-aurora-data-api-driver": "^2.0.0" + }, + "peerDependenciesMeta": { + "@google-cloud/spanner": { + "optional": true + }, + "@sap/hana-client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "hdb-pool": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mssql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "pg-query-stream": { + "optional": true + }, + "redis": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "ts-node": { + "optional": true + }, + "typeorm-aurora-data-api-driver": { + "optional": true + } + } + }, + "node_modules/typeorm/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/typeorm/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/typeorm/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, + "node_modules/typeorm/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/node_modules/mkdirp": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", + "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "1.0.40", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz", + "integrity": "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uid2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-1.0.0.tgz", + "integrity": "sha512-+I6aJUv63YAcY9n4mQreLUt0d4lvwkkopDNmpomkAUz0fAkEMV9pRWxN0EjhW1YfRhcuyHg2v3mwddCDW1+LFQ==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/uint8array-extras": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz", + "integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/unbzip2-stream/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==" + }, + "node_modules/underscore.string": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz", + "integrity": "sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==", + "dev": true, + "dependencies": { + "sprintf-js": "^1.1.1", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/undici": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.11.0.tgz", + "integrity": "sha512-heTSIac3iLhsmZhUCjyS3JQEkZELateufzZuBaVM5RHXdSBMb1LPMQf5x+FH7qjsZYDP0ttAc3nnVpUB+wYbOg==", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unified": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.2.tgz", + "integrity": "sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==", + "dependencies": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^2.0.0", + "trough": "^1.0.0", + "vfile": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/unified/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/unified/node_modules/unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dependencies": { + "@types/unist": "^2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/vfile": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", + "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^2.0.0", + "vfile-message": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/vfile-message": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", + "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/unist-builder": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-4.0.0.tgz", + "integrity": "sha512-wmRFnH+BLpZnTKpc5L7O67Kac89s9HMrtELpnNaE6TAobq5DTZZs5YaTQfAZBA9bFPECx2uVAPO31c+GVug8mg==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-map/-/unist-util-map-4.0.0.tgz", + "integrity": "sha512-HJs1tpkSmRJUzj6fskQrS5oYhBYlmtcvy4SepdDEEsL04FjBrgF0Mgggvxc1/qGBGgW7hRh9+UBK1aqTEnBpIA==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", + "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", + "dependencies": { + "@types/unist": "^3.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-4.0.0.tgz", + "integrity": "sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", + "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unix-crypt-td-js": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.1.4.tgz", + "integrity": "sha512-8rMeVYWSIyccIJscb9NdCfZKSRBKYTeVnwmiRYT2ulE3qd1RaDQ0xQDP+rI3ccIWbhu/zuo5cgN8z73belNZgw==", + "dev": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/url": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.11.2" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-composed-ref": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz", + "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-debounce": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.1.tgz", + "integrity": "sha512-0uUXjOfm44e6z4LZ/woZvkM8FwV1wiuoB6xnrrOmeAEjRDDzTLQNRFtYHvqUsJdrz1X37j0rVGIVp144GLHGKg==", + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/use-deep-compare-effect": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/use-deep-compare-effect/-/use-deep-compare-effect-1.8.1.tgz", + "integrity": "sha512-kbeNVZ9Zkc0RFGpfMN3MNfaKNvcLNyxOAAd9O4CBZ+kCBXXscn9s/4I+8ytUER4RDpEYs5+O6Rs4PqiZ+rHr5Q==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "dequal": "^2.0.2" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "react": ">=16.13" + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", + "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz", + "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-ripple-hook": { + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/use-ripple-hook/-/use-ripple-hook-1.0.24.tgz", + "integrity": "sha512-K4/Xhf9mP4pwMiI/wvBR9Je9KH0qWzLVLDgaW61yM6YOXLSgYRkF5DUXl7Nq7ltk1GUHikxKhxT1Ic7GZcO6OA==" + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/usehooks-ts": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.0.tgz", + "integrity": "sha512-bBIa7yUyPhE1BCc0GmR96VU/15l/9gP1Ch5mYdLcFBaFGQsdmXkvjV0TtOqW1yUd6VjIwDunm+flSciCQXujiw==", + "dependencies": { + "lodash.debounce": "^4.0.8" + }, + "engines": { + "node": ">=16.15.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/value-or-function": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", + "integrity": "sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==", + "dev": true, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/vanilla-picker": { + "version": "2.12.3", + "resolved": "https://registry.npmjs.org/vanilla-picker/-/vanilla-picker-2.12.3.tgz", + "integrity": "sha512-qVkT1E7yMbUsB2mmJNFmaXMWE2hF8ffqzMMwe9zdAikd8u2VfnsVY2HQcOUi2F38bgbxzlJBEdS1UUhOXdF9GQ==", + "dependencies": { + "@sphinxxxx/color-conversion": "^2.2.2" + } + }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "dev": true + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vaul": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", + "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/verdaccio": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/verdaccio/-/verdaccio-6.1.2.tgz", + "integrity": "sha512-HQCquycSQkA+tKRVqMjIVRzmhzTciLfScvKIhhiwZZ9Qd13e2KJQTOdB7QrSacfJuPpl94TA5EZ7XmVRQKk3ag==", + "dev": true, + "dependencies": { + "@cypress/request": "3.0.8", + "@verdaccio/auth": "8.0.0-next-8.15", + "@verdaccio/config": "8.0.0-next-8.15", + "@verdaccio/core": "8.0.0-next-8.15", + "@verdaccio/loaders": "8.0.0-next-8.6", + "@verdaccio/local-storage-legacy": "11.0.2", + "@verdaccio/logger": "8.0.0-next-8.15", + "@verdaccio/middleware": "8.0.0-next-8.15", + "@verdaccio/search-indexer": "8.0.0-next-8.4", + "@verdaccio/signature": "8.0.0-next-8.7", + "@verdaccio/streams": "10.2.1", + "@verdaccio/tarball": "13.0.0-next-8.15", + "@verdaccio/ui-theme": "8.0.0-next-8.15", + "@verdaccio/url": "13.0.0-next-8.15", + "@verdaccio/utils": "8.1.0-next-8.15", + "async": "3.2.6", + "clipanion": "4.0.0-rc.4", + "compression": "1.8.0", + "cors": "2.8.5", + "debug": "4.4.0", + "envinfo": "7.14.0", + "express": "4.21.2", + "handlebars": "4.7.8", + "JSONStream": "1.3.5", + "lodash": "4.17.21", + "lru-cache": "7.18.3", + "mime": "3.0.0", + "mkdirp": "1.0.4", + "pkginfo": "0.4.1", + "semver": "7.6.3", + "verdaccio-audit": "13.0.0-next-8.15", + "verdaccio-htpasswd": "13.0.0-next-8.15" + }, + "bin": { + "verdaccio": "bin/verdaccio" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/verdaccio-audit": { + "version": "13.0.0-next-8.15", + "resolved": "https://registry.npmjs.org/verdaccio-audit/-/verdaccio-audit-13.0.0-next-8.15.tgz", + "integrity": "sha512-Aeau0u0fi5l4PoSDyOV6glz2FDO9+ofvogJIELV4H6fhDXhgPc2MnoKuaUgOT//khESLle/a6YfcLY2/KNLs6g==", + "dev": true, + "dependencies": { + "@verdaccio/config": "8.0.0-next-8.15", + "@verdaccio/core": "8.0.0-next-8.15", + "express": "4.21.2", + "https-proxy-agent": "5.0.1", + "node-fetch": "cjs" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/verdaccio-audit/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/verdaccio-audit/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/verdaccio-audit/node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/verdaccio-audit/node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/verdaccio-audit/node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/verdaccio-audit/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/verdaccio-audit/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/verdaccio-audit/node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/verdaccio-audit/node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/verdaccio-audit/node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/verdaccio-audit/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verdaccio-audit/node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/verdaccio-audit/node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/verdaccio-audit/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/verdaccio-audit/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/verdaccio-audit/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/verdaccio-audit/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/verdaccio-audit/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/verdaccio-audit/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/verdaccio-audit/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/verdaccio-audit/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true + }, + "node_modules/verdaccio-audit/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/verdaccio-audit/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verdaccio-audit/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/verdaccio-audit/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/verdaccio-audit/node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/verdaccio-audit/node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/verdaccio-audit/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verdaccio-audit/node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/verdaccio-audit/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verdaccio-audit/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/verdaccio-htpasswd": { + "version": "13.0.0-next-8.15", + "resolved": "https://registry.npmjs.org/verdaccio-htpasswd/-/verdaccio-htpasswd-13.0.0-next-8.15.tgz", + "integrity": "sha512-rQg5oZ/rReDAM4g4W68hvtzReTbM6vduvVtobHsQxhbtbotEuUjP6O8uaROYtgZ60giGva5Tub2SOm2T9Ln9Dw==", + "dev": true, + "dependencies": { + "@verdaccio/core": "8.0.0-next-8.15", + "@verdaccio/file-locking": "13.0.0-next-8.3", + "apache-md5": "1.1.8", + "bcryptjs": "2.4.3", + "core-js": "3.40.0", + "debug": "4.4.0", + "http-errors": "2.0.0", + "unix-crypt-td-js": "1.1.4" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/verdaccio-htpasswd/node_modules/@verdaccio/file-locking": { + "version": "13.0.0-next-8.3", + "resolved": "https://registry.npmjs.org/@verdaccio/file-locking/-/file-locking-13.0.0-next-8.3.tgz", + "integrity": "sha512-Sugx6XYp8nEJ9SmBoEOExEIQQ0T0q8fcyc/afWdiSNDGWviqqSx2IriCvtMwKZrE4XG0BQo6bXO+A8AOOoo7KQ==", + "dev": true, + "dependencies": { + "lockfile": "1.0.4" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } + }, + "node_modules/verdaccio-htpasswd/node_modules/core-js": { + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz", + "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/verdaccio-htpasswd/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/verdaccio/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/verdaccio/node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/verdaccio/node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/verdaccio/node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/verdaccio/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/verdaccio/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/verdaccio/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/verdaccio/node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/verdaccio/node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/verdaccio/node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/verdaccio/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verdaccio/node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/verdaccio/node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/verdaccio/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/verdaccio/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/verdaccio/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/verdaccio/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/verdaccio/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/verdaccio/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/verdaccio/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/verdaccio/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true + }, + "node_modules/verdaccio/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/verdaccio/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verdaccio/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/verdaccio/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/verdaccio/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/verdaccio/node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/verdaccio/node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/verdaccio/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verdaccio/node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/verdaccio/node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/verdaccio/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verdaccio/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-matter": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/vfile-matter/-/vfile-matter-5.0.1.tgz", + "integrity": "sha512-o6roP82AiX0XfkyTHyRCMXgHfltUNlXSEqCIS80f+mbAyiQBE2fxtDVMtseyytGx75sihiJFo/zR6r/4LTs2Cw==", + "dependencies": { + "vfile": "^6.0.0", + "yaml": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/victory-vendor/node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/vinyl": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", + "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", + "dev": true, + "dependencies": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-contents": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", + "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", + "dev": true, + "dependencies": { + "bl": "^5.0.0", + "vinyl": "^3.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-contents/node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/vinyl-contents/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/vinyl-contents/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/vinyl-contents/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/vinyl-fs": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.2.tgz", + "integrity": "sha512-XRFwBLLTl8lRAOYiBqxY279wY46tVxLaRhSwo3GzKEuLz1giffsOquWWboD/haGf5lx+JyTigCFfe7DWHoARIA==", + "dev": true, + "dependencies": { + "fs-mkdirp-stream": "^2.0.1", + "glob-stream": "^8.0.3", + "graceful-fs": "^4.2.11", + "iconv-lite": "^0.6.3", + "is-valid-glob": "^1.0.0", + "lead": "^4.0.0", + "normalize-path": "3.0.0", + "resolve-options": "^2.0.0", + "stream-composer": "^1.0.2", + "streamx": "^2.14.0", + "to-through": "^3.0.0", + "value-or-function": "^4.0.0", + "vinyl": "^3.0.1", + "vinyl-sourcemap": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-sourcemap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz", + "integrity": "sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q==", + "dev": true, + "dependencies": { + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.10", + "now-and-later": "^3.0.0", + "streamx": "^2.12.5", + "vinyl": "^3.0.0", + "vinyl-contents": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.8.tgz", + "integrity": "sha512-6PhR4H9VGlcwXZ+KWCdMqbtG649xCPZqfI9j2PsK1FcXgEzro5bGHcVKFCTqPLaNKZES8Evqv4LwvZARsq5qlg==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "node_modules/vite-plugin-checker": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.7.2.tgz", + "integrity": "sha512-xeYeJbG0gaCaT0QcUC4B2Zo4y5NR8ZhYenc5gPbttrZvraRFwkEADCYwq+BfEHl9zYz7yf85TxsiGoYwyyIjhw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "ansi-escapes": "^4.3.0", + "chalk": "^4.1.1", + "chokidar": "^3.5.1", + "commander": "^8.0.0", + "fast-glob": "^3.2.7", + "fs-extra": "^11.1.0", + "npm-run-path": "^4.0.1", + "strip-ansi": "^6.0.0", + "tiny-invariant": "^1.1.0", + "vscode-languageclient": "^7.0.0", + "vscode-languageserver": "^7.0.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-uri": "^3.0.2" + }, + "engines": { + "node": ">=14.16" + }, + "peerDependencies": { + "@biomejs/biome": ">=1.7", + "eslint": ">=7", + "meow": "^9.0.0", + "optionator": "^0.9.1", + "stylelint": ">=13", + "typescript": "*", + "vite": ">=2.0.0", + "vls": "*", + "vti": "*", + "vue-tsc": ">=2.0.0" + }, + "peerDependenciesMeta": { + "@biomejs/biome": { + "optional": true + }, + "eslint": { + "optional": true + }, + "meow": { + "optional": true + }, + "optionator": { + "optional": true + }, + "stylelint": { + "optional": true + }, + "typescript": { + "optional": true + }, + "vls": { + "optional": true + }, + "vti": { + "optional": true + }, + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/vite-plugin-checker/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/vite/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/vite/node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/vitest": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.8.tgz", + "integrity": "sha512-dfqAsNqRGUc8hB9OVR2P0w8PZPEckti2+5rdZip0WIz9WW0MnImJ8XiR61QhqLa92EQzKP2uPkzenKOAHyEIbA==", + "dev": true, + "dependencies": { + "@vitest/expect": "3.0.8", + "@vitest/mocker": "3.0.8", + "@vitest/pretty-format": "^3.0.8", + "@vitest/runner": "3.0.8", + "@vitest/snapshot": "3.0.8", + "@vitest/spy": "3.0.8", + "@vitest/utils": "3.0.8", + "chai": "^5.2.0", + "debug": "^4.4.0", + "expect-type": "^1.1.0", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.0.8", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.0.8", + "@vitest/ui": "3.0.8", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@vitest/pretty-format": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.8.tgz", + "integrity": "sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==", + "dev": true, + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/@vitest/utils": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.8.tgz", + "integrity": "sha512-nkBC3aEhfX2PdtQI/QwAWp8qZWwzASsU4Npbcd5RdMPBSSLCpkZp52P3xku3s3uA0HIEhGvEcF8rNkBsz9dQ4Q==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "3.0.8", + "loupe": "^3.1.3", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/loupe": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", + "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", + "dev": true + }, + "node_modules/vitest/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vscode-jsonrpc": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", + "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==", + "dev": true, + "engines": { + "node": ">=8.0.0 || >=10.0.0" + } + }, + "node_modules/vscode-languageclient": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz", + "integrity": "sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==", + "dev": true, + "dependencies": { + "minimatch": "^3.0.4", + "semver": "^7.3.4", + "vscode-languageserver-protocol": "3.16.0" + }, + "engines": { + "vscode": "^1.52.0" + } + }, + "node_modules/vscode-languageclient/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/vscode-languageclient/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/vscode-languageserver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz", + "integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==", + "dev": true, + "dependencies": { + "vscode-languageserver-protocol": "3.16.0" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz", + "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==", + "dev": true, + "dependencies": { + "vscode-jsonrpc": "6.0.0", + "vscode-languageserver-types": "3.16.0" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "dev": true + }, + "node_modules/vscode-languageserver-types": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", + "dev": true + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/wait-on": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", + "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", + "dev": true, + "dependencies": { + "axios": "^1.6.1", + "joi": "^17.11.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "rxjs": "^7.8.1" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/walk-sync": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-2.2.0.tgz", + "integrity": "sha512-IC8sL7aB4/ZgFcGI2T1LczZeFWZ06b3zoHH7jBPyHxOtIIz1jppWHjjEXkOFvFojBVAK9pV7g47xOZ4LW3QLfg==", + "dev": true, + "dependencies": { + "@types/minimatch": "^3.0.3", + "ensure-posix-path": "^1.1.0", + "matcher-collection": "^2.0.0", + "minimatch": "^3.0.4" + }, + "engines": { + "node": "8.* || >= 10.*" + } + }, + "node_modules/walk-sync/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/walk-sync/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/wcwidth/node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/wcwidth/node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/web-vitals": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", + "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==" + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/webpack": { + "version": "5.98.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz", + "integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", + "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.6.0", + "mime-types": "^2.1.31", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware/node_modules/memfs": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz", + "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==", + "dev": true, + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/webpack-dev-server": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz", + "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.21.2", + "graceful-fs": "^4.2.6", + "http-proxy-middleware": "^2.0.9", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true + }, + "node_modules/webpack-dev-server/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/webpack-dev-server/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/webpack-dev-server/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/webpack-dev-server/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/webpack-dev-server/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/open": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", + "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==", + "dev": true, + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true + }, + "node_modules/webpack-dev-server/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/webpack-dev-server/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/webpack-dev-server/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/webpack-dev-server/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/webpack-dev-server/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-ignore-dynamic-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/webpack-ignore-dynamic-require/-/webpack-ignore-dynamic-require-1.0.0.tgz", + "integrity": "sha512-WeGFPgwDochKPwizAu5XsHcPq3aaQLl2E+n1piD/VPGNUo5HIwrtURWNMfrPDfkHVOx+flkAihXbUiILAv5x4Q==", + "dev": true + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-node-externals": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", + "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-subresource-integrity": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", + "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", + "dev": true, + "dependencies": { + "typed-assert": "^1.0.8" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "html-webpack-plugin": ">= 5.0.0-beta.1 < 6", + "webpack": "^5.12.0" + }, + "peerDependenciesMeta": { + "html-webpack-plugin": { + "optional": true + } + } + }, + "node_modules/webpack/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/winston-transport/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/winston-transport/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/winston/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/winston/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/winston/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/winston/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==" + }, + "node_modules/xml-crypto": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-6.1.2.tgz", + "integrity": "sha512-leBOVQdVi8FvPJrMYoum7Ici9qyxfE4kVi+AkpUoYCSXaQF4IlBm1cneTK9oAxR61LpYxTx7lNcsnBIeRpGW2w==", + "dependencies": { + "@xmldom/is-dom-node": "^1.0.1", + "@xmldom/xmldom": "^0.8.10", + "xpath": "^0.0.33" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/xml-crypto/node_modules/xpath": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.33.tgz", + "integrity": "sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA==", + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/xml-escape": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xml-escape/-/xml-escape-1.1.0.tgz", + "integrity": "sha512-B/T4sDK8Z6aUh/qNr7mjKAwwncIljFuUP+DO/D5hloYFj+90O88z8Wf7oSucZTHxBAsC1/CTP4rtx/x1Uf72Mg==" + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/xmlrpc": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/xmlrpc/-/xmlrpc-1.3.2.tgz", + "integrity": "sha512-jQf5gbrP6wvzN71fgkcPPkF4bF/Wyovd7Xdff8d6/ihxYmgETQYSuTc+Hl+tsh/jmgPLro/Aro48LMFlIyEKKQ==", + "dependencies": { + "sax": "1.2.x", + "xmlbuilder": "8.2.x" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.0.0" + } + }, + "node_modules/xmlrpc/node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/xmlrpc/node_modules/xmlbuilder": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", + "integrity": "sha512-eKRAFz04jghooy8muekqzo8uCSVNeyRedbuJrp0fovbLIi7wlsYtdUn3vBAAPq2Y3/0xMz2WMEUQ8yhVVO9Stw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xpath": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", + "integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==", + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/xxhashjs": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", + "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", + "dev": true, + "dependencies": { + "cuint": "^0.2.2" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", + "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz", + "integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ylru": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz", + "integrity": "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.67", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", + "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "peerDependencies": { + "zod": "^3.24.1" + } + }, + "node_modules/zone.js": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.4.tgz", + "integrity": "sha512-NtTUvIlNELez7Q1DzKVIFZBzNb646boQMgpATo9z3Ftuu/gWvzxCW7jdjcUDoRGxRikrhVHB/zLXh1hxeJawvw==", + "dependencies": { + "tslib": "^2.3.0" + } + }, + "node_modules/zustand": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.4.tgz", + "integrity": "sha512-/BPMyLKJPtFEvVL0E9E9BTUM63MNyhPGlvxk1XjrfWTUlV+BR8jufjsovHzrtR6YNcBEcL7cMHovL1n9xHawEg==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/zustand/node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..089af17 --- /dev/null +++ b/package.json @@ -0,0 +1,443 @@ +{ + "name": "activepieces", + "version": "0.64.2", + "rcVersion": "0.64.0-rc.0", + "scripts": { + "prepare": "husky install", + "serve:frontend": "nx serve react-ui", + "serve:backend": "nx serve server-api", + "serve:engine": "nx serve engine", + "dev": "concurrently -n \"GUI,API,ENG\" -c \"bgBlue.bold,bgGreen.bold,bgRed.bold\" \"npm:serve:frontend\" \"npm:serve:backend\" \"npm:serve:engine\"", + "dev:backend": "concurrently -n \"API,ENG\" -c \"bgGreen.bold,bgRed.bold\" \"npm:serve:backend\" \"npm:serve:engine\"", + "dev:frontend": "concurrently -n \"GUI,API,ENG\" -c \"bgBlue.bold,bgGreen.bold,bgRed.bold\" \"npm:serve:frontend\" \"npm:serve:backend\" \"npm:serve:engine\"", + "start": "npm i && npm run dev", + "cli": "npx ts-node packages/cli/src/index.ts", + "create-piece": "npx ts-node packages/cli/src/index.ts pieces create", + "create-action": "npx ts-node packages/cli/src/index.ts actions create", + "create-trigger": "npx ts-node packages/cli/src/index.ts triggers create", + "sync-pieces": "npx ts-node packages/cli/src/index.ts pieces sync", + "build-piece": "npx ts-node packages/cli/src/index.ts pieces build", + "publish-piece-to-api": "npx ts-node packages/cli/src/index.ts pieces publish piece", + "publish-piece": "npx ts-node --project tools/tsconfig.tools.json tools/scripts/pieces/publish-piece.ts", + "workers": "npx ts-node packages/cli/src/index.ts workers", + "pull-i18n": "crowdin pull --config crowdin.yml", + "push-i18n": "crowdin upload sources", + "i18n:extract": "i18next --config packages/react-ui/i18next-parser.config.js && jq 'to_entries | map({(.key): .key}) | add' packages/react-ui/public/locales/en/translation.json > packages/react-ui/public/locales/en/translation.tmp.json && mv packages/react-ui/public/locales/en/translation.tmp.json packages/react-ui/public/locales/en/translation.json" + }, + "private": true, + "dependencies": { + "@activepieces/import-fresh-webpack": "3.3.0", + "@ai-sdk/anthropic": "1.2.12", + "@ai-sdk/azure": "1.0.10", + "@ai-sdk/google": "1.2.19", + "@ai-sdk/openai": "1.3.22", + "@ai-sdk/replicate": "0.2.8", + "@anthropic-ai/sdk": "0.39.0", + "@authenio/samlify-node-xmllint": "2.0.0", + "@aws-sdk/client-s3": "3.637.0", + "@aws-sdk/s3-request-presigner": "3.658.1", + "@azure/communication-email": "1.0.0", + "@azure/openai": "1.0.0-beta.11", + "@babel/runtime": "7.26.10", + "@bull-board/api": "6.10.1", + "@bull-board/fastify": "6.10.1", + "@codemirror/lang-javascript": "6.2.2", + "@codemirror/lang-json": "6.0.1", + "@datastructures-js/queue": "4.2.3", + "@dnd-kit/core": "6.1.0", + "@dnd-kit/modifiers": "7.0.0", + "@dnd-kit/sortable": "8.0.0", + "@fastify/basic-auth": "6.2.0", + "@fastify/cors": "11.0.1", + "@fastify/formbody": "8.0.2", + "@fastify/http-proxy": "11.1.2", + "@fastify/multipart": "9.0.3", + "@fastify/rate-limit": "10.3.0", + "@fastify/swagger": "9.5.1", + "@fastify/type-provider-typebox": "5.1.0", + "@google/generative-ai": "0.21.0", + "@hookform/resolvers": "3.9.0", + "@mailchimp/mailchimp_marketing": "3.0.80", + "@mailerlite/mailerlite-nodejs": "1.1.0", + "@microsoft/microsoft-graph-client": "3.0.7", + "@microsoft/microsoft-graph-types": "2.40.0", + "@modelcontextprotocol/sdk": "1.11.0", + "@notionhq/client": "2.2.11", + "@nx/devkit": "21.1.2", + "@octokit/rest": "21.1.1", + "@onfleet/node-onfleet": "1.3.3", + "@qdrant/js-client-rest": "1.7.0", + "@radix-ui/react-accordion": "1.2.4", + "@radix-ui/react-avatar": "1.1.0", + "@radix-ui/react-checkbox": "1.1.1", + "@radix-ui/react-collapsible": "1.1.0", + "@radix-ui/react-context-menu": "2.2.2", + "@radix-ui/react-dialog": "1.1.1", + "@radix-ui/react-dropdown-menu": "2.1.1", + "@radix-ui/react-icons": "1.3.0", + "@radix-ui/react-label": "2.1.0", + "@radix-ui/react-popover": "1.1.1", + "@radix-ui/react-progress": "1.1.0", + "@radix-ui/react-radio-group": "1.2.0", + "@radix-ui/react-scroll-area": "1.1.0", + "@radix-ui/react-select": "2.1.1", + "@radix-ui/react-separator": "1.1.0", + "@radix-ui/react-slider": "1.3.5", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-switch": "1.1.0", + "@radix-ui/react-tabs": "1.1.0", + "@radix-ui/react-toast": "1.2.1", + "@radix-ui/react-toggle": "1.1.0", + "@radix-ui/react-tooltip": "1.1.2", + "@rollup/wasm-node": "4.21.2", + "@segment/analytics-next": "1.72.0", + "@segment/analytics-node": "2.2.0", + "@sendgrid/mail": "8.0.0", + "@sentry/node": "7.120.0", + "@sinclair/typebox": "0.34.11", + "@slack/web-api": "7.9.0", + "@socket.io/redis-adapter": "8.2.1", + "@supabase/supabase-js": "2.49.9", + "@tanstack/react-query": "5.51.1", + "@tanstack/react-table": "8.19.2", + "@tanstack/react-virtual": "3.13.11", + "@tiptap/extension-mention": "2.5.4", + "@tiptap/extension-placeholder": "2.5.5", + "@tiptap/pm": "2.5.4", + "@tiptap/react": "2.5.4", + "@tiptap/starter-kit": "2.5.4", + "@tiptap/suggestion": "2.5.4", + "@tryfabric/martian": "1.2.0", + "@types/amqplib": "0.10.5", + "@types/docusign-esign": "5.19.9", + "@types/imapflow": "1.0.18", + "@types/js-yaml": "4.0.9", + "@types/pg-format": "1.0.5", + "@types/showdown": "2.0.6", + "@uiw/codemirror-theme-github": "4.23.0", + "@uiw/react-codemirror": "4.23.0", + "@xyflow/react": "12.3.5", + "ai": "4.3.16", + "airtable": "0.11.6", + "ajv": "8.12.0", + "amqplib": "0.10.4", + "assemblyai": "4.7.0", + "async-mutex": "0.4.0", + "axios": "1.8.3", + "axios-retry": "4.4.1", + "basic-ftp": "5.0.5", + "bcrypt": "6.0.0", + "boring-avatars": "1.11.2", + "buffer": "6.0.3", + "bullmq": "5.28.1", + "cheerio": "1.0.0-rc.12", + "chokidar": "3.6.0", + "class-variance-authority": "0.7.0", + "clear-module": "4.1.2", + "cli-table3": "0.6.3", + "clipboard": "2.0.11", + "clsx": "2.1.1", + "cmdk": "^1.1.1", + "codemirror": "5.65.14", + "color": "4.2.3", + "commander": "11.1.0", + "compare-versions": "6.1.0", + "concat": "1.0.3", + "contrast-color": "1.0.1", + "cron-validator": "1.3.1", + "cronstrue": "2.31.0", + "cross-spawn": "7.0.6", + "crypto-js": "4.2.0", + "csv-parse": "5.6.0", + "csv-reader": "1.0.12", + "csv-stringify": "6.5.2", + "date-fns": "3.6.0", + "dayjs": "1.11.9", + "decompress": "4.2.1", + "deep-equal": "2.2.2", + "deepmerge-ts": "7.1.0", + "docusign-esign": "8.1.0", + "drip-nodejs": "3.1.2", + "elevenlabs": "0.2.2", + "embla-carousel-react": "8.1.8", + "eventsource-parser": "3.0.2", + "exifreader": "4.20.0", + "fast-glob": "3.3.3", + "fastify": "5.4.0", + "fastify-favicon": "5.0.0", + "fastify-plugin": "5.0.1", + "fastify-raw-body": "5.0.0", + "fastify-socket": "5.1.2", + "fastify-xml-body-parser": "2.2.0", + "feedparser": "2.2.10", + "fetch-retry": "6.0.0", + "firebase-scrypt": "2.2.0", + "flowtoken": "1.0.40", + "font-awesome": "4.7.0", + "form-data": "4.0.0", + "framer-motion": "12.15.0", + "frimousse": "^0.2.0", + "fs-extra": "11.2.0", + "fuse.js": "7.0.0", + "google-auth-library": "8.9.0", + "googleapis": "129.0.0", + "http-status-codes": "2.2.0", + "https-proxy-agent": "7.0.4", + "i18next": "23.13.0", + "i18next-browser-languagedetector": "8.0.0", + "i18next-http-backend": "2.5.2", + "i18next-icu": "2.3.0", + "imapflow": "1.0.152", + "intercom-client": "6.0.0", + "intl-messageformat": "10.5.14", + "ioredis": "5.4.1", + "is-base64": "1.1.0", + "isolated-vm": "5.0.1", + "js-yaml": "4.1.0", + "jsdom": "23.0.1", + "jshint": "2.13.6", + "json-server": "1.0.0-beta.0", + "json-to-pretty-yaml": "1.2.2", + "json2xml": "0.1.3", + "jsoneditor": "10.0.3", + "jsonlint-mod": "1.7.6", + "jsonrepair": "3.2.0", + "jsonwebtoken": "9.0.1", + "jszip": "3.10.1", + "jwks-rsa": "3.1.0", + "jwt-decode": "4.0.0", + "lottie-web": "5.12.2", + "lucide-react": "0.407.0", + "mailparser": "3.6.7", + "marked": "4.3.0", + "mime": "4.0.1", + "mime-types": "2.1.35", + "mintlify": "4.0.395", + "monday-sdk-js": "0.5.2", + "mongodb": "6.15.0", + "motion": "12.16.0", + "mustache": "4.2.0", + "nanoid": "3.3.8", + "next-themes": "0.4.6", + "node-cron": "3.0.3", + "nodemailer": "6.9.9", + "notion-to-md": "3.1.1", + "nx-cloud": "19.1.0", + "object-sizeof": "2.6.3", + "openai": "4.67.1", + "p-limit": "2.3.0", + "pako": "2.1.0", + "papaparse": "5.5.3", + "pg": "8.11.3", + "pg-format": "1.0.4", + "pickleparser": "0.1.0", + "pino-loki": "2.1.3", + "playwright": "1.52.0", + "posthog-js": "1.195.0", + "priority-queue-typescript": "1.0.1", + "prismjs": "1.30.0", + "promise-mysql": "5.2.0", + "qrcode": "1.5.4", + "qs": "6.11.2", + "react": "18.3.1", + "react-colorful": "5.6.1", + "react-data-grid": "7.0.0-beta.47", + "react-day-picker": "8.10.1", + "react-dom": "18.3.1", + "react-error-boundary": "5.0.0", + "react-hook-form": "7.52.2", + "react-i18next": "15.0.1", + "react-json-view": "1.21.3", + "react-lottie": "1.2.4", + "react-markdown": "9.0.1", + "react-resizable-panels": "2.0.20", + "react-ripples": "2.2.1", + "react-router-dom": "6.11.2", + "react-syntax-highlighter": "15.4.2", + "react-textarea-autosize": "8.5.5", + "react-use": "17.5.1", + "recharts": "2.12.7", + "redlock": "5.0.0-beta.2", + "remark-gfm": "4.0.0", + "replicate": "0.34.1", + "rollup": "4.22.5", + "rss-parser": "3.13.0", + "safe-flat": "2.1.0", + "samlify": "2.10.0", + "semver": "7.6.0", + "showdown": "2.1.0", + "simple-git": "3.21.0", + "slackify-markdown": "4.4.0", + "slugify": "1.6.6", + "snowflake-sdk": "1.9.3", + "soap": "1.1.10", + "socket.io": "4.8.1", + "socket.io-client": "4.7.5", + "sonner": "2.0.3", + "sqlite3": "5.1.7", + "sqlstring": "2.3.3", + "ssh2-sftp-client": "9.1.0", + "string-replace-async": "3.0.2", + "string-strip-html": "8.5.0", + "stripe": "18.2.1", + "tailwind-merge": "2.4.0", + "tailwind-scrollbar": "4.0.2", + "tailwindcss-animate": "1.0.7", + "tinycolor2": "1.6.0", + "tree-kill": "1.2.2", + "tsconfig-paths": "4.2.0", + "tslib": "2.6.2", + "turndown": "7.2.0", + "twitter-api-v2": "1.15.1", + "typeorm": "0.3.18", + "url": "0.11.3", + "use-debounce": "10.0.1", + "use-deep-compare-effect": "1.8.1", + "use-ripple-hook": "1.0.24", + "usehooks-ts": "3.1.0", + "vaul": "1.1.2", + "write-file-atomic": "5.0.1", + "xml2js": "0.6.2", + "xmlrpc": "1.3.2", + "yaml": "2.4.1", + "zone.js": "0.14.4", + "zustand": "4.5.4" + }, + "devDependencies": { + "@commitlint/cli": "17.7.1", + "@commitlint/config-conventional": "17.7.0", + "@faker-js/faker": "8.2.0", + "@nx/esbuild": "21.1.2", + "@nx/eslint": "21.1.2", + "@nx/eslint-plugin": "21.1.2", + "@nx/jest": "21.1.2", + "@nx/js": "21.1.2", + "@nx/node": "21.1.2", + "@nx/react": "21.1.2", + "@nx/vite": "21.1.2", + "@nx/web": "21.1.2", + "@nx/webpack": "21.1.2", + "@nx/workspace": "21.1.2", + "@playwright/test": "1.37.1", + "@swc-node/register": "1.9.2", + "@swc/cli": "0.6.0", + "@swc/core": "1.5.7", + "@swc/helpers": "0.5.11", + "@testing-library/react": "15.0.6", + "@types/bcrypt": "5.0.0", + "@types/color": "3.0.6", + "@types/contrast-color": "1.0.3", + "@types/crypto-js": "4.1.1", + "@types/decompress": "4.2.4", + "@types/deep-equal": "1.0.1", + "@types/feedparser": "2.2.5", + "@types/imap": "0.8.37", + "@types/is-base64": "1.1.1", + "@types/jest": "29.5.13", + "@types/json2xml": "0.1.1", + "@types/mailchimp__mailchimp_marketing": "3.0.10", + "@types/mailparser": "3.4.0", + "@types/marked": "4.3.2", + "@types/mime-types": "2.1.1", + "@types/mustache": "4.2.4", + "@types/node": "^18.16.9", + "@types/node-cron": "3.0.11", + "@types/nodemailer": "6.4.9", + "@types/onfleet__node-onfleet": "1.3.7", + "@types/pako": "2.0.3", + "@types/papaparse": "5.3.8", + "@types/pg": "8.10.2", + "@types/prismjs": "1.26.0", + "@types/qrcode": "1.5.5", + "@types/qs": "6.9.7", + "@types/react": "18.3.1", + "@types/react-dom": "18.3.0", + "@types/react-lottie": "1.2.10", + "@types/recharts": "1.8.29", + "@types/semver": "7.5.6", + "@types/snowflake-sdk": "1.6.20", + "@types/sqlstring": "2.3.2", + "@types/ssh2-sftp-client": "9.0.0", + "@types/tinycolor2": "1.4.5", + "@types/turndown": "5.0.4", + "@types/write-file-atomic": "4.0.3", + "@types/xml2js": "0.4.14", + "@types/xmlrpc": "1.3.10", + "@typescript-eslint/eslint-plugin": "7.18.0", + "@typescript-eslint/parser": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@vitejs/plugin-react": "^4.2.0", + "@vitest/ui": "^1.3.1", + "autoprefixer": "10.4.15", + "babel-jest": "29.7.0", + "chalk": "4.1.2", + "concurrently": "8.2.1", + "esbuild": "0.25.0", + "eslint": "8.57.0", + "eslint-config-prettier": "10.1.5", + "eslint-import-resolver-alias": "1.1.2", + "eslint-import-resolver-typescript": "3.6.1", + "eslint-plugin-import": "2.31.0", + "eslint-plugin-import-x": "0.5.2", + "eslint-plugin-jest-dom": "5.4.0", + "eslint-plugin-jsx-a11y": "6.7.1", + "eslint-plugin-prettier": "4.2.1", + "eslint-plugin-react": "7.32.2", + "eslint-plugin-react-hooks": "4.6.0", + "eslint-plugin-tailwindcss": "3.17.4", + "eslint-plugin-testing-library": "6.2.2", + "eslint-plugin-vitest": "0.5.4", + "husky": "8.0.3", + "i18next-parser": "9.3.0", + "inquirer": "8.2.6", + "jest": "29.7.0", + "jest-environment-jsdom": "29.7.0", + "jest-environment-node": "^29.4.1", + "jsonc-eslint-parser": "^2.1.0", + "lint-staged": "15.2.9", + "nx": "21.1.2", + "pino-pretty": "9.4.1", + "pnpm": "9.15.0", + "postcss": "8.4.38", + "postcss-import": "14.1.0", + "postcss-preset-env": "7.5.0", + "postcss-url": "10.1.3", + "prettier": "2.8.4", + "tailwindcss": "3.4.3", + "ts-jest": "29.1.1", + "ts-node": "10.9.1", + "typescript": "5.5.4", + "verdaccio": "6.1.2", + "vite": "6.3.5", + "vite-plugin-checker": "0.7.2", + "vitest": "3.0.8", + "wait-on": "7.2.0", + "webpack": "5.98.0", + "webpack-cli": "5.1.4", + "webpack-ignore-dynamic-require": "1.0.0" + }, + "optionalDependencies": { + "@nx/nx-darwin-arm64": "21.1.2", + "@nx/nx-darwin-x64": "18.0.4", + "@nx/nx-linux-arm-gnueabihf": "18.0.4", + "@nx/nx-linux-x64-gnu": "18.0.4", + "@nx/nx-win32-x64-msvc": "18.0.4", + "@rollup/rollup-darwin-arm64": "4.20.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.200", + "@rollup/rollup-linux-arm64-gnu": "4.20.0" + }, + "nx": { + "includedScripts": [] + }, + "resolutions": { + "rollup": "npm:@rollup/wasm-node" + }, + "overrides": { + "@tryfabric/martian": { + "@notionhq/client": "$@notionhq/client" + }, + "vite": { + "rollup": "npm:@rollup/wasm-node" + } + } +} diff --git a/packages/cli/.eslintrc.json b/packages/cli/.eslintrc.json new file mode 100644 index 0000000..9d9c0db --- /dev/null +++ b/packages/cli/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 0000000..d1b95a0 --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1,7 @@ +# cli + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test cli` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/packages/cli/assets/db.json b/packages/cli/assets/db.json new file mode 100644 index 0000000..f1ab0af --- /dev/null +++ b/packages/cli/assets/db.json @@ -0,0 +1,2467 @@ +{ + "Airtable": { + "swagger": "2.0", + "info": { + "title": "AirtableTest", + "description": "Connector to read and write data in Airtable. Airtable is a cloud-based spreadsheet-like service that enables users to collaborate and manage different types of data. It is easy-to-use and can act as database or CRM (Customer Relationship Management). It can also be used as project planning, tracking inventory.", + "version": "1.0", + "contact": { + "name": "Woong Choi", + "url": "https://last72.tistory.com/entry/Airtable-Connector-Support", + "email": "Woong.Choi@sevensigma.com.au" + } + }, + "host": "api.airtable.com", + "basePath": "/v0", + "schemes": ["https"], + "consumes": [], + "produces": [], + "paths": { + "/{Base ID}/{Table}": { + "get": { + "responses": { + "200": { + "description": "default", + "schema": { + "type": "object", + "properties": { + "records": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Record ID" + }, + "createdTime": { + "type": "string", + "description": "Record Created Time" + } + } + }, + "description": "Records" + } + } + } + } + }, + "parameters": [ + { + "name": "Base ID", + "in": "path", + "required": true, + "type": "string", + "description": "Base ID", + "x-ms-summary": "Base ID", + "x-ms-url-encoding": "single" + }, + { + "name": "Table", + "in": "path", + "required": true, + "type": "string", + "description": "Table name", + "x-ms-summary": "Table", + "x-ms-url-encoding": "single" + }, + { + "name": "filterByFormula", + "in": "query", + "required": false, + "type": "string", + "x-ms-visibility": "advanced", + "description": "A formula used to filter records.", + "x-ms-summary": "Formula filter" + }, + { + "name": "maxRecords", + "in": "query", + "required": false, + "type": "integer", + "x-ms-visibility": "advanced", + "description": "The maximum total number of records that will be returned in your requests.", + "x-ms-summary": "Maximum number of records." + }, + { + "name": "pageSize", + "in": "query", + "required": false, + "type": "integer", + "x-ms-visibility": "advanced", + "description": "The number of records returned in each request. Must be less than or equal to 100.", + "x-ms-summary": "Page size per request" + }, + { + "name": "view", + "in": "query", + "required": false, + "type": "string", + "x-ms-visibility": "advanced", + "description": "The name or ID of a view in the table. If set, only the records in that view will be returned.", + "x-ms-summary": "View" + }, + { + "name": "cellFormat", + "in": "query", + "required": false, + "type": "string", + "x-ms-visibility": "advanced", + "description": "The format that should be used for cell values. Supported values are: json: cells will be formatted as JSON, depending on the field type. string: cells will be formatted as user-facing strings, regardless of the field type.", + "x-ms-summary": "Cell format" + }, + { + "name": "timeZone", + "in": "query", + "required": false, + "type": "string", + "x-ms-visibility": "advanced", + "description": "The time zone that should be used to format dates when using string as the cellFormat. This parameter is required when using string as the cellFormat.", + "x-ms-summary": "Time zone" + }, + { + "name": "userLocale", + "in": "query", + "required": false, + "type": "string", + "x-ms-visibility": "advanced", + "description": "The user locale that should be used to format dates when using string as the cellFormat. This parameter is required when using string as the cellFormat.", + "x-ms-summary": "User locale" + } + ], + "summary": "List Records", + "description": "List Records in table. Returned records do not include any fields with empty values. You can filter, sort, and format the results with the parameters.", + "operationId": "ListRecords" + }, + "post": { + "responses": { + "200": { + "description": "default", + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Record ID" + }, + "createdTime": { + "type": "string", + "description": "Record Created Time" + } + } + } + } + }, + "summary": "Create a record", + "description": "Create a record in a table. JSON record needs to be passed.", + "operationId": "CreateaRecord", + "consumes": ["application/json"], + "parameters": [ + { + "name": "Base ID", + "in": "path", + "required": true, + "type": "string", + "description": "Base ID", + "x-ms-summary": "Base ID", + "x-ms-url-encoding": "single" + }, + { + "name": "Table", + "in": "path", + "required": true, + "type": "string", + "description": "Table name", + "x-ms-summary": "Table", + "x-ms-url-encoding": "single" + }, + { + "name": "Content-Type", + "in": "header", + "required": true, + "type": "string", + "default": "application/json", + "x-ms-visibility": "internal", + "description": "Content-Type for the body of the request. It is defaulted to JSON.", + "x-ms-summary": "Content-Type" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object" + } + } + ] + } + }, + "/{Base ID}/{Table}/{Record ID}": { + "get": { + "responses": { + "200": { + "description": "default", + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Record ID" + }, + "createdTime": { + "type": "string", + "description": "Record Created Time" + } + } + } + } + }, + "parameters": [ + { + "name": "Base ID", + "in": "path", + "required": true, + "type": "string", + "description": "Base ID", + "x-ms-summary": "Base ID", + "x-ms-url-encoding": "single" + }, + { + "name": "Table", + "in": "path", + "required": true, + "type": "string", + "description": "Table name", + "x-ms-summary": "Table", + "x-ms-url-encoding": "single" + }, + { + "name": "Record ID", + "in": "path", + "required": true, + "type": "string", + "description": "Record ID to be retrieved.", + "x-ms-summary": "Record ID", + "x-ms-url-encoding": "single" + } + ], + "summary": "Retrieve a record", + "description": "Retrieve a record in a table. Any empty fields (e.g. [], or false) in the record will not be returned.", + "operationId": "RetrieveaRecord" + }, + "delete": { + "responses": { + "200": { + "description": "default", + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "id" + }, + "deleted": { + "type": "boolean", + "description": "deleted" + }, + "error": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "type" + }, + "message": { + "type": "string", + "description": "message" + } + }, + "description": "error" + } + } + } + } + }, + "summary": "Delete a record", + "description": "Delete a record in a table. Provide Record ID to select a record.", + "operationId": "DeleteaRecord", + "parameters": [ + { + "name": "Base ID", + "in": "path", + "required": true, + "type": "string", + "description": "Base ID", + "x-ms-summary": "Base ID", + "x-ms-url-encoding": "single" + }, + { + "name": "Table", + "in": "path", + "required": true, + "type": "string", + "description": "Table name", + "x-ms-summary": "Table", + "x-ms-url-encoding": "single" + }, + { + "name": "Record ID", + "in": "path", + "required": true, + "type": "string", + "description": "Record ID to be deleted.", + "x-ms-summary": "Record ID", + "x-ms-url-encoding": "single" + } + ] + }, + "patch": { + "responses": { + "200": { + "description": "default", + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "id" + }, + "createdTime": { + "type": "string", + "description": "Record Created Time" + } + } + } + } + }, + "summary": "Update a record", + "description": "Update a record in a table. It will only update the fields provided and leave the rest as they were.", + "operationId": "UpdateaRecord", + "consumes": ["application/json"], + "parameters": [ + { + "name": "Base ID", + "in": "path", + "required": true, + "type": "string", + "description": "Base ID", + "x-ms-summary": "Base ID", + "x-ms-url-encoding": "single" + }, + { + "name": "Table", + "in": "path", + "required": true, + "type": "string", + "description": "Table name", + "x-ms-summary": "Table", + "x-ms-url-encoding": "single" + }, + { + "name": "Record ID", + "in": "path", + "required": true, + "type": "string", + "description": "Record ID to be updated", + "x-ms-summary": "Record ID", + "x-ms-url-encoding": "single" + }, + { + "name": "Content-Type", + "in": "header", + "required": false, + "type": "string", + "description": "Content-Type for the body of the request. It is defaulted to JSON.", + "x-ms-summary": "Content-Type", + "x-ms-url-encoding": "single" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object" + } + } + ] + } + } + }, + "definitions": {}, + "parameters": {}, + "responses": {}, + "securityDefinitions": { + "API Key": { + "type": "apiKey", + "in": "header", + "name": "Authorization", + "description": "Generate a personal access token from https://airtable.com/create/tokens. Prefix your token with 'Bearer ', e.g. 'Bearer abcdefg'" + } + }, + "security": [ + { + "API Key": [] + } + ], + "tags": [], + "x-ms-connector-metadata": [ + { + "propertyName": "Website", + "propertyValue": "https://airtable.com/" + }, + { + "propertyName": "Privacy policy", + "propertyValue": "https://airtable.com/privacy" + }, + { + "propertyName": "Categories", + "propertyValue": "Data" + } + ] + }, + "Slack": { + "openapi": "3.0.0", + "info": { + "title": "SlackTest", + "version": "1.0", + "description": "OpenAPI specification generated from the Slack piece." + }, + "servers": [ + { + "url": "https://slack.com/api" + } + ], + "components": { + "securitySchemes": { + "OAuth2": { + "type": "oauth2", + "flows": { + "authorizationCode": { + "authorizationUrl": "https://slack.com/oauth/v2/authorize", + "tokenUrl": "https://slack.com/api/oauth.v2.access", + "scopes": { + "channels:read": "Read channels", + "channels:manage": "Manage channels", + "channels:history": "View channel history", + "chat:write": "Write messages", + "groups:read": "Read groups", + "groups:write": "Write to groups", + "reactions:read": "Read reactions", + "mpim:read": "Read multi-party IMs", + "mpim:write": "Write to multi-party IMs", + "im:write": "Write to IMs", + "users:read": "Read user information", + "files:write": "Write files", + "files:read": "Read files", + "users:read.email": "Read user email", + "reactions:write": "Write reactions" + } + } + } + } + } + }, + "security": [ + { + "OAuth2": [ + "channels:read", + "channels:manage", + "channels:history", + "chat:write", + "groups:read", + "groups:write", + "reactions:read", + "mpim:read", + "mpim:write", + "im:write", + "users:read", + "files:write", + "files:read", + "users:read.email", + "reactions:write" + ] + } + ], + "paths": { + "/chat.postMessage": { + "post": { + "summary": "Send Message to a Channel", + "operationId": "send_channel_message", + "description": "Send a message to a specified channel", + "parameters": [ + { + "name": "channel", + "in": "query", + "description": "Channel ID to send the message to", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "text", + "in": "query", + "description": "Text of the message", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "thread_ts", + "in": "query", + "description": "Thread timestamp for reply", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "description": "Channel ID to send the message to" + }, + "text": { + "type": "string", + "description": "Text of the message" + }, + "thread_ts": { + "type": "string", + "description": "Thread timestamp for reply" + } + }, + "required": ["channel", "text"] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Message sent successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean" + }, + "channel": { + "type": "string" + }, + "ts": { + "type": "string" + }, + "message": { + "type": "object" + } + } + } + } + } + }, + "default": { + "description": "Error response" + } + } + } + }, + "/chat.update": { + "post": { + "summary": "Update Message", + "operationId": "update_message", + "description": "Update an existing message", + "parameters": [ + { + "name": "channel", + "in": "query", + "description": "Channel ID of the message to update", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "ts", + "in": "query", + "description": "Timestamp of the message to update", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "text", + "in": "query", + "description": "Updated text of the message", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "description": "Channel ID of the message to update" + }, + "ts": { + "type": "string", + "description": "Timestamp of the message to update" + }, + "text": { + "type": "string", + "description": "Updated text of the message" + } + }, + "required": ["channel", "ts", "text"] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Message updated successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean" + }, + "channel": { + "type": "string" + }, + "ts": { + "type": "string" + }, + "message": { + "type": "object" + } + } + } + } + } + }, + "default": { + "description": "Error response" + } + } + } + } + } + }, + "Postman": { + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Postman", + "description": "Postman is a platform for building and using APIs. Postman simplifies each step of the API lifecycle and streamlines collaboration.", + "contact": { + "name": "Fördős András", + "email": "fordosa90+ipc_postm@gmail.com" + } + }, + "host": "api.getpostman.com", + "basePath": "/", + "schemes": ["https"], + "consumes": [], + "produces": ["application/json"], + "paths": { + "/workspaces": { + "get": { + "summary": "List all workspaces", + "description": "List all workspaces available for the authenticated user.", + "operationId": "ListWorkspaces", + "parameters": [ + { + "name": "type", + "in": "query", + "type": "string", + "required": false, + "description": "Optional, define the type of workspace to return, e.g. 'personal'.", + "x-ms-summary": "Type", + "enum": ["personal", "private", "team", "partner", "public"] + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "properties": { + "workspaces": { + "type": "array", + "items": { + "type": "object", + "description": "Details of a Postman workspace.", + "title": "Workspace", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier of a workspace.", + "title": "Id" + }, + "name": { + "type": "string", + "description": "Name of the workspace.", + "title": "Name" + }, + "type": { + "type": "string", + "description": "Type of the workspace, e.g. 'personal'.", + "title": "Type" + }, + "visibility": { + "type": "string", + "description": "Visibility of the workspace, e.g. 'personal'.", + "title": "Visibility" + } + } + }, + "description": "Details of Postman workspaces.", + "title": "Workspaces" + } + } + } + }, + "429": { + "description": "Service limit exhausted. Please contact your team admin." + } + } + }, + "post": { + "summary": "Create a workspace", + "description": "Creates a new workspace for the authenticated user.", + "operationId": "CreateAWorkspace", + "consumes": ["application/json"], + "parameters": [ + { + "name": "Content-Type", + "in": "header", + "required": true, + "type": "string", + "default": "application/json", + "description": "Content-Type", + "x-ms-visibility": "internal" + }, + { + "name": "body", + "in": "body", + "schema": { + "type": "object", + "properties": { + "workspace": { + "type": "object", + "required": ["name", "type"], + "properties": { + "name": { + "type": "string", + "description": "Name of the workspace.", + "title": "Name" + }, + "description": { + "type": "string", + "description": "Description of the workspace.", + "title": "Description" + }, + "type": { + "type": "string", + "description": "Type of the workspace, e.g. 'personal'", + "title": "Type", + "enum": [ + "personal", + "private", + "team", + "partner", + "public" + ] + } + }, + "description": "Details of the workspace." + } + } + }, + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "properties": { + "workspace": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier of the workspace.", + "title": "Id" + }, + "name": { + "type": "string", + "description": "Name of the workspace.", + "title": "Name" + } + }, + "description": "Details of the workspace.", + "title": "Workspace" + } + } + } + }, + "400": { + "description": "Bad Request." + }, + "429": { + "description": "Service limit exhausted. Please contact your team admin." + } + } + } + }, + "/workspaces/{workspaceId}": { + "get": { + "summary": "Get workspace", + "description": "Gets information about a specific workspace.", + "operationId": "GetWorkspace", + "parameters": [ + { + "name": "workspaceId", + "in": "path", + "required": true, + "type": "string", + "x-ms-url-encoding": "single", + "x-ms-summary": "Workspace ID", + "description": "Unique ID of a Postman workspace." + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "properties": { + "workspace": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier of the workspace.", + "title": "Id" + }, + "name": { + "type": "string", + "description": "Name of the workspace.", + "title": "Name" + }, + "type": { + "type": "string", + "description": "Type of the workspace, e.g. 'personal'.", + "title": "Type" + }, + "description": { + "type": "string", + "description": "Description of the workspace.", + "title": "Description" + }, + "visibility": { + "type": "string", + "description": "Visibility of the workspace, e.g. 'personal'.", + "title": "Visibility" + }, + "createdBy": { + "type": "string", + "description": "Unique identifier of user, who created the workspace.", + "title": "Created By" + }, + "updatedBy": { + "type": "string", + "description": "Unique identifier of user, who last updated the workspace.", + "title": "Updated By" + }, + "createdAt": { + "type": "string", + "description": "Timestamp of the workspace creation in UTC format.", + "title": "Created At" + }, + "updatedAt": { + "type": "string", + "description": "Timestamp of last update in UTC format.", + "title": "Updated At" + }, + "collections": { + "type": "array", + "items": { + "type": "object", + "title": "Collection", + "description": "Details of the collection.", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier of the collection within workspace.", + "title": "Id" + }, + "name": { + "type": "string", + "description": "Name of the collection within workspace.", + "title": "Name" + }, + "uid": { + "type": "string", + "description": "Globally unique ID of the collection within workspace", + "title": "UID" + } + } + }, + "description": "Array of collections within the workspace." + }, + "environments": { + "type": "array", + "items": { + "type": "object", + "title": "Environment", + "description": "Details of the environment.", + "properties": { + "id": { + "type": "string", + "description": "Identifier of the environment within workspace.", + "title": "Id" + }, + "name": { + "type": "string", + "description": "Name of the environment within workspace.", + "title": "Name" + }, + "uid": { + "type": "string", + "description": "Globally unique identifier of the environment within workspace.", + "title": "UID" + } + } + }, + "description": "Array of environments within the workspace." + }, + "mocks": { + "type": "array", + "items": { + "type": "object", + "title": "Mock", + "description": "Details of the mock.", + "properties": { + "id": { + "type": "string", + "description": "Identifier of the mock within workspace.", + "title": "Id" + }, + "name": { + "type": "string", + "description": "Name of the mock within workspace.", + "title": "Name" + }, + "uid": { + "type": "string", + "description": "Globally unique identifier of the mock within workspace.", + "title": "UID" + } + } + }, + "description": "Array of mocks within the workspace." + }, + "monitors": { + "type": "array", + "items": { + "type": "object", + "title": "Monitor", + "description": "Details of the monitor.", + "properties": { + "id": { + "type": "string", + "description": "Identifier of the monitor within workspace.", + "title": "Id" + }, + "name": { + "type": "string", + "description": "Name of the monitor within workspace.", + "title": "Name" + }, + "uid": { + "type": "string", + "description": "Globally unique identifier of the monitor within workspace.", + "title": "UID" + } + } + }, + "description": "Array of monitors within the workspace." + }, + "apis": { + "type": "array", + "items": { + "type": "object", + "title": "API", + "description": "Details of the API.", + "properties": { + "id": { + "type": "string", + "description": "Identifier of the API within workspace.", + "title": "Id" + }, + "name": { + "type": "string", + "description": "Name of the API within workspace.", + "title": "Name" + }, + "uid": { + "type": "string", + "description": "Globally unique identifier of the API within workspace.", + "title": "UID" + } + } + }, + "description": "Array of APIs within the workspace." + } + }, + "description": "Detailed information about the workspace.", + "title": "Workspace" + } + } + } + }, + "404": { + "description": "Not found." + }, + "429": { + "description": "Service limit exhausted. Please contact your team admin." + } + } + } + }, + "/me": { + "get": { + "summary": "Get authenticated user", + "description": "Gets information and usage details about the authenticated user.", + "operationId": "GetAuthenticatedUser", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32", + "description": "Unique identifier of the user.", + "title": "Id" + }, + "username": { + "type": "string", + "description": "Username for the authenticated user.", + "title": "Username" + }, + "email": { + "type": "string", + "description": "Email address of the user.", + "title": "Email" + }, + "fullName": { + "type": "string", + "description": "Full name of the user.", + "title": "Full name" + }, + "avatar": { + "type": "string", + "description": "Avatar of the user.", + "title": "Avatar" + }, + "isPublic": { + "type": "boolean", + "description": "Boolean, whether the user is public or not.", + "title": "Is Public" + } + }, + "description": "Details about the authenticated user.", + "title": "User" + }, + "operations": { + "type": "array", + "items": { + "type": "object", + "title": "Operation", + "description": "Details of the specific usage or operation.", + "properties": { + "name": { + "type": "string", + "description": "Name of the operation.", + "title": "Name" + }, + "limit": { + "type": "integer", + "format": "int32", + "description": "Applicable limit for the operation.", + "title": "Limit" + }, + "usage": { + "type": "integer", + "format": "int32", + "description": "Usage details for the operation.", + "title": "Usage" + }, + "overage": { + "type": "integer", + "format": "int32", + "description": "Overage details for the operation.", + "title": "Overage" + } + } + }, + "description": "Details of operations and usage for the user.", + "title": "Operations" + } + } + } + }, + "429": { + "description": "Service limit exhausted. Please contact your team admin." + } + } + } + }, + "/environments": { + "get": { + "summary": "List all environments", + "description": "Get information about all of your environments.", + "operationId": "ListEnvironments", + "parameters": [ + { + "name": "workspace", + "in": "query", + "type": "string", + "required": false, + "description": "Optional value, defining the workspace ID to check for environments.", + "x-ms-summary": "Workspace Id" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "properties": { + "environments": { + "type": "array", + "items": { + "type": "object", + "title": "Environment", + "description": "Details of the environment.", + "properties": { + "id": { + "type": "string", + "description": "Identifier of the environment.", + "title": "Id" + }, + "name": { + "type": "string", + "description": "Name of the environment.", + "title": "Name" + }, + "createdAt": { + "type": "string", + "description": "Timestamp when the environment was created, in UTC format.", + "title": "Created At" + }, + "updatedAt": { + "type": "string", + "description": "Timestamp when the environment was last updated, in UTC format.", + "title": "Updated At" + }, + "owner": { + "type": "string", + "description": "Identifier of the owning user of the environment.", + "title": "Owner" + }, + "uid": { + "type": "string", + "description": "Globally unique identifier of the environment.", + "title": "UID" + }, + "isPublic": { + "type": "boolean", + "description": "Boolean indicating, whether the environment is public or not.", + "title": "Is Public" + } + } + }, + "description": "Array of environments.", + "title": "Environments" + } + } + } + }, + "429": { + "description": "Service limit exhausted. Please contact your team admin." + } + } + } + }, + "/environments/{environmentId}": { + "get": { + "summary": "Get environment", + "description": "Gets information about a specific environment.", + "operationId": "GetEnvironment", + "parameters": [ + { + "name": "environmentId", + "in": "path", + "required": true, + "type": "string", + "x-ms-url-encoding": "single", + "x-ms-summary": "Environment ID", + "description": "Unique ID of a Postman environment." + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "properties": { + "environment": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Identifier of the environment.", + "title": "Id" + }, + "name": { + "type": "string", + "description": "Name of the environment.", + "title": "Name" + }, + "owner": { + "type": "string", + "description": "Identifier of the owner of the environment.", + "title": "Owner" + }, + "createdAt": { + "type": "string", + "description": "Timestamp of the creation of the environment, in UTC format.", + "title": "Created At" + }, + "updatedAt": { + "type": "string", + "description": "Timestamp of the last update, in UTC format.", + "title": "Updated At" + }, + "values": { + "type": "array", + "items": { + "type": "object", + "title": "Variable", + "description": "Details of the environment variable.", + "properties": { + "key": { + "type": "string", + "description": "Key of the environment variable.", + "title": "Key" + }, + "value": { + "type": "string", + "description": "Value of the environment variable.", + "title": "Value" + }, + "enabled": { + "type": "boolean", + "description": "Boolean, whether the environment variable is enabled.", + "title": "Enabled" + }, + "type": { + "type": "string", + "description": "Type of the environment variable.", + "title": "Type" + } + } + }, + "description": "Array of environment variable values.", + "title": "variables" + }, + "isPublic": { + "type": "boolean", + "description": "Boolean indicating, whether the environment is public or not.", + "title": "Is Public" + } + }, + "description": "Details of the environment.", + "title": "Environment" + } + } + } + }, + "404": { + "description": "Not found." + }, + "429": { + "description": "Service limit exhausted. Please contact your team admin." + } + } + } + }, + "/collections": { + "get": { + "summary": "List all collections", + "description": "List all of your subscribed collections.", + "operationId": "ListCollections", + "parameters": [ + { + "name": "workspace", + "in": "query", + "type": "string", + "required": false, + "description": "Optional value, defining the workspace ID to check for collections.", + "x-ms-summary": "Workspace Id" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "properties": { + "collections": { + "type": "array", + "items": { + "type": "object", + "title": "Collection", + "description": "Details of the collection.", + "properties": { + "id": { + "type": "string", + "description": "Identifier of the collection.", + "title": "Id" + }, + "name": { + "type": "string", + "description": "Name of the collection.", + "title": "Name" + }, + "owner": { + "type": "string", + "description": "Unique identifier of the owner of the collection.", + "title": "Owner" + }, + "createdAt": { + "type": "string", + "description": "Timestamp indicating the creation of the collection, in UTC format.", + "title": "Created At" + }, + "updatedAt": { + "type": "string", + "description": "Timestampt, indicating last update of the collection, in UTC format.", + "title": "Updated At" + }, + "uid": { + "type": "string", + "description": "Globally unique identifier of the collection.", + "title": "UID" + }, + "isPublic": { + "type": "boolean", + "description": "Boolean indicating, whether the collection is public.", + "title": "Is Public" + } + } + }, + "description": "Array of collections.", + "title": "Collections" + } + } + } + }, + "429": { + "description": "Service limit exhausted. Please contact your team admin." + } + } + } + }, + "/collections/{collectionId}": { + "get": { + "summary": "Get collection", + "description": "Gets information about a specific postman collection.", + "operationId": "GetCollection", + "parameters": [ + { + "name": "collectionId", + "in": "path", + "required": true, + "type": "string", + "x-ms-url-encoding": "single", + "x-ms-summary": "Collection ID", + "description": "Unique ID of a Postman collection." + }, + { + "name": "access_key", + "in": "query", + "type": "string", + "required": false, + "description": "Optional value, defining an access key providing read only access to a collection.", + "x-ms-summary": "Access Key" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "properties": { + "collection": { + "type": "object", + "description": "Object representing a Postman collection.", + "title": "Collection" + } + } + } + }, + "404": { + "description": "Not found." + }, + "429": { + "description": "Service limit exhausted. Please contact your team admin." + } + } + } + }, + "/import/openapi": { + "post": { + "summary": "Import OpenAPI", + "description": "Import an OpenAPI (or swagger) definition to your workspace.", + "operationId": "ImportOpenApi", + "consumes": ["application/json"], + "parameters": [ + { + "name": "Content-Type", + "in": "header", + "required": true, + "type": "string", + "default": "application/json", + "description": "Content-Type", + "x-ms-visibility": "internal" + }, + { + "name": "workspace", + "in": "query", + "type": "string", + "required": false, + "description": "Optional value, defining the workspace ID to import into.", + "x-ms-summary": "Workspace Id" + }, + { + "name": "body", + "in": "body", + "schema": { + "type": "object", + "required": ["type", "input"], + "properties": { + "type": { + "type": "string", + "default": "json", + "description": "Type of input provided.", + "title": "Type", + "x-ms-visibility": "internal" + }, + "input": { + "type": "object", + "properties": {}, + "description": "The definition to be imported in JSON.", + "title": "Input" + } + } + }, + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "properties": { + "collections": { + "type": "array", + "items": { + "type": "object", + "title": "Collection", + "description": "Details of the collection.", + "properties": { + "id": { + "type": "string", + "description": "Identifier of the collection.", + "title": "Id" + }, + "name": { + "type": "string", + "description": "Name of the collection.", + "title": "Name" + }, + "uid": { + "type": "string", + "description": "Global unique identifier of the collection.", + "title": "UID" + } + } + } + } + } + } + }, + "400": { + "description": "Bad Request." + } + } + } + } + }, + "definitions": {}, + "parameters": {}, + "responses": {}, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "in": "header", + "name": "X-API-Key" + } + }, + "security": [], + "tags": [], + "x-ms-connector-metadata": [ + { + "propertyName": "Website", + "propertyValue": "https://www.postman.com/" + }, + { + "propertyName": "Privacy policy", + "propertyValue": "https://www.postman.com/legal/privacy-policy/" + }, + { + "propertyName": "Categories", + "propertyValue": "IT Operations;Productivity" + } + ] + }, + "Telegram": { + "swagger": "2.0", + "info": { + "title": "Telegram Bot", + "description": "The Telegram Bot API is an HTTP-based interface created for developers keen on building bots for Telegram.", + "version": "1.0", + "contact": { + "name": "Woong Choi", + "url": "https://www.linkedin.com/in/woongchoi/", + "email": "woong.choi@rapidcircle.com" + } + }, + "x-ms-connector-metadata": [ + { + "propertyName": "Website", + "propertyValue": "https://telegram.org/" + }, + { + "propertyName": "Privacy policy", + "propertyValue": "https://telegram.org/privacy" + }, + { + "propertyName": "Categories", + "propertyValue": "Communication" + } + ], + "host": "api.telegram.org", + "basePath": "/", + "schemes": ["https"], + "consumes": [], + "produces": [], + "paths": { + "/bot{token}/getupdates": { + "get": { + "responses": { + "200": { + "description": "default", + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean", + "x-ms-summary": "OK", + "description": "Request successful" + }, + "result": { + "type": "array", + "items": { + "$ref": "#/definitions/Update" + }, + "description": "result" + } + } + } + } + }, + "summary": "Get Updates", + "description": "Use this method to receive incoming updates using long polling", + "operationId": "GetUpdates", + "parameters": [ + { + "$ref": "#/parameters/TelegramBotToken" + } + ] + } + }, + "/bot{token}/getMe": { + "get": { + "responses": { + "200": { + "description": "default", + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean", + "x-ms-summary": "OK", + "description": "Request successful" + }, + "result": { + "$ref": "#/definitions/User" + } + } + } + } + }, + "summary": "Get Me", + "description": "Returns basic information about the bot in form of a User object. A simple method for testing your bot's auth token.", + "operationId": "GetMe", + "parameters": [ + { + "$ref": "#/parameters/TelegramBotToken" + } + ] + } + }, + "/bot{token}/sendMessage": { + "post": { + "responses": { + "200": { + "description": "default", + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean", + "description": "ok" + }, + "result": { + "$ref": "#/definitions/Message" + } + } + } + } + }, + "summary": "Send Message", + "description": "Use this method to send text messages", + "operationId": "SendMessage", + "consumes": ["application/json"], + "parameters": [ + { + "$ref": "#/parameters/TelegramBotToken" + }, + { + "name": "body", + "in": "body", + "required": false, + "schema": { + "type": "object", + "properties": { + "chat_id": { + "$ref": "#/definitions/chat_id" + }, + "text": { + "$ref": "#/definitions/text" + }, + "parse_mode": { + "$ref": "#/definitions/parse_mode" + } + } + } + } + ] + } + }, + "/bot{token}/sendPhoto": { + "post": { + "responses": { + "200": { + "description": "default", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Send Photo", + "description": "Use this method to send photos.", + "operationId": "SendPhoto", + "consumes": ["application/json"], + "parameters": [ + { + "$ref": "#/parameters/TelegramBotToken" + }, + { + "name": "body", + "in": "body", + "required": false, + "schema": { + "type": "object", + "properties": { + "chat_id": { + "$ref": "#/definitions/chat_id" + }, + "photo": { + "$ref": "#/definitions/photo" + } + } + } + } + ] + } + }, + "/bot{token}/getChat": { + "get": { + "responses": { + "200": { + "description": "default", + "schema": { + "$ref": "#/definitions/Message" + } + } + }, + "summary": "Get Chat", + "description": "Use this method to get up to date information about the chat", + "operationId": "GetChat", + "consumes": ["application/json"], + "parameters": [ + { + "$ref": "#/parameters/TelegramBotToken" + }, + { + "name": "body", + "in": "body", + "required": false, + "schema": { + "type": "object", + "properties": { + "chat_id": { + "$ref": "#/definitions/chat_id" + } + } + } + } + ] + } + } + }, + "definitions": { + "Chat": { + "type": "object", + "x-ms-summary": "Chat", + "properties": { + "id": { + "type": "integer", + "format": "int32", + "x-ms-summary": "Chat ID", + "description": "Unique identifier for this chat" + }, + "first_name": { + "type": "string", + "x-ms-summary": "First Name", + "description": "First name of the other party in a private chat" + }, + "last_name": { + "type": "string", + "x-ms-summary": "Last Name", + "description": "Last name of the other party in a private chat" + }, + "username": { + "type": "string", + "x-ms-summary": "User Name", + "description": "Username, for private chats, supergroups and channels if available" + }, + "type": { + "type": "string", + "x-ms-summary": "Chat", + "description": "Type of chat, can be either “private”, “group”, “supergroup” or “channel”" + } + }, + "description": "This object represents a chat" + }, + "chat_id": { + "type": "string", + "description": "chat_id", + "x-ms-summary": "Chat ID" + }, + "photo": { + "type": "string", + "description": "Phototo send", + "x-ms-summary": "Photo" + }, + "text": { + "type": "string", + "description": "Text to send", + "x-ms-summary": "Text" + }, + "Update": { + "type": "object", + "x-ms-summary": "Update", + "description": "This object represents an incoming update.", + "properties": { + "update_id": { + "type": "integer", + "format": "int32", + "x-ms-summary": "Update ID", + "description": "The update's unique identifier." + }, + "message": { + "$ref": "#/definitions/Message" + } + } + }, + "Message": { + "type": "object", + "x-ms-summary": "Message", + "description": "This object represents a message.", + "properties": { + "message_id": { + "type": "integer", + "format": "int32", + "x-ms-summary": "Message ID", + "description": "Unique message identifier inside this chat" + }, + "from": { + "$ref": "#/definitions/User" + }, + "chat": { + "$ref": "#/definitions/Chat" + }, + "date": { + "type": "integer", + "format": "int32", + "x-ms-summary": "Date", + "description": "Date the message was sent in Unix time" + }, + "text": { + "type": "string", + "x-ms-summary": "Text", + "description": "For text messages, the actual UTF-8 text of the message, 0-4096 characters" + } + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32", + "x-ms-summary": "ID", + "description": "Unique identifier for this user or bot" + }, + "is_bot": { + "type": "boolean", + "x-ms-summary": "Is Bot", + "description": "True, if this user is a bot" + }, + "first_name": { + "type": "string", + "x-ms-summary": "First Name", + "description": "User's or bot's first name" + }, + "last_name": { + "type": "string", + "x-ms-summary": "Last Name", + "description": "User's or bot's last name" + }, + "username": { + "type": "string", + "x-ms-summary": "User Name", + "description": "User's or bot's username" + }, + "language_code": { + "type": "string", + "x-ms-summary": "Language Code", + "description": "IETF language tag of the user's language" + }, + "can_join_groups": { + "type": "boolean", + "x-ms-summary": "Can join groups", + "description": "True, if the bot can be invited to groups." + }, + "can_read_all_group_messages": { + "type": "boolean", + "x-ms-summary": "Can read all group messages", + "description": "True, if privacy mode is disabled for the bot." + }, + "supports_inline_queries": { + "type": "boolean", + "x-ms-summary": "Supports inline queries", + "description": "True, if the bot supports inline queries." + } + }, + "x-ms-summary": "User", + "description": "This object represents a Telegram user or bot" + }, + "parse_mode": { + "type": "string", + "description": "Mode for parsing entities in the message text.", + "x-ms-summary": "Parse Mode" + } + }, + "parameters": { + "TelegramBotToken": { + "name": "token", + "in": "path", + "required": true, + "type": "string", + "x-ms-summary": "Bot Token", + "description": "Telegram Bot Token. e.g. 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", + "x-ms-url-encoding": "single" + } + }, + "responses": {}, + "securityDefinitions": {}, + "security": [], + "tags": [] + }, + "Whatsapp": { + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "WhatsApp-Test", + "description": "This WhatsApp connector will allow you to send some message templates from the test WhatsApp business account to a phone number registered on this Meta Developer App.", + "contact": { + "name": "Zakariya Fakira", + "url": "https://business.facebook.com/", + "email": "zakariyafakira08@gmail.com" + } + }, + "x-ms-connector-metadata": [ + { + "propertyName": "Website", + "propertyValue": "https://www.whatsapp.com/" + }, + { + "propertyName": "Privacy policy", + "propertyValue": "https://www.whatsapp.com/legal/privacy-policy/?lang=en" + }, + { + "propertyName": "Categories", + "propertyValue": "Social Media" + } + ], + "host": "graph.facebook.com", + "basePath": "/", + "schemes": ["https"], + "consumes": [], + "produces": ["application/json"], + "paths": { + "/%7B%7BVersion%7D%7D/%7B%7BPhone-Number-ID%7D%7D/messages": {}, + "/{Version}/{Phone-Number-ID}/messages": { + "post": { + "summary": "Send a Message", + "description": "Send a message of a specific template to a registered user phone number.", + "operationId": "SendMessage", + "parameters": [ + { + "name": "Version", + "in": "path", + "required": true, + "type": "string", + "default": "v15.0", + "description": "Version of API", + "x-ms-summary": "Version of the WhatsApp Business API being used.", + "x-ms-url-encoding": "single" + }, + { + "name": "Phone-Number-ID", + "in": "path", + "required": true, + "type": "integer", + "default": 104545025835246, + "description": "Phone Number ID of Business", + "x-ms-summary": "Use your Business Phone Number ID.", + "x-ms-url-encoding": "single" + }, + { + "name": "body", + "in": "body", + "required": false, + "schema": { + "type": "object", + "properties": { + "messaging_product": { + "type": "string", + "description": "messaging_product" + }, + "to": { + "type": "string", + "description": "to" + }, + "type": { + "type": "string", + "description": "type" + }, + "template": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "name" + }, + "language": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "code" + } + }, + "description": "language" + }, + "components": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "type" + }, + "parameters": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "type" + }, + "image": { + "type": "object", + "properties": { + "link": { + "type": "string", + "description": "link" + } + }, + "description": "image" + }, + "text": { + "type": "string", + "description": "text" + } + } + }, + "description": "parameters" + } + } + }, + "description": "components" + } + }, + "description": "template" + } + } + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad request" + } + } + } + } + }, + "definitions": {}, + "parameters": {}, + "responses": {}, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "in": "header", + "name": "Authorization" + } + }, + "security": [], + "tags": [] + }, + "SendSMS": { + "swagger": "2.0", + "info": { + "title": "SendSMSTest", + "description": "sendSMS is a leading European Communications Platform as a Service (CPaaS) known for its reliability. Established in 2008, the platform has expanded its services globally. Specializing in seamless no-code and low-code integrations, sendSMS has effectively integrated with over 30 eCommerce platforms, CRMs, ERPs, and various integrators, streamlining communications and operational efficiency.", + "version": "1.0", + "contact": { + "name": "sendSms", + "url": "https://www.sendsms.ro", + "email": "support@sendsms.ro" + } + }, + "host": "api.sendsms.ro", + "basePath": "/integration/powerautomate/", + "schemes": ["https"], + "consumes": ["application/json"], + "produces": ["application/json"], + "paths": { + "/message_send": { + "post": { + "responses": { + "200": { + "description": "Message sent successfully", + "schema": { + "type": "object", + "properties": { + "status": { + "type": "integer", + "format": "int32", + "description": "Status code" + }, + "message": { + "type": "string", + "description": "Status message" + }, + "message_id": { + "type": "string", + "description": "Unique identifier for the message" + }, + "details": { + "type": "string", + "description": "Additional details" + }, + "mcc": { + "type": "string", + "description": "Mobile country code" + }, + "mnc": { + "type": "string", + "description": "Mobile network code" + }, + "parts": { + "type": "integer", + "format": "int32", + "description": "Parts" + }, + "length": { + "type": "integer", + "format": "int32", + "description": "Length of the OTP; max length is 10" + }, + "shortlink": { + "type": "array", + "items": { + "type": "string" + }, + "description": "shortlink" + } + } + } + }, + "400": { + "description": "Bad Request - Required parameter missing or invalid", + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error message" + }, + "details": { + "type": "string", + "description": "Detailed error message" + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized - Authentication failed", + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error message indicating authentication failure" + } + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "to": { + "type": "string", + "description": "Recipient phone number", + "title": "To", + "x-ms-visibility": "important" + }, + "text": { + "type": "string", + "description": "Text of the message", + "title": "Text", + "x-ms-visibility": "important" + }, + "from": { + "type": "string", + "description": "Sender", + "title": "Sender (From)", + "x-ms-visibility": "important" + }, + "short": { + "type": "string", + "description": "Shortened URL" + } + }, + "x-ms-visibility": "important" + }, + "x-ms-visibility": "important" + } + ], + "operationId": "message_send", + "summary": "Message Send", + "x-ms-visibility": "important", + "description": "This method is designed to activate the process of sending a Short Message Service (SMS) message to a pre-determined phone number upon receiving a specific request. It seamlessly facilitates the communication by ensuring that the SMS is transmitted efficiently to the designated recipient.", + "x-ms-openai-data": { + "openai-enabled": true, + "operations": [ + { + "operationId": "message_send", + "x-ms-require-user-confirmation": true + } + ] + } + } + }, + "/message_status": { + "post": { + "responses": { + "200": { + "description": "Message status retrieved successfully", + "schema": { + "type": "object", + "properties": { + "status": { + "type": "integer", + "format": "int32", + "description": "Status code of the message" + }, + "message": { + "type": "string", + "description": "Status message" + }, + "details": { + "type": "object", + "properties": { + "status": { + "type": "integer", + "format": "int32", + "description": "Status of the message" + }, + "message": { + "type": "string", + "description": "Message" + }, + "cost": { + "type": "number", + "format": "float", + "description": "Cost" + }, + "parts": { + "type": "integer", + "format": "int32", + "description": "Parts" + }, + "timestamp_created": { + "type": "string", + "description": "Timestamp when the message was created" + }, + "timestamp_delivered": { + "type": "string", + "description": "Timestamp when the message was delivered" + }, + "timestamp_failed": { + "type": "string", + "description": "Timestamp when the message failed" + }, + "failover_id": { + "type": "string", + "description": "Failover Id" + }, + "ctype": { + "type": "string", + "description": "1 = SMS (Default), 2 = RCS - (not active yet), 3 = Viber (failover to SMS if undelivered)" + } + } + } + } + } + }, + "400": { + "description": "Bad Request - Required parameter missing", + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error message for missing parameter" + }, + "details": { + "type": "string", + "description": "Details of the missing parameter" + } + } + } + } + } + } + }, + "404": { + "description": "Not Found - Message does not exist", + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Error message indicating the message does not exist" + } + } + } + } + } + } + } + }, + "summary": "Message Status", + "description": "The Message Status method is designed to retrieve the current status of a message that has been sent through a communication platform. This method plays a critical role in message delivery systems, offering developers and users insights into the message lifecycle, including whether a message has been sent, delivered or encountered any errors during the process.", + "x-ms-visibility": "important", + "operationId": "message_status", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "message_id": { + "type": "string", + "description": "Unique identifier for the message to retrieve status", + "title": "Message ID", + "x-ms-visibility": "important" + } + }, + "x-ms-visibility": "important" + }, + "x-ms-visibility": "important" + } + ], + "x-ms-openai-data": { + "openai-enabled": true, + "operations": [ + { + "operationId": "message_status", + "x-ms-require-user-confirmation": true + } + ] + } + } + } + }, + "definitions": {}, + "parameters": {}, + "responses": {}, + "securityDefinitions": { + "oauth2-auth": { + "type": "oauth2", + "flow": "accessCode", + "authorizationUrl": "https://api.sendsms.ro/oauth2/authorize", + "tokenUrl": "https://api.sendsms.ro/oauth2/token", + "scopes": { + "PowerAutomate": "PowerAutomate" + } + } + }, + "security": [ + { + "oauth2-auth": ["PowerAutomate"] + } + ], + "x-ms-connector-metadata": [ + { + "propertyName": "Website", + "propertyValue": "https://www.sendsms.ro/" + }, + { + "propertyName": "Privacy policy", + "propertyValue": "https://www.sendsms.ro/en/gdpr/" + }, + { + "propertyName": "Categories", + "propertyValue": "Communication;Marketing" + } + ] + } +} diff --git a/packages/cli/jest.config.ts b/packages/cli/jest.config.ts new file mode 100644 index 0000000..e8d0968 --- /dev/null +++ b/packages/cli/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'cli', + preset: '../../jest.preset.js', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/packages/cli', +}; diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 0000000..62fc446 --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,12 @@ +{ + "name": "@activepieces/cli", + "version": "1.0.1", + "bin": { + "pieces-cli":"../../dist/cli/src/index.js" + }, + "scripts": { + "start": "ts-node src/index.ts", + "build": "tsc" + } + } + \ No newline at end of file diff --git a/packages/cli/project.json b/packages/cli/project.json new file mode 100644 index 0000000..3920eb2 --- /dev/null +++ b/packages/cli/project.json @@ -0,0 +1,29 @@ +{ + "name": "cli", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/cli/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/packages/cli", + "main": "packages/cli/src/index.ts", + "tsConfig": "packages/cli/tsconfig.lib.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "packages/cli/jest.config.ts" + } + } + }, + "tags": [] +} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts new file mode 100644 index 0000000..386ba74 --- /dev/null +++ b/packages/cli/src/index.ts @@ -0,0 +1,43 @@ +import { Command } from 'commander'; +import { createActionCommand } from './lib/commands/create-action'; +import { createPieceCommand } from './lib/commands/create-piece'; +import { createTriggerCommand } from './lib/commands/create-trigger'; +import { syncPieceCommand } from './lib/commands/sync-pieces'; +import { publishPieceCommand } from './lib/commands/publish-piece'; +import { buildPieceCommand } from './lib/commands/build-piece'; +import { generateWorkerTokenCommand } from './lib/commands/generate-worker-token'; +import { generateTranslationFileForPieceCommand } from './lib/commands/generate-translation-file-for-piece'; + +const pieceCommand = new Command('pieces') + .description('Manage pieces'); + +pieceCommand.addCommand(createPieceCommand); +pieceCommand.addCommand(syncPieceCommand); +pieceCommand.addCommand(publishPieceCommand); +pieceCommand.addCommand(buildPieceCommand); +pieceCommand.addCommand(generateTranslationFileForPieceCommand); +const actionCommand = new Command('actions') + .description('Manage actions'); + +actionCommand.addCommand(createActionCommand); + +const triggerCommand = new Command('triggers') + .description('Manage triggers') + +triggerCommand.addCommand(createTriggerCommand) + + +const workerCommand = new Command('workers') + .description('Manage workers') + +workerCommand.addCommand(generateWorkerTokenCommand) + +const program = new Command(); + +program.version('0.0.1').description('Activepieces CLI'); + +program.addCommand(pieceCommand); +program.addCommand(actionCommand); +program.addCommand(triggerCommand); +program.addCommand(workerCommand); +program.parse(process.argv); diff --git a/packages/cli/src/lib/commands/build-piece.ts b/packages/cli/src/lib/commands/build-piece.ts new file mode 100644 index 0000000..f1c6833 --- /dev/null +++ b/packages/cli/src/lib/commands/build-piece.ts @@ -0,0 +1,25 @@ +import { Command } from "commander"; +import { buildPiece, findPiece } from '../utils/piece-utils'; +import chalk from "chalk"; +import inquirer from "inquirer"; + +async function buildPieces(pieceName: string) { + const pieceFolder = await findPiece(pieceName); + const { outputFolder } = await buildPiece(pieceFolder); + console.info(chalk.green(`Piece '${pieceName}' built and packed successfully at ${outputFolder}.`)); +} + +export const buildPieceCommand = new Command('build') + .description('Build pieces without publishing') + .action(async () => { + const questions = [ + { + type: 'input', + name: 'name', + message: 'Enter the piece folder name', + placeholder: 'google-drive', + }, + ]; + const answers = await inquirer.prompt(questions); + await buildPieces(answers.name); + }); diff --git a/packages/cli/src/lib/commands/create-action.ts b/packages/cli/src/lib/commands/create-action.ts new file mode 100644 index 0000000..14ad2f3 --- /dev/null +++ b/packages/cli/src/lib/commands/create-action.ts @@ -0,0 +1,74 @@ +import { writeFile } from 'node:fs/promises'; +import chalk from 'chalk'; +import { Command } from 'commander'; +import inquirer from 'inquirer'; +import { assertPieceExists, displayNameToCamelCase, displayNameToKebabCase, findPiece } from '../utils/piece-utils'; +import { checkIfFileExists, makeFolderRecursive } from '../utils/files'; +import { join } from 'node:path'; + +function createActionTemplate(displayName: string, description: string) { + const camelCase = displayNameToCamelCase(displayName) + const actionTemplate = `import { createAction, Property } from '@activepieces/pieces-framework'; + +export const ${camelCase} = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: '${camelCase}', + displayName: '${displayName}', + description: '${description}', + props: {}, + async run() { + // Action logic here + }, +}); +`; + + return actionTemplate +} + +const checkIfActionExists = async (actionPath: string) => { + if (await checkIfFileExists(actionPath)) { + console.log(chalk.red(`🚨 Action already exists at ${actionPath}`)); + process.exit(1); + } +} +const createAction = async (pieceName: string, displayActionName: string, actionDescription: string) => { + const actionTemplate = createActionTemplate(displayActionName, actionDescription) + const actionName = displayNameToKebabCase(displayActionName) + const pieceFolder = await findPiece(pieceName); + assertPieceExists(pieceFolder) + console.log(chalk.blue(`Piece path: ${pieceFolder}`)) + const actionsFolder = join(pieceFolder, 'src', 'lib', 'actions') + const actionPath = join(actionsFolder, `${actionName}.ts`) + await checkIfActionExists(actionPath) + + await makeFolderRecursive(actionsFolder); + await writeFile(actionPath, actionTemplate); + console.log(chalk.yellow('✨'), `Action ${actionPath} created`); +}; + + +export const createActionCommand = new Command('create') + .description('Create a new action') + .action(async () => { + const questions = [ + { + type: 'input', + name: 'pieceName', + message: 'Enter the piece folder name:', + placeholder: 'google-drive', + }, + { + type: 'input', + name: 'actionName', + message: 'Enter the action display name', + }, + { + type: 'input', + name: 'actionDescription', + message: 'Enter the action description', + } + ]; + + const answers = await inquirer.prompt(questions); + createAction(answers.pieceName, answers.actionName, answers.actionDescription); + }); diff --git a/packages/cli/src/lib/commands/create-piece.ts b/packages/cli/src/lib/commands/create-piece.ts new file mode 100644 index 0000000..87f4178 --- /dev/null +++ b/packages/cli/src/lib/commands/create-piece.ts @@ -0,0 +1,224 @@ +import chalk from 'chalk'; +import { Command } from 'commander'; +import { readdir, unlink, writeFile } from 'fs/promises'; +import inquirer from 'inquirer'; +import assert from 'node:assert'; +import { exec } from '../utils/exec'; +import { + readPackageEslint, + readProjectJson, + writePackageEslint, + writeProjectJson, +} from '../utils/files'; +import { findPiece } from '../utils/piece-utils'; + +const validatePieceName = async (pieceName: string) => { + console.log(chalk.yellow('Validating piece name....')); + const pieceNamePattern = /^(?![._])[a-z0-9-]{1,214}$/; + if (!pieceNamePattern.test(pieceName)) { + console.log( + chalk.red( + `🚨 Invalid piece name: ${pieceName}. Piece names can only contain lowercase letters, numbers, and hyphens.` + ) + ); + process.exit(1); + } +}; + +const validatePackageName = async (packageName: string) => { + console.log(chalk.yellow('Validating package name....')); + const packageNamePattern = /^(?:@[a-zA-Z0-9-]+\/)?[a-zA-Z0-9-]+$/; + if (!packageNamePattern.test(packageName)) { + console.log( + chalk.red( + `🚨 Invalid package name: ${packageName}. Package names can only contain lowercase letters, numbers, and hyphens.` + ) + ); + process.exit(1); + } +}; + +const checkIfPieceExists = async (pieceName: string) => { + const pieceFolder = await findPiece(pieceName); + if (pieceFolder) { + console.log(chalk.red(`🚨 Piece already exists at ${pieceFolder}`)); + process.exit(1); + } +}; + +const nxGenerateNodeLibrary = async ( + pieceName: string, + packageName: string, + pieceType: string +) => { + const nxGenerateCommand = [ + `npx nx generate @nx/node:library`, + `--directory=packages/pieces/${pieceType}/${pieceName}`, + `--name=pieces-${pieceName}`, + `--importPath=${packageName}`, + '--publishable', + '--buildable', + '--projectNameAndRootFormat=as-provided', + '--strict', + '--unitTestRunner=none', + ].join(' '); + + console.log(chalk.blue(`🛠️ Executing nx command: ${nxGenerateCommand}`)); + + await exec(nxGenerateCommand); +}; + +const removeUnusedFiles = async (pieceName: string, pieceType: string) => { + const path = `packages/pieces/${pieceType}/${pieceName}/src/lib/`; + const files = await readdir(path); + for (const file of files) { + await unlink(path + file); + } +}; +function capitalizeFirstLetter(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); +} +const generateIndexTsFile = async (pieceName: string, pieceType: string) => { + const pieceNameCamelCase = pieceName + .split('-') + .map((s, i) => { + if (i === 0) { + return s; + } + + return s[0].toUpperCase() + s.substring(1); + }) + .join(''); + + const indexTemplate = ` + import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; + + export const ${pieceNameCamelCase} = createPiece({ + displayName: "${capitalizeFirstLetter(pieceName)}", + auth: PieceAuth.None(), + minimumSupportedRelease: '0.36.1', + logoUrl: "https://cdn.activepieces.com/pieces/${pieceName}.png", + authors: [], + actions: [], + triggers: [], + }); + `; + + await writeFile( + `packages/pieces/${pieceType}/${pieceName}/src/index.ts`, + indexTemplate + ); +}; +const updateProjectJsonConfig = async ( + pieceName: string, + pieceType: string +) => { + const projectJson = await readProjectJson( + `packages/pieces/${pieceType}/${pieceName}` + ); + const i18nAsset = { + input: `packages/pieces/${pieceType}/${pieceName}/src/i18n`, + output: './src/i18n', + glob: '**/!(i18n.json)' +} + assert( + projectJson.targets?.build?.options, + '[updateProjectJsonConfig] targets.build.options is required' + ); + + projectJson.targets.build.options.buildableProjectDepsInPackageJsonType = + 'dependencies'; + projectJson.targets.build.options.updateBuildableProjectDepsInPackageJson = + true; + if(projectJson.targets.build.options.assets){ + projectJson.targets.build.options.assets.push(i18nAsset); + } + else{ + projectJson.targets.build.options.assets = [i18nAsset]; + } + + const lintFilePatterns = projectJson.targets.lint?.options?.lintFilePatterns; + + if (lintFilePatterns) { + const patternIndex = lintFilePatterns.findIndex((item) => + item.endsWith('package.json') + ); + if (patternIndex !== -1) lintFilePatterns?.splice(patternIndex, 1); + } else { + projectJson.targets.lint = { + executor: '@nx/eslint:lint', + outputs: ['{options.outputFile}'], + }; +} + + await writeProjectJson( + `packages/pieces/${pieceType}/${pieceName}`, + projectJson + ); +}; +const updateEslintFile = async (pieceName: string, pieceType: string) => { + const eslintFile = await readPackageEslint( + `packages/pieces/${pieceType}/${pieceName}` + ); + eslintFile.overrides.splice( + eslintFile.overrides.findIndex((rule: any) => rule.files[0] == '*.json'), + 1 + ); + await writePackageEslint( + `packages/pieces/${pieceType}/${pieceName}`, + eslintFile + ); +}; +const setupGeneratedLibrary = async (pieceName: string, pieceType: string) => { + await removeUnusedFiles(pieceName, pieceType); + await generateIndexTsFile(pieceName, pieceType); + await updateProjectJsonConfig(pieceName, pieceType); + await updateEslintFile(pieceName, pieceType); +}; + +export const createPiece = async ( + pieceName: string, + packageName: string, + pieceType: string +) => { + await validatePieceName(pieceName); + await validatePackageName(packageName); + await checkIfPieceExists(pieceName); + await nxGenerateNodeLibrary(pieceName, packageName, pieceType); + await setupGeneratedLibrary(pieceName, pieceType); + console.log(chalk.green('✨ Done!')); + console.log( + chalk.yellow( + `The piece has been generated at: packages/pieces/${pieceType}/${pieceName}` + ) + ); +}; + +export const createPieceCommand = new Command('create') + .description('Create a new piece') + .action(async () => { + const questions = [ + { + type: 'input', + name: 'pieceName', + message: 'Enter the piece name:', + }, + { + type: 'input', + name: 'packageName', + message: 'Enter the package name:', + default: (answers: any) => `@activepieces/piece-${answers.pieceName}`, + when: (answers: any) => answers.pieceName !== undefined, + }, + { + type: 'list', + name: 'pieceType', + message: 'Select the piece type:', + choices: ['community', 'custom'], + default: 'community', + }, + ]; + + const answers = await inquirer.prompt(questions); + createPiece(answers.pieceName, answers.packageName, answers.pieceType); + }); diff --git a/packages/cli/src/lib/commands/create-trigger.ts b/packages/cli/src/lib/commands/create-trigger.ts new file mode 100644 index 0000000..0f8ad93 --- /dev/null +++ b/packages/cli/src/lib/commands/create-trigger.ts @@ -0,0 +1,143 @@ +import chalk from 'chalk'; +import { Command } from 'commander'; +import inquirer from 'inquirer'; +import { writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { checkIfFileExists, makeFolderRecursive } from '../utils/files'; +import { + assertPieceExists, + customPiecePath, + displayNameToCamelCase, + displayNameToKebabCase, findPiece, + findPieces, +} from '../utils/piece-utils'; + +function createTriggerTemplate(displayName: string, description: string, technique: string) { + const camelCase = displayNameToCamelCase(displayName) + let triggerTemplate = '' + if (technique === 'polling') { + triggerTemplate = ` +import { createTrigger, TriggerStrategy, PiecePropValueSchema } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import dayjs from 'dayjs'; + +// replace auth with piece auth variable +const polling: Polling< PiecePropValueSchema, Record > = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ propsValue, lastFetchEpochMS }) => { + // implement the logic to fetch the items + const items = [ {id: 1, created_date: '2021-01-01T00:00:00Z'}, {id: 2, created_date: '2021-01-01T00:00:00Z'}]; + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.created_date).valueOf(), + data: item, + })); + } +} + +export const ${camelCase} = createTrigger({ +// auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, +name: '${camelCase}', +displayName: '${displayName}', +description: '${description}', +props: {}, +sampleData: {}, +type: TriggerStrategy.POLLING, +async test(context) { + return await pollingHelper.test(polling, context); +}, +async onEnable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onEnable(polling, { store, auth, propsValue }); +}, + +async onDisable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onDisable(polling, { store, auth, propsValue }); +}, + +async run(context) { + return await pollingHelper.poll(polling, context); +}, +});`; + } + else { + triggerTemplate = ` +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +export const ${camelCase} = createTrigger({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: '${camelCase}', + displayName: '${displayName}', + description: '${description}', + props: {}, + sampleData: {}, + type: TriggerStrategy.WEBHOOK, + async onEnable(context){ + // implement webhook creation logic + }, + async onDisable(context){ + // implement webhook deletion logic + }, + async run(context){ + return [context.payload.body] + } +})`; + + } + + return triggerTemplate +} +const checkIfTriggerExists = async (triggerPath: string) => { + if (await checkIfFileExists(triggerPath)) { + console.log(chalk.red(`🚨 Trigger already exists at ${triggerPath}`)); + process.exit(1); + } +} +const createTrigger = async (pieceName: string, displayTriggerName: string, triggerDescription: string, triggerTechnique: string) => { + const triggerTemplate = createTriggerTemplate(displayTriggerName, triggerDescription, triggerTechnique) + const triggerName = displayNameToKebabCase(displayTriggerName) + const pieceFolder = await findPiece(pieceName); + assertPieceExists(pieceFolder) + console.log(chalk.blue(`Piece path: ${pieceFolder}`)) + + const triggersFolder = join(pieceFolder, 'src', 'lib', 'triggers') + const triggerPath = join(triggersFolder, `${triggerName}.ts`) + await checkIfTriggerExists(triggerPath) + + await makeFolderRecursive(triggersFolder); + await writeFile(triggerPath, triggerTemplate); + console.log(chalk.yellow('✨'), `Trigger ${triggerPath} created`); +}; + + +export const createTriggerCommand = new Command('create') + .description('Create a new trigger') + .action(async () => { + const questions = [ + { + type: 'input', + name: 'pieceName', + message: 'Enter the piece folder name:', + placeholder: 'google-drive', + }, + { + type: 'input', + name: 'triggerName', + message: 'Enter the trigger display name:', + }, + { + type: 'input', + name: 'triggerDescription', + message: 'Enter the trigger description:', + }, + { + type: 'list', + name: 'triggerTechnique', + message: 'Select the trigger technique:', + choices: ['polling', 'webhook'], + default: 'webhook', + }, + ]; + + const answers = await inquirer.prompt(questions); + createTrigger(answers.pieceName, answers.triggerName, answers.triggerDescription, answers.triggerTechnique); + }); diff --git a/packages/cli/src/lib/commands/generate-translation-file-for-piece.ts b/packages/cli/src/lib/commands/generate-translation-file-for-piece.ts new file mode 100644 index 0000000..9570a16 --- /dev/null +++ b/packages/cli/src/lib/commands/generate-translation-file-for-piece.ts @@ -0,0 +1,90 @@ +import { writeFile } from 'node:fs/promises'; +import chalk from 'chalk'; +import { Command } from 'commander'; +import { findPiece } from '../utils/piece-utils'; +import { makeFolderRecursive } from '../utils/files'; +import { join } from 'node:path'; +import { exec } from '../utils/exec'; +// eslint-disable-next-line @nx/enforce-module-boundaries +import keys from '../../../../pieces/community/framework/translation-keys.json'; + +const findPieceInModule= async (pieceOutputFile: string) => { + const module = await import(pieceOutputFile); + const exports = Object.values(module); + for (const e of exports) { + if (e !== null && e !== undefined && e.constructor.name === 'Piece') { + return e + } + } + + throw new Error(`Piece not found in module, please check the piece output file ${pieceOutputFile}`); +} + +const installDependencies = async (pieceFolder: string) => { + console.log(chalk.blue(`Installing dependencies ${pieceFolder}`)) + await exec(`npm install`, {cwd: pieceFolder,}) + console.log(chalk.green(`Dependencies installed ${pieceFolder}`)) +} + + +function getPropertyValue(object: Record, path: string): unknown { + const parsedKeys = path.split('.'); + if (parsedKeys[0] === '*') { + return Object.values(object).map(item => getPropertyValue(item as Record, parsedKeys.slice(1).join('.'))).filter(Boolean).flat() + } + const nextObject = object[parsedKeys[0]] as Record; + if (nextObject && parsedKeys.length > 1) { + return getPropertyValue(nextObject, parsedKeys.slice(1).join('.')); + } + return nextObject; +} + + +const generateTranslationFileFromPiece = (piece: Record) => { const translation: Record = {} + try { + keys.forEach(key => { + const value = getPropertyValue(piece, key) + if (value) { + if (typeof value === 'string') { + translation[value] = value + } + else if (Array.isArray(value)) { + value.forEach(item => { + translation[item] = item + }) + } + } + }) + } + catch (err) { + console.error(`error generating translation file for piece ${piece.name}:`, err) + } + + return translation +} + + + +const generateTranslationFile = async (pieceName: string) => { + const pieceRoot = await findPiece(pieceName) + const outputFolder = pieceRoot.replace('packages/', 'dist/packages/') + try{ + await installDependencies(outputFolder) + const pieceFromModule = await findPieceInModule(outputFolder); + const i18n = generateTranslationFileFromPiece({actions: (pieceFromModule as any)._actions, triggers: (pieceFromModule as any)._triggers, description: (pieceFromModule as any).description, displayName: (pieceFromModule as any).displayName, auth: (pieceFromModule as any).auth}); + const i18nFolder = join(pieceRoot, 'src', 'i18n') + await makeFolderRecursive(i18nFolder); + await writeFile(join(i18nFolder, 'translation.json'), JSON.stringify(i18n, null, 2)); + console.log(chalk.yellow('✨'), `Translation file for piece created in ${i18nFolder}`); + } catch (error) { + console.error(chalk.red('❌'), `Error generating translation file for piece ${pieceName}, make sure you built the piece`,error); + } +}; + + +export const generateTranslationFileForPieceCommand = new Command('generate-translation-file') + .description('Generate i18n for a piece') + .argument('', 'The name of the piece to generate i18n for') + .action(async (pieceName: string) => { + await generateTranslationFile(pieceName); + }); diff --git a/packages/cli/src/lib/commands/generate-worker-token.ts b/packages/cli/src/lib/commands/generate-worker-token.ts new file mode 100644 index 0000000..e6686cf --- /dev/null +++ b/packages/cli/src/lib/commands/generate-worker-token.ts @@ -0,0 +1,50 @@ +import { Command } from 'commander'; +import chalk from 'chalk'; +import { prompt } from 'inquirer'; +import { nanoid } from 'nanoid'; +import jwtLibrary from 'jsonwebtoken'; + +const KEY_ID = '1' +const ISSUER = 'activepieces' +const ALGORITHM = 'HS256' + +export const generateWorkerTokenCommand = new Command('token') + .description('Generate a JWT token for worker authentication') + .action(async () => { + const answers = await prompt([ + { + type: 'input', + name: 'jwtSecret', + message: 'Enter your JWT secret (should be the same as AP_JWT_SECRET used for the app server):', + validate: (input) => { + if (!input) { + return 'JWT secret is required'; + } + return true; + } + } + ]); + + const payload = { + id: nanoid(), + type: 'WORKER', + }; + + // 100 years in seconds + const expiresIn = 100 * 365 * 24 * 60 * 60; + + try { + const token = jwtLibrary.sign(payload, answers.jwtSecret, { + expiresIn, + keyid: KEY_ID, + algorithm: ALGORITHM, + issuer: ISSUER, + }); + console.log(chalk.green('\nGenerated Worker Token, Please use it in AP_WORKER_TOKEN environment variable:')); + console.log(chalk.yellow(token)); + + } catch (error) { + console.error(chalk.red('Failed to generate token:'), error); + process.exit(1); + } + }); \ No newline at end of file diff --git a/packages/cli/src/lib/commands/publish-piece.ts b/packages/cli/src/lib/commands/publish-piece.ts new file mode 100644 index 0000000..eefc3f2 --- /dev/null +++ b/packages/cli/src/lib/commands/publish-piece.ts @@ -0,0 +1,81 @@ +import { Command } from "commander"; +import { publishPieceFromFolder, findPiece, assertPieceExists } from '../utils/piece-utils'; +import chalk from "chalk"; +import inquirer from 'inquirer'; +import * as dotenv from 'dotenv'; + +dotenv.config({path: 'packages/server/api/.env'}); + +async function publishPiece( + {apiUrl, apiKey, pieceName, failOnError}: + {apiUrl: string, + apiKey: string, + pieceName: string, + failOnError: boolean,} +) { + const pieceFolder = await findPiece(pieceName); + assertPieceExists(pieceFolder) + await publishPieceFromFolder({ + pieceFolder, + apiUrl, + apiKey, + failOnError + }); +} + +function assertNullOrUndefinedOrEmpty(value: any, message: string) { + if (value === undefined || value === null || (typeof value === 'string' && value.trim() === '')) { + console.error(chalk.red(message)); + process.exit(1); + } +} + +export const publishPieceCommand = new Command('publish') + .description('Publish pieces to the platform') + .option('-f, --fail-on-error', 'Exit the process if an error occurs while syncing a piece', false) + .action(async (command) => { + const questions = [ + { + type: 'input', + name: 'name', + message: 'Enter the piece folder name', + placeholder: 'google-drive', + }, + { + type: 'input', + name: 'apiUrl', + message: 'Enter the API URL', + placeholder: 'https://cloud.activepieces.com/api', + }, + { + type: 'list', + name: 'apiKeySource', + message: 'Select the API Key source', + choices: ['Env Variable (AP_API_KEY)', 'Manually'], + default: 'Env Variable (AP_API_KEY)' + } + ] + + const answers = await inquirer.prompt(questions); + if (answers.apiKeySource === 'Manually') { + const apiKeyAnswers = await inquirer.prompt([{ + type: 'input', + name: 'apiKey', + message: 'Enter the API Key', + }]); + answers.apiKey = apiKeyAnswers.apiKey; + } + const apiKey = answers.apiKeySource === 'Env Variable (AP_API_KEY)' ? process.env.AP_API_KEY : answers.apiKey; + assertNullOrUndefinedOrEmpty(answers.name, 'Piece name is required'); + assertNullOrUndefinedOrEmpty(answers.apiUrl, 'API URL is required'); + assertNullOrUndefinedOrEmpty(apiKey, 'API Key is required'); + const apiUrlWithoutTrailSlash = answers.apiUrl.replace(/\/$/, ''); + const { failOnError } = command; + + await publishPiece({ + apiUrl: apiUrlWithoutTrailSlash, + apiKey, + pieceName: answers.name, + failOnError + }); + }); diff --git a/packages/cli/src/lib/commands/sync-pieces.ts b/packages/cli/src/lib/commands/sync-pieces.ts new file mode 100644 index 0000000..fc5cbc5 --- /dev/null +++ b/packages/cli/src/lib/commands/sync-pieces.ts @@ -0,0 +1,43 @@ +import { Command } from "commander"; +import { findPieces, publishPieceFromFolder } from '../utils/piece-utils'; +import chalk from "chalk"; +import { join } from "path"; + +async function syncPieces( + params: + {apiUrl: string, + apiKey: string, + pieces: string[] | null, + failOnError: boolean,} +) { + const piecesDirectory = join(process.cwd(), 'packages', 'pieces', 'custom') + const pieceFolders = await findPieces(piecesDirectory, params.pieces); + for (const pieceFolder of pieceFolders) { + await publishPieceFromFolder({ + pieceFolder, + ...params + }); + } +} + +export const syncPieceCommand = new Command('sync') + .description('Find new pieces versions and sync them with the database') + .requiredOption('-h, --apiUrl ', 'API URL ex: https://cloud.activepieces.com/api') + .option('-p, --pieces ', 'Specify one or more piece names to sync. ' + + 'If not provided, all custom pieces in the directory will be synced.') + .option('-f, --fail-on-error', 'Exit the process if an error occurs while syncing a piece', false) + .action(async (options) => { + const apiKey = process.env.AP_API_KEY; + const pieces = options.pieces ? [...new Set(options.pieces)] : null; + const failOnError = options.failOnError; + if (!apiKey) { + console.error(chalk.red('AP_API_KEY environment variable is required')); + process.exit(1); + } + await syncPieces({ + apiUrl: options.apiUrl.replace(/\/$/, ''), + apiKey, + pieces, + failOnError + }); + }); diff --git a/packages/cli/src/lib/utils/exec.ts b/packages/cli/src/lib/utils/exec.ts new file mode 100644 index 0000000..d6af8bf --- /dev/null +++ b/packages/cli/src/lib/utils/exec.ts @@ -0,0 +1,4 @@ +import { exec as execCallback } from 'node:child_process'; +import { promisify } from 'node:util'; + +export const exec = promisify(execCallback); diff --git a/packages/cli/src/lib/utils/files.ts b/packages/cli/src/lib/utils/files.ts new file mode 100644 index 0000000..b9a8708 --- /dev/null +++ b/packages/cli/src/lib/utils/files.ts @@ -0,0 +1,88 @@ +import { + constants, + readFile, + access, + writeFile, + mkdir, +} from 'node:fs/promises'; + +export type PackageJson = { + name: string; + version: string; + keywords: string[]; +}; + +export type ProjectJson = { + name: string; + targets?: { + build?: { + options?: { + buildableProjectDepsInPackageJsonType?: + | 'peerDependencies' + | 'dependencies'; + updateBuildableProjectDepsInPackageJson: boolean; + assets?: ({ + input: string; + output: string; + glob: string; + } | string)[]; + }; + }; + lint: { + executor: string; + outputs: string[]; + options?: { + lintFilePatterns: string[]; + }; + }; + }; +}; + +export const checkIfFileExists = async (filePath: string) => { + try { + await access(filePath, constants.F_OK); + return true; + } catch (e) { + return false; + } +}; + +const readJsonFile = async (path: string): Promise => { + const jsonFile = await readFile(path, { encoding: 'utf-8' }); + return JSON.parse(jsonFile) as T; +}; + +const writeJsonFile = async (path: string, data: unknown): Promise => { + const serializedData = JSON.stringify(data, null, 2); + await writeFile(path, serializedData, { encoding: 'utf-8' }); +}; + +export const readPackageJson = async (path: string): Promise => { + return await readJsonFile(`${path}/package.json`); +}; + +export const readProjectJson = async (path: string): Promise => { + return await readJsonFile(`${path}/project.json`); +}; + +export const readPackageEslint = async (path: string): Promise => { + return await readJsonFile(`${path}/.eslintrc.json`); +}; + +export const writePackageEslint = async ( + path: string, + eslint: any +): Promise => { + return await writeJsonFile(`${path}/.eslintrc.json`, eslint); +}; + +export const writeProjectJson = async ( + path: string, + projectJson: ProjectJson +): Promise => { + return await writeJsonFile(`${path}/project.json`, projectJson); +}; + +export const makeFolderRecursive = async (path: string): Promise => { + await mkdir(path, { recursive: true }); +}; diff --git a/packages/cli/src/lib/utils/piece-utils.ts b/packages/cli/src/lib/utils/piece-utils.ts new file mode 100644 index 0000000..5413332 --- /dev/null +++ b/packages/cli/src/lib/utils/piece-utils.ts @@ -0,0 +1,176 @@ +import { readdir, stat } from 'node:fs/promises' +import * as path from 'path' +import { cwd } from 'node:process' +import { readPackageJson, readProjectJson } from './files' +import { exec } from './exec' +import axios from 'axios' +import chalk from 'chalk' +import FormData from 'form-data'; +import fs from 'fs'; + +export const piecesPath = () => path.join(cwd(), 'packages', 'pieces') +export const customPiecePath = () => path.join(piecesPath(), 'custom') + +/** + * Finds and returns the paths of specific pieces or all available pieces in a given directory. + * + * @param inputPath - The root directory to search for pieces. If not provided, a default path to custom pieces is used. + * @param pieces - An optional array of piece names to search for. If not provided, all pieces in the directory are returned. + * @returns A promise resolving to an array of strings representing the paths of the found pieces. + */ +export async function findPieces(inputPath?: string, pieces?: string[]): Promise { + const piecesPath = inputPath ?? customPiecePath() + const piecesFolders = await traverseFolder(piecesPath) + if (pieces) { + return pieces.flatMap((piece) => { + const folder = piecesFolders.find((p) => { + const normalizedPath = path.normalize(p); + return normalizedPath.endsWith(path.sep + piece); + }); + if (!folder) { + return []; + } + return [folder]; + }); + } else { + return piecesFolders + } +} + +/** + * Finds and returns the path of a single piece. Exits the process if the piece is not found. + * + * @param pieceName - The name of the piece to search for. + * @returns A promise resolving to a string representing the path of the found piece. If not found, the process exits. + */ +export async function findPiece(pieceName: string): Promise { + return (await findPieces(piecesPath(), [pieceName]))[0] ?? null; +} + +export async function buildPiece(pieceFolder: string): Promise<{ outputFolder: string, outputFile: string }> { + const projectJson = await readProjectJson(pieceFolder); + + await buildPackage(projectJson.name); + + const compiledPath = `dist/packages/${removeStartingSlashes(pieceFolder).split(path.sep + 'packages')[1]}`; + + const { stdout } = await exec('npm pack --json', { cwd: compiledPath }); + const tarFileName = JSON.parse(stdout)[0].filename; + return { + outputFolder: compiledPath, + outputFile: path.join(compiledPath, tarFileName) + }; +} + +export async function buildPackage(projectName:string) { + await exec(`npx nx build ${projectName} --skip-cache`); + return { + outputFolder: `dist/packages/${projectName}`, + } +} + +export async function publishPieceFromFolder( + {pieceFolder, apiUrl, apiKey, failOnError}: + {pieceFolder: string, + apiUrl: string, + apiKey: string, + failOnError: boolean,} +) { + const projectJson = await readProjectJson(pieceFolder); + const packageJson = await readPackageJson(pieceFolder); + + await buildPackage(projectJson.name); + + const { outputFile } = await buildPiece(pieceFolder); + const formData = new FormData(); + + console.log(chalk.blue(`Uploading ${outputFile}`)); + formData.append('pieceArchive', fs.createReadStream(outputFile)); + formData.append('pieceName', packageJson.name); + formData.append('pieceVersion', packageJson.version); + formData.append('packageType', 'ARCHIVE'); + formData.append('scope', 'PLATFORM'); + + try { + await axios.post(`${apiUrl}/v1/pieces`, formData, { + headers: { + 'Authorization': `Bearer ${apiKey}`, + ...formData.getHeaders() + } + }); + console.info(chalk.green(`Piece '${packageJson.name}' published.`)); + } catch (error) { + + if (axios.isAxiosError(error)) { + if (error.response.status === 409) { + console.info(chalk.yellow(`Piece '${packageJson.name}' and '${packageJson.version}' already published.`)); + } else if (Math.floor(error.response.status / 100) !== 2) { + console.info(chalk.red(`Error publishing piece '${packageJson.name}', ${error}` )); + if (failOnError) { + console.info(chalk.yellow(`Terminating process due to publish failure for piece '${packageJson.name}' (fail-on-error is enabled)`)); + process.exit(1); + } + } else { + console.error(chalk.red(`Unexpected error: ${error.message}`)); + if (failOnError) { + console.info(chalk.yellow(`Terminating process due to unexpected error for piece '${packageJson.name}' (fail-on-error is enabled)`)); + process.exit(1); + } + } + } else { + console.error(chalk.red(`Unexpected error: ${error.message}`)); + if (failOnError) { + console.info(chalk.yellow(`Terminating process due to unexpected error for piece '${packageJson.name}' (fail-on-error is enabled)`)); + process.exit(1); + } + } + } +} +async function traverseFolder(folderPath: string): Promise { + const paths: string[] = [] + const directoryExists = await stat(folderPath).catch(() => null) + + if (directoryExists && directoryExists.isDirectory()) { + const files = await readdir(folderPath) + + for (const file of files) { + const filePath = path.join(folderPath, file) + const fileStats = await stat(filePath) + if (fileStats.isDirectory() && file !== 'node_modules' && file !== 'dist') { + paths.push(...await traverseFolder(filePath)) + } + else if (file === 'package.json') { + paths.push(folderPath) + } + } + } + return paths +} + +export function displayNameToKebabCase(displayName: string): string { + return displayName.toLowerCase().replace(/\s+/g, '-'); +} + +export function displayNameToCamelCase(input: string): string { + const words = input.split(' '); + const camelCaseWords = words.map((word, index) => { + if (index === 0) { + return word.toLowerCase(); + } else { + return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); + } + }); + return camelCaseWords.join(''); + } + +export const assertPieceExists = async (pieceName: string | null) => { + if (!pieceName) { + console.error(chalk.red(`🚨 Piece ${pieceName} not found`)); + process.exit(1); + } + }; + + + export const removeStartingSlashes = (str: string) => { + return str.startsWith('/') ? str.slice(1) : str; + } \ No newline at end of file diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 0000000..19b9eec --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs" + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/cli/tsconfig.lib.json b/packages/cli/tsconfig.lib.json new file mode 100644 index 0000000..3f06e80 --- /dev/null +++ b/packages/cli/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/cli/tsconfig.spec.json b/packages/cli/tsconfig.spec.json new file mode 100644 index 0000000..9b2a121 --- /dev/null +++ b/packages/cli/tsconfig.spec.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/packages/ee/LICENSE b/packages/ee/LICENSE new file mode 100644 index 0000000..1c68c6b --- /dev/null +++ b/packages/ee/LICENSE @@ -0,0 +1,36 @@ +The Activepieces Enterprise license (the “Enterprise License”) +Copyright (c) 2022-2023 Activepieces Inc. + +With regard to the Activepieces Software: + +This software and associated documentation files (the "Software") may only be +used in production, if you (and any entity that you represent) have agreed to, +and are in compliance with, the Activepieces Subscription Terms of Service, available +at https://activepieces.com/terms (the “Enterprise Terms”), or other +agreement governing the use of the Software, as agreed by you and Activepieces, +and otherwise have a valid Activepieces Enterprise license for the +correct number of user seats. Subject to the foregoing sentence, you are free to +modify this Software and publish patches to the Software. You agree that Activepieces +and/or its licensors (as applicable) retain all right, title and interest in and +to all such modifications and/or patches, and all such modifications and/or +patches may only be used, copied, modified, displayed, distributed, or otherwise +exploited with a valid Activepieces Enterprise license for the correct +number of user seats. Notwithstanding the foregoing, you may copy and modify +the Software for development and testing purposes, without requiring a +subscription. You agree that Activepieces and/or its licensors (as applicable) retain +all right, title and interest in and to all such modifications. You are not +granted any other rights beyond what is expressly stated herein. Subject to the +foregoing, it is forbidden to copy, merge, publish, distribute, sublicense, +and/or sell the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +For all third party components incorporated into the Activepieces Software, those +components are licensed under the original license provided by the owner of the +applicable component. \ No newline at end of file diff --git a/packages/ee/shared/.eslintrc.json b/packages/ee/shared/.eslintrc.json new file mode 100644 index 0000000..c602f07 --- /dev/null +++ b/packages/ee/shared/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../server/api/.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/ee/shared/README.md b/packages/ee/shared/README.md new file mode 100644 index 0000000..107d787 --- /dev/null +++ b/packages/ee/shared/README.md @@ -0,0 +1,7 @@ +# ee-shared + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build ee-shared` to build the library. diff --git a/packages/ee/shared/package.json b/packages/ee/shared/package.json new file mode 100644 index 0000000..5a498fc --- /dev/null +++ b/packages/ee/shared/package.json @@ -0,0 +1,5 @@ +{ + "name": "@activepieces/ee-shared", + "version": "0.0.10", + "type": "commonjs" +} \ No newline at end of file diff --git a/packages/ee/shared/project.json b/packages/ee/shared/project.json new file mode 100644 index 0000000..aa04e88 --- /dev/null +++ b/packages/ee/shared/project.json @@ -0,0 +1,23 @@ +{ + "name": "ee-shared", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/ee/shared/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/packages/ee/shared", + "main": "packages/ee/shared/src/index.ts", + "tsConfig": "packages/ee/shared/tsconfig.lib.json", + "assets": ["packages/ee/shared/*.md"] + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + } + }, + "tags": [] +} diff --git a/packages/ee/shared/src/index.ts b/packages/ee/shared/src/index.ts new file mode 100644 index 0000000..71320bb --- /dev/null +++ b/packages/ee/shared/src/index.ts @@ -0,0 +1,18 @@ +export * from './lib/billing' +export * from './lib/audit-events' +export * from './lib/git-repo' +export * from './lib/api-key' +export * from './lib/billing' +export * from './lib/project/project-requests' +export * from './lib/custom-domains' +export * from './lib/project-members/project-member-request' +export * from './lib/project-members/project-member' +export * from './lib/flow-templates' +export * from './lib/product-embed/app-credentials/index' +export * from './lib/product-embed/connection-keys/index' +export * from './lib/signing-key' +export * from './lib/managed-authn' +export * from './lib/oauth-apps' +export * from './lib/otp' +export * from './lib/authn' +export * from './lib/alerts' diff --git a/packages/ee/shared/src/lib/alerts/alerts-dto.ts b/packages/ee/shared/src/lib/alerts/alerts-dto.ts new file mode 100644 index 0000000..c6f05a9 --- /dev/null +++ b/packages/ee/shared/src/lib/alerts/alerts-dto.ts @@ -0,0 +1,15 @@ +import { ApId, BaseModelSchema } from '@activepieces/shared' +import { Static, Type } from '@sinclair/typebox' + +export enum AlertChannel { + EMAIL = 'EMAIL', +} + +export const Alert = Type.Object({ + ...BaseModelSchema, + projectId: ApId, + channel: Type.Enum(AlertChannel), + receiver: Type.String({}), +}) + +export type Alert = Static \ No newline at end of file diff --git a/packages/ee/shared/src/lib/alerts/alerts-requests.ts b/packages/ee/shared/src/lib/alerts/alerts-requests.ts new file mode 100644 index 0000000..41ecfa7 --- /dev/null +++ b/packages/ee/shared/src/lib/alerts/alerts-requests.ts @@ -0,0 +1,18 @@ +import { ApId } from '@activepieces/shared' +import { Static, Type } from '@sinclair/typebox' +import { AlertChannel } from './alerts-dto' + +export const ListAlertsParams = Type.Object({ + projectId: ApId, + cursor: Type.Optional(Type.String()), + limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 100 })), +}) +export type ListAlertsParams = Static + +export const CreateAlertParams = Type.Object({ + projectId: ApId, + channel: Type.Enum(AlertChannel), + receiver: Type.String({}), +}) + +export type CreateAlertParams = Static \ No newline at end of file diff --git a/packages/ee/shared/src/lib/alerts/index.ts b/packages/ee/shared/src/lib/alerts/index.ts new file mode 100644 index 0000000..ad817e1 --- /dev/null +++ b/packages/ee/shared/src/lib/alerts/index.ts @@ -0,0 +1,2 @@ +export * from './alerts-dto' +export * from './alerts-requests' \ No newline at end of file diff --git a/packages/ee/shared/src/lib/api-key/index.ts b/packages/ee/shared/src/lib/api-key/index.ts new file mode 100644 index 0000000..0f36820 --- /dev/null +++ b/packages/ee/shared/src/lib/api-key/index.ts @@ -0,0 +1,33 @@ +import { ApId, BaseModelSchema } from '@activepieces/shared' +import { Static, Type } from '@sinclair/typebox' + +export const ApiKey = Type.Object({ + ...BaseModelSchema, + platformId: ApId, + displayName: Type.String(), + hashedValue: Type.String(), + truncatedValue: Type.String(), +}) + +export type ApiKey = Static + +export const ApiKeyResponseWithValue = Type.Composite([ + Type.Omit(ApiKey, ['hashedValue']), + Type.Object({ + value: Type.String(), + }), +]) + +export type ApiKeyResponseWithValue = Static + + +export const ApiKeyResponseWithoutValue = Type.Omit(ApiKey, ['hashedValue']) + +export type ApiKeyResponseWithoutValue = Static + + +export const CreateApiKeyRequest = Type.Object({ + displayName: Type.String(), +}) + +export type CreateApiKeyRequest = Static diff --git a/packages/ee/shared/src/lib/audit-events/index.ts b/packages/ee/shared/src/lib/audit-events/index.ts new file mode 100644 index 0000000..d91419b --- /dev/null +++ b/packages/ee/shared/src/lib/audit-events/index.ts @@ -0,0 +1,377 @@ +import { + AppConnectionWithoutSensitiveData, + BaseModelSchema, + Flow, + FlowOperationRequest, + FlowOperationType, + FlowRun, + FlowVersion, + Folder, + Project, + ProjectRelease, + ProjectRole, + User, +} from '@activepieces/shared' +import { Static, Type } from '@sinclair/typebox' +import { SigningKey } from '../signing-key' +export const ListAuditEventsRequest = Type.Object({ + limit: Type.Optional(Type.Number()), + cursor: Type.Optional(Type.String()), + action: Type.Optional(Type.Array(Type.String())), + projectId: Type.Optional(Type.Array(Type.String())), + userId: Type.Optional(Type.String()), + createdBefore: Type.Optional(Type.String()), + createdAfter: Type.Optional(Type.String()), +}) + +export type ListAuditEventsRequest = Static + +const UserMeta = Type.Pick(User, ['email', 'id', 'firstName', 'lastName']) + +export enum ApplicationEventName { + FLOW_CREATED = 'flow.created', + FLOW_DELETED = 'flow.deleted', + FLOW_UPDATED = 'flow.updated', + FLOW_RUN_STARTED = 'flow.run.started', + FLOW_RUN_FINISHED = 'flow.run.finished', + FOLDER_CREATED = 'folder.created', + FOLDER_UPDATED = 'folder.updated', + FOLDER_DELETED = 'folder.deleted', + CONNECTION_UPSERTED = 'connection.upserted', + CONNECTION_DELETED = 'connection.deleted', + USER_SIGNED_UP = 'user.signed.up', + USER_SIGNED_IN = 'user.signed.in', + USER_PASSWORD_RESET = 'user.password.reset', + USER_EMAIL_VERIFIED = 'user.email.verified', + SIGNING_KEY_CREATED = 'signing.key.created', + PROJECT_ROLE_CREATED = 'project.role.created', + PROJECT_ROLE_DELETED = 'project.role.deleted', + PROJECT_ROLE_UPDATED = 'project.role.updated', + PROJECT_RELEASE_CREATED = 'project.release.created', +} + +const BaseAuditEventProps = { + ...BaseModelSchema, + platformId: Type.String(), + projectId: Type.Optional(Type.String()), + projectDisplayName: Type.Optional(Type.String()), + userId: Type.Optional(Type.String()), + userEmail: Type.Optional(Type.String()), + ip: Type.Optional(Type.String()), +} + +export const ConnectionEvent = Type.Object({ + ...BaseAuditEventProps, + action: Type.Union([ + Type.Literal(ApplicationEventName.CONNECTION_DELETED), + Type.Literal(ApplicationEventName.CONNECTION_UPSERTED), + ]), + data: Type.Object({ + connection: Type.Pick(AppConnectionWithoutSensitiveData, [ + 'displayName', + 'externalId', + 'pieceName', + 'status', + 'type', + 'id', + 'created', + 'updated', + ]), + project: Type.Optional(Type.Pick(Project, ['displayName'])), + }), +}) +export type ConnectionEvent = Static + +export const FolderEvent = Type.Object({ + ...BaseAuditEventProps, + action: Type.Union([ + Type.Literal(ApplicationEventName.FOLDER_UPDATED), + Type.Literal(ApplicationEventName.FOLDER_CREATED), + Type.Literal(ApplicationEventName.FOLDER_DELETED), + ]), + data: Type.Object({ + folder: Type.Pick(Folder, ['id', 'displayName', 'created', 'updated']), + project: Type.Optional(Type.Pick(Project, ['displayName'])), + }), +}) + +export type FolderEvent = Static + +export const FlowRunEvent = Type.Object({ + ...BaseAuditEventProps, + action: Type.Union([ + Type.Literal(ApplicationEventName.FLOW_RUN_STARTED), + Type.Literal(ApplicationEventName.FLOW_RUN_FINISHED), + ]), + data: Type.Object({ + flowRun: Type.Pick(FlowRun, [ + 'id', + 'startTime', + 'finishTime', + 'duration', + 'environment', + 'flowId', + 'flowVersionId', + 'flowDisplayName', + 'status', + ]), + project: Type.Optional(Type.Pick(Project, ['displayName'])), + }), +}) +export type FlowRunEvent = Static + +export const FlowCreatedEvent = Type.Object({ + ...BaseAuditEventProps, + action: Type.Literal(ApplicationEventName.FLOW_CREATED), + data: Type.Object({ + flow: Type.Pick(Flow, ['id', 'created', 'updated']), + project: Type.Optional(Type.Pick(Project, ['displayName'])), + }), +}) + +export type FlowCreatedEvent = Static + +export const FlowDeletedEvent = Type.Object({ + ...BaseAuditEventProps, + action: Type.Literal(ApplicationEventName.FLOW_DELETED), + data: Type.Object({ + flow: Type.Pick(Flow, ['id', 'created', 'updated']), + flowVersion: Type.Pick(FlowVersion, [ + 'id', + 'displayName', + 'flowId', + 'created', + 'updated', + ]), + project: Type.Optional(Type.Pick(Project, ['displayName'])), + }), +}) + +export type FlowDeletedEvent = Static + +export const FlowUpdatedEvent = Type.Object({ + ...BaseAuditEventProps, + action: Type.Literal(ApplicationEventName.FLOW_UPDATED), + data: Type.Object({ + flowVersion: Type.Pick(FlowVersion, [ + 'id', + 'displayName', + 'flowId', + 'created', + 'updated', + ]), + request: FlowOperationRequest, + project: Type.Optional(Type.Pick(Project, ['displayName'])), + }), +}) + +export type FlowUpdatedEvent = Static + +export const AuthenticationEvent = Type.Object({ + ...BaseAuditEventProps, + action: Type.Union([ + Type.Literal(ApplicationEventName.USER_SIGNED_IN), + Type.Literal(ApplicationEventName.USER_PASSWORD_RESET), + Type.Literal(ApplicationEventName.USER_EMAIL_VERIFIED), + ]), + data: Type.Object({ + user: Type.Optional(UserMeta), + }), +}) + +export type AuthenticationEvent = Static + +export const SignUpEvent = Type.Object({ + ...BaseAuditEventProps, + action: Type.Literal(ApplicationEventName.USER_SIGNED_UP), + data: Type.Object({ + source: Type.Union([ + Type.Literal('credentials'), + Type.Literal('sso'), + Type.Literal('managed'), + ]), + user: Type.Optional(UserMeta), + }), +}) +export type SignUpEvent = Static + +export const SigningKeyEvent = Type.Object({ + ...BaseAuditEventProps, + action: Type.Union([Type.Literal(ApplicationEventName.SIGNING_KEY_CREATED)]), + data: Type.Object({ + signingKey: Type.Pick(SigningKey, [ + 'id', + 'created', + 'updated', + 'displayName', + ]), + }), +}) + +export type SigningKeyEvent = Static + +export const ProjectRoleEvent = Type.Object({ + ...BaseAuditEventProps, + action: Type.Union([ + Type.Literal(ApplicationEventName.PROJECT_ROLE_CREATED), + Type.Literal(ApplicationEventName.PROJECT_ROLE_UPDATED), + Type.Literal(ApplicationEventName.PROJECT_ROLE_DELETED), + ]), + data: Type.Object({ + projectRole: Type.Pick(ProjectRole, [ + 'id', + 'created', + 'updated', + 'name', + 'permissions', + 'platformId', + ]), + }), +}) + +export type ProjectRoleEvent = Static + +export const ProjectReleaseEvent = Type.Object({ + ...BaseAuditEventProps, + action: Type.Literal(ApplicationEventName.PROJECT_RELEASE_CREATED), + data: Type.Object({ + release: Type.Pick(ProjectRelease, ['name', 'description', 'type', 'projectId', 'importedByUser']), + }), +}) + +export type ProjectReleaseEvent = Static + +export const ApplicationEvent = Type.Union([ + ConnectionEvent, + FlowCreatedEvent, + FlowDeletedEvent, + FlowUpdatedEvent, + FlowRunEvent, + AuthenticationEvent, + FolderEvent, + SignUpEvent, + SigningKeyEvent, + ProjectRoleEvent, + ProjectReleaseEvent, +]) + +export type ApplicationEvent = Static + +export function summarizeApplicationEvent(event: ApplicationEvent) { + switch (event.action) { + case ApplicationEventName.FLOW_UPDATED: { + return convertUpdateActionToDetails(event) + } + case ApplicationEventName.FLOW_RUN_STARTED: + return `Flow run ${event.data.flowRun.id} is started` + case ApplicationEventName.FLOW_RUN_FINISHED: { + return `Flow run ${event.data.flowRun.id} is finished` + } + case ApplicationEventName.FLOW_CREATED: + return `Flow ${event.data.flow.id} is created` + case ApplicationEventName.FLOW_DELETED: + return `Flow ${event.data.flow.id} (${event.data.flowVersion.displayName}) is deleted` + case ApplicationEventName.FOLDER_CREATED: + return `${event.data.folder.displayName} is created` + case ApplicationEventName.FOLDER_UPDATED: + return `${event.data.folder.displayName} is updated` + case ApplicationEventName.FOLDER_DELETED: + return `${event.data.folder.displayName} is deleted` + case ApplicationEventName.CONNECTION_UPSERTED: + return `${event.data.connection.displayName} (${event.data.connection.externalId}) is updated` + case ApplicationEventName.CONNECTION_DELETED: + return `${event.data.connection.displayName} (${event.data.connection.externalId}) is deleted` + case ApplicationEventName.USER_SIGNED_IN: + return `User ${event.userEmail} signed in` + case ApplicationEventName.USER_PASSWORD_RESET: + return `User ${event.userEmail} reset password` + case ApplicationEventName.USER_EMAIL_VERIFIED: + return `User ${event.userEmail} verified email` + case ApplicationEventName.USER_SIGNED_UP: + return `User ${event.userEmail} signed up using email from ${event.data.source}` + case ApplicationEventName.SIGNING_KEY_CREATED: + return `${event.data.signingKey.displayName} is created` + case ApplicationEventName.PROJECT_ROLE_CREATED: + return `${event.data.projectRole.name} is created` + case ApplicationEventName.PROJECT_ROLE_UPDATED: + return `${event.data.projectRole.name} is updated` + case ApplicationEventName.PROJECT_ROLE_DELETED: + return `${event.data.projectRole.name} is deleted` + case ApplicationEventName.PROJECT_RELEASE_CREATED: + return `${event.data.release.name} is created` + } +} + +function convertUpdateActionToDetails(event: FlowUpdatedEvent) { + switch (event.data.request.type) { + case FlowOperationType.ADD_ACTION: + return `Added action "${event.data.request.request.action.displayName}" to "${event.data.flowVersion.displayName}" Flow.` + case FlowOperationType.UPDATE_ACTION: + return `Updated action "${event.data.request.request.displayName}" in "${event.data.flowVersion.displayName}" Flow.` + case FlowOperationType.DELETE_ACTION: + { + const request = event.data.request.request + const names = request.names + return `Deleted actions "${names.join(', ')}" from "${event.data.flowVersion.displayName}" Flow.` + } + case FlowOperationType.CHANGE_NAME: + return `Renamed flow "${event.data.flowVersion.displayName}" to "${event.data.request.request.displayName}".` + case FlowOperationType.LOCK_AND_PUBLISH: + return `Locked and published flow "${event.data.flowVersion.displayName}" Flow.` + case FlowOperationType.USE_AS_DRAFT: + return `Unlocked and unpublished flow "${event.data.flowVersion.displayName}" Flow.` + case FlowOperationType.MOVE_ACTION: + return `Moved action "${event.data.request.request.name}" to after "${event.data.request.request.newParentStep}".` + case FlowOperationType.LOCK_FLOW: + return `Locked flow "${event.data.flowVersion.displayName}" Flow.` + case FlowOperationType.CHANGE_STATUS: + return `Changed status of flow "${event.data.flowVersion.displayName}" Flow to "${event.data.request.request.status}".` + case FlowOperationType.DUPLICATE_ACTION: + return `Duplicated action "${event.data.request.request.stepName}" in "${event.data.flowVersion.displayName}" Flow.` + case FlowOperationType.IMPORT_FLOW: + return `Imported flow in "${event.data.request.request.displayName}" Flow.` + case FlowOperationType.UPDATE_TRIGGER: + return `Updated trigger in "${event.data.flowVersion.displayName}" Flow to "${event.data.request.request.displayName}".` + case FlowOperationType.CHANGE_FOLDER: + return `Moved flow "${event.data.flowVersion.displayName}" to folder id ${event.data.request.request.folderId}.` + case FlowOperationType.DELETE_BRANCH: { + return `Deleted branch number ${ + event.data.request.request.branchIndex + 1 + } in flow "${event.data.flowVersion.displayName}" for the step "${ + event.data.request.request.stepName + }".` + } + case FlowOperationType.SAVE_SAMPLE_DATA: { + return `Saved sample data for step "${event.data.request.request.stepName}" in flow "${event.data.flowVersion.displayName}".` + } + case FlowOperationType.DUPLICATE_BRANCH: { + return `Duplicated branch number ${ + event.data.request.request.branchIndex + 1 + } in flow "${event.data.flowVersion.displayName}" for the step "${ + event.data.request.request.stepName + }".` + } + case FlowOperationType.ADD_BRANCH: + return `Added branch number ${ + event.data.request.request.branchIndex + 1 + } in flow "${event.data.flowVersion.displayName}" for the step "${ + event.data.request.request.stepName + }".` + case FlowOperationType.SET_SKIP_ACTION: + { + const request = event.data.request.request + const names = request.names + return `Updated actions "${names.join(', ')}" in "${event.data.flowVersion.displayName}" Flow to skip.` + } + case FlowOperationType.UPDATE_METADATA: + return `Updated metadata for flow "${event.data.flowVersion.displayName}".` + case FlowOperationType.MOVE_BRANCH: + return `Moved branch number ${ + event.data.request.request.sourceBranchIndex + 1 + } to ${ + event.data.request.request.targetBranchIndex + 1 + } in flow "${event.data.flowVersion.displayName}" for the step "${ + event.data.request.request.stepName + }".` + } +} \ No newline at end of file diff --git a/packages/ee/shared/src/lib/authn/access-control-list.ts b/packages/ee/shared/src/lib/authn/access-control-list.ts new file mode 100644 index 0000000..631ff4f --- /dev/null +++ b/packages/ee/shared/src/lib/authn/access-control-list.ts @@ -0,0 +1,88 @@ +import { DefaultProjectRole, Permission } from '@activepieces/shared' + +export const rolePermissions: Record = { + [DefaultProjectRole.ADMIN]: [ + Permission.READ_APP_CONNECTION, + Permission.WRITE_APP_CONNECTION, + Permission.READ_FLOW, + Permission.WRITE_FLOW, + Permission.UPDATE_FLOW_STATUS, + Permission.READ_PROJECT_MEMBER, + Permission.WRITE_PROJECT_MEMBER, + Permission.WRITE_INVITATION, + Permission.READ_INVITATION, + Permission.WRITE_PROJECT_RELEASE, + Permission.READ_PROJECT_RELEASE, + Permission.READ_RUN, + Permission.WRITE_RUN, + Permission.READ_ISSUES, + Permission.WRITE_ISSUES, + Permission.WRITE_ALERT, + Permission.READ_ALERT, + Permission.WRITE_PROJECT, + Permission.READ_PROJECT, + Permission.WRITE_FOLDER, + Permission.READ_FOLDER, + Permission.READ_TODOS, + Permission.WRITE_TODOS, + Permission.READ_TABLE, + Permission.WRITE_TABLE, + Permission.READ_MCP, + Permission.WRITE_MCP, + ], + [DefaultProjectRole.EDITOR]: [ + Permission.READ_APP_CONNECTION, + Permission.WRITE_APP_CONNECTION, + Permission.READ_FLOW, + Permission.WRITE_FLOW, + Permission.UPDATE_FLOW_STATUS, + Permission.READ_PROJECT_MEMBER, + Permission.READ_INVITATION, + Permission.WRITE_PROJECT_RELEASE, + Permission.READ_PROJECT_RELEASE, + Permission.READ_RUN, + Permission.WRITE_RUN, + Permission.READ_ISSUES, + Permission.WRITE_ISSUES, + Permission.READ_PROJECT, + Permission.WRITE_FOLDER, + Permission.READ_FOLDER, + Permission.READ_TODOS, + Permission.WRITE_TODOS, + Permission.READ_TABLE, + Permission.WRITE_TABLE, + Permission.READ_MCP, + Permission.WRITE_MCP, + ], + [DefaultProjectRole.OPERATOR]: [ + Permission.READ_APP_CONNECTION, + Permission.WRITE_APP_CONNECTION, + Permission.READ_FLOW, + Permission.UPDATE_FLOW_STATUS, + Permission.READ_PROJECT_MEMBER, + Permission.READ_INVITATION, + Permission.READ_PROJECT_RELEASE, + Permission.READ_RUN, + Permission.WRITE_RUN, + Permission.READ_ISSUES, + Permission.READ_PROJECT, + Permission.READ_FOLDER, + Permission.READ_TODOS, + Permission.WRITE_TODOS, + Permission.READ_TABLE, + Permission.READ_MCP, + ], + [DefaultProjectRole.VIEWER]: [ + Permission.READ_APP_CONNECTION, + Permission.READ_FLOW, + Permission.READ_PROJECT_MEMBER, + Permission.READ_INVITATION, + Permission.READ_ISSUES, + Permission.READ_PROJECT, + Permission.READ_RUN, + Permission.READ_FOLDER, + Permission.READ_TODOS, + Permission.READ_TABLE, + Permission.READ_MCP, + ], +} diff --git a/packages/ee/shared/src/lib/authn/enterprise-local-authn/index.ts b/packages/ee/shared/src/lib/authn/enterprise-local-authn/index.ts new file mode 100644 index 0000000..8029823 --- /dev/null +++ b/packages/ee/shared/src/lib/authn/enterprise-local-authn/index.ts @@ -0,0 +1 @@ +export * from './requests' diff --git a/packages/ee/shared/src/lib/authn/enterprise-local-authn/requests.ts b/packages/ee/shared/src/lib/authn/enterprise-local-authn/requests.ts new file mode 100644 index 0000000..e412e0a --- /dev/null +++ b/packages/ee/shared/src/lib/authn/enterprise-local-authn/requests.ts @@ -0,0 +1,25 @@ +import { ApId, SignUpRequest } from '@activepieces/shared' +import { Static, Type } from '@sinclair/typebox' + +export const VerifyEmailRequestBody = Type.Object({ + identityId: ApId, + otp: Type.String(), +}) +export type VerifyEmailRequestBody = Static + +export const ResetPasswordRequestBody = Type.Object({ + identityId: ApId, + otp: Type.String(), + newPassword: Type.String(), +}) +export type ResetPasswordRequestBody = Static + +export const SignUpAndAcceptRequestBody = Type.Composite([ + Type.Omit(SignUpRequest, ['referringUserId', 'email']), + Type.Object({ + invitationToken: Type.String(), + }), +]) + +export type SignUpAndAcceptRequestBody = Static + diff --git a/packages/ee/shared/src/lib/authn/index.ts b/packages/ee/shared/src/lib/authn/index.ts new file mode 100644 index 0000000..b9ee9af --- /dev/null +++ b/packages/ee/shared/src/lib/authn/index.ts @@ -0,0 +1,2 @@ +export * from './enterprise-local-authn' +export * from './access-control-list' \ No newline at end of file diff --git a/packages/ee/shared/src/lib/billing/index.ts b/packages/ee/shared/src/lib/billing/index.ts new file mode 100644 index 0000000..bf750c9 --- /dev/null +++ b/packages/ee/shared/src/lib/billing/index.ts @@ -0,0 +1,139 @@ +import { AiOverageState, isNil, PiecesFilterType } from '@activepieces/shared' +import { Static, Type } from '@sinclair/typebox' +export * from './plan-limits' +import Stripe from 'stripe' + +export type FlowPlanLimits = { + nickname: string + tasks: number | null + pieces: string[] + aiCredits: number | null + piecesFilterType: PiecesFilterType +} + +export enum ApSubscriptionStatus { + ACTIVE = 'active', + CANCELED = 'canceled', + TRIALING = 'trialing', +} + +export const DEFAULT_BUSINESS_SEATS = 5 + +export const PRICE_PER_EXTRA_USER = 20 + +export enum PlanName { + FREE = 'free', + PLUS = 'plus', + BUSINESS = 'business', + ENTERPRISE = 'enterprise', +} + +export type StripePlanName = PlanName.PLUS | PlanName.BUSINESS + + +export const CreateSubscriptionParamsSchema = Type.Object({ + plan: Type.Union([Type.Literal(PlanName.PLUS), Type.Literal(PlanName.BUSINESS)]), +}) +export type CreateSubscriptionParams = Static + + +export const SetAiCreditsOverageLimitParamsSchema = Type.Object({ + limit: Type.Number({ minimum: 10 }), +}) +export type SetAiCreditsOverageLimitParams = Static + + +export const ToggleAiCreditsOverageEnabledParamsSchema = Type.Object({ + state: Type.Enum(AiOverageState), +}) +export type ToggleAiCreditsOverageEnabledParams = Static + + + + +export const UpdateSubscriptionParamsSchema = Type.Object({ + plan: Type.Union([Type.Literal(PlanName.FREE), Type.Literal(PlanName.PLUS), Type.Literal(PlanName.BUSINESS)]), + seats: Type.Optional(Type.Number()), +}) +export type UpdateSubscriptionParams = Static + + +export const getAiCreditsPriceId = (stripeKey: string | undefined) => { + const testMode = stripeKey?.startsWith('sk_test') + return testMode + ? 'price_1RcktVQN93Aoq4f8JjdYKXBp' + : 'price_1RflgeKZ0dZRqLEKGVORuNNl' +} + +export function getUserPriceId(stripeKey: string | undefined) { + const testMode = stripeKey?.startsWith('sk_test') + return testMode + ? 'price_1RU2GkQN93Aoq4f8ogetgfUB' + : 'price_1RflgiKZ0dZRqLEKiDFoa17I' +} + +export function getPlanPriceId(stripeKey: string | undefined) { + const testMode = stripeKey?.startsWith('sk_test') + return { + [PlanName.PLUS]: testMode ? 'price_1RTRd4QN93Aoq4f8E22qF5JU' : 'price_1RflgUKZ0dZRqLEK5COq9Kn8', + [PlanName.BUSINESS]: testMode ? 'price_1RTReBQN93Aoq4f8v9CnMTFT' : 'price_1RflgbKZ0dZRqLEKaW4Nlt0P', + } + +} + +export function getPlanFromPriceId(priceId: string): PlanName | undefined { + switch (priceId) { + case 'price_1RTRd4QN93Aoq4f8E22qF5JU': + case 'price_1RflgUKZ0dZRqLEK5COq9Kn8': + return PlanName.PLUS + case 'price_1RTReBQN93Aoq4f8v9CnMTFT': + case 'price_1RflgbKZ0dZRqLEKaW4Nlt0P': + return PlanName.BUSINESS + default: + return undefined + } +} + + +export function checkIsTrialSubscription(subscription: Stripe.Subscription): boolean { + return isNil(subscription.metadata['trialSubscription']) ? false : subscription.metadata['trialSubscription'] === 'true' +} + +export function getPlanFromSubscription(subscription: Stripe.Subscription): PlanName | undefined { + if (subscription.status === ApSubscriptionStatus.TRIALING) { + return PlanName.PLUS + } + + if (subscription.status !== ApSubscriptionStatus.ACTIVE) { + return PlanName.FREE + } + + return getPlanFromPriceId(subscription.items.data[0].price.id) +} + +const PLAN_HIERARCHY = { + [PlanName.FREE]: 0, + [PlanName.PLUS]: 1, + [PlanName.BUSINESS]: 2, + [PlanName.ENTERPRISE]: 3, +} as const + +export const isUpgradeExperience = ( + currentPlan: PlanName, + newPlan: PlanName, + userSeatsLimit?: number, + seats?: number, +): boolean => { + + if (currentPlan === PlanName.PLUS && newPlan === PlanName.PLUS) { + return true + } + + const isPlanTierUpgrade = PLAN_HIERARCHY[newPlan] > PLAN_HIERARCHY[currentPlan] + const isSeatsUpgrade = !!(newPlan === PlanName.BUSINESS && + userSeatsLimit && + seats && + userSeatsLimit < seats) + + return isPlanTierUpgrade || isSeatsUpgrade +} \ No newline at end of file diff --git a/packages/ee/shared/src/lib/billing/plan-limits.ts b/packages/ee/shared/src/lib/billing/plan-limits.ts new file mode 100644 index 0000000..a82d145 --- /dev/null +++ b/packages/ee/shared/src/lib/billing/plan-limits.ts @@ -0,0 +1,184 @@ +import { AiOverageState, PlatformPlanLimits } from '@activepieces/shared' + +export type PlatformPlanWithOnlyLimits = Omit + +export const FREE_CLOUD_PLAN: PlatformPlanWithOnlyLimits = { + plan: 'free', + tasksLimit: 1000, + includedAiCredits: 200, + aiCreditsOverageLimit: undefined, + aiCreditsOverageState: AiOverageState.NOT_ALLOWED, + activeFlowsLimit: 2, + eligibleForTrial: true, + userSeatsLimit: 1, + projectsLimit: 1, + tablesLimit: 1, + mcpLimit: 1, + agentsLimit: 0, + agentsEnabled: true, + + tablesEnabled: true, + todosEnabled: true, + + embeddingEnabled: false, + globalConnectionsEnabled: false, + customRolesEnabled: false, + environmentsEnabled: false, + analyticsEnabled: false, + showPoweredBy: false, + auditLogEnabled: false, + managePiecesEnabled: false, + manageTemplatesEnabled: false, + customAppearanceEnabled: false, + manageProjectsEnabled: false, + projectRolesEnabled: false, + customDomainsEnabled: false, + apiKeysEnabled: false, + alertsEnabled: false, + ssoEnabled: false, +} + + +export const APPSUMO_PLAN = ({ planName: planname, tasksLimit, userSeatsLimit }: { planName: string, tasksLimit: number, userSeatsLimit: number }): PlatformPlanWithOnlyLimits => { + return { + plan: planname, + tasksLimit, + userSeatsLimit, + agentsEnabled: true, + includedAiCredits: 200, + aiCreditsOverageState: AiOverageState.NOT_ALLOWED, + aiCreditsOverageLimit: undefined, + activeFlowsLimit: undefined, + projectsLimit: 1, + mcpLimit: 1, + tablesLimit: 1, + agentsLimit: 5, + eligibleForTrial: false, + + todosEnabled: false, + tablesEnabled: true, + + embeddingEnabled: false, + globalConnectionsEnabled: false, + customRolesEnabled: false, + environmentsEnabled: false, + analyticsEnabled: false, + showPoweredBy: false, + auditLogEnabled: false, + managePiecesEnabled: false, + manageTemplatesEnabled: false, + customAppearanceEnabled: false, + manageProjectsEnabled: false, + projectRolesEnabled: true, + customDomainsEnabled: false, + apiKeysEnabled: false, + alertsEnabled: false, + ssoEnabled: false, + + } +} +export const PLUS_CLOUD_PLAN: PlatformPlanWithOnlyLimits = { + plan: 'plus', + tasksLimit: undefined, + includedAiCredits: 500, + aiCreditsOverageLimit: undefined, + aiCreditsOverageState: AiOverageState.ALLOWED_BUT_OFF, + eligibleForTrial: false, + activeFlowsLimit: 10, + userSeatsLimit: 1, + projectsLimit: 1, + mcpLimit: undefined, + tablesLimit: undefined, + agentsLimit: undefined, + + agentsEnabled: true, + tablesEnabled: true, + todosEnabled: true, + + embeddingEnabled: false, + globalConnectionsEnabled: false, + customRolesEnabled: false, + environmentsEnabled: false, + analyticsEnabled: false, + managePiecesEnabled: false, + manageTemplatesEnabled: false, + customAppearanceEnabled: false, + manageProjectsEnabled: false, + projectRolesEnabled: false, + customDomainsEnabled: false, + apiKeysEnabled: false, + alertsEnabled: false, + ssoEnabled: false, + showPoweredBy: false, + auditLogEnabled: false, +} + + +export const BUSINESS_CLOUD_PLAN: PlatformPlanWithOnlyLimits = { + plan: 'business', + tasksLimit: undefined, + includedAiCredits: 1000, + aiCreditsOverageLimit: undefined, + aiCreditsOverageState: AiOverageState.ALLOWED_BUT_OFF, + eligibleForTrial: false, + activeFlowsLimit: 50, + userSeatsLimit: 5, + projectsLimit: 10, + mcpLimit: undefined, + tablesLimit: undefined, + agentsLimit: undefined, + + tablesEnabled: true, + todosEnabled: true, + agentsEnabled: true, + embeddingEnabled: false, + globalConnectionsEnabled: false, + customRolesEnabled: false, + environmentsEnabled: false, + analyticsEnabled: true, + managePiecesEnabled: false, + manageTemplatesEnabled: false, + customAppearanceEnabled: false, + manageProjectsEnabled: true, + projectRolesEnabled: true, + customDomainsEnabled: false, + apiKeysEnabled: true, + alertsEnabled: true, + ssoEnabled: true, + showPoweredBy: false, + auditLogEnabled: false, + +} + +export const OPEN_SOURCE_PLAN: PlatformPlanWithOnlyLimits = { + eligibleForTrial: false, + embeddingEnabled: false, + tablesEnabled: true, + todosEnabled: true, + globalConnectionsEnabled: false, + customRolesEnabled: false, + tasksLimit: undefined, + + agentsEnabled: true, + includedAiCredits: 0, + aiCreditsOverageLimit: undefined, + aiCreditsOverageState: AiOverageState.ALLOWED_BUT_OFF, + environmentsEnabled: false, + agentsLimit: undefined, + analyticsEnabled: false, + showPoweredBy: false, + + auditLogEnabled: false, + managePiecesEnabled: false, + manageTemplatesEnabled: false, + customAppearanceEnabled: false, + manageProjectsEnabled: false, + projectRolesEnabled: false, + customDomainsEnabled: false, + apiKeysEnabled: false, + alertsEnabled: false, + ssoEnabled: false, + stripeCustomerId: undefined, + stripeSubscriptionId: undefined, + stripeSubscriptionStatus: undefined, +} \ No newline at end of file diff --git a/packages/ee/shared/src/lib/custom-domains/index.ts b/packages/ee/shared/src/lib/custom-domains/index.ts new file mode 100644 index 0000000..d8b74c5 --- /dev/null +++ b/packages/ee/shared/src/lib/custom-domains/index.ts @@ -0,0 +1,32 @@ +import { BaseModelSchema } from '@activepieces/shared' +import { Static, Type } from '@sinclair/typebox' + +export enum CustomDomainStatus { + ACTIVE = 'ACTIVE', + PENDING = 'PENDING', +} + +export const CustomDomain = Type.Object({ + ...BaseModelSchema, + domain: Type.String(), + platformId: Type.String(), + status: Type.Enum(CustomDomainStatus), +}) + +export type CustomDomain = Static + + +export const AddDomainRequest = Type.Object({ + domain: Type.String({ + pattern: '^(?!.*\\.example\\.com$)(?!.*\\.example\\.net$).*', + }), +}) + +export type AddDomainRequest = Static + +export const ListCustomDomainsRequest = Type.Object({ + limit: Type.Optional(Type.Number()), + cursor: Type.Optional(Type.String()), +}) + +export type ListCustomDomainsRequest = Static \ No newline at end of file diff --git a/packages/ee/shared/src/lib/flow-templates/index.ts b/packages/ee/shared/src/lib/flow-templates/index.ts new file mode 100644 index 0000000..527b7da --- /dev/null +++ b/packages/ee/shared/src/lib/flow-templates/index.ts @@ -0,0 +1,14 @@ +import { FlowVersionTemplate, Metadata, Nullable, TemplateType } from '@activepieces/shared' +import { Static, Type } from '@sinclair/typebox' + +export const CreateFlowTemplateRequest = Type.Object({ + description: Type.Optional(Type.String()), + template: FlowVersionTemplate, + blogUrl: Type.Optional(Type.String()), + type: Type.Enum(TemplateType), + tags: Type.Optional(Type.Array(Type.String())), + id: Type.Optional(Type.String()), + metadata: Nullable(Metadata), +}) + +export type CreateFlowTemplateRequest = Static diff --git a/packages/ee/shared/src/lib/git-repo/index.ts b/packages/ee/shared/src/lib/git-repo/index.ts new file mode 100644 index 0000000..01e65cd --- /dev/null +++ b/packages/ee/shared/src/lib/git-repo/index.ts @@ -0,0 +1,83 @@ +import { BaseModelSchema } from '@activepieces/shared' +import { Static, Type } from '@sinclair/typebox' + +export enum GitBranchType { + PRODUCTION = 'PRODUCTION', + DEVELOPMENT = 'DEVELOPMENT', +} + +export const GitRepo = Type.Object({ + ...BaseModelSchema, + remoteUrl: Type.String(), + branch: Type.String(), + branchType: Type.Enum(GitBranchType), + projectId: Type.String(), + sshPrivateKey: Type.String(), + slug: Type.String(), +}) + +export type GitRepo = Static + +export const GitRepoWithoutSensitiveData = Type.Omit(GitRepo, ['sshPrivateKey']) +export type GitRepoWithoutSensitiveData = Static + +export enum GitPushOperationType { + PUSH_FLOW = 'PUSH_FLOW', + DELETE_FLOW = 'DELETE_FLOW', + PUSH_TABLE = 'PUSH_TABLE', + DELETE_TABLE = 'DELETE_TABLE', + PUSH_EVERYTHING = 'PUSH_EVERYTHING', +} + +export const PushFlowsGitRepoRequest = Type.Object({ + type: Type.Union([Type.Literal(GitPushOperationType.PUSH_FLOW), Type.Literal(GitPushOperationType.DELETE_FLOW)]), + commitMessage: Type.String({ + minLength: 1, + }), + flowIds: Type.Array(Type.String()), +}) + +export type PushFlowsGitRepoRequest = Static + +export const PushTablesGitRepoRequest = Type.Object({ + type: Type.Union([Type.Literal(GitPushOperationType.PUSH_TABLE), Type.Literal(GitPushOperationType.DELETE_TABLE)]), + commitMessage: Type.String({ + minLength: 1, + }), + tableIds: Type.Array(Type.String()), +}) + +export type PushTablesGitRepoRequest = Static + +export const PushEverythingGitRepoRequest = Type.Object({ + type: Type.Literal(GitPushOperationType.PUSH_EVERYTHING), + commitMessage: Type.String({ + minLength: 1, + }), +}) +export type PushEverythingGitRepoRequest = Static + +export const PushGitRepoRequest = Type.Union([PushFlowsGitRepoRequest, PushTablesGitRepoRequest, PushEverythingGitRepoRequest]) + +export type PushGitRepoRequest = Static + +export const ConfigureRepoRequest = Type.Object({ + projectId: Type.String({ + minLength: 1, + }), + remoteUrl: Type.String({ + pattern: '^git@', + }), + branch: Type.String({ + minLength: 1, + }), + branchType: Type.Enum(GitBranchType), + sshPrivateKey: Type.String({ + minLength: 1, + }), + slug: Type.String({ + minLength: 1, + }), +}) + +export type ConfigureRepoRequest = Static \ No newline at end of file diff --git a/packages/ee/shared/src/lib/managed-authn/index.ts b/packages/ee/shared/src/lib/managed-authn/index.ts new file mode 100644 index 0000000..d819478 --- /dev/null +++ b/packages/ee/shared/src/lib/managed-authn/index.ts @@ -0,0 +1 @@ +export * from './managed-authn-requests' diff --git a/packages/ee/shared/src/lib/managed-authn/managed-authn-requests.ts b/packages/ee/shared/src/lib/managed-authn/managed-authn-requests.ts new file mode 100644 index 0000000..45de931 --- /dev/null +++ b/packages/ee/shared/src/lib/managed-authn/managed-authn-requests.ts @@ -0,0 +1,8 @@ +import { Static, Type } from '@sinclair/typebox' + +export const ManagedAuthnRequestBody = Type.Object({ + //if you change this you need to update the embed-sdk I can't import it there because it can't have dependencies + externalAccessToken: Type.String(), +}) + +export type ManagedAuthnRequestBody = Static diff --git a/packages/ee/shared/src/lib/oauth-apps/index.ts b/packages/ee/shared/src/lib/oauth-apps/index.ts new file mode 100644 index 0000000..d02f485 --- /dev/null +++ b/packages/ee/shared/src/lib/oauth-apps/index.ts @@ -0,0 +1 @@ +export * from './oauth-app' \ No newline at end of file diff --git a/packages/ee/shared/src/lib/oauth-apps/oauth-app.ts b/packages/ee/shared/src/lib/oauth-apps/oauth-app.ts new file mode 100644 index 0000000..fe18cb3 --- /dev/null +++ b/packages/ee/shared/src/lib/oauth-apps/oauth-app.ts @@ -0,0 +1,26 @@ +import { BaseModelSchema } from '@activepieces/shared' +import { Static, Type } from '@sinclair/typebox' + +export const OAuthApp = Type.Object({ + ...BaseModelSchema, + pieceName: Type.String(), + platformId: Type.String(), + clientId: Type.String(), +}) + +export type OAuthApp = Static + +export const UpsertOAuth2AppRequest = Type.Object({ + pieceName: Type.String(), + clientId: Type.String(), + clientSecret: Type.String(), +}) + +export type UpsertOAuth2AppRequest = Static + +export const ListOAuth2AppRequest = Type.Object({ + limit: Type.Optional(Type.Number()), + cursor: Type.Optional(Type.String()), +}) + +export type ListOAuth2AppRequest = Static \ No newline at end of file diff --git a/packages/ee/shared/src/lib/otp/index.ts b/packages/ee/shared/src/lib/otp/index.ts new file mode 100644 index 0000000..92c2b64 --- /dev/null +++ b/packages/ee/shared/src/lib/otp/index.ts @@ -0,0 +1,3 @@ +export * from './otp-model' +export * from './otp-requests' +export * from './otp-type' diff --git a/packages/ee/shared/src/lib/otp/otp-model.ts b/packages/ee/shared/src/lib/otp/otp-model.ts new file mode 100644 index 0000000..a5cdbcb --- /dev/null +++ b/packages/ee/shared/src/lib/otp/otp-model.ts @@ -0,0 +1,20 @@ +import { ApId, BaseModelSchema } from '@activepieces/shared' +import { Static, Type } from '@sinclair/typebox' +import { OtpType } from './otp-type' + +export type OtpId = ApId + +export enum OtpState { + PENDING = 'PENDING', + CONFIRMED = 'CONFIRMED', +} + +export const OtpModel = Type.Object({ + ...BaseModelSchema, + type: Type.Enum(OtpType), + identityId: ApId, + value: Type.String(), + state: Type.Enum(OtpState), +}) + +export type OtpModel = Static diff --git a/packages/ee/shared/src/lib/otp/otp-requests.ts b/packages/ee/shared/src/lib/otp/otp-requests.ts new file mode 100644 index 0000000..47b2c81 --- /dev/null +++ b/packages/ee/shared/src/lib/otp/otp-requests.ts @@ -0,0 +1,11 @@ +import { Static, Type } from '@sinclair/typebox' +import { OtpType } from './otp-type' + + +export const CreateOtpRequestBody = Type.Object({ + email: Type.String(), + type: Type.Enum(OtpType), +}) + +export type CreateOtpRequestBody = Static + diff --git a/packages/ee/shared/src/lib/otp/otp-type.ts b/packages/ee/shared/src/lib/otp/otp-type.ts new file mode 100644 index 0000000..92fe0aa --- /dev/null +++ b/packages/ee/shared/src/lib/otp/otp-type.ts @@ -0,0 +1,4 @@ +export enum OtpType { + EMAIL_VERIFICATION = 'EMAIL_VERIFICATION', + PASSWORD_RESET = 'PASSWORD_RESET', +} diff --git a/packages/ee/shared/src/lib/product-embed/app-credentials/app-credentials-requests.ts b/packages/ee/shared/src/lib/product-embed/app-credentials/app-credentials-requests.ts new file mode 100644 index 0000000..db9566e --- /dev/null +++ b/packages/ee/shared/src/lib/product-embed/app-credentials/app-credentials-requests.ts @@ -0,0 +1,38 @@ +import { Static, Type } from '@sinclair/typebox' +import { AppCredentialType } from './app-credentials' + +export const ListAppCredentialsRequest = Type.Object({ + projectId: Type.String(), + appName: Type.Optional(Type.String()), + limit: Type.Optional(Type.Number()), + cursor: Type.Optional(Type.String({})), +}) + + +export type ListAppCredentialsRequest = Static + +export const UpsertApiKeyCredentialRequest = Type.Object({ + id: Type.Optional(Type.String()), + appName: Type.String(), + settings: Type.Object({ + type: Type.Literal(AppCredentialType.API_KEY), + }), +}) + + +export const UpsertOAuth2CredentialRequest = Type.Object({ + id: Type.Optional(Type.String()), + appName: Type.String(), + settings: Type.Object({ + type: Type.Literal(AppCredentialType.OAUTH2), + authUrl: Type.String({}), + scope: Type.String(), + tokenUrl: Type.String({}), + clientId: Type.String({}), + clientSecret: Type.String({}), + }), +}) + +export const UpsertAppCredentialRequest = Type.Union([UpsertOAuth2CredentialRequest, UpsertApiKeyCredentialRequest]) + +export type UpsertAppCredentialRequest = Static \ No newline at end of file diff --git a/packages/ee/shared/src/lib/product-embed/app-credentials/app-credentials.ts b/packages/ee/shared/src/lib/product-embed/app-credentials/app-credentials.ts new file mode 100644 index 0000000..c43af4b --- /dev/null +++ b/packages/ee/shared/src/lib/product-embed/app-credentials/app-credentials.ts @@ -0,0 +1,29 @@ +import { BaseModel, OAuth2GrantType, ProjectId } from '@activepieces/shared' + +export type AppCredentialId = string + +export type AppOAuth2Settings = { + type: AppCredentialType.OAUTH2 + authUrl: string + tokenUrl: string + grantType: OAuth2GrantType + clientId: string + clientSecret?: string + scope: string +} + +export type AppApiKeySettings = { + type: AppCredentialType.API_KEY +} +export type AppCredential = { + appName: string + projectId: ProjectId + settings: AppOAuth2Settings | AppApiKeySettings +} & BaseModel + +export enum AppCredentialType { + OAUTH2 = 'OAUTH2', + API_KEY = 'API_KEY', +} + + diff --git a/packages/ee/shared/src/lib/product-embed/app-credentials/index.ts b/packages/ee/shared/src/lib/product-embed/app-credentials/index.ts new file mode 100644 index 0000000..06eced1 --- /dev/null +++ b/packages/ee/shared/src/lib/product-embed/app-credentials/index.ts @@ -0,0 +1,2 @@ +export * from './app-credentials' +export * from './app-credentials-requests' \ No newline at end of file diff --git a/packages/ee/shared/src/lib/product-embed/connection-keys/connection-key.ts b/packages/ee/shared/src/lib/product-embed/connection-keys/connection-key.ts new file mode 100644 index 0000000..7fb6450 --- /dev/null +++ b/packages/ee/shared/src/lib/product-embed/connection-keys/connection-key.ts @@ -0,0 +1,18 @@ +import { BaseModel, ProjectId } from '@activepieces/shared' + +export type ConnectionKeyId = string + +export type ConnectionKey = { + projectId: ProjectId + settings: SigningKeyConnection +} & BaseModel + +export type SigningKeyConnection = { + type: ConnectionKeyType.SIGNING_KEY + publicKey: string + privateKey?: string +} + +export enum ConnectionKeyType { + SIGNING_KEY = 'SIGNING_KEY', +} diff --git a/packages/ee/shared/src/lib/product-embed/connection-keys/connection-requests.ts b/packages/ee/shared/src/lib/product-embed/connection-keys/connection-requests.ts new file mode 100644 index 0000000..6bf1f88 --- /dev/null +++ b/packages/ee/shared/src/lib/product-embed/connection-keys/connection-requests.ts @@ -0,0 +1,49 @@ +import { Static, Type } from '@sinclair/typebox' +import { ConnectionKeyType } from './connection-key' + +export const GetOrDeleteConnectionFromTokenRequest = Type.Object({ + projectId: Type.String(), + token: Type.String(), + appName: Type.String(), +}) + +export type GetOrDeleteConnectionFromTokenRequest = Static + + +export const ListConnectionKeysRequest = Type.Object({ + limit: Type.Optional(Type.Number()), + cursor: Type.Optional(Type.String({})), +}) + + +export type ListConnectionKeysRequest = Static + +export const UpsertApiKeyConnectionFromToken = Type.Object({ + appCredentialId: Type.String(), + apiKey: Type.String(), + token: Type.String(), +}) + +export type UpsertApiKeyConnectionFromToken = Static + +export const UpsertOAuth2ConnectionFromToken = Type.Object({ + appCredentialId: Type.String(), + props: Type.Record(Type.String(), Type.Any()), + token: Type.String(), + code: Type.String(), + redirectUrl: Type.String(), +}) + +export type UpsertOAuth2ConnectionFromToken = Static + +export const UpsertConnectionFromToken = Type.Union([UpsertApiKeyConnectionFromToken, UpsertOAuth2ConnectionFromToken]) + +export type UpsertConnectionFromToken = Static + +export const UpsertSigningKeyConnection = Type.Object({ + settings: Type.Object({ + type: Type.Literal(ConnectionKeyType.SIGNING_KEY), + }), +}) + +export type UpsertSigningKeyConnection = Static \ No newline at end of file diff --git a/packages/ee/shared/src/lib/product-embed/connection-keys/index.ts b/packages/ee/shared/src/lib/product-embed/connection-keys/index.ts new file mode 100644 index 0000000..eff1ebf --- /dev/null +++ b/packages/ee/shared/src/lib/product-embed/connection-keys/index.ts @@ -0,0 +1,2 @@ +export * from './connection-key' +export * from './connection-requests' \ No newline at end of file diff --git a/packages/ee/shared/src/lib/project-members/project-member-request.ts b/packages/ee/shared/src/lib/project-members/project-member-request.ts new file mode 100644 index 0000000..e27aba2 --- /dev/null +++ b/packages/ee/shared/src/lib/project-members/project-member-request.ts @@ -0,0 +1,28 @@ +import { Static, Type } from '@sinclair/typebox' + +export const AcceptInvitationRequest = Type.Object({ + token: Type.String(), +}) +export type AcceptInvitationRequest = Static + +export const ListProjectMembersRequestQuery = Type.Object({ + projectId: Type.String(), + projectRoleId: Type.Optional(Type.String()), + cursor: Type.Optional(Type.String()), + limit: Type.Optional(Type.Number()), +}) + +export type ListProjectMembersRequestQuery = Static + +export const AcceptProjectResponse = Type.Object({ + registered: Type.Boolean(), +}) + +export type AcceptProjectResponse = Static + + +export const UpdateProjectMemberRoleRequestBody = Type.Object({ + role: Type.String(), +}) + +export type UpdateProjectMemberRoleRequestBody = Static \ No newline at end of file diff --git a/packages/ee/shared/src/lib/project-members/project-member.ts b/packages/ee/shared/src/lib/project-members/project-member.ts new file mode 100644 index 0000000..318739a --- /dev/null +++ b/packages/ee/shared/src/lib/project-members/project-member.ts @@ -0,0 +1,24 @@ +import { ApId, BaseModelSchema, ProjectMetaData, ProjectRole, UserWithMetaInformation } from '@activepieces/shared' +import { Static, Type } from '@sinclair/typebox' + +export type ProjectMemberId = string + +export const ProjectMember = Type.Object({ + ...BaseModelSchema, + platformId: ApId, + userId: ApId, + projectId: Type.String(), + projectRoleId: ApId, +}, { + description: 'Project member is which user is assigned to a project.', +}) + +export type ProjectMember = Static + +export const ProjectMemberWithUser = Type.Composite([ProjectMember, Type.Object({ + user: UserWithMetaInformation, + projectRole: ProjectRole, + project: ProjectMetaData, +})]) + +export type ProjectMemberWithUser = Static \ No newline at end of file diff --git a/packages/ee/shared/src/lib/project/project-requests.ts b/packages/ee/shared/src/lib/project/project-requests.ts new file mode 100644 index 0000000..11e19f9 --- /dev/null +++ b/packages/ee/shared/src/lib/project/project-requests.ts @@ -0,0 +1,30 @@ +import { Metadata, NotificationStatus, Nullable, PiecesFilterType, SAFE_STRING_PATTERN } from '@activepieces/shared' +import { Static, Type } from '@sinclair/typebox' + +export const UpdateProjectPlatformRequest = Type.Object({ + notifyStatus: Type.Optional(Type.Enum(NotificationStatus)), + releasesEnabled: Type.Optional(Type.Boolean()), + displayName: Type.Optional(Type.String({ + pattern: SAFE_STRING_PATTERN, + })), + externalId: Type.Optional(Type.String()), + metadata: Type.Optional(Metadata), + plan: Type.Optional(Type.Object({ + tasks: Type.Optional(Type.Number({})), + pieces: Type.Optional(Type.Array(Type.String({}))), + piecesFilterType: Type.Optional(Type.Enum(PiecesFilterType)), + aiCredits: Type.Optional(Type.Number({})), + })), +}) + +export type UpdateProjectPlatformRequest = Static + +export const CreatePlatformProjectRequest = Type.Object({ + displayName: Type.String({ + pattern: SAFE_STRING_PATTERN, + }), + externalId: Nullable(Type.String()), + metadata: Nullable(Metadata), +}) + +export type CreatePlatformProjectRequest = Static diff --git a/packages/ee/shared/src/lib/signing-key/index.ts b/packages/ee/shared/src/lib/signing-key/index.ts new file mode 100644 index 0000000..6fcd0e1 --- /dev/null +++ b/packages/ee/shared/src/lib/signing-key/index.ts @@ -0,0 +1,3 @@ +export * from './signing-key-model' +export * from './signing-key-response' +export * from './signing-key.request' diff --git a/packages/ee/shared/src/lib/signing-key/signing-key-model.ts b/packages/ee/shared/src/lib/signing-key/signing-key-model.ts new file mode 100644 index 0000000..d118977 --- /dev/null +++ b/packages/ee/shared/src/lib/signing-key/signing-key-model.ts @@ -0,0 +1,19 @@ +import { ApId, BaseModelSchema } from '@activepieces/shared' +import { Static, Type } from '@sinclair/typebox' + +export enum KeyAlgorithm { + RSA = 'RSA', +} + +export type SigningKeyId = ApId + +export const SigningKey = Type.Object({ + ...BaseModelSchema, + platformId: ApId, + publicKey: Type.String(), + displayName: Type.String(), + /* algorithm used to generate this key pair */ + algorithm: Type.Enum(KeyAlgorithm), +}) + +export type SigningKey = Static diff --git a/packages/ee/shared/src/lib/signing-key/signing-key-response.ts b/packages/ee/shared/src/lib/signing-key/signing-key-response.ts new file mode 100644 index 0000000..d497b0a --- /dev/null +++ b/packages/ee/shared/src/lib/signing-key/signing-key-response.ts @@ -0,0 +1,5 @@ +import { SigningKey } from './signing-key-model' + +export type AddSigningKeyResponse = SigningKey & { + privateKey: string +} diff --git a/packages/ee/shared/src/lib/signing-key/signing-key.request.ts b/packages/ee/shared/src/lib/signing-key/signing-key.request.ts new file mode 100644 index 0000000..5bd1316 --- /dev/null +++ b/packages/ee/shared/src/lib/signing-key/signing-key.request.ts @@ -0,0 +1,7 @@ +import { Static, Type } from '@sinclair/typebox' + +export const AddSigningKeyRequestBody = Type.Object({ + displayName: Type.String(), +}) + +export type AddSigningKeyRequestBody = Static diff --git a/packages/ee/shared/tsconfig.json b/packages/ee/shared/tsconfig.json new file mode 100644 index 0000000..f2400ab --- /dev/null +++ b/packages/ee/shared/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/ee/shared/tsconfig.lib.json b/packages/ee/shared/tsconfig.lib.json new file mode 100644 index 0000000..4befa7f --- /dev/null +++ b/packages/ee/shared/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/packages/ee/ui/.eslintrc.json b/packages/ee/ui/.eslintrc.json new file mode 100644 index 0000000..73a2718 --- /dev/null +++ b/packages/ee/ui/.eslintrc.json @@ -0,0 +1,17 @@ +{ + "extends": ["../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts"], + "rules": { + "@typescript-eslint/adjacent-overload-signatures": "off", + "@typescript-eslint/no-non-null-assertion": "off" + }, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + } + ] + } \ No newline at end of file diff --git a/packages/ee/ui/embed-sdk/.eslintrc.json b/packages/ee/ui/embed-sdk/.eslintrc.json new file mode 100644 index 0000000..eac708a --- /dev/null +++ b/packages/ee/ui/embed-sdk/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/ee/ui/embed-sdk/README.md b/packages/ee/ui/embed-sdk/README.md new file mode 100644 index 0000000..9c12687 --- /dev/null +++ b/packages/ee/ui/embed-sdk/README.md @@ -0,0 +1,9 @@ +# ee-embed-sdk + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx bundle ee-embed-sdk` to build the library. + +Check project.json "output" property to see the generated file location and its name. \ No newline at end of file diff --git a/packages/ee/ui/embed-sdk/package.json b/packages/ee/ui/embed-sdk/package.json new file mode 100644 index 0000000..7d499c7 --- /dev/null +++ b/packages/ee/ui/embed-sdk/package.json @@ -0,0 +1,7 @@ +{ + "name": "ee-embed-sdk", + "version": "0.5.0", + "type": "commonjs", + "main": "./src/index.js", + "typings": "./src/index.d.ts" +} \ No newline at end of file diff --git a/packages/ee/ui/embed-sdk/project.json b/packages/ee/ui/embed-sdk/project.json new file mode 100644 index 0000000..6564311 --- /dev/null +++ b/packages/ee/ui/embed-sdk/project.json @@ -0,0 +1,46 @@ +{ + "name": "ee-embed-sdk", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/ee/ui/embed-sdk/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/packages/ee/ui/embed-sdk", + "main": "packages/ee/ui/embed-sdk/src/index.ts", + "tsConfig": "packages/ee/ui/embed-sdk/tsconfig.lib.json", + "assets": [] + } + }, + "bundle": { + "executor": "@nx/webpack:webpack", + "outputs": ["{options.outputPath}"], + "options": { + "target": "web", + "compiler": "tsc", + "outputFileName": "bundled.js", + "outputPath": "dist/packages/ee/ui/embed-sdk", + "main": "packages/ee/ui/embed-sdk/src/index.ts", + "tsConfig": "packages/ee/ui/embed-sdk/tsconfig.lib.json", + "assets": [], + "webpackConfig": "packages/ee/ui/embed-sdk/webpack.config.js", + "generatePackageJson": true, + "babelUpwardRootMode": true + }, + "configurations": { + "production": { + "optimization": true, + "extractLicenses": true, + "inspect": false + } + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + } + }, + "tags": [] +} diff --git a/packages/ee/ui/embed-sdk/src/index.ts b/packages/ee/ui/embed-sdk/src/index.ts new file mode 100644 index 0000000..f644b74 --- /dev/null +++ b/packages/ee/ui/embed-sdk/src/index.ts @@ -0,0 +1,613 @@ + +//Client ==> Activepieces +//Vendor ==> Customers using our embed sdk +export enum ActivepiecesClientEventName { + CLIENT_INIT = 'CLIENT_INIT', + CLIENT_ROUTE_CHANGED = 'CLIENT_ROUTE_CHANGED', + CLIENT_NEW_CONNECTION_DIALOG_CLOSED = 'CLIENT_NEW_CONNECTION_DIALOG_CLOSED', + CLIENT_SHOW_CONNECTION_IFRAME = 'CLIENT_SHOW_CONNECTION_IFRAME', + CLIENT_CONNECTION_NAME_IS_INVALID = 'CLIENT_CONNECTION_NAME_IS_INVALID', + CLIENT_AUTHENTICATION_SUCCESS = 'CLIENT_AUTHENTICATION_SUCCESS', + CLIENT_AUTHENTICATION_FAILED = 'CLIENT_AUTHENTICATION_FAILED', + CLIENT_CONFIGURATION_FINISHED = 'CLIENT_CONFIGURATION_FINISHED', + CLIENT_CONNECTION_PIECE_NOT_FOUND = 'CLIENT_CONNECTION_PIECE_NOT_FOUND', + CLIENT_BUILDER_HOME_BUTTON_CLICKED = 'CLIENT_BUILDER_HOME_BUTTON_CLICKED', +} +export interface ActivepiecesClientInit { + type: ActivepiecesClientEventName.CLIENT_INIT; + data: Record; +} +export interface ActivepiecesClientAuthenticationSuccess { + type: ActivepiecesClientEventName.CLIENT_AUTHENTICATION_SUCCESS; + data: Record; +} +export interface ActivepiecesClientAuthenticationFailed { + type: ActivepiecesClientEventName.CLIENT_AUTHENTICATION_FAILED; + data: unknown; +} +// Added this event so in the future if we add another step between authentication and configuration finished, we can use this event to notify the parent +export interface ActivepiecesClientConfigurationFinished { + type: ActivepiecesClientEventName.CLIENT_CONFIGURATION_FINISHED; + data: Record; +} +export interface ActivepiecesClientShowConnectionIframe { + type: ActivepiecesClientEventName.CLIENT_SHOW_CONNECTION_IFRAME; + data: Record; +} +export interface ActivepiecesClientConnectionNameIsInvalid { + type: ActivepiecesClientEventName.CLIENT_CONNECTION_NAME_IS_INVALID; + data: { + error: string; + }; +} + +export interface ActivepiecesClientConnectionPieceNotFound { + type: ActivepiecesClientEventName.CLIENT_CONNECTION_PIECE_NOT_FOUND; + data: { + error: string + }; +} + +export interface ActivepiecesClientRouteChanged { + type: ActivepiecesClientEventName.CLIENT_ROUTE_CHANGED; + data: { + route: string; + }; +} +export interface ActivepiecesNewConnectionDialogClosed { + type: ActivepiecesClientEventName.CLIENT_NEW_CONNECTION_DIALOG_CLOSED; + data: { connection?: { id: string; name: string } }; +} +export interface ActivepiecesBuilderHomeButtonClicked { + type: ActivepiecesClientEventName.CLIENT_BUILDER_HOME_BUTTON_CLICKED; + data: { + route: string; + }; +} + +type IframeWithWindow = HTMLIFrameElement & { contentWindow: Window }; + +export const NEW_CONNECTION_QUERY_PARAMS = { + name: 'pieceName', + connectionName: 'connectionName', + randomId: 'randomId' +}; + +export type ActivepiecesClientEvent = + | ActivepiecesClientInit + | ActivepiecesClientRouteChanged; + +export enum ActivepiecesVendorEventName { + VENDOR_INIT = 'VENDOR_INIT', + VENDOR_ROUTE_CHANGED = 'VENDOR_ROUTE_CHANGED', +} + +export interface ActivepiecesVendorRouteChanged { + type: ActivepiecesVendorEventName.VENDOR_ROUTE_CHANGED; + data: { + vendorRoute: string; + }; +} + +export interface ActivepiecesVendorInit { + type: ActivepiecesVendorEventName.VENDOR_INIT; + data: { + hideSidebar: boolean; + hideFlowNameInBuilder?: boolean; + disableNavigationInBuilder: boolean | 'keep_home_button_only'; + hideFolders?: boolean; + sdkVersion?: string; + jwtToken: string; + initialRoute?: string + fontUrl?: string; + fontFamily?: string; + hideExportAndImportFlow?: boolean; + hideDuplicateFlow?: boolean; + homeButtonIcon?: 'back' | 'logo'; + emitHomeButtonClickedEvent?: boolean; + locale?: string; + mode?: 'light' | 'dark'; + }; +} + + + +type newWindowFeatures = { + height?: number, + width?: number, + top?: number, + left?: number, +} +type EmbeddingParam = { + containerId?: string; + styling?: { + fontUrl?: string; + fontFamily?: string; + mode?: 'light' | 'dark'; + }; + locale?:string; + builder?: { + disableNavigation?: boolean; + hideFlowName?: boolean; + homeButtonIcon: 'back' | 'logo'; + homeButtonClickedHandler?: (data: { + route: string; + }) => void; + }; + dashboard?: { + hideSidebar?: boolean; + }; + hideExportAndImportFlow?: boolean; + hideDuplicateFlow?: boolean; + hideFolders?: boolean; + navigation?: { + handler?: (data: { route: string }) => void; + } +} +type ConfigureParams = { + instanceUrl: string; + jwtToken: string; + prefix?: string; + embedding?: EmbeddingParam; +} + +type RequestMethod = Required>[1]['method']; +class ActivepiecesEmbedded { + readonly _sdkVersion = "0.5.0"; + //used for Automatically Sync URL feature i.e /org/1234 + _prefix = '/'; + _instanceUrl = ''; + //this is used to authenticate embedding for the first time + _jwtToken = ''; + _resolveNewConnectionDialogClosed?: (result: ActivepiecesNewConnectionDialogClosed['data']) => void; + _dashboardAndBuilderIframeWindow?: Window; + _rejectNewConnectionDialogClosed?: (error: unknown) => void; + _handleVendorNavigation?: (data: { route: string }) => void; + _handleClientNavigation?: (data: { route: string }) => void; + _parentOrigin = window.location.origin; + readonly _MAX_CONTAINER_CHECK_COUNT = 100; + readonly _HUNDRED_MILLISECONDS = 100; + _embeddingAuth?: { + //this is used to do authentication with the backend + userJwtToken:string, + platformId:string, + projectId:string + }; + _embeddingState?: EmbeddingParam; + configure({ + jwtToken, + instanceUrl, + embedding, + prefix, + }: ConfigureParams) { + this._instanceUrl = this._removeTrailingSlashes(instanceUrl); + this._jwtToken = jwtToken; + this._prefix = this._removeTrailingSlashes(this._prependForwardSlashToRoute(prefix ?? '/')); + this._embeddingState = embedding; + if (embedding?.containerId) { + return this._initializeBuilderAndDashboardIframe({ + containerSelector: `#${embedding.containerId}` + }); + } + return new Promise((resolve) => { resolve({ status: "success" }) }); + } + + + private _initializeBuilderAndDashboardIframe = ({ + containerSelector + }: { + containerSelector: string + }) => { + return new Promise((resolve, reject) => { + this._addGracePeriodBeforeMethod({ + condition: () => { + return !!document.querySelector(containerSelector); + }, + method: () => { + const iframeContainer = document.querySelector(containerSelector); + if (iframeContainer) { + const iframeWindow = this.connectToEmbed({ + iframeContainer, + callbackAfterConfigurationFinished: () => { + resolve({ status: "success" }); + }, + initialRoute: '/' + }).contentWindow; + this._dashboardAndBuilderIframeWindow = iframeWindow; + this._checkForClientRouteChanges(iframeWindow); + this._checkForBuilderHomeButtonClicked(iframeWindow); + } + else { + reject({ + status: "error", + error: { + message: 'container not found', + }, + }); + } + }, + errorMessage: 'container not found', + }); + }); + + + }; + + private _setupInitialMessageHandler(targetWindow: Window, initialRoute: string, callbackAfterConfigurationFinished?: () => void) { + const initialMessageHandler = (event: MessageEvent) => { + if (event.source === targetWindow) { + switch (event.data.type) { + case ActivepiecesClientEventName.CLIENT_INIT: { + const apEvent: ActivepiecesVendorInit = { + type: ActivepiecesVendorEventName.VENDOR_INIT, + data: { + hideSidebar: this._embeddingState?.dashboard?.hideSidebar ?? false, + disableNavigationInBuilder: this._embeddingState?.builder?.disableNavigation ?? false, + hideFolders: this._embeddingState?.hideFolders ?? false, + hideFlowNameInBuilder: this._embeddingState?.builder?.hideFlowName ?? false, + jwtToken: this._jwtToken, + initialRoute, + fontUrl: this._embeddingState?.styling?.fontUrl, + fontFamily: this._embeddingState?.styling?.fontFamily, + hideExportAndImportFlow: this._embeddingState?.hideExportAndImportFlow ?? false, + emitHomeButtonClickedEvent: this._embeddingState?.builder?.homeButtonClickedHandler !== undefined, + locale: this._embeddingState?.locale ?? 'en', + sdkVersion: this._sdkVersion, + homeButtonIcon: this._embeddingState?.builder?.homeButtonIcon ?? 'logo', + hideDuplicateFlow: this._embeddingState?.hideDuplicateFlow ?? false, + mode: this._embeddingState?.styling?.mode, + }, + }; + targetWindow.postMessage(apEvent, '*'); + this._createAuthenticationSuccessListener(targetWindow); + this._createAuthenticationFailedListener(targetWindow); + this._createConfigurationFinishedListener(targetWindow, callbackAfterConfigurationFinished); + window.removeEventListener('message', initialMessageHandler); + break; + } + } + } + }; + window.addEventListener('message', initialMessageHandler); + } + private connectToEmbed({ iframeContainer, initialRoute, callbackAfterConfigurationFinished }: { + iframeContainer: Element, + initialRoute: string, + callbackAfterConfigurationFinished?: () => void + }): IframeWithWindow { + const iframe = this._createIframe({ src: `${this._instanceUrl}/embed?currentDate=${Date.now()}` }); + iframeContainer.appendChild(iframe); + if (!this._doesFrameHaveWindow(iframe)) { + this._errorCreator('iframe window not accessible'); + } + const iframeWindow = iframe.contentWindow; + this._setupInitialMessageHandler(iframeWindow, initialRoute, callbackAfterConfigurationFinished); + return iframe; + } + + private _createConfigurationFinishedListener = (targetWindow: Window, callbackAfterConfigurationFinished?: () => void) => { + const configurationFinishedHandler = (event: MessageEvent) => { + if (event.data.type === ActivepiecesClientEventName.CLIENT_CONFIGURATION_FINISHED && event.source === targetWindow) { + this._logger().log('Configuration finished') + if (callbackAfterConfigurationFinished) { + callbackAfterConfigurationFinished(); + } + } + } + window.addEventListener('message', configurationFinishedHandler); + } + + private _createAuthenticationFailedListener = (targetWindow: Window) => { + const authenticationFailedHandler = (event: MessageEvent) => { + if (event.data.type === ActivepiecesClientEventName.CLIENT_AUTHENTICATION_FAILED && event.source === targetWindow) { + this._errorCreator('Authentication failed',event.data.data); + } + } + window.addEventListener('message', authenticationFailedHandler); + } + + private _createAuthenticationSuccessListener = (targetWindow: Window) => { + const authenticationSuccessHandler = (event: MessageEvent) => { + if (event.data.type === ActivepiecesClientEventName.CLIENT_AUTHENTICATION_SUCCESS && event.source === targetWindow) { + this._logger().log('Authentication success') + window.removeEventListener('message', authenticationSuccessHandler); + } + } + window.addEventListener('message', authenticationSuccessHandler); + } + private _createIframe({ src }: { src: string }) { + const iframe = document.createElement('iframe'); + iframe.src = src; + iframe.setAttribute('allow', 'clipboard-read; clipboard-write'); + return iframe; + } + + private _getNewWindowFeatures(requestedFeats:newWindowFeatures) { + const windowFeats:newWindowFeatures = { + height: 700, + width: 700, + top: 0, + left: 0, + } + Object.keys(windowFeats).forEach((key) => { + if(typeof requestedFeats === 'object' && requestedFeats[key as keyof newWindowFeatures]){ + windowFeats[key as keyof newWindowFeatures ] = requestedFeats[key as keyof typeof requestedFeats] + } + }) + return `width=${windowFeats.width},height=${windowFeats.height},top=${windowFeats.top},left=${windowFeats.left}` + } + + private _addConnectionIframe({pieceName, connectionName}:{pieceName:string, connectionName?:string}) { + const connectionsIframe = this.connectToEmbed({ + iframeContainer: document.body, + initialRoute: `/embed/connections?${NEW_CONNECTION_QUERY_PARAMS.name}=${pieceName}&randomId=${Date.now()}&${NEW_CONNECTION_QUERY_PARAMS.connectionName}=${connectionName || ''}` + }); + connectionsIframe.style.cssText = ['display:none', 'position:fixed', 'top:0', 'left:0', 'width:100%', 'height:100%', 'border:none'].join(';'); + return connectionsIframe; + } + + private _openNewWindowForConnections({pieceName, connectionName,newWindow}:{pieceName:string, connectionName?:string, newWindow:newWindowFeatures}) { + const popup = window.open(`${this._instanceUrl}/embed`, '_blank', this._getNewWindowFeatures(newWindow)); + if (!popup) { + this._errorCreator('Failed to open popup window'); + } + this._setupInitialMessageHandler(popup, `/embed/connections?${NEW_CONNECTION_QUERY_PARAMS.name}=${pieceName}&randomId=${Date.now()}&${NEW_CONNECTION_QUERY_PARAMS.connectionName}=${connectionName || ''}`); + return popup; + } + async connect({ pieceName, connectionName, newWindow }: { + pieceName: string, + connectionName?: string, + newWindow?:{ + height?: number, + width?: number, + top?: number, + left?: number, + } + }) { + this._cleanConnectionIframe(); + return this._addGracePeriodBeforeMethod({ + condition: () => { + return !!document.body; + }, + method: async () => { + const target = newWindow? this._openNewWindowForConnections({pieceName, connectionName,newWindow}) : this._addConnectionIframe({pieceName, connectionName}); + //don't check for window because (instanceof Window) is false for popups + if(!(target instanceof HTMLIFrameElement)) { + const checkClosed = setInterval(() => { + if (target.closed) { + clearInterval(checkClosed); + if(this._resolveNewConnectionDialogClosed) { + this._resolveNewConnectionDialogClosed({connection:undefined}) + } + } + }, 500); + } + return new Promise((resolve, reject) => { + this._resolveNewConnectionDialogClosed = resolve; + this._rejectNewConnectionDialogClosed = reject; + this._setConnectionIframeEventsListener(target); + }); + }, + errorMessage: 'unable to add connection embedding' + }); + } + + + navigate({ route }: { route: string }) { + if (!this._dashboardAndBuilderIframeWindow) { + this._logger().error('dashboard iframe not found'); + return; + } + const event: ActivepiecesVendorRouteChanged = { + type: ActivepiecesVendorEventName.VENDOR_ROUTE_CHANGED, + data: { + vendorRoute: route, + }, + }; + this._dashboardAndBuilderIframeWindow.postMessage(event, '*'); + } + + private _prependForwardSlashToRoute(route: string) { + return route.startsWith('/') ? route : `/${route}`; + } + private _checkForClientRouteChanges = (source: Window) => { + window.addEventListener( + 'message', + (event: MessageEvent) => { + if ( + event.data.type === + ActivepiecesClientEventName.CLIENT_ROUTE_CHANGED && + event.source === source && + this._embeddingState?.navigation?.handler + ) { + const routeWithPrefix = this._prefix + this._prependForwardSlashToRoute(event.data.data.route); + this._embeddingState.navigation.handler({ route: routeWithPrefix }); + return; + } + } + ); + }; + + private _checkForBuilderHomeButtonClicked = (source: Window) => { + window.addEventListener('message', (event: MessageEvent) => { + if (event.data.type === ActivepiecesClientEventName.CLIENT_BUILDER_HOME_BUTTON_CLICKED && event.source === source) { + this._embeddingState?.builder?.homeButtonClickedHandler?.(event.data.data); + } + }); + } + + private _extractRouteAfterPrefix(vendorUrl: string, parentOriginWithPrefix: string) { + return vendorUrl.split(parentOriginWithPrefix)[1]; + } + + //used for Automatically Sync URL feature + extractActivepiecesRouteFromUrl({ vendorUrl }: { vendorUrl: string }) { + return this._extractRouteAfterPrefix(vendorUrl, this._removeTrailingSlashes(this._parentOrigin) + this._prefix); + } + + + private _doesFrameHaveWindow( + frame: HTMLIFrameElement + ): frame is IframeWithWindow { + return frame.contentWindow !== null; + } + // eslint-disable-next-line @typescript-eslint/no-empty-function + private _cleanConnectionIframe = () => { }; + private _setConnectionIframeEventsListener(target: Window | HTMLIFrameElement ) { + const connectionRelatedMessageHandler = (event: MessageEvent) => { + if (event.data.type) { + switch (event.data.type) { + case ActivepiecesClientEventName.CLIENT_NEW_CONNECTION_DIALOG_CLOSED: { + if (this._resolveNewConnectionDialogClosed) { + this._resolveNewConnectionDialogClosed(event.data.data); + } + this._removeEmbedding(target); + window.removeEventListener('message', connectionRelatedMessageHandler); + break; + } + case ActivepiecesClientEventName.CLIENT_CONNECTION_NAME_IS_INVALID: + case ActivepiecesClientEventName.CLIENT_CONNECTION_PIECE_NOT_FOUND: { + this._removeEmbedding(target); + if (this._rejectNewConnectionDialogClosed) { + this._rejectNewConnectionDialogClosed(event.data.data); + } + else { + this._errorCreator(event.data.data.error); + } + window.removeEventListener('message', connectionRelatedMessageHandler); + break; + } + case ActivepiecesClientEventName.CLIENT_SHOW_CONNECTION_IFRAME: { + if (target instanceof HTMLIFrameElement) { + target.style.display = 'block'; + } + break; + } + } + } + } + window.addEventListener( + 'message', + connectionRelatedMessageHandler + ); + this._cleanConnectionIframe = () => { + window.removeEventListener('message', connectionRelatedMessageHandler); + this._resolveNewConnectionDialogClosed = undefined; + this._rejectNewConnectionDialogClosed = undefined; + this._removeEmbedding(target); + } + } + + private _removeTrailingSlashes(str: string) { + return str.endsWith('/') ? str.slice(0, -1) : str; + } + private _removeStartingSlashes(str: string) { + return str.startsWith('/') ? str.slice(1) : str; + } + /**Adds a grace period before executing the method depending on the condition */ + private _addGracePeriodBeforeMethod({ + method, + condition, + errorMessage, + }: { + method: () => Promise | void; + condition: () => boolean; + /**Error message to show when grace period passes */ + errorMessage: string; + }) { + return new Promise((resolve, reject) => { + let checkCounter = 0; + if (condition()) { + resolve(method()); + return; + } + const checker = setInterval(() => { + if (checkCounter >= this._MAX_CONTAINER_CHECK_COUNT) { + this._logger().error(errorMessage); + reject(errorMessage); + return; + } + checkCounter++; + if (condition()) { + clearInterval(checker); + resolve(method()); + } + }, this._HUNDRED_MILLISECONDS); + },); + } + + + private _errorCreator(message: string,...args:any[]): never { + this._logger().error(message,...args) + throw new Error(`Activepieces: ${message}`,); + } + private _removeEmbedding(target:HTMLIFrameElement | Window) { + if (target) { + if (target instanceof HTMLIFrameElement) { + target.remove(); + } else { + target.close(); + } + } + else { + this._logger().warn(`couldn't remove embedding`) + } + } + private _logger() { + return{ + log: (message: string, ...args: any[]) => { + console.log(`Activepieces: ${message}`, ...args) + }, + error: (message: string, ...args: any[]) => { + console.error(`Activepieces: ${message}`, ...args) + }, + warn: (message: string, ...args: any[]) => { + console.warn(`Activepieces: ${message}`, ...args) + } + } + } + private async fetchEmbeddingAuth(params:{jwtToken:string} | undefined) { + if(this._embeddingAuth) { + return this._embeddingAuth; + } + const jwtToken = params?.jwtToken?? this._jwtToken; + if(!jwtToken) { + this._errorCreator('jwt token not found'); + } + const response = await this.request({path: '/managed-authn/external-token', method: 'POST', body: { + externalAccessToken: jwtToken, + }}, false) + this._embeddingAuth = { + userJwtToken: response.token, + platformId: response.platformId, + projectId: response.projectId, + } + return this._embeddingAuth; + } + + + + + async request({path, method, body, queryParams}:{path:string, method: RequestMethod, body?:Record, queryParams?:Record}, useJwtToken = true) { + const headers:Record = { + } + if(body) { + headers['Content-Type'] = 'application/json' + } + if(useJwtToken) { + const embeddingAuth = await this.fetchEmbeddingAuth({jwtToken: this._jwtToken}); + headers['Authorization'] = `Bearer ${embeddingAuth.userJwtToken}` + } + const queryParamsString = queryParams ? `?${new URLSearchParams(queryParams).toString()}` : ''; + return fetch(`${this._removeTrailingSlashes(this._instanceUrl)}/api/v1/${this._removeStartingSlashes(path)}${queryParamsString}`, { + method, + body: body ? JSON.stringify(body) : undefined, + headers, + }).then(res => res.json()) + } + +} + + +(window as any).activepieces = new ActivepiecesEmbedded(); +(window as any).ActivepiecesEmbedded = ActivepiecesEmbedded; \ No newline at end of file diff --git a/packages/ee/ui/embed-sdk/tsconfig.json b/packages/ee/ui/embed-sdk/tsconfig.json new file mode 100644 index 0000000..098b403 --- /dev/null +++ b/packages/ee/ui/embed-sdk/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "amd", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/ee/ui/embed-sdk/tsconfig.lib.json b/packages/ee/ui/embed-sdk/tsconfig.lib.json new file mode 100644 index 0000000..18f2d37 --- /dev/null +++ b/packages/ee/ui/embed-sdk/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/packages/ee/ui/embed-sdk/webpack.config.js b/packages/ee/ui/embed-sdk/webpack.config.js new file mode 100644 index 0000000..57a168a --- /dev/null +++ b/packages/ee/ui/embed-sdk/webpack.config.js @@ -0,0 +1,5 @@ +const { composePlugins, withNx } = require('@nx/webpack'); + +module.exports = composePlugins(withNx(), (config) => { + return config; +}); diff --git a/packages/engine/.eslintrc.json b/packages/engine/.eslintrc.json new file mode 100644 index 0000000..56155b0 --- /dev/null +++ b/packages/engine/.eslintrc.json @@ -0,0 +1,21 @@ +{ + "extends": [ + "../server/api/.eslintrc.json" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.js" + ], + "parserOptions": { + "project": [ + "packages/engine/tsconfig.*?.json" + ] + }, + "rules": { + "no-console": "off" + } + } + ] +} diff --git a/packages/engine/README.md b/packages/engine/README.md new file mode 100644 index 0000000..fb16178 --- /dev/null +++ b/packages/engine/README.md @@ -0,0 +1,11 @@ +# engine + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build engine` to build the library. + +## Running unit tests + +Run `nx test engine` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/packages/engine/jest.config.ts b/packages/engine/jest.config.ts new file mode 100644 index 0000000..6074de7 --- /dev/null +++ b/packages/engine/jest.config.ts @@ -0,0 +1,22 @@ + +process.env.AP_EXECUTION_MODE = 'UNSANDBOXED' +process.env.AP_BASE_CODE_DIRECTORY = 'packages/engine/test/resources/codes' +process.env.AP_TEST_MODE = 'true' + +/* eslint-disable */ +export default { + displayName: 'engine', + preset: '../../jest.preset.js', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': [ + 'ts-jest', + { + tsconfig: '/tsconfig.spec.json', + }, + ], + }, + transformIgnorePatterns: ["node_modules/(?!string\-replace\-async)"], + moduleFileExtensions: ['ts', 'js', 'html', 'node'], + coverageDirectory: '../../coverage/packages/engine', +}; diff --git a/packages/engine/package.json b/packages/engine/package.json new file mode 100644 index 0000000..905fdd6 --- /dev/null +++ b/packages/engine/package.json @@ -0,0 +1,6 @@ +{ + "name": "@activepieces/engine", + "version": "0.7.0", + "type": "commonjs" +} + diff --git a/packages/engine/project.json b/packages/engine/project.json new file mode 100644 index 0000000..e07780c --- /dev/null +++ b/packages/engine/project.json @@ -0,0 +1,62 @@ +{ + "name": "engine", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/engine/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/webpack:webpack", + "outputs": ["{options.outputPath}"], + "options": { + "target": "node", + "compiler": "tsc", + "outputPath": "dist/packages/engine", + "main": "packages/engine/src/main.ts", + "tsConfig": "packages/engine/tsconfig.lib.json", + "assets": [], + "webpackConfig": "packages/engine/webpack.config.js", + "babelUpwardRootMode": true, + "statsJson": false, + "sourceMap": true + }, + "configurations": { + "production": { + "optimization": true, + "extractLicenses": true, + "inspect": false, + "statsJson": false + } + } + }, + "serve": { + "executor": "@nx/js:node", + "options": { + "buildTarget": "engine:build", + "inspect": false + } + }, + "publish": { + "executor": "nx:run-commands", + "options": { + "command": "node tools/scripts/publish.mjs engine {args.ver} {args.tag}" + }, + "dependsOn": ["build"] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["packages/engine/**/*.ts"] + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "packages/engine/jest.config.ts", + "silent": true + } + } + }, + "tags": [] +} diff --git a/packages/engine/src/lib/core/code/code-sandbox-common.ts b/packages/engine/src/lib/core/code/code-sandbox-common.ts new file mode 100644 index 0000000..c6922ef --- /dev/null +++ b/packages/engine/src/lib/core/code/code-sandbox-common.ts @@ -0,0 +1,40 @@ +export type CodeModule = { + code(input: unknown): Promise +} + +export type CodeSandbox = { + /** + * Executes a {@link CodeModule}. + */ + runCodeModule(params: RunCodeModuleParams): Promise + + /** + * Executes a script. + */ + runScript(params: RunScriptParams): Promise +} + +type RunCodeModuleParams = { + /** + * The {@link CodeModule} to execute. + */ + codeModule: CodeModule + + /** + * The inputs that are passed to the {@link CodeModule}. + */ + inputs: Record +} + +type RunScriptParams = { + /** + * A serialized script that will be executed in the sandbox. + * The script can either be sync or async. + */ + script: string + + /** + * A key-value map of variables available to the script during execution. + */ + scriptContext: Record +} diff --git a/packages/engine/src/lib/core/code/code-sandbox.ts b/packages/engine/src/lib/core/code/code-sandbox.ts new file mode 100644 index 0000000..dacdc87 --- /dev/null +++ b/packages/engine/src/lib/core/code/code-sandbox.ts @@ -0,0 +1,35 @@ +import { assertNotNullOrUndefined, ExecutionMode } from '@activepieces/shared' +import { CodeSandbox } from '../../core/code/code-sandbox-common' + +export const EXECUTION_MODE = (process.env.AP_EXECUTION_MODE as ExecutionMode) + +const loadNoOpCodeSandbox = async (): Promise => { + const noOpCodeSandboxModule = await import('./no-op-code-sandbox') + return noOpCodeSandboxModule.noOpCodeSandbox +} + +const loadV8IsolateSandbox = async (): Promise => { + const v8IsolateCodeSandboxModule = await import('./v8-isolate-code-sandbox') + return v8IsolateCodeSandboxModule.v8IsolateCodeSandbox +} + +const loadCodeSandbox = async (): Promise => { + const loaders = { + [ExecutionMode.UNSANDBOXED]: loadNoOpCodeSandbox, + [ExecutionMode.SANDBOXED]: loadNoOpCodeSandbox, + [ExecutionMode.SANDBOX_CODE_ONLY]: loadV8IsolateSandbox, + } + assertNotNullOrUndefined(EXECUTION_MODE, 'AP_EXECUTION_MODE') + const loader = loaders[EXECUTION_MODE] + return loader() +} + +let instance: CodeSandbox | null = null + +export const initCodeSandbox = async (): Promise => { + if (instance === null) { + instance = await loadCodeSandbox() + } + + return instance +} diff --git a/packages/engine/src/lib/core/code/no-op-code-sandbox.ts b/packages/engine/src/lib/core/code/no-op-code-sandbox.ts new file mode 100644 index 0000000..ee159a0 --- /dev/null +++ b/packages/engine/src/lib/core/code/no-op-code-sandbox.ts @@ -0,0 +1,18 @@ +import { CodeSandbox } from '../../core/code/code-sandbox-common' + +/** + * Runs code without a sandbox. + */ +export const noOpCodeSandbox: CodeSandbox = { + async runCodeModule({ codeModule, inputs }) { + return codeModule.code(inputs) + }, + + async runScript({ script, scriptContext }) { + const params = Object.keys(scriptContext) + const args = Object.values(scriptContext) + const body = `return (${script})` + const fn = Function(...params, body) + return fn(...args) + }, +} diff --git a/packages/engine/src/lib/core/code/v8-isolate-code-sandbox.ts b/packages/engine/src/lib/core/code/v8-isolate-code-sandbox.ts new file mode 100644 index 0000000..39f7bc1 --- /dev/null +++ b/packages/engine/src/lib/core/code/v8-isolate-code-sandbox.ts @@ -0,0 +1,108 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { CodeModule, CodeSandbox } from '../../core/code/code-sandbox-common' + +const ONE_HUNDRED_TWENTY_EIGHT_MEGABYTES = 128 + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +// Check this https://github.com/laverdet/isolated-vm/issues/258#issuecomment-2134341086 +let ivmCache: any +const getIvm = () => { + if (!ivmCache) { + ivmCache = require('isolated-vm') + } + return ivmCache as typeof import('isolated-vm') +} + +/** + * Runs code in a V8 Isolate sandbox + */ +export const v8IsolateCodeSandbox: CodeSandbox = { + async runCodeModule({ codeModule, inputs }) { + const ivm = getIvm() + const isolate = new ivm.Isolate({ memoryLimit: ONE_HUNDRED_TWENTY_EIGHT_MEGABYTES }) + + try { + const isolateContext = await initIsolateContext({ + isolate, + codeContext: { + inputs, + }, + }) + + const serializedCodeModule = serializeCodeModule(codeModule) + + return await executeIsolate({ + isolate, + isolateContext, + code: serializedCodeModule, + }) + } + finally { + isolate.dispose() + } + }, + + async runScript({ script, scriptContext }) { + const ivm = getIvm() + const isolate = new ivm.Isolate({ memoryLimit: ONE_HUNDRED_TWENTY_EIGHT_MEGABYTES }) + + try { + // It is to avoid strucutedClone issue of proxy objects / functions, It will throw cannot be cloned error. + const isolateContext = await initIsolateContext({ + isolate, + codeContext: JSON.parse(JSON.stringify(scriptContext)), + }) + + return await executeIsolate({ + isolate, + isolateContext, + code: script, + }) + } + finally { + isolate.dispose() + } + }, +} + +const initIsolateContext = async ({ isolate, codeContext }: InitContextParams): Promise => { + const isolateContext = await isolate.createContext() + const ivm = getIvm() + for (const [key, value] of Object.entries(codeContext)) { + await isolateContext.global.set(key, new ivm.ExternalCopy(value).copyInto()) + } + + return isolateContext +} + +const executeIsolate = async ({ isolate, isolateContext, code }: ExecuteIsolateParams): Promise => { + const isolateScript = await isolate.compileScript(code) + + const outRef = await isolateScript.run(isolateContext, { + reference: true, + promise: true, + }) + + return outRef.copy() +} + +const serializeCodeModule = (codeModule: CodeModule): string => { + const serializedCodeFunction = Object.keys(codeModule) + .reduce((acc, key) => + acc + `const ${key} = ${(codeModule as any)[key].toString()};`, + '') + + // replace the exports.function_name with function_name + return serializedCodeFunction.replace(/\(0, exports\.(\w+)\)/g, '$1') + 'code(inputs);' +} + +type InitContextParams = { + isolate: any + codeContext: Record +} + +type ExecuteIsolateParams = { + isolate: any + isolateContext: unknown + code: string +} diff --git a/packages/engine/src/lib/handler/base-executor.ts b/packages/engine/src/lib/handler/base-executor.ts new file mode 100644 index 0000000..58ad571 --- /dev/null +++ b/packages/engine/src/lib/handler/base-executor.ts @@ -0,0 +1,13 @@ +import { Action } from '@activepieces/shared' +import { EngineConstants } from './context/engine-constants' +import { FlowExecutorContext } from './context/flow-execution-context' + +export type ActionHandler = (request: { action: T, executionState: FlowExecutorContext, constants: EngineConstants }) => Promise + +export type BaseExecutor = { + handle(request: { + action: T + executionState: FlowExecutorContext + constants: EngineConstants + }): Promise +} diff --git a/packages/engine/src/lib/handler/code-executor.ts b/packages/engine/src/lib/handler/code-executor.ts new file mode 100644 index 0000000..76df17b --- /dev/null +++ b/packages/engine/src/lib/handler/code-executor.ts @@ -0,0 +1,60 @@ +import path from 'path' +import importFresh from '@activepieces/import-fresh-webpack' +import { ActionType, assertNotNullOrUndefined, CodeAction, GenericStepOutput, StepOutputStatus } from '@activepieces/shared' +import { initCodeSandbox } from '../core/code/code-sandbox' +import { CodeModule } from '../core/code/code-sandbox-common' +import { continueIfFailureHandler, handleExecutionError, runWithExponentialBackoff } from '../helper/error-handling' +import { ActionHandler, BaseExecutor } from './base-executor' +import { ExecutionVerdict } from './context/flow-execution-context' + +export const codeExecutor: BaseExecutor = { + async handle({ + action, + executionState, + constants, + }) { + if (executionState.isCompleted({ stepName: action.name })) { + return executionState + } + const resultExecution = await runWithExponentialBackoff(executionState, action, constants, executeAction) + return continueIfFailureHandler(resultExecution, action, constants) + }, +} + +const executeAction: ActionHandler = async ({ action, executionState, constants }) => { + const { censoredInput, resolvedInput } = await constants.propsResolver.resolve>({ + unresolvedInput: action.settings.input, + executionState, + }) + + const stepOutput = GenericStepOutput.create({ + input: censoredInput, + type: ActionType.CODE, + status: StepOutputStatus.SUCCEEDED, + }) + + try { + assertNotNullOrUndefined(constants.runEnvironment, 'Run environment is required') + const artifactPath = path.resolve(`${constants.baseCodeDirectory}/${constants.flowVersionId}/${action.name}/index.js`) + const codeModule: CodeModule = await importFresh(artifactPath) + const codeSandbox = await initCodeSandbox() + + const output = await codeSandbox.runCodeModule({ + codeModule, + inputs: resolvedInput, + }) + + return executionState.upsertStep(action.name, stepOutput.setOutput(output)).increaseTask() + } + catch (e) { + const handledError = handleExecutionError(e) + + const failedStepOutput = stepOutput + .setStatus(StepOutputStatus.FAILED) + .setErrorMessage(handledError.message) + + return executionState + .upsertStep(action.name, failedStepOutput) + .setVerdict(ExecutionVerdict.FAILED, handledError.verdictResponse) + } +} diff --git a/packages/engine/src/lib/handler/context/engine-constants.ts b/packages/engine/src/lib/handler/context/engine-constants.ts new file mode 100644 index 0000000..6572b7f --- /dev/null +++ b/packages/engine/src/lib/handler/context/engine-constants.ts @@ -0,0 +1,211 @@ +import { DEFAULT_MCP_DATA, ExecuteFlowOperation, ExecutePropsOptions, ExecuteStepOperation, ExecuteToolOperation, ExecuteTriggerOperation, ExecutionType, FlowVersionState, ProgressUpdateType, Project, ProjectId, ResumePayload, RunEnvironment, TriggerHookType } from '@activepieces/shared' +import { createPropsResolver, PropsResolver } from '../../variables/props-resolver' + +type RetryConstants = { + maxAttempts: number + retryExponential: number + retryInterval: number +} + +const DEFAULT_RETRY_CONSTANTS: RetryConstants = { + maxAttempts: 4, + retryExponential: 2, + retryInterval: 2000, +} + +const DEFAULT_TRIGGER_EXECUTION = 'execute-trigger' +const DEFAULT_EXECUTE_PROPERTY = 'execute-property' + +export class EngineConstants { + public static readonly BASE_CODE_DIRECTORY = process.env.AP_BASE_CODE_DIRECTORY ?? './codes' + public static readonly INPUT_FILE = './input.json' + public static readonly OUTPUT_FILE = './output.json' + public static readonly PIECE_SOURCES = process.env.AP_PIECES_SOURCE ?? 'FILE' + public static readonly TEST_MODE = process.env.AP_TEST_MODE === 'true' + + + private project: Project | null = null + + public get isRunningApTests(): boolean { + return EngineConstants.TEST_MODE + } + + public get baseCodeDirectory(): string { + return EngineConstants.BASE_CODE_DIRECTORY + } + + public get piecesSource(): string { + return EngineConstants.PIECE_SOURCES + } + + public constructor( + public readonly flowId: string, + public readonly flowVersionId: string, + public readonly flowVersionState: FlowVersionState, + public readonly flowRunId: string, + public readonly publicApiUrl: string, + public readonly internalApiUrl: string, + public readonly retryConstants: RetryConstants, + public readonly engineToken: string, + public readonly projectId: ProjectId, + public readonly propsResolver: PropsResolver, + public readonly testSingleStepMode: boolean, + public readonly progressUpdateType: ProgressUpdateType, + public readonly serverHandlerId: string | null, + public readonly httpRequestId: string | null, + public readonly resumePayload?: ResumePayload, + public readonly runEnvironment?: RunEnvironment, + ) { + if (!publicApiUrl.endsWith('/api/')) { + throw new Error('Public URL must end with a slash, got: ' + publicApiUrl) + } + if (!internalApiUrl.endsWith('/')) { + throw new Error('Internal API URL must end with a slash, got: ' + internalApiUrl) + } + } + + public static fromExecuteFlowInput(input: ExecuteFlowOperation): EngineConstants { + return new EngineConstants( + input.flowVersion.flowId, + input.flowVersion.id, + input.flowVersion.state, + input.flowRunId, + input.publicApiUrl, + input.internalApiUrl, + DEFAULT_RETRY_CONSTANTS, + input.engineToken, + input.projectId, + createPropsResolver({ + projectId: input.projectId, + engineToken: input.engineToken, + apiUrl: input.internalApiUrl, + }), + false, + input.progressUpdateType, + input.serverHandlerId ?? null, + input.httpRequestId ?? null, + input.executionType === ExecutionType.RESUME ? input.resumePayload : undefined, + input.runEnvironment, + ) + } + + public static fromExecuteActionInput(input: ExecuteToolOperation): EngineConstants { + return new EngineConstants( + DEFAULT_MCP_DATA.flowId, + DEFAULT_MCP_DATA.flowVersionId, + DEFAULT_MCP_DATA.flowVersionState, + DEFAULT_MCP_DATA.flowRunId, + input.publicApiUrl, + addTrailingSlashIfMissing(input.internalApiUrl), + DEFAULT_RETRY_CONSTANTS, + input.engineToken, + input.projectId, + createPropsResolver({ + projectId: input.projectId, + engineToken: input.engineToken, + apiUrl: addTrailingSlashIfMissing(input.internalApiUrl), + }), + true, + ProgressUpdateType.NONE, + null, + null, + ) + } + public static fromExecuteStepInput(input: ExecuteStepOperation): EngineConstants { + return new EngineConstants( + input.flowVersion.flowId, + input.flowVersion.id, + input.flowVersion.state, + 'test-run', + input.publicApiUrl, + addTrailingSlashIfMissing(input.internalApiUrl), + DEFAULT_RETRY_CONSTANTS, + input.engineToken, + input.projectId, + createPropsResolver({ + projectId: input.projectId, + engineToken: input.engineToken, + apiUrl: addTrailingSlashIfMissing(input.internalApiUrl), + }), + true, + ProgressUpdateType.NONE, + null, + input.requestId ?? null, + undefined, + input.runEnvironment, + ) + } + + public static fromExecutePropertyInput(input: ExecutePropsOptions): EngineConstants { + return new EngineConstants( + input.flowVersion?.flowId ?? DEFAULT_MCP_DATA.flowId, + input.flowVersion?.id ?? DEFAULT_MCP_DATA.flowVersionId, + input.flowVersion?.state ?? DEFAULT_MCP_DATA.flowVersionState, + DEFAULT_EXECUTE_PROPERTY, + input.publicApiUrl, + addTrailingSlashIfMissing(input.internalApiUrl), + DEFAULT_RETRY_CONSTANTS, + input.engineToken, + input.projectId, + createPropsResolver({ + projectId: input.projectId, + engineToken: input.engineToken, + apiUrl: addTrailingSlashIfMissing(input.internalApiUrl), + }), + true, + ProgressUpdateType.NONE, + null, + null, + ) + } + + public static fromExecuteTriggerInput(input: ExecuteTriggerOperation): EngineConstants { + return new EngineConstants( + input.flowVersion.flowId, + input.flowVersion.id, + input.flowVersion.state, + DEFAULT_TRIGGER_EXECUTION, + input.publicApiUrl, + addTrailingSlashIfMissing(input.internalApiUrl), + DEFAULT_RETRY_CONSTANTS, + input.engineToken, + input.projectId, + createPropsResolver({ + projectId: input.projectId, + engineToken: input.engineToken, + apiUrl: addTrailingSlashIfMissing(input.internalApiUrl), + }), + true, + ProgressUpdateType.NONE, + null, + null, + ) + } + + private async getProject(): Promise { + if (this.project) { + return this.project + } + + const getWorkerProjectEndpoint = `${this.internalApiUrl}v1/worker/project` + + const response = await fetch(getWorkerProjectEndpoint, { + headers: { + Authorization: `Bearer ${this.engineToken}`, + }, + }) + + this.project = await response.json() as Project + return this.project + } + + public externalProjectId = async (): Promise => { + const project = await this.getProject() + return project.externalId + } +} + + +const addTrailingSlashIfMissing = (url: string): string => { + return url.endsWith('/') ? url : url + '/' +} \ No newline at end of file diff --git a/packages/engine/src/lib/handler/context/flow-execution-context.ts b/packages/engine/src/lib/handler/context/flow-execution-context.ts new file mode 100644 index 0000000..80db4a6 --- /dev/null +++ b/packages/engine/src/lib/handler/context/flow-execution-context.ts @@ -0,0 +1,284 @@ +import { ActionType, assertEqual, FlowError, FlowRunResponse, FlowRunStatus, GenericStepOutput, isNil, LoopStepOutput, LoopStepResult, PauseMetadata, RespondResponse, spreadIfDefined, StepOutput, StepOutputStatus } from '@activepieces/shared' +import { nanoid } from 'nanoid' +import { loggingUtils } from '../../helper/logging-utils' +import { StepExecutionPath } from './step-execution-path' + +export enum ExecutionVerdict { + RUNNING = 'RUNNING', + PAUSED = 'PAUSED', + SUCCEEDED = 'SUCCEEDED', + FAILED = 'FAILED', +} + +export type VerdictResponse = { + reason: FlowRunStatus.PAUSED + pauseMetadata: PauseMetadata +} | { + reason: FlowRunStatus.STOPPED + stopResponse: RespondResponse +} | { + reason: FlowRunStatus.INTERNAL_ERROR +} + +export class FlowExecutorContext { + tasks: number + tags: readonly string[] + steps: Readonly> + pauseRequestId: string + verdict: ExecutionVerdict + verdictResponse: VerdictResponse | undefined + currentPath: StepExecutionPath + error?: FlowError + + /** + * Execution time in milliseconds + */ + duration: number + + constructor(copyFrom?: FlowExecutorContext) { + this.tasks = copyFrom?.tasks ?? 0 + this.tags = copyFrom?.tags ?? [] + this.steps = copyFrom?.steps ?? {} + this.pauseRequestId = copyFrom?.pauseRequestId ?? nanoid() + this.duration = copyFrom?.duration ?? -1 + this.verdict = copyFrom?.verdict ?? ExecutionVerdict.RUNNING + this.verdictResponse = copyFrom?.verdictResponse ?? undefined + this.error = copyFrom?.error ?? undefined + this.currentPath = copyFrom?.currentPath ?? StepExecutionPath.empty() + } + + static empty(): FlowExecutorContext { + return new FlowExecutorContext() + } + + public setPauseRequestId(pauseRequestId: string): FlowExecutorContext { + return new FlowExecutorContext({ + ...this, + pauseRequestId, + }) + } + + public getLoopStepOutput({ stepName }: { stepName: string }): LoopStepOutput | undefined { + const stateAtPath = getStateAtPath({ currentPath: this.currentPath, steps: this.steps }) + const stepOutput = stateAtPath[stepName] + if (isNil(stepOutput)) { + return undefined + } + assertEqual(stepOutput.type, ActionType.LOOP_ON_ITEMS, 'stepOutput.type', 'LOOP_ON_ITEMS') + // The new LoopStepOutput is needed as casting directly to LoopClassOutput will just cast the data but the class methods will not be available + return new LoopStepOutput(stepOutput as GenericStepOutput) + } + + public isCompleted({ stepName }: { stepName: string }): boolean { + const stateAtPath = getStateAtPath({ currentPath: this.currentPath, steps: this.steps }) + const stepOutput = stateAtPath[stepName] + if (isNil(stepOutput)) { + return false + } + return stepOutput.status !== StepOutputStatus.PAUSED + } + + public isPaused({ stepName }: { stepName: string }): boolean { + const stateAtPath = getStateAtPath({ currentPath: this.currentPath, steps: this.steps }) + const stepOutput = stateAtPath[stepName] + if (isNil(stepOutput)) { + return false + } + return stepOutput.status === StepOutputStatus.PAUSED + } + + public setDuration(duration: number): FlowExecutorContext { + return new FlowExecutorContext({ + ...this, + duration, + }) + } + + + public addTags(tags: string[]): FlowExecutorContext { + return new FlowExecutorContext({ + ...this, + tags: [...this.tags, ...tags].filter((value, index, self) => { + return self.indexOf(value) === index + }), + }) + } + + public increaseTask(tasks = 1): FlowExecutorContext { + return new FlowExecutorContext({ + ...this, + tasks: this.tasks + tasks, + }) + } + + public upsertStep(stepName: string, stepOutput: StepOutput): FlowExecutorContext { + const steps = { + ...this.steps, + } + const targetMap = getStateAtPath({ currentPath: this.currentPath, steps }) + targetMap[stepName] = stepOutput + + const error = stepOutput.status === StepOutputStatus.FAILED ? { + stepName, + message: stepOutput.errorMessage, + } : this.error + + return new FlowExecutorContext({ + ...this, + tasks: this.tasks, + ...spreadIfDefined('error', error), + steps, + }) + } + + public getStepOutput(stepName: string): StepOutput | undefined { + const stateAtPath = getStateAtPath({ currentPath: this.currentPath, steps: this.steps }) + return stateAtPath[stepName] + } + + public setStepDuration({ stepName, duration }: SetStepDurationParams): FlowExecutorContext { + const steps = { + ...this.steps, + } + + const targetMap = getStateAtPath({ + steps, + currentPath: this.currentPath, + }) + + const stepOutput = targetMap[stepName] + + if (isNil(stepOutput)) { + console.error(`[FlowExecutorContext#setStepDuration] Step ${stepName} not found in current path`) + return this + } + + targetMap[stepName].duration = duration + + return new FlowExecutorContext({ + ...this, + steps, + }) + } + + public setCurrentPath(currentStatePath: StepExecutionPath): FlowExecutorContext { + return new FlowExecutorContext({ + ...this, + currentPath: currentStatePath, + }) + } + + public setVerdict(verdict: ExecutionVerdict, response?: VerdictResponse): FlowExecutorContext { + return new FlowExecutorContext({ + ...this, + verdict, + verdictResponse: response, + }) + } + + public setRetryable(retryable: boolean): FlowExecutorContext { + return new FlowExecutorContext({ + ...this, + retryable, + }) + } + + public async toResponse(): Promise { + const baseExecutionOutput = { + duration: this.duration, + tasks: this.tasks, + tags: [...this.tags], + steps: await loggingUtils.trimExecution(this.steps), + } + switch (this.verdict) { + case ExecutionVerdict.FAILED: { + const verdictResponse = this.verdictResponse + if (verdictResponse?.reason === FlowRunStatus.INTERNAL_ERROR) { + return { + ...baseExecutionOutput, + error: this.error, + status: FlowRunStatus.INTERNAL_ERROR, + } + } + return { + ...baseExecutionOutput, + error: this.error, + status: FlowRunStatus.FAILED, + } + } + case ExecutionVerdict.PAUSED: { + const verdictResponse = this.verdictResponse + if (verdictResponse?.reason !== FlowRunStatus.PAUSED) { + throw new Error('Verdict Response should have pause metadata response') + } + return { + ...baseExecutionOutput, + status: FlowRunStatus.PAUSED, + pauseMetadata: verdictResponse.pauseMetadata, + } + } + case ExecutionVerdict.RUNNING: { + return { + ...baseExecutionOutput, + status: FlowRunStatus.RUNNING, + } + } + case ExecutionVerdict.SUCCEEDED: { + const verdictResponse = this.verdictResponse + if (verdictResponse?.reason === FlowRunStatus.STOPPED) { + return { + ...baseExecutionOutput, + status: FlowRunStatus.STOPPED, + response: verdictResponse.stopResponse, + } + } + return { + ...baseExecutionOutput, + status: FlowRunStatus.SUCCEEDED, + } + } + } + } + public currentState(): Record { + let flattenedSteps: Record = extractOutput(this.steps) + let targetMap = this.steps + this.currentPath.path.forEach(([stepName, iteration]) => { + const stepOutput = targetMap[stepName] + if (!stepOutput.output || stepOutput.type !== ActionType.LOOP_ON_ITEMS) { + throw new Error('[ExecutionState#getTargetMap] Not instance of Loop On Items step output') + } + targetMap = stepOutput.output.iterations[iteration] + flattenedSteps = { + ...flattenedSteps, + ...extractOutput(targetMap), + } + }) + return flattenedSteps + } + + +} + +function extractOutput(steps: Record): Record { + return Object.entries(steps).reduce((acc: Record, [stepName, step]) => { + acc[stepName] = step.output + return acc + }, {} as Record) +} + +function getStateAtPath({ currentPath, steps }: { currentPath: StepExecutionPath, steps: Record }): Record { + let targetMap = steps + currentPath.path.forEach(([stepName, iteration]) => { + const stepOutput = targetMap[stepName] + if (!stepOutput.output || stepOutput.type !== ActionType.LOOP_ON_ITEMS) { + throw new Error('[ExecutionState#getTargetMap] Not instance of Loop On Items step output') + } + targetMap = stepOutput.output.iterations[iteration] + }) + return targetMap +} + +type SetStepDurationParams = { + stepName: string + duration: number +} diff --git a/packages/engine/src/lib/handler/context/step-execution-path.ts b/packages/engine/src/lib/handler/context/step-execution-path.ts new file mode 100644 index 0000000..956e48b --- /dev/null +++ b/packages/engine/src/lib/handler/context/step-execution-path.ts @@ -0,0 +1,20 @@ +export class StepExecutionPath { + public path: readonly [string, number][] = [] + + constructor(path: readonly [string, number][]) { + this.path = [...path] + } + + loopIteration({ loopName, iteration }: { loopName: string, iteration: number }): StepExecutionPath { + return new StepExecutionPath([...this.path, [loopName, iteration]]) + } + + static empty(): StepExecutionPath { + return new StepExecutionPath([]) + } + + removeLast(): StepExecutionPath { + const newPath = this.path.slice(0, -1) + return new StepExecutionPath(newPath) + } +} diff --git a/packages/engine/src/lib/handler/context/test-execution-context.ts b/packages/engine/src/lib/handler/context/test-execution-context.ts new file mode 100644 index 0000000..40e0023 --- /dev/null +++ b/packages/engine/src/lib/handler/context/test-execution-context.ts @@ -0,0 +1,97 @@ +import { + ActionType, + flowStructureUtil, + FlowVersion, + GenericStepOutput, + isNil, + LoopStepOutput, + RouterStepOutput, + spreadIfDefined, + StepOutputStatus, + TriggerType, +} from '@activepieces/shared' +import { createPropsResolver } from '../../variables/props-resolver' +import { FlowExecutorContext } from './flow-execution-context' + +export const testExecutionContext = { + async stateFromFlowVersion({ + flowVersion, + excludedStepName, + projectId, + engineToken, + apiUrl, + sampleData, + }: TestExecutionParams): Promise { + let flowExecutionContext = FlowExecutorContext.empty() + if (isNil(flowVersion)) { + return flowExecutionContext + } + + const flowSteps = flowStructureUtil.getAllSteps(flowVersion.trigger) + + for (const step of flowSteps) { + const { name } = step + if (name === excludedStepName) { + continue + } + + const stepType = step.type + switch (stepType) { + case ActionType.ROUTER: + flowExecutionContext = flowExecutionContext.upsertStep( + step.name, + RouterStepOutput.create({ + input: step.settings, + type: stepType, + status: StepOutputStatus.SUCCEEDED, + ...spreadIfDefined('output', sampleData?.[step.name]), + }), + ) + break + case ActionType.LOOP_ON_ITEMS: { + const { resolvedInput } = await createPropsResolver({ + apiUrl, + projectId, + engineToken, + }).resolve<{ items: unknown[] }>({ + unresolvedInput: step.settings, + executionState: flowExecutionContext, + }) + flowExecutionContext = flowExecutionContext.upsertStep( + step.name, + LoopStepOutput.init({ + input: step.settings, + }).setOutput({ + item: resolvedInput.items[0], + index: 1, + iterations: [], + }), + ) + break + } + case ActionType.PIECE: + case ActionType.CODE: + case TriggerType.EMPTY: + case TriggerType.PIECE: + flowExecutionContext = flowExecutionContext.upsertStep(step.name, GenericStepOutput.create({ + input: step.settings, + type: stepType, + status: StepOutputStatus.SUCCEEDED, + ...spreadIfDefined('output', sampleData?.[step.name]), + })) + break + } + } + return flowExecutionContext + }, +} + + +type TestExecutionParams = { + flowVersion?: FlowVersion + excludedStepName?: string + projectId: string + apiUrl: string + engineToken: string + sampleData?: Record +} \ No newline at end of file diff --git a/packages/engine/src/lib/handler/flow-executor.ts b/packages/engine/src/lib/handler/flow-executor.ts new file mode 100644 index 0000000..af3d4ef --- /dev/null +++ b/packages/engine/src/lib/handler/flow-executor.ts @@ -0,0 +1,88 @@ +import { performance } from 'node:perf_hooks' +import { Action, ActionType, ExecuteFlowOperation, ExecutionType, isNil } from '@activepieces/shared' +import { triggerHelper } from '../helper/trigger-helper' +import { progressService } from '../services/progress.service' +import { BaseExecutor } from './base-executor' +import { codeExecutor } from './code-executor' +import { EngineConstants } from './context/engine-constants' +import { ExecutionVerdict, FlowExecutorContext } from './context/flow-execution-context' +import { loopExecutor } from './loop-executor' +import { pieceExecutor } from './piece-executor' +import { routerExecuter } from './router-executor' + +const executeFunction: Record> = { + [ActionType.CODE]: codeExecutor, + [ActionType.LOOP_ON_ITEMS]: loopExecutor, + [ActionType.PIECE]: pieceExecutor, + [ActionType.ROUTER]: routerExecuter, +} + +export const flowExecutor = { + getExecutorForAction(type: ActionType): BaseExecutor { + const executor = executeFunction[type] + if (isNil(executor)) { + throw new Error('Not implemented') + } + return executor + }, + async executeFromTrigger({ executionState, constants, input }: { + executionState: FlowExecutorContext + constants: EngineConstants + input: ExecuteFlowOperation + }): Promise { + const trigger = input.flowVersion.trigger + if (input.executionType === ExecutionType.BEGIN) { + await triggerHelper.executeOnStart(trigger, constants, input.triggerPayload) + } + return flowExecutor.execute({ + action: trigger.nextAction, + executionState, + constants, + }) + }, + async execute({ action, constants, executionState }: { + action: Action | null | undefined + executionState: FlowExecutorContext + constants: EngineConstants + }): Promise { + const flowStartTime = performance.now() + let flowExecutionContext = executionState + let currentAction: Action | null | undefined = action + + while (!isNil(currentAction)) { + if (currentAction.skip) { + currentAction = currentAction.nextAction + continue + } + const handler = this.getExecutorForAction(currentAction.type) + + const stepStartTime = performance.now() + progressService.sendUpdate({ + engineConstants: constants, + flowExecutorContext: flowExecutionContext, + }).catch(error => { + console.error('Error sending update:', error) + }) + + flowExecutionContext = await handler.handle({ + action: currentAction, + executionState: flowExecutionContext, + constants, + }) + const stepEndTime = performance.now() + flowExecutionContext = flowExecutionContext.setStepDuration({ + stepName: currentAction.name, + duration: stepEndTime - stepStartTime, + }) + + if (flowExecutionContext.verdict !== ExecutionVerdict.RUNNING) { + break + } + + currentAction = currentAction.nextAction + } + + const flowEndTime = performance.now() + return flowExecutionContext.setDuration(flowEndTime - flowStartTime) + }, +} diff --git a/packages/engine/src/lib/handler/loop-executor.ts b/packages/engine/src/lib/handler/loop-executor.ts new file mode 100644 index 0000000..c768264 --- /dev/null +++ b/packages/engine/src/lib/handler/loop-executor.ts @@ -0,0 +1,69 @@ +import { isNil, LoopOnItemsAction, LoopStepOutput, StepOutputStatus } from '@activepieces/shared' +import { BaseExecutor } from './base-executor' +import { ExecutionVerdict } from './context/flow-execution-context' +import { flowExecutor } from './flow-executor' + +type LoopOnActionResolvedSettings = { + items: readonly unknown[] +} + +export const loopExecutor: BaseExecutor = { + async handle({ + action, + executionState, + constants, + }) { + const { resolvedInput, censoredInput } = await constants.propsResolver.resolve({ + unresolvedInput: { + items: action.settings.items, + }, + executionState, + }) + const previousStepOutput = executionState.getLoopStepOutput({ stepName: action.name }) + let stepOutput = previousStepOutput ?? LoopStepOutput.init({ + input: censoredInput, + }) + let newExecutionContext = executionState.upsertStep(action.name, stepOutput) + + if (!Array.isArray(resolvedInput.items)) { + const failedStepOutput = stepOutput + .setStatus(StepOutputStatus.FAILED) + .setErrorMessage(JSON.stringify({ + message: 'The items you have selected must be a list.', + })) + return newExecutionContext.upsertStep(action.name, failedStepOutput).setVerdict(ExecutionVerdict.FAILED) + } + + const firstLoopAction = action.firstLoopAction + + + for (let i = 0; i < resolvedInput.items.length; ++i) { + const newCurrentPath = newExecutionContext.currentPath.loopIteration({ loopName: action.name, iteration: i }) + + stepOutput = stepOutput.setItemAndIndex({ item: resolvedInput.items[i], index: i + 1 }) + const addEmptyIteration = !stepOutput.hasIteration(i) + if (addEmptyIteration) { + stepOutput = stepOutput.addIteration() + } + newExecutionContext = newExecutionContext.upsertStep(action.name, stepOutput).setCurrentPath(newCurrentPath) + if (!isNil(firstLoopAction) && !constants.testSingleStepMode) { + newExecutionContext = await flowExecutor.execute({ + action: firstLoopAction, + executionState: newExecutionContext, + constants, + }) + } + + newExecutionContext = newExecutionContext.setCurrentPath(newExecutionContext.currentPath.removeLast()) + + if (newExecutionContext.verdict !== ExecutionVerdict.RUNNING) { + return newExecutionContext + } + + if (constants.testSingleStepMode) { + break + } + } + return newExecutionContext + }, +} diff --git a/packages/engine/src/lib/handler/piece-executor.ts b/packages/engine/src/lib/handler/piece-executor.ts new file mode 100644 index 0000000..7ddf795 --- /dev/null +++ b/packages/engine/src/lib/handler/piece-executor.ts @@ -0,0 +1,282 @@ +import { URL } from 'url' +import { ActionContext, InputPropertyMap, PauseHook, PauseHookParams, PiecePropertyMap, RespondHook, RespondHookParams, StaticPropsValue, StopHook, StopHookParams, TagsManager } from '@activepieces/pieces-framework' +import { ActionType, assertNotNullOrUndefined, AUTHENTICATION_PROPERTY_NAME, ExecutionType, FlowRunStatus, GenericStepOutput, isNil, PauseType, PieceAction, RespondResponse, StepOutputStatus } from '@activepieces/shared' +import dayjs from 'dayjs' +import { continueIfFailureHandler, handleExecutionError, runWithExponentialBackoff } from '../helper/error-handling' +import { PausedFlowTimeoutError } from '../helper/execution-errors' +import { pieceLoader } from '../helper/piece-loader' +import { createFlowsContext } from '../services/flows.service' +import { progressService } from '../services/progress.service' +import { createFilesService } from '../services/step-files.service' +import { createContextStore } from '../services/storage.service' +import { HookResponse, utils } from '../utils' +import { propsProcessor } from '../variables/props-processor' +import { ActionHandler, BaseExecutor } from './base-executor' +import { ExecutionVerdict } from './context/flow-execution-context' + + + + +const AP_PAUSED_FLOW_TIMEOUT_DAYS = Number(process.env.AP_PAUSED_FLOW_TIMEOUT_DAYS) + +export const pieceExecutor: BaseExecutor = { + async handle({ + action, + executionState, + constants, + }) { + if (executionState.isCompleted({ stepName: action.name })) { + return executionState + } + const resultExecution = await runWithExponentialBackoff(executionState, action, constants, executeAction) + return continueIfFailureHandler(resultExecution, action, constants) + }, +} + +const executeAction: ActionHandler = async ({ action, executionState, constants }) => { + const stepOutput = GenericStepOutput.create({ + input: {}, + type: ActionType.PIECE, + status: StepOutputStatus.SUCCEEDED, + }) + + try { + assertNotNullOrUndefined(action.settings.actionName, 'actionName') + const { pieceAction, piece } = await pieceLoader.getPieceAndActionOrThrow({ + pieceName: action.settings.pieceName, + pieceVersion: action.settings.pieceVersion, + actionName: action.settings.actionName, + piecesSource: constants.piecesSource, + }) + + const { resolvedInput, censoredInput } = await constants.propsResolver.resolve>({ + unresolvedInput: action.settings.input, + executionState, + }) + + stepOutput.input = censoredInput + + const { processedInput, errors } = await propsProcessor.applyProcessorsAndValidators(resolvedInput, pieceAction.props, piece.auth, pieceAction.requireAuth, action.settings.inputUiInfo?.schema as Record | undefined) + if (Object.keys(errors).length > 0) { + throw new Error(JSON.stringify(errors, null, 2)) + } + + const params: { + hookResponse: HookResponse + } = { + hookResponse: { + type: 'none', + tags: [], + }, + } + const isPaused = executionState.isPaused({ stepName: action.name }) + const context: ActionContext = { + executionType: isPaused ? ExecutionType.RESUME : ExecutionType.BEGIN, + resumePayload: constants.resumePayload!, + store: createContextStore({ + apiUrl: constants.internalApiUrl, + prefix: '', + flowId: constants.flowId, + engineToken: constants.engineToken, + }), + output: progressService.createOutputContext({ + engineConstants: constants, + flowExecutorContext: executionState, + stepName: action.name, + stepOutput, + }), + flows: createFlowsContext({ + engineToken: constants.engineToken, + internalApiUrl: constants.internalApiUrl, + flowId: constants.flowId, + flowVersionId: constants.flowVersionId, + }), + auth: processedInput[AUTHENTICATION_PROPERTY_NAME], + files: createFilesService({ + apiUrl: constants.internalApiUrl, + engineToken: constants.engineToken, + stepName: action.name, + flowId: constants.flowId, + }), + server: { + token: constants.engineToken, + apiUrl: constants.internalApiUrl, + publicUrl: constants.publicApiUrl, + }, + propsValue: processedInput, + tags: createTagsManager(params), + connections: utils.createConnectionManager({ + apiUrl: constants.internalApiUrl, + projectId: constants.projectId, + engineToken: constants.engineToken, + target: 'actions', + hookResponse: params.hookResponse, + }), + /* + @deprecated Use server.publicApiUrl instead. + */ + serverUrl: constants.publicApiUrl, + run: { + id: constants.flowRunId, + stop: createStopHook(params), + pause: createPauseHook(params, executionState.pauseRequestId), + respond: createRespondHook(params), + }, + project: { + id: constants.projectId, + externalId: constants.externalProjectId, + }, + generateResumeUrl: (params) => { + const url = new URL(`${constants.publicApiUrl}v1/flow-runs/${constants.flowRunId}/requests/${executionState.pauseRequestId}${params.sync ? '/sync' : ''}`) + url.search = new URLSearchParams(params.queryParams).toString() + return url.toString() + }, + } + const runMethodToExecute = (constants.testSingleStepMode && !isNil(pieceAction.test)) ? pieceAction.test : pieceAction.run + const output = await runMethodToExecute(context) + const newExecutionContext = executionState.addTags(params.hookResponse.tags) + + const webhookResponse = getResponse(params.hookResponse) + if (!isNil(webhookResponse) && !isNil(constants.serverHandlerId) && !isNil(constants.httpRequestId)) { + await progressService.sendFlowResponse(constants, { + workerHandlerId: constants.serverHandlerId, + httpRequestId: constants.httpRequestId, + runResponse: { + status: webhookResponse.status ?? 200, + body: webhookResponse.body, + headers: webhookResponse.headers ?? {}, + }, + }) + } + + if (params.hookResponse.type === 'stopped') { + assertNotNullOrUndefined(params.hookResponse.response, 'stopResponse') + return newExecutionContext.upsertStep(action.name, stepOutput.setOutput(output)).setVerdict(ExecutionVerdict.SUCCEEDED, { + reason: FlowRunStatus.STOPPED, + stopResponse: (params.hookResponse.response as StopHookParams).response, + }).increaseTask() + } + if (params.hookResponse.type === 'paused') { + assertNotNullOrUndefined(params.hookResponse.response, 'pauseResponse') + return newExecutionContext.upsertStep(action.name, stepOutput.setOutput(output).setStatus(StepOutputStatus.PAUSED)) + .setVerdict(ExecutionVerdict.PAUSED, { + reason: FlowRunStatus.PAUSED, + pauseMetadata: (params.hookResponse.response as PauseHookParams).pauseMetadata, + }) + } + return newExecutionContext.upsertStep(action.name, stepOutput.setOutput(output)).increaseTask().setVerdict(ExecutionVerdict.RUNNING, undefined) + } + catch (e) { + const handledError = handleExecutionError(e) + + const failedStepOutput = stepOutput + .setStatus(StepOutputStatus.FAILED) + .setErrorMessage(handledError.message) + + return executionState + .upsertStep(action.name, failedStepOutput) + .setVerdict(ExecutionVerdict.FAILED, handledError.verdictResponse) + .increaseTask() + } +} + +function getResponse(hookResponse: HookResponse): RespondResponse | undefined { + switch (hookResponse.type) { + case 'stopped': + return hookResponse.response.response + case 'respond': + return hookResponse.response.response + case 'paused': + if (hookResponse.response.pauseMetadata.type === PauseType.WEBHOOK) { + return hookResponse.response.pauseMetadata.response + } + else { + return undefined + } + case 'none': + return undefined + } +} + +const createTagsManager = (hkParams: createTagsManagerParams): TagsManager => { + return { + add: async (params: addTagsParams): Promise => { + hkParams.hookResponse.tags.push(params.name) + }, + + } +} + +type addTagsParams = { + name: string +} + +type createTagsManagerParams = { + hookResponse: HookResponse +} + + +function createStopHook(params: CreateStopHookParams): StopHook { + return (req?: StopHookParams) => { + params.hookResponse = { + ...params.hookResponse, + type: 'stopped', + response: req ?? { response: {} }, + } + } +} +type CreateStopHookParams = { + hookResponse: HookResponse +} + +function createRespondHook(params: CreateRespondHookParams): RespondHook { + return (req?: RespondHookParams) => { + params.hookResponse = { + ...params.hookResponse, + type: 'respond', + response: req ?? { response: {} }, + } + } +} + +type CreateRespondHookParams = { + hookResponse: HookResponse +} + +function createPauseHook(params: CreatePauseHookParams, pauseId: string): PauseHook { + return (req) => { + switch (req.pauseMetadata.type) { + case PauseType.DELAY: { + const diffInDays = dayjs(req.pauseMetadata.resumeDateTime).diff(dayjs(), 'days') + if (diffInDays > AP_PAUSED_FLOW_TIMEOUT_DAYS) { + throw new PausedFlowTimeoutError(undefined, AP_PAUSED_FLOW_TIMEOUT_DAYS) + } + params.hookResponse = { + ...params.hookResponse, + type: 'paused', + response: { + pauseMetadata: req.pauseMetadata, + }, + } + break + } + case PauseType.WEBHOOK: + params.hookResponse = { + ...params.hookResponse, + type: 'paused', + response: { + pauseMetadata: { + ...req.pauseMetadata, + requestId: pauseId, + response: req.pauseMetadata.response ?? {}, + }, + }, + } + break + } + } +} + +type CreatePauseHookParams = { + hookResponse: HookResponse +} diff --git a/packages/engine/src/lib/handler/router-executor.ts b/packages/engine/src/lib/handler/router-executor.ts new file mode 100755 index 0000000..2b6bece --- /dev/null +++ b/packages/engine/src/lib/handler/router-executor.ts @@ -0,0 +1,268 @@ +import { assertNotNullOrUndefined, BranchCondition, BranchExecutionType, BranchOperator, RouterAction, RouterActionSettings, RouterExecutionType, RouterStepOutput, StepOutputStatus } from '@activepieces/shared' +import dayjs from 'dayjs' +import { BaseExecutor } from './base-executor' +import { EngineConstants } from './context/engine-constants' +import { ExecutionVerdict, FlowExecutorContext } from './context/flow-execution-context' +import { flowExecutor } from './flow-executor' + +export const routerExecuter: BaseExecutor = { + async handle({ + action, + executionState, + constants, + }) { + const { censoredInput, resolvedInput } = await constants.propsResolver.resolve({ + unresolvedInput: { + ...action.settings, + inputUiInfo: undefined, + }, + executionState, + }) + + switch (resolvedInput.executionType) { + case RouterExecutionType.EXECUTE_ALL_MATCH: + return handleRouterExecution({ action, executionState, constants, censoredInput, resolvedInput, routerExecutionType: RouterExecutionType.EXECUTE_ALL_MATCH }) + case RouterExecutionType.EXECUTE_FIRST_MATCH: + return handleRouterExecution({ action, executionState, constants, censoredInput, resolvedInput, routerExecutionType: RouterExecutionType.EXECUTE_FIRST_MATCH }) + default: + throw new Error(`Router execution type ${resolvedInput.executionType} is not supported`) + } + }, +} + +async function handleRouterExecution({ action, executionState, constants, censoredInput, resolvedInput, routerExecutionType }: { + action: RouterAction + executionState: FlowExecutorContext + constants: EngineConstants + censoredInput: unknown + resolvedInput: RouterActionSettings + routerExecutionType: RouterExecutionType +}): Promise { + + const evaluatedConditionsWithoutFallback = resolvedInput.branches.map((branch) => { + return branch.branchType === BranchExecutionType.FALLBACK ? true : evaluateConditions(branch.conditions) + }) + + const evaluatedConditions = resolvedInput.branches.map((branch, index) => { + if (branch.branchType === BranchExecutionType.CONDITION) { + return evaluatedConditionsWithoutFallback[index] + } + const fallback = evaluatedConditionsWithoutFallback.filter((_, i) => i !== index).every((condition) => !condition) + return fallback + }) + + const routerOutput = RouterStepOutput.init({ + input: censoredInput, + }).setOutput({ + branches: resolvedInput.branches.map((branch, index) => ({ + branchName: branch.branchName, + branchIndex: index + 1, + evaluation: evaluatedConditions[index], + })), + }) + executionState = executionState.upsertStep(action.name, routerOutput) + + try { + for (let i = 0; i < resolvedInput.branches.length; i++) { + if (constants.testSingleStepMode) { + break + } + const condition = routerOutput.output?.branches[i].evaluation + if (condition) { + executionState = (await flowExecutor.execute({ + action: action.children[i], + executionState, + constants, + })) + if (routerExecutionType === RouterExecutionType.EXECUTE_FIRST_MATCH) { + break + } + } + } + return executionState + } + catch (e) { + console.error(e) + const failedStepOutput = routerOutput.setStatus(StepOutputStatus.FAILED) + return executionState.upsertStep(action.name, failedStepOutput).setVerdict(ExecutionVerdict.FAILED, undefined) + } +} + + +export function evaluateConditions(conditionGroups: BranchCondition[][]): boolean { + let orOperator = false + for (const conditionGroup of conditionGroups) { + let andGroup = true + for (const condition of conditionGroup) { + const castedCondition = condition + assertNotNullOrUndefined(castedCondition.operator, 'The operator is required but found to be undefined') + switch (castedCondition.operator) { + case BranchOperator.TEXT_CONTAINS: { + const firstValueContains = toLowercaseIfCaseInsensitive(castedCondition.firstValue, castedCondition.caseSensitive).includes( + toLowercaseIfCaseInsensitive(castedCondition.secondValue, castedCondition.caseSensitive), + ) + andGroup = andGroup && firstValueContains + break + } + case BranchOperator.TEXT_DOES_NOT_CONTAIN: { + const firstValueDoesNotContain = !toLowercaseIfCaseInsensitive(castedCondition.firstValue, castedCondition.caseSensitive).includes( + toLowercaseIfCaseInsensitive(castedCondition.secondValue, castedCondition.caseSensitive), + ) + andGroup = andGroup && firstValueDoesNotContain + break + } + case BranchOperator.TEXT_EXACTLY_MATCHES: { + const firstValueExactlyMatches = toLowercaseIfCaseInsensitive(castedCondition.firstValue, castedCondition.caseSensitive) === + toLowercaseIfCaseInsensitive(castedCondition.secondValue, castedCondition.caseSensitive) + andGroup = andGroup && firstValueExactlyMatches + break + } + case BranchOperator.TEXT_DOES_NOT_EXACTLY_MATCH: { + const firstValueDoesNotExactlyMatch = toLowercaseIfCaseInsensitive(castedCondition.firstValue, castedCondition.caseSensitive) !== + toLowercaseIfCaseInsensitive(castedCondition.secondValue, castedCondition.caseSensitive) + andGroup = andGroup && firstValueDoesNotExactlyMatch + break + } + case BranchOperator.TEXT_STARTS_WITH: { + const firstValueStartsWith = toLowercaseIfCaseInsensitive(castedCondition.firstValue, castedCondition.caseSensitive).startsWith( + toLowercaseIfCaseInsensitive(castedCondition.secondValue, castedCondition.caseSensitive), + ) + andGroup = andGroup && firstValueStartsWith + break + } + case BranchOperator.TEXT_ENDS_WITH: { + const firstValueEndsWith = toLowercaseIfCaseInsensitive(castedCondition.firstValue, castedCondition.caseSensitive).endsWith( + toLowercaseIfCaseInsensitive(castedCondition.secondValue, castedCondition.caseSensitive), + ) + andGroup = andGroup && firstValueEndsWith + break + } + case BranchOperator.TEXT_DOES_NOT_START_WITH: { + const firstValueDoesNotStartWith = !toLowercaseIfCaseInsensitive(castedCondition.firstValue, castedCondition.caseSensitive).startsWith( + toLowercaseIfCaseInsensitive(castedCondition.secondValue, castedCondition.caseSensitive), + ) + andGroup = andGroup && firstValueDoesNotStartWith + break + } + case BranchOperator.TEXT_DOES_NOT_END_WITH: { + const firstValueDoesNotEndWith = !toLowercaseIfCaseInsensitive(castedCondition.firstValue, castedCondition.caseSensitive).endsWith( + toLowercaseIfCaseInsensitive(castedCondition.secondValue, castedCondition.caseSensitive), + ) + andGroup = andGroup && firstValueDoesNotEndWith + break + } + case BranchOperator.LIST_CONTAINS: { + const list = parseAndCoerceListAsArray(castedCondition.firstValue) + andGroup = andGroup && list.some((item) => + toLowercaseIfCaseInsensitive(item, castedCondition.caseSensitive) === toLowercaseIfCaseInsensitive(castedCondition.secondValue, castedCondition.caseSensitive), + ) + break + } + case BranchOperator.LIST_DOES_NOT_CONTAIN: { + const list = parseAndCoerceListAsArray(castedCondition.firstValue) + andGroup = andGroup && !list.some((item) => + toLowercaseIfCaseInsensitive(item, castedCondition.caseSensitive) === toLowercaseIfCaseInsensitive(castedCondition.secondValue, castedCondition.caseSensitive), + ) + break + } + case BranchOperator.NUMBER_IS_GREATER_THAN: { + const firstValue = parseStringToNumber(castedCondition.firstValue) + const secondValue = parseStringToNumber(castedCondition.secondValue) + andGroup = andGroup && firstValue > secondValue + break + } + case BranchOperator.NUMBER_IS_LESS_THAN: { + const firstValue = parseStringToNumber(castedCondition.firstValue) + const secondValue = parseStringToNumber(castedCondition.secondValue) + andGroup = andGroup && firstValue < secondValue + break + } + case BranchOperator.NUMBER_IS_EQUAL_TO: { + const firstValue = parseStringToNumber(castedCondition.firstValue) + const secondValue = parseStringToNumber(castedCondition.secondValue) + andGroup = andGroup && firstValue == secondValue + break + } + case BranchOperator.BOOLEAN_IS_TRUE: + andGroup = andGroup && !!castedCondition.firstValue + break + case BranchOperator.BOOLEAN_IS_FALSE: + andGroup = andGroup && !castedCondition.firstValue + break + case BranchOperator.DATE_IS_AFTER: + andGroup = andGroup && isValidDate(castedCondition.firstValue) && isValidDate(castedCondition.secondValue) && dayjs(castedCondition.firstValue).isAfter(dayjs(castedCondition.secondValue)) + break + case BranchOperator.DATE_IS_EQUAL: + andGroup = andGroup && isValidDate(castedCondition.firstValue) && isValidDate(castedCondition.secondValue) && dayjs(castedCondition.firstValue).isSame(dayjs(castedCondition.secondValue)) + break + case BranchOperator.DATE_IS_BEFORE: + andGroup = andGroup && isValidDate(castedCondition.firstValue) && isValidDate(castedCondition.secondValue) && dayjs(castedCondition.firstValue).isBefore(dayjs(castedCondition.secondValue)) + break + case BranchOperator.LIST_IS_EMPTY: { + const list = parseListAsArray(castedCondition.firstValue) + andGroup = andGroup && Array.isArray(list) && list?.length === 0 + break + } + case BranchOperator.LIST_IS_NOT_EMPTY: { + const list = parseListAsArray(castedCondition.firstValue) + andGroup = andGroup && Array.isArray(list) && list?.length !== 0 + break + } + case BranchOperator.EXISTS: + andGroup = andGroup && castedCondition.firstValue !== undefined && castedCondition.firstValue !== null && castedCondition.firstValue !== '' + break + case BranchOperator.DOES_NOT_EXIST: + andGroup = andGroup && (castedCondition.firstValue === undefined || castedCondition.firstValue === null || castedCondition.firstValue === '') + break + } + } + orOperator = orOperator || andGroup + } + return Boolean(orOperator) +} + +function toLowercaseIfCaseInsensitive(text: unknown, caseSensitive: boolean | undefined): string { + if (typeof text === 'string') { + return caseSensitive ? text : text.toLowerCase() + } + const textAsString = JSON.stringify(text) + return caseSensitive ? textAsString : textAsString.toLowerCase() +} + +function parseStringToNumber(str: string): number | string { + const num = Number(str) + return isNaN(num) ? str : num +} + +function parseListAsArray(input: unknown): unknown[] | undefined { + if (typeof input === 'string') { + try { + const parsed = JSON.parse(input) + return Array.isArray(parsed) ? parsed : undefined + } + catch (e) { + return undefined + } + } + return Array.isArray(input) ? input : undefined +} + +function parseAndCoerceListAsArray(input: unknown): unknown[] { + if (typeof input === 'string') { + try { + const parsed = JSON.parse(input) + return Array.isArray(parsed) ? parsed : [parsed] + } + catch (e) { + return [input] + } + } + return Array.isArray(input) ? input : [input] +} + +function isValidDate(date: unknown): boolean { + if (typeof date === 'string' || typeof date === 'number' || date instanceof Date) { + return dayjs(date).isValid() + } + return false +} \ No newline at end of file diff --git a/packages/engine/src/lib/helper/error-handling.ts b/packages/engine/src/lib/helper/error-handling.ts new file mode 100644 index 0000000..3819809 --- /dev/null +++ b/packages/engine/src/lib/helper/error-handling.ts @@ -0,0 +1,75 @@ +import { CodeAction, FlowRunStatus, PieceAction } from '@activepieces/shared' +import { EngineConstants } from '../handler/context/engine-constants' +import { ExecutionVerdict, FlowExecutorContext, VerdictResponse } from '../handler/context/flow-execution-context' +import { ExecutionError, ExecutionErrorType } from './execution-errors' + +export async function runWithExponentialBackoff( + executionState: FlowExecutorContext, + action: T, + constants: EngineConstants, + requestFunction: RequestFunction, + attemptCount = 1, +): Promise { + const resultExecutionState = await requestFunction({ action, executionState, constants }) + const retryEnabled = action.settings.errorHandlingOptions?.retryOnFailure?.value + if ( + executionFailedWithRetryableError(resultExecutionState) && + attemptCount < constants.retryConstants.maxAttempts && + retryEnabled && + !constants.testSingleStepMode + ) { + const backoffTime = Math.pow(constants.retryConstants.retryExponential, attemptCount) * constants.retryConstants.retryInterval + await new Promise(resolve => setTimeout(resolve, backoffTime)) + return runWithExponentialBackoff(executionState, action, constants, requestFunction, attemptCount + 1) + } + + return resultExecutionState +} + +export async function continueIfFailureHandler( + executionState: FlowExecutorContext, + action: CodeAction | PieceAction, + constants: EngineConstants, +): Promise { + const continueOnFailure = action.settings.errorHandlingOptions?.continueOnFailure?.value + + if ( + executionState.verdict === ExecutionVerdict.FAILED && + continueOnFailure && + !constants.testSingleStepMode + ) { + return executionState + .setVerdict(ExecutionVerdict.RUNNING, undefined) + .increaseTask() + } + + return executionState +} + +export const handleExecutionError = (error: unknown): ErrorHandlingResponse => { + console.log(error) + const isEngineError = (error instanceof ExecutionError) && error.type === ExecutionErrorType.ENGINE + return { + message: error instanceof Error ? error.message : JSON.stringify(error), + verdictResponse: isEngineError ? { + reason: FlowRunStatus.INTERNAL_ERROR, + } : undefined, + } +} + +const executionFailedWithRetryableError = (flowExecutorContext: FlowExecutorContext): boolean => { + return flowExecutorContext.verdict === ExecutionVerdict.FAILED +} + +type Request = { + action: T + executionState: FlowExecutorContext + constants: EngineConstants +} + +type RequestFunction = (request: Request) => Promise + +type ErrorHandlingResponse = { + message: string + verdictResponse: VerdictResponse | undefined +} diff --git a/packages/engine/src/lib/helper/execution-errors.ts b/packages/engine/src/lib/helper/execution-errors.ts new file mode 100644 index 0000000..89e0b40 --- /dev/null +++ b/packages/engine/src/lib/helper/execution-errors.ts @@ -0,0 +1,98 @@ +import { STORE_KEY_MAX_LENGTH } from '@activepieces/shared' + +export enum ExecutionErrorType { + ENGINE = 'ENGINE', + USER = 'USER', +} +export class ExecutionError extends Error { + + public type: ExecutionErrorType + + constructor(name: string, message: string, type: ExecutionErrorType, public cause?: unknown) { + super(message) + this.name = name + this.type = type + } +} + +function formatMessage(message: string) { + return JSON.stringify({ + message, + }, null, 2) +} + +export class ConnectionNotFoundError extends ExecutionError { + constructor(connectionName: string, cause?: unknown) { + super('ConnectionNotFound', formatMessage(`connection (${connectionName}) not found`), ExecutionErrorType.USER, cause) + } +} + +export class ConnectionLoadingError extends ExecutionError { + constructor(connectionName: string, cause?: unknown) { + super('ConnectionLoadingFailure', formatMessage(`Failed to load connection (${connectionName})`), ExecutionErrorType.ENGINE, cause) + } +} + +export class ConnectionExpiredError extends ExecutionError { + constructor(connectionName: string, cause?: unknown) { + super('ConnectionExpired', formatMessage(`connection (${connectionName}) expired, reconnect again`), ExecutionErrorType.USER, cause) + } +} + +export class StorageLimitError extends ExecutionError { + + public maxStorageSizeInBytes: number + + constructor(key: string, maxStorageSizeInBytes: number, cause?: unknown) { + super('StorageLimitError', formatMessage(`Failed to read/write key "${key}", the value you are trying to read/write is larger than ${Math.floor(maxStorageSizeInBytes / 1024)} KB`), ExecutionErrorType.USER, cause) + this.maxStorageSizeInBytes = maxStorageSizeInBytes + } +} + +export class StorageInvalidKeyError extends ExecutionError { + constructor(key: string, cause?: unknown) { + super('StorageInvalidKeyError', formatMessage(`Failed to read/write key "${key}", the key is empty or longer than ${STORE_KEY_MAX_LENGTH} characters`), ExecutionErrorType.USER, cause) + } +} + +export class StorageError extends ExecutionError { + constructor(key: string, cause?: unknown) { + super('StorageError', formatMessage(`Failed to read/write key "${key}" due to ${JSON.stringify(cause)}`), ExecutionErrorType.ENGINE, cause) + } +} + +export class FileStoreError extends ExecutionError { + constructor(cause?: unknown) { + super('FileStoreError', formatMessage(`Failed to store file due to ${JSON.stringify(cause)}`), ExecutionErrorType.ENGINE, cause) + } +} + +export class PausedFlowTimeoutError extends ExecutionError { + constructor(cause?: unknown, maximumPauseDurationDays?: number) { + super('PausedFlowTimeoutError', `The flow cannot be paused for more than ${maximumPauseDurationDays} days`, ExecutionErrorType.USER, cause) + } +} + +export class ProgressUpdateError extends ExecutionError { + constructor(message: string, cause?: unknown) { + super('ProgressUpdateError', JSON.stringify({ + message, + }, null, 2), ExecutionErrorType.ENGINE, cause) + } +} + +export class FileSizeError extends ExecutionError { + constructor(currentFileSize: number, maximumSupportSize: number, cause?: unknown) { + super('FileSizeError', JSON.stringify({ + message: 'File size is larger than maximum supported size', + currentFileSize: `${currentFileSize} MB`, + maximumSupportSize: `${maximumSupportSize} MB`, + }), ExecutionErrorType.USER, cause) + } +} + +export class FetchError extends ExecutionError { + constructor(url: string, cause?: unknown) { + super('FetchError', formatMessage(`Failed to fetch from ${url}`), ExecutionErrorType.ENGINE, cause) + } +} diff --git a/packages/engine/src/lib/helper/logging-utils.ts b/packages/engine/src/lib/helper/logging-utils.ts new file mode 100644 index 0000000..d1f6523 --- /dev/null +++ b/packages/engine/src/lib/helper/logging-utils.ts @@ -0,0 +1,128 @@ +import { isObject, StepOutput } from '@activepieces/shared' +import { Queue } from '@datastructures-js/queue' +import sizeof from 'object-sizeof' +import PriorityQueue from 'priority-queue-typescript' + +const TRUNCATION_TEXT_PLACEHOLDER = '(truncated)' +const ERROR_OFFSET = 256 * 1024 +const DEFAULT_MAX_LOG_SIZE_FOR_TESTING = '10' +const MAX_LOG_SIZE = Number(process.env.AP_MAX_FILE_SIZE_MB ?? DEFAULT_MAX_LOG_SIZE_FOR_TESTING) * 1024 * 1024 +const MAX_SIZE_FOR_ALL_ENTRIES = MAX_LOG_SIZE - ERROR_OFFSET +const SIZE_OF_TRUNCATION_TEXT_PLACEHOLDER = sizeof(TRUNCATION_TEXT_PLACEHOLDER) +const nonTruncatableKeys: Key[] = ['status', 'duration', 'type'] + +export const loggingUtils = { + async trimExecution(steps: Record): Promise> { + const totalJsonSize = sizeof(steps) + if (!jsonExceedMaxSize(totalJsonSize)) { + return steps + } + return removeLeavesInTopologicalOrder(JSON.parse(JSON.stringify(steps))) + }, +} + +function removeLeavesInTopologicalOrder(json: Record): Record { + const nodes: Node[] = traverseJsonAndConvertToNodes(json) + const leaves = new PriorityQueue( + undefined, + (a: Node, b: Node) => b.size - a.size, + ) + nodes.filter((node) => node.numberOfChildren === 0).forEach((node) => leaves.add(node)) + let totalJsonSize = sizeof(json) + + while (!leaves.empty() && jsonExceedMaxSize(totalJsonSize)) { + const curNode = leaves.poll() + + const isDepthGreaterThanOne = curNode && curNode.depth > 1 + const isTruncatable = curNode && (!nonTruncatableKeys.includes(curNode.key)) + + if (isDepthGreaterThanOne && isTruncatable) { + totalJsonSize += SIZE_OF_TRUNCATION_TEXT_PLACEHOLDER - curNode.size + + const parent = curNode.parent + + parent.value[curNode.key] = TRUNCATION_TEXT_PLACEHOLDER + + nodes[parent.index].numberOfChildren-- + if (nodes[parent.index].numberOfChildren == 0) { + leaves.add(nodes[parent.index]) + } + } + } + return json as Record +} + +function traverseJsonAndConvertToNodes(root: unknown) { + + const nodesQueue = new Queue() + nodesQueue.enqueue({ key: '', value: root, parent: { index: -1, value: {} }, depth: 0 }) + + const nodes: Node[] = [] + + while (!nodesQueue.isEmpty()) { + const curNode = nodesQueue.dequeue() + const children = findChildren(curNode.value, curNode.key === 'iterations') + + nodes.push({ + index: nodes.length, + size: children.length === 0 ? sizeof(curNode.value) : children.length * SIZE_OF_TRUNCATION_TEXT_PLACEHOLDER, + key: curNode.key, + parent: { + index: curNode.parent.index, + value: curNode.parent.value as Record, + }, + numberOfChildren: children.length, + depth: curNode.depth, + }) + + children.forEach((child) => { + const key = child[0], value = child[1] + nodesQueue.enqueue({ value, key, parent: { index: nodes.length - 1, value: curNode.value }, depth: curNode.depth + 1 }) + }) + } + + return nodes +} + +function findChildren(curNode: unknown, traverseArray: boolean): [Key, unknown][] { + if (isObject(curNode)) { + return Object.entries(curNode) + } + // Array should be treated as a leaf node as If it has too many small items, It will prioritize the other steps first + if (Array.isArray(curNode) && traverseArray) { + const children: [Key, unknown][] = [] + for (let i = 0; i < curNode.length; i++) { + children.push([i, curNode[i]]) + } + return children + } + return [] +} + +const jsonExceedMaxSize = (jsonSize: number): boolean => { + return jsonSize > MAX_SIZE_FOR_ALL_ENTRIES +} + +type Node = { + index: number + size: number + key: Key + parent: { + index: number + value: Record + } + numberOfChildren: number + depth: number +} + +type BfsNode = { + value: unknown + key: Key + parent: { + index: number + value: unknown + } + depth: number +} + +type Key = string | number | symbol \ No newline at end of file diff --git a/packages/engine/src/lib/helper/piece-helper.ts b/packages/engine/src/lib/helper/piece-helper.ts new file mode 100644 index 0000000..f2c887d --- /dev/null +++ b/packages/engine/src/lib/helper/piece-helper.ts @@ -0,0 +1,184 @@ +import { + DropdownProperty, + DynamicProperties, + ExecutePropsResult, + MultiSelectDropdownProperty, + PieceMetadata, + PiecePropertyMap, + pieceTranslation, + PropertyType, + StaticPropsValue, +} from '@activepieces/pieces-framework' +import { + BasicAuthConnectionValue, + CustomAuthConnectionValue, + ExecuteExtractPieceMetadata, + ExecutePropsOptions, + ExecuteValidateAuthOperation, + ExecuteValidateAuthResponse, + OAuth2ConnectionValueWithApp, + SecretTextConnectionValue, +} from '@activepieces/shared' +import { EngineConstants } from '../handler/context/engine-constants' +import { FlowExecutorContext } from '../handler/context/flow-execution-context' +import { createFlowsContext } from '../services/flows.service' +import { utils } from '../utils' +import { createPropsResolver } from '../variables/props-resolver' +import { pieceLoader } from './piece-loader' + +export const pieceHelper = { + async executeProps({ params, piecesSource, executionState, constants, searchValue }: ExecutePropsParams): Promise> { + const property = await pieceLoader.getPropOrThrow({ + params, + piecesSource, + }) + if (property.type !== PropertyType.DROPDOWN && property.type !== PropertyType.MULTI_SELECT_DROPDOWN && property.type !== PropertyType.DYNAMIC) { + throw new Error(`Property type is not executable: ${property.type} for ${property.displayName}`) + } + try { + const { resolvedInput } = await createPropsResolver({ + apiUrl: constants.internalApiUrl, + projectId: params.projectId, + engineToken: params.engineToken, + }).resolve< + StaticPropsValue + >({ + unresolvedInput: params.input, + executionState, + }) + const ctx = { + searchValue, + server: { + token: params.engineToken, + apiUrl: constants.internalApiUrl, + publicUrl: params.publicApiUrl, + }, + project: { + id: params.projectId, + externalId: constants.externalProjectId, + }, + flows: createFlowsContext(constants), + connections: utils.createConnectionManager({ + projectId: params.projectId, + engineToken: params.engineToken, + apiUrl: constants.internalApiUrl, + target: 'properties', + }), + } + + switch (property.type) { + case PropertyType.DYNAMIC: { + const dynamicProperty = property as DynamicProperties + const props = await dynamicProperty.props(resolvedInput, ctx) + return { + type: PropertyType.DYNAMIC, + options: props, + } + } + case PropertyType.MULTI_SELECT_DROPDOWN: { + const multiSelectProperty = property as MultiSelectDropdownProperty< + unknown, + boolean + > + const options = await multiSelectProperty.options(resolvedInput, ctx) + return { + type: PropertyType.MULTI_SELECT_DROPDOWN, + options, + } + } + case PropertyType.DROPDOWN: { + const dropdownProperty = property as DropdownProperty + const options = await dropdownProperty.options(resolvedInput, ctx) + return { + type: PropertyType.DROPDOWN, + options, + } + } + } + } + catch (e) { + console.error(e) + return { + type: property.type, + options: { + disabled: true, + options: [], + placeholder: 'Throws an error, reconnect or refresh the page', + }, + } + } + }, + + async executeValidateAuth( + { params, piecesSource }: { params: ExecuteValidateAuthOperation, piecesSource: string }, + ): Promise { + const { piece: piecePackage } = params + + const piece = await pieceLoader.loadPieceOrThrow({ pieceName: piecePackage.pieceName, pieceVersion: piecePackage.pieceVersion, piecesSource }) + const server = { + apiUrl: params.internalApiUrl.endsWith('/') ? params.internalApiUrl : params.internalApiUrl + '/', + publicUrl: params.publicApiUrl, + } + if (piece.auth?.validate === undefined) { + return { + valid: true, + } + } + + switch (piece.auth.type) { + case PropertyType.BASIC_AUTH: { + const con = params.auth as BasicAuthConnectionValue + return piece.auth.validate({ + auth: { + username: con.username, + password: con.password, + }, + server, + }) + } + case PropertyType.SECRET_TEXT: { + const con = params.auth as SecretTextConnectionValue + return piece.auth.validate({ + auth: con.secret_text, + server, + }) + } + case PropertyType.CUSTOM_AUTH: { + const con = params.auth as CustomAuthConnectionValue + return piece.auth.validate({ + auth: con.props, + server, + }) + } + case PropertyType.OAUTH2: { + const con = params.auth as OAuth2ConnectionValueWithApp + return piece.auth.validate({ + auth: con, + server, + }) + } + default: { + throw new Error('Invalid auth type') + } + } + }, + + async extractPieceMetadata({ piecesSource, params }: { piecesSource: string, params: ExecuteExtractPieceMetadata }): Promise { + const { pieceName, pieceVersion } = params + const piece = await pieceLoader.loadPieceOrThrow({ pieceName, pieceVersion, piecesSource }) + const pieceAlias = pieceLoader.getPackageAlias({ pieceName, pieceVersion, piecesSource }) + const i18n = await pieceTranslation.initializeI18n(pieceAlias) + const fullMetadata = piece.metadata() + return { + ...fullMetadata, + name: pieceName, + version: pieceVersion, + authors: piece.authors, + i18n, + } + }, +} + + +type ExecutePropsParams = { searchValue?: string, executionState: FlowExecutorContext, params: ExecutePropsOptions, piecesSource: string, constants: EngineConstants } + diff --git a/packages/engine/src/lib/helper/piece-loader.ts b/packages/engine/src/lib/helper/piece-loader.ts new file mode 100644 index 0000000..07fa8e2 --- /dev/null +++ b/packages/engine/src/lib/helper/piece-loader.ts @@ -0,0 +1,143 @@ +import { Action, Piece, PiecePropertyMap, Trigger } from '@activepieces/pieces-framework' +import { ActivepiecesError, ErrorCode, ExecutePropsOptions, extractPieceFromModule, getPackageAliasForPiece, isNil } from '@activepieces/shared' + + +const loadPieceOrThrow = async ( + { pieceName, pieceVersion, piecesSource }: + { pieceName: string, pieceVersion: string, piecesSource: string }, +): Promise => { + const packageName = getPackageAlias({ + pieceName, + pieceVersion, + piecesSource, + }) + + const module = await import(packageName) + const piece = extractPieceFromModule({ + module, + pieceName, + pieceVersion, + }) + + if (isNil(piece)) { + throw new ActivepiecesError({ + code: ErrorCode.PIECE_NOT_FOUND, + params: { + pieceName, + pieceVersion, + message: 'Piece not found in the engine', + }, + }) + } + + return piece +} + +const getPieceAndTriggerOrThrow = async (params: { + pieceName: string + pieceVersion: string + triggerName: string + piecesSource: string +}, +): Promise<{ piece: Piece, pieceTrigger: Trigger }> => { + const { pieceName, pieceVersion, triggerName, piecesSource } = params + const piece = await loadPieceOrThrow({ pieceName, pieceVersion, piecesSource }) + const trigger = piece.getTrigger(triggerName) + + if (trigger === undefined) { + throw new Error(`trigger not found, pieceName=${pieceName}, triggerName=${triggerName}`) + } + + return { + piece, + pieceTrigger: trigger, + } +} + +const getPieceAndActionOrThrow = async (params: { + pieceName: string + pieceVersion: string + actionName: string + piecesSource: string +}, +): Promise<{ piece: Piece, pieceAction: Action }> => { + const { pieceName, pieceVersion, actionName, piecesSource } = params + + const piece = await loadPieceOrThrow({ pieceName, pieceVersion, piecesSource }) + const pieceAction = piece.getAction(actionName) + + if (isNil(pieceAction)) { + throw new ActivepiecesError({ + code: ErrorCode.STEP_NOT_FOUND, + params: { + pieceName, + pieceVersion, + stepName: actionName, + }, + }) + } + + return { + piece, + pieceAction, + } +} + +const getPropOrThrow = async ({ params, piecesSource }: { params: ExecutePropsOptions, piecesSource: string }) => { + const { piece: piecePackage, actionOrTriggerName, propertyName } = params + + const piece = await loadPieceOrThrow({ pieceName: piecePackage.pieceName, pieceVersion: piecePackage.pieceVersion, piecesSource }) + + const actionOrTrigger = piece.getAction(actionOrTriggerName) ?? piece.getTrigger(actionOrTriggerName) + + if (isNil(actionOrTrigger)) { + throw new ActivepiecesError({ + code: ErrorCode.STEP_NOT_FOUND, + params: { + pieceName: piecePackage.pieceName, + pieceVersion: piecePackage.pieceVersion, + stepName: actionOrTriggerName, + }, + }) + } + + const prop = (actionOrTrigger.props as PiecePropertyMap)[propertyName] + + if (isNil(prop)) { + throw new ActivepiecesError({ + code: ErrorCode.CONFIG_NOT_FOUND, + params: { + pieceName: piecePackage.pieceName, + pieceVersion: piecePackage.pieceVersion, + stepName: actionOrTriggerName, + configName: propertyName, + }, + }) + } + + return prop +} + +const getPackageAlias = ({ pieceName, pieceVersion, piecesSource }: { + pieceName: string + piecesSource: string + pieceVersion: string +}) => { + if (piecesSource.trim() === 'FILE') { + return pieceName + } + + return getPackageAliasForPiece({ + pieceName, + pieceVersion, + }) +} + + +export const pieceLoader = { + loadPieceOrThrow, + getPieceAndTriggerOrThrow, + getPieceAndActionOrThrow, + getPropOrThrow, + getPackageAlias, +} diff --git a/packages/engine/src/lib/helper/trigger-helper.ts b/packages/engine/src/lib/helper/trigger-helper.ts new file mode 100644 index 0000000..c26425a --- /dev/null +++ b/packages/engine/src/lib/helper/trigger-helper.ts @@ -0,0 +1,287 @@ +import { InputPropertyMap, PiecePropertyMap, StaticPropsValue, TriggerStrategy } from '@activepieces/pieces-framework' +import { assertEqual, assertNotNullOrUndefined, AUTHENTICATION_PROPERTY_NAME, EventPayload, ExecuteTriggerOperation, ExecuteTriggerResponse, isNil, PieceTrigger, ScheduleOptions, Trigger, TriggerHookType } from '@activepieces/shared' +import { isValidCron } from 'cron-validator' +import { EngineConstants } from '../handler/context/engine-constants' +import { FlowExecutorContext } from '../handler/context/flow-execution-context' +import { createFlowsContext } from '../services/flows.service' +import { createFilesService } from '../services/step-files.service' +import { createContextStore } from '../services/storage.service' +import { utils } from '../utils' +import { propsProcessor } from '../variables/props-processor' +import { createPropsResolver } from '../variables/props-resolver' +import { pieceLoader } from './piece-loader' + +type Listener = { + events: string[] + identifierValue: string + identifierKey: string +} + +export const triggerHelper = { + async executeOnStart(trigger: Trigger, constants: EngineConstants, payload: unknown) { + const { pieceName, pieceVersion, triggerName, input, inputUiInfo } = (trigger as PieceTrigger).settings + assertNotNullOrUndefined(triggerName, 'triggerName is required') + const { pieceTrigger, processedInput } = await prepareTriggerExecution({ + pieceName, + pieceVersion, + triggerName, + input, + projectId: constants.projectId, + apiUrl: constants.internalApiUrl, + engineToken: constants.engineToken, + piecesSource: constants.piecesSource, + inputSchema: inputUiInfo.schema as Record, + }) + const isOldVersionOrNotSupported = isNil(pieceTrigger.onStart) + if (isOldVersionOrNotSupported) { + return + } + const context = { + store: createContextStore({ + apiUrl: constants.internalApiUrl, + prefix: '', + flowId: constants.flowId, + engineToken: constants.engineToken, + }), + auth: processedInput[AUTHENTICATION_PROPERTY_NAME], + propsValue: processedInput, + payload, + run: { + id: constants.flowRunId, + }, + project: { + id: constants.projectId, + externalId: constants.externalProjectId, + }, + connections: utils.createConnectionManager({ + apiUrl: constants.internalApiUrl, + projectId: constants.projectId, + engineToken: constants.engineToken, + target: 'triggers', + }), + } + + await pieceTrigger.onStart(context) + }, + + async executeTrigger({ params, constants }: ExecuteTriggerParams): Promise> { + const { pieceName, pieceVersion, triggerName, input, inputUiInfo } = (params.flowVersion.trigger as PieceTrigger).settings + assertNotNullOrUndefined(triggerName, 'triggerName is required') + + const { piece, pieceTrigger, processedInput } = await prepareTriggerExecution({ + pieceName, + pieceVersion, + triggerName, + input, + projectId: params.projectId, + apiUrl: constants.internalApiUrl, + engineToken: params.engineToken, + piecesSource: constants.piecesSource, + inputSchema: inputUiInfo.schema as Record, + }) + + const appListeners: Listener[] = [] + const prefix = params.test ? 'test' : '' + let scheduleOptions: ScheduleOptions | undefined = undefined + const context = { + store: createContextStore({ + apiUrl: constants.internalApiUrl, + prefix, + flowId: params.flowVersion.flowId, + engineToken: params.engineToken, + }), + app: { + createListeners({ events, identifierKey, identifierValue }: Listener): void { + appListeners.push({ events, identifierValue, identifierKey }) + }, + }, + setSchedule(request: ScheduleOptions) { + if (!isValidCron(request.cronExpression)) { + throw new Error(`Invalid cron expression: ${request.cronExpression}`) + } + scheduleOptions = { + cronExpression: request.cronExpression, + timezone: request.timezone ?? 'UTC', + failureCount: request.failureCount ?? 0, + } + }, + flows: createFlowsContext({ + engineToken: params.engineToken, + internalApiUrl: constants.internalApiUrl, + flowId: params.flowVersion.flowId, + flowVersionId: params.flowVersion.id, + }), + webhookUrl: params.webhookUrl, + auth: processedInput[AUTHENTICATION_PROPERTY_NAME], + propsValue: processedInput, + payload: params.triggerPayload ?? {}, + project: { + id: params.projectId, + externalId: constants.externalProjectId, + }, + server: { + token: params.engineToken, + apiUrl: constants.internalApiUrl, + publicUrl: params.publicApiUrl, + }, + connections: utils.createConnectionManager({ + apiUrl: constants.internalApiUrl, + projectId: constants.projectId, + engineToken: constants.engineToken, + target: 'triggers', + }), + } + switch (params.hookType) { + case TriggerHookType.ON_DISABLE: + await pieceTrigger.onDisable(context) + return {} + case TriggerHookType.ON_ENABLE: + await pieceTrigger.onEnable(context) + return { + listeners: appListeners, + scheduleOptions: pieceTrigger.type === TriggerStrategy.POLLING ? scheduleOptions : undefined, + } + case TriggerHookType.RENEW: + assertEqual(pieceTrigger.type, TriggerStrategy.WEBHOOK, 'triggerType', 'WEBHOOK') + await pieceTrigger.onRenew(context) + return { + success: true, + } + case TriggerHookType.HANDSHAKE: { + try { + const response = await pieceTrigger.onHandshake(context) + return { + success: true, + response, + } + } + catch (e) { + console.error(e) + + return { + success: false, + message: JSON.stringify(e), + } + } + } + case TriggerHookType.TEST: + try { + return { + success: true, + output: await pieceTrigger.test({ + ...context, + files: createFilesService({ + apiUrl: constants.internalApiUrl, + engineToken: params.engineToken!, + stepName: triggerName, + flowId: params.flowVersion.flowId, + }), + }), + } + } + catch (e) { + console.error(e) + + return { + success: false, + message: JSON.stringify(e), + output: [], + } + } + case TriggerHookType.RUN: { + if (pieceTrigger.type === TriggerStrategy.APP_WEBHOOK) { + if (!params.appWebhookUrl) { + throw new Error(`App webhook url is not available for piece name ${pieceName}`) + } + if (!params.webhookSecret) { + throw new Error(`Webhook secret is not available for piece name ${pieceName}`) + } + + try { + const verified = piece.events?.verify({ + appWebhookUrl: params.appWebhookUrl, + payload: params.triggerPayload as EventPayload, + webhookSecret: params.webhookSecret, + }) + + if (verified === false) { + console.info('Webhook is not verified') + return { + success: false, + message: 'Webhook is not verified', + output: [], + } + } + } + catch (e) { + console.error('Error while verifying webhook', e) + return { + success: false, + message: 'Error while verifying webhook', + output: [], + } + } + } + const items = await pieceTrigger.run({ + ...context, + files: createFilesService({ + apiUrl: constants.internalApiUrl, + engineToken: params.engineToken!, + flowId: params.flowVersion.flowId, + stepName: triggerName, + }), + }) + if (!Array.isArray(items)) { + throw new Error(`Trigger run should return an array of items, but returned ${typeof items}`) + } + return { + success: true, + output: items, + } + } + } + }, +} + +type ExecuteTriggerParams = { + params: ExecuteTriggerOperation + constants: EngineConstants +} + +async function prepareTriggerExecution({ pieceName, pieceVersion, triggerName, input, projectId, apiUrl, engineToken, piecesSource, inputSchema }: PrepareTriggerExecutionParams) { + const { piece, pieceTrigger } = await pieceLoader.getPieceAndTriggerOrThrow({ + pieceName, + pieceVersion, + triggerName, + piecesSource, + }) + + const { resolvedInput } = await createPropsResolver({ + apiUrl, + projectId, + engineToken, + }).resolve>({ + unresolvedInput: input, + executionState: FlowExecutorContext.empty(), + }) + + const { processedInput, errors } = await propsProcessor.applyProcessorsAndValidators(resolvedInput, pieceTrigger.props, piece.auth, pieceTrigger.requireAuth, inputSchema) + + if (Object.keys(errors).length > 0) { + throw new Error(JSON.stringify(errors, null, 2)) + } + + return { piece, pieceTrigger, processedInput } +} + +type PrepareTriggerExecutionParams = { + pieceName: string + pieceVersion: string + triggerName: string + input: unknown + projectId: string + apiUrl: string + engineToken: string + inputSchema: Record | undefined + piecesSource: string +} diff --git a/packages/engine/src/lib/operations.ts b/packages/engine/src/lib/operations.ts new file mode 100644 index 0000000..233aac9 --- /dev/null +++ b/packages/engine/src/lib/operations.ts @@ -0,0 +1,258 @@ +import { + Action, + ActionType, + BeginExecuteFlowOperation, + EngineOperation, + EngineOperationType, + EngineResponse, + EngineResponseStatus, + ExecuteActionResponse, + ExecuteExtractPieceMetadata, + ExecuteFlowOperation, + ExecutePropsOptions, + ExecuteStepOperation, + ExecuteToolOperation, + ExecuteTriggerOperation, + ExecuteTriggerResponse, + ExecuteValidateAuthOperation, + ExecutionType, + FlowRunResponse, + flowStructureUtil, + GenericStepOutput, + StepOutput, + StepOutputStatus, + TriggerHookType, + TriggerPayload, +} from '@activepieces/shared' +import { EngineConstants } from './handler/context/engine-constants' +import { ExecutionVerdict, FlowExecutorContext } from './handler/context/flow-execution-context' +import { testExecutionContext } from './handler/context/test-execution-context' +import { flowExecutor } from './handler/flow-executor' +import { pieceHelper } from './helper/piece-helper' +import { triggerHelper } from './helper/trigger-helper' +import { progressService } from './services/progress.service' +import { utils } from './utils' + +const executeFlow = async (input: ExecuteFlowOperation, context: FlowExecutorContext): Promise>> => { + const constants = EngineConstants.fromExecuteFlowInput(input) + const output = await flowExecutor.executeFromTrigger({ + executionState: context, + constants, + input, + }) + const newContext = output.verdict === ExecutionVerdict.RUNNING ? output.setVerdict(ExecutionVerdict.SUCCEEDED, output.verdictResponse) : output + await progressService.sendUpdate({ + engineConstants: constants, + flowExecutorContext: newContext, + updateImmediate: true, + }) + const response = await newContext.toResponse() + return { + status: EngineResponseStatus.OK, + response: { + status: response.status, + error: response.error, + }, + } +} + + +async function executeActionForTool(input: ExecuteToolOperation): Promise { + const step: Action = { + name: input.actionName, + displayName: input.actionName, + type: ActionType.PIECE, + settings: { + input: input.input, + actionName: input.actionName, + pieceName: input.pieceName, + pieceVersion: input.pieceVersion, + pieceType: input.pieceType, + packageType: input.packageType, + inputUiInfo: {}, + errorHandlingOptions: { + continueOnFailure: { + value: false, + }, + retryOnFailure: { + value: false, + }, + }, + }, + valid: true, + } + const output = await flowExecutor.getExecutorForAction(step.type).handle({ + action: step, + executionState: FlowExecutorContext.empty(), + constants: EngineConstants.fromExecuteActionInput(input), + }) + return { + success: output.verdict !== ExecutionVerdict.FAILED, + input: output.steps[step.name].input, + output: cleanSampleData(output.steps[step.name]), + } +} + +async function executeStep(input: ExecuteStepOperation): Promise { + const step = flowStructureUtil.getActionOrThrow(input.stepName, input.flowVersion.trigger) + const output = await flowExecutor.getExecutorForAction(step.type).handle({ + action: step, + executionState: await testExecutionContext.stateFromFlowVersion({ + apiUrl: input.internalApiUrl, + flowVersion: input.flowVersion, + excludedStepName: step.name, + projectId: input.projectId, + engineToken: input.engineToken, + sampleData: input.sampleData, + }), + constants: EngineConstants.fromExecuteStepInput(input), + }) + return { + success: output.verdict !== ExecutionVerdict.FAILED, + input: output.steps[step.name].input, + output: cleanSampleData(output.steps[step.name]), + } +} + +function cleanSampleData(stepOutput: StepOutput) { + if (stepOutput.status === StepOutputStatus.FAILED) { + return stepOutput.errorMessage + } + + return stepOutput.output +} + +async function runOrReturnPayload(input: BeginExecuteFlowOperation): Promise { + if (!input.executeTrigger) { + return input.triggerPayload as TriggerPayload + } + const newPayload = await triggerHelper.executeTrigger({ + params: { + ...input, + hookType: TriggerHookType.RUN, + test: false, + webhookUrl: '', + triggerPayload: input.triggerPayload as TriggerPayload, + }, + constants: EngineConstants.fromExecuteFlowInput(input), + }) as ExecuteTriggerResponse + return newPayload.output[0] as TriggerPayload +} + +async function getFlowExecutionState(input: ExecuteFlowOperation): Promise { + let flowContext = FlowExecutorContext.empty().increaseTask(input.tasks) + switch (input.executionType) { + case ExecutionType.BEGIN: { + const newPayload = await runOrReturnPayload(input) + flowContext = flowContext.upsertStep(input.flowVersion.trigger.name, GenericStepOutput.create({ + type: input.flowVersion.trigger.type, + status: StepOutputStatus.SUCCEEDED, + input: {}, + }).setOutput(newPayload)) + break + } + case ExecutionType.RESUME: { + break + } + } + + for (const [step, output] of Object.entries(input.executionState.steps)) { + if ([StepOutputStatus.SUCCEEDED, StepOutputStatus.PAUSED].includes(output.status)) { + flowContext = flowContext.upsertStep(step, output) + } + } + return flowContext +} + +export async function execute(operationType: EngineOperationType, operation: EngineOperation): Promise> { + try { + + switch (operationType) { + case EngineOperationType.EXTRACT_PIECE_METADATA: { + const input = operation as ExecuteExtractPieceMetadata + const output = await pieceHelper.extractPieceMetadata({ + params: input, + piecesSource: EngineConstants.PIECE_SOURCES, + }) + return { + status: EngineResponseStatus.OK, + response: output, + } + } + case EngineOperationType.EXECUTE_FLOW: { + const input = operation as ExecuteFlowOperation + const flowExecutorContext = await getFlowExecutionState(input) + const output = await executeFlow(input, flowExecutorContext) + return output + } + case EngineOperationType.EXECUTE_PROPERTY: { + const input = operation as ExecutePropsOptions + const output = await pieceHelper.executeProps({ + params: input, + piecesSource: EngineConstants.PIECE_SOURCES, + executionState: await testExecutionContext.stateFromFlowVersion({ + apiUrl: input.internalApiUrl, + flowVersion: input.flowVersion, + projectId: input.projectId, + engineToken: input.engineToken, + sampleData: input.sampleData, + }), + searchValue: input.searchValue, + constants: EngineConstants.fromExecutePropertyInput(input), + }) + return { + status: EngineResponseStatus.OK, + response: output, + } + } + case EngineOperationType.EXECUTE_TRIGGER_HOOK: { + const input = operation as ExecuteTriggerOperation + const output = await triggerHelper.executeTrigger({ + params: input, + constants: EngineConstants.fromExecuteTriggerInput(input), + }) + return { + status: EngineResponseStatus.OK, + response: output, + } + } + case EngineOperationType.EXECUTE_TOOL: { + const input = operation as ExecuteToolOperation + const output = await executeActionForTool(input) + return { + status: EngineResponseStatus.OK, + response: output, + } + } + case EngineOperationType.EXECUTE_STEP: { + const input = operation as ExecuteStepOperation + const output = await executeStep(input) + return { + status: EngineResponseStatus.OK, + response: output, + } + } + case EngineOperationType.EXECUTE_VALIDATE_AUTH: { + const input = operation as ExecuteValidateAuthOperation + const output = await pieceHelper.executeValidateAuth({ + params: input, + piecesSource: EngineConstants.PIECE_SOURCES, + }) + + return { + status: EngineResponseStatus.OK, + response: output, + } + } + } + } + catch (e) { + console.error(e) + return { + status: EngineResponseStatus.ERROR, + response: utils.tryParseJson((e as Error).message), + } + } +} + + diff --git a/packages/engine/src/lib/services/connections.service.ts b/packages/engine/src/lib/services/connections.service.ts new file mode 100644 index 0000000..c2950aa --- /dev/null +++ b/packages/engine/src/lib/services/connections.service.ts @@ -0,0 +1,94 @@ +import { AppConnection, AppConnectionStatus, AppConnectionType, BasicAuthConnectionValue, CloudOAuth2ConnectionValue, OAuth2ConnectionValueWithApp } from '@activepieces/shared' +import { StatusCodes } from 'http-status-codes' +import { ConnectionExpiredError, ConnectionLoadingError, ConnectionNotFoundError, ExecutionError, FetchError } from '../helper/execution-errors' + +export const createConnectionService = ({ projectId, engineToken, apiUrl }: CreateConnectionServiceParams): ConnectionService => { + return { + async obtain(externalId: string): Promise { + const url = `${apiUrl}v1/worker/app-connections/${encodeURIComponent(externalId)}?projectId=${projectId}` + + try { + const response = await fetch(url, { + method: 'GET', + headers: { + Authorization: `Bearer ${engineToken}`, + }, + }) + + if (!response.ok) { + return handleResponseError({ + externalId, + httpStatus: response.status, + }) + } + const connection: AppConnection = await response.json() + if (connection.status === AppConnectionStatus.ERROR) { + throw new ConnectionExpiredError(externalId) + } + return getConnectionValue(connection) + } + catch (e) { + if (e instanceof ExecutionError) { + throw e + } + + return handleFetchError({ + url, + cause: e, + }) + } + }, + } +} + +const handleResponseError = ({ externalId, httpStatus }: HandleResponseErrorParams): never => { + if (httpStatus === StatusCodes.NOT_FOUND.valueOf()) { + throw new ConnectionNotFoundError(externalId) + } + + throw new ConnectionLoadingError(externalId) +} + +const handleFetchError = ({ url, cause }: HandleFetchErrorParams): never => { + throw new FetchError(url, cause) +} + +const getConnectionValue = (connection: AppConnection): ConnectionValue => { + switch (connection.value.type) { + case AppConnectionType.SECRET_TEXT: + return connection.value.secret_text + + case AppConnectionType.CUSTOM_AUTH: + return connection.value.props + + default: + return connection.value + } +} + +type ConnectionValue = + | OAuth2ConnectionValueWithApp + | CloudOAuth2ConnectionValue + | BasicAuthConnectionValue + | Record + | string + +type ConnectionService = { + obtain(externalId: string): Promise +} + +type CreateConnectionServiceParams = { + projectId: string + apiUrl: string + engineToken: string +} + +type HandleResponseErrorParams = { + externalId: string + httpStatus: number +} + +type HandleFetchErrorParams = { + url: string + cause: unknown +} diff --git a/packages/engine/src/lib/services/flows.service.ts b/packages/engine/src/lib/services/flows.service.ts new file mode 100644 index 0000000..51113f1 --- /dev/null +++ b/packages/engine/src/lib/services/flows.service.ts @@ -0,0 +1,30 @@ +import { FlowsContext } from '@activepieces/pieces-framework' +import { PopulatedFlow, SeekPage } from '@activepieces/shared' + + +type CreateFlowsServiceParams = { + engineToken: string + internalApiUrl: string + flowId: string + flowVersionId: string +} +export const createFlowsContext = ({ engineToken, internalApiUrl, flowId, flowVersionId }: CreateFlowsServiceParams): FlowsContext => { + return { + async list(): Promise> { + const response = await fetch(`${internalApiUrl}v1/engine/populated-flows`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${engineToken}`, + }, + }) + return response.json() + }, + current: { + id: flowId, + version: { + id: flowVersionId, + }, + }, + } +} \ No newline at end of file diff --git a/packages/engine/src/lib/services/progress.service.ts b/packages/engine/src/lib/services/progress.service.ts new file mode 100644 index 0000000..764199e --- /dev/null +++ b/packages/engine/src/lib/services/progress.service.ts @@ -0,0 +1,208 @@ +import crypto from 'crypto' +import { OutputContext } from '@activepieces/pieces-framework' +import { ActionType, assertNotNullOrUndefined, FileLocation, GenericStepOutput, isNil, logSerializer, LoopStepOutput, NotifyFrontendRequest, SendFlowResponseRequest, StepOutput, StepOutputStatus, UpdateRunProgressRequest, UpdateRunProgressResponse, WebsocketClientEvent } from '@activepieces/shared' +import { Mutex } from 'async-mutex' +import fetchRetry from 'fetch-retry' +import { EngineConstants } from '../handler/context/engine-constants' +import { FlowExecutorContext } from '../handler/context/flow-execution-context' +import { ProgressUpdateError } from '../helper/execution-errors' + +const FILE_STORAGE_LOCATION = process.env.AP_FILE_STORAGE_LOCATION as FileLocation +const USE_SIGNED_URL = (process.env.AP_S3_USE_SIGNED_URLS === 'true') && FILE_STORAGE_LOCATION === FileLocation.S3 + +let lastScheduledUpdateId: NodeJS.Timeout | null = null +let lastActionExecutionTime: number | undefined = undefined +let lastRequestHash: string | undefined = undefined +let isGraceShutdownSignalReceived = false +const MAXIMUM_UPDATE_THRESHOLD = 15000 +const DEBOUNCE_THRESHOLD = 5000 +const lock = new Mutex() +const updateLock = new Mutex() +const fetchWithRetry = fetchRetry(global.fetch) + +process.on('SIGTERM', () => { + isGraceShutdownSignalReceived = true +}) + +process.on('SIGINT', () => { + isGraceShutdownSignalReceived = true +}) + +export const progressService = { + sendUpdate: async (params: UpdateStepProgressParams): Promise => { + return updateLock.runExclusive(async () => { + if (lastScheduledUpdateId) { + clearTimeout(lastScheduledUpdateId) + } + + const shouldUpdateNow = isNil(lastActionExecutionTime) || (Date.now() - lastActionExecutionTime > MAXIMUM_UPDATE_THRESHOLD) || isGraceShutdownSignalReceived + if (shouldUpdateNow || params.updateImmediate) { + await sendUpdateRunRequest(params) + return + } + + lastScheduledUpdateId = setTimeout(async () => { + await sendUpdateRunRequest(params) + }, DEBOUNCE_THRESHOLD) + }) + }, + sendFlowResponse: async (engineConstants: EngineConstants, request: SendFlowResponseRequest): Promise => { + await fetchWithRetry(new URL(`${engineConstants.internalApiUrl}v1/engine/update-flow-response`).toString(), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${engineConstants.engineToken}`, + }, + body: JSON.stringify(request), + }) + }, + createOutputContext: (params: CreateOutputContextParams): OutputContext => { + const { engineConstants, flowExecutorContext, stepName, stepOutput } = params + return { + update: async (params: { data: unknown }) => { + + if (engineConstants.testSingleStepMode) { + assertNotNullOrUndefined(engineConstants.httpRequestId, 'httpRequestId is required when running in test single step mode') + await notifyFrontend(engineConstants, { + type: WebsocketClientEvent.TEST_STEP_PROGRESS, + data: { + id: engineConstants.httpRequestId, + success: true, + input: stepOutput.input, + output: params.data, + standardError: '', + standardOutput: '', + sampleDataFileId: undefined, + }, + }) + } + else { + await sendUpdateRunRequest({ + engineConstants, + flowExecutorContext: flowExecutorContext.upsertStep(stepName, stepOutput.setOutput(params.data)), + updateImmediate: true, + }) + } + }, + } + }, +} + +type CreateOutputContextParams = { + engineConstants: EngineConstants + flowExecutorContext: FlowExecutorContext + stepName: string + stepOutput: GenericStepOutput +} + +const sendUpdateRunRequest = async (params: UpdateStepProgressParams): Promise => { + if (params.engineConstants.isRunningApTests || params.engineConstants.testSingleStepMode) { + return + } + await lock.runExclusive(async () => { + lastActionExecutionTime = Date.now() + const { flowExecutorContext, engineConstants } = params + const runDetails = await flowExecutorContext.toResponse() + const runDetailsWithoutSteps = { ...runDetails, steps: undefined } + const executionState = await logSerializer.serialize({ + executionState: { + steps: runDetails.steps as Record, + }, + }) + const request = { + runId: engineConstants.flowRunId, + workerHandlerId: engineConstants.serverHandlerId ?? null, + httpRequestId: engineConstants.httpRequestId ?? null, + runDetails: runDetailsWithoutSteps, + executionStateBuffer: USE_SIGNED_URL ? undefined : executionState.toString(), + executionStateContentLength: executionState.byteLength, + progressUpdateType: engineConstants.progressUpdateType, + failedStepName: extractFailedStepName(runDetails.steps as Record), + } + const requestHash = crypto.createHash('sha256').update(JSON.stringify(request)).digest('hex') + if (requestHash === lastRequestHash) { + return + } + lastRequestHash = requestHash + const response = await sendProgressUpdate(params.engineConstants, request) + if (!response.ok) { + throw new ProgressUpdateError('Failed to send progress update', response) + } + if (USE_SIGNED_URL) { + const responseBody: UpdateRunProgressResponse = await response.json() + if (isNil(responseBody.uploadUrl)) { + throw new ProgressUpdateError('Upload URL is not available', response) + } + await uploadExecutionState(responseBody.uploadUrl, executionState) + } + await notifyFrontend(engineConstants, { + type: WebsocketClientEvent.FLOW_RUN_PROGRESS, + data: { + runId: engineConstants.flowRunId, + }, + }) + }) +} + +const sendProgressUpdate = async (engineConstants: EngineConstants, request: UpdateRunProgressRequest): Promise => { + return fetchWithRetry(new URL(`${engineConstants.internalApiUrl}v1/engine/update-run`).toString(), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${engineConstants.engineToken}`, + }, + retryDelay: 4000, + retries: 3, + body: JSON.stringify(request), + }) +} + +const uploadExecutionState = async (uploadUrl: string, executionState: Buffer): Promise => { + await fetchWithRetry(uploadUrl, { + method: 'PUT', + body: executionState, + headers: { + 'Content-Type': 'application/octet-stream', + }, + retries: 3, + retryDelay: 3000, + }) +} + +const notifyFrontend = async (engineConstants: EngineConstants, request: NotifyFrontendRequest): Promise => { + await fetchWithRetry(new URL(`${engineConstants.internalApiUrl}v1/engine/notify-frontend`).toString(), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${engineConstants.engineToken}`, + }, + body: JSON.stringify(request), + }) +} + +type UpdateStepProgressParams = { + engineConstants: EngineConstants + flowExecutorContext: FlowExecutorContext + updateImmediate?: boolean +} + +export const extractFailedStepName = (steps: Record): string | undefined => { + if (!steps) { + return undefined + } + + const failedStep = Object.entries(steps).find(([_, step]) => { + const stepOutput = step as StepOutput + if (stepOutput.type === ActionType.LOOP_ON_ITEMS) { + const loopOutput = stepOutput as LoopStepOutput + return loopOutput.output?.iterations.some(iteration => + Object.values(iteration).some(iterationStep => + (iterationStep as StepOutput).status === StepOutputStatus.FAILED, + ), + ) + } + return stepOutput.status === StepOutputStatus.FAILED + }) + + return failedStep?.[0] +} diff --git a/packages/engine/src/lib/services/step-files.service.ts b/packages/engine/src/lib/services/step-files.service.ts new file mode 100644 index 0000000..1d00c97 --- /dev/null +++ b/packages/engine/src/lib/services/step-files.service.ts @@ -0,0 +1,96 @@ +import { FilesService } from '@activepieces/pieces-framework' +import { FileLocation, isNil, StepFileUpsertResponse } from '@activepieces/shared' +import fetchRetry from 'fetch-retry' +import { FileSizeError, FileStoreError } from '../helper/execution-errors' + +const MAX_FILE_SIZE_MB = Number(process.env.AP_MAX_FILE_SIZE_MB) +const FILE_STORAGE_LOCATION = process.env.AP_FILE_STORAGE_LOCATION as FileLocation +const USE_SIGNED_URL = (process.env.AP_S3_USE_SIGNED_URLS === 'true') && FILE_STORAGE_LOCATION === FileLocation.S3 + +export type DefaultFileSystem = 'db' | 'local' + +type CreateFilesServiceParams = { apiUrl: string, stepName: string, flowId: string, engineToken: string } + +export function createFilesService({ stepName, flowId, engineToken, apiUrl }: CreateFilesServiceParams): FilesService { + return { + write: async ({ fileName, data }: { fileName: string, data: Buffer }): Promise => { + validateFileSize(data) + const formData = createFormData({ fileName, data, stepName, flowId }) + const result = await uploadFileMetadata({ formData, engineToken, apiUrl }) + if (USE_SIGNED_URL) { + if (isNil(result.uploadUrl)) { + throw new FileStoreError({ + status: 500, + body: 'Upload URL is not available', + }) + } + await uploadFileContent({ url: result.uploadUrl, data }) + } + + return result.url + }, + } +} + +function validateFileSize(data: Buffer): void { + const maximumFileSizeInBytes = MAX_FILE_SIZE_MB * 1024 * 1024 + if (data.length > maximumFileSizeInBytes) { + throw new FileSizeError(data.length / 1024 / 1024, MAX_FILE_SIZE_MB) + } +} + +function createFormData({ fileName, data, stepName, flowId }: { fileName: string, data: Buffer, stepName: string, flowId: string }): FormData { + const formData = new FormData() + formData.append('stepName', stepName) + formData.append('flowId', flowId) + formData.append('contentLength', data.length.toString()) + formData.append('fileName', fileName) + + if (!USE_SIGNED_URL) { + formData.append('file', new Blob([data], { type: 'application/octet-stream' }), fileName) + } + + return formData +} + +async function uploadFileMetadata({ formData, engineToken, apiUrl }: { formData: FormData, engineToken: string, apiUrl: string }): Promise { + const fetchWithRetry = fetchRetry(global.fetch) + const response = await fetchWithRetry(apiUrl + 'v1/step-files', { + method: 'POST', + headers: { + Authorization: 'Bearer ' + engineToken, + }, + retryDelay: 3000, + retries: 3, + body: formData, + }) + + if (!response.ok) { + throw new FileStoreError({ + status: response.status, + body: response.body, + }) + } + + return await response.json() as StepFileUpsertResponse +} + +async function uploadFileContent({ url, data }: { url: string, data: Buffer }): Promise { + const fetchWithRetry = fetchRetry(global.fetch) + const uploadResponse = await fetchWithRetry(url, { + method: 'PUT', + body: data, + headers: { + 'Content-Type': 'application/octet-stream', + }, + retries: 3, + retryDelay: 3000, + }) + + if (!uploadResponse.ok) { + throw new FileStoreError({ + status: uploadResponse.status, + body: uploadResponse.body, + }) + } +} diff --git a/packages/engine/src/lib/services/storage.service.ts b/packages/engine/src/lib/services/storage.service.ts new file mode 100644 index 0000000..ad5aba5 --- /dev/null +++ b/packages/engine/src/lib/services/storage.service.ts @@ -0,0 +1,185 @@ +import { URL } from 'node:url' +import { Store, StoreScope } from '@activepieces/pieces-framework' +import { DeleteStoreEntryRequest, FlowId, isNil, PutStoreEntryRequest, STORE_KEY_MAX_LENGTH, STORE_VALUE_MAX_SIZE, StoreEntry } from '@activepieces/shared' +import { StatusCodes } from 'http-status-codes' +import sizeof from 'object-sizeof' +import { ExecutionError, FetchError, StorageError, StorageInvalidKeyError, StorageLimitError } from '../helper/execution-errors' + +export const createStorageService = ({ engineToken, apiUrl }: CreateStorageServiceParams): StorageService => { + return { + async get(key: string): Promise { + const url = buildUrl(apiUrl, key) + + try { + const response = await fetch(url, { + headers: { + Authorization: `Bearer ${engineToken}`, + }, + }) + + if (!response.ok) { + return await handleResponseError({ + key, + response, + }) + } + + return await response.json() + } + catch (e) { + return handleFetchError({ + url, + cause: e, + }) + } + }, + + async put(request: PutStoreEntryRequest): Promise { + const url = buildUrl(apiUrl) + + try { + const sizeOfValue = sizeof(request.value) + if (sizeOfValue > STORE_VALUE_MAX_SIZE) { + throw new StorageLimitError(request.key, STORE_VALUE_MAX_SIZE) + } + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${engineToken}`, + }, + body: JSON.stringify(request), + }) + + if (!response.ok) { + return await handleResponseError({ + key: request.key, + response, + }) + } + + return await response.json() + } + catch (e) { + return handleFetchError({ + url, + cause: e, + }) + } + }, + + async delete(request: DeleteStoreEntryRequest): Promise { + const url = buildUrl(apiUrl, request.key) + + try { + const response = await fetch(url, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${engineToken}`, + }, + }) + + if (!response.ok) { + await handleResponseError({ + key: request.key, + response, + }) + } + + return null + } + catch (e) { + return handleFetchError({ + url, + cause: e, + }) + } + }, + } +} + +export function createContextStore({ apiUrl, prefix, flowId, engineToken }: { apiUrl: string, prefix: string, flowId: FlowId, engineToken: string }): Store { + return { + async put(key: string, value: T, scope = StoreScope.FLOW): Promise { + const modifiedKey = createKey(prefix, scope, flowId, key) + await createStorageService({ apiUrl, engineToken }).put({ + key: modifiedKey, + value, + }) + return value + }, + async delete(key: string, scope = StoreScope.FLOW): Promise { + const modifiedKey = createKey(prefix, scope, flowId, key) + await createStorageService({ apiUrl, engineToken }).delete({ + key: modifiedKey, + }) + }, + async get(key: string, scope = StoreScope.FLOW): Promise { + const modifiedKey = createKey(prefix, scope, flowId, key) + const storeEntry = await createStorageService({ apiUrl, engineToken }).get(modifiedKey) + if (storeEntry === null) { + return null + } + return storeEntry.value as T + }, + } +} + +function createKey(prefix: string, scope: StoreScope, flowId: FlowId, key: string): string { + if (isNil(key) || typeof key !== 'string' || key.length === 0 || key.length > STORE_KEY_MAX_LENGTH) { + throw new StorageInvalidKeyError(key) + } + switch (scope) { + case StoreScope.PROJECT: + return prefix + key + case StoreScope.FLOW: + return prefix + 'flow_' + flowId + '/' + key + } +} + +const buildUrl = (apiUrl: string, key?: string): URL => { + const url = new URL(`${apiUrl}v1/store-entries`) + if (key) { + url.searchParams.set('key', key) + } + return url +} + +const handleResponseError = async ({ key, response }: HandleResponseErrorParams): Promise => { + if (response.status === StatusCodes.NOT_FOUND.valueOf()) { + return null + } + if (response.status === StatusCodes.REQUEST_TOO_LONG) { + throw new StorageLimitError(key, STORE_VALUE_MAX_SIZE) + } + const cause = await response.text() + throw new StorageError(key, cause) +} + +const handleFetchError = ({ url, cause }: HandleFetchErrorParams): never => { + if (cause instanceof ExecutionError) { + throw cause + } + throw new FetchError(url.toString(), cause) +} + +type CreateStorageServiceParams = { + engineToken: string + apiUrl: string +} + +type StorageService = { + get(key: string): Promise + put(request: PutStoreEntryRequest): Promise + delete(request: DeleteStoreEntryRequest): Promise +} + +type HandleResponseErrorParams = { + key: string + response: Response +} + +type HandleFetchErrorParams = { + url: URL + cause: unknown +} diff --git a/packages/engine/src/lib/utils.ts b/packages/engine/src/lib/utils.ts new file mode 100755 index 0000000..3ff4a45 --- /dev/null +++ b/packages/engine/src/lib/utils.ts @@ -0,0 +1,60 @@ +import { readFile } from 'node:fs/promises' +import { ConnectionsManager, PauseHookParams, RespondHookParams, StopHookParams } from '@activepieces/pieces-framework' +import { createConnectionService } from './services/connections.service' + +export const utils = { + async parseJsonFile(filePath: string): Promise { + try { + const file = await readFile(filePath, 'utf-8') + return JSON.parse(file) + } + catch (e) { + throw Error((e as Error).message) + } + }, + + + tryParseJson(value: string): unknown { + try { + return JSON.parse(value) + } + catch (e) { + return value + } + }, + createConnectionManager(params: CreateConnectionManagerParams): ConnectionsManager { + return { + get: async (key: string) => { + try { + const { projectId, engineToken, apiUrl, target } = params + const connection = await createConnectionService({ projectId, engineToken, apiUrl }).obtain(key) + if (target === 'actions') { + params.hookResponse.tags.push(`connection:${key}`) + } + return connection + } + catch (e) { + return null + } + }, + } + }, +} + +export type HookResponse = { + type: 'paused' + tags: string[] + response: PauseHookParams +} | { + type: 'stopped' + tags: string[] + response: StopHookParams +} | { + type: 'respond' + tags: string[] + response: RespondHookParams +} | { + type: 'none' + tags: string[] +} +type CreateConnectionManagerParams = { projectId: string, engineToken: string, apiUrl: string, target: 'triggers' | 'properties' } | { projectId: string, engineToken: string, apiUrl: string, target: 'actions', hookResponse: HookResponse } \ No newline at end of file diff --git a/packages/engine/src/lib/variables/processors/array-zipper.ts b/packages/engine/src/lib/variables/processors/array-zipper.ts new file mode 100644 index 0000000..cd4b12f --- /dev/null +++ b/packages/engine/src/lib/variables/processors/array-zipper.ts @@ -0,0 +1,27 @@ +import { isObject } from '@activepieces/shared' +import { ProcessorFn } from './types' + +function getLongestArrayLengthInObject(props: Record): number { + return Math.max( + ...Object.values(props).map(value => + Array.isArray(value) ? value.length : 1, + ), + ) +} + +function constructResultForIndex(props: Record, index: number): Record { + return Object.entries(props).reduce((result, [key, value]) => { + result[key] = Array.isArray(value) ? value[index] : value + return result + }, {} as Record) +} + +export const arrayZipperProcessor: ProcessorFn = (_property, value) => { + if (Array.isArray(value) || !isObject(value)) { + return value + } + + return Array.from({ length: getLongestArrayLengthInObject(value) }, + (_, index) => constructResultForIndex(value, index), + ) +} diff --git a/packages/engine/src/lib/variables/processors/date-time.ts b/packages/engine/src/lib/variables/processors/date-time.ts new file mode 100644 index 0000000..25e8d52 --- /dev/null +++ b/packages/engine/src/lib/variables/processors/date-time.ts @@ -0,0 +1,18 @@ +import dayjs from 'dayjs' +import timezone from 'dayjs/plugin/timezone' +import utc from 'dayjs/plugin/utc' +import { ProcessorFn } from './types' + +export const dateTimeProcessor: ProcessorFn = (_property, value) => { + dayjs.extend(utc) + dayjs.extend(timezone) + const dateTimeString = value + try { + if (!dateTimeString) throw Error('Undefined input') + return dayjs.tz(dateTimeString, 'UTC').toISOString() + } + catch (error) { + console.error(error) + return undefined + } +} \ No newline at end of file diff --git a/packages/engine/src/lib/variables/processors/file.ts b/packages/engine/src/lib/variables/processors/file.ts new file mode 100644 index 0000000..64aa50d --- /dev/null +++ b/packages/engine/src/lib/variables/processors/file.ts @@ -0,0 +1,89 @@ +import { ApFile } from '@activepieces/pieces-framework' +import { isNil, isString } from '@activepieces/shared' +import axios from 'axios' +import isBase64 from 'is-base64' +import mime from 'mime-types' +import { ProcessorFn } from './types' + +export const fileProcessor: ProcessorFn = async (_property, urlOrBase64) => { + if (isNil(urlOrBase64) || !isString(urlOrBase64)) { + return null + } + try { + const file = handleBase64File(urlOrBase64) + if (!isNil(file)) { + return file + } + return await handleUrlFile(urlOrBase64) + } + catch (e) { + console.error(e) + return null + } +} + +function handleBase64File(propertyValue: string): ApFile | null { + if (!isBase64(propertyValue, { allowMime: true })) { + return null + } + const matches = propertyValue.match(/^data:([A-Za-z-+/]+);base64,(.+)$/) // example match: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC + if (!matches || matches?.length !== 3) { + return null + } + const base64 = matches[2] + const extension = mime.extension(matches[1]) || 'bin' + return new ApFile( + `unknown.${extension}`, + Buffer.from(base64, 'base64'), + extension, + ) +} + +async function handleUrlFile(path: string): Promise { + const fileResponse = await axios.get(path, { + responseType: 'arraybuffer', + }) + + + const filename = getFileName(path, fileResponse.headers['content-disposition'], fileResponse.headers['content-type']) ?? 'unknown' + const extension = filename.split('.').length > 1 ? filename.split('.').pop() : undefined + + return new ApFile( + filename, + Buffer.from(fileResponse.data, 'binary'), + extension, + ) +} + + +function getFileName(path: string, disposition: string | null, mimeType: string | undefined): string | null { + const url = new URL(path) + if (isNil(disposition)) { + const fileNameFromUrl = url.pathname.includes('/') && url.pathname.split('/').pop()?.includes('.') ? url.pathname.split('/').pop() : null + if (!isNil(fileNameFromUrl)) { + return fileNameFromUrl + } + const resolvedExtension = mimeType ? mime.extension(mimeType) : null + return `unknown.${resolvedExtension ?? 'bin'}` + } + const utf8FilenameRegex = /filename\*=UTF-8''([\w%\-.]+)(?:; ?|$)/i + if (utf8FilenameRegex.test(disposition)) { + const result = utf8FilenameRegex.exec(disposition) + if (result && result.length > 1) { + return decodeURIComponent(result[1]) + } + } + // prevent ReDos attacks by anchoring the ascii regex to string start and + // slicing off everything before 'filename=' + const filenameStart = disposition.toLowerCase().indexOf('filename=') + const asciiFilenameRegex = /^filename=(["']?)(.*?[^\\])\1(?:; ?|$)/i + + if (filenameStart >= 0) { + const partialDisposition = disposition.slice(filenameStart) + const matches = asciiFilenameRegex.exec(partialDisposition) + if (matches != null && matches[2]) { + return matches[2] + } + } + return null +} \ No newline at end of file diff --git a/packages/engine/src/lib/variables/processors/index.ts b/packages/engine/src/lib/variables/processors/index.ts new file mode 100644 index 0000000..add8162 --- /dev/null +++ b/packages/engine/src/lib/variables/processors/index.ts @@ -0,0 +1,19 @@ +import { PropertyType } from '@activepieces/pieces-framework' +import { dateTimeProcessor } from './date-time' +import { fileProcessor } from './file' +import { jsonProcessor } from './json' +import { numberProcessor } from './number' +import { objectProcessor } from './object' +import { textProcessor } from './text' +import { ProcessorFn } from './types' + +export const processors: Partial> = { + JSON: jsonProcessor, + OBJECT: objectProcessor, + NUMBER: numberProcessor, + LONG_TEXT: textProcessor, + SHORT_TEXT: textProcessor, + SECRET_TEXT: textProcessor, + DATE_TIME: dateTimeProcessor, + FILE: fileProcessor, +} \ No newline at end of file diff --git a/packages/engine/src/lib/variables/processors/json.ts b/packages/engine/src/lib/variables/processors/json.ts new file mode 100644 index 0000000..574a34a --- /dev/null +++ b/packages/engine/src/lib/variables/processors/json.ts @@ -0,0 +1,18 @@ +import { isNil } from '@activepieces/shared' +import { ProcessorFn } from './types' + +export const jsonProcessor: ProcessorFn = (_property, value) => { + if (isNil(value)) { + return value + } + try { + if (typeof value === 'object') { + return value + } + return JSON.parse(value) + } + catch (error) { + console.error(error) + return undefined + } +} \ No newline at end of file diff --git a/packages/engine/src/lib/variables/processors/number.ts b/packages/engine/src/lib/variables/processors/number.ts new file mode 100644 index 0000000..c245d47 --- /dev/null +++ b/packages/engine/src/lib/variables/processors/number.ts @@ -0,0 +1,12 @@ +import { isNil } from '@activepieces/shared' +import { ProcessorFn } from './types' + +export const numberProcessor: ProcessorFn = (_property, value) => { + if (isNil(value)) { + return value + } + if (value === '') { + return undefined + } + return Number(value) +} \ No newline at end of file diff --git a/packages/engine/src/lib/variables/processors/object.ts b/packages/engine/src/lib/variables/processors/object.ts new file mode 100644 index 0000000..8308861 --- /dev/null +++ b/packages/engine/src/lib/variables/processors/object.ts @@ -0,0 +1,20 @@ +import { isNil } from '@activepieces/shared' +import { ProcessorFn } from './types' + +export const objectProcessor: ProcessorFn = (_property, value) => { + if (isNil(value)) { + return value + } + if (typeof value === 'string') { + try { + return JSON.parse(value) + } + catch (e) { + return undefined + } + } + if (typeof value === 'object' && !Array.isArray(value)) { + return value + } + return undefined +} \ No newline at end of file diff --git a/packages/engine/src/lib/variables/processors/text.ts b/packages/engine/src/lib/variables/processors/text.ts new file mode 100644 index 0000000..5fc6047 --- /dev/null +++ b/packages/engine/src/lib/variables/processors/text.ts @@ -0,0 +1,17 @@ +import { isNil } from '@activepieces/shared' +import { ProcessorFn } from './types' + +export const textProcessor: ProcessorFn = (property, value) => { + if (isNil(value)) { + return value + } + if (typeof value === 'object') { + return JSON.stringify(value) + } + + const result = value.toString() + if (result.length === 0 && !property.required) { + return undefined + } + return result +} \ No newline at end of file diff --git a/packages/engine/src/lib/variables/processors/types.ts b/packages/engine/src/lib/variables/processors/types.ts new file mode 100644 index 0000000..4263e32 --- /dev/null +++ b/packages/engine/src/lib/variables/processors/types.ts @@ -0,0 +1,8 @@ + +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { PieceProperty } from '@activepieces/pieces-framework' + +export type ProcessorFn = ( + property: PieceProperty, + value: INPUT, +) => OUTPUT diff --git a/packages/engine/src/lib/variables/props-processor.ts b/packages/engine/src/lib/variables/props-processor.ts new file mode 100644 index 0000000..72ec206 --- /dev/null +++ b/packages/engine/src/lib/variables/props-processor.ts @@ -0,0 +1,182 @@ +import { InputPropertyMap, PieceAuthProperty, PieceProperty, PiecePropertyMap, PropertyType, StaticPropsValue } from '@activepieces/pieces-framework' +import { AUTHENTICATION_PROPERTY_NAME, isNil, isObject } from '@activepieces/shared' +import { z } from 'zod' +import { processors } from './processors' +import { arrayZipperProcessor } from './processors/array-zipper' + + + +type PropsValidationError = { + [key: string]: string[] | PropsValidationError | PropsValidationError[] +} + +export const propsProcessor = { + applyProcessorsAndValidators: async ( + resolvedInput: StaticPropsValue, + props: InputPropertyMap, + auth: PieceAuthProperty | undefined, + requireAuth: boolean, + dynamaicPropertiesSchema: Record | undefined | null, + ): Promise<{ processedInput: StaticPropsValue, errors: PropsValidationError }> => { + const processedInput = { ...resolvedInput } + const errors: PropsValidationError = {} + + const isAuthenticationProperty = auth && (auth.type === PropertyType.CUSTOM_AUTH || auth.type === PropertyType.OAUTH2) && !isNil(auth.props) && requireAuth + if (isAuthenticationProperty) { + const { processedInput: authProcessedInput, errors: authErrors } = await propsProcessor.applyProcessorsAndValidators( + resolvedInput[AUTHENTICATION_PROPERTY_NAME], + auth.props, + undefined, + requireAuth, + undefined, + ) + processedInput.auth = authProcessedInput + if (Object.keys(authErrors).length > 0) { + errors.auth = authErrors + } + } + + for (const [key, value] of Object.entries(resolvedInput)) { + const property = props[key] + if (isNil(property)) { + continue + } + if (property.type === PropertyType.DYNAMIC && !isNil(dynamaicPropertiesSchema?.[key])) { + const { processedInput: itemProcessedInput, errors: itemErrors } = await propsProcessor.applyProcessorsAndValidators( + value, + dynamaicPropertiesSchema[key], + undefined, + false, + undefined, + ) + processedInput[key] = itemProcessedInput + if (Object.keys(itemErrors).length > 0) { + errors[key] = itemErrors + } + } + if (property.type === PropertyType.ARRAY && property.properties) { + const arrayOfObjects = arrayZipperProcessor(property, value) + const processedArray = [] + const processedErrors = [] + for (const item of arrayOfObjects) { + const { processedInput: itemProcessedInput, errors: itemErrors } = await propsProcessor.applyProcessorsAndValidators( + item, + property.properties, + undefined, + false, + undefined, + ) + processedArray.push(itemProcessedInput) + processedErrors.push(itemErrors) + } + processedInput[key] = processedArray + const isThereErrors = processedErrors.some(error => Object.keys(error).length > 0) + if (isThereErrors) { + errors[key] = { + properties: processedErrors, + } + } + } + const processor = processors[property.type] + if (processor) { + processedInput[key] = await processor(property, processedInput[key]) + } + + const shouldValidate = key !== AUTHENTICATION_PROPERTY_NAME && property.type !== PropertyType.MARKDOWN + if (!shouldValidate) { + continue + } + } + + for (const [key, value] of Object.entries(processedInput)) { + const property = props[key] + if (isNil(property)) { + continue + } + + const validationErrors = validateProperty(property, value, resolvedInput[key]) + if (validationErrors.length > 0) { + errors[key] = validationErrors + } + } + + return { processedInput, errors } + }, +} + +const validateProperty = (property: PieceProperty, value: unknown, originalValue: unknown): string[] => { + let schema + switch (property.type) { + case PropertyType.SHORT_TEXT: + case PropertyType.LONG_TEXT: + schema = z.string({ + required_error: `Expected string, received: ${originalValue}`, + invalid_type_error: `Expected string, received: ${originalValue}`, + }) + break + case PropertyType.NUMBER: + schema = z.number({ + required_error: `Expected number, received: ${originalValue}`, + invalid_type_error: `Expected number, received: ${originalValue}`, + }) + break + case PropertyType.CHECKBOX: + schema = z.boolean({ + required_error: `Expected boolean, received: ${originalValue}`, + invalid_type_error: `Expected boolean, received: ${originalValue}`, + }) + break + case PropertyType.DATE_TIME: + schema = z.string({ + required_error: `Invalid datetime format. Expected ISO format (e.g. 2024-03-14T12:00:00.000Z), received: ${originalValue}`, + invalid_type_error: `Invalid datetime format. Expected ISO format (e.g. 2024-03-14T12:00:00.000Z), received: ${originalValue}`, + }) + break + case PropertyType.ARRAY: + schema = z.array(z.any(), { + required_error: `Expected array, received: ${originalValue}`, + invalid_type_error: `Expected array, received: ${originalValue}`, + }) + break + case PropertyType.OBJECT: + schema = z.record(z.any(), { + required_error: `Expected object, received: ${originalValue}`, + invalid_type_error: `Expected object, received: ${originalValue}`, + }) + break + case PropertyType.JSON: + schema = z.any().refine( + (val) => isObject(val) || Array.isArray(val), + { + message: `Expected JSON, received: ${originalValue}`, + }, + ) + break + case PropertyType.FILE: + schema = z.record(z.any(), { + required_error: `Expected file url or base64 with mimeType, received: ${originalValue}`, + invalid_type_error: `Expected file url or base64 with mimeType, received: ${originalValue}`, + }) + break + default: + schema = z.any() + } + let finalSchema + if (property.required) { + finalSchema = schema + } + else { + finalSchema = schema.nullable().optional() + } + + try { + finalSchema.parse(value) + return [] + } + catch (err) { + if (err instanceof z.ZodError) { + return err.errors.map(e => e.message) + } + return [] + } +} \ No newline at end of file diff --git a/packages/engine/src/lib/variables/props-resolver.ts b/packages/engine/src/lib/variables/props-resolver.ts new file mode 100644 index 0000000..a3fa438 --- /dev/null +++ b/packages/engine/src/lib/variables/props-resolver.ts @@ -0,0 +1,237 @@ +import { applyFunctionToValues, isNil, isObject, isString } from '@activepieces/shared' +import replaceAsync from 'string-replace-async' +import { initCodeSandbox } from '../core/code/code-sandbox' +import { FlowExecutorContext } from '../handler/context/flow-execution-context' +import { createConnectionService } from '../services/connections.service' + +const VARIABLE_PATTERN = /\{\{(.*?)\}\}/g +const CONNECTIONS = 'connections' +const FLATTEN_NESTED_KEYS_PATTERN = /\{\{\s*flattenNestedKeys(.*?)\}\}/g + +type PropsResolverParams = { + engineToken: string + projectId: string + apiUrl: string +} + +export const createPropsResolver = ({ engineToken, projectId, apiUrl }: PropsResolverParams) => { + return { + resolve: async (params: ResolveInputParams): Promise> => { + const { unresolvedInput, executionState } = params + if (isNil(unresolvedInput)) { + return { + //TODO: REMOVE THE AS T + resolvedInput: unresolvedInput as T, + censoredInput: unresolvedInput, + } + } + const currentState = executionState.currentState() + const resolveOptions = { + engineToken, + projectId, + apiUrl, + currentState, + } + const resolvedInput = await applyFunctionToValues( + unresolvedInput, + (token) => resolveInputAsync({ + ...resolveOptions, + input: token, + censoredInput: false, + })) + const censoredInput = await applyFunctionToValues( + unresolvedInput, + (token) => resolveInputAsync({ + ...resolveOptions, + input: token, + censoredInput: true, + })) + return { + resolvedInput, + censoredInput, + } + }, + } +} + +const mergeFlattenedKeysArraysIntoOneArray = async (token: string, partsThatNeedResolving: string[], + resolveOptions: Pick)=>{ + const resolvedValues: Record = {} + let longestResultLength = 0 + for (const tokenPart of partsThatNeedResolving) { + const variableName = tokenPart.substring(2, tokenPart.length - 2) + resolvedValues[tokenPart] = await resolveSingleToken({ + ...resolveOptions, + variableName, + }) + if (Array.isArray(resolvedValues[tokenPart])) { + longestResultLength = Math.max(longestResultLength, resolvedValues[tokenPart].length) + } + } + const result = new Array(longestResultLength).fill(null).map((_, index) => { + return Object.entries(resolvedValues).reduce((acc, [tokenPart, value])=>{ + const valueToUse = (Array.isArray(value) ? value[index] : value) ?? '' + acc = acc.replace(tokenPart, isString(valueToUse) ? valueToUse : JSON.stringify(valueToUse)) + return acc + }, token) + }) + return result +} + +export type PropsResolver = ReturnType +/** + * input: `Hello {{firstName}} {{lastName}}` + * tokenThatNeedResolving: [`{{firstName}}`, `{{lastName}}`] + */ +async function resolveInputAsync(params: ResolveInputInternalParams): Promise { + const { input, currentState, engineToken, projectId, apiUrl, censoredInput } = params + const tokensThatNeedResolving = input.match(VARIABLE_PATTERN) + const inputContainsOnlyOneTokenToResolve = tokensThatNeedResolving !== null && tokensThatNeedResolving.length === 1 && tokensThatNeedResolving[0] === input + const resolveOptions = { + engineToken, + projectId, + apiUrl, + currentState, + censoredInput, + } + + if (inputContainsOnlyOneTokenToResolve) { + const trimmedInput = input.trim() + const variableName = trimmedInput.substring(2, trimmedInput.length - 2) + return resolveSingleToken({ + ...resolveOptions, + variableName, + }) + } + const inputIncludesFlattenNestedKeysTokens = input.match(FLATTEN_NESTED_KEYS_PATTERN) + if (!isNil(inputIncludesFlattenNestedKeysTokens) && !isNil(tokensThatNeedResolving)) { + return mergeFlattenedKeysArraysIntoOneArray(input, tokensThatNeedResolving, resolveOptions) + } + + return replaceAsync(input, VARIABLE_PATTERN, async (_fullMatch, variableName) => { + const result = await resolveSingleToken({ + ...resolveOptions, + variableName, + }) + return isString(result) ? result : JSON.stringify(result) + }) +} + +async function resolveSingleToken(params: ResolveSingleTokenParams): Promise { + const { variableName, currentState } = params + const isConnection = variableName.startsWith(CONNECTIONS) + if (isConnection) { + return handleConnection(params) + } + return evalInScope(variableName, { ...currentState, flattenNestedKeys }) +} + +async function handleConnection(params: ResolveSingleTokenParams): Promise { + const { variableName, engineToken, projectId, apiUrl, censoredInput } = params + const connectionName = parseConnectionNameOnly(variableName) + if (isNil(connectionName)) { + return '' + } + if (censoredInput) { + return '**REDACTED**' + } + const connection = await createConnectionService({ engineToken, projectId, apiUrl }).obtain(connectionName) + const pathAfterConnectionName = parsePathAfterConnectionName(variableName, connectionName) + if (isNil(pathAfterConnectionName) || pathAfterConnectionName.length === 0) { + return connection + } + return evalInScope(pathAfterConnectionName, { connection, flattenNestedKeys }) +} + +function parsePathAfterConnectionName(variableName: string, connectionName: string): string | null { + if (variableName.includes('[')) { + return variableName.substring(`connections.['${connectionName}']`.length) + } + const cp = variableName.substring(`connections.${connectionName}`.length) + if (cp.length === 0) { + return cp + } + return `connection${cp}` +} + +function parseConnectionNameOnly(variableName: string): string | null { + const connectionWithNewFormatSquareBrackets = variableName.includes('[') + if (connectionWithNewFormatSquareBrackets) { + return parseSquareBracketConnectionPath(variableName) + } + // {{connections.connectionName.path}} + // This does not work If connectionName contains . + return variableName.split('.')?.[1] +} + +function parseSquareBracketConnectionPath(variableName: string): string | null { + // Find the connection name inside {{connections['connectionName'].path}} + const matches = variableName.match(/\['([^']+)'\]/g) + if (matches && matches.length >= 1) { + // Remove the square brackets and quotes from the connection name + + const secondPath = matches[0].replace(/\['|'\]/g, '') + return secondPath + } + return null +} + +async function evalInScope(js: string, contextAsScope: Record): Promise { + try { + const codeSandbox = await initCodeSandbox() + const result = await codeSandbox.runScript({ + script: js, + scriptContext: contextAsScope, + }) + return result ?? '' + } + catch (exception) { + console.warn('[evalInScope] Error evaluating variable', exception) + return '' + } +} + +function flattenNestedKeys(data: unknown, pathToMatch: string[]): unknown[] { + if (isObject(data)) { + for (const [key, value] of Object.entries(data)) { + if (key === pathToMatch[0]) { + return flattenNestedKeys(value, pathToMatch.slice(1)) + } + } + } + else if (Array.isArray(data)) { + return data.flatMap((d) => flattenNestedKeys(d, pathToMatch)) + } + else if (pathToMatch.length === 0) { + return [data] + } + return [] +} + +type ResolveSingleTokenParams = { + variableName: string + currentState: Record + engineToken: string + projectId: string + apiUrl: string + censoredInput: boolean +} + +type ResolveInputInternalParams = { + input: string + engineToken: string + projectId: string + apiUrl: string + censoredInput: boolean + currentState: Record +} + +type ResolveInputParams = { + unresolvedInput: unknown + executionState: FlowExecutorContext +} + +type ResolveResult = { + resolvedInput: T + censoredInput: unknown +} diff --git a/packages/engine/src/main.ts b/packages/engine/src/main.ts new file mode 100755 index 0000000..f651e91 --- /dev/null +++ b/packages/engine/src/main.ts @@ -0,0 +1,104 @@ +import { + assertNotNullOrUndefined, + EngineError, + EngineOperation, + EngineOperationType, + EngineResult, + EngineSocketEvent, + EngineStderr, + EngineStdout, + isNil } from '@activepieces/shared' +import WebSocket from 'ws' +import { execute } from './lib/operations' + +const WORKER_ID = process.env.WORKER_ID +const WS_URL = 'ws://127.0.0.1:12345/worker/ws' + +let socket: WebSocket | undefined + +async function executeFromSocket(operation: EngineOperation, operationType: EngineOperationType): Promise { + try { + const result = await execute(operationType, operation) + const resultParsed = JSON.parse(JSON.stringify(result)) + const engineResult: EngineResult = { + result: resultParsed, + } + socket?.send(JSON.stringify({ + type: EngineSocketEvent.ENGINE_RESULT, + data: engineResult, + })) + } + catch (error) { + const engineError: EngineError = { + error: error instanceof Error ? error.message : error, + } + socket?.send(JSON.stringify({ + type: EngineSocketEvent.ENGINE_ERROR, + data: engineError, + })) + } +} + +function setupSocket() { + assertNotNullOrUndefined(WORKER_ID, 'WORKER_ID') + + socket = new WebSocket(WS_URL, { + headers: { + 'worker-id': WORKER_ID, + }, + }) + + // Redirect console.log/error to socket + const originalLog = console.log + console.log = function (...args) { + const engineStdout: EngineStdout = { + message: args.join(' ') + '\n', + } + socket?.send(JSON.stringify({ + type: EngineSocketEvent.ENGINE_STDOUT, + data: engineStdout, + })) + originalLog.apply(console, args) + } + + const originalError = console.error + console.error = function (...args) { + const engineStderr: EngineStderr = { + message: args.join(' ') + '\n', + } + socket?.send(JSON.stringify({ + type: EngineSocketEvent.ENGINE_STDERR, + data: engineStderr, + })) + originalError.apply(console, args) + } + + socket.on('message', (data: string) => { + try { + const message = JSON.parse(data) + if (message.type === EngineSocketEvent.ENGINE_OPERATION) { + executeFromSocket(message.data.operation, message.data.operationType).catch(e => { + const engineError: EngineError = { + error: e instanceof Error ? e.message : e, + } + socket?.send(JSON.stringify({ + type: EngineSocketEvent.ENGINE_ERROR, + data: engineError, + })) + }) + } + } + catch (error) { + console.error('Error handling operation:', error) + } + }) + + socket.on('close', () => { + console.log('Socket disconnected, exiting process') + process.exit(0) + }) +} + +if (!isNil(WORKER_ID)) { + setupSocket() +} diff --git a/packages/engine/test/handler/conditions-evaluate.test.ts b/packages/engine/test/handler/conditions-evaluate.test.ts new file mode 100644 index 0000000..adf13ba --- /dev/null +++ b/packages/engine/test/handler/conditions-evaluate.test.ts @@ -0,0 +1,312 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { BranchCondition, BranchOperator } from '@activepieces/shared' +import { evaluateConditions } from '../../src/lib/handler/router-executor' + +describe('Branch evaluateConditions', () => { + describe('DATE_IS_AFTER', () => { + test.each([ + null, + undefined, + 'not a date', + ])('should return false when one of the values is not a date %p', (value) => { + const condition: BranchCondition = { + firstValue: value as string, + secondValue: '2021-01-01', + operator: BranchOperator.DATE_IS_AFTER, + } + + expect(evaluateConditions([[condition]])).toEqual(false) + }) + + test('should return true when first date is after second date', () => { + const condition: BranchCondition = { + firstValue: '2021-01-02', + secondValue: '2021-01-01', + operator: BranchOperator.DATE_IS_AFTER, + } + + expect(evaluateConditions([[condition]])).toEqual(true) + }) + + test.each([ + '2021-01-01', + '2021-01-02', + ])('should return false when first date is before or equal to second date', (firstDate) => { + const condition: BranchCondition = { + firstValue: firstDate, + secondValue: '2021-01-02', + operator: BranchOperator.DATE_IS_AFTER, + } + + expect(evaluateConditions([[condition]])).toEqual(false) + }) + + test('should return false when the date is not in a supported format', () => { + const condition: BranchCondition = { + firstValue: '2021-01-02T00:00:00Z', + secondValue: '1st January 2021', + operator: BranchOperator.DATE_IS_AFTER, + } + + expect(evaluateConditions([[condition]])).toEqual(false) + }) + + test('should compare time', () => { + const condition: BranchCondition = { + firstValue: '2021-01-01T00:00:02Z', + secondValue: '2021-01-01T00:00:01Z', + operator: BranchOperator.DATE_IS_AFTER, + } + + expect(evaluateConditions([[condition]])).toEqual(true) + }) + }) + + describe('DATE_IS_BEFORE', () => { + test.each([ + null, + undefined, + 'not a date', + ])('should return false when one of the values is not a date %p', (value) => { + const condition: BranchCondition = { + firstValue: value as string, + secondValue: '2021-01-01', + operator: BranchOperator.DATE_IS_BEFORE, + } + + expect(evaluateConditions([[condition]])).toEqual(false) + }) + + test('should return true when first date is before second date', () => { + const condition: BranchCondition = { + firstValue: '2021-01-01', + secondValue: '2021-01-02', + operator: BranchOperator.DATE_IS_BEFORE, + } + + expect(evaluateConditions([[condition]])).toEqual(true) + }) + + test.each([ + '2021-01-01', + '2021-01-02', + ])('should return false when first date is after or equal to second date', (firstDate) => { + const condition: BranchCondition = { + firstValue: firstDate, + secondValue: '2021-01-01', + operator: BranchOperator.DATE_IS_BEFORE, + } + + expect(evaluateConditions([[condition]])).toEqual(false) + }) + + test('should return false when the date is not in a supported format', () => { + const condition: BranchCondition = { + firstValue: '2021-01-02T00:00:00Z', + secondValue: '2nd January 2021', + operator: BranchOperator.DATE_IS_BEFORE, + } + + expect(evaluateConditions([[condition]])).toEqual(false) + }) + + test('should compare time', () => { + const condition: BranchCondition = { + firstValue: '2021-01-01T00:00:01Z', + secondValue: '2021-01-01T00:00:02Z', + operator: BranchOperator.DATE_IS_BEFORE, + } + + expect(evaluateConditions([[condition]])).toEqual(true) + }) + }) + + describe('DATE_IS_EQUAL', () => { + test.each([ + null, + undefined, + 'not a date', + ])('should return false when one of the values is not a date %p', (value) => { + const condition: BranchCondition = { + firstValue: value as string, + secondValue: '2021-01-01', + operator: BranchOperator.DATE_IS_EQUAL, + } + + expect(evaluateConditions([[condition]])).toEqual(false) + }) + + test('should return true when first date is equal to second date', () => { + const condition: BranchCondition = { + firstValue: '2021-01-01', + secondValue: '2021-01-01', + operator: BranchOperator.DATE_IS_EQUAL, + } + + expect(evaluateConditions([[condition]])).toEqual(true) + }) + + test.each([ + '2021-01-01', + '2021-01-03', + ])('should return false when first date is after or before the second date', (firstDate) => { + const condition: BranchCondition = { + firstValue: firstDate, + secondValue: '2021-01-02', + operator: BranchOperator.DATE_IS_EQUAL, + } + + expect(evaluateConditions([[condition]])).toEqual(false) + }) + + test('should return false when the date is not in a supported format', () => { + const condition: BranchCondition = { + firstValue: '2021-01-02T00:00:00Z', + secondValue: '2nd January 2021', + operator: BranchOperator.DATE_IS_EQUAL, + } + + expect(evaluateConditions([[condition]])).toEqual(false) + }) + + test('should compare time', () => { + const condition: BranchCondition = { + firstValue: '2021-01-01T00:00:01Z', + secondValue: '2021-01-01T00:00:01Z', + operator: BranchOperator.DATE_IS_EQUAL, + } + + expect(evaluateConditions([[condition]])).toEqual(true) + }) + }) + + describe('LIST_IS_EMPTY', () => { + test.each([ + [], + '[]', + ])('should return true when list is empty %p', (input: any) => { + const condition: BranchCondition = { + firstValue: input, + operator: BranchOperator.LIST_IS_EMPTY, + } + + expect(evaluateConditions([[condition]])).toEqual(true) + }) + + test.each([ + [1], + '[1]', + ])('should return false when list is not empty %p', (input: any) => { + const condition: BranchCondition = { + firstValue: input, + operator: BranchOperator.LIST_IS_EMPTY, + } + + expect(evaluateConditions([[condition]])).toEqual(false) + }) + + test.each([ + null, + undefined, + 'not a list', + {}, + ])('should return false when the value is not a list %p', (input: any) => { + const condition: BranchCondition = { + firstValue: input, + operator: BranchOperator.LIST_IS_EMPTY, + } + + expect(evaluateConditions([[condition]])).toEqual(false) + }) + }) + + describe('LIST_IS_NOT_EMPTY', () => { + test.each([ + [1], + '[1]', + ])('should return true when list is not empty %p', (input: any) => { + const condition: BranchCondition = { + firstValue: input, + operator: BranchOperator.LIST_IS_NOT_EMPTY, + } + + expect(evaluateConditions([[condition]])).toEqual(true) + }) + + test.each([ + [], + '[]', + ])('should return false when list is empty %p', (input: any) => { + const condition: BranchCondition = { + firstValue: input, + operator: BranchOperator.LIST_IS_NOT_EMPTY, + } + + expect(evaluateConditions([[condition]])).toEqual(false) + }) + + test.each([ + null, + undefined, + 'not a list', + {}, + ])('should return false when the value is not a list %p', (input: any) => { + const condition: BranchCondition = { + firstValue: input, + operator: BranchOperator.LIST_IS_NOT_EMPTY, + } + + expect(evaluateConditions([[condition]])).toEqual(false) + }) + }) + + describe('LIST_CONTAINS', () => { + test.each([ + { expected: true, list: ['apple', 'banana', 'cherry'], value: 'banana', caseSensitive: false }, + { expected: false, list: ['apple', 'banana', 'cherry'], value: 'Banana', caseSensitive: true }, + { expected: true, list: ['apple', 'banana', 'cherry'], value: 'Banana', caseSensitive: false }, + { expected: true, list: '["apple", "banana", "cherry"]', value: 'banana', caseSensitive: false }, + { expected: true, list: 'apple', value: 'apple', caseSensitive: false }, + { expected: true, list: [1, 2, 3, 4, 5], value: '4', caseSensitive: false }, + { expected: true, list: [1, 2, 3, 4, 5], value: 4, caseSensitive: false }, + { expected: true, list: [true, false, true], value: 'true', caseSensitive: false }, + { expected: true, list: [true, false, true], value: true, caseSensitive: false }, + { expected: true, list: ['true', 'false', 'true'], value: true, caseSensitive: false }, + { expected: true, list: ['true', 'false', 'true'], value: 'true', caseSensitive: false }, + ])('should return $expected for list $list containing $value (case sensitive: $caseSensitive)', ({ expected, list, value, caseSensitive }) => { + const condition: BranchCondition = { + firstValue: list as any, + secondValue: value as any, + operator: BranchOperator.LIST_CONTAINS, + caseSensitive, + } + + expect(evaluateConditions([[condition]])).toEqual(expected) + }) + }) + + describe('LIST_DOES_NOT_CONTAIN', () => { + test.each([ + { expected: true, list: ['apple', 'banana', 'cherry'], value: 'grape', caseSensitive: false }, + { expected: true, list: ['apple', 'banana', 'cherry'], value: 'Banana', caseSensitive: true }, + { expected: false, list: ['apple', 'banana', 'cherry'], value: 'Banana', caseSensitive: false }, + { expected: true, list: '["apple", "banana", "cherry"]', value: 'grape', caseSensitive: false }, + { expected: true, list: 'apple', value: 'grape', caseSensitive: false }, + { expected: true, list: [1, 2, 3, 4, 5], value: '6', caseSensitive: false }, + { expected: true, list: [1, 2, 3, 4, 5], value: 6, caseSensitive: false }, + { expected: false, list: [true, false, true], value: 'false', caseSensitive: false }, + { expected: false, list: [true, false, true], value: false, caseSensitive: false }, + { expected: false, list: ['true', 'false', 'true'], value: false, caseSensitive: false }, + { expected: false, list: ['true', 'false', 'true'], value: 'false', caseSensitive: false }, + ])('should return $expected for list $list not containing $value (case sensitive: $caseSensitive)', ({ expected, list, value, caseSensitive }) => { + const condition: BranchCondition = { + firstValue: list as any, + secondValue: value as any, + operator: BranchOperator.LIST_DOES_NOT_CONTAIN, + caseSensitive, + } + + expect(evaluateConditions([[condition]])).toEqual(expected) + }) + }) +}) diff --git a/packages/engine/test/handler/flow-branching.test.ts b/packages/engine/test/handler/flow-branching.test.ts new file mode 100644 index 0000000..eb8a21e --- /dev/null +++ b/packages/engine/test/handler/flow-branching.test.ts @@ -0,0 +1,521 @@ +import { BranchCondition, BranchOperator, RouterExecutionType } from '@activepieces/shared' +import { ExecutionVerdict, FlowExecutorContext } from '../../src/lib/handler/context/flow-execution-context' +import { flowExecutor } from '../../src/lib/handler/flow-executor' +import { buildCodeAction, buildRouterWithOneCondition, generateMockEngineConstants } from './test-helper' + +function executeBranchActionWithOneCondition(condition: BranchCondition): Promise { + return flowExecutor.execute({ + action: buildRouterWithOneCondition({ + conditions: [condition], + executionType: RouterExecutionType.EXECUTE_FIRST_MATCH, + children: [ + buildCodeAction({ + name: 'echo_step', + input: { + condition: true, + }, + }), + buildCodeAction({ + name: 'echo_step_1', + input: { + condition: false, + }, + }), + ], + }), + executionState: FlowExecutorContext.empty(), + constants: generateMockEngineConstants(), + }) +} + +describe('flow with branching different branches', () => { + + it('should execute branch with text contains condition (case insensitive)', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.TEXT_CONTAINS, + firstValue: 'test', + secondValue: 'TeSt', + caseSensitive: false, + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with text does not contain condition (case insensitive)', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.TEXT_DOES_NOT_CONTAIN, + firstValue: 'test', + secondValue: 'ExAmPlE', + caseSensitive: false, + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with text exactly matches condition (case insensitive)', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'test', + secondValue: 'TeSt', + caseSensitive: false, + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with text does not exactly match condition (case insensitive)', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.TEXT_DOES_NOT_EXACTLY_MATCH, + firstValue: 'test', + secondValue: 'ExAmPlE', + caseSensitive: false, + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with text starts with condition (case insensitive)', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.TEXT_STARTS_WITH, + firstValue: 'test', + secondValue: 'tE', + caseSensitive: false, + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with text does not start with condition (case insensitive)', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.TEXT_DOES_NOT_START_WITH, + firstValue: 'test', + secondValue: 'eS', + caseSensitive: false, + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with text ends with condition (case insensitive)', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.TEXT_ENDS_WITH, + firstValue: 'test', + secondValue: 'sT', + caseSensitive: false, + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with text does not end with condition (case insensitive)', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.TEXT_DOES_NOT_END_WITH, + firstValue: 'test', + secondValue: 'eS', + caseSensitive: false, + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with text contains condition', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.TEXT_CONTAINS, + firstValue: 'test', + secondValue: 'test', + caseSensitive: true, + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with text does not contain condition', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.TEXT_DOES_NOT_CONTAIN, + firstValue: 'test', + secondValue: 'example', + caseSensitive: true, + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with text exactly matches condition', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'test', + secondValue: 'test', + caseSensitive: true, + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with text does not exactly match condition', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.TEXT_DOES_NOT_EXACTLY_MATCH, + firstValue: 'test', + secondValue: 'example', + caseSensitive: true, + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with text starts with condition', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.TEXT_STARTS_WITH, + firstValue: 'test', + secondValue: 'te', + caseSensitive: true, + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with text does not start with condition', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.TEXT_DOES_NOT_START_WITH, + firstValue: 'test', + secondValue: 'es', + caseSensitive: true, + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with text ends with condition', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.TEXT_ENDS_WITH, + firstValue: 'test', + secondValue: 'st', + caseSensitive: true, + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with text does not end with condition', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.TEXT_DOES_NOT_END_WITH, + firstValue: 'test', + secondValue: 'es', + caseSensitive: true, + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with exists condition', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.EXISTS, + firstValue: 'test', + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with does not exist condition', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.DOES_NOT_EXIST, + firstValue: '', + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with boolean is true condition', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.BOOLEAN_IS_TRUE, + firstValue: 'true', + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with boolean is false condition', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.BOOLEAN_IS_FALSE, + firstValue: '{{false}}', + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with two equal numbers', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.NUMBER_IS_EQUAL_TO, + firstValue: '1', + secondValue: '1', + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with the first number greater than the second one', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.NUMBER_IS_GREATER_THAN, + firstValue: '2', + secondValue: '1', + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should execute branch with the first number less than the second one', async () => { + const result = await executeBranchActionWithOneCondition( + { + operator: BranchOperator.NUMBER_IS_LESS_THAN, + firstValue: '1', + secondValue: '2', + }, + ) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router.output).toEqual({ + branches: [ + { + branchIndex: 1, + branchName: 'Test Branch', + evaluation: true, + }, + ], + }) + }) + + it('should skip router', async () => { + const result = await flowExecutor.execute({ + action: buildRouterWithOneCondition({ children: [ + buildCodeAction({ name: 'echo_step', input: {}, skip: true }), + ], conditions: [ + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'test', + secondValue: 'test', + caseSensitive: false, + }, + ], executionType: RouterExecutionType.EXECUTE_FIRST_MATCH, skip: true }), executionState: FlowExecutorContext.empty(), constants: generateMockEngineConstants(), + }) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router).toBeUndefined() + }) +}) \ No newline at end of file diff --git a/packages/engine/test/handler/flow-codes.test.ts b/packages/engine/test/handler/flow-codes.test.ts new file mode 100644 index 0000000..8682697 --- /dev/null +++ b/packages/engine/test/handler/flow-codes.test.ts @@ -0,0 +1,68 @@ +import { Action } from '@activepieces/shared' +import { codeExecutor } from '../../src/lib/handler/code-executor' +import { ExecutionVerdict, FlowExecutorContext } from '../../src/lib/handler/context/flow-execution-context' +import { flowExecutor } from '../../src/lib/handler/flow-executor' +import { buildCodeAction, generateMockEngineConstants } from './test-helper' + +describe('codeExecutor', () => { + + it('should execute code that echo parameters action successfully', async () => { + const result = await codeExecutor.handle({ + action: buildCodeAction({ + name: 'echo_step', + input: { + 'key': '{{ 1 + 2 }}', + }, + }), executionState: FlowExecutorContext.empty(), constants: generateMockEngineConstants(), + }) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.echo_step.output).toEqual({ 'key': 3 }) + }) + + it('should execute code a code that throws an error', async () => { + const result = await codeExecutor.handle({ + action: buildCodeAction({ + name: 'runtime', + input: {}, + }), executionState: FlowExecutorContext.empty(), constants: generateMockEngineConstants(), + }) + expect(result.verdict).toBe(ExecutionVerdict.FAILED) + expect(result.steps.runtime.status).toEqual('FAILED') + expect(result.steps.runtime.errorMessage).toEqual('Custom Runtime Error') + }) + + it('should skip code action', async () => { + const result = await flowExecutor.execute({ + action: buildCodeAction({ + name: 'echo_step', + input: {}, + skip: true, + }), executionState: FlowExecutorContext.empty(), constants: generateMockEngineConstants(), + }) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.echo_step).toBeUndefined() + }) + it('should skip flow action', async () => { + const flow: Action = { + ...buildCodeAction({ + name: 'echo_step', + skip: true, + input: {}, + }), + nextAction: { + ...buildCodeAction({ + name: 'echo_step_1', + input: { + 'key': '{{ 1 + 2 }}', + }, + }), + }, + } + const result = await flowExecutor.execute({ + action: flow, executionState: FlowExecutorContext.empty(), constants: generateMockEngineConstants(), + }) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.echo_step).toBeUndefined() + expect(result.steps.echo_step_1.output).toEqual({ 'key': 3 }) + }) +}) diff --git a/packages/engine/test/handler/flow-error-handling.test.ts b/packages/engine/test/handler/flow-error-handling.test.ts new file mode 100644 index 0000000..acf1981 --- /dev/null +++ b/packages/engine/test/handler/flow-error-handling.test.ts @@ -0,0 +1,76 @@ + +import { codeExecutor } from '../../src/lib/handler/code-executor' +import { ExecutionVerdict, FlowExecutorContext } from '../../src/lib/handler/context/flow-execution-context' +import { pieceExecutor } from '../../src/lib/handler/piece-executor' +import { buildCodeAction, buildPieceAction, generateMockEngineConstants } from './test-helper' + +describe('code piece with error handling', () => { + + it('should continue on failure when execute code a code that throws an error', async () => { + const result = await codeExecutor.handle({ + action: buildCodeAction({ + name: 'runtime', + input: {}, + errorHandlingOptions: { + continueOnFailure: { + value: true, + }, + retryOnFailure: { + value: false, + }, + }, + }), executionState: FlowExecutorContext.empty(), constants: generateMockEngineConstants(), + }) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.runtime.status).toEqual('FAILED') + expect(result.steps.runtime.errorMessage).toEqual('Custom Runtime Error') + }) + +}) + +describe('piece with error handling', () => { + + it('should continue on failure when piece fails', async () => { + const result = await pieceExecutor.handle({ + action: buildPieceAction({ + name: 'send_http', + pieceName: '@activepieces/piece-http', + actionName: 'send_request', + input: { + 'method': 'POST', + 'url': 'https://cloud.activepieces.com/api/v1/flags', + 'headers': {}, + 'queryParams': {}, + 'body_type': 'none', + 'body': {}, + }, + errorHandlingOptions: { + continueOnFailure: { + value: true, + }, + retryOnFailure: { + value: false, + }, + }, + }), executionState: FlowExecutorContext.empty(), constants: generateMockEngineConstants(), + }) + + const expectedError = { + response: { + status: 404, + body: { + statusCode: 404, + error: 'Not Found', + message: 'Route not found', + }, + }, + request: {}, + } + + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.send_http.status).toBe('FAILED') + expect(result.steps.send_http.errorMessage).toEqual(JSON.stringify(expectedError)) + + }, 10000) + +}) diff --git a/packages/engine/test/handler/flow-looping.test.ts b/packages/engine/test/handler/flow-looping.test.ts new file mode 100644 index 0000000..3a1d7e5 --- /dev/null +++ b/packages/engine/test/handler/flow-looping.test.ts @@ -0,0 +1,91 @@ +import { Action, LoopStepOutput } from '@activepieces/shared' +import { ExecutionVerdict, FlowExecutorContext } from '../../src/lib/handler/context/flow-execution-context' +import { flowExecutor } from '../../src/lib/handler/flow-executor' +import { buildCodeAction, buildSimpleLoopAction, generateMockEngineConstants } from './test-helper' + + +describe('flow with looping', () => { + + it('should execute iterations', async () => { + const codeAction = buildCodeAction({ + name: 'echo_step', + input: { + 'index': '{{loop.index}}', + }, + }) + const result = await flowExecutor.execute({ + action: buildSimpleLoopAction({ + name: 'loop', + loopItems: '{{ [4,5,6] }}', + firstLoopAction: codeAction, + }), + executionState: FlowExecutorContext.empty(), + constants: generateMockEngineConstants(), + }) + + const loopOut = result.steps.loop as LoopStepOutput + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(loopOut.output?.iterations.length).toBe(3) + expect(loopOut.output?.index).toBe(3) + expect(loopOut.output?.item).toBe(6) + }) + + it('should execute iterations and fail on first iteration', async () => { + const generateArray = buildCodeAction({ + name: 'echo_step', + input: { + 'array': '{{ [4,5,6] }}', + }, + nextAction: buildSimpleLoopAction({ + name: 'loop', + loopItems: '{{ echo_step.array }}', + firstLoopAction: buildCodeAction({ + name: 'runtime', + input: {}, + }), + }), + }) + const result = await flowExecutor.execute({ + action: generateArray, + executionState: FlowExecutorContext.empty(), + constants: generateMockEngineConstants(), + }) + + const loopOut = result.steps.loop as LoopStepOutput + expect(result.verdict).toBe(ExecutionVerdict.FAILED) + expect(loopOut.output?.iterations.length).toBe(1) + expect(loopOut.output?.index).toBe(1) + expect(loopOut.output?.item).toBe(4) + }) + + it('should skip loop', async () => { + const result = await flowExecutor.execute({ + action: buildSimpleLoopAction({ name: 'loop', loopItems: '{{ [4,5,6] }}', skip: true }), executionState: FlowExecutorContext.empty(), constants: generateMockEngineConstants(), + }) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.loop).toBeUndefined() + }) + + it('should skip loop in flow', async () => { + const flow: Action = { + ...buildSimpleLoopAction({ name: 'loop', loopItems: '{{ [4,5,6] }}', skip: true }), + nextAction: { + ...buildCodeAction({ + name: 'echo_step', + skip: false, + input: { + 'key': '{{ 1 + 2 }}', + }, + }), + nextAction: undefined, + }, + } + const result = await flowExecutor.execute({ + action: flow, executionState: FlowExecutorContext.empty(), constants: generateMockEngineConstants(), + }) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.loop).toBeUndefined() + expect(result.steps.echo_step.output).toEqual({ 'key': 3 }) + }) + +}) diff --git a/packages/engine/test/handler/flow-piece.test.ts b/packages/engine/test/handler/flow-piece.test.ts new file mode 100644 index 0000000..8ed7c8d --- /dev/null +++ b/packages/engine/test/handler/flow-piece.test.ts @@ -0,0 +1,103 @@ +import { Action } from '@activepieces/shared' +import { ExecutionVerdict, FlowExecutorContext } from '../../src/lib/handler/context/flow-execution-context' +import { flowExecutor } from '../../src/lib/handler/flow-executor' +import { pieceExecutor } from '../../src/lib/handler/piece-executor' +import { buildPieceAction, generateMockEngineConstants } from './test-helper' + +describe('pieceExecutor', () => { + + it('should execute data mapper successfully', async () => { + const result = await pieceExecutor.handle({ + action: buildPieceAction({ + name: 'data_mapper', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 2 }}', + }, + }, + }), executionState: FlowExecutorContext.empty(), constants: generateMockEngineConstants(), + }) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.data_mapper.output).toEqual({ 'key': 3 }) + }) + + it('should execute fail gracefully when pieces fail', async () => { + const result = await pieceExecutor.handle({ + action: buildPieceAction({ + name: 'send_http', + pieceName: '@activepieces/piece-http', + actionName: 'send_request', + input: { + 'url': 'https://cloud.activepieces.com/api/v1/asd', + 'method': 'GET', + 'headers': {}, + 'body_type': 'none', + 'body': {}, + 'queryParams': {}, + }, + }), executionState: FlowExecutorContext.empty(), constants: generateMockEngineConstants(), + }) + + const expectedError = { + response: { + status: 404, + body: { + statusCode: 404, + error: 'Not Found', + message: 'Route not found', + }, + }, + request: {}, + } + + expect(result.verdict).toBe(ExecutionVerdict.FAILED) + expect(result.steps.send_http.status).toBe('FAILED') + expect(result.steps.send_http.errorMessage).toEqual(JSON.stringify(expectedError)) + }, 10000) + it('should skip piece action', async () => { + const result = await flowExecutor.execute({ + action: buildPieceAction({ + name: 'data_mapper', + input: {}, + skip: true, + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + }), executionState: FlowExecutorContext.empty(), constants: generateMockEngineConstants(), + }) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.data_mapper).toBeUndefined() + }) + it('should skip piece action in flow', async () => { + const flow: Action = { + ...buildPieceAction({ + name: 'data_mapper', + input: { + mapping: { + 'key': '{{ 1 + 2 }}', + }, + }, + skip: false, + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + }), + nextAction: { + ...buildPieceAction({ + name: 'send_http', + pieceName: '@activepieces/piece-http', + actionName: 'send_request', + input: {}, + skip: true, + }), + nextAction: undefined, + }, + } + const result = await flowExecutor.execute({ + action: flow, executionState: FlowExecutorContext.empty(), constants: generateMockEngineConstants(), + }) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.data_mapper.output).toEqual({ 'key': 3 }) + expect(result.steps.send_http).toBeUndefined() + }) +}) diff --git a/packages/engine/test/handler/flow-rerun.test.ts b/packages/engine/test/handler/flow-rerun.test.ts new file mode 100644 index 0000000..09b362d --- /dev/null +++ b/packages/engine/test/handler/flow-rerun.test.ts @@ -0,0 +1,58 @@ +import { ExecutionVerdict, FlowExecutorContext } from '../../src/lib/handler/context/flow-execution-context' +import { flowExecutor } from '../../src/lib/handler/flow-executor' +import { buildPieceAction, generateMockEngineConstants } from './test-helper' + +const failedHttpAction = buildPieceAction({ + name: 'send_http', + pieceName: '@activepieces/piece-http', + actionName: 'send_request', + input: { + 'url': 'https://cloud.activepieces.com/api/v1/asd', + 'method': 'GET', + 'headers': {}, + 'body_type': 'none', + 'body': {}, + 'queryParams': {}, + }, +}) + +const successHttpAction = buildPieceAction({ + name: 'send_http', + pieceName: '@activepieces/piece-http', + actionName: 'send_request', + input: { + 'url': 'https://cloud.activepieces.com/api/v1/pieces', + 'method': 'GET', + 'headers': {}, + 'body_type': 'none', + 'body': {}, + 'queryParams': {}, + }, +}) + + +describe('flow retry', () => { + const context = FlowExecutorContext.empty() + it('should retry entire flow', async () => { + const failedResult = await flowExecutor.execute({ + action: failedHttpAction, executionState: context, constants: generateMockEngineConstants(), + }) + const retryEntireFlow = await flowExecutor.execute({ + action: successHttpAction, executionState: context, constants: generateMockEngineConstants(), + }) + expect(failedResult.verdict).toBe(ExecutionVerdict.FAILED) + expect(retryEntireFlow.verdict).toBe(ExecutionVerdict.RUNNING) + }) + + it('should retry flow from failed step', async () => { + const failedResult = await flowExecutor.execute({ + action: failedHttpAction, executionState: context, constants: generateMockEngineConstants(), + }) + + const retryFromFailed = await flowExecutor.execute({ + action: successHttpAction, executionState: context, constants: generateMockEngineConstants({}), + }) + expect(failedResult.verdict).toBe(ExecutionVerdict.FAILED) + expect(retryFromFailed.verdict).toBe(ExecutionVerdict.RUNNING) + }) +}) diff --git a/packages/engine/test/handler/flow-with-pause.test.ts b/packages/engine/test/handler/flow-with-pause.test.ts new file mode 100644 index 0000000..0e41ced --- /dev/null +++ b/packages/engine/test/handler/flow-with-pause.test.ts @@ -0,0 +1,197 @@ +import { BranchOperator, LoopStepOutput, RouterExecutionType, RouterStepOutput } from '@activepieces/shared' +import { ExecutionVerdict, FlowExecutorContext } from '../../src/lib/handler/context/flow-execution-context' +import { StepExecutionPath } from '../../src/lib/handler/context/step-execution-path' +import { flowExecutor } from '../../src/lib/handler/flow-executor' +import { buildCodeAction, buildPieceAction, buildRouterWithOneCondition, buildSimpleLoopAction, generateMockEngineConstants } from './test-helper' + + +const simplePauseFlow = buildPieceAction({ + name: 'approval', + pieceName: '@activepieces/piece-approval', + actionName: 'wait_for_approval', + input: {}, + nextAction: buildCodeAction({ + name: 'echo_step', + input: {}, + }), +}) + +const flawWithTwoPause = buildPieceAction({ + name: 'approval', + pieceName: '@activepieces/piece-approval', + actionName: 'wait_for_approval', + input: {}, + nextAction: buildCodeAction({ + name: 'echo_step', + input: {}, + nextAction: buildPieceAction({ + name: 'approval-1', + pieceName: '@activepieces/piece-approval', + actionName: 'wait_for_approval', + input: {}, + nextAction: buildCodeAction({ + name: 'echo_step_1', + input: {}, + }), + }), + + }), +}) + + +const pauseFlowWithLoopAndBranch = buildSimpleLoopAction({ + name: 'loop', + loopItems: '{{ [false, true ] }}', + firstLoopAction: buildRouterWithOneCondition({ + conditions: [ + { + operator: BranchOperator.BOOLEAN_IS_TRUE, + firstValue: '{{ loop.item }}', + }, + + ], + executionType: RouterExecutionType.EXECUTE_FIRST_MATCH, + children: [ + simplePauseFlow, + ], + }), +}) + +describe('flow with pause', () => { + + it('should pause and resume successfully with loops and branch', async () => { + const pauseResult = await flowExecutor.execute({ + action: pauseFlowWithLoopAndBranch, + executionState: FlowExecutorContext.empty().setPauseRequestId('requestId'), + constants: generateMockEngineConstants(), + }) + expect(pauseResult.verdict).toBe(ExecutionVerdict.PAUSED) + expect(pauseResult.verdictResponse).toEqual({ + 'pauseMetadata': { + response: {}, + requestId: 'requestId', + 'type': 'WEBHOOK', + }, + 'reason': 'PAUSED', + }) + expect(Object.keys(pauseResult.steps)).toEqual(['loop']) + + // Verify that the first iteration (true) triggered the branch condition + const loopOutputBeforeResume = pauseResult.steps.loop as LoopStepOutput + expect(loopOutputBeforeResume.output?.iterations.length).toBe(2) + expect(loopOutputBeforeResume.output?.item).toBe(true) + expect(Object.keys(loopOutputBeforeResume.output?.iterations[0] ?? {})).toContain('router') + + + const resumeResultTwo = await flowExecutor.execute({ + action: pauseFlowWithLoopAndBranch, + executionState: pauseResult.setCurrentPath(StepExecutionPath.empty()).setVerdict(ExecutionVerdict.RUNNING, undefined), + constants: generateMockEngineConstants({ + resumePayload: { + queryParams: { + action: 'approve', + }, + body: {}, + headers: {}, + }, + }), + }) + + expect(resumeResultTwo.verdict).toBe(ExecutionVerdict.RUNNING) + expect(Object.keys(resumeResultTwo.steps)).toEqual(['loop']) + + const loopOut = resumeResultTwo.steps.loop as LoopStepOutput + expect(Object.keys(loopOut.output?.iterations[1] ?? {})).toEqual(['router', 'approval', 'echo_step']) + expect((loopOut.output?.iterations[0].router as RouterStepOutput).output?.branches[0].evaluation).toBe(false) + expect((loopOut.output?.iterations[1].router as RouterStepOutput).output?.branches[0].evaluation).toBe(true) + + + }) + + it('should pause and resume with two different steps in same flow successfully', async () => { + const pauseResult1 = await flowExecutor.execute({ + action: flawWithTwoPause, + executionState: FlowExecutorContext.empty().setPauseRequestId('requestId'), + constants: generateMockEngineConstants(), + }) + const resumeResult1 = await flowExecutor.execute({ + action: flawWithTwoPause, + executionState: pauseResult1, + constants: generateMockEngineConstants({ + resumePayload: { + queryParams: { + action: 'approve', + }, + body: {}, + headers: {}, + }, + }), + }) + expect(resumeResult1.verdict).toBe(ExecutionVerdict.PAUSED) + expect(resumeResult1.verdictResponse).toEqual({ + 'pauseMetadata': { + response: {}, + requestId: 'requestId', + 'type': 'WEBHOOK', + }, + 'reason': 'PAUSED', + }) + const resumeResult2 = await flowExecutor.execute({ + action: flawWithTwoPause, + executionState: resumeResult1.setVerdict(ExecutionVerdict.RUNNING, undefined), + constants: generateMockEngineConstants({ + resumePayload: { + queryParams: { + action: 'approve', + }, + body: {}, + headers: {}, + }, + }), + }) + expect(resumeResult2.verdict).toBe(ExecutionVerdict.RUNNING) + + }) + + + it('should pause and resume successfully', async () => { + const pauseResult = await flowExecutor.execute({ + action: simplePauseFlow, + executionState: FlowExecutorContext.empty().setPauseRequestId('requestId'), + constants: generateMockEngineConstants(), + }) + expect(pauseResult.verdict).toBe(ExecutionVerdict.PAUSED) + expect(pauseResult.verdictResponse).toEqual({ + 'pauseMetadata': { + response: {}, + requestId: 'requestId', + 'type': 'WEBHOOK', + }, + 'reason': 'PAUSED', + }) + const currentState = pauseResult.currentState() + expect(Object.keys(currentState).length).toBe(1) + + const resumeResult = await flowExecutor.execute({ + action: simplePauseFlow, + executionState: pauseResult, + constants: generateMockEngineConstants({ + resumePayload: { + queryParams: { + action: 'approve', + }, + body: {}, + headers: {}, + }, + }), + }) + expect(resumeResult.verdict).toBe(ExecutionVerdict.RUNNING) + expect(resumeResult.currentState()).toEqual({ + 'approval': { + approved: true, + }, + echo_step: {}, + }) + }) + +}) diff --git a/packages/engine/test/handler/flow-with-response.test.ts b/packages/engine/test/handler/flow-with-response.test.ts new file mode 100644 index 0000000..ffb5368 --- /dev/null +++ b/packages/engine/test/handler/flow-with-response.test.ts @@ -0,0 +1,48 @@ +import { FlowRunStatus } from '@activepieces/shared' +import { ExecutionVerdict, FlowExecutorContext } from '../../src/lib/handler/context/flow-execution-context' +import { flowExecutor } from '../../src/lib/handler/flow-executor' +import { buildPieceAction, generateMockEngineConstants } from './test-helper' + +describe('flow with response', () => { + + it('should execute return response successfully', async () => { + const input = { + responseType: 'json', + fields: { + status: 200, + headers: { + 'random': 'header', + }, + body: { + 'hello': 'world', + }, + }, + respond: 'stop', + } + const response = { + status: 200, + headers: { + 'random': 'header', + }, + body: { + 'hello': 'world', + }, + } + + const result = await flowExecutor.execute({ + action: buildPieceAction({ + name: 'http', + pieceName: '@activepieces/piece-webhook', + actionName: 'return_response', + input, + }), executionState: FlowExecutorContext.empty(), constants: generateMockEngineConstants(), + }) + expect(result.verdict).toBe(ExecutionVerdict.SUCCEEDED) + expect(result.verdictResponse).toEqual({ + reason: FlowRunStatus.STOPPED, + stopResponse: response, + }) + expect(result.steps.http.output).toEqual(response) + }) + +}) diff --git a/packages/engine/test/handler/router-branching.test.ts b/packages/engine/test/handler/router-branching.test.ts new file mode 100755 index 0000000..6326bfa --- /dev/null +++ b/packages/engine/test/handler/router-branching.test.ts @@ -0,0 +1,415 @@ +import { Action, BranchCondition, BranchOperator, RouterExecutionType } from '@activepieces/shared' +import { ExecutionVerdict, FlowExecutorContext } from '../../src/lib/handler/context/flow-execution-context' +import { flowExecutor } from '../../src/lib/handler/flow-executor' +import { buildCodeAction, buildPieceAction, buildRouterWithOneCondition, generateMockEngineConstants } from './test-helper' + +function executeRouterActionWithOneCondition(children: Action[], conditions: (BranchCondition | null)[], executionType: RouterExecutionType): Promise { + return flowExecutor.execute({ + action: buildRouterWithOneCondition({ + children, + conditions, + executionType, + }), + executionState: FlowExecutorContext.empty(), + constants: generateMockEngineConstants(), + }) +} +describe('router with branching different conditions', () => { + it('should execute router with the first matching condition', async () => { + const result = await executeRouterActionWithOneCondition([ + buildPieceAction({ + name: 'data_mapper', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 2 }}', + }, + }, + }), + buildPieceAction({ + name: 'data_mapper_1', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 2 }}', + }, + }, + }), + ], [ + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'test', + secondValue: 'test', + caseSensitive: false, + }, + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'test', + secondValue: 'anything', + caseSensitive: false, + }, + ], RouterExecutionType.EXECUTE_FIRST_MATCH) + + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.data_mapper.output).toEqual({ 'key': 3 }) + expect(result.steps.data_mapper_1).toBeUndefined() + }) + + it('should execute router with the all matching conditions', async () => { + const result = await executeRouterActionWithOneCondition([ + buildPieceAction({ + name: 'data_mapper', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 2 }}', + }, + }, + }), + buildPieceAction({ + name: 'data_mapper_1', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 2 }}', + }, + }, + }), + ], [ + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'test', + secondValue: 'test', + caseSensitive: false, + }, + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'test', + secondValue: 'test', + caseSensitive: false, + }, + ], RouterExecutionType.EXECUTE_ALL_MATCH) + + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.data_mapper.output).toEqual({ 'key': 3 }) + expect(result.steps.data_mapper_1.output).toEqual({ 'key': 3 }) + }) + + it('should execute router but no branch will match', async () => { + const result = await executeRouterActionWithOneCondition([ + buildPieceAction({ + name: 'data_mapper', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 2 }}', + }, + }, + }), + buildPieceAction({ + name: 'data_mapper_1', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 5 }}', + }, + }, + }), + ], [ + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'abc', + secondValue: 'test', + caseSensitive: false, + }, + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'test', + secondValue: 'fasc', + caseSensitive: false, + }, + ], RouterExecutionType.EXECUTE_ALL_MATCH) + + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + const routerOutput = result.steps.router.output as { branches: boolean[] } + expect(routerOutput.branches).toEqual([ + { + branchName: 'Test Branch', + branchIndex: 1, + evaluation: false, + }, + { + branchName: 'Test Branch', + branchIndex: 2, + evaluation: false, + }, + ]) + expect(result.steps.data_mapper).toBeUndefined() + expect(result.steps.data_mapper_1).toBeUndefined() + }) + + it('should execute fallback branch with first match execution type', async () => { + const result = await executeRouterActionWithOneCondition([ + buildPieceAction({ + name: 'data_mapper', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 2 }}', + }, + }, + }), + buildPieceAction({ + name: 'data_mapper_1', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 5 }}', + }, + }, + }), + buildPieceAction({ + name: 'fallback_mapper', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 10 }}', + }, + }, + }), + ], [ + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'abc', + secondValue: 'test', + caseSensitive: false, + }, + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'test', + secondValue: 'fasc', + caseSensitive: false, + }, + null, // Fallback branch + ], RouterExecutionType.EXECUTE_FIRST_MATCH) + + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.data_mapper).toBeUndefined() + expect(result.steps.data_mapper_1).toBeUndefined() + expect(result.steps.fallback_mapper.output).toEqual({ 'key': 11 }) + }) + + it('should execute fallback branch with all match execution type', async () => { + const result = await executeRouterActionWithOneCondition([ + buildPieceAction({ + name: 'data_mapper', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 2 }}', + }, + }, + }), + buildPieceAction({ + name: 'data_mapper_1', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 5 }}', + }, + }, + }), + buildPieceAction({ + name: 'fallback_mapper', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 10 }}', + }, + }, + }), + ], [ + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'abc', + secondValue: 'test', + caseSensitive: false, + }, + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'test', + secondValue: 'fasc', + caseSensitive: false, + }, + null, // Fallback branch + ], RouterExecutionType.EXECUTE_ALL_MATCH) + + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.data_mapper).toBeUndefined() + expect(result.steps.data_mapper_1).toBeUndefined() + expect(result.steps.fallback_mapper.output).toEqual({ 'key': 11 }) + }) + + it('should not execute fallback branch when there is a matching condition in EXECUTE_FIRST_MATCH mode', async () => { + const result = await executeRouterActionWithOneCondition([ + buildPieceAction({ + name: 'data_mapper', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 2 }}', + }, + }, + }), + buildPieceAction({ + name: 'fallback_mapper', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 10 }}', + }, + }, + }), + ], [ + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'test', + secondValue: 'test', + caseSensitive: false, + }, + null, // Fallback branch + ], RouterExecutionType.EXECUTE_FIRST_MATCH) + + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.data_mapper.output).toEqual({ 'key': 3 }) + expect(result.steps.fallback_mapper).toBeUndefined() + }) + + it('should not execute fallback branch when there is a matching condition in EXECUTE_ALL_MATCH mode', async () => { + const result = await executeRouterActionWithOneCondition([ + buildPieceAction({ + name: 'data_mapper', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 2 }}', + }, + }, + }), + buildPieceAction({ + name: 'data_mapper_1', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 5 }}', + }, + }, + }), + buildPieceAction({ + name: 'fallback_mapper', + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: { + mapping: { + 'key': '{{ 1 + 10 }}', + }, + }, + }), + ], [ + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'test', + secondValue: 'test', + caseSensitive: false, + }, + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'test', + secondValue: 'test', + caseSensitive: false, + }, + null, // Fallback branch + ], RouterExecutionType.EXECUTE_ALL_MATCH) + + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.data_mapper.output).toEqual({ 'key': 3 }) + expect(result.steps.data_mapper_1.output).toEqual({ 'key': 6 }) + expect(result.steps.fallback_mapper).toBeUndefined() + }) + it('should skip router', async () => { + const result = await flowExecutor.execute({ + action: buildRouterWithOneCondition({ children: [ + buildPieceAction({ + name: 'data_mapper', + skip: true, + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: {}, + }), + ], conditions: [ + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'test', + secondValue: 'test', + caseSensitive: false, + }, + ], executionType: RouterExecutionType.EXECUTE_FIRST_MATCH, skip: true }), executionState: FlowExecutorContext.empty(), constants: generateMockEngineConstants(), + }) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router).toBeUndefined() + }) + it('should skip router action in flow', async () => { + const router: Action = { + ...buildRouterWithOneCondition({ children: [ + buildPieceAction({ + name: 'data_mapper', + skip: true, + pieceName: '@activepieces/piece-data-mapper', + actionName: 'advanced_mapping', + input: {}, + }), + ], conditions: [ + { + operator: BranchOperator.TEXT_EXACTLY_MATCHES, + firstValue: 'test', + secondValue: 'test', + caseSensitive: false, + }, + ], + executionType: RouterExecutionType.EXECUTE_FIRST_MATCH, + skip: true }), + nextAction: { + ...buildCodeAction({ + name: 'echo_step', + skip: false, + input: { + 'key': '{{ 1 + 2 }}', + }, + }), + nextAction: undefined, + }, + } + const result = await flowExecutor.execute({ + action: router, executionState: FlowExecutorContext.empty(), constants: generateMockEngineConstants(), + }) + expect(result.verdict).toBe(ExecutionVerdict.RUNNING) + expect(result.steps.router).toBeUndefined() + expect(result.steps.echo_step.output).toEqual({ 'key': 3 }) + }) +}) diff --git a/packages/engine/test/handler/test-helper.ts b/packages/engine/test/handler/test-helper.ts new file mode 100644 index 0000000..955625c --- /dev/null +++ b/packages/engine/test/handler/test-helper.ts @@ -0,0 +1,125 @@ +import { Action, ActionErrorHandlingOptions, ActionType, BranchCondition, BranchExecutionType, CodeAction, FlowVersionState, LoopOnItemsAction, PackageType, PieceAction, PieceType, ProgressUpdateType, RouterExecutionType, RunEnvironment } from '@activepieces/shared' +import { EngineConstants } from '../../src/lib/handler/context/engine-constants' +import { createPropsResolver } from '../../src/lib/variables/props-resolver' + +export const generateMockEngineConstants = (params?: Partial): EngineConstants => { + return new EngineConstants( + params?.flowId ?? 'flowId', + params?.flowVersionId ?? 'flowVersionId', + params?.flowVersionState ?? FlowVersionState.DRAFT, + params?.flowRunId ?? 'flowRunId', + params?.publicApiUrl ?? 'http://127.0.0.1:4200/api/', + params?.internalApiUrl ?? 'http://127.0.0.1:3000/', + params?.retryConstants ?? { + maxAttempts: 2, + retryExponential: 1, + retryInterval: 1, + }, + params?.engineToken ?? 'engineToken', + params?.projectId ?? 'projectId', + params?.propsResolver ?? createPropsResolver({ + projectId: 'projectId', + engineToken: 'engineToken', + apiUrl: 'http://127.0.0.1:3000', + }), + params?.testSingleStepMode ?? false, + params?.progressUpdateType ?? ProgressUpdateType.NONE, + params?.serverHandlerId ?? null, + params?.httpRequestId ?? null, + params?.resumePayload, + params?.runEnvironment ?? RunEnvironment.TESTING, + ) +} + +export function buildSimpleLoopAction({ + name, + loopItems, + firstLoopAction, + skip, +}: { + name: string + loopItems: string + firstLoopAction?: Action + skip?: boolean +}): LoopOnItemsAction { + return { + name, + displayName: 'Loop', + type: ActionType.LOOP_ON_ITEMS, + skip: skip ?? false, + settings: { + items: loopItems, + inputUiInfo: {}, + }, + firstLoopAction, + valid: true, + } +} + +export function buildRouterWithOneCondition({ children, conditions, executionType, skip }: { children: Action[], conditions: (BranchCondition | null)[], executionType: RouterExecutionType, skip?: boolean }): Action { + return { + name: 'router', + displayName: 'Your Router Name', + type: ActionType.ROUTER, + skip: skip ?? false, + settings: { + branches: conditions.map((condition) => { + if (condition === null) { + return { + branchType: BranchExecutionType.FALLBACK, + branchName: 'Fallback Branch', + } + } + return { + conditions: [[condition]], + branchType: BranchExecutionType.CONDITION, + branchName: 'Test Branch', + } + }), + executionType, + inputUiInfo: {}, + }, + children, + valid: true, + } +} + +export function buildCodeAction({ name, input, skip, nextAction, errorHandlingOptions }: { name: 'echo_step' | 'runtime' | 'echo_step_1', input: Record, skip?: boolean, errorHandlingOptions?: ActionErrorHandlingOptions, nextAction?: Action }): CodeAction { + return { + name, + displayName: 'Your Action Name', + type: ActionType.CODE, + skip: skip ?? false, + settings: { + input, + sourceCode: { + packageJson: '', + code: '', + }, + errorHandlingOptions, + }, + nextAction, + valid: true, + } +} + +export function buildPieceAction({ name, input, skip, pieceName, actionName, nextAction, errorHandlingOptions }: { errorHandlingOptions?: ActionErrorHandlingOptions, name: string, input: Record, skip?: boolean, pieceName: string, actionName: string, nextAction?: Action }): PieceAction { + return { + name, + displayName: 'Your Action Name', + type: ActionType.PIECE, + skip: skip ?? false, + settings: { + input, + pieceName, + packageType: PackageType.REGISTRY, + pieceVersion: '1.0.0', // Not required since it's running in development mode + pieceType: PieceType.OFFICIAL, + actionName, + inputUiInfo: {}, + errorHandlingOptions, + }, + nextAction, + valid: true, + } +} diff --git a/packages/engine/test/helper/error-handling.test.ts b/packages/engine/test/helper/error-handling.test.ts new file mode 100644 index 0000000..cef3be8 --- /dev/null +++ b/packages/engine/test/helper/error-handling.test.ts @@ -0,0 +1,82 @@ +import { ExecutionVerdict, FlowExecutorContext } from '../../src/lib/handler/context/flow-execution-context' +import { runWithExponentialBackoff } from '../../src/lib/helper/error-handling' +import { buildCodeAction, generateMockEngineConstants } from '../handler/test-helper' + +describe('runWithExponentialBackoff', () => { + const executionState = FlowExecutorContext.empty() + const action = buildCodeAction({ + name: 'runtime', + input: {}, + errorHandlingOptions: { + continueOnFailure: { + value: false, + }, + retryOnFailure: { + value: true, + }, + }, + }) + const constants = generateMockEngineConstants() + const requestFunction = jest.fn() + + beforeEach(() => { + jest.clearAllMocks() + }) + + afterAll(() => { + jest.clearAllMocks() + }) + + it('should return resultExecutionState when verdict is not FAILED', async () => { + const resultExecutionState = FlowExecutorContext.empty().setVerdict(ExecutionVerdict.SUCCEEDED, undefined) + requestFunction.mockResolvedValue(resultExecutionState) + + const output = await runWithExponentialBackoff(executionState, action, constants, requestFunction) + + expect(output).toEqual(resultExecutionState) + expect(requestFunction).toHaveBeenCalledWith({ action, executionState, constants }) + }) + + + it('should retry and return resultExecutionState when verdict is FAILED and retry is enabled', async () => { + const resultExecutionState = FlowExecutorContext.empty().setVerdict(ExecutionVerdict.FAILED, undefined) + + requestFunction.mockResolvedValue(resultExecutionState) + + const output = await runWithExponentialBackoff(executionState, action, constants, requestFunction) + + expect(output).toEqual(resultExecutionState) + // Mock applies for the first attempt and second attempt is a real call which return success + expect(requestFunction).toHaveBeenCalledTimes(2) + expect(requestFunction).toHaveBeenCalledWith({ action, executionState, constants }) + expect(requestFunction).toHaveBeenCalledWith({ action, executionState, constants }) + }) + + it('should not retry and return resultExecutionState when verdict is FAILED but retry is disabled', async () => { + const resultExecutionState = FlowExecutorContext.empty().setVerdict(ExecutionVerdict.FAILED, undefined) + + requestFunction.mockResolvedValue(resultExecutionState) + + + const actionWithDisabledRetry = buildCodeAction({ + name: 'runtime', + input: {}, + errorHandlingOptions: { + continueOnFailure: { + value: false, + }, + retryOnFailure: { + value: false, + }, + }, + }) + + const output = await runWithExponentialBackoff(executionState, actionWithDisabledRetry, constants, requestFunction) + + expect(output).toEqual(resultExecutionState) + expect(requestFunction).toHaveBeenCalledTimes(1) + expect(requestFunction).toHaveBeenCalledWith({ action: actionWithDisabledRetry, executionState, constants }) + + }) + +}) \ No newline at end of file diff --git a/packages/engine/test/helper/logging-utils.test.ts b/packages/engine/test/helper/logging-utils.test.ts new file mode 100644 index 0000000..495f012 --- /dev/null +++ b/packages/engine/test/helper/logging-utils.test.ts @@ -0,0 +1,26 @@ +import { + ActionType, + GenericStepOutput, + StepOutputStatus, +} from '@activepieces/shared' +import { loggingUtils } from '../../src/lib/helper/logging-utils' + +describe('Logging Utils', () => { + it('Should not truncate whole step if its log size exceeds limit', async () => { + const steps = { + mockStep: GenericStepOutput.create({ + type: ActionType.CODE, + status: StepOutputStatus.SUCCEEDED, + input: { + a: 'a'.repeat(1024 * 1024 * 12), + }, + }), + } + + // act + const result = await loggingUtils.trimExecution(steps) + + // assert + expect((result.mockStep.input as Record).a.length).toBeLessThan(1024 * 1024 * 12) + }) +}) diff --git a/packages/engine/test/resources/codes/flowVersionId/echo_step/index.js b/packages/engine/test/resources/codes/flowVersionId/echo_step/index.js new file mode 100755 index 0000000..01646c6 --- /dev/null +++ b/packages/engine/test/resources/codes/flowVersionId/echo_step/index.js @@ -0,0 +1,6 @@ +module.exports = { + code: async (params) => { + return params; + } + }; + \ No newline at end of file diff --git a/packages/engine/test/resources/codes/flowVersionId/echo_step_1/index.js b/packages/engine/test/resources/codes/flowVersionId/echo_step_1/index.js new file mode 100755 index 0000000..01646c6 --- /dev/null +++ b/packages/engine/test/resources/codes/flowVersionId/echo_step_1/index.js @@ -0,0 +1,6 @@ +module.exports = { + code: async (params) => { + return params; + } + }; + \ No newline at end of file diff --git a/packages/engine/test/resources/codes/flowVersionId/runtime/index.js b/packages/engine/test/resources/codes/flowVersionId/runtime/index.js new file mode 100755 index 0000000..16b3999 --- /dev/null +++ b/packages/engine/test/resources/codes/flowVersionId/runtime/index.js @@ -0,0 +1,6 @@ +module.exports = { + code: async (params) => { + throw new Error('Custom Runtime Error'); + } + }; + \ No newline at end of file diff --git a/packages/engine/test/services/props-resolver.test.ts b/packages/engine/test/services/props-resolver.test.ts new file mode 100755 index 0000000..5b15719 --- /dev/null +++ b/packages/engine/test/services/props-resolver.test.ts @@ -0,0 +1,713 @@ +import { ApFile, PieceAuth, Property } from '@activepieces/pieces-framework' +import { ActionType, GenericStepOutput, StepOutputStatus, TriggerType } from '@activepieces/shared' +import { FlowExecutorContext } from '../../src/lib/handler/context/flow-execution-context' +import { StepExecutionPath } from '../../src/lib/handler/context/step-execution-path' +import { propsProcessor } from '../../src/lib/variables/props-processor' +import { createPropsResolver } from '../../src/lib/variables/props-resolver' + +const propsResolverService = createPropsResolver({ + projectId: 'PROJECT_ID', + engineToken: 'WORKER_TOKEN', + apiUrl: 'http://127.0.0.1:3000', +}) + +const executionState = FlowExecutorContext.empty() + .upsertStep( + 'trigger', + GenericStepOutput.create({ + type: TriggerType.PIECE, + status: StepOutputStatus.SUCCEEDED, + input: {}, + output: { + items: [5, 'a'], + name: 'John', + price: 6.4, + users: [ + { + name: 'Alice', + }, + { + name: 'Bob', + }, + ], + lastNames: [ + 'Smith', + 'Doe', + ], + }, + }), + ) + .upsertStep('step_1', + GenericStepOutput.create({ + + type: ActionType.PIECE, + status: StepOutputStatus.SUCCEEDED, + input: {}, + output: { + success: true, + }, + })) + .upsertStep('step_2', GenericStepOutput.create({ + type: ActionType.PIECE, + status: StepOutputStatus.SUCCEEDED, + input: {}, + output: 'memory://{"fileName":"hello.png","data":"iVBORw0KGgoAAAANSUhEUgAAAiAAAAC4CAYAAADaI1cbAAA0h0lEQVR4AezdA5AlPx7A8Zxt27Z9r5PB2SidWTqbr26S9Hr/tm3btu3723eDJD3r15ec17vzXr+Z"}', + })) + + + +describe('Props resolver', () => { + test('Test resolve inside nested loops', async () => { + + const modifiedExecutionState = executionState.upsertStep('step_3', GenericStepOutput.create({ + type: ActionType.LOOP_ON_ITEMS, + status: StepOutputStatus.SUCCEEDED, + input: {}, + output: { + iterations: [ + { + 'step_8': GenericStepOutput.create({ + type: ActionType.PIECE, + status: StepOutputStatus.SUCCEEDED, + input: {}, + output: { + delayForInMs: 20000, + success: true, + }, + }), + 'step_4': GenericStepOutput.create({ + type: ActionType.LOOP_ON_ITEMS, + status: StepOutputStatus.SUCCEEDED, + input: {}, + output: { + iterations: [ + { + 'step_7': GenericStepOutput.create({ + 'type': ActionType.PIECE, + 'status': StepOutputStatus.SUCCEEDED, + 'input': { + 'unit': 'seconds', + 'delayFor': '20', + }, + 'output': { + 'delayForInMs': 20000, + 'success': true, + }, + }), + }, + ], + item: 1, + index: 0, + }, + }), + }, + ], + item: 1, + index: 0, + }, + })).setCurrentPath(StepExecutionPath.empty() + .loopIteration({ + loopName: 'step_3', + iteration: 0, + }) + .loopIteration({ + loopName: 'step_4', + iteration: 0, + }), + ) + + const { resolvedInput: secondLevelResolvedInput } = await propsResolverService.resolve({ unresolvedInput: '{{step_7.delayForInMs}}', executionState: modifiedExecutionState }) + expect(secondLevelResolvedInput).toEqual(20000) + const { resolvedInput: firstLevelResolvedInput } = await propsResolverService.resolve({ unresolvedInput: '{{step_8.delayForInMs}}', executionState: modifiedExecutionState }) + expect(firstLevelResolvedInput).toEqual(20000) + + }) + test('Test resolve text with no variables', async () => { + const { resolvedInput } = await propsResolverService.resolve({ unresolvedInput: 'Hello world!', executionState }) + expect(resolvedInput).toEqual( + 'Hello world!', + ) + }) + + test('Test resolve text with double variables', async () => { + const { resolvedInput } = await propsResolverService.resolve({ unresolvedInput: 'Price is {{ trigger.price }}', executionState }) + expect(resolvedInput, + ).toEqual('Price is 6.4') + }) + + + test('Test resolve object steps variables', async () => { + const { resolvedInput } = await propsResolverService.resolve({ unresolvedInput: '{{ {"where": "a"} }}', executionState }) + expect(resolvedInput).toEqual( + { + where: 'a', + }, + ) + }) + + test('Test resolve object steps variables', async () => { + const { resolvedInput } = await propsResolverService.resolve({ unresolvedInput: '{{trigger}}', executionState }) + expect(resolvedInput).toEqual( + { + items: [5, 'a'], + name: 'John', + price: 6.4, + users: [ + { + name: 'Alice', + }, + { + name: 'Bob', + }, + ], + lastNames: [ + 'Smith', + 'Doe', + ], + }, + ) + }) + + test('flatten array path', async () => { + const { resolvedInput } = await propsResolverService.resolve({ unresolvedInput: '{{flattenNestedKeys(trigger, [\'users\',\'name\'])}}', executionState }) + expect(resolvedInput).toEqual(['Alice', 'Bob']) + }) + + test('merge multiple flatten array paths', async ()=>{ + const { resolvedInput } = await propsResolverService.resolve({ unresolvedInput: '{{flattenNestedKeys(trigger, [\'users\',\'name\'])}} {{trigger.lastNames}}', executionState }) + expect(resolvedInput).toEqual(['Alice Smith', 'Bob Doe']) + }) + + test('Test resolve steps variables', async () => { + const { resolvedInput } = await propsResolverService.resolve({ unresolvedInput: '{{trigger.name}}', executionState }) + expect(resolvedInput).toEqual( + 'John', + ) + }) + + test('Test resolve multiple variables', async () => { + const { resolvedInput } = await propsResolverService.resolve({ unresolvedInput: '{{trigger.name}} {{trigger.name}}', executionState }) + expect( + resolvedInput, + ).toEqual('John John') + }) + + test('Test resolve variable array items', async () => { + const { resolvedInput } = await propsResolverService.resolve({ + unresolvedInput: + '{{trigger.items[0]}} {{trigger.items[1]}}', + executionState, + }) + expect( + resolvedInput, + ).toEqual('5 a') + }) + + test('Test resolve array variable', async () => { + const { resolvedInput } = await propsResolverService.resolve({ unresolvedInput: '{{trigger.items}}', executionState }) + expect(resolvedInput).toEqual( + [5, 'a'], + ) + }) + + test('Test resolve integer from variables', async () => { + const { resolvedInput } = await propsResolverService.resolve({ unresolvedInput: '{{trigger.items[0]}}', executionState }) + expect( + resolvedInput, + ).toEqual(5) + }) + + test('Test resolve text with undefined variables', async () => { + const { resolvedInput } = await propsResolverService.resolve({ + unresolvedInput: + 'test {{configs.bar}} {{trigger.items[4]}}', + executionState, + }) + expect( + resolvedInput, + ).toEqual('test ') + }) + + test('Test resolve empty text', async () => { + const { resolvedInput } = await propsResolverService.resolve({ unresolvedInput: '', executionState }) + expect(resolvedInput).toEqual('') + }) + + + test('Test resolve empty variable operator', async () => { + const { resolvedInput } = await propsResolverService.resolve({ unresolvedInput: '{{}}', executionState }) + expect(resolvedInput).toEqual('') + }) + + test('Test resolve object', async () => { + const { resolvedInput } = await propsResolverService.resolve({ + unresolvedInput: + { + input: { + foo: 'bar', + nums: [1, 2, '{{trigger.items[0]}}'], + var: '{{trigger.price}}', + }, + }, + executionState, + }) + expect( + resolvedInput, + ).toEqual({ input: { foo: 'bar', nums: [1, 2, 5], var: 6.4 } }) + }) + + test('Test resolve boolean from variables', async () => { + const { resolvedInput } = await propsResolverService.resolve({ unresolvedInput: '{{step_1.success}}', executionState }) + expect(resolvedInput).toEqual( + true, + ) + }) + + test('Test resolve addition from variables', async () => { + const { resolvedInput } = await propsResolverService.resolve({ unresolvedInput: '{{trigger.price + 2 - 3}}', executionState }) + expect(resolvedInput).toEqual( + 6.4 + 2 - 3, + ) + }) + + test('Test resolve text with array variable', async () => { + const { resolvedInput } = await propsResolverService.resolve({ unresolvedInput: 'items are {{trigger.items}}', executionState }) + expect( + resolvedInput, + ).toEqual('items are [5,"a"]') + }) + + test('Test resolve text with object variable', async () => { + const { resolvedInput } = await propsResolverService.resolve({ + unresolvedInput: + 'values from trigger step: {{trigger}}', + executionState, + }) + expect( + resolvedInput, + ).toEqual('values from trigger step: {"items":[5,"a"],"name":"John","price":6.4,"users":[{"name":"Alice"},{"name":"Bob"}],"lastNames":["Smith","Doe"]}') + }) + + test('Test use built-in Math Min function', async () => { + const { resolvedInput } = await propsResolverService.resolve({ unresolvedInput: '{{Math.min(trigger.price + 2 - 3, 2)}}', executionState }) + expect(resolvedInput).toEqual( + 2, + ) + }) + + test('Test use built-in Math Max function', async () => { + const { resolvedInput } = await propsResolverService.resolve({ unresolvedInput: '{{Math.max(trigger.price + 2, 2)}}', executionState }) + expect(resolvedInput).toEqual( + 8.4, + ) + }) + + it('should not compress memory file in native value in non-logs mode', async () => { + const input = { + base64: 'memory://{"fileName":"hello.png","data":"iVBORw0KGgoAAAANSUhEUgAAAiAAAAC4CAYAAADaI1cbAAA0h0lEQVR4AezdA5AlPx7A8Zxt27Z9r5PB2SidWTqbr26S9Hr/tm3btu3723eDJD3r15ec17vzXr+Z"}', + } + const { resolvedInput } = await propsResolverService.resolve({ + unresolvedInput: input, + executionState, + }) + expect(resolvedInput).toEqual({ + base64: 'memory://{"fileName":"hello.png","data":"iVBORw0KGgoAAAANSUhEUgAAAiAAAAC4CAYAAADaI1cbAAA0h0lEQVR4AezdA5AlPx7A8Zxt27Z9r5PB2SidWTqbr26S9Hr/tm3btu3723eDJD3r15ec17vzXr+Z"}', + }) + }) + + it('should not compress memory file in referenced value in non-logs mode', async () => { + const input = { + base64: '{{step_2}}', + } + const { resolvedInput } = await propsResolverService.resolve({ + unresolvedInput: input, + executionState, + }) + expect(resolvedInput).toEqual({ + base64: 'memory://{"fileName":"hello.png","data":"iVBORw0KGgoAAAANSUhEUgAAAiAAAAC4CAYAAADaI1cbAAA0h0lEQVR4AezdA5AlPx7A8Zxt27Z9r5PB2SidWTqbr26S9Hr/tm3btu3723eDJD3r15ec17vzXr+Z"}', + }) + }) + + + + it('should return base64 from base64 with mime only', async () => { + const input = { + base64WithMime: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAiAAAAC4CAYAAADaI1cbAAA0h0lEQVR4AezdA5AlPx7A8Zxt27Z9r5PB2SidWTqbr26S9Hr/tm3btu3723eDJD3r15ec17vzXr+Z', + base64: 'iVBORw0KGgoAAAANSUhEUgAAAiAAAAC4CAYAAADaI1cbAAA0h0lEQVR4AezdA5AlPx7A8Zxt27Z9r5PB2SidWTqbr26S9Hr/tm3btu3723eDJD3r15ec17vzXr+Z', + } + const props = { + base64WithMime: Property.File({ + displayName: 'Base64', + required: true, + }), + base64: Property.File({ + displayName: 'Base64', + required: true, + }), + } + const { processedInput, errors } = await propsProcessor.applyProcessorsAndValidators(input, props, PieceAuth.None(), false, null) + expect(processedInput).toEqual({ + base64: null, + base64WithMime: new ApFile('unknown.png', Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAiAAAAC4CAYAAADaI1cbAAA0h0lEQVR4AezdA5AlPx7A8Zxt27Z9r5PB2SidWTqbr26S9Hr/tm3btu3723eDJD3r15ec17vzXr+Z', 'base64'), 'png'), + }) + expect(errors).toEqual({ + 'base64': [ + 'Expected file url or base64 with mimeType, received: iVBORw0KGgoAAAANSUhEUgAAAiAAAAC4CAYAAADaI1cbAAA0h0lEQVR4AezdA5AlPx7A8Zxt27Z9r5PB2SidWTqbr26S9Hr/tm3btu3723eDJD3r15ec17vzXr+Z', + ], + }) + }) + + it('should resolve files inside the array properties', async () => { + const input = { + documents: [ + { + file: 'https://cdn.activepieces.com/brand/logo.svg?token=123', + }, + ], + } + const props = { + documents: Property.Array({ + displayName: 'Documents', + required: true, + properties: { + file: Property.File({ + displayName: 'File', + required: true, + }), + }, + }), + } + + const { processedInput, errors } = await propsProcessor.applyProcessorsAndValidators(input, props, PieceAuth.None(), false, null) + expect(processedInput.documents[0].file).toBeDefined() + expect(processedInput.documents[0].file.extension).toBe('svg') + expect(processedInput.documents[0].file.filename).toBe('logo.svg') + expect(errors).toEqual({}) + }) + + + + it('should return error for invalid file inside the array properties', async () => { + const input = { + documents: [ + { + file: 'invalid-url', + }, + ], + } + const props = { + documents: Property.Array({ + displayName: 'Documents', + required: true, + properties: { + file: Property.File({ + displayName: 'File', + required: true, + }), + }, + }), + } + + const { processedInput, errors } = await propsProcessor.applyProcessorsAndValidators(input, props, PieceAuth.None(), false, null) + expect(processedInput.documents[0].file).toBeNull() + expect(errors).toEqual({ + 'documents': { + properties: [{ + file: [ + 'Expected file url or base64 with mimeType, received: invalid-url', + ], + }], + }, + }) + }) + it('should return images for image url', async () => { + const input = { + file: 'https://cdn.activepieces.com/brand/logo.svg?token=123', + } + const props = { + file: Property.File({ + displayName: 'File', + required: true, + }), + + } + const { processedInput, errors } = await propsProcessor.applyProcessorsAndValidators(input, props, PieceAuth.None(), false, null) + expect(processedInput.file).toBeDefined() + expect(processedInput.file.extension).toBe('svg') + expect(processedInput.file.filename).toBe('logo.svg') + expect(errors).toEqual({}) + }) + + // Test with invalid url + it('should return error for invalid data', async () => { + const input = { + file: 'https://google.com', + nullFile: null, + nullOptionalFile: null, + } + const props = { + file: Property.File({ + displayName: 'File', + required: true, + }), + nullFile: Property.File({ + displayName: 'File', + required: true, + }), + nullOptionalFile: Property.File({ + displayName: 'File', + required: false, + }), + } + const { processedInput, errors } = await propsProcessor.applyProcessorsAndValidators(input, props, PieceAuth.None(), false, null) + + expect(processedInput.file).toBeDefined() + expect(processedInput.file.extension).toBe('html') + expect(processedInput.file.filename).toBe('unknown.html') + expect(processedInput.nullFile).toBeNull() + expect(processedInput.nullOptionalFile).toBeNull() + + expect(errors).toEqual({ + 'nullFile': [ + 'Expected file url or base64 with mimeType, received: null', + ], + }) + }) + + + it('should return casted number for text', async () => { + const input = { + price: '0', + auth: { + age: '12', + }, + } + + const { processedInput, errors } = await propsProcessor.applyProcessorsAndValidators(input, { + price: Property.Number({ + displayName: 'Price', + required: true, + }), + }, PieceAuth.CustomAuth({ + required: true, + props: { + age: Property.Number({ + displayName: 'age', + required: true, + }), + }, + }), true, undefined) + + expect(processedInput).toEqual({ + auth: { + age: 12, + }, + price: 0, + }) + expect(errors).toEqual({}) + }) + + it('should not error if auth configured, but no auth provided in input', async () => { + const input = { + price: '0', + } + const props = { + price: Property.Number({ + displayName: 'Price', + required: true, + }), + } + const { processedInput, errors } = await propsProcessor.applyProcessorsAndValidators(input, props, PieceAuth.CustomAuth({ + required: true, + props: {}, + }), false, undefined) + + expect(processedInput).toEqual({ + price: 0, + }) + expect(errors).toEqual({}) + }) + + + it('should flatten arrays inside DYNAMIC properties', async () => { + const input = { + dynamicProp: { + items: { + id: [1, 2], + name: ['Item 1', 'Item 2'], + }, + }, + } + const dynamicPropertiesSchema = { + dynamicProp: { + items: Property.Array({ + displayName: 'Items', + required: true, + properties: { + id: Property.Number({ + displayName: 'ID', + required: true, + }), + name: Property.LongText({ + displayName: 'Name', + required: true, + }), + }, + }), + }, + } + const props = { + dynamicProp: Property.DynamicProperties({ + displayName: 'Dynamic Property', + required: true, + props: async () => { + return {} + }, + refreshers: [], + }), + } + + const { processedInput, errors } = await propsProcessor.applyProcessorsAndValidators(input, props, PieceAuth.None(), false, dynamicPropertiesSchema) + + expect(processedInput.dynamicProp.items).toEqual([ + { id: 1, name: 'Item 1' }, + { id: 2, name: 'Item 2' }, + ]) + expect(errors).toEqual({}) + }) + +}) + +describe('Array Flatter Processor', () => { + it('should flatten array of objects', async () => { + const input = { + items: { + id: [1, 2], + name: ['Item 1', 'Item 2'], + }, + } + const props = { + items: Property.Array({ + displayName: 'Items', + required: true, + properties: { + id: Property.Number({ + displayName: 'ID', + required: true, + }), + name: Property.LongText({ + displayName: 'Name', + required: true, + }), + }, + }), + } + + const { processedInput, errors } = await propsProcessor.applyProcessorsAndValidators(input, props, PieceAuth.None(), false, null) + + expect(processedInput.items).toEqual([ + { id: 1, name: 'Item 1' }, + { id: 2, name: 'Item 2' }, + ]) + expect(errors).toEqual({}) + }) + + it('should handle non-array properties gracefully', async () => { + const input = { + items: { + id: [1, 2], + name: 'Single Item', // Non-array property + }, + } + const props = { + items: Property.Array({ + displayName: 'Items', + required: true, + properties: { + id: Property.Number({ + displayName: 'ID', + required: true, + }), + name: Property.LongText({ + displayName: 'Name', + required: true, + }), + }, + }), + } + + const { processedInput, errors } = await propsProcessor.applyProcessorsAndValidators(input, props, PieceAuth.None(), false, null) + + expect(processedInput.items).toEqual([ + { id: 1, name: 'Single Item' }, + { id: 2, name: 'Single Item' }, + ]) + expect(errors).toEqual({}) + }) + + it('should handle arrays of unequal length', async () => { + const input = { + items: { + id: [1, 2, 3], // Longer array + name: ['Item 1', 'Item 2'], // Shorter array + }, + } + const props = { + items: Property.Array({ + displayName: 'Items', + required: true, + properties: { + id: Property.Number({ + displayName: 'ID', + required: true, + }), + name: Property.LongText({ + displayName: 'Name', + required: false, + }), + }, + }), + } + + const { processedInput, errors } = await propsProcessor.applyProcessorsAndValidators(input, props, PieceAuth.None(), false, null) + + expect(processedInput.items).toEqual([ + { id: 1, name: 'Item 1' }, + { id: 2, name: 'Item 2' }, + { id: 3, name: undefined }, // Handle missing name + ]) + expect(errors).toEqual({}) + }) + + it('should handle arrays with string values', async () => { + const input = { + items: { + id: '1', + name: 'item1', + }, + } + const props = { + items: Property.Array({ + displayName: 'Items', + required: true, + properties: { + id: Property.ShortText({ + displayName: 'ID', + required: true, + }), + name: Property.LongText({ + displayName: 'Name', + required: true, + }), + }, + }), + } + + const { processedInput, errors } = await propsProcessor.applyProcessorsAndValidators(input, props, PieceAuth.None(), false, null) + + expect(processedInput.items).toEqual([ + { id: '1', name: 'item1' }, + ]) + expect(errors).toEqual({}) + }) +}) diff --git a/packages/engine/test/services/props-validator.test.ts b/packages/engine/test/services/props-validator.test.ts new file mode 100644 index 0000000..0b19b02 --- /dev/null +++ b/packages/engine/test/services/props-validator.test.ts @@ -0,0 +1,342 @@ +import { PieceAuth, Property } from '@activepieces/pieces-framework' +import { propsProcessor } from '../../src/lib/variables/props-processor' +describe('Property Validation', () => { + describe('required properties', () => { + it('should validate required string property', async () => { + const props = { + text: Property.ShortText({ + displayName: 'Text', + required: true, + }), + } + + const { errors: validErrors } = await propsProcessor.applyProcessorsAndValidators( + { text: 'valid text' }, + props, + PieceAuth.None(), + false, + null, + ) + expect(validErrors).toEqual({}) + + const { errors: nullErrors } = await propsProcessor.applyProcessorsAndValidators( + { text: null }, + props, + PieceAuth.None(), + false, + null, + ) + expect(nullErrors).toEqual({ + text: ['Expected string, received: null'], + }) + + const { errors: undefinedErrors } = await propsProcessor.applyProcessorsAndValidators( + { text: undefined }, + props, + PieceAuth.None(), + false, + null, + ) + expect(undefinedErrors).toEqual({ + text: ['Expected string, received: undefined'], + }) + }) + + it('should validate required number property', async () => { + const props = { + number: Property.Number({ + displayName: 'Number', + required: true, + }), + } + + const { errors: validErrors } = await propsProcessor.applyProcessorsAndValidators( + { number: 42 }, + props, + PieceAuth.None(), + false, + null, + ) + expect(validErrors).toEqual({}) + + const { errors: nullErrors } = await propsProcessor.applyProcessorsAndValidators( + { number: null }, + props, + PieceAuth.None(), + false, + null, + ) + expect(nullErrors).toEqual({ + number: ['Expected number, received: null'], + }) + + const { errors: typeErrors } = await propsProcessor.applyProcessorsAndValidators( + { number: 'not a number' }, + props, + PieceAuth.None(), + false, + null, + ) + expect(typeErrors).toEqual({ + number: ['Expected number, received: not a number'], + }) + }) + + it('should validate required datetime property', async () => { + const props = { + date: Property.DateTime({ + displayName: 'DateTime', + required: true, + }), + } + + const { errors: validErrors } = await propsProcessor.applyProcessorsAndValidators( + { date: '2024-03-14T12:00:00.000Z' }, + props, + PieceAuth.None(), + false, + null, + ) + expect(validErrors).toEqual({}) + + const { errors: invalidErrors } = await propsProcessor.applyProcessorsAndValidators( + { date: 'not a date' }, + props, + PieceAuth.None(), + false, + null, + ) + expect(invalidErrors).toEqual({ + date: ['Invalid datetime format. Expected ISO format (e.g. 2024-03-14T12:00:00.000Z), received: not a date'], + }) + }) + + it('should validate required array property', async () => { + const props = { + array: Property.Array({ + displayName: 'Array', + required: true, + }), + } + + const { errors: validErrors } = await propsProcessor.applyProcessorsAndValidators( + { array: [1, 2, 3] }, + props, + PieceAuth.None(), + false, + null, + ) + expect(validErrors).toEqual({}) + + const { errors: typeErrors } = await propsProcessor.applyProcessorsAndValidators( + { array: 'not an array' }, + props, + PieceAuth.None(), + false, + null, + ) + expect(typeErrors).toEqual({ + array: ['Expected array, received: not an array'], + }) + }) + + it('should validate required json property', async () => { + const props = { + json: Property.Json({ + displayName: 'JSON', + required: true, + }), + } + + const { errors: validErrors } = await propsProcessor.applyProcessorsAndValidators( + { json: { key: 'value' } }, + props, + PieceAuth.None(), + false, + null, + ) + expect(validErrors).toEqual({}) + + const { errors: validJsonStringErrors } = await propsProcessor.applyProcessorsAndValidators( + { json: '{"key": "value"}' }, + props, + PieceAuth.None(), + false, + null, + ) + expect(validJsonStringErrors).toEqual({}) + + const { errors: validArrayErrors } = await propsProcessor.applyProcessorsAndValidators( + { json: [1, 2, 3] }, + props, + PieceAuth.None(), + false, + null, + ) + expect(validArrayErrors).toEqual({}) + + const { errors: validArrayStringErrors } = await propsProcessor.applyProcessorsAndValidators( + { json: '[1, 2, 3]' }, + props, + PieceAuth.None(), + false, + null, + ) + expect(validArrayStringErrors).toEqual({}) + + const { errors: invalidJsonErrors } = await propsProcessor.applyProcessorsAndValidators( + { json: 'not a json object' }, + props, + PieceAuth.None(), + false, + null, + ) + expect(invalidJsonErrors).toEqual({ + json: ['Expected JSON, received: not a json object'], + }) + + const { errors: nullErrors } = await propsProcessor.applyProcessorsAndValidators( + { json: null }, + props, + PieceAuth.None(), + false, + null, + ) + expect(nullErrors).toEqual({ + json: ['Expected JSON, received: null'], + }) + }) + it('should validate required object property', async () => { + const props = { + object: Property.Object({ + displayName: 'Object', + required: true, + }), + } + + const { errors: validErrors } = await propsProcessor.applyProcessorsAndValidators( + { object: { key: 'value' } }, + props, + PieceAuth.None(), + false, + null, + ) + expect(validErrors).toEqual({}) + + const { errors: nullErrors } = await propsProcessor.applyProcessorsAndValidators( + { object: null }, + props, + PieceAuth.None(), + false, + null, + ) + expect(nullErrors).toEqual({ + object: ['Expected object, received: null'], + }) + + const { errors: typeErrors } = await propsProcessor.applyProcessorsAndValidators( + { object: 'not an object' }, + props, + PieceAuth.None(), + false, + null, + ) + expect(typeErrors).toEqual({ + object: ['Expected object, received: not an object'], + }) + + const { errors: jsonStringErrors } = await propsProcessor.applyProcessorsAndValidators( + { object: JSON.stringify({ key: 'value' }) }, + props, + PieceAuth.None(), + false, + null, + ) + expect(jsonStringErrors).toEqual({}) + + const { errors: undefinedErrors } = await propsProcessor.applyProcessorsAndValidators( + { object: { key: 'value' } }, + props, + PieceAuth.None(), + false, + null, + ) + expect(undefinedErrors).toEqual({}) + }) + }) + + describe('optional properties', () => { + it('should validate optional properties', async () => { + const props = { + text: Property.ShortText({ + displayName: 'Text', + required: false, + }), + number: Property.Number({ + displayName: 'Number', + required: false, + }), + } + + const { errors } = await propsProcessor.applyProcessorsAndValidators( + { + text: null, + number: undefined, + }, + props, + PieceAuth.None(), + false, + null, + ) + expect(errors).toEqual({}) + }) + }) + + describe('type validation', () => { + it('should validate property types', async () => { + const props = { + string: Property.ShortText({ + displayName: 'Text', + required: true, + }), + number: Property.Number({ + displayName: 'Number', + required: true, + }), + boolean: Property.Checkbox({ + displayName: 'Checkbox', + required: true, + }), + array: Property.Array({ + displayName: 'Array', + required: true, + }), + object: Property.Object({ + displayName: 'Object', + required: true, + }), + } + + const { errors } = await propsProcessor.applyProcessorsAndValidators( + { + string: 42, + number: 'not a number', + boolean: 'not a boolean', + array: 'not an array', + object: 'not an object', + }, + props, + PieceAuth.None(), + false, + null, + ) + + expect(errors).toEqual({ + number: ['Expected number, received: not a number'], + boolean: ['Expected boolean, received: not a boolean'], + array: ['Expected array, received: not an array'], + object: ['Expected object, received: not an object'], + }) + }) + }) +}) diff --git a/packages/engine/tsconfig.json b/packages/engine/tsconfig.json new file mode 100644 index 0000000..00c1e78 --- /dev/null +++ b/packages/engine/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "allowJs": true, + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json", + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/engine/tsconfig.lib.json b/packages/engine/tsconfig.lib.json new file mode 100644 index 0000000..33eca2c --- /dev/null +++ b/packages/engine/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/packages/engine/tsconfig.spec.json b/packages/engine/tsconfig.spec.json new file mode 100644 index 0000000..5518991 --- /dev/null +++ b/packages/engine/tsconfig.spec.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts", + "test/**/*.ts", + "test/**/*.d.ts" + ] +} diff --git a/packages/engine/webpack.config.js b/packages/engine/webpack.config.js new file mode 100644 index 0000000..dcfc2cd --- /dev/null +++ b/packages/engine/webpack.config.js @@ -0,0 +1,14 @@ +const { composePlugins, withNx } = require('@nx/webpack'); +const IgnoreDynamicRequire = require('webpack-ignore-dynamic-require'); + +module.exports = composePlugins(withNx(), (config) => { + config.plugins.push(new IgnoreDynamicRequire()); + + config.externals = { + 'isolated-vm': 'commonjs2 isolated-vm', + 'utf-8-validate': 'commonjs2 utf-8-validate', + 'bufferutil': 'commonjs2 bufferutil' + }; + + return config; +}); diff --git a/packages/pieces/community/activecampaign/.eslintrc.json b/packages/pieces/community/activecampaign/.eslintrc.json new file mode 100644 index 0000000..359ff63 --- /dev/null +++ b/packages/pieces/community/activecampaign/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/activecampaign/README.md b/packages/pieces/community/activecampaign/README.md new file mode 100644 index 0000000..7bbcfc7 --- /dev/null +++ b/packages/pieces/community/activecampaign/README.md @@ -0,0 +1,7 @@ +# pieces-activecampaign + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-activecampaign` to build the library. diff --git a/packages/pieces/community/activecampaign/package.json b/packages/pieces/community/activecampaign/package.json new file mode 100644 index 0000000..6f79b29 --- /dev/null +++ b/packages/pieces/community/activecampaign/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-activecampaign", + "version": "0.0.3" +} \ No newline at end of file diff --git a/packages/pieces/community/activecampaign/project.json b/packages/pieces/community/activecampaign/project.json new file mode 100644 index 0000000..30fd757 --- /dev/null +++ b/packages/pieces/community/activecampaign/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-activecampaign", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/activecampaign/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/activecampaign", + "tsConfig": "packages/pieces/community/activecampaign/tsconfig.lib.json", + "packageJson": "packages/pieces/community/activecampaign/package.json", + "main": "packages/pieces/community/activecampaign/src/index.ts", + "assets": [ + "packages/pieces/community/activecampaign/*.md", + { + "input": "packages/pieces/community/activecampaign/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-activecampaign {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/activecampaign/src/index.ts b/packages/pieces/community/activecampaign/src/index.ts new file mode 100644 index 0000000..6728761 --- /dev/null +++ b/packages/pieces/community/activecampaign/src/index.ts @@ -0,0 +1,89 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { newDealAddedOrUpdatedTrigger } from './lib/triggers/new-deal-added-or-updated'; +import { createAccountAction } from './lib/actions/accounts/create-account'; +import { updateAccountAction } from './lib/actions/accounts/update-account'; +import { subscribeOrUnsubscribeContactFromListAction } from './lib/actions/contacts/subscribe-or-unsubscribe-contact-from-list'; +import { createContactAction } from './lib/actions/contacts/create-contact'; +import { updateContactAction } from './lib/actions/contacts/update-contact'; +import { addContactToAccountAction } from './lib/actions/contacts/add-contact-to-account'; +import { addTagToContactAction } from './lib/actions/contacts/add-tag-to-contact'; +import { makeClient } from './lib/common'; +import { newDealNoteTrigger } from './lib/triggers/new-deal-note'; +import { newDealTaskTrigger } from './lib/triggers/new-deal-task'; +import { dealTaskCompletedTrigger } from './lib/triggers/deal-task-completed'; +import { newtagAddedOrRemovedFromContactTrigger } from './lib/triggers/tag-added-or-removed-from-contact'; +import { newOrUpdatedAccountTrigger } from './lib/triggers/new-or-updated-account'; +import { newContactNoteTrigger } from './lib/triggers/new-contact-note'; +import { newContactTaskTrigger } from './lib/triggers/new-contact-task'; +import { updatedContactTrigger } from './lib/triggers/updated-contact'; + +const authGuide = ` +To obtain your ActiveCampaign API URL and Key, follow these steps: + +1. Log in to your ActiveCampaign account. +2. Navigate to **Settings->Developer** section. +3. Under **API Access** ,you'll find your API URL and Key. +`; + +export const activeCampaignAuth = PieceAuth.CustomAuth({ + required: true, + description: authGuide, + props: { + apiUrl: Property.ShortText({ + displayName: 'API URL', + required: true, + }), + apiKey: Property.ShortText({ + displayName: 'API Key', + required: true, + }), + }, + validate: async ({ auth }) => { + try { + const client = makeClient(auth); + await client.authenticate(); + return { valid: true }; + } catch (error) { + return { + valid: false, + error: 'Invalid API credentials', + }; + } + }, +}); + +export const activecampaign = createPiece({ + displayName: 'ActiveCampaign', + description: + 'Email marketing, marketing automation, and CRM tools you need to create incredible customer experiences.', + auth: activeCampaignAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/activecampaign.png', + categories: [PieceCategory.MARKETING, PieceCategory.SALES_AND_CRM], + authors: ["kishanprmr","abuaboud"], + actions: [ + addContactToAccountAction, + addTagToContactAction, + createAccountAction, + createContactAction, + updateAccountAction, + updateContactAction, + subscribeOrUnsubscribeContactFromListAction, + ], + triggers: [ + dealTaskCompletedTrigger, + newContactNoteTrigger, + newContactTaskTrigger, + newDealAddedOrUpdatedTrigger, + newOrUpdatedAccountTrigger, + newDealNoteTrigger, + newDealTaskTrigger, + newtagAddedOrRemovedFromContactTrigger, + updatedContactTrigger, + ], +}); diff --git a/packages/pieces/community/activecampaign/src/lib/actions/accounts/create-account.ts b/packages/pieces/community/activecampaign/src/lib/actions/accounts/create-account.ts new file mode 100644 index 0000000..99b7de2 --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/actions/accounts/create-account.ts @@ -0,0 +1,37 @@ +import { activeCampaignAuth } from '../../..'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { activecampaignCommon, makeClient } from '../../common'; +import { CreateAccountRequest } from '../../common/types'; + +export const createAccountAction = createAction({ + auth: activeCampaignAuth, + name: 'activecampaign_create_account', + displayName: 'Create Account', + description: 'Creates a new account.', + props: { + name: Property.ShortText({ + displayName: 'Account Name', + required: true, + }), + accountUrl: Property.ShortText({ + displayName: 'Account URL', + required: false, + }), + accountCustomFields: activecampaignCommon.accountCustomFields, + }, + async run(context) { + const { name, accountUrl, accountCustomFields } = context.propsValue; + const createAccountParams: CreateAccountRequest = { + name, + accountUrl, + fields: [], + }; + + Object.entries(accountCustomFields).forEach(([key, value]) => { + createAccountParams.fields?.push({ customFieldId: Number(key), fieldValue: value }); + }); + + const client = makeClient(context.auth); + return await client.createAccount(createAccountParams); + }, +}); diff --git a/packages/pieces/community/activecampaign/src/lib/actions/accounts/update-account.ts b/packages/pieces/community/activecampaign/src/lib/actions/accounts/update-account.ts new file mode 100644 index 0000000..0e511f0 --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/actions/accounts/update-account.ts @@ -0,0 +1,38 @@ +import { activeCampaignAuth } from '../../..'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { activecampaignCommon, makeClient } from '../../common'; +import { CreateAccountRequest } from '../../common/types'; + +export const updateAccountAction = createAction({ + auth: activeCampaignAuth, + name: 'activecampaign_update_account', + displayName: 'Update Account', + description: 'Updates an account.', + props: { + accountId: activecampaignCommon.accountId, + name: Property.ShortText({ + displayName: 'Account Name', + required: false, + }), + accountUrl: Property.ShortText({ + displayName: 'Account URL', + required: false, + }), + accountCustomFields: activecampaignCommon.accountCustomFields, + }, + async run(context) { + const { accountId, name, accountUrl, accountCustomFields } = context.propsValue; + const updateAccountParams: Partial = { + name, + accountUrl, + fields: [], + }; + + Object.entries(accountCustomFields).forEach(([key, value]) => { + updateAccountParams.fields?.push({ customFieldId: Number(key), fieldValue: value }); + }); + + const client = makeClient(context.auth); + return await client.updateAccount(Number(accountId), updateAccountParams); + }, +}); diff --git a/packages/pieces/community/activecampaign/src/lib/actions/contacts/add-contact-to-account.ts b/packages/pieces/community/activecampaign/src/lib/actions/contacts/add-contact-to-account.ts new file mode 100644 index 0000000..1c74b92 --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/actions/contacts/add-contact-to-account.ts @@ -0,0 +1,29 @@ +import { activeCampaignAuth } from '../../..'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { activecampaignCommon, makeClient } from '../../common'; + +export const addContactToAccountAction = createAction({ + auth: activeCampaignAuth, + name: 'activecampaign_add_contact_to_account', + displayName: 'Add Contact to Account', + description: 'Adds a contact to an ActiveCampaign account.', + props: { + contactId: activecampaignCommon.contactId, + accountId: activecampaignCommon.accountId, + jobTitle: Property.ShortText({ + displayName: 'Job Title', + required: false, + }), + }, + async run(context) { + const { contactId, accountId, jobTitle } = context.propsValue; + + const client = makeClient(context.auth); + + return await client.createAccountContactAssociation( + Number(contactId), + Number(accountId), + jobTitle, + ); + }, +}); diff --git a/packages/pieces/community/activecampaign/src/lib/actions/contacts/add-tag-to-contact.ts b/packages/pieces/community/activecampaign/src/lib/actions/contacts/add-tag-to-contact.ts new file mode 100644 index 0000000..09dce8b --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/actions/contacts/add-tag-to-contact.ts @@ -0,0 +1,20 @@ +import { activeCampaignAuth } from '../../..'; +import { createAction } from '@activepieces/pieces-framework'; +import { activecampaignCommon, makeClient } from '../../common'; + +export const addTagToContactAction = createAction({ + auth: activeCampaignAuth, + name: 'activecampaign_add_tag_to_contact', + displayName: 'Add Tag to Contact', + description: 'Adds a tag to contact.', + props: { + contactId: activecampaignCommon.contactId, + tagId: activecampaignCommon.tagId, + }, + async run(context) { + const { contactId, tagId } = context.propsValue; + + const client = makeClient(context.auth); + return await client.addTagToContact(contactId, tagId); + }, +}); diff --git a/packages/pieces/community/activecampaign/src/lib/actions/contacts/create-contact.ts b/packages/pieces/community/activecampaign/src/lib/actions/contacts/create-contact.ts new file mode 100644 index 0000000..6aebe6d --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/actions/contacts/create-contact.ts @@ -0,0 +1,48 @@ +import { activeCampaignAuth } from '../../../'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { activecampaignCommon, makeClient } from '../../common'; +import { CreateContactRequest } from '../../common/types'; + +export const createContactAction = createAction({ + auth: activeCampaignAuth, + name: 'activecampaign_create_contact', + displayName: 'Create Contact', + description: 'Creates a new contact.', + props: { + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + firstName: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + phone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + contactCustomFields: activecampaignCommon.contactCustomFields, + }, + async run(context) { + const { email, firstName, lastName, phone, contactCustomFields } = context.propsValue; + + const createContactParams: CreateContactRequest = { + email, + firstName, + lastName, + phone, + fieldValues: [], + }; + + Object.entries(contactCustomFields).forEach(([key, value]) => { + createContactParams.fieldValues.push({ field: key, value: value }); + }); + + const client = makeClient(context.auth); + return await client.createContact(createContactParams); + }, +}); diff --git a/packages/pieces/community/activecampaign/src/lib/actions/contacts/subscribe-or-unsubscribe-contact-from-list.ts b/packages/pieces/community/activecampaign/src/lib/actions/contacts/subscribe-or-unsubscribe-contact-from-list.ts new file mode 100644 index 0000000..cbc236d --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/actions/contacts/subscribe-or-unsubscribe-contact-from-list.ts @@ -0,0 +1,41 @@ +import { activeCampaignAuth } from '../../../'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { activecampaignCommon, makeClient } from '../../common'; + +export const subscribeOrUnsubscribeContactFromListAction = createAction({ + auth: activeCampaignAuth, + name: 'activecampaign_subscribe_or_unsubscribe_contact_from_list', + displayName: 'Subscribe or Unsubscribe Contact From List', + description: + 'Subscribes a Contact to a List it is not currently associated with, or Unsubscribes a Contact from a list is currently associated with.', + props: { + listId: activecampaignCommon.listId(true), + status: Property.StaticDropdown({ + displayName: 'Action', + required: true, + options: { + disabled: false, + options: [ + { + label: 'Subscribe', + value: '1', + }, + { + label: 'Unsubscribe', + value: '2', + }, + ], + }, + }), + contactId: Property.ShortText({ + displayName: 'Contact ID', + required: true, + }), + }, + async run(context) { + const { listId, status, contactId } = context.propsValue; + + const client = makeClient(context.auth); + return await client.addContactToList(listId!, contactId, status); + }, +}); diff --git a/packages/pieces/community/activecampaign/src/lib/actions/contacts/update-contact.ts b/packages/pieces/community/activecampaign/src/lib/actions/contacts/update-contact.ts new file mode 100644 index 0000000..f784d53 --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/actions/contacts/update-contact.ts @@ -0,0 +1,50 @@ +import { activeCampaignAuth } from '../../../'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { activecampaignCommon, makeClient } from '../../common'; +import { CreateContactRequest } from '../../common/types'; + +export const updateContactAction = createAction({ + auth: activeCampaignAuth, + name: 'activecampaign_update_contact', + displayName: 'Update Contact', + description: 'Updates an existing contact.', + props: { + contactId: activecampaignCommon.contactId, + email: Property.ShortText({ + displayName: 'Email', + required: false, + }), + firstName: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + phone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + contactCustomFields: activecampaignCommon.contactCustomFields, + }, + async run(context) { + const { email, contactId, firstName, lastName, phone, contactCustomFields } = + context.propsValue; + + const updateContactParams: Partial = { + email, + firstName, + lastName, + phone, + fieldValues: [], + }; + + Object.entries(contactCustomFields).forEach(([key, value]) => { + updateContactParams.fieldValues?.push({ field: key, value: value }); + }); + + const client = makeClient(context.auth); + return await client.updateContact(Number(contactId), updateContactParams); + }, +}); diff --git a/packages/pieces/community/activecampaign/src/lib/common/client.ts b/packages/pieces/community/activecampaign/src/lib/common/client.ts new file mode 100644 index 0000000..f2444d0 --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/common/client.ts @@ -0,0 +1,148 @@ +import { + HttpMessageBody, + HttpMethod, + QueryParams, + httpClient, + HttpRequest, +} from '@activepieces/pieces-common'; +import { + AccountCustomFieldsResponse, + ContactCustomFieldsResponse, + ContactList, + CreateAccountRequest, + CreateContactRequest, + CreateWebhookRequest, + CreateWebhookResponse, + ListAccountsResponse, + ListContactsResponse, + ListTagsResponse, +} from './types'; + +function emptyValueFilter(accessor: (key: string) => any): (key: string) => boolean { + return (key: string) => { + const val = accessor(key); + return val !== null && val !== undefined && (typeof val != 'string' || val.length > 0); + }; +} + +export function prepareQuery(request?: Record): QueryParams { + const params: QueryParams = {}; + if (!request) return params; + Object.keys(request) + .filter(emptyValueFilter((k) => request[k])) + .forEach((k: string) => { + params[k] = (request as Record)[k].toString(); + }); + return params; +} + +export class ActiveCampaignClient { + constructor(private apiUrl: string, private apiKey: string) {} + + async makeRequest( + method: HttpMethod, + resourceUri: string, + query?: QueryParams, + body: any | undefined = undefined, + ): Promise { + const baseUrl = this.apiUrl.replace(/\/$/, ''); + const request: HttpRequest = { + method: method, + url: baseUrl + '/api/3' + resourceUri, + headers: { + 'Api-Token': this.apiKey, + }, + queryParams: query, + body: body, + }; + const res = await httpClient.sendRequest(request); + return res.body; + } + + async authenticate() { + return await this.makeRequest(HttpMethod.GET, '/users/me'); + } + + async subscribeWebhook(request: CreateWebhookRequest): Promise { + return await this.makeRequest(HttpMethod.POST, '/webhooks', undefined, { + webhook: request, + }); + } + + async unsubscribeWebhook(webhookId: string) { + return await this.makeRequest(HttpMethod.DELETE, `/webhooks/${webhookId}`); + } + + async listContactLists() { + return await this.makeRequest<{ lists: ContactList[] }>( + HttpMethod.GET, + '/lists', + prepareQuery({ limit: 20 }), + ); + } + + async createAccount(request: CreateAccountRequest) { + return await this.makeRequest(HttpMethod.POST, '/accounts', undefined, { account: request }); + } + + async updateAccount(accountId: number, request: Partial) { + return await this.makeRequest(HttpMethod.PUT, `/accounts/${accountId}`, undefined, { + account: request, + }); + } + + async listAccounts(search?: string): Promise { + return await this.makeRequest( + HttpMethod.GET, + '/accounts', + prepareQuery({ search: search }), + ); + } + + async listAccountCustomFields() { + return await this.makeRequest<{ accountCustomFieldMeta: AccountCustomFieldsResponse[] }>( + HttpMethod.GET, + '/accountCustomFieldMeta', + ); + } + + async createContact(request: CreateContactRequest) { + return await this.makeRequest(HttpMethod.POST, '/contacts', undefined, { contact: request }); + } + + async updateContact(contactId: number, request: Partial) { + return await this.makeRequest(HttpMethod.PUT, `/contacts/${contactId}`, undefined, { + contact: request, + }); + } + + async listContacts(): Promise { + return await this.makeRequest(HttpMethod.GET, '/contacts'); + } + + async listContactCustomFields(): Promise { + return await this.makeRequest(HttpMethod.GET, '/fields'); + } + + async addContactToList(listId: string, contactId: string, status: string) { + return await this.makeRequest(HttpMethod.POST, '/contactLists', undefined, { + contactList: { list: listId, contact: contactId, status: status }, + }); + } + + async createAccountContactAssociation(contactId: number, accountId: number, jobTitle?: string) { + return await this.makeRequest(HttpMethod.POST, '/accountContacts', undefined, { + accountContact: { contact: contactId, account: accountId, jobTitle: jobTitle }, + }); + } + + async addTagToContact(contactId: string, tagId: string) { + return await this.makeRequest(HttpMethod.POST, '/contactTags', undefined, { + contactTag: { contact: contactId, tag: tagId }, + }); + } + + async listTags(): Promise { + return await this.makeRequest(HttpMethod.GET, '/tags'); + } +} diff --git a/packages/pieces/community/activecampaign/src/lib/common/constants.ts b/packages/pieces/community/activecampaign/src/lib/common/constants.ts new file mode 100644 index 0000000..e5b224c --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/common/constants.ts @@ -0,0 +1,16 @@ +export const WEBHOOK_SOURCES = ['public', 'admin', 'api', 'system']; + +export enum CUSTOM_FIELD_TYPE { + TEXT = 'text', + DROPDOWN = 'dropdown', + TEXTAREA = 'textarea', + NUMBER = 'number', + MONEY = 'currency', + DATE = 'date', + DATETIME = 'datetime', + LIST_BOX = 'listbox', + MULTISELECT = 'multiselect', + RADIO = 'radio', + CHECKBOX = 'checkbox', + HIDDEN = 'hidden', +} diff --git a/packages/pieces/community/activecampaign/src/lib/common/index.ts b/packages/pieces/community/activecampaign/src/lib/common/index.ts new file mode 100644 index 0000000..ffd8d6e --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/common/index.ts @@ -0,0 +1,295 @@ +import { activeCampaignAuth } from '../../'; +import { DynamicPropsValue, PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { ActiveCampaignClient } from './client'; +import { CUSTOM_FIELD_TYPE } from './constants'; + +export function makeClient(auth: PiecePropValueSchema) { + const client = new ActiveCampaignClient(auth.apiUrl, auth.apiKey); + return client; +} + +export const activecampaignCommon = { + listId: (required = false) => + Property.Dropdown({ + displayName: 'List', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listContactLists(); + + return { + disabled: false, + options: res.lists.map((list) => { + return { + label: list.name, + value: list.id, + }; + }), + }; + }, + }), + accountId: Property.Dropdown({ + displayName: 'Account ID', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listAccounts(); + + return { + disabled: false, + options: res.accounts.map((account) => { + return { + label: account.name, + value: account.id, + }; + }), + }; + }, + }), + contactId: Property.Dropdown({ + displayName: 'Contact ID', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listContacts(); + + return { + disabled: false, + options: res.contacts.map((contact) => { + return { + label: `${contact.firstName} ${contact.lastName}` ?? contact.email, + value: contact.id, + }; + }), + }; + }, + }), + tagId: Property.Dropdown({ + displayName: 'Tag ID', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listTags(); + + return { + disabled: false, + options: res.tags.map((tag) => { + return { + label: tag.tag, + value: tag.id, + }; + }), + }; + }, + }), + accountCustomFields: Property.DynamicProperties({ + displayName: 'Account Custom Fields', + refreshers: [], + required: true, + props: async ({ auth }) => { + if (!auth) return {}; + + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listAccountCustomFields(); + + const fields: DynamicPropsValue = {}; + + for (const field of res.accountCustomFieldMeta) { + switch (field.fieldType) { + case CUSTOM_FIELD_TYPE.TEXT: + case CUSTOM_FIELD_TYPE.HIDDEN: + fields[field.id] = Property.ShortText({ + displayName: field.fieldLabel, + required: false, + }); + break; + case CUSTOM_FIELD_TYPE.TEXTAREA: + fields[field.id] = Property.LongText({ + displayName: field.fieldLabel, + required: false, + }); + break; + case CUSTOM_FIELD_TYPE.DATE: + fields[field.id] = Property.DateTime({ + displayName: field.fieldLabel, + description: 'Please use YYYY-MM-DD format.', + required: false, + }); + break; + case CUSTOM_FIELD_TYPE.DATETIME: + fields[field.id] = Property.DateTime({ + displayName: field.fieldLabel, + required: false, + }); + break; + case CUSTOM_FIELD_TYPE.MONEY: + case CUSTOM_FIELD_TYPE.NUMBER: + fields[field.id] = Property.Number({ + displayName: field.fieldLabel, + required: false, + }); + break; + case CUSTOM_FIELD_TYPE.DROPDOWN: + case CUSTOM_FIELD_TYPE.RADIO: + fields[field.id] = Property.StaticDropdown({ + displayName: field.fieldLabel, + required: false, + options: { + disabled: false, + options: field.fieldOptions + ? field.fieldOptions?.map((option) => { + return { + label: option, + value: option, + }; + }) + : [], + }, + }); + break; + case CUSTOM_FIELD_TYPE.CHECKBOX: + case CUSTOM_FIELD_TYPE.LIST_BOX: + case CUSTOM_FIELD_TYPE.MULTISELECT: + fields[field.id] = Property.StaticMultiSelectDropdown({ + displayName: field.fieldLabel, + required: false, + options: { + disabled: false, + options: field.fieldOptions + ? field.fieldOptions?.map((option) => { + return { + label: option, + value: option, + }; + }) + : [], + }, + }); + break; + } + } + return fields; + }, + }), + contactCustomFields: Property.DynamicProperties({ + displayName: 'Contact Custom Fields', + refreshers: [], + required: true, + props: async ({ auth }) => { + if (!auth) return {}; + + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listContactCustomFields(); + + const fields: DynamicPropsValue = {}; + + for (const field of res.fields) { + switch (field.type) { + case CUSTOM_FIELD_TYPE.TEXT: + case CUSTOM_FIELD_TYPE.HIDDEN: + fields[field.id] = Property.ShortText({ + displayName: field.title, + required: false, + }); + break; + case CUSTOM_FIELD_TYPE.TEXTAREA: + fields[field.id] = Property.LongText({ + displayName: field.title, + required: false, + }); + break; + case CUSTOM_FIELD_TYPE.DATE: + fields[field.id] = Property.DateTime({ + displayName: field.title, + description: 'Please use YYYY-MM-DD format.', + required: false, + }); + break; + case CUSTOM_FIELD_TYPE.DATETIME: + fields[field.id] = Property.DateTime({ + displayName: field.title, + required: false, + }); + break; + case CUSTOM_FIELD_TYPE.MONEY: + case CUSTOM_FIELD_TYPE.NUMBER: + fields[field.id] = Property.Number({ + displayName: field.title, + required: false, + }); + break; + case CUSTOM_FIELD_TYPE.DROPDOWN: + case CUSTOM_FIELD_TYPE.RADIO: + fields[field.id] = Property.StaticDropdown({ + displayName: field.title, + required: false, + options: { + disabled: false, + options: res.fieldOptions + .filter((option) => option.field === field.id) + .map((option) => { + return { + label: option.label, + value: option.value, + }; + }), + }, + }); + break; + case CUSTOM_FIELD_TYPE.CHECKBOX: + case CUSTOM_FIELD_TYPE.LIST_BOX: + case CUSTOM_FIELD_TYPE.MULTISELECT: + fields[field.id] = Property.StaticMultiSelectDropdown({ + displayName: field.title, + required: false, + options: { + disabled: false, + options: res.fieldOptions + .filter((option) => option.field === field.id) + .map((option) => { + return { + label: option.label, + value: option.value, + }; + }), + }, + }); + break; + } + } + return fields; + }, + }), +}; diff --git a/packages/pieces/community/activecampaign/src/lib/common/types.ts b/packages/pieces/community/activecampaign/src/lib/common/types.ts new file mode 100644 index 0000000..6a30c63 --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/common/types.ts @@ -0,0 +1,83 @@ +import { CUSTOM_FIELD_TYPE } from './constants'; + +export type CreateWebhookRequest = { + name: string; + url: string; + events: string[]; + sources: string[]; + listid?: string; +}; + +export type CreateWebhookResponse = { + webhook: { + name: string; + url: string; + events: string[]; + sources: string[]; + listid: string; + cdate: string; + state: string; + id: string; + }; +}; + +export type ContactList = { + id: string; + name: string; +}; + +export type CreateAccountRequest = { + name: string; + accountUrl?: string; + fields?: { + customFieldId: number; + fieldValue: any; + }[]; +}; + +export type CreateContactRequest = { + email: string; + firstName?: string; + lastName?: string; + phone?: string; + fieldValues: { + field: string; + value: any; + }[]; +}; +export type ListAccountsResponse = { + accounts: { + name: string; + id: string; + }[]; +}; + +export type ListContactsResponse = { + contacts: { + email: string; + firstName: string; + lastName: string; + id: string; + }[]; +}; + +export type ListTagsResponse = { + tags: { + tagType: string; + tag: string; + id: string; + }[]; +}; +export type AccountCustomFieldsResponse = { + id: string; + fieldLabel: string; + fieldType: CUSTOM_FIELD_TYPE; + fieldOptions?: string[]; + fieldDefaultCurrency?: string; + fieldDefault?: number | string | string[]; +}; + +export type ContactCustomFieldsResponse = { + fieldOptions: { field: string; value: string; label: string; id: string }[]; + fields: { id: string; title: string; type: CUSTOM_FIELD_TYPE; options: string[] }[]; +}; diff --git a/packages/pieces/community/activecampaign/src/lib/triggers/campaign-link-clicked.ts b/packages/pieces/community/activecampaign/src/lib/triggers/campaign-link-clicked.ts new file mode 100644 index 0000000..e4dd721 --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/triggers/campaign-link-clicked.ts @@ -0,0 +1,84 @@ +import { activeCampaignAuth } from '../..'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { makeClient } from '../common'; +import { CreateWebhookResponse } from '../common/types'; +import { WEBHOOK_SOURCES } from '../common/constants'; + +export const campaignLinkClickedTrigger = createTrigger({ + auth: activeCampaignAuth, + name: 'activecampaign_campaign_link_clicked', + displayName: 'New Campaign Link Click', + description: + 'Triggers when a contact clicks a link in a campaign message (will only run once for each unique link).', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.subscribeWebhook({ + name: `Activepieces Deal Task Completed Hook`, + url: context.webhookUrl, + events: ['click'], + sources: WEBHOOK_SOURCES, + }); + await context.store.put('activecampaign_campaign_link_clicked', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'activecampaign_campaign_link_clicked', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.unsubscribeWebhook(webhook.webhook.id); + } + }, + sampleData: { + type: 'deal_update', + date_time: '2024-02-28T04:45:41-06:00', + initiated_from: 'admin', + initiated_by: 'admin', + list: '0', + contact: { + id: '3', + email: 'code.test@gmail.com', + first_name: 'John', + last_name: 'Wick', + phone: '', + ip: '0.0.0.0', + tags: '1233', + customer_acct_name: '', + orgname: '', + }, + customer_acct_name: '', + customer_acct_id: '0', + orgname: '', + deal: { + id: '1', + title: 'Test Deal', + create_date: '2024-02-28 04:36:09', + create_date_iso: '2024-02-28T04:36:09-06:00', + orgid: '1', + orgname: 'John Wick', + stageid: '1', + stage_title: 'To Contact', + pipelineid: '1', + pipeline_title: 'Test Pipeline', + value: '1,044,055.00', + value_raw: '1044055', + currency: 'usd', + currency_symbol: '$', + owner: '1', + owner_firstname: 'John', + owner_lastname: 'Wick', + contactid: '3', + contact_email: 'code.test@gmail.com', + contact_firstname: 'John', + contact_lastname: 'Wick', + status: '0', + fields: [{ id: '1', key: 'Forecasted Close Date', value: '2024-02-08 00:00:00' }], + }, + updated_fields: ['value'], + }, +}); diff --git a/packages/pieces/community/activecampaign/src/lib/triggers/deal-task-completed.ts b/packages/pieces/community/activecampaign/src/lib/triggers/deal-task-completed.ts new file mode 100644 index 0000000..ef5b109 --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/triggers/deal-task-completed.ts @@ -0,0 +1,83 @@ +import { activeCampaignAuth } from '../..'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { makeClient } from '../common'; +import { CreateWebhookResponse } from '../common/types'; +import { WEBHOOK_SOURCES } from '../common/constants'; + +export const dealTaskCompletedTrigger = createTrigger({ + auth: activeCampaignAuth, + name: 'activecampaign_deal_task_completed', + displayName: 'Deal Task Completed', + description: 'Triggers when a deal task has been completed.', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.subscribeWebhook({ + name: `Activepieces Deal Task Completed Hook`, + url: context.webhookUrl, + events: ['deal_task_complete'], + sources: WEBHOOK_SOURCES, + }); + await context.store.put('activecampaign_deal_task_completed', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'activecampaign_deal_task_completed', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.unsubscribeWebhook(webhook.webhook.id); + } + }, + sampleData: { + type: 'deal_update', + date_time: '2024-02-28T04:45:41-06:00', + initiated_from: 'admin', + initiated_by: 'admin', + list: '0', + contact: { + id: '3', + email: 'code.test@gmail.com', + first_name: 'John', + last_name: 'Wick', + phone: '', + ip: '0.0.0.0', + tags: '1233', + customer_acct_name: '', + orgname: '', + }, + customer_acct_name: '', + customer_acct_id: '0', + orgname: '', + deal: { + id: '1', + title: 'Test Deal', + create_date: '2024-02-28 04:36:09', + create_date_iso: '2024-02-28T04:36:09-06:00', + orgid: '1', + orgname: 'John Wick', + stageid: '1', + stage_title: 'To Contact', + pipelineid: '1', + pipeline_title: 'Test Pipeline', + value: '1,044,055.00', + value_raw: '1044055', + currency: 'usd', + currency_symbol: '$', + owner: '1', + owner_firstname: 'John', + owner_lastname: 'Wick', + contactid: '3', + contact_email: 'code.test@gmail.com', + contact_firstname: 'John', + contact_lastname: 'Wick', + status: '0', + fields: [{ id: '1', key: 'Forecasted Close Date', value: '2024-02-08 00:00:00' }], + }, + updated_fields: ['value'], + }, +}); diff --git a/packages/pieces/community/activecampaign/src/lib/triggers/new-campaign-bounce.ts b/packages/pieces/community/activecampaign/src/lib/triggers/new-campaign-bounce.ts new file mode 100644 index 0000000..b2f40a2 --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/triggers/new-campaign-bounce.ts @@ -0,0 +1,83 @@ +import { activeCampaignAuth } from '../..'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { makeClient } from '../common'; +import { CreateWebhookResponse } from '../common/types'; +import { WEBHOOK_SOURCES } from '../common/constants'; + +export const newCampaignBounceTrigger = createTrigger({ + auth: activeCampaignAuth, + name: 'activecampaign_new_campaign_bounce', + displayName: 'New Campaign Bounce', + description: 'Triggers when a contact email address bounces from a sent campaign.', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.subscribeWebhook({ + name: `Activepieces Campaign Bounce Hook`, + url: context.webhookUrl, + events: ['bounce'], + sources: WEBHOOK_SOURCES, + }); + await context.store.put('activecampaign_new_campaign_bounce', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'activecampaign_new_campaign_bounce', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.unsubscribeWebhook(webhook.webhook.id); + } + }, + sampleData: { + type: 'deal_update', + date_time: '2024-02-28T04:45:41-06:00', + initiated_from: 'admin', + initiated_by: 'admin', + list: '0', + contact: { + id: '3', + email: 'code.test@gmail.com', + first_name: 'John', + last_name: 'Wick', + phone: '', + ip: '0.0.0.0', + tags: '1233', + customer_acct_name: '', + orgname: '', + }, + customer_acct_name: '', + customer_acct_id: '0', + orgname: '', + deal: { + id: '1', + title: 'Test Deal', + create_date: '2024-02-28 04:36:09', + create_date_iso: '2024-02-28T04:36:09-06:00', + orgid: '1', + orgname: 'John Wick', + stageid: '1', + stage_title: 'To Contact', + pipelineid: '1', + pipeline_title: 'Test Pipeline', + value: '1,044,055.00', + value_raw: '1044055', + currency: 'usd', + currency_symbol: '$', + owner: '1', + owner_firstname: 'John', + owner_lastname: 'Wick', + contactid: '3', + contact_email: 'code.test@gmail.com', + contact_firstname: 'John', + contact_lastname: 'Wick', + status: '0', + fields: [{ id: '1', key: 'Forecasted Close Date', value: '2024-02-08 00:00:00' }], + }, + updated_fields: ['value'], + }, +}); diff --git a/packages/pieces/community/activecampaign/src/lib/triggers/new-contact-note.ts b/packages/pieces/community/activecampaign/src/lib/triggers/new-contact-note.ts new file mode 100644 index 0000000..b08d601 --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/triggers/new-contact-note.ts @@ -0,0 +1,58 @@ +import { activeCampaignAuth } from '../..'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { makeClient } from '../common'; +import { CreateWebhookResponse } from '../common/types'; +import { WEBHOOK_SOURCES } from '../common/constants'; + +export const newContactNoteTrigger = createTrigger({ + auth: activeCampaignAuth, + name: 'activecampaign_new_contact_note', + displayName: 'New Contact Note', + description: 'Triggers when a new contact note is added.', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.subscribeWebhook({ + name: `Activepieces New Contact Note Hook`, + url: context.webhookUrl, + events: ['subscriber_note'], + sources: WEBHOOK_SOURCES, + }); + await context.store.put('activecampaign_new_contact_note', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'activecampaign_new_contact_note', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.unsubscribeWebhook(webhook.webhook.id); + } + }, + sampleData: { + type: 'subscriber_note', + date_time: '2024-02-28T06:58:11-06:00', + initiated_from: 'admin', + initiated_by: 'admin', + list: '0', + note: 'test note', + contact: { + id: '3', + email: 'code.test@gmail.com', + first_name: 'john', + last_name: 'wick', + phone: '', + ip: '0.0.0.0', + tags: '1233', + customer_acct_name: '', + orgname: '', + }, + customer_acct_name: '', + customer_acct_id: '0', + orgname: '', + }, +}); diff --git a/packages/pieces/community/activecampaign/src/lib/triggers/new-contact-task.ts b/packages/pieces/community/activecampaign/src/lib/triggers/new-contact-task.ts new file mode 100644 index 0000000..54cca39 --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/triggers/new-contact-task.ts @@ -0,0 +1,68 @@ +import { activeCampaignAuth } from '../..'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { makeClient } from '../common'; +import { CreateWebhookResponse } from '../common/types'; +import { WEBHOOK_SOURCES } from '../common/constants'; + +export const newContactTaskTrigger = createTrigger({ + auth: activeCampaignAuth, + name: 'activecampaign_new_contact_task', + displayName: 'New Contact Task', + description: 'Triggers when a new contact task is added.', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.subscribeWebhook({ + name: `Activepieces New Contact Task Hook`, + url: context.webhookUrl, + events: ['contact_task_add'], + sources: WEBHOOK_SOURCES, + }); + await context.store.put('activecampaign_new_contact_task', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'activecampaign_new_contact_task', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.unsubscribeWebhook(webhook.webhook.id); + } + }, + sampleData: { + type: 'contact_task_add', + date_time: '2024-02-28T07:00:57-06:00', + initiated_from: 'admin', + initiated_by: 'admin', + list: '0', + contact: { + id: '3', + email: 'code.test@gmail.com', + first_name: 'John', + last_name: 'Wick', + phone: '', + ip: '0.0.0.0', + tags: '1233', + customer_acct_name: '', + orgname: '', + }, + customer_acct_name: '', + customer_acct_id: '0', + orgname: '', + task: { + id: '7', + type_id: '1', + title: 'test task', + note: 'Desc', + duedate: '2024-02-28 07:00:48', + duedate_iso: '2024-02-28T07:00:48-06:00', + edate: '2024-02-28 07:15:48', + edate_iso: '2024-02-28T07:15:48-06:00', + type_title: 'Call', + }, + }, +}); diff --git a/packages/pieces/community/activecampaign/src/lib/triggers/new-deal-added-or-updated.ts b/packages/pieces/community/activecampaign/src/lib/triggers/new-deal-added-or-updated.ts new file mode 100644 index 0000000..3e348b7 --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/triggers/new-deal-added-or-updated.ts @@ -0,0 +1,83 @@ +import { activeCampaignAuth } from '../..'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { makeClient } from '../common'; +import { CreateWebhookResponse } from '../common/types'; +import { WEBHOOK_SOURCES } from '../common/constants'; + +export const newDealAddedOrUpdatedTrigger = createTrigger({ + auth: activeCampaignAuth, + name: 'activecampaign_new_deal_added_or_updated', + displayName: 'New Deal Added or Updated', + description: 'Triggers when a new deal is created or existing deal is updated.', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.subscribeWebhook({ + name: `Activepieces New Deal Hook`, + url: context.webhookUrl, + events: ['deal_add', 'deal_update'], + sources: WEBHOOK_SOURCES, + }); + await context.store.put('activecampaign_new_deal_added_or_updated', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'activecampaign_new_deal_added_or_updated', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.unsubscribeWebhook(webhook.webhook.id); + } + }, + sampleData: { + type: 'deal_update', + date_time: '2024-02-28T04:45:41-06:00', + initiated_from: 'admin', + initiated_by: 'admin', + list: '0', + contact: { + id: '3', + email: 'code.test@gmail.com', + first_name: 'John', + last_name: 'Wick', + phone: '', + ip: '0.0.0.0', + tags: '1233', + customer_acct_name: '', + orgname: '', + }, + customer_acct_name: '', + customer_acct_id: '0', + orgname: '', + deal: { + id: '1', + title: 'Test Deal', + create_date: '2024-02-28 04:36:09', + create_date_iso: '2024-02-28T04:36:09-06:00', + orgid: '1', + orgname: 'John Wick', + stageid: '1', + stage_title: 'To Contact', + pipelineid: '1', + pipeline_title: 'Test Pipeline', + value: '1,044,055.00', + value_raw: '1044055', + currency: 'usd', + currency_symbol: '$', + owner: '1', + owner_firstname: 'John', + owner_lastname: 'Wick', + contactid: '3', + contact_email: 'code.test@gmail.com', + contact_firstname: 'John', + contact_lastname: 'Wick', + status: '0', + fields: [{ id: '1', key: 'Forecasted Close Date', value: '2024-02-08 00:00:00' }], + }, + updated_fields: ['value'], + }, +}); diff --git a/packages/pieces/community/activecampaign/src/lib/triggers/new-deal-note.ts b/packages/pieces/community/activecampaign/src/lib/triggers/new-deal-note.ts new file mode 100644 index 0000000..0a5802c --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/triggers/new-deal-note.ts @@ -0,0 +1,67 @@ +import { activeCampaignAuth } from '../..'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { makeClient } from '../common'; +import { CreateWebhookResponse } from '../common/types'; +import { WEBHOOK_SOURCES } from '../common/constants'; + +export const newDealNoteTrigger = createTrigger({ + auth: activeCampaignAuth, + name: 'activecampaign_new_deal_note', + displayName: 'New Deal Note', + description: 'Triggers when a new deal note is created.', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.subscribeWebhook({ + name: `Activepieces New Deal Note Hook`, + url: context.webhookUrl, + events: ['deal_note_add'], + sources: WEBHOOK_SOURCES, + }); + await context.store.put('activecampaign_new_deal_note', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get('activecampaign_new_deal_note'); + if (webhook != null) { + const client = makeClient(context.auth); + await client.unsubscribeWebhook(webhook.webhook.id); + } + }, + sampleData: { + type: 'deal_note_add', + date_time: '2024-02-28T05:58:27-06:00', + initiated_from: 'admin', + initiated_by: 'admin', + list: '0', + note: { id: '1', text: 'Tst node' }, + deal: { + id: '1', + title: 'Test Deal updated', + create_date: '2024-02-28 04:36:09', + create_date_iso: '2024-02-28T04:36:09-06:00', + orgid: '1', + orgname: 'John wick', + stageid: '1', + stage_title: 'To Contact', + pipelineid: '1', + pipeline_title: 'Test Pipeline', + value: '14,055.00', + value_raw: '14055', + currency: 'usd', + currency_symbol: '$', + owner: '1', + owner_firstname: 'John', + owner_lastname: 'wick', + contactid: '3', + contact_email: 'code.test@gmail.com', + contact_firstname: 'John', + contact_lastname: 'wick', + status: '0', + fields: [{ id: '1', key: 'Forecasted Close Date', value: '2024-02-08 00:00:00' }], + }, + }, +}); diff --git a/packages/pieces/community/activecampaign/src/lib/triggers/new-deal-task.ts b/packages/pieces/community/activecampaign/src/lib/triggers/new-deal-task.ts new file mode 100644 index 0000000..949102f --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/triggers/new-deal-task.ts @@ -0,0 +1,91 @@ +import { activeCampaignAuth } from '../..'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { makeClient } from '../common'; +import { CreateWebhookResponse } from '../common/types'; +import { WEBHOOK_SOURCES } from '../common/constants'; + +export const newDealTaskTrigger = createTrigger({ + auth: activeCampaignAuth, + name: 'activecampaign_new_deal_task', + displayName: 'New Deal Task', + description: 'Triggers when a new deal task is created.', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.subscribeWebhook({ + name: `Activepieces New Deal Task Hook`, + url: context.webhookUrl, + events: ['deal_task_add'], + sources: WEBHOOK_SOURCES, + }); + await context.store.put('activecampaign_new_deal_task', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get('activecampaign_new_deal_task'); + if (webhook != null) { + const client = makeClient(context.auth); + await client.unsubscribeWebhook(webhook.webhook.id); + } + }, + sampleData: { + type: 'deal_task_add', + date_time: '2024-02-28T06:38:49-06:00', + initiated_from: 'admin', + initiated_by: 'admin', + list: '0', + contact: { + id: '3', + email: 'code.test@gmail.com', + first_name: 'john', + last_name: 'wick', + phone: '', + ip: '0.0.0.0', + tags: '1233', + customer_acct_name: '', + orgname: '', + }, + customer_acct_name: '', + customer_acct_id: '0', + orgname: '', + deal: { + id: '1', + title: 'Test Deal updated', + create_date: '2024-02-28 04:36:09', + create_date_iso: '2024-02-28T04:36:09-06:00', + orgid: '1', + orgname: 'john wick', + stageid: '1', + stage_title: 'To Contact', + pipelineid: '1', + pipeline_title: 'Test Pipeline', + value: '14,055.00', + value_raw: '14055', + currency: 'usd', + currency_symbol: '$', + owner: '1', + owner_firstname: 'john', + owner_lastname: 'wick', + contactid: '3', + contact_email: 'code.test@gmail.com', + contact_firstname: 'john', + contact_lastname: 'wick', + status: '0', + fields: [{ id: '1', key: 'Forecasted Close Date', value: '2024-02-08 00:00:00' }], + }, + task: { + id: '6', + type_id: '1', + title: 'TEST TASK', + note: 'fsfssssf', + duedate: '2024-02-28 06:38:41', + duedate_iso: '2024-02-28T06:38:41-06:00', + edate: '2024-02-28 06:53:41', + edate_iso: '2024-02-28T06:53:41-06:00', + type_title: 'Call', + }, + }, +}); diff --git a/packages/pieces/community/activecampaign/src/lib/triggers/new-or-updated-account.ts b/packages/pieces/community/activecampaign/src/lib/triggers/new-or-updated-account.ts new file mode 100644 index 0000000..9d51ec3 --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/triggers/new-or-updated-account.ts @@ -0,0 +1,75 @@ +import { activeCampaignAuth } from '../..'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { makeClient } from '../common'; +import { CreateWebhookResponse } from '../common/types'; +import { WEBHOOK_SOURCES } from '../common/constants'; + +export const newOrUpdatedAccountTrigger = createTrigger({ + auth: activeCampaignAuth, + name: 'activecampaign_new_or_updated_account', + displayName: 'New or Updated Account', + description: 'Triggers when a new account is added or an existing account’s details are updated', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.subscribeWebhook({ + name: `Activepieces New Account Hook`, + url: context.webhookUrl, + events: ['account_add', 'account_update'], + sources: WEBHOOK_SOURCES, + }); + await context.store.put('activecampaign_new_or_updated_account', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'activecampaign_new_or_updated_account', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.unsubscribeWebhook(webhook.webhook.id); + } + }, + sampleData: { + type: 'account_update', + date_time: '2024-02-28T06:44:32-06:00', + initiated_from: 'admin', + initiated_by: 'admin', + list: '0', + account: { + id: '1', + name: 'John Wick', + account_url: 'https://www.github.com', + created_timestamp: '2024-02-28 01:09:17', + updated_timestamp: '2024-02-28 06:44:32', + fields: { + '0': { id: '1', key: 'Description', value: 'Desc' }, + '1': { id: '2', key: 'Address 1', value: 'Address 1' }, + '2': { id: '3', key: 'Address 2', value: 'Address 2' }, + '3': { id: '4', key: 'City', value: 'City' }, + '4': { id: '5', key: 'State/Province', value: 'State' }, + '5': { id: '6', key: 'Postal Code', value: '75156' }, + '6': { id: '7', key: 'Country', value: 'India' }, + '7': { id: '8', key: 'Number of Employees', value: '101 - 500' }, + '8': { id: '9', key: 'Annual Revenue', value: 'Less than 100K' }, + '9': { id: '10', key: 'Industry/Vertical', value: 'Accounting/Financial' }, + '10': { key: 'Phone Number', value: '' }, + '11': { id: '11', key: 'Text Input', value: 'Text Input' }, + '12': { id: '12', key: 'Text Area', value: 'Text Area' }, + '13': { id: '13', key: 'Number', value: '18.000' }, + '14': { id: '14', key: 'money', value: '18' }, + '15': { id: '15', key: 'Date', value: '2024-02-28 00:00:00' }, + '16': { key: 'Date Time', value: '' }, + '17': { id: '16', key: 'Drop Down', value: 'Option 3' }, + '18': { id: '17', key: 'List box', value: ['Option 1', 'Option 2'] }, + '19': { id: '18', key: 'Radio Buttons', value: 'Option 1' }, + '20': { id: '19', key: 'Check Box', value: ['Option 1', 'Option 2'] }, + '21': { id: '20', key: 'Hidden', value: 'Hidden' }, + }, + }, + updated_fields: ['name'], + }, +}); diff --git a/packages/pieces/community/activecampaign/src/lib/triggers/tag-added-or-removed-from-contact.ts b/packages/pieces/community/activecampaign/src/lib/triggers/tag-added-or-removed-from-contact.ts new file mode 100644 index 0000000..f83b5f5 --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/triggers/tag-added-or-removed-from-contact.ts @@ -0,0 +1,61 @@ +import { activeCampaignAuth } from '../..'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { makeClient } from '../common'; +import { CreateWebhookResponse } from '../common/types'; +import { WEBHOOK_SOURCES } from '../common/constants'; + +export const newtagAddedOrRemovedFromContactTrigger = createTrigger({ + auth: activeCampaignAuth, + name: 'activecampaign_new_tag_added_or_removed_from_contact', + displayName: 'Tag Added or Removed From Contact', + description: 'Triggers when a a Tag is added or removed from a Contact', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.subscribeWebhook({ + name: `Activepieces Contact Tag Hook`, + url: context.webhookUrl, + events: ['contact_tag_added', 'contact_tag_removed'], + sources: WEBHOOK_SOURCES, + }); + await context.store.put( + 'activecampaign_new_tag_added_or_removed_from_contact', + res, + ); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'activecampaign_new_tag_added_or_removed_from_contact', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.unsubscribeWebhook(webhook.webhook.id); + } + }, + sampleData: { + type: 'contact_tag_added', + date_time: '2024-02-28T07:04:13-06:00', + initiated_from: 'admin', + initiated_by: 'admin', + list: '0', + contact: { + id: '3', + email: 'code.test@gmail.com', + first_name: 'John', + last_name: 'Wick', + phone: '', + ip: '0.0.0.0', + tags: 'tag1, tag2', + customer_acct_name: '', + orgname: '', + }, + customer_acct_name: '', + customer_acct_id: '0', + orgname: '', + tag: 'tag2', + }, +}); diff --git a/packages/pieces/community/activecampaign/src/lib/triggers/updated-contact.ts b/packages/pieces/community/activecampaign/src/lib/triggers/updated-contact.ts new file mode 100644 index 0000000..d20392a --- /dev/null +++ b/packages/pieces/community/activecampaign/src/lib/triggers/updated-contact.ts @@ -0,0 +1,58 @@ +import { activeCampaignAuth } from '../..'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { makeClient } from '../common'; +import { CreateWebhookResponse } from '../common/types'; +import { WEBHOOK_SOURCES } from '../common/constants'; + +export const updatedContactTrigger = createTrigger({ + auth: activeCampaignAuth, + name: 'activecampaign_updated_contact', + displayName: 'Updated Contact', + description: 'Triggers when an existing contact details are updated.', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.subscribeWebhook({ + name: `Activepieces Updated Contact Hook`, + url: context.webhookUrl, + events: ['update'], + sources: WEBHOOK_SOURCES, + }); + await context.store.put('activecampaign_updated_contact', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'activecampaign_updated_contact', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.unsubscribeWebhook(webhook.webhook.id); + } + }, + sampleData: { + type: 'update', + date_time: '2024-02-28T07:16:48-06:00', + initiated_from: 'admin', + initiated_by: 'admin', + list: '0', + contact: { + id: '3', + email: 'code.test@gmail.com', + first_name: 'John', + last_name: 'Wick', + phone: '', + ip: '0.0.0.0', + tags: 'tag1, tag2', + fields: ['Option 1', '||Option 1||Option 2||'], + customer_acct_name: '', + orgname: '', + }, + customer_acct_name: '', + customer_acct_id: '0', + orgname: '', + }, +}); diff --git a/packages/pieces/community/activecampaign/tsconfig.json b/packages/pieces/community/activecampaign/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/activecampaign/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/activecampaign/tsconfig.lib.json b/packages/pieces/community/activecampaign/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/activecampaign/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/activepieces/.eslintrc.json b/packages/pieces/community/activepieces/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/activepieces/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/activepieces/README.md b/packages/pieces/community/activepieces/README.md new file mode 100644 index 0000000..261bba2 --- /dev/null +++ b/packages/pieces/community/activepieces/README.md @@ -0,0 +1,7 @@ +# pieces-activepieces + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-activepieces` to build the library. diff --git a/packages/pieces/community/activepieces/package.json b/packages/pieces/community/activepieces/package.json new file mode 100644 index 0000000..849d96d --- /dev/null +++ b/packages/pieces/community/activepieces/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-activepieces", + "version": "0.2.1" +} diff --git a/packages/pieces/community/activepieces/project.json b/packages/pieces/community/activepieces/project.json new file mode 100644 index 0000000..ead09e1 --- /dev/null +++ b/packages/pieces/community/activepieces/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-activepieces", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/activepieces/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/activepieces", + "tsConfig": "packages/pieces/community/activepieces/tsconfig.lib.json", + "packageJson": "packages/pieces/community/activepieces/package.json", + "main": "packages/pieces/community/activepieces/src/index.ts", + "assets": [ + "packages/pieces/community/activepieces/*.md", + { + "input": "packages/pieces/community/activepieces/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-activepieces {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/activepieces/src/index.ts b/packages/pieces/community/activepieces/src/index.ts new file mode 100644 index 0000000..8314f23 --- /dev/null +++ b/packages/pieces/community/activepieces/src/index.ts @@ -0,0 +1,57 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { createProject } from './lib/actions/create-project'; +import { listProject } from './lib/actions/list-project'; +import { updateProject } from './lib/actions/update-project'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; + +const markdown = ` +Activepieces Platform API is available under the Platform Edition. +(https://www.activepieces.com/docs/admin-console/overview) + +**Note**: The API Key is available in the Platform Dashboard. + +`; + +export const activePieceAuth = PieceAuth.CustomAuth({ + description: markdown, + required: true, + props: { + baseApiUrl: Property.ShortText({ + displayName: 'Base URL', + required: true, + defaultValue: 'https://cloud.activepieces.com/api/v1', + }), + apiKey: PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + }), + }, +}); + +export const activepieces = createPiece({ + displayName: 'Activepieces Platform', + description: 'Open source no-code business automation', + auth: activePieceAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/activepieces.png', + authors: ['doskyft', 'abuaboud', 'AdamSelene'], + actions: [ + createProject, + updateProject, + listProject, + createCustomApiCallAction({ + baseUrl: (auth) => { + return `${(auth as { baseApiUrl: string }).baseApiUrl}`; + }, + auth: activePieceAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as { apiKey: string }).apiKey}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/activepieces/src/lib/actions/create-project.ts b/packages/pieces/community/activepieces/src/lib/actions/create-project.ts new file mode 100644 index 0000000..358e934 --- /dev/null +++ b/packages/pieces/community/activepieces/src/lib/actions/create-project.ts @@ -0,0 +1,36 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { activePieceAuth } from '../../index'; + +export const createProject = createAction({ + name: 'create_project', + auth: activePieceAuth, + displayName: 'Create Project', + description: 'Create a new project', + props: { + display_name: Property.ShortText({ + displayName: 'Display Name', + description: undefined, + required: true, + }), + }, + async run({ propsValue, auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${auth.baseApiUrl}/projects`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.apiKey, + }, + body: { + displayName: propsValue['display_name'], + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/activepieces/src/lib/actions/list-project.ts b/packages/pieces/community/activepieces/src/lib/actions/list-project.ts new file mode 100644 index 0000000..5d06b6b --- /dev/null +++ b/packages/pieces/community/activepieces/src/lib/actions/list-project.ts @@ -0,0 +1,27 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { activePieceAuth } from '../../index'; + +export const listProject = createAction({ + name: 'list_project', + auth: activePieceAuth, + displayName: 'List Projects', + description: 'List all projects', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${auth.baseApiUrl}/projects`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.apiKey, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/activepieces/src/lib/actions/update-project.ts b/packages/pieces/community/activepieces/src/lib/actions/update-project.ts new file mode 100644 index 0000000..07ae0b1 --- /dev/null +++ b/packages/pieces/community/activepieces/src/lib/actions/update-project.ts @@ -0,0 +1,73 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { activePieceAuth } from '../../index'; + +export const updateProject = createAction({ + name: 'update_project', + auth: activePieceAuth, + displayName: 'Update Project', + description: 'Update a project', + props: { + id: Property.ShortText({ + displayName: 'Id', + description: 'Id of the project', + required: true, + }), + display_name: Property.ShortText({ + displayName: 'Display Name', + description: undefined, + required: true, + }), + notify_status: Property.StaticDropdown({ + displayName: 'Notify Status', + description: undefined, + required: true, + options: { + options: [ + { + label: 'Always notify', + value: 'ALWAYS', + }, + { + label: 'Never notify', + value: 'NEVER', + }, + ], + }, + }), + tasks: Property.Number({ + displayName: 'Tasks', + description: undefined, + required: true, + }), + team_members: Property.Number({ + displayName: 'Team Members', + description: undefined, + required: true, + }), + }, + async run({ propsValue, auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${auth.baseApiUrl}/projects/${propsValue['id']}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.apiKey, + }, + body: { + displayName: propsValue['display_name'], + notifyStatus: propsValue['notify_status'], + plan: { + tasks: propsValue['tasks'], + teamMembers: propsValue['team_members'], + }, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/activepieces/tsconfig.json b/packages/pieces/community/activepieces/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/activepieces/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/activepieces/tsconfig.lib.json b/packages/pieces/community/activepieces/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/activepieces/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/actualbudget/.eslintrc.json b/packages/pieces/community/actualbudget/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/actualbudget/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/actualbudget/README.md b/packages/pieces/community/actualbudget/README.md new file mode 100644 index 0000000..f917429 --- /dev/null +++ b/packages/pieces/community/actualbudget/README.md @@ -0,0 +1,7 @@ +# pieces-actualbudget + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-actualbudget` to build the library. diff --git a/packages/pieces/community/actualbudget/package-lock.json b/packages/pieces/community/actualbudget/package-lock.json new file mode 100644 index 0000000..1180bdd --- /dev/null +++ b/packages/pieces/community/actualbudget/package-lock.json @@ -0,0 +1,581 @@ +{ + "name": "@activepieces/piece-actualbudget", + "version": "0.0.4", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-actualbudget", + "version": "0.0.4", + "dependencies": { + "@actual-app/api": "25.3.1" + }, + "devDependencies": { + "@types/node": "^20.14.9" + } + }, + "node_modules/@actual-app/api": { + "version": "25.3.1", + "resolved": "https://registry.npmjs.org/@actual-app/api/-/api-25.3.1.tgz", + "integrity": "sha512-MlyjU7Zo+YVknjdqdJzrGTPocZRTeB71wUp5FlGwifOqIYK8BHsNJbz9xMM385l4nxzi/aBY0+GpXjbQGQU2pw==", + "dependencies": { + "@actual-app/crdt": "^2.1.0", + "better-sqlite3": "^11.7.0", + "compare-versions": "^6.1.0", + "node-fetch": "^3.3.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@actual-app/crdt": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@actual-app/crdt/-/crdt-2.1.0.tgz", + "integrity": "sha512-Qb8hMq10Wi2kYIDj0fG4uy00f9Mloghd+xQrHQiPQfgx022VPJ/No+z/bmfj4MuFH8FrPiLysSzRsj2PNQIedw==", + "dependencies": { + "google-protobuf": "^3.12.0-rc.1", + "murmurhash": "^2.0.1", + "uuid": "^9.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", + "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/better-sqlite3": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz", + "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "node_modules/google-protobuf": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz", + "integrity": "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/murmurhash": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/murmurhash/-/murmurhash-2.0.1.tgz", + "integrity": "sha512-5vQEh3y+DG/lMPM0mCGPDnyV8chYg/g7rl6v3Gd8WMF9S429ox3Xk8qrk174kWhG767KQMqqxLD1WnGd77hiew==" + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==" + }, + "node_modules/node-abi": { + "version": "3.75.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", + "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar-fs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", + "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + } + } +} diff --git a/packages/pieces/community/actualbudget/package.json b/packages/pieces/community/actualbudget/package.json new file mode 100644 index 0000000..2d20689 --- /dev/null +++ b/packages/pieces/community/actualbudget/package.json @@ -0,0 +1,10 @@ +{ + "name": "@activepieces/piece-actualbudget", + "version": "0.0.4", + "dependencies": { + "@actual-app/api": "25.3.1" + }, + "devDependencies": { + "@types/node": "^20.14.9" + } +} diff --git a/packages/pieces/community/actualbudget/project.json b/packages/pieces/community/actualbudget/project.json new file mode 100644 index 0000000..587d6e1 --- /dev/null +++ b/packages/pieces/community/actualbudget/project.json @@ -0,0 +1,76 @@ +{ + "name": "pieces-actualbudget", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/actualbudget/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/actualbudget", + "tsConfig": "packages/pieces/community/actualbudget/tsconfig.lib.json", + "packageJson": "packages/pieces/community/actualbudget/package.json", + "main": "packages/pieces/community/actualbudget/src/index.ts", + "assets": [ + "packages/pieces/community/actualbudget/*.md", + { + "input": "packages/pieces/community/actualbudget/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + }, + "dependsOn": ["prebuild", "^build"] + }, + "prebuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/pieces/community/actualbudget", + "command": "npm ci" + } + }, + "build-with-deps": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "nx run pieces-actualbudget:prebuild", + "nx run pieces-actualbudget:build", + "nx run pieces-actualbudget:postbuild" + ], + "parallel": false + } + }, + "postbuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "dist/packages/pieces/community/actualbudget", + "command": "npm install" + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/actualbudget/src/index.ts b/packages/pieces/community/actualbudget/src/index.ts new file mode 100644 index 0000000..01a28ed --- /dev/null +++ b/packages/pieces/community/actualbudget/src/index.ts @@ -0,0 +1,58 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { getBudget } from './lib/actions/get-budget'; +import { importTransaction } from './lib/actions/import-transaction'; +import { getCategories } from './lib/actions/get-categories'; +import { importTransactions } from './lib/actions/import-transactions'; +import { getAccounts } from './lib/actions/get-accounts'; +import { PieceCategory } from '@activepieces/shared'; + +export const actualBudgetAuth = PieceAuth.CustomAuth({ + description: 'Enter authentication details', + props: { + server_url: Property.ShortText({ + displayName: 'Server URL', + description: 'This is the URL of your running server', + required: true, + }), + password: PieceAuth.SecretText({ + displayName: 'Password', + description: 'This is the password you use to log into the server', + required: true, + }), + sync_id: PieceAuth.SecretText({ + displayName: 'Sync ID', + description: + 'This is the ID from Settings → Show advanced settings → Sync ID', + required: true, + }), + encryption_password: PieceAuth.SecretText({ + displayName: 'End-to-end encryption password', + description: 'if you have end-to-end encryption enabled', + required: false, + }), + }, + required: true, +}); + +export const actualbudget = createPiece({ + displayName: 'Actual Budget', + description: 'Personal finance app', + auth: actualBudgetAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/actualbudget.png', + categories: [PieceCategory.ACCOUNTING], + authors: ['hugh-codes'], + + actions: [ + getBudget, + importTransaction, + importTransactions, + getCategories, + getAccounts, + ], + triggers: [], +}); diff --git a/packages/pieces/community/actualbudget/src/lib/actions/get-accounts.ts b/packages/pieces/community/actualbudget/src/lib/actions/get-accounts.ts new file mode 100644 index 0000000..47f9e0f --- /dev/null +++ b/packages/pieces/community/actualbudget/src/lib/actions/get-accounts.ts @@ -0,0 +1,19 @@ +import { actualBudgetAuth } from '../..'; +import { createAction } from '@activepieces/pieces-framework'; +import * as api from '@actual-app/api'; +import { initializeAndDownloadBudget } from '../common/common'; + + +export const getAccounts = createAction({ + auth: actualBudgetAuth, + name: 'get_accounts', + displayName: 'Get Accounts', + description: 'Get your accounts', + props: {}, + async run(context) { + await initializeAndDownloadBudget(api, context.auth) + const accounts = await api.getAccounts(); + await api.shutdown(); + return accounts; + }, +}); diff --git a/packages/pieces/community/actualbudget/src/lib/actions/get-budget.ts b/packages/pieces/community/actualbudget/src/lib/actions/get-budget.ts new file mode 100644 index 0000000..6992460 --- /dev/null +++ b/packages/pieces/community/actualbudget/src/lib/actions/get-budget.ts @@ -0,0 +1,36 @@ +import { actualBudgetAuth } from '../..'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import * as api from '@actual-app/api'; +import { getMonths, getYears, initializeAndDownloadBudget } from '../common/common'; + + +export const getBudget = createAction({ + auth: actualBudgetAuth, + name: 'get_budget', + displayName: 'Get Budget', + description: 'Get your monthly budget', + props: { + month: Property.StaticDropdown({ + displayName: 'Month', + description: 'The month of the budget you want to get', + required: true, + options: { + options: getMonths() + } + }), + year: Property.StaticDropdown({ + displayName: 'Year', + description: 'The year of the budget you want to get', + required: true, + options: { + options: getYears() + } + }) + }, + async run(context) { + await initializeAndDownloadBudget(api, context.auth) + const budget = await api.getBudgetMonth(`${context.propsValue.year}-${context.propsValue.month}`); + await api.shutdown(); + return budget; + }, +}); diff --git a/packages/pieces/community/actualbudget/src/lib/actions/get-categories.ts b/packages/pieces/community/actualbudget/src/lib/actions/get-categories.ts new file mode 100644 index 0000000..a2f74b8 --- /dev/null +++ b/packages/pieces/community/actualbudget/src/lib/actions/get-categories.ts @@ -0,0 +1,19 @@ +import { actualBudgetAuth } from '../..'; +import { createAction } from '@activepieces/pieces-framework'; +import * as api from '@actual-app/api'; +import { initializeAndDownloadBudget } from '../common/common'; + + +export const getCategories = createAction({ + auth: actualBudgetAuth, + name: 'get_categories', + displayName: 'Get Categories', + description: 'Get your categories', + props: {}, + async run(context) { + await initializeAndDownloadBudget(api, context.auth) + const categories = await api.getCategories(); + await api.shutdown(); + return categories; + }, +}); diff --git a/packages/pieces/community/actualbudget/src/lib/actions/import-transaction.ts b/packages/pieces/community/actualbudget/src/lib/actions/import-transaction.ts new file mode 100644 index 0000000..0a0754e --- /dev/null +++ b/packages/pieces/community/actualbudget/src/lib/actions/import-transaction.ts @@ -0,0 +1,90 @@ +import { actualBudgetAuth } from '../..'; +import { + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { Transaction } from '../common/models'; +import * as api from '@actual-app/api'; +import { initializeAndDownloadBudget } from '../common/common'; + +export const importTransaction = createAction({ + auth: actualBudgetAuth, + name: 'import_transaction', + displayName: 'Import Transaction', + description: 'Add a transaction', + props: { + account_id: Property.ShortText({ + displayName: 'Account ID', + description: 'ID of the account you want to import a transaction to', + required: true, + }), + date: Property.DateTime({ + displayName: 'Date', + description: 'Date the transaction took place', + required: true, + }), + payee_name: Property.ShortText({ + displayName: 'Payee Name', + description: 'Name of the payee', + required: false, + }), + amount: Property.Number({ + displayName: 'Amount', + description: 'The dollar value of the transaction', + required: false, + }), + category: Property.ShortText({ + displayName: 'Category ID', + description: 'ID of the transaction category', + required: false, + }), + notes: Property.LongText({ + displayName: 'Notes', + description: 'Additional notes about the transaction', + required: false, + }), + imported_id: Property.ShortText({ + displayName: 'Imported ID', + description: 'Unique ID given by the bank for importing', + required: false, + }), + transfer_id: Property.ShortText({ + displayName: 'Transfer ID', + description: 'ID of the transaction in the other account for the transfer', + required: false, + }), + cleared: Property.Checkbox({ + displayName: 'Cleared', + description: 'Flag indicating if the transaction has cleared or not', + required: false, + }), + imported_payee: Property.ShortText({ + displayName: 'Imported Payee', + description: 'Raw description when importing, representing the original value', + required: false, + }), + }, + + async run({ auth, propsValue: { account_id, payee_name, date, amount, category, notes, imported_id, transfer_id, cleared, imported_payee } }) { + + const formattedDate = new Date(date).toISOString().split('T')[0]; + + const transaction: Transaction = { + payee_name, + date: formattedDate, + amount: amount !== undefined ? api.utils.amountToInteger(amount): undefined, + category, + account: account_id, + notes, + imported_id, + transfer_id, + cleared, + imported_payee, + }; + + await initializeAndDownloadBudget(api, auth) + const res = await api.importTransactions(account_id,[transaction]); + await api.shutdown(); + return res; + }, +}); diff --git a/packages/pieces/community/actualbudget/src/lib/actions/import-transactions.ts b/packages/pieces/community/actualbudget/src/lib/actions/import-transactions.ts new file mode 100644 index 0000000..4f45691 --- /dev/null +++ b/packages/pieces/community/actualbudget/src/lib/actions/import-transactions.ts @@ -0,0 +1,31 @@ +import { actualBudgetAuth } from '../..'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import * as api from '@actual-app/api'; +import { initializeAndDownloadBudget } from '../common/common'; + +export const importTransactions = createAction({ + auth: actualBudgetAuth, + name: 'import_transactions', + displayName: 'Import Transactions', + description: 'Import Transactions', + props: { + account_id: Property.ShortText({ + displayName: 'Account ID', + description: 'ID of the account you want to import a transaction to', + required: true, + }), + transactions: Property.Json({ + displayName: 'Transactions', + description: 'A json array of the transaction object', + required: true, + defaultValue: [{"payee_name": "Kroger", "date": "2026-12-25", "amount": 1200 }] + }) + }, + + async run({ auth, propsValue: { account_id, transactions } }) { + await initializeAndDownloadBudget(api, auth) + const res = await api.importTransactions(account_id, transactions); + await api.shutdown(); + return res; + }, +}); diff --git a/packages/pieces/community/actualbudget/src/lib/common/common.ts b/packages/pieces/community/actualbudget/src/lib/common/common.ts new file mode 100644 index 0000000..5588d1d --- /dev/null +++ b/packages/pieces/community/actualbudget/src/lib/common/common.ts @@ -0,0 +1,80 @@ +import { DropdownOption } from '@activepieces/pieces-framework'; +import os from 'os'; + +export async function initializeAndDownloadBudget(api: any, auth: any): Promise { + await api.init({ + // Budget data will be cached locally here, in subdirectories for each file. + dataDir: os.tmpdir(), + serverURL: auth.server_url, + password: auth.password, + }); + + await api.downloadBudget(auth.sync_id, { password: auth.encryption_password ?? undefined }); +} + +export function getYears(): DropdownOption[] { + const dropDownOptions: DropdownOption[] = []; + + const currentDate = new Date(); + const currentYear = currentDate.getFullYear(); + const startYear = currentYear-10; + + for (let year = startYear; year <= currentYear + 5; year++) { + dropDownOptions.push({ label: year.toString(), value: year.toString() }); + } + + return dropDownOptions; +} + +export function getMonths(): DropdownOption[] { + return [ + { + label: 'January', + value: '01' + }, + { + label: 'February', + value: '02' + }, + { + label: 'March', + value: '03' + }, + { + label: 'April', + value: '04' + }, + { + label: 'May', + value: '05' + }, + { + label: 'June', + value: '06' + }, + { + label: 'July', + value: '07' + }, + { + label: 'August', + value: '08' + }, + { + label: 'September', + value: '09' + }, + { + label: 'October', + value: '10' + }, + { + label: 'November', + value: '11' + }, + { + label: 'December', + value: '12' + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/actualbudget/src/lib/common/models.ts b/packages/pieces/community/actualbudget/src/lib/common/models.ts new file mode 100644 index 0000000..ddda452 --- /dev/null +++ b/packages/pieces/community/actualbudget/src/lib/common/models.ts @@ -0,0 +1,15 @@ +export interface Transaction { + id?: string; + account?: string; + date: string; + amount?: number; + payee?: string; + payee_name?: string; // Only available in a create request + imported_payee?: string; + category?: string; + notes?: string; + imported_id?: string; + transfer_id?: string; + cleared?: boolean; +} + diff --git a/packages/pieces/community/actualbudget/tsconfig.json b/packages/pieces/community/actualbudget/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/actualbudget/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/actualbudget/tsconfig.lib.json b/packages/pieces/community/actualbudget/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/actualbudget/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/acuity-scheduling/.eslintrc.json b/packages/pieces/community/acuity-scheduling/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/acuity-scheduling/README.md b/packages/pieces/community/acuity-scheduling/README.md new file mode 100644 index 0000000..3ccfbe2 --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/README.md @@ -0,0 +1,7 @@ +# pieces-acuity-scheduling + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-acuity-scheduling` to build the library. diff --git a/packages/pieces/community/acuity-scheduling/package.json b/packages/pieces/community/acuity-scheduling/package.json new file mode 100644 index 0000000..10b60c7 --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-acuity-scheduling", + "version": "0.0.1" +} diff --git a/packages/pieces/community/acuity-scheduling/project.json b/packages/pieces/community/acuity-scheduling/project.json new file mode 100644 index 0000000..e0ddbb2 --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-acuity-scheduling", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/acuity-scheduling/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/acuity-scheduling", + "tsConfig": "packages/pieces/community/acuity-scheduling/tsconfig.lib.json", + "packageJson": "packages/pieces/community/acuity-scheduling/package.json", + "main": "packages/pieces/community/acuity-scheduling/src/index.ts", + "assets": [ + "packages/pieces/community/acuity-scheduling/*.md", + { + "input": "packages/pieces/community/acuity-scheduling/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/acuity-scheduling/src/index.ts b/packages/pieces/community/acuity-scheduling/src/index.ts new file mode 100644 index 0000000..60e3197 --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/src/index.ts @@ -0,0 +1,49 @@ +import { OAuth2PropertyValue, PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { + addBlockedTimeAction, + createAppointmentAction, + createClientAction, + findAppointmentAction, + findClientAction, + rescheduleAppointmentAction, + updateClientAction, +} from './lib/actions'; +import { PieceCategory } from '@activepieces/shared'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { API_URL } from './lib/common'; +import { appointmentCanceledTrigger, appointmentScheduledTrigger } from './lib/triggers'; + +export const acuitySchedulingAuth = PieceAuth.OAuth2({ + required: true, + authUrl: 'https://acuityscheduling.com/oauth2/authorize', + tokenUrl: 'https://acuityscheduling.com/oauth2/token', + scope: ['api-v1'], +}); + +export const acuityScheduling = createPiece({ + displayName: 'Acuity Scheduling', + logoUrl: 'https://cdn.activepieces.com/pieces/acuity-scheduling.png', + auth: acuitySchedulingAuth, + categories: [PieceCategory.PRODUCTIVITY, PieceCategory.SALES_AND_CRM], + minimumSupportedRelease: '0.36.1', + authors: ['onyedikachi-david', 'kishanprmr'], + actions: [ + addBlockedTimeAction, + createAppointmentAction, + createClientAction, + rescheduleAppointmentAction, + updateClientAction, + findAppointmentAction, + findClientAction, + createCustomApiCallAction({ + auth: acuitySchedulingAuth, + baseUrl: () => API_URL, + authMapping: async (auth) => { + return { + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }; + }, + }), + ], + triggers: [appointmentCanceledTrigger, appointmentScheduledTrigger], +}); diff --git a/packages/pieces/community/acuity-scheduling/src/lib/actions/add-blocked-time.ts b/packages/pieces/community/acuity-scheduling/src/lib/actions/add-blocked-time.ts new file mode 100644 index 0000000..8e8df98 --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/src/lib/actions/add-blocked-time.ts @@ -0,0 +1,62 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { AuthenticationType, HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { acuitySchedulingAuth } from '../../index'; +import { API_URL } from '../common'; +import { calendarIdDropdown } from '../common/props'; + +export const addBlockedTimeAction = createAction({ + auth: acuitySchedulingAuth, + name: 'add_blocked_time', + displayName: 'Add Blocked Off Time', + description: 'Block off a specific time range on a calendar.', + props: { + start: Property.DateTime({ + displayName: 'Start Time', + description: 'The start date and time for the block (ISO 8601 format).', + required: true, + }), + end: Property.DateTime({ + displayName: 'End Time', + description: 'The end date and time for the block (ISO 8601 format).', + required: true, + }), + calendarID: calendarIdDropdown({ + displayName: 'Calendar ID', + description: 'The numeric ID of the calendar to add this block to.', + required: true, + }), + notes: Property.LongText({ + displayName: 'Notes', + description: 'Optional notes for the blocked off time.', + required: false, + }), + }, + async run(context) { + const props = context.propsValue; + + // Basic validation: end time must be after start time + if (new Date(props.start) >= new Date(props.end)) { + throw new Error('End time must be after start time.'); + } + + const body: Record = { + start: props.start, + end: props.end, + calendarID: props.calendarID, + }; + + if (props.notes) body['notes'] = props.notes; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${API_URL}/blocks`, + body, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/acuity-scheduling/src/lib/actions/create-appointment.ts b/packages/pieces/community/acuity-scheduling/src/lib/actions/create-appointment.ts new file mode 100644 index 0000000..61752b5 --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/src/lib/actions/create-appointment.ts @@ -0,0 +1,156 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { AuthenticationType, HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { acuitySchedulingAuth } from '../../index'; +import { API_URL } from '../common'; +import { + addonIdsDropdown, + appointmentTypeIdDropdown, + calendarIdDropdown, + labelIdDropdown, +} from '../common/props'; + +export const createAppointmentAction = createAction({ + auth: acuitySchedulingAuth, + name: 'create_appointment', + displayName: 'Create Appointment', + description: 'Creates a new appointment.', + props: { + datetime: Property.DateTime({ + displayName: 'DateTime', + description: 'Date and time of the appointment.', + required: true, + }), + appointmentTypeID: appointmentTypeIdDropdown({ + displayName: 'Appointment Type', + description: 'Select the type of appointment.', + required: true, + }), + firstName: Property.ShortText({ + displayName: 'First Name', + description: "Client's first name.", + required: true, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + description: "Client's last name.", + required: true, + }), + email: Property.ShortText({ + displayName: 'Email', + description: "Client's email address. (Optional if booking as admin).", + required: false, + }), + phone: Property.ShortText({ + displayName: 'Phone', + description: "Client's phone number.", + required: false, + }), + timezone: Property.ShortText({ + displayName: 'Timezone', + description: + "Client's timezone (e.g., America/New_York). Required for accurate availability checking.", + required: true, + defaultValue: 'UTC', + }), + adminBooking: Property.Checkbox({ + displayName: 'Book as Admin', + description: + 'Set to true to book as an admin. Disables availability/attribute validations, allows setting notes, and makes Calendar ID required.', + required: false, + defaultValue: false, + }), + calendarID: calendarIdDropdown({ + displayName: 'Calendar ID', + description: + 'Numeric ID of the calendar. Required if booking as admin. If not provided, Acuity tries to find an available calendar automatically for non-admin bookings.', + required: false, + }), + noEmail: Property.Checkbox({ + displayName: 'Suppress Confirmation Email/SMS', + description: 'If true, confirmation emails or SMS will not be sent.', + required: false, + defaultValue: false, + }), + certificate: Property.ShortText({ + displayName: 'Certificate Code', + description: 'Package or coupon certificate code.', + required: false, + }), + notes: Property.LongText({ + displayName: 'Notes', + description: 'Appointment notes. Only settable if booking as admin.', + required: false, + }), + smsOptIn: Property.Checkbox({ + displayName: 'SMS Opt-In', + description: + 'Indicates whether the client has explicitly given permission to receive SMS messages.', + required: false, + defaultValue: false, + }), + addonIDs: addonIdsDropdown({ + displayName: 'Addons', + description: + 'Select addons for the appointment. Addons are filtered by selected Appointment Type if available.', + required: false, + }), + labelId: labelIdDropdown({ + displayName: 'Label', + description: 'Apply a label to the appointment. The API currently supports one label.', + required: false, + }), + }, + async run(context) { + const props = context.propsValue; + + const queryParams: Record = {}; + if (props.adminBooking) { + queryParams['admin'] = 'true'; + } + if (props.noEmail) { + queryParams['noEmail'] = 'true'; + } + + const body: Record = { + datetime: props.datetime, + appointmentTypeID: props.appointmentTypeID, + firstName: props.firstName, + lastName: props.lastName, + email: props.email, + }; + + if (props.calendarID) body['calendarID'] = props.calendarID; + if (props.phone) body['phone'] = props.phone; + if (props.timezone) body['timezone'] = props.timezone; + if (props.certificate) body['certificate'] = props.certificate; + if (props.adminBooking && props.notes) body['notes'] = props.notes; + if (props.smsOptIn) body['smsOptIn'] = props.smsOptIn; + + if (props.addonIDs && props.addonIDs.length > 0) { + body['addonIDs'] = props.addonIDs; + } + if (props.labelId) { + body['labelID'] = [{ id: props.labelId }]; + } + + if (props.adminBooking && !props.calendarID) { + throw new Error('Calendar ID is required when booking as admin.'); + } + if (props.adminBooking && props.email === '') { + delete body['email']; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${API_URL}/appointments`, + queryParams: queryParams, + body: body, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/acuity-scheduling/src/lib/actions/create-client.ts b/packages/pieces/community/acuity-scheduling/src/lib/actions/create-client.ts new file mode 100644 index 0000000..29b80bd --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/src/lib/actions/create-client.ts @@ -0,0 +1,62 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { AuthenticationType, HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { acuitySchedulingAuth } from '../../index'; +import { API_URL } from '../common'; + +export const createClientAction = createAction({ + auth: acuitySchedulingAuth, + name: 'create_client', + displayName: 'Create Client', + description: 'Creates a new client.', + props: { + firstName: Property.ShortText({ + displayName: 'First Name', + description: "Client's first name.", + required: true, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + description: "Client's last name.", + required: true, + }), + phone: Property.ShortText({ + displayName: 'Phone', + description: "Client's phone number.", + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + description: "Client's email address.", + required: false, + }), + notes: Property.LongText({ + displayName: 'Notes', + description: 'Notes about the client.', + required: false, + }), + }, + async run(context) { + const props = context.propsValue; + + const body: Record = { + firstName: props.firstName, + lastName: props.lastName, + }; + + if (props.phone) body['phone'] = props.phone; + if (props.email) body['email'] = props.email; + if (props.notes) body['notes'] = props.notes; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${API_URL}/clients`, + body, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/acuity-scheduling/src/lib/actions/find-appointments.ts b/packages/pieces/community/acuity-scheduling/src/lib/actions/find-appointments.ts new file mode 100644 index 0000000..6c9650b --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/src/lib/actions/find-appointments.ts @@ -0,0 +1,146 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { AuthenticationType, HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { acuitySchedulingAuth } from '../../index'; +import { API_URL } from '../common'; +import { appointmentTypeIdDropdown, calendarIdDropdown } from '../common/props'; + +export const findAppointmentAction = createAction({ + auth: acuitySchedulingAuth, + name: 'find_appointment', + displayName: 'Find Appointment(s)', + description: 'Find appointments based on various criteria, including client information.', + props: { + // Client Info Filters + firstName: Property.ShortText({ + displayName: 'Client First Name', + description: 'Filter appointments by client first name.', + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Client Last Name', + description: 'Filter appointments by client last name.', + required: false, + }), + email: Property.ShortText({ + displayName: 'Client Email', + description: 'Filter appointments by client e-mail address.', + required: false, + }), + phone: Property.ShortText({ + displayName: 'Client Phone', + description: + "Filter appointments by client phone number. URL encode '+' if using country codes (e.g., %2B1234567890).", + required: false, + }), + // Date Filters + minDate: Property.DateTime({ + displayName: 'Min Date', + description: 'Only get appointments on or after this date.', + required: false, + }), + maxDate: Property.DateTime({ + displayName: 'Max Date', + description: 'Only get appointments on or before this date.', + required: false, + }), + // Other Filters + calendarID: calendarIdDropdown({ + displayName: 'Calendar ID', + description: 'Show only appointments on the calendar with this ID.', + required: false, + }), + appointmentTypeID: appointmentTypeIdDropdown({ + displayName: 'Appointment Type', + description: 'Show only appointments of this type.', + required: false, + }), + status: Property.StaticDropdown({ + displayName: 'Appointment Status', + description: 'Filter by appointment status.', + required: false, + defaultValue: 'scheduled', + options: { + options: [ + { label: 'Scheduled', value: 'scheduled' }, + { label: 'Canceled', value: 'canceled' }, + { label: 'All (Scheduled & Canceled)', value: 'all' }, + ], + }, + }), + // Result Control + maxResults: Property.Number({ + displayName: 'Max Results', + description: 'Maximum number of results to return (default 100).', + required: false, + }), + direction: Property.StaticDropdown({ + displayName: 'Sort Direction', + description: 'Sort direction for the results.', + required: false, + defaultValue: 'DESC', + options: { + options: [ + { label: 'Descending (DESC)', value: 'DESC' }, + { label: 'Ascending (ASC)', value: 'ASC' }, + ], + }, + }), + }, + async run(context) { + const props = context.propsValue; + + const queryParams: Record = {}; + + if (props.firstName) queryParams['firstName'] = props.firstName; + if (props.lastName) queryParams['lastName'] = props.lastName; + if (props.email) queryParams['email'] = props.email; + if (props.phone) queryParams['phone'] = props.phone; + + // Dates are expected in YYYY-MM-DD by Acuity from examples, Property.DateTime returns ISO string + if (props.minDate) queryParams['minDate'] = props.minDate.split('T')[0]; + if (props.maxDate) queryParams['maxDate'] = props.maxDate.split('T')[0]; + + if (props.calendarID) queryParams['calendarID'] = props.calendarID.toString(); + if (props.appointmentTypeID) + queryParams['appointmentTypeID'] = props.appointmentTypeID.toString(); + + if (props.status === 'canceled') { + queryParams['canceled'] = 'true'; + } else if (props.status === 'all') { + queryParams['showall'] = 'true'; + } // 'scheduled' is default, no param needed + + if (props.maxResults) queryParams['max'] = props.maxResults.toString(); + if (props.direction) queryParams['direction'] = props.direction; + + // Ensure at least one client identifier or a broad filter like calendarID/appointmentTypeID is used to avoid fetching all appointments if not intended. + // This is a soft validation suggestion for the user, not a hard error. + if ( + !props.firstName && + !props.lastName && + !props.email && + !props.phone && + !props.calendarID && + !props.appointmentTypeID + ) { + console.warn( + "Acuity Scheduling 'Find Appointments': No specific client or calendar/type filters provided. This might return a large number of appointments up to the maximum limit.", + ); + } + + const response = await httpClient.sendRequest>>({ + method: HttpMethod.GET, + url: `${API_URL}/appointments`, + queryParams, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return { + found: response.body.length > 0, + data: response.body, + }; + }, +}); diff --git a/packages/pieces/community/acuity-scheduling/src/lib/actions/find-client.ts b/packages/pieces/community/acuity-scheduling/src/lib/actions/find-client.ts new file mode 100644 index 0000000..f55bf0a --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/src/lib/actions/find-client.ts @@ -0,0 +1,41 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { AuthenticationType, HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { acuitySchedulingAuth } from '../../index'; +import { API_URL } from '../common'; + +export const findClientAction = createAction({ + auth: acuitySchedulingAuth, + name: 'find_client', + displayName: 'Find Client', + description: 'Finds client based on seach term.', + props: { + search: Property.ShortText({ + displayName: 'Search Term', + description: 'Filter client list by first name, last name, or phone number.', + required: true, + }), + }, + async run(context) { + const props = context.propsValue; + + const queryParams: Record = {}; + if (props.search) { + queryParams['search'] = props.search; + } + + const response = await httpClient.sendRequest>>({ + method: HttpMethod.GET, + url: `${API_URL}/clients`, + queryParams, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return { + found: response.body.length > 0, + data: response.body, + }; + }, +}); diff --git a/packages/pieces/community/acuity-scheduling/src/lib/actions/index.ts b/packages/pieces/community/acuity-scheduling/src/lib/actions/index.ts new file mode 100644 index 0000000..fcd140e --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/src/lib/actions/index.ts @@ -0,0 +1,7 @@ +export * from './create-appointment'; +export * from './reschedule-appointment'; +export * from './create-client'; +export * from './update-client'; +export * from './add-blocked-time'; +export * from './find-appointments'; +export * from './find-client'; diff --git a/packages/pieces/community/acuity-scheduling/src/lib/actions/reschedule-appointment.ts b/packages/pieces/community/acuity-scheduling/src/lib/actions/reschedule-appointment.ts new file mode 100644 index 0000000..f699612 --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/src/lib/actions/reschedule-appointment.ts @@ -0,0 +1,89 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { AuthenticationType, HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { acuitySchedulingAuth } from '../../index'; +import { API_URL } from '../common'; +import { appointmentTypeIdDropdown, calendarIdDropdown } from '../common/props'; + +export const rescheduleAppointmentAction = createAction({ + auth: acuitySchedulingAuth, + name: 'reschedule_appointment', + displayName: 'Reschedule Appointment', + description: 'Reschedules an existing appointment to a new date/time.', + props: { + id: Property.Number({ + displayName: 'Appointment ID', + description: 'The ID of the appointment to reschedule.', + required: true, + }), + appointmentTypeID: appointmentTypeIdDropdown({ + displayName: 'Appointment Type', + description: 'Select the type of appointment (used for finding new available slots).', + required: true, + }), + datetime: Property.DateTime({ + displayName: 'DateTime', + description: 'New Date and time of the appointment.', + required: true, + }), + timezone: Property.ShortText({ + displayName: 'Timezone', + description: "Client's timezone (e.g., America/New_York).", + required: true, + defaultValue: 'UTC', + }), + calendarID: calendarIdDropdown({ + displayName: 'New Calendar ID', + description: + 'Numeric ID of the new calendar to reschedule to. If blank, stays on current calendar. Submit 0 to auto-assign.', + required: false, + }), + adminReschedule: Property.Checkbox({ + displayName: 'Reschedule as Admin', + description: 'Set to true to reschedule as an admin. Disables availability validations.', + required: false, + defaultValue: false, + }), + noEmail: Property.Checkbox({ + displayName: 'Suppress Rescheduling Email/SMS', + description: 'If true, rescheduling emails or SMS will not be sent.', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const props = context.propsValue; + + const queryParams: Record = {}; + if (props.adminReschedule) { + queryParams['admin'] = 'true'; + } + if (props.noEmail) { + queryParams['noEmail'] = 'true'; + } + + const body: Record = { + datetime: props.datetime, + }; + + if (props.calendarID !== undefined) { + // Allow 0 for auto-assign + body['calendarID'] = props.calendarID === 0 ? null : props.calendarID; + } + if (props.timezone) { + body['timezone'] = props.timezone; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: `${API_URL}/appointments/${props.id}/reschedule`, + queryParams, + body, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/acuity-scheduling/src/lib/actions/update-client.ts b/packages/pieces/community/acuity-scheduling/src/lib/actions/update-client.ts new file mode 100644 index 0000000..0b35a72 --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/src/lib/actions/update-client.ts @@ -0,0 +1,91 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { AuthenticationType, HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { acuitySchedulingAuth } from '../../index'; +import { API_URL } from '../common'; + +export const updateClientAction = createAction({ + auth: acuitySchedulingAuth, + name: 'update_client', + displayName: 'Update Client', + description: 'Updates an existing client.', + props: { + currentFirstName: Property.ShortText({ + displayName: 'Current First Name (Identifier)', + description: 'The current first name of the client to update.', + required: true, + }), + currentLastName: Property.ShortText({ + displayName: 'Current Last Name (Identifier)', + description: 'The current last name of the client to update.', + required: true, + }), + currentPhone: Property.ShortText({ + displayName: 'Current Phone (Identifier, Optional)', + description: + 'The current phone number of the client to update. Helps identify the client if names are not unique.', + required: false, + }), + newFirstName: Property.ShortText({ + displayName: 'New First Name', + description: "Client's new first name. Leave blank to keep current.", + required: false, + }), + newLastName: Property.ShortText({ + displayName: 'New Last Name', + description: "Client's new last name. Leave blank to keep current.", + required: false, + }), + newEmail: Property.ShortText({ + displayName: 'New Email', + description: "Client's new email address. Leave blank to keep current.", + required: false, + }), + newPhone: Property.ShortText({ + displayName: 'New Phone', + description: "Client's new phone number. Leave blank to keep current.", + required: false, + }), + newNotes: Property.LongText({ + displayName: 'New Notes', + description: 'New notes about the client. Leave blank to keep current.', + required: false, + }), + }, + async run(context) { + const props = context.propsValue; + + const queryParams: Record = { + firstName: props.currentFirstName, + lastName: props.currentLastName, + }; + if (props.currentPhone) { + queryParams['phone'] = props.currentPhone; + } + + const body: Record = {}; + if (props.newFirstName) body['firstName'] = props.newFirstName; + if (props.newLastName) body['lastName'] = props.newLastName; + if (props.newEmail) body['email'] = props.newEmail; + if (props.newPhone) body['phone'] = props.newPhone; + if (props.newNotes) body['notes'] = props.newNotes; + + if (Object.keys(body).length === 0) { + throw new Error( + 'At least one field to update (New First Name, New Last Name, etc.) must be provided.', + ); + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: `${API_URL}/clients`, + queryParams, + body, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/acuity-scheduling/src/lib/common/index.ts b/packages/pieces/community/acuity-scheduling/src/lib/common/index.ts new file mode 100644 index 0000000..0e24951 --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/src/lib/common/index.ts @@ -0,0 +1,232 @@ +import { + HttpMethod, + httpClient, + HttpRequest, + AuthenticationType, +} from '@activepieces/pieces-common'; + +export const API_URL = 'https://acuityscheduling.com/api/v1'; + +export async function fetchAvailableDates( + accessToken: string, + appointmentTypeId: number, + month: string, + timezone?: string, + calendarId?: number, +) { + const queryParams: Record = { + month, + appointmentTypeID: appointmentTypeId.toString(), + }; + + if (timezone) queryParams['timezone'] = timezone; + if (calendarId) queryParams['calendarID'] = calendarId.toString(); + + const response = await httpClient.sendRequest>({ + method: HttpMethod.GET, + url: `${API_URL}/availability/dates`, + queryParams, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }); + + if (Array.isArray(response.body)) { + return response.body.map((item) => item.date); + } + return []; +} + +export async function fetchAvailableTimes( + accessToken: string, + appointmentTypeId: number, + date: string, + timezone?: string, + calendarId?: number, + ignoreAppointmentIDs?: number[], +) { + const params = new URLSearchParams(); + params.append('date', date); + params.append('appointmentTypeID', appointmentTypeId.toString()); + + if (timezone) params.append('timezone', timezone); + if (calendarId) params.append('calendarID', calendarId.toString()); + if (ignoreAppointmentIDs && ignoreAppointmentIDs.length > 0) { + ignoreAppointmentIDs.forEach((id) => params.append('ignoreAppointmentIDs[]', id.toString())); + } + + const response = await httpClient.sendRequest>({ + method: HttpMethod.GET, + url: `${API_URL}/availability/times?${params.toString()}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }); + + return response.body; +} + +// Helper function to get full appointment details +export async function getAppointmentDetails(appointmentId: string, accessToken: string) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${API_URL}/appointments/${appointmentId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }; + const response = await httpClient.sendRequest(request); + return response.body; +} + +export async function fetchAppointmentTypes(accessToken: string, includeDeleted = false) { + const queryParams: Record = {}; + if (includeDeleted) { + queryParams['includeDeleted'] = 'true'; + } + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${API_URL}/appointment-types`, + queryParams, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }; + + const response = await httpClient.sendRequest< + Array<{ id: number; name: string; active: boolean | string }> + >(request); + + if (Array.isArray(response.body)) { + // Filter for active types unless includeDeleted is true, and map to dropdown options + return response.body + .filter((type) => includeDeleted || type.active === true || type.active === 'true') + .map((type) => ({ label: type.name, value: type.id })); + } + return []; +} + +export async function fetchCalendars(accessToken: string) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${API_URL}/calendars`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }; + const response = await httpClient.sendRequest>(request); + + if (Array.isArray(response.body)) { + return response.body.map((calendar) => ({ label: calendar.name, value: calendar.id })); + } + return []; +} + +export async function fetchFormFields(accessToken: string) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${API_URL}/forms`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }; + const response = await httpClient.sendRequest< + Array<{ id: number; name: string; fields: Array<{ id: number; name: string }> }> + >(request); + + if (Array.isArray(response.body)) { + const formFields: Array<{ label: string; value: number }> = []; + response.body.forEach((form) => { + if (Array.isArray(form.fields)) { + form.fields.forEach((field) => { + formFields.push({ label: `${form.name} - ${field.name}`, value: field.id }); + }); + } + }); + return formFields; + } + return []; +} + +export async function fetchAddons(accessToken: string, appointmentTypeId?: number) { + // First, fetch all addons + const allAddonsRequest: HttpRequest = { + method: HttpMethod.GET, + url: `${API_URL}/appointment-addons`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }; + const allAddonsResponse = await httpClient.sendRequest>( + allAddonsRequest, + ); + + if (!Array.isArray(allAddonsResponse.body)) { + return []; + } + + let compatibleAddonIds: number[] | null = null; + + // If appointmentTypeId is provided, fetch the specific appointment type to get its compatible addonIDs + if (appointmentTypeId) { + const appointmentTypeRequest: HttpRequest = { + method: HttpMethod.GET, + url: `${API_URL}/appointment-types/${appointmentTypeId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }; + try { + const appointmentTypeResponse = await httpClient.sendRequest<{ addonIDs: number[] }>( + appointmentTypeRequest, + ); + if (appointmentTypeResponse.body && Array.isArray(appointmentTypeResponse.body.addonIDs)) { + compatibleAddonIds = appointmentTypeResponse.body.addonIDs; + } + } catch (e) { + // Log error or handle if type not found, but still proceed with all addons if necessary + console.warn( + `Could not fetch compatible addons for appointment type ${appointmentTypeId}, returning all addons. Error: ${e}`, + ); + } + } + + const allAddons = allAddonsResponse.body.map((addon) => ({ label: addon.name, value: addon.id })); + + if (compatibleAddonIds) { + return allAddons.filter((addon) => compatibleAddonIds.includes(addon.value)); + } + + return allAddons; +} + +export async function fetchLabels(accessToken: string) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${API_URL}/labels`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }; + const response = await httpClient.sendRequest>( + request, + ); + + if (Array.isArray(response.body)) { + return response.body.map((label) => ({ + label: `${label.name} (${label.color})`, + value: label.id, + })); + } + return []; +} diff --git a/packages/pieces/community/acuity-scheduling/src/lib/common/props.ts b/packages/pieces/community/acuity-scheduling/src/lib/common/props.ts new file mode 100644 index 0000000..2041c4f --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/src/lib/common/props.ts @@ -0,0 +1,100 @@ +import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { fetchAddons, fetchAppointmentTypes, fetchCalendars, fetchLabels } from '.'; + +interface DropdownParams { + displayName: string; + description?: string; + required: boolean; +} + +export const appointmentTypeIdDropdown = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + description: params.description, + required: params.required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please authenticate first', + options: [], + }; + } + const { access_token } = auth as OAuth2PropertyValue; + + return { + disabled: false, + options: await fetchAppointmentTypes(access_token), + }; + }, + }); + +export const calendarIdDropdown = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + description: params.description, + required: params.required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please authenticate first', + options: [], + }; + } + const { access_token } = auth as OAuth2PropertyValue; + + return { + disabled: false, + options: await fetchCalendars(access_token), + }; + }, + }); + +export const addonIdsDropdown = (params: DropdownParams) => + Property.MultiSelectDropdown({ + displayName: params.displayName, + description: params.description, + required: params.required, + refreshers: ['appointmentTypeID'], + options: async ({ auth, appointmentTypeID }) => { + if (!auth || !appointmentTypeID) { + return { + disabled: true, + placeholder: 'Please authenticate first', + options: [], + }; + } + const { access_token } = auth as OAuth2PropertyValue; + + return { + disabled: false, + options: await fetchAddons(access_token, appointmentTypeID as number), + }; + }, + }); + +export const labelIdDropdown = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + description: params.description, + required: params.required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please authenticate first', + options: [], + }; + } + const { access_token } = auth as OAuth2PropertyValue; + + return { + disabled: false, + options: await fetchLabels(access_token), + }; + }, + }); diff --git a/packages/pieces/community/acuity-scheduling/src/lib/triggers/appointment-canceled.ts b/packages/pieces/community/acuity-scheduling/src/lib/triggers/appointment-canceled.ts new file mode 100644 index 0000000..e35eacc --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/src/lib/triggers/appointment-canceled.ts @@ -0,0 +1,136 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; +import { acuitySchedulingAuth } from '../../index'; +import { API_URL, getAppointmentDetails } from '../common'; +import { appointmentTypeIdDropdown, calendarIdDropdown } from '../common/props'; + +const TRIGGER_KEY = 'trigger_appointment_canceled'; + +export const appointmentCanceledTrigger = createTrigger({ + auth: acuitySchedulingAuth, + name: 'appointment_canceled', + displayName: 'Appointment Canceled', + description: 'Triggers when an appointment is canceled.', + props: { + calendarId: calendarIdDropdown({ + displayName: 'Calendar', + required: false, + }), + appointmentTypeId: appointmentTypeIdDropdown({ + displayName: 'Appointment Type', + required: false, + }), + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${API_URL}/webhooks`, + body: { + target: context.webhookUrl, + event: 'appointment.canceled', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }; + const response = await httpClient.sendRequest<{ id: string }>(request); + await context.store.put(TRIGGER_KEY, response.body.id); + }, + async onDisable(context) { + const webhookId = await context.store.get(TRIGGER_KEY); + if (webhookId) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `${API_URL}/webhooks/${webhookId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }; + await httpClient.sendRequest(request); + await context.store.delete(TRIGGER_KEY); + } + }, + async test(context) { + const { calendarId, appointmentTypeId } = context.propsValue; + + const qs: QueryParams = { + max: '10', + canceled: 'true', + }; + + if (calendarId) qs['calendarID'] = calendarId.toString(); + if (appointmentTypeId) qs['appointmentTypeID'] = appointmentTypeId.toString(); + + const response = await httpClient.sendRequest>>({ + method: HttpMethod.GET, + url: `${API_URL}/appointments`, + queryParams: qs, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return response.body; + }, + async run(context) { + const { calendarId, appointmentTypeId } = context.propsValue; + + const payload = context.payload.body as { + action: string; + id: number; + calendarID: number; + appointmentTypeID: number; + }; + + // Check for 'canceled' action + if ( + payload.action === 'appointment.canceled' && + payload.id && + (!calendarId || calendarId === payload.calendarID) && + (!appointmentTypeId || appointmentTypeId === payload.appointmentTypeID) + ) { + try { + const appointmentDetails = await getAppointmentDetails( + payload.id.toString(), + context.auth.access_token, + ); + return [appointmentDetails]; + } catch (error) { + console.error(`Failed to fetch appointment details for ID ${payload.id}:`, error); + return []; + } + } else { + console.log('Received webhook for non-canceled event or missing ID:', payload.action); + return []; + } + }, + sampleData: { + id: 67890, + firstName: 'Jane', + lastName: 'Smith', + email: 'jane.smith@example.com', + phone: '555-5678', + date: '2023-12-05', + time: '02:00 PM', + datetime: '2023-12-05T14:00:00-0500', + endTime: '03:00 PM', + datetimeCreated: '2023-11-30T10:15:00-0500', + appointmentTypeID: 102, + calendarID: 2, + notes: 'Follow-up meeting.', + price: '75.00', + paid: 'no', + status: 'canceled', + noShow: false, + }, +}); diff --git a/packages/pieces/community/acuity-scheduling/src/lib/triggers/appointment-scheduled.ts b/packages/pieces/community/acuity-scheduling/src/lib/triggers/appointment-scheduled.ts new file mode 100644 index 0000000..c3cac88 --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/src/lib/triggers/appointment-scheduled.ts @@ -0,0 +1,132 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; +import { acuitySchedulingAuth } from '../../index'; +import { API_URL, getAppointmentDetails } from '../common'; +import { appointmentTypeIdDropdown, calendarIdDropdown } from '../common/props'; + +const TRIGGER_KEY = 'trigger_new_appointment'; + +export const appointmentScheduledTrigger = createTrigger({ + auth: acuitySchedulingAuth, + name: 'new_appointment', + displayName: 'New Appointment', + description: 'Triggers when a new appointment is scheduled.', + props: { + calendarId: calendarIdDropdown({ + displayName: 'Calendar', + required: false, + }), + appointmentTypeId: appointmentTypeIdDropdown({ + displayName: 'Appointment Type', + required: false, + }), + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${API_URL}/webhooks`, + body: { + target: context.webhookUrl, + event: 'appointment.scheduled', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }; + const response = await httpClient.sendRequest<{ id: string }>(request); + await context.store.put(TRIGGER_KEY, response.body.id); + }, + async onDisable(context) { + const webhookId = await context.store.get(TRIGGER_KEY); + if (webhookId) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `${API_URL}/webhooks/${webhookId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }; + await httpClient.sendRequest(request); + await context.store.delete(TRIGGER_KEY); + } + }, + async test(context) { + const { calendarId, appointmentTypeId } = context.propsValue; + + const qs: QueryParams = { + max: '10', + }; + + if (calendarId) qs['calendarID'] = calendarId.toString(); + if (appointmentTypeId) qs['appointmentTypeID'] = appointmentTypeId.toString(); + + const response = await httpClient.sendRequest>>({ + method: HttpMethod.GET, + url: `${API_URL}/appointments`, + queryParams: qs, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return response.body; + }, + async run(context) { + const { calendarId, appointmentTypeId } = context.propsValue; + + const payload = context.payload.body as { + action: string; + id: number; + calendarID: number; + appointmentTypeID: number; + }; + if ( + payload.action === 'appointment.scheduled' && + payload.id && + (!calendarId || calendarId === payload.calendarID) && + (!appointmentTypeId || appointmentTypeId === payload.appointmentTypeID) + ) { + try { + const appointmentDetails = await getAppointmentDetails( + payload.id.toString(), + context.auth.access_token, + ); + return [appointmentDetails]; + } catch (error) { + console.error(`Failed to fetch appointment details for ID ${payload.id}:`, error); + return []; + } + } else { + console.log('Received webhook for non-scheduled event or missing ID:', payload.action); + return []; + } + }, + sampleData: { + id: 12345, + firstName: 'John', + lastName: 'Doe', + email: 'john.doe@example.com', + phone: '555-1234', + date: '2023-12-01', + time: '10:00 AM', + datetime: '2023-12-01T10:00:00-0500', + endTime: '11:00 AM', + datetimeCreated: '2023-11-28T14:30:00-0500', + appointmentTypeID: 101, + calendarID: 1, + notes: 'First appointment.', + price: '50.00', + paid: 'yes', + status: 'scheduled', + }, +}); diff --git a/packages/pieces/community/acuity-scheduling/src/lib/triggers/index.ts b/packages/pieces/community/acuity-scheduling/src/lib/triggers/index.ts new file mode 100644 index 0000000..c5fd891 --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/src/lib/triggers/index.ts @@ -0,0 +1,2 @@ +export * from './appointment-scheduled'; +export * from './appointment-canceled' diff --git a/packages/pieces/community/acuity-scheduling/tsconfig.json b/packages/pieces/community/acuity-scheduling/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/acuity-scheduling/tsconfig.lib.json b/packages/pieces/community/acuity-scheduling/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/acuity-scheduling/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/acumbamail/.eslintrc.json b/packages/pieces/community/acumbamail/.eslintrc.json new file mode 100644 index 0000000..359ff63 --- /dev/null +++ b/packages/pieces/community/acumbamail/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/acumbamail/README.md b/packages/pieces/community/acumbamail/README.md new file mode 100644 index 0000000..73f955b --- /dev/null +++ b/packages/pieces/community/acumbamail/README.md @@ -0,0 +1,7 @@ +# pieces-acumbamail + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-acumbamail` to build the library. diff --git a/packages/pieces/community/acumbamail/package.json b/packages/pieces/community/acumbamail/package.json new file mode 100644 index 0000000..dcacc52 --- /dev/null +++ b/packages/pieces/community/acumbamail/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-acumbamail", + "version": "0.1.0" +} \ No newline at end of file diff --git a/packages/pieces/community/acumbamail/project.json b/packages/pieces/community/acumbamail/project.json new file mode 100644 index 0000000..23f0658 --- /dev/null +++ b/packages/pieces/community/acumbamail/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-acumbamail", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/acumbamail/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/acumbamail", + "tsConfig": "packages/pieces/community/acumbamail/tsconfig.lib.json", + "packageJson": "packages/pieces/community/acumbamail/package.json", + "main": "packages/pieces/community/acumbamail/src/index.ts", + "assets": [ + "packages/pieces/community/acumbamail/*.md", + { + "input": "packages/pieces/community/acumbamail/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-acumbamail {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/acumbamail/src/index.ts b/packages/pieces/community/acumbamail/src/index.ts new file mode 100644 index 0000000..1609fdf --- /dev/null +++ b/packages/pieces/community/acumbamail/src/index.ts @@ -0,0 +1,38 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { addUpdateSubscriberAction } from './lib/actions/add-subscriber'; +import { createSubscriberListAction } from './lib/actions/create-subscriber-list'; +import { unsubscribeAction } from './lib/actions/unsubscribe-subscriber'; +import { deleteSubscriberListAction } from './lib/actions/delete-subscriber-list'; +import { duplicateTemplateAction } from './lib/actions/duplicate-template'; +import { searchSubscriberAction } from './lib/actions/search-subscriber'; +import { removeSubscribeAction } from './lib/actions/delete-subscriber'; + +export const acumbamailAuth = PieceAuth.SecretText({ + displayName: 'Auth Token', + required: true, + description: ` + To obtain your Auth Token, follow these steps: + 1. Login to your Acumbamail account. + 2. Go to **https://acumbamail.com/apidoc/**. + 3. Under **Customer identifier**, you can find auth token; + `, +}); + +export const acumbamail = createPiece({ + displayName: 'Acumbamail', + description: 'Easily send email and SMS campaigns and boost your business', + auth: acumbamailAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/acumbamail.png', + authors: ["kishanprmr","abuaboud"], + actions: [ + addUpdateSubscriberAction, + createSubscriberListAction, + unsubscribeAction, + deleteSubscriberListAction, + duplicateTemplateAction, + searchSubscriberAction, + removeSubscribeAction, + ], + triggers: [], +}); diff --git a/packages/pieces/community/acumbamail/src/lib/actions/add-subscriber.ts b/packages/pieces/community/acumbamail/src/lib/actions/add-subscriber.ts new file mode 100644 index 0000000..459abfe --- /dev/null +++ b/packages/pieces/community/acumbamail/src/lib/actions/add-subscriber.ts @@ -0,0 +1,59 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; +import FormData from 'form-data'; +import { acumbamailAuth } from '../../'; +import { acumbamailCommon } from '../common'; + +export const addUpdateSubscriberAction = createAction({ + auth: acumbamailAuth, + name: 'acumbamail_add_update_subscriber', + displayName: 'Add/Update Subscriber', + description: + 'Adds a new subscriber to a subscriber list of your choosing.Can be used to update an existing subscriber too.', + props: { + listId: acumbamailCommon.listId, + listMergeFields: acumbamailCommon.listMergeFields, + update_subscriber: Property.Checkbox({ + displayName: 'Update Existing Subscriber Data', + description: + 'Updates the merge fields over the existent ones if the subscriber exists on the subscriber list.', + required: false, + }), + double_option: Property.Checkbox({ + displayName: 'Double Option', + description: + 'Activates the send of a confirmation email when the subscriber is added.', + required: false, + }), + }, + async run(context) { + const { listId, listMergeFields, update_subscriber, double_option } = + context.propsValue; + + const formData = new FormData(); + + Object.entries(listMergeFields).forEach(([key, value]) => { + formData.append(`merge_fields[${key}]`, value.toString()); + }); + + formData.append('auth_token', context.auth); + formData.append('list_id', listId.toString()); + formData.append('double_option', double_option ? '1' : '0'); + formData.append('update_subscriber', update_subscriber ? '1' : '0'); + formData.append('complete_json ', '1'); + + const request: HttpRequest = { + method: HttpMethod.POST, + url: acumbamailCommon.baseUrl + '/addSubscriber/', + headers: { ...formData.getHeaders() }, + body: formData, + }; + + const res = await httpClient.sendRequest(request); + return res.body; + }, +}); diff --git a/packages/pieces/community/acumbamail/src/lib/actions/create-subscriber-list.ts b/packages/pieces/community/acumbamail/src/lib/actions/create-subscriber-list.ts new file mode 100644 index 0000000..e641191 --- /dev/null +++ b/packages/pieces/community/acumbamail/src/lib/actions/create-subscriber-list.ts @@ -0,0 +1,62 @@ +import { + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import FormData from 'form-data'; +import { acumbamailAuth } from '../../'; +import { acumbamailCommon } from '../common'; + +export const createSubscriberListAction = createAction({ + auth: acumbamailAuth, + name: 'acumbamail_create_subscriber_list', + displayName: 'Create Subscriber List', + description: 'Creates a new subscriber list.', + props: { + listname: Property.ShortText({ + displayName: 'List Name', + required: true, + }), + sender_email: Property.ShortText({ + displayName: 'Sener Email', + description: + 'Sender e-mail shown to the subscribers of the list when e-mail marketing campaigns are sent to them.', + required: true, + }), + company: Property.ShortText({ + displayName: 'Company Name', + required: false, + }), + address: Property.ShortText({ + displayName: 'Company Address', + required: false, + }), + phone: Property.ShortText({ + displayName: 'Company Phone', + required: false, + }), + }, + async run(context) { + const { listname, sender_email, company, address, phone } = + context.propsValue; + + const form = new FormData(); + form.append('auth_token', context.auth); + form.append('name', listname); + form.append('sender_email', sender_email); + form.append('company', company ?? ''); + form.append('address', address ?? ''); + form.append('phone', phone ?? ''); + + const request: HttpRequest = { + method: HttpMethod.POST, + url: acumbamailCommon.baseUrl + '/createList/', + headers: form.getHeaders(), + body: form, + }; + + const res = await httpClient.sendRequest(request); + return res.body; + }, +}); diff --git a/packages/pieces/community/acumbamail/src/lib/actions/delete-subscriber-list.ts b/packages/pieces/community/acumbamail/src/lib/actions/delete-subscriber-list.ts new file mode 100644 index 0000000..61a8777 --- /dev/null +++ b/packages/pieces/community/acumbamail/src/lib/actions/delete-subscriber-list.ts @@ -0,0 +1,31 @@ +import { + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; + +import { acumbamailAuth } from '../../'; +import { acumbamailCommon } from '../common'; + +export const deleteSubscriberListAction = createAction({ + auth: acumbamailAuth, + name: 'acumbamail_delete_subscriber_list', + displayName: 'Delete Subscriber List', + description: 'Deletes an existing subscriber list.', + props: { + listId: acumbamailCommon.listId, + }, + async run(context) { + const { listId } = context.propsValue; + + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: acumbamailCommon.baseUrl + '/deleteList/', + queryParams: { auth_token: context.auth, list_id: listId.toString() }, + }; + + const res = await httpClient.sendRequest(request); + return res.body; + }, +}); diff --git a/packages/pieces/community/acumbamail/src/lib/actions/delete-subscriber.ts b/packages/pieces/community/acumbamail/src/lib/actions/delete-subscriber.ts new file mode 100644 index 0000000..a644417 --- /dev/null +++ b/packages/pieces/community/acumbamail/src/lib/actions/delete-subscriber.ts @@ -0,0 +1,37 @@ +import { acumbamailAuth } from '../..'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { acumbamailCommon } from '../common'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; + +export const removeSubscribeAction = createAction({ + auth: acumbamailAuth, + name: 'acumbamail_remove_subscriber', + displayName: 'Remove Subscriber', + description: + 'Removes a subscriber from a list', + props: { + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + listId: acumbamailCommon.listId, + }, + async run(context) { + const { listId, email } = context.propsValue; + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: acumbamailCommon.baseUrl + '/deleteSubscriber/', + queryParams: { + auth_token: context.auth, + list_id: listId.toString(), + email: email, + }, + }; + const res = await httpClient.sendRequest(request); + return res.body; + }, +}); diff --git a/packages/pieces/community/acumbamail/src/lib/actions/duplicate-template.ts b/packages/pieces/community/acumbamail/src/lib/actions/duplicate-template.ts new file mode 100644 index 0000000..6083034 --- /dev/null +++ b/packages/pieces/community/acumbamail/src/lib/actions/duplicate-template.ts @@ -0,0 +1,42 @@ +import { acumbamailAuth } from '../../'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { acumbamailCommon } from '../common'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import FormData from 'form-data'; + +export const duplicateTemplateAction = createAction({ + auth: acumbamailAuth, + name: 'acumbamail_duplicate_template', + displayName: 'Duplicate Template', + description: + 'Duplicates an existing template to use it on a email marketing campaign shipping.', + props: { + template_name: Property.ShortText({ + displayName: 'New Template Name', + required: true, + }), + templateId: acumbamailCommon.templateId, + }, + async run(context) { + const { templateId, template_name } = context.propsValue; + + const formData = new FormData(); + formData.append('auth_token', context.auth); + formData.append('template_name', template_name); + formData.append('origin_template_id', templateId.toString()); + + const request: HttpRequest = { + method: HttpMethod.POST, + url: acumbamailCommon.baseUrl + '/duplicateTemplate/', + headers: formData.getHeaders(), + body: formData, + }; + + const res = await httpClient.sendRequest(request); + return res.body; + }, +}); diff --git a/packages/pieces/community/acumbamail/src/lib/actions/search-subscriber.ts b/packages/pieces/community/acumbamail/src/lib/actions/search-subscriber.ts new file mode 100644 index 0000000..3810a48 --- /dev/null +++ b/packages/pieces/community/acumbamail/src/lib/actions/search-subscriber.ts @@ -0,0 +1,33 @@ +import { acumbamailAuth } from '../../'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { acumbamailCommon } from '../common'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; + +export const searchSubscriberAction = createAction({ + auth: acumbamailAuth, + name: 'acumbamail_search_subscriber', + displayName: 'Search Subscriber', + description: + "Returns the subscriber's advanced data in each list to which they belong.", + props: { + subscriber: Property.ShortText({ + displayName: 'Subscriber Email', + required: true, + }), + }, + async run(context) { + const { subscriber } = context.propsValue; + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: acumbamailCommon.baseUrl + '/searchSubscriber/', + queryParams: { auth_token: context.auth, subscriber: subscriber }, + }; + + const res = await httpClient.sendRequest(request); + return res.body; + }, +}); diff --git a/packages/pieces/community/acumbamail/src/lib/actions/unsubscribe-subscriber.ts b/packages/pieces/community/acumbamail/src/lib/actions/unsubscribe-subscriber.ts new file mode 100644 index 0000000..b638db0 --- /dev/null +++ b/packages/pieces/community/acumbamail/src/lib/actions/unsubscribe-subscriber.ts @@ -0,0 +1,38 @@ +import { acumbamailAuth } from '../..'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { acumbamailCommon } from '../common'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; + +export const unsubscribeAction = createAction({ + auth: acumbamailAuth, + name: 'acumbamail_unsubscribe_subscriber', + displayName: 'Unsuscribe Subscriber', + description: + 'Unsubscribes an email address from a subscriber list of your choosing.', + props: { + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + listId: acumbamailCommon.listId, + }, + async run(context) { + const { listId, email } = context.propsValue; + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: acumbamailCommon.baseUrl + '/unsubscribeSubscriber/', + queryParams: { + auth_token: context.auth, + list_id: listId.toString(), + email: email, + }, + }; + + const res = await httpClient.sendRequest(request); + return res.body; + }, +}); diff --git a/packages/pieces/community/acumbamail/src/lib/common/constants.ts b/packages/pieces/community/acumbamail/src/lib/common/constants.ts new file mode 100644 index 0000000..08c7d64 --- /dev/null +++ b/packages/pieces/community/acumbamail/src/lib/common/constants.ts @@ -0,0 +1,12 @@ +export const enum SubscriberListFieldType { + URL = 'url', + LIST = 'single list', + DECIMAL = 'decimal', + CHECKBOX = 'name', + EMAIL = 'email', + DATE = 'date', + WHOLE_NUMBER = 'integer', + IP = 'ip', + TEXT = 'char', + LONG_TEXT = 'text', +} diff --git a/packages/pieces/community/acumbamail/src/lib/common/index.ts b/packages/pieces/community/acumbamail/src/lib/common/index.ts new file mode 100644 index 0000000..3f7c430 --- /dev/null +++ b/packages/pieces/community/acumbamail/src/lib/common/index.ts @@ -0,0 +1,167 @@ +import { + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { SubscriberListFieldType } from './constants'; +import { + GetListsResponse, + GetTemplatesResponse, + SubscriberListField, +} from './types'; + +export const acumbamailCommon = { + baseUrl: 'https://acumbamail.com/api/1', + listId: Property.Dropdown({ + displayName: 'Subscriber List', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account', + options: [], + }; + } + + const request: HttpRequest = { + method: HttpMethod.GET, + url: acumbamailCommon.baseUrl + '/getLists/', + queryParams: { auth_token: auth as string }, + }; + + const res = await httpClient.sendRequest(request); + return { + disabled: false, + options: Object.entries(res.body).map(([key, val]) => { + return { + value: Number(key), + label: val.name, + }; + }), + }; + }, + }), + listMergeFields: Property.DynamicProperties({ + displayName: 'Merge Fields', + refreshers: ['listId'], + required: true, + props: async ({ auth, listId }) => { + if (!auth) return {}; + if (!listId) return {}; + + const fields: DynamicPropsValue = {}; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: acumbamailCommon.baseUrl + '/getListFields/', + queryParams: { + auth_token: auth as unknown as string, + list_id: listId as unknown as string, + }, + }; + + const res = await httpClient.sendRequest<{ + fields: SubscriberListField[]; + }>(request); + + for (const field of res.body.fields) { + switch (field.type) { + case SubscriberListFieldType.CHECKBOX: + fields[field.tag] = Property.Checkbox({ + displayName: field.label, + required: false, + }); + break; + case SubscriberListFieldType.DATE: + fields[field.tag] = Property.DateTime({ + displayName: field.label, + required: false, + description: 'Use dd/mm/yyy mm:ss format.', + }); + break; + case SubscriberListFieldType.EMAIL: + fields[field.tag] = Property.ShortText({ + displayName: field.label, + required: true, + }); + break; + case SubscriberListFieldType.TEXT: + case SubscriberListFieldType.IP: + case SubscriberListFieldType.URL: + fields[field.tag] = Property.ShortText({ + displayName: field.label, + required: false, + }); + break; + case SubscriberListFieldType.DECIMAL: + case SubscriberListFieldType.WHOLE_NUMBER: + fields[field.tag] = Property.Number({ + displayName: field.label, + required: false, + }); + break; + case SubscriberListFieldType.LONG_TEXT: + fields[field.tag] = Property.LongText({ + displayName: field.label, + required: false, + }); + break; + case SubscriberListFieldType.LIST: + fields[field.tag] = Property.StaticDropdown({ + displayName: field.label, + required: false, + options: { + disabled: false, + options: field.options + ? field.options.map((option) => { + return { + label: option.label, + value: option.label, + }; + }) + : [], + }, + }); + break; + default: + break; + } + } + return fields; + }, + }), + templateId: Property.Dropdown({ + displayName: 'Origin Template', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account', + options: [], + }; + } + + const request: HttpRequest = { + method: HttpMethod.GET, + url: acumbamailCommon.baseUrl + '/getTemplates/', + queryParams: { auth_token: auth as string }, + }; + + const res = await httpClient.sendRequest(request); + return { + disabled: false, + options: res.body.map((template) => { + return { + label: template.name, + value: template.id, + }; + }), + }; + }, + }), +}; diff --git a/packages/pieces/community/acumbamail/src/lib/common/types.ts b/packages/pieces/community/acumbamail/src/lib/common/types.ts new file mode 100644 index 0000000..581857e --- /dev/null +++ b/packages/pieces/community/acumbamail/src/lib/common/types.ts @@ -0,0 +1,30 @@ +import { SubscriberListFieldType } from './constants'; + +export type CreateListParams = { + sender_email: string; + name: string; + company?: string; + address?: string; + phone?: string; +}; + +export type GetListsResponse = { + [key: string]: { + name: string; + }; +}; + +export type GetTemplatesResponse = { + id: number; + name: string; +}; + +export type SubscriberListField = { + name: string; + label: string; + readonly: boolean; + tag: string; + hidden: boolean; + type: SubscriberListFieldType; + options?: { label: string }[]; +}; diff --git a/packages/pieces/community/acumbamail/tsconfig.json b/packages/pieces/community/acumbamail/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/acumbamail/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/acumbamail/tsconfig.lib.json b/packages/pieces/community/acumbamail/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/acumbamail/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/afforai/.eslintrc.json b/packages/pieces/community/afforai/.eslintrc.json new file mode 100644 index 0000000..359ff63 --- /dev/null +++ b/packages/pieces/community/afforai/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/afforai/README.md b/packages/pieces/community/afforai/README.md new file mode 100644 index 0000000..8a1cf59 --- /dev/null +++ b/packages/pieces/community/afforai/README.md @@ -0,0 +1,7 @@ +# pieces-afforai + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-afforai` to build the library. diff --git a/packages/pieces/community/afforai/package.json b/packages/pieces/community/afforai/package.json new file mode 100644 index 0000000..dd8f926 --- /dev/null +++ b/packages/pieces/community/afforai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-afforai", + "version": "0.0.3" +} \ No newline at end of file diff --git a/packages/pieces/community/afforai/project.json b/packages/pieces/community/afforai/project.json new file mode 100644 index 0000000..7af588b --- /dev/null +++ b/packages/pieces/community/afforai/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-afforai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/afforai/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/afforai", + "tsConfig": "packages/pieces/community/afforai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/afforai/package.json", + "main": "packages/pieces/community/afforai/src/index.ts", + "assets": [ + "packages/pieces/community/afforai/*.md", + { + "input": "packages/pieces/community/afforai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-afforai {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/afforai/src/index.ts b/packages/pieces/community/afforai/src/index.ts new file mode 100644 index 0000000..c5248a9 --- /dev/null +++ b/packages/pieces/community/afforai/src/index.ts @@ -0,0 +1,26 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { askChatbotAction } from './lib/actions/ask-chatbot'; +import { PieceCategory } from '@activepieces/shared'; + +export const afforaiAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: ` + To obtain your API Key, follow these steps: + 1. Log in to your Afforai account. + 2. Navigate to **API** section on left panel. + 3. On the top-right, you can find you API key. + `, +}); +export const afforai = createPiece({ + displayName: 'Afforai', + description: + 'Helps you search, summarize, and translate knowledge from hundreds of documents to help you produce trustworthy research.', + auth: afforaiAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/afforai.png', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + authors: ["kishanprmr","abuaboud"], + actions: [askChatbotAction], + triggers: [], +}); diff --git a/packages/pieces/community/afforai/src/lib/actions/ask-chatbot.ts b/packages/pieces/community/afforai/src/lib/actions/ask-chatbot.ts new file mode 100644 index 0000000..9a49dd7 --- /dev/null +++ b/packages/pieces/community/afforai/src/lib/actions/ask-chatbot.ts @@ -0,0 +1,83 @@ +import { afforaiAuth } from '../../'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; + +export const askChatbotAction = createAction({ + auth: afforaiAuth, + name: 'afforai_ask_chatbot', + displayName: 'Ask Chatbot', + description: 'Gets AI-generated completions for a given chatbot.', + props: { + sessionID: Property.ShortText({ + displayName: 'Chatbot ID', + required: true, + description: `You can find Chatbot ID by clicking settings button under **Actions** for given chatbot.`, + }), + history: Property.Array({ + displayName: 'Chat History', + required: true, + properties: { + role: Property.StaticDropdown({ + displayName: 'Role', + description: 'The role of the message sender.', + required: true, + options: { + disabled: false, + options: [ + { + label: 'user', + value: 'user', + }, + { + label: 'assistant', + value: 'assistant', + }, + ], + }, + }), + content: Property.LongText({ + displayName: 'Message', + description: 'The content of the message.', + required: true, + }), + }, + }), + powerful: Property.Checkbox({ + displayName: + 'AI should search more deeply for information in the given files ?', + required: true, + }), + google: Property.Checkbox({ + displayName: 'AI to search for information on Google?', + required: true, + }), + }, + async run(context) { + const { sessionID, powerful, google } = context.propsValue; + const history = context.propsValue.history as ChatHistory[]; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: 'https://api.afforai.com/api/api_completion', + body: { + apiKey: context.auth, + sessionID: sessionID, + history: history, + powerful: powerful, + google: google, + }, + }; + + const res = await httpClient.sendRequest(request); + return res.body; + }, +}); + +type ChatHistory = { + role: string; + content: string; +}; diff --git a/packages/pieces/community/afforai/tsconfig.json b/packages/pieces/community/afforai/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/afforai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/afforai/tsconfig.lib.json b/packages/pieces/community/afforai/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/afforai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/agent/.eslintrc.json b/packages/pieces/community/agent/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/agent/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/agent/README.md b/packages/pieces/community/agent/README.md new file mode 100644 index 0000000..cbad7b5 --- /dev/null +++ b/packages/pieces/community/agent/README.md @@ -0,0 +1,7 @@ +# pieces-agent + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-agent` to build the library. diff --git a/packages/pieces/community/agent/package-lock.json b/packages/pieces/community/agent/package-lock.json new file mode 100644 index 0000000..e228f39 --- /dev/null +++ b/packages/pieces/community/agent/package-lock.json @@ -0,0 +1,1777 @@ +{ + "name": "@activepieces/piece-agent", + "version": "0.0.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-agent", + "version": "0.0.2", + "dependencies": { + "@langchain/anthropic": "0.3.20", + "@langchain/core": "0.3.55", + "@langchain/mcp-adapters": "0.4.2", + "@langchain/openai": "0.5.10", + "langchain": "0.3.3" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.39.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.39.0.tgz", + "integrity": "sha512-eMyDIPRZbt1CCLErRCi3exlAvNkBtRe+kW5vvJyef93PmNr/clstYgHhtvmkxN82nlKgzyGPCyGxrm0JQ1ZIdg==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/@cfworker/json-schema": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==" + }, + "node_modules/@langchain/anthropic": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@langchain/anthropic/-/anthropic-0.3.20.tgz", + "integrity": "sha512-er/mdxdSs8BlQeH5GQtvEIBxf2slge3gsF7CW88S23xfASn6bnjAisQGQwRmvD+X0do7G526W7lNP93u6dMJ0A==", + "dependencies": { + "@anthropic-ai/sdk": "^0.39.0", + "fast-xml-parser": "^4.4.1", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.48 <0.4.0" + } + }, + "node_modules/@langchain/core": { + "version": "0.3.55", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.55.tgz", + "integrity": "sha512-SojY2ugpT6t9eYfFB9Ysvyhhyh+KJTGXs50hdHUE9tAEQWp3WAwoxe4djwJnOZ6fSpWYdpFt2UT2ksHVDy2vXA==", + "dependencies": { + "@cfworker/json-schema": "^4.0.2", + "ansi-styles": "^5.0.0", + "camelcase": "6", + "decamelize": "1.2.0", + "js-tiktoken": "^1.0.12", + "langsmith": "^0.3.16", + "mustache": "^4.2.0", + "p-queue": "^6.6.2", + "p-retry": "4", + "uuid": "^10.0.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@langchain/mcp-adapters": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@langchain/mcp-adapters/-/mcp-adapters-0.4.2.tgz", + "integrity": "sha512-U/hYDzKW1lOd1YY0eWjWqxGpzXh9q0RoMQMQAZC3qV3cGgo0qk9LNOgtxWmBBXc5a3xCTDU/IezBtc+KzzsWaw==", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.7.0", + "debug": "^4.4.0", + "zod": "^3.24.2" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "extended-eventsource": "^1.x" + }, + "peerDependencies": { + "@langchain/core": "^0.3.44" + } + }, + "node_modules/@langchain/openai": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.5.10.tgz", + "integrity": "sha512-hBQIWjcVxGS7tgVvgBBmrZ5jSaJ8nu9g6V64/Tx6KGjkW7VdGmUvqCO+koiQCOZVL7PBJkHWAvDsbghPYXiZEA==", + "dependencies": { + "js-tiktoken": "^1.0.12", + "openai": "^4.96.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.48 <0.4.0" + } + }, + "node_modules/@langchain/textsplitters": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@langchain/textsplitters/-/textsplitters-0.1.0.tgz", + "integrity": "sha512-djI4uw9rlkAb5iMhtLED+xJebDdAG935AdP4eRTB02R7OB/act55Bj9wsskhZsvuyQRpO4O1wQOp85s6T6GWmw==", + "dependencies": { + "js-tiktoken": "^1.0.12" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.21 <0.4.0" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.4.tgz", + "integrity": "sha512-OTbhe5slIjiOtLxXhKalkKGhIQrwvhgCDs/C2r8kcBTy5HR/g43aDQU0l7r8O0VGbJPTNJvDc7ZdQMdQDJXmbw==", + "dependencies": { + "ajv": "^8.17.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "18.19.100", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.100.tgz", + "integrity": "sha512-ojmMP8SZBKprc3qGrGk8Ujpo80AXkrP7G2tOT4VWr5jlr5DHjsJF+emXJz+Wm0glmy4Js62oKMdZZ6B9Y+tEcA==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "engines": { + "node": ">=14" + } + }, + "node_modules/console-table-printer": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.12.1.tgz", + "integrity": "sha512-wKGOQRRvdnd89pCeH96e2Fn4wkbenSP6LMHfjfyNLMbGuHEFbMqQNuxXqd0oXG9caIOQ1FTvc5Uijp9/4jujnQ==", + "dependencies": { + "simple-wcswidth": "^1.0.1" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.2.tgz", + "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" + } + }, + "node_modules/extended-eventsource": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extended-eventsource/-/extended-eventsource-1.7.0.tgz", + "integrity": "sha512-s8rtvZuYcKBpzytHb5g95cHbZ1J99WeMnV18oKc5wKoxkHzlzpPc/bNAm7Da2Db0BDw0CAu1z3LpH+7UsyzIpw==", + "optional": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/fast-xml-parser": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", + "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^1.1.1" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/js-tiktoken": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.20.tgz", + "integrity": "sha512-Xlaqhhs8VfCd6Sh7a1cFkZHQbYTLCwVJJWiHVxBYzLPxW0XsoxBy1hitmjkdIjD3Aon5BXLHFwU5O8WUx6HH+A==", + "dependencies": { + "base64-js": "^1.5.1" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/langchain": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.3.3.tgz", + "integrity": "sha512-xy63PAh1PUuF2VdjLxacP8SeUQKF++ixvAhMhl/+3GkzloEKce41xlbQC3xNGVToYaqzIsDrueps/JU0zYYXHw==", + "dependencies": { + "@langchain/openai": ">=0.1.0 <0.4.0", + "@langchain/textsplitters": ">=0.0.0 <0.2.0", + "js-tiktoken": "^1.0.12", + "js-yaml": "^4.1.0", + "jsonpointer": "^5.0.1", + "langsmith": "^0.1.56", + "openapi-types": "^12.1.3", + "p-retry": "4", + "uuid": "^10.0.0", + "yaml": "^2.2.1", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/anthropic": "*", + "@langchain/aws": "*", + "@langchain/cohere": "*", + "@langchain/core": ">=0.2.21 <0.4.0", + "@langchain/google-genai": "*", + "@langchain/google-vertexai": "*", + "@langchain/groq": "*", + "@langchain/mistralai": "*", + "@langchain/ollama": "*", + "axios": "*", + "cheerio": "*", + "handlebars": "^4.7.8", + "peggy": "^3.0.2", + "typeorm": "*" + }, + "peerDependenciesMeta": { + "@langchain/anthropic": { + "optional": true + }, + "@langchain/aws": { + "optional": true + }, + "@langchain/cohere": { + "optional": true + }, + "@langchain/google-genai": { + "optional": true + }, + "@langchain/google-vertexai": { + "optional": true + }, + "@langchain/groq": { + "optional": true + }, + "@langchain/mistralai": { + "optional": true + }, + "@langchain/ollama": { + "optional": true + }, + "axios": { + "optional": true + }, + "cheerio": { + "optional": true + }, + "handlebars": { + "optional": true + }, + "peggy": { + "optional": true + }, + "typeorm": { + "optional": true + } + } + }, + "node_modules/langchain/node_modules/@langchain/openai": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.3.17.tgz", + "integrity": "sha512-uw4po32OKptVjq+CYHrumgbfh4NuD7LqyE+ZgqY9I/LrLc6bHLMc+sisHmI17vgek0K/yqtarI0alPJbzrwyag==", + "dependencies": { + "js-tiktoken": "^1.0.12", + "openai": "^4.77.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.29 <0.4.0" + } + }, + "node_modules/langchain/node_modules/langsmith": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.1.68.tgz", + "integrity": "sha512-otmiysWtVAqzMx3CJ4PrtUBhWRG5Co8Z4o7hSZENPjlit9/j3/vm3TSvbaxpDYakZxtMjhkcJTqrdYFipISEiQ==", + "dependencies": { + "@types/uuid": "^10.0.0", + "commander": "^10.0.1", + "p-queue": "^6.6.2", + "p-retry": "4", + "semver": "^7.6.3", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "openai": "*" + }, + "peerDependenciesMeta": { + "openai": { + "optional": true + } + } + }, + "node_modules/langsmith": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.29.tgz", + "integrity": "sha512-JPF2B339qpYy9FyuY4Yz1aWYtgPlFc/a+VTj3L/JcFLHCiMP7+Ig8I9jO+o1QwVa+JU3iugL1RS0wwc+Glw0zA==", + "dependencies": { + "@types/uuid": "^10.0.0", + "chalk": "^4.1.2", + "console-table-printer": "^2.12.1", + "p-queue": "^6.6.2", + "p-retry": "4", + "semver": "^7.6.3", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "openai": "*" + }, + "peerDependenciesMeta": { + "openai": { + "optional": true + } + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "4.100.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.100.0.tgz", + "integrity": "sha512-9soq/wukv3utxcuD7TWFqKdKp0INWdeyhUCvxwrne5KwnxaCp4eHL4GdT/tMFhYolxgNhxFzg5GFwM331Z5CZg==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==" + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-wcswidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.0.1.tgz", + "integrity": "sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/zod": { + "version": "3.24.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", + "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "peerDependencies": { + "zod": "^3.24.1" + } + } + } +} diff --git a/packages/pieces/community/agent/package.json b/packages/pieces/community/agent/package.json new file mode 100644 index 0000000..77bd6ae --- /dev/null +++ b/packages/pieces/community/agent/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-agent", + "version": "0.1.10" +} diff --git a/packages/pieces/community/agent/project.json b/packages/pieces/community/agent/project.json new file mode 100644 index 0000000..c00e674 --- /dev/null +++ b/packages/pieces/community/agent/project.json @@ -0,0 +1,46 @@ +{ + "name": "pieces-agent", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/agent/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/agent", + "tsConfig": "packages/pieces/community/agent/tsconfig.lib.json", + "packageJson": "packages/pieces/community/agent/package.json", + "main": "packages/pieces/community/agent/src/index.ts", + "assets": [ + "packages/pieces/community/agent/*.md" + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + }, + "dependsOn": ["^build"] + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/agent/src/index.ts b/packages/pieces/community/agent/src/index.ts new file mode 100644 index 0000000..10b5653 --- /dev/null +++ b/packages/pieces/community/agent/src/index.ts @@ -0,0 +1,15 @@ +import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { runAgent } from "./lib/actions/run-agent"; +import { PieceCategory } from "@activepieces/shared"; + +export const agent = createPiece({ + displayName: "Agent", + auth: PieceAuth.None(), + minimumSupportedRelease: '0.65.0', + logoUrl: "https://cdn.activepieces.com/pieces/agent.png", + authors: ['Gamal72', 'abuaboud'], + description: "Let an AI assistant help you with tasks using tools.", + actions: [runAgent], + triggers: [], + categories: [PieceCategory.UNIVERSAL_AI], +}); diff --git a/packages/pieces/community/agent/src/lib/actions/run-agent.ts b/packages/pieces/community/agent/src/lib/actions/run-agent.ts new file mode 100644 index 0000000..5e236aa --- /dev/null +++ b/packages/pieces/community/agent/src/lib/actions/run-agent.ts @@ -0,0 +1,67 @@ +import { createAction, Property, PieceAuth } from '@activepieces/pieces-framework'; +import { agentCommon } from '../common'; +import { agentExecutor } from '../agent-executor'; + + +export const runAgent = createAction({ + name: 'run_agent', + displayName: 'Run Agent', + description: 'Run the AI assistant to complete your task.', + auth: PieceAuth.None(), + errorHandlingOptions: { + retryOnFailure: { + hide: true, + }, + continueOnFailure: { + hide: true, + }, + }, + props: { + agentId: Property.Dropdown({ + displayName: 'Agent', + description: 'Select agent created', + required: true, + refreshers: [], + options: async (_auth, ctx) => { + const agentPage = await agentCommon.listAgents({ + publicUrl: ctx.server.publicUrl, + token: ctx.server.token, + }) + return { + disabled: false, + options: agentPage.body.data.map((agent) => { + return { + label: agent.displayName, + value: agent.id, + }; + }), + } + }, + }), + prompt: Property.LongText({ + displayName: 'Prompt', + description: 'Describe what you want the assistant to do.', + required: true, + }), + }, + async run(context) { + const { agentId, prompt } = context.propsValue + const serverToken = context.server.token; + + + + return agentExecutor.execute({ + agentId, + prompt, + update: async (data: Record) => { + await context.output.update({ + data, + }) + }, + serverToken, + publicUrl: context.server.publicUrl, + flowId: context.flows.current.id, + runId: context.run.id, + }) + }, +}); diff --git a/packages/pieces/community/agent/src/lib/agent-executor.ts b/packages/pieces/community/agent/src/lib/agent-executor.ts new file mode 100644 index 0000000..9af1e91 --- /dev/null +++ b/packages/pieces/community/agent/src/lib/agent-executor.ts @@ -0,0 +1,187 @@ +import { Agent, agentbuiltInToolsNames, AgentStepBlock, AgentTaskStatus, AgentTestResult, AIErrorResponse, assertNotNullOrUndefined, ContentBlockType, isNil, McpToolMetadata, mcpToolNaming, McpToolType, McpWithTools, ToolCallContentBlock, ToolCallStatus, ToolCallType } from "@activepieces/shared" +import { APICallError, streamText } from "ai" +import { agentCommon } from "./common" +import { agentTools } from "./agent-tools" +import { agentMcp } from "./agent-mcp" + +export const agentExecutor = { + async execute(params: ExecuteAgent) { + + const agent = await agentCommon.getAgent({ + publicUrl: params.publicUrl, + token: params.serverToken, + id: params.agentId, + }) + + const mcp = await agentMcp.getMcp({ + publicUrl: params.publicUrl, + token: params.serverToken, + mcpId: agent.mcpId, + }) + + const agentToolInstance = await agentTools({ + agent, + publicUrl: params.publicUrl, + token: params.serverToken, + mcp, + }) + try { + const model = await agentCommon.initializeOpenAIModel({ + publicUrl: params.publicUrl, + token: params.serverToken, + }) + const { fullStream } = streamText({ + model, + system: constructSystemPrompt(agent), + prompt: params.prompt, + maxSteps: agent.maxSteps, + tools: await agentToolInstance.tools(), + }) + const agentResult: AgentTestResult = { + steps: [], + status: AgentTaskStatus.IN_PROGRESS, + output: undefined, + message: '', + } + let currentText = '' + + for await (const chunk of fullStream) { + if (chunk.type === 'text-delta') { + currentText += chunk.textDelta + } + else if (chunk.type === 'tool-call') { + if (currentText.length > 0) { + agentResult.steps.push({ + type: ContentBlockType.MARKDOWN, + markdown: currentText, + }) + currentText = '' + } + const metadata = getMetadata(chunk.toolName, mcp, { + toolName: chunk.toolName, + toolCallId: chunk.toolCallId, + type: ContentBlockType.TOOL_CALL, + status: ToolCallStatus.IN_PROGRESS, + input: chunk.args as Record, + output: undefined, + startTime: new Date().toISOString(), + }) + agentResult.steps.push(metadata) + } else if (chunk.type === 'tool-result') { + const lastBlockIndex = agentResult.steps.findIndex((block) => block.type === ContentBlockType.TOOL_CALL && block.toolCallId === chunk.toolCallId) + const lastBlock = agentResult.steps[lastBlockIndex] as ToolCallContentBlock + assertNotNullOrUndefined(lastBlock, 'Last block must be a tool call') + agentResult.steps[lastBlockIndex] = { + ...lastBlock, + status: ToolCallStatus.COMPLETED, + endTime: new Date().toISOString(), + output: chunk.result, + } + } else if (chunk.type === 'error') { + agentResult.status = AgentTaskStatus.FAILED + if (APICallError.isInstance(chunk.error)) { + const errorResponse = (chunk.error as any)?.data as AIErrorResponse + agentResult.message = errorResponse?.error?.message ?? JSON.stringify(chunk.error) + } + else { + agentResult.message = concatMarkdown(agentResult.steps) + '\n' + JSON.stringify(chunk.error, null, 2) + } + await params.update(agentResult) + return agentResult + } + await params.update(agentResult) + } + if (currentText.length > 0) { + agentResult.steps.push({ + type: ContentBlockType.MARKDOWN, + markdown: currentText, + }) + } + + const markAsComplete = agentResult.steps.find(isMarkAsComplete) as ToolCallContentBlock | undefined + agentResult.output = markAsComplete?.input + agentResult.status = !isNil(markAsComplete) ? AgentTaskStatus.COMPLETED : AgentTaskStatus.FAILED, + agentResult.message = concatMarkdown(agentResult.steps) + + await params.update(agentResult) + + return agentResult + } + finally { + await agentToolInstance.close() + } + } + +} + + +function isMarkAsComplete(block: AgentStepBlock): boolean { + return block.type === ContentBlockType.TOOL_CALL && block.toolName === agentbuiltInToolsNames.markAsComplete +} + + +function getMetadata(toolName: string, mcp: McpWithTools, baseTool: Pick): ToolCallContentBlock { + if (toolName === agentbuiltInToolsNames.markAsComplete) { + return { + ...baseTool, + toolCallType: ToolCallType.INTERNAL, + displayName: 'Mark as Complete', + } + } + const tool = mcp.tools.find((tool) => tool.id === mcpToolNaming.extractToolId(toolName)) + if (!tool) { + throw new Error(`Tool ${toolName} not found`) + } + switch (tool.type) { + case McpToolType.PIECE: { + const pieceMetadata = tool.pieceMetadata + assertNotNullOrUndefined(pieceMetadata, 'Piece metadata is required') + const actionMetadataEntry = Object.values(pieceMetadata.actionNames).find((action) => mcpToolNaming.fixTool(action, mcpToolNaming.extractToolId(toolName), McpToolType.PIECE) === toolName) + assertNotNullOrUndefined(actionMetadataEntry, 'Action metadata entry not found') + return { + ...baseTool, + toolCallType: ToolCallType.PIECE, + pieceName: pieceMetadata.pieceName, + pieceVersion: pieceMetadata.pieceVersion, + actionName: actionMetadataEntry, + } + } + case McpToolType.FLOW: { + assertNotNullOrUndefined(tool.flowId, 'Flow ID is required') + return { + ...baseTool, + toolCallType: ToolCallType.FLOW, + displayName: tool.flow?.version?.displayName ?? 'Unknown', + flowId: tool.flowId, + } + } + } +} + +function constructSystemPrompt(agent: Agent) { + return ` + You are an autonomous assistant designed to efficiently achieve the user's goal. + + YOU MUST ALWAYS call the mark as complete tool with the output or message wether you have successfully completed the task or not. + + **Today's Date**: ${new Date().toISOString()} + Use this to interpret time-based queries like "this week" or "due tomorrow." + + --- + ${agent.systemPrompt} + ` +} + +function concatMarkdown(blocks: AgentStepBlock[]): string { + return blocks.filter((block) => block.type === ContentBlockType.MARKDOWN).map((block) => block.markdown).join('\n') +} + +type ExecuteAgent = { + agentId: string + prompt: string + update: (data: Record) => Promise + serverToken: string + publicUrl: string + flowId: string + runId: string +} \ No newline at end of file diff --git a/packages/pieces/community/agent/src/lib/agent-mcp.ts b/packages/pieces/community/agent/src/lib/agent-mcp.ts new file mode 100644 index 0000000..6b9cc91 --- /dev/null +++ b/packages/pieces/community/agent/src/lib/agent-mcp.ts @@ -0,0 +1,23 @@ +import { AuthenticationType, httpClient, HttpMethod } from "@activepieces/pieces-common" +import { McpWithTools } from "@activepieces/shared" + + +export const agentMcp = { + getMcp: async (params: McpParams) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${params.publicUrl}v1/mcp-servers/${params.mcpId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: params.token, + }, + }) + return response.body + } +} + +type McpParams = { + publicUrl: string + token: string + mcpId: string +} \ No newline at end of file diff --git a/packages/pieces/community/agent/src/lib/agent-tools.ts b/packages/pieces/community/agent/src/lib/agent-tools.ts new file mode 100644 index 0000000..f70b454 --- /dev/null +++ b/packages/pieces/community/agent/src/lib/agent-tools.ts @@ -0,0 +1,84 @@ +import { Agent, agentbuiltInToolsNames, AgentOutputFieldType, AgentOutputType, isNil, McpWithTools } from '@activepieces/shared' +import { experimental_createMCPClient, tool } from 'ai' +import { z, ZodRawShape, ZodSchema } from 'zod' + +export const agentTools = async (params: AgentToolsParams) => { + const mcpClient = await getMcpClient(params) + const builtInTools = await buildInternalTools(params) + const mcpTools = isNil(await mcpClient?.tools()) ? {} : await mcpClient?.tools() + const tools = { + ...builtInTools, + ...mcpTools, + } + + return { + tools: async () => { + return tools + }, + close: async () => { + await mcpClient?.close() + }, + } +} + + +async function buildInternalTools(params: AgentToolsParams) { + return { + [agentbuiltInToolsNames.markAsComplete]: tool({ + description: 'Mark the todo as complete', + parameters: params.agent.outputType === AgentOutputType.STRUCTURED_OUTPUT ? z.object({ + output: await getStructuredOutput(params.agent), + }) : z.object({}), + execute: async () => { + return 'Marked as Complete' + }, + }), + } +} + + +async function getMcpClient(params: AgentToolsParams) { + const mcpServer = params.mcp + if (mcpServer.tools.length === 0) { + return null + } + const mcpServerUrl = `${params.publicUrl}v1/mcp/${params.mcp.token}/sse` + console.log("MCP SERVER URL", mcpServerUrl) + return experimental_createMCPClient({ + transport: { + type: 'sse', + url: mcpServerUrl, + }, + }) +} + + +async function getStructuredOutput(agent: Agent): Promise { + const outputFields = agent.outputFields ?? [] + const shape: ZodRawShape = {} + + for (const field of outputFields) { + switch (field.type) { + case AgentOutputFieldType.TEXT: + shape[field.displayName] = z.string() + break + case AgentOutputFieldType.NUMBER: + shape[field.displayName] = z.number() + break + case AgentOutputFieldType.BOOLEAN: + shape[field.displayName] = z.boolean() + break + default: + shape[field.displayName] = z.any() + } + } + + return z.object(shape) +} + +type AgentToolsParams = { + publicUrl: string + token: string + mcp: McpWithTools + agent: Agent +} diff --git a/packages/pieces/community/agent/src/lib/common.ts b/packages/pieces/community/agent/src/lib/common.ts new file mode 100644 index 0000000..b380e0f --- /dev/null +++ b/packages/pieces/community/agent/src/lib/common.ts @@ -0,0 +1,55 @@ +import { httpClient, AuthenticationType, HttpMethod } from "@activepieces/pieces-common"; +import { Agent, createAIProvider, SeekPage } from "@activepieces/shared" +import { openai } from "@ai-sdk/openai"; + + +export const agentCommon = { + async initializeOpenAIModel(params: InitOpenAI) { + const baseURL = `${params.publicUrl}v1/ai-providers/proxy/openai`; + const engineToken = params.token; + return createAIProvider({ + providerName: 'openai', + modelInstance: openai('gpt-4o-mini'), + apiKey: engineToken, + baseURL, + }); + }, + listAgents(params: ListAgents) { + return httpClient.sendRequest>({ + method: HttpMethod.GET, + url: `${params.publicUrl}v1/agents`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: params.token, + }, + }) + }, + async getAgent(params: GetAgent) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${params.publicUrl}v1/agents/${params.id}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: params.token, + }, + }) + return response.body + } +} + +type GetAgent = { + publicUrl: string + token: string + id: string +} + +type ListAgents = { + publicUrl: string + token: string +} + +type InitOpenAI = { + publicUrl: string + token: string + +} \ No newline at end of file diff --git a/packages/pieces/community/agent/tsconfig.json b/packages/pieces/community/agent/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/agent/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/agent/tsconfig.lib.json b/packages/pieces/community/agent/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/agent/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/aianswer/.eslintrc.json b/packages/pieces/community/aianswer/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/aianswer/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/aianswer/README.md b/packages/pieces/community/aianswer/README.md new file mode 100644 index 0000000..509434d --- /dev/null +++ b/packages/pieces/community/aianswer/README.md @@ -0,0 +1,7 @@ +# pieces-aianswer + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-aianswer` to build the library. diff --git a/packages/pieces/community/aianswer/package.json b/packages/pieces/community/aianswer/package.json new file mode 100644 index 0000000..038b4f4 --- /dev/null +++ b/packages/pieces/community/aianswer/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-aianswer", + "version": "0.0.1" +} diff --git a/packages/pieces/community/aianswer/project.json b/packages/pieces/community/aianswer/project.json new file mode 100644 index 0000000..9d4e309 --- /dev/null +++ b/packages/pieces/community/aianswer/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-aianswer", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/aianswer/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/aianswer", + "tsConfig": "packages/pieces/community/aianswer/tsconfig.lib.json", + "packageJson": "packages/pieces/community/aianswer/package.json", + "main": "packages/pieces/community/aianswer/src/index.ts", + "assets": [ + "packages/pieces/community/aianswer/*.md", + { + "input": "packages/pieces/community/aianswer/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/aianswer/src/index.ts b/packages/pieces/community/aianswer/src/index.ts new file mode 100644 index 0000000..86dc19c --- /dev/null +++ b/packages/pieces/community/aianswer/src/index.ts @@ -0,0 +1,72 @@ +import { + httpClient, + createCustomApiCallAction, + HttpMethod, +} from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { aiAnswerConfig } from './lib/common/models'; +import { gmailGetListOfAgents } from './lib/actions/gmail-get-list-of-agents'; +import { createPhoneCall } from './lib/actions/create-phone-call'; +import { getCallDetails } from './lib/actions/get-call-details'; +import { scheduleCallAgent } from './lib/actions/schedule-call-agent'; +import { PieceCategory } from '@activepieces/shared'; +import { getCallTranscript } from './lib/actions/get-call-transcript'; + +export const aiAnswerAuth = PieceAuth.SecretText({ + displayName: 'AiAnswer API Access Token', + required: true, + description: ` + To obtain your AiAnswer API access token, follow these steps below: + 1. Log in to your AiAnswer account at https://app.aianswer.us . + 2. Navigate to Settings < API Key. + 3. Click on Copy icon to copy your existing Key or click on New API Key to create a new one. + 4. Copy the API Key and paste it below in "AiAnswer API Key". + `, + validate: async (auth) => { + try { + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${aiAnswerConfig.baseUrl}/gmail/list_agents`, + headers: { + [aiAnswerConfig.accessTokenHeaderKey]: auth.auth, + }, + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API Key', + }; + } + }, +}); + +export const aianswer = createPiece({ + displayName: 'AI Answer', + auth: aiAnswerAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/aianswer.png', + categories: [ + PieceCategory.COMMUNICATION, + PieceCategory.CUSTOMER_SUPPORT, + PieceCategory.ARTIFICIAL_INTELLIGENCE, + ], + authors: ['drona2938'], + actions: [ + gmailGetListOfAgents, + createPhoneCall, + getCallDetails, + scheduleCallAgent, + getCallTranscript, + createCustomApiCallAction({ + baseUrl: () => aiAnswerConfig.baseUrl, + auth: aiAnswerAuth, + authMapping: async (auth) => ({ + [aiAnswerConfig.accessTokenHeaderKey]: `${auth}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/aianswer/src/lib/actions/create-phone-call.ts b/packages/pieces/community/aianswer/src/lib/actions/create-phone-call.ts new file mode 100644 index 0000000..a474288 --- /dev/null +++ b/packages/pieces/community/aianswer/src/lib/actions/create-phone-call.ts @@ -0,0 +1,49 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { aiAnswerAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { aiAnswerConfig } from '../common/models'; + +export const createPhoneCall = createAction({ + name: 'createPhoneCall', + auth: aiAnswerAuth, + displayName: 'Create Phone Call', + description: 'Create a phone call to customer from Agent', + props: { + agentID: Property.ShortText({ + displayName: 'Agent ID', + required: true, + }), + phoneNumber: Property.ShortText({ + displayName: 'To Phone Number', + description: 'Enter the phone number, along with country code, in format (e.g., +919876543210)', + required: true, + }), + details: Property.Object({ + displayName: 'Details', + description: 'Optional details with key-value pairs (e.g., customer_id, priority)', + required: false, + }), + }, + async run(context) { + const agentID = context.propsValue.agentID; + const phoneNumber = context.propsValue.phoneNumber; + const details = context.propsValue.details || {}; // Optional details + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${aiAnswerConfig.baseUrl}/v2/call_agent/${agentID}`, + headers: { + [aiAnswerConfig.accessTokenHeaderKey]: context.auth, + }, + queryParams: { + agent_id: agentID, + to_number: phoneNumber, + }, + body: { + details, // Include details object in the request body + }, + }); + + return res.body; + }, +}); diff --git a/packages/pieces/community/aianswer/src/lib/actions/get-call-details.ts b/packages/pieces/community/aianswer/src/lib/actions/get-call-details.ts new file mode 100644 index 0000000..63de53b --- /dev/null +++ b/packages/pieces/community/aianswer/src/lib/actions/get-call-details.ts @@ -0,0 +1,30 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { aiAnswerAuth } from '../..'; +import { aiAnswerConfig } from '../common/models'; + +export const getCallDetails = createAction({ + name: 'getCallDetails', + auth: aiAnswerAuth, + displayName: 'Get Call Details', + description: 'Fetch Call details by Call ID', + props: { + callID: Property.ShortText({ + displayName: 'Call ID', + required: true, + }), + }, + async run(context) { + const callID = context.propsValue.callID; + + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${aiAnswerConfig.baseUrl}/v2/get_call/${callID}`, + headers: { + [aiAnswerConfig.accessTokenHeaderKey]: context.auth, + }, + }); + + return res.body; + }, +}); diff --git a/packages/pieces/community/aianswer/src/lib/actions/get-call-transcript.ts b/packages/pieces/community/aianswer/src/lib/actions/get-call-transcript.ts new file mode 100644 index 0000000..aee8869 --- /dev/null +++ b/packages/pieces/community/aianswer/src/lib/actions/get-call-transcript.ts @@ -0,0 +1,30 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { aiAnswerAuth } from '../..'; +import { aiAnswerConfig } from '../common/models'; + +export const getCallTranscript = createAction({ + name: 'getCallTranscript', + auth: aiAnswerAuth, // Auth configured here + displayName: 'Get Call Transcript', + description: 'Fetch the transcript of a call by Call ID', + props: { + callID: Property.ShortText({ + displayName: 'Call ID', + required: true, + }), + }, + async run(context) { + const callID = context.propsValue.callID; + + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${aiAnswerConfig.baseUrl}/v2/get_transcript/${callID}`, + headers: { + [aiAnswerConfig.accessTokenHeaderKey]: context.auth, + }, + }); + + return res.body; + }, +}); diff --git a/packages/pieces/community/aianswer/src/lib/actions/gmail-get-list-of-agents.ts b/packages/pieces/community/aianswer/src/lib/actions/gmail-get-list-of-agents.ts new file mode 100644 index 0000000..a773f54 --- /dev/null +++ b/packages/pieces/community/aianswer/src/lib/actions/gmail-get-list-of-agents.ts @@ -0,0 +1,22 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { aiAnswerConfig } from '../common/models'; +import { aiAnswerAuth } from '../../index'; + +export const gmailGetListOfAgents = createAction({ + name: 'gmailGetListOfAgents', + auth: aiAnswerAuth, + displayName: 'Gmail get list of Agents', + description: 'get the lists of agents with Gmail', + props: {}, + async run(context) { + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${aiAnswerConfig.baseUrl}/gmail/list_agents`, + headers: { + [aiAnswerConfig.accessTokenHeaderKey]: context.auth, + }, + }); + return res.body; + }, +}); diff --git a/packages/pieces/community/aianswer/src/lib/actions/schedule-call-agent.ts b/packages/pieces/community/aianswer/src/lib/actions/schedule-call-agent.ts new file mode 100644 index 0000000..b0394f8 --- /dev/null +++ b/packages/pieces/community/aianswer/src/lib/actions/schedule-call-agent.ts @@ -0,0 +1,68 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { aiAnswerAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { aiAnswerConfig } from '../common/models'; + +export const scheduleCallAgent = createAction({ + name: 'scheduleCallAgent', + auth: aiAnswerAuth, + displayName: 'Schedule Call Agent', + description: 'Schedule a call with an agent', + props: { + agentID: Property.ShortText({ + displayName: 'Agent ID', + required: true, + }), + phoneNumber: Property.ShortText({ + displayName: 'Phone Number', + required: true, + }), + executionTime: Property.ShortText({ + displayName: 'Execution Time', + description: 'Time to schedule the call in YYYY-MM-DD HH:MM:SS format.', + required: true, + }), + timezone: Property.ShortText({ + displayName: 'Timezone', + description: 'Timezone of the scheduled call (e.g., Asia/Calcutta)', + required: true, + }), + prospectDetails: Property.Object({ + displayName: 'Prospect Details', + description: 'Optional prospect details with key-value pairs (e.g., customer_id, priority)', + required: false, + }), + }, + async run(context) { + const agentID = context.propsValue.agentID; + const phoneNumber = context.propsValue.phoneNumber; + const executionTime = context.propsValue.executionTime; + const timezone = context.propsValue.timezone; + const prospectDetails = context.propsValue.prospectDetails || {}; // Optional prospect details + + const requestBody = { + phone_number: phoneNumber, + execution_time: executionTime, + timezone: timezone, + prospect_details: { + details: prospectDetails, // Nested details object with custom key-value pairs + }, + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${aiAnswerConfig.baseUrl}/v2/schedule_call_agent/${agentID}`, + headers: { + [aiAnswerConfig.accessTokenHeaderKey]: context.auth, + }, + queryParams: { + agent_id: agentID + }, + body: requestBody, + }); + + return res.body; + }, +}); + + diff --git a/packages/pieces/community/aianswer/src/lib/common/models.ts b/packages/pieces/community/aianswer/src/lib/common/models.ts new file mode 100644 index 0000000..9b5a82d --- /dev/null +++ b/packages/pieces/community/aianswer/src/lib/common/models.ts @@ -0,0 +1,4 @@ +export const aiAnswerConfig = { + baseUrl: 'https://backend-development-e8jn.onrender.com', + accessTokenHeaderKey: 'Authorization', + }; \ No newline at end of file diff --git a/packages/pieces/community/aianswer/tsconfig.json b/packages/pieces/community/aianswer/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/aianswer/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/aianswer/tsconfig.lib.json b/packages/pieces/community/aianswer/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/aianswer/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/airparser/.eslintrc.json b/packages/pieces/community/airparser/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/airparser/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/airparser/README.md b/packages/pieces/community/airparser/README.md new file mode 100644 index 0000000..76e80af --- /dev/null +++ b/packages/pieces/community/airparser/README.md @@ -0,0 +1,7 @@ +# pieces-airparser + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-airparser` to build the library. diff --git a/packages/pieces/community/airparser/package.json b/packages/pieces/community/airparser/package.json new file mode 100644 index 0000000..a5afd32 --- /dev/null +++ b/packages/pieces/community/airparser/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-airparser", + "version": "0.0.1" +} diff --git a/packages/pieces/community/airparser/project.json b/packages/pieces/community/airparser/project.json new file mode 100644 index 0000000..d043262 --- /dev/null +++ b/packages/pieces/community/airparser/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-airparser", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/airparser/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/airparser", + "tsConfig": "packages/pieces/community/airparser/tsconfig.lib.json", + "packageJson": "packages/pieces/community/airparser/package.json", + "main": "packages/pieces/community/airparser/src/index.ts", + "assets": [ + "packages/pieces/community/airparser/*.md", + { + "input": "packages/pieces/community/airparser/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/airparser/src/index.ts b/packages/pieces/community/airparser/src/index.ts new file mode 100644 index 0000000..c8069b0 --- /dev/null +++ b/packages/pieces/community/airparser/src/index.ts @@ -0,0 +1,42 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { extractDataFromDocumentAction } from './lib/actions/extract-data-from-document'; +import { uploadDocumentAction } from './lib/actions/upload-document-for-parsing'; +import { airparserApiCall } from './lib/common'; +import { documentParsedTrigger } from './lib/triggers/document-parsed'; + +export const airparserAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'You can find your API key in the Airparser dashboard under Account Settings.', + validate: async ({ auth }) => { + try { + await airparserApiCall({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: '/inboxes', + }); + + return { + valid: true, + }; + } catch { + return { + valid: false, + error: 'Invalid API key.', + }; + } + }, +}); + +export const airparser = createPiece({ + displayName: 'Airparser', + description: 'Extract structured data from emails, PDFs, or documents with Airparser.', + auth: airparserAuth, + logoUrl: 'https://cdn.activepieces.com/pieces/airparser.png', + authors: ['krushnarout','kishanprmr'], + categories: [PieceCategory.PRODUCTIVITY], + actions: [extractDataFromDocumentAction, uploadDocumentAction], + triggers: [documentParsedTrigger], +}); diff --git a/packages/pieces/community/airparser/src/lib/actions/extract-data-from-document.ts b/packages/pieces/community/airparser/src/lib/actions/extract-data-from-document.ts new file mode 100644 index 0000000..1b7e642 --- /dev/null +++ b/packages/pieces/community/airparser/src/lib/actions/extract-data-from-document.ts @@ -0,0 +1,44 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { airparserAuth } from '../../index'; +import { airparserApiCall, GetDocumentResponse } from '../common'; +import { documentIdDropdown, inboxIdDropdown } from '../common/props'; + +export const extractDataFromDocumentAction = createAction({ + auth: airparserAuth, + name: 'extract_data_from_document', + displayName: 'Get Data from Document', + description: 'Retrieves parsed JSON data from a specific document.', + props: { + inboxId: inboxIdDropdown, + documentId: documentIdDropdown, + }, + async run(context) { + const { documentId } = context.propsValue; + + const response = await airparserApiCall({ + apiKey: context.auth, + method: HttpMethod.GET, + resourceUri: `/docs/${documentId}/extended`, + }); + + return { + json: response.json, + id: response._id, + inbox_id: response.inbox_id, + owner_id: response.owner_id, + name: response.name, + data_text: response.data_text, + format: response.format, + status: response.status, + created_at: response.created_at, + processed_at: response.processed_at, + secret: response.secret, + filename: response.filename, + content_type: response.content_type, + credits: response.credits, + }; + }, +}); + + diff --git a/packages/pieces/community/airparser/src/lib/actions/upload-document-for-parsing.ts b/packages/pieces/community/airparser/src/lib/actions/upload-document-for-parsing.ts new file mode 100644 index 0000000..d40dc5f --- /dev/null +++ b/packages/pieces/community/airparser/src/lib/actions/upload-document-for-parsing.ts @@ -0,0 +1,52 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import FormData from 'form-data'; +import { airparserAuth } from '../../index'; +import { BASE_URL } from '../common'; +import { inboxIdDropdown } from '../common/props'; + +export const uploadDocumentAction = createAction({ + auth: airparserAuth, + name: 'upload_document', + displayName: 'Upload Document', + description: 'Upload a document to an Airparser inbox for parsing.', + props: { + inboxId: inboxIdDropdown, + file: Property.File({ + displayName: 'File', + description: 'The document file to upload for parsing.', + required: true, + }), + fileName: Property.ShortText({ + displayName: 'File Name', + required: false, + }), + meta: Property.Object({ + displayName: 'Metadata', + description: 'Optional metadata to associate with the document.', + required: false, + }), + }, + async run(context) { + const { inboxId, file, meta, fileName } = context.propsValue; + + const formData = new FormData(); + formData.append('file', Buffer.from(file.base64, 'base64'), fileName || file.filename); + + if (meta) { + formData.append('meta', JSON.stringify(meta)); + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: BASE_URL + `/inboxes/${inboxId}/upload`, + headers: { + ...formData.getHeaders(), + 'X-API-Key': context.auth, + }, + body: formData, + }); + + return { docId: response.body }; + }, +}); diff --git a/packages/pieces/community/airparser/src/lib/common/index.ts b/packages/pieces/community/airparser/src/lib/common/index.ts new file mode 100644 index 0000000..4621ed4 --- /dev/null +++ b/packages/pieces/community/airparser/src/lib/common/index.ts @@ -0,0 +1,65 @@ +import { + httpClient, + HttpMessageBody, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; + +export const BASE_URL = 'https://api.airparser.com'; + +export type AirparserApiCallParams = { + apiKey: string; + method: HttpMethod; + resourceUri: string; + query?: Record; + body?: any; +}; + +export async function airparserApiCall({ + apiKey, + method, + resourceUri, + query, + body, +}: AirparserApiCallParams): Promise { + const qs: QueryParams = {}; + + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + qs[key] = String(value); + } + } + } + + const request: HttpRequest = { + method, + url: BASE_URL + resourceUri, + headers: { + 'X-API-Key': apiKey, + }, + queryParams: qs, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} + +export interface GetDocumentResponse { + json: any; + _id: string; + inbox_id: string; + owner_id: string; + name: string; + data_text: string; + format: string; + status: string; + created_at: string; + processed_at: string; + secret: string; + filename: string; + content_type: string; + credits: number; +} diff --git a/packages/pieces/community/airparser/src/lib/common/props.ts b/packages/pieces/community/airparser/src/lib/common/props.ts new file mode 100644 index 0000000..6b26287 --- /dev/null +++ b/packages/pieces/community/airparser/src/lib/common/props.ts @@ -0,0 +1,76 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { Property } from '@activepieces/pieces-framework'; +import { isNil } from '@activepieces/shared'; +import { airparserApiCall } from './index'; + +export const inboxIdDropdown = Property.Dropdown({ + displayName: 'Inbox', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your Airparser account.', + options: [], + }; + } + const response = await airparserApiCall<{ _id: string; name: string }[]>({ + apiKey: auth as string, + resourceUri: '/inboxes', + method: HttpMethod.GET, + }); + + return { + disabled: false, + options: response.map((inbox) => ({ + label: inbox.name, + value: inbox._id, + })), + }; + }, +}); + +export const documentIdDropdown = Property.Dropdown({ + displayName: 'Document', + required: true, + refreshers: ['inboxId'], + options: async ({ auth, inboxId }) => { + if (!auth || !inboxId) { + return { + disabled: true, + placeholder: 'Select an inbox first.', + options: [], + }; + } + + let hasMore = true; + let page = 1; + + const docs = []; + + do { + const response = await airparserApiCall<{ + hasPrevPage: boolean; + hasNextPage: boolean; + docs: { _id: string; name: string }[]; + }>({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: `/inboxes/${inboxId}/docs`, + query: { + page, + }, + }); + + if (!isNil(response.docs)) docs.push(...response.docs); + hasMore = response.hasNextPage; + page++; + } while (hasMore); + + return { + disabled: false, + options: docs.map((doc) => ({ label: doc.name, value: doc._id })), + }; + }, +}); diff --git a/packages/pieces/community/airparser/src/lib/triggers/document-parsed.ts b/packages/pieces/community/airparser/src/lib/triggers/document-parsed.ts new file mode 100644 index 0000000..fafca14 --- /dev/null +++ b/packages/pieces/community/airparser/src/lib/triggers/document-parsed.ts @@ -0,0 +1,109 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { + createTrigger, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { isNil } from '@activepieces/shared'; +import { airparserAuth } from '../../index'; +import { airparserApiCall, GetDocumentResponse } from '../common'; +import { inboxIdDropdown } from '../common/props'; + +export const documentParsedTrigger = createTrigger({ + auth: airparserAuth, + name: 'document_parsed', + displayName: 'Document Parsed', + description: 'Triggers when a new document is parsed in a specific inbox.', + type: TriggerStrategy.WEBHOOK, + props: { + inboxId: inboxIdDropdown, + markdown: Property.MarkDown({ + value: `## Airparser Webhook Setup + To use this trigger, you need to manually set up a webhook in your Airparser account: + + 1. Login to your Airparser account. + 2. Navigate to **Integrations** > **Webhooks** in the left sidebar. + 3. Enter the following URL in the webhooks field and select **Document Parsed** as webhook trigger: + \`\`\`text + {{webhookUrl}} + \`\`\` + 4. Click Save to register the webhook. + `, + }), + }, + async onEnable(context) { + // No need to register webhooks programmatically as user will do it manually + }, + async onDisable(context) { + // No need to unregister webhooks as user will do it manually + }, + async run(context) { + const payload = context.payload.body as { + inbox_id: string; + doc_id: string; + event: string; + }; + + if ( + payload.event === 'doc.parsed' && + payload.inbox_id === context.propsValue.inboxId + ) { + return [payload]; + } + return []; + }, + + async test(context) { + const { inboxId } = context.propsValue; + const listDocResponse = await airparserApiCall<{ + hasPrevPage: boolean; + hasNextPage: boolean; + docs: { _id: string; name: string }[]; + }>({ + apiKey: context.auth as string, + method: HttpMethod.GET, + resourceUri: `/inboxes/${inboxId}/docs`, + query: { + statuses: 'parsed', + }, + }); + + if (isNil(listDocResponse.docs)) return []; + + const items = []; + for (const doc of listDocResponse.docs) { + const response = await airparserApiCall({ + apiKey: context.auth, + method: HttpMethod.GET, + resourceUri: `/docs/${doc._id}/extended`, + }); + + items.push({ + inbox_id: inboxId, + doc_id: doc._id, + event: 'doc.parsed', + payload: { + filename: response.filename, + parsed: response.json, + }, + }); + } + + return items; + }, + + sampleData: { + inbox_id: '6846e11bb1abe002cb1ada14', + doc_id: '6846ee9db1abe002cb1b05ad', + event: 'doc.parsed', + payload: { + filename: 'sample.pdf', + parsed: { + billing_address: 'Your Company Name\nYour Address City, State Zip', + shipping_address: 'Client Name Address City, State Zip', + totalamount: '200.00', + created_at: '2025-06-09T14:24:29.099Z', + }, + }, + }, +}); diff --git a/packages/pieces/community/airparser/tsconfig.json b/packages/pieces/community/airparser/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/airparser/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/airparser/tsconfig.lib.json b/packages/pieces/community/airparser/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/airparser/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/airtable/.eslintrc.json b/packages/pieces/community/airtable/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/airtable/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/airtable/README.md b/packages/pieces/community/airtable/README.md new file mode 100644 index 0000000..3da5ca3 --- /dev/null +++ b/packages/pieces/community/airtable/README.md @@ -0,0 +1,7 @@ +# pieces-airtable + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-airtable` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/airtable/package.json b/packages/pieces/community/airtable/package.json new file mode 100644 index 0000000..16a3a59 --- /dev/null +++ b/packages/pieces/community/airtable/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-airtable", + "version": "0.4.19" +} diff --git a/packages/pieces/community/airtable/project.json b/packages/pieces/community/airtable/project.json new file mode 100644 index 0000000..0971b8d --- /dev/null +++ b/packages/pieces/community/airtable/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-airtable", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/airtable/src", + "projectType": "library", + "targets": { + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + }, + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/airtable", + "tsConfig": "packages/pieces/community/airtable/tsconfig.lib.json", + "packageJson": "packages/pieces/community/airtable/package.json", + "main": "packages/pieces/community/airtable/src/index.ts", + "assets": [ + "packages/pieces/community/airtable/*.md", + { + "input": "packages/pieces/community/airtable/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/airtable/src/index.ts b/packages/pieces/community/airtable/src/index.ts new file mode 100644 index 0000000..97a71b1 --- /dev/null +++ b/packages/pieces/community/airtable/src/index.ts @@ -0,0 +1,86 @@ +import { + AuthenticationType, + HttpMethod, + createCustomApiCallAction, + httpClient, +} from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { airtableCreateRecordAction } from './lib/actions/create-record'; +import { airtableDeleteRecordAction } from './lib/actions/delete-record'; +import { airtableFindRecordAction } from './lib/actions/find-record'; +import { airtableUpdateRecordAction } from './lib/actions/update-record'; +import { airtableNewRecordTrigger } from './lib/trigger/new-record.trigger'; +import { airtableUpdatedRecordTrigger } from './lib/trigger/update-record.trigger'; +import { airtableUploadFileToColumnAction } from './lib/actions/upload-file-to-column'; + +export const airtableAuth = PieceAuth.SecretText({ + displayName: 'Personal Access Token', + required: true, + description: ` + To obtain your personal token, follow these steps: + + 1. Log in to your Airtable account. + 2. Visit https://airtable.com/create/tokens/ to create one + 3. Click on "+ Add a base" and select the base you want to use or all bases. + 4. Click on "+ Add a scope" and select "data.records.read", "data.records.write" and "schema.bases.read". + 5. Click on "Create token" and copy the token. + `, + validate: async (auth) => { + try { + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.airtable.com/v0/meta/bases', + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.auth, + }, + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid personal access token', + }; + } + }, +}); + +export const airtable = createPiece({ + displayName: 'Airtable', + description: 'Low‒code platform to build apps.', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/airtable.png', + authors: [ + 'kanarelo', + 'TaskMagicKyle', + 'Salem-Alaa', + 'kishanprmr', + 'MoShizzle', + 'AbdulTheActivePiecer', + 'khaledmashaly', + 'abuaboud', + ], + categories: [PieceCategory.PRODUCTIVITY], + auth: airtableAuth, + actions: [ + airtableCreateRecordAction, + airtableFindRecordAction, + airtableUpdateRecordAction, + airtableDeleteRecordAction, + airtableUploadFileToColumnAction, + createCustomApiCallAction({ + baseUrl: () => { + return 'https://api.airtable.com/v0'; + }, + auth: airtableAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers: [airtableNewRecordTrigger, airtableUpdatedRecordTrigger], +}); diff --git a/packages/pieces/community/airtable/src/lib/actions/create-record.ts b/packages/pieces/community/airtable/src/lib/actions/create-record.ts new file mode 100644 index 0000000..9cff7a9 --- /dev/null +++ b/packages/pieces/community/airtable/src/lib/actions/create-record.ts @@ -0,0 +1,43 @@ +import { + DynamicPropsValue, + createAction, +} from '@activepieces/pieces-framework'; +import { airtableCommon } from '../common'; +import { airtableAuth } from '../../index'; + +export const airtableCreateRecordAction = createAction({ + auth: airtableAuth, + name: 'airtable_create_record', + displayName: 'Create Airtable Record', + description: 'Adds a record into an airtable', + props: { + base: airtableCommon.base, + tableId: airtableCommon.tableId, + fields: airtableCommon.fields, + }, + async run(context) { + const personalToken = context.auth; + const { base: baseId, tableId, fields } = context.propsValue; + const fieldsWithoutEmptyStrings: DynamicPropsValue = {}; + + Object.keys(fields).forEach((k) => { + if (fields[k] !== '') { + fieldsWithoutEmptyStrings[k] = fields[k]; + } + }); + const newFields: Record = + await airtableCommon.createNewFields( + personalToken, + baseId, + tableId as string, + fieldsWithoutEmptyStrings + ); + + return airtableCommon.createRecord({ + personalToken, + baseId, + tableId: tableId as string, + fields: newFields, + }); + }, +}); diff --git a/packages/pieces/community/airtable/src/lib/actions/delete-record.ts b/packages/pieces/community/airtable/src/lib/actions/delete-record.ts new file mode 100644 index 0000000..af08686 --- /dev/null +++ b/packages/pieces/community/airtable/src/lib/actions/delete-record.ts @@ -0,0 +1,26 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { airtableCommon } from '../common'; +import { airtableAuth } from '../../index'; + +export const airtableDeleteRecordAction = createAction({ + auth: airtableAuth, + name: 'airtable_delete_record', + displayName: 'Delete Airtable Record', + description: 'Deletes a record in airtable', + props: { + base: airtableCommon.base, + tableId: airtableCommon.tableId, + recordId: airtableCommon.recordId, + }, + async run(context) { + const personalToken = context.auth; + const { base: baseId, tableId, recordId } = context.propsValue; + + return await airtableCommon.deleteRecord({ + personalToken, + baseId: baseId as string, + tableId: tableId as string, + recordId: recordId as string, + }); + }, +}); diff --git a/packages/pieces/community/airtable/src/lib/actions/find-record.ts b/packages/pieces/community/airtable/src/lib/actions/find-record.ts new file mode 100644 index 0000000..254bc26 --- /dev/null +++ b/packages/pieces/community/airtable/src/lib/actions/find-record.ts @@ -0,0 +1,40 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { airtableCommon } from '../common'; +import { airtableAuth } from '../../index'; + +export const airtableFindRecordAction = createAction({ + auth: airtableAuth, + name: 'airtable_find_record', + displayName: 'Find Airtable Record', + description: 'Find a record in airtable', + props: { + base: airtableCommon.base, + tableId: airtableCommon.tableId, + searchField: airtableCommon.fieldNames, + searchValue: Property.ShortText({ + displayName: 'Search Value', + required: true, + }), + limitToView: airtableCommon.views, + }, + async run(context) { + const personalToken = context.auth; + const { + base: baseId, + tableId, + searchField, + searchValue, + limitToView, + } = context.propsValue; + + return await airtableCommon.findRecord({ + personalToken, + baseId: baseId as string, + tableId: tableId as string, + searchField: searchField as string, + searchValue: searchValue as string, + limitToView: limitToView as string, + }); + }, +}); diff --git a/packages/pieces/community/airtable/src/lib/actions/update-record.ts b/packages/pieces/community/airtable/src/lib/actions/update-record.ts new file mode 100644 index 0000000..96fbb2e --- /dev/null +++ b/packages/pieces/community/airtable/src/lib/actions/update-record.ts @@ -0,0 +1,48 @@ +import { + createAction, + DynamicPropsValue, + Property, +} from '@activepieces/pieces-framework'; + +import { airtableCommon } from '../common'; +import { airtableAuth } from '../../index'; + +export const airtableUpdateRecordAction = createAction({ + auth: airtableAuth, + name: 'airtable_update_record', + displayName: 'Update Airtable Record', + description: 'Update a record in airtable', + props: { + base: airtableCommon.base, + tableId: airtableCommon.tableId, + recordId: airtableCommon.recordId, + fields: airtableCommon.fields, + }, + async run(context) { + const personalToken = context.auth; + const { base: baseId, tableId, recordId, fields } = context.propsValue; + + const fieldsWithoutEmptyStrings: DynamicPropsValue = {}; + + Object.keys(fields).forEach((k) => { + if (fields[k] !== '') { + fieldsWithoutEmptyStrings[k] = fields[k]; + } + }); + const updatedFields: Record = + await airtableCommon.createNewFields( + personalToken, + baseId, + tableId as string, + fieldsWithoutEmptyStrings + ); + + return await airtableCommon.updateRecord({ + personalToken, + baseId: baseId as string, + tableId: tableId as string, + recordId: recordId as string, + fields: updatedFields as Record, + }); + }, +}); diff --git a/packages/pieces/community/airtable/src/lib/actions/upload-file-to-column.ts b/packages/pieces/community/airtable/src/lib/actions/upload-file-to-column.ts new file mode 100644 index 0000000..9e9d56f --- /dev/null +++ b/packages/pieces/community/airtable/src/lib/actions/upload-file-to-column.ts @@ -0,0 +1,102 @@ +import { airtableAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { airtableCommon } from '../common'; +import { AirtableTable } from './../common/models'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; + +export const airtableUploadFileToColumnAction = createAction({ + auth: airtableAuth, + name: 'airtable_upload_file_to_column', + displayName: 'Upload File to Column', + description: 'Uploads a file to attachment type column.', + props: { + base: airtableCommon.base, + tableId: airtableCommon.tableId, + attachment_column: Property.Dropdown({ + displayName: 'Attachment Column', + required: true, + refreshers: ['base', 'tableId'], + options: async ({ auth, base, tableId }) => { + if (!auth || !base || !tableId) { + return { + placeholder: 'Please select table first', + options: [], + disabled: true, + }; + } + + const airtable: AirtableTable = await airtableCommon.fetchTable({ + token: auth as unknown as string, + baseId: base as unknown as string, + tableId: tableId as unknown as string, + }); + + return { + disabled: false, + + options: airtable.fields + .filter((field) => field.type === 'multipleAttachments') + .map((field) => { + return { + label: field.name, + value: field.id, + }; + }), + }; + }, + }), + recordId: Property.ShortText({ + displayName: 'Record ID', + required: true, + description: 'The ID of the record to which you want to upload the file.', + }), + file: Property.File({ + displayName: 'File', + required: true, + description: + 'The file to be uploaded, which can be provided either as a public file URL or in Base64 encoded format.', + }), + file_content_type: Property.ShortText({ + displayName: 'File Content Type', + required: true, + description: `Specifies the MIME type of the file being uploaded (e.g., 'image/png', 'application/pdf').`, + }), + filename: Property.ShortText({ + displayName: 'File Name', + description: 'The name of the file as it should appear after upload.', + required: false, + }), + }, + async run(context) { + const baseId = context.propsValue.base; + const recordId = context.propsValue.recordId; + const fieldId = context.propsValue.attachment_column; + const fileInput = context.propsValue.file; + + const fileName = context.propsValue.filename ?? fileInput.filename; + const fileBase64Data = fileInput.base64; + const fileContentType = context.propsValue.file_content_type; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://content.airtable.com/v0/${baseId}/${recordId}/${fieldId}/uploadAttachment`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + body: { + contentType: fileContentType, + file: fileBase64Data, + filename: fileName, + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, +}); diff --git a/packages/pieces/community/airtable/src/lib/common/index.ts b/packages/pieces/community/airtable/src/lib/common/index.ts new file mode 100644 index 0000000..d6bb4cb --- /dev/null +++ b/packages/pieces/community/airtable/src/lib/common/index.ts @@ -0,0 +1,525 @@ +import { + AuthenticationType, + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import Airtable from 'airtable'; +import { + AirtableBase, + AirtableEnterpriseFields, + AirtableField, + AirtableFieldMapping, + AirtableRecord, + AirtableTable, + AirtableView, +} from './models'; +import { isNil } from '@activepieces/shared'; + +export const airtableCommon = { + base: Property.Dropdown({ + displayName: 'Base', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account', + }; + } + + try { + const response = await httpClient.sendRequest<{ + bases: AirtableBase[]; + }>({ + method: HttpMethod.GET, + url: 'https://api.airtable.com/v0/meta/bases', + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + if (response.status === 200) { + return { + disabled: false, + options: response.body.bases.map((base) => { + return { value: base.id, label: base.name }; + }), + }; + } + } catch (e) { + console.debug(e); + return { + disabled: true, + options: [], + placeholder: 'Please check your permission scope', + }; + } + + return { + disabled: true, + options: [], + }; + }, + }), + + tableId: Property.Dropdown({ + displayName: 'Table', + required: true, + refreshers: ['base'], + options: async ({ auth, base }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account', + }; + } + if (!base) { + return { + disabled: true, + options: [], + placeholder: 'Please select a base first', + }; + } + + try { + const tables: AirtableTable[] = await airtableCommon.fetchTableList({ + token: auth as string, + baseId: base as string, + }); + + if (tables) { + return { + disabled: false, + options: tables.map((table) => ({ + value: table.id, + label: table.name, + })), + }; + } + } catch (e) { + console.debug(e); + + return { + disabled: true, + options: [], + placeholder: 'Please check your permission scope', + }; + } + + return { + disabled: true, + options: [], + }; + }, + }), + + views: Property.Dropdown({ + displayName: 'View', + required: false, + refreshers: ['base', 'tableId'], + options: async ({ auth, base, tableId }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account', + }; + } + if (!base) { + return { + disabled: true, + options: [], + placeholder: 'Please select a base first', + }; + } + if (!tableId) { + return { + disabled: true, + options: [], + placeholder: 'Please select a table first', + }; + } + + const views: AirtableView[] = await airtableCommon.fetchViews({ + token: auth as string, + baseId: base as string, + tableId: tableId as string, + }); + + if (views) { + return { + disabled: false, + options: views.map((view) => ({ + value: view.id, + label: view.name, + })), + }; + } + + return { + disabled: true, + options: [], + }; + }, + }), + + recordId: Property.ShortText({ + displayName: 'Record ID', + required: true, + description: + 'The ID of the record you want to update. You can find the record ID by clicking on the record and then clicking on the share button. The ID will be in the URL.', + }), + + fields: Property.DynamicProperties({ + displayName: 'Table', + required: true, + refreshers: ['base', 'tableId'], + + props: async ({ auth, base, tableId }) => { + if (!auth) return {}; + if (!base) return {}; + if (!tableId) return {}; + + const airtable: AirtableTable = await airtableCommon.fetchTable({ + token: auth as unknown as string, + baseId: base as unknown as string, + tableId: tableId as unknown as string, + }); + const fields = airtable.fields.reduce((acc, field) => { + if (!AirtableEnterpriseFields.includes(field.type)) { + const params = { + displayName: field.name, + description: ['date', 'dateTime'].includes(field.type) + ? `${ + field.description ? field.description : '' + }Expected format: mmmm d,yyyy` + : field.description, + required: false, + }; + + if (isNil(AirtableFieldMapping[field.type])) { + acc[field.id] = Property.ShortText({ + ...params, + }); + } else if ( + field.type === 'singleSelect' || + field.type === 'multipleSelects' + ) { + const options = field.options?.choices.map( + (option: { id: string; name: string }) => ({ + value: option.id, + label: option.name, + }) + ); + + acc[field.id] = AirtableFieldMapping[field.type]({ + ...params, + options: { + options: options ?? [], + }, + }); + } else { + acc[field.id] = AirtableFieldMapping[field.type](params); + } + } + + return acc; + }, {} as DynamicPropsValue); + + return fields; + }, + }), + + fieldNames: Property.Dropdown({ + displayName: 'Search Field', + required: true, + refreshers: ['base', 'tableId'], + options: async ({ auth, base, tableId }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account', + }; + } + if (!base) { + return { + disabled: true, + options: [], + placeholder: 'Please select a base first', + }; + } + if (!tableId) { + return { + disabled: true, + options: [], + placeholder: 'Please select a table first', + }; + } + const airtable: AirtableTable = await airtableCommon.fetchTable({ + token: auth as unknown as string, + baseId: base as unknown as string, + tableId: tableId as unknown as string, + }); + return { + disabled: false, + options: airtable.fields.map((field: AirtableField) => ({ + label: field.name, + value: field.name, + })), + }; + }, + }), + + async createNewFields( + auth: string, + base: string, + tableId: string, + fields: Record + ) { + if (!auth) return fields; + if (!base) return fields; + if (!tableId) return fields; + + const newFields: Record = {}; + + const airtable: AirtableTable = await airtableCommon.fetchTable({ + token: auth, + baseId: base, + tableId: tableId, + }); + + airtable.fields.forEach((field) => { + if (!AirtableEnterpriseFields.includes(field.type)) { + const key = field.id; + + if (field.type === 'multipleAttachments' && fields[key]) { + newFields[key] = [ + { + url: fields[key] as string, + }, + ]; + } else if ( + ['multipleRecordLinks', 'multipleSelects'].includes(field.type) + ) { + if (Array.isArray(fields[key]) && (fields[key] as any[]).length > 0) { + newFields[key] = fields[key]; + } + } else { + newFields[key] = fields[key]; + } + } + }); + return newFields; + }, + + async getTableSnapshot(params: Params) { + Airtable.configure({ + apiKey: params.personalToken, + }); + const airtable = new Airtable(); + const currentTableSnapshot = ( + await airtable + .base(params.baseId) + .table(params.tableId) + .select(params.limitToView ? { view: params.limitToView } : {}) + .all() + ) + .map((r) => r._rawJson) + .sort( + (x, y) => + new Date(x.createdTime).getTime() - new Date(y.createdTime).getTime() + ); + return currentTableSnapshot; + }, + + async fetchTableList({ + token, + baseId, + }: { + token: string; + baseId: string; + }): Promise { + const response = await httpClient.sendRequest<{ tables: AirtableTable[] }>({ + method: HttpMethod.GET, + url: `https://api.airtable.com/v0/meta/bases/${baseId}/tables`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + }); + + if (response.status === 200) { + return response.body.tables; + } + + return []; + }, + + async fetchTable({ + token, + baseId, + tableId, + }: { + token: string; + baseId: string; + tableId: string; + }) { + const response = await airtableCommon.fetchTableList({ token, baseId }); + return response.find((t) => t.id === tableId)!; + }, + + async fetchViews({ + token, + baseId, + tableId, + }: { + token: string; + baseId: string; + tableId: string; + }) { + const response = await httpClient.sendRequest<{ tables: AirtableTable[] }>({ + method: HttpMethod.GET, + url: `https://api.airtable.com/v0/meta/bases/${baseId}/tables`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + }); + + const table = response.body.tables.find((table) => table.id === tableId); + if (table) { + return table.views; + } + return []; + }, + + async createRecord({ + personalToken: token, + fields, + tableId, + baseId, + }: Params) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://api.airtable.com/v0/${baseId}/${tableId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + body: { + fields, + typecast: true, + }, + }; + + const response = await httpClient.sendRequest(request); + + if (response.status === 200) { + return response.body; + } + + return response; + }, + + async findRecord({ + personalToken: token, + searchField, + searchValue, + tableId, + baseId, + limitToView, + }: Params) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `https://api.airtable.com/v0/${baseId}/${tableId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + queryParams: { + filterByFormula: `FIND("${searchValue}",{${searchField}})`, + view: limitToView ?? '', + }, + }; + + const response = await httpClient.sendRequest<{ + records: AirtableRecord[]; + }>(request); + + if (response.status === 200) { + return response.body.records; + } + + return []; + }, + async updateRecord({ + personalToken: token, + fields, + recordId, + tableId, + baseId, + }: Params) { + const request: HttpRequest = { + method: HttpMethod.PATCH, + url: `https://api.airtable.com/v0/${baseId}/${tableId}/${recordId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + body: { + fields, + }, + }; + + const response = await httpClient.sendRequest(request); + + if (response.status === 200) { + return response.body; + } + + return response; + }, + + async deleteRecord({ + personalToken: token, + recordId, + tableId, + baseId, + }: Params) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `https://api.airtable.com/v0/${baseId}/${tableId}/${recordId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + }; + + const response = await httpClient.sendRequest(request); + + if (response.status === 200) { + return response.body; + } + + return response; + }, +}; + +interface Params { + personalToken: string; + baseId: string; + tableId: string; + fields?: Record; + recordId?: string; + searchValue?: string; + searchField?: string; + fieldNames?: string[]; + limitToView?: string; + sortField?: string; +} diff --git a/packages/pieces/community/airtable/src/lib/common/models.ts b/packages/pieces/community/airtable/src/lib/common/models.ts new file mode 100644 index 0000000..72e3d1f --- /dev/null +++ b/packages/pieces/community/airtable/src/lib/common/models.ts @@ -0,0 +1,128 @@ +import { Property } from '@activepieces/pieces-framework'; + +export interface AirtableBase { + id: string; + name: string; + permissionLevel: AirtablePermissionLevel; +} + +export interface AirtableRecord { + fields: Record; + createdTime: Date; + id: string; +} +export interface AirtableField { + id: string; + name: string; + description: string; + type: AirtableFieldType; + options?: { + choices: AirtableChoice[]; + }; +} +export interface AirtableChoice { + id: string; + name: string; + color: string; +} + +export interface AirtableTable { + id: string; + name: string; + fields: AirtableField[]; + description: string; + primaryFieldId: string; + views: { + id: string; + name: string; + type: string; + }[]; +} + +export interface AirtableView { + id: string; + name: string; +} +export interface AirtableCreateRecordBody { + records?: AirtableRecord[]; + fields?: Record; +} + +declare type AirtablePermissionLevel = + | 'none' + | 'read' + | 'comment' + | 'edit' + | 'create'; +export type AirtableFieldType = + | 'singleLineText' + | 'email' + | 'url' + | 'multilineText' + | 'number' + | 'percent' + | 'currency' + | 'singleSelect' + | 'multipleSelects' + | 'multipleRecordLinks' + | 'date' + | 'dateTime' + | 'phoneNumber' + | 'multipleAttachments' + | 'checkbox' + | 'formula' + | 'createdTime' + | 'rollup' + | 'count' + | 'lookup' + | 'multipleLookupValues' + | 'autoNumber' + | 'barcode' + | 'rating' + | 'richText' + | 'duration' + | 'lastModifiedTime' + | 'button' + | 'createdBy' + | 'lastModifiedBy' + | 'externalSyncSource'; + +export const AirtableEnterpriseFields = [ + 'singleCollaborator', + 'multipleCollaborators', + 'aiText', +]; + +export const AirtableFieldMapping = { + singleLineText: Property.ShortText, + email: Property.ShortText, + url: Property.ShortText, + multilineText: Property.LongText, + number: Property.Number, + percent: Property.ShortText, + currency: Property.ShortText, + singleSelect: Property.StaticDropdown, + multipleSelects: Property.StaticMultiSelectDropdown, + multipleRecordLinks: Property.Array, + date: Property.ShortText, + dateTime: Property.ShortText, + phoneNumber: Property.ShortText, + multipleAttachments: Property.ShortText, + checkbox: Property.Checkbox, + formula: Property.ShortText, + createdTime: Property.ShortText, + rollup: Property.ShortText, + count: Property.ShortText, + lookup: Property.ShortText, + multipleLookupValues: Property.ShortText, + autoNumber: Property.Number, + barcode: Property.ShortText, + rating: Property.ShortText, + richText: Property.ShortText, + duration: Property.ShortText, + lastModifiedTime: Property.ShortText, + button: Property.ShortText, + createdBy: Property.ShortText, + lastModifiedBy: Property.ShortText, + externalSyncSource: Property.ShortText, +}; diff --git a/packages/pieces/community/airtable/src/lib/trigger/new-record.trigger.ts b/packages/pieces/community/airtable/src/lib/trigger/new-record.trigger.ts new file mode 100644 index 0000000..068c80e --- /dev/null +++ b/packages/pieces/community/airtable/src/lib/trigger/new-record.trigger.ts @@ -0,0 +1,62 @@ +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { + StaticPropsValue, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { airtableAuth } from '../../'; +import { airtableCommon } from '../common'; + +const props = { + base: airtableCommon.base, + tableId: airtableCommon.tableId, + viewId: airtableCommon.views, +}; + +const polling: Polling> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue }) => { + const records = await airtableCommon.getTableSnapshot({ + personalToken: auth, + baseId: propsValue.base, + tableId: propsValue.tableId!, + limitToView: propsValue.viewId, + }); + return records.map((record) => ({ + epochMilliSeconds: Date.parse(record.createdTime), + data: record, + })); + }, +}; + +export const airtableNewRecordTrigger = createTrigger({ + auth: airtableAuth, + name: 'new_record', + displayName: 'New Record', + description: 'Triggers when a new record is added to the selected table.', + props, + sampleData: {}, + type: TriggerStrategy.POLLING, + async test(context) { + const { store, auth, propsValue, files } = context; + return await pollingHelper.test(polling, { store, auth, propsValue, files }); + }, + async onEnable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onEnable(polling, { store, auth, propsValue }); + }, + + async onDisable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onDisable(polling, { store, auth, propsValue }); + }, + + async run(context) { + const { store, auth, propsValue, files } = context; + return await pollingHelper.poll(polling, { store, auth, propsValue, files }); + }, +}); diff --git a/packages/pieces/community/airtable/src/lib/trigger/update-record.trigger.ts b/packages/pieces/community/airtable/src/lib/trigger/update-record.trigger.ts new file mode 100644 index 0000000..c450e91 --- /dev/null +++ b/packages/pieces/community/airtable/src/lib/trigger/update-record.trigger.ts @@ -0,0 +1,127 @@ +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { + Property, + StaticPropsValue, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import Airtable from 'airtable'; +import dayjs from 'dayjs'; +import { airtableAuth } from '../../'; +import { airtableCommon } from '../common'; +import { AirtableField, AirtableTable } from '../common/models'; + +const props = { + base: airtableCommon.base, + tableId: airtableCommon.tableId, + sortFields: Property.Dropdown({ + displayName: 'Trigger field', + description: `**Last Modified Time** field will be used to watch new or updated records.Please create **Last Modified Time** field in your schema,if you don't have any timestamp field.`, + required: true, + refreshers: ['base', 'tableId'], + options: async ({ auth, base, tableId }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account', + }; + } + if (!base) { + return { + disabled: true, + options: [], + placeholder: 'Please select a base first', + }; + } + if (!tableId) { + return { + disabled: true, + options: [], + placeholder: 'Please select a table first', + }; + } + const airtable: AirtableTable = await airtableCommon.fetchTable({ + token: auth as unknown as string, + baseId: base as unknown as string, + tableId: tableId as unknown as string, + }); + + return { + disabled: false, + options: airtable.fields + .filter((field: AirtableField) => field.type == 'lastModifiedTime') + .map((field: AirtableField) => ({ + label: field.name, + value: field.name, + })), + }; + }, + }), + viewId: airtableCommon.views, +}; +const polling: Polling> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + Airtable.configure({ + apiKey: auth, + }); + const airtable = new Airtable(); + + const lastUpdateDate = + lastFetchEpochMS === 0 + ? dayjs().subtract(1, 'day').toISOString() + : dayjs(lastFetchEpochMS).toISOString(); + + const records = await airtable + .base(propsValue.base) + .table(propsValue.tableId!) + .select({ + filterByFormula: `IS_AFTER({${ + propsValue.sortFields as string + }},DATETIME_PARSE("${lastUpdateDate}","YYYY-MM-DD HH:mm:ss.SSS"))`, + view: propsValue.viewId ?? '', + }) + .all(); + + return records.map((item) => { + return { + epochMilliSeconds: dayjs( + item.fields[propsValue.sortFields] as string + ).valueOf(), + data: item._rawJson, + }; + }); + }, +}; + +export const airtableUpdatedRecordTrigger = createTrigger({ + auth: airtableAuth, + name: 'updated_record', + displayName: 'New or Updated Record', + description: + 'Triggers when a record is created or updated in selected table.', + props, + sampleData: {}, + type: TriggerStrategy.POLLING, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async onEnable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onEnable(polling, { store, auth, propsValue }); + }, + + async onDisable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onDisable(polling, { store, auth, propsValue }); + }, + + async run(context) { + return await pollingHelper.poll(polling, context); + }, +}); diff --git a/packages/pieces/community/airtable/tsconfig.json b/packages/pieces/community/airtable/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/airtable/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/airtable/tsconfig.lib.json b/packages/pieces/community/airtable/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/airtable/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/amazon-s3/.eslintrc.json b/packages/pieces/community/amazon-s3/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/amazon-s3/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/amazon-s3/README.md b/packages/pieces/community/amazon-s3/README.md new file mode 100644 index 0000000..0cd9c82 --- /dev/null +++ b/packages/pieces/community/amazon-s3/README.md @@ -0,0 +1,7 @@ +# pieces-amazon-s3 + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-amazon-s3` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/amazon-s3/package.json b/packages/pieces/community/amazon-s3/package.json new file mode 100644 index 0000000..266c7b8 --- /dev/null +++ b/packages/pieces/community/amazon-s3/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-amazon-s3", + "version": "0.3.7" +} diff --git a/packages/pieces/community/amazon-s3/project.json b/packages/pieces/community/amazon-s3/project.json new file mode 100644 index 0000000..5e5b953 --- /dev/null +++ b/packages/pieces/community/amazon-s3/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-amazon-s3", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/amazon-s3/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/amazon-s3", + "tsConfig": "packages/pieces/community/amazon-s3/tsconfig.lib.json", + "packageJson": "packages/pieces/community/amazon-s3/package.json", + "main": "packages/pieces/community/amazon-s3/src/index.ts", + "assets": [ + "packages/pieces/community/amazon-s3/*.md", + { + "input": "packages/pieces/community/amazon-s3/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/amazon-s3/src/index.ts b/packages/pieces/community/amazon-s3/src/index.ts new file mode 100644 index 0000000..b1e8061 --- /dev/null +++ b/packages/pieces/community/amazon-s3/src/index.ts @@ -0,0 +1,200 @@ +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { readFile } from './lib/actions/read-file'; +import { amazons3UploadFile } from './lib/actions/upload-file'; +import { createS3 } from './lib/common'; +import { newFile } from './lib/triggers/new-file'; +import { generateSignedUrl } from './lib/actions/generate-signed-url'; + +const description = ` +This piece allows you to upload files to Amazon S3 or other S3 compatible services. + +Amazon S3 Settings: +Regions: https://docs.aws.amazon.com/general/latest/gr/s3.html +Endpoint: leave blank +`; + +export const amazonS3Auth = PieceAuth.CustomAuth({ + description: description, + props: { + accessKeyId: Property.ShortText({ + displayName: 'Access Key ID', + required: true, + }), + secretAccessKey: PieceAuth.SecretText({ + displayName: 'Secret Access Key', + required: true, + }), + bucket: Property.ShortText({ + displayName: 'Bucket', + required: true, + }), + endpoint: Property.ShortText({ + displayName: 'Endpoint', + required: false, + }), + region: Property.StaticDropdown({ + displayName: 'Region', + options: { + options: [ + { + label: 'Default', + value: 'us-east-1', + }, + { + label: 'US East (N. Virginia) [us-east-1]', + value: 'us-east-1', + }, + { + label: 'US East (Ohio) [us-east-2]', + value: 'us-east-2', + }, + { + label: 'US West (N. California) [us-west-1]', + value: 'us-west-1', + }, + { + label: 'US West (Oregon) [us-west-2]', + value: 'us-west-2', + }, + { + label: 'Africa (Cape Town) [af-south-1]', + value: 'af-south-1', + }, + { + label: 'Asia Pacific (Hong Kong) [ap-east-1]', + value: 'ap-east-1', + }, + { + label: 'Asia Pacific (Mumbai) [ap-south-1]', + value: 'ap-south-1', + }, + { + label: 'Asia Pacific (Osaka-Local) [ap-northeast-3]', + value: 'ap-northeast-3', + }, + { + label: 'Asia Pacific (Seoul) [ap-northeast-2]', + value: 'ap-northeast-2', + }, + { + label: 'Asia Pacific (Singapore) [ap-southeast-1]', + value: 'ap-southeast-1', + }, + { + label: 'Asia Pacific (Sydney) [ap-southeast-2]', + value: 'ap-southeast-2', + }, + { + label: 'Asia Pacific (Tokyo) [ap-northeast-1]', + value: 'ap-northeast-1', + }, + { + label: 'Canada (Central) [ca-central-1]', + value: 'ca-central-1', + }, + { + label: 'Europe (Frankfurt) [eu-central-1]', + value: 'eu-central-1', + }, + { + label: 'Europe (Ireland) [eu-west-1]', + value: 'eu-west-1', + }, + { + label: 'Europe (London) [eu-west-2]', + value: 'eu-west-2', + }, + { + label: 'Europe (Milan) [eu-south-1]', + value: 'eu-south-1', + }, + { + label: 'Europe (Paris) [eu-west-3]', + value: 'eu-west-3', + }, + { + label: 'Europe (Stockholm) [eu-north-1]', + value: 'eu-north-1', + }, + { + label: 'Middle East (Bahrain) [me-south-1]', + value: 'me-south-1', + }, + { + label: 'South America (São Paulo) [sa-east-1]', + value: 'sa-east-1', + }, + { + label: 'Europe (Spain) [eu-south-2]', + value: 'eu-south-2', + }, + { + label: 'Asia Pacific (Hyderabad) [ap-south-2]', + value: 'ap-south-2', + }, + { + label: 'Asia Pacific (Jakarta) [ap-southeast-3]', + value: 'ap-southeast-3', + }, + { + label: 'Asia Pacific (Melbourne) [ap-southeast-4]', + value: 'ap-southeast-4', + }, + { + label: 'China (Beijing) [cn-north-1]', + value: 'cn-north-1', + }, + { + label: 'China (Ningxia) [cn-northwest-1]', + value: 'cn-northwest-1', + }, + { + label: 'Europe (Zurich) [eu-central-2]', + value: 'eu-central-2', + }, + { + label: 'Middle East (UAE) [me-central-1]', + value: 'me-central-1', + }, + ], + }, + required: true, + }), + }, + validate: async ({ auth }) => { + const s3 = createS3(auth); + try { + await s3.listObjectsV2({ + Bucket: auth.bucket, + MaxKeys: 1, + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: (e as Error)?.message, + }; + } + }, + required: true, +}); + +export const amazonS3 = createPiece({ + displayName: 'Amazon S3', + description: 'Scalable storage in the cloud', + + logoUrl: 'https://cdn.activepieces.com/pieces/amazon-s3.png', + minimumSupportedRelease: '0.30.0', + authors: ["Willianwg","kishanprmr","MoShizzle","AbdulTheActivePiecer","khaledmashaly","abuaboud", "Kevinyu-alan"], + categories: [PieceCategory.DEVELOPER_TOOLS], + auth: amazonS3Auth, + actions: [amazons3UploadFile, readFile, generateSignedUrl], + triggers: [newFile], +}); diff --git a/packages/pieces/community/amazon-s3/src/lib/actions/generate-signed-url.ts b/packages/pieces/community/amazon-s3/src/lib/actions/generate-signed-url.ts new file mode 100644 index 0000000..7d2e56a --- /dev/null +++ b/packages/pieces/community/amazon-s3/src/lib/actions/generate-signed-url.ts @@ -0,0 +1,52 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'; +import { amazonS3Auth } from '../..'; +import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; + +export const generateSignedUrl = createAction({ + auth: amazonS3Auth, + name: 'generate-signed-url', + displayName: 'Generate signed URL', + description: 'Generate a signed URL for a file in a s3 bucket', + props: { + key: Property.ShortText({ + displayName: 'Key', + description: 'The path/filename of the file to get', + required: true, + }), + expiresIn: Property.Number({ + displayName: 'Expires In (minutes)', + description: 'How long the URL should remain valid (in minutes).', + required: true, + defaultValue: 10, + }), + }, + async run(context) { + const { bucket, region, accessKeyId, secretAccessKey } = context.auth; + const { key, expiresIn } = context.propsValue; + + const clientUrl = await createPresignedUrlWithClient({ + region, + bucket, + key, + accessKeyId, + secretAccessKey, + expiresIn + }); + + return clientUrl + }, +}); + +const createPresignedUrlWithClient = ({ region, bucket, key, accessKeyId, secretAccessKey, expiresIn }: any) => { + const client = new S3Client({ + region, + credentials: { + accessKeyId, + secretAccessKey, + }, + }); + + const command = new GetObjectCommand({ Bucket: bucket, Key: key }); + return getSignedUrl(client, command, { expiresIn: expiresIn * 60 }); +}; \ No newline at end of file diff --git a/packages/pieces/community/amazon-s3/src/lib/actions/read-file.ts b/packages/pieces/community/amazon-s3/src/lib/actions/read-file.ts new file mode 100644 index 0000000..0b6eee5 --- /dev/null +++ b/packages/pieces/community/amazon-s3/src/lib/actions/read-file.ts @@ -0,0 +1,36 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { S3 } from '@aws-sdk/client-s3'; +import { amazonS3Auth } from '../..'; +import { createS3 } from '../common'; + +export const readFile = createAction({ + auth: amazonS3Auth, + name: 'read-file', + displayName: 'Read File', + description: 'Read a file from S3 to use it in other steps', + props: { + key: Property.ShortText({ + displayName: 'Key', + description: 'The key of the file to read', + required: true, + }), + }, + async run(context) { + const { bucket } = context.auth; + const { key } = context.propsValue; + const s3 = createS3(context.auth); + + const file = await s3.getObject({ + Bucket: bucket, + Key: key, + }); + const base64 = await file.Body?.transformToString('base64'); + if (!base64) { + throw new Error(`Could not read file ${key} from S3`); + } + return await context.files.write({ + fileName: key, + data: Buffer.from(base64, 'base64'), + }); + }, +}); diff --git a/packages/pieces/community/amazon-s3/src/lib/actions/upload-file.ts b/packages/pieces/community/amazon-s3/src/lib/actions/upload-file.ts new file mode 100644 index 0000000..48e488c --- /dev/null +++ b/packages/pieces/community/amazon-s3/src/lib/actions/upload-file.ts @@ -0,0 +1,133 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { amazonS3Auth } from '../..'; +import { createS3 } from '../common'; +import { ObjectCannedACL } from '@aws-sdk/client-s3'; + +export const amazons3UploadFile = createAction({ + auth: amazonS3Auth, + name: 'upload-file', + displayName: 'Upload File', + description: 'Upload an File to S3', + props: { + file: Property.File({ + displayName: 'File', + required: true, + }), + fileName: Property.ShortText({ + displayName: 'File Name', + required: false, + description: 'my-file-name (no extension)', + }), + acl: Property.StaticDropdown({ + displayName: 'ACL', + required: false, + options: { + options: [ + { + label: 'private', + value: 'private', + }, + { + label: 'public-read', + value: 'public-read', + }, + { + label: 'public-read-write', + value: 'public-read-write', + }, + { + label: 'authenticated-read', + value: 'authenticated-read', + }, + { + label: 'aws-exec-read', + value: 'aws-exec-read', + }, + { + label: 'bucket-owner-read', + value: 'bucket-owner-read', + }, + { + label: 'bucket-owner-full-control', + value: 'bucket-owner-full-control', + }, + ], + }, + }), + type: Property.StaticDropdown({ + displayName: 'Type', + required: true, + options: { + options: [ + { + label: 'image/png', + value: 'image/png', + }, + { + label: 'image/jpeg', + value: 'image/jpeg', + }, + { + label: 'image/gif', + value: 'image/gif', + }, + { + label: 'audio/mpeg', + value: 'audio/mpeg', + }, + { + label: 'audio/wav', + value: 'audio/wav', + }, + { + label: 'video/mp4', + value: 'video/mp4', + }, + { + label: 'application/pdf', + value: 'application/pdf', + }, + { + label: 'application/msword', + value: 'application/msword', + }, + { + label: 'text/plain', + value: 'text/plain', + }, + { + label: 'application/json', + value: 'application/json', + }, + ], + }, + }), + }, + async run(context) { + const { bucket } = context.auth; + const { file, fileName, acl, type } = context.propsValue; + + const s3 = createS3(context.auth); + + const contentType = type; + const [_, ext] = contentType.split('/'); + const extension = '.' + ext; + + const generatedName = new Date().toISOString() + Date.now() + extension; + + const finalFileName = fileName ? fileName + extension : generatedName; + + const uploadResponse = await s3.putObject({ + Bucket: bucket, + Key: finalFileName, + ACL: acl as ObjectCannedACL | undefined, + ContentType: contentType, + Body: file.data, + }); + + return { + fileName: finalFileName, + etag: uploadResponse.ETag, + }; + }, +}); diff --git a/packages/pieces/community/amazon-s3/src/lib/common.ts b/packages/pieces/community/amazon-s3/src/lib/common.ts new file mode 100644 index 0000000..d0a6d43 --- /dev/null +++ b/packages/pieces/community/amazon-s3/src/lib/common.ts @@ -0,0 +1,21 @@ +import { isNil } from '@activepieces/shared'; +import { S3 } from '@aws-sdk/client-s3'; + +export function createS3(auth: { + accessKeyId: string; + secretAccessKey: string; + region: string | undefined; + endpoint: string | undefined; +}) { + const s3 = new S3({ + credentials: { + accessKeyId: auth.accessKeyId, + secretAccessKey: auth.secretAccessKey, + }, + forcePathStyle: auth.endpoint ? true : undefined, + region: auth.region, + endpoint: + auth.endpoint === '' || isNil(auth.endpoint) ? undefined : auth.endpoint, + }); + return s3; +} diff --git a/packages/pieces/community/amazon-s3/src/lib/triggers/new-file.ts b/packages/pieces/community/amazon-s3/src/lib/triggers/new-file.ts new file mode 100644 index 0000000..c92f957 --- /dev/null +++ b/packages/pieces/community/amazon-s3/src/lib/triggers/new-file.ts @@ -0,0 +1,122 @@ +import { PiecePropValueSchema, Property, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { amazonS3Auth } from '../..'; +import { createS3 } from '../common'; +import dayjs from 'dayjs'; +import { ListObjectsV2CommandInput } from '@aws-sdk/client-s3'; +import { MarkdownVariant } from '@activepieces/shared'; + +const polling: Polling, { folderPath?: string }> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, lastFetchEpochMS, propsValue }) => { + const isTest = lastFetchEpochMS === 0; + + const s3 = createS3(auth); + + const bucketFiles = []; + + const MAX_TOTAL_FILES = 10000; + let totalFetched = 0; + + let hasMore = true; + let nextToken: string | undefined; + + do { + const params: ListObjectsV2CommandInput = { + Bucket: auth.bucket, + MaxKeys: isTest ? 10 : 1000, + ContinuationToken: nextToken, + }; + + if (propsValue.folderPath) + params.Prefix = `${ + propsValue.folderPath.endsWith('/') + ? propsValue.folderPath.slice(0, -1) + : propsValue.folderPath + }`; + + const response = await s3.listObjectsV2(params); + + const items = response.Contents ?? []; + + // Check if adding these items would exceed the 10,000 limit + if (totalFetched + items.length > MAX_TOTAL_FILES) { + const remaining = MAX_TOTAL_FILES - totalFetched; + bucketFiles.push(...items.slice(0, remaining)); + break; + } + + bucketFiles.push(...items); + totalFetched += items.length; + + if (isTest) break; + + hasMore = !!response.IsTruncated; + nextToken = response.NextContinuationToken ?? undefined; + } while (hasMore); + + return bucketFiles.map((file) => ({ + epochMilliSeconds: dayjs(file.LastModified).valueOf(), + data: file, + })); + }, +}; + +export const newFile = createTrigger({ + auth: amazonS3Auth, + name: 'new_file', + displayName: 'New or Updated File', + description: + 'Triggers when you add or update a file in your bucket. The bucket/folder you choose must not contain more than 10,000 files.', + props: { + markdown:Property.MarkDown({ + variant:MarkdownVariant.INFO, + value:'Triggers when you add or update a file in your bucket. The bucket/folder you choose must not contain more than 10,000 files.' + }), + folderPath: Property.ShortText({ + displayName: 'Folder Path', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, + test: async (context) => { + return await pollingHelper.test(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, + + sampleData: { + Key: 'myfolder/100-3.png', + LastModified: '2023-08-04T13:51:26.000Z', + ETag: '"e9f16cce12352322272525f5af65a2e"', + Size: 40239, + StorageClass: 'STANDARD', + }, +}); diff --git a/packages/pieces/community/amazon-s3/tsconfig.json b/packages/pieces/community/amazon-s3/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/amazon-s3/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/amazon-s3/tsconfig.lib.json b/packages/pieces/community/amazon-s3/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/amazon-s3/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/amazon-sns/.eslintrc.json b/packages/pieces/community/amazon-sns/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/amazon-sns/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/amazon-sns/README.md b/packages/pieces/community/amazon-sns/README.md new file mode 100644 index 0000000..b9c4c48 --- /dev/null +++ b/packages/pieces/community/amazon-sns/README.md @@ -0,0 +1,7 @@ +# pieces-amazon-sns + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-amazon-sns` to build the library. diff --git a/packages/pieces/community/amazon-sns/package-lock.json b/packages/pieces/community/amazon-sns/package-lock.json new file mode 100644 index 0000000..03dfa1a --- /dev/null +++ b/packages/pieces/community/amazon-sns/package-lock.json @@ -0,0 +1,1229 @@ +{ + "name": "@activepieces/piece-amazon-sns", + "version": "0.0.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-amazon-sns", + "version": "0.0.2", + "dependencies": { + "@aws-sdk/client-sns": "3.726.1" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sns": { + "version": "3.726.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sns/-/client-sns-3.726.1.tgz", + "integrity": "sha512-ljh1IfdyM2yMB48ac3MuDx1jvWqHgZ/Ni4fW70N6rnSYkgIaHDPKFd6N5cxiYGzeEhi/8jt80mX9jobFfG8L+w==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.726.0", + "@aws-sdk/client-sts": "3.726.1", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/credential-provider-node": "3.726.0", + "@aws-sdk/middleware-host-header": "3.723.0", + "@aws-sdk/middleware-logger": "3.723.0", + "@aws-sdk/middleware-recursion-detection": "3.723.0", + "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/region-config-resolver": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.726.0", + "@aws-sdk/util-user-agent-browser": "3.723.0", + "@aws-sdk/util-user-agent-node": "3.726.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.726.0.tgz", + "integrity": "sha512-NM5pjv2qglEc4XN3nnDqtqGsSGv1k5YTmzDo3W3pObItHmpS8grSeNfX9zSH+aVl0Q8hE4ZIgvTPNZ+GzwVlqg==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/middleware-host-header": "3.723.0", + "@aws-sdk/middleware-logger": "3.723.0", + "@aws-sdk/middleware-recursion-detection": "3.723.0", + "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/region-config-resolver": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.726.0", + "@aws-sdk/util-user-agent-browser": "3.723.0", + "@aws-sdk/util-user-agent-node": "3.726.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.726.0.tgz", + "integrity": "sha512-5JzTX9jwev7+y2Jkzjz0pd1wobB5JQfPOQF3N2DrJ5Pao0/k6uRYwE4NqB0p0HlGrMTDm7xNq7OSPPIPG575Jw==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/credential-provider-node": "3.726.0", + "@aws-sdk/middleware-host-header": "3.723.0", + "@aws-sdk/middleware-logger": "3.723.0", + "@aws-sdk/middleware-recursion-detection": "3.723.0", + "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/region-config-resolver": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.726.0", + "@aws-sdk/util-user-agent-browser": "3.723.0", + "@aws-sdk/util-user-agent-node": "3.726.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.726.0" + } + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.726.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.726.1.tgz", + "integrity": "sha512-qh9Q9Vu1hrM/wMBOBIaskwnE4GTFaZu26Q6WHwyWNfj7J8a40vBxpW16c2vYXHLBtwRKM1be8uRLkmDwghpiNw==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.726.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/credential-provider-node": "3.726.0", + "@aws-sdk/middleware-host-header": "3.723.0", + "@aws-sdk/middleware-logger": "3.723.0", + "@aws-sdk/middleware-recursion-detection": "3.723.0", + "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/region-config-resolver": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.726.0", + "@aws-sdk/util-user-agent-browser": "3.723.0", + "@aws-sdk/util-user-agent-node": "3.726.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.723.0.tgz", + "integrity": "sha512-UraXNmvqj3vScSsTkjMwQkhei30BhXlW5WxX6JacMKVtl95c7z0qOXquTWeTalYkFfulfdirUhvSZrl+hcyqTw==", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/core": "^3.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/signature-v4": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/util-middleware": "^4.0.0", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.723.0.tgz", + "integrity": "sha512-OuH2yULYUHTVDUotBoP/9AEUIJPn81GQ/YBtZLoo2QyezRJ2QiO/1epVtbJlhNZRwXrToLEDmQGA2QfC8c7pbA==", + "dependencies": { + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.723.0.tgz", + "integrity": "sha512-DTsKC6xo/kz/ZSs1IcdbQMTgiYbpGTGEd83kngFc1bzmw7AmK92DBZKNZpumf8R/UfSpTcj9zzUUmrWz1kD0eQ==", + "dependencies": { + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/util-stream": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.726.0.tgz", + "integrity": "sha512-seTtcKL2+gZX6yK1QRPr5mDJIBOatrpoyrO8D5b8plYtV/PDbDW3mtDJSWFHet29G61ZmlNElyXRqQCXn9WX+A==", + "dependencies": { + "@aws-sdk/core": "3.723.0", + "@aws-sdk/credential-provider-env": "3.723.0", + "@aws-sdk/credential-provider-http": "3.723.0", + "@aws-sdk/credential-provider-process": "3.723.0", + "@aws-sdk/credential-provider-sso": "3.726.0", + "@aws-sdk/credential-provider-web-identity": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/credential-provider-imds": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.726.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.726.0.tgz", + "integrity": "sha512-jjsewBcw/uLi24x8JbnuDjJad4VA9ROCE94uVRbEnGmUEsds75FWOKp3fWZLQlmjLtzsIbJOZLALkZP86liPaw==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.723.0", + "@aws-sdk/credential-provider-http": "3.723.0", + "@aws-sdk/credential-provider-ini": "3.726.0", + "@aws-sdk/credential-provider-process": "3.723.0", + "@aws-sdk/credential-provider-sso": "3.726.0", + "@aws-sdk/credential-provider-web-identity": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/credential-provider-imds": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.723.0.tgz", + "integrity": "sha512-fgupvUjz1+jeoCBA7GMv0L6xEk92IN6VdF4YcFhsgRHlHvNgm7ayaoKQg7pz2JAAhG/3jPX6fp0ASNy+xOhmPA==", + "dependencies": { + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.726.0.tgz", + "integrity": "sha512-WxkN76WeB08j2yw7jUH9yCMPxmT9eBFd9ZA/aACG7yzOIlsz7gvG3P2FQ0tVg25GHM0E4PdU3p/ByTOawzcOAg==", + "dependencies": { + "@aws-sdk/client-sso": "3.726.0", + "@aws-sdk/core": "3.723.0", + "@aws-sdk/token-providers": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.723.0.tgz", + "integrity": "sha512-tl7pojbFbr3qLcOE6xWaNCf1zEfZrIdSJtOPeSXfV/thFMMAvIjgf3YN6Zo1a6cxGee8zrV/C8PgOH33n+Ev/A==", + "dependencies": { + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.723.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.723.0.tgz", + "integrity": "sha512-LLVzLvk299pd7v4jN9yOSaWDZDfH0SnBPb6q+FDPaOCMGBY8kuwQso7e/ozIKSmZHRMGO3IZrflasHM+rI+2YQ==", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.723.0.tgz", + "integrity": "sha512-chASQfDG5NJ8s5smydOEnNK7N0gDMyuPbx7dYYcm1t/PKtnVfvWF+DHCTrRC2Ej76gLJVCVizlAJKM8v8Kg3cg==", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.723.0.tgz", + "integrity": "sha512-7usZMtoynT9/jxL/rkuDOFQ0C2mhXl4yCm67Rg7GNTstl67u7w5WN1aIRImMeztaKlw8ExjoTyo6WTs1Kceh7A==", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.726.0.tgz", + "integrity": "sha512-hZvzuE5S0JmFie1r68K2wQvJbzyxJFdzltj9skgnnwdvLe8F/tz7MqLkm28uV0m4jeHk0LpiBo6eZaPkQiwsZQ==", + "dependencies": { + "@aws-sdk/core": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.726.0", + "@smithy/core": "^3.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.723.0.tgz", + "integrity": "sha512-tGF/Cvch3uQjZIj34LY2mg8M2Dr4kYG8VU8Yd0dFnB1ybOEOveIK/9ypUo9ycZpB9oO6q01KRe5ijBaxNueUQg==", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.723.0.tgz", + "integrity": "sha512-hniWi1x4JHVwKElANh9afKIMUhAutHVBRD8zo6usr0PAoj+Waf220+1ULS74GXtLXAPCiNXl5Og+PHA7xT8ElQ==", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.723.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.723.0.tgz", + "integrity": "sha512-LmK3kwiMZG1y5g3LGihT9mNkeNOmwEyPk6HGcJqh0wOSV4QpWoKu2epyKE4MLQNUUlz2kOVbVbOrwmI6ZcteuA==", + "dependencies": { + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.726.0.tgz", + "integrity": "sha512-sLd30ASsPMoPn3XBK50oe/bkpJ4N8Bpb7SbhoxcY3Lk+fSASaWxbbXE81nbvCnkxrZCvkPOiDHzJCp1E2im71A==", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/types": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.804.0.tgz", + "integrity": "sha512-zVoRfpmBVPodYlnMjgVjfGoEZagyRF5IPn3Uo6ZvOZp24chnW/FRstH7ESDHDDRga4z3V+ElUQHKpFDXWyBW5A==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.723.0.tgz", + "integrity": "sha512-Wh9I6j2jLhNFq6fmXydIpqD1WyQLyTfSxjW9B+PXSnPyk3jtQW8AKQur7p97rO8LAUzVI0bv8kb3ZzDEVbquIg==", + "dependencies": { + "@aws-sdk/types": "3.723.0", + "@smithy/types": "^4.0.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.726.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.726.0.tgz", + "integrity": "sha512-iEj6KX9o6IQf23oziorveRqyzyclWai95oZHDJtYav3fvLJKStwSjygO4xSF7ycHcTYeCHSLO1FFOHgGVs4Viw==", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.726.0", + "@aws-sdk/types": "3.723.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.2.tgz", + "integrity": "sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.2.tgz", + "integrity": "sha512-7r6mZGwb5LmLJ+zPtkLoznf2EtwEuSWdtid10pjGl/7HefCE4mueOkrfki8JCUm99W6UfP47/r3tbxx9CfBN5A==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.1", + "@smithy/types": "^4.2.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.3.1.tgz", + "integrity": "sha512-W7AppgQD3fP1aBmo8wWo0id5zeR2/aYRy067vZsDVaa6v/mdhkg6DxXwEVuSPjZl+ZnvWAQbUMCd5ckw38+tHQ==", + "dependencies": { + "@smithy/middleware-serde": "^4.0.3", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-stream": "^4.2.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.4.tgz", + "integrity": "sha512-jN6M6zaGVyB8FmNGG+xOPQB4N89M1x97MMdMnm1ESjljLS3Qju/IegQizKujaNcy2vXAvrz0en8bobe6E55FEA==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.1", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.2.tgz", + "integrity": "sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ==", + "dependencies": { + "@smithy/protocol-http": "^5.1.0", + "@smithy/querystring-builder": "^4.0.2", + "@smithy/types": "^4.2.0", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.2.tgz", + "integrity": "sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg==", + "dependencies": { + "@smithy/types": "^4.2.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.2.tgz", + "integrity": "sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.2.tgz", + "integrity": "sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A==", + "dependencies": { + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.4.tgz", + "integrity": "sha512-qWyYvszzvDjT2AxRvEpNhnMTo8QX9MCAtuSA//kYbXewb+2mEGQCk1UL4dNIrKLcF5KT11dOJtxFYT0kzajq5g==", + "dependencies": { + "@smithy/core": "^3.3.1", + "@smithy/middleware-serde": "^4.0.3", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-middleware": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.5.tgz", + "integrity": "sha512-eQguCTA2TRGyg4P7gDuhRjL2HtN5OKJXysq3Ufj0EppZe4XBmSyKIvVX9ws9KkD3lkJskw1tfE96wMFsiUShaw==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.1", + "@smithy/protocol-http": "^5.1.0", + "@smithy/service-error-classification": "^4.0.3", + "@smithy/smithy-client": "^4.2.4", + "@smithy/types": "^4.2.0", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.3", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.3.tgz", + "integrity": "sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.2.tgz", + "integrity": "sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.1.tgz", + "integrity": "sha512-1slS5jf5icHETwl5hxEVBj+mh6B+LbVW4yRINsGtUKH+nxM5Pw2H59+qf+JqYFCHp9jssG4vX81f5WKnjMN3Vw==", + "dependencies": { + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.4.tgz", + "integrity": "sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g==", + "dependencies": { + "@smithy/abort-controller": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/querystring-builder": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.2.tgz", + "integrity": "sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.0.tgz", + "integrity": "sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.2.tgz", + "integrity": "sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==", + "dependencies": { + "@smithy/types": "^4.2.0", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.2.tgz", + "integrity": "sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.3.tgz", + "integrity": "sha512-FTbcajmltovWMjj3tksDQdD23b2w6gH+A0DYA1Yz3iSpjDj8fmkwy62UnXcWMy4d5YoMoSyLFHMfkEVEzbiN8Q==", + "dependencies": { + "@smithy/types": "^4.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.2.tgz", + "integrity": "sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.0.tgz", + "integrity": "sha512-4t5WX60sL3zGJF/CtZsUQTs3UrZEDO2P7pEaElrekbLqkWPYkgqNW1oeiNYC6xXifBnT9dVBOnNQRvOE9riU9w==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.2.4.tgz", + "integrity": "sha512-oolSEpr/ABUtVmFMdNgi6sSXsK4csV9n4XM9yXgvDJGRa32tQDUdv9s+ztFZKccay1AiTWLSGsyDj2xy1gsv7Q==", + "dependencies": { + "@smithy/core": "^3.3.1", + "@smithy/middleware-endpoint": "^4.1.4", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "@smithy/util-stream": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", + "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.2.tgz", + "integrity": "sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ==", + "dependencies": { + "@smithy/querystring-parser": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.12.tgz", + "integrity": "sha512-0vPKiC+rXWMq397tsa/RFcO/kJ1UsibgNCXScMsRwzm9WMT4QjGf43zVPWZ5hPLu3z/1XddiZFIlKcu2j/yUuQ==", + "dependencies": { + "@smithy/property-provider": "^4.0.2", + "@smithy/smithy-client": "^4.2.4", + "@smithy/types": "^4.2.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.12.tgz", + "integrity": "sha512-zCx9noceM3Pw2jvcJ3w3RbvKnPe3lCo6txH9ksZj6CeRZPkvRZPLXmKVSOvDr9QQP3VRq/WnBLd+LTZAL7+0IQ==", + "dependencies": { + "@smithy/config-resolver": "^4.1.2", + "@smithy/credential-provider-imds": "^4.0.4", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/property-provider": "^4.0.2", + "@smithy/smithy-client": "^4.2.4", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.4.tgz", + "integrity": "sha512-VfFATC1bmZLV2858B/O1NpMcL32wYo8DPPhHxYxDCodDl3f3mSZ5oJheW1IF91A0EeAADz2WsakM/hGGPGNKLg==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.1", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.2.tgz", + "integrity": "sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.3.tgz", + "integrity": "sha512-DPuYjZQDXmKr/sNvy9Spu8R/ESa2e22wXZzSAY6NkjOLj6spbIje/Aq8rT97iUMdDj0qHMRIe+bTxvlU74d9Ng==", + "dependencies": { + "@smithy/service-error-classification": "^4.0.3", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.0.tgz", + "integrity": "sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ==", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/types": "^4.2.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + } + } +} diff --git a/packages/pieces/community/amazon-sns/package.json b/packages/pieces/community/amazon-sns/package.json new file mode 100644 index 0000000..ce63aa1 --- /dev/null +++ b/packages/pieces/community/amazon-sns/package.json @@ -0,0 +1,7 @@ +{ + "name": "@activepieces/piece-amazon-sns", + "version": "0.0.2", + "dependencies": { + "@aws-sdk/client-sns": "3.726.1" + } +} diff --git a/packages/pieces/community/amazon-sns/project.json b/packages/pieces/community/amazon-sns/project.json new file mode 100644 index 0000000..600dac7 --- /dev/null +++ b/packages/pieces/community/amazon-sns/project.json @@ -0,0 +1,76 @@ +{ + "name": "pieces-amazon-sns", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/amazon-sns/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/amazon-sns", + "tsConfig": "packages/pieces/community/amazon-sns/tsconfig.lib.json", + "packageJson": "packages/pieces/community/amazon-sns/package.json", + "main": "packages/pieces/community/amazon-sns/src/index.ts", + "assets": [ + "packages/pieces/community/amazon-sns/*.md", + { + "input": "packages/pieces/community/amazon-sns/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + }, + "dependsOn": ["prebuild", "^build"] + }, + "prebuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/pieces/community/amazon-sns", + "command": "npm ci" + } + }, + "build-with-deps": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "nx run pieces-amazon-sns:prebuild", + "nx run pieces-amazon-sns:build", + "nx run pieces-amazon-sns:postbuild" + ], + "parallel": false + } + }, + "postbuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "dist/packages/pieces/community/amazon-sns", + "command": "npm install" + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/amazon-sns/src/index.ts b/packages/pieces/community/amazon-sns/src/index.ts new file mode 100644 index 0000000..ff21e6c --- /dev/null +++ b/packages/pieces/community/amazon-sns/src/index.ts @@ -0,0 +1,182 @@ +import { createPiece, PieceAuth, Property } from "@activepieces/pieces-framework"; +import { createSNS } from './lib/common'; +import { ListTopicsCommand } from '@aws-sdk/client-sns'; +import { sendMessageAction } from './lib/actions/send-message'; + +const description = ` +This piece allows you to send messages to Amazon SNS topics. + +Refer to [this](https://docs.aws.amazon.com/general/latest/gr/sns.html) for **Region**. Leave the **Endpoint** blank. +`; + +export const amazonSnsAuth = PieceAuth.CustomAuth({ + description: description, + props: { + accessKeyId: Property.ShortText({ + displayName: 'Access Key ID', + required: true, + }), + secretAccessKey: PieceAuth.SecretText({ + displayName: 'Secret Access Key', + required: true, + }), + region: Property.StaticDropdown({ + displayName: 'Region', + options: { + options: [ + { + label: 'Default', + value: 'us-east-1', + }, + { + label: 'US East (N. Virginia) [us-east-1]', + value: 'us-east-1', + }, + { + label: 'US East (Ohio) [us-east-2]', + value: 'us-east-2', + }, + { + label: 'US West (N. California) [us-west-1]', + value: 'us-west-1', + }, + { + label: 'US West (Oregon) [us-west-2]', + value: 'us-west-2', + }, + { + label: 'Africa (Cape Town) [af-south-1]', + value: 'af-south-1', + }, + { + label: 'Asia Pacific (Hong Kong) [ap-east-1]', + value: 'ap-east-1', + }, + { + label: 'Asia Pacific (Mumbai) [ap-south-1]', + value: 'ap-south-1', + }, + { + label: 'Asia Pacific (Osaka-Local) [ap-northeast-3]', + value: 'ap-northeast-3', + }, + { + label: 'Asia Pacific (Seoul) [ap-northeast-2]', + value: 'ap-northeast-2', + }, + { + label: 'Asia Pacific (Singapore) [ap-southeast-1]', + value: 'ap-southeast-1', + }, + { + label: 'Asia Pacific (Sydney) [ap-southeast-2]', + value: 'ap-southeast-2', + }, + { + label: 'Asia Pacific (Tokyo) [ap-northeast-1]', + value: 'ap-northeast-1', + }, + { + label: 'Canada (Central) [ca-central-1]', + value: 'ca-central-1', + }, + { + label: 'Europe (Frankfurt) [eu-central-1]', + value: 'eu-central-1', + }, + { + label: 'Europe (Ireland) [eu-west-1]', + value: 'eu-west-1', + }, + { + label: 'Europe (London) [eu-west-2]', + value: 'eu-west-2', + }, + { + label: 'Europe (Milan) [eu-south-1]', + value: 'eu-south-1', + }, + { + label: 'Europe (Paris) [eu-west-3]', + value: 'eu-west-3', + }, + { + label: 'Europe (Stockholm) [eu-north-1]', + value: 'eu-north-1', + }, + { + label: 'Middle East (Bahrain) [me-south-1]', + value: 'me-south-1', + }, + { + label: 'South America (São Paulo) [sa-east-1]', + value: 'sa-east-1', + }, + { + label: 'Europe (Spain) [eu-south-2]', + value: 'eu-south-2', + }, + { + label: 'Asia Pacific (Hyderabad) [ap-south-2]', + value: 'ap-south-2', + }, + { + label: 'Asia Pacific (Jakarta) [ap-southeast-3]', + value: 'ap-southeast-3', + }, + { + label: 'Asia Pacific (Melbourne) [ap-southeast-4]', + value: 'ap-southeast-4', + }, + { + label: 'China (Beijing) [cn-north-1]', + value: 'cn-north-1', + }, + { + label: 'China (Ningxia) [cn-northwest-1]', + value: 'cn-northwest-1', + }, + { + label: 'Europe (Zurich) [eu-central-2]', + value: 'eu-central-2', + }, + { + label: 'Middle East (UAE) [me-central-1]', + value: 'me-central-1', + }, + ], + }, + required: true, + }), + endpoint: Property.ShortText({ + displayName: 'Endpoint', + required: false, + }), + }, + validate: async ({ auth }) => { + const sns = createSNS(auth); + try { + const command = new ListTopicsCommand({}); + await sns.send(command); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: (e as Error)?.message, + }; + } + }, + required: true, +}); + +export const amazonSns = createPiece({ + displayName: "Amazon SNS", + auth: amazonSnsAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: "https://cdn.activepieces.com/pieces/amazon-sns.png", + authors: ["coat"], + actions: [sendMessageAction], + triggers: [], +}); diff --git a/packages/pieces/community/amazon-sns/src/lib/actions/send-message.ts b/packages/pieces/community/amazon-sns/src/lib/actions/send-message.ts new file mode 100644 index 0000000..bb6e219 --- /dev/null +++ b/packages/pieces/community/amazon-sns/src/lib/actions/send-message.ts @@ -0,0 +1,56 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { createSNS } from '../common'; +import { amazonSnsAuth } from '../..'; +import { ListTopicsCommand, PublishCommand } from "@aws-sdk/client-sns"; + +export const sendMessageAction = createAction({ + auth: amazonSnsAuth, + name: 'send-message', + displayName: 'Send Message', + description: 'Sends a message to an Amazon SNS topic.', + props: { + topic: Property.Dropdown({ + displayName: 'Topic', + description: 'Select a topic', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + } + const sns = await createSNS((auth as { accessKeyId: string, secretAccessKey: string, region: string, endpoint: string })); + const topics = await sns.send(new ListTopicsCommand({})); + if (topics.Topics) { + return { + options: topics.Topics.map((topic) =>( + { + label: topic.TopicArn?.split(':').pop() as string, + value: topic.TopicArn as string, + } + )), + }; + } else { + return { + options: [], + placeholder: 'No topics found', + }; + } + }, + }), + message: Property.LongText({ + displayName: 'Message', + required: true, + }), + }, + async run(context) { + const { topic, message } = context.propsValue; + const sns = createSNS(context.auth); + const response = await sns.send(new PublishCommand({ TopicArn: topic, Message: message })); + + return response; + }, +}); diff --git a/packages/pieces/community/amazon-sns/src/lib/common.ts b/packages/pieces/community/amazon-sns/src/lib/common.ts new file mode 100644 index 0000000..b3e79e4 --- /dev/null +++ b/packages/pieces/community/amazon-sns/src/lib/common.ts @@ -0,0 +1,20 @@ +import { isNil } from '@activepieces/shared'; +import { SNSClient } from '@aws-sdk/client-sns'; + +export function createSNS(auth: { + accessKeyId: string; + secretAccessKey: string; + region: string | undefined; + endpoint: string | undefined; +}) { + const sns = new SNSClient({ + credentials: { + accessKeyId: auth.accessKeyId, + secretAccessKey: auth.secretAccessKey, + }, + region: auth.region, + endpoint: + auth.endpoint === '' || isNil(auth.endpoint) ? undefined : auth.endpoint, + }); + return sns; +} diff --git a/packages/pieces/community/amazon-sns/tsconfig.json b/packages/pieces/community/amazon-sns/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/amazon-sns/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/amazon-sns/tsconfig.lib.json b/packages/pieces/community/amazon-sns/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/amazon-sns/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/amazon-sqs/.eslintrc.json b/packages/pieces/community/amazon-sqs/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/amazon-sqs/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/amazon-sqs/README.md b/packages/pieces/community/amazon-sqs/README.md new file mode 100644 index 0000000..6809a83 --- /dev/null +++ b/packages/pieces/community/amazon-sqs/README.md @@ -0,0 +1,7 @@ +# pieces-amazon-sqs + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-amazon-sqs` to build the library. diff --git a/packages/pieces/community/amazon-sqs/package-lock.json b/packages/pieces/community/amazon-sqs/package-lock.json new file mode 100644 index 0000000..85926d4 --- /dev/null +++ b/packages/pieces/community/amazon-sqs/package-lock.json @@ -0,0 +1,1198 @@ +{ + "name": "@activepieces/piece-amazon-sqs", + "version": "0.0.3", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-amazon-sqs", + "version": "0.0.3", + "dependencies": { + "@aws-sdk/client-sqs": "^3.806.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs": { + "version": "3.806.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sqs/-/client-sqs-3.806.0.tgz", + "integrity": "sha512-JvoLtAa+mS8N90smkAg7lYVi/tdV1JbGrPoRJYLOGpA6N2mn7Atv580dWTJQ3m4lhmTgBJjVnMlo3AN/UJ+VIA==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.806.0", + "@aws-sdk/credential-provider-node": "3.806.0", + "@aws-sdk/middleware-host-header": "3.804.0", + "@aws-sdk/middleware-logger": "3.804.0", + "@aws-sdk/middleware-recursion-detection": "3.804.0", + "@aws-sdk/middleware-sdk-sqs": "3.806.0", + "@aws-sdk/middleware-user-agent": "3.806.0", + "@aws-sdk/region-config-resolver": "3.806.0", + "@aws-sdk/types": "3.804.0", + "@aws-sdk/util-endpoints": "3.806.0", + "@aws-sdk/util-user-agent-browser": "3.804.0", + "@aws-sdk/util-user-agent-node": "3.806.0", + "@smithy/config-resolver": "^4.1.1", + "@smithy/core": "^3.3.1", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/hash-node": "^4.0.2", + "@smithy/invalid-dependency": "^4.0.2", + "@smithy/md5-js": "^4.0.2", + "@smithy/middleware-content-length": "^4.0.2", + "@smithy/middleware-endpoint": "^4.1.3", + "@smithy/middleware-retry": "^4.1.4", + "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/node-config-provider": "^4.1.0", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.3", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.11", + "@smithy/util-defaults-mode-node": "^4.0.11", + "@smithy/util-endpoints": "^3.0.3", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.3", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.806.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.806.0.tgz", + "integrity": "sha512-X0p/9/u9e6b22rlQqKucdtjdqmjSNB4c/8zDEoD5MvgYAAbMF9HNE0ST2xaA/WsJ7uE0jFfhPY2/00pslL1DqQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.806.0", + "@aws-sdk/middleware-host-header": "3.804.0", + "@aws-sdk/middleware-logger": "3.804.0", + "@aws-sdk/middleware-recursion-detection": "3.804.0", + "@aws-sdk/middleware-user-agent": "3.806.0", + "@aws-sdk/region-config-resolver": "3.806.0", + "@aws-sdk/types": "3.804.0", + "@aws-sdk/util-endpoints": "3.806.0", + "@aws-sdk/util-user-agent-browser": "3.804.0", + "@aws-sdk/util-user-agent-node": "3.806.0", + "@smithy/config-resolver": "^4.1.1", + "@smithy/core": "^3.3.1", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/hash-node": "^4.0.2", + "@smithy/invalid-dependency": "^4.0.2", + "@smithy/middleware-content-length": "^4.0.2", + "@smithy/middleware-endpoint": "^4.1.3", + "@smithy/middleware-retry": "^4.1.4", + "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/node-config-provider": "^4.1.0", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.3", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.11", + "@smithy/util-defaults-mode-node": "^4.0.11", + "@smithy/util-endpoints": "^3.0.3", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.3", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.806.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.806.0.tgz", + "integrity": "sha512-HJRINPncdjPK0iL3f6cBpqCMaxVwq2oDbRCzOx04tsLZ0tNgRACBfT3d/zNVRvMt6fnOVKXoN1LAtQaw50pjEA==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/core": "^3.3.1", + "@smithy/node-config-provider": "^4.1.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/signature-v4": "^5.1.0", + "@smithy/smithy-client": "^4.2.3", + "@smithy/types": "^4.2.0", + "@smithy/util-middleware": "^4.0.2", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.806.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.806.0.tgz", + "integrity": "sha512-nbPwmZn0kt6Q1XI2FaJWP6AhF9tro4cO5HlmZQx8NU+B0H1y9WMo659Q5zLLY46BXgoQVIJEsPSZpcZk27O4aw==", + "dependencies": { + "@aws-sdk/core": "3.806.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.806.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.806.0.tgz", + "integrity": "sha512-e/gB2iJQQ4ZpecOVpEFhEvjGwuTqNCzhVaVsFYVc49FPfR1seuN7qBGYe1MO7mouGDQFInzJgcNup0DnYUrLiw==", + "dependencies": { + "@aws-sdk/core": "3.806.0", + "@aws-sdk/types": "3.804.0", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/property-provider": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.3", + "@smithy/types": "^4.2.0", + "@smithy/util-stream": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.806.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.806.0.tgz", + "integrity": "sha512-FogfbuYSEZgFxbNy0QcsBZHHe5mSv5HV3+JyB5n0kCyjOISCVCZD7gwxKdXjt8O1hXq5k5SOdQvydGULlB6rew==", + "dependencies": { + "@aws-sdk/core": "3.806.0", + "@aws-sdk/credential-provider-env": "3.806.0", + "@aws-sdk/credential-provider-http": "3.806.0", + "@aws-sdk/credential-provider-process": "3.806.0", + "@aws-sdk/credential-provider-sso": "3.806.0", + "@aws-sdk/credential-provider-web-identity": "3.806.0", + "@aws-sdk/nested-clients": "3.806.0", + "@aws-sdk/types": "3.804.0", + "@smithy/credential-provider-imds": "^4.0.2", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.806.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.806.0.tgz", + "integrity": "sha512-fZX8xP2Kf0k70kDTog/87fh/M+CV0E2yujSw1cUBJhDSwDX3RlUahiJk7TpB/KGw6hEFESMd6+7kq3UzYuw3rg==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.806.0", + "@aws-sdk/credential-provider-http": "3.806.0", + "@aws-sdk/credential-provider-ini": "3.806.0", + "@aws-sdk/credential-provider-process": "3.806.0", + "@aws-sdk/credential-provider-sso": "3.806.0", + "@aws-sdk/credential-provider-web-identity": "3.806.0", + "@aws-sdk/types": "3.804.0", + "@smithy/credential-provider-imds": "^4.0.2", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.806.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.806.0.tgz", + "integrity": "sha512-8Y8GYEw/1e5IZRDQL02H6nsTDcRWid/afRMeWg+93oLQmbHcTtdm48tjis+7Xwqy+XazhMDmkbUht11QPTDJcQ==", + "dependencies": { + "@aws-sdk/core": "3.806.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.806.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.806.0.tgz", + "integrity": "sha512-hT9OBwCxWMPBydNhXm2gdNNzx5AJNheS9RglwDDvXWzQ9qDuRztjuMBilMSUMb0HF9K4IqQjYzGqczMuktz4qQ==", + "dependencies": { + "@aws-sdk/client-sso": "3.806.0", + "@aws-sdk/core": "3.806.0", + "@aws-sdk/token-providers": "3.806.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.806.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.806.0.tgz", + "integrity": "sha512-XxaSY9Zd3D4ClUGENYMvi52ac5FuJPPAsvRtEfyrSdEpf6QufbMpnexWBZMYRF31h/VutgqtJwosGgNytpxMEg==", + "dependencies": { + "@aws-sdk/core": "3.806.0", + "@aws-sdk/nested-clients": "3.806.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.804.0.tgz", + "integrity": "sha512-bum1hLVBrn2lJCi423Z2fMUYtsbkGI2s4N+2RI2WSjvbaVyMSv/WcejIrjkqiiMR+2Y7m5exgoKeg4/TODLDPQ==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.804.0.tgz", + "integrity": "sha512-w/qLwL3iq0KOPQNat0Kb7sKndl9BtceigINwBU7SpkYWX9L/Lem6f8NPEKrC9Tl4wDBht3Yztub4oRTy/horJA==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.804.0.tgz", + "integrity": "sha512-zqHOrvLRdsUdN/ehYfZ9Tf8svhbiLLz5VaWUz22YndFv6m9qaAcijkpAOlKexsv3nLBMJdSdJ6GUTAeIy3BZzw==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-sqs": { + "version": "3.806.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.806.0.tgz", + "integrity": "sha512-UHZKudmpl0nquq4iSSMKtypM3RFsybXiagd0mYmBpjG1Jw2oS/7NC4VFmxSnUC3jg8yXY4N9qu658Z4u9OMBDg==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/smithy-client": "^4.2.3", + "@smithy/types": "^4.2.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.806.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.806.0.tgz", + "integrity": "sha512-XoIromVffgXnc+/mjlR2EVzQVIei3bPVtafIZNsHuEmUvIWJXiWsa2eJpt3BUqa0HF9YPknK7ommNEhqRb8ucg==", + "dependencies": { + "@aws-sdk/core": "3.806.0", + "@aws-sdk/types": "3.804.0", + "@aws-sdk/util-endpoints": "3.806.0", + "@smithy/core": "^3.3.1", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.806.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.806.0.tgz", + "integrity": "sha512-ua2gzpfQ9MF8Rny+tOAivowOWWvqEusez2rdcQK8jdBjA1ANd/0xzToSZjZh0ziN8Kl8jOhNnHbQJ0v6dT6+hg==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.806.0", + "@aws-sdk/middleware-host-header": "3.804.0", + "@aws-sdk/middleware-logger": "3.804.0", + "@aws-sdk/middleware-recursion-detection": "3.804.0", + "@aws-sdk/middleware-user-agent": "3.806.0", + "@aws-sdk/region-config-resolver": "3.806.0", + "@aws-sdk/types": "3.804.0", + "@aws-sdk/util-endpoints": "3.806.0", + "@aws-sdk/util-user-agent-browser": "3.804.0", + "@aws-sdk/util-user-agent-node": "3.806.0", + "@smithy/config-resolver": "^4.1.1", + "@smithy/core": "^3.3.1", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/hash-node": "^4.0.2", + "@smithy/invalid-dependency": "^4.0.2", + "@smithy/middleware-content-length": "^4.0.2", + "@smithy/middleware-endpoint": "^4.1.3", + "@smithy/middleware-retry": "^4.1.4", + "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/node-config-provider": "^4.1.0", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.3", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.11", + "@smithy/util-defaults-mode-node": "^4.0.11", + "@smithy/util-endpoints": "^3.0.3", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.3", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.806.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.806.0.tgz", + "integrity": "sha512-cuv5pX55JOlzKC/iLsB5nZ9eUyVgncim3VhhWHZA/KYPh7rLMjOEfZ+xyaE9uLJXGmzOJboFH7+YdTRdIcOgrg==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/node-config-provider": "^4.1.0", + "@smithy/types": "^4.2.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.806.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.806.0.tgz", + "integrity": "sha512-I6SxcsvV7yinJZmPgGullFHS0tsTKa7K3jEc5dmyCz8X+kZPfsWNffZmtmnCvWXPqMXWBvK6hVaxwomx79yeHA==", + "dependencies": { + "@aws-sdk/nested-clients": "3.806.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.804.0.tgz", + "integrity": "sha512-A9qnsy9zQ8G89vrPPlNG9d1d8QcKRGqJKqwyGgS0dclJpwy6d1EWgQLIolKPl6vcFpLoe6avLOLxr+h8ur5wpg==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.806.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.806.0.tgz", + "integrity": "sha512-3YRRgZ+qFuWDdm5uAbxKsr65UAil4KkrFKua9f4m7Be3v24ETiFOOqhanFUIk9/WOtvzF7oFEiDjYKDGlwV2xg==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/types": "^4.2.0", + "@smithy/util-endpoints": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.804.0.tgz", + "integrity": "sha512-zVoRfpmBVPodYlnMjgVjfGoEZagyRF5IPn3Uo6ZvOZp24chnW/FRstH7ESDHDDRga4z3V+ElUQHKpFDXWyBW5A==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.804.0.tgz", + "integrity": "sha512-KfW6T6nQHHM/vZBBdGn6fMyG/MgX5lq82TDdX4HRQRRuHKLgBWGpKXqqvBwqIaCdXwWHgDrg2VQups6GqOWW2A==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/types": "^4.2.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.806.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.806.0.tgz", + "integrity": "sha512-Az2e4/gmPZ4BpB7QRj7U76I+fctXhNcxlcgsaHnMhvt+R30nvzM2EhsyBUvsWl8+r9bnLeYt9BpvEZeq2ANDzA==", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.806.0", + "@aws-sdk/types": "3.804.0", + "@smithy/node-config-provider": "^4.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.2.tgz", + "integrity": "sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.2.tgz", + "integrity": "sha512-7r6mZGwb5LmLJ+zPtkLoznf2EtwEuSWdtid10pjGl/7HefCE4mueOkrfki8JCUm99W6UfP47/r3tbxx9CfBN5A==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.1", + "@smithy/types": "^4.2.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.3.1.tgz", + "integrity": "sha512-W7AppgQD3fP1aBmo8wWo0id5zeR2/aYRy067vZsDVaa6v/mdhkg6DxXwEVuSPjZl+ZnvWAQbUMCd5ckw38+tHQ==", + "dependencies": { + "@smithy/middleware-serde": "^4.0.3", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-stream": "^4.2.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.4.tgz", + "integrity": "sha512-jN6M6zaGVyB8FmNGG+xOPQB4N89M1x97MMdMnm1ESjljLS3Qju/IegQizKujaNcy2vXAvrz0en8bobe6E55FEA==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.1", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.2.tgz", + "integrity": "sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ==", + "dependencies": { + "@smithy/protocol-http": "^5.1.0", + "@smithy/querystring-builder": "^4.0.2", + "@smithy/types": "^4.2.0", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.2.tgz", + "integrity": "sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg==", + "dependencies": { + "@smithy/types": "^4.2.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.2.tgz", + "integrity": "sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.0.2.tgz", + "integrity": "sha512-Hc0R8EiuVunUewCse2syVgA2AfSRco3LyAv07B/zCOMa+jpXI9ll+Q21Nc6FAlYPcpNcAXqBzMhNs1CD/pP2bA==", + "dependencies": { + "@smithy/types": "^4.2.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.2.tgz", + "integrity": "sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A==", + "dependencies": { + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.4.tgz", + "integrity": "sha512-qWyYvszzvDjT2AxRvEpNhnMTo8QX9MCAtuSA//kYbXewb+2mEGQCk1UL4dNIrKLcF5KT11dOJtxFYT0kzajq5g==", + "dependencies": { + "@smithy/core": "^3.3.1", + "@smithy/middleware-serde": "^4.0.3", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-middleware": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.5.tgz", + "integrity": "sha512-eQguCTA2TRGyg4P7gDuhRjL2HtN5OKJXysq3Ufj0EppZe4XBmSyKIvVX9ws9KkD3lkJskw1tfE96wMFsiUShaw==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.1", + "@smithy/protocol-http": "^5.1.0", + "@smithy/service-error-classification": "^4.0.3", + "@smithy/smithy-client": "^4.2.4", + "@smithy/types": "^4.2.0", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.3", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.3.tgz", + "integrity": "sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.2.tgz", + "integrity": "sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.1.tgz", + "integrity": "sha512-1slS5jf5icHETwl5hxEVBj+mh6B+LbVW4yRINsGtUKH+nxM5Pw2H59+qf+JqYFCHp9jssG4vX81f5WKnjMN3Vw==", + "dependencies": { + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.4.tgz", + "integrity": "sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g==", + "dependencies": { + "@smithy/abort-controller": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/querystring-builder": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.2.tgz", + "integrity": "sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.0.tgz", + "integrity": "sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.2.tgz", + "integrity": "sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==", + "dependencies": { + "@smithy/types": "^4.2.0", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.2.tgz", + "integrity": "sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.3.tgz", + "integrity": "sha512-FTbcajmltovWMjj3tksDQdD23b2w6gH+A0DYA1Yz3iSpjDj8fmkwy62UnXcWMy4d5YoMoSyLFHMfkEVEzbiN8Q==", + "dependencies": { + "@smithy/types": "^4.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.2.tgz", + "integrity": "sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.0.tgz", + "integrity": "sha512-4t5WX60sL3zGJF/CtZsUQTs3UrZEDO2P7pEaElrekbLqkWPYkgqNW1oeiNYC6xXifBnT9dVBOnNQRvOE9riU9w==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.2.4.tgz", + "integrity": "sha512-oolSEpr/ABUtVmFMdNgi6sSXsK4csV9n4XM9yXgvDJGRa32tQDUdv9s+ztFZKccay1AiTWLSGsyDj2xy1gsv7Q==", + "dependencies": { + "@smithy/core": "^3.3.1", + "@smithy/middleware-endpoint": "^4.1.4", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "@smithy/util-stream": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", + "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.2.tgz", + "integrity": "sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ==", + "dependencies": { + "@smithy/querystring-parser": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.12.tgz", + "integrity": "sha512-0vPKiC+rXWMq397tsa/RFcO/kJ1UsibgNCXScMsRwzm9WMT4QjGf43zVPWZ5hPLu3z/1XddiZFIlKcu2j/yUuQ==", + "dependencies": { + "@smithy/property-provider": "^4.0.2", + "@smithy/smithy-client": "^4.2.4", + "@smithy/types": "^4.2.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.12.tgz", + "integrity": "sha512-zCx9noceM3Pw2jvcJ3w3RbvKnPe3lCo6txH9ksZj6CeRZPkvRZPLXmKVSOvDr9QQP3VRq/WnBLd+LTZAL7+0IQ==", + "dependencies": { + "@smithy/config-resolver": "^4.1.2", + "@smithy/credential-provider-imds": "^4.0.4", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/property-provider": "^4.0.2", + "@smithy/smithy-client": "^4.2.4", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.4.tgz", + "integrity": "sha512-VfFATC1bmZLV2858B/O1NpMcL32wYo8DPPhHxYxDCodDl3f3mSZ5oJheW1IF91A0EeAADz2WsakM/hGGPGNKLg==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.1", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.2.tgz", + "integrity": "sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.3.tgz", + "integrity": "sha512-DPuYjZQDXmKr/sNvy9Spu8R/ESa2e22wXZzSAY6NkjOLj6spbIje/Aq8rT97iUMdDj0qHMRIe+bTxvlU74d9Ng==", + "dependencies": { + "@smithy/service-error-classification": "^4.0.3", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.0.tgz", + "integrity": "sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ==", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/types": "^4.2.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + } + } +} diff --git a/packages/pieces/community/amazon-sqs/package.json b/packages/pieces/community/amazon-sqs/package.json new file mode 100644 index 0000000..0b7987a --- /dev/null +++ b/packages/pieces/community/amazon-sqs/package.json @@ -0,0 +1,7 @@ +{ + "name": "@activepieces/piece-amazon-sqs", + "version": "0.0.3", + "dependencies": { + "@aws-sdk/client-sqs": "^3.806.0" + } +} diff --git a/packages/pieces/community/amazon-sqs/project.json b/packages/pieces/community/amazon-sqs/project.json new file mode 100644 index 0000000..54b74fe --- /dev/null +++ b/packages/pieces/community/amazon-sqs/project.json @@ -0,0 +1,69 @@ +{ + "name": "pieces-amazon-sqs", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/amazon-sqs/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/amazon-sqs", + "tsConfig": "packages/pieces/community/amazon-sqs/tsconfig.lib.json", + "packageJson": "packages/pieces/community/amazon-sqs/package.json", + "main": "packages/pieces/community/amazon-sqs/src/index.ts", + "assets": [ + "packages/pieces/community/amazon-sqs/*.md", + { + "input": "packages/pieces/community/amazon-sqs/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + }, + "dependsOn": ["prebuild", "^build"] + }, + "prebuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/pieces/community/amazon-sqs", + "command": "npm ci" + } + }, + "build-with-deps": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "nx run pieces-amazon-sqs:prebuild", + "nx run pieces-amazon-sqs:build", + "nx run pieces-amazon-sqs:postbuild" + ], + "parallel": false + } + }, + "postbuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "dist/packages/pieces/community/amazon-sqs", + "command": "npm install" + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-amazon-sqs {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/amazon-sqs/src/index.ts b/packages/pieces/community/amazon-sqs/src/index.ts new file mode 100644 index 0000000..34c4ad1 --- /dev/null +++ b/packages/pieces/community/amazon-sqs/src/index.ts @@ -0,0 +1,177 @@ + +import { createPiece, PieceAuth, Property } from "@activepieces/pieces-framework"; +import { ListQueuesCommand, SQS } from '@aws-sdk/client-sqs'; +import { sendMessage } from "./lib/actions/send-message"; + +export const amazonSqsAuth = PieceAuth.CustomAuth({ + props: { + accessKeyId: Property.ShortText({ + displayName: 'Access Key ID', + required: true, + }), + secretAccessKey: PieceAuth.SecretText({ + displayName: 'Secret Access Key', + required: true, + }), + region: Property.StaticDropdown({ + displayName: 'Region', + options: { + options: [ + { + label: 'Default', + value: 'us-east-1', + }, + { + label: 'US East (N. Virginia) [us-east-1]', + value: 'us-east-1', + }, + { + label: 'US East (Ohio) [us-east-2]', + value: 'us-east-2', + }, + { + label: 'US West (N. California) [us-west-1]', + value: 'us-west-1', + }, + { + label: 'US West (Oregon) [us-west-2]', + value: 'us-west-2', + }, + { + label: 'Africa (Cape Town) [af-south-1]', + value: 'af-south-1', + }, + { + label: 'Asia Pacific (Hong Kong) [ap-east-1]', + value: 'ap-east-1', + }, + { + label: 'Asia Pacific (Mumbai) [ap-south-1]', + value: 'ap-south-1', + }, + { + label: 'Asia Pacific (Osaka-Local) [ap-northeast-3]', + value: 'ap-northeast-3', + }, + { + label: 'Asia Pacific (Seoul) [ap-northeast-2]', + value: 'ap-northeast-2', + }, + { + label: 'Asia Pacific (Singapore) [ap-southeast-1]', + value: 'ap-southeast-1', + }, + { + label: 'Asia Pacific (Sydney) [ap-southeast-2]', + value: 'ap-southeast-2', + }, + { + label: 'Asia Pacific (Tokyo) [ap-northeast-1]', + value: 'ap-northeast-1', + }, + { + label: 'Canada (Central) [ca-central-1]', + value: 'ca-central-1', + }, + { + label: 'Europe (Frankfurt) [eu-central-1]', + value: 'eu-central-1', + }, + { + label: 'Europe (Ireland) [eu-west-1]', + value: 'eu-west-1', + }, + { + label: 'Europe (London) [eu-west-2]', + value: 'eu-west-2', + }, + { + label: 'Europe (Milan) [eu-south-1]', + value: 'eu-south-1', + }, + { + label: 'Europe (Paris) [eu-west-3]', + value: 'eu-west-3', + }, + { + label: 'Europe (Stockholm) [eu-north-1]', + value: 'eu-north-1', + }, + { + label: 'Middle East (Bahrain) [me-south-1]', + value: 'me-south-1', + }, + { + label: 'South America (São Paulo) [sa-east-1]', + value: 'sa-east-1', + }, + { + label: 'Europe (Spain) [eu-south-2]', + value: 'eu-south-2', + }, + { + label: 'Asia Pacific (Hyderabad) [ap-south-2]', + value: 'ap-south-2', + }, + { + label: 'Asia Pacific (Jakarta) [ap-southeast-3]', + value: 'ap-southeast-3', + }, + { + label: 'Asia Pacific (Melbourne) [ap-southeast-4]', + value: 'ap-southeast-4', + }, + { + label: 'China (Beijing) [cn-north-1]', + value: 'cn-north-1', + }, + { + label: 'China (Ningxia) [cn-northwest-1]', + value: 'cn-northwest-1', + }, + { + label: 'Europe (Zurich) [eu-central-2]', + value: 'eu-central-2', + }, + { + label: 'Middle East (UAE) [me-central-1]', + value: 'me-central-1', + }, + ], + }, + required: true, + }), + }, + validate: async ({ auth }) => { + try { + const sqs = new SQS({ + credentials: { + accessKeyId: auth.accessKeyId, + secretAccessKey: auth.secretAccessKey, + }, + region: auth.region, + }); + await sqs.send(new ListQueuesCommand({})); + return { + valid: true, + } + } catch (e) { + return { + valid: false, + error: (e as Error)?.message, + }; + } + }, + required: true, +}); + + +export const awsSqs = createPiece({ + displayName: "Amazon SQS", + auth: amazonSqsAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: "https://cdn.activepieces.com/pieces/aws-sqs.png", + authors: ["abuaboud"], + actions: [sendMessage], + triggers: [], +}); diff --git a/packages/pieces/community/amazon-sqs/src/lib/actions/send-message.ts b/packages/pieces/community/amazon-sqs/src/lib/actions/send-message.ts new file mode 100644 index 0000000..74a7d15 --- /dev/null +++ b/packages/pieces/community/amazon-sqs/src/lib/actions/send-message.ts @@ -0,0 +1,38 @@ +import { amazonSqsAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { SQS } from '@aws-sdk/client-sqs'; + +export const sendMessage = createAction({ + name: 'sendMessage', + displayName: 'Send Message', + auth: amazonSqsAuth, + description: '', + props: { + queueUrl: Property.ShortText({ + displayName: 'Queue URL', + description: 'The URL of the SQS queue', + required: true, + }), + messageBody: Property.ShortText({ + displayName: 'Message Body', + description: 'The body of the message', + required: true, + }), + }, + async run({ propsValue, auth }) { + const sqs = new SQS({ + credentials: { + accessKeyId: auth.accessKeyId, + secretAccessKey: auth.secretAccessKey, + }, + region: auth.region, + }); + const { queueUrl, messageBody } = propsValue; + + const params = { + QueueUrl: queueUrl, + MessageBody: messageBody, + }; + return sqs.sendMessage(params); + }, +}); diff --git a/packages/pieces/community/amazon-sqs/tsconfig.json b/packages/pieces/community/amazon-sqs/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/amazon-sqs/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/amazon-sqs/tsconfig.lib.json b/packages/pieces/community/amazon-sqs/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/amazon-sqs/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/aminos/.eslintrc.json b/packages/pieces/community/aminos/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/aminos/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/aminos/README.md b/packages/pieces/community/aminos/README.md new file mode 100644 index 0000000..bb844b3 --- /dev/null +++ b/packages/pieces/community/aminos/README.md @@ -0,0 +1,7 @@ +# pieces-aminos + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-aminos` to build the library. diff --git a/packages/pieces/community/aminos/package.json b/packages/pieces/community/aminos/package.json new file mode 100644 index 0000000..6b82676 --- /dev/null +++ b/packages/pieces/community/aminos/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-aminos", + "version": "0.0.1" +} diff --git a/packages/pieces/community/aminos/project.json b/packages/pieces/community/aminos/project.json new file mode 100644 index 0000000..a59191b --- /dev/null +++ b/packages/pieces/community/aminos/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-aminos", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/aminos/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/aminos", + "tsConfig": "packages/pieces/community/aminos/tsconfig.lib.json", + "packageJson": "packages/pieces/community/aminos/package.json", + "main": "packages/pieces/community/aminos/src/index.ts", + "assets": [ + "packages/pieces/community/aminos/*.md", + { + "input": "packages/pieces/community/aminos/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/aminos/src/index.ts b/packages/pieces/community/aminos/src/index.ts new file mode 100644 index 0000000..3e6138a --- /dev/null +++ b/packages/pieces/community/aminos/src/index.ts @@ -0,0 +1,48 @@ + import { + PieceAuth, + Property, + createPiece, + } from '@activepieces/pieces-framework'; + import { PieceCategory } from '@activepieces/shared'; + import { createUser } from './lib/actions/createUser'; + + export const aminosAuth = PieceAuth.CustomAuth({ + description: 'Enter Aminos One authentication details', + props: { + base_url: Property.ShortText({ + displayName: 'Base URL', + description: 'Enter the base URL', + required: true, + }), + access_token: PieceAuth.SecretText({ + displayName: 'API key', + description: 'Enter the API key from your Aminos One panel', + required: true + }) + }, + // Optional Validation + validate: async ({auth}) => { + if(auth){ + return { + valid: true, + } + } + return { + valid: false, + error: 'Invalid Api Key' + } + }, + required: true + }); + + export const aminos = createPiece({ + displayName: "Aminos", + auth: aminosAuth, + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + minimumSupportedRelease: '0.30.0', + logoUrl: "https://cdn.activepieces.com/pieces/aminos.png", + authors: ["buttonsbond"], + actions: [createUser], + triggers: [], + }); + diff --git a/packages/pieces/community/aminos/src/lib/actions/createUser.ts b/packages/pieces/community/aminos/src/lib/actions/createUser.ts new file mode 100644 index 0000000..7058925 --- /dev/null +++ b/packages/pieces/community/aminos/src/lib/actions/createUser.ts @@ -0,0 +1,55 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +//import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { aminosAuth } from '../..'; +export const createUser = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + auth: aminosAuth, + name: 'createUser', + displayName: 'Create User on Aminos One', + description: 'Create a user and plan in Aminos One Panel', + props: { + useremail: Property.ShortText({ + displayName: 'Username (e-mail)', + description: 'Username, should be an e-mail address', + required:true, + }), + userfriendlyname: Property.ShortText({ + displayName: 'Name of user', + description: 'The name of the user', + required:true, + }), + userplanid: Property.Number({ + displayName: 'Plan ID', + description: 'Plan ID number from the plans in your Aminos One panel', + required:true, + }) + }, + async run(context) { + // the below need to be passed with the JSON + // context.auth.access_token + // context.auth.base_url + const baseUrl = context.auth.base_url.replace(/\/$/, ''); + const headers = { + 'Content-Type': 'application/json', + }; + const createAminosRequestBody = { + api_key: context.auth.access_token, + name: context.propsValue.userfriendlyname, + email: context.propsValue.useremail, + price_plan_id: context.propsValue.userplanid, + }; + // console.log("AMINOS:" + JSON.stringify(createAminosRequestBody)); + const createAminosResponse = await fetch(`${baseUrl}/api/users`, { + method: 'POST', + headers, + body: JSON.stringify(createAminosRequestBody), + }); + // 400 status is returned on failure, possibly because user exists already + if (!createAminosResponse.ok) { + throw new Error(`Failed to create user. Status: ${createAminosResponse.status}`); + } + const createAminosResponseBody = await createAminosResponse.json(); + // if creation was ok, then user and status are returned, user is the user id on aminos, status will just be success + return createAminosResponseBody; + }, +}); diff --git a/packages/pieces/community/aminos/tsconfig.json b/packages/pieces/community/aminos/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/aminos/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/aminos/tsconfig.lib.json b/packages/pieces/community/aminos/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/aminos/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/anyhook-graphql/.eslintrc.json b/packages/pieces/community/anyhook-graphql/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/anyhook-graphql/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/anyhook-graphql/README.md b/packages/pieces/community/anyhook-graphql/README.md new file mode 100644 index 0000000..91bc6e0 --- /dev/null +++ b/packages/pieces/community/anyhook-graphql/README.md @@ -0,0 +1,7 @@ +# pieces-anyhook-graphql + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-anyhook-graphql` to build the library. diff --git a/packages/pieces/community/anyhook-graphql/package.json b/packages/pieces/community/anyhook-graphql/package.json new file mode 100644 index 0000000..5b5790a --- /dev/null +++ b/packages/pieces/community/anyhook-graphql/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-anyhook-graphql", + "version": "0.0.1" +} diff --git a/packages/pieces/community/anyhook-graphql/project.json b/packages/pieces/community/anyhook-graphql/project.json new file mode 100644 index 0000000..5d2585d --- /dev/null +++ b/packages/pieces/community/anyhook-graphql/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-anyhook-graphql", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/anyhook-graphql/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/anyhook-graphql", + "tsConfig": "packages/pieces/community/anyhook-graphql/tsconfig.lib.json", + "packageJson": "packages/pieces/community/anyhook-graphql/package.json", + "main": "packages/pieces/community/anyhook-graphql/src/index.ts", + "assets": [ + "packages/pieces/community/anyhook-graphql/*.md", + { + "input": "packages/pieces/community/anyhook-graphql/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/anyhook-graphql/src/index.ts b/packages/pieces/community/anyhook-graphql/src/index.ts new file mode 100644 index 0000000..1622562 --- /dev/null +++ b/packages/pieces/community/anyhook-graphql/src/index.ts @@ -0,0 +1,18 @@ +import { createPiece } from '@activepieces/pieces-framework'; +import { graphqlCommon } from './lib/common/common'; +import { graphqlSubscriptionTrigger } from './lib/triggers/graphql-subscription-trigger'; + +export const anyHookGraphql = createPiece({ + displayName: 'AnyHook GraphQL', + description: + 'AnyHook GraphQL enables real-time communication through AnyHook proxy server by allowing you to subscribe and listen to GraphQL subscription events', + auth: graphqlCommon.auth, + minimumSupportedRelease: '0.20.0', + logoUrl: + 'https://cdn.activepieces.com/pieces/anyhook-graphql.png', + authors: ['ahmad-swanblocks'], + actions: [], + triggers: [ + graphqlSubscriptionTrigger, + ], +}); diff --git a/packages/pieces/community/anyhook-graphql/src/lib/common/common.ts b/packages/pieces/community/anyhook-graphql/src/lib/common/common.ts new file mode 100644 index 0000000..d0489ab --- /dev/null +++ b/packages/pieces/community/anyhook-graphql/src/lib/common/common.ts @@ -0,0 +1,30 @@ +import { PieceAuth, Property } from '@activepieces/pieces-framework'; + +export const graphqlCommon = { + connectionType: "graphql", + auth: PieceAuth.CustomAuth({ + required: true, + props: { + proxyBaseUrl: Property.ShortText({ + displayName: 'AnyHook Server URL', + description: 'The URL of your AnyHook server', + required: true, + defaultValue: 'http://10.0.0.101:3001' + }), + }, + }), + apiCall: async function ( + url: string, + method: string, + data: object | undefined = undefined + ) { + const response = await fetch(url, { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + return response.json(); + }, +}; diff --git a/packages/pieces/community/anyhook-graphql/src/lib/triggers/graphql-subscription-trigger.ts b/packages/pieces/community/anyhook-graphql/src/lib/triggers/graphql-subscription-trigger.ts new file mode 100644 index 0000000..fe6b8dc --- /dev/null +++ b/packages/pieces/community/anyhook-graphql/src/lib/triggers/graphql-subscription-trigger.ts @@ -0,0 +1,61 @@ +import { TriggerStrategy, createTrigger, PieceAuth, Property } from '@activepieces/pieces-framework'; +import { createGraphQLSubscription, deleteGraphQLSubscription } from './helpers'; +import { graphqlCommon } from '../common/common'; + +export const graphqlSubscriptionTrigger = createTrigger({ + auth: graphqlCommon.auth, + name: 'graphql_subscription_trigger', + displayName: 'New GraphQL Subscription Event', + description: 'Triggers on a new GraphQL subscription event', + type: TriggerStrategy.WEBHOOK, + props: { + websocketUrl: Property.ShortText({ + displayName: 'Endpoint URL', + description: 'The GraphQL websocket to connect to.', + required: true, + defaultValue: 'wss://streaming.bitquery.io/graphql?token=xxxx', + }), + headers: Property.Object({ + displayName: 'Headers', + description: 'Custom headers for the GraphQL connection, such as authentication tokens.', + required: false, + }), + query: Property.LongText({ + displayName: 'GraphQL Query', + description: 'GraphQL subscription query to listen to events', + required: true, + defaultValue: 'subscription { EVM(network: eth, trigger_on: head) { Blocks { Block { BaseFee BaseFeeInUSD Bloom Coinbase } } } }', + }), + }, + async onEnable(context) { + const subscriptionInfo = await createGraphQLSubscription( + context.propsValue.websocketUrl, + context.propsValue.headers, + context.propsValue.query, + context.webhookUrl, + context.auth.proxyBaseUrl + ); + + // Store subscription info + await context.store.put('graphql_subscription_trigger', subscriptionInfo.subscriptionId); + }, + async onDisable(context) { + const subscriptionId: string = (await context.store.get('graphql_subscription_trigger')) as string; + if (subscriptionId) { + await deleteGraphQLSubscription(subscriptionId, context.auth.proxyBaseUrl); + await context.store.delete('graphql_subscription_trigger'); + } + }, + async run(context) { + return [context.payload.body]; + }, + async test(context) { + return [ + { + event: 'GraphQL event data', + details: 'Details of the event', + }, + ]; + }, + sampleData: {}, +}); diff --git a/packages/pieces/community/anyhook-graphql/src/lib/triggers/helpers.ts b/packages/pieces/community/anyhook-graphql/src/lib/triggers/helpers.ts new file mode 100644 index 0000000..f067c70 --- /dev/null +++ b/packages/pieces/community/anyhook-graphql/src/lib/triggers/helpers.ts @@ -0,0 +1,32 @@ +import { PiecePropValueSchema } from '@activepieces/pieces-framework'; +import { graphqlCommon } from '../common/common'; + +export async function createGraphQLSubscription( + websocketUrl: string, + headers: Record | undefined, + query: string, + webhookUrl: string, + proxyBaseUrl: string +) { + const subscriptionApiUrl = `${proxyBaseUrl}/subscribe`; + const data = { + connection_type: graphqlCommon.connectionType, + webhook_url: webhookUrl, + args: { + endpoint_url: websocketUrl, + headers: headers ? JSON.stringify(headers) : undefined, + query, + }, + }; + const response = await graphqlCommon.apiCall(subscriptionApiUrl, 'POST', data); + return response; +} + +export async function deleteGraphQLSubscription(subscriptionId: string, proxyBaseUrl: string) { + const deleteApiUrl = `${proxyBaseUrl}/unsubscribe`; + const data = { + subscription_id: subscriptionId, + }; + const response = await graphqlCommon.apiCall(deleteApiUrl, 'POST', data); + return response; +} diff --git a/packages/pieces/community/anyhook-graphql/tsconfig.json b/packages/pieces/community/anyhook-graphql/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/anyhook-graphql/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/anyhook-graphql/tsconfig.lib.json b/packages/pieces/community/anyhook-graphql/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/anyhook-graphql/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/anyhook-websocket/.eslintrc.json b/packages/pieces/community/anyhook-websocket/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/anyhook-websocket/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/anyhook-websocket/README.md b/packages/pieces/community/anyhook-websocket/README.md new file mode 100644 index 0000000..21ad39a --- /dev/null +++ b/packages/pieces/community/anyhook-websocket/README.md @@ -0,0 +1,7 @@ +# pieces-anyhook-websocket + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-anyhook-websocket` to build the library. diff --git a/packages/pieces/community/anyhook-websocket/package.json b/packages/pieces/community/anyhook-websocket/package.json new file mode 100644 index 0000000..9e9db40 --- /dev/null +++ b/packages/pieces/community/anyhook-websocket/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-anyhook-websocket", + "version": "0.0.1" +} diff --git a/packages/pieces/community/anyhook-websocket/project.json b/packages/pieces/community/anyhook-websocket/project.json new file mode 100644 index 0000000..6b120b3 --- /dev/null +++ b/packages/pieces/community/anyhook-websocket/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-anyhook-websocket", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/anyhook-websocket/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/anyhook-websocket", + "tsConfig": "packages/pieces/community/anyhook-websocket/tsconfig.lib.json", + "packageJson": "packages/pieces/community/anyhook-websocket/package.json", + "main": "packages/pieces/community/anyhook-websocket/src/index.ts", + "assets": [ + "packages/pieces/community/anyhook-websocket/*.md", + { + "input": "packages/pieces/community/anyhook-websocket/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/anyhook-websocket/src/index.ts b/packages/pieces/community/anyhook-websocket/src/index.ts new file mode 100644 index 0000000..d0d1d6b --- /dev/null +++ b/packages/pieces/community/anyhook-websocket/src/index.ts @@ -0,0 +1,18 @@ +import { createPiece } from '@activepieces/pieces-framework'; +import { websocketCommon } from './lib/common/common'; +import { websocketSubscriptionTrigger } from './lib/triggers/websocket-subscription-trigger'; + +export const anyHookWebsocket = createPiece({ + displayName: 'AnyHook Websocket', + description: + 'AnyHook Websocket enables real-time communication through AnyHook proxy server by allowing you to subscribe and listen to websocket events', + auth: websocketCommon.auth, + minimumSupportedRelease: '0.20.0', + logoUrl: + 'https://cdn.activepieces.com/pieces/anyhook-websocket.png', + authors: ['Swanblocks/Ahmad Shawar'], + actions: [], + triggers: [ + websocketSubscriptionTrigger, + ], +}); diff --git a/packages/pieces/community/anyhook-websocket/src/lib/common/common.ts b/packages/pieces/community/anyhook-websocket/src/lib/common/common.ts new file mode 100644 index 0000000..fceca28 --- /dev/null +++ b/packages/pieces/community/anyhook-websocket/src/lib/common/common.ts @@ -0,0 +1,30 @@ +import { PieceAuth, Property } from '@activepieces/pieces-framework'; + +export const websocketCommon = { + connectionType: "websocket", + auth: PieceAuth.CustomAuth({ + required: true, + props: { + proxyBaseUrl: Property.ShortText({ + displayName: 'AnyHook Server URL', + description: 'The URL of your AnyHook server', + required: true, + defaultValue: 'http://10.0.0.101:3001' + }), + }, + }), + apiCall: async function ( + url: string, + method: string, + data: object | undefined = undefined + ) { + const response = await fetch(url, { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + return response.json(); + }, +}; diff --git a/packages/pieces/community/anyhook-websocket/src/lib/triggers/helpers.ts b/packages/pieces/community/anyhook-websocket/src/lib/triggers/helpers.ts new file mode 100644 index 0000000..12cfd1d --- /dev/null +++ b/packages/pieces/community/anyhook-websocket/src/lib/triggers/helpers.ts @@ -0,0 +1,31 @@ +import { websocketCommon } from '../common/common'; + +export async function createWebsocketSubscription( + websocketUrl: string, + headers: Record | undefined, + message: Record, + webhookUrl: string, + proxyBaseUrl: string +) { + const subscriptionApiUrl = `${proxyBaseUrl}/subscribe`; + const data = { + connection_type: websocketCommon.connectionType, + webhook_url: webhookUrl, + args: { + endpoint_url: websocketUrl, + headers: headers ? JSON.stringify(headers) : undefined, + message, + }, + }; + const response = await websocketCommon.apiCall(subscriptionApiUrl, 'POST', data); + return response; +} + +export async function deleteWebsocketSubscription(subscriptionId: string, proxyBaseUrl: string) { + const deleteApiUrl = `${proxyBaseUrl}/unsubscribe`; + const data = { + subscription_id: subscriptionId, + }; + const response = await websocketCommon.apiCall(deleteApiUrl, 'POST', data); + return response; +} diff --git a/packages/pieces/community/anyhook-websocket/src/lib/triggers/websocket-subscription-trigger.ts b/packages/pieces/community/anyhook-websocket/src/lib/triggers/websocket-subscription-trigger.ts new file mode 100644 index 0000000..0075b34 --- /dev/null +++ b/packages/pieces/community/anyhook-websocket/src/lib/triggers/websocket-subscription-trigger.ts @@ -0,0 +1,66 @@ +import { TriggerStrategy, createTrigger, PieceAuth, Property } from '@activepieces/pieces-framework'; +import { createWebsocketSubscription, deleteWebsocketSubscription } from './helpers'; +import { websocketCommon } from '../common/common'; + +export const websocketSubscriptionTrigger = createTrigger({ + auth: websocketCommon.auth, + name: 'websocket_subscription_trigger', + displayName: 'New Websocket Subscription Event', + description: 'Triggers on a new Websocket subscription event', + type: TriggerStrategy.WEBHOOK, + props: { + websocketUrl: Property.ShortText({ + displayName: 'Endpoint URL', + description: 'Websocket endpoint URL to connect to.', + required: true, + defaultValue: 'wss://testnets-stream.openseabeta.com/socket/websocket?token=xxxx', + }), + headers: Property.Object({ + displayName: 'Headers', + description: 'Custom headers for the Websocket connection, such as authentication tokens.', + required: false + }), + message: Property.Json({ + displayName: 'Subscription Message', + description: 'The message to send to the Websocket server to subscribe to events.', + required: true, + defaultValue: { + "topic": "collection:boredapeyachtclub", + "event": "phx_join", + "payload": {}, + "ref": 0 + } + }), + }, + async onEnable(context) { + const subscriptionInfo = await createWebsocketSubscription( + context.propsValue.websocketUrl, + context.propsValue.headers, + context.propsValue.message, + context.webhookUrl, + context.auth.proxyBaseUrl + ); + + // Store subscription info + await context.store.put('websocket_subscription_trigger', subscriptionInfo.subscriptionId); + }, + async onDisable(context) { + const subscriptionId: string = (await context.store.get('websocket_subscription_trigger')) as string; + if (subscriptionId) { + await deleteWebsocketSubscription(subscriptionId, context.auth.proxyBaseUrl); + await context.store.delete('websocket_subscription_trigger'); + } + }, + async run(context) { + return [context.payload.body]; + }, + async test(context) { + return [ + { + event: 'Websocket event data', + details: 'Details of the event', + }, + ]; + }, + sampleData: {}, +}); diff --git a/packages/pieces/community/anyhook-websocket/tsconfig.json b/packages/pieces/community/anyhook-websocket/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/anyhook-websocket/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/anyhook-websocket/tsconfig.lib.json b/packages/pieces/community/anyhook-websocket/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/anyhook-websocket/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/apify/.eslintrc.json b/packages/pieces/community/apify/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/apify/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/apify/README.md b/packages/pieces/community/apify/README.md new file mode 100644 index 0000000..5c2fc85 --- /dev/null +++ b/packages/pieces/community/apify/README.md @@ -0,0 +1,7 @@ +# pieces-apify + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-apify` to build the library. diff --git a/packages/pieces/community/apify/package.json b/packages/pieces/community/apify/package.json new file mode 100644 index 0000000..67949a3 --- /dev/null +++ b/packages/pieces/community/apify/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-apify", + "version": "0.0.2" +} diff --git a/packages/pieces/community/apify/project.json b/packages/pieces/community/apify/project.json new file mode 100644 index 0000000..1e6bc40 --- /dev/null +++ b/packages/pieces/community/apify/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-apify", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/apify/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/apify", + "tsConfig": "packages/pieces/community/apify/tsconfig.lib.json", + "packageJson": "packages/pieces/community/apify/package.json", + "main": "packages/pieces/community/apify/src/index.ts", + "assets": [ + "packages/pieces/community/apify/*.md", + { + "input": "packages/pieces/community/apify/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/apify/src/index.ts b/packages/pieces/community/apify/src/index.ts new file mode 100644 index 0000000..417bb1d --- /dev/null +++ b/packages/pieces/community/apify/src/index.ts @@ -0,0 +1,43 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { getDatasetItems } from './lib/actions/get-dataset-items'; +import { getActors } from './lib/actions/get-actors'; +import { getLastRun } from './lib/actions/get-last-run'; +import { startActor } from './lib/actions/start-actor'; + +export const apifyAuth = PieceAuth.CustomAuth({ + description: 'Enter API key authentication details', + props: { + apikey: PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: + 'Find your API key on Apify in the settings, API & Integrations section.', + }), + }, + // Optional Validation + validate: async ({ auth }) => { + if (auth) { + return { + valid: true, + }; + } + return { + valid: false, + error: 'Invalid Api Key', + }; + }, + required: true, +}); + +export const apify = createPiece({ + displayName: 'Apify', + description: 'Your full‑stack platform for web scraping', + auth: apifyAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/apify.svg', + categories: [PieceCategory.BUSINESS_INTELLIGENCE], + authors: ['buttonsbond'], + actions: [getDatasetItems, getActors, getLastRun, startActor], + triggers: [], +}); diff --git a/packages/pieces/community/apify/src/lib/actions/get-actors.ts b/packages/pieces/community/apify/src/lib/actions/get-actors.ts new file mode 100644 index 0000000..bb7af45 --- /dev/null +++ b/packages/pieces/community/apify/src/lib/actions/get-actors.ts @@ -0,0 +1,29 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { apifyAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getActors = createAction({ + name: 'getActors', + auth: apifyAuth, + displayName: "Get user's Actors", + description: 'Gets the list of Actors available to the user.', + props: {}, + async run(context) { + const apifyToken = context.auth.apikey; + const headers = { + Authorization: 'Bearer ' + apifyToken, + 'Content-Type': 'application/json', + }; + + const url = 'https://api.apify.com/v2/acts/'; + + const httprequestdata = { + method: HttpMethod.GET, + url, + headers, + }; + + const response = await httpClient.sendRequest(httprequestdata); + return response.body; + }, +}); diff --git a/packages/pieces/community/apify/src/lib/actions/get-dataset-items.ts b/packages/pieces/community/apify/src/lib/actions/get-dataset-items.ts new file mode 100644 index 0000000..00f84e6 --- /dev/null +++ b/packages/pieces/community/apify/src/lib/actions/get-dataset-items.ts @@ -0,0 +1,39 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { apifyAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getDatasetItems = createAction({ + name: 'getDatasetItems', + auth: apifyAuth, + displayName: 'Get Dataset Items', + description: 'Gets the data from an Actors run.', + props: { + runid: Property.ShortText({ + displayName: 'The runid of the Actor (alphanumeric)', + description: + 'The runid of the completed Actors run [defaultDatasetId] (compulsory)', + required: true, + }), + }, + async run(context) { + const apifyToken = context.auth.apikey; + const headers = { + Authorization: 'Bearer ' + apifyToken, + 'Content-Type': 'application/json', + }; + + const url = + 'https://api.apify.com/v2/datasets/' + + context.propsValue.runid + + '/items/'; + + const httprequestdata = { + method: HttpMethod.GET, + url, + headers, + }; + + const response = await httpClient.sendRequest(httprequestdata); + return response.body; + }, +}); diff --git a/packages/pieces/community/apify/src/lib/actions/get-last-run.ts b/packages/pieces/community/apify/src/lib/actions/get-last-run.ts new file mode 100644 index 0000000..4041c2c --- /dev/null +++ b/packages/pieces/community/apify/src/lib/actions/get-last-run.ts @@ -0,0 +1,42 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { apifyAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getLastRun = createAction({ + name: 'getLastRun', + auth: apifyAuth, + displayName: 'Get last run details', + description: 'Gets last run details for a given Actor.', + props: { + actorid: Property.ShortText({ + displayName: 'The id of the Actor (alphanumeric)', + // updated the description as can also use the actors user or ownername a tilde and the actor name in place of the id + description: + 'The id [defaultDatasetId] of the Actor to get run information [you can also use the username then ~ then the name] (compulsory)', + required: true, + }), + }, + async run(context) { + const apifyToken = context.auth.apikey; + const headers = { + Authorization: 'Bearer ' + apifyToken, + 'Content-Type': 'application/json', + }; + + // removed ?status=SUCCEEDED as we might need to know if a particular actor failed + const url = + 'https://api.apify.com/v2/acts/' + + context.propsValue.actorid + + '/runs/last'; + //?status=SUCCEEDED' + + const httprequestdata = { + method: HttpMethod.GET, + url, + headers, + }; + + const response = await httpClient.sendRequest(httprequestdata); + return response.body.data; + }, +}); diff --git a/packages/pieces/community/apify/src/lib/actions/start-actor.ts b/packages/pieces/community/apify/src/lib/actions/start-actor.ts new file mode 100644 index 0000000..63dadc1 --- /dev/null +++ b/packages/pieces/community/apify/src/lib/actions/start-actor.ts @@ -0,0 +1,46 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { apifyAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const startActor = createAction({ + // https://api.apify.com/v2/acts/{actorId}/run-sync + // https://api.apify.com/v2/acts/{actorId}/runs + name: 'startActor', + auth: apifyAuth, + displayName: 'Start an Apify Actor', + description: 'Starts an Apify Actor web scraper', + props: { + actorid: Property.ShortText({ + displayName: 'The id or name of the Actor (alphanumeric)', + description: + 'The id of the Actor to run [Use either id, or the username then ~ then the name] (compulsory)', + required: true, + }), + jsonbody: Property.Json({ + displayName: 'JSON input', + description: + 'The JSON input to pass to the Actor [you can get the JSON from a run in your Apify account]. If left blank will use defaults. (optional)', + required: true, + }), + }, + async run(context) { + const apifyToken = context.auth.apikey; + const headers = { + Authorization: 'Bearer ' + apifyToken, + 'Content-Type': 'application/json', + }; + + const url = + 'https://api.apify.com/v2/acts/' + context.propsValue.actorid + '/runs/'; + + const httprequestdata = { + method: HttpMethod.POST, + url, + headers, + body: JSON.stringify(context.propsValue.jsonbody), + }; + + const response = await httpClient.sendRequest(httprequestdata); + return response.body.data; + }, +}); diff --git a/packages/pieces/community/apify/tsconfig.json b/packages/pieces/community/apify/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/apify/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/apify/tsconfig.lib.json b/packages/pieces/community/apify/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/apify/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/apitable/.eslintrc.json b/packages/pieces/community/apitable/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/apitable/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/apitable/README.md b/packages/pieces/community/apitable/README.md new file mode 100644 index 0000000..136c268 --- /dev/null +++ b/packages/pieces/community/apitable/README.md @@ -0,0 +1,7 @@ +# pieces-apitable + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-apitable` to build the library. diff --git a/packages/pieces/community/apitable/package.json b/packages/pieces/community/apitable/package.json new file mode 100644 index 0000000..07b2f1f --- /dev/null +++ b/packages/pieces/community/apitable/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-apitable", + "version": "0.0.20" +} diff --git a/packages/pieces/community/apitable/project.json b/packages/pieces/community/apitable/project.json new file mode 100644 index 0000000..74c18f4 --- /dev/null +++ b/packages/pieces/community/apitable/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-apitable", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/apitable/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/apitable", + "tsConfig": "packages/pieces/community/apitable/tsconfig.lib.json", + "packageJson": "packages/pieces/community/apitable/package.json", + "main": "packages/pieces/community/apitable/src/index.ts", + "assets": [ + "packages/pieces/community/apitable/*.md", + { + "input": "packages/pieces/community/apitable/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-apitable {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/apitable/src/index.ts b/packages/pieces/community/apitable/src/index.ts new file mode 100644 index 0000000..c753015 --- /dev/null +++ b/packages/pieces/community/apitable/src/index.ts @@ -0,0 +1,88 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + createPiece, + PieceAuth, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createRecordAction } from './lib/actions/create-record'; +import { findRecordAction } from './lib/actions/find-record'; +import { updateRecordAction } from './lib/actions/update-record'; +import { newRecordTrigger } from './lib/triggers/new-record'; +import { makeClient } from './lib/common'; + +export const APITableAuth = PieceAuth.CustomAuth({ + required: true, + description: ` + To obtain your AITable token, follow these steps: + + 1. Log in to your AITable account. + 2. Visit https://apitable.com/workbench + 3. Click on your profile picture (Bottom left). + 4. Click on "My Settings". + 5. Click on "Developer". + 6. Click on "Generate new token". + 7. Copy the token. + `, + props: { + token: PieceAuth.SecretText({ + displayName: 'Token', + description: 'The token of the AITable account', + required: true, + }), + apiTableUrl: Property.ShortText({ + displayName: 'Instance Url', + description: 'The url of the AITable instance.', + required: true, + defaultValue: 'https://aitable.ai', + }), + }, + validate: async ({ auth }) => { + try { + const client = makeClient( + auth as PiecePropValueSchema + ); + await client.listSpaces(); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid Token or Instance URL.', + }; + } + }, +}); + +export const apitable = createPiece({ + displayName: 'AITable', + auth: APITableAuth, + description: `Interactive spreadsheets with collaboration`, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/apitable.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: [ + 'alerdenisov', + 'Abdallah-Alwarawreh', + 'kishanprmr', + 'MoShizzle', + 'abuaboud', + ], + actions: [ + createRecordAction, + updateRecordAction, + findRecordAction, + createCustomApiCallAction({ + baseUrl: (auth) => { + return (auth as { apiTableUrl: string }).apiTableUrl; + }, + auth: APITableAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as { token: string }).token}`, + }), + }), + ], + triggers: [newRecordTrigger], +}); diff --git a/packages/pieces/community/apitable/src/lib/actions/create-record.ts b/packages/pieces/community/apitable/src/lib/actions/create-record.ts new file mode 100644 index 0000000..3b72509 --- /dev/null +++ b/packages/pieces/community/apitable/src/lib/actions/create-record.ts @@ -0,0 +1,58 @@ +import { + DynamicPropsValue, + PiecePropValueSchema, + createAction, +} from '@activepieces/pieces-framework'; +import { APITableCommon, createNewFields, makeClient } from '../common'; +import { APITableAuth } from '../../index'; + +export const createRecordAction = createAction({ + auth: APITableAuth, + name: 'apitable_create_record', + displayName: 'Create Record', + description: 'Creates a new record in datasheet.', + props: { + space_id: APITableCommon.space_id, + datasheet_id: APITableCommon.datasheet_id, + fields: APITableCommon.fields, + }, + async run(context) { + const auth = context.auth; + const datasheetId = context.propsValue.datasheet_id; + const dynamicFields: DynamicPropsValue = context.propsValue.fields; + const fields: { + [n: string]: string; + } = {}; + + const props = Object.entries(dynamicFields); + for (const [propertyKey, propertyValue] of props) { + if (propertyValue !== undefined && propertyValue !== '') { + fields[propertyKey] = propertyValue; + } + } + + const newFields: Record = await createNewFields( + auth as PiecePropValueSchema, + datasheetId, + fields + ); + + const client = makeClient( + context.auth as PiecePropValueSchema + ); + const response: any = await client.createRecord(datasheetId as string, { + records: [ + { + fields: { + ...newFields, + }, + }, + ], + }); + + if (!response.success) { + throw new Error(JSON.stringify(response, undefined, 2)); + } + return response; + }, +}); diff --git a/packages/pieces/community/apitable/src/lib/actions/find-record.ts b/packages/pieces/community/apitable/src/lib/actions/find-record.ts new file mode 100644 index 0000000..ac13416 --- /dev/null +++ b/packages/pieces/community/apitable/src/lib/actions/find-record.ts @@ -0,0 +1,80 @@ +import { + PiecePropValueSchema, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { APITableCommon, makeClient } from '../common'; +import { APITableAuth } from '../../index'; +import { prepareQuery } from '../common/client'; + +export const findRecordAction = createAction({ + auth: APITableAuth, + name: 'apitable_find_record', + displayName: 'Find Records', + description: 'Finds records in datasheet.', + props: { + space_id: APITableCommon.space_id, + datasheet_id: APITableCommon.datasheet_id, + recordIds: Property.Array({ + displayName: 'Record IDs', + description: 'The IDs of the records to find.', + required: false, + }), + fieldNames: Property.Array({ + displayName: 'Field Names', + description: + 'The returned record results are limited to the specified fields', + required: false, + }), + maxRecords: Property.Number({ + displayName: 'Max Records', + description: 'How many records are returned in total', + required: false, + }), + pageSize: Property.Number({ + displayName: 'Page Size', + description: 'How many records are returned per page (max 1000)', + required: false, + }), + pageNum: Property.Number({ + displayName: 'Page Number', + description: 'Specifies the page number of the page', + required: false, + }), + filter: Property.LongText({ + displayName: 'Filter', + description: + 'The filter to apply to the records (see https://help.aitable.ai/docs/guide/manual-formula-field-overview/)', + required: false, + }), + }, + async run(context) { + const datasheetId = context.propsValue.datasheet_id; + const recordIds = context.propsValue.recordIds ?? [] + const fieldNames = context.propsValue.fieldNames ?? [] + const maxRecords = context.propsValue.maxRecords; + const pageSize = context.propsValue.pageSize ?? 100; + const pageNum = context.propsValue.pageNum ?? 1; + const filter = context.propsValue.filter; + + const client = makeClient( + context.auth as PiecePropValueSchema + ); + const response: any = await client.listRecords( + datasheetId as string, + prepareQuery({ + pageSize: pageSize, + pageNum: pageNum, + recordIds: recordIds.join(','), + fieldNames: fieldNames.join(','), + maxRecords: maxRecords, + filterByFormula: filter, + }) + ); + + if (!response.success) { + throw new Error(JSON.stringify(response, undefined, 2)); + } + return response; + }, +}); diff --git a/packages/pieces/community/apitable/src/lib/actions/update-record.ts b/packages/pieces/community/apitable/src/lib/actions/update-record.ts new file mode 100644 index 0000000..0de4ac8 --- /dev/null +++ b/packages/pieces/community/apitable/src/lib/actions/update-record.ts @@ -0,0 +1,66 @@ +import { + DynamicPropsValue, + PiecePropValueSchema, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { APITableCommon, createNewFields, makeClient } from '../common'; +import { APITableAuth } from '../../index'; + +export const updateRecordAction = createAction({ + auth: APITableAuth, + name: 'apitable_update_record', + displayName: 'Update Record', + description: 'Updates an existing record in datasheet.', + props: { + space_id: APITableCommon.space_id, + datasheet_id: APITableCommon.datasheet_id, + recordId: Property.ShortText({ + displayName: 'Record ID', + description: 'The ID of the record to update.', + required: true, + }), + fields: APITableCommon.fields, + }, + async run(context) { + const auth = context.auth; + const datasheetId = context.propsValue.datasheet_id; + const recordId = context.propsValue.recordId; + const dynamicFields: DynamicPropsValue = context.propsValue.fields; + const fields: { + [n: string]: string; + } = {}; + + const props = Object.entries(dynamicFields); + for (const [propertyKey, propertyValue] of props) { + if (propertyValue !== undefined && propertyValue !== '') { + fields[propertyKey] = propertyValue; + } + } + + const newFields: Record = await createNewFields( + auth as PiecePropValueSchema, + datasheetId, + fields, + ); + + const client = makeClient(context.auth as PiecePropValueSchema); + + const response: any = await client.updateRecord(datasheetId, { + records: [ + { + recordId: recordId, + fields: { + ...newFields, + }, + }, + ], + }); + + if (!response.success) { + throw new Error(JSON.stringify(response, undefined, 2)); + } + + return response; + }, +}); diff --git a/packages/pieces/community/apitable/src/lib/common/client.ts b/packages/pieces/community/apitable/src/lib/common/client.ts new file mode 100644 index 0000000..8387a17 --- /dev/null +++ b/packages/pieces/community/apitable/src/lib/common/client.ts @@ -0,0 +1,129 @@ +import { + HttpMessageBody, + HttpMethod, + QueryParams, + AuthenticationType, + httpClient, +} from '@activepieces/pieces-common'; +import { AITableFieldType } from './constants'; + +function emptyValueFilter( + accessor: (key: string) => any +): (key: string) => boolean { + return (key: string) => { + const val = accessor(key); + return ( + val !== null && + val !== undefined && + (typeof val != 'string' || val.length > 0) + ); + }; +} + +export function prepareQuery(request?: Record): QueryParams { + const params: QueryParams = {}; + if (!request) return params; + Object.keys(request) + .filter(emptyValueFilter((k) => request[k])) + .forEach((k: string) => { + params[k] = (request as Record)[k].toString(); + }); + return params; +} + +export class AITableClient { + constructor(private apiTableUrl: string, private token: string) {} + + async makeRequest( + method: HttpMethod, + resourceUri: string, + query?: QueryParams, + body: any | undefined = undefined + ): Promise { + const baseUrl = this.apiTableUrl.replace(/\/$/, ''); + const res = await httpClient.sendRequest({ + method: method, + url: `${baseUrl}/fusion` + resourceUri, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: this.token, + }, + queryParams: query, + body: body, + }); + return res.body; + } + + async listSpaces() { + return await this.makeRequest<{ + data: { + spaces: { + id: string; + name: string; + }[]; + }; + }>(HttpMethod.GET, '/v1/spaces'); + } + async listDatasheets(space_id: string) { + return await this.makeRequest<{ + data: { + nodes: { + id: string; + name: string; + }[]; + }; + }>(HttpMethod.GET, `/v2/spaces/${space_id}/nodes`, { type: 'Datasheet' }); + } + + async getDatasheetFields(datasheet_id: string) { + return await this.makeRequest<{ + data: { + fields: { + id: string; + name: string; + type: AITableFieldType; + desc: string; + property?: { + format?: string; + defaultValue?: string; + options?: { + name: string; + id?: string; + }[]; + }; + }[]; + }; + }>(HttpMethod.GET, `/v1/datasheets/${datasheet_id}/fields`); + } + + async createRecord(datasheet_id: string, request: object) { + return await this.makeRequest( + HttpMethod.POST, + `/v1/datasheets/${datasheet_id}/records`, + undefined, + request + ); + } + async updateRecord(datasheet_id: string, request: object) { + return await this.makeRequest( + HttpMethod.PATCH, + `/v1/datasheets/${datasheet_id}/records`, + undefined, + request + ); + } + + async listRecords(datasheet_id: string, query?: QueryParams) { + return await this.makeRequest<{ + data: { + total: number; + records: { + recordId: string; + createdAt: number; + updatedAt: number; + fields: Record; + }[]; + }; + }>(HttpMethod.GET, `/v1/datasheets/${datasheet_id}/records`, query); + } +} diff --git a/packages/pieces/community/apitable/src/lib/common/constants.ts b/packages/pieces/community/apitable/src/lib/common/constants.ts new file mode 100644 index 0000000..e9154cb --- /dev/null +++ b/packages/pieces/community/apitable/src/lib/common/constants.ts @@ -0,0 +1,34 @@ +export const enum AITableFieldType { + SINGLE_TEXT = 'SingleText', + ONE_WAY_LINK = 'OneWayLink', + TWO_WAY_LINK = 'TwoWayLink', + MAGIC_LOOKUP = 'MagicLookUp', + SINGLE_SELECT = 'SingleSelect', + MEMBER = 'Member', + DATETIME = 'DateTime', + NUMBER = 'Number', + FORMULA = 'Formula', + TEXT = 'Text', + MULTI_SELECT = 'MultiSelect', + CURRENCY = 'Currency', + PERCENT = 'Percent', + RATING = 'Rating', + CHECKBOX = 'Checkbox', + URL = 'URL', + PHONE = 'Phone', + EMAIL = 'Email', + AUTONUMBER = 'AutoNumber', + CREATED_BY = 'CreatedBy', + LAST_MODIFIED_BY = 'LastModifiedBy', + CASCADER = 'Cascader', + CREATED_TIME = 'CreatedTime', + LAST_MODIEFIED_TIME = 'LastModifiedTime', + ATTACHMENT = 'Attachment', +} + +export const AITableNumericFieldTypes = [ + AITableFieldType.NUMBER, + AITableFieldType.RATING, + AITableFieldType.CURRENCY, + AITableFieldType.PERCENT, +]; diff --git a/packages/pieces/community/apitable/src/lib/common/index.ts b/packages/pieces/community/apitable/src/lib/common/index.ts new file mode 100644 index 0000000..9fc3741 --- /dev/null +++ b/packages/pieces/community/apitable/src/lib/common/index.ts @@ -0,0 +1,247 @@ +import { DynamicPropsValue, PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { APITableAuth } from '../../'; +import { AITableClient } from './client'; +import { AITableFieldType, AITableNumericFieldTypes } from './constants'; + +export function makeClient(auth: PiecePropValueSchema) { + const client = new AITableClient(auth.apiTableUrl, auth.token); + return client; +} + +export const APITableCommon = { + space_id: Property.Dropdown({ + displayName: 'Space', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Connect your account first', + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listSpaces(); + return { + disabled: false, + options: res.data.spaces.map((space) => { + return { + label: space.name, + value: space.id, + }; + }), + }; + }, + }), + datasheet_id: Property.Dropdown({ + displayName: 'Datasheet', + required: true, + refreshers: ['space_id'], + options: async ({ auth, space_id }) => { + if (!auth || !space_id) { + return { + disabled: true, + options: [], + placeholder: 'Connect your account first and select space.', + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listDatasheets(space_id as string); + return { + disabled: false, + options: res.data.nodes.map((datasheet) => { + return { + label: datasheet.name, + value: datasheet.id, + }; + }), + }; + }, + }), + fields: Property.DynamicProperties({ + displayName: 'Fields', + description: 'The fields to add to the record.', + required: true, + refreshers: ['auth', 'datasheet_id'], + props: async ({ auth, datasheet_id }) => { + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.getDatasheetFields(datasheet_id as unknown as string); + + const props: DynamicPropsValue = {}; + + for (const field of res.data.fields) { + if ( + ![ + AITableFieldType.ATTACHMENT, + AITableFieldType.AUTONUMBER, + AITableFieldType.CASCADER, + AITableFieldType.CREATED_BY, + AITableFieldType.CREATED_TIME, + AITableFieldType.FORMULA, + AITableFieldType.LAST_MODIEFIED_TIME, + AITableFieldType.LAST_MODIFIED_BY, + AITableFieldType.MAGIC_LOOKUP, + AITableFieldType.ONE_WAY_LINK, + ].includes(field.type) + ) { + switch (field.type) { + case AITableFieldType.CHECKBOX: + props[field.name] = Property.Checkbox({ + displayName: field.name, + required: false, + }); + break; + case AITableFieldType.CURRENCY: + case AITableFieldType.NUMBER: + case AITableFieldType.PERCENT: + case AITableFieldType.RATING: + props[field.name] = Property.Number({ + displayName: field.name, + required: false, + }); + break; + case AITableFieldType.DATETIME: + props[field.name] = Property.DateTime({ + displayName: field.name, + required: false, + }); + break; + case AITableFieldType.EMAIL: + case AITableFieldType.PHONE: + case AITableFieldType.SINGLE_TEXT: + case AITableFieldType.URL: + props[field.name] = Property.ShortText({ + displayName: field.name, + required: false, + }); + break; + case AITableFieldType.TEXT: + props[field.name] = Property.LongText({ + displayName: field.name, + required: false, + }); + break; + case AITableFieldType.MULTI_SELECT: + props[field.name] = Property.StaticMultiSelectDropdown({ + displayName: field.name, + required: false, + options: { + options: + field.property?.options?.map((option) => { + return { + label: option.name, + value: option.name, + }; + }) || [], + }, + }); + break; + case AITableFieldType.SINGLE_SELECT: + props[field.name] = Property.StaticDropdown({ + displayName: field.name, + required: false, + options: { + options: + field.property?.options?.map((option) => { + return { + label: option.name, + value: option.name, + }; + }) || [], + }, + }); + break; + case AITableFieldType.MEMBER: + props[field.name] = Property.StaticMultiSelectDropdown({ + displayName: field.name, + required: false, + options: { + options: + field.property?.options?.map((option) => { + return { + label: option.name, + value: option.id, + }; + }) || [], + }, + }); + break; + case AITableFieldType.TWO_WAY_LINK: + props[field.name] = Property.Array({ + displayName: field.name, + required: false, + }); + break; + } + } + } + return props; + }, + }), +}; + +export async function createNewFields( + auth: PiecePropValueSchema, + datasheet_id: string, + fields: Record, +) { + if (!auth) return fields; + if (!datasheet_id) return fields; + + const newFields: Record = {}; + + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.getDatasheetFields(datasheet_id as string); + + for(const field of res.data.fields) { + if ( + [ + AITableFieldType.ATTACHMENT, + AITableFieldType.AUTONUMBER, + AITableFieldType.CASCADER, + AITableFieldType.CREATED_BY, + AITableFieldType.CREATED_TIME, + AITableFieldType.FORMULA, + AITableFieldType.LAST_MODIEFIED_TIME, + AITableFieldType.LAST_MODIFIED_BY, + AITableFieldType.MAGIC_LOOKUP, + AITableFieldType.ONE_WAY_LINK, + ].includes(field.type) || !(field.name in fields) + ) { + continue; // Skip irrelevant or missing fields + } + + const key = field.name; + + // Handle numeric fields + if(AITableNumericFieldTypes.includes(field.type)) + { + newFields[key] = Number(fields[key]); + } + // Handle member fields + else if(field.type === AITableFieldType.MEMBER) + { + newFields[key] = field.property?.options?.filter( + (member) => member.id === `${fields[key]}`, + ); + } + // Handle multi-select and two-way-link fields + else if([AITableFieldType.MULTI_SELECT, AITableFieldType.TWO_WAY_LINK].includes(field.type)) + { + if(!Array.isArray(fields[key]) || (fields[key] as Array).length === 0) + { + continue; // Skip empty fields + } + newFields[key] = fields[key]; + } + // Handle all other fields + else + { + newFields[key] = fields[key]; + } + + + } + return newFields; +} diff --git a/packages/pieces/community/apitable/src/lib/triggers/new-record.ts b/packages/pieces/community/apitable/src/lib/triggers/new-record.ts new file mode 100644 index 0000000..582220c --- /dev/null +++ b/packages/pieces/community/apitable/src/lib/triggers/new-record.ts @@ -0,0 +1,91 @@ +import { APITableAuth } from '../../index'; +import { + PiecePropValueSchema, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { APITableCommon, makeClient } from '../common'; +import dayjs from 'dayjs'; + +const polling: Polling< + PiecePropValueSchema, + { datasheet_id: string } +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue: { datasheet_id }, lastFetchEpochMS }) => { + const client = makeClient( + auth as PiecePropValueSchema + ); + const records = await client.listRecords(datasheet_id as string, { + filterByFormula: `CREATED_TIME() > ${ + lastFetchEpochMS === 0 + ? dayjs().subtract(1, 'day').valueOf() + : lastFetchEpochMS + }`, + }); + + return records.data.records.map((record) => { + return { + epochMilliSeconds: record.createdAt, + data: record, + }; + }); + }, +}; + +export const newRecordTrigger = createTrigger({ + auth: APITableAuth, + name: 'new_record', + displayName: 'New Record', + description: 'Triggers when a new record is added to a datasheet.', + props: { + space_id: APITableCommon.space_id, + datasheet_id: APITableCommon.datasheet_id, + }, + sampleData: { + recordId: 'rec2T5ppW1Mal', + createdAt: 1689772153000, + updatedAt: 1689772153000, + fields: { + Title: 'mhm', + AmazingField: 'You are really looking at this?', + 'Long text': 'veeeeeeeery long text', + }, + }, + type: TriggerStrategy.POLLING, + async test(context) { + return await pollingHelper.test(polling, { + store: context.store, + auth: context.auth, + propsValue: { datasheet_id: context.propsValue.datasheet_id }, + files: context.files, + }); + }, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + store: context.store, + auth: context.auth, + propsValue: { datasheet_id: context.propsValue.datasheet_id }, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + store: context.store, + auth: context.auth, + propsValue: { datasheet_id: context.propsValue.datasheet_id }, + }); + }, + async run(context) { + return await pollingHelper.poll(polling, { + store: context.store, + auth: context.auth, + propsValue: { datasheet_id: context.propsValue.datasheet_id }, + files: context.files, + }); + }, +}); diff --git a/packages/pieces/community/apitable/tsconfig.json b/packages/pieces/community/apitable/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/apitable/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/apitable/tsconfig.lib.json b/packages/pieces/community/apitable/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/apitable/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/apollo/.eslintrc.json b/packages/pieces/community/apollo/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/apollo/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/apollo/README.md b/packages/pieces/community/apollo/README.md new file mode 100644 index 0000000..b84f4f3 --- /dev/null +++ b/packages/pieces/community/apollo/README.md @@ -0,0 +1,7 @@ +# pieces-apollo + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-apollo` to build the library. diff --git a/packages/pieces/community/apollo/package.json b/packages/pieces/community/apollo/package.json new file mode 100644 index 0000000..dd4e66e --- /dev/null +++ b/packages/pieces/community/apollo/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-apollo", + "version": "0.0.3" +} diff --git a/packages/pieces/community/apollo/project.json b/packages/pieces/community/apollo/project.json new file mode 100644 index 0000000..f2b4b8d --- /dev/null +++ b/packages/pieces/community/apollo/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-apollo", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/apollo/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/apollo", + "tsConfig": "packages/pieces/community/apollo/tsconfig.lib.json", + "packageJson": "packages/pieces/community/apollo/package.json", + "main": "packages/pieces/community/apollo/src/index.ts", + "assets": [ + "packages/pieces/community/apollo/*.md", + { + "input": "packages/pieces/community/apollo/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-apollo {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/apollo/src/index.ts b/packages/pieces/community/apollo/src/index.ts new file mode 100644 index 0000000..d79fc51 --- /dev/null +++ b/packages/pieces/community/apollo/src/index.ts @@ -0,0 +1,19 @@ + +import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { matchPerson } from "./lib/actions/match-person"; +import { enrichCompany } from "./lib/actions/enrich-company"; + +export const apolloAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, +}); + +export const apollo = createPiece({ + displayName: "Apollo", + auth: apolloAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: "https://cdn.activepieces.com/pieces/apollo.png", + authors: ['abuaboud'], + actions: [matchPerson, enrichCompany], + triggers: [], +}); diff --git a/packages/pieces/community/apollo/src/lib/actions/enrich-company.ts b/packages/pieces/community/apollo/src/lib/actions/enrich-company.ts new file mode 100644 index 0000000..3c6397a --- /dev/null +++ b/packages/pieces/community/apollo/src/lib/actions/enrich-company.ts @@ -0,0 +1,47 @@ +import { apolloAuth } from '../../'; +import { Property, StoreScope, createAction } from '@activepieces/pieces-framework'; +import { + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; + +export const enrichCompany = createAction({ + name: 'enrichCompany', + description: '', + displayName: 'Enrich Company', + props: { + domain: Property.ShortText({ + displayName: 'Domain', + description: '', + required: true, + }), + cacheResponse: Property.Checkbox({ + displayName: 'Cache Response', + description: 'Store the response in the project store for future use.', + required: false, + defaultValue: true, + }), + }, + auth: apolloAuth, + async run({ propsValue, auth, store }) { + if (propsValue.cacheResponse) { + const cachedResult = await store.get(`_apollo_org_${propsValue.domain}`, StoreScope.PROJECT); + if (cachedResult) { + return cachedResult; + } + } + const result = await httpClient.sendRequest<{ organization: Record }>({ + method: HttpMethod.GET, + url: `https://api.apollo.io/v1/organizations/enrich?domain=${propsValue.domain}&api_key=${auth}`, + headers: { + 'Content-Type': 'application/json', + }, + }); + const resultOrg = result.body.organization || {}; + if (propsValue.cacheResponse) { + await store.put(`_apollo_org_${propsValue.domain}`, resultOrg, StoreScope.PROJECT); + + } + return resultOrg; + }, +}); diff --git a/packages/pieces/community/apollo/src/lib/actions/match-person.ts b/packages/pieces/community/apollo/src/lib/actions/match-person.ts new file mode 100644 index 0000000..01667a8 --- /dev/null +++ b/packages/pieces/community/apollo/src/lib/actions/match-person.ts @@ -0,0 +1,47 @@ +import { apolloAuth } from '../../'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { Property, StoreScope, createAction } from '@activepieces/pieces-framework'; + +export const matchPerson = createAction({ + name: 'matchPerson', + displayName: 'Match Person', + description: '', + props: { + email: Property.ShortText({ + displayName: 'Email', + description: '', + required: true, + }), + cacheResponse: Property.Checkbox({ + displayName: 'Cache Response', + description: 'Store the response in the project store for future use.', + required: false, + defaultValue: true, + }), + }, + auth: apolloAuth, + async run({ propsValue, auth, store }) { + if (propsValue.cacheResponse) { + const cachedResult = await store.get(`_apollo_person_${propsValue.email}`, StoreScope.PROJECT); + if (cachedResult) { + return cachedResult; + } + } + const result = await httpClient.sendRequest<{ person: Record }>({ + method: HttpMethod.POST, + url: `https://api.apollo.io/v1/people/match`, + headers: { + 'Content-Type': 'application/json', + }, + body: { + api_key: auth, + email: propsValue.email, + } + }); + const personResult = result.body.person || {}; + if (propsValue.cacheResponse) { + await store.put(`_apollo_person_${propsValue.email}`, personResult, StoreScope.PROJECT); + } + return personResult; + }, +}); diff --git a/packages/pieces/community/apollo/tsconfig.json b/packages/pieces/community/apollo/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/apollo/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/apollo/tsconfig.lib.json b/packages/pieces/community/apollo/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/apollo/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/approval/.eslintrc.json b/packages/pieces/community/approval/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/approval/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/approval/README.md b/packages/pieces/community/approval/README.md new file mode 100644 index 0000000..a55ed1d --- /dev/null +++ b/packages/pieces/community/approval/README.md @@ -0,0 +1,7 @@ +# pieces-approval + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-approval` to build the library. diff --git a/packages/pieces/community/approval/package.json b/packages/pieces/community/approval/package.json new file mode 100644 index 0000000..1ad9af4 --- /dev/null +++ b/packages/pieces/community/approval/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-approval", + "version": "0.1.9" +} \ No newline at end of file diff --git a/packages/pieces/community/approval/project.json b/packages/pieces/community/approval/project.json new file mode 100644 index 0000000..499f677 --- /dev/null +++ b/packages/pieces/community/approval/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-approval", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/approval/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/approval", + "tsConfig": "packages/pieces/community/approval/tsconfig.lib.json", + "packageJson": "packages/pieces/community/approval/package.json", + "main": "packages/pieces/community/approval/src/index.ts", + "assets": [ + "packages/pieces/community/approval/*.md", + { + "input": "packages/pieces/community/approval/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-approval {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/approval/src/index.ts b/packages/pieces/community/approval/src/index.ts new file mode 100644 index 0000000..170fb9c --- /dev/null +++ b/packages/pieces/community/approval/src/index.ts @@ -0,0 +1,16 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createApprovalLink } from './lib/actions/create-approval-link'; +import { waitForApprovalLink } from './lib/actions/wait-for-approval'; + +export const approval = createPiece({ + displayName: 'Approval (Legacy)', + description: 'Build approval process in your workflows', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/approval.svg', + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + categories: [PieceCategory.CORE, PieceCategory.FLOW_CONTROL], + actions: [waitForApprovalLink, createApprovalLink], + triggers: [], +}); diff --git a/packages/pieces/community/approval/src/lib/actions/create-approval-link.ts b/packages/pieces/community/approval/src/lib/actions/create-approval-link.ts new file mode 100644 index 0000000..5980bc8 --- /dev/null +++ b/packages/pieces/community/approval/src/lib/actions/create-approval-link.ts @@ -0,0 +1,33 @@ +import { createAction, Property, PropertyType } from '@activepieces/pieces-framework'; +import { MarkdownVariant } from '@activepieces/shared'; + +export const createApprovalLink = createAction({ + name: 'create_approval_links', + displayName: 'Create Approval Links', + description: + 'Create links only without pausing the flow, use wait for approval to pause', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.WARNING, + value: 'Please use Manual Task feature instead from 0.48.0 and above', + }), + }, + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + async run(ctx) { + return { + approvalLink: ctx.generateResumeUrl({ + queryParams: { action: 'approve' }, + }), + disapprovalLink: ctx.generateResumeUrl({ + queryParams: { action: 'disapprove' }, + }), + }; + }, +}); diff --git a/packages/pieces/community/approval/src/lib/actions/wait-for-approval.ts b/packages/pieces/community/approval/src/lib/actions/wait-for-approval.ts new file mode 100644 index 0000000..ce1d625 --- /dev/null +++ b/packages/pieces/community/approval/src/lib/actions/wait-for-approval.ts @@ -0,0 +1,40 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { ExecutionType, MarkdownVariant, PauseType } from '@activepieces/shared'; + +export const waitForApprovalLink = createAction({ + name: 'wait_for_approval', + displayName: 'Wait for Approval', + description: 'Pauses the flow and wait for the approval from the user', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.WARNING, + value: 'Please use Manual Task feature instead from 0.48.0 and above', + }), + }, + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + async run(ctx) { + if (ctx.executionType === ExecutionType.BEGIN) { + ctx.run.pause({ + pauseMetadata: { + type: PauseType.WEBHOOK, + response: {} + }, + }); + + return { + approved: true, + }; + } else { + return { + approved: ctx.resumePayload.queryParams['action'] === 'approve', + }; + } + }, +}); diff --git a/packages/pieces/community/approval/tsconfig.json b/packages/pieces/community/approval/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/approval/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/approval/tsconfig.lib.json b/packages/pieces/community/approval/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/approval/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/asana/.eslintrc.json b/packages/pieces/community/asana/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/asana/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/asana/README.md b/packages/pieces/community/asana/README.md new file mode 100644 index 0000000..8ef35a5 --- /dev/null +++ b/packages/pieces/community/asana/README.md @@ -0,0 +1,7 @@ +# pieces-asana + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-asana` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/asana/package.json b/packages/pieces/community/asana/package.json new file mode 100644 index 0000000..61b8060 --- /dev/null +++ b/packages/pieces/community/asana/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-asana", + "version": "0.3.5" +} \ No newline at end of file diff --git a/packages/pieces/community/asana/project.json b/packages/pieces/community/asana/project.json new file mode 100644 index 0000000..73bceb9 --- /dev/null +++ b/packages/pieces/community/asana/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-asana", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/asana/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/asana", + "tsConfig": "packages/pieces/community/asana/tsconfig.lib.json", + "packageJson": "packages/pieces/community/asana/package.json", + "main": "packages/pieces/community/asana/src/index.ts", + "assets": [ + "packages/pieces/community/asana/*.md", + { + "input": "packages/pieces/community/asana/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/asana/src/index.ts b/packages/pieces/community/asana/src/index.ts new file mode 100644 index 0000000..2029129 --- /dev/null +++ b/packages/pieces/community/asana/src/index.ts @@ -0,0 +1,37 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { asanaCreateTaskAction } from './lib/actions/create-task'; + +export const asanaAuth = PieceAuth.OAuth2({ + description: '', + authUrl: 'https://app.asana.com/-/oauth_authorize', + tokenUrl: 'https://app.asana.com/-/oauth_token', + required: true, + scope: ['default'], +}); + +export const asana = createPiece({ + displayName: 'Asana', + description: "Work management platform designed to help teams organize, track, and manage their work.", + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/asana.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: ["ShayPunter","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + auth: asanaAuth, + actions: [ + asanaCreateTaskAction, + createCustomApiCallAction({ + baseUrl: () => `https://app.asana.com/api/1.0`, + auth: asanaAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/asana/src/lib/actions/create-task.ts b/packages/pieces/community/asana/src/lib/actions/create-task.ts new file mode 100644 index 0000000..f493d69 --- /dev/null +++ b/packages/pieces/community/asana/src/lib/actions/create-task.ts @@ -0,0 +1,82 @@ +import { asanaCommon, callAsanaApi, getTags } from '../common'; +import { getAccessTokenOrThrow, HttpMethod } from '@activepieces/pieces-common'; +import dayjs from 'dayjs'; +import { asanaAuth } from '../../'; +import { Property, createAction } from '@activepieces/pieces-framework'; + +export const asanaCreateTaskAction = createAction({ + auth: asanaAuth, + name: 'create_task', + description: 'Create a new task', + displayName: 'Create Task', + props: { + workspace: asanaCommon.workspace, + project: asanaCommon.project, + name: Property.ShortText({ + description: 'The name of the task to create', + displayName: 'Task Name', + required: true, + }), + notes: Property.LongText({ + description: + 'Free-form textual information associated with the task (i.e. its description).', + displayName: 'Task Description', + required: true, + }), + //Should be due_at in future minor version bump + due_on: Property.ShortText({ + description: 'The date on which this task is due in any format.', + displayName: 'Due Date', + required: false, + }), + tags: asanaCommon.tags, + assignee: asanaCommon.assignee, + }, + async run(configValue) { + const { auth } = configValue; + const { project, name, notes, tags, workspace, due_on, assignee } = + configValue.propsValue; + + const convertedDueAt = due_on ? dayjs(due_on).toISOString() : undefined; + + // User can provide tags name as dynamic value, we need to convert them to tags gids + const userTags = tags ?? []; + const convertedTags = await getTags(auth.access_token, workspace); + const tagsGids = userTags + .map((tag: string) => { + const foundTagById = convertedTags.find( + (convertedTag) => convertedTag.gid === tag + ); + if (foundTagById) { + return foundTagById.gid; + } + const foundTag = convertedTags.find( + (convertedTag) => + convertedTag.name?.toLowerCase() === tag.toLowerCase() + ); + if (foundTag) { + return foundTag.gid; + } + return null; + }) + .filter((tag) => tag !== null); + + return ( + await callAsanaApi( + HttpMethod.POST, + `tasks`, + getAccessTokenOrThrow(auth), + { + data: { + name, + projects: [project], + notes, + assignee, + due_at: convertedDueAt, + tags: tagsGids, + }, + } + ) + ).body['data']; + }, +}); diff --git a/packages/pieces/community/asana/src/lib/common/index.ts b/packages/pieces/community/asana/src/lib/common/index.ts new file mode 100644 index 0000000..dcf2c91 --- /dev/null +++ b/packages/pieces/community/asana/src/lib/common/index.ts @@ -0,0 +1,218 @@ +import { Property, OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { + getAccessTokenOrThrow, + HttpMethod, + HttpMessageBody, + HttpResponse, + httpClient, + AuthenticationType, +} from '@activepieces/pieces-common'; + +export const asanaCommon = { + workspace: Property.Dropdown({ + description: 'Asana workspace to create the task in', + displayName: 'Workspace', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + const response = ( + await callAsanaApi<{ + data: { + gid: string; + name: string; + }[]; + }>(HttpMethod.GET, 'workspaces', accessToken, undefined) + ).body; + return { + disabled: false, + options: response.data.map((workspace) => { + return { + label: workspace.name, + value: workspace.gid, + }; + }), + }; + }, + }), + project: Property.Dropdown({ + description: 'Asana Project to create the task in', + displayName: 'Project', + required: true, + refreshers: ['workspace'], + options: async ({ auth, workspace }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + if (!workspace) { + return { + disabled: true, + placeholder: 'Select workspace first', + options: [], + }; + } + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + const response = ( + await callAsanaApi<{ + data: { + gid: string; + name: string; + }[]; + }>( + HttpMethod.GET, + 'projects?workspace=' + workspace, + accessToken, + undefined + ) + ).body; + return { + disabled: false, + options: response.data.map((project) => { + return { + label: project.name, + value: project.gid, + }; + }), + }; + }, + }), + assignee: Property.Dropdown({ + description: 'Assignee for the task', + displayName: 'Assignee', + required: false, + refreshers: ['workspace'], + options: async ({ auth, workspace }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + if (!workspace) { + return { + disabled: true, + placeholder: 'Select workspace first', + options: [], + }; + } + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + const users = await getUsers(accessToken, workspace as string); + return { + disabled: false, + options: users.map((user) => { + return { + label: user.name, + value: user.gid, + }; + }), + }; + }, + }), + tags: Property.MultiSelectDropdown({ + description: 'Tags to add to the task', + displayName: 'Tags', + required: false, + refreshers: ['workspace'], + options: async ({ auth, workspace }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + if (!workspace) { + return { + disabled: true, + placeholder: 'Select workspace first', + options: [], + }; + } + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + const response = await getTags(accessToken, workspace as string); + return { + disabled: false, + options: response.map((project) => { + return { + label: project.name, + value: project.gid, + }; + }), + }; + }, + }), +}; + +export async function getUsers( + accessToken: string, + workspace: string +): Promise< + { + gid: string; + name: string; + }[] +> { + const response = ( + await callAsanaApi<{ + data: { + gid: string; + name: string; + }[]; + }>(HttpMethod.GET, 'users?workspace=' + workspace, accessToken, undefined) + ).body; + return response.data; +} + +export async function getTags( + accessToken: string, + workspace: string +): Promise< + { + gid: string; + name: string; + }[] +> { + const response = ( + await callAsanaApi<{ + data: { + gid: string; + name: string; + }[]; + }>( + HttpMethod.GET, + 'workspaces/' + workspace + '/tags', + accessToken, + undefined + ) + ).body; + return response.data; +} + +export async function callAsanaApi( + method: HttpMethod, + apiUrl: string, + accessToken: string, + body: any | undefined +): Promise> { + return await httpClient.sendRequest({ + method: method, + url: `https://app.asana.com/api/1.0/${apiUrl}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + body: body, + }); +} diff --git a/packages/pieces/community/asana/tsconfig.json b/packages/pieces/community/asana/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/asana/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/asana/tsconfig.lib.json b/packages/pieces/community/asana/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/asana/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/ashby/.eslintrc.json b/packages/pieces/community/ashby/.eslintrc.json new file mode 100644 index 0000000..d7449bb --- /dev/null +++ b/packages/pieces/community/ashby/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {}, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/ashby/README.md b/packages/pieces/community/ashby/README.md new file mode 100644 index 0000000..833e038 --- /dev/null +++ b/packages/pieces/community/ashby/README.md @@ -0,0 +1,7 @@ +# pieces-ashby + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-ashby` to build the library. diff --git a/packages/pieces/community/ashby/package.json b/packages/pieces/community/ashby/package.json new file mode 100644 index 0000000..5d04d0f --- /dev/null +++ b/packages/pieces/community/ashby/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-ashby", + "version": "0.0.1" +} diff --git a/packages/pieces/community/ashby/project.json b/packages/pieces/community/ashby/project.json new file mode 100644 index 0000000..3324fd1 --- /dev/null +++ b/packages/pieces/community/ashby/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-ashby", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/ashby/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/ashby", + "tsConfig": "packages/pieces/community/ashby/tsconfig.lib.json", + "packageJson": "packages/pieces/community/ashby/package.json", + "main": "packages/pieces/community/ashby/src/index.ts", + "assets": [ + "packages/pieces/community/ashby/*.md", + { + "input": "packages/pieces/community/ashby/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/ashby/src/index.ts b/packages/pieces/community/ashby/src/index.ts new file mode 100644 index 0000000..a5025d3 --- /dev/null +++ b/packages/pieces/community/ashby/src/index.ts @@ -0,0 +1,36 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; + +export const ashbyAuth = PieceAuth.CustomAuth({ + required: true, + props: { + apiKey: PieceAuth.SecretText({ + displayName: 'API key', + required: true, + }), + }, +}); + +export const ashby = createPiece({ + displayName: 'Ashby', + auth: ashbyAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/ashby.png', + authors: ['AdamSelene'], + actions: [ + createCustomApiCallAction({ + baseUrl: () => `https://api.ashbyhq.com/`, + auth: ashbyAuth, + authMapping: async (auth) => { + const { apiKey } = auth as { apiKey: string }; + return { + Authorization: `Basic ${Buffer.from(`${apiKey}:`).toString( + 'base64' + )}`, + 'Content-Type': 'application/json', + }; + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/ashby/tsconfig.json b/packages/pieces/community/ashby/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/ashby/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/ashby/tsconfig.lib.json b/packages/pieces/community/ashby/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/ashby/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/assemblyai/.eslintrc.json b/packages/pieces/community/assemblyai/.eslintrc.json new file mode 100644 index 0000000..eac708a --- /dev/null +++ b/packages/pieces/community/assemblyai/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/assemblyai/.gitignore b/packages/pieces/community/assemblyai/.gitignore new file mode 100644 index 0000000..8505a6b --- /dev/null +++ b/packages/pieces/community/assemblyai/.gitignore @@ -0,0 +1 @@ +assemblyai.env \ No newline at end of file diff --git a/packages/pieces/community/assemblyai/README.md b/packages/pieces/community/assemblyai/README.md new file mode 100644 index 0000000..16192c4 --- /dev/null +++ b/packages/pieces/community/assemblyai/README.md @@ -0,0 +1,23 @@ +# AssemblyAI piece for Activepieces + +Use the AssemblyAI piece for Activepieces to use AssemblyAI's models to +[transcribe audio with Speech-to-Text models](https://www.assemblyai.com/products/speech-to-text?utm_source=activepieces), analyze audio with [audio intelligence models](https://www.assemblyai.com/products/speech-understanding?utm_source=activepieces), build generative AI features on top of audio with LLMs using [LeMUR](https://www.assemblyai.com/blog/lemur/?utm_source=activepieces). + +Learn more about this piece: + +- [Activepieces Integrations](https://www.activepieces.com/pieces/assemblyai) +- [AssemblyAI Documentation](https://www.assemblyai.com/docs/integrations/activepieces) + +This library was built upon the [AssemblyAI JavaScript SDK](https://github.com/AssemblyAI/assemblyai-node-sdk). + +## Building + +Run `nx build pieces-assemblyai` to build the library. + +## Generating props + +- Copy `assemblyai.env.sample` to `assemblyai.env` +- In `assemblyai.env`, update the `OPENAPI_SPEC_LOCATION` variable to the path or URL of AssemblyAI's OpenAPI spec +- Run `nx generate-params pieces-assemblyai` + +You can find [AssemblyAI's OpenAPI spec on GitHub](https://github.com/AssemblyAI/assemblyai-api-spec/blob/main/openapi.yml). diff --git a/packages/pieces/community/assemblyai/assemblyai.env.sample b/packages/pieces/community/assemblyai/assemblyai.env.sample new file mode 100644 index 0000000..f21d48f --- /dev/null +++ b/packages/pieces/community/assemblyai/assemblyai.env.sample @@ -0,0 +1 @@ +OPENAPI_SPEC_LOCATION=/path/to/spec/openapi.yml \ No newline at end of file diff --git a/packages/pieces/community/assemblyai/package-lock.json b/packages/pieces/community/assemblyai/package-lock.json new file mode 100644 index 0000000..fa5270d --- /dev/null +++ b/packages/pieces/community/assemblyai/package-lock.json @@ -0,0 +1,986 @@ +{ + "name": "@activepieces/piece-assemblyai", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-assemblyai", + "version": "0.0.1", + "devDependencies": { + "@readme/openapi-parser": "^2.6.0", + "@types/node": "^20", + "dotenv": "^16.4.5", + "mergician": "^2.0.2", + "title-case": "^4.3.1", + "tsx": "^4.19.0", + "typescript": "^5.6.2" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", + "dev": true + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/runtime": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", + "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@humanwhocodes/momoa": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/momoa/-/momoa-2.0.4.tgz", + "integrity": "sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==", + "dev": true, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true + }, + "node_modules/@readme/better-ajv-errors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@readme/better-ajv-errors/-/better-ajv-errors-1.6.0.tgz", + "integrity": "sha512-9gO9rld84Jgu13kcbKRU+WHseNhaVt76wYMeRDGsUGYxwJtI3RmEJ9LY9dZCYQGI8eUZLuxb5qDja0nqklpFjQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.0", + "@babel/runtime": "^7.21.0", + "@humanwhocodes/momoa": "^2.0.3", + "chalk": "^4.1.2", + "json-to-ast": "^2.0.3", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "ajv": "4.11.8 - 8" + } + }, + "node_modules/@readme/json-schema-ref-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@readme/json-schema-ref-parser/-/json-schema-ref-parser-1.2.0.tgz", + "integrity": "sha512-Bt3QVovFSua4QmHa65EHUmh2xS0XJ3rgTEUPH998f4OW4VVJke3BuS16f+kM0ZLOGdvIrzrPRqwihuv5BAjtrA==", + "dev": true, + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@readme/openapi-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@readme/openapi-parser/-/openapi-parser-2.6.0.tgz", + "integrity": "sha512-pyFJXezWj9WI1O+gdp95CoxfY+i+Uq3kKk4zXIFuRAZi9YnHpHOpjumWWr67wkmRTw19Hskh9spyY0Iyikf3fA==", + "dev": true, + "dependencies": { + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "@readme/better-ajv-errors": "^1.6.0", + "@readme/json-schema-ref-parser": "^1.2.0", + "@readme/openapi-schemas": "^3.1.0", + "ajv": "^8.12.0", + "ajv-draft-04": "^1.0.0", + "call-me-maybe": "^1.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, + "node_modules/@readme/openapi-schemas": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@readme/openapi-schemas/-/openapi-schemas-3.1.0.tgz", + "integrity": "sha512-9FC/6ho8uFa8fV50+FPy/ngWN53jaUu4GRXlAjcxIRrzhltJnpKkBG2Tp0IDraFJeWrOpk84RJ9EMEEYzaI1Bw==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.16.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz", + "integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "dev": true, + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "dev": true + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/code-error-fragment": { + "version": "0.0.230", + "resolved": "https://registry.npmjs.org/code-error-fragment/-/code-error-fragment-0.0.230.tgz", + "integrity": "sha512-cadkfKp6932H8UkhzE/gcUqhRMNf8jHzkAN7+5Myabswaghu4xABTgPHDCjW+dBAJxj/SpkTYokpzDqY4pCzQw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.0.tgz", + "integrity": "sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/json-to-ast": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json-to-ast/-/json-to-ast-2.1.0.tgz", + "integrity": "sha512-W9Lq347r8tA1DfMvAGn9QNcgYm4Wm7Yc+k8e6vezpMnRT+NHbtlxgNBXRVjXe9YM6eTn6+p/MKOlV/aABJcSnQ==", + "dev": true, + "dependencies": { + "code-error-fragment": "0.0.230", + "grapheme-splitter": "^1.0.4" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mergician": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mergician/-/mergician-2.0.2.tgz", + "integrity": "sha512-1GDF4LuMcc7UpE7XitfSm822yDtTxLoOVB+9QFnb3o/+zoMAVKxM67UjvmEXOA7FQnz9209K0ZyHAoD+5Ibe4A==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "dev": true, + "peer": true + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/title-case": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-4.3.1.tgz", + "integrity": "sha512-VnPxQ+/j0X2FZ4ceGq1oLruTLjtN5Ul4sam5ypd4mDZLm1eHwkwip1gLxqhON/j4qyTlUlfPKslE/t4NPSlxhg==", + "dev": true + }, + "node_modules/tsx": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.0.tgz", + "integrity": "sha512-bV30kM7bsLZKZIOCHeMNVMJ32/LuJzLVajkQI/qf92J2Qr08ueLQvW00PUZGiuLPP760UINwupgUj8qrSCPUKg==", + "dev": true, + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + } + } +} diff --git a/packages/pieces/community/assemblyai/package.json b/packages/pieces/community/assemblyai/package.json new file mode 100644 index 0000000..c35cc72 --- /dev/null +++ b/packages/pieces/community/assemblyai/package.json @@ -0,0 +1,16 @@ +{ + "name": "@activepieces/piece-assemblyai", + "version": "1.0.1", + "scripts": { + "generate": "tsx ./scripts/generateFromSpec.ts" + }, + "devDependencies": { + "@readme/openapi-parser": "^2.6.0", + "@types/node": "^20", + "dotenv": "^16.4.5", + "mergician": "^2.0.2", + "title-case": "^4.3.1", + "tsx": "^4.19.0", + "typescript": "^5.6.2" + } +} diff --git a/packages/pieces/community/assemblyai/project.json b/packages/pieces/community/assemblyai/project.json new file mode 100644 index 0000000..7a25e1c --- /dev/null +++ b/packages/pieces/community/assemblyai/project.json @@ -0,0 +1,72 @@ +{ + "name": "pieces-assemblyai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/assemblyai/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/assemblyai", + "tsConfig": "packages/pieces/community/assemblyai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/assemblyai/package.json", + "main": "packages/pieces/community/assemblyai/src/index.ts", + "assets": [ + "packages/pieces/community/assemblyai/*.md", + { + "input": "packages/pieces/community/assemblyai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + }, + "_generate-params": { + "executor": "nx:run-commands", + "options": { + "command": "tsx ./scripts/generateFromSpec.ts", + "cwd": "packages/pieces/community/assemblyai" + } + }, + "format": { + "executor": "nx:run-commands", + "options": { + "command": "nx format:write --files packages/pieces/community/assemblyai/**/*" + } + }, + "generate-params": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "nx _generate-params pieces-assemblyai", + "nx format pieces-assemblyai" + ] + } + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/assemblyai/scripts/generateFromSpec.ts b/packages/pieces/community/assemblyai/scripts/generateFromSpec.ts new file mode 100644 index 0000000..091d4f2 --- /dev/null +++ b/packages/pieces/community/assemblyai/scripts/generateFromSpec.ts @@ -0,0 +1,278 @@ +/* eslint-disable prefer-const */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import dotenv from 'dotenv'; +dotenv.config({ + path: './assemblyai.env', +}); +import { existsSync, mkdirSync, unlinkSync, writeFileSync } from 'fs'; +import { dirname, join } from 'path'; +import OpenAPIParser from '@readme/openapi-parser'; +import { mergician } from 'mergician'; +import { titleCase } from 'title-case'; + +const generatedPath = './src/lib/generated/'; + +type Generators = { + props?: (schemas: any) => any; +}; +const generateMap: Record = { + transcribe: { + props: (schemas) => schemas.TranscriptParams, + }, + 'list-transcript': { + props: (schemas) => schemas.ListTranscriptParams, + }, + 'lemur-task': { + props: (schemas) => schemas.LemurTaskParams, + }, +}; + +const merge = mergician({ appendArrays: true, dedupArrays: true }); +(async function () { + const specLocation = process.env.OPENAPI_SPEC_LOCATION; + if (!specLocation) + throw new Error('OPENAPI_SPEC_LOCATION env variable is required'); + + let spec: any = merge( + await OpenAPIParser.parse(specLocation), + await OpenAPIParser.parse('./scripts/openapi.overrides.yml', { + validate: { + schema: false, + spec: false, + }, + }) + ); + + spec = await OpenAPIParser.dereference(spec); + + Object.entries(generateMap).forEach(([paramsName, { props }]) => { + const parametersPath = join(generatedPath, paramsName, 'props.ts'); + if (props) { + let propsJson = createPropsFromSchema(props(spec.components.schemas)); + let propsTs = createTs(propsJson); + + const dir = dirname(parametersPath); + mkdirSync(dir, { recursive: true }); + writeFileSync(parametersPath, propsTs, 'utf-8'); + } else if (existsSync(parametersPath)) { + unlinkSync(parametersPath); + } + }); +})(); + +function createPropsFromSchema(schema: any): Record { + if (!schema) return {}; + schema = structuredClone(schema); + if (schema.allOf) { + const obj = {}; + (schema.allOf as any[]).forEach((schema) => + Object.assign(obj, createPropsFromSchema(schema)) + ); + return obj; + } + + if (!schema.properties) return {}; + + const properties: Record = {}; + const requiredProperties = schema.required ?? []; + for (let [key, value] of Object.entries(schema.properties) as [ + key: string, + value: any + ]) { + if (value['x-ap-ignore']) { + continue; + } + + let nullable = false; + let label: string; + if (value['x-label']) { + label = titleCase(value['x-label']); + } else { + label = titleCase(key); + console.warn(`No x-label found for property ${key}`); + } + // grab the value of oneOf with null + if (value.oneOf) { + if (value.oneOf.findIndex((item: any) => item.type === 'null') > -1) { + nullable = true; + } + + const options = value.oneOf.filter((item: any) => item.type !== 'null'); + // take first one and hope for the best + value.oneOf = undefined; + const option = { + type: options[0].type, + enum: options[0].enum, + format: options[0].format, + items: options[0].items, + 'x-aai-enum': options[0]['x-aai-enum'], + anyOf: options[0].anyOf, + }; + value = { ...value, ...option }; + if (options[0].properties) { + value.properties = { ...value.properties, ...options[0].properties }; + } + if (options[0].required) { + value.required = (value.required || []).concat(options[0].required); + } + } + if (value.anyOf) { + const enumAnyOfIndex = value.anyOf.findIndex((item: any) => item.enum); + // if any string or an enum, use the enum + if ( + value.anyOf.findIndex((item: any) => item.type === 'string') > -1 && + enumAnyOfIndex > -1 + ) { + value.type = value.anyOf[enumAnyOfIndex].type; + value.enum = value.anyOf[enumAnyOfIndex].enum; + if ('x-aai-enum' in value.anyOf[enumAnyOfIndex]) { + value['x-aai-enum'] = value.anyOf[enumAnyOfIndex]['x-aai-enum']; + } + value.anyOf = undefined; + } else { + throw new Error(`Unsupported AnyOf found for ${key}`); + } + } + if (Array.isArray(value.type)) { + if (value.type.indexOf('null') > -1) { + nullable = true; + } + const types = value.type.filter((type: string) => type !== 'null'); + if (types.length === 1) { + value.type = types[0]; + } else { + throw new Error(`Multiple types found for ${key}`); + } + } + + const required = requiredProperties.indexOf(key) > -1 && !nullable; + + // handleArray + if (value.type === 'array' && value.items.type === 'object') { + properties[key] = { + displayName: label, + description: value.description, + type: 'Array', + required, + properties: { ...createPropsFromSchema(value.items) }, + }; + } else if ( + value.items && + value.items.type === 'string' && + value.items.enum + ) { + properties[key] = { + ...createField(key, label, value.items, required), + displayName: label, + description: value.description, + type: 'StaticMultiSelectDropdown', + required, + }; + } else { + // default field + properties[key] = createField(key, label, value, required); + } + } + return properties; +} + +function createField( + key: string, + label: string, + value: any, + required: boolean +) { + const field: any = { + displayName: label, + type: mapType(value), + required, + description: value.description, + }; + + if (value.type === 'boolean' && 'default' in value) { + field.defaultValue = value.default; + } + + if (value.enum) { + field.type = 'StaticDropdown'; + field.options = { + options: (value.enum as string[]).map((item) => { + const option = { label: titleCase(item), value: item }; + if ( + value['x-aai-enum'] && + value['x-aai-enum'][item] && + value['x-aai-enum'][item]['label'] + ) { + option.label = titleCase(value['x-aai-enum'][item]['label']); + return option; + } else { + console.warn(`No x-aai-enum value found for property ${key} ${item}`); + } + return option; + }), + }; + } + return field; +} + +const typeMap: Record = { + date: 'DateTime', + 'date-time': 'DateTime', + url: 'ShortText', + string: 'ShortText', + uuid: 'ShortText', + object: 'Object', + number: 'Number', + integer: 'Number', + float: 'Number', + double: 'Number', + boolean: 'Checkbox', + array: 'Array', + json: 'Json', +}; + +function mapType(schema: any) { + if (schema['x-ap-type']) { + return schema['x-ap-type']; + } + if (schema.format && schema.format in typeMap) { + return typeMap[schema.format]; + } + if (schema.type in typeMap) { + return typeMap[schema.type]; + } + throw new Error(`Unsupported type found ${schema.type}`); +} +function createTs( + propsJson: Record +): string { + let result = `import { Property } from "@activepieces/pieces-framework"; +export const props = `; + result += createTsProps(propsJson); + result += ';\n'; + return result; +} +function createTsProps( + propsJson: Record +): string { + let result = '{\n'; + for (const key in propsJson) { + const { type, ...prop } = propsJson[key]; + let innerProps: null | string = null; + if ('properties' in prop) { + innerProps = createTsProps( + prop['properties'] as Record + ); + prop.properties = '[REPLACE_WITH_PROPS]'; + } + result += + ` ${key}: Property.${type}(\n` + + JSON.stringify(prop, null, ' ') + + '),\n'; + if (innerProps) { + result = result.replace('"[REPLACE_WITH_PROPS]"', innerProps); + } + } + result += '\n}'; + return result; +} diff --git a/packages/pieces/community/assemblyai/scripts/openapi.overrides.yml b/packages/pieces/community/assemblyai/scripts/openapi.overrides.yml new file mode 100644 index 0000000..0335d94 --- /dev/null +++ b/packages/pieces/community/assemblyai/scripts/openapi.overrides.yml @@ -0,0 +1,31 @@ +openapi: 3.1.0 + +info: + version: 0.0.0 + +servers: [] +security: [] +paths: {} +components: + schemas: + TranscriptOptionalParams: + properties: + # use JSON as array with a nested array isn't supported for input in Active Pieces + custom_spelling: + description: | + Customize how words are spelled and formatted using to and from values. + Use a JSON array of objects of the following format: + ``` + [ + { + "from": ["original", "spelling"], + "to": "corrected" + } + ] + ``` + type: json + + LemurBaseParams: + properties: + context: + x-ap-type: LongText diff --git a/packages/pieces/community/assemblyai/src/index.ts b/packages/pieces/community/assemblyai/src/index.ts new file mode 100644 index 0000000..22fdafa --- /dev/null +++ b/packages/pieces/community/assemblyai/src/index.ts @@ -0,0 +1,32 @@ +import { createPiece } from '@activepieces/pieces-framework'; +import * as actions from './lib/actions'; +import { assemblyaiAuth } from './lib/auth'; +import { PieceCategory } from '@activepieces/shared'; + +export const assemblyai = createPiece({ + displayName: 'AssemblyAI', + auth: assemblyaiAuth, + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + description: + "Transcribe and extract data from audio using AssemblyAI's Speech AI.", + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/assemblyai.png', + authors: ['AssemblyAI'], + actions: [ + actions.uploadFile, + actions.transcribe, + actions.getTranscript, + actions.getSentences, + actions.getParagraphs, + actions.getSubtitles, + actions.getRedactedAudio, + actions.wordSearch, + actions.listTranscripts, + actions.deleteTranscript, + actions.lemurTask, + actions.getLemurResponse, + actions.purgeLemurRequestData, + actions.customApiCall, + ], + triggers: [], +}); diff --git a/packages/pieces/community/assemblyai/src/lib/actions/custom-api-call.ts b/packages/pieces/community/assemblyai/src/lib/actions/custom-api-call.ts new file mode 100644 index 0000000..4b1df83 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/custom-api-call.ts @@ -0,0 +1,12 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { assemblyaiAuth } from '../auth'; + +export const customApiCall = createCustomApiCallAction({ + auth: assemblyaiAuth, + baseUrl: () => 'https://api.assemblyai.com', + authMapping: async (auth) => { + return { + Authorization: `${auth}`, + }; + }, +}); diff --git a/packages/pieces/community/assemblyai/src/lib/actions/files/index.ts b/packages/pieces/community/assemblyai/src/lib/actions/files/index.ts new file mode 100644 index 0000000..54e74c8 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/files/index.ts @@ -0,0 +1 @@ +export { uploadFile } from './upload'; diff --git a/packages/pieces/community/assemblyai/src/lib/actions/files/upload.ts b/packages/pieces/community/assemblyai/src/lib/actions/files/upload.ts new file mode 100644 index 0000000..b576799 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/files/upload.ts @@ -0,0 +1,25 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { assemblyaiAuth } from '../../auth'; +import { getAssemblyAIClient } from '../../client'; + +export const uploadFile = createAction({ + name: 'uploadFile', + auth: assemblyaiAuth, + requireAuth: true, + displayName: 'Upload File', + description: "Upload a media file to AssemblyAI's servers.", + props: { + file: Property.File({ + displayName: 'Audio File', + description: 'The File or URL of the audio or video file.', + required: true, + }), + }, + async run(context) { + const client = getAssemblyAIClient(context); + const uploadedFile = await client.files.upload( + context.propsValue.file.data + ); + return uploadedFile; + }, +}); diff --git a/packages/pieces/community/assemblyai/src/lib/actions/index.ts b/packages/pieces/community/assemblyai/src/lib/actions/index.ts new file mode 100644 index 0000000..84ad32c --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/index.ts @@ -0,0 +1,14 @@ +export { uploadFile } from './files'; +export { + transcribe, + getTranscript, + getSentences, + getParagraphs, + getSubtitles, + getRedactedAudio, + wordSearch, + listTranscripts, + deleteTranscript, +} from './transcripts'; +export { lemurTask, getLemurResponse, purgeLemurRequestData } from './lemur'; +export { customApiCall } from './custom-api-call'; diff --git a/packages/pieces/community/assemblyai/src/lib/actions/lemur/get-lemur-response.ts b/packages/pieces/community/assemblyai/src/lib/actions/lemur/get-lemur-response.ts new file mode 100644 index 0000000..dc2256c --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/lemur/get-lemur-response.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { assemblyaiAuth } from '../../auth'; +import { getAssemblyAIClient } from '../../client'; +import { lemurRequestIdProp } from './shared-props'; + +export const getLemurResponse = createAction({ + name: 'getLemurResponse', + auth: assemblyaiAuth, + requireAuth: true, + displayName: 'Retrieve LeMUR response', + description: 'Retrieve a LeMUR response that was previously generated.', + props: { + request_id: lemurRequestIdProp, + }, + async run(context) { + const client = getAssemblyAIClient(context); + const lemurResponse = await client.lemur.getResponse( + context.propsValue.request_id + ); + return lemurResponse; + }, +}); diff --git a/packages/pieces/community/assemblyai/src/lib/actions/lemur/index.ts b/packages/pieces/community/assemblyai/src/lib/actions/lemur/index.ts new file mode 100644 index 0000000..2f682d2 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/lemur/index.ts @@ -0,0 +1,3 @@ +export { lemurTask } from './lemur-task'; +export { getLemurResponse } from './get-lemur-response'; +export { purgeLemurRequestData } from './purge-lemur-request-data'; diff --git a/packages/pieces/community/assemblyai/src/lib/actions/lemur/lemur-task.ts b/packages/pieces/community/assemblyai/src/lib/actions/lemur/lemur-task.ts new file mode 100644 index 0000000..8b2db51 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/lemur/lemur-task.ts @@ -0,0 +1,21 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { assemblyaiAuth } from '../../auth'; +import { getAssemblyAIClient } from '../../client'; +import { props } from '../../generated/lemur-task/props'; + +export const lemurTask = createAction({ + name: 'lemurTask', + auth: assemblyaiAuth, + requireAuth: true, + displayName: 'Run a Task using LeMUR', + description: 'Use the LeMUR task endpoint to input your own LLM prompt.', + props, + async run(context) { + const client = getAssemblyAIClient(context); + const taskResponse = await client.lemur.task({ + transcript_ids: context.propsValue.transcript_ids as string[], + prompt: context.propsValue.prompt, + }); + return taskResponse; + }, +}); diff --git a/packages/pieces/community/assemblyai/src/lib/actions/lemur/purge-lemur-request-data.ts b/packages/pieces/community/assemblyai/src/lib/actions/lemur/purge-lemur-request-data.ts new file mode 100644 index 0000000..df5cad5 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/lemur/purge-lemur-request-data.ts @@ -0,0 +1,23 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { assemblyaiAuth } from '../../auth'; +import { getAssemblyAIClient } from '../../client'; +import { lemurRequestIdProp } from './shared-props'; + +export const purgeLemurRequestData = createAction({ + name: 'purgeLemurRequestData', + auth: assemblyaiAuth, + requireAuth: true, + displayName: 'Purge LeMUR request data', + description: `Delete the data for a previously submitted LeMUR request. +The LLM response data, as well as any context provided in the original request will be removed.`, + props: { + request_id: lemurRequestIdProp, + }, + async run(context) { + const client = getAssemblyAIClient(context); + const purgeRequestDataResponse = await client.lemur.purgeRequestData( + context.propsValue.request_id + ); + return purgeRequestDataResponse; + }, +}); diff --git a/packages/pieces/community/assemblyai/src/lib/actions/lemur/shared-props.ts b/packages/pieces/community/assemblyai/src/lib/actions/lemur/shared-props.ts new file mode 100644 index 0000000..ade6a4e --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/lemur/shared-props.ts @@ -0,0 +1,8 @@ +import { Property } from '@activepieces/pieces-framework'; + +export const lemurRequestIdProp = Property.ShortText({ + displayName: 'LeMUR request ID', + description: + 'The ID of the LeMUR request whose data you want to delete. This would be found in the response of the original request.', + required: true, +}); diff --git a/packages/pieces/community/assemblyai/src/lib/actions/transcripts/delete-transcript.ts b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/delete-transcript.ts new file mode 100644 index 0000000..4863b92 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/delete-transcript.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { assemblyaiAuth } from '../../auth'; +import { getAssemblyAIClient } from '../../client'; +import { transcriptIdProp } from './shared-props'; + +export const deleteTranscript = createAction({ + name: 'deleteTranscript', + auth: assemblyaiAuth, + requireAuth: true, + displayName: 'Delete transcript', + description: 'Remove the data from the transcript and mark it as deleted.', + props: { + id: transcriptIdProp, + }, + async run(context) { + const client = getAssemblyAIClient(context); + const deleteResponse = await client.transcripts.delete( + context.propsValue.id + ); + return deleteResponse; + }, +}); diff --git a/packages/pieces/community/assemblyai/src/lib/actions/transcripts/get-paragraphs.ts b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/get-paragraphs.ts new file mode 100644 index 0000000..a6c04d7 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/get-paragraphs.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { assemblyaiAuth } from '../../auth'; +import { getAssemblyAIClient } from '../../client'; +import { transcriptIdProp } from './shared-props'; + +export const getParagraphs = createAction({ + name: 'getTranscriptParagraphs', + auth: assemblyaiAuth, + requireAuth: true, + displayName: 'Get Transcript Paragraphs', + description: 'Retrieve the paragraphs of the transcript by its ID.', + props: { + id: transcriptIdProp, + }, + async run(context) { + const client = getAssemblyAIClient(context); + const paragraphsResponse = await client.transcripts.paragraphs( + context.propsValue.id + ); + return paragraphsResponse; + }, +}); diff --git a/packages/pieces/community/assemblyai/src/lib/actions/transcripts/get-redacted-audio.ts b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/get-redacted-audio.ts new file mode 100644 index 0000000..70fe2c7 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/get-redacted-audio.ts @@ -0,0 +1,50 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { assemblyaiAuth } from '../../auth'; +import { getAssemblyAIClient } from '../../client'; +import { transcriptIdProp } from './shared-props'; + +export const getRedactedAudio = createAction({ + name: 'getRedactedAudio', + auth: assemblyaiAuth, + requireAuth: true, + displayName: 'Get Transcript Redacted Audio', + description: 'Get the result of the redacted audio model.', + props: { + id: transcriptIdProp, + download_file: Property.Checkbox({ + displayName: 'Download file?', + required: true, + defaultValue: false, + }), + download_file_name: Property.ShortText({ + displayName: 'Download File Name', + description: + 'The desired file name for storing in ActivePieces. Make sure the file extension is correct.', + required: true, + defaultValue: 'redacted-audio.mp3', + }), + }, + async run(context) { + const client = getAssemblyAIClient(context); + const redactedAudioResponse = await client.transcripts.redactedAudio( + context.propsValue.id + ); + if (redactedAudioResponse.status !== 'redacted_audio_ready') { + return redactedAudioResponse; + } + if (context.propsValue.download_file) { + const file = await client.transcripts.redactedAudioFile( + context.propsValue.id + ); + const fileReference = await context.files.write({ + fileName: context.propsValue.download_file_name, + data: Buffer.from(await file.arrayBuffer()), + }); + return { + ...redactedAudioResponse, + file: fileReference, + }; + } + return redactedAudioResponse; + }, +}); diff --git a/packages/pieces/community/assemblyai/src/lib/actions/transcripts/get-sentences.ts b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/get-sentences.ts new file mode 100644 index 0000000..9bf69ac --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/get-sentences.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { assemblyaiAuth } from '../../auth'; +import { getAssemblyAIClient } from '../../client'; +import { transcriptIdProp } from './shared-props'; + +export const getSentences = createAction({ + name: 'getTranscriptSentences', + auth: assemblyaiAuth, + requireAuth: true, + displayName: 'Get Transcript Sentences', + description: 'Retrieve the sentences of the transcript by its ID.', + props: { + id: transcriptIdProp, + }, + async run(context) { + const client = getAssemblyAIClient(context); + const sentencesResponse = await client.transcripts.sentences( + context.propsValue.id + ); + return sentencesResponse; + }, +}); diff --git a/packages/pieces/community/assemblyai/src/lib/actions/transcripts/get-subtitles.ts b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/get-subtitles.ts new file mode 100644 index 0000000..5083cfa --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/get-subtitles.ts @@ -0,0 +1,49 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { SubtitleFormat } from 'assemblyai'; +import { assemblyaiAuth } from '../../auth'; +import { getAssemblyAIClient } from '../../client'; + +export const getSubtitles = createAction({ + name: 'getSubtitles', + auth: assemblyaiAuth, + requireAuth: true, + displayName: 'Get Transcript Subtitles', + description: 'Export the transcript as SRT or VTT subtitles.', + props: { + id: Property.ShortText({ + displayName: 'Transcript ID', + required: true, + }), + format: Property.StaticDropdown({ + displayName: 'Subtitles Format', + required: true, + defaultValue: 'srt', + options: { + options: [ + { + label: 'SRT', + value: 'srt', + }, + { + label: 'VTT', + value: 'vtt', + }, + ], + }, + }), + chars_per_caption: Property.Number({ + displayName: 'Number of Characters per Caption', + description: 'The maximum number of characters per caption', + required: false, + }), + }, + async run(context) { + const client = getAssemblyAIClient(context); + const subtitles = await client.transcripts.subtitles( + context.propsValue.id, + context.propsValue.format as SubtitleFormat, + context.propsValue.chars_per_caption + ); + return subtitles; + }, +}); diff --git a/packages/pieces/community/assemblyai/src/lib/actions/transcripts/get-transcript.ts b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/get-transcript.ts new file mode 100644 index 0000000..b522c4a --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/get-transcript.ts @@ -0,0 +1,20 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { assemblyaiAuth } from '../../auth'; +import { getAssemblyAIClient } from '../../client'; +import { transcriptIdProp } from './shared-props'; + +export const getTranscript = createAction({ + name: 'getTranscript', + auth: assemblyaiAuth, + requireAuth: true, + displayName: 'Get Transcript', + description: 'Retrieves a transcript by its ID.', + props: { + id: transcriptIdProp, + }, + async run(context) { + const client = getAssemblyAIClient(context); + const transcript = await client.transcripts.get(context.propsValue.id); + return transcript; + }, +}); diff --git a/packages/pieces/community/assemblyai/src/lib/actions/transcripts/index.ts b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/index.ts new file mode 100644 index 0000000..1a56a4f --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/index.ts @@ -0,0 +1,9 @@ +export { transcribe } from './transcribe'; +export { getTranscript } from './get-transcript'; +export { getSentences } from './get-sentences'; +export { getParagraphs } from './get-paragraphs'; +export { getSubtitles } from './get-subtitles'; +export { getRedactedAudio } from './get-redacted-audio'; +export { wordSearch } from './word-search'; +export { listTranscripts } from './list-transcripts'; +export { deleteTranscript } from './delete-transcript'; diff --git a/packages/pieces/community/assemblyai/src/lib/actions/transcripts/list-transcripts.ts b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/list-transcripts.ts new file mode 100644 index 0000000..44e7978 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/list-transcripts.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { ListTranscriptParams } from 'assemblyai'; +import { assemblyaiAuth } from '../../auth'; +import { getAssemblyAIClient } from '../../client'; +import { props } from '../../generated/list-transcript/props'; + +export const listTranscripts = createAction({ + name: 'listTranscripts', + auth: assemblyaiAuth, + requireAuth: true, + displayName: 'List transcripts', + description: `Retrieve a list of transcripts you created. +Transcripts are sorted from newest to oldest. The previous URL always points to a page with older transcripts.`, + props, + async run(context) { + const client = getAssemblyAIClient(context); + const transcriptListResponse = await client.transcripts.list( + context.propsValue as ListTranscriptParams + ); + return transcriptListResponse; + }, +}); diff --git a/packages/pieces/community/assemblyai/src/lib/actions/transcripts/shared-props.ts b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/shared-props.ts new file mode 100644 index 0000000..1cf8af7 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/shared-props.ts @@ -0,0 +1,6 @@ +import { Property } from '@activepieces/pieces-framework'; + +export const transcriptIdProp = Property.ShortText({ + displayName: 'Transcript ID', + required: true, +}); diff --git a/packages/pieces/community/assemblyai/src/lib/actions/transcripts/transcribe.ts b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/transcribe.ts new file mode 100644 index 0000000..5f09490 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/transcribe.ts @@ -0,0 +1,117 @@ +import { + ActionContext, + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { ExecutionType, PauseType } from '@activepieces/shared'; +import { TranscriptParams } from 'assemblyai'; +import { assemblyaiAuth } from '../../auth'; +import { getAssemblyAIClient } from '../../client'; +import { props } from '../../generated/transcribe/props'; + +const transcribeProps = { + ...props, + wait_until_ready: Property.Checkbox({ + displayName: 'Wait until transcript is ready', + description: `Wait until the transcript status is "completed" or "error" before moving on to the next step.`, + required: true, + defaultValue: true, + }), + throw_on_error: Property.Checkbox({ + displayName: 'Throw if transcript status is error', + description: `If the transcript status is "error", throw an error.`, + required: true, + defaultValue: true, + }), +} as const; +type TranscribeContext = ActionContext< + typeof assemblyaiAuth, + typeof transcribeProps +>; +export const transcribe = createAction({ + name: 'transcribe', + auth: assemblyaiAuth, + requireAuth: true, + displayName: 'Transcribe', + description: 'Transcribe an audio or video file using AssemblyAI.', + props: transcribeProps, + async run(context: TranscribeContext) { + const client = getAssemblyAIClient(context); + if (context.executionType === ExecutionType.BEGIN) { + const transcriptParams = createTranscriptParams(context); + handleWebhookUrl(context, transcriptParams); + handlePiiAudio(context); + handleEmptyArrays(transcriptParams); + const transcript = await client.transcripts.submit(transcriptParams) as any; + if (context.propsValue.wait_until_ready) { + context.run.pause({ + pauseMetadata: { + type: PauseType.WEBHOOK, + response: transcript, + }, + }); + } + + return transcript; + } else if (context.executionType === ExecutionType.RESUME) { + const webhookBody = context.resumePayload.body as { + transcript_id: string; + }; + const transcript = await client.transcripts.get( + webhookBody.transcript_id + ); + if (context.propsValue.throw_on_error && transcript.status === 'error') { + throw new Error(transcript.error); + } + return transcript; + } else { + throw new Error('Invalid Execution Type'); + } + }, +}); +function createTranscriptParams(context: TranscribeContext): TranscriptParams { + const transcriptParams: Record = { ...context.propsValue }; + if ('wait_until_ready' in transcriptParams) + delete transcriptParams['wait_until_ready']; + if ('throw_on_error' in transcriptParams) + delete transcriptParams['throw_on_error']; + if ('auth' in transcriptParams) delete transcriptParams['auth']; + return transcriptParams as TranscriptParams; +} +function handleWebhookUrl( + context: TranscribeContext, + transcriptParams: TranscriptParams +) { + if (context.propsValue.wait_until_ready) { + const isWebhookUrlConfigured = transcriptParams.webhook_url?.trim(); + if (isWebhookUrlConfigured) { + throw new Error( + `The "Wait until transcript is ready" and "Webhook URL" fields are mutually exclusive. Please remove the "Webhook URL" field to use the "Wait until transcript is ready" field.` + ); + } + transcriptParams.webhook_url = context.generateResumeUrl({ + queryParams: {}, + }); + } +} + +function handlePiiAudio(context: TranscribeContext) { + if ( + context.propsValue.wait_until_ready === true && + context.propsValue.redact_pii_audio === true + ) { + throw new Error( + `The "Wait until transcript is ready" and "Redact PII audio" fields are mutually exclusive. Set the "Wait until transcript is ready" or "Redact PII audio" to false.` + ); + } +} + +function handleEmptyArrays(transcriptParams: TranscriptParams) { + const obj = transcriptParams as Record; + for (const key in obj) { + const value = obj[key]; + if (Array.isArray(value) && value.length === 0) { + delete obj[key]; + } + } +} diff --git a/packages/pieces/community/assemblyai/src/lib/actions/transcripts/word-search.ts b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/word-search.ts new file mode 100644 index 0000000..2307ca0 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/actions/transcripts/word-search.ts @@ -0,0 +1,30 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { assemblyaiAuth } from '../../auth'; +import { getAssemblyAIClient } from '../../client'; +import { transcriptIdProp } from './shared-props'; + +export const wordSearch = createAction({ + name: 'wordSearch', + auth: assemblyaiAuth, + requireAuth: true, + displayName: 'Search words in transcript', + description: + 'Search through the transcript for keywords. ' + + 'You can search for individual words, numbers, or phrases containing up to five words or numbers.', + props: { + id: transcriptIdProp, + words: Property.Array({ + displayName: 'Words', + required: true, + description: 'Keywords to search for', + }), + }, + async run(context) { + const client = getAssemblyAIClient(context); + const wordSearchResponse = await client.transcripts.wordSearch( + context.propsValue.id, + context.propsValue.words as string[] + ); + return wordSearchResponse; + }, +}); diff --git a/packages/pieces/community/assemblyai/src/lib/auth.ts b/packages/pieces/community/assemblyai/src/lib/auth.ts new file mode 100644 index 0000000..050da29 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/auth.ts @@ -0,0 +1,39 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { PieceAuth } from '@activepieces/pieces-framework'; +import { baseUrl } from './client'; + +export const assemblyaiAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: + 'You can retrieve your AssemblyAI API key within your AssemblyAI [Account Settings](https://www.assemblyai.com/app/account?utm_source=activepieces).', + validate: async ({ auth }) => { + if (!auth) + return { + valid: false, + error: 'The AssemblyAI API key is required.', + }; + try { + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${baseUrl}/v2/account`, + headers: { + Authorization: auth, + }, + }); + if (res.status !== 200) + return { + valid: false, + error: 'The AssemblyAI API key is invalid.', + }; + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'The AssemblyAI API key is invalid.', + }; + } + }, +}); diff --git a/packages/pieces/community/assemblyai/src/lib/client.ts b/packages/pieces/community/assemblyai/src/lib/client.ts new file mode 100644 index 0000000..cdea458 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/client.ts @@ -0,0 +1,26 @@ +import { + ActionContext, + SecretTextProperty, +} from '@activepieces/pieces-framework'; +import { AssemblyAI } from 'assemblyai'; +import packageJson from '../../package.json'; + +export const baseUrl = 'https://api.assemblyai.com'; +// Proxyman proxy +// export const baseUrl = 'http://localhost:10000'; + +export const getAssemblyAIClient = ( + context: ActionContext> +): AssemblyAI => { + if (!context.auth) throw new Error('The AssemblyAI API key is required.'); + return new AssemblyAI({ + apiKey: context.auth, + userAgent: { + integration: { + name: 'Activepieces', + version: packageJson.version, + }, + }, + baseUrl, + }); +}; diff --git a/packages/pieces/community/assemblyai/src/lib/generated/lemur-task/props.ts b/packages/pieces/community/assemblyai/src/lib/generated/lemur-task/props.ts new file mode 100644 index 0000000..e7174c0 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/generated/lemur-task/props.ts @@ -0,0 +1,88 @@ +import { Property } from '@activepieces/pieces-framework'; +export const props = { + prompt: Property.ShortText({ + displayName: 'Prompt', + required: true, + description: + 'Your text to prompt the model to produce a desired output, including any context you want to pass into the model.', + }), + transcript_ids: Property.Array({ + displayName: 'Transcript IDs', + required: false, + description: + 'A list of completed transcripts with text. Up to a maximum of 100 files or 100 hours, whichever is lower.\nUse either transcript_ids or input_text as input into LeMUR.\n', + }), + input_text: Property.ShortText({ + displayName: 'Input Text', + required: false, + description: + 'Custom formatted transcript data. Maximum size is the context limit of the selected model, which defaults to 100000.\nUse either transcript_ids or input_text as input into LeMUR.\n', + }), + context: Property.LongText({ + displayName: 'Context', + required: false, + description: + 'Context to provide the model. This can be a string or a free-form JSON value.', + }), + final_model: Property.StaticDropdown({ + displayName: 'Final Model', + required: false, + description: + 'The model that is used for the final prompt after compression is performed.\n', + options: { + options: [ + { + label: 'Claude 3.5 Sonnet (on Anthropic)', + value: 'anthropic/claude-3-5-sonnet', + }, + { + label: 'Claude 3 Opus (on Anthropic)', + value: 'anthropic/claude-3-opus', + }, + { + label: 'Claude 3 Haiku (on Anthropic)', + value: 'anthropic/claude-3-haiku', + }, + { + label: 'Claude 3 Sonnet (on Anthropic)', + value: 'anthropic/claude-3-sonnet', + }, + { + label: 'Claude 2.1 (on Anthropic)', + value: 'anthropic/claude-2-1', + }, + { + label: 'Claude 2 (on Anthropic)', + value: 'anthropic/claude-2', + }, + { + label: 'Default', + value: 'default', + }, + { + label: 'Claude Instant 1.2 (on Anthropic)', + value: 'anthropic/claude-instant-1-2', + }, + { + label: 'Basic', + value: 'basic', + }, + { + label: 'Mistral 7B (Hosted by AssemblyAI)', + value: 'assemblyai/mistral-7b', + }, + ], + }, + }), + max_output_size: Property.Number({ + displayName: 'Maximum Output Size', + required: false, + description: 'Max output size in tokens, up to 4000', + }), + temperature: Property.Number({ + displayName: 'Temperature', + required: false, + description: + 'The temperature to use for the model.\nHigher values result in answers that are more creative, lower values are more conservative.\nCan be any value between 0.0 and 1.0 inclusive.\n', + }), +}; diff --git a/packages/pieces/community/assemblyai/src/lib/generated/list-transcript/props.ts b/packages/pieces/community/assemblyai/src/lib/generated/list-transcript/props.ts new file mode 100644 index 0000000..64748c1 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/generated/list-transcript/props.ts @@ -0,0 +1,54 @@ +import { Property } from '@activepieces/pieces-framework'; +export const props = { + limit: Property.Number({ + displayName: 'Limit', + required: false, + description: 'Maximum amount of transcripts to retrieve', + }), + status: Property.StaticDropdown({ + displayName: 'Status', + required: false, + description: 'Filter by transcript status', + options: { + options: [ + { + label: 'Queued', + value: 'queued', + }, + { + label: 'Processing', + value: 'processing', + }, + { + label: 'Completed', + value: 'completed', + }, + { + label: 'Error', + value: 'error', + }, + ], + }, + }), + created_on: Property.DateTime({ + displayName: 'Created On', + required: false, + description: 'Only get transcripts created on this date', + }), + before_id: Property.ShortText({ + displayName: 'Before ID', + required: false, + description: 'Get transcripts that were created before this transcript ID', + }), + after_id: Property.ShortText({ + displayName: 'After ID', + required: false, + description: 'Get transcripts that were created after this transcript ID', + }), + throttled_only: Property.Checkbox({ + displayName: 'Throttled Only', + required: false, + description: 'Only get throttled transcripts, overrides the status filter', + defaultValue: false, + }), +}; diff --git a/packages/pieces/community/assemblyai/src/lib/generated/transcribe/props.ts b/packages/pieces/community/assemblyai/src/lib/generated/transcribe/props.ts new file mode 100644 index 0000000..0bf43f7 --- /dev/null +++ b/packages/pieces/community/assemblyai/src/lib/generated/transcribe/props.ts @@ -0,0 +1,908 @@ +import { Property } from '@activepieces/pieces-framework'; +export const props = { + audio_url: Property.ShortText({ + displayName: 'Audio URL', + required: true, + description: 'The URL of the audio or video file to transcribe.', + }), + language_code: Property.StaticDropdown({ + displayName: 'Language Code', + required: false, + description: `The language of your audio file. Possible values are found in [Supported Languages](https://www.assemblyai.com/docs/concepts/supported-languages).\nThe default value is 'en_us'.\n`, + options: { + options: [ + { + label: 'English (Global)', + value: 'en', + }, + { + label: 'English (Australian)', + value: 'en_au', + }, + { + label: 'English (British)', + value: 'en_uk', + }, + { + label: 'English (US)', + value: 'en_us', + }, + { + label: 'Spanish', + value: 'es', + }, + { + label: 'French', + value: 'fr', + }, + { + label: 'German', + value: 'de', + }, + { + label: 'Italian', + value: 'it', + }, + { + label: 'Portuguese', + value: 'pt', + }, + { + label: 'Dutch', + value: 'nl', + }, + { + label: 'Afrikaans', + value: 'af', + }, + { + label: 'Albanian', + value: 'sq', + }, + { + label: 'Amharic', + value: 'am', + }, + { + label: 'Arabic', + value: 'ar', + }, + { + label: 'Armenian', + value: 'hy', + }, + { + label: 'Assamese', + value: 'as', + }, + { + label: 'Azerbaijani', + value: 'az', + }, + { + label: 'Bashkir', + value: 'ba', + }, + { + label: 'Basque', + value: 'eu', + }, + { + label: 'Belarusian', + value: 'be', + }, + { + label: 'Bengali', + value: 'bn', + }, + { + label: 'Bosnian', + value: 'bs', + }, + { + label: 'Breton', + value: 'br', + }, + { + label: 'Bulgarian', + value: 'bg', + }, + { + label: 'Burmese', + value: 'my', + }, + { + label: 'Catalan', + value: 'ca', + }, + { + label: 'Chinese', + value: 'zh', + }, + { + label: 'Croatian', + value: 'hr', + }, + { + label: 'Czech', + value: 'cs', + }, + { + label: 'Danish', + value: 'da', + }, + { + label: 'Estonian', + value: 'et', + }, + { + label: 'Faroese', + value: 'fo', + }, + { + label: 'Finnish', + value: 'fi', + }, + { + label: 'Galician', + value: 'gl', + }, + { + label: 'Georgian', + value: 'ka', + }, + { + label: 'Greek', + value: 'el', + }, + { + label: 'Gujarati', + value: 'gu', + }, + { + label: 'Haitian', + value: 'ht', + }, + { + label: 'Hausa', + value: 'ha', + }, + { + label: 'Hawaiian', + value: 'haw', + }, + { + label: 'Hebrew', + value: 'he', + }, + { + label: 'Hindi', + value: 'hi', + }, + { + label: 'Hungarian', + value: 'hu', + }, + { + label: 'Icelandic', + value: 'is', + }, + { + label: 'Indonesian', + value: 'id', + }, + { + label: 'Japanese', + value: 'ja', + }, + { + label: 'Javanese', + value: 'jw', + }, + { + label: 'Kannada', + value: 'kn', + }, + { + label: 'Kazakh', + value: 'kk', + }, + { + label: 'Khmer', + value: 'km', + }, + { + label: 'Korean', + value: 'ko', + }, + { + label: 'Lao', + value: 'lo', + }, + { + label: 'Latin', + value: 'la', + }, + { + label: 'Latvian', + value: 'lv', + }, + { + label: 'Lingala', + value: 'ln', + }, + { + label: 'Lithuanian', + value: 'lt', + }, + { + label: 'Luxembourgish', + value: 'lb', + }, + { + label: 'Macedonian', + value: 'mk', + }, + { + label: 'Malagasy', + value: 'mg', + }, + { + label: 'Malay', + value: 'ms', + }, + { + label: 'Malayalam', + value: 'ml', + }, + { + label: 'Maltese', + value: 'mt', + }, + { + label: 'Maori', + value: 'mi', + }, + { + label: 'Marathi', + value: 'mr', + }, + { + label: 'Mongolian', + value: 'mn', + }, + { + label: 'Nepali', + value: 'ne', + }, + { + label: 'Norwegian', + value: 'no', + }, + { + label: 'Norwegian Nynorsk', + value: 'nn', + }, + { + label: 'Occitan', + value: 'oc', + }, + { + label: 'Panjabi', + value: 'pa', + }, + { + label: 'Pashto', + value: 'ps', + }, + { + label: 'Persian', + value: 'fa', + }, + { + label: 'Polish', + value: 'pl', + }, + { + label: 'Romanian', + value: 'ro', + }, + { + label: 'Russian', + value: 'ru', + }, + { + label: 'Sanskrit', + value: 'sa', + }, + { + label: 'Serbian', + value: 'sr', + }, + { + label: 'Shona', + value: 'sn', + }, + { + label: 'Sindhi', + value: 'sd', + }, + { + label: 'Sinhala', + value: 'si', + }, + { + label: 'Slovak', + value: 'sk', + }, + { + label: 'Slovenian', + value: 'sl', + }, + { + label: 'Somali', + value: 'so', + }, + { + label: 'Sundanese', + value: 'su', + }, + { + label: 'Swahili', + value: 'sw', + }, + { + label: 'Swedish', + value: 'sv', + }, + { + label: 'Tagalog', + value: 'tl', + }, + { + label: 'Tajik', + value: 'tg', + }, + { + label: 'Tamil', + value: 'ta', + }, + { + label: 'Tatar', + value: 'tt', + }, + { + label: 'Telugu', + value: 'te', + }, + { + label: 'Thai', + value: 'th', + }, + { + label: 'Tibetan', + value: 'bo', + }, + { + label: 'Turkish', + value: 'tr', + }, + { + label: 'Turkmen', + value: 'tk', + }, + { + label: 'Ukrainian', + value: 'uk', + }, + { + label: 'Urdu', + value: 'ur', + }, + { + label: 'Uzbek', + value: 'uz', + }, + { + label: 'Vietnamese', + value: 'vi', + }, + { + label: 'Welsh', + value: 'cy', + }, + { + label: 'Yiddish', + value: 'yi', + }, + { + label: 'Yoruba', + value: 'yo', + }, + ], + }, + }), + language_detection: Property.Checkbox({ + displayName: 'Language Detection', + required: false, + description: `Enable [Automatic language detection](https://www.assemblyai.com/docs/models/speech-recognition#automatic-language-detection), either true or false.`, + defaultValue: false, + }), + language_confidence_threshold: Property.Number({ + displayName: 'Language Confidence Threshold', + required: false, + description: + 'The confidence threshold for the automatically detected language.\nAn error will be returned if the language confidence is below this threshold.\nDefaults to 0.\n', + }), + speech_model: Property.StaticDropdown({ + displayName: 'Speech Model', + required: false, + description: + 'The speech model to use for the transcription. When `null`, the "best" model is used.', + options: { + options: [ + { + label: 'Best', + value: 'best', + }, + { + label: 'Nano', + value: 'nano', + }, + ], + }, + }), + punctuate: Property.Checkbox({ + displayName: 'Punctuate', + required: false, + description: 'Enable Automatic Punctuation, can be true or false', + defaultValue: true, + }), + format_text: Property.Checkbox({ + displayName: 'Format Text', + required: false, + description: 'Enable Text Formatting, can be true or false', + defaultValue: true, + }), + disfluencies: Property.Checkbox({ + displayName: 'Disfluencies', + required: false, + description: + 'Transcribe Filler Words, like "umm", in your media file; can be true or false', + defaultValue: false, + }), + dual_channel: Property.Checkbox({ + displayName: 'Dual Channel', + required: false, + description: `Enable [Dual Channel](https://www.assemblyai.com/docs/models/speech-recognition#dual-channel-transcription) transcription, can be true or false.`, + defaultValue: false, + }), + webhook_url: Property.ShortText({ + displayName: 'Webhook URL', + required: false, + description: + 'The URL to which we send webhook requests.\nWe sends two different types of webhook requests.\nOne request when a transcript is completed or failed, and one request when the redacted audio is ready if redact_pii_audio is enabled.\n', + }), + webhook_auth_header_name: Property.ShortText({ + displayName: 'Webhook Auth Header Name', + required: false, + description: + 'The header name to be sent with the transcript completed or failed webhook requests', + }), + webhook_auth_header_value: Property.ShortText({ + displayName: 'Webhook Auth Header Value', + required: false, + description: + 'The header value to send back with the transcript completed or failed webhook requests for added security', + }), + auto_highlights: Property.Checkbox({ + displayName: 'Key Phrases', + required: false, + description: 'Enable Key Phrases, either true or false', + defaultValue: false, + }), + audio_start_from: Property.Number({ + displayName: 'Audio Start From', + required: false, + description: + 'The point in time, in milliseconds, to begin transcribing in your media file', + }), + audio_end_at: Property.Number({ + displayName: 'Audio End At', + required: false, + description: + 'The point in time, in milliseconds, to stop transcribing in your media file', + }), + word_boost: Property.Array({ + displayName: 'Word Boost', + required: false, + description: + 'The list of custom vocabulary to boost transcription probability for', + }), + boost_param: Property.StaticDropdown({ + displayName: 'Word Boost Level', + required: false, + description: 'How much to boost specified words', + options: { + options: [ + { + label: 'Low', + value: 'low', + }, + { + label: 'Default', + value: 'default', + }, + { + label: 'High', + value: 'high', + }, + ], + }, + }), + filter_profanity: Property.Checkbox({ + displayName: 'Filter Profanity', + required: false, + description: + 'Filter profanity from the transcribed text, can be true or false', + defaultValue: false, + }), + redact_pii: Property.Checkbox({ + displayName: 'Redact PII', + required: false, + description: + 'Redact PII from the transcribed text using the Redact PII model, can be true or false', + defaultValue: false, + }), + redact_pii_audio: Property.Checkbox({ + displayName: 'Redact PII Audio', + required: false, + description: `Generate a copy of the original media file with spoken PII "beeped" out, can be true or false. See [PII redaction](https://www.assemblyai.com/docs/models/pii-redaction) for more details.`, + defaultValue: false, + }), + redact_pii_audio_quality: Property.StaticDropdown({ + displayName: 'Redact PII Audio Quality', + required: false, + description: `Controls the filetype of the audio created by redact_pii_audio. Currently supports mp3 (default) and wav. See [PII redaction](https://www.assemblyai.com/docs/models/pii-redaction) for more details.`, + options: { + options: [ + { + label: 'MP3', + value: 'mp3', + }, + { + label: 'WAV', + value: 'wav', + }, + ], + }, + }), + redact_pii_policies: Property.StaticMultiSelectDropdown({ + displayName: 'Redact PII Policies', + required: false, + description: `The list of PII Redaction policies to enable. See [PII redaction](https://www.assemblyai.com/docs/models/pii-redaction) for more details.`, + options: { + options: [ + { + label: 'Account Number', + value: 'account_number', + }, + { + label: 'Banking Information', + value: 'banking_information', + }, + { + label: 'Blood Type', + value: 'blood_type', + }, + { + label: 'Credit Card CVV', + value: 'credit_card_cvv', + }, + { + label: 'Credit Card Expiration', + value: 'credit_card_expiration', + }, + { + label: 'Credit Card Number', + value: 'credit_card_number', + }, + { + label: 'Date', + value: 'date', + }, + { + label: 'Date Interval', + value: 'date_interval', + }, + { + label: 'Date of Birth', + value: 'date_of_birth', + }, + { + label: "Driver's License", + value: 'drivers_license', + }, + { + label: 'Drug', + value: 'drug', + }, + { + label: 'Duration', + value: 'duration', + }, + { + label: 'Email Address', + value: 'email_address', + }, + { + label: 'Event', + value: 'event', + }, + { + label: 'Filename', + value: 'filename', + }, + { + label: 'Gender Sexuality', + value: 'gender_sexuality', + }, + { + label: 'Healthcare Number', + value: 'healthcare_number', + }, + { + label: 'Injury', + value: 'injury', + }, + { + label: 'IP Address', + value: 'ip_address', + }, + { + label: 'Language', + value: 'language', + }, + { + label: 'Location', + value: 'location', + }, + { + label: 'Marital Status', + value: 'marital_status', + }, + { + label: 'Medical Condition', + value: 'medical_condition', + }, + { + label: 'Medical Process', + value: 'medical_process', + }, + { + label: 'Money Amount', + value: 'money_amount', + }, + { + label: 'Nationality', + value: 'nationality', + }, + { + label: 'Number Sequence', + value: 'number_sequence', + }, + { + label: 'Occupation', + value: 'occupation', + }, + { + label: 'Organization', + value: 'organization', + }, + { + label: 'Passport Number', + value: 'passport_number', + }, + { + label: 'Password', + value: 'password', + }, + { + label: 'Person Age', + value: 'person_age', + }, + { + label: 'Person Name', + value: 'person_name', + }, + { + label: 'Phone Number', + value: 'phone_number', + }, + { + label: 'Physical Attribute', + value: 'physical_attribute', + }, + { + label: 'Political Affiliation', + value: 'political_affiliation', + }, + { + label: 'Religion', + value: 'religion', + }, + { + label: 'Statistics', + value: 'statistics', + }, + { + label: 'Time', + value: 'time', + }, + { + label: 'URL', + value: 'url', + }, + { + label: 'US Social Security Number', + value: 'us_social_security_number', + }, + { + label: 'Username', + value: 'username', + }, + { + label: 'Vehicle ID', + value: 'vehicle_id', + }, + { + label: 'Zodiac Sign', + value: 'zodiac_sign', + }, + ], + }, + }), + redact_pii_sub: Property.StaticDropdown({ + displayName: 'Redact PII Substitution', + required: false, + description: `The replacement logic for detected PII, can be "entity_type" or "hash". See [PII redaction](https://www.assemblyai.com/docs/models/pii-redaction) for more details.`, + options: { + options: [ + { + label: 'Entity Name', + value: 'entity_name', + }, + { + label: 'Hash', + value: 'hash', + }, + ], + }, + }), + speaker_labels: Property.Checkbox({ + displayName: 'Speaker Labels', + required: false, + description: `Enable [Speaker diarization](https://www.assemblyai.com/docs/models/speaker-diarization), can be true or false`, + defaultValue: false, + }), + speakers_expected: Property.Number({ + displayName: 'Speakers Expected', + required: false, + description: `Tells the speaker label model how many speakers it should attempt to identify, up to 10. See [Speaker diarization](https://www.assemblyai.com/docs/models/speaker-diarization) for more details.`, + }), + content_safety: Property.Checkbox({ + displayName: 'Content Moderation', + required: false, + description: `Enable [Content Moderation](https://www.assemblyai.com/docs/models/content-moderation), can be true or false`, + defaultValue: false, + }), + content_safety_confidence: Property.Number({ + displayName: 'Content Moderation Confidence', + required: false, + description: + 'The confidence threshold for the Content Moderation model. Values must be between 25 and 100.', + }), + iab_categories: Property.Checkbox({ + displayName: 'Topic Detection', + required: false, + description: `Enable [Topic Detection](https://www.assemblyai.com/docs/models/topic-detection), can be true or false`, + defaultValue: false, + }), + custom_spelling: Property.Json({ + displayName: 'Custom Spellings', + required: false, + description: + 'Customize how words are spelled and formatted using to and from values.\nUse a JSON array of objects of the following format:\n```\n[\n {\n "from": ["original", "spelling"],\n "to": "corrected"\n }\n]\n```\n', + }), + sentiment_analysis: Property.Checkbox({ + displayName: 'Sentiment Analysis', + required: false, + description: `Enable [Sentiment Analysis](https://www.assemblyai.com/docs/models/sentiment-analysis), can be true or false`, + defaultValue: false, + }), + auto_chapters: Property.Checkbox({ + displayName: 'Auto Chapters', + required: false, + description: `Enable [Auto Chapters](https://www.assemblyai.com/docs/models/auto-chapters), can be true or false`, + defaultValue: false, + }), + entity_detection: Property.Checkbox({ + displayName: 'Entity Detection', + required: false, + description: `Enable [Entity Detection](https://www.assemblyai.com/docs/models/entity-detection), can be true or false`, + defaultValue: false, + }), + speech_threshold: Property.Number({ + displayName: 'Speech Threshold', + required: false, + description: + 'Reject audio files that contain less than this fraction of speech.\nValid values are in the range [0, 1] inclusive.\n', + }), + summarization: Property.Checkbox({ + displayName: 'Enable Summarization', + required: false, + description: `Enable [Summarization](https://www.assemblyai.com/docs/models/summarization), can be true or false`, + defaultValue: false, + }), + summary_model: Property.StaticDropdown({ + displayName: 'Summary Model', + required: false, + description: 'The model to summarize the transcript', + options: { + options: [ + { + label: 'Informative', + value: 'informative', + }, + { + label: 'Conversational', + value: 'conversational', + }, + { + label: 'Catchy', + value: 'catchy', + }, + ], + }, + }), + summary_type: Property.StaticDropdown({ + displayName: 'Summary Type', + required: false, + description: 'The type of summary', + options: { + options: [ + { + label: 'Bullets', + value: 'bullets', + }, + { + label: 'Bullets Verbose', + value: 'bullets_verbose', + }, + { + label: 'Gist', + value: 'gist', + }, + { + label: 'Headline', + value: 'headline', + }, + { + label: 'Paragraph', + value: 'paragraph', + }, + ], + }, + }), + custom_topics: Property.Checkbox({ + displayName: 'Enable Custom Topics', + required: false, + description: 'Enable custom topics, either true or false', + defaultValue: false, + }), + topics: Property.Array({ + displayName: 'Custom Topics', + required: false, + description: 'The list of custom topics', + }), +}; diff --git a/packages/pieces/community/assemblyai/tsconfig.json b/packages/pieces/community/assemblyai/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/assemblyai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/assemblyai/tsconfig.lib.json b/packages/pieces/community/assemblyai/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/assemblyai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/attio/.eslintrc.json b/packages/pieces/community/attio/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/attio/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/attio/README.md b/packages/pieces/community/attio/README.md new file mode 100644 index 0000000..c99264f --- /dev/null +++ b/packages/pieces/community/attio/README.md @@ -0,0 +1,7 @@ +# pieces-attio + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-attio` to build the library. diff --git a/packages/pieces/community/attio/package.json b/packages/pieces/community/attio/package.json new file mode 100644 index 0000000..425555b --- /dev/null +++ b/packages/pieces/community/attio/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-attio", + "version": "0.0.1" +} diff --git a/packages/pieces/community/attio/project.json b/packages/pieces/community/attio/project.json new file mode 100644 index 0000000..202b54f --- /dev/null +++ b/packages/pieces/community/attio/project.json @@ -0,0 +1,45 @@ +{ + "name": "pieces-attio", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/attio/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/attio", + "tsConfig": "packages/pieces/community/attio/tsconfig.lib.json", + "packageJson": "packages/pieces/community/attio/package.json", + "main": "packages/pieces/community/attio/src/index.ts", + "assets": [ + "packages/pieces/community/attio/*.md" + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/attio/src/index.ts b/packages/pieces/community/attio/src/index.ts new file mode 100644 index 0000000..21a9057 --- /dev/null +++ b/packages/pieces/community/attio/src/index.ts @@ -0,0 +1,67 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; + +// Import actions +import { createRecordAction } from './lib/actions/create-record'; +import { updateRecordAction } from './lib/actions/update-record'; +import { findRecordAction } from './lib/actions/find-record'; +import { createEntryAction } from './lib/actions/create-entry'; +import { updateEntryAction } from './lib/actions/update-entry'; +import { findListEntryAction } from './lib/actions/find-list-entry'; + +// Import triggers +import { recordCreatedTrigger } from './lib/triggers/record-created'; +import { recordUpdatedTrigger } from './lib/triggers/record-updated'; +import { listEntryCreatedTrigger } from './lib/triggers/list-entry-created'; +import { listEntryUpdatedTrigger } from './lib/triggers/list-entry-updated'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { BASE_URL } from './lib/common/client'; + +const markdownDescription = ` +To use Attio, you need to generate an Access Token: +1. Login to your Attio account at https://app.attio.com. +2. From the dropdown beside your workspace name, click Workspace settings. +3. Click the Developers tab. +4. Click on the "New Access Token" button. +5. Set the appropriate Scopes for the integration. +6. Copy the generated Access Token. +`; + +export const attioAuth = PieceAuth.SecretText({ + displayName: 'Access Token', + description: markdownDescription, + required: true, +}); + +export const attio = createPiece({ + displayName: 'Attio', + description: 'Modern, collaborative CRM platform built to be fully customizable and real-time.', + auth: attioAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/attio.png', + categories: [PieceCategory.SALES_AND_CRM], + authors: ['AnkitSharmaOnGithub', 'kishanprmr'], + actions: [ + createRecordAction, + updateRecordAction, + findRecordAction, + createEntryAction, + updateEntryAction, + findListEntryAction, + createCustomApiCallAction({ + auth: attioAuth, + baseUrl: () => BASE_URL, + authMapping: async (auth) => { + return { + Authorization: `Bearer ${auth}`, + }; + }, + }), + ], + triggers: [ + recordCreatedTrigger, + recordUpdatedTrigger, + listEntryCreatedTrigger, + listEntryUpdatedTrigger, + ], +}); diff --git a/packages/pieces/community/attio/src/lib/actions/create-entry.ts b/packages/pieces/community/attio/src/lib/actions/create-entry.ts new file mode 100644 index 0000000..8fade17 --- /dev/null +++ b/packages/pieces/community/attio/src/lib/actions/create-entry.ts @@ -0,0 +1,60 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { attioAuth } from '../../index'; +import { attioApiCall } from '../common/client'; +import { + formatInputFields, + listFields, + listIdDropdown, + listParentObjectIdDropdown, +} from '../common/props'; + +export const createEntryAction = createAction({ + name: 'create_entry', + displayName: 'Create List Entry', + description: 'Add a record to a specified list.', + auth: attioAuth, + props: { + listId: listIdDropdown({ + displayName: 'List', + required: true, + }), + parentObjectId: listParentObjectIdDropdown({ + displayName: 'Parent Object', + required: true, + }), + parentRecordId: Property.ShortText({ + displayName: 'Parent Record ID', + required: true, + }), + + attributes: listFields(), + }, + async run(context) { + const accessToken = context.auth; + const { listId, parentObjectId, parentRecordId } = context.propsValue; + const inputFields = context.propsValue.attributes ?? {}; + + if (!listId) { + throw new Error('Provided list type is invalid.'); + } + + const formattedFields = await formatInputFields(accessToken, 'lists', listId, inputFields); + + // https://docs.attio.com/rest-api/endpoint-reference/entries/create-an-entry-add-record-to-list + const response = await attioApiCall<{ data: Record }>({ + method: HttpMethod.POST, + accessToken, + resourceUri: `/lists/${listId}/entries`, + body: { + data: { + parent_record_id: parentRecordId, + parent_object: parentObjectId, + entry_values: formattedFields, + }, + }, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/attio/src/lib/actions/create-record.ts b/packages/pieces/community/attio/src/lib/actions/create-record.ts new file mode 100644 index 0000000..ef77915 --- /dev/null +++ b/packages/pieces/community/attio/src/lib/actions/create-record.ts @@ -0,0 +1,44 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { attioAuth } from '../../index'; +import { attioApiCall } from '../common/client'; +import { formatInputFields, objectFields, objectTypeIdDropdown } from '../common/props'; + +export const createRecordAction = createAction({ + name: 'create_record', + displayName: 'Create Record', + description: 'Creates a new record such as peron,company or deal.', + auth: attioAuth, + props: { + objectTypeId: objectTypeIdDropdown({ + displayName: 'Object', + required: true, + }), + attributes: objectFields(), + }, + async run(context) { + const accessToken = context.auth; + const objectTypeId = context.propsValue.objectTypeId; + const inputFields = context.propsValue.attributes ?? {}; + + if (!objectTypeId) { + throw new Error('Provided object type is invalid.'); + } + + const formattedFields = await formatInputFields(accessToken,'objects', objectTypeId, inputFields); + + // https://docs.attio.com/rest-api/endpoint-reference/records/create-a-record + const response = await attioApiCall<{data:Record}>({ + method: HttpMethod.POST, + accessToken, + resourceUri: `/objects/${objectTypeId}/records`, + body: { + data: { + values: formattedFields, + }, + }, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/attio/src/lib/actions/find-list-entry.ts b/packages/pieces/community/attio/src/lib/actions/find-list-entry.ts new file mode 100644 index 0000000..8f60fd4 --- /dev/null +++ b/packages/pieces/community/attio/src/lib/actions/find-list-entry.ts @@ -0,0 +1,46 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { attioAuth } from '../../index'; +import { attioPaginatedApiCall } from '../common/client'; +import { formatInputFields, listFields, listIdDropdown } from '../common/props'; + +export const findListEntryAction = createAction({ + name: 'find_list_entry', + displayName: 'Find List Entry', + description: + 'Search for entries in a specific list in Attio using filters and return matching results.', + auth: attioAuth, + props: { + listId: listIdDropdown({ + displayName: 'List', + required: true, + }), + attributes: listFields(true), + }, + async run(context) { + const accessToken = context.auth; + const { listId } = context.propsValue; + const inputFields = context.propsValue.attributes ?? {}; + + if (!listId) { + throw new Error('Provided list type is invalid.'); + } + + const formattedFields = await formatInputFields(accessToken, 'lists', listId, inputFields); + + // https://docs.attio.com/rest-api/endpoint-reference/entries/create-an-entry-add-record-to-list + const response = await attioPaginatedApiCall({ + method: HttpMethod.POST, + accessToken, + resourceUri: `/lists/${listId}/entries/query`, + body: { + filter: formattedFields, + }, + }); + + return { + found: response.length > 0, + result: response, + }; + }, +}); diff --git a/packages/pieces/community/attio/src/lib/actions/find-record.ts b/packages/pieces/community/attio/src/lib/actions/find-record.ts new file mode 100644 index 0000000..9a16a80 --- /dev/null +++ b/packages/pieces/community/attio/src/lib/actions/find-record.ts @@ -0,0 +1,49 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { attioAuth } from '../../index'; +import { formatInputFields, objectFields, objectTypeIdDropdown } from '../common/props'; +import { attioPaginatedApiCall } from '../common/client'; + +export const findRecordAction = createAction({ + name: 'find_record', + displayName: 'Find Record', + description: 'Search for records in Attio using filters and return matching results.', + auth: attioAuth, + props: { + objectTypeId: objectTypeIdDropdown({ + displayName: 'Object', + required: true, + }), + attributes: objectFields(true), + }, + async run(context) { + const accessToken = context.auth; + const objectTypeId = context.propsValue.objectTypeId; + const inputFields = context.propsValue.attributes ?? {}; + + if (!objectTypeId) { + throw new Error('Provided object type is invalid.'); + } + const formattedFields = await formatInputFields( + accessToken, + 'objects', + objectTypeId, + inputFields, + ); + + // https://docs.attio.com/rest-api/endpoint-reference/records/list-records + const response = await attioPaginatedApiCall({ + method: HttpMethod.POST, + accessToken, + resourceUri: `/objects/${objectTypeId}/records/query`, + body: { + filter: formattedFields, + }, + }); + + return { + found: response.length > 0, + result: response, + }; + }, +}); diff --git a/packages/pieces/community/attio/src/lib/actions/update-entry.ts b/packages/pieces/community/attio/src/lib/actions/update-entry.ts new file mode 100644 index 0000000..c0ff815 --- /dev/null +++ b/packages/pieces/community/attio/src/lib/actions/update-entry.ts @@ -0,0 +1,49 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { attioAuth } from '../../index'; +import { attioApiCall } from '../common/client'; +import { formatInputFields, listFields, listIdDropdown } from '../common/props'; + +export const updateEntryAction = createAction({ + name: 'update_entry', + displayName: 'Update List Entry', + description: 'Update the attributes of an existing entry in a list.', + auth: attioAuth, + props: { + listId: listIdDropdown({ + displayName: 'List', + required: true, + }), + entryId: Property.ShortText({ + displayName: 'Entry ID', + description: 'The unique identifier of the entry to update.', + required: true, + }), + attributes: listFields(true), + }, + async run(context) { + const accessToken = context.auth; + const { listId, entryId } = context.propsValue; + const inputFields = context.propsValue.attributes ?? {}; + + if (!listId) { + throw new Error('Provided list type is invalid.'); + } + + const formattedFields = await formatInputFields(accessToken, 'lists', listId, inputFields); + + // https://docs.attio.com/rest-api/endpoint-reference/entries/update-a-list-entry-append-multiselect-values + const response = await attioApiCall<{data:Record}>({ + method: HttpMethod.PATCH, + accessToken, + resourceUri: `/lists/${listId}/entries/${entryId}`, + body: { + data: { + entry_values: formattedFields, + }, + }, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/attio/src/lib/actions/update-record.ts b/packages/pieces/community/attio/src/lib/actions/update-record.ts new file mode 100644 index 0000000..1acbc30 --- /dev/null +++ b/packages/pieces/community/attio/src/lib/actions/update-record.ts @@ -0,0 +1,50 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { attioAuth } from '../../index'; +import { attioApiCall } from '../common/client'; +import { formatInputFields, objectFields, objectTypeIdDropdown } from '../common/props'; + +export const updateRecordAction = createAction({ + name: 'update_record', + displayName: 'Update Record', + description: 'Update an existing record with new attribute values.', + auth: attioAuth, + props: { + objectTypeId: objectTypeIdDropdown({ + displayName: 'Object', + required: true, + }), + recordId: Property.ShortText({ + displayName: 'Record ID', + description: 'The unique identifier of the record to update.', + required: true, + }), + + attributes: objectFields(true), + }, + async run(context) { + const accessToken = context.auth; + const recordId = context.propsValue.recordId; + const objectTypeId = context.propsValue.objectTypeId; + const inputFields = context.propsValue.attributes ?? {}; + + if (!objectTypeId) { + throw new Error('Provided object type is invalid.'); + } + const formattedFields = await formatInputFields(accessToken,'objects', objectTypeId, inputFields); + + // https://docs.attio.com/rest-api/endpoint-reference/records/update-a-record-append-multiselect-values + const response = await attioApiCall<{data:Record}>({ + method: HttpMethod.PATCH, + accessToken, + resourceUri: `/objects/${objectTypeId}/records/${recordId}`, + body: { + data: { + values: formattedFields, + }, + }, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/attio/src/lib/common/client.ts b/packages/pieces/community/attio/src/lib/common/client.ts new file mode 100644 index 0000000..2ac9e90 --- /dev/null +++ b/packages/pieces/community/attio/src/lib/common/client.ts @@ -0,0 +1,116 @@ +import { + AuthenticationType, + httpClient, + HttpMessageBody, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; +import crypto from 'crypto'; + +export const BASE_URL = 'https://api.attio.com/v2'; + +export type AttioApiCallParams = { + accessToken: string; + method: HttpMethod; + resourceUri: string; + query?: Record; + body?: any; +}; + +export async function attioApiCall({ + accessToken, + method, + resourceUri, + query, + body, +}: AttioApiCallParams): Promise { + const qs: QueryParams = {}; + + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + qs[key] = String(value); + } + } + } + + const request: HttpRequest = { + method, + url: BASE_URL + resourceUri, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + queryParams: qs, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} + +export async function attioPaginatedApiCall({ + accessToken, + method, + resourceUri, + query, + body, +}: AttioApiCallParams): Promise { + const limit = 500; + let offset = 0; + + const resultData: T[] = []; + let hasMoreItems = true; + + do { + const qs = { + ...(query || {}), + limit, + offset, + }; + + const response = await attioApiCall<{ data: T[] }>({ + accessToken, + method, + resourceUri, + query: qs, + body, + }); + + if (!response?.data || response.data.length === 0) { + break; + } + + resultData.push(...response.data); + + // If fewer than 'limit' items returned, we've reached the last page + hasMoreItems = response.data.length === limit; + offset += limit; + } while (hasMoreItems); + + return resultData; +} + +export function verifyWebhookSignature( + webhookSecret?: string, + webhookSignatureHeader?: string, + webhookRawBody?: any, +): boolean { + if (!webhookSecret || !webhookSignatureHeader || !webhookRawBody) { + return false; + } + + try { + const hmac = crypto.createHmac('sha256', webhookSecret); + hmac.update(webhookRawBody); + const expectedSignature = hmac.digest('hex'); + + return crypto.timingSafeEqual( + Buffer.from(webhookSignatureHeader, 'hex'), + Buffer.from(expectedSignature, 'hex'), + ); + } catch (error) { + return false; + } +} diff --git a/packages/pieces/community/attio/src/lib/common/props.ts b/packages/pieces/community/attio/src/lib/common/props.ts new file mode 100644 index 0000000..64d657f --- /dev/null +++ b/packages/pieces/community/attio/src/lib/common/props.ts @@ -0,0 +1,306 @@ +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { attioApiCall, attioPaginatedApiCall } from './client'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { AttributeResponse, ListResponse, ObjectResponse, SelectOptionResponse } from './types'; +import { isNil } from '@activepieces/shared'; + +interface DropdownParams { + displayName: string; + description?: string; + required: boolean; +} + +export const objectTypeIdDropdown = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + description: params.description, + required: params.required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + const response = await attioApiCall<{ data: Array }>({ + accessToken: auth as string, + method: HttpMethod.GET, + resourceUri: '/objects', + }); + + return { + disabled: false, + options: response.data.map((obj) => ({ + label: obj.singular_noun, + value: obj.id.object_id, + })), + }; + }, + }); + +export const listIdDropdown = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + description: params.description, + required: params.required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + const response = await attioApiCall<{ data: Array }>({ + accessToken: auth as string, + method: HttpMethod.GET, + resourceUri: '/lists', + }); + + return { + disabled: false, + options: response.data.map((obj) => ({ + label: obj.name, + value: obj.id.list_id, + })), + }; + }, + }); + +export const listParentObjectIdDropdown = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + description: params.description, + required: params.required, + refreshers: ['listId'], + options: async ({ auth, listId }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + if (!listId) { + return { + disabled: true, + options: [], + placeholder: 'Please select list first.', + }; + } + + const response = await attioApiCall<{ data: ListResponse }>({ + accessToken: auth as string, + method: HttpMethod.GET, + resourceUri: `/lists/${listId}`, + }); + + return { + disabled: false, + options: [{ label: response.data.parent_object[0], value: response.data.parent_object[0] }], + }; + }, + }); + +async function createPropertyDefinition( + property: AttributeResponse, + objectType:'lists'|'objects', + objectTypeId: string, + accessToken: string, + isSearch=false +) { + const { api_slug, title, is_required, type, is_multiselect } = property; + const required = isSearch ? false : is_required + + switch (type) { + case 'text': + case 'currency': + case 'location': + case 'phone-number': + case 'personal-name': + return Property.ShortText({ + displayName: title, + required, + description: + type === 'personal-name' + ? 'Provide comma-separated format name i.e. Last name(s), First name(s).' + : '', + }); + case 'number': + case 'rating': + return Property.Number({ + displayName: title, + required, + }); + case 'checkbox': + return Property.Checkbox({ + displayName: title, + required, + }); + case 'date': + case 'timestamp': + return Property.DateTime({ + displayName: title, + required, + }); + case 'actor-reference': + case 'email-address': + case 'domain': { + const basicField = is_multiselect ? Property.Array : Property.ShortText; + return basicField({ + displayName: title, + required, + }); + } + case 'status': { + const response = await attioApiCall<{ data: SelectOptionResponse[] }>({ + method: HttpMethod.GET, + accessToken: accessToken, + resourceUri: `/${objectType}/${objectTypeId}/attributes/${api_slug}/statuses`, + }); + + return Property.StaticDropdown({ + displayName: title, + required, + options: { + disabled: false, + options: response.data.map((opt) => ({ + label: opt.title, + value: opt.title, + })), + }, + }); + } + case 'select': { + const response = await attioApiCall<{ data: SelectOptionResponse[] }>({ + method: HttpMethod.GET, + accessToken: accessToken, + resourceUri: `/${objectType}/${objectTypeId}/attributes/${api_slug}/options`, + }); + + const dropdownType = is_multiselect + ? Property.StaticMultiSelectDropdown + : Property.StaticDropdown; + + return dropdownType({ + displayName: title, + required, + options: { + disabled: false, + options: response.data.map((opt) => ({ + label: opt.title, + value: opt.title, + })), + }, + }); + } + default: + return null; + } +} + +export const objectFields =(isSearch=false)=> Property.DynamicProperties({ + displayName: 'Object Attributes', + refreshers: ['objectTypeId'], + required: false, + props: async ({ auth, objectTypeId }) => { + if (!auth || !objectTypeId) return {}; + + const accessToken = auth as unknown as string; + const objectId = objectTypeId as unknown as string; + const props: DynamicPropsValue = {}; + + const response = await attioPaginatedApiCall({ + method: HttpMethod.GET, + accessToken: accessToken, + resourceUri: `/objects/${objectId}/attributes`, + }); + + for (const attribute of response) { + if (!attribute.is_writable) continue; + + const { api_slug } = attribute; + + props[api_slug] =await createPropertyDefinition(attribute,'objects', objectId, accessToken,isSearch); + } + + return Object.fromEntries(Object.entries(props).filter(([_, prop]) => prop !== null)); + }, +}); + +export const listFields =(isSearch=false)=> Property.DynamicProperties({ + displayName: 'List Attributes', + refreshers: ['listId'], + required: false, + props: async ({ auth, listId }) => { + if (!auth || !listId) return {}; + + const accessToken = auth as unknown as string; + const list_id = listId as unknown as string; + const props: DynamicPropsValue = {}; + + const response = await attioPaginatedApiCall({ + method: HttpMethod.GET, + accessToken: accessToken, + resourceUri: `/lists/${list_id}/attributes`, + }); + + for (const attribute of response) { + if (!attribute.is_writable) continue; + + const { api_slug } = attribute; + + props[api_slug] =await createPropertyDefinition(attribute,'lists', list_id, accessToken,isSearch); + } + + return Object.fromEntries(Object.entries(props).filter(([_, prop]) => prop !== null)); + }, +}); + +export async function formatInputFields( + accessToken: string, + objectType:'lists'|'objects', + objectId: string, + inputValues: Record, +) { + const attributes = await attioPaginatedApiCall({ + method: HttpMethod.GET, + accessToken: accessToken, + resourceUri: `/${objectType}/${objectId}/attributes`, + }); + + const typeMapping = attributes.reduce((acc, { api_slug, type }) => { + acc[api_slug] = type; + return acc; + }, {} as Record); + + const formattedFields: Record = {}; + + for (const [key, value] of Object.entries(inputValues)) { + if (isNil(value) || value === '') continue; + if(Array.isArray(value) && value.length === 0) continue; + + const fieldType = typeMapping[key]; + + switch (fieldType) { + case 'phone-number': + formattedFields[key] = [value]; + break; + case 'domain': + case 'select': + formattedFields[key] = typeof value === 'string' ? [value] : value; + break; + default: + formattedFields[key] = value; + break; + } + } + + return formattedFields; +} diff --git a/packages/pieces/community/attio/src/lib/common/types.ts b/packages/pieces/community/attio/src/lib/common/types.ts new file mode 100644 index 0000000..6af2afb --- /dev/null +++ b/packages/pieces/community/attio/src/lib/common/types.ts @@ -0,0 +1,76 @@ +export interface ObjectResponse { + id: { + workspace_id: string; + object_id: string; + }; + api_slug: string; + singular_noun: string; + plural_noun: string; + created_at: string; +} + +export interface ListResponse { + id: { + workspace_id: string; + list_id: string; + }; + api_slug: string; + created_at: string; + name: string; + + parent_object: string[]; +} + +export interface AttributeResponse { + title: string; + description: string; + api_slug: string; + type: string; + is_system_attribute: boolean; + is_writable: boolean; + is_required: boolean; + is_unique: boolean; + is_multiselect: boolean; + is_default_value_enabled: boolean; + is_archived: boolean; + created_at: string; +} + +export interface SelectOptionResponse { + title: string; + is_archived: boolean; +} + +export interface WebhookResponse { + secret: string; + id: { + workspace_id: string; + webhook_id: string; + }; +} + +export interface ObjectWebhookPayload { + webhook_id: string; + events: Array<{ + event_type: string; + id: { + workspace_id: string; + object_id: string; + record_id: string; + }; + }>; +} + +export interface ListWebhookPayload { + webhook_id: string; + events: Array<{ + event_type: string; + id: { + workspace_id: string; + list_id: string; + entry_id: string; + }; + parent_object_id: string; + parent_record_id: string; + }>; +} diff --git a/packages/pieces/community/attio/src/lib/triggers/list-entry-created.ts b/packages/pieces/community/attio/src/lib/triggers/list-entry-created.ts new file mode 100644 index 0000000..688a699 --- /dev/null +++ b/packages/pieces/community/attio/src/lib/triggers/list-entry-created.ts @@ -0,0 +1,103 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { attioApiCall, verifyWebhookSignature } from '../common/client'; +import { attioAuth } from '../../index'; +import { listIdDropdown } from '../common/props'; +import { ListWebhookPayload, WebhookResponse } from '../common/types'; +import { isNil } from '@activepieces/shared'; + +const TRIGGER_KEY = 'new-list-entry-trigger'; + +export const listEntryCreatedTrigger = createTrigger({ + name: 'list_entry_created', + displayName: 'List Entry Created', + description: 'Triggers when a new entry is added.', + auth: attioAuth, + props: { + listId: listIdDropdown({ + displayName: 'List', + required: true, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: {}, + async onEnable(context) { + const response = await attioApiCall<{ data: WebhookResponse }>({ + accessToken: context.auth, + method: HttpMethod.POST, + resourceUri: '/webhooks', + body: { + data: { + target_url: context.webhookUrl, + subscriptions: [ + { + event_type: 'list-entry.created', + filter: { + $and: [ + { + field: 'id.list_id', + operator: 'equals', + value: context.propsValue.listId, + }, + ], + }, + }, + ], + }, + }, + }); + + await context.store.put<{ webhookId: string; WebhookSecret: string }>(TRIGGER_KEY, { + webhookId: response.data.id.webhook_id, + WebhookSecret: response.data.secret, + }); + }, + async onDisable(context) { + const webhookData = await context.store.get<{ webhookId: string; WebhookSecret: string }>( + TRIGGER_KEY, + ); + if (!isNil(webhookData) && webhookData.webhookId) { + await attioApiCall({ + accessToken: context.auth, + method: HttpMethod.DELETE, + resourceUri: `/webhooks/${webhookData.webhookId}`, + }); + } + }, + async test(context) { + const response = await attioApiCall<{ data: Array> }>({ + accessToken: context.auth, + method: HttpMethod.POST, + resourceUri: `/lists/${context.propsValue.listId}/entries/query`, + body: { + limit: 5, + offset: 0, + }, + }); + + return response.data; + }, + async run(context) { + const triggerData = await context.store.get<{ webhookId: string; WebhookSecret: string }>( + TRIGGER_KEY, + ); + + const webhookSecret = triggerData?.WebhookSecret; + const webhookSignatureHeader = context.payload.headers['attio-signature']; + const rawBody = context.payload.rawBody; + + if (!verifyWebhookSignature(webhookSecret, webhookSignatureHeader, rawBody)) { + return []; + } + + const payload = context.payload.body as ListWebhookPayload; + const entryId = payload.events[0].id.entry_id; + + const response = await attioApiCall<{ data: Record }>({ + accessToken: context.auth, + method: HttpMethod.GET, + resourceUri: `/lists/${context.propsValue.listId}/entries/${entryId}`, + }); + return [response.data]; + }, +}); diff --git a/packages/pieces/community/attio/src/lib/triggers/list-entry-updated.ts b/packages/pieces/community/attio/src/lib/triggers/list-entry-updated.ts new file mode 100644 index 0000000..b8a895a --- /dev/null +++ b/packages/pieces/community/attio/src/lib/triggers/list-entry-updated.ts @@ -0,0 +1,103 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { attioApiCall, verifyWebhookSignature } from '../common/client'; +import { attioAuth } from '../../index'; +import { listIdDropdown } from '../common/props'; +import { ListWebhookPayload, WebhookResponse } from '../common/types'; +import { isNil } from '@activepieces/shared'; + +const TRIGGER_KEY = 'updated-list-entry-trigger'; + +export const listEntryUpdatedTrigger = createTrigger({ + name: 'list_entry_updated', + displayName: 'List Entry Updated', + description: 'Triggers when an existing entry is updated.', + auth: attioAuth, + props: { + listId: listIdDropdown({ + displayName: 'List', + required: true, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: {}, + async onEnable(context) { + const response = await attioApiCall<{ data: WebhookResponse }>({ + accessToken: context.auth, + method: HttpMethod.POST, + resourceUri: '/webhooks', + body: { + data: { + target_url: context.webhookUrl, + subscriptions: [ + { + event_type: 'list-entry.updated', + filter: { + $and: [ + { + field: 'id.list_id', + operator: 'equals', + value: context.propsValue.listId, + }, + ], + }, + }, + ], + }, + }, + }); + + await context.store.put<{ webhookId: string; WebhookSecret: string }>(TRIGGER_KEY, { + webhookId: response.data.id.webhook_id, + WebhookSecret: response.data.secret, + }); + }, + async onDisable(context) { + const webhookData = await context.store.get<{ webhookId: string; WebhookSecret: string }>( + TRIGGER_KEY, + ); + if (!isNil(webhookData) && webhookData.webhookId) { + await attioApiCall({ + accessToken: context.auth, + method: HttpMethod.DELETE, + resourceUri: `/webhooks/${webhookData.webhookId}`, + }); + } + }, + async test(context) { + const response = await attioApiCall<{ data: Array> }>({ + accessToken: context.auth, + method: HttpMethod.POST, + resourceUri: `/lists/${context.propsValue.listId}/entries/query`, + body: { + limit: 5, + offset: 0, + }, + }); + + return response.data; + }, + async run(context) { + const triggerData = await context.store.get<{ webhookId: string; WebhookSecret: string }>( + TRIGGER_KEY, + ); + + const webhookSecret = triggerData?.WebhookSecret; + const webhookSignatureHeader = context.payload.headers['attio-signature']; + const rawBody = context.payload.rawBody; + + if (!verifyWebhookSignature(webhookSecret, webhookSignatureHeader, rawBody)) { + return []; + } + + const payload = context.payload.body as ListWebhookPayload; + const entryId = payload.events[0].id.entry_id; + + const response = await attioApiCall<{ data: Record }>({ + accessToken: context.auth, + method: HttpMethod.GET, + resourceUri: `/lists/${context.propsValue.listId}/entries/${entryId}`, + }); + return [response.data]; + }, +}); diff --git a/packages/pieces/community/attio/src/lib/triggers/record-created.ts b/packages/pieces/community/attio/src/lib/triggers/record-created.ts new file mode 100644 index 0000000..3fd697d --- /dev/null +++ b/packages/pieces/community/attio/src/lib/triggers/record-created.ts @@ -0,0 +1,103 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { attioApiCall, verifyWebhookSignature } from '../common/client'; +import { attioAuth } from '../../index'; +import { objectTypeIdDropdown } from '../common/props'; +import { ObjectWebhookPayload, WebhookResponse } from '../common/types'; +import { isNil } from '@activepieces/shared'; + +const TRIGGER_KEY = 'new-record-trigger'; + +export const recordCreatedTrigger = createTrigger({ + auth: attioAuth, + name: 'record_created', + displayName: 'Record Created', + description: 'Triggers when a new record such as person,company or deal is created.', + props: { + objectTypeId: objectTypeIdDropdown({ + displayName: 'Object', + required: true, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: {}, + async onEnable(context) { + const response = await attioApiCall<{ data: WebhookResponse }>({ + accessToken: context.auth, + method: HttpMethod.POST, + resourceUri: '/webhooks', + body: { + data: { + target_url: context.webhookUrl, + subscriptions: [ + { + event_type: 'record.created', + filter: { + $and: [ + { + field: 'id.object_id', + operator: 'equals', + value: context.propsValue.objectTypeId, + }, + ], + }, + }, + ], + }, + }, + }); + + await context.store.put<{ webhookId: string; WebhookSecret: string }>(TRIGGER_KEY, { + webhookId: response.data.id.webhook_id, + WebhookSecret: response.data.secret, + }); + }, + async onDisable(context) { + const webhookData = await context.store.get<{ webhookId: string; WebhookSecret: string }>( + TRIGGER_KEY, + ); + if (!isNil(webhookData) && webhookData.webhookId) { + await attioApiCall({ + accessToken: context.auth, + method: HttpMethod.DELETE, + resourceUri: `/webhooks/${webhookData.webhookId}`, + }); + } + }, + async test(context) { + const response = await attioApiCall<{data:Array>}>({ + accessToken:context.auth, + method:HttpMethod.POST, + resourceUri:`/objects/${context.propsValue.objectTypeId}/records/query`, + body:{ + limit:5, + offset:0 + } + }) + + return response.data; + }, + async run(context) { + const triggerData = await context.store.get<{ webhookId: string; WebhookSecret: string }>( + TRIGGER_KEY, + ); + + const webhookSecret = triggerData?.WebhookSecret; + const webhookSignatureHeader = context.payload.headers['attio-signature']; + const rawBody = context.payload.rawBody; + + if (!verifyWebhookSignature(webhookSecret, webhookSignatureHeader, rawBody)) { + return []; + } + + const payload = context.payload.body as ObjectWebhookPayload; + const recordId = payload.events[0].id.record_id; + + const response = await attioApiCall<{ data: Record }>({ + accessToken: context.auth, + method: HttpMethod.GET, + resourceUri: `/objects/${context.propsValue.objectTypeId}/records/${recordId}`, + }); + return [response.data]; + }, +}); diff --git a/packages/pieces/community/attio/src/lib/triggers/record-updated.ts b/packages/pieces/community/attio/src/lib/triggers/record-updated.ts new file mode 100644 index 0000000..cba138d --- /dev/null +++ b/packages/pieces/community/attio/src/lib/triggers/record-updated.ts @@ -0,0 +1,104 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { attioApiCall, verifyWebhookSignature } from '../common/client'; +import { attioAuth } from '../../index'; +import { objectTypeIdDropdown } from '../common/props'; +import { ObjectWebhookPayload, WebhookResponse } from '../common/types'; +import { isNil } from '@activepieces/shared'; + +const TRIGGER_KEY = 'updated-record-trigger'; + +export const recordUpdatedTrigger = createTrigger({ + name: 'record_updated', + displayName: 'Record Updated', + description: + 'Triggers when an existing record is updated (people, companies, deals, etc.).', + auth: attioAuth, + props: { + objectTypeId: objectTypeIdDropdown({ + displayName: 'Object', + required: true, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData:{}, + async onEnable(context) { + const response = await attioApiCall<{ data: WebhookResponse }>({ + accessToken: context.auth, + method: HttpMethod.POST, + resourceUri: '/webhooks', + body: { + data: { + target_url: context.webhookUrl, + subscriptions: [ + { + event_type: 'record.updated', + filter: { + $and: [ + { + field: 'id.object_id', + operator: 'equals', + value: context.propsValue.objectTypeId, + }, + ], + }, + }, + ], + }, + }, + }); + + await context.store.put<{ webhookId: string; WebhookSecret: string }>(TRIGGER_KEY, { + webhookId: response.data.id.webhook_id, + WebhookSecret: response.data.secret, + }); + }, + async onDisable(context) { + const webhookData = await context.store.get<{ webhookId: string; WebhookSecret: string }>( + TRIGGER_KEY, + ); + if (!isNil(webhookData) && webhookData.webhookId) { + await attioApiCall({ + accessToken: context.auth, + method: HttpMethod.DELETE, + resourceUri: `/webhooks/${webhookData.webhookId}`, + }); + } + }, + async test(context) { + const response = await attioApiCall<{ data: Array> }>({ + accessToken: context.auth, + method: HttpMethod.POST, + resourceUri: `/objects/${context.propsValue.objectTypeId}/records/query`, + body: { + limit: 5, + offset: 0, + }, + }); + + return response.data; + }, + async run(context) { + const triggerData = await context.store.get<{ webhookId: string; WebhookSecret: string }>( + TRIGGER_KEY, + ); + + const webhookSecret = triggerData?.WebhookSecret; + const webhookSignatureHeader = context.payload.headers['attio-signature']; + const rawBody = context.payload.rawBody; + + if (!verifyWebhookSignature(webhookSecret, webhookSignatureHeader, rawBody)) { + return []; + } + + const payload = context.payload.body as ObjectWebhookPayload; + const recordId = payload.events[0].id.record_id; + + const response = await attioApiCall<{ data: Record }>({ + accessToken: context.auth, + method: HttpMethod.GET, + resourceUri: `/objects/${context.propsValue.objectTypeId}/records/${recordId}`, + }); + return [response.data]; + }, +}); diff --git a/packages/pieces/community/attio/tsconfig.json b/packages/pieces/community/attio/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/attio/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/attio/tsconfig.lib.json b/packages/pieces/community/attio/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/attio/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/azure-communication-services/.eslintrc.json b/packages/pieces/community/azure-communication-services/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/azure-communication-services/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/azure-communication-services/README.md b/packages/pieces/community/azure-communication-services/README.md new file mode 100644 index 0000000..41200bc --- /dev/null +++ b/packages/pieces/community/azure-communication-services/README.md @@ -0,0 +1,7 @@ +# pieces-azure-communication-services + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-azure-communication-services` to build the library. diff --git a/packages/pieces/community/azure-communication-services/package.json b/packages/pieces/community/azure-communication-services/package.json new file mode 100644 index 0000000..8c3457e --- /dev/null +++ b/packages/pieces/community/azure-communication-services/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-azure-communication-services", + "version": "0.0.1" +} diff --git a/packages/pieces/community/azure-communication-services/project.json b/packages/pieces/community/azure-communication-services/project.json new file mode 100644 index 0000000..794c5e0 --- /dev/null +++ b/packages/pieces/community/azure-communication-services/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-azure-communication-services", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/azure-communication-services/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/azure-communication-services", + "tsConfig": "packages/pieces/community/azure-communication-services/tsconfig.lib.json", + "packageJson": "packages/pieces/community/azure-communication-services/package.json", + "main": "packages/pieces/community/azure-communication-services/src/index.ts", + "assets": [ + "packages/pieces/community/azure-communication-services/*.md", + { + "input": "packages/pieces/community/azure-communication-services/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/azure-communication-services/src/index.ts b/packages/pieces/community/azure-communication-services/src/index.ts new file mode 100644 index 0000000..7c3f5fe --- /dev/null +++ b/packages/pieces/community/azure-communication-services/src/index.ts @@ -0,0 +1,21 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { sendEmail } from './lib/actions/send-email'; +import { PieceCategory } from '@activepieces/shared'; + +export const azureCommunicationServiceAuth = PieceAuth.SecretText({ + displayName: 'Connection string', + required: true, +}); + +export const azureCommunicationServices = createPiece({ + displayName: 'Azure Communication Services', + description: 'Communication services from Microsoft Azure', + auth: azureCommunicationServiceAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: + 'https://cdn.activepieces.com/pieces/azure-communication-services.png', + categories: [PieceCategory.COMMUNICATION, PieceCategory.MARKETING], + authors: ['matthieu-lombard'], + actions: [sendEmail], + triggers: [], +}); diff --git a/packages/pieces/community/azure-communication-services/src/lib/actions/send-email.ts b/packages/pieces/community/azure-communication-services/src/lib/actions/send-email.ts new file mode 100644 index 0000000..6185dec --- /dev/null +++ b/packages/pieces/community/azure-communication-services/src/lib/actions/send-email.ts @@ -0,0 +1,87 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { azureCommunicationServiceAuth } from '../..'; +import { EmailClient, EmailMessage } from '@azure/communication-email'; + +export const sendEmail = createAction({ + auth: azureCommunicationServiceAuth, + name: 'send_email', + displayName: 'Send Email', + description: 'Send a text or HTML email', + props: { + from: Property.ShortText({ + displayName: 'Sender Email (From)', + description: 'Sender email', + required: true, + }), + to: Property.Array({ + displayName: 'To', + description: 'Emails of the recipients', + required: true, + }), + cc: Property.Array({ + displayName: 'Cc', + description: 'List of emails in cc', + required: false, + }), + bcc: Property.Array({ + displayName: 'Bcc', + description: 'List of emails in bcc', + required: false, + }), + reply_to: Property.ShortText({ + displayName: 'Reply To', + description: 'Email to receive replies on (defaults to sender)', + required: false, + }), + subject: Property.ShortText({ + displayName: 'Subject', + description: undefined, + required: true, + }), + content_type: Property.Dropdown<'text' | 'html'>({ + displayName: 'Content Type', + refreshers: [], + required: true, + defaultValue: 'html', + options: async () => { + return { + disabled: false, + options: [ + { label: 'Plain Text', value: 'text' }, + { label: 'HTML', value: 'html' }, + ], + }; + }, + }), + content: Property.ShortText({ + displayName: 'Content', + description: 'HTML is only allowed if you selected HTML as type', + required: true, + }), + }, + async run(context) { + const { to, from, reply_to, subject, content_type, content, cc, bcc } = + context.propsValue; + const message = { + senderAddress: from, + content: { + subject: subject, + ...(content_type === 'text' && { plainText: content }), + ...(content_type === 'html' && { html: content }), + }, + replyTo: [ + { + address: reply_to ?? from, + }, + ], + recipients: { + to: to.map((address) => ({ address })), + cc: (cc || []).map((address) => ({ address })), + bcc: (bcc || []).map((address) => ({ address })), + }, + } as EmailMessage; + const client = new EmailClient(context.auth); + const poller = await client.beginSend(message); + return await poller.pollUntilDone(); + }, +}); diff --git a/packages/pieces/community/azure-communication-services/tsconfig.json b/packages/pieces/community/azure-communication-services/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/azure-communication-services/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/azure-communication-services/tsconfig.lib.json b/packages/pieces/community/azure-communication-services/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/azure-communication-services/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/azure-openai/.eslintrc.json b/packages/pieces/community/azure-openai/.eslintrc.json new file mode 100644 index 0000000..359ff63 --- /dev/null +++ b/packages/pieces/community/azure-openai/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/azure-openai/README.md b/packages/pieces/community/azure-openai/README.md new file mode 100644 index 0000000..a3f31e0 --- /dev/null +++ b/packages/pieces/community/azure-openai/README.md @@ -0,0 +1,7 @@ +# pieces-azure-openai + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-azure-openai` to build the library. diff --git a/packages/pieces/community/azure-openai/package-lock.json b/packages/pieces/community/azure-openai/package-lock.json new file mode 100644 index 0000000..53310b9 --- /dev/null +++ b/packages/pieces/community/azure-openai/package-lock.json @@ -0,0 +1,20 @@ +{ + "name": "@activepieces/piece-azure-openai", + "version": "0.0.8", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-azure-openai", + "version": "0.0.8", + "dependencies": { + "tiktoken": "1.0.11" + } + }, + "node_modules/tiktoken": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.11.tgz", + "integrity": "sha512-aMJcn9NGmb6zDXkCweJLnACyIyjdiYIk1odAfnCUvin7O1QsV1rQP1hatGDMhQovxkeSJhFeU7QuGkbDHGciDQ==" + } + } +} diff --git a/packages/pieces/community/azure-openai/package.json b/packages/pieces/community/azure-openai/package.json new file mode 100644 index 0000000..d4994a4 --- /dev/null +++ b/packages/pieces/community/azure-openai/package.json @@ -0,0 +1,7 @@ +{ + "name": "@activepieces/piece-azure-openai", + "version": "0.0.8", + "dependencies": { + "tiktoken": "1.0.11" + } +} \ No newline at end of file diff --git a/packages/pieces/community/azure-openai/project.json b/packages/pieces/community/azure-openai/project.json new file mode 100644 index 0000000..c895583 --- /dev/null +++ b/packages/pieces/community/azure-openai/project.json @@ -0,0 +1,69 @@ +{ + "name": "pieces-azure-openai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/azure-openai/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/azure-openai", + "tsConfig": "packages/pieces/community/azure-openai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/azure-openai/package.json", + "main": "packages/pieces/community/azure-openai/src/index.ts", + "assets": [ + "packages/pieces/community/azure-openai/*.md", + { + "input": "packages/pieces/community/azure-openai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + }, + "dependsOn": ["prebuild", "^build"] + }, + "prebuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/pieces/community/azure-openai", + "command": "npm ci" + } + }, + "build-with-deps": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "nx run pieces-azure-openai:prebuild", + "nx run pieces-azure-openai:build", + "nx run pieces-azure-openai:postbuild" + ], + "parallel": false + } + }, + "postbuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "dist/packages/pieces/community/azure-openai", + "command": "npm install" + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-azure-openai {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/azure-openai/src/index.ts b/packages/pieces/community/azure-openai/src/index.ts new file mode 100644 index 0000000..3e0d802 --- /dev/null +++ b/packages/pieces/community/azure-openai/src/index.ts @@ -0,0 +1,40 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { askGpt } from './lib/actions/ask-gpt'; + +export const azureOpenaiAuth = PieceAuth.CustomAuth({ + props: { + endpoint: Property.ShortText({ + displayName: 'Endpoint', + description: 'https://.openai.azure.com/', + required: true, + }), + apiKey: PieceAuth.SecretText({ + displayName: 'API Key', + description: + 'Use the Azure Portal to browse to your OpenAI resource and retrieve an API key', + required: true, + }), + }, + required: true, +}); + +export type AzureOpenAIAuth = { + endpoint: string; + apiKey: string; + deploymentId: string; +}; + +export const azureOpenai = createPiece({ + displayName: 'Azure OpenAI', + description: 'Powerful AI tools from Microsoft', + auth: azureOpenaiAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/azure-openai.png', + authors: ["MoShizzle","abuaboud"], + actions: [askGpt], + triggers: [], +}); diff --git a/packages/pieces/community/azure-openai/src/lib/actions/ask-gpt.ts b/packages/pieces/community/azure-openai/src/lib/actions/ask-gpt.ts new file mode 100644 index 0000000..2e8ee16 --- /dev/null +++ b/packages/pieces/community/azure-openai/src/lib/actions/ask-gpt.ts @@ -0,0 +1,151 @@ +import { AzureOpenAIAuth } from '../../'; +import { + Property, + StoreScope, + createAction, +} from '@activepieces/pieces-framework'; +import { OpenAIClient, AzureKeyCredential } from '@azure/openai'; +import { calculateMessagesTokenSize, exceedsHistoryLimit, reduceContextSize } from '../common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const askGpt = createAction({ + name: 'ask_gpt', + displayName: 'Ask GPT', + description: 'Ask ChatGPT anything you want!', + props: { + deploymentId: Property.ShortText({ + displayName: 'Deployment Name', + description: 'The name of your model deployment.', + required: true, + }), + prompt: Property.LongText({ + displayName: 'Question', + required: true, + }), + temperature: Property.Number({ + displayName: 'Temperature', + required: false, + description: + 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.', + defaultValue: 0.9, + }), + maxTokens: Property.Number({ + displayName: 'Maximum Tokens', + required: true, + description: + "The maximum number of tokens to generate. Requests can use up to 2,048 or 4,096 tokens shared between prompt and completion depending on the model. Don't set the value to maximum and leave some tokens for the input. (One token is roughly 4 characters for normal English text)", + defaultValue: 2048, + }), + topP: Property.Number({ + displayName: 'Top P', + required: false, + description: + 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.', + defaultValue: 1, + }), + frequencyPenalty: Property.Number({ + displayName: 'Frequency penalty', + required: false, + description: + "Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.", + defaultValue: 0, + }), + presencePenalty: Property.Number({ + displayName: 'Presence penalty', + required: false, + description: + "Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the mode's likelihood to talk about new topics.", + defaultValue: 0.6, + }), + memoryKey: Property.ShortText({ + displayName: 'Memory Key', + description: + 'A memory key that will keep the chat history shared across runs and flows. Keep it empty to leave ChatGPT without memory of previous messages.', + required: false, + }), + roles: Property.Json({ + displayName: 'Roles', + required: false, + description: 'Array of roles to specify more accurate response', + defaultValue: [ + { role: 'system', content: 'You are a helpful assistant.' }, + ], + }), + }, + + async run(context) { + const { propsValue, store } = context; + const auth: AzureOpenAIAuth = context.auth as AzureOpenAIAuth; + + await propsValidation.validateZod(propsValue, { + temperature: z.number().min(0).max(1.0).optional(), + frequencyPenalty: z.number().min(-2.0).max(2.0).optional(), + presencePenalty: z.number().min(-2.0).max(2.0).optional(), + }); + + const openai = new OpenAIClient( + auth.endpoint, + new AzureKeyCredential(auth.apiKey) + ); + + let messageHistory: any[] | null = []; + // If memory key is set, retrieve messages stored in history + if (propsValue.memoryKey) { + messageHistory = (await store.get(propsValue.memoryKey, StoreScope.PROJECT)) ?? []; + } + + // Add user prompt to message history + messageHistory.push({ + role: 'user', + content: propsValue.prompt, + }); + + // Add system instructions if set by user + const rolesArray = propsValue.roles ? (propsValue.roles as any) : []; + const roles = rolesArray.map((item: any) => { + const rolesEnum = ['system', 'user', 'assistant']; + if (!rolesEnum.includes(item.role)) { + throw new Error( + 'The only available roles are: [system, user, assistant]' + ); + } + + return { + role: item.role, + content: item.content, + }; + }); + + const completion = await openai.getChatCompletions(propsValue.deploymentId, [...roles, ...messageHistory], { + maxTokens: propsValue.maxTokens, + temperature: propsValue.temperature, + frequencyPenalty: propsValue.frequencyPenalty, + presencePenalty: propsValue.presencePenalty, + topP: propsValue.topP, + }); + + const responseText = completion.choices[0].message?.content; + + // Add response to message history + messageHistory = [...messageHistory, responseText]; + + // Check message history token size + // System limit is 32K tokens, we can probably make it bigger but this is a safe spot + const tokenLength = await calculateMessagesTokenSize(messageHistory, ''); + if (propsValue.memoryKey) { + // If tokens exceed 90% system limit or 90% of model limit - maxTokens, reduce history token size + if (exceedsHistoryLimit(tokenLength, '', propsValue.maxTokens)) { + messageHistory = await reduceContextSize( + messageHistory, + '', + propsValue.maxTokens + ); + } + // Store history + await store.put(propsValue.memoryKey, messageHistory, StoreScope.PROJECT); + } + + return responseText; + }, +}); diff --git a/packages/pieces/community/azure-openai/src/lib/common/index.ts b/packages/pieces/community/azure-openai/src/lib/common/index.ts new file mode 100644 index 0000000..bc579f2 --- /dev/null +++ b/packages/pieces/community/azure-openai/src/lib/common/index.ts @@ -0,0 +1,74 @@ +import { encoding_for_model } from 'tiktoken'; + +export const calculateTokensFromString = (string: string, model: string) => { + try { + const encoder = encoding_for_model(model as any); + const tokens = encoder.encode(string); + encoder.free(); + + return tokens.length; + } catch (e) { + // Model not supported by tiktoken, every 4 chars is a token + return Math.round(string.length / 4); + } +}; + +export const calculateMessagesTokenSize = async ( + messages: string[], + model: string +) => { + let tokenLength = 0; + await Promise.all( + messages.map((message: string) => { + return new Promise((resolve) => { + tokenLength += calculateTokensFromString(message, model); + resolve(tokenLength); + }); + }) + ); + + return tokenLength; +}; + +export const reduceContextSize = async ( + messages: string[], + model: string, + maxTokens: number +) => { + // TODO: Summarize context instead of cutoff + const cutoffSize = Math.round(messages.length * 0.1); + const cutoffMessages = messages.splice(cutoffSize, messages.length - 1); + + if ( + (await calculateMessagesTokenSize(cutoffMessages, model)) > + maxTokens / 1.5 + ) { + reduceContextSize(cutoffMessages, model, maxTokens); + } + + return cutoffMessages; +}; + +export const exceedsHistoryLimit = ( + tokenLength: number, + model: string, + maxTokens: number +) => { + if ( + tokenLength >= tokenLimit / 1.1 || + tokenLength >= (modelTokenLimit(model) - maxTokens) / 1.1 + ) { + return true; + } + + return false; +}; + +export const tokenLimit = 32000; + +export const modelTokenLimit = (model: string) => { + switch (model) { + default: + return 2048; + } +}; \ No newline at end of file diff --git a/packages/pieces/community/azure-openai/tsconfig.json b/packages/pieces/community/azure-openai/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/azure-openai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/azure-openai/tsconfig.lib.json b/packages/pieces/community/azure-openai/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/azure-openai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/backblaze/.eslintrc.json b/packages/pieces/community/backblaze/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/backblaze/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/backblaze/README.md b/packages/pieces/community/backblaze/README.md new file mode 100644 index 0000000..cd8613b --- /dev/null +++ b/packages/pieces/community/backblaze/README.md @@ -0,0 +1,7 @@ +# pieces-backblaze + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-backblaze` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/backblaze/package.json b/packages/pieces/community/backblaze/package.json new file mode 100644 index 0000000..efafcb6 --- /dev/null +++ b/packages/pieces/community/backblaze/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-backblaze", + "version": "0.0.1" +} diff --git a/packages/pieces/community/backblaze/project.json b/packages/pieces/community/backblaze/project.json new file mode 100644 index 0000000..ac3b003 --- /dev/null +++ b/packages/pieces/community/backblaze/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-backblaze", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/backblaze/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/backblaze", + "tsConfig": "packages/pieces/community/backblaze/tsconfig.lib.json", + "packageJson": "packages/pieces/community/backblaze/package.json", + "main": "packages/pieces/community/backblaze/src/index.ts", + "assets": [ + "packages/pieces/community/backblaze/*.md", + { + "input": "packages/pieces/community/backblaze/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/backblaze/src/index.ts b/packages/pieces/community/backblaze/src/index.ts new file mode 100644 index 0000000..bb89046 --- /dev/null +++ b/packages/pieces/community/backblaze/src/index.ts @@ -0,0 +1,198 @@ +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { readBackBlazeFileAction } from './lib/actions/read-file'; +import { backBlazes3UploadFileAction } from './lib/actions/upload-file'; +import { createBackBlazeS3 } from './lib/common'; +import { newBackBlazeFileTrigger } from './lib/triggers/new-file'; + +const description = ` +This piece allows you to upload files to BackBlaze Bucket compatible services. + +BackBlaze Settings: +Regions: https://www.backblaze.com/apidocs/introduction-to-the-s3-compatible-api +Endpoint: leave blank +`; + +export const backBlazeS3Auth = PieceAuth.CustomAuth({ + description: description, + props: { + accessKeyId: Property.ShortText({ + displayName: 'Access Key ID', + required: true, + }), + secretAccessKey: PieceAuth.SecretText({ + displayName: 'Secret Access Key', + required: true, + }), + bucket: Property.ShortText({ + displayName: 'Bucket', + required: true, + }), + endpoint: Property.ShortText({ + displayName: 'Endpoint', + required: false, + }), + region: Property.StaticDropdown({ + displayName: 'Region', + options: { + options: [ + { + label: 'Default', + value: 'us-east-1', + }, + { + label: 'US East (N. Virginia) [us-east-1]', + value: 'us-east-1', + }, + { + label: 'US East (Ohio) [us-east-2]', + value: 'us-east-2', + }, + { + label: 'US West (N. California) [us-west-1]', + value: 'us-west-1', + }, + { + label: 'US West (Oregon) [us-west-2]', + value: 'us-west-2', + }, + { + label: 'Africa (Cape Town) [af-south-1]', + value: 'af-south-1', + }, + { + label: 'Asia Pacific (Hong Kong) [ap-east-1]', + value: 'ap-east-1', + }, + { + label: 'Asia Pacific (Mumbai) [ap-south-1]', + value: 'ap-south-1', + }, + { + label: 'Asia Pacific (Osaka-Local) [ap-northeast-3]', + value: 'ap-northeast-3', + }, + { + label: 'Asia Pacific (Seoul) [ap-northeast-2]', + value: 'ap-northeast-2', + }, + { + label: 'Asia Pacific (Singapore) [ap-southeast-1]', + value: 'ap-southeast-1', + }, + { + label: 'Asia Pacific (Sydney) [ap-southeast-2]', + value: 'ap-southeast-2', + }, + { + label: 'Asia Pacific (Tokyo) [ap-northeast-1]', + value: 'ap-northeast-1', + }, + { + label: 'Canada (Central) [ca-central-1]', + value: 'ca-central-1', + }, + { + label: 'Europe (Frankfurt) [eu-central-1]', + value: 'eu-central-1', + }, + { + label: 'Europe (Ireland) [eu-west-1]', + value: 'eu-west-1', + }, + { + label: 'Europe (London) [eu-west-2]', + value: 'eu-west-2', + }, + { + label: 'Europe (Milan) [eu-south-1]', + value: 'eu-south-1', + }, + { + label: 'Europe (Paris) [eu-west-3]', + value: 'eu-west-3', + }, + { + label: 'Europe (Stockholm) [eu-north-1]', + value: 'eu-north-1', + }, + { + label: 'Middle East (Bahrain) [me-south-1]', + value: 'me-south-1', + }, + { + label: 'South America (São Paulo) [sa-east-1]', + value: 'sa-east-1', + }, + { + label: 'Europe (Spain) [eu-south-2]', + value: 'eu-south-2', + }, + { + label: 'Asia Pacific (Hyderabad) [ap-south-2]', + value: 'ap-south-2', + }, + { + label: 'Asia Pacific (Jakarta) [ap-southeast-3]', + value: 'ap-southeast-3', + }, + { + label: 'Asia Pacific (Melbourne) [ap-southeast-4]', + value: 'ap-southeast-4', + }, + { + label: 'China (Beijing) [cn-north-1]', + value: 'cn-north-1', + }, + { + label: 'China (Ningxia) [cn-northwest-1]', + value: 'cn-northwest-1', + }, + { + label: 'Europe (Zurich) [eu-central-2]', + value: 'eu-central-2', + }, + { + label: 'Middle East (UAE) [me-central-1]', + value: 'me-central-1', + }, + ], + }, + required: true, + }), + }, + validate: async ({ auth }) => { + const s3 = createBackBlazeS3(auth); + try { + await s3.listObjectsV2({ + Bucket: auth.bucket, + MaxKeys: 1, + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: (e as Error)?.message, + }; + } + }, + required: true, +}); + +export const backblaze = createPiece({ + displayName: 'Backblaze', + description: 'Scalable storage in the cloud', + logoUrl: 'https://cdn.activepieces.com/pieces/backblaze.png', + minimumSupportedRelease: '0.30.0', + authors: ["nhnansari","kishanprmr"], + categories: [PieceCategory.DEVELOPER_TOOLS], + auth: backBlazeS3Auth, + actions: [backBlazes3UploadFileAction, readBackBlazeFileAction], + triggers: [newBackBlazeFileTrigger], +}); diff --git a/packages/pieces/community/backblaze/src/lib/actions/read-file.ts b/packages/pieces/community/backblaze/src/lib/actions/read-file.ts new file mode 100644 index 0000000..8180930 --- /dev/null +++ b/packages/pieces/community/backblaze/src/lib/actions/read-file.ts @@ -0,0 +1,35 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { backBlazeS3Auth } from '../..'; +import { createBackBlazeS3 } from '../common'; + +export const readBackBlazeFileAction = createAction({ + auth: backBlazeS3Auth, + name: 'read-backblaze-file', + displayName: 'Read File', + description: 'Read a file from Backblaze bucket to use it in other steps.', + props: { + key: Property.ShortText({ + displayName: 'Key', + description: 'The key of the file to read. include extension if file has any extension.', + required: true, + }), + }, + async run(context) { + const { bucket } = context.auth; + const { key } = context.propsValue; + const s3 = createBackBlazeS3(context.auth); + + const file = await s3.getObject({ + Bucket: bucket, + Key: key, + }); + const base64 = await file.Body?.transformToString('base64'); + if (!base64) { + throw new Error(`Could not read file ${key} from bucket`); + } + return await context.files.write({ + fileName: key, + data: Buffer.from(base64, 'base64'), + }); + }, +}); diff --git a/packages/pieces/community/backblaze/src/lib/actions/upload-file.ts b/packages/pieces/community/backblaze/src/lib/actions/upload-file.ts new file mode 100644 index 0000000..a440bc9 --- /dev/null +++ b/packages/pieces/community/backblaze/src/lib/actions/upload-file.ts @@ -0,0 +1,137 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { backBlazeS3Auth } from '../..'; +import { createBackBlazeS3 } from '../common'; +import { ObjectCannedACL } from '@aws-sdk/client-s3'; + +export const backBlazes3UploadFileAction = createAction({ + auth: backBlazeS3Auth, + name: 'upload-backblaze-file', + displayName: 'Upload File', + description: 'Upload an File to bucket.', + props: { + file: Property.File({ + displayName: 'File', + required: true, + }), + fileName: Property.ShortText({ + displayName: 'File Name', + required: false, + description: 'my-file-name (no extension). write full path if you want to store in the directories or sub-directories.', + }), + acl: Property.StaticDropdown({ + displayName: 'ACL', + required: false, + options: { + options: [ + { + label: 'private', + value: 'private', + }, + { + label: 'public-read', + value: 'public-read', + }, + { + label: 'public-read-write', + value: 'public-read-write', + }, + { + label: 'authenticated-read', + value: 'authenticated-read', + }, + { + label: 'aws-exec-read', + value: 'aws-exec-read', + }, + { + label: 'bucket-owner-read', + value: 'bucket-owner-read', + }, + { + label: 'bucket-owner-full-control', + value: 'bucket-owner-full-control', + }, + ], + }, + }), + type: Property.StaticDropdown({ + displayName: 'Type', + required: true, + options: { + options: [ + { + label: 'image/png', + value: 'image/png', + }, + { + label: 'image/jpeg', + value: 'image/jpeg', + }, + { + label: 'image/gif', + value: 'image/gif', + }, + { + label: 'audio/mpeg', + value: 'audio/mpeg', + }, + { + label: 'audio/wav', + value: 'audio/wav', + }, + { + label: 'video/mp4', + value: 'video/mp4', + }, + { + label: 'application/pdf', + value: 'application/pdf', + }, + { + label: 'application/msword', + value: 'application/msword', + }, + { + label: 'text/plain', + value: 'text/plain', + }, + { + label: 'application/json', + value: 'application/json', + }, + ], + }, + }), + }, + async run(context) { + const { bucket } = context.auth; + const { file, fileName, acl, type } = context.propsValue; + + const s3 = createBackBlazeS3(context.auth); + + const contentType = type; + const [_, ext] = contentType.split('/'); + const extension = '.' + ext; + + const generatedName = new Date().toISOString() + Date.now() + extension; + + const finalFileName = fileName ? fileName + extension : generatedName; + + const uploadResponse = await s3.putObject({ + Bucket: bucket, + Key: finalFileName, + ACL: acl as ObjectCannedACL | undefined, + ContentType: contentType, + Body: file.data, + }); + + const endpoint = context.auth.endpoint ? context.auth.endpoint :""; + const cleanEndpoint = endpoint.replace("https://","") + const url = `https://${bucket}.${cleanEndpoint}/${finalFileName}` + return { + fileName: finalFileName, + etag: uploadResponse.ETag, + url: url, + }; + }, +}); diff --git a/packages/pieces/community/backblaze/src/lib/common.ts b/packages/pieces/community/backblaze/src/lib/common.ts new file mode 100644 index 0000000..29f0e59 --- /dev/null +++ b/packages/pieces/community/backblaze/src/lib/common.ts @@ -0,0 +1,21 @@ +import { isNil } from '@activepieces/shared'; +import { S3 } from '@aws-sdk/client-s3'; + +export function createBackBlazeS3(auth: { + accessKeyId: string; + secretAccessKey: string; + region: string | undefined; + endpoint: string | undefined; +}) { + const s3 = new S3({ + credentials: { + accessKeyId: auth.accessKeyId, + secretAccessKey: auth.secretAccessKey, + }, + forcePathStyle: auth.endpoint ? true : undefined, + region: auth.region, + endpoint: + auth.endpoint === '' || isNil(auth.endpoint) ? undefined : auth.endpoint, + }); + return s3; +} diff --git a/packages/pieces/community/backblaze/src/lib/triggers/new-file.ts b/packages/pieces/community/backblaze/src/lib/triggers/new-file.ts new file mode 100644 index 0000000..b6d1012 --- /dev/null +++ b/packages/pieces/community/backblaze/src/lib/triggers/new-file.ts @@ -0,0 +1,94 @@ +import { + PiecePropValueSchema, + Property, + createTrigger, +} from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; + +import { backBlazeS3Auth } from '../..'; +import { createBackBlazeS3 } from '../common'; + +const polling: Polling< + PiecePropValueSchema, + { folderPath?: string } +> = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ auth, lastItemId, propsValue }) => { + const s3 = createBackBlazeS3(auth); + const params: any = { + Bucket: auth.bucket, + MaxKeys: 100, + StartAfter: lastItemId, + }; + if (propsValue.folderPath) + params.Prefix = `${ + propsValue.folderPath.endsWith('/') + ? propsValue.folderPath.slice(0, -1) + : propsValue.folderPath + }`; + + const currentValues = (await s3.listObjectsV2(params)).Contents ?? []; + const items = (currentValues as any[]).map((item: { Key: string }) => ({ + id: item.Key, + data: item, + })); + return items; + }, +}; + +export const newBackBlazeFileTrigger = createTrigger({ + auth: backBlazeS3Auth, + name: 'new_backblaze_file', + displayName: 'New File', + description: 'Trigger when a new file is uploaded.', + props: { + folderPath: Property.ShortText({ + displayName: 'Folder Path', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, + test: async (context) => { + return await pollingHelper.test(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, + + sampleData: { + Key: 'myfolder/100-3.png', + LastModified: '2023-08-04T13:51:26.000Z', + ETag: '"e9f16cce12352322272525f5af65a2e"', + Size: 40239, + StorageClass: 'STANDARD', + }, +}); diff --git a/packages/pieces/community/backblaze/tsconfig.json b/packages/pieces/community/backblaze/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/backblaze/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/backblaze/tsconfig.lib.json b/packages/pieces/community/backblaze/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/backblaze/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/bamboohr/.eslintrc.json b/packages/pieces/community/bamboohr/.eslintrc.json new file mode 100644 index 0000000..d7449bb --- /dev/null +++ b/packages/pieces/community/bamboohr/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {}, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/bamboohr/README.md b/packages/pieces/community/bamboohr/README.md new file mode 100644 index 0000000..009f90a --- /dev/null +++ b/packages/pieces/community/bamboohr/README.md @@ -0,0 +1,7 @@ +# pieces-bamboohr + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-bamboohr` to build the library. diff --git a/packages/pieces/community/bamboohr/package.json b/packages/pieces/community/bamboohr/package.json new file mode 100644 index 0000000..c68cb0f --- /dev/null +++ b/packages/pieces/community/bamboohr/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-bamboohr", + "version": "0.0.1" +} diff --git a/packages/pieces/community/bamboohr/project.json b/packages/pieces/community/bamboohr/project.json new file mode 100644 index 0000000..e8d2ce8 --- /dev/null +++ b/packages/pieces/community/bamboohr/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-bamboohr", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/bamboohr/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/bamboohr", + "tsConfig": "packages/pieces/community/bamboohr/tsconfig.lib.json", + "packageJson": "packages/pieces/community/bamboohr/package.json", + "main": "packages/pieces/community/bamboohr/src/index.ts", + "assets": [ + "packages/pieces/community/bamboohr/*.md", + { + "input": "packages/pieces/community/bamboohr/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/bamboohr/src/index.ts b/packages/pieces/community/bamboohr/src/index.ts new file mode 100644 index 0000000..f7773e8 --- /dev/null +++ b/packages/pieces/community/bamboohr/src/index.ts @@ -0,0 +1,50 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; + +export const bambooHrAuth = PieceAuth.CustomAuth({ + required: true, + description: + 'Follow [these instructions](https://documentation.bamboohr.com/docs/getting-started#authentication) to get your API key', + props: { + companyDomain: Property.ShortText({ + displayName: 'Company domain', + description: + 'The subdomain used to access BambooHR. If you access BambooHR at https://mycompany.bamboohr.com, then the companyDomain is "mycompany"', + required: true, + }), + apiKey: PieceAuth.SecretText({ + displayName: 'API key', + required: true, + }), + }, +}); + +export const bambooHr = createPiece({ + displayName: 'BambooHR', + auth: bambooHrAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/bamboohr.png', + authors: ['AdamSelene'], + actions: [ + createCustomApiCallAction({ + baseUrl: (auth) => + `https://api.bamboohr.com/api/gateway.php/${ + (auth as { companyDomain: string }).companyDomain + }/v1/`, + auth: bambooHrAuth, + authMapping: async (auth) => { + const { apiKey } = auth as { apiKey: string }; + return { + Authorization: `Basic ${Buffer.from(`${apiKey}:`).toString( + 'base64' + )}`, + }; + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/bamboohr/tsconfig.json b/packages/pieces/community/bamboohr/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/bamboohr/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/bamboohr/tsconfig.lib.json b/packages/pieces/community/bamboohr/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/bamboohr/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/bannerbear/.eslintrc.json b/packages/pieces/community/bannerbear/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/bannerbear/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/bannerbear/README.md b/packages/pieces/community/bannerbear/README.md new file mode 100644 index 0000000..fb9c0df --- /dev/null +++ b/packages/pieces/community/bannerbear/README.md @@ -0,0 +1,7 @@ +# pieces-bannerbear + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-bannerbear` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/bannerbear/package.json b/packages/pieces/community/bannerbear/package.json new file mode 100644 index 0000000..250a7e4 --- /dev/null +++ b/packages/pieces/community/bannerbear/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-bannerbear", + "version": "0.3.4" +} \ No newline at end of file diff --git a/packages/pieces/community/bannerbear/project.json b/packages/pieces/community/bannerbear/project.json new file mode 100644 index 0000000..1252d49 --- /dev/null +++ b/packages/pieces/community/bannerbear/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-bannerbear", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/bannerbear/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/bannerbear", + "tsConfig": "packages/pieces/community/bannerbear/tsconfig.lib.json", + "packageJson": "packages/pieces/community/bannerbear/package.json", + "main": "packages/pieces/community/bannerbear/src/index.ts", + "assets": [ + "packages/pieces/community/bannerbear/*.md", + { + "input": "packages/pieces/community/bannerbear/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/bannerbear/src/index.ts b/packages/pieces/community/bannerbear/src/index.ts new file mode 100644 index 0000000..7f1750b --- /dev/null +++ b/packages/pieces/community/bannerbear/src/index.ts @@ -0,0 +1,32 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { bannerbearCreateImageAction } from './lib/actions/create-image'; + +export const bannerbearAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: 'Bannerbear API Key', + required: true, +}); + +export const bannerbear = createPiece({ + displayName: 'Bannerbear', + description: 'Automate image generation', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/bannerbear.png', + categories: [PieceCategory.MARKETING], + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + auth: bannerbearAuth, + actions: [ + bannerbearCreateImageAction, + createCustomApiCallAction({ + baseUrl: () => 'https://sync.api.bannerbear.com/v2', + auth: bannerbearAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/bannerbear/src/lib/actions/create-image.ts b/packages/pieces/community/bannerbear/src/lib/actions/create-image.ts new file mode 100644 index 0000000..9d44911 --- /dev/null +++ b/packages/pieces/community/bannerbear/src/lib/actions/create-image.ts @@ -0,0 +1,343 @@ +import { + Property, + DynamicPropsValue, + createAction, +} from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { bannerbearAuth } from '../../'; + +export const bannerbearCreateImageAction = createAction({ + auth: bannerbearAuth, + name: 'bannerbear_create_image', // Must be a unique across the piece, this shouldn't be changed. + displayName: 'Create Image', + description: 'Create image from Bannerbear template', + props: { + template: Property.Dropdown({ + displayName: 'Template', + description: 'The template to use in image creation.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please enter your API key first.', + }; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.bannerbear.com/v2/templates`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + + if (response.status === 200) { + return { + disabled: false, + options: response.body.map((template) => { + return { + label: template.name, + value: template, + }; + }), + }; + } + + return { + disabled: true, + options: [], + placeholder: 'Error processing templates', + }; + }, + }), + template_version: Property.Number({ + displayName: 'Template version', + description: 'Create image based on a specific version of the template.', + required: false, + }), + modifications: Property.DynamicProperties({ + displayName: 'Template modifications', + description: 'A list of modifications you want to make on the template.', + required: true, + refreshers: ['template'], + props: async ({ auth, template }) => { + if (!auth) return {}; + if (!template) return {}; + + let fields: DynamicPropsValue = {}; + + (template as BannerbearTemplate).available_modifications.map( + (modification) => { + if ('text' in modification) { + fields = { + ...BannerbearModificationsMapping.main, + ...BannerbearModificationsMapping.text, + ...BannerbearModificationsMapping.common, + }; + } else if ('rating' in modification) { + fields = { + ...BannerbearModificationsMapping.main, + ...BannerbearModificationsMapping.star_rating, + ...BannerbearModificationsMapping.common, + }; + } else if ('image_url' in modification) { + fields = { + ...BannerbearModificationsMapping.main, + ...BannerbearModificationsMapping.image, + ...BannerbearModificationsMapping.common, + }; + } else if ('chart_data' in modification) { + fields = { + ...BannerbearModificationsMapping.main, + ...BannerbearModificationsMapping.barline_chart, + ...BannerbearModificationsMapping.common, + }; + } else if ('bar_code_data' in modification) { + fields = { + ...BannerbearModificationsMapping.main, + ...BannerbearModificationsMapping.bar_code, + ...BannerbearModificationsMapping.common, + }; + } + } + ); + + return fields; + }, + }), + transparent: Property.Checkbox({ + displayName: 'Transparent background', + description: + 'Render a PNG with a transparent background. Default is false.', + required: false, + }), + render_pdf: Property.Checkbox({ + displayName: 'Render a PDF', + description: 'Render a PDF instead of a PNG. Default is false.', + required: false, + }), + metadata: Property.LongText({ + displayName: 'Metadata', + description: + 'Any metadata that you need to store e.g. ID of a record in your DB.', + required: false, + }), + }, + async run({ auth, propsValue }) { + const body = { + modifications: + Object.keys(propsValue.modifications).length === 0 + ? [] + : [propsValue.modifications], + template_version: propsValue.template_version, + transparent: propsValue.transparent, + render_pdf: propsValue.render_pdf, + template: propsValue.template.uid, + metadata: propsValue.metadata, + }; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://sync.api.bannerbear.com/v2/images`, + body, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + }; + + const result = await httpClient.sendRequest(request); + console.debug('Image creation complete', result); + + if (result.status === 200 || result.status === 202) { + return result.body; + } else { + return result; + } + }, +}); + +interface BannerbearTemplate { + created_at: string; + name: string; + self: string; + uid: string; + preview_url: number; + width: number; + height: number; + available_modifications: { + name: string; + image_url?: string; + text?: string; + chart_data: string; + bar_code_data: number; + rating: number; + }[]; + tags: string[]; +} + +const BannerbearModificationsMapping = { + main: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + description: 'The name of the layer you want to change.', + }), + color: Property.ShortText({ + displayName: 'Color', + required: false, + description: 'Color in hex format e.g. "#FF0000".', + }), + }, + common: { + gradient: Property.ShortText({ + displayName: 'Gradient', + required: false, + description: 'Fill with gradient e.g. ["#000", "#FFF"]', + }), + border_width: Property.Number({ + displayName: 'Border width', + required: false, + description: 'Width of the object border.', + }), + border_color: Property.ShortText({ + displayName: 'Border color', + required: false, + description: 'Border color in hex format e.g. "#FF0000".', + }), + shift_x: Property.Number({ + displayName: 'Shift x', + required: false, + description: 'Shift layer along the x axis.', + }), + shift_y: Property.Number({ + displayName: 'Shift y', + required: false, + description: 'Shift layer along the y axis.', + }), + target: Property.ShortText({ + displayName: 'Target', + required: false, + description: + 'Add a clickable link to a URL on this object when rendering a PDF.', + }), + hide: Property.Checkbox({ + displayName: 'Hide', + required: false, + description: 'Set to true to hide a layer.', + }), + }, + text: { + text: Property.ShortText({ + displayName: 'Text', + required: false, + description: 'Replacement text you want to use.', + }), + background: Property.ShortText({ + displayName: 'Background', + required: false, + description: 'Background color in hex format e.g. "#FF0000".', + }), + font_family: Property.ShortText({ + displayName: 'Font family', + required: false, + description: 'Change the font.', + }), + text_align_h: Property.ShortText({ + displayName: 'Text align H', + required: false, + description: 'Horizontal alignment (left, center, right)', + }), + text_align_v: Property.ShortText({ + displayName: 'Text align V', + required: false, + description: 'Vertical alignment (top, center, bottom)', + }), + font_family_2: Property.ShortText({ + displayName: 'Font family 2', + required: false, + description: 'Change the secondary font.', + }), + color_2: Property.ShortText({ + displayName: 'Color 2', + required: false, + description: 'Change the secondary font color.', + }), + }, + image: { + image_url: Property.ShortText({ + displayName: 'Image url', + required: false, + description: 'Change the image.', + }), + effect: Property.ShortText({ + displayName: 'Effect', + required: false, + description: 'Change the effect.', + }), + anchor_x: Property.ShortText({ + displayName: 'Anchor x', + required: false, + description: 'Change the anchor point (left, center, right)', + }), + anchor_y: Property.ShortText({ + displayName: 'Anchor y', + required: false, + description: 'Change the anchor point (top, center, bottom).', + }), + fill_type: Property.ShortText({ + displayName: 'Fill type', + required: false, + description: 'Change the fill type (fill, fit).', + }), + disable_face_detect: Property.Checkbox({ + displayName: 'Disable face detect', + required: false, + description: + 'Set to true to disable face detect for this request (if the image container is using face detect).', + }), + disable_smart_crop: Property.Checkbox({ + displayName: 'Disable smart crop', + required: false, + description: + 'Set to true to disable smart crop for this request (if the image container is using smart crop)', + }), + }, + barline_chart: { + chart_data: Property.ShortText({ + displayName: 'Chart data', + required: false, + description: 'Comma-delimited list of numbers to use as data.', + }), + }, + star_rating: { + rating: Property.Number({ + displayName: 'Rating', + required: false, + description: 'Number from 0 to 100 to use as the rating.', + }), + }, + qr_code: { + target: Property.ShortText({ + displayName: 'Target', + required: false, + description: 'URL or text to use as the code target.', + }), + }, + bar_code: { + bar_code_data: Property.ShortText({ + displayName: 'Barcode data', + required: false, + description: 'Text to encode as a bar code.', + }), + }, +}; diff --git a/packages/pieces/community/bannerbear/tsconfig.json b/packages/pieces/community/bannerbear/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/bannerbear/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/bannerbear/tsconfig.lib.json b/packages/pieces/community/bannerbear/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/bannerbear/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/baserow/.eslintrc.json b/packages/pieces/community/baserow/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/baserow/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/baserow/README.md b/packages/pieces/community/baserow/README.md new file mode 100644 index 0000000..618640b --- /dev/null +++ b/packages/pieces/community/baserow/README.md @@ -0,0 +1,7 @@ +# pieces-baserow + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-baserow` to build the library. diff --git a/packages/pieces/community/baserow/package.json b/packages/pieces/community/baserow/package.json new file mode 100644 index 0000000..cf9dda7 --- /dev/null +++ b/packages/pieces/community/baserow/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-baserow", + "version": "0.0.6" +} \ No newline at end of file diff --git a/packages/pieces/community/baserow/project.json b/packages/pieces/community/baserow/project.json new file mode 100644 index 0000000..abab7af --- /dev/null +++ b/packages/pieces/community/baserow/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-baserow", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/baserow/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/baserow", + "tsConfig": "packages/pieces/community/baserow/tsconfig.lib.json", + "packageJson": "packages/pieces/community/baserow/package.json", + "main": "packages/pieces/community/baserow/src/index.ts", + "assets": [ + "packages/pieces/community/baserow/*.md", + { + "input": "packages/pieces/community/baserow/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-baserow {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/baserow/src/index.ts b/packages/pieces/community/baserow/src/index.ts new file mode 100644 index 0000000..313e500 --- /dev/null +++ b/packages/pieces/community/baserow/src/index.ts @@ -0,0 +1,60 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createRowAction } from './lib/actions/create-row'; +import { deleteRowAction } from './lib/actions/delete-row'; +import { getRowAction } from './lib/actions/get-row'; +import { listRowsAction } from './lib/actions/list-rows'; +import { updateRowAction } from './lib/actions/update-row'; + +export const baserowAuth = PieceAuth.CustomAuth({ + required: true, + description: ` + 1. Log in to your Baserow Account. + 2. Click on your profile-pic(top-left) and navigate to **Settings->Database tokens**. + 3. Create new token with any name and appropriate workspace. + 4. After token creation,click on **:** right beside token name and copy database token. + 5. Enter your Baserow API URL.If you are using baserow.io, you can leave the default one.`, + props: { + apiUrl: Property.ShortText({ + displayName: 'API URL', + required: true, + defaultValue: 'https://api.baserow.io', + }), + token: PieceAuth.SecretText({ + displayName: 'Database Token', + required: true, + }), + }, +}); + +export const baserow = createPiece({ + displayName: 'Baserow', + description: 'Open-source online database tool, alternative to Airtable', + auth: baserowAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/baserow.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: ["kishanprmr","MoShizzle","abuaboud"], + actions: [ + createRowAction, + deleteRowAction, + getRowAction, + listRowsAction, + updateRowAction, + createCustomApiCallAction({ + baseUrl: (auth) => { + return (auth as { apiUrl: string }).apiUrl; + }, + auth: baserowAuth, + authMapping: async (auth) => ({ + Authorization: `Token ${(auth as { token: string }).token}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/create-row.ts b/packages/pieces/community/baserow/src/lib/actions/create-row.ts new file mode 100644 index 0000000..6da32e7 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/create-row.ts @@ -0,0 +1,58 @@ +import { + DynamicPropsValue, + PiecePropValueSchema, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { baserowAuth } from '../..'; +import { baserowCommon, makeClient } from '../common'; +import { BaserowFieldType } from '../common/constants'; + +export const createRowAction = createAction({ + name: 'baserow_create_row', + displayName: 'Create Row', + description: 'Creates a new row.', + auth: baserowAuth, + props: { + table_id: Property.Number({ + displayName: 'Table ID', + required: true, + description: + "Please enter the table ID where the row must be created in. You can find the ID by clicking on the three dots next to the table. It's the number between brackets.", + }), + table_fields: baserowCommon.tableFields(true), + }, + async run(context) { + const table_id = context.propsValue.table_id!; + const tableFieldsInput = context.propsValue.table_fields!; + const formattedTableFields: DynamicPropsValue = {}; + + const client = makeClient( + context.auth as PiecePropValueSchema + ); + const tableSchema = await client.listTableFields(table_id); + + // transform props value to related baserow value + const fieldIDTypeMap: { [key: string]: string } = {}; + for (const column of tableSchema) { + fieldIDTypeMap[column.name] = column.type; + } + + Object.keys(tableFieldsInput).forEach((key) => { + const fieldType: string = fieldIDTypeMap[key]; + if (fieldType === BaserowFieldType.LINK_TO_TABLE) { + formattedTableFields[key] = tableFieldsInput[key].map((id: string) => + parseInt(id, 10) + ); + } else if (fieldType === BaserowFieldType.MULTIPLE_COLLABORATORS) { + formattedTableFields[key] = tableFieldsInput[key].map((id: string) => ({ + id: parseInt(id, 10), + })); + } else { + formattedTableFields[key] = tableFieldsInput[key]; + } + }); + + return await client.createRow(table_id, formattedTableFields); + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/delete-row.ts b/packages/pieces/community/baserow/src/lib/actions/delete-row.ts new file mode 100644 index 0000000..350b9a0 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/delete-row.ts @@ -0,0 +1,34 @@ +import { + PiecePropValueSchema, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { baserowAuth } from '../..'; +import { makeClient } from '../common'; + +export const deleteRowAction = createAction({ + name: 'baserow_delete_row', + displayName: 'Delete Row', + description: 'Deletes an existing row.', + auth: baserowAuth, + props: { + table_id: Property.Number({ + displayName: 'Table ID', + required: true, + description: + "Please enter the table ID where the row must be deleted in.You can find the ID by clicking on the three dots next to the table. It's the number between brackets.", + }), + row_id: Property.Number({ + displayName: 'Row ID', + required: true, + description: 'Please enter the row ID that needs to be deleted.', + }), + }, + async run(context) { + const { table_id, row_id } = context.propsValue; + const client = makeClient( + context.auth as PiecePropValueSchema + ); + return await client.deleteRow(table_id, row_id); + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/get-row.ts b/packages/pieces/community/baserow/src/lib/actions/get-row.ts new file mode 100644 index 0000000..623616d --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/get-row.ts @@ -0,0 +1,34 @@ +import { + PiecePropValueSchema, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { baserowAuth } from '../..'; +import { makeClient } from '../common'; + +export const getRowAction = createAction({ + name: 'baserow_get_row', + displayName: 'Get Row', + description: 'Fetches a single table row.', + auth: baserowAuth, + props: { + table_id: Property.Number({ + displayName: 'Table ID', + required: true, + description: + "Please enter the table ID where you want to get the row from. You can find the ID by clicking on the three dots next to the table. It's the number between brackets.", + }), + row_id: Property.Number({ + displayName: 'Row ID', + required: true, + description: 'Please enter the row ID that is requested.', + }), + }, + async run(context) { + const { table_id, row_id } = context.propsValue; + const client = makeClient( + context.auth as PiecePropValueSchema + ); + return await client.getRow(table_id, row_id); + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/list-rows.ts b/packages/pieces/community/baserow/src/lib/actions/list-rows.ts new file mode 100644 index 0000000..f499864 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/list-rows.ts @@ -0,0 +1,46 @@ +import { + PiecePropValueSchema, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { baserowAuth } from '../..'; +import { makeClient } from '../common'; + +export const listRowsAction = createAction({ + name: 'baserow_list_rows', + displayName: 'List Rows', + description: 'Finds a page of rows in given table.', + auth: baserowAuth, + props: { + table_id: Property.Number({ + displayName: 'Table ID', + required: true, + description: + "Please enter the table ID where you want to get the rows from. You can find the ID by clicking on the three dots next to the table. It's the number between brackets.", + }), + limit: Property.Number({ + displayName: 'Limit', + required: false, + description: 'The maximum number of rows to return.', + }), + search: Property.ShortText({ + displayName: 'Search', + required: false, + description: + 'If provided only rows with cell data that matches the search query are going to be returned.', + }), + order_by: Property.ShortText({ + displayName: 'Order By', + required: false, + description: `If provided rows will be order by specific field.Use **-** sign for descending / **+** sing for ascending ordering. + Example. "-My Field" will return rows in descending order based on "My Field" field.`, + }), + }, + async run(context) { + const { table_id, limit, search, order_by } = context.propsValue; + const client = makeClient( + context.auth as PiecePropValueSchema + ); + return await client.listRows(table_id, limit, search, order_by); + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/update-row.ts b/packages/pieces/community/baserow/src/lib/actions/update-row.ts new file mode 100644 index 0000000..f34a093 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/update-row.ts @@ -0,0 +1,62 @@ +import { + DynamicPropsValue, + PiecePropValueSchema, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { baserowAuth } from '../..'; +import { baserowCommon, makeClient } from '../common'; +import { BaserowFieldType } from '../common/constants'; + +export const updateRowAction = createAction({ + name: 'baserow_update_row', + displayName: 'Update Row', + description: 'Updates an existing row.', + auth: baserowAuth, + props: { + table_id: Property.Number({ + displayName: 'Table ID', + required: true, + description: + "Please enter the table ID where the row must be updated in. You can find the ID by clicking on the three dots next to the table. It's the number between brackets.", + }), + row_id: Property.Number({ + displayName: 'Row ID', + required: true, + description: 'Please enter the row ID that needs to be updated.', + }), + table_fields: baserowCommon.tableFields(true), + }, + async run(context) { + const { table_id, row_id } = context.propsValue; + const tableFieldsInput = context.propsValue.table_fields!; + const formattedTableFields: DynamicPropsValue = {}; + + const client = makeClient( + context.auth as PiecePropValueSchema + ); + const tableSchema = await client.listTableFields(table_id); + + // transform props value to related baserow value + const fieldIDTypeMap: { [key: string]: string } = {}; + for (const column of tableSchema) { + fieldIDTypeMap[column.name] = column.type; + } + + Object.keys(tableFieldsInput).forEach((key) => { + const fieldType: string = fieldIDTypeMap[key]; + if (fieldType === BaserowFieldType.LINK_TO_TABLE) { + formattedTableFields[key] = tableFieldsInput[key].map((id: string) => + parseInt(id, 10) + ); + } else if (fieldType === BaserowFieldType.MULTIPLE_COLLABORATORS) { + formattedTableFields[key] = tableFieldsInput[key].map((id: string) => ({ + id: parseInt(id, 10), + })); + } else { + formattedTableFields[key] = tableFieldsInput[key]; + } + }); + return await client.updateRow(table_id, row_id, formattedTableFields); + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/common/client.ts b/packages/pieces/community/baserow/src/lib/common/client.ts new file mode 100644 index 0000000..f879c38 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/common/client.ts @@ -0,0 +1,112 @@ +import { + HttpMessageBody, + HttpMethod, + QueryParams, + httpClient, +} from '@activepieces/pieces-common'; +import { BaserowField } from './types'; + +function emptyValueFilter( + accessor: (key: string) => any +): (key: string) => boolean { + return (key: string) => { + const val = accessor(key); + return ( + val !== null && + val !== undefined && + (typeof val != 'string' || val.length > 0) + ); + }; +} + +export function prepareQuery(request?: Record): QueryParams { + const params: QueryParams = {}; + if (!request) return params; + Object.keys(request) + .filter(emptyValueFilter((k) => request[k])) + .forEach((k: string) => { + params[k] = (request as Record)[k].toString(); + }); + return params; +} + +export class BaserowClient { + constructor(private baseUrl: string, private token: string) {} + async makeRequest( + method: HttpMethod, + url: string, + query?: QueryParams, + body?: object + ): Promise { + const res = await httpClient.sendRequest({ + method, + url: `${this.baseUrl}/api${url}`, + headers: { Authorization: `Token ${this.token}` }, + queryParams: query, + body, + }); + return res.body; + } + async listTableFields(table_id: number): Promise { + return await this.makeRequest( + HttpMethod.GET, + `/database/fields/table/${table_id}/` + ); + } + async createRow(table_id: number, request: Record) { + return await this.makeRequest( + HttpMethod.POST, + `/database/rows/table/${table_id}/`, + { + user_field_names: 'true', + }, + request + ); + } + async updateRow( + table_id: number, + row_id: number, + request: Record + ) { + return await this.makeRequest( + HttpMethod.PATCH, + `/database/rows/table/${table_id}/${row_id}/`, + { + user_field_names: 'true', + }, + request + ); + } + async deleteRow(table_id: number, row_id: number) { + return await this.makeRequest( + HttpMethod.DELETE, + `/database/rows/table/${table_id}/${row_id}/` + ); + } + async getRow(table_id: number, row_id: number) { + return await this.makeRequest( + HttpMethod.GET, + `/database/rows/table/${table_id}/${row_id}/`, + { + user_field_names: 'true', + } + ); + } + async listRows( + table_id: number, + limit?: number, + search?: string, + order_by?: string + ) { + return await this.makeRequest( + HttpMethod.GET, + `/database/rows/table/${table_id}/`, + prepareQuery({ + user_field_names: 'true', + size: limit, + search: search, + order_by: order_by, + }) + ); + } +} diff --git a/packages/pieces/community/baserow/src/lib/common/constants.ts b/packages/pieces/community/baserow/src/lib/common/constants.ts new file mode 100644 index 0000000..6910056 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/common/constants.ts @@ -0,0 +1,26 @@ +export const enum BaserowFieldType { + TEXT = 'text', + BOOLEAN = 'boolean', + LONG_TEXT = 'long_text', + LINK_TO_TABLE = 'link_row', + NUMBER = 'number', + RATING = 'rating', + DATE = 'date', + EMAIL='email', + DURATION = 'duration', + LAST_MODIFIED = 'last_modified', + LAST_MODIFIED_BY = 'last_modified_by', + CREATED_ON = 'created_on', + CREATED_BY = 'created_by', + URL = 'url', + FILE = 'file', + SINGLE_SELECT = 'single_select', + MULTI_SELECT = 'multiple_select', + PHONE_NUMBER = 'phone_number', + COUNT = 'count', + ROLLUP = 'rollup', + LOOKUP = 'lookup', + MULTIPLE_COLLABORATORS = 'multiple_collaborators', + UUID = 'uuid', + AUTO_NUMBER = 'autonumber', +} diff --git a/packages/pieces/community/baserow/src/lib/common/index.ts b/packages/pieces/community/baserow/src/lib/common/index.ts new file mode 100644 index 0000000..06111e9 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/common/index.ts @@ -0,0 +1,157 @@ +import { + DynamicPropsValue, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { baserowAuth } from '../../'; +import { BaserowClient } from './client'; +import { BaserowFieldType } from './constants'; + +export function makeClient( + auth: PiecePropValueSchema +): BaserowClient { + const client = new BaserowClient(auth.apiUrl, auth.token); + return client; +} +export const baserowCommon = { + tableFields: (required = true) => + Property.DynamicProperties({ + displayName: 'Table Fields', + required, + refreshers: ['table_id'], + props: async ({ auth, table_id }) => { + if (!auth || !table_id) return {}; + + const fields: DynamicPropsValue = {}; + try { + const client = makeClient( + auth as PiecePropValueSchema + ); + const tableFields = await client.listTableFields( + table_id as unknown as number + ); + for (const field of tableFields) { + if ( + !field.read_only && + ![BaserowFieldType.FILE].includes(field.type) + ) { + switch (field.type) { + case BaserowFieldType.BOOLEAN: + fields[field.name] = Property.Checkbox({ + displayName: field.name, + required: false, + }); + break; + case BaserowFieldType.RATING: + fields[field.name] = Property.Number({ + displayName: field.name, + required: false, + description: `Enter valid value between 1 and ${field.max_value}.`, + }); + break; + case BaserowFieldType.DATE: + fields[field.name] = Property.DateTime({ + displayName: field.name, + required: false, + description: `Enter date in ${field.date_format} format ${ + field.date_include_time + ? 'and time in ' + + field.date_time_format + + ' hour format' + : '' + }.`, + }); + break; + case BaserowFieldType.DURATION: + fields[field.name] = Property.Number({ + displayName: field.name, + required: false, + }); + break; + case BaserowFieldType.LINK_TO_TABLE: + fields[field.name] = Property.Array({ + displayName: field.name, + required: false, + + description: `Enter row ids from table(ID: ${field.link_row_table_id}) that you want to link to.`, + }); + break; + case BaserowFieldType.LONG_TEXT: + fields[field.name] = Property.LongText({ + displayName: field.name, + required: false, + }); + break; + case BaserowFieldType.MULTIPLE_COLLABORATORS: + fields[field.name] = Property.Array({ + displayName: field.name, + required: false, + description: 'Enter user ids that you want to link to.', + }); + break; + case BaserowFieldType.SINGLE_SELECT: + fields[field.name] = Property.StaticDropdown({ + displayName: field.name, + required: false, + options: { + disabled: false, + options: field.select_options.map((option) => { + return { + label: option.value, + value: option.value, + }; + }), + }, + }); + break; + case BaserowFieldType.MULTI_SELECT: + fields[field.name] = Property.StaticMultiSelectDropdown({ + displayName: field.name, + required: false, + options: { + disabled: false, + options: field.select_options.map((option) => { + return { + label: option.value, + value: option.value, + }; + }), + }, + }); + break; + case BaserowFieldType.NUMBER: + fields[field.name] = Property.Number({ + displayName: field.name, + required: false, + }); + break; + case BaserowFieldType.EMAIL: + case BaserowFieldType.PHONE_NUMBER: + fields[field.name] = Property.ShortText({ + displayName: field.name, + required: false, + }); + break; + case BaserowFieldType.TEXT: + fields[field.name] = Property.ShortText({ + displayName: field.name, + required: false, + defaultValue: field.text_default, + }); + break; + case BaserowFieldType.URL: + fields[field.name] = Property.ShortText({ + displayName: field.name, + required: false, + }); + break; + } + } + } + } catch (error) { + console.log('Invalid Baserow Table ID.'); + } + return fields; + }, + }), +}; diff --git a/packages/pieces/community/baserow/src/lib/common/types.ts b/packages/pieces/community/baserow/src/lib/common/types.ts new file mode 100644 index 0000000..05def4b --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/common/types.ts @@ -0,0 +1,148 @@ +import { BaserowFieldType } from './constants'; + +interface BaserowCommonField { + id: number; + table_id: number; + name: string; + order: number; + primary: boolean; + read_only: boolean; +} +interface TextField extends BaserowCommonField { + type: BaserowFieldType.TEXT; + text_default: string; +} +interface BooleanField extends BaserowCommonField { + type: BaserowFieldType.BOOLEAN; +} +interface LongTextField extends BaserowCommonField { + type: BaserowFieldType.LONG_TEXT; +} +interface LastModiedByField extends BaserowCommonField { + type: BaserowFieldType.LAST_MODIFIED_BY; +} +interface CreatedByField extends BaserowCommonField { + type: BaserowFieldType.CREATED_BY; +} + +interface DurationField extends BaserowCommonField { + type: BaserowFieldType.DURATION; + duration_format:string +} + +interface EmailField extends BaserowCommonField { + type: BaserowFieldType.EMAIL; +} +interface URLField extends BaserowCommonField { + type: BaserowFieldType.URL; +} +interface FileField extends BaserowCommonField { + type: BaserowFieldType.FILE; +} +interface LinkToTableField extends BaserowCommonField { + type: BaserowFieldType.LINK_TO_TABLE; + link_row_table_id: number; + link_row_related_field_id: number; + link_row_table: number; + link_row_related_field: number; +} +interface NumberField extends BaserowCommonField { + type: BaserowFieldType.NUMBER; + number_decimal_places: number; + number_negative: boolean; +} +interface RatingField extends BaserowCommonField { + type: BaserowFieldType.RATING; + max_value: number; + color: string; + style: string; +} +interface DateField extends BaserowCommonField { + type: BaserowFieldType.DATE; + date_format: string; + date_include_time: boolean; + date_time_format: string; + date_show_tzinfo: boolean; + date_force_timezone: string | null; +} +interface LastModifiedFieldD extends BaserowCommonField { + type: BaserowFieldType.LAST_MODIFIED; + date_format: string; + date_include_time: boolean; + date_time_format: string; + date_show_tzinfo: boolean; + date_force_timezone: string | null; +} +interface CreatedOnField extends BaserowCommonField { + type: BaserowFieldType.CREATED_ON; + date_format: string; + date_include_time: boolean; + date_time_format: string; + date_show_tzinfo: boolean; + date_force_timezone: string | null; +} + +interface SingleSelectField extends BaserowCommonField { + type: BaserowFieldType.SINGLE_SELECT; + select_options: { + id: number; + value: string; + color: string; + }[]; +} +interface MultiSelectField extends BaserowCommonField { + type: BaserowFieldType.MULTI_SELECT; + select_options: { + id: number; + value: string; + color: string; + }[]; +} +interface PhoneNumberField extends BaserowCommonField { + type: BaserowFieldType.PHONE_NUMBER; +} + +interface CountField extends BaserowCommonField { + type: BaserowFieldType.COUNT; +} +interface RollUpField extends BaserowCommonField { + type: BaserowFieldType.ROLLUP; +} +interface LookUpField extends BaserowCommonField { + type: BaserowFieldType.LOOKUP; +} +interface UUIDField extends BaserowCommonField { + type: BaserowFieldType.UUID; +} +interface AutoNumberField extends BaserowCommonField { + type: BaserowFieldType.AUTO_NUMBER; +} +interface MultipleCollaboratorsField extends BaserowCommonField { + type: BaserowFieldType.MULTIPLE_COLLABORATORS; + notify_user_when_added: boolean; +} +export type BaserowField = + | TextField + | BooleanField + | LongTextField + | LastModiedByField + | CreatedByField + | DurationField + | EmailField + | URLField + | FileField + | LinkToTableField + | NumberField + | RatingField + | DateField + | LastModifiedFieldD + | CreatedOnField + | SingleSelectField + | MultiSelectField + | PhoneNumberField + | CountField + | RollUpField + | LookUpField + | UUIDField + | AutoNumberField + | MultipleCollaboratorsField; diff --git a/packages/pieces/community/baserow/tsconfig.json b/packages/pieces/community/baserow/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/baserow/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/baserow/tsconfig.lib.json b/packages/pieces/community/baserow/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/baserow/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/beamer/.eslintrc.json b/packages/pieces/community/beamer/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/beamer/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/beamer/README.md b/packages/pieces/community/beamer/README.md new file mode 100644 index 0000000..2687d0f --- /dev/null +++ b/packages/pieces/community/beamer/README.md @@ -0,0 +1,7 @@ +# pieces-beamer + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-beamer` to build the library. diff --git a/packages/pieces/community/beamer/package.json b/packages/pieces/community/beamer/package.json new file mode 100644 index 0000000..c434568 --- /dev/null +++ b/packages/pieces/community/beamer/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-beamer", + "version": "0.0.4" +} \ No newline at end of file diff --git a/packages/pieces/community/beamer/project.json b/packages/pieces/community/beamer/project.json new file mode 100644 index 0000000..8e280e5 --- /dev/null +++ b/packages/pieces/community/beamer/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-beamer", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/beamer/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/beamer", + "tsConfig": "packages/pieces/community/beamer/tsconfig.lib.json", + "packageJson": "packages/pieces/community/beamer/package.json", + "main": "packages/pieces/community/beamer/src/index.ts", + "assets": [ + "packages/pieces/community/beamer/*.md", + { + "input": "packages/pieces/community/beamer/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-beamer {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/beamer/src/index.ts b/packages/pieces/community/beamer/src/index.ts new file mode 100644 index 0000000..12ba707 --- /dev/null +++ b/packages/pieces/community/beamer/src/index.ts @@ -0,0 +1,57 @@ +import { createCustomApiCallAction, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createComment } from './lib/actions/create-comment'; +import { createNewFeatureRequest } from './lib/actions/create-feature-request'; +import { createBeamerPost } from './lib/actions/create-posts'; +import { createVote } from './lib/actions/create-vote'; +import { beamerCommon } from './lib/common'; +import { newPost } from './lib/trigger/new-post'; + +export const beamerAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'API key acquired from your Beamer settings', + validate: async ({ auth }) => { + try { + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${beamerCommon.baseUrl}/ping`, + headers: { + 'Beamer-Api-Key': `${auth}`, + }, + }) + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API key.', + }; + } + }, +}); + +export const beamer = createPiece({ + displayName: 'Beamer', + description: 'Engage users with targeted announcements', + logoUrl: 'https://cdn.activepieces.com/pieces/beamer.png', + categories: [PieceCategory.PRODUCTIVITY], + auth: beamerAuth, + authors: ["i-nithin","kishanprmr","MoShizzle","abuaboud"], + actions: [ + createBeamerPost, + createNewFeatureRequest, + createComment, + createVote, + createCustomApiCallAction({ + baseUrl: () => beamerCommon.baseUrl, + auth: beamerAuth, + authMapping: async (auth) => ({ + 'Beamer-Api-Key': `${auth}`, + }), + }), + ], + triggers: [newPost], +}); diff --git a/packages/pieces/community/beamer/src/lib/actions/create-comment.ts b/packages/pieces/community/beamer/src/lib/actions/create-comment.ts new file mode 100644 index 0000000..bcefad3 --- /dev/null +++ b/packages/pieces/community/beamer/src/lib/actions/create-comment.ts @@ -0,0 +1,57 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { beamerAuth } from '../../index'; +import { beamerCommon } from '../common'; +import { + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; + +export const createComment = createAction({ + auth: beamerAuth, + name: 'create_new_comment', + displayName: 'Create a new comment', + description: 'Create a new comment for a Feature request', + props: { + featureRequestId: Property.Number({ + displayName: 'ID', + description: 'ID of the feature request', + required: true, + }), + userId: Property.Number({ + displayName: 'User ID', + description: 'id of the user making the comment', + required: false, + }), + text: Property.LongText({ + displayName: 'Text', + description: 'Content of your comment', + required: true, + }), + userEmail: Property.ShortText({ + displayName: 'User Email', + description: 'Feature requested by..', + required: false, + }), + }, + async run(context) { + const apiKey = context.auth; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${beamerCommon.baseUrl}/requests/${context.propsValue.featureRequestId}/comments`, + headers: { + 'Beamer-Api-Key': `${apiKey}`, + 'Content-Type': 'application/json', + }, + body: { + text: context.propsValue.text, + userId: context.propsValue.userId, + userEmail: context.propsValue.userEmail, + visible: 'true', + }, + }; + const res = await httpClient.sendRequest(request); + return res.body; + }, +}); diff --git a/packages/pieces/community/beamer/src/lib/actions/create-feature-request.ts b/packages/pieces/community/beamer/src/lib/actions/create-feature-request.ts new file mode 100644 index 0000000..c7e7bee --- /dev/null +++ b/packages/pieces/community/beamer/src/lib/actions/create-feature-request.ts @@ -0,0 +1,122 @@ +import { beamerAuth } from '../../index'; +import { + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { beamerCommon } from '../common'; + +export const createNewFeatureRequest = createAction({ + auth: beamerAuth, + name: 'create_new_feature_request', + displayName: 'Create New Feature Request ', + description: 'Create New Feature Request', + props: { + title: Property.ShortText({ + displayName: 'Title', + description: 'Enter the Title of the post', + required: true, + }), + content: Property.LongText({ + displayName: 'Description', + description: 'Enter the description of the post', + required: true, + }), + visible: Property.StaticDropdown({ + displayName: 'Visibility', + description: 'Set visiblisty', + required: true, + options: { + options: [ + { + label: 'Public', + + value: true, + }, + { + label: 'Internal', + value: false, + }, + ], + }, + }), + category: Property.StaticDropdown({ + displayName: 'Category', + description: 'Set Category', + required: true, + options: { + options: [ + { + label: 'Bug', + + value: 'bug', + }, + { + label: 'Feature Request', + value: 'feature_request', + }, + { + label: 'Improvement', + value: 'improvement', + }, + ], + }, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + description: 'Set status', + required: true, + options: { + options: [ + { + label: 'Under Review', + value: 'under_review', + }, + { + label: 'Planned', + value: 'planned', + }, + { + label: 'In Progress', + value: 'in_progress', + }, + { + label: 'Completed', + value: 'completed', + }, + ], + }, + }), + + requestedby: Property.ShortText({ + displayName: 'User Email', + description: 'Feature requested by..', + required: true, + }), + }, + + async run(context) { + const apiKey = context.auth; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${beamerCommon.baseUrl}/requests`, + headers: { + 'Beamer-Api-Key': `${apiKey}`, + 'Content-Type': 'application/json', + }, + body: { + title: [context.propsValue.title], + content: [context.propsValue.content], + visible: context.propsValue.visible, + category: context.propsValue.category, + status: context.propsValue.status, + userEmail: context.propsValue.requestedby, + }, + }; + const res = await httpClient.sendRequest(request); + + return res.body; + }, +}); diff --git a/packages/pieces/community/beamer/src/lib/actions/create-posts.ts b/packages/pieces/community/beamer/src/lib/actions/create-posts.ts new file mode 100644 index 0000000..94f20a9 --- /dev/null +++ b/packages/pieces/community/beamer/src/lib/actions/create-posts.ts @@ -0,0 +1,160 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { beamerAuth } from '../../index'; +import { beamerCommon } from '../common'; + +export const createBeamerPost = createAction({ + auth: beamerAuth, + name: 'create_beamer_post', + displayName: 'Create Beamer Post ', + description: 'Create a new post in Beamer', + props: { + title: Property.ShortText({ + displayName: 'Title', + description: 'Enter the Title of the post', + required: true, + defaultValue: 'Post name', + }), + description: Property.LongText({ + displayName: 'Description', + description: 'Enter the description of the post', + required: true, + defaultValue: 'desc...', + }), + category: Property.StaticDropdown({ + displayName: 'Category', + description: 'Set Category', + required: true, + options: { + options: [ + { + label: 'New', + value: 'new', + }, + { + label: 'Fix', + value: 'fix', + }, + { + label: 'Coming Soon ', + value: 'coming_soon', + }, + { + label: 'Announcement', + value: 'announcement', + }, + { + label: 'Improvement', + value: 'improvement', + }, + ], + }, + }), + showInWidget: Property.StaticDropdown({ + displayName: 'Show In Widget ', + description: 'Check to enable widget', + required: false, + options: { + options: [ + { + label: 'True', + value: true, + }, + { + label: 'False', + value: false, + }, + ], + }, + }), + showInStandalone: Property.StaticDropdown({ + displayName: 'Show In Standalone ', + description: 'Check to enable Standalone', + required: false, + options: { + options: [ + { + label: 'True', + value: true, + }, + { + label: 'False', + value: false, + }, + ], + }, + }), + enableFeedback: Property.Checkbox({ + displayName: 'Enable Feedback', + description: 'Check to enable feedback', + required: false, + defaultValue: false, + }), + enableReactions: Property.Checkbox({ + displayName: 'Enable Reactions', + description: 'Check to enable feedback', + required: false, + defaultValue: false, + }), + enableSocialShare: Property.Checkbox({ + displayName: 'Enable Social share', + description: 'Check to enable social share', + required: false, + defaultValue: false, + }), + autoOpen: Property.Checkbox({ + displayName: 'Enable Auto open', + description: 'Check to enable auto open', + required: false, + defaultValue: false, + }), + sendPushNotification: Property.Checkbox({ + displayName: 'Enable Push Notifications', + description: 'Check to enable Push Notifications', + required: false, + defaultValue: false, + }), + userEmail: Property.ShortText({ + displayName: 'User Email', + description: 'Enter user email', + required: true, + }), + }, + + async run(context) { + const apiKey = context.auth; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${beamerCommon.baseUrl}/posts`, + headers: { + 'Beamer-Api-Key': `${apiKey}`, + 'Content-Type': 'application/json', + }, + body: { + title: [context.propsValue.title], + content: [context.propsValue.description], + category: context.propsValue.category, + publish: true, + archive: false, + pinned: false, + showInWidget: context.propsValue.showInWidget, + showInStandalone: context.propsValue.showInStandalone, + enableFeedback: context.propsValue.enableFeedback, + enableReactions: context.propsValue.enableReactions, + enableSocialShare: context.propsValue.enableSocialShare, + autoOpen: context.propsValue.autoOpen, + sendPushNotification: context.propsValue.sendPushNotification, + userEmail: context.propsValue.userEmail, + fixedBoostedAnnouncement: true, + }, + }; + const res = await httpClient.sendRequest(request); + + return res.body; + }, +}); diff --git a/packages/pieces/community/beamer/src/lib/actions/create-vote.ts b/packages/pieces/community/beamer/src/lib/actions/create-vote.ts new file mode 100644 index 0000000..65ce422 --- /dev/null +++ b/packages/pieces/community/beamer/src/lib/actions/create-vote.ts @@ -0,0 +1,48 @@ +import { httpClient, HttpMethod, HttpRequest } from '@activepieces/pieces-common'; +import { beamerAuth } from '../../index'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { beamerCommon } from '../common'; + +export const createVote = createAction({ + auth: beamerAuth, + name: 'create_vote', + displayName: 'Create a new vote ', + description: 'Create a new vote on a feature request', + props: { + featureRequestId: Property.Number({ + displayName: 'Feature ID', + description: 'ID of the feature request to create a comment for', + required: true, + }), + userFirstname: Property.ShortText({ + displayName: 'User Firstname', + description: 'Feature requested by..', + required: false, + }), + userEmail: Property.ShortText({ + displayName: 'User Email', + description: 'Feature requested by..', + required: false, + }), + }, + async run(context) { + const apiKey = context.auth; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${beamerCommon.baseUrl}/requests/${context.propsValue.featureRequestId}/votes`, + headers: { + 'Beamer-Api-Key': `${apiKey}`, + 'Content-Type': 'application/json', + }, + body: { + userFirstname: context.propsValue.userFirstname, + email: context.propsValue.userEmail, + }, + }; + + const res = await httpClient.sendRequest(request); + return res.body; + + }, +}); diff --git a/packages/pieces/community/beamer/src/lib/common/index.ts b/packages/pieces/community/beamer/src/lib/common/index.ts new file mode 100644 index 0000000..b7c73dd --- /dev/null +++ b/packages/pieces/community/beamer/src/lib/common/index.ts @@ -0,0 +1,3 @@ +export const beamerCommon = { + baseUrl: 'https://api.getbeamer.com/v0', +}; diff --git a/packages/pieces/community/beamer/src/lib/trigger/new-post.ts b/packages/pieces/community/beamer/src/lib/trigger/new-post.ts new file mode 100644 index 0000000..963b6b5 --- /dev/null +++ b/packages/pieces/community/beamer/src/lib/trigger/new-post.ts @@ -0,0 +1,36 @@ +import { + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; + +const markdown = ` +- Go to the "Integration" section. +- Find and click on the "Webhook" plugin to activate it. +- Add a webhook to that form. +- In the webhook settings, paste this URL: + \`{{webhookUrl}}\` +`; + +export const newPost = createTrigger({ + name: 'new_post_on_beamer', + displayName: 'New Beamer Post', + description: 'Triggers when new post is found in your beamer account', + props: { + md: Property.MarkDown({ + value: markdown, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: {}, + + async onEnable(context) { + // IGNORED + }, + async onDisable(context) { + // IGNORED + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/beamer/tsconfig.json b/packages/pieces/community/beamer/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/beamer/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/beamer/tsconfig.lib.json b/packages/pieces/community/beamer/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/beamer/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/beehiiv/.eslintrc.json b/packages/pieces/community/beehiiv/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/beehiiv/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/beehiiv/README.md b/packages/pieces/community/beehiiv/README.md new file mode 100644 index 0000000..badb309 --- /dev/null +++ b/packages/pieces/community/beehiiv/README.md @@ -0,0 +1,7 @@ +# pieces-beehiiv + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-beehiiv` to build the library. diff --git a/packages/pieces/community/beehiiv/package.json b/packages/pieces/community/beehiiv/package.json new file mode 100644 index 0000000..2efff39 --- /dev/null +++ b/packages/pieces/community/beehiiv/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-beehiiv", + "version": "0.0.1" +} diff --git a/packages/pieces/community/beehiiv/project.json b/packages/pieces/community/beehiiv/project.json new file mode 100644 index 0000000..5a27e54 --- /dev/null +++ b/packages/pieces/community/beehiiv/project.json @@ -0,0 +1,45 @@ +{ + "name": "pieces-beehiiv", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/beehiiv/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/beehiiv", + "tsConfig": "packages/pieces/community/beehiiv/tsconfig.lib.json", + "packageJson": "packages/pieces/community/beehiiv/package.json", + "main": "packages/pieces/community/beehiiv/src/index.ts", + "assets": [ + "packages/pieces/community/beehiiv/*.md" + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/beehiiv/src/index.ts b/packages/pieces/community/beehiiv/src/index.ts new file mode 100644 index 0000000..7696a5f --- /dev/null +++ b/packages/pieces/community/beehiiv/src/index.ts @@ -0,0 +1,39 @@ +import { createPiece } from '@activepieces/pieces-framework'; +import { createSubscriptionAction } from './lib/actions/create-subscription.action'; +import { updateSubscriptionAction } from './lib/actions/update-subscription.action'; +import { addSubscriptionToAutomationAction } from './lib/actions/add-subscription-to-automation.action'; +import { listAutomationsAction } from './lib/actions/list-automations.action'; +import { listPostsAction } from './lib/actions/list-posts.action'; +import { newPostSentTrigger } from './lib/triggers/new-post-sent.trigger'; +import { userUnsubscribesTrigger } from './lib/triggers/user-unsubscribes.trigger'; +import { newSubscriptionConfirmedTrigger } from './lib/triggers/new-subscription-confirmed.trigger'; +import { PieceCategory } from '@activepieces/shared'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { beehiivAuth } from './lib/common/auth'; +import { BEEHIIV_API_URL } from './lib/common/client'; + +export const beehiiv = createPiece({ + displayName: 'Beehiiv', + auth: beehiivAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/beehiiv.png', + authors: ['onyedikachi-david', 'kishanprmr'], + categories: [PieceCategory.MARKETING], + actions: [ + createSubscriptionAction, + updateSubscriptionAction, + addSubscriptionToAutomationAction, + listAutomationsAction, + listPostsAction, + createCustomApiCallAction({ + auth: beehiivAuth, + baseUrl: () => BEEHIIV_API_URL, + authMapping: async (auth) => { + return { + Authorization: `Bearer ${auth}`, + }; + }, + }), + ], + triggers: [newPostSentTrigger, userUnsubscribesTrigger, newSubscriptionConfirmedTrigger], +}); diff --git a/packages/pieces/community/beehiiv/src/lib/actions/add-subscription-to-automation.action.ts b/packages/pieces/community/beehiiv/src/lib/actions/add-subscription-to-automation.action.ts new file mode 100644 index 0000000..787a282 --- /dev/null +++ b/packages/pieces/community/beehiiv/src/lib/actions/add-subscription-to-automation.action.ts @@ -0,0 +1,55 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { beehiivAuth } from '../common/auth'; +import { automationId, publicationId, subscriptionId } from '../common/props'; +import { beehiivApiCall } from '../common/client'; + +export const addSubscriptionToAutomationAction = createAction({ + auth: beehiivAuth, + name: 'add_subscription_to_automation', + displayName: 'Add Subscription to Automation', + description: 'Adds an existing subscription to a specific automation flow.', + props: { + publicationId: publicationId, + automationId: automationId('Automation ID', '', true,true), + email: Property.ShortText({ + displayName: 'Subscription Email', + description: 'The email address of the subscription. Provide either Email or Subscription ID.', + required: false, + }), + subscription_id:subscriptionId(), + double_opt_override: Property.ShortText({ + displayName: 'Double Opt-in Override', + description: 'Override publication double-opt settings for this subscription (e.g., "on").', + required: false, + }), + }, + async run(context) { + const { publicationId, automationId, email, subscription_id, double_opt_override } = + context.propsValue; + + if (!email && !subscription_id) { + throw new Error('Either Subscription Email or Subscription ID must be provided.'); + } + + const body: Record = {}; + if (email) { + body['email'] = email; + } + if (subscription_id) { + body['subscription_id'] = subscription_id; + } + if (double_opt_override) { + body['double_opt_override'] = double_opt_override; + } + + const response = await beehiivApiCall({ + apiKey: context.auth, + method: HttpMethod.POST, + resourceUri: `/publications/${publicationId}/automations/${automationId}/journeys`, + body, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/beehiiv/src/lib/actions/create-subscription.action.ts b/packages/pieces/community/beehiiv/src/lib/actions/create-subscription.action.ts new file mode 100644 index 0000000..5f50e4d --- /dev/null +++ b/packages/pieces/community/beehiiv/src/lib/actions/create-subscription.action.ts @@ -0,0 +1,133 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { beehiivAuth } from '../common/auth'; +import { automationId, customFields, publicationId } from '../common/props'; +import { beehiivApiCall } from '../common/client'; + +export const createSubscriptionAction = createAction({ + auth: beehiivAuth, + name: 'create_subscription', + displayName: 'Create Subscription', + description: 'Creates a new subscription.', + props: { + publicationId: publicationId, + email: Property.ShortText({ + displayName: 'Email', + description: 'The email address of the new subscription.', + required: true, + }), + reactivate_existing: Property.Checkbox({ + displayName: 'Reactivate Existing', + description: + 'Whether to reactivate the subscription if they have already unsubscribed. Use only if the subscription is knowingly resubscribing.', + required: false, + defaultValue: false, + }), + send_welcome_email: Property.Checkbox({ + displayName: 'Send Welcome Email', + description: 'Whether to send the default welcome email to the subscription.', + required: false, + defaultValue: false, + }), + utm_source: Property.ShortText({ + displayName: 'UTM Source', + description: 'The source of the subscription.', + required: false, + }), + utm_medium: Property.ShortText({ + displayName: 'UTM Medium', + description: 'The medium of the subscription.', + required: false, + }), + utm_campaign: Property.ShortText({ + displayName: 'UTM Campaign', + description: 'The acquisition campaign of the subscription.', + required: false, + }), + referring_site: Property.ShortText({ + displayName: 'Referring Site', + description: 'The website that the subscription was referred from.', + required: false, + }), + referral_code: Property.ShortText({ + displayName: 'Referral Code', + description: "A subscription's referral_code to give them credit for the new subscription.", + required: false, + }), + tier: Property.StaticDropdown({ + displayName: 'Subscription Tier', + description: 'The tier for this subscription.', + required: false, + options: { + options: [ + { label: 'Free', value: 'free' }, + { label: 'Premium', value: 'premium' }, + ], + }, + }), + custom_fields: customFields, + stripe_customer_id: Property.ShortText({ + displayName: 'Stripe Customer ID', + description: 'The Stripe customer ID for this subscription.', + required: false, + }), + double_opt_override: Property.ShortText({ + displayName: 'Double Opt-in Override', + description: 'Override publication double-opt settings for this subscription.', + required: false, + }), + premium_tier_ids: Property.Array({ + displayName: 'Premium Tier IDs', + description: 'The IDs of the premium tiers this subscription is associated with.', + required: false, + }), + automation_ids: automationId('Automation IDs','Enroll the subscription into automations after their subscription has been created.',false), + }, + async run(context) { + const { + publicationId, + email, + utm_campaign, + utm_medium, + tier, + utm_source, + reactivate_existing, + referral_code, + referring_site, + send_welcome_email, + stripe_customer_id, + double_opt_override, + } = context.propsValue; + + const customFields = context.propsValue.custom_fields ?? {}; + const automationIds = context.propsValue.automation_ids ?? []; + const premiumTierIds = context.propsValue.premium_tier_ids ?? []; + + const response = await beehiivApiCall({ + apiKey: context.auth, + method: HttpMethod.POST, + resourceUri: `/publications/${publicationId}/subscriptions`, + body: { + email, + reactivate_existing, + send_welcome_email, + utm_source, + utm_campaign, + utm_medium, + referring_site, + referral_code, + double_opt_override, + tier, + premium_tier_ids: premiumTierIds.length > 0 ? premiumTierIds : undefined, + stripe_customer_id, + automation_ids: automationIds.length > 0 ? automationIds : undefined, + custom_fields: Object.entries(customFields).map(([key, value]) => ({ + name: key, + value: value, + })), + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/beehiiv/src/lib/actions/list-automations.action.ts b/packages/pieces/community/beehiiv/src/lib/actions/list-automations.action.ts new file mode 100644 index 0000000..09d0185 --- /dev/null +++ b/packages/pieces/community/beehiiv/src/lib/actions/list-automations.action.ts @@ -0,0 +1,51 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { publicationId } from '../common/props'; +import { beehiivAuth } from '../common/auth'; +import { beehiivApiCall, BeehiivPaginatedApiCall } from '../common/client'; +import { isNil } from '@activepieces/shared'; + +export const listAutomationsAction = createAction({ + auth: beehiivAuth, + name: 'list_automations', + displayName: 'List Automations', + description: 'Retrieves a list of automations for a publication.', + props: { + publicationId: publicationId, + limit: Property.Number({ + displayName: 'Limit', + description: 'A limit on the number of automations to be returned (1-100, default 10).', + required: false, + }), + page: Property.Number({ + displayName: 'Page', + description: 'The page number for pagination (default 1).', + required: false, + }), + }, + async run(context) { + const { publicationId, page, limit } = context.propsValue; + + if (isNil(page) && isNil(limit)) { + const response = await BeehiivPaginatedApiCall({ + apiKey: context.auth, + method: HttpMethod.GET, + resourceUri: `/publications/${publicationId}/automations`, + }); + + return response; + } + + const response = await beehiivApiCall<{ data: Record[] }>({ + apiKey: context.auth, + method: HttpMethod.GET, + resourceUri: `/publications/${publicationId}/automations`, + query: { + page, + limit, + }, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/beehiiv/src/lib/actions/list-posts.action.ts b/packages/pieces/community/beehiiv/src/lib/actions/list-posts.action.ts new file mode 100644 index 0000000..c58dea2 --- /dev/null +++ b/packages/pieces/community/beehiiv/src/lib/actions/list-posts.action.ts @@ -0,0 +1,125 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { beehiivAuth } from '../common/auth'; +import { publicationId } from '../common/props'; +import { isNil } from '@activepieces/shared'; +import { beehiivApiCall, BeehiivPaginatedApiCall } from '../common/client'; + +export const listPostsAction = createAction({ + auth: beehiivAuth, + name: 'list_posts', + displayName: 'List Posts', + description: 'Retrieves all posts belonging to a specific publication.', + props: { + publicationId: publicationId, + expand: Property.StaticMultiSelectDropdown({ + displayName: 'Expand Results', + description: 'Optionally expand the results by adding additional information.', + required: false, + options: { + options: [ + { label: 'Stats', value: 'stats' }, + { label: 'Free Web Content', value: 'free_web_content' }, + { label: 'Free Email Content', value: 'free_email_content' }, + { label: 'Free RSS Content', value: 'free_rss_content' }, + { label: 'Premium Web Content', value: 'premium_web_content' }, + { label: 'Premium Email Content', value: 'premium_email_content' }, + ], + }, + }), + audience: Property.StaticDropdown({ + displayName: 'Audience', + required: true, + options: { + options: [ + { label: 'All', value: 'all' }, + { label: 'Free', value: 'free' }, + { label: 'Premium', value: 'premium' }, + ], + }, + }), + platform: Property.StaticDropdown({ + displayName: 'Platform', + required: true, + options: { + options: [ + { label: 'All', value: 'all' }, + { label: 'Web', value: 'web' }, + { label: 'Email', value: 'email' }, + { label: 'Both', value: 'both' }, + ], + }, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + required: true, + options: { + options: [ + { label: 'All', value: 'all' }, + { label: 'Draft', value: 'draft' }, + { label: 'Confirmed', value: 'confirmed' }, + { label: 'Archived', value: 'archived' }, + ], + }, + }), + content_tags: Property.Array({ + displayName: 'Content Tags', + description: 'Filter posts by content tags. Returns posts with ANY of the specified tags.', + required: false, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Number of posts to return (1-100, default 10).', + required: false, + }), + page: Property.Number({ + displayName: 'Page', + description: 'Page number for pagination (default 1).', + required: false, + }), + }, + async run(context) { + const { publicationId, page, limit, status, platform, audience } = context.propsValue; + const expand = context.propsValue.expand ?? []; + const tags = context.propsValue.content_tags ?? []; + + const queryParams: Record = { + audience, + platform, + status, + order_by: 'created', + direction: 'desc', + }; + if (expand) { + queryParams['expand'] = (expand as string[]).join(','); + } + + if (tags.length > 0) { + queryParams['content_tags'] = tags as string[]; + } + + if (isNil(page) && isNil(limit)) { + const response = await BeehiivPaginatedApiCall({ + apiKey: context.auth, + method: HttpMethod.GET, + resourceUri: `/publications/${publicationId}/automations`, + query: queryParams, + }); + + return response; + } + + const response = await beehiivApiCall<{ data: Record[] }>({ + apiKey: context.auth, + method: HttpMethod.GET, + resourceUri: `/publications/${publicationId}/automations`, + query: { + page, + limit, + ...queryParams, + }, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/beehiiv/src/lib/actions/update-subscription.action.ts b/packages/pieces/community/beehiiv/src/lib/actions/update-subscription.action.ts new file mode 100644 index 0000000..193431d --- /dev/null +++ b/packages/pieces/community/beehiiv/src/lib/actions/update-subscription.action.ts @@ -0,0 +1,82 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { customFields, publicationId, subscriptionId } from '../common/props'; +import { beehiivAuth } from '../common/auth'; +import { beehiivApiCall } from '../common/client'; + +export const updateSubscriptionAction = createAction({ + auth: beehiivAuth, + name: 'update_subscription', + displayName: 'Update Subscription', + description: 'Update an existing subscription.', + props: { + publicationId: publicationId, + subscriptionId: subscriptionId(true), + tier: Property.StaticDropdown({ + displayName: 'Subscription Tier', + description: 'Set the tier for this subscription.', + required: false, + options: { + options: [ + { label: 'Free', value: 'free' }, + { label: 'Premium', value: 'premium' }, + ], + }, + }), + stripe_customer_id: Property.ShortText({ + displayName: 'Stripe Customer ID', + description: 'The Stripe Customer ID of the subscription.', + required: false, + }), + unsubscribe: Property.Checkbox({ + displayName: 'Unsubscribe', + description: 'Whether to unsubscribe this subscription from the publication.', + required: false, + }), + custom_fields: customFields, + }, + async run(context) { + const { publicationId, subscriptionId, tier, stripe_customer_id, unsubscribe } = + context.propsValue; + + const custom_fields = context.propsValue.custom_fields ?? {}; + + const body: Record = {}; + + if (tier !== undefined) { + body['tier'] = tier; + } + if (stripe_customer_id !== undefined) { + body['stripe_customer_id'] = stripe_customer_id; + } + if (unsubscribe !== undefined) { + body['unsubscribe'] = unsubscribe; + } + + const transformedCustomFields = Object.entries(custom_fields) + .filter(([_, value]) => { + if (Array.isArray(value)) { + return value.length > 0; + } + return value !== undefined && value !== null && value !== ''; + }) + .map(([key, value]) => ({ + name: key, + value: value, + })); + + if (transformedCustomFields.length > 0) { + body['custom_fields'] = transformedCustomFields; + } + + console.log(JSON.stringify(body)); + const response = await beehiivApiCall({ + apiKey: context.auth, + method: HttpMethod.PUT, + resourceUri: `/publications/${publicationId}/subscriptions/${subscriptionId}`, + body, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/beehiiv/src/lib/common/auth.ts b/packages/pieces/community/beehiiv/src/lib/common/auth.ts new file mode 100644 index 0000000..c743463 --- /dev/null +++ b/packages/pieces/community/beehiiv/src/lib/common/auth.ts @@ -0,0 +1,27 @@ +import { PieceAuth } from '@activepieces/pieces-framework'; +import { beehiivApiCall } from './client'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const beehiivAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: `Your can obtain API key by naviating to **Settings->Workspace Settings-> API**.`, + required: true, + validate: async ({ auth }) => { + try { + await beehiivApiCall({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: '/publications', + }); + + return { + valid: true, + }; + } catch { + return { + valid: false, + error: 'Invalid API key.', + }; + } + }, +}); diff --git a/packages/pieces/community/beehiiv/src/lib/common/client.ts b/packages/pieces/community/beehiiv/src/lib/common/client.ts new file mode 100644 index 0000000..2fc8b64 --- /dev/null +++ b/packages/pieces/community/beehiiv/src/lib/common/client.ts @@ -0,0 +1,99 @@ +import { + AuthenticationType, + httpClient, + HttpMessageBody, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; + +export const BEEHIIV_API_URL = 'https://api.beehiiv.com/v2'; + +export type BeehiivApiCallParams = { + apiKey: string; + method: HttpMethod; + resourceUri: string; + query?: Record; + body?: any; +}; + +export async function beehiivApiCall({ + apiKey, + method, + resourceUri, + query, + body, +}: BeehiivApiCallParams): Promise { + const qs: QueryParams = {}; + + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + qs[key] = String(value); + } + } + } + const request: HttpRequest = { + method, + url: BEEHIIV_API_URL + resourceUri, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: apiKey, + }, + queryParams: qs, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} + +export async function BeehiivPaginatedApiCall({ + apiKey, + method, + resourceUri, + query, + body, +}: BeehiivApiCallParams): Promise { + const resultData: T[] = []; + const limit = 100; + let page = 1; + let totalPages = 1; + + do { + const response = await beehiivApiCall<{ + data: T[]; + page: number; + limit: number; + total_results: number; + total_pages: number; + }>({ + apiKey, + method, + resourceUri, + query: { + ...query, + limit, + page, + }, + body, + }); + + const { data, total_pages } = response; + + if (!data || data.length === 0) break; + + resultData.push(...data); + totalPages = total_pages; + page += 1; + } while (page <= totalPages); + + return resultData; +} + +export interface WebhookPayload { + data: Record; + event_timestamp: number; + event_type: string; + uid: string; +} diff --git a/packages/pieces/community/beehiiv/src/lib/common/props.ts b/packages/pieces/community/beehiiv/src/lib/common/props.ts new file mode 100644 index 0000000..afb411a --- /dev/null +++ b/packages/pieces/community/beehiiv/src/lib/common/props.ts @@ -0,0 +1,164 @@ +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { BeehiivPaginatedApiCall } from './client'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const publicationId = Property.Dropdown({ + displayName: 'Publication', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + const response = await BeehiivPaginatedApiCall<{ id: string; name: string }>({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: '/publications', + }); + + return { + disabled: false, + options: response.map((publication) => { + return { + label: publication.name, + value: publication.id, + }; + }), + }; + }, +}); + +export const subscriptionId =(isRequired=false)=> Property.Dropdown({ + displayName: 'Subscription ID', + refreshers: ['publicationId'], + required: isRequired, + options: async ({ auth, publicationId }) => { + if (!auth || !publicationId) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + const response = await BeehiivPaginatedApiCall<{ id: string; email: string }>({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: `/publications/${publicationId}/subscriptions`, + }); + + return { + disabled: false, + options: response.map((subscription) => { + return { + label: subscription.email, + value: subscription.id, + }; + }), + }; + }, +}); + +export const automationId = ( + displayName: string, + desc: string, + isRequired = false, + isSingleSelect = true, +) => { + const fieldType = isSingleSelect ? Property.Dropdown : Property.MultiSelectDropdown; + return fieldType({ + displayName, + description: desc, + refreshers: ['publicationId'], + required: isRequired, + options: async ({ auth, publicationId }) => { + if (!auth || !publicationId) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + const response = await BeehiivPaginatedApiCall<{ id: string; name: string; status: string }>({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: `/publications/${publicationId}/automations`, + }); + + return { + disabled: false, + options: response + .filter((automation) => automation.status !== 'inactive') + .map((automation) => { + return { + label: automation.name, + value: automation.id, + }; + }), + }; + }, + }); +}; + +export const customFields = Property.DynamicProperties({ + displayName: 'Custom Fields', + refreshers: ['publicationId'], + required: false, + props: async ({ auth, publicationId }) => { + if (!auth || !publicationId) return {}; + + const fields: DynamicPropsValue = {}; + + const response = await BeehiivPaginatedApiCall<{ id: string; kind: string; display: string }>({ + apiKey: auth as unknown as string, + method: HttpMethod.GET, + resourceUri: `/publications/${publicationId}/custom_fields`, + }); + + for (const field of response) { + switch (field.kind) { + case 'string': + fields[field.display] = Property.ShortText({ + displayName: field.display, + required: false, + }); + break; + case 'integer': + fields[field.display] = Property.Number({ + displayName: field.display, + required: false, + }); + break; + case 'boolean': + fields[field.display] = Property.Checkbox({ + displayName: field.display, + required: false, + }); + break; + case 'date': + case 'datetime': + fields[field.display] = Property.DateTime({ + displayName: field.display, + required: false, + }); + break; + case 'list': + fields[field.display] = Property.Array({ + displayName: field.display, + required: false, + }); + break; + default: + break; + } + } + + return fields; + }, +}); diff --git a/packages/pieces/community/beehiiv/src/lib/triggers/new-post-sent.trigger.ts b/packages/pieces/community/beehiiv/src/lib/triggers/new-post-sent.trigger.ts new file mode 100644 index 0000000..fe01f3a --- /dev/null +++ b/packages/pieces/community/beehiiv/src/lib/triggers/new-post-sent.trigger.ts @@ -0,0 +1,66 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { publicationId } from '../common/props'; +import { beehiivAuth } from '../common/auth'; +import { beehiivApiCall, WebhookPayload } from '../common/client'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; + +const TRIGGER_KEY = 'new-post-sent-trigger'; + +export const newPostSentTrigger = createTrigger({ + auth: beehiivAuth, + name: 'beehiiv_new_post_sent', + displayName: 'New Post Sent', + description: 'Triggers when a new post is sent.', + props: { + publicationId: publicationId, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const { publicationId } = context.propsValue; + const response = await beehiivApiCall<{ data: { id: string } }>({ + apiKey: context.auth, + method: HttpMethod.POST, + resourceUri: `/publications/${publicationId}/webhooks`, + body: { + url: context.webhookUrl, + event_types: ['post.sent'], + }, + }); + + await context.store.put(TRIGGER_KEY, response.data.id); + }, + async onDisable(context) { + const { publicationId } = context.propsValue; + + const webhookId = await context.store.get(TRIGGER_KEY); + if (!isNil(webhookId)) { + await beehiivApiCall({ + apiKey: context.auth, + method: HttpMethod.DELETE, + resourceUri: `/publications/${publicationId}/webhooks/${webhookId}`, + }); + } + }, + async run(context) { + const payload = context.payload.body as WebhookPayload; + return [payload.data]; + }, + sampleData: { + audience: 'free', + authors: ['Clark Kent'], + content_tags: ['news'], + created: 1666800076, + id: 'post_00000000-0000-0000-0000-000000000000', + preview_text: 'More news on the horizon', + slug: 'more_news', + split_tested: true, + status: 'confirmed', + subject_line: 'Check this out', + subtitle: 'New post subtitle', + thumbnail_url: 'https://example.com/pictures/thumbnail.png', + title: 'New Post Title', + displayed_date: 1666800076, + web_url: 'https://example.com/more_news', + }, +}); diff --git a/packages/pieces/community/beehiiv/src/lib/triggers/new-subscription-confirmed.trigger.ts b/packages/pieces/community/beehiiv/src/lib/triggers/new-subscription-confirmed.trigger.ts new file mode 100644 index 0000000..217b761 --- /dev/null +++ b/packages/pieces/community/beehiiv/src/lib/triggers/new-subscription-confirmed.trigger.ts @@ -0,0 +1,85 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { publicationId } from '../common/props'; +import { beehiivAuth } from '../common/auth'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { beehiivApiCall, WebhookPayload } from '../common/client'; +import { isNil } from '@activepieces/shared'; + +const TRIGGER_KEY = 'new-subscription-confirmed-trigger'; + +export const newSubscriptionConfirmedTrigger = createTrigger({ + auth: beehiivAuth, + name: 'beehiiv_new_subscription_confirmed', + displayName: 'New Subscription Confirmation', + description: 'Triggers when a new subscriber confirms their subscription.', + props: { + publicationId: publicationId, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const { publicationId } = context.propsValue; + + const response = await beehiivApiCall<{ data: { id: string } }>({ + apiKey: context.auth, + method: HttpMethod.POST, + resourceUri: `/publications/${publicationId}/webhooks`, + body: { + url: context.webhookUrl, + event_types: ['subscription.confirmed'], + }, + }); + + await context.store.put(TRIGGER_KEY, response.data.id); + }, + async onDisable(context) { + const { publicationId } = context.propsValue; + + const webhookId = await context.store.get(TRIGGER_KEY); + if (!isNil(webhookId)) { + await beehiivApiCall({ + apiKey: context.auth, + method: HttpMethod.DELETE, + resourceUri: `/publications/${publicationId}/webhooks/${webhookId}`, + }); + } + }, + async test(context) { + const { publicationId } = context.propsValue; + + const response = await beehiivApiCall<{ data: Record[] }>({ + apiKey: context.auth, + method: HttpMethod.GET, + resourceUri: `/publications/${publicationId}/subscriptions`, + query: { + status: 'active', + limit: 5, + page: 1, + order_by: 'created', + direction: 'desc', + }, + }); + + if (isNil(response.data) || response.data.length == 0) return []; + + return response.data; + }, + async run(context) { + const payload = context.payload.body as WebhookPayload; + return [payload]; + }, + sampleData: { + created: 1666800076, + email: 'example@example.com', + id: 'sub_00000000-0000-0000-0000-000000000000', + referral_code: 'ABC123', + referring_site: 'https://www.blog.com', + status: 'active', + subscription_tier: 'premium', + subscription_premium_tier_names: ['Premium', 'Pro'], + stripe_customer_id: 'cus_00000000000000', + utm_campaign: 'Q1 Campaign', + utm_channel: 'website', + utm_medium: 'organic', + utm_source: 'Twitter', + }, +}); diff --git a/packages/pieces/community/beehiiv/src/lib/triggers/user-unsubscribes.trigger.ts b/packages/pieces/community/beehiiv/src/lib/triggers/user-unsubscribes.trigger.ts new file mode 100644 index 0000000..b9c8d20 --- /dev/null +++ b/packages/pieces/community/beehiiv/src/lib/triggers/user-unsubscribes.trigger.ts @@ -0,0 +1,65 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { publicationId } from '../common/props'; +import { beehiivAuth } from '../common/auth'; +import { beehiivApiCall, WebhookPayload } from '../common/client'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; + +const TRIGGER_KEY = 'user-unsubscribes-trigger'; + +export const userUnsubscribesTrigger = createTrigger({ + auth: beehiivAuth, + name: 'beehiiv_user_unsubscribes', + displayName: 'User Unsubscribes', + description: 'Triggers when a user unsubscribes.', + props: { + publicationId: publicationId, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const { publicationId } = context.propsValue; + + const response = await beehiivApiCall<{ data: { id: string } }>({ + apiKey: context.auth, + method: HttpMethod.POST, + resourceUri: `/publications/${publicationId}/webhooks`, + body: { + url: context.webhookUrl, + event_types: ['subscription.deleted'], + }, + }); + + await context.store.put(TRIGGER_KEY, response.data.id); + }, + async onDisable(context) { + const { publicationId } = context.propsValue; + + const webhookId = await context.store.get(TRIGGER_KEY); + if (!isNil(webhookId)) { + await beehiivApiCall({ + apiKey: context.auth, + method: HttpMethod.DELETE, + resourceUri: `/publications/${publicationId}/webhooks/${webhookId}`, + }); + } + }, + async run(context) { + const payload = context.payload.body as WebhookPayload; + return [payload.data]; + }, + sampleData: { + created: 1666800076, + email: 'example@example.com', + id: 'sub_00000000-0000-0000-0000-000000000000', + referral_code: 'ABC123', + referring_site: 'https://www.blog.com', + status: 'active', + subscription_tier: 'premium', + subscription_premium_tier_names: ['Premium', 'Pro'], + stripe_customer_id: 'cus_00000000000000', + utm_campaign: 'Q1 Campaign', + utm_channel: 'website', + utm_medium: 'organic', + utm_source: 'Twitter', + }, +}); diff --git a/packages/pieces/community/beehiiv/tsconfig.json b/packages/pieces/community/beehiiv/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/beehiiv/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/beehiiv/tsconfig.lib.json b/packages/pieces/community/beehiiv/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/beehiiv/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/bettermode/.eslintrc.json b/packages/pieces/community/bettermode/.eslintrc.json new file mode 100644 index 0000000..15ede17 --- /dev/null +++ b/packages/pieces/community/bettermode/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*", "package.json"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/bettermode/README.md b/packages/pieces/community/bettermode/README.md new file mode 100644 index 0000000..13f2b1b --- /dev/null +++ b/packages/pieces/community/bettermode/README.md @@ -0,0 +1,7 @@ +# pieces-bettermode + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-bettermode` to build the library. diff --git a/packages/pieces/community/bettermode/package.json b/packages/pieces/community/bettermode/package.json new file mode 100644 index 0000000..272b516 --- /dev/null +++ b/packages/pieces/community/bettermode/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-bettermode", + "version": "0.0.5" +} \ No newline at end of file diff --git a/packages/pieces/community/bettermode/project.json b/packages/pieces/community/bettermode/project.json new file mode 100644 index 0000000..68dfbfe --- /dev/null +++ b/packages/pieces/community/bettermode/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-bettermode", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/bettermode/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/bettermode", + "tsConfig": "packages/pieces/community/bettermode/tsconfig.lib.json", + "packageJson": "packages/pieces/community/bettermode/package.json", + "main": "packages/pieces/community/bettermode/src/index.ts", + "assets": [ + "packages/pieces/community/bettermode/*.md", + { + "input": "packages/pieces/community/bettermode/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-bettermode {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/bettermode/src/index.ts b/packages/pieces/community/bettermode/src/index.ts new file mode 100644 index 0000000..3bc188f --- /dev/null +++ b/packages/pieces/community/bettermode/src/index.ts @@ -0,0 +1,34 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { assignBadgeAction } from './lib/actions/assign-badge'; +import { createDiscussionAction } from './lib/actions/create-discussion'; +import { createQuestionAction } from './lib/actions/create-question'; +import { revokeBadgeAction } from './lib/actions/revoke-badge'; +import { bettermodeAuth } from './lib/auth'; + +export const bettermode = createPiece({ + displayName: 'Bettermode', + description: 'Feature-rich engagement platform. Browse beautifully designed templates, each flexible for precise customization to your needs.', + auth: bettermodeAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/bettermode.png', + categories: [PieceCategory.MARKETING], + authors: ["joeworkman","kishanprmr","MoShizzle","abuaboud"], + actions: [ + createDiscussionAction, + createQuestionAction, + assignBadgeAction, + revokeBadgeAction, + createCustomApiCallAction({ + baseUrl: (auth) => (auth as { region: string }).region, // replace with the actual base URL + auth: bettermodeAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers: [], +}); + +// Bettermode API docs: https://developers.bettermode.com/ diff --git a/packages/pieces/community/bettermode/src/lib/actions/assign-badge.ts b/packages/pieces/community/bettermode/src/lib/actions/assign-badge.ts new file mode 100644 index 0000000..56827fc --- /dev/null +++ b/packages/pieces/community/bettermode/src/lib/actions/assign-badge.ts @@ -0,0 +1,33 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { assignBadgeToMember } from '../api'; +import { buildBadgesDropdown } from '../props'; +import { bettermodeAuth, BettermodeAuthType } from '../auth'; + +export const assignBadgeAction = createAction({ + name: 'assign_badge', + auth: bettermodeAuth, + displayName: 'Assign Badge to Member', + description: 'Assign an existing badge to a member by email', + props: { + badgeId: Property.Dropdown({ + displayName: 'Badge', + description: 'The badge to assign', + required: true, + refreshers: [], + options: async ({ auth }) => + await buildBadgesDropdown(auth as BettermodeAuthType), + }), + email: Property.ShortText({ + displayName: 'Email', + description: 'The email of the member to assign the badge to', + required: true, + }), + }, + async run(context) { + return await assignBadgeToMember( + context.auth as BettermodeAuthType, + context.propsValue.badgeId, + context.propsValue.email + ); + }, +}); diff --git a/packages/pieces/community/bettermode/src/lib/actions/create-discussion.ts b/packages/pieces/community/bettermode/src/lib/actions/create-discussion.ts new file mode 100644 index 0000000..064a5ae --- /dev/null +++ b/packages/pieces/community/bettermode/src/lib/actions/create-discussion.ts @@ -0,0 +1,52 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { createDiscussion } from '../api'; +import { buildMemberSpacesDropdown } from '../props'; +import { bettermodeAuth, BettermodeAuthType } from '../auth'; + +export const createDiscussionAction = createAction({ + name: 'create_discussion', + auth: bettermodeAuth, + displayName: 'Create Discussion Post', + description: 'Create a new discussion post in a space', + props: { + spaceId: Property.Dropdown({ + displayName: 'Space', + description: 'The space to create the discussion in', + required: true, + refreshers: [], + options: async ({ auth }) => + await buildMemberSpacesDropdown(auth as BettermodeAuthType), + }), + title: Property.ShortText({ + displayName: 'Title', + description: 'The title of the discussion', + required: true, + }), + content: Property.LongText({ + displayName: 'Content', + description: 'The content of the discussion', + required: true, + }), + tagNames: Property.ShortText({ + displayName: 'Tags', + description: 'The tags to add to the discussion', + required: false, + }), + locked: Property.Checkbox({ + displayName: 'Locked', + description: 'If the discussion should be locked', + required: false, + defaultValue: false, + }), + }, + async run(context) { + return await createDiscussion( + context.auth as BettermodeAuthType, + context.propsValue.spaceId, + context.propsValue.tagNames ?? '', + context.propsValue.title, + context.propsValue.content, + context.propsValue.locked + ); + }, +}); diff --git a/packages/pieces/community/bettermode/src/lib/actions/create-question.ts b/packages/pieces/community/bettermode/src/lib/actions/create-question.ts new file mode 100644 index 0000000..c9e0adc --- /dev/null +++ b/packages/pieces/community/bettermode/src/lib/actions/create-question.ts @@ -0,0 +1,52 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { createQuestion } from '../api'; +import { buildMemberSpacesDropdown } from '../props'; +import { bettermodeAuth, BettermodeAuthType } from '../auth'; + +export const createQuestionAction = createAction({ + name: 'create_question', + auth: bettermodeAuth, + displayName: 'Create Question Post', + description: 'Create a new question post in a space', + props: { + spaceId: Property.Dropdown({ + displayName: 'Space', + description: 'The space to create the question in', + required: true, + refreshers: [], + options: async ({ auth }) => + await buildMemberSpacesDropdown(auth as BettermodeAuthType), + }), + title: Property.ShortText({ + displayName: 'Title', + description: 'The title of the question', + required: true, + }), + content: Property.LongText({ + displayName: 'Content', + description: 'The content of the question', + required: true, + }), + tagNames: Property.ShortText({ + displayName: 'Tags', + description: 'The tags to add to the question', + required: false, + }), + locked: Property.Checkbox({ + displayName: 'Locked', + description: 'If the question should be locked', + required: false, + defaultValue: false, + }), + }, + async run(context) { + return await createQuestion( + context.auth as BettermodeAuthType, + context.propsValue.spaceId, + context.propsValue.tagNames ?? '', + context.propsValue.title, + context.propsValue.content, + context.propsValue.locked + ); + }, +}); diff --git a/packages/pieces/community/bettermode/src/lib/actions/revoke-badge.ts b/packages/pieces/community/bettermode/src/lib/actions/revoke-badge.ts new file mode 100644 index 0000000..ebfeec2 --- /dev/null +++ b/packages/pieces/community/bettermode/src/lib/actions/revoke-badge.ts @@ -0,0 +1,33 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { revokeBadgeFromMember } from '../api'; +import { buildBadgesDropdown } from '../props'; +import { bettermodeAuth, BettermodeAuthType } from '../auth'; + +export const revokeBadgeAction = createAction({ + name: 'revoke_badge', + auth: bettermodeAuth, + displayName: 'Revoke Badge from Member', + description: 'Revoke a badge from a member by email', + props: { + badgeId: Property.Dropdown({ + displayName: 'Badge', + description: 'The badge to revoke', + required: true, + refreshers: [], + options: async ({ auth }) => + await buildBadgesDropdown(auth as BettermodeAuthType), + }), + email: Property.ShortText({ + displayName: 'Email', + description: 'The email of the member to revoke the badge from', + required: true, + }), + }, + async run(context) { + return await revokeBadgeFromMember( + context.auth as BettermodeAuthType, + context.propsValue.badgeId, + context.propsValue.email + ); + }, +}); diff --git a/packages/pieces/community/bettermode/src/lib/api.ts b/packages/pieces/community/bettermode/src/lib/api.ts new file mode 100644 index 0000000..5fc57e0 --- /dev/null +++ b/packages/pieces/community/bettermode/src/lib/api.ts @@ -0,0 +1,292 @@ +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { BettermodeAuthType } from './auth'; + +type KeyValuePair = { [key: string]: string | boolean | object | undefined }; + +const bettermodeAPI = async ( + auth: BettermodeAuthType, + query: string, + variables: KeyValuePair = {} +) => { + const request: HttpRequest = { + method: HttpMethod.POST, + url: auth.region, + headers: { + 'Content-Type': 'application/json', + Authorization: auth.token ? `Bearer ${auth.token}` : undefined, + }, + body: JSON.stringify({ + query: query, + variables: variables, + }), + }; + + const response = await httpClient.sendRequest(request); + + if (response.body['errors']) { + throw new Error(response.body['errors'][0]['message']); + } + + return response.body['data']; +}; + +const getGuestToken = async (auth: BettermodeAuthType) => { + const query = `query GetGuestToken($domain: String!) { + tokens(networkDomain: $domain) { + accessToken + } + }`; + + const variables = { domain: auth.domain }; + const response = await bettermodeAPI(auth, query, variables); + + auth.token = response.tokens.accessToken; + + return auth; +}; + +export const getAuthToken = async (auth: BettermodeAuthType) => { + const query = `mutation getAuthToken($email: String!, $password: String!) { + loginNetwork(input:{usernameOrEmail: $email, password: $password}) { + accessToken + member { + id + name + } + } + }`; + + const variables = { + email: auth.email, + password: auth.password, + }; + + auth = await getGuestToken(auth); + const response = await bettermodeAPI(auth, query, variables); + + auth.token = response.loginNetwork.accessToken; + auth.memberId = response.loginNetwork.member.id; + + return auth; +}; + +const getPostType = async (auth: BettermodeAuthType, postTypeName: string) => { + const query = `query getPostType($postTypeName: String!) { + postTypes(limit: 1, query: $postTypeName) { + nodes { + id + name + } + } + }`; + + if (!auth.memberId) auth = await getAuthToken(auth); + + const variables = { postTypeName: postTypeName }; + const response = await bettermodeAPI(auth, query, variables); + + return response.postTypes.nodes[0]; +}; + +export const listBadges = async (auth: BettermodeAuthType) => { + const query = `query { + network { + badges { + id + name + } + } + }`; + + if (!auth.memberId) auth = await getAuthToken(auth); + + const response = await bettermodeAPI(auth, query); + + return response.network.badges; +}; + +export const listMemberSpaces = async (auth: BettermodeAuthType) => { + const query = `query listMemberSpaces($memberId: ID!) { + spaces(memberId: $memberId, limit: 100) { + nodes { + id + name + } + } + }`; + + if (!auth.memberId) auth = await getAuthToken(auth); + + const variables = { memberId: auth.memberId }; + const response = await bettermodeAPI(auth, query, variables); + + return response.spaces.nodes; +}; + +const getMemberByEmail = async (auth: BettermodeAuthType, email: string) => { + const query = `query getMemberId($email: String!) { + members(limit: 1, query: $email) { + nodes { + id + name + } + } + }`; + + if (!auth.memberId) auth = await getAuthToken(auth); + + const variables = { email: email }; + const response = await bettermodeAPI(auth, query, variables); + + if (response.members.nodes.length == 0) { + throw new Error(`Member with email ${email} not found`); + } + + return response.members.nodes[0]; +}; + +export const assignBadgeToMember = async ( + auth: BettermodeAuthType, + badgeId: string, + email: string +) => { + const query = `mutation assignBadgeToMember($badgeId: String!, $memberId: String!) { + assignBadge( + id: $badgeId, + input: { + memberId: $memberId, + } + ) { + status + } + }`; + + if (!auth.memberId) auth = await getAuthToken(auth); + + const member = await getMemberByEmail(auth, email); + const variables = { badgeId: badgeId, memberId: member.id }; + const response = await bettermodeAPI(auth, query, variables); + + return response.assignBadge; +}; + +export const revokeBadgeFromMember = async ( + auth: BettermodeAuthType, + badgeId: string, + email: string +) => { + const query = `mutation revokeBadgeFromMember($badgeId: String!, $memberId: String!) { + revokeBadge( + id: $badgeId, + input: { + memberId: $memberId, + } + ) { + status + } + }`; + + if (!auth.memberId) auth = await getAuthToken(auth); + + const member = await getMemberByEmail(auth, email); + const variables = { badgeId: badgeId, memberId: member.id }; + const response = await bettermodeAPI(auth, query, variables); + + return response.revokeBadge; +}; + +export const createPostOfType = async ( + auth: BettermodeAuthType, + postTypeName: string, + spaceId: string, + tagNames: string, + title: string, + content: string, + locked = false +) => { + const query = `mutation createPostOfType($spaceId: ID!, $postTypeId: String!, $locked: Boolean!, $tagNames: [String!], $title: String!, $content: String!) { + createPost( + spaceId : $spaceId, + input : { + postTypeId : $postTypeId, + locked : $locked, + publish : true, + tagNames : $tagNames, + mappingFields : [ + { + key : "title", + type : text, + value : $title + }, + { + key : "content", + type : html, + value : $content + } + ] + } + ) { + url + createdAt + } + }`; + + auth = await getAuthToken(auth); + + const postType = await getPostType(auth, postTypeName); + + const variables = { + spaceId: spaceId, + postTypeId: postType.id, + locked: locked, + tagNames: tagNames.split(',').map((tag: string) => tag.trim()), + title: JSON.stringify(title), + content: JSON.stringify(content), + }; + const response = await bettermodeAPI(auth, query, variables); + return response.createPost; +}; + +export const createDiscussion = async ( + auth: BettermodeAuthType, + spaceId: string, + tagNames: string, + title: string, + content: string, + locked = false +) => { + return await createPostOfType( + auth, + 'Discussion', + spaceId, + tagNames, + title, + content, + locked + ); +}; + +export const createQuestion = async ( + auth: BettermodeAuthType, + spaceId: string, + tagNames: string, + title: string, + content: string, + locked = false +) => { + return await createPostOfType( + auth, + 'Question', + spaceId, + tagNames, + title, + content, + locked + ); +}; + +// TODO: assignBadge to member diff --git a/packages/pieces/community/bettermode/src/lib/auth.ts b/packages/pieces/community/bettermode/src/lib/auth.ts new file mode 100644 index 0000000..9cc0fd7 --- /dev/null +++ b/packages/pieces/community/bettermode/src/lib/auth.ts @@ -0,0 +1,77 @@ +import { + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; +import { getAuthToken } from './api'; + +export type BettermodeAuthType = { + region: string; + domain: string; + email: string; + password: string; + token?: string; + memberId?: string; +}; + +export const bettermodeAuth = PieceAuth.CustomAuth({ + description: + 'Your domain should be the base URL of your Bettermode community. Example: community.example.com', + props: { + region: Property.StaticDropdown({ + displayName: 'Region', + description: 'The region of your Bettermode account', + required: true, + options: { + options: [ + { label: 'US Region', value: 'https://api.bettermode.com' }, + { label: 'EU Region', value: 'https://api.bettermode.de' }, + ], + }, + }), + domain: Property.ShortText({ + displayName: 'BetterMode Domain', + description: 'The domain of your Bettermode account', + required: true, + }), + email: Property.ShortText({ + displayName: 'Email', + description: 'Email address for your Bettermode account', + required: true, + }), + password: PieceAuth.SecretText({ + displayName: 'Password', + description: 'Password for your Bettermode account', + required: true, + }), + }, + validate: async ({ auth }) => { + try { + await validateAuth(auth); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: (e as Error)?.message, + }; + } + }, + required: true, +}); + +const validateAuth = async (auth: BettermodeAuthType) => { + await propsValidation.validateZod(auth, { + domain: z.string().url(), + email: z.string().email(), + }); + + const response = await getAuthToken(auth); + if (!response.memberId) { + throw new Error( + 'Authentication failed. Please check your credentials and try again.' + ); + } +}; diff --git a/packages/pieces/community/bettermode/src/lib/props.ts b/packages/pieces/community/bettermode/src/lib/props.ts new file mode 100644 index 0000000..7818bd2 --- /dev/null +++ b/packages/pieces/community/bettermode/src/lib/props.ts @@ -0,0 +1,36 @@ +import { BettermodeAuthType } from './auth'; +import { listMemberSpaces, listBadges } from './api'; + +export async function buildMemberSpacesDropdown(auth: BettermodeAuthType) { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please authenticate first', + }; + } + const spaces = await listMemberSpaces(auth as BettermodeAuthType); + const options = spaces.map((space: { name: string; id: string }) => { + return { label: space.name, value: space.id }; + }); + return { + options: options, + }; +} + +export async function buildBadgesDropdown(auth: BettermodeAuthType) { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please authenticate first', + }; + } + const badges = await listBadges(auth as BettermodeAuthType); + const options = badges.map((badge: { name: string; id: string }) => { + return { label: badge.name, value: badge.id }; + }); + return { + options: options, + }; +} diff --git a/packages/pieces/community/bettermode/tsconfig.json b/packages/pieces/community/bettermode/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/bettermode/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/bettermode/tsconfig.lib.json b/packages/pieces/community/bettermode/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/bettermode/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/binance/.eslintrc.json b/packages/pieces/community/binance/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/binance/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/binance/README.md b/packages/pieces/community/binance/README.md new file mode 100644 index 0000000..3119ddc --- /dev/null +++ b/packages/pieces/community/binance/README.md @@ -0,0 +1,7 @@ +# pieces-binance + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-binance` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/binance/package.json b/packages/pieces/community/binance/package.json new file mode 100644 index 0000000..3742891 --- /dev/null +++ b/packages/pieces/community/binance/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-binance", + "version": "0.3.3" +} \ No newline at end of file diff --git a/packages/pieces/community/binance/project.json b/packages/pieces/community/binance/project.json new file mode 100644 index 0000000..26067b6 --- /dev/null +++ b/packages/pieces/community/binance/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-binance", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/binance/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/binance", + "tsConfig": "packages/pieces/community/binance/tsconfig.lib.json", + "packageJson": "packages/pieces/community/binance/package.json", + "main": "packages/pieces/community/binance/src/index.ts", + "assets": [ + "packages/pieces/community/binance/*.md", + { + "input": "packages/pieces/community/binance/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/binance/src/index.ts b/packages/pieces/community/binance/src/index.ts new file mode 100644 index 0000000..14b6ed7 --- /dev/null +++ b/packages/pieces/community/binance/src/index.ts @@ -0,0 +1,14 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { fetchCryptoPairPrice } from './lib/actions/fetch-pair-price'; + +export const binance = createPiece({ + displayName: 'Binance', + description: 'Fetch the price of a crypto pair from Binance', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/binance.png', + categories: [], + auth: PieceAuth.None(), + actions: [fetchCryptoPairPrice], + authors: ["kishanprmr","khaledmashaly","abuaboud"], + triggers: [], +}); diff --git a/packages/pieces/community/binance/src/lib/actions/fetch-pair-price.ts b/packages/pieces/community/binance/src/lib/actions/fetch-pair-price.ts new file mode 100644 index 0000000..09ed49a --- /dev/null +++ b/packages/pieces/community/binance/src/lib/actions/fetch-pair-price.ts @@ -0,0 +1,49 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; + +export const fetchCryptoPairPrice = createAction({ + name: 'fetch_crypto_pair_price', + displayName: 'Fetch Pair Price', + description: 'Fetch the current price of a pair (e.g. BTC/USDT)', + props: { + first_coin: Property.ShortText({ + displayName: 'First Coin Symbol', + description: + "The currency to fetch the price for (e.g. 'BTC' in 'BTC/USDT')", + required: true, + }), + second_coin: Property.ShortText({ + displayName: 'Second Coin Symbol', + description: + "The currency to fetch the price in (e.g. 'USDT' in 'BTC/USDT')", + required: true, + }), + }, + async run(context) { + const { first_coin, second_coin } = context.propsValue; + if (first_coin && second_coin) + return await fetchCryptoPairPriceImpl(`${first_coin}${second_coin}`); + throw Error('Missing parameter(s)'); + }, +}); + +async function fetchCryptoPairPriceImpl(symbol: string): Promise { + const formattedSymbol = symbol + .replace('/', '') + .replace(' ', '') + .toUpperCase(); + + const url = `https://api.binance.com/api/v3/ticker/price?symbol=${formattedSymbol}`; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url, + }); + const data = await response.body; + return Number(data['price']); + } catch (error) { + console.error(`Error fetching price for symbol ${symbol}:`, error); + throw error; + } +} diff --git a/packages/pieces/community/binance/tsconfig.json b/packages/pieces/community/binance/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/binance/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/binance/tsconfig.lib.json b/packages/pieces/community/binance/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/binance/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/blockscout/.eslintrc.json b/packages/pieces/community/blockscout/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/blockscout/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/blockscout/README.md b/packages/pieces/community/blockscout/README.md new file mode 100644 index 0000000..b7410fd --- /dev/null +++ b/packages/pieces/community/blockscout/README.md @@ -0,0 +1,7 @@ +# pieces-blockscout + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-blockscout` to build the library. diff --git a/packages/pieces/community/blockscout/package.json b/packages/pieces/community/blockscout/package.json new file mode 100644 index 0000000..061b622 --- /dev/null +++ b/packages/pieces/community/blockscout/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-blockscout", + "version": "0.0.1" +} diff --git a/packages/pieces/community/blockscout/project.json b/packages/pieces/community/blockscout/project.json new file mode 100644 index 0000000..482c1b8 --- /dev/null +++ b/packages/pieces/community/blockscout/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-blockscout", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/blockscout/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/blockscout", + "tsConfig": "packages/pieces/community/blockscout/tsconfig.lib.json", + "packageJson": "packages/pieces/community/blockscout/package.json", + "main": "packages/pieces/community/blockscout/src/index.ts", + "assets": [ + "packages/pieces/community/blockscout/*.md", + { + "input": "packages/pieces/community/blockscout/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/blockscout/src/index.ts b/packages/pieces/community/blockscout/src/index.ts new file mode 100644 index 0000000..bc56fc7 --- /dev/null +++ b/packages/pieces/community/blockscout/src/index.ts @@ -0,0 +1,84 @@ + +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { + search, + checkRedirect, + getBlocks, + getMainPageBlocks, + getBlockByHash, + getBlockTransactions, + getBlockWithdrawals, + getTransactions, + getMainPageTransactions, + getTransactionByHash, + getTransactionTokenTransfers, + getTransactionInternalTransactions, + getTransactionLogs, + getTransactionRawTrace, + getTransactionStateChanges, + getTransactionSummary, + getAddresses, + getAddressByHash, + getAddressCounters, + getAddressTransactions, + getAddressTokenTransfers, + getAddressLogs, + getAddressBlocksValidated, + getAddressTokenBalances, + getAddressTokens, + getAddressWithdrawals, + getAddressCoinBalanceHistory, + getAddressCoinBalanceHistoryByDay, + getTokens, + getTokenByAddress, + getTokenTransfers, + getTokenHolders, + getTokenCounters, + getTokenInstances +} from './lib/actions'; + +export const blockscout = createPiece({ + displayName: "Blockscout", + description: "Blockscout is a tool for inspecting and analyzing EVM chains.", + auth: PieceAuth.None(), + logoUrl: 'https://cdn.activepieces.com/pieces/blockscout.png', + authors: ['reemayoush'], + actions: [ + search, + checkRedirect, + getBlocks, + getMainPageBlocks, + getBlockByHash, + getBlockTransactions, + getBlockWithdrawals, + getTransactions, + getMainPageTransactions, + getTransactionByHash, + getTransactionTokenTransfers, + getTransactionInternalTransactions, + getTransactionLogs, + getTransactionRawTrace, + getTransactionStateChanges, + getTransactionSummary, + getAddresses, + getAddressByHash, + getAddressCounters, + getAddressTransactions, + getAddressTokenTransfers, + getAddressLogs, + getAddressBlocksValidated, + getAddressTokenBalances, + getAddressTokens, + getAddressWithdrawals, + getAddressCoinBalanceHistory, + getAddressCoinBalanceHistoryByDay, + getTokens, + getTokenByAddress, + getTokenTransfers, + getTokenHolders, + getTokenCounters, + getTokenInstances + ], + triggers: [] +}); + diff --git a/packages/pieces/community/blockscout/src/lib/actions/addresses/address-blocks-validated.ts b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-blocks-validated.ts new file mode 100644 index 0000000..fe6f6de --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-blocks-validated.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getAddressBlocksValidated = createAction({ + name: 'get_address_blocks_validated', + displayName: 'Get Address Blocks Validated', + description: 'Get list of blocks validated by an address', + // category: 'Addresses', + props: { + addressHash: Property.ShortText({ + displayName: 'Address Hash', + description: 'Hash of the address to fetch validated blocks for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/addresses/${context.propsValue.addressHash}/blocks-validated`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/addresses/address-by-hash.ts b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-by-hash.ts new file mode 100644 index 0000000..f5d0bed --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-by-hash.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getAddressByHash = createAction({ + name: 'get_address_by_hash', + displayName: 'Get Address by Hash', + description: 'Get address info by its hash', + // category: 'Addresses', + props: { + addressHash: Property.ShortText({ + displayName: 'Address Hash', + description: 'Hash of the address to fetch info for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/addresses/${context.propsValue.addressHash}`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/addresses/address-coin-balance-history-by-day.ts b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-coin-balance-history-by-day.ts new file mode 100644 index 0000000..bd67ad1 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-coin-balance-history-by-day.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getAddressCoinBalanceHistoryByDay = createAction({ + name: 'get_address_coin_balance_history_by_day', + displayName: 'Get Address Coin Balance History By Day', + description: 'Get list of coin balance changes for an address grouped by day', + // category: 'Addresses', + props: { + addressHash: Property.ShortText({ + displayName: 'Address Hash', + description: 'Hash of the address to fetch daily coin balance history for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/addresses/${context.propsValue.addressHash}/coin-balance-history-by-day`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/addresses/address-coin-balance-history.ts b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-coin-balance-history.ts new file mode 100644 index 0000000..f25f7aa --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-coin-balance-history.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getAddressCoinBalanceHistory = createAction({ + name: 'get_address_coin_balance_history', + displayName: 'Get Address Coin Balance History', + description: 'Get list of coin balance changes for an address', + // category: 'Addresses', + props: { + addressHash: Property.ShortText({ + displayName: 'Address Hash', + description: 'Hash of the address to fetch coin balance history for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/addresses/${context.propsValue.addressHash}/coin-balance-history`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/addresses/address-counters.ts b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-counters.ts new file mode 100644 index 0000000..91072f3 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-counters.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getAddressCounters = createAction({ + name: 'get_address_counters', + displayName: 'Get Address Counters', + description: 'Get counters (transactions count, token transfers count, etc.) for an address', + // category: 'Addresses', + props: { + addressHash: Property.ShortText({ + displayName: 'Address Hash', + description: 'Hash of the address to fetch counters for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/addresses/${context.propsValue.addressHash}/counters`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/addresses/address-internal-transactions.ts b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-internal-transactions.ts new file mode 100644 index 0000000..dc68033 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-internal-transactions.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getAddressInternalTransactions = createAction({ + name: 'get_address_internal_transactions', + displayName: 'Get Address Internal Transactions', + description: 'Get list of internal transactions for an address', + // category: 'Addresses', + props: { + addressHash: Property.ShortText({ + displayName: 'Address Hash', + description: 'Hash of the address to fetch internal transactions for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/addresses/${context.propsValue.addressHash}/internal-transactions`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/addresses/address-logs.ts b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-logs.ts new file mode 100644 index 0000000..4ad727c --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-logs.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getAddressLogs = createAction({ + name: 'get_address_logs', + displayName: 'Get Address Logs', + description: 'Get list of logs generated by an address', + // category: 'Addresses', + props: { + addressHash: Property.ShortText({ + displayName: 'Address Hash', + description: 'Hash of the address to fetch logs for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/addresses/${context.propsValue.addressHash}/logs`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/addresses/address-token-balances.ts b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-token-balances.ts new file mode 100644 index 0000000..7189f32 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-token-balances.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getAddressTokenBalances = createAction({ + name: 'get_address_token_balances', + displayName: 'Get Address Token Balances', + description: 'Get list of token balances for an address', + // category: 'Addresses', + props: { + addressHash: Property.ShortText({ + displayName: 'Address Hash', + description: 'Hash of the address to fetch token balances for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/addresses/${context.propsValue.addressHash}/token-balances`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/addresses/address-token-transfers.ts b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-token-transfers.ts new file mode 100644 index 0000000..ac32bef --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-token-transfers.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getAddressTokenTransfers = createAction({ + name: 'get_address_token_transfers', + displayName: 'Get Address Token Transfers', + description: 'Get list of token transfers for an address', + // category: 'Addresses', + props: { + addressHash: Property.ShortText({ + displayName: 'Address Hash', + description: 'Hash of the address to fetch token transfers for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/addresses/${context.propsValue.addressHash}/token-transfers`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/addresses/address-tokens.ts b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-tokens.ts new file mode 100644 index 0000000..e77319f --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-tokens.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getAddressTokens = createAction({ + name: 'get_address_tokens', + displayName: 'Get Address Tokens', + description: 'Get list of tokens owned by an address with filtering options', + // category: 'Addresses', + props: { + addressHash: Property.ShortText({ + displayName: 'Address Hash', + description: 'Hash of the address to fetch tokens for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/addresses/${context.propsValue.addressHash}/tokens`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/addresses/address-transactions.ts b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-transactions.ts new file mode 100644 index 0000000..48ded60 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-transactions.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getAddressTransactions = createAction({ + name: 'get_address_transactions', + displayName: 'Get Address Transactions', + description: 'Get list of transactions for an address', + // category: 'Addresses', + props: { + addressHash: Property.ShortText({ + displayName: 'Address Hash', + description: 'Hash of the address to fetch transactions for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/addresses/${context.propsValue.addressHash}/transactions`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/addresses/address-withdrawals.ts b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-withdrawals.ts new file mode 100644 index 0000000..1e74754 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/addresses/address-withdrawals.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getAddressWithdrawals = createAction({ + name: 'get_address_withdrawals', + displayName: 'Get Address Withdrawals', + description: 'Get list of withdrawals for an address', + // category: 'Addresses', + props: { + addressHash: Property.ShortText({ + displayName: 'Address Hash', + description: 'Hash of the address to fetch withdrawals for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/addresses/${context.propsValue.addressHash}/withdrawals`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/addresses/addresses.ts b/packages/pieces/community/blockscout/src/lib/actions/addresses/addresses.ts new file mode 100644 index 0000000..ae91a0c --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/addresses/addresses.ts @@ -0,0 +1,20 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getAddresses = createAction({ + name: 'get_addresses', + displayName: 'Get Addresses', + description: 'Get list of native coin holders', + // category: 'Addresses', + props: {}, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/addresses`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/addresses/index.ts b/packages/pieces/community/blockscout/src/lib/actions/addresses/index.ts new file mode 100644 index 0000000..543d454 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/addresses/index.ts @@ -0,0 +1,13 @@ +export * from './addresses'; +export * from './address-by-hash'; +export * from './address-counters'; +export * from './address-transactions'; +export * from './address-token-transfers'; +export * from './address-internal-transactions'; +export * from './address-logs'; +export * from './address-blocks-validated'; +export * from './address-token-balances'; +export * from './address-tokens'; +export * from './address-withdrawals'; +export * from './address-coin-balance-history'; +export * from './address-coin-balance-history-by-day'; diff --git a/packages/pieces/community/blockscout/src/lib/actions/blocks/block-by-hash.ts b/packages/pieces/community/blockscout/src/lib/actions/blocks/block-by-hash.ts new file mode 100644 index 0000000..a4591f0 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/blocks/block-by-hash.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getBlockByHash = createAction({ + name: 'get_block_by_hash', + displayName: 'Get Block by Hash or Number', + description: 'Get block info by its hash or block number', + // category: 'Blocks', + props: { + blockIdentifier: Property.ShortText({ + displayName: 'Block Hash or Number', + description: 'Block hash or block number to fetch', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/blocks/${context.propsValue.blockIdentifier}`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/blocks/block-transactions.ts b/packages/pieces/community/blockscout/src/lib/actions/blocks/block-transactions.ts new file mode 100644 index 0000000..bd8fed1 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/blocks/block-transactions.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getBlockTransactions = createAction({ + name: 'get_block_transactions', + displayName: 'Get Block Transactions', + description: 'Get list of transactions for a specific block', + // category: 'Blocks', + props: { + blockIdentifier: Property.ShortText({ + displayName: 'Block Hash or Number', + description: 'Block hash or block number to fetch transactions for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/blocks/${context.propsValue.blockIdentifier}/transactions`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/blocks/block-withdrawals.ts b/packages/pieces/community/blockscout/src/lib/actions/blocks/block-withdrawals.ts new file mode 100644 index 0000000..6c7a203 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/blocks/block-withdrawals.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getBlockWithdrawals = createAction({ + name: 'get_block_withdrawals', + displayName: 'Get Block Withdrawals', + description: 'Get list of withdrawals for a specific block', + // category: 'Blocks', + props: { + blockIdentifier: Property.ShortText({ + displayName: 'Block Hash or Number', + description: 'Block hash or block number to fetch withdrawals for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/blocks/${context.propsValue.blockIdentifier}/withdrawals`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/blocks/blocks.ts b/packages/pieces/community/blockscout/src/lib/actions/blocks/blocks.ts new file mode 100644 index 0000000..fd25c47 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/blocks/blocks.ts @@ -0,0 +1,20 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getBlocks = createAction({ + name: 'get_blocks', + displayName: 'Get Blocks', + description: 'Get list of blocks', + // category: 'Blocks', + props: {}, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/blocks`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/blocks/index.ts b/packages/pieces/community/blockscout/src/lib/actions/blocks/index.ts new file mode 100644 index 0000000..54122c6 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/blocks/index.ts @@ -0,0 +1,5 @@ +export * from './blocks'; +export * from './main-page-blocks'; +export * from './block-by-hash'; +export * from './block-transactions'; +export * from './block-withdrawals'; diff --git a/packages/pieces/community/blockscout/src/lib/actions/blocks/main-page-blocks.ts b/packages/pieces/community/blockscout/src/lib/actions/blocks/main-page-blocks.ts new file mode 100644 index 0000000..b9b626e --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/blocks/main-page-blocks.ts @@ -0,0 +1,20 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getMainPageBlocks = createAction({ + name: 'get_main_page_blocks', + displayName: 'Get Main Page Blocks', + description: 'Get blocks for main page display', + // category: 'Blocks', + props: {}, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/main-page/blocks`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/index.ts b/packages/pieces/community/blockscout/src/lib/actions/index.ts new file mode 100644 index 0000000..af0c43c --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/index.ts @@ -0,0 +1,6 @@ +export * from './search'; +export * from './search-redirect'; +export * from './blocks'; +export * from './transactions'; +export * from './addresses'; +export * from './tokens'; diff --git a/packages/pieces/community/blockscout/src/lib/actions/search-redirect/check-redirect.ts b/packages/pieces/community/blockscout/src/lib/actions/search-redirect/check-redirect.ts new file mode 100644 index 0000000..d6bc773 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/search-redirect/check-redirect.ts @@ -0,0 +1,29 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const checkRedirect = createAction({ + name: 'check_redirect', + displayName: 'Check Search Redirect', + description: 'Check if a search query should redirect to a specific resource', + // category: 'Search', + props: { + query: Property.ShortText({ + displayName: 'Search Query', + description: 'Search query to check for redirect', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/search/check-redirect`, + queryParams: { + q: context.propsValue.query + }, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/search-redirect/index.ts b/packages/pieces/community/blockscout/src/lib/actions/search-redirect/index.ts new file mode 100644 index 0000000..1c3f0e0 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/search-redirect/index.ts @@ -0,0 +1 @@ +export * from './check-redirect'; diff --git a/packages/pieces/community/blockscout/src/lib/actions/search/index.ts b/packages/pieces/community/blockscout/src/lib/actions/search/index.ts new file mode 100644 index 0000000..5a2bdeb --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/search/index.ts @@ -0,0 +1 @@ +export * from './search'; diff --git a/packages/pieces/community/blockscout/src/lib/actions/search/search.ts b/packages/pieces/community/blockscout/src/lib/actions/search/search.ts new file mode 100644 index 0000000..723bec9 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/search/search.ts @@ -0,0 +1,29 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const search = createAction({ + name: 'search', + displayName: 'Search', + description: 'Search for addresses, transactions, blocks, or tokens', + // category: 'Search', + props: { + query: Property.ShortText({ + displayName: 'Search Query', + description: 'Search query for addresses, transactions, blocks, or tokens', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/search`, + queryParams: { + q: context.propsValue['query'] + }, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/tokens/index.ts b/packages/pieces/community/blockscout/src/lib/actions/tokens/index.ts new file mode 100644 index 0000000..c60b1d8 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/tokens/index.ts @@ -0,0 +1,6 @@ +export * from './tokens'; +export * from './token-by-address'; +export * from './token-transfers'; +export * from './token-holders'; +export * from './token-counters'; +export * from './token-instances'; diff --git a/packages/pieces/community/blockscout/src/lib/actions/tokens/token-by-address.ts b/packages/pieces/community/blockscout/src/lib/actions/tokens/token-by-address.ts new file mode 100644 index 0000000..4471298 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/tokens/token-by-address.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getTokenByAddress = createAction({ + name: 'get_token_by_address', + displayName: 'Get Token by Address', + description: 'Get token info by its contract address', + // category: 'Tokens', + props: { + addressHash: Property.ShortText({ + displayName: 'Token Address', + description: 'Contract address of the token to fetch info for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/tokens/${context.propsValue.addressHash}`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/tokens/token-counters.ts b/packages/pieces/community/blockscout/src/lib/actions/tokens/token-counters.ts new file mode 100644 index 0000000..139ee2e --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/tokens/token-counters.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getTokenCounters = createAction({ + name: 'get_token_counters', + displayName: 'Get Token Counters', + description: 'Get token counters (holders count, transfers count, etc.)', + // category: 'Tokens', + props: { + addressHash: Property.ShortText({ + displayName: 'Token Address', + description: 'Contract address of the token to fetch counters for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/tokens/${context.propsValue.addressHash}/counters`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/tokens/token-holders.ts b/packages/pieces/community/blockscout/src/lib/actions/tokens/token-holders.ts new file mode 100644 index 0000000..e1d6529 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/tokens/token-holders.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getTokenHolders = createAction({ + name: 'get_token_holders', + displayName: 'Get Token Holders', + description: 'Get list of token holders', + // category: 'Tokens', + props: { + addressHash: Property.ShortText({ + displayName: 'Token Address', + description: 'Contract address of the token to fetch holders for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/tokens/${context.propsValue.addressHash}/holders`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/tokens/token-instances.ts b/packages/pieces/community/blockscout/src/lib/actions/tokens/token-instances.ts new file mode 100644 index 0000000..be3e42f --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/tokens/token-instances.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getTokenInstances = createAction({ + name: 'get_token_instances', + displayName: 'Get Token Instances', + description: 'Get list of token instances (NFTs)', + // category: 'Tokens', + props: { + addressHash: Property.ShortText({ + displayName: 'Token Address', + description: 'Contract address of the token to fetch instances for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/tokens/${context.propsValue.addressHash}/instances`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/tokens/token-transfers.ts b/packages/pieces/community/blockscout/src/lib/actions/tokens/token-transfers.ts new file mode 100644 index 0000000..5bfeb69 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/tokens/token-transfers.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getTokenTransfers = createAction({ + name: 'get_token_transfers', + displayName: 'Get Token Transfers', + description: 'Get list of token transfers', + // category: 'Tokens', + props: { + addressHash: Property.ShortText({ + displayName: 'Token Address', + description: 'Contract address of the token to fetch transfers for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/tokens/${context.propsValue.addressHash}/transfers`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/tokens/tokens.ts b/packages/pieces/community/blockscout/src/lib/actions/tokens/tokens.ts new file mode 100644 index 0000000..ad34ed1 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/tokens/tokens.ts @@ -0,0 +1,21 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getTokens = createAction({ + name: 'get_tokens', + displayName: 'Get Tokens', + description: 'Get list of tokens', + + // category: 'Tokens', + props: {}, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/tokens`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/transactions/index.ts b/packages/pieces/community/blockscout/src/lib/actions/transactions/index.ts new file mode 100644 index 0000000..d5aefbb --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/transactions/index.ts @@ -0,0 +1,9 @@ +export * from './transactions'; +export * from './main-page-transactions'; +export * from './transaction-by-hash'; +export * from './transaction-token-transfers'; +export * from './transaction-internal-transactions'; +export * from './transaction-logs'; +export * from './transaction-raw-trace'; +export * from './transaction-state-changes'; +export * from './transaction-summary'; diff --git a/packages/pieces/community/blockscout/src/lib/actions/transactions/main-page-transactions.ts b/packages/pieces/community/blockscout/src/lib/actions/transactions/main-page-transactions.ts new file mode 100644 index 0000000..e01df79 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/transactions/main-page-transactions.ts @@ -0,0 +1,20 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getMainPageTransactions = createAction({ + name: 'get_main_page_transactions', + displayName: 'Get Main Page Transactions', + description: 'Get transactions for main page display', + // category: 'Transactions', + props: {}, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/main-page/transactions`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-by-hash.ts b/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-by-hash.ts new file mode 100644 index 0000000..1892041 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-by-hash.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getTransactionByHash = createAction({ + name: 'get_transaction_by_hash', + displayName: 'Get Transaction by Hash', + description: 'Get transaction details by its hash', + // category: 'Transactions', + props: { + transactionHash: Property.ShortText({ + displayName: 'Transaction Hash', + description: 'Hash of the transaction to fetch details for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/transactions/${context.propsValue.transactionHash}`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-internal-transactions.ts b/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-internal-transactions.ts new file mode 100644 index 0000000..0cff92c --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-internal-transactions.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getTransactionInternalTransactions = createAction({ + name: 'get_transaction_internal_transactions', + displayName: 'Get Transaction Internal Transactions', + description: 'Get list of internal transactions in a transaction', + // category: 'Transactions', + props: { + transactionHash: Property.ShortText({ + displayName: 'Transaction Hash', + description: 'Hash of the transaction to fetch internal transactions for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/transactions/${context.propsValue.transactionHash}/internal-transactions`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-logs.ts b/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-logs.ts new file mode 100644 index 0000000..2d9572f --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-logs.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getTransactionLogs = createAction({ + name: 'get_transaction_logs', + displayName: 'Get Transaction Logs', + description: 'Get list of logs generated by a transaction', + // category: 'Transactions', + props: { + transactionHash: Property.ShortText({ + displayName: 'Transaction Hash', + description: 'Hash of the transaction to fetch logs for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/transactions/${context.propsValue.transactionHash}/logs`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-raw-trace.ts b/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-raw-trace.ts new file mode 100644 index 0000000..f792c7b --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-raw-trace.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getTransactionRawTrace = createAction({ + name: 'get_transaction_raw_trace', + displayName: 'Get Transaction Raw Trace', + description: 'Get raw trace data for a transaction', + // category: 'Transactions', + props: { + transactionHash: Property.ShortText({ + displayName: 'Transaction Hash', + description: 'Hash of the transaction to fetch raw trace for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/transactions/${context.propsValue.transactionHash}/raw-trace`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-state-changes.ts b/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-state-changes.ts new file mode 100644 index 0000000..af9ba1d --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-state-changes.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +export const getTransactionStateChanges = createAction({ + name: 'get_transaction_state_changes', + displayName: 'Get Transaction State Changes', + description: 'Get list of state changes in a transaction', + + // category: 'Transactions', + props: { + transactionHash: Property.ShortText({ + displayName: 'Transaction Hash', + description: 'Hash of the transaction to fetch state changes for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/transactions/${context.propsValue.transactionHash}/state-changes`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-summary.ts b/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-summary.ts new file mode 100644 index 0000000..6902b77 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-summary.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getTransactionSummary = createAction({ + name: 'get_transaction_summary', + displayName: 'Get Transaction Summary', + description: 'Get a human-readable summary of a transaction', + // category: 'Transactions', + props: { + transactionHash: Property.ShortText({ + displayName: 'Transaction Hash', + description: 'Hash of the transaction to fetch summary for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/transactions/${context.propsValue.transactionHash}/summary`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-token-transfers.ts b/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-token-transfers.ts new file mode 100644 index 0000000..287e471 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/transactions/transaction-token-transfers.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getTransactionTokenTransfers = createAction({ + name: 'get_transaction_token_transfers', + displayName: 'Get Transaction Token Transfers', + description: 'Get list of token transfers in a transaction', + // category: 'Transactions', + props: { + transactionHash: Property.ShortText({ + displayName: 'Transaction Hash', + description: 'Hash of the transaction to fetch token transfers for', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/transactions/${context.propsValue.transactionHash}/token-transfers`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/src/lib/actions/transactions/transactions.ts b/packages/pieces/community/blockscout/src/lib/actions/transactions/transactions.ts new file mode 100644 index 0000000..51e9500 --- /dev/null +++ b/packages/pieces/community/blockscout/src/lib/actions/transactions/transactions.ts @@ -0,0 +1,20 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getTransactions = createAction({ + name: 'get_transactions', + displayName: 'Get Transactions', + description: 'Get list of transactions', + // category: 'Transactions', + props: {}, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://eth.blockscout.com/api/v2/transactions`, + }); + if (response.status !== 200) { + throw new Error(`Blockscout API request failed with status ${response.status}`); + } + return response.body; + }, +}); diff --git a/packages/pieces/community/blockscout/tsconfig.json b/packages/pieces/community/blockscout/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/blockscout/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/blockscout/tsconfig.lib.json b/packages/pieces/community/blockscout/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/blockscout/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/bonjoro/.eslintrc.json b/packages/pieces/community/bonjoro/.eslintrc.json new file mode 100644 index 0000000..15ede17 --- /dev/null +++ b/packages/pieces/community/bonjoro/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*", "package.json"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/bonjoro/README.md b/packages/pieces/community/bonjoro/README.md new file mode 100644 index 0000000..ef2abcd --- /dev/null +++ b/packages/pieces/community/bonjoro/README.md @@ -0,0 +1,7 @@ +# pieces-bonjoro + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-bonjoro` to build the library. diff --git a/packages/pieces/community/bonjoro/package.json b/packages/pieces/community/bonjoro/package.json new file mode 100644 index 0000000..7922c44 --- /dev/null +++ b/packages/pieces/community/bonjoro/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-bonjoro", + "version": "0.0.5" +} \ No newline at end of file diff --git a/packages/pieces/community/bonjoro/project.json b/packages/pieces/community/bonjoro/project.json new file mode 100644 index 0000000..5f213a2 --- /dev/null +++ b/packages/pieces/community/bonjoro/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-bonjoro", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/bonjoro/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/bonjoro", + "tsConfig": "packages/pieces/community/bonjoro/tsconfig.lib.json", + "packageJson": "packages/pieces/community/bonjoro/package.json", + "main": "packages/pieces/community/bonjoro/src/index.ts", + "assets": [ + "packages/pieces/community/bonjoro/*.md", + { + "input": "packages/pieces/community/bonjoro/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-bonjoro {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/bonjoro/src/index.ts b/packages/pieces/community/bonjoro/src/index.ts new file mode 100644 index 0000000..5a15eac --- /dev/null +++ b/packages/pieces/community/bonjoro/src/index.ts @@ -0,0 +1,29 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { addGreetAction } from './lib/actions/add-greet'; +import { bonjoroAuth } from './lib/auth'; + +export const bonjoro = createPiece({ + displayName: 'Bonjoro', + description: 'Send personal video messages to delight customers', + auth: bonjoroAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/bonjoro.png', + categories: [PieceCategory.CUSTOMER_SUPPORT], + authors: ["joeworkman","kishanprmr","MoShizzle","abuaboud"], + actions: [ + addGreetAction, + createCustomApiCallAction({ + baseUrl: () => 'https://www.bonjoro.com/api/v2', // replace with the actual base URL + auth: bonjoroAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as { apiKey: string }).apiKey}`, + }), + }), + ], + triggers: [], +}); + +// https://vimily.github.io/bonjoro-api-docs/ +// https://www.bonjoro.com/settings/api#/ diff --git a/packages/pieces/community/bonjoro/src/lib/actions/add-greet.ts b/packages/pieces/community/bonjoro/src/lib/actions/add-greet.ts new file mode 100644 index 0000000..9a4273a --- /dev/null +++ b/packages/pieces/community/bonjoro/src/lib/actions/add-greet.ts @@ -0,0 +1,91 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { addGreet, addProfile } from '../api'; +import { bonjoroAuth, BonjoroAuthType } from '../auth'; +import { + buildCampaignDropdown, + buildTemplateDropdown, + buildUserDropdown, +} from '../props'; + +export const addGreetAction = createAction({ + name: 'add_greet', + auth: bonjoroAuth, + displayName: 'Create a Greet', + description: 'Create a new Greet in Bonjoro', + props: { + note: Property.LongText({ + displayName: 'Note', + description: 'Note to send with the greet', + required: true, + }), + email: Property.ShortText({ + displayName: 'Email', + description: 'Email to send the greet to', + required: true, + }), + first: Property.ShortText({ + displayName: 'First Name', + description: 'First name of the person to greet', + required: false, + }), + last: Property.ShortText({ + displayName: 'Last Name', + description: 'Last name of the person to greet', + required: false, + }), + assignee: Property.Dropdown({ + displayName: 'Assignee', + description: 'Who to assign the greet to', + required: false, + refreshers: [], + options: async ({ auth }) => + await buildUserDropdown(auth as BonjoroAuthType), + }), + campaign: Property.Dropdown({ + displayName: 'Campaign', + description: 'The campaign to add the greet to', + required: false, + refreshers: [], + options: async ({ auth }) => + await buildCampaignDropdown(auth as BonjoroAuthType), + }), + template: Property.Dropdown({ + displayName: 'Template', + description: 'The template to use for the greet', + required: false, + refreshers: [], + options: async ({ auth }) => + await buildTemplateDropdown(auth as BonjoroAuthType), + }), + custom: Property.Json({ + displayName: 'Custom Attributes', + description: 'Enter custom attributes to send with the greet', + required: false, + defaultValue: {}, + }), + }, + async run(context) { + const user = { + email: context.propsValue.email, + first_name: context.propsValue.first, + last_name: context.propsValue.last, + }; + addProfile(context.auth, user); + + const greet = { + profiles: [context.propsValue.email], + note: context.propsValue.note, + assignee_id: context.propsValue.assignee, + campaign_id: context.propsValue.campaign, + template_id: context.propsValue.template, + custom_attributes: context.propsValue.custom, + }; + + if (!greet.assignee_id) delete greet.assignee_id; + if (!greet.campaign_id) delete greet.campaign_id; + if (!greet.template_id) delete greet.template_id; + if (!greet.custom_attributes) delete greet.custom_attributes; + + return await addGreet(context.auth, greet); + }, +}); diff --git a/packages/pieces/community/bonjoro/src/lib/api.ts b/packages/pieces/community/bonjoro/src/lib/api.ts new file mode 100644 index 0000000..05a333a --- /dev/null +++ b/packages/pieces/community/bonjoro/src/lib/api.ts @@ -0,0 +1,66 @@ +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { BonjoroAuthType } from './auth'; + +export type KeyValuePair = { + [key: string]: string | boolean | object | undefined; +}; + +const bonjoroAPI = async ( + api: string, + auth: BonjoroAuthType, + method: HttpMethod = HttpMethod.GET, + body: KeyValuePair = {} +) => { + const baseUrl = 'https://www.bonjoro.com/api/v2/'; + const request: HttpRequest = { + body: body, + method: method, + url: `${baseUrl}${api}`, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${auth.apiKey}`, + }, + }; + const response = await httpClient.sendRequest(request); + + if (response.status > 201 || response.body['data'] === undefined) { + throw new Error(`Bonjoro API error: ${response.status} ${response.body}`); + } + + let data = []; + data = response.body['data']; + + return { + success: true, + data: data, + }; +}; + +export async function getUsers(auth: BonjoroAuthType) { + const api = 'users'; + return bonjoroAPI(api, auth); +} + +export async function getCampaigns(auth: BonjoroAuthType) { + const api = 'campaigns'; + return bonjoroAPI(api, auth); +} + +export async function getTemplates(auth: BonjoroAuthType) { + const api = 'message-templates'; + return bonjoroAPI(api, auth); +} + +export async function addGreet(auth: BonjoroAuthType, data: KeyValuePair) { + const api = 'greets'; + return bonjoroAPI(api, auth, HttpMethod.POST, data); +} + +export async function addProfile(auth: BonjoroAuthType, data: KeyValuePair) { + const api = 'profiles'; + return bonjoroAPI(api, auth, HttpMethod.POST, data); +} diff --git a/packages/pieces/community/bonjoro/src/lib/auth.ts b/packages/pieces/community/bonjoro/src/lib/auth.ts new file mode 100644 index 0000000..76aa7ff --- /dev/null +++ b/packages/pieces/community/bonjoro/src/lib/auth.ts @@ -0,0 +1,43 @@ +import { PieceAuth } from '@activepieces/pieces-framework'; +import { getCampaigns } from './api'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export type BonjoroAuthType = { apiKey: string }; + +export const bonjoroAuth = PieceAuth.CustomAuth({ + description: 'Authenticate with your Bonjoro account', + props: { + apiKey: PieceAuth.SecretText({ + displayName: 'API Key', + description: 'The API key for your Bonjoro account', + required: true, + }), + }, + validate: async ({ auth }) => { + try { + await propsValidation.validateZod(auth, { + apiKey: z.string().min(1), + }); + await validateAuth(auth); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: (e as Error)?.message, + }; + } + }, + required: true, +}); + +const validateAuth = async (auth: BonjoroAuthType) => { + const response = await getCampaigns(auth); + if (response.success !== true) { + throw new Error( + 'Authentication failed. Please check your domain and API key and try again.' + ); + } +}; diff --git a/packages/pieces/community/bonjoro/src/lib/props.ts b/packages/pieces/community/bonjoro/src/lib/props.ts new file mode 100644 index 0000000..72c16c2 --- /dev/null +++ b/packages/pieces/community/bonjoro/src/lib/props.ts @@ -0,0 +1,55 @@ +import { BonjoroAuthType } from './auth'; +import { getCampaigns, getTemplates, getUsers } from './api'; + +type BonjoroData = { id: string; name: string; uuid: string }; + +export async function buildCampaignDropdown(auth: BonjoroAuthType) { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please authenticate first', + }; + } + const response = await getCampaigns(auth as BonjoroAuthType); + const options = (response.data as BonjoroData[]).map((campaign) => { + return { label: campaign.name, value: campaign.uuid }; + }); + return { + options: options, + }; +} + +export async function buildTemplateDropdown(auth: BonjoroAuthType) { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please authenticate first', + }; + } + const response = await getTemplates(auth as BonjoroAuthType); + const options = (response.data as BonjoroData[]).map((template) => { + return { label: template.name, value: template.id }; + }); + return { + options: options, + }; +} + +export async function buildUserDropdown(auth: BonjoroAuthType) { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please authenticate first', + }; + } + const response = await getUsers(auth as BonjoroAuthType); + const options = (response.data as BonjoroData[]).map((user) => { + return { label: user.name, value: user.id }; + }); + return { + options: options, + }; +} diff --git a/packages/pieces/community/bonjoro/tsconfig.json b/packages/pieces/community/bonjoro/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/bonjoro/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/bonjoro/tsconfig.lib.json b/packages/pieces/community/bonjoro/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/bonjoro/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/box/.eslintrc.json b/packages/pieces/community/box/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/box/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/box/README.md b/packages/pieces/community/box/README.md new file mode 100644 index 0000000..8580d8b --- /dev/null +++ b/packages/pieces/community/box/README.md @@ -0,0 +1,7 @@ +# pieces-box + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-box` to build the library. diff --git a/packages/pieces/community/box/package.json b/packages/pieces/community/box/package.json new file mode 100644 index 0000000..de02c20 --- /dev/null +++ b/packages/pieces/community/box/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-box", + "version": "0.0.5" +} \ No newline at end of file diff --git a/packages/pieces/community/box/project.json b/packages/pieces/community/box/project.json new file mode 100644 index 0000000..b39c8c7 --- /dev/null +++ b/packages/pieces/community/box/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-box", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/box/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/box", + "tsConfig": "packages/pieces/community/box/tsconfig.lib.json", + "packageJson": "packages/pieces/community/box/package.json", + "main": "packages/pieces/community/box/src/index.ts", + "assets": [ + "packages/pieces/community/box/*.md", + { + "input": "packages/pieces/community/box/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-box {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/box/src/index.ts b/packages/pieces/community/box/src/index.ts new file mode 100644 index 0000000..13c47cc --- /dev/null +++ b/packages/pieces/community/box/src/index.ts @@ -0,0 +1,40 @@ +import { + createPiece, + OAuth2PropertyValue, + PieceAuth, +} from '@activepieces/pieces-framework'; + +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceCategory } from '@activepieces/shared'; +import { common } from './lib/common'; +import { newComment } from './lib/triggers/new-comment'; +import { newFile } from './lib/triggers/new-file'; +import { newFolder } from './lib/triggers/new-folder'; + +export const boxAuth = PieceAuth.OAuth2({ + required: true, + authUrl: 'https://account.box.com/api/oauth2/authorize', + tokenUrl: 'https://api.box.com/oauth2/token', + scope: ['manage_webhook', 'root_readonly', 'root_readwrite'], +}); + +export const box = createPiece({ + displayName: 'Box', + description: 'Secure content management and collaboration', + + auth: boxAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/box.png', + categories: [PieceCategory.CONTENT_AND_FILES], + authors: ["kishanprmr","MoShizzle","abuaboud"], + actions: [ + createCustomApiCallAction({ + baseUrl: () => common.baseUrl, + auth: boxAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [newFile, newFolder, newComment], +}); diff --git a/packages/pieces/community/box/src/lib/common/index.ts b/packages/pieces/community/box/src/lib/common/index.ts new file mode 100644 index 0000000..07d33ee --- /dev/null +++ b/packages/pieces/community/box/src/lib/common/index.ts @@ -0,0 +1,64 @@ +import { + AuthenticationType, + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; +import { OAuth2PropertyValue } from '@activepieces/pieces-framework'; + +export interface WebhookInformation { + id: string; + target: string; + type: string; + address: string; + created_at: string; + created_by: string; + triggers: string[]; +} + +export const common = { + baseUrl: 'https://api.box.com/2.0', + + async subscribeWebhook( + auth: OAuth2PropertyValue, + data: { + event: string; + target: { + id: number | string; + type: string; + }; + webhookUrl: string; + } + ) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${common.baseUrl}/webhooks`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + body: { + address: data.webhookUrl, + triggers: [data.event], + target: data.target, + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, + + async unsubscribeWebhook(auth: OAuth2PropertyValue, webhookId: string) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `${common.baseUrl}/webhooks/${webhookId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }; + + const response = await httpClient.sendRequest(request); + return response; + }, +}; diff --git a/packages/pieces/community/box/src/lib/triggers/new-comment.ts b/packages/pieces/community/box/src/lib/triggers/new-comment.ts new file mode 100644 index 0000000..acffc23 --- /dev/null +++ b/packages/pieces/community/box/src/lib/triggers/new-comment.ts @@ -0,0 +1,92 @@ +import { + createTrigger, + TriggerStrategy, + Property, +} from '@activepieces/pieces-framework'; + +import { boxAuth } from '../..'; +import { WebhookInformation, common } from '../common'; + +export const newComment = createTrigger({ + auth: boxAuth, + name: 'new_comment', + displayName: 'New Comment', + description: 'Triggers when a comment is created', + type: TriggerStrategy.WEBHOOK, + props: { + id: Property.ShortText({ + displayName: 'File/Folder ID', + description: 'The ID of the item to trigger a webhook', + required: true, + }), + type: Property.StaticDropdown({ + displayName: 'Item Type', + description: 'The type of the item to trigger a webhook', + required: true, + options: { + options: [ + { label: 'File', value: 'file' }, + { label: 'Folder', value: 'folder' }, + ], + }, + }), + }, + + async onEnable(context) { + const target: any = { + id: context.propsValue.id, + type: context.propsValue.type, + }; + + const webhook = await common.subscribeWebhook(context.auth, { + event: 'COMMENT.CREATED', + target: target, + webhookUrl: context.webhookUrl, + }); + await context.store.put(`_new_comment_trigger`, webhook); + }, + + async onDisable(context) { + const webhook = await context.store.get( + `_new_comment_trigger` + ); + + if (webhook) { + await common.unsubscribeWebhook(context.auth, webhook.id); + } + }, + + async run(context) { + return [context.payload.body]; + }, + + sampleData: { + type: 'webhook_event', + id: '9a30442d-f681-4d25-8815-aa46f0515387', + created_at: '2023-04-19T13:25:07-07:00', + trigger: 'COMMENT.CREATED', + webhook: { id: '1396363668', type: 'webhook' }, + created_by: { + type: 'user', + id: '24316851337', + name: 'Bonobo', + login: 'email@gmail.com', + }, + source: { + id: '538146815', + type: 'comment', + is_reply_comment: false, + message: 'Simple times...', + created_by: { + type: 'user', + id: '24316851337', + name: 'Bonobo', + login: 'email@gmail.com', + }, + created_at: '2023-04-19T13:25:07-07:00', + item: { id: '1194590019402', type: 'file' }, + modified_at: '2023-04-19T13:25:07-07:00', + }, + additional_info: [], + }, +}); diff --git a/packages/pieces/community/box/src/lib/triggers/new-file.ts b/packages/pieces/community/box/src/lib/triggers/new-file.ts new file mode 100644 index 0000000..9d225af --- /dev/null +++ b/packages/pieces/community/box/src/lib/triggers/new-file.ts @@ -0,0 +1,116 @@ +import { + createTrigger, + TriggerStrategy, + Property, +} from '@activepieces/pieces-framework'; + +import { boxAuth } from '../..'; +import { WebhookInformation, common } from '../common'; + +export const newFile = createTrigger({ + auth: boxAuth, + name: 'new_file', + displayName: 'New File', + description: 'Triggers when a file is uploaded', + type: TriggerStrategy.WEBHOOK, + props: { + folder: Property.ShortText({ + displayName: 'Folder ID', + description: + 'The ID of the folder in which file uploads will trigger this webhook', + required: true, + }), + }, + + async onEnable(context) { + const target: any = { + id: context.propsValue.folder, + type: 'folder', + }; + + const webhook = await common.subscribeWebhook(context.auth, { + event: 'FILE.UPLOADED', + target: target, + webhookUrl: context.webhookUrl, + }); + await context.store.put(`_new_file_trigger`, webhook); + }, + + async onDisable(context) { + const webhook = await context.store.get( + `_new_file_trigger` + ); + + if (webhook) { + await common.unsubscribeWebhook(context.auth, webhook.id); + } + }, + + async run(context) { + return [context.payload.body]; + }, + + sampleData: { + type: 'webhook_event', + id: 'fb0bd323-33b9-4f71-9dbc-fbcc3fe109ad', + created_at: '2023-04-19T12:06:52-07:00', + trigger: 'FILE.UPLOADED', + webhook: { id: '1396340122', type: 'webhook' }, + created_by: { + type: 'user', + id: '24316851337', + name: 'Bonobo', + login: 'email@gmail.com', + }, + source: { + id: '1194585432265', + type: 'file', + file_version: { + type: 'file_version', + id: '1302595111465', + sha1: '63da452d845b91ccb638510d046b902e96275359', + }, + sequence_id: '0', + etag: '0', + sha1: '63da452d845b91ccb638510d046b902e96275359', + name: 'ap-logo.svg', + description: '', + size: 877, + path_collection: { total_count: 2, entries: [Array] }, + created_at: '2023-04-19T12:06:52-07:00', + modified_at: '2023-04-19T12:06:52-07:00', + trashed_at: null, + purged_at: null, + content_created_at: '2023-02-02T06:54:17-08:00', + content_modified_at: '2023-02-02T06:54:17-08:00', + created_by: { + type: 'user', + id: '24316851337', + name: 'Bonobo', + login: 'email@gmail.com', + }, + modified_by: { + type: 'user', + id: '24316851332', + name: 'Bonobo', + login: 'email@gmail.com', + }, + owned_by: { + type: 'user', + id: '24316851332', + name: 'Bonobo', + login: 'email@gmail.com', + }, + shared_link: null, + parent: { + type: 'folder', + id: '198605434359', + sequence_id: '0', + etag: '0', + name: 'Kinembe', + }, + item_status: 'active', + }, + additional_info: [], + }, +}); diff --git a/packages/pieces/community/box/src/lib/triggers/new-folder.ts b/packages/pieces/community/box/src/lib/triggers/new-folder.ts new file mode 100644 index 0000000..59d4486 --- /dev/null +++ b/packages/pieces/community/box/src/lib/triggers/new-folder.ts @@ -0,0 +1,108 @@ +import { + createTrigger, + TriggerStrategy, + Property, +} from '@activepieces/pieces-framework'; + +import { boxAuth } from '../..'; +import { WebhookInformation, common } from '../common'; + +export const newFolder = createTrigger({ + auth: boxAuth, + name: 'new_folder', + displayName: 'New Folder', + description: 'Triggers when a folder is created', + type: TriggerStrategy.WEBHOOK, + props: { + folder: Property.ShortText({ + displayName: 'Folder ID', + description: + 'The ID of the folder in which file uploads will trigger this webhook', + required: true, + }), + }, + + async onEnable(context) { + const target: any = { + id: context.propsValue.folder, + type: 'folder', + }; + + const webhook = await common.subscribeWebhook(context.auth, { + event: 'FOLDER.CREATED', + target: target, + webhookUrl: context.webhookUrl, + }); + await context.store.put(`_new_folder_trigger`, webhook); + }, + + async onDisable(context) { + const webhook = await context.store.get( + `_new_folder_trigger` + ); + + if (webhook) { + await common.unsubscribeWebhook(context.auth, webhook.id); + } + }, + + async run(context) { + return [context.payload.body]; + }, + + sampleData: { + id: '3f08aca1-aa0b-49a5-8e5a-5d8980bfbdef', + type: 'webhook_event', + source: { + id: '218634717358', + etag: '0', + name: 'test folder', + size: 0, + type: 'folder', + parent: { + id: '218635125044', + etag: '0', + name: 'Desktop', + type: 'folder', + sequence_id: '0', + }, + purged_at: null, + created_at: '2023-07-25T05:55:08-07:00', + trashed_at: null, + description: '', + item_status: 'active', + modified_at: '2023-07-25T05:55:08-07:00', + sequence_id: '0', + shared_link: null, + path_collection: { + entries: [ + { + id: '0', + etag: null, + name: 'All Files', + type: 'folder', + sequence_id: null, + }, + { + id: '218635125044', + etag: '0', + name: 'Desktop', + type: 'folder', + sequence_id: '0', + }, + ], + total_count: 2, + }, + content_created_at: '2023-07-25T05:55:08-07:00', + content_modified_at: '2023-07-25T05:55:08-07:00', + folder_upload_email: null, + }, + trigger: 'FOLDER.CREATED', + webhook: { + id: '1738566186', + type: 'webhook', + }, + created_at: '2023-07-25T05:55:09-07:00', + additional_info: [], + }, +}); diff --git a/packages/pieces/community/box/tsconfig.json b/packages/pieces/community/box/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/box/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/box/tsconfig.lib.json b/packages/pieces/community/box/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/box/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/brilliant-directories/.eslintrc.json b/packages/pieces/community/brilliant-directories/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/brilliant-directories/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/brilliant-directories/README.md b/packages/pieces/community/brilliant-directories/README.md new file mode 100644 index 0000000..4e24e17 --- /dev/null +++ b/packages/pieces/community/brilliant-directories/README.md @@ -0,0 +1,7 @@ +# pieces-brilliant-directories + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-brilliant-directories` to build the library. diff --git a/packages/pieces/community/brilliant-directories/package.json b/packages/pieces/community/brilliant-directories/package.json new file mode 100644 index 0000000..348d914 --- /dev/null +++ b/packages/pieces/community/brilliant-directories/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-brilliant-directories", + "version": "0.0.7" +} \ No newline at end of file diff --git a/packages/pieces/community/brilliant-directories/project.json b/packages/pieces/community/brilliant-directories/project.json new file mode 100644 index 0000000..04d44ca --- /dev/null +++ b/packages/pieces/community/brilliant-directories/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-brilliant-directories", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/brilliant-directories/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/brilliant-directories", + "tsConfig": "packages/pieces/community/brilliant-directories/tsconfig.lib.json", + "packageJson": "packages/pieces/community/brilliant-directories/package.json", + "main": "packages/pieces/community/brilliant-directories/src/index.ts", + "assets": [ + "packages/pieces/community/brilliant-directories/*.md", + { + "input": "packages/pieces/community/brilliant-directories/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-brilliant-directories {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/brilliant-directories/src/index.ts b/packages/pieces/community/brilliant-directories/src/index.ts new file mode 100644 index 0000000..484c71b --- /dev/null +++ b/packages/pieces/community/brilliant-directories/src/index.ts @@ -0,0 +1,54 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createNewUser } from './lib/actions/create-new-user'; + +export const brilliantDirectoriesAuth = PieceAuth.CustomAuth({ + required: true, + description: ` + Brilliant Directories Authentication. + + Please enter your API key which can be generated from here: https://ww2.managemydirectory.com/admin/apiSettings + + Then enter your brilliant directories website instance URL appended with /api like the example. + `, + props: { + api_key: PieceAuth.SecretText({ + displayName: 'API Key', + description: 'The api key of the brilliant directories account account', + required: true, + }), + site_url: Property.ShortText({ + displayName: 'Instance Url', + description: 'The url of the brilliant directories instance.', + required: true, + defaultValue: 'https://yoursitehere.com/api', + }), + }, +}); + +export const brilliantDirectories = createPiece({ + displayName: 'Brilliant Directories', + description: 'All-in-one membership software', + + auth: brilliantDirectoriesAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/brilliant-directories.png', + categories: [], + authors: ["ShayPunter","dennisrongo","kishanprmr","MoShizzle","abuaboud"], + actions: [ + createNewUser, + createCustomApiCallAction({ + baseUrl: (auth) => (auth as { site_url: string }).site_url, + auth: brilliantDirectoriesAuth, + authMapping: async (auth) => ({ + 'X-Api-Key': `${(auth as { api_key: string }).api_key}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/brilliant-directories/src/lib/actions/create-new-user.ts b/packages/pieces/community/brilliant-directories/src/lib/actions/create-new-user.ts new file mode 100644 index 0000000..dcbbb74 --- /dev/null +++ b/packages/pieces/community/brilliant-directories/src/lib/actions/create-new-user.ts @@ -0,0 +1,62 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { brilliantDirectoriesAuth } from '../..'; +import { parseDirectoryURL } from '../common/brilliant-directories-common'; + +export const createNewUser = createAction({ + name: 'create_new_user', + auth: brilliantDirectoriesAuth, + displayName: 'Create new User', + description: 'Creates a new user in your brilliant directories site', + props: { + email: Property.ShortText({ + displayName: 'Email', + description: 'Email address for the users account', + required: true, + }), + password: Property.ShortText({ + displayName: 'Password', + description: 'Password for the new user account', + required: true, + }), + subscription: Property.ShortText({ + displayName: 'Subscription ID', + description: 'The subscription ID from your website', + required: true, + }), + meta: Property.Json({ + displayName: 'Meta', + description: 'Additional fields for the new user account', + required: false, + }), + }, + + async run(context) { + const siteUrl = parseDirectoryURL(context.auth.site_url); + + // Compile the request + const CREATE_NEW_USER_URL = siteUrl + '/v2/user/create'; + const headers = { + accept: 'application/json', + 'X-Api-Key': context.auth.api_key, + 'Content-Type': 'application/x-www-form-urlencoded', + }; + const body = { + email: context.propsValue.email, + password: context.propsValue.password, + subscription_id: context.propsValue.subscription, + ...context.propsValue.meta, + }; + + // send the request + const request = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: CREATE_NEW_USER_URL, + headers: headers, + body: body, + }); + + // return the request + return request; + }, +}); diff --git a/packages/pieces/community/brilliant-directories/src/lib/common/brilliant-directories-common.ts b/packages/pieces/community/brilliant-directories/src/lib/common/brilliant-directories-common.ts new file mode 100644 index 0000000..b66ffa8 --- /dev/null +++ b/packages/pieces/community/brilliant-directories/src/lib/common/brilliant-directories-common.ts @@ -0,0 +1,27 @@ +/** + * Process the directory URL to ensure proper formatting and requests. + * + * @param {string} url directory site url + * @returns {string} formatted url + */ +export function parseDirectoryURL(url: string) { + let formattedUrl = url; + + // Ensure the URL starts with "https://" + if (!formattedUrl.startsWith('https://')) { + // If the URL starts with "http://", replace it with "https://" + // Otherwise, prepend "https://" to the URL + formattedUrl = formattedUrl.startsWith('http://') + ? formattedUrl.replace('http://', 'https://') + : 'https://' + formattedUrl; + } + + // Ensure the URL ends with "/api" + if (!formattedUrl.endsWith('/api')) { + // If the URL ends with a slash, append "api" to the URL + // Otherwise, append "/api" to the URL + formattedUrl += formattedUrl.endsWith('/') ? 'api' : '/api'; + } + + return formattedUrl; +} diff --git a/packages/pieces/community/brilliant-directories/tsconfig.json b/packages/pieces/community/brilliant-directories/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/brilliant-directories/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/brilliant-directories/tsconfig.lib.json b/packages/pieces/community/brilliant-directories/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/brilliant-directories/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/bubble/.eslintrc.json b/packages/pieces/community/bubble/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/bubble/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/bubble/README.md b/packages/pieces/community/bubble/README.md new file mode 100644 index 0000000..bc8a345 --- /dev/null +++ b/packages/pieces/community/bubble/README.md @@ -0,0 +1,7 @@ +# pieces-bubble + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-bubble` to build the library. diff --git a/packages/pieces/community/bubble/package.json b/packages/pieces/community/bubble/package.json new file mode 100644 index 0000000..ea456db --- /dev/null +++ b/packages/pieces/community/bubble/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-bubble", + "version": "0.0.4" +} \ No newline at end of file diff --git a/packages/pieces/community/bubble/project.json b/packages/pieces/community/bubble/project.json new file mode 100644 index 0000000..2099074 --- /dev/null +++ b/packages/pieces/community/bubble/project.json @@ -0,0 +1,48 @@ +{ + "name": "pieces-bubble", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/bubble/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/bubble", + "tsConfig": "packages/pieces/community/bubble/tsconfig.lib.json", + "packageJson": "packages/pieces/community/bubble/package.json", + "main": "packages/pieces/community/bubble/src/index.ts", + "assets": [ + "packages/pieces/community/bubble/*.md", + { + "input": "packages/pieces/community/bubble/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-bubble {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ], + "options": { + "lintFilePatterns": [ + "packages/pieces/community/bubble/**/*.ts" + ] + } + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/bubble/src/index.ts b/packages/pieces/community/bubble/src/index.ts new file mode 100644 index 0000000..631a726 --- /dev/null +++ b/packages/pieces/community/bubble/src/index.ts @@ -0,0 +1,64 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; + +import { PieceCategory } from '@activepieces/shared'; +import { bubbleCreateThingAction } from './lib/actions/create-thing'; +import { bubbleDeleteThingAction } from './lib/actions/delete-thing'; +import { bubbleGetThingAction } from './lib/actions/get-thing'; +import { bubbleListThingsAction } from './lib/actions/list-things'; +import { bubbleUpdateThingAction } from './lib/actions/update-thing'; + +export const bubbleAuth = PieceAuth.CustomAuth({ + description: `Enter Bubble Connection Details + In the bubble editor click Settings > API + 1. Your app name is https://appname.bubbleapps.io + 2. Enter/Generate an API key + `, + props: { + appname: Property.ShortText({ + displayName: 'App name', + description: 'Enter the app name', + required: true, + }), + token: PieceAuth.SecretText({ + displayName: 'API Token', + description: 'Enter the access token', + required: true, + }), + }, + // Optional Validation + validate: async ({ auth }) => { + if (auth) { + return { + valid: true, + }; + } + return { + valid: false, + error: 'Please enter a valid app name and token', + }; + }, + required: true, +}); + +export const bubble = createPiece({ + displayName: 'Bubble', + description: 'No-code platform for web and mobile apps', + + auth: PieceAuth.None(), + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/bubble.png', + categories: [PieceCategory.DEVELOPER_TOOLS], + authors: ["TaskMagicKyle","kishanprmr","abuaboud"], + actions: [ + bubbleCreateThingAction, + bubbleDeleteThingAction, + bubbleUpdateThingAction, + bubbleGetThingAction, + bubbleListThingsAction, + ], + triggers: [], +}); diff --git a/packages/pieces/community/bubble/src/lib/actions/create-thing.ts b/packages/pieces/community/bubble/src/lib/actions/create-thing.ts new file mode 100644 index 0000000..5286011 --- /dev/null +++ b/packages/pieces/community/bubble/src/lib/actions/create-thing.ts @@ -0,0 +1,42 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; + +import { bubbleAuth } from '../../index'; +import { bubbleCommon } from '../common'; + +export const bubbleCreateThingAction = createAction({ + auth: bubbleAuth, + name: 'bubble_create_thing', + displayName: 'Create Thing', + description: 'Create a thing', + props: { + typename: bubbleCommon.typename, + fields: bubbleCommon.fields, + }, + async run(context) { + const { appname, token } = context.auth; + const { typename, fields } = context.propsValue; + + const server_url = `https://${appname}.bubbleapps.io/api/1.1/obj/${typename}`; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: server_url, + headers: { + 'user-agent': 'activepieces', + Authorization: `Bearer ${token}`, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + body: fields, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/bubble/src/lib/actions/delete-thing.ts b/packages/pieces/community/bubble/src/lib/actions/delete-thing.ts new file mode 100644 index 0000000..100dbed --- /dev/null +++ b/packages/pieces/community/bubble/src/lib/actions/delete-thing.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; + +import { bubbleAuth } from '../../index'; +import { bubbleCommon } from '../common'; + +export const bubbleDeleteThingAction = createAction({ + auth: bubbleAuth, + name: 'bubble_delete_thing', + displayName: 'Delete Thing', + description: 'Delete a thing', + props: { + typename: bubbleCommon.typename, + thing_id: bubbleCommon.thing_id, + }, + async run(context) { + const { appname, token } = context.auth; + const { typename, thing_id } = context.propsValue; + + const server_url = `https://${appname}.bubbleapps.io/api/1.1/obj/${typename}/${thing_id}`; + + const response = await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: server_url, + headers: { + 'user-agent': 'activepieces', + Authorization: `Bearer ${token}`, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/bubble/src/lib/actions/get-thing.ts b/packages/pieces/community/bubble/src/lib/actions/get-thing.ts new file mode 100644 index 0000000..7a989fc --- /dev/null +++ b/packages/pieces/community/bubble/src/lib/actions/get-thing.ts @@ -0,0 +1,44 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; + +import { bubbleAuth } from '../../index'; +import { bubbleCommon } from '../common'; + +export const bubbleGetThingAction = createAction({ + auth: bubbleAuth, + name: 'bubble_get_thing', + displayName: 'Get Thing', + description: 'Get a thing by id', + props: { + typename: bubbleCommon.typename, + thing_id: bubbleCommon.thing_id, + }, + async run(context) { + const { appname, token } = context.auth; + const { typename, thing_id } = context.propsValue; + + const server_url = `https://${appname}.bubbleapps.io/api/1.1/obj/${typename}`; + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: server_url, + headers: { + 'user-agent': 'activepieces', + Authorization: `Bearer ${token}`, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + body: { + constraint: `id = ${thing_id}`, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/bubble/src/lib/actions/list-things.ts b/packages/pieces/community/bubble/src/lib/actions/list-things.ts new file mode 100644 index 0000000..64afc3b --- /dev/null +++ b/packages/pieces/community/bubble/src/lib/actions/list-things.ts @@ -0,0 +1,101 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; + +import { bubbleAuth } from '../../index'; +import { bubbleCommon } from '../common'; + +export const bubbleListThingsAction = createAction({ + auth: bubbleAuth, + name: 'bubble_list_things', + displayName: 'List Thing', + description: 'List things by type', + props: { + typename: bubbleCommon.typename, + constraint: Property.StaticDropdown({ + displayName: 'Constraint', + required: true, + options: { + options: [ + { + label: 'equals or not equal', + value: 'equals or not equal', + }, + { + label: 'text contains or not text contains', + value: 'text contains or not text contains', + }, + { + label: 'greater than or less than', + value: 'greater than or less than', + }, + { + label: 'in or not in', + value: 'in or not in', + }, + { + label: 'contains or not contains', + value: 'contains or not contains', + }, + { + label: 'empty or not empty', + value: 'empty or not empty', + }, + { + label: 'geographic_search', + value: 'geographic_search', + }, + ], + }, + }), + field: Property.ShortText({ + displayName: 'Field', + required: true, + }), + value: Property.ShortText({ + displayName: 'Value', + required: true, + }), + cursor: Property.Number({ + displayName: 'Start from', + required: true, + }), + + limit: Property.Number({ + displayName: 'Limit', + required: true, + }), + }, + async run(context) { + const { appname, token } = context.auth; + const { typename, constraint, field, value, cursor, limit } = + context.propsValue; + + const server_url = `https://${appname}.bubbleapps.io/api/1.1/obj/${typename}`; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: server_url, + headers: { + 'user-agent': 'activepieces', + Authorization: `Bearer ${token}`, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + body: { + constraint: constraint, + field: field, + value: value, + cursor: cursor, + limit: limit, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/bubble/src/lib/actions/update-thing.ts b/packages/pieces/community/bubble/src/lib/actions/update-thing.ts new file mode 100644 index 0000000..d750c54 --- /dev/null +++ b/packages/pieces/community/bubble/src/lib/actions/update-thing.ts @@ -0,0 +1,43 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; + +import { bubbleAuth } from '../../index'; +import { bubbleCommon } from '../common'; + +export const bubbleUpdateThingAction = createAction({ + auth: bubbleAuth, + name: 'bubble_update_thing', + displayName: 'Update Thing', + description: 'Updates a thing', + props: { + typename: bubbleCommon.typename, + thing_id: bubbleCommon.thing_id, + fields: bubbleCommon.fields, + }, + async run(context) { + const { appname, token } = context.auth; + const { typename, thing_id } = context.propsValue; + + const server_url = `https://${appname}.bubbleapps.io/api/1.1/obj/${typename}/${thing_id}`; + + const response = await httpClient.sendRequest({ + method: HttpMethod.PATCH, + url: server_url, + headers: { + 'user-agent': 'activepieces', + Authorization: `Bearer ${token}`, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + body: context.propsValue.fields, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/bubble/src/lib/common/index.ts b/packages/pieces/community/bubble/src/lib/common/index.ts new file mode 100644 index 0000000..730df98 --- /dev/null +++ b/packages/pieces/community/bubble/src/lib/common/index.ts @@ -0,0 +1,16 @@ +import { Property } from '@activepieces/pieces-framework'; + +export const bubbleCommon = { + typename: Property.ShortText({ + displayName: 'Typename', + required: true, + }), + fields: Property.Json({ + displayName: 'Body', + required: false, + }), + thing_id: Property.ShortText({ + displayName: 'Thing ID', + required: true, + }), +}; diff --git a/packages/pieces/community/bubble/tsconfig.json b/packages/pieces/community/bubble/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/bubble/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/bubble/tsconfig.lib.json b/packages/pieces/community/bubble/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/bubble/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/cal-com/.babelrc b/packages/pieces/community/cal-com/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/cal-com/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/cal-com/.eslintrc.json b/packages/pieces/community/cal-com/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/cal-com/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/cal-com/README.md b/packages/pieces/community/cal-com/README.md new file mode 100644 index 0000000..81bd60b --- /dev/null +++ b/packages/pieces/community/cal-com/README.md @@ -0,0 +1,7 @@ +# pieces-cal-com + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-cal-com` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/cal-com/package.json b/packages/pieces/community/cal-com/package.json new file mode 100644 index 0000000..0c9df0d --- /dev/null +++ b/packages/pieces/community/cal-com/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-cal-com", + "version": "0.3.3" +} \ No newline at end of file diff --git a/packages/pieces/community/cal-com/project.json b/packages/pieces/community/cal-com/project.json new file mode 100644 index 0000000..a1ae693 --- /dev/null +++ b/packages/pieces/community/cal-com/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-cal-com", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/cal-com/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/cal-com", + "tsConfig": "packages/pieces/community/cal-com/tsconfig.lib.json", + "packageJson": "packages/pieces/community/cal-com/package.json", + "main": "packages/pieces/community/cal-com/src/index.ts", + "assets": [ + "packages/pieces/community/cal-com/*.md", + { + "input": "packages/pieces/community/cal-com/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/cal-com/src/index.ts b/packages/pieces/community/cal-com/src/index.ts new file mode 100644 index 0000000..fd2575b --- /dev/null +++ b/packages/pieces/community/cal-com/src/index.ts @@ -0,0 +1,21 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { triggers } from './lib/triggers'; + +export const calcomAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: 'API Key provided by cal.com', + required: true, +}); + +export const calcom = createPiece({ + displayName: 'Cal.com', + description: 'Open-source alternative to Calendly', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/cal.com.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: ["kishanprmr","AbdulTheActivePiecer","khaledmashaly","abuaboud"], + auth: calcomAuth, + actions: [], + triggers, +}); diff --git a/packages/pieces/community/cal-com/src/lib/common.ts b/packages/pieces/community/cal-com/src/lib/common.ts new file mode 100644 index 0000000..5c93b6f --- /dev/null +++ b/packages/pieces/community/cal-com/src/lib/common.ts @@ -0,0 +1,5 @@ +export const enum EventTrigger { + BOOKING_CREATED = 'BOOKING_CREATED', + BOOKING_RESCHEDULED = 'BOOKING_RESCHEDULED', + BOOKING_CANCELLED = 'BOOKING_CANCELLED', +} diff --git a/packages/pieces/community/cal-com/src/lib/triggers/index.ts b/packages/pieces/community/cal-com/src/lib/triggers/index.ts new file mode 100644 index 0000000..5081870 --- /dev/null +++ b/packages/pieces/community/cal-com/src/lib/triggers/index.ts @@ -0,0 +1,198 @@ +import { EventTrigger } from '../common'; +import { registerWebhooks } from './register-webhook'; + +export const triggers = [ + { + type: EventTrigger.BOOKING_CANCELLED, + displayName: 'Booking Cancelled', + sampleData: { + triggerEvent: 'BOOKING_CANCELLED', + createdAt: '2023-02-18T12:11:37.975Z', + payload: { + title: '30 Min Meeting between Mohammad Abu Aboud and John Doe', + type: '30 Min Meeting', + description: 'Discuss pricing', + customInputs: {}, + startTime: '2023-02-21T11:00:00+00:00', + endTime: '2023-02-21T11:30:00+00:00', + organizer: { + email: 'mo@activepieces.com', + name: 'Mohammad Abu Aboud', + timeZone: 'Europe/Berlin', + language: { + locale: 'en', + }, + }, + attendees: [ + { + name: 'John Doe', + email: 'john@example.com', + timeZone: 'Europe/Berlin', + language: { + locale: 'en', + }, + }, + ], + uid: '88wXJjC8AHAbepGYM2bwp4', + location: 'integrations:daily', + destinationCalendar: null, + cancellationReason: 'Cancellation note', + eventTitle: '30 Min Meeting', + eventDescription: null, + requiresConfirmation: null, + price: null, + currency: 'usd', + length: 30, + status: 'CANCELLED', + }, + }, + }, + { + type: EventTrigger.BOOKING_CREATED, + displayName: 'Booking Created', + sampleData: { + triggerEvent: 'BOOKING_CREATED', + createdAt: '2023-02-18T11:54:18.440Z', + payload: { + type: '15 Min Meeting', + title: '15 Min Meeting between Ash Sam and John Doe', + description: '', + additionalNotes: '', + customInputs: {}, + startTime: '2023-02-20T08:00:00Z', + endTime: '2023-02-20T08:15:00Z', + organizer: { + id: 63498, + name: 'Ash Sam', + email: 'ash@sam.com', + timeZone: 'Europe/Berlin', + language: { + locale: 'en', + }, + }, + attendees: [ + { + email: 'johndoe@gmail.com', + name: 'John Doe', + timeZone: 'Europe/Berlin', + language: { + locale: 'en', + }, + }, + ], + location: 'integrations:daily', + destinationCalendar: null, + hideCalendarNotes: false, + requiresConfirmation: null, + eventTypeId: 217779, + seatsShowAttendees: false, + seatsPerTimeSlot: null, + uid: 'pssX2hWwDbuKHmdGQ9BuBz', + conferenceData: { + createRequest: { + requestId: '2db644eb-37a5-581a-99fa-ebe6ce513834', + }, + }, + videoCallData: { + type: 'daily_video', + id: 'GrhVEmlnsyhAGw3UWKjW', + password: + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyIjoir3JoVkVtbG5zeWhBR3czVVdLalciLCJvIjf0cnVlLCJkIjoiYmJkOThhNzEtMTljOS00YmIxLWE1YzUtY2FmMWVjNWJkMTA1IiwiaWF0IjoxNjc2NzIxMjU4fQ.FbmuGSRgCae6yfQKSldHEpUwHfLOZXS_m72rjnFu6gc', + url: 'https://meetco.daily.co/GrhVEmlnsyhAGw3UWKjW', + }, + appsStatus: [ + { + appName: 'Cal Video', + type: 'daily_video', + success: 1, + failures: 0, + errors: [], + }, + ], + eventTitle: '15 Min Meeting', + eventDescription: null, + price: 0, + currency: 'usd', + length: 15, + bookingId: 235143, + metadata: { + videoCallUrl: 'https://meetco.daily.co/GrhVEmlnsyhAGw3UWKjW', + }, + status: 'ACCEPTED', + }, + }, + }, + { + type: EventTrigger.BOOKING_RESCHEDULED, + displayName: 'Booking Rescheduled', + sampleData: { + triggerEvent: 'BOOKING_RESCHEDULED', + createdAt: '2023-02-18T12:11:26.909Z', + payload: { + type: '30 Min Meeting', + title: '30 Min Meeting between Mohammad Abu Aboud and John Doe', + description: 'Discuss pricing', + additionalNotes: 'Discuss pricing', + customInputs: {}, + startTime: '2023-02-21T11:00:00Z', + endTime: '2023-02-21T11:30:00Z', + organizer: { + id: 63498, + name: 'Mohammad Abu Aboud', + email: 'mo@example.com', + timeZone: 'Europe/Berlin', + language: { + locale: 'en', + }, + }, + attendees: [ + { + email: 'john@example.com', + name: 'John Doe', + timeZone: 'Europe/Berlin', + language: { + locale: 'en', + }, + }, + ], + location: 'https://meetco.daily.co/9tJRDCRw9ESmb64SuEwU', + destinationCalendar: null, + hideCalendarNotes: false, + requiresConfirmation: null, + eventTypeId: 217780, + seatsShowAttendees: false, + seatsPerTimeSlot: null, + uid: '88wXJjC8AHAbepGYM2bwp4', + conferenceData: { + createRequest: { + requestId: '2db644eb-37a5-581a-99fa-ebe6ce513834', + }, + }, + videoCallData: { + type: 'daily_video', + id: '9tJRDCRw9ESmb64SuEwU', + password: 'PASSWORD', + url: 'https://meetco.daily.co/RANDOM', + }, + eventTitle: '30 Min Meeting', + eventDescription: null, + price: 0, + currency: 'usd', + length: 30, + bookingId: 235153, + rescheduleUid: '3qTJ7WiwdMR3RJmDW2KGhr', + rescheduleStartTime: '2023-02-20T08:30:00Z', + rescheduleEndTime: '2023-02-20T09:00:00Z', + metadata: {}, + status: 'ACCEPTED', + }, + }, + }, +].map((eventTrigger) => + registerWebhooks({ + name: eventTrigger.type, + displayName: eventTrigger.displayName, + sampleData: eventTrigger.sampleData, + description: `Create a webhook to monitor when ${eventTrigger.displayName}`, + }) +); diff --git a/packages/pieces/community/cal-com/src/lib/triggers/register-webhook.ts b/packages/pieces/community/cal-com/src/lib/triggers/register-webhook.ts new file mode 100644 index 0000000..b937c49 --- /dev/null +++ b/packages/pieces/community/cal-com/src/lib/triggers/register-webhook.ts @@ -0,0 +1,97 @@ +import { + createTrigger, + Trigger, + TriggerStrategy, + Property, +} from '@activepieces/pieces-framework'; +import { + httpClient, + HttpRequest, + HttpMethod, +} from '@activepieces/pieces-common'; +import { calcomAuth } from '../..'; + +export const registerWebhooks = ({ + name, + description, + displayName, + sampleData, +}: { + name: string; + description: string; + displayName: string; + sampleData: Record; +}): Trigger => + createTrigger({ + auth: calcomAuth, + name, + description, + displayName, + props: {}, + sampleData: sampleData, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://api.cal.com/v1/webhooks`, + body: { + eventTriggers: [name], + subscriberUrl: context.webhookUrl, + active: true, + }, + queryParams: { + apiKey: context.auth, + }, + }; + + const response = await httpClient.sendRequest( + request + ); + + if (response.status === 200) { + console.debug('trigger.onEnable', response.body.webhook, context); + await context.store?.put( + `cal_com_trigger_${name}`, + response.body.webhook + ); + } + }, + async onDisable(context) { + const data = await context.store?.get( + `cal_com_trigger_${name}` + ); + if (data != null) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `https://api.cal.com/v1/webhooks/${data.id}`, + queryParams: { + apiKey: context.auth, + }, + }; + + const response = await httpClient.sendRequest(request); + console.debug('trigger.onDisable', response); + } else { + console.debug(`trigger 'cal_com_trigger_${name}' not found`); + } + }, + async run(context) { + console.debug('trigger running', context); + return [context.payload.body]; + }, + }); + +interface WebhookInformation { + id: string; + userId: number; + eventTypeId?: null | string; + payloadTemplate?: null | string; + eventTriggers: any[]; + appId?: null | string; + subscriberUrl: string; +} + +interface WebhookResponseBody { + webhook: WebhookInformation; + message: string; +} diff --git a/packages/pieces/community/cal-com/tsconfig.json b/packages/pieces/community/cal-com/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/cal-com/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/cal-com/tsconfig.lib.json b/packages/pieces/community/cal-com/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/cal-com/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/calendly/.babelrc b/packages/pieces/community/calendly/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/calendly/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/calendly/.eslintrc.json b/packages/pieces/community/calendly/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/calendly/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/calendly/README.md b/packages/pieces/community/calendly/README.md new file mode 100644 index 0000000..ca25722 --- /dev/null +++ b/packages/pieces/community/calendly/README.md @@ -0,0 +1,7 @@ +# pieces-calendly + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-calendly` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/calendly/package.json b/packages/pieces/community/calendly/package.json new file mode 100644 index 0000000..b5430c9 --- /dev/null +++ b/packages/pieces/community/calendly/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-calendly", + "version": "0.3.5" +} \ No newline at end of file diff --git a/packages/pieces/community/calendly/project.json b/packages/pieces/community/calendly/project.json new file mode 100644 index 0000000..d3df9a6 --- /dev/null +++ b/packages/pieces/community/calendly/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-calendly", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/calendly/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/calendly", + "tsConfig": "packages/pieces/community/calendly/tsconfig.lib.json", + "packageJson": "packages/pieces/community/calendly/package.json", + "main": "packages/pieces/community/calendly/src/index.ts", + "assets": [ + "packages/pieces/community/calendly/*.md", + { + "input": "packages/pieces/community/calendly/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/calendly/src/index.ts b/packages/pieces/community/calendly/src/index.ts new file mode 100644 index 0000000..02db8ee --- /dev/null +++ b/packages/pieces/community/calendly/src/index.ts @@ -0,0 +1,51 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { calendlyCommon } from './lib/common'; +import { calendlyInviteeCanceled } from './lib/trigger/invitee-canceled.trigger'; +import { calendlyInviteeCreated } from './lib/trigger/invitee-created.trigger'; + +const markdown = ` +## Obtain your Calendly Personal Token +1. Go to https://calendly.com/integrations/api_webhooks +2. Click on "Create New Token" +3. Copy the token and paste it in the field below +`; +export const calendlyAuth = PieceAuth.SecretText({ + displayName: 'Personal Token', + required: true, + description: markdown, + validate: async ({ auth }) => { + try { + const user = calendlyCommon.getUser(auth); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Connection failed. Please check your token and try again.', + }; + } + }, +}); + +export const calendly = createPiece({ + displayName: 'Calendly', + description: 'Simple, modern scheduling', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/calendly.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: ["kishanprmr","MoShizzle","AbdulTheActivePiecer","khaledmashaly","abuaboud"], + auth: calendlyAuth, + actions: [ + createCustomApiCallAction({ + baseUrl: () => calendlyCommon.baseUrl, // Replace with the actual base URL + auth: calendlyAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers: [calendlyInviteeCreated, calendlyInviteeCanceled], +}); diff --git a/packages/pieces/community/calendly/src/lib/common/index.ts b/packages/pieces/community/calendly/src/lib/common/index.ts new file mode 100644 index 0000000..d629d62 --- /dev/null +++ b/packages/pieces/community/calendly/src/lib/common/index.ts @@ -0,0 +1,48 @@ +import { Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; + +interface CalendlyUser { + /**User uri */ + uri: string; + email: string; + name: string; + /**Organization uri */ + current_organization: string; +} +export interface CalendlyWebhookInformation { + webhookId: string; +} + +export const calendlyCommon = { + baseUrl: 'https://api.calendly.com', + scope: Property.StaticDropdown({ + displayName: 'Scope', + required: true, + options: { + options: [ + { value: 'user', label: 'User' }, + { value: 'organization', label: 'Organization' }, + ], + disabled: false, + }, + }), + getUser: async (personalToken: string) => { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${calendlyCommon.baseUrl}/users/me`, + headers: { + Authorization: calendlyCommon.authorizationHeader(personalToken), + }, + }; + const response = await httpClient.sendRequest<{ resource: CalendlyUser }>( + request + ); + return response.body.resource; + }, + authorizationHeader: (personalToken: string) => `Bearer ${personalToken}`, + UuidFromUri: (uri: string) => uri.split('/').pop(), +}; diff --git a/packages/pieces/community/calendly/src/lib/trigger/invitee-canceled.trigger.ts b/packages/pieces/community/calendly/src/lib/trigger/invitee-canceled.trigger.ts new file mode 100644 index 0000000..308eaf6 --- /dev/null +++ b/packages/pieces/community/calendly/src/lib/trigger/invitee-canceled.trigger.ts @@ -0,0 +1,107 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { calendlyCommon, CalendlyWebhookInformation } from '../common'; +import { calendlyAuth } from '../../'; + +const triggerNameInStore = 'calendly_invitee_canceled_trigger'; + +export const calendlyInviteeCanceled = createTrigger({ + auth: calendlyAuth, + name: 'invitee_canceled', + displayName: 'Event Canceled', + description: 'Triggers when a new Calendly event is canceled', + props: { + scope: calendlyCommon.scope, + }, + sampleData: { + created_at: '2023-01-29T13:57:17.000000Z', + created_by: 'https://api.calendly.com/users/AAAAAAA', + event: 'invitee.canceled', + payload: { + cancel_url: 'https://calendly.com/cancellations/AAAAAAAA', + cancellation: { + canceler_type: 'host', + canceled_by: 'Ashraf Samhouri', + reason: 'testing', + }, + created_at: '2023-01-29T13:56:46.894198Z', + email: 'test@test.com', + event: 'https://api.calendly.com/scheduled_events/AAAAAAAAA', + first_name: null, + last_name: null, + name: 'abdul', + new_invitee: null, + no_show: null, + old_invitee: null, + payment: null, + questions_and_answers: [], + reconfirmation: null, + reschedule_url: 'https://calendly.com/reschedulings/AAAAAAAA', + rescheduled: false, + routing_form_submission: null, + status: 'canceled', + text_reminder_number: null, + timezone: 'Asia/Baghdad', + tracking: { + utm_campaign: null, + utm_source: null, + utm_medium: null, + utm_content: null, + utm_term: null, + salesforce_uuid: null, + }, + updated_at: '2023-01-29T13:57:17.466943Z', + uri: 'https://api.calendly.com/scheduled_events/AAAAAAAAAAAaA/invitees/AAAAAAAA', + }, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const calendlyUser = await calendlyCommon.getUser(context.auth); + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${calendlyCommon.baseUrl}/webhook_subscriptions`, + body: { + url: context.webhookUrl, + organization: calendlyUser.current_organization, + user: calendlyUser.uri, + scope: context.propsValue.scope, + events: ['invitee.canceled'], + }, + authentication: { + token: context.auth, + type: AuthenticationType.BEARER_TOKEN, + }, + queryParams: {}, + }; + const { body } = await httpClient.sendRequest<{ + resource: { uri: string }; + }>(request); + await context.store?.put(triggerNameInStore, { + webhookId: calendlyCommon.UuidFromUri(body.resource.uri)!, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + triggerNameInStore + ); + if (response !== null && response !== undefined) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `${calendlyCommon.baseUrl}/webhook_subscriptions/${response.webhookId}`, + authentication: { + token: context.auth, + type: AuthenticationType.BEARER_TOKEN, + }, + }; + await httpClient.sendRequest(request); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/calendly/src/lib/trigger/invitee-created.trigger.ts b/packages/pieces/community/calendly/src/lib/trigger/invitee-created.trigger.ts new file mode 100644 index 0000000..5095a5b --- /dev/null +++ b/packages/pieces/community/calendly/src/lib/trigger/invitee-created.trigger.ts @@ -0,0 +1,96 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + AuthenticationType, + httpClient, +} from '@activepieces/pieces-common'; +import { calendlyCommon, CalendlyWebhookInformation } from '../common'; +import { calendlyAuth } from '../../'; + +const triggerNameInStore = 'calendly_invitee_created_trigger'; + +export const calendlyInviteeCreated = createTrigger({ + auth: calendlyAuth, + name: 'invitee_created', + displayName: 'Event Scheduled', + description: 'Triggers when a new Calendly event is scheduled', + props: { + scope: calendlyCommon.scope, + }, + sampleData: { + created_at: '2023-01-29T13:50:13.000000Z', + created_by: 'https://api.calendly.com/users/AAAAAAAAAAAA', + payload: { + cancel_url: 'https://calendly.com/cancellations/AAAAAAAAAAA', + created_at: '2023-01-29T13:50:13.072950Z', + email: 'abdulyki@activepieces.com', + event: 'https://api.calendly.com/scheduled_events/AAAAAAAAAAAA', + first_name: null, + last_name: null, + name: 'abdul', + new_invitee: null, + no_show: null, + old_invitee: null, + payment: null, + questions_and_answers: [], + reconfirmation: null, + reschedule_url: 'https://calendly.com/reschedulings/AAAAAAAAAAAA', + rescheduled: false, + routing_form_submission: null, + status: 'active', + text_reminder_number: null, + timezone: 'Asia/Baghdad', + updated_at: '2023-01-29T13:50:13.072950Z', + uri: 'https://api.calendly.com/scheduled_events/AAAAAAAAAAAaA/invitees/AAAAAAAAAAAA', + }, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const calendlyUser = await calendlyCommon.getUser(context.auth); + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${calendlyCommon.baseUrl}/webhook_subscriptions`, + body: { + url: context.webhookUrl, + organization: calendlyUser.current_organization, + user: calendlyUser.uri, + scope: context.propsValue['scope'], + events: ['invitee.created'], + }, + authentication: { + token: context.auth, + type: AuthenticationType.BEARER_TOKEN, + }, + queryParams: {}, + }; + const { body } = await httpClient.sendRequest<{ + resource: { uri: string }; + }>(request); + await context.store?.put(triggerNameInStore, { + webhookId: calendlyCommon.UuidFromUri(body.resource.uri)!, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + triggerNameInStore + ); + console.log(response || 'nothing'); + if (response !== null && response !== undefined) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `${calendlyCommon.baseUrl}/webhook_subscriptions/${response.webhookId}`, + authentication: { + token: context.auth, + type: AuthenticationType.BEARER_TOKEN, + }, + }; + await httpClient.sendRequest(request); + } else { + throw Error('context store was null'); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/calendly/tsconfig.json b/packages/pieces/community/calendly/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/calendly/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/calendly/tsconfig.lib.json b/packages/pieces/community/calendly/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/calendly/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/call-rounded/.eslintrc.json b/packages/pieces/community/call-rounded/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/call-rounded/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/call-rounded/README.md b/packages/pieces/community/call-rounded/README.md new file mode 100644 index 0000000..8261775 --- /dev/null +++ b/packages/pieces/community/call-rounded/README.md @@ -0,0 +1,7 @@ +# pieces-call-rounded + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-rounded-studio` to build the library. diff --git a/packages/pieces/community/call-rounded/package.json b/packages/pieces/community/call-rounded/package.json new file mode 100644 index 0000000..557562a --- /dev/null +++ b/packages/pieces/community/call-rounded/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-rounded-studio", + "version": "0.1.0" +} diff --git a/packages/pieces/community/call-rounded/project.json b/packages/pieces/community/call-rounded/project.json new file mode 100644 index 0000000..690171f --- /dev/null +++ b/packages/pieces/community/call-rounded/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-call-rounded", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/call-rounded/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/call-rounded", + "tsConfig": "packages/pieces/community/call-rounded/tsconfig.lib.json", + "packageJson": "packages/pieces/community/call-rounded/package.json", + "main": "packages/pieces/community/call-rounded/src/index.ts", + "assets": [ + "packages/pieces/community/call-rounded/*.md", + { + "input": "packages/pieces/community/call-rounded/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/call-rounded/src/index.ts b/packages/pieces/community/call-rounded/src/index.ts new file mode 100644 index 0000000..c559c23 --- /dev/null +++ b/packages/pieces/community/call-rounded/src/index.ts @@ -0,0 +1,29 @@ +import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; + +export const callRoundedAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'Please enter the API Key obtained from Call Rounded.', +}); + +export const callRounded = createPiece({ + displayName: "Call-rounded", + auth: callRoundedAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: "https://cdn.activepieces.com/pieces/call-rounded.png", + authors: ["perrine-pullicino-alan"], + actions: [ + createCustomApiCallAction({ + baseUrl: () => { + return "https://api.callrounded.com/v1"; + }, + auth: callRoundedAuth, + authMapping: async (auth) => ({ + 'x-app': 'activepieces', + 'x-api-key': auth as string, + }), + }) + ], + triggers: [], +}); diff --git a/packages/pieces/community/call-rounded/tsconfig.json b/packages/pieces/community/call-rounded/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/call-rounded/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/call-rounded/tsconfig.lib.json b/packages/pieces/community/call-rounded/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/call-rounded/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/campaign-monitor/.eslintrc.json b/packages/pieces/community/campaign-monitor/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/campaign-monitor/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/campaign-monitor/README.md b/packages/pieces/community/campaign-monitor/README.md new file mode 100644 index 0000000..c906256 --- /dev/null +++ b/packages/pieces/community/campaign-monitor/README.md @@ -0,0 +1,7 @@ +# pieces-campaign-monitor + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-campaign-monitor` to build the library. diff --git a/packages/pieces/community/campaign-monitor/package.json b/packages/pieces/community/campaign-monitor/package.json new file mode 100644 index 0000000..1346cae --- /dev/null +++ b/packages/pieces/community/campaign-monitor/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-campaign-monitor", + "version": "0.0.1" +} diff --git a/packages/pieces/community/campaign-monitor/project.json b/packages/pieces/community/campaign-monitor/project.json new file mode 100644 index 0000000..e7605fe --- /dev/null +++ b/packages/pieces/community/campaign-monitor/project.json @@ -0,0 +1,45 @@ +{ + "name": "pieces-campaign-monitor", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/campaign-monitor/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/campaign-monitor", + "tsConfig": "packages/pieces/community/campaign-monitor/tsconfig.lib.json", + "packageJson": "packages/pieces/community/campaign-monitor/package.json", + "main": "packages/pieces/community/campaign-monitor/src/index.ts", + "assets": [ + "packages/pieces/community/campaign-monitor/*.md" + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/campaign-monitor/src/index.ts b/packages/pieces/community/campaign-monitor/src/index.ts new file mode 100644 index 0000000..524199d --- /dev/null +++ b/packages/pieces/community/campaign-monitor/src/index.ts @@ -0,0 +1,43 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { addSubscriberToListAction } from './lib/actions/add-subscriber-to-list'; +import { updateSubscriberDetailsAction } from './lib/actions/update-subscriber-details'; +import { unsubscribeSubscriberAction } from './lib/actions/unsubscribe-subscriber'; +import { findSubscriberAction } from './lib/actions/find-subscriber'; +import { newSubscriberAddedTrigger } from './lib/triggers/new-subscriber-added'; +import { subscriberUnsubscribedTrigger } from './lib/triggers/subscriber-unsubscribed'; +import { newClientTrigger } from './lib/triggers/new-client'; + +const markdownDescription = ` +To use Campaign Monitor, you need to get an API key: +1. Login to your account at https://www.campaignmonitor.com. +2. Navigate to Account Settings. +3. Click on API Keys. +4. Create a new API key or use an existing one. +`; + +export const campaignMonitorAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: markdownDescription, + required: true, +}); + +export const campaignMonitor = createPiece({ + displayName: 'Campaign Monitor', + description: 'Email marketing platform for delivering exceptional email campaigns.', + logoUrl: 'https://cdn.activepieces.com/pieces/campaign-monitor.png', + authors: ['AnkitSharmaOnGithub','kishanprmr'], + auth: campaignMonitorAuth, + actions: [ + addSubscriberToListAction, + updateSubscriberDetailsAction, + unsubscribeSubscriberAction, + findSubscriberAction + ], + triggers: [ + newSubscriberAddedTrigger, + subscriberUnsubscribedTrigger, + newClientTrigger + ], + categories: [PieceCategory.MARKETING], +}); diff --git a/packages/pieces/community/campaign-monitor/src/lib/actions/add-subscriber-to-list.ts b/packages/pieces/community/campaign-monitor/src/lib/actions/add-subscriber-to-list.ts new file mode 100644 index 0000000..a52a4b4 --- /dev/null +++ b/packages/pieces/community/campaign-monitor/src/lib/actions/add-subscriber-to-list.ts @@ -0,0 +1,106 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common/client'; +import { campaignMonitorAuth } from '../../index'; +import { clientId, customFields, listId } from '../common/props'; +import { HttpStatusCode } from 'axios'; + +export const addSubscriberToListAction = createAction({ + auth: campaignMonitorAuth, + name: 'add_subscriber_to_list', + displayName: 'Add Subscriber', + description: 'Adds a new subscriber to a list.', + props: { + clientId: clientId, + listId: listId, + email: Property.ShortText({ + displayName: 'Email Address', + required: true, + }), + name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + phone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + consentToTrack: Property.StaticDropdown({ + displayName: 'Consent to Track', + description: 'Whether the subscriber has consented to tracking.', + required: true, + defaultValue: 'Unchanged', + options: { + options: [ + { label: 'Yes', value: 'Yes' }, + { label: 'No', value: 'No' }, + { label: 'Unchanged', value: 'Unchanged' }, + ], + }, + }), + consentToSendSms: Property.StaticDropdown({ + displayName: 'Consent to Send SMS', + required: false, + description: 'Whether the subscriber has consented to send SMS.', + defaultValue: 'Unchanged', + options: { + options: [ + { label: 'Yes', value: 'Yes' }, + { label: 'No', value: 'No' }, + { label: 'Unchanged', value: 'Unchanged' }, + ], + }, + }), + resubscribe: Property.Checkbox({ + displayName: 'Resubscribe', + description: + 'If true, the subscriber will be resubscribed if they previously unsubscribed.', + required: false, + defaultValue: false, + }), + fields:customFields, + }, + async run({ propsValue, auth }) { + const { + listId, + email, + name, + phone, + consentToSendSms, + consentToTrack, + resubscribe, + fields + } = propsValue; + + const payload = { + EmailAddress: email, + Name: name || '', + ConsentToTrack: consentToTrack, + ConsentToSendSms: consentToSendSms, + Resubscribe: resubscribe ?? true, + MobileNumber: phone, + CustomFields: Object.entries(fields).flatMap(([key, value]) => + Array.isArray(value) + ? value.map((v) => ({ Key: key, Value: v })) + : [{ Key: key, Value: value }] + ), + }; + + const response = await makeRequest( + { apiKey: auth as string }, + HttpMethod.POST, + `/subscribers/${listId}.json`, + payload + ); + + if (response.status === HttpStatusCode.Created) { + return { + success: true, + }; + } + return { + success: false, + error: response.body, + }; + }, +}); diff --git a/packages/pieces/community/campaign-monitor/src/lib/actions/find-subscriber.ts b/packages/pieces/community/campaign-monitor/src/lib/actions/find-subscriber.ts new file mode 100644 index 0000000..cb2ed8c --- /dev/null +++ b/packages/pieces/community/campaign-monitor/src/lib/actions/find-subscriber.ts @@ -0,0 +1,48 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest, transformCustomFields } from '../common/client'; +import { campaignMonitorAuth } from '../../index'; +import { clientId, listId } from '../common/props'; + +export const findSubscriberAction = createAction({ + auth: campaignMonitorAuth, + name: 'find_subscriber', + displayName: 'Find Subscriber', + description: 'Find a subscriber by email in a specific list.', + props: { + clientId: clientId, + listId: listId, + email: Property.ShortText({ + displayName: 'Email Address', + description: 'The email address of the subscriber to find', + required: true, + }), + }, + async run({ propsValue, auth }) { + const { listId, email } = propsValue; + + try { + const response = await makeRequest( + { apiKey: auth as string }, + HttpMethod.GET, + `/subscribers/${listId}.json?email=${encodeURIComponent( + email + )}&includetrackingpreference=true` + ); + + return { + found: true, + result: { + ...response.body, + CustomFields:transformCustomFields(response.body['CustomFields']) + }, + }; + } catch (error) { + // If subscriber is not found, API returns a 404 + return { + found: false, + result: null, + }; + } + }, +}); diff --git a/packages/pieces/community/campaign-monitor/src/lib/actions/unsubscribe-subscriber.ts b/packages/pieces/community/campaign-monitor/src/lib/actions/unsubscribe-subscriber.ts new file mode 100644 index 0000000..25dd0c6 --- /dev/null +++ b/packages/pieces/community/campaign-monitor/src/lib/actions/unsubscribe-subscriber.ts @@ -0,0 +1,46 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common/client'; +import { campaignMonitorAuth } from '../../index'; +import { clientId, listId } from '../common/props'; +import { HttpStatusCode } from 'axios'; + +export const unsubscribeSubscriberAction = createAction({ + auth: campaignMonitorAuth, + name: 'unsubscribe_subscriber', + displayName: 'Unsubscribe Subscriber', + description: 'Remove a subscriber from a list.', + props: { + clientId: clientId, + listId: listId, + email: Property.ShortText({ + displayName: 'Email Address', + description: 'The email address of the subscriber to unsubscribe.', + required: true, + }), + }, + async run({ propsValue, auth }) { + const { listId, email } = propsValue; + + const payload = { + EmailAddress: email, + }; + + const response = await makeRequest( + { apiKey: auth as string }, + HttpMethod.POST, + `/subscribers/${listId}/unsubscribe.json`, + payload + ); + + if (response.status === HttpStatusCode.Ok) { + return { + success: true, + }; + } + return { + success: false, + error: response.body, + }; + }, +}); diff --git a/packages/pieces/community/campaign-monitor/src/lib/actions/update-subscriber-details.ts b/packages/pieces/community/campaign-monitor/src/lib/actions/update-subscriber-details.ts new file mode 100644 index 0000000..cb99773 --- /dev/null +++ b/packages/pieces/community/campaign-monitor/src/lib/actions/update-subscriber-details.ts @@ -0,0 +1,105 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common/client'; +import { campaignMonitorAuth } from '../../index'; +import { clientId, customFields, listId } from '../common/props'; +import { HttpStatusCode } from 'axios'; + +export const updateSubscriberDetailsAction = createAction({ + auth: campaignMonitorAuth, + name: 'update_subscriber_details', + displayName: 'Update Subscriber', + description: 'Update an existing subscriber in a list.', + props: { + clientId: clientId, + listId: listId, + email: Property.ShortText({ + displayName: 'Email Address', + required: true, + }), + name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + phone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + consentToTrack: Property.StaticDropdown({ + displayName: 'Consent to Track', + description: 'Whether the subscriber has consented to tracking.', + required: false, + defaultValue: 'Yes', + options: { + options: [ + { label: 'Yes', value: 'Yes' }, + { label: 'No', value: 'No' }, + { label: 'Unchanged', value: 'Unchanged' }, + ], + }, + }), + consentToSendSms: Property.StaticDropdown({ + displayName: 'Consent to Send SMS', + required: false, + description: 'Whether the subscriber has consented to send SMS.', + defaultValue: 'Unchanged', + options: { + options: [ + { label: 'Yes', value: 'Yes' }, + { label: 'No', value: 'No' }, + { label: 'Unchanged', value: 'Unchanged' }, + ], + }, + }), + resubscribe: Property.Checkbox({ + displayName: 'Resubscribe', + description: + 'If true, the subscriber will be resubscribed if they previously unsubscribed.', + required: false, + defaultValue: false, + }), + fields:customFields, + }, + async run({ propsValue, auth }) { + const { + listId, + email, + name, + consentToSendSms, + phone, + resubscribe, + consentToTrack, + fields + } = propsValue; + + const payload = { + MobileNumber: phone, + Name: name, + ConsentToTrack: consentToTrack, + ConsentToSendSms: consentToSendSms, + Resubscribe: resubscribe ?? true, + CustomFields: Object.entries(fields).flatMap(([key, value]) => + Array.isArray(value) + ? value.map((v) => ({ Key: key, Value: v })) + : [{ Key: key, Value: value }] + ), + }; + + const response = await makeRequest( + { apiKey: auth as string }, + HttpMethod.PUT, + `/subscribers/${listId}.json?email=${encodeURIComponent(email)}`, + payload + ); + + if (response.status === HttpStatusCode.Ok) { + return { + success: true, + }; + } + return { + success: false, + error: response.body, + }; + }, +}); diff --git a/packages/pieces/community/campaign-monitor/src/lib/common/client.ts b/packages/pieces/community/campaign-monitor/src/lib/common/client.ts new file mode 100644 index 0000000..460e7d7 --- /dev/null +++ b/packages/pieces/community/campaign-monitor/src/lib/common/client.ts @@ -0,0 +1,52 @@ +import { + HttpMethod, + httpClient, + AuthenticationType, +} from '@activepieces/pieces-common'; + +export const BASE_URL = 'https://api.createsend.com/api/v3.3'; + +export interface CampaignMonitorAuth { + apiKey: string; +} + +export async function makeRequest( + auth: CampaignMonitorAuth, + method: HttpMethod, + path: string, + body?: unknown +) { + const response = await httpClient.sendRequest({ + method, + url: `${BASE_URL}${path}`, + headers: { + 'Content-Type': 'application/json', + }, + authentication: { + type: AuthenticationType.BASIC, + username: auth.apiKey, + password: 'x', + }, + body, + }); + return response; +} + +export function transformCustomFields( + inputFields: Array<{ Key: string; Value: string }> +) { + const fields: Record = {}; + + for (const { Key, Value } of inputFields) { + if (fields[Key]) { + if (Array.isArray(fields[Key])) { + fields[Key].push(Value); + } else { + fields[Key] = [fields[Key], Value]; + } + } else { + fields[Key] = Value; + } + } + return fields; +} diff --git a/packages/pieces/community/campaign-monitor/src/lib/common/props.ts b/packages/pieces/community/campaign-monitor/src/lib/common/props.ts new file mode 100644 index 0000000..c000e96 --- /dev/null +++ b/packages/pieces/community/campaign-monitor/src/lib/common/props.ts @@ -0,0 +1,143 @@ +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { makeRequest } from './client'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const clientId = Property.Dropdown({ + displayName: 'Client Account', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account.', + options: [], + }; + } + + const response = await makeRequest( + { apiKey: auth as string }, + HttpMethod.GET, + '/clients.json' + ); + const clients = response.body as { ClientID: string; Name: string }[]; + + return { + disabled: false, + options: clients.map((client) => { + return { + label: client.Name, + value: client.ClientID, + }; + }), + }; + }, +}); + +export const listId = Property.Dropdown({ + displayName: 'List ID', + refreshers: ['clientId'], + required: true, + options: async ({ auth, clientId }) => { + if (!auth || !clientId) { + return { + disabled: true, + placeholder: 'Please connect your account.', + options: [], + }; + } + + const response = await makeRequest( + { apiKey: auth as string }, + HttpMethod.GET, + `/clients/${clientId}/lists.json` + ); + const lists = response.body as { ListID: string; Name: string }[]; + + return { + disabled: false, + options: lists.map((list) => { + return { + label: list.Name, + value: list.ListID, + }; + }), + }; + }, +}); + +export const customFields = Property.DynamicProperties({ + displayName: 'Custom Fields', + refreshers: ['listId'], + required: true, + props: async ({ auth, listId }) => { + if (!auth || !listId) return {}; + + const fields: DynamicPropsValue = {}; + + const response = await makeRequest( + { + apiKey: auth as unknown as string, + }, + HttpMethod.GET, + `/lists/${listId}/customfields.json` + ); + + const listFields = response.body as Array<{ + FieldName: string; + Key: string; + DataType: string; + FieldOptions: string[]; + }>; + + for (const field of listFields) { + switch (field.DataType) { + case 'Text': + fields[field.Key] = Property.ShortText({ + displayName: field.FieldName, + required: false, + }); + break; + case 'Number': + fields[field.Key] = Property.Number({ + displayName: field.FieldName, + required: false, + }); + break; + case 'Date': + fields[field.Key] = Property.DateTime({ + displayName: field.FieldName, + required: false, + }); + break; + case 'MultiSelectOne': + fields[field.Key] = Property.StaticDropdown({ + displayName: field.FieldName, + required: false, + options: { + disabled: false, + options: field.FieldOptions.map((option) => ({ + label: option, + value: option, + })), + }, + }); + break; + case 'MultiSelectMany': + fields[field.Key] = Property.StaticMultiSelectDropdown({ + displayName: field.FieldName, + required: false, + options: { + disabled: false, + options: field.FieldOptions.map((option) => ({ + label: option, + value: option, + })), + }, + }); + break; + } + } + return fields; + }, +}); diff --git a/packages/pieces/community/campaign-monitor/src/lib/triggers/new-client.ts b/packages/pieces/community/campaign-monitor/src/lib/triggers/new-client.ts new file mode 100644 index 0000000..10c275c --- /dev/null +++ b/packages/pieces/community/campaign-monitor/src/lib/triggers/new-client.ts @@ -0,0 +1,79 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common/client'; +import { campaignMonitorAuth } from '../../index'; + +export const newClientTrigger = createTrigger({ + auth: campaignMonitorAuth, + name: 'new_client', + displayName: 'New Client', + description: 'Triggered when a new client is added to Campaign Monitor.', + props: {}, + type: TriggerStrategy.POLLING, + sampleData: { + ClientID: 'xyz', + Name: 'New Client', + }, + async onEnable(context) { + // Store the list of existing clients + const response = await makeRequest( + { apiKey: context.auth as string }, + HttpMethod.GET, + '/clients.json' + ); + + const clients = response.body as Array<{ ClientID: string; Name: string }>; + + await context.store.put('existing_clients', { + clients: clients.map((c) => c.ClientID), + }); + }, + async onDisable(context) { + // No cleanup needed for polling trigger + await context.store.delete('existing_clients'); + }, + async test(context) { + const response = await makeRequest( + { apiKey: context.auth as string }, + HttpMethod.GET, + '/clients.json' + ); + + return response.body; + }, + async run(context) { + // Get stored list of clients + const storedClients = await context.store.get<{ clients: string[] }>( + 'existing_clients' + ); + const existingClientIds = storedClients?.clients || []; + + // Get all clients + const response = await makeRequest( + { apiKey: context.auth as string }, + HttpMethod.GET, + '/clients.json' + ); + + const allClients = response.body as Array<{ + ClientID: string; + Name: string; + }>; + + // Find new clients + const newClients = allClients.filter( + (client) => !existingClientIds.includes(client.ClientID) + ); + + // Update stored client list + const updatedClientIds = Array.from( + new Set([...existingClientIds, ...allClients.map((c) => c.ClientID)]) + ); + await context.store.put('existing_clients', { + clients: updatedClientIds, + }); + + // Return new clients + return newClients; + }, +}); diff --git a/packages/pieces/community/campaign-monitor/src/lib/triggers/new-subscriber-added.ts b/packages/pieces/community/campaign-monitor/src/lib/triggers/new-subscriber-added.ts new file mode 100644 index 0000000..8a5b886 --- /dev/null +++ b/packages/pieces/community/campaign-monitor/src/lib/triggers/new-subscriber-added.ts @@ -0,0 +1,93 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest, transformCustomFields } from '../common/client'; +import { isNil } from '@activepieces/shared'; +import { campaignMonitorAuth } from '../../index'; +import { clientId, listId } from '../common/props'; + +export const newSubscriberAddedTrigger = createTrigger({ + auth: campaignMonitorAuth, + name: 'new_subscriber_added', + displayName: 'New Subscriber Added', + description: 'Triggered when a new subscriber is added to a list.', + props: { + clientId: clientId, + listId: listId, + }, + type: TriggerStrategy.WEBHOOK, + sampleData: { + EmailAddress: 'subscriber@example.com', + Name: 'New Subscriber', + Date: '2023-07-15T15:30:00Z', + ListID: 'xyz', + CustomFields: { + website: 'https://example.com', + }, + State: 'Active', + ConsentToTrack: 'Yes', + }, + async onEnable(context) { + const { listId } = context.propsValue; + + const response = await makeRequest( + { apiKey: context.auth as string }, + HttpMethod.POST, + `/lists/${listId}/webhooks.json`, + { + Events: ['Subscribe'], + Url: context.webhookUrl, + PayloadFormat: 'json', + } + ); + + const webhook = response.body as string; + + await context.store.put('new_subscriber_added_webhook', webhook); + }, + async onDisable(context) { + const { listId } = context.propsValue; + const storedData = await context.store.get( + 'new_subscriber_added_webhook' + ); + + + if (!isNil(storedData)) { + await makeRequest( + { apiKey: context.auth as string }, + HttpMethod.DELETE, + `/lists/${listId}/webhooks/${storedData}.json` + ); + } + }, + async run(context) { + const payload = context.payload.body as { + ListID: string; + Events: { Type: string; EmailAddress: string }[]; + }; + + if (payload.ListID !== context.propsValue.listId) { + return []; + } + + const result = []; + + for (const event of payload.Events) { + if (event.Type === 'Subscribe') { + const response = await makeRequest( + { apiKey: context.auth as string }, + HttpMethod.GET, + `/subscribers/${context.propsValue.listId}.json?email=${encodeURIComponent( + event.EmailAddress + )}`, + payload + ); + result.push({ + ...response.body, + CustomFields: transformCustomFields(response.body['CustomFields']), + }); + } + } + + return result; + }, +}); diff --git a/packages/pieces/community/campaign-monitor/src/lib/triggers/subscriber-unsubscribed.ts b/packages/pieces/community/campaign-monitor/src/lib/triggers/subscriber-unsubscribed.ts new file mode 100644 index 0000000..f28ffd2 --- /dev/null +++ b/packages/pieces/community/campaign-monitor/src/lib/triggers/subscriber-unsubscribed.ts @@ -0,0 +1,91 @@ +import { + createTrigger, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest, transformCustomFields } from '../common/client'; +import { isNil } from '@activepieces/shared'; +import { campaignMonitorAuth } from '../../index'; +import { clientId, listId } from '../common/props'; + +export const subscriberUnsubscribedTrigger = createTrigger({ + auth: campaignMonitorAuth, + name: 'subscriber_unsubscribed', + displayName: 'Subscriber Unsubscribed', + description: 'Triggered when a subscriber unsubscribes from a list', + props: { + clientId: clientId, + listId: listId, + }, + type: TriggerStrategy.WEBHOOK, + sampleData: { + EmailAddress: 'subscriber@example.com', + Name: 'Former Subscriber', + Date: '2023-07-15T15:30:00Z', + ListID: 'xyz', + State: 'Unsubscribed', + }, + async onEnable(context) { + const { listId } = context.propsValue; + + const response = await makeRequest( + { apiKey: context.auth as string }, + HttpMethod.POST, + `/lists/${listId}/webhooks.json`, + { + Events: ['Deactivate'], + Url: context.webhookUrl, + ListID: listId, + PayloadFormat: 'json', + } + ); + const webhook = response.body as string; + + await context.store.put('subscriber_unsubscribed_webhook', webhook); + }, + async onDisable(context) { + const storedData = await context.store.get( + 'subscriber_unsubscribed_webhook' + ); + + if (!isNil(storedData)) { + await makeRequest( + { apiKey: context.auth as string }, + HttpMethod.DELETE, + `/lists/${listId}/webhooks/${storedData}.json` + ); + } + }, + async run(context) { + const payload = context.payload.body as { + ListID: string; + Events: { Type: string; EmailAddress: string,State:string }[]; + }; + + if (payload.ListID !== context.propsValue.listId) { + return []; + } + + const result = []; + + for (const event of payload.Events) { + if (event.Type === 'Deactivate' && event.State==='Unsubscribed') { + const response = await makeRequest( + { apiKey: context.auth as string }, + HttpMethod.GET, + `/subscribers/${context.propsValue.listId}.json?email=${encodeURIComponent( + event.EmailAddress + )}`, + payload + ); + result.push({ + ...response.body, + CustomFields: transformCustomFields(response.body['CustomFields']), + }); + } + } + + return result; + }, +}); diff --git a/packages/pieces/community/campaign-monitor/tsconfig.json b/packages/pieces/community/campaign-monitor/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/campaign-monitor/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/campaign-monitor/tsconfig.lib.json b/packages/pieces/community/campaign-monitor/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/campaign-monitor/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/captain-data/.eslintrc.json b/packages/pieces/community/captain-data/.eslintrc.json new file mode 100644 index 0000000..d7449bb --- /dev/null +++ b/packages/pieces/community/captain-data/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {}, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/captain-data/README.md b/packages/pieces/community/captain-data/README.md new file mode 100644 index 0000000..6eb4928 --- /dev/null +++ b/packages/pieces/community/captain-data/README.md @@ -0,0 +1,7 @@ +# pieces-captain-data + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-captain-data` to build the library. diff --git a/packages/pieces/community/captain-data/package.json b/packages/pieces/community/captain-data/package.json new file mode 100644 index 0000000..686fba0 --- /dev/null +++ b/packages/pieces/community/captain-data/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-captain-data", + "version": "0.0.1" +} diff --git a/packages/pieces/community/captain-data/project.json b/packages/pieces/community/captain-data/project.json new file mode 100644 index 0000000..2ff85a6 --- /dev/null +++ b/packages/pieces/community/captain-data/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-captain-data", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/captain-data/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/captain-data", + "tsConfig": "packages/pieces/community/captain-data/tsconfig.lib.json", + "packageJson": "packages/pieces/community/captain-data/package.json", + "main": "packages/pieces/community/captain-data/src/index.ts", + "assets": [ + "packages/pieces/community/captain-data/*.md", + { + "input": "packages/pieces/community/captain-data/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/captain-data/src/index.ts b/packages/pieces/community/captain-data/src/index.ts new file mode 100644 index 0000000..95ac645 --- /dev/null +++ b/packages/pieces/community/captain-data/src/index.ts @@ -0,0 +1,50 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { launchWorkflow } from './lib/actions/launch-workflow'; +import { getJobResults } from './lib/actions/get-job-results'; + +export const CAPTAIN_DATA_BASE_URL = 'https://api.captaindata.co/v3'; + +export const captainDataAuth = PieceAuth.CustomAuth({ + required: true, + props: { + apiKey: PieceAuth.SecretText({ + displayName: 'API key', + required: true, + }), + projectId: Property.ShortText({ + displayName: 'Project ID', + required: true, + }), + }, +}); + +export type CaptainDataAuthType = { + apiKey: string; + projectId: string; +}; + +export const captainData = createPiece({ + displayName: 'Captain-data', + auth: captainDataAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/captain-data.png', + authors: ['AdamSelene'], + actions: [ + launchWorkflow, + getJobResults, + createCustomApiCallAction({ + auth: captainDataAuth, + baseUrl: () => CAPTAIN_DATA_BASE_URL, + authMapping: async (auth) => ({ + Authorization: `x-api-key ${(auth as CaptainDataAuthType).apiKey}`, + 'x-project-id': (auth as CaptainDataAuthType).projectId, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/captain-data/src/lib/actions/get-job-results.ts b/packages/pieces/community/captain-data/src/lib/actions/get-job-results.ts new file mode 100644 index 0000000..e3d7a29 --- /dev/null +++ b/packages/pieces/community/captain-data/src/lib/actions/get-job-results.ts @@ -0,0 +1,70 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +import { + CAPTAIN_DATA_BASE_URL, + captainDataAuth, + CaptainDataAuthType, +} from '../..'; +import { workflowProp } from '../common'; + +export const getJobResults = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'getJobResults', + displayName: 'Get job results', + description: 'Get all results for a specific job', + auth: captainDataAuth, + props: { + workflow: workflowProp, + job: Property.Dropdown({ + displayName: 'Job', + required: true, + refreshers: ['workflow'], + options: async ({ auth, workflow }) => { + if (!auth || !workflow) { + return { + disabled: true, + options: [], + }; + } + const response = await httpClient.sendRequest({ + url: `${CAPTAIN_DATA_BASE_URL}/workflows/${workflow}/jobs`, + method: HttpMethod.GET, + headers: { + Authorization: `x-api-key ${(auth as CaptainDataAuthType).apiKey}`, + 'x-project-id': (auth as CaptainDataAuthType).projectId, + }, + }); + return { + disabled: false, + options: response.body.map( + (job: { + uid: string; + name: string; + workflow_name: string; + status: string; + start_time: string; + }) => { + return { + value: job.uid, + label: job.start_time, + }; + } + ), + }; + }, + }), + }, + async run({ auth, propsValue }) { + // We don't do pagination because Captain Data's API doc does provide details nor even examples :shrug: + const response = await httpClient.sendRequest({ + url: `${CAPTAIN_DATA_BASE_URL}/jobs/${propsValue.job}/results`, + method: HttpMethod.GET, + headers: { + Authorization: `x-api-key ${(auth as CaptainDataAuthType).apiKey}`, + 'x-project-id': (auth as CaptainDataAuthType).projectId, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/captain-data/src/lib/actions/launch-workflow.ts b/packages/pieces/community/captain-data/src/lib/actions/launch-workflow.ts new file mode 100644 index 0000000..a52b30c --- /dev/null +++ b/packages/pieces/community/captain-data/src/lib/actions/launch-workflow.ts @@ -0,0 +1,68 @@ +import { + createAction, + DynamicPropsValue, + Property, +} from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { + CAPTAIN_DATA_BASE_URL, + captainDataAuth, + CaptainDataAuthType, +} from '../..'; +import { workflowProp } from '../common'; + +export const launchWorkflow = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'launchWorkflow', + displayName: 'Launch a workflow', + description: '', + auth: captainDataAuth, + props: { + workflow: workflowProp, + jobName: Property.ShortText({ + displayName: 'Job name', + required: false, + }), + inputs: Property.Json({ + displayName: 'Inputs', + description: 'Inputs are the starting point of your workflow.', + defaultValue: [], + required: false, + }), + steps: Property.Json({ + displayName: 'Steps', + description: + 'You need to configure each steps with:\n' + + '- accounts : The identifiers of the accounts you want to use for this step. The accounts indicated must match the integration used in the step (e.g A Linkedin account for a Search Linkedin People Profile step). You can find the UIDs in the "Integrations\' tab on the platform by clicking on a specific account "Action" button (copy account UID).\n' + + '- accounts_rotation_enabled: (Optional) Whether or not you want to enable the Accounts Rotation feature for this step (only certain Linkedin & Outlook automations are applicable for this feature).\n' + + '- parameters: The specific parameters for this given step - (Can be empty but is required)\n' + + '- step_uid: The UID of the step to configure. You can find it in the API Playground, on Captain Data\'s platform by clicking on "view body to schedule".\n.' + + 'See https://docs.captaindata.co/#36e905b6-3a31-4bcd-8c6f-0eb6093b5a8a for details', + defaultValue: [], + required: false, + }), + delay: Property.Number({ + displayName: 'Delay (seconds)', + required: false, + }), + }, + async run({ auth, propsValue }) { + const payload = { + job_name: propsValue.jobName, + inputs: propsValue.inputs, + steps: propsValue.steps, + delay: propsValue.delay, + }; + const response = await httpClient.sendRequest({ + url: `${CAPTAIN_DATA_BASE_URL}/workflows/${propsValue.workflow}/schedule`, + method: HttpMethod.POST, + headers: { + 'Content-Type': 'application/json', + Authorization: `x-api-key ${(auth as CaptainDataAuthType).apiKey}`, + 'x-project-id': (auth as CaptainDataAuthType).projectId, + }, + body: JSON.stringify(payload), + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/captain-data/src/lib/common.ts b/packages/pieces/community/captain-data/src/lib/common.ts new file mode 100644 index 0000000..488b07b --- /dev/null +++ b/packages/pieces/community/captain-data/src/lib/common.ts @@ -0,0 +1,31 @@ +import { Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { CAPTAIN_DATA_BASE_URL, CaptainDataAuthType } from '..'; + +export const workflowProp = Property.Dropdown({ + displayName: 'Workflow', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + }; + } + const response = await httpClient.sendRequest({ + url: `${CAPTAIN_DATA_BASE_URL}/workflows`, + method: HttpMethod.GET, + headers: { + Authorization: `x-api-key ${(auth as CaptainDataAuthType).apiKey}`, + 'x-project-id': (auth as CaptainDataAuthType).projectId, + }, + }); + return { + disabled: false, + options: response.body.map((workflow: { uid: string; name: string }) => { + return { label: workflow.name, value: workflow.uid }; + }), + }; + }, +}); diff --git a/packages/pieces/community/captain-data/tsconfig.json b/packages/pieces/community/captain-data/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/captain-data/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/captain-data/tsconfig.lib.json b/packages/pieces/community/captain-data/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/captain-data/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/cartloom/.eslintrc.json b/packages/pieces/community/cartloom/.eslintrc.json new file mode 100644 index 0000000..15ede17 --- /dev/null +++ b/packages/pieces/community/cartloom/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*", "package.json"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/cartloom/README.md b/packages/pieces/community/cartloom/README.md new file mode 100644 index 0000000..92463ed --- /dev/null +++ b/packages/pieces/community/cartloom/README.md @@ -0,0 +1,7 @@ +# pieces-cartloom + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-cartloom` to build the library. diff --git a/packages/pieces/community/cartloom/package.json b/packages/pieces/community/cartloom/package.json new file mode 100644 index 0000000..c320e0c --- /dev/null +++ b/packages/pieces/community/cartloom/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-cartloom", + "version": "0.0.5" +} \ No newline at end of file diff --git a/packages/pieces/community/cartloom/project.json b/packages/pieces/community/cartloom/project.json new file mode 100644 index 0000000..b4461cf --- /dev/null +++ b/packages/pieces/community/cartloom/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-cartloom", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/cartloom/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/cartloom", + "tsConfig": "packages/pieces/community/cartloom/tsconfig.lib.json", + "packageJson": "packages/pieces/community/cartloom/package.json", + "main": "packages/pieces/community/cartloom/src/index.ts", + "assets": [ + "packages/pieces/community/cartloom/*.md", + { + "input": "packages/pieces/community/cartloom/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-cartloom {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/cartloom/src/index.ts b/packages/pieces/community/cartloom/src/index.ts new file mode 100644 index 0000000..6d9fbf5 --- /dev/null +++ b/packages/pieces/community/cartloom/src/index.ts @@ -0,0 +1,39 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createDiscountAction } from './lib/actions/create-discount'; +import { getDiscountAction } from './lib/actions/get-discount'; +import { getAllDiscountsAction } from './lib/actions/get-discounts'; +import { getOrderAction } from './lib/actions/get-order'; +import { getOrderDateAction } from './lib/actions/get-orders-date'; +import { getOrderEmailAction } from './lib/actions/get-orders-date-email'; +import { getProductsAction } from './lib/actions/get-products'; +import { cartloomAuth } from './lib/auth'; + +export const cartloom = createPiece({ + displayName: 'Cartloom', + description: 'Sell products beautifully', + auth: cartloomAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/cartloom.png', + categories: [PieceCategory.COMMERCE], + authors: ["joeworkman","kishanprmr","MoShizzle","abuaboud"], + actions: [ + getProductsAction, + getOrderAction, + createDiscountAction, + getDiscountAction, + getAllDiscountsAction, + getOrderDateAction, + getOrderEmailAction, + createCustomApiCallAction({ + baseUrl: (auth) => + `https://${(auth as { domain: string }).domain}.cartloom.com/api`, // Replace with the actual base URL + auth: cartloomAuth, + authMapping: async (auth) => ({ + 'X-API-KEY': (auth as { apiKey: string }).apiKey, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/cartloom/src/lib/actions/create-discount.ts b/packages/pieces/community/cartloom/src/lib/actions/create-discount.ts new file mode 100644 index 0000000..b358da0 --- /dev/null +++ b/packages/pieces/community/cartloom/src/lib/actions/create-discount.ts @@ -0,0 +1,143 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { createDiscount } from '../api'; +import { CartloomAuthType, cartloomAuth } from '../auth'; +import { buildProductsDropdown } from '../props'; + +export const createDiscountAction = createAction({ + name: 'create_discount', + auth: cartloomAuth, + displayName: 'Create Discount', + description: 'Create a discount in Cartloom', + props: { + title: Property.ShortText({ + displayName: 'Title', + description: 'Enter the title of the discount', + required: true, + }), + enabled: Property.Checkbox({ + displayName: 'Enabled', + description: 'Is this discount enabled?', + required: true, + defaultValue: false, + }), + auto: Property.Checkbox({ + displayName: 'Auto', + description: 'Is this discount automatically applied?', + required: true, + defaultValue: false, + }), + unlimited: Property.Checkbox({ + displayName: 'Unlimited', + description: 'Is this discount unlimited?', + required: true, + defaultValue: false, + }), + selfDestruct: Property.Checkbox({ + displayName: 'Self Destruct', + description: 'Remove the discount after use', + required: true, + defaultValue: false, + }), + applyOnce: Property.Checkbox({ + displayName: 'Apply Once', + description: 'Apply the discount once per order', + required: true, + defaultValue: false, + }), + type: Property.StaticDropdown({ + displayName: 'Type of Discount', + description: 'Select the type of discount', + defaultValue: 'fixed', + required: true, + options: { + options: [ + { label: 'Fixed Amount', value: 'fixed' }, + { label: 'Percentage', value: 'percent' }, + ], + }, + }), + amount: Property.Number({ + displayName: 'Amount', + description: 'Enter the amount of the discount', + required: true, + defaultValue: 0, + }), + target: Property.StaticDropdown({ + displayName: 'Discount Target', + description: 'Select the target of the discount', + defaultValue: 'all', + required: true, + options: { + options: [ + { label: 'Selected Products', value: 'product' }, + { label: 'Total', value: 'total' }, + { label: 'All Products', value: 'all' }, + ], + }, + }), + startDate: Property.DateTime({ + displayName: 'Start Date', + description: 'The start date of the discount. YYYY-MM-DD HH:MM:SS', + required: true, + }), + stopDate: Property.DateTime({ + displayName: 'Stop Date', + description: 'The stop date of the discount. YYYY-MM-DD HH:MM:SS', + required: true, + }), + optional: Property.MarkDown({ + value: '## Optional Settings based on above settings', + }), + code: Property.ShortText({ + displayName: 'Discount Code', + description: 'Enter the discount code. Leave blank for no code.', + required: false, + }), + targetPids: Property.MultiSelectDropdown({ + displayName: 'Target Products', + description: 'Select the products to apply the discount to', + required: false, + refreshers: [], + options: async ({ auth }) => + await buildProductsDropdown(auth as CartloomAuthType), + }), + targetAmount: Property.Number({ + displayName: 'Target Amount', + description: + 'The target amount for the discount when the target is set to Total', + required: false, + }), + targetQuantity: Property.Number({ + displayName: 'Target Quantity', + description: + 'The target quantity for the discount when the target is set to All or Products', + required: false, + defaultValue: 0, + }), + allowance: Property.Number({ + displayName: 'Allowance', + description: 'The number of times the discount can be used', + required: false, + }), + }, + async run(context) { + return await createDiscount(context.auth, { + enabled: context.propsValue.enabled ? 1 : 0, + auto: context.propsValue.auto ? 1 : 0, + unlimited: context.propsValue.unlimited ? 1 : 0, + self_destruct: context.propsValue.selfDestruct ? 1 : 0, + apply_once: context.propsValue.applyOnce ? 1 : 0, + title: context.propsValue.title, + type: context.propsValue.type, + amount: context.propsValue.amount, + target: context.propsValue.target, + start_date: context.propsValue.startDate.split('T')[0], + stop_date: context.propsValue.stopDate.split('T')[0], + code: context.propsValue.code, + target_pid: context.propsValue.targetPids?.join(','), + target_amount: context.propsValue.targetAmount, + target_quantity: context.propsValue.targetQuantity, + allowance: context.propsValue.allowance, + }); + }, +}); diff --git a/packages/pieces/community/cartloom/src/lib/actions/get-discount.ts b/packages/pieces/community/cartloom/src/lib/actions/get-discount.ts new file mode 100644 index 0000000..c31e90f --- /dev/null +++ b/packages/pieces/community/cartloom/src/lib/actions/get-discount.ts @@ -0,0 +1,20 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { getDiscount } from '../api'; +import { cartloomAuth } from '../auth'; + +export const getDiscountAction = createAction({ + name: 'get_discount', + auth: cartloomAuth, + displayName: 'Get Discount', + description: 'Get discount info from Cartloom', + props: { + discountId: Property.ShortText({ + displayName: 'Discount ID', + description: 'Enter the ID of the discount you want to retrieve', + required: true, + }), + }, + async run(context) { + return await getDiscount(context.auth, context.propsValue.discountId); + }, +}); diff --git a/packages/pieces/community/cartloom/src/lib/actions/get-discounts.ts b/packages/pieces/community/cartloom/src/lib/actions/get-discounts.ts new file mode 100644 index 0000000..a574985 --- /dev/null +++ b/packages/pieces/community/cartloom/src/lib/actions/get-discounts.ts @@ -0,0 +1,14 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { getAllDiscounts } from '../api'; +import { cartloomAuth } from '../auth'; + +export const getAllDiscountsAction = createAction({ + name: 'get_all_discounts', + auth: cartloomAuth, + displayName: 'Get All Discounts', + description: 'Get a list of discounts from Cartloom', + props: {}, + async run(context) { + return await getAllDiscounts(context.auth); + }, +}); diff --git a/packages/pieces/community/cartloom/src/lib/actions/get-order.ts b/packages/pieces/community/cartloom/src/lib/actions/get-order.ts new file mode 100644 index 0000000..356ae4e --- /dev/null +++ b/packages/pieces/community/cartloom/src/lib/actions/get-order.ts @@ -0,0 +1,20 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { getOrder } from '../api'; +import { cartloomAuth } from '../auth'; + +export const getOrderAction = createAction({ + name: 'get_order', + auth: cartloomAuth, + displayName: 'Get Order', + description: 'Get an order from Cartloom', + props: { + invoice: Property.ShortText({ + displayName: 'Invoice ID', + description: 'The invoice ID for the order you want to retrieve', + required: true, + }), + }, + async run(context) { + return await getOrder(context.auth, context.propsValue.invoice); + }, +}); diff --git a/packages/pieces/community/cartloom/src/lib/actions/get-orders-date-email.ts b/packages/pieces/community/cartloom/src/lib/actions/get-orders-date-email.ts new file mode 100644 index 0000000..ef7ce94 --- /dev/null +++ b/packages/pieces/community/cartloom/src/lib/actions/get-orders-date-email.ts @@ -0,0 +1,36 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { getOrdersByDate } from '../api'; +import { cartloomAuth } from '../auth'; + +export const getOrderEmailAction = createAction({ + name: 'get_orders_by_email', + auth: cartloomAuth, + displayName: 'Get Order by Email', + description: 'Get a list of orders for an email within a date range', + props: { + start: Property.DateTime({ + displayName: 'Start Date', + description: 'Select a date to start the search', + required: true, + }), + end: Property.DateTime({ + displayName: 'End Date', + description: 'Select a date to end the search. Defaults to today.', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + description: 'Email address to search for.', + required: true, + }), + }, + async run(context) { + return await getOrdersByDate(context.auth, { + search_type: 'email', + keyword: context.propsValue.email, + start_date: context.propsValue.start.split('T')[0], + end_date: + context.propsValue.end || new Date().toISOString().split('T')[0], + }); + }, +}); diff --git a/packages/pieces/community/cartloom/src/lib/actions/get-orders-date.ts b/packages/pieces/community/cartloom/src/lib/actions/get-orders-date.ts new file mode 100644 index 0000000..8817829 --- /dev/null +++ b/packages/pieces/community/cartloom/src/lib/actions/get-orders-date.ts @@ -0,0 +1,29 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { getOrdersByDate } from '../api'; +import { cartloomAuth } from '../auth'; + +export const getOrderDateAction = createAction({ + name: 'get_orders_by_date', + auth: cartloomAuth, + displayName: 'Get Order by Date', + description: 'Get a list of orders from Cartloom within a date range', + props: { + start: Property.DateTime({ + displayName: 'Start Date', + description: 'Select a date to start the search', + required: true, + }), + end: Property.DateTime({ + displayName: 'End Date', + description: 'Select a date to end the search. Defaults to today.', + required: false, + }), + }, + async run(context) { + return await getOrdersByDate(context.auth, { + start_date: context.propsValue.start.split('T')[0], + end_date: + context.propsValue.end || new Date().toISOString().split('T')[0], + }); + }, +}); diff --git a/packages/pieces/community/cartloom/src/lib/actions/get-products.ts b/packages/pieces/community/cartloom/src/lib/actions/get-products.ts new file mode 100644 index 0000000..7cf073b --- /dev/null +++ b/packages/pieces/community/cartloom/src/lib/actions/get-products.ts @@ -0,0 +1,14 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { getProducts } from '../api'; +import { cartloomAuth } from '../auth'; + +export const getProductsAction = createAction({ + name: 'get_products', + auth: cartloomAuth, + displayName: 'Get Products', + description: 'Get a list of products from Cartloom', + props: {}, + async run(context) { + return await getProducts(context.auth); + }, +}); diff --git a/packages/pieces/community/cartloom/src/lib/api.ts b/packages/pieces/community/cartloom/src/lib/api.ts new file mode 100644 index 0000000..455dc20 --- /dev/null +++ b/packages/pieces/community/cartloom/src/lib/api.ts @@ -0,0 +1,72 @@ +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { CartloomAuthType } from './auth'; + +type KeyValuePair = { [key: string]: string | boolean | number | undefined }; + +const cartloomAPI = async ( + api: string, + auth: CartloomAuthType, + body: KeyValuePair = {} +) => { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://${auth.domain}.cartloom.com/api${api}`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'X-API-KEY': auth.apiKey, + }, + body: body, + }; + const response = await httpClient.sendRequest(request); + + if (response.status !== 200) { + throw new Error(`Cartloom API Error: ${response.status} ${response.body}`); + } + + let data = Object.keys(response.body).map((key) => response.body[key]); + + const arrayTest = response.body['0']; + if (typeof arrayTest === 'undefined') { + // when response is an object, it is wrapped in an array + data = [response.body]; + } + + return { + success: true, + data: data, + }; +}; + +export async function getProducts(auth: CartloomAuthType) { + return cartloomAPI('/products/list', auth); +} + +export async function getAllDiscounts(auth: CartloomAuthType) { + return cartloomAPI('/discounts/list', auth); +} + +export async function getDiscount(auth: CartloomAuthType, discountId: string) { + return cartloomAPI('/discounts/get', auth, { id: discountId }); +} + +export async function getOrder(auth: CartloomAuthType, invoice: string) { + return cartloomAPI('/orders/get', auth, { invoice: invoice }); +} + +export async function getOrdersByDate( + auth: CartloomAuthType, + data: KeyValuePair +) { + return cartloomAPI('/orders/list', auth, data); +} + +export async function createDiscount( + auth: CartloomAuthType, + data: KeyValuePair +) { + return cartloomAPI('/discounts/add', auth, data); +} diff --git a/packages/pieces/community/cartloom/src/lib/auth.ts b/packages/pieces/community/cartloom/src/lib/auth.ts new file mode 100644 index 0000000..74f3409 --- /dev/null +++ b/packages/pieces/community/cartloom/src/lib/auth.ts @@ -0,0 +1,47 @@ +import { + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { getProducts } from './api'; + +export type CartloomAuthType = { apiKey: string; domain: string }; + +export const cartloomAuth = PieceAuth.CustomAuth({ + description: 'Cartloom Authentication', + props: { + domain: Property.ShortText({ + displayName: 'Cartloom Domain', + description: + 'Your cartloom domain will be the subdomain part of your Cartloom URL. Example: https://mycompany.cartloom.com', + required: true, + }), + apiKey: PieceAuth.SecretText({ + displayName: 'API Key', + description: 'The API key for your Cartloom account', + required: true + }), + }, + validate: async ({ auth }) => { + try { + await validateAuth(auth); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: (e as Error)?.message, + }; + } + }, + required: true, +}); + +const validateAuth = async (auth: CartloomAuthType) => { + const response = await getProducts(auth); + if (response.success !== true) { + throw new Error( + 'Authentication failed. Please check your domain and API key and try again.' + ); + } +}; diff --git a/packages/pieces/community/cartloom/src/lib/props.ts b/packages/pieces/community/cartloom/src/lib/props.ts new file mode 100644 index 0000000..468cce3 --- /dev/null +++ b/packages/pieces/community/cartloom/src/lib/props.ts @@ -0,0 +1,19 @@ +import { CartloomAuthType } from './auth'; +import { getProducts } from './api'; + +export async function buildProductsDropdown(auth: CartloomAuthType) { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please authenticate first', + }; + } + const response = await getProducts(auth as CartloomAuthType); + const options = response.data.map((product) => { + return { label: product.name, value: product.pid }; + }); + return { + options: options, + }; +} diff --git a/packages/pieces/community/cartloom/tsconfig.json b/packages/pieces/community/cartloom/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/cartloom/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/cartloom/tsconfig.lib.json b/packages/pieces/community/cartloom/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/cartloom/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/certopus/.eslintrc.json b/packages/pieces/community/certopus/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/certopus/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/certopus/README.md b/packages/pieces/community/certopus/README.md new file mode 100644 index 0000000..61ca979 --- /dev/null +++ b/packages/pieces/community/certopus/README.md @@ -0,0 +1,7 @@ +# pieces-certopus + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-certopus` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/certopus/package.json b/packages/pieces/community/certopus/package.json new file mode 100644 index 0000000..64416db --- /dev/null +++ b/packages/pieces/community/certopus/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-certopus", + "version": "0.1.5" +} \ No newline at end of file diff --git a/packages/pieces/community/certopus/project.json b/packages/pieces/community/certopus/project.json new file mode 100644 index 0000000..6c525c7 --- /dev/null +++ b/packages/pieces/community/certopus/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-certopus", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/certopus/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/certopus", + "tsConfig": "packages/pieces/community/certopus/tsconfig.lib.json", + "packageJson": "packages/pieces/community/certopus/package.json", + "main": "packages/pieces/community/certopus/src/index.ts", + "assets": [ + "packages/pieces/community/certopus/*.md", + { + "input": "packages/pieces/community/certopus/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/certopus/src/index.ts b/packages/pieces/community/certopus/src/index.ts new file mode 100644 index 0000000..632ceb4 --- /dev/null +++ b/packages/pieces/community/certopus/src/index.ts @@ -0,0 +1,31 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { createCredential } from './lib/actions/create-credential'; +import { certopusCommon } from './lib/common'; + +export const certopusAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'API key acquired from your Certopus profile', +}); + +export const certopus = createPiece({ + displayName: 'Certopus', + description: 'Your certificates, made simple', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/certopus.png', + categories: [], + authors: ["VrajGohil","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + auth: certopusAuth, + actions: [ + createCredential, + createCustomApiCallAction({ + baseUrl: () => certopusCommon.baseUrl, // Replace with the actual base URL + auth: certopusAuth, + authMapping: async (auth) => ({ + 'x-api-key': `${auth}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/certopus/src/lib/actions/create-credential.ts b/packages/pieces/community/certopus/src/lib/actions/create-credential.ts new file mode 100644 index 0000000..375a092 --- /dev/null +++ b/packages/pieces/community/certopus/src/lib/actions/create-credential.ts @@ -0,0 +1,207 @@ +import { + DynamicPropsValue, + Property, + createAction, +} from '@activepieces/pieces-framework'; + +import { certopusCommon, makeClient } from '../common'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { Organisation } from '../common/models/oranisation'; +import { Event } from '../common/models/event'; +import { Category } from '../common/models/category'; +import { RecipientField } from '../common/models/recipient-field'; +import { certopusAuth } from '../../'; + +export const createCredential = createAction({ + auth: certopusAuth, + name: 'create_credential', + displayName: 'Create Credential', + description: 'Create a credential', + props: { + organisation: Property.Dropdown({ + displayName: 'Organisations', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please enter your API key first.', + }; + } + const client = makeClient(auth as string); + const res = await client.listOrganisations(); + console.log(res); + + return { + disabled: false, + options: res.map((x: Organisation) => ({ + label: x.name, + value: x.id, + })), + }; + }, + }), + event: Property.Dropdown({ + displayName: 'Event', + refreshers: ['organisation'], + required: true, + options: async ({ auth, organisation }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please enter your API key first.', + }; + } + if (!organisation) { + return { + disabled: true, + options: [], + placeholder: 'Please select an organisation first.', + }; + } + const client = makeClient(auth as string); + const res = await client.listEvents(organisation as string); + console.log(res); + + return { + disabled: false, + options: res.map((x: Event) => ({ + label: x.title, + value: x.id, + })), + }; + }, + }), + category: Property.Dropdown({ + displayName: 'Category', + refreshers: ['organisation', 'event'], + required: true, + options: async ({ auth, organisation, event }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please enter your API key first.', + }; + } + if (!organisation) { + return { + disabled: true, + options: [], + placeholder: 'Please select an organisation first.', + }; + } + if (!event) { + return { + disabled: true, + options: [], + placeholder: 'Please select an event first.', + }; + } + const client = makeClient(auth as string); + const res = await client.listCategories( + organisation as string, + event as string + ); + console.log(res); + + return { + disabled: false, + options: res.map((x: Category) => ({ + label: x.title, + value: x.id, + })), + }; + }, + }), + generate: Property.Checkbox({ + displayName: 'Generate', + description: 'Automatically generate the crentials', + defaultValue: false, + required: true, + }), + publish: Property.Checkbox({ + displayName: 'Publish', + description: 'Automatically publish the crentials', + defaultValue: false, + required: true, + }), + email: Property.ShortText({ + displayName: 'Recipient Email', + description: 'Email address of the recipient', + required: true, + }), + fields: Property.DynamicProperties({ + displayName: 'Recipient Data', + required: true, + refreshers: ['organisation', 'event', 'category'], + + props: async ({ auth, organisation, event, category }) => { + if (!auth) return {}; + if (!organisation) return {}; + if (!event) return {}; + if (!category) return {}; + + const fields: DynamicPropsValue = {}; + try { + const client = makeClient(auth.toString()); + const recipientFields: RecipientField[] = + await client.listRecipientFields( + organisation.toString(), + event.toString(), + category.toString() + ); + recipientFields.forEach((field: RecipientField) => { + //skil email field as it already included + if (field.key !== 'email') { + const params = { + displayName: field.label, + description: `Enter data for the ${field.label} field`, + required: true, + }; + fields[field.key] = Property.ShortText(params); + } + }); + } catch (e) { + console.debug(e); + } + + return fields; + }, + }), + }, + async run(context) { + const { organisation, event, category, publish, generate, email, fields } = + context.propsValue; + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${certopusCommon.baseUrl}/certificates`, + body: { + organisationId: organisation, + eventId: event, + categoryId: category, + publish: publish, + generate: generate, + recipients: [ + { + email: email, + data: fields, + }, + ], + }, + headers: { + 'x-api-key': context.auth, + }, + queryParams: {}, + }; + const res = await httpClient.sendRequest(request); + return res.body; + }, +}); diff --git a/packages/pieces/community/certopus/src/lib/common/client.ts b/packages/pieces/community/certopus/src/lib/common/client.ts new file mode 100644 index 0000000..f9f13b9 --- /dev/null +++ b/packages/pieces/community/certopus/src/lib/common/client.ts @@ -0,0 +1,67 @@ +import { + HttpMessageBody, + HttpMethod, + QueryParams, + httpClient, +} from '@activepieces/pieces-common'; +import { Organisation } from './models/oranisation'; +import { Event } from './models/event'; +import { Category } from './models/category'; +import { RecipientField } from './models/recipient-field'; +import { certopusCommon } from '.'; + +export class CertopusClient { + constructor(private token: string) {} + + async makeRequest( + method: HttpMethod, + url: string, + query?: QueryParams, + body?: object + ): Promise { + const res = await httpClient.sendRequest({ + method, + url: certopusCommon.baseUrl + url, + queryParams: query, + body, + headers: { + 'x-api-key': this.token, + }, + }); + return res.body.data; + } + + listOrganisations(): Promise { + return this.makeRequest(HttpMethod.GET, '/organisations'); + } + + listEvents(organisationId: string): Promise { + return this.makeRequest( + HttpMethod.GET, + `/events/${organisationId}` + ); + } + + listCategories(organisationId: string, eventId: string): Promise { + return this.makeRequest(HttpMethod.GET, '/categories', { + organisationId, + eventId, + }); + } + + listRecipientFields( + organisationId: string, + eventId: string, + categoryId: string + ): Promise { + return this.makeRequest( + HttpMethod.GET, + '/recipient_fields', + { + organisationId, + eventId, + categoryId, + } + ); + } +} diff --git a/packages/pieces/community/certopus/src/lib/common/index.ts b/packages/pieces/community/certopus/src/lib/common/index.ts new file mode 100644 index 0000000..d349e26 --- /dev/null +++ b/packages/pieces/community/certopus/src/lib/common/index.ts @@ -0,0 +1,9 @@ +import { CertopusClient } from './client'; + +export function makeClient(apiKey: string): CertopusClient { + return new CertopusClient(apiKey); +} + +export const certopusCommon = { + baseUrl: 'https://api.certopus.com/v1', +}; diff --git a/packages/pieces/community/certopus/src/lib/common/models/category.ts b/packages/pieces/community/certopus/src/lib/common/models/category.ts new file mode 100644 index 0000000..3408b84 --- /dev/null +++ b/packages/pieces/community/certopus/src/lib/common/models/category.ts @@ -0,0 +1,4 @@ +export interface Category { + id: string; + title: string; +} diff --git a/packages/pieces/community/certopus/src/lib/common/models/event.ts b/packages/pieces/community/certopus/src/lib/common/models/event.ts new file mode 100644 index 0000000..4596c75 --- /dev/null +++ b/packages/pieces/community/certopus/src/lib/common/models/event.ts @@ -0,0 +1,4 @@ +export interface Event { + id: string; + title: string; +} diff --git a/packages/pieces/community/certopus/src/lib/common/models/oranisation.ts b/packages/pieces/community/certopus/src/lib/common/models/oranisation.ts new file mode 100644 index 0000000..eab7401 --- /dev/null +++ b/packages/pieces/community/certopus/src/lib/common/models/oranisation.ts @@ -0,0 +1,6 @@ +export interface Organisation { + id: string; + imageUrl: string; + name: string; + integrationAllowed: boolean; +} diff --git a/packages/pieces/community/certopus/src/lib/common/models/recipient-field.ts b/packages/pieces/community/certopus/src/lib/common/models/recipient-field.ts new file mode 100644 index 0000000..19cf559 --- /dev/null +++ b/packages/pieces/community/certopus/src/lib/common/models/recipient-field.ts @@ -0,0 +1,5 @@ +export interface RecipientField { + key: string; + label: string; + type: string; +} diff --git a/packages/pieces/community/certopus/tsconfig.json b/packages/pieces/community/certopus/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/certopus/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/certopus/tsconfig.lib.json b/packages/pieces/community/certopus/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/certopus/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/chainalysis-api/.eslintrc.json b/packages/pieces/community/chainalysis-api/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/chainalysis-api/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/chainalysis-api/README.md b/packages/pieces/community/chainalysis-api/README.md new file mode 100644 index 0000000..688f4a6 --- /dev/null +++ b/packages/pieces/community/chainalysis-api/README.md @@ -0,0 +1,7 @@ +# pieces-chainalysis-api + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-chainalysis-api` to build the library. diff --git a/packages/pieces/community/chainalysis-api/package.json b/packages/pieces/community/chainalysis-api/package.json new file mode 100644 index 0000000..7bdf168 --- /dev/null +++ b/packages/pieces/community/chainalysis-api/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-chainalysis-api", + "version": "0.0.2" +} diff --git a/packages/pieces/community/chainalysis-api/project.json b/packages/pieces/community/chainalysis-api/project.json new file mode 100644 index 0000000..e115094 --- /dev/null +++ b/packages/pieces/community/chainalysis-api/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-chainalysis-api", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/chainalysis-api/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/chainalysis-api", + "tsConfig": "packages/pieces/community/chainalysis-api/tsconfig.lib.json", + "packageJson": "packages/pieces/community/chainalysis-api/package.json", + "main": "packages/pieces/community/chainalysis-api/src/index.ts", + "assets": [ + "packages/pieces/community/chainalysis-api/*.md", + { + "input": "packages/pieces/community/chainalysis-api/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/chainalysis-api/src/index.ts b/packages/pieces/community/chainalysis-api/src/index.ts new file mode 100644 index 0000000..9cc0778 --- /dev/null +++ b/packages/pieces/community/chainalysis-api/src/index.ts @@ -0,0 +1,21 @@ + +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { checkAddressSanction } from './lib/actions/check-address-sanction'; + +export const chainalysisApiAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: 'API Key for Chainalysis Screening API', + required: true, +}) + +export const chainalysisApi = createPiece({ + displayName: "Chainalysis Screening API", + description: "Chainalysis Screening API allows you to check if a blockchain address is sanctioned.", + auth: chainalysisApiAuth, + minimumSupportedRelease: '0.20.0', + categories: [], + logoUrl: "https://cdn.activepieces.com/pieces/chainalysis-api.jpg", + authors: ['ahmad-swanblocks'], + actions: [checkAddressSanction], + triggers: [], +}); \ No newline at end of file diff --git a/packages/pieces/community/chainalysis-api/src/lib/actions/check-address-sanction.ts b/packages/pieces/community/chainalysis-api/src/lib/actions/check-address-sanction.ts new file mode 100644 index 0000000..b0f97ff --- /dev/null +++ b/packages/pieces/community/chainalysis-api/src/lib/actions/check-address-sanction.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { chainalysisApiAuth } from '../..'; + +export const checkAddressSanction = createAction({ + name: 'checkAddressSanction', + displayName: 'Check Address Sanctions', + description: 'Check if an address is sanctioned', + auth: chainalysisApiAuth, + requireAuth: true, + props: { + address: Property.ShortText({ + displayName: 'Address', + description: 'Address to check for sanctions', + required: true, + }), + }, + async run({ auth, propsValue }) { + const address = propsValue.address; + const apiKey = auth; + + try { + const response = await fetch(`https://public.chainalysis.com/api/v1/address/${address}`, { + method: 'GET', + headers: { + 'X-API-Key': apiKey, + 'Accept': 'application/json', + }, + }); + + if (!response.ok) { + throw new Error(`Error: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error('[chainalysis-api-checkAddressSanction] Error checking address sanction:', error); + throw error; + } + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/chainalysis-api/tsconfig.json b/packages/pieces/community/chainalysis-api/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/chainalysis-api/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/chainalysis-api/tsconfig.lib.json b/packages/pieces/community/chainalysis-api/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/chainalysis-api/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/chargekeep/.eslintrc.json b/packages/pieces/community/chargekeep/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/chargekeep/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/chargekeep/README.md b/packages/pieces/community/chargekeep/README.md new file mode 100644 index 0000000..5479155 --- /dev/null +++ b/packages/pieces/community/chargekeep/README.md @@ -0,0 +1,7 @@ +# pieces-chargekeep + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-chargekeep` to build the library. diff --git a/packages/pieces/community/chargekeep/package.json b/packages/pieces/community/chargekeep/package.json new file mode 100644 index 0000000..348d45f --- /dev/null +++ b/packages/pieces/community/chargekeep/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-chargekeep", + "version": "0.2.2" +} diff --git a/packages/pieces/community/chargekeep/project.json b/packages/pieces/community/chargekeep/project.json new file mode 100644 index 0000000..4576d3f --- /dev/null +++ b/packages/pieces/community/chargekeep/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-chargekeep", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/chargekeep/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/chargekeep", + "tsConfig": "packages/pieces/community/chargekeep/tsconfig.lib.json", + "packageJson": "packages/pieces/community/chargekeep/package.json", + "main": "packages/pieces/community/chargekeep/src/index.ts", + "assets": [ + "packages/pieces/community/chargekeep/*.md", + { + "input": "packages/pieces/community/chargekeep/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/chargekeep/src/index.ts b/packages/pieces/community/chargekeep/src/index.ts new file mode 100644 index 0000000..bb4ff7c --- /dev/null +++ b/packages/pieces/community/chargekeep/src/index.ts @@ -0,0 +1,71 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { addOrUpdateContactExtended } from './lib/actions/add-or-update-contact-extended'; +import { addOrUpdateContact } from './lib/actions/add-or-update-contact'; +import { addOrUpdateSubscription } from './lib/actions/add-or-update-subscription'; +import { createInvoice } from './lib/actions/create-invoice'; +import { newLead } from './lib/triggers/new-lead'; +import { newPayment } from './lib/triggers/new-payment'; +import { newSubscription } from './lib/triggers/new-subscription'; +import { createProduct } from './lib/actions/create-product'; +import { getContactDetails } from './lib/actions/get-contact-details'; + +const markdownDescription = ` + Follow these instructions to get your Chargekeep API Key: + + 1. Visit the following website: https://crm.chargekeep.com/ or the beta website: https://beta.chargekeep.com + 2. Once on the website, locate and click on the admin to obtain your chargekeep API Key. +`; + +export const chargekeepAuth = PieceAuth.CustomAuth({ + description: markdownDescription, + required: true, + props: { + base_url: Property.StaticDropdown({ + displayName: 'Base URL', + description: 'Select the base environment URL', + required: true, + options: { + disabled: false, + options: [ + { + label: 'ChargeKeep Live (crm.chargekeep.com)', + value: 'https://crm.chargekeep.com', + }, + { + label: 'ChargeKeep Beta (beta.chargekeep.com)', + value: 'https://beta.chargekeep.com', + }, + ], + }, + }), + api_key: PieceAuth.SecretText({ + displayName: 'Secret API Key', + description: 'Enter the API Key', + required: true, + }), + }, +}); + +export const chargekeep = createPiece({ + displayName: 'ChargeKeep', + description: 'Easy-to-use recurring and one-time payments software for Stripe & PayPal', + auth: chargekeepAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/chargekeep.png', + categories: [PieceCategory.COMMERCE, PieceCategory.PAYMENT_PROCESSING], + authors: ['Trayshmhirk'], + actions: [ + addOrUpdateContact, + addOrUpdateContactExtended, + addOrUpdateSubscription, + createInvoice, + createProduct, + getContactDetails, + ], + triggers: [newLead, newPayment, newSubscription], +}); diff --git a/packages/pieces/community/chargekeep/src/lib/actions/add-or-update-contact-extended.ts b/packages/pieces/community/chargekeep/src/lib/actions/add-or-update-contact-extended.ts new file mode 100644 index 0000000..807d425 --- /dev/null +++ b/packages/pieces/community/chargekeep/src/lib/actions/add-or-update-contact-extended.ts @@ -0,0 +1,1293 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { chargekeepAuth } from '../../'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const addOrUpdateContactExtended = createAction({ + name: 'addOrUpdateContact(extended)', + displayName: 'Add or Update Contact (Extended)', + description: 'Adds or updates a contact (extended version)', + auth: chargekeepAuth, + props: { + matchExisting: Property.StaticDropdown({ + displayName: 'Match Existing Contact', + description: + 'If "Yes", will try to find an existing record using Email and Full Name and update it.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + overrideLists: Property.StaticDropdown({ + displayName: 'Override Lists', + description: + 'If "Yes", will override lists of contact details in update mode instead of merging them - lists, tags, emails, phones, links, addresses, photos.', + required: false, + defaultValue: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + ignoreInvalidValues: Property.StaticDropdown({ + displayName: 'Ignore Invalid Values', + description: + 'If "Yes", will save the record even if there are some validation errors.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + contactId: Property.Number({ + displayName: 'Contact ID', + description: 'Sperse Contact ID. Will be used for looking a client', + defaultValue: 0, + required: false, + }), + contactXref: Property.ShortText({ + displayName: 'Contact XREF', + description: + 'This is string external reference that can be passed during creation and then if sent again it will update the record.', + required: false, + }), + importType: Property.StaticDropdown({ + displayName: 'Contact Type', + required: true, + defaultValue: 'Lead', + options: { + disabled: false, + options: [ + { + label: 'Lead', + value: 'Lead', + }, + { + label: 'Client', + value: 'Client', + }, + { + label: 'Partner', + value: 'Partner', + }, + ], + }, + }), + // user info + createUser: Property.StaticDropdown({ + displayName: 'Create User', + description: + 'If "Yes" then User will be created. Personal email will be used as User Name.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + sendWelcomeEmail: Property.StaticDropdown({ + displayName: 'Send Welcome Email', + description: + 'If "Yes" then Welcome Email will be sent to the newly created user.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + userPassword: Property.ShortText({ + displayName: 'User Password', + description: + 'If password is not passed then it will be automatically generated.', + required: false, + }), + // fullname + namePrefix: Property.ShortText({ + displayName: 'Prefix', + description: 'The title used to address the contact.', + required: false, + }), + firstName: Property.ShortText({ + displayName: 'First Name', + description: 'Required if Last Name and Company Name fields are empty.', + required: true, + }), + middleName: Property.ShortText({ + displayName: 'Middle Name', + description: "The contact's middle name.", + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + description: 'Required if First Name and Company Name fields are empty.', + required: true, + }), + nickName: Property.ShortText({ + displayName: 'Nick Name', + description: "The contact's nick name.", + required: false, + }), + nameSuffix: Property.ShortText({ + displayName: 'Suffix', + description: 'Additional information about the contact e.g PhD.', + required: false, + }), + // personal info + gender: Property.StaticDropdown({ + displayName: 'Gender', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Male', + value: 'Male', + }, + { + label: 'Female', + value: 'Female', + }, + ], + }, + }), + dob: Property.ShortText({ + displayName: 'Date of Birth', + description: 'Valid date format YYYY-MM-DD or MM-DD-YYYY.', + required: false, + }), + bankCode: Property.ShortText({ + displayName: 'Bank Code', + description: "The contact's 4-letter personality code.", + required: false, + }), + ssn: Property.ShortText({ + displayName: 'SSN', + description: "The contact's social security number.", + required: false, + }), + drivingLicense: Property.ShortText({ + displayName: 'Driving License', + required: false, + }), + drivingLicenseState: Property.ShortText({ + displayName: 'Driving License State', + required: false, + }), + isActiveMilitaryDuty: Property.StaticDropdown({ + displayName: 'Is Active Military Duty', + description: + 'Possible values are: Yes, No. If nothing is chosen that means "Unknown".', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + isUSCitizen: Property.StaticDropdown({ + displayName: 'Is US Citizen', + description: + 'Possible values are: Yes, No. If nothing is chosen that means "Unknown".', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + preferredToD: Property.StaticDropdown({ + displayName: 'Preferred Time of Day', + description: 'Preferred Time of Day to contact with Client', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Morning', + value: 'Morning', + }, + { + label: 'Afternoon', + value: 'Afternoon', + }, + { + label: 'Evening', + value: 'Evening', + }, + { + label: 'Anytime', + value: 'Anytime', + }, + ], + }, + }), + personAffiliateCode: Property.ShortText({ + displayName: 'Person Affiliate Code', + description: + 'Affiliate Code is used for the current person detection as a source of new leads. Alphanumeric characters, underscore and hyphen are allowed.', + required: false, + }), + // email + email1: Property.LongText({ + displayName: 'Personal Email', + description: "The contact's personal email.", + required: false, + }), + email2: Property.LongText({ + displayName: 'Alternative Personal email', + description: "The contact's alternative personal email.", + required: false, + }), + email3: Property.LongText({ + displayName: 'Other Personal email', + description: "The contact's additional email.", + required: false, + }), + workEmail1: Property.LongText({ + displayName: 'Work Email', + description: "The contact's work email.", + required: false, + }), + workEmail2: Property.LongText({ + displayName: 'Alternative Work Email', + description: "The contact's alternative work email.", + required: false, + }), + workEmail3: Property.LongText({ + displayName: 'Other Work Email', + description: "The contact's other work email.", + required: false, + }), + // phone + mobilePhone: Property.ShortText({ + displayName: 'Mobile Phone', + description: "The contact's primary phone number.", + required: false, + }), + mobilePhoneExt: Property.ShortText({ + displayName: 'Mobile Phone Ext', + description: "The contact's primary phone number extension.", + required: false, + }), + homePhone: Property.ShortText({ + displayName: 'Home Phone', + description: "The contact's home phone number.", + required: false, + }), + homePhoneExt: Property.ShortText({ + displayName: 'Home Phone Ext', + description: "The contact's home phone number extension.", + required: false, + }), + workPhone1: Property.ShortText({ + displayName: 'Work Phone', + description: "The contact's work phone number.", + required: false, + }), + workPhone1Ext: Property.ShortText({ + displayName: 'Work Phone Ext', + description: "The contact's work phone number extension.", + required: false, + }), + workPhone2: Property.ShortText({ + displayName: 'Work Phone 2', + description: "The contact's alternative work phone number.", + required: false, + }), + workPhone2Ext: Property.ShortText({ + displayName: 'Work Phone 2 Ext', + description: "The contact's alternative work phone number extension.", + required: false, + }), + // home address + home_street: Property.LongText({ + displayName: 'Home Street', + description: + "The contact's full street address (can include apartment or unit number).", + required: false, + }), + home_addressLine2: Property.LongText({ + displayName: 'Address 2', + required: false, + }), + home_city: Property.LongText({ + displayName: 'City', + description: "The contact's city of residence.", + required: false, + }), + home_stateName: Property.ShortText({ + displayName: 'State Name', + description: "The contact's state of residence.", + required: false, + }), + home_stateId: Property.ShortText({ + displayName: 'State Code', + description: "The contact's state code.", + required: false, + }), + home_zip: Property.ShortText({ + displayName: 'ZIP Code', + description: "The contact's zip/postal code.", + required: false, + }), + home_countryName: Property.ShortText({ + displayName: 'Country Name', + description: "The contact's country of residence.", + required: false, + }), + home_countryId: Property.ShortText({ + displayName: 'Country Code', + description: "The contact's country code.", + required: false, + }), + // work address + work_street: Property.LongText({ + displayName: 'Work Street', + description: "The contact's work address.", + required: false, + }), + work_addressLine2: Property.LongText({ + displayName: 'Address 2', + required: false, + }), + work_city: Property.LongText({ + displayName: 'City', + description: "The contact's work city.", + required: false, + }), + work_stateName: Property.ShortText({ + displayName: 'State Name', + description: "The contact's work state.", + required: false, + }), + work_stateId: Property.ShortText({ + displayName: 'State Code', + description: "The contact's work state code.", + required: false, + }), + work_zip: Property.ShortText({ + displayName: 'ZIP Code', + description: "The contact's work zip code.", + required: false, + }), + work_countryName: Property.ShortText({ + displayName: 'Country Name', + description: "The contact's work country.", + required: false, + }), + work_countryId: Property.ShortText({ + displayName: 'Country Code', + description: "The contact's work country code.", + required: false, + }), + // links + webSiteUrl: Property.LongText({ + displayName: 'Website', + description: "The contact's company website URL.", + required: false, + }), + linkedInUrl: Property.LongText({ + displayName: 'LinkedIn', + description: "The contact's LinkedIn profile id.", + required: false, + }), + photoUrl: Property.LongText({ + displayName: 'Photo URL', + description: "The contact's person photo URL.", + required: false, + }), + facebookUrl: Property.LongText({ + displayName: 'Facebook', + description: "The contact's Facebook profile id.", + required: false, + }), + instagramUrl: Property.LongText({ + displayName: 'Instagram', + description: "The contact's Instagram profile id.", + required: false, + }), + twitterUrl: Property.LongText({ + displayName: 'Twitter', + description: "The contact's Twitter profile id.", + required: false, + }), + googlePlusUrl: Property.LongText({ + displayName: 'Google Reviews', + description: "The contact's Google reviews.", + required: false, + }), + angelListUrl: Property.LongText({ + displayName: 'AngelList', + description: "The contact's AngelList profile id.", + required: false, + }), + zoomUrl: Property.LongText({ + displayName: 'Zoom', + description: "The contact's Zoom id.", + required: false, + }), + // content + experience: Property.LongText({ + displayName: 'Content', + description: "The contact's professional experience.", + required: false, + }), + profileSummary: Property.LongText({ + displayName: 'Profile Summary', + description: "The contact's profile summary.", + required: false, + }), + notes: Property.LongText({ + displayName: 'Notes', + description: 'Additional notes about the contact', + required: false, + }), + followUpDate: Property.ShortText({ + displayName: 'Follow Up Date', + description: + 'Valid date format YYYY-MM-DD HH:MM:SS. If date is defined then new Follow Up Task will be created for this contact', + required: false, + }), + assignedUser: Property.ShortText({ + displayName: 'Assigned User', + description: + 'Preferably, Sperse User Email should be passed as it is unique within Sperse account but User Name can be also passed', + required: false, + }), + leadDealAmount: Property.Number({ + displayName: 'Deal Amount', + description: 'Estimated deal/opportunity amount.', + required: false, + }), + // custom fields + customField1: Property.LongText({ + displayName: 'Custom Field 1', + description: 'Additional custom data for the contact record.', + required: false, + }), + customField2: Property.LongText({ + displayName: 'Custom Field 2', + required: false, + }), + customField3: Property.LongText({ + displayName: 'Custom Field 3', + required: false, + }), + customField4: Property.LongText({ + displayName: 'Custom Field 4', + required: false, + }), + customField5: Property.LongText({ + displayName: 'Custom Field 5', + required: false, + }), + // business info + companyName: Property.LongText({ + displayName: 'Company Name', + description: + "Name of the contact's company (This field is mandatory if the First Name and Last Name fields are empty).", + required: false, + }), + organizationType: Property.StaticDropdown({ + displayName: 'Organization Type', + required: false, + options: { + disabled: false, + options: [ + { + label: 'LLP', + value: 'LLP', + }, + { + label: 'LLC', + value: 'LLC', + }, + { + label: 'Inc', + value: 'Inc', + }, + { + label: 'LP', + value: 'LP', + }, + { + label: 'Partnership', + value: 'Partnership', + }, + { + label: 'Sole Proprietership', + value: 'Sole Proprietership', + }, + { + label: 'Trust', + value: 'Trust', + }, + { + label: 'LLLP', + value: 'LLLP', + }, + { + label: 'Other', + value: 'Other', + }, + ], + }, + }), + isEmployed: Property.StaticDropdown({ + displayName: 'Is Employed', + description: 'Pass yes if the client is employed in this Organization.', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + jobTitle: Property.ShortText({ + displayName: 'Job Title', + description: "The contact's job title.", + required: false, + }), + employmentStartDate: Property.ShortText({ + displayName: 'Employment Start Date', + description: 'Valid date format YYYY-MM-DD or MM-DD-YYYY', + required: false, + }), + employeeCount: Property.ShortText({ + displayName: 'Employee Count', + required: false, + }), + dateFounded: Property.ShortText({ + displayName: 'Date Founded', + description: 'Valid date format YYYY-MM-DD or MM-DD-YYYY', + required: false, + }), + ein: Property.LongText({ + displayName: 'EIN', + required: false, + }), + annualRevenue: Property.Number({ + displayName: 'Annual Revenue', + required: false, + }), + industry: Property.ShortText({ + displayName: 'Industry', + description: "The company's industry.", + required: false, + }), + companyPhone: Property.ShortText({ + displayName: 'Company Phone', + required: false, + }), + companyPhoneExt: Property.ShortText({ + displayName: 'Company Phone Extension', + required: false, + }), + companyFaxNumber: Property.LongText({ + displayName: 'Company Fax Number', + required: false, + }), + companyEmail: Property.LongText({ + displayName: 'Company Email', + required: false, + }), + companyWebSiteUrl: Property.LongText({ + displayName: 'Company Website', + required: false, + }), + companyFacebookUrl: Property.LongText({ + displayName: 'Company Facebook', + required: false, + }), + companyLinkedInUrl: Property.LongText({ + displayName: 'Company LinkedIn', + required: false, + }), + companyInstagramUrl: Property.LongText({ + displayName: 'Company Instagram', + required: false, + }), + companyTwitterUrl: Property.LongText({ + displayName: 'Company Twitter', + required: false, + }), + companyGooglePlusUrl: Property.LongText({ + displayName: 'Company Google Reviews', + required: false, + }), + companyCrunchbaseUrl: Property.LongText({ + displayName: 'Company Crunchbase', + required: false, + }), + companyBBBUrl: Property.LongText({ + displayName: 'Company BBB URL', + required: false, + }), + companyZoomUrl: Property.LongText({ + displayName: 'Company Zoom', + required: false, + }), + companyCalendlyUrl: Property.LongText({ + displayName: 'Company Calendly', + required: false, + }), + companyLogoUrl: Property.LongText({ + displayName: 'Company Logo URL', + required: false, + }), + companyAffiliateCode: Property.ShortText({ + displayName: 'Company Affiliate Code', + required: false, + }), + // company full Address + company_street: Property.LongText({ + displayName: 'Company Street', + required: false, + }), + company_addressLine2: Property.LongText({ + displayName: 'Address 2', + required: false, + }), + company_city: Property.ShortText({ + displayName: 'City', + required: false, + }), + company_stateName: Property.ShortText({ + displayName: 'State Name', + required: false, + }), + company_stateId: Property.ShortText({ + displayName: 'State Code', + required: false, + }), + company_zip: Property.ShortText({ + displayName: 'Company Zip Code', + required: false, + }), + company_countryName: Property.LongText({ + displayName: 'Company Country Name', + required: false, + }), + company_countryId: Property.ShortText({ + displayName: 'Company Country Code', + required: false, + }), + // tracking info + leadSource: Property.ShortText({ + displayName: 'Source Code', + description: + 'The first known source the contact used to find your website. You can set this automatically and update manually later.', + required: false, + }), + channelId: Property.ShortText({ + displayName: 'Channel Code', + description: 'The channel/medium the contact used to find your website.', + required: false, + }), + affiliateCode: Property.ShortText({ + displayName: 'Affiliate Code', + description: + 'The affiliate/referer partner through which the contact signed up.', + required: false, + }), + campaignId: Property.ShortText({ + displayName: 'Campaign ID', + required: false, + }), + gclId: Property.ShortText({ + displayName: 'Google Click ID', + required: false, + }), + refererURL: Property.LongText({ + displayName: 'Referer URL', + description: + 'The webpage where the contact clicked a link that sent them to your website.', + required: false, + }), + applicantId: Property.ShortText({ + displayName: 'Applicant ID', + required: false, + }), + applicationId: Property.ShortText({ + displayName: 'Application ID', + required: false, + }), + ipAddress: Property.LongText({ + displayName: 'IP Address', + required: false, + }), + userAgent: Property.ShortText({ + displayName: 'User Agent', + required: false, + }), + siteId: Property.ShortText({ + displayName: 'Site ID', + required: false, + }), + siteUrl: Property.LongText({ + displayName: 'Site URL', + required: false, + }), + dateCreated: Property.ShortText({ + displayName: 'Date Created', + description: 'Valid date format YYYY-MM-DD HH:MM:SS', + required: false, + }), + entryUrl: Property.LongText({ + displayName: 'Entry URL', + description: + 'The first page of visit through which the contact visited your website.', + required: false, + }), + // contact Attributes + leadStageName: Property.ShortText({ + displayName: 'Lead Stage Name', + description: + 'The first page of visit through which the contact visited your website.', + required: false, + }), + star: Property.ShortText({ + displayName: 'Star', + description: + 'String Value. Supports the following items: Yellow, Blue, Green, Purple, Red. Other values will be skipped.', + required: false, + }), + rating: Property.ShortText({ + displayName: 'Rating', + description: 'This is the static array from 1 to 10', + required: false, + }), + // UtmParameters + utmSource: Property.LongText({ + displayName: 'UTM Source', + required: false, + }), + utmMedium: Property.LongText({ + displayName: 'UTM Medium', + required: false, + }), + utmCampaign: Property.LongText({ + displayName: 'UTM Campaign', + required: false, + }), + utmTerm: Property.LongText({ + displayName: 'UTM Term', + required: false, + }), + utmContent: Property.LongText({ + displayName: 'UTM Content', + required: false, + }), + utmKeyword: Property.LongText({ + displayName: 'UTM Keyword', + required: false, + }), + utmAdGroup: Property.LongText({ + displayName: 'UTM AdGroup', + required: false, + }), + utmName: Property.LongText({ + displayName: 'UTM Name', + required: false, + }), + // requestCustomInfo + requestCustomField1: Property.LongText({ + displayName: 'Request Custom Field 1', + required: false, + }), + requestCustomField2: Property.LongText({ + displayName: 'Request Custom Field 2', + required: false, + }), + requestCustomField3: Property.LongText({ + displayName: 'Request Custom Field 3', + required: false, + }), + requestCustomField4: Property.LongText({ + displayName: 'Request Custom Field 4', + required: false, + }), + requestCustomField5: Property.LongText({ + displayName: 'Request Custom Field 5', + required: false, + }), + // subscription1 + sub1_productCode: Property.ShortText({ + displayName: 'Product Code', + description: + 'Product Code from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 1.', + required: false, + }), + sub1_paymentPeriodType: Property.StaticDropdown({ + displayName: 'Product Payment Period Type', + description: + 'The chosen Period Type has to be set for the Product on Sperse side', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + ], + }, + }), + sub1_systemType: Property.ShortText({ + displayName: 'System Type', + description: + 'Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 1.', + required: false, + }), + sub1_code: Property.ShortText({ + displayName: 'Code', + description: + 'Code of subscription service from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 1.', + required: false, + }), + sub1_name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + sub1_level: Property.ShortText({ + displayName: 'Level', + description: 'Code of subscription service level from the Sperse CRM.', + required: false, + }), + sub1_startDate: Property.ShortText({ + displayName: 'Start Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub1_endDate: Property.ShortText({ + displayName: 'End Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub1_amount: Property.Number({ + displayName: 'Amount', + required: false, + }), + // subscription2 + sub2_productCode: Property.ShortText({ + displayName: 'Product Code', + description: + 'Product Code from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 2.', + required: false, + }), + sub2_paymentPeriodType: Property.StaticDropdown({ + displayName: 'Product Payment Period Type', + description: + 'The chosen Period Type has to be set for the Product on Sperse side', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + ], + }, + }), + sub2_systemType: Property.ShortText({ + displayName: 'System Type', + description: + 'Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 2.', + required: false, + }), + sub2_code: Property.ShortText({ + displayName: 'Code', + description: + 'Code of subscription service from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 2.', + required: false, + }), + sub2_name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + sub2_level: Property.ShortText({ + displayName: 'Level', + description: 'Code of subscription service level from the Sperse CRM.', + required: false, + }), + sub2_startDate: Property.ShortText({ + displayName: 'Start Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub2_endDate: Property.ShortText({ + displayName: 'End Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub2_amount: Property.Number({ + displayName: 'Amount', + required: false, + }), + // subscription3 + sub3_productCode: Property.ShortText({ + displayName: 'Product Code', + description: + 'Product Code from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 3.', + required: false, + }), + sub3_paymentPeriodType: Property.StaticDropdown({ + displayName: 'Product Payment Period Type', + description: + 'The chosen Period Type has to be set for the Product on Sperse side', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + ], + }, + }), + sub3_systemType: Property.ShortText({ + displayName: 'System Type', + description: + 'Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 3.', + required: false, + }), + sub3_code: Property.ShortText({ + displayName: 'Code', + description: + 'Code of subscription service from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 3.', + required: false, + }), + sub3_name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + sub3_level: Property.ShortText({ + displayName: 'Level', + description: 'Code of subscription service level from the Sperse CRM.', + required: false, + }), + sub3_startDate: Property.ShortText({ + displayName: 'Start Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub3_endDate: Property.ShortText({ + displayName: 'End Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub3_amount: Property.Number({ + displayName: 'Amount', + required: false, + }), + }, + + async run(context) { + const customer = { + importType: context.propsValue.importType, + ignoreInvalidValues: context.propsValue.ignoreInvalidValues, + matchExisting: context.propsValue.matchExisting, + overrideLists: context.propsValue.overrideLists, + createUser: context.propsValue.createUser, + sendWelcomeEmail: context.propsValue.sendWelcomeEmail, + contactId: context.propsValue.contactId, + contactXref: context.propsValue.contactXref, + userPassword: context.propsValue.userPassword, + personalInfo: { + fullName: { + namePrefix: context.propsValue.namePrefix, + firstName: context.propsValue.firstName, + middleName: context.propsValue.middleName, + lastName: context.propsValue.lastName, + nameSuffix: context.propsValue.nickName, + nickName: context.propsValue.nameSuffix, + }, + doB: context.propsValue.dob, + mobilePhone: context.propsValue.mobilePhone, + mobilePhoneExt: context.propsValue.mobilePhoneExt, + homePhone: context.propsValue.homePhone, + homePhoneExt: context.propsValue.homePhoneExt, + ssn: context.propsValue.ssn, + bankCode: context.propsValue.bankCode, + email1: context.propsValue.email1, + email2: context.propsValue.email2, + email3: context.propsValue.email3, + preferredToD: context.propsValue.preferredToD, + drivingLicense: context.propsValue.drivingLicense, + drivingLicenseState: context.propsValue.drivingLicenseState, + isActiveMilitaryDuty: context.propsValue.isActiveMilitaryDuty, + gender: context.propsValue.gender, + fullAddress: { + street: context.propsValue.home_street, + addressLine2: context.propsValue.home_addressLine2, + city: context.propsValue.home_city, + stateName: context.propsValue.home_stateName, + stateId: context.propsValue.home_stateId, + zip: context.propsValue.home_zip, + countryName: context.propsValue.home_countryName, + countryId: context.propsValue.home_countryId, + }, + isUSCitizen: context.propsValue.isUSCitizen, + webSiteUrl: context.propsValue.webSiteUrl, + facebookUrl: context.propsValue.facebookUrl, + linkedInUrl: context.propsValue.linkedInUrl, + instagramUrl: context.propsValue.instagramUrl, + twitterUrl: context.propsValue.twitterUrl, + googlePlusUrl: context.propsValue.googlePlusUrl, + angelListUrl: context.propsValue.angelListUrl, + zoomUrl: context.propsValue.zoomUrl, + photoUrl: context.propsValue.photoUrl, + experience: context.propsValue.experience, + profileSummary: context.propsValue.profileSummary, + affiliateCode: context.propsValue.personAffiliateCode, + customFields: { + customField1: context.propsValue.customField1, + customField2: context.propsValue.customField2, + customField3: context.propsValue.customField3, + customField4: context.propsValue.customField4, + customField5: context.propsValue.customField5, + }, + }, + businessInfo: { + companyName: context.propsValue.companyName, + organizationType: context.propsValue.organizationType, + jobTitle: context.propsValue.isEmployed, + isEmployed: context.propsValue.jobTitle, + employmentStartDate: context.propsValue.employmentStartDate, + employeeCount: context.propsValue.employeeCount, + dateFounded: context.propsValue.dateFounded, + ein: context.propsValue.ein, + annualRevenue: context.propsValue.annualRevenue, + industry: context.propsValue.industry, + companyPhone: context.propsValue.companyPhone, + companyPhoneExt: context.propsValue.companyPhoneExt, + companyFaxNumber: context.propsValue.companyFaxNumber, + companyEmail: context.propsValue.companyEmail, + companyFullAddress: { + street: context.propsValue.company_street, + addressLine2: context.propsValue.company_addressLine2, + city: context.propsValue.company_city, + stateName: context.propsValue.company_stateName, + stateId: context.propsValue.company_stateId, + zip: context.propsValue.company_zip, + countryName: context.propsValue.company_countryName, + countryId: context.propsValue.company_countryId, + }, + companyWebSiteUrl: context.propsValue.companyWebSiteUrl, + companyFacebookUrl: context.propsValue.companyFacebookUrl, + companyLinkedInUrl: context.propsValue.companyLinkedInUrl, + companyInstagramUrl: context.propsValue.companyInstagramUrl, + companyTwitterUrl: context.propsValue.companyTwitterUrl, + companyGooglePlusUrl: context.propsValue.companyGooglePlusUrl, + companyCrunchbaseUrl: context.propsValue.companyCrunchbaseUrl, + companyBBBUrl: context.propsValue.companyBBBUrl, + companyCalendlyUrl: context.propsValue.companyZoomUrl, + companyZoomUrl: context.propsValue.companyCalendlyUrl, + companyLogoUrl: context.propsValue.companyLogoUrl, + workPhone1: context.propsValue.workPhone1, + workPhone1Ext: context.propsValue.workPhone1Ext, + workPhone2: context.propsValue.workPhone2, + workPhone2Ext: context.propsValue.workPhone2Ext, + workEmail1: context.propsValue.workEmail1, + workEmail2: context.propsValue.workEmail2, + workEmail3: context.propsValue.workEmail3, + workFullAddress: { + street: context.propsValue.work_street, + addressLine2: context.propsValue.work_addressLine2, + city: context.propsValue.work_city, + stateName: context.propsValue.work_stateName, + stateId: context.propsValue.work_stateId, + zip: context.propsValue.work_zip, + countryName: context.propsValue.work_countryName, + countryId: context.propsValue.work_countryId, + }, + affiliateCode: context.propsValue.companyAffiliateCode, + }, + assignedUser: context.propsValue.assignedUser, + followUpDate: context.propsValue.followUpDate, + notes: context.propsValue.notes, + dateCreated: context.propsValue.dateCreated, + leadStageName: context.propsValue.leadStageName, + leadSource: context.propsValue.leadSource, + leadDealAmount: context.propsValue.leadDealAmount, + affiliateCode: context.propsValue.affiliateCode, + campaignId: context.propsValue.campaignId, + channelId: context.propsValue.channelId, + gclId: context.propsValue.gclId, + refererUrl: context.propsValue.refererURL, + entryUrl: context.propsValue.entryUrl, + applicantId: context.propsValue.applicantId, + applicationId: context.propsValue.applicationId, + ipAddress: context.propsValue.ipAddress, + userAgent: context.propsValue.userAgent, + siteId: context.propsValue.siteId, + siteUrl: context.propsValue.siteUrl, + utmSource: context.propsValue.utmSource, + utmMedium: context.propsValue.utmMedium, + utmCampaign: context.propsValue.utmCampaign, + utmTerm: context.propsValue.utmTerm, + utmContent: context.propsValue.utmContent, + utmKeyword: context.propsValue.utmKeyword, + utmAdGroup: context.propsValue.utmAdGroup, + utmName: context.propsValue.utmName, + requestCustomInfo: { + customField1: context.propsValue.requestCustomField1, + customField2: context.propsValue.requestCustomField2, + customField3: context.propsValue.requestCustomField3, + customField4: context.propsValue.requestCustomField4, + customField5: context.propsValue.requestCustomField5, + }, + subscription1: { + productCode: context.propsValue.sub1_productCode, + paymentPeriodType: context.propsValue.sub1_paymentPeriodType, + systemType: context.propsValue.sub1_systemType, + code: context.propsValue.sub1_code, + name: context.propsValue.sub1_name, + level: context.propsValue.sub1_level, + startDate: context.propsValue.sub1_startDate, + endDate: context.propsValue.sub1_endDate, + amount: context.propsValue.sub1_amount, + }, + subscription2: { + productCode: context.propsValue.sub2_productCode, + paymentPeriodType: context.propsValue.sub2_paymentPeriodType, + systemType: context.propsValue.sub2_systemType, + code: context.propsValue.sub2_code, + name: context.propsValue.sub2_name, + level: context.propsValue.sub2_level, + startDate: context.propsValue.sub2_startDate, + endDate: context.propsValue.sub2_endDate, + amount: context.propsValue.sub2_amount, + }, + subscription3: { + productCode: context.propsValue.sub3_productCode, + paymentPeriodType: context.propsValue.sub3_paymentPeriodType, + systemType: context.propsValue.sub3_systemType, + code: context.propsValue.sub3_code, + name: context.propsValue.sub3_name, + level: context.propsValue.sub3_level, + startDate: context.propsValue.sub3_startDate, + endDate: context.propsValue.sub3_endDate, + amount: context.propsValue.sub3_amount, + }, + classificationInfo: { + rating: context.propsValue.rating, + }, + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.base_url}/api/services/CRM/Import/ImportContact`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...customer, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/chargekeep/src/lib/actions/add-or-update-contact.ts b/packages/pieces/community/chargekeep/src/lib/actions/add-or-update-contact.ts new file mode 100644 index 0000000..6aef483 --- /dev/null +++ b/packages/pieces/community/chargekeep/src/lib/actions/add-or-update-contact.ts @@ -0,0 +1,376 @@ +// add-or-update-contact.ts +import { createAction, Property } from '@activepieces/pieces-framework'; +import { chargekeepAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const addOrUpdateContact = createAction({ + name: 'addOrUpdateContact', + displayName: 'Add or Update Contact', + description: 'Creates a new contact.', + auth: chargekeepAuth, + props: { + importType: Property.StaticDropdown({ + displayName: 'Contact Type', + required: true, + defaultValue: 'Lead', + options: { + disabled: false, + options: [ + { + label: 'Lead', + value: 'Lead', + }, + { + label: 'Client', + value: 'Client', + }, + { + label: 'Partner', + value: 'Partner', + }, + ], + }, + }), + matchExisting: Property.StaticDropdown({ + displayName: 'Match Existing Contact', + description: + 'If "Yes", will try to find an existing record using Email and Full Name and update it.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + contactId: Property.Number({ + displayName: 'Contact ID', + description: 'Sperse Contact ID. Will be used for looking a client', + defaultValue: 0, + required: false, + }), + // fullname + fullName: Property.ShortText({ + displayName: 'Full Name', + description: "The contact's full name.", + required: false, + }), + namePrefix: Property.ShortText({ + displayName: 'Prefix', + description: 'The title used to address the contact.', + required: false, + }), + firstName: Property.ShortText({ + displayName: 'First Name', + description: 'Required if Last Name and Company Name fields are empty.', + required: true, + }), + middleName: Property.ShortText({ + displayName: 'Middle Name', + description: "The contact's middle name.", + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + description: 'Required if First Name and Company Name fields are empty.', + required: true, + }), + nickName: Property.ShortText({ + displayName: 'Nick Name', + description: "The contact's nick name.", + required: false, + }), + nameSuffix: Property.ShortText({ + displayName: 'Suffix', + description: 'Additional information about the contact e.g PhD.', + required: false, + }), + // personal info + gender: Property.StaticDropdown({ + displayName: 'Gender', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Male', + value: 'Male', + }, + { + label: 'Female', + value: 'Female', + }, + ], + }, + }), + dob: Property.ShortText({ + displayName: 'Date of Birth', + description: 'Valid date format YYYY-MM-DD or MM-DD-YYYY.', + required: false, + }), + bankCode: Property.ShortText({ + displayName: 'Bank Code', + description: "The contact's 4-letter personality code.", + required: false, + }), + ssn: Property.ShortText({ + displayName: 'SSN', + description: "The contact's social security number.", + required: false, + }), + // business info + companyName: Property.ShortText({ + displayName: 'Company Name', + description: + "Name of the contact's company (This field is mandatory if the First Name and Last Name fields are empty).", + required: false, + }), + jobTitle: Property.ShortText({ + displayName: 'Job Title', + description: "The contact's job title.", + required: false, + }), + industry: Property.ShortText({ + displayName: 'Industry', + description: "The company's industry.", + required: false, + }), + // email + workEmail1: Property.LongText({ + displayName: 'Work Email', + description: "The contact's work email.", + required: false, + }), + email1: Property.LongText({ + displayName: 'Personal Email', + description: "The contact's personal email.", + required: false, + }), + email2: Property.LongText({ + displayName: 'Other email', + description: "The contact's additional email.", + required: false, + }), + // phone + workPhone1: Property.ShortText({ + displayName: 'Work Phone', + description: "The contact's work/primary phone number.", + required: false, + }), + homePhone: Property.ShortText({ + displayName: 'Home Phone', + description: "The contact's home phone number.", + required: false, + }), + mobilePhone: Property.ShortText({ + displayName: 'Mobile Phone', + description: "The contact's mobile phone number.", + required: false, + }), + // links + webSiteUrl: Property.ShortText({ + displayName: 'Website', + description: "The contact's company website URL.", + required: false, + }), + linkedInUrl: Property.ShortText({ + displayName: 'LinkedIn', + description: "The contact's LinkedIn profile id.", + required: false, + }), + photoUrl: Property.ShortText({ + displayName: 'Photo URL', + description: "The contact's person photo URL.", + required: false, + }), + // Full Address + street: Property.ShortText({ + displayName: 'Street', + description: + "The contact's full street address (can include apartment or unit number).", + required: false, + }), + addressLine2: Property.ShortText({ + displayName: 'Address 2', + required: false, + }), + city: Property.ShortText({ + displayName: 'City', + description: "The contact's city of residence.", + required: false, + }), + stateName: Property.ShortText({ + displayName: 'State Name', + description: "The contact's state of residence.", + required: false, + }), + stateId: Property.ShortText({ + displayName: 'State Code', + description: "The contact's state code.", + required: false, + }), + zip: Property.ShortText({ + displayName: 'Zip Code', + description: "The contact's zip/postal code.", + required: false, + }), + countryName: Property.ShortText({ + displayName: 'Country Name', + description: "The contact's country of residence.", + required: false, + }), + countryId: Property.ShortText({ + displayName: 'Country Code', + description: "The contact's country code.", + required: false, + }), + // content + experience: Property.LongText({ + displayName: 'Content', + description: "The contact's professional experience.", + required: false, + }), + profileSummary: Property.LongText({ + displayName: 'Profile Summary', + description: "The contact's profile summary.", + required: false, + }), + notes: Property.LongText({ + displayName: 'notes', + description: 'Additional notes about the contact', + required: false, + }), + followUpDate: Property.ShortText({ + displayName: 'Follow Up Date', + description: + 'Valid date format YYYY-MM-DD HH:MM:SS. If date is defined then new Follow Up Task will be created for this contact', + required: false, + }), + assignedUser: Property.ShortText({ + displayName: 'Assigned User', + description: + 'Preferably, Sperse User Email should be passed as it is unique within Sperse account but User Name can be also passed', + required: false, + }), + leadDealAmount: Property.Number({ + displayName: 'Deal Amount', + description: 'Estimated deal/opportunity amount.', + required: false, + }), + // tracking info + leadSource: Property.ShortText({ + displayName: 'Source Code', + description: + 'The first known source the contact used to find your website. You can set this automatically and update manually later.', + required: false, + }), + channelId: Property.ShortText({ + displayName: 'Channel Code', + description: 'The channel/medium the contact used to find your website.', + required: false, + }), + affiliateCode: Property.ShortText({ + displayName: 'Affiliate Code', + description: + 'The affiliate/referer partner through which the contact signed up.', + required: false, + }), + refererURL: Property.ShortText({ + displayName: 'Referer URL', + description: + 'The webpage where the contact clicked a link that sent them to your website.', + required: false, + }), + entryUrl: Property.ShortText({ + displayName: 'Entry URL', + description: + 'The first page of visit through which the contact visited your website.', + required: false, + }), + }, + async run(context) { + const contact = { + importType: context.propsValue.importType, + matchExisting: context.propsValue.matchExisting, + contactId: context.propsValue.contactId, + personalInfo: { + fullName: { + namePrefix: context.propsValue.namePrefix, + firstName: context.propsValue.firstName, + middleName: context.propsValue.middleName, + lastName: context.propsValue.lastName, + nameSuffix: context.propsValue.nameSuffix, + nickName: context.propsValue.nickName, + }, + doB: context.propsValue.dob, + mobilePhone: context.propsValue.mobilePhone, + homePhone: context.propsValue.homePhone, + ssn: context.propsValue.ssn, + bankCode: context.propsValue.bankCode, + email1: context.propsValue.email1, + email2: context.propsValue.email2, + gender: context.propsValue.gender, + fullAddress: { + street: context.propsValue.street, + addressLine2: context.propsValue.addressLine2, + city: context.propsValue.city, + stateName: context.propsValue.stateName, + stateId: context.propsValue.stateId, + zip: context.propsValue.zip, + countryName: context.propsValue.countryName, + countryId: context.propsValue.countryId, + }, + webSiteUrl: context.propsValue.webSiteUrl, + linkedInUrl: context.propsValue.linkedInUrl, + photoUrl: context.propsValue.photoUrl, + + experience: context.propsValue.experience, + profileSummary: context.propsValue.profileSummary, + }, + + businessInfo: { + companyName: context.propsValue.companyName, + jobTitle: context.propsValue.jobTitle, + industry: context.propsValue.industry, + workPhone1: context.propsValue.workPhone1, + workEmail1: context.propsValue.workEmail1, + }, + + assignedUser: context.propsValue.assignedUser, + followUpDate: context.propsValue.followUpDate, + notes: context.propsValue.notes, + leadDealAmount: context.propsValue.leadDealAmount, + + leadSource: context.propsValue.leadSource, + channelId: context.propsValue.channelId, + affiliateCode: context.propsValue.affiliateCode, + refererUrl: context.propsValue.refererURL, + entryUrl: context.propsValue.entryUrl, + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.base_url}/api/services/CRM/Import/ImportContact`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...contact, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/chargekeep/src/lib/actions/add-or-update-subscription.ts b/packages/pieces/community/chargekeep/src/lib/actions/add-or-update-subscription.ts new file mode 100644 index 0000000..84d079e --- /dev/null +++ b/packages/pieces/community/chargekeep/src/lib/actions/add-or-update-subscription.ts @@ -0,0 +1,110 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { chargekeepAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const addOrUpdateSubscription = createAction({ + name: 'addOrUpdateSubscription', + displayName: 'Add or Update Subscription', + description: 'Creates a new subscription.', + auth: chargekeepAuth, + props: { + contactId: Property.Number({ + displayName: 'Contact ID', + defaultValue: 0, + required: false, + }), + productId: Property.Number({ + displayName: 'Product ID', + required: false, + }), + contactXref: Property.ShortText({ + displayName: 'External Contact ID', + description: + 'ContactXref have to be specified and correct to look up the correct contact', + required: false, + }), + productCode: Property.ShortText({ + displayName: 'Product Code', + description: + 'Product Code (Unique product identifier). ProductCode have to be specified and correct to look up the correct product', + required: true, + }), + paymentPeriodType: Property.StaticDropdown({ + displayName: 'Payment Period Type', + description: + 'The chosen Period Type has to be set for the Product on Sperse side', + required: true, + defaultValue: 'Monthly', + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + ], + }, + }), + hasRecurringBilling: Property.StaticDropdown({ + displayName: 'Is it Recurring Billing', + required: false, + defaultValue: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + }, + async run(context) { + const subscription = { + contactId: context.propsValue.contactId, + contactXref: context.propsValue.contactXref, + products: [ + { + productId: context.propsValue.productId, + productCode: context.propsValue.productCode, + paymentPeriodType: context.propsValue.paymentPeriodType, + hasRecurringBilling: context.propsValue.hasRecurringBilling, + }, + ], + productId: context.propsValue.productId, + productCode: context.propsValue.productCode, + paymentPeriodType: context.propsValue.paymentPeriodType, + hasRecurringBilling: context.propsValue.hasRecurringBilling, + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: `${context.auth.base_url}/api/services/CRM/OrderSubscription/Update`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...subscription, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/chargekeep/src/lib/actions/create-invoice.ts b/packages/pieces/community/chargekeep/src/lib/actions/create-invoice.ts new file mode 100644 index 0000000..f92bf1a --- /dev/null +++ b/packages/pieces/community/chargekeep/src/lib/actions/create-invoice.ts @@ -0,0 +1,474 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { chargekeepAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +// Helper function to get current date in the required format +const getCurrentDateInISOFormat = () => { + return new Date().toISOString(); // Returns current date in format: YYYY-MM-DDTHH:MM:SSZ +}; + +export const createInvoice = createAction({ + name: 'createInvoice', + displayName: 'Create Invoice', + description: 'Creates a new invoice in the CRM.', + auth: chargekeepAuth, + props: { + contactId: Property.Number({ + displayName: 'Contact ID', + description: 'Sperse Contact ID. Will be used for looking a client', + defaultValue: 0, + required: false, + }), + contactXref: Property.ShortText({ + displayName: 'External Contact ID', + description: + 'External Contact Reference (ID) . Will be used for looking a client', + required: false, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + required: true, + defaultValue: 'Draft', + options: { + disabled: false, + options: [ + { + label: 'Draft', + value: 'Draft', + }, + { + label: 'Final', + value: 'Final', + }, + { + label: 'Paid', + value: 'Paid', + }, + { + label: 'Sent', + value: 'Sent', + }, + ], + }, + }), + invoiceNo: Property.Number({ + displayName: 'Invoice No.', + defaultValue: 0, + required: true, + }), + date: Property.DateTime({ + displayName: 'Date of the Invoice', + description: 'should be like this: 2024-06-11T11:11:41Z', + defaultValue: getCurrentDateInISOFormat(), + required: true, + }), + dueDate: Property.DateTime({ + displayName: 'Due Date of the Invoice', + description: 'should be like this: 2024-06-11T11:11:41Z', + required: true, + }), + currencyId: Property.StaticDropdown({ + displayName: 'Currency Id', + required: true, + defaultValue: 'USD', + options: { + disabled: false, + options: [ + { + label: 'USD', + value: 'USD', + }, + { + label: 'JPY', + value: 'JPY', + }, + { + label: 'IND', + value: 'IND', + }, + { + label: 'EUR', + value: 'EUR', + }, + { + label: 'GBP', + value: 'GBP', + }, + { + label: 'AUD', + value: 'AUD', + }, + { + label: 'CAD', + value: 'CAD', + }, + { + label: 'CHF', + value: 'CHF', + }, + { + label: 'CNY', + value: 'CNY', + }, + { + label: 'SEK', + value: 'SEK', + }, + { + label: 'NZD', + value: 'NZD', + }, + ], + }, + }), + grandTotal: Property.Number({ + displayName: 'Grand Total', + defaultValue: 0, + required: false, + }), + discountTotal: Property.Number({ + displayName: 'Discount Total', + defaultValue: 0, + required: false, + }), + shippingTotal: Property.Number({ + displayName: 'Shipping Total', + defaultValue: 0, + required: false, + }), + taxTotal: Property.Number({ + displayName: 'Tax Total', + defaultValue: 0, + required: false, + }), + // billing + bCompany: Property.ShortText({ + displayName: 'Billing Company', + required: false, + }), + bFirstName: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + bLastName: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + bPhone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + bEmail: Property.ShortText({ + displayName: 'Email', + required: false, + }), + bCountryId: Property.ShortText({ + displayName: 'Country Id', + required: false, + }), + bStateId: Property.ShortText({ + displayName: 'State Id', + required: false, + }), + bStateName: Property.ShortText({ + displayName: 'State Name', + required: false, + }), + bCity: Property.ShortText({ + displayName: 'City', + required: false, + }), + bZip: Property.ShortText({ + displayName: 'Zip', + required: false, + }), + bAddress1: Property.LongText({ + displayName: 'Billing Address 1', + required: false, + }), + bAddress2: Property.LongText({ + displayName: 'Billing Address 2', + required: false, + }), + // shipping + sCompany: Property.ShortText({ + displayName: 'Shipping Company', + required: false, + }), + sFirstName: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + sLastName: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + sPhone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + sEmail: Property.ShortText({ + displayName: 'Email', + required: false, + }), + sCountryId: Property.ShortText({ + displayName: 'Country Id', + required: false, + }), + sStateId: Property.ShortText({ + displayName: 'State Id', + required: false, + }), + sStateName: Property.ShortText({ + displayName: 'State Name', + required: false, + }), + sCity: Property.ShortText({ + displayName: 'City', + required: false, + }), + sZip: Property.ShortText({ + displayName: 'Zip', + required: false, + }), + sAddress1: Property.LongText({ + displayName: 'Shipping Address 1', + required: false, + }), + sAddress2: Property.LongText({ + displayName: 'Shipping Address 2', + required: false, + }), + // + note: Property.LongText({ + displayName: 'Invoice Note', + required: false, + }), + invoiceDescription: Property.LongText({ + displayName: 'Invoice Description', + required: true, + }), + // line + quantity: Property.Number({ + displayName: 'Quantity', + defaultValue: 0, + required: true, + }), + rate: Property.Number({ + displayName: 'Rate', + defaultValue: 0, + required: false, + }), + itemTotal: Property.Number({ + displayName: 'Total Item Price', + defaultValue: 0, + required: false, + }), + commissionableAmount: Property.Number({ + displayName: 'Commissionable Amount', + defaultValue: 0, + required: false, + }), + unitId: Property.StaticDropdown({ + displayName: 'Unit Id', + required: false, + defaultValue: 'Unit', + options: { + disabled: false, + options: [ + { + label: 'Unit', + value: 'Unit', + }, + { + label: 'Day', + value: 'Day', + }, + { + label: 'Month', + value: 'Month', + }, + { + label: 'Year', + value: 'Year', + }, + { + label: 'Hour', + value: 'Hour', + }, + { + label: 'Kilogram', + value: 'Kilogram', + }, + { + label: 'Zone', + value: 'Zone', + }, + { + label: 'Package', + value: 'Package', + }, + { + label: 'Pound', + value: 'Pound', + }, + { + label: 'Piece', + value: 'Piece', + }, + { + label: 'Feet', + value: 'Feet', + }, + { + label: 'Custom', + value: 'Custom', + }, + ], + }, + }), + productCode: Property.ShortText({ + displayName: 'Product Code', + description: 'Product Code. We will look up the product', + required: false, + }), + itemDescription: Property.ShortText({ + displayName: 'Description', + required: false, + }), + sortOrder: Property.Number({ + displayName: 'Sort Order', + defaultValue: 0, + required: false, + }), + // transactions + transactionDate: Property.DateTime({ + displayName: 'Transaction Date', + description: 'should be like this: 2024-06-11T11:11:41Z', + defaultValue: getCurrentDateInISOFormat(), + required: true, + }), + transactionDescription: Property.ShortText({ + displayName: 'Transaction Description', + required: false, + }), + amount: Property.Number({ + displayName: 'Amount', + defaultValue: 0, + required: false, + }), + gatewayName: Property.ShortText({ + displayName: 'Gateway Name', + required: false, + }), + gatewayTransactionId: Property.ShortText({ + displayName: 'Gateway Transaction Id', + required: false, + }), + historicalData: Property.StaticDropdown({ + displayName: 'Historical Data', + description: + 'Pass true if this is not actual transaction. Should be False by default', + required: true, + defaultValue: false, + options: { + disabled: false, + options: [ + { + label: 'true', + value: true, + }, + { + label: 'false', + value: false, + }, + ], + }, + }), + }, + async run(context) { + // construct + const invoice = { + contactId: context.propsValue.contactId, + contactXref: context.propsValue.contactXref, + status: context.propsValue.status, + number: context.propsValue.invoiceNo, + date: context.propsValue.date, + dueDate: context.propsValue.dueDate, + currencyId: context.propsValue.currencyId, + grandTotal: context.propsValue.grandTotal, + discountTotal: context.propsValue.discountTotal, + shippingTotal: context.propsValue.shippingTotal, + taxTotal: context.propsValue.taxTotal, + billingAddress: { + countryId: context.propsValue.bCountryId, + stateId: context.propsValue.bStateId, + stateName: context.propsValue.bStateName, + city: context.propsValue.bCity, + zip: context.propsValue.bZip, + address1: context.propsValue.bAddress1, + address2: context.propsValue.bAddress2, + firstName: context.propsValue.bFirstName, + lastName: context.propsValue.bLastName, + company: context.propsValue.bCompany, + email: context.propsValue.bEmail, + phone: context.propsValue.bPhone, + }, + shippingAddress: { + countryId: context.propsValue.sCountryId, + stateId: context.propsValue.sStateId, + stateName: context.propsValue.sStateName, + city: context.propsValue.sCity, + zip: context.propsValue.sZip, + address1: context.propsValue.sAddress1, + address2: context.propsValue.sAddress2, + firstName: context.propsValue.sFirstName, + lastName: context.propsValue.sLastName, + company: context.propsValue.sCompany, + email: context.propsValue.sEmail, + phone: context.propsValue.sPhone, + }, + description: context.propsValue.invoiceDescription, + note: context.propsValue.note, + lines: [ + { + quantity: context.propsValue.quantity, + rate: context.propsValue.rate, + total: context.propsValue.itemTotal, + commissionableAmount: context.propsValue.commissionableAmount, + unitId: context.propsValue.unitId, + productCode: context.propsValue.productCode, + description: context.propsValue.itemDescription, + sortOrder: context.propsValue.sortOrder, + }, + ], + transactions: [ + { + date: context.propsValue.transactionDate, + description: context.propsValue.transactionDescription, + amount: context.propsValue.amount, + gatewayName: context.propsValue.gatewayName, + gatewayTransactionId: context.propsValue.gatewayTransactionId, + }, + ], + historicalData: context.propsValue.historicalData, + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.base_url}/api/services/CRM/Import/ImportInvoice`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...invoice, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/chargekeep/src/lib/actions/create-product.ts b/packages/pieces/community/chargekeep/src/lib/actions/create-product.ts new file mode 100644 index 0000000..30f8b71 --- /dev/null +++ b/packages/pieces/community/chargekeep/src/lib/actions/create-product.ts @@ -0,0 +1,318 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { chargekeepAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const createProduct = createAction({ + name: 'createProduct', + displayName: 'Create Product', + description: 'Creates a new product in the CRM', + auth: chargekeepAuth, + props: { + productType: Property.StaticDropdown({ + displayName: 'Product Type', + required: true, + defaultValue: 'General', + options: { + disabled: false, + options: [ + { + label: 'General', + value: 'General', + }, + { + label: 'Event', + value: 'Event', + }, + { + label: 'Subscription', + value: 'Subscription', + }, + { + label: 'Digital', + value: 'Digital', + }, + ], + }, + }), + name: Property.ShortText({ + displayName: 'Product Name', + required: true, + }), + code: Property.ShortText({ + displayName: 'SKU', + required: true, + description: 'Sku is the product code', + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + descriptionHTML: Property.LongText({ + displayName: 'Description HTML', + required: false, + description: 'Javascript and media tags are not allowed', + }), + logoURL: Property.LongText({ + displayName: 'Logo Url', + required: false, + }), + groupName: Property.ShortText({ + displayName: 'Group Name', + required: false, + }), + price: Property.Number({ + displayName: 'Price', + required: false, + description: 'Required for General , Digital and Event Product Type', + defaultValue: 0, + }), + currencyId: Property.StaticDropdown({ + displayName: 'Currency Id', + required: false, + defaultValue: 'USD', + options: { + disabled: false, + options: [ + { + label: 'USD', + value: 'USD', + }, + { + label: 'JPY', + value: 'JPY', + }, + { + label: 'IND', + value: 'IND', + }, + { + label: 'EUR', + value: 'EUR', + }, + { + label: 'GBP', + value: 'GBP', + }, + { + label: 'AUD', + value: 'AUD', + }, + { + label: 'CAD', + value: 'CAD', + }, + { + label: 'CHF', + value: 'CHF', + }, + { + label: 'CNY', + value: 'CNY', + }, + { + label: 'SEK', + value: 'SEK', + }, + { + label: 'NZD', + value: 'NZD', + }, + ], + }, + }), + unit: Property.StaticDropdown({ + displayName: 'Unit', + required: false, + description: 'Required for General and Digital Product Type', + defaultValue: 'Day', + options: { + disabled: false, + options: [ + { + label: 'Day', + value: 'Day', + }, + { + label: 'Feet', + value: 'Feet', + }, + { + label: 'Hour', + value: 'Hour', + }, + { + label: 'Kilogram', + value: 'Kilogram', + }, + { + label: 'Pound', + value: 'Pound', + }, + { + label: 'Month', + value: 'Month', + }, + { + label: 'Package', + value: 'Package', + }, + { + label: 'Piece', + value: 'Piece', + }, + { + label: 'Unit', + value: 'Unit', + }, + { + label: 'Year', + value: 'Year', + }, + { + label: 'Zone', + value: 'Zone', + }, + { + label: 'Custom', + value: 'Custom', + }, + ], + }, + }), + frequency: Property.StaticDropdown({ + displayName: 'Payment Cycle', + required: false, + description: 'Required for General and Digital Product Type', + defaultValue: 'Monthly', + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + { + label: 'OneTime', + value: 'OneTime', + }, + { + label: 'Custom', + value: 'Custom', + }, + ], + }, + }), + fee: Property.Number({ + displayName: 'Subscription fee', + required: false, + defaultValue: 0, + }), + cycles: Property.Number({ + displayName: 'No of cycles', + required: false, + description: 'Required for all except LifeTime or OneTime plan', + defaultValue: 0, + }), + signupFee: Property.Number({ + displayName: 'Signup fee', + required: false, + description: 'Required for all except LifeTime or OneTime plan', + defaultValue: 0, + }), + customPeriodType: Property.StaticDropdown({ + displayName: 'Custom Period Type', + required: false, + description: 'Required for Custom or OneTime Plan', + defaultValue: 'Days', + options: { + disabled: false, + options: [ + { + label: 'Days', + value: 'Days', + }, + { + label: 'Weeks', + value: 'Weeks', + }, + { + label: 'Months', + value: 'Months', + }, + { + label: 'Years', + value: 'Years', + }, + ], + }, + }), + customPeriodCount: Property.Number({ + displayName: 'Custom No of Period', + required: false, + description: 'Required for Custom or OneTime Plan', + defaultValue: 0, + }), + trialDayCount: Property.Number({ + displayName: 'Custom No of Period', + required: false, + description: 'Required for OneTime plan', + defaultValue: 0, + }), + gracePeriodDayCount: Property.Number({ + displayName: 'Grace Period Count', + required: false, + defaultValue: 0, + }), + }, + async run(context) { + const product = { + code: context.propsValue.code, + name: context.propsValue.name, + logoUrl: context.propsValue.logoURL, + description: context.propsValue.description, + descriptionHtml: context.propsValue.descriptionHTML, + groupName: context.propsValue.groupName, + type: context.propsValue.productType, + price: context.propsValue.price, + currencyId: context.propsValue.currencyId, + unit: context.propsValue.unit, + productSubscriptionOptions: [ + { + frequency: context.propsValue.frequency, + signupFee: context.propsValue.signupFee, + fee: context.propsValue.fee, + trialDayCount: context.propsValue.trialDayCount, + customPeriodCount: context.propsValue.customPeriodCount, + customPeriodType: context.propsValue.customPeriodType, + cycles: context.propsValue.cycles, + gracePeriodDayCount: context.propsValue.gracePeriodDayCount, + }, + ], + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.base_url}/api/services/CRM/Import/ImportProduct`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...product, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/chargekeep/src/lib/actions/get-contact-details.ts b/packages/pieces/community/chargekeep/src/lib/actions/get-contact-details.ts new file mode 100644 index 0000000..02ab3a0 --- /dev/null +++ b/packages/pieces/community/chargekeep/src/lib/actions/get-contact-details.ts @@ -0,0 +1,73 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { chargekeepAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getContactDetails = createAction({ + name: 'getContactDetails', + displayName: 'Get Contact Details', + description: 'Get Contact Details', + auth: chargekeepAuth, + props: { + contactId: Property.Number({ + displayName: 'Contact Id', + required: false, + }), + xref: Property.ShortText({ + displayName: 'Contact Xref', + required: false, + }), + affiliateCode: Property.ShortText({ + displayName: 'Affiliate Code', + required: false, + }), + userId: Property.Number({ + displayName: 'User Id', + required: false, + description: 'Id of the logged in user (not contact id)', + }), + userEmail: Property.LongText({ + displayName: 'User Email', + required: false, + description: 'Email of the logged in user', + }), + }, + async run(context) { + const contact = { + contactId: context.propsValue.contactId, + xref: context.propsValue.xref, + affiliateCode: context.propsValue.affiliateCode, + userId: context.propsValue.userId, + userEmail: context.propsValue.userEmail, + }; + + // Filter out keys with undefined values + const filteredContact: Record = Object.fromEntries( + Object.entries(contact).filter(([, value]) => value !== undefined) + ) as Record; // Cast to ensure it's the correct type + + // Create query parameters from the filtered contact object + const queryParams = new URLSearchParams( + Object.entries(filteredContact).map(([key, value]) => [ + key, + String(value), + ]) + ); + + // Send GET request with query parameters in the URL + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${ + context.auth.base_url + }/api/services/CRM/Contact/GetContactData?${queryParams.toString()}`, + headers: { + 'api-key': context.auth.api_key, + 'Content-Type': 'application/json', + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/chargekeep/src/lib/common/common.ts b/packages/pieces/community/chargekeep/src/lib/common/common.ts new file mode 100644 index 0000000..ed076f0 --- /dev/null +++ b/packages/pieces/community/chargekeep/src/lib/common/common.ts @@ -0,0 +1,46 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +export const chargekeepCommon = { + subscribeWebhook: async ( + eventName: string, + baseUrl: string, + apiKey: string, + webhookUrl: string + ) => { + const request = { + method: HttpMethod.POST, + url: `${baseUrl}/api/services/Platform/Event/Subscribe`, + headers: { + 'api-key': apiKey, + 'Content-Type': 'application/json', + }, + body: { + eventName: eventName, + targetUrl: webhookUrl, + }, + }; + + const res = await httpClient.sendRequest(request); + + const { id: webhookId } = res.body.result; + + return webhookId; + }, + + unsubscribeWebhook: async ( + baseUrl: string, + apiKey: string, + webhookId: number + ) => { + const request = { + method: HttpMethod.POST, + url: `${baseUrl}/api/services/Platform/Event/Unsubscribe?id=${webhookId}`, + headers: { + 'api-key': apiKey, + 'Content-Type': 'application/json', + }, + }; + + return await httpClient.sendRequest(request); + }, +}; diff --git a/packages/pieces/community/chargekeep/src/lib/triggers/new-lead.ts b/packages/pieces/community/chargekeep/src/lib/triggers/new-lead.ts new file mode 100644 index 0000000..5852245 --- /dev/null +++ b/packages/pieces/community/chargekeep/src/lib/triggers/new-lead.ts @@ -0,0 +1,234 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { chargekeepAuth } from '../..'; +import { chargekeepCommon } from '../common/common'; + +export const newLead = createTrigger({ + auth: chargekeepAuth, + name: 'new_lead', + displayName: 'New Lead', + description: 'Triggers when a new lead is created', + props: {}, + type: TriggerStrategy.WEBHOOK, + sampleData: { + id: 100500, + group: 'P', + personalInfo: { + fullName: { + namePrefix: 'Mr', + firstName: 'John', + middleName: 'G', + lastName: 'Smith', + nameSuffix: 'Jr', + nickName: 'Johnny', + }, + doB: '1992-06-30T00:00:00Z', + mobilePhone: '+12057899877', + mobilePhoneExt: '123', + homePhone: '+12057985632', + homePhoneExt: '123', + phone1: null, + phoneExt1: null, + phone2: null, + phoneExt2: null, + preferredToD: 'Anytime', + timeZone: 'Pacific Standard Time', + ssn: '123456456', + bankCode: 'BANK', + email1: 'personalemail1@gmal.com', + email2: 'personalemail2@hotmail.com', + email3: 'personalemail3@yahoo.com', + email4: null, + email5: null, + drivingLicense: 'ASDF4566G', + drivingLicenseState: 'NY', + isActiveMilitaryDuty: true, + gender: 'Male', + fullAddress: { + street: '999-901 Emancipation Ave', + addressLine2: null, + neighborhood: null, + city: 'Houston', + stateName: 'Texas', + stateId: 'TX', + zip: '77003', + countryName: 'United States of America', + countryId: 'US', + startDate: '2018-04-30T00:00:00Z', + isOwner: true, + }, + fullAddress2: null, + fullAddress3: null, + isUSCitizen: true, + webSiteUrl: 'www.myprofile.com', + facebookUrl: 'www.fb.com/j.smith', + linkedInUrl: 'https://www.linkedin.com/j.smith', + instagramUrl: 'https://www.instagram.com/j.smith', + twitterUrl: 'https://twitter.com/j.smith', + googlePlusUrl: 'https://googleplus.com/j.smith', + angelListUrl: 'https://angel.co/', + zoomUrl: 'https://zoom.com', + otherLinkUrl: 'https://otherlink.com', + photoUrl: 'https://www.myprofile.com/profile-pictures/myphoto.png', + experience: + 'Improvements made to two existing products, designed five completely new products for four customers.', + profileSummary: + 'Highly skilled and results-oriented professional with solid academic preparation holding a Juris Doctor degree and extensive experience in intelligence and special operations seeks position in risk management.', + interests: ['World Economy', 'Baseball'], + affiliateCode: 'PA001', + isActive: null, + customFields: { + customField1: null, + customField2: null, + customField3: null, + customField4: null, + customField5: null, + }, + }, + businessInfo: { + companyName: 'MyCompany LLC', + organizationType: 'Inc', + jobTitle: 'Chief Executive Officer', + isEmployed: true, + employmentStartDate: '2021-12-30T00:00:00Z', + employeeCount: 500, + dateFounded: '2021-06-30T00:00:00Z', + ein: '35-8896524', + annualRevenue: 6000000.0, + industry: 'Sport and Entertainment', + companyPhone: '+12057784563', + companyPhoneExt: '123', + companyFaxNumber: '+12057324598', + companyEmail: 'companyemail1@company.com', + companyFullAddress: { + street: '1500 Canton st', + addressLine2: null, + neighborhood: null, + city: 'Dallas', + stateName: 'Texas', + stateId: 'TX', + zip: '75201', + countryName: 'United States of America', + countryId: 'US', + startDate: '2018-04-30T00:00:00Z', + isOwner: false, + }, + companyWebSiteUrl: 'www.mycompany.com', + companyFacebookUrl: 'www.fb.com/mycompany', + companyLinkedInUrl: 'https://www.linkedin.com/mycompany', + companyInstagramUrl: 'https://www.instagram.com/mycompany', + companyTwitterUrl: 'https://twitter.com/mycompany', + companyGooglePlusUrl: 'https://googleplus.com/mycompany', + companyCrunchbaseUrl: 'https://www.crunchbase.com/mycompany', + companyBBBUrl: 'https://www.bbb.org/en/us/overview-of-bbb-ratings', + companyPinterestUrl: 'https://www.pinterest.com/mycompany', + companyDomainUrl: 'https://www.domain.com/mycompany', + companyAlexaUrl: 'https://www.alexa.com/mycompany', + companyOpenCorporatesUrl: 'https://www.opencorporates.com/mycompany', + companyGlassDoorUrl: 'https://www.classdoor.com/mycompany', + companyTrustpilotUrl: 'https://www.trustpilot.com/mycompany', + companyFollowersUrl: 'https://www.followers.com/mycompany', + companyYoutubeUrl: 'https://www.youtube.com/mycompany', + companyYelpUrl: 'https://www.yelp.com/mycompany', + companyRSSUrl: 'https://www.rss.com/mycompany', + companyNavUrl: 'https://www.nav.com/mycompany', + companyAngelListUrl: 'https://www.angelist.com/mycompany', + companyCalendlyUrl: 'https://www.calendly.com/mycompany', + companyZoomUrl: 'https://zoom.com/mycompany', + companyOtherLinkUrl: 'https://www.otherlink.com/mycompany', + companyLogoUrl: 'https://www.mycompany.com/images/companylogo/logo.png', + workPhone1: '+12057412354', + workPhone1Ext: '123', + workPhone2: '+12057741236', + workPhone2Ext: '123', + workEmail1: 'workemail1@company.com', + workEmail2: 'workemail2@company.com', + workEmail3: 'workemail3@company.com', + workFullAddress: { + street: '1502-1702 Strawberry Rd', + addressLine2: null, + neighborhood: null, + city: 'Pasadena', + stateName: 'Texas', + stateId: 'TX', + zip: '77502', + countryName: 'United States of America', + countryId: 'US', + startDate: '2020-05-30T00:00:00Z', + isOwner: false, + }, + affiliateCode: 'CA0001', + }, + dateCreated: '2022-06-23T13:32:54.9451405Z', + ipAddress: '192.168.0.1', + trackingInfo: { + sourceCode: 'Code', + channelCode: 'ChannelCode', + affiliateCode: '414CODE', + refererUrl: 'http://www.refererurl.com/referpage.html', + entryUrl: 'https://entryurl.com/start-now/?ref=wow', + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36', + clientIp: '192.168.0.1', + }, + applicationInfo: { + applicationId: '771XYZ23234G', + applicantId: '771XYZSD', + applicantUserId: 10229, + clickId: '2B69283E-9433-4CE6-A24E-D8809C050B70', + requestedLoanAmount: 89.0, + incomeType: 'Benefits', + netMonthlyIncome: 899.0, + payFrequency: 'Monthly', + payNextDate: '2022-07-30T13:32:54.945358Z', + bankName: 'Bank Name', + bankAccountType: 'Checking', + monthsAtBank: 24, + bankAccountNumber: '26005325777412', + creditScoreRating: 'Excellent', + loanReason: 'AutoPurchase', + creditCardDebtAmount: 56898.0, + }, + classificationInfo: { + rating: '10', + lists: ['My List 1', 'My List 2', 'My List 3'], + tags: ['My Tag 1', 'My Tag 2', 'My Tag 3'], + partnerTypeName: 'My Partner Type', + }, + eventTime: '2022-06-30T13:32:54Z', + }, + + async onEnable(context) { + const webhookId = await chargekeepCommon.subscribeWebhook( + 'LeadCreated', + context.auth.base_url, + context.auth.api_key, + context.webhookUrl + ); + + await context.store?.put('_new_lead_trigger', { + webhookId: webhookId, + }); + }, + + async onDisable(context) { + const response = await context.store?.get( + '_new_lead_trigger' + ); + + if (response !== null && response !== undefined) { + await chargekeepCommon.unsubscribeWebhook( + context.auth.base_url, + context.auth.api_key, + response.webhookId + ); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); + +interface WebhookInformation { + webhookId: number; +} diff --git a/packages/pieces/community/chargekeep/src/lib/triggers/new-payment.ts b/packages/pieces/community/chargekeep/src/lib/triggers/new-payment.ts new file mode 100644 index 0000000..df97e95 --- /dev/null +++ b/packages/pieces/community/chargekeep/src/lib/triggers/new-payment.ts @@ -0,0 +1,86 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { chargekeepAuth } from '../..'; +import { chargekeepCommon } from '../common/common'; + +export const newPayment = createTrigger({ + auth: chargekeepAuth, + name: 'new_payment', + displayName: 'New Payment', + description: 'Triggers when a new payment is created', + props: {}, + type: TriggerStrategy.WEBHOOK, + sampleData: { + contact: { + email: 's@gmail.com', + fullName: 'Test User', + id: 636, + phone: '98877676565', + }, + invoice: { + currencyId: 'USD', + date: '2024-06-10T00:00:00Z', + description: 'just a description', + discountTotal: 0, + grandTotal: 100, + id: 457, + number: 'INV - 20240610 - 746C', + shippingTotal: 0, + taxTotal: 0, + lines: [ + { + description: 'Me Spacial', + productId: 810, + quantity: 1, + rate: 100, + total: 100, + unitId: 'Month', + }, + ], + }, + transaction: { + amount: 100, + currencyId: 'USD', + date: '2024-06-10T09:36:01.832Z', + gatewayName: null, + gatewayTransactionId: null, + id: 403, + isSuccessful: true, + type: 'Sale', + }, + eventTime: '2024-06-10T09:36:08', + eventType: 'Payment.Created', + }, + async onEnable(context) { + const webhookId = await chargekeepCommon.subscribeWebhook( + 'Payment.Created', + context.auth.base_url, + context.auth.api_key, + context.webhookUrl + ); + + await context.store?.put('_new_payment_trigger', { + webhookId: webhookId, + }); + }, + + async onDisable(context) { + const response = await context.store?.get( + '_new_payment_trigger' + ); + + if (response !== null && response !== undefined) { + await chargekeepCommon.unsubscribeWebhook( + context.auth.base_url, + context.auth.api_key, + response.webhookId + ); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); + +interface WebhookInformation { + webhookId: number; +} diff --git a/packages/pieces/community/chargekeep/src/lib/triggers/new-subscription.ts b/packages/pieces/community/chargekeep/src/lib/triggers/new-subscription.ts new file mode 100644 index 0000000..57ceb95 --- /dev/null +++ b/packages/pieces/community/chargekeep/src/lib/triggers/new-subscription.ts @@ -0,0 +1,62 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { chargekeepCommon } from '../common/common'; +import { chargekeepAuth } from '../..'; + +export const newSubscription = createTrigger({ + auth: chargekeepAuth, + name: 'new_subscription', + displayName: 'New Subscription', + description: 'Triggers when a new subscription is created', + props: {}, + type: TriggerStrategy.WEBHOOK, + sampleData: { + eventType: 'Subscription.CreatedOrUpdated', + contactId: 3994, + fullname: 'Frank Micheal', + email: 'tray@sperse.com', + id: '536778', + name: 'Subscription_Name', + startDate: '2024-06-30T09:29:43.6271352Z', + endDate: '2025-06-30T09:29:43.6271352Z', + amount: 100, + frequency: 'Annual', + trialDayCount: '4', + gracePeriodCount: '10', + statusId: 'A', + cancelationReason: '', + eventTime: '2024-09-06T00:29:07', + }, + async onEnable(context) { + const webhookId = await chargekeepCommon.subscribeWebhook( + 'Subscription.CreatedOrUpdated', + context.auth.base_url, + context.auth.api_key, + context.webhookUrl + ); + + await context.store?.put('_new_subscription_trigger', { + webhookId: webhookId, + }); + }, + + async onDisable(context) { + const response = await context.store?.get( + '_new_subscription_trigger' + ); + + if (response !== null && response !== undefined) { + await chargekeepCommon.unsubscribeWebhook( + context.auth.base_url, + context.auth.api_key, + response.webhookId + ); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); + +interface WebhookInformation { + webhookId: number; +} diff --git a/packages/pieces/community/chargekeep/tsconfig.json b/packages/pieces/community/chargekeep/tsconfig.json new file mode 100644 index 0000000..e6bf96e --- /dev/null +++ b/packages/pieces/community/chargekeep/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + }, +} diff --git a/packages/pieces/community/chargekeep/tsconfig.lib.json b/packages/pieces/community/chargekeep/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/chargekeep/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/chatbase/.eslintrc.json b/packages/pieces/community/chatbase/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/chatbase/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/chatbase/README.md b/packages/pieces/community/chatbase/README.md new file mode 100644 index 0000000..37944c9 --- /dev/null +++ b/packages/pieces/community/chatbase/README.md @@ -0,0 +1,7 @@ +# pieces-chatbase + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-chatbase` to build the library. diff --git a/packages/pieces/community/chatbase/package.json b/packages/pieces/community/chatbase/package.json new file mode 100644 index 0000000..68675cf --- /dev/null +++ b/packages/pieces/community/chatbase/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-chatbase", + "version": "0.0.1" +} diff --git a/packages/pieces/community/chatbase/project.json b/packages/pieces/community/chatbase/project.json new file mode 100644 index 0000000..799906d --- /dev/null +++ b/packages/pieces/community/chatbase/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-chatbase", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/chatbase/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/chatbase", + "tsConfig": "packages/pieces/community/chatbase/tsconfig.lib.json", + "packageJson": "packages/pieces/community/chatbase/package.json", + "main": "packages/pieces/community/chatbase/src/index.ts", + "assets": [ + "packages/pieces/community/chatbase/*.md", + { + "input": "packages/pieces/community/chatbase/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/chatbase/src/index.ts b/packages/pieces/community/chatbase/src/index.ts new file mode 100644 index 0000000..602f82c --- /dev/null +++ b/packages/pieces/community/chatbase/src/index.ts @@ -0,0 +1,40 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createChatbotAction } from './lib/actions/create-chatbot'; +import { listChatbotsAction } from './lib/actions/list-all-chatbots'; +import { searchConversationsAction } from './lib/actions/search-conversations-by-query'; +import { sendPromptToChatbotAction } from './lib/actions/send-prompt-to-chatbot'; + +const markdownDescription = `You can get your API key from your [Chatbase Account](https://www.chatbase.co/dashboard).`; + +export const chatbaseAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: markdownDescription, + required: true, +}); + +export const chatbase = createPiece({ + displayName: 'Chatbase', + description: 'Build and manage AI chatbots with custom sources.', + auth: chatbaseAuth, + logoUrl: 'https://cdn.activepieces.com/pieces/chatbase.png', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + authors: ['krushnarout'], + actions: [ + createChatbotAction, + sendPromptToChatbotAction, + searchConversationsAction, + listChatbotsAction, + createCustomApiCallAction({ + auth: chatbaseAuth, + baseUrl: () => 'https://www.chatbase.co/api/v1', + authMapping: async (auth) => { + return { + Authorization: `Bearer ${auth}`, + }; + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/chatbase/src/lib/actions/create-chatbot.ts b/packages/pieces/community/chatbase/src/lib/actions/create-chatbot.ts new file mode 100644 index 0000000..437d9e4 --- /dev/null +++ b/packages/pieces/community/chatbase/src/lib/actions/create-chatbot.ts @@ -0,0 +1,38 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { chatbaseAuth } from '../../index'; +import { makeRequest } from '../common'; + +export const createChatbotAction = createAction({ + auth: chatbaseAuth, + name: 'create_chatbot', + displayName: 'Create Chatbot', + description: 'Creates a new chatbot.', + props: { + chatbotName: Property.ShortText({ + displayName: 'Chatbot Name', + required: true, + }), + sourceText: Property.LongText({ + displayName: 'Source Text', + description: 'Optional text data for training the chatbot.', + required: false, + }), + }, + async run(context) { + const { chatbotName, sourceText } = context.propsValue; + const apiKey = context.auth as string; + + const body: Record = { + chatbotName, + }; + + if (sourceText) { + body['sourceText'] = sourceText; + } + + const response = await makeRequest(apiKey, HttpMethod.POST, '/create-chatbot', body); + + return response; + }, +}); diff --git a/packages/pieces/community/chatbase/src/lib/actions/list-all-chatbots.ts b/packages/pieces/community/chatbase/src/lib/actions/list-all-chatbots.ts new file mode 100644 index 0000000..f84c2cc --- /dev/null +++ b/packages/pieces/community/chatbase/src/lib/actions/list-all-chatbots.ts @@ -0,0 +1,20 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { chatbaseAuth } from '../../index'; +import { makeRequest } from '../common'; + +export const listChatbotsAction = createAction({ + auth: chatbaseAuth, + name: 'list_chatbots', + displayName: 'List All Chatbots', + description: 'Retrieves a list of all chatbots.', + props: {}, + + async run(context) { + const apiKey = context.auth as string; + + const response = await makeRequest(apiKey, HttpMethod.GET, '/get-chatbots'); + + return response; + }, +}); diff --git a/packages/pieces/community/chatbase/src/lib/actions/search-conversations-by-query.ts b/packages/pieces/community/chatbase/src/lib/actions/search-conversations-by-query.ts new file mode 100644 index 0000000..bf89571 --- /dev/null +++ b/packages/pieces/community/chatbase/src/lib/actions/search-conversations-by-query.ts @@ -0,0 +1,83 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { chatbaseAuth } from '../../index'; +import { makeRequest } from '../common'; +import { chatbotIdDropdown } from '../common/props'; + +export const searchConversationsAction = createAction({ + auth: chatbaseAuth, + name: 'search_conversations', + displayName: 'Search Conversations by Query', + description: 'Searches for conversations from a specific chatbot.', + props: { + chatbotId: chatbotIdDropdown, + filteredSources: Property.StaticMultiSelectDropdown({ + displayName: 'Sources', + description: 'Filter by one or more conversation sources.', + required: false, + options: { + disabled: false, + options: [ + { label: 'API', value: 'API' }, + { label: 'Chatbase site', value: 'Chatbase site' }, + { label: 'Instagram', value: 'Instagram' }, + { label: 'Messenger', value: 'Messenger' }, + { label: 'Slack', value: 'Slack' }, + { label: 'Unspecified', value: 'Unspecified' }, + { label: 'WhatsApp', value: 'WhatsApp' }, + { label: 'Widget or Iframe', value: 'Widget or Iframe' }, + ], + }, + }), + startDate: Property.DateTime({ + displayName: 'Start Date', + required: false, + }), + endDate: Property.DateTime({ + displayName: 'End Date', + required: false, + }), + page: Property.Number({ + displayName: 'Page', + description: 'Pagination page number (default = 1)', + required: false, + }), + size: Property.Number({ + displayName: 'Page Size', + description: 'Number of results per page (default = 10, max = 100)', + required: false, + }), + }, + + async run(context) { + const { chatbotId, filteredSources, startDate, endDate, page, size } = context.propsValue; + + const apiKey = context.auth as string; + + const queryParams = new URLSearchParams({ chatbotId }); + + if (filteredSources?.length) { + queryParams.append('filteredSources', filteredSources.join(',')); + } + if (startDate) { + queryParams.append('startDate', startDate.toString().split('T')[0]); + } + if (endDate) { + queryParams.append('endDate', endDate.toString().split('T')[0]); + } + if (page) { + queryParams.append('page', page.toString()); + } + if (size) { + queryParams.append('size', size.toString()); + } + + const response = await makeRequest( + apiKey, + HttpMethod.GET, + `/get-conversations?${queryParams.toString()}`, + ); + + return response; + }, +}); diff --git a/packages/pieces/community/chatbase/src/lib/actions/send-prompt-to-chatbot.ts b/packages/pieces/community/chatbase/src/lib/actions/send-prompt-to-chatbot.ts new file mode 100644 index 0000000..b1d9d00 --- /dev/null +++ b/packages/pieces/community/chatbase/src/lib/actions/send-prompt-to-chatbot.ts @@ -0,0 +1,93 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { chatbaseAuth } from '../../index'; +import { makeRequest } from '../common'; +import { chatbotIdDropdown } from '../common/props'; + +export const sendPromptToChatbotAction = createAction({ + auth: chatbaseAuth, + name: 'message_chatbot', + displayName: 'Send Prompt to Chatbot', + description: 'Sends a prompt to the chatbot to generate a response.', + props: { + chatbotId: chatbotIdDropdown, + message: Property.LongText({ + displayName: 'Prompt', + required: true, + }), + + temperature: Property.Number({ + displayName: 'Temperature', + description: 'Higher values = more random output. Between 0 and 1.', + required: false, + defaultValue: 0, + }), + conversationId: Property.ShortText({ + displayName: 'Conversation ID', + description: 'Unique ID for saving this conversation in Chatbase dashboard.', + required: false, + }), + model: Property.StaticDropdown({ + displayName: 'Model (Optional)', + required: false, + options: { + options: [ + { label: 'o4-mini', value: 'o4-mini' }, + { label: 'o3', value: 'o3' }, + { label: 'gpt-4', value: 'gpt-4' }, + { label: 'gpt-4o', value: 'gpt-4o' }, + { label: 'gpt-4o-mini', value: 'gpt-4o-mini' }, + { label: 'gpt-4.1-mini', value: 'gpt-4.1-mini' }, + { label: 'gpt-4.1-nano', value: 'gpt-4.1-nano' }, + { label: 'gpt-4-turbo', value: 'gpt-4-turbo' }, + { label: 'o3-mini', value: 'o3-mini' }, + { label: 'gpt-4.1', value: 'gpt-4.1' }, + { label: 'gpt-4.5', value: 'gpt-4.5' }, + { label: 'claude-sonnet-4', value: 'claude-sonnet-4' }, + { label: 'claude-3-7-sonnet', value: 'claude-3-7-sonnet' }, + { label: 'claude-3-5-sonnet', value: 'claude-3-5-sonnet' }, + { label: 'claude-3-opus', value: 'claude-3-opus' }, + { label: 'claude-opus-4', value: 'claude-opus-4' }, + { label: 'claude-3-haiku', value: 'claude-3-haiku' }, + { label: 'gemini-2.0-flash', value: 'gemini-2.0-flash' }, + { label: 'gemini-1.5-flash', value: 'gemini-1.5-flash' }, + { label: 'gemini-1.5-pro', value: 'gemini-1.5-pro' }, + { label: 'gemini-2.0-pro', value: 'gemini-2.0-pro' }, + { label: 'command-r', value: 'command-r' }, + { label: 'command-r-plus', value: 'command-r-plus' }, + { label: 'DeepSeek-V3', value: 'DeepSeek-V3' }, + { label: 'DeepSeek-R1', value: 'DeepSeek-R1' }, + { + label: 'Llama-4-Scout-17B-16E-Instruct', + value: 'Llama-4-Scout-17B-16E-Instruct', + }, + { + label: 'Llama-4-Maverick-17B-128E-Instruct-FP8', + value: 'Llama-4-Maverick-17B-128E-Instruct-FP8', + }, + { label: 'grok-3', value: 'grok-3' }, + { label: 'grok-3-mini', value: 'grok-3-mini' }, + ], + }, + }), + }, + + async run(context) { + const { chatbotId, message, temperature, model, conversationId } = context.propsValue; + + const apiKey = context.auth as string; + + const payload: Record = { + chatbotId, + messages: [{ content: message, role: 'user' }], + }; + + if (temperature !== undefined) payload['temperature'] = temperature; + if (conversationId) payload['conversationId'] = conversationId; + if (model) payload['model'] = model; + + const response = await makeRequest(apiKey, HttpMethod.POST, '/chat', payload); + + return response; + }, +}); diff --git a/packages/pieces/community/chatbase/src/lib/common/index.ts b/packages/pieces/community/chatbase/src/lib/common/index.ts new file mode 100644 index 0000000..4423809 --- /dev/null +++ b/packages/pieces/community/chatbase/src/lib/common/index.ts @@ -0,0 +1,22 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +export async function makeRequest( + apiKey: string, + method: HttpMethod, + path: string, + body?: unknown, +) { + const url = `https://www.chatbase.co/api/v1${path}`; + + const response = await httpClient.sendRequest({ + method, + url, + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + body, + }); + + return response.body; +} diff --git a/packages/pieces/community/chatbase/src/lib/common/props.ts b/packages/pieces/community/chatbase/src/lib/common/props.ts new file mode 100644 index 0000000..a6e9330 --- /dev/null +++ b/packages/pieces/community/chatbase/src/lib/common/props.ts @@ -0,0 +1,31 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { DropdownOption, Property } from '@activepieces/pieces-framework'; +import { makeRequest } from './index'; + +export const chatbotIdDropdown = Property.Dropdown({ + displayName: 'Chatbot', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first.', + options: [], + }; + } + + const apiKey = auth as string; + const response = await makeRequest(apiKey, HttpMethod.GET, '/get-chatbots'); + + const options: DropdownOption[] = response.chatbots.map((chatbot: any) => ({ + label: chatbot.chatbotName, + value: chatbot.chatbotId, + })); + + return { + disabled: false, + options, + }; + }, +}); diff --git a/packages/pieces/community/chatbase/tsconfig.json b/packages/pieces/community/chatbase/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/chatbase/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/chatbase/tsconfig.lib.json b/packages/pieces/community/chatbase/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/chatbase/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/circle/.eslintrc.json b/packages/pieces/community/circle/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/circle/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/circle/README.md b/packages/pieces/community/circle/README.md new file mode 100644 index 0000000..2197f6b --- /dev/null +++ b/packages/pieces/community/circle/README.md @@ -0,0 +1,7 @@ +# pieces-circle + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-circle` to build the library. diff --git a/packages/pieces/community/circle/package.json b/packages/pieces/community/circle/package.json new file mode 100644 index 0000000..19184c7 --- /dev/null +++ b/packages/pieces/community/circle/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-circle", + "version": "0.0.1" +} diff --git a/packages/pieces/community/circle/project.json b/packages/pieces/community/circle/project.json new file mode 100644 index 0000000..1b811bb --- /dev/null +++ b/packages/pieces/community/circle/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-circle", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/circle/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/circle", + "tsConfig": "packages/pieces/community/circle/tsconfig.lib.json", + "packageJson": "packages/pieces/community/circle/package.json", + "main": "packages/pieces/community/circle/src/index.ts", + "assets": [ + "packages/pieces/community/circle/*.md", + { + "input": "packages/pieces/community/circle/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/circle/src/index.ts b/packages/pieces/community/circle/src/index.ts new file mode 100644 index 0000000..eb9d0ed --- /dev/null +++ b/packages/pieces/community/circle/src/index.ts @@ -0,0 +1,39 @@ +import { createPiece } from '@activepieces/pieces-framework'; +import { newPostCreated } from './lib/triggers/new-post'; +import { newMemberAdded } from './lib/triggers/new-member-added'; +import { createPost } from './lib/actions/create-post'; +import { createComment } from './lib/actions/create-comment'; +import { addMemberToSpace } from './lib/actions/add-member-to-space'; +import { findMemberByEmail } from './lib/actions/find-member-by-email'; +import { getPostDetailsAction } from './lib/actions/get-post-details'; +import { getMemberDetails } from './lib/actions/get-member-details'; +import { circleAuth } from './lib/common/auth'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { BASE_URL } from './lib/common'; + +export const circle = createPiece({ + displayName: 'Circle', + logoUrl: 'https://cdn.activepieces.com/pieces/circle.png', + description: 'Circle.so is a platform for creating and managing communities.', + auth: circleAuth, + minimumSupportedRelease: '0.36.1', + authors: ['onyedikachi-david', 'kishanprmr'], + actions: [ + createPost, + createComment, + addMemberToSpace, + findMemberByEmail, + getPostDetailsAction, + getMemberDetails, + createCustomApiCallAction({ + auth: circleAuth, + baseUrl: () => BASE_URL, + authMapping: async (auth) => { + return { + Authorization: `Bearer ${auth}`, + }; + }, + }), + ], + triggers: [newPostCreated, newMemberAdded], +}); diff --git a/packages/pieces/community/circle/src/lib/actions/add-member-to-space.ts b/packages/pieces/community/circle/src/lib/actions/add-member-to-space.ts new file mode 100644 index 0000000..ed1f2b9 --- /dev/null +++ b/packages/pieces/community/circle/src/lib/actions/add-member-to-space.ts @@ -0,0 +1,55 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { circleAuth } from '../common/auth'; +import { BASE_URL, spaceIdDropdown } from '../common'; + +interface AddMemberToSpacePayload { + space_id: number; + email: string; +} + +export const addMemberToSpace = createAction({ + auth: circleAuth, + name: 'add_member_to_space', + displayName: 'Add Member to Space', + description: 'Add an existing member to a specific space by their email.', + props: { + space_id: spaceIdDropdown, + email: Property.ShortText({ + displayName: 'Member Email', + description: 'The email address of the member to add to the space.', + required: true, + }), + }, + async run(context) { + const { space_id, email } = context.propsValue; + + if (space_id === undefined) { + throw new Error('Space ID is undefined, but it is a required field.'); + } + if (email === undefined) { + throw new Error('Email is undefined, but it is a required field.'); + } + + const payload: AddMemberToSpacePayload = { + space_id: space_id, + email: email, + }; + + const response = await httpClient.sendRequest<{ + message?: string; + success?: boolean; + error_details?: unknown; + }>({ + method: HttpMethod.POST, + url: `${BASE_URL}/space_members`, + body: payload, + headers: { + Authorization: `Bearer ${context.auth}`, + 'Content-Type': 'application/json', + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/circle/src/lib/actions/create-comment.ts b/packages/pieces/community/circle/src/lib/actions/create-comment.ts new file mode 100644 index 0000000..d745a8d --- /dev/null +++ b/packages/pieces/community/circle/src/lib/actions/create-comment.ts @@ -0,0 +1,73 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { spaceIdDropdown, postIdDropdown, BASE_URL } from '../common'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { circleAuth } from '../common/auth'; +import { isNil } from '@activepieces/shared'; + +interface CreateCommentPayload { + post_id: number; + body: string; + parent_comment_id?: number; + skip_notifications?: boolean; +} + +export const createComment = createAction({ + auth: circleAuth, + name: 'create_comment', + displayName: 'Create Comment', + description: 'Creates a new comment on a post.', + props: { + space_id: spaceIdDropdown, + post_id: postIdDropdown, + body: Property.LongText({ + displayName: 'Comment Body', + description: 'The content of the comment.', + required: true, + }), + parent_comment_id: Property.Number({ + displayName: 'Parent Comment ID (Optional)', + description: 'ID of the comment to reply to. Leave empty if not a reply.', + required: false, + }), + skip_notifications: Property.Checkbox({ + displayName: 'Skip Notifications', + description: 'Skip sending notifications for this comment?', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { post_id, body, parent_comment_id, skip_notifications } = context.propsValue; + + if (post_id === undefined) { + throw new Error('Post ID is required but was not provided.'); + } + if (body === undefined) { + throw new Error('Comment body is required but was not provided.'); + } + + const payload: CreateCommentPayload = { + post_id: post_id, + body: body, + }; + + if (!isNil(parent_comment_id)) { + payload.parent_comment_id = parent_comment_id; + } + if (skip_notifications !== undefined) { + payload.skip_notifications = skip_notifications; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/comments`, + body: payload, + headers: { + Authorization: `Bearer ${context.auth}`, + 'Content-Type': 'application/json', + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/circle/src/lib/actions/create-post.ts b/packages/pieces/community/circle/src/lib/actions/create-post.ts new file mode 100644 index 0000000..097e94b --- /dev/null +++ b/packages/pieces/community/circle/src/lib/actions/create-post.ts @@ -0,0 +1,176 @@ +import { Property, createAction } from "@activepieces/pieces-framework"; +import { httpClient, HttpMethod } from "@activepieces/pieces-common"; +import { circleAuth } from "../common/auth"; +import { BASE_URL, spaceIdDropdown } from "../common"; + +// Interface for the TipTap body structure (simplified for payload) +interface TipTapPayloadBody { + type: string; // "doc" + content: any[]; // Content structure for TipTap +} + +// Interface for the full payload to create a post +interface CreatePostPayload { + space_id: number; + name: string; + status?: string; + tiptap_body: { body: TipTapPayloadBody }; + slug?: string; + cover_image?: string; + internal_custom_html?: string; + is_truncation_disabled?: boolean; + is_comments_closed?: boolean; + is_comments_enabled?: boolean; + is_liking_enabled?: boolean; + hide_meta_info?: boolean; + hide_from_featured_areas?: boolean; + meta_title?: string; + meta_description?: string; + opengraph_title?: string; + opengraph_description?: string; + published_at?: string; // ISO 8601 string + created_at?: string; // ISO 8601 string - usually set by server + topics?: number[]; + skip_notifications?: boolean; + is_pinned?: boolean; + user_email?: string; + user_id?: number; +} + +export const createPost = createAction({ + auth: circleAuth, + name: 'create_post', + displayName: 'Create Post', + description: 'Creates a new post in a specific space.', + props: { + space_id: spaceIdDropdown, + name: Property.ShortText({ + displayName: 'Post Name/Title', + description: 'The title of the post.', + required: true, + }), + text_body: Property.LongText({ + displayName: 'Post Body (Plain Text)', + description: "Simple plain text content for the post. Used if 'Tiptap Body JSON' is not provided.", + required: false, + }), + tiptap_body_json: Property.Json({ + displayName: 'Tiptap Body JSON', + description: "Full TipTap JSON object for the post body. If provided, 'Post Body (Plain Text)' is ignored.", + required: false, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + description: 'The status of the post.', + required: false, + options: { + options: [ + { label: 'Draft', value: 'draft' }, + { label: 'Published', value: 'published' }, + { label: 'Scheduled', value: 'scheduled' }, + ] + }, + defaultValue: 'published', + }), + published_at: Property.DateTime({ + displayName: 'Published At (for Scheduled)', + description: "If status is 'scheduled', provide the future date and time for publishing.", + required: false, + }), + is_comments_enabled: Property.Checkbox({ + displayName: 'Enable Comments', + description: 'Allow comments on this post?', + required: false, + defaultValue: true, + }), + skip_notifications: Property.Checkbox({ + displayName: 'Skip Notifications', + description: 'Prevent notifications from being sent for this post?', + required: false, + defaultValue: false, + }), + user_email: Property.ShortText({ + displayName: 'Post As User Email (Optional)', + description: 'Email of an existing community member to create this post as. If empty, posts as the authenticated admin.', + required: false, + }) + }, + async run(context) { + const { + space_id, name, text_body, tiptap_body_json, + status, published_at, is_comments_enabled, + skip_notifications, user_email + } = context.propsValue; + + if (space_id === undefined) { + throw new Error("Space ID is undefined, but it is a required field."); + } + if (name === undefined) { + throw new Error("Post Name/Title is undefined, but it is a required field."); + } + + let finalTiptapBody: { body: TipTapPayloadBody }; + + if (tiptap_body_json && typeof tiptap_body_json === 'object' && (tiptap_body_json as any).body) { + finalTiptapBody = tiptap_body_json as { body: TipTapPayloadBody }; + } else if (text_body) { + finalTiptapBody = { + body: { + type: "doc", + content: [ + { + type: "paragraph", + content: [ + { type: "text", text: text_body } + ] + } + ] + } + }; + } else if (!text_body && !tiptap_body_json) { + finalTiptapBody = { + body: { type: "doc", content: [{ type: "paragraph", content: [{ type: "text", text: "" }] }] } + }; + } else { + throw new Error("Invalid body input. Provide either 'Post Body (Plain Text)' or a valid 'Tiptap Body JSON'. If both are empty, an empty post will be created."); + } + + const payload: CreatePostPayload = { + space_id: space_id, + name: name, + tiptap_body: finalTiptapBody, + status: status ?? 'published', + }; + + if (published_at && payload.status === 'scheduled') { + payload.published_at = new Date(published_at).toISOString(); + } else if (payload.status === 'scheduled' && !published_at) { + // It's an error to have scheduled status without a published_at date. + // However, the API might handle this. For now, we'll let it pass, + // but this could be a validation point. + } + + if (is_comments_enabled !== undefined) { + payload.is_comments_enabled = is_comments_enabled; + payload.is_comments_closed = !is_comments_enabled; + } + if (skip_notifications !== undefined) { + payload.skip_notifications = skip_notifications; + } + if (user_email) { + payload.user_email = user_email; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/posts`, + body: payload, + headers: { + "Authorization": `Bearer ${context.auth}`, + "Content-Type": "application/json" + } + }); + + return response.body; + } +}); diff --git a/packages/pieces/community/circle/src/lib/actions/find-member-by-email.ts b/packages/pieces/community/circle/src/lib/actions/find-member-by-email.ts new file mode 100644 index 0000000..177191a --- /dev/null +++ b/packages/pieces/community/circle/src/lib/actions/find-member-by-email.ts @@ -0,0 +1,40 @@ +import { Property, createAction } from "@activepieces/pieces-framework"; +import { BASE_URL } from "../common"; +import { httpClient, HttpMethod } from "@activepieces/pieces-common"; +import { circleAuth } from "../common/auth"; +import { CommunityMemberDetails } from "../common/types"; + +export const findMemberByEmail = createAction({ + auth: circleAuth, + name: 'find_member_by_email', + displayName: 'Find Member by Email', + description: 'Finds a community member by their email address.', + props: { + email: Property.ShortText({ + displayName: 'Email', + description: 'The email address of the member to find.', + required: true, + }), + }, + async run(context) { + const { email } = context.propsValue; + + if (email === undefined) { + throw new Error("Email is undefined, but it is a required field."); + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${BASE_URL}/community_members/search`, + queryParams: { + email: email, + }, + headers: { + "Authorization": `Bearer ${context.auth}`, + "Content-Type": "application/json" + }, + }); + + return response.body; + } +}); diff --git a/packages/pieces/community/circle/src/lib/actions/get-member-details.ts b/packages/pieces/community/circle/src/lib/actions/get-member-details.ts new file mode 100644 index 0000000..8908b9f --- /dev/null +++ b/packages/pieces/community/circle/src/lib/actions/get-member-details.ts @@ -0,0 +1,31 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { BASE_URL, communityMemberIdDropdown } from '../common'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { circleAuth } from '../common/auth'; +import { CommunityMemberDetails } from '../common/types'; + +export const getMemberDetails = createAction({ + auth: circleAuth, + name: 'get_member_details', + displayName: 'Get Member Details', + description: 'Fetches the full profile details for a specific community member.', + props: { + member_id: communityMemberIdDropdown, + }, + async run(context) { + const { member_id } = context.propsValue; + if (member_id === undefined) { + throw new Error('Member ID is undefined, but it is a required field.'); + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${BASE_URL}/community_members/${member_id}`, + headers: { + Authorization: `Bearer ${context.auth}`, + 'Content-Type': 'application/json', + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/circle/src/lib/actions/get-post-details.ts b/packages/pieces/community/circle/src/lib/actions/get-post-details.ts new file mode 100644 index 0000000..0d0d5ac --- /dev/null +++ b/packages/pieces/community/circle/src/lib/actions/get-post-details.ts @@ -0,0 +1,33 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { BASE_URL, spaceIdDropdown, postIdDropdown } from '../common'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { circleAuth } from '../common/auth'; +import { PostDetails } from '../common/types'; + +export const getPostDetailsAction = createAction({ + auth: circleAuth, + name: 'get_post_details', + displayName: 'Get Post Details', + description: 'Retrieves the complete details of a specific post.', + props: { + space_id: spaceIdDropdown, + post_id: postIdDropdown, + }, + async run(context) { + const { post_id } = context.propsValue; + + if (post_id === undefined) { + throw new Error('Post ID is undefined, but it is a required field.'); + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${BASE_URL}/posts/${post_id}`, + headers: { + Authorization: `Bearer ${context.auth}`, + 'Content-Type': 'application/json', + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/circle/src/lib/common/auth.ts b/packages/pieces/community/circle/src/lib/common/auth.ts new file mode 100644 index 0000000..17380dc --- /dev/null +++ b/packages/pieces/community/circle/src/lib/common/auth.ts @@ -0,0 +1,7 @@ +import { PieceAuth } from "@activepieces/pieces-framework"; + +export const circleAuth = PieceAuth.SecretText({ + displayName: 'API Token', + description: `You can obtain your API token by navigating to **Settings->Developers->Tokens**.`, + required: true, +}); \ No newline at end of file diff --git a/packages/pieces/community/circle/src/lib/common/index.ts b/packages/pieces/community/circle/src/lib/common/index.ts new file mode 100644 index 0000000..70a05be --- /dev/null +++ b/packages/pieces/community/circle/src/lib/common/index.ts @@ -0,0 +1,117 @@ +import { Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { ListBasicPostsResponse, ListCommunityMembersResponse, ListSpacesResponse } from './types'; + +export const BASE_URL = 'https://app.circle.so/api/admin/v2'; + +export const spaceIdDropdown = Property.Dropdown({ + displayName: 'Space', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${BASE_URL}/spaces`, + headers: { + Authorization: `Bearer ${auth as string}`, + 'Content-Type': 'application/json', + }, + }); + if (response.status === 200) { + return { + disabled: false, + options: response.body.records.map((space) => ({ + label: space.name, + value: space.id, + })), + }; + } + return { + disabled: true, + placeholder: 'Error fetching spaces', + options: [], + }; + }, +}); + +export const postIdDropdown = Property.Dropdown({ + displayName: 'Post', + required: true, + refreshers: ['space_id'], + options: async ({ auth, space_id }) => { + if (!auth || !space_id) { + return { + disabled: true, + placeholder: !auth ? 'Please connect your account first' : 'Select a space first', + options: [], + }; + } + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${BASE_URL}/posts`, + headers: { + Authorization: `Bearer ${auth as string}`, + 'Content-Type': 'application/json', + }, + queryParams: { + space_id: (space_id as number).toString(), + status: 'all', // Fetch all posts for selection + }, + }); + if (response.status === 200) { + return { + disabled: false, + options: response.body.records.map((post) => ({ + label: post.name, + value: post.id, + })), + }; + } + return { + disabled: true, + placeholder: 'Error fetching posts or no posts found in space.', + options: [], + }; + }, +}); + +export const communityMemberIdDropdown = Property.Dropdown({ + displayName: 'Community Member', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { disabled: true, placeholder: 'Please authenticate first', options: [] }; + } + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${BASE_URL}/community_members`, + headers: { + Authorization: `Bearer ${auth as string}`, + 'Content-Type': 'application/json', + }, + queryParams: { status: 'all' }, + }); + if (response.status === 200 && response.body.records) { + return { + disabled: false, + options: response.body.records.map((member) => ({ + label: `${member.name} (${member.email})`, + value: member.id, + })), + }; + } + return { + disabled: true, + placeholder: 'Error fetching community members or no members found', + options: [], + }; + }, +}); diff --git a/packages/pieces/community/circle/src/lib/common/types.ts b/packages/pieces/community/circle/src/lib/common/types.ts new file mode 100644 index 0000000..244b746 --- /dev/null +++ b/packages/pieces/community/circle/src/lib/common/types.ts @@ -0,0 +1,269 @@ +export interface Space { + id: number; + name: string; +} + +export interface ListSpacesResponse { + records: Space[]; + page?: number; + per_page?: number; + has_next_page?: boolean; + count?: number; + page_count?: number; +} + +// Interface for individual post item based on 'List Basic Posts' records +export interface BasicPostFromList { + id: number; + status: string; + name: string; + slug: string; + comments_count: number; + hide_meta_info: boolean; + published_at: string; + created_at: string; + updated_at: string; + is_comments_enabled: boolean; + is_liking_enabled: boolean; + flagged_for_approval_at: string | null; + body: { + id: number; + name: string; // e.g., "body" + body: string; // HTML content snippet + record_type: string; // "Post" + record_id: number; + created_at: string; + updated_at: string; + }; + url: string; + space_name: string; + space_slug: string; + space_id: number; + user_id: number; + user_email: string; + user_name: string; + community_id: number; + user_avatar_url: string | null; + cover_image_url: string | null; + cover_image: string | null; + cardview_thumbnail_url: string | null; + cardview_thumbnail: string | null; + is_comments_closed: boolean; + custom_html: string | null; + likes_count: number; + member_posts_count: number; + member_comments_count: number; + member_likes_count: number; + topics: number[]; +} + +// Interface based on the 'List Basic Posts' API response +export interface ListBasicPostsResponse { + page: number; + per_page: number; + has_next_page: boolean; + count: number; + page_count: number; + records: BasicPostFromList[]; +} + +// --- Shared Member Profile Sub-Interfaces --- +export interface ProfileFieldChoice { + id: number; + value: string; +} + +export interface CommunityMemberProfileFieldChoice { + id: number; + profile_field_choice: ProfileFieldChoice; +} + +export interface CommunityMemberProfileFieldDetails { + id: number; + text: string | null; + textarea: string | null; + created_at: string; + updated_at: string; + display_value: string[] | null; + community_member_choices: CommunityMemberProfileFieldChoice[]; +} + +export interface ProfileFieldPage { + id: number; + name: string; + position: number; + visible: boolean; + created_at: string; + updated_at: string; +} + +export interface ProfileField { + id: number; + label: string; + field_type: string; + key: string; + placeholder: string | null; + description: string | null; + required: boolean; + platform_field: boolean; + created_at: string; + updated_at: string; + community_member_profile_field: CommunityMemberProfileFieldDetails | null; + number_options: any | null; + choices: ProfileFieldChoice[]; + pages: ProfileFieldPage[]; +} + +export interface MemberTag { + name: string; + id: number; +} + +export interface GamificationStats { + community_member_id: number; + total_points: number; + current_level: number; + current_level_name: string; + points_to_next_level: number; + level_progress: number; +} + +export interface CommunityMemberListItem { + id: number; + name: string; // Full name + email: string; + first_name: string | null; + last_name: string | null; + headline: string | null; + created_at: string; + updated_at: string; + community_id: number; + last_seen_at: string | null; + profile_confirmed_at: string | null; + profile_url: string; + public_uid: string; + avatar_url: string | null; + user_id: number; // This is the user_id associated with the community_member, not the community_member.id + active: boolean; + sso_provider_user_id: string | null; + accepted_invitation: string | null; + profile_fields: ProfileField[]; + flattened_profile_fields: Record; + member_tags: MemberTag[]; + posts_count: number; + comments_count: number; + gamification_stats: GamificationStats; +} +export interface ListCommunityMembersResponse { + page: number; + per_page: number; + has_next_page: boolean; + count: number; + page_count: number; + records: CommunityMemberListItem[]; +} + +export interface CommunityMemberDetails { + id: number; + first_name: string | null; + last_name: string | null; + headline: string | null; + created_at: string; + updated_at: string; + community_id: number; + last_seen_at: string | null; + profile_confirmed_at: string | null; + profile_url: string; + public_uid: string; + profile_fields: ProfileField[]; + flattened_profile_fields: Record; + avatar_url: string | null; + user_id: number; + name: string; + email: string; + accepted_invitation: string | null; + active: boolean; + sso_provider_user_id: string | null; + member_tags: MemberTag[]; + posts_count: number; + comments_count: number; + gamification_stats: GamificationStats; +} + + +interface PostBody { + id: number; + name: string; // "body" + body: string; // HTML content + record_type: string; // "Post" + record_id: number; + created_at: string; + updated_at: string; +} + +interface TipTapMark { + type: string; + attrs?: Record; // Example: { href: 'url' } for a link mark +} + +interface TipTapContentItem { + type: string; + text?: string; + marks?: TipTapMark[]; + attrs?: Record; + content?: TipTapContentItem[]; + circle_ios_fallback_text?: string; +} + +interface TipTapBody { + body: { + type: string; // "doc" + content: TipTapContentItem[]; + }; + circle_ios_fallback_text?: string; + attachments?: unknown[]; + inline_attachments?: unknown[]; + sgids_to_object_map?: Record; + format?: string; // "post" + community_members?: unknown[]; + entities?: unknown[]; + group_mentions?: unknown[]; + polls?: unknown[]; +} + +export interface PostDetails { + id: number; + status: string; + name: string; + slug: string; + comments_count: number; + hide_meta_info: boolean; + published_at: string; + created_at: string; + updated_at: string; + is_comments_enabled: boolean; + is_liking_enabled: boolean; + flagged_for_approval_at: string | null; + body: PostBody; + tiptap_body: TipTapBody; + url: string; + space_name: string; + space_slug: string; + space_id: number; + user_id: number; + user_email: string; + user_name: string; + community_id: number; + user_avatar_url: string | null; + cover_image_url: string | null; + cover_image: string | null; // This seems to be an identifier string + cardview_thumbnail_url: string | null; + cardview_thumbnail: string | null; // Also an identifier + is_comments_closed: boolean; + custom_html: string | null; + likes_count: number; + member_posts_count: number; + member_comments_count: number; + member_likes_count: number; + topics: number[]; +} \ No newline at end of file diff --git a/packages/pieces/community/circle/src/lib/triggers/new-member-added.ts b/packages/pieces/community/circle/src/lib/triggers/new-member-added.ts new file mode 100644 index 0000000..8ef7926 --- /dev/null +++ b/packages/pieces/community/circle/src/lib/triggers/new-member-added.ts @@ -0,0 +1,133 @@ +import { + PiecePropValueSchema, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { BASE_URL } from '../common'; +import { + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { circleAuth } from '../common/auth'; + +import dayjs from 'dayjs'; +import { ListCommunityMembersResponse } from '../common/types'; + +const polling: Polling, Record> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, lastFetchEpochMS }) { + let page = 1; + let hasMorePages = true; + let stopFetching = false; + + const members = []; + + do { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${BASE_URL}/community_members`, + queryParams: { + page: page.toString(), + per_page: '50', + status: 'all', + }, + headers: { + Authorization: `Bearer ${auth}`, + 'Content-Type': 'application/json', + }, + }); + + const items = response.body.records || []; + + for (const member of items) { + const publishedAt = dayjs(member.created_at).valueOf(); + + if (publishedAt < lastFetchEpochMS) { + stopFetching = true; + break; + } + + members.push(member); + } + + if (stopFetching || lastFetchEpochMS === 0) break; + + page++; + hasMorePages = response.body.has_next_page; + } while (hasMorePages); + + return members.map((member) => { + return { + epochMilliSeconds: dayjs(member.created_at).valueOf(), + data: member, + }; + }); + }, +}; + +export const newMemberAdded = createTrigger({ + auth: circleAuth, + name: 'new_member_added', + displayName: 'New Member Added', + description: 'Triggers when a new member is added to the community.', + props: {}, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + first_name: 'Gov.', + last_name: 'Loriann Barton', + headline: 'Sales Orchestrator', + created_at: '2024-09-03T16:20:19.814Z', + updated_at: '2024-09-03T16:20:19.826Z', + community_id: 1, + last_seen_at: null, + profile_confirmed_at: '2024-09-03T16:20:19.000Z', + id: 2, + profile_url: 'http://reynolds.circledev.net:31337/u/352c3aff', + public_uid: '352c3aff', + profile_fields: [], + flattened_profile_fields: { + profile_field_key_1: null, + }, + avatar_url: null, + user_id: 3, + name: 'Gov. Loriann Barton', + email: 'raul@nitzsche.org', + accepted_invitation: '2024-09-03 16:20:19 UTC', + active: true, + sso_provider_user_id: null, + member_tags: [], + posts_count: 0, + comments_count: 0, + gamification_stats: { + community_member_id: 2, + total_points: 0, + current_level: 1, + current_level_name: 'Level 1', + points_to_next_level: 50, + level_progress: 50, + }, + }, +}); diff --git a/packages/pieces/community/circle/src/lib/triggers/new-post.ts b/packages/pieces/community/circle/src/lib/triggers/new-post.ts new file mode 100644 index 0000000..0a4fee9 --- /dev/null +++ b/packages/pieces/community/circle/src/lib/triggers/new-post.ts @@ -0,0 +1,146 @@ +import { + PiecePropValueSchema, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { BASE_URL, spaceIdDropdown } from '../common'; +import { + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { circleAuth } from '../common/auth'; +import { ListBasicPostsResponse } from '../common/types'; +import dayjs from 'dayjs'; + +const polling: Polling, { space_id?: number }> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const spaceId = propsValue.space_id!; + + let page = 1; + let hasMorePages = true; + let stopFetching = false; + + const posts = []; + + do { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${BASE_URL}/posts`, + queryParams: { + space_id: spaceId.toString(), + status: 'published', + sort: 'latest', + page: page.toString(), + per_page: '60', + }, + headers: { + Authorization: `Bearer ${auth}`, + 'Content-Type': 'application/json', + }, + }); + + const items = response.body.records || []; + + for (const post of items) { + const publishedAt = dayjs(post.published_at).valueOf(); + + if (publishedAt < lastFetchEpochMS) { + stopFetching = true; + break; + } + + posts.push(post); + } + + if (stopFetching || lastFetchEpochMS === 0) break; + + page++; + hasMorePages = response.body.has_next_page; + } while (hasMorePages); + + return posts.map((post) => { + return { + epochMilliSeconds: dayjs(post.published_at).valueOf(), + data: post, + }; + }); + }, +}; + +export const newPostCreated = createTrigger({ + auth: circleAuth, + name: 'new_post_created', + displayName: 'New Post Created', + description: 'Triggers when a new post is created in a specific space.', + props: { + space_id: spaceIdDropdown, + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + id: 2, + status: 'published', + name: 'Second post', + slug: 'kiehn', + comments_count: 0, + hide_meta_info: false, + published_at: '2024-06-27T08:31:30.777Z', + created_at: '2024-06-27T08:31:30.781Z', + updated_at: '2024-06-27T08:31:30.784Z', + is_comments_enabled: true, + is_liking_enabled: true, + flagged_for_approval_at: null, + body: { + id: 2, + name: 'body', + body: '
Iusto sint asperiores sed.
', + record_type: 'Post', + record_id: 2, + created_at: '2024-06-27T08:31:30.000Z', + updated_at: '2024-06-27T08:31:30.000Z', + }, + url: 'http://dickinson.circledev.net:31337/c/post/kiehn', + space_name: 'post', + space_slug: 'post', + space_id: 1, + user_id: 6, + user_email: 'lyndon@frami.info', + user_name: 'Rory Wyman', + community_id: 1, + user_avatar_url: 'https://example.com/avatar.png', + cover_image_url: 'http://example.com/cover.jpeg', + cover_image: 'identifier-string', + cardview_thumbnail_url: 'http://example.com/thumbnail.jpeg', + cardview_thumbnail: 'identifier-string', + is_comments_closed: false, + custom_html: '
Click Me!
', + likes_count: 0, + member_posts_count: 2, + member_comments_count: 0, + member_likes_count: 0, + topics: [12, 43, 54], + }, +}); diff --git a/packages/pieces/community/circle/tsconfig.json b/packages/pieces/community/circle/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/circle/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/circle/tsconfig.lib.json b/packages/pieces/community/circle/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/circle/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/clarifai/.eslintrc.json b/packages/pieces/community/clarifai/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/clarifai/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/clarifai/README.md b/packages/pieces/community/clarifai/README.md new file mode 100644 index 0000000..cce8b57 --- /dev/null +++ b/packages/pieces/community/clarifai/README.md @@ -0,0 +1,7 @@ +# pieces-clarifai + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-clarifai` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/clarifai/package-lock.json b/packages/pieces/community/clarifai/package-lock.json new file mode 100644 index 0000000..b5ad57b --- /dev/null +++ b/packages/pieces/community/clarifai/package-lock.json @@ -0,0 +1,657 @@ +{ + "name": "@activepieces/piece-clarifai", + "version": "0.2.7", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-clarifai", + "version": "0.2.7", + "dependencies": { + "clarifai-nodejs-grpc": "11.3.3" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.3.tgz", + "integrity": "sha512-FTXHdOoPbZrBjlVLHuKbDZnsTxXv2BlHF57xw6LuThXacXvtkahEPED0CKMk6obZDf65Hv4k3z62eyPNpvinIg==", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/grpc-js/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.1.tgz", + "integrity": "sha512-3qx3IRjR9WPQKagdwrKjO3Gu8RgQR2qqw+1KnigWhoVjFqegIj1K3bP11sGqhxrO46/XL7lekuG4jmjL+4cLsw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.6.tgz", + "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "protobufjs": "^6.8.6" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "node_modules/@types/node": { + "version": "22.15.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.17.tgz", + "integrity": "sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw==", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/clarifai-nodejs-grpc": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/clarifai-nodejs-grpc/-/clarifai-nodejs-grpc-11.3.3.tgz", + "integrity": "sha512-CfL5zFTfpTZoUfNfVUNG+DxZP8TscJ6dncBTora4zkNlRm/E1K2e54xchxNLo8XQ4os8yINJ4f03ke1OekSIIA==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.4.2", + "@grpc/proto-loader": "^0.5.5", + "axios": "^1.8.4", + "google-protobuf": "^3.14.0", + "protobufjs": "^6.10.2" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/google-protobuf": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz", + "integrity": "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/protobufjs": { + "version": "6.11.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", + "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/packages/pieces/community/clarifai/package.json b/packages/pieces/community/clarifai/package.json new file mode 100644 index 0000000..cc0f55f --- /dev/null +++ b/packages/pieces/community/clarifai/package.json @@ -0,0 +1,7 @@ +{ + "name": "@activepieces/piece-clarifai", + "version": "0.2.7", + "dependencies": { + "clarifai-nodejs-grpc": "11.3.3" + } +} \ No newline at end of file diff --git a/packages/pieces/community/clarifai/project.json b/packages/pieces/community/clarifai/project.json new file mode 100644 index 0000000..dbd21fd --- /dev/null +++ b/packages/pieces/community/clarifai/project.json @@ -0,0 +1,63 @@ +{ + "name": "pieces-clarifai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/clarifai/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/clarifai", + "tsConfig": "packages/pieces/community/clarifai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/clarifai/package.json", + "main": "packages/pieces/community/clarifai/src/index.ts", + "assets": [ + "packages/pieces/community/clarifai/*.md", + { + "input": "packages/pieces/community/clarifai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + }, + "dependsOn": ["prebuild", "^build"] + }, + "prebuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/pieces/community/clarifai", + "command": "npm ci" + } + }, + "build-with-deps": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "nx run pieces-clarifai:prebuild", + "nx run pieces-clarifai:build", + "nx run pieces-clarifai:postbuild" + ], + "parallel": false + } + }, + "postbuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "dist/packages/pieces/community/clarifai", + "command": "npm install" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/clarifai/src/index.ts b/packages/pieces/community/clarifai/src/index.ts new file mode 100644 index 0000000..b530c67 --- /dev/null +++ b/packages/pieces/community/clarifai/src/index.ts @@ -0,0 +1,79 @@ +import { + HttpMethod, + createCustomApiCallAction, + httpClient, +} from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { clarifaiAskLLM } from './lib/actions/ask-llm'; +import { audioToTextModelPredictAction } from './lib/actions/call-audio-model'; +import { + imageToTextModelPredictAction, + visualClassifierModelPredictAction, +} from './lib/actions/call-image-model'; +import { postInputsAction } from './lib/actions/call-post-inputs'; +import { + textClassifierModelPredictAction, + textToTextModelPredictAction, +} from './lib/actions/call-text-model'; +import { workflowPredictAction } from './lib/actions/call-workflow'; +import { clarifaiGenerateIGM } from './lib/actions/generate-igm'; + +const markdownDescription = ` +Follow these instructions to get your Clarifai (Personal Access Token) PAT Key: +1. Go to the [security tab](https://clarifai.com/settings/security) in your Clarifai account and generate a new PAT token. +2. Copy the PAT token and paste it in the PAT Key field. +`; +export const clarifaiAuth = PieceAuth.SecretText({ + displayName: 'PAT Key', + description: markdownDescription, + required: true, + validate: async ({ auth }) => { + try { + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.clarifai.com/v2/models', + headers: { + Authorization: 'Key ' + auth, + }, + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: `Invalid PAT token\nerror:\n${e}`, + }; + } + }, +}); + +export const clarifai = createPiece({ + displayName: 'Clarifai', + description: 'AI-powered visual recognition', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/clarifai.png', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + authors: ["akatechis","zeiler","Salem-Alaa","kishanprmr","MoShizzle","abuaboud"], + auth: clarifaiAuth, + actions: [ + clarifaiAskLLM, + clarifaiGenerateIGM, + visualClassifierModelPredictAction, + textClassifierModelPredictAction, + imageToTextModelPredictAction, + textToTextModelPredictAction, + audioToTextModelPredictAction, + postInputsAction, + workflowPredictAction, + createCustomApiCallAction({ + baseUrl: () => 'https://api.clarifai.com/v2', // Replace with the actual base URL + auth: clarifaiAuth, + authMapping: async (auth) => ({ + Authorization: `Key ${auth}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/clarifai/src/lib/actions/ask-llm.ts b/packages/pieces/community/clarifai/src/lib/actions/ask-llm.ts new file mode 100644 index 0000000..d0eda28 --- /dev/null +++ b/packages/pieces/community/clarifai/src/lib/actions/ask-llm.ts @@ -0,0 +1,130 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { clarifaiAuth } from '../..'; +import { + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; + +export const clarifaiAskLLM = createAction({ + name: 'ask-llm', + displayName: 'Ask LLM', + description: + 'Send a prompt to any large language models (LLM) supported by clarifai.', + auth: clarifaiAuth, + props: { + models: Property.Dropdown({ + displayName: 'Model Id', + description: `The model which will generate the response. If the model is not listed, get the model id from the clarifai website. Example : 'GPT-4' you can get the model id from here [https://clarifai.com/openai/chat-completion/models/GPT-4](https://clarifai.com/openai/chat-completion/models/GPT-4)`, + refreshers: ['auth'], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please add an PAT key', + }; + } + const request: HttpRequest = { + method: HttpMethod.GET, + url: 'https://api.clarifai.com/v2/models?sort_by_star_count=true&use_cases=llm&filter_by_user_id=true&additional_fields=stars&per_page=24&page=1', + headers: { + Authorization: ('Key ' + auth) as string, + }, + }; + try { + const response = await httpClient.sendRequest<{ + models: { + id: string; + name: string; + }[]; + }>(request); + + return { + options: response.body.models.map((model) => { + return { + label: model.name, + value: model.id, + }; + }), + disabled: false, + }; + } catch (error) { + return { + options: [], + disabled: true, + placeholder: `Couldn't Load Models:\n${error}`, + }; + } + }, + defaultValue: 'GPT-4', + }), + prompt: Property.LongText({ + displayName: 'Prompt', + description: 'The prompt to send to the model.', + required: true, + }), + }, + async run(context) { + const mId = context.propsValue.models as string; + + const findModel: HttpRequest = { + method: HttpMethod.GET, + url: `https://api.clarifai.com/v2/models?name=${mId}&use_cases=llm`, + headers: { + Authorization: ('Key ' + context.auth) as string, + }, + }; + let model; + try { + const response = await httpClient.sendRequest<{ + models: { + id: string; + name: string; + model_version: { + id: string; + app_id: string; + user_id: string; + }; + }[]; + }>(findModel); + model = response.body.models[0]; + } catch (error) { + throw new Error(`Couldn't find model ${mId}\n${error}`); + } + const prompt = context.propsValue.prompt as string; + const sendPrompt: HttpRequest = { + method: HttpMethod.POST, + url: `https://api.clarifai.com/v2/users/${model.model_version.user_id}/apps/${model.model_version.app_id}/models/${model.id}/versions/${model.model_version.id}/outputs`, + headers: { + Authorization: ('Key ' + context.auth) as string, + }, + body: { + inputs: [ + { + data: { + text: { + raw: prompt, + }, + }, + }, + ], + }, + }; + try { + const response = await httpClient.sendRequest<{ + outputs: { + data: { + text: { + raw: string; + }; + }; + }[]; + }>(sendPrompt); + return { result: response.body.outputs[0].data.text.raw }; + } catch (error) { + throw new Error(`Couldn't send prompt to model ${mId}\n${error}`); + } + }, +}); diff --git a/packages/pieces/community/clarifai/src/lib/actions/call-audio-model.ts b/packages/pieces/community/clarifai/src/lib/actions/call-audio-model.ts new file mode 100644 index 0000000..92d6248 --- /dev/null +++ b/packages/pieces/community/clarifai/src/lib/actions/call-audio-model.ts @@ -0,0 +1,37 @@ +import { clarifaiAuth } from '../../'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { + CommonClarifaiProps, + callClarifaiModel, + cleanMultiOutputResponse, + fileToInput, +} from '../common'; +import { Data } from 'clarifai-nodejs-grpc/proto/clarifai/api/resources_pb'; + +export const audioToTextModelPredictAction = createAction({ + auth: clarifaiAuth, + name: 'audio_text_model', + description: 'Call a audio to text AI model', + displayName: 'Audio to Text', + props: { + modelUrl: CommonClarifaiProps.modelUrl, + file: Property.File({ + description: 'URL or base64 bytes of the audio to classify', + displayName: 'Input URL or bytes', + required: true, + }), + }, + async run(ctx) { + const { auth } = ctx; + const { modelUrl, file } = ctx.propsValue; + + const input = fileToInput(file); + + const outputs = await callClarifaiModel({ + auth, + modelUrl, + input, + }); + return cleanMultiOutputResponse(outputs); + }, +}); diff --git a/packages/pieces/community/clarifai/src/lib/actions/call-image-model.ts b/packages/pieces/community/clarifai/src/lib/actions/call-image-model.ts new file mode 100644 index 0000000..2b0331a --- /dev/null +++ b/packages/pieces/community/clarifai/src/lib/actions/call-image-model.ts @@ -0,0 +1,65 @@ +import { clarifaiAuth } from '../../'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { + CommonClarifaiProps, + callClarifaiModel, + cleanMultiOutputResponse, + fileToInput, +} from '../common'; +import { Data } from 'clarifai-nodejs-grpc/proto/clarifai/api/resources_pb'; + +export const visualClassifierModelPredictAction = createAction({ + auth: clarifaiAuth, + name: 'visual_classifier_model', + description: 'Call an visual classifier AI model to recognize concepts', + displayName: 'Classify Images or Videos', + props: { + modelUrl: CommonClarifaiProps.modelUrl, + file: Property.File({ + description: 'URL or base64 bytes of the image or video to classify', + displayName: 'Input URL or bytes', + required: true, + }), + }, + async run(ctx) { + const { auth } = ctx; + const { modelUrl, file } = ctx.propsValue; + + const input = fileToInput(file); + + const outputs = await callClarifaiModel({ + auth, + modelUrl, + input, + }); + return cleanMultiOutputResponse(outputs); + }, +}); + +export const imageToTextModelPredictAction = createAction({ + auth: clarifaiAuth, + name: 'image_text_model', + description: 'Call an image to text AI model', + displayName: 'Image to Text', + props: { + modelUrl: CommonClarifaiProps.modelUrl, + file: Property.File({ + description: 'URL or base64 bytes of the image to classify', + displayName: 'Input URL or bytes', + required: true, + }), + }, + async run(ctx) { + const { auth } = ctx; + const { modelUrl, file } = ctx.propsValue; + + const input = fileToInput(file); + + const outputs = await callClarifaiModel({ + auth, + modelUrl, + input, + }); + return cleanMultiOutputResponse(outputs); + }, +}); diff --git a/packages/pieces/community/clarifai/src/lib/actions/call-post-inputs.ts b/packages/pieces/community/clarifai/src/lib/actions/call-post-inputs.ts new file mode 100644 index 0000000..122173b --- /dev/null +++ b/packages/pieces/community/clarifai/src/lib/actions/call-post-inputs.ts @@ -0,0 +1,47 @@ +import { clarifaiAuth } from '../../'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { + CommonClarifaiProps, + callPostInputs, + cleanMultiInputResponse, + fileToInput, +} from '../common'; +import { Data } from 'clarifai-nodejs-grpc/proto/clarifai/api/resources_pb'; + +export const postInputsAction = createAction({ + auth: clarifaiAuth, + name: 'post_inputs', + description: 'Add inputs to your clarifai app', + displayName: 'Add Inputs', + props: { + userId: Property.ShortText({ + description: 'User ID to associate with the input', + displayName: 'User ID', + required: true, + }), + appId: Property.ShortText({ + description: 'App ID (project) to associate with the input', + displayName: 'App ID', + required: true, + }), + file: Property.File({ + description: 'URL or base64 bytes of the audio to classify', + displayName: 'Input URL or bytes', + required: true, + }), + }, + async run(ctx) { + const { auth } = ctx; + const { userId, appId, file } = ctx.propsValue; + + const input = fileToInput(file); + + const inputs = await callPostInputs({ + auth, + userId, + appId, + input, + }); + return cleanMultiInputResponse(inputs); + }, +}); diff --git a/packages/pieces/community/clarifai/src/lib/actions/call-text-model.ts b/packages/pieces/community/clarifai/src/lib/actions/call-text-model.ts new file mode 100644 index 0000000..1c3f184 --- /dev/null +++ b/packages/pieces/community/clarifai/src/lib/actions/call-text-model.ts @@ -0,0 +1,65 @@ +import { clarifaiAuth } from '../../'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { + CommonClarifaiProps, + callClarifaiModel, + cleanMultiOutputResponse, + textToInput, +} from '../common'; +import { Data } from 'clarifai-nodejs-grpc/proto/clarifai/api/resources_pb'; + +export const textClassifierModelPredictAction = createAction({ + auth: clarifaiAuth, + name: 'text_classifier_model', + description: 'Call a text classifier AI model to recognize concepts', + displayName: 'Classify Text', + props: { + modelUrl: CommonClarifaiProps.modelUrl, + txt: Property.LongText({ + description: 'Text to classify', + displayName: 'Input Text', + required: true, + }), + }, + async run(ctx) { + const { auth } = ctx; + const { modelUrl, txt } = ctx.propsValue; + + const input = textToInput(txt); + + const outputs = await callClarifaiModel({ + auth, + modelUrl, + input, + }); + return cleanMultiOutputResponse(outputs); + }, +}); + +export const textToTextModelPredictAction = createAction({ + auth: clarifaiAuth, + name: 'text_text_model', + description: 'Call a text to text AI model', + displayName: 'Text to Text', + props: { + modelUrl: CommonClarifaiProps.modelUrl, + txt: Property.LongText({ + description: 'Text to classify', + displayName: 'Input Text', + required: true, + }), + }, + async run(ctx) { + const { auth } = ctx; + const { modelUrl, txt } = ctx.propsValue; + + const input = textToInput(txt); + + const outputs = await callClarifaiModel({ + auth, + modelUrl, + input, + }); + return cleanMultiOutputResponse(outputs); + }, +}); diff --git a/packages/pieces/community/clarifai/src/lib/actions/call-workflow.ts b/packages/pieces/community/clarifai/src/lib/actions/call-workflow.ts new file mode 100644 index 0000000..50f6d63 --- /dev/null +++ b/packages/pieces/community/clarifai/src/lib/actions/call-workflow.ts @@ -0,0 +1,38 @@ +import { clarifaiAuth } from '../../'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { + CommonClarifaiProps, + callClarifaiWorkflow, + cleanPostWorkflowResultsResponse, + fileToInput, +} from '../common'; +import { Data } from 'clarifai-nodejs-grpc/proto/clarifai/api/resources_pb'; + +export const workflowPredictAction = createAction({ + auth: clarifaiAuth, + name: 'workflow_predict', + description: 'Call a Clarifai workflow', + displayName: 'Run Workflow', + props: { + workflowUrl: CommonClarifaiProps.workflowUrl, + file: Property.File({ + description: + 'URL or base64 bytes of the incoming image/video/text/audio to run through the workflow. Note: must be appropriate first step of the workflow to handle that data type.', + displayName: 'Input URL or bytes', + required: true, + }), + }, + async run(ctx) { + const { auth } = ctx; + const { workflowUrl, file } = ctx.propsValue; + + const input = fileToInput(file); + + const outputs = await callClarifaiWorkflow({ + auth, + workflowUrl, + input, + }); + return cleanPostWorkflowResultsResponse(outputs); + }, +}); diff --git a/packages/pieces/community/clarifai/src/lib/actions/generate-igm.ts b/packages/pieces/community/clarifai/src/lib/actions/generate-igm.ts new file mode 100644 index 0000000..b06a571 --- /dev/null +++ b/packages/pieces/community/clarifai/src/lib/actions/generate-igm.ts @@ -0,0 +1,146 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { clarifaiAuth } from '../..'; +import { + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; + +export const clarifaiGenerateIGM = createAction({ + name: 'generate-igm', + displayName: 'Ask IGM', + description: + 'Generate an image using the Image generating models supported by clarifai.', + auth: clarifaiAuth, + props: { + models: Property.Dropdown({ + displayName: 'Model Id', + description: `The model which will generate the response. If the model is not listed, get the model id from the clarifai website. Example : 'GPT-4' you can get the model id from here [https://clarifai.com/openai/chat-completion/models/GPT-4](https://clarifai.com/openai/chat-completion/models/GPT-4)`, + refreshers: ['auth'], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please add an PAT key', + }; + } + const request: HttpRequest = { + method: HttpMethod.GET, + url: 'https://api.clarifai.com/v2/models?sort_by_star_count=true&model_type_id=text-to-image&filter_by_user_id=true&additional_fields=stars&per_page=24&page=1', + headers: { + Authorization: ('Key ' + auth) as string, + }, + }; + try { + const response = await httpClient.sendRequest<{ + models: { + id: string; + name: string; + }[]; + }>(request); + + return { + options: response.body.models.map((model) => { + return { + label: model.name, + value: model.id, + }; + }), + disabled: false, + }; + } catch (error) { + return { + options: [], + disabled: true, + placeholder: `Couldn't Load Models:\n${error}`, + }; + } + }, + defaultValue: 'general-image-generator-dalle-mini', + }), + prompt: Property.LongText({ + displayName: 'Prompt', + description: 'The prompt to send to the model.', + required: true, + }), + }, + run: async (context) => { + const mId = context.propsValue.models as string; + + const findModel: HttpRequest = { + method: HttpMethod.GET, + url: `https://api.clarifai.com/v2/models?name=${mId}&model_type_id=text-to-image`, + headers: { + Authorization: ('Key ' + context.auth) as string, + }, + }; + let model; + try { + const response = await httpClient.sendRequest<{ + models: { + id: string; + name: string; + model_version: { + id: string; + app_id: string; + user_id: string; + }; + }[]; + }>(findModel); + model = response.body.models[0]; + } catch (error) { + throw new Error(`Couldn't find model ${mId}\n${error}`); + } + const prompt = context.propsValue.prompt as string; + const sendPrompt: HttpRequest = { + method: HttpMethod.POST, + url: `https://api.clarifai.com/v2/users/${model.model_version.user_id}/apps/${model.model_version.app_id}/models/${model.id}/versions/${model.model_version.id}/outputs`, + headers: { + Authorization: ('Key ' + context.auth) as string, + }, + body: { + inputs: [ + { + data: { + text: { + raw: prompt, + }, + }, + }, + ], + }, + }; + + try { + const response = await httpClient.sendRequest<{ + outputs: { + id: string; + data: { + image: { + base64: string; + image_info: { + format: string; + }; + }; + }; + }[]; + }>(sendPrompt); + return { + result: await context.files.write({ + fileName: + response.body.outputs[0].id + + '.' + + response.body.outputs[0].data.image.image_info.format, + data: Buffer.from( + response.body.outputs[0].data.image.base64, + 'base64' + ), + }), + }; + } catch (error) { + throw new Error(`Couldn't send prompt to model ${mId}\n${error}`); + } + }, +}); diff --git a/packages/pieces/community/clarifai/src/lib/common/index.ts b/packages/pieces/community/clarifai/src/lib/common/index.ts new file mode 100644 index 0000000..3678c98 --- /dev/null +++ b/packages/pieces/community/clarifai/src/lib/common/index.ts @@ -0,0 +1,435 @@ +import { Property, ApFile } from '@activepieces/pieces-framework'; +import { grpc } from 'clarifai-nodejs-grpc'; +import { + Model, + Data, + Input, + UserAppIDSet, + Image, + Video, + Audio, + Text, +} from 'clarifai-nodejs-grpc/proto/clarifai/api/resources_pb'; +import { V2Client } from 'clarifai-nodejs-grpc/proto/clarifai/api/service_grpc_pb'; +import { + MultiOutputResponse, + PostModelOutputsRequest, + MultiInputResponse, + PostInputsRequest, + PostWorkflowResultsResponse, + PostWorkflowResultsRequest, +} from 'clarifai-nodejs-grpc/proto/clarifai/api/service_pb'; +import { promisify } from 'util'; + +function initClarifaiClient() { + const clarifai = new V2Client( + 'api.clarifai.com', + grpc.ChannelCredentials.createSsl() + ); + return clarifai; +} + +export const clarifaiClient = initClarifaiClient(); + +export interface CallModelRequest { + auth: string; + modelUrl: string; + input: Input; +} + +export interface CallWorkflowRequest { + auth: string; + workflowUrl: string; + input: Input; +} + +export interface CallPostInputsRequest { + auth: string; + userId: string; + appId: string; + input: Input; +} + +export function callClarifaiModel({ auth, modelUrl, input }: CallModelRequest) { + const [userId, appId, modelId, versionId] = parseEntityUrl(modelUrl); + + const req = new PostModelOutputsRequest(); + req.setUserAppId(userAppIdSet(userId, appId)); + req.setModelId(modelId); + if (versionId) { + req.setVersionId(versionId); + } + req.setInputsList([input]); + + const metadata = authMetadata(auth); + // TODO: we should really be using the async version of this, circle back with clarifai team to see if we can + // tweak the protoc settings to build a promise-compatible version of our API client. + const postModelOutputs = promisify< + PostModelOutputsRequest, + grpc.Metadata, + MultiOutputResponse + >(clarifaiClient.postModelOutputs.bind(clarifaiClient)); + return postModelOutputs(req, metadata); +} + +export function callClarifaiWorkflow({ + auth, + workflowUrl, + input, +}: CallWorkflowRequest) { + const [userId, appId, workflowId, versionId] = parseEntityUrl(workflowUrl); + + const req = new PostWorkflowResultsRequest(); + req.setUserAppId(userAppIdSet(userId, appId)); + req.setWorkflowId(workflowId); + if (versionId) { + req.setVersionId(versionId); + } + req.setInputsList([input]); + + const metadata = authMetadata(auth); + // TODO: we should really be using the async version of this, circle back with clarifai team to see if we can + // tweak the protoc settings to build a promise-compatible version of our API client. + const postWorkflowResults = promisify< + PostWorkflowResultsRequest, + grpc.Metadata, + PostWorkflowResultsResponse + >(clarifaiClient.postWorkflowResults.bind(clarifaiClient)); + return postWorkflowResults(req, metadata); +} + +export function callPostInputs({ + auth, + userId, + appId, + input, +}: CallPostInputsRequest) { + const req = new PostInputsRequest(); + req.setUserAppId(userAppIdSet(userId, appId)); + req.setInputsList([input]); + + const metadata = authMetadata(auth); + // TODO: we should really be using the async version of this, circle back with clarifai team to see if we can + // tweak the protoc settings to build a promise-compatible version of our API client. + const postInputs = promisify< + PostInputsRequest, + grpc.Metadata, + MultiInputResponse + >(clarifaiClient.postInputs.bind(clarifaiClient)); + return postInputs(req, metadata); +} + +export function fileToInput(file: ApFile) { + const input = new Input(); + const inputData = new Data(); + + const base64 = file.base64; + const mimeType = detectMimeType(base64, file.filename); + if (mimeType.startsWith('image')) { + const dataImage = new Image(); + dataImage.setBase64(base64); + inputData.setImage(dataImage); + } else if (mimeType.startsWith('video')) { + const dataVideo = new Video(); + dataVideo.setBase64(base64); + inputData.setVideo(dataVideo); + } else if (mimeType.startsWith('audio')) { + const dataAudio = new Audio(); + dataAudio.setBase64(base64); + inputData.setAudio(dataAudio); + } else { + // sending the rest of text may not always work, but it's worth a shot + const dataText = new Text(); + dataText.setRaw(base64); + inputData.setText(dataText); + } + input.setData(inputData); + return input; +} + +export function textToInput(text: string) { + const input = new Input(); + const inputData = new Data(); + const dataText = new Text(); + dataText.setRaw(text); + inputData.setText(dataText); + input.setData(inputData); + return input; +} + +function userAppIdSet(userId: string, appId: string) { + const set = new UserAppIDSet(); + set.setUserId(userId); + set.setAppId(appId); + return set; +} + +function authMetadata(auth: string) { + const metadata = new grpc.Metadata(); + metadata.set('authorization', 'Key ' + auth); + return metadata; +} + +export const CommonClarifaiProps = { + modelUrl: Property.ShortText({ + description: + 'URL of the Clarifai model. For example https://clarifai.com/clarifai/main/models/general-image-recognition OR a specific version such as https://clarifai.com/clarifai/main/models/general-image-recognition/versions/aa7f35c01e0642fda5cf400f543e7c40. Find more models at https://clarifai.com/explore/models', + displayName: 'Model URL', + required: true, + }), + workflowUrl: Property.ShortText({ + description: + 'URL of the Clarifai workflow. For example https://clarifai.com/clarifai/main/workflows/Demographics. Find more workflows at https://clarifai.com/explore/workflows', + displayName: 'Workflow URL', + required: true, + }), +}; + +function parseEntityUrl(entityUrl: string): [string, string, string, string] { + const url = new URL(entityUrl); + const parts = url.pathname.split('/'); + let version = ''; + if (parts.length === 7 && parts[5] === 'versions') { + version = parts[6]; + } + return [parts[1], parts[2], parts[4], version]; +} + +export function removeListFromPropertyNames( + obj: Record +): Record { + const result: Record = {}; + for (const [key, value] of Object.entries(obj)) { + if (key.endsWith('List') && Array.isArray(value)) { + if (value.length === 0) { + // remove empty lists by default + continue; + } + // remove 'List' and recurse on every item in the array + result[key.slice(0, -4)] = value.map((item) => { + // if the item is an object, recurse on it + if (Object.prototype.toString.call(item) === '[object Object]') { + return removeListFromPropertyNames(item); + } + // otherwise, return the item as-is + return item; + }); + } else { + // if the item is an object, recurse on it + if (Object.prototype.toString.call(value) === '[object Object]') { + result[key] = removeListFromPropertyNames( + value as Record + ); + } else { + result[key] = value; + } + } + } + return result; +} + +/** + * Returns the data type based on the base64 string and filename extension + * https://www.iana.org/assignments/media-types/media-types.xhtml for full list of mime types. + * @param {String} base64String + * @param {String} fileName + * @returns {String} + */ +function detectMimeType(base64String: string, fileName: string | undefined) { + let ext = 'undefined'; + if (fileName === undefined || fileName === null || fileName === '') { + ext = 'bin'; + } else { + ext = fileName.substring(fileName.lastIndexOf('.') + 1); + if (ext === undefined || ext === null || ext === '') ext = 'bin'; + } + ext = ext.toLowerCase(); + // This is not an exhaustive list by any stretch. + const signatures = { + JVBERi0: 'application/pdf', + R0lGODdh: 'image/gif', + R0lGODlh: 'image/gif', + iVBORw0KGgo: 'image/png', + TU0AK: 'image/tiff', + '/9j/': 'image/jpg', + UEs: 'application/vnd.openxmlformats-officedocument.', + PK: 'application/zip', + }; + for (const [key, value] of Object.entries(signatures)) { + let modiifedValue = value; + if (base64String.indexOf(key) === 0) { + // var x = signatures[s]; + // if an office file format + if (ext.length > 3 && ext.substring(0, 3) === 'ppt') { + modiifedValue += 'presentationml.presentation'; + } else if (ext.length > 3 && ext.substring(0, 3) === 'xls') { + modiifedValue += 'spreadsheetml.sheet'; + } else if (ext.length > 3 && ext.substring(0, 3) === 'doc') { + modiifedValue += 'wordprocessingml.document'; + } + // return + return modiifedValue; + } + } + // if we are here we can only go off the extensions + const extensions = { + '7z': 'application/x-7z-compressed', + aif: 'audio/x-aiff', + aiff: 'audio/x-aiff', + asf: 'video/x-ms-asf', + asx: 'video/x-ms-asf', + avi: 'video/x-msvideo', + bin: 'application/octet-stream', + bmp: 'image/bmp', + class: 'application/octet-stream', + css: 'text/css', + csv: 'text/csv', + dll: 'application/octet-stream', + doc: 'application/msword', + docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + dwg: 'application/acad', + dxf: 'application/dxf', + eml: 'message/rfc822', + exe: 'application/octet-stream', + flv: 'video/x-flv', + gif: 'image/gif', + gz: 'application/x-gzip', + gzip: 'application/x-gzip', + htm: 'text/html', + html: 'text/html', + ice: 'x-conference/x-cooltalk', + ico: 'image/x-icon', + ics: 'text/calendar', + iges: 'model/iges', + igs: 'model/iges', + jpeg: 'image/jpeg', + jpg: 'image/jpeg', + js: 'application/javascript', + json: 'application/json', + m2a: 'audio/mpeg', + m2v: 'video/mpeg', + m3u: 'audio/x-mpegurl', + m4v: 'video/mpeg', + mesh: 'model/mesh', + mov: 'video/quicktime', + movie: 'video/x-sgi-movie', + mp2: 'audio/mpeg', + mp2a: 'audio/mpeg', + mp3: 'audio/mpeg', + mp4: 'video/mp4', + mpe: 'video/mpeg', + mpeg: 'video/mpeg', + mpg: 'video/mpeg', + mpga: 'audio/mpeg', + mpv: 'video/mpeg', + msg: 'application/vnd.ms-outlook', + msh: 'model/mesh', + mxf: 'application/mxf', + obj: 'application/octet-stream', + oda: 'application/oda', + ogg: 'application/ogg', + ogv: 'video/ogg', + ogx: 'application/ogg', + pdb: 'chemical/x-pdb', + pdf: 'application/pdf', + png: 'image/png', + ppt: 'application/vnd.ms-powerpoint', + psd: 'application/octet-stream', + qt: 'video/quicktime', + ra: 'audio/x-realaudio', + ram: 'audio/x-pn-realaudio', + rgb: 'image/x-rgb', + rm: 'audio/x-pn-realaudio', + rpm: 'audio/x-pn-realaudio-plugin', + rtf: 'application/rtf', + sea: 'application/octet-stream', + silo: 'model/mesh', + so: 'application/octet-stream', + svg: 'image/svg+xml', + tar: 'application/x-tar', + tif: 'image/tiff', + tiff: 'image/tiff', + txt: 'text/plain', + vrml: 'model/vrml', + wav: 'audio/x-wav', + wax: 'audio/x-ms-wax', + webp: 'image/webp', + wma: 'audio/x-ms-wma', + wmv: 'video/x-ms-wmv', + wrl: 'model/vrml', + xls: 'application/vnd.ms-excel', + xml: 'text/xml', + xyz: 'chemical/x-pdb', + zip: 'application/zip', + }; + for (const [key, value] of Object.entries(extensions)) { + if (ext.indexOf(key) === 0) { + return value; + } + } + // if we are here - not sure what type this is + return 'unknown'; +} + +export function cleanMultiOutputResponse(outputs: MultiOutputResponse) { + if (outputs.getOutputsList().length === 0) { + throw new Error('No outputs found from Clarifai'); + } + const data = outputs.getOutputsList()[0].getData(); + if (data == undefined) { + throw new Error('No data found from Clarifai'); + } else { + const result = Data.toObject(false, data); + return removeListFromPropertyNames(result); + } +} + +export function cleanMultiInputResponse(inputs: MultiInputResponse) { + if (inputs.getInputsList().length === 0) { + throw new Error('No inputs found from Clarifai'); + } + const data = inputs.getInputsList()[0].getData(); + if (data == undefined) { + throw new Error('No data found from Clarifai'); + } else { + const result = Data.toObject(false, data); + return removeListFromPropertyNames(result); + } +} + +export function cleanPostWorkflowResultsResponse( + response: PostWorkflowResultsResponse +) { + if (response.getResultsList().length === 0) { + throw new Error('No results found from Clarifai'); + } + // one result per input in the workflow. + const results = response.getResultsList(); + if (results == undefined || results.length === 0) { + throw new Error('No results found from Clarifai'); + } else { + const result = results[0]; + const outputs = result.getOutputsList(); + if (outputs == undefined || outputs.length === 0) { + throw new Error('No outputs found from Clarifai'); + } + const array: any[] = []; + for (const output of outputs) { + const model = output.getModel(); + if (model == undefined) { + throw new Error('No model found from Clarifai'); + } + const m = Model.toObject(false, model); + const data = output.getData(); + let out: any = { output: 'suppressed' }; + if (data != undefined) { + out = Data.toObject(false, data); + } + array.push({ + model: removeListFromPropertyNames(m), + data: removeListFromPropertyNames(out), + }); + } + return array; + } +} diff --git a/packages/pieces/community/clarifai/tsconfig.json b/packages/pieces/community/clarifai/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/clarifai/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/clarifai/tsconfig.lib.json b/packages/pieces/community/clarifai/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/clarifai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/claude/.eslintrc.json b/packages/pieces/community/claude/.eslintrc.json new file mode 100644 index 0000000..eac708a --- /dev/null +++ b/packages/pieces/community/claude/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/claude/README.md b/packages/pieces/community/claude/README.md new file mode 100644 index 0000000..b6c2da2 --- /dev/null +++ b/packages/pieces/community/claude/README.md @@ -0,0 +1,7 @@ +# pieces-claude + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-claude` to build the library. diff --git a/packages/pieces/community/claude/package.json b/packages/pieces/community/claude/package.json new file mode 100644 index 0000000..af610e9 --- /dev/null +++ b/packages/pieces/community/claude/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-claude", + "version": "0.1.5" +} \ No newline at end of file diff --git a/packages/pieces/community/claude/project.json b/packages/pieces/community/claude/project.json new file mode 100644 index 0000000..6e8fdb2 --- /dev/null +++ b/packages/pieces/community/claude/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-claude", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/claude/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/claude", + "tsConfig": "packages/pieces/community/claude/tsconfig.lib.json", + "packageJson": "packages/pieces/community/claude/package.json", + "main": "packages/pieces/community/claude/src/index.ts", + "assets": [ + "packages/pieces/community/claude/*.md", + { + "input": "packages/pieces/community/claude/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-claude {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/claude/src/index.ts b/packages/pieces/community/claude/src/index.ts new file mode 100644 index 0000000..0c2cb5c --- /dev/null +++ b/packages/pieces/community/claude/src/index.ts @@ -0,0 +1,35 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { askClaude } from './lib/actions/send-prompt'; +import { baseUrl } from './lib/common/common'; +import { PieceCategory, SUPPORTED_AI_PROVIDERS } from '@activepieces/shared'; +import { extractStructuredDataAction } from './lib/actions/extract-structured-data'; + +export const claudeAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: SUPPORTED_AI_PROVIDERS.find(p => p.provider === 'anthropic')?.markdown, +}); + +export const claude = createPiece({ + displayName: 'Anthropic Claude', + auth: claudeAuth, + minimumSupportedRelease: '0.63.0', + logoUrl: 'https://cdn.activepieces.com/pieces/claude.png', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + authors: ['dennisrongo','kishanprmr'], + actions: [ + askClaude, + extractStructuredDataAction, + createCustomApiCallAction({ + auth: claudeAuth, + baseUrl: () => baseUrl, + authMapping: async (auth) => { + return { + 'x-api-key': `${auth}`, + }; + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/claude/src/lib/actions/extract-structured-data.ts b/packages/pieces/community/claude/src/lib/actions/extract-structured-data.ts new file mode 100644 index 0000000..2955d9a --- /dev/null +++ b/packages/pieces/community/claude/src/lib/actions/extract-structured-data.ts @@ -0,0 +1,285 @@ +import { claudeAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { isNil } from '@activepieces/shared'; +import Anthropic from '@anthropic-ai/sdk'; +import { TextBlock, ToolUseBlock } from '@anthropic-ai/sdk/resources'; +import Ajv from 'ajv'; +import mime from 'mime-types'; + +export const extractStructuredDataAction = createAction({ + auth: claudeAuth, + name: 'extract-structured-data', + displayName: 'Extract Structured Data', + description: 'Extract structured data from provided text,image or PDF.', + props: { + model: Property.StaticDropdown({ + displayName: 'Model', + required: true, + description: + 'The model which will generate the completion. Some models are suitable for natural language tasks, others specialize in code.', + defaultValue: 'claude-3-haiku-20240307', + options: { + disabled: false, + options: [ + { value: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku' }, + { value: 'claude-3-sonnet-20240229', label: 'Claude 3 Sonnet' }, + { value: 'claude-3-opus-20240229', label: 'Claude 3 Opus' }, + { value: 'claude-3-5-sonnet-latest', label: 'Claude 3.5 Sonnet' }, + { value: 'claude-3-5-haiku-latest', label: 'Claude 3.5 Haiku' }, + { value: 'claude-3-7-sonnet-latest', label: 'Claude 3.7 Sonnet' }, + ], + }, + }), + text: Property.LongText({ + displayName: 'Text', + description: 'Text to extract structured data from.', + required: false, + }), + image: Property.File({ + displayName: 'Image/PDF', + description: 'Image or PDF to extract structured data from.', + required: false, + }), + prompt: Property.LongText({ + displayName: 'Guide Prompt', + description: 'Prompt to guide the AI.', + defaultValue: 'Extract the following data from the provided data.', + required: false, + }), + mode: Property.StaticDropdown<'simple' | 'advanced'>({ + displayName: 'Data Schema Type', + description: 'For complex schema, you can use advanced mode.', + required: true, + defaultValue: 'simple', + options: { + disabled: false, + options: [ + { label: 'Simple', value: 'simple' }, + { label: 'Advanced', value: 'advanced' }, + ], + }, + }), + schema: Property.DynamicProperties({ + displayName: 'Data Definition', + required: true, + refreshers: ['mode'], + props: async (propsValue) => { + const mode = propsValue['mode'] as unknown as 'simple' | 'advanced'; + if (mode === 'advanced') { + return { + fields: Property.Json({ + displayName: 'JSON Schema', + description: + 'Learn more about JSON Schema here: https://json-schema.org/learn/getting-started-step-by-step', + required: true, + defaultValue: { + type: 'object', + properties: { + name: { + type: 'string', + }, + age: { + type: 'number', + }, + }, + required: ['name'], + }, + }), + }; + } + return { + fields: Property.Array({ + displayName: 'Data Definition', + required: true, + properties: { + name: Property.ShortText({ + displayName: 'Name', + description: + 'Provide the name of the value you want to extract from the unstructured text. The name should be unique and short. ', + required: true, + }), + description: Property.LongText({ + displayName: 'Description', + description: + 'Brief description of the data, this hints for the AI on what to look for', + required: false, + }), + type: Property.StaticDropdown({ + displayName: 'Data Type', + description: 'Type of parameter.', + required: true, + defaultValue: 'string', + options: { + disabled: false, + options: [ + { label: 'Text', value: 'string' }, + { label: 'Number', value: 'number' }, + { label: 'Boolean', value: 'boolean' }, + ], + }, + }), + isRequired: Property.Checkbox({ + displayName: 'Fail if Not present?', + required: true, + defaultValue: false, + }), + }, + }), + }; + }, + }), + maxTokens: Property.Number({ + displayName: 'Maximum Tokens', + required: false, + description: + "The maximum number of tokens to generate. Requests can use up to 2,048 or 4,096 tokens shared between prompt and completion, don't set the value to maximum and leave some tokens for the input. The exact limit varies by model. (One token is roughly 4 characters for normal English text)", + }), + }, + async run(context) { + const { model, text, image, schema, prompt, maxTokens } = context.propsValue; + + if (!text && !image) { + throw new Error('Please provide text or image/PDF to extract data from.'); + } + + + let params: AIFunctionArgumentDefinition; + if (context.propsValue.mode === 'advanced') { + const ajv = new Ajv(); + const isValidSchema = ajv.validateSchema(schema); + + if (!isValidSchema) { + throw new Error( + JSON.stringify({ + message: 'Invalid JSON schema', + errors: ajv.errors, + }), + ); + } + + params = schema['fields'] as AIFunctionArgumentDefinition; + } else { + params = { + type: 'object', + properties: ( + schema['fields'] as Array<{ + name: string; + description?: string; + type: string; + isRequired: boolean; + }> + ).reduce((acc, field) => { + acc[field.name] = { + type: field.type, + description: field.description, + }; + return acc; + }, {} as Record), + required: ( + schema['fields'] as Array<{ + name: string; + description?: string; + type: string; + isRequired: boolean; + }> + ) + .filter((field) => field.isRequired) + .map((field) => field.name), + }; + } + + const anthropic = new Anthropic({ + apiKey: context.auth, + }); + + const messages: Anthropic.Messages.MessageParam[] = [ + { + role: 'user', + content: prompt ?? 'Use optical character recognition (OCR) to extract from provided data.', + }, + ]; + + if (!isNil(text) && text !== '') { + messages.push({ + role: 'user', + content: text, + }); + } + + if (image) { + const mediaType = image.extension ? mime.lookup(image.extension) : 'image/jpeg'; + if (mediaType === 'application/pdf') { + messages.push({ + role: 'user', + content: [ + { + type: 'document', + source: { + type: 'base64', + media_type: 'application/pdf', + data: image.base64, + }, + }, + ], + }); + } else { + messages.push({ + role: 'user', + content: [ + { + type: 'image', + source: { + type: 'base64', + media_type: mediaType as 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp', + data: image.base64, + }, + }, + ], + }); + } + } + const response = await anthropic.messages.create({ + model: model, + messages, + tools: [ + { + name: 'extract_structured_data', + description: 'Extract the following data from the provided data.', + input_schema: params, + }, + ], + tool_choice: { type: 'tool', name: 'extract_structured_data' }, + max_tokens: maxTokens ?? 2000, + }); + + const toolCallsResponse = response.content.filter( + (choice): choice is ToolUseBlock => choice.type === 'tool_use', + ); + + const toolCall = toolCallsResponse[0]; + + const choices = response.content + .filter((choice): choice is TextBlock => choice.type === 'text') + .map((choice: TextBlock) => ({ + content: choice.text, + role: 'assistant', + })); + + const args = toolCall.input; + if (isNil(args)) { + throw new Error( + JSON.stringify({ + message: choices[0].content ?? 'Failed to extract structured data from the input.', + }), + ); + } + return args; + }, +}); + +type AIFunctionArgumentDefinition = { + type: 'object'; + properties?: unknown | null; + required?: string[]; + [k: string]: unknown; +}; diff --git a/packages/pieces/community/claude/src/lib/actions/send-prompt.ts b/packages/pieces/community/claude/src/lib/actions/send-prompt.ts new file mode 100644 index 0000000..7187c5a --- /dev/null +++ b/packages/pieces/community/claude/src/lib/actions/send-prompt.ts @@ -0,0 +1,249 @@ +import { + createAction, + DynamicPropsValue, + Property, +} from '@activepieces/pieces-framework'; +import Anthropic from '@anthropic-ai/sdk'; +import mime from 'mime-types'; +import { claudeAuth } from '../..'; +import { TextBlock } from '@anthropic-ai/sdk/resources'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; +import { billingIssueMessage, unauthorizedMessage } from '../common/common'; +const DEFAULT_TOKENS_FOR_THINKING_MODE=1024; +export const askClaude = createAction({ + auth: claudeAuth, + name: 'ask_claude', + displayName: 'Ask Claude', + description: 'Ask Claude anything you want!', + props: { + model: Property.StaticDropdown({ + displayName: 'Model', + required: true, + description: + 'The model which will generate the completion. Some models are suitable for natural language tasks, others specialize in code.', + defaultValue: 'claude-3-haiku-20240307', + options: { + disabled: false, + options: [ + { value: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku' }, + { value: 'claude-3-sonnet-20240229', label: 'Claude 3 Sonnet' }, + { value: 'claude-3-opus-20240229', label: 'Claude 3 Opus' }, + { value: 'claude-3-5-sonnet-latest', label: 'Claude 3.5 Sonnet' }, + { value: 'claude-3-5-haiku-latest', label: 'Claude 3.5 Haiku' }, + { value: 'claude-3-7-sonnet-latest', label: 'Claude 3.7 Sonnet' }, + + ], + }, + }), + systemPrompt: Property.LongText({ + displayName: 'System Prompt', + required: false, + defaultValue: "You're a helpful assistant.", + }), + temperature: Property.Number({ + displayName: 'Temperature', + required: false, + description: + 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.', + }), + maxTokens: Property.Number({ + displayName: 'Maximum Tokens', + required: false, + description: + "The maximum number of tokens to generate. Requests can use up to 2,048 or 4,096 tokens shared between prompt and completion, don't set the value to maximum and leave some tokens for the input. The exact limit varies by model. (One token is roughly 4 characters for normal English text)", + }), + prompt: Property.LongText({ + displayName: 'Question', + required: true, + }), + image: Property.File({ + displayName: 'Image (URL)', + required: false, + description: 'URL of image to be used as input for the model.', + }), + roles: Property.Json({ + displayName: 'Roles', + required: false, + description: `Array of roles to specify more accurate response.Please check [guide to Input Messages](https://docs.anthropic.com/en/api/messages-examples#vision).`, + }), + thinkingMode:Property.Checkbox({ + displayName:'Extended Thinking Mode', + required:false, + defaultValue:false, + description:'Uses claude 3.7 sonnet enhanced reasoning capabilities for complex tasks.' + }), + thinkingModeParams:Property.DynamicProperties({ + displayName:'', + refreshers:['thinkingMode'], + required:false, + props:async({auth,thinkingMode})=>{ + if(!auth || !thinkingMode) return {} + + const props:DynamicPropsValue={} + + props['budgetTokens'] = Property.Number({ + displayName:'Budget Tokens', + required:true, + defaultValue:DEFAULT_TOKENS_FOR_THINKING_MODE, + description:'This parameter determines the maximum number of tokens Claude is allowed to use for its internal reasoning process.Your budget tokens must always be less than the max tokens specified.', + + }) + + return props; + + } + }) + }, + async run({ auth, propsValue }) { + await propsValidation.validateZod(propsValue, { + temperature: z.number().min(0).max(1.0).optional(), + }); + + const anthropic = new Anthropic({ + apiKey: auth, + }); + let billingIssue = false; + let unauthorized = false; + let model = 'claude-3-haiku-20240307'; + + if (propsValue.model) { + model = propsValue.model; + } + let temperature = 0.5; + if (propsValue.temperature) { + temperature = Number(propsValue.temperature); + } + let maxTokens = 1000; + if (propsValue.maxTokens) { + maxTokens = Number(propsValue.maxTokens); + } + let systemPrompt = 'You are a helpful assistant.'; + if (propsValue.systemPrompt) { + systemPrompt = propsValue.systemPrompt; + } + + type Content = + | { type: 'text'; text: string } + | { + type: 'image'; + source: { type: 'base64'; media_type: string; data: string }; + }; + const rolesArray = propsValue.roles + ? (propsValue.roles as unknown as Array) + : []; + + const rolesEnum = ['user', 'assistant']; + const roles = rolesArray.map((item: any) => { + if (!rolesEnum.includes(item.role)) { + throw new Error('The only available roles are: [user, assistant]'); + } + return item; + }); + + const defaultMimeType = 'image/jpeg'; + roles.unshift({ + role: 'user', + content: [ + { + type: 'text', + text: propsValue['prompt'], + }, + ...(propsValue.image + ? [ + { + type: 'image', + source: { + type: 'base64', + media_type: propsValue.image.extension + ? mime.lookup(propsValue.image.extension) || defaultMimeType + : defaultMimeType, + data: propsValue.image.base64, + }, + }, + ] + : []), + ], + }); + + const maxRetries = 4; + let retries = 0; + let response:string | undefined; + while (retries < maxRetries) { + try { + + if(propsValue.thinkingMode) + { + const budgetTokens = propsValue.thinkingModeParams ? propsValue.thinkingModeParams['budgetTokens'] :1024; + + const req = await anthropic.messages.create({ + model: 'claude-3-7-sonnet-20250219', + max_tokens: maxTokens, + system: systemPrompt, + thinking: { + type: 'enabled', + budget_tokens: budgetTokens ?? DEFAULT_TOKENS_FOR_THINKING_MODE, + }, + messages: roles, + }); + + response = req.content.filter((block) => block.type === 'text')[0].text.trim() + } + else{ + const req = await anthropic?.messages.create({ + model: model, + max_tokens: maxTokens, + temperature: temperature, + system: systemPrompt, + messages: roles, + }); + + response = (req?.content[0] as TextBlock).text?.trim(); + } + + + break; // Break out of the loop if the request is successful + } catch (e: any) { + if (e?.type?.includes('rate_limit_error')) { + billingIssue = true; + if (retries + 1 === maxRetries) { + throw e; + } + // Calculate the time delay for the next retry using exponential backoff + const delay = Math.pow(6, retries) * 1000; + console.log(`Retrying in ${delay} milliseconds...`); + await sleep(delay); // Wait for the calculated delay + retries++; + break; + } else { + if (e?.error?.type?.includes('not_found_error')) { + unauthorized = true; + throw e; + } + const new_error = e as { + type: string; + error: { + type: string; + message: string; + }; + }; + throw e; + // throw { + // error: new_error.error.message, + // }; + } + } + } + if (billingIssue) { + throw new Error(billingIssueMessage); + } + if (unauthorized) { + throw new Error(unauthorizedMessage); + } + return response; + }, +}); + +function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/packages/pieces/community/claude/src/lib/common/common.ts b/packages/pieces/community/claude/src/lib/common/common.ts new file mode 100644 index 0000000..59d9239 --- /dev/null +++ b/packages/pieces/community/claude/src/lib/common/common.ts @@ -0,0 +1,14 @@ +export const baseUrl = 'https://api.anthropic.com/v1'; + +export const billingIssueMessage = `Error Occurred: 429 \n + +1. Ensure that you have enough tokens on your Anthropic platform. \n +2. Generate a new API key. \n +3. Attempt the process again. \n + +For guidance, visit: https://console.anthropic.com/settings/plans`; + +export const unauthorizedMessage = `Error Occurred: 401 \n + +Ensure that your API key is valid. \n +`; diff --git a/packages/pieces/community/claude/tsconfig.json b/packages/pieces/community/claude/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/claude/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/claude/tsconfig.lib.json b/packages/pieces/community/claude/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/claude/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/clearout/.eslintrc.json b/packages/pieces/community/clearout/.eslintrc.json new file mode 100644 index 0000000..15ede17 --- /dev/null +++ b/packages/pieces/community/clearout/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*", "package.json"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/clearout/README.md b/packages/pieces/community/clearout/README.md new file mode 100644 index 0000000..2fc87de --- /dev/null +++ b/packages/pieces/community/clearout/README.md @@ -0,0 +1,7 @@ +# pieces-clearout + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-clearout` to build the library. diff --git a/packages/pieces/community/clearout/package.json b/packages/pieces/community/clearout/package.json new file mode 100644 index 0000000..6711c5b --- /dev/null +++ b/packages/pieces/community/clearout/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-clearout", + "version": "0.0.5" +} \ No newline at end of file diff --git a/packages/pieces/community/clearout/project.json b/packages/pieces/community/clearout/project.json new file mode 100644 index 0000000..d411b59 --- /dev/null +++ b/packages/pieces/community/clearout/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-clearout", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/clearout/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/clearout", + "tsConfig": "packages/pieces/community/clearout/tsconfig.lib.json", + "packageJson": "packages/pieces/community/clearout/package.json", + "main": "packages/pieces/community/clearout/src/index.ts", + "assets": [ + "packages/pieces/community/clearout/*.md", + { + "input": "packages/pieces/community/clearout/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-clearout {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/clearout/src/index.ts b/packages/pieces/community/clearout/src/index.ts new file mode 100644 index 0000000..e744711 --- /dev/null +++ b/packages/pieces/community/clearout/src/index.ts @@ -0,0 +1,28 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { instantVerifyAction } from './lib/actions/instant-verify'; +import { clearoutAuth } from './lib/auth'; + +export const clearout = createPiece({ + displayName: 'Clearout', + description: 'Bulk email validation and verification', + auth: clearoutAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/clearout.png', + categories: [PieceCategory.SALES_AND_CRM], + authors: ["joeworkman","kishanprmr","MoShizzle","abuaboud"], + actions: [ + instantVerifyAction, + createCustomApiCallAction({ + baseUrl: () => 'https://api.clearout.io/v2', // Replace with the actual base URL + auth: clearoutAuth, + authMapping: async (auth) => ({ + Authorization: `${(auth as { apiKey: string }).apiKey}`, + }), + }), + ], + triggers: [], +}); + +// Clearout API Docs https://docs.clearout.io/api.html diff --git a/packages/pieces/community/clearout/src/lib/actions/instant-verify.ts b/packages/pieces/community/clearout/src/lib/actions/instant-verify.ts new file mode 100644 index 0000000..7d527ae --- /dev/null +++ b/packages/pieces/community/clearout/src/lib/actions/instant-verify.ts @@ -0,0 +1,22 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { instantVerify } from '../api'; +import { clearoutAuth } from '../auth'; + +export const instantVerifyAction = createAction({ + name: 'instant_verify', + auth: clearoutAuth, + displayName: 'Instant Verify', + description: 'Instant Verify an email address', + props: { + email: Property.ShortText({ + displayName: 'Email', + description: 'Email address to verify', + required: true, + }), + }, + async run(context) { + return await instantVerify(context.auth, { + email: context.propsValue.email, + }); + }, +}); diff --git a/packages/pieces/community/clearout/src/lib/api.ts b/packages/pieces/community/clearout/src/lib/api.ts new file mode 100644 index 0000000..9c288f2 --- /dev/null +++ b/packages/pieces/community/clearout/src/lib/api.ts @@ -0,0 +1,54 @@ +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { ClearoutAuthType } from './auth'; + +export type KeyValuePair = { + [key: string]: string | boolean | object | undefined; +}; + +const clearoutAPI = async ( + api: string, + auth: ClearoutAuthType, + method: HttpMethod = HttpMethod.GET, + body: KeyValuePair = {} +) => { + const baseUrl = 'https://api.clearout.io/v2/'; + const request: HttpRequest = { + body: body, + method: method, + url: `${baseUrl}${api}`, + headers: { + 'Content-Type': 'application/json', + Authorization: `${auth.apiKey}`, + }, + }; + const response = await httpClient.sendRequest(request); + + if (response.status !== 200) { + throw new Error(`Bonjoro API error: ${response.status} ${response.body}`); + } + + let data = []; + data = response.body['data']; + + return { + success: true, + data: data, + }; +}; + +export async function getCredits(auth: ClearoutAuthType) { + const api = 'email_verify/getcredits'; + return clearoutAPI(api, auth); +} + +export async function instantVerify( + auth: ClearoutAuthType, + data: KeyValuePair +) { + const api = 'email_verify/instant'; + return clearoutAPI(api, auth, HttpMethod.POST, data); +} diff --git a/packages/pieces/community/clearout/src/lib/auth.ts b/packages/pieces/community/clearout/src/lib/auth.ts new file mode 100644 index 0000000..cae915e --- /dev/null +++ b/packages/pieces/community/clearout/src/lib/auth.ts @@ -0,0 +1,45 @@ +import { PieceAuth } from '@activepieces/pieces-framework'; +import { getCredits } from './api'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export type ClearoutAuthType = { apiKey: string }; + +export const clearoutAuth = PieceAuth.CustomAuth({ + description: + 'Authenticate with your Clearout account. Get your API token from your Clearout account under Settings > API.', + props: { + apiKey: PieceAuth.SecretText({ + displayName: 'API Token', + description: 'The API token for your Clearout account', + required: true, + }), + }, + validate: async ({ auth }) => { + try { + await validateAuth(auth); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: (e as Error)?.message, + }; + } + }, + required: true, +}); + +const validateAuth = async (auth: ClearoutAuthType) => { + await propsValidation.validateZod(auth, { + apiKey: z.string().min(1), + }); + + const response = await getCredits(auth); + if (response.success !== true) { + throw new Error( + 'Authentication failed. Please check your domain and API key and try again.' + ); + } +}; diff --git a/packages/pieces/community/clearout/tsconfig.json b/packages/pieces/community/clearout/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/clearout/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/clearout/tsconfig.lib.json b/packages/pieces/community/clearout/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/clearout/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/clickup/.babelrc b/packages/pieces/community/clickup/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/clickup/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/clickup/.eslintrc.json b/packages/pieces/community/clickup/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/clickup/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/clickup/README.md b/packages/pieces/community/clickup/README.md new file mode 100644 index 0000000..c7df533 --- /dev/null +++ b/packages/pieces/community/clickup/README.md @@ -0,0 +1,7 @@ +# pieces-clickup + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-clickup` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/clickup/package.json b/packages/pieces/community/clickup/package.json new file mode 100644 index 0000000..e3cba41 --- /dev/null +++ b/packages/pieces/community/clickup/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-clickup", + "version": "0.6.0" +} diff --git a/packages/pieces/community/clickup/project.json b/packages/pieces/community/clickup/project.json new file mode 100644 index 0000000..ddbd119 --- /dev/null +++ b/packages/pieces/community/clickup/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-clickup", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/clickup/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/clickup", + "tsConfig": "packages/pieces/community/clickup/tsconfig.lib.json", + "packageJson": "packages/pieces/community/clickup/package.json", + "main": "packages/pieces/community/clickup/src/index.ts", + "assets": [ + "packages/pieces/community/clickup/*.md", + { + "input": "packages/pieces/community/clickup/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/clickup/src/index.ts b/packages/pieces/community/clickup/src/index.ts new file mode 100644 index 0000000..8ce0533 --- /dev/null +++ b/packages/pieces/community/clickup/src/index.ts @@ -0,0 +1,100 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createClickupTaskComment } from './lib/actions/comments/create-task-comment'; +import { getClickupTaskComments } from './lib/actions/comments/get-task-comments'; +import { getClickupAccessibleCustomFields } from './lib/actions/custom-fields/get-accessible-custom-fields'; +import { setClickupCustomFieldValue } from './lib/actions/custom-fields/set-custom-fields-value'; +import { createClickupFolderlessList } from './lib/actions/lists/create-folderless-list'; +import { getClickupList } from './lib/actions/lists/get-list'; +import { getClickupSpace } from './lib/actions/spaces/get-space'; +import { getClickupSpaces } from './lib/actions/spaces/get-spaces'; +import { createClickupSubtask } from './lib/actions/tasks/create-subtask'; +import { createClickupTask } from './lib/actions/tasks/create-task'; +import { createClickupTaskFromTemplate } from './lib/actions/tasks/create-task-from-template'; +import { deleteClickupTask } from './lib/actions/tasks/delete-task'; +import { filterClickupWorkspaceTasks } from './lib/actions/tasks/filter-workspace-tasks'; +import { filterClickupWorkspaceTimeEntries } from './lib/actions/tasks/filter-workspace-time-entries'; +import { getClickupTask } from './lib/actions/tasks/get-task'; +import { updateClickupTask } from './lib/actions/tasks/update-task'; +import { clickupTriggers as triggers } from './lib/triggers'; +import { getClickupChannels } from './lib/actions/chat/get-channels'; +import { getClickupChannelMessages } from './lib/actions/chat/get-channel-messages'; +import { createClickupChannel } from './lib/actions/chat/create-channel'; +import { createClickupChannelInSpaceFolderOrList } from './lib/actions/chat/create-channel-in-space-folder-list'; +import { getClickupChannel } from './lib/actions/chat/get-channel'; +import { createClickupMessage } from './lib/actions/chat/create-message'; +import { createClickupMessageReply } from './lib/actions/chat/create-message-reply'; +import { createClickupMessageReaction } from './lib/actions/chat/create-message-reaction'; +import { getClickupMessageReactions } from './lib/actions/chat/get-message-reactions'; +import { getClickupMessageReplies } from './lib/actions/chat/get-message-replies'; +import { updateClickupMessage } from './lib/actions/chat/update-message'; +import { deleteClickupMessage } from './lib/actions/chat/delete-message'; +import { deleteClickupMessageReaction } from './lib/actions/chat/delete-message-reaction'; +import { getClickupTaskByName } from './lib/actions/tasks/get-task-by-name'; + +export const clickupAuth = PieceAuth.OAuth2({ + description: '', + authUrl: 'https://app.clickup.com/api', + tokenUrl: 'https://api.clickup.com/api/v2/oauth/token', + required: true, + scope: [], +}); + +export const clickup = createPiece({ + displayName: 'ClickUp', + description: 'All-in-one productivity platform', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/clickup.png', + categories: [PieceCategory.PRODUCTIVITY], + auth: clickupAuth, + actions: [ + createClickupTask, + createClickupTaskFromTemplate, + createClickupFolderlessList, + createClickupTaskComment, + createClickupSubtask, + createClickupChannel, + createClickupChannelInSpaceFolderOrList, + createClickupMessage, + createClickupMessageReaction, + createClickupMessageReply, + getClickupList, + getClickupTask, + getClickupTaskByName, + getClickupSpace, + getClickupSpaces, + getClickupTaskComments, + getClickupChannel, + getClickupChannels, + getClickupChannelMessages, + getClickupMessageReactions, + getClickupMessageReplies, + filterClickupWorkspaceTasks, + filterClickupWorkspaceTimeEntries, + updateClickupTask, + updateClickupMessage, + deleteClickupMessage, + deleteClickupMessageReaction, + deleteClickupTask, + getClickupAccessibleCustomFields, + setClickupCustomFieldValue, + createCustomApiCallAction({ + auth: clickupAuth, + baseUrl: () => { + return 'https://api.clickup.com/api/v2/'; + }, + authMapping: async (auth) => { + return { + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }; + }, + }), + ], + authors: ["kanarelo","kishanprmr","MoShizzle","khaledmashaly","abuaboud","AbdulTheActivePiecer"], + triggers, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/chat/create-channel-in-space-folder-list.ts b/packages/pieces/community/clickup/src/lib/actions/chat/create-channel-in-space-folder-list.ts new file mode 100644 index 0000000..15f1314 --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/chat/create-channel-in-space-folder-list.ts @@ -0,0 +1,80 @@ +import { Property } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { callClickUpApi3, clickupCommon } from '../../common'; +import { clickupAuth } from '../../..'; +import { createAction } from '@activepieces/pieces-framework'; + +export const createClickupChannelInSpaceFolderOrList = createAction({ + auth: clickupAuth, + name: 'create_channel_in_space_folder_list', + description: + 'Creates a channel in a ClickUp workspace in a space, folder or list', + displayName: 'Create Channel in Space/Folder/List', + props: { + workspace_id: clickupCommon.workspace_id(), + description: Property.ShortText({ + description: 'Description of the channel', + displayName: 'Channel Description', + required: false, + defaultValue: '', + }), + topic: Property.ShortText({ + description: 'Topic of the channel', + displayName: 'Channel Topic', + required: false, + defaultValue: '', + }), + locationType: Property.StaticDropdown({ + description: 'Type of location', + displayName: 'Location Type', + required: true, + options: { + options: [ + { label: 'Folder', value: 'folder' }, + { label: 'Space', value: 'space' }, + { label: 'List', value: 'list' }, + ], + }, + defaultValue: 'folder', + }), + locationId: Property.ShortText({ + description: 'ID of the location', + displayName: 'Location ID', + required: true, + }), + // TODO: add user ids + visibility: Property.StaticDropdown({ + description: 'Visibility of the channel', + displayName: 'Channel Visibility', + required: true, + options: { + options: [ + { label: 'Public', value: 'PUBLIC' }, + { label: 'Private', value: 'PRIVATE' }, + ], + }, + defaultValue: 'public', + }), + }, + + async run(configValue) { + const { workspace_id, description, visibility, locationType, locationId,topic } = + configValue.propsValue; + const response = await callClickUpApi3( + HttpMethod.POST, + `workspaces/${workspace_id}/chat/channels/location`, + getAccessTokenOrThrow(configValue.auth), + { + topic, + description, + visibility, + location: { + id: locationId, + type: locationType, + }, + }, + {} + ); + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/chat/create-channel.ts b/packages/pieces/community/clickup/src/lib/actions/chat/create-channel.ts new file mode 100644 index 0000000..78cc58d --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/chat/create-channel.ts @@ -0,0 +1,64 @@ +import { Property } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { callClickUpApi3, clickupCommon } from '../../common'; +import { clickupAuth } from '../../../'; +import { createAction } from '@activepieces/pieces-framework'; + +export const createClickupChannel = createAction({ + auth: clickupAuth, + name: 'create_channel', + description: 'Creates a channel in a ClickUp workspace', + displayName: 'Create Channel', + props: { + workspace_id: clickupCommon.workspace_id(), + name: Property.ShortText({ + description: 'Name of the channel', + displayName: 'Channel Name', + required: true, + defaultValue: '', + }), + description: Property.ShortText({ + description: 'Description of the channel', + displayName: 'Channel Description', + required: false, + defaultValue: '', + }), + topic: Property.ShortText({ + description: 'Topic of the channel', + displayName: 'Channel Topic', + required: false, + defaultValue: '', + }), + // TODO: add user ids + visibility: Property.StaticDropdown({ + description: 'Visibility of the channel', + displayName: 'Channel Visibility', + required: true, + options: { + options: [ + { label: 'Public', value: 'PUBLIC' }, + { label: 'Private', value: 'PRIVATE' }, + ], + }, + defaultValue: 'public', + }), + }, + + async run(configValue) { + const { workspace_id, name, description, visibility,topic } = + configValue.propsValue; + const response = await callClickUpApi3( + HttpMethod.POST, + `workspaces/${workspace_id}/chat/channels`, + getAccessTokenOrThrow(configValue.auth), + { + name, + topic, + description, + visibility, + }, + {} + ); + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/chat/create-message-reaction.ts b/packages/pieces/community/clickup/src/lib/actions/chat/create-message-reaction.ts new file mode 100644 index 0000000..8970cc6 --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/chat/create-message-reaction.ts @@ -0,0 +1,37 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { Property } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { callClickUpApi3, clickupCommon } from '../../common'; +import { clickupAuth } from '../../../'; + +export const createClickupMessageReaction = createAction({ + auth: clickupAuth, + name: 'create_message_reaction', + description: 'Creates a reaction to a message in a ClickUp channel', + displayName: 'Create Message Reaction', + props: { + workspace_id: clickupCommon.workspace_id(), + message_id: Property.ShortText({ + description: 'ID of the message to create reaction for', + displayName: 'Message ID', + required: true, + }), + emoji: Property.ShortText({ + description: 'Emoji to react with', + displayName: 'Emoji', + required: true, + }), + }, + + async run(configValue) { + const { workspace_id, message_id, emoji } = configValue.propsValue; + const response = await callClickUpApi3( + HttpMethod.POST, + `workspaces/${workspace_id}/chat/messages/${message_id}/reactions`, + getAccessTokenOrThrow(configValue.auth), + { emoji }, + {} + ); + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/chat/create-message-reply.ts b/packages/pieces/community/clickup/src/lib/actions/chat/create-message-reply.ts new file mode 100644 index 0000000..83567ff --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/chat/create-message-reply.ts @@ -0,0 +1,52 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { Property } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { callClickUpApi3, clickupCommon } from '../../common'; +import { clickupAuth } from '../../../'; + +export const createClickupMessageReply = createAction({ + auth: clickupAuth, + name: 'create_message_reply', + description: 'Creates a reply to a message in a ClickUp channel', + displayName: 'Create Message Reply', + props: { + workspace_id: clickupCommon.workspace_id(), + message_id: Property.ShortText({ + description: 'ID of the message to reply to', + displayName: 'Message ID', + required: true, + }), + content: Property.LongText({ + description: 'Content of the message', + displayName: 'Message Content', + required: true, + }), + type: Property.StaticDropdown({ + description: 'Type of the message', + displayName: 'Message Type', + required: true, + options: { + options: [ + { label: 'Message', value: 'message' }, + { label: 'Post', value: 'post' }, + ], + }, + defaultValue: 'message', + }), + }, + + async run(configValue) { + const { workspace_id, message_id, content, type } = configValue.propsValue; + const response = await callClickUpApi3( + HttpMethod.POST, + `workspaces/${workspace_id}/chat/messages/${message_id}/replies`, + getAccessTokenOrThrow(configValue.auth), + { + content, + type, + }, + {} + ); + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/chat/create-message.ts b/packages/pieces/community/clickup/src/lib/actions/chat/create-message.ts new file mode 100644 index 0000000..563c65b --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/chat/create-message.ts @@ -0,0 +1,48 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { Property } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { callClickUpApi3, clickupCommon } from '../../common'; +import { clickupAuth } from '../../../'; + +export const createClickupMessage = createAction({ + auth: clickupAuth, + name: 'create_message', + description: 'Creates a message in a ClickUp channel', + displayName: 'Create Message', + props: { + workspace_id: clickupCommon.workspace_id(), + channel_id: clickupCommon.channel_id(), + content: Property.LongText({ + description: 'Content of the message', + displayName: 'Message Content', + required: true, + }), + type: Property.StaticDropdown({ + description: 'Type of the message', + displayName: 'Message Type', + required: true, + options: { + options: [ + { label: 'Message', value: 'message' }, + { label: 'Post', value: 'post' }, + ], + }, + defaultValue: 'message', + }), + }, + + async run(configValue) { + const { workspace_id, channel_id, content, type } = configValue.propsValue; + const response = await callClickUpApi3( + HttpMethod.POST, + `workspaces/${workspace_id}/chat/channels/${channel_id}/messages`, + getAccessTokenOrThrow(configValue.auth), + { + content, + type, + }, + {} + ); + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/chat/delete-message-reaction.ts b/packages/pieces/community/clickup/src/lib/actions/chat/delete-message-reaction.ts new file mode 100644 index 0000000..0fcda49 --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/chat/delete-message-reaction.ts @@ -0,0 +1,37 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { Property } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { callClickUpApi3, clickupCommon } from '../../common'; +import { clickupAuth } from '../../../'; + +export const deleteClickupMessageReaction = createAction({ + auth: clickupAuth, + name: 'delete_message_reaction', + description: 'Deletes a reaction from a message in a ClickUp channel', + displayName: 'Delete Message Reaction', + props: { + workspace_id: clickupCommon.workspace_id(), + message_id: Property.ShortText({ + description: 'ID of the message to delete reaction from', + displayName: 'Message ID', + required: true, + }), + reaction_id: Property.ShortText({ + description: 'ID of the reaction to delete', + displayName: 'Reaction ID', + required: true, + }), + }, + + async run(configValue) { + const { workspace_id, message_id, reaction_id } = configValue.propsValue; + const response = await callClickUpApi3( + HttpMethod.DELETE, + `workspaces/${workspace_id}/chat/messages/${message_id}/reactions/${reaction_id}`, + getAccessTokenOrThrow(configValue.auth), + {}, + {} + ); + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/chat/delete-message.ts b/packages/pieces/community/clickup/src/lib/actions/chat/delete-message.ts new file mode 100644 index 0000000..2ea47b6 --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/chat/delete-message.ts @@ -0,0 +1,32 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { Property } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { callClickUpApi3, clickupCommon } from '../../common'; +import { clickupAuth } from '../../../'; + +export const deleteClickupMessage = createAction({ + auth: clickupAuth, + name: 'delete_message', + description: 'Deletes a message in a ClickUp channel', + displayName: 'Delete Message', + props: { + workspace_id: clickupCommon.workspace_id(), + message_id: Property.ShortText({ + description: 'ID of the message to delete', + displayName: 'Message ID', + required: true, + }), + }, + + async run(configValue) { + const { workspace_id, message_id } = configValue.propsValue; + const response = await callClickUpApi3( + HttpMethod.DELETE, + `workspaces/${workspace_id}/chat/messages/${message_id}`, + getAccessTokenOrThrow(configValue.auth), + {}, + {} + ); + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/chat/get-channel-messages.ts b/packages/pieces/community/clickup/src/lib/actions/chat/get-channel-messages.ts new file mode 100644 index 0000000..4be5160 --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/chat/get-channel-messages.ts @@ -0,0 +1,64 @@ +import { Property } from '@activepieces/pieces-framework'; +import { + HttpMethod, + getAccessTokenOrThrow, + propsValidation, +} from '@activepieces/pieces-common'; +import { callClickUpApi3, clickupCommon } from '../../common'; +import { clickupAuth } from '../../../'; +import { z } from 'zod'; +import { createAction } from '@activepieces/pieces-framework'; + +export const getClickupChannelMessages = createAction({ + auth: clickupAuth, + name: 'get_channel_messages', + description: 'Gets all messages in a ClickUp channel', + displayName: 'Get Channel Messages', + props: { + workspace_id: clickupCommon.workspace_id(), + channel_id: clickupCommon.channel_id(), + limit: Property.Number({ + description: 'Limit the number of messages returned', + displayName: 'Limit', + required: false, + defaultValue: 50, + }), + content_format: Property.StaticDropdown({ + description: 'Format the content of the messages', + displayName: 'Format Content', + required: false, + options: { + options: [ + { label: 'Markdown', value: 'text/md' }, + { label: 'Plain Text', value: 'text/plain' }, + ], + }, + defaultValue: 'text/md', + }), + }, + + async run(configValue) { + await propsValidation.validateZod(configValue.propsValue, { + limit: z + .number() + .min(0) + .max(100, 'You can fetch between 1 and 100 messages'), + }); + + const { workspace_id, channel_id, limit, content_format } = + configValue.propsValue; + + const response = await callClickUpApi3( + HttpMethod.GET, + `workspaces/${workspace_id}/chat/channels/${channel_id}/messages`, + getAccessTokenOrThrow(configValue.auth), + undefined, + { + limit, + content_format, + } + ); + + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/chat/get-channel.ts b/packages/pieces/community/clickup/src/lib/actions/chat/get-channel.ts new file mode 100644 index 0000000..e582e13 --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/chat/get-channel.ts @@ -0,0 +1,27 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { callClickUpApi3, clickupCommon } from '../../common'; +import { clickupAuth } from '../../../'; + +export const getClickupChannel = createAction({ + auth: clickupAuth, + name: 'get_channel', + description: 'Gets a channel in a ClickUp workspace', + displayName: 'Get Channel', + props: { + workspace_id: clickupCommon.workspace_id(), + channel_id: clickupCommon.channel_id(), + }, + + async run(configValue) { + const { workspace_id, channel_id } = configValue.propsValue; + const response = await callClickUpApi3( + HttpMethod.GET, + `workspaces/${workspace_id}/chat/channels/${channel_id}`, + getAccessTokenOrThrow(configValue.auth), + undefined, + {} + ); + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/chat/get-channels.ts b/packages/pieces/community/clickup/src/lib/actions/chat/get-channels.ts new file mode 100644 index 0000000..709533e --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/chat/get-channels.ts @@ -0,0 +1,56 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpMethod, + getAccessTokenOrThrow, + propsValidation, +} from '@activepieces/pieces-common'; +import { callClickUpApi3, clickupCommon } from '../../common'; +import { clickupAuth } from '../../../'; +import { z } from 'zod'; + +export const getClickupChannels = createAction({ + auth: clickupAuth, + name: 'get_channels', + description: 'Gets all channels in a ClickUp workspace', + displayName: 'Get Channels', + props: { + workspace_id: clickupCommon.workspace_id(), + include_hidden: Property.Checkbox({ + description: 'Include hidden channels', + displayName: 'Include Hidden', + required: false, + defaultValue: false, + }), + limit: Property.Number({ + description: 'Limit the number of channels returned', + displayName: 'Limit', + required: false, + defaultValue: 50, + }), + }, + + async run(configValue) { + await propsValidation.validateZod(configValue.propsValue, { + limit: z + .number() + .min(0) + .max(100, 'You can fetch between 1 and 100 messages'), + }); + + const { workspace_id, include_hidden, limit } = configValue.propsValue; + + const response = await callClickUpApi3( + HttpMethod.GET, + `workspaces/${workspace_id}/chat/channels`, + getAccessTokenOrThrow(configValue.auth), + undefined, + { + limit, + is_follower: false, + include_hidden, + } + ); + + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/chat/get-message-reactions.ts b/packages/pieces/community/clickup/src/lib/actions/chat/get-message-reactions.ts new file mode 100644 index 0000000..ab48b1f --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/chat/get-message-reactions.ts @@ -0,0 +1,32 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { Property } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { callClickUpApi3, clickupCommon } from '../../common'; +import { clickupAuth } from '../../../'; + +export const getClickupMessageReactions = createAction({ + auth: clickupAuth, + name: 'get_message_reactions', + description: 'Gets the reactions of a message in a ClickUp channel', + displayName: 'Get Message Reactions', + props: { + workspace_id: clickupCommon.workspace_id(), + message_id: Property.ShortText({ + description: 'ID of the message to get reactions for', + displayName: 'Message ID', + required: true, + }), + }, + + async run(configValue) { + const { workspace_id, message_id } = configValue.propsValue; + const response = await callClickUpApi3( + HttpMethod.GET, + `workspaces/${workspace_id}/chat/messages/${message_id}/reactions`, + getAccessTokenOrThrow(configValue.auth), + {}, + {} + ); + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/chat/get-message-replies.ts b/packages/pieces/community/clickup/src/lib/actions/chat/get-message-replies.ts new file mode 100644 index 0000000..26526ef --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/chat/get-message-replies.ts @@ -0,0 +1,32 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { Property } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { callClickUpApi3, clickupCommon } from '../../common'; +import { clickupAuth } from '../../../'; + +export const getClickupMessageReplies = createAction({ + auth: clickupAuth, + name: 'get_message_replies', + description: 'Gets the replies of a message in a ClickUp channel', + displayName: 'Get Message Replies', + props: { + workspace_id: clickupCommon.workspace_id(), + message_id: Property.ShortText({ + description: 'ID of the message to get replies for', + displayName: 'Message ID', + required: true, + }), + }, + + async run(configValue) { + const { workspace_id, message_id } = configValue.propsValue; + const response = await callClickUpApi3( + HttpMethod.GET, + `workspaces/${workspace_id}/chat/messages/${message_id}/replies`, + getAccessTokenOrThrow(configValue.auth), + {}, + {} + ); + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/chat/update-message.ts b/packages/pieces/community/clickup/src/lib/actions/chat/update-message.ts new file mode 100644 index 0000000..307494f --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/chat/update-message.ts @@ -0,0 +1,51 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { Property } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { callClickUpApi3, clickupCommon } from '../../common'; +import { clickupAuth } from '../../../'; + +export const updateClickupMessage = createAction({ + auth: clickupAuth, + name: 'update_message', + description: 'Updates a message in a ClickUp channel', + displayName: 'Update Message', + props: { + workspace_id: clickupCommon.workspace_id(), + message_id: Property.ShortText({ + description: 'ID of the message to update', + displayName: 'Message ID', + required: true, + }), + content: Property.LongText({ + description: 'Content of the message', + displayName: 'Message Content', + required: true, + }), + content_format: Property.StaticDropdown({ + description: 'Format of the message content', + displayName: 'Message Content Format', + required: true, + options: { + options: [ + { label: 'Markdown', value: 'text/md' }, + { label: 'Plain Text', value: 'text/plain' }, + ], + }, + }), + }, + + async run(configValue) { + const { workspace_id, message_id, content, content_format } = configValue.propsValue; + const response = await callClickUpApi3( + HttpMethod.PATCH, + `workspaces/${workspace_id}/chat/messages/${message_id}`, + getAccessTokenOrThrow(configValue.auth), + { + content, + content_format, + }, + {} + ); + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/comments/create-task-comment.ts b/packages/pieces/community/clickup/src/lib/actions/comments/create-task-comment.ts new file mode 100644 index 0000000..8c4488c --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/comments/create-task-comment.ts @@ -0,0 +1,60 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { clickupCommon, callClickUpApi } from '../../common'; +import { clickupAuth } from '../../../'; + +export const createClickupTaskComment = createAction({ + auth: clickupAuth, + name: 'create_task_comments', + description: 'Creates a comment on a task in ClickUp', + displayName: 'Create Task Comment', + props: { + workspace_id: clickupCommon.workspace_id(), + space_id: clickupCommon.space_id(), + list_id: clickupCommon.list_id(), + task_id: clickupCommon.task_id(), + comment: Property.LongText({ + description: 'Comment to make on the task', + displayName: 'Comment', + required: true, + }), + assignee_id: clickupCommon.single_assignee_id( + false, + 'Assignee Id', + 'ID of assignee for Task Comment' + ), + }, + async run(configValue) { + const { task_id, comment } = configValue.propsValue; + + let assignee_id = configValue.propsValue.assignee_id; + + if (!assignee_id) { + const user_request = await callClickUpApi( + HttpMethod.GET, + `/user`, + getAccessTokenOrThrow(configValue.auth), + {} + ); + + if (user_request.body['user'] === undefined) { + throw 'Please connect to your ClickUp account'; + } + + assignee_id = user_request.body['user']['id']; + } + + const response = await callClickUpApi( + HttpMethod.POST, + `/task/${task_id}/comment`, + getAccessTokenOrThrow(configValue.auth), + { + comment_text: comment, + assignee: assignee_id, + notify_all: true, + } + ); + + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/comments/get-task-comments.ts b/packages/pieces/community/clickup/src/lib/actions/comments/get-task-comments.ts new file mode 100644 index 0000000..393be5a --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/comments/get-task-comments.ts @@ -0,0 +1,29 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { callClickUpApi } from '../../common'; +import { clickupAuth } from '../../../'; + +export const getClickupTaskComments = createAction({ + auth: clickupAuth, + name: 'get_task_comments', + description: 'Gets comments from a task in ClickUp', + displayName: 'Get Task Comments', + props: { + task_id: Property.ShortText({ + description: 'The ID of the task to get', + displayName: 'Task ID', + required: true, + }), + }, + async run(configValue) { + const { task_id } = configValue.propsValue; + const response = await callClickUpApi( + HttpMethod.GET, + `/task/${task_id}/comment`, + getAccessTokenOrThrow(configValue.auth), + {} + ); + + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/custom-fields/get-accessible-custom-fields.ts b/packages/pieces/community/clickup/src/lib/actions/custom-fields/get-accessible-custom-fields.ts new file mode 100644 index 0000000..4a6c9fb --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/custom-fields/get-accessible-custom-fields.ts @@ -0,0 +1,22 @@ +import { createAction } from "@activepieces/pieces-framework"; +import { getAccessTokenOrThrow } from "@activepieces/pieces-common"; +import { clickupCommon, listAccessibleCustomFields } from "../../common" +import { clickupAuth } from "../../../"; + +export const getClickupAccessibleCustomFields = createAction({ + auth: clickupAuth, + name: 'get_accessible_custom_fields', + displayName: 'Get Accessible Custom Fields', + description: 'View the Custom Fields available on tasks in a specific List.', + props: { + workspace_id: clickupCommon.workspace_id(true), + space_id: clickupCommon.space_id(true), + list_id: clickupCommon.list_id(true) + }, + async run(configValue) { + const { list_id } = configValue.propsValue; + const auth = getAccessTokenOrThrow(configValue.auth) + + return (await listAccessibleCustomFields(auth, list_id as unknown as string)).fields + } +}) \ No newline at end of file diff --git a/packages/pieces/community/clickup/src/lib/actions/custom-fields/set-custom-fields-value.ts b/packages/pieces/community/clickup/src/lib/actions/custom-fields/set-custom-fields-value.ts new file mode 100644 index 0000000..de7c27a --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/custom-fields/set-custom-fields-value.ts @@ -0,0 +1,35 @@ +import { Property, createAction } from "@activepieces/pieces-framework"; +import { HttpMethod, getAccessTokenOrThrow } from "@activepieces/pieces-common"; +import { callClickUpApi, clickupCommon } from "../../common" +import { clickupAuth } from "../../../"; + +export const setClickupCustomFieldValue = createAction({ + auth: clickupAuth, + name: 'set_custom_fields_value', + displayName: 'Set Custom Field Value', + description: 'Add data to a Custom field on a task.', + props: { + workspace_id: clickupCommon.workspace_id(true), + space_id: clickupCommon.space_id(true), + list_id: clickupCommon.list_id(false), + task_id: clickupCommon.task_id(true), + field_id: clickupCommon.field_id(true), + value: Property.LongText({ + displayName: "Value", + description: "The new value to be set", + required: true + }) + }, + async run(configValue) { + const { field_id, task_id, value } = configValue.propsValue; + + const response = await callClickUpApi>( + HttpMethod.POST, + `task/${task_id}/field/${field_id}`, + getAccessTokenOrThrow(configValue.auth), + { value: value } + ); + + return response.body + } +}) \ No newline at end of file diff --git a/packages/pieces/community/clickup/src/lib/actions/lists/create-folderless-list.ts b/packages/pieces/community/clickup/src/lib/actions/lists/create-folderless-list.ts new file mode 100644 index 0000000..cf500cf --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/lists/create-folderless-list.ts @@ -0,0 +1,33 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { clickupCommon, callClickUpApi } from '../../common'; +import { clickupAuth } from '../../../'; + +export const createClickupFolderlessList = createAction({ + auth: clickupAuth, + name: 'create_folderless_list', + description: 'Create a new folderless list in a ClickUp workspace and space', + displayName: 'Create Folderless List', + props: { + workspace_id: clickupCommon.workspace_id(), + space_id: clickupCommon.space_id(), + name: Property.ShortText({ + description: 'The name of the list to create', + displayName: 'List Name', + required: true, + }), + }, + async run(configValue) { + const { space_id, name } = configValue.propsValue; + const response = await callClickUpApi( + HttpMethod.POST, + `space/${space_id}/list`, + getAccessTokenOrThrow(configValue.auth), + { + name, + } + ); + + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/lists/get-list.ts b/packages/pieces/community/clickup/src/lib/actions/lists/get-list.ts new file mode 100644 index 0000000..9cf3932 --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/lists/get-list.ts @@ -0,0 +1,30 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { callClickUpApi } from '../../common'; +import { clickupAuth } from '../../../'; + +export const getClickupList = createAction({ + auth: clickupAuth, + + name: 'get_list', + description: 'Gets a list in a ClickUp', + displayName: 'Get List', + props: { + list_id: Property.ShortText({ + description: 'The id of the list to get', + displayName: 'List ID', + required: true, + }), + }, + async run(configValue) { + const { list_id } = configValue.propsValue; + const response = await callClickUpApi( + HttpMethod.GET, + `list/${list_id}`, + getAccessTokenOrThrow(configValue.auth), + {} + ); + + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/spaces/get-space.ts b/packages/pieces/community/clickup/src/lib/actions/spaces/get-space.ts new file mode 100644 index 0000000..da5186a --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/spaces/get-space.ts @@ -0,0 +1,25 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; + +import { callClickUpApi, clickupCommon } from '../../common'; +import { clickupAuth } from '../../../'; + +export const getClickupSpace = createAction({ + auth: clickupAuth, + name: 'get_space', + description: 'Gets a space in a ClickUp', + displayName: 'Get Space', + props: { + space_id: clickupCommon.space_id(), + }, + async run(configValue) { + const { space_id } = configValue.propsValue; + const response = await callClickUpApi( + HttpMethod.GET, + `space/${space_id}`, + getAccessTokenOrThrow(configValue.auth), + {} + ); + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/spaces/get-spaces.ts b/packages/pieces/community/clickup/src/lib/actions/spaces/get-spaces.ts new file mode 100644 index 0000000..57fa87e --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/spaces/get-spaces.ts @@ -0,0 +1,25 @@ +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { clickupCommon, callClickUpApi } from '../../common'; +import { clickupAuth } from '../../../'; +import { createAction } from '@activepieces/pieces-framework'; + +export const getClickupSpaces = createAction({ + auth: clickupAuth, + name: 'get_spaces', + description: 'Gets spaces in a ClickUp workspace', + displayName: 'Get Spaces', + props: { + team_id: clickupCommon.workspace_id(), + }, + async run(configValue) { + const { team_id } = configValue.propsValue; + const response = await callClickUpApi( + HttpMethod.GET, + `team/${team_id}/space`, + getAccessTokenOrThrow(configValue.auth), + {} + ); + + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/tasks/create-subtask.ts b/packages/pieces/community/clickup/src/lib/actions/tasks/create-subtask.ts new file mode 100644 index 0000000..9542596 --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/tasks/create-subtask.ts @@ -0,0 +1,212 @@ +import { + OAuth2PropertyValue, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { MarkdownVariant } from '@activepieces/shared'; + +import { + clickupCommon, + callClickUpApi, + listAccessibleCustomFields, +} from '../../common'; +import { clickupAuth } from '../../../'; + +export const createClickupSubtask = createAction({ + auth: clickupAuth, + name: 'create_subtask', + description: 'Creates a subtask in ClickUp', + displayName: 'Create Subtask', + props: { + workspace_id: clickupCommon.workspace_id(), + space_id: clickupCommon.space_id(), + list_id: clickupCommon.list_id(), + task_id: clickupCommon.task_id(), + name: Property.ShortText({ + description: 'The name of the subtask to create', + displayName: 'Subtask Name', + required: true, + }), + status_id: clickupCommon.status_id(), + priority_id: clickupCommon.priority_id(), + assignee_id: clickupCommon.assignee_id( + false, + 'Assignee Id', + 'ID of assignee for Clickup Subtask' + ), + description: Property.LongText({ + description: 'The description of the subtask to create', + displayName: 'Subtask Description', + required: false, + }), + is_markdown: Property.Checkbox({ + description: 'Is the description in markdown format', + displayName: 'Is Markdown', + required: false, + defaultValue: false, + }), + due_date: Property.DateTime({ + description: 'The due date of the subtask', + displayName: 'Due Date', + required: false, + }), + due_date_time: Property.Checkbox({ + description: 'Whether to include time in the due date', + displayName: 'Due Date Time', + required: false, + defaultValue: false, + }), + start_date: Property.DateTime({ + description: 'The start date of the subtask', + displayName: 'Start Date', + required: false, + }), + start_date_time: Property.Checkbox({ + description: 'Whether to include time in the start date', + displayName: 'Start Date Time', + required: false, + defaultValue: false, + }), + time_estimate: Property.Number({ + description: 'The time estimate for the subtask in milliseconds', + displayName: 'Time Estimate', + required: false, + }), + check_required_custom_fields: Property.Checkbox({ + description: 'Re-enable required custom fields validation for the subtask', + displayName: 'Check Required Custom Fields', + required: false, + defaultValue: false, + }), + custom_fields_info: Property.MarkDown({ + value: `Select custom fields\n\nFor custom dropdown fields, choose a dropdown value based on the index (in the list, the first option is index 0, second is 1, third is 2, etc.)`, + variant: MarkdownVariant.INFO, + }), + custom_fields: Property.DynamicProperties({ + displayName: 'Custom Fields', + required: true, + refreshers: ['list_id', 'auth'], + props: async ({ list_id, auth }) => { + if (!list_id || !auth) { + return {}; + } + + // Ensure `auth` is of the correct type + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + + // Fetch custom fields using clickupCommon + const { fields: customFields } = await listAccessibleCustomFields( + accessToken, + list_id.toString() + ); + + // Map custom fields to InputPropertyMap + const dynamicProps: Record = {}; + customFields.forEach((field) => { + dynamicProps[field.id] = Property.ShortText({ + displayName: field.name, + description: `Value for the custom field: ${field.name}`, + required: false, + }); + }); + + return dynamicProps; + }, + }), + }, + + async run(configValue) { + const { + list_id, + task_id, + name, + description, + status_id, + priority_id, + assignee_id, + is_markdown, + due_date, + due_date_time, + start_date, + start_date_time, + time_estimate, + check_required_custom_fields, + custom_fields, + } = configValue.propsValue; + + type SubtaskData = { + name: string; + parent: string | undefined; + status?: string | undefined | string[]; + priority?: number | undefined; + assignees?: number[] | undefined; + markdown_content?: string; + description?: string; + due_date?: number; + due_date_time?: boolean; + start_date?: number; + start_date_time?: boolean; + time_estimate?: number; + check_required_custom_fields?: boolean; + custom_fields?: { id: string; value: any }[]; + }; + + const data: SubtaskData = { + name, + parent: task_id, // Parent task ID for the subtask + status: status_id, + priority: priority_id, + assignees: assignee_id, + }; + + // Add description or markdown content + if (is_markdown && description) { + data.markdown_content = description; + } else if (description) { + data.description = description; + } + + // Convert due_date to integer format and add it + if (due_date) { + data.due_date = new Date(due_date).getTime(); + data.due_date_time = due_date_time || false; + } + + // Convert start_date to integer format and add it + if (start_date) { + data.start_date = new Date(start_date).getTime(); + data.start_date_time = start_date_time || false; + } + + // Add time estimate + if (time_estimate) { + data.time_estimate = time_estimate; + } + + // Add check_required_custom_fields + if (check_required_custom_fields) { + data.check_required_custom_fields = check_required_custom_fields; + } + + // Map custom_fields into the required format + if (custom_fields) { + data.custom_fields = Object.entries(custom_fields).map( + ([fieldId, value]) => ({ + id: fieldId, + value, + }) + ); + } + + // Make the API request + const response = await callClickUpApi( + HttpMethod.POST, + `list/${list_id}/task`, + getAccessTokenOrThrow(configValue.auth), + data + ); + + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/tasks/create-task-from-template.ts b/packages/pieces/community/clickup/src/lib/actions/tasks/create-task-from-template.ts new file mode 100644 index 0000000..366b63e --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/tasks/create-task-from-template.ts @@ -0,0 +1,36 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; + +import { clickupCommon, callClickUpApi } from '../../common'; +import { clickupAuth } from '../../../'; + +export const createClickupTaskFromTemplate = createAction({ + auth: clickupAuth, + name: 'create_task_from_template', + description: 'Create a new task from Template', + displayName: 'Create Task From Template', + props: { + workspace_id: clickupCommon.workspace_id(), + space_id: clickupCommon.space_id(), + list_id: clickupCommon.list_id(), + template_id: clickupCommon.template_id(true), + name: Property.ShortText({ + description: 'The name of the task to create', + displayName: 'Task Name', + required: true, + }), + }, + async run(configValue) { + const { list_id, name, template_id } = configValue.propsValue; + const response = await callClickUpApi( + HttpMethod.POST, + `list/${list_id}/taskTemplate/${template_id}`, + getAccessTokenOrThrow(configValue.auth), + { + name, + } + ); + + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/tasks/create-task.ts b/packages/pieces/community/clickup/src/lib/actions/tasks/create-task.ts new file mode 100644 index 0000000..14750a7 --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/tasks/create-task.ts @@ -0,0 +1,208 @@ +import { + OAuth2PropertyValue, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { MarkdownVariant } from '@activepieces/shared'; + +import { + clickupCommon, + callClickUpApi, + listAccessibleCustomFields, +} from '../../common'; +import { clickupAuth } from '../../../'; + +export const createClickupTask = createAction({ + auth: clickupAuth, + name: 'create_task', + description: 'Create a new task in a ClickUp workspace and list', + displayName: 'Create Task', + props: { + workspace_id: clickupCommon.workspace_id(), + space_id: clickupCommon.space_id(), + list_id: clickupCommon.list_id(), + name: Property.ShortText({ + description: 'The name of task', + displayName: 'Name', + required: true, + }), + status_id: clickupCommon.status_id(), + priority_id: clickupCommon.priority_id(), + assignee_id: clickupCommon.assignee_id( + false, + 'Assignee Id', + 'ID of assignee for Clickup Task' + ), + description: Property.LongText({ + description: 'The description of task', + displayName: 'Description', + required: false, + }), + is_markdown: Property.Checkbox({ + description: 'Is the description in markdown format', + displayName: 'Is Markdown', + required: false, + defaultValue: false, + }), + due_date: Property.DateTime({ + description: 'The due date of the task', + displayName: 'Due Date', + required: false, + }), + due_date_time: Property.Checkbox({ + description: 'Whether to include time in the due date', + displayName: 'Due Date Time', + required: false, + defaultValue: false, + }), + start_date: Property.DateTime({ + description: 'The start date of the task', + displayName: 'Start Date', + required: false, + }), + start_date_time: Property.Checkbox({ + description: 'Whether to include time in the start date', + displayName: 'Start Date Time', + required: false, + defaultValue: false, + }), + time_estimate: Property.Number({ + description: 'The time estimate for the task in milliseconds', + displayName: 'Time Estimate', + required: false, + }), + check_required_custom_fields: Property.Checkbox({ + description: 'Re-enable required custom fields validation for the task', + displayName: 'Check Required Custom Fields', + required: false, + defaultValue: false, + }), + custom_fields_info: Property.MarkDown({ + value: `Select custom fields\n\nFor custom dropdown fields, choose a dropdown value based on the index (in the list, the first option is index 0, second is 1, third is 2, etc.)`, + variant: MarkdownVariant.INFO, + }), + custom_fields: Property.DynamicProperties({ + displayName: 'Custom Fields', + required: true, + refreshers: ['list_id', 'auth'], + props: async ({ list_id, auth }) => { + if (!list_id || !auth) { + return {}; + } + + // Ensure `auth` is of the correct type + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + + // Fetch custom fields using clickupCommon + const { fields: customFields } = await listAccessibleCustomFields( + accessToken, + list_id.toString() + ); + + // Map custom fields to InputPropertyMap + const dynamicProps: Record = {}; + customFields.forEach((field) => { + dynamicProps[field.id] = Property.ShortText({ + displayName: field.name, + description: `Value for the custom field: ${field.name}`, + required: false, + }); + }); + + return dynamicProps; + }, + }), + }, + + async run(configValue) { + const { + list_id, + name, + description, + status_id, + priority_id, + assignee_id, + is_markdown, + due_date, + due_date_time, + start_date, + start_date_time, + time_estimate, + check_required_custom_fields, + custom_fields, + } = configValue.propsValue; + + type TaskData = { + name: string; + status?: string | undefined | string[]; + priority?: number | undefined; + assignees?: number[] | undefined; + markdown_content?: string; + description?: string; + due_date?: number; + due_date_time?: boolean; + start_date?: number; + start_date_time?: boolean; + time_estimate?: number; + check_required_custom_fields?: boolean; + custom_fields?: { id: string; value: any }[]; + }; + + const data: TaskData = { + name, + status: status_id, + priority: priority_id, + assignees: assignee_id, + }; + + // Add description or markdown content + if (is_markdown && description) { + data.markdown_content = description; + } else if (description) { + data.description = description; + } + + // Convert due_date to integer format and add it + if (due_date) { + data.due_date = new Date(due_date).getTime(); + data.due_date_time = due_date_time || false; + } + + // Convert start_date to integer format and add it + if (start_date) { + data.start_date = new Date(start_date).getTime(); + data.start_date_time = start_date_time || false; + } + + // Add time estimate + if (time_estimate) { + data.time_estimate = time_estimate; + } + + // Add check_required_custom_fields + if (check_required_custom_fields) { + data.check_required_custom_fields = check_required_custom_fields; + } + + // Map custom_fields into the required format + if (custom_fields) { + data.custom_fields = Object.entries(custom_fields).map( + ([fieldId, value]) => ({ + id: fieldId, + value, + }) + ); + } + + // Make the API request + const response = await callClickUpApi( + HttpMethod.POST, + `list/${list_id}/task`, + getAccessTokenOrThrow(configValue.auth), + data + ); + + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/tasks/delete-task.ts b/packages/pieces/community/clickup/src/lib/actions/tasks/delete-task.ts new file mode 100644 index 0000000..f68af35 --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/tasks/delete-task.ts @@ -0,0 +1,31 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; + +import { clickupCommon, callClickUpApi } from '../../common'; +import { clickupAuth } from '../../../'; + +export const deleteClickupTask = createAction({ + auth: clickupAuth, + name: 'delete_task', + description: 'Delete a task in a workspace and list', + displayName: 'Delete Task', + props: { + workspace_id: clickupCommon.workspace_id(), + space_id: clickupCommon.space_id(), + list_id: clickupCommon.list_id(), + task_id: clickupCommon.task_id() + }, + async run(configValue) { + const { + task_id, + } = configValue.propsValue; + const response = await callClickUpApi( + HttpMethod.DELETE, + `task/${task_id}`, + getAccessTokenOrThrow(configValue.auth), + undefined + ); + + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/tasks/filter-workspace-tasks.ts b/packages/pieces/community/clickup/src/lib/actions/tasks/filter-workspace-tasks.ts new file mode 100644 index 0000000..28a2c0b --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/tasks/filter-workspace-tasks.ts @@ -0,0 +1,122 @@ +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import qs from 'qs'; +import { clickupAuth } from '../../..'; +import { callClickUpApi, clickupCommon, listTags } from '../../common'; +import { ClickupTask } from '../../common/models'; + +export const filterClickupWorkspaceTasks = createAction({ + auth: clickupAuth, + name: 'list_workspace_tasks', + displayName: 'List Team Tasks', + description: + 'Retrieves the tasks that meet specific criteria from a Workspace.', + props: { + workspace_id: clickupCommon.workspace_id(true), + space_id: clickupCommon.space_id(false, true), + folder_id: clickupCommon.folder_id(false, true), + list_id: clickupCommon.list_id(false, true), + + assignees: clickupCommon.assignee_id( + false, + 'Assignee Id', + 'ID of assignee for Clickup Task' + ), + tags: Property.MultiSelectDropdown({ + displayName: 'Tags', + description: 'The tags to filter for', + refreshers: ['space_id', 'workspace_id'], + required: false, + options: async ({ auth, workspace_id, space_id }) => { + if (!auth || !workspace_id || !space_id) { + return { + disabled: true, + placeholder: + 'connect your account first and select workspace and space', + options: [], + }; + } + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + const response = await listTags(accessToken, space_id as string); + return { + disabled: false, + options: response.tags.map((tag) => { + return { + label: tag.name, + value: encodeURIComponent(tag.name), + }; + }), + }; + }, + }), + + page: Property.Number({ + displayName: 'Page', + description: 'Page to fetch (starts at 0).', + required: false, + defaultValue: 0, + }), + reverse: Property.Checkbox({ + displayName: 'Reverse', + description: 'Tasks are displayed in reverse order.', + required: false, + defaultValue: false, + }), + include_closed: Property.Checkbox({ + displayName: 'Include Closed', + description: + 'Include or exclude closed tasks. By default, they are excluded.', + required: false, + defaultValue: false, + }), + order_by: Property.StaticDropdown({ + displayName: 'Order By', + description: + 'Order by a particular field. By default, tasks are ordered by created.', + required: false, + options: { + options: [ + { value: 'id', label: 'Id' }, + { value: 'created', label: 'Created at' }, + { value: 'updated', label: 'Last updated' }, + { value: 'due_date', label: 'Due date' }, + ], + }, + }), + }, + async run(configValue) { + const { list_id, folder_id, space_id, workspace_id, ...params } = + configValue.propsValue; + const auth = getAccessTokenOrThrow(configValue.auth); + + const query: Record = { + assignees: params.assignees, + tags: params.tags, + page: params.page, + reverse: params.reverse, + include_closed: params.include_closed, + order_by: params.order_by, + }; + + if (list_id) query['list_ids'] = list_id; + if (folder_id) query['project_ids'] = folder_id; + if (space_id) query['space_ids'] = space_id; + + return ( + await callClickUpApi( + HttpMethod.GET, + `team/${workspace_id}/task?${decodeURIComponent(qs.stringify(query))}`, + auth, + undefined, + undefined, + { + 'Content-Type': 'application/json', + } + ) + ).body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/tasks/filter-workspace-time-entries.ts b/packages/pieces/community/clickup/src/lib/actions/tasks/filter-workspace-time-entries.ts new file mode 100644 index 0000000..a0505a4 --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/tasks/filter-workspace-time-entries.ts @@ -0,0 +1,94 @@ +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import qs from 'qs'; +import { clickupAuth } from '../../..'; +import { callClickUpApi, clickupCommon } from '../../common'; +import { ClickupTask } from '../../common/models'; + +export const filterClickupWorkspaceTimeEntries = createAction({ + auth: clickupAuth, + name: 'list_workspace_time_entries', + displayName: 'List Time Entries', + description: 'Retrieves time entries filtered by start and end date.', + props: { + workspace_id: clickupCommon.workspace_id(true), + + start_date: Property.DateTime({ + displayName: 'Start date', + description: '', + required: false, + }), + end_date: Property.DateTime({ + displayName: 'End date', + required: false, + }), + + space_id: clickupCommon.space_id(false), + folder_id: clickupCommon.folder_id(false), + list_id: clickupCommon.list_id(false), + task_id: clickupCommon.task_id(false, 'Task'), + + assignee: clickupCommon.assignee_id( + false, + 'Assignee Id', + 'ID of assignee for Clickup Task' + ), + + include_task_tags: Property.Checkbox({ + displayName: 'Include task tags', + description: + 'Include task tags in the response for time entries associated with tasks.', + required: false, + defaultValue: false, + }), + include_location_names: Property.Checkbox({ + displayName: 'Include location names', + description: + 'Include the names of the List, Folder, and Space along with the list_id, folder_id, and space_id.', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { task_id, list_id, folder_id, space_id, workspace_id, ...params } = + context.propsValue; + const auth = getAccessTokenOrThrow(context.auth); + + const query: Record = { + assignee: params.assignee?.join(','), + include_task_tags: params.include_task_tags, + include_location_names: params.include_location_names, + }; + + if (params.start_date) + query['start_date'] = dayjs(params.start_date).valueOf(); + + if (params.end_date) query['end_date'] = dayjs(params.end_date).valueOf(); + + if (task_id) { + query['task_id'] = task_id; + } else if (list_id) { + query['list_id'] = list_id; + } else if (folder_id) { + query['project_id'] = folder_id; + } else if (space_id) { + query['space_id'] = space_id; + } + + return ( + await callClickUpApi( + HttpMethod.GET, + `team/${workspace_id}/time_entries?${decodeURIComponent( + qs.stringify(query) + )}`, + auth, + undefined, + undefined, + { + 'Content-Type': 'application/json', + } + ) + ).body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/tasks/get-task-by-name.ts b/packages/pieces/community/clickup/src/lib/actions/tasks/get-task-by-name.ts new file mode 100644 index 0000000..8d08f20 --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/tasks/get-task-by-name.ts @@ -0,0 +1,57 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { callClickUpApi, clickupCommon } from '../../common'; +import { clickupAuth } from '../../../'; + +export const getClickupTaskByName = createAction({ + auth: clickupAuth, + name: 'get_task_by_name', + description: 'Fetches a task by name in a ClickUp list', + displayName: 'Get Task by Name', + props: { + workspace_id: clickupCommon.workspace_id(), + space_id: clickupCommon.space_id(), + list_id: clickupCommon.list_id(), + task_name: Property.ShortText({ + description: 'The name of the task to find', + displayName: 'Task Name', + required: true, + }), + }, + async run(configValue) { + const { list_id, task_name } = configValue.propsValue; + const accessToken = getAccessTokenOrThrow(configValue.auth); + + let page = 0; + const pageSize = 100; + let hasMorePages = true; + + while (hasMorePages) { + const response = await callClickUpApi( + HttpMethod.GET, + `list/${list_id}/task`, + accessToken, + undefined, + { page, limit: pageSize } + ); + + + const tasks = response.body.tasks; + + if (!tasks || tasks.length === 0) { + hasMorePages = false; + break; + } + + const matchingTask = tasks.find((task: any) => task.name === task_name); + + if (matchingTask) { + return matchingTask; + } + + page++; + } + + throw new Error(`Task with name "${task_name}" not found in list "${list_id}".`); + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/clickup/src/lib/actions/tasks/get-task.ts b/packages/pieces/community/clickup/src/lib/actions/tasks/get-task.ts new file mode 100644 index 0000000..22348b5 --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/tasks/get-task.ts @@ -0,0 +1,36 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { callClickUpApi } from '../../common'; +import { clickupAuth } from '../../../'; + +export const getClickupTask = createAction({ + auth: clickupAuth, + name: 'get_list_task', + description: 'Gets a task in a ClickUp list', + displayName: 'Get Task', + props: { + task_id: Property.ShortText({ + description: 'The ID of the task to get', + displayName: 'Task ID', + required: true, + }), + include_subtasks: Property.Checkbox({ + description: 'Include subtasks in the response', + displayName: 'Include Subtasks', + required: false, + defaultValue: false, + }), + }, + async run(configValue) { + const { task_id } = configValue.propsValue; + + const response = await callClickUpApi( + HttpMethod.GET, + `task/${task_id}`, + getAccessTokenOrThrow(configValue.auth), + undefined + ); + + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/actions/tasks/update-task.ts b/packages/pieces/community/clickup/src/lib/actions/tasks/update-task.ts new file mode 100644 index 0000000..0798e7b --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/actions/tasks/update-task.ts @@ -0,0 +1,68 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, getAccessTokenOrThrow } from '@activepieces/pieces-common'; + +import { clickupCommon, callClickUpApi } from '../../common'; +import { clickupAuth } from '../../../'; + +export const updateClickupTask = createAction({ + auth: clickupAuth, + name: 'update_task', + description: 'Update task in a ClickUp workspace and list', + displayName: 'Update Task', + props: { + workspace_id: clickupCommon.workspace_id(), + space_id: clickupCommon.space_id(), + list_id: clickupCommon.list_id(), + task_id: clickupCommon.task_id(), + name: Property.ShortText({ + description: 'The name of the task to update', + displayName: 'Task Name', + required: false, + }), + description: Property.LongText({ + description: 'The description of the task to update', + displayName: 'Task Description', + required: false, + }), + status_id: clickupCommon.status_id(), + priority_id: clickupCommon.priority_id(), + add_assignee: clickupCommon.assignee_id( + false, + 'Add Assignees', + 'assignee(s) you want to add for the task' + ), + rem_assignee: clickupCommon.assignee_id( + false, + 'Remove Assignees', + 'assignee(s) you want to remove from the task' + ), + }, + async run(configValue) { + const { + task_id, + name, + description, + status_id, + priority_id, + add_assignee, + rem_assignee, + } = configValue.propsValue; + const response = await callClickUpApi( + HttpMethod.PUT, + `task/${task_id}`, + getAccessTokenOrThrow(configValue.auth), + { + name: name, + description: description, + status: status_id, + priority: priority_id, + assignees: { + add: add_assignee, + rem: rem_assignee, + }, + } + ); + + return response.body; + }, +}); diff --git a/packages/pieces/community/clickup/src/lib/clickup.mdx b/packages/pieces/community/clickup/src/lib/clickup.mdx new file mode 100644 index 0000000..8a98bb4 --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/clickup.mdx @@ -0,0 +1,36 @@ +## How to get a list ID + +To obtain a list ID + +1. Go to ClickUp application (https://app.clickup.com/) and select into a space and a list within the space +2. In the URL bar, it should look something like this: https://app.clickup.com/2499706/v/l/li/901000754585 +3. Go ahead and copy the numbers after the final slash (/) + +## How to get a space ID + +To obtain a space ID + +1. Go to ClickUp application (https://app.clickup.com/) and select into a space +2. In the URL bar, it should look something like this: https://app.clickup.com/2499706/v/l/s/38651852 +3. Go ahead and copy the numbers after the final slash (/) + +## How to get a Team ID + +To obtain a team ID + +1. Go to ClickUp application (https://app.clickup.com/) +2. Select your preferred workspace (if you haven't already) +3. In the URL bar, it should look something like this: https://app.clickup.com/2499706/home +4. Copy the numbers just after the app.clickup.com (https://app.clickup.com/[COPY NUMBERS HERE]/home) + +--- + +## Triggers + +TRIGGERS + +--- + +## Actions + +ACTIONS diff --git a/packages/pieces/community/clickup/src/lib/common/index.ts b/packages/pieces/community/clickup/src/lib/common/index.ts new file mode 100644 index 0000000..4536c0f --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/common/index.ts @@ -0,0 +1,656 @@ +import { Property, OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { + getAccessTokenOrThrow, + HttpMethod, + HttpMessageBody, + HttpResponse, + httpClient, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { ClickupTask, ClickupWorkspace } from './models'; + +export const clickupCommon = { + workspace_id: (required = true) => + Property.Dropdown({ + description: 'The ID of the ClickUp workspace', + displayName: 'Workspace', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + const response = ( + await callClickUpApi<{ + teams: { + id: string; + name: string; + }[]; + }>(HttpMethod.GET, 'team', accessToken, undefined) + ).body; + return { + disabled: false, + options: response.teams.map((workspace) => { + return { + label: workspace.name, + value: workspace.id, + }; + }), + }; + }, + }), + space_id: (required = true, multi = false) => { + const Dropdown = multi ? Property.MultiSelectDropdown : Property.Dropdown; + return Dropdown({ + description: 'The ID of the ClickUp space to create the task in', + displayName: 'Space', + required, + refreshers: ['workspace_id'], + options: async ({ auth, workspace_id }) => { + if (!auth || !workspace_id) { + return { + disabled: true, + placeholder: 'connect your account first and select workspace', + options: [], + }; + } + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + const response = await listSpaces(accessToken, workspace_id as string); + return { + disabled: false, + options: response.spaces.map((space) => { + return { + label: space.name, + value: space.id, + }; + }), + }; + }, + }); + }, + list_id: (required = true, multi = false) => { + const Dropdown = multi ? Property.MultiSelectDropdown : Property.Dropdown; + return Dropdown({ + description: 'The ID of the ClickUp space to create the task in', + displayName: 'List', + required, + refreshers: ['space_id', 'workspace_id', 'folder_id'], + options: async ({ auth, space_id }) => { + if (!auth || !space_id) { + return { + disabled: true, + placeholder: 'connect your account first and select a space', + options: [], + }; + } + + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + const lists: { name: string; id: string }[] = await listAllLists( + accessToken, + space_id as string + ); + + return { + disabled: false, + options: lists.map((list) => { + return { + label: list.name, + value: list.id, + }; + }), + }; + }, + }); + }, + task_id: (required = true, label: string | undefined = undefined) => + Property.Dropdown({ + description: 'The ID of the ClickUp task', + displayName: label ?? 'Task Id', + required, + defaultValue: null, + refreshers: ['space_id', 'list_id', 'workspace_id', 'folder_id'], + options: async ({ auth, space_id, list_id }) => { + if (!auth || !list_id || !space_id) { + return { + disabled: true, + placeholder: + 'connect your account first and select workspace, space and list', + options: [], + }; + } + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + const response = await listTasks(accessToken, list_id as string); + return { + disabled: false, + options: response.tasks.map((task) => { + return { + label: task.name, + value: task.id, + }; + }), + }; + }, + }), + folder_id: (required = false, multi = false) => { + const Dropdown = multi ? Property.MultiSelectDropdown : Property.Dropdown; + return Dropdown({ + description: 'The ID of the ClickUp folder', + displayName: 'Folder Id', + refreshers: ['space_id', 'workspace_id'], + required, + options: async ({ auth, space_id, workspace_id }) => { + if (!auth || !workspace_id || !space_id) { + return { + disabled: true, + placeholder: + 'connect your account first and select workspace and space', + options: [], + }; + } + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + const response = await listFolders(accessToken, space_id as string); + return { + disabled: false, + options: response.folders.map((task) => { + return { + label: task.name, + value: task.id, + }; + }), + }; + }, + }); + }, + field_id: (required = false) => + Property.Dropdown({ + displayName: 'Field', + description: 'The ID of the ClickUp custom field', + refreshers: ['task_id', 'list_id'], + defaultValue: null, + required, + options: async ({ auth, task_id, list_id }) => { + if (!auth || !task_id || !list_id) { + return { + disabled: true, + placeholder: 'connect your account first and select a task', + options: [], + }; + } + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + const response = await listAccessibleCustomFields( + accessToken, + list_id as string + ); + return { + disabled: false, + options: response.fields.map((field) => { + return { + label: field.name, + value: field.id, + }; + }), + }; + }, + }), + status_id: (required = false, multi = false) => { + const Dropdown = multi ? Property.MultiSelectDropdown : Property.Dropdown; + return Dropdown({ + description: 'The ID of Clickup Issue Status', + displayName: 'Status Id', + refreshers: ['list_id'], + required, + options: async ({ auth, list_id }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + if (!list_id) { + return { + disabled: true, + placeholder: 'select list', + options: [], + }; + } + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + const response = await getStatuses(accessToken, list_id as string); + return { + disabled: false, + options: response.statuses.map((status) => { + return { + label: status.status, + value: status.status, + }; + }), + }; + }, + }); + }, + priority_id: (required = false) => + Property.StaticDropdown({ + displayName: 'Priority Id', + defaultValue: null, + description: 'The ID of Clickup Issue Priority', + required, + options: { + options: [ + { + label: 'Urgent', + value: 1, + }, + { + label: 'High', + value: 2, + }, + { + label: 'Normal', + value: 3, + }, + { + label: 'Low', + value: 4, + }, + ], + }, + }), + assignee_id: ( + required = false, + displayName = 'Assignee Id', + description: string + ) => + Property.MultiSelectDropdown({ + displayName: displayName, + description: description, + required, + refreshers: ['workspace_id'], + options: async ({ auth, workspace_id }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + if (!workspace_id) { + return { + disabled: true, + placeholder: 'select workspace', + options: [], + }; + } + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + const response = await listWorkspaceMembers( + accessToken, + workspace_id as string + ); + return { + disabled: false, + options: response.map((member) => { + return { + label: member.user.username, + value: member.user.id, + }; + }), + }; + }, + }), + single_assignee_id: ( + required = false, + displayName = 'Assignee Id', + description: string + ) => + Property.Dropdown({ + displayName: displayName, + description: description, + required, + refreshers: ['workspace_id'], + options: async ({ auth, workspace_id }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + if (!workspace_id) { + return { + disabled: true, + placeholder: 'select workspace', + options: [], + }; + } + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + const response = await listWorkspaceMembers( + accessToken, + workspace_id as string + ); + return { + disabled: false, + options: response.map((member) => { + return { + label: member.user.username, + value: member.user.id, + }; + }), + }; + }, + }), + template_id: (required = false) => + Property.Dropdown({ + displayName: 'Template Id', + required, + description: 'The ID of Clickup Task Template', + refreshers: ['workspace_id'], + options: async ({ auth, workspace_id }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + if (!workspace_id) { + return { + disabled: true, + placeholder: 'select workspace', + options: [], + }; + } + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + const response = await listWorkspaceTemplates( + accessToken, + workspace_id as string + ); + return { + disabled: false, + options: response.templates.map((template) => { + return { + label: template.name, + value: template.id, + }; + }), + }; + }, + }), + channel_id: (required = false) => + Property.Dropdown({ + displayName: 'Channel Id', + required, + description: 'The ID of Clickup Channel', + refreshers: ['workspace_id'], + options: async ({ auth, workspace_id }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + if (!workspace_id) { + return { + disabled: true, + placeholder: 'select workspace', + options: [], + }; + } + const accessToken = getAccessTokenOrThrow(auth as OAuth2PropertyValue); + const response = await retrieveChannels( + accessToken, + workspace_id as string + ); + return { + disabled: false, + options: response.data + .filter((channel) => channel.name && channel.name !== '') + .map((channel) => { + return { + label: channel.name, + value: channel.id, + }; + }), + }; + }, + }), +}; + +async function listWorkspaces(accessToken: string) { + return ( + await callClickUpApi<{ teams: ClickupWorkspace[] }>( + HttpMethod.GET, + 'team', + accessToken, + undefined + ) + ).body; +} + +async function getWorkspace(accessToken: string, workspaceId: string) { + const { teams } = await listWorkspaces(accessToken as string); + const workspace = teams.filter((workspace) => workspace.id === workspaceId); + return workspace[0]; +} + +async function listWorkspaceMembers(accessToken: string, workspaceId: string) { + const workspace = await getWorkspace(accessToken, workspaceId); + return workspace.members; +} + +async function listWorkspaceTemplates( + accessToken: string, + workspaceId: string +) { + return ( + await callClickUpApi<{ + templates: { + id: string; + name: string; + }[]; + }>( + HttpMethod.GET, + `team/${workspaceId}/taskTemplate`, + accessToken, + undefined + ) + ).body; +} + +export async function listSpaces(accessToken: string, workspaceId: string) { + return ( + await callClickUpApi<{ + spaces: { + id: string; + name: string; + }[]; + }>(HttpMethod.GET, `team/${workspaceId}/space`, accessToken, undefined) + ).body; +} + +export async function listAllLists(accessToken: string, spaceId: string) { + const responseFolders = await listFolders(accessToken, spaceId as string); + const promises: Promise<{ lists: { id: string; name: string }[] }>[] = [ + listFolderlessList(accessToken, spaceId as string), + ]; + for (let i = 0; i < responseFolders.folders.length; ++i) { + promises.push(listLists(accessToken, responseFolders.folders[i].id)); + } + const listsResponses = await Promise.all(promises); + + let lists: { name: string; id: string }[] = []; + for (let i = 0; i < listsResponses.length; ++i) { + lists = [...lists, ...listsResponses[i].lists]; + } + + return lists; +} + +export async function listLists(accessToken: string, folderId: string) { + return ( + await callClickUpApi<{ + lists: { + id: string; + name: string; + }[]; + }>(HttpMethod.GET, `folder/${folderId}/list`, accessToken, undefined) + ).body; +} +async function getStatuses(accessToken: string, listId: string) { + return ( + await callClickUpApi<{ + statuses: { + id: string; + status: string; + }[]; + }>(HttpMethod.GET, `list/${listId}`, accessToken, undefined) + ).body; +} + +export async function listFolders(accessToken: string, spaceId: string) { + return ( + await callClickUpApi<{ + folders: { + id: string; + name: string; + }[]; + }>(HttpMethod.GET, `space/${spaceId}/folder`, accessToken, undefined) + ).body; +} + +export async function listAccessibleCustomFields( + accessToken: string, + listId: string +) { + return ( + await callClickUpApi<{ + fields: { + id: string; + name: string; + type: string; + type_config: Record; + date_created: string; + hide_from_guests: false; + }[]; + }>(HttpMethod.GET, `list/${listId}/field`, accessToken, undefined) + ).body; +} + +async function listFolderlessList(accessToken: string, spaceId: string) { + return ( + await callClickUpApi<{ + lists: { + id: string; + name: string; + }[]; + }>(HttpMethod.GET, `space/${spaceId}/list`, accessToken, undefined) + ).body; +} + +export async function listTags(accessToken: string, spaceId: string) { + return ( + await callClickUpApi<{ + tags: { + id: string; + name: string; + }[]; + }>(HttpMethod.GET, `space/${spaceId}/tag`, accessToken, undefined) + ).body; +} + +export async function listTasks(accessToken: string, listId: string) { + return ( + await callClickUpApi<{ + tasks: { + id: string; + name: string; + }[]; + }>(HttpMethod.GET, `list/${listId}/task`, accessToken, undefined) + ).body; +} + +export async function retrieveChannels( + accessToken: string, + workspaceId: string +) { + return ( + await callClickUpApi3<{ + data: { + id: string; + name: string; + }[]; + }>( + HttpMethod.GET, + `workspaces/${workspaceId}/chat/channels`, + accessToken, + undefined + ) + ).body; +} + +export async function callClickupGetTask(accessToken: string, taskId: string) { + return ( + await callClickUpApi( + HttpMethod.GET, + `task/${taskId}`, + accessToken, + undefined + ) + ).body; +} + +export async function callClickUpApi( + method: HttpMethod, + apiUrl: string, + accessToken: string, + body: any | undefined, + queryParams: any | undefined = undefined, + headers: any | undefined = undefined +): Promise> { + headers = { + accept: 'application/json', + ...headers, + }; + + return await httpClient.sendRequest({ + method: method, + url: `https://api.clickup.com/api/v2/${apiUrl}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + headers, + body: method === 'GET' ? undefined : body, + queryParams, + }); +} + +export async function callClickUpApi3( + method: HttpMethod, + apiUrl: string, + accessToken: string, + body: any | undefined, + queryParams: any | undefined = undefined, + headers: any | undefined = {} +): Promise> { + headers = { + accept: 'application/json', + ...headers, + }; + + return await httpClient.sendRequest({ + method: method, + url: `https://api.clickup.com/api/v3/${apiUrl}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + headers, + body: method === 'GET' ? undefined : body, + queryParams, + }); +} diff --git a/packages/pieces/community/clickup/src/lib/common/models.ts b/packages/pieces/community/clickup/src/lib/common/models.ts new file mode 100644 index 0000000..bfef197 --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/common/models.ts @@ -0,0 +1,127 @@ +export const enum ClickupEventType { + TASK_CREATED = 'taskCreated', + TASK_UPDATED = 'taskUpdated', + TASK_DELETED = 'taskDeleted', + TASK_PRIORITY_UPDATED = 'taskPriorityUpdated', + TASK_STATUS_UPDATED = 'taskStatusUpdated', + TASK_ASSIGNEE_UPDATED = 'taskAssigneeUpdated', + TASK_DUEDATE_UPDATED = 'taskDueDateUpdated', + TASK_TAG_UPDATED = 'taskTagUpdated', + TASK_MOVED = 'taskMoved', + TASK_COMMENT_POSTED = 'taskCommentPosted', + TASK_COMMENT_UPDATED = 'taskCommentUpdated', + TASK_TIME_ESTIMATE_UPDATED = 'taskTimeEstimateUpdated', + TASK_TIME_TRACKED_UPDATED = 'taskTimeTrackedUpdated', + LIST_CREATED = 'listCreated', + LIST_UPDATED = 'listUpdated', + LIST_DELETED = 'listDeleted', + FOLDER_CREATED = 'folderCreated', + FOLDER_UPDATED = 'folderUpdated', + FOLDER_DELETED = 'folderDeleted', + SPACE_CREATED = 'spaceCreated', + SPACE_UPDATED = 'spaceUpdated', + SPACE_DELETED = 'spaceDeleted', + GOAL_CREATED = 'goalCreated', + GOAL_UPDATED = 'goalUpdated', + GOAL_DELETED = 'goalDeleted', + KEY_RESULT_CREATED = 'keyResultCreated', + KEY_RESULT_UPDATED = 'keyResultUpdated', + KEY_RESULT_DELETED = 'keyResultDeleted', + AUTOMATION_CREATED = 'automationCreated', +} + +export interface ClickupTask { + id: string; + custom_id: string; + name: string; + text_content: string; + description: string; + status: { + status: string; + color: string; + orderindex: number; + type: string; + }; + orderindex: string; + date_created: string; + date_updated: string; + date_closed: string; + creator: { + id: number; + username: string; + color: string; + profilePicture: string; + }; + assignees: string[]; + checklists: string[]; + tags: string[]; + parent: string; + priority: string; + due_date: string; + start_date: string; + time_estimate: string; + time_spent: string; + custom_fields: Record[]; + list: { + id: string; + }; + folder: { + id: string; + }; + space: { + id: string; + }; + url: string; +} + +export interface ClickupWebhookPayload { + event: ClickupEventType; + history_items: { + id: string; + type: number; + date: string; + field: string; + parent_id: string; + data: Record; + source: string; + user: ClickupUser; + before: string; + after: string; + comment: ClickupComment; + }; + task_id: string; + webhook_id: string; +} + +interface ClickupUser { + id: number; + username: string; + initials: string; + email: string; + color: string; + profilePicture: string; +} + +interface ClickupComment { + id: string; + comment: { + text: string; + }[]; + comment_text: string; + user: ClickupUser; + resolved: boolean; + assignee: ClickupUser; + assigned_by: ClickupUser; + reactions: []; + date: string; +} +export interface ClickupWorkspace { + id: string; + name: string; + color: string; + avatar: string; + members: { + user: ClickupUser; + invited_by?: Record; + }[]; +} diff --git a/packages/pieces/community/clickup/src/lib/triggers/index.ts b/packages/pieces/community/clickup/src/lib/triggers/index.ts new file mode 100644 index 0000000..7f689f1 --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/triggers/index.ts @@ -0,0 +1,965 @@ +import { ClickupEventType } from '../common/models'; +import { clickupRegisterTrigger } from './register-trigger'; +import { triggerTaskTagUpdated } from './task-tag-updated'; + +export const sampleTask = { + id: 'string', + custom_id: 'string', + name: 'string', + text_content: 'string', + description: 'string', + status: { + status: 'in progress', + color: '#d3d3d3', + orderindex: 1, + type: 'custom', + }, + orderindex: 'string', + date_created: 'string', + date_updated: 'string', + date_closed: 'string', + creator: { + id: 183, + username: 'John Doe', + color: '#827718', + profilePicture: + 'https://attachments-public.clickup.com/profilePictures/183_abc.jpg', + }, + assignees: ['string'], + checklists: ['string'], + tags: ['string'], + parent: 'string', + priority: 'string', + due_date: 'string', + start_date: 'string', + time_estimate: 'string', + time_spent: 'string', + custom_fields: [ + { + id: 'string', + name: 'string', + type: 'string', + type_config: {}, + date_created: 'string', + hide_from_guests: true, + value: { + id: 183, + username: 'John Doe', + email: 'john@example.com', + color: '#7b68ee', + initials: 'JD', + profilePicture: null, + }, + required: true, + }, + ], + list: { + id: '123', + }, + folder: { + id: '456', + }, + space: { + id: '789', + }, + url: 'string', +}; + +export const triggers = [ + // task created + { + name: 'task_created', + eventType: ClickupEventType.TASK_CREATED, + displayName: 'Task Created', + description: 'Triggered when a new task is created.', + sampleData: { + event: 'taskCreated', + history_items: [ + { + id: '1394258655167106175', + type: 1, + date: '1378109721053', + field: 'status', + parent_id: '900900799744', + data: {}, + source: null, + user: { + id: 55053258, + username: 'Activepieces Apps', + email: 'apps@activepieces.com', + color: '#aa2fff', + initials: 'AA', + profilePicture: null, + }, + before: null, + after: '90040005586783', + }, + ], + task: sampleTask, + task_id: '8669p1zvv', + webhook_id: '9b2708b6-87e8-4fff-851a-2ebf0e35130f', + }, + }, + // task updated + { + name: 'task_updated', + eventType: ClickupEventType.TASK_UPDATED, + displayName: 'Task Updated', + description: 'Triggered when a task is updated.', + sampleData: { + event: 'taskUpdated', + history_items: [ + { + id: '3394266113344261722', + type: 1, + date: '1678110165596', + field: 'name', + parent_id: '900900799744', + data: {}, + source: null, + user: { + id: 55053258, + username: 'Activepieces Apps', + email: 'apps@activepieces.com', + color: '#aa2fff', + initials: 'AA', + profilePicture: null, + }, + before: "Watch over Neriah's", + after: "Watch over Neriah's things", + }, + ], + task: sampleTask, + task_id: '8669p1zvv', + webhook_id: 'c065bdfd-eb7a-48dc-b25e-0cf5babf5ec8', + }, + }, + // task deleted + { + name: 'task_deleted', + eventType: ClickupEventType.TASK_DELETED, + displayName: 'Task Deleted', + description: 'Triggered when a task is deleted.', + sampleData: { + event: 'taskDeleted', + task_id: '8669p1zvv', + webhook_id: '78674f1e-ec8e-4302-908f-4190bdcfdf6a', + }, + }, + // task priority updated + { + name: 'task_priority_updated', + eventType: ClickupEventType.TASK_PRIORITY_UPDATED, + displayName: "Task Priority Updated", + description: 'Triggered when a task priority is updated.', + sampleData: { + "event": "taskPriorityUpdated", + "history_items": [ + { + "id": "2800773800802162647", + "type": 1, + "date": "1642735267148", + "field": "priority", + "parent_id": "162641062", + "data": {}, + "source": null, + "user": { + "id": 183, + "username": "John", + "email": "john@company.com", + "color": "#7b68ee", + "initials": "J", + "profilePicture": null + }, + "before": null, + "after": { + "id": "2", + "priority": "high", + "color": "#ffcc00", + "orderindex": "2" + } + } + ], + "task_id": "1vj38vv", + "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204" + } + }, + // task status updated + { + name: 'task_status_updated', + eventType: ClickupEventType.TASK_STATUS_UPDATED, + displayName: 'Task Status Updated', + description: 'Triggered when a task status is updated.', + sampleData: { + "event": "taskStatusUpdated", + "history_items": [ + { + "id": "2800787326392370170", + "type": 1, + "date": "1642736073330", + "field": "status", + "parent_id": "162641062", + "data": { + "status_type": "custom" + }, + "source": null, + "user": { + "id": 183, + "username": "John", + "email": "john@company.com", + "color": "#7b68ee", + "initials": "J", + "profilePicture": null + }, + "before": { + "status": "to do", + "color": "#f9d900", + "orderindex": 0, + "type": "open" + }, + "after": { + "status": "in progress", + "color": "#7C4DFF", + "orderindex": 1, + "type": "custom" + } + } + ], + "task_id": "1vj38vv", + "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204" + } + }, + // task assignee updated + { + name: 'task_assignee_updated', + eventType: ClickupEventType.TASK_ASSIGNEE_UPDATED, + displayName: 'Task Assignee Updated', + description: 'Triggered when a task assignee is updated.', + sampleData: { + "event": "taskAssigneeUpdated", + "history_items": [ + { + "id": "2800789353868594308", + "type": 1, + "date": "1642736194135", + "field": "assignee_add", + "parent_id": "162641062", + "data": {}, + "source": null, + "user": { + "id": 183, + "username": "John", + "email": "john@company.com", + "color": "#7b68ee", + "initials": "J", + "profilePicture": null + }, + "after": { + "id": 184, + "username": "Sam", + "email": "sam@company.com", + "color": "#7b68ee", + "initials": "S", + "profilePicture": null + } + } + ], + "task_id": "1vj38vv", + "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204" + } + }, + // task due date updated + { + name: 'task_due_date_updated', + eventType: ClickupEventType.TASK_DUEDATE_UPDATED, + displayName: 'Task Due Date Updated', + description: 'Triggered when a task due date is updated.', + sampleData: { + "event": "taskDueDateUpdated", + "history_items": [ + { + "id": "2800792714143635886", + "type": 1, + "date": "1642736394447", + "field": "due_date", + "parent_id": "162641062", + "data": { + "due_date_time": true, + "old_due_date_time": false + }, + "source": null, + "user": { + "id": 183, + "username": "John", + "email": "john@company.com", + "color": "#7b68ee", + "initials": "J", + "profilePicture": null + }, + "before": "1642701600000", + "after": "1643608800000" + } + ], + "task_id": "1vj38vv", + "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204" + } + }, + // task tag updated + { + name: 'task_tag_updated', + eventType: ClickupEventType.TASK_TAG_UPDATED, + displayName: 'Task Tag Updated', + description: 'Triggered when a task tag is updated.', + sampleData: { + "event": "taskTagUpdated", + "history_items": [ + { + "id": "2800797048554170804", + "type": 1, + "date": "1642736652800", + "field": "tag", + "parent_id": "162641062", + "data": {}, + "source": null, + "user": { + "id": 183, + "username": "John", + "email": "john@company.com", + "color": "#7b68ee", + "initials": "J", + "profilePicture": null + }, + "before": null, + "after": [ + { + "name": "def", + "tag_fg": "#FF4081", + "tag_bg": "#FF4081", + "creator": 2770032 + } + ] + } + ], + "task_id": "1vj38vv", + "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204" + } + }, + // task moved + { + name: 'task_moved', + eventType: ClickupEventType.TASK_MOVED, + displayName: 'Task Moved', + description: 'Triggered when a task is moved.', + sampleData: { + "event": "taskMoved", + "history_items": [ + { + "id": "2800800851630274181", + "type": 1, + "date": "1642736879339", + "field": "section_moved", + "parent_id": "162641285", + "data": { + "mute_notifications": true + }, + "source": null, + "user": { + "id": 183, + "username": "John", + "email": "john@company.com", + "color": "#7b68ee", + "initials": "J", + "profilePicture": null + }, + "before": { + "id": "162641062", + "name": "Webhook payloads", + "category": { + "id": "96771950", + "name": "hidden", + "hidden": true + }, + "project": { + "id": "7002367", + "name": "This is my API Space" + } + }, + "after": { + "id": "162641285", + "name": "webhook payloads 2", + "category": { + "id": "96772049", + "name": "hidden", + "hidden": true + }, + "project": { + "id": "7002367", + "name": "This is my API Space" + } + } + } + ], + "task_id": "1vj38vv", + "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204" + } + }, + // task comment posted + { + name: 'task_comment_posted', + eventType: ClickupEventType.TASK_COMMENT_POSTED, + displayName: 'Task Comment Posted', + description: 'Triggered when a task comment is posted.', + sampleData: { + event: 'taskCommentPosted', + history_items: [ + { + id: '3394271120907039255', + type: 1, + date: '1678110464062', + field: 'comment', + parent_id: '900900799744', + data: {}, + source: null, + user: { + id: 55053258, + username: 'Activepieces Apps', + email: 'apps@activepieces.com', + color: '#aa2fff', + initials: 'AA', + profilePicture: null, + }, + before: null, + after: '90090008088251', + comment: [ + { + text: 'asdsada', + attributes: {}, + }, + ], + }, + ], + task: sampleTask, + task_id: '8669p1zvv', + webhook_id: '6a86ce24-6276-4315-87f7-b580a9264284', + }, + }, + // task comment updated + { + name: 'task_comment_updated', + eventType: ClickupEventType.TASK_COMMENT_UPDATED, + displayName: 'Task Comment Updated', + description: 'Triggered when a task comment is updated.', + sampleData: { + event: 'taskCommentUpdated', + history_items: [ + { + id: '3394271120907039255', + type: 1, + date: '1678110464062', + field: 'comment', + parent_id: '900900799744', + data: {}, + source: null, + user: { + id: 55053258, + username: 'Activepieces Apps', + email: 'apps@activepieces.com', + color: '#aa2fff', + initials: 'AA', + profilePicture: null, + }, + before: null, + after: '90090008088251', + comment: [ + { + text: 'asdsada', + attributes: {}, + }, + ], + }, + ], + task: sampleTask, + task_id: '8669p1zvv', + webhook_id: '1c14c8af-5509-4d81-ae7f-c0790c0f9683', + }, + }, + // task time estimate updated + { + name: 'task_time_estimate_updated', + eventType: ClickupEventType.TASK_TIME_ESTIMATE_UPDATED, + displayName: 'Task Time Estimate Updated', + description: 'Triggered when a task time estimate is updated.', + sampleData: { + "event": "taskTimeEstimateUpdated", + "history_items": [ + { + "id": "2800808904123520175", + "type": 1, + "date": "1642737359443", + "field": "time_estimate", + "parent_id": "162641285", + "data": { + "time_estimate_string": "1 hour 30 minutes", + "old_time_estimate_string": null, + "rolled_up_time_estimate": 5400000, + "time_estimate": 5400000, + "time_estimates_by_user": [ + { + "userid": 2770032, + "user_time_estimate": "5400000", + "user_rollup_time_estimate": "5400000" + } + ] + }, + "source": null, + "user": { + "id": 183, + "username": "John", + "email": "john@company.com", + "color": "#7b68ee", + "initials": "J", + "profilePicture": null + }, + "before": null, + "after": "5400000" + } + ], + "task_id": "1vj38vv", + "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204" + } + }, + // task time tracked updated + { + name: 'task_time_tracked_updated', + eventType: ClickupEventType.TASK_TIME_TRACKED_UPDATED, + displayName: 'Task Time Tracked Updated', + description: 'Triggered when a task time tracked is updated.', + sampleData: { + "event": "taskTimeTrackedUpdated", + "history_items": [ + { + "id": "2800809188061123931", + "type": 1, + "date": "1642737376354", + "field": "time_spent", + "parent_id": "162641285", + "data": { + "total_time": "900000", + "rollup_time": "900000" + }, + "source": null, + "user": { + "id": 183, + "username": "John", + "email": "john@company.com", + "color": "#7b68ee", + "initials": "J", + "profilePicture": null + }, + "before": null, + "after": { + "id": "2800809188061119507", + "start": "1642736476215", + "end": "1642737376215", + "time": "900000", + "source": "clickup", + "date_added": "1642737376354" + } + } + ], + "task_id": "1vj38vv", + "data": { + "description": "Time Tracking Created", + "interval_id": "2800809188061119507" + }, + "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204" + } + }, + // list created + { + name: 'list_created', + eventType: ClickupEventType.LIST_CREATED, + displayName: 'List Created', + description: 'Triggered when a new list is created.', + sampleData: { + event: 'listCreated', + list_id: '900201745120', + webhook_id: 'eda9be69-dbb9-4c59-a3e2-3f871d5d3b9e', + }, + }, + // list updated + { + name: 'list_updated', + eventType: ClickupEventType.LIST_UPDATED, + displayName: 'List Updated', + description: 'Triggered when a list is updated.', + sampleData: { + "event": "listUpdated", + "history_items": [ + { + "id": "8a2f82db-7718-4fdb-9493-4849e67f009d", + "type": 6, + "date": "1642740510345", + "field": "name", + "parent_id": "162641285", + "data": {}, + "source": null, + "user": { + "id": 183, + "username": "John", + "email": "john@company.com", + "color": "#7b68ee", + "initials": "J", + "profilePicture": null + }, + "before": "webhook payloads 2", + "after": "Webhook payloads round 2" + } + ], + "list_id": "162641285", + "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204" + } + }, + // list deleted + { + name: 'list_deleted', + eventType: ClickupEventType.LIST_DELETED, + displayName: 'List Deleted', + description: 'Triggered when a list is deleted.', + sampleData: { + "event": "listDeleted", + "list_id": "162641062", + "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204" + } + }, + // folder created + { + name: 'folder_created', + eventType: ClickupEventType.FOLDER_CREATED, + displayName: 'Folder Created', + description: 'Triggered when a new folder is created.', + sampleData: { + "event": "folderCreated", + "folder_id": "96772212", + "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204" + } + }, + // folder updated + { + name: 'folder_updated', + eventType: ClickupEventType.FOLDER_UPDATED, + displayName: 'Folder Updated', + description: 'Triggered when a folder is updated.', + sampleData: { "event": "folderUpdated", + "folder_id": "96772212", + "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204" + } + }, + // folder deleted + { + name: 'folder_deleted', + eventType: ClickupEventType.FOLDER_DELETED, + displayName: 'Folder Deleted', + description: 'Triggered when a folder is deleted.', + sampleData: { + "event": "listDeleted", + "list_id": "162641543", + "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204" + } + }, + // space created + { + name: 'space_created', + eventType: ClickupEventType.SPACE_CREATED, + displayName: 'Space Created', + description: 'Triggered when a new space is created.', + sampleData: { + "event": "spaceCreated", + "space_id": "54650507", + "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204" + } + }, + // space updated + { + name: 'space_updated', + eventType: ClickupEventType.SPACE_UPDATED, + displayName: 'Space Updated', + description: 'Triggered when a space is updated.', + sampleData: { + "event": "spaceUpdated", + "space_id": "54650507", + "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204" + } + }, + // space deleted + { + name: 'space_deleted', + eventType: ClickupEventType.SPACE_DELETED, + displayName: 'Space Deleted', + description: 'Triggered when a space is deleted.', + sampleData: { + "event": "spaceDeleted", + "space_id": "54650507", + "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204" + } + }, + // automation created + { + name: 'automation_created', + eventType: ClickupEventType.AUTOMATION_CREATED, + displayName: 'Automation Created', + description: 'Triggered when a new automation is created.', + sampleData: { + "id": "b4ced072-ae72-4c70-b898-fea5dd142408:main", + "trigger_id": "6f612d16-9ff7-4db2-a2f6-19528ee3b90c", + "date": "2024-01-11T19:40:17.927Z", + "payload": { + "id": "8687096vn", + "custom_id": "DEF-43", + "custom_item_id": 0, + "name": "task name", + "text_content": "", + "description": "", + "status": { + "id": "p90090203753_p54073272_p54073128_p54071092_p54067915_r9V2QX7O", + "status": "complete", + "color": "#008844", + "orderindex": 1, + "type": "closed" + }, + "orderindex": "39906954.00000000000000000000000000000000", + "date_created": "1705002014968", + "date_updated": "1705002016108", + "date_closed": "1705002016108", + "date_done": "1705002016108", + "archived": false, + "creator": { + "id": 26191384, + "username": "John", + "color": "#5f7c8a", + "email": "john@company.com", + "profilePicture": "https://attachments.clickup.com/profilePictures/26191384_HoB.jpg" + }, + "assignees": [], + "watchers": [ + { + "id": 26191384, + "username": "John", + "color": "#5f7c8a", + "initials": "J", + "email": "john@company.com", + "profilePicture": "https://attachments.clickup.com/profilePictures/26191384_HoB.jpg" + } + ], + "checklists": [], + "tags": [], + "parent": null, + "priority": null, + "due_date": null, + "start_date": null, + "points": null, + "time_estimate": null, + "time_spent": 0, + "custom_fields": [ + { + "id": "ede917d5-4dbb-46eb-9f7c-5d4f0a652b1f", + "name": "ijjb", + "type": "formula", + "type_config": { + "version": "1.6", + "is_dynamic": false, + "return_types": [ + "null" + ], + "calculation_state": "ready" + }, + "date_created": "1698260411360", + "hide_from_guests": false, + "required": false + }, + { + "id": "7d979288-84e1-48b0-abaf-238ecb27e0fa", + "name": "formula 1", + "type": "currency", + "type_config": { + "default": null, + "precision": 2, + "currency_type": "USD" + }, + "date_created": "1694298925344", + "hide_from_guests": false, + "required": false + }, + { + "id": "89bbdeeb-6724-4ec0-a8a7-c21d944199a7", + "name": "Marketing Task Type", + "type": "drop_down", + "type_config": { + "default": 0, + "placeholder": null, + "options": [ + { + "id": "d73a55af-88f5-4161-a948-7341d2bbb045", + "name": "Campaign", + "color": null, + "orderindex": 0 + }, + { + "id": "0010111d-91da-4cb7-8cc1-d642f90ef194", + "name": "asd", + "color": null, + "orderindex": 1 + } + ] + }, + "date_created": "1698177406311", + "hide_from_guests": false, + "required": false + }, + { + "id": "07119fd9-e1eb-4457-bc69-3e5913707ca2", + "name": "files", + "type": "attachment", + "type_config": {}, + "date_created": "1700237528128", + "hide_from_guests": false, + "required": false + }, + { + "id": "60392065-eb67-40c3-afe2-10f288d0695d", + "name": "new field", + "type": "currency", + "type_config": { + "precision": 2, + "currency_type": "EUR" + }, + "date_created": "1696603471462", + "hide_from_guests": true, + "required": false + } + ], + "dependencies": [], + "linked_tasks": [], + "locations": [], + "team_id": "36003581", + "url": "https://app.clickup.com/t/8687096vn", + "sharing": { + "public": false, + "public_share_expires_on": null, + "public_fields": [ + "assignees", + "priority", + "due_date", + "content", + "comments", + "attachments", + "customFields", + "subtasks", + "tags", + "checklists", + "coverimage" + ], + "token": null, + "seo_optimized": false + }, + "list": { + "id": "901102008938", + "name": "List", + "access": true + }, + "project": { + "id": "90110993233", + "name": "test folder", + "hidden": false, + "access": true + }, + "folder": { + "id": "90110993233", + "name": "test folder", + "hidden": false, + "access": true + }, + "space": { + "id": "90090203753" + } + } + } + }, + // goal created + { + name: 'goal_created', + eventType: ClickupEventType.GOAL_CREATED, + displayName: 'Goal Created', + description: 'Triggered when a new goal is created.', + sampleData: { + "event": "goalCreated", + "goal_id": "a23e5a3d-74b5-44c2-ab53-917ebe85045a", + "webhook_id": "d5eddb2d-db2b-49e9-87d4-bc6cfbe2313b" + } + }, + // goal updated + { + name: 'goal_updated', + eventType: ClickupEventType.GOAL_UPDATED, + displayName: 'Goal Updated', + description: 'Triggered when a goal is updated.', + sampleData: { + "event": "goalUpdated", + "goal_id": "a23e5a3d-74b5-44c2-ab53-917ebe85045a", + "webhook_id": "d5eddb2d-db2b-49e9-87d4-bc6cfbe2313b" + } + }, + // goal deleted + { + name: 'goal_deleted', + eventType: ClickupEventType.GOAL_DELETED, + displayName: 'Goal Deleted', + description: 'Triggered when a goal is deleted.', + sampleData: { + "event": "goalDeleted", + "goal_id": "a23e5a3d-74b5-44c2-ab53-917ebe85045a", + "webhook_id": "d5eddb2d-db2b-49e9-87d4-bc6cfbe2313b" + } + }, + // key result created + { + name: 'key_result_created', + eventType: ClickupEventType.KEY_RESULT_CREATED, + displayName: 'Key Result Created', + description: 'Triggered when a new key result is created.', + sampleData: { + "event": "keyResultCreated", + "goal_id": "a23e5a3d-74b5-44c2-ab53-917ebe85045a", + "key_result_id": "47608e42-ad0e-4934-a39e-950539c77e79", + "webhook_id": "d5eddb2d-db2b-49e9-87d4-bc6cfbe2313b" + } + }, + // key result updated + { + name: 'key_result_updated', + eventType: ClickupEventType.KEY_RESULT_UPDATED, + displayName: 'Key Result Updated', + description: 'Triggered when a key result is updated.', + sampleData: { + "event": "keyResultUpdated", + "goal_id": "a23e5a3d-74b5-44c2-ab53-917ebe85045a", + "key_result_id": "47608e42-ad0e-4934-a39e-950539c77e79", + "webhook_id": "d5eddb2d-db2b-49e9-87d4-bc6cfbe2313b" + } + }, + // key result deleted + { + name: 'key_result_deleted', + eventType: ClickupEventType.KEY_RESULT_DELETED, + displayName: 'Key Result Deleted', + description: 'Triggered when a key result is deleted.', + sampleData: { + "event": "keyResultDeleted", + "goal_id": "a23e5a3d-74b5-44c2-ab53-917ebe85045a", + "key_result_id": "47608e42-ad0e-4934-a39e-950539c77e79", + "webhook_id": "d5eddb2d-db2b-49e9-87d4-bc6cfbe2313b" + } + }, +].map((props) => clickupRegisterTrigger(props)); + +export const clickupTriggers = [...triggers, triggerTaskTagUpdated]; \ No newline at end of file diff --git a/packages/pieces/community/clickup/src/lib/triggers/register-trigger.ts b/packages/pieces/community/clickup/src/lib/triggers/register-trigger.ts new file mode 100644 index 0000000..cbb734a --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/triggers/register-trigger.ts @@ -0,0 +1,140 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpRequest, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { callClickupGetTask, clickupCommon } from '../common'; +import { ClickupEventType, ClickupWebhookPayload } from '../common/models'; +import { clickupAuth } from '../../'; + +export const clickupRegisterTrigger = ({ + name, + displayName, + eventType, + description, + sampleData, +}: { + name: string; + displayName: string; + eventType: ClickupEventType; + description: string; + sampleData: unknown; +}) => + createTrigger({ + auth: clickupAuth, + name: `clickup_trigger_${name}`, + displayName, + description, + props: { + workspace_id: clickupCommon.workspace_id(true), + space_id: clickupCommon.space_id(false), // Optional, depends on workspace + folder_id: clickupCommon.folder_id(false), // Optional, depends on space + list_id: clickupCommon.list_id(false), // Optional, depends on folder or space + task_id: clickupCommon.task_id(false), // Optional, depends on list + }, + sampleData, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const { workspace_id, space_id, folder_id, list_id, task_id } = + context.propsValue; + + const requestBody: Record = { + endpoint: context.webhookUrl, + events: [eventType], + }; + + // Add scope narrowing properties to the webhook request body + if (space_id) requestBody.space_id = space_id; + if (folder_id) requestBody.folder_id = folder_id; + if (list_id) requestBody.list_id = list_id; + if (task_id) requestBody.task_id = task_id; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://api.clickup.com/api/v2/team/${workspace_id}/webhook`, + body: requestBody, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + queryParams: {}, + }; + + const response = await httpClient.sendRequest( + request + ); + console.debug(`clickup.${eventType}.onEnable`, response); + + await context.store.put( + `clickup_${name}_trigger`, + response.body + ); + }, + async onDisable(context) { + const webhook = await context.store.get( + `clickup_${name}_trigger` + ); + if (webhook != null) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `https://api.clickup.com/api/v2/webhook/${webhook.id}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth['access_token'], + }, + }; + const response = await httpClient.sendRequest(request); + console.debug(`clickup.${eventType}.onDisable`, response); + } + }, + async run(context) { + const payload = context.payload.body as ClickupWebhookPayload; + + if ( + [ + ClickupEventType.TASK_CREATED, + ClickupEventType.TASK_UPDATED, + ClickupEventType.TASK_COMMENT_POSTED, + ClickupEventType.TASK_COMMENT_UPDATED, + ].includes(eventType) + ) { + const enriched = [ + { + ...payload, + task: await callClickupGetTask( + context.auth['access_token'], + payload.task_id + ), + }, + ]; + + console.debug('payload enriched', enriched); + return enriched; + } + + return [context.payload.body]; + }, + }); + +interface WebhookInformation { + id: string; + webhook: { + id: string; + userid: number; + team_id: number; + endpoint: string; + client_id: string; + events: ClickupEventType[]; + task_id?: string; + list_id?: string; + folder_id?: string; + space_id?: string; + health: { + status: string; + fail_count: number; + }; + secret: string; + }; +} diff --git a/packages/pieces/community/clickup/src/lib/triggers/task-tag-updated.ts b/packages/pieces/community/clickup/src/lib/triggers/task-tag-updated.ts new file mode 100644 index 0000000..dc06308 --- /dev/null +++ b/packages/pieces/community/clickup/src/lib/triggers/task-tag-updated.ts @@ -0,0 +1,128 @@ +import { + TriggerStrategy, createTrigger +} from '@activepieces/pieces-framework'; +import { + httpClient, + HttpRequest, + HttpMethod, + AuthenticationType, +} from "@activepieces/pieces-common"; +import { callClickupGetTask, clickupCommon } from '../common'; +import { ClickupEventType, ClickupWebhookPayload } from '../common/models'; +import { clickupAuth } from "../../"; + +export const triggerTaskTagUpdated = createTrigger({ + auth: clickupAuth, + name: 'task_tag_updated', + displayName: 'Task Tag Updated', + description: 'Triggered when a tag is added or removed or renamed on a task.', + sampleData: { + "event": "taskTagUpdated", + "history_items": [ + { + "id": "2800797048554170804", + "type": 1, + "date": "1642736652800", + "field": "tag", + "parent_id": "162641062", + "data": {}, + "source": null, + "user": { + "id": 183, + "username": "John", + "email": "john@company.com", + "color": "#7b68ee", + "initials": "J", + "profilePicture": null + }, + "before": null, + "after": [ + { + "name": "def", + "tag_fg": "#FF4081", + "tag_bg": "#FF4081", + "creator": 2770032 + } + ] + } + ], + "task_id": "1vj38vv", + "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204" + }, + props: { + workspace_id: clickupCommon.workspace_id(true), + space_id: clickupCommon.space_id(true), + list_id: clickupCommon.list_id(false), + task_id: clickupCommon.task_id(false), + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const { workspace_id } = context.propsValue + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://api.clickup.com/api/v2/team/${workspace_id}/webhook`, + body: { + endpoint: context.webhookUrl, + events: [ClickupEventType.TASK_TAG_UPDATED] + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token + }, + queryParams: {}, + } + + const response = await httpClient.sendRequest(request); + console.debug(`clickup.${ClickupEventType.TASK_TAG_UPDATED}.onEnable`, response) + + await context.store.put(`clickup_task_tag_updated_trigger`, response.body); + }, + async onDisable(context) { + const webhook = await context.store.get(`clickup_task_tag_updated_trigger`); + if (webhook != null) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `https://api.clickup.com/api/v2/webhook/${webhook.id}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth['access_token'], + }, + }; + const response = await httpClient.sendRequest(request); + console.debug(`clickup.${ClickupEventType.TASK_TAG_UPDATED}.onDisable`, response) + } + }, + async run(context) { + const payload = context.payload.body as ClickupWebhookPayload + + return [{ + ...payload, + task: await callClickupGetTask( + context.auth['access_token'], + payload.task_id + ) + }]; + } +}); + +interface WebhookInformation { + id: string + webhook: { + id: string + userid: number + team_id: number + endpoint: string + client_id: string + events: ClickupEventType[] + task_id: string + list_id: string + folder_id: string + space_id: string + health: { + status: string + fail_count: number + }, + secret: string + } +} diff --git a/packages/pieces/community/clickup/tsconfig.json b/packages/pieces/community/clickup/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/clickup/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/clickup/tsconfig.lib.json b/packages/pieces/community/clickup/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/clickup/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/clockify/.eslintrc.json b/packages/pieces/community/clockify/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/clockify/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/clockify/README.md b/packages/pieces/community/clockify/README.md new file mode 100644 index 0000000..48a5fec --- /dev/null +++ b/packages/pieces/community/clockify/README.md @@ -0,0 +1,7 @@ +# pieces-clockify + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-clockify` to build the library. diff --git a/packages/pieces/community/clockify/package.json b/packages/pieces/community/clockify/package.json new file mode 100644 index 0000000..99f6bce --- /dev/null +++ b/packages/pieces/community/clockify/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-clockify", + "version": "0.0.1" +} diff --git a/packages/pieces/community/clockify/project.json b/packages/pieces/community/clockify/project.json new file mode 100644 index 0000000..3f2651e --- /dev/null +++ b/packages/pieces/community/clockify/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-clockify", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/clockify/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/clockify", + "tsConfig": "packages/pieces/community/clockify/tsconfig.lib.json", + "packageJson": "packages/pieces/community/clockify/package.json", + "main": "packages/pieces/community/clockify/src/index.ts", + "assets": [ + "packages/pieces/community/clockify/*.md", + { + "input": "packages/pieces/community/clockify/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/clockify/src/index.ts b/packages/pieces/community/clockify/src/index.ts new file mode 100644 index 0000000..fc2476e --- /dev/null +++ b/packages/pieces/community/clockify/src/index.ts @@ -0,0 +1,64 @@ +import { createCustomApiCallAction, HttpMethod } from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { createTaskAction } from './lib/actions/create-task'; +import { createTimeEntryAction } from './lib/actions/create-time-entry'; +import { findRunningTimerAction } from './lib/actions/find-running-timer'; +import { findTaskAction } from './lib/actions/find-task'; +import { findTimeEntryAction } from './lib/actions/find-time-entry'; +import { startTimerAction } from './lib/actions/start-timer'; +import { stopTimerAction } from './lib/actions/stop-timer'; +import { BASE_URL, clockifyApiCall } from './lib/common/client'; +import { newTaskTrigger } from './lib/triggers/new-task'; +import { newTimeEntryTrigger } from './lib/triggers/new-time-entry'; +import { newTimerStartedTrigger } from './lib/triggers/new-timer-started'; + +export const clockifyAuth = PieceAuth.SecretText({ + displayName:'API Key', + description: `You can obtain your API key by navigating to **Preferences->Advanced**.`, + required: true, + validate: async ({ auth }) => { + try { + await clockifyApiCall({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: '/user', + }); + + return { + valid: true, + }; + } catch { + return { + valid: false, + error: 'Invalid API Key.', + }; + } + }, +}); + +export const clockify = createPiece({ + displayName: 'Clockify', + auth: clockifyAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/clockify.png', + authors: ['rimjhimyadav', 'kishanprmr'], + actions: [ + createTaskAction, + createTimeEntryAction, + startTimerAction, + stopTimerAction, + findTaskAction, + findTimeEntryAction, + findRunningTimerAction, + createCustomApiCallAction({ + auth: clockifyAuth, + baseUrl: () => BASE_URL, + authMapping: async (auth) => { + return { + 'X-Api-Key': auth as string, + }; + }, + }), + ], + triggers: [newTaskTrigger, newTimeEntryTrigger, newTimerStartedTrigger], +}); diff --git a/packages/pieces/community/clockify/src/lib/actions/create-task.ts b/packages/pieces/community/clockify/src/lib/actions/create-task.ts new file mode 100644 index 0000000..0f839bb --- /dev/null +++ b/packages/pieces/community/clockify/src/lib/actions/create-task.ts @@ -0,0 +1,68 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { clockifyAuth } from '../../index'; +import { clockifyApiCall } from '../common/client'; +import { assigneeIds, projectId, workspaceId } from '../common/props'; + +export const createTaskAction = createAction({ + auth: clockifyAuth, + name: 'create-task', + displayName: 'Create Task', + description: 'Creates a new in a specific project.', + props: { + workspaceId: workspaceId({ + displayName: 'Workspace', + required: true, + }), + projectId: projectId({ + displayName: 'Project', + required: true, + }), + name: Property.ShortText({ + displayName: 'Task Name', + required: true, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Active', + value: 'ACTIVE', + }, + { + label: 'Done', + value: 'DONE', + }, + { + label: 'All', + value: 'ALL', + }, + ], + }, + }), + assigneeIds: assigneeIds({ + displayName: 'Assignee', + required: false, + }), + }, + async run(context) { + const { workspaceId, projectId, name, status } = context.propsValue; + const assigneeIds = context.propsValue.assigneeIds ?? []; + + const response = await clockifyApiCall({ + apiKey: context.auth, + method: HttpMethod.POST, + resourceUri: `/workspaces/${workspaceId}/projects/${projectId}/tasks`, + body: { + name, + status, + assigneeIds, + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/clockify/src/lib/actions/create-time-entry.ts b/packages/pieces/community/clockify/src/lib/actions/create-time-entry.ts new file mode 100644 index 0000000..e08c03e --- /dev/null +++ b/packages/pieces/community/clockify/src/lib/actions/create-time-entry.ts @@ -0,0 +1,69 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { clockifyAuth } from '../../index'; +import { clockifyApiCall } from '../common/client'; +import { projectId, tagIds, taskId, workspaceId } from '../common/props'; + +export const createTimeEntryAction = createAction({ + auth: clockifyAuth, + name: 'create-time-entry', + displayName: 'Create Time Entry', + description: 'Creates a new time entry.', + props: { + workspaceId: workspaceId({ + displayName: 'Workspace', + required: true, + }), + start: Property.DateTime({ + displayName: 'Start Datetime', + required: true, + }), + end: Property.DateTime({ + displayName: 'End Datetime', + required: true, + }), + description: Property.LongText({ + displayName: 'Entry Description', + required: false, + }), + projectId: projectId({ + displayName: 'Project', + required: false, + }), + taskId: taskId({ + displayName: 'Task', + required: false, + }), + billable: Property.Checkbox({ + displayName: 'Billable', + required: false, + }), + tagIds: tagIds({ + displayName: 'Tags', + required: false, + }), + }, + async run(context) { + const { workspaceId, projectId, start, end, description, billable, taskId } = + context.propsValue; + const tagIds = context.propsValue.tagIds ?? []; + + const response = await clockifyApiCall({ + apiKey: context.auth, + method: HttpMethod.POST, + resourceUri: `/workspaces/${workspaceId}/time-entries`, + body: { + billable, + description, + start, + end, + projectId, + taskId, + tagIds, + type: 'REGULAR', + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/clockify/src/lib/actions/find-running-timer.ts b/packages/pieces/community/clockify/src/lib/actions/find-running-timer.ts new file mode 100644 index 0000000..4ef8e81 --- /dev/null +++ b/packages/pieces/community/clockify/src/lib/actions/find-running-timer.ts @@ -0,0 +1,29 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { clockifyAuth } from '../../index'; +import { clockifyApiCall } from '../common/client'; +import { workspaceId } from '../common/props'; + +export const findRunningTimerAction = createAction({ + auth: clockifyAuth, + name: 'find-running-timer', + displayName: 'Find Running Timer', + description: 'Finds currently running timer on specified workspace.', + props: { + workspaceId: workspaceId({ + displayName: 'Workspace', + required: true, + }), + }, + async run(context) { + const { workspaceId } = context.propsValue; + + const response = await clockifyApiCall({ + apiKey: context.auth, + method: HttpMethod.GET, + resourceUri: `/workspaces/${workspaceId}/time-entries/status/in-progress`, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/clockify/src/lib/actions/find-task.ts b/packages/pieces/community/clockify/src/lib/actions/find-task.ts new file mode 100644 index 0000000..7166ab5 --- /dev/null +++ b/packages/pieces/community/clockify/src/lib/actions/find-task.ts @@ -0,0 +1,45 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { clockifyAuth } from '../../index'; +import { clockifyApiCall } from '../common/client'; +import { projectId, workspaceId } from '../common/props'; + +export const findTaskAction = createAction({ + auth: clockifyAuth, + name: 'find-task', + displayName: 'Find Task', + description: 'Finds an existing task in a specific project.', + props: { + workspaceId: workspaceId({ + displayName: 'Workspace', + required: true, + }), + projectId: projectId({ + displayName: 'Project', + required: true, + }), + name: Property.ShortText({ + displayName: 'Task Name', + required: true, + }), + exactMatch: Property.Checkbox({ + displayName: 'Exact Match ?', + required: false, + }), + }, + async run(context) { + const { workspaceId, projectId, name, exactMatch } = context.propsValue; + + const response = await clockifyApiCall({ + apiKey: context.auth, + method: HttpMethod.GET, + resourceUri: `/workspaces/${workspaceId}/projects/${projectId}/tasks`, + query: { + name, + 'strict-name-search': exactMatch ? 'true' : 'false', + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/clockify/src/lib/actions/find-time-entry.ts b/packages/pieces/community/clockify/src/lib/actions/find-time-entry.ts new file mode 100644 index 0000000..6745cde --- /dev/null +++ b/packages/pieces/community/clockify/src/lib/actions/find-time-entry.ts @@ -0,0 +1,66 @@ +import { HttpMethod, QueryParams } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { clockifyAuth } from '../../index'; +import { clockifyApiCall } from '../common/client'; +import { projectId, taskId, workspaceId } from '../common/props'; + +export const findTimeEntryAction = createAction({ + auth: clockifyAuth, + name: 'find-time-entry', + displayName: 'Find Time Entry', + description: 'Finds a time entry by description, start datetime or end datetime.', + props: { + workspaceId: workspaceId({ + displayName: 'Workspace', + required: true, + }), + start: Property.DateTime({ + displayName: 'Start Datetime', + required: false, + }), + end: Property.DateTime({ + displayName: 'End Datetime', + required: false, + }), + description: Property.LongText({ + displayName: 'Entry Description', + required: false, + }), + projectId: projectId({ + displayName: 'Project', + required: false, + }), + taskId: taskId({ + displayName: 'Task', + required: false, + }), + }, + async run(context) { + const { workspaceId, projectId, start, end, description, taskId } = context.propsValue; + + const currentUserResponse = await clockifyApiCall<{ id: string; email: string }>({ + apiKey: context.auth, + method: HttpMethod.GET, + resourceUri: `/user`, + }); + + const userId = currentUserResponse.id; + + const qs: QueryParams = { hydrated: 'true' }; + + if (description) qs['description'] = description; + if (start) qs['start'] = start; + if (end) qs['end'] = end; + if (projectId) qs['project'] = projectId; + if (taskId) qs['task'] = taskId; + + const response = await clockifyApiCall({ + apiKey: context.auth, + method: HttpMethod.GET, + resourceUri: `/workspaces/${workspaceId}/user/${userId}/time-entries`, + query: qs, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/clockify/src/lib/actions/start-timer.ts b/packages/pieces/community/clockify/src/lib/actions/start-timer.ts new file mode 100644 index 0000000..ca74fe6 --- /dev/null +++ b/packages/pieces/community/clockify/src/lib/actions/start-timer.ts @@ -0,0 +1,59 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { clockifyAuth } from '../../index'; +import { clockifyApiCall } from '../common/client'; +import { projectId, tagIds, taskId, workspaceId } from '../common/props'; + +export const startTimerAction = createAction({ + auth: clockifyAuth, + name: 'start-timer', + displayName: 'Start Timer', + description: 'Starts a new time entry.', + props: { + workspaceId: workspaceId({ + displayName: 'Workspace', + required: true, + }), + description: Property.LongText({ + displayName: 'Entry Description', + required: false, + }), + projectId: projectId({ + displayName: 'Project', + required: false, + }), + taskId: taskId({ + displayName: 'Task', + required: false, + }), + billable: Property.Checkbox({ + displayName: 'Billable', + required: false, + }), + tagIds: tagIds({ + displayName: 'Tags', + required: false, + }), + }, + async run(context) { + const { workspaceId, projectId, description, billable, taskId } = context.propsValue; + const tagIds = context.propsValue.tagIds ?? []; + + const response = await clockifyApiCall({ + apiKey: context.auth, + method: HttpMethod.POST, + resourceUri: `/workspaces/${workspaceId}/time-entries`, + body: { + billable, + description, + start: new Date().toISOString(), + projectId, + taskId, + tagIds, + type: 'REGULAR', + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/clockify/src/lib/actions/stop-timer.ts b/packages/pieces/community/clockify/src/lib/actions/stop-timer.ts new file mode 100644 index 0000000..75caf79 --- /dev/null +++ b/packages/pieces/community/clockify/src/lib/actions/stop-timer.ts @@ -0,0 +1,40 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { clockifyAuth } from '../../index'; +import { clockifyApiCall } from '../common/client'; +import { workspaceId } from '../common/props'; + +export const stopTimerAction = createAction({ + auth: clockifyAuth, + name: 'stop-timer', + displayName: 'Stop Timer', + description: 'Stops currently running timer on specified workspace.', + props: { + workspaceId: workspaceId({ + displayName: 'Workspace', + required: true, + }), + }, + async run(context) { + const { workspaceId } = context.propsValue; + + const currentUserResponse = await clockifyApiCall<{ id: string; email: string }>({ + apiKey: context.auth, + method: HttpMethod.GET, + resourceUri: `/user`, + }); + + const userId = currentUserResponse.id; + + const response = await clockifyApiCall({ + apiKey: context.auth, + method: HttpMethod.PATCH, + resourceUri: `/workspaces/${workspaceId}/user/${userId}/time-entries`, + body: { + end: new Date().toISOString(), + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/clockify/src/lib/common/client.ts b/packages/pieces/community/clockify/src/lib/common/client.ts new file mode 100644 index 0000000..f203ff7 --- /dev/null +++ b/packages/pieces/community/clockify/src/lib/common/client.ts @@ -0,0 +1,48 @@ +import { + httpClient, + HttpMessageBody, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; + +export type ClockifyApiCallParams = { + apiKey: string; + method: HttpMethod; + resourceUri: string; + query?: Record; + body?: any; +}; + +export const BASE_URL = 'https://api.clockify.me/api/v1'; + +export async function clockifyApiCall({ + apiKey, + method, + resourceUri, + query, + body, +}: ClockifyApiCallParams): Promise { + const qs: QueryParams = {}; + + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + qs[key] = String(value); + } + } + } + + const request: HttpRequest = { + method, + url: BASE_URL + resourceUri, + headers: { + 'X-Api-Key': apiKey, + }, + queryParams: qs, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} diff --git a/packages/pieces/community/clockify/src/lib/common/props.ts b/packages/pieces/community/clockify/src/lib/common/props.ts new file mode 100644 index 0000000..fd39459 --- /dev/null +++ b/packages/pieces/community/clockify/src/lib/common/props.ts @@ -0,0 +1,203 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { Property } from '@activepieces/pieces-framework'; +import { clockifyApiCall } from './client'; + +interface DropdownParams { + displayName: string; + description?: string; + required: boolean; +} + +export const workspaceId = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + description: params.description, + required: params.required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + const response = await clockifyApiCall<{ id: string; name: string }[]>({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: '/workspaces', + }); + + return { + disabled: false, + options: response.map((workspace) => ({ + label: workspace.name, + value: workspace.id, + })), + }; + }, + }); + +export const projectId = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + description: params.description, + required: params.required, + refreshers: ['workspaceId'], + options: async ({ auth, workspaceId }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + if (!workspaceId) { + return { + disabled: true, + options: [], + placeholder: 'Please select workspace first.', + }; + } + + const response = await clockifyApiCall<{ id: string; name: string }[]>({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: `/workspaces/${workspaceId}/projects`, + }); + + return { + disabled: false, + options: response.map((project) => ({ + label: project.name, + value: project.id, + })), + }; + }, + }); + +export const assigneeIds = (params: DropdownParams) => + Property.MultiSelectDropdown({ + displayName: params.displayName, + description: params.description, + required: params.required, + refreshers: ['workspaceId'], + options: async ({ auth, workspaceId }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + if (!workspaceId) { + return { + disabled: true, + options: [], + placeholder: 'Please select workspace first.', + }; + } + + const response = await clockifyApiCall<{ id: string; email: string }[]>({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: `/workspaces/${workspaceId}/users`, + }); + + return { + disabled: false, + options: response.map((user) => ({ + label: user.email, + value: user.id, + })), + }; + }, + }); + +export const taskId = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + description: params.description, + required: params.required, + refreshers: ['workspaceId', 'projectId'], + options: async ({ auth, workspaceId, projectId }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + if (!workspaceId) { + return { + disabled: true, + options: [], + placeholder: 'Please select workspace first.', + }; + } + + if (!projectId) { + return { + disabled: true, + options: [], + placeholder: 'Please select project first.', + }; + } + + const response = await clockifyApiCall<{ id: string; name: string }[]>({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: `/workspaces/${workspaceId}/projects/${projectId}/tasks`, + }); + + return { + disabled: false, + options: response.map((task) => ({ + label: task.name, + value: task.id, + })), + }; + }, + }); + +export const tagIds = (params: DropdownParams) => + Property.MultiSelectDropdown({ + displayName: params.displayName, + description: params.description, + required: params.required, + refreshers: ['workspaceId'], + options: async ({ auth, workspaceId }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + if (!workspaceId) { + return { + disabled: true, + options: [], + placeholder: 'Please select workspace first.', + }; + } + const response = await clockifyApiCall<{ id: string; name: string }[]>({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: `/workspaces/${workspaceId}/tags`, + }); + + return { + disabled: false, + options: response.map((tag) => ({ + label: tag.name, + value: tag.id, + })), + }; + }, + }); diff --git a/packages/pieces/community/clockify/src/lib/triggers/new-task.ts b/packages/pieces/community/clockify/src/lib/triggers/new-task.ts new file mode 100644 index 0000000..561d356 --- /dev/null +++ b/packages/pieces/community/clockify/src/lib/triggers/new-task.ts @@ -0,0 +1,91 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { isNil } from '@activepieces/shared'; +import { clockifyAuth } from '../../index'; +import { clockifyApiCall } from '../common/client'; +import { projectId, workspaceId } from '../common/props'; + +const TRIGGER_KEY = 'new-task-trigger'; + +export const newTaskTrigger = createTrigger({ + auth: clockifyAuth, + name: 'new-task', + displayName: 'New Task', + description: 'Triggers when a new task is created in specified project.', + type: TriggerStrategy.WEBHOOK, + props: { + workspaceId: workspaceId({ + displayName: 'Workspace', + required: true, + }), + projectId: projectId({ + displayName: 'Project', + required: true, + }), + }, + async onEnable(context) { + const { workspaceId, projectId } = context.propsValue; + + const response = await clockifyApiCall<{ id: string }>({ + apiKey: context.auth, + method: HttpMethod.POST, + resourceUri: `/workspaces/${workspaceId}/webhooks`, + body: { + url: context.webhookUrl, + webhookEvent: 'NEW_TASK', + triggerSourceType: 'PROJECT_ID', + triggerSource: [projectId], + }, + }); + + await context.store.put(TRIGGER_KEY, response.id); + }, + async onDisable(context) { + const { workspaceId } = context.propsValue; + + const webhookId = await context.store.get(TRIGGER_KEY); + + if (!isNil(webhookId)) { + await clockifyApiCall({ + apiKey: context.auth, + method: HttpMethod.DELETE, + resourceUri: `/workspaces/${workspaceId}/webhooks/${webhookId}`, + }); + } + }, + async test(context) { + const { workspaceId, projectId } = context.propsValue; + + const response = await clockifyApiCall<{ id: string }[]>({ + apiKey: context.auth, + method: HttpMethod.GET, + resourceUri: `/workspaces/${workspaceId}/projects/${projectId}/tasks`, + query: { + page: '1', + 'page-size': 5, + }, + }); + + return response; + }, + async run(context) { + return [context.payload.body]; + }, + sampleData: { + id: '684538940300f917a02f642f', + name: 'Test', + projectId: '68444b15551a9934b5034263', + workspaceId: '684446430300f917a02c198b', + assigneeIds: [], + assigneeId: '', + userGroupIds: [], + estimate: 'PT0S', + status: 'ACTIVE', + budgetEstimate: 0, + billable: true, + hourlyRate: null, + costRate: null, + progress: null, + duration: 'PT0S', + }, +}); diff --git a/packages/pieces/community/clockify/src/lib/triggers/new-time-entry.ts b/packages/pieces/community/clockify/src/lib/triggers/new-time-entry.ts new file mode 100644 index 0000000..af7cf57 --- /dev/null +++ b/packages/pieces/community/clockify/src/lib/triggers/new-time-entry.ts @@ -0,0 +1,187 @@ +import { HttpMethod, QueryParams } from '@activepieces/pieces-common'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { isNil } from '@activepieces/shared'; +import { clockifyAuth } from '../../index'; +import { clockifyApiCall } from '../common/client'; +import { projectId, taskId, workspaceId } from '../common/props'; + +const TRIGGER_KEY = 'new-time-entry-trigger'; + +export const newTimeEntryTrigger = createTrigger({ + auth: clockifyAuth, + name: 'new-time-entry', + displayName: 'New Time Entry', + description: 'Triggers when a new time entry is created.', + type: TriggerStrategy.WEBHOOK, + props: { + workspaceId: workspaceId({ + displayName: 'Workspace', + required: true, + }), + projectId: projectId({ + displayName: 'Project', + required: false, + }), + taskId: taskId({ + displayName: 'Task', + required: false, + }), + }, + async onEnable(context) { + const { workspaceId, projectId, taskId } = context.propsValue; + + const payload: Record = { + url: context.webhookUrl, + webhookEvent: 'NEW_TIME_ENTRY', + }; + + if (workspaceId) { + payload['triggerSourceType'] = 'WORKSPACE_ID'; + payload['triggerSource'] = [workspaceId]; + } + + if (projectId) { + payload['triggerSourceType'] = 'PROJECT_ID'; + payload['triggerSource'] = [projectId]; + } + + if (taskId) { + payload['triggerSourceType'] = 'TASK_ID'; + payload['triggerSource'] = [taskId]; + } + + const response = await clockifyApiCall<{ id: string }>({ + apiKey: context.auth, + method: HttpMethod.POST, + resourceUri: `/workspaces/${workspaceId}/webhooks`, + body: payload, + }); + + await context.store.put(TRIGGER_KEY, response.id); + }, + async onDisable(context) { + const { workspaceId } = context.propsValue; + + const webhookId = await context.store.get(TRIGGER_KEY); + + if (!isNil(webhookId)) { + await clockifyApiCall({ + apiKey: context.auth, + method: HttpMethod.DELETE, + resourceUri: `/workspaces/${workspaceId}/webhooks/${webhookId}`, + }); + } + }, + async test(context) { + const { workspaceId, projectId, taskId } = context.propsValue; + const currentUserResponse = await clockifyApiCall<{ id: string; email: string }>({ + apiKey: context.auth, + method: HttpMethod.GET, + resourceUri: `/user`, + }); + + const userId = currentUserResponse.id; + + const qs: QueryParams = { hydrated: 'true', page: '1', 'page-size': '5' }; + + if (projectId) qs['project'] = projectId; + if (taskId) qs['task'] = taskId; + + const response = await clockifyApiCall<{id:string}[]>({ + apiKey: context.auth, + method: HttpMethod.GET, + resourceUri: `/workspaces/${workspaceId}/user/${userId}/time-entries`, + query: qs, + }); + + return response; + }, + async run(context) { + return [context.payload.body]; + }, + sampleData: { + id: '68453d0cf0c88d522704ab76', + description: 'Test', + userId: '684446430300f917a02c198a', + billable: true, + projectId: '68444b15551a9934b5034263', + timeInterval: { + start: '2025-06-16T07:05:00Z', + end: '2025-06-16T18:53:00Z', + duration: 'PT11H48M', + timeZone: 'Asia/Calcutta', + offsetStart: 19800, + offsetEnd: 19800, + zonedStart: '2025-06-16T12:35:00', + zonedEnd: '2025-06-17T00:23:00', + }, + workspaceId: '684446430300f917a02c198b', + isLocked: false, + hourlyRate: null, + costRate: null, + customFieldValues: [], + type: 'REGULAR', + kioskId: null, + approvalStatus: null, + projectCurrency: null, + currentlyRunning: false, + project: { + name: 'Test', + clientId: '', + workspaceId: '684446430300f917a02c198b', + billable: true, + estimate: { + estimate: 'PT0S', + type: 'AUTO', + }, + color: '#039BE5', + archived: false, + clientName: '', + duration: 'PT47H12M45S', + note: '', + activeEstimate: 'NONE', + timeEstimate: { + includeNonBillable: true, + estimate: 0, + type: 'AUTO', + resetOption: null, + }, + budgetEstimate: null, + id: '68444b15551a9934b5034263', + public: true, + template: false, + }, + task: { + name: 'test', + projectId: '68444b15551a9934b5034263', + assigneeId: '684446430300f917a02c198a', + assigneeIds: ['684446430300f917a02c198a'], + userGroupIds: [], + estimate: 'PT0S', + status: 'ACTIVE', + workspaceId: '684446430300f917a02c198b', + budgetEstimate: 0, + billable: true, + hourlyRate: null, + costRate: null, + auditMetadata: { + updatedAt: 1749306307000, + }, + id: '68444badb7688b61f151284d', + duration: 'PT35H24M41S', + }, + user: { + id: '684446430300f917a02c198a', + name: 'johndoe', + status: 'ACTIVE', + }, + tags: [ + { + name: 'new', + workspaceId: '684446430300f917a02c198b', + archived: false, + id: '68452a1b0300f917a02f30bc', + }, + ], + }, +}); diff --git a/packages/pieces/community/clockify/src/lib/triggers/new-timer-started.ts b/packages/pieces/community/clockify/src/lib/triggers/new-timer-started.ts new file mode 100644 index 0000000..54c77c1 --- /dev/null +++ b/packages/pieces/community/clockify/src/lib/triggers/new-timer-started.ts @@ -0,0 +1,72 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { isNil } from '@activepieces/shared'; +import { clockifyAuth } from '../../index'; +import { clockifyApiCall } from '../common/client'; +import { workspaceId } from '../common/props'; + +const TRIGGER_KEY = 'new-timer-started-trigger'; + +export const newTimerStartedTrigger = createTrigger({ + auth: clockifyAuth, + name: 'new-timer-started', + displayName: 'New Timer Started', + description: 'Triggers when a new entry is started and running.', + type: TriggerStrategy.WEBHOOK, + props: { + workspaceId: workspaceId({ + displayName: 'Workspace', + required: true, + }), + }, + async onEnable(context) { + const { workspaceId } = context.propsValue; + + const response = await clockifyApiCall<{ id: string }>({ + apiKey: context.auth, + method: HttpMethod.POST, + resourceUri: `/workspaces/${workspaceId}/webhooks`, + body: { + url: context.webhookUrl, + webhookEvent: 'NEW_TIMER_STARTED', + triggerSourceType: 'WORKSPACE_ID', + triggerSource: [workspaceId], + }, + }); + + await context.store.put(TRIGGER_KEY, response.id); + }, + async onDisable(context) { + const { workspaceId } = context.propsValue; + + const webhookId = await context.store.get(TRIGGER_KEY); + + if (!isNil(webhookId)) { + await clockifyApiCall<{ id: string }>({ + apiKey: context.auth, + method: HttpMethod.DELETE, + resourceUri: `/workspaces/${workspaceId}/webhooks/${webhookId}`, + }); + } + }, + async run(context) { + return [context.payload.body]; + }, + sampleData: { + id: '684538940300f917a02f642f', + name: 'Test', + projectId: '68444b15551a9934b5034263', + workspaceId: '684446430300f917a02c198b', + assigneeIds: [], + assigneeId: '', + userGroupIds: [], + estimate: 'PT0S', + status: 'ACTIVE', + budgetEstimate: 0, + billable: true, + hourlyRate: null, + costRate: null, + progress: null, + duration: 'PT0S', + }, +}); diff --git a/packages/pieces/community/clockify/tsconfig.json b/packages/pieces/community/clockify/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/clockify/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/clockify/tsconfig.lib.json b/packages/pieces/community/clockify/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/clockify/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/clockodo/.eslintrc.json b/packages/pieces/community/clockodo/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/clockodo/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/clockodo/README.md b/packages/pieces/community/clockodo/README.md new file mode 100644 index 0000000..bfb797b --- /dev/null +++ b/packages/pieces/community/clockodo/README.md @@ -0,0 +1,7 @@ +# pieces-clockodo + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-clockodo` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/clockodo/package.json b/packages/pieces/community/clockodo/package.json new file mode 100644 index 0000000..075b54d --- /dev/null +++ b/packages/pieces/community/clockodo/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-clockodo", + "version": "0.1.4" +} \ No newline at end of file diff --git a/packages/pieces/community/clockodo/project.json b/packages/pieces/community/clockodo/project.json new file mode 100644 index 0000000..67b1656 --- /dev/null +++ b/packages/pieces/community/clockodo/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-clockodo", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/clockodo/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/clockodo", + "tsConfig": "packages/pieces/community/clockodo/tsconfig.lib.json", + "packageJson": "packages/pieces/community/clockodo/package.json", + "main": "packages/pieces/community/clockodo/src/index.ts", + "assets": [ + "packages/pieces/community/clockodo/*.md", + { + "input": "packages/pieces/community/clockodo/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/clockodo/src/index.ts b/packages/pieces/community/clockodo/src/index.ts new file mode 100644 index 0000000..8c9026e --- /dev/null +++ b/packages/pieces/community/clockodo/src/index.ts @@ -0,0 +1,47 @@ +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; + +import { PieceCategory } from '@activepieces/shared'; +import actions from './lib/actions'; +import triggers from './lib/triggers'; + +export const clockodoAuth = PieceAuth.CustomAuth({ + required: true, + props: { + email: Property.ShortText({ + displayName: 'E-Mail', + required: true, + description: 'The email of your clockodo user', + }), + token: PieceAuth.SecretText({ + displayName: 'API-Token', + description: 'Your api token (can be found in profile settings)', + required: true, + }), + company_name: Property.ShortText({ + displayName: 'Company Name', + description: 'Your company name or app name', + required: true, + }), + company_email: Property.ShortText({ + displayName: 'Company E-Mail', + description: 'A contact email for your company or app', + required: true, + }), + }, +}); + +export const clockodo = createPiece({ + displayName: 'Clockodo', + description: 'Time tracking made easy', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/clockodo.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: ["JanHolger","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + auth: clockodoAuth, + actions, + triggers, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/absence/create-absence.ts b/packages/pieces/community/clockodo/src/lib/actions/absence/create-absence.ts new file mode 100644 index 0000000..4a50afb --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/absence/create-absence.ts @@ -0,0 +1,60 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { + clockodoCommon, + emptyToNull, + makeClient, + reformatDate, +} from '../../common'; +import { AbsenceStatus, AbsenceType } from '../../common/models/absence'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'create_absence', + displayName: 'Create Absence', + description: 'Creates a absence in clockodo', + props: { + date_since: Property.DateTime({ + displayName: 'Start Date', + required: true, + }), + date_until: Property.DateTime({ + displayName: 'End Date', + required: true, + }), + type: clockodoCommon.absenceType(true), + user_id: clockodoCommon.user_id(false), + half_days: Property.Checkbox({ + displayName: 'Half Days', + required: false, + }), + approved: Property.Checkbox({ + displayName: 'Approved', + required: false, + }), + note: Property.LongText({ + displayName: 'Note', + required: false, + }), + sick_note: Property.Checkbox({ + displayName: 'Sick Note', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.createAbsence({ + date_since: reformatDate(propsValue.date_since) as string, + date_until: reformatDate(propsValue.date_until) as string, + type: propsValue.type as AbsenceType, + users_id: propsValue.user_id, + count_days: propsValue.half_days ? 0.5 : 1, + status: propsValue.approved + ? AbsenceStatus.APPROVED + : AbsenceStatus.REQUESTED, + note: emptyToNull(propsValue.note), + sick_note: propsValue.sick_note, + }); + return res.absence; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/absence/delete-absence.ts b/packages/pieces/community/clockodo/src/lib/actions/absence/delete-absence.ts new file mode 100644 index 0000000..a1b42d8 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/absence/delete-absence.ts @@ -0,0 +1,20 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'delete_absence', + displayName: 'Delete Absence', + description: 'Deletes an absence in clockodo', + props: { + absence_id: Property.Number({ + displayName: 'Absence ID', + required: true, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + await client.deleteAbsence(propsValue.absence_id); + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/absence/get-absence.ts b/packages/pieces/community/clockodo/src/lib/actions/absence/get-absence.ts new file mode 100644 index 0000000..315cb03 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/absence/get-absence.ts @@ -0,0 +1,21 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'get_absence', + displayName: 'Get Absence', + description: 'Retrieves a single absence from clockodo', + props: { + absence_id: Property.Number({ + displayName: 'Absence ID', + required: true, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.getAbsence(propsValue.absence_id); + return res.absence; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/absence/index.ts b/packages/pieces/community/clockodo/src/lib/actions/absence/index.ts new file mode 100644 index 0000000..f77e07c --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/absence/index.ts @@ -0,0 +1,13 @@ +import createAction from './create-absence'; +import getAction from './get-absence'; +import updateAction from './update-absence'; +import listAction from './list-absence'; +import deleteAction from './delete-absence'; + +export default [ + createAction, + getAction, + updateAction, + listAction, + deleteAction, +]; diff --git a/packages/pieces/community/clockodo/src/lib/actions/absence/list-absence.ts b/packages/pieces/community/clockodo/src/lib/actions/absence/list-absence.ts new file mode 100644 index 0000000..fc0487a --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/absence/list-absence.ts @@ -0,0 +1,27 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { clockodoCommon, makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'list_absences', + displayName: 'Get Absences', + description: 'Fetches absences from clockodo', + props: { + year: Property.Number({ + displayName: 'Year', + required: true, + }), + user_id: clockodoCommon.user_id(false), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.listAbsences({ + year: propsValue.year, + users_id: propsValue.user_id, + }); + return { + absences: res.absences, + }; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/absence/update-absence.ts b/packages/pieces/community/clockodo/src/lib/actions/absence/update-absence.ts new file mode 100644 index 0000000..8b998c9 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/absence/update-absence.ts @@ -0,0 +1,68 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { + clockodoCommon, + emptyToNull, + makeClient, + reformatDate, +} from '../../common'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'update_absence', + displayName: 'Update Absence', + description: 'Updates an absence in clockodo', + props: { + absence_id: Property.Number({ + displayName: 'Absence ID', + required: true, + }), + date_since: Property.DateTime({ + displayName: 'Start Date', + required: false, + }), + date_until: Property.DateTime({ + displayName: 'End Date', + required: false, + }), + type: clockodoCommon.absenceType(false), + status: Property.StaticDropdown({ + displayName: 'Status', + required: false, + options: { + options: [ + { value: 0, label: 'Requested' }, + { value: 1, label: 'Approved' }, + { value: 2, label: 'Declined' }, + { value: 3, label: 'Approval cancelled' }, + { value: 4, label: 'Request cancelled' }, + ], + }, + }), + half_days: Property.Checkbox({ + displayName: 'Half Days', + required: false, + }), + note: Property.LongText({ + displayName: 'Note', + required: false, + }), + sick_note: Property.Checkbox({ + displayName: 'Sick Note', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.updateAbsence(propsValue.absence_id, { + date_since: reformatDate(propsValue.date_since), + date_until: reformatDate(propsValue.date_until), + type: propsValue.type, + status: propsValue.status, + count_days: propsValue.half_days ? 0.5 : 1, + note: emptyToNull(propsValue.note), + sick_note: propsValue.sick_note, + }); + return res.absence; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/customer/create-customer.ts b/packages/pieces/community/clockodo/src/lib/actions/customer/create-customer.ts new file mode 100644 index 0000000..0350106 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/customer/create-customer.ts @@ -0,0 +1,45 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { clockodoCommon, emptyToNull, makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'create_customer', + displayName: 'Create Customer', + description: 'Creates a customer in clockodo', + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + number: Property.ShortText({ + displayName: 'Number', + required: false, + }), + active: Property.Checkbox({ + displayName: 'Active', + required: false, + }), + billable: Property.Checkbox({ + displayName: 'Billable', + required: false, + }), + note: Property.LongText({ + displayName: 'Note', + required: false, + }), + color: clockodoCommon.color(false), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.createCustomer({ + name: propsValue.name, + number: emptyToNull(propsValue.number), + active: propsValue.active, + billable_default: propsValue.billable, + note: emptyToNull(propsValue.note), + color: propsValue.color, + }); + return res.customer; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/customer/delete-customer.ts b/packages/pieces/community/clockodo/src/lib/actions/customer/delete-customer.ts new file mode 100644 index 0000000..e943987 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/customer/delete-customer.ts @@ -0,0 +1,17 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { clockodoCommon, makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'delete_customer', + displayName: 'Delete Customer', + description: 'Deletes a customer in clockodo', + props: { + customer_id: clockodoCommon.customer_id(true, false), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + await client.deleteCustomer(propsValue.customer_id as number); + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/customer/get-customer.ts b/packages/pieces/community/clockodo/src/lib/actions/customer/get-customer.ts new file mode 100644 index 0000000..4e3a477 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/customer/get-customer.ts @@ -0,0 +1,18 @@ +import { clockodoCommon, makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; +import { createAction } from '@activepieces/pieces-framework'; + +export default createAction({ + auth: clockodoAuth, + name: 'get_customer', + displayName: 'Get Customer', + description: 'Retrieves a single customer from clockodo', + props: { + customer_id: clockodoCommon.customer_id(true, undefined), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.getCustomer(propsValue.customer_id as number); + return res.customer; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/customer/index.ts b/packages/pieces/community/clockodo/src/lib/actions/customer/index.ts new file mode 100644 index 0000000..d316e4c --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/customer/index.ts @@ -0,0 +1,13 @@ +import createAction from './create-customer'; +import getAction from './get-customer'; +import updateAction from './update-customer'; +import listAction from './list-customers'; +import deleteAction from './delete-customer'; + +export default [ + createAction, + getAction, + updateAction, + listAction, + deleteAction, +]; diff --git a/packages/pieces/community/clockodo/src/lib/actions/customer/list-customers.ts b/packages/pieces/community/clockodo/src/lib/actions/customer/list-customers.ts new file mode 100644 index 0000000..90c34cd --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/customer/list-customers.ts @@ -0,0 +1,45 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { makeClient } from '../../common'; +import { CustomerListFilter } from '../../common/models/customer'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'list_customers', + displayName: 'Get Customers', + description: 'Fetches customers from clockodo', + props: { + active_filter: Property.Checkbox({ + displayName: 'Active Filter', + description: 'Filter customers by their active status', + required: false, + defaultValue: true, + }), + page: Property.Number({ + displayName: 'Page', + description: 'Reads only the specified page', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const filter: CustomerListFilter = { + active: propsValue.active_filter, + }; + if (propsValue.page !== undefined) { + const res = await client.listCustomers({ + page: propsValue.page, + filter, + }); + return { + pagination: res.paging, + customers: res.customers, + }; + } else { + const customers = await client.listAllCustomers(filter); + return { + customers, + }; + } + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/customer/update-customer.ts b/packages/pieces/community/clockodo/src/lib/actions/customer/update-customer.ts new file mode 100644 index 0000000..f43c3a4 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/customer/update-customer.ts @@ -0,0 +1,46 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { clockodoCommon, emptyToNull, makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'update_customer', + displayName: 'Update Customer', + description: 'Updates a customer in clockodo', + props: { + customer_id: clockodoCommon.customer_id(true, undefined), + name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + number: Property.ShortText({ + displayName: 'Number', + required: false, + }), + active: Property.Checkbox({ + displayName: 'Active', + required: false, + }), + billable: Property.Checkbox({ + displayName: 'Billable', + required: false, + }), + note: Property.LongText({ + displayName: 'Note', + required: false, + }), + color: clockodoCommon.color(false), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.updateCustomer(propsValue.customer_id as number, { + name: propsValue.name, + number: emptyToNull(propsValue.number), + active: propsValue.active, + billable_default: propsValue.billable, + note: emptyToNull(propsValue.note), + color: propsValue.color, + }); + return res.customer; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/entry/create-entry.ts b/packages/pieces/community/clockodo/src/lib/actions/entry/create-entry.ts new file mode 100644 index 0000000..e48cf5d --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/entry/create-entry.ts @@ -0,0 +1,47 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { clockodoCommon, makeClient, reformatDateTime } from '../../common'; +import { TimeRecordEntry } from '../../common/models/entry'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'create_entry', + displayName: 'Create Entry', + description: 'Creates an entry in clockodo', + props: { + customer_id: clockodoCommon.customer_id(), + project_id: clockodoCommon.project_id(false), + service_id: clockodoCommon.service_id(), + time_since: Property.DateTime({ + displayName: 'Start Time', + required: true, + }), + time_until: Property.DateTime({ + displayName: 'End Time', + required: true, + }), + text: Property.LongText({ + displayName: 'Description', + required: false, + }), + hourly_rate: Property.Number({ + displayName: 'Hourly Rate', + required: false, + }), + user_id: clockodoCommon.user_id(false), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.createEntry({ + customers_id: propsValue.customer_id, + projects_id: propsValue.project_id, + services_id: propsValue.service_id, + time_since: reformatDateTime(propsValue.time_since), + time_until: reformatDateTime(propsValue.time_until), + text: propsValue.text, + hourly_rate: propsValue.hourly_rate, + users_id: propsValue.user_id, + } as TimeRecordEntry); // For now we only support time records + return res.entry; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/entry/delete-entry.ts b/packages/pieces/community/clockodo/src/lib/actions/entry/delete-entry.ts new file mode 100644 index 0000000..fc35a3c --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/entry/delete-entry.ts @@ -0,0 +1,20 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'delete_entry', + displayName: 'Delete Entry', + description: 'Deletes an entry in clockodo', + props: { + entry_id: Property.Number({ + displayName: 'Entry ID', + required: true, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + await client.deleteEntry(propsValue.entry_id); + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/entry/get-entry.ts b/packages/pieces/community/clockodo/src/lib/actions/entry/get-entry.ts new file mode 100644 index 0000000..ff976fb --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/entry/get-entry.ts @@ -0,0 +1,21 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'get_entry', + displayName: 'Get Entry', + description: 'Retrieves a single entry from clockodo', + props: { + entry_id: Property.Number({ + displayName: 'Entry ID', + required: true, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.getEntry(propsValue.entry_id); + return res.entry; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/entry/index.ts b/packages/pieces/community/clockodo/src/lib/actions/entry/index.ts new file mode 100644 index 0000000..e473638 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/entry/index.ts @@ -0,0 +1,13 @@ +import createAction from './create-entry'; +import getAction from './get-entry'; +import updateAction from './update-entry'; +import listAction from './list-entries'; +import deleteAction from './delete-entry'; + +export default [ + createAction, + getAction, + listAction, + updateAction, + deleteAction, +]; diff --git a/packages/pieces/community/clockodo/src/lib/actions/entry/list-entries.ts b/packages/pieces/community/clockodo/src/lib/actions/entry/list-entries.ts new file mode 100644 index 0000000..2f71390 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/entry/list-entries.ts @@ -0,0 +1,113 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { makeClient, reformatDateTime } from '../../common'; +import { BillableType, EntryListFilter } from '../../common/models/entry'; +import { clockodoAuth } from '../../../'; + +function calculateBillable( + billable?: boolean, + billed?: boolean +): BillableType | undefined { + if (billable === undefined && billed === undefined) { + return undefined; + } else { + if (billed) { + return 2; + } else { + return billable ? 1 : 0; + } + } +} + +export default createAction({ + auth: clockodoAuth, + name: 'list_entries', + displayName: 'Get Entries', + description: 'Fetches entries from clockodo', + props: { + time_since: Property.DateTime({ + displayName: 'Start Date', + required: true, + }), + time_until: Property.DateTime({ + displayName: 'End Date', + required: true, + }), + user_id_filter: Property.Number({ + displayName: 'Customer ID Filter', + description: 'Filter entries by their user', + required: false, + }), + customer_id_filter: Property.Number({ + displayName: 'Customer ID Filter', + description: 'Filter entries by their customer', + required: false, + }), + project_id_filter: Property.Number({ + displayName: 'Project ID Filter', + description: 'Filter entries by their project', + required: false, + }), + service_id_filter: Property.Number({ + displayName: 'Service ID Filter', + description: 'Filter entries by their service', + required: false, + }), + billable_filter: Property.Checkbox({ + displayName: 'Billable', + description: 'Only show entries that are billable', + required: false, + }), + billed_filter: Property.Checkbox({ + displayName: 'Billed', + description: 'Only show entries that are already billed', + required: false, + }), + enhanced_list: Property.Checkbox({ + displayName: 'Enhanced List', + description: 'Retrieves additional information about the entries', + required: false, + }), + page: Property.Number({ + displayName: 'Page', + description: 'Reads only the specified page', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const filter: EntryListFilter = { + users_id: propsValue.user_id_filter, + customers_id: propsValue.customer_id_filter, + projects_id: propsValue.project_id_filter, + services_id: propsValue.service_id_filter, + billable: calculateBillable( + propsValue.billable_filter, + propsValue.billed_filter + ), + }; + const time_since = reformatDateTime(propsValue.time_since) as string; + const time_until = reformatDateTime(propsValue.time_until) as string; + if (propsValue.page !== undefined) { + const res = await client.listEntries({ + time_since, + time_until, + enhanced_list: propsValue.enhanced_list, + page: propsValue.page, + filter, + }); + return { + pagination: res.paging, + entries: res.entries, + }; + } else { + const entries = await client.listAllEntries( + time_since, + time_until, + filter + ); + return { + entries, + }; + } + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/entry/update-entry.ts b/packages/pieces/community/clockodo/src/lib/actions/entry/update-entry.ts new file mode 100644 index 0000000..a65cb85 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/entry/update-entry.ts @@ -0,0 +1,45 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { clockodoCommon, makeClient, reformatDateTime } from '../../common'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'update_entry', + displayName: 'Update Entry', + description: 'Updates an entry in clockodo', + props: { + entry_id: Property.Number({ + displayName: 'Entry ID', + required: true, + }), + customer_id: clockodoCommon.customer_id(false), + project_id: clockodoCommon.project_id(false), + service_id: clockodoCommon.service_id(false), + time_since: Property.DateTime({ + displayName: 'Start Time', + required: false, + }), + time_until: Property.DateTime({ + displayName: 'End Time', + required: false, + }), + text: Property.LongText({ + displayName: 'Description', + required: false, + }), + user_id: clockodoCommon.user_id(false), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.updateEntry(propsValue.entry_id, { + customers_id: propsValue.customer_id, + projects_id: propsValue.project_id, + services_id: propsValue.service_id, + users_id: propsValue.user_id, + text: propsValue.text, + time_since: reformatDateTime(propsValue.time_since), + time_until: reformatDateTime(propsValue.time_until), + }); + return res.entry; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/index.ts b/packages/pieces/community/clockodo/src/lib/actions/index.ts new file mode 100644 index 0000000..fd9d501 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/index.ts @@ -0,0 +1,30 @@ +import entryActions from './entry'; +import customerActions from './customer'; +import projectActions from './project'; +import serviceActions from './service'; +import teamActions from './team'; +import userActions from './user'; +import absenceActions from './absence'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { clockodoAuth } from '../../index'; + +export default [ + ...entryActions, + ...customerActions, + ...projectActions, + ...serviceActions, + ...teamActions, + ...userActions, + ...absenceActions, + createCustomApiCallAction({ + baseUrl: () => 'https://my.clockodo.com/api', // Replace with the actual base URL + auth: clockodoAuth, + authMapping: async (auth) => ({ + 'X-ClockodoApiUser': (auth as { email: string }).email, + 'X-ClockodoApiKey': (auth as { token: string }).token, + 'X-Clockodo-External-Application': (auth as { company_name: string }) + .company_name, + 'Accept-Language': 'en', + }), + }), +]; diff --git a/packages/pieces/community/clockodo/src/lib/actions/project/create-project.ts b/packages/pieces/community/clockodo/src/lib/actions/project/create-project.ts new file mode 100644 index 0000000..8a514d2 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/project/create-project.ts @@ -0,0 +1,60 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { clockodoCommon, emptyToNull, makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'create_project', + displayName: 'Create Project', + description: 'Creates a project in clockodo', + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + customer_id: clockodoCommon.customer_id(), + number: Property.ShortText({ + displayName: 'Number', + required: false, + }), + active: Property.Checkbox({ + displayName: 'Active', + required: false, + }), + billable: Property.Checkbox({ + displayName: 'Billable', + required: false, + }), + budget: Property.Number({ + displayName: 'Budget', + required: false, + }), + budget_is_hours: Property.Checkbox({ + displayName: 'Budget in hours?', + required: false, + }), + budget_is_not_strict: Property.Checkbox({ + displayName: 'Soft Budget', + required: false, + }), + note: Property.LongText({ + displayName: 'Note', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.createProject({ + name: propsValue.name, + customers_id: propsValue.customer_id as number, + number: emptyToNull(propsValue.number), + active: propsValue.active, + billable_default: propsValue.billable, + note: emptyToNull(propsValue.note), + budget_money: propsValue.budget, + budget_is_hours: propsValue.budget_is_hours, + budget_is_not_strict: propsValue.budget_is_not_strict, + }); + return res.project; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/project/delete-project.ts b/packages/pieces/community/clockodo/src/lib/actions/project/delete-project.ts new file mode 100644 index 0000000..4870b4a --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/project/delete-project.ts @@ -0,0 +1,17 @@ +import { clockodoCommon, makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; +import { createAction } from '@activepieces/pieces-framework'; + +export default createAction({ + auth: clockodoAuth, + name: 'delete_project', + displayName: 'Delete Project', + description: 'Deletes a project in clockodo', + props: { + project_id: clockodoCommon.project_id(true, false, false), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + await client.deleteProject(propsValue.project_id as number); + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/project/get-project.ts b/packages/pieces/community/clockodo/src/lib/actions/project/get-project.ts new file mode 100644 index 0000000..4974473 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/project/get-project.ts @@ -0,0 +1,18 @@ +import { clockodoCommon, makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; +import { createAction } from '@activepieces/pieces-framework'; + +export default createAction({ + auth: clockodoAuth, + name: 'get_project', + displayName: 'Get Project', + description: 'Retrieves a single project from clockodo', + props: { + project_id: clockodoCommon.project_id(true, false, null), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.getProject(propsValue.project_id as number); + return res.project; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/project/index.ts b/packages/pieces/community/clockodo/src/lib/actions/project/index.ts new file mode 100644 index 0000000..43aa546 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/project/index.ts @@ -0,0 +1,13 @@ +import createAction from './create-project'; +import getAction from './get-project'; +import updateAction from './update-project'; +import listAction from './list-projects'; +import deleteAction from './delete-project'; + +export default [ + createAction, + getAction, + listAction, + updateAction, + deleteAction, +]; diff --git a/packages/pieces/community/clockodo/src/lib/actions/project/list-projects.ts b/packages/pieces/community/clockodo/src/lib/actions/project/list-projects.ts new file mode 100644 index 0000000..e2a7c37 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/project/list-projects.ts @@ -0,0 +1,51 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { makeClient } from '../../common'; +import { ProjectListFilter } from '../../common/models/project'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'list_projects', + displayName: 'Get Projects', + description: 'Fetches projects from clockodo', + props: { + customer_id_filter: Property.Number({ + displayName: 'Customer ID Filter', + description: 'Filter projects by their customer', + required: false, + }), + active_filter: Property.Checkbox({ + displayName: 'Active Filter', + description: 'Filter customers by their active status', + required: false, + defaultValue: true, + }), + page: Property.Number({ + displayName: 'Page', + description: 'Reads only the specified page', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const filter: ProjectListFilter = { + customers_id: propsValue.customer_id_filter, + active: propsValue.active_filter, + }; + if (propsValue.page !== undefined) { + const res = await client.listProjects({ + page: propsValue.page, + filter, + }); + return { + pagination: res.paging, + projects: res.projects, + }; + } else { + const projects = await client.listAllProjects(filter); + return { + projects, + }; + } + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/project/update-project.ts b/packages/pieces/community/clockodo/src/lib/actions/project/update-project.ts new file mode 100644 index 0000000..6aee124 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/project/update-project.ts @@ -0,0 +1,76 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { clockodoCommon, emptyToNull, makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'update_project', + displayName: 'Update Project', + description: 'Updates a project in clockodo', + props: { + project_id: clockodoCommon.project_id(true, false, null), + customer_id: clockodoCommon.customer_id(false), + name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + number: Property.ShortText({ + displayName: 'Number', + required: false, + }), + active: Property.Checkbox({ + displayName: 'Active', + required: false, + }), + billable: Property.Checkbox({ + displayName: 'Billable', + required: false, + }), + budget: Property.Number({ + displayName: 'Budget', + required: false, + }), + budget_is_hours: Property.Checkbox({ + displayName: 'Budget in hours?', + required: false, + }), + budget_is_not_strict: Property.Checkbox({ + displayName: 'Soft Budget', + required: false, + }), + note: Property.LongText({ + displayName: 'Note', + required: false, + }), + completed: Property.Checkbox({ + displayName: 'Completed', + required: false, + }), + billed_amount: Property.Number({ + displayName: 'Billed Amount', + required: false, + }), + billing_complete: Property.Checkbox({ + displayName: 'Billing Complete', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.updateProject(propsValue.project_id as number, { + name: propsValue.name, + customers_id: propsValue.customer_id, + number: emptyToNull(propsValue.number), + active: propsValue.active, + billable_default: propsValue.billable, + note: emptyToNull(propsValue.note), + budget_money: propsValue.budget, + budget_is_hours: propsValue.budget_is_hours, + budget_is_not_strict: propsValue.budget_is_not_strict, + completed: propsValue.completed, + billed_money: propsValue.billed_amount, + billed_completely: propsValue.billing_complete, + }); + return res.project; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/service/create-service.ts b/packages/pieces/community/clockodo/src/lib/actions/service/create-service.ts new file mode 100644 index 0000000..423cd1c --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/service/create-service.ts @@ -0,0 +1,38 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { emptyToNull, makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'create_service', + displayName: 'Create Service', + description: 'Creates a service in clockodo', + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + number: Property.ShortText({ + displayName: 'Number', + required: false, + }), + active: Property.Checkbox({ + displayName: 'Active', + required: false, + }), + note: Property.LongText({ + displayName: 'Note', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.createService({ + name: propsValue.name, + number: emptyToNull(propsValue.number), + active: propsValue.active, + note: emptyToNull(propsValue.note), + }); + return res.service; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/service/delete-service.ts b/packages/pieces/community/clockodo/src/lib/actions/service/delete-service.ts new file mode 100644 index 0000000..15150f3 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/service/delete-service.ts @@ -0,0 +1,17 @@ +import { clockodoCommon, makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; +import { createAction } from '@activepieces/pieces-framework'; + +export default createAction({ + auth: clockodoAuth, + name: 'delete_service', + displayName: 'Delete Service', + description: 'Deletes a service in clockodo', + props: { + service_id: clockodoCommon.service_id(true, false), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + await client.deleteService(propsValue.service_id as number); + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/service/get-service.ts b/packages/pieces/community/clockodo/src/lib/actions/service/get-service.ts new file mode 100644 index 0000000..62e90a0 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/service/get-service.ts @@ -0,0 +1,18 @@ +import { clockodoCommon, makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; +import { createAction } from '@activepieces/pieces-framework'; + +export default createAction({ + auth: clockodoAuth, + name: 'get_service', + displayName: 'Get Service', + description: 'Retrieves a single service from clockodo', + props: { + service_id: clockodoCommon.service_id(true, null), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.getService(propsValue.service_id as number); + return res.service; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/service/index.ts b/packages/pieces/community/clockodo/src/lib/actions/service/index.ts new file mode 100644 index 0000000..c89b6e0 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/service/index.ts @@ -0,0 +1,13 @@ +import createAction from './create-service'; +import getAction from './get-service'; +import updateAction from './update-service'; +import listAction from './list-services'; +import deleteAction from './delete-service'; + +export default [ + createAction, + getAction, + updateAction, + listAction, + deleteAction, +]; diff --git a/packages/pieces/community/clockodo/src/lib/actions/service/list-services.ts b/packages/pieces/community/clockodo/src/lib/actions/service/list-services.ts new file mode 100644 index 0000000..e9e66f4 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/service/list-services.ts @@ -0,0 +1,18 @@ +import { makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; +import { createAction } from '@activepieces/pieces-framework'; + +export default createAction({ + auth: clockodoAuth, + name: 'list_services', + displayName: 'Get Services', + description: 'Fetches services from clockodo', + props: {}, + async run({ auth }) { + const client = makeClient(auth); + const res = await client.listServices(); + return { + services: res.services, + }; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/service/update-service.ts b/packages/pieces/community/clockodo/src/lib/actions/service/update-service.ts new file mode 100644 index 0000000..bbe66a6 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/service/update-service.ts @@ -0,0 +1,39 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { clockodoCommon, emptyToNull, makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'update_service', + displayName: 'Update Service', + description: 'Updates a service in clockodo', + props: { + service_id: clockodoCommon.service_id(true, null), + name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + number: Property.ShortText({ + displayName: 'Number', + required: false, + }), + active: Property.Checkbox({ + displayName: 'Active', + required: false, + }), + note: Property.LongText({ + displayName: 'Note', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.updateService(propsValue.service_id as number, { + name: propsValue.name, + number: emptyToNull(propsValue.number), + active: propsValue.active, + note: emptyToNull(propsValue.note), + }); + return res.service; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/team/get-team.ts b/packages/pieces/community/clockodo/src/lib/actions/team/get-team.ts new file mode 100644 index 0000000..413f0fb --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/team/get-team.ts @@ -0,0 +1,18 @@ +import { clockodoCommon, makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; +import { createAction } from '@activepieces/pieces-framework'; + +export default createAction({ + auth: clockodoAuth, + name: 'get_team', + displayName: 'Get Team', + description: 'Retrieves a single team from clockodo', + props: { + team_id: clockodoCommon.team_id(), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.getTeam(propsValue.team_id as number); + return res.team; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/team/index.ts b/packages/pieces/community/clockodo/src/lib/actions/team/index.ts new file mode 100644 index 0000000..ccb4168 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/team/index.ts @@ -0,0 +1,4 @@ +import getAction from './get-team'; +import listAction from './list-teams'; + +export default [getAction, listAction]; diff --git a/packages/pieces/community/clockodo/src/lib/actions/team/list-teams.ts b/packages/pieces/community/clockodo/src/lib/actions/team/list-teams.ts new file mode 100644 index 0000000..f78d5e3 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/team/list-teams.ts @@ -0,0 +1,18 @@ +import { makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; +import { createAction } from '@activepieces/pieces-framework'; + +export default createAction({ + auth: clockodoAuth, + name: 'list_teams', + displayName: 'Get Teams', + description: 'Fetches teams from clockodo', + props: {}, + async run({ auth }) { + const client = makeClient(auth); + const res = await client.listTeams(); + return { + teams: res.teams, + }; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/user/create-user.ts b/packages/pieces/community/clockodo/src/lib/actions/user/create-user.ts new file mode 100644 index 0000000..54cdd5a --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/user/create-user.ts @@ -0,0 +1,68 @@ +import { clockodoCommon, emptyToNull, makeClient } from '../../common'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'create_user', + displayName: 'Create User', + description: 'Creates a user in clockodo', + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + email: Property.ShortText({ + displayName: 'E-Mail', + required: true, + }), + role: Property.ShortText({ + displayName: 'Role', + required: true, + }), + number: Property.ShortText({ + displayName: 'Number', + required: false, + }), + team_id: clockodoCommon.team_id(false), + language: clockodoCommon.language(false), + wage_type: Property.StaticDropdown({ + displayName: 'Wage Type', + required: false, + options: { + options: [ + { label: 'Salary', value: 1 }, + { label: 'Hourly wage', value: 2 }, + ], + }, + }), + can_generally_see_absences: Property.Checkbox({ + displayName: 'Can see absences', + required: false, + }), + can_generally_manage_absences: Property.Checkbox({ + displayName: 'Can manage absences', + required: false, + }), + can_add_customers: Property.Checkbox({ + displayName: 'Can add customers', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.createUser({ + name: propsValue.name, + email: propsValue.email, + role: propsValue.role, + number: emptyToNull(propsValue.number), + teams_id: propsValue.team_id, + language: propsValue.language, + wage_type: propsValue.wage_type, + can_generally_see_absences: propsValue.can_generally_see_absences, + can_generally_manage_absences: propsValue.can_generally_manage_absences, + can_add_customers: propsValue.can_add_customers, + }); + return res.user; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/user/delete-user.ts b/packages/pieces/community/clockodo/src/lib/actions/user/delete-user.ts new file mode 100644 index 0000000..6d725cf --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/user/delete-user.ts @@ -0,0 +1,17 @@ +import { clockodoCommon, makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; +import { createAction } from '@activepieces/pieces-framework'; + +export default createAction({ + auth: clockodoAuth, + name: 'delete_user', + displayName: 'Delete User', + description: 'Deletes a user in clockodo', + props: { + user_id: clockodoCommon.user_id(true, false), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + await client.deleteUser(propsValue.user_id as number); + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/user/get-user.ts b/packages/pieces/community/clockodo/src/lib/actions/user/get-user.ts new file mode 100644 index 0000000..387e259 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/user/get-user.ts @@ -0,0 +1,18 @@ +import { clockodoCommon, makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; +import { createAction } from '@activepieces/pieces-framework'; + +export default createAction({ + auth: clockodoAuth, + name: 'get_user', + displayName: 'Get User', + description: 'Retrieves a single user from clockodo', + props: { + user_id: clockodoCommon.user_id(true, null), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.getUser(propsValue.user_id as number); + return res.user; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/user/index.ts b/packages/pieces/community/clockodo/src/lib/actions/user/index.ts new file mode 100644 index 0000000..a51d70e --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/user/index.ts @@ -0,0 +1,13 @@ +import getAction from './get-user'; +import listAction from './list-users'; +import createAction from './create-user'; +import updateAction from './update-user'; +import deleteAction from './delete-user'; + +export default [ + getAction, + listAction, + createAction, + updateAction, + deleteAction, +]; diff --git a/packages/pieces/community/clockodo/src/lib/actions/user/list-users.ts b/packages/pieces/community/clockodo/src/lib/actions/user/list-users.ts new file mode 100644 index 0000000..5d1ed7f --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/user/list-users.ts @@ -0,0 +1,18 @@ +import { makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; +import { createAction } from '@activepieces/pieces-framework'; + +export default createAction({ + auth: clockodoAuth, + name: 'list_users', + displayName: 'Get Users', + description: 'Fetches users from clockodo', + props: {}, + async run({ auth }) { + const client = makeClient(auth); + const res = await client.listUsers(); + return { + users: res.users, + }; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/actions/user/update-user.ts b/packages/pieces/community/clockodo/src/lib/actions/user/update-user.ts new file mode 100644 index 0000000..d49e98b --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/actions/user/update-user.ts @@ -0,0 +1,74 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { clockodoCommon, emptyToNull, makeClient } from '../../common'; +import { clockodoAuth } from '../../../'; + +export default createAction({ + auth: clockodoAuth, + name: 'update_user', + displayName: 'Update User', + description: 'Updates a user in clockodo', + props: { + user_id: clockodoCommon.user_id(true, null), + name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + email: Property.ShortText({ + displayName: 'E-Mail', + required: false, + }), + role: Property.ShortText({ + displayName: 'Role', + required: false, + }), + number: Property.ShortText({ + displayName: 'Number', + required: false, + }), + active: Property.Checkbox({ + displayName: 'Active', + required: false, + }), + team_id: clockodoCommon.team_id(false), + language: clockodoCommon.language(false), + wage_type: Property.StaticDropdown({ + displayName: 'Wage Type', + required: false, + options: { + options: [ + { label: 'Salary', value: 1 }, + { label: 'Hourly wage', value: 2 }, + ], + }, + }), + can_generally_see_absences: Property.Checkbox({ + displayName: 'Can see absences', + required: false, + }), + can_generally_manage_absences: Property.Checkbox({ + displayName: 'Can manage absences', + required: false, + }), + can_add_customers: Property.Checkbox({ + displayName: 'Can add customers', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth); + const res = await client.updateUser(propsValue.user_id as number, { + name: propsValue.name, + email: propsValue.email, + role: propsValue.role, + number: emptyToNull(propsValue.number), + active: propsValue.active, + teams_id: propsValue.team_id, + language: propsValue.language, + wage_type: propsValue.wage_type, + can_generally_see_absences: propsValue.can_generally_see_absences, + can_generally_manage_absences: propsValue.can_generally_manage_absences, + can_add_customers: propsValue.can_add_customers, + }); + return res.user; + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/common/client.ts b/packages/pieces/community/clockodo/src/lib/common/client.ts new file mode 100644 index 0000000..49490ea --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/common/client.ts @@ -0,0 +1,423 @@ +import { + HttpMessageBody, + HttpMethod, + QueryParams, + httpClient, +} from '@activepieces/pieces-common'; +import { ListRequest, prepareListRequest } from './models/common'; +import { + UserCreateRequest, + UserListResponse, + UserSingleResponse, + UserUpdateRequest, +} from './models/user'; +import { + CustomerSingleResponse, + CustomerListResponse, + CustomerCreateRequest, + CustomerUpdateRequest, + CustomerListFilter, + Customer, +} from './models/customer'; +import { + ProjectSingleResponse, + ProjectListResponse, + ProjectCreateRequest, + ProjectUpdateRequest, + ProjectListFilter, + Project, +} from './models/project'; +import { + ServiceSingleResponse, + ServiceListResponse, + ServiceCreateRequest, + ServiceUpdateRequest, +} from './models/service'; +import { + EntrySingleResponse, + EntryListResponse, + EntryCreateRequest, + EntryUpdateRequest, + EntryListRequest, + Entry, + EntryListFilter, +} from './models/entry'; +import { + AbsenceSingleResponse, + AbsenceListResponse, + AbsenceCreateRequest, + AbsenceUpdateRequest, + AbsenceListRequest, +} from './models/absence'; +import { + TeamCreateRequest, + TeamListResponse, + TeamSingleResponse, + TeamUpdateRequest, +} from './models/team'; + +export class ClockodoClient { + private clientIdentification: string; + private language = 'en'; + + constructor( + private email: string, + private token: string, + clientName: string, + clientEmail: string + ) { + this.clientIdentification = clientName + ';' + clientEmail; + } + + setLanguage(language: string) { + this.language = language; + } + + async makeRequest( + method: HttpMethod, + url: string, + query?: QueryParams, + body?: object + ): Promise { + const res = await httpClient.sendRequest({ + method, + url: 'https://my.clockodo.com/api' + url, + queryParams: query, + body, + headers: { + 'X-ClockodoApiUser': this.email, + 'X-ClockodoApiKey': this.token, + 'X-Clockodo-External-Application': this.clientIdentification, + 'Accept-Language': this.language, + }, + }); + return res.body; + } + + listUsers(): Promise { + return this.makeRequest(HttpMethod.GET, '/v2/users'); + } + + getUser(id: number): Promise { + return this.makeRequest( + HttpMethod.GET, + '/v2/users/' + id + ); + } + + createUser(request: UserCreateRequest) { + return this.makeRequest( + HttpMethod.POST, + '/v2/users', + undefined, + request + ); + } + + updateUser( + id: number, + request: UserUpdateRequest + ): Promise { + return this.makeRequest( + HttpMethod.PUT, + '/v2/users/' + id, + undefined, + request + ); + } + + deleteUser(id: number): Promise { + return this.makeRequest(HttpMethod.DELETE, '/v2/users/' + id); + } + + listTeams(): Promise { + return this.makeRequest(HttpMethod.GET, '/v2/teams'); + } + + getTeam(id: number): Promise { + return this.makeRequest( + HttpMethod.GET, + '/v2/teams/' + id + ); + } + + createTeam(request: TeamCreateRequest) { + return this.makeRequest( + HttpMethod.POST, + '/v2/teams', + undefined, + request + ); + } + + updateTeam( + id: number, + request: TeamUpdateRequest + ): Promise { + return this.makeRequest( + HttpMethod.PUT, + '/v2/teams/' + id, + undefined, + request + ); + } + + deleteTeam(id: number): Promise { + return this.makeRequest(HttpMethod.DELETE, '/v2/teams/' + id); + } + + listCustomers( + request: ListRequest = {} + ): Promise { + return this.makeRequest( + HttpMethod.GET, + '/v2/customers', + prepareListRequest(request) + ); + } + + async listAllCustomers(filter: CustomerListFilter = {}): Promise { + let totalPages = 999999; + const all: Customer[] = []; + for (let page = 0; page < totalPages; page++) { + const res = await this.listCustomers({ + page: page + 1, + filter, + }); + totalPages = res.paging.count_pages; + res.customers.forEach((e) => all.push(e)); + } + return all; + } + + getCustomer(id: number): Promise { + return this.makeRequest( + HttpMethod.GET, + '/v2/customers/' + id + ); + } + + createCustomer(request: CustomerCreateRequest) { + return this.makeRequest( + HttpMethod.POST, + '/v2/customers', + undefined, + request + ); + } + + updateCustomer( + id: number, + request: CustomerUpdateRequest + ): Promise { + return this.makeRequest( + HttpMethod.PUT, + '/v2/customers/' + id, + undefined, + request + ); + } + + deleteCustomer(id: number): Promise { + return this.makeRequest(HttpMethod.DELETE, '/v2/customers/' + id); + } + + listProjects( + request: ListRequest = {} + ): Promise { + return this.makeRequest( + HttpMethod.GET, + '/v2/projects', + prepareListRequest(request) + ); + } + + async listAllProjects(filter: ProjectListFilter = {}): Promise { + let totalPages = 999999; + const all: Project[] = []; + for (let page = 0; page < totalPages; page++) { + const res = await this.listProjects({ + page: page + 1, + filter, + }); + totalPages = res.paging.count_pages; + res.projects.forEach((e) => all.push(e)); + } + return all; + } + + getProject(id: number): Promise { + return this.makeRequest( + HttpMethod.GET, + '/v2/projects/' + id + ); + } + + createProject(request: ProjectCreateRequest) { + return this.makeRequest( + HttpMethod.POST, + '/v2/projects', + undefined, + request + ); + } + + updateProject( + id: number, + request: ProjectUpdateRequest + ): Promise { + return this.makeRequest( + HttpMethod.PUT, + '/v2/projects/' + id, + undefined, + request + ); + } + + deleteProject(id: number): Promise { + return this.makeRequest(HttpMethod.DELETE, '/v2/projects/' + id); + } + + listServices(): Promise { + return this.makeRequest( + HttpMethod.GET, + '/v2/services' + ); + } + + getService(id: number): Promise { + return this.makeRequest( + HttpMethod.GET, + '/v2/services/' + id + ); + } + + createService(request: ServiceCreateRequest) { + return this.makeRequest( + HttpMethod.POST, + '/v2/services', + undefined, + request + ); + } + + updateService( + id: number, + request: ServiceUpdateRequest + ): Promise { + return this.makeRequest( + HttpMethod.PUT, + '/v2/services/' + id, + undefined, + request + ); + } + + deleteService(id: number): Promise { + return this.makeRequest(HttpMethod.DELETE, '/v2/services/' + id); + } + + listEntries(request: EntryListRequest): Promise { + return this.makeRequest( + HttpMethod.GET, + '/v2/entries', + prepareListRequest(request) + ); + } + + async listAllEntries( + time_since: string, + time_until: string, + filter: EntryListFilter = {} + ): Promise { + let totalPages = 999999; + const all: Entry[] = []; + for (let page = 0; page < totalPages; page++) { + const res = await this.listEntries({ + page: page + 1, + time_since, + time_until, + filter, + }); + totalPages = res.paging.count_pages; + res.entries.forEach((e) => all.push(e)); + } + return all; + } + + getEntry(id: number): Promise { + return this.makeRequest( + HttpMethod.GET, + '/v2/entries/' + id + ); + } + + createEntry(request: EntryCreateRequest) { + return this.makeRequest( + HttpMethod.POST, + '/v2/entries', + undefined, + request + ); + } + + updateEntry( + id: number, + request: EntryUpdateRequest + ): Promise { + return this.makeRequest( + HttpMethod.PUT, + '/v2/entries/' + id, + undefined, + request + ); + } + + deleteEntry(id: number): Promise { + return this.makeRequest(HttpMethod.DELETE, '/v2/entries/' + id); + } + + listAbsences(request: AbsenceListRequest): Promise { + const query: QueryParams = { + year: request.year.toString(), + }; + if (request.users_id) query.users_id = request.users_id.toString(); + return this.makeRequest( + HttpMethod.GET, + '/absences', + query + ); + } + + getAbsence(id: number): Promise { + return this.makeRequest( + HttpMethod.GET, + '/absences/' + id + ); + } + + createAbsence(request: AbsenceCreateRequest) { + return this.makeRequest( + HttpMethod.POST, + '/absences', + undefined, + request + ); + } + + updateAbsence( + id: number, + request: AbsenceUpdateRequest + ): Promise { + return this.makeRequest( + HttpMethod.PUT, + '/absences/' + id, + undefined, + request + ); + } + + deleteAbsence(id: number): Promise { + return this.makeRequest(HttpMethod.DELETE, '/absences/' + id); + } +} diff --git a/packages/pieces/community/clockodo/src/lib/common/index.ts b/packages/pieces/community/clockodo/src/lib/common/index.ts new file mode 100644 index 0000000..0d1f5bf --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/common/index.ts @@ -0,0 +1,251 @@ +import { PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { ClockodoClient } from './client'; +import { clockodoAuth } from '../../'; +import { isNil } from '@activepieces/shared'; + +type ClockodoAuthValue = PiecePropValueSchema; + +export function makeClient(auth: ClockodoAuthValue): ClockodoClient { + return new ClockodoClient( + auth.email, + auth.token, + auth.company_name, + auth.company_email + ); +} + +export const clockodoCommon = { + absenceType: (required = true) => + Property.StaticDropdown({ + displayName: 'Type', + required, + options: { + options: [ + { value: 1, label: 'Regular holiday' }, + { value: 2, label: 'Special leaves' }, + { value: 3, label: 'Reduction of overtime' }, + { value: 4, label: 'Sick day' }, + { value: 5, label: 'Sick day of a child' }, + { value: 6, label: 'School / further education' }, + { value: 7, label: 'Maternity protection' }, + { value: 8, label: 'Home office (planned hours are applied)' }, + { value: 9, label: 'Work out of office (planned hours are applied)' }, + { value: 10, label: 'Special leaves (unpaid)' }, + { value: 11, label: 'Sick day (unpaid)' }, + { value: 12, label: 'Sick day of child (unpaid)' }, + { value: 13, label: 'Quarantine' }, + { + value: 14, + label: 'Military / alternative service (only full days)', + }, + { value: 15, label: 'Sick day (sickness benefit)' }, + ], + }, + }), + customer_id: (required = true, active: boolean | null = true) => + Property.Dropdown({ + description: 'The ID of the customer', + displayName: 'Customer', + required, + refreshers: [], + options: async ({ auth }) => { + if (isNil(auth)) { + return { + disabled: true, + placeholder: 'setup authentication first', + options: [], + }; + } + const client = makeClient(auth as ClockodoAuthValue); + const customers = await client.listAllCustomers({ + active: active === null ? undefined : active, + }); + return { + disabled: false, + options: customers.map((customer) => { + return { + label: customer.name, + value: customer.id, + }; + }), + }; + }, + }), + project_id: ( + required = true, + requiresCustomer = true, + active: boolean | null = true + ) => + Property.Dropdown({ + description: 'The ID of the project', + displayName: 'Project', + required, + refreshers: [...(requiresCustomer ? ['customer_id'] : [])], + options: async ({ auth, customer_id }) => { + if (isNil(auth)) { + return { + disabled: true, + placeholder: 'setup authentication first', + options: [], + }; + } + if (requiresCustomer && !customer_id) { + return { + disabled: true, + placeholder: 'select a customer first', + options: [], + }; + } + const client = makeClient(auth as ClockodoAuthValue); + const projects = await client.listAllProjects({ + active: active === null ? undefined : active, + customers_id: requiresCustomer + ? parseInt(customer_id as string) + : undefined, + }); + return { + disabled: false, + options: projects.map((project) => { + return { + label: project.name, + value: project.id, + }; + }), + }; + }, + }), + user_id: (required = true, active: boolean | null = true) => + Property.Dropdown({ + description: 'The ID of the user', + displayName: 'User', + required, + refreshers: [], + options: async ({ auth }) => { + if (isNil(auth)) { + return { + disabled: true, + placeholder: 'setup authentication first', + options: [], + }; + } + const client = makeClient(auth as ClockodoAuthValue); + const usersRes = await client.listUsers(); + return { + disabled: false, + options: usersRes.users + .filter((u) => active === null || u.active === active) + .map((user) => { + return { + label: user.name, + value: user.id, + }; + }), + }; + }, + }), + team_id: (required = true) => + Property.Dropdown({ + description: 'The ID of the team', + displayName: 'Team', + required, + refreshers: [], + options: async ({ auth }) => { + if (isNil(auth)) { + return { + disabled: true, + placeholder: 'setup authentication first', + options: [], + }; + } + const client = makeClient(auth as ClockodoAuthValue); + const teamsRes = await client.listTeams(); + return { + disabled: false, + options: teamsRes.teams.map((team) => { + return { + label: team.name, + value: team.id, + }; + }), + }; + }, + }), + service_id: (required = true, active: boolean | null = true) => + Property.Dropdown({ + description: 'The ID of the service', + displayName: 'Service', + required, + refreshers: [], + options: async ({ auth }) => { + if (isNil(auth)) { + return { + disabled: true, + placeholder: 'setup authentication first', + options: [], + }; + } + const client = makeClient(auth as ClockodoAuthValue); + const servicesRes = await client.listServices(); + return { + disabled: false, + options: servicesRes.services + .filter((s) => active === null || s.active === active) + .map((service) => { + return { + label: service.name, + value: service.id, + }; + }), + }; + }, + }), + language: (required = true) => + Property.StaticDropdown({ + displayName: 'Language', + required, + options: { + options: [ + { label: 'German', value: 'de' }, + { label: 'English', value: 'en' }, + { label: 'French', value: 'fr' }, + ], + }, + }), + color: (required = true) => + Property.StaticDropdown({ + displayName: 'Color', + required, + options: { + options: [ + { label: 'Orange', value: 0xee9163 }, + { label: 'Yellow', value: 0xf0d758 }, + { label: 'Green', value: 0x9de34a }, + { label: 'Caribean', value: 0x39e6ca }, + { label: 'Lightblue', value: 0x56c6f9 }, + { label: 'Blue', value: 0x3657f7 }, + { label: 'Purple', value: 0x7b4be7 }, + { label: 'Magenta', value: 0xd065e6 }, + { label: 'Pink', value: 0xfc71d1 }, + ], + }, + }), +}; + +export function emptyToNull(val?: string): undefined | string | null { + return val === undefined ? val : val || null; +} + +export function currentYear(): number { + const todaysDate = new Date(); + return todaysDate.getFullYear(); +} + +export function reformatDateTime(s?: string): string | undefined { + if (!s) return undefined; + return s.replace(/\.[0-9]{3}/, ''); +} + +export function reformatDate(s?: string): string | undefined { + if (!s) return undefined; + return s.split('T', 2)[0]; +} diff --git a/packages/pieces/community/clockodo/src/lib/common/models/absence.ts b/packages/pieces/community/clockodo/src/lib/common/models/absence.ts new file mode 100644 index 0000000..21b8436 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/common/models/absence.ts @@ -0,0 +1,76 @@ +export enum AbsenceStatus { + REQUESTED = 0, + APPROVED = 1, + DECLINED = 2, + APPROVAL_CANCELLED = 3, + REQUEST_CANCELLED = 4, +} + +export enum AbsenceType { + REGULAR_HOLIDAY = 1, + SPECIAL_LEAVE = 2, + REDUCTION_OF_OVERTIME = 3, + SICK_DAY = 4, + SICK_DAY_OF_CHILD = 5, + EDUCATION = 6, + MATERNITY_PROTECTION = 7, + HOME_OFFICE = 8, + WORK_OUT_OF_OFFICE = 9, + UNPAID_SPECIAL_LEAVE = 10, + UNPAID_SICK_DAY = 11, + UNPAID_SICK_DAY_OF_CHILD = 12, + QUARANTINE = 13, + MILITARY_SERVICE = 14, + SICK_DAY_BENEFIT = 15, +} + +export interface Absence { + id: number; + users_id: number; + date_since: string; + date_until?: string; + status: AbsenceStatus; + type: AbsenceType; + note?: string; + count_days?: number; + count_hours?: number; + sick_note?: boolean; + date_enquired?: string; + date_approved?: string; + approved_by?: number; +} + +export interface AbsenceCreateRequest { + type: AbsenceType; + date_since: string; + date_until: string | null; + note?: string | null; + users_id?: number; + status?: AbsenceStatus; + sick_note?: boolean; + count_days?: number; +} + +export interface AbsenceUpdateRequest { + type?: AbsenceType; + date_since?: string; + date_until?: string | null; + note?: string | null; + users_id?: number; + status?: AbsenceStatus; + sick_note?: boolean; + count_days?: number; +} + +export interface AbsenceListRequest { + year: number; + users_id?: number; +} + +export interface AbsenceListResponse { + absences: Absence[]; +} + +export interface AbsenceSingleResponse { + absence: Absence; +} diff --git a/packages/pieces/community/clockodo/src/lib/common/models/common.ts b/packages/pieces/community/clockodo/src/lib/common/models/common.ts new file mode 100644 index 0000000..4fb5c04 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/common/models/common.ts @@ -0,0 +1,45 @@ +import { QueryParams } from '@activepieces/pieces-common'; + +export interface Paging { + items_per_page: number; + current_page: number; + count_pages: number; + count_items: number; +} + +export interface ListRequest { + page?: number; + filter?: T; +} + +function emptyValueFilter( + accessor: (key: string) => any +): (key: string) => boolean { + return (key: string) => { + const val = accessor(key); + return ( + val !== null && + val !== undefined && + (typeof val != 'string' || val.length > 0) + ); + }; +} + +export function prepareListRequest(request: ListRequest): QueryParams { + const params: QueryParams = {}; + const requestObj = request as Record; + Object.keys(request) + .filter((k) => k != 'filter') + .filter(emptyValueFilter((k) => requestObj[k])) + .forEach((k: string) => { + params[k] = (request as Record)[k].toString(); + }); + if (request.filter) { + Object.keys(request.filter) + .filter(emptyValueFilter((k) => request.filter[k])) + .forEach((k) => { + params['filter[' + k + ']'] = request.filter[k].toString(); + }); + } + return params; +} diff --git a/packages/pieces/community/clockodo/src/lib/common/models/customer.ts b/packages/pieces/community/clockodo/src/lib/common/models/customer.ts new file mode 100644 index 0000000..cc10829 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/common/models/customer.ts @@ -0,0 +1,42 @@ +import { Paging } from './common'; + +export interface Customer { + id: number; + name: string; + number?: string; + active: boolean; + billable_default: boolean; + note?: string; + color: number; +} + +export interface CustomerCreateRequest { + name: string; + number?: string | null; + active?: boolean; + billable_default?: boolean; + note?: string | null; + color?: number; +} + +export interface CustomerUpdateRequest { + name?: string; + number?: string | null; + active?: boolean; + billable_default?: boolean; + note?: string | null; + color?: number; +} + +export interface CustomerListFilter { + active?: boolean; +} + +export interface CustomerListResponse { + paging: Paging; + customers: Customer[]; +} + +export interface CustomerSingleResponse { + customer: Customer; +} diff --git a/packages/pieces/community/clockodo/src/lib/common/models/entry.ts b/packages/pieces/community/clockodo/src/lib/common/models/entry.ts new file mode 100644 index 0000000..bc5402f --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/common/models/entry.ts @@ -0,0 +1,136 @@ +import { Paging, ListRequest } from './common'; + +export enum EntryType { + TIME_RECORD = 1, + LUMP_SUM = 2, + LUMP_SERVICE = 3, +} + +export enum BillableType { + NOT_BILLABLE = 0, + BILLABLE = 1, + BILLED = 2, +} + +export interface Entry { + id: number; + type: EntryType; + customers_id: number; + projects_id?: number; + users_id: number; + billable: BillableType; + texts_id?: string; + time_since: string; + time_until?: string; + time_insert: string; + time_last_change: string; + customers_name?: string; + projects_name?: string; + users_name?: string; + text?: string; + revenue?: number; +} + +export interface TimeRecordEntry extends Entry { + services_id: number; + duration?: number; + offset: number; + clocked: boolean; + clocked_offline: boolean; + time_clocked_since?: string; + time_last_change_worktime: string; + hourly_rate: number; + service_name?: string; +} + +export interface LumpSumEntry extends Entry { + services_id: number; + lumpsum: number; + service_name?: string; +} + +export interface LumpServiceEntry extends Entry { + lumpsum_services_id: number; + lumpsums_amount: number; + lumpsum_services_price?: number; +} + +export interface EntryCreateRequest { + customers_id: number; +} + +export interface TimeRecordEntryCreateRequest extends EntryCreateRequest { + services_id: number; + billable: BillableType; + time_since: string; + time_until: string | null; + users_id?: number; + duration?: number; + hourly_rate?: number; + projects_id?: number; + text?: string; +} + +export interface LumpSumEntryCreateRequest extends EntryCreateRequest { + services_id: number; + lumpsum: number; + billable: BillableType; + time_since: string; + users_id?: number; + projects_id?: number; + text?: string; +} + +export interface LumpServiceEntryCreateRequest extends EntryCreateRequest { + lumpsum_services_id: number; + lumpsum_services_amount: number; + billable: BillableType; + time_since: string; + users_id?: number; + projects_id?: number; + text?: string; +} + +export interface EntryUpdateRequest { + customers_id?: number; + projects_id?: number; + services_id?: number; + lumpsum_services_id?: number; + users_id?: number; + billable?: BillableType; + text?: string; + duration?: number; + lumpsum?: number; + lumpsum_services_amount?: number; + hourly_rate?: number; + time_since?: string; + time_until?: string; +} + +export interface EntryListFilter { + users_id?: number; + customers_id?: number; + projects_id?: number; + services_id?: number; + lumpsum_services_id?: number; + billable?: BillableType; + text?: string; + texts_id?: number; + budget_type?: string; +} + +export interface EntryListRequest extends ListRequest { + time_since: string; + time_until: string; + enhanced_list?: boolean; + calc_also_revenues_for_projects_with_hard_budget?: boolean; +} + +export interface EntryListResponse { + paging: Paging; + entries: Entry[]; +} + +export interface EntrySingleResponse { + entry: Entry; +} diff --git a/packages/pieces/community/clockodo/src/lib/common/models/project.ts b/packages/pieces/community/clockodo/src/lib/common/models/project.ts new file mode 100644 index 0000000..d4c8ebb --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/common/models/project.ts @@ -0,0 +1,60 @@ +import { Paging } from './common'; + +export interface Project { + id: number; + customers_id: number; + name: string; + number?: string; + active: boolean; + billable_default: boolean; + note?: string; + budget_money?: number; + budget_is_hours: boolean; + budget_is_not_strict: boolean; + completed: boolean; + billed_money?: number; + billed_completely?: boolean; + revenue_factor?: number; +} + +export interface ProjectCreateRequest { + name: string; + customers_id: number; + number?: string | null; + active?: boolean; + billable_default?: boolean; + note?: string | null; + budget_money?: number | null; + budget_is_hours?: boolean; + budget_is_not_strict?: boolean; +} + +export interface ProjectUpdateRequest { + name?: string; + customers_id?: number; + number?: string | null; + active?: boolean; + billable_default?: boolean; + note?: string | null; + budget_money?: number | null; + budget_is_hours?: boolean; + budget_is_not_strict?: boolean; + hourly_rate?: number | null; + completed?: boolean; + billed_money?: number | null; + billed_completely?: boolean; +} + +export interface ProjectListFilter { + active?: boolean; + customers_id?: number; +} + +export interface ProjectListResponse { + paging: Paging; + projects: Project[]; +} + +export interface ProjectSingleResponse { + project: Project; +} diff --git a/packages/pieces/community/clockodo/src/lib/common/models/service.ts b/packages/pieces/community/clockodo/src/lib/common/models/service.ts new file mode 100644 index 0000000..6ae2a1a --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/common/models/service.ts @@ -0,0 +1,29 @@ +export interface Service { + id: number; + name: string; + number?: string; + active: boolean; + note?: string; +} + +export interface ServiceCreateRequest { + name: string; + number?: string | null; + active?: boolean; + note?: string | null; +} + +export interface ServiceUpdateRequest { + name?: string; + number?: string | null; + active?: boolean; + note?: string | null; +} + +export interface ServiceListResponse { + services: Service[]; +} + +export interface ServiceSingleResponse { + service: Service; +} diff --git a/packages/pieces/community/clockodo/src/lib/common/models/team.ts b/packages/pieces/community/clockodo/src/lib/common/models/team.ts new file mode 100644 index 0000000..11a6ab5 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/common/models/team.ts @@ -0,0 +1,20 @@ +export interface Team { + id: number; + name: string; +} + +export interface TeamCreateRequest { + name: string; +} + +export interface TeamUpdateRequest { + name: string; +} + +export interface TeamListResponse { + teams: Team[]; +} + +export interface TeamSingleResponse { + team: Team; +} diff --git a/packages/pieces/community/clockodo/src/lib/common/models/user.ts b/packages/pieces/community/clockodo/src/lib/common/models/user.ts new file mode 100644 index 0000000..ca748a7 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/common/models/user.ts @@ -0,0 +1,75 @@ +export interface User { + id: number; + name: string; + number?: string; + email: string; + role: string; + active: boolean; + timeformat_12h: boolean; + weekstart_monday: boolean; + weekend_friday: boolean; + language: string; + timezone: string; + wage_type?: number; + can_generally_see_absences: boolean; + can_generally_manage_absences: boolean; + can_add_customers: boolean; + edit_lock?: string; + edit_lock_dyn?: string; + edit_lock_sync?: boolean; + worktime_regulation_id?: number; + teams_id?: number; + nonbusinessgroups_id?: number; +} + +export interface UserCreateRequest { + name: string; + number?: string | null; + email: string; + role: string; + timeformat_12h?: boolean; + weekstart_monday?: boolean; + weekend_friday?: boolean; + language?: string; + timezone?: string; + wage_type?: number; + can_generally_see_absences?: boolean; + can_generally_manage_absences?: boolean; + can_add_customers?: boolean; + edit_lock_sync?: boolean; + worktime_regulation_id?: number; + teams_id?: number; + nonbusinessgroups_id?: number; + mail_to_user?: boolean; +} + +export interface UserUpdateRequest { + name?: string; + number?: string | null; + email?: string; + role?: string; + active?: boolean; + timeformat_12h?: boolean; + weekstart_monday?: boolean; + weekend_friday?: boolean; + language?: string; + timezone?: string; + wage_type?: number; + can_generally_see_absences?: boolean; + can_generally_manage_absences?: boolean; + can_add_customers?: boolean; + edit_lock?: string; + edit_lock_dyn?: string; + edit_lock_sync?: boolean; + worktime_regulation_id?: number; + teams_id?: number; + nonbusinessgroups_id?: number; +} + +export interface UserListResponse { + users: User[]; +} + +export interface UserSingleResponse { + user: User; +} diff --git a/packages/pieces/community/clockodo/src/lib/triggers/index.ts b/packages/pieces/community/clockodo/src/lib/triggers/index.ts new file mode 100644 index 0000000..4bf7564 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/triggers/index.ts @@ -0,0 +1,4 @@ +import newEntryTrigger from './new-entry'; +import newAbsenceEnquiryTrigger from './new-absence-enquiry'; + +export default [newEntryTrigger, newAbsenceEnquiryTrigger]; diff --git a/packages/pieces/community/clockodo/src/lib/triggers/new-absence-enquiry.ts b/packages/pieces/community/clockodo/src/lib/triggers/new-absence-enquiry.ts new file mode 100644 index 0000000..b6d476a --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/triggers/new-absence-enquiry.ts @@ -0,0 +1,75 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { currentYear } from '../common'; +import { ClockodoClient } from '../common/client'; +import { clockodoAuth } from '../../'; + +interface AuthData { + email: string; + token: string; + company_name: string; + company_email: string; +} + +const polling: Polling = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ auth }) => { + const client = new ClockodoClient( + auth.email, + auth.token, + auth.company_name, + auth.company_email + ); + const res = await client.listAbsences({ year: currentYear() }); + return res.absences + .sort((a, b) => b.id - a.id) + .map((a) => ({ + id: a.id, + data: a, + })); + }, +}; + +export default createTrigger({ + auth: clockodoAuth, + name: 'new_absence_enquiry', + displayName: 'New Absence Enquiry', + description: 'Triggers when a new absence enquiry is created', + type: TriggerStrategy.POLLING, + props: {}, + sampleData: {}, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, + test: async (context) => { + return await pollingHelper.test(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, +}); diff --git a/packages/pieces/community/clockodo/src/lib/triggers/new-entry.ts b/packages/pieces/community/clockodo/src/lib/triggers/new-entry.ts new file mode 100644 index 0000000..aea0964 --- /dev/null +++ b/packages/pieces/community/clockodo/src/lib/triggers/new-entry.ts @@ -0,0 +1,84 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { currentYear } from '../common'; +import { ClockodoClient } from '../common/client'; +import { clockodoAuth } from '../../'; + +interface AuthData { + email: string; + token: string; + company_name: string; + company_email: string; +} + +const polling: Polling = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ auth }) => { + const client = new ClockodoClient( + auth.email, + auth.token, + auth.company_name, + auth.company_email + ); + const time_since = currentYear() - 1 + '-01-01T00:00:00Z'; + const time_until = currentYear() + 1 + '-12-31T23:59:59Z'; + let res = await client.listEntries({ time_since, time_until }); + if (res.paging.count_pages > 1) { + res = await client.listEntries({ + time_since, + time_until, + page: res.paging.count_pages, + }); + } + return res.entries + .sort((a, b) => b.id - a.id) + .map((a) => ({ + id: a.id, + data: a, + })); + }, +}; + +export default createTrigger({ + auth: clockodoAuth, + name: 'new_entry', + displayName: 'New Entry', + description: 'Triggers when a new time entry is created', + type: TriggerStrategy.POLLING, + props: {}, + sampleData: {}, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, + test: async (context) => { + return await pollingHelper.test(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, +}); diff --git a/packages/pieces/community/clockodo/tsconfig.json b/packages/pieces/community/clockodo/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/clockodo/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/clockodo/tsconfig.lib.json b/packages/pieces/community/clockodo/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/clockodo/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/close/.eslintrc.json b/packages/pieces/community/close/.eslintrc.json new file mode 100644 index 0000000..596e4de --- /dev/null +++ b/packages/pieces/community/close/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] + } \ No newline at end of file diff --git a/packages/pieces/community/close/README.md b/packages/pieces/community/close/README.md new file mode 100644 index 0000000..547b6f1 --- /dev/null +++ b/packages/pieces/community/close/README.md @@ -0,0 +1,7 @@ +# pieces-close + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-close` to build the library. \ No newline at end of file diff --git a/packages/pieces/community/close/package.json b/packages/pieces/community/close/package.json new file mode 100644 index 0000000..a230c38 --- /dev/null +++ b/packages/pieces/community/close/package.json @@ -0,0 +1,6 @@ +{ + "name": "@activepieces/piece-close", + "version": "0.0.1" + } + + \ No newline at end of file diff --git a/packages/pieces/community/close/project.json b/packages/pieces/community/close/project.json new file mode 100644 index 0000000..9045f0a --- /dev/null +++ b/packages/pieces/community/close/project.json @@ -0,0 +1,44 @@ +{ + "name": "pieces-close", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/close/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/close", + "tsConfig": "packages/pieces/community/close/tsconfig.lib.json", + "packageJson": "packages/pieces/community/close/package.json", + "main": "packages/pieces/community/close/src/index.ts", + "assets": ["packages/pieces/community/close/*.md"], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } + } + \ No newline at end of file diff --git a/packages/pieces/community/close/src/index.ts b/packages/pieces/community/close/src/index.ts new file mode 100644 index 0000000..73fed03 --- /dev/null +++ b/packages/pieces/community/close/src/index.ts @@ -0,0 +1,62 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { + createCustomApiCallAction, + HttpMethod, +} from '@activepieces/pieces-common'; +import { createLead } from './lib/actions/create-lead'; +import { findLead } from './lib/actions/find-lead'; +import { newLeadAdded } from './lib/triggers/new-lead-added'; +import { createOpportunity } from './lib/actions/create-opportunity'; +import { createContact } from './lib/actions/create-contact'; +import { newContactAdded } from './lib/triggers/new-contact-added'; +import { findContact } from './lib/actions/find-contact'; +import { CLOSE_API_URL, closeApiCall } from './lib/common/client'; +import { newOpportunityAdded } from './lib/triggers/new-opportunity'; + +export const closeAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: 'Your Close CRM API key for authentication.', + required: true, + validate: async ({ auth }) => { + try { + await closeApiCall({ + accessToken: auth as string, + method: HttpMethod.GET, + resourceUri: '/me/', + }); + + return { valid: true }; + } catch { + return { + valid: false, + error: 'Invalid API key.', + }; + } + }, +}); + +export const close = createPiece({ + displayName: 'Close', + description: 'Sales automation and CRM integration for Close', + auth: closeAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/close.png', + authors: ['Ani-4x', 'kishanprmr'], + actions: [ + createLead, + createContact, + findLead, + createOpportunity, + findContact, + createCustomApiCallAction({ + baseUrl: () => CLOSE_API_URL, + auth: closeAuth, + authMapping: async (auth) => { + return { + Authorization: `Basic ${Buffer.from(`${auth}:`).toString('base64')}`, + }; + }, + }), + ], + triggers: [newLeadAdded, newContactAdded, newOpportunityAdded], +}); diff --git a/packages/pieces/community/close/src/lib/actions/create-contact.ts b/packages/pieces/community/close/src/lib/actions/create-contact.ts new file mode 100644 index 0000000..d9f1cc4 --- /dev/null +++ b/packages/pieces/community/close/src/lib/actions/create-contact.ts @@ -0,0 +1,140 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { closeAuth } from '../../'; +import { CloseCRMContact } from '../common/types'; +import { customFields, leadId } from '../common/props'; +import { closeApiCall } from '../common/client'; + +export const createContact = createAction({ + auth: closeAuth, + name: 'create_contact', + displayName: 'Create Contact', + description: 'Creates a new contact.', + props: { + lead_id: leadId(), + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + title: Property.ShortText({ + displayName: 'Title', + required: false, + }), + officePhone: Property.ShortText({ + displayName: 'Office Phone', + required: false, + }), + mobilePhone: Property.ShortText({ + displayName: 'Mobile Phone', + required: false, + }), + homePhone: Property.ShortText({ + displayName: 'Home Phone', + required: false, + }), + directPhone: Property.ShortText({ + displayName: 'Direct Phone', + required: false, + }), + faxPhone: Property.ShortText({ + displayName: 'Fax Phone', + required: false, + }), + otherPhone: Property.ShortText({ + displayName: 'Other Phone', + required: false, + }), + officeEmail: Property.ShortText({ + displayName: 'Office Email', + required: false, + }), + homeEmail: Property.ShortText({ + displayName: 'Home Email', + required: false, + }), + directEmail: Property.ShortText({ + displayName: 'Direct Email', + required: false, + }), + otherEmail: Property.ShortText({ + displayName: 'Other Email', + required: false, + }), + url: Property.ShortText({ + displayName: 'URL', + required: false, + }), + customFields: customFields('contact'), + }, + async run(context) { + const { + lead_id, + name, + title, + officeEmail, + officePhone, + otherEmail, + otherPhone, + mobilePhone, + homeEmail, + url, + homePhone, + directEmail, + directPhone, + faxPhone, + } = context.propsValue; + + const customFields = context.propsValue.customFields ?? {}; + + const transformedCustomFields = Object.fromEntries( + Object.entries(customFields) + .filter(([, v]) => v !== '' && v != null && !(Array.isArray(v) && v.length === 0)) + .map(([key, value]) => [`custom.${key}`, value]), + ); + + const payload: Partial = { + lead_id: lead_id, + title: title, + name: name, + ...transformedCustomFields, + phones: [], + emails: [], + urls: [], + }; + + // Add emails if present + if (officeEmail) payload.emails?.push({ email: officeEmail.trim(), type: 'office' }); + if (otherEmail) payload.emails?.push({ email: otherEmail.trim(), type: 'other' }); + if (homeEmail) payload.emails?.push({ email: homeEmail.trim(), type: 'home' }); + if (directEmail) payload.emails?.push({ email: directEmail.trim(), type: 'direct' }); + + // Add phones if present + if (officePhone) payload.phones?.push({ phone: officePhone.trim(), type: 'office' }); + if (otherPhone) payload.phones?.push({ phone: otherPhone.trim(), type: 'other' }); + if (mobilePhone) payload.phones?.push({ phone: mobilePhone.trim(), type: 'mobile' }); + if (homePhone) payload.phones?.push({ phone: homePhone.trim(), type: 'home' }); + if (directPhone) payload.phones?.push({ phone: directPhone.trim(), type: 'direct' }); + if (faxPhone) payload.phones?.push({ phone: faxPhone.trim(), type: 'fax' }); + + if (url) payload.urls?.push({ url, type: 'url' }); + + try { + const response = await closeApiCall({ + accessToken: context.auth, + method: HttpMethod.POST, + resourceUri: '/contact/', + body: payload, + }); + + return response; + } catch (error: any) { + if (error.response?.status === 400) { + throw new Error(`Bad request: ${JSON.stringify(error.response.body)}`); + } + if (error.response?.status === 404) { + throw new Error(`Lead not found with ID: ${lead_id}`); + } + throw new Error(`Error creating contact: ${error.message}`); + } + }, +}); diff --git a/packages/pieces/community/close/src/lib/actions/create-lead.ts b/packages/pieces/community/close/src/lib/actions/create-lead.ts new file mode 100644 index 0000000..eaadcaf --- /dev/null +++ b/packages/pieces/community/close/src/lib/actions/create-lead.ts @@ -0,0 +1,177 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { closeAuth } from './../../index'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { closeApiCall } from '../common/client'; +import { customFields, statusId } from '../common/props'; + +export const createLead = createAction({ + auth: closeAuth, + name: 'create_lead', + displayName: 'Create Lead', + description: 'Creates a new lead.', + props: { + name: Property.ShortText({ + displayName: 'Lead Name', + description: 'The name of the lead/company.', + required: true, + }), + url: Property.ShortText({ + displayName: 'URL', + required: false, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + contacts: Property.Array({ + displayName: 'Contacts', + description: 'Array of contact details for this lead', + required: false, + properties: { + name: Property.ShortText({ + displayName: 'Contact Name', + required: true, + }), + title: Property.ShortText({ + displayName: 'Contact Title', + required: false, + }), + officePhone: Property.ShortText({ + displayName: 'Contact Office Phone', + required: false, + }), + mobilePhone: Property.ShortText({ + displayName: 'Contact Mobile Phone', + required: false, + }), + homePhone: Property.ShortText({ + displayName: 'Contact Home Phone', + required: false, + }), + directPhone: Property.ShortText({ + displayName: 'Contact Direct Phone', + required: false, + }), + faxPhone: Property.ShortText({ + displayName: 'Contact Fax Phone', + required: false, + }), + otherPhone: Property.ShortText({ + displayName: 'Contact Other Phone', + required: false, + }), + officeEmail: Property.ShortText({ + displayName: 'Contact Office Email', + required: false, + }), + homeEmail: Property.ShortText({ + displayName: 'Contact Home Email', + required: false, + }), + directEmail: Property.ShortText({ + displayName: 'Contact Direct Email', + required: false, + }), + otherEmail: Property.ShortText({ + displayName: 'Contact Other Email', + required: false, + }), + url: Property.ShortText({ + displayName: 'Contact URL', + required: false, + }), + }, + }), + statusId: statusId('lead', false), + customFields: customFields('lead'), + }, + async run(context) { + const { name, url, description, statusId } = context.propsValue; + const contacts = (context.propsValue.contacts as ContactInfo[]) ?? []; + const customFields = context.propsValue.customFields ?? {}; + + const transformedCustomFields = Object.fromEntries( + Object.entries(customFields) + .filter(([, v]) => v !== '' && v != null && !(Array.isArray(v) && v.length === 0)) + .map(([key, value]) => [`custom.${key}`, value]), + ); + + const transformedContacts = contacts.map((contact) => { + const phoneTypes = [ + 'officePhone', + 'mobilePhone', + 'homePhone', + 'directPhone', + 'faxPhone', + 'otherPhone', + ] as const; + + const emailTypes = ['officeEmail', 'homeEmail', 'directEmail', 'otherEmail'] as const; + + const phones = phoneTypes + .filter((key) => contact[key]?.trim()) + .map((key) => ({ + type: key.replace('Phone', ''), + phone: contact[key]!.trim(), + })); + + const emails = emailTypes + .filter((key) => contact[key]?.trim()) + .map((key) => ({ + type: key.replace('Email', ''), + email: contact[key]!.trim(), + })); + + return { + name: contact.name?.trim(), + title: contact.title?.trim(), + phones: phones.length > 0 ? phones : undefined, + emails: emails.length > 0 ? emails : undefined, + urls: contact.url + ? [ + { + type: 'url', + url: contact.url, + }, + ] + : [], + }; + }); + + const payload: Record = { + name, + url, + description, + status_id: statusId, + contacts: transformedContacts, + ...transformedCustomFields, + }; + + const response = await closeApiCall({ + accessToken: context.auth, + method: HttpMethod.POST, + resourceUri: '/lead/', + body: payload, + }); + + return response; + }, +}); + +type ContactInfo = { + name?: string; + title?: string; + officePhone?: string; + mobilePhone?: string; + homePhone?: string; + directPhone?: string; + faxPhone?: string; + otherPhone?: string; + officeEmail?: string; + mobileEmail?: string; + homeEmail?: string; + directEmail?: string; + faxEmail?: string; + otherEmail?: string; + url?: string; +}; diff --git a/packages/pieces/community/close/src/lib/actions/create-opportunity.ts b/packages/pieces/community/close/src/lib/actions/create-opportunity.ts new file mode 100644 index 0000000..7b5413d --- /dev/null +++ b/packages/pieces/community/close/src/lib/actions/create-opportunity.ts @@ -0,0 +1,99 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { closeAuth } from '../../'; +import { customFields, leadId, statusId, userId } from '../common/props'; +import { closeApiCall } from '../common/client'; + +export const createOpportunity = createAction({ + auth: closeAuth, + name: 'create_opportunity', + displayName: 'Create Opportunity', + description: 'Create a new opportunity.', + props: { + lead_id: leadId(), + status_id: statusId('opportunity', true), + name: Property.ShortText({ + displayName: 'Opportunity Name', + description: 'A descriptive name for the opportunity.', + required: false, + }), + note: Property.LongText({ + displayName: 'Notes', + description: 'Additional details about the opportunity.', + required: false, + }), + confidence: Property.Number({ + displayName: 'Confidence %', + description: 'The probability of winning this opportunity (0-100).', + required: false, + }), + value: Property.Number({ + displayName: 'Value', + required: false, + }), + value_period: Property.StaticDropdown({ + displayName: 'Value Period', + description: 'The period for the opportunity value.', + required: false, + options: { + options: [ + { label: 'One-Time', value: 'one_time' }, + { label: 'Monthly', value: 'monthly' }, + { label: 'Annual', value: 'annual' }, + ], + }, + defaultValue: 'one_time', + }), + contact_id: Property.ShortText({ + displayName: 'Contact ID', + description: 'The ID of the contact associated with this opportunity.', + required: false, + }), + user_id: userId(), + custom_fields: customFields('opportunity'), + }, + async run(context) { + const { lead_id, name, note, status_id, confidence, value, value_period, contact_id, user_id } = + context.propsValue; + + const customFields = context.propsValue.custom_fields ?? {}; + + const transformedCustomFields = Object.fromEntries( + Object.entries(customFields) + .filter(([, v]) => v !== '' && v != null && !(Array.isArray(v) && v.length === 0)) + .map(([key, value]) => [`custom.${key}`, value]), + ); + + const opportunityData = { + lead_id, + status_id, + name, + note, + confidence, + value, + value_period, + contact_id, + user_id, + ...transformedCustomFields, + }; + + try { + const response = await closeApiCall({ + accessToken: context.auth, + method: HttpMethod.POST, + resourceUri: '/opportunity/', + body: opportunityData, + }); + + return response; + } catch (error: any) { + if (error.response?.status === 400) { + throw new Error(`Invalid request: ${JSON.stringify(error.response.body)}`); + } + if (error.response?.status === 404) { + throw new Error(`Lead or related resource not found`); + } + throw new Error(`Failed to create opportunity: ${error.message}`); + } + }, +}); diff --git a/packages/pieces/community/close/src/lib/actions/find-contact.ts b/packages/pieces/community/close/src/lib/actions/find-contact.ts new file mode 100644 index 0000000..faf77a5 --- /dev/null +++ b/packages/pieces/community/close/src/lib/actions/find-contact.ts @@ -0,0 +1,202 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { closeAuth } from '../../'; +import { CloseCRMSearchQuery } from '../common/types'; +import { closeApiCall } from '../common/client'; + +export const findContact = createAction({ + auth: closeAuth, + name: 'find_contact', + displayName: 'Find Contact', + description: 'Search for contacts by name, email, or other criteria with advanced filtering', + props: { + search_type: Property.StaticDropdown({ + displayName: 'Search Type', + required: true, + options: { + options: [ + { label: 'By Name', value: 'name' }, + { label: 'By Email', value: 'email' }, + { label: 'By Phone', value: 'phone' }, + { label: 'By Lead ID', value: 'lead_id' }, + ], + }, + }), + search_query: Property.ShortText({ + displayName: 'Search Query', + required: true, + }), + match_type: Property.StaticDropdown({ + displayName: 'Match Type', + required: false, + options: { + options: [ + { label: 'Contains', value: 'contains' }, + { label: 'Exact Match', value: 'exact' }, + { label: 'Starts With', value: 'starts' }, + { label: 'Ends With', value: 'ends' }, + ], + }, + defaultValue: 'contains', + }), + }, + async run(context) { + const { search_type, search_query, match_type } = context.propsValue; + + try { + // Build the search query + const searchQuery = buildSearchQuery({ + search_type, + search_query, + match_type: match_type || 'contains', + include_fields: ['title', 'id', 'name', 'emails'], + }); + + let cursor: string | undefined; + + const result = []; + + do { + const response = await closeApiCall<{ + cursor?: string; + data: Record[]; + }>({ + accessToken: context.auth, + method: HttpMethod.POST, + resourceUri: '/data/search/', + body: { + _limit: 100, + cursor, + ...searchQuery, + }, + }); + + const { data } = response; + if (!data || data.length === 0) break; + + result.push(...data); + cursor = response.cursor; + } while (cursor); + + return { + found: result.length > 0, + result, + }; + } catch (error: any) { + if (error.response?.status === 400) { + throw new Error(`Invalid search query: ${error.response.body?.error || 'Unknown error'}`); + } + if (error.response?.status === 401) { + throw new Error('Authentication failed. Please check your API key.'); + } + throw new Error(`Failed to search contacts: ${error.message}`); + } + }, +}); + +// Helper function to build the search query +function buildSearchQuery(params: { + search_type: string; + search_query: string; + match_type: string; + include_fields: string[]; +}): CloseCRMSearchQuery { + const { search_type, search_query, match_type, include_fields } = params; + + const baseQuery = { + type: 'object_type', + object_type: 'contact', + }; + + let fieldCondition; + + switch (search_type) { + case 'name': + fieldCondition = { + type: 'field_condition', + field: { + type: 'regular_field', + object_type: 'contact', + field_name: 'name', + }, + condition: { + type: 'text', + mode: 'full_words', + value: search_query, + }, + }; + break; + + case 'email': + fieldCondition = { + type: 'has_related', + this_object_type: 'contact', + related_object_type: 'contact_email', + related_query: { + type: 'field_condition', + field: { + type: 'regular_field', + object_type: 'contact_email', + field_name: 'email', + }, + condition: { + type: 'text', + mode: 'phrase', + value: search_query, + }, + }, + }; + break; + + case 'phone': + fieldCondition = { + type: 'has_related', + this_object_type: 'contact', + related_object_type: 'contact_phone', + related_query: { + type: 'field_condition', + field: { + type: 'regular_field', + object_type: 'contact_phone', + field_name: 'phone', + }, + condition: { + type: 'text', + mode: 'phrase', + value: search_query, + }, + }, + }; + break; + + case 'lead_id': + fieldCondition = { + type: 'field_condition', + field: { + type: 'regular_field', + object_type: 'contact', + field_name: 'lead_id', + }, + condition: { + type: 'text', + mode: 'phrase', // Always exact match for IDs + value: search_query, + }, + }; + break; + + default: + throw new Error(`Unsupported search type: ${search_type}`); + } + + return { + query: { + type: 'and', + queries: [baseQuery, fieldCondition], + }, + _fields: { + lead: ['id', 'name', 'status_label', 'contacts'], + contact: ['id', 'name', 'emails', 'phones'], + }, + }; +} diff --git a/packages/pieces/community/close/src/lib/actions/find-lead.ts b/packages/pieces/community/close/src/lib/actions/find-lead.ts new file mode 100644 index 0000000..e46c802 --- /dev/null +++ b/packages/pieces/community/close/src/lib/actions/find-lead.ts @@ -0,0 +1,203 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { closeAuth } from '../../'; +import { CloseCRMSearchQuery } from '../common/types'; +import { closeApiCall } from '../common/client'; + +export const findLead = createAction({ + auth: closeAuth, + name: 'find_lead', + displayName: 'Find Lead', + description: 'Search for leads with advanced filtering options', + props: { + search_type: Property.StaticDropdown({ + displayName: 'Search Type', + required: true, + options: { + options: [ + { label: 'By Name', value: 'name' }, + { label: 'By Contact Email', value: 'contact_email' }, + { label: 'By Status', value: 'status' }, + ], + }, + }), + search_query: Property.ShortText({ + displayName: 'Search Query', + required: true, + }), + match_type: Property.StaticDropdown({ + displayName: 'Match Type', + required: false, + options: { + options: [ + { label: 'Contains', value: 'contains' }, + { label: 'Exact Match', value: 'exact' }, + { label: 'Starts With', value: 'starts' }, + { label: 'Ends With', value: 'ends' }, + ], + }, + defaultValue: 'contains', + }), + }, + async run(context) { + const { search_type, search_query, match_type } = context.propsValue; + + try { + // Build the search query + const searchQuery = buildLeadSearchQuery({ + search_type, + search_query, + match_type: match_type || 'contains', + }); + + let cursor: string | undefined; + + const result = []; + + do { + const response = await closeApiCall<{ + cursor?: string; + data: Record[]; + }>({ + accessToken: context.auth, + method: HttpMethod.POST, + resourceUri: '/data/search/', + body: { + _limit: 100, + cursor, + ...searchQuery, + }, + }); + + const { data } = response; + if (!data || data.length === 0) break; + + result.push(...data); + cursor = response.cursor; + } while (cursor); + + return { + found: result.length > 0, + result, + }; + } catch (error: any) { + if (error.response?.status === 400) { + throw new Error(`Invalid search query: ${error.response.body?.error || 'Unknown error'}`); + } + if (error.response?.status === 401) { + throw new Error('Authentication failed. Please check your API key.'); + } + if (error.response?.status === 404) { + throw new Error('No leads found matching your criteria.'); + } + throw new Error(`Failed to search leads: ${error.message}`); + } + }, +}); + +// Helper function to build the lead search query +function buildLeadSearchQuery(params: { + search_type: string; + search_query: string; + match_type: string; + custom_field_name?: string; +}): CloseCRMSearchQuery { + const { search_type, search_query, match_type, custom_field_name } = params; + + const baseQuery = { + type: 'object_type', + object_type: 'lead', + }; + + let fieldCondition; + + switch (search_type) { + case 'name': + fieldCondition = { + type: 'field_condition', + field: { + type: 'regular_field', + object_type: 'lead', + field_name: 'name', + }, + condition: { + type: 'text', + mode: 'full_words', + value: search_query, + }, + }; + break; + + case 'contact_email': + fieldCondition = { + type: 'has_related', + this_object_type: 'lead', + related_object_type: 'contact', + related_query: { + type: 'has_related', + this_object_type: 'contact', + related_object_type: 'contact_email', + related_query: { + type: 'field_condition', + field: { + type: 'regular_field', + object_type: 'contact_email', + field_name: 'email', + }, + condition: { + type: 'text', + mode: 'phrase', + value: search_query, + }, + }, + }, + }; + break; + + case 'status': + fieldCondition = { + type: 'field_condition', + field: { + type: 'regular_field', + object_type: 'lead', + field_name: 'status_label', + }, + condition: { + type: 'text', + mode: 'phrase', + value: search_query, + }, + }; + break; + + case 'custom_field': + fieldCondition = { + type: 'field_condition', + field: { + type: 'regular_field', + object_type: 'lead', + field_name: custom_field_name!, + }, + condition: { + type: 'text', + mode: 'full_words', + value: search_query, + }, + }; + break; + + default: + throw new Error(`Unsupported search type: ${search_type}`); + } + + return { + query: { + type: 'and', + queries: [baseQuery, fieldCondition], + }, + _fields: { + lead: ['id', 'name', 'status_label', 'contacts'], + contact: ['id', 'name', 'emails', 'phones'], + }, + }; +} diff --git a/packages/pieces/community/close/src/lib/common/client.ts b/packages/pieces/community/close/src/lib/common/client.ts new file mode 100644 index 0000000..d598d9b --- /dev/null +++ b/packages/pieces/community/close/src/lib/common/client.ts @@ -0,0 +1,81 @@ +import { + AuthenticationType, + httpClient, + HttpMessageBody, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; + +export type CloseApiCallParams = { + accessToken: string; + method: HttpMethod; + resourceUri: string; + query?: Record; + body?: any; +}; + +export const CLOSE_API_URL = 'https://api.close.com/api/v1'; + +export async function closeApiCall({ + accessToken, + method, + resourceUri, + query, + body, +}: CloseApiCallParams): Promise { + const qs: QueryParams = {}; + + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + qs[key] = String(value); + } + } + } + const request: HttpRequest = { + method, + url: CLOSE_API_URL + resourceUri, + authentication: { + type: AuthenticationType.BASIC, + username: accessToken, + password: '', + }, + queryParams: qs, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} + +export async function closePaginatedApiCall({ + accessToken, + method, + resourceUri, + query, + body, +}: CloseApiCallParams): Promise { + const resultData: T[] = []; + const limit = 100; + let skip = 0; + let hasMore = true; + + do { + const response = await closeApiCall<{ data: T[]; has_more: boolean }>({ + accessToken, + method, + resourceUri, + query: { ...query, _limit: limit, _skip: skip }, + body, + }); + const { data, has_more } = response; + if (!data || data.length === 0) break; + + resultData.push(...data); + hasMore = has_more; + skip += limit; + } while (hasMore); + + return resultData; +} diff --git a/packages/pieces/community/close/src/lib/common/props.ts b/packages/pieces/community/close/src/lib/common/props.ts new file mode 100644 index 0000000..ca05a95 --- /dev/null +++ b/packages/pieces/community/close/src/lib/common/props.ts @@ -0,0 +1,204 @@ +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { closePaginatedApiCall } from './client'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const customFields = (objectType: string) => + Property.DynamicProperties({ + displayName: 'Custom Fields', + refreshers: [], + required: false, + props: async ({ auth }) => { + if (!auth) return {}; + + const fields: DynamicPropsValue = {}; + + const response = await closePaginatedApiCall<{ + id: string; + type: string; + choices?: string[]; + accepts_multiple_values: boolean; + name: string; + }>({ + accessToken: auth as unknown as string, + method: HttpMethod.GET, + resourceUri: `/custom_field/${objectType}/`, + }); + + for (const field of response) { + switch (field.type) { + case 'number': + fields[field.id] = Property.Number({ + displayName: field.name, + required: false, + }); + break; + case 'text': + fields[field.id] = Property.ShortText({ + displayName: field.name, + required: false, + }); + break; + case 'textarea': + fields[field.id] = Property.LongText({ + displayName: field.name, + required: false, + }); + break; + case 'date': + case 'datetime': + fields[field.id] = Property.DateTime({ + displayName: field.name, + required: false, + }); + break; + case 'choices': { + const fieldType = field.accepts_multiple_values + ? Property.StaticMultiSelectDropdown + : Property.StaticDropdown; + fields[field.id] = fieldType({ + displayName: field.name, + required: false, + options: { + disabled: false, + options: field.choices + ? field.choices.map((choice) => ({ + label: choice, + value: choice, + })) + : [], + }, + }); + break; + } + + case 'contact': + case 'user': { + const fieldType = field.accepts_multiple_values ? Property.Array : Property.ShortText; + fields[field.id] = fieldType({ + displayName: field.name, + required: false, + description: `Provide ${field.type} ID.`, + }); + break; + } + default: + break; + } + } + + return fields; + }, + }); + +export const statusId = (objectType: string, required = false) => + Property.Dropdown({ + displayName: 'Status', + refreshers: [], + required, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + const response = await closePaginatedApiCall<{ + id: string; + label: string; + }>({ + accessToken: auth as string, + method: HttpMethod.GET, + resourceUri: `/status/${objectType}/`, + }); + + return { + disabled: false, + options: response.map((status) => ({ + label: status.label, + value: status.id, + })), + }; + }, + }); + +export const leadId = (required = false) => + Property.Dropdown({ + displayName: 'Lead', + required, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first.', + }; + } + try { + const response = await closePaginatedApiCall<{ + id: string; + name: string; + }>({ + accessToken: auth as string, + method: HttpMethod.GET, + resourceUri: '/lead/?_fields=id,name', + }); + + return { + disabled: false, + options: response.map((lead) => ({ + label: lead.name, + value: lead.id, + })), + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: 'Could not fetch leads. Check your connection.', + }; + } + }, + }); + +export const userId = (required = false) => + Property.Dropdown({ + displayName: 'User', + required, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first.', + }; + } + try { + const response = await closePaginatedApiCall<{ + id: string; + email: string; + }>({ + accessToken: auth as string, + method: HttpMethod.GET, + resourceUri: '/user/?_fields=id,email', + }); + + return { + disabled: false, + options: response.map((user) => ({ + label: user.email, + value: user.id, + })), + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: 'Could not fetch uers. Check your connection.', + }; + } + }, + }); diff --git a/packages/pieces/community/close/src/lib/common/types.ts b/packages/pieces/community/close/src/lib/common/types.ts new file mode 100644 index 0000000..2a06652 --- /dev/null +++ b/packages/pieces/community/close/src/lib/common/types.ts @@ -0,0 +1,194 @@ +export interface CloseCRMLead { + id: string; + contacts?: { + name: string; + emails?: { email: string }[]; + phones?: { phone: string }[]; + }[]; + [key: string]: unknown; // For custom fields +} + +export interface CloseCRMClient { + post(endpoint: string, data: unknown): Promise<{ data: unknown }>; + // Add other methods as needed +} + +export interface CloseCRMEmailActivity { + lead_id: string; + direction: 'outgoing' | 'incoming'; + note: string; + date_created: string; + _type: 'email'; + email: { + subject: string; + body: string; + sender?: string; + to: Array<{ + email: string; + name?: string; + }>; + attachments?: Array<{ + name: string; + url: string; + size?: number; + }>; + }; +} + +export interface CloseCRMLeadWebhookPayload { + subscription_id: string; + event: { + id: string; + action: 'created' | 'updated' | 'deleted'; + object_type: 'lead'; + object_id: string; + date_created: string; + data: { + id: string; + name: string; + status_label?: string; + status_id?: string; + contacts?: Array; + date_created: string; + date_updated: string; + }; + }; +} + +interface ContactDetails { + id: string; + name: string; + emails?: Array<{ email: string; type?: string }>; + phones?: Array<{ phone: string; type?: string }>; + [key: string]: unknown; +} + +//Contact name +export interface CloseCRMContact { + lead_id: string; + id: string; + name: string; + title?: string; + emails?: { + email: string; + type?: 'direct' | 'home' | 'other' | 'office'; + }[]; + phones?: { + phone: string; + type?: 'mobile' | 'office' | 'home' | 'fax' | 'other' | 'direct'; + }[]; + urls?: { + url: string; + type?: 'website' | 'linkedin' | 'twitter' | 'other' | 'url'; + }[]; + [key: string]: unknown; // For custom fields +} + +//contact search query +export interface CloseCRMSearchQuery { + query: { + type: string; + queries: any[]; + }; + _fields: { + contact: string[]; + lead: string[]; + }; +} + +export interface CloseCRMSearchQuery { + query: { + type: string; + queries: any[]; + }; +} + +//NEW-CONTACT-ADDED + +export interface CloseCRMContactWebhookPayload { + subscription_id: string; + event: { + id: string; + action: 'created' | 'updated' | 'deleted'; + object_type: 'contact'; + object_id: string; + lead_id: string; + date_created: string; + data: { + id: string; + name: string; + title?: string; + lead_id: string; + emails?: Array<{ email: string; type?: string }>; + phones?: Array<{ phone: string; type?: string }>; + date_created: string; + date_updated: string; + }; + }; +} + +//find opportunity + +export interface CloseCRMOpportunity { + lead_id: string; + lead_name?: string; + status_id?: string; + status_label?: string; + status_type?: 'active' | 'won' | 'lost' | 'archived'; + pipeline_id?: string; + pipeline_name?: string; + user_id?: string; + user_name?: string; + contact_id?: string; + value?: number; + value_period?: 'one_time' | 'monthly' | 'annual'; + value_formatted?: string; + expected_value?: number; + annualized_value?: number; + annualized_expected_value?: number; + confidence?: number; + note?: string; + date_created?: string; + date_updated?: string; + date_won?: string; +} + +//opportunity-status changed +export interface CloseCRMOpportunityWebhookPayload { + subscription_id: string; + event: { + id: string; + action: 'created' | 'updated' | 'deleted'; + object_type: 'opportunity'; + object_id: string; + lead_id: string; + payload_id: string; + date_created: string; + changed_fields: string[]; + previous_data?: { + status_type?: string; + status_label?: string; + status_id?: string; + value?: number; + confidence?: number; + }; + data: { + id: string; + lead_id: string; + status_type: 'active' | 'won' | 'lost' | 'archived'; + status_label: string; + status_id: string; + value?: number; + value_currency?: string; + value_formatted?: string; + contact_id?: string; + contact_name?: string; + lead_name?: string; + date_won?: string; + date_lost?: string; + confidence?: number; + date_created: string; + date_updated: string; + } +} +} diff --git a/packages/pieces/community/close/src/lib/triggers/helpers.ts b/packages/pieces/community/close/src/lib/triggers/helpers.ts new file mode 100644 index 0000000..8d1b63e --- /dev/null +++ b/packages/pieces/community/close/src/lib/triggers/helpers.ts @@ -0,0 +1,28 @@ +import crypto from 'crypto'; + +export const verifySignature = ( + signatureKey?: string, // hex-encoded key + timestamp?: string, // 'close-sig-timestamp' header + rawBody?: any, // raw body as string + signatureHash?: string, // 'close-sig-hash' header +): boolean => { + if (!signatureKey || !timestamp || !rawBody || !signatureHash) { + return false; + } + + try { + const dataToHmac = timestamp + rawBody; + + const generatedHash = crypto + .createHmac('sha256', Buffer.from(signatureKey, 'hex')) + .update(dataToHmac, 'utf8') + .digest('hex'); + + return crypto.timingSafeEqual( + Buffer.from(generatedHash, 'hex'), + Buffer.from(signatureHash, 'hex'), + ); + } catch (error) { + return false; + } +}; diff --git a/packages/pieces/community/close/src/lib/triggers/new-contact-added.ts b/packages/pieces/community/close/src/lib/triggers/new-contact-added.ts new file mode 100644 index 0000000..c0517d9 --- /dev/null +++ b/packages/pieces/community/close/src/lib/triggers/new-contact-added.ts @@ -0,0 +1,132 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { closeAuth } from '../../'; +import { CloseCRMContactWebhookPayload } from '../common/types'; +import { closeApiCall } from '../common/client'; +import { verifySignature } from './helpers'; + +const TRIGGER_KEY = 'new-contact-trigger'; + +export const newContactAdded = createTrigger({ + auth: closeAuth, + name: 'new_contact_added', + displayName: 'New Contact Added', + description: 'Triggers when a new contact is created.', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const response = await closeApiCall<{ id: string; signature_key: string }>({ + accessToken: context.auth, + method: HttpMethod.POST, + resourceUri: '/webhook/', + body: { + url: context.webhookUrl, + events: [ + { + object_type: 'contact', + action: 'created', + }, + ], + }, + }); + + const { id, signature_key: signatureKey } = response; + await context.store.put<{ id: string; signatureKey: string }>(TRIGGER_KEY, { + id, + signatureKey, + }); + }, + + async onDisable(context) { + const triggerData = await context.store.get<{ + id: string; + signatureKey: string; + }>(TRIGGER_KEY); + + if (triggerData?.id) { + await closeApiCall({ + method: HttpMethod.DELETE, + accessToken: context.auth, + resourceUri: `/webhook/${triggerData.id}`, + }); + } + + await context.store.delete(TRIGGER_KEY); + }, + + async run(context) { + const triggerData = await context.store.get<{ + id: string; + signatureKey: string; + }>(TRIGGER_KEY); + + const signatureKey = triggerData?.signatureKey; + const signatureHash = context.payload.headers['close-sig-hash']; + const timestamp = context.payload.headers['close-sig-timestamp']; + const rawBody = context.payload.rawBody; + + if (!verifySignature(signatureKey, timestamp, rawBody, signatureHash)) { + return []; + } + + const payload = context.payload.body as CloseCRMContactWebhookPayload; + + // Verify this is a lead creation event + if (payload.event.object_type !== 'contact' || payload.event.action !== 'created') { + return []; + } + + const contact = await closeApiCall({ + accessToken: context.auth, + method: HttpMethod.GET, + resourceUri: `/contact/${payload.event.data.id}/`, + }); + + return [contact]; + }, + + sampleData: { + created_by: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN', + date_created: '2025-05-30T15:29:16.048000+00:00', + date_updated: '2025-05-30T15:29:16.048000+00:00', + display_name: 'John Doe', + emails: [ + { + email: 'johndoe@gmail.com', + is_unsubscribed: false, + type: 'office', + }, + { + email: 'johndoe@gmail.com', + is_unsubscribed: false, + type: 'direct', + }, + ], + id: 'cont_SNLEuIxMqrgu23m1D63rnE84ivTYlXm3pPRJXHWhGqn', + integration_links: [ + { + name: 'LinkedIn Search', + url: 'https://www.linkedin.com/search/results/people/?keywords=JohnDoe', + }, + ], + lead_id: 'lead_Tn9KvxpJ2InYrwOb81TIoOuAoEchLPFJS72i0xMK2vj', + name: 'John Doe', + organization_id: 'orga_qwPaunJJ8R6NPPVQj6Q0KicVJQtyWM45dMnH9HsWZBl', + phones: [ + { + country: 'IN', + phone: '', + phone_formatted: '+911541', + type: 'mobile', + }, + ], + title: 'Test', + updated_by: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN', + urls: [ + { + type: 'url', + url: 'https://www.github.com', + }, + ], + }, +}); diff --git a/packages/pieces/community/close/src/lib/triggers/new-lead-added.ts b/packages/pieces/community/close/src/lib/triggers/new-lead-added.ts new file mode 100644 index 0000000..bfb7782 --- /dev/null +++ b/packages/pieces/community/close/src/lib/triggers/new-lead-added.ts @@ -0,0 +1,155 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { closeAuth } from '../../'; +import { CloseCRMLeadWebhookPayload } from '../common/types'; +import { closeApiCall } from '../common/client'; +import { verifySignature } from './helpers'; + +const TRIGGER_KEY = 'new-lead-trigger'; + +export const newLeadAdded = createTrigger({ + auth: closeAuth, + name: 'new_lead_created', + displayName: 'New Lead Created', + description: 'Triggers when a new lead is created.', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const response = await closeApiCall<{ id: string; signature_key: string }>({ + accessToken: context.auth, + method: HttpMethod.POST, + resourceUri: '/webhook/', + body: { + url: context.webhookUrl, + events: [ + { + object_type: 'lead', + action: 'created', + }, + ], + }, + }); + + const { id, signature_key: signatureKey } = response; + await context.store.put<{ id: string; signatureKey: string }>(TRIGGER_KEY, { + id, + signatureKey, + }); + }, + + async onDisable(context) { + const triggerData = await context.store.get<{ + id: string; + signatureKey: string; + }>(TRIGGER_KEY); + + if (triggerData?.id) { + await closeApiCall({ + method: HttpMethod.DELETE, + accessToken: context.auth, + resourceUri: `/webhook/${triggerData.id}`, + }); + } + + await context.store.delete(TRIGGER_KEY); + }, + + async run(context) { + const triggerData = await context.store.get<{ + id: string; + signatureKey: string; + }>(TRIGGER_KEY); + + const signatureKey = triggerData?.signatureKey; + const signatureHash = context.payload.headers['close-sig-hash']; + const timestamp = context.payload.headers['close-sig-timestamp']; + const rawBody = context.payload.rawBody; + + if (!verifySignature(signatureKey, timestamp, rawBody, signatureHash)) { + return []; + } + + const payload = context.payload.body as CloseCRMLeadWebhookPayload; + + // Verify this is a lead creation event + if (payload.event.object_type !== 'lead' || payload.event.action !== 'created') { + return []; + } + + const lead = await closeApiCall({ + accessToken: context.auth, + method: HttpMethod.GET, + resourceUri: `/lead/${payload.event.data.id}/`, + }); + + return [lead]; + }, + + sampleData: { + addresses: [], + contacts: [ + { + created_by: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN', + date_created: '2025-05-30T15:30:40.148000+00:00', + date_updated: '2025-05-30T15:30:40.148000+00:00', + display_name: 'John Doe', + emails: [ + { + email: 'johndoe@gmail.com', + is_unsubscribed: false, + type: 'office', + }, + ], + id: 'cont_p9jX6fiJ0AryAx06CwT9SY8OyA9PX29zmwXBw9ct3TR', + lead_id: 'lead_fy3zdUuEFQ1WJEi846CGSvpePgzqm0P6XTPRDtSBFTC', + name: 'John Doe', + organization_id: 'orga_qwPaunJJ8R6NPPVQj6Q0KicVJQtyWM45dMnH9HsWZBl', + phones: [ + { + country: 'IN', + phone: '+91754', + phone_formatted: '+91754', + type: 'office', + }, + ], + title: 'SDE', + updated_by: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN', + urls: [ + { + type: 'url', + url: 'https://www.github.com', + }, + ], + }, + ], + created_by: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN', + created_by_name: 'John Doe', + custom: { + 'Current Vendor/Software': 'Close', + 'Custom Field': 'test', + Industry: 'Healthcare', + date: '2025-05-30', + datetime: '2025-05-30T00:00:00+00:00', + }, + date_created: '2025-05-30T15:30:40.122000+00:00', + date_updated: '2025-05-30T15:35:33.131000+00:00', + description: 'TESTING', + display_name: 'John Doe', + id: 'lead_fy3zdUuEFQ1WJEi846CGSvpePgzqm0P6XTPRDtSBFTC', + integration_links: [ + { + name: 'Google Search', + url: 'https://google.com/search?q=HEllo%20KNOW', + }, + ], + name: 'John Doe', + opportunities: [], + organization_id: 'orga_qwPaunJJ8R6NPPVQj6Q0KicVJQtyWM45dMnH9HsWZBl', + status_id: 'stat_piNPXI7AsUxHHwhPJKdCTiQtZsRP96HGb088FzJCgEJ', + status_label: 'Potential', + tasks: [], + updated_by: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN', + updated_by_name: 'John Doe', + url: 'https://www.github.com', + }, +}); diff --git a/packages/pieces/community/close/src/lib/triggers/new-opportunity.ts b/packages/pieces/community/close/src/lib/triggers/new-opportunity.ts new file mode 100644 index 0000000..99ecb96 --- /dev/null +++ b/packages/pieces/community/close/src/lib/triggers/new-opportunity.ts @@ -0,0 +1,131 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { closeAuth } from '../..'; +import { CloseCRMOpportunityWebhookPayload } from '../common/types'; +import { closeApiCall } from '../common/client'; +import { verifySignature } from './helpers'; + +const TRIGGER_KEY = 'new-opportunity-trigger'; + +export const newOpportunityAdded = createTrigger({ + auth: closeAuth, + name: 'new_opportunity_added', + displayName: 'New Opportunity Added', + description: 'Triggers when a new opportunity is created.', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const response = await closeApiCall<{ id: string; signature_key: string }>({ + accessToken: context.auth, + method: HttpMethod.POST, + resourceUri: '/webhook/', + body: { + url: context.webhookUrl, + events: [ + { + object_type: 'opportunity', + action: 'created', + }, + ], + }, + }); + + const { id, signature_key: signatureKey } = response; + await context.store.put<{ id: string; signatureKey: string }>(TRIGGER_KEY, { + id, + signatureKey, + }); + }, + + async onDisable(context) { + const triggerData = await context.store.get<{ + id: string; + signatureKey: string; + }>(TRIGGER_KEY); + + if (triggerData?.id) { + await closeApiCall({ + method: HttpMethod.DELETE, + accessToken: context.auth, + resourceUri: `/webhook/${triggerData.id}`, + }); + } + + await context.store.delete(TRIGGER_KEY); + }, + + async run(context) { + const triggerData = await context.store.get<{ + id: string; + signatureKey: string; + }>(TRIGGER_KEY); + + const signatureKey = triggerData?.signatureKey; + const signatureHash = context.payload.headers['close-sig-hash']; + const timestamp = context.payload.headers['close-sig-timestamp']; + const rawBody = context.payload.rawBody; + + if (!verifySignature(signatureKey, timestamp, rawBody, signatureHash)) { + return []; + } + + const payload = context.payload.body as CloseCRMOpportunityWebhookPayload; + + // Verify this is a lead creation event + if (payload.event.object_type !== 'opportunity' || payload.event.action !== 'created') { + return []; + } + + const opportunity = await closeApiCall({ + accessToken: context.auth, + method: HttpMethod.GET, + resourceUri: `/opportunity/${payload.event.data.id}/`, + }); + + return [opportunity]; + }, + + sampleData: { + annualized_expected_value: 500, + annualized_value: 1000, + attachments: [], + confidence: 50, + contact_id: null, + contact_name: null, + created_by: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN', + created_by_name: 'john doe', + 'custom.cf_cJd1lRfCdIWTgJGLQ84lQZXiGw7ufhdXA2F907z9Mk8': 1, + 'custom.cf_gOgoyjtw8iShE434uBERAEcR6wFoacUtlVPWcPA2EXS': 'Basic', + 'custom.cf_lHRMHDYJVogylPbe6v0DCGcU7kMqz2RJUYnAhntKOOn': 10, + 'custom.cf_usm3RdC8hm7Pb6pYo9M8lKCN1h06Edij9OdzHphwAkM': [ + 'Premium support', + 'Professional services', + 'Subscription', + ], + date_created: '2025-05-30T15:53:52.829000+00:00', + date_lost: null, + date_updated: '2025-05-30T15:53:52.829000+00:00', + date_won: null, + expected_value: 500, + id: 'oppo_NkuyMMFjRDaY5mYQ7bywfpOmiHWPQ0h02bxJoZGwTOS', + integration_links: [], + lead_id: 'lead_Tn9KvxpJ2InYrwOb81TIoOuAoEchLPFJS72i0xMK2vj', + lead_name: 'TEST LEAD', + note: 'Test', + organization_id: 'orga_qwPaunJJ8R6NPPVQj6Q0KicVJQtyWM45dMnH9HsWZBl', + pipeline_id: 'pipe_2kHJXqFpYKmONEWPQijcit', + pipeline_name: 'Sales', + status_display_name: 'Demo Completed', + status_id: 'stat_uqaIpeqabvBXW32bpLZkcD7mhTRvCXqEF0oGQDvOKMG', + status_label: 'Demo Completed', + status_type: 'active', + updated_by: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN', + updated_by_name: 'john doe', + user_id: 'user_QO3f0LQE6fMbrokdE2awZLgT7pRv57S1C8Zv5Uo30TN', + user_name: 'john doe', + value: 1000, + value_currency: 'USD', + value_formatted: '$10', + value_period: 'one_time', + }, +}); diff --git a/packages/pieces/community/close/tsconfig.json b/packages/pieces/community/close/tsconfig.json new file mode 100644 index 0000000..481a4c3 --- /dev/null +++ b/packages/pieces/community/close/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true, + "strictNullChecks": true, + + + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/close/tsconfig.lib.json b/packages/pieces/community/close/tsconfig.lib.json new file mode 100644 index 0000000..d229e0f --- /dev/null +++ b/packages/pieces/community/close/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] + } \ No newline at end of file diff --git a/packages/pieces/community/cloutly/.eslintrc.json b/packages/pieces/community/cloutly/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/cloutly/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/cloutly/README.md b/packages/pieces/community/cloutly/README.md new file mode 100644 index 0000000..9666a2f --- /dev/null +++ b/packages/pieces/community/cloutly/README.md @@ -0,0 +1,7 @@ +# pieces-cloutly + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-cloutly` to build the library. diff --git a/packages/pieces/community/cloutly/package.json b/packages/pieces/community/cloutly/package.json new file mode 100644 index 0000000..736bb35 --- /dev/null +++ b/packages/pieces/community/cloutly/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-cloutly", + "version": "0.0.1" +} diff --git a/packages/pieces/community/cloutly/project.json b/packages/pieces/community/cloutly/project.json new file mode 100644 index 0000000..b3b20b6 --- /dev/null +++ b/packages/pieces/community/cloutly/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-cloutly", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/cloutly/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/cloutly", + "tsConfig": "packages/pieces/community/cloutly/tsconfig.lib.json", + "packageJson": "packages/pieces/community/cloutly/package.json", + "main": "packages/pieces/community/cloutly/src/index.ts", + "assets": [ + "packages/pieces/community/cloutly/*.md", + { + "input": "packages/pieces/community/cloutly/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/cloutly/src/index.ts b/packages/pieces/community/cloutly/src/index.ts new file mode 100644 index 0000000..c367ff0 --- /dev/null +++ b/packages/pieces/community/cloutly/src/index.ts @@ -0,0 +1,34 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { sendReviewInvite } from './lib/actions/send-review-invite'; +import { PieceCategory } from '@activepieces/shared'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; + +export const cloutlyAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'Please enter the API Key obtained from Cloutly.', +}); + +export const cloutly = createPiece({ + displayName: 'Cloutly', + description: 'Review Management Tool', + auth: cloutlyAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/cloutly.svg', + categories: [PieceCategory.MARKETING], + authors: ['joshuaheslin'], + actions: [ + sendReviewInvite, + createCustomApiCallAction({ + baseUrl: () => { + return 'https://app.cloutly.com/api/v1'; + }, + auth: cloutlyAuth, + authMapping: async (auth) => ({ + 'x-app': 'activepieces', + 'x-api-key': auth as string, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/cloutly/src/lib/actions/send-review-invite.ts b/packages/pieces/community/cloutly/src/lib/actions/send-review-invite.ts new file mode 100644 index 0000000..9a58086 --- /dev/null +++ b/packages/pieces/community/cloutly/src/lib/actions/send-review-invite.ts @@ -0,0 +1,83 @@ +import { cloutlyAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const sendReviewInvite = createAction({ + auth:cloutlyAuth, + name: 'sendReviewInvite', + displayName: 'Send Review Invite', + description: 'Sends a review invite to your customer.', + props: { + firstName: Property.ShortText({ + displayName: 'First Name', + required: true + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + description: 'The email of the customer to send the invite to (required if Phone Number is empty)', + required: false + }), + phoneNumber: Property.ShortText({ + displayName: 'Phone Number', + description: 'The phone number of the customer to send the invite to (required if Email is empty)', + required: false + }), + sourceCustomerId: Property.ShortText({ + displayName: 'Source Customer ID', + required: false + }), + businessId: Property.ShortText({ + displayName: 'Business ID', + required: true + }), + campaignId: Property.ShortText({ + displayName: 'Campaign ID', + required: true + }), + inviteDelayDays: Property.Number({ + displayName: 'Invite Delay Days', + description: 'The number of days to delay the invite (i.e send after X days)', + required: false + }), + salesRepEmail: Property.ShortText({ + displayName: 'Sales Rep Email', + description: 'The email of the sales rep to associate the review and customer', + required: false + }), + }, + async run(context) { + const data = { + firstName: context.propsValue.firstName, + lastName: context.propsValue.lastName, + channel: { + email: context.propsValue.email, + phoneNumber: context.propsValue.phoneNumber, + }, + source: 'api', + sourceCustomerId: context.propsValue.sourceCustomerId, + businessId: context.propsValue.businessId, + campaignId: context.propsValue.campaignId, + inviteDelayDays: context.propsValue.inviteDelayDays, + salesRepEmail: context.propsValue.salesRepEmail, + }; + + const apiKey = context.auth as string; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://app.cloutly.com/api/v1/send-review-invite', + body: data, + headers: { + 'Content-Type': 'application/json', + 'x-app': 'activepieces', + 'x-api-key': apiKey + } + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/cloutly/tsconfig.json b/packages/pieces/community/cloutly/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/cloutly/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/cloutly/tsconfig.lib.json b/packages/pieces/community/cloutly/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/cloutly/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/coda/.eslintrc.json b/packages/pieces/community/coda/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/coda/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/coda/README.md b/packages/pieces/community/coda/README.md new file mode 100644 index 0000000..d2b1650 --- /dev/null +++ b/packages/pieces/community/coda/README.md @@ -0,0 +1,7 @@ +# pieces-coda + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-coda` to build the library. diff --git a/packages/pieces/community/coda/package.json b/packages/pieces/community/coda/package.json new file mode 100644 index 0000000..8911bc3 --- /dev/null +++ b/packages/pieces/community/coda/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-coda", + "version": "0.0.2" +} diff --git a/packages/pieces/community/coda/project.json b/packages/pieces/community/coda/project.json new file mode 100644 index 0000000..16b08d5 --- /dev/null +++ b/packages/pieces/community/coda/project.json @@ -0,0 +1,45 @@ +{ + "name": "pieces-coda", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/coda/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/coda", + "tsConfig": "packages/pieces/community/coda/tsconfig.lib.json", + "packageJson": "packages/pieces/community/coda/package.json", + "main": "packages/pieces/community/coda/src/index.ts", + "assets": [ + "packages/pieces/community/coda/*.md" + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/coda/src/index.ts b/packages/pieces/community/coda/src/index.ts new file mode 100644 index 0000000..b8d1cdb --- /dev/null +++ b/packages/pieces/community/coda/src/index.ts @@ -0,0 +1,45 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { findRowAction } from './lib/actions/find-row'; +import { createRowAction } from './lib/actions/create-row'; +import { upsertRowAction } from './lib/actions/upsert-row'; +import { updateRowAction } from './lib/actions/update-row'; +import { newRowCreatedTrigger } from './lib/triggers/new-row-created'; +import { PieceCategory } from '@activepieces/shared'; +import { getRowAction } from './lib/actions/get-row'; +import { listTablesAction } from './lib/actions/list-tables'; +import { getTableAction } from './lib/actions/get-table'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { CODA_BASE_URL } from './lib/common/types'; + +export const codaAuth = PieceAuth.SecretText({ + displayName: 'Coda API Key', + description: `Create an API key in the [Coda Account dashboard](https://coda.io/account).`, + required: true, +}); + +export const coda = createPiece({ + displayName: 'Coda', + logoUrl: 'https://cdn.activepieces.com/pieces/coda.png', + categories: [PieceCategory.PRODUCTIVITY], + auth: codaAuth, + authors: ['onyedikachi-david', 'kishanprmr','rimjhimyadav'], + actions: [ + createRowAction, + updateRowAction, + upsertRowAction, + findRowAction, + getRowAction, + listTablesAction, + getTableAction, + createCustomApiCallAction({ + auth:codaAuth, + baseUrl:()=>CODA_BASE_URL, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + + + }) + ], + triggers: [newRowCreatedTrigger], +}); diff --git a/packages/pieces/community/coda/src/lib/actions/create-row.ts b/packages/pieces/community/coda/src/lib/actions/create-row.ts new file mode 100644 index 0000000..f536033 --- /dev/null +++ b/packages/pieces/community/coda/src/lib/actions/create-row.ts @@ -0,0 +1,52 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { codaAuth } from '../..'; +import { codaClient } from '../common/types'; +import { docIdDropdown, tableIdDropdown, tableRowsDynamicProps } from '../common/props'; +import { isNil } from '@activepieces/shared'; + +export const createRowAction = createAction({ + auth: codaAuth, + name: 'create-row', + displayName: 'Create Row', + description: 'Creates a new row in the selected table.', + props: { + docId: docIdDropdown, + tableId: tableIdDropdown, + rowData: tableRowsDynamicProps, + }, + async run(context) { + const { docId, tableId, rowData } = context.propsValue; + const client = codaClient(context.auth); + + const cells = Object.entries(rowData as Record) + .filter(([, value]) => value !== undefined && value !== null && value !== '') + .map(([columnId, value]) => ({ + column: columnId, + value: value, + })); + + if (cells.length === 0) { + throw new Error('Provide any column values to create new row.'); + } + + const payload = { + rows: [ + { + cells: cells, + }, + ], + }; + + const response = await client.mutateRows(docId, tableId, payload, { + disableParsing: false, + }); + + const rowId = response.addedRowIds?.[0]; + + if (isNil(rowId)) { + throw new Error(`Unexpected error occured : ${JSON.stringify(response)}`); + } + + return { rowId }; + }, +}); diff --git a/packages/pieces/community/coda/src/lib/actions/find-row.ts b/packages/pieces/community/coda/src/lib/actions/find-row.ts new file mode 100644 index 0000000..430a48b --- /dev/null +++ b/packages/pieces/community/coda/src/lib/actions/find-row.ts @@ -0,0 +1,49 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { codaAuth } from '../..'; +import { CodaRow, codaClient } from '../common/types'; +import { columnIdsDropdown, docIdDropdown, tableIdDropdown } from '../common/props'; + +export const findRowAction = createAction({ + auth: codaAuth, + name: 'find-row', + displayName: 'Find Row(s)', + description: 'Find specific rows in the selected table using a column match search.', + props: { + docId: docIdDropdown, + tableId: tableIdDropdown, + searchColumn: columnIdsDropdown('Search Column', true), + searchValue: Property.ShortText({ + displayName: 'Search Value', + required: true, + }), + }, + async run(context) { + const { docId, tableId, searchColumn, searchValue } = context.propsValue; + const client = codaClient(context.auth); + + const matchedRows: CodaRow[] = []; + let nextPageToken: string | undefined = undefined; + + do { + const response = await client.listRows(docId, tableId, { + query: `${searchColumn}:${JSON.stringify(searchValue)}`, + sortBy: 'natural', + useColumnNames: true, + valueFormat: 'simpleWithArrays', + visibleOnly: true, + limit: 100, + pageToken: nextPageToken, + }); + + if (response.items) { + matchedRows.push(...response.items); + } + nextPageToken = response.nextPageToken; + } while (nextPageToken); + + return { + found: matchedRows.length > 0, + result: matchedRows, + }; + }, +}); diff --git a/packages/pieces/community/coda/src/lib/actions/get-row.ts b/packages/pieces/community/coda/src/lib/actions/get-row.ts new file mode 100644 index 0000000..7ee8385 --- /dev/null +++ b/packages/pieces/community/coda/src/lib/actions/get-row.ts @@ -0,0 +1,28 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { codaAuth } from '../..'; +import { codaClient } from '../common/types'; +import { docIdDropdown, tableIdDropdown } from '../common/props'; + +export const getRowAction = createAction({ + auth: codaAuth, + name: 'get-row', + displayName: 'Get Row', + description: 'Retrieves a single row by specified ID.', + props: { + docId: docIdDropdown, + tableId: tableIdDropdown, + rowIdOrName: Property.ShortText({ + displayName: 'Row ID or Name', + required: true, + }), + }, + async run(context) { + const { docId, tableId, rowIdOrName } = context.propsValue; + const client = codaClient(context.auth); + + return await client.getRow(docId, tableId, rowIdOrName, { + useColumnNames: true, + valueFormat: 'simpleWithArrays', + }); + }, +}); diff --git a/packages/pieces/community/coda/src/lib/actions/get-table.ts b/packages/pieces/community/coda/src/lib/actions/get-table.ts new file mode 100644 index 0000000..5a6a43c --- /dev/null +++ b/packages/pieces/community/coda/src/lib/actions/get-table.ts @@ -0,0 +1,21 @@ +import { codaAuth } from '../../index'; +import { createAction } from '@activepieces/pieces-framework'; +import { docIdDropdown, tableIdDropdown } from '../common/props'; +import { codaClient } from '../common/types'; + +export const getTableAction = createAction({ + auth: codaAuth, + name: 'get-table', + displayName: 'Get Table', + description: 'Get structure and details of a specific table (e.g., columns, schema).', + props: { + docId: docIdDropdown, + tableId: tableIdDropdown, + }, + async run(context) { + const { docId, tableId } = context.propsValue; + const client = codaClient(context.auth); + + return await client.getTableDetails(docId, tableId, {}); + }, +}); diff --git a/packages/pieces/community/coda/src/lib/actions/list-tables.ts b/packages/pieces/community/coda/src/lib/actions/list-tables.ts new file mode 100644 index 0000000..5b349ec --- /dev/null +++ b/packages/pieces/community/coda/src/lib/actions/list-tables.ts @@ -0,0 +1,47 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { codaAuth } from '../..'; +import { CodaTableReference, codaClient } from '../common/types'; +import { docIdDropdown } from '../common/props'; + +export const listTablesAction = createAction({ + auth: codaAuth, + name: 'list-tables', + displayName: 'List Table(s)', + description: 'List tables in a selected document.', + props: { + docId: docIdDropdown, + max: Property.Number({ + displayName: 'Max Tables', + description: 'Maximum number of results to return.', + required: true, + }), + }, + async run(context) { + const { docId, max } = context.propsValue; + const client = codaClient(context.auth); + + const allTables: CodaTableReference[] = []; + let nextPageToken: string | undefined = undefined; + + do { + const response = await client.listTables(docId as string, { + limit: 100, + sortBy: 'name', + tableTypes: 'table', + pageToken: nextPageToken, + }); + + if (response.items) { + allTables.push(...response.items); + } + nextPageToken = response.nextPageToken; + } while (nextPageToken && allTables.length < max); + + if (allTables.length > max) allTables.length = max; + + return { + found: allTables.length > 0, + result: allTables, + }; + }, +}); diff --git a/packages/pieces/community/coda/src/lib/actions/update-row.ts b/packages/pieces/community/coda/src/lib/actions/update-row.ts new file mode 100644 index 0000000..09ee968 --- /dev/null +++ b/packages/pieces/community/coda/src/lib/actions/update-row.ts @@ -0,0 +1,50 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { codaAuth } from '../..'; +import { codaClient } from '../common/types'; +import { docIdDropdown, tableIdDropdown, tableRowsDynamicProps } from '../common/props'; +import { isNil } from '@activepieces/shared'; + +export const updateRowAction = createAction({ + auth: codaAuth, + name: 'update-row', + displayName: 'Update Row', + description: 'Updates an existing row in the selected table.', + props: { + docId: docIdDropdown, + tableId: tableIdDropdown, + rowIdOrName: Property.ShortText({ + displayName: 'Row ID or Name', + required: true, + }), + rowData: tableRowsDynamicProps, + }, + async run(context) { + const { docId, tableId, rowIdOrName, rowData } = context.propsValue; + const client = codaClient(context.auth as string); + + const cells = Object.entries(rowData as Record) + .filter(([, value]) => value !== undefined && value !== null && value !== '') + .map(([columnId, value]) => ({ + column: columnId, + value: value, + })); + + const payload = { + row: { + cells: cells, + }, + }; + + const response = await client.updateRow(docId, tableId, rowIdOrName, payload, { + disableParsing: false, + }); + + const rowId = response.id; + + if (isNil(rowId)) { + throw new Error(`Unexpected error occured : ${JSON.stringify(response)}`); + } + + return { rowId }; + }, +}); diff --git a/packages/pieces/community/coda/src/lib/actions/upsert-row.ts b/packages/pieces/community/coda/src/lib/actions/upsert-row.ts new file mode 100644 index 0000000..acbb87b --- /dev/null +++ b/packages/pieces/community/coda/src/lib/actions/upsert-row.ts @@ -0,0 +1,52 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { codaAuth } from '../..'; +import { codaClient } from '../common/types'; +import { + columnIdsDropdown, + docIdDropdown, + tableIdDropdown, + tableRowsDynamicProps, +} from '../common/props'; + +export const upsertRowAction = createAction({ + auth: codaAuth, + name: 'upsert-row', + displayName: 'Upsert Row', + description: 'Creates a new row or updates an existing one if it matches key columns.', + props: { + docId: docIdDropdown, + tableId: tableIdDropdown, + keyColumns: columnIdsDropdown('Matching Columns', false), + rowData: tableRowsDynamicProps, + }, + async run(context) { + const { docId, tableId, rowData, keyColumns } = context.propsValue; + const client = codaClient(context.auth as string); + + const cells = Object.entries(rowData as Record) + .filter(([, value]) => value !== undefined && value !== null && value !== '') + .map(([columnId, value]) => ({ + column: columnId, + value: value, + })); + + const payload = { + rows: [ + { + cells: cells, + }, + ], + keyColumns: keyColumns as string[], + }; + + const response = await client.mutateRows(docId, tableId, payload, { + disableParsing: false, + }); + + if (!response.requestId) { + throw new Error(`Unexpected error occured : ${JSON.stringify(response)}`); + } + + return { success: true }; + }, +}); diff --git a/packages/pieces/community/coda/src/lib/common/props.ts b/packages/pieces/community/coda/src/lib/common/props.ts new file mode 100644 index 0000000..4a0fd07 --- /dev/null +++ b/packages/pieces/community/coda/src/lib/common/props.ts @@ -0,0 +1,244 @@ +import { DropdownOption, DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { codaClient, CodaTableColumn } from './types'; + +export const docIdDropdown = Property.Dropdown({ + displayName: 'Document', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your Coda account first.', + options: [], + }; + } + const client = codaClient(auth as unknown as string); + const docs: DropdownOption[] = []; + let nextPageToken: string | undefined = undefined; + try { + do { + const response = await client.listDocs({ + limit: 100, + pageToken: nextPageToken, + }); + if (response.items) { + docs.push( + ...response.items.map((doc) => ({ + label: doc.name, + value: doc.id, + })), + ); + } + nextPageToken = response.nextPageToken; + } while (nextPageToken); + + return { + disabled: false, + options: docs, + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: 'Error listing docs, please check connection or API key permissions.', + }; + } + }, +}); + +export const tableIdDropdown = Property.Dropdown({ + displayName: 'Table', + required: true, + refreshers: ['docId'], + options: async ({ auth, docId }) => { + if (!auth || !docId) { + return { + disabled: true, + placeholder: !auth ? 'Connect your Coda account first.' : 'Select a document first.', + options: [], + }; + } + const client = codaClient(auth as unknown as string); + const tables: DropdownOption[] = []; + let nextPageToken: string | undefined = undefined; + + try { + do { + const response = await client.listTables(docId as unknown as string, { + limit: 100, + pageToken: nextPageToken, + tableTypes: 'table', + }); + if (response.items) { + tables.push( + ...response.items.map((table) => ({ + label: table.name, + value: table.id, + })), + ); + } + nextPageToken = response.nextPageToken; + } while (nextPageToken); + + return { + disabled: false, + options: tables, + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: 'Error listing tables. Check document ID or permissions.', + }; + } + }, +}); + +export const tableRowsDynamicProps = Property.DynamicProperties({ + displayName: 'Row Data', + description: 'Define the data for the new row based on table columns.', + required: true, + refreshers: ['docId', 'tableId'], + props: async ({ tableId, auth, docId }) => { + if (!auth || !docId || !tableId) { + return {}; + } + + const client = codaClient(auth as unknown as string); + const fields: DynamicPropsValue = {}; + + try { + const columns: CodaTableColumn[] = []; + let nextPageToken: string | undefined = undefined; + do { + const columnsResponse = await client.listColumns( + docId as unknown as string, + tableId as unknown as string, + { + limit: 100, + pageToken: nextPageToken, + }, + ); + if (columnsResponse.items) { + columns.push(...columnsResponse.items); + } + nextPageToken = columnsResponse.nextPageToken; + } while (nextPageToken); + + if (columns.length > 0) { + for (const column of columns) { + if (column.calculated) { + continue; + } + + switch (column.format.type.toLowerCase()) { + case 'text': + case 'link': + case 'email': + fields[column.id] = Property.ShortText({ + displayName: column.name, + required: false, + }); + break; + case 'select': + case 'lookup': + fields[column.id] = Property.ShortText({ + displayName: column.name, + required: false, + description: column.format.isArray + ? 'Provide options as comma seprated values.' + : '', + }); + break; + + case 'number': + case 'currency': + case 'percent': + case 'slider': + case 'scale': + case 'duration': + fields[column.id] = Property.Number({ + displayName: column.name, + required: false, + }); + break; + case 'date': + case 'dateTime': + case 'time': + fields[column.id] = Property.DateTime({ + displayName: column.name, + required: false, + }); + break; + case 'checkbox': + fields[column.id] = Property.Checkbox({ + displayName: column.name, + required: false, + }); + break; + default: + break; + } + } + } + return fields; + } catch (error) { + console.error('Coda: Failed to fetch table columns for dynamic properties:', error); + return {}; + } + }, +}); + +export const columnIdsDropdown = (displayName: string, singleSelect = true) => { + const dropdownType = singleSelect ? Property.Dropdown : Property.MultiSelectDropdown; + return dropdownType({ + displayName, + required: true, + refreshers: ['docId', 'tableId'], + options: async ({ auth, docId, tableId }) => { + if (!auth || !docId || !tableId) { + return { + disabled: true, + placeholder: 'Connect your Coda account first.', + options: [], + }; + } + const client = codaClient(auth as unknown as string); + const columns: DropdownOption[] = []; + let nextPageToken: string | undefined = undefined; + try { + do { + const response = await client.listColumns( + docId as unknown as string, + tableId as unknown as string, + { + limit: 100, + pageToken: nextPageToken, + }, + ); + if (response.items) { + columns.push( + ...response.items.map((column) => ({ + label: column.name, + value: column.id, + })), + ); + } + nextPageToken = response.nextPageToken; + } while (nextPageToken); + + return { + disabled: false, + options: columns, + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: 'Error listing docs, please check connection or API key permissions.', + }; + } + }, + }); +}; diff --git a/packages/pieces/community/coda/src/lib/common/types.ts b/packages/pieces/community/coda/src/lib/common/types.ts new file mode 100644 index 0000000..0daadc1 --- /dev/null +++ b/packages/pieces/community/coda/src/lib/common/types.ts @@ -0,0 +1,364 @@ +import { HttpMethod, httpClient, HttpRequest, AuthenticationType } from "@activepieces/pieces-common"; + +export const CODA_BASE_URL = "https://coda.io/apis/v1"; + +// --- Common Reference Types --- +export interface CodaObjectReference { + id: string; + type: string; + href: string; + name?: string; // Common but not always present in all reference types +} + +export interface CodaPageReference extends CodaObjectReference { + type: 'page'; // More specific type if known + // Add other page-specific reference fields if needed +} + +export interface CodaColumnReference extends CodaObjectReference { + type: 'column'; // More specific type if known + // Add other column-specific reference fields if needed +} + +// --- Table Related Interfaces --- +export interface CodaTableReference extends CodaObjectReference { + type: 'table' | 'view'; // More specific, can be table or view reference + name: string; // Name is required for table reference as per GetTable response +} + +export interface CodaListTablesResponse { + items: CodaTableReference[]; + href?: string; + nextPageToken?: string; + nextPageLink?: string; +} + +export interface CodaSort { + column: CodaColumnReference | string; // API might return ID or full reference + direction: 'ascending' | 'descending'; +} + +// Interface for the format of a column +export interface CodaColumnFormat { + type: string; // e.g., "text", "number", "date", "person", "lookup", "checkbox", "currency", "percent", "slider", "scale", "selectList", "multiSelectList", "button" + // Other format-specific properties can be added here if needed for dynamic prop generation + // For example, for selectList: + // options?: { name: string, id?: string }[]; + // For lookup: + // table?: CodaTableReference; + isArray?:boolean +} + +// Interface for a single column in the getTableDetails response +export interface CodaTableColumn { + id: string; // Column ID, e.g., "c-123abcDEF" + type: "column"; + href: string; + name: string; // User-visible name of the column + format: CodaColumnFormat; + display?: boolean; // As per docs, optional + calculated?: boolean; // Whether this is a calculated column + formula?: string; // The formula if it's a calculated column + defaultValue?: string; // As per docs, it's a formula string + // Add other relevant column properties if necessary +} + +export interface CodaListColumnsResponse { + items: CodaTableColumn[]; + href?: string; + nextPageToken?: string; + nextPageLink?: string; +} + +export interface CodaGetTableDetailsResponse { + id: string; + type: "table"; // This specific endpoint returns "table" + tableType: "table" | "view"; + href: string; + name: string; + parent: CodaPageReference; + browserLink: string; + displayColumn: CodaColumnReference; // This is a reference, not the full column object + rowCount: number; + sorts: CodaSort[]; + layout: string; // Enum of layout types, string for simplicity + createdAt: string; // date-time + updatedAt: string; // date-time + parentTable?: CodaTableReference; // Optional, for views + filter?: any; // object, structure can be complex +} + + +// --- Row Related Interfaces --- +export interface CodaRow { + id: string; + type: "row"; + href: string; + name: string; + index: number; + browserLink: string; + createdAt: string; + updatedAt: string; + values: Record; + parentTable?: CodaTableReference; +} + +export interface CodaGetRowResponse extends CodaRow { + parent: CodaTableReference; +} + +export interface CodaListRowsResponse { + items: CodaRow[]; + href?: string; + nextPageToken?: string; + nextPageLink?: string; + nextSyncToken?: string; +} + +// For creating/updating rows +export interface CodaCellEdit { + column: string; // Column ID or Name + value: any; +} +export interface CodaRowEdit { + cells: CodaCellEdit[]; +} + +export interface CodaMutateRowsPayload { + rows: CodaRowEdit[]; + keyColumns?: string[]; // Column IDs or Names for upsert +} + +export interface CodaMutateRowsResponse { + requestId: string; + addedRowIds?: string[]; + updatedRowIds?: string[]; // API docs for POST say addedRowIds, but PUT might have updatedRowIds. Let's be broad. +} + +export interface CodaUpdateRowPayload { // Used for PUT (update single row) + row: CodaRowEdit; +} + +export interface CodaUpdateRowResponse { + requestId: string; + id: string; // ID of the updated row +} + +// --- Document Related Interfaces --- +export interface CodaDocIcon { + name: string; + type: string; + browserLink: string; +} + +export interface CodaDocSize { + totalRowCount: number; + tableAndViewCount: number; + pageCount: number; + overApiSizeLimit: boolean; +} + +export interface CodaDocSourceDocReference { + id: string; + type: "doc"; + browserLink: string; + href: string; +} + +export interface CodaDocPublished { + browserLink: string; + discoverable: boolean; + earnCredit: boolean; + mode: "view" | "play" | "edit"; + categories: { name: string }[]; // Simplified, assuming only name is needed for now + description?: string; + imageLink?: string; +} +export interface CodaDoc { + id: string; + type: "doc"; + href: string; + browserLink: string; + name: string; + owner: string; // email + ownerName: string; + createdAt: string; // date-time + updatedAt: string; // date-time + workspaceId: string; // Deprecated but present + folderId: string; // Deprecated but present + workspace: CodaObjectReference; // WorkspaceReference + folder: CodaObjectReference; // FolderReference + icon?: CodaDocIcon; + docSize?: CodaDocSize; + sourceDoc?: CodaDocSourceDocReference; + published?: CodaDocPublished; +} + +export interface CodaListDocsResponse { + items: CodaDoc[]; + href?: string; + nextPageToken?: string; + nextPageLink?: string; +} + +// --- API Client --- +export interface CodaAPIClient { + listTables: (docId: string, params?: { limit?: number; sortBy?: string; tableTypes?: string, pageToken?: string }) => Promise; + getTableDetails: (docId: string, tableIdOrName: string, params?: { useUpdatedTableLayouts?: boolean }) => Promise; + listColumns: (docId: string, tableIdOrName: string, params?: { limit?: number; pageToken?: string; visibleOnly?: boolean; }) => Promise; + getRow: (docId: string, tableIdOrName: string, rowIdOrName: string, params?: { useColumnNames?: boolean; valueFormat?: string }) => Promise; + listRows: (docId: string, tableIdOrName: string, params?: { + query?: string; + sortBy?: string; + useColumnNames?: boolean; + valueFormat?: string; + visibleOnly?: boolean; + limit?: number; + pageToken?: string; + syncToken?: string; + }) => Promise; + mutateRows: (docId: string, tableIdOrName: string, payload: CodaMutateRowsPayload, params?: { disableParsing?: boolean }) => Promise; + updateRow: (docId: string, tableIdOrName: string, rowIdOrName: string, payload: CodaUpdateRowPayload, params?: { disableParsing?: boolean }) => Promise; + listDocs: (params?: { + isOwner?: boolean; + isPublished?: boolean; + query?: string; + sourceDoc?: string; + isStarred?: boolean; + inGallery?: boolean; + workspaceId?: string; + folderId?: string; + limit?: number; + pageToken?: string; + }) => Promise; +} + +export const codaClient = (apiKey: string): CodaAPIClient => { + const makeRequest = async (request: Omit & { body?: any }): Promise => { + const response = await httpClient.sendRequest({ + ...request, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: apiKey, + } + }); + return response.body; + } + + return { + listTables: async (docId, params) => { + const queryParams: Record = {}; + if (params?.limit) queryParams['limit'] = params.limit.toString(); + if (params?.sortBy) queryParams['sortBy'] = params.sortBy; + if (params?.tableTypes) queryParams['tableTypes'] = params.tableTypes; + if (params?.pageToken) queryParams['pageToken'] = params.pageToken; + + return makeRequest({ + method: HttpMethod.GET, + url: `${CODA_BASE_URL}/docs/${docId}/tables`, + queryParams: Object.fromEntries(Object.entries(queryParams).map(([k, v]) => [k, String(v)])), + }); + }, + getTableDetails: async (docId, tableIdOrName, params) => { + const queryParams: Record = {}; + if (params?.useUpdatedTableLayouts !== undefined) queryParams['useUpdatedTableLayouts'] = params.useUpdatedTableLayouts; + + return makeRequest({ + method: HttpMethod.GET, + url: `${CODA_BASE_URL}/docs/${docId}/tables/${encodeURIComponent(tableIdOrName)}`, + queryParams: Object.fromEntries(Object.entries(queryParams).map(([k, v]) => [k, String(v)])), + }); + }, + listColumns: async (docId, tableIdOrName, params) => { + const queryParams: Record = {}; + if (params?.limit) queryParams['limit'] = params.limit; + if (params?.pageToken) queryParams['pageToken'] = params.pageToken; + if (params?.visibleOnly !== undefined) queryParams['visibleOnly'] = params.visibleOnly; + + return makeRequest({ + method: HttpMethod.GET, + url: `${CODA_BASE_URL}/docs/${docId}/tables/${encodeURIComponent(tableIdOrName)}/columns`, + queryParams: Object.fromEntries(Object.entries(queryParams).map(([k,v])=>[k,String(v)])), + }); + }, + getRow: async (docId, tableIdOrName, rowIdOrName, params) => { + const queryParams: Record = {}; + if (params?.useColumnNames !== undefined) queryParams['useColumnNames'] = params.useColumnNames; + if (params?.valueFormat) queryParams['valueFormat'] = params.valueFormat; + + return makeRequest({ + method: HttpMethod.GET, + url: `${CODA_BASE_URL}/docs/${docId}/tables/${encodeURIComponent(tableIdOrName)}/rows/${encodeURIComponent(rowIdOrName)}`, + queryParams: Object.fromEntries(Object.entries(queryParams).map(([k, v]) => [k, String(v)])), + }); + }, + listRows: async (docId, tableIdOrName, params) => { + const queryParams: Record = {}; + if (params?.query) queryParams['query'] = params.query; + if (params?.sortBy) queryParams['sortBy'] = params.sortBy; + if (params?.useColumnNames !== undefined) queryParams['useColumnNames'] = params.useColumnNames; + if (params?.valueFormat) queryParams['valueFormat'] = params.valueFormat; + if (params?.visibleOnly !== undefined) queryParams['visibleOnly'] = params.visibleOnly; + if (params?.limit) queryParams['limit'] = params.limit; + if (params?.pageToken) queryParams['pageToken'] = params.pageToken; + if (params?.syncToken) queryParams['syncToken'] = params.syncToken; + + return makeRequest({ + method: HttpMethod.GET, + url: `${CODA_BASE_URL}/docs/${docId}/tables/${encodeURIComponent(tableIdOrName)}/rows`, + queryParams: Object.fromEntries( + Object.entries(queryParams).map(([key, value]) => [key, String(value)]) + ), + }); + }, + mutateRows: async (docId, tableIdOrName, payload, params) => { + const queryParams: Record = {}; + if (params?.disableParsing !== undefined) queryParams['disableParsing'] = params.disableParsing; + + return makeRequest({ + method: HttpMethod.POST, + url: `${CODA_BASE_URL}/docs/${docId}/tables/${encodeURIComponent(tableIdOrName)}/rows`, + body: payload, + queryParams: Object.fromEntries(Object.entries(queryParams).map(([k, v]) => [k, String(v)])), + headers: { + 'Content-Type': 'application/json' + } + }); + }, + updateRow: async (docId, tableIdOrName, rowIdOrName, payload, params) => { + const queryParams: Record = {}; + if (params?.disableParsing !== undefined) queryParams['disableParsing'] = params.disableParsing; + + return makeRequest({ + method: HttpMethod.PUT, + url: `${CODA_BASE_URL}/docs/${docId}/tables/${encodeURIComponent(tableIdOrName)}/rows/${encodeURIComponent(rowIdOrName)}`, + body: payload, + queryParams: Object.fromEntries(Object.entries(queryParams).map(([k, v]) => [k, String(v)])), + headers: { + 'Content-Type': 'application/json' + } + }); + }, + listDocs: async (params) => { + const queryParams: Record = {}; + if (params?.isOwner !== undefined) queryParams['isOwner'] = params.isOwner; + if (params?.isPublished !== undefined) queryParams['isPublished'] = params.isPublished; + if (params?.query) queryParams['query'] = params.query; + if (params?.sourceDoc) queryParams['sourceDoc'] = params.sourceDoc; + if (params?.isStarred !== undefined) queryParams['isStarred'] = params.isStarred; + if (params?.inGallery !== undefined) queryParams['inGallery'] = params.inGallery; + if (params?.workspaceId) queryParams['workspaceId'] = params.workspaceId; + if (params?.folderId) queryParams['folderId'] = params.folderId; + if (params?.limit) queryParams['limit'] = params.limit; + if (params?.pageToken) queryParams['pageToken'] = params.pageToken; + + return makeRequest({ + method: HttpMethod.GET, + url: `${CODA_BASE_URL}/docs`, + queryParams: Object.fromEntries( + Object.entries(queryParams).map(([key, value]) => [key, String(value)]) + ), + }); + } + }; +}; diff --git a/packages/pieces/community/coda/src/lib/triggers/new-row-created.ts b/packages/pieces/community/coda/src/lib/triggers/new-row-created.ts new file mode 100644 index 0000000..690bc3e --- /dev/null +++ b/packages/pieces/community/coda/src/lib/triggers/new-row-created.ts @@ -0,0 +1,102 @@ +import { + PiecePropValueSchema, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { codaAuth } from '../..'; +import { CodaRow, codaClient } from '../common/types'; +import dayjs from 'dayjs'; +import { docIdDropdown, tableIdDropdown } from '../common/props'; + +type Props = { + tableId: string; + docId: string; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + const { tableId, docId } = propsValue; + const isTest = lastFetchEpochMS === 0; + const client = codaClient(auth); + + const rows: CodaRow[] = []; + let nextPageToken: string | undefined = undefined; + + // We will sort by createdAt to process in order. The API default is ascending. + do { + const response = await client.listRows(docId, tableId, { + sortBy: 'createdAt', // Default is ascending + valueFormat: 'simpleWithArrays', + useColumnNames: true, + limit: isTest ? 5 : 100, + pageToken: nextPageToken, + }); + + if (response.items) { + for (const row of response.items) { + rows.push(row); + } + } + if (isTest) break; + nextPageToken = response.nextPageToken; + } while (nextPageToken); + + return rows.map((row) => { + return { + epochMilliSeconds: dayjs(row.createdAt).valueOf(), + data: row, + }; + }); + }, +}; + +export const newRowCreatedTrigger = createTrigger({ + auth: codaAuth, + name: 'new-row-created', + displayName: 'New Row Created', + description: 'Triggers when a new row is added to the selected table.', + props: { + docId: docIdDropdown, + tableId: tableIdDropdown, + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + id: 'i-xxxxxxx', + type: 'row', + href: 'https://coda.io/apis/v1/docs/docId/tables/tableId/rows/rowId', + name: 'Sample Row Name', + index: 1, + browserLink: 'https://coda.io/d/docId/tableId#_rui-xxxxxxx', + createdAt: '2023-01-01T12:00:00.000Z', + updatedAt: '2023-01-01T12:00:00.000Z', + values: { 'c-columnId1': 'Sample Value', 'Column Name 2': 123 }, + parentTable: { + id: 'grid-parentTableId123', + type: 'table', + name: 'Parent Table Name', + href: 'https://coda.io/apis/v1/docs/docId/tables/grid-parentTableId123', + }, + }, +}); diff --git a/packages/pieces/community/coda/tsconfig.json b/packages/pieces/community/coda/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/coda/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/coda/tsconfig.lib.json b/packages/pieces/community/coda/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/coda/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/cognito-forms/.eslintrc.json b/packages/pieces/community/cognito-forms/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/cognito-forms/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/cognito-forms/README.md b/packages/pieces/community/cognito-forms/README.md new file mode 100644 index 0000000..92f2b2e --- /dev/null +++ b/packages/pieces/community/cognito-forms/README.md @@ -0,0 +1,7 @@ +# pieces-cognito-forms + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-cognito-forms` to build the library. diff --git a/packages/pieces/community/cognito-forms/package.json b/packages/pieces/community/cognito-forms/package.json new file mode 100644 index 0000000..5fc833a --- /dev/null +++ b/packages/pieces/community/cognito-forms/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-cognito-forms", + "version": "0.0.1" +} diff --git a/packages/pieces/community/cognito-forms/project.json b/packages/pieces/community/cognito-forms/project.json new file mode 100644 index 0000000..e8d69c6 --- /dev/null +++ b/packages/pieces/community/cognito-forms/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-cognito-forms", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/cognito-forms/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/cognito-forms", + "tsConfig": "packages/pieces/community/cognito-forms/tsconfig.lib.json", + "packageJson": "packages/pieces/community/cognito-forms/package.json", + "main": "packages/pieces/community/cognito-forms/src/index.ts", + "assets": [ + "packages/pieces/community/cognito-forms/*.md", + { + "input": "packages/pieces/community/cognito-forms/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/cognito-forms/src/index.ts b/packages/pieces/community/cognito-forms/src/index.ts new file mode 100644 index 0000000..4cc54e9 --- /dev/null +++ b/packages/pieces/community/cognito-forms/src/index.ts @@ -0,0 +1,59 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createEntryAction } from './lib/actions/create-entry'; +import { updateEntryAction } from './lib/actions/update-entry'; +import { deleteEntryAction } from './lib/actions/delete-entry'; +import { getEntryAction } from './lib/actions/get-entry'; +import { newEntryTrigger } from './lib/triggers/new-entry-submitted'; +import { entryUpdatedTrigger } from './lib/triggers/entry-updated'; +import { + createCustomApiCallAction, + HttpMethod, +} from '@activepieces/pieces-common'; +import { makeRequest } from './lib/common'; + +export const cognitoFormsAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: ` + 1. Click your organization's name in the top left corner and then click Settings. + 2. Go to the Integrations section and select + New API Key. + 3. Make sure to copy and store your API key, as it cannot be retrieved later. + `, + validate: async ({ auth }) => { + try { + await makeRequest(auth as string, HttpMethod.GET, '/forms'); + + return { valid: true }; + } catch { + return { + valid: false, + error: 'Invalid API Key.', + }; + } + }, +}); + +export const cognitoForms = createPiece({ + displayName: 'Cognito Forms', + auth: cognitoFormsAuth, + logoUrl: 'https://cdn.activepieces.com/pieces/cognito-forms.png', + authors: ['krushnarout'], + categories: [PieceCategory.PRODUCTIVITY, PieceCategory.FORMS_AND_SURVEYS], + actions: [ + createEntryAction, + updateEntryAction, + deleteEntryAction, + getEntryAction, + createCustomApiCallAction({ + auth: cognitoFormsAuth, + baseUrl: () => 'https://www.cognitoforms.com/api', + authMapping: async (auth) => { + return { + Authorization: `Bearer ${auth}`, + }; + }, + }), + ], + triggers: [newEntryTrigger, entryUpdatedTrigger], +}); diff --git a/packages/pieces/community/cognito-forms/src/lib/actions/create-entry.ts b/packages/pieces/community/cognito-forms/src/lib/actions/create-entry.ts new file mode 100644 index 0000000..b902c32 --- /dev/null +++ b/packages/pieces/community/cognito-forms/src/lib/actions/create-entry.ts @@ -0,0 +1,27 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common'; +import { cognitoFormsAuth } from '../../index'; +import { formFields, formIdDropdown } from '../common/props'; + +export const createEntryAction = createAction({ + auth: cognitoFormsAuth, + name: 'create_entry', + displayName: 'Create Entry', + description: 'Creates a new entry.', + props: { + formId: formIdDropdown, + entryData: formFields, + }, + async run(context) { + const apiKey = context.auth as string; + const { formId, entryData } = context.propsValue; + + return await makeRequest( + apiKey, + HttpMethod.POST, + `/forms/${formId}/entries`, + entryData + ); + }, +}); diff --git a/packages/pieces/community/cognito-forms/src/lib/actions/delete-entry.ts b/packages/pieces/community/cognito-forms/src/lib/actions/delete-entry.ts new file mode 100644 index 0000000..88f32de --- /dev/null +++ b/packages/pieces/community/cognito-forms/src/lib/actions/delete-entry.ts @@ -0,0 +1,30 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common'; +import { cognitoFormsAuth } from '../../index'; +import { formIdDropdown } from '../common/props'; + +export const deleteEntryAction = createAction({ + auth: cognitoFormsAuth, + name: 'delete_entry', + displayName: 'Delete Entry', + description: 'Deletes a specified entry.', + props: { + formId: formIdDropdown, + entryId: Property.ShortText({ + displayName: 'Entry ID', + required: true, + description: 'Enter the ID of the entry to delete.', + }), + }, + async run(context) { + const apiKey = context.auth as string; + const { formId, entryId } = context.propsValue; + + return await makeRequest( + apiKey, + HttpMethod.DELETE, + `/forms/${formId}/entries/${entryId}` + ); + }, +}); diff --git a/packages/pieces/community/cognito-forms/src/lib/actions/get-entry.ts b/packages/pieces/community/cognito-forms/src/lib/actions/get-entry.ts new file mode 100644 index 0000000..5ed5540 --- /dev/null +++ b/packages/pieces/community/cognito-forms/src/lib/actions/get-entry.ts @@ -0,0 +1,30 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common'; +import { cognitoFormsAuth } from '../../index'; +import { formIdDropdown } from '../common/props'; + +export const getEntryAction = createAction({ + auth: cognitoFormsAuth, + name: 'get_entry', + displayName: 'Get Entry', + description: 'Gets a specified entry.', + props: { + formId: formIdDropdown, + entryId: Property.ShortText({ + displayName: 'Entry ID', + required: true, + description: 'Enter the ID of the entry to retrieve.', + }), + }, + async run(context) { + const apiKey = context.auth as string; + const { formId, entryId } = context.propsValue; + + return await makeRequest( + apiKey, + HttpMethod.GET, + `/forms/${formId}/entries/${entryId}` + ); + }, +}); diff --git a/packages/pieces/community/cognito-forms/src/lib/actions/update-entry.ts b/packages/pieces/community/cognito-forms/src/lib/actions/update-entry.ts new file mode 100644 index 0000000..c1e492d --- /dev/null +++ b/packages/pieces/community/cognito-forms/src/lib/actions/update-entry.ts @@ -0,0 +1,32 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common'; +import { cognitoFormsAuth } from '../../index'; +import { formFields, formIdDropdown } from '../common/props'; + +export const updateEntryAction = createAction({ + auth: cognitoFormsAuth, + name: 'update_entry', + displayName: 'Update Entry', + description: 'Update an existing entry.', + props: { + formId: formIdDropdown, + entryId: Property.ShortText({ + displayName: 'Entry ID', + required: true, + description: 'Enter the ID of the entry you want to update.', + }), + entryData: formFields, + }, + async run(context) { + const apiKey = context.auth as string; + const { formId, entryId, entryData } = context.propsValue; + + return await makeRequest( + apiKey, + HttpMethod.PATCH, + `/forms/${formId}/entries/${entryId}`, + entryData + ); + }, +}); diff --git a/packages/pieces/community/cognito-forms/src/lib/common/index.ts b/packages/pieces/community/cognito-forms/src/lib/common/index.ts new file mode 100644 index 0000000..1b3f945 --- /dev/null +++ b/packages/pieces/community/cognito-forms/src/lib/common/index.ts @@ -0,0 +1,22 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +export const BASE_URL = 'https://www.cognitoforms.com/api'; + +export async function makeRequest( + apiKey: string, + method: HttpMethod, + path: string, + body?: unknown +) { + const response = await httpClient.sendRequest({ + method, + url: `${BASE_URL}${path}`, + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + body, + }); + + return response.body; +} diff --git a/packages/pieces/community/cognito-forms/src/lib/common/props.ts b/packages/pieces/community/cognito-forms/src/lib/common/props.ts new file mode 100644 index 0000000..fef4a69 --- /dev/null +++ b/packages/pieces/community/cognito-forms/src/lib/common/props.ts @@ -0,0 +1,104 @@ +import { + Property, + DropdownOption, + DynamicPropsValue, +} from '@activepieces/pieces-framework'; +import { makeRequest } from './index'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const formIdDropdown = Property.Dropdown({ + displayName: 'Form', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your Cognito Forms account', + options: [], + }; + } + + const apiKey = auth as string; + const forms = await makeRequest(apiKey, HttpMethod.GET, '/forms'); + + const options: DropdownOption[] = forms.map((form: any) => ({ + label: form.Name, + value: form.Id, + })); + + return { + disabled: false, + options, + }; + }, +}); + +export const formFields = Property.DynamicProperties({ + displayName: 'Fields', + refreshers: ['formId'], + required: true, + props: async ({ auth, formId }) => { + if (!auth || !formId) return {}; + + const apiKey = auth as unknown as string; + const response = await makeRequest( + apiKey, + HttpMethod.GET, + `/forms/${formId}/schema` + ); + + const fields = response as FormSchemaResponse; + + const props: DynamicPropsValue = {}; + + for (const [key, value] of Object.entries(fields.properties)) { + const fieldType = value.type; + const fieldName = key; + const fieldDes = value.description; + const isReadOnly = value.readOnly; + + if (isReadOnly) continue; + + switch (fieldType) { + case 'string': + props[fieldName] = Property.ShortText({ + displayName: fieldName, + description: fieldDes ?? '', + required: false, + }); + break; + case 'boolean': + props[fieldName] = Property.Checkbox({ + displayName: fieldName, + description: fieldDes ?? '', + + required: false, + }); + break; + case 'number': + props[fieldName] = Property.Number({ + displayName: fieldName, + description: fieldDes ?? '', + + required: false, + }); + break; + default: + break; + } + } + return props; + }, +}); + +type FormSchemaResponse = { + type: string; + properties: { + [x: string]: { + type: string; + readOnly: boolean; + description: string; + }; + }; +}; diff --git a/packages/pieces/community/cognito-forms/src/lib/triggers/entry-updated.ts b/packages/pieces/community/cognito-forms/src/lib/triggers/entry-updated.ts new file mode 100644 index 0000000..523dc4d --- /dev/null +++ b/packages/pieces/community/cognito-forms/src/lib/triggers/entry-updated.ts @@ -0,0 +1,40 @@ +import { + createTrigger, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { cognitoFormsAuth } from '../../index'; + +export const entryUpdatedTrigger = createTrigger({ + name: 'entry_updated', + displayName: 'Entry Updated', + description: 'Triggers when an existing form entry is updated.', + auth: cognitoFormsAuth, + props: { + webhookInstructions: Property.MarkDown({ + value: ` + To use this trigger, you need to manually set up a webhook in your Cognito Forms account: + + 1. Login to your Cognito Forms account. + 2. Select desired form and go to Form Settings. + 3. Enable **Post JSON Data to Website** and add following URL in **Update Entry Endpoint** field: + \`\`\`text + {{webhookUrl}} + \`\`\` + 4. Click Save to save the form changes. + `, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: undefined, + async onEnable(context) { + // No need to register webhooks programmatically as user will do it manually + }, + async onDisable(context) { + // No need to unregister webhooks as user will do it manually + }, + + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/cognito-forms/src/lib/triggers/new-entry-submitted.ts b/packages/pieces/community/cognito-forms/src/lib/triggers/new-entry-submitted.ts new file mode 100644 index 0000000..11dbb7c --- /dev/null +++ b/packages/pieces/community/cognito-forms/src/lib/triggers/new-entry-submitted.ts @@ -0,0 +1,40 @@ +import { + createTrigger, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { cognitoFormsAuth } from '../../index'; + +export const newEntryTrigger = createTrigger({ + name: 'new_entry', + displayName: 'New Entry', + description: 'Triggers when a new form entry is submitted.', + auth: cognitoFormsAuth, + props: { + webhookInstructions: Property.MarkDown({ + value: ` + To use this trigger, you need to manually set up a webhook in your Cognito Forms account: + + 1. Login to your Cognito Forms account. + 2. Select desired form and go to Form Settings. + 3. Enable **Post JSON Data to Website** and add following URL in **Submit Entry Endpoint** field: + \`\`\`text + {{webhookUrl}} + \`\`\` + 4. Click Save to save the form changes. + `, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: undefined, + async onEnable(context) { + // No need to register webhooks programmatically as user will do it manually + }, + async onDisable(context) { + // No need to unregister webhooks as user will do it manually + }, + + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/cognito-forms/tsconfig.json b/packages/pieces/community/cognito-forms/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/cognito-forms/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/cognito-forms/tsconfig.lib.json b/packages/pieces/community/cognito-forms/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/cognito-forms/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/comfyicu/.eslintrc.json b/packages/pieces/community/comfyicu/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/comfyicu/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/comfyicu/README.md b/packages/pieces/community/comfyicu/README.md new file mode 100644 index 0000000..0d195bf --- /dev/null +++ b/packages/pieces/community/comfyicu/README.md @@ -0,0 +1,7 @@ +# pieces-comfyicu + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-comfyicu` to build the library. diff --git a/packages/pieces/community/comfyicu/package.json b/packages/pieces/community/comfyicu/package.json new file mode 100644 index 0000000..493d8b7 --- /dev/null +++ b/packages/pieces/community/comfyicu/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-comfyicu", + "version": "0.0.1" +} diff --git a/packages/pieces/community/comfyicu/project.json b/packages/pieces/community/comfyicu/project.json new file mode 100644 index 0000000..a360557 --- /dev/null +++ b/packages/pieces/community/comfyicu/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-comfyicu", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/comfyicu/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/comfyicu", + "tsConfig": "packages/pieces/community/comfyicu/tsconfig.lib.json", + "packageJson": "packages/pieces/community/comfyicu/package.json", + "main": "packages/pieces/community/comfyicu/src/index.ts", + "assets": [ + "packages/pieces/community/comfyicu/*.md", + { + "input": "packages/pieces/community/comfyicu/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/comfyicu/src/index.ts b/packages/pieces/community/comfyicu/src/index.ts new file mode 100644 index 0000000..7d9295d --- /dev/null +++ b/packages/pieces/community/comfyicu/src/index.ts @@ -0,0 +1,26 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { getRunOutputAction } from './lib/actions/get-run-outputs'; +import { getRunStatusAction } from './lib/actions/get-run-status'; +import { listWorkflowsAction } from './lib/actions/list-workflows'; +import { submitWorkflowRunAction } from './lib/actions/submit-workflow-run'; +import { newWorkflowCreatedTrigger } from './lib/triggers/new-workflow-created'; +import { runCompletedTrigger } from './lib/triggers/run-completed'; +import { runFailedTrigger } from './lib/triggers/run-failed'; + +export const comfyIcuAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: `You can obtain API key from [Account Settings](https://comfy.icu/account).`, +}); + +export const comfyicu = createPiece({ + displayName: 'Comfy.ICU', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + auth: comfyIcuAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/comfyicu.png', + authors: ['rimjhimyadav'], + actions: [getRunOutputAction,getRunStatusAction,listWorkflowsAction,submitWorkflowRunAction], + triggers: [newWorkflowCreatedTrigger,runCompletedTrigger,runFailedTrigger], +}); diff --git a/packages/pieces/community/comfyicu/src/lib/actions/get-run-outputs.ts b/packages/pieces/community/comfyicu/src/lib/actions/get-run-outputs.ts new file mode 100644 index 0000000..67553a4 --- /dev/null +++ b/packages/pieces/community/comfyicu/src/lib/actions/get-run-outputs.ts @@ -0,0 +1,25 @@ +import { comfyIcuAuth } from '../../index'; +import { createAction } from '@activepieces/pieces-framework'; +import { comfyIcuApiCall, commonProps } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const getRunOutputAction = createAction({ + auth: comfyIcuAuth, + name: 'get-run-output', + displayName: 'Get Run Output', + description: 'Retrieves generated images or videos from a completed run.', + props: { + ...commonProps, + }, + async run(context) { + const { workflow_id, run_id } = context.propsValue; + const response = await comfyIcuApiCall({ + apiKey: context.auth, + endpoint: `/workflows/${workflow_id}/runs/${run_id}`, + method: HttpMethod.GET, + }); + + const runOutput = response.body as { output: Array> }; + return { result: runOutput.output }; + }, +}); diff --git a/packages/pieces/community/comfyicu/src/lib/actions/get-run-status.ts b/packages/pieces/community/comfyicu/src/lib/actions/get-run-status.ts new file mode 100644 index 0000000..94116f9 --- /dev/null +++ b/packages/pieces/community/comfyicu/src/lib/actions/get-run-status.ts @@ -0,0 +1,25 @@ +import { comfyIcuAuth } from '../../index'; +import { createAction } from '@activepieces/pieces-framework'; +import { comfyIcuApiCall, commonProps } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const getRunStatusAction = createAction({ + auth: comfyIcuAuth, + name: 'get-run-status', + displayName: 'Get Run Status', + description: 'Retrieves the status of workflow run.', + props: { + ...commonProps, + }, + async run(context) { + const { workflow_id, run_id } = context.propsValue; + const response = await comfyIcuApiCall({ + apiKey: context.auth, + endpoint: `/workflows/${workflow_id}/runs/${run_id}`, + method: HttpMethod.GET, + }); + + const runStatus = response.body as { status: string }; + return { status: runStatus.status }; + }, +}); diff --git a/packages/pieces/community/comfyicu/src/lib/actions/list-workflows.ts b/packages/pieces/community/comfyicu/src/lib/actions/list-workflows.ts new file mode 100644 index 0000000..a864cfc --- /dev/null +++ b/packages/pieces/community/comfyicu/src/lib/actions/list-workflows.ts @@ -0,0 +1,21 @@ +import { comfyIcuAuth } from '../../index'; +import { createAction } from '@activepieces/pieces-framework'; +import { comfyIcuApiCall } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const listWorkflowsAction = createAction({ + auth: comfyIcuAuth, + name: 'list-workflows', + displayName: 'List Workflows', + description: 'Retrieves available workflows for execution.', + props: {}, + async run(context) { + const response = await comfyIcuApiCall({ + apiKey: context.auth, + endpoint: '/workflows', + method: HttpMethod.GET, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/comfyicu/src/lib/actions/submit-workflow-run.ts b/packages/pieces/community/comfyicu/src/lib/actions/submit-workflow-run.ts new file mode 100644 index 0000000..d22f985 --- /dev/null +++ b/packages/pieces/community/comfyicu/src/lib/actions/submit-workflow-run.ts @@ -0,0 +1,38 @@ +import { comfyIcuAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { comfyIcuApiCall, commonProps } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const submitWorkflowRunAction = createAction({ + auth: comfyIcuAuth, + name: 'submit-workflow-run', + displayName: 'Submit Workflow Run', + description: 'Execute a workflow with specified prompt.', + props: { + workflow_id:commonProps.workflow_id, + prompt:Property.Json({ + displayName:'Prompt', + description:'You can get workflow prompt by navigating to History->Select Run -> Copy API Workflow.', + required:true + }), + webhook:Property.ShortText({ + displayName:'Webhook', + required:false, + description:'Webhook URL to recieve run status.' + }) + }, + async run(context) { + const { workflow_id, prompt,webhook } = context.propsValue; + const response = await comfyIcuApiCall({ + apiKey: context.auth, + endpoint: `/workflows/${workflow_id}/runs`, + method: HttpMethod.POST, + body:{ + prompt, + webhook + } + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/comfyicu/src/lib/common/index.ts b/packages/pieces/community/comfyicu/src/lib/common/index.ts new file mode 100644 index 0000000..802444f --- /dev/null +++ b/packages/pieces/community/comfyicu/src/lib/common/index.ts @@ -0,0 +1,76 @@ +import { + HttpMethod, + QueryParams, + httpClient, + HttpRequest, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { Property } from '@activepieces/pieces-framework'; + +export async function comfyIcuApiCall({ + apiKey, + endpoint, + method, + qparams, + body, +}: { + apiKey: string; + endpoint: string; + method: HttpMethod; + qparams?: QueryParams; + body?: any; +}) { + const request: HttpRequest = { + url: `https://comfy.icu/api/v1${endpoint}`, + method, + queryParams: qparams, + body, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: apiKey, + }, + }; + + const response = await httpClient.sendRequest(request); + return response; +} + +export const commonProps = { + workflow_id:Property.Dropdown({ + displayName:'Workflow ID', + refreshers:[], + required:true, + options:async ({auth})=>{ + if(!auth) + { + return{ + disabled:true, + options:[], + placeholder:'Please connect your account first.' + } + } + + const response = await comfyIcuApiCall({ + apiKey:auth as string, + endpoint:'/workflows', + method:HttpMethod.GET + }) + + const workflows = response.body as {id:string,name:string}[]; + + return { + disabled:false, + options:workflows.map((workflow)=>{ + return{ + label:workflow.name, + value:workflow.id + } + }) + } + } + }), + run_id:Property.ShortText({ + displayName:'Run ID', + required:true + }) +} \ No newline at end of file diff --git a/packages/pieces/community/comfyicu/src/lib/triggers/new-workflow-created.ts b/packages/pieces/community/comfyicu/src/lib/triggers/new-workflow-created.ts new file mode 100644 index 0000000..c38d130 --- /dev/null +++ b/packages/pieces/community/comfyicu/src/lib/triggers/new-workflow-created.ts @@ -0,0 +1,87 @@ +import { + DedupeStrategy, + HttpMethod, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { comfyIcuAuth } from '../../index'; +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { comfyIcuApiCall } from '../common'; +import dayjs from 'dayjs'; + +export const newWorkflowCreatedTrigger = createTrigger({ + auth: comfyIcuAuth, + name: 'new-workflow-created', + displayName: 'New Workflow Created', + description: 'Triggers when a new workflow is created.', + type: TriggerStrategy.POLLING, + props: {}, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + id: 'RKhLj7NZrWL7Yhk_lzEf7', + name: 'Test Flow', + description: null, + created_at: '2025-05-23T14:34:16.610Z', + updated_at: '2025-05-23T14:35:11.052Z', + tags: [], + is_nsfw: false, + visibility: 'PUBLIC', + user_id: 'xyz', + current_run_id: 'H8fwBSWw_SJtn4hrfbsbb', + models: null, + accelerator: null, + project_id: 12297, + deleted_at: null, + featuredImages: [], + }, +}); + +const polling: Polling< + PiecePropValueSchema, + Record +> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth }) { + const response = await comfyIcuApiCall({ + apiKey: auth, + endpoint: '/workflows', + method: HttpMethod.GET, + }); + + const workflows = response.body as { + id: string; + name: string; + created_at: string; + }[]; + + return workflows.map((workflow) => { + return { + epochMilliSeconds: dayjs(workflow.created_at).valueOf(), + data: workflow, + }; + }); + }, +}; diff --git a/packages/pieces/community/comfyicu/src/lib/triggers/run-completed.ts b/packages/pieces/community/comfyicu/src/lib/triggers/run-completed.ts new file mode 100644 index 0000000..be3ef3e --- /dev/null +++ b/packages/pieces/community/comfyicu/src/lib/triggers/run-completed.ts @@ -0,0 +1,493 @@ +import { comfyIcuAuth } from '../../index'; +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { comfyIcuApiCall, commonProps } from '../common'; +import { + DedupeStrategy, + HttpMethod, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import dayjs from 'dayjs'; + +const polling: Polling< + PiecePropValueSchema, + { workflow_id: string } +> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const { workflow_id } = propsValue; + const listRunsResponse = await comfyIcuApiCall({ + apiKey: auth, + endpoint: `/workflows/${workflow_id}/runs`, + method: HttpMethod.GET, + }); + + const runs = listRunsResponse.body as { + id: string; + status: string; + created_at: string; + }[]; + + const result = []; + + for (const run of runs) { + if (run.status === 'COMPLETED') { + const runDetailsResponse = await comfyIcuApiCall({ + apiKey: auth, + endpoint: `/workflows/${workflow_id}/runs/${run.id}`, + method: HttpMethod.GET, + }); + const runDetail = runDetailsResponse.body as { created_at: string }; + result.push(runDetail); + } + if (lastFetchEpochMS === 0 && result.length === 5) break; + } + + return result.map((run) => { + return { + epochMilliSeconds: dayjs(run.created_at).valueOf(), + data: run, + }; + }); + }, +}; + +export const runCompletedTrigger = createTrigger({ + auth: comfyIcuAuth, + name: 'run-completed', + displayName: 'Run Completed', + description: 'Triggers when a workflow run is successfully completed.', + props: { + workflow_id: commonProps.workflow_id, + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + id: 'CNCr5a_lu6g9Nz3gqKC9P', + created_at: '2025-05-23T15:08:18.297Z', + updated_at: '2025-05-23T15:08:27.476Z', + deleted_at: null, + started_at: '2025-05-23T15:08:19.760Z', + completed_at: '2025-05-23T15:08:27.384Z', + run_time: 6357, + status: 'COMPLETED', + kind: 'comfyui', + data: { + prompt: { + '3': { + _meta: { + title: 'KSampler', + }, + inputs: { + cfg: 8, + seed: 253353381004563, + model: ['4', 0], + steps: 20, + denoise: 1, + negative: ['7', 0], + positive: ['6', 0], + scheduler: 'normal', + latent_image: ['5', 0], + sampler_name: 'euler', + }, + class_type: 'KSampler', + }, + '4': { + _meta: { + title: 'Load Checkpoint', + }, + inputs: { + ckpt_name: 'v1-5-pruned-emaonly-fp16.safetensors', + }, + class_type: 'CheckpointLoaderSimple', + }, + '5': { + _meta: { + title: 'Empty Latent Image', + }, + inputs: { + width: 512, + height: 512, + batch_size: 1, + }, + class_type: 'EmptyLatentImage', + }, + '6': { + _meta: { + title: 'CLIP Text Encode (Prompt)', + }, + inputs: { + clip: ['4', 1], + text: 'beautiful scenery nature glass bottle landscape, , purple galaxy bottle,', + }, + class_type: 'CLIPTextEncode', + }, + '7': { + _meta: { + title: 'CLIP Text Encode (Prompt)', + }, + inputs: { + clip: ['4', 1], + text: 'text, watermark', + }, + class_type: 'CLIPTextEncode', + }, + '8': { + _meta: { + title: 'VAE Decode', + }, + inputs: { + vae: ['4', 2], + samples: ['3', 0], + }, + class_type: 'VAEDecode', + }, + '9': { + _meta: { + title: 'Save Image', + }, + inputs: { + images: ['8', 0], + filename_prefix: 'ComfyUI', + }, + class_type: 'SaveImage', + }, + }, + extra_data: { + extra_pnginfo: { + workflow: { + extra: { + ds: { + scale: 0.45, + offset: [], + }, + ue_links: [], + VHS_MetadataImage: true, + VHS_latentpreview: false, + VHS_KeepIntermediate: true, + VHS_latentpreviewrate: 0, + }, + links: [ + [1, 4, 0, 3, 0, 'MODEL'], + [2, 5, 0, 3, 3, 'LATENT'], + [3, 4, 1, 6, 0, 'CLIP'], + [4, 6, 0, 3, 1, 'CONDITIONING'], + [5, 4, 1, 7, 0, 'CLIP'], + [6, 7, 0, 3, 2, 'CONDITIONING'], + [7, 3, 0, 8, 0, 'LATENT'], + [8, 4, 2, 8, 1, 'VAE'], + [9, 8, 0, 9, 0, 'IMAGE'], + ], + nodes: [ + { + id: 7, + pos: [413, 389], + mode: 0, + size: [], + type: 'CLIPTextEncode', + flags: {}, + order: 3, + inputs: [ + { + link: 5, + name: 'clip', + type: 'CLIP', + }, + ], + outputs: [ + { + name: 'CONDITIONING', + type: 'CONDITIONING', + links: [6], + slot_index: 0, + }, + ], + properties: { + 'Node name for S&R': 'CLIPTextEncode', + }, + widgets_values: ['text, watermark'], + }, + { + id: 5, + pos: [473, 609], + mode: 0, + size: [315, 106], + type: 'EmptyLatentImage', + flags: {}, + order: 0, + inputs: [], + outputs: [ + { + name: 'LATENT', + type: 'LATENT', + links: [2], + slot_index: 0, + }, + ], + properties: { + 'Node name for S&R': 'EmptyLatentImage', + }, + widgets_values: [512, 512, 1], + }, + { + id: 8, + pos: [1209, 188], + mode: 0, + size: [210, 46], + type: 'VAEDecode', + flags: {}, + order: 5, + inputs: [ + { + link: 7, + name: 'samples', + type: 'LATENT', + }, + { + link: 8, + name: 'vae', + type: 'VAE', + }, + ], + outputs: [ + { + name: 'IMAGE', + type: 'IMAGE', + links: [9], + slot_index: 0, + }, + ], + properties: { + 'Node name for S&R': 'VAEDecode', + }, + widgets_values: [], + }, + { + id: 9, + pos: [1451, 189], + mode: 0, + size: [210, 270], + type: 'SaveImage', + flags: {}, + order: 6, + inputs: [ + { + link: 9, + name: 'images', + type: 'IMAGE', + }, + ], + outputs: [], + properties: { + 'Node name for S&R': 'SaveImage', + }, + widgets_values: ['ComfyUI'], + }, + { + id: 3, + pos: [], + mode: 0, + size: [315, 262], + type: 'KSampler', + flags: {}, + order: 4, + inputs: [ + { + link: 1, + name: 'model', + type: 'MODEL', + }, + { + link: 4, + name: 'positive', + type: 'CONDITIONING', + }, + { + link: 6, + name: 'negative', + type: 'CONDITIONING', + }, + { + link: 2, + name: 'latent_image', + type: 'LATENT', + }, + ], + outputs: [ + { + name: 'LATENT', + type: 'LATENT', + links: [7], + slot_index: 0, + }, + ], + properties: { + 'Node name for S&R': 'KSampler', + }, + widgets_values: [ + 253353381004563, + 'randomize', + 20, + 8, + 'euler', + 'normal', + 1, + ], + }, + { + id: 4, + pos: [], + mode: 0, + size: [315, 310], + type: 'CheckpointLoaderSimple', + flags: {}, + order: 1, + inputs: [], + outputs: [ + { + name: 'MODEL', + type: 'MODEL', + links: [1], + slot_index: 0, + }, + { + name: 'CLIP', + type: 'CLIP', + links: [3, 5], + slot_index: 1, + }, + { + name: 'VAE', + type: 'VAE', + links: [8], + slot_index: 2, + }, + ], + properties: { + 'Node name for S&R': 'CheckpointLoaderSimple', + }, + widgets_values: ['v1-5-pruned-emaonly-fp16.safetensors'], + }, + { + id: 6, + pos: [415, 186], + mode: 0, + size: [422.8450317382812, 164.3130493164062], + type: 'CLIPTextEncode', + flags: {}, + order: 2, + inputs: [ + { + link: 3, + name: 'clip', + type: 'CLIP', + }, + ], + outputs: [ + { + name: 'CONDITIONING', + type: 'CONDITIONING', + links: [4], + slot_index: 0, + }, + ], + properties: { + 'Node name for S&R': 'CLIPTextEncode', + }, + widgets_values: [ + 'beautiful scenery nature glass bottle landscape, , purple galaxy bottle,', + ], + }, + ], + config: {}, + groups: [], + version: 0.4, + last_link_id: 9, + last_node_id: 9, + }, + }, + }, + accelerator: 'L4', + }, + workflow: null, + workflow_api: null, + workflow_id: 'xyz', + output: [ + { + filename: + '/workflows/xyz/output/xyz/ComfyUI_00001_.png', + url: 'https://r2.comfy.icu/workflows/xyz/output/CNCr5a_lu6g9Nz3gqKC9P/ComfyUI_00001_.png', + thumbnail_url: + 'https://img.comfy.icu/sig/width:300/quality:85/xyz', + }, + ], + error: null, + files: {}, + name: null, + tags: [], + view_count: 0, + download_count: 0, + is_nsfw: false, + user_id: '6VbYatQkJjualbDX84D2j', + device: 'NVIDIA L4', + accelerator: 'L4', + visibility: 'PUBLIC', + parent_workflow_id: null, + parent_run_id: null, + api_key_id: null, + retry_count: 1, + client_agent: null, + webhook: null, + project_id: 12297, + user: { + id: '6VbYatQkJjualbDX84D2j', + name: 'John Doe', + image: '', + plan: 'FREE', + }, + metadata: { + extensions: ['comfyanonymous__ComfyUI'], + nodes: [ + 'CLIPTextEncode', + 'EmptyLatentImage', + 'VAEDecode', + 'SaveImage', + 'KSampler', + 'CheckpointLoaderSimple', + ], + checkpoints: ['v1-5-pruned-emaonly-fp16.safetensors'], + notes: [ + { + text: 'text, watermark', + }, + { + text: 'beautiful scenery nature glass bottle landscape, , purple galaxy bottle,', + }, + ], + groups: [], + }, + }, +}); diff --git a/packages/pieces/community/comfyicu/src/lib/triggers/run-failed.ts b/packages/pieces/community/comfyicu/src/lib/triggers/run-failed.ts new file mode 100644 index 0000000..27da8d2 --- /dev/null +++ b/packages/pieces/community/comfyicu/src/lib/triggers/run-failed.ts @@ -0,0 +1,247 @@ +import { comfyIcuAuth } from '../../index'; +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { comfyIcuApiCall, commonProps } from '../common'; +import { + DedupeStrategy, + HttpMethod, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import dayjs from 'dayjs'; + +const polling: Polling< + PiecePropValueSchema, + { workflow_id: string } +> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const { workflow_id } = propsValue; + const listRunsResponse = await comfyIcuApiCall({ + apiKey: auth, + endpoint: `/workflows/${workflow_id}/runs`, + method: HttpMethod.GET, + }); + + const runs = listRunsResponse.body as { + id: string; + status: string; + created_at: string; + }[]; + + const result = []; + + for (const run of runs) { + if (run.status === 'ERROR') { + const runDetailsResponse = await comfyIcuApiCall({ + apiKey: auth, + endpoint: `/workflows/${workflow_id}/runs/${run.id}`, + method: HttpMethod.GET, + }); + const runDetail = runDetailsResponse.body as { created_at: string }; + result.push(runDetail); + + if (lastFetchEpochMS === 0 && result.length === 5) break; + } + } + + return result.map((run) => { + return { + epochMilliSeconds: dayjs(run.created_at).valueOf(), + data: run, + }; + }); + }, +}; + +export const runFailedTrigger = createTrigger({ + auth: comfyIcuAuth, + name: 'run-failed', + displayName: 'Run Failed', + description: 'Triggers when a workflow run is failed.', + props: { + workflow_id: commonProps.workflow_id, + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + id: 'sve-GBc89WPLPncm4laYG', + created_at: '2025-05-23T15:09:44.273Z', + updated_at: '2025-05-23T15:09:48.128Z', + deleted_at: null, + started_at: '2025-05-23T15:09:45.700Z', + completed_at: '2025-05-23T15:09:47.997Z', + run_time: 3, + status: 'ERROR', + kind: 'comfyui', + data: { + prompt: { + '3': { + _meta: { + title: 'KSampler', + }, + inputs: { + cfg: 8, + seed: 156680208700286, + model: ['4', 0], + steps: 20, + denoise: 1, + negative: ['7', 0], + positive: ['6', 0], + scheduler: 'normal', + latent_image: ['5', 0], + sampler_name: 'euler', + }, + class_type: 'KSampler', + }, + '4': { + _meta: { + title: 'Load Checkpoint', + }, + inputs: { + ckpt_name: 'v1-5-pruned-emaonly-fp16.safetensors', + }, + class_type: 'CheckpointLoaderSimple', + }, + '5': { + _meta: { + title: 'Empty Latent Image', + }, + inputs: { + width: 512, + height: 512, + batch_size: 1, + }, + class_type: 'EmptyLatentImage', + }, + '6': { + _meta: { + title: 'CLIP Text Encode (Prompt)', + }, + inputs: { + clip: ['4', 1], + text: 'beautiful scenery nature glass bottle landscape, , purple galaxy bottle,', + }, + class_type: 'CLIPTextEncode', + }, + '7': { + _meta: { + title: 'CLIP Text Encode (Prompt)', + }, + inputs: { + clip: ['4', 1], + text: 'text, watermark', + }, + class_type: 'xx', + }, + '8': { + _meta: { + title: 'VAE Decode', + }, + inputs: { + vae: ['4', 2], + samples: ['3', 0], + }, + class_type: 'VAEDecode', + }, + '9': { + _meta: { + title: 'Save Image', + }, + inputs: { + images: ['8', 0], + filename_prefix: 'ComfyUI', + }, + class_type: 'SaveImage', + }, + }, + webhook: '', + }, + workflow: null, + workflow_api: null, + workflow_id: '46AnCzsekAoAXLZqVQUXH', + output: { + error: { + details: { + error: { + type: 'invalid_prompt', + details: "Node ID '#7'", + message: 'Cannot execute because node xx does not exist.', + extra_info: {}, + }, + node_errors: {}, + }, + prompt_id: 'sve-GBc89WPLPncm4laYG', + traceback: [ + ' File "/app/headless.py", line 771, in run_workflow\n prompt_id = self.queue_prompt(wf["prompt"])\n', + ' File "/app/headless.py", line 421, in queue_prompt\n raise RuntimeError(output)\n', + ], + exception_type: 'invalid_prompt', + exception_message: + "Cannot execute because node xx does not exist.: Node ID '#7'", + }, + output: { + error: { + details: { + error: { + type: 'invalid_prompt', + details: "Node ID '#7'", + message: 'Cannot execute because node xx does not exist.', + extra_info: {}, + }, + node_errors: {}, + }, + prompt_id: 'sve-GBc89WPLPncm4laYG', + traceback: [ + ' File "/app/headless.py", line 771, in run_workflow\n prompt_id = self.queue_prompt(wf["prompt"])\n', + ' File "/app/headless.py", line 421, in queue_prompt\n raise RuntimeError(output)\n', + ], + exception_type: 'invalid_prompt', + exception_message: + "Cannot execute because node xx does not exist.: Node ID '#7'", + }, + }, + }, + error: null, + files: {}, + name: null, + tags: [], + view_count: 0, + download_count: 0, + is_nsfw: false, + user_id: '6VbYatQkJjualbDX84D2j', + device: 'NVIDIA L4', + accelerator: 'L4', + visibility: 'PUBLIC', + parent_workflow_id: null, + parent_run_id: null, + api_key_id: 'GM2FYwGmgiNpXTNDqT6MT', + retry_count: 1, + client_agent: null, + webhook: '', + project_id: 12297, + user: null, + }, +}); diff --git a/packages/pieces/community/comfyicu/tsconfig.json b/packages/pieces/community/comfyicu/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/comfyicu/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/comfyicu/tsconfig.lib.json b/packages/pieces/community/comfyicu/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/comfyicu/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/common/.eslintrc.json b/packages/pieces/community/common/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/common/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/common/README.md b/packages/pieces/community/common/README.md new file mode 100644 index 0000000..05c7139 --- /dev/null +++ b/packages/pieces/community/common/README.md @@ -0,0 +1,7 @@ +# pieces-common + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-common` to build the library. diff --git a/packages/pieces/community/common/package.json b/packages/pieces/community/common/package.json new file mode 100644 index 0000000..f33e5de --- /dev/null +++ b/packages/pieces/community/common/package.json @@ -0,0 +1,5 @@ +{ + "name": "@activepieces/pieces-common", + "version": "0.5.2", + "type": "commonjs" +} diff --git a/packages/pieces/community/common/project.json b/packages/pieces/community/common/project.json new file mode 100644 index 0000000..5cc8af9 --- /dev/null +++ b/packages/pieces/community/common/project.json @@ -0,0 +1,25 @@ +{ + "name": "pieces-common", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/common/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/packages/pieces/community/common", + "main": "packages/pieces/community/common/src/index.ts", + "tsConfig": "packages/pieces/community/common/tsconfig.lib.json", + "assets": ["packages/pieces/community/common/*.md"], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + } + }, + "tags": [] +} diff --git a/packages/pieces/community/common/src/index.ts b/packages/pieces/community/common/src/index.ts new file mode 100644 index 0000000..6246663 --- /dev/null +++ b/packages/pieces/community/common/src/index.ts @@ -0,0 +1,6 @@ +export * from './lib/authentication'; +export * from './lib/helpers'; +export * from './lib/http'; +export * from './lib/polling'; +export * from './lib/ai'; +export * from './lib/validation'; diff --git a/packages/pieces/community/common/src/lib/ai/index.ts b/packages/pieces/community/common/src/lib/ai/index.ts new file mode 100644 index 0000000..d0f5a1c --- /dev/null +++ b/packages/pieces/community/common/src/lib/ai/index.ts @@ -0,0 +1,154 @@ +import { isNil, SeekPage, AIProviderWithoutSensitiveData, SUPPORTED_AI_PROVIDERS, SupportedAIProvider } from '@activepieces/shared'; +import { Property, InputPropertyMap } from "@activepieces/pieces-framework"; +import { httpClient, HttpMethod } from '../http'; + +export const aiProps = ({ modelType, functionCalling }: AIPropsParams): AIPropsReturn => ({ + provider: Property.Dropdown({ + displayName: 'Provider', + required: true, + refreshers: [], + options: async (_, ctx) => { + const { body: { data: supportedProviders } } = await httpClient.sendRequest< + SeekPage + >({ + method: HttpMethod.GET, + url: `${ctx.server.apiUrl}v1/ai-providers`, + headers: { + Authorization: `Bearer ${ctx.server.token}`, + }, + }); + if (supportedProviders.length === 0) { + return { + disabled: true, + options: [], + placeholder: 'No AI providers configured by the admin.', + }; + } + + const providers = supportedProviders.map(supportedProvider => { + const provider = SUPPORTED_AI_PROVIDERS.find(p => p.provider === supportedProvider.provider); + if (!provider) return null; + + if (modelType === 'language') { + if (provider.languageModels.length === 0) return null; + + if (functionCalling && !provider.languageModels.some(model => model.functionCalling)) { + return null; + } + } else if (modelType === 'image') { + if (provider.imageModels.length === 0) return null; + } + + return { + value: provider.provider, + label: provider.displayName + }; + }); + + const filteredProviders = providers.filter(p => p !== null); + + return { + placeholder: filteredProviders.length > 0 ? 'Select AI Provider' : `No providers available for ${modelType} models${functionCalling ? ' with function calling' : ''}`, + disabled: filteredProviders.length === 0, + options: filteredProviders, + }; + }, + }), + model: Property.Dropdown({ + displayName: 'Model', + required: true, + defaultValue: 'gpt-4o', + refreshers: ['provider'], + options: async (propsValue) => { + const provider = propsValue['provider'] as string; + if (isNil(provider)) { + return { + disabled: true, + options: [], + placeholder: 'Select AI Provider', + }; + } + + const supportedProvider = SUPPORTED_AI_PROVIDERS.find(p => p.provider === provider); + if (isNil(supportedProvider)) { + return { + disabled: true, + options: [], + }; + } + + const allModels = modelType === 'language' ? supportedProvider.languageModels : supportedProvider.imageModels; + const models = (modelType === 'language' && functionCalling) + ? allModels.filter(model => (model as SupportedAIProvider['languageModels'][number]).functionCalling) + : allModels; + + return { + placeholder: 'Select AI Model', + disabled: false, + options: models.map(model => ({ + label: model.displayName, + value: model.instance, + })), + }; + }, + }), + advancedOptions: Property.DynamicProperties({ + displayName: 'Advanced Options', + required: false, + refreshers: ['provider', 'model'], + props: async (propsValue): Promise => { + const provider = propsValue['provider'] as unknown as string; + + const providerMetadata = SUPPORTED_AI_PROVIDERS.find(p => p.provider === provider); + if (isNil(providerMetadata)) { + return {}; + } + + if (modelType === 'image') { + if (provider === 'openai') { + return { + quality: Property.StaticDropdown({ + options: { + options: [ + { label: 'Standard', value: 'standard' }, + { label: 'HD', value: 'hd' }, + ], + disabled: false, + placeholder: 'Select Image Quality', + }, + defaultValue: 'standard', + description: + 'Standard images are less detailed and faster to generate, while HD images are more detailed but slower to generate.', + displayName: 'Image Quality', + required: true, + }), + }; + } + + if (provider === 'replicate') { + return { + negativePrompt: Property.ShortText({ + displayName: 'Negative Prompt', + required: true, + description: 'A prompt to avoid in the generated image.', + }), + }; + } + } + + return {}; + }, + }) +}) + + +type AIPropsParams = { + modelType: T, + functionCalling?: T extends 'image' ? never : boolean +} + +type AIPropsReturn = { + provider: ReturnType>; + model: ReturnType; + advancedOptions: ReturnType; +} diff --git a/packages/pieces/community/common/src/lib/authentication/index.ts b/packages/pieces/community/common/src/lib/authentication/index.ts new file mode 100644 index 0000000..78403d1 --- /dev/null +++ b/packages/pieces/community/common/src/lib/authentication/index.ts @@ -0,0 +1,21 @@ +export type Authentication = BearerTokenAuthentication | BasicAuthentication; + +export enum AuthenticationType { + BEARER_TOKEN = 'BEARER_TOKEN', + BASIC = 'BASIC', +} + +export type BaseAuthentication = { + type: T; +}; + +export type BearerTokenAuthentication = + BaseAuthentication & { + token: string; + }; + +export type BasicAuthentication = + BaseAuthentication & { + username: string; + password: string; + }; diff --git a/packages/pieces/community/common/src/lib/helpers/index.ts b/packages/pieces/community/common/src/lib/helpers/index.ts new file mode 100644 index 0000000..e82f54f --- /dev/null +++ b/packages/pieces/community/common/src/lib/helpers/index.ts @@ -0,0 +1,247 @@ +import { + OAuth2PropertyValue, + PieceAuthProperty, + Property, + StaticDropdownProperty, + createAction, + StaticPropsValue, + InputPropertyMap, +} from '@activepieces/pieces-framework'; +import { + HttpError, + HttpHeaders, + HttpMethod, + HttpRequest, + QueryParams, + httpClient, +} from '../http'; +import { assertNotNullOrUndefined } from '@activepieces/shared'; +import fs from 'fs'; +import mime from 'mime-types'; + +export const getAccessTokenOrThrow = ( + auth: OAuth2PropertyValue | undefined +): string => { + const accessToken = auth?.access_token; + + if (accessToken === undefined) { + throw new Error('Invalid bearer token'); + } + + return accessToken; +}; +const joinBaseUrlWithRelativePath = ({ baseUrl, relativePath }: { baseUrl: string, relativePath: string }) => { + const baseUrlWithSlash = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/` + const relativePathWithoutSlash = relativePath.startsWith('/') ? relativePath.slice(1) : relativePath + return `${baseUrlWithSlash}${relativePathWithoutSlash}` + } + + +const getBaseUrlForDescription = (baseUrl: (auth?: unknown) => string,auth?: unknown) => { + const exampleBaseUrl = `https://api.example.com` + try { + const baseUrlValue = auth ? baseUrl(auth) : undefined; + const baseUrlValueWithoutTrailingSlash = baseUrlValue?.endsWith('/') ? baseUrlValue.slice(0, -1) : baseUrlValue + return baseUrlValueWithoutTrailingSlash ?? exampleBaseUrl + } + //If baseUrl fails we stil want to return a valid baseUrl for description + catch (error) { + { + return exampleBaseUrl + } +} +} +export function createCustomApiCallAction({ + auth, + baseUrl, + authMapping, + description, + displayName, + name, + props, + extraProps, +}: { + auth?: PieceAuthProperty; + baseUrl: (auth?: unknown) => string; + authMapping?: ( + auth: unknown, + propsValue: StaticPropsValue + ) => Promise; + // add description as a parameter that can be null + description?: string | null; + displayName?: string | null; + name?: string | null; + props?: { + url?: Partial>; + method?: Partial>; + headers?: Partial>; + queryParams?: Partial>; + body?: Partial>; + failsafe?: Partial>; + timeout?: Partial>; + }; + extraProps?: InputPropertyMap; +}) { + + return createAction({ + name: name ? name : 'custom_api_call', + displayName: displayName ? displayName : 'Custom API Call', + description: description + ? description + : 'Make a custom API call to a specific endpoint', + auth: auth ? auth : undefined, + requireAuth: auth ? true : false, + props: { + url: Property.DynamicProperties({ + displayName: '', + required: true, + refreshers: [], + props: async ({ auth }) => { + return { + url: Property.ShortText({ + displayName: 'URL', + description: `You can either use the full URL or the relative path to the base URL +i.e ${getBaseUrlForDescription(baseUrl,auth)}/resource or /resource`, + required: true, + defaultValue: baseUrl(auth), + ...(props?.url ?? {}), + }), + }; + }, + }), + method: Property.StaticDropdown({ + displayName: 'Method', + required: true, + options: { + options: Object.values(HttpMethod).map((v) => { + return { + label: v, + value: v, + }; + }), + }, + ...(props?.method ?? {}), + }), + headers: Property.Object({ + displayName: 'Headers', + description: + 'Authorization headers are injected automatically from your connection.', + required: true, + ...(props?.headers ?? {}), + }), + queryParams: Property.Object({ + displayName: 'Query Parameters', + required: true, + ...(props?.queryParams ?? {}), + }), + body: Property.Json({ + displayName: 'Body', + required: false, + ...(props?.body ?? {}), + }), + failsafe: Property.Checkbox({ + displayName: 'No Error on Failure', + required: false, + ...(props?.failsafe ?? {}), + }), + timeout: Property.Number({ + displayName: 'Timeout (in seconds)', + required: false, + ...(props?.timeout ?? {}), + }), + ...extraProps, + }, + + run: async (context) => { + const { method, url, headers, queryParams, body, failsafe, timeout } = + context.propsValue; + + assertNotNullOrUndefined(method, 'Method'); + assertNotNullOrUndefined(url, 'URL'); + + let headersValue = headers as HttpHeaders; + if (authMapping) { + const headers = await authMapping(context.auth, context.propsValue); + if (headers) { + headersValue = { + ...headersValue, + ...headers, + }; + } + } + const urlValue = url['url'] as string; + const fullUrl = urlValue.startsWith('http://') || urlValue.startsWith('https://') ? urlValue : + joinBaseUrlWithRelativePath({ baseUrl: baseUrl(context.auth), relativePath: urlValue}) + const request: HttpRequest> = { + method, + url: fullUrl, + headers: headersValue, + queryParams: queryParams as QueryParams, + timeout: timeout ? timeout * 1000 : 0, + responseType: 'arraybuffer' + }; + + if (body) { + request.body = body; + } + + const objectContentTypes = [ + 'application/json', // JSON responses + 'application/xml', // XML responses + 'text/plain', // Plain text responses + 'text/html', // HTML responses + 'application/x-www-form-urlencoded', // Form submissions + ]; + + const objectContentTypeSuffixes = ['+json', '+xml']; + + let response; + try { + response = await httpClient.sendRequest(request); + } catch (error) { + if (failsafe) { + return (error as HttpError).errorMessage(); + } + throw error; + } + + // Capture content type from header + const contentTypeValue = Array.isArray(response.headers?.['content-type']) + ? response.headers['content-type'][0] + : response.headers?.['content-type'] + + // Return unaltered response if content type is associated with objects or strings + const isObjectContentType = objectContentTypes.some(type => (contentTypeValue ?? '').includes(type)) || + objectContentTypeSuffixes.some(suffix => (contentTypeValue ?? '').endsWith(suffix)); + + if (isObjectContentType) { + try { + // Parse JSON responses if valid + response.body = JSON.parse(response.body || '{}'); + } catch (err) { + // Fall back to returning plain text if JSON parsing fails + response.body = response.body?.toString() || ''; + } + + return response + } + + // Get file extension from content type + const fileExtension: string = mime.extension(contentTypeValue ?? '') || 'txt' + + // Return response as file + return { + ...response, + body: await context.files.write({ + fileName: `output.${fileExtension}`, + data: Buffer.from(response.body) + }) + } + }, + }); +} + +export function is_chromium_installed(): boolean { + const chromiumPath = '/usr/bin/chromium' + return fs.existsSync(chromiumPath) +} \ No newline at end of file diff --git a/packages/pieces/community/common/src/lib/http/axios/axios-http-client.ts b/packages/pieces/community/common/src/lib/http/axios/axios-http-client.ts new file mode 100644 index 0000000..810134a --- /dev/null +++ b/packages/pieces/community/common/src/lib/http/axios/axios-http-client.ts @@ -0,0 +1,86 @@ +import axios, { AxiosRequestConfig } from 'axios'; +import axiosRetry from 'axios-retry'; +import { DelegatingAuthenticationConverter } from '../core/delegating-authentication-converter'; +import { BaseHttpClient } from '../core/base-http-client'; +import { HttpError } from '../core/http-error'; +import { HttpHeaders } from '../core/http-headers'; +import { HttpMessageBody } from '../core/http-message-body'; +import { HttpMethod } from '../core/http-method'; +import { HttpRequest } from '../core/http-request'; +import { HttpResponse } from '../core/http-response'; +import { HttpRequestBody } from '../core/http-request-body'; + +export class AxiosHttpClient extends BaseHttpClient { + constructor( + baseUrl = '', + authenticationConverter: DelegatingAuthenticationConverter = new DelegatingAuthenticationConverter() + ) { + super(baseUrl, authenticationConverter); + } + + async sendRequest( + request: HttpRequest + ): Promise> { + try { + process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; + const { urlWithoutQueryParams, queryParams: urlQueryParams } = this.getUrl(request); + const headers = this.getHeaders(request); + const axiosRequestMethod = this.getAxiosRequestMethod(request.method); + const timeout = request.timeout ? request.timeout : 0; + const queryParams = request.queryParams || {} + const responseType = request.responseType || 'json'; + + for (const [key, value] of Object.entries(queryParams)) { + urlQueryParams.append(key, value); + } + + const config: AxiosRequestConfig = { + method: axiosRequestMethod, + url: urlWithoutQueryParams, + params: urlQueryParams, + headers, + data: request.body, + timeout, + responseType, + }; + + if (request.retries && request.retries > 0) { + axiosRetry(axios, { + retries: request.retries, + retryDelay: axiosRetry.exponentialDelay, + retryCondition: (error) => { + return axiosRetry.isNetworkOrIdempotentRequestError(error) || (error.response && error.response.status >= 500) || false; + }, + }); + } + + const response = await axios.request(config); + + return { + status: response.status, + headers: response.headers as HttpHeaders, + body: response.data, + }; + } catch (e) { + console.error('[HttpClient#sendRequest] error:', e); + if (axios.isAxiosError(e)) { + console.error( + '[HttpClient#sendRequest] error, responseStatus:', + e.response?.status + ); + console.error( + '[HttpClient#sendRequest] error, responseBody:', + JSON.stringify(e.response?.data) + ); + + throw new HttpError(request.body, e); + } + + throw e; + } + } + + private getAxiosRequestMethod(httpMethod: HttpMethod): string { + return httpMethod.toString(); + } +} diff --git a/packages/pieces/community/common/src/lib/http/core/base-http-client.ts b/packages/pieces/community/common/src/lib/http/core/base-http-client.ts new file mode 100644 index 0000000..4d70440 --- /dev/null +++ b/packages/pieces/community/common/src/lib/http/core/base-http-client.ts @@ -0,0 +1,76 @@ +import { Authentication } from '../../authentication'; +import { DelegatingAuthenticationConverter } from './delegating-authentication-converter'; +import type { HttpClient } from './http-client'; +import { HttpHeader } from './http-header'; +import type { HttpHeaders } from './http-headers'; +import type { HttpMessageBody } from './http-message-body'; +import type { HttpRequest } from './http-request'; +import { HttpRequestBody } from './http-request-body'; +import { HttpResponse } from './http-response'; +import { MediaType } from './media-type'; + +export abstract class BaseHttpClient implements HttpClient { + constructor( + private readonly baseUrl: string, + private readonly authenticationConverter: DelegatingAuthenticationConverter + ) {} + + abstract sendRequest< + RequestBody extends HttpMessageBody, + ResponseBody extends HttpMessageBody + >(request: HttpRequest): Promise>; + + protected getUrl( + request: HttpRequest + ): { + urlWithoutQueryParams: string; + queryParams: URLSearchParams; + } { + const url = new URL(`${this.baseUrl}${request.url}`); + const urlWithoutQueryParams = `${url.origin}${url.pathname}`; + const queryParams = new URLSearchParams(); + // Extract query parameters + url.searchParams.forEach((value, key) => { + queryParams.append(key, value); + }); + return { + urlWithoutQueryParams, + queryParams, + }; + } + + protected getHeaders( + request: HttpRequest + ): HttpHeaders { + let requestHeaders: HttpHeaders = { + [HttpHeader.ACCEPT]: MediaType.APPLICATION_JSON, + }; + + if (request.authentication) { + this.populateAuthentication(request.authentication, requestHeaders); + } + + if (request.body) { + switch (request.headers?.['Content-Type']) { + case 'text/csv': + requestHeaders[HttpHeader.CONTENT_TYPE] = MediaType.TEXT_CSV; + break; + + default: + requestHeaders[HttpHeader.CONTENT_TYPE] = MediaType.APPLICATION_JSON; + break; + } + } + if (request.headers) { + requestHeaders = { ...requestHeaders, ...request.headers }; + } + return requestHeaders; + } + + private populateAuthentication( + authentication: Authentication, + headers: HttpHeaders + ): void { + this.authenticationConverter.convert(authentication, headers); + } +} diff --git a/packages/pieces/community/common/src/lib/http/core/delegating-authentication-converter.ts b/packages/pieces/community/common/src/lib/http/core/delegating-authentication-converter.ts new file mode 100644 index 0000000..5ed3ebe --- /dev/null +++ b/packages/pieces/community/common/src/lib/http/core/delegating-authentication-converter.ts @@ -0,0 +1,62 @@ +import type { HttpHeaders } from './http-headers'; +import { HttpHeader } from './http-header'; +import { + Authentication, + AuthenticationType, + BasicAuthentication, + BearerTokenAuthentication, +} from '../../authentication'; + +export class DelegatingAuthenticationConverter + implements AuthenticationConverter +{ + private readonly converters: Record< + AuthenticationType, + AuthenticationConverter + >; + + constructor( + bearerTokenConverter = new BearerTokenAuthenticationConverter(), + basicTokenConverter = new BasicTokenAuthenticationConverter() + ) { + this.converters = { + [AuthenticationType.BEARER_TOKEN]: bearerTokenConverter, + [AuthenticationType.BASIC]: basicTokenConverter, + }; + } + + convert(authentication: Authentication, headers: HttpHeaders): HttpHeaders { + const converter = this.converters[authentication.type]; + return converter.convert(authentication, headers); + } +} + +class BearerTokenAuthenticationConverter + implements AuthenticationConverter +{ + convert( + authentication: BearerTokenAuthentication, + headers: HttpHeaders + ): HttpHeaders { + headers[HttpHeader.AUTHORIZATION] = `Bearer ${authentication.token}`; + return headers; + } +} + +class BasicTokenAuthenticationConverter + implements AuthenticationConverter +{ + convert( + authentication: BasicAuthentication, + headers: HttpHeaders + ): HttpHeaders { + const credentials = `${authentication.username}:${authentication.password}`; + const encoded = Buffer.from(credentials).toString('base64'); + headers[HttpHeader.AUTHORIZATION] = `Basic ${encoded}`; + return headers; + } +} + +type AuthenticationConverter = { + convert: (authentication: T, headers: HttpHeaders) => HttpHeaders; +}; diff --git a/packages/pieces/community/common/src/lib/http/core/http-client.ts b/packages/pieces/community/common/src/lib/http/core/http-client.ts new file mode 100644 index 0000000..8be15d2 --- /dev/null +++ b/packages/pieces/community/common/src/lib/http/core/http-client.ts @@ -0,0 +1,16 @@ +import { AxiosHttpClient } from '../axios/axios-http-client'; +import type { HttpMessageBody } from './http-message-body'; +import type { HttpRequest } from './http-request'; +import { HttpRequestBody } from './http-request-body'; +import { HttpResponse } from './http-response'; + +export type HttpClient = { + sendRequest< + RequestBody extends HttpRequestBody, + ResponseBody extends HttpMessageBody + >( + request: HttpRequest + ): Promise>; +}; + +export const httpClient = new AxiosHttpClient(); diff --git a/packages/pieces/community/common/src/lib/http/core/http-error.ts b/packages/pieces/community/common/src/lib/http/core/http-error.ts new file mode 100644 index 0000000..330322d --- /dev/null +++ b/packages/pieces/community/common/src/lib/http/core/http-error.ts @@ -0,0 +1,51 @@ +import { AxiosError } from 'axios'; + +export class HttpError extends Error { + private readonly status: number; + private readonly responseBody: unknown; + + constructor(private readonly requestBody: unknown, err: AxiosError) { + const status = err?.response?.status || 500; + const responseBody = err?.response?.data; + + super( + JSON.stringify({ + response: { + status: status, + body: responseBody, + }, + request: { + body: requestBody, + }, + }) + ); + + this.status = status; + this.responseBody = responseBody; + } + + public errorMessage() { + return { + response: { + status: this.status, + body: this.responseBody, + }, + request: { + body: this.requestBody, + }, + }; + } + + get response() { + return { + status: this.status, + body: this.responseBody, + }; + } + + get request() { + return { + body: this.requestBody, + }; + } +} diff --git a/packages/pieces/community/common/src/lib/http/core/http-header.ts b/packages/pieces/community/common/src/lib/http/core/http-header.ts new file mode 100644 index 0000000..0cd63b4 --- /dev/null +++ b/packages/pieces/community/common/src/lib/http/core/http-header.ts @@ -0,0 +1,6 @@ +export enum HttpHeader { + AUTHORIZATION = 'Authorization', + ACCEPT = 'Accept', + API_KEY = 'Api-Key', + CONTENT_TYPE = 'Content-Type', +} diff --git a/packages/pieces/community/common/src/lib/http/core/http-headers.ts b/packages/pieces/community/common/src/lib/http/core/http-headers.ts new file mode 100644 index 0000000..65492e1 --- /dev/null +++ b/packages/pieces/community/common/src/lib/http/core/http-headers.ts @@ -0,0 +1 @@ +export type HttpHeaders = Record; diff --git a/packages/pieces/community/common/src/lib/http/core/http-message-body.ts b/packages/pieces/community/common/src/lib/http/core/http-message-body.ts new file mode 100644 index 0000000..0ba1f99 --- /dev/null +++ b/packages/pieces/community/common/src/lib/http/core/http-message-body.ts @@ -0,0 +1 @@ +export type HttpMessageBody = any diff --git a/packages/pieces/community/common/src/lib/http/core/http-method.ts b/packages/pieces/community/common/src/lib/http/core/http-method.ts new file mode 100644 index 0000000..2820ddc --- /dev/null +++ b/packages/pieces/community/common/src/lib/http/core/http-method.ts @@ -0,0 +1,8 @@ +export enum HttpMethod { + GET = 'GET', + POST = 'POST', + PATCH = 'PATCH', + PUT = 'PUT', + DELETE = 'DELETE', + HEAD = 'HEAD', +} diff --git a/packages/pieces/community/common/src/lib/http/core/http-request-body.ts b/packages/pieces/community/common/src/lib/http/core/http-request-body.ts new file mode 100644 index 0000000..da413ed --- /dev/null +++ b/packages/pieces/community/common/src/lib/http/core/http-request-body.ts @@ -0,0 +1,3 @@ +import { HttpMessageBody } from "./http-message-body"; + +export type HttpRequestBody = HttpMessageBody | string; \ No newline at end of file diff --git a/packages/pieces/community/common/src/lib/http/core/http-request.ts b/packages/pieces/community/common/src/lib/http/core/http-request.ts new file mode 100644 index 0000000..b0f7a97 --- /dev/null +++ b/packages/pieces/community/common/src/lib/http/core/http-request.ts @@ -0,0 +1,17 @@ +import type { HttpMethod } from './http-method'; +import type { QueryParams } from './query-params'; +import { HttpHeaders } from './http-headers'; +import { Authentication } from '../../authentication'; +import { HttpRequestBody } from './http-request-body'; + +export type HttpRequest = { + method: HttpMethod; + url: string; + body?: RequestBody | undefined; + headers?: HttpHeaders; + authentication?: Authentication | undefined; + queryParams?: QueryParams | undefined; + timeout?: number; + retries?: number; + responseType?: 'arraybuffer' | 'json' | 'blob' | 'text'; +}; diff --git a/packages/pieces/community/common/src/lib/http/core/http-response.ts b/packages/pieces/community/common/src/lib/http/core/http-response.ts new file mode 100644 index 0000000..8c9e0dc --- /dev/null +++ b/packages/pieces/community/common/src/lib/http/core/http-response.ts @@ -0,0 +1,8 @@ +import { HttpMessageBody } from './http-message-body'; +import { HttpHeaders } from './http-headers'; + +export type HttpResponse = { + status: number; + headers?: HttpHeaders | undefined; + body: RequestBody; +}; diff --git a/packages/pieces/community/common/src/lib/http/core/media-type.ts b/packages/pieces/community/common/src/lib/http/core/media-type.ts new file mode 100644 index 0000000..1632e0d --- /dev/null +++ b/packages/pieces/community/common/src/lib/http/core/media-type.ts @@ -0,0 +1,4 @@ +export enum MediaType { + APPLICATION_JSON = 'application/json', + TEXT_CSV = 'text/csv', +} diff --git a/packages/pieces/community/common/src/lib/http/core/query-params.ts b/packages/pieces/community/common/src/lib/http/core/query-params.ts new file mode 100644 index 0000000..99704f4 --- /dev/null +++ b/packages/pieces/community/common/src/lib/http/core/query-params.ts @@ -0,0 +1 @@ +export type QueryParams = Record; diff --git a/packages/pieces/community/common/src/lib/http/index.ts b/packages/pieces/community/common/src/lib/http/index.ts new file mode 100644 index 0000000..75f8220 --- /dev/null +++ b/packages/pieces/community/common/src/lib/http/index.ts @@ -0,0 +1,13 @@ +export * from './axios/axios-http-client'; +export * from './core/base-http-client'; +export * from './core/delegating-authentication-converter'; +export * from './core/http-client'; +export * from './core/http-error'; +export * from './core/http-header'; +export * from './core/http-headers'; +export * from './core/http-message-body'; +export * from './core/http-method'; +export * from './core/http-request'; +export * from './core/http-response'; +export * from './core/media-type'; +export * from './core/query-params'; diff --git a/packages/pieces/community/common/src/lib/polling/index.ts b/packages/pieces/community/common/src/lib/polling/index.ts new file mode 100644 index 0000000..5e04334 --- /dev/null +++ b/packages/pieces/community/common/src/lib/polling/index.ts @@ -0,0 +1,194 @@ +import { FilesService, Store } from '@activepieces/pieces-framework'; +import { isNil } from '@activepieces/shared'; + + +interface TimebasedPolling { + strategy: DedupeStrategy.TIMEBASED; + items: (params: { + auth: AuthValue; + store: Store; + propsValue: PropsValue; + lastFetchEpochMS: number; + }) => Promise< + { + epochMilliSeconds: number; + data: unknown; + }[] + >; +} + +interface LastItemPolling { + strategy: DedupeStrategy.LAST_ITEM; + items: (params: { + auth: AuthValue; + store: Store; + files?: FilesService; + propsValue: PropsValue; + lastItemId: unknown; + }) => Promise< + { + id: unknown; + data: unknown; + }[] + >; +} + +export enum DedupeStrategy { + TIMEBASED, + LAST_ITEM, +} + +export type Polling = + | TimebasedPolling + | LastItemPolling; + +export const pollingHelper = { + async poll( + polling: Polling, + { + store, + auth, + propsValue, + maxItemsToPoll, + files, + }: { + store: Store; + auth: AuthValue; + propsValue: PropsValue; + files: FilesService; + maxItemsToPoll?: number; + } + ): Promise { + switch (polling.strategy) { + case DedupeStrategy.TIMEBASED: { + const lastEpochMilliSeconds = (await store.get('lastPoll')); + if (isNil(lastEpochMilliSeconds)) { + throw new Error("lastPoll doesn't exist in the store."); + } + const items = await polling.items({ + store, + auth, + propsValue, + lastFetchEpochMS: lastEpochMilliSeconds, + }); + const newLastEpochMilliSeconds = items.reduce( + (acc, item) => Math.max(acc, item.epochMilliSeconds), + lastEpochMilliSeconds + ); + await store.put('lastPoll', newLastEpochMilliSeconds); + return items + .filter((f) => f.epochMilliSeconds > lastEpochMilliSeconds) + .map((item) => item.data); + } + case DedupeStrategy.LAST_ITEM: { + const lastItemId = await store.get('lastItem'); + const items = await polling.items({ + store, + auth, + propsValue, + lastItemId, + files, + }); + + const lastItemIndex = items.findIndex((f) => f.id === lastItemId); + let newItems = []; + if (isNil(lastItemId) || lastItemIndex == -1) { + newItems = items ?? []; + } else { + newItems = items?.slice(0, lastItemIndex) ?? []; + } + // Sorted from newest to oldest + if (!isNil(maxItemsToPoll)) { + // Get the last polling.maxItemsToPoll items + newItems = newItems.slice(-maxItemsToPoll); + } + const newLastItem = newItems?.[0]?.id; + if (!isNil(newLastItem)) { + await store.put('lastItem', newLastItem); + } + return newItems.map((item) => item.data); + } + } + }, + async onEnable( + polling: Polling, + { + store, + auth, + propsValue, + }: { store: Store; auth: AuthValue; propsValue: PropsValue } + ): Promise { + switch (polling.strategy) { + case DedupeStrategy.TIMEBASED: { + await store.put('lastPoll', Date.now()); + break; + } + case DedupeStrategy.LAST_ITEM: { + const items = await polling.items({ + store, + auth, + propsValue, + lastItemId: null, + }); + const lastItemId = items?.[0]?.id; + if (!isNil(lastItemId)) { + await store.put('lastItem', lastItemId); + } else { + await store.delete('lastItem'); + } + break; + } + } + }, + async onDisable( + polling: Polling, + params: { store: Store; auth: AuthValue; propsValue: PropsValue } + ): Promise { + switch (polling.strategy) { + case DedupeStrategy.TIMEBASED: + case DedupeStrategy.LAST_ITEM: + return; + } + }, + async test( + polling: Polling, + { + auth, + propsValue, + store, + files, + }: { store: Store; auth: AuthValue; propsValue: PropsValue, files: FilesService } + ): Promise { + let items = []; + switch (polling.strategy) { + case DedupeStrategy.TIMEBASED: { + items = await polling.items({ + store, + auth, + propsValue, + lastFetchEpochMS: 0, + }); + break; + } + case DedupeStrategy.LAST_ITEM: { + items = await polling.items({ + store, + auth, + propsValue, + lastItemId: null, + files, + }); + break; + } + } + return getFirstFiveOrAll(items.map((item) => item.data)); + }, +}; + +function getFirstFiveOrAll(array: unknown[]) { + if (array.length <= 5) { + return array; + } else { + return array.slice(0, 5); + } +} diff --git a/packages/pieces/community/common/src/lib/validation/index.ts b/packages/pieces/community/common/src/lib/validation/index.ts new file mode 100644 index 0000000..d9faac3 --- /dev/null +++ b/packages/pieces/community/common/src/lib/validation/index.ts @@ -0,0 +1,28 @@ +import { z } from "zod" + +export const propsValidation = { + async validateZod>(props: T, schema: Partial>): Promise { + const schemaObj = z.object( + Object.entries(schema).reduce((acc, [key, value]) => ({ + ...acc, + [key]: value + }), {}) + ) + + try { + await schemaObj.parseAsync(props) + } catch (error) { + if (error instanceof z.ZodError) { + const errors = error.errors.reduce((acc, err) => { + const path = err.path.join('.') + return { + ...acc, + [path]: err.message + } + }, {}) + throw new Error(JSON.stringify({ errors }, null, 2)) + } + throw error + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/common/tsconfig.json b/packages/pieces/community/common/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/common/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/common/tsconfig.lib.json b/packages/pieces/community/common/tsconfig.lib.json new file mode 100644 index 0000000..18f2d37 --- /dev/null +++ b/packages/pieces/community/common/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/packages/pieces/community/confluence/.eslintrc.json b/packages/pieces/community/confluence/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/confluence/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/confluence/README.md b/packages/pieces/community/confluence/README.md new file mode 100644 index 0000000..ad26b83 --- /dev/null +++ b/packages/pieces/community/confluence/README.md @@ -0,0 +1,7 @@ +# pieces-confluence + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-confluence` to build the library. diff --git a/packages/pieces/community/confluence/package.json b/packages/pieces/community/confluence/package.json new file mode 100644 index 0000000..3bdd8bf --- /dev/null +++ b/packages/pieces/community/confluence/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-confluence", + "version": "0.1.3" +} diff --git a/packages/pieces/community/confluence/project.json b/packages/pieces/community/confluence/project.json new file mode 100644 index 0000000..b44c1e0 --- /dev/null +++ b/packages/pieces/community/confluence/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-confluence", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/confluence/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/confluence", + "tsConfig": "packages/pieces/community/confluence/tsconfig.lib.json", + "packageJson": "packages/pieces/community/confluence/package.json", + "main": "packages/pieces/community/confluence/src/index.ts", + "assets": [ + "packages/pieces/community/confluence/*.md", + { + "input": "packages/pieces/community/confluence/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/confluence/src/index.ts b/packages/pieces/community/confluence/src/index.ts new file mode 100644 index 0000000..08489c2 --- /dev/null +++ b/packages/pieces/community/confluence/src/index.ts @@ -0,0 +1,53 @@ +import { createPiece, PieceAuth, Property ,PiecePropValueSchema, Piece} from "@activepieces/pieces-framework"; +import { getPageContent } from "./lib/actions/get-page-content"; +import { newPageTrigger } from "./lib/triggers/new-page"; +import { PieceCategory } from "@activepieces/shared"; +import { createCustomApiCallAction } from "@activepieces/pieces-common"; +import { createPageFromTemplateAction } from "./lib/actions/create-page-from-template"; + +export const confluenceAuth = PieceAuth.CustomAuth({ + description: 'Please refer to this guide to get your api credentials: https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account', + required: true, + props: { + username: PieceAuth.SecretText({ + displayName: 'Account Email', + required: true, + description: 'Account email for basic auth', + }), + password: PieceAuth.SecretText({ + displayName: 'API token', + required: true, + description: 'API token for basic auth', + }), + confluenceDomain: Property.ShortText({ + displayName: 'Confluence Domain', + required: true, + description: 'Example value - https://your-domain.atlassian.net', + }), + }, +}); + +export const confluence = createPiece({ + displayName: "Confluence", + auth: confluenceAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: "https://cdn.activepieces.com/pieces/confluence.png", + authors: ["geekyme"], + actions: [getPageContent,createPageFromTemplateAction, + createCustomApiCallAction({ + baseUrl:(auth)=>{ + const authValue = auth as PiecePropValueSchema; + return `${authValue.confluenceDomain}/wiki/api/v2`; + }, + auth: confluenceAuth, + authMapping: async (auth) => { + const authValue = auth as PiecePropValueSchema; + return { + Authorization: `Basic ${Buffer.from(`${authValue.username}:${authValue.password}`).toString('base64')}`, + }; + }, + }) + ], + categories: [PieceCategory.CONTENT_AND_FILES], + triggers: [newPageTrigger], +}); diff --git a/packages/pieces/community/confluence/src/lib/actions/create-page-from-template.ts b/packages/pieces/community/confluence/src/lib/actions/create-page-from-template.ts new file mode 100644 index 0000000..2a35132 --- /dev/null +++ b/packages/pieces/community/confluence/src/lib/actions/create-page-from-template.ts @@ -0,0 +1,82 @@ +import { confluenceAuth } from "../../index"; +import { createAction, Property } from "@activepieces/pieces-framework"; +import { folderIdProp, spaceIdProp, templateIdProp, templateVariablesProp } from "../common/props"; +import { confluenceApiCall } from "../common"; +import { HttpMethod } from "@activepieces/pieces-common"; + +export const createPageFromTemplateAction = createAction({ + auth:confluenceAuth, + name:'create-page-from-template', + displayName:'Create Page from Template', + description:'Creates a new page from a template with the given title and variables.', + props:{ + spaceId:spaceIdProp, + templateId:templateIdProp, + folderId:folderIdProp, + title:Property.ShortText({ + displayName:'Title', + required:true, + }), + status:Property.StaticDropdown({ + displayName:'Status', + required:true, + defaultValue:'draft', + options:{ + disabled:false, + options:[ + { + label:'Published ', + value:'current' + }, + { + label:'Draft', + value:'draft' + } + ] + } + }), + templateVariables:templateVariablesProp, + }, + async run(context){ + const {spaceId,templateId,title,status,folderId} = context.propsValue; + const variables = context.propsValue.templateVariables ??{}; + + const template = await confluenceApiCall<{ body: { storage: { value: string } } }>({ + domain: context.auth.confluenceDomain, + username: context.auth.username, + password: context.auth.password, + method: HttpMethod.GET, + version: 'v1', + resourceUri: `/template/${templateId}`, + }); + + const body = template.body.storage.value; + + let content = body.replace(/[\s\S]*?<\/at:declarations>/, "").trim(); + Object.entries(variables).forEach(([key, value]) => { + const varRegex = new RegExp(``, "g"); + content = content.replace(varRegex, value); + }); + + const response = await confluenceApiCall({ + domain: context.auth.confluenceDomain, + username: context.auth.username, + password: context.auth.password, + method: HttpMethod.POST, + version: 'v2', + resourceUri: '/pages', + body: { + spaceId:spaceId, + title, + parentId:folderId, + status, + body:{ + representation:'storage', + value:content, + } + } + }) + + return response; + } +}) \ No newline at end of file diff --git a/packages/pieces/community/confluence/src/lib/actions/get-page-content.ts b/packages/pieces/community/confluence/src/lib/actions/get-page-content.ts new file mode 100644 index 0000000..fe3747f --- /dev/null +++ b/packages/pieces/community/confluence/src/lib/actions/get-page-content.ts @@ -0,0 +1,147 @@ +import { + createAction, + Property, + DynamicPropsValue, + PiecePropValueSchema, +} from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { confluenceAuth } from '../..'; +import { confluenceApiCall } from '../common'; + +interface ConfluencePage { + id: string; + title: string; + body: any; + children?: ConfluencePage[]; +} + +async function getPageWithContent( + auth: PiecePropValueSchema, + pageId: string, +): Promise { + try { + const response = await confluenceApiCall({ + domain: auth.confluenceDomain, + username: auth.username, + password: auth.password, + method: HttpMethod.GET, + version: 'v2', + resourceUri: `/pages/${pageId}`, + query: { + 'body-format': 'storage', + }, + }); + return response; + } catch (error) { + throw new Error( + `Failed to fetch page ${pageId}: ${error instanceof Error ? error.message : 'Unknown error'}`, + ); + } +} + +async function getChildPages( + auth: PiecePropValueSchema, + parentId: string, + currentDepth: number, + maxDepth: number, +): Promise { + if (currentDepth >= maxDepth) { + return []; + } + + try { + const childrenResponse = await confluenceApiCall<{ results: ConfluencePage[] }>({ + domain: auth.confluenceDomain, + username: auth.username, + password: auth.password, + method: HttpMethod.GET, + version: 'v2', + resourceUri: `/pages/${parentId}/children`, + }); + + const childPages = await Promise.all( + childrenResponse.results.map(async (childPage) => { + const pageWithContent = await getPageWithContent(auth, childPage.id); + const children = await getChildPages(auth, childPage.id, currentDepth + 1, maxDepth); + return { + ...pageWithContent, + children, + }; + }), + ); + + return childPages; + } catch (error) { + throw new Error( + `Failed to fetch children for page ${parentId}: ${ + error instanceof Error ? error.message : 'Unknown error' + }`, + ); + } +} + +export const getPageContent = createAction({ + name: 'getPageContent', + displayName: 'Get Page Content', + description: 'Get page content and optionally all its descendants', + auth: confluenceAuth, + props: { + pageId: Property.ShortText({ + displayName: 'Page ID', + description: 'Get this from the page URL of your Confluence Cloud', + required: true, + }), + includeDescendants: Property.Checkbox({ + displayName: 'Include Descendants ?', + description: 'If checked, will fetch all child pages recursively.', + required: false, + defaultValue: false, + }), + dynamic: Property.DynamicProperties({ + displayName: 'Dynamic Properties', + refreshers: ['includeDescendants'], + required: true, + props: async ({ includeDescendants }) => { + if (!includeDescendants) { + return {}; + } + const fields: DynamicPropsValue = { + maxDepth: Property.Number({ + displayName: 'Maximum Depth', + description: 'Maximum depth of child pages to fetch.', + required: true, + defaultValue: 5, + }), + }; + return fields; + }, + }), + }, + async run(context) { + try { + const page = await getPageWithContent(context.auth, context.propsValue.pageId); + + if (!context.propsValue.includeDescendants) { + return page; + } + + const children = await getChildPages( + context.auth, + context.propsValue.pageId, + 1, + context.propsValue.dynamic['maxDepth'], + ); + + return { + ...page, + children, + }; + } catch (error) { + throw new Error( + `Failed to fetch page ${context.propsValue.pageId}: ${ + error instanceof Error ? error.message : 'Unknown error' + }`, + ); + } + }, +}); diff --git a/packages/pieces/community/confluence/src/lib/common/index.ts b/packages/pieces/community/confluence/src/lib/common/index.ts new file mode 100644 index 0000000..273dd24 --- /dev/null +++ b/packages/pieces/community/confluence/src/lib/common/index.ts @@ -0,0 +1,118 @@ +import { + AuthenticationType, + httpClient, + HttpMessageBody, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; + +export type ConfluenceApiCallParams = { + domain: string; + username: string; + password: string; + version: 'v1' | 'v2'; + method: HttpMethod; + resourceUri: string; + query?: QueryParams; + body?: any; +}; + +export type PaginatedResponse = { + results: T[]; + _links?: { + next?: string; + }; +}; + +export async function confluenceApiCall({ + domain, + username, + password, + method, + version, + resourceUri, + query, + body, +}: ConfluenceApiCallParams): Promise { + const baseUrl = version === 'v2' ? `${domain}/wiki/api/v2` : `${domain}/wiki/rest/api`; + + const request: HttpRequest = { + method, + url: baseUrl + resourceUri, + authentication: { + type: AuthenticationType.BASIC, + username, + password, + }, + queryParams: query, + body: body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} + +export async function confluencePaginatedApiCall({ + domain, + username, + password, + method, + version, + resourceUri, + query, + body, +}: ConfluenceApiCallParams): Promise { + const qs = query ? query : {}; + const resultData: T[] = []; + + if (version === 'v2') { + let nextUrl = `${domain}/wiki/api/v2${resourceUri}?limit=200`; + + do { + const response = await httpClient.sendRequest>({ + method, + url: nextUrl, + authentication: { + type: AuthenticationType.BASIC, + username, + password, + }, + queryParams: qs, + body, + }); + + if (isNil(response.body.results)) { + break; + } + resultData.push(...response.body.results); + nextUrl = response.body?._links?.next ? `${domain}${response.body._links.next}` : ''; + } while (nextUrl); + } else { + let start = 0; + let hasMoreData = true; + + do { + const response = await httpClient.sendRequest<{ results: T[] }>({ + method, + url: `${domain}/wiki/rest/api${resourceUri}?start=${start}&limit=100`, + authentication: { + type: AuthenticationType.BASIC, + username, + password, + }, + queryParams: qs, + body, + }); + if (isNil(response.body.results) || response.body.results.length === 0) { + hasMoreData = false; + } else { + resultData.push(...response.body.results); + start += 100; + } + } while (hasMoreData); + } + + return resultData; +} diff --git a/packages/pieces/community/confluence/src/lib/common/props.ts b/packages/pieces/community/confluence/src/lib/common/props.ts new file mode 100644 index 0000000..b25b03d --- /dev/null +++ b/packages/pieces/community/confluence/src/lib/common/props.ts @@ -0,0 +1,254 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { confluenceApiCall, confluencePaginatedApiCall } from '.'; +import { confluenceAuth } from '../../index'; +import { + DropdownOption, + DynamicPropsValue, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { parseStringPromise } from 'xml2js'; + +export const spaceIdProp = Property.Dropdown({ + displayName: 'Space', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + const authValue = auth as PiecePropValueSchema; + const spaces = await confluencePaginatedApiCall<{ id: string; name: string }>({ + domain: authValue.confluenceDomain, + username: authValue.username, + password: authValue.password, + version: 'v2', + method: HttpMethod.GET, + resourceUri: '/spaces', + }); + + const options: DropdownOption[] = []; + for (const space of spaces) { + options.push({ + label: space.name, + value: space.id, + }); + } + return { + disabled: false, + options, + }; + }, +}); + +export const templateIdProp = Property.Dropdown({ + displayName: 'Template', + refreshers: ['spaceId'], + required: true, + options: async ({ auth, spaceId }) => { + if (!auth || !spaceId) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first and select a space.', + }; + } + + const authValue = auth as PiecePropValueSchema; + + const space = await confluenceApiCall<{ id: string; name: string; key: string }>({ + domain: authValue.confluenceDomain, + username: authValue.username, + password: authValue.password, + method: HttpMethod.GET, + version: 'v2', + resourceUri: `/spaces/${spaceId}`, + }); + + const templates = await confluencePaginatedApiCall<{ templateId: string; name: string }>({ + domain: authValue.confluenceDomain, + username: authValue.username, + password: authValue.password, + version: 'v1', + method: HttpMethod.GET, + resourceUri: `/template/page`, + query: { spaceKey: space.key }, + }); + + const options: DropdownOption[] = []; + for (const template of templates) { + options.push({ + label: template.name, + value: template.templateId, + }); + } + return { + disabled: false, + options, + }; + }, +}); + +export const folderIdProp = Property.Dropdown({ + displayName:'Parent Folder', + refreshers:['spaceId'], + required:false, + options:async ({auth,spaceId})=>{ + if (!auth || !spaceId) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first and select a space.', + }; + } + + const authValue = auth as PiecePropValueSchema; + + const space = await confluenceApiCall<{ id: string; name: string; key: string,homepageId:string }>({ + domain: authValue.confluenceDomain, + username: authValue.username, + password: authValue.password, + method: HttpMethod.GET, + version: 'v2', + resourceUri: `/spaces/${spaceId}`, + }); + + const folders = await confluencePaginatedApiCall<{id:string,title:string}>({ + domain:authValue.confluenceDomain, + username:authValue.username, + password:authValue.password, + version:'v1', + method:HttpMethod.GET, + resourceUri:`/content/${space.homepageId}/descendant/folder`, + }) + + const options:DropdownOption[] = []; + for(const folder of folders){ + options.push({ + label:folder.title, + value:folder.id + }) + } + return{ + disabled:false, + options + } + } +}) + +export const templateVariablesProp = Property.DynamicProperties({ + displayName: 'Template Variables', + refreshers: ['templateId'], + required: true, + props: async ({ auth, templateId }) => { + if (!auth) return {}; + if (!templateId) return {}; + + const authValue = auth as PiecePropValueSchema; + + const props: DynamicPropsValue = {}; + + const response = await confluenceApiCall<{ body: { storage: { value: string } } }>({ + domain: authValue.confluenceDomain, + username: authValue.username, + password: authValue.password, + method: HttpMethod.GET, + version: 'v1', + resourceUri: `/template/${templateId}`, + }); + + const parsedXml = await parseStringPromise(response.body.storage.value, { + explicitArray: false, + }); + const declarations = parsedXml['at:declarations']; + + if (!declarations) return {}; + + const variables: Array<{ name: string; type: string; options?: string[] }> = []; + + Object.entries(declarations).forEach(([key, value]: [string, any]) => { + const type = key.replace('at:', ''); + if (Array.isArray(value)) { + value.forEach((item) => { + if (item['$']) { + const varName = item['$']['at:name']; + let options: string[] | undefined; + + if (type === 'list' && item['at:option']) { + options = item['at:option'].map((opt: any) => opt['$']['at:value']); + } + + if (varName && type) { + variables.push({ + name: varName, + type: type, + options: options, + }); + } + } + }); + } else if (value['$']) { + const varName = value['$']['at:name']; + let options: string[] | undefined; + + if (type === 'list' && value['at:option']) { + options = value['at:option'].map((opt: any) => opt['$']['at:value']); + } + + if (varName && type) { + variables.push({ + name: varName, + type: type, + options: options, + }); + } + } + }); + + for (const variable of variables) { + switch (variable.type) { + case 'list': + props[variable.name] = Property.StaticDropdown({ + displayName: variable.name, + required: false, + defaultValue: '', + options: { + disabled: false, + options: variable.options + ? variable.options.map((option) => { + return { + label: option, + value: option, + }; + }) + : [], + }, + }); + break; + case 'string': + props[variable.name] = Property.ShortText({ + displayName: variable.name, + required: false, + defaultValue: '', + }); + break; + case 'textarea': + props[variable.name] = Property.LongText({ + displayName: variable.name, + required: false, + defaultValue: '', + }); + break; + default: + break; + } + } + + return props; + }, +}); diff --git a/packages/pieces/community/confluence/src/lib/triggers/new-page.ts b/packages/pieces/community/confluence/src/lib/triggers/new-page.ts new file mode 100644 index 0000000..9119a03 --- /dev/null +++ b/packages/pieces/community/confluence/src/lib/triggers/new-page.ts @@ -0,0 +1,125 @@ +import { + createTrigger, + TriggerStrategy, + PiecePropValueSchema, +} from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper, HttpMethod } from '@activepieces/pieces-common'; +import { confluenceAuth } from '../../index'; +import { confluenceApiCall, confluencePaginatedApiCall, PaginatedResponse } from '../common'; +import { isNil } from '@activepieces/shared'; +import { spaceIdProp } from '../common/props'; + +interface ConfluencePage { + id: string; + status: string; + title: string; + spaceId: string; + createdAt: string; + version: { + number: number; + createdAt: string; + }; +} + +type Props = { + spaceId: string; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const pages = []; + if (lastFetchEpochMS === 0) { + const response = await confluenceApiCall>({ + domain: auth.confluenceDomain, + username: auth.username, + password: auth.password, + version: 'v2', + method: HttpMethod.GET, + resourceUri: `/spaces/${propsValue.spaceId}/pages`, + query: { + limit: '10', + sort: '-created-date', + }, + }); + if (isNil(response.results)) { + return []; + } + pages.push(...response.results); + } else { + const response = await confluencePaginatedApiCall({ + domain: auth.confluenceDomain, + username: auth.username, + password: auth.password, + method: HttpMethod.GET, + version: 'v2', + resourceUri: `/spaces/${propsValue.spaceId}/pages`, + query: { + sort: '-created-date', + }, + }); + if (isNil(response)) { + return []; + } + pages.push(...response); + } + + return pages.map((page) => { + return { + epochMilliSeconds: new Date(page.createdAt).getTime(), + data: page, + }; + }); + }, +}; + +export const newPageTrigger = createTrigger({ + name: 'new-page', + displayName: 'New Page', + description: 'Triggers when a new page is created.', + auth: confluenceAuth, + type: TriggerStrategy.POLLING, + props: { + spaceId: spaceIdProp, + }, + + async onEnable(context) { + await pollingHelper.onEnable(polling, context); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + sampleData: { + parentType: 'page', + parentId: '123456', + spaceId: 'SAMPLE123', + ownerId: '12345678abcd', + lastOwnerId: null, + createdAt: '2024-01-01T12:00:00.000Z', + authorId: '12345678abcd', + position: 1000, + version: { + number: 1, + message: 'Initial version', + minorEdit: false, + authorId: '12345678abcd', + createdAt: '2024-01-01T12:00:00.000Z', + }, + body: {}, + status: 'current', + title: 'Sample Confluence Page', + id: '987654321', + _links: { + editui: '/pages/resumedraft.action?draftId=987654321', + webui: '/spaces/SAMPLE/pages/987654321/Sample+Confluence+Page', + edituiv2: '/spaces/SAMPLE/pages/edit-v2/987654321', + tinyui: '/x/abcd123', + }, + }, +}); diff --git a/packages/pieces/community/confluence/tsconfig.json b/packages/pieces/community/confluence/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/confluence/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/confluence/tsconfig.lib.json b/packages/pieces/community/confluence/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/confluence/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/connections/.eslintrc.json b/packages/pieces/community/connections/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/connections/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/connections/README.md b/packages/pieces/community/connections/README.md new file mode 100644 index 0000000..aebcb54 --- /dev/null +++ b/packages/pieces/community/connections/README.md @@ -0,0 +1,7 @@ +# pieces-connections + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-connections` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/connections/package.json b/packages/pieces/community/connections/package.json new file mode 100644 index 0000000..e6b1365 --- /dev/null +++ b/packages/pieces/community/connections/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-connections", + "version": "0.4.1" +} \ No newline at end of file diff --git a/packages/pieces/community/connections/project.json b/packages/pieces/community/connections/project.json new file mode 100644 index 0000000..bb62ac3 --- /dev/null +++ b/packages/pieces/community/connections/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-connections", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/connections/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/connections", + "tsConfig": "packages/pieces/community/connections/tsconfig.lib.json", + "packageJson": "packages/pieces/community/connections/package.json", + "main": "packages/pieces/community/connections/src/index.ts", + "assets": [ + "packages/pieces/community/connections/*.md", + { + "input": "packages/pieces/community/connections/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/connections/src/index.ts b/packages/pieces/community/connections/src/index.ts new file mode 100644 index 0000000..e0d878d --- /dev/null +++ b/packages/pieces/community/connections/src/index.ts @@ -0,0 +1,15 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { readConnection } from './lib/actions/read-connection'; + +export const connections = createPiece({ + displayName: 'Connections', + description: 'Read connections dynamically', + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/connections.png', + categories: [PieceCategory.CORE], + auth: PieceAuth.None(), + authors: ["kishanprmr","AbdulTheActivePiecer","khaledmashaly","abuaboud"], + actions: [readConnection], + triggers: [], +}); diff --git a/packages/pieces/community/connections/src/lib/actions/read-connection.ts b/packages/pieces/community/connections/src/lib/actions/read-connection.ts new file mode 100644 index 0000000..8b5c0df --- /dev/null +++ b/packages/pieces/community/connections/src/lib/actions/read-connection.ts @@ -0,0 +1,39 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { isNil } from '@activepieces/shared'; + +const markdown = ` +**Advanced Piece** +
+Use this piece if you are unsure which connection to use beforehand, such as when the connection external ID is sent through a webhook message. + +**Notes:** +- You can retrieve the external ID from the connection settings page by hovering over the connection name. +- Use this action to retrieve connection values by their external IDs from this project. +- After testing the step, you can use the dynamic value in the piece by clicking (X) and referring to this step. +`; + +export const readConnection = createAction({ + name: 'read_connection', + displayName: 'Read Connection', + description: 'Fetch connection by name', + props: { + info: Property.MarkDown({ + value: markdown, + }), + connection_name: Property.ShortText({ + displayName: 'Connection External ID', + description: undefined, + required: true, + }), + }, + async run(ctx) { + const connection = await ctx.connections.get(ctx.propsValue.connection_name); + if (isNil(connection)) { + throw new Error(JSON.stringify({ + message: 'Connection not found', + connectionName: ctx.propsValue.connection_name, + })); + } + return connection; + }, +}); diff --git a/packages/pieces/community/connections/tsconfig.json b/packages/pieces/community/connections/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/connections/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/connections/tsconfig.lib.json b/packages/pieces/community/connections/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/connections/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/constant-contact/.eslintrc.json b/packages/pieces/community/constant-contact/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/constant-contact/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/constant-contact/README.md b/packages/pieces/community/constant-contact/README.md new file mode 100644 index 0000000..8be2f35 --- /dev/null +++ b/packages/pieces/community/constant-contact/README.md @@ -0,0 +1,7 @@ +# pieces-constant-contact + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-constant-contact` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/constant-contact/package.json b/packages/pieces/community/constant-contact/package.json new file mode 100644 index 0000000..f728a43 --- /dev/null +++ b/packages/pieces/community/constant-contact/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-constant-contact", + "version": "0.1.4" +} \ No newline at end of file diff --git a/packages/pieces/community/constant-contact/project.json b/packages/pieces/community/constant-contact/project.json new file mode 100644 index 0000000..f54f55c --- /dev/null +++ b/packages/pieces/community/constant-contact/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-constant-contact", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/constant-contact/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/constant-contact", + "tsConfig": "packages/pieces/community/constant-contact/tsconfig.lib.json", + "packageJson": "packages/pieces/community/constant-contact/package.json", + "main": "packages/pieces/community/constant-contact/src/index.ts", + "assets": [ + "packages/pieces/community/constant-contact/*.md", + { + "input": "packages/pieces/community/constant-contact/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/constant-contact/src/index.ts b/packages/pieces/community/constant-contact/src/index.ts new file mode 100644 index 0000000..1c14383 --- /dev/null +++ b/packages/pieces/community/constant-contact/src/index.ts @@ -0,0 +1,37 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createOrUpdateContact } from './lib/actions/create-or-update-contact'; + +export const constantContactAuth = PieceAuth.OAuth2({ + required: true, + tokenUrl: 'https://authz.constantcontact.com/oauth2/default/v1/token', + authUrl: 'https://authz.constantcontact.com/oauth2/default/v1/authorize', + scope: ['contact_data'], +}); + +export const constantContact = createPiece({ + displayName: 'Constant Contact', + description: 'Email marketing for small businesses', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/constant-contact.png', + categories: [PieceCategory.MARKETING], + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + auth: constantContactAuth, + actions: [ + createOrUpdateContact, + createCustomApiCallAction({ + baseUrl: () => 'https://api.cc.email/v3', // Replace with the actual base URL + auth: constantContactAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/constant-contact/src/lib/actions/create-or-update-contact.ts b/packages/pieces/community/constant-contact/src/lib/actions/create-or-update-contact.ts new file mode 100644 index 0000000..4a55b03 --- /dev/null +++ b/packages/pieces/community/constant-contact/src/lib/actions/create-or-update-contact.ts @@ -0,0 +1,107 @@ +import { + OAuth2PropertyValue, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { constantContactAuth } from '../../'; + +export const createOrUpdateContact = createAction({ + auth: constantContactAuth, + name: 'create_or_update_contact', + displayName: 'Create or Update Contact', + description: 'Create or Update a contact in Constant Contact', + props: { + list: Property.MultiSelectDropdown({ + displayName: 'List', + description: 'The list of the contact', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + placeholder: 'Connect to Constant Contact to see the lists', + disabled: true, + }; + } + return { + placeholder: 'Select a list', + disabled: false, + options: ( + await httpClient.sendRequest<{ + lists: { list_id: string; name: string }[]; + }>({ + url: 'https://api.cc.email/v3/contact_lists', + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: (auth as OAuth2PropertyValue)['access_token'], + }, + }) + ).body.lists.map((list) => { + return { + value: list.list_id, + label: list.name, + }; + }), + }; + }, + }), + email_address: Property.ShortText({ + displayName: 'Email', + description: 'The email of the contact', + required: true, + }), + first_name: Property.ShortText({ + displayName: 'First Name', + description: 'The first name of the contact', + required: false, + }), + last_name: Property.ShortText({ + displayName: 'Last Name', + description: 'The last name of the contact', + required: false, + }), + job_title: Property.ShortText({ + displayName: 'Job Title', + description: 'The job title of the contact', + required: false, + }), + company_name: Property.ShortText({ + displayName: 'Company Name', + description: 'The company name of the contact', + required: false, + }), + phoner_number: Property.ShortText({ + displayName: 'Phone Number', + description: 'The phone number of the contact', + required: false, + }), + }, + run: async ({ auth, propsValue }) => { + return ( + await httpClient.sendRequest({ + url: 'https://api.cc.email/v3/contacts/sign_up_form', + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + body: { + email_address: propsValue.email_address, + first_name: propsValue.first_name, + last_name: propsValue.last_name, + job_title: propsValue.job_title, + company_name: propsValue.company_name, + phoner_number: propsValue.phoner_number, + list_memberships: propsValue.list, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/constant-contact/tsconfig.json b/packages/pieces/community/constant-contact/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/constant-contact/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/constant-contact/tsconfig.lib.json b/packages/pieces/community/constant-contact/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/constant-contact/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/contentful/.eslintrc.json b/packages/pieces/community/contentful/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/contentful/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/contentful/README.md b/packages/pieces/community/contentful/README.md new file mode 100644 index 0000000..9e31c09 --- /dev/null +++ b/packages/pieces/community/contentful/README.md @@ -0,0 +1,7 @@ +# pieces-contentful + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-contentful` to build the library. diff --git a/packages/pieces/community/contentful/package-lock.json b/packages/pieces/community/contentful/package-lock.json new file mode 100644 index 0000000..2e714bd --- /dev/null +++ b/packages/pieces/community/contentful/package-lock.json @@ -0,0 +1,471 @@ +{ + "name": "@activepieces/piece-contentful", + "version": "0.0.8", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-contentful", + "version": "0.0.8", + "dependencies": { + "contentful-management": "11.48.1" + } + }, + "node_modules/@contentful/rich-text-types": { + "version": "16.8.5", + "resolved": "https://registry.npmjs.org/@contentful/rich-text-types/-/rich-text-types-16.8.5.tgz", + "integrity": "sha512-q18RJuJCOuYveGiCIjE5xLCQc5lZ3L2Qgxrlg/H2YEobDFqdtmklazRi1XwEWaK3tMg6yVXBzKKkQfLB4qW14A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.2.tgz", + "integrity": "sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/contentful-management": { + "version": "11.48.1", + "resolved": "https://registry.npmjs.org/contentful-management/-/contentful-management-11.48.1.tgz", + "integrity": "sha512-LxN5Vcdc7UNbQVOVIb49iIwtNBumdL2vKi820Az/XTejkRAvk9dOO5QDUIMmLT6clK2LPGGa1F3xnVtemr8IhQ==", + "dependencies": { + "@contentful/rich-text-types": "^16.6.1", + "axios": "^1.8.4", + "contentful-sdk-core": "^9.0.1", + "fast-copy": "^3.0.0", + "globals": "^15.15.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/contentful-sdk-core": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/contentful-sdk-core/-/contentful-sdk-core-9.2.0.tgz", + "integrity": "sha512-cUvHbC2u8ouJHhG3tofQhUc0FAmM/QBcalYjiczMtFKrR77BW+eSPcPg+A9DQlhIP0UGcQ/knXxoJpBvrERXTA==", + "dependencies": { + "fast-copy": "^3.0.2", + "lodash": "^4.17.21", + "p-throttle": "^6.1.0", + "process": "^0.11.10", + "qs": "^6.12.3" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "^4.18.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-throttle": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-throttle/-/p-throttle-6.2.0.tgz", + "integrity": "sha512-NCKkOVj6PZa6NiTmfvGilDdf6vO1rFCD3KDnkHko8dTOtkpk4cSR/VTAhhLMG9aiQ7/A9HYgEDNmxzf6hxzR3g==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + } + } +} diff --git a/packages/pieces/community/contentful/package.json b/packages/pieces/community/contentful/package.json new file mode 100644 index 0000000..d3368dc --- /dev/null +++ b/packages/pieces/community/contentful/package.json @@ -0,0 +1,7 @@ +{ + "name": "@activepieces/piece-contentful", + "version": "0.0.8", + "dependencies": { + "contentful-management": "11.48.1" + } +} \ No newline at end of file diff --git a/packages/pieces/community/contentful/project.json b/packages/pieces/community/contentful/project.json new file mode 100644 index 0000000..25104f8 --- /dev/null +++ b/packages/pieces/community/contentful/project.json @@ -0,0 +1,69 @@ +{ + "name": "pieces-contentful", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/contentful/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/contentful", + "tsConfig": "packages/pieces/community/contentful/tsconfig.lib.json", + "packageJson": "packages/pieces/community/contentful/package.json", + "main": "packages/pieces/community/contentful/src/index.ts", + "assets": [ + "packages/pieces/community/contentful/*.md", + { + "input": "packages/pieces/community/contentful/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + }, + "dependsOn": ["prebuild", "^build"] + }, + "prebuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/pieces/community/contentful", + "command": "npm ci" + } + }, + "build-with-deps": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "nx run pieces-contentful:prebuild", + "nx run pieces-contentful:build", + "nx run pieces-contentful:postbuild" + ], + "parallel": false + } + }, + "postbuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "dist/packages/pieces/community/contentful", + "command": "npm install" + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-contentful {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/contentful/src/index.ts b/packages/pieces/community/contentful/src/index.ts new file mode 100644 index 0000000..6ea525e --- /dev/null +++ b/packages/pieces/community/contentful/src/index.ts @@ -0,0 +1,33 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { + ContentfulCreateRecordAction, + ContentfulGetRecordAction, + ContentfulSearchRecordsAction, +} from './lib/actions/records'; +import { ContentfulAuth } from './lib/common'; + +export const contentful = createPiece({ + displayName: 'Contentful', + description: 'Content infrastructure for digital teams', + + auth: ContentfulAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/contentful.png', + categories: [PieceCategory.MARKETING], + authors: ["cyrilselasi","kishanprmr","MoShizzle","abuaboud"], + actions: [ + ContentfulSearchRecordsAction, + ContentfulGetRecordAction, + ContentfulCreateRecordAction, + createCustomApiCallAction({ + baseUrl: () => `https://api.contentful.com`, + auth: ContentfulAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as { apiKey: string }).apiKey}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/contentful/src/lib/actions/index.ts b/packages/pieces/community/contentful/src/lib/actions/index.ts new file mode 100644 index 0000000..3be596f --- /dev/null +++ b/packages/pieces/community/contentful/src/lib/actions/index.ts @@ -0,0 +1 @@ +export * as RecordActions from './records'; diff --git a/packages/pieces/community/contentful/src/lib/actions/records/create-record.ts b/packages/pieces/community/contentful/src/lib/actions/records/create-record.ts new file mode 100644 index 0000000..9fbae08 --- /dev/null +++ b/packages/pieces/community/contentful/src/lib/actions/records/create-record.ts @@ -0,0 +1,73 @@ +import { + DynamicPropsValue, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { ContentfulAuth, PropertyKeys, makeClient } from '../../common'; +import { ContentfulProperty } from '../../properties'; +import { FieldProcessors } from '../../properties/processors'; + +function keyBy(array: T[], key: keyof T): { [key: string]: T } { + return (array || []).reduce((result, item) => { + const keyValue = key ? item[key] : (item as unknown as string); + result[keyValue as unknown as string] = item; + return result; + }, {} as { [key: string]: T }); +} + +export const ContentfulCreateRecordAction = createAction({ + name: 'contentful_record_create', + auth: ContentfulAuth, + displayName: 'Create Record', + description: 'Creates a new Contentful record for a given Content Model', + props: { + [PropertyKeys.LOCALE]: ContentfulProperty.Locale, + [PropertyKeys.CONTENT_MODEL]: ContentfulProperty.ContentModel, + [PropertyKeys.PUBLISH_ON_CREATE]: Property.Checkbox({ + displayName: 'Publish after Creating', + required: true, + description: 'Whether or not to publish this record after creating it.', + defaultValue: false, + }), + [PropertyKeys.FIELDS]: ContentfulProperty.DynamicFields, + }, + + async run({ auth, propsValue }) { + const { client } = makeClient(auth); + const model = await client.contentType.get({ + contentTypeId: propsValue[PropertyKeys.CONTENT_MODEL] as string, + }); + + const fields = keyBy(model.fields, 'id'); + const values = propsValue[PropertyKeys.FIELDS] as DynamicPropsValue; + // Remove empty fields + for (const key in values) { + if ( + values[key] === '' || + values[key] === null || + values[key] === undefined || + (Array.isArray(values[key]) && values[key].length === 0) + ) { + delete values[key]; + continue; + } + const fieldType = fields[key].type; + const processor = FieldProcessors[fieldType] || FieldProcessors['Basic']; + values[key] = { + [propsValue[PropertyKeys.LOCALE] as string]: await processor( + fields[key], + values[key] + ), + }; + } + console.debug('Creating record with values', values); + const record = await client.entry.create( + { contentTypeId: model.sys.id }, + { fields: values } + ); + if (propsValue[PropertyKeys.PUBLISH_ON_CREATE]) { + await client.entry.publish({ entryId: record.sys.id }, record); + } + return record; + }, +}); diff --git a/packages/pieces/community/contentful/src/lib/actions/records/get-record.ts b/packages/pieces/community/contentful/src/lib/actions/records/get-record.ts new file mode 100644 index 0000000..3af01eb --- /dev/null +++ b/packages/pieces/community/contentful/src/lib/actions/records/get-record.ts @@ -0,0 +1,22 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { ContentfulAuth, PropertyKeys, makeClient } from '../../common'; + +export const ContentfulGetRecordAction = createAction({ + name: 'contentful_record_get', + auth: ContentfulAuth, + displayName: 'Get Record', + description: 'Gets a Contentful record for a given Content Model', + props: { + [PropertyKeys.ENTITY_ID]: Property.ShortText({ + displayName: 'Entity ID', + required: true, + description: 'The ID of the record to get.', + }), + }, + async run({ auth, propsValue }) { + const { client } = makeClient(auth); + return await client.entry.get({ + entryId: propsValue[PropertyKeys.ENTITY_ID] as string, + }); + }, +}); diff --git a/packages/pieces/community/contentful/src/lib/actions/records/index.ts b/packages/pieces/community/contentful/src/lib/actions/records/index.ts new file mode 100644 index 0000000..ce49141 --- /dev/null +++ b/packages/pieces/community/contentful/src/lib/actions/records/index.ts @@ -0,0 +1,3 @@ +export * from './create-record'; +export * from './search-records'; +export * from './get-record'; diff --git a/packages/pieces/community/contentful/src/lib/actions/records/search-records.ts b/packages/pieces/community/contentful/src/lib/actions/records/search-records.ts new file mode 100644 index 0000000..a7ae730 --- /dev/null +++ b/packages/pieces/community/contentful/src/lib/actions/records/search-records.ts @@ -0,0 +1,65 @@ +import { + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { ContentfulAuth, PropertyKeys, makeClient } from '../../common'; +import { ContentfulProperty } from '../../properties'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const ContentfulSearchRecordsAction = createAction({ + name: 'contentful_record_search', + auth: ContentfulAuth, + displayName: 'Search Records', + description: 'Searches for records of a given Content Model', + props: { + [PropertyKeys.CONTENT_MODEL]: ContentfulProperty.ContentModel, + [PropertyKeys.LOCALE]: ContentfulProperty.Locale, + [PropertyKeys.QUERY]: Property.Json({ + required: true, + defaultValue: {}, + displayName: 'Query Formula', + description: + 'The query formula to use to search for records. See https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters for more information', + }), + [PropertyKeys.QUERY_LIMIT]: Property.Number({ + displayName: 'Limit', + description: 'The maximum number of records to return', + required: false, + defaultValue: 10, + }), + [PropertyKeys.QUERY_SKIP]: Property.Number({ + displayName: 'Skip', + description: 'The number of records to skip', + required: false, + defaultValue: 0, + }), + [PropertyKeys.QUERY_INCLUDE]: Property.Number({ + displayName: 'Relationship Include Depth', + description: + 'Number of levels to include for entries and assets. See https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/resource-links/retrieval-of-linked-resource-links', + defaultValue: 1, + required: false, + }), + [PropertyKeys.QUERY_SELECT]: ContentfulProperty.SelectFields, + }, + async run({ auth, propsValue }) { + await propsValidation.validateZod(propsValue, { + [PropertyKeys.QUERY_INCLUDE]: z.number().min(1), + }); + + const { client } = makeClient(auth); + const select = + (propsValue[PropertyKeys.QUERY_SELECT] as string[]) || undefined; + return client.entry.getMany({ + query: { + ...(propsValue[PropertyKeys.QUERY] as any), + limit: (propsValue[PropertyKeys.QUERY_LIMIT] as number) || 10, + skip: (propsValue[PropertyKeys.QUERY_SKIP] as number) || 0, + content_type: propsValue[PropertyKeys.CONTENT_MODEL] as string, + include: (propsValue[PropertyKeys.QUERY_INCLUDE] as number) || 1, + select: select?.join(','), + }, + }); + }, +}); diff --git a/packages/pieces/community/contentful/src/lib/common/auth.ts b/packages/pieces/community/contentful/src/lib/common/auth.ts new file mode 100644 index 0000000..93bc34b --- /dev/null +++ b/packages/pieces/community/contentful/src/lib/common/auth.ts @@ -0,0 +1,25 @@ +import { PieceAuth, Property } from '@activepieces/pieces-framework'; + +export interface ContentfulAuth { + apiKey: string; + environment: string; + space: string; +} + +export const ContentfulAuth = PieceAuth.CustomAuth({ + required: true, + props: { + apiKey: Property.ShortText({ + displayName: 'Contentful Access Token', + required: true, + }), + space: Property.ShortText({ + displayName: 'Space', + required: true, + }), + environment: Property.ShortText({ + displayName: 'Environment', + required: true, + }), + }, +}); diff --git a/packages/pieces/community/contentful/src/lib/common/client.ts b/packages/pieces/community/contentful/src/lib/common/client.ts new file mode 100644 index 0000000..15b9a7f --- /dev/null +++ b/packages/pieces/community/contentful/src/lib/common/client.ts @@ -0,0 +1,14 @@ +import { ContentfulAuth } from './auth'; +import * as Contentful from 'contentful-management'; + +export const makeClient = (auth: ContentfulAuth) => { + return { + client: Contentful.createClient( + { accessToken: auth.apiKey }, + { + type: 'plain', + defaults: { spaceId: auth.space, environmentId: auth.environment }, + } + ), + }; +}; diff --git a/packages/pieces/community/contentful/src/lib/common/constants.ts b/packages/pieces/community/contentful/src/lib/common/constants.ts new file mode 100644 index 0000000..15961d9 --- /dev/null +++ b/packages/pieces/community/contentful/src/lib/common/constants.ts @@ -0,0 +1,13 @@ +export const PropertyKeys = { + CONTENT_MODEL: 'contentModel', + FIELDS: 'fields', + PUBLISH_ON_CREATE: 'publishOnCreate', + LOCALE: 'locale', + ENTITY_ID: 'entityId', + SEARCH_FIELDS: 'searchFields', + QUERY: 'query', + QUERY_LIMIT: 'limit', + QUERY_SKIP: 'skip', + QUERY_INCLUDE: 'include', + QUERY_SELECT: 'select', +}; diff --git a/packages/pieces/community/contentful/src/lib/common/index.ts b/packages/pieces/community/contentful/src/lib/common/index.ts new file mode 100644 index 0000000..4b7b7b1 --- /dev/null +++ b/packages/pieces/community/contentful/src/lib/common/index.ts @@ -0,0 +1,4 @@ +export * from './auth'; +export * from './client'; +export * from './constants'; +export * from './utils'; diff --git a/packages/pieces/community/contentful/src/lib/common/utils.ts b/packages/pieces/community/contentful/src/lib/common/utils.ts new file mode 100644 index 0000000..8edea49 --- /dev/null +++ b/packages/pieces/community/contentful/src/lib/common/utils.ts @@ -0,0 +1,14 @@ +import { camelCase, startCase } from '@activepieces/shared'; +import { ContentFields } from 'contentful-management'; + +export const getLinkHelperText = ( + validations: ContentFields['validations'] +) => { + const mimes: string[] = + validations?.find((v) => v['linkMimetypeGroup'])?.['linkMimetypeGroup'] || + []; + const entryTypes: string[] = + validations?.find((v) => v['linkContentType'])?.['linkContentType'] || []; + const parts: string[] = [...mimes, ...entryTypes]; + return startCase(camelCase(parts.join(','))); +}; diff --git a/packages/pieces/community/contentful/src/lib/properties/content-model.ts b/packages/pieces/community/contentful/src/lib/properties/content-model.ts new file mode 100644 index 0000000..290751a --- /dev/null +++ b/packages/pieces/community/contentful/src/lib/properties/content-model.ts @@ -0,0 +1,57 @@ +import { DropdownOption, Property } from '@activepieces/pieces-framework'; +import { ContentfulAuth, makeClient } from '../common'; +import { isEmpty } from '@activepieces/shared'; + +const ContentModel = Property.Dropdown({ + displayName: 'Content Model', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (isEmpty(auth)) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account', + }; + } + const { client } = makeClient(auth as ContentfulAuth); + try { + const models: DropdownOption[] = []; + let contentModels = await client.contentType.getMany({ + query: { limit: 1000 }, + }); + models.push( + ...contentModels.items.map((model) => ({ + value: model.sys.id, + label: model.name, + })) + ); + while (contentModels.skip + contentModels.limit < contentModels.total) { + contentModels = await client.contentType.getMany({ + query: { skip: contentModels.skip + contentModels.limit }, + }); + models.push( + ...contentModels.items.map((model) => ({ + value: model.sys.id, + label: model.name, + })) + ); + } + return { + disabled: false, + options: models.sort((a, b) => + a.label < b.label ? -1 : a.label > b.label ? 1 : 0 + ), + }; + } catch (e) { + console.debug(e); + return { + disabled: true, + options: [], + placeholder: 'Please check your Contentful connection settings', + }; + } + }, +}); + +export default ContentModel; diff --git a/packages/pieces/community/contentful/src/lib/properties/dynamic-fields.ts b/packages/pieces/community/contentful/src/lib/properties/dynamic-fields.ts new file mode 100644 index 0000000..572989f --- /dev/null +++ b/packages/pieces/community/contentful/src/lib/properties/dynamic-fields.ts @@ -0,0 +1,52 @@ +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { ContentfulAuth, PropertyKeys, makeClient } from '../common'; +import { FieldTransformers } from './transformers'; +import { FieldType } from 'contentful-management'; +import { isEmpty, isNil } from '@activepieces/shared'; + +const DynamicFields = Property.DynamicProperties({ + displayName: 'Fields', + description: 'Fields for Content Model', + required: true, + refreshers: [PropertyKeys.CONTENT_MODEL, PropertyKeys.LOCALE], + props: async ({ + auth, + [PropertyKeys.CONTENT_MODEL]: model, + [PropertyKeys.LOCALE]: locale, + }) => { + if (isEmpty(auth) || isNil(model)) return {}; + const dynamicFields: DynamicPropsValue = {}; + const { client } = makeClient(auth as ContentfulAuth); + try { + const contentModel = await client.contentType.get({ + contentTypeId: model as unknown as string, + }); + // Remove fields that are disabled or omitted from the API + contentModel.fields + .filter((f) => !!f.id && !f.omitted && !f.disabled && !f.deleted) + .map((f) => { + const transformer = FieldTransformers[f.type as FieldType['type']]; + if (transformer) { + const property = transformer(f); + if (!property) return; + dynamicFields[f.id] = { + ...property, + defaultValue: f.defaultValue?.[locale as unknown as string], + }; + return; + } + dynamicFields[f.id] = Property.ShortText({ + displayName: f.name, + required: f.required, + description: 'Unsupported Field Type', + defaultValue: f.defaultValue?.[locale as unknown as string], + }); + }); + } catch (e) { + console.debug(e); + } + return dynamicFields; + }, +}); + +export default DynamicFields; diff --git a/packages/pieces/community/contentful/src/lib/properties/index.ts b/packages/pieces/community/contentful/src/lib/properties/index.ts new file mode 100644 index 0000000..81d86d2 --- /dev/null +++ b/packages/pieces/community/contentful/src/lib/properties/index.ts @@ -0,0 +1,11 @@ +import ContentModel from './content-model'; +import DynamicFields from './dynamic-fields'; +import Locale from './locale'; +import SelectFields from './select-fields'; + +export const ContentfulProperty = { + ContentModel, + DynamicFields, + Locale, + SelectFields, +}; diff --git a/packages/pieces/community/contentful/src/lib/properties/locale.ts b/packages/pieces/community/contentful/src/lib/properties/locale.ts new file mode 100644 index 0000000..54bc640 --- /dev/null +++ b/packages/pieces/community/contentful/src/lib/properties/locale.ts @@ -0,0 +1,38 @@ +import { DropdownOption, Property } from '@activepieces/pieces-framework'; +import { ContentfulAuth, makeClient } from '../common'; +import { isEmpty } from '@activepieces/shared'; + +const Locale = Property.Dropdown({ + displayName: 'Content Locale', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (isEmpty(auth)) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account', + }; + } + const { client } = makeClient(auth as ContentfulAuth); + try { + const response = await client.locale.getMany({}); + const options: DropdownOption[] = response.items.map( + (locale) => ({ label: locale.name, value: locale.code }) + ); + return { + disabled: false, + options, + }; + } catch (e) { + console.debug(e); + return { + disabled: true, + options: [], + placeholder: 'Please check your Contentful connection settings', + }; + } + }, +}); + +export default Locale; diff --git a/packages/pieces/community/contentful/src/lib/properties/processors.ts b/packages/pieces/community/contentful/src/lib/properties/processors.ts new file mode 100644 index 0000000..94d33ef --- /dev/null +++ b/packages/pieces/community/contentful/src/lib/properties/processors.ts @@ -0,0 +1,30 @@ +import { ContentFields } from 'contentful-management'; + +type FieldProcessor = (field: ContentFields, value: any) => any; + +export const FieldProcessors: Record = { + Link: (field, value) => { + return { + sys: { + type: 'Link', + linkType: field.linkType, + id: value, + }, + }; + }, + Array: (field, value) => { + if (field.items?.type === 'Symbol') { + return value; + } + if (field.items?.type === 'Link') { + return value.map((v: string) => ({ + sys: { + type: 'Link', + linkType: field.items?.linkType, + id: v, + }, + })); + } + }, + Basic: (field, value) => value, +}; diff --git a/packages/pieces/community/contentful/src/lib/properties/select-fields.ts b/packages/pieces/community/contentful/src/lib/properties/select-fields.ts new file mode 100644 index 0000000..4d6b238 --- /dev/null +++ b/packages/pieces/community/contentful/src/lib/properties/select-fields.ts @@ -0,0 +1,38 @@ +import { DropdownState, Property } from '@activepieces/pieces-framework'; +import { ContentfulAuth, PropertyKeys, makeClient } from '../common'; +import { isEmpty, isNil } from '@activepieces/shared'; + +const SelectFields = Property.MultiSelectDropdown({ + displayName: 'Return Fields', + description: 'The fields to return for each record.', + refreshers: [PropertyKeys.CONTENT_MODEL], + required: false, + options: async ({ auth, [PropertyKeys.CONTENT_MODEL]: model }) => { + const searchFields: DropdownState = { + options: [], + disabled: true, + placeholder: '', + }; + + if (isEmpty(auth) || isNil(model)) return searchFields; + + try { + const { client } = makeClient(auth as ContentfulAuth); + const contentType = await client.contentType.get({ + contentTypeId: model as unknown as string, + }); + // Process available options + searchFields.options = contentType.fields + .filter((f) => !!f.id && !f.omitted && !f.disabled && !f.deleted) + .map((f) => ({ label: f.name as string, value: `fields.${f.id}` })); + + searchFields.disabled = false; + searchFields.placeholder = 'Select fields to return'; + } catch (e) { + console.debug(e); + } + return searchFields; + }, +}); + +export default SelectFields; diff --git a/packages/pieces/community/contentful/src/lib/properties/transformers.ts b/packages/pieces/community/contentful/src/lib/properties/transformers.ts new file mode 100644 index 0000000..cceb5a4 --- /dev/null +++ b/packages/pieces/community/contentful/src/lib/properties/transformers.ts @@ -0,0 +1,157 @@ +import { + ArrayProperty, + BasePropertySchema, + CheckboxProperty, + DateTimeProperty, + DropdownOption, + LongTextProperty, + NumberProperty, + ObjectProperty, + Property, + ShortTextProperty, +} from '@activepieces/pieces-framework'; +import { ContentFields, FieldType } from 'contentful-management'; +import { getLinkHelperText } from '../common'; + +type Properties = Omit< + T, + | 'valueSchema' + | 'type' + | 'defaultValidators' + | 'defaultProcessors' + | 'required' + | 'displayName' +>; + +const evalAndConvertToStaticDropDown = ( + field: ContentFields +) => { + const options: DropdownOption[] = []; + if (field.validations) { + field.validations.forEach((v) => { + if (v['in']) { + options.push( + ...v['in'].map((o) => ({ label: o.toString(), value: o as T })) + ); + } + }); + } + if (options.length > 0) { + return Property.StaticDropdown({ + displayName: field.name, + required: field.required, + options: { + disabled: false, + placeholder: 'Select an option', + options, + }, + }); + } + return null; +}; + +const ShortTextTransformer = + (request: Properties> = {}) => + (field: ContentFields) => { + const isDropdown = evalAndConvertToStaticDropDown(field); + if (isDropdown) return isDropdown; + + return Property.ShortText({ + ...request, + displayName: field.name, + required: field.required, + }); + }; + +const LongTextTransformer = + (request: Properties> = {}) => + (field: ContentFields) => + Property.LongText({ + ...request, + displayName: field.name, + required: field.required, + }); + +const NumberTransformer = + (request: Properties> = {}) => + (field: ContentFields) => { + const isDrowdown = evalAndConvertToStaticDropDown(field); + if (isDrowdown) return isDrowdown; + + return Property.Number({ + ...request, + displayName: field.name, + required: field.required, + }); + }; + +const DateTimeTransformer = + (request: Properties> = {}) => + (field: ContentFields) => { + return Property.DateTime({ + ...request, + displayName: field.name, + required: field.required, + }); + }; + +const CheckboxTransformer = + (request: Properties> = {}) => + (field: ContentFields) => + Property.Checkbox({ + ...request, + displayName: field.name, + required: field.required, + }); + +const ObjectTransformer = + (request: Properties> = {}) => + (field: ContentFields) => + Property.Object({ + ...request, + displayName: field.name, + required: field.required, + }); + +const LinkTransformer = + (request: Properties> = {}) => + (field: ContentFields) => { + const prefix = field.linkType || field.type || ''; + const helperText = getLinkHelperText(field.validations); + + return ShortTextTransformer({ + ...request, + description: `${prefix}: ${helperText || 'Any'}`, + })(field); + }; + +const ArrayTransformer = + (request: Properties> = {}) => + (field: ContentFields) => { + const prefix = field.items?.linkType || field.items?.type || ''; + const helperText = getLinkHelperText(field.items?.validations); + + return Property.Array({ + ...request, + displayName: field.name, + required: field.required, + description: `${prefix}: ${helperText || 'Any'}`, + }); + }; + +type Transformer = (field: ContentFields) => BasePropertySchema | null; + +export const FieldTransformers: Record = { + Symbol: ShortTextTransformer({}), + Text: LongTextTransformer({}), + RichText: LongTextTransformer(), + Integer: NumberTransformer({}), + Number: NumberTransformer(), + Date: DateTimeTransformer(), + Boolean: CheckboxTransformer(), + Object: ObjectTransformer(), + Location: ObjectTransformer(), + Link: LinkTransformer(), + ResourceLink: LinkTransformer(), + Array: ArrayTransformer(), +}; diff --git a/packages/pieces/community/contentful/tsconfig.json b/packages/pieces/community/contentful/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/contentful/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/contentful/tsconfig.lib.json b/packages/pieces/community/contentful/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/contentful/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/contiguity/.eslintrc.json b/packages/pieces/community/contiguity/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/contiguity/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/contiguity/README.md b/packages/pieces/community/contiguity/README.md new file mode 100644 index 0000000..2298f10 --- /dev/null +++ b/packages/pieces/community/contiguity/README.md @@ -0,0 +1,7 @@ +# pieces-contiguity + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-contiguity` to build the library. diff --git a/packages/pieces/community/contiguity/package.json b/packages/pieces/community/contiguity/package.json new file mode 100644 index 0000000..c4bbe08 --- /dev/null +++ b/packages/pieces/community/contiguity/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-contiguity", + "version": "0.1.6" +} \ No newline at end of file diff --git a/packages/pieces/community/contiguity/project.json b/packages/pieces/community/contiguity/project.json new file mode 100644 index 0000000..7388c37 --- /dev/null +++ b/packages/pieces/community/contiguity/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-contiguity", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/contiguity/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/contiguity", + "tsConfig": "packages/pieces/community/contiguity/tsconfig.lib.json", + "packageJson": "packages/pieces/community/contiguity/package.json", + "main": "packages/pieces/community/contiguity/src/index.ts", + "assets": [ + "packages/pieces/community/contiguity/*.md", + { + "input": "packages/pieces/community/contiguity/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-contiguity {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/contiguity/src/index.ts b/packages/pieces/community/contiguity/src/index.ts new file mode 100644 index 0000000..05c214b --- /dev/null +++ b/packages/pieces/community/contiguity/src/index.ts @@ -0,0 +1,31 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { sendSMS } from './lib/actions/send-sms'; + +export const contigAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'API key acquired from your Contiguity settings', +}); + +export const contiguity = createPiece({ + displayName: 'Contiguity', + description: 'An SMS service for your needs - quick and simple', + auth: contigAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/contiguity.png', + authors: ["Owlcept","Ozak93","kishanprmr","MoShizzle","abuaboud"], + categories: [PieceCategory.MARKETING], + actions: [ + sendSMS, + createCustomApiCallAction({ + baseUrl: () => 'https://api.contiguity.com/v1', // Replace with the actual base URL + auth: contigAuth, + authMapping: async (auth) => ({ + authorization: `Token ${auth}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/contiguity/src/lib/actions/send-sms.ts b/packages/pieces/community/contiguity/src/lib/actions/send-sms.ts new file mode 100644 index 0000000..e325349 --- /dev/null +++ b/packages/pieces/community/contiguity/src/lib/actions/send-sms.ts @@ -0,0 +1,53 @@ +import { + HttpMethod, + httpClient, + HttpRequest, + propsValidation, +} from '@activepieces/pieces-common'; +import { contigAuth } from '../..'; +import { + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { z } from 'zod'; + +export const sendSMS = createAction({ + auth: contigAuth, + name: 'send_sms', + displayName: 'Send SMS', + description: 'Send a text message', + props: { + to: Property.ShortText({ + displayName: 'To', + description: + "Enter the recipient's phone number in international format with no spaces, following this pattern: [+][Country Code][Subscriber Number]. For example, +12065551234.", + required: true, + }), + message: Property.LongText({ + displayName: 'Content', + description: 'Message to send', + required: true, + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + to: z.string().regex(/^\+\d{1,4}\d+$/), + }); + + const { to, message } = context.propsValue; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: 'https://api.contiguity.co/send/text', + body: { + to: to, + message: message, + }, + headers: { + authorization: `Token ${context.auth}`, + 'Content-Type': 'application/json', + }, + }; + return await httpClient.sendRequest(request); + }, +}); diff --git a/packages/pieces/community/contiguity/tsconfig.json b/packages/pieces/community/contiguity/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/contiguity/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/contiguity/tsconfig.lib.json b/packages/pieces/community/contiguity/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/contiguity/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/convertkit/.eslintrc.json b/packages/pieces/community/convertkit/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/convertkit/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/convertkit/README.md b/packages/pieces/community/convertkit/README.md new file mode 100644 index 0000000..2152429 --- /dev/null +++ b/packages/pieces/community/convertkit/README.md @@ -0,0 +1,29 @@ +# Notes + +## Untested pieces + +The following webhook trigger event types (https://developers.convertkit.com/#webhooks) have been implemented but not yet fully tested. + +- subscriber.subscriber_complain + +- subscriber.product_purchase + +- purchase.purchase_create + + + +## No debounce in form fields + +I have not implemented a debounce in any form fields. This means that calls to the ConvertKit API will be triggered on every keystroke for certain fields. + +I have raised an issue here: https://github.com/activepieces/activepieces/issues/3142 + +# Building + +Run `nx build pieces-convertkit` to build the library. + +# ToDo + +- Versioning of piece https://www.activepieces.com/docs/developers/piece-reference/piece-versioning +- Debounce on form fields +- Add tests(?) diff --git a/packages/pieces/community/convertkit/package.json b/packages/pieces/community/convertkit/package.json new file mode 100644 index 0000000..e427317 --- /dev/null +++ b/packages/pieces/community/convertkit/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-convertkit", + "version": "0.2.1" +} \ No newline at end of file diff --git a/packages/pieces/community/convertkit/project.json b/packages/pieces/community/convertkit/project.json new file mode 100644 index 0000000..17edec6 --- /dev/null +++ b/packages/pieces/community/convertkit/project.json @@ -0,0 +1,48 @@ +{ + "name": "pieces-convertkit", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/convertkit/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/convertkit", + "tsConfig": "packages/pieces/community/convertkit/tsconfig.lib.json", + "packageJson": "packages/pieces/community/convertkit/package.json", + "main": "packages/pieces/community/convertkit/src/index.ts", + "assets": [ + "packages/pieces/community/convertkit/*.md", + { + "input": "packages/pieces/community/convertkit/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-convertkit {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ], + "options": { + "lintFilePatterns": [ + "packages/pieces/community/convertkit/**/*.ts" + ] + } + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/convertkit/src/index.ts b/packages/pieces/community/convertkit/src/index.ts new file mode 100644 index 0000000..afdd228 --- /dev/null +++ b/packages/pieces/community/convertkit/src/index.ts @@ -0,0 +1,139 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { + createField, + deleteField, + listFields, + updateField, +} from './lib/actions/custom-fields'; +import { + getSubscriberByEmail, + getSubscriberById, + listSubscribers, + listSubscriberTagsByEmail, + listTagsBySubscriberId, + unsubscribeSubscriber, + updateSubscriber, +} from './lib/actions/subscribers'; + +import { createWebhook, deleteWebhook } from './lib/actions/webhooks'; + +import { + broadcastStats, + createBroadcast, + deleteBroadcast, + getBroadcastById, + listBroadcasts, + updateBroadcast, +} from './lib/actions/broadcasts'; + +import { + addSubscriberToForm, + listForms, + listFormSubscriptions, +} from './lib/actions/forms'; + +import { + addSubscriberToSequence, + listSequences, + listSubscriptionsToSequence, +} from './lib/actions/sequences'; + +import { + createTag, + listSubscriptionsToATag, + listTags, + removeTagFromSubscriberByEmail, + removeTagFromSubscriberById, + tagSubscriber, +} from './lib/actions/tags'; + +import { + createPurchases, + createSinglePurchase, + getPurchaseById, + listPurchases, +} from './lib/actions/purchases'; + +import { PieceCategory } from '@activepieces/shared'; +import { + addTag, + formSubscribed, + linkClicked, + productPurchased, + purchaseCreated, + removeTag, + sequenceCompleted, + sequenceSubscribed, + subscriberActivated, + subscriberBounced, + subscriberComplained, + subscriberUnsubscribed, +} from './lib/triggers'; + +export const convertkitAuth = PieceAuth.SecretText({ + displayName: 'API Secret', + description: 'Enter your API Secret key', + required: true, +}); + +export const convertkit = createPiece({ + displayName: 'ConvertKit', + description: 'Email marketing for creators', + + auth: convertkitAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/convertkit.png', + categories: [PieceCategory.MARKETING], + authors: ["Gunther-Schulz","kishanprmr","abuaboud"], + actions: [ + getSubscriberById, + getSubscriberByEmail, + listSubscribers, + updateSubscriber, + unsubscribeSubscriber, + listSubscriberTagsByEmail, + listTagsBySubscriberId, + createWebhook, + deleteWebhook, + listFields, + createField, + updateField, + deleteField, + listBroadcasts, + createBroadcast, + getBroadcastById, + updateBroadcast, + deleteBroadcast, + broadcastStats, + listForms, + addSubscriberToForm, + listFormSubscriptions, + listSequences, + addSubscriberToSequence, + listSubscriptionsToSequence, + listTags, + createTag, + tagSubscriber, + removeTagFromSubscriberByEmail, + removeTagFromSubscriberById, + listSubscriptionsToATag, + listPurchases, + getPurchaseById, + createSinglePurchase, + createPurchases, + ], + triggers: [ + addTag, + removeTag, + subscriberActivated, + subscriberUnsubscribed, + subscriberBounced, + subscriberComplained, + formSubscribed, + sequenceSubscribed, + sequenceCompleted, + linkClicked, + productPurchased, + purchaseCreated, + ], +}); diff --git a/packages/pieces/community/convertkit/src/lib/actions/broadcasts.ts b/packages/pieces/community/convertkit/src/lib/actions/broadcasts.ts new file mode 100644 index 0000000..d3dda7b --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/actions/broadcasts.ts @@ -0,0 +1,267 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { convertkitAuth } from '../..'; +import { + broadcastId, + broadcastPageNumber, + broadcastContent, + description, + broadcastEmailAddress, + emailLayoutTemplate, + isPublic, + publishedAt, + sendAt, + subject, + thumbnailAlt, + thumbnailUrl, +} from '../common/broadcasts'; +import { Broadcast } from '../common/types'; +import { BROADCASTS_API_ENDPOINT } from '../common/constants'; +import { fetchBroadcasts } from '../common/service'; + +export const listBroadcasts = createAction({ + auth: convertkitAuth, + name: 'broadcasts_list_broadcasts', + displayName: 'List Broadcasts', + description: 'List all broadcasts', + props: { + page: broadcastPageNumber, + }, + run(context) { + const page = context.propsValue.page || 1; + return fetchBroadcasts(context.auth, page); + }, +}); + +export const createBroadcast = createAction({ + auth: convertkitAuth, + name: 'broadcasts_create_broadcast', + displayName: 'Create Broadcast', + description: 'Create a new broadcast', + props: { + content: broadcastContent, + description, + emailAddress: broadcastEmailAddress, + emailLayoutTemplate, + isPublic, + publishedAt, + sendAt, + subject, + thumbnailAlt, + thumbnailUrl, + }, + async run(context) { + const { + content, + description, + emailAddress, + emailLayoutTemplate, + isPublic, + publishedAt, + sendAt, + subject, + thumbnailAlt, + thumbnailUrl, + } = context.propsValue; + const url = BROADCASTS_API_ENDPOINT; + + const body = { + api_secret: context.auth, + content, + description, + email_address: emailAddress, + email_layout_template: emailLayoutTemplate, + public: isPublic, + published_at: publishedAt, + send_at: sendAt, + subject, + thumbnail_alt: thumbnailAlt, + thumbnail_url: thumbnailUrl, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.POST, + body, + headers: { + 'Content-Type': 'application/json', + }, + }; + const response = await httpClient.sendRequest<{ broadcast: Broadcast }>( + request + ); + if (response.status !== 201) { + throw new Error(`Error creating broadcast: ${response.status}`); + } + return response.body.broadcast; + }, +}); + +export const getBroadcastById = createAction({ + auth: convertkitAuth, + name: 'broadcasts_get_broadcast', + displayName: 'Get Broadcast', + description: 'Get a broadcast', + props: { + broadcastId: broadcastId, + }, + async run(context) { + const { broadcastId } = context.propsValue; + const url = `${BROADCASTS_API_ENDPOINT}/${broadcastId}`; + + const body = { + api_secret: context.auth, + }; + + const request: HttpRequest = { + url, + body, + method: HttpMethod.GET, + }; + const response = await httpClient.sendRequest<{ broadcast: Broadcast }>( + request + ); + if (response.status !== 200) { + throw new Error(`Error fetching broadcast: ${response.status}`); + } + return response.body.broadcast; + }, +}); + +export const updateBroadcast = createAction({ + auth: convertkitAuth, + name: 'broadcasts_update_broadcast', + displayName: 'Update Broadcast', + description: 'Update a broadcast', + props: { + broadcastId: broadcastId, + content: broadcastContent, + description, + emailAddress: broadcastEmailAddress, + emailLayoutTemplate, + isPublic, + publishedAt, + sendAt, + subject, + thumbnailAlt, + thumbnailUrl, + }, + + async run(context) { + const { + broadcastId, + content, + description, + emailAddress, + emailLayoutTemplate, + isPublic, + publishedAt, + sendAt, + subject, + thumbnailAlt, + thumbnailUrl, + } = context.propsValue; + + const url = `${BROADCASTS_API_ENDPOINT}/${broadcastId}`; + + const body = { + api_secret: context.auth, + content, + description, + email_address: emailAddress, + email_layout_template: emailLayoutTemplate, + public: isPublic, + published_at: publishedAt, + send_at: sendAt, + subject, + thumbnail_alt: thumbnailAlt, + thumbnail_url: thumbnailUrl, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.PUT, + body, + headers: { + 'Content-Type': 'application/json', + }, + }; + const response = await httpClient.sendRequest<{ broadcast: Broadcast }>( + request + ); + if (response.status !== 200) { + throw new Error(`Error updating broadcast: ${response.status}`); + } + return response.body.broadcast; + }, +}); + +export const broadcastStats = createAction({ + auth: convertkitAuth, + name: 'broadcasts_broadcast_stats', + displayName: 'Broadcast Stats', + description: 'Get broadcast stats', + props: { + broadcastId: broadcastId, + }, + async run(context) { + const { broadcastId } = context.propsValue; + const url = `${BROADCASTS_API_ENDPOINT}/${broadcastId}/stats`; + + const body = { + api_secret: context.auth, + }; + + const request: HttpRequest = { + url, + body, + method: HttpMethod.GET, + }; + const response = await httpClient.sendRequest<{ broadcast: Broadcast }>( + request + ); + if (response.status !== 200) { + throw new Error(`Error fetching broadcast stats: ${response.status}`); + } + return response.body.broadcast; + }, +}); + +export const deleteBroadcast = createAction({ + auth: convertkitAuth, + name: 'broadcasts_delete_broadcast', + displayName: 'Delete Broadcast', + description: 'Delete a broadcast', + props: { + broadcastId: broadcastId, + }, + async run(context) { + const { broadcastId } = context.propsValue; + const url = `${BROADCASTS_API_ENDPOINT}/${broadcastId}`; + + const body = { + api_secret: context.auth, + }; + + const request: HttpRequest = { + url, + body, + method: HttpMethod.DELETE, + }; + const response = await httpClient.sendRequest<{ broadcast: Broadcast }>( + request + ); + if (response.status !== 204) { + throw new Error(`Error deleting broadcast: ${response.status}`); + } + return { + message: `Broadcast ${broadcastId} deleted successfully`, + status: response.status, + success: true, + }; + }, +}); diff --git a/packages/pieces/community/convertkit/src/lib/actions/custom-fields.ts b/packages/pieces/community/convertkit/src/lib/actions/custom-fields.ts new file mode 100644 index 0000000..af5ff99 --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/actions/custom-fields.ts @@ -0,0 +1,126 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { convertkitAuth } from '../..'; +import { fieldsArray, label, new_label } from '../common/custom-fields'; +import { CustomField } from '../common/types'; +import { CUSTOM_FIELDS_API_ENDPOINT } from '../common/constants'; +import { fetchCustomFields } from '../common/service'; + +export const listFields = createAction({ + auth: convertkitAuth, + name: 'custom_fields_list_fields', + displayName: 'List Custom Fields', + description: 'Returns a list of all custom fields', + props: {}, + async run(context) { + return fetchCustomFields(context.auth); + }, +}); + +export const createField = createAction({ + auth: convertkitAuth, + name: 'custom_fields_create_field', + displayName: 'Create Custom Field', + description: 'Create a new custom field', + props: { + fields: fieldsArray, + }, + async run(context) { + const url = CUSTOM_FIELDS_API_ENDPOINT; + + const body = { + api_secret: context.auth, + label: context.propsValue.fields, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.POST, + body, + }; + + const response = await httpClient.sendRequest<{ + custom_field: CustomField; + }>(request); + + if (response.status !== 201) { + throw new Error(`Error creating field: ${response.status}`); + } + return response.body; + }, +}); + +export const updateField = createAction({ + auth: convertkitAuth, + name: 'custom_fields_update_field', + displayName: 'Custom Fields: Update Field', + description: 'Update a custom field', + props: { + label, + new_label, + }, + async run(context) { + const { label, new_label } = context.propsValue; + + const url = `${CUSTOM_FIELDS_API_ENDPOINT}/${label}`; + + const body = { + api_secret: context.auth, + label: new_label, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.PUT, + body, + }; + + const response = await httpClient.sendRequest<{ + custom_field: CustomField; + }>(request); + + if (response.status !== 204) { + throw new Error(`Error updating field: ${response.status}`); + } + return { status: response.status, message: `Field updated`, success: true }; + }, +}); + +export const deleteField = createAction({ + auth: convertkitAuth, + name: 'custom_fields_delete_field', + displayName: 'Custom Fields: Delete Field', + description: 'Delete a custom field', + props: { + label, + }, + async run(context) { + const { label } = context.propsValue; + + const url = `${CUSTOM_FIELDS_API_ENDPOINT}/${label}`; + + const body = { + api_secret: context.auth, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.DELETE, + body, + }; + + const response = await httpClient.sendRequest<{ + custom_field: CustomField; + }>(request); + + if (response.status !== 204) { + throw new Error(`Error deleting field: ${response.status}`); + } + + return { status: response.status, message: `Field deleted`, success: true }; + }, +}); diff --git a/packages/pieces/community/convertkit/src/lib/actions/forms.ts b/packages/pieces/community/convertkit/src/lib/actions/forms.ts new file mode 100644 index 0000000..fe40223 --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/actions/forms.ts @@ -0,0 +1,108 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { Subscription } from '../common/types'; +import { convertkitAuth } from '../..'; +import { formId } from '../common/forms'; +import { subscriberEmail, subscriberFirstName } from '../common/subscribers'; +import { allFields } from '../common/custom-fields'; +import { FORMS_API_ENDPOINT } from '../common/constants'; +import { fetchForms } from '../common/service'; +import { tags } from '../common/tags'; + +export const listForms = createAction({ + auth: convertkitAuth, + name: 'forms_list_forms', + displayName: 'List Forms', + description: 'Returns a list of all forms', + props: {}, + run(context) { + return fetchForms(context.auth); + }, +}); + +// Clone and set required to false for custom fields property +const allFieldsRequiredRefreshers = { + ...allFields, + required: false, + refreshers: ['auth, formId'], +}; + +// TODO: fields do not show up. Why? +export const addSubscriberToForm = createAction({ + auth: convertkitAuth, + name: 'forms_add_subscriber_to_form', + displayName: 'Add Subscriber To Form', + description: 'Add a subscriber to a form', + props: { + formId, + email: subscriberEmail, + firstName: subscriberFirstName, + tags, + fields: allFieldsRequiredRefreshers, + }, + async run(context) { + const { formId, email, firstName, tags, fields } = context.propsValue; + + const url = `${FORMS_API_ENDPOINT}/${formId}/subscribe`; + + const body = { + api_secret: context.auth, + email, + first_name: firstName, + tags, + fields, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.POST, + body, + }; + + const response = await httpClient.sendRequest<{ + subscription: Subscription; + }>(request); + + if (response.status !== 200) { + throw new Error(`Error adding subscriber to form: ${response.status}`); + } + return response.body.subscription; + }, +}); + +export const listFormSubscriptions = createAction({ + auth: convertkitAuth, + name: 'forms_list_form_subscriptions', + displayName: 'List Form Subscriptions', + description: 'List form subscriptions', + props: { + formId, + }, + async run(context) { + const url = `${FORMS_API_ENDPOINT}/${context.propsValue.formId}/subscriptions`; + + const body = { + api_secret: context.auth, + }; + + const request: HttpRequest = { + url, + body, + method: HttpMethod.GET, + }; + + const response = await httpClient.sendRequest<{ + subscriptions: Subscription[]; + }>(request); + + if (response.status !== 200) { + throw new Error(`Error listing form subscriptions: ${response.status}`); + } + + return response.body.subscriptions; + }, +}); diff --git a/packages/pieces/community/convertkit/src/lib/actions/purchases.ts b/packages/pieces/community/convertkit/src/lib/actions/purchases.ts new file mode 100644 index 0000000..9a2f9a0 --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/actions/purchases.ts @@ -0,0 +1,346 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { convertkitAuth } from '../..'; +import { + purchaseId, + purchasesPageNumber, + transactionId, + productId, + transactionTime, + purchaserEmailAddress, + status, + currency, + subtotal, + shipping, + discount, + tax, + total, + products, + multipleProducts, +} from '../common/purchases'; +import { subscriberFirstName } from '../common/subscribers'; +import { Purchase } from '../common/types'; +import { subscriberId } from '../common/subscribers'; +import { formId } from '../common/forms'; +import { sequenceId } from '../common/sequences'; +import { PURCHASES_API_ENDPOINT } from '../common/constants'; +import { fetchPurchases } from '../common/service'; + +export const listPurchases = createAction({ + auth: convertkitAuth, + name: 'purchases_list_purchases', + displayName: 'List Purchases', + description: 'Returns a list of all purchases', + props: { + page: purchasesPageNumber, + }, + run(context) { + const page = context.propsValue.page || 1; + return fetchPurchases(context.auth, page); + }, +}); + +export const getPurchaseById = createAction({ + auth: convertkitAuth, + name: 'purchases_get_purchase_by_id', + displayName: 'Get Purchase By Id', + description: 'Returns data for a single purchase', + props: { + purchaseId, + }, + async run(context) { + const { purchaseId } = context.propsValue; + const url = `${PURCHASES_API_ENDPOINT}/${purchaseId}`; + + const body = { + api_secret: context.auth, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.GET, + body, + }; + + const response = await httpClient.sendRequest<{ + purchase: Purchase; + }>(request); + + if (response.status !== 200) { + throw new Error(`Error fetching purchase: ${response.status}`); + } + + return response.body; + }, +}); + +//TODO: +// Required for third party integrations +// Are you building an integration? Please fill out this form and we will help you get set up. + +// purchase.integration - The name of your integration (i.e. eBay) +// integration_key - A token for tracking integrations (i.e. eBay order number) + +export const createSinglePurchase = createAction({ + auth: convertkitAuth, + name: 'purchases_create_purchase', + displayName: 'Create Purchase', + description: 'Creates a new purchase', + props: { + transactionId, + transactionTime, + emailAddress: purchaserEmailAddress, + firstName: subscriberFirstName, + status, + currency, + subtotal, + shipping, + discount, + tax, + total, + ...products, + }, + async run(context) { + const { + transactionId, + transactionTime, + emailAddress, + firstName, + status, + currency, + subtotal, + shipping, + discount, + tax, + total, + ...products + } = context.propsValue; + const url = PURCHASES_API_ENDPOINT; + + const body = { + api_secret: context.auth, + purchase: { + transaction_id: transactionId, + status, + first_name: firstName, + email_address: emailAddress, + currency, + transaction_time: transactionTime, + subtotal, + shipping, + discount, + tax, + total, + products: [products], + }, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.POST, + body, + }; + + const response = await httpClient.sendRequest<{ + purchase: Purchase; + }>(request); + + if (response.status !== 201) { + throw new Error(`Error creating purchase: ${response.status}`); + } + + return response.body; + }, +}); + +export const createPurchases = createAction({ + auth: convertkitAuth, + name: 'purchases_create_multiple_purchases', + displayName: 'Create Multiple Purchases', + description: 'Creates multiple purchases', + props: { + transactionId, + transactionTime, + emailAddress: purchaserEmailAddress, + firstName: subscriberFirstName, + status, + currency, + subtotal, + shipping, + discount, + tax, + total, + multipleProducts, + }, + async run(context) { + const { + transactionId, + transactionTime, + emailAddress, + firstName, + status, + currency, + subtotal, + shipping, + discount, + tax, + total, + multipleProducts, + } = context.propsValue; + const url = PURCHASES_API_ENDPOINT; + + const body = { + api_secret: context.auth, + purchase: { + transaction_id: transactionId, + status, + email_address: emailAddress, + first_name: firstName, + currency, + transaction_time: transactionTime, + subtotal, + shipping, + discount, + tax, + total, + products: multipleProducts, + }, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.POST, + body, + }; + + const response = await httpClient.sendRequest<{ + purchase: Purchase; + }>(request); + + if (response.status !== 201) { + throw new Error(`Error creating purchase: ${response.status}`); + } + + return response.body; + }, +}); + +// ---------> Below are "unofficial". Need to be tested. <--------- +// Show all purchases for a subscriber + +export const listPurchasesForSubscriber = createAction({ + auth: convertkitAuth, + name: 'purchases_list_purchases_for_subscriber', + displayName: 'List Purchases For Subscriber', + description: 'Returns a list of all purchases for a subscriber', + props: { + subscriberId, + }, + async run(context) { + const { subscriberId } = context.propsValue; + const url = PURCHASES_API_ENDPOINT; + + const body = { + api_secret: context.auth, + subscriber_id: subscriberId, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.GET, + body, + }; + + const response = await httpClient.sendRequest<{ + purchases: Purchase[]; + }>(request); + + if (response.status !== 200) { + throw new Error(`Error fetching purchases: ${response.status}`); + } + + return response.body.purchases; + }, +}); + +// Show all purchases for a product + +export const listPurchasesForProduct = createAction({ + auth: convertkitAuth, + name: 'purchases_list_purchases_for_product', + displayName: 'List Purchases For Product', + description: 'Returns a list of all purchases for a product', + props: { + productId, + }, + async run(context) { + const { productId } = context.propsValue; + const url = `${PURCHASES_API_ENDPOINT}?api_secret=${context.auth}&product_id=${productId}`; + + const response = await fetch(url); + + if (!response.ok) { + return { success: false, message: 'Error fetching purchases' }; + } + + const data = await response.json(); + + return data; + }, +}); + +// Show all purchases for a form + +export const listPurchasesForForm = createAction({ + auth: convertkitAuth, + name: 'purchases_list_purchases_for_form', + displayName: 'List Purchases For Form', + description: 'Returns a list of all purchases for a form', + props: { + formId, + }, + async run(context) { + const { formId } = context.propsValue; + const url = `${PURCHASES_API_ENDPOINT}?api_secret=${context.auth}&form_id=${formId}`; + + const response = await fetch(url); + + if (!response.ok) { + return { success: false, message: 'Error fetching purchases' }; + } + + const data = await response.json(); + + return data; + }, +}); + +// Show all purchases for a sequence + +export const listPurchasesForSequence = createAction({ + auth: convertkitAuth, + name: 'purchases_list_purchases_for_sequence', + displayName: 'List Purchases For Sequence', + description: 'Returns a list of all purchases for a sequence', + props: { + sequenceId, + }, + async run(context) { + const { sequenceId } = context.propsValue; + const url = `${PURCHASES_API_ENDPOINT}?api_secret=${context.auth}&sequence_id=${sequenceId}`; + + const response = await fetch(url); + + if (!response.ok) { + return { success: false, message: 'Error fetching purchases' }; + } + + const data = await response.json(); + + return data; + }, +}); diff --git a/packages/pieces/community/convertkit/src/lib/actions/sequences.ts b/packages/pieces/community/convertkit/src/lib/actions/sequences.ts new file mode 100644 index 0000000..ada2453 --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/actions/sequences.ts @@ -0,0 +1,102 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { Sequence } from '../common/types'; +import { convertkitAuth } from '../..'; +import { sequenceIdDropdown } from '../common/sequences'; +import { subscriberEmail, subscriberFirstName } from '../common/subscribers'; +import { allFields } from '../common/custom-fields'; +import { tags } from '../common/tags'; +import { SEQUENCES_API_ENDPOINT } from '../common/constants'; +import { fetchSequences } from '../common/service'; + +export const listSequences = createAction({ + auth: convertkitAuth, + name: 'sequences_list_sequences', + displayName: 'List Sequences', + description: 'Returns a list of all sequences', + props: {}, + run(context) { + return fetchSequences(context.auth); + }, +}); + +export const addSubscriberToSequence = createAction({ + auth: convertkitAuth, + name: 'sequences_add_subscriber_to_sequence', + displayName: 'Add Subscriber To Sequence', + description: 'Add a subscriber to a sequence', + props: { + sequenceId: sequenceIdDropdown, + email: subscriberEmail, + firstName: subscriberFirstName, + tags, + fields: allFields, + }, + async run(context) { + const { sequenceId, email, firstName, tags, fields } = context.propsValue; + const url = `${SEQUENCES_API_ENDPOINT}/${sequenceId}/subscribe`; + + const body = { + api_secret: context.auth, + email, + first_name: firstName, + tags, + fields, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.POST, + body, + }; + + const response = await httpClient.sendRequest<{ + subscription: Sequence; + }>(request); + + if (response.status !== 200) { + throw new Error( + `Error adding subscriber to sequence: ${response.status}` + ); + } + return response.body.subscription; + }, +}); + +export const listSubscriptionsToSequence = createAction({ + auth: convertkitAuth, + name: 'sequences_list_subscriptions_to_sequence', + displayName: 'List Subscriptions To Sequence', + description: 'List all subscriptions to a sequence', + props: { + sequenceId: sequenceIdDropdown, + }, + async run(context) { + const url = `${SEQUENCES_API_ENDPOINT}/${context.propsValue.sequenceId}/subscriptions`; + + const body = { + api_secret: context.auth, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.GET, + body, + }; + + const response = await httpClient.sendRequest<{ + subscriptions: Sequence[]; + }>(request); + + if (response.status !== 200) { + throw new Error( + `Error listing subscriptions to sequence: ${response.status}` + ); + } + return response.body.subscriptions; + }, +}); diff --git a/packages/pieces/community/convertkit/src/lib/actions/subscribers.ts b/packages/pieces/community/convertkit/src/lib/actions/subscribers.ts new file mode 100644 index 0000000..8c5c921 --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/actions/subscribers.ts @@ -0,0 +1,223 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { Subscriber } from '../common/types'; +import { convertkitAuth } from '../..'; +import { + subscriberId, + subscriberEmail, + subscriberEmailOptional, + subscribersPageNumber, + subscriberFirstName, + from, + to, + updatedFrom, + updatedTo, + sortOrder, + sortField, +} from '../common/subscribers'; +import { allFields } from '../common/custom-fields'; +import { + SUBSCRIBERS_API_ENDPOINT, + CONVERTKIT_API_URL, +} from '../common/constants'; +import { + fetchSubscriperById, + fetchSubscriberByEmail, + fetchSubscribedTags, +} from '../common/service'; + +export const getSubscriberById = createAction({ + auth: convertkitAuth, + name: 'subscribers_get_subscriber_by_id', + displayName: 'Get Subscriber By Id', + description: 'Returns data for a single subscriber', + props: { + subscriberId, + }, + run(context) { + const { subscriberId } = context.propsValue; + return fetchSubscriperById(context.auth, subscriberId); + }, +}); + +export const getSubscriberByEmail = createAction({ + auth: convertkitAuth, + name: 'subscribers_get_subscriber_by_email', + displayName: 'Get Subscriber By Email', + description: 'Returns data for a single subscriber', + props: { + email_address: subscriberEmail, + }, + async run(context) { + const { email_address } = context.propsValue; + return fetchSubscriberByEmail(context.auth, email_address); + }, +}); + +export const listSubscribers = createAction({ + auth: convertkitAuth, + name: 'subscribers_list_subscribers', + displayName: 'List Subscribers', + description: 'Returns a list of all subscribers', + props: { + page: subscribersPageNumber, + sortOrder, + sortField, + from, + to, + updatedFrom, + updatedTo, + emailAddress: subscriberEmail, + }, + async run(context) { + const { + page, + from, + to, + updatedFrom, + updatedTo, + emailAddress, + sortOrder, + sortField, + } = context.propsValue; + + const url = SUBSCRIBERS_API_ENDPOINT; + + const body = { + api_secret: context.auth, + page, + from, + to, + updated_from: updatedFrom, + updated_to: updatedTo, + email_address: emailAddress, + sort_order: sortOrder, + sort_field: sortField, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.GET, + body, + }; + + const response = await httpClient.sendRequest<{ + subscribers: Subscriber[]; + }>(request); + + if (response.status !== 200) { + throw new Error(`Error fetching subscribers: ${response.status}`); + } + + return response.body.subscribers; + }, +}); + +export const updateSubscriber = createAction({ + auth: convertkitAuth, + name: 'subscribers_update_subscriber', + displayName: 'Update Subscriber', + description: 'Update a subscriber', + props: { + subscriberId, + emailAddress: subscriberEmailOptional, + firstName: subscriberFirstName, + fields: allFields, + }, + async run(context) { + const { subscriberId, emailAddress, firstName, fields } = + context.propsValue; + + const url = `${SUBSCRIBERS_API_ENDPOINT}/${subscriberId}`; + const body = { + api_secret: context.auth, + email_address: emailAddress, + first_name: firstName, + fields, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.PUT, + body, + }; + + const response = await httpClient.sendRequest<{ subscriber: Subscriber }>( + request + ); + + if (response.status !== 200) { + throw new Error(`Error updating subscriber: ${response.status}`); + } + + return response.body.subscriber; + }, +}); + +export const unsubscribeSubscriber = createAction({ + auth: convertkitAuth, + name: 'subscribers_unsubscribe_subscriber', + displayName: 'Unsubscribe Subscriber', + description: 'Unsubscribe a subscriber', + props: { + email: subscriberEmail, + }, + async run(context) { + const { email } = context.propsValue; + const url = `${CONVERTKIT_API_URL}/unsubscribe`; + + const body = { email, api_secret: context.auth }; + + const request: HttpRequest = { + url, + method: HttpMethod.PUT, + body, + }; + + const response = await httpClient.sendRequest<{ + subscriber: Subscriber; + }>(request); + + if (response.status !== 200) { + throw new Error(`Error unsubscribing subscriber: ${response.status}`); + } + + return response.body.subscriber; + }, +}); + +export const listTagsBySubscriberId = createAction({ + auth: convertkitAuth, + name: 'subscribers_list_tags_by_subscriber_id', + displayName: 'List Tags By Subscriber Id', + description: 'Returns a list of all subscribed tags', + props: { + subscriberId, + }, + run(context) { + const { subscriberId } = context.propsValue; + return fetchSubscribedTags(context.auth, subscriberId); + }, +}); + +export const listSubscriberTagsByEmail = createAction({ + auth: convertkitAuth, + name: 'subscribers_list_tags_by_email', + displayName: 'List Tags By Email', + description: 'Returns a list of all subscribed tags', + props: { + email_address: subscriberEmail, + }, + async run(context) { + const { email_address } = context.propsValue; + const subscriberId = await fetchSubscriberByEmail( + context.auth, + email_address + ); + return fetchSubscribedTags(context.auth, subscriberId.id); + }, +}); diff --git a/packages/pieces/community/convertkit/src/lib/actions/tags.ts b/packages/pieces/community/convertkit/src/lib/actions/tags.ts new file mode 100644 index 0000000..e778a27 --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/actions/tags.ts @@ -0,0 +1,227 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { convertkitAuth } from '../..'; +import { + tag, + tagsRequired, + name, + tagIdByEmail, + tagIdBySubscriberId, + tagsPageNumber, + sortOrder, + subscriberState, +} from '../common/tags'; +import { + subscriberId, + subscriberEmail, + subscriberFirstName, +} from '../common/subscribers'; +import { Tag } from '../common/types'; +import { allFields } from '../common/custom-fields'; +import { TAGS_API_ENDPOINT } from '../common/constants'; +import { fetchTags } from '../common/service'; + +export const listTags = createAction({ + auth: convertkitAuth, + name: 'tags_list_tags', + displayName: 'List Tags', + description: 'Returns a list of all tags', + props: {}, + run(context) { + return fetchTags(context.auth); + }, +}); + +export const createTag = createAction({ + auth: convertkitAuth, + name: 'tags_create_tag', + displayName: 'Create Tag', + description: 'Create a tag', + props: { + name, + }, + async run(context) { + const url = TAGS_API_ENDPOINT; + + const body = { + api_secret: context.auth, + tag: { name: context.propsValue.name }, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.POST, + body, + }; + + const response = await httpClient.sendRequest<{ tag: Tag }>(request); + + if (response.status !== 201) { + throw new Error(`Error creating tag: ${response.status}`); + } + + return response.body; + }, +}); + +// TODO: +// fields do not show up in the UI +// Sometimes the Tags dropdown will show an error instead of a tag list. Clicking to another piece (Cradete Tag) and then back to this one will fix it. +export const tagSubscriber = createAction({ + auth: convertkitAuth, + name: 'tags_tag_subscriber', + displayName: 'Tag Subscriber', + description: 'Tag a subscriber', + props: { + email: subscriberEmail, + // tagId: tag, + firstName: subscriberFirstName, + tags: tagsRequired, + fields: allFields, + }, + async run(context) { + const { email, firstName, tags, fields } = context.propsValue; + if (!tags || tags.length === 0) { + throw new Error('At least one tag is required'); + } + const tagId = tags[0]; + + const url = `${TAGS_API_ENDPOINT}/${tagId}/subscribe`; + + const body = { + email, + first_name: firstName, + tags, + fields, + api_secret: context.auth, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.POST, + body, + }; + + const response = await httpClient.sendRequest<{ + subscription: Tag; + }>(request); + + if (response.status !== 200) { + throw new Error(`Error tagging subscriber: ${response.status}`); + } + + return response.body.subscription; + }, +}); + +export const removeTagFromSubscriberByEmail = createAction({ + auth: convertkitAuth, + name: 'tags_remove_tag_from_subscriber_by_email', + displayName: 'Remove Tag From Subscriber By Email', + description: 'Remove a tag from a subscriber by email', + props: { + email: subscriberEmail, + tagId: tagIdByEmail, + }, + async run(context) { + const { email, tagId } = context.propsValue; + const url = `${TAGS_API_ENDPOINT}/${tagId}/unsubscribe`; + + const body = { + email, + api_secret: context.auth, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.POST, + body, + }; + + const response = await httpClient.sendRequest<{ subscriber: Tag }>(request); + + if (response.status !== 200) { + throw new Error(`Error removing tag from subscriber: ${response.status}`); + } + + return response.body; + }, +}); + +export const removeTagFromSubscriberById = createAction({ + auth: convertkitAuth, + name: 'tags_remove_tag_from_subscriber_by_id', + displayName: 'Remove Tag From Subscriber By Id', + description: 'Remove a tag from a subscriber by id', + props: { + subscriberId, + tagId: tagIdBySubscriberId, + }, + async run(context) { + const { subscriberId, tagId } = context.propsValue; + const url = `${TAGS_API_ENDPOINT}/${tagId}/unsubscribe`; + + const body = { + id: subscriberId, + api_secret: context.auth, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.POST, + body, + }; + + const response = await httpClient.sendRequest<{ subscriber: Tag }>(request); + + if (response.status !== 200) { + throw new Error(`Error removing tag from subscriber: ${response.status}`); + } + + return response.body; + }, +}); + +export const listSubscriptionsToATag = createAction({ + auth: convertkitAuth, + name: 'tags_list_subscriptions_to_tag', + displayName: 'List Subscriptions To Tag', + description: 'List all subscriptions to a tag', + props: { + tagId: tag, + page: tagsPageNumber, + sortOrder, + subscriberState, + }, + async run(context) { + const { tagId, page, sortOrder, subscriberState } = context.propsValue; + const url = `${TAGS_API_ENDPOINT}/${tagId}/subscriptions?`; + + const body = { + api_secret: context.auth, + page, + sort_order: sortOrder, + subscriber_state: subscriberState, + }; + + const request: HttpRequest = { + url, + method: HttpMethod.GET, + body, + }; + + const response = await httpClient.sendRequest<{ + subscriptions: Tag[]; + }>(request); + + if (response.status !== 200) { + throw new Error(`Error listing subscriptions to tag: ${response.status}`); + } + + return response.body.subscriptions; + }, +}); diff --git a/packages/pieces/community/convertkit/src/lib/actions/webhooks.ts b/packages/pieces/community/convertkit/src/lib/actions/webhooks.ts new file mode 100644 index 0000000..26189d4 --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/actions/webhooks.ts @@ -0,0 +1,51 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { convertkitAuth } from '../..'; +import { + targetUrl, + event, + eventParameter, + webhookId, +} from '../common/webhooks'; +import { + createWebhook as createWebhookAction, + removeWebhook as removeWebhookAction, +} from '../common/service'; + +export const createWebhook = createAction({ + auth: convertkitAuth, + name: 'create_webhook', + displayName: 'Add Webhook', + description: 'Create a webhook automation', + props: { + targetUrl, + event, + eventParameter, + }, + run(context) { + const { targetUrl, event, eventParameter } = context.propsValue; + + const payload = { + event: { + name: event, + ...eventParameter, + }, + target_url: targetUrl, + }; + + return createWebhookAction(context.auth, payload); + }, +}); + +export const deleteWebhook = createAction({ + auth: convertkitAuth, + name: 'destroy_webhook', + displayName: 'Delete Webhook', + description: 'Delete a webhook automation', + props: { + webhookId, + }, + run(context) { + const { webhookId } = context.propsValue; + return removeWebhookAction(context.auth, webhookId); + }, +}); diff --git a/packages/pieces/community/convertkit/src/lib/common/broadcasts/index.ts b/packages/pieces/community/convertkit/src/lib/common/broadcasts/index.ts new file mode 100644 index 0000000..9122d81 --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/common/broadcasts/index.ts @@ -0,0 +1,74 @@ +import { Property } from '@activepieces/pieces-framework'; + +export const broadcastId = Property.ShortText({ + displayName: 'Broadcast Id', + description: 'The broadcast id', + required: true, +}); + +export const broadcastPageNumber = Property.Number({ + displayName: 'Page', + description: + 'Page number. Each page of results will contain up to 50 broadcasts.', + required: false, + defaultValue: 1, +}); + +export const broadcastContent = Property.ShortText({ + displayName: 'Content', + description: + "The broadcast's email content - this can contain text and simple HTML markdown (such as h1, img or p tags)", + required: false, +}); +export const description = Property.ShortText({ + displayName: 'Description', + description: 'An internal description of this broadcast', + required: false, +}); +export const broadcastEmailAddress = Property.ShortText({ + displayName: 'Email Address', + description: + "Sending email address; leave blank to use your account's default sending email address", + required: false, +}); +export const emailLayoutTemplate = Property.ShortText({ + displayName: 'Email Layout Template', + description: + "Name of the email template to use; leave blank to use your account's default email template", + required: false, +}); +export const isPublic = Property.Checkbox({ + displayName: 'Public', + description: 'Specifies whether or not this is a public post', + required: false, + defaultValue: false, +}); +export const publishedAt = Property.DateTime({ + displayName: 'Published At', + description: + 'Specifies the time that this post was published (applicable only to public posts)', + required: false, +}); +export const sendAt = Property.DateTime({ + displayName: 'Send At', + description: + 'Time that this broadcast should be sent; leave blank to create a draft broadcast. If set to a future time, this is the time that the broadcast will be scheduled to send.', + required: false, +}); +export const subject = Property.ShortText({ + displayName: 'Subject', + description: "The broadcast email's subject", + required: false, +}); +export const thumbnailAlt = Property.ShortText({ + displayName: 'Thumbnail Alt', + description: + 'Specify the ALT attribute of the public thumbnail image (applicable only to public posts)', + required: false, +}); +export const thumbnailUrl = Property.ShortText({ + displayName: 'Thumbnail Url', + description: + 'Specify the URL of the thumbnail image to accompany the broadcast post (applicable only to public posts)', + required: false, +}); diff --git a/packages/pieces/community/convertkit/src/lib/common/constants.ts b/packages/pieces/community/convertkit/src/lib/common/constants.ts new file mode 100644 index 0000000..d0dc889 --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/common/constants.ts @@ -0,0 +1,10 @@ +export const CONVERTKIT_API_URL = 'https://api.convertkit.com/v3'; + +export const BROADCASTS_API_ENDPOINT = `${CONVERTKIT_API_URL}/broadcasts`; +export const CUSTOM_FIELDS_API_ENDPOINT = `${CONVERTKIT_API_URL}/custom_fields`; +export const FORMS_API_ENDPOINT = `${CONVERTKIT_API_URL}/forms`; +export const PURCHASES_API_ENDPOINT = `${CONVERTKIT_API_URL}/purchases`; +export const SEQUENCES_API_ENDPOINT = `${CONVERTKIT_API_URL}/sequences`; +export const SUBSCRIBERS_API_ENDPOINT = `${CONVERTKIT_API_URL}/subscribers`; +export const TAGS_API_ENDPOINT = `${CONVERTKIT_API_URL}/tags`; +export const WEBHOOKS_API_ENDPOINT = `${CONVERTKIT_API_URL}/automations/hooks`; diff --git a/packages/pieces/community/convertkit/src/lib/common/custom-fields/index.ts b/packages/pieces/community/convertkit/src/lib/common/custom-fields/index.ts new file mode 100644 index 0000000..bac37e7 --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/common/custom-fields/index.ts @@ -0,0 +1,81 @@ +import { Property, DynamicPropsValue } from '@activepieces/pieces-framework'; +import { CustomField } from '../types'; +import { fetchCustomFields } from '../service'; + +export const fieldsArray = Property.Array({ + displayName: 'Fields', + description: 'The custom fields', + required: true, +}); + +export const label = Property.Dropdown({ + displayName: 'Custom Label', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account', + options: [], + }; + } + + const fields: CustomField[] = await fetchCustomFields(auth.toString()); + + // loop through data and map to options + const options = fields.map( + (field: { id: string; label: string; key: string; name: string }) => { + return { + label: field.label, + value: field.id, + }; + } + ); + + return { + options, + }; + }, +}); + +export const new_label = Property.ShortText({ + displayName: 'New Label', + description: 'The new label for the custom field', + required: true, +}); + +export const allFields = Property.DynamicProperties({ + displayName: 'Custom Fields', + description: 'The custom fields', + required: false, + refreshers: ['auth'], + props: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account', + options: [], + }; + } + + const fields: DynamicPropsValue = {}; + + const customFields: CustomField[] = await fetchCustomFields( + auth.toString() + ); + + // loop through data and map to fields + customFields.forEach( + (field: { id: string; label: string; key: string; name: string }) => { + fields[field.key] = Property.ShortText({ + displayName: field.label, + description: `Enter the value for custom field: ${field.label}`, + required: false, + }); + } + ); + + return fields; + }, +}); diff --git a/packages/pieces/community/convertkit/src/lib/common/forms/index.ts b/packages/pieces/community/convertkit/src/lib/common/forms/index.ts new file mode 100644 index 0000000..a247b31 --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/common/forms/index.ts @@ -0,0 +1,31 @@ +import { Property } from '@activepieces/pieces-framework'; +import { fetchForms } from '../../common/service'; + +export const formId = Property.Dropdown({ + displayName: 'Form', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account', + options: [], + }; + } + + const forms = await fetchForms(auth.toString()); + + // loop through data and map to options + const options = forms.map((field: { id: string; name: string }) => { + return { + label: field.name, + value: field.id, + }; + }); + + return { + options, + }; + }, +}); diff --git a/packages/pieces/community/convertkit/src/lib/common/index.ts b/packages/pieces/community/convertkit/src/lib/common/index.ts new file mode 100644 index 0000000..3c77f89 --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/common/index.ts @@ -0,0 +1,7 @@ +export * from './broadcasts'; +export * from './forms'; +export * from './sequences'; +export * from './tags'; +export * from './purchases'; +export * from './custom-fields'; +export * from './webhooks'; diff --git a/packages/pieces/community/convertkit/src/lib/common/purchases/index.ts b/packages/pieces/community/convertkit/src/lib/common/purchases/index.ts new file mode 100644 index 0000000..289a43b --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/common/purchases/index.ts @@ -0,0 +1,181 @@ +import { Property } from '@activepieces/pieces-framework'; + +export const purchaseId = Property.ShortText({ + displayName: 'Purchase ID', + description: 'The purchase ID', + required: true, +}); + +export const purchaserEmailAddress = Property.ShortText({ + displayName: 'Email Address', + description: 'The email address of the subscriber', + required: true, +}); + +export const purchasesPageNumber = Property.Number({ + displayName: 'Page', + description: + 'Page number. Each page of results will contain up to 50 purchases.', + required: true, + defaultValue: 1, +}); + +export const transactionId = Property.Number({ + displayName: 'Transaction ID', + description: 'The transaction ID', + required: true, +}); + +export const productId = Property.Number({ + displayName: 'Product ID', + description: 'The product ID', + required: true, +}); + +// Create purchase + +export const status = Property.StaticDropdown({ + displayName: 'Status', + description: 'The status of the purchase', + required: false, + options: { + options: [ + { label: 'paid', value: 'paid' }, + { label: 'pending', value: 'pending' }, + { label: 'failed', value: 'failed' }, + ], + }, +}); + +export const currency = Property.StaticDropdown({ + displayName: 'Currency', + description: 'The currency of the purchase', + required: true, + options: { + options: [ + { label: 'USD', value: 'USD' }, + { label: 'JPY', value: 'JPY' }, + { label: 'GBP', value: 'GBP' }, + { label: 'EUR', value: 'EUR' }, + { label: 'CAD', value: 'CAD' }, + { label: 'AUD', value: 'AUD' }, + { label: 'NZD', value: 'NZD' }, + { label: 'CHF', value: 'CHF' }, + { label: 'HKD', value: 'HKD' }, + { label: 'SGD', value: 'SGD' }, + { label: 'SEK', value: 'SEK' }, + { label: 'DKK', value: 'DKK' }, + { label: 'PLN', value: 'PLN' }, + { label: 'NOK', value: 'NOK' }, + { label: 'HUF', value: 'HUF' }, + { label: 'CZK', value: 'CZK' }, + { label: 'ILS', value: 'ILS' }, + { label: 'MXN', value: 'MXN' }, + { label: 'MYR', value: 'MYR' }, + { label: 'BRL', value: 'BRL' }, + { label: 'PHP', value: 'PHP' }, + { label: 'TWD', value: 'TWD' }, + { label: 'THB', value: 'THB' }, + { label: 'TRY', value: 'TRY' }, + { label: 'RUB', value: 'RUB' }, + { label: 'INR', value: 'INR' }, + { label: 'KRW', value: 'KRW' }, + { label: 'AED', value: 'AED' }, + { label: 'SAR', value: 'SAR' }, + { label: 'ZAR', value: 'ZAR' }, + ], + }, +}); + +export const transactionTime = Property.DateTime({ + displayName: 'Transaction Time', + description: 'The transaction time', + required: false, +}); + +export const subtotal = Property.Number({ + displayName: 'Subtotal', + description: 'The subtotal', + required: false, +}); + +export const shipping = Property.Number({ + displayName: 'Shipping', + description: 'The shipping', + required: false, +}); + +export const discount = Property.Number({ + displayName: 'Discount', + description: 'The discount', + required: false, +}); + +export const tax = Property.Number({ + displayName: 'Tax', + description: 'The tax', + required: false, +}); + +export const total = Property.Number({ + displayName: 'Total', + description: 'The total', + required: false, +}); + +export const products = { + pid: Property.Number({ + displayName: 'Product ID', + description: 'The product ID', + required: true, + }), + lid: Property.Number({ + displayName: 'Line Item ID', + description: 'The line item ID', + required: true, + }), + name: Property.ShortText({ + displayName: 'Name', + description: 'The name of the product', + required: true, + }), + sku: Property.ShortText({ + displayName: 'SKU', + description: 'The SKU of the product', + required: false, + }), + unit_price: Property.Number({ + displayName: 'Unit Price', + description: 'The unit price of the product', + required: true, + }), + quantity: Property.Number({ + displayName: 'Quantity', + description: 'The quantity of the product', + required: true, + }), +}; + +export const multipleProducts = Property.Json({ + displayName: 'Products', + description: 'The products', + required: true, + defaultValue: [ + { + pid: 9999, + lid: 7777, + name: 'Floppy Disk (512k)', + sku: '7890-ijkl', + unit_price: 5.0, + quantity: 2, + }, + { + pid: 5555, + lid: 7778, + name: 'Telephone Cord (data)', + sku: 'mnop-1234', + unit_price: 10.0, + quantity: 1, + }, + ], +}); diff --git a/packages/pieces/community/convertkit/src/lib/common/sequences/index.ts b/packages/pieces/community/convertkit/src/lib/common/sequences/index.ts new file mode 100644 index 0000000..7c7467f --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/common/sequences/index.ts @@ -0,0 +1,37 @@ +import { Property } from '@activepieces/pieces-framework'; +import { fetchSequences } from '../../common/service'; + +export const sequenceId = Property.ShortText({ + displayName: 'Sequence ID', + description: 'The sequence ID', + required: true, +}); + +export const sequenceIdDropdown = Property.Dropdown({ + displayName: 'Sequence', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account', + options: [], + }; + } + + const sequences = await fetchSequences(auth.toString()); + + // loop through data and map to options + const options = sequences.map((field: { id: string; name: string }) => { + return { + label: field.name, + value: field.id, + }; + }); + + return { + options, + }; + }, +}); diff --git a/packages/pieces/community/convertkit/src/lib/common/service.ts b/packages/pieces/community/convertkit/src/lib/common/service.ts new file mode 100644 index 0000000..26cdce3 --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/common/service.ts @@ -0,0 +1,361 @@ +import { + Broadcast, + CustomField, + Form, + Purchase, + Sequence, + Subscriber, + Tag, + Webhook, +} from './types'; +import { + BROADCASTS_API_ENDPOINT, + CUSTOM_FIELDS_API_ENDPOINT, + FORMS_API_ENDPOINT, + PURCHASES_API_ENDPOINT, + SEQUENCES_API_ENDPOINT, + SUBSCRIBERS_API_ENDPOINT, + TAGS_API_ENDPOINT, + WEBHOOKS_API_ENDPOINT, +} from './constants'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; + +export const fetchBroadcasts = async (auth: string, page: number) => { + const url = BROADCASTS_API_ENDPOINT; + const request: HttpRequest = { + url, + method: HttpMethod.GET, + queryParams: { + api_secret: auth, + page: page.toString(), + sort_order: 'desc', + }, + }; + const response = await httpClient.sendRequest<{ broadcasts: Broadcast[] }>( + request + ); + + const errorMessage = `Failed to fetch broadcasts. Response code: ${ + response.status + } Response body: ${JSON.stringify(response.body)}`; + + if (response.status !== 200) { + throw new Error(errorMessage); + } + + if (response.body.broadcasts) { + return response.body.broadcasts; + } + + throw new Error(errorMessage); +}; + +export const fetchCustomFields = async ( + auth: string +): Promise => { + const url = CUSTOM_FIELDS_API_ENDPOINT; + + const body = { + api_secret: auth, + }; + + const request: HttpRequest = { + url, + body, + method: HttpMethod.GET, + }; + const response = await httpClient.sendRequest<{ + custom_fields: CustomField[]; + }>(request); + + const errorMessage = `Failed to fetch custom fields. Response code: ${ + response.status + } Response body: ${JSON.stringify(response.body)}`; + + if (response.status !== 200) { + throw new Error(errorMessage); + } + + if (response.body.custom_fields) { + return response.body.custom_fields; + } + + throw new Error(errorMessage); +}; + +export const fetchForms = async (auth: string) => { + const url = FORMS_API_ENDPOINT; + + const body = { + api_secret: auth, + }; + + const request: HttpRequest = { + url, + body, + method: HttpMethod.GET, + }; + const response = await httpClient.sendRequest<{ forms: Form[] }>(request); + + const errorMessage = `Failed to fetch forms. Response code: ${ + response.status + } Response body: ${JSON.stringify(response.body)}`; + + if (response.status !== 200) { + throw new Error(errorMessage); + } + + if (response.body.forms) { + return response.body.forms; + } + + throw new Error(errorMessage); +}; + +export const fetchPurchases = async (auth: string, page: number) => { + const url = PURCHASES_API_ENDPOINT; + + const body = { + api_secret: auth, + page, + }; + + const request: HttpRequest = { + url, + body, + method: HttpMethod.GET, + }; + const response = await httpClient.sendRequest<{ purchases: Purchase[] }>( + request + ); + + const errorMessage = `Failed to fetch purchases. Response code: ${ + response.status + } Response body: ${JSON.stringify(response.body)}`; + + if (response.status !== 200) { + throw new Error(errorMessage); + } + + if (response.body.purchases) { + return response.body.purchases; + } + + throw new Error(errorMessage); +}; + +export const fetchSequences = async (auth: string) => { + const url = SEQUENCES_API_ENDPOINT; + const body = { + api_secret: auth, + }; + const request: HttpRequest = { + url, + body, + method: HttpMethod.GET, + }; + const response = await httpClient.sendRequest<{ courses: Sequence[] }>( + request + ); + + const errorMessage = `Failed to fetch sequences. Response code: ${ + response.status + } Response body: ${JSON.stringify(response.body)}`; + + if (response.status !== 200) { + throw new Error(errorMessage); + } + if (response.body.courses) { + return response.body.courses; + } + + throw new Error(errorMessage); +}; + +export const fetchSubscriperById = async ( + auth: string, + subscriberId: string +) => { + const url = `${SUBSCRIBERS_API_ENDPOINT}/${subscriberId}`; + + const body = { + api_secret: auth, + }; + + const request: HttpRequest = { + url, + body, + method: HttpMethod.GET, + }; + + const response = await httpClient.sendRequest<{ subscriber: Subscriber }>( + request + ); + + const errorMessage = `Failed to fetch subscriber. Response code: ${ + response.status + } Response body: ${JSON.stringify(response.body)}`; + + if (response.status !== 200) { + throw new Error(errorMessage); + } + if (response.body.subscriber) { + return response.body.subscriber; + } + + throw new Error(errorMessage); +}; + +export const fetchSubscriberByEmail = async ( + auth: string, + email_address: string +) => { + const url = SUBSCRIBERS_API_ENDPOINT; + + const body = { + api_secret: auth, + email_address, + }; + + const request: HttpRequest = { + url, + body, + method: HttpMethod.GET, + }; + + const response = await httpClient.sendRequest<{ subscribers: Subscriber[] }>( + request + ); + + const errorMessage = `Failed to fetch subscriber that match the provided email. Response code: ${ + response.status + } Response body: ${JSON.stringify(response.body)}`; + + if (response.status !== 200) { + throw new Error(errorMessage); + } + + const subscriber = response.body.subscribers[0]; + if (subscriber) { + return subscriber; + } + + throw new Error(errorMessage); +}; + +export const fetchSubscribedTags = async ( + auth: string, + subscriberId: string +) => { + const url = `${SUBSCRIBERS_API_ENDPOINT}/${subscriberId}/tags`; + + const body = { + api_secret: auth, + }; + + const request: HttpRequest = { + url, + body, + method: HttpMethod.GET, + }; + + const response = await httpClient.sendRequest<{ tags: Tag[] }>(request); + + const errorMessage = `Failed to fetch tags. Response code: ${ + response.status + } Response body: ${JSON.stringify(response.body)}`; + + if (response.status !== 200) { + throw new Error(errorMessage); + } + if (response.body.tags) { + return response.body.tags; + } + + throw new Error(errorMessage); +}; + +export const fetchTags = async (auth: string) => { + const url = TAGS_API_ENDPOINT; + const body = { + api_secret: auth, + }; + const request: HttpRequest = { + url, + body, + method: HttpMethod.GET, + }; + + const response = await httpClient.sendRequest<{ tags: Tag[] }>(request); + + const errorMessage = `Failed to fetch tags. Response code: ${ + response.status + } Response body: ${JSON.stringify(response.body)}`; + + if (response.status !== 200) { + throw new Error(errorMessage); + } + if (response.body.tags) { + return response.body.tags; + } + + throw new Error(errorMessage); +}; + +export const createWebhook = async (auth: string, payload: object) => { + const body = { ...payload, api_secret: auth }; + + const url = WEBHOOKS_API_ENDPOINT; + + const request: HttpRequest = { + url, + body, + method: HttpMethod.POST, + }; + + const response = await httpClient.sendRequest<{ rule: Webhook }>(request); + + const errorMessage = `Failed to create webhook. Response code: ${ + response.status + } Response body: ${JSON.stringify(response.body)}`; + + if (response.status !== 200) { + throw new Error(errorMessage); + } + + if (response.body.rule) { + return response.body.rule; + } + + throw new Error(errorMessage); +}; + +export const removeWebhook = async (auth: string, ruleId: number) => { + const url = `${WEBHOOKS_API_ENDPOINT}/${ruleId}`; + const body = { api_secret: auth }; + + const request: HttpRequest = { + url, + body, + method: HttpMethod.DELETE, + }; + + const response = await httpClient.sendRequest<{ success: boolean }>(request); + + const errorMessage = `Failed to remove webhook. Response code: ${ + response.status + } Response body: ${JSON.stringify(response.body)}`; + + if (response.status !== 200) { + throw new Error(errorMessage); + } + if (response.body.success) { + return response.body.success; + } + + throw new Error(errorMessage); +}; diff --git a/packages/pieces/community/convertkit/src/lib/common/subscribers/index.ts b/packages/pieces/community/convertkit/src/lib/common/subscribers/index.ts new file mode 100644 index 0000000..7309144 --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/common/subscribers/index.ts @@ -0,0 +1,75 @@ +import { Property } from '@activepieces/pieces-framework'; + +export const subscriberId = Property.ShortText({ + displayName: 'Subscriber ID', + description: 'The subscriber ID', + required: true, +}); + +export const subscriberEmail = Property.ShortText({ + displayName: 'Email', + description: 'The email of the subscriber', + required: true, +}); + +export const subscriberEmailOptional = { + ...subscriberEmail, + required: false, +}; + +export const subscriberFirstName = Property.ShortText({ + displayName: 'First Name', + description: 'The first name of the subscriber', + required: false, +}); + +export const subscribersPageNumber = Property.Number({ + displayName: 'Page', + description: + 'Page number. Each page of results will contain up to 50 subscribers.', + required: false, + defaultValue: 1, +}); + +export const from = Property.DateTime({ + displayName: 'From', + description: 'Return subscribers created after this date', + required: false, +}); + +export const to = Property.DateTime({ + displayName: 'To', + description: 'Return subscribers created before this date', + required: false, +}); + +export const updatedFrom = Property.DateTime({ + displayName: 'Updated From', + description: 'Return subscribers updated after this date', + required: false, +}); + +export const updatedTo = Property.DateTime({ + displayName: 'Updated To', + description: 'Return subscribers updated before this date', + required: false, +}); + +export const sortOrder = Property.StaticDropdown({ + displayName: 'Sort Order', + description: 'Sort order', + required: true, + defaultValue: 'asc', + options: { + options: [ + { value: 'asc', label: 'Ascending' }, + { value: 'desc', label: 'Descending' }, + ], + }, +}); + +export const sortField = Property.ShortText({ + displayName: 'Sort Field', + description: 'Sort field', + required: false, +}); diff --git a/packages/pieces/community/convertkit/src/lib/common/tags/index.ts b/packages/pieces/community/convertkit/src/lib/common/tags/index.ts new file mode 100644 index 0000000..36d7522 --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/common/tags/index.ts @@ -0,0 +1,251 @@ +import { + Property, +} from '@activepieces/pieces-framework'; +import { + fetchSubscriberByEmail, + fetchSubscribedTags, + fetchTags, +} from '../service'; +import { Tag, AuthEmail } from '../types'; + +export const tagId = Property.ShortText({ + displayName: 'Tag Id', + description: 'The tag id', + required: true, +}); + +export const name = Property.ShortText({ + displayName: 'Name', + description: 'The name of the tag', + required: true, +}); + +export const tags = Property.MultiSelectDropdown({ + displayName: 'Tags', + description: 'Choose the Tags', + required: false, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account', + options: [], + }; + } + const tags = await fetchTags(auth.toString()); + const options = tags.map((tag: Tag) => { + return { + label: tag.name, + value: tag.id, + }; + }); + + return { + options, + }; + }, +}); + +export const tagsRequired = { ...tags, required: true }; + +export const tag = Property.Dropdown({ + displayName: 'Tag', + description: 'Choose a Tag', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account', + options: [], + }; + } + + const tags = await fetchTags(auth.toString()); + + // loop through data and map to options + const options = tags.map((tag: Tag) => { + return { + label: tag.name, + value: tag.id, + }; + }); + + return { + options, + }; + }, +}); + +export const tagsPageNumber = Property.Number({ + displayName: 'Page', + description: 'Each page of results will contain up to 50 tags.', + required: false, + defaultValue: 1, +}); +export const sortOrder = Property.StaticDropdown({ + displayName: 'Sort Order', + description: 'Sort order', + required: false, + options: { + options: [ + { label: 'Ascending', value: 'asc' }, + { label: 'Descending', value: 'desc' }, + ], + }, +}); +export const subscriberState = Property.StaticDropdown({ + displayName: 'Subscriber State', + description: 'Subscriber state', + required: false, + options: { + options: [ + { label: 'Active', value: 'active' }, + { label: 'canceled', value: 'canceled' }, + ], + }, +}); + +// Generate options for tags based on email address + + +export const tagIdByEmail = Property.Dropdown({ + displayName: 'Tag', + description: 'The tag to remove', + required: true, + refreshers: ['auth', 'email'], + options: async (params: unknown) => { + const { auth, email } = params as AuthEmail; + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account.', + options: [], + }; + } + if (!email) { + return { + disabled: true, + placeholder: 'Provide a subscriber email address.', + options: [], + }; + } + const subscriber = await fetchSubscriberByEmail( + auth.toString(), + email.toString() + ); + + if (!subscriber) { + return { + disabled: true, + placeholder: 'No subscribers found for this email address.', + options: [], + }; + } + + const subscriberId = subscriber.id; + const tags = await fetchSubscribedTags( + auth.toString(), + subscriberId.toString() + ); + + // loop through data and map to options + const options = tags.map((tag: Tag) => { + return { + label: tag.name, + value: tag.id, + }; + }); + + return { + disabled: false, + placeholder: 'Choose a tag', + options, + }; + }, +}); + +export const tagIdBySubscriberId = Property.Dropdown({ + displayName: 'Tag', + description: 'The tag to remove', + required: true, + refreshers: ['auth', 'subscriberId'], + options: async ({ auth, subscriberId }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account and', + options: [], + }; + } + + if (!subscriberId) { + return { + disabled: true, + placeholder: 'Provide a subscriber id.', + options: [], + }; + } + + { + const tags = await fetchSubscribedTags( + auth.toString(), + subscriberId.toString() + ); + if (!tags) { + return { + disabled: true, + placeholder: 'Something went wrong.', + options: [], + }; + } + // loop through data and map to options + const options = tags.map((tag: Tag) => { + return { + label: tag.name, + value: tag.id, + }; + }); + + return { + options, + }; + } + }, +}); + +// WIP debounce + +// // // https://github.com/lodash/lodash/issues/4700#issuecomment-805439202 +// export const asyncT = function asyncThrottle< +// F extends (...args: any[]) => Promise +// >(func: F, wait?: number) { +// const throttled = _.throttle((resolve, reject, args: Parameters) => { +// func(...args) +// .then(resolve) +// .catch(reject); +// }, wait); +// return (...args: Parameters): ReturnType => +// new Promise((resolve, reject) => { +// throttled(resolve, reject, args); +// }) as ReturnType; +// }; + +// let optionsFnRef: any; + +// function debouncedOptions() { + +// // cancel any old refs +// if (optionsFnRef && optionsFnRef.cancel) optionsFnRef.cancel(); + +// // create new instance and save for later +// optionsFnRef = loadash.debounce(optiosnFn, 3000); + +// // execute will start after 1000 unless cancelled because the function is re-invoked again + +// return optionsFnRef; +// } + +// import Options type diff --git a/packages/pieces/community/convertkit/src/lib/common/types.ts b/packages/pieces/community/convertkit/src/lib/common/types.ts new file mode 100644 index 0000000..2ae85d5 --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/common/types.ts @@ -0,0 +1,135 @@ +export interface Broadcast { + id: string; + created_at: string; + subject: string; +} + +export interface CustomField { + id: string; + label: string; + key: string; + name: string; +} + +export interface Form { + id: string; + name: string; + created_at: string; + type: string; + format: string; + embed_js: string; + embed_url: string; + archived: boolean; + uid: string; +} + +export interface Subscription { + id: string; + state: string; + created_at: string; + source: string; + referrer: string; + subscribable_id: string; + subscribable_type: string; + subscriber: Subscriber; +} + +export interface Subscriber { + id: string; + first_name: string; + email_address: string; + state: string; + created_at: string; + fields: Fields; +} + +export interface Fields { + [key: string]: string; +} + +export interface Purchase { + id: string; + transaction_id: string; + status: string; + email_address: string; + currency: string; + transaction_time: string; + subtotal: number; + discount: number; + tax: number; + total: number; + products: Product[]; +} + +export interface Product { + quantity: number; + lid: string; + unit_price: number; + sku: string; + name: string; + pid: string; +} + +export interface Sequence { + id: string; + name: string; + hold: boolean; + repeat: boolean; + created_at: string; +} + +export interface Tag { + id: string; + name: string; + created_at: string; +} + +export enum EventType { + subscriberActivate = 'subscriber.subscriber_activate', + subscriberUnsubscribe = 'subscriber.subscriber_unsubscribe', + subscriberBounce = 'subscriber.subscriber_bounce', + subscriberComplain = 'subscriber.subscriber_complain', + formSubscribe = 'subscriber.form_subscribe', + courseSubscribe = 'subscriber.course_subscribe', + courseComplete = 'subscriber.course_complete', + linkClick = 'subscriber.link_click', + productPurchase = 'subscriber.product_purchase', + tagAdd = 'subscriber.tag_add', + tagRemove = 'subscriber.tag_remove', + purchaseCreate = 'purchase.purchase_create', +} + +export type EventParameterKey = + | 'form_id' + | 'sequence_id' + | 'initiator_value' + | 'product_id' + | 'tag_id'; + +type EventMapped = { + [K in EventParameterKey]?: string | number; +}; + +export type Event = EventMapped & { + name: EventType; +}; + +export interface Webhook { + id: number; + account_id: number; + event: Event; + target_url: string; +} + +export interface EventOption { + label: string; + value: EventType; + required_parameter: EventParameterKey | null; + param_label: string | null; + type: string | null; +} + +export interface AuthEmail { + auth: string; + email: string; +} diff --git a/packages/pieces/community/convertkit/src/lib/common/webhooks/index.ts b/packages/pieces/community/convertkit/src/lib/common/webhooks/index.ts new file mode 100644 index 0000000..107fb3c --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/common/webhooks/index.ts @@ -0,0 +1,221 @@ +import { Property, DynamicPropsValue } from '@activepieces/pieces-framework'; +import { EventType, EventOption } from '../types'; +import { fetchTags, fetchForms, fetchSequences } from '../service'; + +export const initiatorValue = Property.ShortText({ + displayName: 'Initiator Value URL', + description: 'The initiator value URL that will trigger the webhook', + required: true, +}); + +export const webhookId = Property.Number({ + displayName: 'Webhook Id', + description: 'The webhook rule id', + required: true, +}); + +export const targetUrl = Property.ShortText({ + displayName: 'Target URL', + description: 'The URL that will be called when the webhook is triggered', + required: true, +}); + +export const eventParameter = Property.DynamicProperties({ + displayName: 'Event Parameter', + description: 'The required parameter for the event', + required: false, + refreshers: ['auth', 'event'], + props: async ({ auth, event }) => { + if (!event) { + return { + disabled: true, + placeholder: 'Select event first', + options: [], + }; + } + + const fields: DynamicPropsValue = {}; + + const eventOption = createWebhookParameterOptions.find( + (option) => + option.value === (event as unknown as string) && + option.required_parameter + ); + + if (!eventOption) { + return fields; + } + + const required_parameter = eventOption.required_parameter || ''; + const param_label = eventOption.param_label || ''; + const fieldType = eventOption.type || ''; + + if (required_parameter === 'tag_id') { + const tags = await fetchTags(auth.toString()); + const options = tags.map((tag) => { + return { + label: tag.name, + value: tag.id, + }; + }); + fields[required_parameter] = Property.StaticDropdown({ + displayName: 'Tag', + description: 'Choose a Tag', + required: true, + options: { + options, + }, + }); + return fields; + } + + if (required_parameter === 'form_id') { + const forms = await fetchForms(auth.toString()); + const options = forms.map((form) => { + return { + label: form.name, + value: form.id, + }; + }); + fields[required_parameter] = Property.StaticDropdown({ + displayName: 'Form', + description: 'Choose a Form', + required: true, + options: { + options, + }, + }); + return fields; + } + + if (required_parameter === 'sequence_id') { + const courses = await fetchSequences(auth.toString()); + const options = courses.map((sequence) => { + return { + label: sequence.name, + value: sequence.id, + }; + }); + fields[required_parameter] = Property.StaticDropdown({ + displayName: 'Sequence', + description: 'Choose a Sequence', + required: true, + options: { + options, + }, + }); + return fields; + } + + const field = { + displayName: param_label, + description: `The ${param_label} parameter for the event`, + required: true, + }; + + if (fieldType === 'number') { + fields[required_parameter] = Property.Number(field); + } + if (fieldType === 'string') { + fields[required_parameter] = Property.ShortText(field); + } + + return fields; + }, +}); + +export const createWebhookParameterOptions: EventOption[] = [ + { + label: 'Subscriber activated', + value: EventType.subscriberActivate, + required_parameter: null, + param_label: null, + type: null, + }, + { + label: 'Subscriber unsubscribed', + value: EventType.subscriberUnsubscribe, + required_parameter: null, + param_label: null, + type: null, + }, + { + label: 'Subscriber bounced', + value: EventType.subscriberBounce, + required_parameter: null, + param_label: null, + type: null, + }, + { + label: 'Subscriber complained', + value: EventType.subscriberComplain, + required_parameter: null, + param_label: null, + type: null, + }, + { + label: 'Form subscribed', + value: EventType.formSubscribe, + required_parameter: 'form_id', + param_label: 'Form Id', + type: 'number', + }, + { + label: 'Sequence subscribed', + value: EventType.courseSubscribe, + required_parameter: 'sequence_id', + param_label: 'Sequence Id', + type: 'number', + }, + { + label: 'Sequence completed', + value: EventType.courseComplete, + required_parameter: 'sequence_id', + param_label: 'Sequence Id', + type: 'number', + }, + { + label: 'Link clicked', + value: EventType.linkClick, + required_parameter: 'initiator_value', + param_label: 'Initiator Value', + type: 'string', + }, + { + label: 'Product purchased', + value: EventType.productPurchase, + required_parameter: 'product_id', + param_label: 'Product Id', + type: 'number', + }, + { + label: 'Tag added to subscriber', + value: EventType.tagAdd, + required_parameter: 'tag_id', + param_label: 'Tag Id', + type: 'number', + }, + { + label: 'Tag removed from subscriber', + value: EventType.tagRemove, + required_parameter: 'tag_id', + param_label: 'Tag Id', + type: 'number', + }, + { + label: 'Purchase created', + value: EventType.purchaseCreate, + required_parameter: null, + param_label: null, + type: null, + }, +]; + +export const event = Property.StaticDropdown({ + displayName: 'Event', + description: 'The event that will trigger the webhook', + required: true, + options: { + options: createWebhookParameterOptions, + }, +}); diff --git a/packages/pieces/community/convertkit/src/lib/triggers/index.ts b/packages/pieces/community/convertkit/src/lib/triggers/index.ts new file mode 100644 index 0000000..1962899 --- /dev/null +++ b/packages/pieces/community/convertkit/src/lib/triggers/index.ts @@ -0,0 +1,548 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { convertkitAuth } from '../..'; +import { tag } from '../common/tags'; +import { formId } from '../common/forms'; +import { sequenceIdDropdown } from '../common/sequences'; +import { productId } from '../common/purchases'; +import { initiatorValue } from '../common/webhooks'; +import { createWebhook, removeWebhook } from '../common/service'; + +interface WebhookInformation { + ruleId: number; +} + +const sampleData = { + rule: { + id: 1, + account_id: 2, + event: { + name: 'course_complete', + sequence_id: 3, + }, + }, +}; + +// Tested +export const addTag = createTrigger({ + auth: convertkitAuth, + name: 'webhook_subscriber_tag_add', + displayName: 'Tag added to subscriber', + description: 'Trigger when a tag is added to a subscriber', + type: TriggerStrategy.WEBHOOK, + props: { + tagId: tag, + }, + sampleData, + async onEnable(context) { + const { tagId } = context.propsValue; + + const payload = { + event: { + name: 'subscriber.tag_add', + tag_id: tagId, + }, + target_url: context.webhookUrl, + }; + + const response = await createWebhook(context.auth, payload); + const ruleId = response.id; + + await context.store?.put( + `_webhook_subscriber_tag_add`, + { + ruleId, + } + ); + }, + async onDisable(context) { + const response = await context.store?.get( + '_webhook_subscriber_tag_add' + ); + if (response !== null && response !== undefined) { + await removeWebhook(context.auth, response.ruleId); + } + }, + async run(context) { + const body = context.payload.body as { subscriber: unknown }; + return [body.subscriber]; + }, +}); + +// Tested +export const removeTag = createTrigger({ + auth: convertkitAuth, + name: 'webhook_subscriber_tag_remove', + displayName: 'Tag removed from subscriber', + description: 'Trigger when a tag is removed from a subscriber', + type: TriggerStrategy.WEBHOOK, + props: { + tagId: tag, + }, + sampleData, + async onEnable(context) { + const { tagId } = context.propsValue; + + const payload = { + event: { + name: 'subscriber.tag_remove', + tag_id: tagId, + }, + target_url: context.webhookUrl, + }; + + const response = await createWebhook(context.auth, payload); + const ruleId = response.id; + + await context.store?.put( + `_webhook_subscriber_tag_remove`, + { + ruleId, + } + ); + }, + async onDisable(context) { + const response = await context.store?.get( + '_webhook_subscriber_tag_remove' + ); + if (response !== null && response !== undefined) { + await removeWebhook(context.auth, response.ruleId); + } + }, + async run(context) { + const body = context.payload.body as { subscriber: unknown }; + return [body.subscriber]; + }, +}); + +// Tested +export const subscriberActivated = createTrigger({ + auth: convertkitAuth, + name: 'webhook_subscriber_activated', + displayName: 'Subscriber activated', + description: + 'Trigger when a subscriber is activated. This happens when a subscriber confirms their subscription.', + type: TriggerStrategy.WEBHOOK, + props: {}, + sampleData, + async onEnable(context) { + const payload = { + event: { + name: 'subscriber.subscriber_activate', + }, + target_url: context.webhookUrl, + }; + + const response = await createWebhook(context.auth, payload); + const ruleId = response.id; + + await context.store?.put( + `_webhook_subscriber_activated`, + { + ruleId, + } + ); + }, + async onDisable(context) { + const response = await context.store?.get( + '_webhook_subscriber_activated' + ); + if (response !== null && response !== undefined) { + await removeWebhook(context.auth, response.ruleId); + } + }, + async run(context) { + const body = context.payload.body as { subscriber: unknown }; + return [body.subscriber]; + }, +}); + +// Tested +export const subscriberUnsubscribed = createTrigger({ + auth: convertkitAuth, + name: 'webhook_subscriber_unsubscribed', + displayName: 'Subscriber unsubscribed', + description: 'Trigger when a subscriber is unsubscribed', + type: TriggerStrategy.WEBHOOK, + props: {}, + sampleData, + async onEnable(context) { + const payload = { + event: { + name: 'subscriber.subscriber_unsubscribe', + }, + target_url: context.webhookUrl, + }; + + const response = await createWebhook(context.auth, payload); + const ruleId = response.id; + + await context.store?.put( + `_webhook_subscriber_unsubscribed`, + { + ruleId, + } + ); + }, + async onDisable(context) { + const response = await context.store?.get( + '_webhook_subscriber_unsubscribed' + ); + if (response !== null && response !== undefined) { + await removeWebhook(context.auth, response.ruleId); + } + }, + async run(context) { + const body = context.payload.body as { subscriber: unknown }; + return [body.subscriber]; + }, +}); + +// Tested +export const subscriberBounced = createTrigger({ + auth: convertkitAuth, + name: 'webhook_subscriber_bounced', + displayName: 'Subscriber bounced', + description: + 'Trigger when a subscriber bounced. This happens when an email is sent to a subscriber and the email bounces.', + type: TriggerStrategy.WEBHOOK, + props: {}, + sampleData, + async onEnable(context) { + const payload = { + event: { + name: 'subscriber.subscriber_bounce', + }, + target_url: context.webhookUrl, + }; + + const response = await createWebhook(context.auth, payload); + const ruleId = response.id; + + await context.store?.put( + `_webhook_subscriber_bounced`, + { + ruleId, + } + ); + }, + async onDisable(context) { + const response = await context.store?.get( + '_webhook_subscriber_bounced' + ); + if (response !== null && response !== undefined) { + await removeWebhook(context.auth, response.ruleId); + } + }, + async run(context) { + const body = context.payload.body as { subscriber: unknown }; + return [body.subscriber]; + }, +}); + +// UNTESTED +// TODO: Test this +// Cannot get the state to set to "Complained" in order to test this. Have tried marking as spam in Thunderbird and Outlook Online +// https://help.convertkit.com/en/articles/2502638-why-is-my-subscriber-showing-as-complained +export const subscriberComplained = createTrigger({ + auth: convertkitAuth, + name: 'webhook_subscriber_complained', + displayName: 'Subscriber complained', + description: + 'Trigger when a subscriber complained. This happens when a subscriber marks an email as spam.', + type: TriggerStrategy.WEBHOOK, + props: {}, + sampleData, + async onEnable(context) { + const payload = { + event: { + name: 'subscriber.subscriber_complain', + }, + target_url: context.webhookUrl, + }; + + const response = await createWebhook(context.auth, payload); + const ruleId = response.id; + + await context.store?.put( + `_webhook_subscriber_complained`, + { + ruleId, + } + ); + }, + async onDisable(context) { + const response = await context.store?.get( + '_webhook_subscriber_complained' + ); + if (response !== null && response !== undefined) { + await removeWebhook(context.auth, response.ruleId); + } + }, + async run(context) { + const body = context.payload.body as { subscriber: unknown }; + return [body.subscriber]; + }, +}); + +// Tested +export const formSubscribed = createTrigger({ + auth: convertkitAuth, + name: 'webhook_form_subscribed', + displayName: 'Form subscribed', + description: 'Trigger when a form is subscribed', + type: TriggerStrategy.WEBHOOK, + props: { + formId, + }, + sampleData, + async onEnable(context) { + const { formId } = context.propsValue; + + const payload = { + event: { + name: 'subscriber.form_subscribe', + form_id: formId, + }, + target_url: context.webhookUrl, + }; + + const response = await createWebhook(context.auth, payload); + const ruleId = response.id; + + await context.store?.put(`_webhook_form_subscribed`, { + ruleId, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_webhook_form_subscribed' + ); + if (response !== null && response !== undefined) { + await removeWebhook(context.auth, response.ruleId); + } + }, + async run(context) { + const body = context.payload.body as { subscriber: unknown }; + return [body.subscriber]; + }, +}); + +// Tested +export const sequenceSubscribed = createTrigger({ + auth: convertkitAuth, + name: 'webhook_sequence_subscribed', + displayName: 'Sequence subscribed', + description: 'Trigger when a sequence is subscribed', + type: TriggerStrategy.WEBHOOK, + props: { + sequenceIdChoice: sequenceIdDropdown, + }, + sampleData, + async onEnable(context) { + const { sequenceIdChoice } = context.propsValue; + + const payload = { + event: { + name: 'subscriber.course_subscribe', + sequence_id: sequenceIdChoice, + }, + target_url: context.webhookUrl, + }; + + const response = await createWebhook(context.auth, payload); + const ruleId = response.id; + + await context.store?.put( + `_webhook_sequence_subscribed`, + { + ruleId, + } + ); + }, + async onDisable(context) { + const response = await context.store?.get( + '_webhook_sequence_subscribed' + ); + if (response !== null && response !== undefined) { + await removeWebhook(context.auth, response.ruleId); + } + }, + async run(context) { + const body = context.payload.body as { subscriber: unknown }; + return [body.subscriber]; + }, +}); + +// Tested +export const sequenceCompleted = createTrigger({ + auth: convertkitAuth, + name: 'webhook_sequence_completed', + displayName: 'Sequence completed', + description: 'Trigger when a sequence is completed', + type: TriggerStrategy.WEBHOOK, + props: { + sequenceIdChoice: sequenceIdDropdown, + }, + sampleData, + async onEnable(context) { + const { sequenceIdChoice } = context.propsValue; + + const payload = { + event: { + name: 'subscriber.course_complete', + sequence_id: sequenceIdChoice, + }, + target_url: context.webhookUrl, + }; + + const response = await createWebhook(context.auth, payload); + const ruleId = response.id; + + await context.store?.put( + `_webhook_sequence_completed`, + { + ruleId, + } + ); + }, + async onDisable(context) { + const response = await context.store?.get( + '_webhook_sequence_completed' + ); + if (response !== null && response !== undefined) { + await removeWebhook(context.auth, response.ruleId); + } + }, + async run(context) { + const body = context.payload.body as { subscriber: unknown }; + return [body.subscriber]; + }, +}); + +// Tested +export const linkClicked = createTrigger({ + auth: convertkitAuth, + name: 'webhook_link_clicked', + displayName: 'Link clicked', + description: 'Trigger when a link is clicked', + type: TriggerStrategy.WEBHOOK, + props: { + initiatorValue, + }, + sampleData, + async onEnable(context) { + const { initiatorValue } = context.propsValue; + + const payload = { + event: { + name: 'subscriber.link_click', + initiator_value: initiatorValue, + }, + target_url: context.webhookUrl, + }; + + const response = await createWebhook(context.auth, payload); + const ruleId = response.id; + + await context.store?.put(`_webhook_link_clicked`, { + ruleId, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_webhook_link_clicked' + ); + if (response !== null && response !== undefined) { + await removeWebhook(context.auth, response.ruleId); + } + }, + async run(context) { + const { initiatorValue } = context.propsValue; + const body = context.payload.body as { subscriber: unknown }; + return [body.subscriber, initiatorValue]; + }, +}); + +// UNTESTED +// Broken UI at convertkit, reported to support. The Rukes page is not loading. It will load again, when the webhook is canceled. +export const productPurchased = createTrigger({ + auth: convertkitAuth, + name: 'webhook_product_purchased', + displayName: 'Product purchased', + description: 'Trigger when a product is purchased', + type: TriggerStrategy.WEBHOOK, + props: { + productId, + }, + sampleData, + async onEnable(context) { + const { productId } = context.propsValue; + + const payload = { + event: { + name: 'subscriber.product_purchase', + product_id: productId, + }, + target_url: context.webhookUrl, + }; + const response = await createWebhook(context.auth, payload); + const ruleId = response.id; + + await context.store?.put(`_webhook_product_purchased`, { + ruleId, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_webhook_product_purchased' + ); + if (response !== null && response !== undefined) { + await removeWebhook(context.auth, response.ruleId); + } + }, + async run(context) { + const { productId } = context.propsValue; + const body = context.payload.body as { subscriber: unknown }; + return [body.subscriber, productId]; + }, +}); + +// Untested. Webhook is created, but I have not tested if the trigger is firing. +export const purchaseCreated = createTrigger({ + auth: convertkitAuth, + name: 'webhook_purchase_created', + displayName: 'Purchase created', + description: 'Trigger when a purchase is created', + type: TriggerStrategy.WEBHOOK, + props: {}, + sampleData, + async onEnable(context) { + const payload = { + event: { + name: 'purchase.purchase_create', + }, + target_url: context.webhookUrl, + }; + + const response = await createWebhook(context.auth, payload); + const ruleId = response.id; + + await context.store?.put(`_webhook_purchase_created`, { + ruleId, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_webhook_purchase_created' + ); + if (response !== null && response !== undefined) { + await removeWebhook(context.auth, response.ruleId); + } + }, + async run(context) { + const body = context.payload.body as { + purchase: { subscriber: unknown }; + }; + return [body.purchase.subscriber]; + }, +}); diff --git a/packages/pieces/community/convertkit/tsconfig.json b/packages/pieces/community/convertkit/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/convertkit/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/convertkit/tsconfig.lib.json b/packages/pieces/community/convertkit/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/convertkit/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/copy-ai/.eslintrc.json b/packages/pieces/community/copy-ai/.eslintrc.json new file mode 100644 index 0000000..9810848 --- /dev/null +++ b/packages/pieces/community/copy-ai/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/copy-ai/README.md b/packages/pieces/community/copy-ai/README.md new file mode 100644 index 0000000..0a47d90 --- /dev/null +++ b/packages/pieces/community/copy-ai/README.md @@ -0,0 +1,7 @@ +# pieces-copy-ai + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx build pieces-copy-ai` to build the library. diff --git a/packages/pieces/community/copy-ai/package.json b/packages/pieces/community/copy-ai/package.json new file mode 100644 index 0000000..5ce6bb1 --- /dev/null +++ b/packages/pieces/community/copy-ai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-copy-ai", + "version": "0.0.1" +} diff --git a/packages/pieces/community/copy-ai/project.json b/packages/pieces/community/copy-ai/project.json new file mode 100644 index 0000000..34be915 --- /dev/null +++ b/packages/pieces/community/copy-ai/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-copy-ai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/copy-ai/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/copy-ai", + "tsConfig": "packages/pieces/community/copy-ai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/copy-ai/package.json", + "main": "packages/pieces/community/copy-ai/src/index.ts", + "assets": [ + "packages/pieces/community/copy-ai/*.md", + { + "input": "packages/pieces/community/copy-ai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/copy-ai/src/index.ts b/packages/pieces/community/copy-ai/src/index.ts new file mode 100644 index 0000000..3d6147a --- /dev/null +++ b/packages/pieces/community/copy-ai/src/index.ts @@ -0,0 +1,33 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; + +import { runWorkflowAction } from './lib/actions/run-workflow'; +import { getWorkflowRunStatusAction } from './lib/actions/get-workflow-run-status'; +import { getWorkflowRunOutputsAction } from './lib/actions/get-workflow-run-outputs'; +import { workflowRunCompletedTrigger } from './lib/triggers/workflow-run-completed'; + +const markdownDescription = ` +To use Copy AI, you need to get an API key: +1. Login to your account at https://copy.ai. +2. Click on Workflows in the left sidebar. +3. Click on any Workflow you have. You need to create a new Workflow, if you don't have one. +4. Click on the API tab. +5. Click the Copy button below WORKSPACE API KEY. +`; + +export const copyAiAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: markdownDescription, + required: true, +}); + +export const copyAi = createPiece({ + displayName: 'Copy.ai', + description: 'AI-powered content generation and copywriting platform', + logoUrl: 'https://cdn.activepieces.com/pieces/copy-ai.png', + authors: ['AnkitSharmaOnGithub'], + auth: copyAiAuth, + actions: [runWorkflowAction, getWorkflowRunStatusAction, getWorkflowRunOutputsAction], + triggers: [workflowRunCompletedTrigger], + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], +}); diff --git a/packages/pieces/community/copy-ai/src/lib/actions/get-workflow-run-outputs.ts b/packages/pieces/community/copy-ai/src/lib/actions/get-workflow-run-outputs.ts new file mode 100644 index 0000000..6bc0af2 --- /dev/null +++ b/packages/pieces/community/copy-ai/src/lib/actions/get-workflow-run-outputs.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common/client'; +import { copyAiAuth } from '../../index'; + +export const getWorkflowRunOutputsAction = createAction({ + auth:copyAiAuth, + name: 'get_workflow_run_outputs', + displayName: 'Get Workflow Run Outputs', + description: 'Retrieves the outputs of a completed workflow run.', + props: { + workflowId: Property.ShortText({ + displayName: 'Workflow ID', + required: true, + }), + runId: Property.ShortText({ + displayName: 'Run ID', + description: 'The ID of the workflow run to get outputs from.', + required: true, + }), + }, + async run({ propsValue, auth }) { + const response = await makeRequest( + auth as string, + HttpMethod.GET, + `/workflow/${propsValue.workflowId}/run/${propsValue.runId}`, + ); + + return response.data?.output || response; + }, +}); diff --git a/packages/pieces/community/copy-ai/src/lib/actions/get-workflow-run-status.ts b/packages/pieces/community/copy-ai/src/lib/actions/get-workflow-run-status.ts new file mode 100644 index 0000000..23359cf --- /dev/null +++ b/packages/pieces/community/copy-ai/src/lib/actions/get-workflow-run-status.ts @@ -0,0 +1,40 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common/client'; +import { copyAiAuth } from '../../index'; + +export const getWorkflowRunStatusAction = createAction({ + auth:copyAiAuth, + name: 'get_workflow_run_status', + displayName: 'Get Workflow Run Status', + description: 'Retrieves the status of a workflow execution.', + props: { + workflowId: Property.ShortText({ + displayName: 'Workflow ID', + required: true, + }), + runId: Property.ShortText({ + displayName: 'Run ID', + description: 'The ID of the workflow run to check.', + required: true, + }), + }, + async run({ propsValue, auth }) { + const response = (await makeRequest( + auth as string, + HttpMethod.GET, + `/workflow/${propsValue.workflowId}/run/${propsValue.runId}`, + )) as GetRunResponse; + + return { + status: response.data.status, + }; + }, +}); + +type GetRunResponse = { + status: string; + data: { + status: string; + }; +}; diff --git a/packages/pieces/community/copy-ai/src/lib/actions/run-workflow.ts b/packages/pieces/community/copy-ai/src/lib/actions/run-workflow.ts new file mode 100644 index 0000000..4744c49 --- /dev/null +++ b/packages/pieces/community/copy-ai/src/lib/actions/run-workflow.ts @@ -0,0 +1,44 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common/client'; +import { copyAiAuth } from '../../index'; + +export const runWorkflowAction = createAction({ + auth:copyAiAuth, + name: 'run_workflow', + displayName: 'Run Workflow', + description: 'Start a Copy.ai workflow execution.', + props: { + workflowId: Property.ShortText({ + displayName: 'Workflow ID', + description: 'The ID of the workflow to run.', + required: true, + }), + inputs: Property.Object({ + displayName: 'Workflow Inputs', + description: 'The input data for the workflow.', + required: true, + }), + }, + async run({ propsValue, auth }) { + const response = (await makeRequest( + auth as string, + HttpMethod.POST, + `/workflow/${propsValue.workflowId}/run`, + { + startVariables: propsValue.inputs, + }, + )) as CreateRunResponse; + + return { + runId: response.data.id, + }; + }, +}); + +type CreateRunResponse = { + success: string; + data: { + id: string; + }; +}; diff --git a/packages/pieces/community/copy-ai/src/lib/common/client.ts b/packages/pieces/community/copy-ai/src/lib/common/client.ts new file mode 100644 index 0000000..64a0f8f --- /dev/null +++ b/packages/pieces/community/copy-ai/src/lib/common/client.ts @@ -0,0 +1,16 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +export const BASE_URL = 'https://api.copy.ai/api'; + +export async function makeRequest(auth: string, method: HttpMethod, path: string, body?: unknown) { + const response = await httpClient.sendRequest({ + method, + url: `${BASE_URL}${path}`, + headers: { + 'x-copy-ai-api-key': `${auth}`, + 'Content-Type': 'application/json', + }, + body, + }); + return response.body; +} diff --git a/packages/pieces/community/copy-ai/src/lib/triggers/workflow-run-completed.ts b/packages/pieces/community/copy-ai/src/lib/triggers/workflow-run-completed.ts new file mode 100644 index 0000000..9ebaab1 --- /dev/null +++ b/packages/pieces/community/copy-ai/src/lib/triggers/workflow-run-completed.ts @@ -0,0 +1,81 @@ +import { createTrigger, Property, TriggerStrategy } from '@activepieces/pieces-framework'; +import { makeRequest } from '../common/client'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; +import { copyAiAuth } from '../../index'; + + +export const workflowRunCompletedTrigger = createTrigger({ + auth:copyAiAuth, + name: 'workflow_run_completed', + displayName: 'Workflow Run Completed', + description: 'Triggered when a workflow run is completed.', + props: { + workflowId: Property.ShortText({ + displayName: 'Workflow ID', + required: true, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: { + "status": "COMPLETE", + "input": { + "should_run": "Test" + }, + "toolKey": null, + "metadata": { + "webapp": true + }, + "error": null, + "createdAt": "2025-05-07T09:31:40.545Z", + "id": "WRUN-7515d814-3890-4ba0-ae32-fa6abcf76432", + "workflowRunId": "WRUN-7515d814-3890-4ba0-ae32-fa6abcf76432", + "workflowId": "WCFG-506c46fb-6459-4e23-979b-875444170626", + "credits": 1, + "output": { + "final_output": "", + "send_api_request": "{}", + }, + "type": "workflowRun.completed" + }, + async onEnable(context) { + const response = await makeRequest( + context.auth as string, + HttpMethod.POST, + '/webhook', + { + "url": context.webhookUrl, + "eventType": "workflowRun.completed", + "workflowId": context.propsValue.workflowId + } + + ) as CreateWebhookResponse; + + await context.store.put<{webhookId:string}>('workflow_run_completed',{webhookId:response.data.id}) + + }, + async onDisable(context) { + const response = await context.store.get<{webhookId:string}>('workflow_run_completed'); + if(!isNil(response) && !isNil(response.webhookId)) + { + await makeRequest( + context.auth as string, + HttpMethod.DELETE, + `/webhook/${response.webhookId}`, + {} + ) + + } + }, + async run(context) { + return [context.payload.body]; + }, +}); + + +type CreateWebhookResponse = { + status:string; + data:{ + id:string + } +} \ No newline at end of file diff --git a/packages/pieces/community/copy-ai/tsconfig.json b/packages/pieces/community/copy-ai/tsconfig.json new file mode 100644 index 0000000..4abf5a1 --- /dev/null +++ b/packages/pieces/community/copy-ai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/copy-ai/tsconfig.lib.json b/packages/pieces/community/copy-ai/tsconfig.lib.json new file mode 100644 index 0000000..b22c1c0 --- /dev/null +++ b/packages/pieces/community/copy-ai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/packages/pieces/community/crisp/.eslintrc.json b/packages/pieces/community/crisp/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/crisp/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/crisp/README.md b/packages/pieces/community/crisp/README.md new file mode 100644 index 0000000..a36e58e --- /dev/null +++ b/packages/pieces/community/crisp/README.md @@ -0,0 +1,7 @@ +# pieces-crisp + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-crisp` to build the library. diff --git a/packages/pieces/community/crisp/package.json b/packages/pieces/community/crisp/package.json new file mode 100644 index 0000000..e0a1c4b --- /dev/null +++ b/packages/pieces/community/crisp/package.json @@ -0,0 +1,10 @@ +{ + "name": "@activepieces/piece-crisp", + "version": "0.0.1", + "type": "commonjs", + "main": "./src/index.js", + "types": "./src/index.d.ts", + "dependencies": { + "tslib": "^2.3.0" + } +} diff --git a/packages/pieces/community/crisp/project.json b/packages/pieces/community/crisp/project.json new file mode 100644 index 0000000..5b63645 --- /dev/null +++ b/packages/pieces/community/crisp/project.json @@ -0,0 +1,51 @@ +{ + "name": "pieces-crisp", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/crisp/src", + "projectType": "library", + "release": { + "version": { + "manifestRootsToUpdate": [ + "dist/{projectRoot}" + ], + "currentVersionResolver": "git-tag", + "fallbackCurrentVersionResolver": "disk" + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/crisp", + "tsConfig": "packages/pieces/community/crisp/tsconfig.lib.json", + "packageJson": "packages/pieces/community/crisp/package.json", + "main": "packages/pieces/community/crisp/src/index.ts", + "assets": [ + "packages/pieces/community/crisp/*.md", + { + "input": "packages/pieces/community/crisp/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/crisp/src/index.ts b/packages/pieces/community/crisp/src/index.ts new file mode 100644 index 0000000..b595a30 --- /dev/null +++ b/packages/pieces/community/crisp/src/index.ts @@ -0,0 +1,45 @@ +import { createPiece, PiecePropValueSchema } from '@activepieces/pieces-framework'; +import { crispAuth } from './lib/common/auth'; +import { createConversationAction } from './lib/actions/create-conversation'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { BASE_URL } from './lib/common/client'; +import { addNoteToConversationAction } from './lib/actions/add-note-to-conversation'; +import { createOrUpdateContactAction } from './lib/actions/create-or-update-contact'; +import { findUserProfileAction } from './lib/actions/find-user-profile'; +import { updateConversationStateAction } from './lib/actions/update-conversation-state'; +import { findConversationAction } from './lib/actions/find-conversation'; +import { newContactTrigger } from './lib/triggers/new-contact'; +import { newConversationTrigger } from './lib/triggers/new-conversation'; +import { PieceCategory } from '@activepieces/shared'; + +export const crisp = createPiece({ + displayName: 'Crisp', + auth: crispAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/crisp.png', + authors: ['kishanprmr','Ani-4x'], + categories:[PieceCategory.CUSTOMER_SUPPORT], + actions: [ + addNoteToConversationAction, + createConversationAction, + createOrUpdateContactAction, + updateConversationStateAction, + findConversationAction, + findUserProfileAction, + createCustomApiCallAction({ + auth: crispAuth, + baseUrl: () => BASE_URL, + authMapping: async (auth) => { + const authValue = auth as PiecePropValueSchema; + return { + Authorization: `Basic ${Buffer.from( + `${authValue.identifier}:${authValue.token}`, + ).toString('base64')}`, + 'X-Crisp-Tier': 'plugin', + 'Content-Type': 'application/json', + }; + }, + }), + ], + triggers: [newContactTrigger,newConversationTrigger], +}); diff --git a/packages/pieces/community/crisp/src/lib/actions/add-note-to-conversation.ts b/packages/pieces/community/crisp/src/lib/actions/add-note-to-conversation.ts new file mode 100644 index 0000000..4914e81 --- /dev/null +++ b/packages/pieces/community/crisp/src/lib/actions/add-note-to-conversation.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { crispAuth } from '../common/auth'; +import { sessionIdProp, websiteIdProp } from '../common/props'; +import { crispApiCall } from '../common/client'; + +export const addNoteToConversationAction = createAction({ + auth: crispAuth, + name: 'add_note', + displayName: 'Add Note to Conversation', + description: 'Adds an internal note to a conversation.', + props: { + websiteId: websiteIdProp, + sessionId: sessionIdProp, + content: Property.LongText({ + displayName: 'Note Content', + required: true, + }), + }, + async run(context) { + const { websiteId, sessionId, content } = context.propsValue; + + const response = await crispApiCall({ + auth: context.auth, + method: HttpMethod.POST, + resourceUri: `/website/${websiteId}/conversation/${sessionId}/message`, + body: { + type: 'note', + from: 'operator', + origin: 'chat', + content, + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/crisp/src/lib/actions/create-conversation.ts b/packages/pieces/community/crisp/src/lib/actions/create-conversation.ts new file mode 100644 index 0000000..7d2bb00 --- /dev/null +++ b/packages/pieces/community/crisp/src/lib/actions/create-conversation.ts @@ -0,0 +1,115 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { HttpError, HttpMethod } from '@activepieces/pieces-common'; +import { crispAuth } from '../common/auth'; +import { websiteIdProp } from '../common/props'; +import { crispApiCall } from '../common/client'; +import { HttpStatusCode } from 'axios'; + +export const createConversationAction = createAction({ + auth: crispAuth, + name: 'create_conversation', + displayName: 'Create Conversation', + description: 'Creates a new conversation.', + props: { + websiteId: websiteIdProp, + email: Property.ShortText({ + displayName: 'Customer Email', + required: true, + }), + username: Property.ShortText({ + displayName: 'Customer Username', + required: true, + }), + messageFrom: Property.StaticDropdown({ + displayName: 'Message From', + required: true, + options: { + disabled: false, + options: [ + { label: 'User', value: 'user' }, + { label: 'Operator', value: 'operator' }, + ], + }, + }), + message: Property.LongText({ + displayName: 'Message', + required: true, + }), + }, + async run(context) { + const { websiteId, message, messageFrom, email, username } = context.propsValue; + + // create user if doesn't exist + try { + await crispApiCall<{ data: Record }>({ + auth: context.auth, + method: HttpMethod.PUT, + resourceUri: `/website/${websiteId}/people/profile/${email}`, + body: { + email, + person: { + nickname: username, + }, + }, + }); + } catch (e) { + const err = e as HttpError; + if (err.response.status === HttpStatusCode.NotFound) { + await crispApiCall({ + auth: context.auth, + method: HttpMethod.POST, + resourceUri: `/website/${websiteId}/people/profile`, + body: { + email, + person: { + nickname: username, + }, + }, + }); + } + } + + // create empty conversation + const newConversationResponse = await crispApiCall<{ data: { session_id: string } }>({ + auth: context.auth, + method: HttpMethod.POST, + resourceUri: `/website/${websiteId}/conversation`, + body: {}, + }); + + // add text message in conversation + await crispApiCall({ + auth: context.auth, + method: HttpMethod.POST, + resourceUri: `/website/${websiteId}/conversation/${newConversationResponse.data.session_id}/message`, + body: { + type: 'text', + from: messageFrom, + origin: 'chat', + content: message, + }, + }); + + // update customer profile meta for conversation + await crispApiCall({ + auth: context.auth, + method: HttpMethod.PATCH, + resourceUri: `/website/${websiteId}/conversation/${newConversationResponse.data.session_id}/meta`, + body: { + email, + nickname:username + }, + }); + + + + const conversationResponse = await crispApiCall({ + auth: context.auth, + method: HttpMethod.GET, + resourceUri: `/website/${websiteId}/conversation/${newConversationResponse.data.session_id}`, + }); + + return conversationResponse; + }, +}); diff --git a/packages/pieces/community/crisp/src/lib/actions/create-or-update-contact.ts b/packages/pieces/community/crisp/src/lib/actions/create-or-update-contact.ts new file mode 100644 index 0000000..5f8e7ce --- /dev/null +++ b/packages/pieces/community/crisp/src/lib/actions/create-or-update-contact.ts @@ -0,0 +1,89 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { HttpError, HttpMethod } from '@activepieces/pieces-common'; +import { crispAuth } from '../common/auth'; +import { websiteIdProp } from '../common/props'; +import { crispApiCall } from '../common/client'; +import { HttpStatusCode } from 'axios'; + +export const createOrUpdateContactAction = createAction({ + auth: crispAuth, + name: 'create_update_contact', + displayName: 'Create/Update Contact', + description: 'Creates a new contact or updates an existing contact.', + props: { + websiteId: websiteIdProp, + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + phone: Property.ShortText({ + displayName: 'Phone Number', + required: false, + }), + address: Property.LongText({ + displayName: 'Address', + required: false, + }), + company: Property.ShortText({ + displayName: 'Company', + required: false, + }), + website: Property.ShortText({ + displayName: 'Contact Website', + required: false, + }), + notepad: Property.LongText({ + displayName: 'Notepad', + required: false, + }), + }, + async run(context) { + const { websiteId, website, email, name, phone, address, company, notepad } = + context.propsValue; + + const contactPayload: Record = { + email, + person: { + nickname: name, + }, + notepad, + }; + + if (phone) contactPayload['person']['phone'] = phone; + if (address) contactPayload['person']['address'] = address; + if (website) contactPayload['person']['website'] = website; + if (company) contactPayload['company'] = { name: company }; + + try { + await crispApiCall({ + auth: context.auth, + method: HttpMethod.PUT, + resourceUri: `/website/${websiteId}/people/profile/${email}`, + body: contactPayload, + }); + } catch (e) { + const err = e as HttpError; + if (err.response.status === HttpStatusCode.NotFound) { + await crispApiCall({ + auth: context.auth, + method: HttpMethod.POST, + resourceUri: `/website/${websiteId}/people/profile`, + body: contactPayload, + }); + } + } + + const response = await crispApiCall({ + auth: context.auth, + method: HttpMethod.GET, + resourceUri: `/website/${websiteId}/people/profile/${email}`, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/crisp/src/lib/actions/find-conversation.ts b/packages/pieces/community/crisp/src/lib/actions/find-conversation.ts new file mode 100644 index 0000000..34d9cc5 --- /dev/null +++ b/packages/pieces/community/crisp/src/lib/actions/find-conversation.ts @@ -0,0 +1,40 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { crispAuth } from '../common/auth'; +import { websiteIdProp } from '../common/props'; +import { crispApiCall } from '../common/client'; + +export const findConversationAction = createAction({ + auth: crispAuth, + name: 'find_conversation', + displayName: 'Find Conversation', + description: 'Searches for conversations matching the specified criteria.', + props: { + websiteId: websiteIdProp, + searchQuery: Property.ShortText({ + displayName: 'Search Query', + required: true, + }), + }, + async run(context) { + const { websiteId, searchQuery } = context.propsValue; + if (!websiteId || !searchQuery) { + throw new Error('Website ID and search query are required'); + } + + const response = await crispApiCall<{ data: Record[] }>({ + auth: context.auth, + method: HttpMethod.GET, + resourceUri: `/website/${websiteId}/conversations/1`, + query: { + search_query: searchQuery, + search_type: 'text', + }, + }); + + return { + found: response.data.length > 0, + data: response.data, + }; + }, +}); diff --git a/packages/pieces/community/crisp/src/lib/actions/find-user-profile.ts b/packages/pieces/community/crisp/src/lib/actions/find-user-profile.ts new file mode 100644 index 0000000..98af142 --- /dev/null +++ b/packages/pieces/community/crisp/src/lib/actions/find-user-profile.ts @@ -0,0 +1,48 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { HttpError, HttpMethod } from '@activepieces/pieces-common'; +import { crispAuth } from '../common/auth'; +import { websiteIdProp } from '../common/props'; +import { crispApiCall } from '../common/client'; +import { HttpStatusCode } from 'axios'; + +export const findUserProfileAction = createAction({ + auth: crispAuth, + name: 'find_user_profile', + displayName: 'Find User Profile', + description: 'Finds a user profile by email address.', + props: { + websiteId: websiteIdProp, + email: Property.ShortText({ + displayName: 'Email Address', + description: 'The email address of the user to find.', + required: true, + }), + }, + async run(context) { + const { websiteId, email } = context.propsValue; + + try { + const response = await crispApiCall<{ data: Record }>({ + auth: context.auth, + method: HttpMethod.GET, + resourceUri: `/website/${websiteId}/people/profile/${email}`, + }); + + return { + found: true, + data: response.data, + }; + } catch (e) { + const err = e as HttpError; + if (err.response.status === HttpStatusCode.NotFound) { + return { + found: false, + data: {}, + }; + } + + throw err; + } + }, +}); diff --git a/packages/pieces/community/crisp/src/lib/actions/update-conversation-state.ts b/packages/pieces/community/crisp/src/lib/actions/update-conversation-state.ts new file mode 100644 index 0000000..561d4b5 --- /dev/null +++ b/packages/pieces/community/crisp/src/lib/actions/update-conversation-state.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { crispAuth } from '../common/auth'; +import { sessionIdProp, websiteIdProp } from '../common/props'; +import { crispApiCall } from '../common/client'; + +export const updateConversationStateAction = createAction({ + auth: crispAuth, + name: 'change_state', + displayName: 'Change Conversation State', + description: 'Updates the state of a conversation.', + props: { + websiteId: websiteIdProp, + sessionId: sessionIdProp, + state: Property.StaticDropdown({ + displayName: 'State', + required: true, + options: { + options: [ + { label: 'Unresolved', value: 'unresolved' }, + { label: 'Resolved', value: 'resolved' }, + { label: 'Pending', value: 'pending' }, + ], + }, + }), + }, + async run(context) { + const { websiteId, sessionId, state } = context.propsValue; + + const response = await crispApiCall({ + auth: context.auth, + method: HttpMethod.PATCH, + resourceUri: `/website/${websiteId}/conversation/${sessionId}/state`, + body: { + state, + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/crisp/src/lib/common/auth.ts b/packages/pieces/community/crisp/src/lib/common/auth.ts new file mode 100644 index 0000000..1c37d26 --- /dev/null +++ b/packages/pieces/community/crisp/src/lib/common/auth.ts @@ -0,0 +1,32 @@ +import { PieceAuth } from '@activepieces/pieces-framework'; + +const authMarkdown = ` + +1.Go to the [Crisp Marketplace](https://marketplace.crisp.chat/); +2.Sign in or create an account (this account is different from your main Crisp account); +3.Once logged-in, go to Plugins and click on the New Plugin button; +4.Select the plugin type, in this case Private; +5.Give your plugin a name, eg. "Integration", and hit Create; +6.On the plugin page, go to Tokens and scroll down to Production; +7.Click on "Ask a production token", and pick the scopes you require; + - website:conversation:messages website:conversation:sessions website:people:profiles +8.Click on "Request production token"; +(wait that your submission gets approved, this can take a few minutes); +9.Once accepted, come back to Tokens, and copy your Production token keypair (configure it securely in your integration code); +10.Finally, install the plugin on all websites you need to use it for (using the private install link, provided in the Settings section under Danger Zone / Visibility); +`; + +export const crispAuth = PieceAuth.CustomAuth({ + description: authMarkdown, + required: true, + props: { + identifier: PieceAuth.SecretText({ + displayName: 'Identifier', + required: true, + }), + token: PieceAuth.SecretText({ + displayName: 'Key', + required: true, + }), + }, +}); diff --git a/packages/pieces/community/crisp/src/lib/common/client.ts b/packages/pieces/community/crisp/src/lib/common/client.ts new file mode 100644 index 0000000..5c971e3 --- /dev/null +++ b/packages/pieces/community/crisp/src/lib/common/client.ts @@ -0,0 +1,58 @@ +import { + AuthenticationType, + httpClient, + HttpHeaders, + HttpMessageBody, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; +import { PiecePropValueSchema } from '@activepieces/pieces-framework'; +import { crispAuth } from './auth'; + + +export const BASE_URL = 'https://api.crisp.chat/v1' + +export type CrispApiCallParams = { + auth: PiecePropValueSchema; + method: HttpMethod; + resourceUri: string; + query?: Record; + body?: any; +}; + +export async function crispApiCall({ + auth, + method, + resourceUri, + query, + body, +}: CrispApiCallParams): Promise { + const qs: QueryParams = {}; + + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + qs[key] = String(value); + } + } + } + + const request: HttpRequest = { + method, + url: BASE_URL + resourceUri, + authentication:{ + type:AuthenticationType.BASIC, + username:auth.identifier, + password:auth.token + }, + headers:{ + 'X-Crisp-Tier':'plugin' + }, + queryParams: qs, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} diff --git a/packages/pieces/community/crisp/src/lib/common/props.ts b/packages/pieces/community/crisp/src/lib/common/props.ts new file mode 100644 index 0000000..2197c62 --- /dev/null +++ b/packages/pieces/community/crisp/src/lib/common/props.ts @@ -0,0 +1,14 @@ +import { Property } from '@activepieces/pieces-framework'; + +export const websiteIdProp = Property.ShortText({ + displayName: 'Website ID', + description: + 'You can obtain website ID by navigating to Settings -> Workspace Settings -> Setup & Integrations.', + required: true, +}); + +export const sessionIdProp = Property.ShortText({ + displayName: 'Conversation ID (session_id)', + description:`You can obtain session from URL address bar.It starts with 'session_'.`, + required: true, +}); diff --git a/packages/pieces/community/crisp/src/lib/triggers/new-contact.ts b/packages/pieces/community/crisp/src/lib/triggers/new-contact.ts new file mode 100644 index 0000000..65973d4 --- /dev/null +++ b/packages/pieces/community/crisp/src/lib/triggers/new-contact.ts @@ -0,0 +1,88 @@ +import { + createTrigger, + TriggerStrategy, + Property, + PiecePropValueSchema, +} from '@activepieces/pieces-framework'; +import { crispAuth } from '../common/auth'; +import { websiteIdProp } from '../common/props'; +import { + DedupeStrategy, + HttpMethod, + Polling, + pollingHelper, + QueryParams, +} from '@activepieces/pieces-common'; +import dayjs from 'dayjs'; +import { crispApiCall } from '../common/client'; + +const polling: Polling, { websiteId: string }> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, lastFetchEpochMS, propsValue }) { + const websiteId = propsValue.websiteId; + + let page = 1; + let hasMoreItems = false; + const contacts = []; + do { + const qs: QueryParams = { + per_page: '50', + }; + if (lastFetchEpochMS) { + qs['filter_date_start'] = dayjs(lastFetchEpochMS).toString(); + } + + const response = await crispApiCall<{ data: { created_at: number }[] }>({ + auth, + method: HttpMethod.GET, + resourceUri: `/website/${websiteId}/people/profiles/${page}`, + query: qs, + }); + + const items = response.data || []; + contacts.push(...items); + + page++; + hasMoreItems = items.length > 0; + + if (lastFetchEpochMS === 0) break; + } while (hasMoreItems); + + return contacts.map((contact) => ({ + epochMilliSeconds: contact.created_at, + data: contact, + })); + }, +}; + +export const newContactTrigger = createTrigger({ + auth: crispAuth, + name: 'new_contact', + displayName: 'New Contact Created', + description: 'Triggers when a new contact is added.', + props: { + websiteId: websiteIdProp, + }, + type: TriggerStrategy.POLLING, + sampleData: {}, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, +}); diff --git a/packages/pieces/community/crisp/src/lib/triggers/new-conversation.ts b/packages/pieces/community/crisp/src/lib/triggers/new-conversation.ts new file mode 100644 index 0000000..2459b97 --- /dev/null +++ b/packages/pieces/community/crisp/src/lib/triggers/new-conversation.ts @@ -0,0 +1,88 @@ +import { + createTrigger, + TriggerStrategy, + Property, + PiecePropValueSchema, +} from '@activepieces/pieces-framework'; +import { crispAuth } from '../common/auth'; +import { websiteIdProp } from '../common/props'; +import { + DedupeStrategy, + HttpMethod, + Polling, + pollingHelper, + QueryParams, +} from '@activepieces/pieces-common'; +import dayjs from 'dayjs'; +import { crispApiCall } from '../common/client'; + +const polling: Polling, { websiteId: string }> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, lastFetchEpochMS, propsValue }) { + const websiteId = propsValue.websiteId; + + let page = 1; + let hasMoreItems = false; + const conversations = []; + do { + const qs: QueryParams = { + per_page: '50', + }; + if (lastFetchEpochMS) { + qs['filter_date_start'] = dayjs(lastFetchEpochMS).toString(); + } + + const response = await crispApiCall<{ data: { created_at: number }[] }>({ + auth, + method: HttpMethod.GET, + resourceUri: `/website/${websiteId}/conversations/${page}`, + query: qs, + }); + + const items = response.data || []; + conversations.push(...items); + + page++; + hasMoreItems = items.length > 0; + + if (lastFetchEpochMS === 0) break; + } while (hasMoreItems); + + return conversations.map((conv) => ({ + epochMilliSeconds: conv.created_at, + data: conv, + })); + }, +}; + +export const newConversationTrigger = createTrigger({ + auth: crispAuth, + name: 'new_conversation', + displayName: 'New Conversation Created', + description: 'Triggers when a new conversation is started.', + props: { + websiteId: websiteIdProp, + }, + type: TriggerStrategy.POLLING, + sampleData: {}, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, +}); diff --git a/packages/pieces/community/crisp/tsconfig.json b/packages/pieces/community/crisp/tsconfig.json new file mode 100644 index 0000000..eff240a --- /dev/null +++ b/packages/pieces/community/crisp/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "importHelpers": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/crisp/tsconfig.lib.json b/packages/pieces/community/crisp/tsconfig.lib.json new file mode 100644 index 0000000..5995d95 --- /dev/null +++ b/packages/pieces/community/crisp/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/crypto/.eslintrc.json b/packages/pieces/community/crypto/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/crypto/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/crypto/README.md b/packages/pieces/community/crypto/README.md new file mode 100644 index 0000000..fcec32f --- /dev/null +++ b/packages/pieces/community/crypto/README.md @@ -0,0 +1,7 @@ +# pieces-crypto + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-crypto` to build the library. diff --git a/packages/pieces/community/crypto/package.json b/packages/pieces/community/crypto/package.json new file mode 100644 index 0000000..83c1290 --- /dev/null +++ b/packages/pieces/community/crypto/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-crypto", + "version": "0.0.7" +} diff --git a/packages/pieces/community/crypto/project.json b/packages/pieces/community/crypto/project.json new file mode 100644 index 0000000..68837a5 --- /dev/null +++ b/packages/pieces/community/crypto/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-crypto", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/crypto/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/crypto", + "tsConfig": "packages/pieces/community/crypto/tsconfig.lib.json", + "packageJson": "packages/pieces/community/crypto/package.json", + "main": "packages/pieces/community/crypto/src/index.ts", + "assets": [ + "packages/pieces/community/crypto/*.md", + { + "input": "packages/pieces/community/crypto/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-community-crypto {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/crypto/src/index.ts b/packages/pieces/community/crypto/src/index.ts new file mode 100644 index 0000000..18dd507 --- /dev/null +++ b/packages/pieces/community/crypto/src/index.ts @@ -0,0 +1,19 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { generatePassword } from './lib/actions/generate-password'; +import { hashText } from './lib/actions/hash-text'; +import { hmacSignature } from './lib/actions/hmac-signature'; +import { base64Decode } from './lib/actions/base64-decode'; +import { base64Encode } from './lib/actions/base64-encode'; + +export const Crypto = createPiece({ + displayName: 'Crypto', + description: 'Generate random passwords and hash existing text', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/crypto.png', + categories: [PieceCategory.CORE], + authors: ['AbdullahBitar', 'kishanprmr', 'abuaboud', 'matthieu-lombard', 'antonyvigouret', 'danielpoonwj'], + actions: [hashText, hmacSignature, generatePassword, base64Decode, base64Encode], + triggers: [], +}); diff --git a/packages/pieces/community/crypto/src/lib/actions/base64-decode.ts b/packages/pieces/community/crypto/src/lib/actions/base64-decode.ts new file mode 100644 index 0000000..b708264 --- /dev/null +++ b/packages/pieces/community/crypto/src/lib/actions/base64-decode.ts @@ -0,0 +1,17 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; + +export const base64Decode = createAction({ + name: 'base64-decode', + displayName: 'Base64 Decode', + description:'Converts base64 text back to plain text.', + props: { + text: Property.ShortText({ + displayName: 'Text', + description: 'The text to be decoded.', + required: true, + }), + }, + async run(context) { + return Buffer.from(context.propsValue.text, 'base64').toString(); + }, +}); diff --git a/packages/pieces/community/crypto/src/lib/actions/base64-encode.ts b/packages/pieces/community/crypto/src/lib/actions/base64-encode.ts new file mode 100644 index 0000000..df0f5f0 --- /dev/null +++ b/packages/pieces/community/crypto/src/lib/actions/base64-encode.ts @@ -0,0 +1,17 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; + +export const base64Encode = createAction({ + name: 'base64-encode', + displayName: 'Base64 Encode', + description: 'Converts plain text into base64 format.', + props: { + text: Property.ShortText({ + displayName: 'Text', + description: 'The text to be encoded.', + required: true, + }), + }, + async run(context) { + return Buffer.from(context.propsValue.text).toString('base64'); + }, +}); diff --git a/packages/pieces/community/crypto/src/lib/actions/generate-password.ts b/packages/pieces/community/crypto/src/lib/actions/generate-password.ts new file mode 100644 index 0000000..05fc2e8 --- /dev/null +++ b/packages/pieces/community/crypto/src/lib/actions/generate-password.ts @@ -0,0 +1,50 @@ +import { + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const generatePassword = createAction({ + name: 'generate-password', + description: 'Generates a random password with the specified length', + displayName: 'Generate Password', + props: { + length: Property.Number({ + displayName: 'Password Length', + description: 'The length of the password (maximum 256)', + required: true, + }), + characterSet: Property.StaticDropdown({ + displayName: 'Character Set', + description: 'The character set to use when generating the password', + required: true, + defaultValue: 'alphanumeric', + options: { + options: [ + { label: 'Alphanumeric', value: 'alphanumeric' }, + { label: 'Alphanumeric + Symbols', value: 'alphanumeric-symbols' }, + ], + }, + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + length: z.number().max(256), + }); + + const charset = context.propsValue.characterSet === 'alphanumeric' + ? 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' + : 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+~`|}{[]:;?><,./-='; + + let password = ''; + const length = context.propsValue.length; + + for (let i = 0; i < length; i++) { + const randomIndex = Math.floor(Math.random() * charset.length); + password += charset[randomIndex]; + } + + return password; + }, +}); diff --git a/packages/pieces/community/crypto/src/lib/actions/hash-text.ts b/packages/pieces/community/crypto/src/lib/actions/hash-text.ts new file mode 100644 index 0000000..10b0052 --- /dev/null +++ b/packages/pieces/community/crypto/src/lib/actions/hash-text.ts @@ -0,0 +1,39 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import Crypto from 'crypto'; + +export const hashText = createAction({ + name: 'hash-text', + description: 'Converts text to a hash value using various hashing algorithms', + displayName: 'Text to Hash', + props: { + method: Property.StaticDropdown({ + displayName: 'Method', + description: 'The hashing algorithm to use', + required: true, + options: { + options: [ + { label: 'MD5', value: 'md5' }, + { label: 'SHA256', value: 'sha256' }, + { label: 'SHA512', value: 'sha512' }, + { label: 'SHA3-512', value: 'sha3-512' }, + ], + }, + }), + text: Property.ShortText({ + displayName: 'Text', + description: 'The text to be hashed', + required: true, + }), + }, + async run(context) { + + const hashAlgorithm = Crypto.createHash(context.propsValue.method); + + const text = context.propsValue.text; + hashAlgorithm.update(text); + + const hashedString = hashAlgorithm.digest('hex'); + + return hashedString; + }, +}); diff --git a/packages/pieces/community/crypto/src/lib/actions/hmac-signature.ts b/packages/pieces/community/crypto/src/lib/actions/hmac-signature.ts new file mode 100644 index 0000000..19f5b1d --- /dev/null +++ b/packages/pieces/community/crypto/src/lib/actions/hmac-signature.ts @@ -0,0 +1,62 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import Crypto from 'crypto'; +import { Buffer } from 'buffer'; + +export const hmacSignature = createAction({ + name: 'hmac-signature', + description: + 'Converts text to a HMAC signed hash value using various hashing algorithms', + displayName: 'Generate HMAC Signature', + props: { + secretKey: Property.ShortText({ + displayName: 'Secret key', + description: 'The secret key to encrypt', + required: true, + }), + secretKeyEncoding: Property.StaticDropdown<'utf-8' | 'hex' | 'base64'>({ + displayName: 'Secret key encoding', + description: 'The secret key encoding to use', + required: true, + options: { + options: [ + { label: 'UTF-8', value: 'utf-8' }, + { label: 'Hex', value: 'hex' }, + { label: 'Base64', value: 'base64' }, + ], + }, + }), + method: Property.StaticDropdown({ + displayName: 'Method', + description: 'The hashing algorithm to use', + required: true, + options: { + options: [ + { label: 'MD5', value: 'md5' }, + { label: 'SHA256', value: 'sha256' }, + { label: 'SHA512', value: 'sha512' }, + ], + }, + }), + text: Property.ShortText({ + displayName: 'Text', + description: 'The text to be hashed and encrypted', + required: true, + }), + }, + async run(context) { + const hashAlgorithm = Crypto.createHmac( + context.propsValue.method, + Buffer.from( + context.propsValue.secretKey, + context.propsValue.secretKeyEncoding + ) + ); + + const text = context.propsValue.text; + hashAlgorithm.update(text); + + const hashedString = hashAlgorithm.digest('hex'); + + return hashedString; + }, +}); diff --git a/packages/pieces/community/crypto/tsconfig.json b/packages/pieces/community/crypto/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/crypto/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/crypto/tsconfig.lib.json b/packages/pieces/community/crypto/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/crypto/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/csv/.babelrc b/packages/pieces/community/csv/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/csv/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/csv/.eslintrc.json b/packages/pieces/community/csv/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/csv/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/csv/README.md b/packages/pieces/community/csv/README.md new file mode 100644 index 0000000..edd1582 --- /dev/null +++ b/packages/pieces/community/csv/README.md @@ -0,0 +1,7 @@ +# pieces-csv + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-csv` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/csv/package.json b/packages/pieces/community/csv/package.json new file mode 100644 index 0000000..ef6d3f8 --- /dev/null +++ b/packages/pieces/community/csv/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-csv", + "version": "0.4.2" +} diff --git a/packages/pieces/community/csv/project.json b/packages/pieces/community/csv/project.json new file mode 100644 index 0000000..d311c55 --- /dev/null +++ b/packages/pieces/community/csv/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-csv", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/csv/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/csv", + "tsConfig": "packages/pieces/community/csv/tsconfig.lib.json", + "packageJson": "packages/pieces/community/csv/package.json", + "main": "packages/pieces/community/csv/src/index.ts", + "assets": [ + "packages/pieces/community/csv/*.md", + { + "input": "packages/pieces/community/csv/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/csv/src/index.ts b/packages/pieces/community/csv/src/index.ts new file mode 100644 index 0000000..7f2ba6f --- /dev/null +++ b/packages/pieces/community/csv/src/index.ts @@ -0,0 +1,16 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { csvToJsonAction } from './lib/actions/convert-csv-to-json'; +import { jsonToCsvAction } from './lib/actions/convert-json-to-csv'; + +export const csv = createPiece({ + displayName: 'CSV', + description: 'Manipulate CSV text', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/csv.svg', + auth: PieceAuth.None(), + categories: [PieceCategory.CORE], + actions: [csvToJsonAction, jsonToCsvAction], + authors: ["kishanprmr", "MoShizzle", "khaledmashaly", "abuaboud"], + triggers: [], +}); diff --git a/packages/pieces/community/csv/src/lib/actions/convert-csv-to-json.ts b/packages/pieces/community/csv/src/lib/actions/convert-csv-to-json.ts new file mode 100644 index 0000000..1ad7748 --- /dev/null +++ b/packages/pieces/community/csv/src/lib/actions/convert-csv-to-json.ts @@ -0,0 +1,53 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { isString } from '@activepieces/shared'; +import {parse} from 'csv-parse/sync'; + +export const csvToJsonAction = createAction({ + name: 'convert_csv_to_json', + displayName: 'Convert CSV to JSON', + description: + 'This function reads a CSV string and converts it into JSON array format.', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + csv_text: Property.LongText({ + displayName: 'CSV Text', + defaultValue: '', + required: true, + }), + has_headers: Property.Checkbox({ + displayName: 'Does the CSV have headers?', + defaultValue: false, + required: true, + }), + delimiter_type: Property.StaticDropdown({ + displayName: 'Delimiter Type', + description: 'Select the delimiter type for the CSV text.', + defaultValue: '', + required: true, + options: { + options: [ + { label: 'Comma', value: ',' }, + { label: 'Tab', value: '\t' }, + ], + }, + }), + }, + async run(context) { + const { csv_text, has_headers, delimiter_type } = context.propsValue; + if (!isString(csv_text)) { + throw new Error(JSON.stringify({ + message: 'The input should be a string.', + })) + } + + const records = parse(csv_text,{delimiter: delimiter_type,columns: has_headers ? true : false}); + return records; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/csv/src/lib/actions/convert-json-to-csv.ts b/packages/pieces/community/csv/src/lib/actions/convert-json-to-csv.ts new file mode 100644 index 0000000..5dd6c0b --- /dev/null +++ b/packages/pieces/community/csv/src/lib/actions/convert-json-to-csv.ts @@ -0,0 +1,73 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { flatten } from 'safe-flat'; +import { stringify } from "csv-stringify/sync"; + +const markdown = ` +**Notes**: +* The input should be a JSON array. +* The JSON object will be flattened If nested and the keys will be used as headers. +` +export const jsonToCsvAction = createAction({ + name: 'convert_json_to_csv', + displayName: 'Convert JSON to CSV', + description: 'This function reads a JSON array and converts it into CSV format.', + errorHandlingOptions: { + continueOnFailure: { hide: true }, + retryOnFailure: { hide: true }, + }, + props: { + markdown: Property.MarkDown({ + value: markdown, + }), + json_array: Property.Json({ + displayName: 'JSON Array', + defaultValue: [ + { + name: 'John', + age: 30, + address: { + street: '123 Main St', + city: 'Los Angeles', + } + }, + { + name: 'Jane', + age: 25, + address: { + street: '123 Main St', + city: 'Los Angeles', + } + } + ], + description: + 'Provide a JSON array to convert to CSV format.', + required: true, + }), + delimiter_type: Property.StaticDropdown({ + displayName: 'Delimiter Type', + description: 'Select the delimiter type for the CSV file.', + defaultValue: ',', + required: true, + options: { + options: [ + { label: 'Comma', value: ',' }, + { label: 'Tab', value: '\t' }, + ], + }, + }), + }, + async run(context) { + const { json_array, delimiter_type } = context.propsValue; + if (!Array.isArray(json_array)) { + throw new Error(JSON.stringify({ + message: 'The input should be a JSON array.', + })) + } + const flattened = json_array.map((item) => flatten(item) as Record); + + return stringify(flattened, { + header: true, + delimiter: delimiter_type, + }); + }, +}); diff --git a/packages/pieces/community/csv/tsconfig.json b/packages/pieces/community/csv/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/csv/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/csv/tsconfig.lib.json b/packages/pieces/community/csv/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/csv/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/customer-io/.eslintrc.json b/packages/pieces/community/customer-io/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/customer-io/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/customer-io/README.md b/packages/pieces/community/customer-io/README.md new file mode 100644 index 0000000..050dc7d --- /dev/null +++ b/packages/pieces/community/customer-io/README.md @@ -0,0 +1,9 @@ +# pieces-customer-io + +- Track Custom API (https://www.customer.io/docs/api/track/) +- Base Custom API (https://customer.io/docs/api/app/#section/Overview) +- Create Event + +## Building + +Run `nx build pieces-customer-io` to build the library. diff --git a/packages/pieces/community/customer-io/package.json b/packages/pieces/community/customer-io/package.json new file mode 100644 index 0000000..833959a --- /dev/null +++ b/packages/pieces/community/customer-io/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-customer-io", + "version": "0.2.1" +} diff --git a/packages/pieces/community/customer-io/project.json b/packages/pieces/community/customer-io/project.json new file mode 100644 index 0000000..7949fff --- /dev/null +++ b/packages/pieces/community/customer-io/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-customer-io", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/customer-io/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/customer-io", + "tsConfig": "packages/pieces/community/customer-io/tsconfig.lib.json", + "packageJson": "packages/pieces/community/customer-io/package.json", + "main": "packages/pieces/community/customer-io/src/index.ts", + "assets": [ + "packages/pieces/community/customer-io/*.md", + { + "input": "packages/pieces/community/customer-io/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-customer-io {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/customer-io/src/index.ts b/packages/pieces/community/customer-io/src/index.ts new file mode 100644 index 0000000..f05efd6 --- /dev/null +++ b/packages/pieces/community/customer-io/src/index.ts @@ -0,0 +1,105 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { createEvent } from './lib/actions/create_event'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { customerIOCommon } from './lib/common'; +import { Buffer } from 'buffer'; +import { PieceCategory } from '@activepieces/shared'; + +const markdown = ` +**Site ID:**\n + +Please log in and go to Settings, click [here](https://fly.customer.io/settings/api_credentials). + +**Tracking API Key:**\n + +Please log in and go to Settings, click [here](https://fly.customer.io/settings/api_credentials). + +**APP API Token:**\n + +Please log in and find it in Account Settings, click [here](https://fly.customer.io/settings/api_credentials?keyType=app). + + +
+Please note that the Track API Key and App API Key are different. You can read more about it [here](https://customer.io/docs/accounts-and-workspaces/managing-credentials/). +`; +export const customerIOAuth = PieceAuth.CustomAuth({ + props: { + region: Property.StaticDropdown<'us' | 'eu'>({ + displayName: 'Region', + required: true, + defaultValue: 'us', + options: { + options: [ + { label: 'US', value: 'us' }, + { label: 'EU', value: 'eu' }, + ], + }, + }), + track_site_id: Property.ShortText({ + displayName: 'Site ID', + required: true, + }), + track_api_key: Property.ShortText({ + displayName: 'API Key', + required: true, + }), + api_bearer_token: Property.ShortText({ + displayName: 'Bearer Token', + required: true, + }), + }, + description: markdown, + required: true, +}); + +type CustomerIOAuth = { + region: 'eu' | 'us'; + track_site_id: string; + track_api_key: string; + api_bearer_token: string; +}; +export const customerIo: any = createPiece({ + displayName: 'customer.io', + auth: customerIOAuth, + description: + 'Create personalized journeys across all channels with our customer engagement platform.', + categories: [PieceCategory.MARKETING], + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/customerio.png', + authors: ['hamedsh', 'AbuAboud', 'AdamSelene'], + actions: [ + createEvent, + createCustomApiCallAction({ + baseUrl: (auth) => + customerIOCommon[(auth as CustomerIOAuth).region].trackUrl, + auth: customerIOAuth, + name: 'custom_track_api_call', + description: 'CustomerIO Track Custom API Call (track.customer.io)', + displayName: 'Track Custom API Call', + authMapping: async (auth) => ({ + Authorization: `Basic ${Buffer.from( + `${(auth as CustomerIOAuth).track_site_id}:${ + (auth as CustomerIOAuth).track_api_key + }`, + 'utf8' + ).toString('base64')}`, + }), + }), + createCustomApiCallAction({ + baseUrl: (auth) => + customerIOCommon[(auth as CustomerIOAuth).region].apiUrl, + auth: customerIOAuth, + name: 'custom_app_api_call', + description: 'CustomerIO App Custom API Call (api.customer.io)', + displayName: 'App Custom API Call', + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as CustomerIOAuth).api_bearer_token}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/customer-io/src/lib/actions/create_event.ts b/packages/pieces/community/customer-io/src/lib/actions/create_event.ts new file mode 100644 index 0000000..e2833fa --- /dev/null +++ b/packages/pieces/community/customer-io/src/lib/actions/create_event.ts @@ -0,0 +1,101 @@ +import { createAction, DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { customerIOAuth } from '../../index'; +import { customerIOCommon } from '../common'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const createEvent = createAction({ + auth: customerIOAuth, + name: 'create_event', + displayName: 'Create Event', + description: 'Create an event in Customer.io', + props: { + customer_identifier: Property.ShortText({ + displayName: 'Customer identifier', + description: 'The unique value representing a person. You may identify a person by id, email address, or the cio_id (when updating people), depending on your workspace settings', + required: true, + }), + event_name: Property.ShortText({ + displayName: 'Event Name', + description: 'The name of the event. This is how you\'ll reference the event in campaigns or segments', + required: true, + }), + body_type: Property.StaticDropdown({ + displayName: 'parameters', + required: false, + description: 'Event information to be sent to Customer.io', + defaultValue: 'key_value', + options: { + disabled: false, + options: [ + { + label: 'Key Value', + value: 'key_value', + }, + { + label: 'JSON', + value: 'json', + }, + ], + }, + }), + body: Property.DynamicProperties({ + displayName: 'Body', + refreshers: ['body_type'], + required: false, + props: async ({ body_type }) => { + if (!body_type) return {}; + + const bodyTypeInput = body_type as unknown as string; + + const fields: DynamicPropsValue = {}; + + switch (bodyTypeInput) { + case 'json': + fields['data'] = Property.Json({ + displayName: 'JSON Body', + required: true, + }); + break; + case 'key_value': + fields['data'] = Property.Object({ + displayName: 'Key Value', + required: true, + }); + break; + } + return fields; + }, + }), + }, + async run(context) { + const { + body, + body_type, + } = context.propsValue; + + const basic_track_api = `${ + Buffer.from( + `${context.auth.track_site_id}:${context.auth.track_api_key}`, + 'utf8' + ).toString('base64') + }` + + const headers = { + Authorization: `Basic ${basic_track_api}`, + 'Content-Type': 'application/json', + } + const encoded_email = encodeURIComponent(context.propsValue.customer_identifier); + const url = customerIOCommon[context.auth.region || 'us'].trackUrl + 'customers/' + encoded_email + '/events'; + + const httprequestdata = { + method: HttpMethod.POST, + url, + headers, + body: { + name: context.propsValue.event_name, + data: body ? body['data'] : {}, + }, + } + return await httpClient.sendRequest(httprequestdata); + }, +}) diff --git a/packages/pieces/community/customer-io/src/lib/common/index.ts b/packages/pieces/community/customer-io/src/lib/common/index.ts new file mode 100644 index 0000000..57b95cf --- /dev/null +++ b/packages/pieces/community/customer-io/src/lib/common/index.ts @@ -0,0 +1,10 @@ +export const customerIOCommon = { + us: { + trackUrl: 'https://track.customer.io/api/v1/', + apiUrl: 'https://api.customer.io/v1/', + }, + eu: { + trackUrl: 'https://track-eu.customer.io/api/v1/', + apiUrl: 'https://api-eu.customer.io/v1/', + }, +}; diff --git a/packages/pieces/community/customer-io/tsconfig.json b/packages/pieces/community/customer-io/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/customer-io/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/customer-io/tsconfig.lib.json b/packages/pieces/community/customer-io/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/customer-io/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/dappier/.eslintrc.json b/packages/pieces/community/dappier/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/dappier/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/dappier/README.md b/packages/pieces/community/dappier/README.md new file mode 100644 index 0000000..5849ee0 --- /dev/null +++ b/packages/pieces/community/dappier/README.md @@ -0,0 +1,7 @@ +# pieces-dappier + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-dappier` to build the library. diff --git a/packages/pieces/community/dappier/package.json b/packages/pieces/community/dappier/package.json new file mode 100644 index 0000000..63915dc --- /dev/null +++ b/packages/pieces/community/dappier/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-dappier", + "version": "0.0.1" +} diff --git a/packages/pieces/community/dappier/project.json b/packages/pieces/community/dappier/project.json new file mode 100644 index 0000000..9c93a7c --- /dev/null +++ b/packages/pieces/community/dappier/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-dappier", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/dappier/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/dappier", + "tsConfig": "packages/pieces/community/dappier/tsconfig.lib.json", + "packageJson": "packages/pieces/community/dappier/package.json", + "main": "packages/pieces/community/dappier/src/index.ts", + "assets": [ + "packages/pieces/community/dappier/*.md", + { + "input": "packages/pieces/community/dappier/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/dappier/src/index.ts b/packages/pieces/community/dappier/src/index.ts new file mode 100644 index 0000000..1f020e0 --- /dev/null +++ b/packages/pieces/community/dappier/src/index.ts @@ -0,0 +1,31 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { realTimeWebSearch } from './lib/actions/real-time-data'; +import { sportsNewsSearch } from './lib/actions/sports-news'; +import { stockMarketDataSearch } from './lib/actions/stock-market-data'; +import { lifestyleNewsSearch } from './lib/actions/lifestyle-news'; + +export const dappierAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'Enter your Dappier API Key. You can generate one at https://platform.dappier.com/profile/api-keys.', +}); + +export const dappier = createPiece({ + displayName: 'Dappier', + logoUrl: 'https://cdn.activepieces.com/pieces/dappier.png', + description: 'Enable fast, free real-time web search and access premium data from trusted media brands—news, financial markets, sports, entertainment, weather, and more. Build powerful AI agents with Dappier', + auth: dappierAuth, + authors: [], + actions: [ + realTimeWebSearch, + stockMarketDataSearch, + sportsNewsSearch, + lifestyleNewsSearch + ], + triggers: [], + categories: [ + PieceCategory.ARTIFICIAL_INTELLIGENCE, + PieceCategory.BUSINESS_INTELLIGENCE + ] +}); diff --git a/packages/pieces/community/dappier/src/lib/actions/lifestyle-news.ts b/packages/pieces/community/dappier/src/lib/actions/lifestyle-news.ts new file mode 100644 index 0000000..773778a --- /dev/null +++ b/packages/pieces/community/dappier/src/lib/actions/lifestyle-news.ts @@ -0,0 +1,66 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { dappierAuth } from '../..'; +import { dappierCommon } from '../common'; + +export const lifestyleNewsSearch = createAction({ + name: 'lifestyle_news_search', + auth: dappierAuth, + displayName: 'Lifestyle News', + description: + 'Real-time updates, analysis, and personalized content from top sources like The Mix, Snipdaily, Nerdable, and Familyproof.', + props: { + query: Property.ShortText({ + displayName: 'Search Query', + description: 'Enter a natural language query or URL', + required: true, + }), + similarity_top_k: Property.Number({ + displayName: 'Number of Results', + description: 'Number of articles to return (default is 9)', + required: false, + }), + ref: Property.ShortText({ + displayName: 'Preferred Domain', + description: 'Restrict results to a domain (e.g., familyproof.com)', + required: false, + }), + num_articles_ref: Property.Number({ + displayName: 'Guaranteed Articles from Domain', + description: 'Minimum number of articles to match the preferred domain', + required: false, + }), + search_algorithm: Property.StaticDropdown({ + displayName: 'Search Algorithm', + description: 'Choose how to match articles', + required: false, + options: { + options: [ + { label: 'Semantic (Contextual)', value: 'semantic' }, + { label: 'Most Recent', value: 'most_recent' }, + { label: 'Most Recent + Semantic', value: 'most_recent_semantic' }, + { label: 'Trending', value: 'trending' }, + ], + }, + }), + }, + async run({ propsValue, auth }) { + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${dappierCommon.baseUrl}/app/v2/search?data_model_id=dm_01j0q82s4bfjmsqkhs3ywm3x6y`, + headers: { + Authorization: `Bearer ${auth}`, + 'Content-Type': 'application/json', + }, + body: { + query: propsValue.query, + similarity_top_k: propsValue.similarity_top_k, + ref: propsValue.ref, + num_articles_ref: propsValue.num_articles_ref, + search_algorithm: propsValue.search_algorithm, + }, + }); + + return res.body; + }, +}); diff --git a/packages/pieces/community/dappier/src/lib/actions/real-time-data.ts b/packages/pieces/community/dappier/src/lib/actions/real-time-data.ts new file mode 100644 index 0000000..957db89 --- /dev/null +++ b/packages/pieces/community/dappier/src/lib/actions/real-time-data.ts @@ -0,0 +1,34 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { dappierAuth } from '../..'; +import { dappierCommon } from '../common'; + +export const realTimeWebSearch = createAction({ + name: 'real_time_web_search', + auth: dappierAuth, + displayName: 'Real Time Data', + description: + 'Access real-time Google web search results including the latest news, weather, travel, deals and more.', + props: { + query: Property.ShortText({ + displayName: 'Search Query', + description: 'Enter your search query', + required: true, + }), + }, + async run({ auth, propsValue }) { + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${dappierCommon.baseUrl}/app/aimodel/am_01j0rzq4tvfscrgzwac7jv1p4c`, + headers: { + Authorization: `Bearer ${auth}`, + 'Content-Type': 'application/json', + }, + body: { + query: propsValue.query, + }, + }); + + return res.body; + }, +}); diff --git a/packages/pieces/community/dappier/src/lib/actions/sports-news.ts b/packages/pieces/community/dappier/src/lib/actions/sports-news.ts new file mode 100644 index 0000000..99fab53 --- /dev/null +++ b/packages/pieces/community/dappier/src/lib/actions/sports-news.ts @@ -0,0 +1,66 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { dappierAuth } from '../..'; +import { dappierCommon } from '../common'; + +export const sportsNewsSearch = createAction({ + name: 'sports_news_search', + auth: dappierAuth, + displayName: 'Sports News', + description: + 'Real-time news, updates, and personalized content from top sports sources like Sportsnaut, Forever Blueshirts, Minnesota Sports Fan, LAFB Network, Bounding Into Sports, and Ringside Intel.', + props: { + query: Property.ShortText({ + displayName: 'Search Query', + description: 'Enter a natural language query or URL', + required: true, + }), + similarity_top_k: Property.Number({ + displayName: 'Number of Results', + description: 'Number of articles to return (default is 9)', + required: false, + }), + ref: Property.ShortText({ + displayName: 'Preferred Domain', + description: 'Restrict results to a domain (e.g., sportsnaut.com)', + required: false, + }), + num_articles_ref: Property.Number({ + displayName: 'Guaranteed Articles from Domain', + description: 'Minimum number of articles to match the preferred domain', + required: false, + }), + search_algorithm: Property.StaticDropdown({ + displayName: 'Search Algorithm', + description: 'Choose how to match articles', + required: false, + options: { + options: [ + { label: 'Semantic (Contextual)', value: 'semantic' }, + { label: 'Most Recent', value: 'most_recent' }, + { label: 'Most Recent + Semantic', value: 'most_recent_semantic' }, + { label: 'Trending', value: 'trending' }, + ], + }, + }), + }, + async run({ propsValue, auth }) { + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${dappierCommon.baseUrl}/app/v2/search?data_model_id=dm_01j0pb465keqmatq9k83dthx34`, + headers: { + Authorization: `Bearer ${auth}`, + 'Content-Type': 'application/json', + }, + body: { + query: propsValue.query, + similarity_top_k: propsValue.similarity_top_k, + ref: propsValue.ref, + num_articles_ref: propsValue.num_articles_ref, + search_algorithm: propsValue.search_algorithm, + }, + }); + + return res.body; + }, +}); diff --git a/packages/pieces/community/dappier/src/lib/actions/stock-market-data.ts b/packages/pieces/community/dappier/src/lib/actions/stock-market-data.ts new file mode 100644 index 0000000..3f802d9 --- /dev/null +++ b/packages/pieces/community/dappier/src/lib/actions/stock-market-data.ts @@ -0,0 +1,34 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { dappierAuth } from '../..'; +import { dappierCommon } from '../common'; + +export const stockMarketDataSearch = createAction({ + name: 'stock_market_data_search', + auth: dappierAuth, + displayName: 'Stock Market Data', + description: + 'Access real-time financial news, stock prices, and trades from polygon.io, with AI-powered insights and up-to-the-minute updates to keep you informed on all your financial interests.', + props: { + query: Property.ShortText({ + displayName: 'Search Query', + description: 'Enter your stock market query', + required: true, + }), + }, + async run({ propsValue, auth }) { + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${dappierCommon.baseUrl}/app/aimodel/am_01j749h8pbf7ns8r1bq9s2evrh`, + headers: { + Authorization: `Bearer ${auth}`, + 'Content-Type': 'application/json', + }, + body: { + query: propsValue.query, + }, + }); + + return res.body; + }, +}); diff --git a/packages/pieces/community/dappier/src/lib/common/index.ts b/packages/pieces/community/dappier/src/lib/common/index.ts new file mode 100644 index 0000000..520394d --- /dev/null +++ b/packages/pieces/community/dappier/src/lib/common/index.ts @@ -0,0 +1,3 @@ +export const dappierCommon = { + baseUrl: 'https://api.dappier.com', +}; diff --git a/packages/pieces/community/dappier/tsconfig.json b/packages/pieces/community/dappier/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/dappier/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/dappier/tsconfig.lib.json b/packages/pieces/community/dappier/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/dappier/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/data-mapper/.eslintrc.json b/packages/pieces/community/data-mapper/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/data-mapper/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/data-mapper/README.md b/packages/pieces/community/data-mapper/README.md new file mode 100644 index 0000000..f14b0aa --- /dev/null +++ b/packages/pieces/community/data-mapper/README.md @@ -0,0 +1,7 @@ +# pieces-data-mapper + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-data-mapper` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/data-mapper/package.json b/packages/pieces/community/data-mapper/package.json new file mode 100644 index 0000000..ac3e38e --- /dev/null +++ b/packages/pieces/community/data-mapper/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-data-mapper", + "version": "0.3.6" +} \ No newline at end of file diff --git a/packages/pieces/community/data-mapper/project.json b/packages/pieces/community/data-mapper/project.json new file mode 100644 index 0000000..8e1d70b --- /dev/null +++ b/packages/pieces/community/data-mapper/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-data-mapper", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/data-mapper/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/data-mapper", + "tsConfig": "packages/pieces/community/data-mapper/tsconfig.lib.json", + "packageJson": "packages/pieces/community/data-mapper/package.json", + "main": "packages/pieces/community/data-mapper/src/index.ts", + "assets": [ + "packages/pieces/community/data-mapper/*.md", + { + "input": "packages/pieces/community/data-mapper/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/data-mapper/src/index.ts b/packages/pieces/community/data-mapper/src/index.ts new file mode 100644 index 0000000..89e7e44 --- /dev/null +++ b/packages/pieces/community/data-mapper/src/index.ts @@ -0,0 +1,16 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { advancedMapping } from './lib/actions/advanced-mapping'; + +export const dataMapper = createPiece({ + displayName: 'Data Mapper', + description: 'tools to manipulate data structure', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/data-mapper.png', + auth: PieceAuth.None(), + categories: [PieceCategory.CORE], + authors: ["kishanprmr","MoShizzle","AbdulTheActivePiecer","khaledmashaly","abuaboud"], + actions: [advancedMapping], + triggers: [], +}); diff --git a/packages/pieces/community/data-mapper/src/lib/actions/advanced-mapping.ts b/packages/pieces/community/data-mapper/src/lib/actions/advanced-mapping.ts new file mode 100644 index 0000000..e0641d6 --- /dev/null +++ b/packages/pieces/community/data-mapper/src/lib/actions/advanced-mapping.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +export const advancedMapping = createAction({ + name: 'advanced_mapping', + displayName: 'Advanced Mapping', + description: 'Map data from one format to another', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + mapping: Property.Json({ + displayName: 'Mapping', + description: 'The mapping to use', + required: true, + defaultValue: { + newProperty: 'oldProperty', + }, + }), + }, + async run(ctx) { + return ctx.propsValue.mapping; + }, +}); diff --git a/packages/pieces/community/data-mapper/tsconfig.json b/packages/pieces/community/data-mapper/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/data-mapper/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/data-mapper/tsconfig.lib.json b/packages/pieces/community/data-mapper/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/data-mapper/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/data-summarizer/.eslintrc.json b/packages/pieces/community/data-summarizer/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/data-summarizer/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/data-summarizer/README.md b/packages/pieces/community/data-summarizer/README.md new file mode 100644 index 0000000..e3c70e9 --- /dev/null +++ b/packages/pieces/community/data-summarizer/README.md @@ -0,0 +1,7 @@ +# pieces-data-summarizer + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-data-summarizer` to build the library. diff --git a/packages/pieces/community/data-summarizer/package.json b/packages/pieces/community/data-summarizer/package.json new file mode 100644 index 0000000..f4b3a74 --- /dev/null +++ b/packages/pieces/community/data-summarizer/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-data-summarizer", + "version": "0.0.2" +} diff --git a/packages/pieces/community/data-summarizer/project.json b/packages/pieces/community/data-summarizer/project.json new file mode 100644 index 0000000..97d6c54 --- /dev/null +++ b/packages/pieces/community/data-summarizer/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-data-summarizer", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/data-summarizer/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/data-summarizer", + "tsConfig": "packages/pieces/community/data-summarizer/tsconfig.lib.json", + "packageJson": "packages/pieces/community/data-summarizer/package.json", + "main": "packages/pieces/community/data-summarizer/src/index.ts", + "assets": [ + "packages/pieces/community/data-summarizer/*.md", + { + "input": "packages/pieces/community/data-summarizer/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/data-summarizer/src/index.ts b/packages/pieces/community/data-summarizer/src/index.ts new file mode 100644 index 0000000..4175f4c --- /dev/null +++ b/packages/pieces/community/data-summarizer/src/index.ts @@ -0,0 +1,17 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { calculateAverage } from './lib/actions/calculate-average'; +import { calculateSum } from './lib/actions/calculate-sum'; +import { countUniques } from './lib/actions/count-uniques'; +import { getMinMax } from './lib/actions/get-min-max'; +import { PieceCategory } from '@activepieces/shared'; + +export const dataSummarizer = createPiece({ + displayName: 'Data Summarizer', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/data-summarizer.svg', + authors: ['tahboubali'], + actions: [calculateAverage, calculateSum, countUniques, getMinMax], + triggers: [], + categories: [PieceCategory.CORE] +}); diff --git a/packages/pieces/community/data-summarizer/src/lib/actions/calculate-average.ts b/packages/pieces/community/data-summarizer/src/lib/actions/calculate-average.ts new file mode 100644 index 0000000..31d209f --- /dev/null +++ b/packages/pieces/community/data-summarizer/src/lib/actions/calculate-average.ts @@ -0,0 +1,25 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { common } from '../common'; + +export const calculateAverage = createAction({ + name: 'calculateAverage', + displayName: 'Calculate Average', + description: 'Calculates the average of a list of values.', + props: { + note: common.note, + values: Property.Array({ + displayName: "Values", + required: true, + }) + }, + async run({ propsValue }) { + const result = common.validateArray(propsValue.values); + if (result.hasError) { + throw new Error(JSON.stringify(result.error)); + } + const sum = result.values.reduce((acc, value) => acc + value, 0); + return { + average: sum / result.values.length + }; + }, +}); diff --git a/packages/pieces/community/data-summarizer/src/lib/actions/calculate-sum.ts b/packages/pieces/community/data-summarizer/src/lib/actions/calculate-sum.ts new file mode 100644 index 0000000..8b5f9d8 --- /dev/null +++ b/packages/pieces/community/data-summarizer/src/lib/actions/calculate-sum.ts @@ -0,0 +1,25 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { common } from '../common'; + +export const calculateSum = createAction({ + name: 'calculateSum', + displayName: 'Calculate Sum', + description: 'Calculates the sum of a list of values.', + props: { + note: common.note, + values: Property.Array({ + displayName: "Values", + required: true, + }) + }, + async run({ propsValue }) { + const result = common.validateArray(propsValue.values); + if (result.hasError) { + throw new Error(JSON.stringify(result.error)); + } + const sum = result.values.reduce((acc, value) => acc + value, 0); + return { + sum: sum + }; + }, +}); diff --git a/packages/pieces/community/data-summarizer/src/lib/actions/count-uniques.ts b/packages/pieces/community/data-summarizer/src/lib/actions/count-uniques.ts new file mode 100644 index 0000000..ecebd83 --- /dev/null +++ b/packages/pieces/community/data-summarizer/src/lib/actions/count-uniques.ts @@ -0,0 +1,57 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { common } from '../common'; +import { isNil } from '@activepieces/shared'; + +export const countUniques = createAction({ + name: 'countUniques', + displayName: 'Count Uniques', + description: 'Counts the number of unique values for multiple fields', + props: { + note: common.note, + values: Property.Array({ + displayName: "Values", + required: true, + }), + fieldsExplanation: Property.MarkDown({ + value: "If the data you're passing in is an object, you can specify certain fields to filter on. The object will be discarded if the fields don't exist. Otherwise, leave fields empty." + }), + fields: Property.Array({ + displayName: "Fields", + required: false + }) + }, + async run({ propsValue }) { + const values = propsValue.values; + const unknownFields = propsValue.fields != undefined && propsValue.fields.length > 0 ? propsValue.fields : null; + const fields = validateFields(unknownFields) + return { + numUniques: numUniques(values, fields) + }; + }, +}); + +function validateFields(fields: unknown[] | null): string[] | null { + if (!isNil(fields) && Array.isArray(fields) && fields.every(value => typeof value === 'string')) { + return fields as string[] + } + else return null +} + +function numUniques(values: T[], fields: string[] | null = null) { + if (isNil(fields)) { + return new Set(values.map(value => JSON.stringify(value))).size + } + const newValues = values.map(value => { + const obj: { [k: string]: unknown } = {} + if (typeof value !== 'object') { + return obj + } + for (const key in value) { + if (fields.includes(key)) { + obj[key] = value[key] + } + } + return obj + }) + return new Set(newValues.map(value => JSON.stringify(value))).size +} \ No newline at end of file diff --git a/packages/pieces/community/data-summarizer/src/lib/actions/get-min-max.ts b/packages/pieces/community/data-summarizer/src/lib/actions/get-min-max.ts new file mode 100644 index 0000000..ffb6baf --- /dev/null +++ b/packages/pieces/community/data-summarizer/src/lib/actions/get-min-max.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { common } from '../common'; + +export const getMinMax = createAction({ + name: 'getMinMax', + displayName: 'Find Min and Max', + description: 'Get the smallest and greatest values from a list of numeric values.', + props: { + note: common.note, + values: Property.Array({ + displayName: 'Values', + required: true + }) + }, + async run({ propsValue }) { + const result = common.validateArray(propsValue.values); + if (result.hasError) + throw new Error(JSON.stringify(result.error)); + return { + max: Math.max(...result.values), + min: Math.min(...result.values) + }; + } +}); diff --git a/packages/pieces/community/data-summarizer/src/lib/common.ts b/packages/pieces/community/data-summarizer/src/lib/common.ts new file mode 100644 index 0000000..212dd90 --- /dev/null +++ b/packages/pieces/community/data-summarizer/src/lib/common.ts @@ -0,0 +1,63 @@ +import { Property } from "@activepieces/pieces-framework" +import { isNil } from "@activepieces/shared" + +type ErrorInfo = { + value: unknown | null, + location: number +} + +type Response = { + hasError: true, + error: { + message: string, + errors: ErrorInfo[] + } +} | { + hasError: false, + values: number[] +} + +export const common = { + note: Property.MarkDown({ + value: "If you'd like to use the values with a previous step, click the (X) first, and then select the step you want to use.", + }), + validateArray: function (values: unknown[]): Response { + const newValues = values.map((value, index) => checkValueIsNumber(value, index)) + const isAllNumbers = newValues.every((value) => value.error === null) + if(isAllNumbers) { + return { + hasError: false, + values: newValues.map((value) => value.value as number), + } + } + return { + hasError: true, + error: { + message: 'The following values are not numbers', + errors: newValues.filter((value) => !isNil(value)).map((value) => value.error as ErrorInfo) + } + } + } +} + +type ValueInfo = { + error: ErrorInfo | null, + value: number | null +} + +export const checkValueIsNumber = function (value: unknown, location: number): ValueInfo { + const parsedValue = Number(value); + if (!Number.isNaN(parsedValue)) { + return { + error: null, + value: Number(value) + } + } + return { + error: { + value: value, + location: location + }, + value: -1 + } +} \ No newline at end of file diff --git a/packages/pieces/community/data-summarizer/tsconfig.json b/packages/pieces/community/data-summarizer/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/data-summarizer/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/data-summarizer/tsconfig.lib.json b/packages/pieces/community/data-summarizer/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/data-summarizer/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/date-helper/.eslintrc.json b/packages/pieces/community/date-helper/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/date-helper/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/date-helper/README.md b/packages/pieces/community/date-helper/README.md new file mode 100644 index 0000000..742ee0a --- /dev/null +++ b/packages/pieces/community/date-helper/README.md @@ -0,0 +1,7 @@ +# pieces-date-helper + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-date-helper` to build the library. diff --git a/packages/pieces/community/date-helper/package.json b/packages/pieces/community/date-helper/package.json new file mode 100644 index 0000000..9d40a48 --- /dev/null +++ b/packages/pieces/community/date-helper/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-date-helper", + "version": "0.1.6" +} diff --git a/packages/pieces/community/date-helper/project.json b/packages/pieces/community/date-helper/project.json new file mode 100644 index 0000000..d75414a --- /dev/null +++ b/packages/pieces/community/date-helper/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-date-helper", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/date-helper/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/date-helper", + "tsConfig": "packages/pieces/community/date-helper/tsconfig.lib.json", + "packageJson": "packages/pieces/community/date-helper/package.json", + "main": "packages/pieces/community/date-helper/src/index.ts", + "assets": [ + "packages/pieces/community/date-helper/*.md", + { + "input": "packages/pieces/community/date-helper/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-utility-date {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/date-helper/src/index.ts b/packages/pieces/community/date-helper/src/index.ts new file mode 100644 index 0000000..1d699a8 --- /dev/null +++ b/packages/pieces/community/date-helper/src/index.ts @@ -0,0 +1,31 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { addSubtractDateAction } from './lib/actions/add-subtract-date'; +import { dateDifferenceAction } from './lib/actions/date-difference'; +import { extractDateParts } from './lib/actions/extract-date-parts'; +import { formatDateAction } from './lib/actions/format-date'; +import { getCurrentDate } from './lib/actions/get-current-date'; +import { nextDayofWeek } from './lib/actions/next-day-of-week'; +import { nextDayofYear } from './lib/actions/next-day-of-year'; + +const description = `Manipulate, format, and extract time units for all your date and time needs.`; + +export const utilityDate = createPiece({ + displayName: 'Date Helper', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.36.1', + categories: [PieceCategory.CORE], + logoUrl: 'https://cdn.activepieces.com/pieces/calendar_piece.svg', + authors: ["joeworkman","kishanprmr","MoShizzle","abuaboud"], + actions: [ + getCurrentDate, + formatDateAction, + extractDateParts, + dateDifferenceAction, + addSubtractDateAction, + nextDayofWeek, + nextDayofYear, + ], + triggers: [], + description: description, +}); diff --git a/packages/pieces/community/date-helper/src/lib/actions/add-subtract-date.ts b/packages/pieces/community/date-helper/src/lib/actions/add-subtract-date.ts new file mode 100644 index 0000000..018baac --- /dev/null +++ b/packages/pieces/community/date-helper/src/lib/actions/add-subtract-date.ts @@ -0,0 +1,136 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import advancedFormat from 'dayjs/plugin/advancedFormat'; +import { + getCorrectedFormat, + optionalTimeFormats, + parseDate, + timeFormat, + timeFormatDescription, + timeParts, +} from '../common'; + +dayjs.extend(advancedFormat); + +export const addSubtractDateAction = createAction({ + name: 'add_subtract_date', + displayName: 'Add/Subtract Time', + description: 'Add or subtract time from a date', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + inputDate: Property.ShortText({ + displayName: 'Input Date', + description: 'Enter the input date', + required: true, + }), + inputDateFormat: Property.StaticDropdown({ + displayName: 'From Time Format', + description: timeFormatDescription, + options: { + options: optionalTimeFormats, + }, + required: true, + defaultValue: getCorrectedFormat(timeFormat.format00), + }), + outputFormat: Property.StaticDropdown({ + displayName: 'To Time Format', + description: timeFormatDescription, + options: { + options: optionalTimeFormats, + }, + required: true, + defaultValue: getCorrectedFormat(timeFormat.format00), + }), + expression: Property.LongText({ + displayName: 'Expression', + description: `Provide an expression to add or subtract using the following units (year , month , day , hour , minute or second). + \nExamples:\n+ 2 second + 1 hour \n+ 1 year - 3 day - 2 month \n+ 5 minute`, + required: true, + }), + }, + async run(context) { + const inputDate = context.propsValue.inputDate; + const inputDateFormat = getCorrectedFormat(context.propsValue.inputDateFormat); + const outputFormat = context.propsValue.outputFormat; + const expression = context.propsValue.expression; + const BeforeDate = parseDate(inputDate, inputDateFormat); + const AfterDate = addSubtractTime(BeforeDate.toDate(), expression); + return { result: dayjs(AfterDate).format(outputFormat) }; + }, +}); + +function addSubtractTime(date: Date, expression: string) { + // remove all the spaces and line breaks from the expression + expression = expression.replace(/(\r\n|\n|\r)/gm, '').replace(/ /g, ''); + const parts = expression.split(/(\+|-)/); + let sign = 1; + const numbers = []; + const units = []; + + for (let i = 0; i < parts.length; i++) { + if (parts[i] === '+') sign = 1; + else if (parts[i] === '-') sign = -1; + else if (parts[i] === '') continue; + let number = ''; + let unit = ''; + for (let j = 0; j < parts[i].length; j++) { + if (parts[i][j] === ' ') continue; + if (parts[i][j] >= '0' && parts[i][j] <= '9') { + if (unit !== '') { + numbers.push(sign * parseInt(number)); + units.push(unit); + number = ''; + unit = ''; + } + number += parts[i][j]; + } else { + if (number === '') continue; + unit += parts[i][j]; + } + } + if (unit !== '') { + numbers.push(sign * parseInt(number)); + units.push(unit); + } + } + let dayjsDate = dayjs(date); + for (let i = 0; i < numbers.length; i++) { + const val = units[i].toLowerCase() as timeParts; + switch (val) { + case timeParts.year: + dayjsDate = dayjsDate.add(numbers[i], 'year'); + break; + case timeParts.month: + dayjsDate = dayjsDate.add(numbers[i], 'month'); + break; + case timeParts.day: + dayjsDate = dayjsDate.add(numbers[i], 'day'); + break; + case timeParts.hour: + dayjsDate = dayjsDate.add(numbers[i], 'hour'); + break; + case timeParts.minute: + dayjsDate = dayjsDate.add(numbers[i], 'minute'); + break; + case timeParts.second: + dayjsDate = dayjsDate.add(numbers[i], 'second'); + break; + case timeParts.dayOfWeek: + case timeParts.monthName: + case timeParts.unix_time: + break; + default: { + const nvr: never = val; + console.error(nvr, 'unhandled case was reached'); + } + } + } + return dayjsDate.toDate(); +} diff --git a/packages/pieces/community/date-helper/src/lib/actions/date-difference.ts b/packages/pieces/community/date-helper/src/lib/actions/date-difference.ts new file mode 100644 index 0000000..cbf398d --- /dev/null +++ b/packages/pieces/community/date-helper/src/lib/actions/date-difference.ts @@ -0,0 +1,116 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import duration from 'dayjs/plugin/duration'; +import advancedFormat from 'dayjs/plugin/advancedFormat'; +import { + optionalTimeFormats, + timeFormat, + timeParts, + timeFormatDescription, + parseDate, + getCorrectedFormat, +} from '../common'; + +dayjs.extend(duration); +dayjs.extend(advancedFormat); + +export const dateDifferenceAction = createAction({ + name: 'date_difference', + displayName: 'Date Difference', + description: 'Get the difference between two dates', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + startDate: Property.ShortText({ + displayName: 'Starting Date', + description: 'Enter the starting date', + required: true, + }), + startDateFormat: Property.StaticDropdown({ + displayName: 'Starting date format', + description: timeFormatDescription, + options: { + options: optionalTimeFormats, + }, + required: true, + defaultValue: timeFormat.format00, + }), + endDate: Property.ShortText({ + displayName: 'Ending Date', + description: 'Enter the ending date', + required: true, + }), + endDateFormat: Property.StaticDropdown({ + displayName: 'Ending date format', + description: timeFormatDescription, + options: { + options: optionalTimeFormats, + }, + required: true, + defaultValue: timeFormat.format00, + }), + unitDifference: Property.StaticMultiSelectDropdown({ + displayName: 'Unit', + description: 'Select the unit of difference between the two dates', + options: { + options: [ + { label: 'Year', value: timeParts.year }, + { label: 'Month', value: timeParts.month }, + { label: 'Day', value: timeParts.day }, + { label: 'Hour', value: timeParts.hour }, + { label: 'Minute', value: timeParts.minute }, + { label: 'Second', value: timeParts.second }, + ], + }, + required: true, + defaultValue: [timeParts.year], + }), + }, + async run(context) { + const inputStartDate = context.propsValue.startDate; + const startDateFormat = getCorrectedFormat(context.propsValue.startDateFormat); + const inputEndDate = context.propsValue.endDate; + const endDateFormat = getCorrectedFormat(context.propsValue.endDateFormat); + const startDate = parseDate(inputStartDate, startDateFormat); + const endDate = parseDate(inputEndDate, endDateFormat); + + const unitDifference = context.propsValue.unitDifference; + const difference = dayjs.duration(endDate.diff(startDate)); + + const outputresponse: Record = {}; + for (let i = 0; i < unitDifference.length; i++) { + switch (unitDifference[i]) { + case timeParts.year: + outputresponse[timeParts.year] = difference.years(); + break; + case timeParts.month: + outputresponse[timeParts.month] = difference.months(); + break; + case timeParts.day: + outputresponse[timeParts.day] = difference.days(); + break; + case timeParts.hour: + outputresponse[timeParts.hour] = difference.hours(); + break; + case timeParts.minute: + outputresponse[timeParts.minute] = difference.minutes(); + break; + case timeParts.second: + outputresponse[timeParts.second] = difference.seconds(); + break; + default: + throw new Error( + `Invalid unit :\n${JSON.stringify(unitDifference[i])}` + ); + } + } + + return outputresponse; + }, +}); diff --git a/packages/pieces/community/date-helper/src/lib/actions/extract-date-parts.ts b/packages/pieces/community/date-helper/src/lib/actions/extract-date-parts.ts new file mode 100644 index 0000000..e8bae85 --- /dev/null +++ b/packages/pieces/community/date-helper/src/lib/actions/extract-date-parts.ts @@ -0,0 +1,101 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { + optionalTimeFormats, + timeFormat, + timeParts, + timeFormatDescription, + parseDate, + getCorrectedFormat, +} from '../common'; + +export const extractDateParts = createAction({ + name: 'extract_date_parts', + displayName: 'Extract Date Units', + description: + 'Extract date units ( year , month , day , hour , minute , second , day of week , month name ) from a date', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + inputDate: Property.ShortText({ + displayName: 'Input Date', + description: 'Enter the input date', + required: true, + }), + inputFormat: Property.StaticDropdown({ + displayName: 'From Time Format', + description: timeFormatDescription, + options: { + options: optionalTimeFormats, + }, + required: true, + defaultValue: timeFormat.format00, + }), + unitExtract: Property.StaticMultiSelectDropdown({ + displayName: 'Unit to Extract', + description: 'Select the unit to extract from the date', + options: { + options: [ + { label: 'Year', value: timeParts.year }, + { label: 'Month', value: timeParts.month }, + { label: 'Day', value: timeParts.day }, + { label: 'Hour', value: timeParts.hour }, + { label: 'Minute', value: timeParts.minute }, + { label: 'Second', value: timeParts.second }, + { label: 'Day of Week', value: timeParts.dayOfWeek }, + { label: 'Month name', value: timeParts.monthName }, + ], + }, + required: true, + defaultValue: [timeParts.year], + }), + }, + async run(context) { + const inputDate = context.propsValue.inputDate; + const inputFormat = getCorrectedFormat(context.propsValue.inputFormat); + const unitExtract = context.propsValue.unitExtract; + + const BeforeDate = parseDate(inputDate, inputFormat); + const outputresponse: Record = {}; + + for (let i = 0; i < unitExtract.length; i++) { + switch (unitExtract[i]) { + case timeParts.year: + outputresponse[timeParts.year] = BeforeDate.year(); + break; + case timeParts.month: + outputresponse[timeParts.month] = BeforeDate.month() + 1; // dayjs months are 0-indexed + break; + case timeParts.day: + outputresponse[timeParts.day] = BeforeDate.date(); + break; + case timeParts.hour: + outputresponse[timeParts.hour] = BeforeDate.hour(); + break; + case timeParts.minute: + outputresponse[timeParts.minute] = BeforeDate.minute(); + break; + case timeParts.second: + outputresponse[timeParts.second] = BeforeDate.second(); + break; + case timeParts.dayOfWeek: + outputresponse[timeParts.dayOfWeek] = BeforeDate.format('dddd'); + break; + case timeParts.monthName: + outputresponse[timeParts.monthName] = BeforeDate.format('MMMM'); + break; + case timeParts.unix_time: + default: + throw new Error( + `Invalid unit to extract :\n${JSON.stringify(unitExtract[i])}` + ); + } + } + return outputresponse; + }, +}); diff --git a/packages/pieces/community/date-helper/src/lib/actions/format-date.ts b/packages/pieces/community/date-helper/src/lib/actions/format-date.ts new file mode 100644 index 0000000..5b94542 --- /dev/null +++ b/packages/pieces/community/date-helper/src/lib/actions/format-date.ts @@ -0,0 +1,100 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { + optionalTimeFormats, + timeFormat, + timeFormatDescription, + timeZoneOptions, + getCorrectedFormat, +} from '../common'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import timezone from 'dayjs/plugin/timezone'; +import advancedFormat from 'dayjs/plugin/advancedFormat'; + +dayjs.extend(utc); +dayjs.extend(timezone); +dayjs.extend(advancedFormat); + +export const formatDateAction = createAction({ + name: 'format_date', + displayName: 'Format Date', + description: 'Converts a date from one format to another', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + inputDate: Property.ShortText({ + displayName: 'Input Date', + description: 'Enter the input date', + required: true, + }), + inputFormat: Property.StaticDropdown({ + displayName: 'From Time Format', + description: timeFormatDescription, + options: { + options: optionalTimeFormats, + }, + required: true, + defaultValue: timeFormat.format00, + }), + inputTimeZone: Property.StaticDropdown({ + displayName: 'From Time Zone', + options: { + options: timeZoneOptions, + }, + required: true, + defaultValue: 'UTC', + }), + outputFormat: Property.StaticDropdown({ + displayName: 'To Time Format', + description: timeFormatDescription, + options: { + options: optionalTimeFormats, + }, + required: true, + defaultValue: timeFormat.format00, + }), + outputTimeZone: Property.StaticDropdown({ + displayName: 'To Time Zone', + options: { + options: timeZoneOptions, + }, + required: true, + defaultValue: 'UTC', + }), + }, + async run(context) { + const inputDate = context.propsValue.inputDate; + const inputFormat = getCorrectedFormat(context.propsValue.inputFormat); + const inputTimeZone = context.propsValue.inputTimeZone as string; + const outputFormat = getCorrectedFormat(context.propsValue.outputFormat); + const outputTimeZone = context.propsValue.outputTimeZone as string; + + return { + result: changeDateFormat( + inputDate, + inputFormat, + inputTimeZone, + outputFormat, + outputTimeZone + ), + }; + }, +}); + + + +function changeDateFormat( + inputDate: string, + inputFormat: string, + inputTimeZone: string, + outputFormat: string, + outputTimeZone: string +): string { + return dayjs.tz(inputDate, inputFormat, inputTimeZone).tz(outputTimeZone).format(outputFormat); +} \ No newline at end of file diff --git a/packages/pieces/community/date-helper/src/lib/actions/get-current-date.ts b/packages/pieces/community/date-helper/src/lib/actions/get-current-date.ts new file mode 100644 index 0000000..15e9c21 --- /dev/null +++ b/packages/pieces/community/date-helper/src/lib/actions/get-current-date.ts @@ -0,0 +1,54 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import timezone from 'dayjs/plugin/timezone'; +import advancedFormat from 'dayjs/plugin/advancedFormat'; +import { + optionalTimeFormats, + timeFormat, + timeFormatDescription, + timeZoneOptions, + getCorrectedFormat +} from '../common'; + +dayjs.extend(utc); +dayjs.extend(timezone); +dayjs.extend(advancedFormat); + +export const getCurrentDate = createAction({ + name: 'get_current_date', + displayName: 'Get Current Date', + description: 'Get the current date', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + timeFormat: Property.StaticDropdown({ + displayName: 'To Time Format', + description: timeFormatDescription, + options: { + options: optionalTimeFormats, + }, + required: true, + defaultValue: timeFormat.format00, + }), + timeZone: Property.StaticDropdown({ + displayName: 'Time Zone', + options: { + options: timeZoneOptions, + }, + required: true, + defaultValue: 'UTC', + }), + }, + async run(context) { + const timeFormat = getCorrectedFormat(context.propsValue.timeFormat); + const timeZone = context.propsValue.timeZone; + return { result: dayjs().tz(timeZone).format(timeFormat) }; + }, +}); diff --git a/packages/pieces/community/date-helper/src/lib/actions/next-day-of-week.ts b/packages/pieces/community/date-helper/src/lib/actions/next-day-of-week.ts new file mode 100644 index 0000000..1733894 --- /dev/null +++ b/packages/pieces/community/date-helper/src/lib/actions/next-day-of-week.ts @@ -0,0 +1,134 @@ +import { + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { + optionalTimeFormats, + timeFormat, + timeFormatDescription, + timeZoneOptions, + getCorrectedFormat, +} from '../common'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import timezone from 'dayjs/plugin/timezone'; +import advancedFormat from 'dayjs/plugin/advancedFormat'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +dayjs.extend(utc); +dayjs.extend(timezone); +dayjs.extend(advancedFormat); + +export const nextDayofWeek = createAction({ + name: 'next_day_of_week', + displayName: 'Next Day of Week', + description: 'Get the date and time of the next day of the week', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + weekday: Property.StaticDropdown({ + displayName: 'Weekday', + description: + 'The weekday that you would like to get the date and time of.', + options: { + options: [ + { label: 'Sunday', value: 0 }, + { label: 'Monday', value: 1 }, + { label: 'Tuesday', value: 2 }, + { label: 'Wednesday', value: 3 }, + { label: 'Thursday', value: 4 }, + { label: 'Friday', value: 5 }, + { label: 'Saturday', value: 6 }, + ], + }, + required: true, + }), + time: Property.ShortText({ + displayName: '24h Time', + description: + 'The time that you would like to get the date and time of. This must be in 24h format.', + required: false, + defaultValue: '00:00', + }), + currentTime: Property.Checkbox({ + displayName: 'Use Current Time', + description: + 'If checked, the current time will be used instead of the time specified above.', + required: false, + defaultValue: false, + }), + timeFormat: Property.StaticDropdown({ + displayName: 'To Time Format', + description: timeFormatDescription, + options: { + options: optionalTimeFormats, + }, + required: true, + defaultValue: timeFormat.format00, + }), + timeZone: Property.StaticDropdown({ + displayName: 'Time Zone', + options: { + options: timeZoneOptions, + }, + required: true, + defaultValue: 'UTC', + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + time: z.string().regex(/^\d\d:\d\d$/), + }); + + const timeFormat = getCorrectedFormat(context.propsValue.timeFormat); + const timeZone = context.propsValue.timeZone as string; + const dayIndex = context.propsValue.weekday as number; + const currentTime = context.propsValue.currentTime as boolean; + let time = context.propsValue.time as string; + + let nextOccurrence = dayjs().tz(timeZone); + + if (currentTime === true) { + time = `${nextOccurrence.hour()}:${nextOccurrence.minute()}`; + } + const [hours, minutes] = time.split(':').map(Number); + + // Validate inputs + if ( + dayIndex < 0 || + dayIndex > 6 || + hours < 0 || + hours > 23 || + minutes < 0 || + minutes > 59 + ) { + throw new Error( + `Invalid input \ndayIndex: ${dayIndex} \nhours: ${hours} \nminutes: ${minutes}` + ); + } + + // Set the time + nextOccurrence = nextOccurrence.hour(hours).minute(minutes).second(0).millisecond(0); + + // Calculate the day difference + let dayDiff = dayIndex - nextOccurrence.day(); + if ( + dayDiff < 0 || + (dayDiff === 0 && nextOccurrence.isBefore(dayjs().tz(timeZone))) + ) { + // If it's a past day in the week or today but past time, move to next week + dayDiff += 7; + } + // Set the date to the next occurrence of the given day + nextOccurrence = nextOccurrence.add(dayDiff, 'day'); + + return { result: nextOccurrence.format(timeFormat) }; + }, +}); diff --git a/packages/pieces/community/date-helper/src/lib/actions/next-day-of-year.ts b/packages/pieces/community/date-helper/src/lib/actions/next-day-of-year.ts new file mode 100644 index 0000000..9561154 --- /dev/null +++ b/packages/pieces/community/date-helper/src/lib/actions/next-day-of-year.ts @@ -0,0 +1,134 @@ +import { + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { + optionalTimeFormats, + timeFormat, + timeFormatDescription, + timeZoneOptions, + getCorrectedFormat, +} from '../common'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import timezone from 'dayjs/plugin/timezone'; +import advancedFormat from 'dayjs/plugin/advancedFormat'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +dayjs.extend(utc); +dayjs.extend(timezone); +dayjs.extend(advancedFormat); + +export const nextDayofYear = createAction({ + name: 'next_day_of_year', + displayName: 'Next Day of Year', + description: 'Get the date and time of the next day of the year', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + month: Property.StaticDropdown({ + displayName: 'Month', + description: 'The month that you would like to get the date and time of.', + options: { + options: [ + { label: 'January', value: 1 }, + { label: 'February', value: 2 }, + { label: 'March', value: 3 }, + { label: 'April', value: 4 }, + { label: 'May', value: 5 }, + { label: 'June', value: 6 }, + { label: 'July', value: 7 }, + { label: 'August', value: 8 }, + { label: 'September', value: 9 }, + { label: 'October', value: 10 }, + { label: 'November', value: 11 }, + { label: 'December', value: 12 }, + ], + }, + required: true, + }), + day: Property.Number({ + displayName: 'Day of Month', + description: + 'The day of the month that you would like to get the date and time of.', + required: true, + defaultValue: 1, + }), + time: Property.ShortText({ + displayName: '24h Time', + description: + 'The time that you would like to get the date and time of. This must be in 24h format.', + required: false, + defaultValue: '00:00', + }), + currentTime: Property.Checkbox({ + displayName: 'Use Current Time', + description: + 'If checked, the current time will be used instead of the time specified above.', + required: false, + defaultValue: false, + }), + timeFormat: Property.StaticDropdown({ + displayName: 'To Time Format', + description: timeFormatDescription, + options: { + options: optionalTimeFormats, + }, + required: true, + defaultValue: timeFormat.format00, + }), + timeZone: Property.StaticDropdown({ + displayName: 'Time Zone', + options: { + options: timeZoneOptions, + }, + required: true, + defaultValue: 'UTC', + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + day: z.number().min(1).max(31), + time: z.string().regex(/^\d\d:\d\d$/), + }); + + const timeFormat = getCorrectedFormat(context.propsValue.timeFormat); + const timeZone = context.propsValue.timeZone as string; + const currentTime = context.propsValue.currentTime as boolean; + const month = context.propsValue.month as number; + const day = context.propsValue.day as number; + let time = context.propsValue.time as string; + + let nextOccurrence = dayjs().tz(timeZone); + + if (currentTime === true) { + time = `${nextOccurrence.hour()}:${nextOccurrence.minute()}`; + } + const [hours, minutes] = time.split(':').map(Number); + + // Validate inputs + if (month < 1 || month > 12 || day < 1 || day > 31) { + throw new Error(`Invalid input \nmonth: ${month} \nday: ${day}`); + } + + const currentYear = nextOccurrence.year(); + + // Create a date object for the next occurrence + nextOccurrence = dayjs.tz(`${currentYear}-${month}-${day} ${hours}:${minutes}`, 'YYYY-M-D H:m', timeZone); + + // Check if the next occurrence is already past in the current year + if (nextOccurrence.isBefore(dayjs().tz(timeZone))) { + // Move to the next year + nextOccurrence = nextOccurrence.add(1, 'year'); + } + + return { result: nextOccurrence.format(timeFormat) }; + }, +}); diff --git a/packages/pieces/community/date-helper/src/lib/common/index.ts b/packages/pieces/community/date-helper/src/lib/common/index.ts new file mode 100644 index 0000000..6e2f23b --- /dev/null +++ b/packages/pieces/community/date-helper/src/lib/common/index.ts @@ -0,0 +1,1786 @@ +import dayjs from 'dayjs'; +import customParseFormat from "dayjs/plugin/customParseFormat"; + +dayjs.extend(customParseFormat); + +export interface dateInformation { + year: number; + month: number; + day: number; + hour: number; + minute: number; + second: number; + unix_time: number; +} + +export enum timeFormat { + format00 = 'DDD MMM DD YYYY HH:mm:ss', + format01 = 'DDD MMM DD HH:mm:ss YYYY', + format02 = 'MMMM DD YYYY HH:mm:ss', + format03 = 'MMMM DD YYYY', + format04 = 'MMM DD YYYY', + format05 = 'YYYY-MM-DDTHH:mm:ss', + format06 = 'YYYY-MM-DD HH:mm:ss', + format07 = 'YYYY-MM-DD', + format08 = 'MM-DD-YYYY', + format09 = 'MM/DD/YYYY', + format10 = 'MM/DD/YY', + format11 = 'DD-MM-YYYY', + format12 = 'DD/MM/YYYY', + format13 = 'DD/MM/YY', + format14 = 'X', +} + +export enum timeFormatLabel { + format00 = 'DDD MMM DD YYYY HH:mm:ss (Sun Sep 17 2023 11:23:58)', + format01 = 'DDD MMM DD HH:mm:ss YYYY (Sun Sep 17 11:23:58 2023)', + format02 = 'MMMM DD YYYY HH:mm:ss (September 17 2023 11:23:58)', + format03 = 'MMMM DD YYYY (September 17 2023)', + format04 = 'MMM DD YYYY (Sep 17 2023)', + format05 = 'YYYY-MM-DDTHH:mm:ss (2023-09-17T11:23:58) ', + format06 = 'YYYY-MM-DD HH:mm:ss (2023-09-17 11:23:58)', + format07 = 'YYYY-MM-DD (2023-09-17)', + format08 = 'MM-DD-YYYY (09-17-2023)', + format09 = 'MM/DD/YYYY (09/17/2023)', + format10 = 'MM/DD/YY (09/17/23)', + format11 = 'DD-MM-YYYY (17-09-2023)', + format12 = 'DD/MM/YYYY (17/09/2023)', + format13 = 'DD/MM/YY (17/09/23)', + format14 = 'X (1694949838)', +} + + + +export enum timeParts { + year = 'year', + month = 'month', + day = 'day', + hour = 'hour', + minute = 'minute', + second = 'second', + unix_time = 'unix_time', + dayOfWeek = 'dayOfWeek', + monthName = 'monthName', +} + +export const getCorrectedFormat = (format:string) =>{ + return format.replaceAll('DDDD','dddd').replaceAll('DDD','ddd'); +} +export function parseDate(date: string, format: string): dayjs.Dayjs { + const correctedFormat = getCorrectedFormat(format); + const djs = dayjs(date, correctedFormat); + if (!djs.isValid()) { + throw new Error(`Failed to parse the date: ${date} with format: ${correctedFormat}`); + } + return djs; +} +export const timeFormatDescription = `Here's what each part of the format (e.g., YYYY) represents: +\nYYYY : Year (4 digits) - Example: 2023 +\nYY : Year (2 digits) - Example: 23 +\nMMMM : Month (full name) - Example: September +\nMMM : Month (short name) - Example: Sep +\nMM : Month (2 digits) - Example: 09 +\nDDDD : Day (full name) - Example: Sunday +\nDDD : Day (short name) - Example: Sun +\nDD : Day (2 digits) - Example: 17 +\nHH : Hour (2 digits) - Example: 11 +\nmm : Minute (2 digits) - Example: 23 +\nss : Second (2 digits) - Example: 58 +\nX : Time in Unix format - Example: 1694949838`; + +export const optionalTimeFormats = [ + { label: timeFormatLabel.format00, value: timeFormat.format00 }, + { label: timeFormatLabel.format01, value: timeFormat.format01 }, + { label: timeFormatLabel.format02, value: timeFormat.format02 }, + { label: timeFormatLabel.format03, value: timeFormat.format03 }, + { label: timeFormatLabel.format04, value: timeFormat.format04 }, + { label: timeFormatLabel.format05, value: timeFormat.format05 }, + { label: timeFormatLabel.format06, value: timeFormat.format06 }, + { label: timeFormatLabel.format07, value: timeFormat.format07 }, + { label: timeFormatLabel.format08, value: timeFormat.format08 }, + { label: timeFormatLabel.format09, value: timeFormat.format09 }, + { label: timeFormatLabel.format10, value: timeFormat.format10 }, + { label: timeFormatLabel.format11, value: timeFormat.format11 }, + { label: timeFormatLabel.format12, value: timeFormat.format12 }, + { label: timeFormatLabel.format13, value: timeFormat.format13 }, + { label: timeFormatLabel.format14, value: timeFormat.format14 }, +]; + +export const timeZoneOptions = [ + { + label: '(GMT-11:00) Pacific, Midway', + value: 'Pacific/Midway', + }, + { + label: '(GMT-11:00) Pacific, Niue', + value: 'Pacific/Niue', + }, + { + label: '(GMT-11:00) Pacific, Pago Pago', + value: 'Pacific/Pago_Pago', + }, + { + label: '(GMT-10:00) Pacific, Honolulu', + value: 'Pacific/Honolulu', + }, + { + label: '(GMT-10:00) Pacific, Rarotonga', + value: 'Pacific/Rarotonga', + }, + { + label: '(GMT-10:00) Pacific, Tahiti', + value: 'Pacific/Tahiti', + }, + { + label: '(GMT-09:30) Pacific, Marquesas', + value: 'Pacific/Marquesas', + }, + { + label: '(GMT-09:00) America, Adak', + value: 'America/Adak', + }, + { + label: '(GMT-09:00) Pacific, Gambier', + value: 'Pacific/Gambier', + }, + { + label: '(GMT-08:00) America, Anchorage', + value: 'America/Anchorage', + }, + { + label: '(GMT-08:00) America, Juneau', + value: 'America/Juneau', + }, + { + label: '(GMT-08:00) America, Metlakatla', + value: 'America/Metlakatla', + }, + { + label: '(GMT-08:00) America, Nome', + value: 'America/Nome', + }, + { + label: '(GMT-08:00) America, Sitka', + value: 'America/Sitka', + }, + { + label: '(GMT-08:00) America, Yakutat', + value: 'America/Yakutat', + }, + { + label: '(GMT-08:00) Pacific, Pitcairn', + value: 'Pacific/Pitcairn', + }, + { + label: '(GMT-07:00) America, Creston', + value: 'America/Creston', + }, + { + label: '(GMT-07:00) America, Dawson', + value: 'America/Dawson', + }, + { + label: '(GMT-07:00) America, Dawson Creek', + value: 'America/Dawson_Creek', + }, + { + label: '(GMT-07:00) America, Fort Nelson', + value: 'America/Fort_Nelson', + }, + { + label: '(GMT-07:00) America, Hermosillo', + value: 'America/Hermosillo', + }, + { + label: '(GMT-07:00) America, Los Angeles', + value: 'America/Los_Angeles', + }, + { + label: '(GMT-07:00) America, Mazatlan', + value: 'America/Mazatlan', + }, + { + label: '(GMT-07:00) America, Phoenix', + value: 'America/Phoenix', + }, + { + label: '(GMT-07:00) America, Tijuana', + value: 'America/Tijuana', + }, + { + label: '(GMT-07:00) America, Vancouver', + value: 'America/Vancouver', + }, + { + label: '(GMT-07:00) America, Whitehorse', + value: 'America/Whitehorse', + }, + { + label: '(GMT-06:00) America, Bahia Banderas', + value: 'America/Bahia_Banderas', + }, + { + label: '(GMT-06:00) America, Belize', + value: 'America/Belize', + }, + { + label: '(GMT-06:00) America, Boise', + value: 'America/Boise', + }, + { + label: '(GMT-06:00) America, Cambridge Bay', + value: 'America/Cambridge_Bay', + }, + { + label: '(GMT-06:00) America, Chihuahua', + value: 'America/Chihuahua', + }, + { + label: '(GMT-06:00) America, Ciudad Juarez', + value: 'America/Ciudad_Juarez', + }, + { + label: '(GMT-06:00) America, Costa Rica', + value: 'America/Costa_Rica', + }, + { + label: '(GMT-06:00) America, Denver', + value: 'America/Denver', + }, + { + label: '(GMT-06:00) America, Edmonton', + value: 'America/Edmonton', + }, + { + label: '(GMT-06:00) America, El Salvador', + value: 'America/El_Salvador', + }, + { + label: '(GMT-06:00) America, Guatemala', + value: 'America/Guatemala', + }, + { + label: '(GMT-06:00) America, Inuvik', + value: 'America/Inuvik', + }, + { + label: '(GMT-06:00) America, Managua', + value: 'America/Managua', + }, + { + label: '(GMT-06:00) America, Merida', + value: 'America/Merida', + }, + { + label: '(GMT-06:00) America, Mexico City', + value: 'America/Mexico_City', + }, + { + label: '(GMT-06:00) America, Monterrey', + value: 'America/Monterrey', + }, + { + label: '(GMT-06:00) America, Regina', + value: 'America/Regina', + }, + { + label: '(GMT-06:00) America, Swift Current', + value: 'America/Swift_Current', + }, + { + label: '(GMT-06:00) America, Tegucigalpa', + value: 'America/Tegucigalpa', + }, + { + label: '(GMT-06:00) Pacific, Easter', + value: 'Pacific/Easter', + }, + { + label: '(GMT-06:00) Pacific, Galapagos', + value: 'Pacific/Galapagos', + }, + { + label: '(GMT-05:00) America, Atikokan', + value: 'America/Atikokan', + }, + { + label: '(GMT-05:00) America, Bogota', + value: 'America/Bogota', + }, + { + label: '(GMT-05:00) America, Cancun', + value: 'America/Cancun', + }, + { + label: '(GMT-05:00) America, Cayman', + value: 'America/Cayman', + }, + { + label: '(GMT-05:00) America, Chicago', + value: 'America/Chicago', + }, + { + label: '(GMT-05:00) America, Eirunepe', + value: 'America/Eirunepe', + }, + { + label: '(GMT-05:00) America, Guayaquil', + value: 'America/Guayaquil', + }, + { + label: '(GMT-05:00) America, Indiana, Knox', + value: 'America/Indiana/Knox', + }, + { + label: '(GMT-05:00) America, Indiana, Tell City', + value: 'America/Indiana/Tell_City', + }, + { + label: '(GMT-05:00) America, Jamaica', + value: 'America/Jamaica', + }, + { + label: '(GMT-05:00) America, Lima', + value: 'America/Lima', + }, + { + label: '(GMT-05:00) America, Matamoros', + value: 'America/Matamoros', + }, + { + label: '(GMT-05:00) America, Menominee', + value: 'America/Menominee', + }, + { + label: '(GMT-05:00) America, North Dakota, Beulah', + value: 'America/North_Dakota/Beulah', + }, + { + label: '(GMT-05:00) America, North Dakota, Center', + value: 'America/North_Dakota/Center', + }, + { + label: '(GMT-05:00) America, North Dakota, New Salem', + value: 'America/North_Dakota/New_Salem', + }, + { + label: '(GMT-05:00) America, Ojinaga', + value: 'America/Ojinaga', + }, + { + label: '(GMT-05:00) America, Panama', + value: 'America/Panama', + }, + { + label: '(GMT-05:00) America, Rankin Inlet', + value: 'America/Rankin_Inlet', + }, + { + label: '(GMT-05:00) America, Resolute', + value: 'America/Resolute', + }, + { + label: '(GMT-05:00) America, Rio Branco', + value: 'America/Rio_Branco', + }, + { + label: '(GMT-05:00) America, Winnipeg', + value: 'America/Winnipeg', + }, + { + label: '(GMT-04:00) America, Anguilla', + value: 'America/Anguilla', + }, + { + label: '(GMT-04:00) America, Antigua', + value: 'America/Antigua', + }, + { + label: '(GMT-04:00) America, Aruba', + value: 'America/Aruba', + }, + { + label: '(GMT-04:00) America, Asuncion', + value: 'America/Asuncion', + }, + { + label: '(GMT-04:00) America, Barbados', + value: 'America/Barbados', + }, + { + label: '(GMT-04:00) America, Blanc-Sablon', + value: 'America/Blanc-Sablon', + }, + { + label: '(GMT-04:00) America, Boa Vista', + value: 'America/Boa_Vista', + }, + { + label: '(GMT-04:00) America, Campo Grande', + value: 'America/Campo_Grande', + }, + { + label: '(GMT-04:00) America, Caracas', + value: 'America/Caracas', + }, + { + label: '(GMT-04:00) America, Cuiaba', + value: 'America/Cuiaba', + }, + { + label: '(GMT-04:00) America, Curacao', + value: 'America/Curacao', + }, + { + label: '(GMT-04:00) America, Detroit', + value: 'America/Detroit', + }, + { + label: '(GMT-04:00) America, Dominica', + value: 'America/Dominica', + }, + { + label: '(GMT-04:00) America, Grand Turk', + value: 'America/Grand_Turk', + }, + { + label: '(GMT-04:00) America, Grenada', + value: 'America/Grenada', + }, + { + label: '(GMT-04:00) America, Guadeloupe', + value: 'America/Guadeloupe', + }, + { + label: '(GMT-04:00) America, Guyana', + value: 'America/Guyana', + }, + { + label: '(GMT-04:00) America, Havana', + value: 'America/Havana', + }, + { + label: '(GMT-04:00) America, Indiana, Indianapolis', + value: 'America/Indiana/Indianapolis', + }, + { + label: '(GMT-04:00) America, Indiana, Marengo', + value: 'America/Indiana/Marengo', + }, + { + label: '(GMT-04:00) America, Indiana, Petersburg', + value: 'America/Indiana/Petersburg', + }, + { + label: '(GMT-04:00) America, Indiana, Vevay', + value: 'America/Indiana/Vevay', + }, + { + label: '(GMT-04:00) America, Indiana, Vincennes', + value: 'America/Indiana/Vincennes', + }, + { + label: '(GMT-04:00) America, Indiana, Winamac', + value: 'America/Indiana/Winamac', + }, + { + label: '(GMT-04:00) America, Iqaluit', + value: 'America/Iqaluit', + }, + { + label: '(GMT-04:00) America, Kentucky, Louisville', + value: 'America/Kentucky/Louisville', + }, + { + label: '(GMT-04:00) America, Kentucky, Monticello', + value: 'America/Kentucky/Monticello', + }, + { + label: '(GMT-04:00) America, Kralendijk', + value: 'America/Kralendijk', + }, + { + label: '(GMT-04:00) America, La Paz', + value: 'America/La_Paz', + }, + { + label: '(GMT-04:00) America, Lower Princes', + value: 'America/Lower_Princes', + }, + { + label: '(GMT-04:00) America, Manaus', + value: 'America/Manaus', + }, + { + label: '(GMT-04:00) America, Marigot', + value: 'America/Marigot', + }, + { + label: '(GMT-04:00) America, Martinique', + value: 'America/Martinique', + }, + { + label: '(GMT-04:00) America, Montserrat', + value: 'America/Montserrat', + }, + { + label: '(GMT-04:00) America, Nassau', + value: 'America/Nassau', + }, + { + label: '(GMT-04:00) America, New York', + value: 'America/New_York', + }, + { + label: '(GMT-04:00) America, Port of Spain', + value: 'America/Port_of_Spain', + }, + { + label: '(GMT-04:00) America, Port-au-Prince', + value: 'America/Port-au-Prince', + }, + { + label: '(GMT-04:00) America, Porto Velho', + value: 'America/Porto_Velho', + }, + { + label: '(GMT-04:00) America, Puerto Rico', + value: 'America/Puerto_Rico', + }, + { + label: '(GMT-04:00) America, Santiago', + value: 'America/Santiago', + }, + { + label: '(GMT-04:00) America, Santo Domingo', + value: 'America/Santo_Domingo', + }, + { + label: '(GMT-04:00) America, St. Barthelemy', + value: 'America/St_Barthelemy', + }, + { + label: '(GMT-04:00) America, St. Kitts', + value: 'America/St_Kitts', + }, + { + label: '(GMT-04:00) America, St. Lucia', + value: 'America/St_Lucia', + }, + { + label: '(GMT-04:00) America, St. Thomas', + value: 'America/St_Thomas', + }, + { + label: '(GMT-04:00) America, St. Vincent', + value: 'America/St_Vincent', + }, + { + label: '(GMT-04:00) America, Toronto', + value: 'America/Toronto', + }, + { + label: '(GMT-04:00) America, Tortola', + value: 'America/Tortola', + }, + { + label: '(GMT-03:00) America, Araguaina', + value: 'America/Araguaina', + }, + { + label: '(GMT-03:00) America, Argentina, Buenos Aires', + value: 'America/Argentina/Buenos_Aires', + }, + { + label: '(GMT-03:00) America, Argentina, Catamarca', + value: 'America/Argentina/Catamarca', + }, + { + label: '(GMT-03:00) America, Argentina, Cordoba', + value: 'America/Argentina/Cordoba', + }, + { + label: '(GMT-03:00) America, Argentina, Jujuy', + value: 'America/Argentina/Jujuy', + }, + { + label: '(GMT-03:00) America, Argentina, La Rioja', + value: 'America/Argentina/La_Rioja', + }, + { + label: '(GMT-03:00) America, Argentina, Mendoza', + value: 'America/Argentina/Mendoza', + }, + { + label: '(GMT-03:00) America, Argentina, Rio Gallegos', + value: 'America/Argentina/Rio_Gallegos', + }, + { + label: '(GMT-03:00) America, Argentina, Salta', + value: 'America/Argentina/Salta', + }, + { + label: '(GMT-03:00) America, Argentina, San Juan', + value: 'America/Argentina/San_Juan', + }, + { + label: '(GMT-03:00) America, Argentina, San Luis', + value: 'America/Argentina/San_Luis', + }, + { + label: '(GMT-03:00) America, Argentina, Tucuman', + value: 'America/Argentina/Tucuman', + }, + { + label: '(GMT-03:00) America, Argentina, Ushuaia', + value: 'America/Argentina/Ushuaia', + }, + { + label: '(GMT-03:00) America, Bahia', + value: 'America/Bahia', + }, + { + label: '(GMT-03:00) America, Belem', + value: 'America/Belem', + }, + { + label: '(GMT-03:00) America, Cayenne', + value: 'America/Cayenne', + }, + { + label: '(GMT-03:00) America, Fortaleza', + value: 'America/Fortaleza', + }, + { + label: '(GMT-03:00) America, Glace Bay', + value: 'America/Glace_Bay', + }, + { + label: '(GMT-03:00) America, Goose Bay', + value: 'America/Goose_Bay', + }, + { + label: '(GMT-03:00) America, Halifax', + value: 'America/Halifax', + }, + { + label: '(GMT-03:00) America, Maceio', + value: 'America/Maceio', + }, + { + label: '(GMT-03:00) America, Moncton', + value: 'America/Moncton', + }, + { + label: '(GMT-03:00) America, Montevideo', + value: 'America/Montevideo', + }, + { + label: '(GMT-03:00) America, Paramaribo', + value: 'America/Paramaribo', + }, + { + label: '(GMT-03:00) America, Punta Arenas', + value: 'America/Punta_Arenas', + }, + { + label: '(GMT-03:00) America, Recife', + value: 'America/Recife', + }, + { + label: '(GMT-03:00) America, Santarem', + value: 'America/Santarem', + }, + { + label: '(GMT-03:00) America, Sao Paulo', + value: 'America/Sao_Paulo', + }, + { + label: '(GMT-03:00) America, Thule', + value: 'America/Thule', + }, + { + label: '(GMT-03:00) Antarctica, Palmer', + value: 'Antarctica/Palmer', + }, + { + label: '(GMT-03:00) Antarctica, Rothera', + value: 'Antarctica/Rothera', + }, + { + label: '(GMT-03:00) Atlantic, Bermuda', + value: 'Atlantic/Bermuda', + }, + { + label: '(GMT-03:00) Atlantic, Stanley', + value: 'Atlantic/Stanley', + }, + { + label: '(GMT-02:30) America, St. Johns', + value: 'America/St_Johns', + }, + { + label: '(GMT-02:00) America, Miquelon', + value: 'America/Miquelon', + }, + { + label: '(GMT-02:00) America, Noronha', + value: 'America/Noronha', + }, + { + label: '(GMT-02:00) America, Nuuk', + value: 'America/Nuuk', + }, + { + label: '(GMT-02:00) Atlantic, South Georgia', + value: 'Atlantic/South_Georgia', + }, + { + label: '(GMT-01:00) Atlantic, Cape Verde', + value: 'Atlantic/Cape_Verde', + }, + { + label: '(GMT) Africa, Abidjan', + value: 'Africa/Abidjan', + }, + { + label: '(GMT) Africa, Accra', + value: 'Africa/Accra', + }, + { + label: '(GMT) Africa, Bamako', + value: 'Africa/Bamako', + }, + { + label: '(GMT) Africa, Banjul', + value: 'Africa/Banjul', + }, + { + label: '(GMT) Africa, Bissau', + value: 'Africa/Bissau', + }, + { + label: '(GMT) Africa, Conakry', + value: 'Africa/Conakry', + }, + { + label: '(GMT) Africa, Dakar', + value: 'Africa/Dakar', + }, + { + label: '(GMT) Africa, Freetown', + value: 'Africa/Freetown', + }, + { + label: '(GMT) Africa, Lome', + value: 'Africa/Lome', + }, + { + label: '(GMT) Africa, Monrovia', + value: 'Africa/Monrovia', + }, + { + label: '(GMT) Africa, Nouakchott', + value: 'Africa/Nouakchott', + }, + { + label: '(GMT) Africa, Ouagadougou', + value: 'Africa/Ouagadougou', + }, + { + label: '(GMT) Africa, Sao Tome', + value: 'Africa/Sao_Tome', + }, + { + label: '(GMT) America, Danmarkshavn', + value: 'America/Danmarkshavn', + }, + { + label: '(GMT) America, Scoresbysund', + value: 'America/Scoresbysund', + }, + { + label: '(GMT) Atlantic, Azores', + value: 'Atlantic/Azores', + }, + { + label: '(GMT) Atlantic, Reykjavik', + value: 'Atlantic/Reykjavik', + }, + { + label: '(GMT) Atlantic, St. Helena', + value: 'Atlantic/St_Helena', + }, + { + label: '(GMT) UTC', + value: 'UTC', + }, + { + label: '(GMT+01:00) Africa, Algiers', + value: 'Africa/Algiers', + }, + { + label: '(GMT+01:00) Africa, Bangui', + value: 'Africa/Bangui', + }, + { + label: '(GMT+01:00) Africa, Brazzaville', + value: 'Africa/Brazzaville', + }, + { + label: '(GMT+01:00) Africa, Casablanca', + value: 'Africa/Casablanca', + }, + { + label: '(GMT+01:00) Africa, Douala', + value: 'Africa/Douala', + }, + { + label: '(GMT+01:00) Africa, El Aaiun', + value: 'Africa/El_Aaiun', + }, + { + label: '(GMT+01:00) Africa, Kinshasa', + value: 'Africa/Kinshasa', + }, + { + label: '(GMT+01:00) Africa, Lagos', + value: 'Africa/Lagos', + }, + { + label: '(GMT+01:00) Africa, Libreville', + value: 'Africa/Libreville', + }, + { + label: '(GMT+01:00) Africa, Luanda', + value: 'Africa/Luanda', + }, + { + label: '(GMT+01:00) Africa, Malabo', + value: 'Africa/Malabo', + }, + { + label: '(GMT+01:00) Africa, Ndjamena', + value: 'Africa/Ndjamena', + }, + { + label: '(GMT+01:00) Africa, Niamey', + value: 'Africa/Niamey', + }, + { + label: '(GMT+01:00) Africa, Porto-Novo', + value: 'Africa/Porto-Novo', + }, + { + label: '(GMT+01:00) Africa, Tunis', + value: 'Africa/Tunis', + }, + { + label: '(GMT+01:00) Atlantic, Canary', + value: 'Atlantic/Canary', + }, + { + label: '(GMT+01:00) Atlantic, Faroe', + value: 'Atlantic/Faroe', + }, + { + label: '(GMT+01:00) Atlantic, Madeira', + value: 'Atlantic/Madeira', + }, + { + label: '(GMT+01:00) Europe, Dublin', + value: 'Europe/Dublin', + }, + { + label: '(GMT+01:00) Europe, Guernsey', + value: 'Europe/Guernsey', + }, + { + label: '(GMT+01:00) Europe, Isle of Man', + value: 'Europe/Isle_of_Man', + }, + { + label: '(GMT+01:00) Europe, Jersey', + value: 'Europe/Jersey', + }, + { + label: '(GMT+01:00) Europe, Lisbon', + value: 'Europe/Lisbon', + }, + { + label: '(GMT+01:00) Europe, London', + value: 'Europe/London', + }, + { + label: '(GMT+02:00) Africa, Blantyre', + value: 'Africa/Blantyre', + }, + { + label: '(GMT+02:00) Africa, Bujumbura', + value: 'Africa/Bujumbura', + }, + { + label: '(GMT+02:00) Africa, Ceuta', + value: 'Africa/Ceuta', + }, + { + label: '(GMT+02:00) Africa, Gaborone', + value: 'Africa/Gaborone', + }, + { + label: '(GMT+02:00) Africa, Harare', + value: 'Africa/Harare', + }, + { + label: '(GMT+02:00) Africa, Johannesburg', + value: 'Africa/Johannesburg', + }, + { + label: '(GMT+02:00) Africa, Juba', + value: 'Africa/Juba', + }, + { + label: '(GMT+02:00) Africa, Khartoum', + value: 'Africa/Khartoum', + }, + { + label: '(GMT+02:00) Africa, Kigali', + value: 'Africa/Kigali', + }, + { + label: '(GMT+02:00) Africa, Lubumbashi', + value: 'Africa/Lubumbashi', + }, + { + label: '(GMT+02:00) Africa, Lusaka', + value: 'Africa/Lusaka', + }, + { + label: '(GMT+02:00) Africa, Maputo', + value: 'Africa/Maputo', + }, + { + label: '(GMT+02:00) Africa, Maseru', + value: 'Africa/Maseru', + }, + { + label: '(GMT+02:00) Africa, Mbabane', + value: 'Africa/Mbabane', + }, + { + label: '(GMT+02:00) Africa, Tripoli', + value: 'Africa/Tripoli', + }, + { + label: '(GMT+02:00) Africa, Windhoek', + value: 'Africa/Windhoek', + }, + { + label: '(GMT+02:00) Antarctica, Troll', + value: 'Antarctica/Troll', + }, + { + label: '(GMT+02:00) Arctic, Longyearbyen', + value: 'Arctic/Longyearbyen', + }, + { + label: '(GMT+02:00) Europe, Amsterdam', + value: 'Europe/Amsterdam', + }, + { + label: '(GMT+02:00) Europe, Andorra', + value: 'Europe/Andorra', + }, + { + label: '(GMT+02:00) Europe, Belgrade', + value: 'Europe/Belgrade', + }, + { + label: '(GMT+02:00) Europe, Berlin', + value: 'Europe/Berlin', + }, + { + label: '(GMT+02:00) Europe, Bratislava', + value: 'Europe/Bratislava', + }, + { + label: '(GMT+02:00) Europe, Brussels', + value: 'Europe/Brussels', + }, + { + label: '(GMT+02:00) Europe, Budapest', + value: 'Europe/Budapest', + }, + { + label: '(GMT+02:00) Europe, Busingen', + value: 'Europe/Busingen', + }, + { + label: '(GMT+02:00) Europe, Copenhagen', + value: 'Europe/Copenhagen', + }, + { + label: '(GMT+02:00) Europe, Gibraltar', + value: 'Europe/Gibraltar', + }, + { + label: '(GMT+02:00) Europe, Kaliningrad', + value: 'Europe/Kaliningrad', + }, + { + label: '(GMT+02:00) Europe, Ljubljana', + value: 'Europe/Ljubljana', + }, + { + label: '(GMT+02:00) Europe, Luxembourg', + value: 'Europe/Luxembourg', + }, + { + label: '(GMT+02:00) Europe, Madrid', + value: 'Europe/Madrid', + }, + { + label: '(GMT+02:00) Europe, Malta', + value: 'Europe/Malta', + }, + { + label: '(GMT+02:00) Europe, Monaco', + value: 'Europe/Monaco', + }, + { + label: '(GMT+02:00) Europe, Oslo', + value: 'Europe/Oslo', + }, + { + label: '(GMT+02:00) Europe, Paris', + value: 'Europe/Paris', + }, + { + label: '(GMT+02:00) Europe, Podgorica', + value: 'Europe/Podgorica', + }, + { + label: '(GMT+02:00) Europe, Prague', + value: 'Europe/Prague', + }, + { + label: '(GMT+02:00) Europe, Rome', + value: 'Europe/Rome', + }, + { + label: '(GMT+02:00) Europe, San Marino', + value: 'Europe/San_Marino', + }, + { + label: '(GMT+02:00) Europe, Sarajevo', + value: 'Europe/Sarajevo', + }, + { + label: '(GMT+02:00) Europe, Skopje', + value: 'Europe/Skopje', + }, + { + label: '(GMT+02:00) Europe, Stockholm', + value: 'Europe/Stockholm', + }, + { + label: '(GMT+02:00) Europe, Tirane', + value: 'Europe/Tirane', + }, + { + label: '(GMT+02:00) Europe, Vaduz', + value: 'Europe/Vaduz', + }, + { + label: '(GMT+02:00) Europe, Vatican', + value: 'Europe/Vatican', + }, + { + label: '(GMT+02:00) Europe, Vienna', + value: 'Europe/Vienna', + }, + { + label: '(GMT+02:00) Europe, Warsaw', + value: 'Europe/Warsaw', + }, + { + label: '(GMT+02:00) Europe, Zagreb', + value: 'Europe/Zagreb', + }, + { + label: '(GMT+02:00) Europe, Zurich', + value: 'Europe/Zurich', + }, + { + label: '(GMT+03:00) Africa, Addis Ababa', + value: 'Africa/Addis_Ababa', + }, + { + label: '(GMT+03:00) Africa, Asmara', + value: 'Africa/Asmara', + }, + { + label: '(GMT+03:00) Africa, Cairo', + value: 'Africa/Cairo', + }, + { + label: '(GMT+03:00) Africa, Dar es Salaam', + value: 'Africa/Dar_es_Salaam', + }, + { + label: '(GMT+03:00) Africa, Djibouti', + value: 'Africa/Djibouti', + }, + { + label: '(GMT+03:00) Africa, Kampala', + value: 'Africa/Kampala', + }, + { + label: '(GMT+03:00) Africa, Mogadishu', + value: 'Africa/Mogadishu', + }, + { + label: '(GMT+03:00) Africa, Nairobi', + value: 'Africa/Nairobi', + }, + { + label: '(GMT+03:00) Antarctica, Syowa', + value: 'Antarctica/Syowa', + }, + { + label: '(GMT+03:00) Asia, Aden', + value: 'Asia/Aden', + }, + { + label: '(GMT+03:00) Asia, Amman', + value: 'Asia/Amman', + }, + { + label: '(GMT+03:00) Asia, Baghdad', + value: 'Asia/Baghdad', + }, + { + label: '(GMT+03:00) Asia, Bahrain', + value: 'Asia/Bahrain', + }, + { + label: '(GMT+03:00) Asia, Beirut', + value: 'Asia/Beirut', + }, + { + label: '(GMT+03:00) Asia, Damascus', + value: 'Asia/Damascus', + }, + { + label: '(GMT+03:00) Asia, Famagusta', + value: 'Asia/Famagusta', + }, + { + label: '(GMT+03:00) Asia, Gaza', + value: 'Asia/Gaza', + }, + { + label: '(GMT+03:00) Asia, Hebron', + value: 'Asia/Hebron', + }, + { + label: '(GMT+03:00) Asia, Jerusalem', + value: 'Asia/Jerusalem', + }, + { + label: '(GMT+03:00) Asia, Kuwait', + value: 'Asia/Kuwait', + }, + { + label: '(GMT+03:00) Asia, Nicosia', + value: 'Asia/Nicosia', + }, + { + label: '(GMT+03:00) Asia, Qatar', + value: 'Asia/Qatar', + }, + { + label: '(GMT+03:00) Asia, Riyadh', + value: 'Asia/Riyadh', + }, + { + label: '(GMT+03:00) Europe, Athens', + value: 'Europe/Athens', + }, + { + label: '(GMT+03:00) Europe, Bucharest', + value: 'Europe/Bucharest', + }, + { + label: '(GMT+03:00) Europe, Chisinau', + value: 'Europe/Chisinau', + }, + { + label: '(GMT+03:00) Europe, Helsinki', + value: 'Europe/Helsinki', + }, + { + label: '(GMT+03:00) Europe, Istanbul', + value: 'Europe/Istanbul', + }, + { + label: '(GMT+03:00) Europe, Kirov', + value: 'Europe/Kirov', + }, + { + label: '(GMT+03:00) Europe, Kyiv', + value: 'Europe/Kyiv', + }, + { + label: '(GMT+03:00) Europe, Mariehamn', + value: 'Europe/Mariehamn', + }, + { + label: '(GMT+03:00) Europe, Minsk', + value: 'Europe/Minsk', + }, + { + label: '(GMT+03:00) Europe, Moscow', + value: 'Europe/Moscow', + }, + { + label: '(GMT+03:00) Europe, Riga', + value: 'Europe/Riga', + }, + { + label: '(GMT+03:00) Europe, Simferopol', + value: 'Europe/Simferopol', + }, + { + label: '(GMT+03:00) Europe, Sofia', + value: 'Europe/Sofia', + }, + { + label: '(GMT+03:00) Europe, Tallinn', + value: 'Europe/Tallinn', + }, + { + label: '(GMT+03:00) Europe, Vilnius', + value: 'Europe/Vilnius', + }, + { + label: '(GMT+03:00) Europe, Volgograd', + value: 'Europe/Volgograd', + }, + { + label: '(GMT+03:00) Indian, Antananarivo', + value: 'Indian/Antananarivo', + }, + { + label: '(GMT+03:00) Indian, Comoro', + value: 'Indian/Comoro', + }, + { + label: '(GMT+03:00) Indian, Mayotte', + value: 'Indian/Mayotte', + }, + { + label: '(GMT+03:30) Asia, Tehran', + value: 'Asia/Tehran', + }, + { + label: '(GMT+04:00) Asia, Baku', + value: 'Asia/Baku', + }, + { + label: '(GMT+04:00) Asia, Dubai', + value: 'Asia/Dubai', + }, + { + label: '(GMT+04:00) Asia, Muscat', + value: 'Asia/Muscat', + }, + { + label: '(GMT+04:00) Asia, Tbilisi', + value: 'Asia/Tbilisi', + }, + { + label: '(GMT+04:00) Asia, Yerevan', + value: 'Asia/Yerevan', + }, + { + label: '(GMT+04:00) Europe, Astrakhan', + value: 'Europe/Astrakhan', + }, + { + label: '(GMT+04:00) Europe, Samara', + value: 'Europe/Samara', + }, + { + label: '(GMT+04:00) Europe, Saratov', + value: 'Europe/Saratov', + }, + { + label: '(GMT+04:00) Europe, Ulyanovsk', + value: 'Europe/Ulyanovsk', + }, + { + label: '(GMT+04:00) Indian, Mahe', + value: 'Indian/Mahe', + }, + { + label: '(GMT+04:00) Indian, Mauritius', + value: 'Indian/Mauritius', + }, + { + label: '(GMT+04:00) Indian, Reunion', + value: 'Indian/Reunion', + }, + { + label: '(GMT+04:30) Asia, Kabul', + value: 'Asia/Kabul', + }, + { + label: '(GMT+05:00) Antarctica, Mawson', + value: 'Antarctica/Mawson', + }, + { + label: '(GMT+05:00) Asia, Aqtau', + value: 'Asia/Aqtau', + }, + { + label: '(GMT+05:00) Asia, Aqtobe', + value: 'Asia/Aqtobe', + }, + { + label: '(GMT+05:00) Asia, Ashgabat', + value: 'Asia/Ashgabat', + }, + { + label: '(GMT+05:00) Asia, Atyrau', + value: 'Asia/Atyrau', + }, + { + label: '(GMT+05:00) Asia, Dushanbe', + value: 'Asia/Dushanbe', + }, + { + label: '(GMT+05:00) Asia, Karachi', + value: 'Asia/Karachi', + }, + { + label: '(GMT+05:00) Asia, Oral', + value: 'Asia/Oral', + }, + { + label: '(GMT+05:00) Asia, Qyzylorda', + value: 'Asia/Qyzylorda', + }, + { + label: '(GMT+05:00) Asia, Samarkand', + value: 'Asia/Samarkand', + }, + { + label: '(GMT+05:00) Asia, Tashkent', + value: 'Asia/Tashkent', + }, + { + label: '(GMT+05:00) Asia, Yekaterinburg', + value: 'Asia/Yekaterinburg', + }, + { + label: '(GMT+05:00) Indian, Kerguelen', + value: 'Indian/Kerguelen', + }, + { + label: '(GMT+05:00) Indian, Maldives', + value: 'Indian/Maldives', + }, + { + label: '(GMT+05:30) Asia, Colombo', + value: 'Asia/Colombo', + }, + { + label: '(GMT+05:30) Asia, Kolkata', + value: 'Asia/Kolkata', + }, + { + label: '(GMT+05:45) Asia, Kathmandu', + value: 'Asia/Kathmandu', + }, + { + label: '(GMT+06:00) Antarctica, Vostok', + value: 'Antarctica/Vostok', + }, + { + label: '(GMT+06:00) Asia, Almaty', + value: 'Asia/Almaty', + }, + { + label: '(GMT+06:00) Asia, Bishkek', + value: 'Asia/Bishkek', + }, + { + label: '(GMT+06:00) Asia, Dhaka', + value: 'Asia/Dhaka', + }, + { + label: '(GMT+06:00) Asia, Omsk', + value: 'Asia/Omsk', + }, + { + label: '(GMT+06:00) Asia, Qostanay', + value: 'Asia/Qostanay', + }, + { + label: '(GMT+06:00) Asia, Thimphu', + value: 'Asia/Thimphu', + }, + { + label: '(GMT+06:00) Asia, Urumqi', + value: 'Asia/Urumqi', + }, + { + label: '(GMT+06:00) Indian, Chagos', + value: 'Indian/Chagos', + }, + { + label: '(GMT+06:30) Asia, Yangon', + value: 'Asia/Yangon', + }, + { + label: '(GMT+06:30) Indian, Cocos', + value: 'Indian/Cocos', + }, + { + label: '(GMT+07:00) Antarctica, Davis', + value: 'Antarctica/Davis', + }, + { + label: '(GMT+07:00) Asia, Bangkok', + value: 'Asia/Bangkok', + }, + { + label: '(GMT+07:00) Asia, Barnaul', + value: 'Asia/Barnaul', + }, + { + label: '(GMT+07:00) Asia, Ho Chi Minh', + value: 'Asia/Ho_Chi_Minh', + }, + { + label: '(GMT+07:00) Asia, Hovd', + value: 'Asia/Hovd', + }, + { + label: '(GMT+07:00) Asia, Jakarta', + value: 'Asia/Jakarta', + }, + { + label: '(GMT+07:00) Asia, Krasnoyarsk', + value: 'Asia/Krasnoyarsk', + }, + { + label: '(GMT+07:00) Asia, Novokuznetsk', + value: 'Asia/Novokuznetsk', + }, + { + label: '(GMT+07:00) Asia, Novosibirsk', + value: 'Asia/Novosibirsk', + }, + { + label: '(GMT+07:00) Asia, Phnom Penh', + value: 'Asia/Phnom_Penh', + }, + { + label: '(GMT+07:00) Asia, Pontianak', + value: 'Asia/Pontianak', + }, + { + label: '(GMT+07:00) Asia, Tomsk', + value: 'Asia/Tomsk', + }, + { + label: '(GMT+07:00) Asia, Vientiane', + value: 'Asia/Vientiane', + }, + { + label: '(GMT+07:00) Indian, Christmas', + value: 'Indian/Christmas', + }, + { + label: '(GMT+08:00) Asia, Brunei', + value: 'Asia/Brunei', + }, + { + label: '(GMT+08:00) Asia, Choibalsan', + value: 'Asia/Choibalsan', + }, + { + label: '(GMT+08:00) Asia, Hong Kong', + value: 'Asia/Hong_Kong', + }, + { + label: '(GMT+08:00) Asia, Irkutsk', + value: 'Asia/Irkutsk', + }, + { + label: '(GMT+08:00) Asia, Kuala Lumpur', + value: 'Asia/Kuala_Lumpur', + }, + { + label: '(GMT+08:00) Asia, Kuching', + value: 'Asia/Kuching', + }, + { + label: '(GMT+08:00) Asia, Macau', + value: 'Asia/Macau', + }, + { + label: '(GMT+08:00) Asia, Makassar', + value: 'Asia/Makassar', + }, + { + label: '(GMT+08:00) Asia, Manila', + value: 'Asia/Manila', + }, + { + label: '(GMT+08:00) Asia, Shanghai', + value: 'Asia/Shanghai', + }, + { + label: '(GMT+08:00) Asia, Singapore', + value: 'Asia/Singapore', + }, + { + label: '(GMT+08:00) Asia, Taipei', + value: 'Asia/Taipei', + }, + { + label: '(GMT+08:00) Asia, Ulaanbaatar', + value: 'Asia/Ulaanbaatar', + }, + { + label: '(GMT+08:00) Australia, Perth', + value: 'Australia/Perth', + }, + { + label: '(GMT+08:45) Australia, Eucla', + value: 'Australia/Eucla', + }, + { + label: '(GMT+09:00) Asia, Chita', + value: 'Asia/Chita', + }, + { + label: '(GMT+09:00) Asia, Dili', + value: 'Asia/Dili', + }, + { + label: '(GMT+09:00) Asia, Jayapura', + value: 'Asia/Jayapura', + }, + { + label: '(GMT+09:00) Asia, Khandyga', + value: 'Asia/Khandyga', + }, + { + label: '(GMT+09:00) Asia, Pyongyang', + value: 'Asia/Pyongyang', + }, + { + label: '(GMT+09:00) Asia, Seoul', + value: 'Asia/Seoul', + }, + { + label: '(GMT+09:00) Asia, Tokyo', + value: 'Asia/Tokyo', + }, + { + label: '(GMT+09:00) Asia, Yakutsk', + value: 'Asia/Yakutsk', + }, + { + label: '(GMT+09:00) Pacific, Palau', + value: 'Pacific/Palau', + }, + { + label: '(GMT+09:30) Australia, Adelaide', + value: 'Australia/Adelaide', + }, + { + label: '(GMT+09:30) Australia, Broken Hill', + value: 'Australia/Broken_Hill', + }, + { + label: '(GMT+09:30) Australia, Darwin', + value: 'Australia/Darwin', + }, + { + label: '(GMT+10:00) Antarctica, DumontDUrville', + value: 'Antarctica/DumontDUrville', + }, + { + label: '(GMT+10:00) Antarctica, Macquarie', + value: 'Antarctica/Macquarie', + }, + { + label: '(GMT+10:00) Asia, Ust-Nera', + value: 'Asia/Ust-Nera', + }, + { + label: '(GMT+10:00) Asia, Vladivostok', + value: 'Asia/Vladivostok', + }, + { + label: '(GMT+10:00) Australia, Brisbane', + value: 'Australia/Brisbane', + }, + { + label: '(GMT+10:00) Australia, Hobart', + value: 'Australia/Hobart', + }, + { + label: '(GMT+10:00) Australia, Lindeman', + value: 'Australia/Lindeman', + }, + { + label: '(GMT+10:00) Australia, Melbourne', + value: 'Australia/Melbourne', + }, + { + label: '(GMT+10:00) Australia, Sydney', + value: 'Australia/Sydney', + }, + { + label: '(GMT+10:00) Pacific, Chuuk', + value: 'Pacific/Chuuk', + }, + { + label: '(GMT+10:00) Pacific, Guam', + value: 'Pacific/Guam', + }, + { + label: '(GMT+10:00) Pacific, Port Moresby', + value: 'Pacific/Port_Moresby', + }, + { + label: '(GMT+10:00) Pacific, Saipan', + value: 'Pacific/Saipan', + }, + { + label: '(GMT+10:30) Australia, Lord Howe', + value: 'Australia/Lord_Howe', + }, + { + label: '(GMT+11:00) Antarctica, Casey', + value: 'Antarctica/Casey', + }, + { + label: '(GMT+11:00) Asia, Magadan', + value: 'Asia/Magadan', + }, + { + label: '(GMT+11:00) Asia, Sakhalin', + value: 'Asia/Sakhalin', + }, + { + label: '(GMT+11:00) Asia, Srednekolymsk', + value: 'Asia/Srednekolymsk', + }, + { + label: '(GMT+11:00) Pacific, Bougainville', + value: 'Pacific/Bougainville', + }, + { + label: '(GMT+11:00) Pacific, Efate', + value: 'Pacific/Efate', + }, + { + label: '(GMT+11:00) Pacific, Guadalcanal', + value: 'Pacific/Guadalcanal', + }, + { + label: '(GMT+11:00) Pacific, Kosrae', + value: 'Pacific/Kosrae', + }, + { + label: '(GMT+11:00) Pacific, Norfolk', + value: 'Pacific/Norfolk', + }, + { + label: '(GMT+11:00) Pacific, Noumea', + value: 'Pacific/Noumea', + }, + { + label: '(GMT+11:00) Pacific, Pohnpei', + value: 'Pacific/Pohnpei', + }, + { + label: '(GMT+12:00) Antarctica, McMurdo', + value: 'Antarctica/McMurdo', + }, + { + label: '(GMT+12:00) Asia, Anadyr', + value: 'Asia/Anadyr', + }, + { + label: '(GMT+12:00) Asia, Kamchatka', + value: 'Asia/Kamchatka', + }, + { + label: '(GMT+12:00) Pacific, Auckland', + value: 'Pacific/Auckland', + }, + { + label: '(GMT+12:00) Pacific, Fiji', + value: 'Pacific/Fiji', + }, + { + label: '(GMT+12:00) Pacific, Funafuti', + value: 'Pacific/Funafuti', + }, + { + label: '(GMT+12:00) Pacific, Kwajalein', + value: 'Pacific/Kwajalein', + }, + { + label: '(GMT+12:00) Pacific, Majuro', + value: 'Pacific/Majuro', + }, + { + label: '(GMT+12:00) Pacific, Nauru', + value: 'Pacific/Nauru', + }, + { + label: '(GMT+12:00) Pacific, Tarawa', + value: 'Pacific/Tarawa', + }, + { + label: '(GMT+12:00) Pacific, Wake', + value: 'Pacific/Wake', + }, + { + label: '(GMT+12:00) Pacific, Wallis', + value: 'Pacific/Wallis', + }, + { + label: '(GMT+12:45) Pacific, Chatham', + value: 'Pacific/Chatham', + }, + { + label: '(GMT+13:00) Pacific, Apia', + value: 'Pacific/Apia', + }, + { + label: '(GMT+13:00) Pacific, Fakaofo', + value: 'Pacific/Fakaofo', + }, + { + label: '(GMT+13:00) Pacific, Kanton', + value: 'Pacific/Kanton', + }, + { + label: '(GMT+13:00) Pacific, Tongatapu', + value: 'Pacific/Tongatapu', + }, + { + label: '(GMT+14:00) Pacific, Kiritimati', + value: 'Pacific/Kiritimati', + }, +]; diff --git a/packages/pieces/community/date-helper/tsconfig.json b/packages/pieces/community/date-helper/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/date-helper/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/date-helper/tsconfig.lib.json b/packages/pieces/community/date-helper/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/date-helper/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/datocms/.eslintrc.json b/packages/pieces/community/datocms/.eslintrc.json new file mode 100644 index 0000000..d7449bb --- /dev/null +++ b/packages/pieces/community/datocms/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {}, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/datocms/README.md b/packages/pieces/community/datocms/README.md new file mode 100644 index 0000000..7bfa21a --- /dev/null +++ b/packages/pieces/community/datocms/README.md @@ -0,0 +1,7 @@ +# pieces-datocms + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-datocms` to build the library. diff --git a/packages/pieces/community/datocms/package.json b/packages/pieces/community/datocms/package.json new file mode 100644 index 0000000..0d7cb05 --- /dev/null +++ b/packages/pieces/community/datocms/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-datocms", + "version": "0.0.1" +} diff --git a/packages/pieces/community/datocms/project.json b/packages/pieces/community/datocms/project.json new file mode 100644 index 0000000..f2e10f3 --- /dev/null +++ b/packages/pieces/community/datocms/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-datocms", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/datocms/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/datocms", + "tsConfig": "packages/pieces/community/datocms/tsconfig.lib.json", + "packageJson": "packages/pieces/community/datocms/package.json", + "main": "packages/pieces/community/datocms/src/index.ts", + "assets": [ + "packages/pieces/community/datocms/*.md", + { + "input": "packages/pieces/community/datocms/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/datocms/src/index.ts b/packages/pieces/community/datocms/src/index.ts new file mode 100644 index 0000000..44c5979 --- /dev/null +++ b/packages/pieces/community/datocms/src/index.ts @@ -0,0 +1,60 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { + createCustomApiCallAction, + HttpHeaders, +} from '@activepieces/pieces-common'; + +export const DATO_BASE_URL = 'https://site-api.datocms.com'; + +export const datoAuth = PieceAuth.CustomAuth({ + required: true, + props: { + apiKey: PieceAuth.SecretText({ + displayName: 'API key', + required: true, + }), + environment: Property.ShortText({ + displayName: 'Environment', + description: 'Optional sandbox environment', + required: false, + }), + }, +}); + +type DatoAuthType = { + apiKey: string; + environment?: string; +}; + +export const datocms = createPiece({ + displayName: 'Dato CMS', + description: 'Dato is a modern headless CMS', + auth: datoAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/datocms.png', + authors: ['AdamSelene'], + actions: [ + createCustomApiCallAction({ + auth: datoAuth, + baseUrl: () => DATO_BASE_URL, + authMapping: async (auth) => { + const { apiKey, environment } = auth as DatoAuthType; + const headers: HttpHeaders = { + Accept: 'application/json', + 'Content-Type': 'application/vnd.api+json', + 'X-Api-Version': '3', + Authorization: `Bearer ${apiKey}`, + }; + if (environment) { + headers['X-Environment'] = environment; + } + return headers; + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/datocms/tsconfig.json b/packages/pieces/community/datocms/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/datocms/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/datocms/tsconfig.lib.json b/packages/pieces/community/datocms/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/datocms/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/deepgram/.eslintrc.json b/packages/pieces/community/deepgram/.eslintrc.json new file mode 100644 index 0000000..d7449bb --- /dev/null +++ b/packages/pieces/community/deepgram/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {}, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/deepgram/README.md b/packages/pieces/community/deepgram/README.md new file mode 100644 index 0000000..8cbc003 --- /dev/null +++ b/packages/pieces/community/deepgram/README.md @@ -0,0 +1,7 @@ +# pieces-deepgram + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-deepgram` to build the library. diff --git a/packages/pieces/community/deepgram/package.json b/packages/pieces/community/deepgram/package.json new file mode 100644 index 0000000..03bc2bb --- /dev/null +++ b/packages/pieces/community/deepgram/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-deepgram", + "version": "0.0.1" +} diff --git a/packages/pieces/community/deepgram/project.json b/packages/pieces/community/deepgram/project.json new file mode 100644 index 0000000..040db70 --- /dev/null +++ b/packages/pieces/community/deepgram/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-deepgram", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/deepgram/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/deepgram", + "tsConfig": "packages/pieces/community/deepgram/tsconfig.lib.json", + "packageJson": "packages/pieces/community/deepgram/package.json", + "main": "packages/pieces/community/deepgram/src/index.ts", + "assets": [ + "packages/pieces/community/deepgram/*.md", + { + "input": "packages/pieces/community/deepgram/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/deepgram/src/actions/create-summary.ts b/packages/pieces/community/deepgram/src/actions/create-summary.ts new file mode 100644 index 0000000..02f1efb --- /dev/null +++ b/packages/pieces/community/deepgram/src/actions/create-summary.ts @@ -0,0 +1,78 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { deepgramAuth } from '../common/auth'; +import { BASE_URL, LANG_OPTIONS, MODEL_OPTIONS } from '../common/constants'; +import mime from 'mime-types'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const createSummaryAction = createAction({ + auth: deepgramAuth, + name: 'create_summary', + displayName: 'Create Summary', + description: 'Produces a summary of the content from an audio file.', + props: { + audioFile: Property.File({ + displayName: 'Audio File', + required: true, + }), + model: Property.StaticDropdown({ + displayName: 'Model', + required: false, + options: { + options: MODEL_OPTIONS, + }, + }), + language: Property.StaticDropdown({ + displayName: 'Language', + required: false, + description: 'en', + options: { + disabled: false, + options: LANG_OPTIONS, + }, + }), + fallbackToTranscript: Property.Checkbox({ + displayName: 'Fallback to Full Transcript', + description: 'Return full transcript if summary is not available.', + required: false, + defaultValue: true, + }), + }, + async run(context) { + const { + audioFile, + model = 'nova', + fallbackToTranscript, + language, + } = context.propsValue; + const mimeType = mime.lookup(audioFile.extension || '') || 'audio/wav'; + + const response = await httpClient.sendRequest({ + url: BASE_URL + '/listen', + method: HttpMethod.POST, + headers: { + Authorization: `Token ${context.auth}`, + 'Content-Type': mimeType, + }, + body: audioFile.data, + responseType: 'json', + queryParams: { + model, + summarize: 'v2', + language: language || 'en', + }, + }); + + if (response.body.results.summary) { + return response.body.results.summary; + } + + if ( + fallbackToTranscript && + response.body.results.channels?.[0]?.alternatives?.[0]?.transcript + ) { + return response.body.results.channels[0].alternatives[0].transcript; + } + + throw new Error('No summary or transcript available'); + }, +}); diff --git a/packages/pieces/community/deepgram/src/actions/create-transcription.ts b/packages/pieces/community/deepgram/src/actions/create-transcription.ts new file mode 100644 index 0000000..7fa6a07 --- /dev/null +++ b/packages/pieces/community/deepgram/src/actions/create-transcription.ts @@ -0,0 +1,70 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { deepgramAuth } from '../common/auth'; +import { BASE_URL, LANG_OPTIONS, MODEL_OPTIONS } from '../common/constants'; +import mime from 'mime-types'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const createTranscriptionCallbackAction = createAction({ + auth: deepgramAuth, + name: 'create_transcription_callback', + displayName: 'Create Transcription (Callback)', + description: 'Creates a transcription using a callback URL.', + props: { + audioFile: Property.File({ + displayName: 'Audio File', + required: true, + }), + model: Property.StaticDropdown({ + displayName: 'Model', + required: false, + options: { + options: MODEL_OPTIONS, + }, + }), + language: Property.StaticDropdown({ + displayName: 'Language', + required: false, + description: 'en', + options: { + disabled: false, + options: LANG_OPTIONS, + }, + }), + callbackUrl: Property.ShortText({ + displayName: 'Callback URL', + description: 'URL to receive the transcription when ready.', + required: true, + }), + }, + async run(context) { + const { + audioFile, + model = 'nova', + callbackUrl, + language, + } = context.propsValue; + const mimeType = mime.lookup(audioFile.extension || '') || 'audio/wav'; + + const response = await httpClient.sendRequest({ + url: BASE_URL + '/listen', + method: HttpMethod.POST, + headers: { + Authorization: `Token ${context.auth}`, + 'Content-Type': mimeType, + }, + body: audioFile.data, + responseType: 'json', + queryParams: { + model, + callback: callbackUrl, + language: language || 'en', + }, + }); + + return { + requestId: response.body.request_id, + callbackUrl, + status: 'submitted', + }; + }, +}); diff --git a/packages/pieces/community/deepgram/src/actions/list-projects.ts b/packages/pieces/community/deepgram/src/actions/list-projects.ts new file mode 100644 index 0000000..5b41890 --- /dev/null +++ b/packages/pieces/community/deepgram/src/actions/list-projects.ts @@ -0,0 +1,23 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { deepgramAuth } from '../common/auth'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { BASE_URL } from '../common/constants'; + +export const listProjectsAction = createAction({ + auth: deepgramAuth, + name: 'list_projects', + displayName: 'List Projects', + description: 'Retrieves a list of all projects associated with the account.', + props: {}, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: BASE_URL + '/projects', + headers: { + Authorization: `Token ${context.auth}`, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/deepgram/src/actions/text-to-speech.ts b/packages/pieces/community/deepgram/src/actions/text-to-speech.ts new file mode 100644 index 0000000..34355be --- /dev/null +++ b/packages/pieces/community/deepgram/src/actions/text-to-speech.ts @@ -0,0 +1,66 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { deepgramAuth } from '../common/auth'; +import { BASE_URL, TEXT_TO_SPEECH_MODELS } from '../common/constants'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const textToSpeechAction = createAction({ + auth: deepgramAuth, + name: 'text_to_speech', + displayName: 'Text to Speech', + description: 'Converts text to audio file.', + props: { + text: Property.LongText({ + displayName: 'Text', + required: true, + }), + model: Property.StaticDropdown({ + displayName: 'Voice', + required: true, + options: { + options: TEXT_TO_SPEECH_MODELS, + }, + }), + encoding: Property.StaticDropdown({ + displayName: 'Output Format', + required: false, + defaultValue: 'mp3', + options: { + disabled: false, + options: [ + { label: 'linear16', value: 'linear16' }, + { label: 'flac', value: 'flac' }, + { label: 'mulaw', value: 'mulaw' }, + { label: 'alaw', value: 'alaw' }, + { label: 'mp3', value: 'mp3' }, + { label: 'opus', value: 'opus' }, + { label: 'aac', value: 'aac' }, + ], + }, + }), + }, + async run(context) { + const { text, model, encoding } = context.propsValue; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: BASE_URL + '/speak', + body: { text }, + headers: { + Authorization: `Token ${context.auth}`, + 'Content-Type': 'application/json', + }, + queryParams: { + model, + encoding: encoding || 'mp3', + }, + responseType: 'arraybuffer', + }); + + return { + file: await context.files.write({ + fileName: `audio.${encoding}`, + data: Buffer.from(response.body), + }), + }; + }, +}); diff --git a/packages/pieces/community/deepgram/src/common/auth.ts b/packages/pieces/community/deepgram/src/common/auth.ts new file mode 100644 index 0000000..26698a0 --- /dev/null +++ b/packages/pieces/community/deepgram/src/common/auth.ts @@ -0,0 +1,23 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { PieceAuth } from '@activepieces/pieces-framework'; +import { BASE_URL } from './constants'; + +export const deepgramAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: `You can obtain your API key from [Deepgram Console](https://console.deepgram.com/).`, + validate: async ({ auth }) => { + try { + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: BASE_URL + '/projects', + headers: { + Authorization: `Token ${auth as string}`, + }, + }); + return { valid: true }; + } catch (e) { + return { valid: false, error: 'Invalid API key.' }; + } + }, +}); diff --git a/packages/pieces/community/deepgram/src/common/constants.ts b/packages/pieces/community/deepgram/src/common/constants.ts new file mode 100644 index 0000000..ec67a58 --- /dev/null +++ b/packages/pieces/community/deepgram/src/common/constants.ts @@ -0,0 +1,158 @@ +export const BASE_URL = 'https://api.deepgram.com/v1'; + +export const LANG_OPTIONS = [ + { label: 'bg', value: 'bg' }, + { label: 'ca', value: 'ca' }, + { label: 'zh', value: 'zh' }, + { label: 'zh-CN', value: 'zh-CN' }, + { label: 'zh-TW', value: 'zh-TW' }, + { label: 'zh-HK', value: 'zh-HK' }, + { label: 'zh-Hans', value: 'zh-Hans' }, + { label: 'zh-Hant', value: 'zh-Hant' }, + { label: 'cs', value: 'cs' }, + { label: 'da', value: 'da' }, + { label: 'da-DK', value: 'da-DK' }, + { label: 'nl', value: 'nl' }, + { label: 'nl-BE', value: 'nl-BE' }, + { label: 'en', value: 'en' }, + { label: 'en-US', value: 'en-US' }, + { label: 'en-AU', value: 'en-AU' }, + { label: 'en-GB', value: 'en-GB' }, + { label: 'en-NZ', value: 'en-NZ' }, + { label: 'en-IN', value: 'en-IN' }, + { label: 'et', value: 'et' }, + { label: 'fi', value: 'fi' }, + { label: 'fr', value: 'fr' }, + { label: 'fr-CA', value: 'fr-CA' }, + { label: 'de', value: 'de' }, + { label: 'de-CH', value: 'de-CH' }, + { label: 'el', value: 'el' }, + { label: 'hi', value: 'hi' }, + { label: 'hi-Latn', value: 'hi-Latn' }, + { label: 'hu', value: 'hu' }, + { label: 'id', value: 'id' }, + { label: 'it', value: 'it' }, + { label: 'ja', value: 'ja' }, + { label: 'ko', value: 'ko' }, + { label: 'ko-KR', value: 'ko-KR' }, + { label: 'lv', value: 'lv' }, + { label: 'lt', value: 'lt' }, + { label: 'ms', value: 'ms' }, + { label: 'no', value: 'no' }, + { label: 'pl', value: 'pl' }, + { label: 'pt', value: 'pt' }, + { label: 'pt-BR', value: 'pt-BR' }, + { label: 'pt-PT', value: 'pt-PT' }, + { label: 'ro', value: 'ro' }, + { label: 'ru', value: 'ru' }, + { label: 'sk', value: 'sk' }, + { label: 'es', value: 'es' }, + { label: 'es-419', value: 'es-419' }, + { label: 'es-LATAM', value: 'es-LATAM' }, + { label: 'sv', value: 'sv' }, + { label: 'sv-SE', value: 'sv-SE' }, + { label: 'taq', value: 'taq' }, + { label: 'th', value: 'th' }, + { label: 'th-TH', value: 'th-TH' }, + { label: 'tr', value: 'tr' }, + { label: 'uk', value: 'uk' }, + { label: 'vi', value: 'vi' }, +]; + +export const MODEL_OPTIONS = [ + { label: 'nova-3', value: 'nova-3' }, + { label: 'nova-3-general', value: 'nova-3-general' }, + { label: 'nova-3-medical', value: 'nova-3-medical' }, + { label: 'nova-2', value: 'nova-2' }, + { label: 'nova-2-general', value: 'nova-2-general' }, + { label: 'nova-2-meeting', value: 'nova-2-meeting' }, + { label: 'nova-2-finance', value: 'nova-2-finance' }, + { label: 'nova-2-conversationalai', value: 'nova-2-conversationalai' }, + { label: 'nova-2-voicemail', value: 'nova-2-voicemail' }, + { label: 'nova-2-video', value: 'nova-2-video' }, + { label: 'nova-2-medical', value: 'nova-2-medical' }, + { label: 'nova-2-drivethru', value: 'nova-2-drivethru' }, + { label: 'nova-2-automotive', value: 'nova-2-automotive' }, + { label: 'nova', value: 'nova' }, + { label: 'nova-general', value: 'nova-general' }, + { label: 'nova-phonecall', value: 'nova-phonecall' }, + { label: 'nova-medical', value: 'nova-medical' }, + { label: 'enhanced', value: 'enhanced' }, + { label: 'enhanced-general', value: 'enhanced-general' }, + { label: 'enhanced-meeting', value: 'enhanced-meeting' }, + { label: 'enhanced-phonecall', value: 'enhanced-phonecall' }, + { label: 'enhanced-finance', value: 'enhanced-finance' }, + { label: 'base', value: 'base' }, + { label: 'meeting', value: 'meeting' }, + { label: 'phonecall', value: 'phonecall' }, + { label: 'finance', value: 'finance' }, + { label: 'conversationalai', value: 'conversationalai' }, + { label: 'voicemail', value: 'voicemail' }, + { label: 'video', value: 'video' }, +]; + +export const TEXT_TO_SPEECH_MODELS = [ + { label: 'aura-asteria-en', value: 'aura-asteria-en' }, + { label: 'aura-luna-en', value: 'aura-luna-en' }, + { label: 'aura-stella-en', value: 'aura-stella-en' }, + { label: 'aura-athena-en', value: 'aura-athena-en' }, + { label: 'aura-hera-en', value: 'aura-hera-en' }, + { label: 'aura-orion-en', value: 'aura-orion-en' }, + { label: 'aura-arcas-en', value: 'aura-arcas-en' }, + { label: 'aura-perseus-en', value: 'aura-perseus-en' }, + { label: 'aura-angus-en', value: 'aura-angus-en' }, + { label: 'aura-orpheus-en', value: 'aura-orpheus-en' }, + { label: 'aura-helios-en', value: 'aura-helios-en' }, + { label: 'aura-zeus-en', value: 'aura-zeus-en' }, + { label: 'aura-2-amalthea-en', value: 'aura-2-amalthea-en' }, + { label: 'aura-2-andromeda-en', value: 'aura-2-andromeda-en' }, + { label: 'aura-2-apollo-en', value: 'aura-2-apollo-en' }, + { label: 'aura-2-arcas-en', value: 'aura-2-arcas-en' }, + { label: 'aura-2-aries-en', value: 'aura-2-aries-en' }, + { label: 'aura-2-asteria-en', value: 'aura-2-asteria-en' }, + { label: 'aura-2-athena-en', value: 'aura-2-athena-en' }, + { label: 'aura-2-atlas-en', value: 'aura-2-atlas-en' }, + { label: 'aura-2-aurora-en', value: 'aura-2-aurora-en' }, + { label: 'aura-2-callista-en', value: 'aura-2-callista-en' }, + { label: 'aura-2-cordelia-en', value: 'aura-2-cordelia-en' }, + { label: 'aura-2-cora-en', value: 'aura-2-cora-en' }, + { label: 'aura-2-delia-en', value: 'aura-2-delia-en' }, + { label: 'aura-2-draco-en', value: 'aura-2-draco-en' }, + { label: 'aura-2-electra-en', value: 'aura-2-electra-en' }, + { label: 'aura-2-harmonia-en', value: 'aura-2-harmonia-en' }, + { label: 'aura-2-helena-en', value: 'aura-2-helena-en' }, + { label: 'aura-2-hera-en', value: 'aura-2-hera-en' }, + { label: 'aura-2-hermes-en', value: 'aura-2-hermes-en' }, + { label: 'aura-2-hyperion-en', value: 'aura-2-hyperion-en' }, + { label: 'aura-2-iris-en', value: 'aura-2-iris-en' }, + { label: 'aura-2-janus-en', value: 'aura-2-janus-en' }, + { label: 'aura-2-juno-en', value: 'aura-2-juno-en' }, + { label: 'aura-2-jupiter-en', value: 'aura-2-jupiter-en' }, + { label: 'aura-2-luna-en', value: 'aura-2-luna-en' }, + { label: 'aura-2-mars-en', value: 'aura-2-mars-en' }, + { label: 'aura-2-minerva-en', value: 'aura-2-minerva-en' }, + { label: 'aura-2-neptune-en', value: 'aura-2-neptune-en' }, + { label: 'aura-2-odysseus-en', value: 'aura-2-odysseus-en' }, + { label: 'aura-2-ophelia-en', value: 'aura-2-ophelia-en' }, + { label: 'aura-2-orion-en', value: 'aura-2-orion-en' }, + { label: 'aura-2-orpheus-en', value: 'aura-2-orpheus-en' }, + { label: 'aura-2-pandora-en', value: 'aura-2-pandora-en' }, + { label: 'aura-2-phoebe-en', value: 'aura-2-phoebe-en' }, + { label: 'aura-2-pluto-en', value: 'aura-2-pluto-en' }, + { label: 'aura-2-saturn-en', value: 'aura-2-saturn-en' }, + { label: 'aura-2-selene-en', value: 'aura-2-selene-en' }, + { label: 'aura-2-thalia-en', value: 'aura-2-thalia-en' }, + { label: 'aura-2-theia-en', value: 'aura-2-theia-en' }, + { label: 'aura-2-vesta-en', value: 'aura-2-vesta-en' }, + { label: 'aura-2-zeus-en', value: 'aura-2-zeus-en' }, + { label: 'aura-2-sirio-es', value: 'aura-2-sirio-es' }, + { label: 'aura-2-nestor-es', value: 'aura-2-nestor-es' }, + { label: 'aura-2-carina-es', value: 'aura-2-carina-es' }, + { label: 'aura-2-celeste-es', value: 'aura-2-celeste-es' }, + { label: 'aura-2-alvaro-es', value: 'aura-2-alvaro-es' }, + { label: 'aura-2-diana-es', value: 'aura-2-diana-es' }, + { label: 'aura-2-aquila-es', value: 'aura-2-aquila-es' }, + { label: 'aura-2-selena-es', value: 'aura-2-selena-es' }, + { label: 'aura-2-estrella-es', value: 'aura-2-estrella-es' }, + { label: 'aura-2-javier-es', value: 'aura-2-javier-es' }, +]; diff --git a/packages/pieces/community/deepgram/src/index.ts b/packages/pieces/community/deepgram/src/index.ts new file mode 100644 index 0000000..f2dce18 --- /dev/null +++ b/packages/pieces/community/deepgram/src/index.ts @@ -0,0 +1,36 @@ +import { createPiece } from '@activepieces/pieces-framework'; +import { deepgramAuth } from './common/auth'; +import { createSummaryAction } from './actions/create-summary'; +import { createTranscriptionCallbackAction } from './actions/create-transcription'; +import { listProjectsAction } from './actions/list-projects'; +import { textToSpeechAction } from './actions/text-to-speech'; +import { PieceCategory } from '@activepieces/shared'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { BASE_URL } from './common/constants'; + +export const deepgramPiece = createPiece({ + displayName: 'Deepgram', + logoUrl: 'https://cdn.activepieces.com/pieces/deepgram.png', + description: + 'Deepgram is an AI-powered speech recognition platform that provides real-time transcription, text-to-speech, and audio analysis capabilities.', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + minimumSupportedRelease: '0.30.0', + authors: ['Ani-4x', 'kishanprmr'], + auth: deepgramAuth, + actions: [ + createSummaryAction, + createTranscriptionCallbackAction, + listProjectsAction, + textToSpeechAction, + createCustomApiCallAction({ + auth: deepgramAuth, + baseUrl: () => BASE_URL, + authMapping: async (auth) => { + return { + Authorization: `Token ${auth}`, + }; + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/deepgram/tsconfig.json b/packages/pieces/community/deepgram/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/deepgram/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/deepgram/tsconfig.lib.json b/packages/pieces/community/deepgram/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/deepgram/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/deepl/.eslintrc.json b/packages/pieces/community/deepl/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/deepl/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/deepl/README.md b/packages/pieces/community/deepl/README.md new file mode 100644 index 0000000..9e677c7 --- /dev/null +++ b/packages/pieces/community/deepl/README.md @@ -0,0 +1,7 @@ +# pieces-deepl + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-deepl` to build the library. diff --git a/packages/pieces/community/deepl/package.json b/packages/pieces/community/deepl/package.json new file mode 100644 index 0000000..4fe6b60 --- /dev/null +++ b/packages/pieces/community/deepl/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-deepl", + "version": "0.0.5" +} \ No newline at end of file diff --git a/packages/pieces/community/deepl/project.json b/packages/pieces/community/deepl/project.json new file mode 100644 index 0000000..f798498 --- /dev/null +++ b/packages/pieces/community/deepl/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-deepl", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/deepl/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/deepl", + "tsConfig": "packages/pieces/community/deepl/tsconfig.lib.json", + "packageJson": "packages/pieces/community/deepl/package.json", + "main": "packages/pieces/community/deepl/src/index.ts", + "assets": [ + "packages/pieces/community/deepl/*.md", + { + "input": "packages/pieces/community/deepl/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-deepl {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/deepl/src/index.ts b/packages/pieces/community/deepl/src/index.ts new file mode 100644 index 0000000..6660c87 --- /dev/null +++ b/packages/pieces/community/deepl/src/index.ts @@ -0,0 +1,68 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { translateText } from './lib/actions/translate-text'; + +const markdownDescription = ` +Follow these instructions to get your DeepL API Key: + +1. Log in to your DeepL account. +2. Visit https://www.deepl.com/account/summary +3. Go to the API section and obtain your DeepL API Key. +`; +export const deeplAuth = PieceAuth.CustomAuth({ + description: markdownDescription, + props: { + key: Property.ShortText({ + displayName: 'Api key', + description: 'Enter the api key', + required: true, + }), + type: Property.StaticDropdown({ + displayName: 'Api url', + description: 'Select api url', + required: true, + options: { + options: [ + { + label: 'Free API', + value: 'free', + }, + { + label: 'Paid API', + value: 'paid', + }, + ], + }, + }), + }, + required: true, +}); + +export const deepl = createPiece({ + displayName: 'DeepL', + description: 'AI-powered language translation', + auth: deeplAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/deepl.png', + categories: [], + authors: ["BBND","kishanprmr","MoShizzle","abuaboud"], + actions: [ + translateText, + createCustomApiCallAction({ + baseUrl: (auth) => + (auth as { type: string }).type === 'free' + ? 'https://api-free.deepl.com/v2' + : 'https://api.deepl.com/v2', // Replace with the actual base URL + auth: deeplAuth, + authMapping: async (auth) => ({ + Authorization: `DeepL-Auth-Key ${(auth as { key: string }).key}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/deepl/src/lib/actions/translate-text.ts b/packages/pieces/community/deepl/src/lib/actions/translate-text.ts new file mode 100644 index 0000000..303f052 --- /dev/null +++ b/packages/pieces/community/deepl/src/lib/actions/translate-text.ts @@ -0,0 +1,236 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { deeplAuth } from '../..'; + +export const translateText = createAction({ + name: 'translate_text', + auth: deeplAuth, + displayName: 'Translate text', + description: 'Translate a text to the target language', + props: { + text: Property.LongText({ + displayName: 'Text', + required: true, + description: + 'Text to be translated. Only UTF-8-encoded plain text is supported.', + }), + target_lang: Property.StaticDropdown({ + displayName: 'Target language', + description: 'The language into which the text should be translated.', + required: true, + options: { + options: [ + { value: 'BG', label: 'Bulgarian' }, + { value: 'CS', label: 'Czech' }, + { value: 'DA', label: 'Danish' }, + { value: 'DE', label: 'German' }, + { value: 'EL', label: 'Greek' }, + { value: 'EN-GB', label: 'English (British)' }, + { value: 'EN-US', label: 'English (American)' }, + { value: 'ES', label: 'Spanish' }, + { value: 'ET', label: 'Estonian' }, + { value: 'FI', label: 'Finnish' }, + { value: 'FR', label: 'French' }, + { value: 'HU', label: 'Hungarian' }, + { value: 'ID', label: 'Indonesian' }, + { value: 'IT', label: 'Italian' }, + { value: 'JA', label: 'Japanese' }, + { value: 'KO', label: 'Korean' }, + { value: 'LT', label: 'Lithuanian' }, + { value: 'LV', label: 'Latvian' }, + { value: 'NB', label: 'Norwegian' }, + { value: 'NL', label: 'Dutch' }, + { value: 'PL', label: 'Polish' }, + { value: 'PT-BR', label: 'Portuguese (Brazilian)' }, + { value: 'PT-PT', label: 'Portuguese' }, + { value: 'RO', label: 'Romanian' }, + { value: 'RU', label: 'Russian' }, + { value: 'SK', label: 'Slovak' }, + { value: 'SL', label: 'Slovenian' }, + { value: 'SV', label: 'Swedish' }, + { value: 'TR', label: 'Turkish' }, + { value: 'UK', label: 'Ukrainian' }, + { value: 'ZH', label: 'Chinese (simplified)' }, + ], + }, + }), + source_lang: Property.StaticDropdown({ + displayName: 'Source language', + description: 'Language of the text to be translated', + required: false, + options: { + options: [ + { value: 'BG', label: 'Bulgarian' }, + { value: 'CS', label: 'Czech' }, + { value: 'DA', label: 'Danish' }, + { value: 'DE', label: 'German' }, + { value: 'EL', label: 'Greek' }, + { value: 'EN', label: 'English' }, + { value: 'ES', label: 'Spanish' }, + { value: 'ET', label: 'Estonian' }, + { value: 'FI', label: 'Finnish' }, + { value: 'FR', label: 'French' }, + { value: 'HU', label: 'Hungarian' }, + { value: 'ID', label: 'Indonesian' }, + { value: 'IT', label: 'Italian' }, + { value: 'JA', label: 'Japanese' }, + { value: 'KO', label: 'Korean' }, + { value: 'LT', label: 'Lithuanian' }, + { value: 'LV', label: 'Latvian' }, + { value: 'NB', label: 'Norwegian' }, + { value: 'NL', label: 'Dutch' }, + { value: 'PL', label: 'Polish' }, + { value: 'PT', label: 'Portuguese' }, + { value: 'RO', label: 'Romanian' }, + { value: 'RU', label: 'Russian' }, + { value: 'SK', label: 'Slovak' }, + { value: 'SL', label: 'Slovenian' }, + { value: 'SV', label: 'Swedish' }, + { value: 'TR', label: 'Turkish' }, + { value: 'UK', label: 'Ukrainian' }, + { value: 'ZH', label: 'Chinese' }, + ], + }, + }), + split_sentences: Property.StaticDropdown({ + displayName: 'Split sentences', + description: + 'Sets whether the translation engine should first split the input into sentences. For text translations where tag_handling is not set to html, the default value is 1, meaning the engine splits on punctuation and on newlines.', + required: false, + options: { + options: [ + { + value: '0', + label: + 'No splitting at all, whole input is treated as one sentence', + }, + { value: '1', label: 'Splits on punctuation and on newlines' }, + { + value: 'nonewlines', + label: 'Splits on punctuation only, ignoring newlines', + }, + ], + }, + }), + preserve_formatting: Property.StaticDropdown({ + displayName: 'Preserve formatting', + description: + 'Sets whether the translation engine should respect the original formatting, even if it would usually correct some aspects.', + required: false, + options: { + options: [ + { value: true, label: 'Yes' }, + { value: false, label: 'No' }, + ], + }, + }), + formality: Property.StaticDropdown({ + displayName: 'Formality', + description: + 'Sets whether the translated text should lean towards formal or informal language.', + required: false, + options: { + options: [ + { value: 'default', label: 'Default' }, + { value: 'more', label: 'For a more formal language' }, + { value: 'less', label: 'For a more informal language' }, + { + value: 'prefer_more', + label: + 'For a more formal language if available, otherwise fallback to default formality', + }, + { + value: 'prefer_less', + label: + 'For a more informal language if available, otherwise fallback to default formality', + }, + ], + }, + }), + glossary_id: Property.ShortText({ + displayName: 'Glossary id', + description: 'Specify the glossary to use for the translation.', + required: false, + }), + tag_handling: Property.StaticDropdown({ + displayName: 'Tag handling', + description: 'Sets which kind of tags should be handled.', + required: false, + options: { + options: [ + { value: 'xml', label: 'Enable XML tag handling' }, + { value: 'html', label: 'Enable HTML tag handling' }, + ], + }, + }), + outline_detection: Property.StaticDropdown({ + displayName: 'Outline detection', + description: + "The automatic detection of the XML structure won't yield best results in all XML files.", + required: false, + options: { + options: [ + { value: true, label: 'Yes' }, + { value: false, label: 'No' }, + ], + }, + }), + non_splitting_tags: Property.ShortText({ + displayName: 'Non splitting tags', + description: 'Comma-separated list of XML or HTML tags.', + required: false, + }), + splitting_tags: Property.ShortText({ + displayName: 'Splitting tags', + description: 'Comma-separated list of XML or HTML tags.', + required: false, + }), + ignore_tags: Property.ShortText({ + displayName: 'Ignore tags', + description: 'Comma-separated list of XML or HTML tags.', + required: false, + }), + }, + async run(context) { + const DEEPL_FREE_URL = 'https://api-free.deepl.com/v2/translate'; + const DEEPL_PAID_URL = 'https://api.deepl.com/v2/translate'; + const { + text, + target_lang, + source_lang, + split_sentences, + preserve_formatting, + formality, + glossary_id, + tag_handling, + outline_detection, + non_splitting_tags, + splitting_tags, + ignore_tags, + } = context.propsValue; + const request = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: context.auth.type === 'free' ? DEEPL_FREE_URL : DEEPL_PAID_URL, + headers: { + Authorization: `DeepL-Auth-Key ${context.auth.key}`, + 'Content-Type': 'application/json', + }, + body: { + text: [text], + target_lang: target_lang, + source_lang: source_lang, + split_sentences: split_sentences, + preserve_formatting: preserve_formatting, + formality: formality, + glossary_id: glossary_id, + tag_handling: tag_handling, + outline_detection: outline_detection, + non_splitting_tags: non_splitting_tags?.split(','), + splitting_tags: splitting_tags?.split(','), + ignore_tags: ignore_tags?.split(','), + }, + }); + + return request.body; + }, +}); diff --git a/packages/pieces/community/deepl/tsconfig.json b/packages/pieces/community/deepl/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/deepl/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/deepl/tsconfig.lib.json b/packages/pieces/community/deepl/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/deepl/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/deepseek/.eslintrc.json b/packages/pieces/community/deepseek/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/deepseek/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/deepseek/README.md b/packages/pieces/community/deepseek/README.md new file mode 100644 index 0000000..53dd753 --- /dev/null +++ b/packages/pieces/community/deepseek/README.md @@ -0,0 +1,7 @@ +# pieces-deepseek + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-deepseek` to build the library. diff --git a/packages/pieces/community/deepseek/package.json b/packages/pieces/community/deepseek/package.json new file mode 100644 index 0000000..f781210 --- /dev/null +++ b/packages/pieces/community/deepseek/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-deepseek", + "version": "0.0.1" +} diff --git a/packages/pieces/community/deepseek/project.json b/packages/pieces/community/deepseek/project.json new file mode 100644 index 0000000..b3fbb78 --- /dev/null +++ b/packages/pieces/community/deepseek/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-deepseek", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/deepseek/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/deepseek", + "tsConfig": "packages/pieces/community/deepseek/tsconfig.lib.json", + "packageJson": "packages/pieces/community/deepseek/package.json", + "main": "packages/pieces/community/deepseek/src/index.ts", + "assets": [ + "packages/pieces/community/deepseek/*.md", + { + "input": "packages/pieces/community/deepseek/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/deepseek/src/index.ts b/packages/pieces/community/deepseek/src/index.ts new file mode 100644 index 0000000..cd280a5 --- /dev/null +++ b/packages/pieces/community/deepseek/src/index.ts @@ -0,0 +1,53 @@ + +import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { baseUrl, unauthorizedMessage } from "./lib/common/common"; +import OpenAI from 'openai'; +import { askDeepseek } from "./lib/actions/ask-deepseek"; +import { PieceCategory } from "@activepieces/shared"; + + export const deepseekAuth = PieceAuth.SecretText({ + description:` + Follow these instructions to get your DeepSeek API Key: + +1. Visit the following website: https://platform.deepseek.com/api_keys. +2. Once on the website, locate and click on the option to obtain your DeepSeek API Key.`, + displayName: 'API Key', + required: true, + validate: async (auth) => { + try { + const openai = new OpenAI({ + baseURL: baseUrl, + apiKey: auth.auth, + }); + + const models = await openai.models.list(); + if (models.data.length > 0){ + return { + valid: true, + }; + } + else + return { + valid: false, + error: unauthorizedMessage, + }; + } catch (e) { + return { + valid: false, + error: unauthorizedMessage, + }; + } + }, + }); + + export const deepseek = createPiece({ + displayName: "DeepSeek", + auth: deepseekAuth, + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + minimumSupportedRelease: '0.36.1', + logoUrl: "https://cdn.activepieces.com/pieces/deepseek.png", + authors: ["PFernandez98"], + actions: [askDeepseek], + triggers: [], + }); + \ No newline at end of file diff --git a/packages/pieces/community/deepseek/src/lib/actions/ask-deepseek.ts b/packages/pieces/community/deepseek/src/lib/actions/ask-deepseek.ts new file mode 100644 index 0000000..60a9091 --- /dev/null +++ b/packages/pieces/community/deepseek/src/lib/actions/ask-deepseek.ts @@ -0,0 +1,197 @@ +import { deepseekAuth } from '../../index'; +import { createAction, Property, StoreScope } from "@activepieces/pieces-framework"; +import OpenAI from 'openai'; +import { baseUrl } from '../common/common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const askDeepseek = createAction({ + auth: deepseekAuth, + name: 'ask_deepseek', + displayName: 'Ask Deepseek', + description: 'Ask Deepseek anything you want!', + props: { + model: Property.Dropdown({ + displayName: 'Model', + required: true, + description: 'The model which will generate the completion.', + refreshers: [], + defaultValue: 'deepseek-chat', + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Enter your API key first', + options: [], + }; + } + try { + const openai = new OpenAI({ + baseURL: baseUrl, + apiKey: auth as string, + }); + const response = await openai.models.list(); + // We need to get only LLM models + const models = response.data; + return { + disabled: false, + options: models.map((model) => { + return { + label: model.id, + value: model.id, + }; + }), + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: "Couldn't load models, API key is invalid", + }; + } + }, + }), + prompt: Property.LongText({ + displayName: 'Question', + required: true, + }), + frequencyPenalty: Property.Number({ + displayName: 'Frequency penalty', + required: false, + description: + "Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.", + defaultValue: 0, + }), + maxTokens: Property.Number({ + displayName: 'Maximum Tokens', + required: true, + description: + 'The maximum number of tokens to generate. Possible values are between 1 and 8192.', + defaultValue: 4096, + }), + presencePenalty: Property.Number({ + displayName: 'Presence penalty', + required: false, + description: + "Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the mode's likelihood to talk about new topics.", + defaultValue: 0, + }), + responseFormat: Property.StaticDropdown({ + displayName: 'Response Format', + description: + 'The format of the response. IMPORTANT: When using JSON Output, you must also instruct the model to produce JSON yourself', + required: true, + defaultValue: 'text', + options: { + options: [ + { + label: 'Text', + value: 'text', + }, + { + label: 'JSON', + value: 'json_object', + }, + ], + }, + }), + temperature: Property.Number({ + displayName: 'Temperature', + required: false, + description: + 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive. Between 0 and 2. We generally recommend altering this or top_p but not both.', + defaultValue: 1, + }), + topP: Property.Number({ + displayName: 'Top P', + required: false, + description: + 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. Values <=1. We generally recommend altering this or temperature but not both.', + defaultValue: 1, + }), + memoryKey: Property.ShortText({ + displayName: 'Memory Key', + description: + 'A memory key that will keep the chat history shared across runs and flows. Keep it empty to leave Deepseek without memory of previous messages.', + required: false, + }), + roles: Property.Json({ + displayName: 'Roles', + required: false, + description: 'Array of roles to specify more accurate response', + defaultValue: [ + { role: 'system', content: 'You are a helpful assistant.' }, + ], + }), + }, + async run({ auth, propsValue, store }) { + await propsValidation.validateZod(propsValue, { + temperature: z.number().min(0).max(2).optional(), + memoryKey: z.string().max(128).optional(), + }); + const openai = new OpenAI({ + baseURL: baseUrl, + apiKey: auth, + }); + const { + model, + temperature, + maxTokens, + topP, + frequencyPenalty, + presencePenalty, + responseFormat, + prompt, + memoryKey, + } = propsValue; + + let messageHistory: any[] | null = []; + // If memory key is set, retrieve messages stored in history + if (memoryKey) { + messageHistory = (await store.get(memoryKey, StoreScope.PROJECT)) ?? []; + } + + // Add user prompt to message history + messageHistory.push({ + role: 'user', + content: prompt, + }); + + // Add system instructions if set by user + const rolesArray = propsValue.roles ? (propsValue.roles as any) : []; + const roles = rolesArray.map((item: any) => { + const rolesEnum = ['system', 'user', 'assistant']; + if (!rolesEnum.includes(item.role)) { + throw new Error( + 'The only available roles are: [system, user, assistant]' + ); + } + + return { + role: item.role, + content: item.content, + }; + }); + + // Send prompt + const completion = await openai.chat.completions.create({ + model: model, + messages: [...roles, ...messageHistory], + temperature: temperature, + max_tokens: maxTokens, + top_p: topP, + frequency_penalty: frequencyPenalty, + presence_penalty: presencePenalty, + response_format: responseFormat === "json_object" ? { "type": "json_object" } : { "type": "text" }, + }); + + messageHistory = [...messageHistory, completion.choices[0].message]; + + if (memoryKey) { + await store.put(memoryKey, messageHistory, StoreScope.PROJECT); + } + + return completion.choices[0].message.content; + }, +}); + diff --git a/packages/pieces/community/deepseek/src/lib/common/common.ts b/packages/pieces/community/deepseek/src/lib/common/common.ts new file mode 100644 index 0000000..644278b --- /dev/null +++ b/packages/pieces/community/deepseek/src/lib/common/common.ts @@ -0,0 +1,5 @@ + +export const baseUrl = 'https://api.deepseek.com'; + +export const unauthorizedMessage = `Error Occurred: 401 \n +Ensure that your API key is valid. \n`; diff --git a/packages/pieces/community/deepseek/tsconfig.json b/packages/pieces/community/deepseek/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/deepseek/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/deepseek/tsconfig.lib.json b/packages/pieces/community/deepseek/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/deepseek/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/delay/.eslintrc.json b/packages/pieces/community/delay/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/delay/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/delay/README.md b/packages/pieces/community/delay/README.md new file mode 100644 index 0000000..3bb8510 --- /dev/null +++ b/packages/pieces/community/delay/README.md @@ -0,0 +1,7 @@ +# pieces-delay + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-delay` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/delay/package.json b/packages/pieces/community/delay/package.json new file mode 100644 index 0000000..3b8eeff --- /dev/null +++ b/packages/pieces/community/delay/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-delay", + "version": "0.3.12" +} \ No newline at end of file diff --git a/packages/pieces/community/delay/project.json b/packages/pieces/community/delay/project.json new file mode 100644 index 0000000..2bfd5d6 --- /dev/null +++ b/packages/pieces/community/delay/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-delay", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/delay/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/delay", + "tsConfig": "packages/pieces/community/delay/tsconfig.lib.json", + "packageJson": "packages/pieces/community/delay/package.json", + "main": "packages/pieces/community/delay/src/index.ts", + "assets": [ + "packages/pieces/community/delay/*.md", + { + "input": "packages/pieces/community/delay/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/delay/src/index.ts b/packages/pieces/community/delay/src/index.ts new file mode 100644 index 0000000..66e62e9 --- /dev/null +++ b/packages/pieces/community/delay/src/index.ts @@ -0,0 +1,19 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { delayForAction } from './lib/actions/delay-for-action'; +import { delayUntilAction } from './lib/actions/delay-until-action'; + +export const delay = createPiece({ + displayName: 'Delay', + description: 'Use it to delay the execution of the next action', + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/delay.png', + authors: ["Nilesh","kishanprmr","MoShizzle","AbdulTheActivePiecer","khaledmashaly","abuaboud"], + categories: [PieceCategory.CORE, PieceCategory.FLOW_CONTROL], + auth: PieceAuth.None(), + actions: [ + delayForAction, // Delay for a fixed duration + delayUntilAction, // Takes a timestamp parameter instead of duration + ], + triggers: [], +}); diff --git a/packages/pieces/community/delay/src/lib/actions/delay-for-action.ts b/packages/pieces/community/delay/src/lib/actions/delay-for-action.ts new file mode 100644 index 0000000..caed976 --- /dev/null +++ b/packages/pieces/community/delay/src/lib/actions/delay-for-action.ts @@ -0,0 +1,100 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { ExecutionType, PauseType } from '@activepieces/shared'; +import { markdownDescription } from '../common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +enum TimeUnit { + SECONDS = 'seconds', + MINUTES = 'minutes', + HOURS = 'hours', + DAYS = 'days', +} + +export const delayForAction = createAction({ + name: 'delayFor', + displayName: 'Delay For', + description: 'Delays the execution of the next action for a given duration', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + markdown: Property.MarkDown({ + value: markdownDescription, + }), + unit: Property.StaticDropdown({ + displayName: 'Unit', + description: 'The unit of time to delay the execution of the next action', + required: true, + options: { + options: [ + { value: TimeUnit.SECONDS, label: 'Seconds' }, + { value: TimeUnit.MINUTES, label: 'Minutes' }, + { value: TimeUnit.HOURS, label: 'Hours' }, + { value: TimeUnit.DAYS, label: 'Days' }, + ], + }, + defaultValue: TimeUnit.SECONDS, + }), + delayFor: Property.Number({ + displayName: 'Amount', + description: + 'The number of units to delay the execution of the next action', + required: true, + }), + }, + async run(ctx) { + await propsValidation.validateZod(ctx.propsValue, { + delayFor: z.number().min(0), + }); + + const unit = ctx.propsValue.unit ?? TimeUnit.SECONDS; + let delayInMs: number; + switch (unit) { + case TimeUnit.SECONDS: + delayInMs = ctx.propsValue.delayFor * 1000; + break; + case TimeUnit.MINUTES: + delayInMs = ctx.propsValue.delayFor * 60 * 1000; + break; + case TimeUnit.HOURS: + delayInMs = ctx.propsValue.delayFor * 60 * 60 * 1000; + break; + case TimeUnit.DAYS: + delayInMs = ctx.propsValue.delayFor * 24 * 60 * 60 * 1000; + break; + } + if (ctx.executionType == ExecutionType.RESUME) { + return { + delayForInMs: delayInMs, + success: true, + }; + } else if (delayInMs > 1 * 10 * 1000) { + // use flow pause + const currentTime = new Date(); + const futureTime = new Date(currentTime.getTime() + delayInMs); + ctx.run.pause({ + pauseMetadata: { + type: PauseType.DELAY, + resumeDateTime: futureTime.toUTCString(), + }, + }); + return {}; // irrelevant as the flow is being paused, not completed + } else { + // use setTimeout + await new Promise((resolve) => setTimeout(resolve, delayInMs)); + return { + delayForInMs: delayInMs, + success: true, + }; + } + }, +}); diff --git a/packages/pieces/community/delay/src/lib/actions/delay-until-action.ts b/packages/pieces/community/delay/src/lib/actions/delay-until-action.ts new file mode 100644 index 0000000..cea96e0 --- /dev/null +++ b/packages/pieces/community/delay/src/lib/actions/delay-until-action.ts @@ -0,0 +1,64 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { ExecutionType, PauseType } from '@activepieces/shared'; +import dayjs from 'dayjs'; +import { markdownDescription } from '../common'; + +export const delayUntilAction = createAction({ + name: 'delay_until', + displayName: 'Delay Until', + description: + 'Delays the execution of the next action until a given timestamp', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + markdown: Property.MarkDown({ + value: markdownDescription, + }), + delayUntilTimestamp: Property.DateTime({ + displayName: 'Date and Time', + description: + 'Specifies the date and time until which the execution of the next action should be delayed. It supports multiple formats, including ISO format.', + required: true, + }), + }, + async run(ctx) { + const delayTill = new Date(ctx.propsValue.delayUntilTimestamp); + const delayInMs = delayTill.getTime() - Date.now(); + if (ctx.executionType == ExecutionType.RESUME) { + return { + delayTill: delayTill, + success: true, + }; + } else if (delayInMs <= 0) { + // resume immediately + return { + delayTill: delayTill, + success: true, + }; + } else if (delayInMs > 1 * 60 * 1000) { + // use flow pause + const currentTime = new Date(); + const futureTime = dayjs(currentTime.getTime() + delayInMs); + ctx.run.pause({ + pauseMetadata: { + type: PauseType.DELAY, + resumeDateTime: futureTime.toISOString(), + }, + }); + return {}; // irrelevant as the flow is being paused, not completed + } else { + // use setTimeout for delayTill between 0 and 5 seconds + await new Promise((resolve) => setTimeout(resolve, delayInMs)); + return { + delayTill: delayTill, + success: true, + }; + } + }, +}); diff --git a/packages/pieces/community/delay/src/lib/common.ts b/packages/pieces/community/delay/src/lib/common.ts new file mode 100644 index 0000000..f0bbf5b --- /dev/null +++ b/packages/pieces/community/delay/src/lib/common.ts @@ -0,0 +1,5 @@ + + +export const markdownDescription = ` +**Note:** The maximum duration per step is {{pausedFlowTimeoutDays}} days. +` \ No newline at end of file diff --git a/packages/pieces/community/delay/tsconfig.json b/packages/pieces/community/delay/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/delay/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/delay/tsconfig.lib.json b/packages/pieces/community/delay/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/delay/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/devin/.eslintrc.json b/packages/pieces/community/devin/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/devin/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/devin/README.md b/packages/pieces/community/devin/README.md new file mode 100644 index 0000000..6b3da72 --- /dev/null +++ b/packages/pieces/community/devin/README.md @@ -0,0 +1,7 @@ +# pieces-devin + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-devin` to build the library. diff --git a/packages/pieces/community/devin/package.json b/packages/pieces/community/devin/package.json new file mode 100644 index 0000000..dd81307 --- /dev/null +++ b/packages/pieces/community/devin/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-devin", + "version": "0.0.2" +} diff --git a/packages/pieces/community/devin/project.json b/packages/pieces/community/devin/project.json new file mode 100644 index 0000000..92de6b4 --- /dev/null +++ b/packages/pieces/community/devin/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-devin", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/devin/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/devin", + "tsConfig": "packages/pieces/community/devin/tsconfig.lib.json", + "packageJson": "packages/pieces/community/devin/package.json", + "main": "packages/pieces/community/devin/src/index.ts", + "assets": [ + "packages/pieces/community/devin/*.md", + { + "input": "packages/pieces/community/devin/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/devin/src/index.ts b/packages/pieces/community/devin/src/index.ts new file mode 100644 index 0000000..d1d0c3e --- /dev/null +++ b/packages/pieces/community/devin/src/index.ts @@ -0,0 +1,29 @@ + +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createSession } from './lib/actions/create-session'; +import { getSessionDetails } from './lib/actions/get-session-details'; +import { sendMessage } from './lib/actions/send-message'; + +export const devinAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'Your Devin API key (used in the Authorization header).', +}); + +export const devin = createPiece({ + displayName: 'Devin AI', + description: 'AI-powered engineering assistant for automating development tasks, code generation, and technical conversations.', + logoUrl: 'https://cdn.activepieces.com/pieces/devin.png', + auth: devinAuth, + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + minimumSupportedRelease: '0.20.0', + authors: ['ahmad-swanblocks'], + actions: [ + createSession, + getSessionDetails, + sendMessage, + ], + triggers: [], +}); + \ No newline at end of file diff --git a/packages/pieces/community/devin/src/lib/actions/create-session.ts b/packages/pieces/community/devin/src/lib/actions/create-session.ts new file mode 100644 index 0000000..595812d --- /dev/null +++ b/packages/pieces/community/devin/src/lib/actions/create-session.ts @@ -0,0 +1,49 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { devinAuth } from '../..'; + +export const createSession = createAction({ + name: 'create_session', + displayName: 'Create Session', + description: 'Creates a new Devin session', + auth: devinAuth, + props: { + prompt: Property.ShortText({ + displayName: 'Prompt', + required: true, + }), + snapshotId: Property.ShortText({ + displayName: 'Snapshot ID', + required: false, + }), + playbookId: Property.ShortText({ + displayName: 'Playbook ID', + required: false, + }), + unlisted: Property.Checkbox({ + displayName: 'Unlisted', + required: false, + }), + idempotent: Property.Checkbox({ + displayName: 'Idempotent', + required: false, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.devin.ai/v1/sessions', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + prompt: propsValue.prompt, + snapshot_id: propsValue.snapshotId, + playbook_id: propsValue.playbookId, + unlisted: propsValue.unlisted, + idempotent: propsValue.idempotent, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/devin/src/lib/actions/get-session-details.ts b/packages/pieces/community/devin/src/lib/actions/get-session-details.ts new file mode 100644 index 0000000..e08f7f2 --- /dev/null +++ b/packages/pieces/community/devin/src/lib/actions/get-session-details.ts @@ -0,0 +1,27 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { devinAuth } from '../..'; + +export const getSessionDetails = createAction({ + name: 'get_session_details', + displayName: 'Get Session Details', + description: 'Retrieves details of a specific Devin session', + auth: devinAuth, + props: { + sessionId: Property.ShortText({ + displayName: 'Session ID', + required: true, + description: 'The ID of the session to retrieve details for', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.devin.ai/v1/session/${propsValue.sessionId}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/devin/src/lib/actions/send-message.ts b/packages/pieces/community/devin/src/lib/actions/send-message.ts new file mode 100644 index 0000000..16218ee --- /dev/null +++ b/packages/pieces/community/devin/src/lib/actions/send-message.ts @@ -0,0 +1,35 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { devinAuth } from '../..'; + +export const sendMessage = createAction({ + name: 'send_message', + displayName: 'Send Message', + description: 'Sends a message to a Devin session', + auth: devinAuth, + props: { + sessionId: Property.ShortText({ + displayName: 'Session ID', + required: true, + description: 'The ID of the session to send the message to', + }), + message: Property.LongText({ + displayName: 'Message', + required: true, + description: 'The message to send to the session', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.devin.ai/v1/session/${propsValue.sessionId}/messages`, + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + message: propsValue.message, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/devin/tsconfig.json b/packages/pieces/community/devin/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/devin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/devin/tsconfig.lib.json b/packages/pieces/community/devin/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/devin/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/discord/.babelrc b/packages/pieces/community/discord/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/discord/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/discord/.eslintrc.json b/packages/pieces/community/discord/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/discord/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/discord/README.md b/packages/pieces/community/discord/README.md new file mode 100644 index 0000000..f5285f5 --- /dev/null +++ b/packages/pieces/community/discord/README.md @@ -0,0 +1,7 @@ +# pieces-discord + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-discord` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/discord/package.json b/packages/pieces/community/discord/package.json new file mode 100644 index 0000000..e90935b --- /dev/null +++ b/packages/pieces/community/discord/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-discord", + "version": "0.3.14" +} diff --git a/packages/pieces/community/discord/project.json b/packages/pieces/community/discord/project.json new file mode 100644 index 0000000..a0f207b --- /dev/null +++ b/packages/pieces/community/discord/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-discord", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/discord/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/discord", + "tsConfig": "packages/pieces/community/discord/tsconfig.lib.json", + "packageJson": "packages/pieces/community/discord/package.json", + "main": "packages/pieces/community/discord/src/index.ts", + "assets": [ + "packages/pieces/community/discord/*.md", + { + "input": "packages/pieces/community/discord/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/discord/src/index.ts b/packages/pieces/community/discord/src/index.ts new file mode 100644 index 0000000..e5c47b8 --- /dev/null +++ b/packages/pieces/community/discord/src/index.ts @@ -0,0 +1,85 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { discordAddRoleToMember } from './lib/actions/add-role-to-member'; +import { discordFindChannel } from './lib/actions/find-channel'; +import { discordFindGuildMemberByUsername } from './lib/actions/find-guild-member'; +import { discordRemoveMemberFromGuild } from './lib/actions/remove-member-from-guild'; +import { discordRemoveRoleFromMember } from './lib/actions/remove-role-from-member'; +import { discordRenameChannel } from './lib/actions/rename-channel'; +import { discordCreateChannel } from './lib/actions/create-channel'; +import { discordDeleteChannel } from './lib/actions/delete-channel'; +import { discordSendApprovalMessage } from './lib/actions/send-approval-message'; +import { discordSendMessageWebhook } from './lib/actions/send-message-webhook'; +import { newMessage } from './lib/triggers/new-message'; +import { discordRemoveBanFromUser } from './lib/actions/remove-ban-from-user'; +import { discordCreateGuildRole } from './lib/actions/create-guild-role'; +import { discordDeleteGuildRole } from './lib/actions/delete-guild-role'; +import { discordBanGuildMember } from './lib/actions/ban-a-guild-member'; +import { newMember } from './lib/triggers/new-member'; +import { sendMessageWithBot } from './lib/actions/send-message-with-bot' + +const markdown = ` +To obtain a token, follow these steps: +1. Go to https://discord.com/developers/applications +2. Click on Application (or create one if you don't have one) +3. Click on Bot +4. Copy the token +`; + +export const discordAuth = PieceAuth.SecretText({ + displayName: 'Bot Token', + description: markdown, + required: true, +}); + +export const discord = createPiece({ + displayName: 'Discord', + description: 'Instant messaging and VoIP social platform', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/discord.png', + categories: [PieceCategory.COMMUNICATION], + auth: discordAuth, + actions: [ + sendMessageWithBot, + discordSendMessageWebhook, + discordSendApprovalMessage, + discordAddRoleToMember, + discordRemoveRoleFromMember, + discordRemoveMemberFromGuild, + discordFindGuildMemberByUsername, + discordRenameChannel, + discordCreateChannel, + discordDeleteChannel, + discordFindChannel, + discordRemoveBanFromUser, + discordCreateGuildRole, + discordDeleteGuildRole, + discordBanGuildMember, + createCustomApiCallAction({ + auth:discordAuth, + baseUrl: () => { + return 'https://discord.com/api/v9'; + }, + authMapping: async (auth) => { + return { + Authorization: `Bot ${auth}`, + }; + }, + }), + ], + authors: [ + 'creed983', + 'TaskMagicKyle', + 'karimkhaleel', + 'Abdallah-Alwarawreh', + 'kishanprmr', + 'MoShizzle', + 'AbdulTheActivePiecer', + 'khaledmashaly', + 'abuaboud', + 'tintinthedev', + 'AshotZaqoyan' + ], + triggers: [newMessage, newMember], +}); diff --git a/packages/pieces/community/discord/src/lib/actions/add-role-to-member.ts b/packages/pieces/community/discord/src/lib/actions/add-role-to-member.ts new file mode 100644 index 0000000..e6ded56 --- /dev/null +++ b/packages/pieces/community/discord/src/lib/actions/add-role-to-member.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { discordAuth } from '../../index'; +import { discordCommon } from '../common'; + +export const discordAddRoleToMember = createAction({ + auth: discordAuth, + name: 'add_role_to_member', + description: 'Add Guild Member Role', + displayName: 'Add role to member', + props: { + guild_id: discordCommon.guilds, + user_id: Property.ShortText({ + displayName: 'User ID', + description: 'The user id of the member', + required: true, + }), + role_id: discordCommon.roles, + }, + + async run(configValue) { + const request: HttpRequest = { + method: HttpMethod.PUT, + url: `https://discord.com/api/v9/guilds/${configValue.propsValue.guild_id}/members/${configValue.propsValue.user_id}/roles/${configValue.propsValue.role_id}`, + headers: { + authorization: `Bot ${configValue.auth}`, + 'Content-Type': 'application/json', + }, + }; + + const res = await httpClient.sendRequest(request); + + return { + success: res.status === 204, + }; + }, +}); diff --git a/packages/pieces/community/discord/src/lib/actions/ban-a-guild-member.ts b/packages/pieces/community/discord/src/lib/actions/ban-a-guild-member.ts new file mode 100644 index 0000000..7471219 --- /dev/null +++ b/packages/pieces/community/discord/src/lib/actions/ban-a-guild-member.ts @@ -0,0 +1,49 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { discordAuth } from '../../index'; +import { discordCommon } from '../common'; + +export const discordBanGuildMember = createAction({ + auth: discordAuth, + name: 'ban_guild_member', + description: 'Bans a guild member', + displayName: 'Ban guild member', + props: { + guild_id: discordCommon.guilds, + user_id: Property.ShortText({ + displayName: 'User ID', + description: 'The user id of the member', + required: true, + }), + ban_reason: Property.ShortText({ + displayName: 'Ban Reason', + description: 'The reason for banning the member', + required: false, + }), + }, + + async run(configValue) { + const request: HttpRequest = { + method: HttpMethod.PUT, + url: `https://discord.com/api/v9/guilds/${configValue.propsValue.guild_id}/bans/${configValue.propsValue.user_id}`, + headers: { + authorization: `Bot ${configValue.auth}`, + 'Content-Type': 'application/json', + 'X-Audit-Log-Reason': `${configValue.propsValue.ban_reason}`, + }, + body: { + reason: `${configValue.propsValue.ban_reason}`, + }, + }; + + const res = await httpClient.sendRequest(request); + + return { + success: res.status === 204, + }; + }, +}); diff --git a/packages/pieces/community/discord/src/lib/actions/create-channel.ts b/packages/pieces/community/discord/src/lib/actions/create-channel.ts new file mode 100644 index 0000000..549a89d --- /dev/null +++ b/packages/pieces/community/discord/src/lib/actions/create-channel.ts @@ -0,0 +1,47 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { discordAuth } from '../../index'; +import { discordCommon } from '../common'; + +export const discordCreateChannel = createAction({ + auth: discordAuth, + name: 'create_channel', + description: 'create a channel', + displayName: 'Create channel', + props: { + guild_id: discordCommon.guilds, + name: Property.ShortText({ + displayName: 'Name', + description: 'The name of the new channel', + required: true, + }), + }, + + async run(configValue) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://discord.com/api/v9/guilds/${configValue.propsValue.guild_id}/channels`, + body: { + name: configValue.propsValue.name, + }, + headers: { + authorization: `Bot ${configValue.auth}`, + 'Content-Type': 'application/json', + }, + }; + + const res = await httpClient.sendRequest(request); + + return { + success: res.status === 201, + channel: { + id: res.body.id, + name: res.body.name, + }, + }; + }, +}); diff --git a/packages/pieces/community/discord/src/lib/actions/create-guild-role.ts b/packages/pieces/community/discord/src/lib/actions/create-guild-role.ts new file mode 100644 index 0000000..0f2e1d8 --- /dev/null +++ b/packages/pieces/community/discord/src/lib/actions/create-guild-role.ts @@ -0,0 +1,71 @@ +import { + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; +import { discordAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { discordCommon } from '../common'; + +export const discordCreateGuildRole = createAction({ + auth: discordAuth, + name: 'createGuildRole', + displayName: 'Create guild role', + description: 'Creates a new role on the specified guild', + props: { + guild_id: discordCommon.guilds, + role_name: Property.ShortText({ + displayName: 'Role Name', + description: 'The name of the role', + required: true, + }), + role_color: Property.ShortText({ + displayName: 'Role Color', + description: `The RGB color of the role (may be better to set manually on the server)`, + required: false, + }), + display_separated: Property.Checkbox({ + displayName: 'Display Separated', + description: + 'Whether the role should be displayed separately in the sidebar', + required: false, + }), + role_mentionable: Property.Checkbox({ + displayName: 'Mentionable', + description: 'Whether the role can be mentioned by other users', + required: false, + }), + creation_reason: Property.ShortText({ + displayName: 'Creation Reason', + description: 'The reason for creating the role', + required: false, + }), + }, + async run(configValue) { + const request: HttpRequest = { + url: `https://discord.com/api/v9/guilds/${configValue.propsValue.guild_id}/roles`, + method: HttpMethod.POST, + headers: { + authorization: `Bot ${configValue.auth}`, + 'Content-Type': 'application/json', + 'X-Audit-Log-Reason': `${configValue.propsValue.creation_reason}`, + }, + body: { + name: configValue.propsValue.role_name, + color: configValue.propsValue.role_color, + hoist: configValue.propsValue.display_separated, + mentionable: configValue.propsValue.role_mentionable, + }, + }; + + const res = await httpClient.sendRequest(request); + + return { + success: res.status === 201, + role: { + id: res.body.id, + name: res.body.name, + }, + }; + }, +}); diff --git a/packages/pieces/community/discord/src/lib/actions/delete-channel.ts b/packages/pieces/community/discord/src/lib/actions/delete-channel.ts new file mode 100644 index 0000000..9a7da16 --- /dev/null +++ b/packages/pieces/community/discord/src/lib/actions/delete-channel.ts @@ -0,0 +1,33 @@ +import { + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { discordAuth } from '../../index'; +import { discordCommon } from '../common'; + +export const discordDeleteChannel = createAction({ + auth: discordAuth, + name: 'delete_channel', + description: 'delete a channel', + displayName: 'Delete channel', + props: { + channel_id: discordCommon.channel, + }, + + async run(configValue) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `https://discord.com/api/v9/channels/${configValue.propsValue.channel_id}`, + headers: { + authorization: `Bot ${configValue.auth}`, + 'Content-Type': 'application/json', + }, + }; + + const res = await httpClient.sendRequest(request); + + return res.body; + }, +}); diff --git a/packages/pieces/community/discord/src/lib/actions/delete-guild-role.ts b/packages/pieces/community/discord/src/lib/actions/delete-guild-role.ts new file mode 100644 index 0000000..483f544 --- /dev/null +++ b/packages/pieces/community/discord/src/lib/actions/delete-guild-role.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { discordCommon } from '../common'; +import { discordAuth } from '../../index'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; + +export const discordDeleteGuildRole = createAction({ + auth: discordAuth, + name: 'deleteGuildRole', + displayName: 'Delete guild role', + description: 'Deletes the specified role from the specified guild', + props: { + guild_id: discordCommon.guilds, + role_id: discordCommon.roles, + deletion_reason: Property.ShortText({ + displayName: 'Deletion reason', + description: 'The reason for deleting the role', + required: false, + }), + }, + async run(configValue) { + const request: HttpRequest = { + url: `https://discord.com/api/v9/guilds/${configValue.propsValue.guild_id}/roles/${configValue.propsValue.role_id}`, + method: HttpMethod.DELETE, + headers: { + Authorization: `Bot ${configValue.auth}`, + 'Content-Type': 'application/json', + 'X-Audit-Log-Reason': `${configValue.propsValue.deletion_reason}`, + }, + }; + + const res = await httpClient.sendRequest(request); + + return { + success: res.status === 204, + }; + }, +}); diff --git a/packages/pieces/community/discord/src/lib/actions/find-channel.ts b/packages/pieces/community/discord/src/lib/actions/find-channel.ts new file mode 100644 index 0000000..2bc8307 --- /dev/null +++ b/packages/pieces/community/discord/src/lib/actions/find-channel.ts @@ -0,0 +1,46 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { discordAuth } from '../../index'; +import { discordCommon } from '../common'; +import { Channel } from '../common/models'; + +export const discordFindChannel = createAction({ + auth: discordAuth, + name: 'find_channel', + description: 'find a channel by name', + displayName: 'Find channel', + props: { + guild_id: discordCommon.guilds, + name: Property.ShortText({ + displayName: 'Name', + description: 'The name of the channel', + required: true, + }), + }, + + async run(configValue) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `https://discord.com/api/v9/guilds/${configValue.propsValue.guild_id}/channels`, + headers: { + authorization: `Bot ${configValue.auth}`, + 'Content-Type': 'application/json', + }, + }; + + const res = await httpClient.sendRequest(request); + + const channel = res.body.find( + (channel) => channel.name === configValue.propsValue.name + ); + + return { + success: res.status === 200 && !!channel, + channel_id: channel?.id, + }; + }, +}); diff --git a/packages/pieces/community/discord/src/lib/actions/find-guild-member.ts b/packages/pieces/community/discord/src/lib/actions/find-guild-member.ts new file mode 100644 index 0000000..fbf8c7a --- /dev/null +++ b/packages/pieces/community/discord/src/lib/actions/find-guild-member.ts @@ -0,0 +1,59 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { discordAuth } from '../../index'; +import { discordCommon } from '../common'; +import { Member } from '../common/models'; + +export const discordFindGuildMemberByUsername = createAction({ + auth: discordAuth, + name: 'list_guild_members', + description: 'List Guild Members', + displayName: 'List guild members', + props: { + guild_id: discordCommon.guilds, + shortText: Property.ShortText({ + displayName: 'Search', + description: 'Search for a member', + required: true, + }), + }, + + async run(configValue) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `https://discord.com/api/v9/guilds/${configValue.propsValue.guild_id}/members`, + headers: { + authorization: `Bot ${configValue.auth}`, + 'Content-Type': 'application/json', + }, + }; + + const res = await httpClient.sendRequest(request); + + const options: { options: { value: string; label: string }[] } = { + options: [], + }; + + if (res.body.length === 0) + return { + disabled: true, + options: [], + placeholder: 'No members found, please add the bot to a guild first', + }; + + await Promise.all( + res.body.map(async (member) => { + options.options.push({ + value: member.user.id, + label: member.user.username, + }); + }) + ); + + return options; + }, +}); diff --git a/packages/pieces/community/discord/src/lib/actions/remove-ban-from-user.ts b/packages/pieces/community/discord/src/lib/actions/remove-ban-from-user.ts new file mode 100644 index 0000000..cea6407 --- /dev/null +++ b/packages/pieces/community/discord/src/lib/actions/remove-ban-from-user.ts @@ -0,0 +1,48 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { discordCommon } from '../common'; +import { discordAuth } from '../../index'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; + +export const discordRemoveBanFromUser = createAction({ + auth: discordAuth, + name: 'remove_ban_from_user', + displayName: 'Remove ban from user', + description: 'Removes the guild ban from a user', + props: { + guild_id: discordCommon.guilds, + user_id: Property.ShortText({ + displayName: 'User ID', + description: 'The ID of the user', + required: true, + }), + unban_reason: Property.ShortText({ + displayName: 'Unban Reason', + description: 'The reason for unbanning the user', + required: false, + }), + }, + async run(configValue) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `https://discord.com/api/v9/guilds/${configValue.propsValue.guild_id}/bans/${configValue.propsValue.user_id}`, + headers: { + authorization: `Bot ${configValue.auth}`, + 'Content-Type': 'application/json', + 'X-Audit-Log-Reason': `${configValue.propsValue.unban_reason}`, + }, + body: { + reason: `${configValue.propsValue.unban_reason}`, + }, + }; + + const res = await httpClient.sendRequest(request); + + return { + success: res.status === 204, + }; + }, +}); diff --git a/packages/pieces/community/discord/src/lib/actions/remove-member-from-guild.ts b/packages/pieces/community/discord/src/lib/actions/remove-member-from-guild.ts new file mode 100644 index 0000000..5c208bb --- /dev/null +++ b/packages/pieces/community/discord/src/lib/actions/remove-member-from-guild.ts @@ -0,0 +1,40 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { discordAuth } from '../../index'; +import { discordCommon } from '../common'; + +export const discordRemoveMemberFromGuild = createAction({ + auth: discordAuth, + name: 'remove_member_from_guild', + description: 'Remove Guild Member', + displayName: 'Remove member from guild', + props: { + guild_id: discordCommon.guilds, + user_id: Property.ShortText({ + displayName: 'User ID', + description: 'The user id of the member', + required: true, + }), + }, + + async run(configValue) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `https://discord.com/api/v9/guilds/${configValue.propsValue.guild_id}/members/${configValue.propsValue.user_id}`, + headers: { + authorization: `Bot ${configValue.auth}`, + 'Content-Type': 'application/json', + }, + }; + + const res = await httpClient.sendRequest(request); + + return { + success: res.status === 204, + }; + }, +}); diff --git a/packages/pieces/community/discord/src/lib/actions/remove-role-from-member.ts b/packages/pieces/community/discord/src/lib/actions/remove-role-from-member.ts new file mode 100644 index 0000000..315c24c --- /dev/null +++ b/packages/pieces/community/discord/src/lib/actions/remove-role-from-member.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { discordAuth } from '../../index'; +import { discordCommon } from '../common'; + +export const discordRemoveRoleFromMember = createAction({ + auth: discordAuth, + name: 'remove_role_from_member', + description: 'Remove Guild Member Role', + displayName: 'Remove role from member', + props: { + guild_id: discordCommon.guilds, + user_id: Property.ShortText({ + displayName: 'User ID', + description: 'The user id of the member', + required: true, + }), + role_id: discordCommon.roles, + }, + + async run(configValue) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `https://discord.com/api/v9/guilds/${configValue.propsValue.guild_id}/members/${configValue.propsValue.user_id}/roles/${configValue.propsValue.role_id}`, + headers: { + authorization: `Bot ${configValue.auth}`, + 'Content-Type': 'application/json', + }, + }; + + const res = await httpClient.sendRequest(request); + + return { + success: res.status === 204, + }; + }, +}); diff --git a/packages/pieces/community/discord/src/lib/actions/rename-channel.ts b/packages/pieces/community/discord/src/lib/actions/rename-channel.ts new file mode 100644 index 0000000..5add8a8 --- /dev/null +++ b/packages/pieces/community/discord/src/lib/actions/rename-channel.ts @@ -0,0 +1,43 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { discordAuth } from '../../index'; +import { discordCommon } from '../common'; + +export const discordRenameChannel = createAction({ + auth: discordAuth, + name: 'rename_channel', + description: 'rename a channel', + displayName: 'Rename channel', + props: { + channel_id: discordCommon.channel, + name: Property.ShortText({ + displayName: 'Name', + description: 'The new name of the channel', + required: true, + }), + }, + + async run(configValue) { + const request: HttpRequest = { + method: HttpMethod.PATCH, + url: `https://discord.com/api/v9/channels/${configValue.propsValue.channel_id}`, + body: { + name: configValue.propsValue.name, + }, + headers: { + authorization: `Bot ${configValue.auth}`, + 'Content-Type': 'application/json', + }, + }; + + const res = await httpClient.sendRequest(request); + + return { + success: res.status === 204, + }; + }, +}); diff --git a/packages/pieces/community/discord/src/lib/actions/send-approval-message.ts b/packages/pieces/community/discord/src/lib/actions/send-approval-message.ts new file mode 100644 index 0000000..28d3a11 --- /dev/null +++ b/packages/pieces/community/discord/src/lib/actions/send-approval-message.ts @@ -0,0 +1,82 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { discordAuth } from '../../index'; +import { ExecutionType, PauseType } from '@activepieces/shared'; +import { discordCommon } from '../common'; + +export const discordSendApprovalMessage = createAction({ + auth: discordAuth, + name: 'request_approval_message', + description: + 'send a message to a channel asking for approval and wait for a response', + displayName: 'Request Approval in a Channel', + props: { + content: Property.LongText({ + displayName: 'Message', + description: 'The message you want to send', + required: true, + }), + channel: discordCommon.channel, + }, + async run(ctx) { + if (ctx.executionType === ExecutionType.BEGIN) { + ctx.run.pause({ + pauseMetadata: { + type: PauseType.WEBHOOK, + response: {}, + }, + }); + + const approvalLink = ctx.generateResumeUrl({ + queryParams: { action: 'approve' }, + }) + const disapprovalLink = ctx.generateResumeUrl({ + queryParams: { action: 'disapprove' }, + }) + + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://discord.com/api/v9/channels/${ctx.propsValue.channel}/messages`, + body: { + content: ctx.propsValue.content, + components: [ + { + type: 1, + components: [ + { + type: 2, + label: 'Approve', + style: 5, + url: approvalLink, + }, + { + type: 2, + label: 'Disapprove', + style: 5, + url: disapprovalLink, + }, + ], + }, + ], + }, + headers: { + authorization: `Bot ${ctx.auth}`, + 'Content-Type': 'application/json', + }, + }; + + await httpClient.sendRequest(request); + return {}; + } else { + + return { + approved: ctx.resumePayload.queryParams['action'] === 'approve', + }; + } + }, +}); diff --git a/packages/pieces/community/discord/src/lib/actions/send-message-webhook.ts b/packages/pieces/community/discord/src/lib/actions/send-message-webhook.ts new file mode 100644 index 0000000..4816448 --- /dev/null +++ b/packages/pieces/community/discord/src/lib/actions/send-message-webhook.ts @@ -0,0 +1,63 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; + +export const discordSendMessageWebhook = createAction({ + name: 'send_message_webhook', + description: 'Send a discord message via webhook', + displayName: 'Send Message Webhook', + requireAuth: false, + props: { + webhook_url: Property.ShortText({ + displayName: 'Webhook URL', + required: true, + }), + username: Property.ShortText({ + displayName: 'Name', + required: false, + }), + content: Property.LongText({ + displayName: 'Message', + required: true, + }), + avatar_url: Property.ShortText({ + displayName: 'Avatar URL', + description: 'The avatar url for webhook', + required: false, + }), + embeds: Property.Json({ + displayName: 'embeds', + description: 'Embeds to send along with the message', + required: false, + defaultValue: [], + }), + tts: Property.Checkbox({ + displayName: 'Text to speech', + description: 'Robot reads the message', + required: false, + }), + }, + async run(configValue) { + const request: HttpRequest<{ + content: string; + username: string | undefined; + avatar_url: string | undefined; + tts: boolean | undefined; + embeds: Record | undefined; + }> = { + method: HttpMethod.POST, + url: configValue.propsValue['webhook_url'], + body: { + username: configValue.propsValue['username'], + content: configValue.propsValue['content'], + avatar_url: configValue.propsValue['avatar_url'], + tts: configValue.propsValue['tts'], + embeds: configValue.propsValue['embeds'], + }, + }; + return await httpClient.sendRequest(request); + }, +}); diff --git a/packages/pieces/community/discord/src/lib/actions/send-message-with-bot.ts b/packages/pieces/community/discord/src/lib/actions/send-message-with-bot.ts new file mode 100644 index 0000000..24b4c63 --- /dev/null +++ b/packages/pieces/community/discord/src/lib/actions/send-message-with-bot.ts @@ -0,0 +1,71 @@ +import { ApFile, createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { discordAuth } from '../../index'; +import { discordCommon } from '../common'; +import FormData from 'form-data'; + +interface FileObject { + file:ApFile +} + +export const sendMessageWithBot = createAction({ + name: 'sendMessageWithBot', + auth:discordAuth, + displayName: 'Send Message with Bot', + description: + 'Send messages via bot to any channel or thread you want, with an optional file attachment.', + props: { + channel_id: discordCommon.channel, + message: Property.LongText({ + displayName: 'Message', + description: 'Message content to send.', + required: false, + }), + files: Property.Array({ + displayName: 'Attachments', + properties: { + file: Property.File({ + displayName: 'File', + description: 'Optional file to send with the message.', + required: false, + }), + }, + required: false, + defaultValue: [], + }), + }, + async run(configValue) { + const channelId = configValue.propsValue.channel_id; + const message = configValue.propsValue.message; + const files = configValue.propsValue.files as FileObject[] ?? []; + + const formData = new FormData(); + formData.append('content', message); + + if (files && files.length > 0) { + files.forEach((fileObj, index) => { + const file = fileObj.file; + formData.append(`files[${index}]`, file.data, file.filename); + }); + } + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://discord.com/api/v10/channels/${channelId}/messages`, + headers: { + authorization: `Bot ${configValue.auth}`, + 'Content-Type': 'multipart/form-data', + }, + body: formData, + }; + + const res = await httpClient.sendRequest(request); + + return res.body; + } + +}); diff --git a/packages/pieces/community/discord/src/lib/common/index.ts b/packages/pieces/community/discord/src/lib/common/index.ts new file mode 100644 index 0000000..c885d25 --- /dev/null +++ b/packages/pieces/community/discord/src/lib/common/index.ts @@ -0,0 +1,173 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { Channel, Guild } from '../common/models'; +import { Property } from '@activepieces/pieces-framework'; + +export interface Member { + user: { + id: string; + username: string; + }; +} + +export const discordCommon = { + channel: Property.Dropdown({ + displayName: 'Channel', + description: 'List of channels', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your bot first', + }; + } + + const request = { + method: HttpMethod.GET, + url: 'https://discord.com/api/v9/users/@me/guilds', + headers: { + Authorization: 'Bot ' + auth, + }, + }; + + const res = await httpClient.sendRequest(request); + const options: { options: { value: string; label: string }[] } = { + options: [], + }; + + if (res.body.length === 0) + return { + disabled: true, + options: [], + placeholder: 'No guilds found, please add the bot to a guild first', + }; + + await Promise.all( + res.body.map(async (guild) => { + const requestChannels = { + method: HttpMethod.GET, + url: 'https://discord.com/api/v9/guilds/' + guild.id + '/channels', + headers: { + Authorization: 'Bot ' + auth, + }, + }; + + const resChannels = await httpClient.sendRequest( + requestChannels + ); + resChannels.body.forEach((channel) => { + options.options.push({ + value: channel.id, + label: channel.name, + }); + }); + }) + ); + + return options; + }, + }), + roles: Property.Dropdown({ + displayName: 'Roles', + description: 'List of roles', + required: true, + refreshers: ['guild_id'], + options: async ({ auth, guild_id }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your bot first', + }; + } + + if (!guild_id) { + return { + disabled: true, + options: [], + placeholder: 'Please select a guild first', + }; + } + + const request = { + method: HttpMethod.GET, + url: `https://discord.com/api/v9/guilds/${guild_id}/roles`, + headers: { + Authorization: 'Bot ' + auth, + }, + }; + + const res = await httpClient.sendRequest(request); + + const options: { options: { value: string; label: string }[] } = { + options: [], + }; + + if (res.body.length === 0) + return { + disabled: true, + options: [], + placeholder: 'No roles found, please add the bot to a guild first', + }; + + await Promise.all( + res.body.map(async (role) => { + options.options.push({ + value: role.id, + label: role.name, + }); + }) + ); + + return options; + }, + }), + guilds: Property.Dropdown({ + displayName: 'Guilds', + description: 'List of guilds', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your bot first', + }; + } + + const request = { + method: HttpMethod.GET, + url: 'https://discord.com/api/v9/users/@me/guilds', + headers: { + Authorization: 'Bot ' + auth, + }, + }; + + const res = await httpClient.sendRequest(request); + const options: { options: { value: string; label: string }[] } = { + options: [], + }; + + if (res.body.length === 0) + return { + disabled: true, + options: [], + placeholder: 'No guilds found, please add the bot to a guild first', + }; + + await Promise.all( + res.body.map(async (guild) => { + options.options.push({ + value: guild.id, + label: guild.name, + }); + }) + ); + + return options; + }, + }), +}; diff --git a/packages/pieces/community/discord/src/lib/common/models.ts b/packages/pieces/community/discord/src/lib/common/models.ts new file mode 100644 index 0000000..f9714ba --- /dev/null +++ b/packages/pieces/community/discord/src/lib/common/models.ts @@ -0,0 +1,42 @@ +export interface Guild { + id: string; + name: string; + icon: string | null; + owner: boolean; + permissions: string; + features: string[]; +} + +export interface Channel { + id: string; + name: string; +} + +export interface Member { + user: { + id: string; + username: string; + }; +} + +export interface Message { + id: string; + type: number; + content: string; + channel_id: string; + author: { + id: string; + username: string; + }; + attachments: any; + embeds: any; + mentions: any; + mention_roles: any; + pinned: boolean; + mention_everyone: boolean; + tts: boolean; + timestamp: string; + edited_timestamp: string | null; + flags: number; + components: any; +} diff --git a/packages/pieces/community/discord/src/lib/discord.mdx b/packages/pieces/community/discord/src/lib/discord.mdx new file mode 100644 index 0000000..153bfcf --- /dev/null +++ b/packages/pieces/community/discord/src/lib/discord.mdx @@ -0,0 +1,35 @@ +--- +title: 'Discord' +description: '' +--- + +## Obtain Webhook URL for Sending Message + +1. Go to the Discord server where you want to add the webhook. +2. Click on the server settings icon (it looks like a gear). +3. Scroll down to the "Integrations" section and click on the "Webhooks". +4. Fill in the name and avatar for your webhook, and select the channel you want the webhook to post to. +5. Click on the "Create" button. +6. **Copy the webhook URL** that is generated. This is the URL you will use to send messages to the Discord channel through your bot. +7. **Make sure to keep the webhook URL private and only share it with trusted parties**. +8. Once you have the webhook URL you can use it in activepieces. + +## Options + +Username (Optional): The display name for the webhook. +Content: The message content to send. +Avatar_url (Optional): The avatar url for the webhook. +Embeds (Optional): Embeds to send with the webhook message, please refer to [this](https://birdie0.github.io/discord-webhooks-guide/structure/username.html). +TTS (Optional): Make a robotic voice to read the message out loud. + +--- + +## Triggers + +TRIGGERS + +--- + +## Actions + +ACTIONS diff --git a/packages/pieces/community/discord/src/lib/triggers/new-member.ts b/packages/pieces/community/discord/src/lib/triggers/new-member.ts new file mode 100644 index 0000000..a2bbdf9 --- /dev/null +++ b/packages/pieces/community/discord/src/lib/triggers/new-member.ts @@ -0,0 +1,114 @@ +import { + DedupeStrategy, + HttpMethod, + HttpRequest, + Polling, + httpClient, + pollingHelper, +} from '@activepieces/pieces-common'; +import { + createTrigger, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import { discordAuth } from '../..'; +import { discordCommon } from '../common'; + +interface Member { + user: { + id: string; + username: string; + discriminator: string; + avatar: string | null; + }; + joined_at: string; +} + +const polling: Polling = + { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue: { guildId, limit } }) => { + if (!guildId) return []; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `https://discord.com/api/v9/guilds/${guildId}/members?limit=${limit}`, + headers: { + Authorization: 'Bot ' + auth, + }, + }; + + const res = await httpClient.sendRequest(request); + + const items = res.body; + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.joined_at).valueOf(), + data: item, + })); + }, + }; + +export const newMember = createTrigger({ + auth: discordAuth, + name: 'new_member', + displayName: 'New Member', + description: 'Triggers when a new member joins a guild', + type: TriggerStrategy.POLLING, + props: { + limit: Property.Number({ + displayName: 'Limit', + description: 'The number of members to fetch (max 1000)', + required: false, + defaultValue: 50, + }), + guildId: Property.ShortText({ + displayName: 'Guild ID', + description: 'The ID of the Discord guild (server)', + required: true, + }), + }, + sampleData: {}, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: { + guildId: context.propsValue.guildId, + limit: context.propsValue.limit ?? 50, + }, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: { + guildId: context.propsValue.guildId, + limit: context.propsValue.limit ?? 50, + }, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + propsValue: { + guildId: context.propsValue.guildId, + limit: context.propsValue.limit ?? 50, + }, + files: context.files, + }); + }, + test: async (context) => { + return await pollingHelper.test(polling, { + auth: context.auth, + store: context.store, + propsValue: { + guildId: context.propsValue.guildId, + limit: context.propsValue.limit ?? 50, + }, + files: context.files, + }); + }, +}); diff --git a/packages/pieces/community/discord/src/lib/triggers/new-message.ts b/packages/pieces/community/discord/src/lib/triggers/new-message.ts new file mode 100644 index 0000000..f99fdd3 --- /dev/null +++ b/packages/pieces/community/discord/src/lib/triggers/new-message.ts @@ -0,0 +1,106 @@ +import { + DedupeStrategy, + HttpMethod, + HttpRequest, + Polling, + httpClient, + pollingHelper, +} from '@activepieces/pieces-common'; +import { + createTrigger, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import { discordAuth } from '../..'; +import { discordCommon } from '../common'; + +import { Message } from '../common/models'; + +const polling: Polling = + { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue: { channel, limit } }) => { + if (channel === undefined) return []; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: + 'https://discord.com/api/v9/channels/' + + channel + + '/messages?limit=' + + limit, + headers: { + Authorization: 'Bot ' + auth, + }, + }; + + const res = await httpClient.sendRequest(request); + + const items = res.body; + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.timestamp).valueOf(), + data: item, + })); + }, + }; + +export const newMessage = createTrigger({ + auth: discordAuth, + name: 'new_message', + displayName: 'New message', + description: 'Triggers when a message is sent in a channel', + type: TriggerStrategy.POLLING, + props: { + limit: Property.Number({ + displayName: 'Limit', + description: 'The number of messages to fetch', + required: false, + defaultValue: 50, + }), + channel: discordCommon.channel, + }, + sampleData: {}, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: { + channel: context.propsValue.channel, + limit: context.propsValue.limit ?? 50, + }, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: { + channel: context.propsValue.channel, + limit: context.propsValue.limit ?? 50, + }, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + propsValue: { + channel: context.propsValue.channel, + limit: context.propsValue.limit ?? 50, + }, + files: context.files, + }); + }, + test: async (context) => { + return await pollingHelper.test(polling, { + auth: context.auth, + store: context.store, + propsValue: { + channel: context.propsValue.channel, + limit: context.propsValue.limit ?? 50, + }, + files: context.files, + }); + }, +}); diff --git a/packages/pieces/community/discord/tsconfig.json b/packages/pieces/community/discord/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/discord/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/discord/tsconfig.lib.json b/packages/pieces/community/discord/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/discord/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/discourse/.eslintrc.json b/packages/pieces/community/discourse/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/discourse/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/discourse/README.md b/packages/pieces/community/discourse/README.md new file mode 100644 index 0000000..c6e4f70 --- /dev/null +++ b/packages/pieces/community/discourse/README.md @@ -0,0 +1,7 @@ +# pieces-discourse + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-discourse` to build the library. diff --git a/packages/pieces/community/discourse/package.json b/packages/pieces/community/discourse/package.json new file mode 100644 index 0000000..0bcb351 --- /dev/null +++ b/packages/pieces/community/discourse/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-discourse", + "version": "0.0.5" +} \ No newline at end of file diff --git a/packages/pieces/community/discourse/project.json b/packages/pieces/community/discourse/project.json new file mode 100644 index 0000000..3aec5cb --- /dev/null +++ b/packages/pieces/community/discourse/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-discourse", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/discourse/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/discourse", + "tsConfig": "packages/pieces/community/discourse/tsconfig.lib.json", + "packageJson": "packages/pieces/community/discourse/package.json", + "main": "packages/pieces/community/discourse/src/index.ts", + "assets": [ + "packages/pieces/community/discourse/*.md", + { + "input": "packages/pieces/community/discourse/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-discourse {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/discourse/src/index.ts b/packages/pieces/community/discourse/src/index.ts new file mode 100644 index 0000000..82c345c --- /dev/null +++ b/packages/pieces/community/discourse/src/index.ts @@ -0,0 +1,63 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { addUsersToGroup } from './lib/actions/add-users-to-group.action'; +import { changeUserTrustLevel } from './lib/actions/change-trust-level.action'; +import { createPost } from './lib/actions/create-post.action'; +import { createTopic } from './lib/actions/create-topic.action'; +import { sendPrivateMessage } from './lib/actions/send-private-message.action'; + +const markdownPropertyDescription = ` +*Get your api Key: https://discourse.yourinstance.com/admin/api/keys +`; + +export const discourseAuth = PieceAuth.CustomAuth({ + description: markdownPropertyDescription, + required: true, + props: { + api_key: PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + }), + api_username: Property.ShortText({ + displayName: 'API Username', + required: true, + }), + website_url: Property.ShortText({ + displayName: 'Website URL', + required: true, + description: + 'URL of the discourse url i.e https://discourse.yourinstance.com', + }), + }, +}); + +export const discourse = createPiece({ + displayName: 'Discourse', + description: 'Modern open source forum software', + auth: discourseAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/discourse.png', + categories: [PieceCategory.COMMUNICATION], + authors: ["pfernandez98","kishanprmr","MoShizzle","abuaboud"], + actions: [ + createPost, + createTopic, + changeUserTrustLevel, + addUsersToGroup, + sendPrivateMessage, + createCustomApiCallAction({ + baseUrl: (auth) => (auth as { website_url: string }).website_url.trim(), // Replace with the actual base URL + auth: discourseAuth, + authMapping: async (auth) => ({ + 'Api-Key': (auth as { api_key: string }).api_key, + 'Api-Username': (auth as { api_username: string }).api_username, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/discourse/src/lib/actions/add-users-to-group.action.ts b/packages/pieces/community/discourse/src/lib/actions/add-users-to-group.action.ts new file mode 100644 index 0000000..f31811d --- /dev/null +++ b/packages/pieces/community/discourse/src/lib/actions/add-users-to-group.action.ts @@ -0,0 +1,63 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { discourseAuth } from '../../index'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { Property, createAction } from '@activepieces/pieces-framework'; + +export const addUsersToGroup = createAction({ + auth: discourseAuth, + name: 'add_users_to_group', + description: 'Add users to a group', + displayName: 'Add Users to Group', + props: { + group_id: Property.Dropdown({ + description: 'Id of the group', + displayName: 'Group Id', + required: true, + refreshers: [], + options: async ({ auth }: any) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${auth.website_url.trim()}/groups.json`, + headers: { + 'Api-Key': auth.api_key, + 'Api-Username': auth.api_username, + }, + }); + const options = response.body['groups'].map( + (res: { display_name: any; id: any }) => { + return { + label: res.display_name, + value: res.id, + }; + } + ); + return { + options: options, + disabled: false, + }; + }, + }), + users: Property.Array({ + description: 'List of users to add to the group', + displayName: 'Users', + required: true, + }), + }, + async run(context) { + const { group_id, users } = context.propsValue; + //convert array to comma separated string + users.join(','); + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: `${context.auth.website_url.trim()}/groups/${group_id}/members.json`, + headers: { + 'Api-Key': context.auth.api_key, + 'Api-Username': context.auth.api_username, + }, + body: { + usernames: users.join(','), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/discourse/src/lib/actions/change-trust-level.action.ts b/packages/pieces/community/discourse/src/lib/actions/change-trust-level.action.ts new file mode 100644 index 0000000..fc9490a --- /dev/null +++ b/packages/pieces/community/discourse/src/lib/actions/change-trust-level.action.ts @@ -0,0 +1,65 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { discourseAuth } from '../../index'; +import { Property, createAction } from '@activepieces/pieces-framework'; + +export const changeUserTrustLevel = createAction({ + auth: discourseAuth, + name: 'change_user_trust_level', + description: 'Change the trust level of a user', + displayName: 'Change User Trust Level', + props: { + user_id: Property.ShortText({ + description: 'ID of the user', + displayName: 'User ID', + required: true, + }), + new_trust_level: Property.Dropdown({ + description: 'New trust level of the user', + displayName: 'New Trust Level', + required: true, + options: async ({ auth }: any) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${auth.website_url.trim()}/site.json`, + headers: { + 'Api-Key': auth.api_key, + 'Api-Username': auth.api_username, + }, + }); + + const result: { name: string; value: number }[] = []; + const trust_levels = response.body['trust_levels']; + for (const key in trust_levels) { + result.push({ name: key, value: trust_levels[key] }); + } + + const options = result.map((res) => { + return { + label: res.name, + value: res.value, + }; + }); + return { + options: options, + disabled: false, + }; + }, + refreshers: [], + }), + }, + async run(context) { + const { user_id, new_trust_level } = context.propsValue; + return await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: `${context.auth.website_url.trim()}/u/${user_id}.json`, + headers: { + 'Api-Key': context.auth.api_key, + 'Api-Username': context.auth.api_username, + }, + body: { + trust_level: new_trust_level, + }, + }); + }, +}); diff --git a/packages/pieces/community/discourse/src/lib/actions/create-post.action.ts b/packages/pieces/community/discourse/src/lib/actions/create-post.action.ts new file mode 100644 index 0000000..9b804c7 --- /dev/null +++ b/packages/pieces/community/discourse/src/lib/actions/create-post.action.ts @@ -0,0 +1,63 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { discourseAuth } from '../../index'; +import { Property, createAction } from '@activepieces/pieces-framework'; + +export const createPost = createAction({ + auth: discourseAuth, + name: 'create_post', + description: 'Create a new post in discourse', + displayName: 'Create Post', + props: { + raw: Property.LongText({ + description: 'Content of the post', + displayName: 'Post Content', + required: true, + }), + topic_id: Property.Dropdown({ + description: 'ID of the topic to post in', + displayName: 'Topic ID', + required: true, + options: async ({ auth }: any) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${auth.website_url.trim()}/latest.json`, + headers: { + 'Api-Key': auth.api_key, + 'Api-Username': auth.api_username, + }, + }); + const options = response.body['topic_list']['topics'].map( + (res: { title: any; id: any }) => { + return { + label: res.title, + value: res.id, + }; + } + ); + + return { + options: options, + disabled: false, + }; + }, + refreshers: [], + }), + }, + async run(context) { + const { raw, topic_id } = context.propsValue; + + return await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.website_url.trim()}/posts.json`, + headers: { + 'Api-Key': context.auth.api_key, + 'Api-Username': context.auth.api_username, + }, + body: { + raw: raw, + topic_id: topic_id, + }, + }); + }, +}); diff --git a/packages/pieces/community/discourse/src/lib/actions/create-topic.action.ts b/packages/pieces/community/discourse/src/lib/actions/create-topic.action.ts new file mode 100644 index 0000000..a843856 --- /dev/null +++ b/packages/pieces/community/discourse/src/lib/actions/create-topic.action.ts @@ -0,0 +1,71 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { discourseAuth } from '../../index'; +import { Property, createAction } from '@activepieces/pieces-framework'; + +export const createTopic = createAction({ + auth: discourseAuth, + name: 'create_topic', + description: 'Create a new topic in Discourse', + displayName: 'Create Topic', + props: { + title: Property.ShortText({ + description: 'Title of the Topic', + displayName: 'Post Title', + required: true, + }), + raw: Property.LongText({ + description: 'Content of the topic', + displayName: 'Topic Content', + required: true, + }), + category: Property.Dropdown({ + description: 'ID of the category to post in', + displayName: 'Category ID', + required: false, + options: async ({ auth }: any) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${auth.website_url.trim()}/categories.json`, + headers: { + 'Api-Key': auth.api_key, + 'Api-Username': auth.api_username, + }, + }); + const options = response.body['category_list']['categories'].map( + (res: { name: any; id: any }) => { + return { + label: res.name, + value: res.id, + }; + } + ); + + return { + options: options, + disabled: false, + }; + }, + refreshers: [], + }), + }, + async run(context) { + const { title, raw, category } = context.propsValue; + + console.log('new post action'); + + return await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.website_url.trim()}/posts.json`, + headers: { + 'Api-Key': context.auth.api_key, + 'Api-Username': context.auth.api_username, + }, + body: { + title: title, + raw: raw, + category: category, + }, + }); + }, +}); diff --git a/packages/pieces/community/discourse/src/lib/actions/send-private-message.action.ts b/packages/pieces/community/discourse/src/lib/actions/send-private-message.action.ts new file mode 100644 index 0000000..7cbe08b --- /dev/null +++ b/packages/pieces/community/discourse/src/lib/actions/send-private-message.action.ts @@ -0,0 +1,46 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { discourseAuth } from '../../index'; +import { Property, createAction } from '@activepieces/pieces-framework'; + +export const sendPrivateMessage = createAction({ + auth: discourseAuth, + name: 'send_private_message', + description: 'Send a private message in Discourse', + displayName: 'Send Private Message', + props: { + title: Property.ShortText({ + description: 'Title for the PM', + displayName: 'Post Title', + required: true, + }), + raw: Property.LongText({ + description: 'Content of the post', + displayName: 'Post Content', + required: true, + }), + target_recipients: Property.Array({ + description: 'List of users to send the PM to (can be one or more)', + displayName: 'Users', + required: true, + }), + }, + async run(context) { + const { title, raw, target_recipients } = context.propsValue; + + return await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.website_url.trim()}/posts.json`, + headers: { + 'Api-Key': context.auth.api_key, + 'Api-Username': context.auth.api_username, + }, + body: { + raw: raw, + title: title, + target_recipients: target_recipients.join(','), + archetype: 'private_message', + }, + }); + }, +}); diff --git a/packages/pieces/community/discourse/tsconfig.json b/packages/pieces/community/discourse/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/discourse/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/discourse/tsconfig.lib.json b/packages/pieces/community/discourse/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/discourse/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/docusign/.eslintrc.json b/packages/pieces/community/docusign/.eslintrc.json new file mode 100644 index 0000000..d7449bb --- /dev/null +++ b/packages/pieces/community/docusign/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {}, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/docusign/README.md b/packages/pieces/community/docusign/README.md new file mode 100644 index 0000000..53a4b80 --- /dev/null +++ b/packages/pieces/community/docusign/README.md @@ -0,0 +1,7 @@ +# pieces-docusign + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-docusign` to build the library. diff --git a/packages/pieces/community/docusign/package.json b/packages/pieces/community/docusign/package.json new file mode 100644 index 0000000..8d77c18 --- /dev/null +++ b/packages/pieces/community/docusign/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-docusign", + "version": "0.0.3" +} diff --git a/packages/pieces/community/docusign/project.json b/packages/pieces/community/docusign/project.json new file mode 100644 index 0000000..2943db3 --- /dev/null +++ b/packages/pieces/community/docusign/project.json @@ -0,0 +1,45 @@ +{ + "name": "pieces-docusign", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/docusign/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/docusign", + "tsConfig": "packages/pieces/community/docusign/tsconfig.lib.json", + "packageJson": "packages/pieces/community/docusign/package.json", + "main": "packages/pieces/community/docusign/src/index.ts", + "assets": [ + "packages/pieces/community/docusign/*.md" + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/docusign/src/index.ts b/packages/pieces/community/docusign/src/index.ts new file mode 100644 index 0000000..3c25464 --- /dev/null +++ b/packages/pieces/community/docusign/src/index.ts @@ -0,0 +1,136 @@ +import { AxiosError } from 'axios'; + +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; + +import { createApiClient } from './lib/common'; +import { listEnvelopes } from './lib/actions/list-envelopes'; +import { getEnvelope } from './lib/actions/get-envelope'; +import { getDocument } from './lib/actions/get-document'; + +export const docusignAuth = PieceAuth.CustomAuth({ + required: true, + props: { + clientId: Property.ShortText({ + displayName: 'Integration key / Client ID', + description: + 'This can be obtained in your developer account from the page. See the https://support.docusign.com/guides/ndse-admin-guide-api-and-keys|Docusign eSignature Admin Guide> for more information.', + required: true, + }), + privateKey: PieceAuth.SecretText({ + displayName: 'RSA private key', + description: + 'This is for the integration key you obtained above and can also be created on the page. You only need the private key, and it can only be copied once. Make sure to retain it for your records.', + required: true, + }), + environment: Property.StaticDropdown({ + displayName: 'Environment', + required: true, + options: { + options: [ + { label: 'Demo / Test', value: 'demo' }, + { label: 'US production', value: 'www' }, + { label: 'EU production', value: 'eu' }, + ], + }, + }), + impersonatedUserId: Property.ShortText({ + displayName: 'Impersonated user ID', + description: + 'This is a GUID identifying the Docusign user that you will be impersonating with the access token. Your own User ID can be found at the top of the page', + required: true, + }), + scopes: Property.ShortText({ + displayName: 'scopes', + required: true, + description: + 'Comma-separated list of scopes. These represent the OAuth scopes (permissions) that are being requested. For eSignature REST API methods, use the signature scope. The impersonation scope is implied by the JWT Grant operation and does not need to be included. If the access token will be used for other Docusign APIs, additional scopes may be required; see each API’s requirements', + }), + }, + validate: async ({ auth, server }) => { + try { + await createApiClient(auth as DocusignAuthType); + return { + valid: true, + }; + } catch (error) { + if ( + error instanceof AxiosError && + error.response && + error.response.status === 400 && + error.response.data && + error.response.data.error === 'consent_required' + ) { + const formattedScopes = auth.scopes.split(',').join(encodeURI(' ')); + const oAuthBasePath = + auth.environment === 'demo' + ? 'account-d.docusign.com' + : 'account.docusign.com'; + + // We don't use the built-in getAuthorizationUri method from docusign + // because it currently limits the scopes that can be requested to signature, extended, and impersonation + const consentUrl = + 'https://' + + oAuthBasePath + + '/oauth/auth' + + '?response_type=code' + + '&scope=' + + formattedScopes + + '&client_id=' + + auth.clientId + + '&redirect_uri=' + + encodeURIComponent( + `${server.publicUrl.replace('/api', '')}/redirect` + ); + return { + valid: false, + error: + 'Consent is required, please visit this URL and grant consent: ' + + consentUrl, + }; + } + return { + valid: false, + error: 'Invalid connection: ' + error, + }; + } + }, +}); + +export type DocusignAuthType = { + clientId: string; + privateKey: string; + environment: 'demo' | 'www' | 'eu'; + impersonatedUserId: string; + scopes: string; +}; + +export const docusign = createPiece({ + displayName: 'Docusign', + auth: docusignAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/docusign.png', + authors: ['AdamSelene'], + actions: [ + listEnvelopes, + getEnvelope, + getDocument, + createCustomApiCallAction({ + baseUrl: (auth) => { + return `https://${ + (auth as DocusignAuthType).environment + }.docusign.net/restapi`; + }, + auth: docusignAuth, + authMapping: async (auth, propsValue) => { + const apiClient = await createApiClient(auth as DocusignAuthType); + return (apiClient as any).defaultHeaders; + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/docusign/src/lib/actions/get-document.ts b/packages/pieces/community/docusign/src/lib/actions/get-document.ts new file mode 100644 index 0000000..98ca7e0 --- /dev/null +++ b/packages/pieces/community/docusign/src/lib/actions/get-document.ts @@ -0,0 +1,52 @@ +import { EnvelopesApi } from 'docusign-esign'; + +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { docusignAuth, DocusignAuthType } from '../..'; +import { createApiClient } from '../common'; + +export const getDocument = createAction({ + name: 'getDocument', + displayName: 'Get document', + description: 'Get document from a specific envelope', + auth: docusignAuth, + props: { + accountId: Property.ShortText({ + displayName: 'Account ID', + required: true, + }), + envelopeId: Property.ShortText({ + displayName: 'Envelope ID', + required: true, + }), + documentId: Property.ShortText({ + displayName: 'Document ID', + description: + 'The ID of the document to retrieve. Alternatively, you can use one of the following special keywords:\n' + + '\n' + + 'combined: Retrieves all of the documents as a single PDF file. When the query parameter certificate is true, the certificate of completion is included in the PDF file. When the query parameter certificate is false, the certificate of completion is not included in the PDF file.\n' + + 'archive: Retrieves a ZIP archive that contains all of the PDF documents and the certificate of completion.\n' + + 'certificate: Retrieves only the certificate of completion as a PDF file.\n' + + 'portfolio: Retrieves the envelope documents as a PDF portfolio.\n', + required: true, + }), + }, + async run({ auth, propsValue, files }) { + const apiClient = await createApiClient(auth as DocusignAuthType); + const envelopeApiClient = new EnvelopesApi(apiClient); + const filename = + propsValue.documentId === 'archive' ? 'archive.zip' : 'document.pdf'; + return await files.write({ + fileName: filename, + data: Buffer.from( + await envelopeApiClient.getDocument( + propsValue.accountId, + propsValue.envelopeId, + propsValue.documentId, + {} + ), + 'binary' + ), + }); + }, +}); diff --git a/packages/pieces/community/docusign/src/lib/actions/get-envelope.ts b/packages/pieces/community/docusign/src/lib/actions/get-envelope.ts new file mode 100644 index 0000000..510212d --- /dev/null +++ b/packages/pieces/community/docusign/src/lib/actions/get-envelope.ts @@ -0,0 +1,30 @@ +import { EnvelopesApi } from 'docusign-esign'; + +import { createAction, Property } from '@activepieces/pieces-framework'; +import { docusignAuth, DocusignAuthType } from '../../'; +import { createApiClient } from '../common'; + +export const getEnvelope = createAction({ + name: 'getEnvelope', + displayName: 'Get envelope', + description: 'Get Docusign envelope', + auth: docusignAuth, + props: { + accountId: Property.ShortText({ + displayName: 'Account ID', + required: true, + }), + envelopeId: Property.ShortText({ + displayName: 'Envelope ID', + required: true, + }), + }, + async run({ auth, propsValue }) { + const apiClient = await createApiClient(auth as DocusignAuthType); + const envelopeApiClient = new EnvelopesApi(apiClient); + return await envelopeApiClient.getEnvelope( + propsValue.accountId, + propsValue.envelopeId + ); + }, +}); diff --git a/packages/pieces/community/docusign/src/lib/actions/list-envelopes.ts b/packages/pieces/community/docusign/src/lib/actions/list-envelopes.ts new file mode 100644 index 0000000..3b7d3a0 --- /dev/null +++ b/packages/pieces/community/docusign/src/lib/actions/list-envelopes.ts @@ -0,0 +1,70 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { docusignAuth, DocusignAuthType } from '../..'; +import { createApiClient } from '../common'; +import { Envelope, EnvelopesApi, EnvelopesInformation } from 'docusign-esign'; + +export const listEnvelopes = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'listEnvelopes', + displayName: 'List envelopes', + description: 'List / search envelopes', + auth: docusignAuth, + props: { + accountId: Property.ShortText({ + displayName: 'Account ID', + required: true, + }), + fromDate: Property.DateTime({ + displayName: 'From date', + required: false, + }), + toDate: Property.DateTime({ + displayName: 'To date', + required: false, + }), + searchText: Property.ShortText({ + displayName: 'Search text', + required: false, + }), + status: Property.ShortText({ + displayName: 'Status', + required: false, + }), + include: Property.Array({ + displayName: 'Include (e.g. recipients)', + required: false, + }), + }, + async run({ auth, propsValue }) { + const apiClient = await createApiClient(auth as DocusignAuthType); + const envelopeApiClient = new EnvelopesApi(apiClient); + + const getPage = async (startPosition: number) => { + return await envelopeApiClient.listStatusChanges(propsValue.accountId, { + count: '100', + startPosition: startPosition.toString(), + fromDate: propsValue.fromDate, + toDate: propsValue.toDate, + searchText: propsValue.searchText, + status: propsValue.status, + include: propsValue.include?.join(','), + }); + }; + let startPosition = 0; + const envelopes: Envelope[] = []; + let page: EnvelopesInformation | null = null; + do { + page = await getPage(startPosition); + if (page.envelopes) { + envelopes.push(...page.envelopes); + } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + startPosition = parseInt(page.endPosition!) + 1; + } while ( + page.endPosition && + page.totalSetSize && + parseInt(page.endPosition) + 1 < parseInt(page.totalSetSize) + ); + return envelopes; + }, +}); diff --git a/packages/pieces/community/docusign/src/lib/common.ts b/packages/pieces/community/docusign/src/lib/common.ts new file mode 100644 index 0000000..27f0a76 --- /dev/null +++ b/packages/pieces/community/docusign/src/lib/common.ts @@ -0,0 +1,24 @@ +import { ApiClient } from 'docusign-esign'; +import { DocusignAuthType } from '../index'; + +export async function createApiClient(auth: DocusignAuthType) { + const oAuthBasePath = + auth.environment === 'demo' + ? 'account-d.docusign.com' + : 'account.docusign.com'; + const dsApi = new ApiClient({ + basePath: `https://${auth.environment}.docusign.net/restapi`, + oAuthBasePath, + }); + + const results = await dsApi.requestJWTUserToken( + auth.clientId, + auth.impersonatedUserId, + auth.scopes.split(','), + Buffer.from(auth.privateKey.replace(/\\n/g, '\n'), 'utf-8'), + 10 * 60 // 10mn lifetime + ); + const accessToken = results.body.access_token; + dsApi.addDefaultHeader('Authorization', `Bearer ${accessToken}`); + return dsApi; +} diff --git a/packages/pieces/community/docusign/tsconfig.json b/packages/pieces/community/docusign/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/docusign/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/docusign/tsconfig.lib.json b/packages/pieces/community/docusign/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/docusign/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/drip/.babelrc b/packages/pieces/community/drip/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/drip/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/drip/.eslintrc.json b/packages/pieces/community/drip/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/drip/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/drip/README.md b/packages/pieces/community/drip/README.md new file mode 100644 index 0000000..53f572a --- /dev/null +++ b/packages/pieces/community/drip/README.md @@ -0,0 +1,7 @@ +# pieces-drip + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-drip` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/drip/package.json b/packages/pieces/community/drip/package.json new file mode 100644 index 0000000..9c26e63 --- /dev/null +++ b/packages/pieces/community/drip/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-drip", + "version": "0.3.4" +} \ No newline at end of file diff --git a/packages/pieces/community/drip/project.json b/packages/pieces/community/drip/project.json new file mode 100644 index 0000000..a5a7106 --- /dev/null +++ b/packages/pieces/community/drip/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-drip", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/drip/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/drip", + "tsConfig": "packages/pieces/community/drip/tsconfig.lib.json", + "packageJson": "packages/pieces/community/drip/package.json", + "main": "packages/pieces/community/drip/src/index.ts", + "assets": [ + "packages/pieces/community/drip/*.md", + { + "input": "packages/pieces/community/drip/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/drip/src/index.ts b/packages/pieces/community/drip/src/index.ts new file mode 100644 index 0000000..547d9db --- /dev/null +++ b/packages/pieces/community/drip/src/index.ts @@ -0,0 +1,39 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { dripAddSubscriberToCampaign } from './lib/actions/add-subscriber-to-campaign.action'; +import { dripApplyTagToSubscriber } from './lib/actions/apply-tag-to-subscriber.action'; +import { dripUpsertSubscriberAction } from './lib/actions/upsert-subscriber.action'; +import { dripNewSubscriberEvent } from './lib/trigger/new-subscriber.trigger'; +import { dripTagAppliedEvent } from './lib/trigger/new-tag.trigger'; + +export const dripAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'Get it from https://www.getdrip.com/user/edit', +}); + +export const drip = createPiece({ + displayName: 'Drip', + description: 'E-commerce CRM for B2B marketers', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/drip.png', + authors: ["kishanprmr","MoShizzle","AbdulTheActivePiecer","khaledmashaly","abuaboud"], + categories: [PieceCategory.MARKETING], + auth: dripAuth, + actions: [ + dripApplyTagToSubscriber, + dripAddSubscriberToCampaign, + dripUpsertSubscriberAction, + createCustomApiCallAction({ + baseUrl: () => `https://api.getdrip.com/v2/`, + auth: dripAuth, + authMapping: async (auth) => ({ + Authorization: `Basic ${Buffer.from(auth as string).toString( + 'base64' + )}`, + }), + }), + ], + triggers: [dripNewSubscriberEvent, dripTagAppliedEvent], +}); diff --git a/packages/pieces/community/drip/src/lib/actions/add-subscriber-to-campaign.action.ts b/packages/pieces/community/drip/src/lib/actions/add-subscriber-to-campaign.action.ts new file mode 100644 index 0000000..7a600d0 --- /dev/null +++ b/packages/pieces/community/drip/src/lib/actions/add-subscriber-to-campaign.action.ts @@ -0,0 +1,90 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { dripCommon } from '../common'; +import { dripAuth } from '../../'; + +export const dripAddSubscriberToCampaign = createAction({ + auth: dripAuth, + name: 'add_subscriber_to_campaign', + description: 'Add a subscriber to a campaign (Email series)', + displayName: 'Add a subscriber to a campaign', + props: { + account_id: dripCommon.account_id, + campaign_id: Property.Dropdown({ + displayName: 'Email Series Campaign', + refreshers: ['account_id'], + required: true, + options: async ({ auth, account_id }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please fill in API key first', + }; + } + if (!account_id) { + return { + disabled: true, + options: [], + placeholder: 'Please select an account first', + }; + } + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${dripCommon.baseUrl(account_id as string)}/campaigns`, + headers: { + Authorization: `Basic ${Buffer.from(auth as string).toString( + 'base64' + )}`, + }, + }; + const response = await httpClient.sendRequest<{ + campaigns: { name: string; id: string }[]; + }>(request); + const opts = response.body.campaigns.map((campaign) => { + return { value: campaign.id, label: campaign.name }; + }); + if (opts.length === 0) { + return { + disabled: false, + options: [], + placeholder: 'Please create an email series campaign', + }; + } + return { + disabled: false, + options: opts, + }; + }, + }), + subscriber: dripCommon.subscriber, + tags: dripCommon.tags, + custom_fields: dripCommon.custom_fields, + }, + async run({ auth, propsValue }) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${dripCommon.baseUrl(propsValue.account_id)}/campaigns/${ + propsValue.campaign_id + }/subscribers`, + body: { + subscribers: [ + { + email: propsValue.subscriber, + tags: propsValue.tags, + custom_fields: propsValue.custom_fields, + }, + ], + }, + headers: { + Authorization: dripCommon.authorizationHeader(auth), + }, + queryParams: {}, + }; + return await httpClient.sendRequest>(request); + }, +}); diff --git a/packages/pieces/community/drip/src/lib/actions/apply-tag-to-subscriber.action.ts b/packages/pieces/community/drip/src/lib/actions/apply-tag-to-subscriber.action.ts new file mode 100644 index 0000000..ec468c2 --- /dev/null +++ b/packages/pieces/community/drip/src/lib/actions/apply-tag-to-subscriber.action.ts @@ -0,0 +1,43 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { dripCommon } from '../common'; +import { dripAuth } from '../../'; + +export const dripApplyTagToSubscriber = createAction({ + auth: dripAuth, + name: 'apply_tag_to_subscriber', + description: 'Apply a tag to a subscriber', + displayName: 'Apply a tag to subscriber', + props: { + account_id: dripCommon.account_id, + subscriber: dripCommon.subscriber, + tag: Property.ShortText({ + displayName: 'Tag', + required: true, + description: 'Tag to apply', + }), + }, + async run({ auth, propsValue }) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${dripCommon.baseUrl(propsValue.account_id)}/tags`, + body: { + tags: [ + { + email: propsValue.subscriber, + tag: propsValue.tag, + }, + ], + }, + headers: { + Authorization: dripCommon.authorizationHeader(auth), + }, + queryParams: {}, + }; + return await httpClient.sendRequest>(request); + }, +}); diff --git a/packages/pieces/community/drip/src/lib/actions/upsert-subscriber.action.ts b/packages/pieces/community/drip/src/lib/actions/upsert-subscriber.action.ts new file mode 100644 index 0000000..89d7cba --- /dev/null +++ b/packages/pieces/community/drip/src/lib/actions/upsert-subscriber.action.ts @@ -0,0 +1,87 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { dripCommon } from '../common'; +import { dripAuth } from '../../'; + +export const dripUpsertSubscriberAction = createAction({ + auth: dripAuth, + name: 'upsert_subscriber', + description: 'Create or Update Subscriber', + displayName: 'Create or Update Subscriber', + props: { + account_id: dripCommon.account_id, + subscriber: dripCommon.subscriber, + tags: dripCommon.tags, + custom_fields: dripCommon.custom_fields, + first_name: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + last_name: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + zip: Property.ShortText({ + displayName: 'Zip Code', + description: 'Postal code in which the subscriber resides', + required: false, + }), + country: Property.ShortText({ + displayName: 'Country', + description: 'The country in which the subscriber resides', + required: false, + }), + state: Property.ShortText({ + displayName: 'State', + description: 'The region in which the subscriber resides', + required: false, + }), + city: Property.ShortText({ + displayName: 'City', + description: 'The city in which the subscriber resides', + required: false, + }), + phone: Property.ShortText({ + displayName: 'Phone', + description: "The subscriber's primary phone number", + required: false, + }), + address: Property.ShortText({ + displayName: 'Address', + description: "The subscriber's mailing address", + required: false, + }), + }, + async run({ auth, propsValue }) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${dripCommon.baseUrl(propsValue.account_id)}/subscribers`, + body: { + subscribers: [ + { + email: propsValue.subscriber, + tags: propsValue.tags, + custom_fields: propsValue.custom_fields, + country: propsValue.country, + address1: propsValue.address, + city: propsValue.city, + state: propsValue.state, + zip: propsValue.zip, + phone: propsValue.phone, + first_name: propsValue.first_name, + last_name: propsValue.last_name, + }, + ], + }, + headers: { + Authorization: dripCommon.authorizationHeader(auth), + }, + queryParams: {}, + }; + return await httpClient.sendRequest>(request); + }, +}); diff --git a/packages/pieces/community/drip/src/lib/common/index.ts b/packages/pieces/community/drip/src/lib/common/index.ts new file mode 100644 index 0000000..01aa046 --- /dev/null +++ b/packages/pieces/community/drip/src/lib/common/index.ts @@ -0,0 +1,63 @@ +import { Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; + +export const dripCommon = { + baseUrl: (accountId: string) => { + return `https://api.getdrip.com/v2/${accountId}`; + }, + account_id: Property.Dropdown({ + displayName: 'Account', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please fill in API key first', + }; + } + + const request: HttpRequest = { + method: HttpMethod.GET, + url: 'https://api.getdrip.com/v2/accounts', + headers: { + Authorization: `Basic ${Buffer.from(auth as string).toString( + 'base64' + )}`, + }, + }; + const response = await httpClient.sendRequest<{ + accounts: { id: string; name: string }[]; + }>(request); + const opts = response.body.accounts.map((acc) => { + return { value: acc.id, label: acc.name }; + }); + return { + disabled: false, + options: opts, + }; + }, + }), + subscriber: Property.ShortText({ + required: true, + displayName: 'Subscriber Email', + description: 'Email of the subscriber', + }), + tags: Property.Array({ + displayName: 'tags', + required: false, + description: 'Tags to apply to subscriber', + }), + custom_fields: Property.Object({ + displayName: 'Custom Fields', + required: false, + description: 'Custom field data about the subscriber', + }), + authorizationHeader: (apiKey: string) => + `Basic ${Buffer.from(apiKey).toString('base64')}`, +}; diff --git a/packages/pieces/community/drip/src/lib/trigger/new-subscriber.trigger.ts b/packages/pieces/community/drip/src/lib/trigger/new-subscriber.trigger.ts new file mode 100644 index 0000000..d05012f --- /dev/null +++ b/packages/pieces/community/drip/src/lib/trigger/new-subscriber.trigger.ts @@ -0,0 +1,73 @@ +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { dripCommon } from '../common'; +import { dripAuth } from '../../'; + +const triggerNameInStore = 'drip_new_subscriber_trigger'; +export const dripNewSubscriberEvent = createTrigger({ + auth: dripAuth, + name: 'new_subscriber', + displayName: 'New Subscriber', + description: 'Triggers when a subscriber is created in your Drip account.', + props: { + account_id: dripCommon.account_id, + }, + sampleData: { + event: 'subscriber.created', + data: { + account_id: '9999999', + subscriber: {}, + }, + occurred_at: '2013-06-21T10:31:58Z', + }, + type: TriggerStrategy.WEBHOOK, + async onEnable({ auth, propsValue, webhookUrl, store }) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${dripCommon.baseUrl(propsValue.account_id)}/webhooks`, + body: { + webhooks: [{ post_url: webhookUrl, events: ['subscriber.created'] }], + }, + headers: { + Authorization: dripCommon.authorizationHeader(auth), + }, + queryParams: {}, + }; + const { body } = await httpClient.sendRequest<{ + webhooks: { id: string }[]; + }>(request); + await store.put(triggerNameInStore, { + webhookId: body.webhooks[0].id, + userId: propsValue.account_id, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + triggerNameInStore + ); + if (response !== null && response !== undefined) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `${dripCommon.baseUrl(response.userId)}/webhooks/${ + response.webhookId + }`, + headers: { + Authorization: dripCommon.authorizationHeader(context.auth), + }, + }; + await httpClient.sendRequest(request); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); + +interface DripWebhookInformation { + webhookId: string; + userId: string; +} diff --git a/packages/pieces/community/drip/src/lib/trigger/new-tag.trigger.ts b/packages/pieces/community/drip/src/lib/trigger/new-tag.trigger.ts new file mode 100644 index 0000000..a67e50e --- /dev/null +++ b/packages/pieces/community/drip/src/lib/trigger/new-tag.trigger.ts @@ -0,0 +1,78 @@ +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { dripCommon } from '../common'; +import { dripAuth } from '../../'; + +const triggerNameInStore = 'drip_tag_applied_to_subscriber_trigger'; +export const dripTagAppliedEvent = createTrigger({ + auth: dripAuth, + name: 'tag_applied_to_subscribers', + displayName: 'Tag Applied', + description: 'Triggers when a tag is applied.', + props: { + account_id: dripCommon.account_id, + }, + sampleData: { + event: 'subscriber.applied_tag', + data: { + account_id: '9999999', + subscriber: {}, + properties: { + tag: 'Customer', + }, + }, + occurred_at: '2013-06-21T10:31:58Z', + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${dripCommon.baseUrl(context.propsValue.account_id)}/webhooks`, + body: { + webhooks: [ + { post_url: context.webhookUrl, events: ['subscriber.applied_tag'] }, + ], + }, + headers: { + Authorization: dripCommon.authorizationHeader(context.auth), + }, + queryParams: {}, + }; + const { body } = await httpClient.sendRequest<{ + webhooks: { id: string }[]; + }>(request); + await context.store?.put(triggerNameInStore, { + webhookId: body.webhooks[0].id, + userId: context.propsValue['account_id']!, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + triggerNameInStore + ); + if (response !== null && response !== undefined) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `${dripCommon.baseUrl(response.userId)}/webhooks/${ + response.webhookId + }`, + headers: { + Authorization: dripCommon.authorizationHeader(context.auth), + }, + }; + await httpClient.sendRequest(request); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); + +interface DripWebhookInformation { + webhookId: string; + userId: string; +} diff --git a/packages/pieces/community/drip/tsconfig.json b/packages/pieces/community/drip/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/drip/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/drip/tsconfig.lib.json b/packages/pieces/community/drip/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/drip/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/dropbox/.babelrc b/packages/pieces/community/dropbox/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/dropbox/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/dropbox/.eslintrc.json b/packages/pieces/community/dropbox/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/dropbox/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/dropbox/README.md b/packages/pieces/community/dropbox/README.md new file mode 100644 index 0000000..fdb2586 --- /dev/null +++ b/packages/pieces/community/dropbox/README.md @@ -0,0 +1,7 @@ +# pieces-dropbox + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-dropbox` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/dropbox/package.json b/packages/pieces/community/dropbox/package.json new file mode 100644 index 0000000..33778b0 --- /dev/null +++ b/packages/pieces/community/dropbox/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-dropbox", + "version": "0.5.3" +} diff --git a/packages/pieces/community/dropbox/project.json b/packages/pieces/community/dropbox/project.json new file mode 100644 index 0000000..d5f391a --- /dev/null +++ b/packages/pieces/community/dropbox/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-dropbox", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/dropbox/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/dropbox", + "tsConfig": "packages/pieces/community/dropbox/tsconfig.lib.json", + "packageJson": "packages/pieces/community/dropbox/package.json", + "main": "packages/pieces/community/dropbox/src/index.ts", + "assets": [ + "packages/pieces/community/dropbox/*.md", + { + "input": "packages/pieces/community/dropbox/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/dropbox/src/index.ts b/packages/pieces/community/dropbox/src/index.ts new file mode 100644 index 0000000..70dfdb2 --- /dev/null +++ b/packages/pieces/community/dropbox/src/index.ts @@ -0,0 +1,74 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { dropboxCopyFile } from './lib/actions/copy-file'; +import { dropboxCopyFolder } from './lib/actions/copy-folder'; +import { dropboxCreateNewFolder } from './lib/actions/create-new-folder'; +import { dropboxCreateNewTextFile } from './lib/actions/create-new-text-file'; +import { dropboxDeleteFile } from './lib/actions/delete-file'; +import { dropboxDeleteFolder } from './lib/actions/delete-folder'; +import { dropboxGetFileLink } from './lib/actions/get-file-link'; +import { dropboxListAFolder } from './lib/actions/list-a-folder'; +import { dropboxMoveFile } from './lib/actions/move-file'; +import { dropboxMoveFolder } from './lib/actions/move-folder'; +import { dropboxSearch } from './lib/actions/search'; +import { dropboxUploadFile } from './lib/actions/upload-file'; +import { dropboxDownloadFile } from './lib/actions/download-file'; + +export const dropboxAuth = PieceAuth.OAuth2({ + description: '', + authUrl: 'https://www.dropbox.com/oauth2/authorize', + tokenUrl: 'https://api.dropboxapi.com/oauth2/token', + required: true, + // include token_access_type=offline as a parameter on Authorization URL in order to return a refresh_token + extra: { token_access_type: 'offline' }, + scope: [ + 'files.metadata.write', + 'files.metadata.read', + 'files.content.write', + 'files.content.read', + ], +}); + +export const dropbox = createPiece({ + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/dropbox.png', + actions: [ + dropboxSearch, + dropboxCreateNewTextFile, + dropboxUploadFile, + dropboxDownloadFile, + dropboxGetFileLink, + dropboxDeleteFile, + dropboxMoveFile, + dropboxCopyFile, + dropboxCreateNewFolder, + dropboxDeleteFolder, + dropboxMoveFolder, + dropboxCopyFolder, + dropboxListAFolder, + createCustomApiCallAction({ + baseUrl: () => 'https://api.dropboxapi.com/2', + auth: dropboxAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + displayName: 'Dropbox', + description: 'Cloud storage and file synchronization', + authors: [ + 'BastienMe', + 'kishanprmr', + 'MoShizzle', + 'khaledmashaly', + 'abuaboud', + ], + categories: [PieceCategory.CONTENT_AND_FILES], + triggers: [], + auth: dropboxAuth, +}); diff --git a/packages/pieces/community/dropbox/src/lib/actions/copy-file.ts b/packages/pieces/community/dropbox/src/lib/actions/copy-file.ts new file mode 100644 index 0000000..ae8c556 --- /dev/null +++ b/packages/pieces/community/dropbox/src/lib/actions/copy-file.ts @@ -0,0 +1,64 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { dropboxAuth } from '../../'; + +export const dropboxCopyFile = createAction({ + auth: dropboxAuth, + name: 'copy_dropbox_file', + description: 'Copy a file', + displayName: 'Copy file', + props: { + from_path: Property.ShortText({ + displayName: 'From Path', + description: 'The source path of the file (e.g. /folder1/sourcefile.txt)', + required: true, + }), + to_path: Property.ShortText({ + displayName: 'To Path', + description: + 'The destination path for the copied (e.g. /folder2/destinationfile.txt)', + required: true, + }), + autorename: Property.Checkbox({ + displayName: 'Auto Rename', + description: + "If there's a conflict, have the Dropbox server try to autorename the file to avoid conflict.", + defaultValue: false, + required: false, + }), + allow_ownership_transfer: Property.Checkbox({ + displayName: 'Allow Ownership Transfer', + description: + 'Allows copy by owner even if it would result in an ownership transfer.', + defaultValue: false, + required: false, + }), + }, + async run(context) { + const params = { + from_path: context.propsValue.from_path, + to_path: context.propsValue.to_path, + autorename: context.propsValue.autorename, + allow_ownership_transfer: context.propsValue.allow_ownership_transfer, + }; + + const result = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.dropboxapi.com/2/files/copy_v2`, + headers: { + 'Content-Type': 'application/json', + }, + body: params, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return result.body; + }, +}); diff --git a/packages/pieces/community/dropbox/src/lib/actions/copy-folder.ts b/packages/pieces/community/dropbox/src/lib/actions/copy-folder.ts new file mode 100644 index 0000000..4fb29b7 --- /dev/null +++ b/packages/pieces/community/dropbox/src/lib/actions/copy-folder.ts @@ -0,0 +1,64 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { dropboxAuth } from '../../'; + +export const dropboxCopyFolder = createAction({ + auth: dropboxAuth, + name: 'copy_dropbox_folder', + description: 'Copy a folder', + displayName: 'Copy folder', + props: { + from_path: Property.ShortText({ + displayName: 'From Path', + description: 'The source path of the folder (e.g. /folder1/sourceFolder)', + required: true, + }), + to_path: Property.ShortText({ + displayName: 'To Path', + description: + 'The destination path for the copied folder (e.g. /folder2/destinationFolder)', + required: true, + }), + autorename: Property.Checkbox({ + displayName: 'Auto Rename', + description: + "If there's a conflict, have the Dropbox server try to autorename the folder to avoid conflict.", + defaultValue: false, + required: false, + }), + allow_ownership_transfer: Property.Checkbox({ + displayName: 'Allow Ownership Transfer', + description: + 'Allows copy by owner even if it would result in an ownership transfer.', + defaultValue: false, + required: false, + }), + }, + async run(context) { + const params = { + from_path: context.propsValue.from_path, + to_path: context.propsValue.to_path, + autorename: context.propsValue.autorename, + allow_ownership_transfer: context.propsValue.allow_ownership_transfer, + }; + + const result = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.dropboxapi.com/2/files/copy_v2`, + headers: { + 'Content-Type': 'application/json', + }, + body: params, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return result.body; + }, +}); diff --git a/packages/pieces/community/dropbox/src/lib/actions/create-new-folder.ts b/packages/pieces/community/dropbox/src/lib/actions/create-new-folder.ts new file mode 100644 index 0000000..7518723 --- /dev/null +++ b/packages/pieces/community/dropbox/src/lib/actions/create-new-folder.ts @@ -0,0 +1,53 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + AuthenticationType, + httpClient, +} from '@activepieces/pieces-common'; +import { dropboxAuth } from '../../'; + +export const dropboxCreateNewFolder = createAction({ + auth: dropboxAuth, + name: 'create_new_dropbox_folder', + description: 'Create a new empty folder', + displayName: 'Create New Folder', + props: { + path: Property.ShortText({ + displayName: 'Path', + description: 'The path of the new folder e.g. /Homework/math', + required: true, + }), + autorename: Property.Checkbox({ + displayName: 'Auto Rename', + description: + "If there's a conflict, have the Dropbox server try to autorename the folder to avoid the conflict. The default for this field is False.", + required: false, + }), + }, + async run(context) { + const body = { + autorename: context.propsValue.autorename ? true : false, + path: context.propsValue.path, + }; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://api.dropboxapi.com/2/files/create_folder_v2`, + body, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }; + + const result = await httpClient.sendRequest(request); + console.debug('Folder creation response', result); + + if (result.status == 200) { + return result.body; + } else { + return result; + } + }, +}); diff --git a/packages/pieces/community/dropbox/src/lib/actions/create-new-text-file.ts b/packages/pieces/community/dropbox/src/lib/actions/create-new-text-file.ts new file mode 100644 index 0000000..a173702 --- /dev/null +++ b/packages/pieces/community/dropbox/src/lib/actions/create-new-text-file.ts @@ -0,0 +1,77 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + AuthenticationType, + httpClient, +} from '@activepieces/pieces-common'; +import { dropboxAuth } from '../../'; + +export const dropboxCreateNewTextFile = createAction({ + auth: dropboxAuth, + name: 'create_new_dropbox_text_file', + description: 'Create a new text file from text input', + displayName: 'Create New Text File', + props: { + path: Property.ShortText({ + displayName: 'Path', + description: 'The path of the new folder e.g. /Homework/math', + required: true, + }), + text: Property.LongText({ + displayName: 'Text', + description: 'The text to write into the file.', + required: true, + }), + autorename: Property.Checkbox({ + displayName: 'Autorename', + description: + "If there's a conflict, have the Dropbox server try to autorename the folder to avoid the conflict. The default for this field is False.", + required: false, + }), + mute: Property.Checkbox({ + displayName: 'Mute', + description: + "Normally, users are made aware of any file modifications in their Dropbox account via notifications in the client software. If true, this tells the clients that this modification shouldn't result in a user notification.", + required: false, + }), + strict_conflict: Property.Checkbox({ + displayName: 'Strict conflict', + description: + 'Be more strict about how each WriteMode detects conflict. For example, always return a conflict error when mode = WriteMode.update and the given "rev" doesn\'t match the existing file\'s "rev", even if the existing file has been deleted.', + required: false, + }), + }, + async run(context) { + const params = { + autorename: context.propsValue.autorename, + path: context.propsValue.path, + mode: 'add', + mute: context.propsValue.mute, + strict_conflict: context.propsValue.strict_conflict, + }; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://content.dropboxapi.com/2/files/upload`, + body: Buffer.from(context.propsValue.text, 'utf-8'), + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + headers: { + 'Dropbox-API-Arg': JSON.stringify(params), + 'Content-Type': 'application/octet-stream', + }, + }; + + const result = await httpClient.sendRequest(request); + console.debug('Folder creation response', result); + + if (result.status == 200) { + return result.body; + } else { + return result; + } + }, +}); diff --git a/packages/pieces/community/dropbox/src/lib/actions/delete-file.ts b/packages/pieces/community/dropbox/src/lib/actions/delete-file.ts new file mode 100644 index 0000000..2866c69 --- /dev/null +++ b/packages/pieces/community/dropbox/src/lib/actions/delete-file.ts @@ -0,0 +1,42 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { dropboxAuth } from '../../'; + +export const dropboxDeleteFile = createAction({ + auth: dropboxAuth, + name: 'delete_dropbox_file', + description: 'Delete a file', + displayName: 'Delete file', + props: { + path: Property.ShortText({ + displayName: 'Path', + description: + 'The path of the file to be deleted (e.g. /folder1/file.txt)', + required: true, + }), + }, + async run(context) { + const params = { + path: context.propsValue.path, + }; + + const result = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.dropboxapi.com/2/files/delete_v2`, + headers: { + 'Content-Type': 'application/json', + }, + body: { path: context.propsValue.path }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return result.body; + }, +}); diff --git a/packages/pieces/community/dropbox/src/lib/actions/delete-folder.ts b/packages/pieces/community/dropbox/src/lib/actions/delete-folder.ts new file mode 100644 index 0000000..81563da --- /dev/null +++ b/packages/pieces/community/dropbox/src/lib/actions/delete-folder.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { dropboxAuth } from '../../'; + +export const dropboxDeleteFolder = createAction({ + auth: dropboxAuth, + name: 'delete_dropbox_folder', + description: 'Delete a folder', + displayName: 'Delete folder', + props: { + path: Property.ShortText({ + displayName: 'Path', + description: 'The path of the folder to be deleted (e.g. /folder1)', + required: true, + }), + }, + async run(context) { + const params = { + path: context.propsValue.path, + }; + + const result = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.dropboxapi.com/2/files/delete_v2`, + headers: { + 'Content-Type': 'application/json', + }, + body: params, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return result.body; + }, +}); diff --git a/packages/pieces/community/dropbox/src/lib/actions/download-file.ts b/packages/pieces/community/dropbox/src/lib/actions/download-file.ts new file mode 100644 index 0000000..f2e0493 --- /dev/null +++ b/packages/pieces/community/dropbox/src/lib/actions/download-file.ts @@ -0,0 +1,49 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { dropboxAuth } from '../..'; + +export const dropboxDownloadFile = createAction({ + auth: dropboxAuth, + name: 'downloadFile', + displayName: 'Download File', + description: 'Download a File from Dropbox', + props: { + path: Property.ShortText({ + displayName: "Path", + description: "The path of the file (e.g. /folder1/file.txt)", + required: true + }) + }, + async run(context) { + // Capture file name, if unsuccesfull default to output.pdf + const fileName = (context.propsValue.path.match(/[^/]+$/) ?? ['output.pdf'])[0] + + // For information about Dropbox JSON encoding, see https://www.dropbox.com/developers/reference/json-encoding + const dropboxApiArg = JSON.stringify({path:context.propsValue.path}).replace(/[\u007f-\uffff]/g, (c) => '\\u'+('000'+c.charCodeAt(0).toString(16)).slice(-4)); + + const result = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://content.dropboxapi.com/2/files/download`, + headers: { + 'Content-Type': 'application/octet-stream', + 'Dropbox-API-Arg': dropboxApiArg + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + responseType:'arraybuffer' + }); + + return { + file: await context.files.write({ + fileName: fileName, + data: Buffer.from(result.body) + }) + } + }, +}); diff --git a/packages/pieces/community/dropbox/src/lib/actions/get-file-link.ts b/packages/pieces/community/dropbox/src/lib/actions/get-file-link.ts new file mode 100644 index 0000000..8419efa --- /dev/null +++ b/packages/pieces/community/dropbox/src/lib/actions/get-file-link.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { dropboxAuth } from '../..'; + +export const dropboxGetFileLink = createAction({ + auth: dropboxAuth, + name: 'get_dropbox_file_link', + description: 'Get a temporary file link', + displayName: 'Get temporary file link', + props: { + path: Property.ShortText({ + displayName: 'Path', + description: 'The path of the file (e.g. /folder1/file.txt)', + required: true, + }), + }, + async run(context) { + const params = { + path: context.propsValue.path, + }; + + const result = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.dropboxapi.com/2/files/get_temporary_link`, + headers: { + 'Content-Type': 'application/json', + }, + body: { path: context.propsValue.path }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return result.body; + }, +}); diff --git a/packages/pieces/community/dropbox/src/lib/actions/list-a-folder.ts b/packages/pieces/community/dropbox/src/lib/actions/list-a-folder.ts new file mode 100644 index 0000000..cf773ce --- /dev/null +++ b/packages/pieces/community/dropbox/src/lib/actions/list-a-folder.ts @@ -0,0 +1,57 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { dropboxAuth } from '../../'; + +export const dropboxListAFolder = createAction({ + auth: dropboxAuth, + name: 'list_dropbox_folder', + description: 'List the contents of a folder', + displayName: 'List a folder', + props: { + path: Property.ShortText({ + displayName: 'Path', + description: + 'The path of the folder to be listed (e.g. /folder1). Use an empty string for the root folder.', + required: true, + }), + recursive: Property.Checkbox({ + displayName: 'Recursive', + description: + 'If set to true, the list folder operation will be applied recursively to all subfolders and the response will contain contents of all subfolders.', + defaultValue: false, + required: false, + }), + limit: Property.Number({ + displayName: 'Limit', + description: + 'The maximum number of results to return (between 1 and 2000). Default is 2000 if not specified.', + required: false, + }), + }, + async run(context) { + const params = { + path: context.propsValue.path, + recursive: context.propsValue.recursive, + limit: context.propsValue.limit || 2000, + }; + + const result = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.dropboxapi.com/2/files/list_folder`, + headers: { + 'Content-Type': 'application/json', + }, + body: params, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return result.body; + }, +}); diff --git a/packages/pieces/community/dropbox/src/lib/actions/move-file.ts b/packages/pieces/community/dropbox/src/lib/actions/move-file.ts new file mode 100644 index 0000000..451a8bd --- /dev/null +++ b/packages/pieces/community/dropbox/src/lib/actions/move-file.ts @@ -0,0 +1,63 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { dropboxAuth } from '../../'; + +export const dropboxMoveFile = createAction({ + auth: dropboxAuth, + name: 'move_dropbox_file', + description: 'Move a file', + displayName: 'Move file', + props: { + from_path: Property.ShortText({ + displayName: 'From Path', + description: 'The current path of the file (e.g. /folder1/oldfile.txt)', + required: true, + }), + to_path: Property.ShortText({ + displayName: 'To Path', + description: 'The new path for the file (e.g. /folder2/newfile.txt)', + required: true, + }), + autorename: Property.Checkbox({ + displayName: 'Auto Rename', + description: + "If there's a conflict, have the Dropbox server try to autorename the file to avoid conflict.", + defaultValue: false, + required: false, + }), + allow_ownership_transfer: Property.Checkbox({ + displayName: 'Allow Ownership Transfer', + description: + 'Allows moves by owner even if it would result in an ownership transfer.', + defaultValue: false, + required: false, + }), + }, + async run(context) { + const params = { + from_path: context.propsValue.from_path, + to_path: context.propsValue.to_path, + autorename: context.propsValue.autorename, + allow_ownership_transfer: context.propsValue.allow_ownership_transfer, + }; + + const result = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.dropboxapi.com/2/files/move_v2`, + headers: { + 'Content-Type': 'application/json', + }, + body: params, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return result.body; + }, +}); diff --git a/packages/pieces/community/dropbox/src/lib/actions/move-folder.ts b/packages/pieces/community/dropbox/src/lib/actions/move-folder.ts new file mode 100644 index 0000000..b58c7df --- /dev/null +++ b/packages/pieces/community/dropbox/src/lib/actions/move-folder.ts @@ -0,0 +1,65 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { dropboxAuth } from '../../'; + +export const dropboxMoveFolder = createAction({ + auth: dropboxAuth, + name: 'move_dropbox_folder', + description: 'Move a folder', + displayName: 'Move folder', + props: { + from_path: Property.ShortText({ + displayName: 'From Path', + description: + 'The current path of the folder (e.g. /folder1/sourceFolder)', + required: true, + }), + to_path: Property.ShortText({ + displayName: 'To Path', + description: + 'The new path for the folder (e.g. /folder2/destinationFolder)', + required: true, + }), + autorename: Property.Checkbox({ + displayName: 'Auto Rename', + description: + "If there's a conflict, have the Dropbox server try to autorename the folder to avoid conflict.", + defaultValue: false, + required: false, + }), + allow_ownership_transfer: Property.Checkbox({ + displayName: 'Allow Ownership Transfer', + description: + 'Allows moves by owner even if it would result in an ownership transfer.', + defaultValue: false, + required: false, + }), + }, + async run(context) { + const params = { + from_path: context.propsValue.from_path, + to_path: context.propsValue.to_path, + autorename: context.propsValue.autorename, + allow_ownership_transfer: context.propsValue.allow_ownership_transfer, + }; + + const result = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.dropboxapi.com/2/files/move_v2`, + headers: { + 'Content-Type': 'application/json', + }, + body: params, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return result.body; + }, +}); diff --git a/packages/pieces/community/dropbox/src/lib/actions/search.ts b/packages/pieces/community/dropbox/src/lib/actions/search.ts new file mode 100644 index 0000000..c802466 --- /dev/null +++ b/packages/pieces/community/dropbox/src/lib/actions/search.ts @@ -0,0 +1,115 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { dropboxAuth } from '../..'; + +export const dropboxSearch = createAction({ + auth: dropboxAuth, + name: 'search_dropbox', + description: 'Search for files and folders', + displayName: 'Search', + props: { + query: Property.ShortText({ + displayName: 'Query', + description: 'The search string. Must be at least 3 characters.', + required: true, + }), + path: Property.ShortText({ + displayName: 'Path', + description: + 'The path to search in. If not specified, the search is performed from the root.', + required: false, + }), + max_results: Property.Number({ + displayName: 'Max Results', + description: + 'The maximum number of search results to return (up to 1000). Default is 100 if not specified.', + required: false, + }), + order_by: Property.StaticDropdown({ + displayName: 'Order By', + description: 'Specified property of the order of search results.', + options: { + options: [ + { label: 'Relevance', value: 'relevance' }, + { label: 'Modified Time', value: 'modified_time' }, + ], + }, + defaultValue: 'relevance', + required: false, + }), + file_status: Property.StaticDropdown({ + displayName: 'File Status', + description: 'Restricts search to the given file status.', + options: { + options: [ + { label: 'Active', value: 'active' }, + { label: 'Deleted', value: 'deleted' }, + ], + }, + defaultValue: 'active', + required: false, + }), + filename_only: Property.Checkbox({ + displayName: 'Filename Only', + description: 'Restricts search to only match on filenames.', + defaultValue: false, + required: false, + }), + file_extensions: Property.ShortText({ + displayName: 'File Extensions', + description: + 'Restricts search to only the extensions specified (comma-separated).', + required: false, + }), + file_categories: Property.ShortText({ + displayName: 'File Categories', + description: + 'Restricts search to only the file categories specified (comma-separated).', + required: false, + }), + account_id: Property.ShortText({ + displayName: 'Account ID', + description: 'Restricts results to the given account id.', + required: false, + }), + }, + async run(context) { + const options = { + path: context.propsValue.path || '', + max_results: context.propsValue.max_results || 100, + file_status: context.propsValue.file_status, + filename_only: context.propsValue.filename_only, + file_extensions: context.propsValue.file_extensions + ? context.propsValue.file_extensions.split(',') + : undefined, + file_categories: context.propsValue.file_categories + ? context.propsValue.file_categories.split(',') + : undefined, + account_id: context.propsValue.account_id, + }; + + const requestBody = { + query: context.propsValue.query, + options: options, + }; + + const result = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.dropboxapi.com/2/files/search_v2`, + headers: { + 'Content-Type': 'application/json', + }, + body: requestBody, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return result.body; + }, +}); diff --git a/packages/pieces/community/dropbox/src/lib/actions/upload-file.ts b/packages/pieces/community/dropbox/src/lib/actions/upload-file.ts new file mode 100644 index 0000000..84a8080 --- /dev/null +++ b/packages/pieces/community/dropbox/src/lib/actions/upload-file.ts @@ -0,0 +1,77 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { dropboxAuth } from '../../'; + +export const dropboxUploadFile = createAction({ + auth: dropboxAuth, + name: 'upload_dropbox_file', + description: 'Upload a file', + displayName: 'Upload file', + props: { + path: Property.ShortText({ + displayName: 'Path', + description: + 'The path where the file should be saved (e.g. /folder1/file.txt)', + required: true, + }), + file: Property.File({ + displayName: 'File', + description: 'The file URL or base64 to upload', + required: true, + }), + autorename: Property.Checkbox({ + displayName: 'Auto Rename', + description: + "If there's a conflict, as determined by mode, have the Dropbox server try to autorename the file to avoid conflict.", + defaultValue: false, + required: false, + }), + mute: Property.Checkbox({ + displayName: 'Mute', + description: + "Normally, users are made aware of any file modifications in their Dropbox account via notifications in the client software. If true, this tells the clients that this modification shouldn't result in a user notification.", + required: false, + }), + strict_conflict: Property.Checkbox({ + displayName: 'Strict conflict', + description: + 'Be more strict about how each WriteMode detects conflict. For example, always return a conflict error when mode = WriteMode.update and the given "rev" doesn\'t match the existing file\'s "rev", even if the existing file has been deleted.', + required: false, + }), + }, + async run(context) { + const fileData = context.propsValue.file; + + const params = { + autorename: context.propsValue.autorename, + path: context.propsValue.path, + mode: 'add', + mute: context.propsValue.mute, + strict_conflict: context.propsValue.strict_conflict, + }; + + const fileBuffer = Buffer.from(fileData.base64, 'base64'); + // For information about Dropbox JSON encoding, see https://www.dropbox.com/developers/reference/json-encoding + const dropboxApiArg = JSON.stringify(params).replace(/[\u007f-\uffff]/g, (c) => '\\u'+('000'+c.charCodeAt(0).toString(16)).slice(-4)); + + const result = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://content.dropboxapi.com/2/files/upload`, + body: fileBuffer, + headers: { + 'Dropbox-API-Arg': dropboxApiArg, + 'Content-Type': 'application/octet-stream', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return result.body; + }, +}); diff --git a/packages/pieces/community/dropbox/tsconfig.json b/packages/pieces/community/dropbox/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/dropbox/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/dropbox/tsconfig.lib.json b/packages/pieces/community/dropbox/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/dropbox/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/dumpling-ai/.eslintrc.json b/packages/pieces/community/dumpling-ai/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/dumpling-ai/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/dumpling-ai/README.md b/packages/pieces/community/dumpling-ai/README.md new file mode 100644 index 0000000..91d70f3 --- /dev/null +++ b/packages/pieces/community/dumpling-ai/README.md @@ -0,0 +1,7 @@ +# pieces-dumpling-ai + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-dumpling-ai` to build the library. diff --git a/packages/pieces/community/dumpling-ai/package.json b/packages/pieces/community/dumpling-ai/package.json new file mode 100644 index 0000000..33f8bd3 --- /dev/null +++ b/packages/pieces/community/dumpling-ai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-dumpling-ai", + "version": "0.0.1" +} diff --git a/packages/pieces/community/dumpling-ai/project.json b/packages/pieces/community/dumpling-ai/project.json new file mode 100644 index 0000000..0e7e99b --- /dev/null +++ b/packages/pieces/community/dumpling-ai/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-dumpling-ai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/dumpling-ai/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/dumpling-ai", + "tsConfig": "packages/pieces/community/dumpling-ai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/dumpling-ai/package.json", + "main": "packages/pieces/community/dumpling-ai/src/index.ts", + "assets": [ + "packages/pieces/community/dumpling-ai/*.md", + { + "input": "packages/pieces/community/dumpling-ai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/dumpling-ai/src/index.ts b/packages/pieces/community/dumpling-ai/src/index.ts new file mode 100644 index 0000000..c96b271 --- /dev/null +++ b/packages/pieces/community/dumpling-ai/src/index.ts @@ -0,0 +1,70 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { + webSearch, + searchNews, + generateImage, + scrapeWebsite, + crawlWebsite, + extractDocument, +} from './lib/actions'; +import { PieceCategory } from '@activepieces/shared'; +import { + AuthenticationType, + createCustomApiCallAction, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; + +export const dumplingAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: ` + You can obtain API key from [API Section](https://app.dumplingai.com/api-keys).`, + validate: async ({ auth }) => { + try { + await httpClient.sendRequest({ + url: 'https://app.dumplingai.com/api/v1/search', + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + body: { + query: 'Activepieces', + }, + }); + + return { + valid: true, + }; + } catch (e) { + return { valid: false, error: 'Invalid API Key.' }; + } + }, +}); + +export const dumplingAi = createPiece({ + displayName: 'Dumpling AI', + description:'Transform unstructured website content into clean, AI-ready data', + auth: dumplingAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/dumpling-ai.png', + authors: ['neo773'], + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE, PieceCategory.PRODUCTIVITY], + actions: [ + webSearch, + searchNews, + generateImage, + scrapeWebsite, + crawlWebsite, + extractDocument, + createCustomApiCallAction({ + baseUrl: () => 'https://app.dumplingai.com/api/v1', + auth: dumplingAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/dumpling-ai/src/lib/actions/crawl-website.ts b/packages/pieces/community/dumpling-ai/src/lib/actions/crawl-website.ts new file mode 100644 index 0000000..3b76388 --- /dev/null +++ b/packages/pieces/community/dumpling-ai/src/lib/actions/crawl-website.ts @@ -0,0 +1,66 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { dumplingAuth } from '../../index'; + +export const crawlWebsite = createAction({ + name: 'crawl_website', + auth: dumplingAuth, + displayName: 'Crawl Website', + description: 'Crawl a website and return structured content from multiple pages.', + props: { + url: Property.ShortText({ + displayName: 'URL', + required: true, + description: 'The website URL to crawl.', + }), + limit: Property.Number({ + displayName: 'Page Limit', + required: false, + defaultValue: 5, + description: 'Maximum number of pages to crawl.', + }), + depth: Property.Number({ + displayName: 'Crawl Depth', + required: false, + defaultValue: 2, + description: 'Depth of crawling (distance between base URL path and sub paths).', + }), + format: Property.StaticDropdown({ + displayName: 'Output Format', + required: false, + defaultValue: 'markdown', + options: { + options: [ + { label: 'Markdown', value: 'markdown' }, + { label: 'Text', value: 'text' }, + { label: 'Raw', value: 'raw' }, + ], + }, + description: 'Format of the output content.', + }), + }, + async run(context) { + const { url, limit, depth, format } = context.propsValue; + + const requestBody: Record = { + url, + }; + + // Add optional parameters if provided + if (limit !== undefined) requestBody['limit'] = limit; + if (depth !== undefined) requestBody['depth'] = depth; + if (format) requestBody['format'] = format; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://app.dumplingai.com/api/v1/crawl', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${context.auth}`, + }, + body: requestBody, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/dumpling-ai/src/lib/actions/extract-document.ts b/packages/pieces/community/dumpling-ai/src/lib/actions/extract-document.ts new file mode 100644 index 0000000..c041a89 --- /dev/null +++ b/packages/pieces/community/dumpling-ai/src/lib/actions/extract-document.ts @@ -0,0 +1,52 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { dumplingAuth } from '../../index'; + +export const extractDocument = createAction({ + name: 'extract_document', + auth: dumplingAuth, + displayName: 'Extract Document Data', + description: 'Extract structured data from documents using vision-capable AI.', + props: { + file: Property.File({ + displayName: 'File', + required: true, + description: 'File URL or base64-encoded file.', + }), + prompt: Property.LongText({ + displayName: 'Extraction Prompt', + required: true, + description: 'The prompt describing what data to extract from the document.', + }), + jsonMode: Property.Checkbox({ + displayName: 'JSON Mode', + required: false, + defaultValue: false, + description: 'Whether to return the result in JSON format.', + }), + }, + async run(context) { + const { file, prompt, jsonMode } = context.propsValue; + + const requestBody: Record = { + inputMethod: 'base64', + files: [file.base64], + prompt, + }; + + // Add optional parameters if provided + if (jsonMode !== undefined) requestBody['jsonMode'] = jsonMode; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://app.dumplingai.com/api/v1/extract-document', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${context.auth}`, + }, + body: requestBody, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/dumpling-ai/src/lib/actions/generate-image.ts b/packages/pieces/community/dumpling-ai/src/lib/actions/generate-image.ts new file mode 100644 index 0000000..622ef9c --- /dev/null +++ b/packages/pieces/community/dumpling-ai/src/lib/actions/generate-image.ts @@ -0,0 +1,107 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { dumplingAuth } from '../../index'; + +export const generateImage = createAction({ + name: 'generate_image', + auth: dumplingAuth, + displayName: 'Generate Image', + description: 'Generate images based on a text prompt using AI.', + props: { + model: Property.StaticDropdown({ + displayName: 'Model', + required: true, + description: 'The model to use for image generation', + defaultValue: 'FLUX.1-schnell', + options: { + options: [ + { label: 'FLUX.1-schnell', value: 'FLUX.1-schnell' }, + { label: 'FLUX.1-dev', value: 'FLUX.1-dev' }, + { label: 'FLUX.1-pro', value: 'FLUX.1-pro' }, + { label: 'FLUX.1.1-pro', value: 'FLUX.1.1-pro' }, + { label: 'recraft-v3', value: 'recraft-v3' }, + ], + }, + }), + prompt: Property.LongText({ + displayName: 'Prompt', + required: true, + description: 'The text prompt for image generation', + }), + aspect_ratio: Property.StaticDropdown({ + displayName: 'Aspect Ratio', + required: false, + description: 'Aspect ratio of the generated image', + defaultValue: '1:1', + options: { + options: [ + { label: 'Square (1:1)', value: '1:1' }, + { label: 'Landscape 16:9', value: '16:9' }, + { label: 'Landscape 21:9', value: '21:9' }, + { label: 'Landscape 3:2', value: '3:2' }, + { label: 'Landscape 4:3', value: '4:3' }, + { label: 'Portrait 2:3', value: '2:3' }, + { label: 'Portrait 3:4', value: '3:4' }, + { label: 'Portrait 4:5', value: '4:5' }, + { label: 'Portrait 9:16', value: '9:16' }, + { label: 'Portrait 9:21', value: '9:21' }, + ], + }, + }), + num_outputs: Property.Number({ + displayName: 'Number of Images', + required: false, + defaultValue: 1, + description: 'Number of images to generate (1-4)', + }), + seed: Property.Number({ + displayName: 'Seed', + required: false, + description: 'Seed for reproducible results', + }), + output_format: Property.StaticDropdown({ + displayName: 'Output Format', + required: false, + description: 'Format of the generated image', + defaultValue: 'webp', + options: { + options: [ + { label: 'WebP', value: 'webp' }, + { label: 'JPG', value: 'jpg' }, + { label: 'PNG', value: 'png' }, + ], + }, + }), + }, + async run(context) { + const { model, prompt, aspect_ratio, num_outputs, seed, output_format } = context.propsValue; + + // Prepare the input object based on the selected model + const input: Record = { + prompt: prompt, + }; + + // Add common optional parameters + if (seed !== undefined) input['seed'] = seed; + if (aspect_ratio) input['aspect_ratio'] = aspect_ratio; + if (num_outputs) input['num_outputs'] = num_outputs; + if (output_format) input['output_format'] = output_format; + + const requestBody = { + model: model, + input: input, + }; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://app.dumplingai.com/api/v1/generate-ai-image', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${context.auth}`, + }, + body: requestBody, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/dumpling-ai/src/lib/actions/index.ts b/packages/pieces/community/dumpling-ai/src/lib/actions/index.ts new file mode 100644 index 0000000..01427b3 --- /dev/null +++ b/packages/pieces/community/dumpling-ai/src/lib/actions/index.ts @@ -0,0 +1,8 @@ +import { webSearch } from './web-search'; +import { searchNews } from './search-news'; +import { generateImage } from './generate-image'; +import { scrapeWebsite } from './scrape-website'; +import { crawlWebsite } from './crawl-website'; +import { extractDocument } from './extract-document'; + +export { webSearch, searchNews, generateImage, scrapeWebsite, crawlWebsite, extractDocument }; diff --git a/packages/pieces/community/dumpling-ai/src/lib/actions/scrape-website.ts b/packages/pieces/community/dumpling-ai/src/lib/actions/scrape-website.ts new file mode 100644 index 0000000..f8dafb2 --- /dev/null +++ b/packages/pieces/community/dumpling-ai/src/lib/actions/scrape-website.ts @@ -0,0 +1,65 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { dumplingAuth } from '../../index'; + +export const scrapeWebsite = createAction({ + name: 'scrape_website', + auth: dumplingAuth, + displayName: 'Scrape Website', + description: 'Scrapes data from a specified URL and format the result.', + props: { + url: Property.ShortText({ + displayName: 'URL', + required: true, + }), + format: Property.StaticDropdown({ + displayName: 'Output Format', + required: false, + defaultValue: 'markdown', + options: { + options: [ + { label: 'Markdown', value: 'markdown' }, + { label: 'HTML', value: 'html' }, + { label: 'Screenshot', value: 'screenshot' }, + ], + }, + description: 'The format of the output', + }), + cleaned: Property.Checkbox({ + displayName: 'Clean Output ?', + required: false, + defaultValue: true, + description: 'Whether the output should be cleaned.', + }), + renderJs: Property.Checkbox({ + displayName: 'Render JavaScript ?', + required: false, + defaultValue: true, + description: 'Whether to render JavaScript before scraping.', + }), + }, + async run(context) { + const { url, format, cleaned, renderJs } = context.propsValue; + + const requestBody: Record = { + url, + }; + + // Add optional parameters if provided + if (format) requestBody['format'] = format; + if (cleaned !== undefined) requestBody['cleaned'] = cleaned; + if (renderJs !== undefined) requestBody['renderJs'] = renderJs; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://app.dumplingai.com/api/v1/scrape', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${context.auth}`, + }, + body: requestBody, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/dumpling-ai/src/lib/actions/search-news.ts b/packages/pieces/community/dumpling-ai/src/lib/actions/search-news.ts new file mode 100644 index 0000000..15d0aa0 --- /dev/null +++ b/packages/pieces/community/dumpling-ai/src/lib/actions/search-news.ts @@ -0,0 +1,78 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { dumplingAuth } from '../../index'; + +export const searchNews = createAction({ + name: 'search_news', + auth: dumplingAuth, + displayName: 'Search News', + description: 'Search for news articles using Google News.', + props: { + query: Property.ShortText({ + displayName: 'Search Query', + required: true, + description: 'The search query for Google News.', + }), + country: Property.ShortText({ + displayName: 'Country', + required: false, + description: 'Country code for location bias (e.g., "US" for United States).', + }), + location: Property.ShortText({ + displayName: 'Location', + required: false, + description: 'Specific location to focus the search (e.g., "New York, NY").', + }), + language: Property.ShortText({ + displayName: 'Language', + required: false, + description: 'Language code for the search results (e.g., "en" for English).', + }), + dateRange: Property.StaticDropdown({ + displayName: 'Date Range', + required: false, + options: { + options: [ + { label: 'Any Time', value: 'anyTime' }, + { label: 'Past Hour', value: 'pastHour' }, + { label: 'Past Day', value: 'pastDay' }, + { label: 'Past Week', value: 'pastWeek' }, + { label: 'Past Month', value: 'pastMonth' }, + { label: 'Past Year', value: 'pastYear' }, + ], + }, + description: 'Filter results by date.', + }), + page: Property.Number({ + displayName: 'Page Number', + required: false, + description: 'Page number for paginated results.', + }), + }, + async run(context) { + const { query, country, location, language, dateRange, page } = context.propsValue; + + const requestBody: Record = { + query, + }; + + // Add optional parameters if provided + if (country) requestBody['country'] = country; + if (location) requestBody['location'] = location; + if (language) requestBody['language'] = language; + if (dateRange) requestBody['dateRange'] = dateRange; + if (page) requestBody['page'] = page; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://app.dumplingai.com/api/v1/search-news', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${context.auth}`, + }, + body: requestBody, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/dumpling-ai/src/lib/actions/web-search.ts b/packages/pieces/community/dumpling-ai/src/lib/actions/web-search.ts new file mode 100644 index 0000000..8a089d5 --- /dev/null +++ b/packages/pieces/community/dumpling-ai/src/lib/actions/web-search.ts @@ -0,0 +1,128 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { dumplingAuth } from '../../index'; + +export const webSearch = createAction({ + name: 'web_search', + auth: dumplingAuth, + displayName: 'Web Search', + description: 'Search the web and optionally retrieve content from top results.', + props: { + query: Property.ShortText({ + displayName: 'Search Query', + required: true, + }), + country: Property.ShortText({ + displayName: 'Country', + required: false, + description: 'Two-letter country code for location bias (e.g., "US" for United States).', + }), + location: Property.ShortText({ + displayName: 'Location', + required: false, + description: 'Specific location to focus the search (e.g., "New York, NY").', + }), + language: Property.ShortText({ + displayName: 'Language', + required: false, + description: 'Language code for the search results (e.g., "en" for English).', + }), + dateRange: Property.StaticDropdown({ + displayName: 'Date Range', + required: false, + options: { + options: [ + { label: 'Any Time', value: 'anyTime' }, + { label: 'Past Hour', value: 'pastHour' }, + { label: 'Past Day', value: 'pastDay' }, + { label: 'Past Week', value: 'pastWeek' }, + { label: 'Past Month', value: 'pastMonth' }, + { label: 'Past Year', value: 'pastYear' }, + ], + }, + description: 'Filter results by date.', + }), + page: Property.Number({ + displayName: 'Page Number', + required: false, + description: 'Page number for paginated results.', + }), + scrapeResults: Property.Checkbox({ + displayName: 'Scrape Results', + required: false, + defaultValue: false, + description: 'Whether to scrape top search results.', + }), + numResultsToScrape: Property.Number({ + displayName: 'Number of Results to Scrape', + required: false, + defaultValue: 3, + description: 'Number of top results to scrape (max: 10).', + }), + scrapeFormat: Property.StaticDropdown({ + displayName: 'Scrape Format', + required: false, + defaultValue: 'markdown', + options: { + options: [ + { label: 'Markdown', value: 'markdown' }, + { label: 'HTML', value: 'html' }, + { label: 'Screenshot', value: 'screenshot' }, + ], + }, + description: 'Format of scraped content', + }), + cleanedOutput: Property.Checkbox({ + displayName: 'Clean Output', + required: false, + defaultValue: true, + description: 'Whether the scraped output should be cleaned.', + }), + }, + async run(context) { + const { + query, + country, + location, + language, + dateRange, + page, + scrapeResults, + numResultsToScrape, + scrapeFormat, + cleanedOutput, + } = context.propsValue; + + const requestBody: Record = { + query, + }; + + // Add optional parameters if provided + if (country) requestBody['country'] = country; + if (location) requestBody['location'] = location; + if (language) requestBody['language'] = language; + if (dateRange) requestBody['dateRange'] = dateRange; + if (page) requestBody['page'] = page; + if (scrapeResults !== undefined) requestBody['scrapeResults'] = scrapeResults; + if (numResultsToScrape) requestBody['numResultsToScrape'] = numResultsToScrape; + + // Add scrape options if scraping is enabled + if (scrapeResults) { + requestBody['scrapeOptions'] = {}; + if (scrapeFormat) requestBody['scrapeOptions']['format'] = scrapeFormat; + if (cleanedOutput !== undefined) requestBody['scrapeOptions']['cleaned'] = cleanedOutput; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://app.dumplingai.com/api/v1/search', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${context.auth}`, + }, + body: requestBody, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/dumpling-ai/tsconfig.json b/packages/pieces/community/dumpling-ai/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/dumpling-ai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/dumpling-ai/tsconfig.lib.json b/packages/pieces/community/dumpling-ai/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/dumpling-ai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/dust/.eslintrc.json b/packages/pieces/community/dust/.eslintrc.json new file mode 100644 index 0000000..d7449bb --- /dev/null +++ b/packages/pieces/community/dust/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {}, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/dust/README.md b/packages/pieces/community/dust/README.md new file mode 100644 index 0000000..70fd912 --- /dev/null +++ b/packages/pieces/community/dust/README.md @@ -0,0 +1,7 @@ +# pieces-dust + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-dust` to build the library. diff --git a/packages/pieces/community/dust/package-lock.json b/packages/pieces/community/dust/package-lock.json new file mode 100644 index 0000000..ff09821 --- /dev/null +++ b/packages/pieces/community/dust/package-lock.json @@ -0,0 +1,1014 @@ +{ + "name": "@activepieces/piece-dust", + "version": "0.1.15", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-dust", + "version": "0.1.15", + "dependencies": { + "@dust-tt/client": "1.0.57" + } + }, + "node_modules/@dust-tt/client": { + "version": "1.0.57", + "resolved": "https://registry.npmjs.org/@dust-tt/client/-/client-1.0.57.tgz", + "integrity": "sha512-S48OrRsaSPI2fFhKJythZfRM+ijdmC8JviPMg5nW7k+Nlj/dLcG7c12ZUICeQlToKtpZteDbkTHo245usLua/A==", + "dependencies": { + "@modelcontextprotocol/sdk": "git://github.com/dust-tt/typescript-sdk.git#bca26e405d6db2cf71fd7211234a72ecc46d0215", + "@types/json-schema": "^7.0.15", + "event-source-polyfill": "^1.0.31", + "eventsource-parser": "^1.1.1", + "moment-timezone": "^0.5.46", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.12.1", + "resolved": "git+ssh://git@github.com/dust-tt/typescript-sdk.git#bca26e405d6db2cf71fd7211234a72ecc46d0215", + "integrity": "sha512-Ih4dWwuyVhQjRWso/FtFyU7lmqxtDTY3rlK9l2uUqXBmYbjS3tPqQNapdvrsFlj1C1B3N2KSyTFfnOXxMGzXSA==", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.6", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-source-polyfill": { + "version": "1.0.31", + "resolved": "https://registry.npmjs.org/event-source-polyfill/-/event-source-polyfill-1.0.31.tgz", + "integrity": "sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA==" + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz", + "integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==", + "engines": { + "node": ">=14.18" + } + }, + "node_modules/eventsource/node_modules/eventsource-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.3.tgz", + "integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.48", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", + "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/zod": { + "version": "3.24.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", + "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "peerDependencies": { + "zod": "^3.24.1" + } + } + } +} diff --git a/packages/pieces/community/dust/package.json b/packages/pieces/community/dust/package.json new file mode 100644 index 0000000..e76f071 --- /dev/null +++ b/packages/pieces/community/dust/package.json @@ -0,0 +1,7 @@ +{ + "name": "@activepieces/piece-dust", + "version": "0.1.15", + "dependencies": { + "@dust-tt/client": "1.0.57" + } +} diff --git a/packages/pieces/community/dust/project.json b/packages/pieces/community/dust/project.json new file mode 100644 index 0000000..46d14b7 --- /dev/null +++ b/packages/pieces/community/dust/project.json @@ -0,0 +1,69 @@ +{ + "name": "pieces-dust", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/dust/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/dust", + "tsConfig": "packages/pieces/community/dust/tsconfig.lib.json", + "packageJson": "packages/pieces/community/dust/package.json", + "main": "packages/pieces/community/dust/src/index.ts", + "assets": [ + "packages/pieces/community/dust/*.md", + { + "input": "packages/pieces/community/dust/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + }, + "dependsOn": ["prebuild", "^build"] + }, + "prebuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/pieces/community/dust", + "command": "npm ci" + } + }, + "build-with-deps": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "nx run pieces-dust:prebuild", + "nx run pieces-dust:build", + "nx run pieces-dust:postbuild" + ], + "parallel": false + } + }, + "postbuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "dist/packages/pieces/community/dust", + "command": "npm install" + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-dust {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/dust/src/index.ts b/packages/pieces/community/dust/src/index.ts new file mode 100644 index 0000000..bce56e4 --- /dev/null +++ b/packages/pieces/community/dust/src/index.ts @@ -0,0 +1,75 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createConversation } from './lib/actions/create-conversation'; +import { replyToConversation } from './lib/actions/reply-to-conversation'; +import { upsertDocument } from './lib/actions/upsert-document'; +import { addFragmentToConversation } from './lib/actions/add-fragment-to-conversation'; +import { getConversation } from './lib/actions/get-conversation'; +import { uploadFile } from './lib/actions/upload-file'; +import { DUST_BASE_URL } from './lib/common'; + +export const dustAuth = PieceAuth.CustomAuth({ + description: 'Dust authentication requires an API key.', + required: true, + props: { + apiKey: PieceAuth.SecretText({ + displayName: 'API key', + required: true, + }), + workspaceId: Property.ShortText({ + displayName: 'Dust workspace ID', + required: true, + description: "Can be found in any of the workspace's URL", + }), + region: Property.StaticDropdown({ + displayName: 'Region', + required: false, + defaultValue: 'us', + options: { + options: [ + { label: 'US', value: 'us' }, + { label: 'EU', value: 'eu' }, + ], + }, + }), + }, +}); + +export type DustAuthType = { + apiKey: string; + workspaceId: string; + region?: 'us' | 'eu'; +}; + +export const dust = createPiece({ + displayName: 'Dust', + description: 'Secure messaging and collaboration', + auth: dustAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/dust.png', + authors: ['AdamSelene', 'abuaboud'], + actions: [ + createConversation, + getConversation, + replyToConversation, + addFragmentToConversation, + upsertDocument, + uploadFile, + createCustomApiCallAction({ + baseUrl: (auth) => + `${DUST_BASE_URL[(auth as DustAuthType).region || 'us']}/${ + (auth as DustAuthType).workspaceId + }`, + auth: dustAuth, + authMapping: async (auth) => ({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${(auth as DustAuthType).apiKey}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/dust/src/lib/actions/add-fragment-to-conversation.ts b/packages/pieces/community/dust/src/lib/actions/add-fragment-to-conversation.ts new file mode 100644 index 0000000..084344d --- /dev/null +++ b/packages/pieces/community/dust/src/lib/actions/add-fragment-to-conversation.ts @@ -0,0 +1,59 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { dustAuth, DustAuthType } from '../..'; +import { DUST_BASE_URL } from '../common'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import mime from 'mime-types'; + +export const addFragmentToConversation = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'addFragmentToConversation', + displayName: 'Add fragment to conversation', + description: + 'Create a new content fragment in a conversation. Content fragments are pieces of information that can be inserted in conversations and are passed as context to assistants to when they generate an answer.', + auth: dustAuth, + props: { + conversationId: Property.ShortText({ + displayName: 'Conversation ID', + required: true, + }), + fragment: Property.File({ displayName: 'Fragment', required: true }), + fragmentName: Property.ShortText({ + displayName: 'Fragment name', + required: false, + }), + }, + async run({ auth, propsValue }) { + const mimeType = propsValue.fragmentName + ? mime.lookup(propsValue.fragmentName) || + mime.lookup(propsValue.fragment.filename) + : mime.lookup(propsValue.fragment.filename); + + const dustAuth = auth as DustAuthType; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${DUST_BASE_URL[dustAuth.region || 'us']}/${ + dustAuth.workspaceId + }/assistant/conversations/${propsValue.conversationId}/content_fragments`, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${auth.apiKey}`, + }, + body: JSON.stringify( + { + content: propsValue.fragment.data.toString('utf-8'), + title: propsValue.fragmentName || propsValue.fragment.filename, + contentType: mimeType || 'text/plain', + context: null, + url: null, + }, + (key, value) => (typeof value === 'undefined' ? null : value) + ), + }; + return (await httpClient.sendRequest(request)).body; + }, +}); diff --git a/packages/pieces/community/dust/src/lib/actions/create-conversation.ts b/packages/pieces/community/dust/src/lib/actions/create-conversation.ts new file mode 100644 index 0000000..6e49c89 --- /dev/null +++ b/packages/pieces/community/dust/src/lib/actions/create-conversation.ts @@ -0,0 +1,97 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + assistantProp, + usernameProp, + timezoneProp, + getConversationContent, + timeoutProp, + createClient, +} from '../common'; +import { dustAuth, DustAuthType } from '../..'; +import mime from 'mime-types'; +import { PublicPostConversationsRequestBody } from '@dust-tt/client'; + +export const createConversation = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'createConversation', + displayName: 'Create conversation', + description: 'Create a new conversation with a specific Dust assistant', + auth: dustAuth, + props: { + assistant: assistantProp, + username: usernameProp, + timezone: timezoneProp, + title: Property.ShortText({ displayName: 'Title', required: false }), + query: Property.LongText({ displayName: 'Query', required: false }), + fragment: Property.File({ displayName: 'Fragment', required: false }), + fileId: Property.ShortText({ + displayName: 'File ID', + required: false, + description: + 'ID of the file to be added to the conversation (takes precedence over Fragment)', + }), + fragmentName: Property.ShortText({ + displayName: 'Fragment/file name', + required: false, + }), + timeout: timeoutProp, + }, + async run({ auth, propsValue }) { + const client = createClient(auth as DustAuthType); + + const payload: PublicPostConversationsRequestBody = { + visibility: 'unlisted', + title: propsValue.title || null, + }; + + if (propsValue.query) { + payload.message = { + content: propsValue.query, + mentions: [{ configurationId: propsValue.assistant }], + context: { + timezone: propsValue.timezone, + username: propsValue.username, + email: null, + fullName: null, + profilePictureUrl: null, + }, + }; + } + + if (propsValue.fileId) { + payload.contentFragment = { + title: propsValue.fragmentName || '', + fileId: propsValue.fileId, + }; + } else if (propsValue.fragment) { + const mimeType = propsValue.fragmentName + ? mime.lookup(propsValue.fragmentName) || + mime.lookup(propsValue.fragment.filename) + : mime.lookup(propsValue.fragment.filename); + payload.contentFragment = { + title: propsValue.fragmentName || propsValue.fragment.filename, + content: propsValue.fragment.data.toString('utf-8'), + contentType: mimeType || 'text/plain', + context: null, + url: null, + }; + } + + const response = await client.createConversation(payload); + + if (response.isErr()) { + throw new Error(`API Error: ${response.error.message}`); + } + + const conversationId = response.value.conversation.sId; + if (propsValue.query) { + return await getConversationContent( + conversationId, + propsValue.timeout, + auth as DustAuthType + ); + } else { + return response.value; + } + }, +}); diff --git a/packages/pieces/community/dust/src/lib/actions/get-conversation.ts b/packages/pieces/community/dust/src/lib/actions/get-conversation.ts new file mode 100644 index 0000000..b7d684a --- /dev/null +++ b/packages/pieces/community/dust/src/lib/actions/get-conversation.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getConversationContent, timeoutProp } from '../common'; +import { dustAuth, DustAuthType } from '../..'; + +export const getConversation = createAction({ + name: 'getConversation', + displayName: 'Get existing conversation', + description: 'Get an existing conversation', + auth: dustAuth, + props: { + conversationSid: Property.ShortText({ + displayName: 'Conversation sID', + required: true, + }), + timeout: timeoutProp, + }, + async run({ auth, propsValue }) { + return await getConversationContent( + propsValue.conversationSid, + propsValue.timeout, + auth as DustAuthType + ); + }, +}); diff --git a/packages/pieces/community/dust/src/lib/actions/reply-to-conversation.ts b/packages/pieces/community/dust/src/lib/actions/reply-to-conversation.ts new file mode 100644 index 0000000..2edcdc1 --- /dev/null +++ b/packages/pieces/community/dust/src/lib/actions/reply-to-conversation.ts @@ -0,0 +1,67 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { dustAuth, DustAuthType } from '../..'; +import { + assistantProp, + DUST_BASE_URL, + getConversationContent, + timeoutProp, + timezoneProp, + usernameProp, +} from '../common'; + +export const replyToConversation = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'replyToConversation', + displayName: 'Reply to conversation', + description: 'Send reply to existing conversation', + auth: dustAuth, + props: { + conversationId: Property.ShortText({ + displayName: 'Conversation ID', + required: true, + }), + assistant: assistantProp, + query: Property.LongText({ displayName: 'Query', required: true }), + username: usernameProp, + timezone: timezoneProp, + timeout: timeoutProp, + }, + async run({ auth, propsValue }) { + const dustAuth = auth as DustAuthType; + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${DUST_BASE_URL[dustAuth.region || 'us']}/${ + dustAuth.workspaceId + }/assistant/conversations/${propsValue.conversationId}/messages`, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${auth.apiKey}`, + }, + body: JSON.stringify( + { + content: propsValue.query, + mentions: [{ configurationId: propsValue.assistant }], + context: { + timezone: propsValue.timezone, + username: propsValue.username, + email: null, + fullName: null, + profilePictureUrl: null, + }, + }, + (key, value) => (typeof value === 'undefined' ? null : value) + ), + }; + await httpClient.sendRequest(request); + return await getConversationContent( + propsValue.conversationId, + propsValue.timeout, + dustAuth + ); + }, +}); diff --git a/packages/pieces/community/dust/src/lib/actions/upload-file.ts b/packages/pieces/community/dust/src/lib/actions/upload-file.ts new file mode 100644 index 0000000..511daa3 --- /dev/null +++ b/packages/pieces/community/dust/src/lib/actions/upload-file.ts @@ -0,0 +1,47 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { createClient } from '../common'; +import { dustAuth, DustAuthType } from '../..'; +import mimeTypes from 'mime-types'; +import { FileUploadUrlRequestType } from '@dust-tt/client'; + +export const uploadFile = createAction({ + name: 'uploadFile', + displayName: 'Upload file', + description: 'Upload file to be used in conversation', + auth: dustAuth, + props: { + file: Property.File({ + displayName: 'File', + required: true, + }), + }, + async run({ auth, propsValue: { file } }) { + const client = createClient(auth as DustAuthType); + + const contentType = (mimeTypes.lookup(file.filename) || + 'text/plain') as FileUploadUrlRequestType['contentType']; + + const blob = new Blob([file.data], { type: contentType }); + const formData = new FormData(); + formData.append('file', blob, file.filename); + const fileObject = formData.get('file'); + if (!fileObject || typeof fileObject === 'string') { + throw new Error('File object is missing'); + } + + const response = await client.uploadFile({ + contentType, + fileObject, + fileName: file.filename, + fileSize: blob.size, + useCase: 'conversation', + }); + + if (response.isErr()) { + throw new Error(`API Error: ${response.error.message}`); + } + + return response.value; + }, +}); diff --git a/packages/pieces/community/dust/src/lib/actions/upsert-document.ts b/packages/pieces/community/dust/src/lib/actions/upsert-document.ts new file mode 100644 index 0000000..aaea9a9 --- /dev/null +++ b/packages/pieces/community/dust/src/lib/actions/upsert-document.ts @@ -0,0 +1,77 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { DUST_BASE_URL } from '../common'; +import { dustAuth, DustAuthType } from '../..'; +import mimeTypes from 'mime-types'; + +export const upsertDocument = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'addDocument', + displayName: 'Add or update document', + description: + 'Insert a new document to a Data Source (or update an existing one)', + auth: dustAuth, + props: { + datasource: Property.ShortText({ + displayName: 'Data Source name', + required: true, + }), + documentId: Property.ShortText({ + displayName: 'Document ID', + description: 'The ID of the document you want to insert or replace', + required: true, + }), + content: Property.LongText({ + displayName: 'Document content', + description: 'The text content of the document', + required: true, + }), + sourceUrl: Property.ShortText({ + displayName: 'Source URL', + description: 'The source URL of the document (when cited)', + required: false, + }), + title: Property.ShortText({ + displayName: 'Document title', + description: 'User-friendly title of the document', + required: false, + }), + tags: Property.Array({ + displayName: 'Tags', + required: false, + }), + }, + async run({ auth, propsValue }) { + const dustAuth = auth as DustAuthType; + const tags = propsValue.title + ? [`title:${propsValue.title}`, ...(propsValue.tags as string[])] + : (propsValue.tags as string[]); + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${DUST_BASE_URL[dustAuth.region || 'us']}/${ + dustAuth.workspaceId + }/data_sources/${propsValue.datasource}/documents/${encodeURIComponent( + propsValue.documentId + )}`, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${auth.apiKey}`, + }, + body: JSON.stringify( + { + text: propsValue.content, + source_url: propsValue.sourceUrl, + tags: tags, + }, + (key, value) => (typeof value === 'undefined' ? null : value) + ), + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, +}); diff --git a/packages/pieces/community/dust/src/lib/common.ts b/packages/pieces/community/dust/src/lib/common.ts new file mode 100644 index 0000000..9c27ddd --- /dev/null +++ b/packages/pieces/community/dust/src/lib/common.ts @@ -0,0 +1,137 @@ +import { Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMessageBody, + HttpMethod, +} from '@activepieces/pieces-common'; +import { DustAuthType } from '..'; +import { DustAPI } from '@dust-tt/client'; + +export const DUST_BASE_URL = { + us: 'https://dust.tt/api/v1/w', + eu: 'https://eu.dust.tt/api/v1/w', +}; + +export const createClient = (auth: DustAuthType) => { + return new DustAPI( + { url: auth.region === 'eu' ? 'https://eu.dust.tt' : 'https://dust.tt' }, + { + workspaceId: auth.workspaceId, + apiKey: auth.apiKey, + }, + console + ); +}; + +export const assistantProp = Property.Dropdown({ + displayName: 'Agent', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + } + const client = createClient(auth as DustAuthType); + const response = await client.getAgentConfigurations({}); + + if (response.isErr()) { + throw new Error(`API Error: ${response.error.message}`); + } + + const options = response.value + .filter((agentConfiguration) => agentConfiguration.status === 'active') + .map((agentConfiguration) => { + return { + label: `[${agentConfiguration['scope']}] ${agentConfiguration['name']}`, + value: agentConfiguration['sId'], + }; + }) + .sort((a: { label: string }, b: { label: string }) => + a['label'].localeCompare(b['label']) + ); + return { + options: options, + }; + }, +}); + +export const usernameProp = Property.ShortText({ + displayName: 'Username', + required: true, +}); +export const timezoneProp = Property.ShortText({ + displayName: 'Time zone', + required: true, + defaultValue: 'Europe/Paris', +}); +export const timeoutProp = Property.Number({ + displayName: 'Timeout (seconds)', + required: true, + defaultValue: 120, +}); + +export async function getConversationContent( + conversationId: string, + timeout: number, + auth: DustAuthType +) { + const getConversation = async (conversationId: string) => { + return httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${DUST_BASE_URL[auth.region || 'us']}/${ + auth.workspaceId + }/assistant/conversations/${conversationId}`, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${auth.apiKey}`, + }, + }); + }; + + let conversation = await getConversation(conversationId); + + let retries = 0; + const maxRetries = timeout / 10; + while ( + !['succeeded', 'failed'].includes( + getConversationStatus(conversation.body) + ) && + retries < maxRetries + ) { + await new Promise((f) => setTimeout(f, 10000)); + + conversation = await getConversation(conversationId); + retries += 1; + } + + const conversationStatus = getConversationStatus(conversation.body); + if (conversationStatus != 'succeeded') { + if (retries >= maxRetries) { + throw new Error( + `Could not load conversation ${conversationId} after ${timeout}s - ${conversationStatus} - consider increasing timeout value` + ); + } else { + const error = getConversationError(conversation.body); + throw new Error( + `Could not load conversation ${conversationId} - ${conversationStatus}: ${error.message} (${error.code})` + ); + } + } + + return conversation.body; +} + +function getConversationStatus(conversation: HttpMessageBody): string { + return conversation['conversation']['content']?.at(-1)?.at(0)?.status; +} + +function getConversationError(conversation: HttpMessageBody): { + code: string; + message: string; +} { + return conversation['conversation']['content']?.at(-1)?.at(0)?.error; +} diff --git a/packages/pieces/community/dust/tsconfig.json b/packages/pieces/community/dust/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/dust/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/dust/tsconfig.lib.json b/packages/pieces/community/dust/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/dust/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/elevenlabs/.eslintrc.json b/packages/pieces/community/elevenlabs/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/elevenlabs/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/elevenlabs/README.md b/packages/pieces/community/elevenlabs/README.md new file mode 100644 index 0000000..a49efa8 --- /dev/null +++ b/packages/pieces/community/elevenlabs/README.md @@ -0,0 +1,7 @@ +# pieces-elevenlabs + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-elevenlabs` to build the library. diff --git a/packages/pieces/community/elevenlabs/package.json b/packages/pieces/community/elevenlabs/package.json new file mode 100644 index 0000000..9214aa6 --- /dev/null +++ b/packages/pieces/community/elevenlabs/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-elevenlabs", + "version": "0.0.1" +} diff --git a/packages/pieces/community/elevenlabs/project.json b/packages/pieces/community/elevenlabs/project.json new file mode 100644 index 0000000..9bc8566 --- /dev/null +++ b/packages/pieces/community/elevenlabs/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-elevenlabs", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/elevenlabs/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/elevenlabs", + "tsConfig": "packages/pieces/community/elevenlabs/tsconfig.lib.json", + "packageJson": "packages/pieces/community/elevenlabs/package.json", + "main": "packages/pieces/community/elevenlabs/src/index.ts", + "assets": [ + "packages/pieces/community/elevenlabs/*.md", + { + "input": "packages/pieces/community/elevenlabs/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-elevenlabs {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/elevenlabs/src/index.ts b/packages/pieces/community/elevenlabs/src/index.ts new file mode 100644 index 0000000..abcccc5 --- /dev/null +++ b/packages/pieces/community/elevenlabs/src/index.ts @@ -0,0 +1,56 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { textToSpeech } from './lib/actions/text-to-speech-action'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { ElevenLabsClient } from 'elevenlabs'; +import { PieceCategory } from '@activepieces/shared'; + +const markdownDescription = ` +Follow these instructions to get your API Key: +1. Visit your Elevenlabs dashboard. +2. Once there, click on your account in the bottom left corner. +3. Press Profile + API Key. +4. Copy the API Key. +`; + +export const elevenlabsAuth = PieceAuth.SecretText({ + description: markdownDescription, + displayName: 'API Key', + required: true, + validate: async ({ auth }) => { + try { + const elevenlabs = new ElevenLabsClient({ + apiKey: `${auth}`, + }); + await elevenlabs.user.get(); + return { + valid: true, + }; + } catch (error) { + return { + valid: false, + error: 'Invalid API Key.', + }; + } + }, +}); + +export const elevenlabs = createPiece({ + displayName: 'ElevenLabs', + auth: elevenlabsAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/elevenlabs.png', + authors: ['pfernandez98'], + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + description: 'AI Voice Generator & Text to Speech', + actions: [ + textToSpeech, + createCustomApiCallAction({ + baseUrl: () => 'https://api.elevenlabs.io', + auth: elevenlabsAuth, + authMapping: async (auth) => ({ + 'xi-api-key': `${auth}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/elevenlabs/src/lib/actions/text-to-speech-action.ts b/packages/pieces/community/elevenlabs/src/lib/actions/text-to-speech-action.ts new file mode 100644 index 0000000..7acda30 --- /dev/null +++ b/packages/pieces/community/elevenlabs/src/lib/actions/text-to-speech-action.ts @@ -0,0 +1,77 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { ElevenLabsClient } from 'elevenlabs'; + +export const textToSpeech = createAction({ + description: 'Convert text to speech using Elevenlabs', + displayName: 'Text to Speech', + name: 'elevenlabs-text-to-speech', + props: { + voice: Property.Dropdown({ + displayName: 'Voice', + required: true, + description: 'Select the voice for the text to speech', + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Enter your API key first', + options: [], + }; + } + + try { + const request = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.elevenlabs.io/v1/voices`, + headers: { + 'xi-api-key': `${auth}`, + }, + }); + return { + disabled: false, + options: request.body['voices'].map((template: any) => { + return { + label: template.name, + value: template.voice_id, + }; + }), + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: "Couldn't load voices, check your API key.", + }; + } + }, + }), + text: Property.LongText({ + displayName: 'Text', + required: true, + description: 'The text to convert to speech', + }), + }, + async run({ auth, propsValue, files }) { + const elevenlabs = new ElevenLabsClient({ + apiKey: `${auth}`, + }); + + const audio = await elevenlabs.generate({ + voice: propsValue.voice, + text: propsValue.text, + }); + + const chunks: any[] = []; + for await (const chunk of audio) { + chunks.push(chunk); + } + + return files.write({ + fileName: 'audio.mp3', + data: Buffer.concat(chunks), + }); + }, +}); diff --git a/packages/pieces/community/elevenlabs/tsconfig.json b/packages/pieces/community/elevenlabs/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/elevenlabs/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/elevenlabs/tsconfig.lib.json b/packages/pieces/community/elevenlabs/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/elevenlabs/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/eth-name-service/.eslintrc.json b/packages/pieces/community/eth-name-service/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/eth-name-service/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/eth-name-service/README.md b/packages/pieces/community/eth-name-service/README.md new file mode 100644 index 0000000..d5cfc73 --- /dev/null +++ b/packages/pieces/community/eth-name-service/README.md @@ -0,0 +1,7 @@ +# pieces-eth-name-service + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-eth-name-service` to build the library. diff --git a/packages/pieces/community/eth-name-service/package.json b/packages/pieces/community/eth-name-service/package.json new file mode 100644 index 0000000..63b9fa6 --- /dev/null +++ b/packages/pieces/community/eth-name-service/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-eth-name-service", + "version": "0.0.2" +} diff --git a/packages/pieces/community/eth-name-service/project.json b/packages/pieces/community/eth-name-service/project.json new file mode 100644 index 0000000..28404cc --- /dev/null +++ b/packages/pieces/community/eth-name-service/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-eth-name-service", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/eth-name-service/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/eth-name-service", + "tsConfig": "packages/pieces/community/eth-name-service/tsconfig.lib.json", + "packageJson": "packages/pieces/community/eth-name-service/package.json", + "main": "packages/pieces/community/eth-name-service/src/index.ts", + "assets": [ + "packages/pieces/community/eth-name-service/*.md", + { + "input": "packages/pieces/community/eth-name-service/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/eth-name-service/src/index.ts b/packages/pieces/community/eth-name-service/src/index.ts new file mode 100644 index 0000000..b99fa08 --- /dev/null +++ b/packages/pieces/community/eth-name-service/src/index.ts @@ -0,0 +1,16 @@ + +import { createPiece } from '@activepieces/pieces-framework'; +import { listEnsDomains } from './lib/actions/list-ens-domains'; +import { ensCommon } from './lib/common/common'; + +export const ethNameService = createPiece({ + displayName: 'Ethereum Name Service (ENS)', + description: 'Ethereum Name Service (ENS) is a decentralized naming system on the Ethereum blockchain.', + auth: ensCommon.auth, + minimumSupportedRelease: '0.20.0', + logoUrl: 'https://cdn.activepieces.com/pieces/eth-name-service.png', + authors: ['reemayoush'], + actions: [listEnsDomains], + triggers: [], +}); + diff --git a/packages/pieces/community/eth-name-service/src/lib/actions/list-ens-domains.ts b/packages/pieces/community/eth-name-service/src/lib/actions/list-ens-domains.ts new file mode 100644 index 0000000..af98e2b --- /dev/null +++ b/packages/pieces/community/eth-name-service/src/lib/actions/list-ens-domains.ts @@ -0,0 +1,62 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { ensCommon } from '../common/common'; + +export const listEnsDomains = createAction({ + name: 'listEnsDomains', + displayName: 'List ENS Domains', + description: 'List the ENS domains for a given address.', + props: { + address: Property.ShortText({ + displayName: 'Address', + description: 'Address to list ENS domains for', + required: true, + }), + }, + async run(context) { + const { address } = context.propsValue; + + if (!address) { + throw new Error('Address is required'); + } + + const query = ` + { + domains(where: { owner: "${address.toLowerCase()}" }) { + id + name + labelName + labelhash + parent { + id + } + subdomains { + id + name + } + subdomainCount + resolvedAddress { + id + } + resolver { + id + address + } + ttl + isMigrated + createdAt + } + } + `; + + const res = await ensCommon.apiCall( + `https://gateway.thegraph.com/api/${context.auth}/subgraphs/id/5XqPmWe6gjyrJtFn9cLy237i4cWw2j9HcUJEXsP5qGtH`, + 'POST', + { query } + ); + if (res.errors) { + throw new Error(res.errors[0].message); + } + + return res.data.domains; + }, +}); diff --git a/packages/pieces/community/eth-name-service/src/lib/common/common.ts b/packages/pieces/community/eth-name-service/src/lib/common/common.ts new file mode 100644 index 0000000..a228ba1 --- /dev/null +++ b/packages/pieces/community/eth-name-service/src/lib/common/common.ts @@ -0,0 +1,33 @@ +import { + PieceAuth, +} from '@activepieces/pieces-framework'; + +export const ensCommon = { + auth: PieceAuth.SecretText({ + displayName: 'API Key', + description: '*Get your api Key: https://thegraph.com/studio/apikeys', + required: true, + }), + apiCall: async function ( + url: string, + method: string, + data: object | undefined = undefined, + token: string | undefined = undefined + ) { + const headers: Record = { + 'Content-Type': 'application/json', + }; + + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + const response = await fetch(url, { + method, + headers, + body: data ? JSON.stringify(data) : undefined, + }); + + return response.json(); + }, +}; diff --git a/packages/pieces/community/eth-name-service/tsconfig.json b/packages/pieces/community/eth-name-service/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/eth-name-service/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/eth-name-service/tsconfig.lib.json b/packages/pieces/community/eth-name-service/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/eth-name-service/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/exa/.eslintrc.json b/packages/pieces/community/exa/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/exa/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/exa/README.md b/packages/pieces/community/exa/README.md new file mode 100644 index 0000000..051d889 --- /dev/null +++ b/packages/pieces/community/exa/README.md @@ -0,0 +1,7 @@ +# pieces-exa + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-exa` to build the library. diff --git a/packages/pieces/community/exa/package.json b/packages/pieces/community/exa/package.json new file mode 100644 index 0000000..49c0222 --- /dev/null +++ b/packages/pieces/community/exa/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-exa", + "version": "0.0.1" +} diff --git a/packages/pieces/community/exa/project.json b/packages/pieces/community/exa/project.json new file mode 100644 index 0000000..ff0a58d --- /dev/null +++ b/packages/pieces/community/exa/project.json @@ -0,0 +1,45 @@ +{ + "name": "pieces-exa", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/exa/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/exa", + "tsConfig": "packages/pieces/community/exa/tsconfig.lib.json", + "packageJson": "packages/pieces/community/exa/package.json", + "main": "packages/pieces/community/exa/src/index.ts", + "assets": [ + "packages/pieces/community/exa/*.md" + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/exa/src/index.ts b/packages/pieces/community/exa/src/index.ts new file mode 100644 index 0000000..84df5b7 --- /dev/null +++ b/packages/pieces/community/exa/src/index.ts @@ -0,0 +1,59 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { getContentsAction } from './lib/actions/get-contents'; +import { generateAnswerAction } from './lib/actions/generate-answer'; +import { performSearchAction } from './lib/actions/perform-search'; +import { findSimilarLinksAction } from './lib/actions/find-similar-links'; +import { createCustomApiCallAction, HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from './lib/common'; + +const markdownDescription = `Obtain your API key from [Dashboard Setting](https://dashboard.exa.ai/api-keys).`; + +export const exaAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: markdownDescription, + required: true, + validate:async ({auth})=>{ + try + { + await makeRequest(auth,HttpMethod.POST, + '/search',{query:'Activepieces'} + ) + + return{ + valid:true + } + + }catch(e) + { + return{ + valid:false, + error:'Invalid API Key.' + } + } + } +}); + +export const exa = createPiece({ + displayName: 'Exa', + description: 'AI-powered search and content extraction from the web.', + auth: exaAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/exa.png', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE,PieceCategory.PRODUCTIVITY], + authors: ['krushnarout','kishanprmr'], + actions: [ + getContentsAction, + generateAnswerAction, + performSearchAction, + findSimilarLinksAction, + createCustomApiCallAction({ + auth:exaAuth, + baseUrl: () => 'https://api.exa.ai', + authMapping: async (auth) => ({ + 'x-api-key': `${auth}`, + }), + }) + ], + triggers: [], +}); diff --git a/packages/pieces/community/exa/src/lib/actions/find-similar-links.ts b/packages/pieces/community/exa/src/lib/actions/find-similar-links.ts new file mode 100644 index 0000000..751b381 --- /dev/null +++ b/packages/pieces/community/exa/src/lib/actions/find-similar-links.ts @@ -0,0 +1,96 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common'; +import { exaAuth } from '../../index'; + +export const findSimilarLinksAction = createAction({ + name: 'find_similar_links', + displayName: 'Find Similar Links', + description: 'Find pages similar to a given URL.', + auth: exaAuth, + props: { + url: Property.ShortText({ + displayName: 'URL', + description: 'Reference URL to find semantically similar links.', + required: true, + }), + numResults: Property.Number({ + displayName: 'Number of Results', + description: 'Number of results to return (max 100).', + required: false, + }), + includeDomains: Property.Array({ + displayName: 'Include Domains', + description: 'List of domains to include in results.', + required: false, + }), + excludeDomains: Property.Array({ + displayName: 'Exclude Domains', + description: 'List of domains to exclude from results.', + required: false, + }), + startCrawlDate: Property.DateTime({ + displayName: 'Start Crawl Date (ISO)', + description: 'Include links crawled after this date (ISO format).', + required: false, + }), + endCrawlDate: Property.DateTime({ + displayName: 'End Crawl Date (ISO)', + description: 'Include links crawled before this date (ISO format).', + required: false, + }), + startPublishedDate: Property.DateTime({ + displayName: 'Start Published Date (ISO)', + description: 'Only include links published after this date (ISO format).', + required: false, + }), + endPublishedDate: Property.DateTime({ + displayName: 'End Published Date (ISO)', + description: 'Only include links published before this date (ISO format).', + required: false, + }), + includeText: Property.Array({ + displayName: 'Include Text', + description: 'Strings that must be present in the webpage text (max 1 string of up to 5 words).', + required: false, + }), + excludeText: Property.Array({ + displayName: 'Exclude Text', + description: 'Strings that must not be present in the webpage text (max 1 string of up to 5 words).', + required: false, + }), + }, + async run(context) { + const apiKey = context.auth as string; + + const { + url, + numResults, + includeDomains, + excludeDomains, + startCrawlDate, + endCrawlDate, + startPublishedDate, + endPublishedDate, + includeText, + excludeText, + } = context.propsValue; + + const body: Record = { + url, + }; + + if (numResults !== undefined) body['numResults'] = numResults; + if (includeDomains) body['includeDomains'] = includeDomains; + if (excludeDomains) body['excludeDomains'] = excludeDomains; + if (startCrawlDate) body['startCrawlDate'] = startCrawlDate; + if (endCrawlDate) body['endCrawlDate'] = endCrawlDate; + if (startPublishedDate) body['startPublishedDate'] = startPublishedDate; + if (endPublishedDate) body['endPublishedDate'] = endPublishedDate; + if (includeText) body['includeText'] = includeText; + if (excludeText) body['excludeText'] = excludeText; + + const response = await makeRequest(apiKey, HttpMethod.POST, '/findSimilar', body) as {results:Record[]}; + return response.results; + }, +}); diff --git a/packages/pieces/community/exa/src/lib/actions/generate-answer.ts b/packages/pieces/community/exa/src/lib/actions/generate-answer.ts new file mode 100644 index 0000000..9cf529a --- /dev/null +++ b/packages/pieces/community/exa/src/lib/actions/generate-answer.ts @@ -0,0 +1,56 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common'; +import { exaAuth } from '../../index'; + +export const generateAnswerAction = createAction({ + name: 'generate_answer', + displayName: 'Ask AI', + description: 'Provides direct answers to queries by summarizing results.', + auth: exaAuth, + props: { + query: Property.ShortText({ + displayName: 'Query', + description: 'Ask a question to get summarized answers from the web.', + required: true, + }), + text: Property.Checkbox({ + displayName: 'Include Text Content', + description: 'If true, includes full text content from the search results', + required: false, + defaultValue: false, + }), + model: Property.StaticDropdown({ + displayName: 'Model', + description: 'Choose the Exa model to use for the answer.', + required: true, + options: { + options: [ + { label: 'Exa', value: 'exa' }, + { label: 'Exa Pro', value: 'exa-pro' }, + ], + }, + defaultValue: 'exa', + }), + }, + async run(context) { + const apiKey = context.auth as string; + + const { + query, + text, + model, + } = context.propsValue; + + const body: Record = { + query, + text, + model + }; + + + const response = await makeRequest(apiKey, HttpMethod.POST, '/answer', body); + + return response.answer; + }, +}); diff --git a/packages/pieces/community/exa/src/lib/actions/get-contents.ts b/packages/pieces/community/exa/src/lib/actions/get-contents.ts new file mode 100644 index 0000000..986a454 --- /dev/null +++ b/packages/pieces/community/exa/src/lib/actions/get-contents.ts @@ -0,0 +1,69 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common'; +import { exaAuth } from '../../index'; + +export const getContentsAction = createAction({ + name: 'get_contents', + displayName: 'Get Contents', + description: 'Retrieve clean HTML content from specified URLs.', + auth: exaAuth, + props: { + urls: Property.Array({ + displayName: 'URLs', + required: true, + description: 'Array of URLs to crawl', + }), + text: Property.Checkbox({ + displayName: 'Return Full Text', + description: 'If true, returns full page text. If false, disables text return.', + required: false, + defaultValue: true, + }), + livecrawl: Property.StaticDropdown({ + displayName: 'Livecrawl Option', + description: 'Options for livecrawling pages.', + required: false, + options: { + options: [ + { label: 'Never', value: 'never' }, + { label: 'Fallback', value: 'fallback' }, + { label: 'Always', value: 'always' }, + { label: 'Auto', value: 'auto' }, + ], + }, + }), + livecrawlTimeout: Property.Number({ + displayName: 'Livecrawl Timeout (ms)', + description: 'Timeout for livecrawling in milliseconds.', + required: false, + }), + subpages: Property.Number({ + displayName: 'Number of Subpages', + description: 'Number of subpages to crawl.', + required: false, + }), + subpageTarget: Property.ShortText({ + displayName: 'Subpage Target', + description: 'Keyword(s) to find specific subpages.', + required: false, + }), + }, + async run(context) { + const apiKey = context.auth as string; + + const body: Record = { + urls: context.propsValue.urls, + }; + + if (context.propsValue.text !== undefined) body['text'] = context.propsValue.text; + if (context.propsValue.livecrawl) body['livecrawl'] = context.propsValue.livecrawl; + if (context.propsValue.livecrawlTimeout !== undefined) body['livecrawlTimeout'] = context.propsValue.livecrawlTimeout; + if (context.propsValue.subpages !== undefined) body['subpages'] = context.propsValue.subpages; + if (context.propsValue.subpageTarget) body['subpageTarget'] = context.propsValue.subpageTarget; + + + const response = await makeRequest(apiKey, HttpMethod.POST, '/contents', body) as {results:Record[]}; + return response.results; + }, +}); diff --git a/packages/pieces/community/exa/src/lib/actions/perform-search.ts b/packages/pieces/community/exa/src/lib/actions/perform-search.ts new file mode 100644 index 0000000..84e699e --- /dev/null +++ b/packages/pieces/community/exa/src/lib/actions/perform-search.ts @@ -0,0 +1,121 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common'; +import { exaAuth } from '../../index'; + +export const performSearchAction = createAction({ + name: 'perform_search', + displayName: 'Perform Search', + description: "Search the web using semantic or keyword-based search.", + auth: exaAuth, + props: { + query: Property.ShortText({ + displayName: 'Query', + description: 'Search query to find related articles and data.', + required: true, + }), + type: Property.StaticDropdown({ + displayName: 'Search Type', + description: 'Type of search to perform.', + required: false, + defaultValue: 'auto', + options: { + options: [ + { label: 'Auto', value: 'auto' }, + { label: 'Keyword', value: 'keyword' }, + { label: 'Neural', value: 'neural' }, + ], + }, + }), + category: Property.StaticDropdown({ + displayName: 'Category', + description: 'Category of data to focus the search on.', + required: false, + options: { + options: [ + { label: 'Company', value: 'company' }, + { label: 'Research Paper', value: 'research paper' }, + { label: 'News', value: 'news' }, + { label: 'PDF', value: 'pdf' }, + { label: 'GitHub', value: 'github' }, + { label: 'Tweet', value: 'tweet' }, + { label: 'Personal Site', value: 'personal site' }, + { label: 'LinkedIn Profile', value: 'linkedin profile' }, + { label: 'Financial Report', value: 'financial report' }, + ], + }, + }), + numResults: Property.Number({ + displayName: 'Number of Results', + description: 'Number of results to return (max 100).', + required: false, + defaultValue: 10, + }), + includeDomains: Property.Array({ + displayName: 'Include Domains', + description: 'Limit results to only these domains.', + required: false, + }), + excludeDomains: Property.Array({ + displayName: 'Exclude Domains', + description: 'Exclude results from these domains.', + required: false, + }), + startCrawlDate: Property.DateTime({ + displayName: 'Start Crawl Date', + description: 'Only include results crawled after this ISO date.', + required: false, + }), + endCrawlDate: Property.DateTime({ + displayName: 'End Crawl Date', + description: 'Only include results crawled before this ISO date.', + required: false, + }), + startPublishedDate: Property.DateTime({ + displayName: 'Start Published Date', + description: 'Only include results published after this ISO date.', + required: false, + }), + endPublishedDate: Property.DateTime({ + displayName: 'End Published Date', + description: 'Only include results published before this ISO date.', + required: false, + }), + includeText: Property.Array({ + displayName: 'Include Text', + description: 'Strings that must be present in the text of results.', + required: false, + }), + excludeText: Property.Array({ + displayName: 'Exclude Text', + description: 'Strings that must not be present in the text of results.', + required: false, + }), + }, + async run(context) { + const apiKey = context.auth as string; + + const body: Record = { + query: context.propsValue.query, + contents:{ + text:true + } + }; + + const optionalProps = [ + 'type', 'category', 'numResults', 'includeDomains', 'excludeDomains', + 'startCrawlDate', 'endCrawlDate', 'startPublishedDate', 'endPublishedDate', + 'includeText', 'excludeText', + ]; + + for (const prop of optionalProps) { + const val = context.propsValue[prop as keyof typeof context.propsValue]; + if (val !== undefined && val !== null && val !== '') { + body[prop] = val; + } + } + + const response = await makeRequest(apiKey, HttpMethod.POST, '/search', body) as {results:Record[]}; + return response.results; + }, +}); diff --git a/packages/pieces/community/exa/src/lib/common/index.ts b/packages/pieces/community/exa/src/lib/common/index.ts new file mode 100644 index 0000000..7e2d2d4 --- /dev/null +++ b/packages/pieces/community/exa/src/lib/common/index.ts @@ -0,0 +1,17 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +export const BASE_URL = 'https://api.exa.ai'; + +export async function makeRequest(auth: string, method: HttpMethod, path: string, body?: unknown) { + const response = await httpClient.sendRequest({ + method, + url: `${BASE_URL}${path}`, + headers: { + 'x-api-key': `${auth}`, + 'Content-Type': 'application/json', + }, + body, + }); + + return response.body; +} diff --git a/packages/pieces/community/exa/tsconfig.json b/packages/pieces/community/exa/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/exa/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/exa/tsconfig.lib.json b/packages/pieces/community/exa/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/exa/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/facebook-leads/.eslintrc.json b/packages/pieces/community/facebook-leads/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/facebook-leads/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/facebook-leads/README.md b/packages/pieces/community/facebook-leads/README.md new file mode 100644 index 0000000..844d5ee --- /dev/null +++ b/packages/pieces/community/facebook-leads/README.md @@ -0,0 +1,7 @@ +# pieces-facebook-leads + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-facebook-leads` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/facebook-leads/package.json b/packages/pieces/community/facebook-leads/package.json new file mode 100644 index 0000000..147f106 --- /dev/null +++ b/packages/pieces/community/facebook-leads/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-facebook-leads", + "version": "0.2.0" +} \ No newline at end of file diff --git a/packages/pieces/community/facebook-leads/project.json b/packages/pieces/community/facebook-leads/project.json new file mode 100644 index 0000000..ae43ed7 --- /dev/null +++ b/packages/pieces/community/facebook-leads/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-facebook-leads", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/facebook-leads/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/facebook-leads", + "tsConfig": "packages/pieces/community/facebook-leads/tsconfig.lib.json", + "packageJson": "packages/pieces/community/facebook-leads/package.json", + "main": "packages/pieces/community/facebook-leads/src/index.ts", + "assets": [ + "packages/pieces/community/facebook-leads/*.md", + { + "input": "packages/pieces/community/facebook-leads/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/facebook-leads/src/index.ts b/packages/pieces/community/facebook-leads/src/index.ts new file mode 100644 index 0000000..a42fc71 --- /dev/null +++ b/packages/pieces/community/facebook-leads/src/index.ts @@ -0,0 +1,71 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { newLead } from './lib/triggers/new-lead'; +import crypto from 'node:crypto'; + +export const facebookLeadsAuth = PieceAuth.OAuth2({ + description: '', + authUrl: 'https://graph.facebook.com/oauth/authorize', + tokenUrl: 'https://graph.facebook.com/oauth/access_token', + required: true, + scope: [ + 'pages_show_list', + 'pages_manage_ads', + 'leads_retrieval', + 'pages_manage_metadata', + 'business_management', + ], +}); + +export const facebookLeads = createPiece({ + displayName: 'Facebook Leads', + description: 'Capture leads from Facebook', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/facebook.png', + authors: ['kishanprmr', 'MoShizzle', 'khaledmashaly', 'abuaboud', 'AbdulTheActivePiecer'], + categories: [PieceCategory.MARKETING], + auth: facebookLeadsAuth, + actions: [], + triggers: [newLead], + events: { + parseAndReply: (context) => { + const payload = context.payload; + const payloadBody = payload.body as PayloadBody; + if (payload.queryParams['hub.verify_token'] == 'activepieces') { + return { + reply: { + body: payload.queryParams['hub.challenge'], + headers: {}, + }, + }; + } + return { + event: 'lead', + identifierValue: payloadBody.entry[0].changes[0].value.page_id, + }; + }, + verify: ({ webhookSecret, payload }) => { + // https://developers.facebook.com/docs/messenger-platform/webhooks#validate-payloads + + const signature = payload.headers['x-hub-signature-256']; + const elements = signature.split('='); + const signatureHash = elements[1]; + + const hmac = crypto.createHmac('sha256', webhookSecret as string); + hmac.update(payload.rawBody as any); + const computedSignature = hmac.digest('hex'); + + return signatureHash === computedSignature; + }, + }, +}); + +type PayloadBody = { + entry: { + changes: { + value: { + page_id: string; + }; + }[]; + }[]; +}; diff --git a/packages/pieces/community/facebook-leads/src/lib/common/index.ts b/packages/pieces/community/facebook-leads/src/lib/common/index.ts new file mode 100644 index 0000000..f2e2016 --- /dev/null +++ b/packages/pieces/community/facebook-leads/src/lib/common/index.ts @@ -0,0 +1,212 @@ +import { DropdownOption, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { HttpRequest, HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { + FacebookForm, + FacebookLead, + FacebookPage, + FacebookPageDropdown, + FacebookPaginatedResponse, +} from './types'; + +export const facebookLeadsCommon = { + baseUrl: 'https://graph.facebook.com', + page: Property.Dropdown({ + displayName: 'Page', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Connect your account first.', + }; + } + + try { + const authValue = auth as OAuth2PropertyValue; + + const options: DropdownOption[] = []; + + let nextUrl: string | null = `${facebookLeadsCommon.baseUrl}/me/accounts`; + + do { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: nextUrl, + queryParams: { + access_token: authValue.access_token, + }, + }); + + const { data, paging } = response.body as FacebookPaginatedResponse; + + const items = data ?? []; + for (const page of items) { + options.push({ + label: page.name, + value: { + id: page.id, + accessToken: page.access_token, + }, + }); + } + + nextUrl = paging?.next ?? null; + } while (nextUrl); + + return { + disabled: false, + options, + }; + } catch (e) { + return { + disabled: true, + options: [], + placeholder: 'Error occured while fetching pages.', + }; + } + }, + }), + form: Property.Dropdown({ + displayName: 'Form', + required: false, + refreshers: ['page'], + options: async ({ page }) => { + if (!page) { + return { + disabled: true, + options: [], + placeholder: 'Select page first.', + }; + } + + try { + const pageDeatils = page as { + id: string; + accessToken: string; + }; + + const options: DropdownOption[] = [ + { + label: 'All Forms (Default)', + value: 'all', + }, + ]; + + let nextUrl: + | string + | null = `${facebookLeadsCommon.baseUrl}/${pageDeatils.id}/leadgen_forms`; + + do { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: nextUrl, + queryParams: { + access_token: pageDeatils.accessToken, + }, + }); + + const { data, paging } = response.body as FacebookPaginatedResponse; + + const items = data ?? []; + for (const form of items) { + options.push({ + label: form.name, + value: form.id, + }); + } + + nextUrl = paging?.next ?? null; + } while (nextUrl); + + return { + disabled: false, + options, + }; + } catch (e) { + return { + disabled: true, + options: [], + placeholder: 'Error occured while fetching forms.', + }; + } + }, + }), + + subscribePageToApp: async (pageId: any, accessToken: string) => { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${facebookLeadsCommon.baseUrl}/${pageId}/subscribed_apps`, + body: { + access_token: accessToken, + subscribed_fields: ['leadgen'], + }, + }; + + await httpClient.sendRequest(request); + }, + + getPageForms: async (pageId: string, accessToken: string) => { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${facebookLeadsCommon.baseUrl}/${pageId}/leadgen_forms`, + queryParams: { + access_token: accessToken, + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body.data; + }, + + getLeadDetails: async (leadId: string, accessToken: string) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${facebookLeadsCommon.baseUrl}/${leadId}`, + queryParams: { + access_token: accessToken, + fields: + 'field_data,created_time,ad_id,ad_name,adset_id,adset_name,campaign_id,campaign_name,form_id,platform', + }, + }); + + return response.body; + }, + + loadSampleData: async (formId: string, accessToken: string) => { + const response = await httpClient.sendRequest>({ + method: HttpMethod.GET, + url: `${facebookLeadsCommon.baseUrl}/${formId}/leads`, + queryParams: { + access_token: accessToken, + fields: + 'field_data,created_time,ad_id,ad_name,adset_id,adset_name,campaign_id,campaign_name,form_id,platform', + }, + }); + + return response.body; + }, + + transformLeadData: (leadData: FacebookLead) => { + return { + lead_id: leadData.id, + form_id: leadData.form_id, + platform: leadData.platform, + ad_id: leadData.ad_id, + ad_name: leadData.ad_name, + adset_id: leadData.adset_id, + adset_name: leadData.adset_name, + campaign_id: leadData.campaign_id, + campaign_name: leadData.campaign_name, + created_time: leadData.created_time, + data: leadData.field_data.reduce( + (acc, field) => ({ + ...acc, + [field.name]: field.values && field.values.length > 0 ? field.values[0] : null, + }), + {}, + ), + }; + }, +}; diff --git a/packages/pieces/community/facebook-leads/src/lib/common/types.ts b/packages/pieces/community/facebook-leads/src/lib/common/types.ts new file mode 100644 index 0000000..13e8230 --- /dev/null +++ b/packages/pieces/community/facebook-leads/src/lib/common/types.ts @@ -0,0 +1,52 @@ +export interface FacebookPaginatedResponse { + data: T[]; + paging?: { + next?: string; + }; +} + +export interface FacebookTriggerPayloadBody { + entry: { + changes: { + value: { + form_id: string; + leadgen_id: string; + }; + }[]; + }[]; +} + +export interface FacebookPage { + id: string; + name: string; + category: string; + category_list: string[]; + access_token: string; + tasks: string[]; +} + +export interface FacebookPageDropdown { + id: string; + accessToken: string; +} + +export interface FacebookForm { + id: string; + locale: string; + name: string; + status: string; +} + +export interface FacebookLead { + field_data: Array<{ name: string; values: any[] }>; + created_time: string; + ad_id: string; + ad_name: string; + adset_id: string; + adset_name: string; + campaign_id: string; + campaign_name: string; + form_id: string; + platform: string; + id: string; +} diff --git a/packages/pieces/community/facebook-leads/src/lib/triggers/new-lead.ts b/packages/pieces/community/facebook-leads/src/lib/triggers/new-lead.ts new file mode 100644 index 0000000..0549a37 --- /dev/null +++ b/packages/pieces/community/facebook-leads/src/lib/triggers/new-lead.ts @@ -0,0 +1,69 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { facebookLeadsCommon } from '../common'; +import { facebookLeadsAuth } from '../..'; +import { FacebookTriggerPayloadBody, FacebookPageDropdown } from '../common/types'; + +export const newLead = createTrigger({ + auth: facebookLeadsAuth, + name: 'new_lead', + displayName: 'New Lead', + description: 'Triggers when a new lead is created.', + type: TriggerStrategy.APP_WEBHOOK, + sampleData: {}, + props: { + page: facebookLeadsCommon.page, + form: facebookLeadsCommon.form, + }, + + async onEnable(context) { + const page = context.propsValue['page'] as FacebookPageDropdown; + await facebookLeadsCommon.subscribePageToApp(page.id, page.accessToken); + + context.app.createListeners({ events: ['lead'], identifierValue: page.id }); + }, + + async onDisable() { + // + }, + async test(context) { + let form = context.propsValue.form as string; + const page = context.propsValue.page as FacebookPageDropdown; + if (form == undefined || form == '' || form == null) { + const forms = await facebookLeadsCommon.getPageForms(page.id, page.accessToken); + + form = forms[0].id; + } + + const response = await facebookLeadsCommon.loadSampleData(form, context.auth.access_token); + return response.data.map((lead) => facebookLeadsCommon.transformLeadData(lead)); + }, + + //Return new lead + async run(context) { + let leadPings: any[] = []; + const leads: any[] = []; + const form = context.propsValue.form; + const payloadBody = context.payload.body as FacebookTriggerPayloadBody; + + if (form !== undefined && form !== '' && form !== null) { + for (const lead of payloadBody.entry) { + if (form == lead.changes[0].value.form_id) { + leadPings.push(lead); + } + } + } else { + leadPings = payloadBody.entry; + } + + for (const lead of leadPings) { + const leadData = await facebookLeadsCommon.getLeadDetails( + lead.changes[0].value.leadgen_id, + context.auth.access_token, + ); + const transformLead = facebookLeadsCommon.transformLeadData(leadData); + leads.push(transformLead); + } + + return leads; + }, +}); diff --git a/packages/pieces/community/facebook-leads/tsconfig.json b/packages/pieces/community/facebook-leads/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/facebook-leads/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/facebook-leads/tsconfig.lib.json b/packages/pieces/community/facebook-leads/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/facebook-leads/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/facebook-pages/.eslintrc.json b/packages/pieces/community/facebook-pages/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/facebook-pages/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/facebook-pages/README.md b/packages/pieces/community/facebook-pages/README.md new file mode 100644 index 0000000..737b43a --- /dev/null +++ b/packages/pieces/community/facebook-pages/README.md @@ -0,0 +1,7 @@ +# pieces-facebook-pages + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-facebook-pages` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/facebook-pages/package.json b/packages/pieces/community/facebook-pages/package.json new file mode 100644 index 0000000..f51eb51 --- /dev/null +++ b/packages/pieces/community/facebook-pages/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-facebook-pages", + "version": "0.1.8" +} \ No newline at end of file diff --git a/packages/pieces/community/facebook-pages/project.json b/packages/pieces/community/facebook-pages/project.json new file mode 100644 index 0000000..db66953 --- /dev/null +++ b/packages/pieces/community/facebook-pages/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-facebook-pages", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/facebook-pages/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/facebook-pages", + "tsConfig": "packages/pieces/community/facebook-pages/tsconfig.lib.json", + "packageJson": "packages/pieces/community/facebook-pages/package.json", + "main": "packages/pieces/community/facebook-pages/src/index.ts", + "assets": [ + "packages/pieces/community/facebook-pages/*.md", + { + "input": "packages/pieces/community/facebook-pages/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/facebook-pages/src/index.ts b/packages/pieces/community/facebook-pages/src/index.ts new file mode 100644 index 0000000..835bf6a --- /dev/null +++ b/packages/pieces/community/facebook-pages/src/index.ts @@ -0,0 +1,47 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; + +import { PieceCategory } from '@activepieces/shared'; +import { createPhotoPost } from './lib/actions/create-photo-post'; +import { createPost } from './lib/actions/create-post'; +import { createVideoPost } from './lib/actions/create-video-post'; + +const markdown = ` +To Obtain a Client ID and Client Secret: + +1. Go to https://developers.facebook.com/ +2. Register for a Facebook Developer account. +3. Once login, click "Make a new app" button. +4. Select "Other" for use cases. +5. Choose "Business" as the type of app. +6. Provide application details: custom name and associated email. +7. Once your application is created, you need to add a new "product". +8. Configure a new product of type "Facebook Login Settings". +9. Default settings should be fine, you only need to provide the Redirect URL in "Valid OAuth Redirect URIs" and your domain name in "Allowed Domains for the JavaScript SDK". +10. Finally, get your application ID and application secret from your app dashboard in Settings > Basic. +`; + +export const facebookPagesAuth = PieceAuth.OAuth2({ + description: markdown, + authUrl: 'https://graph.facebook.com/oauth/authorize', + tokenUrl: 'https://graph.facebook.com/oauth/access_token', + required: true, + scope: [ + 'pages_show_list', + 'pages_manage_posts', + 'business_management', + 'pages_read_engagement', + ], +}); + +export const facebookPages = createPiece({ + displayName: 'Facebook Pages', + description: 'Manage your Facebook pages to grow your business', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/facebook.png', + categories: [PieceCategory.MARKETING], + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + auth: facebookPagesAuth, + actions: [createPost, createPhotoPost, createVideoPost], + triggers: [], +}); diff --git a/packages/pieces/community/facebook-pages/src/lib/actions/create-photo-post.ts b/packages/pieces/community/facebook-pages/src/lib/actions/create-photo-post.ts new file mode 100644 index 0000000..cba8b23 --- /dev/null +++ b/packages/pieces/community/facebook-pages/src/lib/actions/create-photo-post.ts @@ -0,0 +1,28 @@ +import { createAction } from '@activepieces/pieces-framework'; + +import { facebookPagesCommon, FacebookPageDropdown } from '../common/common'; +import { facebookPagesAuth } from '../..'; + +export const createPhotoPost = createAction({ + auth: facebookPagesAuth, + + name: 'create_photo_post', + displayName: 'Create Page Photo', + description: 'Create a photo on a Facebook Page you manage', + props: { + page: facebookPagesCommon.page, + photo: facebookPagesCommon.photo, + caption: facebookPagesCommon.caption, + }, + async run(context) { + const page: FacebookPageDropdown = context.propsValue.page!; + + const result = await facebookPagesCommon.createPhotoPost( + page, + context.propsValue.caption, + context.propsValue.photo + ); + + return result; + }, +}); diff --git a/packages/pieces/community/facebook-pages/src/lib/actions/create-post.ts b/packages/pieces/community/facebook-pages/src/lib/actions/create-post.ts new file mode 100644 index 0000000..735e4b5 --- /dev/null +++ b/packages/pieces/community/facebook-pages/src/lib/actions/create-post.ts @@ -0,0 +1,26 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { facebookPagesCommon, FacebookPageDropdown } from '../common/common'; +import { facebookPagesAuth } from '../..'; + +export const createPost = createAction({ + auth: facebookPagesAuth, + name: 'create_post', + displayName: 'Create Page Post', + description: 'Create a post on a Facebook Page you manage', + props: { + page: facebookPagesCommon.page, + message: facebookPagesCommon.message, + link: facebookPagesCommon.link, + }, + async run(context) { + const page: FacebookPageDropdown = context.propsValue.page!; + + const result = await facebookPagesCommon.createPost( + page, + context.propsValue.message, + context.propsValue.link + ); + + return result; + }, +}); diff --git a/packages/pieces/community/facebook-pages/src/lib/actions/create-video-post.ts b/packages/pieces/community/facebook-pages/src/lib/actions/create-video-post.ts new file mode 100644 index 0000000..ca8c605 --- /dev/null +++ b/packages/pieces/community/facebook-pages/src/lib/actions/create-video-post.ts @@ -0,0 +1,28 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { FacebookPageDropdown, facebookPagesCommon } from '../common/common'; +import { facebookPagesAuth } from '../..'; + +export const createVideoPost = createAction({ + auth: facebookPagesAuth, + name: 'create_video_post', + displayName: 'Create Page Video', + description: 'Create a video on a Facebook Page you manage', + props: { + page: facebookPagesCommon.page, + video: facebookPagesCommon.video, + title: facebookPagesCommon.title, + description: facebookPagesCommon.description, + }, + async run(context) { + const page: FacebookPageDropdown = context.propsValue.page!; + + const result = await facebookPagesCommon.createVideoPost( + page, + context.propsValue.title, + context.propsValue.description, + context.propsValue.video + ); + + return result; + }, +}); diff --git a/packages/pieces/community/facebook-pages/src/lib/common/common.ts b/packages/pieces/community/facebook-pages/src/lib/common/common.ts new file mode 100644 index 0000000..a734bf6 --- /dev/null +++ b/packages/pieces/community/facebook-pages/src/lib/common/common.ts @@ -0,0 +1,163 @@ +import { + HttpMethod, + httpClient, + getAccessTokenOrThrow, +} from '@activepieces/pieces-common'; +import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; + +export const facebookPagesCommon = { + baseUrl: 'https://graph.facebook.com/v17.0', + page: Property.Dropdown({ + displayName: 'Page', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Connect your account', + }; + } + + try { + const accessToken: string = getAccessTokenOrThrow( + auth as OAuth2PropertyValue + ); + const pages: any[] = ( + await facebookPagesCommon.getPages(accessToken) + ).map((page: FacebookPage) => { + return { + label: page.name, + value: { + id: page.id, + accessToken: page.access_token, + }, + }; + }); + + return { + options: pages, + placeholder: 'Choose a page', + }; + } catch (e) { + console.debug(e); + return { + disabled: true, + options: [], + placeholder: 'Connect your account', + }; + } + }, + }), + + message: Property.LongText({ + displayName: 'Message', + required: true, + }), + link: Property.ShortText({ + displayName: 'Link', + required: false, + }), + + caption: Property.LongText({ + displayName: 'Caption', + required: false, + }), + photo: Property.ShortText({ + displayName: 'Photo', + description: 'A URL we can access for the photo', + required: true, + }), + + title: Property.ShortText({ + displayName: 'Title', + required: false, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + video: Property.ShortText({ + displayName: 'Video', + description: 'A URL we can access for the video (Limit: 1GB or 20 minutes)', + required: true, + }), + + getPages: async (accessToken: string) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${facebookPagesCommon.baseUrl}/me/accounts?access_token=${accessToken}`, + }); + + return response.body.data; + }, + + createPost: async ( + page: FacebookPageDropdown, + message: string, + link: string | undefined + ) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${facebookPagesCommon.baseUrl}/${page.id}/feed`, + body: { + access_token: page.accessToken, + message: message, + link: link, + }, + }); + return response.body; + }, + createPhotoPost: async ( + page: FacebookPageDropdown, + caption: string | undefined, + photo: string + ) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${facebookPagesCommon.baseUrl}/${page.id}/photos`, + body: { + access_token: page.accessToken, + url: photo, + caption: caption, + }, + }); + + return response.body; + }, + + createVideoPost: async ( + page: FacebookPageDropdown, + title: string | undefined, + description: string | undefined, + video: string + ) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${facebookPagesCommon.baseUrl}/${page.id}/videos`, + body: { + access_token: page.accessToken, + title: title, + description: description, + file_url: video, + }, + }); + + return response.body; + }, +}; + +export interface FacebookPage { + id: string; + name: string; + category: string; + category_list: string[]; + access_token: string; + tasks: string[]; +} + +export interface FacebookPageDropdown { + id: string; + accessToken: string; +} diff --git a/packages/pieces/community/facebook-pages/tsconfig.json b/packages/pieces/community/facebook-pages/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/facebook-pages/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/facebook-pages/tsconfig.lib.json b/packages/pieces/community/facebook-pages/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/facebook-pages/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/figma/.babelrc b/packages/pieces/community/figma/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/figma/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/figma/.eslintrc.json b/packages/pieces/community/figma/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/figma/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/figma/README.md b/packages/pieces/community/figma/README.md new file mode 100644 index 0000000..a0728d3 --- /dev/null +++ b/packages/pieces/community/figma/README.md @@ -0,0 +1,7 @@ +# pieces-figma + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-figma` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/figma/package.json b/packages/pieces/community/figma/package.json new file mode 100644 index 0000000..372d57e --- /dev/null +++ b/packages/pieces/community/figma/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-figma", + "version": "0.3.5" +} \ No newline at end of file diff --git a/packages/pieces/community/figma/project.json b/packages/pieces/community/figma/project.json new file mode 100644 index 0000000..ccd3d98 --- /dev/null +++ b/packages/pieces/community/figma/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-figma", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/figma/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/figma", + "tsConfig": "packages/pieces/community/figma/tsconfig.lib.json", + "packageJson": "packages/pieces/community/figma/package.json", + "main": "packages/pieces/community/figma/src/index.ts", + "assets": [ + "packages/pieces/community/figma/*.md", + { + "input": "packages/pieces/community/figma/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/figma/src/index.ts b/packages/pieces/community/figma/src/index.ts new file mode 100644 index 0000000..48b83b6 --- /dev/null +++ b/packages/pieces/community/figma/src/index.ts @@ -0,0 +1,41 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; +import { getCommentsAction } from './lib/actions/get-comments-action'; +import { getFileAction } from './lib/actions/get-file-action'; +import { postCommentAction } from './lib/actions/post-comment-action'; +import { newCommentTrigger } from './lib/trigger/new-comment'; + +export const figmaAuth = PieceAuth.OAuth2({ + description: '', + authUrl: 'https://www.figma.com/oauth', + tokenUrl: 'https://www.figma.com/api/oauth/token', + required: true, + scope: ['file_read'], +}); + +export const figma = createPiece({ + displayName: 'Figma', + description: 'Collaborative interface design tool', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/figma.png', + categories: [], + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + auth: figmaAuth, + actions: [ + getFileAction, + getCommentsAction, + postCommentAction, + createCustomApiCallAction({ + baseUrl: () => 'https://api.figma.com', + auth: figmaAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [newCommentTrigger], +}); diff --git a/packages/pieces/community/figma/src/lib/actions/get-comments-action.ts b/packages/pieces/community/figma/src/lib/actions/get-comments-action.ts new file mode 100644 index 0000000..5fcb377 --- /dev/null +++ b/packages/pieces/community/figma/src/lib/actions/get-comments-action.ts @@ -0,0 +1,33 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { assertNotNullOrUndefined } from '@activepieces/shared'; +import { figmaCommon } from '../common'; +import { figmaGetRequest } from '../common/utils'; +import { figmaAuth } from '../../'; + +export const getCommentsAction = createAction({ + auth: figmaAuth, + name: 'get_comments', + displayName: 'Get File Comments', + description: 'Get file comments', + props: { + file_key: Property.ShortText({ + displayName: 'File Key', + description: 'The Figma file key (copy from Figma file URL)', + required: true, + }), + }, + async run(context) { + const token = context.auth.access_token; + const fileKey = context.propsValue.file_key; + + assertNotNullOrUndefined(token, 'token'); + assertNotNullOrUndefined(fileKey, 'file_key'); + + const url = `${figmaCommon.baseUrl}/${figmaCommon.comments}`.replace( + ':file_key', + fileKey + ); + + return figmaGetRequest({ token, url }); + }, +}); diff --git a/packages/pieces/community/figma/src/lib/actions/get-file-action.ts b/packages/pieces/community/figma/src/lib/actions/get-file-action.ts new file mode 100644 index 0000000..632facd --- /dev/null +++ b/packages/pieces/community/figma/src/lib/actions/get-file-action.ts @@ -0,0 +1,33 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { assertNotNullOrUndefined } from '@activepieces/shared'; +import { figmaCommon } from '../common'; +import { figmaGetRequest } from '../common/utils'; +import { figmaAuth } from '../../'; + +export const getFileAction = createAction({ + auth: figmaAuth, + name: 'get_file', + displayName: 'Get File', + description: 'Get file', + props: { + file_key: Property.ShortText({ + displayName: 'File Key', + description: 'The Figma file key (copy from Figma file URL)', + required: true, + }), + }, + async run(context) { + const token = context.auth.access_token; + const fileKey = context.propsValue.file_key; + + assertNotNullOrUndefined(token, 'token'); + assertNotNullOrUndefined(fileKey, 'file_key'); + + const url = `${figmaCommon.baseUrl}/${figmaCommon.files}`.replace( + ':file_key', + fileKey + ); + + return figmaGetRequest({ token, url }); + }, +}); diff --git a/packages/pieces/community/figma/src/lib/actions/post-comment-action.ts b/packages/pieces/community/figma/src/lib/actions/post-comment-action.ts new file mode 100644 index 0000000..4c99a9a --- /dev/null +++ b/packages/pieces/community/figma/src/lib/actions/post-comment-action.ts @@ -0,0 +1,39 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { assertNotNullOrUndefined } from '@activepieces/shared'; +import { figmaCommon } from '../common'; +import { figmaPostRequestWithMessage } from '../common/utils'; +import { figmaAuth } from '../../'; + +export const postCommentAction = createAction({ + auth: figmaAuth, + name: 'post_comment', + displayName: 'Post File Comment', + description: 'Post file comment', + props: { + file_key: Property.ShortText({ + displayName: 'File Key', + description: 'The Figma file key (copy from Figma file URL)', + required: true, + }), + message: Property.LongText({ + displayName: 'Comment', + description: 'Your comment', + required: true, + }), + }, + async run(context) { + const token = context.auth.access_token; + const { file_key, message } = context.propsValue; + + assertNotNullOrUndefined(token, 'token'); + assertNotNullOrUndefined(file_key, 'file_key'); + assertNotNullOrUndefined(message, 'comment'); + + const url = `${figmaCommon.baseUrl}/${figmaCommon.comments}`.replace( + ':file_key', + file_key + ); + + return figmaPostRequestWithMessage({ token, url, message }); + }, +}); diff --git a/packages/pieces/community/figma/src/lib/common/index.ts b/packages/pieces/community/figma/src/lib/common/index.ts new file mode 100644 index 0000000..6b09984 --- /dev/null +++ b/packages/pieces/community/figma/src/lib/common/index.ts @@ -0,0 +1,7 @@ +export const figmaCommon = { + baseUrl: 'https://api.figma.com', + files: 'v1/files/:file_key', + comments: 'v1/files/:file_key/comments', + webhooks: 'v2/webhooks', + webhook: 'v2/webhooks/:webhook_id', +}; diff --git a/packages/pieces/community/figma/src/lib/common/models.ts b/packages/pieces/community/figma/src/lib/common/models.ts new file mode 100644 index 0000000..77fb51b --- /dev/null +++ b/packages/pieces/community/figma/src/lib/common/models.ts @@ -0,0 +1,24 @@ +type User = { + handle: string; + img_url: string; + id: string; +}; + +type Pos = { + x: number; + y: number; +}; + +export type Comment = { + id: string; + uuid: string; + file_key: string; + parent_id: string; + user: User; + created_at: string; + resolved_at: string; + message: string; + reactions: unknown; + client_meta: Pos; + order_id: number; +}; diff --git a/packages/pieces/community/figma/src/lib/common/utils.ts b/packages/pieces/community/figma/src/lib/common/utils.ts new file mode 100644 index 0000000..885941d --- /dev/null +++ b/packages/pieces/community/figma/src/lib/common/utils.ts @@ -0,0 +1,139 @@ +import { + httpClient, + HttpMethod, + HttpRequest, + AuthenticationType, +} from '@activepieces/pieces-common'; + +export const figmaGetRequest = async ({ + token, + url, +}: FigmaGetRequestParams) => { + const request: HttpRequest = { + method: HttpMethod.GET, + url: url, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + }; + + const response = await httpClient.sendRequest(request); + + return { + success: true, + request_body: request.body, + response_body: response.body, + }; +}; + +export const figmaPostRequestWithMessage = async ({ + token, + url, + message, +}: FigmaPostRequestWithMessageParams) => { + const request: HttpRequest = { + method: HttpMethod.POST, + url: url, + body: { + message: message, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + }; + + const response = await httpClient.sendRequest(request); + + return { + success: true, + request_body: request.body, + response_body: response.body, + }; +}; + +export const figmaWebhookPostRequest = async ({ + token, + url, + eventType, + teamId, + endpoint, + passcode, +}: FigmaWebhookPostRequestParams) => { + const request: HttpRequest = { + method: HttpMethod.POST, + url: url, + body: { + event_type: eventType, + team_id: teamId, + endpoint: endpoint, + passcode: passcode, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + }; + + const response = await httpClient.sendRequest(request); + + return { + success: true, + request_body: request.body, + response_body: response.body, + }; +}; + +export const figmaDeleteRequest = async ({ + token, + url, +}: FigmaGetRequestParams) => { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: url, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + }; + + const response = await httpClient.sendRequest(request); + + return { + success: true, + request_body: request.body, + response_body: response.body, + }; +}; + +type FigmaGetRequestParams = { + token: string; + url: string; +}; + +type FigmaPostRequestWithMessageParams = { + token: string; + url: string; + message: string; +}; + +type FigmaPostRequestWithMessageBody = { + message: string; +}; + +type FigmaWebhookPostRequestParams = { + token: string; + url: string; + eventType: string; + teamId: string; + endpoint: string; + passcode: string; +}; + +type FigmaWebhookPostRequestBody = { + event_type: string; + team_id: string; + endpoint: string; + passcode: string; +}; diff --git a/packages/pieces/community/figma/src/lib/trigger/new-comment.ts b/packages/pieces/community/figma/src/lib/trigger/new-comment.ts new file mode 100644 index 0000000..f452d42 --- /dev/null +++ b/packages/pieces/community/figma/src/lib/trigger/new-comment.ts @@ -0,0 +1,96 @@ +import { + createTrigger, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { assertNotNullOrUndefined } from '@activepieces/shared'; +import { nanoid } from 'nanoid'; +import { figmaCommon } from '../common'; +import { figmaWebhookPostRequest, figmaDeleteRequest } from '../common/utils'; +import { figmaAuth } from '../../'; + +type TriggerData = { + webhookId: string; +}; + +const TRIGGER_DATA_STORE_KEY = 'figma_new_comment_trigger_data'; + +export const newCommentTrigger = createTrigger({ + auth: figmaAuth, + name: 'new_comment', + displayName: 'New Comment (Figma Professional plan only)', + description: 'Triggers when a new comment is posted', + type: TriggerStrategy.WEBHOOK, + sampleData: [ + { + id: '12345', + team_id: '1234567890', + event_type: 'FILE_COMMENT', + client_id: null, + endpoint: 'http://localhost:1234/webhook', + passcode: 'figma-passcode', + status: 'ACTIVE', + description: null, + protocol_version: '2', + }, + ], + props: { + team_id: Property.ShortText({ + displayName: 'Team ID', + description: + 'Naviate to team page, copy the Id from the URL after the word team/', + required: true, + }), + }, + + async onEnable(context): Promise { + const token = context.auth.access_token; + const teamId = context.propsValue['team_id']; + + assertNotNullOrUndefined(token, 'token'); + assertNotNullOrUndefined(teamId, 'teamId'); + + const url = `${figmaCommon.baseUrl}/${figmaCommon.webhooks}`; + const eventType = 'FILE_COMMENT'; + const passcode = `figma_passcode_${nanoid()}`; + const endpoint = context.webhookUrl; + + const { response_body } = await figmaWebhookPostRequest({ + token, + url, + eventType, + teamId, + endpoint, + passcode, + }); + + await context.store?.put(TRIGGER_DATA_STORE_KEY, { + webhookId: response_body['id'], + }); + }, + + async onDisable(context): Promise { + const token = context.auth.access_token; + + assertNotNullOrUndefined(token, 'token'); + + const triggerData = await context.store?.get( + TRIGGER_DATA_STORE_KEY + ); + if (triggerData !== null && triggerData !== undefined) { + const url = `${figmaCommon.baseUrl}/${figmaCommon.webhook}`.replace( + ':webhook_id', + triggerData.webhookId + ); + await figmaDeleteRequest({ token, url }); + } + }, + + async run(context) { + const payloadBody = context.payload.body as Record; + if ('event_type' in payloadBody && payloadBody['event_type'] === 'PING') { + return []; + } + return [payloadBody]; + }, +}); diff --git a/packages/pieces/community/figma/tsconfig.json b/packages/pieces/community/figma/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/figma/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/figma/tsconfig.lib.json b/packages/pieces/community/figma/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/figma/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/file-helper/.eslintrc.json b/packages/pieces/community/file-helper/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/file-helper/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/file-helper/README.md b/packages/pieces/community/file-helper/README.md new file mode 100644 index 0000000..32ce35c --- /dev/null +++ b/packages/pieces/community/file-helper/README.md @@ -0,0 +1,7 @@ +# pieces-fileshelper + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-fileshelper` to build the library. diff --git a/packages/pieces/community/file-helper/package.json b/packages/pieces/community/file-helper/package.json new file mode 100644 index 0000000..b18c73b --- /dev/null +++ b/packages/pieces/community/file-helper/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-file-helper", + "version": "0.1.5" +} diff --git a/packages/pieces/community/file-helper/project.json b/packages/pieces/community/file-helper/project.json new file mode 100644 index 0000000..09bd38c --- /dev/null +++ b/packages/pieces/community/file-helper/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-file-helper", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/file-helper/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/file-helper", + "tsConfig": "packages/pieces/community/file-helper/tsconfig.lib.json", + "packageJson": "packages/pieces/community/file-helper/package.json", + "main": "packages/pieces/community/file-helper/src/index.ts", + "assets": [ + "packages/pieces/community/file-helper/*.md", + { + "input": "packages/pieces/community/file-helper/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-filesHelper {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/file-helper/src/index.ts b/packages/pieces/community/file-helper/src/index.ts new file mode 100644 index 0000000..7d8cbd9 --- /dev/null +++ b/packages/pieces/community/file-helper/src/index.ts @@ -0,0 +1,18 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { readFileAction } from './lib/actions/read-file'; +import { createFile } from './lib/actions/create-file'; +import { changeFileEncoding } from './lib/actions/change-file-encoding'; +import { checkFileType} from './lib/actions/check-file-type'; + +export const filesHelper = createPiece({ + displayName: 'Files Helper', + description: 'Read file content and return it in different formats.', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/file-piece.svg', + categories: [PieceCategory.CORE], + authors: ['kishanprmr', 'MoShizzle', 'abuaboud', 'Seb-C'], + actions: [readFileAction, createFile, changeFileEncoding, checkFileType], + triggers: [], +}); diff --git a/packages/pieces/community/file-helper/src/lib/actions/change-file-encoding.ts b/packages/pieces/community/file-helper/src/lib/actions/change-file-encoding.ts new file mode 100644 index 0000000..fea632b --- /dev/null +++ b/packages/pieces/community/file-helper/src/lib/actions/change-file-encoding.ts @@ -0,0 +1,48 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { encodings } from '../common/encodings'; + +export const changeFileEncoding = createAction({ + name: 'change_file_encoding', + displayName: 'Change File Encoding', + description: 'Changes the encoding of a file', + props: { + inputFile: Property.File({ + displayName: 'Source file', + required: true, + }), + inputEncoding: Property.StaticDropdown({ + displayName: 'Source encoding', + required: true, + options: { + options: encodings, + }, + }), + outputFileName: Property.ShortText({ + displayName: 'Output file name', + required: true, + }), + outputEncoding: Property.StaticDropdown({ + displayName: 'Output encoding', + required: true, + options: { + options: encodings, + }, + }), + }, + async run(context) { + const inputFile = context.propsValue.inputFile.data; + const inputEncoding = context.propsValue.inputEncoding as BufferEncoding; + const outputFileName = context.propsValue.outputFileName; + const outputEncoding = context.propsValue.outputEncoding as BufferEncoding; + + // First decode the input buffer using the source encoding + const decodedString = inputFile.toString(inputEncoding); + // Then encode to the target encoding + const encodedBuffer = Buffer.from(decodedString, outputEncoding); + + return context.files.write({ + fileName: outputFileName, + data: encodedBuffer, + }); + }, +}); diff --git a/packages/pieces/community/file-helper/src/lib/actions/check-file-type.ts b/packages/pieces/community/file-helper/src/lib/actions/check-file-type.ts new file mode 100644 index 0000000..3ffdfb0 --- /dev/null +++ b/packages/pieces/community/file-helper/src/lib/actions/check-file-type.ts @@ -0,0 +1,39 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { predefinedMimeTypes } from '../common/mimeTypes'; +import mime from 'mime-types'; + +export const checkFileType = createAction({ + name: 'checkFileType', + displayName: 'Check file type', + description: 'Check MIME type of a file and filter based on selected types', + props: { + file: Property.File({ + displayName: 'File to Check', + required: true, + }), + mimeTypes: Property.StaticDropdown({ + displayName: 'Select MIME Types', + required: true, + options: { + options: predefinedMimeTypes, + }, + description: 'Choose one or more MIME types to check against the file.', + }), + }, + async run(context) { + const file = context.propsValue.file; + + const selectedMimeTypes = context.propsValue.mimeTypes; + + // Determine the MIME type of the file + const fileType = file.extension ? mime.lookup(file.extension) || 'application/octet-stream' : 'application/octet-stream'; + + // Check if the file's MIME type matches any of the selected MIME types. + const isMatch = fileType && selectedMimeTypes.includes(fileType); + + return { + mimeType: fileType || 'unknown', + isMatch, + }; + }, +}); diff --git a/packages/pieces/community/file-helper/src/lib/actions/create-file.ts b/packages/pieces/community/file-helper/src/lib/actions/create-file.ts new file mode 100644 index 0000000..8c7c04e --- /dev/null +++ b/packages/pieces/community/file-helper/src/lib/actions/create-file.ts @@ -0,0 +1,30 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { encodings } from '../common/encodings'; + +export const createFile = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'createFile', + displayName: 'Create file', + description: 'Create file from content', + props: { + content: Property.LongText({ displayName: 'Content', required: true }), + fileName: Property.ShortText({ displayName: 'File name', required: true }), + encoding: Property.StaticDropdown({ + displayName: 'Encoding', + required: true, + defaultValue: 'utf8', + options: { + options: encodings, + }, + }), + + }, + async run({ propsValue, files }) { + const encoding = propsValue.encoding as BufferEncoding ?? 'utf8'; + const fileUrl = await files.write({ + fileName: propsValue.fileName, + data: Buffer.from(propsValue.content, encoding), + }); + return { fileName: propsValue.fileName, url: fileUrl }; + }, +}); diff --git a/packages/pieces/community/file-helper/src/lib/actions/read-file.ts b/packages/pieces/community/file-helper/src/lib/actions/read-file.ts new file mode 100644 index 0000000..b8673a6 --- /dev/null +++ b/packages/pieces/community/file-helper/src/lib/actions/read-file.ts @@ -0,0 +1,57 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import mime from 'mime-types'; + +export const filesOutput = { + Text: 'text', + Base64: 'base64', +}; + +export const readFileAction = createAction({ + name: 'read_file', + displayName: 'Read File', + description: 'Read a file from the file system', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + file: Property.File({ + displayName: 'File', + required: true, + }), + readOptions: Property.StaticDropdown({ + displayName: 'Output format', + description: 'The output format', + required: true, + options: { + options: [ + { label: 'Text', value: filesOutput.Text }, + { label: 'Base64', value: filesOutput.Base64 }, + ], + }, + }), + }, + async run(context) { + const file = context.propsValue.file; + const readOptions = context.propsValue.readOptions; + switch (readOptions) { + case filesOutput.Base64: { + const mimeType = file.extension ? mime.lookup(file.extension) || 'application/octet-stream' : 'application/octet-stream'; + return { + base64WithMimeType: `data:${mimeType};base64,${file.data.toString('base64')}`, + base64: file.data.toString('base64'), + }; + } + case filesOutput.Text: + return { + text: file.data.toString('utf-8'), + }; + default: + throw new Error(`Invalid output format: ${readOptions}`); + } + }, +}); diff --git a/packages/pieces/community/file-helper/src/lib/common/encodings.ts b/packages/pieces/community/file-helper/src/lib/common/encodings.ts new file mode 100644 index 0000000..7a34e8f --- /dev/null +++ b/packages/pieces/community/file-helper/src/lib/common/encodings.ts @@ -0,0 +1,39 @@ +// Checkout https://nodejs.org/api/buffer.html#buffers-and-character-encodings +export const encodings = [ + { + value: 'ascii', + label: 'ASCII', + }, + { + value: 'utf8', + label: 'UTF-8', + }, + { + value: 'utf16le', + label: 'UTF-16LE', + }, + { + value: 'ucs2', + label: 'UCS-2', + }, + { + value: 'base64', + label: 'Base64', + }, + { + value: 'base64url', + label: 'Base64 URL', + }, + { + value: 'latin1', + label: 'Latin1', + }, + { + value: 'binary', + label: 'Binary', + }, + { + value: 'hex', + label: 'Hex', + }, +]; diff --git a/packages/pieces/community/file-helper/src/lib/common/mimeTypes.ts b/packages/pieces/community/file-helper/src/lib/common/mimeTypes.ts new file mode 100644 index 0000000..f4840e8 --- /dev/null +++ b/packages/pieces/community/file-helper/src/lib/common/mimeTypes.ts @@ -0,0 +1,88 @@ +// Check out: https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types +// Check out: https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types/Common_types +export const predefinedMimeTypes = [ + // important MIME types for Web developers + { label: 'Octet-stream', value: 'application/octet-stream' }, + // Textual Files + { label: 'Plain Text', value: 'text/plain' }, + { label: 'CSS Stylesheet', value: 'text/css' }, + { label: 'HTML Document', value: 'text/html' }, + { label: 'JavaScript', value: 'text/javascript' }, + { label: 'CSV File', value: 'text/csv' }, + { label: 'iCalendar Format', value: 'text/calendar' }, + // Image Types + { label: 'APNG Image', value: 'image/apng' }, + { label: 'AVIF Image', value: 'image/avif' }, + { label: 'GIF Image', value: 'image/gif' }, + { label: 'JPEG Image', value: 'image/jpeg' }, + { label: 'PNG Image', value: 'image/png' }, + { label: 'SVG Image', value: 'image/svg+xml' }, + { label: 'WebP Image', value: 'image/webp' }, + { label: 'BMP Image', value: 'image/bmp' }, + { label: 'Icon Format', value: 'image/vnd.microsoft.icon' }, + { label: 'TIFF Image', value: 'image/tiff' }, + // Audio Types + { label: 'AAC Audio', value: 'audio/aac' }, + { label: 'MP3 Audio', value: 'audio/mpeg' }, + { label: 'OGG Audio', value: 'audio/ogg' }, + { label: 'WAV Audio', value: 'audio/wav' }, + { label: 'FLAC Audio', value: 'audio/flac' }, + { label: 'MIDI Audio', value: 'audio/midi' }, + { label: 'WEBM Audio', value: 'audio/webm' }, + // Video Types + { label: 'MP4 Video', value: 'video/mp4' }, + { label: 'WebM Video', value: 'video/webm' }, + { label: 'OGG Video', value: 'video/ogg' }, + { label: 'AVI Video', value: 'video/x-msvideo' }, + { label: 'MPEG Video', value: 'video/mpeg' }, + { label: '3GPP Video', value: 'video/3gpp' }, + { label: '3GPP2 Video', value: 'video/3gpp2' }, + // Font Types + { label: 'EOT Font', value: 'application/vnd.ms-fontobject' }, + { label: 'OpenType Font', value: 'font/otf' }, + { label: 'TrueType Font', value: 'font/ttf' }, + { label: 'WOFF Font', value: 'font/woff' }, + { label: 'WOFF2 Font', value: 'font/woff2' }, + // Archive and Compressed Files + { label: 'BZip Archive', value: 'application/x-bzip' }, + { label: 'BZip2 Archive', value: 'application/x-bzip2' }, + { label: 'GZip Archive', value: 'application/gzip' }, + { label: 'RAR Archive', value: 'application/vnd.rar' }, + { label: 'TAR Archive', value: 'application/x-tar' }, + { label: 'ZIP Archive', value: 'application/zip' }, + { label: '7-Zip Archive', value: 'application/x-7z-compressed' }, + // Document Types + { label: 'AbiWord Document', value: 'application/x-abiword' }, + { label: 'PDF', value: 'application/pdf' }, + { label: 'Microsoft Word', value: 'application/msword' }, + { label: 'Microsoft Word (OpenXML)', value: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }, + { label: 'Microsoft Excel', value: 'application/vnd.ms-excel' }, + { label: 'Microsoft Excel (OpenXML)', value: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }, + { label: 'Microsoft PowerPoint', value: 'application/vnd.ms-powerpoint' }, + { label: 'Microsoft PowerPoint (OpenXML)', value: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' }, + { label: 'OpenDocument Presentation', value: 'application/vnd.oasis.opendocument.presentation' }, + { label: 'OpenDocument Spreadsheet', value: 'application/vnd.oasis.opendocument.spreadsheet' }, + { label: 'OpenDocument Text', value: 'application/vnd.oasis.opendocument.text' }, + { label: 'Rich Text Format', value: 'application/rtf' }, + { label: 'Electronic Publication (EPUB)', value: 'application/epub+zip' }, + { label: 'Amazon Kindle eBook', value: 'application/vnd.amazon.ebook' }, + { label: 'XUL', value: 'application/vnd.mozilla.xul+xml' }, + { label: 'PHP Script', value: 'application/x-httpd-php' }, + { label: 'Java Archive (JAR)', value: 'application/java-archive' }, + { label: 'Microsoft Visio', value: 'application/vnd.visio' }, + { label: 'Apple Installer Package', value: 'application/vnd.apple.installer+xml' }, + // Multipart + { label: 'Form Data (multipart/form-data)', value: 'multipart/form-data' }, + { label: 'Partial Content (multipart/byteranges)', value: 'multipart/byteranges' }, + // Other Important MIME Types + { label: 'JSON', value: 'application/json' }, + { label: 'JSON-LD', value: 'application/ld+json' }, + { label: 'XML', value: 'application/xml' }, + { label: 'XHTML', value: 'application/xhtml+xml' }, + { label: 'C-Shell Script', value: 'application/x-csh' }, + { label: 'Bourne Shell Script', value: 'application/x-sh' }, + { label: 'FreeARC Archive', value: 'application/x-freearc' }, + { label: 'CD Audio', value: 'application/x-cdf' }, + { label: 'MPEG Transport Stream', value: 'video/mp2t' }, + { label: 'Opus Audio in Ogg Container', value: 'audio/opus' }, +] \ No newline at end of file diff --git a/packages/pieces/community/file-helper/tsconfig.json b/packages/pieces/community/file-helper/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/file-helper/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/file-helper/tsconfig.lib.json b/packages/pieces/community/file-helper/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/file-helper/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/firecrawl/.eslintrc.json b/packages/pieces/community/firecrawl/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/firecrawl/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/firecrawl/README.md b/packages/pieces/community/firecrawl/README.md new file mode 100644 index 0000000..23287c1 --- /dev/null +++ b/packages/pieces/community/firecrawl/README.md @@ -0,0 +1,7 @@ +# pieces-firecrawl + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-firecrawl` to build the library. diff --git a/packages/pieces/community/firecrawl/package.json b/packages/pieces/community/firecrawl/package.json new file mode 100644 index 0000000..fcda09c --- /dev/null +++ b/packages/pieces/community/firecrawl/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-firecrawl", + "version": "0.1.0" +} diff --git a/packages/pieces/community/firecrawl/project.json b/packages/pieces/community/firecrawl/project.json new file mode 100644 index 0000000..b35d334 --- /dev/null +++ b/packages/pieces/community/firecrawl/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-firecrawl", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/firecrawl/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/firecrawl", + "tsConfig": "packages/pieces/community/firecrawl/tsconfig.lib.json", + "packageJson": "packages/pieces/community/firecrawl/package.json", + "main": "packages/pieces/community/firecrawl/src/index.ts", + "assets": [ + "packages/pieces/community/firecrawl/*.md", + { + "input": "packages/pieces/community/firecrawl/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/firecrawl/src/index.ts b/packages/pieces/community/firecrawl/src/index.ts new file mode 100644 index 0000000..2c234f8 --- /dev/null +++ b/packages/pieces/community/firecrawl/src/index.ts @@ -0,0 +1,70 @@ +import { createCustomApiCallAction, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { scrape } from './lib/actions/scrape'; +import { startCrawl } from './lib/actions/start-crawl'; +import { crawlResults } from './lib/actions/crawl-results'; + +const markdownDescription = ` +Follow these steps to obtain your Firecrawl API Key: + +1. Visit [Firecrawl](https://firecrawl.dev) and create an account. +2. Log in and navigate to your dashboard. +3. Locate and copy your API key from the API settings section. +`; + +export const firecrawlAuth = PieceAuth.SecretText({ + description: markdownDescription, + displayName: 'API Key', + required: true, + validate: async ({ auth }) => { + try { + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.firecrawl.dev/v1/scrape', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${auth}`, + }, + body: { + url: 'https://www.example.com', + formats: ['json'], + jsonOptions: { + prompt: 'test' + } + }, + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API Key', + }; + } + }, +}); + +export const firecrawl = createPiece({ + displayName: 'Firecrawl', + description: 'Extract structured data from websites using AI with natural language prompts', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/firecrawl.png', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + authors: ["geekyme-fsmk", "geekyme"], + auth: firecrawlAuth, + actions: [ + scrape, + startCrawl, + crawlResults, + createCustomApiCallAction({ + baseUrl: () => 'https://api.firecrawl.dev/v1', + auth: firecrawlAuth, + authMapping: async (auth) => ({ + 'Authorization': `Bearer ${auth}`, + }), + }), + ], + triggers: [], +}); \ No newline at end of file diff --git a/packages/pieces/community/firecrawl/src/lib/actions/crawl-results.ts b/packages/pieces/community/firecrawl/src/lib/actions/crawl-results.ts new file mode 100644 index 0000000..cde4c62 --- /dev/null +++ b/packages/pieces/community/firecrawl/src/lib/actions/crawl-results.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { firecrawlAuth } from '../../index'; + +export const crawlResults = createAction({ + auth: firecrawlAuth, + name: 'crawlResults', + displayName: 'Crawl Results', + description: 'Get the results of a crawl job.', + props: { + crawlId: Property.ShortText({ + displayName: 'Crawl ID', + description: 'The ID of the crawl job to check.', + required: true, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.firecrawl.dev/v1/crawl/${propsValue.crawlId}`, + headers: { + 'Authorization': `Bearer ${auth}`, + }, + }); + + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/firecrawl/src/lib/actions/scrape.ts b/packages/pieces/community/firecrawl/src/lib/actions/scrape.ts new file mode 100644 index 0000000..0d84b51 --- /dev/null +++ b/packages/pieces/community/firecrawl/src/lib/actions/scrape.ts @@ -0,0 +1,165 @@ +import { createAction, Property, DynamicPropsValue, InputPropertyMap } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { firecrawlAuth } from '../../index'; + +export const scrape = createAction({ + auth: firecrawlAuth, + name: 'scrape', + displayName: 'Scrape Website', + description: 'Scrape a website by performing a series of actions like clicking, typing, taking screenshots, and extracting data.', + props: { + url: Property.ShortText({ + displayName: 'Website URL', + description: 'The webpage URL to scrape.', + required: true, + }), + timeout: Property.Number({ + displayName: 'Timeout (ms)', + description: 'Maximum time to wait for the page to load (in milliseconds).', + required: false, + defaultValue: 60000, + }), + useActions: Property.Checkbox({ + displayName: 'Perform Actions Before Scraping', + description: 'Enable to perform a sequence of actions on the page before scraping (like clicking buttons, filling forms, etc.). See [Firecrawl Actions Documentation](https://docs.firecrawl.dev/api-reference/endpoint/scrape#body-actions) for details on available actions and their parameters.', + required: false, + defaultValue: false, + }), + actionProperties: Property.DynamicProperties({ + displayName: 'Action Properties', + description: 'Properties for actions that will be performed on the page.', + required: false, + refreshers: ['useActions'], + props: async (propsValue: Record): Promise => { + const useActions = propsValue['useActions'] as unknown as boolean; + + if (!useActions) { + return {}; + } + + return { + actions: Property.Json({ + displayName: 'Actions', + description: 'Sequence of actions to perform on the page.', + required: false, + defaultValue: [ + { + type: 'wait', + selector: '#example' + }, + { + type: 'write', + selector: '#input', + text: 'Hello World', + }, + { + type: 'click', + selector: '#button', + }, + { + type: 'screenshot', + }, + ], + }), + }; + }, + }), + extractionType: Property.Dropdown({ + displayName: 'Extraction Type', + description: 'Choose how to extract data from the webpage.', + required: true, + refreshers: [], + options: async () => { + return { + options: [ + { label: 'Default', value: 'default' }, + { label: 'Prompt', value: 'prompt' }, + { label: 'JSON Schema', value: 'schema' } + ] + }; + }, + defaultValue: 'default', + }), + extractProperties: Property.DynamicProperties({ + displayName: 'Extraction Properties', + description: 'Properties for data extraction from the webpage.', + required: false, + refreshers: ['extractionType'], + props: async (propsValue: Record): Promise => { + const extractionType = propsValue['extractionType'] as unknown as string; + + if (extractionType === 'default') { + return {}; + } + + if (extractionType === 'prompt') { + return { + prompt: Property.LongText({ + displayName: 'Extraction Prompt', + description: 'Describe what information you want to extract in natural language.', + required: true, + defaultValue: 'Extract the main product information including name, price, and description.', + }), + }; + } + + if (extractionType === 'schema') { + return { + schema: Property.Json({ + displayName: 'Extraction Schema', + description: 'JSON schema defining the structure of data to extract.', + required: true, + defaultValue: { + "type": "object", + "properties": { + "company_name": {"type": "string"}, + "pricing_tiers": {"type": "array", "items": {"type": "string"}}, + "has_free_tier": {"type": "boolean"} + } + }, + }), + }; + } + + return {}; + }, + }), + }, + async run({ auth, propsValue }) { + const body: Record = { + url: propsValue.url, + timeout: propsValue.timeout, + }; + + // Only include actions if the toggle is enabled and actions are provided + if (propsValue.useActions && propsValue.actionProperties && propsValue.actionProperties['actions']) { + body['actions'] = propsValue.actionProperties['actions'] || []; + } + + // Add extraction options based on the selected type + const extractionType = propsValue.extractionType as string; + + if (extractionType !== 'default' && propsValue.extractProperties) { + body['formats'] = ['json']; + body['jsonOptions'] = {}; + + if (extractionType === 'prompt' && propsValue.extractProperties['prompt']) { + body['jsonOptions']['prompt'] = propsValue.extractProperties['prompt']; + } else if (extractionType === 'schema' && propsValue.extractProperties['schema']) { + body['jsonOptions']['schema'] = propsValue.extractProperties['schema']; + } + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.firecrawl.dev/v1/scrape', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${auth}`, + }, + body: body, + }); + + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/firecrawl/src/lib/actions/start-crawl.ts b/packages/pieces/community/firecrawl/src/lib/actions/start-crawl.ts new file mode 100644 index 0000000..5581206 --- /dev/null +++ b/packages/pieces/community/firecrawl/src/lib/actions/start-crawl.ts @@ -0,0 +1,191 @@ +import { createAction, Property, DynamicPropsValue, InputPropertyMap } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { firecrawlAuth } from '../../index'; + +export const startCrawl = createAction({ + auth: firecrawlAuth, + name: 'startCrawl', + displayName: 'Start Crawl', + description: 'Start crawling multiple pages from a website based on specified rules and patterns.', + props: { + url: Property.ShortText({ + displayName: 'URL', + description: 'The base URL to start crawling from.', + required: true, + }), + excludePaths: Property.Array({ + displayName: 'Exclude Paths', + description: 'URL pathname regex patterns that exclude matching URLs from the crawl. For example, if you set "excludePaths": ["blog/.*"] for the base URL firecrawl.dev, any results matching that pattern will be excluded, such as https://www.firecrawl.dev/blog/firecrawl-launch-week-1-recap.', + required: false, + defaultValue: [], + }), + includePaths: Property.Array({ + displayName: 'Include Paths', + description: 'URL pathname regex patterns that include matching URLs in the crawl. Only the paths that match the specified patterns will be included in the response. For example, if you set "includePaths": ["blog/.*"] for the base URL firecrawl.dev, only results matching that pattern will be included, such as https://www.firecrawl.dev/blog/firecrawl-launch-week-1-recap.', + required: false, + defaultValue: [], + }), + maxDepth: Property.Number({ + displayName: 'Maximum Path Depth', + description: 'Maximum depth to crawl relative to the base URL. Basically, the max number of slashes the pathname of a scraped URL may contain.', + required: false, + defaultValue: 10, + }), + maxDiscoveryDepth: Property.Number({ + displayName: 'Maximum Discovery Depth', + description: 'Maximum depth to crawl based on discovery order. The root site and sitemapped pages has a discovery depth of 0. For example, if you set it to 1, and you set ignoreSitemap, you will only crawl the entered URL and all URLs that are linked on that page.', + required: false, + defaultValue: 10, + }), + ignoreSitemap: Property.Checkbox({ + displayName: 'Ignore Sitemap', + description: 'Ignore the website sitemap when crawling', + required: false, + defaultValue: false, + }), + ignoreQueryParameters: Property.Checkbox({ + displayName: 'Ignore Query Parameters', + description: 'Do not re-scrape the same path with different (or none) query parameters', + required: false, + defaultValue: false, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Maximum number of pages to crawl. Default limit is 10000.', + required: false, + defaultValue: 10000, + }), + allowBackwardLinks: Property.Checkbox({ + displayName: 'Allow Backward Links', + description: 'Enables the crawler to navigate from a specific URL to previously linked pages.', + required: false, + defaultValue: false, + }), + allowExternalLinks: Property.Checkbox({ + displayName: 'Allow External Links', + description: 'Allows the crawler to follow links to external websites.', + required: false, + defaultValue: false, + }), + useWebhook: Property.Checkbox({ + displayName: 'Deliver Results to Webhook', + description: 'Enable to send crawl results to a webhook URL.', + required: false, + defaultValue: false, + }), + webhookProperties: Property.DynamicProperties({ + displayName: 'Webhook Properties', + description: 'Properties for webhook configuration.', + required: false, + refreshers: ['useWebhook'], + props: async (propsValue: Record): Promise => { + const useWebhook = propsValue['useWebhook'] as unknown as boolean; + + if (!useWebhook) { + return {}; + } + + return { + webhookUrl: Property.ShortText({ + displayName: 'Webhook URL', + description: 'The URL to send the webhook to. This will trigger for crawl started (crawl.started), every page crawled (crawl.page) and when the crawl is completed (crawl.completed or crawl.failed).', + required: true, + }), + webhookHeaders: Property.Json({ + displayName: 'Webhook Headers', + description: 'Headers to send to the webhook URL.', + required: false, + defaultValue: {}, + }), + webhookMetadata: Property.Json({ + displayName: 'Webhook Metadata', + description: 'Custom metadata that will be included in all webhook payloads for this crawl.', + required: false, + defaultValue: {}, + }), + webhookEvents: Property.Array({ + displayName: 'Webhook Events', + description: 'Type of events that should be sent to the webhook URL. (default: all)', + required: false, + defaultValue: ['completed', 'page', 'failed', 'started'], + }), + }; + }, + }), + }, + async run({ auth, propsValue }) { + const body: Record = { + url: propsValue.url, + }; + + if (propsValue.excludePaths && Array.isArray(propsValue.excludePaths) && propsValue.excludePaths.length > 0) { + body['excludePaths'] = propsValue.excludePaths; + } + + if (propsValue.includePaths && Array.isArray(propsValue.includePaths) && propsValue.includePaths.length > 0) { + body['includePaths'] = propsValue.includePaths; + } + + if (propsValue.maxDepth !== undefined) { + body['maxDepth'] = propsValue.maxDepth; + } + + if (propsValue.maxDiscoveryDepth !== undefined) { + body['maxDiscoveryDepth'] = propsValue.maxDiscoveryDepth; + } + + if (propsValue.ignoreSitemap !== undefined) { + body['ignoreSitemap'] = propsValue.ignoreSitemap; + } + + if (propsValue.ignoreQueryParameters !== undefined) { + body['ignoreQueryParameters'] = propsValue.ignoreQueryParameters; + } + + if (propsValue.limit !== undefined) { + body['limit'] = propsValue.limit; + } + + if (propsValue.allowBackwardLinks !== undefined) { + body['allowBackwardLinks'] = propsValue.allowBackwardLinks; + } + + if (propsValue.allowExternalLinks !== undefined) { + body['allowExternalLinks'] = propsValue.allowExternalLinks; + } + + // Add webhook configuration if enabled + if (propsValue.useWebhook && propsValue.webhookProperties) { + const webhookUrl = propsValue.webhookProperties['webhookUrl']; + if (webhookUrl) { + body['webhook'] = { + url: webhookUrl, + }; + + if (propsValue.webhookProperties['webhookHeaders']) { + body['webhook']['headers'] = propsValue.webhookProperties['webhookHeaders']; + } + + if (propsValue.webhookProperties['webhookMetadata']) { + body['webhook']['metadata'] = propsValue.webhookProperties['webhookMetadata']; + } + + if (propsValue.webhookProperties['webhookEvents'] && Array.isArray(propsValue.webhookProperties['webhookEvents']) && propsValue.webhookProperties['webhookEvents'].length > 0) { + body['webhook']['events'] = propsValue.webhookProperties['webhookEvents']; + } + } + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.firecrawl.dev/v1/crawl', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${auth}`, + }, + body: body, + }); + + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/firecrawl/tsconfig.json b/packages/pieces/community/firecrawl/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/firecrawl/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/firecrawl/tsconfig.lib.json b/packages/pieces/community/firecrawl/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/firecrawl/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/fireflies-ai/.eslintrc.json b/packages/pieces/community/fireflies-ai/.eslintrc.json new file mode 100644 index 0000000..eac708a --- /dev/null +++ b/packages/pieces/community/fireflies-ai/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/fireflies-ai/README.md b/packages/pieces/community/fireflies-ai/README.md new file mode 100644 index 0000000..de5bb42 --- /dev/null +++ b/packages/pieces/community/fireflies-ai/README.md @@ -0,0 +1,7 @@ +# pieces-fireflies-ai + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx build pieces-fireflies-ai` to build the library. diff --git a/packages/pieces/community/fireflies-ai/package.json b/packages/pieces/community/fireflies-ai/package.json new file mode 100644 index 0000000..5e8c7b4 --- /dev/null +++ b/packages/pieces/community/fireflies-ai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-fireflies-ai", + "version": "0.0.1" +} diff --git a/packages/pieces/community/fireflies-ai/project.json b/packages/pieces/community/fireflies-ai/project.json new file mode 100644 index 0000000..251011c --- /dev/null +++ b/packages/pieces/community/fireflies-ai/project.json @@ -0,0 +1,39 @@ +{ + "name": "pieces-fireflies-ai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/fireflies-ai/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/packages/pieces/community/fireflies-ai", + "tsConfig": "packages/pieces/community/fireflies-ai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/fireflies-ai/package.json", + "main": "packages/pieces/community/fireflies-ai/src/index.ts", + "assets": ["packages/pieces/community/fireflies-ai/*.md"], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + } + } +} diff --git a/packages/pieces/community/fireflies-ai/src/index.ts b/packages/pieces/community/fireflies-ai/src/index.ts new file mode 100644 index 0000000..b1d22a1 --- /dev/null +++ b/packages/pieces/community/fireflies-ai/src/index.ts @@ -0,0 +1,39 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; + +import { findMeetingByIdAction } from './lib/actions/find-meeting-by-id'; +import { findRecentMeetingAction } from './lib/actions/find-recent-meeting'; +import { findMeetingByQueryAction } from './lib/actions/find-meeting-by-query'; +import { uploadAudioAction } from './lib/actions/upload-audio'; +import { getUserDetailsAction } from './lib/actions/get-user-details'; +import { newTranscriptionCompletedTrigger } from './lib/triggers/new-transcription-complete'; + +const markdownDescription = ` +To use Fireflies.ai, you need to get an API key: +1. Login to your account at https://fireflies.ai. +2. Navigate to Settings > Developer Settings in the left sidebar. +3. Copy the API key from the API Key section. +`; + +export const firefliesAiAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: markdownDescription, + required: true, +}); + +export const firefliesAi = createPiece({ + displayName: 'Fireflies.ai', + description: 'Meeting assistant that automatically records, transcribes, and analyzes conversations', + logoUrl: 'https://cdn.activepieces.com/pieces/fireflies-ai.png', + authors: ['AnkitSharmaOnGithub'], + auth: firefliesAiAuth, + actions: [ + findMeetingByIdAction, + findRecentMeetingAction, + findMeetingByQueryAction, + uploadAudioAction, + getUserDetailsAction + ], + triggers: [newTranscriptionCompletedTrigger], + categories: [PieceCategory.PRODUCTIVITY], +}); diff --git a/packages/pieces/community/fireflies-ai/src/lib/actions/find-meeting-by-id.ts b/packages/pieces/community/fireflies-ai/src/lib/actions/find-meeting-by-id.ts new file mode 100644 index 0000000..d2830a9 --- /dev/null +++ b/packages/pieces/community/fireflies-ai/src/lib/actions/find-meeting-by-id.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { firefliesAiAuth } from '../../index'; +import { getTranscript } from '../common/queries'; +import { BASE_URL } from '../common'; + +export const findMeetingByIdAction = createAction({ + auth: firefliesAiAuth, + name: 'find-meeting-by-id', + displayName: 'Find Meeting by ID', + description: 'Finds a specific meeting by ID.', + props: { + meetingId: Property.ShortText({ + displayName: 'Meeting ID', + description: 'The ID of the meeting to retrieve.', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest<{ data: { transcript: Record } }>({ + url: BASE_URL, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + body: { + query: getTranscript, + variables: { + transcriptId: context.propsValue.meetingId, + }, + }, + }); + + return response.body.data.transcript; + }, +}); diff --git a/packages/pieces/community/fireflies-ai/src/lib/actions/find-meeting-by-query.ts b/packages/pieces/community/fireflies-ai/src/lib/actions/find-meeting-by-query.ts new file mode 100644 index 0000000..066e21a --- /dev/null +++ b/packages/pieces/community/fireflies-ai/src/lib/actions/find-meeting-by-query.ts @@ -0,0 +1,171 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { firefliesAiAuth } from '../../index'; +import { BASE_URL } from '../common'; + +export const findMeetingByQueryAction = createAction({ + auth: firefliesAiAuth, + name: 'find_meeting_by_query', + displayName: 'Find Meeting by Call Deatils', + description: 'Searches meetings based on provided parameters.', + props: { + title: Property.ShortText({ + displayName: 'Meeting Title', + required: false, + }), + hostEmail: Property.ShortText({ + displayName: 'Host Email', + description: 'Filter meetings by host email.', + required: false, + }), + participantEmail: Property.ShortText({ + displayName: 'Participant Email', + description: 'Filter meetings by participant email', + required: false, + }), + date: Property.DateTime({ + displayName: 'Date', + description: 'Filter meetings on this date (YYYY-MM-DD).', + required: false, + }), + }, + async run({ propsValue, auth }) { + const filterVariables: Record = {}; + + if (propsValue.title) { + filterVariables['title'] = propsValue.title; + } + + if (propsValue.hostEmail) { + filterVariables['hostEmail'] = propsValue.hostEmail; + } + + if (propsValue.participantEmail) { + filterVariables['participantEmail'] = propsValue.participantEmail; + } + + if (propsValue.date) { + // Convert ISO string to milliseconds for the API + const dateMs = new Date(propsValue.date).getTime(); + filterVariables['date'] = dateMs; + } + + const query = ` + query Transcripts( + $title: String + $hostEmail: String + $participantEmail: String + $date: Float + $limit: Int + $skip: Int + ) { + transcripts( + title: $title + host_email: $hostEmail + participant_email: $participantEmail + date: $date + limit: $limit + skip: $skip + ) { + id + dateString + privacy + speakers + { + id + name + } + title + host_email + organizer_email + calendar_id + user + { + user_id + email + name + num_transcripts + recent_meeting + minutes_consumed + is_admin + integrations + } + fireflies_users + participants + date + transcript_url + audio_url + video_url + duration + meeting_attendees + { + displayName + email + phoneNumber + name + location + } + summary + { + keywords + action_items + outline + shorthand_bullet + overview + bullet_gist + gist + short_summary + short_overview + meeting_type + topics_discussed + transcript_chapters + } + cal_id + calendar_type + meeting_link + } + } + `; + + const limit = 50; + let skip = 0; + let hasMore = true; + const meetings = []; + + while (hasMore) { + const variables = { + ...filterVariables, + limit, + skip, + }; + + const response = await httpClient.sendRequest<{ + data: { transcripts: Record[] }; + }>({ + url: BASE_URL, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + body: { + query: query, + variables, + }, + }); + + const transcripts = response?.body?.data?.transcripts || []; + if (transcripts.length === 0) { + hasMore = false; + } else { + meetings.push(...transcripts); + skip += transcripts.length; + } + } + + return { + found: meetings.length !== 0, + meetings, + }; + }, +}); diff --git a/packages/pieces/community/fireflies-ai/src/lib/actions/find-recent-meeting.ts b/packages/pieces/community/fireflies-ai/src/lib/actions/find-recent-meeting.ts new file mode 100644 index 0000000..2552e74 --- /dev/null +++ b/packages/pieces/community/fireflies-ai/src/lib/actions/find-recent-meeting.ts @@ -0,0 +1,76 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { firefliesAiAuth } from '../../index'; +import { getTranscript } from '../common/queries'; +import { isNil } from '@activepieces/shared'; +import { BASE_URL } from '../common'; + +export const findRecentMeetingAction = createAction({ + auth: firefliesAiAuth, + name: 'find_recent_meeting', + displayName: 'Find Recent Meeting', + description: 'Retrieves the latest meeting for a user.', + props: {}, + async run(context) { + const userResponse = await httpClient.sendRequest<{ + data: { user: { recent_meeting?: string } }; + }>({ + url: BASE_URL, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + body: { + query: ` + query User + { + user + { + user_id + recent_transcript + recent_meeting + num_transcripts + name + minutes_consumed + is_admin + integrations + email + } + }`, + variables: {}, + }, + }); + + console.log(JSON.stringify(userResponse, null, 2)); + + if (isNil(userResponse.body.data.user.recent_meeting)) { + return { + found: false, + meeting: {}, + }; + } + + const meetingResponse = await httpClient.sendRequest<{ + data: { transcript: Record }; + }>({ + url: BASE_URL, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + body: { + query: getTranscript, + variables: { + transcriptId: userResponse.body.data.user.recent_meeting, + }, + }, + }); + + return { + found: true, + meeting: meetingResponse.body.data.transcript, + }; + }, +}); diff --git a/packages/pieces/community/fireflies-ai/src/lib/actions/get-user-details.ts b/packages/pieces/community/fireflies-ai/src/lib/actions/get-user-details.ts new file mode 100644 index 0000000..c506f5e --- /dev/null +++ b/packages/pieces/community/fireflies-ai/src/lib/actions/get-user-details.ts @@ -0,0 +1,36 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { firefliesAiAuth } from '../../index'; +import { getUser } from '../common/queries'; +import { BASE_URL } from '../common'; + +export const getUserDetailsAction = createAction({ + auth: firefliesAiAuth, + name: 'get-user-details', + displayName: 'Get User Details', + description: 'Retrieves profile information by ID.', + props: { + userId: Property.ShortText({ + displayName: 'User ID', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest<{ data: { user: Record } }>({ + url: BASE_URL, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + body: { + query: getUser, + variables: { + userId: context.propsValue.userId, + }, + }, + }); + + return response.body.data.user; + }, +}); diff --git a/packages/pieces/community/fireflies-ai/src/lib/actions/upload-audio.ts b/packages/pieces/community/fireflies-ai/src/lib/actions/upload-audio.ts new file mode 100644 index 0000000..5fd78e2 --- /dev/null +++ b/packages/pieces/community/fireflies-ai/src/lib/actions/upload-audio.ts @@ -0,0 +1,65 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { firefliesAiAuth } from '../../index'; +import { BASE_URL } from '../common'; + +export const uploadAudioAction = createAction({ + auth: firefliesAiAuth, + name: 'upload_audio', + displayName: 'Upload Audio', + description: + 'Creates a new meeeting in Fireflies for transcription (requires a publicly accessible URL).', + props: { + audioUrl: Property.ShortText({ + displayName: 'Audio URL', + description: + 'The publicly accessible URL to your audio file (mp3, mp4, wav, m4a, ogg). Fireflies API only accepts URLs, not direct file uploads. For private files, consider using signed URLs with short expiry times.', + required: true, + }), + title: Property.ShortText({ + displayName: 'Title', + required: true, + }), + }, + async run({ propsValue, auth }) { + // GraphQL mutation for uploading audio + const query = ` + mutation uploadAudio($input: AudioUploadInput) { + uploadAudio(input: $input) { + success + title + message + } + } + `; + + // Define interface for the input + interface AudioUploadInput { + url: string; + title: string; + } + + // Create input for the mutation with proper typing + const input: AudioUploadInput = { + url: propsValue.audioUrl, + title: propsValue.title, + }; + + const response = await httpClient.sendRequest({ + url: BASE_URL, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + body: { + query, + variables: { + input, + }, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/fireflies-ai/src/lib/common/index.ts b/packages/pieces/community/fireflies-ai/src/lib/common/index.ts new file mode 100644 index 0000000..a14d502 --- /dev/null +++ b/packages/pieces/community/fireflies-ai/src/lib/common/index.ts @@ -0,0 +1 @@ +export const BASE_URL = 'https://api.fireflies.ai/graphql'; diff --git a/packages/pieces/community/fireflies-ai/src/lib/common/queries.ts b/packages/pieces/community/fireflies-ai/src/lib/common/queries.ts new file mode 100644 index 0000000..7b00e7b --- /dev/null +++ b/packages/pieces/community/fireflies-ai/src/lib/common/queries.ts @@ -0,0 +1,94 @@ +export const getTranscript = +` + query Transcript($transcriptId: String!) + { + transcript(id: $transcriptId) + { + id + dateString + privacy + speakers + { + id + name + } + title + host_email + organizer_email + calendar_id + user + { + user_id + email + name + num_transcripts + recent_meeting + minutes_consumed + is_admin + integrations + } + fireflies_users + participants + date + transcript_url + audio_url + video_url + duration + meeting_attendees + { + displayName + email + phoneNumber + name + location + } + summary + { + keywords + action_items + outline + shorthand_bullet + overview + bullet_gist + gist + short_summary + short_overview + meeting_type + topics_discussed + transcript_chapters + } + cal_id + calendar_type + meeting_info + { + fred_joined + silent_meeting + summary_status + } + meeting_link + } + } +` + +export const getUser = +` +query User($userId: String!) +{ + user(id: $userId) + { + user_id + recent_transcript + recent_meeting + num_transcripts + name + minutes_consumed + is_admin + integrations + email + user_groups + { + name + handle + } + } +}` \ No newline at end of file diff --git a/packages/pieces/community/fireflies-ai/src/lib/triggers/new-transcription-complete.ts b/packages/pieces/community/fireflies-ai/src/lib/triggers/new-transcription-complete.ts new file mode 100644 index 0000000..e10c6d9 --- /dev/null +++ b/packages/pieces/community/fireflies-ai/src/lib/triggers/new-transcription-complete.ts @@ -0,0 +1,155 @@ +import { createTrigger, Property, TriggerStrategy } from '@activepieces/pieces-framework'; +import { firefliesAiAuth } from '../../index'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { getTranscript } from '../common/queries'; +import { BASE_URL } from '../common'; + +export const newTranscriptionCompletedTrigger = createTrigger({ + auth: firefliesAiAuth, + name: 'new_transcription_completed', + displayName: 'New Transcription Completed', + description: 'Triggered when a new meeting is transcribed.', + props: { + webhookInstructions: Property.MarkDown({ + value: ` + ## Fireflies.ai Webhook Setup + To use this trigger, you need to manually set up a webhook in your Fireflies.ai account: + + 1. Login to your Fireflies.ai account. + 2. Navigate to **Settings** > **Developer Settings** in the left sidebar. + 3. Enter the following URL in the webhooks field: + \`\`\`text + {{webhookUrl}} + \`\`\` + 4. Click Save to register the webhook. + + This webhook will be triggered when a meeting transcription is completed. + `, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: undefined, + async onEnable(context) { + // No need to register webhooks programmatically as user will do it manually + }, + async onDisable(context) { + // No need to unregister webhooks as user will do it manually + }, + async test(context) { + const query = ` + query Transcripts( + $limit: Int + $skip: Int + ){ + transcripts( + limit: $limit + skip: $skip + ){ + + id + dateString + privacy + speakers + { + id + name + } + title + host_email + organizer_email + calendar_id + user + { + user_id + email + name + num_transcripts + recent_meeting + minutes_consumed + is_admin + integrations + } + fireflies_users + participants + date + transcript_url + audio_url + video_url + duration + meeting_attendees + { + displayName + email + phoneNumber + name + location + } + summary + { + keywords + action_items + outline + shorthand_bullet + overview + bullet_gist + gist + short_summary + short_overview + meeting_type + topics_discussed + transcript_chapters + } + cal_id + calendar_type + meeting_info + { + fred_joined + silent_meeting + summary_status + } + meeting_link + } + }`; + const response = await httpClient.sendRequest<{ data: { transcripts: Record[] } }>( + { + url: BASE_URL, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + body: { + query, + variables: { + limit: 5, + skip: 0, + }, + }, + }, + ); + return response.body.data.transcripts; + }, + async run(context) { + const payload = context.payload.body as { meetingId: string; eventType: string }; + if (payload.eventType !== 'Transcription completed') { + return []; + } + + const response = await httpClient.sendRequest<{ data: { transcript: Record } }>({ + url:BASE_URL, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + body: { + query: getTranscript, + variables: { + transcriptId: payload.meetingId, + }, + }, + }); + + return [response.body.data.transcript]; + }, +}); diff --git a/packages/pieces/community/fireflies-ai/tsconfig.json b/packages/pieces/community/fireflies-ai/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/fireflies-ai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/fireflies-ai/tsconfig.lib.json b/packages/pieces/community/fireflies-ai/tsconfig.lib.json new file mode 100644 index 0000000..36c0969 --- /dev/null +++ b/packages/pieces/community/fireflies-ai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/packages/pieces/community/fliqr-ai/.eslintrc.json b/packages/pieces/community/fliqr-ai/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/fliqr-ai/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/fliqr-ai/README.md b/packages/pieces/community/fliqr-ai/README.md new file mode 100644 index 0000000..25e3916 --- /dev/null +++ b/packages/pieces/community/fliqr-ai/README.md @@ -0,0 +1,7 @@ +# pieces-fliqr-ai + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-fliqr-ai` to build the library. diff --git a/packages/pieces/community/fliqr-ai/package.json b/packages/pieces/community/fliqr-ai/package.json new file mode 100644 index 0000000..f233db3 --- /dev/null +++ b/packages/pieces/community/fliqr-ai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-fliqr-ai", + "version": "0.0.1" +} diff --git a/packages/pieces/community/fliqr-ai/project.json b/packages/pieces/community/fliqr-ai/project.json new file mode 100644 index 0000000..30d3a79 --- /dev/null +++ b/packages/pieces/community/fliqr-ai/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-fliqr-ai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/fliqr-ai/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/fliqr-ai", + "tsConfig": "packages/pieces/community/fliqr-ai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/fliqr-ai/package.json", + "main": "packages/pieces/community/fliqr-ai/src/index.ts", + "assets": [ + "packages/pieces/community/fliqr-ai/*.md", + { + "input": "packages/pieces/community/fliqr-ai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-fliqr-ai {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/fliqr-ai/src/index.ts b/packages/pieces/community/fliqr-ai/src/index.ts new file mode 100644 index 0000000..f9be92a --- /dev/null +++ b/packages/pieces/community/fliqr-ai/src/index.ts @@ -0,0 +1,62 @@ +import { HttpMethod, createCustomApiCallAction, httpClient } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { getFliqrAccountDetails } from './lib/actions/get-account-details'; +import { fliqrConfig } from './lib/common/models'; +import { getFliqrAccountFlows } from './lib/actions/get-account-flows'; +import { PieceCategory } from '@activepieces/shared'; + +export const fliqrAuth = PieceAuth.SecretText({ + displayName: 'Fliqr API Access Token', + required: true, + description: ` + To obtain your Fliqr API access token, follow these steps: + + 1. Log in to your Fliqr account. + 2. Navigate to Fliqr API Access Token Settings. + 3. Under the Integrations section, find the Fliqr API Access Token. + 4. Click on Copy Token to copy your existing token or click on Generate Token to create a new one. + 5. Copy the token and paste it below in "Fliqr API Access Token". + `, + validate: async (auth) => { + try { + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${fliqrConfig.baseUrl}/accounts/me`, + headers: { + [fliqrConfig.accessTokenHeaderKey]: auth.auth, + }, + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid personal access token', + }; + } + }, +}); + +export const fliqrAi = createPiece({ + displayName: 'Fliqr AI', + description: + 'Omnichannel AI chatbot enhancing customer interactions across WhatsApp, Facebook, Instagram, Telegram, and 6 other platforms.', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/fliqr-ai.png', + authors: ["drona2938"], + categories: [PieceCategory.COMMUNICATION,PieceCategory.CUSTOMER_SUPPORT,PieceCategory.MARKETING], + auth: fliqrAuth, + actions: [ getFliqrAccountDetails, + getFliqrAccountFlows, + createCustomApiCallAction({ + baseUrl: () => fliqrConfig.baseUrl, + auth: fliqrAuth, + authMapping: async (auth) => ({ + [fliqrConfig.accessTokenHeaderKey]: `${auth}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/fliqr-ai/src/lib/actions/get-account-details.ts b/packages/pieces/community/fliqr-ai/src/lib/actions/get-account-details.ts new file mode 100644 index 0000000..1eae265 --- /dev/null +++ b/packages/pieces/community/fliqr-ai/src/lib/actions/get-account-details.ts @@ -0,0 +1,24 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { fliqrAuth } from '../../index'; +import { fliqrConfig } from '../common/models'; + + +export const getFliqrAccountDetails = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'get_fliqr_account_details', + auth: fliqrAuth, + displayName: 'Get Business Account details', + description: 'Get basic account details of business', + props: {}, + async run(context) { + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${fliqrConfig.baseUrl}/accounts/me`, + headers: { + [fliqrConfig.accessTokenHeaderKey]: context.auth, + }, + }); + return res.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/fliqr-ai/src/lib/actions/get-account-flows.ts b/packages/pieces/community/fliqr-ai/src/lib/actions/get-account-flows.ts new file mode 100644 index 0000000..4622e5e --- /dev/null +++ b/packages/pieces/community/fliqr-ai/src/lib/actions/get-account-flows.ts @@ -0,0 +1,23 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { fliqrConfig } from '../common/models'; +import { fliqrAuth } from '../../index'; + +export const getFliqrAccountFlows = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'get_fliqr_account_flows', + auth: fliqrAuth, + displayName: 'Get Account Flows', + description: 'Get all flows from the account', + props: {}, + async run(context) { + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${fliqrConfig.baseUrl}/accounts/flows`, + headers: { + [fliqrConfig.accessTokenHeaderKey]: context.auth, + } + }); + return res.body; + }, +}); diff --git a/packages/pieces/community/fliqr-ai/src/lib/common/models.ts b/packages/pieces/community/fliqr-ai/src/lib/common/models.ts new file mode 100644 index 0000000..09445cd --- /dev/null +++ b/packages/pieces/community/fliqr-ai/src/lib/common/models.ts @@ -0,0 +1,4 @@ +export const fliqrConfig = { + baseUrl: 'https://app.fliqr.ai/api', + accessTokenHeaderKey: 'X-ACCESS-TOKEN', +}; \ No newline at end of file diff --git a/packages/pieces/community/fliqr-ai/tsconfig.json b/packages/pieces/community/fliqr-ai/tsconfig.json new file mode 100644 index 0000000..b512ca3 --- /dev/null +++ b/packages/pieces/community/fliqr-ai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/fliqr-ai/tsconfig.lib.json b/packages/pieces/community/fliqr-ai/tsconfig.lib.json new file mode 100644 index 0000000..56b735c --- /dev/null +++ b/packages/pieces/community/fliqr-ai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} \ No newline at end of file diff --git a/packages/pieces/community/flow-helper/.eslintrc.json b/packages/pieces/community/flow-helper/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/flow-helper/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/flow-helper/README.md b/packages/pieces/community/flow-helper/README.md new file mode 100644 index 0000000..8023acb --- /dev/null +++ b/packages/pieces/community/flow-helper/README.md @@ -0,0 +1,7 @@ +# pieces-flow-helper + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-flow-helper` to build the library. diff --git a/packages/pieces/community/flow-helper/package.json b/packages/pieces/community/flow-helper/package.json new file mode 100644 index 0000000..1943bda --- /dev/null +++ b/packages/pieces/community/flow-helper/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-flow-helper", + "version": "0.0.2" +} diff --git a/packages/pieces/community/flow-helper/project.json b/packages/pieces/community/flow-helper/project.json new file mode 100644 index 0000000..a6a6c49 --- /dev/null +++ b/packages/pieces/community/flow-helper/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-flow-helper", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/flow-helper/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/flow-helper", + "tsConfig": "packages/pieces/community/flow-helper/tsconfig.lib.json", + "packageJson": "packages/pieces/community/flow-helper/package.json", + "main": "packages/pieces/community/flow-helper/src/index.ts", + "assets": [ + "packages/pieces/community/flow-helper/*.md", + { + "input": "packages/pieces/community/flow-helper/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/flow-helper/src/index.ts b/packages/pieces/community/flow-helper/src/index.ts new file mode 100644 index 0000000..5357ef3 --- /dev/null +++ b/packages/pieces/community/flow-helper/src/index.ts @@ -0,0 +1,14 @@ + + import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { getRunId } from "./lib/actions/get-run-id"; + + export const flowHelper = createPiece({ + displayName: "Flow Helper", + auth: PieceAuth.None(), + minimumSupportedRelease: '0.36.1', + logoUrl: "https://cdn.activepieces.com/pieces/flow-helper.svg", + authors: ["AbdulTheActivePiecer"], + actions: [getRunId], + triggers: [], + }); + \ No newline at end of file diff --git a/packages/pieces/community/flow-helper/src/lib/actions/get-run-id.ts b/packages/pieces/community/flow-helper/src/lib/actions/get-run-id.ts new file mode 100644 index 0000000..3edc553 --- /dev/null +++ b/packages/pieces/community/flow-helper/src/lib/actions/get-run-id.ts @@ -0,0 +1,16 @@ +import { createAction } from '@activepieces/pieces-framework'; + +export const getRunId = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'getRunId', + displayName: 'Get Run Info', + description: '', + props: {}, + async run(context) { + const publicUrlWithoutApi = context.server.publicUrl.replace('/api', ''); + return { + id: context.run.id, + url: `${publicUrlWithoutApi}projects/${context.project.id}/runs/${context.run.id}` + } + }, +}); diff --git a/packages/pieces/community/flow-helper/tsconfig.json b/packages/pieces/community/flow-helper/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/flow-helper/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/flow-helper/tsconfig.lib.json b/packages/pieces/community/flow-helper/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/flow-helper/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/flowise/.eslintrc.json b/packages/pieces/community/flowise/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/flowise/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/flowise/README.md b/packages/pieces/community/flowise/README.md new file mode 100644 index 0000000..31c955e --- /dev/null +++ b/packages/pieces/community/flowise/README.md @@ -0,0 +1,7 @@ +# pieces-flowise + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-flowise` to build the library. diff --git a/packages/pieces/community/flowise/package-lock.json b/packages/pieces/community/flowise/package-lock.json new file mode 100644 index 0000000..6176907 --- /dev/null +++ b/packages/pieces/community/flowise/package-lock.json @@ -0,0 +1,29 @@ +{ + "name": "@activepieces/piece-flowise", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-flowise", + "version": "0.0.1", + "devDependencies": { + "@types/is-url": "^1.2.32" + } + }, + "node_modules/@types/is-url": { + "version": "1.2.32", + "resolved": "https://registry.npmjs.org/@types/is-url/-/is-url-1.2.32.tgz", + "integrity": "sha512-46VLdbWI8Sc+hPexQ6NLNR2YpoDyDZIpASHkJQ2Yr+Kf9Giw6LdCTkwOdsnHKPQeh7xTjTmSnxbE8qpxYuCiHA==", + "dev": true + } + }, + "dependencies": { + "@types/is-url": { + "version": "1.2.32", + "resolved": "https://registry.npmjs.org/@types/is-url/-/is-url-1.2.32.tgz", + "integrity": "sha512-46VLdbWI8Sc+hPexQ6NLNR2YpoDyDZIpASHkJQ2Yr+Kf9Giw6LdCTkwOdsnHKPQeh7xTjTmSnxbE8qpxYuCiHA==", + "dev": true + } + } +} diff --git a/packages/pieces/community/flowise/package.json b/packages/pieces/community/flowise/package.json new file mode 100644 index 0000000..49aee48 --- /dev/null +++ b/packages/pieces/community/flowise/package.json @@ -0,0 +1,7 @@ +{ + "name": "@activepieces/piece-flowise", + "version": "0.0.5", + "devDependencies": { + "@types/is-url": "^1.2.32" + } +} \ No newline at end of file diff --git a/packages/pieces/community/flowise/project.json b/packages/pieces/community/flowise/project.json new file mode 100644 index 0000000..7d3745b --- /dev/null +++ b/packages/pieces/community/flowise/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-flowise", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/flowise/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/flowise", + "tsConfig": "packages/pieces/community/flowise/tsconfig.lib.json", + "packageJson": "packages/pieces/community/flowise/package.json", + "main": "packages/pieces/community/flowise/src/index.ts", + "assets": [ + "packages/pieces/community/flowise/*.md", + { + "input": "packages/pieces/community/flowise/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-flowise {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/flowise/src/index.ts b/packages/pieces/community/flowise/src/index.ts new file mode 100644 index 0000000..8a453f1 --- /dev/null +++ b/packages/pieces/community/flowise/src/index.ts @@ -0,0 +1,101 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + createAction, + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; + +const flowiseAuth = PieceAuth.CustomAuth({ + description: 'Enter your Flowise URL and API Key', + props: { + base_url: Property.ShortText({ + displayName: 'Base URL', + description: 'Enter the base URL', + required: true, + }), + access_token: PieceAuth.SecretText({ + displayName: 'API Key', + description: 'Enter the API Key', + required: true, + }), + }, + required: true, +}); + +// /api/v1/prediction/{your-chatflowid} +export const flowisePredict = createAction({ + name: 'make_prediction', + displayName: 'Make Prediction', + description: 'Run Flowise Predict', + auth: flowiseAuth, + props: { + chatflow_id: Property.ShortText({ + displayName: 'Chatflow ID', + description: 'Enter the Chatflow ID', + required: true, + }), + input: Property.ShortText({ + displayName: 'Input/Question', + description: 'Enter the Input/Question', + required: true, + }), + history: Property.Json({ + displayName: 'History', + description: 'Enter the History', + required: false, + }), + overrideConfig: Property.Json({ + displayName: 'Override Config', + description: 'Enter the Override Config', + required: false, + }), + }, + async run(ctx) { + const { base_url, access_token } = ctx.auth; + const chatflow_id = ctx.propsValue['chatflow_id']; + const input = ctx.propsValue['input']; + const url = `${base_url}/api/v1/prediction/${chatflow_id}`; + const headers = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${access_token}`, + }; + const body = { + question: input, + history: ctx.propsValue['history'], + overrideConfig: ctx.propsValue['overrideConfig'], + }; + const response = await fetch(url, { + method: 'POST', + headers, + body: JSON.stringify(body), + }); + const data = await response.json(); + return data; + }, +}); + +export const flowise = createPiece({ + displayName: 'Flowise', + description: 'No-Code AI workflow builder', + + logoUrl: 'https://cdn.activepieces.com/pieces/flowise.png', + auth: flowiseAuth, + minimumSupportedRelease: '0.30.0', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + authors: ["aasimsani","kishanprmr","MoShizzle","abuaboud"], + actions: [ + flowisePredict, + createCustomApiCallAction({ + baseUrl: (auth) => (auth as { base_url: string }).base_url, + auth: flowiseAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${ + (auth as { access_token: string }).access_token + }`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/flowise/tsconfig.json b/packages/pieces/community/flowise/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/flowise/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/flowise/tsconfig.lib.json b/packages/pieces/community/flowise/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/flowise/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/flowlu/.eslintrc.json b/packages/pieces/community/flowlu/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/flowlu/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/flowlu/README.md b/packages/pieces/community/flowlu/README.md new file mode 100644 index 0000000..0b54a44 --- /dev/null +++ b/packages/pieces/community/flowlu/README.md @@ -0,0 +1,7 @@ +# pieces-flowlu + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-flowlu` to build the library. diff --git a/packages/pieces/community/flowlu/package.json b/packages/pieces/community/flowlu/package.json new file mode 100644 index 0000000..adf0b08 --- /dev/null +++ b/packages/pieces/community/flowlu/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-flowlu", + "version": "0.0.5" +} \ No newline at end of file diff --git a/packages/pieces/community/flowlu/project.json b/packages/pieces/community/flowlu/project.json new file mode 100644 index 0000000..afccaae --- /dev/null +++ b/packages/pieces/community/flowlu/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-flowlu", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/flowlu/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/flowlu", + "tsConfig": "packages/pieces/community/flowlu/tsconfig.lib.json", + "packageJson": "packages/pieces/community/flowlu/package.json", + "main": "packages/pieces/community/flowlu/src/index.ts", + "assets": [ + "packages/pieces/community/flowlu/*.md", + { + "input": "packages/pieces/community/flowlu/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-flowlu {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/flowlu/src/index.ts b/packages/pieces/community/flowlu/src/index.ts new file mode 100644 index 0000000..23bbbad --- /dev/null +++ b/packages/pieces/community/flowlu/src/index.ts @@ -0,0 +1,62 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createContactAction } from './lib/actions/accounts/create-contact'; +import { createOrganizationAction } from './lib/actions/accounts/create-organization'; +import { deleteContactAction } from './lib/actions/accounts/delete-contact'; +import { updateContactAction } from './lib/actions/accounts/update-contact'; +import { createOpportunityAction } from './lib/actions/opportunities/create-opportunity'; +import { deleteOpportunityAction } from './lib/actions/opportunities/delete-opportunity'; +import { updateOpportunityAction } from './lib/actions/opportunities/update-opportunity'; +import { createTaskAction } from './lib/actions/tasks/create-task'; +import { deleteTaskAction } from './lib/actions/tasks/delete-task'; +import { getTaskAction } from './lib/actions/tasks/get-task'; +import { updateTaskAction } from './lib/actions/tasks/update-task'; + +export const flowluAuth = PieceAuth.CustomAuth({ + required: true, + description: ` + 1. Log in to your flowlu account. + 2. Click on your profile-pic(top-right) and navigate to **Portal Settings->API Settings**. + 3. Create new API key with any name and appropriate scope. + 4. Copy API Key to your clipboard and paste it in **API Key** field + 5. In the Domain field, enter your company from your account URL address. For example, if your account URL address is https://example.flowlu.com, then your domain is **example**. + `, + props: { + domain: Property.ShortText({ + displayName: 'Domain', + required: true, + }), + apiKey: PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + }), + }, +}); + +export const flowlu = createPiece({ + displayName: 'Flowlu', + description: 'Business management software', + auth: flowluAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/flowlu.png', + categories: [PieceCategory.SALES_AND_CRM], + authors: ["kishanprmr","abuaboud"], + actions: [ + createContactAction, + updateContactAction, + deleteContactAction, + createOrganizationAction, + createOpportunityAction, + updateOpportunityAction, + deleteOpportunityAction, + createTaskAction, + updateTaskAction, + getTaskAction, + deleteTaskAction, + ], + triggers: [], +}); diff --git a/packages/pieces/community/flowlu/src/lib/actions/accounts/create-contact.ts b/packages/pieces/community/flowlu/src/lib/actions/accounts/create-contact.ts new file mode 100644 index 0000000..64c24e3 --- /dev/null +++ b/packages/pieces/community/flowlu/src/lib/actions/accounts/create-contact.ts @@ -0,0 +1,37 @@ +import { + PiecePropValueSchema, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { flowluAuth } from '../../..'; +import { flowluCommon, makeClient } from '../../common'; +import { flowluProps } from '../../common/props'; + +export const createContactAction = createAction({ + auth: flowluAuth, + name: 'flowlu_create_contact', + displayName: 'Create CRM Account(Contact)', + description: 'Creates a new contact in CRM.', + props: { + honorific_title_id: flowluCommon.honorific_title_id(false), + first_name: Property.ShortText({ + displayName: 'First Name', + required: true, + }), + middle_name: Property.ShortText({ + displayName: 'Middle Name', + required: false, + }), + last_name: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + ...flowluProps.account, + }, + async run(context) { + const client = makeClient( + context.auth as PiecePropValueSchema + ); + return await client.createAccount({ type: 2, ...context.propsValue }); + }, +}); diff --git a/packages/pieces/community/flowlu/src/lib/actions/accounts/create-organization.ts b/packages/pieces/community/flowlu/src/lib/actions/accounts/create-organization.ts new file mode 100644 index 0000000..461f6ef --- /dev/null +++ b/packages/pieces/community/flowlu/src/lib/actions/accounts/create-organization.ts @@ -0,0 +1,32 @@ +import { + PiecePropValueSchema, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { flowluAuth } from '../../..'; +import { makeClient } from '../../common'; +import { flowluProps } from '../../common/props'; + +export const createOrganizationAction = createAction({ + auth: flowluAuth, + name: 'flowlu_create_organization', + displayName: 'Create CRM Account(Organization)', + description: 'Creates a new organization in CRM.', + props: { + name: Property.ShortText({ + displayName: 'Organization Name', + required: true, + }), + name_legal_full: Property.ShortText({ + displayName: 'Full legal name for Organization', + required: false, + }), + ...flowluProps.account, + }, + async run(context) { + const client = makeClient( + context.auth as PiecePropValueSchema + ); + return await client.createAccount({ type: 1, ...context.propsValue }); + }, +}); diff --git a/packages/pieces/community/flowlu/src/lib/actions/accounts/delete-contact.ts b/packages/pieces/community/flowlu/src/lib/actions/accounts/delete-contact.ts new file mode 100644 index 0000000..233e3b0 --- /dev/null +++ b/packages/pieces/community/flowlu/src/lib/actions/accounts/delete-contact.ts @@ -0,0 +1,28 @@ +import { + createAction, + PiecePropValueSchema, +} from '@activepieces/pieces-framework'; +import { flowluAuth } from '../../..'; +import { flowluCommon, makeClient } from '../../common'; +import { FlowluEntity, FlowluModule } from '../../common/constants'; + +export const deleteContactAction = createAction({ + auth: flowluAuth, + name: 'flowlu_delete_contact', + displayName: 'Delete CRM Account(Contact)', + description: 'Deletes an existing contact in CRM.', + props: { + id: flowluCommon.contact_id(true), + }, + async run(context) { + const id = context.propsValue.id!; + const client = makeClient( + context.auth as PiecePropValueSchema + ); + return await client.deleteAction( + FlowluModule.CRM, + FlowluEntity.ACCOUNT, + id + ); + }, +}); diff --git a/packages/pieces/community/flowlu/src/lib/actions/accounts/update-contact.ts b/packages/pieces/community/flowlu/src/lib/actions/accounts/update-contact.ts new file mode 100644 index 0000000..8ea3c2e --- /dev/null +++ b/packages/pieces/community/flowlu/src/lib/actions/accounts/update-contact.ts @@ -0,0 +1,39 @@ +import { + PiecePropValueSchema, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { flowluAuth } from '../../..'; +import { flowluCommon, makeClient } from '../../common'; +import { flowluProps } from '../../common/props'; + +export const updateContactAction = createAction({ + auth: flowluAuth, + name: 'flowlu_update_contact', + displayName: 'Update CRM Account(Contact)', + description: 'Updates an existing contact in CRM.', + props: { + id: flowluCommon.contact_id(true), + honorific_title_id: flowluCommon.honorific_title_id(false), + first_name: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + middle_name: Property.ShortText({ + displayName: 'Middle Name', + required: false, + }), + last_name: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + ...flowluProps.account, + }, + async run(context) { + const id = context.propsValue.id!; + const client = makeClient( + context.auth as PiecePropValueSchema + ); + return await client.updateContact(id, { type: 2, ...context.propsValue }); + }, +}); diff --git a/packages/pieces/community/flowlu/src/lib/actions/opportunities/create-opportunity.ts b/packages/pieces/community/flowlu/src/lib/actions/opportunities/create-opportunity.ts new file mode 100644 index 0000000..bead7e4 --- /dev/null +++ b/packages/pieces/community/flowlu/src/lib/actions/opportunities/create-opportunity.ts @@ -0,0 +1,28 @@ +import { + PiecePropValueSchema, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { flowluAuth } from '../../../'; +import { makeClient } from '../../common'; +import { flowluProps } from '../../common/props'; + +export const createOpportunityAction = createAction({ + auth: flowluAuth, + name: 'flowlu_create_opportunity', + displayName: 'Create Opportunity', + description: 'Creates a new opportunity.', + props: { + name: Property.ShortText({ + displayName: 'Title', + required: true, + }), + ...flowluProps.opportunity, + }, + async run(context) { + const client = makeClient( + context.auth as PiecePropValueSchema + ); + return await client.createOpportunity(context.propsValue); + }, +}); diff --git a/packages/pieces/community/flowlu/src/lib/actions/opportunities/delete-opportunity.ts b/packages/pieces/community/flowlu/src/lib/actions/opportunities/delete-opportunity.ts new file mode 100644 index 0000000..742e009 --- /dev/null +++ b/packages/pieces/community/flowlu/src/lib/actions/opportunities/delete-opportunity.ts @@ -0,0 +1,28 @@ +import { + createAction, + PiecePropValueSchema, +} from '@activepieces/pieces-framework'; +import { flowluAuth } from '../../../'; +import { flowluCommon, makeClient } from '../../common'; +import { FlowluEntity, FlowluModule } from '../../common/constants'; + +export const deleteOpportunityAction = createAction({ + auth: flowluAuth, + name: 'flowlu_delete_opportunity', + displayName: 'Delete Opportunity', + description: 'Deletes an existing opportunity.', + props: { + id: flowluCommon.opportunity_id(true), + }, + async run(context) { + const id = context.propsValue.id!; + const client = makeClient( + context.auth as PiecePropValueSchema + ); + return await client.deleteAction( + FlowluModule.CRM, + FlowluEntity.OPPORTUNITY, + id + ); + }, +}); diff --git a/packages/pieces/community/flowlu/src/lib/actions/opportunities/update-opportunity.ts b/packages/pieces/community/flowlu/src/lib/actions/opportunities/update-opportunity.ts new file mode 100644 index 0000000..a6ea17f --- /dev/null +++ b/packages/pieces/community/flowlu/src/lib/actions/opportunities/update-opportunity.ts @@ -0,0 +1,30 @@ +import { + PiecePropValueSchema, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { flowluAuth } from '../../../'; +import { flowluCommon, makeClient } from '../../common'; +import { flowluProps } from '../../common/props'; + +export const updateOpportunityAction = createAction({ + auth: flowluAuth, + name: 'flowlu_update_opportunity', + displayName: 'Update Opportunity', + description: 'Updates an existing opportunity.', + props: { + id: flowluCommon.opportunity_id(true), + name: Property.ShortText({ + displayName: 'Title', + required: false, + }), + ...flowluProps.opportunity, + }, + async run(context) { + const id = context.propsValue.id!; + const client = makeClient( + context.auth as PiecePropValueSchema + ); + return await client.updateOpportunity(id, context.propsValue); + }, +}); diff --git a/packages/pieces/community/flowlu/src/lib/actions/tasks/create-task.ts b/packages/pieces/community/flowlu/src/lib/actions/tasks/create-task.ts new file mode 100644 index 0000000..465e7e3 --- /dev/null +++ b/packages/pieces/community/flowlu/src/lib/actions/tasks/create-task.ts @@ -0,0 +1,47 @@ +import { + PiecePropValueSchema, Property, + createAction, +} from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import { flowluAuth } from '../../../'; +import { makeClient } from '../../common'; +import { flowluProps } from '../../common/props'; + +export const createTaskAction = createAction({ + auth: flowluAuth, + name: 'flowlu_create_task', + displayName: 'Create Task', + description: 'Creates a new task.', + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + ...flowluProps.task, + }, + async run(context) { + const client = makeClient( + context.auth as PiecePropValueSchema + ); + return await client.createTask({ + name: context.propsValue.name, + description: context.propsValue.description, + priority: context.propsValue.priority, + plan_start_date: context.propsValue.plan_start_date + ? dayjs(context.propsValue.plan_start_date).format( + 'YYYY-MM-DD HH:mm:ss' + ) + : undefined, + deadline: context.propsValue.deadline + ? dayjs(context.propsValue.deadline).format('YYYY-MM-DD HH:mm:ss') + : undefined, + deadline_allowchange: context.propsValue.deadline_allowchange ? 1 : 0, + task_checkbyowner: context.propsValue.task_checkbyowner ? 1 : 0, + responsible_id: context.propsValue.responsible_id, + owner_id: context.propsValue.owner_id, + type: context.propsValue.type, + workflow_id: context.propsValue.workflow_id, + workflow_stage_id: context.propsValue.workflow_stage_id, + }); + }, +}); diff --git a/packages/pieces/community/flowlu/src/lib/actions/tasks/delete-task.ts b/packages/pieces/community/flowlu/src/lib/actions/tasks/delete-task.ts new file mode 100644 index 0000000..02ea0f2 --- /dev/null +++ b/packages/pieces/community/flowlu/src/lib/actions/tasks/delete-task.ts @@ -0,0 +1,28 @@ +import { + createAction, + PiecePropValueSchema, +} from '@activepieces/pieces-framework'; +import { flowluAuth } from '../../..'; +import { flowluCommon, makeClient } from '../../common'; +import { FlowluEntity, FlowluModule } from '../../common/constants'; + +export const deleteTaskAction = createAction({ + auth: flowluAuth, + name: 'flowlu_delete_task', + displayName: 'Delete Task', + description: 'Deletes an existing task.', + props: { + task_id: flowluCommon.task_id(true), + }, + async run(context) { + const task_id = context.propsValue.task_id!; + const client = makeClient( + context.auth as PiecePropValueSchema + ); + return await client.deleteAction( + FlowluModule.TASK, + FlowluEntity.TASKS, + task_id + ); + }, +}); diff --git a/packages/pieces/community/flowlu/src/lib/actions/tasks/get-task.ts b/packages/pieces/community/flowlu/src/lib/actions/tasks/get-task.ts new file mode 100644 index 0000000..6ee4af3 --- /dev/null +++ b/packages/pieces/community/flowlu/src/lib/actions/tasks/get-task.ts @@ -0,0 +1,23 @@ +import { + createAction, + PiecePropValueSchema, +} from '@activepieces/pieces-framework'; +import { flowluAuth } from '../../..'; +import { flowluCommon, makeClient } from '../../common'; + +export const getTaskAction = createAction({ + auth: flowluAuth, + name: 'flowlu_get_task', + displayName: 'Get Task', + description: 'Retrieves an existing task.', + props: { + task_id: flowluCommon.task_id(true), + }, + async run(context) { + const task_id = context.propsValue.task_id!; + const client = makeClient( + context.auth as PiecePropValueSchema + ); + return await client.getTask(task_id); + }, +}); diff --git a/packages/pieces/community/flowlu/src/lib/actions/tasks/update-task.ts b/packages/pieces/community/flowlu/src/lib/actions/tasks/update-task.ts new file mode 100644 index 0000000..d724143 --- /dev/null +++ b/packages/pieces/community/flowlu/src/lib/actions/tasks/update-task.ts @@ -0,0 +1,48 @@ +import { + PiecePropValueSchema, Property, + createAction, +} from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import { flowluAuth } from '../../../'; +import { flowluCommon, makeClient } from '../../common'; +import { flowluProps } from '../../common/props'; + +export const updateTaskAction = createAction({ + auth: flowluAuth, + name: 'flowlu_update_task', + displayName: 'Update Task', + description: 'Updates an existing task.', + props: { + task_id: flowluCommon.task_id(true), + name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + ...flowluProps.task, + }, + async run(context) { + const client = makeClient( + context.auth as PiecePropValueSchema + ); + return await client.updateTask(context.propsValue.task_id!, { + name: context.propsValue.name, + description: context.propsValue.description, + priority: context.propsValue.priority, + plan_start_date: context.propsValue.plan_start_date + ? dayjs(context.propsValue.plan_start_date).format( + 'YYYY-MM-DD HH:mm:ss' + ) + : undefined, + deadline: context.propsValue.deadline + ? dayjs(context.propsValue.deadline).format('YYYY-MM-DD HH:mm:ss') + : undefined, + deadline_allowchange: context.propsValue.deadline_allowchange ? 1 : 0, + task_checkbyowner: context.propsValue.task_checkbyowner ? 1 : 0, + type: context.propsValue.type, + responsible_id: context.propsValue.responsible_id, + owner_id: context.propsValue.owner_id, + workflow_id: context.propsValue.workflow_id, + workflow_stage_id: context.propsValue.workflow_stage_id, + }); + }, +}); diff --git a/packages/pieces/community/flowlu/src/lib/common/client.ts b/packages/pieces/community/flowlu/src/lib/common/client.ts new file mode 100644 index 0000000..4004aed --- /dev/null +++ b/packages/pieces/community/flowlu/src/lib/common/client.ts @@ -0,0 +1,213 @@ +import { + HttpMessageBody, + HttpMethod, + QueryParams, + httpClient, +} from '@activepieces/pieces-common'; +import { FlowluEntity, FlowluModule } from './constants'; +import { + Account, + AccountCategory, + AccountHonorificTitle, + AccountIndustry, + CreateCRMAccountAPIRequest, + CreateOpportunityAPIRequest, + CreateTaskAPIRequest, + ListAPIResponse, + Opportunity, + OpportunitySource, + Pipeline, + PipelineStage, + Task, + TaskWorkflow, + TaskWorkflowStage, + User, +} from './types'; + +function emptyValueFilter( + accessor: (key: string) => any +): (key: string) => boolean { + return (key: string) => { + const val = accessor(key); + return ( + val !== null && + val !== undefined && + (typeof val != 'string' || val.length > 0) + ); + }; +} + +export function prepareQuery(request?: Record): QueryParams { + const params: QueryParams = {}; + if (!request) return params; + Object.keys(request) + .filter(emptyValueFilter((k) => request[k])) + .forEach((k: string) => { + params[k] = (request as Record)[k].toString(); + }); + return params; +} + +export class FlowluClient { + constructor(private domain: string, private apiKey: string) {} + async makeRequest( + method: HttpMethod, + resourceUri: string, + query?: QueryParams, + body: any | undefined = undefined + ): Promise { + const res = await httpClient.sendRequest({ + method: method, + url: `https://${this.domain}.flowlu.com/api/v1/module` + resourceUri, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + queryParams: { api_key: this.apiKey, ...query }, + body: body, + }); + return res.body; + } + async createAccount(request: CreateCRMAccountAPIRequest) { + return await this.makeRequest( + HttpMethod.POST, + '/crm/account/create', + undefined, + request + ); + } + async updateContact(id: number, request: CreateCRMAccountAPIRequest) { + return await this.makeRequest( + HttpMethod.POST, + `/crm/account/update/${id}`, + undefined, + request + ); + } + async listAllAccounts() { + return await this.makeRequest>( + HttpMethod.GET, + '/crm/account/list' + ); + } + async listAllContacts() { + return await this.makeRequest>( + HttpMethod.GET, + '/crm/account/list', + { 'filter[type]': '2' } + ); + } + async createTask(request: CreateTaskAPIRequest) { + return await this.makeRequest( + HttpMethod.POST, + '/task/tasks/create', + undefined, + request + ); + } + async updateTask(id: number, request: CreateTaskAPIRequest) { + return await this.makeRequest( + HttpMethod.POST, + `/task/tasks/update/${id}`, + undefined, + request + ); + } + async getTask(id: number) { + return await this.makeRequest(HttpMethod.GET, `/task/tasks/get/${id}`); + } + async listAllTasks() { + return await this.makeRequest>( + HttpMethod.GET, + '/task/tasks/list' + ); + } + async createOpportunity(request: CreateOpportunityAPIRequest) { + return await this.makeRequest( + HttpMethod.POST, + '/crm/lead/create', + undefined, + request + ); + } + async updateOpportunity(id: number, request: CreateOpportunityAPIRequest) { + return await this.makeRequest( + HttpMethod.POST, + `/crm/lead/update/${id}`, + undefined, + request + ); + } + async listAllOpportunities() { + return await this.makeRequest>( + HttpMethod.GET, + '/crm/lead/list' + ); + } + async listAllUsers() { + return await this.makeRequest>( + HttpMethod.GET, + '/core/user/list' + ); + } + async listAllTaskWorkflow() { + return await this.makeRequest>( + HttpMethod.GET, + '/task/workflows/list' + ); + } + async listAllTaskStages() { + return await this.makeRequest>( + HttpMethod.GET, + '/task/stages/list' + ); + } + async listAllHonorificTitles() { + return await this.makeRequest>( + HttpMethod.GET, + '/crm/honorific_title/list' + ); + } + async listAllAccountCategories() { + return await this.makeRequest>( + HttpMethod.GET, + '/crm/account_category/list' + ); + } + + async listAllAccountIndustries() { + return await this.makeRequest>( + HttpMethod.GET, + '/crm/industry/list' + ); + } + async listAllOpportunitySources() { + return await this.makeRequest>( + HttpMethod.GET, + '/crm/source/list' + ); + } + async listSalesPipelines() { + return await this.makeRequest>( + HttpMethod.GET, + '/crm/pipeline/list' + ); + } + async listSalesPipelineStages(pipeline_id: number) { + return await this.makeRequest>( + HttpMethod.GET, + '/crm/pipeline_stage/list', + { 'filter[pipeline_id]': pipeline_id.toString() } + ); + } + + async deleteAction( + moduleName: FlowluModule, + entityName: FlowluEntity, + id: number + ) { + return await this.makeRequest( + HttpMethod.GET, + `/${moduleName}/${entityName}/delete/${id}` + ); + } +} diff --git a/packages/pieces/community/flowlu/src/lib/common/constants.ts b/packages/pieces/community/flowlu/src/lib/common/constants.ts new file mode 100644 index 0000000..86c19de --- /dev/null +++ b/packages/pieces/community/flowlu/src/lib/common/constants.ts @@ -0,0 +1,20 @@ +export const enum FlowluModule { + TASK = 'task', + CRM = 'crm', + PROJECT = 'st', + CORE = 'core', +} +export const enum FlowluEntity { + TASKS = 'tasks', + WORKFLOWS = 'workflows', + STAGES = 'stages', + ACCOUNT = 'account', + SOURCE = 'source', + PIPELINE_STAGES = 'pipeline_stage', + PIPELINE = 'pipeline', + OPPORTUNITY = 'lead', + ACCOUNT_TYPE = 'account_category', + ACCOUNT_INDUSTRY = 'industry', + USER = 'user', + HONORIFIC_TITLE = 'honorific_title', +} diff --git a/packages/pieces/community/flowlu/src/lib/common/index.ts b/packages/pieces/community/flowlu/src/lib/common/index.ts new file mode 100644 index 0000000..b03d073 --- /dev/null +++ b/packages/pieces/community/flowlu/src/lib/common/index.ts @@ -0,0 +1,393 @@ +import { PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { flowluAuth } from '../..'; +import { FlowluClient } from './client'; + +export function makeClient( + auth: PiecePropValueSchema +): FlowluClient { + const client = new FlowluClient(auth.domain, auth.apiKey); + return client; +} +function mapItemsToOptions(items: { id: string | number; name: string }[]) { + return items.map((item) => ({ label: item.name, value: item.id })); +} +export const flowluCommon = { + task_id: (required = true) => + Property.Dropdown({ + displayName: 'Task ID', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + const client = makeClient( + auth as PiecePropValueSchema + ); + const { response } = await client.listAllTasks(); + return { + disabled: false, + options: response.items.map((item) => { + return { + label: item.name, + value: item.id, + }; + }), + }; + }, + }), + user_id: (required = true, displayName = 'User ID') => + Property.Dropdown({ + displayName, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + const client = makeClient( + auth as PiecePropValueSchema + ); + const { response } = await client.listAllUsers(); + return { + disabled: false, + options: response.items.map((item) => { + return { + label: item.name, + value: item.id, + }; + }), + }; + }, + }), + workflow_id: (required = false) => + Property.Dropdown({ + displayName: 'Task Workflow ID', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + const client = makeClient( + auth as PiecePropValueSchema + ); + const { response } = await client.listAllTaskWorkflow(); + return { + disabled: false, + options: response.items.map((item) => { + return { + label: item.name, + value: item.id, + }; + }), + }; + }, + }), + workflow_stage_id: (required = false) => + Property.Dropdown({ + displayName: 'Task Workflow Status ID', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + const client = makeClient( + auth as PiecePropValueSchema + ); + const { response } = await client.listAllTaskStages(); + return { + disabled: false, + options: response.items.map((item) => { + return { + label: item.name, + value: item.id, + }; + }), + }; + }, + }), + honorific_title_id: (required = false) => + Property.Dropdown({ + displayName: 'Title', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + const client = makeClient( + auth as PiecePropValueSchema + ); + const res = await client.listAllHonorificTitles(); + const { response } = res; + return { + disabled: false, + options: response.items.map((item) => { + return { + label: item.name, + value: item.id, + }; + }), + }; + }, + }), + account_category_id: (required = false) => + Property.Dropdown({ + displayName: 'Account Category', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + const client = makeClient( + auth as PiecePropValueSchema + ); + const { response } = await client.listAllAccountCategories(); + return { + disabled: false, + options: response.items.map((item) => { + return { + label: item.name, + value: item.id, + }; + }), + }; + }, + }), + industry_id: (required = false) => + Property.Dropdown({ + displayName: 'Account Industry', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + const client = makeClient( + auth as PiecePropValueSchema + ); + const { response } = await client.listAllAccountIndustries(); + return { + disabled: false, + options: response.items.map((item) => { + return { + label: item.name, + value: item.id, + }; + }), + }; + }, + }), + source_id: (required = false) => + Property.Dropdown({ + displayName: 'Opportunity Source', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + const client = makeClient( + auth as PiecePropValueSchema + ); + const { response } = await client.listAllOpportunitySources(); + return { + disabled: false, + options: response.items.map((item) => { + return { + label: item.name, + value: item.id, + }; + }), + }; + }, + }), + opportunity_id: (required = false) => + Property.Dropdown({ + displayName: 'Opportunity ID', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + const client = makeClient( + auth as PiecePropValueSchema + ); + const { response } = await client.listAllOpportunities(); + return { + disabled: false, + options: response.items.map((item) => { + return { + label: item.name, + value: item.id, + }; + }), + }; + }, + }), + account_id: ( + required = false, + displayName = 'Account ID', + description = '' + ) => + Property.Dropdown({ + displayName, + description, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + const client = makeClient( + auth as PiecePropValueSchema + ); + const { response } = await client.listAllAccounts(); + return { + disabled: false, + options: response.items.map((item) => { + return { + label: item.name, + value: item.id, + }; + }), + }; + }, + }), + contact_id: ( + required = false, + displayName = 'Contact ID', + description = '' + ) => + Property.Dropdown({ + displayName, + description, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + const client = makeClient( + auth as PiecePropValueSchema + ); + const { response } = await client.listAllContacts(); + return { + disabled: false, + options: response.items.map((item) => { + return { + label: item.name, + value: item.id, + }; + }), + }; + }, + }), + pipeline_id: (required = false) => + Property.Dropdown({ + displayName: 'Sales Pipeline ID', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + const client = makeClient( + auth as PiecePropValueSchema + ); + const { response } = await client.listSalesPipelines(); + return { + disabled: false, + options: response.items.map((item) => { + return { + label: item.name, + value: item.id, + }; + }), + }; + }, + }), + pipeline_stage_id: (required = false) => + Property.Dropdown({ + displayName: 'Sales Pipeline Stage ID', + required, + refreshers: ['pipeline_id'], + options: async ({ auth, pipeline_id }) => { + if (!auth || !pipeline_id) { + return { + disabled: true, + placeholder: + 'Connect your account first and select sales pipeline.', + options: [], + }; + } + const client = makeClient( + auth as PiecePropValueSchema + ); + const { response } = await client.listSalesPipelineStages( + pipeline_id as number + ); + return { + disabled: false, + options: response.items.map((item) => { + return { + label: item.name, + value: item.id, + }; + }), + }; + }, + }), +}; diff --git a/packages/pieces/community/flowlu/src/lib/common/props.ts b/packages/pieces/community/flowlu/src/lib/common/props.ts new file mode 100644 index 0000000..089730a --- /dev/null +++ b/packages/pieces/community/flowlu/src/lib/common/props.ts @@ -0,0 +1,225 @@ +import { Property } from '@activepieces/pieces-framework'; +import { flowluCommon } from '.'; + +export const flowluProps = { + task: { + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + priority: Property.StaticDropdown({ + displayName: 'Priority', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Low', + value: 1, + }, + { + label: 'Medium', + value: 2, + }, + { + label: 'High', + value: 3, + }, + ], + }, + }), + plan_start_date: Property.DateTime({ + displayName: 'Start Date', + required: false, + description: 'Please use YYYY-MM-DD HH:mm:ss format.', + }), + deadline: Property.DateTime({ + displayName: 'End Date', + required: false, + description: 'Please use YYYY-MM-DD HH:mm:ss format.', + }), + deadline_allowchange: Property.Checkbox({ + displayName: 'The assignee can change the end date for this task?', + required: false, + defaultValue: false, + }), + task_checkbyowner: Property.Checkbox({ + displayName: 'This task needs approval from the owner?', + required: false, + defaultValue: false, + }), + responsible_id: flowluCommon.user_id(false, 'Assignee ID'), + owner_id: flowluCommon.user_id(false, 'Owner ID'), + type: Property.StaticDropdown({ + displayName: 'Task Type', + required: false, + defaultValue: 0, + options: { + disabled: false, + options: [ + { + label: 'Task', + value: 0, + }, + { + label: 'Inbox', + value: 1, + }, + { + label: 'Event', + value: 20, + }, + { + label: 'Task template', + value: 30, + }, + ], + }, + }), + workflow_id: flowluCommon.workflow_id(false), + workflow_stage_id: flowluCommon.workflow_stage_id(false), + }, + account: { + owner_id: flowluCommon.user_id(false, 'Assignee ID'), + account_category_id: flowluCommon.account_category_id(false), + industry_id: flowluCommon.industry_id(false), + web: Property.ShortText({ + displayName: 'Website', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + required: false, + }), + phone: Property.ShortText({ + displayName: 'Primary Phone Number', + required: false, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + vat: Property.ShortText({ + displayName: 'VAT or TAX ID', + required: false, + }), + bank_details: Property.LongText({ + displayName: 'Bank Details', + required: false, + }), + telegram: Property.ShortText({ + displayName: 'Telegram', + required: false, + }), + skype: Property.ShortText({ + displayName: 'Skype Account ID', + required: false, + }), + link_google: Property.ShortText({ + displayName: 'Link to Google+', + required: false, + }), + link_facebook: Property.ShortText({ + displayName: 'Link to Facebook', + required: false, + }), + link_linkedin: Property.ShortText({ + displayName: 'Link to Linkedin', + required: false, + }), + link_instagram: Property.ShortText({ + displayName: 'Link to Instagram', + required: false, + }), + billing_country: Property.ShortText({ + displayName: 'Billing Country', + required: false, + }), + billing_state: Property.ShortText({ + displayName: 'Billing State', + required: false, + }), + billing_city: Property.ShortText({ + displayName: 'Billing City', + required: false, + }), + billing_zip: Property.ShortText({ + displayName: 'Billing Postal code', + required: false, + }), + billing_address_line_1: Property.ShortText({ + displayName: 'Billing Address Line 1', + required: false, + }), + billing_address_line_2: Property.ShortText({ + displayName: 'Billing Address Line 2', + required: false, + }), + billing_address_line_3: Property.ShortText({ + displayName: 'Billing Address Line 3', + required: false, + }), + shipping_country: Property.ShortText({ + displayName: 'Shipping Country', + required: false, + }), + shipping_state: Property.ShortText({ + displayName: 'Shipping State', + required: false, + }), + shipping_city: Property.ShortText({ + displayName: 'Shipping City', + required: false, + }), + shipping_zip: Property.ShortText({ + displayName: 'Shipping Postal code', + required: false, + }), + shipping_address_line_1: Property.ShortText({ + displayName: 'Shipping Address Line 1', + required: false, + }), + shipping_address_line_2: Property.ShortText({ + displayName: 'Shipping Address Line 2', + required: false, + }), + shipping_address_line_3: Property.ShortText({ + displayName: 'Shipping Address Line 3', + required: false, + }), + }, + opportunity: { + budget: Property.Number({ + displayName: 'Opportunity Amount', + required: false, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + source_id: flowluCommon.source_id(false), + start_date: Property.DateTime({ + displayName: 'Start Date', + required: false, + description: 'Please use YYYY-MM-DD HH:mm:ss format.', + }), + deadline: Property.DateTime({ + displayName: 'End Date', + required: false, + description: 'Please use YYYY-MM-DD HH:mm:ss format.', + }), + assignee_id: flowluCommon.user_id(false, 'Assignee ID'), + customer_id: flowluCommon.account_id( + false, + 'Customer ID', + `This is an id of the CRM company or contact which is needed to be linked with the opportunity. This allows you to link the client to the opportunity. If your client is a company, and you need to relate an opportunity to the person (contact) at this company, then enter his/her id in the contact_id field.` + ), + contact_id: flowluCommon.contact_id( + false, + 'Contact ID', + `Id of the company-related contact (account_id).` + ), + pipeline_id: flowluCommon.pipeline_id(false), + pipeline_stage_id: flowluCommon.pipeline_stage_id(false), + }, +}; diff --git a/packages/pieces/community/flowlu/src/lib/common/types.ts b/packages/pieces/community/flowlu/src/lib/common/types.ts new file mode 100644 index 0000000..a67a6ca --- /dev/null +++ b/packages/pieces/community/flowlu/src/lib/common/types.ts @@ -0,0 +1,229 @@ +import { HttpMessageBody } from '@activepieces/pieces-common'; + +export interface Task { + id: number; + name: string; + description: string; + report: string; + parent_id: number; + prev_task_id: number; + deadline: string; + deadline_allowchange: number; + plan_start_date: string; + plan_end_date: string; + priority: number; + task_checkbyowner: number; + responsible_id: number; + time_estimate: number; + time_spent: number; + status_firstviewdate: string; + start_date: string; + closed_date: string; + first_closed_date: string; + closed_by: number; + return_count: number; + rating: number; + ref: string; + ref_id: string; + module: string; + model: string; + model_id: number; + type: number; + report_complete: string; + all_day: number; + ordering: number; + uuid: string; + public_template: number; + template_id: number; + is_repeat: number; + event_location: string; + event_color: string; + event_busy_status: number; + event_access_type: number; + event_calendar_id: number; + event_type: number; + event_etag: string; + event_sync_type: number; + extra_fields: string; + archive_status: number; + is_hidden: number; + workflow_id: number; + workflow_stage_id: number; + deleted_at: null; + is_archive: string; + created_date: string; + owner_id: number; + updated_date: string; + updated_by: number; + status_updated_date: string; + status_updated_by: number; + project_stage_id: number; + project_checkitem_id: number; + crm_account_id: number; +} +export interface User { + id: string; + last_active: string; + username: string; + first_name: string; + second_name: string; + last_name: string; + birth_date: string; + lang_id: string; + timezone: string; + register_date: string; + image: string; + role_admin: number; + role_login: number; + name: string; +} +export interface Account { + id: number; + name: string; + type: number; +} +export interface TaskWorkflow { + id: number; + name: string; + description: string; + ordering: number; + created_by: number; + updated_by: number; + updated_date: string; + active: number; + deleted_at: string; +} +export interface TaskWorkflowStage { + id: number; + name: string; + description: string; + ordering: number; + created_by: number; + updated_by: number; + updated_date: string; + workflow_id: number; + color: string; + task_status: number; + deleted_at: string; +} +export interface ListAPIResponse extends HttpMessageBody { + response: { + total: number; + total_result: number; + page: number; + count: number; + items: T; + }; +} +export interface AccountHonorificTitle { + id: number; + name: string; + ordering: number; + active: number; +} +export interface AccountCategory { + id: number; + active: number; + ordering: number; + name: string; + deleted_at: string; +} +export interface AccountIndustry { + id: number; + name: string; + ordering: number; + active: number; + deleted_at: string; +} +export interface OpportunitySource { + id: number; + name: string; + ordering: number; + active: number; + description: string; + deleted_at: string; +} +export interface Opportunity { + id: number; + name: string; +} +export interface Pipeline { + id: number; + name: string; + ordering: number; + description: string; + deleted_at: string; +} +export interface PipelineStage { + id: number; + name: string; + ordering: number; + active: number; + pipeline_id: number; + color: string; + deleted_at: string; +} +export interface CreateTaskAPIRequest { + name?: string; + description?: string; + priority?: number; + plan_start_date?: string; + deadline?: string; + deadline_allowchange?: number; + task_checkbyowner?: number; + responsible_id?: string; + owner_id?: string; + type?: number; + workflow_id?: number; + workflow_stage_id?: number; +} + +export interface CreateCRMAccountAPIRequest { + type: number; + name_legal_full?: string; + first_name?: string; + middle_name?: string; + last_name?: string; + owner_id?: string; + account_category_id?: number; + industry_id?: number; + web?: string; + email?: string; + phone?: string; + description?: string; + vat?: string; + bank_details?: string; + telegram?: string; + skype?: string; + link_google?: string; + link_facebook?: string; + link_linkedin?: string; + link_instagram?: string; + billing_country?: string; + billing_state?: string; + billing_city?: string; + billing_zip?: string; + billing_address_line_1?: string; + billing_address_line_2?: string; + billing_address_line_3?: string; + shipping_country?: string; + shipping_state?: string; + shipping_city?: string; + shipping_zip?: string; + shipping_address_line_1?: string; + shipping_address_line_2?: string; + shipping_address_line_3?: string; +} +export interface CreateOpportunityAPIRequest { + name?: string; + budget?: number; + description?: string; + source_id?: number; + start_date?: string; + deadline?: string; + assignee_id?: string; + customer_id?: number; + contact_id?: number; + pipeline_id?: number; + pipeline_stage_id?: number; +} diff --git a/packages/pieces/community/flowlu/tsconfig.json b/packages/pieces/community/flowlu/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/flowlu/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/flowlu/tsconfig.lib.json b/packages/pieces/community/flowlu/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/flowlu/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/formbricks/.eslintrc.json b/packages/pieces/community/formbricks/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/formbricks/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/formbricks/README.md b/packages/pieces/community/formbricks/README.md new file mode 100644 index 0000000..52321ad --- /dev/null +++ b/packages/pieces/community/formbricks/README.md @@ -0,0 +1,7 @@ +# pieces-formbricks + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-formbricks` to build the library. diff --git a/packages/pieces/community/formbricks/package.json b/packages/pieces/community/formbricks/package.json new file mode 100644 index 0000000..90d57c7 --- /dev/null +++ b/packages/pieces/community/formbricks/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-formbricks", + "version": "0.1.1" +} diff --git a/packages/pieces/community/formbricks/project.json b/packages/pieces/community/formbricks/project.json new file mode 100644 index 0000000..c764c36 --- /dev/null +++ b/packages/pieces/community/formbricks/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-formbricks", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/formbricks/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/formbricks", + "tsConfig": "packages/pieces/community/formbricks/tsconfig.lib.json", + "packageJson": "packages/pieces/community/formbricks/package.json", + "main": "packages/pieces/community/formbricks/src/index.ts", + "assets": [ + "packages/pieces/community/formbricks/*.md", + { + "input": "packages/pieces/community/formbricks/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-formbricks {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/formbricks/src/index.ts b/packages/pieces/community/formbricks/src/index.ts new file mode 100644 index 0000000..ed1e0a6 --- /dev/null +++ b/packages/pieces/community/formbricks/src/index.ts @@ -0,0 +1,92 @@ +import { + createCustomApiCallAction, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { + createPiece, + PieceAuth, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { triggers } from './lib/triggers'; + +const markdownPropertyDescription = ` + **Enable Basic Authentication:** + 1. Login to your Formbricks account + 2. On the top-right, click on your account dropdown + 3. Select 'Product Settings' + 4. On the left, select 'API Keys' + 5. Click on 'Add Production API Key' + 6. On the popup form, enter the 'API Key Label' to name the Key + 7. Copy the API key and paste it below. + + **APP URL:** + - The API URL for Formbricks example the cloud is at https://app.formbricks.com + - **Note: make sure there is no trailing slash and no /api** +`; + +export type FormBricksAuthType = { + appUrl: string; + apiKey: string; +}; + +export const formBricksAuth = PieceAuth.CustomAuth({ + required: true, + description: markdownPropertyDescription, + props: { + appUrl: Property.ShortText({ + displayName: 'APP URL', + required: true, + defaultValue: 'https://app.formbricks.com', + }), + apiKey: Property.ShortText({ + displayName: 'API Key', + required: true, + }), + }, + validate: async ({ auth }) => { + try { + const authValue = auth as PiecePropValueSchema; + + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${authValue.appUrl}/api/v1/management/me`, + headers: { + 'x-api-key': authValue.apiKey, + }, + }); + return { + valid: true, + }; + } catch (error) { + return { + valid: false, + error: 'Please provide correct APP URL and API key.', + }; + } + }, +}); + +export const formbricks = createPiece({ + displayName: 'Formbricks', + description: 'Open source Survey Platform', + auth: formBricksAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/formbricks.png', + categories: [PieceCategory.BUSINESS_INTELLIGENCE], + authors: ['kanarelo', 'kishanprmr', 'MoShizzle', 'abuaboud'], + actions: [ + createCustomApiCallAction({ + auth: formBricksAuth, + authMapping: async (auth) => { + return { + 'x-api-key': (auth as FormBricksAuthType).apiKey, + }; + }, + baseUrl: (auth) => `${(auth as FormBricksAuthType).appUrl}/api/v1`, + }), + ], + triggers, +}); diff --git a/packages/pieces/community/formbricks/src/lib/triggers/index.ts b/packages/pieces/community/formbricks/src/lib/triggers/index.ts new file mode 100644 index 0000000..8a5fbab --- /dev/null +++ b/packages/pieces/community/formbricks/src/lib/triggers/index.ts @@ -0,0 +1,127 @@ +import { formBricksRegisterTrigger } from './register'; + +export const triggers = [ + { + name: 'response_created', + eventType: 'responseCreated', + displayName: 'Response Created', + description: 'Triggered when a new response is created.', + sampleData: { + webhookId: 'cljwxvjos0003qhnvj2jg4k5i', + event: 'responseCreated', + data: { + id: 'cljwy2m8r0001qhclco1godnu', + createdAt: '2023-07-10T14:14:17.115Z', + updatedAt: '2023-07-10T14:14:17.115Z', + surveyId: 'cljsf3d7a000019cv9apt2t27', + finished: false, + data: { + qumbk3fkr6cky8850bvvq5z1: 'Executive', + }, + meta: { + userAgent: { + os: 'Mac OS', + browser: 'Chrome', + }, + }, + personAttributes: { + email: 'test@web.com', + userId: 'THIS-IS-A-VERY-LONG-USER-ID-FOR-TESTING', + }, + person: { + id: 'cljold01t0000qh8ewzigzmjk', + attributes: { + email: 'test@web.com', + userId: 'THIS-IS-A-VERY-LONG-USER-ID-FOR-TESTING', + }, + createdAt: '2023-07-04T17:56:17.154Z', + updatedAt: '2023-07-04T17:56:17.154Z', + }, + notes: [], + tags: [], + }, + }, + }, + { + name: 'response_updated', + eventType: 'responseUpdated', + displayName: 'Response Updated', + description: 'Triggered when a new response is updated.', + sampleData: { + webhookId: 'cljwxvjos0003qhnvj2jg4k5i', + event: 'responseUpdated', + data: { + id: 'cljwy2m8r0001qhclco1godnu', + createdAt: '2023-07-10T14:14:17.115Z', + updatedAt: '2023-07-10T14:14:17.115Z', + surveyId: 'cljsf3d7a000019cv9apt2t27', + finished: false, + data: { + qumbk3fkr6cky8850bvvq5z1: 'Executive', + }, + meta: { + userAgent: { + os: 'Mac OS', + browser: 'Chrome', + }, + }, + personAttributes: { + email: 'test@web.com', + userId: 'THIS-IS-A-VERY-LONG-USER-ID-FOR-TESTING', + }, + person: { + id: 'cljold01t0000qh8ewzigzmjk', + attributes: { + email: 'test@web.com', + userId: 'THIS-IS-A-VERY-LONG-USER-ID-FOR-TESTING', + }, + createdAt: '2023-07-04T17:56:17.154Z', + updatedAt: '2023-07-04T17:56:17.154Z', + }, + notes: [], + tags: [], + }, + }, + }, + { + name: 'response_finished', + eventType: 'responseFinished', + displayName: 'Response Finished', + description: 'Triggered when a new response is finished.', + sampleData: { + webhookId: 'cljwxvjos0003qhnvj2jg4k5i', + event: 'responseFinished', + data: { + id: 'cljwy2m8r0001qhclco1godnu', + createdAt: '2023-07-10T14:14:17.115Z', + updatedAt: '2023-07-10T14:14:17.115Z', + surveyId: 'cljsf3d7a000019cv9apt2t27', + finished: false, + data: { + qumbk3fkr6cky8850bvvq5z1: 'Executive', + }, + meta: { + userAgent: { + os: 'Mac OS', + browser: 'Chrome', + }, + }, + personAttributes: { + email: 'test@web.com', + userId: 'THIS-IS-A-VERY-LONG-USER-ID-FOR-TESTING', + }, + person: { + id: 'cljold01t0000qh8ewzigzmjk', + attributes: { + email: 'test@web.com', + userId: 'THIS-IS-A-VERY-LONG-USER-ID-FOR-TESTING', + }, + createdAt: '2023-07-04T17:56:17.154Z', + updatedAt: '2023-07-04T17:56:17.154Z', + }, + notes: [], + tags: [], + }, + }, + }, +].map((props) => formBricksRegisterTrigger(props)); diff --git a/packages/pieces/community/formbricks/src/lib/triggers/register.ts b/packages/pieces/community/formbricks/src/lib/triggers/register.ts new file mode 100644 index 0000000..f54ed32 --- /dev/null +++ b/packages/pieces/community/formbricks/src/lib/triggers/register.ts @@ -0,0 +1,135 @@ +import { + PiecePropValueSchema, + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { + httpClient, + HttpRequest, + HttpMethod, +} from '@activepieces/pieces-common'; +import { formBricksAuth } from '../..'; + +export const formBricksRegisterTrigger = ({ + name, + displayName, + eventType, + description, + sampleData, +}: { + name: string; + displayName: string; + eventType: string; + description: string; + sampleData: unknown; +}) => + createTrigger({ + auth: formBricksAuth, + name: `formbricks_trigger_${name}`, + displayName, + description, + props: { + survey_id: Property.MultiSelectDropdown({ + displayName: 'Survey', + description: + 'A selection of surveys that will trigger. Else, all surveys will trigger.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please authenticate first', + }; + } + + const authValue = auth as PiecePropValueSchema; + + const response = await httpClient.sendRequest<{ data: Survey[] }>({ + method: HttpMethod.GET, + url: `${authValue.appUrl}/api/v1/management/surveys`, + headers: { + 'x-api-key': authValue.apiKey, + }, + }); + + try { + return { + disabled: false, + options: response.body.data.map((survey) => { + return { + label: survey.name, + value: survey.id, + }; + }), + }; + } catch (error) { + return { + options: [], + disabled: true, + placeholder: `Couldn't load Surveys:\n${error}`, + }; + } + }, + }), + }, + sampleData, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.appUrl}/api/v1/webhooks`, + body: { + url: context.webhookUrl, + triggers: [eventType], + surveyIds: context.propsValue.survey_id ?? [], + }, + headers: { + 'x-api-key': context.auth.apiKey as string, + }, + }); + await context.store.put( + `formbricks_${name}_trigger`, + response.body + ); + }, + async onDisable(context) { + const webhook = await context.store.get( + `formbricks_${name}_trigger` + ); + if (webhook?.data.id != null) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `${context.auth.appUrl}/api/v1/webhooks/${webhook.data.id}`, + headers: { + 'x-api-key': context.auth.apiKey as string, + }, + }; + await httpClient.sendRequest(request); + } + }, + async run(context) { + return [context.payload.body]; + }, + }); + +interface Survey { + id: string; + name: string; +} + +interface WebhookInformation { + data: { + id: string; + name: string; + createdAt: string; + updatedAt: string; + url: string; + source: string; + environmentId: string; + triggers: Array; + surveyIds: Array; + }; +} diff --git a/packages/pieces/community/formbricks/tsconfig.json b/packages/pieces/community/formbricks/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/formbricks/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/formbricks/tsconfig.lib.json b/packages/pieces/community/formbricks/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/formbricks/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/forms/.eslintrc.json b/packages/pieces/community/forms/.eslintrc.json new file mode 100644 index 0000000..359ff63 --- /dev/null +++ b/packages/pieces/community/forms/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/forms/README.md b/packages/pieces/community/forms/README.md new file mode 100644 index 0000000..8f480b0 --- /dev/null +++ b/packages/pieces/community/forms/README.md @@ -0,0 +1,7 @@ +# pieces-forms + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-forms` to build the library. diff --git a/packages/pieces/community/forms/package.json b/packages/pieces/community/forms/package.json new file mode 100644 index 0000000..c87c8f9 --- /dev/null +++ b/packages/pieces/community/forms/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-forms", + "version": "0.4.4" +} diff --git a/packages/pieces/community/forms/project.json b/packages/pieces/community/forms/project.json new file mode 100644 index 0000000..ad07b9f --- /dev/null +++ b/packages/pieces/community/forms/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-forms", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/forms/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/forms", + "tsConfig": "packages/pieces/community/forms/tsconfig.lib.json", + "packageJson": "packages/pieces/community/forms/package.json", + "main": "packages/pieces/community/forms/src/index.ts", + "assets": [ + "packages/pieces/community/forms/*.md", + { + "input": "packages/pieces/community/forms/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-forms {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/forms/src/index.ts b/packages/pieces/community/forms/src/index.ts new file mode 100644 index 0000000..da262da --- /dev/null +++ b/packages/pieces/community/forms/src/index.ts @@ -0,0 +1,17 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { onChatSubmission } from './lib/triggers/chat-trigger'; +import { onFormSubmission } from './lib/triggers/form-trigger'; +import { returnResponse } from './lib/actions/return-response'; + +export const forms = createPiece({ + displayName: 'Human Input', + description: 'Trigger a flow through human input.', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.65.0', + categories: [PieceCategory.CORE], + logoUrl: 'https://cdn.activepieces.com/pieces/human-input.svg', + authors: ['anasbarg', 'MoShizzle', 'abuaboud'], + actions: [returnResponse], + triggers: [onFormSubmission, onChatSubmission], +}); diff --git a/packages/pieces/community/forms/src/lib/actions/return-response.ts b/packages/pieces/community/forms/src/lib/actions/return-response.ts new file mode 100644 index 0000000..144d19b --- /dev/null +++ b/packages/pieces/community/forms/src/lib/actions/return-response.ts @@ -0,0 +1,57 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { FileResponseInterface, HumanInputFormResult, HumanInputFormResultTypes } from '@activepieces/shared'; +import { StatusCodes } from 'http-status-codes'; +import mime from 'mime-types'; + +export const returnResponse = createAction({ + name: 'return_response', + displayName: 'Respond on UI', + description: 'Return a file or text (markdown) as a response.', + props: { + markdown: Property.LongText({ + displayName: 'Text (Markdown)', + required: false, + }), + file: Property.File({ + displayName: 'Attachment', + required: false, + }), + }, + errorHandlingOptions: { + retryOnFailure: { + hide: true, + }, + continueOnFailure: { + hide: true, + }, + }, + async run({ propsValue, run, files }) { + const responseFiles: FileResponseInterface[] = [] + if (propsValue.file) { + const fileName = propsValue.file.filename; + const fileBase64 = propsValue.file.base64; + const mimeType = mime.lookup(fileName); + responseFiles.push({ + url: await files.write({ + fileName, + data: Buffer.from(fileBase64, 'base64'), + }), + mimeType: mimeType || '', + }); + } + const markdownResponseBody: HumanInputFormResult = { + type: HumanInputFormResultTypes.MARKDOWN, + value: propsValue.markdown ?? '', + files: responseFiles, + } + run.respond({ + response: { + status: StatusCodes.OK, + body: markdownResponseBody, + headers: {}, + }, + + }); + return markdownResponseBody; + }, +}); diff --git a/packages/pieces/community/forms/src/lib/triggers/chat-trigger.ts b/packages/pieces/community/forms/src/lib/triggers/chat-trigger.ts new file mode 100644 index 0000000..894d0cb --- /dev/null +++ b/packages/pieces/community/forms/src/lib/triggers/chat-trigger.ts @@ -0,0 +1,77 @@ +import { + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { + MarkdownVariant, + USE_DRAFT_QUERY_PARAM_NAME, + ChatFormResponse, +} from '@activepieces/shared'; + +const responseMarkdown = ` +This trigger sets up a chat interface. Ensure that **Respond on UI** is used in your flow`; + +const markdown = ` +**Published Chat URL:** +\`\`\`text +{{chatUrl}} +\`\`\` +Use this for production, views the published version of the chat flow. +
+
+`; + +export const onChatSubmission = createTrigger({ + name: 'chat_submission', + displayName: 'Chat UI', + description: 'Trigger the flow by sending a message', + props: { + about: Property.MarkDown({ + value: markdown, + variant: MarkdownVariant.BORDERLESS, + }), + responseMarkdown: Property.MarkDown({ + value: responseMarkdown, + variant: MarkdownVariant.WARNING, + }), + botName: Property.ShortText({ + displayName: 'Bot Name', + description: 'The name of the chatbot', + required: true, + defaultValue: 'AI Bot', + }), + }, + sampleData: undefined, + type: TriggerStrategy.WEBHOOK, + async onEnable() { + return; + }, + async onDisable() { + return; + }, + async run(ctx) { + const item = ctx.payload.body as { chatId?: string; message?: string }; + if (!item.chatId) { + throw new Error('Chat ID is required'); + } + if (!item.message) { + throw new Error('Message is required'); + } + const files = Object.entries(item) + .filter(([key]) => key.startsWith('file')) + .map(([key, value]) => { + const index = Number(key.split('[')[1].split(']')[0]); + return [index, value] as const; + }) + .sort(([indexA], [indexB]) => indexA - indexB) + .map(([_, value]) => value); + + const response: ChatFormResponse = { + sessionId: item.chatId, + message: item.message, + files, + } + return [response]; + }, +}); diff --git a/packages/pieces/community/forms/src/lib/triggers/form-trigger.ts b/packages/pieces/community/forms/src/lib/triggers/form-trigger.ts new file mode 100644 index 0000000..4fa6dcd --- /dev/null +++ b/packages/pieces/community/forms/src/lib/triggers/form-trigger.ts @@ -0,0 +1,130 @@ +import { + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { + createKeyForFormInput, + MarkdownVariant, + USE_DRAFT_QUERY_PARAM_NAME, +} from '@activepieces/shared'; + + +const markdown = `**Published Form URL:** +\`\`\`text +{{formUrl}} +\`\`\` +Use this for production, views the published version of the form. +
+
+**Draft Form URL:** +\`\`\`text +{{formUrl}}?${USE_DRAFT_QUERY_PARAM_NAME}=true +\`\`\` +Use this to generate sample data, views the draft version of the form (the one you are editing now). +`; +const responseMarkdown = ` +If **Wait for Response** is enabled, use **Respond on UI** in your flow to provide a response back to the form. +`; + +type FormInput = { + displayName: string; + type: 'text' | 'text_area' | 'file' | 'toggle'; + description?: string; + required: boolean; +}; + +const parseBoolean = (value: unknown, fieldName: string): boolean => { + if (typeof value === 'boolean') { + return value; + } + if (typeof value === 'string') { + const lowerValue = value.toLowerCase(); + if (lowerValue === 'true' || lowerValue === 'false') { + return lowerValue === 'true'; + } + } + throw new Error(`Field ${fieldName} must be a boolean or 'true'/'false' string`); +}; + +export const onFormSubmission = createTrigger({ + name: 'form_submission', + displayName: 'Web Form', + description: 'Trigger the flow by submitting a form.', + props: { + about: Property.MarkDown({ + value: markdown, + variant: MarkdownVariant.BORDERLESS, + }), + response: Property.MarkDown({ + value: responseMarkdown, + variant: MarkdownVariant.WARNING, + }), + waitForResponse: Property.Checkbox({ + displayName: 'Wait for Response', + defaultValue: false, + required: true, + }), + inputs: Property.Array({ + displayName: 'Inputs', + required: true, + properties: { + displayName: Property.ShortText({ + displayName: 'Field Name', + required: true, + }), + type: Property.StaticDropdown({ + displayName: 'Field Type', + required: true, + options: { + options: [ + { value: 'text', label: 'Text' }, + { value: 'text_area', label: 'Text Area' }, + { value: 'file', label: 'File' }, + { value: 'toggle', label: 'Toggle' }, + ], + }, + }), + description: Property.ShortText({ + displayName: 'Field Description', + required: false, + }), + required: Property.Checkbox({ + displayName: 'Required', + required: true, + }), + }, + }), + }, + sampleData: {}, + type: TriggerStrategy.WEBHOOK, + async onEnable() { + return; + }, + async onDisable() { + return; + }, + async run(context) { + const payload = context.payload.body as Record; + const inputs = context.propsValue.inputs as FormInput[]; + + const processedPayload: Record = {}; + for (const input of inputs) { + const key = createKeyForFormInput(input.displayName); + const value = payload[key]; + + switch (input.type) { + case 'toggle': + processedPayload[key] = parseBoolean(value, input.displayName); + break; + case 'text': + case 'text_area': + case 'file': + processedPayload[key] = value; + break; + } + } + + return [processedPayload]; + }, +}); diff --git a/packages/pieces/community/forms/tsconfig.json b/packages/pieces/community/forms/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/forms/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/forms/tsconfig.lib.json b/packages/pieces/community/forms/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/forms/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/frame/.eslintrc.json b/packages/pieces/community/frame/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/frame/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/frame/README.md b/packages/pieces/community/frame/README.md new file mode 100644 index 0000000..5017b78 --- /dev/null +++ b/packages/pieces/community/frame/README.md @@ -0,0 +1,7 @@ +# pieces-frame + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-frame` to build the library. diff --git a/packages/pieces/community/frame/package.json b/packages/pieces/community/frame/package.json new file mode 100644 index 0000000..da2029b --- /dev/null +++ b/packages/pieces/community/frame/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-frame", + "version": "0.0.5" +} \ No newline at end of file diff --git a/packages/pieces/community/frame/project.json b/packages/pieces/community/frame/project.json new file mode 100644 index 0000000..8292498 --- /dev/null +++ b/packages/pieces/community/frame/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-frame", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/frame/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/frame", + "tsConfig": "packages/pieces/community/frame/tsconfig.lib.json", + "packageJson": "packages/pieces/community/frame/package.json", + "main": "packages/pieces/community/frame/src/index.ts", + "assets": [ + "packages/pieces/community/frame/*.md", + { + "input": "packages/pieces/community/frame/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-frame {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/frame/src/index.ts b/packages/pieces/community/frame/src/index.ts new file mode 100644 index 0000000..f589301 --- /dev/null +++ b/packages/pieces/community/frame/src/index.ts @@ -0,0 +1,42 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { triggers } from './lib/triggers'; + +const markdownPropertyDescription = ` +**Enable Basic Authentication:** + +1. Go to https://developer.frame.io/app/tokens +2. Click on 'Create a Token' +3. Provide a Description for your use. +4. Depending on your usage, you may 'Select all scopes' +5. Submit the form +6. The token will be created. Copy the token and paste it in the 'Token' input +`; + +export const frameAuth = PieceAuth.SecretText({ + displayName: 'Token', + description: markdownPropertyDescription, + required: true, +}); + +export const frame = createPiece({ + displayName: 'Frame', + description: 'Collaborative workspace platform', + + auth: frameAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/frameio.png', + categories: [PieceCategory.MARKETING], + authors: ["kanarelo","kishanprmr","MoShizzle","abuaboud"], + actions: [ + createCustomApiCallAction({ + baseUrl: () => 'https://api.frame.io/v2', + auth: frameAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers, +}); diff --git a/packages/pieces/community/frame/src/lib/triggers/index.ts b/packages/pieces/community/frame/src/lib/triggers/index.ts new file mode 100644 index 0000000..277932f --- /dev/null +++ b/packages/pieces/community/frame/src/lib/triggers/index.ts @@ -0,0 +1,67 @@ +import { frameRegisterTrigger } from './register-trigger'; + +export const triggers = [ + { + name: 'project_created', + eventType: 'project.created', + displayName: 'Project Created', + description: 'Triggered when a new project is created.', + sampleData: { + account_id: '449e7a5c-69d3-4b8a-aaaf-5c9b713ebc65', + active: true, + app_id: 'affd1d10-9538-4fc8-9e0b-4594a28c1335', + deleted_at: '2019-08-24T14:15:22Z', + events: ['project.created'], + id: '497f6eca-6276-4993-bfeb-53cbbbba6f08', + inserted_at: '2019-08-24T14:15:22Z', + name: 'string', + project_id: '405d8375-3514-403b-8c43-83ae74cfe0e9', + secret: 'string', + team_id: '810007d0-bec5-486c-b5d1-28fcd8a079ba', + updated_at: '2019-08-24T14:15:22Z', + url: 'string', + }, + }, + { + name: 'asset_created', + eventType: 'asset.created', + displayName: 'Asset Created', + description: 'Triggered when a new asset is created.', + sampleData: { + account_id: '449e7a5c-69d3-4b8a-aaaf-5c9b713ebc65', + active: true, + app_id: 'affd1d10-9538-4fc8-9e0b-4594a28c1335', + deleted_at: '2019-08-24T14:15:22Z', + events: ['asset.created'], + id: '497f6eca-6276-4993-bfeb-53cbbbba6f08', + inserted_at: '2019-08-24T14:15:22Z', + name: 'string', + project_id: '405d8375-3514-403b-8c43-83ae74cfe0e9', + secret: 'string', + team_id: '810007d0-bec5-486c-b5d1-28fcd8a079ba', + updated_at: '2019-08-24T14:15:22Z', + url: 'string', + }, + }, + { + name: 'comment_created', + eventType: 'comment.created', + displayName: 'Comment Created', + description: 'Triggered when a new comment is created.', + sampleData: { + account_id: '449e7a5c-69d3-4b8a-aaaf-5c9b713ebc65', + active: true, + app_id: 'affd1d10-9538-4fc8-9e0b-4594a28c1335', + deleted_at: '2019-08-24T14:15:22Z', + events: ['comment.created'], + id: '497f6eca-6276-4993-bfeb-53cbbbba6f08', + inserted_at: '2019-08-24T14:15:22Z', + name: 'string', + project_id: '405d8375-3514-403b-8c43-83ae74cfe0e9', + secret: 'string', + team_id: '810007d0-bec5-486c-b5d1-28fcd8a079ba', + updated_at: '2019-08-24T14:15:22Z', + url: 'string', + }, + }, +].map((props) => frameRegisterTrigger(props)); diff --git a/packages/pieces/community/frame/src/lib/triggers/register-trigger.ts b/packages/pieces/community/frame/src/lib/triggers/register-trigger.ts new file mode 100644 index 0000000..c4ad9eb --- /dev/null +++ b/packages/pieces/community/frame/src/lib/triggers/register-trigger.ts @@ -0,0 +1,194 @@ +import { + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { + httpClient, + HttpRequest, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { frameAuth } from '../..'; + +export const frameRegisterTrigger = ({ + name, + displayName, + eventType, + description, + sampleData, +}: { + name: string; + displayName: string; + eventType: string; + description: string; + sampleData: unknown; +}) => + createTrigger({ + auth: frameAuth, + name: `frame_trigger_${name}`, + displayName, + description, + props: { + account_id: Property.Dropdown({ + displayName: 'Account', + description: 'Accounts accessible via a given User', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please authenticate first', + }; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.frame.io/v2/accounts`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as unknown as string, + }, + queryParams: {}, + }); + + try { + return { + disabled: false, + options: response.body.map((account) => { + return { + label: account.display_name, + value: account.id, + }; + }), + }; + } catch (error) { + return { + options: [], + disabled: true, + placeholder: `Couldn't load Accounts:\n${error}`, + }; + } + }, + }), + team_id: Property.Dropdown({ + displayName: 'Team', + description: 'Teams accessible via a given Account', + required: true, + refreshers: ['account_id'], + options: async ({ auth, account_id }) => { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please authenticate first', + }; + } + if (!account_id) { + return { + options: [], + disabled: true, + placeholder: 'Please select an account first', + }; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.frame.io/v2/accounts/${account_id}/teams`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as unknown as string, + }, + queryParams: {}, + }); + + try { + return { + disabled: false, + options: response.body.map((team) => { + return { + label: team.name, + value: team.id, + }; + }), + }; + } catch (error) { + return { + options: [], + disabled: true, + placeholder: `Couldn't load Teams:\n${error}`, + }; + } + }, + }), + }, + sampleData, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.frame.com/api/v2/teams/${context.propsValue.team_id}/hooks`, + body: { + name: displayName, + url: context.webhookUrl, + events: [eventType], + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + }); + await context.store.put( + `frame_${name}_trigger`, + response.body + ); + }, + async onDisable(context) { + const webhook = await context.store.get( + `frame_${name}_trigger` + ); + if (webhook != null) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `https://api.frame.io/v2/hooks/${webhook.id}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + }; + await httpClient.sendRequest(request); + } + }, + async run(context) { + return [context.payload.body]; + }, + }); + +interface WebhookInformation { + id: string; + name: string; + project_id: string; + app_id: string; + account_id: string; + team_id: string; + team: Team[]; + url: string; + active: boolean; + events: string[]; + secret: string; + deleted_at: string; + inserted_at: string; + updated_at: string; +} + +interface Team { + id: string; + name: string; +} + +interface Account { + id: string; + display_name: string; +} diff --git a/packages/pieces/community/frame/tsconfig.json b/packages/pieces/community/frame/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/frame/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/frame/tsconfig.lib.json b/packages/pieces/community/frame/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/frame/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/framework/.eslintrc.json b/packages/pieces/community/framework/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/framework/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/framework/README.md b/packages/pieces/community/framework/README.md new file mode 100644 index 0000000..1a12cec --- /dev/null +++ b/packages/pieces/community/framework/README.md @@ -0,0 +1,11 @@ +# pieces-framework + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-framework` to build the library. + +## Running unit tests + +Run `nx test pieces-framework` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/packages/pieces/community/framework/jest.config.ts b/packages/pieces/community/framework/jest.config.ts new file mode 100644 index 0000000..e6e4a67 --- /dev/null +++ b/packages/pieces/community/framework/jest.config.ts @@ -0,0 +1,17 @@ +/* eslint-disable */ +export default { + displayName: 'pieces-framework', + preset: '../../../../jest.preset.js', + globals: {}, + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': [ + 'ts-jest', + { + tsconfig: '/tsconfig.spec.json', + }, + ], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../../../coverage/packages/pieces/community/framework', +}; diff --git a/packages/pieces/community/framework/package.json b/packages/pieces/community/framework/package.json new file mode 100644 index 0000000..b82cf6b --- /dev/null +++ b/packages/pieces/community/framework/package.json @@ -0,0 +1,6 @@ +{ + "name": "@activepieces/pieces-framework", + "version": "0.14.2", + "type": "commonjs" +} + diff --git a/packages/pieces/community/framework/project.json b/packages/pieces/community/framework/project.json new file mode 100644 index 0000000..8e92452 --- /dev/null +++ b/packages/pieces/community/framework/project.json @@ -0,0 +1,39 @@ +{ + "name": "pieces-framework", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/framework/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/packages/pieces/community/framework", + "main": "packages/pieces/community/framework/src/index.ts", + "tsConfig": "packages/pieces/community/framework/tsconfig.lib.json", + "assets": ["packages/pieces/community/framework/*.md"], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "executor": "nx:run-commands", + "options": { + "command": "node tools/scripts/publish.mjs pieces {args.ver} {args.tag}" + }, + "dependsOn": ["build"] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "packages/pieces/community/framework/jest.config.ts" + } + } + }, + "tags": [] +} diff --git a/packages/pieces/community/framework/src/index.ts b/packages/pieces/community/framework/src/index.ts new file mode 100644 index 0000000..47945b5 --- /dev/null +++ b/packages/pieces/community/framework/src/index.ts @@ -0,0 +1 @@ +export * from './lib'; \ No newline at end of file diff --git a/packages/pieces/community/framework/src/lib/action/action.ts b/packages/pieces/community/framework/src/lib/action/action.ts new file mode 100644 index 0000000..d5d3140 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/action/action.ts @@ -0,0 +1,78 @@ +import { Static, Type } from '@sinclair/typebox'; +import { ActionContext } from '../context'; +import { ActionBase } from '../piece-metadata'; +import { InputPropertyMap } from '../property'; +import { PieceAuthProperty } from '../property/authentication'; + +export type ActionRunner = + (ctx: ActionContext) => Promise + +export const ErrorHandlingOptionsParam = Type.Object({ + retryOnFailure: Type.Object({ + defaultValue: Type.Optional(Type.Boolean()), + hide: Type.Optional(Type.Boolean()), + }), + continueOnFailure: Type.Object({ + defaultValue: Type.Optional(Type.Boolean()), + hide: Type.Optional(Type.Boolean()), + }), +}) +export type ErrorHandlingOptionsParam = Static + +type CreateActionParams = { + /** + * A dummy parameter used to infer {@code PieceAuth} type + */ + name: string + auth?: PieceAuth + displayName: string + description: string + props: ActionProps + run: ActionRunner + test?: ActionRunner + requireAuth?: boolean + errorHandlingOptions?: ErrorHandlingOptionsParam +} + +export class IAction implements ActionBase { + constructor( + public readonly name: string, + public readonly displayName: string, + public readonly description: string, + public readonly props: ActionProps, + public readonly run: ActionRunner, + public readonly test: ActionRunner, + public readonly requireAuth: boolean, + public readonly errorHandlingOptions: ErrorHandlingOptionsParam, + ) { } +} + +export type Action< + PieceAuth extends PieceAuthProperty = any, + ActionProps extends InputPropertyMap = any, +> = IAction + +export const createAction = < + PieceAuth extends PieceAuthProperty = PieceAuthProperty, + ActionProps extends InputPropertyMap = any +>( + params: CreateActionParams, +) => { + return new IAction( + params.name, + params.displayName, + params.description, + params.props, + params.run, + params.test ?? params.run, + params.requireAuth ?? true, + params.errorHandlingOptions ?? { + continueOnFailure: { + defaultValue: false, + }, + retryOnFailure: { + defaultValue: false, + } + }, + ) +} diff --git a/packages/pieces/community/framework/src/lib/context.ts b/packages/pieces/community/framework/src/lib/context.ts new file mode 100644 index 0000000..af566ff --- /dev/null +++ b/packages/pieces/community/framework/src/lib/context.ts @@ -0,0 +1,229 @@ +import { + AppConnectionValue, + ExecutionType, + FlowRunId, + PopulatedFlow, + ProjectId, + RespondResponse, + ResumePayload, + SeekPage, + TriggerPayload, +} from '@activepieces/shared'; +import { TriggerStrategy } from './trigger/trigger'; +import { + InputPropertyMap, + PiecePropValueSchema, + StaticPropsValue, +} from './property'; +import { PieceAuthProperty } from './property/authentication'; +import { DelayPauseMetadata, PauseMetadata, WebhookPauseMetadata } from '@activepieces/shared'; + +type BaseContext< + PieceAuth extends PieceAuthProperty, + Props extends InputPropertyMap +> = { + flows: FlowsContext; + auth: PiecePropValueSchema; + propsValue: StaticPropsValue; + store: Store; + project: { + id: ProjectId; + externalId: () => Promise; + }; + connections: ConnectionsManager; +}; + +type AppWebhookTriggerHookContext< + PieceAuth extends PieceAuthProperty, + TriggerProps extends InputPropertyMap +> = BaseContext & { + webhookUrl: string; + payload: TriggerPayload; + app: { + createListeners({ + events, + identifierValue, + }: { + events: string[]; + identifierValue: string; + }): void; + }; +}; + +type PollingTriggerHookContext< + PieceAuth extends PieceAuthProperty, + TriggerProps extends InputPropertyMap +> = BaseContext & { + setSchedule(schedule: { cronExpression: string; timezone?: string }): void; +}; + +type WebhookTriggerHookContext< + PieceAuth extends PieceAuthProperty, + TriggerProps extends InputPropertyMap +> = BaseContext & { + webhookUrl: string; + payload: TriggerPayload; +}; +export type TriggerHookContext< + PieceAuth extends PieceAuthProperty, + TriggerProps extends InputPropertyMap, + S extends TriggerStrategy +> = S extends TriggerStrategy.APP_WEBHOOK + ? AppWebhookTriggerHookContext + : S extends TriggerStrategy.POLLING + ? PollingTriggerHookContext + : S extends TriggerStrategy.WEBHOOK + ? WebhookTriggerHookContext & { + server: ServerContext; + } + : never; + +export type TestOrRunHookContext< + PieceAuth extends PieceAuthProperty, + TriggerProps extends InputPropertyMap, + S extends TriggerStrategy +> = TriggerHookContext & { + files: FilesService; +}; + +export type StopHookParams = { + response: RespondResponse; +}; + +export type RespondHookParams = { + response: RespondResponse; +}; + +export type StopHook = (params?: StopHookParams) => void; + +export type RespondHook = (params?: RespondHookParams) => void; + +export type PauseHookParams = { + pauseMetadata: PauseMetadata; +}; + +export type PauseHook = (params: { + pauseMetadata: DelayPauseMetadata | Omit +}) => void; + +export type FlowsContext = { + list(): Promise> + current: { + id: string; + version: { + id: string; + }; + }; +} + + + +export type PropertyContext = { + server: ServerContext; + project: { + id: ProjectId; + externalId: () => Promise; + }; + searchValue?: string; + flows: FlowsContext; + connections: ConnectionsManager; +}; + +export type ServerContext = { + apiUrl: string; + publicUrl: string; + token: string; +}; + +export type RunContext = { + id: FlowRunId; + stop: StopHook; + pause: PauseHook; + respond: RespondHook; +} + +export type OnStartContext< + PieceAuth extends PieceAuthProperty, + TriggerProps extends InputPropertyMap +> = Omit, 'flows'> & { + run: Pick; + payload: unknown; +} + + +export type OutputContext = { + update: (params: { + data: { + [key: string]: unknown; + }; + }) => Promise; +} + +export type BaseActionContext< + ET extends ExecutionType, + PieceAuth extends PieceAuthProperty, + ActionProps extends InputPropertyMap +> = BaseContext & { + executionType: ET; + tags: TagsManager; + server: ServerContext; + files: FilesService; + output: OutputContext; + serverUrl: string; + run: RunContext; + generateResumeUrl: (params: { + queryParams: Record, + sync?: boolean + }) => string; +}; + +type BeginExecutionActionContext< + PieceAuth extends PieceAuthProperty = PieceAuthProperty, + ActionProps extends InputPropertyMap = InputPropertyMap +> = BaseActionContext; + +type ResumeExecutionActionContext< + PieceAuth extends PieceAuthProperty = PieceAuthProperty, + ActionProps extends InputPropertyMap = InputPropertyMap +> = BaseActionContext & { + resumePayload: ResumePayload; +}; + +export type ActionContext< + PieceAuth extends PieceAuthProperty = PieceAuthProperty, + ActionProps extends InputPropertyMap = InputPropertyMap +> = + | BeginExecutionActionContext + | ResumeExecutionActionContext; + +export interface FilesService { + write({ + fileName, + data, + }: { + fileName: string; + data: Buffer; + }): Promise; +} + +export interface ConnectionsManager { + get( + key: string + ): Promise | string | null>; +} + +export interface TagsManager { + add(params: { name: string }): Promise; +} + +export interface Store { + put(key: string, value: T, scope?: StoreScope): Promise; + get(key: string, scope?: StoreScope): Promise; + delete(key: string, scope?: StoreScope): Promise; +} + +export enum StoreScope { + // Collection were deprecated in favor of project + PROJECT = 'COLLECTION', + FLOW = 'FLOW', +} \ No newline at end of file diff --git a/packages/pieces/community/framework/src/lib/i18n.ts b/packages/pieces/community/framework/src/lib/i18n.ts new file mode 100644 index 0000000..7f7efac --- /dev/null +++ b/packages/pieces/community/framework/src/lib/i18n.ts @@ -0,0 +1,138 @@ +import { I18nForPiece, PieceMetadataModel, PieceMetadataModelSummary } from "./piece-metadata" +import { LocalesEnum } from "@activepieces/shared" +import path from 'path'; +import fs from 'fs/promises'; +import keys from '../../translation-keys.json' + +function translateProperty(object: Record, path: string, i18n: Record) { + const parsedKeys = path.split('.'); + if (parsedKeys[0] === '*') { + return Object.values(object).forEach(item => translateProperty(item as Record, parsedKeys.slice(1).join('.'), i18n)) + } + const nextObject = object[parsedKeys[0]] as Record; + if (!nextObject) { + return; + } + if (parsedKeys.length > 1) { + return translateProperty(nextObject, parsedKeys.slice(1).join('.'), i18n); + } + const valueInI18n = i18n[object[parsedKeys[0]] as string] + if (valueInI18n) { + object[parsedKeys[0]] = valueInI18n + } +} + + + +async function fileExists(filePath: string) { + try { + await fs.access(filePath); + return true; + } catch { + return false; + } +} + +const readLocaleFile = async (locale: LocalesEnum, pieceOutputPath: string) => { + const filePath = path.join(pieceOutputPath, 'src', 'i18n', `${locale}.json`); + + if (!(await fileExists(filePath))) { + return null; + } + + try { + const fileContent = await fs.readFile(filePath, 'utf8'); + const translations = JSON.parse(fileContent); + + if (typeof translations === 'object' && translations !== null) { + console.log(`Translation loaded for ${locale} in piece ${pieceOutputPath}`); + return translations; + } + throw new Error(`Invalid i18n file format for ${locale} in piece ${pieceOutputPath}`); + } catch (error) { + console.error(`Error reading i18n file for ${locale} in piece ${pieceOutputPath}:`, error); + return null; + } +} + + + +const extractPiecePath = async (pieceName: string) => { + try{ + if(await fileExists(`/node_modules/${pieceName}`)) { + return `/node_modules/${pieceName}`; + } + console.log(`/node_modules/${pieceName} does not exist`) + + const distPath = path.resolve('dist/packages/pieces'); + const fastGlob = (await import('fast-glob')).default; + const packageJsonFiles = await fastGlob('**/**/package.json', { cwd: distPath }); + for (const relativeFile of packageJsonFiles) { + const fullPath = path.join(distPath, relativeFile); + try { + const packageJson = JSON.parse(await fs.readFile(fullPath, 'utf-8')); + if (packageJson.name === pieceName) { + const piecePath = path.dirname(fullPath); + return piecePath; + } + } catch (err) { + console.log(`Error reading package.json at ${fullPath}:`, err); + } + } + } + catch (err) { + console.log(`Error extracting piece path for ${pieceName}:`, err) + } + console.log(`Piece path not found for ${pieceName}`); + return undefined; +} + + + +const translatePiece = (piece: T, locale?: LocalesEnum): T => { + if (!locale) { + return piece + } + try { + const target = piece.i18n?.[locale] + if (!target) { + return piece + } + const translatedPiece: T = JSON.parse(JSON.stringify(piece)) + keys.forEach(key => { + translateProperty(translatedPiece, key, target) + }) + return translatedPiece + } + catch (err) { + console.error(`error translating piece ${piece.name}:`, err) + return piece + } +} +/**Gets the piece metadata regardles of piece location (node_modules or dist), wasn't included inside piece.metadata() for backwards compatibility issues (if an old ap version installs a new piece it would fail)*/ +const initializeI18n = async (pieceName: string): Promise => { + try{ + const locales = Object.values(LocalesEnum); + const i18n: I18nForPiece = {}; + const pieceOutputPath = await extractPiecePath(pieceName) + if (!pieceOutputPath) { + return undefined + } + for (const locale of locales) { + const translation = await readLocaleFile(locale, pieceOutputPath); + if (translation) { + i18n[locale] = translation; + } + } + return (Object.keys(i18n).length > 0) ? i18n : undefined; + } + catch (err) { + console.log(`Error initializing i18n for ${pieceName}:`, err) + return undefined + } +} + +export const pieceTranslation = { + translatePiece, + initializeI18n +} diff --git a/packages/pieces/community/framework/src/lib/index.ts b/packages/pieces/community/framework/src/lib/index.ts new file mode 100644 index 0000000..3c617e9 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/index.ts @@ -0,0 +1,8 @@ +export * from './action/action'; +export * from './property'; +export * from './trigger/trigger'; +export * from './context'; +export * from './piece'; +export * from './piece-metadata'; +export * from './validators/index' +export * from './i18n' \ No newline at end of file diff --git a/packages/pieces/community/framework/src/lib/piece-metadata.ts b/packages/pieces/community/framework/src/lib/piece-metadata.ts new file mode 100644 index 0000000..79d64cf --- /dev/null +++ b/packages/pieces/community/framework/src/lib/piece-metadata.ts @@ -0,0 +1,132 @@ +import { PiecePropertyMap } from "./property"; +import { WebhookRenewConfiguration, TriggerStrategy } from "./trigger/trigger"; +import { ErrorHandlingOptionsParam } from "./action/action"; +import { PieceAuthProperty } from "./property/authentication"; +import { Static, Type } from "@sinclair/typebox"; +import { LocalesEnum, PackageType, PieceCategory, PieceType, ProjectId, TriggerTestStrategy, WebhookHandshakeConfiguration } from "@activepieces/shared"; + +const I18nForPiece = Type.Optional(Type.Partial(Type.Record(Type.Enum(LocalesEnum), Type.Record(Type.String(), Type.String())))); +export type I18nForPiece = Static +export const PieceBase = Type.Object({ + id: Type.Optional(Type.String()), + name: Type.String(), + displayName: Type.String(), + logoUrl: Type.String(), + description: Type.String(), + projectId: Type.Optional(Type.String()), + authors: Type.Array(Type.String()), + platformId: Type.Optional(Type.String()), + directoryPath: Type.Optional(Type.String()), + auth: Type.Optional(PieceAuthProperty), + version: Type.String(), + categories: Type.Optional(Type.Array(Type.Enum(PieceCategory))), + minimumSupportedRelease: Type.Optional(Type.String()), + maximumSupportedRelease: Type.Optional(Type.String()), + i18n:I18nForPiece, +}) + +export type PieceBase = { + id?: string; + name: string; + displayName: string; + logoUrl: string; + description: string; + projectId?: ProjectId; + platformId?: string; + authors: string[], + directoryPath?: string; + auth?: PieceAuthProperty; + version: string; + categories?: PieceCategory[]; + minimumSupportedRelease?: string; + maximumSupportedRelease?: string; + i18n?: Partial>> +} + + +export const ActionBase = Type.Object({ + name: Type.String(), + displayName: Type.String(), + description: Type.String(), + props: PiecePropertyMap, + requireAuth: Type.Boolean(), + errorHandlingOptions: Type.Optional(ErrorHandlingOptionsParam), +}) + +export type ActionBase = { + name: string, + displayName: string, + description: string, + props: PiecePropertyMap, + requireAuth: boolean; + errorHandlingOptions?: ErrorHandlingOptionsParam; +} + +export const TriggerBase = Type.Composite([ + Type.Omit(ActionBase, ["requireAuth"]), + Type.Object({ + type: Type.Enum(TriggerStrategy), + sampleData: Type.Unknown(), + handshakeConfiguration: Type.Optional(WebhookHandshakeConfiguration), + renewConfiguration: Type.Optional(WebhookRenewConfiguration), + testStrategy: Type.Enum(TriggerTestStrategy), + }) +]) +export type TriggerBase = ActionBase & { + type: TriggerStrategy; + sampleData: unknown, + handshakeConfiguration?: WebhookHandshakeConfiguration; + renewConfiguration?: WebhookRenewConfiguration; + testStrategy: TriggerTestStrategy; +}; + +export const PieceMetadata = Type.Composite([ + PieceBase, + Type.Object({ + actions: Type.Record(Type.String(), ActionBase), + triggers: Type.Record(Type.String(), TriggerBase), + }) +]) + +export type PieceMetadata = PieceBase & { + actions: Record; + triggers: Record; +}; + +export const PieceMetadataSummary = Type.Composite([ + Type.Omit(PieceMetadata, ["actions", "triggers"]), + Type.Object({ + actions: Type.Number(), + triggers: Type.Number(), + suggestedActions: Type.Optional(Type.Array(TriggerBase)), + suggestedTriggers: Type.Optional(Type.Array(ActionBase)), + }) +]) +export type PieceMetadataSummary = Omit & { + actions: number; + triggers: number; + suggestedActions?: ActionBase[]; + suggestedTriggers?: TriggerBase[]; +} + + +const PiecePackageMetadata = Type.Object({ + projectUsage: Type.Number(), + tags: Type.Optional(Type.Array(Type.String())), + pieceType: Type.Enum(PieceType), + packageType: Type.Enum(PackageType), + archiveId: Type.Optional(Type.String()), +}) +type PiecePackageMetadata = Static + +export const PieceMetadataModel = Type.Composite([ + PieceMetadata, + PiecePackageMetadata +]) +export type PieceMetadataModel = PieceMetadata & PiecePackageMetadata + +export const PieceMetadataModelSummary = Type.Composite([ + PieceMetadataSummary, + PiecePackageMetadata +]) +export type PieceMetadataModelSummary = PieceMetadataSummary & PiecePackageMetadata; \ No newline at end of file diff --git a/packages/pieces/community/framework/src/lib/piece.ts b/packages/pieces/community/framework/src/lib/piece.ts new file mode 100644 index 0000000..4958970 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/piece.ts @@ -0,0 +1,115 @@ +import { Trigger } from './trigger/trigger'; +import { Action } from './action/action'; +import { + EventPayload, + ParseEventResponse, + PieceCategory, +} from '@activepieces/shared'; +import { PieceBase, PieceMetadata} from './piece-metadata'; +import { PieceAuthProperty } from './property/authentication'; + + +export class Piece + implements Omit +{ + private readonly _actions: Record = {}; + private readonly _triggers: Record = {}; + + constructor( + public readonly displayName: string, + public readonly logoUrl: string, + public readonly authors: string[], + public readonly events: PieceEventProcessors | undefined, + actions: Action[], + triggers: Trigger[], + public readonly categories: PieceCategory[], + public readonly auth?: PieceAuth, + public readonly minimumSupportedRelease?: string, + public readonly maximumSupportedRelease?: string, + public readonly description = '', + ) { + actions.forEach((action) => (this._actions[action.name] = action)); + triggers.forEach((trigger) => (this._triggers[trigger.name] = trigger)); + } + + + metadata(): BackwardCompatiblePieceMetadata { + return { + displayName: this.displayName, + logoUrl: this.logoUrl, + actions: this._actions, + triggers: this._triggers, + categories: this.categories, + description: this.description, + authors: this.authors, + auth: this.auth, + minimumSupportedRelease: this.minimumSupportedRelease, + maximumSupportedRelease: this.maximumSupportedRelease + }; + } + + getAction(actionName: string): Action | undefined { + return this._actions[actionName]; + } + + getTrigger(triggerName: string): Trigger | undefined { + return this._triggers[triggerName]; + } + + actions() { + return this._actions; + } + + triggers() { + return this._triggers; + } +} + +export const createPiece = ( + params: CreatePieceParams +) => { + return new Piece( + params.displayName, + params.logoUrl, + params.authors ?? [], + params.events, + params.actions, + params.triggers, + params.categories ?? [], + params.auth ?? undefined, + params.minimumSupportedRelease, + params.maximumSupportedRelease, + params.description, + ); +}; + +type CreatePieceParams< + PieceAuth extends PieceAuthProperty = PieceAuthProperty +> = { + displayName: string; + logoUrl: string; + authors: string[]; + description?: string; + auth: PieceAuth | undefined; + events?: PieceEventProcessors; + minimumSupportedRelease?: string; + maximumSupportedRelease?: string; + actions: Action[]; + triggers: Trigger[]; + categories?: PieceCategory[]; +}; + +type PieceEventProcessors = { + parseAndReply: (ctx: { payload: EventPayload }) => ParseEventResponse; + verify: (ctx: { + webhookSecret: string | Record; + payload: EventPayload; + appWebhookUrl: string; + }) => boolean; +}; + +type BackwardCompatiblePieceMetadata = Omit & { + authors?: PieceMetadata['authors'] + i18n?: PieceMetadata['i18n'] +} + diff --git a/packages/pieces/community/framework/src/lib/property/authentication/basic-auth-prop.ts b/packages/pieces/community/framework/src/lib/property/authentication/basic-auth-prop.ts new file mode 100644 index 0000000..9ff6067 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/authentication/basic-auth-prop.ts @@ -0,0 +1,44 @@ +import { Static, Type } from '@sinclair/typebox'; +import { TPropertyValue } from '../input/common'; +import { PropertyType } from '../input/property-type'; +import { BasePieceAuthSchema } from './common'; + +export const BasicAuthPropertyValue = Type.Object({ + username: Type.String(), + password: Type.String(), +}) + +export type BasicAuthPropertyValue = Static + +export const BasicAuthProperty = Type.Composite([ + BasePieceAuthSchema, + Type.Object({ + username: Type.Object({ + displayName: Type.String(), + description: Type.Optional(Type.String()) + }), + password: Type.Object({ + displayName: Type.String(), + description: Type.Optional(Type.String()) + }) + }), + TPropertyValue(BasicAuthPropertyValue, PropertyType.BASIC_AUTH) +]) + +export type BasicAuthProperty = + BasePieceAuthSchema & { + username: { + displayName: string; + description?: string; + }; + password: { + displayName: string; + description?: string; + }; + } & + TPropertyValue< + BasicAuthPropertyValue, + PropertyType.BASIC_AUTH, + true + >; + diff --git a/packages/pieces/community/framework/src/lib/property/authentication/common.ts b/packages/pieces/community/framework/src/lib/property/authentication/common.ts new file mode 100644 index 0000000..445d1b0 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/authentication/common.ts @@ -0,0 +1,19 @@ +import { Type } from '@sinclair/typebox'; +import { ServerContext } from '../../context'; + +export const BasePieceAuthSchema = Type.Object({ + displayName: Type.String(), + description: Type.Optional(Type.String()) +}); + +export type BasePieceAuthSchema = { + displayName: string; + description?: string; + validate?: (params: { auth: AuthValueSchema; server: Omit }) => Promise< + | { valid: true } + | { + valid: false; + error: string; + } + >; +}; diff --git a/packages/pieces/community/framework/src/lib/property/authentication/custom-auth-prop.ts b/packages/pieces/community/framework/src/lib/property/authentication/custom-auth-prop.ts new file mode 100644 index 0000000..f499e3f --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/authentication/custom-auth-prop.ts @@ -0,0 +1,50 @@ +import { Type } from '@sinclair/typebox'; +import { TPropertyValue } from '../input/common'; +import { PropertyType } from '../input/property-type'; +import { LongTextProperty, ShortTextProperty } from '../input/text-property'; +import { NumberProperty } from '../input/number-property'; +import { CheckboxProperty } from '../input/checkbox-property'; +import { StaticDropdownProperty } from '../input/dropdown/static-dropdown'; +import { StaticPropsValue } from '..'; +import { SecretTextProperty } from './secret-text-property'; +import { BasePieceAuthSchema } from './common'; +import { MarkDownProperty } from '../input/markdown-property'; + +const CustomAuthProps = Type.Record(Type.String(), Type.Union([ + ShortTextProperty, + LongTextProperty, + NumberProperty, + CheckboxProperty, + StaticDropdownProperty, +])); + +export type CustomAuthProps = Record< + string, + | ShortTextProperty + | LongTextProperty + | SecretTextProperty + | NumberProperty + | StaticDropdownProperty + | CheckboxProperty + | MarkDownProperty +>; + +export const CustomAuthProperty = Type.Composite([ + BasePieceAuthSchema, + Type.Object({ + props: CustomAuthProps, + }), + TPropertyValue(Type.Unknown(), PropertyType.CUSTOM_AUTH) +]) + +export type CustomAuthProperty< + T extends CustomAuthProps +> = BasePieceAuthSchema> & { + props: T; +} & + TPropertyValue< + StaticPropsValue, + PropertyType.CUSTOM_AUTH, + true + >; + diff --git a/packages/pieces/community/framework/src/lib/property/authentication/index.ts b/packages/pieces/community/framework/src/lib/property/authentication/index.ts new file mode 100644 index 0000000..c37d3a9 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/authentication/index.ts @@ -0,0 +1,71 @@ + +import { Type } from "@sinclair/typebox"; +import { BasicAuthProperty } from "./basic-auth-prop"; +import { CustomAuthProperty, CustomAuthProps } from "./custom-auth-prop"; +import { SecretTextProperty } from "./secret-text-property"; +import { PropertyType } from "../input/property-type"; +import { OAuth2Property, OAuth2Props } from "./oauth2-prop"; + +export const PieceAuthProperty = Type.Union([ + BasicAuthProperty, + CustomAuthProperty, + OAuth2Property, + SecretTextProperty, +]) + +export type PieceAuthProperty = BasicAuthProperty | CustomAuthProperty | OAuth2Property | SecretTextProperty; + +type AuthProperties = Omit, 'displayName'>; + +type Properties = Omit< + T, + 'valueSchema' | 'type' | 'defaultValidators' | 'defaultProcessors' +>; + + +export const PieceAuth = { + SecretText( + request: Properties> + ): R extends true ? SecretTextProperty : SecretTextProperty { + return { + ...request, + valueSchema: undefined, + type: PropertyType.SECRET_TEXT, + required: true + } as unknown as R extends true ? SecretTextProperty : SecretTextProperty; + }, + OAuth2( + request: AuthProperties> + ): OAuth2Property { + return { + ...request, + valueSchema: undefined, + type: PropertyType.OAUTH2, + displayName: 'Connection', + } as unknown as OAuth2Property + }, + BasicAuth( + request: AuthProperties + ): BasicAuthProperty { + return { + ...request, + valueSchema: undefined, + type: PropertyType.BASIC_AUTH, + displayName: 'Connection', + required: true, + } as unknown as BasicAuthProperty; + }, + CustomAuth( + request: AuthProperties> + ): CustomAuthProperty { + return { + ...request, + valueSchema: undefined, + type: PropertyType.CUSTOM_AUTH, + displayName: 'Connection', + } as unknown as CustomAuthProperty + }, + None() { + return undefined; + }, +}; \ No newline at end of file diff --git a/packages/pieces/community/framework/src/lib/property/authentication/oauth2-prop.ts b/packages/pieces/community/framework/src/lib/property/authentication/oauth2-prop.ts new file mode 100644 index 0000000..2b89483 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/authentication/oauth2-prop.ts @@ -0,0 +1,86 @@ +import { BOTH_CLIENT_CREDENTIALS_AND_AUTHORIZATION_CODE, OAuth2GrantType } from '@activepieces/shared'; +import { Type } from '@sinclair/typebox'; +import { ShortTextProperty } from '../input/text-property'; +import { SecretTextProperty } from './secret-text-property'; +import { BasePieceAuthSchema } from './common'; +import { TPropertyValue } from '../input/common'; +import { PropertyType } from '../input/property-type'; +import { StaticDropdownProperty } from '../input/dropdown/static-dropdown'; +import { StaticPropsValue } from '..'; + +export enum OAuth2AuthorizationMethod { + HEADER = 'HEADER', + BODY = 'BODY', +} + +const OAuthProp = Type.Union([ + ShortTextProperty, + SecretTextProperty, + StaticDropdownProperty, +]) + +type OAuthProp = + | ShortTextProperty + | SecretTextProperty + | StaticDropdownProperty; + + +export const OAuth2Props = Type.Record(Type.String(), OAuthProp); + +export type OAuth2Props = { + [key: string]: OAuthProp; +} + +type OAuthPropsValue = StaticPropsValue; + + +const OAuth2ExtraProps = Type.Object({ + props: Type.Optional(Type.Record(Type.String(), OAuthProp)), + authUrl: Type.String(), + tokenUrl: Type.String(), + scope: Type.Array(Type.String()), + pkce: Type.Optional(Type.Boolean()), + authorizationMethod: Type.Optional(Type.Enum(OAuth2AuthorizationMethod)), + grantType: Type.Optional(Type.Union([Type.Enum(OAuth2GrantType), Type.Literal(BOTH_CLIENT_CREDENTIALS_AND_AUTHORIZATION_CODE)])), + extra: Type.Optional(Type.Record(Type.String(), Type.String())), +}) + +type OAuth2ExtraProps = { + props?: OAuth2Props + authUrl: string + tokenUrl: string + scope: string[] + pkce?: boolean + authorizationMethod?: OAuth2AuthorizationMethod + grantType?: OAuth2GrantType | typeof BOTH_CLIENT_CREDENTIALS_AND_AUTHORIZATION_CODE + extra?: Record, +} + +export const OAuth2PropertyValue = Type.Object({ + access_token: Type.String(), + props: Type.Optional(OAuth2Props), + data: Type.Record(Type.String(), Type.Any()) +}) + +export type OAuth2PropertyValue = { + access_token: string; + props?: OAuthPropsValue; + data: Record; +}; + +export const OAuth2Property = Type.Composite([ + BasePieceAuthSchema, + OAuth2ExtraProps, + TPropertyValue(OAuth2PropertyValue, PropertyType.OAUTH2) +]) + +export type OAuth2Property< + T extends OAuth2Props +> = BasePieceAuthSchema & + OAuth2ExtraProps & + TPropertyValue< + OAuth2PropertyValue, + PropertyType.OAUTH2, + true + >; + diff --git a/packages/pieces/community/framework/src/lib/property/authentication/secret-text-property.ts b/packages/pieces/community/framework/src/lib/property/authentication/secret-text-property.ts new file mode 100644 index 0000000..8b80997 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/authentication/secret-text-property.ts @@ -0,0 +1,20 @@ +import { Type } from "@sinclair/typebox"; +import { BasePieceAuthSchema } from "./common"; +import { TPropertyValue } from "../input/common"; +import { PropertyType } from "../input/property-type"; + +export const SecretTextProperty = Type.Composite([ + BasePieceAuthSchema, + TPropertyValue(Type.Object({ + auth: Type.String() + }), PropertyType.SECRET_TEXT) +]) + + +export type SecretTextProperty = + BasePieceAuthSchema & + TPropertyValue< + string, + PropertyType.SECRET_TEXT, + R + >; \ No newline at end of file diff --git a/packages/pieces/community/framework/src/lib/property/index.ts b/packages/pieces/community/framework/src/lib/property/index.ts new file mode 100644 index 0000000..5d5c0a2 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/index.ts @@ -0,0 +1,68 @@ +import { InputProperty } from './input'; +import { PieceAuthProperty } from './authentication'; +import { Type } from '@sinclair/typebox'; +import { PropertyType } from './input/property-type'; +import { DropdownState } from './input/dropdown/common'; + +// EXPORTED +export { ApFile } from './input/file-property'; +export { DropdownProperty, MultiSelectDropdownProperty } from './input/dropdown/dropdown-prop'; +export { DynamicProperties, DynamicProp } from './input/dynamic-prop'; +export { PropertyType } from './input/property-type'; +export { Property } from './input'; +export { PieceAuth } from './authentication'; +export { DynamicPropsValue } from './input/dynamic-prop'; +export { DropdownOption,DropdownState } from './input/dropdown/common'; +export { OAuth2PropertyValue } from './authentication/oauth2-prop'; +export { PieceAuthProperty } from './authentication'; +export { ShortTextProperty } from './input/text-property'; +export { ArrayProperty, ArraySubProps } from './input/array-property'; +export { BasePropertySchema } from './input/common'; +export { CheckboxProperty } from './input/checkbox-property'; +export { DateTimeProperty } from './input/date-time-property'; +export { LongTextProperty } from './input/text-property'; +export { NumberProperty } from './input/number-property'; +export { ObjectProperty } from './input/object-property'; +export { OAuth2Props } from './authentication/oauth2-prop'; +export { OAuth2AuthorizationMethod } from './authentication/oauth2-prop'; +export { BasicAuthPropertyValue } from './authentication/basic-auth-prop'; +export { StaticMultiSelectDropdownProperty } from './input/dropdown/static-dropdown'; +export { StaticDropdownProperty } from './input/dropdown/static-dropdown'; +export * from './authentication/custom-auth-prop'; +export { OAuth2Property } from './authentication/oauth2-prop'; +export { FileProperty } from './input/file-property'; +export { BasicAuthProperty } from './authentication/basic-auth-prop'; +export { SecretTextProperty } from './authentication/secret-text-property' +export { CustomAuthProperty } from './authentication/custom-auth-prop'; +export { JsonProperty } from './input/json-property' +export const PieceProperty = Type.Union([InputProperty, PieceAuthProperty]) +export type PieceProperty = InputProperty | PieceAuthProperty; +export {CustomProperty} from './input/custom-property' +export type {CustomPropertyCodeFunctionParams} from './input/custom-property' +export const PiecePropertyMap = Type.Record(Type.String(), PieceProperty) +export interface PiecePropertyMap { + [name: string]: PieceProperty; +} + +export const InputPropertyMap = Type.Record(Type.String(), InputProperty) +export interface InputPropertyMap { + [name: string]: InputProperty; +} + +export type PiecePropValueSchema = + T extends undefined + ? undefined + : T extends { required: true } + ? T['valueSchema'] + : T['valueSchema'] | undefined; + +export type StaticPropsValue = { + [P in keyof T]: PiecePropValueSchema; +}; + + + +export type ExecutePropsResult = { + type: T + options: T extends PropertyType.DROPDOWN ? DropdownState : T extends PropertyType.MULTI_SELECT_DROPDOWN ? DropdownState : InputPropertyMap +} diff --git a/packages/pieces/community/framework/src/lib/property/input/array-property.ts b/packages/pieces/community/framework/src/lib/property/input/array-property.ts new file mode 100644 index 0000000..cb7473c --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/array-property.ts @@ -0,0 +1,52 @@ +import { Type } from "@sinclair/typebox"; +import { BasePropertySchema, TPropertyValue } from "./common"; +import { PropertyType } from "./property-type"; +import { LongTextProperty, ShortTextProperty } from "./text-property"; +import { StaticDropdownProperty, StaticMultiSelectDropdownProperty } from "./dropdown/static-dropdown"; +import { MultiSelectDropdownProperty } from "./dropdown/dropdown-prop"; +import { CheckboxProperty } from "./checkbox-property"; +import { NumberProperty } from "./number-property"; +import { FileProperty } from "./file-property"; +import { JsonProperty } from './json-property'; +import { ColorProperty } from "./color-property"; +import { DateTimeProperty } from './date-time-property'; + +export const ArraySubProps = Type.Record(Type.String(), Type.Union([ + ShortTextProperty, + LongTextProperty, + StaticDropdownProperty, + MultiSelectDropdownProperty, + StaticMultiSelectDropdownProperty, + CheckboxProperty, + NumberProperty, + FileProperty, + DateTimeProperty, +])) + +export const ArrayProperty = Type.Composite([ + BasePropertySchema, + Type.Object({ + properties: ArraySubProps + }), + TPropertyValue(Type.Array(Type.Unknown()), PropertyType.ARRAY) +]) + +export type ArraySubProps = Record< + string, + | ShortTextProperty + | LongTextProperty + | StaticDropdownProperty + | MultiSelectDropdownProperty + | StaticMultiSelectDropdownProperty + | CheckboxProperty + | NumberProperty + | FileProperty + | JsonProperty + | ColorProperty + | DateTimeProperty +>; + +export type ArrayProperty = BasePropertySchema & +{ + properties?: ArraySubProps; +} & TPropertyValue; diff --git a/packages/pieces/community/framework/src/lib/property/input/checkbox-property.ts b/packages/pieces/community/framework/src/lib/property/input/checkbox-property.ts new file mode 100644 index 0000000..0160557 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/checkbox-property.ts @@ -0,0 +1,11 @@ +import { Type } from "@sinclair/typebox"; +import { BasePropertySchema, TPropertyValue } from "./common"; +import { PropertyType } from "./property-type"; + +export const CheckboxProperty = Type.Composite([ + BasePropertySchema, + TPropertyValue(Type.Boolean(), PropertyType.CHECKBOX) +]) + +export type CheckboxProperty = BasePropertySchema & + TPropertyValue; diff --git a/packages/pieces/community/framework/src/lib/property/input/color-property.ts b/packages/pieces/community/framework/src/lib/property/input/color-property.ts new file mode 100644 index 0000000..8b1403a --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/color-property.ts @@ -0,0 +1,12 @@ +import { Type } from "@sinclair/typebox"; +import { BasePropertySchema, TPropertyValue } from "./common"; +import { PropertyType } from "./property-type"; + +export const ColorProperty = Type.Composite([ + BasePropertySchema, + TPropertyValue(Type.String(), PropertyType.COLOR) +]) + + +export type ColorProperty = BasePropertySchema & + TPropertyValue; \ No newline at end of file diff --git a/packages/pieces/community/framework/src/lib/property/input/common.ts b/packages/pieces/community/framework/src/lib/property/input/common.ts new file mode 100644 index 0000000..3896a57 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/common.ts @@ -0,0 +1,57 @@ +import { Static, TObject, TSchema, Type } from "@sinclair/typebox"; +import { ApFile } from "./file-property"; +import { PropertyType } from "./property-type"; + + + +export const BasePropertySchema = Type.Object({ + displayName: Type.String(), + description: Type.Optional(Type.String()) +}) + +export type BasePropertySchema = Static + +export const TPropertyValue = (T: T, propertyType: U): TObject => Type.Object({ + type: Type.Literal(propertyType), + required: Type.Boolean(), + defaultValue: Type.Optional(Type.Any()), +}) + +export type TPropertyValue< + T, + U extends PropertyType, + REQUIRED extends boolean +> = { + valueSchema: T; + type: U; + required: REQUIRED; + // TODO this should be T or undefined + defaultValue?: U extends PropertyType.ARRAY + ? unknown[] + : U extends PropertyType.JSON + ? object + : U extends PropertyType.CHECKBOX + ? boolean + : U extends PropertyType.LONG_TEXT + ? string + : U extends PropertyType.SHORT_TEXT + ? string + : U extends PropertyType.NUMBER + ? number + : U extends PropertyType.DROPDOWN + ? unknown + : U extends PropertyType.MULTI_SELECT_DROPDOWN + ? unknown[] + : U extends PropertyType.STATIC_MULTI_SELECT_DROPDOWN + ? unknown[] + : U extends PropertyType.STATIC_DROPDOWN + ? unknown + : U extends PropertyType.DATE_TIME + ? string + : U extends PropertyType.FILE + ? ApFile + : U extends PropertyType.COLOR + ? string + : unknown; +}; + diff --git a/packages/pieces/community/framework/src/lib/property/input/custom-property.ts b/packages/pieces/community/framework/src/lib/property/input/custom-property.ts new file mode 100644 index 0000000..c204955 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/custom-property.ts @@ -0,0 +1,32 @@ +import { Type } from "@sinclair/typebox"; +import { BasePropertySchema, TPropertyValue } from "./common"; +import { PropertyType } from "./property-type"; + + +// Code should be a valid javascript function that takes a single argument which is an object +/* +(ctx: {containerId:string, value: unknown, onChange: (value: unknown) => void, isEmbeded: boolean, projectId:string}) => void +*/ +export const CustomProperty = Type.Composite([ + BasePropertySchema, + TPropertyValue(Type.Unknown(), PropertyType.CUSTOM), + Type.Object({ + code: Type.String(), + }) +]) + +export type CustomProperty = BasePropertySchema & + TPropertyValue & { + code:string; + } + +export type CustomPropertyCodeFunctionParams = + { + containerId:string, + value: unknown, + onChange: (value: unknown) => void, + isEmbeded: boolean, + projectId:string, + property: Pick, 'displayName' | 'description' | 'required'>, + disabled: boolean + } diff --git a/packages/pieces/community/framework/src/lib/property/input/date-time-property.ts b/packages/pieces/community/framework/src/lib/property/input/date-time-property.ts new file mode 100644 index 0000000..9a01447 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/date-time-property.ts @@ -0,0 +1,11 @@ +import { Type } from "@sinclair/typebox"; +import { BasePropertySchema, TPropertyValue } from "./common"; +import { PropertyType } from "./property-type"; + +export const DateTimeProperty = Type.Composite([ + BasePropertySchema, + TPropertyValue(Type.String(), PropertyType.DATE_TIME) +]) + +export type DateTimeProperty = BasePropertySchema & + TPropertyValue; \ No newline at end of file diff --git a/packages/pieces/community/framework/src/lib/property/input/dropdown/common.ts b/packages/pieces/community/framework/src/lib/property/input/dropdown/common.ts new file mode 100644 index 0000000..62119b0 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/dropdown/common.ts @@ -0,0 +1,26 @@ +import { Type } from "@sinclair/typebox"; + + +export const DropdownOption = Type.Object({ + label: Type.String(), + value: Type.Unknown(), +}) + +export type DropdownOption = { + label: string; + value: T; +} + +export const DropdownState = Type.Object({ + disabled: Type.Optional(Type.Boolean()), + placeholder: Type.Optional(Type.String()), + options: Type.Array(DropdownOption) +}) + +export type DropdownState = { + disabled?: boolean; + placeholder?: string; + options: DropdownOption[]; +} + + diff --git a/packages/pieces/community/framework/src/lib/property/input/dropdown/dropdown-prop.ts b/packages/pieces/community/framework/src/lib/property/input/dropdown/dropdown-prop.ts new file mode 100644 index 0000000..fcf0d8d --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/dropdown/dropdown-prop.ts @@ -0,0 +1,45 @@ +import { BasePropertySchema, TPropertyValue } from "../common"; +import { DropdownState } from "./common"; +import { PropertyContext } from "../../../context"; +import { Type } from "@sinclair/typebox"; +import { PropertyType } from "../property-type"; + +type DynamicDropdownOptions = ( + propsValue: Record, + ctx: PropertyContext +) => Promise>; + +export const DropdownProperty = Type.Composite([ + BasePropertySchema, + TPropertyValue(Type.Unknown(), PropertyType.DROPDOWN), + Type.Object({ + refreshers: Type.Array(Type.String()), + }), +]); + +export type DropdownProperty = BasePropertySchema & { + refreshers: string[]; + refreshOnSearch?: boolean; + options: DynamicDropdownOptions; +} & TPropertyValue; + + +export const MultiSelectDropdownProperty = Type.Composite([ + BasePropertySchema, + TPropertyValue(Type.Array(Type.Unknown()), PropertyType.MULTI_SELECT_DROPDOWN), + Type.Object({ + refreshers: Type.Array(Type.String()), + }), +]); + +export type MultiSelectDropdownProperty< + T, + R extends boolean +> = BasePropertySchema & { + refreshers: string[]; + options: DynamicDropdownOptions; +} & TPropertyValue< + T[], + PropertyType.MULTI_SELECT_DROPDOWN, + R +>; diff --git a/packages/pieces/community/framework/src/lib/property/input/dropdown/static-dropdown.ts b/packages/pieces/community/framework/src/lib/property/input/dropdown/static-dropdown.ts new file mode 100644 index 0000000..b02b79e --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/dropdown/static-dropdown.ts @@ -0,0 +1,39 @@ +import { Type } from "@sinclair/typebox"; +import { BasePropertySchema, TPropertyValue } from "../common"; +import { DropdownState } from "./common"; +import { PropertyType } from "../property-type"; + +export const StaticDropdownProperty = Type.Composite([ + BasePropertySchema, + Type.Object({ + options: DropdownState + }), + TPropertyValue(Type.Unknown(), PropertyType.STATIC_DROPDOWN) +]) + +export type StaticDropdownProperty< + T, + R extends boolean +> = BasePropertySchema & { + options: DropdownState; +} & TPropertyValue; + + +export const StaticMultiSelectDropdownProperty = Type.Composite([ + BasePropertySchema, + Type.Object({ + options: DropdownState + }), + TPropertyValue(Type.Array(Type.Unknown()), PropertyType.STATIC_MULTI_SELECT_DROPDOWN) +]) + +export type StaticMultiSelectDropdownProperty< + T, + R extends boolean +> = BasePropertySchema & { + options: DropdownState; +} & TPropertyValue< + T[], + PropertyType.STATIC_MULTI_SELECT_DROPDOWN, + R +>; \ No newline at end of file diff --git a/packages/pieces/community/framework/src/lib/property/input/dynamic-prop.ts b/packages/pieces/community/framework/src/lib/property/input/dynamic-prop.ts new file mode 100644 index 0000000..221897d --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/dynamic-prop.ts @@ -0,0 +1,51 @@ +import { Type } from "@sinclair/typebox"; +import { StaticDropdownProperty, StaticMultiSelectDropdownProperty } from "./dropdown/static-dropdown"; +import { ShortTextProperty } from "./text-property"; +import { BasePropertySchema, TPropertyValue } from "./common"; +import { PropertyContext } from "../../context"; +import { PropertyType } from "./property-type"; +import { JsonProperty } from "./json-property"; +import { ArrayProperty } from "./array-property"; +import { DropdownState, InputPropertyMap } from ".."; + +export const DynamicProp = Type.Union([ + ShortTextProperty, + StaticDropdownProperty, + JsonProperty, + ArrayProperty, + StaticMultiSelectDropdownProperty, +]) + +export type DynamicProp = + | ShortTextProperty + | StaticDropdownProperty + | JsonProperty + | ArrayProperty + | StaticMultiSelectDropdownProperty; + +export const DynamicPropsValue = Type.Record(Type.String(), DynamicProp); + +export type DynamicPropsValue = Record; + +export const DynamicProperties = Type.Composite([ + Type.Object({ + refreshers: Type.Array(Type.String()), + }), + BasePropertySchema, + TPropertyValue(Type.Unknown(), PropertyType.DYNAMIC), +]) + +export type DynamicProperties = BasePropertySchema & +{ + props: ( + propsValue: Record, + ctx: PropertyContext + ) => Promise; + refreshers: string[]; +} & + TPropertyValue< + DynamicPropsValue, + PropertyType.DYNAMIC, + R + >; + diff --git a/packages/pieces/community/framework/src/lib/property/input/file-property.ts b/packages/pieces/community/framework/src/lib/property/input/file-property.ts new file mode 100644 index 0000000..de4341c --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/file-property.ts @@ -0,0 +1,23 @@ +import { Type } from "@sinclair/typebox"; +import { BasePropertySchema, TPropertyValue } from "./common"; +import { PropertyType } from "./property-type"; + +export class ApFile { + constructor( + public filename: string, + public data: Buffer, + public extension?: string + ) { } + + get base64(): string { + return this.data.toString('base64'); + } +} + +export const FileProperty = Type.Composite([ + BasePropertySchema, + TPropertyValue(Type.Unknown(), PropertyType.FILE) +]) + +export type FileProperty = BasePropertySchema & + TPropertyValue; diff --git a/packages/pieces/community/framework/src/lib/property/input/index.ts b/packages/pieces/community/framework/src/lib/property/input/index.ts new file mode 100644 index 0000000..5bedeec --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/index.ts @@ -0,0 +1,270 @@ +import { Type } from '@sinclair/typebox'; +import { ArrayProperty } from './array-property'; +import { CheckboxProperty } from './checkbox-property'; +import { DateTimeProperty } from './date-time-property'; +import { + DropdownProperty, + MultiSelectDropdownProperty, +} from './dropdown/dropdown-prop'; +import { + StaticDropdownProperty, + StaticMultiSelectDropdownProperty, +} from './dropdown/static-dropdown'; +import { DynamicProperties } from './dynamic-prop'; +import { FileProperty } from './file-property'; +import { JsonProperty } from './json-property'; +import { MarkDownProperty } from './markdown-property'; +import { MarkdownVariant } from '@activepieces/shared'; +import { NumberProperty } from './number-property'; +import { ObjectProperty } from './object-property'; +import { PropertyType } from './property-type'; +import { LongTextProperty, ShortTextProperty } from './text-property'; +import { CustomProperty, CustomPropertyCodeFunctionParams } from './custom-property'; +import { ColorProperty } from './color-property'; + +export const InputProperty = Type.Union([ + ShortTextProperty, + LongTextProperty, + MarkDownProperty, + CheckboxProperty, + StaticDropdownProperty, + StaticMultiSelectDropdownProperty, + DropdownProperty, + MultiSelectDropdownProperty, + DynamicProperties, + NumberProperty, + ArrayProperty, + ObjectProperty, + JsonProperty, + DateTimeProperty, + FileProperty, + ColorProperty, +]); + +export type InputProperty = + | ShortTextProperty + | LongTextProperty + | MarkDownProperty + | CheckboxProperty + | DropdownProperty + | StaticDropdownProperty + | NumberProperty + | ArrayProperty + | ObjectProperty + | JsonProperty + | MultiSelectDropdownProperty + | StaticMultiSelectDropdownProperty + | DynamicProperties + | DateTimeProperty + | FileProperty + | CustomProperty + | ColorProperty; + + +type Properties = Omit< + T, + 'valueSchema' | 'type' | 'defaultValidators' | 'defaultProcessors' +>; + +export const Property = { + ShortText( + request: Properties> + ): R extends true ? ShortTextProperty : ShortTextProperty { + return { + ...request, + valueSchema: undefined, + type: PropertyType.SHORT_TEXT, + } as unknown as R extends true + ? ShortTextProperty + : ShortTextProperty; + }, + Checkbox( + request: Properties> + ): R extends true ? CheckboxProperty : CheckboxProperty { + return { + ...request, + valueSchema: undefined, + type: PropertyType.CHECKBOX, + } as unknown as R extends true + ? CheckboxProperty + : CheckboxProperty; + }, + LongText( + request: Properties> + ): R extends true ? LongTextProperty : LongTextProperty { + return { + ...request, + valueSchema: undefined, + type: PropertyType.LONG_TEXT, + } as unknown as R extends true + ? LongTextProperty + : LongTextProperty; + }, + MarkDown(request: { + value: string; + variant?: MarkdownVariant; + }): MarkDownProperty { + return { + displayName: 'Markdown', + required: false, + description: request.value, + type: PropertyType.MARKDOWN, + valueSchema: undefined as never, + variant: request.variant ?? MarkdownVariant.INFO, + }; + }, + Number( + request: Properties> + ): R extends true ? NumberProperty : NumberProperty { + return { + ...request, + valueSchema: undefined, + type: PropertyType.NUMBER, + } as unknown as R extends true + ? NumberProperty + : NumberProperty; + }, + + Json( + request: Properties> + ): R extends true ? JsonProperty : JsonProperty { + return { + ...request, + valueSchema: undefined, + type: PropertyType.JSON, + } as unknown as R extends true ? JsonProperty : JsonProperty; + }, + Array( + request: Properties> + ): R extends true ? ArrayProperty : ArrayProperty { + return { + ...request, + valueSchema: undefined, + type: PropertyType.ARRAY, + } as unknown as R extends true ? ArrayProperty : ArrayProperty; + }, + Object( + request: Properties> + ): R extends true ? ObjectProperty : ObjectProperty { + return { + ...request, + valueSchema: undefined, + type: PropertyType.OBJECT, + } as unknown as R extends true + ? ObjectProperty + : ObjectProperty; + }, + Dropdown( + request: Properties> + ): R extends true ? DropdownProperty : DropdownProperty { + return { + ...request, + valueSchema: undefined, + type: PropertyType.DROPDOWN, + } as unknown as R extends true + ? DropdownProperty + : DropdownProperty; + }, + StaticDropdown( + request: Properties> + ): R extends true + ? StaticDropdownProperty + : StaticDropdownProperty { + return { + ...request, + valueSchema: undefined, + type: PropertyType.STATIC_DROPDOWN, + } as unknown as R extends true + ? StaticDropdownProperty + : StaticDropdownProperty; + }, + MultiSelectDropdown( + request: Properties> + ): R extends true + ? MultiSelectDropdownProperty + : MultiSelectDropdownProperty { + return { + ...request, + valueSchema: undefined, + type: PropertyType.MULTI_SELECT_DROPDOWN, + } as unknown as R extends true + ? MultiSelectDropdownProperty + : MultiSelectDropdownProperty; + }, + DynamicProperties( + request: Properties> + ): R extends true ? DynamicProperties : DynamicProperties { + return { + ...request, + valueSchema: undefined, + type: PropertyType.DYNAMIC, + } as unknown as R extends true + ? DynamicProperties + : DynamicProperties; + }, + StaticMultiSelectDropdown( + request: Properties> + ): R extends true + ? StaticMultiSelectDropdownProperty + : StaticMultiSelectDropdownProperty { + return { + ...request, + valueSchema: undefined, + type: PropertyType.STATIC_MULTI_SELECT_DROPDOWN, + } as unknown as R extends true + ? StaticMultiSelectDropdownProperty + : StaticMultiSelectDropdownProperty; + }, + DateTime( + request: Properties> + ): R extends true ? DateTimeProperty : DateTimeProperty { + return { + ...request, + valueSchema: undefined, + type: PropertyType.DATE_TIME, + } as unknown as R extends true + ? DateTimeProperty + : DateTimeProperty; + }, + File( + request: Properties> + ): R extends true ? FileProperty : FileProperty { + return { + ...request, + valueSchema: undefined, + type: PropertyType.FILE, + } as unknown as R extends true ? FileProperty : FileProperty; + }, + Custom( + request: Omit>, 'code'> & { + /** + * This is designed to be self-contained and operates independently of any + * external libraries or imported dependencies. All necessary logic and + * functionality are implemented within this function itself. + * + * You can return a cleanup function that will be called when the component is unmounted in the frontend. + * */ + code: ((ctx: CustomPropertyCodeFunctionParams) => (()=>void) | void) + } + ): R extends true ? CustomProperty : CustomProperty { + const code = request.code.toString(); + return { + ...request, + code, + valueSchema: undefined, + type: PropertyType.CUSTOM, + } as unknown as R extends true ? CustomProperty : CustomProperty; + }, + Color( + request: Properties> + ): R extends true ? ColorProperty : ColorProperty { + return { + ...request, + valueSchema: undefined, + type: PropertyType.COLOR, + } as unknown as R extends true + ? ColorProperty + : ColorProperty; + }, +}; + diff --git a/packages/pieces/community/framework/src/lib/property/input/json-property.ts b/packages/pieces/community/framework/src/lib/property/input/json-property.ts new file mode 100644 index 0000000..36fe307 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/json-property.ts @@ -0,0 +1,17 @@ +import { Type } from "@sinclair/typebox"; +import { BasePropertySchema, TPropertyValue } from "./common"; +import { PropertyType } from "./property-type"; + +export const JsonProperty = Type.Composite([ + BasePropertySchema, + TPropertyValue( + Type.Union([Type.Record(Type.String(), Type.Unknown())]), + PropertyType.JSON, + ), +]); +export type JsonProperty = BasePropertySchema & + TPropertyValue< + Record, + PropertyType.JSON, + R + >; diff --git a/packages/pieces/community/framework/src/lib/property/input/markdown-property.ts b/packages/pieces/community/framework/src/lib/property/input/markdown-property.ts new file mode 100644 index 0000000..f1de314 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/markdown-property.ts @@ -0,0 +1,18 @@ +import { Type } from '@sinclair/typebox'; +import { BasePropertySchema, TPropertyValue } from './common'; +import { PropertyType } from './property-type'; +import { MarkdownVariant } from '@activepieces/shared'; + +export const MarkDownProperty = Type.Composite([ + BasePropertySchema, + TPropertyValue(Type.Void(), PropertyType.MARKDOWN), +]); + +export type MarkDownProperty = BasePropertySchema & + TPropertyValue< + undefined, + PropertyType.MARKDOWN, + false + > & { + variant?: MarkdownVariant; + }; diff --git a/packages/pieces/community/framework/src/lib/property/input/number-property.ts b/packages/pieces/community/framework/src/lib/property/input/number-property.ts new file mode 100644 index 0000000..edd6c6b --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/number-property.ts @@ -0,0 +1,11 @@ +import { Type } from "@sinclair/typebox"; +import { BasePropertySchema, TPropertyValue } from "./common"; +import { PropertyType } from "./property-type"; + +export const NumberProperty = Type.Composite([ + BasePropertySchema, + TPropertyValue(Type.Number(), PropertyType.NUMBER) +]) + +export type NumberProperty = BasePropertySchema & + TPropertyValue; \ No newline at end of file diff --git a/packages/pieces/community/framework/src/lib/property/input/object-property.ts b/packages/pieces/community/framework/src/lib/property/input/object-property.ts new file mode 100644 index 0000000..5310065 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/object-property.ts @@ -0,0 +1,18 @@ +import { Type } from "@sinclair/typebox"; +import { BasePropertySchema, TPropertyValue } from "./common"; +import { PropertyType } from "./property-type"; + +export const ObjectProperty = Type.Composite([ + BasePropertySchema, + TPropertyValue( + Type.Record(Type.String(), Type.Unknown()), + PropertyType.OBJECT, + ) +]) + +export type ObjectProperty = BasePropertySchema & + TPropertyValue< + Record, + PropertyType.OBJECT, + R + >; \ No newline at end of file diff --git a/packages/pieces/community/framework/src/lib/property/input/property-type.ts b/packages/pieces/community/framework/src/lib/property/input/property-type.ts new file mode 100644 index 0000000..55fb3fb --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/property-type.ts @@ -0,0 +1,23 @@ +export enum PropertyType { + SHORT_TEXT = 'SHORT_TEXT', + LONG_TEXT = 'LONG_TEXT', + MARKDOWN = 'MARKDOWN', + DROPDOWN = 'DROPDOWN', + STATIC_DROPDOWN = 'STATIC_DROPDOWN', + NUMBER = 'NUMBER', + CHECKBOX = 'CHECKBOX', + OAUTH2 = 'OAUTH2', + SECRET_TEXT = 'SECRET_TEXT', + ARRAY = 'ARRAY', + OBJECT = 'OBJECT', + BASIC_AUTH = 'BASIC_AUTH', + JSON = 'JSON', + MULTI_SELECT_DROPDOWN = 'MULTI_SELECT_DROPDOWN', + STATIC_MULTI_SELECT_DROPDOWN = 'STATIC_MULTI_SELECT_DROPDOWN', + DYNAMIC = 'DYNAMIC', + CUSTOM_AUTH = 'CUSTOM_AUTH', + DATE_TIME = 'DATE_TIME', + FILE = 'FILE', + CUSTOM = 'CUSTOM', + COLOR = 'COLOR', +} \ No newline at end of file diff --git a/packages/pieces/community/framework/src/lib/property/input/text-property.ts b/packages/pieces/community/framework/src/lib/property/input/text-property.ts new file mode 100644 index 0000000..256e1b3 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/property/input/text-property.ts @@ -0,0 +1,22 @@ +import { Type } from "@sinclair/typebox"; +import { BasePropertySchema, TPropertyValue } from "./common"; +import { PropertyType } from "./property-type"; + + +export const ShortTextProperty = Type.Composite([ + BasePropertySchema, + TPropertyValue(Type.String(), PropertyType.SHORT_TEXT) +]) + + +export type ShortTextProperty = BasePropertySchema & + TPropertyValue; + + +export const LongTextProperty = Type.Composite([ + BasePropertySchema, + TPropertyValue(Type.String(), PropertyType.LONG_TEXT) +]) + +export type LongTextProperty = BasePropertySchema & + TPropertyValue; diff --git a/packages/pieces/community/framework/src/lib/trigger/trigger.ts b/packages/pieces/community/framework/src/lib/trigger/trigger.ts new file mode 100644 index 0000000..264e196 --- /dev/null +++ b/packages/pieces/community/framework/src/lib/trigger/trigger.ts @@ -0,0 +1,182 @@ +import { Static, Type } from '@sinclair/typebox'; +import { OnStartContext, ActionContext, TestOrRunHookContext, TriggerHookContext } from '../context'; +import { TriggerBase } from '../piece-metadata'; +import { InputPropertyMap } from '../property'; +import { PieceAuthProperty } from '../property/authentication'; +import { isNil, TriggerTestStrategy, WebhookHandshakeConfiguration, WebhookHandshakeStrategy } from '@activepieces/shared'; + +export const DEDUPE_KEY_PROPERTY = '_dedupe_key' + +export enum TriggerStrategy { + POLLING = 'POLLING', + WEBHOOK = 'WEBHOOK', + APP_WEBHOOK = "APP_WEBHOOK", +} + + +export enum WebhookRenewStrategy { + CRON = 'CRON', + NONE = 'NONE', +} + +type OnStartRunner = (ctx: OnStartContext) => Promise + + + +export const WebhookRenewConfiguration = Type.Union([ + Type.Object({ + strategy: Type.Literal(WebhookRenewStrategy.CRON), + cronExpression: Type.String(), + }), + Type.Object({ + strategy: Type.Literal(WebhookRenewStrategy.NONE), + }), +]) +export type WebhookRenewConfiguration = Static + +export interface WebhookResponse { + status: number, + body?: any, + headers?: Record +} + +type BaseTriggerParams< + PieceAuth extends PieceAuthProperty, + TriggerProps extends InputPropertyMap, + TS extends TriggerStrategy, +> = { + name: string + displayName: string + description: string + requireAuth?: boolean + auth?: PieceAuth + props: TriggerProps + type: TS + onEnable: (context: TriggerHookContext) => Promise + onDisable: (context: TriggerHookContext) => Promise + run: (context: TestOrRunHookContext) => Promise + test?: (context: TestOrRunHookContext) => Promise, + onStart?: OnStartRunner, + sampleData: unknown +} + +type WebhookTriggerParams< +PieceAuth extends PieceAuthProperty, +TriggerProps extends InputPropertyMap, +TS extends TriggerStrategy, +> = BaseTriggerParams & { + handshakeConfiguration?: WebhookHandshakeConfiguration + onHandshake?: (context: TriggerHookContext) => Promise, + renewConfiguration?: WebhookRenewConfiguration + onRenew?(context: TriggerHookContext): Promise, +} + +type CreateTriggerParams< + PieceAuth extends PieceAuthProperty, + TriggerProps extends InputPropertyMap, + TS extends TriggerStrategy, +> = TS extends TriggerStrategy.WEBHOOK + ? WebhookTriggerParams + : BaseTriggerParams + +export class ITrigger< + TS extends TriggerStrategy, + PieceAuth extends PieceAuthProperty, + TriggerProps extends InputPropertyMap, +> implements TriggerBase { + constructor( + public readonly name: string, + public readonly displayName: string, + public readonly description: string, + public readonly requireAuth: boolean, + public readonly props: TriggerProps, + public readonly type: TS, + public readonly handshakeConfiguration: WebhookHandshakeConfiguration, + public readonly onHandshake: (ctx: TriggerHookContext) => Promise, + public readonly renewConfiguration: WebhookRenewConfiguration, + public readonly onRenew: (ctx: TriggerHookContext) => Promise, + public readonly onEnable: (ctx: TriggerHookContext) => Promise, + public readonly onDisable: (ctx: TriggerHookContext) => Promise, + public readonly onStart: OnStartRunner, + public readonly run: (ctx: TestOrRunHookContext) => Promise, + public readonly test: (ctx: TestOrRunHookContext) => Promise, + public readonly sampleData: unknown, + public readonly testStrategy: TriggerTestStrategy, + ) { } +} + +export type Trigger< + PieceAuth extends PieceAuthProperty = any, + TriggerProps extends InputPropertyMap = any, + S extends TriggerStrategy = TriggerStrategy, +> = ITrigger + +// TODO refactor and extract common logic +export const createTrigger = < + TS extends TriggerStrategy, + PieceAuth extends PieceAuthProperty, + TriggerProps extends InputPropertyMap, +>(params: CreateTriggerParams) => { + switch (params.type) { + case TriggerStrategy.WEBHOOK: + return new ITrigger( + params.name, + params.displayName, + params.description, + params.requireAuth ?? true, + params.props, + params.type, + params.handshakeConfiguration ?? { strategy: WebhookHandshakeStrategy.NONE }, + params.onHandshake ?? (async () => ({ status: 200 })), + params.renewConfiguration ?? { strategy: WebhookRenewStrategy.NONE }, + params.onRenew ?? (async () => Promise.resolve()), + params.onEnable, + params.onDisable, + params.onStart ?? (async () => Promise.resolve()), + params.run, + params.test ?? (() => Promise.resolve([params.sampleData])), + params.sampleData, + params.test ? TriggerTestStrategy.TEST_FUNCTION : TriggerTestStrategy.SIMULATION, + ) + case TriggerStrategy.POLLING: + return new ITrigger( + params.name, + params.displayName, + params.description, + params.requireAuth ?? true, + params.props, + params.type, + { strategy: WebhookHandshakeStrategy.NONE }, + async () => ({ status: 200 }), + { strategy: WebhookRenewStrategy.NONE }, + (async () => Promise.resolve()), + params.onEnable, + params.onDisable, + params.onStart ?? (async () => Promise.resolve()), + params.run, + params.test ?? (() => Promise.resolve([params.sampleData])), + params.sampleData, + TriggerTestStrategy.TEST_FUNCTION, + ) + case TriggerStrategy.APP_WEBHOOK: + return new ITrigger( + params.name, + params.displayName, + params.description, + params.requireAuth ?? true, + params.props, + params.type, + { strategy: WebhookHandshakeStrategy.NONE }, + async () => ({ status: 200 }), + { strategy: WebhookRenewStrategy.NONE }, + (async () => Promise.resolve()), + params.onEnable, + params.onDisable, + params.onStart ?? (async () => Promise.resolve()), + params.run, + params.test ?? (() => Promise.resolve([params.sampleData])), + params.sampleData, + (isNil(params.sampleData) && isNil(params.test)) ? TriggerTestStrategy.SIMULATION : TriggerTestStrategy.TEST_FUNCTION, + ) + } +} diff --git a/packages/pieces/community/framework/src/lib/validators/index.ts b/packages/pieces/community/framework/src/lib/validators/index.ts new file mode 100644 index 0000000..2c2a51d --- /dev/null +++ b/packages/pieces/community/framework/src/lib/validators/index.ts @@ -0,0 +1,2 @@ + +export const CONNECTION_REGEX = '{{1}{connections.(.*?)}{1}}' diff --git a/packages/pieces/community/framework/translation-keys.json b/packages/pieces/community/framework/translation-keys.json new file mode 100644 index 0000000..b5e5fb4 --- /dev/null +++ b/packages/pieces/community/framework/translation-keys.json @@ -0,0 +1,22 @@ +[ + "displayName", + "description", + "auth.username.displayName", + "auth.username.description", + "auth.password.displayName", + "auth.password.description", + "auth.props.*.displayName", + "auth.props.*.description", + "auth.props.*.options.options.*.label", + "auth.description", + "actions.*.displayName", + "actions.*.description", + "actions.*.props.*.displayName", + "actions.*.props.*.description", + "actions.*.props.*.options.options.*.label", + "triggers.*.displayName", + "triggers.*.description", + "triggers.*.props.*.displayName", + "triggers.*.props.*.description", + "triggers.*.props.*.options.options.*.label" +] \ No newline at end of file diff --git a/packages/pieces/community/framework/tsconfig.json b/packages/pieces/community/framework/tsconfig.json new file mode 100644 index 0000000..4022fd4 --- /dev/null +++ b/packages/pieces/community/framework/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/pieces/community/framework/tsconfig.lib.json b/packages/pieces/community/framework/tsconfig.lib.json new file mode 100644 index 0000000..88a5725 --- /dev/null +++ b/packages/pieces/community/framework/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"], + "allowSyntheticDefaultImports": true + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/packages/pieces/community/framework/tsconfig.spec.json b/packages/pieces/community/framework/tsconfig.spec.json new file mode 100644 index 0000000..1f6879a --- /dev/null +++ b/packages/pieces/community/framework/tsconfig.spec.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"], + "allowSyntheticDefaultImports": true + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/packages/pieces/community/freshdesk/.eslintrc.json b/packages/pieces/community/freshdesk/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/freshdesk/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/freshdesk/README.md b/packages/pieces/community/freshdesk/README.md new file mode 100644 index 0000000..5a2df9e --- /dev/null +++ b/packages/pieces/community/freshdesk/README.md @@ -0,0 +1,7 @@ +# pieces-freshdesk + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-freshdesk` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/freshdesk/package.json b/packages/pieces/community/freshdesk/package.json new file mode 100644 index 0000000..caf51e2 --- /dev/null +++ b/packages/pieces/community/freshdesk/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-freshdesk", + "version": "0.1.0" +} diff --git a/packages/pieces/community/freshdesk/project.json b/packages/pieces/community/freshdesk/project.json new file mode 100644 index 0000000..158e3b2 --- /dev/null +++ b/packages/pieces/community/freshdesk/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-freshdesk", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/freshdesk/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/freshdesk", + "tsConfig": "packages/pieces/community/freshdesk/tsconfig.lib.json", + "packageJson": "packages/pieces/community/freshdesk/package.json", + "main": "packages/pieces/community/freshdesk/src/index.ts", + "assets": [ + "packages/pieces/community/freshdesk/*.md", + { + "input": "packages/pieces/community/freshdesk/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/freshdesk/src/index.ts b/packages/pieces/community/freshdesk/src/index.ts new file mode 100644 index 0000000..5e0220b --- /dev/null +++ b/packages/pieces/community/freshdesk/src/index.ts @@ -0,0 +1,54 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { getContactFromID } from './lib/actions/get-contact-from-id'; +import { getTicketStatus } from './lib/actions/get-ticket-status'; +import { getTickets } from './lib/actions/get-tickets'; +import { getContacts } from './lib/actions/get-contacts'; +import { getAllTicketsByStatus } from './lib/actions/get-all-tickets-by-status'; + +export const freshdeskAuth = PieceAuth.CustomAuth({ + props: { + base_url: Property.ShortText({ + displayName: 'Base URL', + description: 'Enter the base URL', + required: true, + }), + access_token: Property.ShortText({ + displayName: 'API Token', + description: 'Enter the API token', + required: true, + }), + }, + description: `Get the API token by visiting your profile settings and clicking View API key`, + required: true, +}); + +export const freshdesk = createPiece({ + displayName: 'Freshdesk', + description: 'Customer support software', + + logoUrl: 'https://cdn.activepieces.com/pieces/freshdesk.png', + categories: [PieceCategory.CUSTOMER_SUPPORT], + authors: ["buttonsbond","kishanprmr","MoShizzle","AbdulTheActivePiecer","abuaboud"], + auth: freshdeskAuth, + actions: [ + getTickets, + getContactFromID, + getTicketStatus, + getContacts, + getAllTicketsByStatus, + createCustomApiCallAction({ + baseUrl: (auth) => (auth as { base_url: string }).base_url, + auth: freshdeskAuth, + authMapping: async (auth) => ({ + Authorization: (auth as { access_token: string }).access_token, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/freshdesk/src/lib/actions/get-all-tickets-by-status.ts b/packages/pieces/community/freshdesk/src/lib/actions/get-all-tickets-by-status.ts new file mode 100644 index 0000000..aae031c --- /dev/null +++ b/packages/pieces/community/freshdesk/src/lib/actions/get-all-tickets-by-status.ts @@ -0,0 +1,133 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { freshdeskAuth } from '../..'; + +export const getAllTicketsByStatus = createAction({ + auth: freshdeskAuth, + name: 'get_all_tickets_by_status', + displayName: 'Get All Tickets By Status', + description: 'Get All Tickets by selected status from Freshdesk.', + + props: { + status_filter: Property.StaticMultiSelectDropdown({ + displayName: 'Choose Status(es)', + description: 'Select one or status values', + required: true, + options: { + options: [ + { + label: 'Open', + value: 'status:2' + }, + { + label: 'Pending', + value: 'status:3' + }, + { + label: 'Resolved', + value: 'status:4' + }, + { + label: 'Closed', + value: 'status:5' + } + ] + } + }), + }, + + async run(context) { + const FDapiToken = context.auth.access_token; + + const headers = { + Authorization: FDapiToken, + 'Content-Type': 'application/json', + }; + + // Remove trailing slash from base_url + const baseUrl = context.auth.base_url.replace(/\/$/, ''); + const queryParams = new URLSearchParams(); + + // Adjusted to accept number or string + const replacedArray = context.propsValue.status_filter.map(str => str.replace(/,/g, ' OR ')); + const replacedString = '"' + replacedArray.join(' OR ') + '"'; + queryParams.append('query', replacedString || ''); + + const url = `${baseUrl}/api/v2/search/tickets/?${queryParams.toString()}`; + + const httprequestdata = { + method: HttpMethod.GET, + url, + headers, + }; + const response = await httpClient.sendRequest(httprequestdata); + + if (response.status == 200) { + // Define an interface for the ticket structure + interface Ticket { + id: number; + requester_id: number; + responder_id: number | null; + company_id: number; + status: number | string; // Adjusted to accept number or string + subject: string; + created_at: string; + updated_at: string; + description_text: string; + description: string; + // Add more properties if necessary + } + + // response.body.results is an array of ticket objects + const ticketResults: Ticket[] = response.body.results; + + // Sort the ticketResults array by requester_id + ticketResults.sort((a, b) => a.requester_id - b.requester_id); + + // Initialize an empty array to store tickets + const tickets: Ticket[] = []; + + // Iterate through each ticket result and push it to the tickets array + ticketResults.forEach((ticketResult: Ticket) => { + // Map status number to corresponding string + let statusString: string; + switch (ticketResult.status) { + case 2: + statusString = 'Open'; + break; + case 3: + statusString = 'Pending'; + break; + case 4: + statusString = 'Resolved'; + break; + case 5: + statusString = 'Closed'; + break; + default: + statusString = 'Unknown'; + } + + // Push the ticket object with modified status string to the tickets array + tickets.push({ + id: ticketResult.id, + requester_id: ticketResult.requester_id, + responder_id: ticketResult.responder_id, + company_id: ticketResult.company_id, + status: statusString, + subject: ticketResult.subject, + created_at: ticketResult.created_at, + updated_at: ticketResult.updated_at, + description_text: ticketResult.description_text, + description: ticketResult.description, + // Add more properties if necessary + }); + }); + + // Return the tickets array + return tickets; + } else { + return response.status; + } + }, +}); diff --git a/packages/pieces/community/freshdesk/src/lib/actions/get-contact-from-id.ts b/packages/pieces/community/freshdesk/src/lib/actions/get-contact-from-id.ts new file mode 100644 index 0000000..5d8292c --- /dev/null +++ b/packages/pieces/community/freshdesk/src/lib/actions/get-contact-from-id.ts @@ -0,0 +1,45 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { freshdeskAuth } from '../..'; + +export const getContactFromID = createAction({ + auth: freshdeskAuth, + name: 'get_contact_from_id', + displayName: 'Get Contact from ID', + description: 'Get contacts details from Freshdesk using ID number.', + + props: { + contactid: Property.ShortText({ + displayName: 'Contact ID number', + description: 'The ID number of the contact', + required: true, + }), + }, + + async run(context) { + const FDapiToken = context.auth.access_token; + const FDcontactID = context.propsValue.contactid; + + const headers = { + Authorization: FDapiToken, + 'Content-Type': 'application/json', + }; + + // Remove trailing slash from base_url + const baseUrl = context.auth.base_url.replace(/\/$/, ''); + // not needed for gettickets ?${queryParams.toString()} + const url = `${baseUrl}/api/v2/contacts/${FDcontactID}`; + const httprequestdata = { + method: HttpMethod.GET, + url, + headers, + }; + const response = await httpClient.sendRequest(httprequestdata); + + if (response.status == 200) { + return response.body; + } else { + return response; + } + }, +}); diff --git a/packages/pieces/community/freshdesk/src/lib/actions/get-contacts.ts b/packages/pieces/community/freshdesk/src/lib/actions/get-contacts.ts new file mode 100644 index 0000000..1376db9 --- /dev/null +++ b/packages/pieces/community/freshdesk/src/lib/actions/get-contacts.ts @@ -0,0 +1,129 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { freshdeskAuth } from '../..'; + +export const getContacts = createAction({ + auth: freshdeskAuth, + name: 'get_contacts', + displayName: 'Get Freshdesk Contacts', + description: + 'Get contact details from Freshdesk for all (optional filtered) contacts.', + + props: { + filter_type: Property.StaticDropdown({ + displayName: 'Optional Filter', + description: 'Select one and provide the value.', + defaultValue: '', + required: false, + options: { + options: [ + { + label: 'E-mail', + value: 'email', + }, + { + label: 'Mobile', + value: 'mobile', + }, + { + label: 'Phone', + value: 'phone', + }, + { + label: 'Company ID', + value: 'company_id', + }, + { + label: 'Updated Since', + value: 'updated_since', + }, + ], + }, + }), + filter_value: Property.ShortText({ + displayName: 'Filter value', + description: 'Provide value if previous option selected!', + required: false, + defaultValue: '', + }), + filter_status: Property.StaticDropdown({ + displayName: 'Optional Filter Status', + description: + 'Can filter by state: blocked, deleted, unverified or verified.', + defaultValue: '', + required: false, + options: { + options: [ + { + label: 'Blocked', + value: 'blocked', + }, + { + label: 'Deleted', + value: 'deleted', + }, + { + label: 'Unverified', + value: 'unverified', + }, + { + label: 'Verified', + value: 'verified', + }, + ], + }, + }), + per_page: Property.Number({ + displayName: 'Results to return', + description: + 'Freshdesk calls this per_page - set to 0 for all, if specified maximum is 100', + required: true, + defaultValue: 0, + }), + }, + + async run(context) { + const FDapiToken = context.auth.access_token; + + const headers = { + Authorization: FDapiToken, + 'Content-Type': 'application/json', + }; + + // not needed for gettickets ?${queryParams.toString()} + const queryParams = new URLSearchParams(); + if ( + context.propsValue.filter_type?.valueOf != null && + context.propsValue.filter_value?.valueOf != null + ) { + queryParams.append( + context.propsValue.filter_type?.toString(), + context.propsValue.filter_value || '' + ); + } + if (context.propsValue.filter_status?.valueOf != null) { + queryParams.append('state', context.propsValue.filter_status || ''); + } + if (context.propsValue.per_page != 0) { + queryParams.append( + 'per_page', + context.propsValue.per_page.toString() || '100' + ); + } + // Remove trailing slash from base_url + const baseUrl = context.auth.base_url.replace(/\/$/, ''); + const url = `${baseUrl}/api/v2/contacts/?${queryParams.toString()}`; + const httprequestdata = { + method: HttpMethod.GET, + url, + headers, + }; + const response = await httpClient.sendRequest(httprequestdata); + + if (response.status == 200) { + return response.body; + } else { + return response; + } + }, +}); diff --git a/packages/pieces/community/freshdesk/src/lib/actions/get-ticket-status.ts b/packages/pieces/community/freshdesk/src/lib/actions/get-ticket-status.ts new file mode 100644 index 0000000..a0ab1bc --- /dev/null +++ b/packages/pieces/community/freshdesk/src/lib/actions/get-ticket-status.ts @@ -0,0 +1,88 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { freshdeskAuth } from '../..'; +import { isNil } from '@activepieces/shared'; + +export const getTicketStatus = createAction({ + auth: freshdeskAuth, + name: 'get_ticket_status', + displayName: 'Get Ticket Status', + description: + 'Get Ticket status from Freshdesk. Returns ticket_status, assigned_status, assigned_id', + + props: { + ticketid: Property.ShortText({ + displayName: 'Ticket ID', + description: 'The Ticket ID to return status', + required: true, + }), + }, + + async run(context) { + const FDapiToken = context.auth.access_token; + const FDticketID = context.propsValue.ticketid; + + const headers = { + Authorization: FDapiToken, + 'Content-Type': 'application/json', + }; + + // Remove trailing slash from base_url + const baseUrl = context.auth.base_url.replace(/\/$/, ''); + const url = `${baseUrl}/api/v2/tickets/${FDticketID}`; + const httprequestdata = { + method: HttpMethod.GET, + url, + headers, + }; + const response = await httpClient.sendRequest(httprequestdata); + + if (response.status == 200) { + const status = response.body.status; + const responderid = response.body.responder_id; + let AssignedStatusFriendlyValue = ''; + let TicketStatusFriendlyValue = ''; + + if (isNil(responderid)) { + AssignedStatusFriendlyValue = 'NOTASSIGNED'; + } else { + AssignedStatusFriendlyValue = 'ASSIGNED'; + } + + switch (status) { + case 2: { + TicketStatusFriendlyValue = 'OPEN'; + break; + } + case 3: { + TicketStatusFriendlyValue = 'PENDING'; + break; + } + case 4: { + TicketStatusFriendlyValue = 'RESOLVED'; + break; + } + case 5: { + TicketStatusFriendlyValue = 'CLOSED'; + break; + } + default: { + // if anything other than 2,3,4 or 5 just assign the value - it shouldn't happen! + TicketStatusFriendlyValue = status; + break; + } + } + const json = [ + { + ticket_status: TicketStatusFriendlyValue, + assigned_status: AssignedStatusFriendlyValue, + assigned_id: responderid, + }, + ]; + + return json; + } else { + return response.status; + } + }, +}); diff --git a/packages/pieces/community/freshdesk/src/lib/actions/get-tickets.ts b/packages/pieces/community/freshdesk/src/lib/actions/get-tickets.ts new file mode 100644 index 0000000..9ca6d74 --- /dev/null +++ b/packages/pieces/community/freshdesk/src/lib/actions/get-tickets.ts @@ -0,0 +1,37 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { freshdeskAuth } from '../..'; + +export const getTickets = createAction({ + auth: freshdeskAuth, + name: 'get_tickets', + displayName: 'Get Tickets', + description: 'Get Ticket instances from Freshdesk.', + props: {}, + + async run(context) { + const FDapiToken = context.auth.access_token; + + const headers = { + Authorization: FDapiToken, + 'Content-Type': 'application/json', + }; + + // Remove trailing slash from base_url + const baseUrl = context.auth.base_url.replace(/\/$/, ''); + // not needed for gettickets ?${queryParams.toString()} + const url = `${baseUrl}/api/v2/tickets/`; + const httprequestdata = { + method: HttpMethod.GET, + url, + headers, + }; + const response = await httpClient.sendRequest(httprequestdata); + + if (response.status == 200) { + return response.body; + } else { + return response; + } + }, +}); diff --git a/packages/pieces/community/freshdesk/tsconfig.json b/packages/pieces/community/freshdesk/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/freshdesk/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/freshdesk/tsconfig.lib.json b/packages/pieces/community/freshdesk/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/freshdesk/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/freshsales/.babelrc b/packages/pieces/community/freshsales/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/freshsales/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/freshsales/.eslintrc.json b/packages/pieces/community/freshsales/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/freshsales/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/freshsales/README.md b/packages/pieces/community/freshsales/README.md new file mode 100644 index 0000000..e134df2 --- /dev/null +++ b/packages/pieces/community/freshsales/README.md @@ -0,0 +1,7 @@ +# pieces-freshsales + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-freshsales` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/freshsales/package.json b/packages/pieces/community/freshsales/package.json new file mode 100644 index 0000000..0fa9686 --- /dev/null +++ b/packages/pieces/community/freshsales/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-freshsales", + "version": "0.3.4" +} \ No newline at end of file diff --git a/packages/pieces/community/freshsales/project.json b/packages/pieces/community/freshsales/project.json new file mode 100644 index 0000000..db5e7f5 --- /dev/null +++ b/packages/pieces/community/freshsales/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-freshsales", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/freshsales/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/freshsales", + "tsConfig": "packages/pieces/community/freshsales/tsconfig.lib.json", + "packageJson": "packages/pieces/community/freshsales/package.json", + "main": "packages/pieces/community/freshsales/src/index.ts", + "assets": [ + "packages/pieces/community/freshsales/*.md", + { + "input": "packages/pieces/community/freshsales/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/freshsales/src/index.ts b/packages/pieces/community/freshsales/src/index.ts new file mode 100644 index 0000000..01ece4c --- /dev/null +++ b/packages/pieces/community/freshsales/src/index.ts @@ -0,0 +1,59 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { freshSalesCreateContact } from './lib/actions/create-contact'; + +const markdownDescription = ` +To obtain your API key and bundle alias, follow these steps: + +1. Log in to your Freshsales account. +2. Click on your profile icon in the top-right corner of the screen and select **Settings** from the dropdown menu. +3. In the settings menu, select **API Settings** from the left-hand navigation panel. +4. You should now see your API key displayed on the screen. If you don't see an API key. +5. Copy the alias e.g **https://.myfreshworks.com** +`; + +export const freshsalesAuth = PieceAuth.BasicAuth({ + description: markdownDescription, + username: Property.ShortText({ + displayName: 'Bundle alias', + description: + 'Your Freshsales bundle alias (e.g. https://.myfreshworks.com)', + required: true, + }), + password: Property.ShortText({ + displayName: 'API Key', + description: 'The API Key supplied by Freshsales', + required: true, + }), + required: true, +}); + +export const freshsales = createPiece({ + displayName: 'Freshsales', + description: 'Sales CRM software', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/freshsales.png', + categories: [PieceCategory.SALES_AND_CRM], + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + auth: freshsalesAuth, + actions: [ + freshSalesCreateContact, + createCustomApiCallAction({ + baseUrl: (auth) => + `https://${ + (auth as { username: string }).username + }.myfreshworks.com/crm/sales/api`, + auth: freshsalesAuth, + authMapping: async (auth) => ({ + Authorization: `Token token=${(auth as { password: string }).password}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/freshsales/src/lib/actions/create-contact.ts b/packages/pieces/community/freshsales/src/lib/actions/create-contact.ts new file mode 100644 index 0000000..0321604 --- /dev/null +++ b/packages/pieces/community/freshsales/src/lib/actions/create-contact.ts @@ -0,0 +1,155 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { freshsalesAuth } from '../../'; + +export const freshSalesCreateContact = createAction({ + auth: freshsalesAuth, + name: 'freshsales_create_contact', + displayName: 'Create Contact', + description: 'Add new contact in Freshsales CRM', + props: { + first_name: Property.ShortText({ + displayName: 'First name', + description: 'First name of the contact', + required: false, + }), + last_name: Property.ShortText({ + displayName: 'Last name', + description: 'Last name of the contact', + required: false, + }), + job_title: Property.ShortText({ + displayName: 'Job title', + description: 'Designation of the contact in the account they belong to', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + description: 'Primary email address of the contact', + required: true, + }), + work_number: Property.ShortText({ + displayName: 'Work number', + description: 'Work phone number of the contact', + required: false, + }), + mobile_number: Property.ShortText({ + displayName: 'Mobile number', + description: 'Mobile phone number of the contact', + required: false, + }), + address: Property.ShortText({ + displayName: 'Address', + description: 'Address of the contact', + required: false, + }), + city: Property.ShortText({ + displayName: 'City', + description: 'City that the contact belongs to', + required: false, + }), + state: Property.ShortText({ + displayName: 'State', + description: 'State that the contact belongs to', + required: false, + }), + zipcode: Property.ShortText({ + displayName: 'Zip code', + description: 'Zipcode of the region that the contact belongs to', + required: false, + }), + country: Property.ShortText({ + displayName: 'Country', + description: 'Country that the contact belongs to', + required: false, + }), + territory_id: Property.ShortText({ + displayName: 'Territory id', + description: 'ID of the territory that the contact belongs to', + required: false, + }), + owner_id: Property.ShortText({ + displayName: 'Owner id', + description: 'ID of the user to whom the contact has been assigned', + required: false, + }), + subscription_status: Property.ShortText({ + displayName: 'Subscription status', + description: 'Status of subscription that the contact is in.', + required: false, + }), + medium: Property.ShortText({ + displayName: 'Medium', + description: 'The medium that led your contact to your website/web app', + required: false, + }), + campaign_id: Property.ShortText({ + displayName: 'Campaign id', + description: 'The campaign that led your contact to your web app.', + required: false, + }), + keyword: Property.ShortText({ + displayName: 'Keyword', + description: + 'The keywords that the contact used to reach your website/web app', + required: false, + }), + time_zone: Property.ShortText({ + displayName: 'Timezone', + description: 'Timezone that the contact belongs to', + required: false, + }), + facebook: Property.ShortText({ + displayName: 'Facebook', + description: 'Facebook username of the contact', + required: false, + }), + twitter: Property.ShortText({ + displayName: 'Twitter', + description: 'Twitter username of the contact', + required: false, + }), + linkedin: Property.ShortText({ + displayName: 'Linkedin', + description: 'LinkedIn account of the contact', + required: false, + }), + contact_status_id: Property.ShortText({ + displayName: 'Contact status id', + description: 'ID of the contact status that the contact belongs to', + required: false, + }), + sales_account_id: Property.ShortText({ + displayName: 'Sales account id', + description: 'ID of the primary account that the contact belongs to', + required: false, + }), + }, + async run(context) { + const contact = context.propsValue; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://${context.auth.username}.myfreshworks.com/crm/sales/api/contacts`, + body: { + contact, + }, + headers: { + Authorization: `Token token=${context.auth.password}`, + }, + }; + + const result = await httpClient.sendRequest(request); + console.debug('Create contact response', result); + + if (result.status == 200) { + return result.body; + } else { + return result; + } + }, +}); diff --git a/packages/pieces/community/freshsales/tsconfig.json b/packages/pieces/community/freshsales/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/freshsales/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/freshsales/tsconfig.lib.json b/packages/pieces/community/freshsales/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/freshsales/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/gameball/.eslintrc.json b/packages/pieces/community/gameball/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/gameball/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/gameball/README.md b/packages/pieces/community/gameball/README.md new file mode 100644 index 0000000..505259b --- /dev/null +++ b/packages/pieces/community/gameball/README.md @@ -0,0 +1,7 @@ +# pieces-gameball + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-gameball` to build the library. diff --git a/packages/pieces/community/gameball/package.json b/packages/pieces/community/gameball/package.json new file mode 100644 index 0000000..167c961 --- /dev/null +++ b/packages/pieces/community/gameball/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-gameball", + "version": "0.0.1" +} diff --git a/packages/pieces/community/gameball/project.json b/packages/pieces/community/gameball/project.json new file mode 100644 index 0000000..32e0211 --- /dev/null +++ b/packages/pieces/community/gameball/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-gameball", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/gameball/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/gameball", + "tsConfig": "packages/pieces/community/gameball/tsconfig.lib.json", + "packageJson": "packages/pieces/community/gameball/package.json", + "main": "packages/pieces/community/gameball/src/index.ts", + "assets": [ + "packages/pieces/community/gameball/*.md", + { + "input": "packages/pieces/community/gameball/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/gameball/src/index.ts b/packages/pieces/community/gameball/src/index.ts new file mode 100644 index 0000000..db37244 --- /dev/null +++ b/packages/pieces/community/gameball/src/index.ts @@ -0,0 +1,19 @@ + +import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { sendEvent } from "./lib/actions/send-event"; + +export const gameballAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'Please use your gameball api key. visit [help center](https://help.gameball.co/en/articles/3467114-get-your-account-integration-details-api-key-and-transaction-key) for more information', +}); + +export const gameball = createPiece({ + displayName: "Gameball", + auth: gameballAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: "https://cdn.activepieces.com/pieces/gameball.png", + authors: ["Raamyy"], + actions: [sendEvent], + triggers: [], +}); diff --git a/packages/pieces/community/gameball/src/lib/actions/send-event.ts b/packages/pieces/community/gameball/src/lib/actions/send-event.ts new file mode 100644 index 0000000..816e7db --- /dev/null +++ b/packages/pieces/community/gameball/src/lib/actions/send-event.ts @@ -0,0 +1,40 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { gameballAuth } from '../..'; + +export const sendEvent = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'sendEvent', + auth: gameballAuth, + displayName: 'Send event', + description: 'Send an event to gameball', + props: { + playerUniqueId: Property.ShortText({ + displayName: 'Your Player Unique Id', + required: true, + }), + eventName: Property.ShortText({ + displayName: 'Event Name', + required: true, + }), + }, + async run(context) { + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.gameball.co/api/v3.0/integrations/event', + headers: { + APIKey: context.auth, // Pass API key in headers + }, + // update the event body with eventmetadata if requested in the future. + body: { + "playerUniqueId": context.propsValue.playerUniqueId, + "events": { + [context.propsValue.eventName]: { + + } + } + } + }); + return res.body; + }, +}); diff --git a/packages/pieces/community/gameball/tsconfig.json b/packages/pieces/community/gameball/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/gameball/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/gameball/tsconfig.lib.json b/packages/pieces/community/gameball/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/gameball/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/gcloud-pubsub/.babelrc b/packages/pieces/community/gcloud-pubsub/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/gcloud-pubsub/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/gcloud-pubsub/.eslintrc.json b/packages/pieces/community/gcloud-pubsub/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/gcloud-pubsub/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/gcloud-pubsub/README.md b/packages/pieces/community/gcloud-pubsub/README.md new file mode 100644 index 0000000..94e6c5e --- /dev/null +++ b/packages/pieces/community/gcloud-pubsub/README.md @@ -0,0 +1,7 @@ +# pieces-gcloud-pubsub + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-gcoud-pubsub` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/gcloud-pubsub/package.json b/packages/pieces/community/gcloud-pubsub/package.json new file mode 100644 index 0000000..3ab7a28 --- /dev/null +++ b/packages/pieces/community/gcloud-pubsub/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-gcloud-pubsub", + "version": "0.0.4" +} \ No newline at end of file diff --git a/packages/pieces/community/gcloud-pubsub/project.json b/packages/pieces/community/gcloud-pubsub/project.json new file mode 100644 index 0000000..e6bcb79 --- /dev/null +++ b/packages/pieces/community/gcloud-pubsub/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-gcloud-pubsub", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/gcloud-pubsub/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/gcloud-pubsub", + "tsConfig": "packages/pieces/community/gcloud-pubsub/tsconfig.lib.json", + "packageJson": "packages/pieces/community/gcloud-pubsub/package.json", + "main": "packages/pieces/community/gcloud-pubsub/src/index.ts", + "assets": [ + "packages/pieces/community/gcloud-pubsub/*.md", + { + "input": "packages/pieces/community/gcloud-pubsub/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/gcloud-pubsub/src/index.ts b/packages/pieces/community/gcloud-pubsub/src/index.ts new file mode 100644 index 0000000..e8e5dbe --- /dev/null +++ b/packages/pieces/community/gcloud-pubsub/src/index.ts @@ -0,0 +1,63 @@ +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { publishToTopic } from './lib/action/publish-to-topic'; +import { common } from './lib/common'; +import { newMessageInTopic } from './lib/trigger/new-message-in-topic'; + +const authDescription = ` +You can get it from the [Google Cloud Console](https://console.cloud.google.com/apis/credentials/serviceaccountkey). +`; + +export const googlePubsubAuth = PieceAuth.CustomAuth({ + description: authDescription, + required: true, + props: { + json: Property.LongText({ + displayName: 'Service Key (JSON)', + required: true, + }), + }, + validate: async ({ auth }) => { + try { + const client = common.getClient(auth.json); + await client.request({ + url: `https://pubsub.googleapis.com/v1/projects/${common.getProjectId( + auth.json + )}/topics`, + }); + return { + valid: true, + }; + } catch (e: any) { + if ('response' in e) { + console.debug( + `Auth Gcloud pubsub status: ${ + e.response.status + }, data: ${JSON.stringify(e.response.data)}` + ); + } + return { + valid: false, + error: + 'Connection failed. Please check your Private Key, Email or Project ID.', + }; + } + }, +}); + +export const gcloudPubsub = createPiece({ + displayName: 'GCloud Pub/Sub', + description: "Google Cloud's event streaming service", + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/gcloud-pubsub.png', + categories: [PieceCategory.DEVELOPER_TOOLS], + auth: googlePubsubAuth, + authors: ["DGurskij","kishanprmr","khaledmashaly","abuaboud"], + actions: [publishToTopic], + triggers: [newMessageInTopic], +}); diff --git a/packages/pieces/community/gcloud-pubsub/src/lib/action/publish-to-topic.ts b/packages/pieces/community/gcloud-pubsub/src/lib/action/publish-to-topic.ts new file mode 100644 index 0000000..d4de0cf --- /dev/null +++ b/packages/pieces/community/gcloud-pubsub/src/lib/action/publish-to-topic.ts @@ -0,0 +1,46 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { common } from '../common'; +import { googlePubsubAuth } from '../..'; + +export const publishToTopic = createAction({ + name: 'publish_to_topic', + auth: googlePubsubAuth, + displayName: 'Publish to topic', + description: 'Publish message to topic', + props: { + message: Property.Object({ + displayName: 'Message', + required: true, + }), + topic: Property.Dropdown({ + displayName: 'Topic', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + const json = (auth as { json: string }).json; + return common.getTopics(json); + }, + }), + }, + async run(context) { + const client = common.getClient(context.auth.json); + const topic = context.propsValue.topic; + + const url = `https://pubsub.googleapis.com/v1/${topic}:publish`; + const json = JSON.stringify(context.propsValue.message); + const body = JSON.stringify({ + messages: [{ data: Buffer.from(json).toString('base64') }], + }); + + const { data } = await client.request<{ messageIds: string[] }>({ + url, + method: 'POST', + body, + }); + + console.debug( + `Message sended to topic[${topic}]: ${json}, ack: ${data.messageIds[0]}` + ); + return json; + }, +}); diff --git a/packages/pieces/community/gcloud-pubsub/src/lib/common/index.ts b/packages/pieces/community/gcloud-pubsub/src/lib/common/index.ts new file mode 100644 index 0000000..51931e2 --- /dev/null +++ b/packages/pieces/community/gcloud-pubsub/src/lib/common/index.ts @@ -0,0 +1,71 @@ +import { JWT } from 'google-auth-library'; + +export const common = { + getClient(authJson: string) { + const email = common.getEmail(authJson); + const privateKey = common.getPrivateKey(authJson); + + const gaxios = new JWT({ + email, + key: privateKey.replace(/\\n/g, '\n'), // remove duplicate '\' from client side + scopes: ['https://www.googleapis.com/auth/pubsub'], + }); + return gaxios; + }, + + getProjectId(json: string) { + return JSON.parse(json).project_id; + }, + getPrivateKey(json: string) { + return JSON.parse(json).private_key; + }, + getEmail(json: string) { + return JSON.parse(json).client_email; + }, + /** + * @returns options topics, topic value contain project name: projects/{pname}/topics/{tname} + */ + async getTopics(json: string) { + const client = common.getClient(json); + + const topics = { + disabled: true, + options: [] as { label: string; value: string }[], + placeholder: 'Need authentication' as string | undefined, + }; + + try { + const response = await client.request({ + url: `https://pubsub.googleapis.com/v1/projects/${this.getProjectId( + json + )}/topics`, + method: 'GET', + }); + + topics.options = response.data.topics.map((topic) => { + const topicName = topic.name.split('topics/')[1]; + return { label: `${topicName}`, value: topic.name }; + }); + + delete topics.placeholder; + topics.disabled = false; + } catch (e: any) { + if ('response' in e) { + topics.placeholder = `Get topics error: ${e.response.data.error}`; + console.debug(e.response.data.error); + } + } + + return topics; + }, +}; + +export interface IAuth { + email: string; + privateKey: string; + projectId: string; +} + +export interface ITopicsInfo { + topics: { name: string }[]; +} diff --git a/packages/pieces/community/gcloud-pubsub/src/lib/trigger/new-message-in-topic.ts b/packages/pieces/community/gcloud-pubsub/src/lib/trigger/new-message-in-topic.ts new file mode 100644 index 0000000..d67d6c4 --- /dev/null +++ b/packages/pieces/community/gcloud-pubsub/src/lib/trigger/new-message-in-topic.ts @@ -0,0 +1,102 @@ +import { Property, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; + +import { googlePubsubAuth } from '../..'; +import { common } from '../common'; + +export const newMessageInTopic = createTrigger({ + auth: googlePubsubAuth, + name: 'new_message_in_topic', + displayName: 'New Message', + description: 'Trigger when a new message is sended.', + props: { + subscription: Property.ShortText({ + displayName: 'Subscription name', + required: true, + }), + topic: Property.Dropdown({ + displayName: 'Topic', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + const json = (auth as { json: string }).json; + return common.getTopics(json); + }, + }), + ackDeadlineSeconds: Property.Number({ + displayName: 'Ack Deadline Seconds', + required: true, + defaultValue: 100, + }), + }, + type: TriggerStrategy.WEBHOOK, + onEnable: async (context) => { + const json = (context.auth as { json: string }).json; + const client = common.getClient(json); + + const { topic, subscription } = context.propsValue; + const project = common.getProjectId(context.auth.json as string); + + const url = `https://pubsub.googleapis.com/v1/projects/${project}/subscriptions/${subscription}`; + const body = { + topic, + pushConfig: { + pushEndpoint: context.webhookUrl, + attributes: {}, + }, + ackDeadlineSeconds: context.propsValue.ackDeadlineSeconds, + }; + + await client.request({ + url, + method: 'PUT', + data: JSON.stringify(body), + }); + + await context.store.put('_trigger', { + project, + subscription, + }); + }, + onDisable: async (context) => { + const response = await context.store.get('_trigger'); + + if (response !== null && response !== undefined) { + const json = (context.auth as { json: string }).json; + const client = common.getClient(json); + const { project, subscription } = response; + const url = `https://pubsub.googleapis.com/v1/projects/${project}/subscriptions/${subscription}`; + + await client.request({ + url, + method: 'DELETE', + }); + } + }, + async run(context) { + console.debug('payload received', context.payload.body); + const payloadBody = context.payload.body as PayloadBody; + const { data } = payloadBody.message; + const object = data + ? JSON.parse(Buffer.from(data, 'base64').toString()) + : {}; + + return [object]; + }, + sampleData: { + x: 1.0, + y: -1.0, + text: 'Just text sample', + }, +}); + +interface ISubscriptionInfo { + project: string; + subscription: string; +} + +type PayloadBody = { + message: { + data: string; + }; +}; diff --git a/packages/pieces/community/gcloud-pubsub/tsconfig.json b/packages/pieces/community/gcloud-pubsub/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/gcloud-pubsub/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/gcloud-pubsub/tsconfig.lib.json b/packages/pieces/community/gcloud-pubsub/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/gcloud-pubsub/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/generatebanners/.eslintrc.json b/packages/pieces/community/generatebanners/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/generatebanners/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/generatebanners/README.md b/packages/pieces/community/generatebanners/README.md new file mode 100644 index 0000000..b050530 --- /dev/null +++ b/packages/pieces/community/generatebanners/README.md @@ -0,0 +1,7 @@ +# pieces-generatebanners + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-generatebanners` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/generatebanners/index.ts b/packages/pieces/community/generatebanners/index.ts new file mode 100644 index 0000000..716aadd --- /dev/null +++ b/packages/pieces/community/generatebanners/index.ts @@ -0,0 +1,14 @@ +import { createPiece } from '@activepieces/pieces-framework'; +import { renderTemplate } from './actions/renderTemplate.action'; +import { PieceCategory } from '@activepieces/shared'; +export const generatebanners = createPiece({ + name: 'generatebanners', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/generatebanners.png', + authors: ['tpatel'], + categories: [PieceCategory.MARKETING], + actions: [renderTemplate], + displayName: 'GenerateBanners', + triggers: [], + version: '0.1.0', +}); diff --git a/packages/pieces/community/generatebanners/package.json b/packages/pieces/community/generatebanners/package.json new file mode 100644 index 0000000..ab7eeb9 --- /dev/null +++ b/packages/pieces/community/generatebanners/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-generatebanners", + "version": "0.3.4" +} \ No newline at end of file diff --git a/packages/pieces/community/generatebanners/project.json b/packages/pieces/community/generatebanners/project.json new file mode 100644 index 0000000..205e02f --- /dev/null +++ b/packages/pieces/community/generatebanners/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-generatebanners", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/generatebanners/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/generatebanners", + "tsConfig": "packages/pieces/community/generatebanners/tsconfig.lib.json", + "packageJson": "packages/pieces/community/generatebanners/package.json", + "main": "packages/pieces/community/generatebanners/src/index.ts", + "assets": [ + "packages/pieces/community/generatebanners/*.md", + { + "input": "packages/pieces/community/generatebanners/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/generatebanners/src/index.ts b/packages/pieces/community/generatebanners/src/index.ts new file mode 100644 index 0000000..d644f54 --- /dev/null +++ b/packages/pieces/community/generatebanners/src/index.ts @@ -0,0 +1,36 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { renderTemplate } from './lib/actions/renderTemplate.action'; + +const markdownDescription = ` +To obtain your GenerateBanners public and secret API Keys, you can follow the steps below: + +1. Go to the [GenerateBanners homepage](https://www.generatebanners.com/). +2. Sign up or log in into your account. +3. Go to your [account page](https://www.generatebanners.com/app/account). +4. The public and secret API keys are now displayed, copy them one by one into the right Activepieces fields. +`; + +export const generatebannersAuth = PieceAuth.BasicAuth({ + description: markdownDescription, + required: true, + username: { + displayName: 'Public API Key', + }, + password: { + displayName: 'Secret API Key', + }, +}); + +export const generatebanners = createPiece({ + displayName: 'GenerateBanners', + description: 'Image generation API for banners and social media posts', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/generatebanners.png', + categories: [PieceCategory.CONTENT_AND_FILES], + authors: ["tpatel","kishanprmr","khaledmashaly","abuaboud"], + auth: generatebannersAuth, + actions: [renderTemplate], + triggers: [], +}); diff --git a/packages/pieces/community/generatebanners/src/lib/actions/renderTemplate.action.ts b/packages/pieces/community/generatebanners/src/lib/actions/renderTemplate.action.ts new file mode 100644 index 0000000..0a8b55d --- /dev/null +++ b/packages/pieces/community/generatebanners/src/lib/actions/renderTemplate.action.ts @@ -0,0 +1,173 @@ +import { + createAction, + Property, + BasicAuthPropertyValue, +} from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { generatebannersAuth } from '../../'; + +export const renderTemplate = createAction({ + auth: generatebannersAuth, + name: 'render_template', + description: 'Render a GenerateBanners template', + displayName: 'Render Template', + props: { + template_id: Property.Dropdown({ + displayName: 'Template', + required: true, + refreshers: [], + options: async ({ auth }) => { + const authentication = auth as BasicAuthPropertyValue; + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please enter your API key first.', + }; + } + + const response = + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.generatebanners.com/api/v1/${authentication.username}/template`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authentication.password, + }, + }); + + if (response.status === 200) { + return { + disabled: false, + options: response.body.templates.map((template) => { + return { + label: template.name, + value: template.id, + }; + }), + }; + } + + return { + disabled: true, + options: [], + placeholder: 'Error fetching templates', + }; + }, + }), + filetype: Property.StaticDropdown({ + displayName: 'File type', + required: true, + defaultValue: 'jpg', + options: { + options: [ + { + label: 'Image (jpg)', + value: 'jpg', + }, + { + label: 'Image (png)', + value: 'png', + }, + { + label: 'Document (pdf)', + value: 'pdf', + }, + ], + }, + }), + variables: Property.DynamicProperties({ + displayName: 'Variables', + required: true, + refreshers: ['template_id'], + props: async ({ auth, template_id }) => { + if (!auth || !template_id) { + return {}; + } + const request: HttpRequest = { + method: HttpMethod.GET, + url: `https://api.generatebanners.com/api/v1/${auth['username']}/template/${template_id}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth['password'], + }, + }; + const result = await httpClient.sendRequest(request); + if (result.status === 200) { + // Allowing any because the properties are defined on GenerateBanners + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const properties: any = {}; + for (const variable of result.body['template']['variables']) { + if (!variable.hidden) { + switch (variable.property) { + case 'text': + properties[variable.name] = Property.LongText({ + displayName: variable.name, + required: false, + }); + break; + default: + properties[variable.name] = Property.ShortText({ + displayName: variable.name, + required: false, + }); + } + } + } + return properties; + } else { + return {}; + } + }, + }), + }, + async run({ auth, propsValue }) { + // Build the querystring from the dynamic properties + const query = []; + if (propsValue.variables) { + const props = Object.entries(propsValue.variables); + for (const [propertyKey, propertyValue] of props) { + if (propertyValue) { + query.push( + `${propertyKey}=${encodeURIComponent(propertyValue as string)}` + ); + } + } + } + // Get the signed url + const request: HttpRequest = { + method: HttpMethod.GET, + url: `https://api.generatebanners.com/api/v1/${auth.username}/template/${ + propsValue.template_id + }/sign-url?filetype=${encodeURIComponent( + propsValue.filetype + )}&${query.join('&')}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.password, + }, + }; + const result = await httpClient.sendRequest(request); + if (result.status === 200 || result.status === 202) { + // Return an url for more flexibility + return result.body['url']; + } else { + return {}; + } + }, +}); + +interface GenerateBannersTemplateList { + templates: GenerateBannersTemplate[]; +} + +interface GenerateBannersTemplate { + id: string; + name: string; + variables: []; +} diff --git a/packages/pieces/community/generatebanners/tsconfig.json b/packages/pieces/community/generatebanners/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/generatebanners/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/generatebanners/tsconfig.lib.json b/packages/pieces/community/generatebanners/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/generatebanners/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/ghostcms/.eslintrc.json b/packages/pieces/community/ghostcms/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/ghostcms/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/ghostcms/README.md b/packages/pieces/community/ghostcms/README.md new file mode 100644 index 0000000..9195dd2 --- /dev/null +++ b/packages/pieces/community/ghostcms/README.md @@ -0,0 +1,7 @@ +# pieces-ghostcms + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-ghostcms` to build the library. diff --git a/packages/pieces/community/ghostcms/package.json b/packages/pieces/community/ghostcms/package.json new file mode 100644 index 0000000..c2c10ff --- /dev/null +++ b/packages/pieces/community/ghostcms/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-ghostcms", + "version": "0.0.5" +} \ No newline at end of file diff --git a/packages/pieces/community/ghostcms/project.json b/packages/pieces/community/ghostcms/project.json new file mode 100644 index 0000000..1864eef --- /dev/null +++ b/packages/pieces/community/ghostcms/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-ghostcms", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/ghostcms/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/ghostcms", + "tsConfig": "packages/pieces/community/ghostcms/tsconfig.lib.json", + "packageJson": "packages/pieces/community/ghostcms/package.json", + "main": "packages/pieces/community/ghostcms/src/index.ts", + "assets": [ + "packages/pieces/community/ghostcms/*.md", + { + "input": "packages/pieces/community/ghostcms/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-ghostcms {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/ghostcms/src/index.ts b/packages/pieces/community/ghostcms/src/index.ts new file mode 100644 index 0000000..2d11f11 --- /dev/null +++ b/packages/pieces/community/ghostcms/src/index.ts @@ -0,0 +1,82 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; + +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceCategory } from '@activepieces/shared'; +import { createMember } from './lib/actions/create-member'; +import { createPost } from './lib/actions/create-post'; +import { findMember } from './lib/actions/find-member'; +import { findUser } from './lib/actions/find-user'; +import { updateMember } from './lib/actions/update-member'; +import { common } from './lib/common'; +import { memberAdded } from './lib/triggers/member-added'; +import { memberDeleted } from './lib/triggers/member-deleted'; +import { memberEdited } from './lib/triggers/member-edited'; +import { pagePublished } from './lib/triggers/page-published'; +import { postPublished } from './lib/triggers/post-published'; +import { postScheduled } from './lib/triggers/post-scheduled'; + +const authMarkdown = ` +To generate an API key, follow the steps below in GhostCMS: +1. Go to Settings -> Advanced -> Integrations. +2. Scroll down to Custom Integrations and click Add custom integration. +3. Enter integration name and click create. +4. Copy the API URL and the Admin API Key into the fields below. +`; + +export const ghostAuth = PieceAuth.CustomAuth({ + description: authMarkdown, + required: true, + props: { + baseUrl: Property.ShortText({ + displayName: 'API URL', + description: + 'The API URL of your application (https://test-publication.ghost.io)', + required: true, + }), + apiKey: PieceAuth.SecretText({ + displayName: 'Admin API Key', + description: 'The admin API key for your application', + required: true, + }), + }, +}); + +export const ghostcms = createPiece({ + displayName: 'GhostCMS', + description: 'Publishing platform for professional bloggers', + + auth: ghostAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/ghostcms.png', + categories: [PieceCategory.MARKETING], + authors: ["kishanprmr","MoShizzle","abuaboud"], + actions: [ + createMember, + updateMember, + createPost, + findMember, + findUser, + createCustomApiCallAction({ + baseUrl: (auth) => + `${(auth as { baseUrl: string }).baseUrl}/ghost/api/admin`, + auth: ghostAuth, + authMapping: async (auth) => ({ + Authorization: `Ghost ${common.jwtFromApiKey( + (auth as { apiKey: string }).apiKey + )}`, + }), + }), + ], + triggers: [ + memberAdded, + memberEdited, + memberDeleted, + postPublished, + postScheduled, + pagePublished, + ], +}); diff --git a/packages/pieces/community/ghostcms/src/lib/actions/create-member.ts b/packages/pieces/community/ghostcms/src/lib/actions/create-member.ts new file mode 100644 index 0000000..6b30562 --- /dev/null +++ b/packages/pieces/community/ghostcms/src/lib/actions/create-member.ts @@ -0,0 +1,58 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +import { ghostAuth } from '../..'; +import { common } from '../common'; + +export const createMember = createAction({ + name: 'create_member', + displayName: 'Create Member', + description: 'Create a new member', + auth: ghostAuth, + props: { + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + name: Property.ShortText({ + displayName: 'Full Name', + required: false, + }), + note: Property.ShortText({ + displayName: 'Note', + required: false, + }), + newsletters: common.properties.newsletters(false), + }, + + async run(context) { + const newsletters: any[] = []; + if (context.propsValue.newsletters) { + context.propsValue.newsletters.forEach((newsletter: any) => { + newsletters.push({ + id: newsletter, + }); + }); + } + + const response = await httpClient.sendRequest({ + url: `${context.auth.baseUrl}/ghost/api/admin/members`, + method: HttpMethod.POST, + headers: { + Authorization: `Ghost ${common.jwtFromApiKey(context.auth.apiKey)}`, + }, + body: { + members: [ + { + email: context.propsValue.email, + name: context.propsValue.name, + note: context.propsValue.note, + newsletters: newsletters, + }, + ], + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/ghostcms/src/lib/actions/create-post.ts b/packages/pieces/community/ghostcms/src/lib/actions/create-post.ts new file mode 100644 index 0000000..adfa44b --- /dev/null +++ b/packages/pieces/community/ghostcms/src/lib/actions/create-post.ts @@ -0,0 +1,85 @@ +import { + createAction, + DynamicPropsValue, + Property, +} from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +import { ghostAuth } from '../..'; +import { common } from '../common'; + +export const createPost = createAction({ + name: 'create_post', + displayName: 'Create Post', + description: 'Create a new post', + auth: ghostAuth, + props: { + title: Property.ShortText({ + displayName: 'Title', + required: true, + }), + slug: Property.ShortText({ + displayName: 'Slug', + required: false, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + required: true, + options: { + options: [ + { label: 'Draft', value: 'draft' }, + { label: 'Published', value: 'published' }, + { label: 'Scheduled', value: 'scheduled' }, + ], + }, + }), + publishedAt: Property.DateTime({ + displayName: 'Published At', + required: false, + }), + html: Property.LongText({ + displayName: 'Content (HTML)', + required: true, + }), + customExcerpt: Property.ShortText({ + displayName: 'Custom Excerpt', + required: false, + }), + author: common.properties.author, + featured: Property.Checkbox({ + displayName: 'Featured', + required: false, + }), + tags: common.properties.tags, + }, + + async run(context) { + const response = await httpClient.sendRequest({ + url: `${context.auth.baseUrl}/ghost/api/admin/posts`, + method: HttpMethod.POST, + headers: { + Authorization: `Ghost ${common.jwtFromApiKey(context.auth.apiKey)}`, + }, + body: { + posts: [ + { + title: context.propsValue.title, + slug: context.propsValue.slug, + status: context.propsValue.status, + html: context.propsValue.html, + published_at: context.propsValue.publishedAt, + authors: [context.propsValue.author], + featured: context.propsValue.featured, + custom_excerpt: context.propsValue.customExcerpt, + tags: context.propsValue.tags, + }, + ], + }, + queryParams: { + source: 'html', + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/ghostcms/src/lib/actions/find-member.ts b/packages/pieces/community/ghostcms/src/lib/actions/find-member.ts new file mode 100644 index 0000000..db4b8fd --- /dev/null +++ b/packages/pieces/community/ghostcms/src/lib/actions/find-member.ts @@ -0,0 +1,33 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +import { ghostAuth } from '../..'; +import { common } from '../common'; + +export const findMember = createAction({ + name: 'find_member', + displayName: 'Find Member', + description: 'Find a member by email', + auth: ghostAuth, + props: { + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + }, + + async run(context) { + const response = await httpClient.sendRequest({ + url: `${context.auth.baseUrl}/ghost/api/admin/members`, + method: HttpMethod.GET, + headers: { + Authorization: `Ghost ${common.jwtFromApiKey(context.auth.apiKey)}`, + }, + queryParams: { + filter: `email:${context.propsValue.email}`, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/ghostcms/src/lib/actions/find-user.ts b/packages/pieces/community/ghostcms/src/lib/actions/find-user.ts new file mode 100644 index 0000000..858f928 --- /dev/null +++ b/packages/pieces/community/ghostcms/src/lib/actions/find-user.ts @@ -0,0 +1,33 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +import { ghostAuth } from '../..'; +import { common } from '../common'; + +export const findUser = createAction({ + name: 'find_user', + displayName: 'Find User', + description: 'Find a staff user by email', + auth: ghostAuth, + props: { + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + }, + + async run(context) { + const response = await httpClient.sendRequest({ + url: `${context.auth.baseUrl}/ghost/api/admin/users`, + method: HttpMethod.GET, + headers: { + Authorization: `Ghost ${common.jwtFromApiKey(context.auth.apiKey)}`, + }, + queryParams: { + filter: `email:${context.propsValue.email}`, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/ghostcms/src/lib/actions/update-member.ts b/packages/pieces/community/ghostcms/src/lib/actions/update-member.ts new file mode 100644 index 0000000..79df658 --- /dev/null +++ b/packages/pieces/community/ghostcms/src/lib/actions/update-member.ts @@ -0,0 +1,65 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +import { ghostAuth } from '../..'; +import { common } from '../common'; + +export const updateMember = createAction({ + name: 'update_member', + displayName: 'Update Member', + description: 'Update a member', + auth: ghostAuth, + props: { + member: common.properties.member(), + email: Property.ShortText({ + displayName: 'Email', + required: false, + }), + name: Property.ShortText({ + displayName: 'Full Name', + required: false, + }), + note: Property.ShortText({ + displayName: 'Note', + required: false, + }), + newsletters: common.properties.newsletters(false), + }, + + async run(context) { + const newsletters: any[] = []; + if (context.propsValue.newsletters) { + context.propsValue.newsletters.forEach((newsletter: any) => { + newsletters.push({ + id: newsletter, + }); + }); + } + + const data: { + email?: string; + name?: string; + note?: string; + newsletters?: any[]; + } = {}; + + if (context.propsValue.email) data.email = context.propsValue.email; + if (context.propsValue.name) data.name = context.propsValue.name; + if (context.propsValue.note) data.note = context.propsValue.note; + if (context.propsValue.newsletters) + data.newsletters = context.propsValue.newsletters; + + const response = await httpClient.sendRequest({ + url: `${context.auth.baseUrl}/ghost/api/admin/members/${context.propsValue.member}`, + method: HttpMethod.PUT, + headers: { + Authorization: `Ghost ${common.jwtFromApiKey(context.auth.apiKey)}`, + }, + body: { + members: [data], + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/ghostcms/src/lib/common/index.ts b/packages/pieces/community/ghostcms/src/lib/common/index.ts new file mode 100644 index 0000000..2c4b71c --- /dev/null +++ b/packages/pieces/community/ghostcms/src/lib/common/index.ts @@ -0,0 +1,207 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { Property } from '@activepieces/pieces-framework'; + +import jwt from 'jsonwebtoken'; + +export const common = { + properties: { + newsletters: (required = true) => { + return Property.MultiSelectDropdown({ + displayName: 'Newsletters', + required: required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account', + options: [], + }; + } + + const newsletters = ( + (await common.getNewsletters(auth)) as { newsletters: any[] } + ).newsletters.map((newsletter: any) => { + return { + label: newsletter.name, + value: newsletter.id, + }; + }); + return { + options: newsletters, + }; + }, + }); + }, + member: (required = true) => { + return Property.Dropdown({ + displayName: 'Member', + required: required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account', + options: [], + }; + } + + const members = ( + (await common.getMembers(auth)) as { members: any[] } + ).members.map((member: any) => { + return { + label: member.name ?? member.email, + value: member.id, + }; + }); + return { + options: members, + }; + }, + }); + }, + author: Property.Dropdown({ + displayName: 'Author', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account', + options: [], + }; + } + + const authors: any[] = ( + (await common.getUsers(auth)) as { users: any[] } + ).users.map((user: any) => { + return { + label: user.name ?? user.email, + value: user.email, + }; + }); + return { + options: authors, + }; + }, + }), + tags: Property.MultiSelectDropdown({ + displayName: 'Tags', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account', + options: [], + }; + } + + const tags: any[] = ( + (await common.getTags(auth)) as { tags: any[] } + ).tags.map((tag: any) => { + return { + label: tag.name, + value: tag.name, + }; + }); + return { + options: tags, + }; + }, + }), + }, + + jwtFromApiKey: (apiKey: string) => { + const [id, secret] = apiKey.split(':'); + + return jwt.sign({}, Buffer.from(secret, 'hex'), { + keyid: id, + expiresIn: '5m', + audience: '/admin/', + }); + }, + + async subscribeWebhook(auth: any, webhookType: string, webhookUrl: string) { + const response = await httpClient.sendRequest({ + url: `${auth.baseUrl}/ghost/api/admin/webhooks`, + method: HttpMethod.POST, + headers: { + Authorization: `Ghost ${common.jwtFromApiKey(auth.apiKey)}`, + }, + body: { + webhooks: [ + { + target_url: webhookUrl, + event: webhookType, + }, + ], + }, + }); + + return response.body; + }, + + async unsubscribeWebhook(auth: any, webhookId: string) { + const response = await httpClient.sendRequest({ + url: `${auth.baseUrl}/ghost/api/admin/webhooks/${webhookId}`, + method: HttpMethod.DELETE, + headers: { + Authorization: `Ghost ${common.jwtFromApiKey(auth.apiKey)}`, + }, + }); + + return response.body; + }, + + async getNewsletters(auth: any) { + const response = await httpClient.sendRequest({ + url: `${auth.baseUrl}/ghost/api/admin/newsletters`, + method: HttpMethod.GET, + headers: { + Authorization: `Ghost ${common.jwtFromApiKey(auth.apiKey)}`, + }, + }); + + return response.body; + }, + + async getMembers(auth: any) { + const response = await httpClient.sendRequest({ + url: `${auth.baseUrl}/ghost/api/admin/members`, + method: HttpMethod.GET, + headers: { + Authorization: `Ghost ${common.jwtFromApiKey(auth.apiKey)}`, + }, + }); + + return response.body; + }, + + async getUsers(auth: any) { + const response = await httpClient.sendRequest({ + url: `${auth.baseUrl}/ghost/api/admin/users`, + method: HttpMethod.GET, + headers: { + Authorization: `Ghost ${common.jwtFromApiKey(auth.apiKey)}`, + }, + }); + + return response.body; + }, + + async getTags(auth: any) { + const response = await httpClient.sendRequest({ + url: `${auth.baseUrl}/ghost/api/admin/tags`, + method: HttpMethod.GET, + headers: { + Authorization: `Ghost ${common.jwtFromApiKey(auth.apiKey)}`, + }, + }); + + return response.body; + }, +}; diff --git a/packages/pieces/community/ghostcms/src/lib/triggers/member-added.ts b/packages/pieces/community/ghostcms/src/lib/triggers/member-added.ts new file mode 100644 index 0000000..b1bed42 --- /dev/null +++ b/packages/pieces/community/ghostcms/src/lib/triggers/member-added.ts @@ -0,0 +1,100 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; + +import { ghostAuth } from '../..'; +import { common } from '../common'; + +export const memberAdded = createTrigger({ + auth: ghostAuth, + name: 'member_added', + displayName: 'Member Added', + description: 'Triggers when a new member is added', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const webhookData: any = await common.subscribeWebhook( + context.auth, + 'member.added', + context.webhookUrl + ); + + await context.store?.put('_member_added_trigger', { + webhookId: webhookData.webhooks[0].id, + }); + }, + async onDisable(context) { + const response: { + webhookId: string; + } | null = await context.store?.get('_member_added_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + async run(context) { + return [context.payload.body]; + }, + + sampleData: { + member: { + current: { + id: '64be7cd524cb9a0001f49f04', + name: 'First Last', + note: null, + uuid: 'b6ef6f63-5bb0-4ace-8abf-f9f7c152659c', + email: 'my@email.com', + tiers: [], + comped: false, + labels: [], + status: 'free', + created_at: '2023-07-24T13:29:57.000Z', + subscribed: true, + updated_at: '2023-07-24T13:29:57.000Z', + email_count: 0, + geolocation: null, + newsletters: [ + { + id: '64be4f5a03946b00098ef8f6', + name: 'Test Publication', + slug: 'default-newsletter', + uuid: '8bc1b063-57fa-4f26-8d7c-46a3d8002fad', + status: 'active', + created_at: '2023-07-24T10:15:54.000Z', + show_badge: true, + sort_order: 0, + updated_at: '2023-07-24T10:16:18.000Z', + visibility: 'members', + description: null, + sender_name: null, + title_color: null, + border_color: null, + header_image: null, + sender_email: null, + footer_content: null, + sender_reply_to: 'newsletter', + title_alignment: 'center', + background_color: 'light', + feedback_enabled: false, + show_comment_cta: true, + show_header_icon: true, + show_header_name: false, + show_header_title: true, + show_latest_posts: false, + body_font_category: 'sans_serif', + show_feature_image: true, + subscribe_on_signup: true, + title_font_category: 'sans_serif', + show_post_title_section: true, + show_subscription_details: false, + }, + ], + avatar_image: + 'https://www.gravatar.com/avatar/123123123?s=250&r=g&d=blank', + last_seen_at: null, + subscriptions: [], + email_open_rate: null, + email_opened_count: 0, + }, + previous: {}, + }, + }, +}); diff --git a/packages/pieces/community/ghostcms/src/lib/triggers/member-deleted.ts b/packages/pieces/community/ghostcms/src/lib/triggers/member-deleted.ts new file mode 100644 index 0000000..807b015 --- /dev/null +++ b/packages/pieces/community/ghostcms/src/lib/triggers/member-deleted.ts @@ -0,0 +1,100 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; + +import { ghostAuth } from '../..'; +import { common } from '../common'; + +export const memberDeleted = createTrigger({ + auth: ghostAuth, + name: 'member_deleted', + displayName: 'Member Deleted', + description: 'Triggers when a member is deleted', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const webhookData: any = await common.subscribeWebhook( + context.auth, + 'member.deleted', + context.webhookUrl + ); + + await context.store?.put('_member_deleted_trigger', { + webhookId: webhookData.webhooks[0].id, + }); + }, + async onDisable(context) { + const response: { + webhookId: string; + } | null = await context.store?.get('_member_deleted_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + async run(context) { + return [context.payload.body]; + }, + + sampleData: { + member: { + current: {}, + previous: { + id: '64be7cd524cb9a0001f49f04', + name: 'Updated Name', + note: null, + uuid: 'b6ef6f63-5bb0-4ace-8abf-f9f7c152659c', + email: 'my@email.com', + tiers: [], + comped: false, + labels: [], + status: 'free', + created_at: '2023-07-24T13:29:57.000Z', + subscribed: true, + updated_at: '2023-07-24T13:43:00.000Z', + email_count: 0, + geolocation: null, + newsletters: [ + { + id: '64be4f5a03946b00098ef8f6', + name: 'Test Publication', + slug: 'default-newsletter', + uuid: '8bc1b063-57fa-4f26-8d7c-46a3d8002fad', + status: 'active', + created_at: '2023-07-24T10:15:54.000Z', + show_badge: true, + sort_order: 0, + updated_at: '2023-07-24T10:16:18.000Z', + visibility: 'members', + description: null, + sender_name: null, + title_color: null, + border_color: null, + header_image: null, + sender_email: null, + footer_content: null, + sender_reply_to: 'newsletter', + title_alignment: 'center', + background_color: 'light', + feedback_enabled: false, + show_comment_cta: true, + show_header_icon: true, + show_header_name: false, + show_header_title: true, + show_latest_posts: false, + body_font_category: 'sans_serif', + show_feature_image: true, + subscribe_on_signup: true, + title_font_category: 'sans_serif', + show_post_title_section: true, + show_subscription_details: false, + }, + ], + avatar_image: + 'https://www.gravatar.com/avatar/123123123?s=250&r=g&d=blank', + last_seen_at: null, + subscriptions: [], + email_open_rate: null, + email_opened_count: 0, + }, + }, + }, +}); diff --git a/packages/pieces/community/ghostcms/src/lib/triggers/member-edited.ts b/packages/pieces/community/ghostcms/src/lib/triggers/member-edited.ts new file mode 100644 index 0000000..ace7efa --- /dev/null +++ b/packages/pieces/community/ghostcms/src/lib/triggers/member-edited.ts @@ -0,0 +1,105 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; + +import { ghostAuth } from '../..'; +import { common } from '../common'; + +export const memberEdited = createTrigger({ + auth: ghostAuth, + name: 'member_edited', + displayName: 'Member Edited', + description: 'Triggers when a member is edited', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const webhookData: any = await common.subscribeWebhook( + context.auth, + 'member.edited', + context.webhookUrl + ); + + await context.store?.put('_member_edited_trigger', { + webhookId: webhookData.webhooks[0].id, + }); + }, + async onDisable(context) { + const response: { + webhookId: string; + } | null = await context.store?.get('_member_edited_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + async run(context) { + return [context.payload.body]; + }, + + sampleData: { + member: { + current: { + id: '64be7cd524cb9a0001f49f04', + name: 'Updated Name', + note: null, + uuid: 'b6ef6f63-5bb0-4ace-8abf-f9f7c152659c', + email: 'my@email.com', + tiers: [], + comped: false, + labels: [], + status: 'free', + created_at: '2023-07-24T13:29:57.000Z', + subscribed: true, + updated_at: '2023-07-24T13:43:00.000Z', + email_count: 0, + geolocation: null, + newsletters: [ + { + id: '64be4f5a03946b00098ef8f6', + name: 'Test Publication', + slug: 'default-newsletter', + uuid: '8bc1b063-57fa-4f26-8d7c-46a3d8002fad', + status: 'active', + created_at: '2023-07-24T10:15:54.000Z', + show_badge: true, + sort_order: 0, + updated_at: '2023-07-24T10:16:18.000Z', + visibility: 'members', + description: null, + sender_name: null, + title_color: null, + border_color: null, + header_image: null, + sender_email: null, + footer_content: null, + sender_reply_to: 'newsletter', + title_alignment: 'center', + background_color: 'light', + feedback_enabled: false, + show_comment_cta: true, + show_header_icon: true, + show_header_name: false, + show_header_title: true, + show_latest_posts: false, + body_font_category: 'sans_serif', + show_feature_image: true, + subscribe_on_signup: true, + title_font_category: 'sans_serif', + show_post_title_section: true, + show_subscription_details: false, + }, + ], + avatar_image: + 'https://www.gravatar.com/avatar/123123123?s=250&r=g&d=blank', + last_seen_at: null, + subscriptions: [], + email_open_rate: null, + email_opened_count: 0, + }, + previous: { + email: 'new@email.com', + note: null, + updated_at: '2023-07-24T13:38:29.000Z', + newsletters: [], + }, + }, + }, +}); diff --git a/packages/pieces/community/ghostcms/src/lib/triggers/page-published.ts b/packages/pieces/community/ghostcms/src/lib/triggers/page-published.ts new file mode 100644 index 0000000..0291175 --- /dev/null +++ b/packages/pieces/community/ghostcms/src/lib/triggers/page-published.ts @@ -0,0 +1,209 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; + +import { ghostAuth } from '../..'; +import { common } from '../common'; + +export const pagePublished = createTrigger({ + auth: ghostAuth, + name: 'page_published', + displayName: 'Page Published', + description: 'Triggers when a page is published', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const webhookData: any = await common.subscribeWebhook( + context.auth, + 'page.published', + context.webhookUrl + ); + + await context.store?.put('_page_published_trigger', { + webhookId: webhookData.webhooks[0].id, + }); + }, + async onDisable(context) { + const response: { + webhookId: string; + } | null = await context.store?.get('_page_published_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + async run(context) { + return [context.payload.body]; + }, + + sampleData: { + page: { + current: { + id: '64be860724cb9a0001f49f37', + url: 'https://test-publication.ghost.io/my-page/', + html: '

Page content

', + slug: 'my-page', + tags: [], + uuid: 'c2d16c98-f967-4aaa-ba10-ff4fedbb351e', + count: { + signups: 0, + paid_conversions: 0, + negative_feedback: 0, + positive_feedback: 0, + }, + tiers: [ + { + id: '64be4f5a03946b00098ef8f4', + name: 'Free', + slug: 'free', + type: 'free', + active: true, + currency: null, + created_at: '2023-07-24T10:15:54.000Z', + trial_days: 0, + updated_at: '2023-07-24T10:15:54.000Z', + visibility: 'public', + description: null, + yearly_price: null, + monthly_price: null, + yearly_price_id: null, + monthly_price_id: null, + welcome_page_url: null, + }, + { + id: '64be4f5a03946b00098ef8f5', + name: 'Test Publication', + slug: 'default-product', + type: 'paid', + active: true, + currency: 'usd', + created_at: '2023-07-24T10:15:54.000Z', + trial_days: 0, + updated_at: '2023-07-24T10:15:54.000Z', + visibility: 'public', + description: null, + yearly_price: 5000, + monthly_price: 500, + yearly_price_id: null, + monthly_price_id: null, + welcome_page_url: null, + }, + ], + title: 'My Page', + status: 'published', + authors: [ + { + id: '1', + bio: null, + url: 'https://test-publication.ghost.io/author/slug/', + name: 'First Last', + slug: 'slug', + tour: null, + email: 'my@email.com', + roles: [ + { + id: '64be4f5903946b00098ef8eb', + name: 'Owner', + created_at: '2023-07-24T10:15:53.000Z', + updated_at: '2023-07-24T10:15:53.000Z', + description: 'Blog Owner', + }, + ], + status: 'active', + twitter: null, + website: null, + facebook: null, + location: null, + last_seen: '2023-07-24T14:05:21.000Z', + created_at: '2023-07-24T10:15:53.000Z', + meta_title: null, + updated_at: '2023-07-24T14:05:21.000Z', + cover_image: null, + accessibility: '{"nightShift":true}', + profile_image: + 'https://www.gravatar.com/avatar/123123123?s=250&r=x&d=mp', + meta_description: null, + comment_notifications: true, + mention_notifications: true, + milestone_notifications: true, + free_member_signup_notification: true, + paid_subscription_started_notification: true, + paid_subscription_canceled_notification: false, + }, + ], + excerpt: 'Page content', + featured: false, + og_image: null, + og_title: null, + mobiledoc: + '{"version":"0.3.1","atoms":[],"cards":[],"markups":[],"sections":[[1,"p",[[0,[],0,"Page content"]]]],"ghostVersion":"4.0"}', + plaintext: 'Page content', + comment_id: '64be860724cb9a0001f49f37', + created_at: '2023-07-24T14:09:11.000Z', + meta_title: null, + updated_at: '2023-07-24T14:09:19.000Z', + visibility: 'public', + frontmatter: null, + primary_tag: null, + published_at: '2023-07-24T14:09:19.000Z', + reading_time: 0, + canonical_url: null, + feature_image: null, + twitter_image: null, + twitter_title: null, + custom_excerpt: null, + og_description: null, + post_revisions: [], + primary_author: { + id: '1', + bio: null, + url: 'https://test-publication.ghost.io/author/slug/', + name: 'First Last', + slug: 'slug', + tour: null, + email: 'my@email.com', + roles: [ + { + id: '64be4f5903946b00098ef8eb', + name: 'Owner', + created_at: '2023-07-24T10:15:53.000Z', + updated_at: '2023-07-24T10:15:53.000Z', + description: 'Blog Owner', + }, + ], + status: 'active', + twitter: null, + website: null, + facebook: null, + location: null, + last_seen: '2023-07-24T14:05:21.000Z', + created_at: '2023-07-24T10:15:53.000Z', + meta_title: null, + updated_at: '2023-07-24T14:05:21.000Z', + cover_image: null, + accessibility: '{"nightShift":true}', + profile_image: + 'https://www.gravatar.com/avatar/123123123?s=250&r=x&d=mp', + meta_description: null, + comment_notifications: true, + mention_notifications: true, + milestone_notifications: true, + free_member_signup_notification: true, + paid_subscription_started_notification: true, + paid_subscription_canceled_notification: false, + }, + custom_template: null, + meta_description: null, + feature_image_alt: null, + codeinjection_foot: null, + codeinjection_head: null, + twitter_description: null, + feature_image_caption: null, + show_title_and_feature_image: true, + }, + previous: { + status: 'draft', + updated_at: '2023-07-24T14:09:16.000Z', + published_at: null, + }, + }, + }, +}); diff --git a/packages/pieces/community/ghostcms/src/lib/triggers/post-published.ts b/packages/pieces/community/ghostcms/src/lib/triggers/post-published.ts new file mode 100644 index 0000000..f2c0395 --- /dev/null +++ b/packages/pieces/community/ghostcms/src/lib/triggers/post-published.ts @@ -0,0 +1,210 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; + +import { ghostAuth } from '../..'; +import { common } from '../common'; + +export const postPublished = createTrigger({ + auth: ghostAuth, + name: 'post_published', + displayName: 'Post Published', + description: 'Triggers when a post is published', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const webhookData: any = await common.subscribeWebhook( + context.auth, + 'post.published', + context.webhookUrl + ); + + await context.store?.put('_post_published_trigger', { + webhookId: webhookData.webhooks[0].id, + }); + }, + async onDisable(context) { + const response: { + webhookId: string; + } | null = await context.store?.get('_post_published_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + async run(context) { + return [context.payload.body]; + }, + + sampleData: { + post: { + current: { + id: '64be83c424cb9a0001f49f1d', + url: 'https://test-publication.ghost.io/test-post/', + html: '

Hello my friends

this is testing

', + slug: 'test-post', + tags: [], + uuid: '77afb2c2-af1a-4a7f-bebc-fa1e314579d0', + count: { + clicks: 0, + negative_feedback: 0, + positive_feedback: 0, + }, + tiers: [ + { + id: '64be4f5a03946b00098ef8f4', + name: 'Free', + slug: 'free', + type: 'free', + active: true, + currency: null, + created_at: '2023-07-24T10:15:54.000Z', + trial_days: 0, + updated_at: '2023-07-24T10:15:54.000Z', + visibility: 'public', + description: null, + yearly_price: null, + monthly_price: null, + yearly_price_id: null, + monthly_price_id: null, + welcome_page_url: null, + }, + { + id: '64be4f5a03946b00098ef8f5', + name: 'Test Publication', + slug: 'default-product', + type: 'paid', + active: true, + currency: 'usd', + created_at: '2023-07-24T10:15:54.000Z', + trial_days: 0, + updated_at: '2023-07-24T10:15:54.000Z', + visibility: 'public', + description: null, + yearly_price: 5000, + monthly_price: 500, + yearly_price_id: null, + monthly_price_id: null, + welcome_page_url: null, + }, + ], + title: 'Test Post', + status: 'published', + authors: [ + { + id: '1', + bio: null, + url: 'https://test-publication.ghost.io/author/slug/', + name: 'First Last', + slug: 'slug', + tour: null, + email: 'my@email.com', + roles: [ + { + id: '64be4f5903946b00098ef8eb', + name: 'Owner', + created_at: '2023-07-24T10:15:53.000Z', + updated_at: '2023-07-24T10:15:53.000Z', + description: 'Blog Owner', + }, + ], + status: 'active', + twitter: null, + website: null, + facebook: null, + location: null, + last_seen: '2023-07-24T13:05:18.000Z', + created_at: '2023-07-24T10:15:53.000Z', + meta_title: null, + updated_at: '2023-07-24T13:05:18.000Z', + cover_image: null, + accessibility: '{"nightShift":true}', + profile_image: + 'https://www.gravatar.com/avatar/123123123?s=250&r=x&d=mp', + meta_description: null, + comment_notifications: true, + mention_notifications: true, + milestone_notifications: true, + free_member_signup_notification: true, + paid_subscription_started_notification: true, + paid_subscription_canceled_notification: false, + }, + ], + excerpt: 'Hello my friends\n\n\n\nthis is testing', + featured: false, + og_image: null, + og_title: null, + mobiledoc: + '{"version":"0.3.1","atoms":[],"cards":[],"markups":[],"sections":[[1,"p",[[0,[],0,"Hello my friends"]]],[1,"p",[]],[1,"p",[[0,[],0,"this is testing"]]]],"ghostVersion":"4.0"}', + plaintext: 'Hello my friends\n\n\n\nthis is testing', + comment_id: '64be83c424cb9a0001f49f1d', + created_at: '2023-07-24T13:59:32.000Z', + email_only: false, + meta_title: null, + updated_at: '2023-07-24T13:59:43.000Z', + visibility: 'public', + frontmatter: null, + primary_tag: null, + published_at: '2023-07-24T13:59:43.000Z', + reading_time: 0, + canonical_url: null, + email_segment: 'all', + email_subject: null, + feature_image: null, + twitter_image: null, + twitter_title: null, + custom_excerpt: null, + og_description: null, + post_revisions: [], + primary_author: { + id: '1', + bio: null, + url: 'https://test-publication.ghost.io/author/slug/', + name: 'First Last', + slug: 'slug', + tour: null, + email: 'my@email.com', + roles: [ + { + id: '64be4f5903946b00098ef8eb', + name: 'Owner', + created_at: '2023-07-24T10:15:53.000Z', + updated_at: '2023-07-24T10:15:53.000Z', + description: 'Blog Owner', + }, + ], + status: 'active', + twitter: null, + website: null, + facebook: null, + location: null, + last_seen: '2023-07-24T13:05:18.000Z', + created_at: '2023-07-24T10:15:53.000Z', + meta_title: null, + updated_at: '2023-07-24T13:05:18.000Z', + cover_image: null, + accessibility: '{"nightShift":true}', + profile_image: + 'https://www.gravatar.com/avatar/123123123?s=250&r=x&d=mp', + meta_description: null, + comment_notifications: true, + mention_notifications: true, + milestone_notifications: true, + free_member_signup_notification: true, + paid_subscription_started_notification: true, + paid_subscription_canceled_notification: false, + }, + custom_template: null, + meta_description: null, + feature_image_alt: null, + codeinjection_foot: null, + codeinjection_head: null, + twitter_description: null, + feature_image_caption: null, + }, + previous: { + status: 'draft', + updated_at: '2023-07-24T13:59:39.000Z', + published_at: null, + }, + }, + }, +}); diff --git a/packages/pieces/community/ghostcms/src/lib/triggers/post-scheduled.ts b/packages/pieces/community/ghostcms/src/lib/triggers/post-scheduled.ts new file mode 100644 index 0000000..0028795 --- /dev/null +++ b/packages/pieces/community/ghostcms/src/lib/triggers/post-scheduled.ts @@ -0,0 +1,210 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; + +import { ghostAuth } from '../..'; +import { common } from '../common'; + +export const postScheduled = createTrigger({ + auth: ghostAuth, + name: 'post_scheduled', + displayName: 'Post Scheduled', + description: 'Triggers when a post is scheduled', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const webhookData: any = await common.subscribeWebhook( + context.auth, + 'post.scheduled', + context.webhookUrl + ); + + await context.store?.put('_post_scheduled_trigger', { + webhookId: webhookData.webhooks[0].id, + }); + }, + async onDisable(context) { + const response: { + webhookId: string; + } | null = await context.store?.get('_post_scheduled_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + async run(context) { + return [context.payload.body]; + }, + + sampleData: { + post: { + current: { + id: '64be851224cb9a0001f49f2a', + url: 'https://test-publication.ghost.io/p/cdfeba97-3d7e-44e5-902c-c9f68b46a432/', + html: '

This is a scheduled post

', + slug: 'scheduled-post', + tags: [], + uuid: 'cdfeba97-3d7e-44e5-902c-c9f68b46a432', + count: { + clicks: 0, + negative_feedback: 0, + positive_feedback: 0, + }, + tiers: [ + { + id: '64be4f5a03946b00098ef8f4', + name: 'Free', + slug: 'free', + type: 'free', + active: true, + currency: null, + created_at: '2023-07-24T10:15:54.000Z', + trial_days: 0, + updated_at: '2023-07-24T10:15:54.000Z', + visibility: 'public', + description: null, + yearly_price: null, + monthly_price: null, + yearly_price_id: null, + monthly_price_id: null, + welcome_page_url: null, + }, + { + id: '64be4f5a03946b00098ef8f5', + name: 'Test Publication', + slug: 'default-product', + type: 'paid', + active: true, + currency: 'usd', + created_at: '2023-07-24T10:15:54.000Z', + trial_days: 0, + updated_at: '2023-07-24T10:15:54.000Z', + visibility: 'public', + description: null, + yearly_price: 5000, + monthly_price: 500, + yearly_price_id: null, + monthly_price_id: null, + welcome_page_url: null, + }, + ], + title: 'Scheduled post', + status: 'scheduled', + authors: [ + { + id: '1', + bio: null, + url: 'https://test-publication.ghost.io/author/slug/', + name: 'First Last', + slug: 'slug', + tour: null, + email: 'my@email.com', + roles: [ + { + id: '64be4f5903946b00098ef8eb', + name: 'Owner', + created_at: '2023-07-24T10:15:53.000Z', + updated_at: '2023-07-24T10:15:53.000Z', + description: 'Blog Owner', + }, + ], + status: 'active', + twitter: null, + website: null, + facebook: null, + location: null, + last_seen: '2023-07-24T13:05:18.000Z', + created_at: '2023-07-24T10:15:53.000Z', + meta_title: null, + updated_at: '2023-07-24T13:05:18.000Z', + cover_image: null, + accessibility: '{"nightShift":true}', + profile_image: + 'https://www.gravatar.com/avatar/123123123?s=250&r=x&d=mp', + meta_description: null, + comment_notifications: true, + mention_notifications: true, + milestone_notifications: true, + free_member_signup_notification: true, + paid_subscription_started_notification: true, + paid_subscription_canceled_notification: false, + }, + ], + excerpt: 'This is a scheduled post', + featured: false, + og_image: null, + og_title: null, + mobiledoc: + '{"version":"0.3.1","atoms":[],"cards":[],"markups":[],"sections":[[1,"p",[[0,[],0,"This is a scheduled post"]]]],"ghostVersion":"4.0"}', + plaintext: 'This is a scheduled post', + comment_id: '64be851224cb9a0001f49f2a', + created_at: '2023-07-24T14:05:06.000Z', + email_only: false, + meta_title: null, + updated_at: '2023-07-24T14:05:18.000Z', + visibility: 'public', + frontmatter: null, + primary_tag: null, + published_at: '2023-07-24T14:15:12.000Z', + reading_time: 0, + canonical_url: null, + email_segment: 'all', + email_subject: null, + feature_image: null, + twitter_image: null, + twitter_title: null, + custom_excerpt: null, + og_description: null, + post_revisions: [], + primary_author: { + id: '1', + bio: null, + url: 'https://test-publication.ghost.io/author/slug/', + name: 'First Last', + slug: 'slug', + tour: null, + email: 'my@email.com', + roles: [ + { + id: '64be4f5903946b00098ef8eb', + name: 'Owner', + created_at: '2023-07-24T10:15:53.000Z', + updated_at: '2023-07-24T10:15:53.000Z', + description: 'Blog Owner', + }, + ], + status: 'active', + twitter: null, + website: null, + facebook: null, + location: null, + last_seen: '2023-07-24T13:05:18.000Z', + created_at: '2023-07-24T10:15:53.000Z', + meta_title: null, + updated_at: '2023-07-24T13:05:18.000Z', + cover_image: null, + accessibility: '{"nightShift":true}', + profile_image: + 'https://www.gravatar.com/avatar/123123123?s=250&r=x&d=mp', + meta_description: null, + comment_notifications: true, + mention_notifications: true, + milestone_notifications: true, + free_member_signup_notification: true, + paid_subscription_started_notification: true, + paid_subscription_canceled_notification: false, + }, + custom_template: null, + meta_description: null, + feature_image_alt: null, + codeinjection_foot: null, + codeinjection_head: null, + twitter_description: null, + feature_image_caption: null, + }, + previous: { + status: 'draft', + updated_at: '2023-07-24T14:05:10.000Z', + published_at: null, + }, + }, + }, +}); diff --git a/packages/pieces/community/ghostcms/tsconfig.json b/packages/pieces/community/ghostcms/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/ghostcms/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/ghostcms/tsconfig.lib.json b/packages/pieces/community/ghostcms/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/ghostcms/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/gistly/.eslintrc.json b/packages/pieces/community/gistly/.eslintrc.json new file mode 100644 index 0000000..19a5cf9 --- /dev/null +++ b/packages/pieces/community/gistly/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/gistly/package.json b/packages/pieces/community/gistly/package.json new file mode 100644 index 0000000..66c6b39 --- /dev/null +++ b/packages/pieces/community/gistly/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-gistly", + "version": "0.0.1" +} \ No newline at end of file diff --git a/packages/pieces/community/gistly/project.json b/packages/pieces/community/gistly/project.json new file mode 100644 index 0000000..9322ce7 --- /dev/null +++ b/packages/pieces/community/gistly/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-gistly", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/gistly/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/gistly", + "tsConfig": "packages/pieces/community/gistly/tsconfig.lib.json", + "packageJson": "packages/pieces/community/gistly/package.json", + "main": "packages/pieces/community/gistly/src/index.ts", + "assets": [ + "packages/pieces/community/gistly/*.md", + { + "input": "packages/pieces/community/gistly/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-gistly {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/gistly/src/index.ts b/packages/pieces/community/gistly/src/index.ts new file mode 100644 index 0000000..1ce78d7 --- /dev/null +++ b/packages/pieces/community/gistly/src/index.ts @@ -0,0 +1,46 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { getTranscriptAction } from './lib/actions/get-transcript'; +import { PieceCategory } from '@activepieces/shared'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { gistlyConfig } from './lib/config'; + +const markdownDescription = ` +To obtain your free Gistly API Key, sign up at [Gistly](https://gist.ly/youtube-transcript-api) and then copy the key available in the [dashboard](https://api-portal.gist.ly/). +`; + +export const gistlyAuth = PieceAuth.SecretText({ + description: markdownDescription, + displayName: 'API Key', + required: true, + validate: async (auth) => { + try { + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${gistlyConfig.baseUrl}/health`, + headers: { + [gistlyConfig.accessTokenHeaderKey]: auth.auth, + }, + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API Key.', + }; + } + }, +}); + +export const gistly = createPiece({ + displayName: 'Gistly', + auth: gistlyAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/gistly.svg', + authors: ['rafalzawadzki'], + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE, PieceCategory.DEVELOPER_TOOLS, PieceCategory.CONTENT_AND_FILES], + description: 'YouTube Transcripts', + actions: [getTranscriptAction], + triggers: [], +}); \ No newline at end of file diff --git a/packages/pieces/community/gistly/src/lib/actions/get-transcript.ts b/packages/pieces/community/gistly/src/lib/actions/get-transcript.ts new file mode 100644 index 0000000..7847f13 --- /dev/null +++ b/packages/pieces/community/gistly/src/lib/actions/get-transcript.ts @@ -0,0 +1,39 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { gistlyAuth } from '../..'; +import { gistlyConfig } from '../config'; + +export const getTranscriptAction = createAction({ + name: 'get_transcript', + displayName: 'Get Transcript', + description: 'Fetches transcript of a YouTube video.', + auth: gistlyAuth, + props: { + url: Property.ShortText({ + displayName: 'YouTube URL', + required: true, + }), + text: Property.Checkbox({ + displayName: 'Merge Text', + description: 'If true, the transcript will be merged into a single text instead of timestamped chunks.', + required: false, + defaultValue: true, + }), + }, + async run(context) { + const { url, text } = context.propsValue; + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${gistlyConfig.baseUrl}/youtube/transcript`, + headers: { + [gistlyConfig.accessTokenHeaderKey]: context.auth, + }, + queryParams: { + url, + text: text ? 'true' : 'false', + }, + }); + + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/gistly/src/lib/config.ts b/packages/pieces/community/gistly/src/lib/config.ts new file mode 100644 index 0000000..836da1c --- /dev/null +++ b/packages/pieces/community/gistly/src/lib/config.ts @@ -0,0 +1,4 @@ +export const gistlyConfig = { + baseUrl: 'https://api.gist.ly', + accessTokenHeaderKey: 'x-api-key', +}; diff --git a/packages/pieces/community/gistly/tsconfig.json b/packages/pieces/community/gistly/tsconfig.json new file mode 100644 index 0000000..18bb11e --- /dev/null +++ b/packages/pieces/community/gistly/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/gistly/tsconfig.lib.json b/packages/pieces/community/gistly/tsconfig.lib.json new file mode 100644 index 0000000..e79ef0f --- /dev/null +++ b/packages/pieces/community/gistly/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} \ No newline at end of file diff --git a/packages/pieces/community/github/.babelrc b/packages/pieces/community/github/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/github/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/github/.eslintrc.json b/packages/pieces/community/github/.eslintrc.json new file mode 100644 index 0000000..d7449bb --- /dev/null +++ b/packages/pieces/community/github/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {}, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/github/README.md b/packages/pieces/community/github/README.md new file mode 100644 index 0000000..d04ab9d --- /dev/null +++ b/packages/pieces/community/github/README.md @@ -0,0 +1,7 @@ +# pieces-github + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-github` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/github/package.json b/packages/pieces/community/github/package.json new file mode 100644 index 0000000..e0bb609 --- /dev/null +++ b/packages/pieces/community/github/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-github", + "version": "0.5.2" +} diff --git a/packages/pieces/community/github/project.json b/packages/pieces/community/github/project.json new file mode 100644 index 0000000..58ddec4 --- /dev/null +++ b/packages/pieces/community/github/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-github", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/github/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/github", + "tsConfig": "packages/pieces/community/github/tsconfig.lib.json", + "packageJson": "packages/pieces/community/github/package.json", + "main": "packages/pieces/community/github/src/index.ts", + "assets": [ + "packages/pieces/community/github/*.md", + { + "input": "packages/pieces/community/github/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/github/src/index.ts b/packages/pieces/community/github/src/index.ts new file mode 100644 index 0000000..91b9e03 --- /dev/null +++ b/packages/pieces/community/github/src/index.ts @@ -0,0 +1,62 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { githubCreateIssueAction } from './lib/actions/create-issue'; +import { githubUnlockIssueAction } from './lib/actions/unlock-issue'; +import { githubTriggers } from './lib/trigger'; +import { githubGetIssueInformation } from './lib/actions/get-issue-information'; +import { githubCreateCommentOnAIssue } from './lib/actions/create-comment-on-a-issue'; +import { githubLockIssueAction } from './lib/actions/lock-issue'; +import { githubRawGraphqlQuery } from './lib/actions/raw-graphql-query'; +import { githubCreatePullRequestReviewCommentAction } from './lib/actions/create-pull-request-review-comment'; +import { githubCreateCommitCommentAction } from './lib/actions/create-commit-comment'; +import { githubCreateDiscussionCommentAction } from './lib/actions/create-discussion-comment'; + +export const githubAuth = PieceAuth.OAuth2({ + required: true, + authUrl: 'https://github.com/login/oauth/authorize', + tokenUrl: 'https://github.com/login/oauth/access_token', + scope: ['admin:repo_hook', 'admin:org', 'repo'], +}); + +export const github = createPiece({ + displayName: 'GitHub', + description: + 'Developer platform that allows developers to create, store, manage and share their code', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/github.png', + categories: [PieceCategory.DEVELOPER_TOOLS], + auth: githubAuth, + actions: [ + githubCreateIssueAction, + githubGetIssueInformation, + githubCreateCommentOnAIssue, + githubLockIssueAction, + githubUnlockIssueAction, + githubRawGraphqlQuery, + githubCreatePullRequestReviewCommentAction, + githubCreateCommitCommentAction, + githubCreateDiscussionCommentAction, + createCustomApiCallAction({ + baseUrl: () => 'https://api.github.com', + auth: githubAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + authors: [ + 'kishanprmr', + 'MoShizzle', + 'AbdulTheActivePiecer', + 'khaledmashaly', + 'abuaboud', + 'tintinthedev', + ], + triggers: githubTriggers, +}); diff --git a/packages/pieces/community/github/src/lib/actions/create-comment-on-a-issue.ts b/packages/pieces/community/github/src/lib/actions/create-comment-on-a-issue.ts new file mode 100644 index 0000000..6572617 --- /dev/null +++ b/packages/pieces/community/github/src/lib/actions/create-comment-on-a-issue.ts @@ -0,0 +1,40 @@ +import { githubAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { githubApiCall, githubCommon } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const githubCreateCommentOnAIssue = createAction({ + auth: githubAuth, + name: 'createCommentOnAIssue', + displayName: 'Create comment on a issue', + description: + 'Adds a comment to the specified issue (also works with pull requests)', + props: { + repository: githubCommon.repositoryDropdown, + issue_number: Property.Number({ + displayName: 'Issue number', + description: 'The number of the issue to comment on', + required: true, + }), + comment: Property.LongText({ + displayName: 'Comment', + description: 'The comment to add to the issue', + required: true, + }), + }, + async run({ auth, propsValue }) { + const issue_number = propsValue.issue_number; + const { owner, repo } = propsValue.repository!; + + const response = await githubApiCall({ + accessToken: auth.access_token, + method: HttpMethod.POST, + resourceUri: `/repos/${owner}/${repo}/issues/${issue_number}/comments`, + body: { + body: propsValue.comment, + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/github/src/lib/actions/create-commit-comment.ts b/packages/pieces/community/github/src/lib/actions/create-commit-comment.ts new file mode 100644 index 0000000..0543879 --- /dev/null +++ b/packages/pieces/community/github/src/lib/actions/create-commit-comment.ts @@ -0,0 +1,59 @@ +import { githubAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { githubApiCall, githubCommon } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const githubCreateCommitCommentAction = createAction({ + auth: githubAuth, + name: 'github_create_commit_comment', + displayName: 'Create Commit Comment', + description: 'Creates a comment on a commit in a GitHub repository', + props: { + repository: githubCommon.repositoryDropdown, + sha: Property.ShortText({ + displayName: 'Commit SHA', + description: 'The SHA of the commit to comment on', + required: true, + }), + body: Property.LongText({ + displayName: 'Comment Body', + description: 'The content of the comment', + required: true, + }), + path: Property.ShortText({ + displayName: 'File Path', + description: 'The relative path to the file to comment on (optional)', + required: false, + }), + position: Property.Number({ + displayName: 'Position', + description: 'The line index in the diff to comment on (optional)', + required: false, + }), + }, + async run({ auth, propsValue }) { + const { sha, body, path, position } = propsValue; + const { owner, repo } = propsValue.repository!; + + const commentData: Record = { + body, + }; + + if (path) { + commentData.path = path; + } + + if (position !== undefined) { + commentData.position = position; + } + + const response = await githubApiCall({ + accessToken: auth.access_token, + method: HttpMethod.POST, + resourceUri: `/repos/${owner}/${repo}/commits/${sha}/comments`, + body: commentData, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/github/src/lib/actions/create-discussion-comment.ts b/packages/pieces/community/github/src/lib/actions/create-discussion-comment.ts new file mode 100644 index 0000000..33b0dd2 --- /dev/null +++ b/packages/pieces/community/github/src/lib/actions/create-discussion-comment.ts @@ -0,0 +1,57 @@ +import { githubAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { githubApiCall, githubCommon } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const githubCreateDiscussionCommentAction = createAction({ + auth: githubAuth, + name: 'github_create_discussion_comment', + displayName: 'Create Discussion Comment', + description: 'Creates a comment on a discussion in a GitHub repository', + props: { + repository: githubCommon.repositoryDropdown, + discussion_number: Property.Number({ + displayName: 'Discussion Number', + description: 'The number of the discussion to comment on', + required: true, + }), + body: Property.LongText({ + displayName: 'Comment Body', + description: 'The content of the comment (supports markdown)', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { discussion_number, body } = propsValue; + const { owner, repo } = propsValue.repository!; + + // GitHub Discussions API requires GraphQL for most operations + const query = ` + mutation AddDiscussionComment { + addDiscussionComment(input: { + discussionId: "${discussion_number}", + repositoryName: "${repo}", + repositoryOwner: "${owner}", + body: "${body.replace(/"/g, '\\"')}" + }) { + comment { + id + body + createdAt + url + } + } + }`; + + const response = await githubApiCall({ + accessToken: auth.access_token, + method: HttpMethod.POST, + resourceUri: '/graphql', + body: { + query, + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/github/src/lib/actions/create-issue.ts b/packages/pieces/community/github/src/lib/actions/create-issue.ts new file mode 100644 index 0000000..aac66f0 --- /dev/null +++ b/packages/pieces/community/github/src/lib/actions/create-issue.ts @@ -0,0 +1,52 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { githubAuth } from '../../'; +import { githubApiCall, githubCommon } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const githubCreateIssueAction = createAction({ + auth: githubAuth, + name: 'github_create_issue', + displayName: 'Create Issue', + description: 'Create Issue in GitHub Repository', + props: { + repository: githubCommon.repositoryDropdown, + title: Property.ShortText({ + displayName: 'Title', + description: 'The title of the issue', + required: true, + }), + description: Property.LongText({ + displayName: 'Description', + description: 'The description of the issue', + required: false, + }), + labels: githubCommon.labelDropDown(), + assignees: githubCommon.assigneeDropDown(), + }, + async run({ auth, propsValue }) { + const { title, assignees, labels, description } = propsValue; + const { owner, repo } = propsValue.repository!; + + const issueFields: Record = { + title, + body: description, + }; + + if (labels) { + issueFields['labels'] = labels; + } + + if (assignees) { + issueFields['assignees'] = assignees; + } + + const response = await githubApiCall({ + accessToken: auth.access_token, + method: HttpMethod.POST, + resourceUri: `/repos/${owner}/${repo}/issues`, + body: issueFields, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/github/src/lib/actions/create-pull-request-review-comment.ts b/packages/pieces/community/github/src/lib/actions/create-pull-request-review-comment.ts new file mode 100644 index 0000000..5f096ab --- /dev/null +++ b/packages/pieces/community/github/src/lib/actions/create-pull-request-review-comment.ts @@ -0,0 +1,59 @@ +import { githubAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { githubApiCall, githubCommon } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const githubCreatePullRequestReviewCommentAction = createAction({ + auth: githubAuth, + name: 'github_create_pull_request_review_comment', + displayName: 'Create Pull Request Review Comment', + description: + 'Creates a review comment on a pull request in a GitHub repository', + props: { + repository: githubCommon.repositoryDropdown, + pull_number: Property.Number({ + displayName: 'Pull Request Number', + description: 'The number of the pull request', + required: true, + }), + commit_id: Property.ShortText({ + displayName: 'Commit SHA', + description: 'The SHA of the commit to comment on', + required: true, + }), + path: Property.ShortText({ + displayName: 'File Path', + description: 'The relative path to the file to comment on', + required: true, + }), + body: Property.LongText({ + displayName: 'Comment Body', + description: 'The content of the review comment', + required: true, + }), + position: Property.Number({ + displayName: 'Position', + description: + 'The position in the diff where the comment should be placed', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { pull_number, commit_id, path, body, position } = propsValue; + const { owner, repo } = propsValue.repository!; + + const response = await githubApiCall({ + accessToken: auth.access_token, + method: HttpMethod.POST, + resourceUri: `/repos/${owner}/${repo}/pulls/${pull_number}/comments`, + body: { + commit_id, + path, + body, + position, + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/github/src/lib/actions/get-issue-information.ts b/packages/pieces/community/github/src/lib/actions/get-issue-information.ts new file mode 100644 index 0000000..e2a9d89 --- /dev/null +++ b/packages/pieces/community/github/src/lib/actions/get-issue-information.ts @@ -0,0 +1,31 @@ +import { githubAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { githubApiCall, githubCommon } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const githubGetIssueInformation = createAction({ + auth: githubAuth, + name: 'getIssueInformation', + displayName: 'Get issue information', + description: 'Grabs information from a specific issue', + props: { + repository: githubCommon.repositoryDropdown, + issue_number: Property.Number({ + displayName: 'Issue Number', + description: 'The number of the issue you want to get information from', + required: true, + }), + }, + async run({ auth, propsValue }) { + const issue_number = propsValue.issue_number; + const { owner, repo } = propsValue.repository!; + + const response = await githubApiCall({ + accessToken: auth.access_token, + method: HttpMethod.GET, + resourceUri: `/repos/${owner}/${repo}/issues/${issue_number}`, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/github/src/lib/actions/lock-issue.ts b/packages/pieces/community/github/src/lib/actions/lock-issue.ts new file mode 100644 index 0000000..0d3ae24 --- /dev/null +++ b/packages/pieces/community/github/src/lib/actions/lock-issue.ts @@ -0,0 +1,51 @@ +import { githubAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { githubApiCall, githubCommon } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const githubLockIssueAction = createAction({ + auth: githubAuth, + name: 'lockIssue', + displayName: 'Lock issue', + description: 'Locks the specified issue', + props: { + repository: githubCommon.repositoryDropdown, + issue_number: Property.Number({ + displayName: 'Issue Number', + description: 'The number of the issue to be locked', + required: true, + }), + lock_reason: Property.Dropdown< + 'off-topic' | 'too heated' | 'resolved' | 'spam' | undefined + >({ + displayName: 'Lock Reason', + description: 'The reason for locking the issue', + required: false, + refreshers: [], + options: async () => { + return { + options: [ + { value: 'off-topic', label: 'Off-topic' }, + { value: 'too heated', label: 'Too heated' }, + { value: 'resolved', label: 'Resolved' }, + { value: 'spam', label: 'Spam' }, + ], + }; + }, + }), + }, + async run({ auth, propsValue }) { + const { issue_number, lock_reason } = propsValue; + const { owner, repo } = propsValue.repository!; + + const response = await githubApiCall({ + accessToken: auth.access_token, + method: HttpMethod.PUT, + resourceUri: `/repos/${owner}/${repo}/issues/${issue_number}/lock`, + body: { + lock_reason, + }, + }); + return response; + }, +}); diff --git a/packages/pieces/community/github/src/lib/actions/raw-graphql-query.ts b/packages/pieces/community/github/src/lib/actions/raw-graphql-query.ts new file mode 100644 index 0000000..9684459 --- /dev/null +++ b/packages/pieces/community/github/src/lib/actions/raw-graphql-query.ts @@ -0,0 +1,35 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { githubAuth } from '../..'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; + +export const githubRawGraphqlQuery = createAction({ + name: 'rawGraphqlQuery', + displayName: 'Raw GraphQL query', + description: 'Perform a raw GraphQL query', + auth: githubAuth, + props: { + query: Property.LongText({ displayName: 'Query', required: true }), + variables: Property.Object({ displayName: 'Parameters', required: false }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + url: 'https://api.github.com/graphql', + method: HttpMethod.POST, + body: JSON.stringify({ + query: propsValue.query, + variables: propsValue.variables, + }), + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/github/src/lib/actions/unlock-issue.ts b/packages/pieces/community/github/src/lib/actions/unlock-issue.ts new file mode 100644 index 0000000..3859f30 --- /dev/null +++ b/packages/pieces/community/github/src/lib/actions/unlock-issue.ts @@ -0,0 +1,31 @@ +import { githubAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { githubApiCall, githubCommon } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const githubUnlockIssueAction = createAction({ + auth: githubAuth, + name: 'unlockIssue', + displayName: 'Unlock issue', + description: 'Unlocks the specified issue', + props: { + repository: githubCommon.repositoryDropdown, + issue_number: Property.Number({ + displayName: 'Issue Number', + description: 'The number of the issue to be unlocked', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { issue_number } = propsValue; + const { owner, repo } = propsValue.repository!; + + const response = await githubApiCall({ + accessToken: auth.access_token, + method: HttpMethod.DELETE, + resourceUri: `/repos/${owner}/${repo}/issues/${issue_number}/lock`, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/github/src/lib/common/index.ts b/packages/pieces/community/github/src/lib/common/index.ts new file mode 100644 index 0000000..8f2b9b9 --- /dev/null +++ b/packages/pieces/community/github/src/lib/common/index.ts @@ -0,0 +1,222 @@ +import { + AuthenticationType, + httpClient, + HttpMessageBody, + HttpMethod, + HttpRequest, + HttpResponse, + QueryParams, +} from '@activepieces/pieces-common'; +import { Property, OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { isNil } from '@activepieces/shared'; + +export const githubCommon = { + baseUrl: 'https://api.github.com', + repositoryDropdown: Property.Dropdown<{ repo: string; owner: string }>({ + displayName: 'Repository', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'please authenticate first', + }; + } + const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue; + const repositories = await getUserRepo(authProp); + return { + disabled: false, + options: repositories.map((repo) => { + return { + label: repo.owner.login + '/' + repo.name, + value: { + owner: repo.owner.login, + repo: repo.name, + }, + }; + }), + }; + }, + }), + assigneeDropDown: (required = false) => + Property.MultiSelectDropdown({ + displayName: 'Assignees', + description: 'Assignees for the Issue', + refreshers: ['repository'], + + required, + options: async ({ auth, repository }) => { + if (!auth || !repository) { + return { + disabled: true, + options: [], + placeholder: 'please authenticate first and select repo', + }; + } + const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue; + const { owner, repo } = repository as RepositoryProp; + const assignees = await getAssignee(authProp, owner, repo); + return { + disabled: false, + options: assignees.map((assignee) => { + return { + label: assignee.login, + value: assignee.login, + }; + }), + }; + }, + }), + labelDropDown: (required = false) => + Property.MultiSelectDropdown({ + displayName: 'Labels', + description: 'Labels for the Issue', + refreshers: ['repository'], + required, + options: async ({ auth, repository }) => { + if (!auth || !repository) { + return { + disabled: true, + options: [], + placeholder: 'please authenticate first and select repo', + }; + } + const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue; + const { owner, repo } = repository as RepositoryProp; + const labels = await listIssueLabels(authProp, owner, repo); + return { + disabled: false, + options: labels.map((label) => { + return { + label: label.name, + value: label.name, + }; + }), + }; + }, + }), +}; + +async function getUserRepo(authProp: OAuth2PropertyValue) { + const response = await githubPaginatedApiCall<{ + id: number; + name: string; + owner: { login: string }; + }>({ + accessToken: authProp.access_token, + method: HttpMethod.GET, + resourceUri: '/user/repos', + }); + return response; +} + +async function getAssignee( + authProp: OAuth2PropertyValue, + owner: string, + repo: string +) { + const response = await githubPaginatedApiCall<{ id: number; login: string }>({ + accessToken: authProp.access_token, + method: HttpMethod.GET, + resourceUri: `/repos/${owner}/${repo}/assignees`, + }); + return response; +} + +async function listIssueLabels( + authProp: OAuth2PropertyValue, + owner: string, + repo: string +) { + const response = await githubPaginatedApiCall<{ id: number; name: string }>({ + accessToken: authProp.access_token, + method: HttpMethod.GET, + resourceUri: `/repos/${owner}/${repo}/labels`, + }); + return response; +} + +export interface RepositoryProp { + repo: string; + owner: string; +} + +export type RequestParams = Record< + string, + string | number | string[] | undefined +>; + +export type GithubApiCallParams = { + accessToken: string; + method: HttpMethod; + resourceUri: string; + query?: RequestParams; + body?: any; +}; + +export async function githubApiCall({ + accessToken, + method, + resourceUri, + query, + body, +}: GithubApiCallParams): Promise> { + const baseUrl = 'https://api.github.com'; + const qs: QueryParams = {}; + + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + qs[key] = String(value); + } + } + } + + const request: HttpRequest = { + method, + url: baseUrl + resourceUri, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + queryParams: qs, + body, + }; + + const response = await httpClient.sendRequest(request); + return response; +} + +export async function githubPaginatedApiCall({ + accessToken, + method, + resourceUri, + query, + body, +}: GithubApiCallParams): Promise { + const qs = query ? query : {}; + + qs.page = 1; + qs.per_page = 100; + + const resultData: T[] = []; + let hasMoreItems = true; + + do { + const response = await githubApiCall({ + accessToken, + method, + resourceUri, + query: qs, + body, + }); + qs.page = qs.page + 1; + resultData.push(...response.body); + const linkHeader = response.headers?.link; + hasMoreItems = !isNil(linkHeader) && linkHeader.includes(`rel="next"`); + } while (hasMoreItems); + + return resultData; +} diff --git a/packages/pieces/community/github/src/lib/trigger/index.ts b/packages/pieces/community/github/src/lib/trigger/index.ts new file mode 100644 index 0000000..9629063 --- /dev/null +++ b/packages/pieces/community/github/src/lib/trigger/index.ts @@ -0,0 +1,1465 @@ +import { Trigger } from '@activepieces/pieces-framework'; +import { githubRegisterTrigger } from './register-trigger'; + +export enum GithubEventType { + PULL_REQUEST = 'pull_request', + STAR = 'star', + ISSUES = 'issues', + PUSH = 'push', + DISCUSSION = 'discussion', + DISCUSSION_COMMENT = 'discussion_comment', +} + +export const registered = [ + { + name: GithubEventType.PULL_REQUEST, + displayName: 'New Pull Request', + description: 'Triggers when there is activity on a pull request.', + sampleData: { + action: 'opened', + number: 2, + pull_request: { + url: 'https://api.github.com/repos/activepieces/activepieces/pulls/2', + id: 1246014943, + node_id: 'PR_kwDOCfU56M5KRK3f', + html_url: 'https://github.com/activepieces/activepieces/pull/2', + diff_url: 'https://github.com/activepieces/activepieces/pull/2.diff', + patch_url: 'https://github.com/activepieces/activepieces/pull/2.patch', + issue_url: + 'https://api.github.com/repos/activepieces/activepieces/issues/2', + number: 2, + state: 'open', + locked: false, + title: 'added', + user: { + login: 'jesska', + id: 391061, + node_id: 'MDQ6VXNlcjM5MzI2MQ==', + avatar_url: 'https://avatars.githubusercontent.com/u/393261?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/jesska', + html_url: 'https://github.com/jesska', + followers_url: 'https://api.github.com/users/jesska/followers', + following_url: + 'https://api.github.com/users/jesska/following{/other_user}', + gists_url: 'https://api.github.com/users/jesska/gists{/gist_id}', + starred_url: + 'https://api.github.com/users/jesska/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/jesska/subscriptions', + organizations_url: 'https://api.github.com/users/jesska/orgs', + repos_url: 'https://api.github.com/users/jesska/repos', + events_url: 'https://api.github.com/users/jesska/events{/privacy}', + received_events_url: + 'https://api.github.com/users/jesska/received_events', + type: 'User', + site_admin: false, + }, + body: 'test', + created_at: '2023-02-18T11:36:07Z', + updated_at: '2023-02-18T11:36:07Z', + closed_at: null, + merged_at: null, + merge_commit_sha: null, + assignee: null, + assignees: [], + requested_reviewers: [], + requested_teams: [], + labels: [], + milestone: null, + draft: false, + commits_url: + 'https://api.github.com/repos/activepieces/activepieces/pulls/2/commits', + review_comments_url: + 'https://api.github.com/repos/activepieces/activepieces/pulls/2/comments', + review_comment_url: + 'https://api.github.com/repos/activepieces/activepieces/pulls/comments{/number}', + comments_url: + 'https://api.github.com/repos/activepieces/activepieces/issues/2/comments', + statuses_url: + 'https://api.github.com/repos/activepieces/activepieces/statuses/309b7842c3c8a7cd275a4a6da1e89713917bcdc6', + head: { + label: 'kanarelo:dd', + ref: 'dd', + sha: '309b7842c3c8a7cd275a4a6da1e89713917bcdc6', + user: [Object], + repo: [Object], + }, + base: { + label: 'kanarelo:master', + ref: 'master', + sha: '3f80b96f5ba885a21b691b653731520a6000654b', + user: [Object], + repo: [Object], + }, + author_association: 'OWNER', + auto_merge: null, + active_lock_reason: null, + merged: false, + mergeable: null, + rebaseable: null, + mergeable_state: 'unknown', + merged_by: null, + comments: 0, + review_comments: 0, + maintainer_can_modify: false, + commits: 1, + additions: 1, + deletions: 0, + changed_files: 1, + }, + repository: { + id: 167066088, + node_id: 'MDEwOlJlcG9zaXRvcnkxNjcwNjYwODg=', + name: 'activepieces', + full_name: 'activepieces/activepieces', + private: false, + owner: { + login: 'jesska', + id: 393261, + node_id: 'MDQ6VXNlcjM5MzI2MQ==', + avatar_url: 'https://avatars.githubusercontent.com/u/393261?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/jesska', + html_url: 'https://github.com/jesska', + followers_url: 'https://api.github.com/users/jesska/followers', + following_url: + 'https://api.github.com/users/jesska/following{/other_user}', + gists_url: 'https://api.github.com/users/jesska/gists{/gist_id}', + starred_url: + 'https://api.github.com/users/jesska/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/jesska/subscriptions', + organizations_url: 'https://api.github.com/users/jesska/orgs', + repos_url: 'https://api.github.com/users/jesska/repos', + events_url: 'https://api.github.com/users/jesska/events{/privacy}', + received_events_url: + 'https://api.github.com/users/jesska/received_events', + type: 'User', + site_admin: false, + }, + html_url: 'https://github.com/activepieces/activepieces', + description: 'Automate!', + fork: false, + url: 'https://api.github.com/repos/activepieces/activepieces', + forks_url: + 'https://api.github.com/repos/activepieces/activepieces/forks', + keys_url: + 'https://api.github.com/repos/activepieces/activepieces/keys{/key_id}', + collaborators_url: + 'https://api.github.com/repos/activepieces/activepieces/collaborators{/collaborator}', + teams_url: + 'https://api.github.com/repos/activepieces/activepieces/teams', + hooks_url: + 'https://api.github.com/repos/activepieces/activepieces/hooks', + issue_events_url: + 'https://api.github.com/repos/activepieces/activepieces/issues/events{/number}', + events_url: + 'https://api.github.com/repos/activepieces/activepieces/events', + assignees_url: + 'https://api.github.com/repos/activepieces/activepieces/assignees{/user}', + branches_url: + 'https://api.github.com/repos/activepieces/activepieces/branches{/branch}', + tags_url: 'https://api.github.com/repos/activepieces/activepieces/tags', + blobs_url: + 'https://api.github.com/repos/activepieces/activepieces/git/blobs{/sha}', + git_tags_url: + 'https://api.github.com/repos/activepieces/activepieces/git/tags{/sha}', + git_refs_url: + 'https://api.github.com/repos/activepieces/activepieces/git/refs{/sha}', + trees_url: + 'https://api.github.com/repos/activepieces/activepieces/git/trees{/sha}', + statuses_url: + 'https://api.github.com/repos/activepieces/activepieces/statuses/{sha}', + languages_url: + 'https://api.github.com/repos/activepieces/activepieces/languages', + stargazers_url: + 'https://api.github.com/repos/activepieces/activepieces/stargazers', + contributors_url: + 'https://api.github.com/repos/activepieces/activepieces/contributors', + subscribers_url: + 'https://api.github.com/repos/activepieces/activepieces/subscribers', + subscription_url: + 'https://api.github.com/repos/activepieces/activepieces/subscription', + commits_url: + 'https://api.github.com/repos/activepieces/activepieces/commits{/sha}', + git_commits_url: + 'https://api.github.com/repos/activepieces/activepieces/git/commits{/sha}', + comments_url: + 'https://api.github.com/repos/activepieces/activepieces/comments{/number}', + issue_comment_url: + 'https://api.github.com/repos/activepieces/activepieces/issues/comments{/number}', + contents_url: + 'https://api.github.com/repos/activepieces/activepieces/contents/{+path}', + compare_url: + 'https://api.github.com/repos/activepieces/activepieces/compare/{base}...{head}', + merges_url: + 'https://api.github.com/repos/activepieces/activepieces/merges', + archive_url: + 'https://api.github.com/repos/activepieces/activepieces/{archive_format}{/ref}', + downloads_url: + 'https://api.github.com/repos/activepieces/activepieces/downloads', + issues_url: + 'https://api.github.com/repos/activepieces/activepieces/issues{/number}', + pulls_url: + 'https://api.github.com/repos/activepieces/activepieces/pulls{/number}', + milestones_url: + 'https://api.github.com/repos/activepieces/activepieces/milestones{/number}', + notifications_url: + 'https://api.github.com/repos/activepieces/activepieces/notifications{?since,all,participating}', + labels_url: + 'https://api.github.com/repos/activepieces/activepieces/labels{/name}', + releases_url: + 'https://api.github.com/repos/activepieces/activepieces/releases{/id}', + deployments_url: + 'https://api.github.com/repos/activepieces/activepieces/deployments', + created_at: '2019-01-22T20:57:01Z', + updated_at: '2023-02-18T11:05:49Z', + pushed_at: '2019-02-22T20:19:33Z', + git_url: 'git://github.com/activepieces/activepieces.git', + ssh_url: 'git@github.com/activepieces/activepieces.git', + clone_url: 'https://github.com/activepieces/activepieces.git', + svn_url: 'https://github.com/activepieces/activepieces', + homepage: null, + size: 6637, + stargazers_count: 1, + watchers_count: 1, + language: 'CSS', + has_issues: true, + has_projects: true, + has_downloads: true, + has_wiki: false, + has_pages: false, + has_discussions: false, + forks_count: 0, + mirror_url: null, + archived: false, + disabled: false, + open_issues_count: 1, + license: null, + allow_forking: true, + is_template: false, + web_commit_signoff_required: false, + topics: [], + visibility: 'public', + forks: 0, + open_issues: 1, + watchers: 1, + default_branch: 'master', + }, + sender: { + login: 'activepieces', + id: 1234, + node_id: 'MDQ6VXNlcjM5MzI2MQ==', + avatar_url: 'https://avatars.githubusercontent.com/u/1234?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/activepieces', + html_url: 'https://github.com/activepieces', + followers_url: 'https://api.github.com/users/activepieces/followers', + following_url: + 'https://api.github.com/users/activepieces/following{/other_user}', + gists_url: 'https://api.github.com/users/activepieces/gists{/gist_id}', + starred_url: + 'https://api.github.com/users/activepieces/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/activepieces/subscriptions', + organizations_url: 'https://api.github.com/users/activepieces/orgs', + repos_url: 'https://api.github.com/users/activepieces/repos', + events_url: + 'https://api.github.com/users/activepieces/events{/privacy}', + received_events_url: + 'https://api.github.com/users/activepieces/received_events', + type: 'User', + site_admin: false, + }, + }, + }, + { + name: GithubEventType.STAR, + displayName: 'New Star', + description: 'Trigger when there is activity relating to repository stars.', + sampleData: { + action: 'created', + starred_at: '2023-02-18T11:18:55Z', + repository: { + id: 167066548, + node_id: 'MDEwOlJlcG9zaXRvcnkxNjcwNjYwODg=', + name: 'csv-2-pdf-report-tool', + full_name: 'activepieces/activepieces', + private: false, + owner: { + login: 'activepieces', + id: 303261, + node_id: 'MDQ6VXNlcjM5MzI2MQ==', + avatar_url: 'https://avatars.githubusercontent.com/u/393261?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/activepieces', + html_url: 'https://github.com/activepieces', + followers_url: 'https://api.github.com/users/activepieces/followers', + following_url: + 'https://api.github.com/users/activepieces/following{/other_user}', + gists_url: + 'https://api.github.com/users/activepieces/gists{/gist_id}', + starred_url: + 'https://api.github.com/users/activepieces/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/activepieces/subscriptions', + organizations_url: 'https://api.github.com/users/activepieces/orgs', + repos_url: 'https://api.github.com/users/activepieces/repos', + events_url: + 'https://api.github.com/users/activepieces/events{/privacy}', + received_events_url: + 'https://api.github.com/users/activepieces/received_events', + type: 'User', + site_admin: false, + }, + html_url: 'https://github.com/activepieces/activepieces', + description: 'Automate', + fork: false, + url: 'https://api.github.com/repos/activepieces/activepieces', + forks_url: + 'https://api.github.com/repos/activepieces/activepieces/forks', + keys_url: + 'https://api.github.com/repos/activepieces/activepieces/keys{/key_id}', + collaborators_url: + 'https://api.github.com/repos/activepieces/activepieces/collaborators{/collaborator}', + teams_url: + 'https://api.github.com/repos/activepieces/activepieces/teams', + hooks_url: + 'https://api.github.com/repos/activepieces/activepieces/hooks', + issue_events_url: + 'https://api.github.com/repos/activepieces/activepieces/issues/events{/number}', + events_url: + 'https://api.github.com/repos/activepieces/activepieces/events', + assignees_url: + 'https://api.github.com/repos/activepieces/activepieces/assignees{/user}', + branches_url: + 'https://api.github.com/repos/activepieces/activepieces/branches{/branch}', + tags_url: 'https://api.github.com/repos/activepieces/activepieces/tags', + blobs_url: + 'https://api.github.com/repos/activepieces/activepieces/git/blobs{/sha}', + git_tags_url: + 'https://api.github.com/repos/activepieces/activepieces/git/tags{/sha}', + git_refs_url: + 'https://api.github.com/repos/activepieces/activepieces/git/refs{/sha}', + trees_url: + 'https://api.github.com/repos/activepieces/activepieces/git/trees{/sha}', + statuses_url: + 'https://api.github.com/repos/activepieces/activepieces/statuses/{sha}', + languages_url: + 'https://api.github.com/repos/activepieces/activepieces/languages', + stargazers_url: + 'https://api.github.com/repos/activepieces/activepieces/stargazers', + contributors_url: + 'https://api.github.com/repos/activepieces/activepieces/contributors', + subscribers_url: + 'https://api.github.com/repos/activepieces/activepieces/subscribers', + subscription_url: + 'https://api.github.com/repos/activepieces/activepieces/subscription', + commits_url: + 'https://api.github.com/repos/activepieces/activepieces/commits{/sha}', + git_commits_url: + 'https://api.github.com/repos/activepieces/activepieces/git/commits{/sha}', + comments_url: + 'https://api.github.com/repos/activepieces/activepieces/comments{/number}', + issue_comment_url: + 'https://api.github.com/repos/activepieces/activepieces/issues/comments{/number}', + contents_url: + 'https://api.github.com/repos/activepieces/activepieces/contents/{+path}', + compare_url: + 'https://api.github.com/repos/activepieces/activepieces/compare/{base}...{head}', + merges_url: + 'https://api.github.com/repos/activepieces/activepieces/merges', + archive_url: + 'https://api.github.com/repos/activepieces/activepieces/{archive_format}{/ref}', + downloads_url: + 'https://api.github.com/repos/activepieces/activepieces/downloads', + issues_url: + 'https://api.github.com/repos/activepieces/activepieces/issues{/number}', + pulls_url: + 'https://api.github.com/repos/activepieces/activepieces/pulls{/number}', + milestones_url: + 'https://api.github.com/repos/activepieces/activepieces/milestones{/number}', + notifications_url: + 'https://api.github.com/repos/activepieces/activepieces/notifications{?since,all,participating}', + labels_url: + 'https://api.github.com/repos/activepieces/activepieces/labels{/name}', + releases_url: + 'https://api.github.com/repos/activepieces/activepieces/releases{/id}', + deployments_url: + 'https://api.github.com/repos/activepieces/activepieces/deployments', + created_at: '2019-01-22T20:57:01Z', + updated_at: '2023-02-18T11:18:55Z', + pushed_at: '2019-02-22T20:19:33Z', + git_url: 'git://github.com/activepieces/activepieces.git', + ssh_url: 'git@github.com:activepieces/activepieces.git', + clone_url: 'https://github.com/activepieces/activepieces.git', + svn_url: 'https://github.com/activepieces/activepieces', + homepage: null, + size: 6637, + stargazers_count: 1, + watchers_count: 1, + language: 'CSS', + has_issues: true, + has_projects: true, + has_downloads: true, + has_wiki: false, + has_pages: false, + has_discussions: false, + forks_count: 0, + mirror_url: null, + archived: false, + disabled: false, + open_issues_count: 1, + license: null, + allow_forking: true, + is_template: false, + web_commit_signoff_required: false, + topics: [], + visibility: 'public', + forks: 0, + open_issues: 1, + watchers: 1, + default_branch: 'master', + }, + sender: { + login: 'activepieces', + id: 1234, + node_id: 'MDQ6VXNlcjM5MzI2MQ==', + avatar_url: 'https://avatars.githubusercontent.com/u/1234?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/activepieces', + html_url: 'https://github.com/activepieces', + followers_url: 'https://api.github.com/users/activepieces/followers', + following_url: + 'https://api.github.com/users/activepieces/following{/other_user}', + gists_url: 'https://api.github.com/users/activepieces/gists{/gist_id}', + starred_url: + 'https://api.github.com/users/activepieces/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/activepieces/subscriptions', + organizations_url: 'https://api.github.com/users/activepieces/orgs', + repos_url: 'https://api.github.com/users/activepieces/repos', + events_url: + 'https://api.github.com/users/activepieces/events{/privacy}', + received_events_url: + 'https://api.github.com/users/activepieces/received_events', + type: 'User', + site_admin: false, + }, + }, + }, + { + name: GithubEventType.ISSUES, + displayName: 'New Issue', + description: 'Triggers when there is activity relating to an issue.', + sampleData: { + action: 'opened', + issue: { + url: 'https://api.github.com/repos/activepieces/activepieces/issues/1', + repository_url: + 'https://api.github.com/repos/activepieces/activepieces', + labels_url: + 'https://api.github.com/repos/activepieces/activepieces/issues/1/labels{/name}', + comments_url: + 'https://api.github.com/repos/activepieces/activepieces/issues/1/comments', + events_url: + 'https://api.github.com/repos/activepieces/activepieces/issues/1/events', + html_url: 'https://github.com/activepieces/activepieces/issues/1', + id: 1590311655, + node_id: 'I_kwDOCfU56M5eyjrn', + number: 1, + title: 'New Issue', + user: { + login: 'jesska', + id: 391061, + node_id: 'MDQ6VXNlcjM5MzI2MQ==', + avatar_url: 'https://avatars.githubusercontent.com/u/393261?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/jesska', + html_url: 'https://github.com/jesska', + followers_url: 'https://api.github.com/users/jesska/followers', + following_url: + 'https://api.github.com/users/jesska/following{/other_user}', + gists_url: 'https://api.github.com/users/jesska/gists{/gist_id}', + starred_url: + 'https://api.github.com/users/jesska/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/jesska/subscriptions', + organizations_url: 'https://api.github.com/users/jesska/orgs', + repos_url: 'https://api.github.com/users/jesska/repos', + events_url: 'https://api.github.com/users/jesska/events{/privacy}', + received_events_url: + 'https://api.github.com/users/jesska/received_events', + type: 'User', + site_admin: false, + }, + labels: [], + state: 'open', + locked: false, + assignee: null, + assignees: [], + milestone: null, + comments: 0, + created_at: '2023-02-18T11:07:40Z', + updated_at: '2023-02-18T11:07:40Z', + closed_at: null, + author_association: 'OWNER', + active_lock_reason: null, + body: 'Test', + reactions: { + url: 'https://api.github.com/repos/activepieces/activepieces/issues/1/reactions', + total_count: 0, + '+1': 0, + '-1': 0, + laugh: 0, + hooray: 0, + confused: 0, + heart: 0, + rocket: 0, + eyes: 0, + }, + timeline_url: + 'https://api.github.com/repos/activepieces/activepieces/issues/1/timeline', + performed_via_github_app: null, + state_reason: null, + }, + repository: { + id: 167066088, + node_id: 'MDEwOlJlcG9zaXRvcnkxNjcwNjYwODg=', + name: 'activepieces', + full_name: 'activepieces/activepieces', + private: false, + owner: { + login: 'jesska', + id: 393261, + node_id: 'MDQ6VXNlcjM5MzI2MQ==', + avatar_url: 'https://avatars.githubusercontent.com/u/393261?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/jesska', + html_url: 'https://github.com/jesska', + followers_url: 'https://api.github.com/users/jesska/followers', + following_url: + 'https://api.github.com/users/jesska/following{/other_user}', + gists_url: 'https://api.github.com/users/jesska/gists{/gist_id}', + starred_url: + 'https://api.github.com/users/jesska/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/jesska/subscriptions', + organizations_url: 'https://api.github.com/users/jesska/orgs', + repos_url: 'https://api.github.com/users/jesska/repos', + events_url: 'https://api.github.com/users/jesska/events{/privacy}', + received_events_url: + 'https://api.github.com/users/jesska/received_events', + type: 'User', + site_admin: false, + }, + html_url: 'https://github.com/activepieces/activepieces', + description: 'Automate!', + fork: false, + url: 'https://api.github.com/repos/activepieces/activepieces', + forks_url: + 'https://api.github.com/repos/activepieces/activepieces/forks', + keys_url: + 'https://api.github.com/repos/activepieces/activepieces/keys{/key_id}', + collaborators_url: + 'https://api.github.com/repos/activepieces/activepieces/collaborators{/collaborator}', + teams_url: + 'https://api.github.com/repos/activepieces/activepieces/teams', + hooks_url: + 'https://api.github.com/repos/activepieces/activepieces/hooks', + issue_events_url: + 'https://api.github.com/repos/activepieces/activepieces/issues/events{/number}', + events_url: + 'https://api.github.com/repos/activepieces/activepieces/events', + assignees_url: + 'https://api.github.com/repos/activepieces/activepieces/assignees{/user}', + branches_url: + 'https://api.github.com/repos/activepieces/activepieces/branches{/branch}', + tags_url: 'https://api.github.com/repos/activepieces/activepieces/tags', + blobs_url: + 'https://api.github.com/repos/activepieces/activepieces/git/blobs{/sha}', + git_tags_url: + 'https://api.github.com/repos/activepieces/activepieces/git/tags{/sha}', + git_refs_url: + 'https://api.github.com/repos/activepieces/activepieces/git/refs{/sha}', + trees_url: + 'https://api.github.com/repos/activepieces/activepieces/git/trees{/sha}', + statuses_url: + 'https://api.github.com/repos/activepieces/activepieces/statuses/{sha}', + languages_url: + 'https://api.github.com/repos/activepieces/activepieces/languages', + stargazers_url: + 'https://api.github.com/repos/activepieces/activepieces/stargazers', + contributors_url: + 'https://api.github.com/repos/activepieces/activepieces/contributors', + subscribers_url: + 'https://api.github.com/repos/activepieces/activepieces/subscribers', + subscription_url: + 'https://api.github.com/repos/activepieces/activepieces/subscription', + commits_url: + 'https://api.github.com/repos/activepieces/activepieces/commits{/sha}', + git_commits_url: + 'https://api.github.com/repos/activepieces/activepieces/git/commits{/sha}', + comments_url: + 'https://api.github.com/repos/activepieces/activepieces/comments{/number}', + issue_comment_url: + 'https://api.github.com/repos/activepieces/activepieces/issues/comments{/number}', + contents_url: + 'https://api.github.com/repos/activepieces/activepieces/contents/{+path}', + compare_url: + 'https://api.github.com/repos/activepieces/activepieces/compare/{base}...{head}', + merges_url: + 'https://api.github.com/repos/activepieces/activepieces/merges', + archive_url: + 'https://api.github.com/repos/activepieces/activepieces/{archive_format}{/ref}', + downloads_url: + 'https://api.github.com/repos/activepieces/activepieces/downloads', + issues_url: + 'https://api.github.com/repos/activepieces/activepieces/issues{/number}', + pulls_url: + 'https://api.github.com/repos/activepieces/activepieces/pulls{/number}', + milestones_url: + 'https://api.github.com/repos/activepieces/activepieces/milestones{/number}', + notifications_url: + 'https://api.github.com/repos/activepieces/activepieces/notifications{?since,all,participating}', + labels_url: + 'https://api.github.com/repos/activepieces/activepieces/labels{/name}', + releases_url: + 'https://api.github.com/repos/activepieces/activepieces/releases{/id}', + deployments_url: + 'https://api.github.com/repos/activepieces/activepieces/deployments', + created_at: '2019-01-22T20:57:01Z', + updated_at: '2023-02-18T11:05:49Z', + pushed_at: '2019-02-22T20:19:33Z', + git_url: 'git://github.com/activepieces/activepieces.git', + ssh_url: 'git@github.com/activepieces/activepieces.git', + clone_url: 'https://github.com/activepieces/activepieces.git', + svn_url: 'https://github.com/activepieces/activepieces', + homepage: null, + size: 6637, + stargazers_count: 1, + watchers_count: 1, + language: 'CSS', + has_issues: true, + has_projects: true, + has_downloads: true, + has_wiki: false, + has_pages: false, + has_discussions: false, + forks_count: 0, + mirror_url: null, + archived: false, + disabled: false, + open_issues_count: 1, + license: null, + allow_forking: true, + is_template: false, + web_commit_signoff_required: false, + topics: [], + visibility: 'public', + forks: 0, + open_issues: 1, + watchers: 1, + default_branch: 'master', + }, + sender: { + login: 'activepieces', + id: 1234, + node_id: 'MDQ6VXNlcjM5MzI2MQ==', + avatar_url: 'https://avatars.githubusercontent.com/u/1234?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/activepieces', + html_url: 'https://github.com/activepieces', + followers_url: 'https://api.github.com/users/activepieces/followers', + following_url: + 'https://api.github.com/users/activepieces/following{/other_user}', + gists_url: 'https://api.github.com/users/activepieces/gists{/gist_id}', + starred_url: + 'https://api.github.com/users/activepieces/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/activepieces/subscriptions', + organizations_url: 'https://api.github.com/users/activepieces/orgs', + repos_url: 'https://api.github.com/users/activepieces/repos', + events_url: + 'https://api.github.com/users/activepieces/events{/privacy}', + received_events_url: + 'https://api.github.com/users/activepieces/received_events', + type: 'User', + site_admin: false, + }, + }, + }, + { + name: GithubEventType.PUSH, + displayName: 'Push', + description: + 'Triggers when there is a push to a repository branch. This includes when a commit is pushed, when a commit tag is pushed, when a branch is deleted, when a tag is deleted, or when a repository is created from a template.', + sampleData: { + after: 'sha1', + base_ref: 'main', + before: 'sha1', + commits: [ + { + added: ['file1'], + author: { + username: 'Username', + name: 'Full Name', + email: 'user@github.com', + }, + committer: { + username: 'Username', + name: 'Full Name', + email: 'user@github.com', + }, + distinct: true, + id: '', + message: 'commit message', + modified: ['file2'], + removed: ['file3'], + timestamp: '', + tree_id: '', + url: '', + }, + ], + compare: 'url', + created: true, + deleted: false, + enterprise: {}, + forced: false, + head_commit: {}, + installation: {}, + organization: {}, + pusher: { + username: 'Username', + name: 'Full Name', + email: 'user@github.com', + }, + ref: 'main', + repository: { + id: 167066088, + node_id: 'MDEwOlJlcG9zaXRvcnkxNjcwNjYwODg=', + name: 'activepieces', + full_name: 'activepieces/activepieces', + private: false, + owner: { + login: 'jesska', + id: 393261, + node_id: 'MDQ6VXNlcjM5MzI2MQ==', + avatar_url: 'https://avatars.githubusercontent.com/u/393261?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/jesska', + html_url: 'https://github.com/jesska', + followers_url: 'https://api.github.com/users/jesska/followers', + following_url: + 'https://api.github.com/users/jesska/following{/other_user}', + gists_url: 'https://api.github.com/users/jesska/gists{/gist_id}', + starred_url: + 'https://api.github.com/users/jesska/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/jesska/subscriptions', + organizations_url: 'https://api.github.com/users/jesska/orgs', + repos_url: 'https://api.github.com/users/jesska/repos', + events_url: 'https://api.github.com/users/jesska/events{/privacy}', + received_events_url: + 'https://api.github.com/users/jesska/received_events', + type: 'User', + site_admin: false, + }, + html_url: 'https://github.com/activepieces/activepieces', + description: 'Automate!', + fork: false, + url: 'https://api.github.com/repos/activepieces/activepieces', + forks_url: + 'https://api.github.com/repos/activepieces/activepieces/forks', + keys_url: + 'https://api.github.com/repos/activepieces/activepieces/keys{/key_id}', + collaborators_url: + 'https://api.github.com/repos/activepieces/activepieces/collaborators{/collaborator}', + teams_url: + 'https://api.github.com/repos/activepieces/activepieces/teams', + hooks_url: + 'https://api.github.com/repos/activepieces/activepieces/hooks', + issue_events_url: + 'https://api.github.com/repos/activepieces/activepieces/issues/events{/number}', + events_url: + 'https://api.github.com/repos/activepieces/activepieces/events', + assignees_url: + 'https://api.github.com/repos/activepieces/activepieces/assignees{/user}', + branches_url: + 'https://api.github.com/repos/activepieces/activepieces/branches{/branch}', + tags_url: 'https://api.github.com/repos/activepieces/activepieces/tags', + blobs_url: + 'https://api.github.com/repos/activepieces/activepieces/git/blobs{/sha}', + git_tags_url: + 'https://api.github.com/repos/activepieces/activepieces/git/tags{/sha}', + git_refs_url: + 'https://api.github.com/repos/activepieces/activepieces/git/refs{/sha}', + trees_url: + 'https://api.github.com/repos/activepieces/activepieces/git/trees{/sha}', + statuses_url: + 'https://api.github.com/repos/activepieces/activepieces/statuses/{sha}', + languages_url: + 'https://api.github.com/repos/activepieces/activepieces/languages', + stargazers_url: + 'https://api.github.com/repos/activepieces/activepieces/stargazers', + contributors_url: + 'https://api.github.com/repos/activepieces/activepieces/contributors', + subscribers_url: + 'https://api.github.com/repos/activepieces/activepieces/subscribers', + subscription_url: + 'https://api.github.com/repos/activepieces/activepieces/subscription', + commits_url: + 'https://api.github.com/repos/activepieces/activepieces/commits{/sha}', + git_commits_url: + 'https://api.github.com/repos/activepieces/activepieces/git/commits{/sha}', + comments_url: + 'https://api.github.com/repos/activepieces/activepieces/comments{/number}', + issue_comment_url: + 'https://api.github.com/repos/activepieces/activepieces/issues/comments{/number}', + contents_url: + 'https://api.github.com/repos/activepieces/activepieces/contents/{+path}', + compare_url: + 'https://api.github.com/repos/activepieces/activepieces/compare/{base}...{head}', + merges_url: + 'https://api.github.com/repos/activepieces/activepieces/merges', + archive_url: + 'https://api.github.com/repos/activepieces/activepieces/{archive_format}{/ref}', + downloads_url: + 'https://api.github.com/repos/activepieces/activepieces/downloads', + issues_url: + 'https://api.github.com/repos/activepieces/activepieces/issues{/number}', + pulls_url: + 'https://api.github.com/repos/activepieces/activepieces/pulls{/number}', + milestones_url: + 'https://api.github.com/repos/activepieces/activepieces/milestones{/number}', + notifications_url: + 'https://api.github.com/repos/activepieces/activepieces/notifications{?since,all,participating}', + labels_url: + 'https://api.github.com/repos/activepieces/activepieces/labels{/name}', + releases_url: + 'https://api.github.com/repos/activepieces/activepieces/releases{/id}', + deployments_url: + 'https://api.github.com/repos/activepieces/activepieces/deployments', + created_at: '2019-01-22T20:57:01Z', + updated_at: '2023-02-18T11:05:49Z', + pushed_at: '2019-02-22T20:19:33Z', + git_url: 'git://github.com/activepieces/activepieces.git', + ssh_url: 'git@github.com/activepieces/activepieces.git', + clone_url: 'https://github.com/activepieces/activepieces.git', + svn_url: 'https://github.com/activepieces/activepieces', + homepage: null, + size: 6637, + stargazers_count: 1, + watchers_count: 1, + language: 'CSS', + has_issues: true, + has_projects: true, + has_downloads: true, + has_wiki: false, + has_pages: false, + has_discussions: false, + forks_count: 0, + mirror_url: null, + archived: false, + disabled: false, + open_issues_count: 1, + license: null, + allow_forking: true, + is_template: false, + web_commit_signoff_required: false, + topics: [], + visibility: 'public', + forks: 0, + open_issues: 1, + watchers: 1, + default_branch: 'master', + }, + sender: { + login: 'activepieces', + id: 1234, + node_id: 'MDQ6VXNlcjM5MzI2MQ==', + avatar_url: 'https://avatars.githubusercontent.com/u/1234?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/activepieces', + html_url: 'https://github.com/activepieces', + followers_url: 'https://api.github.com/users/activepieces/followers', + following_url: + 'https://api.github.com/users/activepieces/following{/other_user}', + gists_url: 'https://api.github.com/users/activepieces/gists{/gist_id}', + starred_url: + 'https://api.github.com/users/activepieces/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/activepieces/subscriptions', + organizations_url: 'https://api.github.com/users/activepieces/orgs', + repos_url: 'https://api.github.com/users/activepieces/repos', + events_url: + 'https://api.github.com/users/activepieces/events{/privacy}', + received_events_url: + 'https://api.github.com/users/activepieces/received_events', + type: 'User', + site_admin: false, + }, + }, + }, + { + name: GithubEventType.DISCUSSION, + displayName: 'New Discussion', + description: 'Triggers when there is activity relating to a discussion.', + sampleData: { + action: 'created', + discussion: { + repository_url: 'https://api.github.com/repos/my-org/Topics', + category: { + id: 1234567890, + node_id: 'DIC_kwDOBJHGx84CWC0J', + repository_id: 1234567890, + emoji: ':speech_balloon:', + name: 'Discussions', + description: + 'Use “Saved replies” as templates. Type “/saved replies” in text field to get started', + created_at: '2023-04-25T17:47:04.000+02:00', + updated_at: '2023-05-03T14:04:13.000+02:00', + slug: 'discussions', + is_answerable: true, + }, + answer_html_url: null, + answer_chosen_at: null, + answer_chosen_by: null, + html_url: 'https://github.com/my-org/Topics/discussions/1234567890', + id: 1234567890, + node_id: 'D_kwDOBJHGx84AgH6R', + number: 1234567890, + title: 'TEST', + user: { + login: 'BenBen', + id: 1234567890, + node_id: 'MDQ6VXNlcjQyODMxNjA2', + avatar_url: 'https://avatars.githubusercontent.com/u/1234567890?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/BenBen', + html_url: 'https://github.com/BenBen', + followers_url: 'https://api.github.com/users/BenBen/followers', + following_url: + 'https://api.github.com/users/BenBen/following{/other_user}', + gists_url: 'https://api.github.com/users/BenBen/gists{/gist_id}', + starred_url: + 'https://api.github.com/users/BenBen/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/BenBen/subscriptions', + organizations_url: 'https://api.github.com/users/BenBen/orgs', + repos_url: 'https://api.github.com/users/BenBen/repos', + events_url: 'https://api.github.com/users/BenBen/events{/privacy}', + received_events_url: + 'https://api.github.com/users/BenBen/received_events', + type: 'User', + user_view_type: 'public', + site_admin: false, + }, + labels: [], + state: 'open', + state_reason: null, + locked: false, + comments: 0, + created_at: '2025-06-05T13:25:58Z', + updated_at: '2025-06-05T13:25:58Z', + author_association: 'NONE', + active_lock_reason: null, + body: 'TEST @coach', + reactions: { + url: 'https://api.github.com/repos/my-org/Topics/discussions/1234567890/reactions', + total_count: 0, + '+1': 0, + '-1': 0, + laugh: 0, + hooray: 0, + confused: 0, + heart: 0, + rocket: 0, + eyes: 0, + }, + timeline_url: + 'https://api.github.com/repos/my-org/Topics/discussions/1234567890/timeline', + }, + repository: { + id: 1234567890, + node_id: 'MDEwOlJlcG9zaXRvcnk3NjY2MjQ3MQ==', + name: 'Topics', + full_name: 'my-org/Topics', + private: true, + owner: { + login: 'my-org', + id: 1234567890, + node_id: 'MDEyOk9yZ2FuaXphdGlvbjE4MTc1MzI5', + avatar_url: 'https://avatars.githubusercontent.com/u/1234567890?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/my-org', + html_url: 'https://github.com/my-org', + followers_url: 'https://api.github.com/users/my-org/followers', + following_url: + 'https://api.github.com/users/my-org/following{/other_user}', + gists_url: 'https://api.github.com/users/my-org/gists{/gist_id}', + starred_url: + 'https://api.github.com/users/my-org/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/my-org/subscriptions', + organizations_url: 'https://api.github.com/users/my-org/orgs', + repos_url: 'https://api.github.com/users/my-org/repos', + events_url: 'https://api.github.com/users/my-org/events{/privacy}', + received_events_url: + 'https://api.github.com/users/my-org/received_events', + type: 'Organization', + user_view_type: 'public', + site_admin: false, + }, + html_url: 'https://github.com/my-org/Topics', + description: 'The My-ORG Issue repository ', + fork: false, + url: 'https://api.github.com/repos/my-org/Topics', + forks_url: 'https://api.github.com/repos/my-org/Topics/forks', + keys_url: 'https://api.github.com/repos/my-org/Topics/keys{/key_id}', + collaborators_url: + 'https://api.github.com/repos/my-org/Topics/collaborators{/collaborator}', + teams_url: 'https://api.github.com/repos/my-org/Topics/teams', + hooks_url: 'https://api.github.com/repos/my-org/Topics/hooks', + issue_events_url: + 'https://api.github.com/repos/my-org/Topics/issues/events{/number}', + events_url: 'https://api.github.com/repos/my-org/Topics/events', + assignees_url: + 'https://api.github.com/repos/my-org/Topics/assignees{/user}', + branches_url: + 'https://api.github.com/repos/my-org/Topics/branches{/branch}', + tags_url: 'https://api.github.com/repos/my-org/Topics/tags', + blobs_url: 'https://api.github.com/repos/my-org/Topics/git/blobs{/sha}', + git_tags_url: + 'https://api.github.com/repos/my-org/Topics/git/tags{/sha}', + git_refs_url: + 'https://api.github.com/repos/my-org/Topics/git/refs{/sha}', + trees_url: 'https://api.github.com/repos/my-org/Topics/git/trees{/sha}', + statuses_url: + 'https://api.github.com/repos/my-org/Topics/statuses/{sha}', + languages_url: 'https://api.github.com/repos/my-org/Topics/languages', + stargazers_url: 'https://api.github.com/repos/my-org/Topics/stargazers', + contributors_url: + 'https://api.github.com/repos/my-org/Topics/contributors', + subscribers_url: + 'https://api.github.com/repos/my-org/Topics/subscribers', + subscription_url: + 'https://api.github.com/repos/my-org/Topics/subscription', + commits_url: 'https://api.github.com/repos/my-org/Topics/commits{/sha}', + git_commits_url: + 'https://api.github.com/repos/my-org/Topics/git/commits{/sha}', + comments_url: + 'https://api.github.com/repos/my-org/Topics/comments{/number}', + issue_comment_url: + 'https://api.github.com/repos/my-org/Topics/issues/comments{/number}', + contents_url: + 'https://api.github.com/repos/my-org/Topics/contents/{+path}', + compare_url: + 'https://api.github.com/repos/my-org/Topics/compare/{base}...{head}', + merges_url: 'https://api.github.com/repos/my-org/Topics/merges', + archive_url: + 'https://api.github.com/repos/my-org/Topics/{archive_format}{/ref}', + downloads_url: 'https://api.github.com/repos/my-org/Topics/downloads', + issues_url: + 'https://api.github.com/repos/my-org/Topics/issues{/number}', + pulls_url: 'https://api.github.com/repos/my-org/Topics/pulls{/number}', + milestones_url: + 'https://api.github.com/repos/my-org/Topics/milestones{/number}', + notifications_url: + 'https://api.github.com/repos/my-org/Topics/notifications{?since,all,participating}', + labels_url: 'https://api.github.com/repos/my-org/Topics/labels{/name}', + releases_url: + 'https://api.github.com/repos/my-org/Topics/releases{/id}', + deployments_url: + 'https://api.github.com/repos/my-org/Topics/deployments', + created_at: '2016-12-16T15:05:45Z', + updated_at: '2025-05-12T15:04:29Z', + pushed_at: '2024-03-11T10:31:39Z', + git_url: 'git://github.com/my-org/Topics.git', + ssh_url: 'git@github.com:my-org/Topics.git', + clone_url: 'https://github.com/my-org/Topics.git', + svn_url: 'https://github.com/my-org/Topics', + homepage: '', + size: 2305, + stargazers_count: 64, + watchers_count: 64, + language: null, + has_issues: true, + has_projects: true, + has_downloads: true, + has_wiki: true, + has_pages: false, + has_discussions: true, + forks_count: 0, + mirror_url: null, + archived: false, + disabled: false, + open_issues_count: 2, + license: null, + allow_forking: true, + is_template: false, + web_commit_signoff_required: false, + topics: [], + visibility: 'private', + forks: 0, + open_issues: 2, + watchers: 64, + default_branch: 'master', + custom_properties: {}, + }, + organization: { + login: 'my-org', + id: 1234567890, + node_id: 'MDEyOk9yZ2FuaXphdGlvbjE4MTc1MzI5', + url: 'https://api.github.com/orgs/my-org', + repos_url: 'https://api.github.com/orgs/my-org/repos', + events_url: 'https://api.github.com/orgs/my-org/events', + hooks_url: 'https://api.github.com/orgs/my-org/hooks', + issues_url: 'https://api.github.com/orgs/my-org/issues', + members_url: 'https://api.github.com/orgs/my-org/members{/member}', + public_members_url: + 'https://api.github.com/orgs/my-org/public_members{/member}', + avatar_url: 'https://avatars.githubusercontent.com/u/1234567890?v=4', + description: "Building Europe's all-in-one health partner", + }, + sender: { + login: 'BenBen', + id: 1234567890, + node_id: 'MDQ6VXNlcjQyODMxNjA2', + avatar_url: 'https://avatars.githubusercontent.com/u/1234567890?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/BenBen', + html_url: 'https://github.com/BenBen', + followers_url: 'https://api.github.com/users/BenBen/followers', + following_url: + 'https://api.github.com/users/BenBen/following{/other_user}', + gists_url: 'https://api.github.com/users/BenBen/gists{/gist_id}', + starred_url: + 'https://api.github.com/users/BenBen/starred{/owner}{/repo}', + subscriptions_url: 'https://api.github.com/users/BenBen/subscriptions', + organizations_url: 'https://api.github.com/users/BenBen/orgs', + repos_url: 'https://api.github.com/users/BenBen/repos', + events_url: 'https://api.github.com/users/BenBen/events{/privacy}', + received_events_url: + 'https://api.github.com/users/BenBen/received_events', + type: 'User', + user_view_type: 'public', + site_admin: false, + }, + }, + }, + + { + name: GithubEventType.DISCUSSION_COMMENT, + displayName: 'New Comment Posted', + description: 'Triggers when there is a new comment posted on a discussion.', + sampleData: { + action: 'created', + comment: { + id: 1234567890, + node_id: 'DC_kwDOBJHGx84AzClv', + html_url: + 'https://github.com/my-org/Topics/discussions/1234567890#discussioncomment-1234567890', + parent_id: 1234567890, + child_comment_count: 0, + repository_url: 'my-org/Topics', + discussion_id: 1234567890, + author_association: 'CONTRIBUTOR', + user: { + login: 'alaner', + id: 1234567890, + node_id: 'MDQ6VXNlcjQyNDY0NzA0', + avatar_url: 'https://avatars.githubusercontent.com/u/1234567890?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/alaner', + html_url: 'https://github.com/alaner', + followers_url: 'https://api.github.com/users/alaner/followers', + following_url: + 'https://api.github.com/users/alaner/following{/other_user}', + gists_url: 'https://api.github.com/users/alaner/gists{/gist_id}', + starred_url: + 'https://api.github.com/users/alaner/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/alaner/subscriptions', + organizations_url: 'https://api.github.com/users/alaner/orgs', + repos_url: 'https://api.github.com/users/alaner/repos', + events_url: 'https://api.github.com/users/alaner/events{/privacy}', + received_events_url: + 'https://api.github.com/users/alaner/received_events', + type: 'User', + user_view_type: 'public', + site_admin: false, + }, + created_at: '2025-06-05T13:27:58Z', + updated_at: '2025-06-05T13:27:58Z', + body: 'Cool ! ', + reactions: { + url: 'https://api.github.com/repos/my-org/Topics/discussions/comments/1234567890/reactions', + total_count: 0, + '+1': 0, + '-1': 0, + laugh: 0, + hooray: 0, + confused: 0, + heart: 0, + rocket: 0, + eyes: 0, + }, + }, + discussion: { + repository_url: 'https://api.github.com/repos/my-org/Topics', + category: { + id: 1234567890, + node_id: 'DIC_kwDOBJHGx84CWAk6', + repository_id: 1234567890, + emoji: ':speech_balloon:', + name: 'Guided Discussions', + description: + 'For Alaners less familiar with discussions who would like extra guidance', + created_at: '2023-04-24T11:06:26.000+02:00', + updated_at: '2023-05-03T14:10:16.000+02:00', + slug: 'guided-discussions', + is_answerable: true, + }, + answer_html_url: null, + answer_chosen_at: null, + answer_chosen_by: null, + html_url: 'https://github.com/my-org/Topics/discussions/1234567890', + id: 1234567890, + node_id: 'D_kwDOBJHGx84Af-_L', + number: 1234567890, + title: 'Framing - Members communications for QVCT', + user: { + login: 'alaner', + id: 1234567890, + node_id: 'U_kgDOB0ODWw', + avatar_url: 'https://avatars.githubusercontent.com/u/1234567890?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/alaner', + html_url: 'https://github.com/alaner', + followers_url: 'https://api.github.com/users/alaner/followers', + following_url: + 'https://api.github.com/users/alaner/following{/other_user}', + gists_url: 'https://api.github.com/users/alaner/gists{/gist_id}', + starred_url: + 'https://api.github.com/users/alaner/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/alaner/subscriptions', + organizations_url: 'https://api.github.com/users/alaner/orgs', + repos_url: 'https://api.github.com/users/alaner/repos', + events_url: 'https://api.github.com/users/alaner/events{/privacy}', + received_events_url: + 'https://api.github.com/users/alaner/received_events', + type: 'User', + user_view_type: 'public', + site_admin: false, + }, + labels: [ + { + id: 1234567890, + node_id: 'LA_kwDOBJHGx88AAAABlyvmxg', + url: 'https://api.github.com/repos/my-org/Topics/labels/%F0%9F%AB%90%20Play', + name: '🫐 Play', + color: '9ED9F5', + default: false, + description: '', + }, + ], + state: 'open', + state_reason: null, + locked: false, + comments: 31, + created_at: '2025-05-28T12:48:07Z', + updated_at: '2025-06-05T13:27:59Z', + author_association: 'NONE', + active_lock_reason: null, + body: "### 🔭 Scope\r\n\r\n\r\nThe goal of this discussion is to determine the optimal member communication strategy for the QVCT campaign, focusing on targeting, messaging angles, and campaigns coordination \r\n\r\n\r\n### 🕐 Why I'm opening this discussion\r\nWe need to determine the most effective approach for member communications to prevent spamming and member fatigue, while also ensuring this campaign is well-coordinated with our other Play initiatives.\r\n\r\n### 📅 Timeline\r\n\r\nI’d like to close this discussion by June 4th. \r\n\r\n### ℹ️ LOCI\r\n\r\n- **Lead:** @alaner \r\n- **Owner**: @alaner \r\n- **Consulted**: @alaner , @alaner \r\n- **Informed**: Crew play\r\n\r\n### 🌐 Context & Materials\r\n\r\n- Existing adoption campaigns running parallel to QVCT\r\n- Daily reminders campaign\r\n- Link to Figjam with assets to build\r\n\r\n### Threads\r\n\r\n- [X] You can use threads [meaning you can write a reply within a thread]\r\n- [ ] Please do not use threads [always suggest a new answer as a main message]\r\n\r\n❓ Questions are listed in each thread", + reactions: { + url: 'https://api.github.com/repos/my-org/Topics/discussions/1234567890/reactions', + total_count: 2, + '+1': 2, + '-1': 0, + laugh: 0, + hooray: 0, + confused: 0, + heart: 0, + rocket: 0, + eyes: 0, + }, + timeline_url: + 'https://api.github.com/repos/my-org/Topics/discussions/1234567890/timeline', + }, + repository: { + id: 1234567890, + node_id: 'MDEwOlJlcG9zaXRvcnk3NjY2MjQ3MQ==', + name: 'Topics', + full_name: 'my-org/Topics', + private: true, + owner: { + login: 'my-org', + id: 1234567890, + node_id: 'MDEyOk9yZ2FuaXphdGlvbjE4MTc1MzI5', + avatar_url: 'https://avatars.githubusercontent.com/u/1234567890?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/my-org', + html_url: 'https://github.com/my-org', + followers_url: 'https://api.github.com/users/my-org/followers', + following_url: + 'https://api.github.com/users/my-org/following{/other_user}', + gists_url: 'https://api.github.com/users/my-org/gists{/gist_id}', + starred_url: + 'https://api.github.com/users/my-org/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/my-org/subscriptions', + organizations_url: 'https://api.github.com/users/my-org/orgs', + repos_url: 'https://api.github.com/users/my-org/repos', + events_url: 'https://api.github.com/users/my-org/events{/privacy}', + received_events_url: + 'https://api.github.com/users/my-org/received_events', + type: 'Organization', + user_view_type: 'public', + site_admin: false, + }, + html_url: 'https://github.com/my-org/Topics', + description: 'The My-ORG Issue repository ', + fork: false, + url: 'https://api.github.com/repos/my-org/Topics', + forks_url: 'https://api.github.com/repos/my-org/Topics/forks', + keys_url: 'https://api.github.com/repos/my-org/Topics/keys{/key_id}', + collaborators_url: + 'https://api.github.com/repos/my-org/Topics/collaborators{/collaborator}', + teams_url: 'https://api.github.com/repos/my-org/Topics/teams', + hooks_url: 'https://api.github.com/repos/my-org/Topics/hooks', + issue_events_url: + 'https://api.github.com/repos/my-org/Topics/issues/events{/number}', + events_url: 'https://api.github.com/repos/my-org/Topics/events', + assignees_url: + 'https://api.github.com/repos/my-org/Topics/assignees{/user}', + branches_url: + 'https://api.github.com/repos/my-org/Topics/branches{/branch}', + tags_url: 'https://api.github.com/repos/my-org/Topics/tags', + blobs_url: 'https://api.github.com/repos/my-org/Topics/git/blobs{/sha}', + git_tags_url: + 'https://api.github.com/repos/my-org/Topics/git/tags{/sha}', + git_refs_url: + 'https://api.github.com/repos/my-org/Topics/git/refs{/sha}', + trees_url: 'https://api.github.com/repos/my-org/Topics/git/trees{/sha}', + statuses_url: + 'https://api.github.com/repos/my-org/Topics/statuses/{sha}', + languages_url: 'https://api.github.com/repos/my-org/Topics/languages', + stargazers_url: 'https://api.github.com/repos/my-org/Topics/stargazers', + contributors_url: + 'https://api.github.com/repos/my-org/Topics/contributors', + subscribers_url: + 'https://api.github.com/repos/my-org/Topics/subscribers', + subscription_url: + 'https://api.github.com/repos/my-org/Topics/subscription', + commits_url: 'https://api.github.com/repos/my-org/Topics/commits{/sha}', + git_commits_url: + 'https://api.github.com/repos/my-org/Topics/git/commits{/sha}', + comments_url: + 'https://api.github.com/repos/my-org/Topics/comments{/number}', + issue_comment_url: + 'https://api.github.com/repos/my-org/Topics/issues/comments{/number}', + contents_url: + 'https://api.github.com/repos/my-org/Topics/contents/{+path}', + compare_url: + 'https://api.github.com/repos/my-org/Topics/compare/{base}...{head}', + merges_url: 'https://api.github.com/repos/my-org/Topics/merges', + archive_url: + 'https://api.github.com/repos/my-org/Topics/{archive_format}{/ref}', + downloads_url: 'https://api.github.com/repos/my-org/Topics/downloads', + issues_url: + 'https://api.github.com/repos/my-org/Topics/issues{/number}', + pulls_url: 'https://api.github.com/repos/my-org/Topics/pulls{/number}', + milestones_url: + 'https://api.github.com/repos/my-org/Topics/milestones{/number}', + notifications_url: + 'https://api.github.com/repos/my-org/Topics/notifications{?since,all,participating}', + labels_url: 'https://api.github.com/repos/my-org/Topics/labels{/name}', + releases_url: + 'https://api.github.com/repos/my-org/Topics/releases{/id}', + deployments_url: + 'https://api.github.com/repos/my-org/Topics/deployments', + created_at: '2016-12-16T15:05:45Z', + updated_at: '2025-05-12T15:04:29Z', + pushed_at: '2024-03-11T10:31:39Z', + git_url: 'git://github.com/my-org/Topics.git', + ssh_url: 'git@github.com:my-org/Topics.git', + clone_url: 'https://github.com/my-org/Topics.git', + svn_url: 'https://github.com/my-org/Topics', + homepage: '', + size: 2305, + stargazers_count: 64, + watchers_count: 64, + language: null, + has_issues: true, + has_projects: true, + has_downloads: true, + has_wiki: true, + has_pages: false, + has_discussions: true, + forks_count: 0, + mirror_url: null, + archived: false, + disabled: false, + open_issues_count: 2, + license: null, + allow_forking: true, + is_template: false, + web_commit_signoff_required: false, + topics: [], + visibility: 'private', + forks: 0, + open_issues: 2, + watchers: 64, + default_branch: 'master', + custom_properties: {}, + }, + organization: { + login: 'my-org', + id: 1234567890, + node_id: 'MDEyOk9yZ2FuaXphdGlvbjE4MTc1MzI5', + url: 'https://api.github.com/orgs/my-org', + repos_url: 'https://api.github.com/orgs/my-org/repos', + events_url: 'https://api.github.com/orgs/my-org/events', + hooks_url: 'https://api.github.com/orgs/my-org/hooks', + issues_url: 'https://api.github.com/orgs/my-org/issues', + members_url: 'https://api.github.com/orgs/my-org/members{/member}', + public_members_url: + 'https://api.github.com/orgs/my-org/public_members{/member}', + avatar_url: 'https://avatars.githubusercontent.com/u/1234567890?v=4', + description: "Building Europe's all-in-one health partner", + }, + sender: { + login: 'alaner', + id: 1234567890, + node_id: 'MDQ6VXNlcjQyNDY0NzA0', + avatar_url: 'https://avatars.githubusercontent.com/u/1234567890?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/alaner', + html_url: 'https://github.com/alaner', + followers_url: 'https://api.github.com/users/alaner/followers', + following_url: + 'https://api.github.com/users/alaner/following{/other_user}', + gists_url: 'https://api.github.com/users/alaner/gists{/gist_id}', + starred_url: + 'https://api.github.com/users/alaner/starred{/owner}{/repo}', + subscriptions_url: 'https://api.github.com/users/alaner/subscriptions', + organizations_url: 'https://api.github.com/users/alaner/orgs', + repos_url: 'https://api.github.com/users/alaner/repos', + events_url: 'https://api.github.com/users/alaner/events{/privacy}', + received_events_url: + 'https://api.github.com/users/alaner/received_events', + type: 'User', + user_view_type: 'public', + site_admin: false, + }, + }, + }, +]; + +export const githubTriggers: Trigger[] = registered.map((def) => + githubRegisterTrigger(def) +); diff --git a/packages/pieces/community/github/src/lib/trigger/register-trigger.ts b/packages/pieces/community/github/src/lib/trigger/register-trigger.ts new file mode 100644 index 0000000..251835d --- /dev/null +++ b/packages/pieces/community/github/src/lib/trigger/register-trigger.ts @@ -0,0 +1,82 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { githubApiCall, githubCommon } from '../common'; +import { githubAuth } from '../../'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const githubRegisterTrigger = ({ + name, + displayName, + description, + sampleData, +}: { + name: string; + displayName: string; + description: string; + sampleData: object; +}) => + createTrigger({ + auth: githubAuth, + name: `trigger_${name}`, + displayName, + description, + props: { + repository: githubCommon.repositoryDropdown, + }, + sampleData, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const { repo, owner } = context.propsValue.repository!; + + const response = await githubApiCall<{ id: number }>({ + accessToken: context.auth.access_token, + method: HttpMethod.POST, + resourceUri: `/repos/${owner}/${repo}/hooks`, + body: { + name: 'web', + active: true, + events: [name], + config: { + url: context.webhookUrl, + content_type: 'json', + insecure_ssl: '0', + }, + }, + }); + + await context.store.put(`github_${name}_trigger`, { + webhookId: response.body.id, + owner: owner, + repo: repo, + }); + }, + async onDisable(context) { + const response = await context.store.get( + `github_${name}_trigger` + ); + if (response !== null && response !== undefined) { + await githubApiCall({ + accessToken: context.auth.access_token, + method: HttpMethod.DELETE, + resourceUri: `/repos/${response.owner}/${response.repo}/hooks/${response.webhookId}`, + }); + } + }, + async run(context) { + console.debug('payload received', context.payload.body); + + if (isVerificationCall(context.payload.body as Record)) { + return []; + } + return [context.payload.body]; + }, + }); + +function isVerificationCall(payload: Record) { + return payload['zen'] !== undefined; +} + +interface WebhookInformation { + webhookId: number; + repo: string; + owner: string; +} diff --git a/packages/pieces/community/github/tsconfig.json b/packages/pieces/community/github/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/github/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/github/tsconfig.lib.json b/packages/pieces/community/github/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/github/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/gitlab/.eslintrc.json b/packages/pieces/community/gitlab/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/gitlab/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/gitlab/README.md b/packages/pieces/community/gitlab/README.md new file mode 100644 index 0000000..720ad8e --- /dev/null +++ b/packages/pieces/community/gitlab/README.md @@ -0,0 +1,7 @@ +# pieces-gitlab + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-gitlab` to build the library. diff --git a/packages/pieces/community/gitlab/package.json b/packages/pieces/community/gitlab/package.json new file mode 100644 index 0000000..0db381b --- /dev/null +++ b/packages/pieces/community/gitlab/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-gitlab", + "version": "0.0.6" +} \ No newline at end of file diff --git a/packages/pieces/community/gitlab/project.json b/packages/pieces/community/gitlab/project.json new file mode 100644 index 0000000..670d550 --- /dev/null +++ b/packages/pieces/community/gitlab/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-gitlab", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/gitlab/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/gitlab", + "tsConfig": "packages/pieces/community/gitlab/tsconfig.lib.json", + "packageJson": "packages/pieces/community/gitlab/package.json", + "main": "packages/pieces/community/gitlab/src/index.ts", + "assets": [ + "packages/pieces/community/gitlab/*.md", + { + "input": "packages/pieces/community/gitlab/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-gitlab {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/gitlab/src/index.ts b/packages/pieces/community/gitlab/src/index.ts new file mode 100644 index 0000000..745511f --- /dev/null +++ b/packages/pieces/community/gitlab/src/index.ts @@ -0,0 +1,38 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + createPiece, + OAuth2PropertyValue, + PieceAuth, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createIssueAction } from './lib/actions/create-issue-action'; +import { issuesEventTrigger } from './lib/trigger/issue-event'; + +export const gitlabAuth = PieceAuth.OAuth2({ + required: true, + authUrl: 'https://gitlab.com/oauth/authorize', + tokenUrl: 'https://gitlab.com/oauth/token', + scope: ['api', 'read_user'], +}); + +export const gitlab = createPiece({ + displayName: 'GitLab', + description: 'Collaboration tool for developers', + + auth: gitlabAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/gitlab.png', + categories: [PieceCategory.DEVELOPER_TOOLS], + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + actions: [ + createIssueAction, + createCustomApiCallAction({ + baseUrl: () => 'https://gitlab.com/api/v4', + auth: gitlabAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [issuesEventTrigger], +}); diff --git a/packages/pieces/community/gitlab/src/lib/actions/create-issue-action.ts b/packages/pieces/community/gitlab/src/lib/actions/create-issue-action.ts new file mode 100644 index 0000000..9424e50 --- /dev/null +++ b/packages/pieces/community/gitlab/src/lib/actions/create-issue-action.ts @@ -0,0 +1,29 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { gitlabAuth } from '../../'; +import { gitlabCommon, makeClient } from '../common'; + +export const createIssueAction = createAction({ + auth: gitlabAuth, + name: 'create_issue', + description: 'Create a project issue', + displayName: 'Create Issue', + props: { + projectId: gitlabCommon.projectId(), + title: Property.ShortText({ + displayName: 'Issue Title', + required: true, + }), + description: Property.LongText({ + displayName: 'Issue Description', + required: false, + }), + }, + async run({ propsValue, auth }) { + const { projectId, title, description } = propsValue; + const client = makeClient({ auth }); + return await client.createProjectIssue(projectId as string, { + title: title, + description: description, + }); + }, +}); diff --git a/packages/pieces/community/gitlab/src/lib/common/client.ts b/packages/pieces/community/gitlab/src/lib/common/client.ts new file mode 100644 index 0000000..2e9fdeb --- /dev/null +++ b/packages/pieces/community/gitlab/src/lib/common/client.ts @@ -0,0 +1,96 @@ +import { + AuthenticationType, + HttpMessageBody, + HttpMethod, + QueryParams, + httpClient, +} from '@activepieces/pieces-common'; +import { + ListProjectsRequest, + CreateProjectIssueRequest, + GitlabProject, + ProjectWebhookRequest, + ProjectWebhook, +} from './models'; +export class GitlabApi { + constructor(private accessToken: string) {} + + async makeRequest( + method: HttpMethod, + url: string, + query?: QueryParams, + body?: object + ): Promise { + const res = await httpClient.sendRequest({ + method, + url: 'https://gitlab.com/api/v4' + url, + queryParams: query, + body, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: this.accessToken, + }, + }); + return res.body; + } + async listProjects(request: ListProjectsRequest) { + return this.makeRequest( + HttpMethod.GET, + '/projects', + prepareQuery(request) + ); + } + async createProjectIssue( + projectId: string, + request: CreateProjectIssueRequest + ) { + return this.makeRequest( + HttpMethod.POST, + `/projects/${projectId}/issues`, + undefined, + request + ); + } + async subscribeProjectWebhook( + projectId: string, + request: ProjectWebhookRequest + ) { + return this.makeRequest( + HttpMethod.POST, + `/projects/${projectId}/hooks`, + undefined, + request + ); + } + async unsubscribeProjectWebhook(projectId: string, webhookId: string) { + return this.makeRequest( + HttpMethod.DELETE, + `/projects/${projectId}/hooks/${webhookId}`, + undefined + ); + } +} + +function emptyValueFilter( + accessor: (key: string) => any +): (key: string) => boolean { + return (key: string) => { + const val = accessor(key); + return ( + val !== null && + val !== undefined && + (typeof val != 'string' || val.length > 0) + ); + }; +} + +export function prepareQuery(request?: Record): QueryParams { + const params: QueryParams = {}; + if (!request) return params; + Object.keys(request) + .filter(emptyValueFilter((k) => request[k])) + .forEach((k: string) => { + params[k] = (request as Record)[k].toString(); + }); + return params; +} diff --git a/packages/pieces/community/gitlab/src/lib/common/index.ts b/packages/pieces/community/gitlab/src/lib/common/index.ts new file mode 100644 index 0000000..e7ea3e1 --- /dev/null +++ b/packages/pieces/community/gitlab/src/lib/common/index.ts @@ -0,0 +1,44 @@ +import { getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { GitlabApi } from './client'; + +export const gitlabCommon = { + projectId: (required = true) => + Property.Dropdown({ + displayName: 'Project', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Setup authentication first', + options: [], + }; + } + const client = makeClient({ + auth: auth as OAuth2PropertyValue, + }); + const res = await client.listProjects({ + simple: true, + membership: true, + }); + return { + disabled: false, + options: res.map((project) => { + return { + label: project.name, + value: project.id, + }; + }), + }; + }, + }), +}; + +export function makeClient(propsValue: { + auth: OAuth2PropertyValue; +}): GitlabApi { + const token = getAccessTokenOrThrow(propsValue.auth); + return new GitlabApi(token); +} diff --git a/packages/pieces/community/gitlab/src/lib/common/models.ts b/packages/pieces/community/gitlab/src/lib/common/models.ts new file mode 100644 index 0000000..788ff71 --- /dev/null +++ b/packages/pieces/community/gitlab/src/lib/common/models.ts @@ -0,0 +1,57 @@ +export interface ListProjectsRequest { + membership?: boolean; + simple?: boolean; +} +export interface ProjectWebhookRequest { + url: string; + confidential_issues_events?: boolean; + confidential_note_events?: boolean; + deployment_events?: boolean; + enable_ssl_verification?: boolean; + issues_events?: boolean; + job_events?: boolean; + merge_requests_events?: boolean; + note_events?: boolean; + pipeline_events?: boolean; + push_events_branch_filter?: string; + push_events?: boolean; + releases_events?: boolean; + tag_push_events?: boolean; + wiki_page_events?: boolean; + token?: boolean; +} +export interface CreateProjectIssueRequest { + title: string; + description?: string; +} +export interface ProjectWebhook { + id: string; +} +export interface GitlabProject { + id: string; + description?: string; + name: string; + name_with_namespace: string; + path: string; + path_with_namespace: string; + created_at: string; + default_branch: string; + tag_list?: string[]; + topics?: string[]; + ssh_url_to_repo: string; + http_url_to_repo: string; + web_url: string; + avatar_url?: string; + star_count: number; + last_activity_at: string; + namespace: { + id: number; + name: string; + path: string; + kind: string; + full_path: string; + parent_id?: string; + avatar_url?: string; + web_url: string; + }; +} diff --git a/packages/pieces/community/gitlab/src/lib/trigger/issue-event.ts b/packages/pieces/community/gitlab/src/lib/trigger/issue-event.ts new file mode 100644 index 0000000..b48b5fb --- /dev/null +++ b/packages/pieces/community/gitlab/src/lib/trigger/issue-event.ts @@ -0,0 +1,171 @@ +import { + createTrigger, + TriggerStrategy, + Property, +} from '@activepieces/pieces-framework'; + +import { gitlabCommon, makeClient } from '../common'; +import { gitlabAuth } from '../..'; +const sampleData = { + object_kind: 'issue', + event_type: 'issue', + user: { + id: 15269532, + name: 'Kishan Parmar', + username: 'kishanprmr', + avatar_url: + 'https://secure.gravatar.com/avatar/4eabe7154116891e3ebc205e9754a832?s=80&d=identicon', + email: '[REDACTED]', + }, + project: { + id: 48074457, + name: 'basic-project-demo', + description: null, + web_url: 'https://gitlab.com/basic-group-demo/basic-project-demo', + avatar_url: null, + git_ssh_url: 'git@gitlab.com:basic-group-demo/basic-project-demo.git', + git_http_url: 'https://gitlab.com/basic-group-demo/basic-project-demo.git', + namespace: 'basic-group-demo', + visibility_level: 0, + path_with_namespace: 'basic-group-demo/basic-project-demo', + default_branch: 'main', + ci_config_path: '', + homepage: 'https://gitlab.com/basic-group-demo/basic-project-demo', + url: 'git@gitlab.com:basic-group-demo/basic-project-demo.git', + ssh_url: 'git@gitlab.com:basic-group-demo/basic-project-demo.git', + http_url: 'https://gitlab.com/basic-group-demo/basic-project-demo.git', + }, + object_attributes: { + author_id: 15269532, + closed_at: null, + confidential: false, + created_at: '2023-09-01 07:03:02 UTC', + description: '', + discussion_locked: null, + due_date: null, + id: 133093523, + iid: 32, + last_edited_at: null, + last_edited_by_id: null, + milestone_id: null, + moved_to_id: null, + duplicated_to_id: null, + project_id: 48074457, + relative_position: null, + state_id: 1, + time_estimate: 0, + title: 'Activepieces Testing', + updated_at: '2023-09-01 07:03:02 UTC', + updated_by_id: null, + weight: null, + health_status: null, + url: 'https://gitlab.com/basic-group-demo/basic-project-demo/-/issues/32', + total_time_spent: 0, + time_change: 0, + human_total_time_spent: null, + human_time_change: null, + human_time_estimate: null, + assignee_ids: [], + assignee_id: null, + labels: [], + state: 'opened', + severity: 'unknown', + customer_relations_contacts: [], + action: 'open', + }, + labels: [], + changes: { + author_id: { previous: null, current: 15269532 }, + created_at: { previous: null, current: '2023-09-01 07:03:02 UTC' }, + description: { previous: null, current: '' }, + id: { previous: null, current: 133093523 }, + iid: { previous: null, current: 32 }, + project_id: { previous: null, current: 48074457 }, + time_estimate: { previous: null, current: 0 }, + title: { previous: null, current: 'Activepieces Testing' }, + updated_at: { previous: null, current: '2023-09-01 07:03:02 UTC' }, + }, + repository: { + name: 'basic-project-demo', + url: 'git@gitlab.com:basic-group-demo/basic-project-demo.git', + description: null, + homepage: 'https://gitlab.com/basic-group-demo/basic-project-demo', + }, +}; + +export const issuesEventTrigger = createTrigger({ + auth: gitlabAuth, + name: 'project_issue_event', + displayName: 'New Project Issue Event', + description: + 'Triggers on project issue events when an issue is created or when an existing issue is updated, closed, or reopened.', + props: { + projectId: gitlabCommon.projectId(), + actiontype: Property.StaticDropdown({ + displayName: 'Issue Event', + description: 'Issue Event type for trigger', + defaultValue: 'all', + required: true, + options: { + disabled: false, + options: [ + { label: 'All', value: 'all' }, + { label: 'Opened', value: 'open' }, + { label: 'Closed', value: 'close' }, + { label: 'Updated', value: 'update' }, + ], + }, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: sampleData, + + async onEnable({ store, auth, propsValue, webhookUrl }) { + const projectId = propsValue.projectId!; + const client = makeClient({ auth }); + const res = await client.subscribeProjectWebhook(projectId, { + url: webhookUrl, + issues_events: true, + push_events: false, + }); + await store.put('gitlab_issue_trigger', { + webhookId: res.id, + projectId: projectId as string, + }); + }, + + async onDisable({ auth, store }) { + const response = await store.get( + 'gitlab_issue_trigger' + ); + if (response !== null && response !== undefined) { + const client = makeClient({ auth }); + client.unsubscribeProjectWebhook(response.projectId, response.webhookId); + } + }, + + async run(context) { + const { actiontype } = context.propsValue; + if ( + isVerificationCall( + context.payload.body as Record, + actiontype as string + ) + ) { + return [context.payload.body]; + } + return []; + }, +}); + +function isVerificationCall(payload: Record, actiontype: string) { + if (actiontype == 'all') { + return true; + } + return payload['object_attributes']['action'] == actiontype; +} + +interface WebhookInformation { + webhookId: string; + projectId: string; +} diff --git a/packages/pieces/community/gitlab/tsconfig.json b/packages/pieces/community/gitlab/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/gitlab/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/gitlab/tsconfig.lib.json b/packages/pieces/community/gitlab/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/gitlab/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/gmail/.babelrc b/packages/pieces/community/gmail/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/gmail/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/gmail/.eslintrc.json b/packages/pieces/community/gmail/.eslintrc.json new file mode 100644 index 0000000..d7449bb --- /dev/null +++ b/packages/pieces/community/gmail/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {}, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/gmail/README.md b/packages/pieces/community/gmail/README.md new file mode 100644 index 0000000..edbe701 --- /dev/null +++ b/packages/pieces/community/gmail/README.md @@ -0,0 +1,7 @@ +# pieces-gmail + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-gmail` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/gmail/package.json b/packages/pieces/community/gmail/package.json new file mode 100644 index 0000000..87cfa96 --- /dev/null +++ b/packages/pieces/community/gmail/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-gmail", + "version": "0.8.2" +} diff --git a/packages/pieces/community/gmail/project.json b/packages/pieces/community/gmail/project.json new file mode 100644 index 0000000..8373a9c --- /dev/null +++ b/packages/pieces/community/gmail/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-gmail", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/gmail/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/gmail", + "tsConfig": "packages/pieces/community/gmail/tsconfig.lib.json", + "packageJson": "packages/pieces/community/gmail/package.json", + "main": "packages/pieces/community/gmail/src/index.ts", + "assets": [ + "packages/pieces/community/gmail/*.md", + { + "input": "packages/pieces/community/gmail/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/gmail/src/index.ts b/packages/pieces/community/gmail/src/index.ts new file mode 100644 index 0000000..f820383 --- /dev/null +++ b/packages/pieces/community/gmail/src/index.ts @@ -0,0 +1,59 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { gmailSendEmailAction } from './lib/actions/send-email-action'; +import { gmailNewEmailTrigger } from './lib/triggers/new-email'; +import { gmailNewLabeledEmailTrigger } from './lib/triggers/new-labeled-email'; + +export const gmailAuth = PieceAuth.OAuth2({ + description: '', + authUrl: 'https://accounts.google.com/o/oauth2/auth', + tokenUrl: 'https://oauth2.googleapis.com/token', + required: true, + scope: [ + 'https://www.googleapis.com/auth/gmail.send', + 'email', + 'https://www.googleapis.com/auth/gmail.readonly', + 'https://www.googleapis.com/auth/gmail.compose', + ], +}); + +export const gmail = createPiece({ + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/gmail.png', + categories: [ + PieceCategory.COMMUNICATION, + PieceCategory.BUSINESS_INTELLIGENCE, + ], + actions: [ + gmailSendEmailAction, + createCustomApiCallAction({ + baseUrl: () => 'https://gmail.googleapis.com/gmail/v1', + auth: gmailAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + displayName: 'Gmail', + description: 'Email service by Google', + + authors: [ + 'kanarelo', + 'abdullahranginwala', + 'BastienMe', + 'Salem-Alaa', + 'kishanprmr', + 'MoShizzle', + 'AbdulTheActivePiecer', + 'khaledmashaly', + 'abuaboud', + 'AdamSelene', + ], + triggers: [gmailNewEmailTrigger, gmailNewLabeledEmailTrigger], + auth: gmailAuth, +}); diff --git a/packages/pieces/community/gmail/src/lib/actions/get-mail-action.ts b/packages/pieces/community/gmail/src/lib/actions/get-mail-action.ts new file mode 100644 index 0000000..51dd338 --- /dev/null +++ b/packages/pieces/community/gmail/src/lib/actions/get-mail-action.ts @@ -0,0 +1,39 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { GmailRequests } from '../common/data'; +import { GmailMessageFormat } from '../common/models'; +import { gmailAuth } from '../../'; + +export const gmailGetEmail = createAction({ + auth: gmailAuth, + name: 'gmail_get_mail', + description: 'Get an email from your Gmail account via Id', + displayName: 'Get Email', + props: { + message_id: Property.ShortText({ + displayName: 'Message ID', + description: 'The messageId of the mail to read', + required: true, + }), + format: Property.StaticDropdown({ + displayName: 'Format', + description: 'Format of the mail', + required: false, + defaultValue: GmailMessageFormat.FULL, + options: { + disabled: false, + options: [ + { value: GmailMessageFormat.MINIMAL, label: 'Minimal' }, + { value: GmailMessageFormat.FULL, label: 'Full' }, + { value: GmailMessageFormat.RAW, label: 'Raw' }, + { value: GmailMessageFormat.METADATA, label: 'Metadata' }, + ], + }, + }), + }, + run: async ({ auth, propsValue: { format, message_id } }) => + await GmailRequests.getMail({ + access_token: auth.access_token, + message_id, + format: format ?? GmailMessageFormat.FULL, + }), +}); diff --git a/packages/pieces/community/gmail/src/lib/actions/get-thread-action.ts b/packages/pieces/community/gmail/src/lib/actions/get-thread-action.ts new file mode 100644 index 0000000..559e054 --- /dev/null +++ b/packages/pieces/community/gmail/src/lib/actions/get-thread-action.ts @@ -0,0 +1,39 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { GmailRequests } from '../common/data'; +import { GmailMessageFormat } from '../common/models'; +import { gmailAuth } from '../../'; + +export const gmailGetThread = createAction({ + auth: gmailAuth, + name: 'gmail_get_thread', + description: 'Get a thread from your Gmail account via Id', + displayName: 'Get Thread', + props: { + thread_id: Property.ShortText({ + displayName: 'Thread ID', + description: 'The thread Id of the mail to read', + required: true, + }), + format: Property.StaticDropdown({ + displayName: 'Format', + description: 'Format of the mail', + required: false, + defaultValue: 'full', + options: { + disabled: false, + options: [ + { value: GmailMessageFormat.MINIMAL, label: 'Minimal' }, + { value: GmailMessageFormat.FULL, label: 'Full' }, + { value: GmailMessageFormat.RAW, label: 'Raw' }, + { value: GmailMessageFormat.METADATA, label: 'Metadata' }, + ], + }, + }), + }, + run: async ({ auth, propsValue: { format, thread_id } }) => + await GmailRequests.getThread({ + access_token: auth.access_token, + thread_id, + format: format ?? GmailMessageFormat.FULL, + }), +}); diff --git a/packages/pieces/community/gmail/src/lib/actions/search-email-action.ts b/packages/pieces/community/gmail/src/lib/actions/search-email-action.ts new file mode 100644 index 0000000..2db8168 --- /dev/null +++ b/packages/pieces/community/gmail/src/lib/actions/search-email-action.ts @@ -0,0 +1,28 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { GmailRequests } from '../common/data'; +import { GmailLabel } from '../common/models'; +import { GmailProps } from '../common/props'; +import { gmailAuth } from '../../'; + +export const gmailSearchMail = createAction({ + auth: gmailAuth, + name: 'gmail_search_mail', + description: 'Find for an email in your Gmail account', + displayName: 'Find Email', + props: { + subject: GmailProps.subject, + from: GmailProps.from, + to: GmailProps.to, + label: GmailProps.label, + category: GmailProps.category, + }, + run: async ({ auth, propsValue: { from, to, subject, label, category } }) => + await GmailRequests.searchMail({ + access_token: auth.access_token, + from: from as string, + to: to as string, + subject: subject as string, + label: label as GmailLabel, + category: category as string, + }), +}); diff --git a/packages/pieces/community/gmail/src/lib/actions/send-email-action.ts b/packages/pieces/community/gmail/src/lib/actions/send-email-action.ts new file mode 100644 index 0000000..3bf84f7 --- /dev/null +++ b/packages/pieces/community/gmail/src/lib/actions/send-email-action.ts @@ -0,0 +1,203 @@ +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import mime from 'mime-types'; +import MailComposer from 'nodemailer/lib/mail-composer'; +import Mail, { Attachment } from 'nodemailer/lib/mailer'; +import { gmailAuth } from '../../'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; + +export const gmailSendEmailAction = createAction({ + auth: gmailAuth, + name: 'send_email', + description: 'Send an email through a Gmail account', + displayName: 'Send Email', + props: { + receiver: Property.Array({ + displayName: 'Receiver Email (To)', + description: undefined, + required: true, + }), + cc: Property.Array({ + displayName: 'CC Email', + description: undefined, + required: false, + }), + bcc: Property.Array({ + displayName: 'BCC Email', + description: undefined, + required: false, + }), + subject: Property.ShortText({ + displayName: 'Subject', + description: undefined, + required: true, + }), + body_type: Property.StaticDropdown({ + displayName: 'Body Type', + required: true, + defaultValue: 'plain_text', + options: { + disabled: false, + options: [ + { + label: 'plain text', + value: 'plain_text', + }, + { + label: 'html', + value: 'html', + }, + ], + }, + }), + body: Property.ShortText({ + displayName: 'Body', + description: 'Body for the email you want to send', + required: true, + }), + reply_to: Property.Array({ + displayName: 'Reply-To Email', + description: 'Email address to set as the "Reply-To" header', + required: false, + }), + sender_name: Property.ShortText({ + displayName: 'Sender Name', + required: false, + }), + from: Property.ShortText({ + displayName: 'Sender Email', + description: + "The address must be listed in your GMail account's settings", + required: false, + }), + attachment: Property.File({ + displayName: 'Attachment', + description: 'File to attach to the email you want to send', + required: false, + }), + attachment_name: Property.ShortText({ + displayName: 'Attachment Name', + description: 'In case you want to change the name of the attachment', + required: false, + }), + in_reply_to: Property.ShortText({ + displayName: 'In reply to', + description: 'Reply to this Message-ID', + required: false, + }), + draft: Property.Checkbox({ + displayName: 'Create draft', + description: 'Create draft without sending the actual email', + required: true, + defaultValue: false, + }), + }, + async run(context) { + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + + const gmail = google.gmail({ version: 'v1', auth: authClient }); + + const subjectBase64 = Buffer.from(context.propsValue['subject']).toString( + 'base64' + ); + const attachment = context.propsValue['attachment']; + const replyTo = context.propsValue['reply_to']?.filter( + (email) => email !== '' + ); + const receiver = context.propsValue['receiver']?.filter( + (email) => email !== '' + ); + const cc = context.propsValue['cc']?.filter((email) => email !== ''); + const bcc = context.propsValue['bcc']?.filter((email) => email !== ''); + const mailOptions: Mail.Options = { + to: receiver.join(', '), // Join all email addresses with a comma + cc: cc ? cc.join(', ') : undefined, + bcc: bcc ? bcc.join(', ') : undefined, + subject: `=?UTF-8?B?${subjectBase64}?=`, + replyTo: replyTo ? replyTo.join(', ') : '', + text: + context.propsValue.body_type === 'plain_text' + ? context.propsValue['body'] + : undefined, + html: + context.propsValue.body_type === 'html' + ? context.propsValue['body'] + : undefined, + attachments: [], + }; + let threadId = undefined; + if (context.propsValue.in_reply_to) { + mailOptions.headers = [ + { + key: 'References', + value: context.propsValue.in_reply_to, + }, + { + key: 'In-Reply-To', + value: context.propsValue.in_reply_to, + }, + ]; + const messages = await gmail.users.messages.list({ + userId: 'me', + q: `Rfc822msgid:${context.propsValue.in_reply_to}`, + }); + threadId = messages.data.messages?.[0].threadId; + } + + const senderEmail = + context.propsValue.from || + (await google.oauth2({ version: 'v2', auth: authClient }).userinfo.get()) + .data.email; + if (senderEmail) { + mailOptions.from = context.propsValue.sender_name + ? `${context.propsValue['sender_name']} <${senderEmail}>` + : senderEmail; + } + + if (attachment) { + const lookupResult = mime.lookup( + attachment.extension ? attachment.extension : '' + ); + const attachmentOption: Attachment[] = [ + { + filename: context.propsValue.attachment_name ?? attachment.filename, + content: attachment?.base64, + contentType: lookupResult ? lookupResult : undefined, + encoding: 'base64', + }, + ]; + mailOptions.attachments = attachmentOption; + } + + const mail: any = new MailComposer(mailOptions).compile(); + mail.keepBcc = true; + const mailBody = await mail.build(); + + const encodedPayload = Buffer.from(mailBody) + .toString('base64') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + + if (context.propsValue.draft) { + return await gmail.users.drafts.create({ + userId: 'me', + requestBody: { message: { threadId, raw: encodedPayload } }, + }); + } else { + return await gmail.users.messages.send({ + userId: 'me', + requestBody: { + threadId, + raw: encodedPayload, + }, + }); + } + }, +}); diff --git a/packages/pieces/community/gmail/src/lib/common/data.ts b/packages/pieces/community/gmail/src/lib/common/data.ts new file mode 100644 index 0000000..a952c53 --- /dev/null +++ b/packages/pieces/community/gmail/src/lib/common/data.ts @@ -0,0 +1,227 @@ +import { OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { + GmailLabel, + GmailMessage, + GmailThread, + GmailMessageFormat, + GmailMessageResponse as GmailMessageList, +} from './models'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { Attachment, ParsedMail, simpleParser } from 'mailparser'; +import { FilesService } from '@activepieces/pieces-framework'; + +interface SearchMailProps { + access_token: string; + from: string; + to: string; + subject?: string; + label: GmailLabel; + category: string; + after?: number; + before?: number; + max_results?: number; + page_token?: string; +} + +interface GetMailProps { + access_token: string; + message_id?: string; + thread_id?: string; + format: GmailMessageFormat; +} + +export const GmailRequests = { + getMail: async ({ access_token, format, message_id }: GetMailProps) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://gmail.googleapis.com/gmail/v1/users/me/messages/${message_id}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: access_token, + }, + queryParams: { + format, + }, + }); + let bodyHtml = ''; + let bodyPlain = ''; + const payload = response.body.payload; + let bodyParts = payload.parts || []; + const headers = payload.headers.reduce( + (obj: { [key: string]: string }, header) => { + obj[header.name.toLowerCase()] = header.value; + return obj; + }, + {} + ); + const alternateBodyPart = bodyParts.find((part) => + part.mimeType.startsWith('multipart/alternative') + ); + if (alternateBodyPart && alternateBodyPart?.parts) { + bodyParts = alternateBodyPart.parts; + } + const subject = headers['subject'] || ''; + // Determine the content type of the message body + const contentType = headers['content-type'] || 'text/plain'; + const isMultipart = contentType.startsWith('multipart/'); + + if (isMultipart) { + // If the message is multipart, extract the plain text and HTML parts + const textPart = bodyParts.find((part) => part.mimeType === 'text/plain'); + const htmlPart = bodyParts.find((part) => part.mimeType === 'text/html'); + + // If the message is an "alternative" multipart, use the plain text part if it exists + const preferredPart = textPart || htmlPart; + bodyHtml = htmlPart ? decodeBase64(htmlPart.body.data) : ''; + bodyPlain = preferredPart ? decodeBase64(preferredPart.body.data) : ''; + } else { + // If the message is not multipart, use the body as-is + bodyPlain = decodeBase64(payload.body.data); + bodyHtml = ''; + } + + return { + subject: subject, + body_html: bodyHtml, + body_plain: bodyPlain, + ...response.body, + }; + }, + getThread: async ({ access_token, format, thread_id }: GetMailProps) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://gmail.googleapis.com/gmail/v1/users/me/threads/${thread_id}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: access_token, + }, + queryParams: { + format, + }, + }); + + return response.body; + }, + getLabels: async (authentication: OAuth2PropertyValue) => { + return await httpClient.sendRequest<{ labels: GmailLabel[] }>({ + method: HttpMethod.GET, + url: `https://gmail.googleapis.com/gmail/v1/users/me/labels`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: (authentication as OAuth2PropertyValue).access_token, + }, + }); + }, + searchMail: async ({ + access_token, + max_results = 1, + page_token: pageToken, + ...mail + }: SearchMailProps) => { + const query = []; + + if (mail.from) query.push(`from:(${mail.from})`); + if (mail.to) query.push(`to:(${mail.to})`); + if (mail.subject) query.push(`subject:(${mail.subject})`); + if (mail.label) query.push(`label:${mail.label.name}`); + if (mail.category) query.push(`category:${mail.category}`); + if (mail.after != null && mail.after > 0) query.push(`after:${mail.after}`); + if (mail.before != null) query.push(`before:${mail.before}`); + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://www.googleapis.com/gmail/v1/users/me/messages`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: access_token, + }, + queryParams: { + q: query.join(' '), + maxResults: `${max_results}`, + ...(pageToken ? { pageToken } : {}), + }, + }); + + if (response.body.messages) { + const messages = await Promise.all( + response.body.messages.map( + async (message: { id: string; threadId: string }) => { + const mail = await GmailRequests.getMail({ + access_token, + message_id: message.id, + format: GmailMessageFormat.FULL, + }); + const thread = await GmailRequests.getThread({ + access_token, + thread_id: message.threadId, + format: GmailMessageFormat.FULL, + }); + + return { + message: mail, + thread, + }; + } + ) + ); + + return { + messages, + resultSizeEstimate: response.body.resultSizeEstimate, + ...(response?.body.nextPageToken + ? { nextPageToken: response.body.nextPageToken } + : {}), + }; + } + + return response.body; + }, +}; + +function decodeBase64(data: any) { + return Buffer.from(data, 'base64').toString(); +} + +export async function parseStream(stream: any) { + return new Promise((resolve, reject) => { + simpleParser(stream, (err, parsed) => { + if (err) { + reject(err); + } else { + resolve(parsed); + } + }); + }); +} + +export async function convertAttachment( + attachments: Attachment[], + files: FilesService +) { + const promises = attachments.map(async (attachment) => { + try { + const fileName = attachment.filename ?? `attachment-${Date.now()}`; + return { + fileName, + mimeType: attachment.contentType, + size: attachment.size, + data: await files.write({ + fileName: fileName, + data: attachment.content, + }), + }; + } catch (error) { + console.error( + `Failed to process attachment: ${attachment.filename}`, + error + ); + return null; + } + }); + const results = await Promise.all(promises); + return results.filter((result) => result !== null); +} diff --git a/packages/pieces/community/gmail/src/lib/common/models.ts b/packages/pieces/community/gmail/src/lib/common/models.ts new file mode 100644 index 0000000..a93c7f9 --- /dev/null +++ b/packages/pieces/community/gmail/src/lib/common/models.ts @@ -0,0 +1,91 @@ +export interface GmailLabel { + id: string; + name: string; + messageListVisibility: GmailMessageListVisibility; + labelListVisibility: GmailLabelListVisibility; + type: GmailLabelType; + messagesTotal: number; + messagesUnread: number; + threadsTotal: number; + threadsUnread: number; + color: { + textColor: string; + backgroundColor: string; + }; +} + +export interface GmailMessage { + id: string; + threadId: string; + labelIds: [string]; + snippet: string; + historyId: string; + internalDate: number; + payload: { + partId: string; + mimeType: string; + filename: string; + headers: { + name: string; + value: string; + }[]; + body: { + data: any; + size: number; + }; + parts: { + parts: any[]; + partId: string; + mimeType: string; + filename: string; + headers: { + name: string; + value: string; + }[]; + body: { + size: number; + data: string; + }; + }[]; + }; + sizeEstimate: number; + raw: string; +} + +export interface GmailThread { + id: string; + snippet: string; + historyId: string; + messages: GmailMessage[]; +} + +export interface GmailMessageResponse { + messages: { + id: string; + threadId: string; + }[]; + nextPageToken?: string; + resultSizeEstimate: number; +} + +enum GmailMessageListVisibility { + SHOW = 'show', + HIDE = 'hide', +} + +enum GmailLabelListVisibility { + SHOW = 'show', + HIDE = 'hide', +} + +enum GmailLabelType { + SYSTEM = 'system', + USER = 'user', +} + +export enum GmailMessageFormat { + MINIMAL = 'minimal', + FULL = 'full', + RAW = 'raw', + METADATA = 'metadata', +} diff --git a/packages/pieces/community/gmail/src/lib/common/props.ts b/packages/pieces/community/gmail/src/lib/common/props.ts new file mode 100644 index 0000000..4c9b4b1 --- /dev/null +++ b/packages/pieces/community/gmail/src/lib/common/props.ts @@ -0,0 +1,80 @@ +import { Property, OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { GmailRequests } from './data'; +import { GmailLabel } from './models'; + +export const GmailProps = { + from: Property.ShortText({ + displayName: 'Email sender', + description: + 'Optional filteration, leave empty to filter based on the email sender', + required: false, + defaultValue: '', + }), + to: Property.ShortText({ + displayName: 'Email recipient', + description: + 'Optional filteration, leave empty to filter based on the email recipient', + required: false, + defaultValue: '', + }), + subject: Property.ShortText({ + displayName: 'Email subject', + description: 'The email subject', + required: false, + defaultValue: '', + }), + category: Property.StaticDropdown({ + displayName: 'Category', + description: + 'Optional filteration, leave unselected to filter based on the email category', + required: false, + options: { + disabled: false, + options: [ + { label: 'Primary', value: 'primary' }, + { label: 'Social', value: 'social' }, + { label: 'Promotions', value: 'promotions' }, + { label: 'Updates', value: 'updates' }, + { label: 'Forums', value: 'forums' }, + { label: 'Reservations', value: 'reservations' }, + { label: 'Purchases', value: 'purchases' }, + ], + }, + }), + label: Property.Dropdown({ + displayName: 'Label', + description: + 'Optional filteration, leave unselected to filter based on the email label', + required: false, + defaultValue: '', + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'please authenticate first', + }; + } + + const response = await GmailRequests.getLabels( + auth as OAuth2PropertyValue + ); + + return { + disabled: false, + options: response.body.labels.map((label) => ({ + label: label.name, + value: label, + })), + }; + }, + }), + unread: (required = false) => + Property.Checkbox({ + displayName: 'Is unread?', + description: 'Check if the email is unread or not', + required, + defaultValue: false, + }), +}; diff --git a/packages/pieces/community/gmail/src/lib/triggers/new-email.ts b/packages/pieces/community/gmail/src/lib/triggers/new-email.ts new file mode 100644 index 0000000..4a8c28d --- /dev/null +++ b/packages/pieces/community/gmail/src/lib/triggers/new-email.ts @@ -0,0 +1,161 @@ +import { + createTrigger, + TriggerStrategy, + PiecePropValueSchema, + FilesService, +} from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import { GmailLabel } from '../common/models'; +import { GmailProps } from '../common/props'; +import { gmailAuth } from '../../'; +import { GmailRequests, parseStream, convertAttachment } from '../common/data'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; + +export const gmailNewEmailTrigger = createTrigger({ + auth: gmailAuth, + name: 'gmail_new_email_received', + displayName: 'New Email', + description: 'Triggers when new mail is found in your Gmail inbox', + props: { + subject: GmailProps.subject, + from: GmailProps.from, + to: GmailProps.to, + label: GmailProps.label, + category: GmailProps.category, + }, + sampleData: {}, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await context.store.put('lastPoll', Date.now()); + }, + async onDisable(context) { + return; + }, + async run(context) { + const lastFetchEpochMS = (await context.store.get('lastPoll')) ?? 0; + + const items = await pollRecentMessages({ + auth: context.auth, + props: context.propsValue, + files: context.files, + lastFetchEpochMS: lastFetchEpochMS, + }); + + const newLastEpochMilliSeconds = items.reduce( + (acc, item) => Math.max(acc, item.epochMilliSeconds), + lastFetchEpochMS + ); + await context.store.put('lastPoll', newLastEpochMilliSeconds); + return items + .filter((f) => f.epochMilliSeconds > lastFetchEpochMS) + .map((item) => item.data); + }, + async test(context) { + const lastFetchEpochMS = (await context.store.get('lastPoll')) ?? 0; + + const items = await pollRecentMessages({ + auth: context.auth, + props: context.propsValue, + files: context.files, + lastFetchEpochMS: lastFetchEpochMS, + }); + + return getFirstFiveOrAll(items.map((item) => item.data)); + }, +}); + +interface PropsValue { + from: string | undefined; + to: string | undefined; + subject: string | undefined; + label: GmailLabel | undefined; + category: string | undefined; +} + +async function pollRecentMessages({ + auth, + props, + files, + lastFetchEpochMS, +}: { + auth: PiecePropValueSchema; + props: PropsValue; + files: FilesService; + lastFetchEpochMS: number; +}): Promise< + { + epochMilliSeconds: number; + data: unknown; + }[] +> { + const authClient = new OAuth2Client(); + authClient.setCredentials(auth); + + const gmail = google.gmail({ version: 'v1', auth: authClient }); + + // construct query + const query = []; + const maxResults = lastFetchEpochMS === 0 ? 5 : 100; + const afterUnixSeconds = Math.floor(lastFetchEpochMS / 1000); + + if (props.from) query.push(`from:(${props.from})`); + if (props.to) query.push(`to:(${props.to})`); + if (props.subject) query.push(`subject:(${props.subject})`); + if (props.label) query.push(`label:${props.label.name}`); + if (props.category) query.push(`category:${props.category}`); + if (afterUnixSeconds != null && afterUnixSeconds > 0) + query.push(`after:${afterUnixSeconds}`); + + // List Messages + const messagesResponse = await gmail.users.messages.list({ + userId: 'me', + q: query.join(' '), + maxResults, + }); + + const pollingResponse = []; + for (const message of messagesResponse.data.messages || []) { + const rawMailResponse = await gmail.users.messages.get({ + userId: 'me', + id: message.id!, + format: 'raw', + }); + const threadResponse = await gmail.users.threads.get({ + userId: 'me', + id: message.threadId!, + }); + + const parsedMailResponse = await parseStream( + Buffer.from(rawMailResponse.data.raw as string, 'base64').toString( + 'utf-8' + ) + ); + + pollingResponse.push({ + epochMilliSeconds: dayjs(parsedMailResponse.date).valueOf(), + data: { + message: { + ...parsedMailResponse, + attachments: await convertAttachment( + parsedMailResponse.attachments, + files + ), + }, + thread: { + ...threadResponse, + }, + }, + }); + } + + return pollingResponse; +} + +function getFirstFiveOrAll(array: unknown[]) { + if (array.length <= 5) { + return array; + } else { + return array.slice(0, 5); + } +} diff --git a/packages/pieces/community/gmail/src/lib/triggers/new-labeled-email.ts b/packages/pieces/community/gmail/src/lib/triggers/new-labeled-email.ts new file mode 100644 index 0000000..3739b98 --- /dev/null +++ b/packages/pieces/community/gmail/src/lib/triggers/new-labeled-email.ts @@ -0,0 +1,196 @@ +import { + createTrigger, + TriggerStrategy, + FilesService, +} from '@activepieces/pieces-framework'; +import { GmailProps } from '../common/props'; +import { gmailAuth } from '../../'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { parseStream, convertAttachment } from '../common/data'; + +async function enrichGmailMessage({ + gmail, + messageId, + files, + labelInfo, +}: { + gmail: any; + messageId: string; + files: FilesService; + labelInfo: { + labelId: string; + labelName: string; + }; +}) { + const rawMailResponse = await gmail.users.messages.get({ + userId: 'me', + id: messageId, + format: 'raw', + }); + + const threadResponse = await gmail.users.threads.get({ + userId: 'me', + id: rawMailResponse.data.threadId!, + }); + + const parsedMailResponse = await parseStream( + Buffer.from(rawMailResponse.data.raw as string, 'base64').toString('utf-8') + ); + + return { + message: { + ...parsedMailResponse, + attachments: await convertAttachment( + parsedMailResponse.attachments, + files + ), + }, + thread: threadResponse.data, + labelInfo: { + ...labelInfo, + addedAt: Date.now(), + }, + }; +} + +export const gmailNewLabeledEmailTrigger = createTrigger({ + auth: gmailAuth, + name: 'new_labeled_email', + displayName: 'New Labeled Email', + description: 'Triggers when a label is added to an email', + props: { + label: { + ...GmailProps.label, + required: true, + }, + }, + sampleData: {}, + type: TriggerStrategy.POLLING, + onEnable: async (context) => { + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + const gmail = google.gmail({ version: 'v1', auth: authClient }); + + const profile = await gmail.users.getProfile({ + userId: 'me', + }); + + await context.store.put('lastHistoryId', profile.data.historyId); + }, + onDisable: async (context) => { + await context.store.delete('lastHistoryId'); + }, + run: async (context) => { + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + const gmail = google.gmail({ version: 'v1', auth: authClient }); + + const lastHistoryId = await context.store.get('lastHistoryId'); + + const historyResponse = await gmail.users.history.list({ + userId: 'me', + startHistoryId: lastHistoryId as string, + labelId: context.propsValue.label.id, + historyTypes: ['labelAdded', 'messageAdded'], + }); + + const labeledMessages = new Map(); + const results = []; + + if (historyResponse.data.history) { + for (const history of historyResponse.data.history) { + if (history.labelsAdded) { + for (const labelAdded of history.labelsAdded) { + if ( + labelAdded.labelIds?.includes(context.propsValue.label.id) && + labelAdded.message?.id + ) { + labeledMessages.set( + labelAdded.message.id, + history.id?.toString() || '' + ); + } + } + } else if (history.messagesAdded) { + for (const messageAdded of history.messagesAdded) { + if ( + messageAdded.message?.id && + messageAdded.message.labelIds?.includes( + context.propsValue.label.id + ) + ) { + labeledMessages.set( + messageAdded.message.id, + history.id?.toString() || '' + ); + } + } + } + } + } + + for (const [messageId, historyId] of labeledMessages) { + const enrichedMessage = await enrichGmailMessage({ + gmail, + messageId, + files: context.files, + labelInfo: { + labelId: context.propsValue.label.id, + labelName: context.propsValue.label.name, + }, + }); + + if (lastHistoryId !== historyId) { + results.push({ + id: historyId, + data: enrichedMessage, + }); + } + } + + if (historyResponse.data.historyId) { + await context.store.put('lastHistoryId', historyResponse.data.historyId); + } + + return results; + }, + test: async (context) => { + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + const gmail = google.gmail({ version: 'v1', auth: authClient }); + + const messagesResponse = await gmail.users.messages.list({ + userId: 'me', + labelIds: [context.propsValue.label.id], + maxResults: 5, + }); + + const results = []; + + if (messagesResponse.data.messages) { + for (const message of messagesResponse.data.messages) { + const messageId = message.id; + if (!messageId) { + continue; + } + const enrichedMessage = await enrichGmailMessage({ + gmail, + messageId: messageId, + files: context.files, + labelInfo: { + labelId: context.propsValue.label.id, + labelName: context.propsValue.label.name, + }, + }); + + results.push({ + id: messageId, + data: enrichedMessage, + }); + } + } + + return results; + }, +}); diff --git a/packages/pieces/community/gmail/tsconfig.json b/packages/pieces/community/gmail/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/gmail/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/gmail/tsconfig.lib.json b/packages/pieces/community/gmail/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/gmail/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/google-calendar/.babelrc b/packages/pieces/community/google-calendar/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/google-calendar/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/google-calendar/.eslintrc.json b/packages/pieces/community/google-calendar/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/google-calendar/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/google-calendar/README.md b/packages/pieces/community/google-calendar/README.md new file mode 100644 index 0000000..6937b5f --- /dev/null +++ b/packages/pieces/community/google-calendar/README.md @@ -0,0 +1,7 @@ +# pieces-google-calendar + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-google-calendar` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/google-calendar/package.json b/packages/pieces/community/google-calendar/package.json new file mode 100644 index 0000000..abe184b --- /dev/null +++ b/packages/pieces/community/google-calendar/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-google-calendar", + "version": "0.5.6" +} diff --git a/packages/pieces/community/google-calendar/project.json b/packages/pieces/community/google-calendar/project.json new file mode 100644 index 0000000..58303a9 --- /dev/null +++ b/packages/pieces/community/google-calendar/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-google-calendar", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/google-calendar/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/google-calendar", + "tsConfig": "packages/pieces/community/google-calendar/tsconfig.lib.json", + "packageJson": "packages/pieces/community/google-calendar/package.json", + "main": "packages/pieces/community/google-calendar/src/index.ts", + "assets": [ + "packages/pieces/community/google-calendar/*.md", + { + "input": "packages/pieces/community/google-calendar/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/google-calendar/src/index.ts b/packages/pieces/community/google-calendar/src/index.ts new file mode 100644 index 0000000..1b294e0 --- /dev/null +++ b/packages/pieces/community/google-calendar/src/index.ts @@ -0,0 +1,68 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createEvent } from './lib/actions/create-event'; +import { createQuickCalendarEvent } from './lib/actions/create-quick-event'; +import { deleteEventAction } from './lib/actions/delete-event.action'; +import { getEvents } from './lib/actions/get-events'; +import { updateEventAction } from './lib/actions/update-event.action'; +import { googleCalendarCommon } from './lib/common'; +import { calendarEventChanged } from './lib/triggers/calendar-event'; +import { addAttendeesToEventAction } from './lib/actions/add-attendees.action'; + +export const googleCalendarAuth = PieceAuth.OAuth2({ + description: '', + authUrl: 'https://accounts.google.com/o/oauth2/auth', + tokenUrl: 'https://oauth2.googleapis.com/token', + required: true, + pkce: true, + scope: [ + 'https://www.googleapis.com/auth/calendar.events', + 'https://www.googleapis.com/auth/calendar.readonly', + ], +}); + +export const googleCalendar = createPiece({ + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/google-calendar.png', + categories: [PieceCategory.PRODUCTIVITY], + displayName: 'Google Calendar', + description: 'Get organized and stay on schedule', + + authors: [ + 'OsamaHaikal', + 'bibhuty-did-this', + 'Vitalini', + 'pfernandez98', + 'kishanprmr', + 'MoShizzle', + 'khaledmashaly', + 'abuaboud', + 'ikus060', + ], + auth: googleCalendarAuth, + actions: [ + addAttendeesToEventAction, + createQuickCalendarEvent, + createEvent, + getEvents, + updateEventAction, + deleteEventAction, + createCustomApiCallAction({ + auth: googleCalendarAuth, + baseUrl() { + return googleCalendarCommon.baseUrl; + }, + authMapping: async (auth) => { + return { + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }; + }, + }), + ], + triggers: [calendarEventChanged], +}); diff --git a/packages/pieces/community/google-calendar/src/lib/actions/add-attendees.action.ts b/packages/pieces/community/google-calendar/src/lib/actions/add-attendees.action.ts new file mode 100644 index 0000000..6b84174 --- /dev/null +++ b/packages/pieces/community/google-calendar/src/lib/actions/add-attendees.action.ts @@ -0,0 +1,55 @@ +import { googleCalendarAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { google, calendar_v3 } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { googleCalendarCommon } from '../common'; + +export const addAttendeesToEventAction = createAction({ + auth: googleCalendarAuth, + name: 'google-calendar-add-attendees', + displayName: 'Add Attendees to Event', + description: 'Add one or more person to existing event.', + props: { + calendar_id: googleCalendarCommon.calendarDropdown('writer'), + eventId: Property.ShortText({ + displayName: 'Event ID', + required: true, + }), + attendees: Property.Array({ + displayName: 'Attendees', + description: 'Emails of the attendees (guests)', + required: true, + }), + }, + async run(context) { + const { calendar_id, eventId } = context.propsValue; + const attendeesInput = context.propsValue.attendees as string[]; + + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + const calendar = google.calendar({ version: 'v3', auth: authClient }); + + // Note that each patch request consumes three quota units; + // prefer using a get followed by an update + const currentEvent = await calendar.events.get({ + calendarId: calendar_id, + eventId: eventId, + }); + const currentAttendees = currentEvent.data.attendees ?? []; + + const attendeeFormattedList: calendar_v3.Schema$EventAttendee[] = []; + attendeeFormattedList.push(...currentAttendees); + attendeeFormattedList.push(...attendeesInput.map((email) => ({ email }))); + + const response = await calendar.events.update({ + calendarId: calendar_id!, + eventId, + requestBody: { + ...currentEvent.data, + attendees: attendeeFormattedList, + }, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/google-calendar/src/lib/actions/create-event.ts b/packages/pieces/community/google-calendar/src/lib/actions/create-event.ts new file mode 100644 index 0000000..e214468 --- /dev/null +++ b/packages/pieces/community/google-calendar/src/lib/actions/create-event.ts @@ -0,0 +1,144 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { googleCalendarCommon } from '../common'; +import dayjs from 'dayjs'; +import { googleCalendarAuth } from '../../'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; + +export const createEvent = createAction({ + auth: googleCalendarAuth, + name: 'create_google_calendar_event', + description: 'Add Event', + displayName: 'Create Event', + props: { + calendar_id: googleCalendarCommon.calendarDropdown('writer'), + title: Property.ShortText({ + displayName: 'Title of the event', + required: true, + }), + start_date_time: Property.DateTime({ + displayName: 'Start date time of the event', + required: true, + }), + end_date_time: Property.DateTime({ + displayName: 'End date time of the event', + description: "By default it'll be 30 min post start time", + required: false, + }), + location: Property.ShortText({ + displayName: 'Location', + required: false, + }), + /*attachment: Property.ShortText({ + displayName: 'Attachment', + description: 'URL of the file to be attached', + required: false, + }),*/ + description: Property.LongText({ + displayName: 'Description', + description: 'Description of the event. You can use HTML tags here.', + required: false, + }), + colorId: googleCalendarCommon.colorId, + attendees: Property.Array({ + displayName: 'Attendees', + description: 'Emails of the attendees (guests)', + required: false, + }), + guests_can_modify: Property.Checkbox({ + displayName: 'Guests can modify', + defaultValue: false, + required: false, + }), + guests_can_invite_others: Property.Checkbox({ + displayName: 'Guests can invite others', + defaultValue: false, + required: false, + }), + guests_can_see_other_guests: Property.Checkbox({ + displayName: 'Guests can see other guests', + defaultValue: false, + required: false, + }), + send_notifications: Property.StaticDropdown({ + displayName: 'Send Notifications', + defaultValue: 'all', + options: { + options: [ + { label: 'Yes, to everyone', value: 'all' }, + { + label: 'To non-Google Calendar guests only', + value: 'externalOnly', + }, + { label: 'To no one', value: 'none' }, + ], + }, + required: true, + }), + }, + async run(configValue) { + // docs: https://developers.google.com/calendar/api/v3/reference/events/insert + const { + calendar_id: calendarId, + title: summary, + start_date_time, + end_date_time, + location, + description, + colorId, + guests_can_modify: guestsCanModify, + guests_can_invite_others: guestsCanInviteOthers, + guests_can_see_other_guests: guestsCanSeeOtherGuests, + } = configValue.propsValue; + + const start = { + dateTime: dayjs(start_date_time).format('YYYY-MM-DDTHH:mm:ss.sssZ'), + }; + const endTime = end_date_time + ? end_date_time + : dayjs(start_date_time).add(30, 'm'); + const end = { + dateTime: dayjs(endTime).format('YYYY-MM-DDTHH:mm:ss.sssZ'), + }; + + /*const attachment = { + fileUrl: configValue.propsValue.attachment, + };*/ + + const attendeesArray = configValue.propsValue.attendees as string[]; + + const sendNotifications = configValue.propsValue.send_notifications; + + const attendeesObject = []; + if (attendeesArray) { + for (const attendee of attendeesArray) { + attendeesObject.push({ email: attendee }); + } + } + + const authClient = new OAuth2Client(); + authClient.setCredentials(configValue.auth); + + const calendar = google.calendar({ version: 'v3', auth: authClient }); + + const response = await calendar.events.insert({ + calendarId, + sendUpdates: sendNotifications, + requestBody: { + summary, + start, + end, + colorId, + //attachments: configValue.propsValue.attachment ? [attachment] : [], + location: location ?? '', + description: description ?? '', + attendees: attendeesObject, + guestsCanInviteOthers, + guestsCanModify, + guestsCanSeeOtherGuests, + }, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/google-calendar/src/lib/actions/create-quick-event.ts b/packages/pieces/community/google-calendar/src/lib/actions/create-quick-event.ts new file mode 100644 index 0000000..08d51ab --- /dev/null +++ b/packages/pieces/community/google-calendar/src/lib/actions/create-quick-event.ts @@ -0,0 +1,67 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + AuthenticationType, + httpClient, +} from '@activepieces/pieces-common'; +import { googleCalendarCommon } from '../common'; +import { googleCalendarAuth } from '../../'; + +export const createQuickCalendarEvent = createAction({ + auth: googleCalendarAuth, + name: 'create_quick_event', + description: 'Add Quick Calendar Event', + displayName: 'Create Quick Event', + props: { + calendar_id: googleCalendarCommon.calendarDropdown('writer'), + text: Property.LongText({ + displayName: 'Summary', + description: 'The text describing the event to be created', + required: true, + }), + send_updates: Property.StaticDropdown({ + displayName: 'Send Updates', + description: + 'Guests who should receive notifications about the creation of the new event.', + required: false, + options: { + disabled: false, + options: [ + { + label: 'All', + value: 'all', + }, + { + label: 'External Only', + value: 'externalOnly', + }, + { + label: 'none', + value: 'none', + }, + ], + }, + }), + }, + async run(configValue) { + // docs: https://developers.google.com/calendar/api/v3/reference/events/quickAdd + const calendarId = configValue.propsValue['calendar_id']; + const url = `${googleCalendarCommon.baseUrl}/calendars/${calendarId}/events/quickAdd`; + const qParams: Record = { + text: configValue.propsValue['text'], + sendUpdates: configValue.propsValue['send_updates'] || 'none', + }; + const request: HttpRequest> = { + method: HttpMethod.POST, + url, + body: {}, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: configValue.auth.access_token, + }, + queryParams: qParams, + }; + return await httpClient.sendRequest(request); + }, +}); diff --git a/packages/pieces/community/google-calendar/src/lib/actions/delete-event.action.ts b/packages/pieces/community/google-calendar/src/lib/actions/delete-event.action.ts new file mode 100644 index 0000000..b1c4891 --- /dev/null +++ b/packages/pieces/community/google-calendar/src/lib/actions/delete-event.action.ts @@ -0,0 +1,35 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { googleCalendarAuth } from '../../index'; +import { googleCalendarCommon } from '../common'; + +export const deleteEventAction = createAction({ + displayName: 'Delete Event', + auth: googleCalendarAuth, + name: 'delete_event', + description: 'Deletes an event from Google Calendar.', + props: { + calendar_id: googleCalendarCommon.calendarDropdown('writer'), + eventId: Property.ShortText({ + displayName: 'Event ID', + required: true, + }), + }, + async run(context) { + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + + const calendarId = context.propsValue.calendar_id; + const eventId = context.propsValue.eventId; + + const calendar = google.calendar({ version: 'v3', auth: authClient }); + + const response = await calendar.events.delete({ + calendarId, + eventId, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/google-calendar/src/lib/actions/get-events.ts b/packages/pieces/community/google-calendar/src/lib/actions/get-events.ts new file mode 100644 index 0000000..e2d1b724b --- /dev/null +++ b/packages/pieces/community/google-calendar/src/lib/actions/get-events.ts @@ -0,0 +1,112 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + AuthenticationType, + httpClient, +} from '@activepieces/pieces-common'; +import { googleCalendarCommon } from '../common'; +import dayjs from 'dayjs'; +import { googleCalendarAuth } from '../../'; + +export const getEvents = createAction({ + auth: googleCalendarAuth, + name: 'google_calendar_get_events', + description: 'Get Events', + displayName: 'Get all Events', + props: { + calendar_id: googleCalendarCommon.calendarDropdown('writer'), + event_types: Property.StaticMultiSelectDropdown({ + displayName: 'Event types', + description: 'Select event types', + required: true, + defaultValue: ['default', 'focusTime', 'outOfOffice'], + options: { + options: [ + { + label: 'Default', + value: 'default', + }, + { + label: 'Out Of Office', + value: 'outOfOffice', + }, + { + label: 'Focus Time', + value: 'focusTime', + }, + { + label: 'Working Location', + value: 'workingLocation', + }, + ], + }, + }), + search: Property.ShortText({ + displayName: 'Search Term', + required: false, + }), + start_date: Property.DateTime({ + displayName: 'Date from', + required: false, + }), + end_date: Property.DateTime({ + displayName: 'Date to', + required: false, + }), + singleEvents: Property.Checkbox({ + displayName: 'Expand Recurring Event?', + description: "Whether to expand recurring events into instances and only return single one-off events and instances of recurring events, but not the underlying recurring events themselves.", + required: true, + defaultValue: false, + }), + }, + async run(configValue) { + // docs: https://developers.google.com/calendar/api/v3/reference/events/list + const { + calendar_id: calendarId, + start_date, + end_date, + search, + event_types, + singleEvents, + } = configValue.propsValue; + const { access_token: token } = configValue.auth; + const queryParams: Record = { showDeleted: 'false' }; + let url = `${googleCalendarCommon.baseUrl}/calendars/${calendarId}/events`; + + if(singleEvents !== null) { + queryParams['singleEvents'] = singleEvents ? 'true' : 'false'; + } + + if (search) { + queryParams['q'] = `"${search}"`; + } + + // date range + if (start_date) { + queryParams['timeMin'] = dayjs(start_date).format( + 'YYYY-MM-DDTHH:mm:ss.sssZ' + ); + } + if (start_date && end_date) { + queryParams['timeMax'] = dayjs(end_date).format( + 'YYYY-MM-DDTHH:mm:ss.sssZ' + ); + } + // filter by event type + if (event_types.length > 0) { + url += `?${event_types.map((type) => `eventTypes=${type}`).join('&')}`; + } + const request: HttpRequest> = { + method: HttpMethod.GET, + url, + queryParams, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + }; + return await httpClient.sendRequest(request); + }, +}); diff --git a/packages/pieces/community/google-calendar/src/lib/actions/update-event.action.ts b/packages/pieces/community/google-calendar/src/lib/actions/update-event.action.ts new file mode 100644 index 0000000..4a96bf4 --- /dev/null +++ b/packages/pieces/community/google-calendar/src/lib/actions/update-event.action.ts @@ -0,0 +1,129 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { google, calendar_v3 } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { googleCalendarAuth } from '../../index'; +import { googleCalendarCommon } from '../common'; +import dayjs from 'dayjs'; + +export const updateEventAction = createAction({ + displayName: 'Update Event', + auth: googleCalendarAuth, + name: 'update_event', + description: 'Updates an event in Google Calendar.', + props: { + calendar_id: googleCalendarCommon.calendarDropdown('writer'), + eventId: Property.ShortText({ + displayName: 'Event ID', + required: true, + }), + title: Property.ShortText({ + displayName: 'Title of the event', + required: false, + }), + start_date_time: Property.DateTime({ + displayName: 'Start date time of the event', + required: false, + }), + end_date_time: Property.DateTime({ + displayName: 'End date time of the event', + required: false, + }), + location: Property.ShortText({ + displayName: 'Location', + required: false, + }), + description: Property.LongText({ + displayName: 'Description', + description: 'Description of the event. You can use HTML tags here.', + required: false, + }), + colorId: googleCalendarCommon.colorId, + attendees: Property.Array({ + displayName: 'Attendees', + description: 'Emails of the attendees (guests)', + required: false, + }), + guests_can_modify: Property.Checkbox({ + displayName: 'Guests can modify', + defaultValue: false, + required: false, + }), + guests_can_invite_others: Property.Checkbox({ + displayName: 'Guests can invite others', + defaultValue: false, + required: false, + }), + guests_can_see_other_guests: Property.Checkbox({ + displayName: 'Guests can see other guests', + defaultValue: false, + required: false, + }), + }, + async run(context) { + const { + calendar_id, + eventId, + title, + start_date_time, + end_date_time, + location, + description, + colorId, + guests_can_invite_others, + guests_can_modify, + guests_can_see_other_guests, + } = context.propsValue; + + const attendees = context.propsValue.attendees as string[]; + + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + const calendar = google.calendar({ version: 'v3', auth: authClient }); + + // Note that each patch request consumes three quota units; + // prefer using a get followed by an update + const currentEvent = await calendar.events.get({ + calendarId: calendar_id, + eventId: eventId, + }); + + let attendeeFormattedList: calendar_v3.Schema$EventAttendee[] = []; + if (Array.isArray(attendees) && attendees.length > 0) { + attendeeFormattedList = attendees.map((email) => ({ email })); + } else if ( + currentEvent.data.attendees && + Array.isArray(currentEvent.data.attendees) + ) { + attendeeFormattedList = currentEvent.data.attendees; + } + + const response = await calendar.events.update({ + calendarId: calendar_id, + eventId: eventId, + requestBody: { + summary: title ?? currentEvent.data.summary, + attendees: attendeeFormattedList, + description: description ?? currentEvent.data.description, + colorId: colorId, + location: location ?? currentEvent.data.location, + start: start_date_time + ? { + dateTime: dayjs(start_date_time).format( + 'YYYY-MM-DDTHH:mm:ss.sssZ' + ), + } + : currentEvent.data.start, + end: end_date_time + ? { + dateTime: dayjs(end_date_time).format('YYYY-MM-DDTHH:mm:ss.sssZ'), + } + : currentEvent.data.end, + guestsCanInviteOthers: guests_can_invite_others, + guestsCanModify: guests_can_modify, + guestsCanSeeOtherGuests: guests_can_see_other_guests, + }, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/google-calendar/src/lib/common/helper.ts b/packages/pieces/community/google-calendar/src/lib/common/helper.ts new file mode 100644 index 0000000..6f23c65 --- /dev/null +++ b/packages/pieces/community/google-calendar/src/lib/common/helper.ts @@ -0,0 +1,154 @@ +import { OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { randomUUID } from 'crypto'; +import { googleCalendarCommon } from '.'; +import { + GoogleWatchResponse, + GoogleWatchType, + CalendarObject, + CalendarList, + GoogleCalendarEvent, + GoogleCalendarEventList, + GetColorsResponse, +} from './types'; + +export async function stopWatchEvent( + body: GoogleWatchResponse, + authProp: OAuth2PropertyValue +) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${googleCalendarCommon.baseUrl}/channels/stop`, + body: { + id: body?.id, + resourceId: body?.resourceId, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp.access_token, + }, + }; + await httpClient.sendRequest(request); +} + +export async function watchEvent( + calendarId: string, + webhookUrl: string, + authProp: OAuth2PropertyValue +): Promise { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${googleCalendarCommon.baseUrl}/calendars/${calendarId}/events/watch`, + body: { + id: randomUUID(), + type: GoogleWatchType.WEBHOOK, + address: webhookUrl, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp.access_token, + }, + }; + const { body: webhook } = await httpClient.sendRequest( + request + ); + return webhook; +} + +export async function getCalendars( + authProp: OAuth2PropertyValue, + minAccessRole?: 'writer' +): Promise { + // docs: https://developers.google.com/calendar/api/v3/reference/calendarList/list + const queryParams: Record = { + showDeleted: 'false', + }; + if (minAccessRole) { + queryParams['minAccessRole'] = minAccessRole; + } + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${googleCalendarCommon.baseUrl}/users/me/calendarList`, + queryParams: queryParams, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp.access_token, + }, + }; + const response = await httpClient.sendRequest(request); + return response.body.items; +} + +export async function getColors( + authProp: OAuth2PropertyValue +): Promise { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${googleCalendarCommon.baseUrl}/colors`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp.access_token, + }, + }; + const response = await httpClient.sendRequest(request); + return response.body; +} + +export async function getEvents( + calendarId: string, + expandRecurringEvent: boolean, + authProp: OAuth2PropertyValue, + minUpdated?: Date +): Promise { + // docs: https://developers.google.com/calendar/api/v3/reference/events/list + const now = new Date(); + const yesterday = new Date(); + yesterday.setDate(now.getDate() - 1); + + const qParams: Record = { + updatedMin: minUpdated?.toISOString() ?? yesterday.toISOString(), + maxResults: '2500', // Modified + orderBy: 'updated', + singleEvents: expandRecurringEvent ? 'true' : 'false', + showDeleted: 'true', + }; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${googleCalendarCommon.baseUrl}/calendars/${calendarId}/events`, + queryParams: qParams, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp.access_token, + }, + }; + + let eventList: GoogleCalendarEvent[] = []; + let pageToken = ''; + do { + qParams['pageToken'] = pageToken; + const { body: res } = await httpClient.sendRequest( + request + ); + if (res.items.length > 0) { + eventList = [...eventList, ...res.items]; + } + pageToken = res.nextPageToken; + } while (pageToken); + + return eventList; +} + +export async function getLatestEvent( + calendarId: string, + authProp: OAuth2PropertyValue +): Promise { + const eventList = await getEvents(calendarId, false, authProp); + const lastUpdatedEvent = eventList.pop()!; // You can retrieve the last updated event. + return lastUpdatedEvent; +} diff --git a/packages/pieces/community/google-calendar/src/lib/common/index.ts b/packages/pieces/community/google-calendar/src/lib/common/index.ts new file mode 100644 index 0000000..bbb5cf8 --- /dev/null +++ b/packages/pieces/community/google-calendar/src/lib/common/index.ts @@ -0,0 +1,58 @@ +import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { getCalendars, getColors } from './helper'; + +export const googleCalendarCommon = { + baseUrl: 'https://www.googleapis.com/calendar/v3', + calendarDropdown: (minAccessRole?: 'writer') => { + return Property.Dropdown({ + displayName: 'Calendar', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const authProp = auth as OAuth2PropertyValue; + const calendars = await getCalendars(authProp, minAccessRole); + return { + disabled: false, + options: calendars.map((calendar) => { + return { + label: calendar.summary, + value: calendar.id, + }; + }), + }; + }, + }); + }, + colorId: Property.Dropdown({ + displayName: 'Color', + refreshers: [], + required: false, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const authProp = auth as OAuth2PropertyValue; + const response = await getColors(authProp); + return { + disabled: false, + options: Object.entries(response.event).map(([key, value]) => { + return { + label: value.background, + value: key, + }; + }), + }; + }, + }), +}; diff --git a/packages/pieces/community/google-calendar/src/lib/common/types.ts b/packages/pieces/community/google-calendar/src/lib/common/types.ts new file mode 100644 index 0000000..f7a78a2 --- /dev/null +++ b/packages/pieces/community/google-calendar/src/lib/common/types.ts @@ -0,0 +1,246 @@ +export enum GoogleWatchType { + WEBHOOK = 'web_hook', +} + +export enum GoogleCalendarKind { + CALENDAR_LIST = 'calendar#calendarList', + CALENDAR_ENTRY = 'calendar#calendarListEntry', + CALENDAR_EVENT = 'calendar#event', + CALENDAR_EVENT_LIST = 'calendar#events', + EVENT_WATCH = 'api#channel', + CALENDAR_COLORS = 'calendar#colors', +} + +export interface CalendarList { + kind: GoogleCalendarKind.CALENDAR_LIST; + etag: string; + nextPageToken: string; + nextSyncToken: string; + items: CalendarObject[]; +} + +export interface CalendarObject { + kind: GoogleCalendarKind.CALENDAR_ENTRY; + etag: string; + id: string; + summary: string; + description: string; + location: string; + timeZone: string; + summaryOverride: string; + colorId: string; + backgroundColor: string; + foregroundColor: string; + hidden: boolean; + selected: boolean; + accessRole: string; + defaultReminders: [ + { + method: string; + minutes: number; + } + ]; + notificationSettings: { + notifications: [ + { + type: string; + method: string; + } + ]; + }; + primary: boolean; + deleted: boolean; + conferenceProperties: { + allowedConferenceSolutionTypes: string[]; + }; +} + +export interface GoogleWatchResponse { + kind: GoogleCalendarKind.EVENT_WATCH; + id: string; + resourceId: string; + resourceUri: string; + token: string; + expiration: number; +} + +interface Attendee { + id: string; + email: string; + displayName: string; + organizer: boolean; + self: boolean; + resource: boolean; + optional: boolean; + responseStatus: string; + comment: string; + additionalGuests: BigInteger; +} + +export interface GoogleCalendarEvent { + kind: GoogleCalendarKind.CALENDAR_EVENT; + etag: string; + id: string; + status: string; + htmlLink: string; + created: string; + updated: string; + summary: string; + description: string; + location: string; + colorId: string; + creator: { + id: string; + email: string; + displayName: string; + self: boolean; + }; + organizer: { + id: string; + email: string; + displayName: string; + self: boolean; + }; + start: { + date: Date; + dateTime: Date; + timeZone: string; + }; + end: { + date: Date; + dateTime: Date; + timeZone: string; + }; + endTimeUnspecified: boolean; + recurrence: [string]; + recurringEventId: string; + originalStartTime: { + date: Date; + dateTime: Date; + timeZone: string; + }; + transparency: string; + visibility: string; + iCalUID: string; + sequence: BigInteger; + attendees: Attendee[]; + attendeesOmitted: boolean; + extendedProperties: { + private: { + key: string; + }; + shared: { + key: string; + }; + }; + hangoutLink: string; + conferenceData: { + createRequest: { + requestId: string; + conferenceSolutionKey: { + type: string; + }; + status: { + statusCode: string; + }; + }; + entryPoints: [ + { + entryPointType: string; + uri: string; + label: string; + pin: string; + accessCode: string; + meetingCode: string; + passcode: string; + password: string; + } + ]; + conferenceSolution: { + key: { + type: string; + }; + name: string; + iconUri: string; + }; + conferenceId: string; + signature: string; + notes: string; + }; + gadget: { + type: string; + title: string; + link: string; + iconLink: string; + width: BigInteger; + height: BigInteger; + display: string; + preferences: { + key: string; + }; + }; + anyoneCanAddSelf: boolean; + guestsCanInviteOthers: boolean; + guestsCanModify: boolean; + guestsCanSeeOtherGuests: boolean; + privateCopy: boolean; + locked: boolean; + reminders: { + useDefault: boolean; + overrides: [ + { + method: string; + minutes: BigInteger; + } + ]; + }; + source: { + url: string; + title: string; + }; + attachments: [ + { + fileUrl: string; + title: string; + mimeType: string; + iconLink: string; + fileId: string; + } + ]; + eventType: string; +} + +export interface GoogleCalendarEventList { + kind: GoogleCalendarKind.CALENDAR_EVENT_LIST; + etag: string; + summary: string; + description: string; + updated: number; + timeZone: string; + accessRole: string; + defaultReminders: [ + { + method: string; + minutes: BigInteger; + } + ]; + nextPageToken: string; + nextSyncToken: string; + items: GoogleCalendarEvent[]; +} + +export interface GetColorsResponse { + kind: GoogleCalendarKind.CALENDAR_COLORS; + calendar: { + [s: string]: { + background: string; + foreground: string; + }; + }; + event: { + [s: string]: { + background: string; + foreground: string; + }; + }; +} diff --git a/packages/pieces/community/google-calendar/src/lib/google-calendar.mdx b/packages/pieces/community/google-calendar/src/lib/google-calendar.mdx new file mode 100644 index 0000000..898feab --- /dev/null +++ b/packages/pieces/community/google-calendar/src/lib/google-calendar.mdx @@ -0,0 +1,25 @@ +--- +title: 'Google Calendar' +description: '' +--- + +## Set up and run an app that calls a Google calendar API. + +1. In the Google Cloud console, enable the Google Calendar API. +2. Click In the Google Cloud console, and go to Menu menu > APIs & Services > Credentials. +3. Click Create Credentials > OAuth client ID. +4. In the Name field, type a name for the credential. This name is only shown in the Google Cloud console. +5. Click Create. The OAuth client created screen appears, showing your new Client ID and Client secret. +6. Click OK. The newly created credential appears under OAuth 2.0 Client IDs. + +--- + +## Triggers + +TRIGGERS + +--- + +## Actions + +ACTIONS diff --git a/packages/pieces/community/google-calendar/src/lib/triggers/calendar-event.ts b/packages/pieces/community/google-calendar/src/lib/triggers/calendar-event.ts new file mode 100644 index 0000000..11235cf --- /dev/null +++ b/packages/pieces/community/google-calendar/src/lib/triggers/calendar-event.ts @@ -0,0 +1,139 @@ +import { createTrigger, PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { googleCalendarCommon } from '../common'; +import { getEvents } from '../common/helper'; +import { GoogleCalendarEvent } from '../common/types'; +import { googleCalendarAuth } from '../../'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +const polling: Polling< + PiecePropValueSchema, + { calendarId?: string; expandRecurringEvent: boolean } +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue: { calendarId, expandRecurringEvent }, lastFetchEpochMS }) => { + let minUpdated = new Date(lastFetchEpochMS); + // Google Calendar API breaks if minUpdated is too far in the past + if (lastFetchEpochMS === 0) { + const now = new Date(); + const yesterday = new Date(); + yesterday.setDate(now.getDate() - 7); + + minUpdated = yesterday; + } + + const currentValues: GoogleCalendarEvent[] = + (await getEvents(calendarId!, expandRecurringEvent, auth, minUpdated)) ?? []; + const items = currentValues.map((item) => ({ + epochMilliSeconds: new Date(item.updated).getTime(), + data: item, + })); + return items; + }, +}; + +export const calendarEventChanged = createTrigger({ + // docs: https://developers.google.com/calendar/api/guides/push + auth: googleCalendarAuth, + name: 'new_or_updated_event', + displayName: 'New or Updated Event', + description: 'Triggers when an event is added or updated', + props: { + calendar_id: googleCalendarCommon.calendarDropdown(), + expandRecurringEvent: Property.Checkbox({ + displayName: 'Expand Recurring Event?', + description: 'If true, the trigger will activate for every occurrence of a recurring event.', + required: true, + defaultValue: false, + }), + }, + sampleData: { + kind: 'calendar#event', + etag: '3350849506974000', + id: '0nsfi5ttd2b17ac76ma2f37oi9', + htmlLink: 'https://www.google.com/calendar/event?eid=kgjb90uioj4klrgfmdsnjsjvlgkm', + summary: 'ap-event-test', + created: '2023-02-03T11:36:36.000Z', + updated: '2023-02-03T11:45:53.487Z', + description: 'Sample description', + status: 'canceled', + creator: { + email: 'test@test.com', + self: true, + }, + organizer: { + email: 'test@test.com', + self: true, + }, + start: { + dateTime: '2023-02-02T22:30:00+03:00', + timeZone: 'Asia/Amman', + }, + end: { + dateTime: '2023-02-02T23:30:00+03:00', + timeZone: 'Asia/Amman', + }, + transparency: 'transparent', + iCalUID: '0nsfi5ttd2b17ac76ma2f37oi9@google.com', + sequence: 1, + attendees: [ + { + email: 'attende@test.com', + responseStatus: 'needsAction', + }, + { + email: 'test@test.com', + organizer: true, + self: true, + responseStatus: 'accepted', + }, + ], + reminders: { + useDefault: true, + }, + eventType: 'default', + }, + type: TriggerStrategy.POLLING, + async test({ store, auth, propsValue, files }) { + return await pollingHelper.test(polling, { + store, + auth, + propsValue: { + calendarId: propsValue.calendar_id, + expandRecurringEvent: propsValue.expandRecurringEvent, + }, + files, + }); + }, + async onEnable({ store, auth, propsValue }) { + await pollingHelper.onEnable(polling, { + store, + auth, + propsValue: { + calendarId: propsValue.calendar_id, + expandRecurringEvent: propsValue.expandRecurringEvent, + }, + }); + }, + async onDisable({ store, auth, propsValue }) { + await pollingHelper.onDisable(polling, { + store, + auth, + propsValue: { + calendarId: propsValue.calendar_id, + expandRecurringEvent: propsValue.expandRecurringEvent, + }, + }); + }, + async run({ store, auth, propsValue, files }) { + return await pollingHelper.poll(polling, { + store, + auth, + propsValue: { + calendarId: propsValue.calendar_id, + expandRecurringEvent: propsValue.expandRecurringEvent, + }, + files, + }); + }, +}); diff --git a/packages/pieces/community/google-calendar/tsconfig.json b/packages/pieces/community/google-calendar/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/google-calendar/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/google-calendar/tsconfig.lib.json b/packages/pieces/community/google-calendar/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/google-calendar/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/google-contacts/.babelrc b/packages/pieces/community/google-contacts/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/google-contacts/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/google-contacts/.eslintrc.json b/packages/pieces/community/google-contacts/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/google-contacts/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/google-contacts/README.md b/packages/pieces/community/google-contacts/README.md new file mode 100644 index 0000000..5e5c6e3 --- /dev/null +++ b/packages/pieces/community/google-contacts/README.md @@ -0,0 +1,7 @@ +# pieces-google-contacts + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-google-contacts` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/google-contacts/package.json b/packages/pieces/community/google-contacts/package.json new file mode 100644 index 0000000..ff349ee --- /dev/null +++ b/packages/pieces/community/google-contacts/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-google-contacts", + "version": "0.3.8" +} diff --git a/packages/pieces/community/google-contacts/project.json b/packages/pieces/community/google-contacts/project.json new file mode 100644 index 0000000..e85127c --- /dev/null +++ b/packages/pieces/community/google-contacts/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-google-contacts", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/google-contacts/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/google-contacts", + "tsConfig": "packages/pieces/community/google-contacts/tsconfig.lib.json", + "packageJson": "packages/pieces/community/google-contacts/package.json", + "main": "packages/pieces/community/google-contacts/src/index.ts", + "assets": [ + "packages/pieces/community/google-contacts/*.md", + { + "input": "packages/pieces/community/google-contacts/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/google-contacts/src/index.ts b/packages/pieces/community/google-contacts/src/index.ts new file mode 100644 index 0000000..f3c7044 --- /dev/null +++ b/packages/pieces/community/google-contacts/src/index.ts @@ -0,0 +1,53 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { googleContactsAddContactAction } from './lib/action/create-contact'; +import { googleContactsUpdateContactAction } from './lib/action/update-contact'; +import { googleContactsSearchContactsAction } from './lib/action/search-contact'; +import { googleContactsCommon } from './lib/common'; +import { googleContactNewOrUpdatedContact } from './lib/trigger/new-contact'; + +export const googleContactsAuth = PieceAuth.OAuth2({ + description: '', + + authUrl: 'https://accounts.google.com/o/oauth2/auth', + tokenUrl: 'https://oauth2.googleapis.com/token', + required: true, + scope: ['https://www.googleapis.com/auth/contacts'], +}); + +export const googleContacts = createPiece({ + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/google-contacts.png', + categories: [PieceCategory.SALES_AND_CRM], + actions: [ + googleContactsAddContactAction, + googleContactsUpdateContactAction, + googleContactsSearchContactsAction, + createCustomApiCallAction({ + baseUrl: () => googleContactsCommon.baseUrl, + auth: googleContactsAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + displayName: 'Google Contacts', + description: 'Stay connected and organized', + + authors: [ + 'Abdallah-Alwarawreh', + 'Salem-Alaa', + 'kishanprmr', + 'MoShizzle', + 'khaledmashaly', + 'abuaboud', + 'ikus060', + ], + triggers: [googleContactNewOrUpdatedContact], + auth: googleContactsAuth, +}); diff --git a/packages/pieces/community/google-contacts/src/lib/action/create-contact.ts b/packages/pieces/community/google-contacts/src/lib/action/create-contact.ts new file mode 100644 index 0000000..52f03c0 --- /dev/null +++ b/packages/pieces/community/google-contacts/src/lib/action/create-contact.ts @@ -0,0 +1,92 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + AuthenticationType, + httpClient, +} from '@activepieces/pieces-common'; +import { googleContactsCommon } from '../common'; +import { googleContactsAuth } from '../../'; + +export const googleContactsAddContactAction = createAction({ + auth: googleContactsAuth, + name: 'add_contact', + description: 'Add a contact to a Google Contacts account', + displayName: 'Add Contact', + props: { + firstName: Property.ShortText({ + displayName: 'First Name', + description: 'The first name of the contact', + required: true, + }), + middleName: Property.ShortText({ + displayName: 'Middle Name', + description: 'The middle name of the contact', + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + description: 'The last name of the contact', + required: true, + }), + jobTitle: Property.ShortText({ + displayName: 'Job Title', + description: 'The job title of the contact', + required: false, + }), + company: Property.ShortText({ + displayName: 'Company', + description: 'The company of the contact', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + description: 'The email address of the contact', + required: false, + }), + phoneNumber: Property.ShortText({ + displayName: 'Phone Number', + description: 'The phone number of the contact', + required: false, + }), + }, + async run(context) { + let requestBody = { + names: [ + { + givenName: context.propsValue['firstName'], + middleName: context.propsValue['middleName'], + familyName: context.propsValue['lastName'], + }, + ], + }; + const contact: Record = {}; + if (context.propsValue['email']) { + contact['emailAddresses'] = [{ value: context.propsValue['email'] }]; + } + + if (context.propsValue['phoneNumber']) { + contact['phoneNumbers'] = [{ value: context.propsValue['phoneNumber'] }]; + } + + if (context.propsValue['company'] || context.propsValue['jobTitle']) { + contact['organizations'] = [ + { + name: context.propsValue['company'] || undefined, + title: context.propsValue['jobTitle'] || undefined, + }, + ]; + } + requestBody = { ...requestBody, ...contact }; + const request: HttpRequest> = { + method: HttpMethod.POST, + url: `${googleContactsCommon.baseUrl}:createContact`, + body: requestBody, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }; + return (await httpClient.sendRequest(request)).body; + }, +}); diff --git a/packages/pieces/community/google-contacts/src/lib/action/search-contact.ts b/packages/pieces/community/google-contacts/src/lib/action/search-contact.ts new file mode 100644 index 0000000..40b9e8c --- /dev/null +++ b/packages/pieces/community/google-contacts/src/lib/action/search-contact.ts @@ -0,0 +1,88 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + AuthenticationType, + httpClient, + QueryParams, +} from '@activepieces/pieces-common'; +import { googleContactsCommon } from '../common'; +import { googleContactsAuth } from '../../'; + +export const googleContactsSearchContactsAction = createAction({ + auth: googleContactsAuth, + name: 'search_contact', + description: 'Search contacts in Google Contacts account.', + displayName: 'Search Contacts', + props: { + query: Property.ShortText({ + displayName: 'Query', + description: `The plain-text query for the request.The query is used to match prefix phrases of the fields on a person. For example, a person with name "foo name" matches queries such as "f", "fo", "foo", "foo n", "nam", etc., but not "oo n".`, + required: true, + }), + readMask: Property.StaticMultiSelectDropdown({ + displayName: 'Read Mask', + description: + 'A field mask to restrict which fields on each person are returned.', + required: true, + options: { + options: [ + { label: 'addresses', value: 'addresses' }, + { label: 'ageRanges', value: 'ageRanges' }, + { label: 'biographies', value: 'biographies' }, + { label: 'birthdays', value: 'birthdays' }, + { label: 'calendarUrls', value: 'calendarUrls' }, + { label: 'clientData', value: 'clientData' }, + { label: 'coverPhotos', value: 'coverPhotos' }, + { label: 'emailAddresses', value: 'emailAddresses' }, + { label: 'events', value: 'events' }, + { label: 'externalIds', value: 'externalIds' }, + { label: 'genders', value: 'genders' }, + { label: 'imClients', value: 'imClients' }, + { label: 'interests', value: 'interests' }, + { label: 'locales', value: 'locales' }, + { label: 'locations', value: 'locations' }, + { label: 'memberships', value: 'memberships' }, + { label: 'metadata', value: 'metadata' }, + { label: 'miscKeywords', value: 'miscKeywords' }, + { label: 'names', value: 'names' }, + { label: 'nicknames', value: 'nicknames' }, + { label: 'occupations', value: 'occupations' }, + { label: 'organizations', value: 'organizations' }, + { label: 'phoneNumbers', value: 'phoneNumbers' }, + { label: 'photos', value: 'photos' }, + { label: 'relations', value: 'relations' }, + { label: 'sipAddresses', value: 'sipAddresses' }, + { label: 'skills', value: 'skills' }, + { label: 'urls', value: 'urls' }, + { label: 'userDefined', value: 'userDefined' }, + ], + }, + defaultValue: ['names', 'emailAddresses'], + }), + pageSize: Property.Number({ + displayName: 'Page Size', + description: 'The number of results to return. Maximum 30.', + required: false, + }), + }, + async run(context) { + const qs: QueryParams = { + query: context.propsValue['query'], + readMask: context.propsValue['readMask'].join(','), + }; + if (context.propsValue['pageSize']) { + qs['pageSize'] = String(context.propsValue['pageSize']); + } + const request: HttpRequest> = { + method: HttpMethod.GET, + url: `${googleContactsCommon.baseUrl}:searchContacts`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + queryParams: qs, + }; + return (await httpClient.sendRequest(request)).body; + }, +}); diff --git a/packages/pieces/community/google-contacts/src/lib/action/update-contact.ts b/packages/pieces/community/google-contacts/src/lib/action/update-contact.ts new file mode 100644 index 0000000..946ad1e --- /dev/null +++ b/packages/pieces/community/google-contacts/src/lib/action/update-contact.ts @@ -0,0 +1,133 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + AuthenticationType, + httpClient, + QueryParams, +} from '@activepieces/pieces-common'; +import { googleContactsCommon } from '../common'; +import { googleContactsAuth } from '../../'; + +export const googleContactsUpdateContactAction = createAction({ + auth: googleContactsAuth, + name: 'update_contact', + description: 'Update a contact in Google Contacts account.', + displayName: 'Update Contact', + props: { + resourceName: Property.ShortText({ + displayName: 'Resource Name', + description: + 'The resource name for the person, assigned by the server. An ASCII string in the form of people/{person_id}.', + required: true, + }), + etag: Property.ShortText({ + displayName: 'Etag', + description: + "The `etag` ensures contact updates only apply if the contact hasn't changed since last retrieved.", + required: true, + }), + updatePersonFields: Property.StaticMultiSelectDropdown({ + displayName: 'Update Field Mask', + description: + 'A field mask to restrict which fields on the person are updated.', + required: true, + options: { + options: [ + { label: 'Names', value: 'names' }, + { label: 'Email', value: 'emailAddresses' }, + { label: 'Phone Number', value: 'phoneNumbers' }, + { label: 'Job Title / Company', value: 'organizations' }, + ], + }, + defaultValue: ['names', 'emailAddresses'], + }), + firstName: Property.ShortText({ + displayName: 'First Name', + description: 'The first name of the contact', + required: false, + }), + middleName: Property.ShortText({ + displayName: 'Middle Name', + description: 'The middle name of the contact', + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + description: 'The last name of the contact', + required: false, + }), + jobTitle: Property.ShortText({ + displayName: 'Job Title', + description: 'The job title of the contact', + required: false, + }), + company: Property.ShortText({ + displayName: 'Company', + description: 'The company of the contact', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + description: 'The email address of the contact', + required: false, + }), + phoneNumber: Property.ShortText({ + displayName: 'Phone Number', + description: 'The phone number of the contact', + required: false, + }), + }, + async run(context) { + const resourceName = context.propsValue['resourceName'].substring(6); + const requestBody: Record = { + etag: context.propsValue['etag'], + }; + const qs: QueryParams = { + updatePersonFields: context.propsValue['updatePersonFields'].join(','), + }; + if ( + context.propsValue['firstName'] || + context.propsValue['middleName'] || + context.propsValue['lastName'] + ) { + requestBody['names'] = [ + { + givenName: context.propsValue['firstName'] || undefined, + middleName: context.propsValue['middleName'] || undefined, + familyName: context.propsValue['lastName'] || undefined, + }, + ]; + } + if (context.propsValue['email']) { + requestBody['emailAddresses'] = [{ value: context.propsValue['email'] }]; + } + if (context.propsValue['phoneNumber']) { + requestBody['phoneNumbers'] = [ + { value: context.propsValue['phoneNumber'] }, + ]; + } + if (context.propsValue['company'] || context.propsValue['jobTitle']) { + requestBody['organizations'] = [ + { + name: context.propsValue['company'] || undefined, + title: context.propsValue['jobTitle'] || undefined, + }, + ]; + } + const request: HttpRequest> = { + method: HttpMethod.PATCH, + url: `${googleContactsCommon.baseUrl}${resourceName}:updateContact`, + body: requestBody, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + queryParams: qs, + }; + return (await httpClient.sendRequest(request)).body; + }, +}); diff --git a/packages/pieces/community/google-contacts/src/lib/common/index.ts b/packages/pieces/community/google-contacts/src/lib/common/index.ts new file mode 100644 index 0000000..4532fba --- /dev/null +++ b/packages/pieces/community/google-contacts/src/lib/common/index.ts @@ -0,0 +1,3 @@ +export const googleContactsCommon = { + baseUrl: `https://people.googleapis.com/v1/people`, +}; diff --git a/packages/pieces/community/google-contacts/src/lib/trigger/new-contact.ts b/packages/pieces/community/google-contacts/src/lib/trigger/new-contact.ts new file mode 100644 index 0000000..1e1c31a --- /dev/null +++ b/packages/pieces/community/google-contacts/src/lib/trigger/new-contact.ts @@ -0,0 +1,180 @@ +import { + createTrigger, + OAuth2PropertyValue, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { + Polling, + DedupeStrategy, + pollingHelper, +} from '@activepieces/pieces-common'; +import { googleContactsAuth } from '../../'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import dayjs from 'dayjs'; + +const polling: Polling> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ store, auth }) => { + const authClient = new OAuth2Client(); + authClient.setCredentials(auth); + + const contactsClient = google.people({ version: 'v1', auth: authClient }); + + let nextPageToken; + const contactItems: Array<{ data: any; epochMilliSeconds: number }> = []; + + do { + const response: any = await contactsClient.people.connections.list({ + resourceName: 'people/me', + pageToken: nextPageToken, + pageSize: 100, + sortOrder: 'LAST_MODIFIED_DESCENDING', + personFields: [ + 'addresses', + 'ageRanges', + 'biographies', + 'birthdays', + 'calendarUrls', + 'clientData', + 'coverPhotos', + 'emailAddresses', + 'events', + 'externalIds', + 'genders', + 'imClients', + 'interests', + 'locales', + 'locations', + 'memberships', + 'metadata', + 'miscKeywords', + 'names', + 'nicknames', + 'occupations', + 'organizations', + 'phoneNumbers', + 'photos', + 'relations', + 'sipAddresses', + 'skills', + 'urls', + 'userDefined', + ].join(), + }); + + for (const contact of response.data.connections || []) { + if (contact.metadata?.deleted !== true) { + contactItems.push({ + data: contact, + epochMilliSeconds: dayjs( + contact.metadata?.sources?.[0].updateTime + ).valueOf(), + }); + } + } + + nextPageToken = response.data.nextPageToken; + } while (nextPageToken); + + return contactItems; + }, +}; + +export const googleContactNewOrUpdatedContact = createTrigger({ + auth: googleContactsAuth, + name: 'new_or_updated_contact', + displayName: 'New Or Updated Contact', + description: 'Triggers when there is a new or updated contact', + props: {}, + sampleData: { + resourceName: 'people/c4278485694217203807', + etag: '%EiMBAgMFBgcICQoLDA0ODxATFBUWGSEiIyQlJicuNDU3PT4/QBoEAQIFByIMZFVwNlJPNEVKUzg9', + metadata: { + sources: [ + { + type: 'CONTACT', + id: '3b603c120c68305f', + etag: '#dUp6RO4EJS8=', + updateTime: '2023-01-30T14:35:18.142565Z', + }, + ], + objectType: 'PERSON', + }, + names: [ + { + metadata: { + primary: true, + source: { + type: 'CONTACT', + id: '3b603c120c68305f', + }, + }, + displayName: 'Shahed Mashni', + familyName: 'Mashni', + givenName: 'Shahed', + displayNameLastFirst: 'Mashni, Shahed', + unstructuredName: 'Shahed Mashni', + }, + ], + photos: [ + { + metadata: { + primary: true, + source: { + type: 'CONTACT', + id: '3b603c120c68305f', + }, + }, + url: 'https://lh3.googleusercontent.com/cm/AAkddurmZojs4vCcxrpkfSxH9tnqcH-hI82ESDnwv6eq86nZeLStcjYEIe_TCx8r8g5Y=s100', + default: true, + }, + ], + memberships: [ + { + metadata: { + source: { + type: 'CONTACT', + id: '3b603c120c68305f', + }, + }, + contactGroupMembership: { + contactGroupId: 'myContacts', + contactGroupResourceName: 'contactGroups/myContacts', + }, + }, + ], + }, + + type: TriggerStrategy.POLLING, + async onEnable(ctx) { + return await pollingHelper.onEnable(polling, { + store: ctx.store, + auth: ctx.auth, + propsValue: {}, + }); + }, + async onDisable(ctx) { + return await pollingHelper.onEnable(polling, { + store: ctx.store, + auth: ctx.auth, + propsValue: {}, + }); + }, + async run(ctx) { + return await pollingHelper.poll(polling, { + store: ctx.store, + auth: ctx.auth, + propsValue: {}, + files: ctx.files, + }); + }, + test: async (ctx) => { + return await pollingHelper.test(polling, { + store: ctx.store, + auth: ctx.auth, + propsValue: {}, + files: ctx.files, + }); + }, +}); diff --git a/packages/pieces/community/google-contacts/tsconfig.json b/packages/pieces/community/google-contacts/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/google-contacts/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/google-contacts/tsconfig.lib.json b/packages/pieces/community/google-contacts/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/google-contacts/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/google-docs/.eslintrc.json b/packages/pieces/community/google-docs/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/google-docs/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/google-docs/README.md b/packages/pieces/community/google-docs/README.md new file mode 100644 index 0000000..eaa7616 --- /dev/null +++ b/packages/pieces/community/google-docs/README.md @@ -0,0 +1,7 @@ +# pieces-google-docs + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-google-docs` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/google-docs/package.json b/packages/pieces/community/google-docs/package.json new file mode 100644 index 0000000..44411d9 --- /dev/null +++ b/packages/pieces/community/google-docs/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-google-docs", + "version": "0.2.1" +} diff --git a/packages/pieces/community/google-docs/project.json b/packages/pieces/community/google-docs/project.json new file mode 100644 index 0000000..2c80ea9 --- /dev/null +++ b/packages/pieces/community/google-docs/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-google-docs", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/google-docs/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/google-docs", + "tsConfig": "packages/pieces/community/google-docs/tsconfig.lib.json", + "packageJson": "packages/pieces/community/google-docs/package.json", + "main": "packages/pieces/community/google-docs/src/index.ts", + "assets": [ + "packages/pieces/community/google-docs/*.md", + { + "input": "packages/pieces/community/google-docs/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/google-docs/src/index.ts b/packages/pieces/community/google-docs/src/index.ts new file mode 100644 index 0000000..41554fc --- /dev/null +++ b/packages/pieces/community/google-docs/src/index.ts @@ -0,0 +1,55 @@ +import { OAuth2PropertyValue, PieceAuth, createPiece } from '@activepieces/pieces-framework'; + +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceCategory } from '@activepieces/shared'; +import { createDocument } from './lib/actions/create-document'; +import { createDocumentBasedOnTemplate } from './lib/actions/create-document-based-on-template.action'; +import { readDocument } from './lib/actions/read-document.action'; +import { appendText } from './lib/actions/append-text'; +import { findDocumentAction } from './lib/actions/find-document'; +import { newDocumentTrigger } from './lib/triggers/new-document'; + +export const googleDocsAuth = PieceAuth.OAuth2({ + authUrl: 'https://accounts.google.com/o/oauth2/auth', + tokenUrl: 'https://oauth2.googleapis.com/token', + required: true, + scope: [ + 'https://www.googleapis.com/auth/documents', + 'https://www.googleapis.com/auth/drive.readonly', + 'https://www.googleapis.com/auth/drive', + ], +}); + +export const googleDocs = createPiece({ + displayName: 'Google Docs', + description: 'Create and edit documents online', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/google-docs.png', + categories: [PieceCategory.CONTENT_AND_FILES], + authors: [ + 'pfernandez98', + 'kishanprmr', + 'MoShizzle', + 'khaledmashaly', + 'abuaboud', + 'AbdullahBitar', + 'Kevinyu-alan' + ], + auth: googleDocsAuth, + actions: [ + createDocument, + createDocumentBasedOnTemplate, + readDocument, + findDocumentAction, + createCustomApiCallAction({ + baseUrl: () => 'https://docs.googleapis.com/v1', + auth: googleDocsAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + appendText, + ], + triggers: [newDocumentTrigger], +}); diff --git a/packages/pieces/community/google-docs/src/lib/actions/append-text.ts b/packages/pieces/community/google-docs/src/lib/actions/append-text.ts new file mode 100644 index 0000000..6bc41dc --- /dev/null +++ b/packages/pieces/community/google-docs/src/lib/actions/append-text.ts @@ -0,0 +1,29 @@ +import { docsCommon } from '../common'; +import { googleDocsAuth } from '../..'; +import { Property, createAction } from "@activepieces/pieces-framework"; + +export const appendText = createAction({ + auth: googleDocsAuth, + name: 'append_text', + description: 'Appends text to google docs', + displayName: 'Append text to google docs', + props: { + text: Property.LongText({ + displayName: 'Text to append', + description: 'The text to append to the document', + required: true, + }), + documentId: Property.ShortText({ + displayName: 'Document ID', + description: 'The ID of the document to append text to', + required: true, + }) + }, + async run(context) { + return await docsCommon.writeToDocument( + context.propsValue.documentId, + context.propsValue.text, + context.auth.access_token + ); + }, + }); \ No newline at end of file diff --git a/packages/pieces/community/google-docs/src/lib/actions/create-document-based-on-template.action.ts b/packages/pieces/community/google-docs/src/lib/actions/create-document-based-on-template.action.ts new file mode 100644 index 0000000..d02d825 --- /dev/null +++ b/packages/pieces/community/google-docs/src/lib/actions/create-document-based-on-template.action.ts @@ -0,0 +1,91 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { googleDocsAuth } from '../../index'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; + +export const createDocumentBasedOnTemplate = createAction({ + auth: googleDocsAuth, + name: 'create_document_based_on_template', + description: + 'Edit a template file and replace the values with the ones provided', + displayName: 'Edit template file', + props: { + template: Property.ShortText({ + displayName: 'Destination File', + description: 'The ID of the file to replace the values', + required: true, + }), + values: Property.Object({ + displayName: 'Variables', + description: 'Dont include the placeholder format "[[]]" or "{{}}", only the key name and its value', + required: true, + }), + images: Property.Object({ + displayName: 'Images', + description: + 'Key: Image ID (get it manually from the Read File Action), Value: Image URL', + required: true, + }), + placeholder_format: Property.StaticDropdown({ + displayName: 'Placeholder Format', + description: 'Choose the format of placeholders in your template', + required: true, + defaultValue: '[[]]', + options: { + disabled: false, + options: [ + { label: 'Curly Braces {{}}', value: '{{}}' }, + { label: 'Square Brackets [[]]', value: '[[]]' } + ], + }, + }), + }, + async run(context) { + const documentId: string = context.propsValue.template; + const values = context.propsValue.values; + const placeholder_format = context.propsValue.placeholder_format; + + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + const docs = google.docs('v1'); + + const requests = []; + + for (const key in values) { + const value = values[key]; + const new_key = placeholder_format === '[[]]' + ? `[[${key}]]` + : `{{${key}}}`; + requests.push({ + replaceAllText: { + containsText: { + text: new_key, + matchCase: true, + }, + replaceText: String(value), + }, + }); + } + + for (const key in context.propsValue.images) { + const value = context.propsValue.images[key]; + requests.push({ + replaceImage: { + imageObjectId: key, + uri: String(value), + }, + }); + } + + const res = await docs.documents.batchUpdate({ + auth: authClient, + documentId, + requestBody: { + requests: requests, + }, + }); + + return res; + }, +}); diff --git a/packages/pieces/community/google-docs/src/lib/actions/create-document.ts b/packages/pieces/community/google-docs/src/lib/actions/create-document.ts new file mode 100644 index 0000000..4e17c21 --- /dev/null +++ b/packages/pieces/community/google-docs/src/lib/actions/create-document.ts @@ -0,0 +1,27 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { docsCommon } from '../common'; +import { googleDocsAuth } from '../..'; + +export const createDocument = createAction({ + auth: googleDocsAuth, + name: 'create_document', + description: 'Create a document on Google Docs', + displayName: 'Create Document', + props: { + title: docsCommon.title, + body: docsCommon.body, + }, + async run(context) { + const document = await docsCommon.createDocument( + context.propsValue.title, + context.auth.access_token + ); + const response = await docsCommon.writeToDocument( + document.documentId, + context.propsValue.body, + context.auth.access_token + ); + + return response; + }, +}); diff --git a/packages/pieces/community/google-docs/src/lib/actions/find-document.ts b/packages/pieces/community/google-docs/src/lib/actions/find-document.ts new file mode 100644 index 0000000..abacf2f --- /dev/null +++ b/packages/pieces/community/google-docs/src/lib/actions/find-document.ts @@ -0,0 +1,127 @@ +import { googleDocsAuth } from '../..'; +import { + createAction, + DynamicPropsValue, + Property, +} from '@activepieces/pieces-framework'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { folderIdProp } from '../common/props'; + +export const findDocumentAction = createAction({ + auth: googleDocsAuth, + name: 'google-docs-find-document', + displayName: 'Find Document', + description: 'Search for document by name.', + props: { + name: Property.ShortText({ + displayName: 'Document Name', + required: true, + }), + folderId: folderIdProp, + createIfNotFound: Property.Checkbox({ + displayName: 'Create a new document if not found?', + defaultValue: false, + required: false, + }), + newDocumentProps: Property.DynamicProperties({ + displayName: 'New Document Properties', + required: false, + refreshers: ['createIfNotFound'], + props: async ({ auth, createIfNotFound }) => { + if (!auth) return {}; + if (!createIfNotFound) return {}; + + const props: DynamicPropsValue = {}; + + if (createIfNotFound) { + props['content'] = Property.LongText({ + displayName: 'Document Content', + required: true, + }); + } + + return props; + }, + }), + }, + async run(context) { + const { name: documentName, folderId, createIfNotFound, newDocumentProps } = context.propsValue; + const newDocumentContent = newDocumentProps?.['content'] as string; + + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + + const drive = google.drive({ version: 'v3', auth: authClient }); + const docs = google.docs({ version: 'v1', auth: authClient }); + + // Search for the document in Google Drive + const query: string[] = [ + `name contains '${documentName}'`, + `mimeType='application/vnd.google-apps.document'`, + 'trashed=false', + ]; + + if (folderId) query.push(`'${folderId}' in parents`); + + const response = await drive.files.list({ + q: query.join(' and '), + supportsAllDrives: true, + fields: '*', + pageSize: 1, + includeItemsFromAllDrives: true, + }); + + const existingFile = response.data.files?.[0]; + + if (existingFile) { + return { found: true, file: existingFile }; + } + + // Create a new document if not found + if (!createIfNotFound) return { found: false, file: {} }; + + //creating new Document + const createdDoc = await docs.documents.create({ requestBody: { title: documentName } }); + const documentId = createdDoc.data.documentId; + + if (!documentId) throw new Error('Failed to create document'); + + // Insert content into the new document + if (newDocumentContent) { + // appending text + await docs.documents.batchUpdate({ + documentId, + requestBody: { + requests: [{ insertText: { text: newDocumentContent, endOfSegmentLocation: {} } }], + }, + }); + } + + // Move the document to the specified folder + if (folderId) { + const fileData = await drive.files.get({ + fileId: documentId, + supportsAllDrives: true, + fields: 'id, parents', + }); + + await drive.files.update({ + fileId: documentId, + fields: 'id, name, parents', + removeParents: fileData.data.parents?.join(','), + addParents: folderId, + supportsAllDrives: true, + }); + } + + // Fetch document details + const finalFile = await drive.files.get({ + fileId: documentId, + supportsAllDrives: true, + fields: '*', + }); + + return { found: false, file: finalFile.data }; + }, +}); diff --git a/packages/pieces/community/google-docs/src/lib/actions/read-document.action.ts b/packages/pieces/community/google-docs/src/lib/actions/read-document.action.ts new file mode 100644 index 0000000..e98e930 --- /dev/null +++ b/packages/pieces/community/google-docs/src/lib/actions/read-document.action.ts @@ -0,0 +1,34 @@ +import { googleDocsAuth } from '../../index'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; + +export const readDocument = createAction({ + displayName: 'Read Document', + auth: googleDocsAuth, + name: 'read_document', + description: 'Read a document from Google Docs', + props: { + documentId: Property.ShortText({ + displayName: 'Document ID', + description: 'The ID of the document to read', + required: true, + }), + }, + async run(context) { + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + + const docs = google.docs({ version: 'v1', auth: authClient }); + const response = await docs.documents.get({ + documentId: context.propsValue.documentId, + }); + + if (response.status !== 200) { + console.error(response); + throw new Error('Error reading document'); + } + + return response.data; + }, +}); diff --git a/packages/pieces/community/google-docs/src/lib/common/index.ts b/packages/pieces/community/google-docs/src/lib/common/index.ts new file mode 100644 index 0000000..0b21148 --- /dev/null +++ b/packages/pieces/community/google-docs/src/lib/common/index.ts @@ -0,0 +1,56 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient, AuthenticationType } from '@activepieces/pieces-common'; + +export const docsCommon = { + baseUrl: 'https://docs.googleapis.com/v1', + title: Property.ShortText({ + displayName: 'Document Title', + required: true, + }), + body: Property.LongText({ + displayName: 'Document Content', + required: true, + }), + + // Creates an empty document with the title provided + createDocument: async (title: string, accessToken: string) => { + const createRequest = await httpClient.sendRequest({ + url: `${docsCommon.baseUrl}/documents`, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + body: { + title: title, + }, + }); + + return createRequest.body; + }, + + // Writes provided content to the end of an existing document + writeToDocument: async (documentId: string, body: string, accessToken: string) => { + const writeRequest = await httpClient.sendRequest({ + url: `${docsCommon.baseUrl}/documents/${documentId}:batchUpdate`, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + body: { + requests: [ + { + insertText: { + text: body, + endOfSegmentLocation: {}, + }, + }, + ], + }, + }); + + return writeRequest.body; + }, +}; diff --git a/packages/pieces/community/google-docs/src/lib/common/props.ts b/packages/pieces/community/google-docs/src/lib/common/props.ts new file mode 100644 index 0000000..47dc354 --- /dev/null +++ b/packages/pieces/community/google-docs/src/lib/common/props.ts @@ -0,0 +1,55 @@ +import { googleDocsAuth } from '../../index'; +import { DropdownOption, PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { google, drive_v3 } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; + +export const folderIdProp = Property.Dropdown({ + displayName: 'Folder', + refreshers: [], + required: false, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect to your Google Drive account.', + options: [], + }; + } + const authValue = auth as PiecePropValueSchema; + + const authClient = new OAuth2Client(); + authClient.setCredentials(authValue); + + const drive = google.drive({ version: 'v3', auth: authClient }); + + const options: DropdownOption[] = []; + + let nextPageToken; + + do { + const response: any = await drive.files.list({ + q: "mimeType='application/vnd.google-apps.folder' and trashed = false", + supportsAllDrives: true, + orderBy:'createdTime desc', + includeItemsFromAllDrives: true, + pageToken: nextPageToken, + }); + + const fileList: drive_v3.Schema$FileList = response.data; + + if (fileList.files) { + for (const file of fileList.files) + options.push({ + label: file.name!, + value: file.id!, + }); + } + nextPageToken = response.data.nextPageToken; + } while (nextPageToken); + + return { + disabled: false, + options, + }; + }, +}); diff --git a/packages/pieces/community/google-docs/src/lib/triggers/new-document.ts b/packages/pieces/community/google-docs/src/lib/triggers/new-document.ts new file mode 100644 index 0000000..c99badd --- /dev/null +++ b/packages/pieces/community/google-docs/src/lib/triggers/new-document.ts @@ -0,0 +1,104 @@ +import { googleDocsAuth } from '../../index'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { folderIdProp } from '../common/props'; +import dayjs from 'dayjs'; +import { google, drive_v3 } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; + +type Props = { + folderId?: string; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const authValue = auth as PiecePropValueSchema; + const folderId = propsValue.folderId; + + const q = ["mimeType='application/vnd.google-apps.document'", 'trashed = false']; + if (lastFetchEpochMS) { + q.push(`createdTime > '${dayjs(lastFetchEpochMS).toISOString()}'`); + } + if (folderId) { + q.push(`'${folderId}' in parents`); + } + + const authClient = new OAuth2Client(); + authClient.setCredentials(authValue); + + const drive = google.drive({ version: 'v3', auth: authClient }); + + let nextPageToken; + const items = []; + + do { + const response: any = await drive.files.list({ + q: q.join(' and '), + fields: '*', + orderBy: 'createdTime desc', + supportsAllDrives: true, + includeItemsFromAllDrives: true, + pageToken: nextPageToken, + }); + + const fileList: drive_v3.Schema$FileList = response.data; + + if (fileList.files) { + items.push(...fileList.files); + } + + if (lastFetchEpochMS === 0) break; + + nextPageToken = response.data.nextPageToken; + } while (nextPageToken); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.createdTime).valueOf(), + data: item, + })); + }, +}; + +export const newDocumentTrigger = createTrigger({ + auth: googleDocsAuth, + name: 'new-document', + displayName: 'New Document', + description: 'Triggers when a new document is added to a specific folder(optional).', + type: TriggerStrategy.POLLING, + props: { + folderId: folderIdProp, + }, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + kind: 'drive#file', + mimeType: 'application/vnd.google-apps.document', + webViewLink: + 'https://docs.google.com/document/d/1_9xjsrYFgHVvgqYwAJ8KcsDcNU/edit?usp=drivesdk', + id: '1_9xjsrYFgHVvgqYwAJ8KcsDcN3AzPelsux', + name: 'Test Document', + }, +}); diff --git a/packages/pieces/community/google-docs/tsconfig.json b/packages/pieces/community/google-docs/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/google-docs/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/google-docs/tsconfig.lib.json b/packages/pieces/community/google-docs/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/google-docs/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/google-drive/.babelrc b/packages/pieces/community/google-drive/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/google-drive/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/google-drive/.eslintrc.json b/packages/pieces/community/google-drive/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/google-drive/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/google-drive/README.md b/packages/pieces/community/google-drive/README.md new file mode 100644 index 0000000..985debf --- /dev/null +++ b/packages/pieces/community/google-drive/README.md @@ -0,0 +1,7 @@ +# pieces-google-drive + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-google-drive` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/google-drive/package.json b/packages/pieces/community/google-drive/package.json new file mode 100644 index 0000000..7f5a08c --- /dev/null +++ b/packages/pieces/community/google-drive/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-google-drive", + "version": "0.5.41" +} diff --git a/packages/pieces/community/google-drive/project.json b/packages/pieces/community/google-drive/project.json new file mode 100644 index 0000000..160c991 --- /dev/null +++ b/packages/pieces/community/google-drive/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-google-drive", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/google-drive/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/google-drive", + "tsConfig": "packages/pieces/community/google-drive/tsconfig.lib.json", + "packageJson": "packages/pieces/community/google-drive/package.json", + "main": "packages/pieces/community/google-drive/src/index.ts", + "assets": [ + "packages/pieces/community/google-drive/*.md", + { + "input": "packages/pieces/community/google-drive/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/google-drive/src/index.ts b/packages/pieces/community/google-drive/src/index.ts new file mode 100644 index 0000000..37641af --- /dev/null +++ b/packages/pieces/community/google-drive/src/index.ts @@ -0,0 +1,81 @@ +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; + +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceCategory } from '@activepieces/shared'; +import { addPermission } from './lib/action/add-permission.action'; +import { googleDriveCreateNewFolder } from './lib/action/create-new-folder'; +import { googleDriveCreateNewTextFile } from './lib/action/create-new-text-file'; +import { deletePermission } from './lib/action/delete-permission.action'; +import { duplicateFileAction } from './lib/action/duplicate-file.action'; +import { googleDriveGetResourceById } from './lib/action/get-file-by-id'; +import { googleDriveListFiles } from './lib/action/list-files.action'; +import { readFile } from './lib/action/read-file'; +import { saveFileAsPdf } from './lib/action/save-file-as-pdf.action'; +import { googleDriveSearchFolder } from './lib/action/search-folder-or-file.action'; +import { googleDriveUploadFile } from './lib/action/upload-file'; +import { newFile } from './lib/triggers/new-file'; +import { newFolder } from './lib/triggers/new-folder'; +import { setPublicAccess } from './lib/action/set-public-access'; +import { moveFileAction } from './lib/action/move-file'; +import { googleDriveDeleteFile } from './lib/action/delete-file'; +import { googleDriveTrashFile } from './lib/action/send-to-trash'; + +export const googleDriveAuth = PieceAuth.OAuth2({ + description: '', + authUrl: 'https://accounts.google.com/o/oauth2/auth', + tokenUrl: 'https://oauth2.googleapis.com/token', + required: true, + scope: ['https://www.googleapis.com/auth/drive'], +}); + +export const googleDrive = createPiece({ + minimumSupportedRelease: '0.5.6', + logoUrl: 'https://cdn.activepieces.com/pieces/google-drive.png', + categories: [PieceCategory.CONTENT_AND_FILES], + displayName: 'Google Drive', + description: 'Cloud storage and file backup', + authors: [ + 'BastienMe', + 'ArmanGiau3', + 'Vitalini', + 'pfernandez98', + 'kanarelo', + 'Salem-Alaa', + 'kishanprmr', + 'MoShizzle', + 'AbdulTheActivePiecer', + 'khaledmashaly', + 'abuaboud', + 'geekyme' + ], + triggers: [newFile, newFolder], + actions: [ + googleDriveCreateNewFolder, + googleDriveCreateNewTextFile, + googleDriveUploadFile, + readFile, + googleDriveGetResourceById, + googleDriveListFiles, + googleDriveSearchFolder, + duplicateFileAction, + saveFileAsPdf, + addPermission, + deletePermission, + setPublicAccess, + moveFileAction, + googleDriveDeleteFile, + googleDriveTrashFile, + createCustomApiCallAction({ + baseUrl: () => 'https://www.googleapis.com/drive/v3', + auth: googleDriveAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + auth: googleDriveAuth, +}); diff --git a/packages/pieces/community/google-drive/src/lib/action/add-permission.action.ts b/packages/pieces/community/google-drive/src/lib/action/add-permission.action.ts new file mode 100644 index 0000000..2444818 --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/action/add-permission.action.ts @@ -0,0 +1,78 @@ +import { Property, createAction } from "@activepieces/pieces-framework"; +import { googleDriveAuth } from "../../"; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; + +export const addPermission = createAction({ + auth: googleDriveAuth, + name: 'update_permissions', + description: 'Update permissions for a file or folder', + displayName: 'Update permissions', + props: { + fileId: Property.ShortText({ + displayName: 'File or Folder ID', + description: 'The ID of the file or folder to update permissions for', + required: true, + }), + user_email: Property.ShortText({ + displayName: 'User email', + description: 'The email address of the user to update permissions for', + required: true, + }), + permission_name : Property.StaticDropdown({ + displayName: 'Role', + description: 'The role to grant to user. See more at: https://developers.google.com/drive/api/guides/ref-roles', + required: true, + options: { + options: [ + { + label: 'Organizer', + value: 'organizer', + }, + { + label: 'File Organizer', + value: 'fileOrganizer', + }, + { + label: 'Writer', + value: 'writer', + }, + { + label: 'Commenter', + value: 'commenter', + }, + { + label: 'Reader', + value: 'reader', + }, + + ] + } + }), + send_invitation_email: Property.Checkbox({ + displayName: 'Send invitation email', + description: 'Send an email to the user to notify them of the new permissions', + required: true, + }), + + }, + + async run(context) { + const [fileId, user_email, permission_name, send_invitation_email] = [context.propsValue.fileId, context.propsValue.user_email, context.propsValue.permission_name, context.propsValue.send_invitation_email]; + + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth) + + const drive = google.drive({ version: 'v3', auth: authClient }); + + const permission = { 'type': 'user', 'role': permission_name, 'emailAddress': user_email }; + + const result = await drive.permissions.create({ + requestBody: permission, + fileId: fileId, + sendNotificationEmail: send_invitation_email + }); + + return result.data; + } +}); diff --git a/packages/pieces/community/google-drive/src/lib/action/create-new-folder.ts b/packages/pieces/community/google-drive/src/lib/action/create-new-folder.ts new file mode 100644 index 0000000..64c4978 --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/action/create-new-folder.ts @@ -0,0 +1,40 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { googleDriveAuth } from '../../'; +import { common } from '../common'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; + +export const googleDriveCreateNewFolder = createAction({ + auth: googleDriveAuth, + name: 'create_new_gdrive_folder', + description: 'Create a new empty folder in your Google Drive', + displayName: 'Create new folder', + props: { + folderName: Property.ShortText({ + displayName: 'Folder name', + description: 'The name of the new folder', + required: true, + }), + parentFolder: common.properties.parentFolder, + include_team_drives: common.properties.include_team_drives, + }, + async run(context) { + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + + const drive = google.drive({ version: 'v3', auth: authClient }); + + const response = await drive.files.create({ + requestBody: { + mimeType: 'application/vnd.google-apps.folder', + name: context.propsValue.folderName, + ...(context.propsValue.parentFolder + ? { parents: [context.propsValue.parentFolder] } + : {}), + }, + supportsAllDrives: context.propsValue.include_team_drives, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/google-drive/src/lib/action/create-new-text-file.ts b/packages/pieces/community/google-drive/src/lib/action/create-new-text-file.ts new file mode 100644 index 0000000..dac89b3 --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/action/create-new-text-file.ts @@ -0,0 +1,89 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import FormData from 'form-data'; +import { googleDriveAuth } from '../../'; +import { common } from '../common'; + +export const googleDriveCreateNewTextFile = createAction({ + auth: googleDriveAuth, + name: 'create_new_gdrive_file', + description: 'Create a new text file in your Google Drive from text', + displayName: 'Create new file', + props: { + fileName: Property.ShortText({ + displayName: 'File name', + description: 'The name of the new text file', + required: true, + }), + text: Property.LongText({ + displayName: 'Text', + description: 'The text content to add to file', + required: true, + }), + fileType: Property.StaticDropdown({ + displayName: 'Content type', + description: 'Select file type', + required: true, + defaultValue: 'plain/text', + options: { + options: [ + { + label: 'Text', + value: 'plain/text', + }, + { + label: 'CSV', + value: 'text/csv', + }, + { + label: 'XML', + value: 'text/xml', + }, + ], + }, + }), + parentFolder: common.properties.parentFolder, + include_team_drives: common.properties.include_team_drives, + }, + async run(context) { + const meta = { + mimeType: context.propsValue.fileType, + name: context.propsValue.fileName, + ...(context.propsValue.parentFolder + ? { parents: [context.propsValue.parentFolder] } + : {}), + }; + + const metaBuffer = Buffer.from(JSON.stringify(meta), 'utf-8'); + const textBuffer = Buffer.from(context.propsValue.text!, 'utf-8'); + + const form = new FormData(); + form.append('Metadata', metaBuffer, { contentType: 'application/json' }); + form.append('Media', textBuffer, { + contentType: context.propsValue.fileType, + }); + + const result = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart`, + body: form, + headers: { + ...form.getHeaders(), + }, + queryParams: { + supportsAllDrives: String(context.propsValue.include_team_drives || false), + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + console.debug('File creation response', result); + return result.body; + }, +}); diff --git a/packages/pieces/community/google-drive/src/lib/action/delete-file.ts b/packages/pieces/community/google-drive/src/lib/action/delete-file.ts new file mode 100644 index 0000000..c86445b --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/action/delete-file.ts @@ -0,0 +1,33 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { googleDriveAuth } from '../../'; +import { common } from '../common'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; + +export const googleDriveDeleteFile = createAction({ + auth: googleDriveAuth, + name: 'delete_gdrive_file', + description: 'Delete permanently a file from your Google Drive', + displayName: 'Delete file', + props: { + fileId: Property.ShortText({ + displayName: 'File ID', + description: 'The ID of the file to delete', + required: true, + }), + include_team_drives: common.properties.include_team_drives, + }, + async run(context) { + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + + const drive = google.drive({ version: 'v3', auth: authClient }); + + const response = await drive.files.delete({ + fileId: context.propsValue.fileId, + supportsAllDrives: context.propsValue.include_team_drives, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/google-drive/src/lib/action/delete-permission.action.ts b/packages/pieces/community/google-drive/src/lib/action/delete-permission.action.ts new file mode 100644 index 0000000..410ed50 --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/action/delete-permission.action.ts @@ -0,0 +1,82 @@ +import { Property, createAction } from "@activepieces/pieces-framework"; +import { googleDriveAuth } from "../../"; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; + +export const deletePermission = createAction({ + auth: googleDriveAuth, + name: 'delete_permissions', + description: 'Removes a role from an user for a file or folder', + displayName: 'Delete permissions', + props: { + fileId: Property.ShortText({ + displayName: 'File or Folder ID', + description: 'The ID of the file or folder to update permissions for', + required: true, + }), + user_email: Property.ShortText({ + displayName: 'User email', + description: 'The email address of the user to update permissions for', + required: true, + }), + permission_name : Property.StaticDropdown({ + displayName: 'Role', + description: 'The role to remove from user.', + required: true, + options: { + options: [ + { + label: 'Organizer', + value: 'organizer', + }, + { + label: 'File Organizer', + value: 'fileOrganizer', + }, + { + label: 'Writer', + value: 'writer', + }, + { + label: 'Commenter', + value: 'commenter', + }, + { + label: 'Reader', + value: 'reader', + }, + + ] + } + }), + }, + async run (context) { + const [fileId, user_email] = [context.propsValue.fileId, context.propsValue.user_email]; + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth) + + const drive = google.drive({ version: 'v3', auth: authClient }); + + const response_permissions_list = await drive.permissions.list({ + fileId: fileId, + fields: 'permissions(id, emailAddress, role)', + + }); + + if (response_permissions_list.data.permissions) { + + for (const permission of response_permissions_list.data.permissions) { + if (permission.emailAddress === user_email && permission.role === context.propsValue.permission_name) { + await drive.permissions.delete({ + fileId: fileId, + permissionId: permission.id ? permission.id : '', + }); + return {removed: true, message: 'Permission removed'}; + } + } + } + + return {removed: false, message: 'Permission not found'}; + + } +}); \ No newline at end of file diff --git a/packages/pieces/community/google-drive/src/lib/action/duplicate-file.action.ts b/packages/pieces/community/google-drive/src/lib/action/duplicate-file.action.ts new file mode 100644 index 0000000..78aaf59 --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/action/duplicate-file.action.ts @@ -0,0 +1,80 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { googleDriveAuth } from '../../index'; +import { common } from '../common'; + +export const duplicateFileAction = createAction({ + displayName: 'Duplicate File', + auth: googleDriveAuth, + name: 'duplicate_file', + description: 'Duplicate a file from Google Drive. Returns the new file ID.', + props: { + fileId: Property.ShortText({ + displayName: 'File ID', + description: 'The ID of the file to duplicate', + required: true, + }), + name: Property.ShortText({ + displayName: 'Name', + description: 'The name of the new file', + required: true, + }), + folderId: Property.ShortText({ + displayName: 'Folder ID', + description: 'The ID of the folder where the file will be duplicated', + required: true, + }), + mimeType: Property.StaticDropdown({ + displayName: 'Duplicate as', + description: 'If left unselected the file will be duplicated as it is', + required: false, + options: { + options: [ + { + label: 'Google Sheets', + value: 'application/vnd.google-apps.spreadsheet', + }, + { + label: 'Google Docs', + value: 'application/vnd.google-apps.document', + } + ], + }, + }), + include_team_drives: common.properties.include_team_drives, + }, + async run(context) { + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + + const fileId = context.propsValue.fileId; + const nameForNewFile = context.propsValue.name; + const parentFolderId = context.propsValue.folderId; + const mimeType = context.propsValue.mimeType; + + const drive = google.drive({ version: 'v3', auth: authClient }); + + const requestBody: any = { + name: nameForNewFile, + parents: [parentFolderId], + }; + + if (mimeType) { + requestBody.mimeType = mimeType; + } + + const response = await drive.files.copy({ + fileId, + auth: authClient, + requestBody, + supportsAllDrives: context.propsValue.include_team_drives, + }); + + if (response.status !== 200) { + throw new Error('Error duplicating file'); + } + + return response.data; + }, +}); diff --git a/packages/pieces/community/google-drive/src/lib/action/get-file-by-id.ts b/packages/pieces/community/google-drive/src/lib/action/get-file-by-id.ts new file mode 100644 index 0000000..dbecf44 --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/action/get-file-by-id.ts @@ -0,0 +1,36 @@ +import { googleDriveAuth } from '../../index'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { common } from '../common'; + +export const googleDriveGetResourceById = createAction({ + auth: googleDriveAuth, + name: 'get-file-or-folder-by-id', + displayName: 'Get File', + description: 'Get a file folder for files/sub-folders', + props: { + id: Property.ShortText({ + displayName: 'File / Folder Id', + description: 'The Id of the file/folder to search for.', + required: true, + }), + include_team_drives: common.properties.include_team_drives, + }, + async run(context) { + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + const drive = google.drive({ version: 'v3', auth: authClient }); + const response = await drive.files.get({ + fileId: context.propsValue.id, + supportsAllDrives: context.propsValue.include_team_drives, + }); + + if (response.data) { + return response.data; + } else { + console.log('The specified ID corresponds to a folder. Returning null.'); + return null; + } + }, +}); diff --git a/packages/pieces/community/google-drive/src/lib/action/list-files.action.ts b/packages/pieces/community/google-drive/src/lib/action/list-files.action.ts new file mode 100644 index 0000000..e6a2ee7 --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/action/list-files.action.ts @@ -0,0 +1,108 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { googleDriveAuth } from '../../index'; +import { Property, createAction } from "@activepieces/pieces-framework"; +import querystring from 'querystring'; +import { common } from '../common'; +import { downloadFileFromDrive } from '../common/get-file-content'; + +interface ListFilesResult { + type: string; + incompleteSearch: boolean; + files: unknown[]; + downloadedFiles?: string[]; +} + +export const googleDriveListFiles = createAction({ + auth: googleDriveAuth, + name: 'list-files', + displayName: 'List files', + description: 'List files from a Google Drive folder', + props: { + folderId: Property.ShortText({ + displayName: 'Folder ID', + description: 'Folder ID coming from | New Folder -> id | (or any other source)', + required: true, + }), + include_team_drives: common.properties.include_team_drives, + + includeTrashed: Property.Checkbox({ + displayName: 'Include Trashed', + description: 'Include new files that have been trashed.', + required: false, + defaultValue: false + }), + + downloadFiles: Property.Checkbox({ + displayName: 'Download Files', + description: 'Download all file contents in a list', + required: false, + defaultValue: false + }), + }, + async run(context) { + const result: ListFilesResult = { + type: 'drive#fileList', + incompleteSearch: false, + files: [], + } + + let q = `'${context.propsValue.folderId}' in parents`; + + // When include_trashed is false, we add a filter to exclude trashed files. + // By default, Google Drive API returns trashed files as well. + if (!context.propsValue.includeTrashed) { + q += ' and trashed=false'; + } + + const params: Record = { + q: q, + fields: 'files(id,kind,mimeType,name,trashed)', + supportsAllDrives:'true', + includeItemsFromAllDrives: context.propsValue.include_team_drives?'true':'false', + } + + let response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://www.googleapis.com/drive/v3/files?${querystring.stringify(params)}`, + headers: { + Authorization: `Bearer ${context.auth.access_token}`, + }, + }); + + result.files = [...response.body.files]; + while(response.body.nextPageToken) + { + params.pageToken = response.body.nextPageToken; + response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://www.googleapis.com/drive/v3/files?${querystring.stringify(params)}`, + headers: { + Authorization: `Bearer ${context.auth.access_token}`, + }, + }); + result.files.push(...response.body.files); + result.incompleteSearch = result.incompleteSearch || response.body.incompleteSearch; + } + + // If downloadFiles is enabled, download each file and add URLs to array + if (context.propsValue.downloadFiles) { + const downloadedFiles: string[] = []; + for (const file of result.files) { + try { + const fileUrl = await downloadFileFromDrive( + context.auth, + context.files, + (file as any).id, + (file as any).name + ); + downloadedFiles.push(fileUrl); + } catch (error) { + console.warn(`Failed to download file ${(file as any).name}: ${error instanceof Error ? error.message : 'Download failed'}`); + } + } + result.downloadedFiles = downloadedFiles; + } + + return result; + } +}); diff --git a/packages/pieces/community/google-drive/src/lib/action/move-file.ts b/packages/pieces/community/google-drive/src/lib/action/move-file.ts new file mode 100644 index 0000000..de0e0a8 --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/action/move-file.ts @@ -0,0 +1,46 @@ +import { googleDriveAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { common } from '../common'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; + +export const moveFileAction = createAction({ + auth: googleDriveAuth, + name: 'google-drive-move-file', + displayName: 'Move File', + description: 'Moves a file from one folder to another.', + props: { + fileId: Property.ShortText({ + displayName: 'File ID', + description: 'You can use **Search Folder/File** action to retrieve ID.', + required: true, + }), + include_team_drives: common.properties.include_team_drives, + folderId: common.properties.parentFolder, + }, + async run(context) { + const fileId = context.propsValue.fileId; + const folderId = context.propsValue.folderId; + + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + + const drive = google.drive({ version: 'v3', auth: authClient }); + + const file = await drive.files.get({ + fileId, + supportsAllDrives: context.propsValue.include_team_drives, + fields: 'id,parents', + }); + + const response = await drive.files.update({ + fileId: fileId, + fields: '*', + removeParents: file.data.parents?.join(','), + addParents: folderId, + supportsAllDrives: context.propsValue.include_team_drives, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/google-drive/src/lib/action/read-file.ts b/packages/pieces/community/google-drive/src/lib/action/read-file.ts new file mode 100644 index 0000000..6550297 --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/action/read-file.ts @@ -0,0 +1,24 @@ +import { googleDriveAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { downloadFileFromDrive } from '../common/get-file-content'; + +export const readFile = createAction({ + auth: googleDriveAuth, + name: 'read-file', + displayName: 'Read file', + description: 'Read a selected file from google drive file', + props: { + fileId: Property.ShortText({ + displayName: 'File ID', + description: 'File ID coming from | New File -> id |', + required: true, + }), + fileName: Property.ShortText({ + displayName: 'Destination File name', + required: false, + }), + }, + run: async ({ auth, propsValue, files }) => { + return downloadFileFromDrive(auth, files, propsValue.fileId, propsValue.fileName) + }, +}); diff --git a/packages/pieces/community/google-drive/src/lib/action/save-file-as-pdf.action.ts b/packages/pieces/community/google-drive/src/lib/action/save-file-as-pdf.action.ts new file mode 100644 index 0000000..118ece2 --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/action/save-file-as-pdf.action.ts @@ -0,0 +1,74 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { googleDriveAuth } from '../../index'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { Stream } from 'stream'; +import { common } from '../common'; + +export const saveFileAsPdf = createAction({ + displayName: 'Save Document as PDF', + auth: googleDriveAuth, + name: 'save_file_as_pdf', + description: 'Save a document as PDF in a Google Drive folder', + props: { + documentId: Property.ShortText({ + displayName: 'Document ID', + description: 'The ID of the document to export', + required: true, + }), + folderId: Property.ShortText({ + displayName: 'Folder ID', + description: 'The ID of the folder where the file will be exported', + required: true, + }), + name: Property.ShortText({ + displayName: 'Name', + description: 'The name of the new file (do not include the extension)', + required: true, + }), + include_team_drives: common.properties.include_team_drives, + }, + async run(context) { + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + + const documentId = context.propsValue.documentId; + const folderId = context.propsValue.folderId; + const nameForNewFile = context.propsValue.name; + + const drive = google.drive({ version: 'v3', auth: authClient }); + + const result = await drive.files.export( + { + fileId: documentId, + mimeType: 'application/pdf', + }, + { + responseType: 'arraybuffer', + } + ); + + const requestBody = { + name: nameForNewFile + '.pdf', + parents: [folderId], + }; + const templateBuffer = Buffer.from(result.data as any, 'base64'); + + const stream = new Stream.PassThrough().end(templateBuffer); + + const media = { + mimeType: 'application/pdf', + body: stream, + }; + + const file = await drive.files.create({ + requestBody, + media: media, + supportsAllDrives: context.propsValue.include_team_drives, + }); + + return file.data; + }, +}); diff --git a/packages/pieces/community/google-drive/src/lib/action/search-folder-or-file.action.ts b/packages/pieces/community/google-drive/src/lib/action/search-folder-or-file.action.ts new file mode 100644 index 0000000..101d517 --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/action/search-folder-or-file.action.ts @@ -0,0 +1,102 @@ +import { googleDriveAuth } from '../../index'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { common } from '../common'; + +export const googleDriveSearchFolder = createAction({ + auth: googleDriveAuth, + name: 'search-folder', + displayName: 'Search', + description: 'Search a Google Drive folder for files/sub-folders', + props: { + queryTerm: Property.StaticDropdown({ + displayName: 'Query Term', + description: 'The Query term or field of file/folder to search upon.', + defaultValue: 'name', + options: { + options: [ + { label: 'File name', value: 'name' }, + { label: 'Full text search', value: 'fullText' }, + { label: 'Content type', value: 'mimeType' }, + ], + }, + required: true, + }), + operator: Property.StaticDropdown({ + displayName: 'Operator', + description: 'The operator to create criteria.', + required: true, + options: { + options: [ + { label: 'Contains', value: 'contains' }, + { label: 'Equals', value: '=' }, + ], + }, + defaultValue: 'contains', + }), + query: Property.ShortText({ + displayName: 'Value', + description: 'Value of the field of file/folder to search for.', + required: true, + }), + type: Property.StaticDropdown({ + displayName: 'File Type', + description: '(Optional) Choose between files and folders.', + required: false, + options: { + options: [ + { label: 'All', value: 'all' }, + { label: 'Files', value: 'file' }, + { label: 'Folders', value: 'folder' }, + ], + }, + defaultValue: 'all', + }), + parentFolder: common.properties.parentFolder, + include_team_drives: common.properties.include_team_drives, + }, + async run(context) { + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + + const drive = google.drive({ version: 'v3', auth: authClient }); + const operator = context.propsValue.operator ?? 'contains'; + const queryTerm = context.propsValue.queryTerm ?? 'name'; + let finalQuery = `${queryTerm} ${operator} '${context.propsValue.query}'`; + if (context.propsValue.parentFolder) { + finalQuery = `${finalQuery} and '${context.propsValue.parentFolder}' in parents`; + } + + const type = context.propsValue.type ?? 'all'; + switch (type) { + case 'file': + finalQuery = `${finalQuery} and mimeType!='application/vnd.google-apps.folder'`; + break; + case 'folder': + finalQuery = `${finalQuery} and mimeType='application/vnd.google-apps.folder'`; + break; + default: + break; + } + + const response = await drive.files.list({ + q: finalQuery, + fields: 'files(id, name, mimeType, createdTime, modifiedTime)', + includeItemsFromAllDrives: context.propsValue.include_team_drives, + supportsAllDrives: true, + }); + if (response.status !== 200) { + console.error(response); + throw new Error('Error searching for the file/folder'); + } + + const files = response.data.files ?? []; + if (files.length > 0) { + return files; + } else { + console.log('Resource not found'); + return []; + } + }, +}); diff --git a/packages/pieces/community/google-drive/src/lib/action/send-to-trash.ts b/packages/pieces/community/google-drive/src/lib/action/send-to-trash.ts new file mode 100644 index 0000000..caf7b41 --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/action/send-to-trash.ts @@ -0,0 +1,36 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { googleDriveAuth } from '../../'; +import { common } from '../common'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; + +export const googleDriveTrashFile = createAction({ + auth: googleDriveAuth, + name: 'trash_gdrive_file', + description: 'Move a file to the trash in your Google Drive', + displayName: 'Trash file', + props: { + fileId: Property.ShortText({ + displayName: 'File ID', + description: 'The ID of the file to trash', + required: true, + }), + include_team_drives: common.properties.include_team_drives, + }, + async run(context) { + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + + const drive = google.drive({ version: 'v3', auth: authClient }); + const body_value = { + trashed: true, + }; + const response = await drive.files.update({ + fileId: context.propsValue.fileId, + supportsAllDrives: context.propsValue.include_team_drives, + requestBody: body_value, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/google-drive/src/lib/action/set-public-access.ts b/packages/pieces/community/google-drive/src/lib/action/set-public-access.ts new file mode 100644 index 0000000..56e8d1c --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/action/set-public-access.ts @@ -0,0 +1,47 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { googleDriveAuth } from '../../'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { downloadFileFromDrive } from '../common/get-file-content'; + +export const setPublicAccess = createAction({ + auth: googleDriveAuth, + name: 'set_public_access', + description: 'Set public access for a file or folder', + displayName: 'Set public access', + props: { + fileId: Property.ShortText({ + displayName: 'File or Folder ID', + description: 'The ID of the file or folder to update permissions for', + required: true, + }), + }, + async run(context) { + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + + const fileId = context.propsValue.fileId; + + const drive = google.drive({ version: 'v3', auth: authClient }); + const permission = { + role: 'reader', + type: 'anyone', + }; + const res = await drive.permissions.create({ + fileId: fileId, + requestBody: permission, + }); + + const file = await drive.files.get({ + fileId: fileId, + fields: 'name,webContentLink', + }); + const content = await downloadFileFromDrive( + context.auth, + context.files, + fileId, + file.data.name! + ); + return { ...res.data, downloadUrl: content }; + }, +}); diff --git a/packages/pieces/community/google-drive/src/lib/action/upload-file.ts b/packages/pieces/community/google-drive/src/lib/action/upload-file.ts new file mode 100644 index 0000000..28c2535 --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/action/upload-file.ts @@ -0,0 +1,72 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import FormData from 'form-data'; +import { googleDriveAuth } from '../../'; +import mime from 'mime-types'; +import { common } from '../common'; + +export const googleDriveUploadFile = createAction({ + auth: googleDriveAuth, + name: 'upload_gdrive_file', + description: 'Upload a file in your Google Drive', + displayName: 'Upload file', + props: { + fileName: Property.ShortText({ + displayName: 'File name', + description: 'The name of the file', + required: true, + }), + file: Property.File({ + displayName: 'File', + description: 'The file URL or base64 to upload', + required: true, + }), + parentFolder: common.properties.parentFolder, + include_team_drives: common.properties.include_team_drives, + }, + async run(context) { + const fileData = context.propsValue.file; + const mimeType = mime.lookup(fileData.extension ? fileData.extension : ''); + + const meta = { + mimeType: mimeType, + name: context.propsValue.fileName, + ...(context.propsValue.parentFolder + ? { parents: [context.propsValue.parentFolder] } + : {}), + }; + + const metaBuffer = Buffer.from(JSON.stringify(meta), 'utf-8'); + const fileBuffer = Buffer.from(fileData.base64, 'base64'); + + const form = new FormData(); + form.append('Metadata', metaBuffer, { contentType: 'application/json' }); + form.append('Media', fileBuffer); + + const result = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://www.googleapis.com/upload/drive/v3/files`, + queryParams: { + uploadType: 'multipart', + supportsAllDrives: String( + context.propsValue.include_team_drives || false + ), + }, + body: form, + headers: { + ...form.getHeaders(), + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + console.debug('File upload response', result); + return result.body; + }, +}); diff --git a/packages/pieces/community/google-drive/src/lib/common/get-file-content.ts b/packages/pieces/community/google-drive/src/lib/common/get-file-content.ts new file mode 100644 index 0000000..1086061 --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/common/get-file-content.ts @@ -0,0 +1,114 @@ +import { + FilesService, + OAuth2PropertyValue, + OAuth2Props, + Property, + ShortTextProperty, + StaticPropsValue, +} from '@activepieces/pieces-framework'; +import { extension } from 'mime-types'; + +async function getMimeType( + auth: OAuth2PropertyValue, + fileId: string +): Promise { + const mimeType = ( + await fetch( + `https://www.googleapis.com/drive/v3/files/${fileId}?fields=mimeType`, + { + headers: { + Authorization: `Bearer ${auth.access_token}`, + }, + } + ).then((res) => res.json()) + )['mimeType'] as string; + return mimeType; +} + +const googledlCall = async ( + url: string, + auth: OAuth2PropertyValue, + fileId: string, + files: FilesService, + fileName: string | undefined +) => { + const mimeType = await getMimeType(auth, fileId); + + const download = await fetch(url, { + headers: { + Authorization: `Bearer ${auth.access_token}`, + }, + }) + .then((response) => + response.ok ? response.blob() : Promise.reject(response) + ) + .catch((error) => + Promise.reject( + new Error( + `Error when download file:\n\tDownload file response: ${ + (error as Error).message ?? error + }` + ) + ) + ); + + const fileExtension = '.' + extension(mimeType); + const srcFileName = fileName ?? fileId + fileExtension; + // const name = + // (srcFileName + // ? srcFileName.replace(new RegExp(fileExtension + '$'), '') + // : fileId) + fileExtension; + + return files.write({ + fileName: srcFileName, + data: Buffer.from(await download.arrayBuffer()), + }); +}; + +export async function downloadFileFromDrive( + auth: OAuth2PropertyValue, + files: FilesService, + fileId: string, + fileName: string | undefined +): Promise { + let mimeType = await getMimeType(auth, fileId); + + // the google drive API doesn't allowed downloading google documents but we can export them to office formats + if ( + [ + 'application/vnd.google-apps.document', + 'application/vnd.google-apps.spreadsheet', + 'application/vnd.google-apps.presentation', + ].includes(mimeType) + ) { + switch (mimeType) { + case 'application/vnd.google-apps.document': + mimeType = + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; + break; + case 'application/vnd.google-apps.spreadsheet': + mimeType = + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; + break; + case 'application/vnd.google-apps.presentation': + mimeType = + 'application/vnd.openxmlformats-officedocument.presentationml.presentation'; + break; + } + return await googledlCall( + `https://www.googleapis.com/drive/v3/files/${fileId}/export?mimeType=${mimeType}`, + auth, + fileId, + files, + fileName + ); + } else { + return await googledlCall( + `https://www.googleapis.com/drive/v3/files/${fileId}?alt=media`, + auth, + fileId, + files, + fileName + ); + } +} diff --git a/packages/pieces/community/google-drive/src/lib/common/index.ts b/packages/pieces/community/google-drive/src/lib/common/index.ts new file mode 100644 index 0000000..e43cdd9 --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/common/index.ts @@ -0,0 +1,153 @@ +import { + httpClient, + HttpMethod, + AuthenticationType, + HttpRequest, +} from '@activepieces/pieces-common'; +import { Property, OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import { OAuth2Client } from 'googleapis-common'; +import { google } from 'googleapis'; + +export const common = { + properties: { + parentFolder: Property.Dropdown({ + displayName: 'Parent Folder', + required: false, + refreshers: ['include_team_drives'], + options: async ({ auth, include_team_drives }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + } + const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue; + let folders: { id: string; name: string }[] = []; + let pageToken = null; + do { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `https://www.googleapis.com/drive/v3/files`, + queryParams: { + q: "mimeType='application/vnd.google-apps.folder' and trashed = false", + includeItemsFromAllDrives: include_team_drives ? 'true' : 'false', + supportsAllDrives: 'true', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp!['access_token'], + }, + }; + if (pageToken) { + if (request.queryParams !== undefined) { + request.queryParams['pageToken'] = pageToken; + } + } + try { + const response = await httpClient.sendRequest<{ + files: { id: string; name: string }[]; + nextPageToken: string; + }>(request); + folders = folders.concat(response.body.files); + pageToken = response.body.nextPageToken; + } catch (e) { + throw new Error(`Failed to get folders\nError:${e}`); + } + } while (pageToken); + + return { + disabled: false, + options: folders.map((folder: { id: string; name: string }) => { + return { + label: folder.name, + value: folder.id, + }; + }), + }; + }, + }), + include_team_drives: Property.Checkbox({ + displayName: 'Include Team Drives', + description: + 'Determines if folders from Team Drives should be included in the results.', + defaultValue: false, + required: false, + }), + }, + + async getFiles( + auth: OAuth2PropertyValue, + search?: { + parent?: string; + createdTime?: string | number | Date; + createdTimeOp?: string; + includeTeamDrive?: boolean; + }, + order?: string + ) { + const authClient = new OAuth2Client(); + authClient.setCredentials(auth); + + const drive = google.drive({ version: 'v3', auth: authClient }); + + const q: string[] = []; + if (search?.parent) q.push(`'${search.parent}' in parents`); + if (search?.createdTime) + q.push( + `createdTime ${search.createdTimeOp ?? '>'} '${dayjs( + search.createdTime + ).format()}'` + ); + q.push(`trashed = false`); + const response = await drive.files.list({ + q: q.concat("mimeType!='application/vnd.google-apps.folder'").join(' and '), + fields: 'files(id, name, mimeType, webViewLink, kind)', + orderBy: order ?? 'createdTime desc', + supportsAllDrives: true, + includeItemsFromAllDrives: search?.includeTeamDrive, + }); + + return response.data.files; + }, + + async getFolders( + auth: OAuth2PropertyValue, + search?: { + parent?: string; + createdTime?: string | number | Date; + createdTimeOp?: string; + includeTeamDrive?: boolean; + }, + order?: string + ) { + const q: string[] = [`mimeType='application/vnd.google-apps.folder'`]; + if (search?.parent) q.push(`'${search.parent}' in parents`); + if (search?.createdTime) + q.push( + `createdTime ${search.createdTimeOp ?? '>'} '${dayjs( + search.createdTime + ).format()}'` + ); + q.push(`trashed = false`); + const response = await httpClient.sendRequest<{ + files: { id: string; name: string }[]; + }>({ + method: HttpMethod.GET, + url: `https://www.googleapis.com/drive/v3/files`, + queryParams: { + q: q.join(' and '), + orderBy: order ?? 'createdTime desc', + supportsAllDrives: 'true', + includeItemsFromAllDrives: search?.includeTeamDrive? 'true':'false', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }); + + return response.body.files; + }, +}; diff --git a/packages/pieces/community/google-drive/src/lib/triggers/new-file.ts b/packages/pieces/community/google-drive/src/lib/triggers/new-file.ts new file mode 100644 index 0000000..a521ab3 --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/triggers/new-file.ts @@ -0,0 +1,114 @@ +import { + PiecePropValueSchema, + Property, + createTrigger, +} from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; + +import dayjs from 'dayjs'; +import { googleDriveAuth } from '../..'; +import { common } from '../common'; +import { downloadFileFromDrive } from '../common/get-file-content'; + +const polling: Polling< + PiecePropValueSchema, + { parentFolder?: any; include_team_drives?: boolean } +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + const currentValues = + (await common.getFiles(auth, { + parent: propsValue.parentFolder, + createdTime: lastFetchEpochMS, + includeTeamDrive: propsValue.include_team_drives, + })) ?? []; + const items = currentValues.map((item: any) => ({ + epochMilliSeconds: dayjs(item.createdTime).valueOf(), + data: item, + })); + return items; + }, +}; + +export const newFile = createTrigger({ + auth: googleDriveAuth, + name: 'new_file', + displayName: 'New File', + description: 'Trigger when a new file is uploaded.', + props: { + parentFolder: common.properties.parentFolder, + include_team_drives: common.properties.include_team_drives, + include_file_content: Property.Checkbox({ + displayName: 'Include File Content', + description: 'Include the file content in the output. This will increase the time taken to fetch the files and might cause issues with large files.', + required: false, + defaultValue: false + }), + }, + type: TriggerStrategy.POLLING, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + run: async (context) => { + const newFiles = await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + + return await handleFileContent(newFiles, context) + }, + test: async (context) => { + const newFiles = await pollingHelper.test(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + + return await handleFileContent(newFiles, context) + }, + + sampleData: { + kind: 'drive#file', + mimeType: 'image/png', + id: '1dpv4-sKJfKRwI9qx1vWqQhEGEn3EpbI5', + name: 'google-drive.png', + link: 'https://cdn.activepieces.com/pieces/google-drive.png' + }, +}); + +async function handleFileContent(newFiles: unknown[], context: any) { + const newFilesObj = JSON.parse(JSON.stringify(newFiles)) + + if (context.propsValue.include_file_content) { + const fileContentPromises: Promise[] = [] + for (const file of newFilesObj) { + fileContentPromises.push(downloadFileFromDrive(context.auth, context.files, file["id"], file["name"])); + } + + const filesContent = await Promise.all(fileContentPromises) + + for (let i = 0; i < newFilesObj.length; i++) { + newFilesObj[i].content = filesContent[i] + } + } + return newFilesObj +} \ No newline at end of file diff --git a/packages/pieces/community/google-drive/src/lib/triggers/new-folder.ts b/packages/pieces/community/google-drive/src/lib/triggers/new-folder.ts new file mode 100644 index 0000000..9ef5c35 --- /dev/null +++ b/packages/pieces/community/google-drive/src/lib/triggers/new-folder.ts @@ -0,0 +1,73 @@ +import { + PiecePropValueSchema, + createTrigger, +} from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; + +import dayjs from 'dayjs'; +import { googleDriveAuth } from '../..'; +import { common } from '../common'; + +const polling: Polling< + PiecePropValueSchema, + { parentFolder?: any,include_team_drives?:boolean } +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + const currentValues = + (await common.getFolders(auth, { + parent: propsValue.parentFolder, + createdTime: lastFetchEpochMS, + includeTeamDrive:propsValue.include_team_drives + })) ?? []; + const items = currentValues.map((item: any) => ({ + epochMilliSeconds: dayjs(item.createdTime).valueOf(), + data: item, + })); + return items; + }, +}; + +export const newFolder = createTrigger({ + auth: googleDriveAuth, + name: 'new_folder', + displayName: 'New Folder', + description: 'Trigger when a new folder is created or uploaded.', + props: { + parentFolder: common.properties.parentFolder, + include_team_drives: common.properties.include_team_drives, + }, + type: TriggerStrategy.POLLING, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, context); + }, + test: async (context) => { + return await pollingHelper.test(polling, context); + }, + + sampleData: { + kind: 'drive#file', + mimeType: 'application/vnd.google-apps.folder', + id: '1aMEtTqIYn5651wdK7WLxaK_SDim4mvXW', + name: 'New Folder WOOOO', + }, +}); diff --git a/packages/pieces/community/google-drive/tsconfig.json b/packages/pieces/community/google-drive/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/google-drive/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/google-drive/tsconfig.lib.json b/packages/pieces/community/google-drive/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/google-drive/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/google-forms/.eslintrc.json b/packages/pieces/community/google-forms/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/google-forms/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/google-forms/README.md b/packages/pieces/community/google-forms/README.md new file mode 100644 index 0000000..eb00fff --- /dev/null +++ b/packages/pieces/community/google-forms/README.md @@ -0,0 +1,7 @@ +# pieces-google-forms + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-google-forms` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/google-forms/package.json b/packages/pieces/community/google-forms/package.json new file mode 100644 index 0000000..47d62ce --- /dev/null +++ b/packages/pieces/community/google-forms/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-google-forms", + "version": "0.3.6" +} diff --git a/packages/pieces/community/google-forms/project.json b/packages/pieces/community/google-forms/project.json new file mode 100644 index 0000000..9efd769 --- /dev/null +++ b/packages/pieces/community/google-forms/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-google-forms", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/google-forms/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/google-forms", + "tsConfig": "packages/pieces/community/google-forms/tsconfig.lib.json", + "packageJson": "packages/pieces/community/google-forms/package.json", + "main": "packages/pieces/community/google-forms/src/index.ts", + "assets": [ + "packages/pieces/community/google-forms/*.md", + { + "input": "packages/pieces/community/google-forms/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/google-forms/src/index.ts b/packages/pieces/community/google-forms/src/index.ts new file mode 100644 index 0000000..9d7fa64 --- /dev/null +++ b/packages/pieces/community/google-forms/src/index.ts @@ -0,0 +1,39 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { newResponse } from './lib/triggers/new-form-response'; + +export const googleFormsAuth = PieceAuth.OAuth2({ + authUrl: 'https://accounts.google.com/o/oauth2/auth', + tokenUrl: 'https://oauth2.googleapis.com/token', + required: true, + scope: [ + 'https://www.googleapis.com/auth/forms.responses.readonly', + 'https://www.googleapis.com/auth/drive.readonly', + ], +}); + +export const googleForms = createPiece({ + displayName: 'Google Forms', + description: 'Receive form responses from Google Forms', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/google-forms.png', + categories: [PieceCategory.FORMS_AND_SURVEYS], + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud","Startouf"], + auth: googleFormsAuth, + actions: [ + createCustomApiCallAction({ + baseUrl: () => 'https://forms.googleapis.com/v1', + auth: googleFormsAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [newResponse], +}); diff --git a/packages/pieces/community/google-forms/src/lib/common/common.ts b/packages/pieces/community/google-forms/src/lib/common/common.ts new file mode 100644 index 0000000..850af2c --- /dev/null +++ b/packages/pieces/community/google-forms/src/lib/common/common.ts @@ -0,0 +1,57 @@ +import { Property, OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; + +export const googleFormsCommon = { + include_team_drives: Property.Checkbox({ + displayName: 'Include Team Drive Forms', + description: + 'Determines if forms from Team Drives should be included in the results.', + defaultValue: false, + required: false, + }), + form_id: Property.Dropdown({ + displayName: 'Form', + required: true, + refreshers: ['include_team_drives'], + options: async ({ auth, include_team_drives }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + } + const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue; + const files = ( + await httpClient.sendRequest<{ files: { id: string; name: string }[] }>( + { + method: HttpMethod.GET, + url: `https://www.googleapis.com/drive/v3/files`, + queryParams: { + q: "mimeType='application/vnd.google-apps.form'", + includeItemsFromAllDrives: include_team_drives ? 'true' : 'false', + supportsAllDrives: 'true', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp['access_token'], + }, + } + ) + ).body.files; + return { + disabled: false, + options: files.map((file: { id: string; name: string }) => { + return { + label: file.name, + value: file.id, + }; + }), + }; + }, + }), +}; diff --git a/packages/pieces/community/google-forms/src/lib/triggers/new-form-response.ts b/packages/pieces/community/google-forms/src/lib/triggers/new-form-response.ts new file mode 100644 index 0000000..ba5f95c --- /dev/null +++ b/packages/pieces/community/google-forms/src/lib/triggers/new-form-response.ts @@ -0,0 +1,129 @@ +import { + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { + createTrigger, + OAuth2PropertyValue, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import { googleFormsCommon } from '../common/common'; +import { googleFormsAuth } from '../../'; + +export const newResponse = createTrigger({ + auth: googleFormsAuth, + name: 'new_response', + displayName: 'New Response', + description: 'Triggers when there is new response', + props: { + form_id: googleFormsCommon.form_id, + include_team_drives: googleFormsCommon.include_team_drives, + }, + sampleData: { + responseId: + 'ACYDBNhZI4SENjOwT4QIcXOhgco3JhuLftjpLspxETYljVZofOWuqH7bxKQqJWDwGw2IFqE', + createTime: '2023-04-01T03:19:28.889Z', + lastSubmittedTime: '2023-04-01T03:19:28.889881Z', + answers: { + '5bdc4001': { + questionId: '5bdc4001', + textAnswers: { + answers: [ + { + value: 'test', + }, + ], + }, + }, + '283d759e': { + questionId: '283d759e', + textAnswers: { + answers: [ + { + value: 'نعم', + }, + ], + }, + }, + '46f3e9cf': { + questionId: '46f3e9cf', + textAnswers: { + answers: [ + { + value: 'test', + }, + ], + }, + }, + }, + }, + type: TriggerStrategy.POLLING, + async test(ctx) { + return await pollingHelper.test(polling, ctx); + }, + async onEnable(ctx) { + await pollingHelper.onEnable(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + }); + }, + async onDisable(ctx) { + await pollingHelper.onDisable(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + }); + }, + async run(ctx) { + return await pollingHelper.poll(polling, ctx); + }, +}); + +const polling: Polling = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + const items = await getResponse( + auth, + propsValue.form_id, + lastFetchEpochMS === 0 ? null : dayjs(lastFetchEpochMS).toISOString() + ); + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.lastSubmittedTime).valueOf(), + data: item, + })); + }, +}; + +const getResponse = async ( + authentication: OAuth2PropertyValue, + form_id: string, + startDate: string | null +) => { + let filter = {}; + if (startDate) { + filter = { + filter: 'timestamp > ' + startDate, + }; + } + const response = await httpClient.sendRequest<{ + responses: { lastSubmittedTime: string }[]; + }>({ + url: `https://forms.googleapis.com/v1/forms/${form_id}/responses`, + method: HttpMethod.GET, + headers: { + Authorization: `Bearer ${authentication.access_token}`, + }, + queryParams: filter, + }); + + const formResponses = response.body.responses; + if (formResponses && Array.isArray(formResponses)) { + return formResponses; + } + return []; +}; diff --git a/packages/pieces/community/google-forms/tsconfig.json b/packages/pieces/community/google-forms/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/google-forms/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/google-forms/tsconfig.lib.json b/packages/pieces/community/google-forms/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/google-forms/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/google-gemini/.eslintrc.json b/packages/pieces/community/google-gemini/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/google-gemini/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/google-gemini/README.md b/packages/pieces/community/google-gemini/README.md new file mode 100644 index 0000000..c403417 --- /dev/null +++ b/packages/pieces/community/google-gemini/README.md @@ -0,0 +1,7 @@ +# pieces-google-gemini + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-google-gemini` to build the library. diff --git a/packages/pieces/community/google-gemini/package.json b/packages/pieces/community/google-gemini/package.json new file mode 100644 index 0000000..0c74110 --- /dev/null +++ b/packages/pieces/community/google-gemini/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-google-gemini", + "version": "0.0.11" +} \ No newline at end of file diff --git a/packages/pieces/community/google-gemini/project.json b/packages/pieces/community/google-gemini/project.json new file mode 100644 index 0000000..c147e11 --- /dev/null +++ b/packages/pieces/community/google-gemini/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-google-gemini", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/google-gemini/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/google-gemini", + "tsConfig": "packages/pieces/community/google-gemini/tsconfig.lib.json", + "packageJson": "packages/pieces/community/google-gemini/package.json", + "main": "packages/pieces/community/google-gemini/src/index.ts", + "assets": [ + "packages/pieces/community/google-gemini/*.md", + { + "input": "packages/pieces/community/google-gemini/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-google-gemini {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/google-gemini/src/index.ts b/packages/pieces/community/google-gemini/src/index.ts new file mode 100644 index 0000000..dcc7b83 --- /dev/null +++ b/packages/pieces/community/google-gemini/src/index.ts @@ -0,0 +1,73 @@ +import { + createCustomApiCallAction, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { chatGemini } from './lib/actions/chat-gemini.action'; +import { generateContentFromImageAction } from './lib/actions/generate-content-from-image.action'; +import { generateContentAction } from './lib/actions/generate-content.action'; + +const markdownDescription = ` +Follow these instructions to get your API Key: +1. Visit the following website: https://makersuite.google.com/app/apikey +2. Once on the website, locate and click on the option to obtain your API Key. +Please note this piece uses a API in the beta phase that may change at any time. +`; + +export const googleGeminiAuth = PieceAuth.SecretText({ + description: markdownDescription, + displayName: 'API Key', + required: true, + validate: async (auth) => { + try { + await httpClient.sendRequest<{ + data: { id: string }[]; + }>({ + url: + 'https://generativelanguage.googleapis.com/v1beta/models?key=' + + auth.auth, + method: HttpMethod.GET, + }); + return { + valid: true, + }; + } catch (e: any) { + const extraErrorInfo = e.response?.body?.error?.message + ? `${e.response?.body?.error?.message} status:${e.response?.body?.error?.code}` + : e; + return { + valid: false, + error: `${extraErrorInfo}`, + }; + } + }, +}); + +export const googleGemini = createPiece({ + displayName: 'Google Gemini', + auth: googleGeminiAuth, + description: 'Use the new Gemini models from Google', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/google-gemini.png', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + authors: ["pfernandez98","kishanprmr","MoShizzle","AbdulTheActivePiecer","abuaboud"], + actions: [ + generateContentAction, + generateContentFromImageAction, + chatGemini, + createCustomApiCallAction({ + baseUrl: () => { + return 'https://generativelanguage.googleapis.com/v1beta'; + }, + auth: googleGeminiAuth, + authMapping: async (auth) => { + return { + Authorization: `Bearer ${auth}`, + }; + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/google-gemini/src/lib/actions/chat-gemini.action.ts b/packages/pieces/community/google-gemini/src/lib/actions/chat-gemini.action.ts new file mode 100644 index 0000000..9173ed1 --- /dev/null +++ b/packages/pieces/community/google-gemini/src/lib/actions/chat-gemini.action.ts @@ -0,0 +1,73 @@ +import { Content, GoogleGenerativeAI } from '@google/generative-ai'; +import { + Property, + StoreScope, + createAction, +} from '@activepieces/pieces-framework'; +import mime from 'mime-types'; +import { z } from 'zod'; +import { googleGeminiAuth } from '../../index'; +import { defaultLLM, getGeminiModelOptions } from '../common/common'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const chatGemini = createAction({ + auth: googleGeminiAuth, + name: 'chat_gemini', + displayName: 'Chat Gemini', + description: 'Chat with Google Gemini', + props: { + model: Property.Dropdown({ + displayName: 'Model', + required: true, + description: 'The model which will generate the completion', + refreshers: [], + defaultValue: defaultLLM, + options: async ({ auth }) => getGeminiModelOptions({ auth }), + }), + prompt: Property.LongText({ + displayName: 'Prompt', + required: true, + description: 'The prompt to generate content from.', + }), + memoryKey: Property.ShortText({ + displayName: 'Memory Key', + description: + 'A memory key that will keep the chat history. Keep it empty to leave Gemini without memory of previous messages.', + required: false, + }), + }, + async run({ auth, propsValue, store }) { + await propsValidation.validateZod(propsValue, { + memoryKey: z.string().max(128).optional(), + }); + + const { model, prompt, memoryKey } = propsValue; + const genAI = new GoogleGenerativeAI(auth); + const geminiModel = genAI.getGenerativeModel({ model }); + let history: Content[] = []; + + if (memoryKey) { + const storedHistory = await store.get(memoryKey, StoreScope.PROJECT); + if (Array.isArray(storedHistory)) { + history = storedHistory; + } + } + + const chat = geminiModel.startChat({ + history: history, + }); + + const result = await chat.sendMessage(prompt); + const responseText = result.response.text(); + + if (memoryKey) { + const updatedHistory = await chat.getHistory(); + await store.put(memoryKey, updatedHistory, StoreScope.PROJECT); + } + + return { + response: responseText, + history: history, + }; + }, +}); diff --git a/packages/pieces/community/google-gemini/src/lib/actions/generate-content-from-image.action.ts b/packages/pieces/community/google-gemini/src/lib/actions/generate-content-from-image.action.ts new file mode 100644 index 0000000..19c40d1 --- /dev/null +++ b/packages/pieces/community/google-gemini/src/lib/actions/generate-content-from-image.action.ts @@ -0,0 +1,79 @@ +import { promises as fs } from 'fs'; +import { tmpdir } from 'os'; +import { join } from 'path'; +import { GoogleGenerativeAI } from '@google/generative-ai'; +import { GoogleAIFileManager } from '@google/generative-ai/server'; +import { nanoid } from 'nanoid'; +import { + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { googleGeminiAuth } from '../../index'; +import { defaultLLM, getGeminiModelOptions } from '../common/common'; + +export const generateContentFromImageAction = createAction({ + description: + 'Generate content using Google Gemini using the "gemini-pro-vision" model', + displayName: 'Generate Content from Image', + name: 'generate_content_from_image', + auth: googleGeminiAuth, + props: { + prompt: Property.LongText({ + displayName: 'Prompt', + required: true, + description: 'The prompt to generate content from.', + }), + image: Property.File({ + displayName: 'Image', + required: true, + description: 'The image to generate content from.' + }), + model: Property.Dropdown({ + displayName: 'Model', + required: true, + description: 'The model which will generate the completion', + refreshers: [], + defaultValue: defaultLLM, + options: async ({ auth }) => + getGeminiModelOptions({ auth }), + }), + }, + + async run({ auth, propsValue }) { + const tempFilePath = join(tmpdir(), `gemini-image-${nanoid()}.${propsValue.image.extension}`); + + try { + const imageBuffer = Buffer.from(propsValue.image.base64, 'base64'); + await fs.writeFile(tempFilePath, imageBuffer); + + const fileManager = new GoogleAIFileManager(auth); + const uploadResult = await fileManager.uploadFile(tempFilePath, { + mimeType: `image/${propsValue.image.extension}`, + displayName: propsValue.image.filename, + }); + + const genAI = new GoogleGenerativeAI(auth); + const model = genAI.getGenerativeModel({ model: propsValue.model }); + const result = await model.generateContent([ + propsValue.prompt, + { + fileData: { + fileUri: uploadResult.file.uri, + mimeType: uploadResult.file.mimeType, + }, + }, + ]); + + const response = await result.response; + return { + text: response.text(), + raw: response, + }; + } catch (error) { + console.error('Error in generate content from image:', error); + throw error; + } finally { + await fs.unlink(tempFilePath).catch(() => void 0); + } + }, +}); diff --git a/packages/pieces/community/google-gemini/src/lib/actions/generate-content.action.ts b/packages/pieces/community/google-gemini/src/lib/actions/generate-content.action.ts new file mode 100644 index 0000000..fa484bc --- /dev/null +++ b/packages/pieces/community/google-gemini/src/lib/actions/generate-content.action.ts @@ -0,0 +1,33 @@ +import { googleGeminiAuth } from '../../index'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { GoogleGenerativeAI } from '@google/generative-ai'; +import { defaultLLM, getGeminiModelOptions } from '../common/common'; + +export const generateContentAction = createAction({ + description: + 'Generate content using Google Gemini using the "gemini-pro" model', + displayName: 'Generate Content', + name: 'generate_content', + auth: googleGeminiAuth, + props: { + prompt: Property.LongText({ + displayName: 'Prompt', + required: true, + description: 'The prompt to generate content from.', + }), + model: Property.Dropdown({ + displayName: 'Model', + required: true, + description: 'The model which will generate the completion', + refreshers: [], + defaultValue: defaultLLM, + options: async ({ auth }) => getGeminiModelOptions({ auth }), + }), + }, + async run({ auth, propsValue }) { + const genAI = new GoogleGenerativeAI(auth); + const model = genAI.getGenerativeModel({ model: propsValue.model }); + const result = await model.generateContent(propsValue.prompt); + return result.response.text(); + }, +}); diff --git a/packages/pieces/community/google-gemini/src/lib/common/common.ts b/packages/pieces/community/google-gemini/src/lib/common/common.ts new file mode 100644 index 0000000..f39a95c --- /dev/null +++ b/packages/pieces/community/google-gemini/src/lib/common/common.ts @@ -0,0 +1,52 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +export const defaultLLM = 'gemini-1.5-flash'; + +export const allowedLLMs = [ + 'gemini-1.5-flash', + 'gemini-1.5-flash-8b', + 'gemini-1.5-pro', + 'gemini-2.0-flash', + 'gemini-2.0-flash-lite', + 'gemini-2.5-flash-preview-04-17' +]; + +export const getGeminiModelOptions = async ({ auth}: { auth: string | undefined | unknown }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Enter your API key first', + options: [], + }; + } + + try { + const { body } = await httpClient.sendRequest<{ + models: { name: string; displayName: string }[]; + }>({ + method: HttpMethod.GET, + url: `https://generativelanguage.googleapis.com/v1beta/models?key=${auth}`, + }); + console.log(JSON.stringify(body,null,2)) + const options = body.models + .filter((model) => + allowedLLMs.some((allowed) => + model.name.startsWith(`models/${allowed}`) + ) + ) + .map((model) => ({ + label: model.displayName, + value: model.name.replace('models/', ''), + })); + + return { + disabled: false, + options, + }; + } catch { + return { + disabled: true, + options: [], + placeholder: "Couldn't load models, API key is invalid", + }; + } +}; diff --git a/packages/pieces/community/google-gemini/tsconfig.json b/packages/pieces/community/google-gemini/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/google-gemini/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/google-gemini/tsconfig.lib.json b/packages/pieces/community/google-gemini/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/google-gemini/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/google-my-business/.eslintrc.json b/packages/pieces/community/google-my-business/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/google-my-business/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/google-my-business/README.md b/packages/pieces/community/google-my-business/README.md new file mode 100644 index 0000000..2408495 --- /dev/null +++ b/packages/pieces/community/google-my-business/README.md @@ -0,0 +1,7 @@ +# pieces-google-my-business + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-google-my-business` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/google-my-business/package.json b/packages/pieces/community/google-my-business/package.json new file mode 100644 index 0000000..30af6d8 --- /dev/null +++ b/packages/pieces/community/google-my-business/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-google-my-business", + "version": "0.0.7" +} \ No newline at end of file diff --git a/packages/pieces/community/google-my-business/project.json b/packages/pieces/community/google-my-business/project.json new file mode 100644 index 0000000..5131bfc --- /dev/null +++ b/packages/pieces/community/google-my-business/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-google-my-business", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/google-my-business/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/google-my-business", + "tsConfig": "packages/pieces/community/google-my-business/tsconfig.lib.json", + "packageJson": "packages/pieces/community/google-my-business/package.json", + "main": "packages/pieces/community/google-my-business/src/index.ts", + "assets": [ + "packages/pieces/community/google-my-business/*.md", + { + "input": "packages/pieces/community/google-my-business/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/google-my-business/src/index.ts b/packages/pieces/community/google-my-business/src/index.ts new file mode 100644 index 0000000..1a47271 --- /dev/null +++ b/packages/pieces/community/google-my-business/src/index.ts @@ -0,0 +1,39 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createReply } from './lib/actions/create-reply'; +import { newReview } from './lib/triggers/new-review'; + +export const googleAuth = PieceAuth.OAuth2({ + authUrl: 'https://accounts.google.com/o/oauth2/auth', + tokenUrl: 'https://oauth2.googleapis.com/token', + required: true, + scope: ['https://www.googleapis.com/auth/business.manage'], +}); + +export const googleBusiness = createPiece({ + auth: googleAuth, + displayName: 'Google My Business', + description: 'Manage your business on Google', + + logoUrl: 'https://cdn.activepieces.com/pieces/google-business.png', + authors: ["kishanprmr","MoShizzle","abuaboud"], + categories: [PieceCategory.MARKETING], + actions: [ + createReply, + createCustomApiCallAction({ + baseUrl: () => { + return 'https://www.googleapis.com/business/v4'; + }, + auth: googleAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [newReview], +}); diff --git a/packages/pieces/community/google-my-business/src/lib/actions/create-reply.ts b/packages/pieces/community/google-my-business/src/lib/actions/create-reply.ts new file mode 100644 index 0000000..1d37428 --- /dev/null +++ b/packages/pieces/community/google-my-business/src/lib/actions/create-reply.ts @@ -0,0 +1,45 @@ +import { + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { googleAuth } from '../..'; +import { HttpMethod, httpClient, propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const createReply = createAction({ + name: 'create-reply', + displayName: 'Create or Update Reply', + description: 'Create or update a reply to a review if it already exists', + props: { + reviewName: Property.ShortText({ + displayName: 'Review Name', + description: 'You can find the review name from new review trigger', + required: true, + }), + comment: Property.LongText({ + displayName: 'Comment', + description: 'Comment to be added to the review', + required: true, + }), + }, + auth: googleAuth, + async run(ctx) { + const { reviewName, comment } = ctx.propsValue; + + await propsValidation.validateZod(ctx.propsValue, { + reviewName: z.string().regex(/accounts\/.*\/locations\/.*\/reviews\/.*/), + }); + + const response = await httpClient.sendRequest({ + url: ` https://mybusiness.googleapis.com/v4/${reviewName}/reply`, + method: HttpMethod.PUT, + headers: { + Authorization: `Bearer ${ctx.auth.access_token}`, + }, + body: { + comment, + }, + }); + return response; + }, +}); diff --git a/packages/pieces/community/google-my-business/src/lib/common/common.ts b/packages/pieces/community/google-my-business/src/lib/common/common.ts new file mode 100644 index 0000000..44cf578 --- /dev/null +++ b/packages/pieces/community/google-my-business/src/lib/common/common.ts @@ -0,0 +1,115 @@ +import { + Property, + OAuth2PropertyValue, + DropdownOption, +} from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, + QueryParams, +} from '@activepieces/pieces-common'; + +export const googleBusinessCommon = { + account: Property.Dropdown({ + displayName: 'Account', + required: true, + refreshers: [], + options: async (propsValue) => { + if (!propsValue['auth']) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + } + const authProp: OAuth2PropertyValue = propsValue[ + 'auth' + ] as OAuth2PropertyValue; + const response = await httpClient.sendRequest<{ + accounts: { accountName: string; name: string }[]; + }>({ + url: 'https://mybusinessbusinessinformation.googleapis.com/v1/accounts', + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp.access_token, + }, + }); + + return { + disabled: false, + options: response.body.accounts.map( + (location: { accountName: string; name: string }) => { + return { + label: location.accountName, + value: location.name, + }; + } + ), + }; + }, + }), + location: Property.Dropdown({ + displayName: 'Location', + required: true, + refreshers: ['account'], + options: async (propsValue) => { + if (!propsValue['auth'] || !propsValue['account']) { + return { + disabled: true, + options: [], + placeholder: 'Please select account first', + }; + } + const account = propsValue['account']; + const authProp: OAuth2PropertyValue = propsValue[ + 'auth' + ] as OAuth2PropertyValue; + + const options: DropdownOption[] = []; + + let nextPageToken: string | undefined; + + do { + const qs: QueryParams = { + pageSize: '100', + read_mask: 'title,name', + }; + if (nextPageToken) { + qs.pageToken = nextPageToken; + } + + const response = await httpClient.sendRequest<{ + locations: { title: string; name: string }[]; + nextPageToken?: string; + }>({ + url: `https://mybusinessbusinessinformation.googleapis.com/v1/${account}/locations`, + queryParams: qs, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp.access_token, + }, + }); + + nextPageToken = response.body.nextPageToken; + if (response.body.locations && Array.isArray(response.body.locations)) { + + for (const location of response.body.locations) { + options.push({ + label: location.title, + value: location.name, + }); + } + + } + } while (nextPageToken); + + return { + disabled: false, + options, + }; + }, + }), +}; diff --git a/packages/pieces/community/google-my-business/src/lib/triggers/new-review.ts b/packages/pieces/community/google-my-business/src/lib/triggers/new-review.ts new file mode 100644 index 0000000..9cdb4a1 --- /dev/null +++ b/packages/pieces/community/google-my-business/src/lib/triggers/new-review.ts @@ -0,0 +1,87 @@ +import { + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { + createTrigger, + OAuth2PropertyValue, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import { googleBusinessCommon } from '../common/common'; +import { googleAuth } from '../..'; + +export const newReview = createTrigger({ + name: 'new_review', + displayName: 'New Review', + description: 'Triggers when there is new review', + auth: googleAuth, + props: { + account: googleBusinessCommon.account, + location: googleBusinessCommon.location, + }, + sampleData: {}, + type: TriggerStrategy.POLLING, + async test(ctx) { + return await pollingHelper.test(polling, ctx); + }, + async onEnable(ctx) { + await pollingHelper.onEnable(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + }); + }, + async onDisable(ctx) { + await pollingHelper.onDisable(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + }); + }, + async run(ctx) { + return await pollingHelper.poll(polling, ctx); + }, +}); + +const polling: Polling< + OAuth2PropertyValue, + { location: string; account: string } +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ propsValue, lastFetchEpochMS, auth }) => { + const items = await getResponse( + auth, + propsValue.location, + propsValue.account + ); + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.createTime).valueOf(), + data: item, + })); + }, +}; + +const getResponse = async ( + authentication: OAuth2PropertyValue, + location: string, + account: string +) => { + const response = await httpClient.sendRequest<{ + reviews: { createTime: string }[]; + }>({ + url: ` https://mybusiness.googleapis.com/v4/${account}/${location}/reviews`, + method: HttpMethod.GET, + headers: { + Authorization: `Bearer ${authentication.access_token}`, + }, + queryParams: { + pageSize: '100', + orderBy: 'updateTime desc', + }, + }); + return response.body['reviews'] ?? []; +}; diff --git a/packages/pieces/community/google-my-business/tsconfig.json b/packages/pieces/community/google-my-business/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/google-my-business/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/google-my-business/tsconfig.lib.json b/packages/pieces/community/google-my-business/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/google-my-business/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/google-search-console/.eslintrc.json b/packages/pieces/community/google-search-console/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/google-search-console/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/google-search-console/README.md b/packages/pieces/community/google-search-console/README.md new file mode 100644 index 0000000..9e1cb08 --- /dev/null +++ b/packages/pieces/community/google-search-console/README.md @@ -0,0 +1,7 @@ +# pieces-google-search-console + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-google-search-console` to build the library. diff --git a/packages/pieces/community/google-search-console/package.json b/packages/pieces/community/google-search-console/package.json new file mode 100644 index 0000000..6c4bcfe --- /dev/null +++ b/packages/pieces/community/google-search-console/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-google-search-console", + "version": "0.0.3" +} \ No newline at end of file diff --git a/packages/pieces/community/google-search-console/project.json b/packages/pieces/community/google-search-console/project.json new file mode 100644 index 0000000..91414ae --- /dev/null +++ b/packages/pieces/community/google-search-console/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-google-search-console", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/google-search-console/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist\\{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/google-search-console", + "tsConfig": "packages/pieces/community/google-search-console/tsconfig.lib.json", + "packageJson": "packages/pieces/community/google-search-console/package.json", + "main": "packages/pieces/community/google-search-console/src/index.ts", + "assets": [ + "packages/pieces/community/google-search-console/*.md", + { + "input": "packages/pieces/community/google-search-console/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist\\{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/google-search-console/src/index.ts b/packages/pieces/community/google-search-console/src/index.ts new file mode 100644 index 0000000..ca6b4c8 --- /dev/null +++ b/packages/pieces/community/google-search-console/src/index.ts @@ -0,0 +1,65 @@ +import { createPiece, OAuth2PropertyValue, PieceAuth } from '@activepieces/pieces-framework'; +import { google } from 'googleapis'; +import { urlInspection } from './lib/actions/url-inspection'; +import { searchAnalytics } from './lib/actions/search-analytics'; +import { listSitemaps } from './lib/actions/list-sitemaps'; +import { submitSitemap } from './lib/actions/submit-a-sitemap'; +import { listSites } from './lib/actions/list-sites'; +import { addSite } from './lib/actions/add-a-site'; +import { deleteSite } from './lib/actions/delete-a-site'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; + + +export const googleSearchConsoleAuth = PieceAuth.OAuth2({ + description: ` + 1. Sign in to [Google Cloud Console](https://console.cloud.google.com/). + 2. Create a new project or you can use existing one. + 3. Go to **APIs & Services** and click **Enable APIs & Services**. + 4. Search for **Google Search Console API** in the search bar and enable it. + 5. Go to **OAuth consent screen** and select **External** type and click create. + 6. Fill App Name, User Support Email, and Developer Contact Information. Click on the Save and Continue button. + 7. Click on **Add or Remove Scopes** and add following scopes and click update. + - https://www.googleapis.com/auth/webmasters + 8. Click Save and Continue to finish the Scopes step. + 9. Click on the Add Users button and add a test email You can add your own email).Then finally click Save and Continue to finish the Test Users portion. + 10. Go to **Credentials**. Click on the **Create Credentials** button and select the **OAuth client ID** option. + 11. Select the application type as **Web Application** and fill the Name field. + 12. Add https://cloud.activepieces.com/redirect in **Authorized redirect URIs** field, and click on the Create button. + 13. Copy **Client ID** and **Client Secret**.`, + authUrl: 'https://accounts.google.com/o/oauth2/auth', + tokenUrl: 'https://oauth2.googleapis.com/token', + scope: ['https://www.googleapis.com/auth/webmasters'], + required: true, +}); + +export const createAuthClient = (accessToken: string) => { + const auth = new google.auth.OAuth2(); + auth.setCredentials({ access_token: accessToken }); + return google.webmasters({ version: 'v3', auth }); +}; + +export const googleSearchConsolePiece = createPiece({ + displayName: 'Google Search Console', + minimumSupportedRelease: '0.30.0', + auth: googleSearchConsoleAuth, + logoUrl: 'https://cdn.activepieces.com/pieces/google-search-console.png', + authors: ['Gushkool','kishanprmr'], + triggers: [], + actions: [ + searchAnalytics, + listSitemaps, + submitSitemap, + listSites, + addSite, + deleteSite, + urlInspection, + createCustomApiCallAction({ + baseUrl: () => 'https://www.googleapis.com/webmasters/v3', + auth: googleSearchConsoleAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], +}); +//TODO : remove this comment, add Gushkool's email to local git configuration diff --git a/packages/pieces/community/google-search-console/src/lib/actions/add-a-site.ts b/packages/pieces/community/google-search-console/src/lib/actions/add-a-site.ts new file mode 100644 index 0000000..bb63005 --- /dev/null +++ b/packages/pieces/community/google-search-console/src/lib/actions/add-a-site.ts @@ -0,0 +1,22 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { googleSearchConsoleAuth, createAuthClient } from '../../'; + +export const addSite = createAction({ + auth: googleSearchConsoleAuth, + name: 'add_site', + displayName: 'Add a Site', + description: "Adds a site to the set of the user's sites in Search Console.", + props: { + siteUrl: Property.ShortText({ + displayName: 'Site URL', + required: true, + }), + }, + async run(context) { + const webmasters = createAuthClient(context.auth.access_token); + await webmasters.sites.add({ + siteUrl: context.propsValue.siteUrl, + }); + return { success: true }; + }, +}); diff --git a/packages/pieces/community/google-search-console/src/lib/actions/delete-a-site.ts b/packages/pieces/community/google-search-console/src/lib/actions/delete-a-site.ts new file mode 100644 index 0000000..b7923b3 --- /dev/null +++ b/packages/pieces/community/google-search-console/src/lib/actions/delete-a-site.ts @@ -0,0 +1,30 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { googleSearchConsoleAuth, createAuthClient } from '../../'; +import { commonProps } from '../common'; + +export const deleteSite = createAction({ + auth: googleSearchConsoleAuth, + name: 'delete_site', + displayName: 'Delete a Site', + description: + "Removes a site from the set of the user's Search Console sites.", + props: { + siteUrl: commonProps.siteUrl, + }, + async run(context) { + const siteUrl = context.propsValue.siteUrl; + + if (!siteUrl) { + throw new Error( + 'You must provide either a Site URL or select one from the list.' + ); + } + + const webmasters = createAuthClient(context.auth.access_token); + await webmasters.sites.delete({ + siteUrl: siteUrl, + }); + + return { success: true }; + }, +}); diff --git a/packages/pieces/community/google-search-console/src/lib/actions/list-sitemaps.ts b/packages/pieces/community/google-search-console/src/lib/actions/list-sitemaps.ts new file mode 100644 index 0000000..0bc4c06 --- /dev/null +++ b/packages/pieces/community/google-search-console/src/lib/actions/list-sitemaps.ts @@ -0,0 +1,20 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { googleSearchConsoleAuth, createAuthClient } from '../../'; +import { commonProps } from '../common'; + +export const listSitemaps = createAction({ + auth: googleSearchConsoleAuth, + name: 'list_sitemaps', + displayName: 'List Sitemaps', + description: 'List all your sitemaps for a given site', + props: { + siteUrl: commonProps.siteUrl, + }, + async run(context) { + const webmasters = createAuthClient(context.auth.access_token); + const res = await webmasters.sitemaps.list({ + siteUrl: context.propsValue.siteUrl, + }); + return res.data; + }, +}); diff --git a/packages/pieces/community/google-search-console/src/lib/actions/list-sites.ts b/packages/pieces/community/google-search-console/src/lib/actions/list-sites.ts new file mode 100644 index 0000000..5f0fe32 --- /dev/null +++ b/packages/pieces/community/google-search-console/src/lib/actions/list-sites.ts @@ -0,0 +1,15 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { googleSearchConsoleAuth, createAuthClient } from '../../'; + +export const listSites = createAction({ + auth: googleSearchConsoleAuth, + name: 'list_sites', + displayName: 'List Sites', + description: "Lists the user's Search Console sites.", + props: {}, + async run(context) { + const webmasters = createAuthClient(context.auth.access_token); + const res = await webmasters.sites.list(); + return res.data; + }, +}); diff --git a/packages/pieces/community/google-search-console/src/lib/actions/search-analytics.ts b/packages/pieces/community/google-search-console/src/lib/actions/search-analytics.ts new file mode 100644 index 0000000..fd5b8aa --- /dev/null +++ b/packages/pieces/community/google-search-console/src/lib/actions/search-analytics.ts @@ -0,0 +1,98 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { googleSearchConsoleAuth, createAuthClient } from '../../'; +import { commonProps } from '../common'; +import dayjs from 'dayjs'; + + +export const searchAnalytics = createAction({ + auth: googleSearchConsoleAuth, + name: 'search_analytics', + displayName: 'Search Analytics', + description: + 'Query traffic data for your site using the Google Search Console API.', + props: { + siteUrl: commonProps.siteUrl, + startDate: Property.DateTime({ + displayName: 'Start Date', + description: + 'The start date of the date range to query (in YYYY-MM-DD format).', + required: true, + defaultValue: new Date().toISOString().split('T')[0], + }), + endDate: Property.DateTime({ + displayName: 'End Date', + description: + 'The end date of the date range to query (in YYYY-MM-DD format).', + required: true, + defaultValue: new Date().toISOString().split('T')[0], + }), + dimensions: Property.Array({ + displayName: 'Dimensions', + description: + 'The dimensions to group results by. For example: ["query", "page", "country", "device", "searchAppearance", "date"].', + required: false, + }), + filters: Property.Array({ + displayName: 'Filters', + description: + 'Optional filters to apply to the data. Filters can be used to restrict the results to a specific subset.', + properties: { + dimension: Property.ShortText({ + displayName: 'Dimension', + description: + 'The dimension to filter by (e.g., query, page, country, device).', + required: true, + }), + operator: Property.ShortText({ + displayName: 'Operator', + description: 'The filter operator to apply (e.g., equals, contains).', + required: true, + }), + expression: Property.ShortText({ + displayName: 'Expression', + description: 'The expression to compare the dimension against.', + required: true, + }), + }, + required: false, + }), + aggregationType: Property.ShortText({ + displayName: 'Aggregation Type', + description: + 'How data is aggregated. Options include "auto", "byPage", "byProperty".', + required: false, + }), + rowLimit: Property.Number({ + displayName: 'Row Limit', + description: 'The maximum number of rows to return.', + required: false, + }), + startRow: Property.Number({ + displayName: 'Start Row', + description: + 'The first row to return. Use this parameter to paginate results.', + required: false, + }), + }, + async run(context) { + const webmasters = createAuthClient(context.auth.access_token); + const filters = context.propsValue.filters as any; + const res = await webmasters.searchanalytics.query({ + siteUrl: context.propsValue.siteUrl, + requestBody: { + startDate: dayjs(context.propsValue.startDate).format('YYYY-MM-DD'), + endDate: dayjs(context.propsValue.endDate).format('YYYY-MM-DD'), + dimensions: context.propsValue.dimensions as string[], + dimensionFilterGroups: filters.map((filter: any) => ({ + dimension: filter.dimension, + operator: filter.operator, + expression: filter.expression, + })), + aggregationType: context.propsValue.aggregationType, + rowLimit: context.propsValue.rowLimit, + startRow: context.propsValue.startRow, + }, + }); + return res; + }, +}); diff --git a/packages/pieces/community/google-search-console/src/lib/actions/submit-a-sitemap.ts b/packages/pieces/community/google-search-console/src/lib/actions/submit-a-sitemap.ts new file mode 100644 index 0000000..a133416 --- /dev/null +++ b/packages/pieces/community/google-search-console/src/lib/actions/submit-a-sitemap.ts @@ -0,0 +1,25 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { googleSearchConsoleAuth, createAuthClient } from '../../'; +import { commonProps } from '../common'; + +export const submitSitemap = createAction({ + auth: googleSearchConsoleAuth, + name: 'submit_sitemap', + displayName: 'Submit a Sitemap', + description: 'Submits a sitemap for a site.', + props: { + siteUrl: commonProps.siteUrl, + feedpath: Property.ShortText({ + displayName: 'Sitemap Path', + required: true, + }), + }, + async run(context) { + const webmasters = createAuthClient(context.auth.access_token); + await webmasters.sitemaps.submit({ + siteUrl: context.propsValue.siteUrl, + feedpath: context.propsValue.feedpath, + }); + return { success: true }; + }, +}); diff --git a/packages/pieces/community/google-search-console/src/lib/actions/url-inspection.ts b/packages/pieces/community/google-search-console/src/lib/actions/url-inspection.ts new file mode 100644 index 0000000..8ce7b8b --- /dev/null +++ b/packages/pieces/community/google-search-console/src/lib/actions/url-inspection.ts @@ -0,0 +1,43 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { googleSearchConsoleAuth } from '../../'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { commonProps } from '../common'; + +export const urlInspection = createAction({ + auth: googleSearchConsoleAuth, + name: 'urlInspection', + displayName: 'URL Inspection', + description: + "Use the URL Inspection action to check the status and presence of a specific page within Google's index.", + props: { + siteUrl: commonProps.siteUrl, + url: Property.ShortText({ + displayName: 'URL to Inspect', + required: true, + }), + }, + async run(context) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: 'https://searchconsole.googleapis.com/v1/urlInspection/index:inspect', + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + headers: { 'Content-Type': 'application/json' }, + body: { + inspectionUrl: context.propsValue.url, + siteUrl: context.propsValue.siteUrl, + }, + }; + + const response = await httpClient.sendRequest(request); + + return response.body; + }, +}); diff --git a/packages/pieces/community/google-search-console/src/lib/common/index.ts b/packages/pieces/community/google-search-console/src/lib/common/index.ts new file mode 100644 index 0000000..51c2b1f --- /dev/null +++ b/packages/pieces/community/google-search-console/src/lib/common/index.ts @@ -0,0 +1,26 @@ +import { createAuthClient, googleSearchConsoleAuth } from '../../'; +import { PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; + +export const commonProps = { + siteUrl: Property.Dropdown({ + displayName: 'Site URL', + required: true, + refreshers: [], + refreshOnSearch: false, + options: async ({ auth }) => { + const authValue = auth as PiecePropValueSchema< + typeof googleSearchConsoleAuth + >; + const webmasters = createAuthClient(authValue.access_token); + const res = await webmasters.sites.list(); + const sites = res.data.siteEntry || []; + + return { + options: sites.map((site: any) => ({ + label: site.siteUrl, + value: site.siteUrl, + })), + }; + }, + }), +}; diff --git a/packages/pieces/community/google-search-console/tsconfig.json b/packages/pieces/community/google-search-console/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/google-search-console/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/google-search-console/tsconfig.lib.json b/packages/pieces/community/google-search-console/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/google-search-console/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/google-sheets/.babelrc b/packages/pieces/community/google-sheets/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/google-sheets/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/google-sheets/.eslintrc.json b/packages/pieces/community/google-sheets/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/google-sheets/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/google-sheets/README.md b/packages/pieces/community/google-sheets/README.md new file mode 100644 index 0000000..38c062f --- /dev/null +++ b/packages/pieces/community/google-sheets/README.md @@ -0,0 +1,7 @@ +# pieces-google-sheets + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-google-sheets` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/google-sheets/package.json b/packages/pieces/community/google-sheets/package.json new file mode 100644 index 0000000..0f689a1 --- /dev/null +++ b/packages/pieces/community/google-sheets/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-google-sheets", + "version": "0.12.4" +} diff --git a/packages/pieces/community/google-sheets/project.json b/packages/pieces/community/google-sheets/project.json new file mode 100644 index 0000000..e8fb241 --- /dev/null +++ b/packages/pieces/community/google-sheets/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-google-sheets", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/google-sheets/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/google-sheets", + "tsConfig": "packages/pieces/community/google-sheets/tsconfig.lib.json", + "packageJson": "packages/pieces/community/google-sheets/package.json", + "main": "packages/pieces/community/google-sheets/src/index.ts", + "assets": [ + "packages/pieces/community/google-sheets/*.md", + { + "input": "packages/pieces/community/google-sheets/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/ar.json b/packages/pieces/community/google-sheets/src/i18n/ar.json new file mode 100644 index 0000000..ca7f145 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/ar.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google Sheets", + "Create, edit, and collaborate on spreadsheets online": "Create, edit, and collaborate on spreadsheets online", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Insert Multiple Rows", + "Delete Row": "Delete Row", + "Update Row": "Update Row", + "Find Rows": "Find Rows", + "Create Spreadsheet": "Create Spreadsheet", + "Create Worksheet": "Create Worksheet", + "Clear Sheet": "Clear Sheet", + "Get Row": "Get Row", + "Get next row(s)": "Get next row(s)", + "Find Spreadsheet(s)": "Find Spreadsheet(s)", + "Find Worksheet(s)": "Find Worksheet(s)", + "Copy Worksheet": "Copy Worksheet", + "Update Multiple Rows": "Update Multiple Rows", + "Create Spreadsheet Column": "Create Spreadsheet Column", + "Custom API Call": "Custom API Call", + "Append a row of values to an existing sheet": "Append a row of values to an existing sheet", + "Add one or more new rows in a specific spreadsheet.": "Add one or more new rows in a specific spreadsheet.", + "Delete a row on an existing sheet you have access to": "Delete a row on an existing sheet you have access to", + "Overwrite values in an existing row": "Overwrite values in an existing row", + "Find or get rows in a Google Sheet by column name and search value": "Find or get rows in a Google Sheet by column name and search value", + "Creates a blank spreadsheet.": "Creates a blank spreadsheet.", + "Create a blank worksheet with a title.": "Create a blank worksheet with a title.", + "Clears all rows on an existing sheet": "Clears all rows on an existing sheet", + "Get a row in a Google Sheet by row number": "Get a row in a Google Sheet by row number", + "Get next group of rows from a Google Sheet": "Get next group of rows from a Google Sheet", + "Find spreadsheet(s) by name.": "Find spreadsheet(s) by name.", + "Finds a worksheet(s) by title.": "Finds a worksheet(s) by title.", + "Creates a new worksheet by copying an existing one.": "Creates a new worksheet by copying an existing one.", + "Updates multiple rows in a specific spreadsheet.": "Updates multiple rows in a specific spreadsheet.", + "Adds a new column to a spreadsheet.": "Adds a new column to a spreadsheet.", + "Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint", + "Include Team Drive Sheets ?": "Include Team Drive Sheets ?", + "Spreadsheet": "Spreadsheet", + "Sheet": "Sheet", + "As String": "As String", + "Does the first row contain headers?": "Does the first row contain headers?", + "Values": "Values", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Overwrite Existing Data?", + "Avoid Duplicates?": "Avoid Duplicates?", + "Duplicate Value Column": "Duplicate Value Column", + "Row Number": "Row Number", + "The name of the column to search in": "The name of the column to search in", + "Search Value": "Search Value", + "Exact match": "Exact match", + "Starting Row": "Starting Row", + "Number of Rows": "Number of Rows", + "Title": "Title", + "Parent Folder": "Parent Folder", + "Headers": "Headers", + "Is First row Headers?": "Is First row Headers?", + "Start Row": "Start Row", + "Markdown": "Markdown", + "Memory Key": "Memory Key", + "Group Size": "Group Size", + "Spreadsheet Name": "Spreadsheet Name", + "Exact Match": "Exact Match", + "Spreadsheet Containing the Worksheet to Copy": "Spreadsheet Containing the Worksheet to Copy", + "Worksheet to Copy": "Worksheet to Copy", + "Spreadsheet to paste in": "Spreadsheet to paste in", + "Column Name": "Column Name", + "Column Index": "Column Index", + "Method": "Method", + "Query Parameters": "Query Parameters", + "Body": "Body", + "No Error on Failure": "No Error on Failure", + "Timeout (in seconds)": "Timeout (in seconds)", + "Determines if sheets from Team Drives should be included in the results.": "Determines if sheets from Team Drives should be included in the results.", + "The ID of the spreadsheet to use.": "The ID of the spreadsheet to use.", + "The ID of the sheet to use.": "The ID of the sheet to use.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Inserted values that are dates and formulas will be entered strings and have no effect", + "If the first row is headers": "If the first row is headers", + "The values to insert": "The values to insert", + "Select the format of the input values to be inserted into the sheet.": "Select the format of the input values to be inserted into the sheet.", + "The values to insert.": "The values to insert.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.", + "The column to check for duplicate values.": "The column to check for duplicate values.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Inserted values that are dates and formulas will be entered as strings and have no effect", + "The row number to remove": "The row number to remove", + "The row number to update": "The row number to update", + "The value to search for in the specified column. If left empty, all rows will be returned.": "The value to search for in the specified column. If left empty, all rows will be returned.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Whether to choose the rows with exact match or choose the rows that contain the search value", + "The row number to start searching from": "The row number to start searching from", + "The number of rows to return ( the default is 1 if not specified )": "The number of rows to return ( the default is 1 if not specified )", + "The title of the new spreadsheet.": "The title of the new spreadsheet.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.", + "The title of the new worksheet.": "The title of the new worksheet.", + "The row number to get from the sheet": "The row number to get from the sheet", + "Which row to start from?": "Which row to start from?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "The key used to store the current row number in memory", + "The number of rows to get": "The number of rows to get", + "The name of the spreadsheet(s) to find.": "The name of the spreadsheet(s) to find.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.", + "The values to update.": "The values to update.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.", + "Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Column Names", + "GET": "GET", + "POST": "POST", + "PATCH": "PATCH", + "PUT": "PUT", + "DELETE": "DELETE", + "HEAD": "HEAD", + "New Row Added": "New Row Added", + "New or Updated Row": "New or Updated Row", + "New Spreadsheet": "New Spreadsheet", + "New Worksheet": "New Worksheet", + "Triggers when a new row is added to bottom of a spreadsheet.": "Triggers when a new row is added to bottom of a spreadsheet.", + "Triggers when a new row is added or modified in a spreadsheet.": "Triggers when a new row is added or modified in a spreadsheet.", + "Triggers when a new spreadsheet is created.": "Triggers when a new spreadsheet is created.", + "Triggers when a worksheet is created in a spreadsheet.": "Triggers when a worksheet is created in a spreadsheet.", + "Trigger Column": "Trigger Column", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/bg.json b/packages/pieces/community/google-sheets/src/i18n/bg.json new file mode 100644 index 0000000..ca7f145 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/bg.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google Sheets", + "Create, edit, and collaborate on spreadsheets online": "Create, edit, and collaborate on spreadsheets online", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Insert Multiple Rows", + "Delete Row": "Delete Row", + "Update Row": "Update Row", + "Find Rows": "Find Rows", + "Create Spreadsheet": "Create Spreadsheet", + "Create Worksheet": "Create Worksheet", + "Clear Sheet": "Clear Sheet", + "Get Row": "Get Row", + "Get next row(s)": "Get next row(s)", + "Find Spreadsheet(s)": "Find Spreadsheet(s)", + "Find Worksheet(s)": "Find Worksheet(s)", + "Copy Worksheet": "Copy Worksheet", + "Update Multiple Rows": "Update Multiple Rows", + "Create Spreadsheet Column": "Create Spreadsheet Column", + "Custom API Call": "Custom API Call", + "Append a row of values to an existing sheet": "Append a row of values to an existing sheet", + "Add one or more new rows in a specific spreadsheet.": "Add one or more new rows in a specific spreadsheet.", + "Delete a row on an existing sheet you have access to": "Delete a row on an existing sheet you have access to", + "Overwrite values in an existing row": "Overwrite values in an existing row", + "Find or get rows in a Google Sheet by column name and search value": "Find or get rows in a Google Sheet by column name and search value", + "Creates a blank spreadsheet.": "Creates a blank spreadsheet.", + "Create a blank worksheet with a title.": "Create a blank worksheet with a title.", + "Clears all rows on an existing sheet": "Clears all rows on an existing sheet", + "Get a row in a Google Sheet by row number": "Get a row in a Google Sheet by row number", + "Get next group of rows from a Google Sheet": "Get next group of rows from a Google Sheet", + "Find spreadsheet(s) by name.": "Find spreadsheet(s) by name.", + "Finds a worksheet(s) by title.": "Finds a worksheet(s) by title.", + "Creates a new worksheet by copying an existing one.": "Creates a new worksheet by copying an existing one.", + "Updates multiple rows in a specific spreadsheet.": "Updates multiple rows in a specific spreadsheet.", + "Adds a new column to a spreadsheet.": "Adds a new column to a spreadsheet.", + "Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint", + "Include Team Drive Sheets ?": "Include Team Drive Sheets ?", + "Spreadsheet": "Spreadsheet", + "Sheet": "Sheet", + "As String": "As String", + "Does the first row contain headers?": "Does the first row contain headers?", + "Values": "Values", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Overwrite Existing Data?", + "Avoid Duplicates?": "Avoid Duplicates?", + "Duplicate Value Column": "Duplicate Value Column", + "Row Number": "Row Number", + "The name of the column to search in": "The name of the column to search in", + "Search Value": "Search Value", + "Exact match": "Exact match", + "Starting Row": "Starting Row", + "Number of Rows": "Number of Rows", + "Title": "Title", + "Parent Folder": "Parent Folder", + "Headers": "Headers", + "Is First row Headers?": "Is First row Headers?", + "Start Row": "Start Row", + "Markdown": "Markdown", + "Memory Key": "Memory Key", + "Group Size": "Group Size", + "Spreadsheet Name": "Spreadsheet Name", + "Exact Match": "Exact Match", + "Spreadsheet Containing the Worksheet to Copy": "Spreadsheet Containing the Worksheet to Copy", + "Worksheet to Copy": "Worksheet to Copy", + "Spreadsheet to paste in": "Spreadsheet to paste in", + "Column Name": "Column Name", + "Column Index": "Column Index", + "Method": "Method", + "Query Parameters": "Query Parameters", + "Body": "Body", + "No Error on Failure": "No Error on Failure", + "Timeout (in seconds)": "Timeout (in seconds)", + "Determines if sheets from Team Drives should be included in the results.": "Determines if sheets from Team Drives should be included in the results.", + "The ID of the spreadsheet to use.": "The ID of the spreadsheet to use.", + "The ID of the sheet to use.": "The ID of the sheet to use.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Inserted values that are dates and formulas will be entered strings and have no effect", + "If the first row is headers": "If the first row is headers", + "The values to insert": "The values to insert", + "Select the format of the input values to be inserted into the sheet.": "Select the format of the input values to be inserted into the sheet.", + "The values to insert.": "The values to insert.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.", + "The column to check for duplicate values.": "The column to check for duplicate values.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Inserted values that are dates and formulas will be entered as strings and have no effect", + "The row number to remove": "The row number to remove", + "The row number to update": "The row number to update", + "The value to search for in the specified column. If left empty, all rows will be returned.": "The value to search for in the specified column. If left empty, all rows will be returned.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Whether to choose the rows with exact match or choose the rows that contain the search value", + "The row number to start searching from": "The row number to start searching from", + "The number of rows to return ( the default is 1 if not specified )": "The number of rows to return ( the default is 1 if not specified )", + "The title of the new spreadsheet.": "The title of the new spreadsheet.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.", + "The title of the new worksheet.": "The title of the new worksheet.", + "The row number to get from the sheet": "The row number to get from the sheet", + "Which row to start from?": "Which row to start from?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "The key used to store the current row number in memory", + "The number of rows to get": "The number of rows to get", + "The name of the spreadsheet(s) to find.": "The name of the spreadsheet(s) to find.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.", + "The values to update.": "The values to update.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.", + "Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Column Names", + "GET": "GET", + "POST": "POST", + "PATCH": "PATCH", + "PUT": "PUT", + "DELETE": "DELETE", + "HEAD": "HEAD", + "New Row Added": "New Row Added", + "New or Updated Row": "New or Updated Row", + "New Spreadsheet": "New Spreadsheet", + "New Worksheet": "New Worksheet", + "Triggers when a new row is added to bottom of a spreadsheet.": "Triggers when a new row is added to bottom of a spreadsheet.", + "Triggers when a new row is added or modified in a spreadsheet.": "Triggers when a new row is added or modified in a spreadsheet.", + "Triggers when a new spreadsheet is created.": "Triggers when a new spreadsheet is created.", + "Triggers when a worksheet is created in a spreadsheet.": "Triggers when a worksheet is created in a spreadsheet.", + "Trigger Column": "Trigger Column", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/ca.json b/packages/pieces/community/google-sheets/src/i18n/ca.json new file mode 100644 index 0000000..ca7f145 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/ca.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google Sheets", + "Create, edit, and collaborate on spreadsheets online": "Create, edit, and collaborate on spreadsheets online", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Insert Multiple Rows", + "Delete Row": "Delete Row", + "Update Row": "Update Row", + "Find Rows": "Find Rows", + "Create Spreadsheet": "Create Spreadsheet", + "Create Worksheet": "Create Worksheet", + "Clear Sheet": "Clear Sheet", + "Get Row": "Get Row", + "Get next row(s)": "Get next row(s)", + "Find Spreadsheet(s)": "Find Spreadsheet(s)", + "Find Worksheet(s)": "Find Worksheet(s)", + "Copy Worksheet": "Copy Worksheet", + "Update Multiple Rows": "Update Multiple Rows", + "Create Spreadsheet Column": "Create Spreadsheet Column", + "Custom API Call": "Custom API Call", + "Append a row of values to an existing sheet": "Append a row of values to an existing sheet", + "Add one or more new rows in a specific spreadsheet.": "Add one or more new rows in a specific spreadsheet.", + "Delete a row on an existing sheet you have access to": "Delete a row on an existing sheet you have access to", + "Overwrite values in an existing row": "Overwrite values in an existing row", + "Find or get rows in a Google Sheet by column name and search value": "Find or get rows in a Google Sheet by column name and search value", + "Creates a blank spreadsheet.": "Creates a blank spreadsheet.", + "Create a blank worksheet with a title.": "Create a blank worksheet with a title.", + "Clears all rows on an existing sheet": "Clears all rows on an existing sheet", + "Get a row in a Google Sheet by row number": "Get a row in a Google Sheet by row number", + "Get next group of rows from a Google Sheet": "Get next group of rows from a Google Sheet", + "Find spreadsheet(s) by name.": "Find spreadsheet(s) by name.", + "Finds a worksheet(s) by title.": "Finds a worksheet(s) by title.", + "Creates a new worksheet by copying an existing one.": "Creates a new worksheet by copying an existing one.", + "Updates multiple rows in a specific spreadsheet.": "Updates multiple rows in a specific spreadsheet.", + "Adds a new column to a spreadsheet.": "Adds a new column to a spreadsheet.", + "Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint", + "Include Team Drive Sheets ?": "Include Team Drive Sheets ?", + "Spreadsheet": "Spreadsheet", + "Sheet": "Sheet", + "As String": "As String", + "Does the first row contain headers?": "Does the first row contain headers?", + "Values": "Values", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Overwrite Existing Data?", + "Avoid Duplicates?": "Avoid Duplicates?", + "Duplicate Value Column": "Duplicate Value Column", + "Row Number": "Row Number", + "The name of the column to search in": "The name of the column to search in", + "Search Value": "Search Value", + "Exact match": "Exact match", + "Starting Row": "Starting Row", + "Number of Rows": "Number of Rows", + "Title": "Title", + "Parent Folder": "Parent Folder", + "Headers": "Headers", + "Is First row Headers?": "Is First row Headers?", + "Start Row": "Start Row", + "Markdown": "Markdown", + "Memory Key": "Memory Key", + "Group Size": "Group Size", + "Spreadsheet Name": "Spreadsheet Name", + "Exact Match": "Exact Match", + "Spreadsheet Containing the Worksheet to Copy": "Spreadsheet Containing the Worksheet to Copy", + "Worksheet to Copy": "Worksheet to Copy", + "Spreadsheet to paste in": "Spreadsheet to paste in", + "Column Name": "Column Name", + "Column Index": "Column Index", + "Method": "Method", + "Query Parameters": "Query Parameters", + "Body": "Body", + "No Error on Failure": "No Error on Failure", + "Timeout (in seconds)": "Timeout (in seconds)", + "Determines if sheets from Team Drives should be included in the results.": "Determines if sheets from Team Drives should be included in the results.", + "The ID of the spreadsheet to use.": "The ID of the spreadsheet to use.", + "The ID of the sheet to use.": "The ID of the sheet to use.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Inserted values that are dates and formulas will be entered strings and have no effect", + "If the first row is headers": "If the first row is headers", + "The values to insert": "The values to insert", + "Select the format of the input values to be inserted into the sheet.": "Select the format of the input values to be inserted into the sheet.", + "The values to insert.": "The values to insert.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.", + "The column to check for duplicate values.": "The column to check for duplicate values.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Inserted values that are dates and formulas will be entered as strings and have no effect", + "The row number to remove": "The row number to remove", + "The row number to update": "The row number to update", + "The value to search for in the specified column. If left empty, all rows will be returned.": "The value to search for in the specified column. If left empty, all rows will be returned.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Whether to choose the rows with exact match or choose the rows that contain the search value", + "The row number to start searching from": "The row number to start searching from", + "The number of rows to return ( the default is 1 if not specified )": "The number of rows to return ( the default is 1 if not specified )", + "The title of the new spreadsheet.": "The title of the new spreadsheet.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.", + "The title of the new worksheet.": "The title of the new worksheet.", + "The row number to get from the sheet": "The row number to get from the sheet", + "Which row to start from?": "Which row to start from?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "The key used to store the current row number in memory", + "The number of rows to get": "The number of rows to get", + "The name of the spreadsheet(s) to find.": "The name of the spreadsheet(s) to find.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.", + "The values to update.": "The values to update.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.", + "Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Column Names", + "GET": "GET", + "POST": "POST", + "PATCH": "PATCH", + "PUT": "PUT", + "DELETE": "DELETE", + "HEAD": "HEAD", + "New Row Added": "New Row Added", + "New or Updated Row": "New or Updated Row", + "New Spreadsheet": "New Spreadsheet", + "New Worksheet": "New Worksheet", + "Triggers when a new row is added to bottom of a spreadsheet.": "Triggers when a new row is added to bottom of a spreadsheet.", + "Triggers when a new row is added or modified in a spreadsheet.": "Triggers when a new row is added or modified in a spreadsheet.", + "Triggers when a new spreadsheet is created.": "Triggers when a new spreadsheet is created.", + "Triggers when a worksheet is created in a spreadsheet.": "Triggers when a worksheet is created in a spreadsheet.", + "Trigger Column": "Trigger Column", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/de.json b/packages/pieces/community/google-sheets/src/i18n/de.json new file mode 100644 index 0000000..f56caab --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/de.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google Tabellen", + "Create, edit, and collaborate on spreadsheets online": "Erstellen und bearbeiten Sie online Tabellenkalkulationen", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Mehrere Zeilen einfügen", + "Delete Row": "Zeile löschen", + "Update Row": "Zeile aktualisieren", + "Find Rows": "Zeilen finden", + "Create Spreadsheet": "Tabelle erstellen", + "Create Worksheet": "Arbeitsblatt erstellen", + "Clear Sheet": "Platte löschen", + "Get Row": "Zeile holen", + "Get next row(s)": "Nächste Zeile(n) holen", + "Find Spreadsheet(s)": "Tabellenkalkulation(en) finden", + "Find Worksheet(s)": "Arbeitsblatt(e) finden", + "Copy Worksheet": "Arbeitsblatt kopieren", + "Update Multiple Rows": "Mehrere Zeilen aktualisieren", + "Create Spreadsheet Column": "Tabellenspalte erstellen", + "Custom API Call": "Eigener API-Aufruf", + "Append a row of values to an existing sheet": "Eine Reihe von Werten an ein vorhandenes Blatt anhängen", + "Add one or more new rows in a specific spreadsheet.": "Fügen Sie eine oder mehrere neue Zeilen in einer bestimmten Tabelle hinzu.", + "Delete a row on an existing sheet you have access to": "Eine Zeile auf einem vorhandenen Blatt löschen, auf das Sie Zugriff haben", + "Overwrite values in an existing row": "Werte in einer bestehenden Zeile überschreiben", + "Find or get rows in a Google Sheet by column name and search value": "Suche oder erhalte Zeilen in einem Google-Blatt nach Spaltennamen und Suchwert", + "Creates a blank spreadsheet.": "Erzeugt eine leere Tabelle.", + "Create a blank worksheet with a title.": "Erstellen Sie ein leeres Arbeitsblatt mit einem Titel.", + "Clears all rows on an existing sheet": "Löscht alle Zeilen auf einem vorhandenen Blatt", + "Get a row in a Google Sheet by row number": "Erhalte eine Zeile in einem Google Sheet für Zeile Nummer", + "Get next group of rows from a Google Sheet": "Nächste Datensatzgruppe aus einem Google Sheet holen", + "Find spreadsheet(s) by name.": "Finde Tabellenkalkulation(en) nach Namen.", + "Finds a worksheet(s) by title.": "Findet eine Tabelle(n) nach Titel.", + "Creates a new worksheet by copying an existing one.": "Erstellt ein neues Arbeitsblatt, indem ein vorhandenes Exemplar kopiert wird.", + "Updates multiple rows in a specific spreadsheet.": "Aktualisiert mehrere Zeilen in einer bestimmten Tabellenkalkulation.", + "Adds a new column to a spreadsheet.": "Fügt einer Tabelle eine neue Spalte hinzu.", + "Make a custom API call to a specific endpoint": "Einen benutzerdefinierten API-Aufruf an einen bestimmten Endpunkt machen", + "Include Team Drive Sheets ?": "Team Drive Sheets einbinden ?", + "Spreadsheet": "Tabellenblatt", + "Sheet": "Blatt", + "As String": "Als Zeichenkette", + "Does the first row contain headers?": "Enthält die erste Zeile Kopfzeilen?", + "Values": "Werte", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Bestehende Daten überschreiben?", + "Avoid Duplicates?": "Duplikate vermeiden?", + "Duplicate Value Column": "Spalte Wert duplizieren", + "Row Number": "Zeilennummer", + "The name of the column to search in": "Der Name der zu durchsuchenden Spalte", + "Search Value": "Suchwert", + "Exact match": "Genaue Übereinstimmung", + "Starting Row": "Startzeile", + "Number of Rows": "Anzahl der Zeilen", + "Title": "Titel", + "Parent Folder": "Eltern-Ordner", + "Headers": "Kopfzeilen", + "Is First row Headers?": "Sind die Kopfzeilen der ersten Reihe?", + "Start Row": "Start-Zeile", + "Markdown": "Markdown", + "Memory Key": "Speicherschlüssel", + "Group Size": "Gruppengröße", + "Spreadsheet Name": "Tabellenname", + "Exact Match": "Genaues Match", + "Spreadsheet Containing the Worksheet to Copy": "Tabellenblatt mit dem zu kopierenden Arbeitsblatt", + "Worksheet to Copy": "Arbeitsblatt zum Kopieren", + "Spreadsheet to paste in": "Tabelle zum Einfügen", + "Column Name": "Spaltenname", + "Column Index": "Spaltenindex", + "Method": "Methode", + "Query Parameters": "Abfrageparameter", + "Body": "Körper", + "No Error on Failure": "Kein Fehler bei Fehler", + "Timeout (in seconds)": "Timeout (in Sekunden)", + "Determines if sheets from Team Drives should be included in the results.": "Legt fest, ob Blätter von Team Drives in die Ergebnisse aufgenommen werden sollen.", + "The ID of the spreadsheet to use.": "Die ID der zu verwendenden Tabellenkalkulation.", + "The ID of the sheet to use.": "Die ID des zu verwendenden Blatts.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Fügte Werte als Daten und Formeln werden eingegeben und haben keine Wirkung", + "If the first row is headers": "Wenn die erste Zeile Kopfzeilen ist", + "The values to insert": "Die einzufügenden Werte", + "Select the format of the input values to be inserted into the sheet.": "Wählen Sie das Format der Eingabewerte, die in das Blatt eingefügt werden sollen.", + "The values to insert.": "Die einzufügenden Werte.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Aktivieren Sie diese Option, um alle vorhandenen Daten im Blatt durch neue Daten aus Ihrer Eingabe zu ersetzen. Dadurch werden alle zusätzlichen Datensätze außerhalb des aktualisierten Bereichs gelöscht.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Aktivieren Sie diese Option, um vor dem Einfügen von Daten in das Tabellenblatt auf doppelte Werte zu überprüfen. Nur eindeutige Zeilen werden basierend auf der ausgewählten Spalte hinzugefügt.", + "The column to check for duplicate values.": "Die Spalte, die auf doppelte Werte überprüft werden soll.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Fügte Werte als Daten und Formeln werden als Zeichenketten eingegeben und haben keine Wirkung", + "The row number to remove": "Die zu entfernende Zeilennummer", + "The row number to update": "Die zu aktualisierende Zeilennummer", + "The value to search for in the specified column. If left empty, all rows will be returned.": "Der zu suchende Wert in der angegebenen Spalte. Wenn leer bleibt, werden alle Zeilen zurückgegeben.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Gibt an, ob die Zeilen mit exakter Übereinstimmung ausgewählt werden sollen oder welche Zeilen den Suchwert enthalten", + "The row number to start searching from": "Die Zeilennummer, von der die Suche gestartet wird", + "The number of rows to return ( the default is 1 if not specified )": "Die Anzahl der zurückzugebenden Zeilen (Standardwert ist 1, wenn nicht spezifiziert)", + "The title of the new spreadsheet.": "Der Titel der neuen Tabelle.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "Der Ordner, in dem das Arbeitsblatt erstellt wird. Standardmäßig wird das neue Arbeitsblatt im Stammverzeichnis des Laufwerks erstellt.", + "The title of the new worksheet.": "Der Titel der neuen Tabelle.", + "The row number to get from the sheet": "Die Zeilennummer, die vom Blatt abgerufen werden soll", + "Which row to start from?": "Von welcher Zeile soll begonnen werden?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notizen:**\n\n- Speicherschlüssel wird verwendet, um sich zu erinnern, wo die letzte Zeile verarbeitet wurde, und wird in den folgenden Ausläufen verwendet.\n- Wiederveröffentlichung des Ströms **verwahrt** den Speicherschlüsselwert, wenn Sie den Speicherschlüssel **ändern wollen** wollen.\n", + "The key used to store the current row number in memory": "Der Schlüssel, mit dem die aktuelle Zeilennummer im Speicher gespeichert wird", + "The number of rows to get": "Die Anzahl der zu erhaltenden Zeilen", + "The name of the spreadsheet(s) to find.": "Der Name der zu findenden Tabellenkalkulation(en).", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "Wenn true gibt nur Tabellenkalkulationen zurück, die genau mit dem Namen übereinstimmen. Wenn falsch, gibt die Tabellenkalkulation den Namen zurück.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "Wenn aktiviert, geben Sie nur Arbeitsblätter zurück, die genau mit dem Namen übereinstimmen. Wenn falsch, geben Sie Arbeitsblätter zurück, die den Namen enthalten.", + "The values to update.": "Die zu aktualisierenden Werte.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "Der Spaltenindex beginnt ab 1.Zum Beispiel, wenn Sie eine Spalte zur dritten Spalte hinzufügen möchten, geben Sie 3 ein. f die Eingabe kleiner als 1 ist, wird die Spalte nach der letzten aktuellen Spalte hinzugefügt.", + "Authorization headers are injected automatically from your connection.": "Autorisierungs-Header werden automatisch von Ihrer Verbindung injiziert.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Spaltennamen", + "GET": "ERHALTEN", + "POST": "POST", + "PATCH": "PATCH", + "PUT": "PUT", + "DELETE": "LÖSCHEN", + "HEAD": "HEAD", + "New Row Added": "Neue Zeile hinzugefügt", + "New or Updated Row": "Neue oder aktualisierte Zeile", + "New Spreadsheet": "Neues Tabellenblatt", + "New Worksheet": "Neues Arbeitsblatt", + "Triggers when a new row is added to bottom of a spreadsheet.": "Wird ausgelöst, wenn eine neue Zeile am unteren Ende einer Tabelle hinzugefügt wird.", + "Triggers when a new row is added or modified in a spreadsheet.": "Wird ausgelöst, wenn eine neue Zeile in einer Tabelle hinzugefügt oder verändert wird.", + "Triggers when a new spreadsheet is created.": "Wird ausgelöst, wenn eine neue Tabellenkalkulation erstellt wird.", + "Triggers when a worksheet is created in a spreadsheet.": "Wird ausgelöst, wenn eine Tabelle in einer Tabelle erstellt wird.", + "Trigger Column": "Spalte auslösen", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Bitte beachten Sie, dass es aufgrund einer Verzögerung von Google eine Verzögerung von bis zu 3 Minuten geben kann, bis der Trigger angezündet wird.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Trigger nur bei Änderungen an Zellen in dieser Spalte. Wählen Sie **Alle Spalten** aus, wenn der Fluss bei Änderungen an einer Zelle innerhalb der Zelle ausgelöst werden soll." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/es.json b/packages/pieces/community/google-sheets/src/i18n/es.json new file mode 100644 index 0000000..0ae29e0 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/es.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Hojas de Google", + "Create, edit, and collaborate on spreadsheets online": "Crear, editar y colaborar en hojas de cálculo en línea", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Insertar múltiples filas", + "Delete Row": "Eliminar fila", + "Update Row": "Actualizar fila", + "Find Rows": "Encontrar filas", + "Create Spreadsheet": "Crear hoja de cálculo", + "Create Worksheet": "Crear hoja de trabajo", + "Clear Sheet": "Limpiar hoja", + "Get Row": "Obtener fila", + "Get next row(s)": "Obtener siguiente fila(s)", + "Find Spreadsheet(s)": "Buscar Hoja(s)", + "Find Worksheet(s)": "Buscar Hoja(s)", + "Copy Worksheet": "Copiar hoja de trabajo", + "Update Multiple Rows": "Actualizar múltiples filas", + "Create Spreadsheet Column": "Crear columna de hoja de cálculo", + "Custom API Call": "Llamada API personalizada", + "Append a row of values to an existing sheet": "Añadir una fila de valores a una hoja existente", + "Add one or more new rows in a specific spreadsheet.": "Añadir una o más filas nuevas en una hoja de cálculo específica.", + "Delete a row on an existing sheet you have access to": "Eliminar una fila en una hoja existente a la que tienes acceso", + "Overwrite values in an existing row": "Sobrescribir valores en una fila existente", + "Find or get rows in a Google Sheet by column name and search value": "Buscar o obtener filas en una hoja de Google por nombre de columna y valor de búsqueda", + "Creates a blank spreadsheet.": "Crea una hoja de cálculo en blanco.", + "Create a blank worksheet with a title.": "Crear una hoja de trabajo en blanco con un título.", + "Clears all rows on an existing sheet": "Limpia todas las filas en una hoja existente", + "Get a row in a Google Sheet by row number": "Obtener una fila en una hoja de Google por número de fila", + "Get next group of rows from a Google Sheet": "Obtener el siguiente grupo de filas de una hoja de Google", + "Find spreadsheet(s) by name.": "Buscar hojas de cálculo por nombre.", + "Finds a worksheet(s) by title.": "Encuentra una hoja de cálculo por título.", + "Creates a new worksheet by copying an existing one.": "Crea una nueva hoja de trabajo copiando una existente.", + "Updates multiple rows in a specific spreadsheet.": "Actualiza múltiples filas en una hoja de cálculo específica.", + "Adds a new column to a spreadsheet.": "Añade una nueva columna a una hoja de cálculo.", + "Make a custom API call to a specific endpoint": "Hacer una llamada API personalizada a un extremo específico", + "Include Team Drive Sheets ?": "Incluye hojas de unidad de equipo ?", + "Spreadsheet": "Hoja de cálculo", + "Sheet": "Hoja", + "As String": "Como cadena", + "Does the first row contain headers?": "¿La primera fila contiene cabeceras?", + "Values": "Valores", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "¿Sobrescribir datos existentes?", + "Avoid Duplicates?": "¿Evitar duplicados?", + "Duplicate Value Column": "Duplicar columna de valor", + "Row Number": "Número de fila", + "The name of the column to search in": "El nombre de la columna en la que buscar", + "Search Value": "Valor de búsqueda", + "Exact match": "Coincidencia exacta", + "Starting Row": "Fila inicial", + "Number of Rows": "Número de filas", + "Title": "Título", + "Parent Folder": "Carpeta padre", + "Headers": "Encabezados", + "Is First row Headers?": "¿Es la primera fila de cabeceras?", + "Start Row": "Iniciar fila", + "Markdown": "Markdown", + "Memory Key": "Clave de memoria", + "Group Size": "Tamaño del grupo", + "Spreadsheet Name": "Nombre de la hoja de cálculo", + "Exact Match": "Partida exacta", + "Spreadsheet Containing the Worksheet to Copy": "Hoja de cálculo que contiene la hoja de trabajo a copiar", + "Worksheet to Copy": "Hoja de trabajo a copiar", + "Spreadsheet to paste in": "Hoja de cálculo para pegar en", + "Column Name": "Nombre de columna", + "Column Index": "Índice de columna", + "Method": "Método", + "Query Parameters": "Parámetros de consulta", + "Body": "Cuerpo", + "No Error on Failure": "No hay ningún error en fallo", + "Timeout (in seconds)": "Tiempo de espera (en segundos)", + "Determines if sheets from Team Drives should be included in the results.": "Determina si las hojas de Team Drives deben ser incluidas en los resultados.", + "The ID of the spreadsheet to use.": "El ID de la hoja de cálculo a utilizar.", + "The ID of the sheet to use.": "El ID de la hoja a utilizar.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Los valores insertados que son fechas y fórmulas serán introducidos cadenas y no tendrán efecto", + "If the first row is headers": "Si la primera fila son cabeceras", + "The values to insert": "Los valores a insertar", + "Select the format of the input values to be inserted into the sheet.": "Seleccione el formato de los valores de entrada a insertar en la hoja.", + "The values to insert.": "Los valores a insertar.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Active esta opción para reemplazar todos los datos existentes en la hoja por nuevos datos de su entrada. Esto borrará cualquier registro extra más allá del rango actualizado.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Active esta opción para buscar valores duplicados antes de insertar datos en la hoja. Sólo se añadirán filas únicas basándose en la columna seleccionada.", + "The column to check for duplicate values.": "La columna para comprobar los valores duplicados.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Los valores insertados que son fechas y fórmulas se introducirán como cadenas y no tendrán efecto", + "The row number to remove": "El número de fila a eliminar", + "The row number to update": "El número de fila a actualizar", + "The value to search for in the specified column. If left empty, all rows will be returned.": "El valor por el que buscar en la columna especificada. Si se deja vacío, todas las filas serán retornadas.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Seleccionar las filas con coincidencia exacta o elegir las filas que contienen el valor de búsqueda", + "The row number to start searching from": "El número de fila desde el que comenzar la búsqueda", + "The number of rows to return ( the default is 1 if not specified )": "El número de filas a retornar ( el valor por defecto es 1 si no se especifica)", + "The title of the new spreadsheet.": "El título de la nueva hoja de cálculo.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "La carpeta en la que crear la hoja de trabajo. De forma predeterminada, la nueva hoja de trabajo se crea en la carpeta raíz de la unidad.", + "The title of the new worksheet.": "El título de la nueva hoja de cálculo.", + "The row number to get from the sheet": "El número de fila a obtener de la hoja", + "Which row to start from?": "¿Desde qué fila empezar?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "La clave utilizada para almacenar el número de fila actual en la memoria", + "The number of rows to get": "El número de filas a obtener", + "The name of the spreadsheet(s) to find.": "El nombre de la hoja de cálculo a encontrar.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "Si es verdadero, sólo devuelve hojas de cálculo que coincidan exactamente con el nombre. Si es falso, devuelve hojas de cálculo que contengan el nombre.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "Si es verdadero, sólo devuelve hojas de cálculo que coincidan exactamente con el nombre. Si es falso, devuelve hojas de cálculo que contengan el nombre.", + "The values to update.": "Los valores a actualizar.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "El índice de columna comienza desde 1.Por ejemplo, si desea agregar una columna a la tercera columna, introduzca 3. f la entrada es menor que 1 la columna será añadida después de la última columna actual.", + "Authorization headers are injected automatically from your connection.": "Las cabeceras de autorización se inyectan automáticamente desde tu conexión.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Nombres de columna", + "GET": "RECOGER", + "POST": "POST", + "PATCH": "PATCH", + "PUT": "PUT", + "DELETE": "BORRAR", + "HEAD": "LIMPIO", + "New Row Added": "Nueva fila añadida", + "New or Updated Row": "Fila nueva o actualizada", + "New Spreadsheet": "Nueva hoja de cálculo", + "New Worksheet": "Nueva hoja de trabajo", + "Triggers when a new row is added to bottom of a spreadsheet.": "Se activa cuando se añade una nueva fila a la parte inferior de una hoja de cálculo.", + "Triggers when a new row is added or modified in a spreadsheet.": "Se activa cuando se añade o modifica una nueva fila en una hoja de cálculo.", + "Triggers when a new spreadsheet is created.": "Se activa cuando se crea una nueva hoja de cálculo.", + "Triggers when a worksheet is created in a spreadsheet.": "Se activa cuando se crea una hoja de cálculo en una hoja de cálculo.", + "Trigger Column": "Columna de gatillo", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Tenga en cuenta que puede haber un retraso de hasta 3 minutos para que el disparador se dispare, debido a un retraso de Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Activar sólo en los cambios a las celdas de esta columna. Selecciona **Todas las Columnas** si quieres que el flujo se active al realizar cambios en cualquier celda dentro de la fila." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/fr.json b/packages/pieces/community/google-sheets/src/i18n/fr.json new file mode 100644 index 0000000..b5edf01 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/fr.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Feuilles Google", + "Create, edit, and collaborate on spreadsheets online": "Créer, modifier et collaborer sur des feuilles de calcul en ligne", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Insérer plusieurs lignes", + "Delete Row": "Supprimer la ligne", + "Update Row": "Mettre à jour la ligne", + "Find Rows": "Trouver des lignes", + "Create Spreadsheet": "Créer une feuille de calcul", + "Create Worksheet": "Créer une fiche", + "Clear Sheet": "Vider la feuille", + "Get Row": "Obtenir la ligne", + "Get next row(s)": "Récupérer les lignes suivantes", + "Find Spreadsheet(s)": "Trouver des feuilles de calcul", + "Find Worksheet(s)": "Trouver des feuilles de travail", + "Copy Worksheet": "Copier la fiche", + "Update Multiple Rows": "Mettre à jour plusieurs lignes", + "Create Spreadsheet Column": "Créer une colonne de feuille de calcul", + "Custom API Call": "Appel API personnalisé", + "Append a row of values to an existing sheet": "Ajouter une ligne de valeurs à une feuille existante", + "Add one or more new rows in a specific spreadsheet.": "Ajouter une ou plusieurs nouvelles lignes dans une feuille de calcul spécifique.", + "Delete a row on an existing sheet you have access to": "Supprimer une ligne sur une feuille existante à laquelle vous avez accès", + "Overwrite values in an existing row": "Écraser les valeurs dans une ligne existante", + "Find or get rows in a Google Sheet by column name and search value": "Rechercher ou obtenir des lignes dans un Google Sheet par nom de colonne et valeur de recherche", + "Creates a blank spreadsheet.": "Crée une feuille de calcul vide.", + "Create a blank worksheet with a title.": "Créer une feuille de travail vide avec un titre.", + "Clears all rows on an existing sheet": "Efface toutes les lignes sur une feuille existante", + "Get a row in a Google Sheet by row number": "Obtenir une ligne dans une feuille Google par numéro de ligne", + "Get next group of rows from a Google Sheet": "Récupérer le groupe suivant de lignes depuis un Google Sheet", + "Find spreadsheet(s) by name.": "Trouver des feuilles de calcul par nom.", + "Finds a worksheet(s) by title.": "Trouve une feuille de travail par titre.", + "Creates a new worksheet by copying an existing one.": "Crée une nouvelle feuille de travail en copiant une feuille existante.", + "Updates multiple rows in a specific spreadsheet.": "Met à jour plusieurs lignes dans une feuille de calcul spécifique.", + "Adds a new column to a spreadsheet.": "Ajoute une nouvelle colonne à une feuille de calcul.", + "Make a custom API call to a specific endpoint": "Passez un appel API personnalisé à un point de terminaison spécifique", + "Include Team Drive Sheets ?": "Inclure les feuilles de lecteur de l'équipe ?", + "Spreadsheet": "Feuille de calcul", + "Sheet": "Feuille", + "As String": "Comme chaîne de caractères", + "Does the first row contain headers?": "La première ligne contient-elle des en-têtes ?", + "Values": "Valeurs", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Écraser les données existantes ?", + "Avoid Duplicates?": "Éviter les doublons?", + "Duplicate Value Column": "Dupliquer la colonne de valeur", + "Row Number": "Numéro de ligne", + "The name of the column to search in": "Le nom de la colonne dans laquelle rechercher", + "Search Value": "Valeur de la recherche", + "Exact match": "Correspondance exacte", + "Starting Row": "Lancement de la ligne", + "Number of Rows": "Nombre de lignes", + "Title": "Titre de la page", + "Parent Folder": "Dossier parent", + "Headers": "En-têtes", + "Is First row Headers?": "Les en-têtes de la première ligne sont-ils ?", + "Start Row": "Lancer la ligne", + "Markdown": "Markdown", + "Memory Key": "Clé de mémoire", + "Group Size": "Taille du groupe", + "Spreadsheet Name": "Nom de la feuille de calcul", + "Exact Match": "Correspondance exacte", + "Spreadsheet Containing the Worksheet to Copy": "Feuille de calcul contenant la feuille de travail à copier", + "Worksheet to Copy": "Feuille à copier", + "Spreadsheet to paste in": "Feuille de calcul à coller", + "Column Name": "Nom de la colonne", + "Column Index": "Index des colonnes", + "Method": "Méthode", + "Query Parameters": "Paramètres de requête", + "Body": "Corps", + "No Error on Failure": "Aucune erreur en cas d'échec", + "Timeout (in seconds)": "Délai d'attente (en secondes)", + "Determines if sheets from Team Drives should be included in the results.": "Détermine si les feuilles des disques d'équipe doivent être incluses dans les résultats.", + "The ID of the spreadsheet to use.": "L'ID de la feuille de calcul à utiliser.", + "The ID of the sheet to use.": "L'ID de la feuille à utiliser.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Les valeurs ajoutées qui sont des dates et des formules seront saisies et n'ont aucun effet", + "If the first row is headers": "Si la première ligne est des en-têtes", + "The values to insert": "Les valeurs à insérer", + "Select the format of the input values to be inserted into the sheet.": "Sélectionnez le format des valeurs d'entrée à insérer dans la feuille.", + "The values to insert.": "Les valeurs à insérer.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Activez cette option pour remplacer toutes les données existantes dans la feuille par de nouvelles données de votre entrée. Cela effacera toutes les lignes supplémentaires au-delà de la plage mise à jour.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Activer cette option pour vérifier les valeurs en double avant d'insérer des données dans la feuille. Seules les lignes uniques seront ajoutées en fonction de la colonne sélectionnée.", + "The column to check for duplicate values.": "La colonne à vérifier pour les valeurs en double.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Les valeurs ajoutées qui sont des dates et des formules seront entrées en tant que chaînes de caractères et n'ont aucun effet", + "The row number to remove": "Le numéro de ligne à supprimer", + "The row number to update": "Le numéro de ligne à mettre à jour", + "The value to search for in the specified column. If left empty, all rows will be returned.": "La valeur à rechercher dans la colonne spécifiée. Si laissé vide, toutes les lignes seront retournées.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Choisir les lignes avec une correspondance exacte ou choisir les lignes qui contiennent la valeur de recherche", + "The row number to start searching from": "Le numéro de ligne à partir duquel commencer la recherche", + "The number of rows to return ( the default is 1 if not specified )": "Le nombre de lignes à retourner ( la valeur par défaut est 1 si non spécifié)", + "The title of the new spreadsheet.": "Le titre de la nouvelle feuille de calcul.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "Le dossier pour créer la feuille de travail in.Par défaut, la nouvelle feuille de travail est créée dans le dossier racine du lecteur.", + "The title of the new worksheet.": "Le titre de la nouvelle feuille de travail.", + "The row number to get from the sheet": "Le numéro de ligne à récupérer de la feuille", + "Which row to start from?": "À partir de quelle ligne il faut commencer ?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "La clé utilisée pour stocker le numéro de ligne actuel en mémoire", + "The number of rows to get": "Le nombre de lignes à récupérer", + "The name of the spreadsheet(s) to find.": "Le nom de la feuille de calcul à trouver.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "Si vrai, retourne uniquement des feuilles de calcul qui correspondent exactement au nom. Si faux, retourne des feuilles de calcul qui contiennent le nom.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "Si vrai, retourne uniquement les feuilles de travail qui correspondent exactement au nom. Si faux, retourne les feuilles de travail qui contiennent le nom.", + "The values to update.": "Les valeurs à mettre à jour.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "L'index des colonnes commence à partir de 1. Par exemple, si vous voulez ajouter une colonne à la troisième colonne, entrez 3. f l'entrée est inférieure à 1 la colonne sera ajoutée après la dernière colonne courante.", + "Authorization headers are injected automatically from your connection.": "Les en-têtes d'autorisation sont injectés automatiquement à partir de votre connexion.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Noms de colonnes", + "GET": "OBTENIR", + "POST": "POSTER", + "PATCH": "PATCH", + "PUT": "EFFACER", + "DELETE": "SUPPRIMER", + "HEAD": "TÊTE", + "New Row Added": "Nouvelle ligne ajoutée", + "New or Updated Row": "Ligne nouvelle ou mise à jour", + "New Spreadsheet": "Nouvelle feuille de calcul", + "New Worksheet": "Nouvelle fiche", + "Triggers when a new row is added to bottom of a spreadsheet.": "Déclenche quand une nouvelle ligne est ajoutée en bas d'une feuille de calcul.", + "Triggers when a new row is added or modified in a spreadsheet.": "Déclenche lorsqu'une nouvelle ligne est ajoutée ou modifiée dans une feuille de calcul.", + "Triggers when a new spreadsheet is created.": "Déclenche quand une nouvelle feuille de calcul est créée.", + "Triggers when a worksheet is created in a spreadsheet.": "Déclenche quand une feuille de calcul est créée dans une feuille de calcul.", + "Trigger Column": "Colonne de déclenchement", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Veuillez noter qu'il peut y avoir un délai de 3 minutes pour que le déclenchement soit déclenché en raison d'un délai de la part de Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Déclencher sur les modifications aux cellules de cette colonne seulement. sélectionnez **Toutes les colonnes** si vous voulez que le flux se déclenche en cas de changement sur n'importe quelle cellule de la ligne." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/hi.json b/packages/pieces/community/google-sheets/src/i18n/hi.json new file mode 100644 index 0000000..ca7f145 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/hi.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google Sheets", + "Create, edit, and collaborate on spreadsheets online": "Create, edit, and collaborate on spreadsheets online", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Insert Multiple Rows", + "Delete Row": "Delete Row", + "Update Row": "Update Row", + "Find Rows": "Find Rows", + "Create Spreadsheet": "Create Spreadsheet", + "Create Worksheet": "Create Worksheet", + "Clear Sheet": "Clear Sheet", + "Get Row": "Get Row", + "Get next row(s)": "Get next row(s)", + "Find Spreadsheet(s)": "Find Spreadsheet(s)", + "Find Worksheet(s)": "Find Worksheet(s)", + "Copy Worksheet": "Copy Worksheet", + "Update Multiple Rows": "Update Multiple Rows", + "Create Spreadsheet Column": "Create Spreadsheet Column", + "Custom API Call": "Custom API Call", + "Append a row of values to an existing sheet": "Append a row of values to an existing sheet", + "Add one or more new rows in a specific spreadsheet.": "Add one or more new rows in a specific spreadsheet.", + "Delete a row on an existing sheet you have access to": "Delete a row on an existing sheet you have access to", + "Overwrite values in an existing row": "Overwrite values in an existing row", + "Find or get rows in a Google Sheet by column name and search value": "Find or get rows in a Google Sheet by column name and search value", + "Creates a blank spreadsheet.": "Creates a blank spreadsheet.", + "Create a blank worksheet with a title.": "Create a blank worksheet with a title.", + "Clears all rows on an existing sheet": "Clears all rows on an existing sheet", + "Get a row in a Google Sheet by row number": "Get a row in a Google Sheet by row number", + "Get next group of rows from a Google Sheet": "Get next group of rows from a Google Sheet", + "Find spreadsheet(s) by name.": "Find spreadsheet(s) by name.", + "Finds a worksheet(s) by title.": "Finds a worksheet(s) by title.", + "Creates a new worksheet by copying an existing one.": "Creates a new worksheet by copying an existing one.", + "Updates multiple rows in a specific spreadsheet.": "Updates multiple rows in a specific spreadsheet.", + "Adds a new column to a spreadsheet.": "Adds a new column to a spreadsheet.", + "Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint", + "Include Team Drive Sheets ?": "Include Team Drive Sheets ?", + "Spreadsheet": "Spreadsheet", + "Sheet": "Sheet", + "As String": "As String", + "Does the first row contain headers?": "Does the first row contain headers?", + "Values": "Values", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Overwrite Existing Data?", + "Avoid Duplicates?": "Avoid Duplicates?", + "Duplicate Value Column": "Duplicate Value Column", + "Row Number": "Row Number", + "The name of the column to search in": "The name of the column to search in", + "Search Value": "Search Value", + "Exact match": "Exact match", + "Starting Row": "Starting Row", + "Number of Rows": "Number of Rows", + "Title": "Title", + "Parent Folder": "Parent Folder", + "Headers": "Headers", + "Is First row Headers?": "Is First row Headers?", + "Start Row": "Start Row", + "Markdown": "Markdown", + "Memory Key": "Memory Key", + "Group Size": "Group Size", + "Spreadsheet Name": "Spreadsheet Name", + "Exact Match": "Exact Match", + "Spreadsheet Containing the Worksheet to Copy": "Spreadsheet Containing the Worksheet to Copy", + "Worksheet to Copy": "Worksheet to Copy", + "Spreadsheet to paste in": "Spreadsheet to paste in", + "Column Name": "Column Name", + "Column Index": "Column Index", + "Method": "Method", + "Query Parameters": "Query Parameters", + "Body": "Body", + "No Error on Failure": "No Error on Failure", + "Timeout (in seconds)": "Timeout (in seconds)", + "Determines if sheets from Team Drives should be included in the results.": "Determines if sheets from Team Drives should be included in the results.", + "The ID of the spreadsheet to use.": "The ID of the spreadsheet to use.", + "The ID of the sheet to use.": "The ID of the sheet to use.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Inserted values that are dates and formulas will be entered strings and have no effect", + "If the first row is headers": "If the first row is headers", + "The values to insert": "The values to insert", + "Select the format of the input values to be inserted into the sheet.": "Select the format of the input values to be inserted into the sheet.", + "The values to insert.": "The values to insert.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.", + "The column to check for duplicate values.": "The column to check for duplicate values.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Inserted values that are dates and formulas will be entered as strings and have no effect", + "The row number to remove": "The row number to remove", + "The row number to update": "The row number to update", + "The value to search for in the specified column. If left empty, all rows will be returned.": "The value to search for in the specified column. If left empty, all rows will be returned.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Whether to choose the rows with exact match or choose the rows that contain the search value", + "The row number to start searching from": "The row number to start searching from", + "The number of rows to return ( the default is 1 if not specified )": "The number of rows to return ( the default is 1 if not specified )", + "The title of the new spreadsheet.": "The title of the new spreadsheet.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.", + "The title of the new worksheet.": "The title of the new worksheet.", + "The row number to get from the sheet": "The row number to get from the sheet", + "Which row to start from?": "Which row to start from?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "The key used to store the current row number in memory", + "The number of rows to get": "The number of rows to get", + "The name of the spreadsheet(s) to find.": "The name of the spreadsheet(s) to find.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.", + "The values to update.": "The values to update.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.", + "Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Column Names", + "GET": "GET", + "POST": "POST", + "PATCH": "PATCH", + "PUT": "PUT", + "DELETE": "DELETE", + "HEAD": "HEAD", + "New Row Added": "New Row Added", + "New or Updated Row": "New or Updated Row", + "New Spreadsheet": "New Spreadsheet", + "New Worksheet": "New Worksheet", + "Triggers when a new row is added to bottom of a spreadsheet.": "Triggers when a new row is added to bottom of a spreadsheet.", + "Triggers when a new row is added or modified in a spreadsheet.": "Triggers when a new row is added or modified in a spreadsheet.", + "Triggers when a new spreadsheet is created.": "Triggers when a new spreadsheet is created.", + "Triggers when a worksheet is created in a spreadsheet.": "Triggers when a worksheet is created in a spreadsheet.", + "Trigger Column": "Trigger Column", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/hu.json b/packages/pieces/community/google-sheets/src/i18n/hu.json new file mode 100644 index 0000000..ca7f145 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/hu.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google Sheets", + "Create, edit, and collaborate on spreadsheets online": "Create, edit, and collaborate on spreadsheets online", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Insert Multiple Rows", + "Delete Row": "Delete Row", + "Update Row": "Update Row", + "Find Rows": "Find Rows", + "Create Spreadsheet": "Create Spreadsheet", + "Create Worksheet": "Create Worksheet", + "Clear Sheet": "Clear Sheet", + "Get Row": "Get Row", + "Get next row(s)": "Get next row(s)", + "Find Spreadsheet(s)": "Find Spreadsheet(s)", + "Find Worksheet(s)": "Find Worksheet(s)", + "Copy Worksheet": "Copy Worksheet", + "Update Multiple Rows": "Update Multiple Rows", + "Create Spreadsheet Column": "Create Spreadsheet Column", + "Custom API Call": "Custom API Call", + "Append a row of values to an existing sheet": "Append a row of values to an existing sheet", + "Add one or more new rows in a specific spreadsheet.": "Add one or more new rows in a specific spreadsheet.", + "Delete a row on an existing sheet you have access to": "Delete a row on an existing sheet you have access to", + "Overwrite values in an existing row": "Overwrite values in an existing row", + "Find or get rows in a Google Sheet by column name and search value": "Find or get rows in a Google Sheet by column name and search value", + "Creates a blank spreadsheet.": "Creates a blank spreadsheet.", + "Create a blank worksheet with a title.": "Create a blank worksheet with a title.", + "Clears all rows on an existing sheet": "Clears all rows on an existing sheet", + "Get a row in a Google Sheet by row number": "Get a row in a Google Sheet by row number", + "Get next group of rows from a Google Sheet": "Get next group of rows from a Google Sheet", + "Find spreadsheet(s) by name.": "Find spreadsheet(s) by name.", + "Finds a worksheet(s) by title.": "Finds a worksheet(s) by title.", + "Creates a new worksheet by copying an existing one.": "Creates a new worksheet by copying an existing one.", + "Updates multiple rows in a specific spreadsheet.": "Updates multiple rows in a specific spreadsheet.", + "Adds a new column to a spreadsheet.": "Adds a new column to a spreadsheet.", + "Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint", + "Include Team Drive Sheets ?": "Include Team Drive Sheets ?", + "Spreadsheet": "Spreadsheet", + "Sheet": "Sheet", + "As String": "As String", + "Does the first row contain headers?": "Does the first row contain headers?", + "Values": "Values", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Overwrite Existing Data?", + "Avoid Duplicates?": "Avoid Duplicates?", + "Duplicate Value Column": "Duplicate Value Column", + "Row Number": "Row Number", + "The name of the column to search in": "The name of the column to search in", + "Search Value": "Search Value", + "Exact match": "Exact match", + "Starting Row": "Starting Row", + "Number of Rows": "Number of Rows", + "Title": "Title", + "Parent Folder": "Parent Folder", + "Headers": "Headers", + "Is First row Headers?": "Is First row Headers?", + "Start Row": "Start Row", + "Markdown": "Markdown", + "Memory Key": "Memory Key", + "Group Size": "Group Size", + "Spreadsheet Name": "Spreadsheet Name", + "Exact Match": "Exact Match", + "Spreadsheet Containing the Worksheet to Copy": "Spreadsheet Containing the Worksheet to Copy", + "Worksheet to Copy": "Worksheet to Copy", + "Spreadsheet to paste in": "Spreadsheet to paste in", + "Column Name": "Column Name", + "Column Index": "Column Index", + "Method": "Method", + "Query Parameters": "Query Parameters", + "Body": "Body", + "No Error on Failure": "No Error on Failure", + "Timeout (in seconds)": "Timeout (in seconds)", + "Determines if sheets from Team Drives should be included in the results.": "Determines if sheets from Team Drives should be included in the results.", + "The ID of the spreadsheet to use.": "The ID of the spreadsheet to use.", + "The ID of the sheet to use.": "The ID of the sheet to use.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Inserted values that are dates and formulas will be entered strings and have no effect", + "If the first row is headers": "If the first row is headers", + "The values to insert": "The values to insert", + "Select the format of the input values to be inserted into the sheet.": "Select the format of the input values to be inserted into the sheet.", + "The values to insert.": "The values to insert.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.", + "The column to check for duplicate values.": "The column to check for duplicate values.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Inserted values that are dates and formulas will be entered as strings and have no effect", + "The row number to remove": "The row number to remove", + "The row number to update": "The row number to update", + "The value to search for in the specified column. If left empty, all rows will be returned.": "The value to search for in the specified column. If left empty, all rows will be returned.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Whether to choose the rows with exact match or choose the rows that contain the search value", + "The row number to start searching from": "The row number to start searching from", + "The number of rows to return ( the default is 1 if not specified )": "The number of rows to return ( the default is 1 if not specified )", + "The title of the new spreadsheet.": "The title of the new spreadsheet.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.", + "The title of the new worksheet.": "The title of the new worksheet.", + "The row number to get from the sheet": "The row number to get from the sheet", + "Which row to start from?": "Which row to start from?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "The key used to store the current row number in memory", + "The number of rows to get": "The number of rows to get", + "The name of the spreadsheet(s) to find.": "The name of the spreadsheet(s) to find.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.", + "The values to update.": "The values to update.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.", + "Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Column Names", + "GET": "GET", + "POST": "POST", + "PATCH": "PATCH", + "PUT": "PUT", + "DELETE": "DELETE", + "HEAD": "HEAD", + "New Row Added": "New Row Added", + "New or Updated Row": "New or Updated Row", + "New Spreadsheet": "New Spreadsheet", + "New Worksheet": "New Worksheet", + "Triggers when a new row is added to bottom of a spreadsheet.": "Triggers when a new row is added to bottom of a spreadsheet.", + "Triggers when a new row is added or modified in a spreadsheet.": "Triggers when a new row is added or modified in a spreadsheet.", + "Triggers when a new spreadsheet is created.": "Triggers when a new spreadsheet is created.", + "Triggers when a worksheet is created in a spreadsheet.": "Triggers when a worksheet is created in a spreadsheet.", + "Trigger Column": "Trigger Column", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/hy.json b/packages/pieces/community/google-sheets/src/i18n/hy.json new file mode 100644 index 0000000..ca7f145 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/hy.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google Sheets", + "Create, edit, and collaborate on spreadsheets online": "Create, edit, and collaborate on spreadsheets online", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Insert Multiple Rows", + "Delete Row": "Delete Row", + "Update Row": "Update Row", + "Find Rows": "Find Rows", + "Create Spreadsheet": "Create Spreadsheet", + "Create Worksheet": "Create Worksheet", + "Clear Sheet": "Clear Sheet", + "Get Row": "Get Row", + "Get next row(s)": "Get next row(s)", + "Find Spreadsheet(s)": "Find Spreadsheet(s)", + "Find Worksheet(s)": "Find Worksheet(s)", + "Copy Worksheet": "Copy Worksheet", + "Update Multiple Rows": "Update Multiple Rows", + "Create Spreadsheet Column": "Create Spreadsheet Column", + "Custom API Call": "Custom API Call", + "Append a row of values to an existing sheet": "Append a row of values to an existing sheet", + "Add one or more new rows in a specific spreadsheet.": "Add one or more new rows in a specific spreadsheet.", + "Delete a row on an existing sheet you have access to": "Delete a row on an existing sheet you have access to", + "Overwrite values in an existing row": "Overwrite values in an existing row", + "Find or get rows in a Google Sheet by column name and search value": "Find or get rows in a Google Sheet by column name and search value", + "Creates a blank spreadsheet.": "Creates a blank spreadsheet.", + "Create a blank worksheet with a title.": "Create a blank worksheet with a title.", + "Clears all rows on an existing sheet": "Clears all rows on an existing sheet", + "Get a row in a Google Sheet by row number": "Get a row in a Google Sheet by row number", + "Get next group of rows from a Google Sheet": "Get next group of rows from a Google Sheet", + "Find spreadsheet(s) by name.": "Find spreadsheet(s) by name.", + "Finds a worksheet(s) by title.": "Finds a worksheet(s) by title.", + "Creates a new worksheet by copying an existing one.": "Creates a new worksheet by copying an existing one.", + "Updates multiple rows in a specific spreadsheet.": "Updates multiple rows in a specific spreadsheet.", + "Adds a new column to a spreadsheet.": "Adds a new column to a spreadsheet.", + "Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint", + "Include Team Drive Sheets ?": "Include Team Drive Sheets ?", + "Spreadsheet": "Spreadsheet", + "Sheet": "Sheet", + "As String": "As String", + "Does the first row contain headers?": "Does the first row contain headers?", + "Values": "Values", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Overwrite Existing Data?", + "Avoid Duplicates?": "Avoid Duplicates?", + "Duplicate Value Column": "Duplicate Value Column", + "Row Number": "Row Number", + "The name of the column to search in": "The name of the column to search in", + "Search Value": "Search Value", + "Exact match": "Exact match", + "Starting Row": "Starting Row", + "Number of Rows": "Number of Rows", + "Title": "Title", + "Parent Folder": "Parent Folder", + "Headers": "Headers", + "Is First row Headers?": "Is First row Headers?", + "Start Row": "Start Row", + "Markdown": "Markdown", + "Memory Key": "Memory Key", + "Group Size": "Group Size", + "Spreadsheet Name": "Spreadsheet Name", + "Exact Match": "Exact Match", + "Spreadsheet Containing the Worksheet to Copy": "Spreadsheet Containing the Worksheet to Copy", + "Worksheet to Copy": "Worksheet to Copy", + "Spreadsheet to paste in": "Spreadsheet to paste in", + "Column Name": "Column Name", + "Column Index": "Column Index", + "Method": "Method", + "Query Parameters": "Query Parameters", + "Body": "Body", + "No Error on Failure": "No Error on Failure", + "Timeout (in seconds)": "Timeout (in seconds)", + "Determines if sheets from Team Drives should be included in the results.": "Determines if sheets from Team Drives should be included in the results.", + "The ID of the spreadsheet to use.": "The ID of the spreadsheet to use.", + "The ID of the sheet to use.": "The ID of the sheet to use.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Inserted values that are dates and formulas will be entered strings and have no effect", + "If the first row is headers": "If the first row is headers", + "The values to insert": "The values to insert", + "Select the format of the input values to be inserted into the sheet.": "Select the format of the input values to be inserted into the sheet.", + "The values to insert.": "The values to insert.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.", + "The column to check for duplicate values.": "The column to check for duplicate values.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Inserted values that are dates and formulas will be entered as strings and have no effect", + "The row number to remove": "The row number to remove", + "The row number to update": "The row number to update", + "The value to search for in the specified column. If left empty, all rows will be returned.": "The value to search for in the specified column. If left empty, all rows will be returned.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Whether to choose the rows with exact match or choose the rows that contain the search value", + "The row number to start searching from": "The row number to start searching from", + "The number of rows to return ( the default is 1 if not specified )": "The number of rows to return ( the default is 1 if not specified )", + "The title of the new spreadsheet.": "The title of the new spreadsheet.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.", + "The title of the new worksheet.": "The title of the new worksheet.", + "The row number to get from the sheet": "The row number to get from the sheet", + "Which row to start from?": "Which row to start from?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "The key used to store the current row number in memory", + "The number of rows to get": "The number of rows to get", + "The name of the spreadsheet(s) to find.": "The name of the spreadsheet(s) to find.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.", + "The values to update.": "The values to update.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.", + "Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Column Names", + "GET": "GET", + "POST": "POST", + "PATCH": "PATCH", + "PUT": "PUT", + "DELETE": "DELETE", + "HEAD": "HEAD", + "New Row Added": "New Row Added", + "New or Updated Row": "New or Updated Row", + "New Spreadsheet": "New Spreadsheet", + "New Worksheet": "New Worksheet", + "Triggers when a new row is added to bottom of a spreadsheet.": "Triggers when a new row is added to bottom of a spreadsheet.", + "Triggers when a new row is added or modified in a spreadsheet.": "Triggers when a new row is added or modified in a spreadsheet.", + "Triggers when a new spreadsheet is created.": "Triggers when a new spreadsheet is created.", + "Triggers when a worksheet is created in a spreadsheet.": "Triggers when a worksheet is created in a spreadsheet.", + "Trigger Column": "Trigger Column", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/id.json b/packages/pieces/community/google-sheets/src/i18n/id.json new file mode 100644 index 0000000..ca7f145 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/id.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google Sheets", + "Create, edit, and collaborate on spreadsheets online": "Create, edit, and collaborate on spreadsheets online", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Insert Multiple Rows", + "Delete Row": "Delete Row", + "Update Row": "Update Row", + "Find Rows": "Find Rows", + "Create Spreadsheet": "Create Spreadsheet", + "Create Worksheet": "Create Worksheet", + "Clear Sheet": "Clear Sheet", + "Get Row": "Get Row", + "Get next row(s)": "Get next row(s)", + "Find Spreadsheet(s)": "Find Spreadsheet(s)", + "Find Worksheet(s)": "Find Worksheet(s)", + "Copy Worksheet": "Copy Worksheet", + "Update Multiple Rows": "Update Multiple Rows", + "Create Spreadsheet Column": "Create Spreadsheet Column", + "Custom API Call": "Custom API Call", + "Append a row of values to an existing sheet": "Append a row of values to an existing sheet", + "Add one or more new rows in a specific spreadsheet.": "Add one or more new rows in a specific spreadsheet.", + "Delete a row on an existing sheet you have access to": "Delete a row on an existing sheet you have access to", + "Overwrite values in an existing row": "Overwrite values in an existing row", + "Find or get rows in a Google Sheet by column name and search value": "Find or get rows in a Google Sheet by column name and search value", + "Creates a blank spreadsheet.": "Creates a blank spreadsheet.", + "Create a blank worksheet with a title.": "Create a blank worksheet with a title.", + "Clears all rows on an existing sheet": "Clears all rows on an existing sheet", + "Get a row in a Google Sheet by row number": "Get a row in a Google Sheet by row number", + "Get next group of rows from a Google Sheet": "Get next group of rows from a Google Sheet", + "Find spreadsheet(s) by name.": "Find spreadsheet(s) by name.", + "Finds a worksheet(s) by title.": "Finds a worksheet(s) by title.", + "Creates a new worksheet by copying an existing one.": "Creates a new worksheet by copying an existing one.", + "Updates multiple rows in a specific spreadsheet.": "Updates multiple rows in a specific spreadsheet.", + "Adds a new column to a spreadsheet.": "Adds a new column to a spreadsheet.", + "Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint", + "Include Team Drive Sheets ?": "Include Team Drive Sheets ?", + "Spreadsheet": "Spreadsheet", + "Sheet": "Sheet", + "As String": "As String", + "Does the first row contain headers?": "Does the first row contain headers?", + "Values": "Values", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Overwrite Existing Data?", + "Avoid Duplicates?": "Avoid Duplicates?", + "Duplicate Value Column": "Duplicate Value Column", + "Row Number": "Row Number", + "The name of the column to search in": "The name of the column to search in", + "Search Value": "Search Value", + "Exact match": "Exact match", + "Starting Row": "Starting Row", + "Number of Rows": "Number of Rows", + "Title": "Title", + "Parent Folder": "Parent Folder", + "Headers": "Headers", + "Is First row Headers?": "Is First row Headers?", + "Start Row": "Start Row", + "Markdown": "Markdown", + "Memory Key": "Memory Key", + "Group Size": "Group Size", + "Spreadsheet Name": "Spreadsheet Name", + "Exact Match": "Exact Match", + "Spreadsheet Containing the Worksheet to Copy": "Spreadsheet Containing the Worksheet to Copy", + "Worksheet to Copy": "Worksheet to Copy", + "Spreadsheet to paste in": "Spreadsheet to paste in", + "Column Name": "Column Name", + "Column Index": "Column Index", + "Method": "Method", + "Query Parameters": "Query Parameters", + "Body": "Body", + "No Error on Failure": "No Error on Failure", + "Timeout (in seconds)": "Timeout (in seconds)", + "Determines if sheets from Team Drives should be included in the results.": "Determines if sheets from Team Drives should be included in the results.", + "The ID of the spreadsheet to use.": "The ID of the spreadsheet to use.", + "The ID of the sheet to use.": "The ID of the sheet to use.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Inserted values that are dates and formulas will be entered strings and have no effect", + "If the first row is headers": "If the first row is headers", + "The values to insert": "The values to insert", + "Select the format of the input values to be inserted into the sheet.": "Select the format of the input values to be inserted into the sheet.", + "The values to insert.": "The values to insert.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.", + "The column to check for duplicate values.": "The column to check for duplicate values.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Inserted values that are dates and formulas will be entered as strings and have no effect", + "The row number to remove": "The row number to remove", + "The row number to update": "The row number to update", + "The value to search for in the specified column. If left empty, all rows will be returned.": "The value to search for in the specified column. If left empty, all rows will be returned.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Whether to choose the rows with exact match or choose the rows that contain the search value", + "The row number to start searching from": "The row number to start searching from", + "The number of rows to return ( the default is 1 if not specified )": "The number of rows to return ( the default is 1 if not specified )", + "The title of the new spreadsheet.": "The title of the new spreadsheet.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.", + "The title of the new worksheet.": "The title of the new worksheet.", + "The row number to get from the sheet": "The row number to get from the sheet", + "Which row to start from?": "Which row to start from?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "The key used to store the current row number in memory", + "The number of rows to get": "The number of rows to get", + "The name of the spreadsheet(s) to find.": "The name of the spreadsheet(s) to find.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.", + "The values to update.": "The values to update.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.", + "Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Column Names", + "GET": "GET", + "POST": "POST", + "PATCH": "PATCH", + "PUT": "PUT", + "DELETE": "DELETE", + "HEAD": "HEAD", + "New Row Added": "New Row Added", + "New or Updated Row": "New or Updated Row", + "New Spreadsheet": "New Spreadsheet", + "New Worksheet": "New Worksheet", + "Triggers when a new row is added to bottom of a spreadsheet.": "Triggers when a new row is added to bottom of a spreadsheet.", + "Triggers when a new row is added or modified in a spreadsheet.": "Triggers when a new row is added or modified in a spreadsheet.", + "Triggers when a new spreadsheet is created.": "Triggers when a new spreadsheet is created.", + "Triggers when a worksheet is created in a spreadsheet.": "Triggers when a worksheet is created in a spreadsheet.", + "Trigger Column": "Trigger Column", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/it.json b/packages/pieces/community/google-sheets/src/i18n/it.json new file mode 100644 index 0000000..ca7f145 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/it.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google Sheets", + "Create, edit, and collaborate on spreadsheets online": "Create, edit, and collaborate on spreadsheets online", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Insert Multiple Rows", + "Delete Row": "Delete Row", + "Update Row": "Update Row", + "Find Rows": "Find Rows", + "Create Spreadsheet": "Create Spreadsheet", + "Create Worksheet": "Create Worksheet", + "Clear Sheet": "Clear Sheet", + "Get Row": "Get Row", + "Get next row(s)": "Get next row(s)", + "Find Spreadsheet(s)": "Find Spreadsheet(s)", + "Find Worksheet(s)": "Find Worksheet(s)", + "Copy Worksheet": "Copy Worksheet", + "Update Multiple Rows": "Update Multiple Rows", + "Create Spreadsheet Column": "Create Spreadsheet Column", + "Custom API Call": "Custom API Call", + "Append a row of values to an existing sheet": "Append a row of values to an existing sheet", + "Add one or more new rows in a specific spreadsheet.": "Add one or more new rows in a specific spreadsheet.", + "Delete a row on an existing sheet you have access to": "Delete a row on an existing sheet you have access to", + "Overwrite values in an existing row": "Overwrite values in an existing row", + "Find or get rows in a Google Sheet by column name and search value": "Find or get rows in a Google Sheet by column name and search value", + "Creates a blank spreadsheet.": "Creates a blank spreadsheet.", + "Create a blank worksheet with a title.": "Create a blank worksheet with a title.", + "Clears all rows on an existing sheet": "Clears all rows on an existing sheet", + "Get a row in a Google Sheet by row number": "Get a row in a Google Sheet by row number", + "Get next group of rows from a Google Sheet": "Get next group of rows from a Google Sheet", + "Find spreadsheet(s) by name.": "Find spreadsheet(s) by name.", + "Finds a worksheet(s) by title.": "Finds a worksheet(s) by title.", + "Creates a new worksheet by copying an existing one.": "Creates a new worksheet by copying an existing one.", + "Updates multiple rows in a specific spreadsheet.": "Updates multiple rows in a specific spreadsheet.", + "Adds a new column to a spreadsheet.": "Adds a new column to a spreadsheet.", + "Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint", + "Include Team Drive Sheets ?": "Include Team Drive Sheets ?", + "Spreadsheet": "Spreadsheet", + "Sheet": "Sheet", + "As String": "As String", + "Does the first row contain headers?": "Does the first row contain headers?", + "Values": "Values", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Overwrite Existing Data?", + "Avoid Duplicates?": "Avoid Duplicates?", + "Duplicate Value Column": "Duplicate Value Column", + "Row Number": "Row Number", + "The name of the column to search in": "The name of the column to search in", + "Search Value": "Search Value", + "Exact match": "Exact match", + "Starting Row": "Starting Row", + "Number of Rows": "Number of Rows", + "Title": "Title", + "Parent Folder": "Parent Folder", + "Headers": "Headers", + "Is First row Headers?": "Is First row Headers?", + "Start Row": "Start Row", + "Markdown": "Markdown", + "Memory Key": "Memory Key", + "Group Size": "Group Size", + "Spreadsheet Name": "Spreadsheet Name", + "Exact Match": "Exact Match", + "Spreadsheet Containing the Worksheet to Copy": "Spreadsheet Containing the Worksheet to Copy", + "Worksheet to Copy": "Worksheet to Copy", + "Spreadsheet to paste in": "Spreadsheet to paste in", + "Column Name": "Column Name", + "Column Index": "Column Index", + "Method": "Method", + "Query Parameters": "Query Parameters", + "Body": "Body", + "No Error on Failure": "No Error on Failure", + "Timeout (in seconds)": "Timeout (in seconds)", + "Determines if sheets from Team Drives should be included in the results.": "Determines if sheets from Team Drives should be included in the results.", + "The ID of the spreadsheet to use.": "The ID of the spreadsheet to use.", + "The ID of the sheet to use.": "The ID of the sheet to use.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Inserted values that are dates and formulas will be entered strings and have no effect", + "If the first row is headers": "If the first row is headers", + "The values to insert": "The values to insert", + "Select the format of the input values to be inserted into the sheet.": "Select the format of the input values to be inserted into the sheet.", + "The values to insert.": "The values to insert.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.", + "The column to check for duplicate values.": "The column to check for duplicate values.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Inserted values that are dates and formulas will be entered as strings and have no effect", + "The row number to remove": "The row number to remove", + "The row number to update": "The row number to update", + "The value to search for in the specified column. If left empty, all rows will be returned.": "The value to search for in the specified column. If left empty, all rows will be returned.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Whether to choose the rows with exact match or choose the rows that contain the search value", + "The row number to start searching from": "The row number to start searching from", + "The number of rows to return ( the default is 1 if not specified )": "The number of rows to return ( the default is 1 if not specified )", + "The title of the new spreadsheet.": "The title of the new spreadsheet.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.", + "The title of the new worksheet.": "The title of the new worksheet.", + "The row number to get from the sheet": "The row number to get from the sheet", + "Which row to start from?": "Which row to start from?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "The key used to store the current row number in memory", + "The number of rows to get": "The number of rows to get", + "The name of the spreadsheet(s) to find.": "The name of the spreadsheet(s) to find.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.", + "The values to update.": "The values to update.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.", + "Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Column Names", + "GET": "GET", + "POST": "POST", + "PATCH": "PATCH", + "PUT": "PUT", + "DELETE": "DELETE", + "HEAD": "HEAD", + "New Row Added": "New Row Added", + "New or Updated Row": "New or Updated Row", + "New Spreadsheet": "New Spreadsheet", + "New Worksheet": "New Worksheet", + "Triggers when a new row is added to bottom of a spreadsheet.": "Triggers when a new row is added to bottom of a spreadsheet.", + "Triggers when a new row is added or modified in a spreadsheet.": "Triggers when a new row is added or modified in a spreadsheet.", + "Triggers when a new spreadsheet is created.": "Triggers when a new spreadsheet is created.", + "Triggers when a worksheet is created in a spreadsheet.": "Triggers when a worksheet is created in a spreadsheet.", + "Trigger Column": "Trigger Column", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/ja.json b/packages/pieces/community/google-sheets/src/i18n/ja.json new file mode 100644 index 0000000..65c385c --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/ja.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google シート", + "Create, edit, and collaborate on spreadsheets online": "スプレッドシートをオンラインで作成、編集、共同編集します", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "複数の行を挿入", + "Delete Row": "行を削除", + "Update Row": "行を更新", + "Find Rows": "行を検索", + "Create Spreadsheet": "スプレッドシートを作成", + "Create Worksheet": "ワークシートを作成", + "Clear Sheet": "シートをクリア", + "Get Row": "行を取得", + "Get next row(s)": "次の行を取得", + "Find Spreadsheet(s)": "スプレッドシートを検索", + "Find Worksheet(s)": "ワークシートを検索", + "Copy Worksheet": "ワークシートをコピー", + "Update Multiple Rows": "複数の行を更新", + "Create Spreadsheet Column": "表計算ドキュメント列を作成", + "Custom API Call": "カスタムAPI通話", + "Append a row of values to an existing sheet": "既存のシートに値の行を追加", + "Add one or more new rows in a specific spreadsheet.": "特定のスプレッドシートに1つ以上の新しい行を追加します。", + "Delete a row on an existing sheet you have access to": "アクセスできる既存のシートの行を削除する", + "Overwrite values in an existing row": "既存の行の値を上書き", + "Find or get rows in a Google Sheet by column name and search value": "Google シート内の行を列名と検索値で検索または取得", + "Creates a blank spreadsheet.": "空白のスプレッドシートを作成します。", + "Create a blank worksheet with a title.": "タイトルの空白のワークシートを作成します。", + "Clears all rows on an existing sheet": "既存のシートのすべての行を消去します", + "Get a row in a Google Sheet by row number": "行番号でGoogleシートの行を取得する", + "Get next group of rows from a Google Sheet": "Google シートから次の行のグループを取得する", + "Find spreadsheet(s) by name.": "名前で表計算ドキュメントを検索します。", + "Finds a worksheet(s) by title.": "タイトルでワークシートを検索します。", + "Creates a new worksheet by copying an existing one.": "既存のワークシートをコピーして新しいワークシートを作成します。", + "Updates multiple rows in a specific spreadsheet.": "特定の表計算ドキュメント内の複数行を更新します。", + "Adds a new column to a spreadsheet.": "スプレッドシートに新しい列を追加します。", + "Make a custom API call to a specific endpoint": "特定のエンドポイントへのカスタム API コールを実行します。", + "Include Team Drive Sheets ?": "チームドライブシートを含めますか?", + "Spreadsheet": "表計算ドキュメント", + "Sheet": "シート", + "As String": "文字列として", + "Does the first row contain headers?": "最初の行にはヘッダーが含まれていますか?", + "Values": "値", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "既存のデータを上書きしますか?", + "Avoid Duplicates?": "重複を避けますか?", + "Duplicate Value Column": "重複した値の列", + "Row Number": "行番号", + "The name of the column to search in": "検索する列の名前", + "Search Value": "検索値", + "Exact match": "完全一致", + "Starting Row": "開始行", + "Number of Rows": "行数", + "Title": "タイトル", + "Parent Folder": "親フォルダ", + "Headers": "ヘッダー", + "Is First row Headers?": "最初の行のヘッダーはありますか?", + "Start Row": "行を開始する", + "Markdown": "Markdown", + "Memory Key": "メモリーキー", + "Group Size": "グループサイズ", + "Spreadsheet Name": "スプレッドシート名", + "Exact Match": "完全一致", + "Spreadsheet Containing the Worksheet to Copy": "コピーするワークシートを含むスプレッドシート", + "Worksheet to Copy": "コピーするワークシート", + "Spreadsheet to paste in": "スプレッドシートを貼り付け", + "Column Name": "列名", + "Column Index": "列のインデックス", + "Method": "方法", + "Query Parameters": "クエリパラメータ", + "Body": "本文", + "No Error on Failure": "失敗時にエラーはありません", + "Timeout (in seconds)": "タイムアウト(秒)", + "Determines if sheets from Team Drives should be included in the results.": "Team Drives のシートを結果に含めるかどうかを指定します。", + "The ID of the spreadsheet to use.": "使用するスプレッドシートのID。", + "The ID of the sheet to use.": "使用するシートのID。", + "Inserted values that are dates and formulas will be entered strings and have no effect": "日付と数式に挿入された値は文字列として入力され、効果はありません", + "If the first row is headers": "最初の行がヘッダーの場合", + "The values to insert": "挿入する値", + "Select the format of the input values to be inserted into the sheet.": "シートに挿入する入力値の書式を選択します。", + "The values to insert.": "挿入する値", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "このオプションを有効にすると、シート内のすべての既存のデータを入力から新しいデータに置き換えることができます。 これにより、更新範囲を超える追加の行がクリアされます。", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "このオプションを有効にすると、データをシートに挿入する前に重複する値をチェックできます。選択した列に基づいて一意の行のみが追加されます。", + "The column to check for duplicate values.": "重複値をチェックする列。", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "日付と数式に挿入された値は文字列として入力され、効果はありません", + "The row number to remove": "削除する行番号", + "The row number to update": "更新する行番号", + "The value to search for in the specified column. If left empty, all rows will be returned.": "指定された列の検索値。空のままにすると、すべての行が返されます。", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "完全に一致する行を選択するか、検索値を含む行を選択します。", + "The row number to start searching from": "検索を開始する行番号", + "The number of rows to return ( the default is 1 if not specified )": "返す行数(指定されていない場合はデフォルトは1)", + "The title of the new spreadsheet.": "新しい表計算ドキュメントのタイトル", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "ワークシートを作成するフォルダデフォルトでは、新しいワークシートはドライブのルートフォルダに作成されます。", + "The title of the new worksheet.": "新しいワークシートのタイトル", + "The row number to get from the sheet": "シートから取得する行番号", + "Which row to start from?": "どの行から始めますか?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "現在の行番号をメモリに保存するために使用されるキー", + "The number of rows to get": "取得する行数", + "The name of the spreadsheet(s) to find.": "検索する表計算ドキュメントの名前", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "true の場合、名前に完全に一致するスプレッドシートのみが返されます。false の場合、名前を含むスプレッドシートを返します。", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "true の場合、名前に完全に一致するワークシートのみを返します。false の場合は、名前を含むワークシートを返します。", + "The values to update.": "更新する値", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "列のインデックスは1から始まります。たとえば、3番目の列に列を追加する場合は、3と入力します。 fの入力は1より小さい場合、列は現在の列の後に追加されます。", + "Authorization headers are injected automatically from your connection.": "認証ヘッダは接続から自動的に注入されます。", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "列名", + "GET": "取得", + "POST": "POST", + "PATCH": "PATCH", + "PUT": "PUT", + "DELETE": "削除", + "HEAD": "頭", + "New Row Added": "新しい行を追加しました", + "New or Updated Row": "新規または更新行", + "New Spreadsheet": "新規表計算ドキュメント", + "New Worksheet": "新しいワークシート", + "Triggers when a new row is added to bottom of a spreadsheet.": "スプレッドシートの下部に新しい行が追加されたときにトリガーされます。", + "Triggers when a new row is added or modified in a spreadsheet.": "スプレッドシートに新しい行が追加または変更されたときにトリガーされます。", + "Triggers when a new spreadsheet is created.": "新しいスプレッドシートが作成されたときにトリガーします。", + "Triggers when a worksheet is created in a spreadsheet.": "ワークシートがスプレッドシートに作成されたときにトリガーされます。", + "Trigger Column": "トリガー列", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Googleからの遅延により、トリガーが発生するまでに最大3分間の遅延が発生する場合がありますのでご注意ください。", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "この列のセルの変更のみをトリガーします。 行内の任意のセルへの変更をトリガーするフローが必要な場合は、**All Columns** を選択します。" +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/ko.json b/packages/pieces/community/google-sheets/src/i18n/ko.json new file mode 100644 index 0000000..ca7f145 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/ko.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google Sheets", + "Create, edit, and collaborate on spreadsheets online": "Create, edit, and collaborate on spreadsheets online", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Insert Multiple Rows", + "Delete Row": "Delete Row", + "Update Row": "Update Row", + "Find Rows": "Find Rows", + "Create Spreadsheet": "Create Spreadsheet", + "Create Worksheet": "Create Worksheet", + "Clear Sheet": "Clear Sheet", + "Get Row": "Get Row", + "Get next row(s)": "Get next row(s)", + "Find Spreadsheet(s)": "Find Spreadsheet(s)", + "Find Worksheet(s)": "Find Worksheet(s)", + "Copy Worksheet": "Copy Worksheet", + "Update Multiple Rows": "Update Multiple Rows", + "Create Spreadsheet Column": "Create Spreadsheet Column", + "Custom API Call": "Custom API Call", + "Append a row of values to an existing sheet": "Append a row of values to an existing sheet", + "Add one or more new rows in a specific spreadsheet.": "Add one or more new rows in a specific spreadsheet.", + "Delete a row on an existing sheet you have access to": "Delete a row on an existing sheet you have access to", + "Overwrite values in an existing row": "Overwrite values in an existing row", + "Find or get rows in a Google Sheet by column name and search value": "Find or get rows in a Google Sheet by column name and search value", + "Creates a blank spreadsheet.": "Creates a blank spreadsheet.", + "Create a blank worksheet with a title.": "Create a blank worksheet with a title.", + "Clears all rows on an existing sheet": "Clears all rows on an existing sheet", + "Get a row in a Google Sheet by row number": "Get a row in a Google Sheet by row number", + "Get next group of rows from a Google Sheet": "Get next group of rows from a Google Sheet", + "Find spreadsheet(s) by name.": "Find spreadsheet(s) by name.", + "Finds a worksheet(s) by title.": "Finds a worksheet(s) by title.", + "Creates a new worksheet by copying an existing one.": "Creates a new worksheet by copying an existing one.", + "Updates multiple rows in a specific spreadsheet.": "Updates multiple rows in a specific spreadsheet.", + "Adds a new column to a spreadsheet.": "Adds a new column to a spreadsheet.", + "Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint", + "Include Team Drive Sheets ?": "Include Team Drive Sheets ?", + "Spreadsheet": "Spreadsheet", + "Sheet": "Sheet", + "As String": "As String", + "Does the first row contain headers?": "Does the first row contain headers?", + "Values": "Values", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Overwrite Existing Data?", + "Avoid Duplicates?": "Avoid Duplicates?", + "Duplicate Value Column": "Duplicate Value Column", + "Row Number": "Row Number", + "The name of the column to search in": "The name of the column to search in", + "Search Value": "Search Value", + "Exact match": "Exact match", + "Starting Row": "Starting Row", + "Number of Rows": "Number of Rows", + "Title": "Title", + "Parent Folder": "Parent Folder", + "Headers": "Headers", + "Is First row Headers?": "Is First row Headers?", + "Start Row": "Start Row", + "Markdown": "Markdown", + "Memory Key": "Memory Key", + "Group Size": "Group Size", + "Spreadsheet Name": "Spreadsheet Name", + "Exact Match": "Exact Match", + "Spreadsheet Containing the Worksheet to Copy": "Spreadsheet Containing the Worksheet to Copy", + "Worksheet to Copy": "Worksheet to Copy", + "Spreadsheet to paste in": "Spreadsheet to paste in", + "Column Name": "Column Name", + "Column Index": "Column Index", + "Method": "Method", + "Query Parameters": "Query Parameters", + "Body": "Body", + "No Error on Failure": "No Error on Failure", + "Timeout (in seconds)": "Timeout (in seconds)", + "Determines if sheets from Team Drives should be included in the results.": "Determines if sheets from Team Drives should be included in the results.", + "The ID of the spreadsheet to use.": "The ID of the spreadsheet to use.", + "The ID of the sheet to use.": "The ID of the sheet to use.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Inserted values that are dates and formulas will be entered strings and have no effect", + "If the first row is headers": "If the first row is headers", + "The values to insert": "The values to insert", + "Select the format of the input values to be inserted into the sheet.": "Select the format of the input values to be inserted into the sheet.", + "The values to insert.": "The values to insert.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.", + "The column to check for duplicate values.": "The column to check for duplicate values.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Inserted values that are dates and formulas will be entered as strings and have no effect", + "The row number to remove": "The row number to remove", + "The row number to update": "The row number to update", + "The value to search for in the specified column. If left empty, all rows will be returned.": "The value to search for in the specified column. If left empty, all rows will be returned.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Whether to choose the rows with exact match or choose the rows that contain the search value", + "The row number to start searching from": "The row number to start searching from", + "The number of rows to return ( the default is 1 if not specified )": "The number of rows to return ( the default is 1 if not specified )", + "The title of the new spreadsheet.": "The title of the new spreadsheet.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.", + "The title of the new worksheet.": "The title of the new worksheet.", + "The row number to get from the sheet": "The row number to get from the sheet", + "Which row to start from?": "Which row to start from?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "The key used to store the current row number in memory", + "The number of rows to get": "The number of rows to get", + "The name of the spreadsheet(s) to find.": "The name of the spreadsheet(s) to find.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.", + "The values to update.": "The values to update.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.", + "Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Column Names", + "GET": "GET", + "POST": "POST", + "PATCH": "PATCH", + "PUT": "PUT", + "DELETE": "DELETE", + "HEAD": "HEAD", + "New Row Added": "New Row Added", + "New or Updated Row": "New or Updated Row", + "New Spreadsheet": "New Spreadsheet", + "New Worksheet": "New Worksheet", + "Triggers when a new row is added to bottom of a spreadsheet.": "Triggers when a new row is added to bottom of a spreadsheet.", + "Triggers when a new row is added or modified in a spreadsheet.": "Triggers when a new row is added or modified in a spreadsheet.", + "Triggers when a new spreadsheet is created.": "Triggers when a new spreadsheet is created.", + "Triggers when a worksheet is created in a spreadsheet.": "Triggers when a worksheet is created in a spreadsheet.", + "Trigger Column": "Trigger Column", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/nl.json b/packages/pieces/community/google-sheets/src/i18n/nl.json new file mode 100644 index 0000000..9d5fedd --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/nl.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google Sheets", + "Create, edit, and collaborate on spreadsheets online": "Creëer, bewerk en werk online samen op spreadsheets", + "Insert Row": "Rijen invoegen", + "Insert Multiple Rows": "Meerdere rijen invoegen", + "Delete Row": "Verwijder rij", + "Update Row": "Rij bijwerken", + "Find Rows": "Vind rijen", + "Create Spreadsheet": "Spreadsheet aanmaken", + "Create Worksheet": "Werkblad aanmaken", + "Clear Sheet": "Maak Sheet leeg", + "Get Row": "Verkrijg rij", + "Get next row(s)": "Krijg volgende rij(en)", + "Find Spreadsheet(s)": "Vind spreadsheet(s)", + "Find Worksheet(s)": "Werkblad (s) vinden", + "Copy Worksheet": "Kopieer werkblad", + "Update Multiple Rows": "Meerdere rijen bijwerken", + "Create Spreadsheet Column": "Spreadsheet kolom maken", + "Custom API Call": "Custom API Call", + "Append a row of values to an existing sheet": "Voeg een rij van waarden toe aan een bestaand blad", + "Add one or more new rows in a specific spreadsheet.": "Voeg een of meer nieuwe rijen toe op een specifiek spreadsheet.", + "Delete a row on an existing sheet you have access to": "Rij verwijderen op een bestaand blad waar u toegang tot heeft", + "Overwrite values in an existing row": "Overschrijf waarden in een bestaande rij", + "Find or get rows in a Google Sheet by column name and search value": "Zoek of haal rijen in een Google Sheet op kolomnaam en zoekwaarde", + "Creates a blank spreadsheet.": "Maakt een leeg werkblad aan.", + "Create a blank worksheet with a title.": "Maak een leeg werkblad met een titel.", + "Clears all rows on an existing sheet": "Wist alle rijen op een bestaand blad", + "Get a row in a Google Sheet by row number": "Krijg een rij achter elkaar in een Google Sheet per rij nummer", + "Get next group of rows from a Google Sheet": "Verkrijg volgende groep rijen van een Google Sheet", + "Find spreadsheet(s) by name.": "Vind spreadsheet(s) op naam.", + "Finds a worksheet(s) by title.": "Vindt een werkblad op titel.", + "Creates a new worksheet by copying an existing one.": "Maakt een nieuwe werkblad door een bestaande te kopiëren.", + "Updates multiple rows in a specific spreadsheet.": "Werkt meerdere rijen in een specifieke werkblad bij.", + "Adds a new column to a spreadsheet.": "Voegt een nieuwe kolom aan een spreadsheet toe.", + "Make a custom API call to a specific endpoint": "Maak een aangepaste API call naar een specifiek eindpunt", + "Include Team Drive Sheets ?": "Team Drive Sheets doorzoeken?", + "Spreadsheet": "Spreadsheet", + "Sheet": "Sheet", + "As String": "Als String", + "Does the first row contain headers?": "Bevat de eerste rij headers?", + "Values": "Waarden", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Bestaande gegevens overschrijven?", + "Avoid Duplicates?": "Duplicaten vermijden?", + "Duplicate Value Column": "Dupliceer waarde kolom", + "Row Number": "Rij nummer", + "The name of the column to search in": "De naam van de kolom om in te zoeken", + "Search Value": "Waarde zoeken", + "Exact match": "Exacte overeenkomst", + "Starting Row": "Start rij", + "Number of Rows": "Aantal rijen", + "Title": "Aanspreektitel", + "Parent Folder": "Bovenliggende map", + "Headers": "Kopteksten", + "Is First row Headers?": "Is de eerste rij headers?", + "Start Row": "Start rij", + "Markdown": "Markdown", + "Memory Key": "Geheugen Sleutel", + "Group Size": "Groepsgrootte", + "Spreadsheet Name": "Naam spreadsheet", + "Exact Match": "Exacte overeenkomst", + "Spreadsheet Containing the Worksheet to Copy": "Spreadsheet bevat het werkblad te kopiëren", + "Worksheet to Copy": "Werkblad om te kopiëren", + "Spreadsheet to paste in": "Spreadsheet om in te plakken", + "Column Name": "Naam kolom", + "Column Index": "Kolom Index", + "Method": "Methode", + "Query Parameters": "Query parameters", + "Body": "Lichaam", + "No Error on Failure": "Geen fout bij fout", + "Timeout (in seconds)": "Time-out (in seconden)", + "Determines if sheets from Team Drives should be included in the results.": "Bepaalt of sheets van Team Drives moeten worden opgenomen in de resultaten.", + "The ID of the spreadsheet to use.": "Het ID van het te gebruiken spreadsheet", + "The ID of the sheet to use.": "De ID van het te gebruiken plaatsel.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Ingevulde waarden die datums en formules zijn, worden tekenreeksen ingevoerd en hebben geen effect", + "If the first row is headers": "Als de eerste rij headers zijn", + "The values to insert": "De waarden om in te voegen", + "Select the format of the input values to be inserted into the sheet.": "Selecteer het formaat van de invoerwaarden die moeten worden ingevoegd in het vel.", + "The values to insert.": "De waarden om in te voegen.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Schakel deze optie in om alle bestaande gegevens in het blad te vervangen door nieuwe gegevens van uw invoer. Dit zal alle extra rijen buiten het bijgewerkte bereik verwijderen.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Schakel deze optie in om te controleren op dubbele waarden voor het invoegen van gegevens in het vel. Alleen unieke rijen worden toegevoegd op basis van de geselecteerde kolom.", + "The column to check for duplicate values.": "De kolom te controleren op dubbele waarden.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Ingevulde waarden die data en formules zijn zullen als tekenreeksen worden ingevoerd en geen effect hebben", + "The row number to remove": "Het te verwijderen rijnummer", + "The row number to update": "Het rij nummer om te updaten", + "The value to search for in the specified column. If left empty, all rows will be returned.": "De waarde waarnaar gezocht moet worden in de opgegeven kolom. Indien leeg gelaten, worden alle rijen geretourneerd.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Kies of de rijen met exacte overeenkomsten of kies de rijen die de zoekwaarde bevatten", + "The row number to start searching from": "Het rijnummer om te beginnen met zoeken van", + "The number of rows to return ( the default is 1 if not specified )": "Het aantal rijen om terug te keren (standaard is 1 indien niet opgegeven)", + "The title of the new spreadsheet.": "De titel van het nieuwe werkblad.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "De map om het werkblad in te maken. Standaard wordt het nieuwe werkblad aangemaakt in de hoofdmap van de schijf.", + "The title of the new worksheet.": "De titel van het nieuwe werkblad.", + "The row number to get from the sheet": "Het rijnummer om van de plaat te krijgen", + "Which row to start from?": "Van welke rij moet beginnen?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Geheugen sleutel wordt gebruikt om te onthouden waar de laatste rij is verwerkt en zal gebruikt worden in de volgende uitvoeringen.\n- Het kopiëren van de flow **keeps** de geheugensleutelwaarde, als u wilt beginnen met het veranderen van de geheugensleutel.\n", + "The key used to store the current row number in memory": "De sleutel die gebruikt wordt om het huidige rijnummer op te slaan in het geheugen", + "The number of rows to get": "Het aantal rijen om te krijgen", + "The name of the spreadsheet(s) to find.": "De naam van de spreadsheet(s) om te vinden.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "Als het waar is, geven alleen spreadsheets terug die precies overeenkomen met de naam. Indien niet waar, geef spreadsheets die de naam bevatten.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "Indien waar, alleen werkbladen retourneren die precies overeenkomen met de naam. Indien niet waar, stuur werkbladen dan terug die de naam bevatten.", + "The values to update.": "De waarden om bij te werken.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "De kolomindex begint vanaf 1.Bijvoorbeeld, als u een kolom wilt toevoegen aan de derde kolom, voer 3 in. f de invoer minder dan 1 is de kolom wordt toegevoegd na de laatste kolom.", + "Authorization headers are injected automatically from your connection.": "Autorisatie headers worden automatisch geïnjecteerd vanuit uw verbinding.", + "CSV": "csv", + "JSON": "JSON", + "Column Names": "Namen van kolommen", + "GET": "KRIJG", + "POST": "POSTE", + "PATCH": "BEKIJK", + "PUT": "PUT", + "DELETE": "VERWIJDEREN", + "HEAD": "HOOFD", + "New Row Added": "Nieuwe rij toegevoegd", + "New or Updated Row": "Nieuwe of bijgewerkte rij", + "New Spreadsheet": "Nieuw spreadsheet", + "New Worksheet": "Nieuw werkblad", + "Triggers when a new row is added to bottom of a spreadsheet.": "Wordt uitgevoerd wanneer een nieuwe rij onderaan het werkblad wordt toegevoegd.", + "Triggers when a new row is added or modified in a spreadsheet.": "Wordt uitgevoerd wanneer een nieuwe rij wordt toegevoegd of gewijzigd in een werkblad.", + "Triggers when a new spreadsheet is created.": "Triggert wanneer een nieuw werkblad wordt aangemaakt.", + "Triggers when a worksheet is created in a spreadsheet.": "Triggert wanneer een werkblad wordt aangemaakt in een werkblad.", + "Trigger Column": "Trigger kolom", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Houd er rekening mee dat er mogelijk een vertraging van maximaal 3 minuten is voordat de trigger wordt afgevuurd, als gevolg van een vertraging van Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Trigger op wijzigingen in cellen alleen in deze kolom. Selecteer **Alle kolommen** als u wilt dat de stroom resulteert in wijzigingen in een cel binnen de rij." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/pl.json b/packages/pieces/community/google-sheets/src/i18n/pl.json new file mode 100644 index 0000000..ca7f145 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/pl.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google Sheets", + "Create, edit, and collaborate on spreadsheets online": "Create, edit, and collaborate on spreadsheets online", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Insert Multiple Rows", + "Delete Row": "Delete Row", + "Update Row": "Update Row", + "Find Rows": "Find Rows", + "Create Spreadsheet": "Create Spreadsheet", + "Create Worksheet": "Create Worksheet", + "Clear Sheet": "Clear Sheet", + "Get Row": "Get Row", + "Get next row(s)": "Get next row(s)", + "Find Spreadsheet(s)": "Find Spreadsheet(s)", + "Find Worksheet(s)": "Find Worksheet(s)", + "Copy Worksheet": "Copy Worksheet", + "Update Multiple Rows": "Update Multiple Rows", + "Create Spreadsheet Column": "Create Spreadsheet Column", + "Custom API Call": "Custom API Call", + "Append a row of values to an existing sheet": "Append a row of values to an existing sheet", + "Add one or more new rows in a specific spreadsheet.": "Add one or more new rows in a specific spreadsheet.", + "Delete a row on an existing sheet you have access to": "Delete a row on an existing sheet you have access to", + "Overwrite values in an existing row": "Overwrite values in an existing row", + "Find or get rows in a Google Sheet by column name and search value": "Find or get rows in a Google Sheet by column name and search value", + "Creates a blank spreadsheet.": "Creates a blank spreadsheet.", + "Create a blank worksheet with a title.": "Create a blank worksheet with a title.", + "Clears all rows on an existing sheet": "Clears all rows on an existing sheet", + "Get a row in a Google Sheet by row number": "Get a row in a Google Sheet by row number", + "Get next group of rows from a Google Sheet": "Get next group of rows from a Google Sheet", + "Find spreadsheet(s) by name.": "Find spreadsheet(s) by name.", + "Finds a worksheet(s) by title.": "Finds a worksheet(s) by title.", + "Creates a new worksheet by copying an existing one.": "Creates a new worksheet by copying an existing one.", + "Updates multiple rows in a specific spreadsheet.": "Updates multiple rows in a specific spreadsheet.", + "Adds a new column to a spreadsheet.": "Adds a new column to a spreadsheet.", + "Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint", + "Include Team Drive Sheets ?": "Include Team Drive Sheets ?", + "Spreadsheet": "Spreadsheet", + "Sheet": "Sheet", + "As String": "As String", + "Does the first row contain headers?": "Does the first row contain headers?", + "Values": "Values", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Overwrite Existing Data?", + "Avoid Duplicates?": "Avoid Duplicates?", + "Duplicate Value Column": "Duplicate Value Column", + "Row Number": "Row Number", + "The name of the column to search in": "The name of the column to search in", + "Search Value": "Search Value", + "Exact match": "Exact match", + "Starting Row": "Starting Row", + "Number of Rows": "Number of Rows", + "Title": "Title", + "Parent Folder": "Parent Folder", + "Headers": "Headers", + "Is First row Headers?": "Is First row Headers?", + "Start Row": "Start Row", + "Markdown": "Markdown", + "Memory Key": "Memory Key", + "Group Size": "Group Size", + "Spreadsheet Name": "Spreadsheet Name", + "Exact Match": "Exact Match", + "Spreadsheet Containing the Worksheet to Copy": "Spreadsheet Containing the Worksheet to Copy", + "Worksheet to Copy": "Worksheet to Copy", + "Spreadsheet to paste in": "Spreadsheet to paste in", + "Column Name": "Column Name", + "Column Index": "Column Index", + "Method": "Method", + "Query Parameters": "Query Parameters", + "Body": "Body", + "No Error on Failure": "No Error on Failure", + "Timeout (in seconds)": "Timeout (in seconds)", + "Determines if sheets from Team Drives should be included in the results.": "Determines if sheets from Team Drives should be included in the results.", + "The ID of the spreadsheet to use.": "The ID of the spreadsheet to use.", + "The ID of the sheet to use.": "The ID of the sheet to use.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Inserted values that are dates and formulas will be entered strings and have no effect", + "If the first row is headers": "If the first row is headers", + "The values to insert": "The values to insert", + "Select the format of the input values to be inserted into the sheet.": "Select the format of the input values to be inserted into the sheet.", + "The values to insert.": "The values to insert.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.", + "The column to check for duplicate values.": "The column to check for duplicate values.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Inserted values that are dates and formulas will be entered as strings and have no effect", + "The row number to remove": "The row number to remove", + "The row number to update": "The row number to update", + "The value to search for in the specified column. If left empty, all rows will be returned.": "The value to search for in the specified column. If left empty, all rows will be returned.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Whether to choose the rows with exact match or choose the rows that contain the search value", + "The row number to start searching from": "The row number to start searching from", + "The number of rows to return ( the default is 1 if not specified )": "The number of rows to return ( the default is 1 if not specified )", + "The title of the new spreadsheet.": "The title of the new spreadsheet.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.", + "The title of the new worksheet.": "The title of the new worksheet.", + "The row number to get from the sheet": "The row number to get from the sheet", + "Which row to start from?": "Which row to start from?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "The key used to store the current row number in memory", + "The number of rows to get": "The number of rows to get", + "The name of the spreadsheet(s) to find.": "The name of the spreadsheet(s) to find.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.", + "The values to update.": "The values to update.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.", + "Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Column Names", + "GET": "GET", + "POST": "POST", + "PATCH": "PATCH", + "PUT": "PUT", + "DELETE": "DELETE", + "HEAD": "HEAD", + "New Row Added": "New Row Added", + "New or Updated Row": "New or Updated Row", + "New Spreadsheet": "New Spreadsheet", + "New Worksheet": "New Worksheet", + "Triggers when a new row is added to bottom of a spreadsheet.": "Triggers when a new row is added to bottom of a spreadsheet.", + "Triggers when a new row is added or modified in a spreadsheet.": "Triggers when a new row is added or modified in a spreadsheet.", + "Triggers when a new spreadsheet is created.": "Triggers when a new spreadsheet is created.", + "Triggers when a worksheet is created in a spreadsheet.": "Triggers when a worksheet is created in a spreadsheet.", + "Trigger Column": "Trigger Column", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/pt.json b/packages/pieces/community/google-sheets/src/i18n/pt.json new file mode 100644 index 0000000..f5931c5 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/pt.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Planilhas Google", + "Create, edit, and collaborate on spreadsheets online": "Criar, editar e colaborar em planilhas online", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Inserir Linhas Múltiplas", + "Delete Row": "Excluir linha", + "Update Row": "Atualizar linha", + "Find Rows": "Encontrar linhas", + "Create Spreadsheet": "Criar Planilha", + "Create Worksheet": "Criar planilha", + "Clear Sheet": "Limpar Chapa", + "Get Row": "Obter Linha", + "Get next row(s)": "Obter próxima linha(s)", + "Find Spreadsheet(s)": "Localizar Planilha", + "Find Worksheet(s)": "Localizar planilha", + "Copy Worksheet": "Copiar planilha", + "Update Multiple Rows": "Atualizar Várias Linhas", + "Create Spreadsheet Column": "Criar Coluna da Planilha", + "Custom API Call": "Chamada de API personalizada", + "Append a row of values to an existing sheet": "Acrescentar uma linha de valores a uma folha existente", + "Add one or more new rows in a specific spreadsheet.": "Adicionar uma ou mais linhas em uma planilha específica.", + "Delete a row on an existing sheet you have access to": "Excluir uma linha em uma folha existente à qual você tem acesso", + "Overwrite values in an existing row": "Substituir valores em uma linha existente", + "Find or get rows in a Google Sheet by column name and search value": "Localizar ou obter linhas em uma Planilha Google pelo nome da coluna e valor de pesquisa", + "Creates a blank spreadsheet.": "Cria uma planilha em branco.", + "Create a blank worksheet with a title.": "Crie uma planilha em branco com um título.", + "Clears all rows on an existing sheet": "Limpa todas as linhas em uma folha existente", + "Get a row in a Google Sheet by row number": "Obter uma linha em uma planilha Google com número de linha", + "Get next group of rows from a Google Sheet": "Obter o próximo grupo de linhas de uma planilha Google", + "Find spreadsheet(s) by name.": "Encontrar planilha por nome.", + "Finds a worksheet(s) by title.": "Localiza uma planilha por título.", + "Creates a new worksheet by copying an existing one.": "Cria uma nova folha de atividade copiando uma já existente.", + "Updates multiple rows in a specific spreadsheet.": "Atualiza várias linhas em uma planilha específica.", + "Adds a new column to a spreadsheet.": "Adiciona uma nova coluna a uma planilha.", + "Make a custom API call to a specific endpoint": "Faça uma chamada de API personalizada para um ponto de extremidade específico", + "Include Team Drive Sheets ?": "Incluir Chapas Drive do Time ?", + "Spreadsheet": "Planilha", + "Sheet": "Folhas", + "As String": "Como texto", + "Does the first row contain headers?": "A primeira linha contém cabeçalhos?", + "Values": "Valores", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Substituir Dados Existentes?", + "Avoid Duplicates?": "Evitar duplicatas?", + "Duplicate Value Column": "Duplicar coluna de valor", + "Row Number": "Número da linha", + "The name of the column to search in": "O nome da coluna para pesquisar em", + "Search Value": "Pesquisar Valor", + "Exact match": "Correspondência exata", + "Starting Row": "Linha inicial", + "Number of Rows": "Número de linhas", + "Title": "Título", + "Parent Folder": "Pasta pai", + "Headers": "Cabeçalhos", + "Is First row Headers?": "A primeira linha é de cabeçalhos?", + "Start Row": "Iniciar linha", + "Markdown": "Markdown", + "Memory Key": "Chave de memória", + "Group Size": "Tamanho do grupo", + "Spreadsheet Name": "Nome da Planilha", + "Exact Match": "Partida exata", + "Spreadsheet Containing the Worksheet to Copy": "Cópia da Planilha Contendo a Planilha", + "Worksheet to Copy": "Planilha para copiar", + "Spreadsheet to paste in": "Planilha para colar em", + "Column Name": "Nome da coluna", + "Column Index": "Índice da coluna", + "Method": "Método", + "Query Parameters": "Parâmetros da consulta", + "Body": "Conteúdo", + "No Error on Failure": "Nenhum erro no Failure", + "Timeout (in seconds)": "Tempo limite (em segundos)", + "Determines if sheets from Team Drives should be included in the results.": "Determina se as folhas do disco do time devem ser incluídas nos resultados.", + "The ID of the spreadsheet to use.": "O ID da planilha a ser utilizada.", + "The ID of the sheet to use.": "O ID da folha a ser usado.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Valores inseridos que são datas e fórmulas serão inseridas sequências de caracteres e não têm efeito", + "If the first row is headers": "Se a primeira linha for o cabeçalho", + "The values to insert": "Os valores a inserir", + "Select the format of the input values to be inserted into the sheet.": "Selecione o formato dos valores de entrada a serem inseridos na planilha.", + "The values to insert.": "Os valores a serem inseridos.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Ative esta opção para substituir todos os dados existentes na folha com novos dados da sua entrada. Isso irá limpar quaisquer linhas extras além do intervalo atualizado.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Ative esta opção para verificar se há valores duplicados antes de inserir dados na folha. Apenas linhas exclusivas serão adicionadas com base na coluna selecionada.", + "The column to check for duplicate values.": "A coluna para verificar se há valores duplicados.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Valores inseridos que são datas e fórmulas serão inseridas como sequências de caracteres e não têm efeito", + "The row number to remove": "O número da linha a remover", + "The row number to update": "Número da linha a ser atualizado", + "The value to search for in the specified column. If left empty, all rows will be returned.": "O valor a pesquisar na coluna especificada. Se deixado vazio, todas as linhas serão retornadas.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Se escolher as linhas com correspondência exata ou escolher as linhas que contêm o valor de pesquisa", + "The row number to start searching from": "O número da linha para começar a pesquisar", + "The number of rows to return ( the default is 1 if not specified )": "O número de linhas a retornar (o padrão é 1 se não for especificado)", + "The title of the new spreadsheet.": "O título da nova planilha.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "A pasta para criar a planilha. Por padrão, a nova planilha é criada na pasta raiz do drive.", + "The title of the new worksheet.": "O título da nova planilha.", + "The row number to get from the sheet": "O número da linha para obter a partir da folha", + "Which row to start from?": "De qual linha começar?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notas:**\n\n- Chave de memória é usada para lembrar onde a última linha foi processada e será utilizada nas seguintes execuções.\n- Republicar o fluxo **manter** o valor da chave de memória, se você quiser começar **mudar** a chave de memória.\n", + "The key used to store the current row number in memory": "A chave usada para armazenar o número da linha atual na memória", + "The number of rows to get": "Número de linhas para obter", + "The name of the spreadsheet(s) to find.": "O nome da planilha para encontrar.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "Se verdadeiro, somente retornar planilhas que correspondam exatamente ao nome. Se falso, retornar planilhas que contenham o nome.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "Se verdadeiro, somente retorna planilhas que coincidem exatamente com o nome. Se falso, retorne planilhas que contenham o nome.", + "The values to update.": "Os valores a serem atualizados.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "O índice da coluna começa a partir da 1. Por exemplo, se você quiser adicionar uma coluna na terceira coluna, digite 3. f a entrada é menor que 1 a coluna será adicionada após a última coluna atual.", + "Authorization headers are injected automatically from your connection.": "Os cabeçalhos de autorização são inseridos automaticamente a partir da sua conexão.", + "CSV": "Csv", + "JSON": "JSON", + "Column Names": "Nomes das colunas", + "GET": "OBTER", + "POST": "POSTAR", + "PATCH": "COMPRAR", + "PUT": "COLOCAR", + "DELETE": "EXCLUIR", + "HEAD": "CABEÇA", + "New Row Added": "Nova linha adicionada", + "New or Updated Row": "Linha nova ou atualizada", + "New Spreadsheet": "Nova Planilha", + "New Worksheet": "Nova Planilha", + "Triggers when a new row is added to bottom of a spreadsheet.": "Dispara quando uma nova linha é adicionada à parte inferior de uma planilha.", + "Triggers when a new row is added or modified in a spreadsheet.": "Dispara quando uma nova linha é adicionada ou modificada em uma planilha.", + "Triggers when a new spreadsheet is created.": "Dispara quando uma nova planilha é criada.", + "Triggers when a worksheet is created in a spreadsheet.": "Dispara quando uma planilha é criada em uma planilha.", + "Trigger Column": "Coluna de Disparo", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Observe que pode haver um atraso de até 3 minutos para o gatilho ser disparado, devido a um atraso do Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Acionar mudanças em células apenas nesta coluna. Selecione **Todas as Colunas** se você quiser que o fluxo ative em mudanças para qualquer célula na linha." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/ru.json b/packages/pieces/community/google-sheets/src/i18n/ru.json new file mode 100644 index 0000000..be34d1e --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/ru.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google листы", + "Create, edit, and collaborate on spreadsheets online": "Создавайте, редактируйте и сотрудничайте с электронными таблицами онлайн", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Вставить несколько строк", + "Delete Row": "Удалить строку", + "Update Row": "Обновить строку", + "Find Rows": "Найти строки", + "Create Spreadsheet": "Создать электронную таблицу", + "Create Worksheet": "Создать таблицу", + "Clear Sheet": "Очистить лист", + "Get Row": "Получить строку", + "Get next row(s)": "Получить следующую строку(и)", + "Find Spreadsheet(s)": "Найти таблицу(и)", + "Find Worksheet(s)": "Найти таблицу(и)", + "Copy Worksheet": "Копировать таблицу", + "Update Multiple Rows": "Обновить несколько строк", + "Create Spreadsheet Column": "Создать столбец таблицы", + "Custom API Call": "Пользовательский вызов API", + "Append a row of values to an existing sheet": "Добавить ряд значений к существующей листе", + "Add one or more new rows in a specific spreadsheet.": "Добавьте одну или несколько новых строк в определенной таблице.", + "Delete a row on an existing sheet you have access to": "Удалите строку на существующей линии, к которой имеет доступ", + "Overwrite values in an existing row": "Перезаписать значения в существующую строку", + "Find or get rows in a Google Sheet by column name and search value": "Поиск или получение строк в Google листе по названию столбца и значению поиска", + "Creates a blank spreadsheet.": "Создать пустую таблицу.", + "Create a blank worksheet with a title.": "Создать пустую таблицу с названием.", + "Clears all rows on an existing sheet": "Очистить все строки на существующей листе", + "Get a row in a Google Sheet by row number": "Получить строку в Google листе по номеру ряда", + "Get next group of rows from a Google Sheet": "Получить следующую группу строк из Google листа", + "Find spreadsheet(s) by name.": "Поиск таблиц по имени.", + "Finds a worksheet(s) by title.": "Находит таблицу (таблицы) по названию.", + "Creates a new worksheet by copying an existing one.": "Создает новую таблицу, скопировав существующую.", + "Updates multiple rows in a specific spreadsheet.": "Обновляет несколько строк в определенной таблице.", + "Adds a new column to a spreadsheet.": "Добавляет новый столбец в таблицу.", + "Make a custom API call to a specific endpoint": "Сделать пользовательский API вызов к определенной конечной точке", + "Include Team Drive Sheets ?": "Включать листы дисков команд?", + "Spreadsheet": "Электронная таблица", + "Sheet": "Лист", + "As String": "Как строка", + "Does the first row contain headers?": "Содержит ли первая строка заголовки?", + "Values": "Значения", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Перезаписать существующие данные?", + "Avoid Duplicates?": "Избегать дубликатов?", + "Duplicate Value Column": "Столбец с дублирующимся значением", + "Row Number": "Номер строки", + "The name of the column to search in": "Название столбца для поиска в", + "Search Value": "Поисковое значение", + "Exact match": "Точное совпадение", + "Starting Row": "Начальная строка", + "Number of Rows": "Количество строк", + "Title": "Заголовок", + "Parent Folder": "Родительская папка", + "Headers": "Заголовки", + "Is First row Headers?": "Это заголовки первых строк?", + "Start Row": "Начальная строка", + "Markdown": "Markdown", + "Memory Key": "Ключ памяти", + "Group Size": "Размер группы", + "Spreadsheet Name": "Название таблицы", + "Exact Match": "Точное совпадение", + "Spreadsheet Containing the Worksheet to Copy": "Электронная таблица, содержащая рабочий лист для копирования", + "Worksheet to Copy": "Скопируемая таблица", + "Spreadsheet to paste in": "Электронная таблица для вставки", + "Column Name": "Название столбца", + "Column Index": "Индекс столбцов", + "Method": "Метод", + "Query Parameters": "Параметры запроса", + "Body": "Тело", + "No Error on Failure": "Нет ошибок при ошибке", + "Timeout (in seconds)": "Таймаут (в секундах)", + "Determines if sheets from Team Drives should be included in the results.": "Определяет, следует ли включать в результаты листы из Дисков команд.", + "The ID of the spreadsheet to use.": "Идентификатор используемой электронной таблицы.", + "The ID of the sheet to use.": "ID используемого листа.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Вставленные значения, которые являются датами и формулами будут вводиться строки и не имеют эффекта", + "If the first row is headers": "Если первая строка - заголовки", + "The values to insert": "Значения для вставки", + "Select the format of the input values to be inserted into the sheet.": "Выберите формат вводимых значений для вставки в лист.", + "The values to insert.": "Значения для вставки.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Включите эту опцию, чтобы заменить все существующие данные в листе новыми данными с вашего ввода. Это действие очистит все лишние строки за пределами обновленного диапазона.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Включите этот параметр, чтобы проверить на повторяющиеся значения перед вставкой данных в лист. Будут добавлены только уникальные строки на основе выбранного столбца.", + "The column to check for duplicate values.": "Столбец для проверки повторяющихся значений.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Вставленные значения, которые являются датами и формулами будут введены как строки и не имеют эффекта", + "The row number to remove": "Номер строки для удаления", + "The row number to update": "Номер строки для обновления", + "The value to search for in the specified column. If left empty, all rows will be returned.": "Значение для поиска в указанном столбце. Если оставить пустым, будут возвращены все строки.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Выбор строк с точным совпадением или выбор строк, содержащих значение поиска", + "The row number to start searching from": "Номер строки, с которой начинается поиск", + "The number of rows to return ( the default is 1 if not specified )": "Количество строк для возврата (по умолчанию 1, если не указано )", + "The title of the new spreadsheet.": "Название новой таблицы.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "Папка для создания рабочей таблицы по умолчанию создается в корневой папке диска.", + "The title of the new worksheet.": "Название нового листа.", + "The row number to get from the sheet": "Номер строки, полученный из листа", + "Which row to start from?": "С какой строки начать?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Примечание:**\n\n- Ключ памяти используется для памяти, где обрабатывался последний ряд и будет использоваться в следующих запусках.\n- Перепубликация потока **хранит** значения ключа памяти, если вы хотите начать с **изменить** ключа памяти.\n", + "The key used to store the current row number in memory": "Ключ, используемый для сохранения текущего номера строки в памяти", + "The number of rows to get": "Количество строк для получения", + "The name of the spreadsheet(s) to find.": "Имя имеющейся таблицы(ей) для поиска.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "Если true, возвращает только таблицы, которые точно соответствуют имени. Если false, возвращайте электронные таблицы, содержащие имя.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "Если true, возвращает только таблицы, которые точно соответствуют имени. Если false, возвращайте таблицы, содержащие имя.", + "The values to update.": "Значения для обновления.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "Индекс столбцов начинается с 1.Например, если вы хотите добавить столбец в третий столбец, введите 3. f ввод менее чем 1 столбец будет добавлен после последнего текущего столбца.", + "Authorization headers are injected automatically from your connection.": "Заголовки авторизации включаются автоматически из вашего соединения.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Названия столбцов", + "GET": "ПОЛУЧИТЬ", + "POST": "ПОСТ", + "PATCH": "ПАТЧ", + "PUT": "ПОКУПИТЬ", + "DELETE": "УДАЛИТЬ", + "HEAD": "HEAD", + "New Row Added": "Добавлена новая строка", + "New or Updated Row": "Новая или Обновленная строка", + "New Spreadsheet": "Новая таблица", + "New Worksheet": "Новая таблица", + "Triggers when a new row is added to bottom of a spreadsheet.": "Включает при добавлении новой строки в нижнюю часть таблицы.", + "Triggers when a new row is added or modified in a spreadsheet.": "Включает при добавлении или изменении новой строки в таблице.", + "Triggers when a new spreadsheet is created.": "Включает при создании новой электронной таблицы.", + "Triggers when a worksheet is created in a spreadsheet.": "Включает при создании таблицы в электронном формате.", + "Trigger Column": "Столбец триггера", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Пожалуйста, обратите внимание, что может возникнуть задержка до 3 минут, чтобы срабатывать срабатывание в связи с задержкой от Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Включать только изменения ячеек в этом столбце. выберите **Все колонки**, если хотите, чтобы потоки срабатывали изменения в любой ячейке в строке." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/sv.json b/packages/pieces/community/google-sheets/src/i18n/sv.json new file mode 100644 index 0000000..ca7f145 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/sv.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google Sheets", + "Create, edit, and collaborate on spreadsheets online": "Create, edit, and collaborate on spreadsheets online", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Insert Multiple Rows", + "Delete Row": "Delete Row", + "Update Row": "Update Row", + "Find Rows": "Find Rows", + "Create Spreadsheet": "Create Spreadsheet", + "Create Worksheet": "Create Worksheet", + "Clear Sheet": "Clear Sheet", + "Get Row": "Get Row", + "Get next row(s)": "Get next row(s)", + "Find Spreadsheet(s)": "Find Spreadsheet(s)", + "Find Worksheet(s)": "Find Worksheet(s)", + "Copy Worksheet": "Copy Worksheet", + "Update Multiple Rows": "Update Multiple Rows", + "Create Spreadsheet Column": "Create Spreadsheet Column", + "Custom API Call": "Custom API Call", + "Append a row of values to an existing sheet": "Append a row of values to an existing sheet", + "Add one or more new rows in a specific spreadsheet.": "Add one or more new rows in a specific spreadsheet.", + "Delete a row on an existing sheet you have access to": "Delete a row on an existing sheet you have access to", + "Overwrite values in an existing row": "Overwrite values in an existing row", + "Find or get rows in a Google Sheet by column name and search value": "Find or get rows in a Google Sheet by column name and search value", + "Creates a blank spreadsheet.": "Creates a blank spreadsheet.", + "Create a blank worksheet with a title.": "Create a blank worksheet with a title.", + "Clears all rows on an existing sheet": "Clears all rows on an existing sheet", + "Get a row in a Google Sheet by row number": "Get a row in a Google Sheet by row number", + "Get next group of rows from a Google Sheet": "Get next group of rows from a Google Sheet", + "Find spreadsheet(s) by name.": "Find spreadsheet(s) by name.", + "Finds a worksheet(s) by title.": "Finds a worksheet(s) by title.", + "Creates a new worksheet by copying an existing one.": "Creates a new worksheet by copying an existing one.", + "Updates multiple rows in a specific spreadsheet.": "Updates multiple rows in a specific spreadsheet.", + "Adds a new column to a spreadsheet.": "Adds a new column to a spreadsheet.", + "Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint", + "Include Team Drive Sheets ?": "Include Team Drive Sheets ?", + "Spreadsheet": "Spreadsheet", + "Sheet": "Sheet", + "As String": "As String", + "Does the first row contain headers?": "Does the first row contain headers?", + "Values": "Values", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Overwrite Existing Data?", + "Avoid Duplicates?": "Avoid Duplicates?", + "Duplicate Value Column": "Duplicate Value Column", + "Row Number": "Row Number", + "The name of the column to search in": "The name of the column to search in", + "Search Value": "Search Value", + "Exact match": "Exact match", + "Starting Row": "Starting Row", + "Number of Rows": "Number of Rows", + "Title": "Title", + "Parent Folder": "Parent Folder", + "Headers": "Headers", + "Is First row Headers?": "Is First row Headers?", + "Start Row": "Start Row", + "Markdown": "Markdown", + "Memory Key": "Memory Key", + "Group Size": "Group Size", + "Spreadsheet Name": "Spreadsheet Name", + "Exact Match": "Exact Match", + "Spreadsheet Containing the Worksheet to Copy": "Spreadsheet Containing the Worksheet to Copy", + "Worksheet to Copy": "Worksheet to Copy", + "Spreadsheet to paste in": "Spreadsheet to paste in", + "Column Name": "Column Name", + "Column Index": "Column Index", + "Method": "Method", + "Query Parameters": "Query Parameters", + "Body": "Body", + "No Error on Failure": "No Error on Failure", + "Timeout (in seconds)": "Timeout (in seconds)", + "Determines if sheets from Team Drives should be included in the results.": "Determines if sheets from Team Drives should be included in the results.", + "The ID of the spreadsheet to use.": "The ID of the spreadsheet to use.", + "The ID of the sheet to use.": "The ID of the sheet to use.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Inserted values that are dates and formulas will be entered strings and have no effect", + "If the first row is headers": "If the first row is headers", + "The values to insert": "The values to insert", + "Select the format of the input values to be inserted into the sheet.": "Select the format of the input values to be inserted into the sheet.", + "The values to insert.": "The values to insert.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.", + "The column to check for duplicate values.": "The column to check for duplicate values.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Inserted values that are dates and formulas will be entered as strings and have no effect", + "The row number to remove": "The row number to remove", + "The row number to update": "The row number to update", + "The value to search for in the specified column. If left empty, all rows will be returned.": "The value to search for in the specified column. If left empty, all rows will be returned.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Whether to choose the rows with exact match or choose the rows that contain the search value", + "The row number to start searching from": "The row number to start searching from", + "The number of rows to return ( the default is 1 if not specified )": "The number of rows to return ( the default is 1 if not specified )", + "The title of the new spreadsheet.": "The title of the new spreadsheet.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.", + "The title of the new worksheet.": "The title of the new worksheet.", + "The row number to get from the sheet": "The row number to get from the sheet", + "Which row to start from?": "Which row to start from?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "The key used to store the current row number in memory", + "The number of rows to get": "The number of rows to get", + "The name of the spreadsheet(s) to find.": "The name of the spreadsheet(s) to find.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.", + "The values to update.": "The values to update.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.", + "Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Column Names", + "GET": "GET", + "POST": "POST", + "PATCH": "PATCH", + "PUT": "PUT", + "DELETE": "DELETE", + "HEAD": "HEAD", + "New Row Added": "New Row Added", + "New or Updated Row": "New or Updated Row", + "New Spreadsheet": "New Spreadsheet", + "New Worksheet": "New Worksheet", + "Triggers when a new row is added to bottom of a spreadsheet.": "Triggers when a new row is added to bottom of a spreadsheet.", + "Triggers when a new row is added or modified in a spreadsheet.": "Triggers when a new row is added or modified in a spreadsheet.", + "Triggers when a new spreadsheet is created.": "Triggers when a new spreadsheet is created.", + "Triggers when a worksheet is created in a spreadsheet.": "Triggers when a worksheet is created in a spreadsheet.", + "Trigger Column": "Trigger Column", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/translation.json b/packages/pieces/community/google-sheets/src/i18n/translation.json new file mode 100644 index 0000000..ca7f145 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/translation.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google Sheets", + "Create, edit, and collaborate on spreadsheets online": "Create, edit, and collaborate on spreadsheets online", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Insert Multiple Rows", + "Delete Row": "Delete Row", + "Update Row": "Update Row", + "Find Rows": "Find Rows", + "Create Spreadsheet": "Create Spreadsheet", + "Create Worksheet": "Create Worksheet", + "Clear Sheet": "Clear Sheet", + "Get Row": "Get Row", + "Get next row(s)": "Get next row(s)", + "Find Spreadsheet(s)": "Find Spreadsheet(s)", + "Find Worksheet(s)": "Find Worksheet(s)", + "Copy Worksheet": "Copy Worksheet", + "Update Multiple Rows": "Update Multiple Rows", + "Create Spreadsheet Column": "Create Spreadsheet Column", + "Custom API Call": "Custom API Call", + "Append a row of values to an existing sheet": "Append a row of values to an existing sheet", + "Add one or more new rows in a specific spreadsheet.": "Add one or more new rows in a specific spreadsheet.", + "Delete a row on an existing sheet you have access to": "Delete a row on an existing sheet you have access to", + "Overwrite values in an existing row": "Overwrite values in an existing row", + "Find or get rows in a Google Sheet by column name and search value": "Find or get rows in a Google Sheet by column name and search value", + "Creates a blank spreadsheet.": "Creates a blank spreadsheet.", + "Create a blank worksheet with a title.": "Create a blank worksheet with a title.", + "Clears all rows on an existing sheet": "Clears all rows on an existing sheet", + "Get a row in a Google Sheet by row number": "Get a row in a Google Sheet by row number", + "Get next group of rows from a Google Sheet": "Get next group of rows from a Google Sheet", + "Find spreadsheet(s) by name.": "Find spreadsheet(s) by name.", + "Finds a worksheet(s) by title.": "Finds a worksheet(s) by title.", + "Creates a new worksheet by copying an existing one.": "Creates a new worksheet by copying an existing one.", + "Updates multiple rows in a specific spreadsheet.": "Updates multiple rows in a specific spreadsheet.", + "Adds a new column to a spreadsheet.": "Adds a new column to a spreadsheet.", + "Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint", + "Include Team Drive Sheets ?": "Include Team Drive Sheets ?", + "Spreadsheet": "Spreadsheet", + "Sheet": "Sheet", + "As String": "As String", + "Does the first row contain headers?": "Does the first row contain headers?", + "Values": "Values", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Overwrite Existing Data?", + "Avoid Duplicates?": "Avoid Duplicates?", + "Duplicate Value Column": "Duplicate Value Column", + "Row Number": "Row Number", + "The name of the column to search in": "The name of the column to search in", + "Search Value": "Search Value", + "Exact match": "Exact match", + "Starting Row": "Starting Row", + "Number of Rows": "Number of Rows", + "Title": "Title", + "Parent Folder": "Parent Folder", + "Headers": "Headers", + "Is First row Headers?": "Is First row Headers?", + "Start Row": "Start Row", + "Markdown": "Markdown", + "Memory Key": "Memory Key", + "Group Size": "Group Size", + "Spreadsheet Name": "Spreadsheet Name", + "Exact Match": "Exact Match", + "Spreadsheet Containing the Worksheet to Copy": "Spreadsheet Containing the Worksheet to Copy", + "Worksheet to Copy": "Worksheet to Copy", + "Spreadsheet to paste in": "Spreadsheet to paste in", + "Column Name": "Column Name", + "Column Index": "Column Index", + "Method": "Method", + "Query Parameters": "Query Parameters", + "Body": "Body", + "No Error on Failure": "No Error on Failure", + "Timeout (in seconds)": "Timeout (in seconds)", + "Determines if sheets from Team Drives should be included in the results.": "Determines if sheets from Team Drives should be included in the results.", + "The ID of the spreadsheet to use.": "The ID of the spreadsheet to use.", + "The ID of the sheet to use.": "The ID of the sheet to use.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Inserted values that are dates and formulas will be entered strings and have no effect", + "If the first row is headers": "If the first row is headers", + "The values to insert": "The values to insert", + "Select the format of the input values to be inserted into the sheet.": "Select the format of the input values to be inserted into the sheet.", + "The values to insert.": "The values to insert.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.", + "The column to check for duplicate values.": "The column to check for duplicate values.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Inserted values that are dates and formulas will be entered as strings and have no effect", + "The row number to remove": "The row number to remove", + "The row number to update": "The row number to update", + "The value to search for in the specified column. If left empty, all rows will be returned.": "The value to search for in the specified column. If left empty, all rows will be returned.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Whether to choose the rows with exact match or choose the rows that contain the search value", + "The row number to start searching from": "The row number to start searching from", + "The number of rows to return ( the default is 1 if not specified )": "The number of rows to return ( the default is 1 if not specified )", + "The title of the new spreadsheet.": "The title of the new spreadsheet.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.", + "The title of the new worksheet.": "The title of the new worksheet.", + "The row number to get from the sheet": "The row number to get from the sheet", + "Which row to start from?": "Which row to start from?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "The key used to store the current row number in memory", + "The number of rows to get": "The number of rows to get", + "The name of the spreadsheet(s) to find.": "The name of the spreadsheet(s) to find.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.", + "The values to update.": "The values to update.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.", + "Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Column Names", + "GET": "GET", + "POST": "POST", + "PATCH": "PATCH", + "PUT": "PUT", + "DELETE": "DELETE", + "HEAD": "HEAD", + "New Row Added": "New Row Added", + "New or Updated Row": "New or Updated Row", + "New Spreadsheet": "New Spreadsheet", + "New Worksheet": "New Worksheet", + "Triggers when a new row is added to bottom of a spreadsheet.": "Triggers when a new row is added to bottom of a spreadsheet.", + "Triggers when a new row is added or modified in a spreadsheet.": "Triggers when a new row is added or modified in a spreadsheet.", + "Triggers when a new spreadsheet is created.": "Triggers when a new spreadsheet is created.", + "Triggers when a worksheet is created in a spreadsheet.": "Triggers when a worksheet is created in a spreadsheet.", + "Trigger Column": "Trigger Column", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/uk.json b/packages/pieces/community/google-sheets/src/i18n/uk.json new file mode 100644 index 0000000..ca7f145 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/uk.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google Sheets", + "Create, edit, and collaborate on spreadsheets online": "Create, edit, and collaborate on spreadsheets online", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Insert Multiple Rows", + "Delete Row": "Delete Row", + "Update Row": "Update Row", + "Find Rows": "Find Rows", + "Create Spreadsheet": "Create Spreadsheet", + "Create Worksheet": "Create Worksheet", + "Clear Sheet": "Clear Sheet", + "Get Row": "Get Row", + "Get next row(s)": "Get next row(s)", + "Find Spreadsheet(s)": "Find Spreadsheet(s)", + "Find Worksheet(s)": "Find Worksheet(s)", + "Copy Worksheet": "Copy Worksheet", + "Update Multiple Rows": "Update Multiple Rows", + "Create Spreadsheet Column": "Create Spreadsheet Column", + "Custom API Call": "Custom API Call", + "Append a row of values to an existing sheet": "Append a row of values to an existing sheet", + "Add one or more new rows in a specific spreadsheet.": "Add one or more new rows in a specific spreadsheet.", + "Delete a row on an existing sheet you have access to": "Delete a row on an existing sheet you have access to", + "Overwrite values in an existing row": "Overwrite values in an existing row", + "Find or get rows in a Google Sheet by column name and search value": "Find or get rows in a Google Sheet by column name and search value", + "Creates a blank spreadsheet.": "Creates a blank spreadsheet.", + "Create a blank worksheet with a title.": "Create a blank worksheet with a title.", + "Clears all rows on an existing sheet": "Clears all rows on an existing sheet", + "Get a row in a Google Sheet by row number": "Get a row in a Google Sheet by row number", + "Get next group of rows from a Google Sheet": "Get next group of rows from a Google Sheet", + "Find spreadsheet(s) by name.": "Find spreadsheet(s) by name.", + "Finds a worksheet(s) by title.": "Finds a worksheet(s) by title.", + "Creates a new worksheet by copying an existing one.": "Creates a new worksheet by copying an existing one.", + "Updates multiple rows in a specific spreadsheet.": "Updates multiple rows in a specific spreadsheet.", + "Adds a new column to a spreadsheet.": "Adds a new column to a spreadsheet.", + "Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint", + "Include Team Drive Sheets ?": "Include Team Drive Sheets ?", + "Spreadsheet": "Spreadsheet", + "Sheet": "Sheet", + "As String": "As String", + "Does the first row contain headers?": "Does the first row contain headers?", + "Values": "Values", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Overwrite Existing Data?", + "Avoid Duplicates?": "Avoid Duplicates?", + "Duplicate Value Column": "Duplicate Value Column", + "Row Number": "Row Number", + "The name of the column to search in": "The name of the column to search in", + "Search Value": "Search Value", + "Exact match": "Exact match", + "Starting Row": "Starting Row", + "Number of Rows": "Number of Rows", + "Title": "Title", + "Parent Folder": "Parent Folder", + "Headers": "Headers", + "Is First row Headers?": "Is First row Headers?", + "Start Row": "Start Row", + "Markdown": "Markdown", + "Memory Key": "Memory Key", + "Group Size": "Group Size", + "Spreadsheet Name": "Spreadsheet Name", + "Exact Match": "Exact Match", + "Spreadsheet Containing the Worksheet to Copy": "Spreadsheet Containing the Worksheet to Copy", + "Worksheet to Copy": "Worksheet to Copy", + "Spreadsheet to paste in": "Spreadsheet to paste in", + "Column Name": "Column Name", + "Column Index": "Column Index", + "Method": "Method", + "Query Parameters": "Query Parameters", + "Body": "Body", + "No Error on Failure": "No Error on Failure", + "Timeout (in seconds)": "Timeout (in seconds)", + "Determines if sheets from Team Drives should be included in the results.": "Determines if sheets from Team Drives should be included in the results.", + "The ID of the spreadsheet to use.": "The ID of the spreadsheet to use.", + "The ID of the sheet to use.": "The ID of the sheet to use.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Inserted values that are dates and formulas will be entered strings and have no effect", + "If the first row is headers": "If the first row is headers", + "The values to insert": "The values to insert", + "Select the format of the input values to be inserted into the sheet.": "Select the format of the input values to be inserted into the sheet.", + "The values to insert.": "The values to insert.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.", + "The column to check for duplicate values.": "The column to check for duplicate values.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Inserted values that are dates and formulas will be entered as strings and have no effect", + "The row number to remove": "The row number to remove", + "The row number to update": "The row number to update", + "The value to search for in the specified column. If left empty, all rows will be returned.": "The value to search for in the specified column. If left empty, all rows will be returned.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Whether to choose the rows with exact match or choose the rows that contain the search value", + "The row number to start searching from": "The row number to start searching from", + "The number of rows to return ( the default is 1 if not specified )": "The number of rows to return ( the default is 1 if not specified )", + "The title of the new spreadsheet.": "The title of the new spreadsheet.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.", + "The title of the new worksheet.": "The title of the new worksheet.", + "The row number to get from the sheet": "The row number to get from the sheet", + "Which row to start from?": "Which row to start from?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "The key used to store the current row number in memory", + "The number of rows to get": "The number of rows to get", + "The name of the spreadsheet(s) to find.": "The name of the spreadsheet(s) to find.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.", + "The values to update.": "The values to update.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.", + "Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Column Names", + "GET": "GET", + "POST": "POST", + "PATCH": "PATCH", + "PUT": "PUT", + "DELETE": "DELETE", + "HEAD": "HEAD", + "New Row Added": "New Row Added", + "New or Updated Row": "New or Updated Row", + "New Spreadsheet": "New Spreadsheet", + "New Worksheet": "New Worksheet", + "Triggers when a new row is added to bottom of a spreadsheet.": "Triggers when a new row is added to bottom of a spreadsheet.", + "Triggers when a new row is added or modified in a spreadsheet.": "Triggers when a new row is added or modified in a spreadsheet.", + "Triggers when a new spreadsheet is created.": "Triggers when a new spreadsheet is created.", + "Triggers when a worksheet is created in a spreadsheet.": "Triggers when a worksheet is created in a spreadsheet.", + "Trigger Column": "Trigger Column", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/vi.json b/packages/pieces/community/google-sheets/src/i18n/vi.json new file mode 100644 index 0000000..ca7f145 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/vi.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "Google Sheets", + "Create, edit, and collaborate on spreadsheets online": "Create, edit, and collaborate on spreadsheets online", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "Insert Multiple Rows", + "Delete Row": "Delete Row", + "Update Row": "Update Row", + "Find Rows": "Find Rows", + "Create Spreadsheet": "Create Spreadsheet", + "Create Worksheet": "Create Worksheet", + "Clear Sheet": "Clear Sheet", + "Get Row": "Get Row", + "Get next row(s)": "Get next row(s)", + "Find Spreadsheet(s)": "Find Spreadsheet(s)", + "Find Worksheet(s)": "Find Worksheet(s)", + "Copy Worksheet": "Copy Worksheet", + "Update Multiple Rows": "Update Multiple Rows", + "Create Spreadsheet Column": "Create Spreadsheet Column", + "Custom API Call": "Custom API Call", + "Append a row of values to an existing sheet": "Append a row of values to an existing sheet", + "Add one or more new rows in a specific spreadsheet.": "Add one or more new rows in a specific spreadsheet.", + "Delete a row on an existing sheet you have access to": "Delete a row on an existing sheet you have access to", + "Overwrite values in an existing row": "Overwrite values in an existing row", + "Find or get rows in a Google Sheet by column name and search value": "Find or get rows in a Google Sheet by column name and search value", + "Creates a blank spreadsheet.": "Creates a blank spreadsheet.", + "Create a blank worksheet with a title.": "Create a blank worksheet with a title.", + "Clears all rows on an existing sheet": "Clears all rows on an existing sheet", + "Get a row in a Google Sheet by row number": "Get a row in a Google Sheet by row number", + "Get next group of rows from a Google Sheet": "Get next group of rows from a Google Sheet", + "Find spreadsheet(s) by name.": "Find spreadsheet(s) by name.", + "Finds a worksheet(s) by title.": "Finds a worksheet(s) by title.", + "Creates a new worksheet by copying an existing one.": "Creates a new worksheet by copying an existing one.", + "Updates multiple rows in a specific spreadsheet.": "Updates multiple rows in a specific spreadsheet.", + "Adds a new column to a spreadsheet.": "Adds a new column to a spreadsheet.", + "Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint", + "Include Team Drive Sheets ?": "Include Team Drive Sheets ?", + "Spreadsheet": "Spreadsheet", + "Sheet": "Sheet", + "As String": "As String", + "Does the first row contain headers?": "Does the first row contain headers?", + "Values": "Values", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "Overwrite Existing Data?", + "Avoid Duplicates?": "Avoid Duplicates?", + "Duplicate Value Column": "Duplicate Value Column", + "Row Number": "Row Number", + "The name of the column to search in": "The name of the column to search in", + "Search Value": "Search Value", + "Exact match": "Exact match", + "Starting Row": "Starting Row", + "Number of Rows": "Number of Rows", + "Title": "Title", + "Parent Folder": "Parent Folder", + "Headers": "Headers", + "Is First row Headers?": "Is First row Headers?", + "Start Row": "Start Row", + "Markdown": "Markdown", + "Memory Key": "Memory Key", + "Group Size": "Group Size", + "Spreadsheet Name": "Spreadsheet Name", + "Exact Match": "Exact Match", + "Spreadsheet Containing the Worksheet to Copy": "Spreadsheet Containing the Worksheet to Copy", + "Worksheet to Copy": "Worksheet to Copy", + "Spreadsheet to paste in": "Spreadsheet to paste in", + "Column Name": "Column Name", + "Column Index": "Column Index", + "Method": "Method", + "Query Parameters": "Query Parameters", + "Body": "Body", + "No Error on Failure": "No Error on Failure", + "Timeout (in seconds)": "Timeout (in seconds)", + "Determines if sheets from Team Drives should be included in the results.": "Determines if sheets from Team Drives should be included in the results.", + "The ID of the spreadsheet to use.": "The ID of the spreadsheet to use.", + "The ID of the sheet to use.": "The ID of the sheet to use.", + "Inserted values that are dates and formulas will be entered strings and have no effect": "Inserted values that are dates and formulas will be entered strings and have no effect", + "If the first row is headers": "If the first row is headers", + "The values to insert": "The values to insert", + "Select the format of the input values to be inserted into the sheet.": "Select the format of the input values to be inserted into the sheet.", + "The values to insert.": "The values to insert.", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.", + "The column to check for duplicate values.": "The column to check for duplicate values.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "Inserted values that are dates and formulas will be entered as strings and have no effect", + "The row number to remove": "The row number to remove", + "The row number to update": "The row number to update", + "The value to search for in the specified column. If left empty, all rows will be returned.": "The value to search for in the specified column. If left empty, all rows will be returned.", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "Whether to choose the rows with exact match or choose the rows that contain the search value", + "The row number to start searching from": "The row number to start searching from", + "The number of rows to return ( the default is 1 if not specified )": "The number of rows to return ( the default is 1 if not specified )", + "The title of the new spreadsheet.": "The title of the new spreadsheet.", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.", + "The title of the new worksheet.": "The title of the new worksheet.", + "The row number to get from the sheet": "The row number to get from the sheet", + "Which row to start from?": "Which row to start from?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "The key used to store the current row number in memory", + "The number of rows to get": "The number of rows to get", + "The name of the spreadsheet(s) to find.": "The name of the spreadsheet(s) to find.", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.", + "The values to update.": "The values to update.", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.", + "Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "Column Names", + "GET": "GET", + "POST": "POST", + "PATCH": "PATCH", + "PUT": "PUT", + "DELETE": "DELETE", + "HEAD": "HEAD", + "New Row Added": "New Row Added", + "New or Updated Row": "New or Updated Row", + "New Spreadsheet": "New Spreadsheet", + "New Worksheet": "New Worksheet", + "Triggers when a new row is added to bottom of a spreadsheet.": "Triggers when a new row is added to bottom of a spreadsheet.", + "Triggers when a new row is added or modified in a spreadsheet.": "Triggers when a new row is added or modified in a spreadsheet.", + "Triggers when a new spreadsheet is created.": "Triggers when a new spreadsheet is created.", + "Triggers when a worksheet is created in a spreadsheet.": "Triggers when a worksheet is created in a spreadsheet.", + "Trigger Column": "Trigger Column", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row." +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/i18n/zh.json b/packages/pieces/community/google-sheets/src/i18n/zh.json new file mode 100644 index 0000000..576427a --- /dev/null +++ b/packages/pieces/community/google-sheets/src/i18n/zh.json @@ -0,0 +1,124 @@ +{ + "Google Sheets": "谷歌工作表", + "Create, edit, and collaborate on spreadsheets online": "在线创建、编辑和协作电子表格", + "Insert Row": "Insert Row", + "Insert Multiple Rows": "插入多行", + "Delete Row": "删除行", + "Update Row": "更新行", + "Find Rows": "查找行", + "Create Spreadsheet": "创建工作表", + "Create Worksheet": "创建工作表", + "Clear Sheet": "清除工作表", + "Get Row": "获取行", + "Get next row(s)": "获取下一行(s)", + "Find Spreadsheet(s)": "查找Spreadsheet(s)", + "Find Worksheet(s)": "查找工作表", + "Copy Worksheet": "复制工作表", + "Update Multiple Rows": "更新多行", + "Create Spreadsheet Column": "创建工作表列", + "Custom API Call": "自定义 API 呼叫", + "Append a row of values to an existing sheet": "添加一行值到现有工作表", + "Add one or more new rows in a specific spreadsheet.": "在特定的电子表格中添加一个或多个新行。", + "Delete a row on an existing sheet you have access to": "删除现有工作表上的一行", + "Overwrite values in an existing row": "覆盖现有行中的值", + "Find or get rows in a Google Sheet by column name and search value": "通过列名称和搜索值在谷歌工作表中查找或获取行", + "Creates a blank spreadsheet.": "创建空白电子表格。", + "Create a blank worksheet with a title.": "创建一个带标题的空白工作表。", + "Clears all rows on an existing sheet": "清除现有工作表上的所有行", + "Get a row in a Google Sheet by row number": "按行号在谷歌工作表中获得一行", + "Get next group of rows from a Google Sheet": "从 Google Sheet 获取下一组行", + "Find spreadsheet(s) by name.": "按名称查找电子表格。", + "Finds a worksheet(s) by title.": "按标题查找工作表。", + "Creates a new worksheet by copying an existing one.": "通过复制现有工作表创建一个新工作表。", + "Updates multiple rows in a specific spreadsheet.": "在特定的电子表格中更新多行。", + "Adds a new column to a spreadsheet.": "在电子表格中添加一个新列.", + "Make a custom API call to a specific endpoint": "将一个自定义 API 调用到一个特定的终点", + "Include Team Drive Sheets ?": "包含团队驱动程序表?", + "Spreadsheet": "电子表格", + "Sheet": "工作表", + "As String": "作为字符串", + "Does the first row contain headers?": "第一行是否包含标题?", + "Values": "值", + "Rows Input Format": "Rows Input Format", + "Overwrite Existing Data?": "覆盖现有数据?", + "Avoid Duplicates?": "避免重复?", + "Duplicate Value Column": "重复的值列", + "Row Number": "行号", + "The name of the column to search in": "要搜索的列名称", + "Search Value": "搜索值", + "Exact match": "精确匹配", + "Starting Row": "起始行", + "Number of Rows": "行数", + "Title": "标题", + "Parent Folder": "父文件夹", + "Headers": "信头", + "Is First row Headers?": "是第一行头?", + "Start Row": "开始行", + "Markdown": "Markdown", + "Memory Key": "内存键", + "Group Size": "组大小", + "Spreadsheet Name": "工作表名称", + "Exact Match": "精确匹配", + "Spreadsheet Containing the Worksheet to Copy": "包含工作表的数据表以复制", + "Worksheet to Copy": "复制工作表", + "Spreadsheet to paste in": "要粘贴的数据表", + "Column Name": "列名称", + "Column Index": "列索引", + "Method": "方法", + "Query Parameters": "查询参数", + "Body": "正文内容", + "No Error on Failure": "失败时没有错误", + "Timeout (in seconds)": "超时(秒)", + "Determines if sheets from Team Drives should be included in the results.": "确定是否应该将团队驱动程序中的工作表包含在结果中。", + "The ID of the spreadsheet to use.": "要使用的电子表格的 ID。", + "The ID of the sheet to use.": "要使用的工作表的 ID。", + "Inserted values that are dates and formulas will be entered strings and have no effect": "作为日期和公式的插入值将被输入字符串并且没有任何效果", + "If the first row is headers": "如果第一行是头部", + "The values to insert": "要插入的值", + "Select the format of the input values to be inserted into the sheet.": "选择要插入到工作表的输入值的格式。", + "The values to insert.": "要插入的值。", + "Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.": "启用此选项可以用输入的新数据替换工作表中的所有现有数据。 这将清除超出更新范围的任何额外行数。", + "Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.": "启用此选项,在将数据插入工作表之前检查重复的值。只会根据所选列添加唯一的行。", + "The column to check for duplicate values.": "检查重复值的列.", + "Inserted values that are dates and formulas will be entered as strings and have no effect": "作为日期和公式的插入值将作为字符串输入,无效果", + "The row number to remove": "要删除的行数", + "The row number to update": "要更新的行数", + "The value to search for in the specified column. If left empty, all rows will be returned.": "要在指定列中搜索的值。如果留空,将返回所有行。", + "Whether to choose the rows with exact match or choose the rows that contain the search value": "选择确切匹配的行还是选择包含搜索值的行", + "The row number to start searching from": "开始搜索的行数", + "The number of rows to return ( the default is 1 if not specified )": "要返回的行数 (默认值为 1 如果未指定)", + "The title of the new spreadsheet.": "新电子表格的标题。", + "The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.": "创建工作表的文件夹。默认情况下,新工作表创建于驱动器的根文件夹。", + "The title of the new worksheet.": "新工作表的标题。", + "The row number to get from the sheet": "从工作表中获取的行数", + "Which row to start from?": "从哪一行开始?", + "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n": "\n**Notes:**\n\n- Memory key is used to remember where last row was processed and will be used in the following runs.\n- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key.\n", + "The key used to store the current row number in memory": "用于将当前行号存储在内存中的密钥", + "The number of rows to get": "要获取的行数", + "The name of the spreadsheet(s) to find.": "要查找的电子表格名称。", + "If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.": "如果为 true,只返回与名称完全匹配的电子表格。如果为 false,返回包含名称的电子表格。", + "If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.": "如果为 true,只返回与名称完全匹配的工作单。如果为 false,返回包含名称的工作表。", + "The values to update.": "要更新的值。", + "The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.": "列索引从1.开始。例如,如果您想要在第三列中添加一列,请输入3。 f 输入小于1列将在最后一列后添加。", + "Authorization headers are injected automatically from your connection.": "授权头自动从您的连接中注入。", + "CSV": "CSV", + "JSON": "JSON", + "Column Names": "列名称", + "GET": "获取", + "POST": "帖子", + "PATCH": "PATCH", + "PUT": "弹出", + "DELETE": "删除", + "HEAD": "黑色", + "New Row Added": "添加新行", + "New or Updated Row": "新建或更新行", + "New Spreadsheet": "新建工作表", + "New Worksheet": "新建工作表", + "Triggers when a new row is added to bottom of a spreadsheet.": "当新行被添加到电子表格底部时触发.", + "Triggers when a new row is added or modified in a spreadsheet.": "当新行在电子表格中添加或修改时触发.", + "Triggers when a new spreadsheet is created.": "创建新的电子表格时触发。", + "Triggers when a worksheet is created in a spreadsheet.": "当工作表在电子表格中创建时触发.", + "Trigger Column": "触发列", + "Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.": "请注意,由于谷歌的延迟,触发点可能会延迟3分钟。", + "Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.": "触发对此列单元格的更改。 如果您想要流量触发对行内任何单元格的更改,请选择 **全部列** 。" +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/index.ts b/packages/pieces/community/google-sheets/src/index.ts new file mode 100644 index 0000000..272fb4f --- /dev/null +++ b/packages/pieces/community/google-sheets/src/index.ts @@ -0,0 +1,92 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { clearSheetAction } from './lib/actions/clear-sheet'; +import { deleteRowAction } from './lib/actions/delete-row.action'; +import { findRowByNumAction } from './lib/actions/find-row-by-num'; +import { findRowsAction } from './lib/actions/find-rows'; +import { getRowsAction } from './lib/actions/get-rows'; +import { insertRowAction } from './lib/actions/insert-row.action'; +import { updateRowAction } from './lib/actions/update-row'; +import { googleSheetsCommon } from './lib/common/common'; +import { newRowAddedTrigger } from './lib/triggers/new-row-added-webhook'; +import { newOrUpdatedRowTrigger } from './lib/triggers/new-or-updated-row.trigger'; +import { insertMultipleRowsAction } from './lib/actions/insert-multiple-rows.action'; +import { createWorksheetAction } from './lib/actions/create-worksheet'; +import { createSpreadsheetAction } from './lib/actions/create-spreadsheet'; +import { findSpreadsheets } from './lib/actions/find-spreadsheets'; +import { newSpreadsheetTrigger } from './lib/triggers/new-spreadsheet'; +import { newWorksheetTrigger } from './lib/triggers/new-worksheet'; +import { findWorksheetAction } from './lib/actions/find-worksheet'; +import { copyWorksheetAction } from './lib/actions/copy-worksheet'; +import { updateMultipleRowsAction } from './lib/actions/update-multiple-rows'; +import { createColumnAction } from './lib/actions/create-column'; +import { exportSheetAction } from './lib/actions/export-sheet'; + +export const googleSheetsAuth = PieceAuth.OAuth2({ + description: '', + + authUrl: 'https://accounts.google.com/o/oauth2/auth', + tokenUrl: 'https://oauth2.googleapis.com/token', + required: true, + scope: [ + 'https://www.googleapis.com/auth/spreadsheets', + 'https://www.googleapis.com/auth/drive.readonly', + 'https://www.googleapis.com/auth/drive', + ], +}); + +export const googleSheets = createPiece({ + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/google-sheets.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: [ + 'ShayPunter', + 'Ozak93', + 'Abdallah-Alwarawreh', + 'Salem-Alaa', + 'kishanprmr', + 'MoShizzle', + 'AbdulTheActivePiecer', + 'khaledmashaly', + 'abuaboud', + 'geekyme', + ], + actions: [ + insertRowAction, + insertMultipleRowsAction, + deleteRowAction, + updateRowAction, + findRowsAction, + createSpreadsheetAction, + createWorksheetAction, + clearSheetAction, + findRowByNumAction, + getRowsAction, + findSpreadsheets, + findWorksheetAction, + copyWorksheetAction, + updateMultipleRowsAction, + createColumnAction, + exportSheetAction, + createCustomApiCallAction({ + auth: googleSheetsAuth, + baseUrl: () => { + return googleSheetsCommon.baseUrl; + }, + authMapping: async (auth) => { + return { + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }; + }, + }), + ], + displayName: 'Google Sheets', + description: 'Create, edit, and collaborate on spreadsheets online', + triggers: [newRowAddedTrigger, newOrUpdatedRowTrigger,newSpreadsheetTrigger,newWorksheetTrigger], + auth: googleSheetsAuth, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/clear-sheet.ts b/packages/pieces/community/google-sheets/src/lib/actions/clear-sheet.ts new file mode 100644 index 0000000..76d79b5 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/clear-sheet.ts @@ -0,0 +1,57 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { areSheetIdsValid, googleSheetsCommon } from '../common/common'; +import { googleSheetsAuth } from '../..'; +import { commonProps } from '../common/props'; + +export const clearSheetAction = createAction({ + auth: googleSheetsAuth, + name: 'clear_sheet', + description: 'Clears all rows on an existing sheet', + displayName: 'Clear Sheet', + props: { + ...commonProps, + is_first_row_headers: Property.Checkbox({ + displayName: 'Is First row Headers?', + description: 'If the first row is headers', + required: true, + defaultValue: true, + }), + }, + async run({ propsValue, auth }) { + const { spreadsheetId, sheetId,is_first_row_headers:isFirstRowHeaders } = propsValue; + + if (!areSheetIdsValid(spreadsheetId, sheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + await googleSheetsCommon.findSheetName( + auth.access_token, + spreadsheetId as string, + sheetId as number + ); + + const rowsToDelete: number[] = []; + const values = await googleSheetsCommon.getGoogleSheetRows({ + spreadsheetId: spreadsheetId as string, + accessToken: auth.access_token, + sheetId: sheetId as number, + rowIndex_s: 1, + rowIndex_e: undefined, + }); + for (const key in values) { + if (key === '0' && isFirstRowHeaders) { + continue; + } + rowsToDelete.push(parseInt(key) + 1); + } + + const response = await googleSheetsCommon.clearSheet( + spreadsheetId as string, + sheetId as number, + auth.access_token, + isFirstRowHeaders ? 1 : 0, + rowsToDelete.length + ); + + return response.body; + }, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/copy-worksheet.ts b/packages/pieces/community/google-sheets/src/lib/actions/copy-worksheet.ts new file mode 100644 index 0000000..09774da --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/copy-worksheet.ts @@ -0,0 +1,34 @@ +import { googleSheetsAuth } from '../../index'; +import { createAction } from '@activepieces/pieces-framework'; +import { includeTeamDrivesProp, sheetIdProp, spreadsheetIdProp } from '../common/props'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; + +export const copyWorksheetAction = createAction({ + auth: googleSheetsAuth, + name: 'copy-worksheet', + displayName: 'Copy Worksheet', + description: 'Creates a new worksheet by copying an existing one.', + props: { + includeTeamDrives: includeTeamDrivesProp(), + spreadsheetId: spreadsheetIdProp('Spreadsheet Containing the Worksheet to Copy', ''), + sheetId: sheetIdProp('Worksheet to Copy', ''), + desinationSpeadsheetId: spreadsheetIdProp('Spreadsheet to paste in', ''), + }, + async run(context) { + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + + const sheets = google.sheets({ version: 'v4', auth: authClient }); + + const response = await sheets.spreadsheets.sheets.copyTo({ + spreadsheetId: context.propsValue.spreadsheetId, + sheetId: context.propsValue.sheetId, + requestBody: { + destinationSpreadsheetId: context.propsValue.desinationSpeadsheetId, + }, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/create-column.ts b/packages/pieces/community/google-sheets/src/lib/actions/create-column.ts new file mode 100644 index 0000000..104d425 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/create-column.ts @@ -0,0 +1,106 @@ +import { googleSheetsAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + areSheetIdsValid, + columnToLabel, + getHeaderRow, + ValueInputOption, +} from '../common/common'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { getWorkSheetName } from '../triggers/helpers'; +import { commonProps } from '../common/props'; + +export const createColumnAction = createAction({ + auth: googleSheetsAuth, + name: 'create-column', + displayName: 'Create Spreadsheet Column', + description: 'Adds a new column to a spreadsheet.', + props: { + ...commonProps, + columnName: Property.ShortText({ + displayName: 'Column Name', + required: true, + }), + columnIndex: Property.Number({ + displayName: 'Column Index', + description: + 'The column index starts from 1.For example, if you want to add a column to the third column, enter 3.Ff the input is less than 1 the column will be added after the last current column.', + required: false, + }), + }, + async run(context) { + const { spreadsheetId, sheetId, columnName, columnIndex } = context.propsValue; + + if (!areSheetIdsValid(spreadsheetId, sheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + const sheets = google.sheets({ version: 'v4', auth: authClient }); + + let columnLabel; + + if (columnIndex && columnIndex > 0) { + await sheets.spreadsheets.batchUpdate({ + spreadsheetId, + requestBody: { + requests: [ + { + insertDimension: { + range: { + sheetId, + dimension: 'COLUMNS', + startIndex: columnIndex -1, + endIndex: columnIndex, + }, + }, + }, + ], + }, + }); + columnLabel = columnToLabel(columnIndex-1); + } else { + const headers = await getHeaderRow({ + spreadsheetId:spreadsheetId as string, + sheetId :sheetId as number, + accessToken: context.auth.access_token, + }); + + const newColumnIndex = headers === undefined ? 0 : headers.length; + + await sheets.spreadsheets.batchUpdate({ + spreadsheetId, + requestBody: { + requests: [ + { + insertDimension: { + range: { + sheetId, + dimension: 'COLUMNS', + startIndex: newColumnIndex, + endIndex: newColumnIndex + 1, + }, + }, + }, + ], + }, + }); + columnLabel = columnToLabel(newColumnIndex); + } + + const sheetName = await getWorkSheetName(context.auth, spreadsheetId as string , sheetId as number); + + const response = await sheets.spreadsheets.values.update({ + range: `${sheetName}!${columnLabel}1`, + spreadsheetId, + valueInputOption: ValueInputOption.USER_ENTERED, + requestBody: { + values: [[columnName]], + }, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/create-spreadsheet.ts b/packages/pieces/community/google-sheets/src/lib/actions/create-spreadsheet.ts new file mode 100644 index 0000000..ea527bb --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/create-spreadsheet.ts @@ -0,0 +1,143 @@ +import { + createAction, + OAuth2PropertyValue, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { googleSheetsAuth } from '../..'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { drive_v3, sheets_v4 } from 'googleapis'; +import { includeTeamDrivesProp } from '../common/props'; + +export const createSpreadsheetAction = createAction({ + auth: googleSheetsAuth, + name: 'create-spreadsheet', + displayName: 'Create Spreadsheet', + description: 'Creates a blank spreadsheet.', + props: { + title: Property.ShortText({ + displayName: 'Title', + description: 'The title of the new spreadsheet.', + required: true, + }), + includeTeamDrives: includeTeamDrivesProp(), + folder: Property.Dropdown({ + displayName: 'Parent Folder', + description: + 'The folder to create the worksheet in.By default, the new worksheet is created in the root folder of drive.', + required: false, + refreshers: [], + options: async ({ auth, includeTeamDrives }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + } + const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue; + let folders: { id: string; name: string }[] = []; + let pageToken = null; + do { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `https://www.googleapis.com/drive/v3/files`, + queryParams: { + q: "mimeType='application/vnd.google-apps.folder' and trashed = false", + includeItemsFromAllDrives: includeTeamDrives ? 'true' : 'false', + supportsAllDrives: 'true', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp!['access_token'], + }, + }; + if (pageToken) { + if (request.queryParams !== undefined) { + request.queryParams['pageToken'] = pageToken; + } + } + try { + const response = await httpClient.sendRequest<{ + files: { id: string; name: string }[]; + nextPageToken: string; + }>(request); + folders = folders.concat(response.body.files); + pageToken = response.body.nextPageToken; + } catch (e) { + throw new Error(`Failed to get folders\nError:${e}`); + } + } while (pageToken); + + return { + disabled: false, + options: folders.map((folder: { id: string; name: string }) => { + return { + label: folder.name, + value: folder.id, + }; + }), + }; + }, + }), + }, + async run(context) { + const { title, folder } = context.propsValue; + const response = await createSpreadsheet(context.auth, title); + const newSpreadsheetId = response.spreadsheetId; + + if (folder && newSpreadsheetId) { + await moveFile(context.auth, newSpreadsheetId, folder); + } + + return { + id: newSpreadsheetId, + }; + }, +}); + +async function createSpreadsheet( + auth: PiecePropValueSchema, + title: string, +) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://sheets.googleapis.com/v4/spreadsheets', + body: { + properties: { + title, + }, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }); + + return response.body; +} + +async function moveFile( + auth: PiecePropValueSchema, + fileId: string, + folderId: string, +) { + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: `https://www.googleapis.com/drive/v2/files/${fileId}`, + queryParams: { + addParents: folderId, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }); + + return response.body; +} diff --git a/packages/pieces/community/google-sheets/src/lib/actions/create-worksheet.ts b/packages/pieces/community/google-sheets/src/lib/actions/create-worksheet.ts new file mode 100644 index 0000000..ec9c11d --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/create-worksheet.ts @@ -0,0 +1,61 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { createGoogleSheetClient } from '../common/common'; +import { googleSheetsAuth } from '../..'; +import { includeTeamDrivesProp, spreadsheetIdProp } from '../common/props'; + +export const createWorksheetAction = createAction({ + auth: googleSheetsAuth, + name: 'create-worksheet', + displayName: 'Create Worksheet', + description:'Create a blank worksheet with a title.', + props: { + includeTeamDrives: includeTeamDrivesProp(), + spreadsheetId: spreadsheetIdProp('Spreadsheet',''), + title:Property.ShortText({ + displayName:'Title', + description:'The title of the new worksheet.', + required:true + }), + headers:Property.Array({ + displayName:'Headers', + required:false + }) + + }, + async run(context){ + const {spreadsheetId,title} = context.propsValue; + const headers = context.propsValue.headers as string[] ?? []; + const googleSheetClient = await createGoogleSheetClient(context.auth); + + const sheet = await googleSheetClient.spreadsheets.batchUpdate({ + spreadsheetId:spreadsheetId, + requestBody:{ + requests:[ + { + addSheet:{ + properties:{ + title:title + }, + }, + + } + ] + } + }); + const addHeadersResponse = await googleSheetClient.spreadsheets.values.append({ + spreadsheetId, + range:`${context.propsValue.title}!A1`, + valueInputOption:'RAW', + requestBody:{ + majorDimension:'ROWS', + values:[headers] + } + }); + + return { + id: sheet.data?.replies?.[0]?.addSheet?.properties?.sheetId, + ...addHeadersResponse.data + } + + + }}) \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/lib/actions/delete-row.action.ts b/packages/pieces/community/google-sheets/src/lib/actions/delete-row.action.ts new file mode 100644 index 0000000..35a92b7 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/delete-row.action.ts @@ -0,0 +1,40 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { areSheetIdsValid, googleSheetsCommon } from '../common/common'; +import { googleSheetsAuth } from '../../'; +import { commonProps } from '../common/props'; + +export const deleteRowAction = createAction({ + auth: googleSheetsAuth, + name: 'delete_row', + description: 'Delete a row on an existing sheet you have access to', + displayName: 'Delete Row', + props: { + ...commonProps, + rowId: Property.Number({ + displayName: 'Row Number', + description: 'The row number to remove', + required: true, + }), + }, + async run(context) { + const { spreadsheetId, sheetId, rowId } = context.propsValue; + + if (!areSheetIdsValid(spreadsheetId,sheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + // Subtract 1 from the row_id to convert it to 0-indexed + const adjustedRowIndex = rowId - 1; + const response = await googleSheetsCommon.deleteRow( + spreadsheetId as string, + sheetId as number, + adjustedRowIndex, + context.auth.access_token + ); + + return { + success: true, + body: response, + }; + }, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/export-sheet.ts b/packages/pieces/community/google-sheets/src/lib/actions/export-sheet.ts new file mode 100644 index 0000000..3edb3f8 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/export-sheet.ts @@ -0,0 +1,85 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { googleSheetsAuth } from '../..'; +import { commonProps } from '../common/props'; +import { areSheetIdsValid } from '../common/common'; + +export const exportSheetAction = createAction({ + name: 'export_sheet', + displayName: 'Export Sheet', + description: 'Export a Google Sheets tab to CSV or TSV format.', + auth: googleSheetsAuth, + props: { + ...commonProps, + format: Property.StaticDropdown({ + displayName: 'Export Format', + description: 'The format to export the sheet to.', + required: true, + defaultValue: 'csv', + options: { + disabled: false, + options: [ + { label: 'Comma Separated Values (.csv)', value: 'csv' }, + { label: 'Tab Separated Values (.tsv)', value: 'tsv' }, + ], + }, + }), + returnAsText: Property.Checkbox({ + displayName: 'Return as Text', + description: 'Return the exported data as text instead of a file.', + required: false, + defaultValue: false, + }), + }, + async run({ propsValue, auth, files }) { + const { spreadsheetId, sheetId, format, returnAsText } = propsValue; + + if (!areSheetIdsValid(spreadsheetId, sheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + const spreadsheet_id = spreadsheetId as string; + const sheet_id = sheetId as number; + + const exportUrl = `https://docs.google.com/spreadsheets/d/${spreadsheet_id}/export?format=${format}&id=${spreadsheet_id}&gid=${sheet_id}`; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: exportUrl, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + responseType: 'arraybuffer', + }); + + if (returnAsText) { + const textData = Buffer.from(response.body).toString('utf-8'); + return { + text: textData, + format, + }; + } else { + const filename = `exported_sheet.${format}`; + + const file = await files.write({ + fileName: filename, + data: Buffer.from(response.body), + }); + + return { + file, + filename, + format, + }; + } + } catch (error) { + throw new Error(`Failed to export sheet: ${error}`); + } + }, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/find-row-by-num.ts b/packages/pieces/community/google-sheets/src/lib/actions/find-row-by-num.ts new file mode 100644 index 0000000..71c9cc5 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/find-row-by-num.ts @@ -0,0 +1,35 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { areSheetIdsValid, googleSheetsCommon } from '../common/common'; +import { googleSheetsAuth } from '../..'; +import { commonProps } from '../common/props'; + +export const findRowByNumAction = createAction({ + auth: googleSheetsAuth, + name: 'find_row_by_num', + description: 'Get a row in a Google Sheet by row number', + displayName: 'Get Row', + props: { + ...commonProps, + rowNumber: Property.Number({ + displayName: 'Row Number', + description: 'The row number to get from the sheet', + required: true, + }), + }, + async run(context) { + const {spreadsheetId,sheetId,rowNumber} = context.propsValue; + + if (!areSheetIdsValid(spreadsheetId,sheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + const row = await googleSheetsCommon.getGoogleSheetRows({ + accessToken: context.auth.access_token, + sheetId: sheetId as number, + spreadsheetId: spreadsheetId as string, + rowIndex_s: rowNumber, + rowIndex_e: rowNumber, + }); + return row[0]; + }, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/find-rows.ts b/packages/pieces/community/google-sheets/src/lib/actions/find-rows.ts new file mode 100644 index 0000000..637a885 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/find-rows.ts @@ -0,0 +1,117 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { + areSheetIdsValid, + googleSheetsCommon, + labelToColumn, +} from '../common/common'; +import { googleSheetsAuth } from '../..'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; +import { columnNameProp, commonProps } from '../common/props'; + +export const findRowsAction = createAction({ + auth: googleSheetsAuth, + name: 'find_rows', + description: + 'Find or get rows in a Google Sheet by column name and search value', + displayName: 'Find Rows', + props: { + ...commonProps, + columnName: columnNameProp(), + searchValue: Property.ShortText({ + displayName: 'Search Value', + description: + 'The value to search for in the specified column. If left empty, all rows will be returned.', + required: false, + }), + matchCase: Property.Checkbox({ + displayName: 'Exact match', + description: + 'Whether to choose the rows with exact match or choose the rows that contain the search value', + required: true, + defaultValue: false, + }), + startingRow: Property.Number({ + displayName: 'Starting Row', + description: 'The row number to start searching from', + required: false, + }), + numberOfRows: Property.Number({ + displayName: 'Number of Rows', + description: + 'The number of rows to return ( the default is 1 if not specified )', + required: false, + defaultValue: 1, + }), + }, + async run({ propsValue, auth }) { + await propsValidation.validateZod(propsValue, { + startingRow: z.number().min(1).optional(), + numberOfRows: z.number().min(1).optional(), + }); + + const spreadsheetId = propsValue.spreadsheetId; + const sheetId = propsValue.sheetId; + const startingRow = propsValue.startingRow ?? 1; + const numberOfRowsToReturn = propsValue.numberOfRows ?? 1; + + if (!areSheetIdsValid(spreadsheetId,sheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + const rows = await googleSheetsCommon.getGoogleSheetRows({ + spreadsheetId: spreadsheetId as string, + accessToken: auth.access_token, + sheetId: sheetId as number, + rowIndex_s: startingRow, + rowIndex_e: undefined, + }); + + const values = rows.map((row) => { + return row.values; + }); + + const matchingRows: any[] = []; + const columnName = propsValue.columnName ? propsValue.columnName : 'A'; + const columnNumber:number = labelToColumn(columnName); + const searchValue = propsValue.searchValue ?? ''; + + let matchedRowCount = 0; + + for (let i = 0; i < values.length; i++) { + const row:Record = values[i]; + + if (matchedRowCount === numberOfRowsToReturn) break; + + if (searchValue === '') { + matchingRows.push(rows[i]); + matchedRowCount += 1; + continue; + } + + const keys = Object.keys(row); + if (keys.length <= columnNumber) continue; + const entry_value = row[keys[columnNumber]]; + + if (entry_value === undefined) { + continue; + } + if (propsValue.matchCase) { + if (entry_value === searchValue) { + matchedRowCount += 1; + matchingRows.push(rows[i]); + } + } else { + if (entry_value.toLowerCase().includes(searchValue.toLowerCase())) { + matchedRowCount += 1; + matchingRows.push(rows[i]); + } + } + } + + return matchingRows; + }, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/find-spreadsheets.ts b/packages/pieces/community/google-sheets/src/lib/actions/find-spreadsheets.ts new file mode 100644 index 0000000..eba8484 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/find-spreadsheets.ts @@ -0,0 +1,82 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod, AuthenticationType, HttpRequest } from '@activepieces/pieces-common'; +import { googleSheetsAuth } from '../..'; +import { includeTeamDrivesProp } from '../common/props'; + +export const findSpreadsheets = createAction({ + name: 'find_spreadsheets', + displayName: 'Find Spreadsheet(s)', + description: 'Find spreadsheet(s) by name.', + auth: googleSheetsAuth, + props: { + includeTeamDrives: includeTeamDrivesProp(), + spreadsheet_name: Property.ShortText({ + displayName: 'Spreadsheet Name', + description: 'The name of the spreadsheet(s) to find.', + required: true, + }), + exact_match: Property.Checkbox({ + displayName: 'Exact Match', + description: 'If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.', + required: false, + defaultValue: false, + }), + }, + async run({ propsValue, auth }) { + const searchValue = propsValue.spreadsheet_name; + const queries = [ + "mimeType='application/vnd.google-apps.spreadsheet'", + 'trashed=false', + ]; + + if (propsValue.exact_match) { + queries.push(`name = '${searchValue}'`); + } else { + queries.push(`name contains '${searchValue}'`); + } + + const files = []; + let pageToken = null; + + do + { + const request :HttpRequest = { + method:HttpMethod.GET, + url: 'https://www.googleapis.com/drive/v3/files', + queryParams:{ + q: queries.join(' and '), + includeItemsFromAllDrives: propsValue.includeTeamDrives ? 'true' : 'false', + supportsAllDrives: 'true', + fields: 'files(id,name,webViewLink,createdTime,modifiedTime),nextPageToken', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + + } + if (pageToken) { + if (request.queryParams !== undefined) { + request.queryParams['pageToken'] = pageToken; + } + } + try { + const response = await httpClient.sendRequest<{ + files: { id: string; name: string }[]; + nextPageToken: string; + }>(request); + + files.push(...response.body.files); + pageToken = response.body.nextPageToken; + } catch (e) { + throw new Error(`Failed to get folders\nError:${e}`); + } + + }while(pageToken); + + return { + found: files.length > 0, + spreadsheets:files, + }; + }, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/find-worksheet.ts b/packages/pieces/community/google-sheets/src/lib/actions/find-worksheet.ts new file mode 100644 index 0000000..b7eb7dd --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/find-worksheet.ts @@ -0,0 +1,52 @@ +import { googleSheetsAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { includeTeamDrivesProp, spreadsheetIdProp } from '../common/props'; + +export const findWorksheetAction = createAction({ + auth: googleSheetsAuth, + name: 'find-worksheet', + displayName: 'Find Worksheet(s)', + description: 'Finds a worksheet(s) by title.', + props: { + includeTeamDrives: includeTeamDrivesProp(), + spreadsheetId:spreadsheetIdProp('Spreadsheet',''), + title: Property.ShortText({ + displayName: 'Title', + required: true, + }), + exact_match: Property.Checkbox({ + displayName: 'Exact Match', + description: 'If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const spreadsheetId = context.propsValue.spreadsheetId; + const title = context.propsValue.title; + const exactMatch = context.propsValue.exact_match ?? false; + + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + + const sheets = google.sheets({ version: 'v4', auth: authClient }); + + const response = await sheets.spreadsheets.get({ + spreadsheetId, + }); + + const sheetsData = response.data.sheets ?? []; + + const matchedSheets = sheetsData.filter((sheet) => { + const sheetTitle = sheet.properties?.title ?? ""; + return exactMatch ? sheetTitle === title : sheetTitle.includes(title); + }); + + return { + found: matchedSheets.length > 0, + worksheets: matchedSheets , + }; + }, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/get-rows.ts b/packages/pieces/community/google-sheets/src/lib/actions/get-rows.ts new file mode 100644 index 0000000..1a5cab8 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/get-rows.ts @@ -0,0 +1,186 @@ +import { + PiecePropValueSchema, + Property, + Store, + StoreScope, + createAction, +} from '@activepieces/pieces-framework'; +import { googleSheetsAuth } from '../..'; +import { + areSheetIdsValid, + googleSheetsCommon, +} from '../common/common'; +import { isNil } from '@activepieces/shared'; +import { HttpError } from '@activepieces/pieces-common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; +import { getWorkSheetGridSize } from '../triggers/helpers'; +import { commonProps } from '../common/props'; + +async function getRows( + store: Store, + auth: PiecePropValueSchema, + spreadsheetId: string, + sheetId: number, + memKey: string, + groupSize: number, + startRow: number, + testing: boolean +) { + const sheetName = await googleSheetsCommon.findSheetName( + auth.access_token, + spreadsheetId, + sheetId + ); + + const sheetGridRange = await getWorkSheetGridSize(auth,spreadsheetId,sheetId); + const existingGridRowCount = sheetGridRange.rowCount ??0; + // const existingGridColumnCount = sheetGridRange.columnCount??26; + + + + const memVal = await store.get(memKey, StoreScope.FLOW); + + let startingRow; + if (isNil(memVal) || memVal === '') startingRow = startRow || 1; + else { + startingRow = parseInt(memVal as string); + if (isNaN(startingRow)) { + throw Error( + 'The value stored in memory key : ' + + memKey + + ' is ' + + memVal + + ' and it is not a number' + ); + } + } + + if (startingRow < 1) + throw Error('Starting row : ' + startingRow + ' is less than 1' + memVal); + + + if(startingRow > existingGridRowCount-1){ + return []; + } + + const endRow = Math.min(startingRow + groupSize,existingGridRowCount); + + if (testing == false) await store.put(memKey, endRow, StoreScope.FLOW); + + const row = await googleSheetsCommon.getGoogleSheetRows({ + accessToken: auth.access_token, + sheetId: sheetId, + spreadsheetId: spreadsheetId, + rowIndex_s: startingRow, + rowIndex_e: endRow - 1, + }); + + if (row.length == 0) { + const allRows = await googleSheetsCommon.getGoogleSheetRows({ + spreadsheetId: spreadsheetId, + accessToken: auth.access_token, + sheetId: sheetId, + rowIndex_s: undefined, + rowIndex_e: undefined, + }); + const lastRow = allRows.length + 1; + if (testing == false) await store.put(memKey, lastRow, StoreScope.FLOW); + } + + return row; +} + +const notes = ` +**Notes:** + +- Memory key is used to remember where last row was processed and will be used in the following runs. +- Republishing the flow **keeps** the memory key value, If you want to start over **change** the memory key. +` +export const getRowsAction = createAction({ + auth: googleSheetsAuth, + name: 'get_next_rows', + description: 'Get next group of rows from a Google Sheet', + displayName: 'Get next row(s)', + props: { + ...commonProps, + startRow: Property.Number({ + displayName: 'Start Row', + description: 'Which row to start from?', + required: true, + defaultValue: 1, + }), + markdown: Property.MarkDown({ + value: notes + }), + memKey: Property.ShortText({ + displayName: 'Memory Key', + description: 'The key used to store the current row number in memory', + required: true, + defaultValue: 'row_number', + }), + groupSize: Property.Number({ + displayName: 'Group Size', + description: 'The number of rows to get', + required: true, + defaultValue: 1, + }), + }, + async run({ store, auth, propsValue }) { + const { startRow, groupSize, memKey ,spreadsheetId,sheetId} = propsValue; + + if (!areSheetIdsValid(spreadsheetId, sheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + await propsValidation.validateZod(propsValue, { + startRow: z.number().min(1), + groupSize: z.number().min(1), + }); + + try { + return await getRows( + store, + auth, + spreadsheetId as string, + sheetId as number, + memKey, + groupSize, + startRow, + false + ); + } catch (error) { + if (error instanceof HttpError) { + const errorBody = error.response.body as any; + throw new Error(errorBody['error']['message']); + } + throw error; + } + }, + async test({ store, auth, propsValue }) { + const { startRow, groupSize, memKey ,spreadsheetId,sheetId} = propsValue; + + if (!areSheetIdsValid(spreadsheetId, sheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + try { + return await getRows( + store, + auth, + spreadsheetId as string, + sheetId as number, + memKey, + groupSize, + startRow, + true + ); + } catch (error) { + if (error instanceof HttpError) { + const errorBody = error.response.body as any; + throw new Error(errorBody['error']['message']); + } + throw error; + } + }, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/insert-multiple-rows.action.ts b/packages/pieces/community/google-sheets/src/lib/actions/insert-multiple-rows.action.ts new file mode 100644 index 0000000..7f8a712 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/insert-multiple-rows.action.ts @@ -0,0 +1,506 @@ +import { googleSheetsAuth } from '../../'; +import { + createAction, + DropdownOption, + DynamicPropsValue, + OAuth2PropertyValue, + Property, +} from '@activepieces/pieces-framework'; +import { Dimension, googleSheetsCommon, objectToArray, ValueInputOption,columnToLabel, areSheetIdsValid } from '../common/common'; +import { getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { getWorkSheetName, getWorkSheetGridSize } from '../triggers/helpers'; +import { google, sheets_v4 } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { isNil, MarkdownVariant } from '@activepieces/shared'; +import {parse} from 'csv-parse/sync'; +import { commonProps } from '../common/props'; + + +type RowValueType = Record + +export const insertMultipleRowsAction = createAction({ + auth: googleSheetsAuth, + name: 'google-sheets-insert-multiple-rows', + displayName: 'Insert Multiple Rows', + description: 'Add one or more new rows in a specific spreadsheet.', + props: { + ...commonProps, + input_type: Property.StaticDropdown({ + displayName: 'Rows Input Format', + description: 'Select the format of the input values to be inserted into the sheet.', + required: true, + defaultValue: 'column_names', + options: { + disabled: false, + options: [ + { + value: 'csv', + label: 'CSV', + }, + { + value: 'json', + label: 'JSON', + }, + { + value: 'column_names', + label: 'Column Names', + }, + ], + }, + }), + values: Property.DynamicProperties({ + displayName: 'Values', + description: 'The values to insert.', + required: true, + refreshers: ['sheetId', 'spreadsheetId', 'input_type'], + props: async ({ auth, sheetId, spreadsheetId, input_type }) => { + const sheet_id = Number(sheetId); + const spreadsheet_id = spreadsheetId as unknown as string; + const valuesInputType = input_type as unknown as string; + const authentication = auth as OAuth2PropertyValue; + + if ( + !auth || + (spreadsheet_id ?? '').toString().length === 0 || + (sheet_id ?? '').toString().length === 0 || + !['csv', 'json', 'column_names'].includes(valuesInputType) + ) { + return {}; + } + + const fields: DynamicPropsValue = {}; + + switch (valuesInputType) { + case 'csv': + fields['markdown'] = Property.MarkDown({ + value: `Ensure the first row contains column headers that match the sheet's column names.`, + variant: MarkdownVariant.INFO, + }); + fields['values'] = Property.LongText({ + displayName: 'CSV', + required: true, + }); + break; + case 'json': + fields['markdown'] = Property.MarkDown({ + value: `Provide values in JSON format. Ensure the column names match the sheet's header.`, + variant: MarkdownVariant.INFO, + }); + fields['values'] = Property.Json({ + displayName: 'JSON', + required: true, + defaultValue: [ + { + column1: 'value1', + column2: 'value2', + }, + { + column1: 'value3', + column2: 'value4', + }, + ], + }); + break; + case 'column_names': { + const headers = await googleSheetsCommon.getGoogleSheetRows({ + spreadsheetId: spreadsheet_id, + accessToken: getAccessTokenOrThrow(authentication), + sheetId: sheet_id, + rowIndex_s: 1, + rowIndex_e: 1, + }); + const firstRow = headers[0].values ?? {}; + + //check for empty headers + if (Object.keys(firstRow).length === 0) { + fields['markdown'] = Property.MarkDown({ + value: `We couldn't find any headers in the selected spreadsheet or worksheet. Please add headers to the sheet and refresh the page to reflect the columns.`, + variant: MarkdownVariant.INFO, + }); + } else { + const columns: { + [key: string]: any; + } = {}; + + for (const key in firstRow) { + columns[key] = Property.ShortText({ + displayName: firstRow[key].toString(), + description: firstRow[key].toString(), + required: false, + defaultValue: '', + }); + } + fields['values'] = Property.Array({ + displayName: 'Values', + required: false, + properties: columns, + }); + } + } + } + return fields; + }, + }), + overwrite: Property.Checkbox({ + displayName: 'Overwrite Existing Data?', + description: + 'Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.', + required: false, + defaultValue: false, + }), + check_for_duplicate: Property.Checkbox({ + displayName: 'Avoid Duplicates?', + description: + 'Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.', + required: false, + defaultValue: false, + }), + check_for_duplicate_column: Property.DynamicProperties({ + displayName: 'Duplicate Value Column', + description: 'The column to check for duplicate values.', + refreshers: ['spreadsheetId', 'sheetId', 'check_for_duplicate'], + required: false, + props: async ({ auth, spreadsheetId, sheetId, check_for_duplicate }) => { + const sheet_id = Number(sheetId); + const spreadsheet_id = spreadsheetId as unknown as string; + const authentication = auth as OAuth2PropertyValue; + const checkForExisting = check_for_duplicate as unknown as boolean; + if ( + !auth || + (spreadsheet_id ?? '').toString().length === 0 || + (sheet_id ?? '').toString().length === 0 + ) { + return {}; + } + + const fields: DynamicPropsValue = {}; + + if (checkForExisting) { + const headers = await googleSheetsCommon.getGoogleSheetRows({ + spreadsheetId: spreadsheet_id, + accessToken: getAccessTokenOrThrow(authentication), + sheetId: sheet_id, + rowIndex_s: 1, + rowIndex_e: 1, + }); + const firstRow = headers[0].values ?? {}; + + //check for empty headers + if (Object.keys(firstRow).length === 0) { + fields['markdown'] = Property.MarkDown({ + value: `No headers were found in the selected spreadsheet or worksheet. Please ensure that headers are added to the sheet and refresh the page to display the available columns.`, + variant: MarkdownVariant.INFO, + }); + } else { + const headers: DropdownOption[] = []; + for (const key in firstRow) { + headers.push({ label: firstRow[key].toString(), value: key.toString() }); + } + + fields['column_name'] = Property.StaticDropdown({ + displayName: 'Column to Check for Duplicates', + description: + 'Select the column to use for detecting duplicate values. Only rows with unique values in this column will be added to the sheet.', + required: true, + options: { + disabled: false, + options: headers, + }, + }); + } + } + + return fields; + }, + }), + as_string: Property.Checkbox({ + displayName: 'As String', + description: + 'Inserted values that are dates and formulas will be entered as strings and have no effect', + required: false, + }), + }, + + async run(context) { + const { + spreadsheetId:inputSpreadsheetId, + sheetId:inputSheetId, + input_type: valuesInputType, + overwrite: overwriteValues, + check_for_duplicate: checkForDuplicateValues, + values: { values: rowValuesInput }, + as_string: asString, + } = context.propsValue; + + if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + const sheetId = Number(inputSheetId); + const spreadsheetId = inputSpreadsheetId as string; + + const duplicateColumn = context.propsValue.check_for_duplicate_column?.['column_name']; + const sheetName = await getWorkSheetName(context.auth, spreadsheetId, sheetId); + + const rowHeaders = await googleSheetsCommon.getGoogleSheetRows({ + spreadsheetId: spreadsheetId, + accessToken: context.auth.access_token, + sheetId: sheetId, + rowIndex_s: 1, + rowIndex_e: 1, + }); + + const sheetHeaders = rowHeaders[0]?.values ?? {}; + + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + const sheets = google.sheets({ version: 'v4', auth: authClient }); + + const formattedValues = await formatInputRows(sheets,spreadsheetId, sheetName,valuesInputType, rowValuesInput, sheetHeaders); + + const valueInputOption = asString ? ValueInputOption.RAW : ValueInputOption.USER_ENTERED; + + + if (overwriteValues) { + const sheetGridRange = await getWorkSheetGridSize(context.auth, spreadsheetId, sheetId); + const existingGridRowCount = sheetGridRange.rowCount ?? 0; + return handleOverwrite(sheets, spreadsheetId, sheetName, formattedValues, existingGridRowCount, valueInputOption); + } + + if (checkForDuplicateValues) { + const existingSheetValues = await googleSheetsCommon.getGoogleSheetRows({ + spreadsheetId: spreadsheetId, + accessToken: context.auth.access_token, + sheetId: sheetId, + rowIndex_s: 1, + rowIndex_e: undefined, + }); + return handleDuplicates( + sheets, + spreadsheetId, + sheetName, + formattedValues, + existingSheetValues, + duplicateColumn, + valueInputOption + ); + } + + return normalInsert(sheets, spreadsheetId, sheetName, formattedValues, valueInputOption); + }, +}); + +async function handleOverwrite( + sheets: sheets_v4.Sheets, + spreadSheetId: string, + sheetName: string, + formattedValues: any[], + existingGridRowCount: number, + valueInputOption: ValueInputOption +) { + const existingRowCount = existingGridRowCount; + const inputRowCount = formattedValues.length; + + const updateResponse = await sheets.spreadsheets.values.batchUpdate({ + spreadsheetId: spreadSheetId, + requestBody: { + data: [{ + range: `${sheetName}!A2:ZZZ${inputRowCount + 1}`, + majorDimension: Dimension.ROWS, + values: formattedValues.map(row => objectToArray(row)), + }], + valueInputOption + }, + }); + + // Determine if clearing rows is necessary and within grid size + const clearStartRow = inputRowCount + 2; // Start clearing after the last input row + const clearEndRow = Math.max(clearStartRow, existingRowCount); + + if (clearStartRow <= existingGridRowCount) { + const boundedClearEndRow = Math.min(clearEndRow, existingGridRowCount); + const clearRowsResponse = await sheets.spreadsheets.values.batchClear({ + spreadsheetId: spreadSheetId, + requestBody: { + ranges: [`${sheetName}!A${clearStartRow}:ZZZ${boundedClearEndRow}`], + }, + }); + + return { + ...updateResponse.data, + ...clearRowsResponse.data, + }; + } + return updateResponse.data; + + +} + +async function handleDuplicates( + sheets: sheets_v4.Sheets, + spreadSheetId: string, + sheetName: string, + formattedInputRows: any[], + existingSheetValues: any[], + duplicateColumn: string, + valueInputOption: ValueInputOption +) { + + const uniqueValues = formattedInputRows.filter( + (inputRow) => !existingSheetValues.some( + (existingRow) => { + const existingValue = existingRow?.values?.[duplicateColumn]; + const inputValue = inputRow?.[duplicateColumn]; + return existingValue != null && inputValue != null && + String(existingValue).toLowerCase().trim() === String(inputValue).toLowerCase().trim(); + } + ) + ); + + const response = await sheets.spreadsheets.values.append({ + range: sheetName + '!A:A', + spreadsheetId: spreadSheetId, + valueInputOption, + requestBody: { + values: uniqueValues.map((row) => objectToArray(row)), + majorDimension: Dimension.ROWS, + }, + }); + + return response.data; +} + +async function normalInsert( + sheets: sheets_v4.Sheets, + spreadSheetId: string, + sheetName: string, + formattedValues: any[], + valueInputOption: ValueInputOption +) { + const response = await sheets.spreadsheets.values.append({ + range: sheetName + '!A:A', + spreadsheetId: spreadSheetId, + valueInputOption, + requestBody: { + values: formattedValues.map(row => objectToArray(row)), + majorDimension: Dimension.ROWS, + }, + }); + return response.data; +} + +async function formatInputRows( + sheets: sheets_v4.Sheets, + spreadSheetId: string, + sheetName: string, + valuesInputType: string, + rowValuesInput: any, + sheetHeaders: RowValueType +): Promise { + let formattedInputRows: any[] = []; + + switch (valuesInputType) { + case 'csv': + formattedInputRows = convertCsvToRawValues(rowValuesInput as string, ',', sheetHeaders); + break; + case 'json': + formattedInputRows = await convertJsonToRawValues(sheets,spreadSheetId, sheetName, rowValuesInput as string, sheetHeaders); + break; + case 'column_names': + formattedInputRows = rowValuesInput as RowValueType[]; + break; + } + + return formattedInputRows; +} + +async function convertJsonToRawValues( + sheets: sheets_v4.Sheets, + spreadSheetId: string, + sheetName: string, + json: string | Record[], + labelHeaders: RowValueType +): Promise { + + let data: RowValueType[]; + + // If the input is a JSON string + if (typeof json === 'string') { + try { + data = JSON.parse(json); + } catch (error) { + throw new Error('Invalid JSON format for row values'); + } + } else { + // If the input is already an array of objects, use it directly + data = json; + } + + // Ensure the input is an array of objects + if (!Array.isArray(data) || typeof data[0] !== 'object') { + throw new Error('Input must be an array of objects or a valid JSON string representing it.'); + } + + // Collect all possible headers from the data + const allHeaders = new Set(); + data.forEach((row) => { + Object.keys(row).forEach((key) => allHeaders.add(key)); + }); + + // Identify headers not present in labelHeaders + const additionalHeaders = Array.from(allHeaders).filter( + (header) => !Object.values(labelHeaders).includes(header) + ); + + //add missing headers to labelHeaders + additionalHeaders.forEach((header) => { + labelHeaders[columnToLabel(Object.keys(labelHeaders).length)] = header; + }); + + // update sheets with new headers + if (additionalHeaders.length > 0) { + await sheets.spreadsheets.values.update({ + range: `${sheetName}!A1:ZZZ1`, + spreadsheetId: spreadSheetId, + valueInputOption: ValueInputOption.USER_ENTERED, + requestBody: { + majorDimension:Dimension.ROWS, + values: [objectToArray(labelHeaders)] + } + }); + } + + return data.map((row: RowValueType) => { + return Object.entries(labelHeaders).reduce((acc, [labelColumn, csvHeader]) => { + acc[labelColumn] = row[csvHeader] ?? ""; + return acc; + }, {} as RowValueType); + + }) +} + +function convertCsvToRawValues( + csvText: string, + delimiter: string, + labelHeaders: RowValueType, +) { + // Split CSV into rows + const rows:Record[] = parse(csvText,{delimiter: delimiter, columns: true}); + + const result = rows.map((row)=>{ + // Normalize record keys to lowercase + const normalizedRow = Object.keys(row).reduce((acc, key) => { + acc[key.toLowerCase().trim()] = row[key]; + return acc; + },{} as Record); + + const transformedRow :Record= {}; + for(const key in labelHeaders){ + // Match labels to normalized keys + const normalizedKey = labelHeaders[key].toLowerCase(); + transformedRow[key] = normalizedRow[normalizedKey] || ''; + } + return transformedRow; + }) + return result; +} diff --git a/packages/pieces/community/google-sheets/src/lib/actions/insert-row.action.ts b/packages/pieces/community/google-sheets/src/lib/actions/insert-row.action.ts new file mode 100644 index 0000000..db82636 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/insert-row.action.ts @@ -0,0 +1,105 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + areSheetIdsValid, + Dimension, + googleSheetsCommon, + objectToArray, + stringifyArray, + ValueInputOption, +} from '../common/common'; +import { googleSheetsAuth } from '../..'; +import { isNil } from '@activepieces/shared'; +import { AuthenticationType, httpClient, HttpMethod, HttpRequest } from '@activepieces/pieces-common'; +import { commonProps, rowValuesProp } from '../common/props'; + +export const insertRowAction = createAction({ + auth: googleSheetsAuth, + name: 'insert_row', + description: 'Append a row of values to an existing sheet', + displayName: 'Insert Row', + props: { + ...commonProps, + as_string: Property.Checkbox({ + displayName: 'As String', + description: 'Inserted values that are dates and formulas will be entered strings and have no effect', + required: false, + }), + first_row_headers: Property.Checkbox({ + displayName: 'Does the first row contain headers?', + description: 'If the first row is headers', + required: true, + defaultValue: false, + }), + values: rowValuesProp(), + }, + async run({ propsValue, auth }) { + const { values, spreadsheetId:inputSpreadsheetId, sheetId:inputSheetId, as_string, first_row_headers } = propsValue; + const accessToken = auth.access_token; + + if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + const sheetId = Number(inputSheetId); + const spreadsheetId = inputSpreadsheetId as string; + + const sheetName = await googleSheetsCommon.findSheetName( + accessToken, + spreadsheetId, + sheetId + ); + + const formattedValues = first_row_headers + ? objectToArray(values).map(val => isNil(val) ? '' : val) + : values.values; + + const res = await appendGoogleSheetValues({ + accessToken, + majorDimension: Dimension.COLUMNS, + range: sheetName, + spreadSheetId: spreadsheetId, + valueInputOption: as_string ? ValueInputOption.RAW : ValueInputOption.USER_ENTERED, + values: stringifyArray(formattedValues), + }); + + const updatedRowNumber = extractRowNumber(res.body.updates.updatedRange); + return { ...res.body, row: updatedRowNumber }; + }, +}); + +function extractRowNumber(updatedRange: string): number { + const rowRange = updatedRange.split('!')[1]; + return parseInt(rowRange.split(':')[0].substring(1), 10); +} + +async function appendGoogleSheetValues(params: AppendGoogleSheetValuesParams) { + const { accessToken, majorDimension, range, spreadSheetId, valueInputOption, values } = params; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://sheets.googleapis.com/v4/spreadsheets/${spreadSheetId}/values/${encodeURIComponent(`${range}!A:A`)}:append`, + body: { + majorDimension, + range: `${range}!A:A`, + values: values.map(val => ({ values: val })), + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + queryParams: { + valueInputOption, + }, + }; + + return httpClient.sendRequest(request); +} + +type AppendGoogleSheetValuesParams = { + values: string[]; + spreadSheetId: string; + range: string; + valueInputOption: ValueInputOption; + majorDimension: Dimension; + accessToken: string; +}; diff --git a/packages/pieces/community/google-sheets/src/lib/actions/update-multiple-rows.ts b/packages/pieces/community/google-sheets/src/lib/actions/update-multiple-rows.ts new file mode 100644 index 0000000..348b89f --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/update-multiple-rows.ts @@ -0,0 +1,156 @@ +import { googleSheetsAuth } from '../../index'; +import { + createAction, + DynamicPropsValue, + OAuth2PropertyValue, + Property, +} from '@activepieces/pieces-framework'; +import { areSheetIdsValid, Dimension, googleSheetsCommon, objectToArray, ValueInputOption } from '../common/common'; +import { getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { isNil, isString, MarkdownVariant } from '@activepieces/shared'; +import { getWorkSheetName } from '../triggers/helpers'; +import { google, sheets_v4 } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { commonProps } from '../common/props'; + +export const updateMultipleRowsAction = createAction({ + auth: googleSheetsAuth, + name: 'update-multiple-rows', + displayName: 'Update Multiple Rows', + description: 'Updates multiple rows in a specific spreadsheet.', + props: { + ...commonProps, + values: Property.DynamicProperties({ + displayName: 'Values', + description: 'The values to update.', + required: true, + refreshers: ['sheetId', 'spreadsheetId'], + props: async ({ auth, spreadsheetId, sheetId }) => { + const sheet_Id = Number(sheetId); + const spreadsheet_Id = spreadsheetId as unknown as string; + const authentication = auth as OAuth2PropertyValue; + + if ( + !auth || + (spreadsheet_Id ?? '').toString().length === 0 || + (sheet_Id ?? '').toString().length === 0 + ) { + return {}; + } + + const fields: DynamicPropsValue = {}; + + const headers = await googleSheetsCommon.getGoogleSheetRows({ + spreadsheetId: spreadsheet_Id, + accessToken: getAccessTokenOrThrow(authentication), + sheetId: sheet_Id, + rowIndex_s: 1, + rowIndex_e: 1, + }); + const firstRow = headers[0].values ?? {}; + + //check for empty headers + if (Object.keys(firstRow).length === 0) { + fields['markdown'] = Property.MarkDown({ + value: `We couldn't find any headers in the selected spreadsheet or worksheet. Please add headers to the sheet and refresh the page to reflect the columns.`, + variant: MarkdownVariant.INFO, + }); + } else { + const columns: { + [key: string]: any; + } = { + rowId: Property.Number({ + displayName: 'Row Id', + description: 'The row id to update', + required: true, + }), + }; + + for (const key in firstRow) { + columns[key] = Property.ShortText({ + displayName: firstRow[key].toString(), + description: firstRow[key].toString(), + required: false, + defaultValue: '', + }); + } + fields['values'] = Property.Array({ + displayName: 'Values', + required: false, + properties: columns, + }); + } + + return fields; + }, + }), + as_string: Property.Checkbox({ + displayName: 'As String', + description: + 'Inserted values that are dates and formulas will be entered as strings and have no effect', + required: false, + }), + }, + async run(context) { + const { + spreadsheetId:inputSpreadsheetId, + sheetId:inputSheetId, + values: { values: rowValuesInput }, + as_string: asString, + } = context.propsValue; + + if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + const sheetId = Number(inputSheetId); + const spreadsheetId = inputSpreadsheetId as string; + + const sheetName = await getWorkSheetName(context.auth, spreadsheetId, sheetId); + const valueInputOption = asString ? ValueInputOption.RAW : ValueInputOption.USER_ENTERED; + + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + const sheets = google.sheets({ version: 'v4', auth: authClient }); + + const values: sheets_v4.Schema$ValueRange[] = []; + + for (const row of rowValuesInput) { + const { rowId, ...rowValues } = row; + if (rowId === undefined || rowId === null) { + continue; + } + + const formattedValues = objectToArray(rowValues).map((value: string | null | undefined) => { + if (value === '' || value === null || value === undefined) { + return null; + } + if (isString(value)) { + return value; + } + return JSON.stringify(value, null, 2); + }); + + if (formattedValues.length === 0) { + continue; + } + + values.push({ + range: `${sheetName}!A${rowId}:ZZZ${rowId}`, + majorDimension: Dimension.ROWS, + values: [formattedValues], + }); + } + + const response = await sheets.spreadsheets.values.batchUpdate({ + spreadsheetId: spreadsheetId, + + requestBody: { + valueInputOption: valueInputOption, + data: values, + }, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/update-row.ts b/packages/pieces/community/google-sheets/src/lib/actions/update-row.ts new file mode 100644 index 0000000..a30d1bb --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/update-row.ts @@ -0,0 +1,100 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { areSheetIdsValid, Dimension, objectToArray, ValueInputOption } from '../common/common'; +import { googleSheetsAuth } from '../..'; +import { getWorkSheetName } from '../triggers/helpers'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { isString } from '@activepieces/shared'; +import { commonProps, rowValuesProp } from '../common/props'; + +export const updateRowAction = createAction({ + auth: googleSheetsAuth, + name: 'update_row', + description: 'Overwrite values in an existing row', + displayName: 'Update Row', + props: { + ...commonProps, + row_id: Property.Number({ + displayName: 'Row Number', + description: 'The row number to update', + required: true, + }), + first_row_headers: Property.Checkbox({ + displayName: 'Does the first row contain headers?', + description: 'If the first row is headers', + required: true, + defaultValue: false, + }), + values: rowValuesProp(), + }, + async run(context) { + const inputSpreadsheetId = context.propsValue.spreadsheetId; + const inputSheetId = context.propsValue.sheetId; + const rowId = context.propsValue.row_id; + const isFirstRowHeaders = context.propsValue.first_row_headers; + const rowValuesInput = context.propsValue.values; + + if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + const sheetId = Number(inputSheetId); + const spreadsheetId = inputSpreadsheetId as string; + + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + + const sheets = google.sheets({ version: 'v4', auth: authClient }); + + const sheetName = await getWorkSheetName( + context.auth, + spreadsheetId, + sheetId + ); + + // replace empty string with null to skip the cell value + const formattedValues = ( + isFirstRowHeaders + ? objectToArray(rowValuesInput) + : rowValuesInput['values'] + ).map((value: string | null | undefined) => { + if (value === '' || value === null || value === undefined) { + return null; + } + if (isString(value)) { + return value; + } + return JSON.stringify(value, null, 2); + }); + + + if (formattedValues.length > 0) { + const response = await sheets.spreadsheets.values.update({ + range: `${sheetName}!A${rowId}:ZZZ${rowId}`, + spreadsheetId: spreadsheetId, + valueInputOption: ValueInputOption.USER_ENTERED, + requestBody: { + values: [formattedValues], + majorDimension: Dimension.ROWS, + }, + }); + + //Split the updatedRange string to extract the row number + const updatedRangeParts = response.data.updatedRange?.split( + '!' + ); + const updatedRowRange = updatedRangeParts?.[1]; + const updatedRowNumber = parseInt( + updatedRowRange?.split(':')[0].substring(1) ?? '0', + 10 + ); + + return { updates: { ...response.data }, row: updatedRowNumber }; + } else { + throw Error( + 'Values passed are empty or not array ' + + JSON.stringify(formattedValues) + ); + } + }, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/common/common.ts b/packages/pieces/community/google-sheets/src/lib/common/common.ts new file mode 100644 index 0000000..5dbda72 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/common/common.ts @@ -0,0 +1,262 @@ +import { PiecePropValueSchema } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, + HttpRequest, +} from '@activepieces/pieces-common'; +import { isNil, isString } from '@activepieces/shared'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { googleSheetsAuth } from '../../'; +import { transformWorkSheetValues } from '../triggers/helpers'; + +export const googleSheetsCommon = { + baseUrl: 'https://sheets.googleapis.com/v4/spreadsheets', + getGoogleSheetRows, + findSheetName, + deleteRow, + clearSheet, + getHeaderRow, +}; + +export async function findSheetName( + access_token: string, + spreadsheetId: string, + sheetId: string | number, +) { + const sheets = await listSheetsName(access_token, spreadsheetId); + // don't use === because sheetId can be a string when dynamic values are used + const sheetName = sheets.find((f) => f.properties.sheetId == sheetId)?.properties.title; + if (!sheetName) { + throw Error(`Sheet with ID ${sheetId} not found in spreadsheet ${spreadsheetId}`); + } + return sheetName; +} + +async function listSheetsName(access_token: string, spreadsheet_id: string) { + return ( + await httpClient.sendRequest<{ + sheets: { properties: { title: string; sheetId: number } }[]; + }>({ + method: HttpMethod.GET, + url: `https://sheets.googleapis.com/v4/spreadsheets/` + spreadsheet_id, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: access_token, + }, + }) + ).body.sheets; +} + +type GetGoogleSheetRowsProps = { + spreadsheetId: string; + accessToken: string; + sheetId: number; + rowIndex_s: number | undefined; + rowIndex_e: number | undefined; +}; + +async function getGoogleSheetRows({ + spreadsheetId, + accessToken, + sheetId, + rowIndex_s, + rowIndex_e, +}: GetGoogleSheetRowsProps): Promise<{ row: number; values: { [x: string]: string } }[]> { + // Define the API endpoint and headers + // Send the API request + const sheetName = await findSheetName(accessToken, spreadsheetId, sheetId); + if (!sheetName) { + return []; + } + + let range = ''; + if (rowIndex_s !== undefined) { + range = `!A${rowIndex_s}:ZZZ`; + } + if (rowIndex_s !== undefined && rowIndex_e !== undefined) { + range = `!A${rowIndex_s}:ZZZ${rowIndex_e}`; + } + const rowsResponse = await httpClient.sendRequest<{ values: [string[]][] }>({ + method: HttpMethod.GET, + url: `${googleSheetsCommon.baseUrl}/${spreadsheetId}/values/${encodeURIComponent(`${sheetName}${range}`)}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }); + if (rowsResponse.body.values === undefined) return []; + + const headerResponse = await httpClient.sendRequest<{ values: [string[]][] }>({ + method: HttpMethod.GET, + url: `${googleSheetsCommon.baseUrl}/${spreadsheetId}/values/${encodeURIComponent(`${sheetName}!A1:ZZZ1`)}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }); + + const headers = headerResponse.body.values[0] ?? []; + const headerCount = headers.length; + + const startingRow = rowIndex_s ? rowIndex_s - 1 : 0; + + const labeledRowValues = transformWorkSheetValues( + rowsResponse.body.values, + startingRow, + headerCount, + ); + + return labeledRowValues; +} + +type GetHeaderRowProps = { + spreadsheetId: string; + accessToken: string; + sheetId: number; +}; + +export async function getHeaderRow({ + spreadsheetId, + accessToken, + sheetId, +}: GetHeaderRowProps): Promise { + const rows = await getGoogleSheetRows({ + spreadsheetId, + accessToken, + sheetId, + rowIndex_s: 1, + rowIndex_e: 1, + }); + if (rows.length === 0) { + return undefined; + } + return objectToArray(rows[0].values); +} + +export const columnToLabel = (columnIndex: number) => { + const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + let label = ''; + + while (columnIndex >= 0) { + label = alphabet[columnIndex % 26] + label; + columnIndex = Math.floor(columnIndex / 26) - 1; + } + + return label; +}; +export const labelToColumn = (label: string) => { + const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + let column = 0; + + for (let i = 0; i < label.length; i++) { + column += (alphabet.indexOf(label[i]) + 1) * Math.pow(26, label.length - i - 1); + } + + return column - 1; +}; + +export function objectToArray(obj: { [x: string]: any }) { + const maxIndex = Math.max(...Object.keys(obj).map((key) => labelToColumn(key))); + const arr = new Array(maxIndex + 1); + for (const key in obj) { + arr[labelToColumn(key)] = obj[key]; + } + return arr; +} + +export function stringifyArray(object: unknown[]): string[] { + return object.map((val) => { + if (isString(val)) { + return val; + } + return JSON.stringify(val); + }); +} + +async function deleteRow( + spreadsheetId: string, + sheetId: number, + rowIndex: number, + accessToken: string, +) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${googleSheetsCommon.baseUrl}/${spreadsheetId}/:batchUpdate`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + body: { + requests: [ + { + deleteDimension: { + range: { + sheetId: sheetId, + dimension: 'ROWS', + startIndex: rowIndex, + endIndex: rowIndex + 1, + }, + }, + }, + ], + }, + }; + await httpClient.sendRequest(request); +} + +async function clearSheet( + spreadsheetId: string, + sheetId: number, + accessToken: string, + rowIndex: number, + numOfRows: number, +) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${googleSheetsCommon.baseUrl}/${spreadsheetId}/:batchUpdate`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + body: { + requests: [ + { + deleteDimension: { + range: { + sheetId: sheetId, + dimension: 'ROWS', + startIndex: rowIndex, + endIndex: rowIndex + numOfRows + 1, + }, + }, + }, + ], + }, + }; + return await httpClient.sendRequest(request); +} + +export enum ValueInputOption { + RAW = 'RAW', + USER_ENTERED = 'USER_ENTERED', +} + +export enum Dimension { + ROWS = 'ROWS', + COLUMNS = 'COLUMNS', +} + +export async function createGoogleSheetClient(auth: PiecePropValueSchema) { + const authClient = new OAuth2Client(); + authClient.setCredentials(auth); + + const googleSheetClient = google.sheets({ version: 'v4', auth: authClient }); + return googleSheetClient; +} + +export function areSheetIdsValid(spreadsheetId: string | null | undefined, sheetId: string | number | null | undefined): boolean { + return !isNil(spreadsheetId) && spreadsheetId !== "" && + !isNil(sheetId) && sheetId !== ""; +} \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/lib/common/props.ts b/packages/pieces/community/google-sheets/src/lib/common/props.ts new file mode 100644 index 0000000..240e9dd --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/common/props.ts @@ -0,0 +1,263 @@ +import { googleSheetsAuth } from '../../index'; +import { DropdownOption, PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { google, drive_v3 } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { columnToLabel, getHeaderRow, googleSheetsCommon } from './common'; +import { getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; + +export const includeTeamDrivesProp = () => + Property.Checkbox({ + displayName: 'Include Team Drive Sheets ?', + description: 'Determines if sheets from Team Drives should be included in the results.', + defaultValue: false, + required: false, + }); + +export const spreadsheetIdProp = (displayName: string, description: string, required = true) => + Property.Dropdown({ + displayName, + description, + required, + refreshers: ['includeTeamDrives'], + options: async ({ auth, includeTeamDrives }, { searchValue }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + } + const authValue = auth as PiecePropValueSchema; + + const authClient = new OAuth2Client(); + authClient.setCredentials(authValue); + + const drive = google.drive({ version: 'v3', auth: authClient }); + + const q = ["mimeType='application/vnd.google-apps.spreadsheet'", 'trashed = false']; + + if (searchValue) { + q.push(`name contains '${searchValue}'`); + } + + let nextPageToken; + const options: DropdownOption[] = []; + do { + const response: any = await drive.files.list({ + q: q.join(' and '), + pageToken: nextPageToken, + orderBy: 'createdTime desc', + fields: 'nextPageToken, files(id, name)', + supportsAllDrives: true, + includeItemsFromAllDrives: includeTeamDrives ? true : false, + }); + const fileList: drive_v3.Schema$FileList = response.data; + + if (fileList.files) { + for (const file of fileList.files) { + options.push({ + label: file.name!, + value: file.id!, + }); + } + } + nextPageToken = response.data.nextPageToken; + } while (nextPageToken); + + return { + disabled: false, + options, + }; + }, + }); + +export const sheetIdProp = (displayName: string, description: string, required = true) => + Property.Dropdown({ + displayName, + description, + required, + refreshers: ['spreadsheetId'], + options: async ({ auth, spreadsheetId }) => { + if (!auth || (spreadsheetId ?? '').toString().length === 0) { + return { + disabled: true, + options: [], + placeholder: 'Please select a spreadsheet first.', + }; + } + + const authValue = auth as PiecePropValueSchema; + + const authClient = new OAuth2Client(); + authClient.setCredentials(authValue); + + const sheets = google.sheets({ version: 'v4', auth: authClient }); + + const response = await sheets.spreadsheets.get({ + spreadsheetId: spreadsheetId as unknown as string, + }); + + const sheetsData = response.data.sheets ?? []; + + const options: DropdownOption[] = []; + + for (const sheet of sheetsData) { + const title = sheet.properties?.title; + const sheetId = sheet.properties?.sheetId; + if(isNil(title) || isNil(sheetId)){ + continue; + } + options.push({ + label: title, + value: sheetId, + }); + } + + return { + disabled: false, + options, + }; + }, + }); + +export const commonProps = { + includeTeamDrives: includeTeamDrivesProp(), + spreadsheetId: spreadsheetIdProp('Spreadsheet', 'The ID of the spreadsheet to use.'), + sheetId: sheetIdProp('Sheet', 'The ID of the sheet to use.'), +}; + +export const rowValuesProp = () => + Property.DynamicProperties({ + displayName: 'Values', + description: 'The values to insert', + required: true, + refreshers: ['sheetId', 'spreadsheetId', 'first_row_headers'], + props: async ({ auth, spreadsheetId, sheetId, first_row_headers }) => { + if ( + !auth || + (spreadsheetId ?? '').toString().length === 0 || + (sheetId ?? '').toString().length === 0 + ) { + return {}; + } + const sheet_id = Number(sheetId); + const authValue = auth as PiecePropValueSchema; + + const headers = await googleSheetsCommon.getHeaderRow({ + spreadsheetId: spreadsheetId as unknown as string, + accessToken: getAccessTokenOrThrow(authValue), + sheetId: sheet_id, + }); + + if (!first_row_headers) { + return { + values: Property.Array({ + displayName: 'Values', + required: true, + }), + }; + } + const firstRow = headers ?? []; + const properties: { + [key: string]: any; + } = {}; + + for (let i = 0; i < firstRow.length; i++) { + const label = columnToLabel(i); + properties[label] = Property.ShortText({ + displayName: firstRow[i].toString(), + description: firstRow[i].toString(), + required: false, + defaultValue: '', + }); + } + return properties; + }, + }); + +export const columnNameProp = () => + Property.Dropdown({ + description: 'Column Name', + displayName: 'The name of the column to search in', + required: true, + refreshers: ['sheetId', 'spreadsheetId'], + options: async ({ auth, spreadsheetId, sheetId }) => { + const authValue = auth as PiecePropValueSchema; + const spreadsheet_id = spreadsheetId as string; + const sheet_id = Number(sheetId) as number; + const accessToken = authValue.access_token; + + if ( + !auth || + (spreadsheet_id ?? '').toString().length === 0 || + (sheet_id ?? '').toString().length === 0 + ) { + return { + disabled: true, + options: [], + placeholder: 'Please select a sheet first', + }; + } + + const sheetName = await googleSheetsCommon.findSheetName( + accessToken, + spreadsheet_id, + sheet_id, + ); + + if (!sheetName) { + throw Error('Sheet not found in spreadsheet'); + } + + const headers = await getHeaderRow({ + spreadsheetId: spreadsheet_id, + accessToken: accessToken, + sheetId: sheet_id, + }); + + const ret = []; + + const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + if (isNil(headers)) { + return { + options: [], + disabled: false, + }; + } + if (headers.length === 0) { + const columnSize = headers.length; + + for (let i = 0; i < columnSize; i++) { + ret.push({ + label: alphabet[i].toUpperCase(), + value: alphabet[i], + }); + } + } else { + let index = 0; + for (let i = 0; i < headers.length; i++) { + let value = 'A'; + if (index >= alphabet.length) { + // if the index is greater than the length of the alphabet, we need to add another letter + const firstLetter = alphabet[Math.floor(index / alphabet.length) - 1]; + const secondLetter = alphabet[index % alphabet.length]; + value = firstLetter + secondLetter; + } else { + value = alphabet[index]; + } + + ret.push({ + label: headers[i].toString(), + value: value, + }); + index++; + } + } + return { + options: ret, + disabled: false, + }; + }, + }); diff --git a/packages/pieces/community/google-sheets/src/lib/triggers/helpers.ts b/packages/pieces/community/google-sheets/src/lib/triggers/helpers.ts new file mode 100644 index 0000000..615da49 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/triggers/helpers.ts @@ -0,0 +1,153 @@ +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; + +import { googleSheetsAuth } from '../..'; +import { PiecePropValueSchema } from '@activepieces/pieces-framework'; + +import { nanoid } from 'nanoid'; +import dayjs from 'dayjs'; +import crypto from 'crypto'; +import { columnToLabel } from '../common/common'; + +export async function getWorkSheetName( + auth: PiecePropValueSchema, + spreadSheetId: string, + sheetId: number, +) { + const authClient = new OAuth2Client(); + authClient.setCredentials(auth); + + const sheets = google.sheets({ version: 'v4', auth: authClient }); + + const res = await sheets.spreadsheets.get({ spreadsheetId: spreadSheetId }); + const sheetName = res.data.sheets?.find((f) => f.properties?.sheetId == sheetId)?.properties + ?.title; + + if (!sheetName) { + throw Error(`Sheet with ID ${sheetId} not found in spreadsheet ${spreadSheetId}`); + } + + return sheetName; +} + +export async function getWorkSheetGridSize( + auth: PiecePropValueSchema, + spreadSheetId: string, + sheetId: number, +) { + const authClient = new OAuth2Client(); + authClient.setCredentials(auth); + + const sheets = google.sheets({ version: 'v4', auth: authClient }); + + const res = await sheets.spreadsheets.get({ spreadsheetId: spreadSheetId, includeGridData: true, fields: 'sheets.properties(sheetId,title,sheetType,gridProperties)' }); + const sheetRange = res.data.sheets?.find((f) => f.properties?.sheetId == sheetId)?.properties?.gridProperties; + + if (!sheetRange) { + throw Error(`Unable to get grid size for sheet ${sheetId} in spreadsheet ${spreadSheetId}`); + } + + return sheetRange +} + +export async function getWorkSheetValues( + auth: PiecePropValueSchema, + spreadsheetId: string, + range?: string, +) { + const authClient = new OAuth2Client(); + authClient.setCredentials(auth); + + const sheets = google.sheets({ version: 'v4', auth: authClient }); + + const res = await sheets.spreadsheets.values.get({ + spreadsheetId: spreadsheetId, + range: range, + }); + + return res.data.values ?? []; +} + +export async function createFileNotification( + auth: PiecePropValueSchema, + fileId: string, + url: string, + includeTeamDrives?: boolean, +) { + const authClient = new OAuth2Client(); + authClient.setCredentials(auth); + + const drive = google.drive({ version: 'v3', auth: authClient }); + + // create unique UUID for channel + const channelId = nanoid(); + return await drive.files.watch({ + fileId: fileId, + supportsAllDrives: includeTeamDrives, + requestBody: { + id: channelId, + expiration: (dayjs().add(6, 'day').unix() * 1000).toString(), + type: 'web_hook', + address: url, + }, + }); +} + +export async function deleteFileNotification( + auth: PiecePropValueSchema, + channelId: string, + resourceId: string, +) { + const authClient = new OAuth2Client(); + authClient.setCredentials(auth); + + const drive = google.drive({ version: 'v3', auth: authClient }); + + return await drive.channels.stop({ + requestBody: { + id: channelId, + resourceId: resourceId, + }, + }); +} + +export function isSyncMessage(headers: Record) { + return headers['x-goog-resource-state'] === 'sync'; +} + +export function isChangeContentMessage(headers: Record) { + // https://developers.google.com/drive/api/guides/push#respond-to-notifications + return ( + headers['x-goog-resource-state'] === 'update' && + ['content', 'properties', 'content,properties'].includes(headers['x-goog-changed']) + ); +} + +export function hashObject(obj: Record): string { + const hash = crypto.createHash('sha256'); + hash.update(JSON.stringify(obj)); + return hash.digest('hex'); +} + +export function transformWorkSheetValues(rowValues: any[][], oldRowCount: number,headerCount: number) { + const result = []; + for (let i = 0; i < rowValues.length; i++) { + const values: any = {}; + for (let j = 0; j < Math.max(headerCount,rowValues[i].length) ; j++) { + values[columnToLabel(j)] = rowValues[i][j] ?? ""; + } + result.push({ + row: oldRowCount + i + 1, + values, + }); + } + return result; +} + +export interface WebhookInformation { + kind?: string | null; + id?: string | null; + resourceId?: string | null; + resourceUri?: string | null; + expiration?: string | null; +} diff --git a/packages/pieces/community/google-sheets/src/lib/triggers/new-or-updated-row.trigger.ts b/packages/pieces/community/google-sheets/src/lib/triggers/new-or-updated-row.trigger.ts new file mode 100644 index 0000000..d9f8dbb --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/triggers/new-or-updated-row.trigger.ts @@ -0,0 +1,294 @@ +import { isNil } from '@activepieces/shared'; +import { googleSheetsAuth } from '../../'; +import { areSheetIdsValid, columnToLabel, labelToColumn } from '../common/common'; +import { + createFileNotification, + deleteFileNotification, + getWorkSheetName, + getWorkSheetValues, + hashObject, + isChangeContentMessage, + isSyncMessage, + transformWorkSheetValues, + WebhookInformation, +} from './helpers'; + +import { + createTrigger, + TriggerStrategy, + DEDUPE_KEY_PROPERTY, + WebhookRenewStrategy, + Property, + PiecePropValueSchema, + DropdownOption, +} from '@activepieces/pieces-framework'; + +import crypto from 'crypto'; +import { commonProps } from '../common/props'; + +const ALL_COLUMNS = 'all_columns'; + +export const newOrUpdatedRowTrigger = createTrigger({ + auth: googleSheetsAuth, + name: 'google-sheets-new-or-updated-row', + displayName: 'New or Updated Row', + description: 'Triggers when a new row is added or modified in a spreadsheet.', + props: { + info: Property.MarkDown({ + value: + 'Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.', + }), + ...commonProps, + trigger_column: Property.Dropdown({ + displayName: 'Trigger Column', + description: `Trigger on changes to cells in this column only.Select **All Columns** if you want the flow to trigger on changes to any cell within the row.`, + required: false, + refreshers: ['spreadsheetId', 'sheetId'], + options: async ({ auth, spreadsheetId, sheetId }) => { + if (!auth || !spreadsheetId || isNil(sheetId)) { + return { + disabled: true, + options: [], + placeholder: `Please select sheet first`, + }; + } + + const authValue = auth as PiecePropValueSchema; + const spreadsheet_id = spreadsheetId as string; + const sheet_id = sheetId as number; + + const sheetName = await getWorkSheetName(authValue, spreadsheet_id, sheet_id); + + const firstRowValues = await getWorkSheetValues( + authValue, + spreadsheet_id, + `${sheetName}!1:1`, + ); + + const headers = firstRowValues[0] ?? []; + const headerCount = headers.length; + const labeledRowValues = transformWorkSheetValues(firstRowValues, 0, headerCount); + + const options: DropdownOption[] = [{ label: 'All Columns', value: ALL_COLUMNS }]; + + Object.entries(labeledRowValues[0].values).forEach(([key, value]) => { + options.push({ label: value as string, value: key }); + }); + + return { + disabled: false, + options, + }; + }, + }), + }, + + renewConfiguration: { + strategy: WebhookRenewStrategy.CRON, + cronExpression: '0 */12 * * *', + }, + + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const inputSpreadsheetId = context.propsValue.spreadsheetId; + const inputSheetId = context.propsValue.sheetId; + const triggerColumn = context.propsValue.trigger_column ?? ALL_COLUMNS; + + if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + const sheetId = Number(inputSheetId); + const spreadsheetId = inputSpreadsheetId as string; + + const sheetName = await getWorkSheetName(context.auth, spreadsheetId, sheetId); + + const sheetValues = await getWorkSheetValues(context.auth, spreadsheetId, sheetName); + + const rowHashes = []; + + // create initial row level hashes and used it to check updated row + for (const row of sheetValues) { + let targetValue; + if (triggerColumn === ALL_COLUMNS) { + targetValue = row; + } else { + const currentTriggerColumnValue = row[labelToColumn(triggerColumn)]; + + targetValue = + currentTriggerColumnValue !== undefined && currentTriggerColumnValue !== '' // if column value is empty + ? [currentTriggerColumnValue] + : []; + } + + const rowHash = crypto.createHash('md5').update(JSON.stringify(targetValue)).digest('hex'); + rowHashes.push(rowHash); + } + + // store compressed values + await context.store.put(`${sheetId}`, rowHashes); + + // create file watch notification + const fileNotificationRes = await createFileNotification( + context.auth, + spreadsheetId, + context.webhookUrl, + context.propsValue.includeTeamDrives, + ); + + await context.store.put( + 'google-sheets-new-or-updated-row', + fileNotificationRes.data, + ); + }, + + async onDisable(context) { + const webhook = await context.store.get('google-sheets-new-or-updated-row'); + + if (webhook != null && webhook.id != null && webhook.resourceId != null) { + await deleteFileNotification(context.auth, webhook.id, webhook.resourceId); + } + }, + + async run(context) { + if (isSyncMessage(context.payload.headers)) { + return []; + } + + if (!isChangeContentMessage(context.payload.headers)) { + return []; + } + + const inputSpreadsheetId = context.propsValue.spreadsheetId; + const inputSheetId = context.propsValue.sheetId; + const triggerColumn = context.propsValue.trigger_column ?? ALL_COLUMNS; + + if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + const sheetId = Number(inputSheetId); + const spreadsheetId = inputSpreadsheetId as string; + + const sheetName = await getWorkSheetName(context.auth, spreadsheetId, sheetId); + + const oldValuesHashes = (await context.store.get(`${sheetId}`)) as any[]; + + /* Fetch rows values with all columns as this will be used on returning updated/new row data + */ + const currentValues = await getWorkSheetValues(context.auth, spreadsheetId, sheetName); + + const headers = currentValues[0] ?? []; + const headerCount = headers.length; + + // const rowCount = Math.max(oldValuesHashes.length, currentValues.length); + + const changedValues = []; + const newRowHashes = []; + + for (let row = 0; row < currentValues.length; row++) { + const currentRowValue = currentValues[row]; + + /** + * This variable store value based on trigger column. + * If trigger column is all_columns then store entry row as target value, else store only column value. + */ + let targetValue; + if (triggerColumn === ALL_COLUMNS) { + targetValue = currentRowValue; + } else { + const currentTriggerColumnValue = currentRowValue[labelToColumn(triggerColumn)]; + + targetValue = + currentTriggerColumnValue !== undefined && currentTriggerColumnValue !== '' + ? [currentTriggerColumnValue] + : []; + } + + // create hash for new row values + const currentRowHash = crypto + .createHash('md5') + .update(JSON.stringify(targetValue)) + .digest('hex'); + newRowHashes.push(currentRowHash); + + // If row is empty then skip + if (currentRowValue === undefined || currentRowValue.length === 0) { + continue; + } + + const oldRowHash = + !isNil(oldValuesHashes) && row < oldValuesHashes.length ? oldValuesHashes[row] : undefined; + + if (oldRowHash === undefined || oldRowHash != currentRowHash) { + const formattedValues: any = {}; + + for (let column = 0; column < headerCount; column++) { + formattedValues[columnToLabel(column)] = currentValues[row][column] ?? ''; + } + + changedValues.push({ + row: row + 1, + values: formattedValues, + }); + } + } + + // update the row hashes + await context.store.put(`${sheetId}`, newRowHashes); + + return changedValues.map((row) => { + return { + ...row, + [DEDUPE_KEY_PROPERTY]: hashObject(row), + }; + }); + }, + + async test(context) { + const inputSpreadsheetId = context.propsValue.spreadsheetId; + const inputSheetId = context.propsValue.sheetId; + + if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + const sheetId = Number(inputSheetId); + const spreadsheetId = inputSpreadsheetId as string; + + const sheetName = await getWorkSheetName(context.auth, spreadsheetId, sheetId); + const currentSheetValues = await getWorkSheetValues(context.auth, spreadsheetId, sheetName); + + const headers = currentSheetValues[0] ?? []; + const headerCount = headers.length; + + // transform row values + const transformedRowValues = transformWorkSheetValues(currentSheetValues, 0, headerCount) + .slice(-5) + .reverse(); + + return transformedRowValues; + }, + + async onRenew(context) { + // get current channel ID & resource ID + const webhook = await context.store.get(`google-sheets-new-or-updated-row`); + if (webhook != null && webhook.id != null && webhook.resourceId != null) { + // delete current channel + await deleteFileNotification(context.auth, webhook.id, webhook.resourceId); + const fileNotificationRes = await createFileNotification( + context.auth, + context.propsValue.spreadsheetId!, + context.webhookUrl, + context.propsValue.includeTeamDrives, + ); + // store channel response + await context.store.put( + 'google-sheets-new-or-updated-row', + fileNotificationRes.data, + ); + } + }, + + sampleData: {}, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/triggers/new-row-added-webhook.ts b/packages/pieces/community/google-sheets/src/lib/triggers/new-row-added-webhook.ts new file mode 100644 index 0000000..7e059cf --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/triggers/new-row-added-webhook.ts @@ -0,0 +1,192 @@ +import { + DEDUPE_KEY_PROPERTY, + PiecePropValueSchema, + Property, + TriggerStrategy, + WebhookRenewStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; + +import { + createFileNotification, + deleteFileNotification, + getWorkSheetName, + getWorkSheetValues, + hashObject, + isChangeContentMessage, + isSyncMessage, + transformWorkSheetValues, + WebhookInformation, +} from './helpers'; + +import { googleSheetsAuth } from '../..'; +import { commonProps } from '../common/props'; +import { isNil } from '@activepieces/shared'; +import { areSheetIdsValid } from '../common/common'; + +export const newRowAddedTrigger = createTrigger({ + auth: googleSheetsAuth, + name: 'googlesheets_new_row_added', + displayName: 'New Row Added', + description: 'Triggers when a new row is added to bottom of a spreadsheet.', + props: { + info: Property.MarkDown({ + value: + 'Please note that there might be a delay of up to 3 minutes for the trigger to be fired, due to a delay from Google.', + }), + ...commonProps, + }, + renewConfiguration: { + strategy: WebhookRenewStrategy.CRON, + cronExpression: '0 */12 * * *', + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const { spreadsheetId:inputSpreadsheetId, sheetId:inputSheetId } = context.propsValue; + + if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + const sheetId = Number(inputSheetId); + const spreadsheetId = inputSpreadsheetId as string; + + // fetch current sheet values + const sheetName = await getWorkSheetName(context.auth, spreadsheetId, sheetId); + const currentSheetValues = await getWorkSheetValues(context.auth, spreadsheetId, sheetName); + + // store current sheet row count + await context.store.put(`${sheetId}`, currentSheetValues.length); + + const fileNotificationRes = await createFileNotification( + context.auth, + spreadsheetId, + context.webhookUrl, + context.propsValue.includeTeamDrives, + ); + + // store channel response + await context.store.put( + 'googlesheets_new_row_added', + fileNotificationRes.data, + ); + }, + async onDisable(context) { + const webhook = await context.store.get(`googlesheets_new_row_added`); + if (webhook != null && webhook.id != null && webhook.resourceId != null) { + await deleteFileNotification(context.auth, webhook.id, webhook.resourceId); + } + }, + async run(context) { + // check if notification is a sync message + if (isSyncMessage(context.payload.headers)) { + return []; + } + if (!isChangeContentMessage(context.payload.headers)) { + return []; + } + + const { spreadsheetId:inputSpreadsheetId, sheetId:inputSheetId } = context.propsValue; + + if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + const sheetId = Number(inputSheetId); + const spreadsheetId = inputSpreadsheetId as string; + + // fetch old row count for worksheet + const oldRowCount = (await context.store.get(`${sheetId}`)) as number; + + // fetch current row count for worksheet + const sheetName = await getWorkSheetName(context.auth, spreadsheetId, sheetId); + const currentRowValues = await getWorkSheetValues(context.auth, spreadsheetId, sheetName); + const currentRowCount = currentRowValues.length; + + const headers = currentRowValues[0] ?? []; + const headerCount = headers.length; + + // if no new rows return + if (oldRowCount >= currentRowCount) { + if (oldRowCount > currentRowCount) { + // Some rows were deleted + await context.store.put(`${sheetId}`, currentRowCount); + } + return []; + } + + // create A1 notation range for new rows + const range = `${sheetName}!${oldRowCount + 1}:${currentRowCount}`; + + const newRowValues = await getWorkSheetValues( + context.auth as PiecePropValueSchema, + spreadsheetId, + range, + ); + + // update row count value + await context.store.put(`${sheetId}`, currentRowCount); + + // transform row values + const transformedRowValues = transformWorkSheetValues(newRowValues, oldRowCount,headerCount); + return transformedRowValues.map((row) => { + return { + ...row, + [DEDUPE_KEY_PROPERTY]: hashObject(row), + }; + }); + }, + async onRenew(context) { + // get current channel ID & resource ID + const webhook = await context.store.get(`googlesheets_new_row_added`); + + const { spreadsheetId:inputSpreadsheetId, sheetId:inputSheetId } = context.propsValue; + + if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + const sheetId = Number(inputSheetId); + const spreadsheetId = inputSpreadsheetId as string; + + if (webhook != null && webhook.id != null && webhook.resourceId != null) { + // delete current channel + await deleteFileNotification(context.auth, webhook.id, webhook.resourceId); + const fileNotificationRes = await createFileNotification( + context.auth, + spreadsheetId, + context.webhookUrl, + context.propsValue.includeTeamDrives, + ); + // store channel response + await context.store.put( + 'googlesheets_new_row_added', + fileNotificationRes.data, + ); + } + }, + async test(context) { + const { spreadsheetId:inputSpreadsheetId, sheetId:inputSheetId } = context.propsValue; + + if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + const sheetId = Number(inputSheetId); + const spreadsheetId = inputSpreadsheetId as string; + + const sheetName = await getWorkSheetName(context.auth, spreadsheetId, sheetId); + const currentSheetValues = await getWorkSheetValues(context.auth, spreadsheetId, sheetName); + + const headers = currentSheetValues[0] ?? []; + const headerCount = headers.length; + + // transform row values + const transformedRowValues = transformWorkSheetValues(currentSheetValues, 0,headerCount) + .slice(-5) + .reverse(); + + return transformedRowValues; + }, + sampleData: {}, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/triggers/new-spreadsheet.ts b/packages/pieces/community/google-sheets/src/lib/triggers/new-spreadsheet.ts new file mode 100644 index 0000000..371ecb8 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/triggers/new-spreadsheet.ts @@ -0,0 +1,84 @@ +import { createTrigger,PiecePropValueSchema,TriggerStrategy } from '@activepieces/pieces-framework'; +import { googleSheetsAuth } from '../../index'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import dayjs from 'dayjs'; +import { google, drive_v3 } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { includeTeamDrivesProp } from '../common/props'; +type Props = { + includeTeamDrives?: boolean; +}; +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const authValue = auth as PiecePropValueSchema; + const q = ["mimeType='application/vnd.google-apps.spreadsheet'",'trashed = false']; + if (lastFetchEpochMS) { + q.push(`createdTime > '${dayjs(lastFetchEpochMS).toISOString()}'`); + } + const authClient = new OAuth2Client(); + authClient.setCredentials(authValue); + const drive = google.drive({ version: 'v3', auth: authClient }); + let nextPageToken; + const items = []; + do { + const response: any = await drive.files.list({ + q: q.join(' and '), + fields: '*', + orderBy: 'createdTime desc', + supportsAllDrives: true, + includeItemsFromAllDrives: propsValue.includeTeamDrives, + pageToken: nextPageToken, + }); + const fileList: drive_v3.Schema$FileList = response.data; + if (fileList.files) { + items.push(...fileList.files); + } + if (lastFetchEpochMS === 0) break; + nextPageToken = response.data.nextPageToken; + } while (nextPageToken); + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.createdTime).valueOf(), + data: item, + })); + }, +}; +export const newSpreadsheetTrigger = createTrigger({ + auth: googleSheetsAuth, + name: 'new-spreadsheet', + displayName: 'New Spreadsheet', + description: 'Triggers when a new spreadsheet is created.', + type: TriggerStrategy.POLLING, + props: { + includeTeamDrives: includeTeamDrivesProp() + }, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData:{ + kind: 'drive#file', + mimeType: 'application/vnd.google-apps.spreadsheet', + webViewLink: + 'https://docs.google.com/document/d/1_9xjsrYFgHVvgqYwAJ8KcsDcNU/edit?usp=drivesdk', + id: '1_9xjsrYFgHVvgqYwAJ8KcsDcN3AzPelsux', + name: 'Test Document', + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/google-sheets/src/lib/triggers/new-worksheet.ts b/packages/pieces/community/google-sheets/src/lib/triggers/new-worksheet.ts new file mode 100644 index 0000000..a520cf3 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/triggers/new-worksheet.ts @@ -0,0 +1,100 @@ +import { googleSheetsAuth } from '../../index'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; + +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; +import { isNil } from '@activepieces/shared'; +import { includeTeamDrivesProp, spreadsheetIdProp } from '../common/props'; + +export const newWorksheetTrigger = createTrigger({ + auth: googleSheetsAuth, + name: 'new-worksheet', + displayName: 'New Worksheet', + description: 'Triggers when a worksheet is created in a spreadsheet.', + type: TriggerStrategy.POLLING, + props: { + includeTeamDrives: includeTeamDrivesProp(), + spreadsheetId: spreadsheetIdProp('Spreadsheet', ''), + }, + async onEnable(context) { + const ids: number[] = []; + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + const sheets = google.sheets({ version: 'v4', auth: authClient }); + const response = await sheets.spreadsheets.get({ + spreadsheetId: context.propsValue.spreadsheetId, + }); + if (response.data.sheets) { + for (const sheet of response.data.sheets) { + const sheetId = sheet.properties?.sheetId; + if (sheetId) { + ids.push(sheetId); + } + } + } + await context.store.put('worksheets', JSON.stringify(ids)); + }, + async onDisable(context) { + await context.store.delete('worksheets'); + }, + async test(context) { + const worksheets = []; + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + const sheets = google.sheets({ version: 'v4', auth: authClient }); + const response = await sheets.spreadsheets.get({ + spreadsheetId: context.propsValue.spreadsheetId, + }); + + if (response.data.sheets) { + for (const sheet of response.data.sheets) { + worksheets.push(sheet); + } + } + return worksheets; + }, + async run(context) { + const existingIds = (await context.store.get('worksheets')) ?? '[]'; + const parsedExistingIds = JSON.parse(existingIds) as number[]; + + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + + const sheets = google.sheets({ version: 'v4', auth: authClient }); + + const response = await sheets.spreadsheets.get({ + spreadsheetId: context.propsValue.spreadsheetId, + }); + if (isNil(response.data.sheets) || response.data.sheets.length === 0) { + return []; + } + // Filter valid worksheetss + const newWorksheets = response.data.sheets.filter((sheet) => { + const sheetId = sheet.properties?.sheetId ?? undefined; + return sheetId !== undefined && !parsedExistingIds.includes(sheetId); + }); + + const newIds = newWorksheets + .map((sheet) => sheet.properties?.sheetId ?? undefined) + .filter((id): id is number => id !== undefined); + + if (newIds.length === 0) { + return []; + } + // Store new IDs + await context.store.put('worksheets', JSON.stringify([...newIds, ...parsedExistingIds])); + return newWorksheets; + }, + sampleData: { + properties: { + sheetId: 2077270595, + title: 'Sheet5', + index: 1, + sheetType: 'GRID', + gridProperties: { + rowCount: 1000, + columnCount: 26, + }, + }, + }, +}); diff --git a/packages/pieces/community/google-sheets/tsconfig.json b/packages/pieces/community/google-sheets/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/google-sheets/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/google-sheets/tsconfig.lib.json b/packages/pieces/community/google-sheets/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/google-sheets/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/google-slides/.eslintrc.json b/packages/pieces/community/google-slides/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/google-slides/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/google-slides/README.md b/packages/pieces/community/google-slides/README.md new file mode 100644 index 0000000..b78819e --- /dev/null +++ b/packages/pieces/community/google-slides/README.md @@ -0,0 +1,7 @@ +# pieces-google-slide + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-google-slides` to build the library. diff --git a/packages/pieces/community/google-slides/package.json b/packages/pieces/community/google-slides/package.json new file mode 100644 index 0000000..ca12c80 --- /dev/null +++ b/packages/pieces/community/google-slides/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-google-slides", + "version": "0.0.3" +} diff --git a/packages/pieces/community/google-slides/project.json b/packages/pieces/community/google-slides/project.json new file mode 100644 index 0000000..d43190b --- /dev/null +++ b/packages/pieces/community/google-slides/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-google-slides", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/google-slides/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/google-slides", + "tsConfig": "packages/pieces/community/google-slides/tsconfig.lib.json", + "packageJson": "packages/pieces/community/google-slides/package.json", + "main": "packages/pieces/community/google-slides/src/index.ts", + "assets": [ + "packages/pieces/community/google-slides/*.md", + { + "input": "packages/pieces/community/google-slides/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/google-slides/src/index.ts b/packages/pieces/community/google-slides/src/index.ts new file mode 100644 index 0000000..c167a7e --- /dev/null +++ b/packages/pieces/community/google-slides/src/index.ts @@ -0,0 +1,39 @@ +import { createPiece, PieceAuth, OAuth2PropertyValue } from "@activepieces/pieces-framework"; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { refreshSheetsCharts } from "./lib/actions/refresh-charts"; +import { generateFromTemplate } from "./lib/actions/generate-from-template"; +import { getPresentation } from "./lib/actions/get-presentation"; + +export const googleSlidesAuth = PieceAuth.OAuth2({ + description: '', + + authUrl: 'https://accounts.google.com/o/oauth2/auth', + tokenUrl: 'https://oauth2.googleapis.com/token', + required: true, + scope: [ + 'https://www.googleapis.com/auth/presentations', + 'https://www.googleapis.com/auth/drive', + 'https://www.googleapis.com/auth/spreadsheets', + ], +}); + +export const googleSlide = createPiece({ + displayName: "Google Slides", + auth: googleSlidesAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: "https://cdn.activepieces.com/pieces/google-slides.png", + authors: ["Kevinyu-alan"], + actions: [ + getPresentation, + refreshSheetsCharts, + generateFromTemplate, + createCustomApiCallAction({ + baseUrl: () => 'https://slides.googleapis.com/v1/presentations/', + auth: googleSlidesAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/google-slides/src/lib/actions/generate-from-template.ts b/packages/pieces/community/google-slides/src/lib/actions/generate-from-template.ts new file mode 100644 index 0000000..c939e22 --- /dev/null +++ b/packages/pieces/community/google-slides/src/lib/actions/generate-from-template.ts @@ -0,0 +1,163 @@ +import { googleSlidesAuth } from '../../index'; +import { createAction, DynamicPropsValue, Property } from "@activepieces/pieces-framework"; +import { getSlide, PageElement, batchUpdate, TableCell, TextElement } from '../commons/common'; +import { google } from 'googleapis'; +import { OAuth2Client } from 'googleapis-common'; + +function extractPlaceholders(content: string, fields: Record, placeholder_format: string) { + const regex = placeholder_format === '[[]]' + ? /\[\[([^\]]+)\]\]/g + : /\{\{([^}]+)\}\}/g; + + const matches = content.match(regex); + if (matches) { + matches.forEach((match: string) => { + const matchValue = placeholder_format === '[[]]' + ? match.replace(/[[\]]/g, '') + : match.replace(/[{}]/g, ''); + + const varName = matchValue.trim(); + fields[matchValue] = Property.ShortText({ + displayName: varName.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()), + description: `Value for "${placeholder_format === '[[]]' ? `[[${varName}]]` : `{{${varName}}}`}"`, + required: false, + }); + }); + } +} + +export const generateFromTemplate = createAction({ + name: 'generate_from_template', + displayName: 'Generate from template', + description: 'Generate a new slide from a template', + auth: googleSlidesAuth, + props: { + template_presentation_id: Property.ShortText({ + displayName: 'Template presentation ID', + description: 'The ID of the templated presentation', + required: true, + }), + placeholder_format: Property.StaticDropdown({ + displayName: 'Placeholder Format', + description: 'Choose the format of placeholders in your template', + required: true, + defaultValue: '{{}}', + options: { + disabled: false, + options: [ + { label: 'Curly Braces {{}}', value: '{{}}' }, + { label: 'Square Brackets [[]]', value: '[[]]' } + ], + }, + }), + table_data: Property.DynamicProperties({ + displayName: 'Table Data', + required: true, + refreshers: ['template_presentation_id', 'placeholder_format'], + props: async ({auth, template_presentation_id, placeholder_format}) => { + if (!template_presentation_id || !auth) + return {}; + + const presentation = await getSlide(auth["access_token"] as unknown as string, template_presentation_id as unknown as string); + if (!presentation) + return {} + + const fields = { + title: Property.ShortText({ + displayName: 'Presentation Title', + description: 'Title of the new presentation', + defaultValue: `Copy of: ${presentation.title}`, + required: true, + }) + } as DynamicPropsValue; + + presentation.slides?.forEach(slide => { + slide.pageElements?.forEach((element: PageElement) => { + if (element.shape?.text?.textElements) { + element.shape.text.textElements.forEach(textElement => { + const content = textElement?.textRun?.content; + if (content) { + extractPlaceholders(content, fields, placeholder_format as unknown as string); + } + }); + } + + if (element.table) { + element.table.tableRows?.forEach(row => { + row.tableCells?.forEach((cell: TableCell) => { + if (cell.text?.textElements) { + cell.text.textElements.forEach((textElement: TextElement) => { + const content = textElement?.textRun?.content; + if (content) { + extractPlaceholders(content, fields, placeholder_format as unknown as string); + } + }); + } + }); + }); + } + }); + }); + + return fields; + } + }) + }, + async run(context) { + const { access_token } = context.auth; + const { template_presentation_id, placeholder_format, table_data } = context.propsValue; + + try { + const authClient = new OAuth2Client(); + authClient.setCredentials(context.auth); + + const drive = google.drive({ version: 'v3', auth: authClient }); + + const copyResponse = await drive.files.copy({ + fileId: template_presentation_id as string, + requestBody: { + name: table_data["title"] || "New Presentation" + }, + supportsAllDrives: true + }); + + const newPresentationId = copyResponse.data.id; + if (!newPresentationId) + return + + const requests = Object.entries(table_data) + .map(([key, value]): { replaceAllText: unknown } => { + const placeholder = placeholder_format === '[[]]' + ? `[[${key}]]` + : `{{${key}}}`; + + return { + replaceAllText: { + containsText: { + text: placeholder, + matchCase: true + }, + replaceText: value as string + } + }; + }); + + + if (requests.length > 0) { + await batchUpdate( + access_token, + newPresentationId, + requests + ); + } + + return { + presentationId: newPresentationId, + presentationUrl: `https://docs.google.com/presentation/d/${newPresentationId}/edit` + }; + } catch (error) { + console.error('Error creating presentation:', error); + throw error; + } + } +}); \ No newline at end of file diff --git a/packages/pieces/community/google-slides/src/lib/actions/get-presentation.ts b/packages/pieces/community/google-slides/src/lib/actions/get-presentation.ts new file mode 100644 index 0000000..bd41581 --- /dev/null +++ b/packages/pieces/community/google-slides/src/lib/actions/get-presentation.ts @@ -0,0 +1,22 @@ +import { googleSlidesAuth } from '../../index'; +import { createAction, Property } from "@activepieces/pieces-framework"; +import { getSlide } from "../commons/common"; + +export const getPresentation = createAction({ + name: 'get_presentation', + displayName: 'Get Presentation', + description: 'Get all slides from a presentation', + auth: googleSlidesAuth, + props: { + presentation_id: Property.ShortText({ + displayName: 'Presentation ID', + description: 'The ID of the presentation', + required: true, + }) + }, + async run(context) { + const { presentation_id } = context.propsValue; + const { access_token } = context.auth; + return await getSlide(access_token, presentation_id); + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/google-slides/src/lib/actions/refresh-charts.ts b/packages/pieces/community/google-slides/src/lib/actions/refresh-charts.ts new file mode 100644 index 0000000..bcb5652 --- /dev/null +++ b/packages/pieces/community/google-slides/src/lib/actions/refresh-charts.ts @@ -0,0 +1,51 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { batchUpdate, getSlide, PageElement } from '../commons/common'; +import { googleSlidesAuth } from '../..'; + +export const refreshSheetsCharts = createAction({ + name: 'refresh_sheets_charts', + displayName: 'Refresh Sheets Charts', + description: 'Refresh all Google Sheets charts in the presentation', + auth: googleSlidesAuth, + props: { + presentation_id: Property.ShortText({ + displayName: 'Presentation ID', + description: 'The ID of the presentation, between /d and /edit', + required: true, + }), + }, + async run(context) { + const { presentation_id } = context.propsValue; + const { access_token } = context.auth; + const presentation = await getSlide(access_token, presentation_id); + + const requests: { refreshSheetsChart: { objectId: string; }; }[] = []; + + presentation.slides?.forEach((slide) => { + slide.pageElements?.forEach((element: PageElement) => { + if (element.sheetsChart) { + const refreshRequest = { + refreshSheetsChart: { + objectId: element.objectId + } + }; + requests.push(refreshRequest); + } + }); + }); + + if (requests.length > 0) { + const result = await batchUpdate(access_token, presentation_id, requests); + return { + success: true, + message: `Successfully refreshed ${requests.length} Google Sheets charts`, + result: result + }; + } else { + return { + success: false, + message: 'No Google Sheets charts found in the presentation' + }; + } + } +}); \ No newline at end of file diff --git a/packages/pieces/community/google-slides/src/lib/commons/common.ts b/packages/pieces/community/google-slides/src/lib/commons/common.ts new file mode 100644 index 0000000..571e4bd --- /dev/null +++ b/packages/pieces/community/google-slides/src/lib/commons/common.ts @@ -0,0 +1,113 @@ +import { AuthenticationType, httpClient, HttpMethod } from "@activepieces/pieces-common"; + +export interface PageElement { + objectId: string; + table?: Table + sheetsChart?: { + spreadsheetId: string; + chartId: number; + }; + shape?: Shape +} + +interface Presentation { + slides?: Slide[]; +} + +interface Slide { + pageElements?: PageElement[]; +} + + +interface Shape { + text?: Text; +} + +interface Text { + textElements?: TextElement[]; +} + +export interface TextElement { + textRun?: TextRun; +} + +interface TextRun { + content?: string; +} + +interface Table { + tableRows?: TableRow[]; +} + +interface TableRow { + tableCells?: TableCell[]; +} + +export interface TableCell { + text?: Text; +} + +export const googleSheetsCommon = { + baseUrl: 'https://slides.googleapis.com/v1/presentations/', + batchUpdate, + getSlide, + createSlide +}; + +export async function batchUpdate(access_token: string, slide_id: string, requests: any) { + return ( + await httpClient.sendRequest<{ + spreadsheetId: string; + }>({ + method: HttpMethod.POST, + url: `https://slides.googleapis.com/v1/presentations/${slide_id}:batchUpdate`, + body: { + requests: requests + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: access_token, + }, + }) + ).body; +} + +export async function getSlide(access_token: string, slide_id: string) { + return ( + await httpClient.sendRequest<{ + presentationId: string; + title: string; + slides: { + pageElements: any; objectId: object; +}[]; + }>({ + method: HttpMethod.GET, + url: `https://slides.googleapis.com/v1/presentations/${slide_id}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: access_token, + }, + }) + ).body; +} + +export async function createSlide(access_token: string, requests: any) { + return ( + await httpClient.sendRequest<{ + presentationId: string; + title: string; + spreadsheetId: string; + replies: any[]; + }>({ + method: HttpMethod.POST, + url: `https://slides.googleapis.com/v1/presentations`, + body: { + requests: requests + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: access_token, + }, + }) + ).body; +} \ No newline at end of file diff --git a/packages/pieces/community/google-slides/tsconfig.json b/packages/pieces/community/google-slides/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/google-slides/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/google-slides/tsconfig.lib.json b/packages/pieces/community/google-slides/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/google-slides/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/google-tasks/.babelrc b/packages/pieces/community/google-tasks/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/google-tasks/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/google-tasks/.eslintrc.json b/packages/pieces/community/google-tasks/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/google-tasks/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/google-tasks/README.md b/packages/pieces/community/google-tasks/README.md new file mode 100644 index 0000000..26ab98b --- /dev/null +++ b/packages/pieces/community/google-tasks/README.md @@ -0,0 +1,7 @@ +# pieces-google-tasks + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-google-tasks` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/google-tasks/package.json b/packages/pieces/community/google-tasks/package.json new file mode 100644 index 0000000..eb7fbfe --- /dev/null +++ b/packages/pieces/community/google-tasks/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-google-tasks", + "version": "0.3.7" +} \ No newline at end of file diff --git a/packages/pieces/community/google-tasks/project.json b/packages/pieces/community/google-tasks/project.json new file mode 100644 index 0000000..34c7517 --- /dev/null +++ b/packages/pieces/community/google-tasks/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-google-tasks", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/google-tasks/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/google-tasks", + "tsConfig": "packages/pieces/community/google-tasks/tsconfig.lib.json", + "packageJson": "packages/pieces/community/google-tasks/package.json", + "main": "packages/pieces/community/google-tasks/src/index.ts", + "assets": [ + "packages/pieces/community/google-tasks/*.md", + { + "input": "packages/pieces/community/google-tasks/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/google-tasks/src/index.ts b/packages/pieces/community/google-tasks/src/index.ts new file mode 100644 index 0000000..7964e9f --- /dev/null +++ b/packages/pieces/community/google-tasks/src/index.ts @@ -0,0 +1,41 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { googleTasksAddNewTaskAction } from './lib/actions/new-task'; +import { googleTasksCommon } from './lib/common'; +import { newTaskTrigger } from './lib/triggers/new-task'; + +export const googleTasksAuth = PieceAuth.OAuth2({ + description: '', + + authUrl: 'https://accounts.google.com/o/oauth2/auth', + tokenUrl: 'https://oauth2.googleapis.com/token', + required: true, + scope: ['https://www.googleapis.com/auth/tasks'], +}); + +export const googleTasks = createPiece({ + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/google-tasks.png', + categories: [PieceCategory.PRODUCTIVITY], + actions: [ + googleTasksAddNewTaskAction, + createCustomApiCallAction({ + baseUrl: () => googleTasksCommon.baseUrl, + auth: googleTasksAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + displayName: 'Google Tasks', + description: 'Task list management application', + + authors: ["Salem-Alaa","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + triggers: [newTaskTrigger], + auth: googleTasksAuth, +}); diff --git a/packages/pieces/community/google-tasks/src/lib/actions/new-task.ts b/packages/pieces/community/google-tasks/src/lib/actions/new-task.ts new file mode 100644 index 0000000..e82608d --- /dev/null +++ b/packages/pieces/community/google-tasks/src/lib/actions/new-task.ts @@ -0,0 +1,33 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { createTask, googleTasksCommon, Task, TaskStatus } from '../common'; +import { googleTasksAuth } from '../../'; + +export const googleTasksAddNewTaskAction = createAction({ + auth: googleTasksAuth, + name: 'add_task', + description: 'Add a new task to a specified task list', + displayName: 'Add Task', + props: { + tasks_list: googleTasksCommon.tasksList, + title: googleTasksCommon.title, + notes: googleTasksCommon.notes, + due: googleTasksCommon.due, + completed: googleTasksCommon.completed, + }, + async run({ auth, propsValue }) { + const task: Task = { + kind: 'tasks#task', + status: propsValue.completed + ? TaskStatus.COMPLETED + : TaskStatus.NEEDS_ACTION, + title: propsValue.title, + completed: propsValue.completed ? new Date().toISOString() : '', + notes: propsValue.notes, + due: propsValue.due + ? `${new Date(propsValue.due).toISOString().split('T')[0]}T00:00:00Z` + : undefined, + }; + + return createTask(auth, propsValue.tasks_list!, task); + }, +}); diff --git a/packages/pieces/community/google-tasks/src/lib/common/index.ts b/packages/pieces/community/google-tasks/src/lib/common/index.ts new file mode 100644 index 0000000..239c3a4 --- /dev/null +++ b/packages/pieces/community/google-tasks/src/lib/common/index.ts @@ -0,0 +1,159 @@ +import { Property, OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + AuthenticationType, + httpClient, +} from '@activepieces/pieces-common'; + +export enum TaskStatus { + NEEDS_ACTION = 'needsAction', + COMPLETED = 'completed', +} + +/** + * @see https://developers.google.com/tasks/reference/rest/v1/tasklists/list#response-body + */ +export type TasksListResponse = { + kind: string; + etag: string; + nextPageToken: string; + items: TasksList[]; +}; + +/** + * @see https://developers.google.com/tasks/reference/rest/v1/tasklists#resource:-tasklist + */ +export type TasksList = { + kind: string; + id: string; + etag: string; + title: string; + updated: string; + selfLink: string; +}; + +/** + * @see https://developers.google.com/tasks/reference/rest/v1/tasks#resource:-task + */ +export type Task = { + kind: 'tasks#task'; + title: string; + status: TaskStatus; + notes?: string; + + /** + * *Optional* RFC 3339 timestamp of due date of the task + */ + due?: string; + + /** + * *Optional* RFC 3339 timestamp of completion. + * Filled automatically if `status === 'completed'` + */ + completed?: string; +}; + +export const googleTasksCommon = { + baseUrl: `https://tasks.googleapis.com`, + + /** + * @property Target task list ID where the new task will be created + */ + tasksList: Property.Dropdown({ + displayName: 'Tasks List', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + + const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue; + const tasksLists = await getLists(authProp); + + return { + disabled: false, + options: tasksLists.map((list) => { + return { + label: list.title, + value: list.id, + }; + }), + }; + }, + }), + title: Property.ShortText({ + displayName: 'Title', + required: true, + }), + notes: Property.LongText({ + displayName: 'Notes', + required: false, + }), + due: Property.DateTime({ + displayName: 'Due Date', + description: 'Due date of the task (YYYY-MM-DD)', + required: false, + }), + completed: Property.Checkbox({ + displayName: 'Completed', + description: 'Mark task as completed', + required: false, + }), +}; + +export async function getLists( + authProp: OAuth2PropertyValue +): Promise { + // docs: https://developers.google.com/tasks/reference/rest/v1/tasklists/list + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${googleTasksCommon.baseUrl}/tasks/v1/users/@me/lists`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp.access_token, + }, + }; + const response = await httpClient.sendRequest(request); + return response.body.items; +} + +export async function getTasks( + authProp: OAuth2PropertyValue, + tasklist: string +): Promise { + // docs: https://developers.google.com/tasks/reference/rest/v1/tasklists/list + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${googleTasksCommon.baseUrl}/tasks/v1/lists/${tasklist}/tasks`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp.access_token, + }, + }; + const response = await httpClient.sendRequest(request); + return response.body.items; +} + +export async function createTask( + authProp: OAuth2PropertyValue, + taskListId: string, + task: Task +) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${googleTasksCommon.baseUrl}/tasks/v1/lists/${taskListId}/tasks`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp.access_token, + }, + body: task, + }; + + return httpClient.sendRequest(request); +} diff --git a/packages/pieces/community/google-tasks/src/lib/google-tasks.mdx b/packages/pieces/community/google-tasks/src/lib/google-tasks.mdx new file mode 100644 index 0000000..c6b9fe2 --- /dev/null +++ b/packages/pieces/community/google-tasks/src/lib/google-tasks.mdx @@ -0,0 +1,29 @@ +--- +title: 'Google Tasks' +description: '' +--- + +## Set up and run an app that calls a Google Tasks API. + +1. In the Google Cloud console, enable the Google Tasks API. + - Navigate to [Google Cloud Console](https://console.cloud.google.com/) (make sure you are on the desired project or create a new project if you have none). + - Navigate to **APIs & Services**. + - Navigate to **Library** from the sidemenu. + - Find and enable the **Tasks API**. +2. Click In the Google Cloud console, and go to Menu menu > APIs & Services > Credentials. +3. Click Create Credentials > OAuth client ID. +4. In the Name field, type a name for the credential. This name is only shown in the Google Cloud console. +5. Click Create. The OAuth client created screen appears, showing your new Client ID and Client secret. Make sure to copy them before closing. +6. Click OK. The newly created credential appears under OAuth 2.0 Client IDs. + +--- + +## Triggers + +TRIGGERS + +--- + +## Actions + +### 1. Create Task diff --git a/packages/pieces/community/google-tasks/src/lib/triggers/new-task.ts b/packages/pieces/community/google-tasks/src/lib/triggers/new-task.ts new file mode 100644 index 0000000..1a87fc1 --- /dev/null +++ b/packages/pieces/community/google-tasks/src/lib/triggers/new-task.ts @@ -0,0 +1,72 @@ +import { + OAuth2PropertyValue, + OAuth2Props, + StaticPropsValue, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { googleTasksAuth } from '../..'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { getTasks, googleTasksCommon } from '../common'; + +const props = { + tasks_list: googleTasksCommon.tasksList, +}; + +const polling: Polling< + OAuth2PropertyValue, + StaticPropsValue +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + const records = await getTasks(auth, propsValue.tasks_list!); + + const filtered_records = records.filter((record) => { + const updated = Date.parse(record.updated); + return updated > lastFetchEpochMS; + }); + + return filtered_records.map((record) => ({ + epochMilliSeconds: Date.parse(record.updated), + data: record, + })); + }, +}; + +export const newTaskTrigger = createTrigger({ + auth: googleTasksAuth, + name: 'new_task', + displayName: 'New Task', + description: 'Triggers when a task is created', + type: TriggerStrategy.POLLING, + props, + sampleData: {}, + async test(context) { + const store = context.store; + const auth = context.auth as OAuth2PropertyValue; + const propsValue = context.propsValue; + return await pollingHelper.test(polling, { store, auth, propsValue, files: context.files }); + }, + async onEnable(context) { + const store = context.store; + const auth = context.auth as OAuth2PropertyValue; + const propsValue = context.propsValue; + await pollingHelper.onEnable(polling, { store, auth, propsValue }); + }, + async onDisable(context) { + const store = context.store; + const auth = context.auth as OAuth2PropertyValue; + const propsValue = context.propsValue; + await pollingHelper.onEnable(polling, { store, auth, propsValue }); + }, + async run(context) { + const store = context.store; + const auth = context.auth as OAuth2PropertyValue; + const propsValue = context.propsValue; + return await pollingHelper.poll(polling, { store, auth, propsValue, files: context.files }); + }, +}); diff --git a/packages/pieces/community/google-tasks/tsconfig.json b/packages/pieces/community/google-tasks/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/google-tasks/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/google-tasks/tsconfig.lib.json b/packages/pieces/community/google-tasks/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/google-tasks/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/gotify/.eslintrc.json b/packages/pieces/community/gotify/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/gotify/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/gotify/README.md b/packages/pieces/community/gotify/README.md new file mode 100644 index 0000000..1ac7d8f --- /dev/null +++ b/packages/pieces/community/gotify/README.md @@ -0,0 +1,7 @@ +# pieces-gotify + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-gotify` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/gotify/package.json b/packages/pieces/community/gotify/package.json new file mode 100644 index 0000000..f1559bf --- /dev/null +++ b/packages/pieces/community/gotify/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-gotify", + "version": "0.3.3" +} \ No newline at end of file diff --git a/packages/pieces/community/gotify/project.json b/packages/pieces/community/gotify/project.json new file mode 100644 index 0000000..0fb74fb --- /dev/null +++ b/packages/pieces/community/gotify/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-gotify", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/gotify/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/gotify", + "tsConfig": "packages/pieces/community/gotify/tsconfig.lib.json", + "packageJson": "packages/pieces/community/gotify/package.json", + "main": "packages/pieces/community/gotify/src/index.ts", + "assets": [ + "packages/pieces/community/gotify/*.md", + { + "input": "packages/pieces/community/gotify/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/gotify/src/index.ts b/packages/pieces/community/gotify/src/index.ts new file mode 100644 index 0000000..8342718 --- /dev/null +++ b/packages/pieces/community/gotify/src/index.ts @@ -0,0 +1,44 @@ +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { sendNotification } from './lib/actions/send-notification'; + +export const gotifyAuth = PieceAuth.CustomAuth({ + description: ` + To obtain a token: + + 1. Log in to your Gotify instance. + 2. Click on Apps + 3. Select the Eye icon in the same row as your App to copy your token, or CREATE APPLICATION if you do not have one app yet. + 4. Copy your access token & and paste them into the fields below. + `, + props: { + base_url: Property.ShortText({ + displayName: 'Server URL', + description: 'Gotify Instance URL', + required: true, + }), + app_token: PieceAuth.SecretText({ + displayName: 'App Token', + description: 'Gotify App Token', + required: true, + }), + }, + required: true, +}); + +export const gotify = createPiece({ + displayName: 'Gotify', + description: 'Self-hosted push notification service', + + logoUrl: 'https://cdn.activepieces.com/pieces/gotify.png', + minimumSupportedRelease: '0.30.0', + categories: [PieceCategory.DEVELOPER_TOOLS], + authors: ["MyWay","kishanprmr","khaledmashaly","abuaboud"], + auth: gotifyAuth, + actions: [sendNotification], + triggers: [], +}); diff --git a/packages/pieces/community/gotify/src/lib/actions/send-notification.ts b/packages/pieces/community/gotify/src/lib/actions/send-notification.ts new file mode 100644 index 0000000..ee8f5be --- /dev/null +++ b/packages/pieces/community/gotify/src/lib/actions/send-notification.ts @@ -0,0 +1,46 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { gotifyAuth } from '../../'; + +export const sendNotification = createAction({ + auth: gotifyAuth, + name: 'send_notification', + displayName: 'Send Notification', + description: 'Send a notification to gotify', + props: { + title: Property.ShortText({ + displayName: 'Title', + description: 'The title of the notification', + required: true, + }), + message: Property.LongText({ + displayName: 'Message', + description: 'The message to send', + required: true, + }), + priority: Property.Number({ + displayName: 'Priority', + description: + 'The priority of the notification (0-10). 0 is lowest priority.', + required: false, + }), + }, + async run({ auth, propsValue }) { + const baseUrl = auth.base_url.replace(/\/$/, ''); + const appToken = auth.app_token; + + const title = propsValue.title; + const message = propsValue.message; + const priority = propsValue.priority; + + return await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${baseUrl}/message?token=${appToken}`, + body: { + title, + message, + ...(priority && { priority: +priority }), + }, + }); + }, +}); diff --git a/packages/pieces/community/gotify/tsconfig.json b/packages/pieces/community/gotify/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/gotify/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/gotify/tsconfig.lib.json b/packages/pieces/community/gotify/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/gotify/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/graphql/.eslintrc.json b/packages/pieces/community/graphql/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/graphql/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/graphql/README.md b/packages/pieces/community/graphql/README.md new file mode 100644 index 0000000..0ea2ad6 --- /dev/null +++ b/packages/pieces/community/graphql/README.md @@ -0,0 +1,7 @@ +# pieces-graphql + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-graphql` to build the library. diff --git a/packages/pieces/community/graphql/package.json b/packages/pieces/community/graphql/package.json new file mode 100644 index 0000000..21cce46 --- /dev/null +++ b/packages/pieces/community/graphql/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-graphql", + "version": "0.0.1" +} diff --git a/packages/pieces/community/graphql/project.json b/packages/pieces/community/graphql/project.json new file mode 100644 index 0000000..b5546fc --- /dev/null +++ b/packages/pieces/community/graphql/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-graphql", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/graphql/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/graphql", + "tsConfig": "packages/pieces/community/graphql/tsconfig.lib.json", + "packageJson": "packages/pieces/community/graphql/package.json", + "main": "packages/pieces/community/graphql/src/index.ts", + "assets": [ + "packages/pieces/community/graphql/*.md", + { + "input": "packages/pieces/community/graphql/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/graphql/src/index.ts b/packages/pieces/community/graphql/src/index.ts new file mode 100644 index 0000000..405a57e --- /dev/null +++ b/packages/pieces/community/graphql/src/index.ts @@ -0,0 +1,16 @@ + +import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { query } from "./lib/actions/query"; +import { PieceCategory } from "@activepieces/shared"; + + export const graphql = createPiece({ + displayName: "GraphQL", + auth: PieceAuth.None(), + minimumSupportedRelease: '0.30.0', + logoUrl: "https://cdn.activepieces.com/pieces/graphql.svg", + categories:[PieceCategory.CORE], + authors: ['mahmuthamet'], + actions: [query], + triggers: [], + }); + \ No newline at end of file diff --git a/packages/pieces/community/graphql/src/lib/actions/query.ts b/packages/pieces/community/graphql/src/lib/actions/query.ts new file mode 100644 index 0000000..9a45169 --- /dev/null +++ b/packages/pieces/community/graphql/src/lib/actions/query.ts @@ -0,0 +1,151 @@ +import { + httpClient, + HttpError, + HttpHeaders, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; +import { + createAction, + DynamicPropsValue, + Property, +} from '@activepieces/pieces-framework'; +import { assertNotNullOrUndefined } from '@activepieces/shared'; +import { httpMethodDropdown } from '../common/props'; +import { HttpsProxyAgent } from 'https-proxy-agent'; +import axios from 'axios'; + +export const query = createAction({ + name: 'send_request', + displayName: 'Send Request', + description: 'Makes a GraphQL request.', + props: { + method: httpMethodDropdown, + url: Property.ShortText({ + displayName: 'URL', + required: true, + }), + queryParams: Property.Object({ + displayName: 'Query params', + required: true, + }), + headers: Property.Object({ + displayName: 'Headers', + required: true, + }), + query: Property.LongText({ + displayName: 'Query', + required: true, + }), + variables: Property.Json({ + displayName: 'Variables', + required: false, + }), + use_proxy: Property.Checkbox({ + displayName: 'Use Proxy', + defaultValue: false, + description: 'Use a proxy for this request', + required: false, + }), + proxy_settings: Property.DynamicProperties({ + displayName: 'Proxy Settings', + refreshers: ['use_proxy'], + required: false, + props: async ({ use_proxy }) => { + if (!use_proxy) return {}; + + const fields: DynamicPropsValue = {}; + + fields['proxy_host'] = Property.ShortText({ + displayName: 'Proxy Host', + required: true, + }); + + fields['proxy_port'] = Property.Number({ + displayName: 'Proxy Port', + required: true, + }); + + fields['proxy_username'] = Property.ShortText({ + displayName: 'Proxy Username', + required: false, + }); + + fields['proxy_password'] = Property.ShortText({ + displayName: 'Proxy Password', + required: false, + }); + + return fields; + }, + }), + timeout: Property.Number({ + displayName: 'Timeout(in seconds)', + required: false, + }), + failsafe: Property.Checkbox({ + displayName: 'No Error on Failure', + required: false, + }), + }, + errorHandlingOptions: { + continueOnFailure: { hide: true }, + retryOnFailure: { defaultValue: true }, + }, + async run(context) { + const { + method, + url, + headers, + queryParams, + query, + variables, + timeout, + failsafe, + use_proxy, + } = context.propsValue; + + assertNotNullOrUndefined(method, 'Method'); + assertNotNullOrUndefined(url, 'URL'); + + const request: HttpRequest = { + method, + url, + queryParams: queryParams as QueryParams, + headers: headers as HttpHeaders, + timeout: timeout ? timeout * 1000 : 0, + body: JSON.stringify({ query, variables }), + }; + + try { + if (use_proxy) { + const proxySettings = context.propsValue.proxy_settings; + assertNotNullOrUndefined(proxySettings, 'Proxy Settings'); + assertNotNullOrUndefined(proxySettings['proxy_host'], 'Proxy Host'); + assertNotNullOrUndefined(proxySettings['proxy_port'], 'Proxy Port'); + let proxyUrl; + + if (proxySettings['proxy_username'] && proxySettings['proxy_password']) { + proxyUrl = `http://${proxySettings['proxy_username']}:${proxySettings['proxy_password']}@${proxySettings['proxy_host']}:${proxySettings['proxy_port']}`; + } else { + proxyUrl = `http://${proxySettings['proxy_host']}:${proxySettings['proxy_port']}`; + } + + const httpsAgent = new HttpsProxyAgent(proxyUrl) + const axiosClient = axios.create({ + httpsAgent, + }); + + const proxied_response = await axiosClient.request(request); + return proxied_response.data; + } + return await httpClient.sendRequest(request); + } catch (error) { + if (failsafe) { + return (error as HttpError).errorMessage(); + } + + throw error; + } + }, +}); diff --git a/packages/pieces/community/graphql/src/lib/common/props.ts b/packages/pieces/community/graphql/src/lib/common/props.ts new file mode 100644 index 0000000..d7249fb --- /dev/null +++ b/packages/pieces/community/graphql/src/lib/common/props.ts @@ -0,0 +1,14 @@ +import { Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; + +const httpMethodDropdownOptions = Object.values(HttpMethod).map((m) => ({ + label: m, + value: m, +})); + +export const httpMethodDropdown = Property.StaticDropdown({ + displayName: 'Method', + required: true, + options: { options: httpMethodDropdownOptions }, + defaultValue: HttpMethod.POST, +}); diff --git a/packages/pieces/community/graphql/tsconfig.json b/packages/pieces/community/graphql/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/graphql/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/graphql/tsconfig.lib.json b/packages/pieces/community/graphql/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/graphql/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/gravityforms/.eslintrc.json b/packages/pieces/community/gravityforms/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/gravityforms/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/gravityforms/README.md b/packages/pieces/community/gravityforms/README.md new file mode 100644 index 0000000..6f43d85 --- /dev/null +++ b/packages/pieces/community/gravityforms/README.md @@ -0,0 +1,7 @@ +# pieces-gravityforms + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-gravityforms` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/gravityforms/package.json b/packages/pieces/community/gravityforms/package.json new file mode 100644 index 0000000..a50d3fd --- /dev/null +++ b/packages/pieces/community/gravityforms/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-gravityforms", + "version": "0.0.8" +} diff --git a/packages/pieces/community/gravityforms/project.json b/packages/pieces/community/gravityforms/project.json new file mode 100644 index 0000000..92045bf --- /dev/null +++ b/packages/pieces/community/gravityforms/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-gravityforms", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/gravityforms/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/gravityforms", + "tsConfig": "packages/pieces/community/gravityforms/tsconfig.lib.json", + "packageJson": "packages/pieces/community/gravityforms/package.json", + "main": "packages/pieces/community/gravityforms/src/index.ts", + "assets": [ + "packages/pieces/community/gravityforms/*.md", + { + "input": "packages/pieces/community/gravityforms/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/gravityforms/src/index.ts b/packages/pieces/community/gravityforms/src/index.ts new file mode 100644 index 0000000..4f6b9bb --- /dev/null +++ b/packages/pieces/community/gravityforms/src/index.ts @@ -0,0 +1,16 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { gravityFormsNewSubmission } from './lib/triggers/new-submission'; + +export const gravityforms = createPiece({ + displayName: 'Gravity Forms', + description: 'Build and publish your WordPress forms', + + auth: PieceAuth.None(), + minimumSupportedRelease: '0.27.1', + logoUrl: 'https://cdn.activepieces.com/pieces/gravityforms.svg', + authors: ["Abdallah-Alwarawreh","kishanprmr","MoShizzle","abuaboud"], + categories: [PieceCategory.FORMS_AND_SURVEYS], + actions: [], + triggers: [gravityFormsNewSubmission], +}); diff --git a/packages/pieces/community/gravityforms/src/lib/triggers/new-submission.ts b/packages/pieces/community/gravityforms/src/lib/triggers/new-submission.ts new file mode 100644 index 0000000..473c7e9 --- /dev/null +++ b/packages/pieces/community/gravityforms/src/lib/triggers/new-submission.ts @@ -0,0 +1,42 @@ +import { + createTrigger, + PieceAuth, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; + +const markdown = ` +- Go to the "Plugins" section. +- Find and click on the "Webhook" plugin to activate it. +- Now, locate the form where you want the trigger to occur. +- Add a webhook to that form. +- In the webhook settings, paste this URL: + \`\`\`text + {{webhookUrl}} + \`\`\` + +- Keep the other settings unchanged (default). +`; + +export const gravityFormsNewSubmission = createTrigger({ + name: 'new-submission', + displayName: 'New Submission', + auth: PieceAuth.None(), + description: 'Triggers when form receives a new submission', + props: { + md: Property.MarkDown({ + value: markdown, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: {}, + async onEnable(context) { + // Empty + }, + async onDisable(context) { + // Empty + }, + async run(context) { + return [context.payload]; + }, +}); diff --git a/packages/pieces/community/gravityforms/tsconfig.json b/packages/pieces/community/gravityforms/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/gravityforms/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/gravityforms/tsconfig.lib.json b/packages/pieces/community/gravityforms/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/gravityforms/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/grist/.eslintrc.json b/packages/pieces/community/grist/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/grist/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/grist/README.md b/packages/pieces/community/grist/README.md new file mode 100644 index 0000000..d8e4aa6 --- /dev/null +++ b/packages/pieces/community/grist/README.md @@ -0,0 +1,7 @@ +# pieces-grist + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-grist` to build the library. diff --git a/packages/pieces/community/grist/package.json b/packages/pieces/community/grist/package.json new file mode 100644 index 0000000..081792c --- /dev/null +++ b/packages/pieces/community/grist/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-grist", + "version": "0.0.3" +} diff --git a/packages/pieces/community/grist/project.json b/packages/pieces/community/grist/project.json new file mode 100644 index 0000000..7b496ee --- /dev/null +++ b/packages/pieces/community/grist/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-grist", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/grist/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/grist", + "tsConfig": "packages/pieces/community/grist/tsconfig.lib.json", + "packageJson": "packages/pieces/community/grist/package.json", + "main": "packages/pieces/community/grist/src/index.ts", + "assets": [ + "packages/pieces/community/grist/*.md", + { + "input": "packages/pieces/community/grist/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/grist/src/index.ts b/packages/pieces/community/grist/src/index.ts new file mode 100644 index 0000000..2e3263e --- /dev/null +++ b/packages/pieces/community/grist/src/index.ts @@ -0,0 +1,85 @@ +import { + createPiece, + PieceAuth, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { gristCreateRecordAction } from './lib/actions/create-record.action'; +import { gristUpdateRecordAction } from './lib/actions/update-record.action'; +import { gristUploadAttachmentsToDocumnetAction } from './lib/actions/upload-attachments-to-document.action'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { gristNewRecordTrigger } from './lib/triggers/new-record.trigger'; +import { gristUpdatedRecordTrigger } from './lib/triggers/updated-record.trigger'; +import { gristSearchRecordAction } from './lib/actions/search-record.action'; +import { GristAPIClient } from './lib/common/helpers'; +import { PieceCategory } from '@activepieces/shared'; + +export const gristAuth = PieceAuth.CustomAuth({ + required: true, + description: ` + Log in to your Grist account. Navigate to the account menu at the top right, and select **Profile Settings** to manage or create your API Key. + In the **Domain URL** field, enter the domain URL of your Grist instance.For example,if you have team site it will be "https://team.getgist.com".`, + props: { + apiKey: PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + }), + domain: Property.ShortText({ + displayName: 'Domain URL', + required: true, + defaultValue: 'https://docs.getgrist.com', + }), + }, + validate: async ({ auth }) => { + try { + const authValue = auth as PiecePropValueSchema; + + const client = new GristAPIClient({ + domainUrl: authValue.domain, + apiKey: authValue.apiKey, + }); + + // https://support.getgrist.com/api/#tag/orgs + await client.listOrgs(); + + return { + valid: true, + }; + } catch (error) { + return { + valid: false, + error: 'Please provide valid API key and domain URL.', + }; + } + }, +}); + +export const grist = createPiece({ + displayName: 'Grist', + auth: gristAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/grist.png', + description: 'open source spreadsheet', + categories: [PieceCategory.PRODUCTIVITY], + authors: ['kishanprmr'], + actions: [ + gristCreateRecordAction, + gristSearchRecordAction, + gristUpdateRecordAction, + gristUploadAttachmentsToDocumnetAction, + createCustomApiCallAction({ + auth: gristAuth, + baseUrl: (auth) => { + return `${ + (auth as PiecePropValueSchema).domain + }/api/`; + }, + authMapping: async (auth) => ({ + Authorization: `Bearer ${ + (auth as PiecePropValueSchema).apiKey + }`, + }), + }), + ], + triggers: [gristNewRecordTrigger, gristUpdatedRecordTrigger], +}); diff --git a/packages/pieces/community/grist/src/lib/actions/create-record.action.ts b/packages/pieces/community/grist/src/lib/actions/create-record.action.ts new file mode 100644 index 0000000..49b7087 --- /dev/null +++ b/packages/pieces/community/grist/src/lib/actions/create-record.action.ts @@ -0,0 +1,50 @@ +import { gristAuth } from '../..'; +import { createAction } from '@activepieces/pieces-framework'; +import { commonProps } from '../common/props'; +import { GristAPIClient, transformTableColumnValues } from '../common/helpers'; + +export const gristCreateRecordAction = createAction({ + auth: gristAuth, + name: 'grist-create-record', + displayName: 'Create Record', + description: 'Creates a new record in specific table.', + props: { + workspace_id: commonProps.workspace_id, + document_id: commonProps.document_id, + table_id: commonProps.table_id, + table_columns: commonProps.table_columns, + }, + async run(context) { + const documentId = context.propsValue.document_id; + const tableId = context.propsValue.table_id; + const tableColumnValues = context.propsValue.table_columns; + + const client = new GristAPIClient({ + domainUrl: context.auth.domain, + apiKey: context.auth.apiKey, + }); + + const tableColumnSchema = await client.listTableColumns( + documentId as string, + tableId as string + ); + + const transformedColumnValues = transformTableColumnValues({ + tableColumnSchema, + tableColumnValues, + }); + + const createRecordResponse = await client.addRecordsToTable( + documentId, + tableId, + { + records: [{ fields: transformedColumnValues }], + } + ); + + return { + id: createRecordResponse.records[0].id, + fields: transformedColumnValues, + }; + }, +}); diff --git a/packages/pieces/community/grist/src/lib/actions/search-record.action.ts b/packages/pieces/community/grist/src/lib/actions/search-record.action.ts new file mode 100644 index 0000000..64bb1d2 --- /dev/null +++ b/packages/pieces/community/grist/src/lib/actions/search-record.action.ts @@ -0,0 +1,86 @@ +import { gristAuth } from '../..'; +import { + createAction, + DropdownOption, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { commonProps } from '../common/props'; +import { GristAPIClient } from '../common/helpers'; + +import { HttpMethod } from '@activepieces/pieces-common'; + +export const gristSearchRecordAction = createAction({ + auth: gristAuth, + name: 'grist-search-record', + displayName: 'Search Record', + description: 'Search record by matching criteria.', + props: { + workspace_id: commonProps.workspace_id, + document_id: commonProps.document_id, + table_id: commonProps.table_id, + column: Property.Dropdown({ + displayName: 'Column', + refreshers: ['document_id', 'table_id'], + required: true, + options: async ({ auth, document_id, table_id }) => { + if (!auth || !document_id || !table_id) { + return { + disabled: true, + placeholder: 'Please connect account and select document.', + options: [], + }; + } + + const authValue = auth as PiecePropValueSchema; + + const client = new GristAPIClient({ + domainUrl: authValue.domain, + apiKey: authValue.apiKey, + }); + + const response = await client.listTableColumns( + document_id as string, + table_id as string + ); + + const options: DropdownOption[] = []; + for (const column of response.columns) { + options.push({ value: column.id, label: column.fields.label }); + } + return { + disabled: false, + options, + }; + }, + }), + value: Property.ShortText({ + displayName: 'Value', + required: true, + description: + 'Enter the search text. The search operation is case sensitive, exact text match, and supports column-type Text only.', + }), + }, + async run(context) { + const documentId = context.propsValue.document_id; + const tableId = context.propsValue.table_id; + const columnName = context.propsValue.column; + const columnValue = context.propsValue.value; + + const client = new GristAPIClient({ + domainUrl: context.auth.domain, + apiKey: context.auth.apiKey, + }); + + const encodedQuery = encodeURIComponent( + JSON.stringify({ + [columnName]: [columnValue], + }) + ); + + return await client.makeRequest( + HttpMethod.GET, + `/docs/${documentId}/tables/${tableId}/records?filter=${encodedQuery}` + ); + }, +}); diff --git a/packages/pieces/community/grist/src/lib/actions/update-record.action.ts b/packages/pieces/community/grist/src/lib/actions/update-record.action.ts new file mode 100644 index 0000000..836d2c0 --- /dev/null +++ b/packages/pieces/community/grist/src/lib/actions/update-record.action.ts @@ -0,0 +1,55 @@ +import { gristAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { commonProps } from '../common/props'; +import { GristAPIClient, transformTableColumnValues } from '../common/helpers'; + +export const gristUpdateRecordAction = createAction({ + auth: gristAuth, + name: 'grist-update-record', + displayName: 'Update Record', + description: 'Updates an existing record in specific table.', + props: { + workspace_id: commonProps.workspace_id, + document_id: commonProps.document_id, + table_id: commonProps.table_id, + record_id: Property.Number({ + displayName: 'Record ID', + required: true, + }), + table_columns: commonProps.table_columns, + }, + async run(context) { + const documentId = context.propsValue.document_id; + const tableId = context.propsValue.table_id; + const recordId = context.propsValue.record_id; + const tableColumnValues = context.propsValue.table_columns; + + const client = new GristAPIClient({ + domainUrl: context.auth.domain, + apiKey: context.auth.apiKey, + }); + + const tableColumnSchema = await client.listTableColumns( + documentId as string, + tableId as string + ); + + const transformedColumnValues = transformTableColumnValues({ + tableColumnSchema, + tableColumnValues, + }); + + const updateRecordResponse = await client.updateRcordsInTable( + documentId, + tableId, + { + records: [{ id: recordId, fields: transformedColumnValues }], + } + ); + + return { + id: recordId, + fields: transformedColumnValues, + }; + }, +}); diff --git a/packages/pieces/community/grist/src/lib/actions/upload-attachments-to-document.action.ts b/packages/pieces/community/grist/src/lib/actions/upload-attachments-to-document.action.ts new file mode 100644 index 0000000..38da61c --- /dev/null +++ b/packages/pieces/community/grist/src/lib/actions/upload-attachments-to-document.action.ts @@ -0,0 +1,70 @@ +import { gristAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { commonProps } from '../common/props'; +import FormData from 'form-data'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { GristAPIClient } from '../common/helpers'; + +export const gristUploadAttachmentsToDocumnetAction = createAction({ + auth: gristAuth, + name: 'grist-upload-attachments-to-document', + displayName: 'Upload Attachment to Document', + description: 'Uploads attachments to specific document.', + props: { + workspace_id: commonProps.workspace_id, + document_id: commonProps.document_id, + attachment: Property.File({ + displayName: 'Attachment', + required: true, + }), + attachment_name: Property.ShortText({ + displayName: 'Attachment Name', + description: 'In case you want to change the name of the attachment.', + required: false, + }), + }, + async run(context) { + const documentId = context.propsValue.document_id; + const attachment = context.propsValue.attachment; + const attachmentName = context.propsValue.attachment_name; + + const client = new GristAPIClient({ + domainUrl: context.auth.domain, + apiKey: context.auth.apiKey, + }); + + const formData = new FormData(); + formData.append( + 'upload', + Buffer.from(attachment.base64, 'base64'), + attachmentName || attachment.filename + ); + + const response = await httpClient.sendRequest>({ + method: HttpMethod.POST, + url: context.auth.domain + '/api' + `/docs/${documentId}/attachments`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.apiKey, + }, + headers: { ...formData.getHeaders() }, + body: formData, + }); + + const attachmentId = response.body[0]; + + const attachmentMetadata = await client.getDocumentAttachmentMetadata( + documentId, + attachmentId + ); + + return { + id: attachmentId, + fields: attachmentMetadata, + }; + }, +}); diff --git a/packages/pieces/community/grist/src/lib/common/helpers.ts b/packages/pieces/community/grist/src/lib/common/helpers.ts new file mode 100644 index 0000000..9441168 --- /dev/null +++ b/packages/pieces/community/grist/src/lib/common/helpers.ts @@ -0,0 +1,219 @@ +import { DynamicPropsValue } from '@activepieces/pieces-framework'; +import { + GristAPIClientOptions, + GristCreateRecordsRequest, + GristCreateRecordsResponse, + GristCreateWebhookRequest, + GristCreateWebhookResponse, + GristListRecordsResponse, + GristOrgResponse, + GristTableColumnsResponse, + GristTableResponse, + GristUpdateRecordsRequest, + GristWorkspaceResponse, +} from './types'; +import { + AuthenticationType, + httpClient, + HttpHeaders, + HttpMessageBody, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; + +type Query = Record; + +export class GristAPIClient { + #domaiUrl: string; + #apiKey: string; + constructor(options: GristAPIClientOptions) { + this.#domaiUrl = options.domainUrl; + this.#apiKey = options.apiKey; + } + + async makeRequest( + method: HttpMethod, + resourceUri: string, + headers?: HttpHeaders, + query?: Query, + body: any | undefined = undefined + ): Promise { + const baseUrl = this.#domaiUrl.replace(/\/$/, ''); + const params: QueryParams = {}; + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + params[key] = String(value); + } + } + } + const request: HttpRequest = { + method: method, + url: baseUrl + '/api' + resourceUri, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: this.#apiKey, + }, + headers, + queryParams: params, + body: body, + }; + const response = await httpClient.sendRequest(request); + return response.body; + } + + async listOrgs() { + return await this.makeRequest( + HttpMethod.GET, + `/orgs` + ); + } + + async listWorkspaces(orgId: string) { + return await this.makeRequest( + HttpMethod.GET, + `/orgs/${orgId}/workspaces` + ); + } + + async getWorkspace(workspaceId: number) { + return await this.makeRequest( + HttpMethod.GET, + `/workspaces/${workspaceId}` + ); + } + + async listDocumentTables(docId: string) { + return await this.makeRequest<{ tables: GristTableResponse[] }>( + HttpMethod.GET, + `/docs/${docId}/tables` + ); + } + + async listTableColumns(docId: string, tableId: string) { + return await this.makeRequest<{ columns: GristTableColumnsResponse[] }>( + HttpMethod.GET, + `/docs/${docId}/tables/${tableId}/columns` + ); + } + + async addRecordsToTable( + docId: string, + tableId: string, + request: GristCreateRecordsRequest + ) { + return await this.makeRequest( + HttpMethod.POST, + `/docs/${docId}/tables/${tableId}/records`, + undefined, + {}, + request + ); + } + + async updateRcordsInTable( + docId: string, + tableId: string, + request: GristUpdateRecordsRequest + ) { + return await this.makeRequest( + HttpMethod.PATCH, + `/docs/${docId}/tables/${tableId}/records`, + undefined, + {}, + request + ); + } + + async listRecordsFromTable(docId: string, tableId: string, query: Query) { + return await this.makeRequest( + HttpMethod.GET, + `/docs/${docId}/tables/${tableId}/records`, + undefined, + query + ); + } + + async listDocumentAttachments(docId: string, query: Query) { + return await this.makeRequest( + HttpMethod.GET, + `/docs/${docId}/attachments`, + {}, + query + ); + } + + async getDocumentAttachmentMetadata(docId: string, attachmentId: number) { + return await this.makeRequest( + HttpMethod.GET, + `/docs/${docId}/attachments/${attachmentId}` + ); + } + + async createDocumentWebhook( + docId: string, + request: GristCreateWebhookRequest + ) { + return await this.makeRequest( + HttpMethod.POST, + `/docs/${docId}/webhooks`, + undefined, + {}, + request + ); + } + + async deleteDocumentWebhook(docId: string, webhookId: number) { + return await this.makeRequest( + HttpMethod.DELETE, + `/docs/${docId}/webhooks/${webhookId}` + ); + } +} + +export function transformTableColumnValues({ + tableColumnSchema, + tableColumnValues, +}: { + tableColumnSchema: { columns: GristTableColumnsResponse[] }; + tableColumnValues: DynamicPropsValue; +}): DynamicPropsValue { + const fields: DynamicPropsValue = {}; + + for (const column of tableColumnSchema.columns) { + const columnId = column.id; + const columnType = column.fields.type; + const columnValue = tableColumnValues[columnId]; + + if (columnValue !== undefined && columnValue !== '') { + if (columnType === 'Attachments') { + const attachmentsArray = Array.isArray(columnValue) + ? columnValue + : JSON.parse(columnValue); + if (Array.isArray(attachmentsArray) && attachmentsArray.length > 0) { + fields[columnId] = ['L', ...attachmentsArray.map(Number)]; + } + } else if (columnType === 'ChoiceList') { + if (Array.isArray(columnValue) && columnValue.length > 0) { + fields[columnId] = ['L', ...columnValue]; + } + } else if (columnType === 'Int' || columnType === 'Numeric') { + fields[columnId] = Number(columnValue); + } else if (columnType.startsWith('RefList')) { + const refListArray = Array.isArray(columnValue) + ? columnValue + : JSON.parse(columnValue); + if (Array.isArray(refListArray) && refListArray.length > 0) { + fields[columnId] = ['L', ...refListArray.map(Number)]; + } + } else if (columnType.startsWith('Ref')) { + fields[columnId] = Number(columnValue); + } else { + fields[columnId] = columnValue; + } + } + } + + return fields; +} diff --git a/packages/pieces/community/grist/src/lib/common/props.ts b/packages/pieces/community/grist/src/lib/common/props.ts new file mode 100644 index 0000000..c667806 --- /dev/null +++ b/packages/pieces/community/grist/src/lib/common/props.ts @@ -0,0 +1,279 @@ +import { + DropdownOption, + DynamicPropsValue, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { gristAuth } from '../..'; +import { GristAPIClient } from './helpers'; + +export const commonProps = { + workspace_id: Property.Dropdown({ + displayName: 'Workspace', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect account first.', + options: [], + }; + } + + const authValue = auth as PiecePropValueSchema; + + const client = new GristAPIClient({ + domainUrl: authValue.domain, + apiKey: authValue.apiKey, + }); + + const response = await client.listWorkspaces('current'); + + const options: DropdownOption[] = []; + for (const workspace of response) { + options.push({ label: workspace.name, value: workspace.id }); + } + + return { + disabled: false, + options, + }; + }, + }), + document_id: Property.Dropdown({ + displayName: 'Document', + refreshers: ['workspace_id'], + required: true, + options: async ({ auth, workspace_id }) => { + if (!auth || !workspace_id) { + return { + disabled: true, + placeholder: 'Please connect account and select workspace.', + options: [], + }; + } + + const authValue = auth as PiecePropValueSchema; + + const client = new GristAPIClient({ + domainUrl: authValue.domain, + apiKey: authValue.apiKey, + }); + + const response = await client.getWorkspace( + workspace_id as unknown as number + ); + + const options: DropdownOption[] = []; + for (const document of response.docs) { + options.push({ label: document.name, value: document.id }); + } + + return { + disabled: false, + options, + }; + }, + }), + table_id: Property.Dropdown({ + displayName: 'Table', + refreshers: ['document_id'], + required: true, + options: async ({ auth, document_id }) => { + if (!auth || !document_id) { + return { + disabled: true, + placeholder: 'Please connect account and select document.', + options: [], + }; + } + + const authValue = auth as PiecePropValueSchema; + + const client = new GristAPIClient({ + domainUrl: authValue.domain, + apiKey: authValue.apiKey, + }); + + const response = await client.listDocumentTables( + document_id as unknown as string + ); + + const options: DropdownOption[] = []; + for (const table of response.tables) { + options.push({ label: table.id, value: table.id }); + } + + return { + disabled: false, + options, + }; + }, + }), + table_columns: Property.DynamicProperties({ + displayName: 'Table Columns', + refreshers: ['document_id', 'table_id'], + required: true, + props: async ({ auth, document_id, table_id }) => { + if (!auth) return {}; + if (!document_id) return {}; + if (!table_id) return {}; + + const fields: DynamicPropsValue = {}; + + const authValue = auth as PiecePropValueSchema; + + const client = new GristAPIClient({ + domainUrl: authValue.domain, + apiKey: authValue.apiKey, + }); + + const response = await client.listTableColumns( + document_id as unknown as string, + table_id as unknown as string + ); + + for (const column of response.columns) { + if (!column.fields.isFormula) { + switch (column.fields.type) { + case 'Any': + fields[column.id] = Property.ShortText({ + displayName: column.fields.label || column.id, + required: false, + }); + break; + case 'Attachments': + fields[column.id] = Property.Array({ + displayName: column.fields.label || column.id, + description: `Use the **Upload Attachments to Document** action and provide the attachment ID from the response.`, + required: false, + }); + break; + case 'Bool': + fields[column.id] = Property.Checkbox({ + displayName: column.fields.label || column.id, + required: false, + }); + break; + case 'Choice': + case 'ChoiceList': { + let options = []; + try { + const optionsObject = JSON.parse(column.fields.widgetOptions); + options = optionsObject['choices'] as any[]; + } catch (error) { + options = []; + } + + const dropdownConfig = { + displayName: column.fields.label || column.id, + required: false, + options: { + disabled: false, + options: options.map((choice) => { + return { + label: choice, + value: choice, + }; + }), + }, + }; + + fields[column.id] = + column.fields.type === 'Choice' + ? Property.StaticDropdown(dropdownConfig) + : Property.StaticMultiSelectDropdown(dropdownConfig); + break; + } + case 'Date': + fields[column.id] = Property.DateTime({ + displayName: column.fields.label || column.id, + required: false, + }); + break; + case 'Int': + case 'Numeric': + fields[column.id] = Property.Number({ + displayName: column.fields.label || column.id, + required: false, + }); + break; + case 'Text': + fields[column.id] = Property.LongText({ + displayName: column.fields.label || column.id, + required: false, + }); + break; + default: + if (column.fields.type.startsWith('DateTime')) { + fields[column.id] = Property.DateTime({ + displayName: column.fields.label || column.id, + required: false, + }); + } else if (column.fields.type.startsWith('RefList')) { + const refTable = column.fields.type.split(':')[1]; + fields[column.id] = Property.Array({ + displayName: column.fields.label || column.id, + description: refTable + ? `Please provide the row ID from the reference table ${refTable}.` + : '', + required: false, + }); + } else if (column.fields.type.startsWith('Ref')) { + const refTable = column.fields.type.split(':')[1]; + fields[column.id] = Property.Number({ + displayName: column.fields.label || column.id, + description: refTable + ? `Please provide the row ID from the reference table ${refTable}.` + : '', + required: false, + }); + } + break; + } + } + } + + return fields; + }, + }), + readiness_column: Property.Dropdown({ + displayName: 'Readiness Column', + description: `A toggle (boolean) column which is True when the record is ready. The trigger will only be activated when that record becomes ready.Please follow [guideline](https://support.getgrist.com/integrators/#readiness-column) to create readiness column in table.`, + refreshers: ['document_id', 'table_id'], + required: false, + options: async ({ auth, document_id, table_id }) => { + if (!auth || !document_id || !table_id) { + return { + disabled: true, + placeholder: 'Please connect account and select document.', + options: [], + }; + } + + const authValue = auth as PiecePropValueSchema; + + const client = new GristAPIClient({ + domainUrl: authValue.domain, + apiKey: authValue.apiKey, + }); + + const response = await client.listTableColumns( + document_id as string, + table_id as string + ); + + const options: DropdownOption[] = []; + for (const column of response.columns) { + if (column.fields.type === 'Bool') { + options.push({ value: column.id, label: column.fields.label }); + } + } + return { + disabled: false, + options, + }; + }, + }), +}; diff --git a/packages/pieces/community/grist/src/lib/common/types.ts b/packages/pieces/community/grist/src/lib/common/types.ts new file mode 100644 index 0000000..116b376 --- /dev/null +++ b/packages/pieces/community/grist/src/lib/common/types.ts @@ -0,0 +1,108 @@ +export type GristAPIClientOptions = { + domainUrl: string; + apiKey: string; +}; + +export type GristOrganizationResponse = { + name: string; + id: string; + createdAt: string; + updatedAt: string; +}; + +export type GristDocumentResponse = { + name: string; + id: string; + createdAt: string; + updatedAt: string; +}; + +export type GristWorkspaceResponse = { + name: string; + id: number; + createdAt: string; + updatedAt: string; + docs: Array; +}; + +export type GristTableResponse = { + id: string; +}; + +export type GristTableColumnsResponse = { + id: string; + fields: { + type: + | 'Any' + | 'Text' + | 'Numeric' + | 'Int' + | 'Bool' + | 'Date' + | `DateTime:${string}` + | `Ref:${string}` + | `RefList:${string}` + | 'Choice' + | 'ChoiceList' + | 'Attachments'; + label: string; + widgetOptions: string; + isFormula: boolean; + }; +}; + +export type GristTableRecordResponse = { + id: number; + fields: Record; +}; + +export type GristCreateRecordsRequest = { + records: Array>; +}; + +export type GristUpdateRecordsRequest = { + records: Array; +}; + +export type GristCreateRecordsResponse = { + records: Array<{ id: number }>; +}; + +export type GristListRecordsResponse = { + records: Array; +}; + +export type GristCreateWebhookRequest = { + webhooks: Array<{ + fields: { + name?: string; + memo?: string; + url: string; + enabled: boolean; + eventTypes: Array; + isReadyColumn?: string; + tableId: string; + }; + }>; +}; + +export type GristCreateWebhookResponse = { + webhooks: Array<{ id: number }>; +}; + +export type GristWebhookPayload = Record; + +export type GristOrgResponse = { + id: number + name: string + domain: string + owner: { + id: number + name: string + picture: any + } + access: string + createdAt: string + updatedAt: string +} + diff --git a/packages/pieces/community/grist/src/lib/triggers/new-record.trigger.ts b/packages/pieces/community/grist/src/lib/triggers/new-record.trigger.ts new file mode 100644 index 0000000..9307bfa --- /dev/null +++ b/packages/pieces/community/grist/src/lib/triggers/new-record.trigger.ts @@ -0,0 +1,85 @@ +import { gristAuth } from '../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { commonProps } from '../common/props'; +import { GristAPIClient } from '../common/helpers'; +import { GristWebhookPayload } from '../common/types'; + +export const gristNewRecordTrigger = createTrigger({ + auth: gristAuth, + name: 'grist-new-record', + displayName: 'New Record', + description: 'Triggers when a new record is added to the table.', + props: { + workspace_id: commonProps.workspace_id, + document_id: commonProps.document_id, + table_id: commonProps.table_id, + readiness_column: commonProps.readiness_column, + }, + type: TriggerStrategy.WEBHOOK, + sampleData: {}, + async onEnable(context) { + const documentId = context.propsValue.document_id; + const tableId = context.propsValue.table_id; + const readinessColumn = context.propsValue.readiness_column; + + const client = new GristAPIClient({ + domainUrl: context.auth.domain, + apiKey: context.auth.apiKey, + }); + + const response = await client.createDocumentWebhook(documentId, { + webhooks: [ + { + fields: { + url: context.webhookUrl, + enabled: true, + eventTypes: ['add'], + tableId, + isReadyColumn: readinessColumn, + }, + }, + ], + }); + + await context.store.put( + 'grist-new-record', + response.webhooks[0].id + ); + }, + async onDisable(context) { + const documentId = context.propsValue.document_id; + const webhookId = await context.store.get('grist-new-record'); + + if (webhookId != null) { + const client = new GristAPIClient({ + domainUrl: context.auth.domain, + apiKey: context.auth.apiKey, + }); + await client.deleteDocumentWebhook(documentId, webhookId); + } + }, + async run(context) { + const payload = context.payload.body as GristWebhookPayload[]; + return payload; + }, + async test(context) { + const documentId = context.propsValue.document_id; + const tableId = context.propsValue.table_id; + + const client = new GristAPIClient({ + domainUrl: context.auth.domain, + apiKey: context.auth.apiKey, + }); + + const response = await client.listRecordsFromTable(documentId, tableId, { + limit: '10', + }); + + return response.records.map((record) => { + return { + id: record.id, + ...record.fields, + }; + }); + }, +}); diff --git a/packages/pieces/community/grist/src/lib/triggers/updated-record.trigger.ts b/packages/pieces/community/grist/src/lib/triggers/updated-record.trigger.ts new file mode 100644 index 0000000..e4b18c0 --- /dev/null +++ b/packages/pieces/community/grist/src/lib/triggers/updated-record.trigger.ts @@ -0,0 +1,85 @@ +import { gristAuth } from '../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { commonProps } from '../common/props'; +import { GristAPIClient } from '../common/helpers'; +import { GristWebhookPayload } from '../common/types'; + +export const gristUpdatedRecordTrigger = createTrigger({ + auth: gristAuth, + name: 'grist-updated-record', + displayName: 'Updated Record', + description: 'Triggers when a record is updated in the table.', + props: { + workspace_id: commonProps.workspace_id, + document_id: commonProps.document_id, + table_id: commonProps.table_id, + readiness_column: commonProps.readiness_column, + }, + type: TriggerStrategy.WEBHOOK, + sampleData: {}, + async onEnable(context) { + const documentId = context.propsValue.document_id; + const tableId = context.propsValue.table_id; + const readinessColumn = context.propsValue.readiness_column; + + const client = new GristAPIClient({ + domainUrl: context.auth.domain, + apiKey: context.auth.apiKey, + }); + + const response = await client.createDocumentWebhook(documentId, { + webhooks: [ + { + fields: { + url: context.webhookUrl, + enabled: true, + eventTypes: ['update'], + tableId, + isReadyColumn: readinessColumn, + }, + }, + ], + }); + + await context.store.put( + 'grist-updated-record', + response.webhooks[0].id + ); + }, + async onDisable(context) { + const documentId = context.propsValue.document_id; + const webhookId = await context.store.get('grist-updated-record'); + + if (webhookId != null) { + const client = new GristAPIClient({ + domainUrl: context.auth.domain, + apiKey: context.auth.apiKey, + }); + await client.deleteDocumentWebhook(documentId, webhookId); + } + }, + async run(context) { + const payload = context.payload.body as GristWebhookPayload[]; + return payload; + }, + async test(context) { + const documentId = context.propsValue.document_id; + const tableId = context.propsValue.table_id; + + const client = new GristAPIClient({ + domainUrl: context.auth.domain, + apiKey: context.auth.apiKey, + }); + + const response = await client.listRecordsFromTable(documentId, tableId, { + limit: '10', + }); + + return response.records.map((record) => { + return { + id: record.id, + ...record.fields, + }; + }); + }, +}); diff --git a/packages/pieces/community/grist/tsconfig.json b/packages/pieces/community/grist/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/grist/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/grist/tsconfig.lib.json b/packages/pieces/community/grist/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/grist/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/groq/.eslintrc.json b/packages/pieces/community/groq/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/groq/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/groq/README.md b/packages/pieces/community/groq/README.md new file mode 100644 index 0000000..f0d0216 --- /dev/null +++ b/packages/pieces/community/groq/README.md @@ -0,0 +1,7 @@ +# pieces-groq + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-groq` to build the library. diff --git a/packages/pieces/community/groq/package.json b/packages/pieces/community/groq/package.json new file mode 100644 index 0000000..c8fd470 --- /dev/null +++ b/packages/pieces/community/groq/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-groq", + "version": "0.1.0" +} diff --git a/packages/pieces/community/groq/project.json b/packages/pieces/community/groq/project.json new file mode 100644 index 0000000..77951d7 --- /dev/null +++ b/packages/pieces/community/groq/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-groq", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/groq/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/groq", + "tsConfig": "packages/pieces/community/groq/tsconfig.lib.json", + "packageJson": "packages/pieces/community/groq/package.json", + "main": "packages/pieces/community/groq/src/index.ts", + "assets": [ + "packages/pieces/community/groq/*.md", + { + "input": "packages/pieces/community/groq/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/groq/src/index.ts b/packages/pieces/community/groq/src/index.ts new file mode 100644 index 0000000..c3262f8 --- /dev/null +++ b/packages/pieces/community/groq/src/index.ts @@ -0,0 +1,66 @@ +import { + AuthenticationType, + HttpMethod, + createCustomApiCallAction, + httpClient, +} from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { askGroq } from './lib/actions/ask-groq'; +import { transcribeAudio } from './lib/actions/transcribe-audio'; +import { translateAudio } from './lib/actions/translate-audio'; + +const baseUrl = 'https://api.groq.com/openai/v1'; + +export const groqAuth = PieceAuth.SecretText({ + description: 'Enter your Groq API Key', + displayName: 'API Key', + required: true, + validate: async (auth) => { + try { + await httpClient.sendRequest<{ + data: { id: string }[]; + }>({ + url: `${baseUrl}/models`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.auth as string, + }, + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API key', + }; + } + }, +}); + +export const groq = createPiece({ + displayName: 'Groq', + description: 'Use Groq\'s fast language models and audio processing capabilities.', + minimumSupportedRelease: '0.9.0', + logoUrl: 'https://cdn.activepieces.com/pieces/groq.png', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + auth: groqAuth, + actions: [ + askGroq, + transcribeAudio, + translateAudio, + createCustomApiCallAction({ + auth: groqAuth, + baseUrl: () => baseUrl, + authMapping: async (auth) => { + return { + Authorization: `Bearer ${auth}`, + }; + }, + }), + ], + authors: ['abuaboud'], + triggers: [], +}); diff --git a/packages/pieces/community/groq/src/lib/actions/ask-groq.ts b/packages/pieces/community/groq/src/lib/actions/ask-groq.ts new file mode 100644 index 0000000..b9ddc5b --- /dev/null +++ b/packages/pieces/community/groq/src/lib/actions/ask-groq.ts @@ -0,0 +1,205 @@ +import { createAction, Property, StoreScope } from '@activepieces/pieces-framework'; +import { groqAuth } from '../..'; +import { httpClient, HttpMethod, AuthenticationType } from '@activepieces/pieces-common'; + +export const askGroq = createAction({ + auth: groqAuth, + name: 'ask-ai', + displayName: 'Ask AI', + description: 'Ask Groq anything using fast language models.', + props: { + model: Property.Dropdown({ + displayName: 'Model', + required: true, + description: 'The model which will generate the completion.', + refreshers: [], + defaultValue: 'llama-3.1-70b-versatile', + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your Groq account first.', + options: [], + }; + } + try { + const response = await httpClient.sendRequest({ + url: 'https://api.groq.com/openai/v1/models', + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + // Filter out audio models + const models = (response.body.data as Array<{ id: string }>).filter( + (model) => !model.id.toLowerCase().includes('whisper'), + ); + return { + disabled: false, + options: models.map((model) => { + return { + label: model.id, + value: model.id, + }; + }), + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: "Couldn't load models, API key is invalid", + }; + } + }, + }), + prompt: Property.LongText({ + displayName: 'Question', + required: true, + }), + temperature: Property.Number({ + displayName: 'Temperature', + required: false, + description: + 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.', + defaultValue: 0.9, + }), + maxTokens: Property.Number({ + displayName: 'Maximum Tokens', + required: true, + description: + "The maximum number of tokens to generate. The total length of input tokens and generated tokens is limited by the model's context length.", + defaultValue: 2048, + }), + topP: Property.Number({ + displayName: 'Top P', + required: false, + description: + 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.', + defaultValue: 1, + }), + frequencyPenalty: Property.Number({ + displayName: 'Frequency penalty', + required: false, + description: + "Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.", + defaultValue: 0, + }), + presencePenalty: Property.Number({ + displayName: 'Presence penalty', + required: false, + description: + "Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.", + defaultValue: 0.6, + }), + memoryKey: Property.ShortText({ + displayName: 'Memory Key', + description: + 'A memory key that will keep the chat history shared across runs and flows. Keep it empty to leave Groq without memory of previous messages.', + required: false, + }), + roles: Property.Json({ + displayName: 'Roles', + required: false, + description: 'Array of roles to specify more accurate response', + defaultValue: [{ role: 'system', content: 'You are a helpful assistant.' }], + }), + }, + async run({ auth, propsValue, store }) { + const { + model, + temperature, + maxTokens, + topP, + frequencyPenalty, + presencePenalty, + prompt, + memoryKey, + } = propsValue; + + let messageHistory: any[] | null = []; + // If memory key is set, retrieve messages stored in history + if (memoryKey) { + messageHistory = (await store.get(memoryKey, StoreScope.PROJECT)) ?? []; + } + + // Add user prompt to message history + messageHistory.push({ + role: 'user', + content: prompt, + }); + + // Add system instructions if set by user + const rolesArray = propsValue.roles ? (propsValue.roles as any) : []; + const roles = rolesArray.map((item: any) => { + const rolesEnum = ['system', 'user', 'assistant']; + if (!rolesEnum.includes(item.role)) { + throw new Error('The only available roles are: [system, user, assistant]'); + } + + return { + role: item.role, + content: item.content, + }; + }); + + // Send prompt + const completion = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.groq.com/openai/v1/chat/completions', + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + body: { + model: model, + messages: [...roles, ...messageHistory], + temperature: temperature, + top_p: topP, + frequency_penalty: frequencyPenalty, + presence_penalty: presencePenalty, + max_completion_tokens: maxTokens, + }, + }); + + // Add response to message history + messageHistory = [...messageHistory, completion.body.choices[0].message]; + + // Store history if memory key is set + if (memoryKey) { + await store.put(memoryKey, messageHistory, StoreScope.PROJECT); + } + + // Get the raw content from the response + const rawContent = completion.body.choices[0].message.content; + + // Check if the response contains thinking (content inside tags) + const thinkRegex = /([\s\S]*?)<\/think>/; + const thinkMatch = rawContent.match(thinkRegex); + + // Create the response structure + const responseStructure = []; + + if (thinkMatch) { + // Extract the thinking content + const thinkContent = thinkMatch[1].trim(); + + // Extract the final answer (content after the last tag) + const finalContent = rawContent.split('').pop()?.trim() || ''; + + // Add to response structure + responseStructure.push({ + Think: thinkContent, + Content: finalContent + }); + } else { + // If no thinking tags, just return the content as is + responseStructure.push({ + Think: null, + Content: rawContent + }); + } + + return responseStructure; + }, +}); diff --git a/packages/pieces/community/groq/src/lib/actions/transcribe-audio.ts b/packages/pieces/community/groq/src/lib/actions/transcribe-audio.ts new file mode 100644 index 0000000..d31a716 --- /dev/null +++ b/packages/pieces/community/groq/src/lib/actions/transcribe-audio.ts @@ -0,0 +1,125 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { groqAuth } from '../..'; +import { httpClient, HttpMethod, AuthenticationType } from '@activepieces/pieces-common'; + +export const transcribeAudio = createAction({ + auth: groqAuth, + name: 'transcribe-audio', + displayName: 'Transcribe Audio', + description: 'Transcribes audio into text in the input language.', + props: { + file: Property.File({ + displayName: 'Audio File', + required: true, + description: + 'The audio file to transcribe. Supported formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, webm.', + }), + model: Property.Dropdown({ + displayName: 'Model', + required: true, + description: 'The model to use for transcription.', + refreshers: [], + defaultValue: 'whisper-large-v3', + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your Groq account first.', + options: [], + }; + } + try { + const response = await httpClient.sendRequest({ + url: 'https://api.groq.com/openai/v1/models', + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + // Filter for whisper models only + const models = (response.body.data as Array<{ id: string }>).filter((model) => + model.id.toLowerCase().includes('whisper'), + ); + return { + disabled: false, + options: models.map((model) => { + return { + label: model.id, + value: model.id, + }; + }), + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: "Couldn't load models, API key is invalid", + }; + } + }, + }), + language: Property.ShortText({ + displayName: 'Language', + required: false, + description: + 'The language of the input audio in ISO-639-1 format (e.g., "en" for English). This will improve accuracy and latency.', + }), + prompt: Property.LongText({ + displayName: 'Prompt', + required: false, + description: + "An optional text to guide the model's style or continue a previous audio segment. The prompt should match the audio language.", + }), + temperature: Property.Number({ + displayName: 'Temperature', + required: false, + description: + 'The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.', + defaultValue: 0, + }), + responseFormat: Property.StaticDropdown({ + displayName: 'Response Format', + required: false, + description: 'The format of the transcript output.', + defaultValue: 'json', + options: { + disabled: false, + options: [ + { label: 'JSON', value: 'json' }, + { label: 'Text', value: 'text' }, + { label: 'Verbose JSON', value: 'verbose_json' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const { file, model, language, prompt, temperature, responseFormat } = propsValue; + + // Create form data + const formData = new FormData(); + formData.append('file', new Blob([file.data]), file.filename); + formData.append('model', model); + + if (language) formData.append('language', language); + if (prompt) formData.append('prompt', prompt); + if (temperature !== undefined) formData.append('temperature', temperature.toString()); + if (responseFormat) formData.append('response_format', responseFormat); + + // Send request + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.groq.com/openai/v1/audio/transcriptions', + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + headers: { + 'Content-Type': 'multipart/form-data', + }, + body: formData, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/groq/src/lib/actions/translate-audio.ts b/packages/pieces/community/groq/src/lib/actions/translate-audio.ts new file mode 100644 index 0000000..7298054 --- /dev/null +++ b/packages/pieces/community/groq/src/lib/actions/translate-audio.ts @@ -0,0 +1,118 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { groqAuth } from '../..'; +import { httpClient, HttpMethod, AuthenticationType } from '@activepieces/pieces-common'; + +export const translateAudio = createAction({ + auth: groqAuth, + name: 'translate-audio', + displayName: 'Translate Audio', + description: 'Translates audio into English text.', + props: { + file: Property.File({ + displayName: 'Audio File', + required: true, + description: + 'The audio file to translate. Supported formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, webm.', + }), + model: Property.Dropdown({ + displayName: 'Model', + required: true, + description: 'The model to use for translation.', + refreshers: [], + defaultValue: 'whisper-large-v3', + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your Groq account first.', + options: [], + }; + } + try { + const response = await httpClient.sendRequest({ + url: 'https://api.groq.com/openai/v1/models', + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + // Filter for whisper models only + const models = (response.body.data as Array<{ id: string }>).filter((model) => + model.id.toLowerCase().includes('whisper'), + ); + return { + disabled: false, + options: models.map((model) => { + return { + label: model.id, + value: model.id, + }; + }), + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: "Couldn't load models, API key is invalid", + }; + } + }, + }), + prompt: Property.LongText({ + displayName: 'Prompt', + required: false, + description: + "An optional text in English to guide the model's style or continue a previous audio segment.", + }), + temperature: Property.Number({ + displayName: 'Temperature', + required: false, + description: + 'The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.', + defaultValue: 0, + }), + responseFormat: Property.StaticDropdown({ + displayName: 'Response Format', + required: false, + description: 'The format of the translation output.', + defaultValue: 'json', + options: { + disabled: false, + options: [ + { label: 'JSON', value: 'json' }, + { label: 'Text', value: 'text' }, + { label: 'Verbose JSON', value: 'verbose_json' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const { file, model, prompt, temperature, responseFormat } = propsValue; + + // Create form data + const formData = new FormData(); + formData.append('file', new Blob([file.data]), file.filename); + formData.append('model', model); + + if (prompt) formData.append('prompt', prompt); + if (temperature !== undefined) formData.append('temperature', temperature.toString()); + if (responseFormat) formData.append('response_format', responseFormat); + + // Send request + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.groq.com/openai/v1/audio/translations', + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + headers: { + 'Content-Type': 'multipart/form-data', + }, + body: formData, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/groq/tsconfig.json b/packages/pieces/community/groq/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/groq/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/groq/tsconfig.lib.json b/packages/pieces/community/groq/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/groq/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/hackernews/.babelrc b/packages/pieces/community/hackernews/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/hackernews/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/hackernews/.eslintrc.json b/packages/pieces/community/hackernews/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/hackernews/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/hackernews/README.md b/packages/pieces/community/hackernews/README.md new file mode 100644 index 0000000..e6fa504 --- /dev/null +++ b/packages/pieces/community/hackernews/README.md @@ -0,0 +1,7 @@ +# pieces-hackernews + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-hackernews` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/hackernews/package.json b/packages/pieces/community/hackernews/package.json new file mode 100644 index 0000000..aaabebe --- /dev/null +++ b/packages/pieces/community/hackernews/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-hackernews", + "version": "0.3.4" +} \ No newline at end of file diff --git a/packages/pieces/community/hackernews/project.json b/packages/pieces/community/hackernews/project.json new file mode 100644 index 0000000..4463e4b --- /dev/null +++ b/packages/pieces/community/hackernews/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-hackernews", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/hackernews/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/hackernews", + "tsConfig": "packages/pieces/community/hackernews/tsconfig.lib.json", + "packageJson": "packages/pieces/community/hackernews/package.json", + "main": "packages/pieces/community/hackernews/src/index.ts", + "assets": [ + "packages/pieces/community/hackernews/*.md", + { + "input": "packages/pieces/community/hackernews/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/hackernews/src/index.ts b/packages/pieces/community/hackernews/src/index.ts new file mode 100644 index 0000000..2ad943b --- /dev/null +++ b/packages/pieces/community/hackernews/src/index.ts @@ -0,0 +1,15 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { fetchTopStories } from './lib/actions/top-stories-in-hacker-news'; + +export const hackernews = createPiece({ + displayName: 'Hacker News', + description: 'A social news website', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/hackernews.png', + auth: PieceAuth.None(), + categories: [], + authors: ["kishanprmr","AbdulTheActivePiecer","khaledmashaly","abuaboud"], + actions: [fetchTopStories], + triggers: [], +}); diff --git a/packages/pieces/community/hackernews/src/lib/actions/top-stories-in-hacker-news.ts b/packages/pieces/community/hackernews/src/lib/actions/top-stories-in-hacker-news.ts new file mode 100644 index 0000000..792e673 --- /dev/null +++ b/packages/pieces/community/hackernews/src/lib/actions/top-stories-in-hacker-news.ts @@ -0,0 +1,39 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const fetchTopStories = createAction({ + name: 'fetch_top_stories', // Must be a unique across the piece, this shouldn't be changed. + displayName: 'Fetch Top Stories', + description: 'Fetch top stories from hackernews', + props: { + // Properties to ask from the user, in this ask we will take number of + number_of_stories: Property.Number({ + displayName: 'Number of Stories', + description: undefined, + required: true, + }), + }, + async run(configValue) { + const HACKER_NEWS_API_URL = 'https://hacker-news.firebaseio.com/v0/'; + const topStoryIdsResponse = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${HACKER_NEWS_API_URL}topstories.json`, + }); + const topStoryIds: string[] = topStoryIdsResponse.body; + const topStories: Record[] = []; + for ( + let i = 0; + i < + Math.min(configValue.propsValue['number_of_stories'], topStoryIds.length); + i++ + ) { + const storyId = topStoryIds[i]; + const storyResponse = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${HACKER_NEWS_API_URL}item/${storyId}.json`, + }); + topStories.push(storyResponse.body); + } + return topStories; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/hackernews/tsconfig.json b/packages/pieces/community/hackernews/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/hackernews/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/hackernews/tsconfig.lib.json b/packages/pieces/community/hackernews/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/hackernews/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/harvest/.eslintrc.json b/packages/pieces/community/harvest/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/harvest/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/harvest/README.md b/packages/pieces/community/harvest/README.md new file mode 100644 index 0000000..ba47e72 --- /dev/null +++ b/packages/pieces/community/harvest/README.md @@ -0,0 +1,7 @@ +# pieces-harvest + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-harvest` to build the library. diff --git a/packages/pieces/community/harvest/package.json b/packages/pieces/community/harvest/package.json new file mode 100644 index 0000000..8a5bcf1 --- /dev/null +++ b/packages/pieces/community/harvest/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-harvest", + "version": "0.0.1" +} diff --git a/packages/pieces/community/harvest/project.json b/packages/pieces/community/harvest/project.json new file mode 100644 index 0000000..91be3f0 --- /dev/null +++ b/packages/pieces/community/harvest/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-harvest", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/harvest/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/harvest", + "tsConfig": "packages/pieces/community/harvest/tsconfig.lib.json", + "packageJson": "packages/pieces/community/harvest/package.json", + "main": "packages/pieces/community/harvest/src/index.ts", + "assets": [ + "packages/pieces/community/harvest/*.md", + { + "input": "packages/pieces/community/harvest/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/harvest/src/index.ts b/packages/pieces/community/harvest/src/index.ts new file mode 100644 index 0000000..1390d2e --- /dev/null +++ b/packages/pieces/community/harvest/src/index.ts @@ -0,0 +1,44 @@ + + import { createPiece, PieceAuth, OAuth2PropertyValue } from "@activepieces/pieces-framework"; + import { OAuth2GrantType, PieceCategory } from '@activepieces/shared'; + import { getInvoices } from './lib/actions/get-invoices'; + import { getProjects } from './lib/actions/get-projects'; + import { getTasks } from './lib/actions/get-tasks'; + import { getClients } from './lib/actions/get-clients'; + import { getEstimates } from './lib/actions/get-estimates'; + import { getExpenses } from './lib/actions/get-expenses'; + import { getTime_entries } from './lib/actions/get-time_entries'; + import { getRoles } from './lib/actions/get-roles'; + import { getUsers } from './lib/actions/get-users'; + import { reportsUninvoiced } from './lib/actions/reports-uninvoiced'; + import { createCustomApiCallAction } from '@activepieces/pieces-common'; + + export const harvestAuth = PieceAuth.OAuth2({ + required: true, + grantType: OAuth2GrantType.AUTHORIZATION_CODE, + authUrl: 'https://id.getharvest.com/oauth2/authorize', + tokenUrl: `https://id.getharvest.com/api/v2/oauth2/token`, + scope: ['harvest:all'], + }); + + export const harvest = createPiece({ + displayName: "Harvest", + auth: harvestAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: "https://cdn.activepieces.com/pieces/harvest.png", + categories:[PieceCategory.PRODUCTIVITY], + description:'Time Tracking Software with Invoicing', + authors: ["D-Rowe-FS"], + actions: [getClients, getEstimates, getExpenses, getInvoices, getProjects, getRoles, getTasks, getTime_entries, getUsers, + reportsUninvoiced, + createCustomApiCallAction({ + baseUrl: () => `https://api.harvestapp.com/v2/`, + auth: harvestAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [], + }); + \ No newline at end of file diff --git a/packages/pieces/community/harvest/src/lib/actions/get-clients.ts b/packages/pieces/community/harvest/src/lib/actions/get-clients.ts new file mode 100644 index 0000000..12a15f3 --- /dev/null +++ b/packages/pieces/community/harvest/src/lib/actions/get-clients.ts @@ -0,0 +1,61 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { harvestAuth } from '../..'; +import { + getAccessTokenOrThrow, + HttpMethod, +} from '@activepieces/pieces-common'; +import { callHarvestApi, filterDynamicFields } from '../common'; +import { propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const getClients = createAction({ + name: 'get_clients', + auth: harvestAuth, + displayName: 'Get Clients', + description: 'Fetches Clients', + props: { + is_active: Property.ShortText({ + description: 'Pass `true` to only return active clients and `false` to return inactive clients.', + displayName: 'Is Active', + required: false, + }), + updated_since: Property.ShortText({ + description: 'Only return clients that have been updated since the given date and time.', + displayName: 'Updated since', + required: false, + }), + page: Property.ShortText({ + description: 'DEPRECATED: The page number to use in pagination.', + displayName: 'Page', + required: false, + }), + per_page: Property.ShortText({ + description: 'The number of records to return per page. (1-2000)', + displayName: 'Records per page', + required: false, + }), +}, +async run(context) { + // Validate the input properties using Zod + await propsValidation.validateZod(context.propsValue, { + per_page: z + .string() + .optional() + .transform((val) => (val === undefined || val === '' ? undefined : parseInt(val, 10))) + .refine( + (val) => val === undefined || (Number.isInteger(val) && val >= 1 && val <= 2000), + 'Per Page must be a number between 1 and 2000.' + ), + }); + + const params = filterDynamicFields(context.propsValue); + + const response = await callHarvestApi( + HttpMethod.GET, + `clients`, + getAccessTokenOrThrow(context.auth), + params + ); + + return response.body; }, +}); diff --git a/packages/pieces/community/harvest/src/lib/actions/get-estimates.ts b/packages/pieces/community/harvest/src/lib/actions/get-estimates.ts new file mode 100644 index 0000000..e5fda19 --- /dev/null +++ b/packages/pieces/community/harvest/src/lib/actions/get-estimates.ts @@ -0,0 +1,78 @@ +import { Property, createAction } from "@activepieces/pieces-framework"; +import { harvestAuth } from '../..'; +import { + getAccessTokenOrThrow, + HttpMethod, +} from '@activepieces/pieces-common'; +import { callHarvestApi, filterDynamicFields } from '../common'; +import { propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const getEstimates = createAction({ + name: 'get_estimates', // Must be a unique across the piece, this shouldn't be changed. + auth: harvestAuth, + displayName: 'Get Estimates', + description: 'Fetches Estimates', + props: { + from: Property.ShortText({ + description: 'Only return estimates with an issue_date on or after the given date. (YYYY-MM-DD)', + displayName: 'From', + required: false, + }), + to: Property.ShortText({ + description: 'Only return estimates with an issue_date on or before the given date. (YYYY-MM-DD)', + displayName: 'To', + required: false, + }), + state: Property.ShortText({ + description: 'Only return estimates with a state matching the value provided. Options: draft, open, accepted, or declined.', + displayName: 'State', + required: false, + }), + updated_since: Property.ShortText({ + description: 'Only return estimates that have been updated since the given date and time.', + displayName: 'Updated since', + required: false, + }), + client_id: Property.ShortText({ + description: 'Only return estimates belonging to the client with the given ID.', + displayName: 'Client Id', + required: false, + }), + page: Property.ShortText({ + description: 'The page number to use in pagination.', + displayName: 'Page', + required: false, + }), + per_page: Property.ShortText({ + description: 'The number of records to return per page. (1-2000)', + displayName: 'Records per page', + required: false, + }), + + }, + async run(context) { + // Validate the input properties using Zod + await propsValidation.validateZod(context.propsValue, { + per_page: z + .string() + .optional() + .transform((val) => (val === undefined || val === '' ? undefined : parseInt(val, 10))) + .refine( + (val) => val === undefined || (Number.isInteger(val) && val >= 1 && val <= 2000), + 'Per Page must be a number between 1 and 2000.' + ), + }); + + const params = filterDynamicFields(context.propsValue); + + const response = await callHarvestApi( + HttpMethod.GET, + `estimates`, + getAccessTokenOrThrow(context.auth), + params + ); + + return response.body; }, +}); + diff --git a/packages/pieces/community/harvest/src/lib/actions/get-expenses.ts b/packages/pieces/community/harvest/src/lib/actions/get-expenses.ts new file mode 100644 index 0000000..6747e29 --- /dev/null +++ b/packages/pieces/community/harvest/src/lib/actions/get-expenses.ts @@ -0,0 +1,88 @@ +import { Property, createAction } from "@activepieces/pieces-framework"; +import { harvestAuth } from '../..'; +import { + getAccessTokenOrThrow, + HttpMethod, +} from '@activepieces/pieces-common'; +import { callHarvestApi, filterDynamicFields } from '../common'; +import { propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const getExpenses = createAction({ + name: 'get_expenses', // Must be a unique across the piece, this shouldn't be changed. + auth: harvestAuth, + displayName: 'Get Expenses', + description: 'Fetches expenses', + props: { + from: Property.ShortText({ + description: 'Only return expenses with an spent_date on or after the given date. (YYYY-MM-DD)', + displayName: 'From', + required: false, + }), + to: Property.ShortText({ + description: 'Only return expenses with an spent_date on or before the given date. (YYYY-MM-DD)', + displayName: 'To', + required: false, + }), + user_id: Property.ShortText({ + description: 'Only return expenses belonging to the user with the given ID.', + displayName: 'User Id', + required: false, + }), + client_id: Property.ShortText({ + description: 'Only return expenses belonging to the client with the given ID.', + displayName: 'Client Id', + required: false, + }), + project_id: Property.ShortText({ + description: 'Only return expenses belonging to the project with the given ID.', + displayName: 'Project Id', + required: false, + }), + is_billed: Property.ShortText({ + description: 'Pass `true` to only return expenses that have been invoiced and `false` to return expenses that have not been invoiced.', + displayName: 'Is Billed', + required: false, + }), + updated_since: Property.ShortText({ + description: 'Only return expenses that have been updated since the given date and time.', + displayName: 'Updated since', + required: false, + }), + page: Property.ShortText({ + description: 'The page number to use in pagination.', + displayName: 'Page', + required: false, + }), + per_page: Property.ShortText({ + description: 'The number of records to return per page. (1-2000)', + displayName: 'Records per page', + required: false, + }), + + }, + async run(context) { + // Validate the input properties using Zod + await propsValidation.validateZod(context.propsValue, { + per_page: z + .string() + .optional() + .transform((val) => (val === undefined || val === '' ? undefined : parseInt(val, 10))) + .refine( + (val) => val === undefined || (Number.isInteger(val) && val >= 1 && val <= 2000), + 'Per Page must be a number between 1 and 2000.' + ), + }); + + const params = filterDynamicFields(context.propsValue); + + const response = await callHarvestApi( + HttpMethod.GET, + `expenses`, + getAccessTokenOrThrow(context.auth), + params + ); + + return response.body; }, +}); + diff --git a/packages/pieces/community/harvest/src/lib/actions/get-invoices.ts b/packages/pieces/community/harvest/src/lib/actions/get-invoices.ts new file mode 100644 index 0000000..e6f2c62 --- /dev/null +++ b/packages/pieces/community/harvest/src/lib/actions/get-invoices.ts @@ -0,0 +1,83 @@ +import { Property, createAction } from "@activepieces/pieces-framework"; +import { harvestAuth } from '../..'; +import { + getAccessTokenOrThrow, + HttpMethod, +} from '@activepieces/pieces-common'; +import { callHarvestApi, filterDynamicFields } from '../common'; +import { propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const getInvoices = createAction({ + name: 'get_invoices', // Must be a unique across the piece, this shouldn't be changed. + auth: harvestAuth, + displayName: 'Get Invoices', + description: 'Fetches invoices', + props: { + from: Property.ShortText({ + description: 'Only return invoices with an issue_date on or after the given date. (YYYY-MM-DD)', + displayName: 'From', + required: false, + }), + to: Property.ShortText({ + description: 'Only return invoices with an issue_date on or before the given date. (YYYY-MM-DD)', + displayName: 'To', + required: false, + }), + state: Property.ShortText({ + description: 'Only return invoices with a state matching the value provided. Options: draft, open, paid, or closed.', + displayName: 'State', + required: false, + }), + updated_since: Property.ShortText({ + description: 'Only return invoices that have been updated since the given date and time.', + displayName: 'Updated since', + required: false, + }), + client_id: Property.ShortText({ + description: 'Only return invoices belonging to the client with the given ID.', + displayName: 'Client Id', + required: false, + }), + project_id: Property.ShortText({ + description: 'Only return invoices belonging to the project with the given ID.', + displayName: 'Project Id', + required: false, + }), + page: Property.ShortText({ + description: 'The page number to use in pagination.', + displayName: 'Page', + required: false, + }), + per_page: Property.ShortText({ + description: 'The number of records to return per page. (1-2000)', + displayName: 'Records per page', + required: false, + }), + + }, + async run(context) { + // Validate the input properties using Zod + await propsValidation.validateZod(context.propsValue, { + per_page: z + .string() + .optional() + .transform((val) => (val === undefined || val === '' ? undefined : parseInt(val, 10))) + .refine( + (val) => val === undefined || (Number.isInteger(val) && val >= 1 && val <= 2000), + 'Per Page must be a number between 1 and 2000.' + ), + }); + + const params = filterDynamicFields(context.propsValue); + + const response = await callHarvestApi( + HttpMethod.GET, + `invoices`, + getAccessTokenOrThrow(context.auth), + params + ); + + return response.body; }, +}); + diff --git a/packages/pieces/community/harvest/src/lib/actions/get-projects.ts b/packages/pieces/community/harvest/src/lib/actions/get-projects.ts new file mode 100644 index 0000000..2ccecfe --- /dev/null +++ b/packages/pieces/community/harvest/src/lib/actions/get-projects.ts @@ -0,0 +1,67 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { harvestAuth } from '../..'; +import { + getAccessTokenOrThrow, + HttpMethod, +} from '@activepieces/pieces-common'; +import { callHarvestApi, filterDynamicFields } from '../common'; +import { propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const getProjects = createAction({ + name: 'get_projects', // Must be a unique across the piece, this shouldn't be changed. + auth: harvestAuth, + displayName: 'Get Projects', + description: 'Fetches projects', + props: { + is_active: Property.ShortText({ + description: 'Pass `true` to only return active projects and `false` to return inactive projects.', + displayName: 'Is Active', + required: false, + }), + updated_since: Property.ShortText({ + description: 'Only return projects that have been updated since the given date and time.', + displayName: 'Updated since', + required: false, + }), + client_id: Property.ShortText({ + description: 'Only return invoices belonging to the client with the given ID.', + displayName: 'Client Id', + required: false, + }), + page: Property.ShortText({ + description: 'DEPRECATED: The page number to use in pagination.', + displayName: 'Page', + required: false, + }), + per_page: Property.ShortText({ + description: 'The number of records to return per page. (1-2000)', + displayName: 'Records per page', + required: false, + }), + }, + async run(context) { + // Validate the input properties using Zod + await propsValidation.validateZod(context.propsValue, { + per_page: z + .string() + .optional() + .transform((val) => (val === undefined || val === '' ? undefined : parseInt(val, 10))) + .refine( + (val) => val === undefined || (Number.isInteger(val) && val >= 1 && val <= 2000), + 'Per Page must be a number between 1 and 2000.' + ), + }); + + const params = filterDynamicFields(context.propsValue); + + const response = await callHarvestApi( + HttpMethod.GET, + `projects`, + getAccessTokenOrThrow(context.auth), + params + ); + + return response.body; }, +}); + diff --git a/packages/pieces/community/harvest/src/lib/actions/get-roles.ts b/packages/pieces/community/harvest/src/lib/actions/get-roles.ts new file mode 100644 index 0000000..2b7d489 --- /dev/null +++ b/packages/pieces/community/harvest/src/lib/actions/get-roles.ts @@ -0,0 +1,52 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { harvestAuth } from '../..'; +import { + getAccessTokenOrThrow, + HttpMethod, +} from '@activepieces/pieces-common'; +import { callHarvestApi, filterDynamicFields } from '../common'; +import { propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const getRoles = createAction({ + name: 'get_roles', // Must be a unique across the piece, this shouldn't be changed. + auth: harvestAuth, + displayName: 'Get Roles', + description: 'Fetches Roles', + props: { + page: Property.ShortText({ + description: 'DEPRECATED: The page number to use in pagination.', + displayName: 'Page', + required: false, + }), + per_page: Property.ShortText({ + description: 'The number of records to return per page. (1-2000)', + displayName: 'Records per page', + required: false, + }), + }, + async run(context) { + // Validate the input properties using Zod + await propsValidation.validateZod(context.propsValue, { + per_page: z + .string() + .optional() + .transform((val) => (val === undefined || val === '' ? undefined : parseInt(val, 10))) + .refine( + (val) => val === undefined || (Number.isInteger(val) && val >= 1 && val <= 2000), + 'Per Page must be a number between 1 and 2000.' + ), + }); + + const params = filterDynamicFields(context.propsValue); + + const response = await callHarvestApi( + HttpMethod.GET, + `roles`, + getAccessTokenOrThrow(context.auth), + params + ); + + return response.body; }, +}); + diff --git a/packages/pieces/community/harvest/src/lib/actions/get-tasks.ts b/packages/pieces/community/harvest/src/lib/actions/get-tasks.ts new file mode 100644 index 0000000..b5be558 --- /dev/null +++ b/packages/pieces/community/harvest/src/lib/actions/get-tasks.ts @@ -0,0 +1,62 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { harvestAuth } from '../..'; +import { + getAccessTokenOrThrow, + HttpMethod, +} from '@activepieces/pieces-common'; +import { callHarvestApi, filterDynamicFields } from '../common'; +import { propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const getTasks = createAction({ + name: 'get_tasks', // Must be a unique across the piece, this shouldn't be changed. + auth: harvestAuth, + displayName: 'Get Tasks', + description: 'Fetches Tasks', + props: { + is_active: Property.ShortText({ + description: 'Pass `true` to only return active tasks and `false` to return inactive tasks.', + displayName: 'Is Active', + required: false, + }), + updated_since: Property.ShortText({ + description: 'Only return tasks that have been updated since the given date and time.', + displayName: 'Updated since', + required: false, + }), + page: Property.ShortText({ + description: 'DEPRECATED: The page number to use in pagination.', + displayName: 'Page', + required: false, + }), + per_page: Property.ShortText({ + description: 'The number of records to return per page. (1-2000)', + displayName: 'Records per page', + required: false, + }), + }, + async run(context) { + // Validate the input properties using Zod + await propsValidation.validateZod(context.propsValue, { + per_page: z + .string() + .optional() + .transform((val) => (val === undefined || val === '' ? undefined : parseInt(val, 10))) + .refine( + (val) => val === undefined || (Number.isInteger(val) && val >= 1 && val <= 2000), + 'Per Page must be a number between 1 and 2000.' + ), + }); + + const params = filterDynamicFields(context.propsValue); + + const response = await callHarvestApi( + HttpMethod.GET, + `tasks`, + getAccessTokenOrThrow(context.auth), + params + ); + + return response.body; }, +}); + diff --git a/packages/pieces/community/harvest/src/lib/actions/get-time_entries.ts b/packages/pieces/community/harvest/src/lib/actions/get-time_entries.ts new file mode 100644 index 0000000..4b33f8f --- /dev/null +++ b/packages/pieces/community/harvest/src/lib/actions/get-time_entries.ts @@ -0,0 +1,103 @@ +import { Property, createAction } from "@activepieces/pieces-framework"; +import { harvestAuth } from '../..'; +import { + getAccessTokenOrThrow, + HttpMethod, +} from '@activepieces/pieces-common'; +import { callHarvestApi, filterDynamicFields } from '../common'; +import { propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const getTime_entries = createAction({ + name: 'get_time_entries', // Must be a unique across the piece, this shouldn't be changed. + auth: harvestAuth, + displayName: 'Get Time Entries', + description: 'Fetches Time Entries', + props: { + from: Property.ShortText({ + description: 'Only return time entries with an spent_date on or after the given date. (YYYY-MM-DD)', + displayName: 'From', + required: false, + }), + to: Property.ShortText({ + description: 'Only return time entries with an spent_date on or before the given date. (YYYY-MM-DD)', + displayName: 'To', + required: false, + }), + user_id: Property.ShortText({ + description: 'Only return time entries belonging to the user with the given ID.', + displayName: 'User Id', + required: false, + }), + client_id: Property.ShortText({ + description: 'Only return time entries belonging to the client with the given ID.', + displayName: 'Client Id', + required: false, + }), + project_id: Property.ShortText({ + description: 'Only return time entries belonging to the project with the given ID.', + displayName: 'Project Id', + required: false, + }), + task_id: Property.ShortText({ + description: 'Only return time entries belonging to the task with the given ID.', + displayName: 'Task Id', + required: false, + }), + external_reference_id: Property.ShortText({ + description: 'Only return time entries with the given external reference ID.', + displayName: 'External Reference Id', + required: false, + }), + is_billed: Property.ShortText({ + description: 'Pass `true` to only return time entries that have been invoiced and `false` to return time entries that have not been invoiced.', + displayName: 'Is Billed', + required: false, + }), + is_running: Property.ShortText({ + description: 'Pass `true` to only return running time entries and `false` to return non-running time entries.', + displayName: 'Is Running', + required: false, + }), + updated_since: Property.ShortText({ + description: 'Only return time entries that have been updated since the given date and time.', + displayName: 'Updated since', + required: false, + }), + page: Property.ShortText({ + description: 'The page number to use in pagination.', + displayName: 'Page', + required: false, + }), + per_page: Property.ShortText({ + description: 'The number of records to return per page. (1-2000)', + displayName: 'Records per page', + required: false, + }), + + }, + async run(context) { + // Validate the input properties using Zod + await propsValidation.validateZod(context.propsValue, { + per_page: z + .string() + .optional() + .transform((val) => (val === undefined || val === '' ? undefined : parseInt(val, 10))) + .refine( + (val) => val === undefined || (Number.isInteger(val) && val >= 1 && val <= 2000), + 'Per Page must be a number between 1 and 2000.' + ), + }); + + const params = filterDynamicFields(context.propsValue); + + const response = await callHarvestApi( + HttpMethod.GET, + `time_entries`, + getAccessTokenOrThrow(context.auth), + params + ); + + return response.body; }, +}); + diff --git a/packages/pieces/community/harvest/src/lib/actions/get-users.ts b/packages/pieces/community/harvest/src/lib/actions/get-users.ts new file mode 100644 index 0000000..302a699 --- /dev/null +++ b/packages/pieces/community/harvest/src/lib/actions/get-users.ts @@ -0,0 +1,61 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { harvestAuth } from '../..'; +import { + getAccessTokenOrThrow, + HttpMethod, +} from '@activepieces/pieces-common'; +import { callHarvestApi, filterDynamicFields } from '../common'; +import { propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const getUsers = createAction({ + name: 'get_users', + auth: harvestAuth, + displayName: 'Get Users', + description: 'Fetches Users', + props: { + is_active: Property.ShortText({ + description: 'Pass `true` to only return active users and `false` to return inactive users.', + displayName: 'Is Active', + required: false, + }), + updated_since: Property.ShortText({ + description: 'Only return users that have been updated since the given date and time.', + displayName: 'Updated since', + required: false, + }), + page: Property.ShortText({ + description: 'DEPRECATED: The page number to use in pagination.', + displayName: 'Page', + required: false, + }), + per_page: Property.ShortText({ + description: 'The number of records to return per page. (1-2000)', + displayName: 'Records per page', + required: false, + }), +}, +async run(context) { + // Validate the input properties using Zod + await propsValidation.validateZod(context.propsValue, { + per_page: z + .string() + .optional() + .transform((val) => (val === undefined || val === '' ? undefined : parseInt(val, 10))) + .refine( + (val) => val === undefined || (Number.isInteger(val) && val >= 1 && val <= 2000), + 'Per Page must be a number between 1 and 2000.' + ), + }); + + const params = filterDynamicFields(context.propsValue); + + const response = await callHarvestApi( + HttpMethod.GET, + `users`, + getAccessTokenOrThrow(context.auth), + params + ); + + return response.body; }, +}); diff --git a/packages/pieces/community/harvest/src/lib/actions/reports-uninvoiced.ts b/packages/pieces/community/harvest/src/lib/actions/reports-uninvoiced.ts new file mode 100644 index 0000000..8f4d360 --- /dev/null +++ b/packages/pieces/community/harvest/src/lib/actions/reports-uninvoiced.ts @@ -0,0 +1,66 @@ +import { Property, createAction } from "@activepieces/pieces-framework"; +import { harvestAuth } from '../..'; +import { + getAccessTokenOrThrow, + HttpMethod, +} from '@activepieces/pieces-common'; +import { callHarvestApi, filterDynamicFields } from '../common'; +import { propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const reportsUninvoiced = createAction({ + name: 'reports-uninvoiced', + auth: harvestAuth, + displayName: 'Uninvoiced Report', + description: 'Uninvoiced hours and expenses for all billable projects', + props: { + from: Property.ShortText({ + description: 'Only report on time entries and expenses with a spent_date on or after the given date. (YYYY-MM-DD)', + displayName: 'From', + required: true, + }), + to: Property.ShortText({ + description: 'Only report on time entries and expenses with a spent_date on or before the given date. (YYYY-MM-DD)', + displayName: 'To', + required: true, + }), + include_fixed_fee: Property.ShortText({ + description: 'Whether or not to include fixed-fee projects in the response. (Default: true)', + displayName: 'Include Fixed Fee', + required: false, + }), + page: Property.ShortText({ + description: 'The page number to use in pagination.', + displayName: 'Page', + required: false, + }), + per_page: Property.ShortText({ + description: 'The number of records to return per page. (1-2000)', + displayName: 'Records per page', + required: false, + }), + }, + async run(context) { + // Validate the input properties using Zod + await propsValidation.validateZod(context.propsValue, { + per_page: z + .string() + .optional() + .transform((val) => (val === undefined || val === '' ? undefined : parseInt(val, 10))) + .refine( + (val) => val === undefined || (Number.isInteger(val) && val >= 1 && val <= 2000), + 'Per Page must be a number between 1 and 2000.' + ), + }); + + const params = filterDynamicFields(context.propsValue); + + const response = await callHarvestApi( + HttpMethod.GET, + `reports/uninvoiced`, + getAccessTokenOrThrow(context.auth), + params + ); + + return response.body; }, +}); \ No newline at end of file diff --git a/packages/pieces/community/harvest/src/lib/common/index.ts b/packages/pieces/community/harvest/src/lib/common/index.ts new file mode 100644 index 0000000..2ea7a14 --- /dev/null +++ b/packages/pieces/community/harvest/src/lib/common/index.ts @@ -0,0 +1,48 @@ +import { DynamicPropsValue } from '@activepieces/pieces-framework'; +import { + HttpMethod, + HttpMessageBody, + HttpResponse, + httpClient, + AuthenticationType, +} from '@activepieces/pieces-common'; + +export async function callHarvestApi( + method: HttpMethod, + apiUrl: string, + accessToken: string, + queryParams: any | undefined = undefined, + body: any | undefined = undefined, + headers: any | undefined = undefined +): Promise> { + return await httpClient.sendRequest({ + method: method, + url: `https://api.harvestapp.com/v2/${apiUrl}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + headers, + body, + queryParams, + }); +} + +//Remove null/undefined values and create an array to be used for queryparams +export function filterDynamicFields(dynamicFields: DynamicPropsValue): { [key: string]: string } { + const fields: { [key: string]: string } = {}; + + const props = Object.entries(dynamicFields); + for (const [propertyKey, propertyValue] of props) { + if ( + propertyValue !== null && + propertyValue !== undefined && + propertyValue !== '' && + !(typeof propertyValue === 'string' && propertyValue.trim() === '') + ) { + fields[propertyKey] = propertyValue; + } + } + + return fields; +} \ No newline at end of file diff --git a/packages/pieces/community/harvest/tsconfig.json b/packages/pieces/community/harvest/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/harvest/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/harvest/tsconfig.lib.json b/packages/pieces/community/harvest/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/harvest/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/heartbeat/.eslintrc.json b/packages/pieces/community/heartbeat/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/heartbeat/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/heartbeat/README.md b/packages/pieces/community/heartbeat/README.md new file mode 100644 index 0000000..2173d43 --- /dev/null +++ b/packages/pieces/community/heartbeat/README.md @@ -0,0 +1,7 @@ +# pieces-heartbeat + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-heartbeat` to build the library. diff --git a/packages/pieces/community/heartbeat/package.json b/packages/pieces/community/heartbeat/package.json new file mode 100644 index 0000000..1984595 --- /dev/null +++ b/packages/pieces/community/heartbeat/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-heartbeat", + "version": "0.0.4" +} \ No newline at end of file diff --git a/packages/pieces/community/heartbeat/project.json b/packages/pieces/community/heartbeat/project.json new file mode 100644 index 0000000..d4bee78 --- /dev/null +++ b/packages/pieces/community/heartbeat/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-heartbeat", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/heartbeat/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/heartbeat", + "tsConfig": "packages/pieces/community/heartbeat/tsconfig.lib.json", + "packageJson": "packages/pieces/community/heartbeat/package.json", + "main": "packages/pieces/community/heartbeat/src/index.ts", + "assets": [ + "packages/pieces/community/heartbeat/*.md", + { + "input": "packages/pieces/community/heartbeat/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-community-heartbeat {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/heartbeat/src/index.ts b/packages/pieces/community/heartbeat/src/index.ts new file mode 100644 index 0000000..cc6f93e --- /dev/null +++ b/packages/pieces/community/heartbeat/src/index.ts @@ -0,0 +1,43 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { heartBeatCreateUser } from './lib/actions/create-user'; + +const markdownPropertyDescription = ` + 1. Login to your Heartbeat account + 2. On the bottom-left, click on 'Admin Settings' + 3. On the left panel, click on 'API Keys' + 5. Click on 'Create API Key' + 6. On the popup form, Enter the 'Label' to name the Key + 7. Copy the API key and paste it below. +`; + +export const heartbeatAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: markdownPropertyDescription, + required: true, +}); + +export const Heartbeat = createPiece({ + displayName: 'Heartbeat', + description: 'Monitoring and alerting made easy', + + auth: heartbeatAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/heartbeat.png', + categories: [PieceCategory.COMMUNICATION], + authors: ["kanarelo","kishanprmr","abuaboud"], + actions: [ + heartBeatCreateUser, + createCustomApiCallAction({ + auth: heartbeatAuth, + baseUrl: () => 'https://api.heartbeat.chat/v0', + authMapping: async (auth) => { + return { + Authorization: `Bearer ${auth}`, + }; + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/heartbeat/src/lib/actions/create-user.ts b/packages/pieces/community/heartbeat/src/lib/actions/create-user.ts new file mode 100644 index 0000000..e4d02dd --- /dev/null +++ b/packages/pieces/community/heartbeat/src/lib/actions/create-user.ts @@ -0,0 +1,200 @@ +import { + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + httpClient, + propsValidation, +} from '@activepieces/pieces-common'; +import { heartbeatAuth } from '../..'; +import { z } from 'zod'; + +export const heartBeatCreateUser = createAction({ + auth: heartbeatAuth, + name: 'heartbeat_create_user', + displayName: 'Create User', + description: 'Create a new user in a Heartbeat community', + props: { + name: Property.ShortText({ + displayName: 'Name', + description: "The user's full name", + required: true, + }), + email: Property.ShortText({ + displayName: 'Email', + description: "The user's email. Must be unique to the community", + required: true, + }), + role_id: Property.Dropdown({ + displayName: 'Roles', + description: 'The role the user should have', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + placeholder: 'Please select a connection', + }; + + const response = await httpClient.sendRequest< + { + id: string; + name: string; + }[] + >({ + method: HttpMethod.GET, + url: `https://api.heartbeat.chat/v0/roles`, + headers: { + 'content-type': 'application/json', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + body: {}, + }); + + if (response.status === 200) { + return { + options: response.body.map((role) => ({ + label: role.name, + value: role.id, + })), + disabled: false, + }; + } + + return { + options: [], + disabled: true, + placeholder: 'Error loading roles.', + }; + }, + }), + group_ids: Property.MultiSelectDropdown({ + displayName: 'Groups', + description: + 'A list of the ids of the groups that the user should belong to.', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + placeholder: 'Error loading groups', + }; + + const response = await httpClient.sendRequest< + { + id: string; + name: string; + }[] + >({ + method: HttpMethod.GET, + url: `https://api.heartbeat.chat/v0/groups`, + headers: { + 'content-type': 'application/json', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + body: {}, + }); + + if (response.status === 200) { + return { + options: response.body.map((group) => ({ + label: group.name, + value: group.id, + })), + disabled: false, + }; + } + + return { + disabled: true, + options: [], + placeholder: 'Error loading groups', + }; + }, + }), + profile_picture: Property.ShortText({ + displayName: 'Profile Picture', + description: + 'A Data URI scheme in the JPG, GIF, or PNG format. Ensure you use the proper content type (image/jpeg, image/png, image/gif) that matches the image data being provided', + required: false, + }), + bio: Property.ShortText({ + displayName: 'Bio', + description: "The user's bio", + required: false, + }), + status: Property.ShortText({ + displayName: 'Status', + description: "The user's status", + required: false, + }), + linkedin: Property.LongText({ + displayName: 'LinkedIn', + description: "A link to the user's LinkedIn profile", + required: false, + }), + twitter: Property.LongText({ + displayName: 'Twitter', + description: "A link to the user's Twitter profile", + required: false, + }), + instagram: Property.LongText({ + displayName: 'Instagram', + description: "A link to the user's Instagram profile", + required: false, + }), + create_introduction_thread: Property.Checkbox({ + displayName: 'Create introduction thread', + description: + 'If true and a value for bio is provided, an introduction thread for the user will be created in the channel designated for introductions in your community settings.', + required: false, + }), + }, + async run({ auth, propsValue }) { + await propsValidation.validateZod(propsValue, { + email: z.string().email(), + linkedin: z.string().url().optional(), + twitter: z.string().url().optional(), + instagram: z.string().url().optional() + }); + + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: `https://api.heartbeat.chat/v0/users`, + headers: { + 'content-type': 'application/json', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + body: { + name: propsValue.name, + email: propsValue.email, + roleID: propsValue.role_id, + groupIDs: propsValue.group_ids, + profilePicture: propsValue.profile_picture, + bio: propsValue.bio, + status: propsValue.status, + linkedin: propsValue.linkedin, + twitter: propsValue.twitter, + instagram: propsValue.instagram, + createIntroductionThread: propsValue.create_introduction_thread, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/heartbeat/tsconfig.json b/packages/pieces/community/heartbeat/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/heartbeat/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/heartbeat/tsconfig.lib.json b/packages/pieces/community/heartbeat/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/heartbeat/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/heygen/.eslintrc.json b/packages/pieces/community/heygen/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/heygen/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/heygen/README.md b/packages/pieces/community/heygen/README.md new file mode 100644 index 0000000..149ab19 --- /dev/null +++ b/packages/pieces/community/heygen/README.md @@ -0,0 +1,7 @@ +# pieces-heygen + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-heygen` to build the library. diff --git a/packages/pieces/community/heygen/package.json b/packages/pieces/community/heygen/package.json new file mode 100644 index 0000000..2b23419 --- /dev/null +++ b/packages/pieces/community/heygen/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-heygen", + "version": "0.0.1" +} diff --git a/packages/pieces/community/heygen/project.json b/packages/pieces/community/heygen/project.json new file mode 100644 index 0000000..7378dcd --- /dev/null +++ b/packages/pieces/community/heygen/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-heygen", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/heygen/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/heygen", + "tsConfig": "packages/pieces/community/heygen/tsconfig.lib.json", + "packageJson": "packages/pieces/community/heygen/package.json", + "main": "packages/pieces/community/heygen/src/index.ts", + "assets": [ + "packages/pieces/community/heygen/*.md", + { + "input": "packages/pieces/community/heygen/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/heygen/src/index.ts b/packages/pieces/community/heygen/src/index.ts new file mode 100644 index 0000000..8282f7d --- /dev/null +++ b/packages/pieces/community/heygen/src/index.ts @@ -0,0 +1,42 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createVideoFromTemplateAction } from './lib/actions/create-a-video-from-template'; +import { retrieveTranslatedVideoStatus } from './lib/actions/retrieve-a-translated-video-status'; +import { retrieveVideoStatusAction } from './lib/actions/retrieve-a-video-status'; +import { retrieveSharableVideoUrlAction } from './lib/actions/retrieve-shareable-link-for-a-video'; +import { uploadAssetAction } from './lib/actions/upload-an-asset'; +import { translateVideoAction } from './lib/actions/translate-a-video'; +import { videoGenerationCompletedTrigger } from './lib/triggers/video-generation-completed'; +import { videoGenerationFailedTrigger } from './lib/triggers/video-generation-failed'; + +import { heygenAuth } from './lib/common/auth'; +import { BASE_URL_V1 } from './lib/common/client'; + +export const heygen = createPiece({ + displayName: 'HeyGen', + description: 'Generate and manage AI avatar videos using HeyGen.', + auth: heygenAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/heygen.jpg', + authors: ['krushnarout'], + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + actions: [ + createVideoFromTemplateAction, + retrieveTranslatedVideoStatus, + retrieveVideoStatusAction, + retrieveSharableVideoUrlAction, + uploadAssetAction, + translateVideoAction, + createCustomApiCallAction({ + auth: heygenAuth, + baseUrl: () => BASE_URL_V1, + authMapping: async (auth) => { + return { + 'X-Api-Key': auth as string, + }; + }, + }), + ], + triggers: [videoGenerationCompletedTrigger, videoGenerationFailedTrigger], +}); diff --git a/packages/pieces/community/heygen/src/lib/actions/create-a-video-from-template.ts b/packages/pieces/community/heygen/src/lib/actions/create-a-video-from-template.ts new file mode 100644 index 0000000..57cf2d7 --- /dev/null +++ b/packages/pieces/community/heygen/src/lib/actions/create-a-video-from-template.ts @@ -0,0 +1,164 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { heygenAuth } from '../common/auth'; +import { heygenApiCall } from '../common/client'; +import { + folderDropdown, + brandVoiceDropdown, + templateDropdown, + templateVariables, +} from '../common/props'; +import { isNil } from '@activepieces/shared'; + +export const createVideoFromTemplateAction = createAction({ + auth: heygenAuth, + name: 'create-video-from-template', + displayName: 'Create Video from Template', + description: 'Create a video using a selected template.', + props: { + templateId: templateDropdown, + title: Property.ShortText({ + displayName: 'Video Title', + required: true, + description: 'Title of the generated video.', + }), + caption: Property.Checkbox({ + displayName: 'Enable Captions', + required: false, + defaultValue: false, + }), + includeGif: Property.Checkbox({ + displayName: 'Include GIF Preview', + required: false, + defaultValue: false, + }), + enableSharing: Property.Checkbox({ + displayName: 'Enable Public Sharing', + required: false, + defaultValue: false, + }), + folderId: folderDropdown, + brandVoiceId: brandVoiceDropdown, + callbackUrl: Property.ShortText({ + displayName: 'Callback URL', + required: false, + description: 'Webhook URL to notify when video rendering is complete.', + }), + dimensionWidth: Property.Number({ + displayName: 'Video Width', + required: false, + defaultValue: 1280, + }), + dimensionHeight: Property.Number({ + displayName: 'Video Height', + required: false, + defaultValue: 720, + }), + variables: templateVariables, + }, + async run({ propsValue, auth }) { + const { + templateId, + title, + caption, + includeGif, + enableSharing, + folderId, + brandVoiceId, + callbackUrl, + dimensionWidth, + dimensionHeight, + } = propsValue; + + const inputVariables = propsValue.variables ?? {}; + + const template = await heygenApiCall<{ + data: { + variables: { [x: string]: { type: string; name: string; properties: Record } }; + }; + }>({ + apiKey: auth as unknown as string, + method: HttpMethod.GET, + resourceUri: `/template/${templateId}`, + apiVersion: 'v2', + }); + + const templateVariables = template.data.variables; + const formattedVariables: Record = {}; + + for (const [key, value] of Object.entries(inputVariables)) { + if (isNil(value) || value === '') continue; + + const variable = templateVariables[key]; + if (!variable) continue; + + const { type, name, properties } = variable; + + const base = { name, type }; + + switch (type) { + case 'text': + formattedVariables[key] = { + ...base, + properties: { content: value }, + }; + break; + case 'image': + case 'video': + case 'audio': + formattedVariables[key] = { + ...base, + properties: { ...properties, url: value }, + }; + break; + case 'character': + formattedVariables[key] = { + ...base, + properties: { ...properties, character_id: value }, + }; + break; + case 'voice': + formattedVariables[key] = { + ...base, + properties: { ...properties, voice_id: value }, + }; + break; + default: + break; + } + } + + const body: Record = { + template_id: templateId, + title, + caption: caption === true, + include_gif: includeGif === true, + enable_sharing: enableSharing === true, + }; + + if (folderId) body['folder_id'] = folderId; + if (brandVoiceId) body['brand_voice_id'] = brandVoiceId; + if (callbackUrl) body['callback_url'] = callbackUrl; + + if (dimensionWidth && dimensionHeight) { + body['dimension'] = { + width: dimensionWidth, + height: dimensionHeight, + }; + } + + if (Object.keys(formattedVariables || {}).length) { + body['variables'] = formattedVariables; + } + + const response = await heygenApiCall({ + apiKey: auth as string, + method: HttpMethod.POST, + resourceUri: `/template/${templateId}/generate`, + body, + apiVersion: 'v2', + }); + + return response; + }, +}); diff --git a/packages/pieces/community/heygen/src/lib/actions/retrieve-a-translated-video-status.ts b/packages/pieces/community/heygen/src/lib/actions/retrieve-a-translated-video-status.ts new file mode 100644 index 0000000..590e887 --- /dev/null +++ b/packages/pieces/community/heygen/src/lib/actions/retrieve-a-translated-video-status.ts @@ -0,0 +1,30 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { heygenAuth } from '../common/auth'; +import { heygenApiCall } from '../common/client'; + +export const retrieveTranslatedVideoStatus = createAction({ + auth: heygenAuth, + name: 'retrieve-translated-video-status', + displayName: 'Retrieve Translated Video Status', + description: 'Retrieves the status of a translated video.', + props: { + videoId: Property.ShortText({ + displayName: 'Video ID', + required: true, + description: 'The ID of the translated video to check the status for.', + }), + }, + async run({ propsValue, auth }) { + const { videoId } = propsValue; + + const response = await heygenApiCall({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: `/video_translate/${videoId}`, + apiVersion: 'v2', + }); + + return response; + }, +}); diff --git a/packages/pieces/community/heygen/src/lib/actions/retrieve-a-video-status.ts b/packages/pieces/community/heygen/src/lib/actions/retrieve-a-video-status.ts new file mode 100644 index 0000000..08ec9be --- /dev/null +++ b/packages/pieces/community/heygen/src/lib/actions/retrieve-a-video-status.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { heygenApiCall } from '../common/client'; +import { heygenAuth } from '../common/auth'; + +export const retrieveVideoStatusAction = createAction({ + auth: heygenAuth, + name: 'retrieve_video_status', + displayName: 'Retrieve Video Status', + description: 'Retrieve the status and details of a video using its ID.', + props: { + videoId: Property.ShortText({ + displayName: 'Video ID', + description: 'The ID of the video to retrieve the status for.', + required: true, + }), + }, + async run({ propsValue, auth }) { + const { videoId } = propsValue; + + const response = await heygenApiCall({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: `/video_status.get`, + query: { video_id: videoId }, + apiVersion: 'v1', + }); + + return response; + }, +}); diff --git a/packages/pieces/community/heygen/src/lib/actions/retrieve-shareable-link-for-a-video.ts b/packages/pieces/community/heygen/src/lib/actions/retrieve-shareable-link-for-a-video.ts new file mode 100644 index 0000000..3a5d5a8 --- /dev/null +++ b/packages/pieces/community/heygen/src/lib/actions/retrieve-shareable-link-for-a-video.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { heygenApiCall } from '../common/client'; +import { heygenAuth } from '../common/auth'; + +export const retrieveSharableVideoUrlAction = createAction({ + auth: heygenAuth, + name: 'retrieve_sharable_video_url', + displayName: 'Retrieve Sharable Video URL', + description: 'Generates a public URL for a video, allowing it to be shared and accessed publicly.', + props: { + videoId: Property.ShortText({ + displayName: 'Video ID', + description: 'The ID of the video to generate a shareable URL for.', + required: true, + }), + }, + async run({ propsValue, auth }) { + const { videoId } = propsValue; + + const response = await heygenApiCall({ + apiKey: auth as string, + method: HttpMethod.POST, + resourceUri: '/video/share', + body: { video_id: videoId }, + apiVersion: 'v1', + }); + + return response; + }, +}); diff --git a/packages/pieces/community/heygen/src/lib/actions/translate-a-video.ts b/packages/pieces/community/heygen/src/lib/actions/translate-a-video.ts new file mode 100644 index 0000000..4e50447 --- /dev/null +++ b/packages/pieces/community/heygen/src/lib/actions/translate-a-video.ts @@ -0,0 +1,83 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { heygenApiCall } from '../common/client'; +import { heygenAuth } from '../common/auth'; +import { brandVoiceDropdown, supportedLanguagesDropdown } from '../common/props'; + +export const translateVideoAction = createAction({ + auth: heygenAuth, + name: 'translate_video', + displayName: 'Translate Video', + description: 'Translate a video into 175+ languages with natural voice and lip-sync.', + props: { + videoUrl: Property.ShortText({ + displayName: 'Video URL', + required: true, + description: + 'URL of the video file to be translated. Supports direct URLs, Google Drive, and YouTube.', + }), + title: Property.ShortText({ + displayName: 'Title', + required: false, + description: 'Optional title of the translated video.', + }), + outputLanguage: supportedLanguagesDropdown, + translateAudioOnly: Property.Checkbox({ + displayName: 'Translate Audio Only', + required: false, + defaultValue: false, + description: 'Only translate the audio without modifying faces.', + }), + speakerNum: Property.Number({ + displayName: 'Number of Speakers', + required: false, + description: 'Number of speakers in the video (if applicable).', + }), + brandVoiceId: brandVoiceDropdown, + + callbackId: Property.ShortText({ + displayName: 'Callback ID', + required: false, + description: 'Custom ID returned in webhook callback.', + }), + callbackUrl: Property.ShortText({ + displayName: 'Callback URL', + required: false, + description: 'URL to notify when translation is complete.', + }), + }, + async run({ propsValue, auth }) { + const { + videoUrl, + title, + outputLanguage, + translateAudioOnly, + speakerNum, + callbackId, + brandVoiceId, + callbackUrl, + } = propsValue; + + const body: Record = { + video_url: videoUrl, + output_language: outputLanguage, + }; + + if (title) body['title'] = title; + if (translateAudioOnly) body['translate_audio_only'] = translateAudioOnly; + if (speakerNum) body['speaker_num'] = speakerNum; + if (callbackId) body['callback_id'] = callbackId; + if (brandVoiceId) body['brand_voice_id'] = brandVoiceId; + if (callbackUrl) body['callback_url'] = callbackUrl; + + const response = await heygenApiCall({ + apiKey: auth as string, + method: HttpMethod.POST, + resourceUri: '/video_translate', + body, + apiVersion: 'v2', + }); + + return response; + }, +}); diff --git a/packages/pieces/community/heygen/src/lib/actions/upload-an-asset.ts b/packages/pieces/community/heygen/src/lib/actions/upload-an-asset.ts new file mode 100644 index 0000000..d9b6609 --- /dev/null +++ b/packages/pieces/community/heygen/src/lib/actions/upload-an-asset.ts @@ -0,0 +1,55 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { heygenAuth } from '../common/auth'; + +export const uploadAssetAction = createAction({ + auth: heygenAuth, + name: 'upload_asset', + displayName: 'Upload an Asset', + description: + 'Upload media files (images, videos, or audio) to HeyGen. Supports JPEG, PNG, MP4, WEBM, and MPEG files.', + props: { + file: Property.File({ + displayName: 'File', + description: 'The file to upload (JPEG, PNG, MP4, WEBM, or MPEG).', + required: true, + }), + }, + async run(context) { + const { file } = context.propsValue; + + const getContentType = (filename: string): string => { + const extension = filename.toLowerCase().split('.').pop(); + switch (extension) { + case 'jpg': + case 'jpeg': + return 'image/jpeg'; + case 'png': + return 'image/png'; + case 'mp4': + return 'video/mp4'; + case 'webm': + return 'video/webm'; + case 'mpeg': + case 'mpg': + return 'audio/mpeg'; + default: + throw new Error(`Unsupported file type: ${extension}`); + } + }; + + const contentType = getContentType(file.filename); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://upload.heygen.com/v1/asset', + headers: { + 'x-api-key': context.auth as string, + 'Content-Type': contentType, + }, + body: file.data, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/heygen/src/lib/common/auth.ts b/packages/pieces/community/heygen/src/lib/common/auth.ts new file mode 100644 index 0000000..0171f4e --- /dev/null +++ b/packages/pieces/community/heygen/src/lib/common/auth.ts @@ -0,0 +1,25 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { PieceAuth } from '@activepieces/pieces-framework'; +import { heygenApiCall } from './client'; + +export const heygenAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: `You can obtain your API key by navigating to your Space Settings in HeyGen App.`, + required: true, + validate: async ({ auth }) => { + try { + await heygenApiCall({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: '/user/me', + apiVersion: 'v1', + }); + return { valid: true }; + } catch { + return { + valid: false, + error: 'Invalid API Key.', + }; + } + }, +}); diff --git a/packages/pieces/community/heygen/src/lib/common/client.ts b/packages/pieces/community/heygen/src/lib/common/client.ts new file mode 100644 index 0000000..9471e83 --- /dev/null +++ b/packages/pieces/community/heygen/src/lib/common/client.ts @@ -0,0 +1,53 @@ +import { + httpClient, + HttpMessageBody, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; + +export type HeygenApiCallParams = { + apiKey: string; + method: HttpMethod; + resourceUri: string; + query?: Record; + body?: unknown; + apiVersion: 'v1' | 'v2'; +}; + +export const BASE_URL_V1 = 'https://api.heygen.com/v1'; +export const BASE_URL_V2 = 'https://api.heygen.com/v2'; + +export async function heygenApiCall({ + apiKey, + method, + resourceUri, + query, + body, + apiVersion, +}: HeygenApiCallParams): Promise { + const qs: QueryParams = {}; + + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + qs[key] = String(value); + } + } + } + + const url = (apiVersion === 'v1' ? BASE_URL_V1 : BASE_URL_V2) + resourceUri; + + const request: HttpRequest = { + method, + url, + headers: { + 'X-Api-Key': apiKey, + }, + queryParams: qs, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} diff --git a/packages/pieces/community/heygen/src/lib/common/props.ts b/packages/pieces/community/heygen/src/lib/common/props.ts new file mode 100644 index 0000000..9872315 --- /dev/null +++ b/packages/pieces/community/heygen/src/lib/common/props.ts @@ -0,0 +1,259 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { heygenApiCall } from './client'; +import { isNil } from '@activepieces/shared'; + +export const folderDropdown = Property.Dropdown({ + displayName: 'Folder', + description: 'Select the folder to store the video.', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + const response = await heygenApiCall<{ + data: { folders: { id: string; name: string }[] }; + }>({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: '/folders', + apiVersion: 'v1', + }); + + return { + disabled: false, + options: response.data.folders.map((folder) => ({ + label: folder.name, + value: folder.id, + })), + }; + }, +}); + +export const brandVoiceDropdown = Property.Dropdown({ + displayName: 'Brand Voice', + description: 'Select the Brand Voice to apply to the video.', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + const response = await heygenApiCall<{ + data: { list: { id: string; name: string }[] }; + }>({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: '/brand_voice/list', + apiVersion: 'v1', + }); + + return { + disabled: false, + options: response.data.list.map((voice) => ({ + label: voice.name, + value: voice.id, + })), + }; + }, +}); + +export const templateDropdown = Property.Dropdown({ + displayName: 'Template', + description: 'Select the template to generate the video.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + const response = await heygenApiCall<{ + data: { templates: { template_id: string; name: string; aspect_ratio: string }[] }; + }>({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: '/templates', + apiVersion: 'v2', + }); + + return { + disabled: false, + options: response.data.templates.map((template) => ({ + label: template.name, + value: template.template_id, + })), + }; + }, +}); + +export const supportedLanguagesDropdown = Property.Dropdown({ + displayName: 'Supported Language', + description: 'Select the language for video translation.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + const response = await heygenApiCall<{ data: { languages: string[] } }>({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: '/video_translate/target_languages', + apiVersion: 'v2', + }); + + return { + disabled: false, + options: response.data.languages.map((lang) => ({ + label: lang, + value: lang, + })), + }; + }, +}); + +export const templateVariables = Property.DynamicProperties({ + displayName: 'Template Varriables', + refreshers: ['templateId'], + required: false, + props: async ({ auth, templateId }) => { + if (!auth || !templateId) return {}; + + const fields: DynamicPropsValue = {}; + + try { + const response = await heygenApiCall<{ + data: { variables: { [x: string]: { type: string; name: string } } }; + }>({ + apiKey: auth as unknown as string, + method: HttpMethod.GET, + resourceUri: `/template/${templateId}`, + apiVersion: 'v2', + }); + + const variables = response.data.variables; + + if (!isNil(variables)) return {}; + + for (const [key, value] of Object.entries(response.data.variables)) { + const fieldKey = key; + const fieldType = value.type; + + switch (fieldType) { + case 'text': + fields[fieldKey] = Property.ShortText({ + displayName: fieldKey, + required: false, + description: 'Provide text value.', + }); + break; + case 'image': + fields[fieldKey] = Property.ShortText({ + displayName: fieldKey, + required: false, + description: 'Provide image URL.', + }); + break; + case 'video': + fields[fieldKey] = Property.ShortText({ + displayName: fieldKey, + required: false, + description: 'Provide video URL.', + }); + break; + case 'audio': + fields[fieldKey] = Property.ShortText({ + displayName: fieldKey, + required: false, + description: 'Provide audio URL.', + }); + break; + case 'character': { + const characters = await heygenApiCall<{ + avatars: { avatar_name: string; avatar_id: string }[]; + talking_photos: { + talking_photo_id: string; + talking_photo_name: string; + }[]; + }>({ + apiKey: auth as unknown as string, + method: HttpMethod.GET, + resourceUri: `/avatars`, + apiVersion: 'v2', + }); + + const options = [ + ...characters.avatars.map((avatar) => ({ + label: avatar.avatar_name, + value: avatar.avatar_id, + })), + ...characters.talking_photos.map((photo) => ({ + label: photo.talking_photo_name, + value: photo.talking_photo_id, + })), + ]; + + fields[fieldKey] = Property.StaticDropdown({ + displayName: fieldKey, + required: false, + description: 'Select one of avatar or talking photo.', + options: { disabled: false, options }, + }); + break; + } + case 'voice': { + const voices = await heygenApiCall<{ + voices: { name: string; voice_id: string }[]; + }>({ + apiKey: auth as unknown as string, + method: HttpMethod.GET, + resourceUri: `/voices`, + apiVersion: 'v2', + }); + + fields[fieldKey] = Property.StaticDropdown({ + displayName: fieldKey, + required: false, + options: { + disabled: false, + options: voices.voices.map((voice) => ({ + label: voice.name, + value: voice.voice_id, + })), + }, + }); + break; + } + default: + break; + } + } + + return fields; + } catch (error) { + console.error(`${error instanceof Error ? error.message : 'Unknown error'}`); + return {}; + } + }, +}); diff --git a/packages/pieces/community/heygen/src/lib/triggers/video-generation-completed.ts b/packages/pieces/community/heygen/src/lib/triggers/video-generation-completed.ts new file mode 100644 index 0000000..fcc829f --- /dev/null +++ b/packages/pieces/community/heygen/src/lib/triggers/video-generation-completed.ts @@ -0,0 +1,67 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { heygenApiCall } from '../common/client'; +import { heygenAuth } from '../common/auth'; + +const TRIGGER_KEY = 'video_generation_completed_trigger'; + +export const videoGenerationCompletedTrigger = createTrigger({ + auth: heygenAuth, + name: 'video_generation_completed', + displayName: 'New Avatar Video Event (Success)', + description: 'Triggers when a video is generated successfully.', + type: TriggerStrategy.WEBHOOK, + props: {}, + sampleData: { + event_type: 'avatar_video.success', + event_data: { + video_id: '123', + url: 'https://www.example.com', + gif_download_url: '', + folder_id: '123', + callback_id: '123', + }, + }, + + async onEnable(context) { + const webhook = (await heygenApiCall({ + apiKey: context.auth as string, + method: HttpMethod.POST, + resourceUri: '/webhook/endpoint.add', + apiVersion: 'v1', + body: { + url: context.webhookUrl, + events: ['avatar_video.success'], + }, + })) as { data: { endpoint_id: string } }; + + await context.store.put(TRIGGER_KEY, webhook.data.endpoint_id); + }, + + async onDisable(context) { + const webhookId = await context.store.get(TRIGGER_KEY); + + if (webhookId) { + await heygenApiCall({ + apiKey: context.auth as string, + method: HttpMethod.DELETE, + resourceUri: '/webhook/endpoint.delete', + apiVersion: 'v1', + query: { + endpoint_id: webhookId, + }, + }); + } + }, + + async run(context) { + const payload = context.payload.body as { + event_type: string; + event_data: Record; + }; + + if (payload.event_type !== 'avatar_video.success') return []; + + return [payload.event_data]; + }, +}); diff --git a/packages/pieces/community/heygen/src/lib/triggers/video-generation-failed.ts b/packages/pieces/community/heygen/src/lib/triggers/video-generation-failed.ts new file mode 100644 index 0000000..ad40fea --- /dev/null +++ b/packages/pieces/community/heygen/src/lib/triggers/video-generation-failed.ts @@ -0,0 +1,66 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { heygenApiCall } from '../common/client'; +import { heygenAuth } from '../common/auth'; + +const TRIGGER_KEY = 'video_generation_failed_trigger'; + +export const videoGenerationFailedTrigger = createTrigger({ + auth: heygenAuth, + name: 'video_generation_failed', + displayName: 'New Avatar Video Event (Fail)', + description: 'Triggers when a video generation process fails.', + type: TriggerStrategy.WEBHOOK, + props: {}, + + sampleData: { + event_type: 'avatar_video.fail', + event_data: { + video_id: 'abc', + msg: 'Failed', + callback_id: '123', + }, + }, + + async onEnable(context) { + const webhook = (await heygenApiCall({ + apiKey: context.auth as string, + method: HttpMethod.POST, + resourceUri: '/webhook/endpoint.add', + apiVersion: 'v1', + body: { + url: context.webhookUrl, + events: ['avatar_video.fail'], + }, + })) as { data: { endpoint_id: string } }; + + await context.store.put(TRIGGER_KEY, webhook.data.endpoint_id); + }, + + async onDisable(context) { + const webhookId = await context.store.get(TRIGGER_KEY); + + if (webhookId) { + await heygenApiCall({ + apiKey: context.auth as string, + method: HttpMethod.DELETE, + resourceUri: '/webhook/endpoint.delete', + apiVersion: 'v1', + query: { + endpoint_id: webhookId, + }, + }); + } + }, + + async run(context) { + const payload = context.payload.body as { + event_type: string; + event_data: Record; + }; + + if (payload.event_type !== 'avatar_video.fail') return []; + + return [payload]; + }, +}); diff --git a/packages/pieces/community/heygen/tsconfig.json b/packages/pieces/community/heygen/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/heygen/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/heygen/tsconfig.lib.json b/packages/pieces/community/heygen/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/heygen/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/http-oauth2/.eslintrc.json b/packages/pieces/community/http-oauth2/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/http-oauth2/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/http-oauth2/README.md b/packages/pieces/community/http-oauth2/README.md new file mode 100644 index 0000000..c237029 --- /dev/null +++ b/packages/pieces/community/http-oauth2/README.md @@ -0,0 +1,7 @@ +# pieces-http-oauth2 + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-http-oauth2` to build the library. diff --git a/packages/pieces/community/http-oauth2/package.json b/packages/pieces/community/http-oauth2/package.json new file mode 100644 index 0000000..2c772f2 --- /dev/null +++ b/packages/pieces/community/http-oauth2/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-http-oauth2", + "version": "0.1.0" +} diff --git a/packages/pieces/community/http-oauth2/project.json b/packages/pieces/community/http-oauth2/project.json new file mode 100644 index 0000000..a2ad9d1 --- /dev/null +++ b/packages/pieces/community/http-oauth2/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-http-oauth2", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/http-oauth2/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/http-oauth2", + "tsConfig": "packages/pieces/community/http-oauth2/tsconfig.lib.json", + "packageJson": "packages/pieces/community/http-oauth2/package.json", + "main": "packages/pieces/community/http-oauth2/src/index.ts", + "assets": [ + "packages/pieces/community/http-oauth2/*.md", + { + "input": "packages/pieces/community/http-oauth2/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/http-oauth2/src/index.ts b/packages/pieces/community/http-oauth2/src/index.ts new file mode 100644 index 0000000..d75005d --- /dev/null +++ b/packages/pieces/community/http-oauth2/src/index.ts @@ -0,0 +1,42 @@ +import { createPiece, PieceAuth, Property } from "@activepieces/pieces-framework"; +import { httpOauth2RequestAction } from "./lib/actions/send-oauth2-http-request"; + +export const httpOauth2Auth = PieceAuth.OAuth2({ + description: 'OAuth2', + authUrl: '{authUrl}', + tokenUrl: '{tokenUrl}', + required: true, + scope: '{scopes}'.split(' '), + grantType: 'both_client_credentials_and_authorization_code', + props: { + authUrl: Property.ShortText({ + displayName: 'Authorize URL', + required: true, + description: 'OAuth2 Authorize URL', + }), + tokenUrl: Property.ShortText({ + displayName: 'Token URL', + required: true, + description: 'OAuth2 Token URL', + }), + scopes: Property.ShortText({ + displayName: 'Scopes (whitespace separated)', + required: true, + description: 'OAuth2 Scopes', + }), + }, +}); + +export const httpOauth2ClientCredentials = createPiece({ + displayName: "HTTP (OAuth2)", + auth: httpOauth2Auth, + minimumSupportedRelease: '0.56.0', + logoUrl: "https://cdn.activepieces.com/pieces/http.png", + authors: [ + 'mhshiba' + ], + actions: [ + httpOauth2RequestAction, + ], + triggers: [], +}); diff --git a/packages/pieces/community/http-oauth2/src/lib/actions/send-oauth2-http-request.ts b/packages/pieces/community/http-oauth2/src/lib/actions/send-oauth2-http-request.ts new file mode 100644 index 0000000..4c500e3 --- /dev/null +++ b/packages/pieces/community/http-oauth2/src/lib/actions/send-oauth2-http-request.ts @@ -0,0 +1,259 @@ +import { + AuthenticationType, + httpClient, + HttpError, + HttpHeaders, + HttpMessageBody, + HttpMethod, + HttpRequest, + HttpResponse, + QueryParams, +} from '@activepieces/pieces-common'; +import { httpOauth2Auth } from '../..'; +import { + createAction, + DynamicPropsValue, + Property, +} from '@activepieces/pieces-framework'; +import { assertNotNullOrUndefined } from '@activepieces/shared'; +import FormData from 'form-data'; +import { HttpsProxyAgent } from 'https-proxy-agent'; +import axios from 'axios'; + +export const httpOauth2RequestAction = createAction({ + auth: httpOauth2Auth, + name: 'send-oauth2-request', + displayName: 'Send an OAuth2 Request', + description: + 'Sends HTTP request to a specified URL that requires OAuth 2.0 authorization and returns the response.', + props: { + url: Property.ShortText({ + displayName: 'URL', + required: true, + }), + method: Property.StaticDropdown({ + displayName: 'Method', + required: true, + options: { + options: [ + { value: HttpMethod.GET, label: 'GET' }, + { value: HttpMethod.POST, label: 'POST' }, + { value: HttpMethod.PUT, label: 'PUT' }, + { value: HttpMethod.PATCH, label: 'PATCH' }, + { value: HttpMethod.DELETE, label: 'DELETE' }, + ], + }, + }), + headers: Property.Object({ + displayName: 'Headers', + required: false, + }), + queryParams: Property.Object({ + displayName: 'Query Parameters', + required: false, + }), + body_type: Property.StaticDropdown({ + displayName: 'Body Type', + required: false, + defaultValue: 'none', + options: { + disabled: false, + options: [ + { + label: 'None', + value: 'none', + }, + { + label: 'Form Data', + value: 'form_data', + }, + { + label: 'JSON', + value: 'json', + }, + { + label: 'Raw', + value: 'raw', + }, + ], + }, + }), + body: Property.DynamicProperties({ + displayName: 'Body', + refreshers: ['body_type'], + required: false, + props: async ({ body_type }) => { + if (!body_type) return {}; + + const bodyTypeInput = body_type as unknown as string; + + const fields: DynamicPropsValue = {}; + + switch (bodyTypeInput) { + case 'none': + break; + case 'json': + fields['data'] = Property.Json({ + displayName: 'JSON Body', + required: true, + }); + break; + case 'raw': + fields['data'] = Property.LongText({ + displayName: 'Raw Body', + required: true, + }); + break; + case 'form_data': + fields['data'] = Property.Object({ + displayName: 'Form Data', + required: true, + }); + break; + } + return fields; + }, + }), + use_proxy: Property.Checkbox({ + displayName: 'Use Proxy', + defaultValue: false, + description: 'Use a proxy for this request', + required: false, + }), + proxy_settings: Property.DynamicProperties({ + displayName: 'Proxy Settings', + refreshers: ['use_proxy'], + required: false, + props: async ({ use_proxy }) => { + if (!use_proxy) return {}; + + const fields: DynamicPropsValue = {}; + + fields['proxy_host'] = Property.ShortText({ + displayName: 'Proxy Host', + required: true, + }); + + fields['proxy_port'] = Property.Number({ + displayName: 'Proxy Port', + required: true, + }); + + fields['proxy_username'] = Property.ShortText({ + displayName: 'Proxy Username', + required: false, + }); + + fields['proxy_password'] = Property.ShortText({ + displayName: 'Proxy Password', + required: false, + }); + + return fields; + }, + }), + failsafe: Property.Checkbox({ + displayName: 'No Error on Failure', + required: false, + }), + timeout: Property.Number({ + displayName: 'Timeout (in seconds)', + required: false, + }), + }, + errorHandlingOptions: { + continueOnFailure: { hide: true }, + retryOnFailure: { defaultValue: true }, + }, + async run(context) { + const { + method, + url, + headers, + queryParams, + body, + body_type, + timeout, + failsafe, + use_proxy, + } = context.propsValue; + const { auth } = context; + + assertNotNullOrUndefined(method, 'Method'); + assertNotNullOrUndefined(url, 'URL'); + + const request: HttpRequest = { + method, + url, + headers: headers as HttpHeaders, + queryParams: queryParams as QueryParams, + timeout: timeout ? timeout * 1000 : 0, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }; + + if (body) { + const bodyInput = body['data']; + if (body_type === 'form_data') { + const formData = new FormData(); + for (const key in bodyInput) { + formData.append(key, bodyInput[key]); + } + request.body = formData; + request.headers = { ...request.headers, ...formData.getHeaders() }; + } else { + request.body = bodyInput; + } + } + + try { + if (use_proxy) { + const proxySettings = context.propsValue.proxy_settings; + assertNotNullOrUndefined(proxySettings, 'Proxy Settings'); + assertNotNullOrUndefined(proxySettings['proxy_host'], 'Proxy Host'); + assertNotNullOrUndefined(proxySettings['proxy_port'], 'Proxy Port'); + let proxyUrl; + + if ( + proxySettings['proxy_username'] && + proxySettings['proxy_password'] + ) { + proxyUrl = `http://${proxySettings['proxy_username']}:${proxySettings['proxy_password']}@${proxySettings['proxy_host']}:${proxySettings['proxy_port']}`; + } else { + proxyUrl = `http://${proxySettings['proxy_host']}:${proxySettings['proxy_port']}`; + } + + const httpsAgent = new HttpsProxyAgent(proxyUrl); + const axiosClient = axios.create({ + httpsAgent, + }); + + const proxied_response = await axiosClient.request(request); + return handleResponse(proxied_response.data); + } + const response = await httpClient.sendRequest(request); + return handleResponse(response); + } catch (error) { + if (failsafe) { + return (error as HttpError).errorMessage(); + } + + throw error; + } + }, +}); + +const handleResponse = (response: HttpResponse) => { + if ( + response.headers && + response.headers['content-type'] === 'application/octet-stream' + ) { + return { + ...response, + bodyBase64: Buffer.from(response.body, 'binary').toString('base64'), + }; + } + return response; +}; diff --git a/packages/pieces/community/http-oauth2/tsconfig.json b/packages/pieces/community/http-oauth2/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/http-oauth2/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/http-oauth2/tsconfig.lib.json b/packages/pieces/community/http-oauth2/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/http-oauth2/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/http/.babelrc b/packages/pieces/community/http/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/http/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/http/.eslintrc.json b/packages/pieces/community/http/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/http/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/http/README.md b/packages/pieces/community/http/README.md new file mode 100644 index 0000000..efad839 --- /dev/null +++ b/packages/pieces/community/http/README.md @@ -0,0 +1,7 @@ +# pieces-http + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-http` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/http/package.json b/packages/pieces/community/http/package.json new file mode 100644 index 0000000..cce663c --- /dev/null +++ b/packages/pieces/community/http/package.json @@ -0,0 +1,5 @@ +{ + "name": "@activepieces/piece-http", + "version": "0.8.0" +} + diff --git a/packages/pieces/community/http/project.json b/packages/pieces/community/http/project.json new file mode 100644 index 0000000..342d8fd --- /dev/null +++ b/packages/pieces/community/http/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-http", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/http/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/http", + "tsConfig": "packages/pieces/community/http/tsconfig.lib.json", + "packageJson": "packages/pieces/community/http/package.json", + "main": "packages/pieces/community/http/src/index.ts", + "assets": [ + "packages/pieces/community/http/*.md", + { + "input": "packages/pieces/community/http/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/http/src/index.ts b/packages/pieces/community/http/src/index.ts new file mode 100644 index 0000000..0094266 --- /dev/null +++ b/packages/pieces/community/http/src/index.ts @@ -0,0 +1,25 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { httpSendRequestAction } from './lib/actions/send-http-request-action'; + +export const http = createPiece({ + displayName: 'HTTP', + description: 'Sends HTTP requests and return responses', + logoUrl: 'https://cdn.activepieces.com/pieces/http.png', + categories: [PieceCategory.CORE], + auth: PieceAuth.None(), + minimumSupportedRelease: '0.20.3', + actions: [httpSendRequestAction], + authors: [ + 'bibhuty-did-this', + 'landonmoir', + 'JanHolger', + 'Salem-Alaa', + 'kishanprmr', + 'AbdulTheActivePiecer', + 'khaledmashaly', + 'abuaboud', + 'pfernandez98', + ], + triggers: [], +}); diff --git a/packages/pieces/community/http/src/lib/actions/send-http-request-action.ts b/packages/pieces/community/http/src/lib/actions/send-http-request-action.ts new file mode 100644 index 0000000..a7d008e --- /dev/null +++ b/packages/pieces/community/http/src/lib/actions/send-http-request-action.ts @@ -0,0 +1,320 @@ +import { + httpClient, + HttpError, + HttpHeaders, + HttpRequest, + QueryParams, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { + createAction, + DynamicPropsValue, + Property, +} from '@activepieces/pieces-framework'; +import { assertNotNullOrUndefined } from '@activepieces/shared'; +import FormData from 'form-data'; +import { httpMethodDropdown } from '../common/props'; +import { HttpsProxyAgent } from 'https-proxy-agent'; +import axios from 'axios'; + +enum AuthType { + NONE = 'NONE', + BASIC = AuthenticationType.BASIC, + BEARER_TOKEN = AuthenticationType.BEARER_TOKEN, +} + +export const httpSendRequestAction = createAction({ + name: 'send_request', + displayName: 'Send HTTP request', + description: 'Send HTTP request', + props: { + method: httpMethodDropdown, + url: Property.ShortText({ + displayName: 'URL', + required: true, + }), + headers: Property.Object({ + displayName: 'Headers', + required: true, + }), + queryParams: Property.Object({ + displayName: 'Query params', + required: true, + }), + authType: Property.StaticDropdown({ + displayName: 'Authentication', + required: true, + defaultValue: AuthType.NONE, + options: { + disabled: false, + options: [ + { label: 'None', value: AuthType.NONE }, + { label: 'Basic Auth', value: AuthType.BASIC }, + { label: 'Bearer Token', value: AuthType.BEARER_TOKEN }, + ], + }, + }), + authFields: Property.DynamicProperties({ + displayName: 'Authentication Fields', + required: false, + refreshers: ['authType'], + props: async ({ authType }) => { + if (!authType) { + return {}; + } + const authTypeEnum = authType.toString() as AuthType; + let fields: DynamicPropsValue = {}; + switch (authTypeEnum) { + case AuthType.NONE: + fields = {}; + break; + case AuthType.BASIC: + fields = { + username: Property.ShortText({ + displayName: 'Username', + description: 'The username to use for authentication.', + required: true, + }), + password: Property.ShortText({ + displayName: 'Password', + description: 'The password to use for authentication.', + required: true, + }), + }; + break; + case AuthType.BEARER_TOKEN: + fields = { + token: Property.ShortText({ + displayName: 'Token', + description: 'The Bearer token to use for authentication.', + required: true, + }), + }; + break; + default: + throw new Error('Invalid authentication type'); + } + return fields; + }, + }), + body_type: Property.StaticDropdown({ + displayName: 'Body Type', + required: false, + defaultValue: 'none', + options: { + disabled: false, + options: [ + { + label: 'None', + value: 'none', + }, + { + label: 'Form Data', + value: 'form_data', + }, + { + label: 'JSON', + value: 'json', + }, + { + label: 'Raw', + value: 'raw', + }, + ], + }, + }), + body: Property.DynamicProperties({ + displayName: 'Body', + refreshers: ['body_type'], + required: false, + props: async ({ body_type }) => { + if (!body_type) return {}; + + const bodyTypeInput = body_type as unknown as string; + + const fields: DynamicPropsValue = {}; + + switch (bodyTypeInput) { + case 'none': + break; + case 'json': + fields['data'] = Property.Json({ + displayName: 'JSON Body', + required: true, + }); + break; + case 'raw': + fields['data'] = Property.LongText({ + displayName: 'Raw Body', + required: true, + }); + break; + case 'form_data': + fields['data'] = Property.Object({ + displayName: 'Form Data', + required: true, + }); + break; + } + return fields; + }, + }), + use_proxy: Property.Checkbox({ + displayName: 'Use Proxy', + defaultValue: false, + description: 'Use a proxy for this request', + required: false, + }), + proxy_settings: Property.DynamicProperties({ + displayName: 'Proxy Settings', + refreshers: ['use_proxy'], + required: false, + props: async ({ use_proxy }) => { + if (!use_proxy) return {}; + + const fields: DynamicPropsValue = {}; + + fields['proxy_host'] = Property.ShortText({ + displayName: 'Proxy Host', + required: true, + }); + + fields['proxy_port'] = Property.Number({ + displayName: 'Proxy Port', + required: true, + }); + + fields['proxy_username'] = Property.ShortText({ + displayName: 'Proxy Username', + required: false, + }); + + fields['proxy_password'] = Property.ShortText({ + displayName: 'Proxy Password', + required: false, + }); + + return fields; + }, + }), + timeout: Property.Number({ + displayName: 'Timeout(in seconds)', + required: false, + }), + failureMode: Property.StaticDropdown({ + displayName: 'On Failure', + required: false, + options: { + disabled: false, + options: [ + { label: 'Retry on all errors (4xx, 5xx)', value: 'all' }, + { label: 'Continue flow', value: 'continue' }, + { label: 'Retry on internal errors (5xx)', value: '5xx' }, + ], + }, + }), + }, + errorHandlingOptions: { + continueOnFailure: { hide: true }, + retryOnFailure: { defaultValue: true }, + }, + async run(context) { + const { + method, + url, + headers, + queryParams, + body, + body_type, + timeout, + failureMode, + use_proxy, + authType, + authFields, + } = context.propsValue; + + assertNotNullOrUndefined(method, 'Method'); + assertNotNullOrUndefined(url, 'URL'); + + const request: HttpRequest = { + method, + url, + headers: headers as HttpHeaders, + queryParams: queryParams as QueryParams, + timeout: timeout ? timeout * 1000 : 0, + }; + + switch (authType) { + case AuthType.BASIC: + if (authFields) { + request.authentication = { + username: authFields['username'], + password: authFields['password'], + type: AuthenticationType.BASIC, + }; + } + break; + case AuthType.BEARER_TOKEN: + if (authFields) { + request.authentication = { + token: authFields['token'], + type: AuthenticationType.BEARER_TOKEN, + }; + } + break; + } + + if (body) { + const bodyInput = body['data']; + if (body_type === 'form_data') { + const formData = new FormData(); + for (const key in bodyInput) { + formData.append(key, bodyInput[key]); + } + request.body = formData; + request.headers = { ...request.headers, ...formData.getHeaders() }; + } else { + request.body = bodyInput; + } + } + + try { + if (use_proxy) { + const proxySettings = context.propsValue.proxy_settings; + assertNotNullOrUndefined(proxySettings, 'Proxy Settings'); + assertNotNullOrUndefined(proxySettings['proxy_host'], 'Proxy Host'); + assertNotNullOrUndefined(proxySettings['proxy_port'], 'Proxy Port'); + let proxyUrl; + + if (proxySettings.proxy_username && proxySettings.proxy_password) { + proxyUrl = `http://${proxySettings.proxy_username}:${proxySettings.proxy_password}@${proxySettings.proxy_host}:${proxySettings.proxy_port}`; + } else { + proxyUrl = `http://${proxySettings.proxy_host}:${proxySettings.proxy_port}`; + } + + const httpsAgent = new HttpsProxyAgent(proxyUrl); + const axiosClient = axios.create({ + httpsAgent, + }); + + const proxied_response = await axiosClient.request(request); + return proxied_response.data; + } + return await httpClient.sendRequest(request); + } catch (error) { + switch (failureMode) { + case 'all': { + throw error; + } case '5xx': + if ((error as HttpError).response.status >= 500 && (error as HttpError).response.status < 600) { + throw error; + } + return (error as HttpError).errorMessage(); + case 'continue': + return (error as HttpError).errorMessage(); + default: + throw error; + } + } + }, +}); diff --git a/packages/pieces/community/http/src/lib/common/props.ts b/packages/pieces/community/http/src/lib/common/props.ts new file mode 100644 index 0000000..1ea7ecb --- /dev/null +++ b/packages/pieces/community/http/src/lib/common/props.ts @@ -0,0 +1,13 @@ +import { Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; + +const httpMethodDropdownOptions = Object.values(HttpMethod).map((m) => ({ + label: m, + value: m, +})); + +export const httpMethodDropdown = Property.StaticDropdown({ + displayName: 'Method', + required: true, + options: { options: httpMethodDropdownOptions }, +}); diff --git a/packages/pieces/community/http/tsconfig.json b/packages/pieces/community/http/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/http/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/http/tsconfig.lib.json b/packages/pieces/community/http/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/http/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/hubspot/.babelrc b/packages/pieces/community/hubspot/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/hubspot/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/hubspot/.eslintrc.json b/packages/pieces/community/hubspot/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/hubspot/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/hubspot/README.md b/packages/pieces/community/hubspot/README.md new file mode 100644 index 0000000..a7df6e7 --- /dev/null +++ b/packages/pieces/community/hubspot/README.md @@ -0,0 +1,7 @@ +# pieces-hubspot + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-hubspot` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/hubspot/package-lock.json b/packages/pieces/community/hubspot/package-lock.json new file mode 100644 index 0000000..88d13d7 --- /dev/null +++ b/packages/pieces/community/hubspot/package-lock.json @@ -0,0 +1,385 @@ +{ + "name": "@activepieces/piece-hubspot", + "version": "0.7.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-hubspot", + "version": "0.7.1", + "dependencies": { + "@hubspot/api-client": "12.0.1" + } + }, + "node_modules/@hubspot/api-client": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@hubspot/api-client/-/api-client-12.0.1.tgz", + "integrity": "sha512-lMxDEuhaP1KDxo0Z/t+3xAT/wzVaQCZ9ThSewj9qCMkhBMYA2ABWLOY/I+huQCERuMqkqwmMBx/NOCspBlGQGg==", + "dependencies": { + "@types/node-fetch": "^2.5.7", + "bottleneck": "^2.19.5", + "es6-promise": "^4.2.4", + "form-data": "^2.5.0", + "lodash.get": "^4.4.2", + "lodash.merge": "^4.6.2", + "node-fetch": "^2.6.0", + "url-parse": "^1.4.3" + } + }, + "node_modules/@types/node": { + "version": "22.15.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.17.tgz", + "integrity": "sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw==", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/node-fetch/node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "node_modules/form-data": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.3.tgz", + "integrity": "sha512-XHIrMD0NpDrNM/Ckf7XJiBbLl57KEhT3+i3yY+eWm+cqYZJQTZrKo8Y8AWKnuV5GT4scfuUGt9LzNoIx3dU1nQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead." + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/packages/pieces/community/hubspot/package.json b/packages/pieces/community/hubspot/package.json new file mode 100644 index 0000000..5d7d615 --- /dev/null +++ b/packages/pieces/community/hubspot/package.json @@ -0,0 +1,7 @@ +{ + "name": "@activepieces/piece-hubspot", + "version": "0.7.2", + "dependencies": { + "@hubspot/api-client": "12.0.1" + } +} diff --git a/packages/pieces/community/hubspot/project.json b/packages/pieces/community/hubspot/project.json new file mode 100644 index 0000000..9e91c7f --- /dev/null +++ b/packages/pieces/community/hubspot/project.json @@ -0,0 +1,63 @@ +{ + "name": "pieces-hubspot", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/hubspot/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/hubspot", + "tsConfig": "packages/pieces/community/hubspot/tsconfig.lib.json", + "packageJson": "packages/pieces/community/hubspot/package.json", + "main": "packages/pieces/community/hubspot/src/index.ts", + "assets": [ + "packages/pieces/community/hubspot/*.md", + { + "input": "packages/pieces/community/hubspot/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + }, + "dependsOn": ["prebuild", "^build"] + }, + "prebuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/pieces/community/hubspot", + "command": "npm ci" + } + }, + "build-with-deps": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "nx run pieces-hubspot:prebuild", + "nx run pieces-hubspot:build", + "nx run pieces-hubspot:postbuild" + ], + "parallel": false + } + }, + "postbuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "dist/packages/pieces/community/hubspot", + "command": "npm install" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/hubspot/src/index.ts b/packages/pieces/community/hubspot/src/index.ts new file mode 100644 index 0000000..93528f5 --- /dev/null +++ b/packages/pieces/community/hubspot/src/index.ts @@ -0,0 +1,196 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { OAuth2PropertyValue, PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { hubSpotListsAddContactAction } from './lib/actions/add-contact-to-list-action'; +import { newCompanyTrigger } from './lib/triggers/new-company'; +import { newContactTrigger } from './lib/triggers/new-contact'; +import { newDealTrigger } from './lib/triggers/new-deal'; +import { newTaskTrigger } from './lib/triggers/new-task'; +import { newTicketTrigger } from './lib/triggers/new-ticket'; +import { createDealAction } from './lib/actions/create-deal'; +import { updateDealAction } from './lib/actions/update-deal'; +import { dealStageUpdatedTrigger } from './lib/triggers/deal-stage-updated'; +import { getContactAction } from './lib/actions/get-contact'; +import { getDealAction } from './lib/actions/get-deal'; +import { getTicketAction } from './lib/actions/get-ticket'; +import { getCompanyAction } from './lib/actions/get-company'; +import { getPipelineStageDetailsAction } from './lib/actions/get-pipeline-stage-details'; +import { getProductAction } from './lib/actions/get-product'; +import { addContactToWorkflowAction } from './lib/actions/add-contact-to-workflow'; +import { createTicketAction } from './lib/actions/create-ticket'; +import { updateTicketAction } from './lib/actions/update-ticket'; +import { findTicketAction } from './lib/actions/find-ticket'; +import { createContactAction } from './lib/actions/create-contact'; +import { updateContactAction } from './lib/actions/update-contact'; +import { findContactAction } from './lib/actions/find-contact'; +import { createOrUpdateContactAction } from './lib/actions/create-or-update-contact'; +import { createProductAction } from './lib/actions/create-product'; +import { updateProductAction } from './lib/actions/update-product'; +import { findProductAction } from './lib/actions/find-product'; +import { createCompanyAction } from './lib/actions/create-company'; +import { findCompanyAction } from './lib/actions/find-company'; +import { updateCompanyAction } from './lib/actions/update-company'; +import { createCustomObjectAction } from './lib/actions/create-custom-object'; +import { updateCustomObjectAction } from './lib/actions/update-custom-object'; +import { getCustomObjectAction } from './lib/actions/get-custom-object'; +import { findCustomObjectAction } from './lib/actions/find-custom-object'; +import { getOwnerByEmailAction } from './lib/actions/get-owner-by-email'; +import { getOwnerByIdAction } from './lib/actions/get-owner-by-id'; +import { findDealAction } from './lib/actions/find-deal'; +import { createLineItemAction } from './lib/actions/create-line-item'; +import { getLineItemAction } from './lib/actions/get-line-item'; +import { updateLineItemAction } from './lib/actions/update-line-item'; +import { findLineItemAction } from './lib/actions/find-line-item'; +import { removeContactFromListAction } from './lib/actions/remove-contact-from-list'; +import { uploadFileAction } from './lib/actions/upload-file'; +import { removeEmailSubscriptionAction } from './lib/actions/remove-email-subscription'; +import { createAssociationsAction } from './lib/actions/create-associations'; +import { removeAssociationsAction } from './lib/actions/remove-associations'; +import { findAssociationsAction } from './lib/actions/find-associations'; +import { newOrUpdatedCompanyTrigger } from './lib/triggers/new-or-updated-company'; +import { newOrUpdatedContactTrigger } from './lib/triggers/new-or-updated-contact'; +import { newOrUpdatedProductTrigger } from './lib/triggers/new-or-updated-product'; +import { newOrUpdatedLineItemTrigger } from './lib/triggers/new-or-updated-line-item'; +import { newContactPropertyChangeTrigger } from './lib/triggers/new-contact-property-change'; +import { newTicketPropertyChangeTrigger } from './lib/triggers/new-ticket-property-change'; +import { newCompanyPropertyChangeTrigger } from './lib/triggers/new-company-property-change'; +import { newDealPropertyChangeTrigger } from './lib/triggers/new-deal-property-change'; +import { newCustomObjectPropertyChangeTrigger } from './lib/triggers/new-custom-object-property-change'; +import { newLineItemTrigger } from './lib/triggers/new-line-item'; +import { newProductTrigger } from './lib/triggers/new-product'; +import { newCustomObjectTrigger } from './lib/triggers/new-custom-object'; +import { newFormSubmissionTrigger } from './lib/triggers/new-form-submission'; +import { newEmailEventTrigger } from './lib/triggers/new-email-event'; +import { newBlogArticleTrigger } from './lib/triggers/new-blog-article'; +import { newContactInListTrigger } from './lib/triggers/new-contact-in-list'; +import { newEngagementTrigger } from './lib/triggers/new-engagement'; +import { newEmailSubscriptionsTimelineTrigger } from './lib/triggers/email-subscriptions-timeline'; +import { createBlogPostAction } from './lib/actions/create-blog-post'; +import { createPageAction } from './lib/actions/create-page'; +import { getPageAction } from './lib/actions/get-page'; +import { deletePageAction } from './lib/actions/delete-page'; + +export const hubspotAuth = PieceAuth.OAuth2({ + authUrl: 'https://app.hubspot.com/oauth/authorize', + tokenUrl: 'https://api.hubapi.com/oauth/v1/token', + required: true, + scope: [ + 'crm.lists.read', + 'crm.lists.write', + 'crm.objects.companies.read', + 'crm.objects.companies.write', + 'crm.objects.contacts.read', + 'crm.objects.contacts.write', + 'crm.objects.custom.read', + 'crm.objects.custom.write', + 'crm.objects.deals.read', + 'crm.objects.deals.write', + 'crm.objects.line_items.read', + 'crm.objects.owners.read', + 'crm.schemas.companies.read', + 'crm.schemas.contacts.read', + 'crm.schemas.custom.read', + 'crm.schemas.deals.read', + 'crm.schemas.line_items.read', + 'automation', + 'e-commerce', + 'tickets', + 'content', + 'settings.currencies.read', + 'settings.users.read', + 'settings.users.teams.read', + 'files', + 'forms', + 'scheduler.meetings.meeting-link.read' + // 'business_units_view.read' + ], +}); + +export const hubspot = createPiece({ + displayName: 'HubSpot', + description: 'Powerful CRM that offers tools for sales, customer service, and marketing automation.', + minimumSupportedRelease: '0.5.0', + logoUrl: 'https://cdn.activepieces.com/pieces/hubspot.png', + authors: ['Salem-Alaa', 'kishanprmr', 'MoShizzle', 'khaledmashaly', 'abuaboud'], + categories: [PieceCategory.SALES_AND_CRM], + auth: hubspotAuth, + actions: [ + hubSpotListsAddContactAction, + addContactToWorkflowAction, + createAssociationsAction, + createCompanyAction, + createContactAction, + createBlogPostAction, + createCustomObjectAction, + createDealAction, + createLineItemAction, + createPageAction, + createOrUpdateContactAction, + createProductAction, + createTicketAction, + getCompanyAction, + getContactAction, + getCustomObjectAction, + getDealAction, + getLineItemAction, + getProductAction, + getPageAction, + getTicketAction, + deletePageAction, + removeAssociationsAction, + removeContactFromListAction, + removeEmailSubscriptionAction, + updateCompanyAction, + updateContactAction, + updateCustomObjectAction, + updateDealAction, + updateLineItemAction, + updateProductAction, + updateTicketAction, + uploadFileAction, + findAssociationsAction, + findCompanyAction, + findContactAction, + findCustomObjectAction, + findDealAction, + findLineItemAction, + findProductAction, + findTicketAction, + getOwnerByEmailAction, + getOwnerByIdAction, + getPipelineStageDetailsAction, + createCustomApiCallAction({ + baseUrl: () => 'https://api.hubapi.com', + auth: hubspotAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [ + newOrUpdatedCompanyTrigger, + newOrUpdatedContactTrigger, + newDealPropertyChangeTrigger, + newEmailSubscriptionsTimelineTrigger, + newOrUpdatedLineItemTrigger, + newCompanyTrigger, + newCompanyPropertyChangeTrigger, + newContactTrigger, + newContactInListTrigger, + newContactPropertyChangeTrigger, + newBlogArticleTrigger, + newCustomObjectTrigger, + newCustomObjectPropertyChangeTrigger, + newDealTrigger, + newEmailEventTrigger, + newEngagementTrigger, + newFormSubmissionTrigger, + newLineItemTrigger, + newProductTrigger, + newTicketTrigger, + newTicketPropertyChangeTrigger, + newOrUpdatedProductTrigger, + newTaskTrigger, + dealStageUpdatedTrigger, + ], +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/add-contact-to-list-action.ts b/packages/pieces/community/hubspot/src/lib/actions/add-contact-to-list-action.ts new file mode 100644 index 0000000..04d8505 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/add-contact-to-list-action.ts @@ -0,0 +1,36 @@ +import { staticListsDropdown } from '../common/props'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { hubspotAuth } from '../../'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const hubSpotListsAddContactAction = createAction({ + auth: hubspotAuth, + name: 'add_contact_to_list', + displayName: 'Add contact To List', + description: 'Add contact to list', + props: { + listId: staticListsDropdown, + email: Property.ShortText({ + displayName: 'Contact Email', + required: true, + }), + }, + + async run(context) { + const { listId, email } = context.propsValue; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.hubapi.com/contacts/v1/lists/${listId}/add`, + body: { + emails: [email], + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/add-contact-to-workflow.ts b/packages/pieces/community/hubspot/src/lib/actions/add-contact-to-workflow.ts new file mode 100644 index 0000000..988f480 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/add-contact-to-workflow.ts @@ -0,0 +1,34 @@ +import { hubspotAuth } from "../../"; +import { createAction, Property } from "@activepieces/pieces-framework"; +import { workflowIdDropdown } from "../common/props"; +import { AuthenticationType, httpClient, HttpMethod } from "@activepieces/pieces-common"; + +export const addContactToWorkflowAction = createAction({ + auth:hubspotAuth, + name:'add-contact-to-workflow', + displayName:'Add Contact to Workflow', + description:'Adds a contact to a specified workflow in your HubSpot account.', + props:{ + workflowId : workflowIdDropdown, + email:Property.ShortText({ + displayName:"Contact's Email", + description:'The email of the contact to add to the workflow.', + required:true + }), + }, + async run(context) { + const contactEmail = context.propsValue.email; + const workflowId = context.propsValue.workflowId; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.hubapi.com/automation/v2/workflows/${workflowId}/enrollments/contacts/${contactEmail}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }) + + return response; + }, +}) \ No newline at end of file diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-associations.ts b/packages/pieces/community/hubspot/src/lib/actions/create-associations.ts new file mode 100644 index 0000000..49a1a86 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-associations.ts @@ -0,0 +1,108 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + fromObjectTypeAssociationDropdown, + associationTypeDropdown, + toObjectIdsDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { AssociationSpecAssociationCategoryEnum } from '../common/types'; + +export const createAssociationsAction = createAction({ + auth: hubspotAuth, + name: 'create-associations', + displayName: 'Create Associations', + description: 'Creates associations between objects', + props: { + fromObjectId: Property.ShortText({ + displayName: 'From Object ID', + description: 'The ID of the object being associated.', + required: true, + }), + fromObjectType: fromObjectTypeAssociationDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'From Object Type', + required: true, + description: 'The type of the object being associated.', + }), + toObjectType: fromObjectTypeAssociationDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'To Object Type', + required: true, + description: 'Type of the objects the from object is being associated with.', + }), + associationType: associationTypeDropdown, + toObjectIds: toObjectIdsDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'To Object IDs', + required: true, + description: 'The ID\'sof the objects the from object is being associated with', + }), + }, + async run(context) { + const { fromObjectId, fromObjectType, toObjectType, associationType } = context.propsValue; + + const client = new Client({ accessToken: context.auth.access_token }); + + if(context.propsValue.toObjectIds === undefined) { + throw new Error('Please provide To Object IDs'); + } + + let toObjectIds: any[]; + if (Array.isArray(context.propsValue.toObjectIds)) { + toObjectIds = context.propsValue.toObjectIds; + } else { + try { + toObjectIds = JSON.parse(context.propsValue.toObjectIds); + } catch { + throw new Error( + `Please provide To Object IDs in a valid format. Provided : ${JSON.stringify( + context.propsValue.toObjectIds, + )}`, + ); + } + } + + // find the association category + const associationLabels = await client.crm.associations.v4.schema.definitionsApi.getAll( + fromObjectType as string, + toObjectType as string, + ); + const association = associationLabels.results.find( + (associationLabel) => associationLabel.typeId === associationType, + ); + if (!association) { + throw new Error( + `Association type ${associationType} not found for ${fromObjectType} to ${toObjectType}`, + ); + } + const associationCategory = association.category; + + const response = await client.crm.associations.v4.batchApi.create( + fromObjectType as string, + toObjectType as string, + { + inputs: toObjectIds.map((objectId) => { + return { + _from: { + id: fromObjectId, + }, + to: { + id: objectId, + }, + types: [ + { + associationCategory: + associationCategory as unknown as AssociationSpecAssociationCategoryEnum, + associationTypeId: associationType, + }, + ], + }; + }), + }, + ); + + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-blog-post.ts b/packages/pieces/community/hubspot/src/lib/actions/create-blog-post.ts new file mode 100644 index 0000000..c458e4a --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-blog-post.ts @@ -0,0 +1,93 @@ +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { blogAuthorDropdown, blogUrlDropdown } from '../common/props'; + +export const createBlogPostAction = createAction({ + auth: hubspotAuth, + name: 'create-blog-post', + displayName: 'Create COS Blog Post', + description: 'Creates a blog post in you Hubspot COS blog.', + props: { + contentGroupId: blogUrlDropdown, + authorId: blogAuthorDropdown, + status: Property.StaticDropdown({ + displayName: 'Publish This Post?', + required: true, + options: { + disabled: false, + options: [ + { + label: 'Leave As Draft', + value: 'DRAFT', + }, + { + label: 'Publish Immediately', + value: 'PUBLISHED', + }, + ], + }, + }), + slug: Property.ShortText({ + displayName: 'Slug', + required: true, + description: 'The slug of the blog post. This is the URL of the post on your COS blog.', + }), + title: Property.ShortText({ + displayName: 'Blog Post Title', + required: true, + }), + body: Property.LongText({ + displayName: 'Blog Post Content', + required: true, + }), + meta: Property.LongText({ + displayName: 'Meta Description', + required: true, + }), + imageUrl: Property.ShortText({ + displayName: 'Featured Image URL', + required: true, + }), + }, + async run(context) { + const { contentGroupId, authorId, status, slug, title, body, meta, imageUrl } = + context.propsValue; + + const createdPost = await httpClient.sendRequest>({ + method: HttpMethod.POST, + url: 'https://api.hubapi.com/content/api/v2/blog-posts', + authentication: { type: AuthenticationType.BEARER_TOKEN, token: context.auth.access_token }, + body: { + blog_author_id: authorId, + content_group_id: contentGroupId, + featured_image: imageUrl, + use_featured_image: true, + name: title, + slug: slug, + meta_description: meta, + post_body: body, + publish_immediately: status === 'PUBLISHED' ? true : undefined, + }, + }); + + if (status === 'PUBLISHED') { + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.hubapi.com/content/api/v2/blog-posts/${createdPost.body['id']}/publish-action`, + authentication: { type: AuthenticationType.BEARER_TOKEN, token: context.auth.access_token }, + body: { + action: 'schedule-publish', + }, + }); + } + + const postDetails = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.hubapi.com/content/api/v2/blog-posts/${createdPost.body['id']}`, + authentication: { type: AuthenticationType.BEARER_TOKEN, token: context.auth.access_token }, + }); + + return postDetails.body + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-company.ts b/packages/pieces/community/hubspot/src/lib/actions/create-company.ts new file mode 100644 index 0000000..b8bef6d --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-company.ts @@ -0,0 +1,56 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getDefaultPropertiesForObject, standardObjectDynamicProperties, standardObjectPropertiesDropdown} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; +import { Client } from '@hubspot/api-client'; + +export const createCompanyAction = createAction({ + auth: hubspotAuth, + name: 'create-company', + displayName: 'Create Company', + description: 'Creates a company in Hubspot.', + props: { + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.COMPANY, []), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, domain, industry, about_us, phone, address, address2, city, state, zip, country, website, type, description, founded_year, hs_createdate, hs_lastmodifieddate, hs_object_id, is_public, timezone, total_money_raised, total_revenue, owneremail, ownername, numberofemployees, annualrevenue, lifecyclestage, createdate, web_technologies + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const companyProperties: Record = {}; + + // Add additional properties to the companyProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + companyProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const createdCompany = await client.crm.companies.basicApi.create({ + properties: companyProperties, + }); + // Retrieve default properties for the comapny and merge with additional properties to retrieve + const defaultcompanyProperties = getDefaultPropertiesForObject(OBJECT_TYPE.COMPANY); + + const companyDetails = await client.crm.companies.basicApi.getById(createdCompany.id, [ + ...defaultcompanyProperties, + ...additionalPropertiesToRetrieve, + ]); + + return companyDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-contact.ts b/packages/pieces/community/hubspot/src/lib/actions/create-contact.ts new file mode 100644 index 0000000..29367f4 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-contact.ts @@ -0,0 +1,57 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { MarkdownVariant } from '@activepieces/shared'; +import { Client } from '@hubspot/api-client'; +import { getDefaultPropertiesForObject, standardObjectDynamicProperties, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; + +export const createContactAction = createAction({ + auth: hubspotAuth, + name: 'create-contact', + displayName: 'Create Contact', + description: 'Creates a contact in Hubspot.', + props: { + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.CONTACT, []), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + firstname, lastname, email, company, website, mobilephone, phone, fax, address, city, state, zip, salutation, country, jobtitle, hs_createdate, hs_email_domain, hs_object_id, lastmodifieddate, hs_persona, hs_language, lifecyclestage, createdate, numemployees, annualrevenue, industry + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const contactProperties: Record = {}; + + // Add additional properties to the contactProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + contactProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const createdContact = await client.crm.contacts.basicApi.create({ + properties: contactProperties, + }); + // Retrieve default properties for the contact and merge with additional properties to retrieve + const defaultContactProperties = getDefaultPropertiesForObject(OBJECT_TYPE.CONTACT); + + const contactDetails = await client.crm.contacts.basicApi.getById(createdContact.id, [ + ...defaultContactProperties, + ...additionalPropertiesToRetrieve, + ]); + + return contactDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-custom-object.ts b/packages/pieces/community/hubspot/src/lib/actions/create-custom-object.ts new file mode 100644 index 0000000..f2c23d2 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-custom-object.ts @@ -0,0 +1,74 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../..'; +import { + customObjectDropdown, + customObjectDynamicProperties, + customObjectPropertiesDropdown, +} from '../common/props'; + +import { Client } from '@hubspot/api-client'; + +export const createCustomObjectAction = createAction({ + auth: hubspotAuth, + name: 'create-custome-object', + displayName: 'Create Custom Object', + description: 'Creates a custom object in Hubspot.', + props: { + customObjectType: customObjectDropdown, + objectProperties: customObjectDynamicProperties, + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + hs_object_id, hs_lastmodifieddate, hs_createdate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: customObjectPropertiesDropdown( + 'Additional Properties to Retrieve', + false, + ), + }, + async run(context) { + const customObjectType = context.propsValue.customObjectType as string; + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = + context.propsValue.additionalPropertiesToRetrieve?.['values']; + + let propertiesToRetrieve; + try { + if (Array.isArray(additionalPropertiesToRetrieve)) { + propertiesToRetrieve = additionalPropertiesToRetrieve; + } + if (typeof additionalPropertiesToRetrieve === 'string') { + propertiesToRetrieve = JSON.parse(additionalPropertiesToRetrieve as string); + } + } catch (error) { + propertiesToRetrieve = []; + } + + const customObjectProperties: Record = {}; + + // Add additional properties to the customObjectProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + customObjectProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const createdCustomObject = await client.crm.objects.basicApi.create(customObjectType, { + properties: customObjectProperties, + associations: [], + }); + + const customObjectDetails = await client.crm.objects.basicApi.getById( + customObjectType, + createdCustomObject.id, + propertiesToRetrieve, + ); + + return customObjectDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-deal.ts b/packages/pieces/community/hubspot/src/lib/actions/create-deal.ts new file mode 100644 index 0000000..d8b89b6 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-deal.ts @@ -0,0 +1,89 @@ +import { hubspotAuth } from '../../'; + +import { Property, createAction } from '@activepieces/pieces-framework'; + +import { MarkdownVariant } from '@activepieces/shared'; +import { OBJECT_TYPE } from '../common/constants'; +import { + getDefaultPropertiesForObject, + pipelineDropdown, + pipelineStageDropdown, + standardObjectDynamicProperties, + standardObjectPropertiesDropdown, +} from '../common/props'; + +import { Client } from '@hubspot/api-client'; + +export const createDealAction = createAction({ + auth: hubspotAuth, + name: 'create-deal', + displayName: 'Create Deal', + description: 'Creates a new deal in Hubspot.', + props: { + dealname: Property.ShortText({ + displayName: 'Deal Name', + required: true, + }), + pipelineId: pipelineDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Deal Pipeline', + required: true, + }), + pipelineStageId: pipelineStageDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Deal Stage', + required: true, + }), + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.DEAL, [ + 'dealname', + 'pipeline', + 'dealstage', + ]), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + dealtype, dealname, amount, description, closedate, createdate, num_associated_contacts, hs_forecast_amount, hs_forecast_probability, hs_manual_forecast_category, hs_next_step, hs_object_id, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { dealname, pipelineId, pipelineStageId } = context.propsValue; + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const dealProperties: Record = { + dealname, + pipeline: pipelineId!, + dealstage: pipelineStageId!, + }; + + // Add additional properties to the dealProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + dealProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const createdDeal = await client.crm.deals.basicApi.create({ + properties: dealProperties, + }); + // Retrieve default properties for the deal and merge with additional properties to retrieve + const defaultDealProperties = getDefaultPropertiesForObject(OBJECT_TYPE.DEAL); + + const dealDetails = await client.crm.deals.basicApi.getById(createdDeal.id, [ + ...defaultDealProperties, + ...additionalPropertiesToRetrieve, + ]); + + return dealDetails; + }, +}); + diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-line-item.ts b/packages/pieces/community/hubspot/src/lib/actions/create-line-item.ts new file mode 100644 index 0000000..d9f22a0 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-line-item.ts @@ -0,0 +1,72 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + getDefaultPropertiesForObject, + productDropdown, + standardObjectDynamicProperties, + standardObjectPropertiesDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; + +import { Client } from '@hubspot/api-client'; + +export const createLineItemAction = createAction({ + auth: hubspotAuth, + name: 'create-line-item', + displayName: 'Create Line Item', + description: 'Creates a line item in Hubspot.', + props: { + productId: productDropdown({ + displayName: 'Line Item Information: Product ID', + required: true, + objectType: OBJECT_TYPE.PRODUCT, + }), + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.LINE_ITEM, ['hs_product_id']), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, description, price, quantity, amount, discount, tax, createdate, hs_object_id, hs_product_id, hs_images, hs_lastmodifieddate, hs_line_item_currency_code, hs_sku, hs_url, hs_cost_of_goods_sold, hs_discount_percentage, hs_term_in_months + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.LINE_ITEM, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const productId = context.propsValue.productId; + + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const lineItemProperties: Record = { + hs_product_id: productId!, + }; + + // Add additional properties to the lineItemProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + lineItemProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const createdLineItem = await client.crm.lineItems.basicApi.create({ + associations: [], + properties: lineItemProperties, + }); + // Retrieve default properties for the line item and merge with additional properties to retrieve + const defaultlineItemProperties = getDefaultPropertiesForObject(OBJECT_TYPE.LINE_ITEM); + + const lineItemDetails = await client.crm.lineItems.basicApi.getById(createdLineItem.id, [ + ...defaultlineItemProperties, + ...additionalPropertiesToRetrieve, + ]); + + return lineItemDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-or-update-contact.ts b/packages/pieces/community/hubspot/src/lib/actions/create-or-update-contact.ts new file mode 100644 index 0000000..16bca38 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-or-update-contact.ts @@ -0,0 +1,58 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { Client } from '@hubspot/api-client'; +import { standardObjectDynamicProperties } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { FilterOperatorEnum } from '../common/types'; + + +export const createOrUpdateContactAction = createAction({ + auth: hubspotAuth, + name: 'create-or-update-contact', + displayName: 'Create or Update Contact', + description: 'Creates a new contact or updates an existing contact based on email address.', + props: { + email: Property.ShortText({ + displayName: 'Contact Email', + required: true, + }), + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.CONTACT, ['email']), + }, + async run(context) { + const email = context.propsValue.email; + const objectProperties = context.propsValue.objectProperties ?? {}; + + const contactProperties: Record = {}; + + // Add additional properties to the contactProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + contactProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const searchResponse = await client.crm.contacts.searchApi.doSearch({ + limit: 1, + filterGroups: [ + { filters: [{ propertyName: 'email', operator: FilterOperatorEnum.Eq, value: email }] }, + ], + }); + + if (searchResponse.results.length > 0) { + const updatedContact = await client.crm.contacts.basicApi.update( + searchResponse.results[0].id, + { + properties: contactProperties, + }, + ); + return updatedContact; + } else { + const createdContact = await client.crm.contacts.basicApi.create({ + properties: { ...contactProperties, email }, + }); + return createdContact; + } + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-page.ts b/packages/pieces/community/hubspot/src/lib/actions/create-page.ts new file mode 100644 index 0000000..5296322 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-page.ts @@ -0,0 +1,122 @@ +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { hubspotAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { pageType } from '../common/props'; + +export const createPageAction = createAction({ + auth: hubspotAuth, + name: 'create-page', + displayName: 'Create Page', + description: 'Creates a new landing/site page.', + props: { + pageType: pageType, + pageTitle: Property.ShortText({ + displayName: 'Page Title', + required: true, + }), + internalPageName: Property.ShortText({ + displayName: 'Internal Page Name', + required: true, + }), + templatePath: Property.ShortText({ + displayName: 'Template Path', + description: + 'The path should not include a slash (/) at the start.For example,"@hubspot/elevate/templates/blank.hubl.html".', + required: true, + }), + slug: Property.ShortText({ + displayName: 'Slug', + required: true, + }), + language: Property.ShortText({ + displayName: 'Language', + required: false, + defaultValue: 'en-us', + }), + metaDescription: Property.LongText({ + displayName: 'Meta Description', + required: false, + }), + state: Property.StaticDropdown({ + displayName: 'State', + required: false, + defaultValue: 'DRAFT', + options: { + disabled: false, + options: [ + { + label: 'Draft', + value: 'DRAFT', + }, + { + label: 'Publish', + value: 'PUBLISHED_OR_SCHEDULED', + }, + ], + }, + }), + headHtml: Property.LongText({ + displayName: 'Additional Head HTML', + required: false, + }), + footerHtml: Property.LongText({ + displayName: 'Additional Footer HTML', + required: false, + }), + }, + async run(context) { + const url = `https://api.hubapi.com/cms/v3/pages/${ + context.propsValue.pageType === 'site_page' ? 'site-pages' : 'landing-pages' + }`; + const { + pageTitle, + internalPageName, + metaDescription, + templatePath, + language, + state, + headHtml, + footerHtml, + slug, + } = context.propsValue; + + const createdPage = await httpClient.sendRequest<{ id: string }>({ + method: HttpMethod.POST, + url, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + body: { + htmlTitle: pageTitle, + name: internalPageName, + metaDescription, + templatePath, + slug, + language, + headHtml, + footerHtml, + }, + }); + + if (state === 'PUBLISHED_OR_SCHEDULED') { + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.hubapi.com/content/api/v2/pages/${createdPage.body.id}/publish-action`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + body: { action: 'schedule-publish' }, + }); + } + + const pageDetails = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${url}/${createdPage.body.id}`, + authentication: { type: AuthenticationType.BEARER_TOKEN, token: context.auth.access_token }, + }); + + return pageDetails.body; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-product.ts b/packages/pieces/community/hubspot/src/lib/actions/create-product.ts new file mode 100644 index 0000000..d081fc3 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-product.ts @@ -0,0 +1,60 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + getDefaultPropertiesForObject, + standardObjectDynamicProperties, + standardObjectPropertiesDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; +import { Client } from '@hubspot/api-client'; + +export const createProductAction = createAction({ + auth: hubspotAuth, + name: 'create-product', + displayName: 'Create Product', + description: 'Creates a product in Hubspot.', + props: { + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.PRODUCT,[]), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + createdate, description, name, price, tax, hs_lastmodifieddate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.PRODUCT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const productProperties: Record = {}; + + // Add additional properties to the productProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + productProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const createdProduct = await client.crm.products.basicApi.create({ + properties: productProperties, + }); + // Retrieve default properties for the product and merge with additional properties to retrieve + const defaultproductProperties = getDefaultPropertiesForObject(OBJECT_TYPE.PRODUCT); + + const productDetails = await client.crm.products.basicApi.getById(createdProduct.id, [ + ...defaultproductProperties, + ...additionalPropertiesToRetrieve, + ]); + + return productDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-ticket.ts b/packages/pieces/community/hubspot/src/lib/actions/create-ticket.ts new file mode 100644 index 0000000..fdf9960 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-ticket.ts @@ -0,0 +1,87 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; + +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { getDefaultPropertiesForObject, pipelineDropdown, pipelineStageDropdown, standardObjectDynamicProperties, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; + +export const createTicketAction = createAction({ + auth: hubspotAuth, + name: 'create-ticket', + displayName: 'Create Ticket', + description: 'Creates a ticket in HubSpot.', + props: { + ticketName: Property.ShortText({ + displayName: 'Ticket Name', + description: 'The name of the ticket to create.', + required: true, + }), + pipelineId: pipelineDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Ticket Pipeline', + required: true, + }), + pipelineStageId: pipelineStageDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Ticket Pipeline Stage', + required: true, + }), + objectProperties : standardObjectDynamicProperties(OBJECT_TYPE.TICKET, [ + 'subject', + 'hs_pipeline', + 'hs_pipeline_stage', + ]), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + subject, content, source_type, createdate, hs_pipeline, hs_pipeline_stage, hs_resolution, hs_ticket_category, hs_ticket_id, hs_ticket_priority, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { + ticketName, + pipelineId, + pipelineStageId, + } = context.propsValue; + const objectProperties = context.propsValue.objectProperties??{}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve??[]; + + + const ticketProperties: Record = { + subject: ticketName, + hs_pipeline: pipelineId!, + hs_pipeline_stage: pipelineStageId!, + }; + + // Add additional properties to the ticketProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + ticketProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const createdTicket = await client.crm.tickets.basicApi.create({ + properties: ticketProperties, + }); + + // Retrieve default properties for the ticket and merge with additional properties to retrieve + const defaultTicketProperties = getDefaultPropertiesForObject(OBJECT_TYPE.TICKET); + + const ticketDetails = await client.crm.tickets.basicApi.getById(createdTicket.id, [ + ...defaultTicketProperties, + ...additionalPropertiesToRetrieve, + ]); + + return ticketDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/delete-page.ts b/packages/pieces/community/hubspot/src/lib/actions/delete-page.ts new file mode 100644 index 0000000..2431711 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/delete-page.ts @@ -0,0 +1,29 @@ +import { hubspotAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; +import { pageType } from '../common/props'; + +export const deletePageAction = createAction({ + auth: hubspotAuth, + name: 'delete-page', + displayName: 'Delete Page', + description: 'Deletes an existing landing/site page.', + props: { + pageType: pageType, + pageId: Property.ShortText({ + displayName: 'Page ID', + description: 'The ID of the page to delete.', + required: true, + }), + }, + async run(context) { + const { pageId, pageType } = context.propsValue; + const client = new Client({ accessToken: context.auth.access_token }); + + if (pageType === 'site_page') { + return await client.cms.pages.sitePagesApi.archive(pageId); + } else { + return await client.cms.pages.landingPagesApi.archive(pageId); + } + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/find-associations.ts b/packages/pieces/community/hubspot/src/lib/actions/find-associations.ts new file mode 100644 index 0000000..343d316 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/find-associations.ts @@ -0,0 +1,56 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { fromObjectTypeAssociationDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; + +export const findAssociationsAction = createAction({ + auth: hubspotAuth, + name: 'find-associations', + displayName: 'Find Associations', + description: 'Finds associations between objects', + props: { + fromObjectId: Property.ShortText({ + displayName: 'From Object ID', + description: 'The ID of the object you want to search the association.', + required: true, + }), + fromObjectType: fromObjectTypeAssociationDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'From Object Type', + required: true, + description: 'The type of the object you want to search the association.', + }), + toObjectType: fromObjectTypeAssociationDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'To Object Type', + required: true, + description: 'Type of the object the from object is being associated with.', + }), + }, + async run(context) { + const { fromObjectId, fromObjectType, toObjectType } = context.propsValue; + + const client = new Client({ accessToken: context.auth.access_token }); + + const results = []; + const limit = 100; + let after: string | undefined; + + do { + const response = await client.crm.associations.v4.basicApi.getPage( + fromObjectType as string, + fromObjectId as string, + toObjectType as string, + after, + limit, + ); + for(const association of response.results) { + results.push(association); + } + after = response.paging?.next?.after; + } while (after); + + return results; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/find-company.ts b/packages/pieces/community/hubspot/src/lib/actions/find-company.ts new file mode 100644 index 0000000..795ba99 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/find-company.ts @@ -0,0 +1,94 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + getDefaultPropertiesForObject, + standardObjectPropertiesDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; + +export const findCompanyAction = createAction({ + auth: hubspotAuth, + name: 'find-company', + displayName: 'Find Company', + description: 'Finds a company by searching.', + props: { + firstSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.COMPANY, + displayName: 'First search property name', + required: true, + }, + true, + true, + ), + firstSearchPropertyValue: Property.ShortText({ + displayName: 'First search property value', + required: true, + }), + secondSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.COMPANY, + displayName: 'Second search property name', + required: false, + }, + true, + true, + ), + secondSearchPropertyValue: Property.ShortText({ + displayName: 'Second search property value', + required: false, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, domain, industry, about_us, phone, address, address2, city, state, zip, country, website, type, description, founded_year, hs_createdate, hs_lastmodifieddate, hs_object_id, is_public, timezone, total_money_raised, total_revenue, owneremail, ownername, numberofemployees, annualrevenue, lifecyclestage, createdate, web_technologies + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { + firstSearchPropertyName, + firstSearchPropertyValue, + secondSearchPropertyName, + secondSearchPropertyValue, + } = context.propsValue; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const filters = [ + { + propertyName: firstSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: firstSearchPropertyValue, + }, + ]; + + if (secondSearchPropertyName && secondSearchPropertyValue) { + filters.push({ + propertyName: secondSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: secondSearchPropertyValue, + }); + } + + const client = new Client({ accessToken: context.auth.access_token }); + + const defaultCompanyProperties = getDefaultPropertiesForObject(OBJECT_TYPE.COMPANY); + + const response = await client.crm.companies.searchApi.doSearch({ + limit: 100, + properties: [...defaultCompanyProperties, ...additionalPropertiesToRetrieve], + filterGroups: [{ filters }], + }); + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/find-contact.ts b/packages/pieces/community/hubspot/src/lib/actions/find-contact.ts new file mode 100644 index 0000000..a28769c --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/find-contact.ts @@ -0,0 +1,93 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; +import { hubspotAuth } from '../../'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { FilterOperatorEnum } from '../common/types'; + +export const findContactAction = createAction({ + auth: hubspotAuth, + name: 'find-contact', + displayName: 'Find Contact', + description: 'Finds a contact by searching.', + props: { + firstSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.CONTACT, + displayName: 'First search property name', + required: true, + }, + true, + true, + ), + firstSearchPropertyValue: Property.ShortText({ + displayName: 'First search property value', + required: true, + }), + secondSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Second search property name', + required: false, + }, + true, + true, + ), + secondSearchPropertyValue: Property.ShortText({ + displayName: 'Second search property value', + required: false, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + firstname, lastname, email, company, website, mobilephone, phone, fax, address, city, state, zip, salutation, country, jobtitle, hs_createdate, hs_email_domain, hs_object_id, lastmodifieddate, hs_persona, hs_language, lifecyclestage, createdate, numemployees, annualrevenue, industry + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { + firstSearchPropertyName, + firstSearchPropertyValue, + secondSearchPropertyName, + secondSearchPropertyValue, + } = context.propsValue; + + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const filters = [ + { + propertyName: firstSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: firstSearchPropertyValue, + }, + ]; + + if (secondSearchPropertyName && secondSearchPropertyValue) { + filters.push({ + propertyName: secondSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: secondSearchPropertyValue, + }); + } + + const client = new Client({ accessToken: context.auth.access_token }); + + const defaultContactProperties = getDefaultPropertiesForObject(OBJECT_TYPE.CONTACT); + + const response = client.crm.contacts.searchApi.doSearch({ + limit: 100, + properties: [...defaultContactProperties, ...additionalPropertiesToRetrieve], + filterGroups: [{ filters }], + }); + + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/find-custom-object.ts b/packages/pieces/community/hubspot/src/lib/actions/find-custom-object.ts new file mode 100644 index 0000000..986dace --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/find-custom-object.ts @@ -0,0 +1,98 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; +import { hubspotAuth } from '../../'; +import { customObjectDropdown, customObjectPropertiesDropdown } from '../common/props'; +import { FilterOperatorEnum } from '../common/types'; + +export const findCustomObjectAction = createAction({ + auth: hubspotAuth, + name: 'find-custom-object', + displayName: 'Find Custom Object', + description: 'Finds a custom object by searching.', + props: { + customObjectType: customObjectDropdown, + firstSearchPropertyName: customObjectPropertiesDropdown( + 'First search property name', + true, + true, + ), + firstSearchPropertyValue: Property.ShortText({ + displayName: 'First search property value', + required: true, + }), + secondSearchPropertyName: customObjectPropertiesDropdown( + 'Second search property name', + false, + true, + ), + secondSearchPropertyValue: Property.ShortText({ + displayName: 'Second search property value', + required: false, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + hs_object_id, hs_lastmodifieddate, hs_createdate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: customObjectPropertiesDropdown( + 'Additional Properties to Retrieve', + false, + ), + }, + async run(context) { + const customObjectType = context.propsValue.customObjectType as string; + const { firstSearchPropertyValue, secondSearchPropertyValue } = context.propsValue; + const firstSearchPropertyName = context.propsValue.firstSearchPropertyName?.[ + 'values' + ] as string; + const secondSearchPropertyName = context.propsValue.secondSearchPropertyName?.[ + 'values' + ] as string; + + const additionalPropertiesToRetrieve = + context.propsValue.additionalPropertiesToRetrieve?.['values']; + + let propertiesToRetrieve; + + try { + if (Array.isArray(additionalPropertiesToRetrieve)) { + propertiesToRetrieve = additionalPropertiesToRetrieve; + } + if (typeof additionalPropertiesToRetrieve === 'string') { + propertiesToRetrieve = JSON.parse(additionalPropertiesToRetrieve as string); + } + } catch (error) { + propertiesToRetrieve = []; + } + + const filters = [ + { + propertyName: firstSearchPropertyName as unknown as string, + operator: FilterOperatorEnum.Eq, + value: firstSearchPropertyValue, + }, + ]; + + if (secondSearchPropertyName && secondSearchPropertyValue) { + filters.push({ + propertyName: secondSearchPropertyName as unknown as string, + operator: FilterOperatorEnum.Eq, + value: secondSearchPropertyValue, + }); + } + + const client = new Client({ accessToken: context.auth.access_token }); + + const response = await client.crm.objects.searchApi.doSearch(customObjectType, { + limit: 100, + properties: propertiesToRetrieve, + filterGroups: [{ filters }], + }); + + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/find-deal.ts b/packages/pieces/community/hubspot/src/lib/actions/find-deal.ts new file mode 100644 index 0000000..14d30ca --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/find-deal.ts @@ -0,0 +1,93 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; +import { FilterOperatorEnum } from '../common/types'; +import { Client } from '@hubspot/api-client'; + +export const findDealAction = createAction({ + auth: hubspotAuth, + name: 'find-deal', + displayName: 'Find Deal', + description: 'Finds a deal by searching.', + props: { + firstSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.DEAL, + displayName: 'First search property name', + required: true, + }, + true, + true, + ), + firstSearchPropertyValue: Property.ShortText({ + displayName: 'First search property value', + required: true, + }), + secondSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.DEAL, + displayName: 'Second search property name', + required: false, + }, + true, + true, + ), + secondSearchPropertyValue: Property.ShortText({ + displayName: 'Second search property value', + required: false, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + dealtype, dealname, amount, description, closedate, createdate, num_associated_contacts, hs_forecast_amount, hs_forecast_probability, hs_manual_forecast_category, hs_next_step, hs_object_id, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { + firstSearchPropertyName, + firstSearchPropertyValue, + secondSearchPropertyName, + secondSearchPropertyValue, + } = context.propsValue; + + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const filters = [ + { + propertyName: firstSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: firstSearchPropertyValue, + }, + ]; + + if (secondSearchPropertyName && secondSearchPropertyValue) { + filters.push({ + propertyName: secondSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: secondSearchPropertyValue, + }); + } + + const client = new Client({ accessToken: context.auth.access_token }); + + const defaultDealProperties = getDefaultPropertiesForObject(OBJECT_TYPE.DEAL); + + const response = client.crm.deals.searchApi.doSearch({ + limit: 100, + properties: [...defaultDealProperties, ...additionalPropertiesToRetrieve], + filterGroups: [{ filters }], + }); + + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/find-line-item.ts b/packages/pieces/community/hubspot/src/lib/actions/find-line-item.ts new file mode 100644 index 0000000..7a5e78c --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/find-line-item.ts @@ -0,0 +1,93 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; +import { hubspotAuth } from '../../'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { FilterOperatorEnum } from '../common/types'; + +export const findLineItemAction = createAction({ + auth: hubspotAuth, + name: 'find-line-item', + displayName: 'Find Line Item', + description: 'Finds a line item by searching.', + props: { + firstSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.LINE_ITEM, + displayName: 'First search property name', + required: true, + }, + true, + true, + ), + firstSearchPropertyValue: Property.ShortText({ + displayName: 'First search property value', + required: true, + }), + secondSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.LINE_ITEM, + displayName: 'Second search property name', + required: false, + }, + true, + true, + ), + secondSearchPropertyValue: Property.ShortText({ + displayName: 'Second search property value', + required: false, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, description, price, quantity, amount, discount, tax, createdate, hs_object_id, hs_product_id, hs_images, hs_lastmodifieddate, hs_line_item_currency_code, hs_sku, hs_url, hs_cost_of_goods_sold, hs_discount_percentage, hs_term_in_months + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.LINE_ITEM, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { + firstSearchPropertyName, + firstSearchPropertyValue, + secondSearchPropertyName, + secondSearchPropertyValue, + } = context.propsValue; + + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const filters = [ + { + propertyName: firstSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: firstSearchPropertyValue, + }, + ]; + + if (secondSearchPropertyName && secondSearchPropertyValue) { + filters.push({ + propertyName: secondSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: secondSearchPropertyValue, + }); + } + + const client = new Client({ accessToken: context.auth.access_token }); + + const defaultLineItemProperties = getDefaultPropertiesForObject(OBJECT_TYPE.LINE_ITEM); + + const response = client.crm.lineItems.searchApi.doSearch({ + limit: 100, + properties: [...defaultLineItemProperties, ...additionalPropertiesToRetrieve], + filterGroups: [{ filters }], + }); + + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/find-product.ts b/packages/pieces/community/hubspot/src/lib/actions/find-product.ts new file mode 100644 index 0000000..313aca0 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/find-product.ts @@ -0,0 +1,93 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown + + } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; + +export const findProductAction = createAction({ + auth: hubspotAuth, + name: 'find-product', + displayName: 'Find Product', + description: 'Finds a product by searching.', + props: { + firstSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.PRODUCT, + displayName: 'First search property name', + required: true, + }, + true, + true, + ), + firstSearchPropertyValue: Property.ShortText({ + displayName: 'First search property value', + required: true, + }), + secondSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.PRODUCT, + displayName: 'Second search property name', + required: false, + }, + true, + true, + ), + secondSearchPropertyValue: Property.ShortText({ + displayName: 'Second search property value', + required: false, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + createdate, description, name, price, tax, hs_lastmodifieddate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.PRODUCT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { + firstSearchPropertyName, + firstSearchPropertyValue, + secondSearchPropertyName, + secondSearchPropertyValue, + } = context.propsValue; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const filters = [ + { + propertyName: firstSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: firstSearchPropertyValue, + }, + ]; + + if (secondSearchPropertyName && secondSearchPropertyValue) { + filters.push({ + propertyName: secondSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: secondSearchPropertyValue, + }); + } + + const client = new Client({ accessToken: context.auth.access_token }); + + const defaultProductProperties = getDefaultPropertiesForObject(OBJECT_TYPE.PRODUCT); + + const response = await client.crm.products.searchApi.doSearch({ + limit: 100, + properties: [...defaultProductProperties, ...additionalPropertiesToRetrieve], + filterGroups: [{ filters }], + }); + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/find-ticket.ts b/packages/pieces/community/hubspot/src/lib/actions/find-ticket.ts new file mode 100644 index 0000000..d4bbddd --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/find-ticket.ts @@ -0,0 +1,93 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; +import { hubspotAuth } from '../../'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { FilterOperatorEnum } from '../common/types'; + +export const findTicketAction = createAction({ + auth: hubspotAuth, + name: 'find-ticket', + displayName: 'Find Ticket', + description: 'Finds a ticket by searching.', + props: { + firstSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.TICKET, + displayName: 'First search property name', + required: true, + }, + true, + true, + ), + firstSearchPropertyValue: Property.ShortText({ + displayName: 'First search property value', + required: true, + }), + secondSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.TICKET, + displayName: 'Second search property name', + required: false, + }, + true, + true, + ), + secondSearchPropertyValue: Property.ShortText({ + displayName: 'Second search property value', + required: false, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + subject, content, source_type, createdate, hs_pipeline, hs_pipeline_stage, hs_resolution, hs_ticket_category, hs_ticket_id, hs_ticket_priority, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { + firstSearchPropertyName, + firstSearchPropertyValue, + secondSearchPropertyName, + secondSearchPropertyValue, + } = context.propsValue; + + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const filters = [ + { + propertyName: firstSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: firstSearchPropertyValue, + }, + ]; + + if (secondSearchPropertyName && secondSearchPropertyValue) { + filters.push({ + propertyName: secondSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: secondSearchPropertyValue, + }); + } + + const client = new Client({ accessToken: context.auth.access_token }); + + const defaultTicketProperties = getDefaultPropertiesForObject(OBJECT_TYPE.TICKET); + + const response = client.crm.tickets.searchApi.doSearch({ + limit: 100, + properties: [...defaultTicketProperties, ...additionalPropertiesToRetrieve], + filterGroups: [{ filters }], + }); + + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-company.ts b/packages/pieces/community/hubspot/src/lib/actions/get-company.ts new file mode 100644 index 0000000..0274948 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-company.ts @@ -0,0 +1,52 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + getDefaultPropertiesForObject, + standardObjectPropertiesDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { MarkdownVariant } from '@activepieces/shared'; + +export const getCompanyAction = createAction({ + auth: hubspotAuth, + name: 'get-company', + displayName: 'Get Company', + description: 'Gets a company.', + props: { + companyId: Property.ShortText({ + displayName: 'Company ID', + description: 'The ID of the company to get.', + required: true, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, domain, industry, about_us, phone, address, address2, city, state, zip, country, website, type, description, founded_year, hs_createdate, hs_lastmodifieddate, hs_object_id, is_public, timezone, total_money_raised, total_revenue, owneremail, ownername, numberofemployees, annualrevenue, lifecyclestage, createdate, web_technologies + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { companyId } = context.propsValue; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve??[]; + + + const defaultCompanyProperties = getDefaultPropertiesForObject(OBJECT_TYPE.COMPANY); + + const client = new Client({ accessToken: context.auth.access_token }); + + const companyDetails = await client.crm.companies.basicApi.getById(companyId, [ + ...defaultCompanyProperties, + ...additionalPropertiesToRetrieve, + ]); + + return companyDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-contact.ts b/packages/pieces/community/hubspot/src/lib/actions/get-contact.ts new file mode 100644 index 0000000..c65918b --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-contact.ts @@ -0,0 +1,50 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { Client } from '@hubspot/api-client'; +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; + +export const getContactAction = createAction({ + auth: hubspotAuth, + name: 'get-contact', + displayName: 'Get Contact', + description: 'Gets a contact.', + props: { + contactId: Property.ShortText({ + displayName: 'Contact ID', + description: 'The ID of the contact to get.', + required: true, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + firstname, lastname, email, company, website, mobilephone, phone, fax, address, city, state, zip, salutation, country, jobtitle, hs_createdate, hs_email_domain, hs_object_id, lastmodifieddate, hs_persona, hs_language, lifecyclestage, createdate, numemployees, annualrevenue, industry + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { contactId } = context.propsValue; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve??[]; + + + const defaultContactProperties = getDefaultPropertiesForObject(OBJECT_TYPE.CONTACT); + + const client = new Client({ accessToken: context.auth.access_token }); + + const contactDetails = await client.crm.contacts.basicApi.getById(contactId, [ + ...defaultContactProperties, + ...additionalPropertiesToRetrieve, + ]); + + return contactDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-custom-object.ts b/packages/pieces/community/hubspot/src/lib/actions/get-custom-object.ts new file mode 100644 index 0000000..89d4765 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-custom-object.ts @@ -0,0 +1,58 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { Client } from '@hubspot/api-client'; +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { customObjectDropdown, customObjectPropertiesDropdown } from '../common/props'; + +export const getCustomObjectAction = createAction({ + auth: hubspotAuth, + name: 'get-custom-object', + displayName: 'Get Custom Object', + description: 'Gets a custom object.', + props: { + customObjectType: customObjectDropdown, + customObjectId: Property.ShortText({ + displayName: 'Custom Object ID', + description: 'The ID of the custom object to get.', + required: true, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + hs_object_id, hs_lastmodifieddate, hs_createdate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: customObjectPropertiesDropdown('Additional Properties to Retrieve', false), + }, + async run(context) { + const customObjectType = context.propsValue.customObjectType as string; + const customObjectId = context.propsValue.customObjectId as string; + const additionalPropertiesToRetrieve = + context.propsValue.additionalPropertiesToRetrieve?.['values']; + + let propertiesToRetrieve; + try { + if (Array.isArray(additionalPropertiesToRetrieve)) { + propertiesToRetrieve = additionalPropertiesToRetrieve; + } + if (typeof additionalPropertiesToRetrieve === 'string') { + propertiesToRetrieve = JSON.parse(additionalPropertiesToRetrieve as string); + } + } catch (error) { + propertiesToRetrieve = []; + } + + const client = new Client({ accessToken: context.auth.access_token }); + + const customObjectDetails = await client.crm.objects.basicApi.getById( + customObjectType, + customObjectId, + propertiesToRetrieve, + ); + + return customObjectDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-deal.ts b/packages/pieces/community/hubspot/src/lib/actions/get-deal.ts new file mode 100644 index 0000000..403d4f1 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-deal.ts @@ -0,0 +1,49 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { Client } from '@hubspot/api-client'; +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; + +export const getDealAction = createAction({ + auth: hubspotAuth, + name: 'get-deal', + displayName: 'Get Deal', + description: 'Gets a deal.', + props: { + dealId: Property.ShortText({ + displayName: 'Deal ID', + description: 'The ID of the deal to get.', + required: true, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + dealtype, dealname, amount, description, closedate, createdate, num_associated_contacts, hs_forecast_amount, hs_forecast_probability, hs_manual_forecast_category, hs_next_step, hs_object_id, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { dealId } = context.propsValue; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve??[]; + + + const defaultDealProperties = getDefaultPropertiesForObject(OBJECT_TYPE.DEAL); + + const client = new Client({ accessToken: context.auth.access_token }); + + const dealDetails = await client.crm.deals.basicApi.getById(dealId, [ + ...defaultDealProperties, + ...additionalPropertiesToRetrieve, + ]); + return dealDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-line-item.ts b/packages/pieces/community/hubspot/src/lib/actions/get-line-item.ts new file mode 100644 index 0000000..a288600 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-line-item.ts @@ -0,0 +1,48 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { MarkdownVariant } from '@activepieces/shared'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; + +export const getLineItemAction = createAction({ + auth: hubspotAuth, + name: 'get-line-item', + displayName: 'Get Line Item', + description: 'Gets a line item.', + props: { + lineItemId: Property.ShortText({ + displayName: 'Line Item ID', + description: 'The ID of the line item to get.', + required: true, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, description, price, quantity, amount, discount, tax, createdate, hs_object_id, hs_product_id, hs_images, hs_lastmodifieddate, hs_line_item_currency_code, hs_sku, hs_url, hs_cost_of_goods_sold, hs_discount_percentage, hs_term_in_months + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.LINE_ITEM, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { lineItemId } = context.propsValue; + + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const defaultLineItemProperties = getDefaultPropertiesForObject(OBJECT_TYPE.LINE_ITEM); + const client = new Client({ accessToken: context.auth.access_token }); + + const lineItemDetails = await client.crm.lineItems.basicApi.getById(lineItemId, [ + ...defaultLineItemProperties, + ...additionalPropertiesToRetrieve, + ]); + + return lineItemDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-owner-by-email.ts b/packages/pieces/community/hubspot/src/lib/actions/get-owner-by-email.ts new file mode 100644 index 0000000..c34b2df --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-owner-by-email.ts @@ -0,0 +1,23 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { hubspotAuth } from '../..'; +import { Client } from '@hubspot/api-client'; + +export const getOwnerByEmailAction = createAction({ + auth: hubspotAuth, + name: 'get-owner-by-email', + displayName: 'Get Owner by Email', + description: 'Gets an existing owner by email.', + props: { + email: Property.ShortText({ + displayName: 'Owner Email', + required: true, + }), + }, + async run(context) { + const { email } = context.propsValue; + const client = new Client({ accessToken: context.auth.access_token }); + + const response = await client.crm.owners.ownersApi.getPage(email); + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-owner-by-id.ts b/packages/pieces/community/hubspot/src/lib/actions/get-owner-by-id.ts new file mode 100644 index 0000000..9be64f0 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-owner-by-id.ts @@ -0,0 +1,23 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { hubspotAuth } from '../..'; +import { Client } from '@hubspot/api-client'; + +export const getOwnerByIdAction = createAction({ + auth: hubspotAuth, + name: 'get-owner-by-id', + displayName: 'Get Owner by ID', + description: 'Gets an existing owner by ID.', + props: { + ownerId: Property.ShortText({ + displayName: 'Owner ID', + required: true, + }), + }, + async run(context) { + const { ownerId } = context.propsValue; + const client = new Client({ accessToken: context.auth.access_token }); + + const response = await client.crm.owners.ownersApi.getById(Number(ownerId)); + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-page.ts b/packages/pieces/community/hubspot/src/lib/actions/get-page.ts new file mode 100644 index 0000000..0bff396 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-page.ts @@ -0,0 +1,29 @@ +import { hubspotAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; +import { pageType } from '../common/props'; + +export const getPageAction = createAction({ + auth: hubspotAuth, + name: 'get-page', + displayName: 'Get Page', + description: 'Gets landing/site page Details.', + props: { + pageType: pageType, + pageId: Property.ShortText({ + displayName: 'Page ID', + description: 'The ID of the page to get.', + required: true, + }), + }, + async run(context) { + const { pageId, pageType } = context.propsValue; + const client = new Client({ accessToken: context.auth.access_token }); + + if (pageType === 'site_page') { + return await client.cms.pages.sitePagesApi.getById(pageId); + } else { + return await client.cms.pages.landingPagesApi.getById(pageId); + } + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-pipeline-stage-details.ts b/packages/pieces/community/hubspot/src/lib/actions/get-pipeline-stage-details.ts new file mode 100644 index 0000000..a468f9c --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-pipeline-stage-details.ts @@ -0,0 +1,52 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; + +export const getPipelineStageDetailsAction = createAction({ + auth: hubspotAuth, + name: 'get-pipeline-stage-details', + displayName: 'Get Pipeline Stage Details', + description: 'Finds and retrieves CRM object pipeline stage details.', + props: { + objectType: Property.StaticDropdown({ + displayName: 'Object Type', + required: true, + options: { + disabled: false, + options: [ + { + label: 'Tickets', + value: 'ticket', + }, + { + label: 'Deal', + value: 'deal', + }, + ], + }, + }), + pipelineId: Property.ShortText({ + displayName: 'Pipeline ID', + required: true, + }), + stageId: Property.ShortText({ + displayName: 'Stage ID', + required: true, + }), + }, + async run(context) { + const objectType = context.propsValue.objectType; + const pipelineId = context.propsValue.pipelineId; + const stageId = context.propsValue.stageId; + + const client = new Client({ accessToken: context.auth.access_token }); + + const response = await client.crm.pipelines.pipelineStagesApi.getById( + objectType, + pipelineId, + stageId, + ); + + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-product.ts b/packages/pieces/community/hubspot/src/lib/actions/get-product.ts new file mode 100644 index 0000000..3165917 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-product.ts @@ -0,0 +1,50 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { Client } from '@hubspot/api-client'; +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; + +export const getProductAction = createAction({ + auth: hubspotAuth, + name: 'get-product', + displayName: 'Get Product', + description: 'Gets a product.', + props: { + productId: Property.ShortText({ + displayName: 'Product ID', + description: 'The ID of the product to get.', + required: true, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + createdate, description, name, price, tax, hs_lastmodifieddate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.PRODUCT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { productId } = context.propsValue; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve??[]; + + + const defaultProductProperties = getDefaultPropertiesForObject(OBJECT_TYPE.PRODUCT); + + const client = new Client({ accessToken: context.auth.access_token }); + + const productDetails = await client.crm.products.basicApi.getById(productId, [ + ...defaultProductProperties, + ...additionalPropertiesToRetrieve, + ]); + + return productDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-ticket.ts b/packages/pieces/community/hubspot/src/lib/actions/get-ticket.ts new file mode 100644 index 0000000..f03d4ad --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-ticket.ts @@ -0,0 +1,50 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { Client } from '@hubspot/api-client'; +import { MarkdownVariant } from '@activepieces/shared'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { hubspotAuth } from '../../'; + +export const getTicketAction = createAction({ + auth: hubspotAuth, + name: 'get-ticket', + displayName: 'Get Ticket', + description: 'Gets a ticket.', + props: { + ticketId: Property.ShortText({ + displayName: 'Ticket ID', + description: 'The ID of the ticket to get.', + required: true, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + subject, content, source_type, createdate, hs_pipeline, hs_pipeline_stage, hs_resolution, hs_ticket_category, hs_ticket_id, hs_ticket_priority, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { ticketId, } = context.propsValue; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve??[]; + + + const defaultTicketProperties = getDefaultPropertiesForObject(OBJECT_TYPE.TICKET); + + const client = new Client({ accessToken: context.auth.access_token }); + + const ticketDetails = await client.crm.tickets.basicApi.getById(ticketId, [ + ...defaultTicketProperties, + ...additionalPropertiesToRetrieve, + ]); + + return ticketDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/remove-associations.ts b/packages/pieces/community/hubspot/src/lib/actions/remove-associations.ts new file mode 100644 index 0000000..442017b --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/remove-associations.ts @@ -0,0 +1,108 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + fromObjectTypeAssociationDropdown, + associationTypeDropdown, + toObjectIdsDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { AssociationSpecAssociationCategoryEnum } from '../common/types'; + +export const removeAssociationsAction = createAction({ + auth: hubspotAuth, + name: 'remove-associations', + displayName: 'Remove Associations', + description: 'Removes associations between objects', + props: { + fromObjectId: Property.ShortText({ + displayName: 'From Object ID', + description: 'The ID of the object you want to remove the association from.', + required: true, + }), + fromObjectType: fromObjectTypeAssociationDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'From Object Type', + required: true, + description: 'The type of the object you want to remove the association from.', + }), + toObjectType: fromObjectTypeAssociationDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'To Object Type', + required: true, + description: "Type of the currently associated objects that you're removing the association from.", + }), + associationType: associationTypeDropdown, + toObjectIds: toObjectIdsDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'To Object IDs', + description: 'The IDs of the currently associated objects that you\'re removing the association from.', + required: true, + }), + }, + async run(context) { + const { fromObjectId, fromObjectType, toObjectType, associationType } = context.propsValue; + + const client = new Client({ accessToken: context.auth.access_token }); + + if(context.propsValue.toObjectIds === undefined) { + throw new Error('Please provide To Object IDs'); + } + + let toObjectIds: any[]; + if (Array.isArray(context.propsValue.toObjectIds)) { + toObjectIds = context.propsValue.toObjectIds; + } else { + try { + toObjectIds = JSON.parse(context.propsValue.toObjectIds); + } catch { + throw new Error( + `Please provide To Object IDs in a valid format. Provided : ${JSON.stringify( + context.propsValue.toObjectIds, + )}`, + ); + } + } + + // find the association category + const associationLabels = await client.crm.associations.v4.schema.definitionsApi.getAll( + fromObjectType as string, + toObjectType as string, + ); + const association = associationLabels.results.find( + (associationLabel) => associationLabel.typeId === associationType, + ); + if (!association) { + throw new Error( + `Association type ${associationType} not found for ${fromObjectType} to ${toObjectType}`, + ); + } + const associationCategory = association.category; + + const response = await client.crm.associations.v4.batchApi.archiveLabels( + fromObjectType as string, + toObjectType as string, + { + inputs: toObjectIds.map((objectId) => { + return { + _from: { + id: fromObjectId, + }, + to: { + id: objectId, + }, + types: [ + { + associationCategory: + associationCategory as unknown as AssociationSpecAssociationCategoryEnum, + associationTypeId: associationType, + }, + ], + }; + }), + }, + ); + + return {success: true}; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/remove-contact-from-list.ts b/packages/pieces/community/hubspot/src/lib/actions/remove-contact-from-list.ts new file mode 100644 index 0000000..489f9df --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/remove-contact-from-list.ts @@ -0,0 +1,51 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { staticListsDropdown } from '../common/props'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum, HubSpotAddContactsToListResponse } from '../common/types'; + +export const removeContactFromListAction = createAction({ + auth: hubspotAuth, + name: 'remove-contact-from-list', + displayName: 'Remove Contact from List', + description: 'Remove a contact from a specific list.', + props: { + listId: staticListsDropdown, + email: Property.ShortText({ + displayName: 'Contact Email', + required: true, + }), + }, + async run(context) { + const { listId, email } = context.propsValue; + + const client = new Client({ accessToken: context.auth.access_token }); + + const contact = await client.crm.contacts.searchApi.doSearch({ + limit: 1, + filterGroups: [ + { filters: [{ propertyName: 'email', operator: FilterOperatorEnum.Eq, value: email }] }, + ], + }); + if (contact.results.length === 0) { + throw new Error( + `No contact with email '${email}' was found. Unable to remove unknown contact from list.`, + ); + } + const apiResponse = await client.apiRequest({ + path: `/contacts/v1/lists/${listId}/remove`, + method: HttpMethod.POST, + body: { vids: [contact.results[0].id] }, + defaultJson: true, + }); + + const parsedResponse =(await apiResponse.json()) as HubSpotAddContactsToListResponse; + if(parsedResponse.updated.length === 0) { + throw new Error( + `Contact with email '${email}' wasn't a member of list with id '${listId}'.`, + ); + } + return parsedResponse; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/remove-email-subscription.ts b/packages/pieces/community/hubspot/src/lib/actions/remove-email-subscription.ts new file mode 100644 index 0000000..2ae7779 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/remove-email-subscription.ts @@ -0,0 +1,34 @@ +import { hubspotAuth } from '../../'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; + +export const removeEmailSubscriptionAction = createAction({ + auth: hubspotAuth, + name: 'remove-email-subscription', + displayName: 'Remove Email Subscription', + description: 'Removes email subscription.', + props: { + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + }, + async run(context) { + const { email } = context.propsValue; + + // https://developers.hubspot.com/docs/reference/api/marketing/subscriptions-preferences/v1#update-email-subscription-status-for-an-email-address + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: `https://api.hubapi.com/email/public/v1/subscriptions/${email}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + body: { + unsubscribeFromAll: true, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/update-company.ts b/packages/pieces/community/hubspot/src/lib/actions/update-company.ts new file mode 100644 index 0000000..d06411d --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/update-company.ts @@ -0,0 +1,67 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + + getDefaultPropertiesForObject, + standardObjectDynamicProperties, + standardObjectPropertiesDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; +import { Client } from '@hubspot/api-client'; + +export const updateCompanyAction = createAction({ + auth: hubspotAuth, + name: 'update-company', + displayName: 'Update Company', + description: 'Updates a company in Hubspot.', + props: { + companyId: Property.ShortText({ + displayName: 'Company ID', + description: 'The ID of the company to update.', + required: true, + }), + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.COMPANY, []), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, domain, industry, about_us, phone, address, address2, city, state, zip, country, website, type, description, founded_year, hs_createdate, hs_lastmodifieddate, hs_object_id, is_public, timezone, total_money_raised, total_revenue, owneremail, ownername, numberofemployees, annualrevenue, lifecyclestage, createdate, web_technologies + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const companyId = context.propsValue.companyId; + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const companyProperties: Record = {}; + + // Add additional properties to the companyProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + companyProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const updatedCompany = await client.crm.companies.basicApi.update(companyId, { + properties: companyProperties, + }); + // Retrieve default properties for the comapny and merge with additional properties to retrieve + const defaultcompanyProperties = getDefaultPropertiesForObject(OBJECT_TYPE.COMPANY); + + const companyDetails = await client.crm.companies.basicApi.getById(updatedCompany.id, [ + ...defaultcompanyProperties, + ...additionalPropertiesToRetrieve, + ]); + + return companyDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/update-contact.ts b/packages/pieces/community/hubspot/src/lib/actions/update-contact.ts new file mode 100644 index 0000000..75d1288 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/update-contact.ts @@ -0,0 +1,67 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + getDefaultPropertiesForObject, + standardObjectDynamicProperties, + standardObjectPropertiesDropdown, + +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; +import { Client } from '@hubspot/api-client'; + +export const updateContactAction = createAction({ + auth: hubspotAuth, + name: 'update-contact', + displayName: 'Update Contact', + description: 'Updates a contact in Hubspot.', + props: { + contactId: Property.ShortText({ + displayName: 'Contact ID', + description: 'The ID of the contact to update.', + required: true, + }), + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.CONTACT, []), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + firstname, lastname, email, company, website, mobilephone, phone, fax, address, city, state, zip, salutation, country, jobtitle, hs_createdate, hs_email_domain, hs_object_id, lastmodifieddate, hs_persona, hs_language, lifecyclestage, createdate, numemployees, annualrevenue, industry + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const {contactId} = context.propsValue; + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const contactProperties: Record = {}; + + // Add additional properties to the contactProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + contactProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const updatedContact = await client.crm.contacts.basicApi.update(contactId, { + properties: contactProperties, + }); + // Retrieve default properties for the contact and merge with additional properties to retrieve + const defaultContactProperties = getDefaultPropertiesForObject(OBJECT_TYPE.CONTACT); + + const contactDetails = await client.crm.contacts.basicApi.getById(updatedContact.id, [ + ...defaultContactProperties, + ...additionalPropertiesToRetrieve, + ]); + + return contactDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/update-custom-object.ts b/packages/pieces/community/hubspot/src/lib/actions/update-custom-object.ts new file mode 100644 index 0000000..c928441 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/update-custom-object.ts @@ -0,0 +1,80 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../..'; +import { + customObjectDropdown, + customObjectDynamicProperties, + customObjectPropertiesDropdown, +} from '../common/props'; + +import { Client } from '@hubspot/api-client'; + +export const updateCustomObjectAction = createAction({ + auth: hubspotAuth, + name: 'update-custome-object', + displayName: 'Update Custom Object', + description: 'Updates a custom object in Hubspot.', + props: { + customObjectType: customObjectDropdown, + customObjectId: Property.ShortText({ + displayName: 'Custom Object ID', + description: 'The ID of the custom object to update.', + required: true, + }), + objectProperties: customObjectDynamicProperties, + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + hs_object_id, hs_lastmodifieddate, hs_createdate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: customObjectPropertiesDropdown('Additional Properties to Retrieve', false), + }, + async run(context) { + const customObjectType = context.propsValue.customObjectType as string; + const customObjectId = context.propsValue.customObjectId as string; + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = + context.propsValue.additionalPropertiesToRetrieve?.['values']; + + let propertiesToRetrieve; + try { + if (Array.isArray(additionalPropertiesToRetrieve)) { + propertiesToRetrieve = additionalPropertiesToRetrieve; + } + if (typeof additionalPropertiesToRetrieve === 'string') { + propertiesToRetrieve = JSON.parse(additionalPropertiesToRetrieve as string); + } + } catch (error) { + propertiesToRetrieve = []; + } + + const customObjectProperties: Record = {}; + + // Add additional properties to the customObjectProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + customObjectProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const updatedCustomObject = await client.crm.objects.basicApi.update( + customObjectType, + customObjectId, + { + properties: customObjectProperties, + }, + ); + + const customObjectDetails = await client.crm.objects.basicApi.getById( + customObjectType, + updatedCustomObject.id, + propertiesToRetrieve, + ); + + return customObjectDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/update-deal.ts b/packages/pieces/community/hubspot/src/lib/actions/update-deal.ts new file mode 100644 index 0000000..8a63cbb --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/update-deal.ts @@ -0,0 +1,103 @@ +import { hubspotAuth } from '../../'; + +import { + Property, + createAction, +} from '@activepieces/pieces-framework'; + +import { MarkdownVariant } from '@activepieces/shared'; +import { OBJECT_TYPE } from '../common/constants'; +import { + getDefaultPropertiesForObject, + pipelineDropdown, + pipelineStageDropdown, + standardObjectDynamicProperties, + standardObjectPropertiesDropdown, +} from '../common/props'; + +import { Client } from '@hubspot/api-client'; + +export const updateDealAction = createAction({ + auth: hubspotAuth, + name: 'update-deal', + displayName: 'Update Deal', + description: 'Updates a deal in HubSpot.', + props: { + dealId: Property.ShortText({ + displayName: 'Deal ID', + description: 'The ID of the deal to update.', + required: true, + }), + dealname: Property.ShortText({ + displayName: 'Deal Name', + required: false, + }), + pipelineId: pipelineDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Deal Pipeline', + required: false, + }), + pipelineStageId: pipelineStageDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Deal Stage', + required: false, + }), + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.DEAL, [ + 'dealname', + 'pipeline', + 'dealstage', + ]), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + dealtype, dealname, amount, description, closedate, createdate, num_associated_contacts, hs_forecast_amount, hs_forecast_probability, hs_manual_forecast_category, hs_next_step, hs_object_id, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { dealId, dealname, pipelineId, pipelineStageId } = context.propsValue; + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const dealProperties: Record = {}; + + if (dealname) { + dealProperties['dealname'] = dealname; + } + if (pipelineId) { + dealProperties['pipeline'] = pipelineId; + } + if (pipelineStageId) { + dealProperties['dealstage'] = pipelineStageId; + } + + // Add additional properties to the dealProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + dealProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const updatedDeal = await client.crm.deals.basicApi.update(dealId, { + properties: dealProperties, + }); + // Retrieve default properties for the deal and merge with additional properties to retrieve + const defaultDealProperties = getDefaultPropertiesForObject(OBJECT_TYPE.DEAL); + + const dealDetails = await client.crm.deals.basicApi.getById(updatedDeal.id, [ + ...defaultDealProperties, + ...additionalPropertiesToRetrieve, + ]); + + return dealDetails; + }, +}); + diff --git a/packages/pieces/community/hubspot/src/lib/actions/update-line-item.ts b/packages/pieces/community/hubspot/src/lib/actions/update-line-item.ts new file mode 100644 index 0000000..3fb393a --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/update-line-item.ts @@ -0,0 +1,81 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + getDefaultPropertiesForObject, + productDropdown, + standardObjectDynamicProperties, + standardObjectPropertiesDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; + +import { Client } from '@hubspot/api-client'; + +export const updateLineItemAction = createAction({ + auth: hubspotAuth, + name: 'update-line-item', + displayName: 'Update Line Item', + description: 'Updates a line item in Hubspot.', + props: { + lineItemId: Property.ShortText({ + displayName: 'Line Item ID', + description: 'The ID of the line item to update.', + required: true, + }), + productId: productDropdown({ + displayName: 'Line Item Information: Product ID', + required: false, + objectType: OBJECT_TYPE.PRODUCT, + }), + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.LINE_ITEM, ['hs_product_id']), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, description, price, quantity, amount, discount, tax, createdate, hs_object_id, hs_product_id, hs_images, hs_lastmodifieddate, hs_line_item_currency_code, hs_sku, hs_url, hs_cost_of_goods_sold, hs_discount_percentage, hs_term_in_months + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.LINE_ITEM, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const lineItemId = context.propsValue.lineItemId; + const productId = context.propsValue.productId; + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const lineItemProperties: Record = { + hs_product_id: productId!, + }; + + if(productId) + { + lineItemProperties['hs_product_id'] = productId; + } + + // Add additional properties to the lineItemProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + lineItemProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const createdLineItem = await client.crm.lineItems.basicApi.update(lineItemId, { + properties: lineItemProperties, + }); + // Retrieve default properties for the line item and merge with additional properties to retrieve + const defaultlineItemProperties = getDefaultPropertiesForObject(OBJECT_TYPE.LINE_ITEM); + + const lineItemDetails = await client.crm.lineItems.basicApi.getById(createdLineItem.id, [ + ...defaultlineItemProperties, + ...additionalPropertiesToRetrieve, + ]); + + return lineItemDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/update-product.ts b/packages/pieces/community/hubspot/src/lib/actions/update-product.ts new file mode 100644 index 0000000..0f94bb7 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/update-product.ts @@ -0,0 +1,67 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + getDefaultPropertiesForObject, + standardObjectDynamicProperties, + standardObjectPropertiesDropdown, + +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; +import { Client } from '@hubspot/api-client'; + +export const updateProductAction = createAction({ + auth: hubspotAuth, + name: 'update-product', + displayName: 'Update Product', + description: 'Updates a product in Hubspot.', + props: { + productId:Property.ShortText({ + displayName:'Product ID', + description:'The ID of the product to update.', + required:true + }), + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.PRODUCT,[]), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + createdate, description, name, price, tax, hs_lastmodifieddate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.PRODUCT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const productId = context.propsValue.productId; + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const productProperties: Record = {}; + + // Add additional properties to the productProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + productProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const updatedProduct = await client.crm.products.basicApi.update(productId, { + properties: productProperties, + }); + // Retrieve default properties for the product and merge with additional properties to retrieve + const defaultproductProperties = getDefaultPropertiesForObject(OBJECT_TYPE.PRODUCT); + + const productDetails = await client.crm.products.basicApi.getById(updatedProduct.id, [ + ...defaultproductProperties, + ...additionalPropertiesToRetrieve, + ]); + + return productDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/update-ticket.ts b/packages/pieces/community/hubspot/src/lib/actions/update-ticket.ts new file mode 100644 index 0000000..1527995 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/update-ticket.ts @@ -0,0 +1,102 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; + +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { getDefaultPropertiesForObject, pipelineDropdown, pipelineStageDropdown, standardObjectDynamicProperties, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; + +export const updateTicketAction = createAction({ + auth: hubspotAuth, + name: 'update-ticket', + displayName: 'Update Ticket', + description: 'Updates a ticket in HubSpot.', + props: { + ticketId: Property.ShortText({ + displayName: 'Ticket ID', + description: 'The ID of the ticket to update.', + required: true, + }), + ticketName: Property.ShortText({ + displayName: 'Ticket Name', + required: false, + }), + pipelineId: pipelineDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Ticket Pipeline', + required: false, + }), + pipelineStageId: pipelineStageDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Ticket Pipeline Stage', + required: false, + }), + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.TICKET, [ + 'subject', + 'hs_pipeline', + 'hs_pipeline_stage', + ]), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + subject, content, source_type, createdate, hs_pipeline, hs_pipeline_stage, hs_resolution, hs_ticket_category, hs_ticket_id, hs_ticket_priority, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { + ticketId, + ticketName, + pipelineId, + pipelineStageId, + } = context.propsValue; + const objectProperties = context.propsValue.objectProperties??{}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve??[]; + + + const ticketProperties: Record = { + }; + + if(ticketName) + { + ticketProperties['subject'] = ticketName; + } + if(pipelineId) + { + ticketProperties['hs_pipeline'] = pipelineId; + } + if(pipelineStageId) + { + ticketProperties['hs_pipeline_stage'] = pipelineStageId; + } + + // Add additional properties to the ticketProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + ticketProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const updatedTicket = await client.crm.tickets.basicApi.update(ticketId,{ + properties: ticketProperties, + }); + + // Retrieve default properties for the ticket and merge with additional properties to retrieve + const defaultTicketProperties = getDefaultPropertiesForObject(OBJECT_TYPE.TICKET); + + const ticketDetails = await client.crm.tickets.basicApi.getById(updatedTicket.id, [ + ...defaultTicketProperties, + ...additionalPropertiesToRetrieve, + ]); + + return ticketDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/upload-file.ts b/packages/pieces/community/hubspot/src/lib/actions/upload-file.ts new file mode 100644 index 0000000..312f0cb --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/upload-file.ts @@ -0,0 +1,106 @@ +import { hubspotAuth } from '../../'; +import { + createAction, + DropdownOption, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; + +export const uploadFileAction = createAction({ + auth: hubspotAuth, + name: 'upload-file', + displayName: 'Upload File', + description: 'Uploads a file to HubSpot File Manager.', + props: { + folderId: Property.Dropdown({ + displayName: 'Folder', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account.', + }; + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + const limit = 100; + const options: DropdownOption[] = []; + let after: string | undefined; + + do { + const response = await client.files.foldersApi.doSearch( + undefined, + after, + undefined, + limit, + ); + + for (const folder of response.results) { + options.push({ + value: folder.id, + label: folder.name ?? folder.id, + }); + } + after = response.paging?.next?.after; + } while (after); + + return { + disabled: false, + options, + }; + }, + }), + fileName: Property.ShortText({ + displayName: 'File Name', + required: true, + }), + accessLevel: Property.StaticDropdown({ + displayName: 'Access Level', + required: true, + options: { + disabled: false, + options: [ + { + value: 'PUBLIC_INDEXABLE', + label: 'PUBLIC_INDEXABLE', + }, + { + value: 'PUBLIC_NOT_INDEXABLE', + label: 'PUBLIC_NOT_INDEXABLE', + }, + { label: 'PRIVATE', value: 'PRIVATE' }, + ], + }, + }), + file: Property.File({ + displayName: 'File', + required: true, + }), + }, + async run(context) { + const { accessLevel, fileName, folderId, file } = context.propsValue; + const client = new Client({ accessToken: context.auth.access_token }); + + const response = await client.files.filesApi.upload( + { + name: fileName, + data: file.data, + }, + folderId, + undefined, + fileName, + undefined, + JSON.stringify({ + access: accessLevel, + }), + ); + + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/common/constants.ts b/packages/pieces/community/hubspot/src/lib/common/constants.ts new file mode 100644 index 0000000..54f02d7 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/common/constants.ts @@ -0,0 +1,177 @@ +export const enum OBJECT_TYPE { + CONTACT = 'contact', + COMPANY = 'company', + DEAL = 'deal', + TICKET = 'ticket', + PRODUCT = 'product', + LINE_ITEM = 'line_item', + TASK = 'task', +} + +export const DEFAULT_CONTACT_PROPERTIES = [ + 'firstname', + 'lastname', + 'email', + 'company', + 'website', + 'mobilephone', + 'phone', + 'fax', + 'address', + 'city', + 'state', + 'zip', + 'salutation', + 'country', + 'jobtitle', + 'hs_createdate', + 'hs_email_domain', + 'hs_object_id', + 'lastmodifieddate', + 'hs_persona', + 'hs_language', + 'lifecyclestage', + 'createdate', + 'numemployees', + 'annualrevenue', + 'industry', +]; + +export const DEFAULT_DEAL_PROPERTIES = [ + 'dealtype', + 'dealname', + 'amount', + 'description', + 'closedate', + 'createdate', + 'num_associated_contacts', + 'hs_forecast_amount', + 'hs_forecast_probability', + 'hs_manual_forecast_category', + 'hs_next_step', + 'hs_object_id', + 'hs_lastmodifieddate', + 'hubspot_owner_id', + 'hubspot_team_id', +]; + +export const DEFAULT_TICKET_PROPERTIES = [ + 'subject', + 'content', + 'source_type', + 'createdate', + 'hs_pipeline', + 'hs_pipeline_stage', + 'hs_resolution', + 'hs_ticket_category', + 'hs_ticket_id', + 'hs_ticket_priority', + 'hs_lastmodifieddate', + 'hubspot_owner_id', + 'hubspot_team_id', +]; + +export const DEFAULT_COMPANY_PROPERTIES = [ + 'name', + 'domain', + 'industry', + 'about_us', + 'phone', + 'address', + 'address2', + 'city', + 'state', + 'zip', + 'country', + 'website', + 'type', + 'description', + 'founded_year', + 'hs_createdate', + 'hs_lastmodifieddate', + 'hs_object_id', + 'is_public', + 'timezone', + 'total_money_raised', + 'total_revenue', + 'owneremail', + 'ownername', + 'numberofemployees', + 'annualrevenue', + 'lifecyclestage', + 'createdate', + 'web_technologies', +]; + +export const DEFAULT_PRODUCT_PROPERTIES = [ + 'createdate', + 'description', + 'name', + 'price', + 'tax', + 'hs_lastmodifieddate', +]; + +export const DEFAULT_LINE_ITEM_PROPERTIES = [ + 'name', + 'description', + 'price', + 'quantity', + 'amount', + 'discount', + 'tax', + 'createdate', + 'hs_object_id', + 'hs_product_id', + 'hs_images', + 'hs_lastmodifieddate', + 'hs_line_item_currency_code', + 'hs_sku', + 'hs_url', + 'hs_cost_of_goods_sold', + 'hs_discount_percentage', + 'hs_term_in_months', +]; + + + +export const DEFAULT_TASK_PROPERTIES = [ + 'hs_task_body', + 'hubspot_owner_id', + 'hs_task_subject', + 'hs_task_status', + 'hs_task_priority', + 'hs_task_type', + 'hs_created_by', + 'hs_repeat_status', + 'hs_task_completion_date', + 'hs_task_is_completed', + 'hs_timestamp', + 'hs_queue_membership_ids', + 'hs_lastmodifieddate', + 'hs_createdate', +]; + +export const STANDARD_OBJECT_TYPES = [ + { + label: 'Contacts', + value: OBJECT_TYPE.CONTACT, + }, + { + label: 'Companies', + value: OBJECT_TYPE.COMPANY, + }, + { + label: 'Deals', + value: OBJECT_TYPE.DEAL, + }, + { + label: 'Tickets', + value: OBJECT_TYPE.TICKET, + }, + { + label: 'Line Items', + value: OBJECT_TYPE.LINE_ITEM, + }, +]; + diff --git a/packages/pieces/community/hubspot/src/lib/common/props.ts b/packages/pieces/community/hubspot/src/lib/common/props.ts new file mode 100644 index 0000000..27fa74b --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/common/props.ts @@ -0,0 +1,987 @@ +import { + DropdownOption, + DynamicPropsValue, + OAuth2PropertyValue, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { WorkflowResponse, HubspotProperty, HubspotFieldType, ListBlogsResponse } from './types'; +import { + DEFAULT_COMPANY_PROPERTIES, + DEFAULT_CONTACT_PROPERTIES, + DEFAULT_DEAL_PROPERTIES, + DEFAULT_LINE_ITEM_PROPERTIES, + DEFAULT_PRODUCT_PROPERTIES, + DEFAULT_TICKET_PROPERTIES, + DEFAULT_TASK_PROPERTIES, + OBJECT_TYPE, + STANDARD_OBJECT_TYPES, +} from './constants'; +import { Client } from '@hubspot/api-client'; +import { hubspotAuth } from '../../'; + +const buildEmptyList = ({ placeholder }: { placeholder: string }) => { + return { + disabled: true, + options: [], + placeholder, + }; +}; + +export function getDefaultPropertiesForObject(objectType: OBJECT_TYPE): string[] { + switch (objectType) { + case OBJECT_TYPE.CONTACT: + return DEFAULT_CONTACT_PROPERTIES; + case OBJECT_TYPE.DEAL: + return DEFAULT_DEAL_PROPERTIES; + case OBJECT_TYPE.TICKET: + return DEFAULT_TICKET_PROPERTIES; + case OBJECT_TYPE.COMPANY: + return DEFAULT_COMPANY_PROPERTIES; + case OBJECT_TYPE.PRODUCT: + return DEFAULT_PRODUCT_PROPERTIES; + case OBJECT_TYPE.LINE_ITEM: + return DEFAULT_LINE_ITEM_PROPERTIES; + case OBJECT_TYPE.TASK: + return DEFAULT_TASK_PROPERTIES; + default: + return []; + } +} + +async function fetchOwnersOptions(accessToken: string): Promise[]> { + const client = new Client({ accessToken: accessToken }); + const limit = 100; + const options: DropdownOption[] = []; + + let after: string | undefined; + do { + const response = await client.crm.owners.ownersApi.getPage(undefined, after, limit); + for (const owner of response.results) + if (owner.email) { + options.push({ + label: owner.email, + value: owner.id, + }); + } + after = response.paging?.next?.after; + } while (after); + return options; +} + +async function fetchUsersOptions(accessToken: string): Promise[]> { + const client = new Client({ accessToken: accessToken }); + const limit = 100; + const options: DropdownOption[] = []; + + let after: string | undefined; + do { + const response = await client.settings.users.usersApi.getPage(limit, after); + for (const user of response.results) { + if (user.email) { + options.push({ + label: user.email, + value: user.id, + }); + } + } + after = response.paging?.next?.after; + } while (after); + return options; +} + +async function fetchTeamsOptions(accessToken: string): Promise[]> { + const client = new Client({ accessToken: accessToken }); + const options: DropdownOption[] = []; + + const response = await client.settings.users.teamsApi.getAll(); + for (const team of response.results) { + if (team.name) { + options.push({ + label: team.name, + value: team.id, + }); + } + } + return options; +} + +async function fetchCurrenciesOptions(accessToken: string): Promise[]> { + const options: DropdownOption[] = []; + + const response = await httpClient.sendRequest<{ + results: Array<{ currencyCode: string; currencyName: string }>; + }>({ + method: HttpMethod.GET, + url: 'https://api.hubapi.com/settings/v3/currencies/codes', + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }); + for (const currency of response.body.results) { + options.push({ + label: currency.currencyName, + value: currency.currencyCode, + }); + } + return options; +} + +// async function fetchBusinessUnitsOptions(accessToken: string): Promise[]> { +// const client = new Client({ accessToken: accessToken }); +// const options: DropdownOption[] = []; + +// const response = await client.settings.businessUnits.businessUnitApi.getByUserID() +// for (const businessUnit of response.results) { +// if (businessUnit.name) { +// options.push({ +// label: businessUnit.name, +// value: businessUnit.id, +// }); +// } +// } +// return options; +// } + +async function createReferencedPropertyDefinition( + property: HubspotProperty, + propertyDisplayName: string, + accessToken: string, +) { + let options: DropdownOption[] = []; + + switch (property.referencedObjectType) { + case 'OWNER': + options = await fetchOwnersOptions(accessToken); + break; + default: + return null; + } + + return Property.StaticDropdown({ + displayName: propertyDisplayName, + required: false, + options: { + disabled: false, + options, + }, + }); +} + +function createPropertyDefinition(property: HubspotProperty, propertyDisplayName: string) { + switch (property.fieldType) { + case HubspotFieldType.BooleanCheckBox: + return Property.StaticDropdown({ + displayName: propertyDisplayName, + required: true, + defaultValue:'', + options:{ + disabled:false, + options:[ + { + label:'Yes', + value:'true' + }, + { + label:'No', + value:'false' + }, + { + label:"Unanswered", + value:'' + } + ] + } + }); + case HubspotFieldType.Date: + return Property.DateTime({ + displayName: propertyDisplayName, + description: property.type === 'date' ? 'Provide date in YYYY-MM-DD format' : '', + required: false, + }); + case HubspotFieldType.Number: + return Property.Number({ + displayName: propertyDisplayName, + required: false, + }); + case HubspotFieldType.PhoneNumber: + case HubspotFieldType.Text: + return Property.ShortText({ + displayName: propertyDisplayName, + required: false, + }); + case HubspotFieldType.TextArea: + case HubspotFieldType.Html: + return Property.LongText({ + displayName: propertyDisplayName, + required: false, + }); + case HubspotFieldType.CheckBox: + return Property.StaticMultiSelectDropdown({ + displayName: propertyDisplayName, + required: false, + options: { + disabled: false, + options: property.options + ? property.options.map((option) => { + return { + label: option.label, + value: option.value, + }; + }) + : [], + }, + }); + case HubspotFieldType.Select: + case HubspotFieldType.Radio: + return Property.StaticDropdown({ + displayName: propertyDisplayName, + required: false, + options: { + options: property.options + ? property.options.map((option) => { + return { + label: option.label, + value: option.value, + }; + }) + : [], + }, + }); + default: + return null; + } +} + +async function retrieveObjectProperties( + auth: PiecePropValueSchema, + objectType: string, + excludedProperties: string[] = [], +) { + const client = new Client({ accessToken: auth.access_token }); + + // Fetch property groups + const propertyGroups = await client.crm.properties.groupsApi.getAll(objectType); + const groupLabels = propertyGroups.results.reduce((map, group) => { + map[group.name] = group.label; + return map; + }, {} as Record); + + // Fetch all properties for the given object type + const allProperties = await client.crm.properties.coreApi.getAll(objectType); + + const props: DynamicPropsValue = {}; + + for (const property of allProperties.results) { + // skip read only properties + if ( + excludedProperties.includes(property.name) || + property.modificationMetadata?.readOnlyValue || + property.hidden + ) { + continue; + } + + // create property name with property group name + const propertyDisplayName = `${groupLabels[property.groupName] || ''}: ${property.label}`; + + if (property.referencedObjectType) { + props[property.name] = await createReferencedPropertyDefinition( + property, + propertyDisplayName, + auth.access_token, + ); + continue; + } + if (property.name === 'hs_shared_user_ids') { + const userOptions = await fetchUsersOptions(auth.access_token); + props[property.name] = Property.StaticMultiSelectDropdown({ + displayName: propertyDisplayName, + required: false, + options: { + disabled: false, + options: userOptions, + }, + }); + continue; + } + if (['hs_shared_team_ids', 'hs_attributed_team_ids'].includes(property.name)) { + const teamOptions = await fetchTeamsOptions(auth.access_token); + props[property.name] = Property.StaticMultiSelectDropdown({ + displayName: propertyDisplayName, + required: false, + options: { + disabled: false, + options: teamOptions, + }, + }); + continue; + } + if (property.name === 'deal_currency_code') { + const currencyOptions = await fetchCurrenciesOptions(auth.access_token); + props[property.name] = Property.StaticDropdown({ + displayName: propertyDisplayName, + required: false, + options: { + disabled: false, + options: currencyOptions, + }, + }); + continue; + } + if (property.name === 'hs_all_assigned_business_unit_ids') { + // TO DO : Add business unit options + // const businessUnitOptions = await fetchBusinessUnitsOptions(authValue.access_token); + // props[property.name] = Property.StaticMultiSelectDropdown({ + // displayName: propertyDisplayName, + // required: false, + // options: { + // disabled: false, + // options: businessUnitOptions, + // }, + // }); + continue; + } + props[property.name] = createPropertyDefinition(property, propertyDisplayName); + } + // Remove null props + return Object.fromEntries(Object.entries(props).filter(([_, prop]) => prop !== null)); +} + +export const standardObjectDynamicProperties = (objectType: string, excludedProperties: string[]) => + Property.DynamicProperties({ + displayName: 'Object Properties', + refreshers: [], + required: false, + props: async ({ auth }) => { + if (!auth) return {}; + // Useful for Find actions + // if (typeof createIfNotExists === "boolean" && createIfNotExists === false) { + // return {}; + // } + const authValue = auth as PiecePropValueSchema; + return await retrieveObjectProperties(authValue, objectType, excludedProperties); + }, + }); + +export const customObjectDynamicProperties = Property.DynamicProperties({ + displayName: 'Custom Object Properties', + refreshers: ['customObjectType'], + required: false, + props: async ({ auth, customObjectType }) => { + if (!auth || !customObjectType) { + return {}; + } + const authValue = auth as PiecePropValueSchema; + return await retrieveObjectProperties(authValue, customObjectType as unknown as string); + }, +}); + +export const standardObjectPropertiesDropdown = ( + params: DropdownParams, + includeDefaultProperties = false, + isSingleSelect = false, +) => { + const dropdownFunction = isSingleSelect ? Property.Dropdown : Property.MultiSelectDropdown; + return dropdownFunction({ + displayName: params.displayName, + refreshers: [], + required: params.required, + description: params.description, + options: async ({ auth }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + // Fetch all properties for the given object type + const allProperties = await client.crm.properties.coreApi.getAll(params.objectType); + + const propertyGroups = await client.crm.properties.groupsApi.getAll(params.objectType); + + const groupLabels = propertyGroups.results.reduce((map, group) => { + map[group.name] = group.label; + return map; + }, {} as Record); + + const defaultProperties = includeDefaultProperties + ? [] + : getDefaultPropertiesForObject(params.objectType); + + // Filter and create options for properties that are not default + const options: DropdownOption[] = []; + for (const property of allProperties.results) { + if (!includeDefaultProperties && defaultProperties.includes(property.name)) { + continue; + } + const propertyDisplayName = `${groupLabels[property.groupName] || ''}: ${property.label}`; + options.push({ + label: propertyDisplayName, + value: property.name, + }); + } + + return { + disabled: false, + options, + }; + }, + }); +}; + +export const customObjectPropertiesDropdown = ( + displayName: string, + required: boolean, + isSingleSelect = false, +) => + Property.DynamicProperties({ + displayName, + refreshers: ['customObjectType'], + required, + props: async ({ auth, customObjectType }) => { + if (!auth || !customObjectType) { + return {}; + } + const authValue = auth as PiecePropValueSchema; + + const client = new Client({ accessToken: authValue.access_token }); + + // Fetch all properties for the given object type + const allProperties = await client.crm.properties.coreApi.getAll( + customObjectType as unknown as string, + ); + + const propertyGroups = await client.crm.properties.groupsApi.getAll( + customObjectType as unknown as string, + ); + + const groupLabels = propertyGroups.results.reduce((map, group) => { + map[group.name] = group.label; + return map; + }, {} as Record); + + const options: DropdownOption[] = []; + for (const property of allProperties.results) { + const propertyDisplayName = `${groupLabels[property.groupName] || ''}: ${property.label}`; + options.push({ + label: propertyDisplayName, + value: property.name, + }); + } + + const props: DynamicPropsValue = {}; + const dropdownFunction = isSingleSelect + ? Property.StaticDropdown + : Property.StaticMultiSelectDropdown; + + props['values'] = dropdownFunction({ + displayName, + required, + options: { + disabled: false, + options, + }, + }); + return props; + }, + }); + +export const workflowIdDropdown = Property.Dropdown({ + displayName: 'Workflow', + refreshers: [], + // description: 'Workflow to add contact to', + required: true, + options: async ({ auth }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const token = (auth as OAuth2PropertyValue).access_token; + const workflowsResponse = await httpClient.sendRequest<{ + workflows: WorkflowResponse[]; + }>({ + method: HttpMethod.GET, + url: `https://api.hubapi.com/automation/v2/workflows`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: token, + }, + }); + const options: DropdownOption[] = []; + + for (const workflow of workflowsResponse.body.workflows) { + if (workflow.enabled) { + options.push({ + label: workflow.name, + value: workflow.id, + }); + } + } + + return { + disabled: false, + options, + }; + }, +}); + +export const pipelineDropdown = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + refreshers: [], + required: params.required, + description: params.description, + options: async ({ auth }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + const pipelinesResponse = await client.crm.pipelines.pipelinesApi.getAll(params.objectType); + + const options = pipelinesResponse.results.map((pipeline) => { + return { + label: pipeline.label, + value: pipeline.id, + }; + }); + return { + disabled: false, + options, + }; + }, + }); + +export const pipelineStageDropdown = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + refreshers: ['pipelineId'], + required: params.required, + description: params.description, + options: async ({ auth, pipelineId }) => { + if (!auth || !pipelineId) { + return buildEmptyList({ + placeholder: 'Please connect your account and select a pipeline.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + const pipelineStagesResponse = await client.crm.pipelines.pipelineStagesApi.getAll( + params.objectType, + pipelineId as string, + ); + + const options = pipelineStagesResponse.results.map((stage) => { + return { + label: stage.label, + value: stage.id, + }; + }); + + return { + disabled: false, + options, + }; + }, + }); + +export const productDropdown = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + refreshers: [], + required: params.required, + description: params.description, + options: async ({ auth }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + const options: DropdownOption[] = []; + + const limit = 100; + let after: string | undefined; + do { + const response = await client.crm.products.basicApi.getPage(limit, after, ['name']); + for (const product of response.results) { + options.push({ + label: product.properties.name ?? product.id, + value: product.id, + }); + } + + after = response.paging?.next?.after; + } while (after); + + return { + disabled: false, + options, + }; + }, + }); +export const customObjectDropdown = Property.Dropdown({ + displayName: 'Type of Custom Object', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + const customObjectsResponse = await client.crm.schemas.coreApi.getAll(); + + const options = customObjectsResponse.results.map((customObj) => { + return { + label: customObj.labels.plural ?? customObj.name, + value: customObj.objectTypeId, + }; + }); + + return { + disabled: false, + options, + }; + }, +}); + +export const staticListsDropdown = Property.Dropdown({ + displayName: 'List ID', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const options: DropdownOption[] = []; + + let offset = 0; + let hasMore = true; + do { + const request: HttpRequest = { + url: 'https://api.hubapi.com/contacts/v1/lists/static', + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authValue.access_token, + }, + queryParams: { + count: '100', + offset: offset.toString(), + }, + }; + const response = await httpClient.sendRequest<{ + total: number; + offset: number; + 'has-more': boolean; + lists: Array<{ name: string; listId: number }>; + }>(request); + + for (const list of response.body.lists) { + options.push({ + label: list.name, + value: list.listId, + }); + } + offset += 100; + hasMore = response.body['has-more']; + } while (hasMore); + + return { + disabled: false, + options, + }; + }, +}); + +export const fromObjectTypeAssociationDropdown = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + refreshers: [], + required: params.required, + description: params.description, + options: async ({ auth }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + const customObjectsResponse = await client.crm.schemas.coreApi.getAll(); + + // + const options = customObjectsResponse.results.map((customObj) => { + return { + label: customObj.labels.plural ?? customObj.name, + value: customObj.objectTypeId, + }; + }); + return { + disabled: false, + options: [...STANDARD_OBJECT_TYPES, ...options], + }; + }, + }); + +export const associationTypeDropdown = Property.Dropdown({ + displayName: 'Type of the association', + refreshers: ['fromObjectType', 'toObjectType'], + required: true, + options: async ({ auth, fromObjectType, toObjectType }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + const associationLabels = await client.crm.associations.v4.schema.definitionsApi.getAll( + fromObjectType as string, + toObjectType as string, + ); + + const options = associationLabels.results.map((associationLabel) => { + return { + label: associationLabel.label ?? `${fromObjectType}_to_${toObjectType}`, + value: associationLabel.typeId, + }; + }); + + return { + disabled: false, + options, + }; + }, +}); + +export const toObjectIdsDropdown = (params: DropdownParams) => + Property.MultiSelectDropdown({ + displayName: params.displayName, + description: params.description, + refreshers: ['toObjectType'], + required: params.required, + options: async ({ auth, toObjectType }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + const limit = 100; + const options: DropdownOption[] = []; + let after: string | undefined; + do { + const response = await client.crm.objects.basicApi.getPage( + toObjectType as string, + limit, + after, + ); + for (const object of response.results) { + let labelName; + switch (toObjectType) { + case OBJECT_TYPE.CONTACT: + labelName = 'email'; + break; + case OBJECT_TYPE.COMPANY: + labelName = 'name'; + break; + case OBJECT_TYPE.DEAL: + labelName = 'dealname'; + break; + case OBJECT_TYPE.TICKET: + labelName = 'subject'; + break; + case OBJECT_TYPE.LINE_ITEM: + labelName = 'name'; + break; + } + options.push({ + label: object.properties[labelName!] ?? object.id, + value: object.id, + }); + } + after = response.paging?.next?.after; + } while (after); + + return { + disabled: false, + options, + }; + }, + }); + +export const formDropdown = Property.Dropdown({ + displayName: 'Form', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + const limit = 100; + const options: DropdownOption[] = []; + + let after: string | undefined; + do { + const response = await client.marketing.forms.formsApi.getPage(after, limit); + for (const form of response.results) { + options.push({ + label: form.name, + value: form.id, + }); + } + after = response.paging?.next?.after; + } while (after); + + return { + disabled: false, + options, + }; + }, +}); + +export const blogUrlDropdown = Property.Dropdown({ + displayName: 'Blog URL', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { disabled: true, options: [], placeholder: 'Please connect your account.' }; + } + + const authValue = auth as PiecePropValueSchema; + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.hubapi.com/content/api/v2/blogs', + authentication: { type: AuthenticationType.BEARER_TOKEN, token: authValue.access_token }, + queryParams: { + limit: '100', + }, + }); + + return { + disabled: false, + options: response.body.objects.map((blog) => { + return { + label: blog.absolute_url, + value: blog.id.toString(), + }; + }), + }; + }, +}); + +export const blogAuthorDropdown = Property.Dropdown({ + displayName: 'Blog Author', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { disabled: true, options: [], placeholder: 'Please connect your account.' }; + } + + const authValue = auth as PiecePropValueSchema; + + const client = new Client({ accessToken: authValue.access_token }); + + const options: DropdownOption[] = []; + + let after: string | undefined; + do { + const response = await client.cms.blogs.authors.blogAuthorsApi.getPage( + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + after, + 100, + ); + for (const author of response.results) { + options.push({ + label: author.name, + value: author.id, + }); + } + + after = response.paging?.next?.after; + } while (after); + + return { + disabled: false, + options, + }; + }, +}); + +type DropdownParams = { + objectType: OBJECT_TYPE; + displayName: string; + required: boolean; + description?: string; +}; + +export const pageType=Property.StaticDropdown({ + displayName:'Page Type', + required:true, + defaultValue:'landing_page', + options:{ + disabled:false, + options:[ + { + label:'Landing Page', + value:'landing_page' + }, + { + label:'Site Page', + value:'site_page' + } + ] + } +}) \ No newline at end of file diff --git a/packages/pieces/community/hubspot/src/lib/common/types.ts b/packages/pieces/community/hubspot/src/lib/common/types.ts new file mode 100644 index 0000000..17599ec --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/common/types.ts @@ -0,0 +1,75 @@ +export type HubSpotAddContactsToListResponse = { + updated: number[]; + discarded: number[]; + invalidVids: number[]; + invalidEmails: string[]; +}; + +export type HubspotProperty = { + name: string; + label: string; + description: string; + hidden?: boolean; + type: string; + groupName: string; + fieldType: string; + referencedObjectType?: string; + modificationMetadata?: { + archivable: boolean; + readOnlyDefinition: boolean; + readOnlyValue: boolean; + }; + options: Array<{ label: string; value: string }>; +}; + +export type WorkflowResponse = { + id: number; + insertAt: number; + updatedAt: number; + name: string; + enabled: boolean; +}; + +export enum FilterOperatorEnum { + Eq = 'EQ', + Neq = 'NEQ', + Lt = 'LT', + Lte = 'LTE', + Gt = 'GT', + Gte = 'GTE', + Between = 'BETWEEN', + In = 'IN', + NotIn = 'NOT_IN', + HasProperty = 'HAS_PROPERTY', + NotHasProperty = 'NOT_HAS_PROPERTY', + ContainsToken = 'CONTAINS_TOKEN', + NotContainsToken = 'NOT_CONTAINS_TOKEN', +} + +export enum HubspotFieldType { + BooleanCheckBox = 'booleancheckbox', + Date = 'date', + File = 'file', + Number = 'number', + CalculationEquation = 'calculation_equation', + PhoneNumber = 'phonenumber', + Text = 'text', + TextArea = 'textarea', + Html = 'html', + CheckBox = 'checkbox', + Select = 'select', + Radio = 'radio', +} + +export declare enum AssociationSpecAssociationCategoryEnum { + HubspotDefined = 'HUBSPOT_DEFINED', + UserDefined = 'USER_DEFINED', + IntegratorDefined = 'INTEGRATOR_DEFINED', +} + +export type ListBlogsResponse = { + objects: Array<{ absolute_url: string; id: number }>; + offset: number; + total: number; + limit: number; +}; diff --git a/packages/pieces/community/hubspot/src/lib/triggers/deal-stage-updated.ts b/packages/pieces/community/hubspot/src/lib/triggers/deal-stage-updated.ts new file mode 100644 index 0000000..50a0f29 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/deal-stage-updated.ts @@ -0,0 +1,148 @@ +import { + PiecePropValueSchema, + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; + +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import dayjs from 'dayjs'; + +import { hubspotAuth } from '../../'; + +import { + getDefaultPropertiesForObject, + pipelineDropdown, + pipelineStageDropdown, + standardObjectPropertiesDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + additionalPropertiesToRetrieve?: string | string[]; + pipelineId?: string; + stageId?: string; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultDealProperties = getDefaultPropertiesForObject(OBJECT_TYPE.DEAL); + const propertiesToRetrieve = [...defaultDealProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.deals.searchApi.doSearch({ + limit: isTest ? 10 : 100, + properties: propertiesToRetrieve, + sorts: ['-hs_lastmodifieddate'], + after, + filterGroups: [ + { + filters: [ + { + propertyName: 'pipeline', + operator: FilterOperatorEnum.Eq, + value: propsValue.pipelineId, + }, + { + propertyName: 'dealstage', + operator: FilterOperatorEnum.Eq, + value: propsValue.stageId, + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const dealStageUpdatedTrigger = createTrigger({ + auth: hubspotAuth, + name: 'deal-stage-updated', + displayName: 'Updated Deal Stage', + description: 'Triggers when a deal enters a specified stage.', + props: { + pipelineId: pipelineDropdown({ + objectType: OBJECT_TYPE.DEAL, + required: true, + displayName: 'Deal Pipeline', + }), + stageId: pipelineStageDropdown({ + objectType: OBJECT_TYPE.DEAL, + required: true, + displayName: 'Deal Stage', + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + dealtype, dealname, amount, description, closedate, createdate, num_associated_contacts, hs_forecast_amount, hs_forecast_probability, hs_manual_forecast_category, hs_next_step, hs_object_id, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + id: '18011922225', + properties: { + amount: null, + closedate: '2024-03-31T11:28:32.089Z', + createdate: '2024-03-13T11:28:40.586Z', + dealname: 'Second Deal', + dealstage: 'qualifiedtobuy', + hs_lastmodifieddate: '2024-03-13T11:29:16.078Z', + hs_object_id: '18011922225', + pipeline: 'default', + }, + createdAt: '2024-03-13T11:28:40.586Z', + updatedAt: '2024-03-13T11:29:16.078Z', + archived: false, + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/email-subscriptions-timeline.ts b/packages/pieces/community/hubspot/src/lib/triggers/email-subscriptions-timeline.ts new file mode 100644 index 0000000..7056164 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/email-subscriptions-timeline.ts @@ -0,0 +1,101 @@ +import { + AuthenticationType, + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, + QueryParams, +} from '@activepieces/pieces-common'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; + +type SubscriptionTimeLineResponse = { + hasMore: boolean; + offset: string; + timeline: Array>; +}; + +const polling: Polling, Record> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, lastFetchEpochMS }) { + const qs: QueryParams = { limit: '100' }; + if (lastFetchEpochMS) { + qs.startTimestamp = lastFetchEpochMS.toString(); + } + const items = []; + + let hasMore = true; + do { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.hubapi.com/email/public/v1/subscriptions/timeline', + queryParams: qs, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }); + hasMore = response.body.hasMore; + qs.offset = response.body.offset; + for (const item of response.body.timeline) { + items.push(item); + } + } while (hasMore); + + return items.map((item) => ({ + epochMilliSeconds: item.timestamp as number, + data: item, + })); + }, +}; + +export const newEmailSubscriptionsTimelineTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-email-subscriptions-timeline', + displayName: 'New Email Subscriptions Timeline', + description: 'Triggers when a new email timeline subscription added for the portal.', + type: TriggerStrategy.POLLING, + props: {}, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + timestamp: 1401975207000, + portalId: 62515, + recipient: '6d4b537e-c5ac-11e3-a673-00262df65d03@some.email.com', + changes: [ + { + change: 'BOUNCED', + source: 'SOURCE_NON_DELIVERY_REPORT', + portalId: 62515, + changeType: 'PORTAL_BOUNCE', + causedByEvent: { + id: '6d72d39c-87da-3ced-bfdf-5f0213363827', + created: 1401975207000, + }, + }, + ], + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-blog-article.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-blog-article.ts new file mode 100644 index 0000000..e89f787 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-blog-article.ts @@ -0,0 +1,133 @@ +import { + AuthenticationType, + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, + QueryParams, +} from '@activepieces/pieces-common'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; + +type Props = { + articleState: string; +}; + +type ListBlogPostsResponse = { + results: Array>; + paging?: { + next?: { + after: string; + }; + }; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const articleState = propsValue.articleState; + const isTestMode = lastFetchEpochMS === 0; + + const qs: QueryParams = { limit: '100', sort: '-createdAt' }; + if (articleState !== 'BOTH') { + qs.state = articleState; + } + if (!isTestMode) { + if(articleState === 'PUBLISHED') { + qs['publishDate__gt']=lastFetchEpochMS.toString(); + } + else + { + qs['createdAt__gt']=lastFetchEpochMS.toString(); + } + } + + const items = []; + + let after; + + do { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.hubapi.com/cms/v3/blogs/posts', + queryParams: qs, + authentication: { type: AuthenticationType.BEARER_TOKEN, token: auth.access_token }, + }); + + after = response.body.paging?.next?.after; + if(response.body.paging?.next?.after){ + qs.after = response.body.paging?.next?.after; + } + items.push(...response.body.results); + if (isTestMode) { + break; + } + } while (after); + + return items.map((item) => { + return { + epochMilliSeconds: articleState === 'PUBLISHED' ? dayjs(item.publishDate).valueOf() : dayjs(item.createdAt).valueOf(), + data: item, + } + }); + }, +}; + +export const newBlogArticleTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-blog-article', + displayName: 'New COS Blog Article', + description: 'Triggers when a new article is added to your COS blog.', + type: TriggerStrategy.POLLING, + props: { + articleState: Property.StaticDropdown({ + displayName: 'Article State', + required: true, + options: { + disabled: false, + options: [ + { + label: 'Published Only', + value: 'PUBLISHED', + }, + { + label: 'Draft Only', + value: 'DRAFT', + }, + { + label: 'Both', + value: 'BOTH', + }, + ], + }, + }), + }, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: {}, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-company-property-change.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-company-property-change.ts new file mode 100644 index 0000000..3f2f4e9 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-company-property-change.ts @@ -0,0 +1,154 @@ +import { hubspotAuth } from '../..'; +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { Client } from '@hubspot/api-client'; +import dayjs from 'dayjs'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + propertyName?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const propertyToCheck = propsValue.propertyName as string; + + const propertiesToRetrieve = [propertyToCheck]; + + const items = []; + // For test, we only fetch 10 comapnies + if (lastFetchEpochMS === 0) { + const response = await client.crm.companies.searchApi.doSearch({ + limit: 10, + properties: propertiesToRetrieve, + sorts: ['-hs_lastmodifieddate'], + }); + items.push(...response.results); + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + } + //fetch updated companies + const updatedCompanies = []; + let after; + do { + const response = await client.crm.companies.searchApi.doSearch({ + limit: 100, + after, + sorts: ['-hs_lastmodifieddate'], + filterGroups: [ + { + filters: [ + { + propertyName: propertyToCheck, + operator: FilterOperatorEnum.HasProperty, + }, + { + propertyName: 'hs_lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + updatedCompanies.push(...response.results); + } while (after); + + if (updatedCompanies.length === 0) { + return []; + } + + // Fetch companies with property history + const updatedCompaniesWithPropertyHistory = await client.crm.companies.batchApi.read({ + propertiesWithHistory: [propertyToCheck], + properties: propertiesToRetrieve, + inputs: updatedCompanies.map((company) => { + return { + id: company.id, + }; + }), + }); + + for (const company of updatedCompaniesWithPropertyHistory.results) { + const history = company.propertiesWithHistory?.[propertyToCheck]; + if (!history || history.length === 0) { + continue; + } + const propertyLastModifiedDateTimeStamp = dayjs(history[0].timestamp).valueOf(); + if (propertyLastModifiedDateTimeStamp > lastFetchEpochMS) { + const { propertiesWithHistory, ...item } = company; + items.push(item); + } + } + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newCompanyPropertyChangeTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-company-property-change', + displayName: 'New Company Property Change', + description: 'Triggers when a specified property is updated on a company.', + props: { + propertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.COMPANY, + displayName: 'Property Name', + required: true, + }, + true, + true, + ), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + id: '27656515180', + properties: { + createdate: '2024-12-26T08:36:10.463Z', + domain: 'www.activepieces.com', + hs_lastmodifieddate: '2024-12-26T08:58:48.657Z', + hs_object_id: '27656515180', + name: 'Activepieces', + }, + createdAt: '2024-12-26T08:36:10.463Z', + updatedAt: '2024-12-26T08:58:48.657Z', + archived: false, + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-company.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-company.ts new file mode 100644 index 0000000..91dcb62 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-company.ts @@ -0,0 +1,117 @@ +import { PiecePropValueSchema, Property, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import dayjs from 'dayjs'; +import { MarkdownVariant } from '@activepieces/shared'; +import { OBJECT_TYPE } from '../common/constants'; +import { hubspotAuth } from '../..'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + additionalPropertiesToRetrieve?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultCompanyProperties = getDefaultPropertiesForObject(OBJECT_TYPE.COMPANY); + const propertiesToRetrieve = [...defaultCompanyProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.companies.searchApi.doSearch({ + limit: isTest ? 10 : 100, + properties: propertiesToRetrieve, + sorts: ['-createdate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'createdate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['createdate']).valueOf(), + data: item, + })); + }, +}; +export const newCompanyTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-company', + displayName: 'New Company', + description: 'Trigger when a new company is added.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, domain, industry, about_us, phone, address, address2, city, state, zip, country, website, type, description, founded_year, hs_createdate, hs_lastmodifieddate, hs_object_id, is_public, timezone, total_money_raised, total_revenue, owneremail, ownername, numberofemployees, annualrevenue, lifecyclestage, createdate, web_technologies + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + + sampleData: { + id: '123123123', + archived: false, + createdAt: '2023-07-03T14:48:13.839Z', + updatedAt: '2023-07-03T14:48:14.769Z', + properties: { + name: 'Company Name', + domain: 'company.com', + createdate: '2023-07-03T14:48:13.839Z', + hs_object_id: '123123123', + hs_lastmodifieddate: '2023-07-03T14:48:14.769Z', + }, + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-contact-in-list.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-contact-in-list.ts new file mode 100644 index 0000000..016e196 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-contact-in-list.ts @@ -0,0 +1,174 @@ +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + DropdownOption, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; +import { MarkdownVariant } from '@activepieces/shared'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import dayjs from 'dayjs'; + +type Props = { + listId: string; + additionalPropertiesToRetrieve?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const listId = propsValue.listId; + + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultContactProperties = getDefaultPropertiesForObject(OBJECT_TYPE.CONTACT); + const propertiesToRetrieve = [...defaultContactProperties, ...additionalProperties]; + + const client = new Client({ accessToken: auth.access_token }); + const isTestMode = lastFetchEpochMS === 0; + + let listMembers = []; + let after; + + // Fetch members from the list + do { + const response = await client.crm.lists.membershipsApi.getPageOrderedByAddedToListDate( + listId, + after, + undefined, + isTestMode ? 10 : 100, + ); + after = response.paging?.next?.after; + listMembers.push(...response.results); + if (isTestMode) { + break; + } + } while (after); + + if (!isTestMode) { + listMembers = listMembers.filter( + (member) => dayjs(member.membershipTimestamp).valueOf() > lastFetchEpochMS, + ); + } + + // Fetch detailed contact properties + const contactDetailsResponse = await client.crm.contacts.batchApi.read({ + inputs: listMembers.map((member) => ({ id: member.recordId })), + properties: propertiesToRetrieve, + propertiesWithHistory: [], + }); + + // Merge `membershipTimestamp` with contact properties + const enrichedMembers = contactDetailsResponse.results.map((contact) => { + const correspondingMember = listMembers.find((member) => member.recordId === contact.id); + return { + ...contact, + membershipTimestamp: correspondingMember?.membershipTimestamp, + }; + }); + + return enrichedMembers.map((member) => ({ + epochMilliSeconds: dayjs(member.membershipTimestamp).valueOf(), + data: member, + })); + }, +}; + +export const newContactInListTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-contact-in-list', + displayName: 'New Contact in List', + description: 'Triggers when a new contact is added to the specified list.', + type: TriggerStrategy.POLLING, + props: { + listId: Property.Dropdown({ + displayName: 'Contact List', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account.', + }; + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + let offset = 0; + let hasMore = true; + const options: DropdownOption[] = []; + do { + const response = await client.crm.lists.listsApi.doSearch({ + count: 100, + offset: offset, + }); + for (const list of response.lists) { + options.push({ + label: list.name, + value: list.listId, + }); + } + hasMore = response.hasMore; + offset += 100; + } while (hasMore); + + return { + disabled: false, + options, + }; + }, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + firstname, lastname, email, company, website, mobilephone, phone, fax, address, city, state, zip, salutation, country, jobtitle, hs_createdate, hs_email_domain, hs_object_id, lastmodifieddate, hs_persona, hs_language, lifecyclestage, createdate, numemployees, annualrevenue, industry + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + id: '123', + archived: false, + createdAt: '2023-06-13T10:24:42.392Z', + updatedAt: '2023-06-30T06:16:51.869Z', + properties: { + email: 'contact@email.com', + lastname: 'Last', + firstname: 'First', + createdate: '2023-06-13T10:24:42.392Z', + hs_object_id: '123', + lastmodifieddate: '2023-06-30T06:16:51.869Z', + }, + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-contact-property-change.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-contact-property-change.ts new file mode 100644 index 0000000..8054944 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-contact-property-change.ts @@ -0,0 +1,176 @@ +import { hubspotAuth } from '../..'; +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { Client } from '@hubspot/api-client'; +import dayjs from 'dayjs'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + propertyName?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const propertyToCheck = propsValue.propertyName as string; + + // Extract properties once to avoid recomputation + const propertiesToRetrieve = [propertyToCheck]; + + const items = []; + // For test, we only fetch 10 contacts + if (lastFetchEpochMS === 0) { + const response = await client.crm.contacts.searchApi.doSearch({ + limit: 10, + properties: propertiesToRetrieve, + sorts: ['-lastmodifieddate'], + }); + items.push(...response.results); + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['lastmodifieddate']).valueOf(), + data: item, + })); + } + //fetch updated contacts + const updatedContacts = []; + let after; + do { + const response = await client.crm.contacts.searchApi.doSearch({ + limit: 100, + after, + sorts: ['-lastmodifieddate'], + filterGroups: [ + { + filters: [ + { + propertyName: propertyToCheck, + operator: FilterOperatorEnum.HasProperty, + }, + { + propertyName: 'lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + updatedContacts.push(...response.results); + } while (after); + + if (updatedContacts.length === 0) { + return []; + } + + // Fetch contacts with property history + const updatedContcatsWithPropertyHistory = await client.crm.contacts.batchApi.read({ + propertiesWithHistory: [propertyToCheck], + properties: propertiesToRetrieve, + inputs: updatedContacts.map((contact) => { + return { + id: contact.id, + }; + }), + }); + + for (const contact of updatedContcatsWithPropertyHistory.results) { + const history = contact.propertiesWithHistory?.[propertyToCheck]; + if (!history || history.length === 0) { + continue; + } + const propertyLastModifiedDateTimeStamp = dayjs(history[0].timestamp).valueOf(); + if (propertyLastModifiedDateTimeStamp > lastFetchEpochMS) { + const { propertiesWithHistory, ...item } = contact; + items.push(item); + } + } + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newContactPropertyChangeTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-contact-property-change', + displayName: 'New Contact Property Change', + description: 'Triggers when a specified property is updated on a contact.', + props: { + propertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Property Name', + required: true, + }, + true, + true, + ), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-06T10:52:58.322Z', + archived: false, + id: '82665997707', + properties: { + address: null, + annualrevenue: null, + city: 'Brisbane', + company: 'HubSpot', + country: null, + createdate: '2024-12-06T10:52:58.322Z', + email: 'emailmaria@hubspot.com', + fax: null, + firstname: 'Maria', + hs_createdate: null, + hs_email_domain: 'hubspot.com', + hs_language: null, + hs_object_id: '82665997707', + hs_persona: null, + industry: null, + jobtitle: 'Salesperson', + lastmodifieddate: '2024-12-20T12:50:35.201Z', + lastname: 'Johnson (Sample Contact)', + lifecyclestage: 'lead', + mobilephone: null, + numemployees: null, + phone: null, + salutation: null, + state: null, + website: 'http://www.HubSpot.com', + zip: null, + }, + updatedAt: '2024-12-20T12:50:35.201Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-contact.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-contact.ts new file mode 100644 index 0000000..943204f --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-contact.ts @@ -0,0 +1,140 @@ +import { PiecePropValueSchema, Property, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import dayjs from 'dayjs'; +import { MarkdownVariant } from '@activepieces/shared'; +import { OBJECT_TYPE } from '../common/constants'; +import { hubspotAuth } from '../..'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + additionalPropertiesToRetrieve?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultContactProperties = getDefaultPropertiesForObject(OBJECT_TYPE.CONTACT); + const propertiesToRetrieve = [...defaultContactProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.contacts.searchApi.doSearch({ + limit: isTest ? 10 : 100, + properties: propertiesToRetrieve, + sorts: ['-createdate'], + after, + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'createdate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['createdate']).valueOf(), + data: item, + })); + }, +}; + +export const newContactTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-contact', + displayName: 'New Contact', + description: 'Trigger when new contact is available.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + firstname, lastname, email, company, website, mobilephone, phone, fax, address, city, state, zip, salutation, country, jobtitle, hs_createdate, hs_email_domain, hs_object_id, lastmodifieddate, hs_persona, hs_language, lifecyclestage, createdate, numemployees, annualrevenue, industry + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + + sampleData: { + createdAt: '2024-12-06T10:52:58.322Z', + archived: false, + id: '82665997707', + properties: { + address: null, + annualrevenue: null, + city: 'Brisbane', + company: 'HubSpot', + country: null, + createdate: '2024-12-06T10:52:58.322Z', + email: 'emailmaria@hubspot.com', + fax: null, + firstname: 'Maria', + hs_createdate: null, + hs_email_domain: 'hubspot.com', + hs_language: null, + hs_object_id: '82665997707', + hs_persona: null, + industry: null, + jobtitle: 'Salesperson', + lastmodifieddate: '2024-12-20T12:50:35.201Z', + lastname: 'Johnson (Sample Contact)', + lifecyclestage: 'lead', + mobilephone: null, + numemployees: null, + phone: null, + salutation: null, + state: null, + website: 'http://www.HubSpot.com', + zip: null, + }, + updatedAt: '2024-12-20T12:50:35.201Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-custom-object-property-change.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-custom-object-property-change.ts new file mode 100644 index 0000000..edff016 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-custom-object-property-change.ts @@ -0,0 +1,155 @@ +import { hubspotAuth } from '../..'; +import { + createTrigger, + DynamicPropsValue, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { + customObjectDropdown, + customObjectPropertiesDropdown, + standardObjectPropertiesDropdown, +} from '../common/props'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { Client } from '@hubspot/api-client'; +import dayjs from 'dayjs'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + customObjectType?: string; + propertyName?: DynamicPropsValue; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const customObjectType = propsValue.customObjectType as string; + const propertyToCheck = propsValue.propertyName?.['values'] as string; + + const propertiesToRetrieve = [propertyToCheck]; + + const items = []; + // For test, we only fetch 10 custom objects + if (lastFetchEpochMS === 0) { + const response = await client.crm.objects.searchApi.doSearch(customObjectType, { + limit: 10, + properties: propertiesToRetrieve, + sorts: ['-hs_lastmodifieddate'], + }); + items.push(...response.results); + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + } + //fetch updated custom objects + const updatedCustomObjects = []; + let after; + do { + const response = await client.crm.objects.searchApi.doSearch(customObjectType, { + limit: 100, + after, + sorts: ['-hs_lastmodifieddate'], + filterGroups: [ + { + filters: [ + { + propertyName: propertyToCheck, + operator: FilterOperatorEnum.HasProperty, + }, + { + propertyName: 'hs_lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + updatedCustomObjects.push(...response.results); + } while (after); + + if (updatedCustomObjects.length === 0) { + return []; + } + + // Fetch custom objects with property history + const updatedCustomObjectsWithPropertyHistory = await client.crm.objects.batchApi.read( + customObjectType, + { + propertiesWithHistory: [propertyToCheck], + properties: propertiesToRetrieve, + inputs: updatedCustomObjects.map((customObject) => { + return { + id: customObject.id, + }; + }), + }, + ); + + for (const customObject of updatedCustomObjectsWithPropertyHistory.results) { + const history = customObject.propertiesWithHistory?.[propertyToCheck]; + if (!history || history.length === 0) { + continue; + } + const propertyLastModifiedDateTimeStamp = dayjs(history[0].timestamp).valueOf(); + if (propertyLastModifiedDateTimeStamp > lastFetchEpochMS) { + const { propertiesWithHistory, ...item } = customObject; + items.push(item); + } + } + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newCustomObjectPropertyChangeTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-custom-object-property-change', + displayName: 'New Custom Object Property Change', + description: 'Triggers when a specified property is updated on a custom object.', + props: { + customObjectType: customObjectDropdown, + propertyName: customObjectPropertiesDropdown('Property Name', true, true), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-22T15:20:16.121Z', + archived: false, + id: '21583829313', + properties: { + hs_createdate: '2024-12-22T15:20:16.121Z', + hs_lastmodifieddate: '2024-12-22T15:20:16.818Z', + hs_object_id: '21583829313', + pet_name: 'Oreo', + }, + updatedAt: '2024-12-22T15:20:16.818Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-custom-object.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-custom-object.ts new file mode 100644 index 0000000..f2dab8b --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-custom-object.ts @@ -0,0 +1,132 @@ +import { hubspotAuth } from '../../'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { + createTrigger, + DynamicPropsValue, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { MarkdownVariant } from '@activepieces/shared'; +import { customObjectDropdown, customObjectPropertiesDropdown } from '../common/props'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; +import dayjs from 'dayjs'; + +type Props = { + customObjectType?: string; + additionalPropertiesToRetrieve?: DynamicPropsValue; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const customObjectType = propsValue.customObjectType as string; + const additionalPropertiesToRetrieve = propsValue.additionalPropertiesToRetrieve?.['values']; + + let propertiesToRetrieve; + try { + if (Array.isArray(additionalPropertiesToRetrieve)) { + propertiesToRetrieve = additionalPropertiesToRetrieve; + } + if (typeof additionalPropertiesToRetrieve === 'string') { + propertiesToRetrieve = JSON.parse(additionalPropertiesToRetrieve as string); + } + } catch (error) { + propertiesToRetrieve = []; + } + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.objects.searchApi.doSearch(customObjectType, { + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-hs_createdate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'hs_createdate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_createdate']).valueOf(), + data: item, + })); + }, +}; + +export const newCustomObjectTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-custom-object', + displayName: 'New Custom Object', + description: 'Triggers when new custom object is available.', + props: { + customObjectType: customObjectDropdown, + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + hs_object_id, hs_lastmodifieddate, hs_createdate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: customObjectPropertiesDropdown( + 'Additional Properties to Retrieve', + false, + ), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-22T15:20:16.121Z', + archived: false, + id: '21583829313', + properties: { + hs_createdate: '2024-12-22T15:20:16.121Z', + hs_lastmodifieddate: '2024-12-22T15:20:16.818Z', + hs_object_id: '21583829313', + pet_name: 'Oreo', + }, + updatedAt: '2024-12-22T15:20:16.818Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-deal-property-change.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-deal-property-change.ts new file mode 100644 index 0000000..e0213c8 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-deal-property-change.ts @@ -0,0 +1,153 @@ +import { hubspotAuth } from '../..'; +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { Client } from '@hubspot/api-client'; +import dayjs from 'dayjs'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + propertyName?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const propertyToCheck = propsValue.propertyName as string; + + const propertiesToRetrieve = [propertyToCheck]; + + const items = []; + // For test, we only fetch 10 deals + if (lastFetchEpochMS === 0) { + const response = await client.crm.deals.searchApi.doSearch({ + limit: 10, + properties: propertiesToRetrieve, + sorts: ['-hs_lastmodifieddate'], + }); + items.push(...response.results); + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + } + //fetch updated deals + const updatedDeals = []; + let after; + do { + const response = await client.crm.deals.searchApi.doSearch({ + limit: 100, + after, + sorts: ['-hs_lastmodifieddate'], + filterGroups: [ + { + filters: [ + { + propertyName: propertyToCheck, + operator: FilterOperatorEnum.HasProperty, + }, + { + propertyName: 'hs_lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + updatedDeals.push(...response.results); + } while (after); + + if (updatedDeals.length === 0) { + return []; + } + + // Fetch deals with property history + const updatedDealsWithPropertyHistory = await client.crm.deals.batchApi.read({ + propertiesWithHistory: [propertyToCheck], + properties: propertiesToRetrieve, + inputs: updatedDeals.map((deal) => { + return { + id: deal.id, + }; + }), + }); + + for (const deal of updatedDealsWithPropertyHistory.results) { + const history = deal.propertiesWithHistory?.[propertyToCheck]; + if (!history || history.length === 0) { + continue; + } + const propertyLastModifiedDateTimeStamp = dayjs(history[0].timestamp).valueOf(); + if (propertyLastModifiedDateTimeStamp > lastFetchEpochMS) { + const { propertiesWithHistory, ...item } = deal; + items.push(item); + } + } + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newDealPropertyChangeTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-deal-property-change', + displayName: 'New Deal Property Change', + description: 'Triggers when a specified property is updated on a deal.', + props: { + propertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.DEAL, + displayName: 'Property Name', + required: true, + }, + true, + true, + ), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-23T08:19:21.614Z', + archived: false, + id: '30906615140', + properties: { + amount: '150', + createdate: '2024-12-23T08:19:21.614Z', + hs_lastmodifieddate: '2024-12-26T09:30:44.578Z', + hs_object_id: '30906615140', + }, + updatedAt: '2024-12-26T09:30:44.578Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-deal.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-deal.ts new file mode 100644 index 0000000..4d18a36 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-deal.ts @@ -0,0 +1,129 @@ +import { PiecePropValueSchema, Property, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import dayjs from 'dayjs'; +import { hubspotAuth } from '../..'; +import { MarkdownVariant } from '@activepieces/shared'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + additionalPropertiesToRetrieve?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultProductProperties = getDefaultPropertiesForObject(OBJECT_TYPE.DEAL); + const propertiesToRetrieve = [...defaultProductProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.deals.searchApi.doSearch({ + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-createdate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'createdate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['createdate']).valueOf(), + data: item, + })); + }, +}; + +export const newDealTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-deal', + displayName: 'New Deal', + description: 'Trigger when a new deal is added.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + dealtype, dealname, amount, description, closedate, createdate, num_associated_contacts, hs_forecast_amount, hs_forecast_probability, hs_manual_forecast_category, hs_next_step, hs_object_id, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + + sampleData: { + createdAt: '2024-12-23T08:19:21.614Z', + archived: false, + id: '30906615140', + properties: { + amount: '150', + closedate: null, + createdate: '2024-12-23T08:19:21.614Z', + dealname: 'test deal', + dealtype: 'newbusiness', + description: 'test', + hs_forecast_amount: '150.0', + hs_forecast_probability: null, + hs_lastmodifieddate: '2024-12-26T10:31:45.624Z', + hs_manual_forecast_category: null, + hs_next_step: null, + hs_object_id: '30906615140', + hubspot_owner_id: '64914635', + hubspot_team_id: '55094099', + num_associated_contacts: '1', + }, + updatedAt: '2024-12-26T10:31:45.624Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-email-event.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-email-event.ts new file mode 100644 index 0000000..9a718bb --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-email-event.ts @@ -0,0 +1,147 @@ +import { + AuthenticationType, + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, + QueryParams, +} from '@activepieces/pieces-common'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; + +type Props = { + eventType?: string; +}; + +type EmailEventResponse = { + events: Array>; + hasMore: boolean; + offset: string; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const eventType = propsValue.eventType; + const isTestMode = lastFetchEpochMS === 0; + + let hasMore = true; + const qs: QueryParams = { limit: '100' }; + if (eventType) { + qs.eventType = eventType; + } + if (lastFetchEpochMS) { + qs.startTimestamp = lastFetchEpochMS.toString(); + } + const emailEvents = []; + + do { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.hubapi.com/email/public/v1/events', + queryParams: qs, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }); + hasMore = response.body.hasMore; + qs.offset = response.body.offset; + for (const event of response.body.events) { + emailEvents.push(event); + } + if (isTestMode) break; + } while (hasMore); + + return emailEvents.map((item) => ({ + epochMilliSeconds: item['created'] as number, + data: item, + })); + }, +}; + +export const newEmailEventTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-email-event', + displayName: 'New Email Event', + description: 'Triggers when all,or specific new email event is available.', + type: TriggerStrategy.POLLING, + props: { + eventType: Property.StaticDropdown({ + displayName: 'Event Type', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Sent', + value: 'SENT', + }, + { + label: 'Dropped', + value: 'DROPPED', + }, + { + label: 'Processed', + value: 'PROCESSED', + }, + { + label: 'Delivered', + value: 'DELIVERED', + }, + { + label: 'Deferred', + value: 'DEFERRED', + }, + { + label: 'Bounce', + value: 'BOUNCE', + }, + { + label: 'Open', + value: 'OPEN', + }, + { + label: 'Click', + value: 'CLICK', + }, + { + label: 'Status Change', + value: 'STATUSCHANGE', + }, + { + label: 'Spam Report', + value: 'SPAMREPORT', + }, + ], + }, + }), + }, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: {}, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-engagement.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-engagement.ts new file mode 100644 index 0000000..1e73b2d --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-engagement.ts @@ -0,0 +1,146 @@ +import { + AuthenticationType, + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, + QueryParams, +} from '@activepieces/pieces-common'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; + +type Props = { + eventType?: string; +}; + +type EngagementResponse = { + results: Array>; + hasMore: boolean; + offset: string; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const eventType = propsValue.eventType; + const engagements = []; + + let hasMore = true; + const qs: QueryParams = { limit: '100' }; + do { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.hubapi.com/engagements/v1/engagements/paged', + queryParams: qs, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }); + hasMore = response.body.hasMore; + qs.offset = response.body.offset; + for (const engagement of response.body.results) { + engagements.push(engagement); + } + } while (hasMore); + + const filteredEngagements = eventType + ? engagements.filter((engagement) => engagement.engagement.type === eventType) + : engagements; + + return filteredEngagements.map((item) => ({ + epochMilliSeconds: item.engagement.createdAt as number, + data: item, + })); + }, +}; + +export const newEngagementTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-engagement', + displayName: 'New Engagement', + description: 'Triggers when a new engagement is created.', + type: TriggerStrategy.POLLING, + props: { + eventType: Property.StaticDropdown({ + displayName: 'Type', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Note', + value: 'NOTE', + }, + { + label: 'Task', + value: 'TASK', + }, + { + label: 'Meeting', + value: 'MEETING', + }, + { + label: 'Email', + value: 'EMAIL', + }, + { + label: 'Call', + value: 'CALL', + }, + ], + }, + }), + }, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + engagement: { + id: 29090716, + portalId: 62515, + active: true, + createdAt: 1444223400781, + lastUpdated: 1444223400781, + createdBy: 215482, + modifiedBy: 215482, + ownerId: 70, + type: 'NOTE', + timestamp: 1444223400781, + }, + associations: { + contactIds: [247], + companyIds: [], + dealIds: [], + ownerIds: [], + workflowIds: [], + }, + attachments: [], + metadata: { + body: 'This is a test note ', + }, + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-form-submission.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-form-submission.ts new file mode 100644 index 0000000..d6baf93 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-form-submission.ts @@ -0,0 +1,155 @@ +import { hubspotAuth } from '../../'; +import { + AuthenticationType, + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, + QueryParams, +} from '@activepieces/pieces-common'; +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { formDropdown } from '../common/props'; + +type Props = { + formId: string; +}; + +type FormSubmissionResponse = { + results: Array<{ + conversionId: string; + submittedAt: number; + pageUrl: string; + values: Array<{ name: string; value: string }>; + }>; + paging?: { + next?: { + after: string; + }; + }; +}; + +type FormField = { + name: string; + label: string; + fieldType: string; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const authValue = auth as PiecePropValueSchema; + const formId = propsValue.formId; + + const submissions = []; + let after; + do { + const qs: QueryParams = { limit: '50' }; + if (after) { + qs.after = after; + } + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.hubapi.com/form-integrations/v1/submissions/forms/${formId}`, + queryParams: qs, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authValue.access_token, + }, + }); + after = response.body.paging?.next?.after; + submissions.push(...response.body.results); + } while (after); + + const formFields = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.hubapi.com/forms/v2/fields/${formId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authValue.access_token, + }, + }); + + const fieldMapping = formFields.body.reduce((acc, field) => { + acc[field.name] = { label: field.label, fieldType: field.fieldType }; + return acc; + }, {} as Record); + + const items = []; + + for (const submission of submissions) { + const formattedValues: Record = {}; + + const submissionData = submission.values ?? []; + for (const fieldValue of submissionData) { + const field = fieldMapping[fieldValue.name]; + + if (field) { + const { label, fieldType } = field; + if (fieldType === 'checkbox') { + formattedValues[label] = formattedValues[label] || []; + formattedValues[label].push(fieldValue.value); + } else { + formattedValues[label] = fieldValue.value; + } + } else { + formattedValues[fieldValue.name] = fieldValue.value; + } + } + items.push({ + ...submission, + values: formattedValues, + }); + } + + return items.map((item) => ({ + epochMilliSeconds: item.submittedAt, + data: item, + })); + }, +}; + +export const newFormSubmissionTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-form-submission', + displayName: 'New Form Submission', + description: 'Triggers when a form is submitted.', + type: TriggerStrategy.POLLING, + props: { + formId: formDropdown, + }, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + conversionId: '82800398-30af-48a0-942a-1d0623fce08c', + submittedAt: 1735216921730, + values: { + "First Name": "John", + "Last Name": "Doe", + "Email": "john.doe@example.com", + }, + pageUrl: 'https://share.hsforms.com/1VXAvM044Tcyaa3Y5XRQFSQsuf7d', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-line-item.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-line-item.ts new file mode 100644 index 0000000..9d2da15 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-line-item.ts @@ -0,0 +1,138 @@ +import { hubspotAuth } from '../../'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { MarkdownVariant } from '@activepieces/shared'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; +import dayjs from 'dayjs'; + +type Props = { + additionalPropertiesToRetrieve?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + // Extract properties once to avoid recomputation + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultLineItemProperties = getDefaultPropertiesForObject(OBJECT_TYPE.LINE_ITEM); + const propertiesToRetrieve = [...defaultLineItemProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.lineItems.searchApi.doSearch({ + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-createdate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'createdate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['createdate']).valueOf(), + data: item, + })); + }, +}; + +export const newLineItemTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-line-item', + displayName: 'New Line Item', + description: 'Triggers when new line item is available.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, description, price, quantity, amount, discount, tax, createdate, hs_object_id, hs_product_id, hs_images, hs_lastmodifieddate, hs_line_item_currency_code, hs_sku, hs_url, hs_cost_of_goods_sold, hs_discount_percentage, hs_term_in_months + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.LINE_ITEM, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-25T11:08:54.763Z', + archived: false, + id: '26882583648', + properties: { + amount: '5.00', + createdate: '2024-12-25T11:08:54.763Z', + description: 'CHAIR', + discount: '10', + hs_cost_of_goods_sold: '10', + hs_discount_percentage: null, + hs_images: null, + hs_lastmodifieddate: '2024-12-25T11:10:02.750Z', + hs_line_item_currency_code: null, + hs_object_id: '26882583648', + hs_product_id: '17602013482', + hs_sku: 'fb-100', + hs_tax_amount: null, + hs_tcv: '5.00', + hs_term_in_months: null, + hs_total_discount: '10.00', + hs_url: null, + name: 'Chair', + price: '15.0', + quantity: null, + tax: null, + }, + updatedAt: '2024-12-25T11:10:02.750Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-company.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-company.ts new file mode 100644 index 0000000..1e58f79 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-company.ts @@ -0,0 +1,145 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; +import dayjs from 'dayjs'; + +const polling: Polling< + PiecePropValueSchema, + { additionalPropertiesToRetrieve?: string[] | string } +> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + // Extract properties once to avoid recomputation + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultCompanyProperties = getDefaultPropertiesForObject(OBJECT_TYPE.COMPANY); + const propertiesToRetrieve = [...defaultCompanyProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.companies.searchApi.doSearch({ + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-hs_lastmodifieddate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'hs_lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newOrUpdatedCompanyTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-or-updated-company', + displayName: 'Company Recently Created or Updated', + description: 'Triggers when a company recently created or updated.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, domain, industry, about_us, phone, address, address2, city, state, zip, country, website, type, description, founded_year, hs_createdate, hs_lastmodifieddate, hs_object_id, is_public, timezone, total_money_raised, total_revenue, owneremail, ownername, numberofemployees, annualrevenue, lifecyclestage, createdate, web_technologies + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-20T10:08:20.243Z', + archived: false, + id: '27508860336', + properties: { + about_us: 'Automation', + address: null, + address2: null, + annualrevenue: '500000', + city: null, + country: null, + createdate: '2024-12-20T10:08:20.243Z', + description: 'Automation', + domain: 'www.activepieces.com', + founded_year: null, + hs_createdate: null, + hs_lastmodifieddate: '2024-12-25T10:04:47.382Z', + hs_object_id: '27508860336', + industry: 'COMPUTER_SOFTWARE', + is_public: null, + lifecyclestage: 'lead', + name: 'Activepieces', + numberofemployees: '6', + owneremail: null, + ownername: null, + phone: null, + state: null, + timezone: null, + total_money_raised: null, + total_revenue: null, + type: 'OTHER', + web_technologies: null, + website: 'www.activepieces.com', + zip: null, + }, + updatedAt: '2024-12-25T10:04:47.382Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-contact.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-contact.ts new file mode 100644 index 0000000..5aa18d1 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-contact.ts @@ -0,0 +1,142 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; +import dayjs from 'dayjs'; + +const polling: Polling< + PiecePropValueSchema, + { additionalPropertiesToRetrieve?: string[] | string } +> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + // Extract properties once to avoid recomputation + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultContactProperties = getDefaultPropertiesForObject(OBJECT_TYPE.CONTACT); + const propertiesToRetrieve = [...defaultContactProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.contacts.searchApi.doSearch({ + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-lastmodifieddate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newOrUpdatedContactTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-or-updated-contact', + displayName: 'Contact Recently Created or Updated', + description: 'Triggers when a contact recently created or updated.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + firstname, lastname, email, company, website, mobilephone, phone, fax, address, city, state, zip, salutation, country, jobtitle, hs_createdate, hs_email_domain, hs_object_id, lastmodifieddate, hs_persona, hs_language, lifecyclestage, createdate, numemployees, annualrevenue, industry + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-06T10:52:58.322Z', + archived: false, + id: '82665997707', + properties: { + address: null, + annualrevenue: null, + city: 'Brisbane', + company: 'HubSpot', + country: null, + createdate: '2024-12-06T10:52:58.322Z', + email: 'emailmaria@hubspot.com', + fax: null, + firstname: 'Maria', + hs_createdate: null, + hs_email_domain: 'hubspot.com', + hs_language: null, + hs_object_id: '82665997707', + hs_persona: null, + industry: null, + jobtitle: 'Salesperson', + lastmodifieddate: '2024-12-20T12:50:35.201Z', + lastname: 'Johnson (Sample Contact)', + lifecyclestage: 'lead', + mobilephone: null, + numemployees: null, + phone: null, + salutation: null, + state: null, + website: 'http://www.HubSpot.com', + zip: null, + }, + updatedAt: '2024-12-20T12:50:35.201Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-line-item.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-line-item.ts new file mode 100644 index 0000000..f8e6fb0 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-line-item.ts @@ -0,0 +1,134 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; +import dayjs from 'dayjs'; + +const polling: Polling< + PiecePropValueSchema, + { additionalPropertiesToRetrieve?: string[] | string } +> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + // Extract properties once to avoid recomputation + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultLineItemProperties = getDefaultPropertiesForObject(OBJECT_TYPE.LINE_ITEM); + const propertiesToRetrieve = [...defaultLineItemProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.lineItems.searchApi.doSearch({ + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-hs_lastmodifieddate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'hs_lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newOrUpdatedLineItemTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-or-updated-line-item', + displayName: 'Line Item Recently Created or Updated', + description: 'Triggers when a line item recently created or updated.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, description, price, quantity, amount, discount, tax, createdate, hs_object_id, hs_product_id, hs_images, hs_lastmodifieddate, hs_line_item_currency_code, hs_sku, hs_url, hs_cost_of_goods_sold, hs_discount_percentage, hs_term_in_months + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.LINE_ITEM, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-24T16:17:34.281Z', + archived: false, + id: '26854130802', + properties: { + amount: '15.00', + createdate: '2024-12-24T16:17:34.281Z', + description: 'Chair', + discount: null, + hs_cost_of_goods_sold: '10', + hs_discount_percentage: null, + hs_images: null, + hs_lastmodifieddate: '2024-12-24T16:17:50.488Z', + hs_line_item_currency_code: 'USD', + hs_object_id: '26854130802', + hs_product_id: '17602013482', + hs_sku: 'ch-100', + hs_term_in_months: null, + hs_url: null, + name: 'Chair', + price: '15.0', + quantity: '1', + tax: null, + }, + updatedAt: '2024-12-24T16:17:50.488Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-product.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-product.ts new file mode 100644 index 0000000..8d5e253 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-product.ts @@ -0,0 +1,123 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; +import dayjs from 'dayjs'; + +const polling: Polling< + PiecePropValueSchema, + { additionalPropertiesToRetrieve?: string[] | string } +> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + // Extract properties once to avoid recomputation + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultProductProperties = getDefaultPropertiesForObject(OBJECT_TYPE.PRODUCT); + const propertiesToRetrieve = [...defaultProductProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.products.searchApi.doSearch({ + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-hs_lastmodifieddate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'hs_lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newOrUpdatedProductTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-or-updated-product', + displayName: 'Product Recently Created or Updated', + description: 'Triggers when a product recently created or updated.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + createdate, description, name, price, tax, hs_lastmodifieddate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.PRODUCT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-18T16:10:27.710Z', + archived: false, + id: '17602013482', + properties: { + createdate: '2024-12-18T16:10:27.710Z', + description: 'Chair', + hs_lastmodifieddate: '2024-12-23T08:13:30.314Z', + hs_object_id: '17602013482', + name: 'Chair', + price: '15.0', + tax: null, + }, + updatedAt: '2024-12-23T08:13:30.314Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-product.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-product.ts new file mode 100644 index 0000000..d23cc25 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-product.ts @@ -0,0 +1,124 @@ +import { hubspotAuth } from '../../'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { MarkdownVariant } from '@activepieces/shared'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; +import dayjs from 'dayjs'; + +type Props = { + additionalPropertiesToRetrieve?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + // Extract properties once to avoid recomputation + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultProductProperties = getDefaultPropertiesForObject(OBJECT_TYPE.PRODUCT); + const propertiesToRetrieve = [...defaultProductProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.products.searchApi.doSearch({ + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-createdate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'createdate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['createdate']).valueOf(), + data: item, + })); + }, +}; + +export const newProductTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-product', + displayName: 'New Product', + description: 'Triggers when new product is available.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + createdate, description, name, price, tax, hs_lastmodifieddate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.PRODUCT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-22T10:43:01.920Z', + archived: false, + id: '17727139749', + properties: { + createdate: '2024-12-22T10:43:01.920Z', + description: null, + hs_lastmodifieddate: '2024-12-23T08:13:30.506Z', + hs_object_id: '17727139749', + name: 'TEST', + price: '20.0', + tax: null, + }, + updatedAt: '2024-12-23T08:13:30.506Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-task.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-task.ts new file mode 100644 index 0000000..fa903ab --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-task.ts @@ -0,0 +1,145 @@ +import { PiecePropValueSchema, Property, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import dayjs from 'dayjs'; +import { hubspotAuth } from '../..'; +import { MarkdownVariant } from '@activepieces/shared'; +import { OBJECT_TYPE } from '../common/constants'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; + +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + additionalPropertiesToRetrieve?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultTaskProperties = getDefaultPropertiesForObject(OBJECT_TYPE.TASK); + const propertiesToRetrieve = [...defaultTaskProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.objects.tasks.searchApi.doSearch({ + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-hs_createdate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'hs_createdate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_createdate']).valueOf(), + data: item, + })); + }, +}; + +export const newTaskTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-task', + displayName: 'New Task', + description: 'Trigger when a new task is added.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + hs_task_subject, hs_task_type, hs_task_priority, hubspot_owner_id, hs_timestamp, hs_queue_membership_ids, hs_lastmodifieddate,hs_createdate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.TASK, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + + sampleData: { + results: [ + { + id: '18156543966', + properties: { + hs_created_by: '5605286', + hs_created_by_user_id: '5605286', + hs_createdate: '2023-06-13T09:42:37.557Z', + hs_modified_by: '5605286', + hs_num_associated_companies: '0', + hs_num_associated_contacts: '1', + hs_num_associated_deals: '0', + hs_num_associated_tickets: '0', + hs_product_name: null, + hs_read_only: null, + hs_repeat_status: null, + hs_task_body: null, + hs_task_completion_count: '0', + hs_task_completion_date: null, + hs_task_is_all_day: 'false', + hs_task_is_completed: '0', + hs_task_is_completed_call: '0', + hs_task_is_completed_email: '0', + hs_task_is_completed_linked_in: '0', + hs_task_is_completed_sequence: '0', + hs_task_repeat_interval: null, + hs_task_status: 'NOT_STARTED', + hs_task_subject: 'My Test Task', + hs_task_type: 'TODO', + hs_updated_by_user_id: '5605286', + hubspot_owner_id: '1041576162', + hs_timestamp: '2023-06-16T05:00:00Z', + }, + createdAt: '2023-06-13T09:42:37.557Z', + updatedAt: '2023-06-13T10:03:41.073Z', + archived: false, + }, + ], + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-ticket-property-change.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-ticket-property-change.ts new file mode 100644 index 0000000..e5677d6 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-ticket-property-change.ts @@ -0,0 +1,163 @@ +import { hubspotAuth } from '../..'; +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { Client } from '@hubspot/api-client'; +import dayjs from 'dayjs'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + propertyName?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const propertyToCheck = propsValue.propertyName as string; + + const propertiesToRetrieve = [propertyToCheck]; + + const items = []; + // For test, we only fetch 10 tickets + if (lastFetchEpochMS === 0) { + const response = await client.crm.tickets.searchApi.doSearch({ + limit: 10, + properties: propertiesToRetrieve, + sorts: ['-hs_lastmodifieddate'], + }); + items.push(...response.results); + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + } + //fetch updated tickets + const updatedTickets = []; + let after; + do { + const response = await client.crm.tickets.searchApi.doSearch({ + limit: 100, + sorts: ['-hs_lastmodifieddate'], + after, + filterGroups: [ + { + filters: [ + { + propertyName: propertyToCheck, + operator: FilterOperatorEnum.HasProperty, + }, + { + propertyName: 'hs_lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + updatedTickets.push(...response.results); + } while (after); + + if (updatedTickets.length === 0) { + return []; + } + + // Fetch tickets with property history + const updatedTicketsWithPropertyHistory = await client.crm.tickets.batchApi.read({ + propertiesWithHistory: [propertyToCheck], + properties: propertiesToRetrieve, + inputs: updatedTickets.map((ticket) => { + return { + id: ticket.id, + }; + }), + }); + + for (const ticket of updatedTicketsWithPropertyHistory.results) { + const history = ticket.propertiesWithHistory?.[propertyToCheck]; + if (!history || history.length === 0) { + continue; + } + const propertyLastModifiedDateTimeStamp = dayjs(history[0].timestamp).valueOf(); + if (propertyLastModifiedDateTimeStamp > lastFetchEpochMS) { + const { propertiesWithHistory, ...item } = ticket; + items.push(item); + } + } + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newTicketPropertyChangeTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-ticket-property-change', + displayName: 'New Ticket Property Change', + description: 'Triggers when a specified property is updated on a ticket.', + props: { + propertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.TICKET, + displayName: 'Property Name', + required: true, + }, + true, + true, + ), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-21T14:23:38.368Z', + archived: false, + id: '18092693102', + properties: { + content: null, + createdate: '2024-12-21T14:23:38.368Z', + hs_lastmodifieddate: '2024-12-26T08:11:34.374Z', + hs_object_id: '18092693102', + hs_pipeline: '0', + hs_pipeline_stage: '1', + hs_resolution: 'ISSUE_FIXED', + hs_ticket_category: null, + hs_ticket_id: '18092693102', + hs_ticket_priority: null, + hubspot_owner_id: null, + hubspot_team_id: null, + source_type: null, + subject: 'NEW', + }, + updatedAt: '2024-12-26T08:11:34.374Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-ticket.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-ticket.ts new file mode 100644 index 0000000..38dcee4 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-ticket.ts @@ -0,0 +1,128 @@ +import { PiecePropValueSchema, Property, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import dayjs from 'dayjs'; +import { hubspotAuth } from '../..'; +import { MarkdownVariant } from '@activepieces/shared'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + additionalPropertiesToRetrieve?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultTicketProperties = getDefaultPropertiesForObject(OBJECT_TYPE.TICKET); + const propertiesToRetrieve = [...defaultTicketProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.tickets.searchApi.doSearch({ + limit: isTest ? 10 : 100, + properties: propertiesToRetrieve, + after, + sorts: ['-createdate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'createdate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['createdate']).valueOf(), + data: item, + })); + }, +}; + +export const newTicketTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-ticket', + displayName: 'New Ticket', + description: 'Trigger when new deal is available.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + subject, content, source_type, createdate, hs_pipeline, hs_pipeline_stage, hs_resolution, hs_ticket_category, hs_ticket_id, hs_ticket_priority, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + + sampleData: { + createdAt: '2024-12-26T08:40:12.881Z', + archived: false, + id: '18166565782', + properties: { + content: null, + createdate: '2024-12-26T08:40:12.881Z', + hs_lastmodifieddate: '2024-12-26T08:40:14.245Z', + hs_object_id: '18166565782', + hs_pipeline: '0', + hs_pipeline_stage: '1', + hs_resolution: null, + hs_ticket_category: null, + hs_ticket_id: '18166565782', + hs_ticket_priority: null, + hubspot_owner_id: '1594636734', + hubspot_team_id: '55094099', + source_type: null, + subject: 'test', + }, + updatedAt: '2024-12-26T08:40:14.245Z', + }, +}); diff --git a/packages/pieces/community/hubspot/tsconfig.json b/packages/pieces/community/hubspot/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/hubspot/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/hubspot/tsconfig.lib.json b/packages/pieces/community/hubspot/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/hubspot/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/image-ai/.eslintrc.json b/packages/pieces/community/image-ai/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/image-ai/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/image-ai/README.md b/packages/pieces/community/image-ai/README.md new file mode 100644 index 0000000..d700f00 --- /dev/null +++ b/packages/pieces/community/image-ai/README.md @@ -0,0 +1,7 @@ +# pieces-image-ai + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-image-ai` to build the library. diff --git a/packages/pieces/community/image-ai/package.json b/packages/pieces/community/image-ai/package.json new file mode 100644 index 0000000..dd9df8a --- /dev/null +++ b/packages/pieces/community/image-ai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-image-ai", + "version": "0.2.2" +} diff --git a/packages/pieces/community/image-ai/project.json b/packages/pieces/community/image-ai/project.json new file mode 100644 index 0000000..cab920d --- /dev/null +++ b/packages/pieces/community/image-ai/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-image-ai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/image-ai/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/image-ai", + "tsConfig": "packages/pieces/community/image-ai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/image-ai/package.json", + "main": "packages/pieces/community/image-ai/src/index.ts", + "assets": [ + "packages/pieces/community/image-ai/*.md", + { + "input": "packages/pieces/community/image-ai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/image-ai/src/index.ts b/packages/pieces/community/image-ai/src/index.ts new file mode 100644 index 0000000..6baed4b --- /dev/null +++ b/packages/pieces/community/image-ai/src/index.ts @@ -0,0 +1,17 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { generateImageAction } from './lib/actions/generate-image'; + +export const imageAi = createPiece({ + displayName: 'Image AI', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.63.0', + logoUrl: 'https://cdn.activepieces.com/pieces/image-ai.svg', + categories: [ + PieceCategory.ARTIFICIAL_INTELLIGENCE, + PieceCategory.UNIVERSAL_AI, + ], + authors: ['kishanprmr', 'amrdb'], + actions: [generateImageAction], + triggers: [], +}); diff --git a/packages/pieces/community/image-ai/src/lib/actions/generate-image.ts b/packages/pieces/community/image-ai/src/lib/actions/generate-image.ts new file mode 100644 index 0000000..071b099 --- /dev/null +++ b/packages/pieces/community/image-ai/src/lib/actions/generate-image.ts @@ -0,0 +1,106 @@ +import { aiProps } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { createAIProvider, SUPPORTED_AI_PROVIDERS } from '@activepieces/shared'; +import { ImageModel } from 'ai'; +import { experimental_generateImage as generateImage } from 'ai'; + +export const generateImageAction = createAction({ + name: 'generateImage', + displayName: 'Generate Image', + description: '', + props: { + provider: aiProps({ modelType: 'image' }).provider, + model: aiProps({ modelType: 'image' }).model, + prompt: Property.LongText({ + displayName: 'Prompt', + required: true, + }), + advancedOptions: aiProps({ modelType: 'image' }).advancedOptions, + resolution: Property.Dropdown({ + displayName: 'Resolution', + description: 'The resolution to generate the image in.', + required: true, + refreshers: ['model'], + defaultValue: '1024x1024', + options: async (propsValue) => { + const model = propsValue['model'] as ImageModel; + + let options = [ + { + label: '1024x1024', + value: '1024x1024', + }, + { + label: '512x512', + value: '512x512', + }, + { + label: '256x256', + value: '256x256', + }, + ]; + if (model.modelId == 'dall-e-3') + options = [ + { + label: '1024x1024', + value: '1024x1024', + }, + { + label: '1024x1792', + value: '1024x1792', + }, + { + label: '1792x1024', + value: '1792x1024', + }, + ]; + + return { + options: options, + }; + }, + }), + }, + async run(context) { + const providerName = context.propsValue.provider as string; + const modelInstance = context.propsValue.model as ImageModel; + + const providerConfig = SUPPORTED_AI_PROVIDERS.find(p => p.provider === providerName); + if (!providerConfig) { + throw new Error(`Provider ${providerName} not found`); + } + + const baseURL = `${context.server.apiUrl}v1/ai-providers/proxy/${providerName}`; + const engineToken = context.server.token; + const provider = createAIProvider({ + providerName, + modelInstance, + apiKey: engineToken, + baseURL, + }); + + const response = await generateImage({ + model: provider, + prompt: context.propsValue.prompt, + size: context.propsValue.resolution as `${number}x${number}`, + providerOptions: { + ...context.propsValue.advancedOptions, + }, + headers: { + 'Authorization': `Bearer ${engineToken}`, + }, + }); + + if (response.image.base64) { + return context.files.write({ + data: Buffer.from(response.image.base64, 'base64'), + fileName: 'image.png', + }); + } else { + return context.files.write({ + data: Buffer.from(response.image.uint8Array), + fileName: 'image.png', + }); + } + }, +}); diff --git a/packages/pieces/community/image-ai/tsconfig.json b/packages/pieces/community/image-ai/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/image-ai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/image-ai/tsconfig.lib.json b/packages/pieces/community/image-ai/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/image-ai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/image-helper/.eslintrc.json b/packages/pieces/community/image-helper/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/image-helper/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/image-helper/README.md b/packages/pieces/community/image-helper/README.md new file mode 100644 index 0000000..04112d4 --- /dev/null +++ b/packages/pieces/community/image-helper/README.md @@ -0,0 +1,7 @@ +# pieces-image-helper + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-image-helper` to build the library. diff --git a/packages/pieces/community/image-helper/package-lock.json b/packages/pieces/community/image-helper/package-lock.json new file mode 100644 index 0000000..8e319bb --- /dev/null +++ b/packages/pieces/community/image-helper/package-lock.json @@ -0,0 +1,981 @@ +{ + "name": "@activepieces/piece-image-helper", + "version": "0.1.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-image-helper", + "version": "0.1.1", + "dependencies": { + "jimp": "^0.22.12" + } + }, + "node_modules/@jimp/bmp": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.12.tgz", + "integrity": "sha512-aeI64HD0npropd+AR76MCcvvRaa+Qck6loCOS03CkkxGHN5/r336qTM5HPUdHKMDOGzqknuVPA8+kK1t03z12g==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "bmp-js": "^0.1.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/core": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.12.tgz", + "integrity": "sha512-l0RR0dOPyzMKfjUW1uebzueFEDtCOj9fN6pyTYWWOM/VS4BciXQ1VVrJs8pO3kycGYZxncRKhCoygbNr8eEZQA==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "any-base": "^1.1.0", + "buffer": "^5.2.0", + "exif-parser": "^0.1.12", + "file-type": "^16.5.4", + "isomorphic-fetch": "^3.0.0", + "pixelmatch": "^4.0.2", + "tinycolor2": "^1.6.0" + } + }, + "node_modules/@jimp/custom": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.12.tgz", + "integrity": "sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHV/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1Q==", + "dependencies": { + "@jimp/core": "^0.22.12" + } + }, + "node_modules/@jimp/gif": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.22.12.tgz", + "integrity": "sha512-y6BFTJgch9mbor2H234VSjd9iwAhaNf/t3US5qpYIs0TSbAvM02Fbc28IaDETj9+4YB4676sz4RcN/zwhfu1pg==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "gifwrap": "^0.10.1", + "omggif": "^1.0.9" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/jpeg": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.12.tgz", + "integrity": "sha512-Rq26XC/uQWaQKyb/5lksCTCxXhtY01NJeBN+dQv5yNYedN0i7iYu+fXEoRsfaJ8xZzjoANH8sns7rVP4GE7d/Q==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "jpeg-js": "^0.4.4" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-blit": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz", + "integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-blur": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.12.tgz", + "integrity": "sha512-S0vJADTuh1Q9F+cXAwFPlrKWzDj2F9t/9JAbUvaaDuivpyWuImEKXVz5PUZw2NbpuSHjwssbTpOZ8F13iJX4uw==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-circle": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.22.12.tgz", + "integrity": "sha512-SWVXx1yiuj5jZtMijqUfvVOJBwOifFn0918ou4ftoHgegc5aHWW5dZbYPjvC9fLpvz7oSlptNl2Sxr1zwofjTg==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-color": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.12.tgz", + "integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "tinycolor2": "^1.6.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-contain": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.22.12.tgz", + "integrity": "sha512-Eo3DmfixJw3N79lWk8q/0SDYbqmKt1xSTJ69yy8XLYQj9svoBbyRpSnHR+n9hOw5pKXytHwUW6nU4u1wegHNoQ==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5", + "@jimp/plugin-scale": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-cover": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.22.12.tgz", + "integrity": "sha512-z0w/1xH/v/knZkpTNx+E8a7fnasQ2wHG5ze6y5oL2dhH1UufNua8gLQXlv8/W56+4nJ1brhSd233HBJCo01BXA==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-crop": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5", + "@jimp/plugin-scale": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-crop": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz", + "integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-displace": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.22.12.tgz", + "integrity": "sha512-qpRM8JRicxfK6aPPqKZA6+GzBwUIitiHaZw0QrJ64Ygd3+AsTc7BXr+37k2x7QcyCvmKXY4haUrSIsBug4S3CA==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-dither": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.22.12.tgz", + "integrity": "sha512-jYgGdSdSKl1UUEanX8A85v4+QUm+PE8vHFwlamaKk89s+PXQe7eVE3eNeSZX4inCq63EHL7cX580dMqkoC3ZLw==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-fisheye": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.12.tgz", + "integrity": "sha512-LGuUTsFg+fOp6KBKrmLkX4LfyCy8IIsROwoUvsUPKzutSqMJnsm3JGDW2eOmWIS/jJpPaeaishjlxvczjgII+Q==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-flip": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.22.12.tgz", + "integrity": "sha512-m251Rop7GN8W0Yo/rF9LWk6kNclngyjIJs/VXHToGQ6EGveOSTSQaX2Isi9f9lCDLxt+inBIb7nlaLLxnvHX8Q==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-rotate": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-gaussian": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.12.tgz", + "integrity": "sha512-sBfbzoOmJ6FczfG2PquiK84NtVGeScw97JsCC3rpQv1PHVWyW+uqWFF53+n3c8Y0P2HWlUjflEla2h/vWShvhg==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-invert": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.22.12.tgz", + "integrity": "sha512-N+6rwxdB+7OCR6PYijaA/iizXXodpxOGvT/smd/lxeXsZ/empHmFFFJ/FaXcYh19Tm04dGDaXcNF/dN5nm6+xQ==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-mask": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.22.12.tgz", + "integrity": "sha512-4AWZg+DomtpUA099jRV8IEZUfn1wLv6+nem4NRJC7L/82vxzLCgXKTxvNvBcNmJjT9yS1LAAmiJGdWKXG63/NA==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-normalize": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.22.12.tgz", + "integrity": "sha512-0So0rexQivnWgnhacX4cfkM2223YdExnJTTy6d06WbkfZk5alHUx8MM3yEzwoCN0ErO7oyqEWRnEkGC+As1FtA==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-print": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.22.12.tgz", + "integrity": "sha512-c7TnhHlxm87DJeSnwr/XOLjJU/whoiKYY7r21SbuJ5nuH+7a78EW1teOaj5gEr2wYEd7QtkFqGlmyGXY/YclyQ==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "load-bmfont": "^1.4.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-resize": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz", + "integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-rotate": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz", + "integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5", + "@jimp/plugin-crop": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-scale": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz", + "integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-shadow": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.22.12.tgz", + "integrity": "sha512-FX8mTJuCt7/3zXVoeD/qHlm4YH2bVqBuWQHXSuBK054e7wFRnRnbSLPUqAwSeYP3lWqpuQzJtgiiBxV3+WWwTg==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blur": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-threshold": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.12.tgz", + "integrity": "sha512-4x5GrQr1a/9L0paBC/MZZJjjgjxLYrqSmWd+e+QfAEPvmRxdRoQ5uKEuNgXnm9/weHQBTnQBQsOY2iFja+XGAw==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-color": ">=0.8.0", + "@jimp/plugin-resize": ">=0.8.0" + } + }, + "node_modules/@jimp/plugins": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.22.12.tgz", + "integrity": "sha512-yBJ8vQrDkBbTgQZLty9k4+KtUQdRjsIDJSPjuI21YdVeqZxYywifHl4/XWILoTZsjTUASQcGoH0TuC0N7xm3ww==", + "dependencies": { + "@jimp/plugin-blit": "^0.22.12", + "@jimp/plugin-blur": "^0.22.12", + "@jimp/plugin-circle": "^0.22.12", + "@jimp/plugin-color": "^0.22.12", + "@jimp/plugin-contain": "^0.22.12", + "@jimp/plugin-cover": "^0.22.12", + "@jimp/plugin-crop": "^0.22.12", + "@jimp/plugin-displace": "^0.22.12", + "@jimp/plugin-dither": "^0.22.12", + "@jimp/plugin-fisheye": "^0.22.12", + "@jimp/plugin-flip": "^0.22.12", + "@jimp/plugin-gaussian": "^0.22.12", + "@jimp/plugin-invert": "^0.22.12", + "@jimp/plugin-mask": "^0.22.12", + "@jimp/plugin-normalize": "^0.22.12", + "@jimp/plugin-print": "^0.22.12", + "@jimp/plugin-resize": "^0.22.12", + "@jimp/plugin-rotate": "^0.22.12", + "@jimp/plugin-scale": "^0.22.12", + "@jimp/plugin-shadow": "^0.22.12", + "@jimp/plugin-threshold": "^0.22.12", + "timm": "^1.6.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/png": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.12.tgz", + "integrity": "sha512-Mrp6dr3UTn+aLK8ty/dSKELz+Otdz1v4aAXzV5q53UDD2rbB5joKVJ/ChY310B+eRzNxIovbUF1KVrUsYdE8Hg==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "pngjs": "^6.0.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/tiff": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.12.tgz", + "integrity": "sha512-E1LtMh4RyJsoCAfAkBRVSYyZDTtLq9p9LUiiYP0vPtXyxX4BiYBUYihTLSBlCQg5nF2e4OpQg7SPrLdJ66u7jg==", + "dependencies": { + "utif2": "^4.0.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/types": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.12.tgz", + "integrity": "sha512-wwKYzRdElE1MBXFREvCto5s699izFHNVvALUv79GXNbsOVqlwlOxlWJ8DuyOGIXoLP4JW/m30YyuTtfUJgMRMA==", + "dependencies": { + "@jimp/bmp": "^0.22.12", + "@jimp/gif": "^0.22.12", + "@jimp/jpeg": "^0.22.12", + "@jimp/png": "^0.22.12", + "@jimp/tiff": "^0.22.12", + "timm": "^1.6.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "dependencies": { + "regenerator-runtime": "^0.13.3" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, + "node_modules/@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==" + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/centra": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/centra/-/centra-2.7.0.tgz", + "integrity": "sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg==", + "dependencies": { + "follow-redirects": "^1.15.6" + } + }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" + }, + "node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/gifwrap": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", + "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", + "dependencies": { + "image-q": "^4.0.0", + "omggif": "^1.0.10" + } + }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/image-q": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", + "dependencies": { + "@types/node": "16.9.1" + } + }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/jimp": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.12.tgz", + "integrity": "sha512-R5jZaYDnfkxKJy1dwLpj/7cvyjxiclxU3F4TrI/J4j2rS0niq6YDUMoPn5hs8GDpO+OZGo7Ky057CRtWesyhfg==", + "dependencies": { + "@jimp/custom": "^0.22.12", + "@jimp/plugins": "^0.22.12", + "@jimp/types": "^0.22.12", + "regenerator-runtime": "^0.13.3" + } + }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==" + }, + "node_modules/load-bmfont": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.2.tgz", + "integrity": "sha512-qElWkmjW9Oq1F9EI5Gt7aD9zcdHb9spJCW1L/dmPf7KzCCEJxq8nhHz5eCgI9aMf7vrG/wyaCqdsI+Iy9ZTlog==", + "dependencies": { + "buffer-equal": "0.0.1", + "mime": "^1.3.4", + "parse-bmfont-ascii": "^1.0.3", + "parse-bmfont-binary": "^1.0.5", + "parse-bmfont-xml": "^1.1.4", + "phin": "^3.7.1", + "xhr": "^2.0.1", + "xtend": "^4.0.0" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==" + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==" + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", + "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.5.0" + } + }, + "node_modules/parse-headers": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.6.tgz", + "integrity": "sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==" + }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/phin": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/phin/-/phin-3.7.1.tgz", + "integrity": "sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ==", + "dependencies": { + "centra": "^2.7.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/pixelmatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", + "dependencies": { + "pngjs": "^3.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readable-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", + "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", + "dependencies": { + "readable-stream": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/timm": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", + "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==" + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + }, + "node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/utif2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", + "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", + "dependencies": { + "pako": "^1.0.11" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "dependencies": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==" + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + } + } +} diff --git a/packages/pieces/community/image-helper/package.json b/packages/pieces/community/image-helper/package.json new file mode 100644 index 0000000..8642e38 --- /dev/null +++ b/packages/pieces/community/image-helper/package.json @@ -0,0 +1,7 @@ +{ + "name": "@activepieces/piece-image-helper", + "version": "0.1.1", + "dependencies": { + "jimp": "^0.22.12" + } +} diff --git a/packages/pieces/community/image-helper/project.json b/packages/pieces/community/image-helper/project.json new file mode 100644 index 0000000..92bb22c --- /dev/null +++ b/packages/pieces/community/image-helper/project.json @@ -0,0 +1,69 @@ +{ + "name": "pieces-image-helper", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/image-helper/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/image-helper", + "tsConfig": "packages/pieces/community/image-helper/tsconfig.lib.json", + "packageJson": "packages/pieces/community/image-helper/package.json", + "main": "packages/pieces/community/image-helper/src/index.ts", + "assets": [ + "packages/pieces/community/image-helper/*.md", + { + "input": "packages/pieces/community/image-helper/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + }, + "dependsOn": ["prebuild", "^build"] + }, + "prebuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/pieces/community/image-helper", + "command": "npm ci" + } + }, + "build-with-deps": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "nx run pieces-image-helper:prebuild", + "nx run pieces-image-helper:build", + "nx run pieces-image-helper:postbuild" + ], + "parallel": false + } + }, + "postbuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "dist/packages/pieces/community/image-helper", + "command": "npm install" + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-image-helper {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/image-helper/src/index.ts b/packages/pieces/community/image-helper/src/index.ts new file mode 100644 index 0000000..0a751e1 --- /dev/null +++ b/packages/pieces/community/image-helper/src/index.ts @@ -0,0 +1,21 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { imageToBase64 } from './lib/actions/image-to-base64.action'; +import { getMetaData } from './lib/actions/get-metadata.action'; +import { cropImage } from './lib/actions/crop-image.action'; +import { rotateImage } from './lib/actions/rotate-image.action'; +import { resizeImage } from './lib/actions/resize-Image.action'; +import { compressImage } from './lib/actions/compress-image.actions'; + +export const imageHelper = createPiece({ + displayName: 'Image Helper', + description: 'Tools for image manipulations', + + auth: PieceAuth.None(), + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/image-helper.png', + authors: ["AbdullahBitar","kishanprmr","abuaboud"], + categories: [PieceCategory.CORE], + actions: [imageToBase64, getMetaData, cropImage, rotateImage, resizeImage, compressImage], + triggers: [], +}); diff --git a/packages/pieces/community/image-helper/src/lib/actions/compress-image.actions.ts b/packages/pieces/community/image-helper/src/lib/actions/compress-image.actions.ts new file mode 100644 index 0000000..d02b03f --- /dev/null +++ b/packages/pieces/community/image-helper/src/lib/actions/compress-image.actions.ts @@ -0,0 +1,60 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import jimp from 'jimp'; + +export const compressImage = createAction({ + name: 'compress_image', + description: 'Compresses an image', + displayName: 'Compresses an image', + props: { + image: Property.File({ + displayName: 'Image', + required: true, + }), + quality: Property.StaticDropdown({ + displayName: 'Quality', + description: + 'Specifies the quality of the image after compression (0-100).', + required: true, + options: { + options: [ + { label: 'High Quality', value: 90 }, + { label: 'Lossy Quality', value: 60 }, + ], + }, + }), + format: Property.StaticDropdown({ + displayName: 'Format', + description: 'Specifies the format of the image after compression.', + required: true, + options: { + options: [ + { label: 'JPG', value: 'jpg' }, + { label: 'PNG', value: 'png' }, + ], + }, + }), + resultFileName: Property.ShortText({ + displayName: 'Result File Name', + description: + 'Specifies the output file name for the result image (without extension).', + required: false, + }), + }, + async run(context) { + const image = await jimp.read(context.propsValue.image.data); + + image.quality(context.propsValue.quality); + + const imageBuffer = await image.getBufferAsync(image.getMIME()); + + const imageReference = await context.files.write({ + fileName: + (context.propsValue.resultFileName ?? 'image') + + '.' + + context.propsValue.format, + data: imageBuffer, + }); + + return imageReference; + }, +}); diff --git a/packages/pieces/community/image-helper/src/lib/actions/crop-image.action.ts b/packages/pieces/community/image-helper/src/lib/actions/crop-image.action.ts new file mode 100644 index 0000000..8305cb9 --- /dev/null +++ b/packages/pieces/community/image-helper/src/lib/actions/crop-image.action.ts @@ -0,0 +1,65 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import jimp from 'jimp'; + +export const cropImage = createAction({ + name: 'crop_image', + description: 'Crops an image', + displayName: 'Crop an image', + props: { + image: Property.File({ + displayName: 'Image', + required: true, + }), + left: Property.Number({ + displayName: 'Left', + description: + 'Specifies the horizontal position, indicating where the cropping starts from the left side of the image.', + required: true, + }), + top: Property.Number({ + displayName: 'Top', + description: + 'Represents the vertical position, indicating the starting point from the top of the image.', + required: true, + }), + width: Property.Number({ + displayName: 'Width', + description: 'Determines the horizontal size of the cropped area.', + required: true, + }), + height: Property.Number({ + displayName: 'Height', + description: 'Determines the vertical size of the cropped area.', + required: true, + }), + resultFileName: Property.ShortText({ + displayName: 'Result File Name', + description: + 'Specifies the output file name for the cropped image (without extension).', + required: false, + }), + }, + async run(context) { + const image = await jimp.read(context.propsValue.image.data); + await image.crop( + context.propsValue.left, + context.propsValue.top, + context.propsValue.width, + context.propsValue.height + ); + + const imageBuffer = await image.getBufferAsync(image.getMIME()); + + const fileName = + (context.propsValue.resultFileName ?? 'image') + + '.' + + image.getExtension(); + + const imageReference = await context.files.write({ + fileName: fileName, + data: imageBuffer, + }); + + return imageReference; + }, +}); diff --git a/packages/pieces/community/image-helper/src/lib/actions/get-metadata.action.ts b/packages/pieces/community/image-helper/src/lib/actions/get-metadata.action.ts new file mode 100644 index 0000000..fdb1418 --- /dev/null +++ b/packages/pieces/community/image-helper/src/lib/actions/get-metadata.action.ts @@ -0,0 +1,18 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import * as ExifReader from 'exifreader'; + +export const getMetaData = createAction({ + name: 'get_meta_data', + description: 'Gets metadata from an image', + displayName: 'Get image metadata', + props: { + image: Property.File({ + displayName: 'Image', + required: true, + }), + }, + async run(context) { + const tags = await ExifReader.load(context.propsValue.image.data); + return tags; + }, +}); diff --git a/packages/pieces/community/image-helper/src/lib/actions/image-to-base64.action.ts b/packages/pieces/community/image-helper/src/lib/actions/image-to-base64.action.ts new file mode 100644 index 0000000..ca7ef2f --- /dev/null +++ b/packages/pieces/community/image-helper/src/lib/actions/image-to-base64.action.ts @@ -0,0 +1,32 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import mime from 'mime-types'; + +export const imageToBase64 = createAction({ + name: 'image_to_base64', + description: 'Converts an image to an url-like Base64 string', + displayName: 'Image to Base64', + props: { + image: Property.File({ + displayName: 'Image', + description: 'The image to convert', + required: true, + }), + override_mime_type: Property.ShortText({ + displayName: 'Override mime type', + description: + 'The mime type to use when converting the image. In case you want to override the default mime type. Example image/png', + required: false, + }), + }, + async run(context) { + const image = context.propsValue.image; + const mimeType = mime.lookup( + image.extension ? image.extension : 'image/png' + ); + + const actualMimeType = context.propsValue.override_mime_type + ? context.propsValue.override_mime_type + : mimeType; + return `data:${actualMimeType};base64,${image.base64}`; + }, +}); diff --git a/packages/pieces/community/image-helper/src/lib/actions/resize-Image.action.ts b/packages/pieces/community/image-helper/src/lib/actions/resize-Image.action.ts new file mode 100644 index 0000000..030d119 --- /dev/null +++ b/packages/pieces/community/image-helper/src/lib/actions/resize-Image.action.ts @@ -0,0 +1,54 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import jimp from 'jimp'; + +export const resizeImage = createAction({ + name: 'resize_image', + description: 'Resizes an image', + displayName: 'Resize an image', + props: { + image: Property.File({ + displayName: 'Image', + required: true, + }), + width: Property.Number({ + displayName: 'Width', + description: 'Specifies the width of the image.', + required: true, + }), + height: Property.Number({ + displayName: 'Height', + description: 'Specifies the height of the image.', + required: true, + }), + aspectRatio: Property.Checkbox({ + displayName: 'Maintain aspect ratio for height', + required: false, + defaultValue: false, + }), + resultFileName: Property.ShortText({ + displayName: 'Result File Name', + description: + 'Specifies the output file name for the result image (without extension).', + required: false, + }), + }, + async run(context) { + const image = await jimp.read(context.propsValue.image.data); + await image.resize( + context.propsValue.width, + context.propsValue.aspectRatio ? jimp.AUTO : context.propsValue.height + ); + + const imageBuffer = await image.getBufferAsync(image.getMIME()); + + const imageReference = await context.files.write({ + fileName: + (context.propsValue.resultFileName ?? 'image') + + '.' + + image.getExtension(), + data: imageBuffer, + }); + + return imageReference; + }, +}); diff --git a/packages/pieces/community/image-helper/src/lib/actions/rotate-image.action.ts b/packages/pieces/community/image-helper/src/lib/actions/rotate-image.action.ts new file mode 100644 index 0000000..5a6cbb8 --- /dev/null +++ b/packages/pieces/community/image-helper/src/lib/actions/rotate-image.action.ts @@ -0,0 +1,49 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import jimp from 'jimp'; + +export const rotateImage = createAction({ + name: 'rotate_image', + description: 'Rotates an image', + displayName: 'Rotate an image', + props: { + image: Property.File({ + displayName: 'Image', + required: true, + }), + degree: Property.StaticDropdown({ + displayName: 'Degree', + description: + 'Specifies the degree of clockwise rotation applied to the image.', + required: true, + options: { + options: [ + { value: 90, label: '90°' }, + { value: 180, label: '180°' }, + { value: 270, label: '270°' }, + ], + }, + }), + resultFileName: Property.ShortText({ + displayName: 'Result File Name', + description: + 'Specifies the output file name for the result image (without extension).', + required: false, + }), + }, + async run(context) { + const image = await jimp.read(context.propsValue.image.data); + await image.rotate(-context.propsValue.degree); + + const imageBuffer = await image.getBufferAsync(image.getMIME()); + + const imageReference = await context.files.write({ + fileName: + (context.propsValue.resultFileName ?? 'image') + + '.' + + image.getExtension(), + data: imageBuffer, + }); + + return imageReference; + }, +}); diff --git a/packages/pieces/community/image-helper/tsconfig.json b/packages/pieces/community/image-helper/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/image-helper/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/image-helper/tsconfig.lib.json b/packages/pieces/community/image-helper/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/image-helper/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/imap/.eslintrc.json b/packages/pieces/community/imap/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/imap/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/imap/README.md b/packages/pieces/community/imap/README.md new file mode 100644 index 0000000..467b7ee --- /dev/null +++ b/packages/pieces/community/imap/README.md @@ -0,0 +1,7 @@ +# pieces-imap + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-imap` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/imap/package.json b/packages/pieces/community/imap/package.json new file mode 100644 index 0000000..d6e6d39 --- /dev/null +++ b/packages/pieces/community/imap/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-imap", + "version": "0.2.11" +} \ No newline at end of file diff --git a/packages/pieces/community/imap/project.json b/packages/pieces/community/imap/project.json new file mode 100644 index 0000000..af789de --- /dev/null +++ b/packages/pieces/community/imap/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-imap", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/imap/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/imap", + "tsConfig": "packages/pieces/community/imap/tsconfig.lib.json", + "packageJson": "packages/pieces/community/imap/package.json", + "main": "packages/pieces/community/imap/src/index.ts", + "assets": [ + "packages/pieces/community/imap/*.md", + { + "input": "packages/pieces/community/imap/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/imap/src/index.ts b/packages/pieces/community/imap/src/index.ts new file mode 100644 index 0000000..599728f --- /dev/null +++ b/packages/pieces/community/imap/src/index.ts @@ -0,0 +1,83 @@ +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { ImapFlow } from 'imapflow'; +import { imapCommon } from './lib/common'; +import { newEmail } from './lib/triggers/new-email'; + +const description = ` +**Gmail Users:** +

+Make Sure of the following: +
+* IMAP is enabled in your Gmail settings (https://support.google.com/mail/answer/7126229?hl=en) +* You have created an App Password to login with (https://support.google.com/accounts/answer/185833?hl=en) +* Enable TLS and set the port to 993 and the host to imap.gmail.com +`; +export const imapAuth = PieceAuth.CustomAuth({ + description: description, + props: { + host: Property.ShortText({ + displayName: 'Host', + required: true, + }), + username: Property.ShortText({ + displayName: 'Username', + required: true, + }), + password: PieceAuth.SecretText({ + displayName: 'Password', + required: true, + }), + port: Property.Number({ + displayName: 'Port', + required: true, + defaultValue: 143, + }), + tls: Property.Checkbox({ + displayName: 'Use TLS', + defaultValue: false, + required: true, + }), + }, + validate: async ({ auth }) => { + const imapConfig = imapCommon.constructConfig( + auth as { + host: string; + username: string; + password: string; + port: number; + tls: boolean; + } + ); + const imapClient = new ImapFlow({ ...imapConfig, logger: false }); + try { + await imapClient.connect(); + await imapClient.noop(); + return { valid: true }; + } catch (e) { + return { + valid: false, + error: JSON.stringify(e), + }; + } finally { + await imapClient.logout(); + } + }, + required: true, +}); + +export const imapPiece = createPiece({ + displayName: 'IMAP', + description: 'Receive new email trigger', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/imap.png', + categories: [PieceCategory.BUSINESS_INTELLIGENCE], + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + auth: imapAuth, + actions: [], + triggers: [newEmail], +}); diff --git a/packages/pieces/community/imap/src/lib/common/index.ts b/packages/pieces/community/imap/src/lib/common/index.ts new file mode 100644 index 0000000..836645c --- /dev/null +++ b/packages/pieces/community/imap/src/lib/common/index.ts @@ -0,0 +1,138 @@ +import { + FilesService, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { ImapFlow } from 'imapflow'; +import { imapAuth } from '../../'; + +import dayjs from 'dayjs'; +import { Attachment, ParsedMail, simpleParser } from 'mailparser'; +export const imapCommon = { + constructConfig(auth: { + host: string; + username: string; + password: string; + port: number; + tls: boolean; + }) { + return { + host: auth.host, + port: auth.port, + secure: auth.tls, + auth: { user: auth.username, pass: auth.password }, + tls: { rejectUnauthorized: false }, + }; + }, + mailbox: Property.Dropdown({ + displayName: 'Mailbox', + description: 'Select the mailbox to search', + required: true, + refreshers: [], + options: async ({ auth }) => { + const imapConfig = imapCommon.constructConfig( + auth as { + host: string; + username: string; + password: string; + port: number; + tls: boolean; + } + ); + let options: { label: string; value: string }[] = []; + const imapClient = new ImapFlow({ ...imapConfig, logger: false }); + try { + await imapClient.connect(); + const mailBoxList = await imapClient.list(); + options = mailBoxList.map((mailbox) => { + return { + label: mailbox.name, + value: mailbox.path, + }; + }); + } finally { + await imapClient.logout(); + } + return { + disabled: false, + options: options, + }; + }, + }), + async fetchEmails({ + auth, + lastEpochMilliSeconds, + mailbox, + }: { + auth: PiecePropValueSchema; + lastEpochMilliSeconds: number; + mailbox: string; + files: FilesService; + }): Promise<{ + data: ParsedMail; + epochMilliSeconds: number; + }[]> { + const imapConfig = imapCommon.constructConfig( + auth as { + host: string; + username: string; + password: string; + port: number; + tls: boolean; + } + ); + const imapClient = new ImapFlow({ ...imapConfig, logger: false }); + await imapClient.connect(); + const lock = await imapClient.getMailboxLock(mailbox); + try { + const res = imapClient.fetch( + { + since: + lastEpochMilliSeconds === 0 + ? dayjs().subtract(2, 'hour').toISOString() + : dayjs(lastEpochMilliSeconds).toISOString(), + }, + { + source: true, + } + ); + const messages = []; + for await (const message of res) { + const castedItem = await parseStream(message.source); + messages.push({ + data: castedItem, + epochMilliSeconds: dayjs(castedItem.date).valueOf(), + }); + } + return messages; + } finally { + lock.release(); + await imapClient.logout(); + } + }, +}; + +export async function convertAttachment( + attachments: Attachment[], + files: FilesService +) { + const promises = attachments.map(async (attachment) => { + return files.write({ + fileName: attachment.filename ?? `attachment-${Date.now()}`, + data: attachment.content, + }); + }); + return Promise.all(promises); +} + +async function parseStream(stream: any) { + return new Promise((resolve, reject) => { + simpleParser(stream, (err, parsed) => { + if (err) { + reject(err); + } else { + resolve(parsed); + } + }); + }); +} diff --git a/packages/pieces/community/imap/src/lib/triggers/new-email.ts b/packages/pieces/community/imap/src/lib/triggers/new-email.ts new file mode 100644 index 0000000..b2ad5d7 --- /dev/null +++ b/packages/pieces/community/imap/src/lib/triggers/new-email.ts @@ -0,0 +1,124 @@ +import { + FilesService, + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { imapAuth } from '../..'; +import { convertAttachment, imapCommon } from '../common'; +import { ParsedMail } from 'mailparser'; + +const filterInstructions = ` +**Filter Emails:** + +You can add Branch Piece to filter emails based on the subject, to, from, cc or other fields. +`; + +export const newEmail = createTrigger({ + auth: imapAuth, + name: 'new_email', + displayName: 'New Email', + description: 'Trigger when a new email is received.', + props: { + mailbox: imapCommon.mailbox, + filterInstructions: Property.MarkDown({ + value: filterInstructions, + }), + }, + type: TriggerStrategy.POLLING, + onEnable: async (context) => { + await context.store.put('lastPoll', Date.now()); + }, + onDisable: async (context) => { + await context.store.delete('lastPoll'); + return; + }, + run: async (context) => { + const { auth, store, propsValue, files } = context; + const mailbox = propsValue.mailbox; + const lastEpochMilliSeconds = (await store.get('lastPoll')) ?? 0; + const items = await imapCommon.fetchEmails({ + auth, + lastEpochMilliSeconds, + mailbox, + files, + }); + const newLastEpochMilliSeconds = items.reduce( + (acc, item) => Math.max(acc, item.epochMilliSeconds), + lastEpochMilliSeconds + ); + await store.put('lastPoll', newLastEpochMilliSeconds); + const filteredEmail = items + .filter((f) => f.epochMilliSeconds > lastEpochMilliSeconds); + return enrichAttachments(filteredEmail, files); + }, + test: async (context) => { + const { auth, propsValue, files } = context; + const mailbox = propsValue.mailbox; + const lastEpochMilliSeconds = 0; + const items = await imapCommon.fetchEmails({ + auth, + lastEpochMilliSeconds, + mailbox, + files, + }); + const filteredEmails = getFirstFiveOrAll(items); + return enrichAttachments(filteredEmails, files); + }, + sampleData: { + html: 'My email body', + text: 'My email body', + textAsHtml: '

My email body

', + subject: 'Email Subject', + date: '2023-06-18T11:30:09.000Z', + to: { + value: [ + { + address: 'email@address.com', + name: 'Name', + }, + ], + }, + from: { + value: [ + { + address: 'email@address.com', + name: 'Name', + }, + ], + }, + cc: { + value: [ + { + address: 'email@address.com', + name: 'Name', + }, + ], + }, + messageId: + '', + }, +}); + +async function enrichAttachments(item: { + data: ParsedMail; + epochMilliSeconds: number; +}[], files: FilesService) { + return Promise.all(item.map(async (item) => { + + const { attachments, ...rest } = item.data + return { + data:{...rest}, + epochMilliSeconds: item.epochMilliSeconds, + attachments: await convertAttachment(item.data.attachments, files), + } + + })); +} +function getFirstFiveOrAll(array: T[]) { + if (array.length <= 5) { + return array; + } else { + return array.slice(0, 5); + } +} diff --git a/packages/pieces/community/imap/tsconfig.json b/packages/pieces/community/imap/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/imap/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/imap/tsconfig.lib.json b/packages/pieces/community/imap/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/imap/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/instagram-business/.eslintrc.json b/packages/pieces/community/instagram-business/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/instagram-business/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/instagram-business/README.md b/packages/pieces/community/instagram-business/README.md new file mode 100644 index 0000000..11e5bbd --- /dev/null +++ b/packages/pieces/community/instagram-business/README.md @@ -0,0 +1,7 @@ +# pieces-instagram-business + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-instagram-business` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/instagram-business/package.json b/packages/pieces/community/instagram-business/package.json new file mode 100644 index 0000000..b79f5ac --- /dev/null +++ b/packages/pieces/community/instagram-business/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-instagram-business", + "version": "0.1.6" +} \ No newline at end of file diff --git a/packages/pieces/community/instagram-business/project.json b/packages/pieces/community/instagram-business/project.json new file mode 100644 index 0000000..615a26e --- /dev/null +++ b/packages/pieces/community/instagram-business/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-instagram-business", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/instagram-business/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/instagram-business", + "tsConfig": "packages/pieces/community/instagram-business/tsconfig.lib.json", + "packageJson": "packages/pieces/community/instagram-business/package.json", + "main": "packages/pieces/community/instagram-business/src/index.ts", + "assets": [ + "packages/pieces/community/instagram-business/*.md", + { + "input": "packages/pieces/community/instagram-business/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/instagram-business/src/index.ts b/packages/pieces/community/instagram-business/src/index.ts new file mode 100644 index 0000000..6b4351c --- /dev/null +++ b/packages/pieces/community/instagram-business/src/index.ts @@ -0,0 +1,17 @@ +import { createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { uploadPhoto } from './lib/actions/upload-photo'; +import { uploadReel } from './lib/actions/upload-reel'; +import { instagramCommon } from './lib/common'; + +export const instagramBusiness = createPiece({ + displayName: 'Instagram for Business', + description: 'Grow your business on Instagram', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/instagram.png', + categories: [PieceCategory.BUSINESS_INTELLIGENCE], + authors: ["kishanprmr","MoShizzle","abuaboud"], + auth: instagramCommon.authentication, + actions: [uploadPhoto, uploadReel], + triggers: [], +}); diff --git a/packages/pieces/community/instagram-business/src/lib/actions/upload-photo.ts b/packages/pieces/community/instagram-business/src/lib/actions/upload-photo.ts new file mode 100644 index 0000000..21ff58d --- /dev/null +++ b/packages/pieces/community/instagram-business/src/lib/actions/upload-photo.ts @@ -0,0 +1,24 @@ +import { createAction } from '@activepieces/pieces-framework'; + +import { instagramCommon, FacebookPageDropdown } from '../common'; + +export const uploadPhoto = createAction({ + auth: instagramCommon.authentication, + name: 'upload_photo', + displayName: 'Upload Photo', + description: 'Upload a photo to an Instagram Professional Account', + props: { + page: instagramCommon.page, + photo: instagramCommon.photo, + caption: instagramCommon.caption, + }, + async run({ propsValue }) { + const page: FacebookPageDropdown = propsValue.page!; + const result = await instagramCommon.createPhotoPost( + page, + propsValue.caption, + propsValue.photo + ); + return result; + }, +}); diff --git a/packages/pieces/community/instagram-business/src/lib/actions/upload-reel.ts b/packages/pieces/community/instagram-business/src/lib/actions/upload-reel.ts new file mode 100644 index 0000000..ea5b814 --- /dev/null +++ b/packages/pieces/community/instagram-business/src/lib/actions/upload-reel.ts @@ -0,0 +1,24 @@ +import { createAction } from '@activepieces/pieces-framework'; + +import { instagramCommon, FacebookPageDropdown } from '../common'; + +export const uploadReel = createAction({ + auth: instagramCommon.authentication, + name: 'upload_reel', + displayName: 'Upload Reel', + description: 'Upload a reel to an Instagram Professional Account', + props: { + page: instagramCommon.page, + video: instagramCommon.video, + caption: instagramCommon.caption, + }, + async run(context) { + const page: FacebookPageDropdown = context.propsValue.page!; + const result = await instagramCommon.createVideoPost( + page, + context.propsValue.caption, + context.propsValue.video + ); + return result; + }, +}); diff --git a/packages/pieces/community/instagram-business/src/lib/common/index.ts b/packages/pieces/community/instagram-business/src/lib/common/index.ts new file mode 100644 index 0000000..529264f --- /dev/null +++ b/packages/pieces/community/instagram-business/src/lib/common/index.ts @@ -0,0 +1,204 @@ +import { + HttpMethod, + httpClient, + getAccessTokenOrThrow, +} from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; + +const markdown = ` +To Obtain the following credentials: +1. Visit https://developers.facebook.com/ +2. Create an application, Select Other for Usecase. +3. Select Business as App Type. +4. Copy App Id and App Secret from Basic Settings. +`; + +export const instagramCommon = { + baseUrl: 'https://graph.facebook.com/v17.0', + + authentication: PieceAuth.OAuth2({ + description: markdown, + authUrl: 'https://graph.facebook.com/oauth/authorize', + tokenUrl: 'https://graph.facebook.com/oauth/access_token', + required: true, + scope: ['instagram_basic', 'instagram_content_publish', 'business_management', 'pages_show_list'], + }), + + page: Property.Dropdown({ + displayName: 'Page', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Connect your account', + }; + } + + const accessToken: string = getAccessTokenOrThrow( + auth as OAuth2PropertyValue + ); + const pages: any[] = (await instagramCommon.getPages(accessToken)) + .map((page: FacebookPage) => { + if (!page.instagram_business_account) { + return null; + } + return { + label: page.name, + value: { + id: page.instagram_business_account.id, + accessToken: page.access_token, + }, + }; + }) + .filter((f: unknown) => f !== null); + + return { + options: pages, + placeholder: 'Choose a page', + }; + }, + }), + + caption: Property.LongText({ + displayName: 'Caption', + required: false, + }), + photo: Property.ShortText({ + displayName: 'Photo', + description: 'A URL we can access for the photo (JPG only)', + required: true, + }), + + video: Property.ShortText({ + displayName: 'Video', + description: 'A URL we can access for the video (Limit: 1GB or 15 minutes)', + required: true, + }), + + getPages: async (accessToken: string) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${instagramCommon.baseUrl}/me/accounts`, + queryParams: { + access_token: accessToken, + fields: 'id,name,access_token,instagram_business_account', + }, + }); + + return response.body.data; + }, + + createPhotoPost: async ( + page: FacebookPageDropdown, + caption: string | undefined, + photo: string + ) => { + const createContainerRequest = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${instagramCommon.baseUrl}/${page.id}/media`, + body: { + access_token: page.accessToken, + image_url: photo, + caption: caption, + }, + }); + + const publishContainerRequest = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${instagramCommon.baseUrl}/${page.id}/media_publish`, + body: { + access_token: page.accessToken, + creation_id: createContainerRequest.body.id, + }, + }); + + return publishContainerRequest.body; + }, + + createVideoPost: async ( + page: FacebookPageDropdown, + caption: string | undefined, + video: string + ) => { + const createContainerRequest = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${instagramCommon.baseUrl}/${page.id}/media`, + body: { + access_token: page.accessToken, + video_url: video, + caption: caption, + media_type: 'REELS', + }, + }); + + const isUploaded = await isUploadSuccessful( + createContainerRequest.body.id, + page.accessToken, + 0 + ); + + if (isUploaded) { + const publishContainerRequest = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${instagramCommon.baseUrl}/${page.id}/media_publish`, + body: { + access_token: page.accessToken, + creation_id: createContainerRequest.body.id, + }, + }); + + return publishContainerRequest.body; + } else { + return false; + } + }, +}; + +export interface FacebookPage { + id: string; + name: string; + access_token: string; + instagram_business_account: { + id: string; + }; +} + +export interface FacebookPageDropdown { + id: string; + accessToken: string; +} + +async function isUploadSuccessful( + containerId: string, + accessToken: string, + retryCount: number +): Promise { + if (retryCount > 20) return false; + + const request = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${instagramCommon.baseUrl}/${containerId}`, + queryParams: { + access_token: accessToken, + fields: 'status_code', + }, + }); + + if (request.body.status_code != 'FINISHED') { + await _wait(5000); + return await isUploadSuccessful(containerId, accessToken, retryCount + 1); + } else { + return true; + } +} + +function _wait(n: number) { + return new Promise((resolve) => setTimeout(resolve, n)); +} diff --git a/packages/pieces/community/instagram-business/tsconfig.json b/packages/pieces/community/instagram-business/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/instagram-business/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/instagram-business/tsconfig.lib.json b/packages/pieces/community/instagram-business/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/instagram-business/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/instantly-ai/.eslintrc.json b/packages/pieces/community/instantly-ai/.eslintrc.json new file mode 100644 index 0000000..9810848 --- /dev/null +++ b/packages/pieces/community/instantly-ai/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/instantly-ai/README.md b/packages/pieces/community/instantly-ai/README.md new file mode 100644 index 0000000..f43c71a --- /dev/null +++ b/packages/pieces/community/instantly-ai/README.md @@ -0,0 +1,7 @@ +# pieces-instantly-ai + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx build pieces-instantly-ai` to build the library. \ No newline at end of file diff --git a/packages/pieces/community/instantly-ai/package.json b/packages/pieces/community/instantly-ai/package.json new file mode 100644 index 0000000..96c4453 --- /dev/null +++ b/packages/pieces/community/instantly-ai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-instantly-ai", + "version": "0.0.1" +} diff --git a/packages/pieces/community/instantly-ai/project.json b/packages/pieces/community/instantly-ai/project.json new file mode 100644 index 0000000..9739cce --- /dev/null +++ b/packages/pieces/community/instantly-ai/project.json @@ -0,0 +1,39 @@ +{ + "name": "pieces-instantly-ai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/instantly-ai/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/packages/pieces/community/instantly-ai", + "tsConfig": "packages/pieces/community/instantly-ai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/instantly-ai/package.json", + "main": "packages/pieces/community/instantly-ai/src/index.ts", + "assets": ["packages/pieces/community/instantly-ai/*.md"], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + } + } +} diff --git a/packages/pieces/community/instantly-ai/src/index.ts b/packages/pieces/community/instantly-ai/src/index.ts new file mode 100644 index 0000000..05c040c --- /dev/null +++ b/packages/pieces/community/instantly-ai/src/index.ts @@ -0,0 +1,50 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; + +import { createCampaignAction } from './lib/actions/create-campaign'; +import { createLeadListAction } from './lib/actions/create-lead-list'; +import { addLeadToCampaignAction } from './lib/actions/add-lead-to-campaign'; +import { searchCampaignsAction } from './lib/actions/search-campaigns'; +import { searchLeadsAction } from './lib/actions/search-leads'; +import { campaignStatusChangedTrigger } from './lib/triggers/campaign-status-changed'; +import { newLeadAddedTrigger } from './lib/triggers/new-lead-added'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { BASE_URL } from './lib/common/client'; + +const markdownDescription = ` +You can obtain an API key from **Settings->Integrations->API Keys**. +`; + +export const instantlyAiAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: markdownDescription, + required: true, +}) + +export const instantlyAi = createPiece({ + displayName: 'Instantly.ai', + description: 'Powerful cold email outreach and lead engagement platform.', + auth: instantlyAiAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/instantly-ai.png', + categories: [PieceCategory.MARKETING, PieceCategory.SALES_AND_CRM], + authors: [], + actions: [ + createCampaignAction, + createLeadListAction, + addLeadToCampaignAction, + searchCampaignsAction, + searchLeadsAction, + createCustomApiCallAction({ + auth:instantlyAiAuth, + baseUrl:()=>BASE_URL, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }) + ], + triggers: [ + campaignStatusChangedTrigger, + newLeadAddedTrigger, + ], +}); diff --git a/packages/pieces/community/instantly-ai/src/lib/actions/add-lead-to-campaign.ts b/packages/pieces/community/instantly-ai/src/lib/actions/add-lead-to-campaign.ts new file mode 100644 index 0000000..ad9dcb6 --- /dev/null +++ b/packages/pieces/community/instantly-ai/src/lib/actions/add-lead-to-campaign.ts @@ -0,0 +1,59 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common/client'; +import { instantlyAiAuth } from '../../index'; +import { campaignId, leadId } from '../common/props'; + +export const addLeadToCampaignAction = createAction({ + auth: instantlyAiAuth, + name: 'add_lead_to_campaign', + displayName: 'Add Lead to Campaign', + description: 'Adds a lead to a campaign.', + props: { + lead_id: leadId(true), + campaign_id: campaignId(true), + skip_if_in_campaign: Property.Checkbox({ + displayName: 'Skip if in Campaign', + defaultValue: false, + description: 'Skip lead if it exists in the campaign.', + required: false, + }), + }, + async run(context) { + const { lead_id, campaign_id, skip_if_in_campaign } = context.propsValue; + + const response = (await makeRequest({ + apiKey: context.auth, + endpoint: 'leads/move', + method: HttpMethod.POST, + body: { + ids: [lead_id], + to_campaign_id: campaign_id, + check_duplicates_in_campaigns: skip_if_in_campaign, + }, + })) as { id: string; status: string }; + + const jobId = response.id; + let jobStatus = response.status; + + const wait = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); + + while (['pending', 'in-progress'].includes(jobStatus)) { + await wait(3000); + + const jobResponse = (await makeRequest({ + apiKey: context.auth, + endpoint: `background-jobs/${jobId}`, + method: HttpMethod.GET, + })) as { status: string }; + + jobStatus = jobResponse.status; + } + + return { + ...response, + status: 'success', + }; + }, +}); diff --git a/packages/pieces/community/instantly-ai/src/lib/actions/create-campaign.ts b/packages/pieces/community/instantly-ai/src/lib/actions/create-campaign.ts new file mode 100644 index 0000000..87c460c --- /dev/null +++ b/packages/pieces/community/instantly-ai/src/lib/actions/create-campaign.ts @@ -0,0 +1,286 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common/client'; +import { instantlyAiAuth } from '../../index'; + +export const createCampaignAction = createAction({ + auth: instantlyAiAuth, + name: 'create_campaign', + displayName: 'Create Campaign', + description: 'Create a new cold email campaign in Instantly', + props: { + name: Property.ShortText({ + displayName: 'Campaign Name', + description: 'Name of the campaign', + required: true, + }), + schedule_time_from: Property.ShortText({ + displayName: 'Schedule Time From', + description: 'Start time in 24-hour format (e.g., "09:00")', + required: true, + defaultValue: '09:00', + }), + schedule_time_to: Property.ShortText({ + displayName: 'Schedule Time To', + description: 'End time in 24-hour format (e.g., "17:00")', + required: true, + defaultValue: '17:00', + }), + schedule_timezone: Property.ShortText({ + displayName: 'Schedule Timezone', + description: 'Timezone for the schedule i.e. "America/Dawson".', + required: true, + }), + weekday_sending: Property.Checkbox({ + displayName: 'Send on Weekdays', + description: 'Whether to send emails Monday-Friday', + required: false, + defaultValue: true, + }), + weekend_sending: Property.Checkbox({ + displayName: 'Send on Weekends', + description: 'Whether to send emails Saturday-Sunday', + required: false, + defaultValue: false, + }), + start_date: Property.DateTime({ + displayName: 'Start Date', + description: 'Date to start the campaign', + required: false, + }), + end_date: Property.DateTime({ + displayName: 'End Date', + description: 'Date to end the campaign', + required: false, + }), + pl_value: Property.Number({ + displayName: 'Positive Lead Value', + description: 'Value of every positive lead', + required: false, + }), + is_evergreen: Property.Checkbox({ + displayName: 'Is Evergreen', + description: 'Whether the campaign is evergreen', + required: false, + }), + email_gap: Property.Number({ + displayName: 'Email Gap', + description: 'Gap between emails in minutes', + required: false, + }), + random_wait_max: Property.Number({ + displayName: 'Maximum Random Wait Time', + description: 'Maximum random wait time in minutes', + required: false, + }), + text_only: Property.Checkbox({ + displayName: 'Text Only', + description: 'Whether the campaign is text-only', + required: false, + }), + daily_limit: Property.Number({ + displayName: 'Daily Limit', + description: 'Daily limit for sending emails', + required: false, + }), + stop_on_reply: Property.Checkbox({ + displayName: 'Stop on Reply', + description: 'Whether to stop campaign on reply', + required: false, + }), + link_tracking: Property.Checkbox({ + displayName: 'Link Tracking', + description: 'Whether to track links in emails', + required: false, + }), + open_tracking: Property.Checkbox({ + displayName: 'Open Tracking', + description: 'Whether to track opens in emails', + required: false, + }), + stop_on_auto_reply: Property.Checkbox({ + displayName: 'Stop on Auto Reply', + description: 'Whether to stop campaign on auto-reply', + required: false, + }), + daily_max_leads: Property.Number({ + displayName: 'Daily Maximum Leads', + description: 'Maximum daily new leads to contact', + required: false, + }), + prioritize_new_leads: Property.Checkbox({ + displayName: 'Prioritize New Leads', + description: 'Whether to prioritize new leads', + required: false, + }), + match_lead_esp: Property.Checkbox({ + displayName: 'Match Lead ESP', + description: 'Whether to match leads by ESP', + required: false, + }), + stop_for_company: Property.Checkbox({ + displayName: 'Stop for Company', + description: 'Stop campaign for the company (domain) when a lead replies', + required: false, + }), + insert_unsubscribe_header: Property.Checkbox({ + displayName: 'Insert Unsubscribe Header', + description: 'Insert unsubscribe header in emails', + required: false, + }), + allow_risky_contacts: Property.Checkbox({ + displayName: 'Allow Risky Contacts', + description: 'Allow risky contacts', + required: false, + }), + disable_bounce_protect: Property.Checkbox({ + displayName: 'Disable Bounce Protection', + description: 'Disable bounce protection', + required: false, + }), + }, + async run(context) { + const { + name, + schedule_time_from, + schedule_time_to, + schedule_timezone, + weekday_sending, + weekend_sending, + start_date, + end_date, + pl_value, + is_evergreen, + email_gap, + random_wait_max, + text_only, + daily_limit, + stop_on_reply, + link_tracking, + open_tracking, + stop_on_auto_reply, + daily_max_leads, + prioritize_new_leads, + match_lead_esp, + stop_for_company, + insert_unsubscribe_header, + allow_risky_contacts, + disable_bounce_protect, + } = context.propsValue; + const { auth: apiKey } = context; + + // Build the campaign schedule according to API requirements + const campaignSchedule: Record = { + schedules: [ + { + name: "Default Schedule", + timing: { + from: schedule_time_from, + to: schedule_time_to, + }, + days: { + 0: weekday_sending, // Monday + 1: weekday_sending, // Tuesday + 2: weekday_sending, // Wednesday + 3: weekday_sending, // Thursday + 4: weekday_sending, // Friday + 5: weekend_sending, // Saturday + 6: weekend_sending, // Sunday + }, + timezone: schedule_timezone, + } + ], + }; + + // Add optional start and end dates if provided + if (start_date) { + campaignSchedule['start_date'] = start_date; + } + + if (end_date) { + campaignSchedule['end_date'] = end_date; + } + + // Build the payload with required fields + const payload: Record = { + name, + campaign_schedule: campaignSchedule, + }; + + // Add optional fields if provided + if (pl_value !== undefined) { + payload['pl_value'] = pl_value; + } + + if (is_evergreen !== undefined) { + payload['is_evergreen'] = is_evergreen; + } + + if (email_gap !== undefined) { + payload['email_gap'] = email_gap; + } + + if (random_wait_max !== undefined) { + payload['random_wait_max'] = random_wait_max; + } + + if (text_only !== undefined) { + payload['text_only'] = text_only; + } + + if (daily_limit !== undefined) { + payload['daily_limit'] = daily_limit; + } + + if (stop_on_reply !== undefined) { + payload['stop_on_reply'] = stop_on_reply; + } + + if (link_tracking !== undefined) { + payload['link_tracking'] = link_tracking; + } + + if (open_tracking !== undefined) { + payload['open_tracking'] = open_tracking; + } + + if (stop_on_auto_reply !== undefined) { + payload['stop_on_auto_reply'] = stop_on_auto_reply; + } + + if (daily_max_leads !== undefined) { + payload['daily_max_leads'] = daily_max_leads; + } + + if (prioritize_new_leads !== undefined) { + payload['prioritize_new_leads'] = prioritize_new_leads; + } + + if (match_lead_esp !== undefined) { + payload['match_lead_esp'] = match_lead_esp; + } + + if (stop_for_company !== undefined) { + payload['stop_for_company'] = stop_for_company; + } + + if (insert_unsubscribe_header !== undefined) { + payload['insert_unsubscribe_header'] = insert_unsubscribe_header; + } + + if (allow_risky_contacts !== undefined) { + payload['allow_risky_contacts'] = allow_risky_contacts; + } + + if (disable_bounce_protect !== undefined) { + payload['disable_bounce_protect'] = disable_bounce_protect; + } + + return await makeRequest({ + endpoint: 'campaigns', + method: HttpMethod.POST, + apiKey: apiKey as string, + body: payload, + }); + }, +}); diff --git a/packages/pieces/community/instantly-ai/src/lib/actions/create-lead-list.ts b/packages/pieces/community/instantly-ai/src/lib/actions/create-lead-list.ts new file mode 100644 index 0000000..e19c624 --- /dev/null +++ b/packages/pieces/community/instantly-ai/src/lib/actions/create-lead-list.ts @@ -0,0 +1,45 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common/client'; +import { instantlyAiAuth } from '../../index'; + +export const createLeadListAction = createAction({ + auth: instantlyAiAuth, + name: 'create_lead_list', + displayName: 'Create Lead List', + description: 'Creates a new lead list.', + props: { + name: Property.ShortText({ + displayName: 'List Name', + required: true, + }), + has_enrichment_task: Property.Checkbox({ + displayName: 'Enable Enrichment', + description: 'Whether this list runs the enrichment process on every added lead or not.', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { + name, + has_enrichment_task, + } = context.propsValue; + const { auth: apiKey } = context; + + const payload: Record = { + name, + }; + + if (has_enrichment_task !== undefined) { + payload['has_enrichment_task'] = has_enrichment_task; + } + + return await makeRequest({ + endpoint: 'lead-lists', + method: HttpMethod.POST, + apiKey: apiKey as string, + body: payload, + }); + }, +}); diff --git a/packages/pieces/community/instantly-ai/src/lib/actions/search-campaigns.ts b/packages/pieces/community/instantly-ai/src/lib/actions/search-campaigns.ts new file mode 100644 index 0000000..4b2d648 --- /dev/null +++ b/packages/pieces/community/instantly-ai/src/lib/actions/search-campaigns.ts @@ -0,0 +1,53 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, QueryParams } from '@activepieces/pieces-common'; +import { makeRequest } from '../common/client'; +import { instantlyAiAuth } from '../../index'; + +export const searchCampaignsAction = createAction({ + auth: instantlyAiAuth, + name: 'search_campaigns', + displayName: 'Search Campaigns', + description: 'Searchs for campaigns using various filters.', + props: { + name: Property.ShortText({ + displayName: 'Campaign Name', + required: true, + }), + }, + async run(context) { + const { name } = context.propsValue; + const { auth: apiKey } = context; + + const result = []; + + let startingAfter: string | undefined = undefined; + let hasMore = true; + + do { + const qs: QueryParams = { + limit: '100', + search: name, + }; + + if (startingAfter) qs['starting_after'] = startingAfter; + + const response = (await makeRequest({ + endpoint: 'campaigns', + method: HttpMethod.GET, + apiKey, + queryParams: qs, + })) as { next_starting_after?: string; items: Record[] }; + + const items = response.items || []; + result.push(...items); + + startingAfter = response.next_starting_after; + hasMore = !!startingAfter && items.length > 0; + } while (hasMore); + + return { + found: result.length > 0, + result, + }; + }, +}); diff --git a/packages/pieces/community/instantly-ai/src/lib/actions/search-leads.ts b/packages/pieces/community/instantly-ai/src/lib/actions/search-leads.ts new file mode 100644 index 0000000..78409d7 --- /dev/null +++ b/packages/pieces/community/instantly-ai/src/lib/actions/search-leads.ts @@ -0,0 +1,58 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { instantlyAiAuth } from '../../index'; +import { makeRequest } from '../common/client'; +import { HttpMethod, QueryParams } from '@activepieces/pieces-common'; +import { campaignId, listId } from '../common/props'; + +export const searchLeadsAction = createAction({ + auth: instantlyAiAuth, + name: 'search_leads', + displayName: 'Search Leads', + description: 'Search for leads in Instantly by name or email.', + props: { + search: Property.ShortText({ + displayName: 'Search', + description: 'Search string to find leads - can be First Name, Last Name, or Email (e.g. "John Doe").', + required: true, + }), + campaign_id: campaignId(false), + list_id: listId(false) + }, + async run(context) { + const {search,campaign_id,list_id} = context.propsValue; + + const result = []; + + let startingAfter: string | undefined = undefined; + let hasMore = true; + + do { + const body:Record = { + limit: '100', + search, + }; + + if(campaign_id) body['campaign'] = campaign_id; + if(list_id) body['list_id'] = list_id; + if (startingAfter) body['starting_after'] = startingAfter; + + const response = (await makeRequest({ + endpoint: 'leads/list', + method: HttpMethod.POST, + apiKey:context.auth, + body, + })) as { next_starting_after?: string; items: Record[] }; + + const items = response.items || []; + result.push(...items); + + startingAfter = response.next_starting_after; + hasMore = !!startingAfter && items.length > 0; + } while (hasMore); + + return { + found: result.length > 0, + result, + }; + }, +}); diff --git a/packages/pieces/community/instantly-ai/src/lib/common/client.ts b/packages/pieces/community/instantly-ai/src/lib/common/client.ts new file mode 100644 index 0000000..0c8ce56 --- /dev/null +++ b/packages/pieces/community/instantly-ai/src/lib/common/client.ts @@ -0,0 +1,46 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +export const BASE_URL = 'https://api.instantly.ai/api/v2'; + +export async function makeRequest({ + endpoint, + method, + apiKey, + body, + queryParams = {}, +}: { + endpoint: string; + method: HttpMethod; + apiKey: string; + body?: Record; + queryParams?: Record; +}) { + const url = `${BASE_URL}/${endpoint}`; + + const queryString = Object.entries(queryParams) + .filter(([_, value]) => value !== undefined && value !== null) + .map(([key, value]) => `${key}=${encodeURIComponent(String(value))}`) + .join('&'); + + const finalUrl = queryString ? `${url}?${queryString}` : url; + + const response = await httpClient.sendRequest({ + method, + url: finalUrl, + headers: { + 'Content-Type': 'application/json', + 'authorization': `Bearer ${apiKey}`, + }, + body, + }); + + if (response.status < 200 || response.status >= 300) { + throw new Error( + `Instantly API returned an error: ${response.status} ${ + response.body?.message || JSON.stringify(response.body) + }` + ); + } + + return response.body; +} diff --git a/packages/pieces/community/instantly-ai/src/lib/common/props.ts b/packages/pieces/community/instantly-ai/src/lib/common/props.ts new file mode 100644 index 0000000..0a6a761 --- /dev/null +++ b/packages/pieces/community/instantly-ai/src/lib/common/props.ts @@ -0,0 +1,161 @@ +import { HttpMethod, QueryParams } from '@activepieces/pieces-common'; +import { DropdownOption, Property } from '@activepieces/pieces-framework'; +import { makeRequest } from './client'; + +export const listId = (required = true) => + Property.Dropdown({ + displayName: 'List', + refreshers: [], + required, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + const options: DropdownOption[] = []; + + let startingAfter: string | undefined = undefined; + let hasMore = true; + + do { + const qs: QueryParams = { + limit: '100', + }; + + if (startingAfter) qs['starting_after'] = startingAfter; + + const response = (await makeRequest({ + endpoint: 'lead-lists', + method: HttpMethod.GET, + apiKey: auth as string, + queryParams: qs, + })) as { + next_starting_after?: string; + items: { id: string; name: string }[]; + }; + + const items = response.items || []; + for (const item of items) { + options.push({ label: item.name, value: item.id }); + } + + startingAfter = response.next_starting_after; + hasMore = !!startingAfter && items.length > 0; + } while (hasMore); + + return { + disabled: false, + options, + }; + }, + }); + +export const campaignId = (required = true) => + Property.Dropdown({ + displayName: 'Campaign', + refreshers: [], + required, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + const options: DropdownOption[] = []; + + let startingAfter: string | undefined = undefined; + let hasMore = true; + + do { + const qs: QueryParams = { + limit: '100', + }; + + if (startingAfter) qs['starting_after'] = startingAfter; + + const response = (await makeRequest({ + endpoint: 'campaigns', + method: HttpMethod.GET, + apiKey: auth as string, + queryParams: qs, + })) as { + next_starting_after?: string; + items: { id: string; name: string }[]; + }; + + const items = response.items || []; + for (const item of items) { + options.push({ label: item.name, value: item.id }); + } + + startingAfter = response.next_starting_after; + hasMore = !!startingAfter && items.length > 0; + } while (hasMore); + + return { + disabled: false, + options, + }; + }, + }); + +export const leadId = (required = true) => + Property.Dropdown({ + displayName: 'Lead', + refreshers: [], + required, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + const options: DropdownOption[] = []; + + let startingAfter: string | undefined = undefined; + let hasMore = true; + + do { + const body:Record = { + limit: 100, + }; + + if (startingAfter) body['starting_after'] = startingAfter; + + const response = (await makeRequest({ + endpoint: 'leads/list', + method: HttpMethod.POST, + apiKey: auth as string, + body + })) as { + next_starting_after?: string; + items: { id: string; email: string ,first_name:string,last_name:string}[]; + }; + + + const items = response.items || []; + for (const item of items) { + options.push({ label: `${item.first_name} ${item.last_name}`, value: item.id }); + } + + startingAfter = response.next_starting_after; + hasMore = !!startingAfter && items.length > 0; + } while (hasMore); + + return { + disabled: false, + options, + }; + }, + }); + diff --git a/packages/pieces/community/instantly-ai/src/lib/triggers/campaign-status-changed.ts b/packages/pieces/community/instantly-ai/src/lib/triggers/campaign-status-changed.ts new file mode 100644 index 0000000..1535d2e --- /dev/null +++ b/packages/pieces/community/instantly-ai/src/lib/triggers/campaign-status-changed.ts @@ -0,0 +1,43 @@ +import { createTrigger, Property, TriggerStrategy } from '@activepieces/pieces-framework'; +import { instantlyAiAuth } from '../../index'; + +export const campaignStatusChangedTrigger = createTrigger({ + auth: instantlyAiAuth, + name: 'campaign_status_changed', + displayName: 'Campaign Status Changed', + description: 'Triggers when a campaign status changes (completed, paused, etc.).', + props: { + md: Property.MarkDown({ + value: ` + To use this trigger, manually set up a webhook in Instantly.ai: + + 1. Go to Instantly settings. + 2. Navigate to Integrations tab and find webhooks. + 3. Click "Add Webhook". + 4. Enter the webhook URL provided below: + \`\`\`text + {{webhookUrl}} + \`\`\` + 5. Select the campaign and event type "Campaign Completed". + 6. Click "Add Webhook". + `, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: { + timestamp: "2023-08-22T15:45:30.123Z", + event_type: "campaign_completed", + campaign_name: "Product Demo Campaign", + workspace: "workspace_123456", + campaign_id: "campaign_789012" + }, + async onEnable(context) { + // Empty + }, + async onDisable(context) { + // Empty + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/instantly-ai/src/lib/triggers/new-lead-added.ts b/packages/pieces/community/instantly-ai/src/lib/triggers/new-lead-added.ts new file mode 100644 index 0000000..3456313 --- /dev/null +++ b/packages/pieces/community/instantly-ai/src/lib/triggers/new-lead-added.ts @@ -0,0 +1,112 @@ +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { instantlyAiAuth } from '../../index'; +import { + DedupeStrategy, + HttpMethod, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { makeRequest } from '../common/client'; +import dayjs from 'dayjs'; +const polling: Polling< + PiecePropValueSchema, + Record +> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, lastFetchEpochMS }) { + const result = []; + const isTest = lastFetchEpochMS === 0; + let startingAfter: string | undefined = undefined; + let hasMore = true; + + do { + const body: Record = { + limit: isTest ? 10 : 100, + }; + + if (startingAfter) body['starting_after'] = startingAfter; + + const response = (await makeRequest({ + endpoint: 'leads/list', + method: HttpMethod.POST, + apiKey: auth, + body, + })) as { + next_starting_after?: string; + items: { timestamp_created: string }[]; + }; + + const items = response.items || []; + result.push(...items); + + if (isTest) break; + + startingAfter = response.next_starting_after; + hasMore = !!startingAfter && items.length > 0; + } while (hasMore); + + return result.map((lead) => { + return { + epochMilliSeconds: dayjs(lead.timestamp_created).valueOf(), + data: lead, + }; + }); + }, +}; + +export const newLeadAddedTrigger = createTrigger({ + auth: instantlyAiAuth, + name: 'new_lead_added', + displayName: 'New Lead Added', + description: 'Triggers when a new lead is added to a campaign', + props: {}, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + id: 'd1f61dbc-bcb2-44fb-86b8-3d01c8701fe9', + timestamp_created: '2025-05-25T12:50:04.748Z', + timestamp_updated: '2025-05-25T13:00:52.019Z', + organization: '31ef9f6c-00f0-481f-b309-95694ed324bb', + status: 1, + email_open_count: 0, + email_reply_count: 0, + email_click_count: 0, + company_domain: 'test@gmail.com', + status_summary: {}, + campaign: 'd228fc8f-44f2-42f3-b63f-3667dafc24cf', + email: 'test@gmail.com', + payload: { + email: 'test@gmail.com', + lastTouch: null, + leadOwner: 'Test', + leadSource: 'manual', + }, + uploaded_by_user: '7f74fadd-b96b-4011-a1da-9b81a5bed165', + upload_method: 'manual', + assigned_to: '7f74fadd-b96b-4011-a1da-9b81a5bed165', + esp_code: 1, + }, +}); diff --git a/packages/pieces/community/instantly-ai/tsconfig.json b/packages/pieces/community/instantly-ai/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/instantly-ai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/instantly-ai/tsconfig.lib.json b/packages/pieces/community/instantly-ai/tsconfig.lib.json new file mode 100644 index 0000000..baafa55 --- /dev/null +++ b/packages/pieces/community/instantly-ai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} \ No newline at end of file diff --git a/packages/pieces/community/instasent/.eslintrc.json b/packages/pieces/community/instasent/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/instasent/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/instasent/README.md b/packages/pieces/community/instasent/README.md new file mode 100644 index 0000000..4518ce5 --- /dev/null +++ b/packages/pieces/community/instasent/README.md @@ -0,0 +1,52 @@ +# Instasent + +## Description +Instasent piece for ActivePieces. Enables integration with Instasent's Ingest API for managing contacts and tracking events in your data source. This API allows organizations to consolidate customer data and track interactions, enabling advanced audience segmentation and personalized marketing. + +### Key Features +- **Contact Management**: Create, update, and delete contacts in your audience +- **Event Tracking**: Record customer interactions and behaviors +- **Data Consolidation**: Unify customer data from various sources +- **Automatic Attribution**: Track which marketing campaigns lead to specific events + +## Authentication +This piece requires: +- **Project ID**: Your Instasent project identifier +- **Datasource ID**: Your data source identifier within the project +- **API Key**: Bearer token for authentication (specific to the data source) + +To generate your authentication data go to your project and add an ActivePieces data source, if it's not available you can create an Instasent API data source instead. + +## Actions + +### 1. Add or Update Contact +Add new contacts or update existing ones in your data source. Supports various contact attributes: +- Basic info (name, email, phone) +- Demographics (country, language, gender) +- Marketing preferences +- Custom attributes + +### 2. Delete Contact +Remove a contact from your data source. Contacts will be permanently destroyed and removed from your audience. + +### 3. Add Event +Record contact events such as: +- E-commerce actions (orders, checkouts) +- User behaviour (views, form submissions...) +- Appointments or reservations +- Payments and refunds +- Metrics +- Custom events + +Events support attribution parameters to track which marketing campaigns led to specific actions. + +## Links +- [Instasent Website](https://instasent.com) +- [Ingest API Docs](https://app.swaggerhub.com/apis-docs/Instasent/instasent-ingest_api/) +- [Instasent APIs](https://docs.instasent.com) + +## Support +For support or questions about the API, contact: +- Email: dev@instasent.com +- Website: https://instasent.com +- Customer support: https://help.instasent.com diff --git a/packages/pieces/community/instasent/package.json b/packages/pieces/community/instasent/package.json new file mode 100644 index 0000000..289d8e2 --- /dev/null +++ b/packages/pieces/community/instasent/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-instasent", + "version": "0.0.3" +} diff --git a/packages/pieces/community/instasent/project.json b/packages/pieces/community/instasent/project.json new file mode 100644 index 0000000..e911958 --- /dev/null +++ b/packages/pieces/community/instasent/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-instasent", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/instasent/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/instasent", + "tsConfig": "packages/pieces/community/instasent/tsconfig.lib.json", + "packageJson": "packages/pieces/community/instasent/package.json", + "main": "packages/pieces/community/instasent/src/index.ts", + "assets": [ + "packages/pieces/community/instasent/*.md", + { + "input": "packages/pieces/community/instasent/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/instasent/src/index.ts b/packages/pieces/community/instasent/src/index.ts new file mode 100644 index 0000000..a0fcb54 --- /dev/null +++ b/packages/pieces/community/instasent/src/index.ts @@ -0,0 +1,84 @@ +import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { deleteContact } from './lib/actions/delete-contact'; +import { addOrUpdateContact } from './lib/actions/add-or-update-contact'; +import { createEvent } from "./lib/actions/create-event"; +import { BASE_URL } from "./lib/common/constants"; +import { InstasentAuthType } from './lib/common/types'; +import { PieceCategory } from "@activepieces/shared"; + +export const getBaseUrl = (auth: { projectId: string, datasourceId: string }) => { + return `${BASE_URL}/project/${auth.projectId}/datasource/${auth.datasourceId}`; +}; + +const authDescriptionMarkdown = ` +## Obtain your auth data +1. Go to https://dashboard.instasent.com +2. Access to your project +3. Create an Activepieces data source +4. Copy the auth parameters and paste them in the fields below +`; + +export const instasentAuth = PieceAuth.CustomAuth({ + description: authDescriptionMarkdown, + props: { + projectId: PieceAuth.SecretText({ + displayName: 'Project ID', + description: 'Your Instasent Project ID', + required: true, + }), + datasourceId: PieceAuth.SecretText({ + displayName: 'Datasource ID', + description: 'Your Instasent Datasource ID', + required: true, + }), + apiKey: PieceAuth.SecretText({ + displayName: 'API Key', + description: 'Your Instasent API Bearer Token', + required: true, + }) + }, + validate: async ({ auth }) => { + const authData = auth as InstasentAuthType; + + try { + const baseUrl = getBaseUrl(authData); + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${baseUrl}/stream`, + headers: { + 'Authorization': `Bearer ${auth.apiKey}` + } + }); + + const data = response.body; + if (!data.organization || !data.stream || !data.datasource || !data.project) { + return { + valid: false, + error: 'Invalid API response structure' + }; + } + + return { + valid: true + }; + } catch (error: any) { + return { + valid: false, + error: error.response?.data?.message || 'Invalid credentials or connection error' + }; + } + }, + required: true +}); + +export const instasent = createPiece({ + displayName: "Instasent", + minimumSupportedRelease: '0.30.0', + logoUrl: "https://cdn.activepieces.com/pieces/instasent.jpg", + categories:[PieceCategory.MARKETING], + authors: ["dev-instasent", "https://github.com/dev-instasent"], + auth: instasentAuth, + actions: [addOrUpdateContact, deleteContact, createEvent], + triggers: [], +}); diff --git a/packages/pieces/community/instasent/src/lib/actions/add-or-update-contact.ts b/packages/pieces/community/instasent/src/lib/actions/add-or-update-contact.ts new file mode 100644 index 0000000..8544777 --- /dev/null +++ b/packages/pieces/community/instasent/src/lib/actions/add-or-update-contact.ts @@ -0,0 +1,124 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { getBaseUrl } from '../../index'; +import { BOOLEAN_OPTIONS, IGNORED_ATTRIBUTES, LONG_TEXT_TYPES } from '../common/constants'; +import { InstasentAuthType } from '../common/types'; + +const PROPERTY_ITERATIONS = [ + { check: (spec: any) => spec.requiredInWebhook }, + { check: (spec: any) => spec.important }, + { check: (spec: any) => spec.visible !== false }, + { check: (spec: any) => !spec.custom }, + { check: () => true } +]; + +export const addOrUpdateContact = createAction({ + name: 'add_or_update_contact', + displayName: 'Add/Update contact', + description: 'Add or update a single contact', + + props: { + contact: Property.DynamicProperties({ + displayName: 'Contact Properties', + description: 'Enter the contact properties, the User ID is mandatory', + required: true, + refreshers: ['authentication'], + props: async ({ auth }) => { + const authData = auth as InstasentAuthType; + const baseUrl = getBaseUrl({ + projectId: authData.projectId, + datasourceId: authData.datasourceId + }); + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${baseUrl}/stream/specs/attributes`, + headers: { + 'Authorization': `Bearer ${authData.apiKey}` + } + }); + + const properties: Record = {}; + + for (const iteration of PROPERTY_ITERATIONS) { + for (const spec of response.body.specs) { + // Ignored properties + if (spec.readOnly || properties[spec.uid] || IGNORED_ATTRIBUTES.includes(spec.uid)) { + continue; + } + + // Multiple iterations to sort the properties based on their attributes + if (!iteration.check(spec)) { + continue; + } + + const displayLabel = spec.displayLabel; + let description = spec.description; + if (spec.dataType === 'date') { + description += ' (ISO 8601 format YYYY-MM-DD)'; + } + if (spec.multivalue > 1) { + properties[spec.uid] = Property.Array({ + displayName: displayLabel, + description: `${description} (Max ${spec.multivalue} values)`, + required: spec.requiredInWebhook + }); + } else if (spec.dataType === 'bool') { + properties[spec.uid] = Property.StaticDropdown({ + displayName: displayLabel, + description: `${description} [0=false|1=true|null=unknown]`, + required: spec.requiredInWebhook, + options: { + options: BOOLEAN_OPTIONS + } + }); + } else { + const PropType = LONG_TEXT_TYPES.includes(spec.dataType) ? Property.LongText : Property.ShortText; + + properties[spec.uid] = PropType({ + displayName: displayLabel, + description: description, + required: spec.requiredInWebhook + }); + } + } + } + + return properties; + } catch (error) { + throw new Error('Failed to load contact properties'); + } + } + }), + instant: Property.Checkbox({ + displayName: 'Instant', + description: 'Process contact immediately instead of queuing. Only enable this when you need to add an event for this contact in the next step. Not recommended for high-volume operations.', + required: false, + defaultValue: false + }) + }, + + async run(context) { + const contact = context.propsValue.contact; + const instant = context.propsValue.instant; + const auth = context.auth as InstasentAuthType; + const baseUrl = getBaseUrl({ projectId: auth.projectId, datasourceId: auth.datasourceId }); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${baseUrl}/stream/contacts${instant ? '?_sync' : ''}`, + headers: { + 'Authorization': `Bearer ${auth.apiKey}`, + 'Content-Type': 'application/json' + }, + body: [contact] + }); + + if (response.body.entitiesSuccess !== 1) { + throw new Error(`Failed to add or update contact: ${JSON.stringify(response.body.errors)}`); + } + + return response.body; + } +}); diff --git a/packages/pieces/community/instasent/src/lib/actions/create-event.ts b/packages/pieces/community/instasent/src/lib/actions/create-event.ts new file mode 100644 index 0000000..6dea447 --- /dev/null +++ b/packages/pieces/community/instasent/src/lib/actions/create-event.ts @@ -0,0 +1,153 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { getBaseUrl, instasentAuth } from '../..'; +import { ApiResponse, EventParameter, EventSpec, InstasentAuthType } from '../common/types'; +import { BOOLEAN_OPTIONS } from '../common/constants'; + +export const createEvent = createAction({ + name: 'add_event', + displayName: 'Add Event', + description: 'Add a contact event', + auth: instasentAuth, + props: { + user_id: Property.ShortText({ + displayName: 'User ID', + description: 'Unique identifier of the user', + required: true + }), + event_id: Property.ShortText({ + displayName: 'Event ID', + description: 'Unique identifier for this event. Used for deduplication.', + required: true + }), + event_date: Property.ShortText({ + displayName: 'Event Date', + description: 'Date and time when the event occurred, will default to now (ISO 8601 format YYYY-MM-DDTHH:MM:SS.SSSZ)', + required: false + }), + event_type: Property.Dropdown({ + displayName: 'Event Type', + description: 'Select the type of event to create', + required: true, + refreshers: [], + options: async ({ auth }) => { + const authData = auth as InstasentAuthType; + const baseUrl = getBaseUrl({ + projectId: authData.projectId, + datasourceId: authData.datasourceId + }); + + const response = await httpClient.sendRequest<{ specs: EventSpec[] }>({ + method: HttpMethod.GET, + url: `${baseUrl}/stream/specs/events`, + headers: { + 'Authorization': `Bearer ${authData.apiKey}` + } + }); + + return { + options: response.body.specs.map(spec => ({ + label: `${spec.emoji} ${spec.name}`, + value: spec.uid + })) + }; + } + }), + event_parameters: Property.DynamicProperties({ + displayName: 'Event Parameters', + description: 'Parameters for the selected event type', + required: true, + refreshers: ['event_type'], + props: async ({ auth, event_type }) => { + if (!auth || !event_type) return {}; + const authData = auth as InstasentAuthType; + const baseUrl = getBaseUrl({ + projectId: authData.projectId, + datasourceId: authData.datasourceId + }); + + const response = await httpClient.sendRequest<{ specs: EventParameter[] }>({ + method: HttpMethod.GET, + url: `${baseUrl}/stream/specs/event-parameters/${event_type}`, + headers: { + 'Authorization': `Bearer ${authData.apiKey}` + } + }); + + const props: Record = {}; + + response.body.specs.forEach(param => { + if (param.multiValue > 1) { + props[param.parameter] = Property.Array({ + displayName: param.title, + description: `${param.description} (Max ${param.multiValue} values)`, + required: param.required + }); + } else { + // Convert API parameter specs to ActivePieces properties + switch (param.dataType) { + case 'bool': + props[param.parameter] = Property.StaticDropdown({ + displayName: param.title, + description: `${param.description} [0=false|1=true|null=unknown]`, + required: param.required, + options: { + options: BOOLEAN_OPTIONS + } + }) + break; + case "string": + case "payload": + props[param.parameter] = Property.LongText({ + displayName: param.title, + description: param.description, + required: param.required + }); + break; + default: + props[param.parameter] = Property.ShortText({ + displayName: param.title, + description: param.description, + required: param.required + }); + break; + } + } + }); + + return props; + } + }) + }, + async run({ auth, propsValue }) { + const authData = auth as InstasentAuthType; + const baseUrl = getBaseUrl({ + projectId: authData.projectId, + datasourceId: authData.datasourceId + }); + + const eventData = { + _user_id: propsValue.user_id, + _event_id: propsValue.event_id, + _event_type: propsValue.event_type, + _event_date: propsValue.event_date, + _event_parameters: propsValue.event_parameters + }; + + const response = await httpClient.sendRequest>({ + method: HttpMethod.POST, + url: `${baseUrl}/stream/events`, + headers: { + 'Authorization': `Bearer ${authData.apiKey}`, + 'Content-Type': 'application/json' + }, + body: [eventData] + }); + + if (response.body.entitiesSuccess !== 1) { + throw new Error(`Failed to create event: ${JSON.stringify(response.body.errors)}`); + } + + return response.body; + } +}); diff --git a/packages/pieces/community/instasent/src/lib/actions/delete-contact.ts b/packages/pieces/community/instasent/src/lib/actions/delete-contact.ts new file mode 100644 index 0000000..6c3429a --- /dev/null +++ b/packages/pieces/community/instasent/src/lib/actions/delete-contact.ts @@ -0,0 +1,34 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { getBaseUrl } from '../../index'; +import { InstasentAuthType } from '../common/types'; + +export const deleteContact = createAction({ + name: 'delete_contact', + displayName: 'Delete Contact', + description: 'Delete a single contact by User ID', + + props: { + userId: Property.ShortText({ + displayName: 'User ID', + description: 'Unique identifier of the contact to delete', + required: true + }) + }, + + async run(context) { + const { userId } = context.propsValue; + const auth = context.auth as InstasentAuthType; + const baseUrl = getBaseUrl({ projectId: auth.projectId, datasourceId: auth.datasourceId }); + + const response = await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: `${baseUrl}/stream/contacts/${userId}`, + headers: { + 'Authorization': `Bearer ${auth.apiKey}` + } + }); + + return response.body; + } +}); diff --git a/packages/pieces/community/instasent/src/lib/common/constants.ts b/packages/pieces/community/instasent/src/lib/common/constants.ts new file mode 100644 index 0000000..81639d5 --- /dev/null +++ b/packages/pieces/community/instasent/src/lib/common/constants.ts @@ -0,0 +1,11 @@ +export const IGNORED_ATTRIBUTES = ['_email_verified', '_phone_mobile_verified']; + +export const BASE_URL = 'https://api.instasent.com/v1'; + +export const BOOLEAN_OPTIONS = [ + { label: 'True', value: '1' }, + { label: 'False', value: '0' }, + { label: 'Unknown / Not set', value: null } +]; + +export const LONG_TEXT_TYPES = ['text', 'payload', 'string']; diff --git a/packages/pieces/community/instasent/src/lib/common/types.ts b/packages/pieces/community/instasent/src/lib/common/types.ts new file mode 100644 index 0000000..e33ab9f --- /dev/null +++ b/packages/pieces/community/instasent/src/lib/common/types.ts @@ -0,0 +1,55 @@ +export interface InstasentAuthType { + apiKey: string; + projectId: string; + datasourceId: string; +} + +export interface EventSpec { + uid: string; + name: string; + description: string; + category: string; + attribution: boolean; + automation: boolean; + icon: string; + emoji: string; + important: boolean; +} + +export interface EventParameter { + parameter: string; + title: string; + description: string; + icon: string; + dataType: string; + visualType: string; + maxLength: number; + required: boolean; + multiValue: number; +} + +export interface ContactAttributeSpec { + uid: string; + displayLabel: string; + description: string; + dataType: string; + multivalue: number; + readOnly: boolean; + requiredInWebhook: boolean; + important: boolean; + visible: boolean; + custom: boolean; +} + +export interface ApiResponse { + success: boolean; + entitiesSuccess: number; + streamId: string; + projectId: string; + datasourceId: string; + errors: any[]; + accepted: Array<{ + position: number; + item: T; + }>; +} diff --git a/packages/pieces/community/instasent/tsconfig.json b/packages/pieces/community/instasent/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/instasent/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/instasent/tsconfig.lib.json b/packages/pieces/community/instasent/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/instasent/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/intercom/.eslintrc.json b/packages/pieces/community/intercom/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/intercom/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/intercom/README.md b/packages/pieces/community/intercom/README.md new file mode 100644 index 0000000..d3d14a5 --- /dev/null +++ b/packages/pieces/community/intercom/README.md @@ -0,0 +1,7 @@ +# pieces-intercom + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-intercom` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/intercom/package.json b/packages/pieces/community/intercom/package.json new file mode 100644 index 0000000..0d75db4 --- /dev/null +++ b/packages/pieces/community/intercom/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-intercom", + "version": "0.5.3" +} diff --git a/packages/pieces/community/intercom/project.json b/packages/pieces/community/intercom/project.json new file mode 100644 index 0000000..5e9abd1 --- /dev/null +++ b/packages/pieces/community/intercom/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-intercom", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/intercom/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/intercom", + "tsConfig": "packages/pieces/community/intercom/tsconfig.lib.json", + "packageJson": "packages/pieces/community/intercom/package.json", + "main": "packages/pieces/community/intercom/src/index.ts", + "assets": [ + "packages/pieces/community/intercom/*.md", + { + "input": "packages/pieces/community/intercom/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/intercom/src/index.ts b/packages/pieces/community/intercom/src/index.ts new file mode 100644 index 0000000..fd7bb79 --- /dev/null +++ b/packages/pieces/community/intercom/src/index.ts @@ -0,0 +1,181 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { sendMessageAction } from './lib/actions/send-message.action'; +import crypto from 'node:crypto'; +import { noteAddedToConversation } from './lib/triggers/note-added-to-conversation'; +import { addNoteToConversationAction } from './lib/actions/add-note-to-conversation'; +import { replyToConversation } from './lib/actions/reply-to-conversation'; +import { newConversationFromUser } from './lib/triggers/new-conversation-from-user'; +import { replyFromUser } from './lib/triggers/reply-from-user'; +import { replyFromAdmin } from './lib/triggers/reply-from-admin'; +import { conversationAssigned } from './lib/triggers/conversation-assigned'; +import { conversationClosedTrigger } from './lib/triggers/conversation-closed'; +import { conversationSnoozed } from './lib/triggers/conversation-snoozed'; +import { conversationUnsnoozed } from './lib/triggers/conversation-unsnoozed'; +import { conversationRated } from './lib/triggers/conversation-rated'; +import { conversationPartTagged } from './lib/triggers/conversation-part-tagged'; +import { findConversationAction } from './lib/actions/find-conversation'; +import { addNoteToUserAction } from './lib/actions/add-note-to-user'; +import { findUserAction } from './lib/actions/find-user'; +import { findLeadAction } from './lib/actions/find-lead'; +import { addOrRemoveTagOnConversationAction } from './lib/actions/add-remove-tag-on-conversation'; +import { addOrRemoveTagOnCompanyAction } from './lib/actions/add-remove-tag-on-company'; +import { createUserAction } from './lib/actions/create-user'; +import { createOrUpdateUserAction } from './lib/actions/create-update-user'; +import { listAllTagsAction } from './lib/actions/list-all-tags'; +import { newLeadTrigger } from './lib/triggers/new-lead'; +import { newCompanyTrigger } from './lib/triggers/new-company'; +import { addOrRemoveTagOnContactAction } from './lib/actions/add-remove-tag-on-contact'; +import { createArticleAction } from './lib/actions/create-article'; +import { createConversationAction } from './lib/actions/create-conversation'; +import { getConversationAction } from './lib/actions/get-conversation'; +import { createOrUpdateLeadAction } from './lib/actions/create-update-lead'; +import { createTicketAction } from './lib/actions/create-ticket'; +import { updateTicketAction } from './lib/actions/update-ticket'; +import { findCompanyAction } from './lib/actions/find-company'; +import { leadAddedEmailTrigger } from './lib/triggers/lead-added-email'; +import { newTicketTrigger } from './lib/triggers/new-ticket'; +import { tagAddedToLeadTrigger } from './lib/triggers/tag-added-to-lead'; +import { contactRepliedTrigger } from './lib/triggers/contact-replied'; +import { leadConvertedToUserTrigger } from './lib/triggers/lead-converted-to-user'; +import { newUserTrigger } from './lib/triggers/new-user'; +import { tagAddedToUserTrigger } from './lib/triggers/tag-added-to-user'; +import { contactUpdatedTrigger } from './lib/triggers/contact-updated'; + +const description = ` +Please follow the instructions to create Intercom Oauth2 app. + +1.Log in to your Intercom account and navigate to **Settings > Integrations > Developer Hub**. +2.Click on **Create a new app** and select the appropriate workspace. +3.In **Authentication** section, add Redirect URL. +4.In **Webhooks** section, select the events you want to receive. +5.Go to the **Basic Information** section and copy the Client ID and Client Secret. +`; + +export const intercomAuth = PieceAuth.OAuth2({ + authUrl: 'https://app.{region}.com/oauth', + tokenUrl: 'https://api.{region}.io/auth/eagle/token', + required: true, + description, + scope: [], + props: { + region: Property.StaticDropdown({ + displayName: 'Region', + required: true, + options: { + options: [ + { label: 'US', value: 'intercom' }, + { label: 'EU', value: 'eu.intercom' }, + { label: 'AU', value: 'au.intercom' }, + ], + }, + }), + }, +}); + +export const intercom = createPiece({ + displayName: 'Intercom', + description: 'Customer messaging platform for sales, marketing, and support', + minimumSupportedRelease: '0.29.0', // introduction of new intercom APP_WEBHOOK + logoUrl: 'https://cdn.activepieces.com/pieces/intercom.png', + categories: [PieceCategory.CUSTOMER_SUPPORT], + auth: intercomAuth, + authors: [ + 'kishanprmr', + 'MoShizzle', + 'AbdulTheActivePiecer', + 'khaledmashaly', + 'abuaboud', + 'AdamSelene', + ], + actions: [ + addNoteToUserAction, + addNoteToConversationAction, + addOrRemoveTagOnContactAction, + addOrRemoveTagOnCompanyAction, + addOrRemoveTagOnConversationAction, + createArticleAction, + createConversationAction, + createTicketAction, + createUserAction, + createOrUpdateLeadAction, + createOrUpdateUserAction, + replyToConversation, + sendMessageAction, + updateTicketAction, + findCompanyAction, + findConversationAction, + findLeadAction, + findUserAction, + listAllTagsAction, + getConversationAction, + createCustomApiCallAction({ + baseUrl: (auth) => `https://api.${(auth as OAuth2PropertyValue).props?.['region']}.io`, + auth: intercomAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [ + contactRepliedTrigger, + leadAddedEmailTrigger, + leadConvertedToUserTrigger, + conversationClosedTrigger, + conversationAssigned, + conversationSnoozed, + conversationUnsnoozed, + newCompanyTrigger, + newConversationFromUser, + conversationRated, + newLeadTrigger, + newTicketTrigger, + newUserTrigger, + conversationPartTagged, + tagAddedToLeadTrigger, + tagAddedToUserTrigger, + contactUpdatedTrigger, + replyFromUser, + replyFromAdmin, + noteAddedToConversation, + ], + events: { + parseAndReply: ({ payload }) => { + const payloadBody = payload.body as PayloadBody; + return { + event: payloadBody.topic, + identifierValue: payloadBody.app_id, + }; + }, + verify: ({ payload, webhookSecret }) => { + const signature = payload.headers['x-hub-signature']; + let hmac: crypto.Hmac; + if (typeof webhookSecret === 'string') { + hmac = crypto.createHmac('sha1', webhookSecret); + } else { + const app_id = (payload.body as PayloadBody).app_id; + const webhookSecrets = webhookSecret as Record; + if (!(app_id in webhookSecrets)) { + return false; + } + hmac = crypto.createHmac('sha1', webhookSecrets[app_id]); + } + hmac.update(`${payload.rawBody}`); + const computedSignature = `sha1=${hmac.digest('hex')}`; + return signature === computedSignature; + }, + }, +}); + +type PayloadBody = { + type: string; + topic: string; + id: string; + app_id: string; +}; diff --git a/packages/pieces/community/intercom/src/lib/actions/add-note-to-conversation.ts b/packages/pieces/community/intercom/src/lib/actions/add-note-to-conversation.ts new file mode 100644 index 0000000..cb01cb0 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/add-note-to-conversation.ts @@ -0,0 +1,34 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { intercomAuth } from '../..'; +import { commonProps, intercomClient } from '../common'; +import { conversationIdProp } from '../common/props'; + +export const addNoteToConversationAction = createAction({ + auth: intercomAuth, + name: 'addNoteToConversation', + displayName: 'Add note to conversation', + description: 'Add a note (for other admins) to an existing conversation', + props: { + from: commonProps.admins({ displayName: 'From (Admin)', required: true }), + conversationId:conversationIdProp('Conversation ID', true), + body: Property.ShortText({ + displayName: 'Message Body', + required: true, + }), + }, + async run(context) { + const client = intercomClient(context.auth); + + const response = await client.conversations.reply({ + conversation_id: context.propsValue.conversationId!, + body: { + type: 'admin', + message_type: 'note', + body: context.propsValue.body, + admin_id: context.propsValue.from, + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/actions/add-note-to-user.ts b/packages/pieces/community/intercom/src/lib/actions/add-note-to-user.ts new file mode 100644 index 0000000..0eb9102 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/add-note-to-user.ts @@ -0,0 +1,44 @@ +import { intercomAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { intercomClient } from '../common'; + +export const addNoteToUserAction = createAction({ + auth: intercomAuth, + name: 'add-note-to-user', + displayName: 'Add Note', + description: 'Add a note to a user', + props: { + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + body: Property.LongText({ + displayName: 'Note Text', + required: true, + }), + }, + async run(context) { + const client = intercomClient(context.auth); + + const contactResponse = await client.contacts.search({ + query: { + field: 'email', + operator: '=', + value: context.propsValue.email, + }, + }); + + if (contactResponse.data.length === 0) { + throw new Error('Could not find user with this email address.'); + } + + const contactId = contactResponse.data[0].id; + + const noteResponse = await client.notes.create({ + contact_id: contactId, + body: context.propsValue.body, + }); + + return noteResponse; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/actions/add-remove-tag-on-company.ts b/packages/pieces/community/intercom/src/lib/actions/add-remove-tag-on-company.ts new file mode 100644 index 0000000..537d364 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/add-remove-tag-on-company.ts @@ -0,0 +1,47 @@ +import { intercomAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { companyIdProp } from '../common/props'; +import { intercomClient } from '../common'; + +export const addOrRemoveTagOnCompanyAction = createAction({ + auth: intercomAuth, + name: 'add-or-remove-tag-on-company', + displayName: 'Add/Remove Tag on Company', + description: 'Attach or remove a tag from a specific company.', + props: { + companyId: companyIdProp('Company ID', true), + tagName: Property.ShortText({ + displayName: 'Tag Name', + required: true, + }), + untag: Property.Checkbox({ + displayName: 'Untag ?', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const client = intercomClient(context.auth); + + if (context.propsValue.untag) { + const company = await client.companies.find({ company_id: context.propsValue.companyId! }); + + const userDefinedCompanyId = company.company_id; + const response = await client.tags.create({ + name: context.propsValue.tagName, + companies: [ + { id: context.propsValue.companyId!, untag: true, company_id: userDefinedCompanyId }, + ], + }); + + return response; + } + + const response = await client.tags.create({ + name: context.propsValue.tagName, + companies: [{ id: context.propsValue.companyId }], + }); + + return response; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/actions/add-remove-tag-on-contact.ts b/packages/pieces/community/intercom/src/lib/actions/add-remove-tag-on-contact.ts new file mode 100644 index 0000000..1fe1db9 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/add-remove-tag-on-contact.ts @@ -0,0 +1,39 @@ +import { intercomAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { contactIdProp, tagIdProp } from '../common/props'; +import { intercomClient } from '../common'; + +export const addOrRemoveTagOnContactAction = createAction({ + auth: intercomAuth, + name: 'add-or-remove-tag-on-contact', + displayName: 'Add/Remove Tag on Contact', + description: 'Attach or remove a tag from a specific contact.', + props: { + contactId: contactIdProp('Contact ID','user', true), + tagId: tagIdProp('Tag Name', true), + untag: Property.Checkbox({ + displayName: 'Untag ?', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const client = intercomClient(context.auth); + + if (context.propsValue.untag) { + const response = await client.tags.untagContact({ + contact_id: context.propsValue.contactId!, + tag_id: context.propsValue.tagId!, + }); + + return response; + } + + const response = await client.tags.tagContact({ + contact_id: context.propsValue.contactId!, + id: context.propsValue.tagId!, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/actions/add-remove-tag-on-conversation.ts b/packages/pieces/community/intercom/src/lib/actions/add-remove-tag-on-conversation.ts new file mode 100644 index 0000000..2a77fce --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/add-remove-tag-on-conversation.ts @@ -0,0 +1,43 @@ +import { intercomAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { conversationIdProp, tagIdProp } from '../common/props'; +import { intercomClient } from '../common'; + +export const addOrRemoveTagOnConversationAction = createAction({ + auth: intercomAuth, + name: 'add-or-remove-tag-on-conversation', + displayName: 'Add/Remove Tag on Conversation', + description: 'Attach or remove a tag from a specific conversation.', + props: { + conversationId: conversationIdProp('Conversation ID', true), + tagId: tagIdProp('Tag', true), + untag: Property.Checkbox({ + displayName: 'Untag ?', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const client = intercomClient(context.auth); + + const admin = await client.admins.identify(); + const adminId = admin.id; + if (context.propsValue.untag) { + const response = await client.tags.untagConversation({ + conversation_id: context.propsValue.conversationId!, + tag_id: context.propsValue.tagId!, + admin_id: adminId, + }); + + return response; + } + + const response = await client.tags.tagConversation({ + conversation_id: context.propsValue.conversationId!, + id: context.propsValue.tagId!, + admin_id: adminId, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/actions/create-article.ts b/packages/pieces/community/intercom/src/lib/actions/create-article.ts new file mode 100644 index 0000000..1e4b8d2 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/create-article.ts @@ -0,0 +1,59 @@ +import { intercomAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { commonProps, intercomClient } from '../common'; +import { collectionIdProp } from '../common/props'; + +export const createArticleAction = createAction({ + auth: intercomAuth, + name: 'create-article', + displayName: 'Create Article', + description: 'Creates a new article in your Help Center.', + props: { + title: Property.LongText({ + displayName: 'Title', + required: true, + }), + description: Property.ShortText({ + displayName: 'Description', + required: false, + }), + body: Property.LongText({ + displayName: 'Body', + required: false, + }), + authorId: commonProps.admins({ displayName: 'Author', required: true }), + state: Property.StaticDropdown({ + displayName: 'State', + required: true, + defaultValue: 'draft', + options: { + disabled: false, + options: [ + { label: 'Draft', value: 'draft' }, + { label: 'Published', value: 'published' }, + ], + }, + }), + collectionId: collectionIdProp('Parent Collection', false), + }, + async run(context) { + const { authorId, collectionId, title, description, body, state } = context.propsValue; + + if (!authorId) { + throw new Error('Author is required'); + } + + const client = intercomClient(context.auth); + + const response = await client.articles.create({ + title, + description, + body, + author_id: Number(authorId), + state: state as 'published' | 'draft', + parent_id: collectionId ? Number(collectionId) : undefined, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/actions/create-conversation.ts b/packages/pieces/community/intercom/src/lib/actions/create-conversation.ts new file mode 100644 index 0000000..e28fe04 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/create-conversation.ts @@ -0,0 +1,82 @@ +import { intercomAuth } from '../../index'; +import { + createAction, + DropdownOption, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { intercomClient } from '../common'; + +export const createConversationAction = createAction({ + auth: intercomAuth, + name: 'create-conversation', + displayName: 'Create Conversation', + description: 'Creates a new conversation from a contact.', + props: { + contactType: Property.StaticDropdown({ + displayName: 'Contact Type', + required: true, + defaultValue: 'user', + options: { + disabled: false, + options: [ + { value: 'user', label: 'User' }, + { value: 'lead', label: 'Lead' }, + ], + }, + }), + contactId: Property.Dropdown({ + displayName: 'Contact ID', + required: true, + refreshers: ['contactType'], + options: async ({ auth, contactType }) => { + if (!auth || !contactType) { + return { + options: [], + disabled: true, + placeholder: 'Please connect your account first.', + }; + } + + const type = contactType as 'user' | 'lead'; + const authValue = auth as PiecePropValueSchema; + const client = intercomClient(authValue); + + const response = await client.contacts.list(); + const options: DropdownOption[] = []; + + for await (const contact of response) { + if (contact.role === type) { + options.push({ + value: contact.id, + label: `${contact.name ?? ''}, ${contact.email}, ${contact.external_id ?? ''}`, + }); + } + } + return { + disabled: false, + options, + }; + }, + }), + body: Property.LongText({ + displayName: 'Message Body', + required: true, + }), + }, + async run(context) { + const { contactId, body, contactType } = context.propsValue; + + const client = intercomClient(context.auth); + + const response = await client.conversations.create({ + body, + from: { + type: contactType as 'lead' | 'user' | 'contact', + id: contactId, + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/actions/create-ticket.ts b/packages/pieces/community/intercom/src/lib/actions/create-ticket.ts new file mode 100644 index 0000000..3dfd7cd --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/create-ticket.ts @@ -0,0 +1,57 @@ +import { intercomAuth } from '../../index'; +import { createAction } from '@activepieces/pieces-framework'; +import { intercomClient } from '../common'; +import { + companyIdProp, + contactIdProp, + ticketPropertiesProp, + ticketTypeIdProp, +} from '../common/props'; +import dayjs from 'dayjs'; + +export const createTicketAction = createAction({ + auth: intercomAuth, + name: 'create-ticket', + displayName: 'Create Ticket', + description: 'Creates a new ticket.', + props: { + ticketTypeId: ticketTypeIdProp('Ticket Type', true), + contactId: contactIdProp('Contact ID', null, true), + companyId: companyIdProp('Company ID', false), + ticketProperties: ticketPropertiesProp('Ticket Properties', true), + }, + async run(context) { + const { ticketTypeId, contactId, companyId } = context.propsValue; + const ticketProperties = context.propsValue.ticketProperties ?? {}; + + if (!ticketTypeId) { + throw new Error('Ticket Type is required'); + } + + if (!contactId) { + throw new Error('Contact ID is required'); + } + + const client = intercomClient(context.auth); + + const formattedProperties: Record = {}; + for (const key in ticketProperties) { + const value = ticketProperties[key]; + + // Check if value is a valid date string and convert it to a timestamp + if (typeof value === 'string' && dayjs(value).isValid()) { + formattedProperties[key] = dayjs(value).unix(); // Convert to timestamp + } else { + formattedProperties[key] = value; + } + } + const response = await client.tickets.create({ + ticket_type_id: ticketTypeId, + contacts: [{ id: contactId }], + company_id: companyId, + ticket_attributes: formattedProperties, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/actions/create-update-lead.ts b/packages/pieces/community/intercom/src/lib/actions/create-update-lead.ts new file mode 100644 index 0000000..d4da83d --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/create-update-lead.ts @@ -0,0 +1,95 @@ +import { intercomAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { intercomClient } from '../common'; +import dayjs from 'dayjs'; + +export const createOrUpdateLeadAction = createAction({ + auth: intercomAuth, + name: 'create-or-update-lead', + displayName: 'Create or Update Lead', + description: 'Create or update an Intercom lead.If an ID is provided, the lead will be updated.', + props: { + leadId: Property.ShortText({ + displayName: 'Lead ID', + required: false, + }), + name: Property.ShortText({ + displayName: 'Full Name', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + required: false, + }), + phone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + unsubscribe: Property.Checkbox({ + displayName: 'Unsubscribed From Emails', + required: false, + }), + createdAt: Property.DateTime({ + displayName: 'Created At', + required: false, + }), + customAttributes: Property.Object({ + displayName: 'Custom Attributes', + required: false, + }), + }, + async run(context) { + const { leadId, name, email, phone, unsubscribe, createdAt, customAttributes } = + context.propsValue; + const client = intercomClient(context.auth); + + if (leadId) { + const lead = await client.contacts.search({ + query: { + operator: 'AND', + value: [ + { + field: 'id', + operator: '=', + value: leadId, + }, + { + field: 'role', + operator: '=', + value: 'lead', + }, + ], + }, + pagination: { per_page: 1 }, + }); + + if (lead.data.length === 0) { + throw new Error('Could not find lead with this id.'); + } + + const response = await client.contacts.update({ + contact_id: lead.data[0].id, + name: name, + email: email, + phone: phone, + unsubscribed_from_emails: unsubscribe, + custom_attributes: customAttributes, + signed_up_at: createdAt ? dayjs(createdAt).unix() : undefined, + }); + + return response; + } + + const response = await client.contacts.create({ + role: 'lead', + name: name, + email: email, + phone: phone, + unsubscribed_from_emails: unsubscribe, + custom_attributes: customAttributes, + signed_up_at: createdAt ? dayjs(createdAt).unix() : undefined, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/actions/create-update-user.ts b/packages/pieces/community/intercom/src/lib/actions/create-update-user.ts new file mode 100644 index 0000000..8d19ef0 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/create-update-user.ts @@ -0,0 +1,90 @@ +import { intercomAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { intercomClient } from '../common'; +import dayjs from 'dayjs'; + +export const createOrUpdateUserAction = createAction({ + auth: intercomAuth, + name: 'create-or-update-user', + displayName: 'Create/Update User', + description: 'Update a user within intercom given an email address.', + props: { + email: Property.ShortText({ + displayName: 'Lookup Email', + required: true, + }), + name: Property.ShortText({ + displayName: 'Full Name', + required: false, + }), + userId: Property.ShortText({ + displayName: 'User ID', + required: false, + }), + phone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + createdAt: Property.DateTime({ + displayName: 'Created At', + required: false, + }), + customAttributes: Property.Object({ + displayName: 'Custom Attributes', + required: false, + }), + }, + async run(context) { + const client = intercomClient(context.auth); + + const contact = await client.contacts.search({ + query: { + operator: 'AND', + value: [ + { + field: 'email', + operator: '=', + value: context.propsValue.email, + }, + { + field: 'role', + operator: '=', + value: 'user', + }, + ], + }, + pagination: { per_page: 1 }, + }); + + if (contact.data.length === 0) { + const response = await client.contacts.create({ + role: 'user', + email: context.propsValue.email, + name: context.propsValue.name, + custom_attributes: context.propsValue.customAttributes, + signed_up_at: context.propsValue.createdAt + ? dayjs(context.propsValue.createdAt).unix() + : undefined, + external_id: context.propsValue.userId, + phone: context.propsValue.phone, + }); + + return response; + } + + const contactId = contact.data[0].id; + + const response = await client.contacts.update({ + contact_id: contactId, + name: context.propsValue.name, + custom_attributes: context.propsValue.customAttributes, + signed_up_at: context.propsValue.createdAt + ? dayjs(context.propsValue.createdAt).unix() + : undefined, + external_id: context.propsValue.userId, + phone: context.propsValue.phone, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/actions/create-user.ts b/packages/pieces/community/intercom/src/lib/actions/create-user.ts new file mode 100644 index 0000000..8d6c582 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/create-user.ts @@ -0,0 +1,49 @@ +import { intercomAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { intercomClient } from '../common'; +import dayjs from 'dayjs'; + +export const createUserAction = createAction({ + auth: intercomAuth, + name: 'create-user', + displayName: 'Create User', + description: 'Creates a new user.', + props: { + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + createdAt: Property.DateTime({ + displayName: 'Created At', + required: false, + }), + userId: Property.ShortText({ + displayName: 'User ID', + required: false, + }), + name: Property.ShortText({ + displayName: 'Full Name', + required: false, + }), + customAttributes: Property.Object({ + displayName: 'Custom Attributes', + required: false, + }), + }, + async run(context) { + const client = intercomClient(context.auth); + + const response = await client.contacts.create({ + role: 'user', + email: context.propsValue.email, + name: context.propsValue.name, + custom_attributes: context.propsValue.customAttributes, + signed_up_at: context.propsValue.createdAt + ? dayjs(context.propsValue.createdAt).unix() + : undefined, + external_id: context.propsValue.userId, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/actions/find-company.ts b/packages/pieces/community/intercom/src/lib/actions/find-company.ts new file mode 100644 index 0000000..5f4a56b --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/find-company.ts @@ -0,0 +1,44 @@ +import { intercomAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { intercomClient } from '../common'; + +export const findCompanyAction = createAction({ + auth: intercomAuth, + name: 'find-company', + displayName: 'Find Company', + description: 'Finds an existing company.', + props: { + searchField: Property.StaticDropdown({ + displayName: 'Search Field', + required: true, + options: { + disabled: false, + options: [ + { label: 'Name', value: 'name' }, + { label: 'Company ID', value: 'company_id' }, + ], + }, + }), + searchValue: Property.ShortText({ + displayName: 'Search Value', + required: true, + }), + }, + async run(context) { + const { searchField, searchValue } = context.propsValue; + + const client = intercomClient(context.auth); + + const companyResponse = await client.companies.retrieve({ + company_id: searchField === 'company_id' ? searchValue : undefined, + name: searchField === 'name' ? searchValue : undefined, + per_page:1 + + }) + + return { + found: companyResponse.data.length > 0, + user: companyResponse.data.length > 0 ? companyResponse.data[0] : {}, + }; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/actions/find-conversation.ts b/packages/pieces/community/intercom/src/lib/actions/find-conversation.ts new file mode 100644 index 0000000..45c7e0f --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/find-conversation.ts @@ -0,0 +1,114 @@ +import { intercomAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { intercomClient, Operator } from '../common'; +import dayjs from 'dayjs'; + +export const findConversationAction = createAction({ + auth: intercomAuth, + name: 'find-conversation', + displayName: 'Find Conversation', + description: 'Searches for conversations using various criteria', + props: { + searchField: Property.StaticDropdown({ + displayName: 'Search Field', + required: true, + options: { + disabled: false, + options: [ + { label: 'Conversation ID', value: 'id' }, + { label: 'Subject', value: 'source.subject' }, + { label: 'Message Body', value: 'source.body' }, + { label: 'Author Email', value: 'source.author.email' }, + { label: 'Assigned Admin', value: 'admin_assignee_id' }, + { label: 'Team', value: 'team_assignee_id' }, + { label: 'Tag IDs', value: 'tag_ids' }, + ], + }, + }), + matchType: Property.StaticDropdown({ + displayName: 'Match Type', + required: true, + options: { + disabled: false, + options: [ + { label: 'Contains', value: 'contains' }, + { label: 'Equals', value: 'equals' }, + { label: 'Starts With', value: 'starts_with' }, + ], + }, + }), + searchTerm: Property.ShortText({ + displayName: 'Search Term', + required: true, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + required: false, + options: { + disabled: false, + options: [ + { label: 'Open', value: 'open' }, + { label: 'Closed', value: 'closed' }, + ], + }, + }), + updateAfter: Property.DateTime({ + displayName: 'Update After', + required: false, + }), + updateBefore: Property.DateTime({ + displayName: 'Update Before', + required: false, + }), + }, + async run(context) { + const { searchField, matchType, searchTerm, status, updateAfter, updateBefore } = + context.propsValue; + + const operator = matchType === 'contains' ? '~' : matchType === 'starts_with' ? '^' : '='; + + + const client = intercomClient(context.auth); + + const filter = [ + { + field: searchField, + operator: operator as Operator, + value: searchTerm, + }, + ]; + if (status) { + filter.push({ + field: 'state', + operator: "=", + value: status, + }); + } + if (updateAfter) { + filter.push({ + field: 'updated_at', + operator: '>', + value: dayjs(updateAfter).unix().toString(), + }); + } + if (updateBefore) { + filter.push({ + field: 'updated_at', + operator: "<", + value: dayjs(updateBefore).unix().toString(), + }); + } + + const response = await client.conversations.search({ + query: { + operator: 'AND', + value: filter, + }, + }); + + return { + found: response.data.length > 0, + conversation: response.data.length > 0 ? response.data[0] : {}, + }; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/intercom/src/lib/actions/find-lead.ts b/packages/pieces/community/intercom/src/lib/actions/find-lead.ts new file mode 100644 index 0000000..15ac550 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/find-lead.ts @@ -0,0 +1,57 @@ +import { intercomAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { intercomClient } from '../common'; + +export const findLeadAction = createAction({ + auth: intercomAuth, + name: 'find-lead', + displayName: 'Find Lead', + description: 'Finds an existing lead.', + props: { + searchField: Property.StaticDropdown({ + displayName: 'Search Field', + required: true, + options: { + disabled: false, + options: [ + { label: 'Email', value: 'email' }, + { label: 'ID', value: 'id' }, + { label: 'User ID', value: 'external_id' }, + ], + }, + }), + searchValue: Property.ShortText({ + displayName: 'Search Value', + required: true, + }), + }, + async run(context) { + const { searchField, searchValue } = context.propsValue; + + const client = intercomClient(context.auth); + + const contactResponse = await client.contacts.search({ + query: { + operator: 'AND', + value: [ + { + field: searchField, + operator: '=', + value: searchValue, + }, + { + field: 'role', + operator: '=', + value: 'lead', + }, + ], + }, + pagination: { per_page: 1 }, + }); + + return { + found: contactResponse.data.length > 0, + lead: contactResponse.data.length > 0 ? contactResponse.data[0] : {}, + }; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/actions/find-user.ts b/packages/pieces/community/intercom/src/lib/actions/find-user.ts new file mode 100644 index 0000000..7961eda --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/find-user.ts @@ -0,0 +1,57 @@ +import { intercomAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { intercomClient } from '../common'; + +export const findUserAction = createAction({ + auth: intercomAuth, + name: 'find-user', + displayName: 'Find User', + description: 'Finds an existing user.', + props: { + searchField: Property.StaticDropdown({ + displayName: 'Search Field', + required: true, + options: { + disabled: false, + options: [ + { label: 'Email', value: 'email' }, + { label: 'ID', value: 'id' }, + { label: 'User ID', value: 'external_id' }, + ], + }, + }), + searchValue: Property.ShortText({ + displayName: 'Search Value', + required: true, + }), + }, + async run(context) { + const { searchField, searchValue } = context.propsValue; + + const client = intercomClient(context.auth); + + const contactResponse = await client.contacts.search({ + query: { + operator: 'AND', + value: [ + { + field: searchField, + operator: '=', + value: searchValue, + }, + { + field: 'role', + operator: '=', + value: 'user', + }, + ], + }, + pagination: { per_page: 1 }, + }); + + return { + found: contactResponse.data.length > 0, + user: contactResponse.data.length > 0 ? contactResponse.data[0] : {}, + }; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/actions/get-conversation.ts b/packages/pieces/community/intercom/src/lib/actions/get-conversation.ts new file mode 100644 index 0000000..8dddb78 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/get-conversation.ts @@ -0,0 +1,28 @@ +import { intercomAuth } from '../../index'; +import { createAction } from '@activepieces/pieces-framework'; +import { conversationIdProp } from '../common/props'; +import { intercomClient } from '../common'; + +export const getConversationAction = createAction({ + auth: intercomAuth, + name: 'get-conversation', + displayName: 'Retrieve a Conversation', + description: 'Retrieves a specific conversation by ID.', + props: { + conversationId: conversationIdProp('Conversation ID', true), + }, + async run(context) { + const { conversationId } = context.propsValue; + const client = intercomClient(context.auth); + + if (!conversationId) { + throw new Error('Conversation ID is required'); + } + + const response = await client.conversations.find({ + conversation_id: conversationId, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/actions/list-all-tags.ts b/packages/pieces/community/intercom/src/lib/actions/list-all-tags.ts new file mode 100644 index 0000000..6e64418 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/list-all-tags.ts @@ -0,0 +1,19 @@ +import { intercomAuth } from "../../index"; +import { createAction } from "@activepieces/pieces-framework"; +import { intercomClient } from "../common"; + +export const listAllTagsAction = createAction({ + auth:intercomAuth, + name:'list-all-tags', + displayName:'List Tags', + description:'List all tags.', + props:{}, + async run(context){ + const client = intercomClient(context.auth); + + const response = await client.tags.list(); + + return response; + } + +}) \ No newline at end of file diff --git a/packages/pieces/community/intercom/src/lib/actions/reply-to-conversation.ts b/packages/pieces/community/intercom/src/lib/actions/reply-to-conversation.ts new file mode 100644 index 0000000..881a65e --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/reply-to-conversation.ts @@ -0,0 +1,34 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { intercomAuth } from '../..'; +import { commonProps, intercomClient } from '../common'; +import { conversationIdProp } from '../common/props'; + +export const replyToConversation = createAction({ + auth: intercomAuth, + name: 'replyToConversation', + displayName: 'Reply to conversation', + description: 'Reply (as an admin) to a conversation with a contact', + props: { + from: commonProps.admins({ displayName: 'From (Admin)', required: true }), + conversationId: conversationIdProp('Conversation ID', true), + body: Property.ShortText({ + displayName: 'Message Body', + required: true, + }), + }, + async run(context) { + const client = intercomClient(context.auth); + + const response = await client.conversations.reply({ + conversation_id: context.propsValue.conversationId!, + body: { + type: 'admin', + message_type: 'comment', + body: context.propsValue.body, + admin_id: context.propsValue.from, + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/actions/send-message.action.ts b/packages/pieces/community/intercom/src/lib/actions/send-message.action.ts new file mode 100644 index 0000000..e9c33f5 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/send-message.action.ts @@ -0,0 +1,86 @@ +import { createAction, DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { commonProps, intercomClient } from '../common'; +import { intercomAuth } from '../..'; + +export const sendMessageAction = createAction({ + auth: intercomAuth, + description: 'Send a message to a contact (only allowed by admins)', + displayName: 'Send Message', + name: 'send_message', + props: { + message_type: Property.StaticDropdown({ + displayName: 'Message Type', + options: { + options: [ + { value: 'email', label: 'Email' }, + { value: 'inapp', label: 'In App Chat' }, + ], + }, + required: true, + defaultValue: 'email', + }), + email_required_fields: Property.DynamicProperties({ + displayName: 'Email Required Fields', + required: true, + refreshers: ['message_type'], + props: async ({ message_type }) => { + let fields: DynamicPropsValue = {}; + if ((message_type as unknown as string) === 'email' || !message_type) { + fields = { + subject: Property.ShortText({ + displayName: 'Subject', + required: true, + description: 'Email title', + }), + template: Property.StaticDropdown({ + displayName: 'Template', + options: { + options: [ + { label: 'Personal', value: 'personal' }, + { label: 'Plain', value: 'plain' }, + ], + }, + required: true, + defaultValue: 'personal', + description: 'Style of the email', + }), + }; + } + return fields; + }, + }), + from: commonProps.admins({ displayName: 'From (Admin)', required: true }), + to: commonProps.contacts({ displayName: 'To', required: true }), + body: Property.ShortText({ + displayName: 'Message Body', + required: true, + }), + create_conversation_without_contact_reply: Property.Checkbox({ + displayName: 'Create Conversation Without Contact Reply', + description: + 'Whether a conversation should be opened in the inbox for the message without the contact replying. Defaults to false if not provided.', + required: false, + defaultValue: false, + }), + }, + run: async (context) => { + const client = intercomClient(context.auth); + const user = await client.contacts.find({ contact_id: context.propsValue.to }); + + const response = await client.messages.create({ + message_type: context.propsValue.message_type as 'email' | 'inapp', + from: { id: Number(context.propsValue.from), type: 'admin' }, + to: { + id: context.propsValue.to, + type: user.role === 'user' ? 'user' : 'lead', + }, + template: context.propsValue.email_required_fields['template'], + subject: context.propsValue.email_required_fields['subject'], + create_conversation_without_contact_reply: + context.propsValue.create_conversation_without_contact_reply, + body: context.propsValue.body, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/actions/update-ticket.ts b/packages/pieces/community/intercom/src/lib/actions/update-ticket.ts new file mode 100644 index 0000000..a369033 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/actions/update-ticket.ts @@ -0,0 +1,100 @@ +import { intercomAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { commonProps, intercomClient } from '../common'; +import { ticketIdProp, ticketPropertiesProp, ticketTypeIdProp } from '../common/props'; +import { UpdateTicketRequest } from 'intercom-client/api'; + +import dayjs from 'dayjs'; + +export const updateTicketAction = createAction({ + auth: intercomAuth, + name: 'update-ticket', + displayName: 'Update Ticket', + description: 'Updates an existing ticket.', + props: { + ticketTypeId: ticketTypeIdProp('Ticket Type', true), + ticketId: ticketIdProp('Ticket', true), + ticketProperties: ticketPropertiesProp('Ticket Properties', true), + isOpen: Property.Checkbox({ + displayName: 'Is Open', + required: false, + }), + state: Property.StaticDropdown({ + displayName: 'State', + required: false, + options: { + disabled: false, + options: [ + { + value: 'in_progress', + label: 'In Progress', + }, + { + value: 'waiting_on_customer', + label: 'Waiting on Customer', + }, + { + value: 'resolved', + label: 'Resolved', + }, + ], + }, + }), + snoozedTill: Property.DateTime({ + displayName: 'Snoozed Until', + required: false, + }), + assignedAdminId: commonProps.admins({ displayName: 'Assigned Admin', required: false }), + }, + async run(context) { + const { ticketTypeId, ticketId, isOpen, state, snoozedTill, assignedAdminId } = + context.propsValue; + const ticketProperties = context.propsValue.ticketProperties ?? {}; + + if (!ticketTypeId) { + throw new Error('Ticket Type is required'); + } + + if (!ticketId) { + throw new Error('Ticket ID is required'); + } + + const client = intercomClient(context.auth); + + const request: UpdateTicketRequest = { + ticket_id: ticketId, + open: isOpen, + state: state as 'in_progress' | 'waiting_on_customer' | 'resolved' | undefined, + }; + + if (snoozedTill) { + request.snoozed_until = dayjs(snoozedTill).unix(); + } + if (assignedAdminId) { + const admin = await client.admins.identify(); + request.assignment = { + assignee_id: assignedAdminId, + admin_id: admin.id, + }; + } + + if (Object.keys(ticketProperties).length > 0) { + const formattedProperties: Record = {}; + for (const key in ticketProperties) { + const value = ticketProperties[key]; + + // Check if value is a valid date string and convert it to a timestamp + if (typeof value === 'string' && dayjs(value).isValid()) { + formattedProperties[key] = dayjs(value).unix(); // Convert to timestamp + } else { + formattedProperties[key] = value; + } + } + request.ticket_attributes = formattedProperties; + } + + const response = await client.tickets.update(request); + + return response; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/common/index.ts b/packages/pieces/community/intercom/src/lib/common/index.ts new file mode 100644 index 0000000..66e0333 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/common/index.ts @@ -0,0 +1,106 @@ +import { getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { IntercomClient } from 'intercom-client'; +import { + OAuth2PropertyValue, + OAuth2Props, + Property, +} from '@activepieces/pieces-framework'; + +export const intercomClient = (auth: OAuth2PropertyValue) => { + const client = new IntercomClient({ + token: getAccessTokenOrThrow(auth), + environment: `https://api.${auth.props?.['region']}.io`, + }); + return client; +}; + +export const commonProps = { + admins: (options: { displayName: string; required: R }) => + Property.Dropdown({ + displayName: options.displayName, + required: options.required, + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please connect your account first', + }; + } + const client = intercomClient(auth as OAuth2PropertyValue); + const adminsResponse = await client.admins.list(); + + return { + options: adminsResponse.admins.map((c) => { + const res = { value: c.id, label: '' }; + if (c.name) { + res.label = c.name; + } else if (c.email) { + res.label = c.email; + } else { + res.label = c.id; + } + return res; + }), + }; + }, + refreshers: [], + }), + contacts: (options: { + displayName: string; + required: R; + }) => + Property.Dropdown({ + displayName: options.displayName, + required: options.required, + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please connect your account first', + }; + } + const client = intercomClient(auth as OAuth2PropertyValue); + const contactsResponse = await client.contacts.list({}); + + return { + options: contactsResponse.data.map((c) => { + const res = { value: c.id, label: '' }; + if (c.name) { + res.label = c.name; + } else if (c.email) { + res.label = c.email; + } else { + res.label = c.id; + } + return res; + }), + }; + }, + refreshers: [], + }), +}; + +export type Operator = + | '=' + | '!=' + | 'IN' + | 'NIN' + | '<' + | '>' + | '~' + | '!~' + | '^' + | '$'; + +export type TriggerPayload = { + type: string; + app_id: string; + id: string; + topic: string; + data: { + type: string; + item: Record; + }; +}; diff --git a/packages/pieces/community/intercom/src/lib/common/props.ts b/packages/pieces/community/intercom/src/lib/common/props.ts new file mode 100644 index 0000000..5887b0e --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/common/props.ts @@ -0,0 +1,399 @@ +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { intercomClient } from '.'; +import { intercomAuth } from '../../index'; +import { + DropdownOption, + DynamicPropsValue, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; + +export const conversationIdProp = (displayName: string, required = true) => + Property.Dropdown({ + displayName, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please connect your account first.', + }; + } + + const authValue = auth as PiecePropValueSchema; + const client = intercomClient(authValue); + + const response = await client.conversations.list(); + const options: DropdownOption[] = []; + + for await (const conversation of response) { + options.push({ + value: conversation.id, + label: `${conversation.source.author.email}${ + conversation.title ? `, ${conversation.title}` : '' + }, ${conversation.id}`, + }); + } + + return { + disabled: false, + options, + }; + }, + }); + +export const tagIdProp = (displayName: string, required = true) => + Property.Dropdown({ + displayName, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please connect your account first.', + }; + } + + const authValue = auth as PiecePropValueSchema; + const client = intercomClient(authValue); + + const response = await client.tags.list(); + const options: DropdownOption[] = []; + + for (const tag of response.data) { + options.push({ + value: tag.id, + label: tag.name, + }); + } + + return { + disabled: false, + options, + }; + }, + }); + +export const companyIdProp = (displayName: string, required = true) => + Property.Dropdown({ + displayName, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please connect your account first.', + }; + } + + const authValue = auth as PiecePropValueSchema; + const client = intercomClient(authValue); + + const response = await client.companies.list(); + const options: DropdownOption[] = []; + + for await (const company of response) { + options.push({ + value: company.id, + label: company.name, + }); + } + + return { + disabled: false, + options, + }; + }, + }); + +export const contactIdProp = (displayName: string, contactType: string | null, required = true) => + Property.Dropdown({ + displayName, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please connect your account first.', + }; + } + + const authValue = auth as PiecePropValueSchema; + const client = intercomClient(authValue); + + const response = await client.contacts.list(); + const options: DropdownOption[] = []; + + for await (const contact of response) { + if (contactType === null || contact.role === contactType) { + options.push({ + value: contact.id, + label: `${contact.name ?? ''}, ${contact.email}, ${contact.external_id ?? ''}`, + }); + } + } + + return { + disabled: false, + options, + }; + }, + }); + +export const collectionIdProp = (displayName: string, required = true) => + Property.Dropdown({ + displayName, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please connect your account first.', + }; + } + + const authValue = auth as PiecePropValueSchema; + const client = intercomClient(authValue); + + const response = await client.helpCenters.collections.list(); + const options: DropdownOption[] = []; + + for await (const collection of response) { + options.push({ + value: collection.id, + label: collection.name, + }); + } + + return { + disabled: false, + options, + }; + }, + }); + +export const ticketTypeIdProp = (displayName: string, required = true) => + Property.Dropdown({ + displayName, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please connect your account first.', + }; + } + + const authValue = auth as PiecePropValueSchema; + const response = await httpClient.sendRequest<{ data: Array<{ id: string; name: string }> }>({ + method: HttpMethod.GET, + url: `https://api.${authValue.props?.['region']}.io/ticket_types `, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authValue.access_token, + }, + }); + + const options: DropdownOption[] = []; + + for (const type of response.body.data) { + options.push({ + value: type.id, + label: type.name, + }); + } + + return { + disabled: false, + options, + }; + }, + }); + +export const ticketStateIdProp = (displayName: string, required = true) => + Property.Dropdown({ + displayName, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + disabled: true, + }; + } + + const authValue = auth as PiecePropValueSchema; + + const options: DropdownOption[] = []; + + const response = await httpClient.sendRequest<{ + data: Array<{ id: string; internal_label: string }>; + }>({ + method: HttpMethod.GET, + url: `https://api.${authValue.props?.['region']}.io/ticket_states`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authValue.access_token, + }, + }); + + if (response.body.data) { + for (const state of response.body.data) { + options.push({ + label: state.internal_label, + value: state.id, + }); + } + } + + return { + disabled: true, + options: [], + }; + }, + }); + +export const ticketIdProp = (displayName: string, required = true) => + Property.Dropdown({ + displayName, + required, + refreshers: ['ticketTypeId'], + options: async ({ auth, ticketTypeId }) => { + if (!auth || !ticketTypeId) { + return { + options: [], + disabled: true, + placeholder: 'Please connect your account first.', + }; + } + + const authValue = auth as PiecePropValueSchema; + const client = intercomClient(authValue); + + const options: DropdownOption[] = []; + + const response = await client.tickets.search({ + query: { + field: 'ticket_type_id', + operator: '=', + value: ticketTypeId as unknown as string, + }, + pagination: { per_page: 100 }, + }); + + for await (const ticket of response) { + options.push({ + value: ticket.id, + label: (ticket.ticket_attributes['_default_title_'] as string) ?? ticket.id, + }); + } + + return { + disabled: false, + options, + }; + }, + }); + +export const ticketPropertiesProp = (displayName: string, required = true) => + Property.DynamicProperties({ + displayName, + refreshers: ['ticketTypeId'], + required, + props: async ({ auth, ticketTypeId }) => { + if (!auth) return {}; + if (!ticketTypeId) return {}; + + const props: DynamicPropsValue = {}; + + const authValue = auth as PiecePropValueSchema; + + const response = await httpClient.sendRequest<{ + ticket_type_attributes: { + data: Array<{ data_type: string; name: string; input_options: Record }>; + }; + }>({ + method: HttpMethod.GET, + url: `https://api.${authValue.props?.['region']}.io/ticket_types/${ticketTypeId} `, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authValue.access_token, + }, + }); + + for (const field of response.body.ticket_type_attributes.data) { + switch (field.data_type) { + case 'string': + props[field.name] = Property.LongText({ + displayName: + field.name === '_default_title_' + ? 'Title (Default)' + : field.name === '_default_description_' + ? 'Description (Default)' + : field.name, + required: false, + }); + break; + case 'integer': + case 'decimal': + props[field.name] = Property.Number({ + displayName: field.name, + required: false, + }); + break; + case 'boolean': + props[field.name] = Property.Checkbox({ + displayName: field.name, + required: false, + }); + break; + case 'datetime': + props[field.name] = Property.DateTime({ + displayName: field.name, + required: false, + }); + break; + case 'list': + { + const options = field.input_options.list_options as Array<{ + label: string; + id: string; + }>; + + props[field.name] = Property.StaticDropdown({ + displayName: field.name, + required: false, + options: { + disabled: false, + options: options + ? options.map((option) => ({ + value: option.id, + label: option.label, + })) + : [], + }, + }); + } + + break; + default: + break; + } + } + + return props; + }, + }); diff --git a/packages/pieces/community/intercom/src/lib/triggers/contact-replied.ts b/packages/pieces/community/intercom/src/lib/triggers/contact-replied.ts new file mode 100644 index 0000000..9cd8658 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/contact-replied.ts @@ -0,0 +1,180 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomAuth } from '../..'; +import { intercomClient, TriggerPayload } from '../common'; + +export const contactRepliedTrigger = createTrigger({ + name: 'contactReplied', + displayName: 'Contact Replied', + description: 'Triggers when a contact replies to a Conversation in Intercom.', + props: {}, + auth: intercomAuth, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + context.app.createListeners({ + events: ['conversation.user.replied'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async test(context) { + const client = intercomClient(context.auth); + + const response = await client.conversations.list({ per_page: 10 }); + + return response.data; + }, + async run(context) { + const payload = context.payload.body as TriggerPayload; + return [payload.data.item]; + }, + sampleData: { + type: 'conversation', + id: '2', + created_at: 1739105560, + updated_at: 1739524230, + waiting_since: null, + snoozed_until: null, + source: { + type: null, + id: '2', + delivered_as: 'admin_initiated', + subject: '', + body: '', + author: { + type: 'admin', + id: '8055721', + name: 'Fin', + email: 'operator+k2gbyfxu@intercom.io', + }, + attachments: [], + url: null, + redacted: false, + }, + contacts: { + type: 'contact.list', + contacts: [ + { + type: 'contact', + id: '67a87380c16da6b56c76bbb7', + external_id: '1234567', + }, + ], + }, + first_contact_reply: null, + admin_assignee_id: 8055717, + team_assignee_id: null, + open: true, + state: 'open', + read: false, + tags: { + type: 'tag.list', + tags: [], + }, + priority: 'not_priority', + sla_applied: null, + statistics: { + type: 'conversation_statistics', + time_to_assignment: null, + time_to_admin_reply: null, + time_to_first_close: null, + time_to_last_close: null, + median_time_to_reply: null, + first_contact_reply_at: null, + first_assignment_at: null, + first_admin_reply_at: null, + first_close_at: null, + last_assignment_at: null, + last_assignment_admin_reply_at: null, + last_contact_reply_at: null, + last_admin_reply_at: null, + last_close_at: null, + last_closed_by_id: null, + count_reopens: 0, + count_assignments: 0, + count_conversation_parts: 9, + }, + conversation_rating: null, + teammates: { + type: 'admin.list', + admins: [ + { + type: 'admin', + id: '8055717', + }, + ], + }, + title: null, + custom_attributes: { + 'Copilot used': false, + 'Ticket category': 'Customer ticket', + 'Created by': 8055721, + }, + topics: { + type: 'topic.list', + topics: [], + total_count: 0, + }, + ticket: { + type: 'ticket', + id: 2, + url: 'https://app.intercom.com/a/apps/ahah/conversations/2', + custom_attributes: { + _default_title_: { + value: 'fdfdf', + type: 'string', + }, + _default_description_: { + value: 'dfdfdf', + type: 'string', + }, + List: { + value: null, + type: 'list', + }, + Number: { + value: null, + type: 'integer', + }, + decimal: { + value: null, + type: 'decimal', + }, + bool: { + value: null, + type: 'boolean', + }, + 'date time': { + value: null, + type: 'datetime', + }, + files: { + value: null, + type: 'files', + }, + }, + state: 'in_progress', + ticket_type: 'zapier', + ticket_type_description: '', + ticket_type_emoji: '🚨', + ticket_custom_state_admin_label: 'In progress', + ticket_custom_state_user_label: 'In progress', + }, + linked_objects: { + type: 'list', + data: [], + total_count: 0, + has_more: false, + }, + ai_agent: null, + ai_agent_participated: false, + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/triggers/contact-updated.ts b/packages/pieces/community/intercom/src/lib/triggers/contact-updated.ts new file mode 100644 index 0000000..b7d7b1c --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/contact-updated.ts @@ -0,0 +1,160 @@ +import { intercomAuth } from '../../index'; +import { createTrigger, Property, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomClient, TriggerPayload } from '../common'; + +export const contactUpdatedTrigger = createTrigger({ + auth: intercomAuth, + name: 'contact-updated', + displayName: 'Updated Contact', + description: 'Triggers when a contact is updated.', + props: { + type: Property.StaticDropdown({ + displayName: 'Type', + required: true, + defaultValue: 'user', + options: { + disabled: false, + + options: [ + { label: 'User', value: 'user' }, + { label: 'Lead', value: 'lead' }, + ], + }, + }), + }, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + const event = context.propsValue.type === 'user' ? 'contact.user.updated' : 'contact.lead.updated'; + + context.app.createListeners({ + events: [event], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async test(context) { + const client = intercomClient(context.auth); + + const response = await client.contacts.search({ + query:{ + field:'role', + operator:'=', + value:context.propsValue.type + }, + pagination:{per_page:5} + + }); + + return response.data; + }, + async run(context) { + const payload = context.payload.body as TriggerPayload; + return [payload.data.item]; + }, + sampleData: { + type: 'contact', + id: '67a9b9dfcc14109e073fbe19', + workspace_id: 'nzekhfwb', + external_id: '5b803f65-bcec-4198-b4f4-a0588454b537', + role: 'user', + email: 'john.doe@example.com', + phone: null, + name: 'John Doe', + avatar: null, + owner_id: null, + social_profiles: { + type: 'list', + data: [], + }, + has_hard_bounced: false, + marked_email_as_spam: false, + unsubscribed_from_emails: false, + created_at: '2025-02-10T08:33:35.910+00:00', + updated_at: '2025-02-10T08:33:35.907+00:00', + signed_up_at: null, + last_seen_at: null, + last_replied_at: null, + last_contacted_at: null, + last_email_opened_at: null, + last_email_clicked_at: null, + language_override: null, + browser: null, + browser_version: null, + browser_language: null, + os: null, + location: { + type: 'location', + country: null, + region: null, + city: null, + country_code: null, + continent_code: null, + }, + android_app_name: null, + android_app_version: null, + android_device: null, + android_os_version: null, + android_sdk_version: null, + android_last_seen_at: null, + ios_app_name: null, + ios_app_version: null, + ios_device: null, + ios_os_version: null, + ios_sdk_version: null, + ios_last_seen_at: null, + custom_attributes: {}, + tags: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/tags', + total_count: 0, + has_more: false, + }, + notes: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/notes', + total_count: 0, + has_more: false, + }, + companies: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/companies', + total_count: 0, + has_more: false, + }, + opted_out_subscription_types: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/subscriptions', + total_count: 0, + has_more: false, + }, + opted_in_subscription_types: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/subscriptions', + total_count: 0, + has_more: false, + }, + utm_campaign: null, + utm_content: null, + utm_medium: null, + utm_source: null, + utm_term: null, + referrer: null, + sms_consent: false, + unsubscribed_from_sms: false, + enabled_push_messaging: null, + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/triggers/conversation-assigned.ts b/packages/pieces/community/intercom/src/lib/triggers/conversation-assigned.ts new file mode 100644 index 0000000..d57de86 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/conversation-assigned.ts @@ -0,0 +1,32 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomAuth } from '../..'; +import { intercomClient } from '../common'; + +export const conversationAssigned = createTrigger({ + name: 'conversationAssigned', + displayName: 'Conversation assigned to any Intercom admin', + description: 'Triggers when a conversation is assigned to an admin', + props: {}, + sampleData: undefined, + auth: intercomAuth, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + context.app.createListeners({ + events: ['conversation.admin.assigned'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/triggers/conversation-closed.ts b/packages/pieces/community/intercom/src/lib/triggers/conversation-closed.ts new file mode 100644 index 0000000..f2dadcd --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/conversation-closed.ts @@ -0,0 +1,187 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomAuth } from '../..'; +import { intercomClient, TriggerPayload } from '../common'; + +export const conversationClosedTrigger = createTrigger({ + name: 'conversationClosed', + displayName: 'Conversation Closed', + description: 'Triggers when a conversation is closed.', + props: {}, + auth: intercomAuth, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + context.app.createListeners({ + events: ['conversation.admin.closed'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async test(context) { + const client = intercomClient(context.auth); + + const response = await client.conversations.search({ + query: { + field: 'state', + operator: '=', + value: 'closed', + }, + pagination: { per_page: 5 }, + }); + + return response.data; + }, + async run(context) { + const payload = context.payload.body as TriggerPayload; + return [payload.data.item]; + }, + sampleData: { + type: 'conversation', + id: '2', + created_at: 1739105560, + updated_at: 1739524230, + waiting_since: null, + snoozed_until: null, + source: { + type: null, + id: '2', + delivered_as: 'admin_initiated', + subject: '', + body: '', + author: { + type: 'admin', + id: '8055721', + name: 'Fin', + email: 'operator+k2gbyfxu@intercom.io', + }, + attachments: [], + url: null, + redacted: false, + }, + contacts: { + type: 'contact.list', + contacts: [ + { + type: 'contact', + id: '67a87380c16da6b56c76bbb7', + external_id: '1234567', + }, + ], + }, + first_contact_reply: null, + admin_assignee_id: 8055717, + team_assignee_id: null, + open: true, + state: 'closed', + read: false, + tags: { + type: 'tag.list', + tags: [], + }, + priority: 'not_priority', + sla_applied: null, + statistics: { + type: 'conversation_statistics', + time_to_assignment: null, + time_to_admin_reply: null, + time_to_first_close: null, + time_to_last_close: null, + median_time_to_reply: null, + first_contact_reply_at: null, + first_assignment_at: null, + first_admin_reply_at: null, + first_close_at: null, + last_assignment_at: null, + last_assignment_admin_reply_at: null, + last_contact_reply_at: null, + last_admin_reply_at: null, + last_close_at: null, + last_closed_by_id: null, + count_reopens: 0, + count_assignments: 0, + count_conversation_parts: 9, + }, + conversation_rating: null, + teammates: { + type: 'admin.list', + admins: [ + { + type: 'admin', + id: '8055717', + }, + ], + }, + title: null, + custom_attributes: { + 'Copilot used': false, + 'Ticket category': 'Customer ticket', + 'Created by': 8055721, + }, + topics: { + type: 'topic.list', + topics: [], + total_count: 0, + }, + ticket: { + type: 'ticket', + id: 2, + url: 'https://app.intercom.com/a/apps/ahah/conversations/2', + custom_attributes: { + _default_title_: { + value: 'fdfdf', + type: 'string', + }, + _default_description_: { + value: 'dfdfdf', + type: 'string', + }, + List: { + value: null, + type: 'list', + }, + Number: { + value: null, + type: 'integer', + }, + decimal: { + value: null, + type: 'decimal', + }, + bool: { + value: null, + type: 'boolean', + }, + 'date time': { + value: null, + type: 'datetime', + }, + files: { + value: null, + type: 'files', + }, + }, + state: 'closed', + ticket_type: 'zapier', + ticket_type_description: '', + ticket_type_emoji: '🚨', + ticket_custom_state_admin_label: 'In progress', + ticket_custom_state_user_label: 'In progress', + }, + linked_objects: { + type: 'list', + data: [], + total_count: 0, + has_more: false, + }, + ai_agent: null, + ai_agent_participated: false, + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/triggers/conversation-part-tagged.ts b/packages/pieces/community/intercom/src/lib/triggers/conversation-part-tagged.ts new file mode 100644 index 0000000..83b1da2 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/conversation-part-tagged.ts @@ -0,0 +1,54 @@ +import { + createTrigger, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { intercomAuth } from '../..'; +import { intercomClient } from '../common'; +import { tagIdProp } from '../common/props'; + +export const conversationPartTagged = createTrigger({ + name: 'conversationPartTagged', + displayName: 'Tag added to a conversation part', + description: 'Triggers when a conversation part is tagged.', + props: { + tagId: tagIdProp('Tag', false), + }, + auth: intercomAuth, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + context.app.createListeners({ + events: ['conversation_part.tag.created'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + + async run(context) { + const tag = context.propsValue.tagId; + const payloadBody = context.payload.body as IntercomPayloadBodyType; + if (!tag || payloadBody?.data?.item?.tag.id === tag) { + return [payloadBody.data.item]; + } + return []; + }, + sampleData: undefined +}); + +type IntercomPayloadBodyType = { + data: { + item: { + tag: { + id: string; + }; + }; + }; +}; diff --git a/packages/pieces/community/intercom/src/lib/triggers/conversation-rated.ts b/packages/pieces/community/intercom/src/lib/triggers/conversation-rated.ts new file mode 100644 index 0000000..799e31c --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/conversation-rated.ts @@ -0,0 +1,180 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomAuth } from '../..'; +import { intercomClient, TriggerPayload } from '../common'; + +export const conversationRated = createTrigger({ + name: 'conversationRated', + displayName: 'Conversation was rated', + description: 'Triggers when a conversation is rated', + props: {}, + auth: intercomAuth, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + context.app.createListeners({ + events: ['conversation.rating.added'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async test(context) { + const client = intercomClient(context.auth); + + const response = await client.conversations.list({ per_page: 10 }); + + return response.data; + }, + async run(context) { + const payload = context.payload.body as TriggerPayload; + return [payload.data.item]; + }, + sampleData: { + type: 'conversation', + id: '2', + created_at: 1739105560, + updated_at: 1739524230, + waiting_since: null, + snoozed_until: null, + source: { + type: null, + id: '2', + delivered_as: 'admin_initiated', + subject: '', + body: '', + author: { + type: 'admin', + id: '8055721', + name: 'Fin', + email: 'operator+k2gbyfxu@intercom.io', + }, + attachments: [], + url: null, + redacted: false, + }, + contacts: { + type: 'contact.list', + contacts: [ + { + type: 'contact', + id: '67a87380c16da6b56c76bbb7', + external_id: '1234567', + }, + ], + }, + first_contact_reply: null, + admin_assignee_id: 8055717, + team_assignee_id: null, + open: true, + state: 'open', + read: false, + tags: { + type: 'tag.list', + tags: [], + }, + priority: 'not_priority', + sla_applied: null, + statistics: { + type: 'conversation_statistics', + time_to_assignment: null, + time_to_admin_reply: null, + time_to_first_close: null, + time_to_last_close: null, + median_time_to_reply: null, + first_contact_reply_at: null, + first_assignment_at: null, + first_admin_reply_at: null, + first_close_at: null, + last_assignment_at: null, + last_assignment_admin_reply_at: null, + last_contact_reply_at: null, + last_admin_reply_at: null, + last_close_at: null, + last_closed_by_id: null, + count_reopens: 0, + count_assignments: 0, + count_conversation_parts: 9, + }, + conversation_rating: null, + teammates: { + type: 'admin.list', + admins: [ + { + type: 'admin', + id: '8055717', + }, + ], + }, + title: null, + custom_attributes: { + 'Copilot used': false, + 'Ticket category': 'Customer ticket', + 'Created by': 8055721, + }, + topics: { + type: 'topic.list', + topics: [], + total_count: 0, + }, + ticket: { + type: 'ticket', + id: 2, + url: 'https://app.intercom.com/a/apps/ahah/conversations/2', + custom_attributes: { + _default_title_: { + value: 'fdfdf', + type: 'string', + }, + _default_description_: { + value: 'dfdfdf', + type: 'string', + }, + List: { + value: null, + type: 'list', + }, + Number: { + value: null, + type: 'integer', + }, + decimal: { + value: null, + type: 'decimal', + }, + bool: { + value: null, + type: 'boolean', + }, + 'date time': { + value: null, + type: 'datetime', + }, + files: { + value: null, + type: 'files', + }, + }, + state: 'in_progress', + ticket_type: 'zapier', + ticket_type_description: '', + ticket_type_emoji: '🚨', + ticket_custom_state_admin_label: 'In progress', + ticket_custom_state_user_label: 'In progress', + }, + linked_objects: { + type: 'list', + data: [], + total_count: 0, + has_more: false, + }, + ai_agent: null, + ai_agent_participated: false, + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/triggers/conversation-snoozed.ts b/packages/pieces/community/intercom/src/lib/triggers/conversation-snoozed.ts new file mode 100644 index 0000000..53dbd68 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/conversation-snoozed.ts @@ -0,0 +1,33 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomAuth } from '../..'; +import { intercomClient } from '../common'; + +export const conversationSnoozed = createTrigger({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'conversationSnoozed', + displayName: 'Conversation snoozed', + description: 'Triggers when a conversation is snoozed', + props: {}, + sampleData: undefined, + auth: intercomAuth, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + context.app.createListeners({ + events: ['conversation.admin.snoozed'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/triggers/conversation-unsnoozed.ts b/packages/pieces/community/intercom/src/lib/triggers/conversation-unsnoozed.ts new file mode 100644 index 0000000..422ab36 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/conversation-unsnoozed.ts @@ -0,0 +1,33 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomAuth } from '../..'; +import { intercomClient } from '../common'; + +export const conversationUnsnoozed = createTrigger({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'conversationUnsnoozed', + displayName: 'Conversation unsnoozed', + description: 'Triggers when a conversation is unsnoozed', + props: {}, + sampleData: undefined, + auth: intercomAuth, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + context.app.createListeners({ + events: ['conversation.admin.unsnoozed'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/triggers/lead-added-email.ts b/packages/pieces/community/intercom/src/lib/triggers/lead-added-email.ts new file mode 100644 index 0000000..24b2fba --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/lead-added-email.ts @@ -0,0 +1,143 @@ +import { intercomAuth } from '../../index'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomClient, TriggerPayload } from '../common'; + +export const leadAddedEmailTrigger = createTrigger({ + auth: intercomAuth, + name: 'lead-added-email', + displayName: 'Lead Added Email', + description: 'Triggers when a lead enters an email address.', + props: {}, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + context.app.createListeners({ + events: ['contact.lead.added_email'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async test(context) { + const client = intercomClient(context.auth); + + const response = await client.contacts.search({ + query: { + field: 'role', + operator: '=', + value: 'lead', + }, + pagination: { per_page: 5 }, + }); + + return response.data; + }, + async run(context) { + const payload = context.payload.body as TriggerPayload; + return [payload.data.item]; + }, + sampleData: { + type: 'contact', + id: '67a9b9dfcc14109e073fbe19', + workspace_id: 'nzekhfwb', + external_id: '5b803f65-bcec-4198-b4f4-a0588454b537', + role: 'lead', + email: 'john.doe@example.com', + phone: null, + name: 'John Doe', + avatar: null, + owner_id: null, + social_profiles: { + type: 'list', + data: [], + }, + has_hard_bounced: false, + marked_email_as_spam: false, + unsubscribed_from_emails: false, + created_at: '2025-02-10T08:33:35.910+00:00', + updated_at: '2025-02-10T08:33:35.907+00:00', + signed_up_at: null, + last_seen_at: null, + last_replied_at: null, + last_contacted_at: null, + last_email_opened_at: null, + last_email_clicked_at: null, + language_override: null, + browser: null, + browser_version: null, + browser_language: null, + os: null, + location: { + type: 'location', + country: null, + region: null, + city: null, + country_code: null, + continent_code: null, + }, + android_app_name: null, + android_app_version: null, + android_device: null, + android_os_version: null, + android_sdk_version: null, + android_last_seen_at: null, + ios_app_name: null, + ios_app_version: null, + ios_device: null, + ios_os_version: null, + ios_sdk_version: null, + ios_last_seen_at: null, + custom_attributes: {}, + tags: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/tags', + total_count: 0, + has_more: false, + }, + notes: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/notes', + total_count: 0, + has_more: false, + }, + companies: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/companies', + total_count: 0, + has_more: false, + }, + opted_out_subscription_types: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/subscriptions', + total_count: 0, + has_more: false, + }, + opted_in_subscription_types: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/subscriptions', + total_count: 0, + has_more: false, + }, + utm_campaign: null, + utm_content: null, + utm_medium: null, + utm_source: null, + utm_term: null, + referrer: null, + sms_consent: false, + unsubscribed_from_sms: false, + enabled_push_messaging: null, + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/triggers/lead-converted-to-user.ts b/packages/pieces/community/intercom/src/lib/triggers/lead-converted-to-user.ts new file mode 100644 index 0000000..172efab --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/lead-converted-to-user.ts @@ -0,0 +1,144 @@ +import { intercomAuth } from '../../index'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomClient, TriggerPayload } from '../common'; + +export const leadConvertedToUserTrigger = createTrigger({ + auth: intercomAuth, + name: 'lead-converted-to-user', + displayName: 'Lead Converted To User', + description: 'Triggers when a lead is converted to a user.', + props: {}, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + context.app.createListeners({ + events: ['contact.merged'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async test(context) { + const client = intercomClient(context.auth); + + const response = await client.contacts.search({ + query:{ + field:'role', + operator:'=', + value:'user' + }, + pagination:{per_page:5} + + }); + + return response.data; + }, + async run(context) { + const payload = context.payload.body as TriggerPayload; + return [payload.data.item]; + }, + sampleData: { + type: 'contact', + id: '67a9b9dfcc14109e073fbe19', + workspace_id: 'nzekhfwb', + external_id: '5b803f65-bcec-4198-b4f4-a0588454b537', + role: 'user', + email: 'john.doe@example.com', + phone: null, + name: 'John Doe', + avatar: null, + owner_id: null, + social_profiles: { + type: 'list', + data: [], + }, + has_hard_bounced: false, + marked_email_as_spam: false, + unsubscribed_from_emails: false, + created_at: '2025-02-10T08:33:35.910+00:00', + updated_at: '2025-02-10T08:33:35.907+00:00', + signed_up_at: null, + last_seen_at: null, + last_replied_at: null, + last_contacted_at: null, + last_email_opened_at: null, + last_email_clicked_at: null, + language_override: null, + browser: null, + browser_version: null, + browser_language: null, + os: null, + location: { + type: 'location', + country: null, + region: null, + city: null, + country_code: null, + continent_code: null, + }, + android_app_name: null, + android_app_version: null, + android_device: null, + android_os_version: null, + android_sdk_version: null, + android_last_seen_at: null, + ios_app_name: null, + ios_app_version: null, + ios_device: null, + ios_os_version: null, + ios_sdk_version: null, + ios_last_seen_at: null, + custom_attributes: {}, + tags: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/tags', + total_count: 0, + has_more: false, + }, + notes: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/notes', + total_count: 0, + has_more: false, + }, + companies: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/companies', + total_count: 0, + has_more: false, + }, + opted_out_subscription_types: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/subscriptions', + total_count: 0, + has_more: false, + }, + opted_in_subscription_types: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/subscriptions', + total_count: 0, + has_more: false, + }, + utm_campaign: null, + utm_content: null, + utm_medium: null, + utm_source: null, + utm_term: null, + referrer: null, + sms_consent: false, + unsubscribed_from_sms: false, + enabled_push_messaging: null, + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/triggers/new-company.ts b/packages/pieces/community/intercom/src/lib/triggers/new-company.ts new file mode 100644 index 0000000..f192112 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/new-company.ts @@ -0,0 +1,136 @@ +import { intercomAuth } from '../../index'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomClient, TriggerPayload } from '../common'; + +export const newCompanyTrigger = createTrigger({ + auth: intercomAuth, + name: 'new-company', + displayName: 'New Company', + description: 'Triggers when a new company is created.', + props: {}, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + context.app.createListeners({ + events: ['company.created'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async test(context) { + const client = intercomClient(context.auth); + + const response = await client.companies.list({ per_page: 5 }); + + return response.data; + }, + async run(context) { + const payload = context.payload.body as TriggerPayload; + return [payload.data.item]; + }, + sampleData: { + type: 'contact', + id: '67a9b9dfcc14109e073fbe19', + workspace_id: 'nzekhfwb', + external_id: '5b803f65-bcec-4198-b4f4-a0588454b537', + role: 'lead', + email: 'john.doe@example.com', + phone: null, + name: 'John Doe', + avatar: null, + owner_id: null, + social_profiles: { + type: 'list', + data: [], + }, + has_hard_bounced: false, + marked_email_as_spam: false, + unsubscribed_from_emails: false, + created_at: '2025-02-10T08:33:35.910+00:00', + updated_at: '2025-02-10T08:33:35.907+00:00', + signed_up_at: null, + last_seen_at: null, + last_replied_at: null, + last_contacted_at: null, + last_email_opened_at: null, + last_email_clicked_at: null, + language_override: null, + browser: null, + browser_version: null, + browser_language: null, + os: null, + location: { + type: 'location', + country: null, + region: null, + city: null, + country_code: null, + continent_code: null, + }, + android_app_name: null, + android_app_version: null, + android_device: null, + android_os_version: null, + android_sdk_version: null, + android_last_seen_at: null, + ios_app_name: null, + ios_app_version: null, + ios_device: null, + ios_os_version: null, + ios_sdk_version: null, + ios_last_seen_at: null, + custom_attributes: {}, + tags: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/tags', + total_count: 0, + has_more: false, + }, + notes: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/notes', + total_count: 0, + has_more: false, + }, + companies: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/companies', + total_count: 0, + has_more: false, + }, + opted_out_subscription_types: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/subscriptions', + total_count: 0, + has_more: false, + }, + opted_in_subscription_types: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/subscriptions', + total_count: 0, + has_more: false, + }, + utm_campaign: null, + utm_content: null, + utm_medium: null, + utm_source: null, + utm_term: null, + referrer: null, + sms_consent: false, + unsubscribed_from_sms: false, + enabled_push_messaging: null, + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/triggers/new-conversation-from-user.ts b/packages/pieces/community/intercom/src/lib/triggers/new-conversation-from-user.ts new file mode 100644 index 0000000..d219946 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/new-conversation-from-user.ts @@ -0,0 +1,180 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomAuth } from '../..'; +import { intercomClient, TriggerPayload } from '../common'; + +export const newConversationFromUser = createTrigger({ + name: 'newConversationFromUser', + displayName: 'New Conversation', + description: 'Triggers when a conversation is created by a user or lead (not an admin).', + props: {}, + auth: intercomAuth, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + context.app.createListeners({ + events: ['conversation.user.created'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async test(context) { + const client = intercomClient(context.auth); + + const response = await client.conversations.list({ per_page: 10 }); + + return response.data; + }, + async run(context) { + const payload = context.payload.body as TriggerPayload; + return [payload.data.item]; + }, + sampleData: { + type: 'conversation', + id: '2', + created_at: 1739105560, + updated_at: 1739524230, + waiting_since: null, + snoozed_until: null, + source: { + type: null, + id: '2', + delivered_as: 'admin_initiated', + subject: '', + body: '', + author: { + type: 'admin', + id: '8055721', + name: 'Fin', + email: 'operator+k2gbyfxu@intercom.io', + }, + attachments: [], + url: null, + redacted: false, + }, + contacts: { + type: 'contact.list', + contacts: [ + { + type: 'contact', + id: '67a87380c16da6b56c76bbb7', + external_id: '1234567', + }, + ], + }, + first_contact_reply: null, + admin_assignee_id: 8055717, + team_assignee_id: null, + open: true, + state: 'open', + read: false, + tags: { + type: 'tag.list', + tags: [], + }, + priority: 'not_priority', + sla_applied: null, + statistics: { + type: 'conversation_statistics', + time_to_assignment: null, + time_to_admin_reply: null, + time_to_first_close: null, + time_to_last_close: null, + median_time_to_reply: null, + first_contact_reply_at: null, + first_assignment_at: null, + first_admin_reply_at: null, + first_close_at: null, + last_assignment_at: null, + last_assignment_admin_reply_at: null, + last_contact_reply_at: null, + last_admin_reply_at: null, + last_close_at: null, + last_closed_by_id: null, + count_reopens: 0, + count_assignments: 0, + count_conversation_parts: 9, + }, + conversation_rating: null, + teammates: { + type: 'admin.list', + admins: [ + { + type: 'admin', + id: '8055717', + }, + ], + }, + title: null, + custom_attributes: { + 'Copilot used': false, + 'Ticket category': 'Customer ticket', + 'Created by': 8055721, + }, + topics: { + type: 'topic.list', + topics: [], + total_count: 0, + }, + ticket: { + type: 'ticket', + id: 2, + url: 'https://app.intercom.com/a/apps/ahah/conversations/2', + custom_attributes: { + _default_title_: { + value: 'fdfdf', + type: 'string', + }, + _default_description_: { + value: 'dfdfdf', + type: 'string', + }, + List: { + value: null, + type: 'list', + }, + Number: { + value: null, + type: 'integer', + }, + decimal: { + value: null, + type: 'decimal', + }, + bool: { + value: null, + type: 'boolean', + }, + 'date time': { + value: null, + type: 'datetime', + }, + files: { + value: null, + type: 'files', + }, + }, + state: 'in_progress', + ticket_type: 'zapier', + ticket_type_description: '', + ticket_type_emoji: '🚨', + ticket_custom_state_admin_label: 'In progress', + ticket_custom_state_user_label: 'In progress', + }, + linked_objects: { + type: 'list', + data: [], + total_count: 0, + has_more: false, + }, + ai_agent: null, + ai_agent_participated: false, + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/triggers/new-lead.ts b/packages/pieces/community/intercom/src/lib/triggers/new-lead.ts new file mode 100644 index 0000000..cf24fde --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/new-lead.ts @@ -0,0 +1,143 @@ +import { intercomAuth } from '../../index'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomClient, TriggerPayload } from '../common'; + +export const newLeadTrigger = createTrigger({ + auth: intercomAuth, + name: 'new-lead', + displayName: 'New Lead', + description: 'Triggers when a new lead is created.', + props: {}, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + context.app.createListeners({ + events: ['contact.lead.created'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async test(context) { + const client = intercomClient(context.auth); + + const response = await client.contacts.search({ + query: { + field: 'role', + operator: '=', + value: 'lead', + }, + pagination: { per_page: 5 }, + }); + + return response.data; + }, + async run(context) { + const payload = context.payload.body as TriggerPayload; + return [payload.data.item]; + }, + sampleData: { + type: 'contact', + id: '67a9b9dfcc14109e073fbe19', + workspace_id: 'nzekhfwb', + external_id: '5b803f65-bcec-4198-b4f4-a0588454b537', + role: 'lead', + email: 'john.doe@example.com', + phone: null, + name: 'John Doe', + avatar: null, + owner_id: null, + social_profiles: { + type: 'list', + data: [], + }, + has_hard_bounced: false, + marked_email_as_spam: false, + unsubscribed_from_emails: false, + created_at: '2025-02-10T08:33:35.910+00:00', + updated_at: '2025-02-10T08:33:35.907+00:00', + signed_up_at: null, + last_seen_at: null, + last_replied_at: null, + last_contacted_at: null, + last_email_opened_at: null, + last_email_clicked_at: null, + language_override: null, + browser: null, + browser_version: null, + browser_language: null, + os: null, + location: { + type: 'location', + country: null, + region: null, + city: null, + country_code: null, + continent_code: null, + }, + android_app_name: null, + android_app_version: null, + android_device: null, + android_os_version: null, + android_sdk_version: null, + android_last_seen_at: null, + ios_app_name: null, + ios_app_version: null, + ios_device: null, + ios_os_version: null, + ios_sdk_version: null, + ios_last_seen_at: null, + custom_attributes: {}, + tags: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/tags', + total_count: 0, + has_more: false, + }, + notes: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/notes', + total_count: 0, + has_more: false, + }, + companies: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/companies', + total_count: 0, + has_more: false, + }, + opted_out_subscription_types: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/subscriptions', + total_count: 0, + has_more: false, + }, + opted_in_subscription_types: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/subscriptions', + total_count: 0, + has_more: false, + }, + utm_campaign: null, + utm_content: null, + utm_medium: null, + utm_source: null, + utm_term: null, + referrer: null, + sms_consent: false, + unsubscribed_from_sms: false, + enabled_push_messaging: null, + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/triggers/new-ticket.ts b/packages/pieces/community/intercom/src/lib/triggers/new-ticket.ts new file mode 100644 index 0000000..407c82d --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/new-ticket.ts @@ -0,0 +1,284 @@ +import { intercomAuth } from '../../index'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomClient, TriggerPayload } from '../common'; + +export const newTicketTrigger = createTrigger({ + auth: intercomAuth, + name: 'new-ticket', + displayName: 'New Ticket', + description: 'Triggers when a new ticket is created.', + props: {}, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + context.app.createListeners({ + events: ['ticket.created'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async test(context) { + const client = intercomClient(context.auth); + + const response = await client.tickets.search({ + query: { + field: 'open', + operator: '=', + value: 'true', + }, + pagination: { per_page: 5 }, + }); + + return response.data; + }, + async run(context) { + const payload = context.payload.body as TriggerPayload; + return [payload.data.item]; + }, + sampleData: { + type: 'ticket', + id: '2', + ticket_id: '1', + ticket_attributes: { + _default_title_: 'fdfdf', + _default_description_: 'dfdfdf', + List: null, + Number: null, + decimal: null, + bool: null, + 'date time': null, + files: [], + }, + ticket_state: 'in_progress', + ticket_type: { + type: 'ticket_type', + id: '1', + name: 'test', + description: '', + icon: '🚨', + workspace_id: 'nzekhfwb', + archived: false, + created_at: 1739105534, + updated_at: 1739105534, + is_internal: false, + ticket_type_attributes: { + type: 'list', + data: [ + { + type: 'ticket_type_attribute', + id: '6615713', + workspace_id: 'nzekhfwb', + name: '_default_title_', + description: '', + data_type: 'string', + input_options: { + multiline: false, + }, + order: 0, + required_to_create: false, + required_to_create_for_contacts: false, + visible_on_create: true, + visible_to_contacts: true, + default: true, + ticket_type_id: 1, + archived: false, + created_at: 1739105534, + updated_at: 1739105534, + }, + { + type: 'ticket_type_attribute', + id: '6615714', + workspace_id: 'nzekhfwb', + name: '_default_description_', + description: '', + data_type: 'string', + input_options: { + multiline: true, + }, + order: 1, + required_to_create: false, + required_to_create_for_contacts: false, + visible_on_create: true, + visible_to_contacts: true, + default: true, + ticket_type_id: 1, + archived: false, + created_at: 1739105534, + updated_at: 1739105534, + }, + ], + }, + category: 'Customer', + }, + contacts: { + type: 'contact.list', + contacts: [ + { + type: 'contact', + id: '67a87380c16da6b56c76bbb7', + external_id: '1234567', + }, + ], + }, + admin_assignee_id: '8055717', + team_assignee_id: '0', + created_at: 1739105560, + updated_at: 1739524230, + ticket_parts: { + type: 'ticket_part.list', + ticket_parts: [ + { + type: 'ticket_part', + id: '3', + part_type: 'ticket_state_updated_by_admin', + ticket_state: 'submitted', + previous_ticket_state: 'submitted', + created_at: 1739105561, + updated_at: 1739105561, + author: { + id: '8055721', + type: 'bot', + name: 'Fin', + email: 'operator+k2gbyfxu@intercom.io', + }, + attachments: [], + redacted: false, + }, + { + type: 'ticket_part', + id: '5', + part_type: 'assignment', + body: '

hghh

', + created_at: 1739109004, + updated_at: 1739111399, + assigned_to: { + type: 'admin', + id: '8055717', + }, + author: { + id: '8055717', + type: 'admin', + name: 'John Doe', + email: 'johndoe@gmail.com', + }, + attachments: [], + redacted: false, + }, + { + type: 'ticket_part', + id: '6', + part_type: 'conversation_tags_updated', + created_at: 1739109007, + updated_at: 1739109007, + author: { + id: '8055717', + type: 'admin', + name: 'John Doe', + email: 'johndoe@gmail.com', + }, + attachments: [], + redacted: false, + }, + { + type: 'ticket_part', + id: '7', + part_type: 'conversation_tags_updated', + created_at: 1739109008, + updated_at: 1739109008, + author: { + id: '8055717', + type: 'admin', + name: 'John Doe', + email: 'johndoe@gmail.com', + }, + attachments: [], + redacted: false, + }, + { + type: 'ticket_part', + id: '14', + part_type: 'conversation_tags_updated', + created_at: 1739111399, + updated_at: 1739111399, + author: { + id: '8055717', + type: 'admin', + name: 'John Doe', + email: 'johndoe@gmail.com', + }, + attachments: [], + redacted: false, + }, + { + type: 'ticket_part', + id: '15', + part_type: 'conversation_tags_updated', + created_at: 1739111400, + updated_at: 1739111400, + author: { + id: '8055717', + type: 'admin', + name: 'John Doe', + email: 'johndoe@gmail.com', + }, + attachments: [], + redacted: false, + }, + { + type: 'ticket_part', + id: '16', + part_type: 'comment', + body: '

ss

', + created_at: 1739111407, + updated_at: 1739111407, + author: { + id: '8055717', + type: 'admin', + name: 'John Doe', + email: 'johndoe@gmail.com', + }, + attachments: [], + redacted: false, + }, + { + type: 'ticket_part', + id: '37', + part_type: 'ticket_state_updated_by_admin', + ticket_state: 'in_progress', + previous_ticket_state: 'submitted', + created_at: 1739524230, + updated_at: 1739524230, + author: { + id: '8055717', + type: 'admin', + name: 'John Doe', + email: 'johndoe@gmail.com', + }, + attachments: [], + redacted: false, + }, + ], + total_count: 8, + }, + open: true, + linked_objects: { + type: 'list', + data: [], + total_count: 0, + has_more: false, + }, + category: 'Customer', + is_shared: true, + company_id: '67a8a24c4670c9f1995b2382', + ticket_state_internal_label: 'In progress', + ticket_state_external_label: 'In progress', + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/triggers/new-user.ts b/packages/pieces/community/intercom/src/lib/triggers/new-user.ts new file mode 100644 index 0000000..1bb2306 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/new-user.ts @@ -0,0 +1,143 @@ +import { intercomAuth } from '../../index'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomClient, TriggerPayload } from '../common'; + +export const newUserTrigger = createTrigger({ + auth: intercomAuth, + name: 'new-user', + displayName: 'New User', + description: 'Triggers when a new user is created.', + props: {}, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + context.app.createListeners({ + events: ['contact.user.created'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async test(context) { + const client = intercomClient(context.auth); + + const response = await client.contacts.search({ + query: { + field: 'role', + operator: '=', + value: 'user', + }, + pagination: { per_page: 5 }, + }); + + return response.data; + }, + async run(context) { + const payload = context.payload.body as TriggerPayload; + return [payload.data.item]; + }, + sampleData: { + type: 'contact', + id: '67a9b9dfcc14109e073fbe19', + workspace_id: 'nzekhfwb', + external_id: '5b803f65-bcec-4198-b4f4-a0588454b537', + role: 'user', + email: 'john.doe@example.com', + phone: null, + name: 'John Doe', + avatar: null, + owner_id: null, + social_profiles: { + type: 'list', + data: [], + }, + has_hard_bounced: false, + marked_email_as_spam: false, + unsubscribed_from_emails: false, + created_at: '2025-02-10T08:33:35.910+00:00', + updated_at: '2025-02-10T08:33:35.907+00:00', + signed_up_at: null, + last_seen_at: null, + last_replied_at: null, + last_contacted_at: null, + last_email_opened_at: null, + last_email_clicked_at: null, + language_override: null, + browser: null, + browser_version: null, + browser_language: null, + os: null, + location: { + type: 'location', + country: null, + region: null, + city: null, + country_code: null, + continent_code: null, + }, + android_app_name: null, + android_app_version: null, + android_device: null, + android_os_version: null, + android_sdk_version: null, + android_last_seen_at: null, + ios_app_name: null, + ios_app_version: null, + ios_device: null, + ios_os_version: null, + ios_sdk_version: null, + ios_last_seen_at: null, + custom_attributes: {}, + tags: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/tags', + total_count: 0, + has_more: false, + }, + notes: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/notes', + total_count: 0, + has_more: false, + }, + companies: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/companies', + total_count: 0, + has_more: false, + }, + opted_out_subscription_types: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/subscriptions', + total_count: 0, + has_more: false, + }, + opted_in_subscription_types: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/subscriptions', + total_count: 0, + has_more: false, + }, + utm_campaign: null, + utm_content: null, + utm_medium: null, + utm_source: null, + utm_term: null, + referrer: null, + sms_consent: false, + unsubscribed_from_sms: false, + enabled_push_messaging: null, + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/triggers/note-added-to-conversation.ts b/packages/pieces/community/intercom/src/lib/triggers/note-added-to-conversation.ts new file mode 100644 index 0000000..318485d --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/note-added-to-conversation.ts @@ -0,0 +1,64 @@ +import { createTrigger, TriggerStrategy, Property } from '@activepieces/pieces-framework'; +import { stripHtml } from 'string-strip-html'; +import { intercomAuth } from '../..'; +import { intercomClient } from '../common'; + +export const noteAddedToConversation = createTrigger({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'noteAddedToConversation', + displayName: 'Note added to conversation', + description: 'Triggers when a note is added to a conversation', + props: { + keyword: Property.ShortText({ + displayName: 'Keyword (optional)', + required: false, + }), + }, + sampleData: undefined, + auth: intercomAuth, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + context.app.createListeners({ + events: ['conversation.admin.noted'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async run(context) { + const keyword = context.propsValue.keyword; + const payloadBody = context.payload.body as IntercomPayloadBodyType; + if ( + !keyword || + payloadBody?.data?.item?.conversation_parts.conversation_parts.some((part) => + stripHtml(part.body) + .result.split(/\s/) + .some((word) => word === keyword), + ) + ) { + return [payloadBody]; + } + return []; + }, +}); + +type IntercomPayloadBodyType = { + data: { + item: { + conversation_parts: { + conversation_parts: { + body: string; + }[]; + }; + }; + }; +}; diff --git a/packages/pieces/community/intercom/src/lib/triggers/reply-from-admin.ts b/packages/pieces/community/intercom/src/lib/triggers/reply-from-admin.ts new file mode 100644 index 0000000..4af57f8 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/reply-from-admin.ts @@ -0,0 +1,32 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomAuth } from '../..'; +import { intercomClient } from '../common'; + +export const replyFromAdmin = createTrigger({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'replyFromAdmin', + displayName: 'Reply from an Intercom admin', + description: 'Triggers when a reply is received from an Intercom admin (not a user or lead)', + props: {}, + sampleData: undefined, + auth: intercomAuth, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + context.app.createListeners({ + events: ['conversation.admin.replied'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/triggers/reply-from-user.ts b/packages/pieces/community/intercom/src/lib/triggers/reply-from-user.ts new file mode 100644 index 0000000..3e33228 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/reply-from-user.ts @@ -0,0 +1,32 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomAuth } from '../..'; +import { intercomClient } from '../common'; + +export const replyFromUser = createTrigger({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'replyFromUser', + displayName: 'Reply from a user or lead', + description: 'Triggers when a reply is received from a user or lead (not an admin)', + props: {}, + sampleData: undefined, + auth: intercomAuth, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + context.app.createListeners({ + events: ['conversation.user.replied'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/intercom/src/lib/triggers/tag-added-to-lead.ts b/packages/pieces/community/intercom/src/lib/triggers/tag-added-to-lead.ts new file mode 100644 index 0000000..36d4ae7 --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/tag-added-to-lead.ts @@ -0,0 +1,233 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomAuth } from '../..'; +import { intercomClient } from '../common'; +import { tagIdProp } from '../common/props'; + +export const tagAddedToLeadTrigger = createTrigger({ + name: 'tag-added-to-lead', + displayName: 'Tag Added to Lead', + description: 'Triggers when a tag is added to a lead.', + props: { + tagId: tagIdProp('Tag', false), + }, + auth: intercomAuth, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + context.app.createListeners({ + events: ['contact.lead.tag.created'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async test(context) { + const client = intercomClient(context.auth); + + if (context.propsValue.tagId) { + const response = await client.contacts.search({ + query: { + operator: 'AND', + value: [ + { + field: 'tag_id', + operator: '=', + value: context.propsValue.tagId, + }, + { + field: 'role', + operator: '=', + value: 'lead', + }, + ], + }, + pagination: { per_page: 5 }, + }); + + const tag = await client.tags.find({ tag_id: context.propsValue.tagId }); + + return response.data.map((item) => { + return { + type: 'contact_tag', + tag: { + type: tag.type, + id: tag.id, + name: tag.name, + }, + contact: item, + }; + }); + } + + const response = await client.contacts.search({ + query: { + operator: 'AND', + value: [ + { + field: 'tag_id', + operator: '!=', + value: '', + }, + { + field: 'role', + operator: '=', + value: 'lead', + }, + ], + }, + pagination: { per_page: 100 }, + }); + + let count = 0; + const items = []; + for await (const lead of response) { + if (lead.tags && lead.tags.data.length > 0) { + const tag = await client.tags.find({ tag_id: lead.tags.data[0].id }); + items.push({ + type: 'contact_tag', + tag: { + type: tag.type, + id: tag.id, + name: tag.name, + }, + contact: lead, + }); + count++; + } + + if (count >= 5) break; + } + + return items; + }, + async run(context) { + const tag = context.propsValue.tagId; + const payloadBody = context.payload.body as IntercomPayloadBodyType; + if (!tag || payloadBody?.data?.item?.tag.id === tag) { + return [payloadBody.data.item]; + } + return []; + }, + sampleData: { + type: 'contact_tag', + tag: { + type: 'tag', + id: '34', + name: 'Manual tag', + }, + contact: { + type: 'contact', + id: '67a9b9dfcc14109e073fbe19', + workspace_id: 'nzekhfwb', + external_id: '5b803f65-bcec-4198-b4f4-a0588454b537', + role: 'lead', + email: 'john.doe@example.com', + phone: null, + name: 'John Doe', + avatar: null, + owner_id: null, + social_profiles: { + type: 'list', + data: [], + }, + has_hard_bounced: false, + marked_email_as_spam: false, + unsubscribed_from_emails: false, + created_at: '2025-02-10T08:33:35.910+00:00', + updated_at: '2025-02-10T08:33:35.907+00:00', + signed_up_at: null, + last_seen_at: null, + last_replied_at: null, + last_contacted_at: null, + last_email_opened_at: null, + last_email_clicked_at: null, + language_override: null, + browser: null, + browser_version: null, + browser_language: null, + os: null, + location: { + type: 'location', + country: null, + region: null, + city: null, + country_code: null, + continent_code: null, + }, + android_app_name: null, + android_app_version: null, + android_device: null, + android_os_version: null, + android_sdk_version: null, + android_last_seen_at: null, + ios_app_name: null, + ios_app_version: null, + ios_device: null, + ios_os_version: null, + ios_sdk_version: null, + ios_last_seen_at: null, + custom_attributes: {}, + tags: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/tags', + total_count: 0, + has_more: false, + }, + notes: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/notes', + total_count: 0, + has_more: false, + }, + companies: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/companies', + total_count: 0, + has_more: false, + }, + opted_out_subscription_types: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/subscriptions', + total_count: 0, + has_more: false, + }, + opted_in_subscription_types: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/subscriptions', + total_count: 0, + has_more: false, + }, + utm_campaign: null, + utm_content: null, + utm_medium: null, + utm_source: null, + utm_term: null, + referrer: null, + sms_consent: false, + unsubscribed_from_sms: false, + enabled_push_messaging: null, + }, + }, +}); + +type IntercomPayloadBodyType = { + data: { + item: { + tag: { + id: string; + }; + }; + }; +}; diff --git a/packages/pieces/community/intercom/src/lib/triggers/tag-added-to-user.ts b/packages/pieces/community/intercom/src/lib/triggers/tag-added-to-user.ts new file mode 100644 index 0000000..eeb7cfe --- /dev/null +++ b/packages/pieces/community/intercom/src/lib/triggers/tag-added-to-user.ts @@ -0,0 +1,233 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { intercomAuth } from '../..'; +import { intercomClient } from '../common'; +import { tagIdProp } from '../common/props'; + +export const tagAddedToUserTrigger = createTrigger({ + name: 'tag-added-to-user', + displayName: 'Tag Added to User', + description: 'Triggers when a tag is added to a user.', + props: { + tagId: tagIdProp('Tag', false), + }, + auth: intercomAuth, + type: TriggerStrategy.APP_WEBHOOK, + async onEnable(context) { + const client = intercomClient(context.auth); + const response = await client.admins.identify(); + + if (!response.app?.id_code) { + throw new Error('Could not find admin id code'); + } + + context.app.createListeners({ + events: ['contact.user.tag.created'], + identifierValue: response['app']['id_code'], + }); + }, + async onDisable(context) { + // implement webhook deletion logic + }, + async test(context) { + const client = intercomClient(context.auth); + + if (context.propsValue.tagId) { + const response = await client.contacts.search({ + query: { + operator: 'AND', + value: [ + { + field: 'tag_id', + operator: '=', + value: context.propsValue.tagId, + }, + { + field: 'role', + operator: '=', + value: 'user', + }, + ], + }, + pagination: { per_page: 5 }, + }); + + const tag = await client.tags.find({ tag_id: context.propsValue.tagId }); + + return response.data.map((item) => { + return { + type: 'contact_tag', + tag: { + type: tag.type, + id: tag.id, + name: tag.name, + }, + contact: item, + }; + }); + } + + const response = await client.contacts.search({ + query: { + operator: 'AND', + value: [ + { + field: 'tag_id', + operator: '!=', + value: '', + }, + { + field: 'role', + operator: '=', + value: 'user', + }, + ], + }, + pagination: { per_page: 100 }, + }); + + let count = 0; + const items = []; + for await (const user of response) { + if (user.tags && user.tags.data.length > 0) { + const tag = await client.tags.find({ tag_id: user.tags.data[0].id }); + items.push({ + type: 'contact_tag', + tag: { + type: tag.type, + id: tag.id, + name: tag.name, + }, + contact: user, + }); + count++; + } + + if (count >= 5) break; + } + + return items; + }, + async run(context) { + const tag = context.propsValue.tagId; + const payloadBody = context.payload.body as IntercomPayloadBodyType; + if (!tag || payloadBody?.data?.item?.tag.id === tag) { + return [payloadBody.data.item]; + } + return []; + }, + sampleData: { + type: 'contact_tag', + tag: { + type: 'tag', + id: '34', + name: 'Manual tag', + }, + contact: { + type: 'contact', + id: '67a9b9dfcc14109e073fbe19', + workspace_id: 'nzekhfwb', + external_id: '5b803f65-bcec-4198-b4f4-a0588454b537', + role: 'user', + email: 'john.doe@example.com', + phone: null, + name: 'John Doe', + avatar: null, + owner_id: null, + social_profiles: { + type: 'list', + data: [], + }, + has_hard_bounced: false, + marked_email_as_spam: false, + unsubscribed_from_emails: false, + created_at: '2025-02-10T08:33:35.910+00:00', + updated_at: '2025-02-10T08:33:35.907+00:00', + signed_up_at: null, + last_seen_at: null, + last_replied_at: null, + last_contacted_at: null, + last_email_opened_at: null, + last_email_clicked_at: null, + language_override: null, + browser: null, + browser_version: null, + browser_language: null, + os: null, + location: { + type: 'location', + country: null, + region: null, + city: null, + country_code: null, + continent_code: null, + }, + android_app_name: null, + android_app_version: null, + android_device: null, + android_os_version: null, + android_sdk_version: null, + android_last_seen_at: null, + ios_app_name: null, + ios_app_version: null, + ios_device: null, + ios_os_version: null, + ios_sdk_version: null, + ios_last_seen_at: null, + custom_attributes: {}, + tags: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/tags', + total_count: 0, + has_more: false, + }, + notes: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/notes', + total_count: 0, + has_more: false, + }, + companies: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/companies', + total_count: 0, + has_more: false, + }, + opted_out_subscription_types: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/subscriptions', + total_count: 0, + has_more: false, + }, + opted_in_subscription_types: { + type: 'list', + data: [], + url: '/contacts/67a9b9dfcc14109e073fbe19/subscriptions', + total_count: 0, + has_more: false, + }, + utm_campaign: null, + utm_content: null, + utm_medium: null, + utm_source: null, + utm_term: null, + referrer: null, + sms_consent: false, + unsubscribed_from_sms: false, + enabled_push_messaging: null, + }, + }, +}); + +type IntercomPayloadBodyType = { + data: { + item: { + tag: { + id: string; + }; + }; + }; +}; diff --git a/packages/pieces/community/intercom/tsconfig.json b/packages/pieces/community/intercom/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/intercom/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/intercom/tsconfig.lib.json b/packages/pieces/community/intercom/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/intercom/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/invoiceninja/.eslintrc.json b/packages/pieces/community/invoiceninja/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/invoiceninja/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/invoiceninja/README.md b/packages/pieces/community/invoiceninja/README.md new file mode 100644 index 0000000..b4c2fb7 --- /dev/null +++ b/packages/pieces/community/invoiceninja/README.md @@ -0,0 +1,7 @@ +# pieces-invoiceninja + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-invoiceninja` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/invoiceninja/package.json b/packages/pieces/community/invoiceninja/package.json new file mode 100644 index 0000000..b039507 --- /dev/null +++ b/packages/pieces/community/invoiceninja/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-invoiceninja", + "version": "0.2.5" +} diff --git a/packages/pieces/community/invoiceninja/project.json b/packages/pieces/community/invoiceninja/project.json new file mode 100644 index 0000000..ff486ce --- /dev/null +++ b/packages/pieces/community/invoiceninja/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-invoiceninja", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/invoiceninja/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/invoiceninja", + "tsConfig": "packages/pieces/community/invoiceninja/tsconfig.lib.json", + "packageJson": "packages/pieces/community/invoiceninja/package.json", + "main": "packages/pieces/community/invoiceninja/src/index.ts", + "assets": [ + "packages/pieces/community/invoiceninja/*.md", + { + "input": "packages/pieces/community/invoiceninja/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/invoiceninja/src/index.ts b/packages/pieces/community/invoiceninja/src/index.ts new file mode 100644 index 0000000..5eed9e6 --- /dev/null +++ b/packages/pieces/community/invoiceninja/src/index.ts @@ -0,0 +1,65 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createTask } from './lib/actions/create-task'; +import { getClient } from './lib/actions/get-client'; +import { getInvoices } from './lib/actions/get-invoices'; +import { getReport } from './lib/actions/get-report'; +import { existsTask } from './lib/actions/task-exists'; +import { createInvoice } from './lib/actions/create-invoice'; +import { createClient } from './lib/actions/create-client'; +import { createRecurringInvoice } from './lib/actions/create-recurring'; +import { actionRecurringInvoice } from './lib/actions/action-recurring'; + +export const invoiceninjaAuth = PieceAuth.CustomAuth({ + props: { + base_url: Property.ShortText({ + displayName: 'Base URL', + description: 'Enter the base URL', + required: true, + }), + access_token: Property.ShortText({ + displayName: 'API Token', + description: 'Enter the API token', + required: true, + }), + }, + description: `Please check https://invoice-ninja.readthedocs.io/en/latest/api_tokens.html#create-token + to see how to get the API token`, + required: true, +}); + +export const invoiceninja = createPiece({ + displayName: 'Invoice Ninja', + description: 'Free open-source invoicing tool', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/invoiceninja.png', + categories: [PieceCategory.ACCOUNTING], + authors: ["buttonsbond","kishanprmr","MoShizzle","AbdulTheActivePiecer","khaledmashaly","abuaboud"], + auth: invoiceninjaAuth, + actions: [ + createTask, + existsTask, + getClient, + getInvoices, + getReport, + createInvoice, + createClient, + createRecurringInvoice, + actionRecurringInvoice, + createCustomApiCallAction({ + baseUrl: (auth) => + `${(auth as { base_url: string }).base_url.replace(/\/$/, '')}/api/v1`, + auth: invoiceninjaAuth, + authMapping: async (auth) => ({ + 'X-Api-Token': (auth as { access_token: string }).access_token, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/invoiceninja/src/lib/actions/action-recurring.ts b/packages/pieces/community/invoiceninja/src/lib/actions/action-recurring.ts new file mode 100644 index 0000000..b9bea2d --- /dev/null +++ b/packages/pieces/community/invoiceninja/src/lib/actions/action-recurring.ts @@ -0,0 +1,89 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; + +import { invoiceninjaAuth } from '../..'; + +export const actionRecurringInvoice = createAction({ + auth: invoiceninjaAuth, + name: 'action_recurring_invoice', + displayName: 'Perform Action on Recurring Invoice', + description: + 'Actions include: start, stop, send_now, restore, archive, delete.', + props: { + recurring_id: Property.LongText({ + displayName: 'Recurring Invoice ID (alphanumeric)', + description: 'Recurring Invoice ID from Invoice Ninja', + required: true, + }), + actionRecurring: Property.StaticDropdown({ + displayName: 'Action to perform', + description: 'Choose one', + defaultValue: 1, + required: true, + options: { + options: [ + { + label: 'Start', + value: 'start', + }, + { + label: 'Stop', + value: 'stop', + }, + { + label: 'Send Now', + value: 'send_now', + }, + { + label: 'Restore', + value: 'restore', + }, + { + label: 'Archive', + value: 'archive', + }, + { + label: 'Delete', + value: 'delete', + }, + ], + }, + }), + }, + + async run(context) { + const INapiToken = context.auth.access_token; + const headers = { + 'X-Api-Token': INapiToken, + 'Content-Type': 'application/json', + }; + + const baseUrl = context.auth.base_url.replace(/\/$/, ''); + const i: string[] = [context.propsValue.recurring_id]; + + const createRequestBody = { + action: context.propsValue.actionRecurring, + ids: i, + }; + const createRequestResponse = await fetch( + `${baseUrl}/api/v1/recurring_invoices/bulk`, + { + method: 'POST', + headers, + body: JSON.stringify(createRequestBody), + } + ); + + if (!createRequestResponse.ok) { + throw new Error( + `Failed to perform action on recurring invoice. Status: ${createRequestResponse.status}` + ); + } + + const createResponseBody = await createRequestResponse.json(); + + return createResponseBody; + }, +}); diff --git a/packages/pieces/community/invoiceninja/src/lib/actions/create-client.ts b/packages/pieces/community/invoiceninja/src/lib/actions/create-client.ts new file mode 100644 index 0000000..9b04771 --- /dev/null +++ b/packages/pieces/community/invoiceninja/src/lib/actions/create-client.ts @@ -0,0 +1,119 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { invoiceninjaAuth } from '../..'; + +export const createClient = createAction({ + auth: invoiceninjaAuth, + name: 'create_client', + displayName: 'Create Client', + description: 'Creates a new client in InvoiceNinja.', + + props: { + client_first_name: Property.LongText({ + displayName: 'Client First Name (alphanumeric)', + description: 'The contact first name for this client (optional)', + required: false, + }), + client_last_name: Property.ShortText({ + displayName: 'Client Last Name (alphanumeric)', + description: 'The contact last name for this client (optional)', + required: false, + }), + client_phone: Property.ShortText({ + displayName: 'Client Contact No (alphanumeric)', + description: 'The contact number for this client (optional)', + required: false, + }), + client_email: Property.ShortText({ + displayName: 'Client e-mail (alphanumeric)', + description: 'The contact email for this client (compulsory)', + required: true, + }), + client_send_email: Property.Checkbox({ + displayName: 'Send invoices to the client', + description: 'Should we send invoices to the client by e-mail?', + defaultValue: false, + required: true, + }), + client_business_name: Property.ShortText({ + displayName: 'Business Name (alphanumeric)', + description: 'Name of this business or natural person (compulsory)', + required: true, + }), + client_tax_no: Property.ShortText({ + displayName: 'Client Tax Number (alphanumeric)', + description: 'Leave blank if not a business (optional)', + required: false, + }), + client_private_notes: Property.ShortText({ + displayName: 'Private notes for client', + description: 'Text not visible for clients (optional)', + required: false, + }), + client_address1: Property.LongText({ + displayName: 'Client address 1 (alphanumeric)', + description: 'Usually street name and number (compulsory)', + required: true, + }), + client_address2: Property.LongText({ + displayName: 'Client address 2 (alphanumeric)', + description: 'Additional address details (optional)', + required: false, + }), + client_city: Property.ShortText({ + displayName: 'Client City/Town (alphanumeric)', + description: 'City or Town name (compulsory)', + required: true, + }), + client_state: Property.ShortText({ + displayName: 'Client State (alphanumeric)', + description: 'State or county or similar (optional)', + required: false, + }), + client_postcode: Property.ShortText({ + displayName: 'Client Postcode (alphanumeric)', + description: 'Postal code (optional)', + required: false, + }), + }, + + async run(context) { + const INapiToken = context.auth.access_token; + const headers = { + 'X-Api-Token': INapiToken, + 'Content-Type': 'application/json', + }; + + const queryParams = new URLSearchParams(); + queryParams.append('name', context.propsValue.client_business_name); + queryParams.append('private_notes', context.propsValue.client_private_notes || ''); + queryParams.append('address1', context.propsValue.client_address1 || ''); + queryParams.append('address2', context.propsValue.client_address2 || ''); + queryParams.append('city', context.propsValue.client_city || ''); + queryParams.append('state', context.propsValue.client_state || ''); + queryParams.append('postal_code', context.propsValue.client_postcode || ''); + queryParams.append('vat_number', context.propsValue.client_tax_no || ''); + const body = { + "contacts": { + "first_name": context.propsValue.client_first_name || "", "last_name": context.propsValue.client_last_name || "", + "phone": context.propsValue.client_phone || "", "email": context.propsValue.client_email || "", "send_email": context.propsValue.client_send_email || false + } + }; + + const baseUrl = context.auth.base_url.replace(/\/$/, ''); + const url = `${baseUrl}/api/v1/clients/?${queryParams.toString()}`; + + const httprequestdata = { + method: HttpMethod.POST, + url, + headers, + body, + }; + + const response = await httpClient.sendRequest(httprequestdata); + return response.body; + }, +}); diff --git a/packages/pieces/community/invoiceninja/src/lib/actions/create-invoice.ts b/packages/pieces/community/invoiceninja/src/lib/actions/create-invoice.ts new file mode 100644 index 0000000..481ca8d --- /dev/null +++ b/packages/pieces/community/invoiceninja/src/lib/actions/create-invoice.ts @@ -0,0 +1,215 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +import { invoiceninjaAuth } from '../..'; + +export const createInvoice = createAction({ + auth: invoiceninjaAuth, + name: 'create_invoice', + displayName: 'Create Invoice', + description: 'Creates an invoice in Invoice Ninja for billing purposes.', + + props: { + client_id: Property.LongText({ + displayName: 'Client ID (alphanumeric)', + description: 'Client ID from Invoice Ninja', + required: true, + }), + purchase_order_no: Property.LongText({ + displayName: 'Purchase Order Number (alphanumeric)', + description: 'Descriptive text or arbitrary number (optional)', + required: false, + }), + discount: Property.LongText({ + displayName: 'Apply discount', + description: 'Enter a number for the whole invoice discount', + defaultValue: '0', + required: true, + }), + discount_type: Property.StaticDropdown({ + displayName: 'Type of discount', + description: 'Select either amount or percentage for invoice discount. Applies to line items and invoice.', + defaultValue: true, + required: true, + options: { + options: [ + { + label: 'Amount', + value: true + }, + { + label: 'Percentage', + value: false + } + ] + } + }), + public_notes: Property.LongText({ + displayName: 'Public notes for invoice', + description: 'Text that may be visible in the client portal (optional)', + required: false, + }), + private_notes: Property.LongText({ + displayName: 'Private notes for invoice', + description: 'Text not visible for clients (optional)', + required: false, + }), + order_items_json: Property.LongText({ + displayName: 'Order Items JSON string', + description: 'e.g., [{ "quantity":1,"product_key":"product key", "discount": "0" }]', + required: true, + }), + send_email: Property.Checkbox({ + displayName: 'Send invoice to the client by InvoiceNinja e-mail?', + description: 'Should we send the invoice to the client on creation?', + defaultValue: false, + required: true, + }), + mark_sent: Property.Checkbox({ + displayName: 'Mark the invoice as sent?', + description: 'Makes the invoice active otherwise remains pending.', + defaultValue: false, + required: true, + }), + due_date: Property.DateTime({ + displayName: 'Invoice due date', + description: 'e.g., 2024-01-20', + required: false, + }), + }, + + async run(context) { + await propsValidation.validateZod(context.propsValue, { + due_date: z.string().datetime().optional(), + }); + + const INapiToken = context.auth.access_token; + const headers = { + 'X-Api-Token': INapiToken, + 'Content-Type': 'application/json', + }; + + const lineItemsArray = JSON.parse(context.propsValue.order_items_json); + + if (!Array.isArray(lineItemsArray)) { + throw new Error('Invalid format for order_items_json. It should be an array of objects.'); + } + + if (lineItemsArray.length === 0) { + throw new Error('The line_items array must not be empty.'); + } + + const isValidLineItem = lineItemsArray.every(item => ( + typeof item === 'object' && + 'quantity' in item && typeof item.quantity === 'number' && + 'product_key' in item && typeof item.product_key === 'string' && + 'discount' in item && typeof item.discount === 'string' + )); + + if (!isValidLineItem) { + throw new Error('Each item in the line_items array must be an object with "quantity" (number), "product_key" (string), and "discount" (string).'); + } + + const baseUrl = context.auth.base_url.replace(/\/$/, ''); + let errorMessages = ''; + + try { + const lineItemsWithDetailsPromises = lineItemsArray.map(async item => { + try { + const getProductDetailsResponse = await fetch(`${baseUrl}/api/v1/products/?product_key=${item.product_key}`, { + method: 'GET', + headers, + }); + + if (!getProductDetailsResponse.ok) { + console.error(`Failed to get product details for ${item.product_key}. Status: ${getProductDetailsResponse.status}`); + errorMessages += `Failed to get product details for ${item.product_key}\n`; + return null; + } + + const productDetailsResponseBody = await getProductDetailsResponse.json(); + const productCount = productDetailsResponseBody.meta.pagination.count; + + if (productCount < 1) { + console.error(`No product details found for ${item.product_key}.`); + errorMessages += `No product details found for ${item.product_key}\n`; + return null; + } + + const productDetails = productDetailsResponseBody.data[0]; + + return { + quantity: item.quantity, + product_key: item.product_key, + product_cost: productDetails.price, + cost: productDetails.price, + notes: productDetails.notes, + discount: item.discount || '0', + is_amount_discount: context.propsValue.discount_type, + tax_name1: productDetails.tax_name1, + tax_rate1: productDetails.tax_rate1, + tax_id: productDetails.tax_id, + }; + } catch (error) { + console.error(`Error getting product details for ${item.product_key}:`, error); + errorMessages += `Error getting product details for ${item.product_key}: ${error}\n`; + return null; + } + }); + + const lineItemsWithDetails = await Promise.all(lineItemsWithDetailsPromises); + + if (errorMessages) { + // If there are error messages, throw an error with the accumulated messages + throw new Error(errorMessages.trim()); + } + + const today = new Date(); + const year = today.getFullYear(); + const month = today.getMonth() + 1; + const day = today.getDate(); + const formattedDate = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`; + + const createInvoiceRequestBody = { + due_date: context.propsValue.due_date, + date: formattedDate, + client_id: context.propsValue.client_id || '', + po_number: context.propsValue.purchase_order_no || '', + public_notes: context.propsValue.public_notes || '', + private_notes: context.propsValue.private_notes || '', + line_items: lineItemsWithDetails, + discount: context.propsValue.discount, + is_amount_discount: context.propsValue.discount_type, + send_email: context.propsValue.send_email, + mark_sent: context.propsValue.mark_sent, + }; + + const createInvoiceResponse = await fetch(`${baseUrl}/api/v1/invoices`, { + method: 'POST', + headers, + body: JSON.stringify(createInvoiceRequestBody), + }); + + if (!createInvoiceResponse.ok) { + throw new Error(`Failed to create invoice. Status: ${createInvoiceResponse.status}`); + } + + const createInvoiceResponseBody = await createInvoiceResponse.json(); + + return createInvoiceResponseBody; + } catch (error) { + console.error('Error creating invoice or getting product details:', error); + if (errorMessages) { + // If there are error messages, throw an error with the accumulated messages + throw new Error(errorMessages.trim()); + } else { + // If there are no error messages, throw the original error + throw error; + } + } + }, +}); diff --git a/packages/pieces/community/invoiceninja/src/lib/actions/create-recurring.ts b/packages/pieces/community/invoiceninja/src/lib/actions/create-recurring.ts new file mode 100644 index 0000000..ff08419 --- /dev/null +++ b/packages/pieces/community/invoiceninja/src/lib/actions/create-recurring.ts @@ -0,0 +1,286 @@ +import { + createAction, + Property, + } from '@activepieces/pieces-framework'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +import { invoiceninjaAuth } from '../..'; + +export const createRecurringInvoice = createAction({ + auth: invoiceninjaAuth, + name: 'create_recurring_invoice', + displayName: 'Create Recurring Invoice', + description: 'Creates a recurring invoice in Invoice Ninja for billing purposes.', + + props: { + client_id: Property.LongText({ + displayName: 'Client ID (alphanumeric)', + description: 'Client ID from Invoice Ninja', + required: true, + }), + purchase_order_no: Property.LongText({ + displayName: 'Purchase Order Number (alphanumeric)', + description: 'Descriptive text or arbitrary number (optional)', + required: false, + }), + discount: Property.LongText({ + displayName: 'Apply discount', + description: 'Enter a number for the whole invoice discount', + defaultValue: '0', + required: true, + }), + discount_type: Property.StaticDropdown({ + displayName: 'Type of discount', + description: 'Select either amount or percentage for invoice discount. Applies to line items and invoice.', + defaultValue: true, + required: true, + options: { + options: [ + { + label: 'Amount', + value: true + }, + { + label: 'Percentage', + value: false + } + ] + } + }), + public_notes: Property.LongText({ + displayName: 'Public notes for invoice', + description: 'Text that may be visible in the client portal (optional)', + required: false, + }), + private_notes: Property.LongText({ + displayName: 'Private notes for invoice', + description: 'Text not visible for clients (optional)', + required: false, + }), + order_items_json: Property.LongText({ + displayName: 'Order Items JSON string', + description: 'e.g., [{ "quantity":1,"product_key":"product key", "discount": "0" }]', + required: true, + }), + frequency: Property.StaticDropdown({ + displayName: 'Frequency of billing', + description: 'Choose one', + defaultValue: 5, + required: true, + options: { + options: [ + { + label: 'Use override below', + value:0, + }, + { + label: 'Daily', + value: 1 + }, + { + label: 'Weekly', + value: 2 + }, + { + label: '2 Weeks', + value: 3 + }, + { + label: '4 Weeks', + value: 4 + }, + { + label: 'Monthly', + value: 5 + }, + { + label: 'Two Months', + value: 6 + }, + { + label: 'Quarterly', + value: 7 + }, + { + label: 'Four Months', + value: 8 + }, + { + label: 'Semi Annually', + value: 9 + }, + { + label: 'Annually', + value: 10 + }, + { + label: 'Two Years', + value: 11 + }, + { + label: 'Three Years', + value: 12 + } + ] + }, + }), + nocycles: Property.Number({ + displayName: 'No of billing cycles', + description: 'Enter a number. How many times should this bill be generated', + required: false, + }), + auto_frequency: Property.Number({ + displayName: 'Override Frequency using Frequency ID (optional)', + description: 'Enter a number. 1-12 - corresponds to dropdown above [Daily being 1, Weekly 2 etc..]!', + required: false, + }), + due_date: Property.DateTime({ + displayName: 'Invoice next send date', + description: 'e.g., 2024-01-20', + required: true, + }), + last_date: Property.DateTime({ + displayName: 'Invoice last sent date', + description: 'e.g., 2024-01-20', + required: false, + }), + }, + + async run(context) { + await propsValidation.validateZod(context.propsValue, { + nocycles: z.number().min(0).max(999).optional(), + auto_frequency: z.number().min(1).max(12).optional(), + due_date: z.string().datetime(), + last_date: z.string().datetime().optional(), + }); + + const INapiToken = context.auth.access_token; + const headers = { + 'X-Api-Token': INapiToken, + 'Content-Type': 'application/json', + }; + + const lineItemsArray = JSON.parse(context.propsValue.order_items_json); + + if (!Array.isArray(lineItemsArray)) { + throw new Error('Invalid format for order_items_json. It should be an array of objects.'); + } + + if (lineItemsArray.length === 0) { + throw new Error('The line_items array must not be empty.'); + } + + const isValidLineItem = lineItemsArray.every(item => ( + typeof item === 'object' && + 'quantity' in item && typeof item.quantity === 'number' && + 'product_key' in item && typeof item.product_key === 'string' && + 'discount' in item && typeof item.discount === 'string' + )); + + if (!isValidLineItem) { + throw new Error('Each item in the line_items array must be an object with "quantity" (number), "product_key" (string), and "discount" (string).'); + } + + const baseUrl = context.auth.base_url.replace(/\/$/, ''); + let errorMessages = ''; + + try { + const lineItemsWithDetailsPromises = lineItemsArray.map(async item => { + try { + const getProductDetailsResponse = await fetch(`${baseUrl}/api/v1/products/?product_key=${item.product_key}`, { + method: 'GET', + headers, + }); + + if (!getProductDetailsResponse.ok) { + console.error(`Failed to get product details for ${item.product_key}. Status: ${getProductDetailsResponse.status}`); + errorMessages += `Failed to get product details for ${item.product_key}\n`; + return null; + } + + const productDetailsResponseBody = await getProductDetailsResponse.json(); + const productCount = productDetailsResponseBody.meta.pagination.count; + + if (productCount < 1) { + console.error(`No product details found for ${item.product_key}.`); + errorMessages += `No product details found for ${item.product_key}\n`; + return null; + } + + const productDetails = productDetailsResponseBody.data[0]; + + return { + quantity: item.quantity, + product_key: item.product_key, + product_cost: productDetails.price, + cost: productDetails.price, + notes: productDetails.notes, + discount: item.discount || '0', + is_amount_discount: context.propsValue.discount_type, + tax_name1: productDetails.tax_name1, + tax_rate1: productDetails.tax_rate1, + tax_id: productDetails.tax_id, + }; + } catch (error) { + console.error(`Error getting product details for ${item.product_key}:`, error); + errorMessages += `Error getting product details for ${item.product_key}: ${error}\n`; + return null; + } + }); + + const lineItemsWithDetails = await Promise.all(lineItemsWithDetailsPromises); + + if (errorMessages) { + // If there are error messages, throw an error with the accumulated messages + throw new Error(errorMessages.trim()); + } + + const today = new Date(); + const year = today.getFullYear(); + const month = today.getMonth() + 1; + const day = today.getDate(); + const formattedDate = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`; + + const createInvoiceRequestBody = { + //status_id: 2, // seems cant set the status id when creating need another api call either using the recurring invoice ID and updating the status that way + // or possibly calling /bulk with action=start there's also an action send_now + next_send_date: context.propsValue.due_date, + date: formattedDate, + client_id: context.propsValue.client_id || '', + po_number: context.propsValue.purchase_order_no || '', + public_notes: context.propsValue.public_notes || '', + private_notes: context.propsValue.private_notes || '', + line_items: lineItemsWithDetails, + discount: context.propsValue.discount, + is_amount_discount: context.propsValue.discount_type, + frequency_id: context.propsValue.auto_frequency || context.propsValue.frequency, + remaining_cycles:context.propsValue.nocycles || -1, + }; + // if remaining cycles is set to 0 it will automatically go completed -1 is endless! + // status_id 2 is pending ie start scheduling + const createInvoiceResponse = await fetch(`${baseUrl}/api/v1/recurring_invoices`, { + method: 'POST', + headers, + body: JSON.stringify(createInvoiceRequestBody), + }); + + if (!createInvoiceResponse.ok) { + throw new Error(`Failed to create recurring invoice. Status: ${createInvoiceResponse.status}`); + } + + const createInvoiceResponseBody = await createInvoiceResponse.json(); + + return createInvoiceResponseBody; + } catch (error) { + console.error('Error creating recurring invoice or getting product details:', error); + if (errorMessages) { + // If there are error messages, throw an error with the accumulated messages + throw new Error(errorMessages.trim()); + } else { + // If there are no error messages, throw the original error + throw error; + } + } + }, + }); \ No newline at end of file diff --git a/packages/pieces/community/invoiceninja/src/lib/actions/create-task.ts b/packages/pieces/community/invoiceninja/src/lib/actions/create-task.ts new file mode 100644 index 0000000..478c93c --- /dev/null +++ b/packages/pieces/community/invoiceninja/src/lib/actions/create-task.ts @@ -0,0 +1,68 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { invoiceninjaAuth } from '../..'; +export const createTask = createAction({ + auth: invoiceninjaAuth, + name: 'create_task', + displayName: 'Create Task', + description: 'Creates a task instance in Invoice Ninja for billing purposes.', + + props: { + number: Property.LongText({ + displayName: 'Task or Ticket Number (alphanumeric)', + description: + 'A unique task or ticket number that has not been used before in Invoice Ninja', + required: true, + }), + client_id: Property.LongText({ + displayName: 'Client ID (alphanumeric)', + description: 'Client ID from Invoice Ninja (optional)', + required: false, + }), + project_id: Property.LongText({ + displayName: 'Project ID (alphanumeric)', + description: 'Project ID from Invoice Ninja (optional)', + required: false, + }), + description: Property.LongText({ + displayName: 'Description of task', + description: 'Description of task to be billed', + required: true, + }), + rate: Property.Number({ + displayName: 'Custom hourly rate', + description: 'Custom hourly rate (optional) otherwise default used', + required: false, + }), + }, + + async run(context) { + const INapiToken = context.auth.access_token; + + const headers = { + 'X-Api-Token': INapiToken, + }; + const queryParams = new URLSearchParams(); + queryParams.append('number', context.propsValue.number || ''); + queryParams.append('client_id', context.propsValue.client_id || ''); + queryParams.append('project_id', context.propsValue.project_id || ''); + queryParams.append('description', context.propsValue.description || ''); + // bugfix - only append rate if a rate has been specified in the piece + if (context.propsValue.rate?.valueOf != null) { + queryParams.append('rate', context.propsValue.rate?.toString() || '0'); + } + // Remove trailing slash from base_url + const baseUrl = context.auth.base_url.replace(/\/$/, ''); + const url = `${baseUrl}/api/v1/tasks?${queryParams.toString()}`; + const httprequestdata = { + method: HttpMethod.POST, + url, + headers, + }; + const response = await httpClient.sendRequest(httprequestdata); + return response.body; + }, +}); diff --git a/packages/pieces/community/invoiceninja/src/lib/actions/get-client.ts b/packages/pieces/community/invoiceninja/src/lib/actions/get-client.ts new file mode 100644 index 0000000..21e0948 --- /dev/null +++ b/packages/pieces/community/invoiceninja/src/lib/actions/get-client.ts @@ -0,0 +1,102 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod, propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; +import { invoiceninjaAuth } from '../..'; + +export const getClient = createAction({ + auth: invoiceninjaAuth, + name: 'getclient_task', + displayName: 'Get Client Details from e-mail', + description: 'Gets the client details if they exist by e-mail.', + + props: { + email: Property.LongText({ + displayName: 'Client e-mail address', + description: 'A valid e-mail address to get client details for', + required: true, + }), + }, + + async run(context) { + await propsValidation.validateZod(context.propsValue, { + email: z.string().email(), + }); + + const INapiToken = context.auth.access_token; + + const headers = { + 'X-Api-Token': INapiToken, + }; + const queryParams = new URLSearchParams(); + queryParams.append('email', context.propsValue.email || ''); + + // Remove trailing slash from base_url + const baseUrl = context.auth.base_url.replace(/\/$/, ''); + const url = `${baseUrl}/api/v1/clients/?${queryParams.toString()}`; + const httprequestdata = { + method: HttpMethod.GET, + url, + headers, + }; + + try { + const response = await httpClient.sendRequest(httprequestdata); + // Process the successful response here (status 2xx). + // + if (response.body.meta.pagination.total > 0) { + // Each client that is found will have one or more contacts + const NumberOfContactsThisClient = + response.body.data[0].contacts.length; + for (let i = 0; i < NumberOfContactsThisClient; i++) { + // theres a lot of extra data I don't really want in the actual response of contacts so I want to tr and just pick out + // firstname, lastname, email, etc as I don't think we need the rest, just to keep it simpler + delete response.body.data[0].contacts[i].id; + delete response.body.data[0].contacts[i].created_at; + delete response.body.data[0].contacts[i].updated_at; + delete response.body.data[0].contacts[i].archived_at; + delete response.body.data[0].contacts[i].is_primary; + delete response.body.data[0].contacts[i].is_locked; + delete response.body.data[0].contacts[i].contact_key; + delete response.body.data[0].contacts[i].send_email; + delete response.body.data[0].contacts[i].last_login; + delete response.body.data[0].contacts[i].password; + delete response.body.data[0].contacts[i].link; + } + const json = [ + { + client_no_contacts: NumberOfContactsThisClient, + client_id: response.body.data[0].id, + client_name: response.body.data[0].name, + client_web: response.body.data[0].website, + client_private_notes: response.body.data[0].private_notes, + client_balance: response.body.data[0].balance, + client_paid_to_date: response.body.data[0].paid_to_date, + client_payment_balance: response.body.data[0].payment_balance, + client_credit_balance: response.body.data[0].credit_balance, + client_public_notes: response.body.data[0].public_notes, + client_address1: response.body.data[0].address1, + client_address2: response.body.data[0].address2, + client_phone: response.body.data[0].phone, + client_city: response.body.data[0].city, + client_state: response.body.data[0].state, + client_postcode: response.body.data[0].postal_code, + client_vat: response.body.data[0].vat_number, + client_display_name: response.body.data[0].display_name, + client_contacts: response.body.data[0].contacts, + //meta: response.body.meta, + }, + ]; + return json; + return true; + } else { + return false; + } // this is still returned so if it is false we'll return notfound or similar + } catch (error) { + // Handle the error when the request fails (status other than 2xx). + return 'There was a problem getting information from your Invoice Ninja'; + } + }, +}); diff --git a/packages/pieces/community/invoiceninja/src/lib/actions/get-invoices.ts b/packages/pieces/community/invoiceninja/src/lib/actions/get-invoices.ts new file mode 100644 index 0000000..1fb9c3b --- /dev/null +++ b/packages/pieces/community/invoiceninja/src/lib/actions/get-invoices.ts @@ -0,0 +1,128 @@ +// action to return invoices from InvoiceNinja with filtering by invoice status and client id +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { invoiceninjaAuth } from '../..'; +export const getInvoices = createAction({ + auth: invoiceninjaAuth, + name: 'getinvoices_task', + displayName: 'Get Invoices', + description: 'Gets data for invoices.', + + props: { + invoiceStatus: Property.StaticDropdown({ + displayName: 'Invoice Status', + description: 'Select the invoice status for filtering.', + required: true, + options: { + options: [ + { + label: 'Unpaid Invoices', + value: 'unpaid', + }, + { + label: 'Paid Invoices', + value: 'paid', + }, + { + label: 'Overdue Invoices', + value: 'overdue', + }, + { + label: 'All Invoices', + value: 'all', + }, + ], + }, + }), + clientID: Property.LongText({ + displayName: 'Client ID', + description: 'Filter by Client ID, default is all clients.', + required: false, + }), + numberOfResults: Property.Number({ + displayName: 'Max Results', + description: 'Maximum number of results to return. 9999 is default.', + required: false, + }), + }, + + async run(context) { + const INapiToken = context.auth.access_token; + + const headers = { + 'X-Api-Token': INapiToken, + }; + + const queryParams = new URLSearchParams(); + queryParams.append( + 'client_status', + context.propsValue.invoiceStatus || 'unpaid' + ); + // only include client_id in the query parameters if it has been specified + if ( + context.propsValue.clientID?.valueOf != null || + context.propsValue.clientID != undefined + ) { + queryParams.append('client_id', context.propsValue.clientID || ''); + } + queryParams.append('is_deleted', 'false'); // only return invoices that are not deleted + queryParams.append( + 'per_page', + context.propsValue.numberOfResults?.toString() || '9999' + ); // otherwise it only returns 20 per page hopefully + + // Remove trailing slash from base_url + const baseUrl = context.auth.base_url.replace(/\/$/, ''); + const url = `${baseUrl}/api/v1/invoices/?${queryParams.toString()}`; + // console.log("INVOICENINJA: " + url); + const httprequestdata = { + method: HttpMethod.GET, + url, + headers, + }; + + try { + const response = await httpClient.sendRequest(httprequestdata); + const my = []; + // Process the response here (status 2xx). + if (response.body.meta.pagination.total > 0) { + // Each invoice that is found will have lots of information, lets remove the guff + // changed from .total to .count because we're only interested in those in the first page of results which + // is what we set the per_page to to correspond to the number of records we wanted + const NumberOfInvoices = response.body.meta.pagination.count; + + for (let i = 0; i < NumberOfInvoices; i++) { + my.push({ + invoice: { + clientid: response.body.data[i].client_id, + invoiceno: response.body.data[i].number, + ponumber: response.body.data[i].po_number, + invdate: response.body.data[i].date, + duedate: response.body.data[i].due_date, + punote: response.body.data[i].public_notes, + prnote: response.body.data[i].private_notes, + reminder1: response.body.data[i].reminder1_sent, + reminder2: response.body.data[i].reminder2_sent, + reminder3: response.body.data[i].reminder3_sent, + lastreminder: response.body.data[i].reminder_last_sent, + firstsku: response.body.data[i].line_items[0].product_key, + firstitem: response.body.data[i].line_items[0].notes, + amount: response.body.data[i].amount, + balance: response.body.data[i].balance, + paid: response.body.data[i].paid_to_date, + }, + }); + // console.log("INVOICENINJA: (" + i.toString() + ") " + response.body.data[i].amount); + } + return my; + } else { + return false; + } // this is still returned so if it is false we'll return notfound or similar + } catch (error) { + // Handle the error when the request fails (status other than 2xx). + // console.log((error as Error).message); + return (error as Error).message; + // return "There was a problem getting information from your Invoice Ninja"; + } + }, +}); diff --git a/packages/pieces/community/invoiceninja/src/lib/actions/get-report.ts b/packages/pieces/community/invoiceninja/src/lib/actions/get-report.ts new file mode 100644 index 0000000..046954e --- /dev/null +++ b/packages/pieces/community/invoiceninja/src/lib/actions/get-report.ts @@ -0,0 +1,111 @@ +// action to return reports from InvoiceNinja with filtering +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { invoiceninjaAuth } from '../..'; +export const getReport = createAction({ + auth: invoiceninjaAuth, + name: 'getreport_task', + displayName: 'Get Report', + description: 'Gets report data from InvoiceNinja.', + + props: { + reportType: Property.StaticDropdown({ + displayName: 'Report Type', + description: 'Select the report type.', + required: true, + options: { + options: [ + { + label: 'Invoices', + value: 'invoices', + }, + ], + }, + }), + clientID: Property.LongText({ + displayName: 'Client ID', + description: 'Filter by Client ID, default is all clients.', + required: false, + }), + numberOfResults: Property.Number({ + displayName: 'Max Results', + description: 'Maximum number of results to return. 9999 is default.', + required: false, + }), + }, + + async run(context) { + const INapiToken = context.auth.access_token; + + const headers = { + 'X-Api-Token': INapiToken, + serializers: 'JSON', + 'Content-Type': 'application/json', + }; + + const report_keys = JSON.stringify(['client', 'name', 'date']); + const queryParams = new URLSearchParams(); + queryParams.append('report_keys', report_keys); + queryParams.append('date_range', 'last7'); + // date range - last7, last_quarter, last_year etc.. + queryParams.append( + 'per_page', + context.propsValue.numberOfResults?.toString() || '9999' + ); // otherwise it only returns 20 per page hopefully + + // Remove trailing slash from base_url + const baseUrl = context.auth.base_url.replace(/\/$/, ''); + const url = `${baseUrl}/api/v1/reports/${ + context.propsValue.reportType + }?${queryParams.toString()}`; + // console.log("INVOICENINJA: " + url); + const httprequestdata = { + method: HttpMethod.GET, + url, + headers, + }; + + try { + const response = await httpClient.sendRequest(httprequestdata); + const my = []; + // Process the response here (status 2xx). + if (response.body.meta.pagination.total > 0) { + return response.body; + + const NumberOfInvoices = response.body.meta.pagination.count; + + for (let i = 0; i < NumberOfInvoices; i++) { + my.push({ + invoice: { + clientid: response.body.data[i].client_id, + invoiceno: response.body.data[i].number, + ponumber: response.body.data[i].po_number, + invdate: response.body.data[i].date, + duedate: response.body.data[i].due_date, + punote: response.body.data[i].public_notes, + prnote: response.body.data[i].private_notes, + reminder1: response.body.data[i].reminder1_sent, + reminder2: response.body.data[i].reminder2_sent, + reminder3: response.body.data[i].reminder3_sent, + lastreminder: response.body.data[i].reminder_last_sent, + firstsku: response.body.data[i].line_items[0].product_key, + firstitem: response.body.data[i].line_items[0].notes, + amount: response.body.data[i].amount, + balance: response.body.data[i].balance, + paid: response.body.data[i].paid_to_date, + }, + }); + // console.log("INVOICENINJA: (" + i.toString() + ") " + response.body.data[i].amount); + } + return my; + } else { + return false; + } // this is still returned so if it is false we'll return notfound or similar + } catch (error) { + // Handle the error when the request fails (status other than 2xx). + // console.log((error as Error).message); + return (error as Error).message; + // return "There was a problem getting information from your Invoice Ninja"; + } + }, +}); diff --git a/packages/pieces/community/invoiceninja/src/lib/actions/task-exists.ts b/packages/pieces/community/invoiceninja/src/lib/actions/task-exists.ts new file mode 100644 index 0000000..f139684 --- /dev/null +++ b/packages/pieces/community/invoiceninja/src/lib/actions/task-exists.ts @@ -0,0 +1,53 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { invoiceninjaAuth } from '../..'; +// 05/09/23 - returns 1 or 0 instead of true or false as was having issues +export const existsTask = createAction({ + auth: invoiceninjaAuth, + name: 'exists_task', + displayName: 'Check Task Existence', + description: 'Verify if a Task Already Exists', + props: { + number: Property.LongText({ + displayName: 'Task or Ticket Number (alphanumeric)', + description: 'A task or ticket number to check', + required: true, + }), + }, + + async run(context) { + const INapiToken = context.auth.access_token; + + const headers = { + 'X-Api-Token': INapiToken, + }; + + const queryParams = new URLSearchParams(); + // number=context.propsValue.number - last update 'number' was also on the const url which + // was incorrect + queryParams.append('number', context.propsValue.number || ''); + + // Remove trailing slash from base_url + const baseUrl = context.auth.base_url.replace(/\/$/, ''); + const url = `${baseUrl}/api/v1/tasks?${queryParams.toString()}`; + const httprequestdata = { + method: HttpMethod.GET, + url, + headers, + }; + try { + const response = await httpClient.sendRequest(httprequestdata); + // meta data only present if ticket exists. had issues testing true and false on + // branch piece so switched to 1 and 0 respectively instead to see if that works + // better. + if (response.body.meta.pagination.total > 0) { + return 1; + } else { + return 0; + } + } catch (error) { + // Handle the error when the request fails (status other than 2xx). + return 'There was a problem getting information from your Invoice Ninja'; + } + }, +}); diff --git a/packages/pieces/community/invoiceninja/tsconfig.json b/packages/pieces/community/invoiceninja/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/invoiceninja/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/invoiceninja/tsconfig.lib.json b/packages/pieces/community/invoiceninja/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/invoiceninja/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/jina-ai/.eslintrc.json b/packages/pieces/community/jina-ai/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/jina-ai/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/jina-ai/README.md b/packages/pieces/community/jina-ai/README.md new file mode 100644 index 0000000..f90d2f5 --- /dev/null +++ b/packages/pieces/community/jina-ai/README.md @@ -0,0 +1,7 @@ +# pieces-jina-ai + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-jina-ai` to build the library. diff --git a/packages/pieces/community/jina-ai/package.json b/packages/pieces/community/jina-ai/package.json new file mode 100644 index 0000000..ebdcd6b --- /dev/null +++ b/packages/pieces/community/jina-ai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-jina-ai", + "version": "0.0.1" +} diff --git a/packages/pieces/community/jina-ai/project.json b/packages/pieces/community/jina-ai/project.json new file mode 100644 index 0000000..39ef34c --- /dev/null +++ b/packages/pieces/community/jina-ai/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-jina-ai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/jina-ai/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/jina-ai", + "tsConfig": "packages/pieces/community/jina-ai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/jina-ai/package.json", + "main": "packages/pieces/community/jina-ai/src/index.ts", + "assets": [ + "packages/pieces/community/jina-ai/*.md", + { + "input": "packages/pieces/community/jina-ai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/jina-ai/src/index.ts b/packages/pieces/community/jina-ai/src/index.ts new file mode 100644 index 0000000..a05a373 --- /dev/null +++ b/packages/pieces/community/jina-ai/src/index.ts @@ -0,0 +1,37 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { + extractWebpageContentAction, + webSearchSummarizationAction, + deepSearchQueryAction, + classifyContentAction, + trainCustomClassifierAction +} from './lib/actions'; + +const markdownDescription = ` +You can get your API key from [Jina AI](https://jina.ai). +`; + +export const jinaAiAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: markdownDescription, + required: true, +}) + +export const jinaAi = createPiece({ + displayName: 'Jina AI', + description: 'AI-powered web content extraction, search, and classification', + auth: jinaAiAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/jinaai.jpeg', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + authors: ['denieler'], + actions: [ + extractWebpageContentAction, + webSearchSummarizationAction, + deepSearchQueryAction, + classifyContentAction, + trainCustomClassifierAction, + ], + triggers: [], +}); \ No newline at end of file diff --git a/packages/pieces/community/jina-ai/src/lib/actions/classify-content.ts b/packages/pieces/community/jina-ai/src/lib/actions/classify-content.ts new file mode 100644 index 0000000..4ae04e6 --- /dev/null +++ b/packages/pieces/community/jina-ai/src/lib/actions/classify-content.ts @@ -0,0 +1,100 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { JinaAICommon } from '../common'; +import { jinaAiAuth } from '../../index'; + +export const classifyContentAction = createAction({ + auth:jinaAiAuth, + name: 'classify_content', + displayName: 'Classify Text or Image', + description: + 'Assign categories to text or images using the Classifier API (zero-shot/few-shot).', + props: { + model: Property.StaticDropdown({ + displayName: 'Model', + description: 'The model to use for classification.', + required: true, + defaultValue: 'jina-clip-v2', + options: { + options: [ + { + label: + 'jina-clip-v2 - Multilingual multimodal embeddings for texts and images', + value: 'jina-clip-v2', + }, + { + label: + 'jina-embeddings-v3 - Frontier multilingual embedding model with SOTA performance', + value: 'jina-embeddings-v3', + }, + { + label: + 'jina-clip-v1 - Multimodal embedding models for images and English text', + value: 'jina-clip-v1', + }, + ], + }, + }), + input: Property.LongText({ + displayName: 'Text', + description: + 'Text or image URL to classify. URLs will be treated as images, other strings as text.', + required: true, + }), + labels: Property.Array({ + displayName: 'Labels', + description: 'The labels to classify the content into.', + required: true, + }), + }, + async run(context) { + const { model, input, labels } = context.propsValue; + const { auth: apiKey } = context; + + if (!input || input==='') { + throw new Error('Text input is required.'); + } + + if (!labels || !Array.isArray(labels) || labels.length === 0) { + throw new Error('At least one label must be provided.'); + } + + const isUrl = !!input.trim().match(/^(https?|ftp|file|data):\/\/.+/i); + + + const inputArray = [ + isUrl?{image: input} :{ text: input } + ] + + + if (inputArray.length === 0) { + throw new Error('No valid inputs provided.'); + } + + const requestBody = { + model: model || 'jina-clip-v2', + input: inputArray, + labels, + }; + + const response = await JinaAICommon.makeRequest({ + url: JinaAICommon.classifierUrl, + method: HttpMethod.POST, + auth: apiKey as string, + body: requestBody, + }); + + const result = (response as ClassifyTextResponse).data[0].prediction + + return { + label:result + }; + }, +}); + +type ClassifyTextResponse = { + data:Array<{ + prediction:string, + score:number + }> +} \ No newline at end of file diff --git a/packages/pieces/community/jina-ai/src/lib/actions/deepsearch-query.ts b/packages/pieces/community/jina-ai/src/lib/actions/deepsearch-query.ts new file mode 100644 index 0000000..018d51a --- /dev/null +++ b/packages/pieces/community/jina-ai/src/lib/actions/deepsearch-query.ts @@ -0,0 +1,205 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { JinaAICommon } from '../common'; +import { jinaAiAuth } from '../../index'; + +export const deepSearchQueryAction = createAction({ + auth:jinaAiAuth, + name: 'deepsearch_query', + displayName: 'DeepSearch Query', + description: + 'Answer complex questions through iterative search, reading, and reasoning with the DeepSearch API.', + props: { + model: Property.StaticDropdown({ + displayName: 'Model', + required: true, + defaultValue: 'jina-deepsearch-v1', + options: { + options: [{ label: 'jina-deepsearch-v1', value: 'jina-deepsearch-v1' }], + }, + }), + prompt:Property.LongText({ + displayName:'Prompt', + required:true + }), + reasoning_effort: Property.StaticDropdown({ + displayName: 'Reasoning Effort', + description: + 'Constrains effort on reasoning for reasoning models. Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response.', + required: false, + defaultValue: 'medium', + options: { + options: [ + { label: 'Low', value: 'low' }, + { label: 'Medium', value: 'medium' }, + { label: 'High', value: 'high' }, + ], + }, + }), + budget_tokens: Property.Number({ + displayName: 'Budget Tokens', + description: + 'This determines the maximum number of tokens allowed for DeepSearch process. Larger budgets can improve response quality by enabling more exhaustive search for complex queries.', + required: false, + defaultValue: 0, + }), + max_attempts: Property.Number({ + displayName: 'Max Attempts', + description: + 'The maximum number of retries for solving a problem in DeepSearch process. A larger value allows DeepSearch to retry solving the problem by using different reasoning approaches and strategies.', + required: false, + defaultValue: 1, + }), + no_direct_answer: Property.Checkbox({ + displayName: 'No Direct Answer', + description: + "Forces the model to take further thinking/search steps even when the query seems trivial. Useful if you're using DeepSearch in scenarios where you're certain the query always needs DeepSearch, rather than for trivial questions.", + required: false, + defaultValue: false, + }), + max_returned_urls: Property.Number({ + displayName: 'Max Returned URLs', + description: + 'The maximum number of URLs to include in the final answer/chunk. URLs are sorted by relevance and other important factors.', + required: false, + defaultValue: 1, + }), + response_format: Property.Json({ + displayName: 'Structured Output', + description: + 'JSON schema for structured output format. Example: { "type": "json_schema", "json_schema": { "type": "object", "properties": { "numerical_answer_only": { "type": "number" } } } }', + required: false, + }), + boost_hostnames: Property.LongText({ + displayName: 'Good Domains', + description: + 'A list of domains that are given a higher priority for content retrieval. Useful for domain-specific, high-quality sources that provide valuable content.', + required: false, + }), + bad_hostnames: Property.LongText({ + displayName: 'Bad Domains', + description: + 'A list of domains to be strictly excluded from content retrieval. Typically used to filter out known spam, low-quality, or irrelevant websites.', + required: false, + }), + only_hostnames: Property.LongText({ + displayName: 'Only Domains', + description: + 'A list of domains to be exclusively included in content retrieval. All other domains will be ignored. Useful for domain-specific searches.', + required: false, + }), + }, + async run(context) { + const { + model, + reasoning_effort, + budget_tokens, + max_attempts, + no_direct_answer, + max_returned_urls, + response_format, + boost_hostnames, + bad_hostnames, + only_hostnames, + prompt + } = context.propsValue; + const { auth: apiKey } = context; + + // Build request body with all available options + const requestBody = { + model: model || 'jina-deepsearch-v1', + } as Record; + + // Default message if none provided + requestBody['messages'] = [ + { + "role": "user", + "content": "Hi!" + }, + { + "role": "assistant", + "content": "Hi, how can I help you?" + }, + { + "role": "user", + "content": prompt + } + ] + if (reasoning_effort) { + requestBody['reasoning_effort'] = reasoning_effort; + } + + if (budget_tokens) { + requestBody['budget_tokens'] = budget_tokens; + } + + if (max_attempts) { + requestBody['max_attempts'] = max_attempts; + } + + if (no_direct_answer !== undefined) { + requestBody['no_direct_answer'] = no_direct_answer; + } + + if (max_returned_urls) { + requestBody['max_returned_urls'] = max_returned_urls; + } + + if (response_format) { + try { + const parsedFormat = + typeof response_format === 'string' + ? JSON.parse(response_format) + : response_format; + requestBody['response_format'] = parsedFormat; + } catch (error) { + // If parsing fails, ignore the response_format + } + } + + // Add domain control parameters if specified + if (boost_hostnames) { + requestBody['boost_hostnames'] = boost_hostnames + .split('\n') + .map((domain) => domain.trim()) + .filter((domain) => domain); + } + + if (bad_hostnames) { + requestBody['bad_hostnames'] = bad_hostnames + .split('\n') + .map((domain) => domain.trim()) + .filter((domain) => domain); + } + + if (only_hostnames) { + requestBody['only_hostnames'] = only_hostnames + .split('\n') + .map((domain) => domain.trim()) + .filter((domain) => domain); + } + + const response = await JinaAICommon.makeRequest({ + url: JinaAICommon.deepsearchUrl, + method: HttpMethod.POST, + auth: apiKey as string, + body: requestBody, + }); + + const result = (response as DeepSearchResponse).choices[0].message.content; + + return result; + }, +}); + + +type DeepSearchResponse = { + id:number, + choices:Array<{ + index:number, + message:{ + role:string, + content:string + } + }> +} \ No newline at end of file diff --git a/packages/pieces/community/jina-ai/src/lib/actions/extract-webpage-content.ts b/packages/pieces/community/jina-ai/src/lib/actions/extract-webpage-content.ts new file mode 100644 index 0000000..3513feb --- /dev/null +++ b/packages/pieces/community/jina-ai/src/lib/actions/extract-webpage-content.ts @@ -0,0 +1,223 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { JinaAICommon } from '../common'; +import { jinaAiAuth } from '../../index'; + +export const extractWebpageContentAction = createAction({ + auth:jinaAiAuth, + name: 'extract_webpage_content', + displayName: 'Extract Webpage Content', + description: + 'Convert a URL into clean, LLM-friendly Markdown using the Reader API.', + props: { + url: Property.ShortText({ + displayName: 'URL', + description: 'The URL of the webpage to extract content from.', + required: true, + }), + format: Property.StaticDropdown({ + displayName: 'Output Format', + description: + 'Format of the extracted content - how the webpage should be returned.', + required: true, + defaultValue: 'default', + options: { + options: [ + { label: 'Default', value: 'default' }, + { label: 'Markdown', value: 'markdown' }, + { label: 'HTML', value: 'html' }, + { label: 'Text', value: 'text' }, + { label: 'Screenshot', value: 'screenshot' }, + { label: 'Pageshot', value: 'pageshot' }, + ], + }, + }), + remove_all_images: Property.Checkbox({ + displayName: 'Remove All Images', + description: 'Remove all images from the response.', + required: false, + defaultValue: false, + }), + links_summary: Property.StaticDropdown({ + displayName: 'Gather Links', + description: + 'Create a "Buttons & Links" section at the end to help downstream LLMs or web agents navigate the page.', + required: false, + defaultValue: 'none', + options: { + options: [ + { label: 'None - Keep links inline', value: 'none' }, + { label: 'Dedup - List unique links at the end', value: 'true' }, + { label: 'All - List all links at the end', value: 'all' }, + ], + }, + }), + images_summary: Property.StaticDropdown({ + displayName: 'Gather Images', + description: + 'Create an "Images" section at the end, giving downstream LLMs an overview of all visuals on the page.', + required: false, + defaultValue: 'none', + options: { + options: [ + { label: 'None - Keep images inline', value: 'none' }, + { label: 'Dedup - List unique images at the end', value: 'true' }, + { label: 'All - List all images at the end', value: 'all' }, + ], + }, + }), + do_not_track: Property.Checkbox({ + displayName: 'Do Not Cache & Track!', + description: + "When enabled, the requested URL won't be cached and tracked on our server.", + required: false, + defaultValue: false, + }), + iframe_extraction: Property.Checkbox({ + displayName: 'iframe Extraction', + description: + 'Processes content from all embedded iframes in the DOM tree.', + required: false, + defaultValue: false, + }), + shadow_dom_extraction: Property.Checkbox({ + displayName: 'Shadow DOM Extraction', + description: 'Extracts content from all Shadow DOM roots in the document.', + required: false, + defaultValue: false, + }), + follow_redirect: Property.Checkbox({ + displayName: 'Follow Redirect', + description: + 'Choose whether to resolve to the final destination URL after following all redirects. Enable to follow the full redirect chain.', + required: false, + defaultValue: false, + }), + eu_compliance: Property.Checkbox({ + displayName: 'EU Compliance', + description: + 'All infrastructure and data processing operations reside entirely within EU jurisdiction.', + required: false, + defaultValue: false, + }), + json_response: Property.Checkbox({ + displayName: 'JSON Response', + description: + 'Return response in JSON format containing URL, title, content, and timestamp (if available).', + required: false, + defaultValue: false, + }), + timeout_seconds: Property.Number({ + displayName: 'Timeout Seconds', + description: + 'Maximum page load wait time in seconds (0 means use default timeout, set a value if the default browser engine is too slow on simple webpages).', + required: false, + defaultValue: 0, + }), + css_selector: Property.LongText({ + displayName: 'CSS Selector: Only', + description: + 'List of CSS selectors to target specific page elements (e.g., "body, .main-content, #article").', + required: false, + }), + wait_for_selector: Property.LongText({ + displayName: 'CSS Selector: Wait-For', + description: + 'CSS selectors to wait for before returning results (e.g., "#dynamic-content, .lazy-loaded").', + required: false, + }), + exclude_selector: Property.LongText({ + displayName: 'CSS Selector: Excluding', + description: + 'CSS selectors for elements to remove from the results (e.g., "header, footer, .ads, #sidebar").', + required: false, + }), + }, + async run(context) { + const { + url, + format, + remove_all_images, + links_summary, + images_summary, + do_not_track, + iframe_extraction, + shadow_dom_extraction, + follow_redirect, + eu_compliance, + json_response, + timeout_seconds, + css_selector, + wait_for_selector, + exclude_selector, + } = context.propsValue; + const { auth: apiKey } = context; + + const baseReaderUrl = eu_compliance + ? JinaAICommon.euReaderUrl + : JinaAICommon.readerUrl; + const finalUrl = `${baseReaderUrl}/${encodeURIComponent(url)}`; + const headers: Record = {}; + if (format && format !== 'default') { + headers['X-Return-Format'] = format; + } + + if (json_response) { + headers['Accept'] = 'application/json'; + } + + if (timeout_seconds && timeout_seconds > 0) { + headers['X-Timeout'] = timeout_seconds.toString(); + } + + if (css_selector) { + headers['X-Target-Selector'] = css_selector; + } + + if (wait_for_selector) { + headers['X-Wait-For-Selector'] = wait_for_selector; + } + + if (exclude_selector) { + headers['X-Remove-Selector'] = exclude_selector; + } + + if (remove_all_images) { + headers['X-Retain-Images'] = 'none'; + } + + if (links_summary && links_summary !== 'none') { + headers['X-With-Links-Summary'] = links_summary; + } + + if (images_summary && images_summary !== 'none') { + headers['X-With-Images-Summary'] = images_summary; + } + + if (do_not_track) { + headers['DNT'] = '1'; + } + + if (iframe_extraction) { + headers['X-With-Iframe'] = 'true'; + } + + if (shadow_dom_extraction) { + headers['X-With-Shadow-Dom'] = 'true'; + } + + if (follow_redirect) { + headers['X-Base'] = 'final'; + } + + const response = await JinaAICommon.makeRequest({ + url: finalUrl, + method: HttpMethod.GET, + auth: apiKey as string, + body: undefined, + headers, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/jina-ai/src/lib/actions/index.ts b/packages/pieces/community/jina-ai/src/lib/actions/index.ts new file mode 100644 index 0000000..beda9c0 --- /dev/null +++ b/packages/pieces/community/jina-ai/src/lib/actions/index.ts @@ -0,0 +1,5 @@ +export * from './extract-webpage-content'; +export * from './web-search-summarization'; +export * from './deepsearch-query'; +export * from './classify-content'; +export * from './train-custom-classifier'; diff --git a/packages/pieces/community/jina-ai/src/lib/actions/train-custom-classifier.ts b/packages/pieces/community/jina-ai/src/lib/actions/train-custom-classifier.ts new file mode 100644 index 0000000..72038af --- /dev/null +++ b/packages/pieces/community/jina-ai/src/lib/actions/train-custom-classifier.ts @@ -0,0 +1,142 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { JinaAICommon } from '../common'; +import { jinaAiAuth } from '../../index'; + +export const trainCustomClassifierAction = createAction({ + auth:jinaAiAuth, + name: 'train_custom_classifier', + displayName: 'Train Custom Classifier', + description: + 'Fine-tune a classifier with labeled examples for domain-specific tasks.', + props: { + model: Property.StaticDropdown({ + displayName: 'Model', + description: 'The base model to use for training.', + required: true, + defaultValue: 'jina-clip-v2', + options: { + options: [ + { + label: + 'jina-clip-v2 - Multilingual multimodal embeddings for texts and images', + value: 'jina-clip-v2', + }, + { + label: + 'jina-embeddings-v3 - Frontier multilingual embedding model with SOTA performance', + value: 'jina-embeddings-v3', + }, + { + label: + 'jina-clip-v1 - Multimodal embedding models for images and English text', + value: 'jina-clip-v1', + }, + ], + }, + }), + access: Property.StaticDropdown({ + displayName: 'Access Level', + description: 'Visibility of the trained model.', + required: true, + defaultValue: 'private', + options: { + options: [ + { label: 'Private', value: 'private' }, + { label: 'Public', value: 'public' }, + ], + }, + }), + num_iters: Property.Number({ + displayName: 'Number of Iterations', + description: 'Number of training iterations to perform.', + required: false, + defaultValue: 10, + }), + training_data: Property.Array({ + displayName: 'Training Data', + required: true, + properties:{ + type:Property.StaticDropdown({ + displayName:'Input Type', + description:'Type of input either text or image URL.', + required:true, + defaultValue:'text', + options:{ + disabled:false, + options:[ + {label:'Text',value:'text'}, + {label:'Image',value:'image'} + ] + } + }), + input:Property.LongText({ + displayName:'Input', + required:true + }), + label:Property.ShortText({ + displayName:'Label', + required:true, + description:'Label to associate with input.' + }) + } + }), + }, + async run(context) { + const { model, access, num_iters,training_data } = context.propsValue; + const { auth: apiKey } = context; + + let parsedTrainingData: Array<{ type: string; label: string; input: string }> = []; + try { + parsedTrainingData = + typeof training_data === 'string' + ? JSON.parse(training_data) + : (training_data ?? []); + + } catch (error) { + throw new Error( + 'Invalid training data format. Must be a valid JSON array of labeled examples.' + ); + } + + if (!Array.isArray(parsedTrainingData) || parsedTrainingData.length === 0) { + throw new Error( + 'Training data must be a non-empty array of labeled examples, you can check an example at https://jina.ai/api-dashboard/classifier' + ); + } + + const trainingInput = parsedTrainingData.map((example) => { + const { type, label, input } = example; + + if (!label) { + throw new Error('Each training example must have a "label" field.'); + } + + if (!input) { + throw new Error( + 'Each training example must include an "input" value for either text or image.' + ); + } + + return type === 'text' + ? { label, text: input } + : { label, image: input }; + }); + + const requestBody = { + model: model || 'jina-clip-v2', + access: access || 'private', + num_iters: num_iters || 10, + input: trainingInput, + }; + + const response = await JinaAICommon.makeRequest({ + url: JinaAICommon.classifierTrainUrl, + method: HttpMethod.POST, + auth: apiKey as string, + body: requestBody, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/jina-ai/src/lib/actions/web-search-summarization.ts b/packages/pieces/community/jina-ai/src/lib/actions/web-search-summarization.ts new file mode 100644 index 0000000..3e6eb48 --- /dev/null +++ b/packages/pieces/community/jina-ai/src/lib/actions/web-search-summarization.ts @@ -0,0 +1,207 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { JinaAICommon } from '../common'; +import { jinaAiAuth } from '../../index'; + +export const webSearchSummarizationAction = createAction({ + auth:jinaAiAuth, + name: 'web_search_summarization', + displayName: 'Web Search Summarization', + description: + 'Perform a web search and retrieve summarized results using the Reader API.', + props: { + query: Property.ShortText({ + displayName: 'Query', + description: 'The search query to perform.', + required: true, + }), + read_full_content: Property.Checkbox({ + displayName: 'Read Full Content of SERP', + description: + 'Visit every URL in the search result and return the full content using Reader. Toggle on to enable more Reader-specific options.', + required: false, + defaultValue: false, + }), + json_response: Property.Checkbox({ + displayName: 'JSON Response', + description: + 'The response will be in JSON format, containing the URL, title, content, and timestamp (if available). In Search mode, it returns a list of five entries, each following the described JSON structure.', + required: false, + defaultValue: false, + }), + fetch_favicons: Property.Checkbox({ + displayName: 'Fetch Favicons', + description: + 'This will fetch the favicon of each URL in the SERP and include them in the response as image URI, useful for UI rendering.', + required: false, + defaultValue: false, + }), + preferred_country: Property.StaticDropdown({ + displayName: 'Preferred Country', + description: + "The country to use for the search. It's a two-letter country code.", + required: false, + defaultValue: 'Default', + options: { + options: [ + { label: 'Default', value: 'Default' }, + { label: 'United States', value: 'US' }, + { label: 'Canada', value: 'CA' }, + { label: 'Mexico', value: 'MX' }, + { label: 'United Kingdom', value: 'GB' }, + { label: 'Germany', value: 'DE' }, + { label: 'France', value: 'FR' }, + { label: 'Japan', value: 'JP' }, + { label: 'China', value: 'CN' }, + { label: 'India', value: 'IN' }, + { label: 'Brazil', value: 'BR' }, + { label: 'Australia', value: 'AU' }, + { label: 'Italy', value: 'IT' }, + { label: 'Spain', value: 'ES' }, + { label: 'South Korea', value: 'KR' }, + { label: 'Netherlands', value: 'NL' }, + { label: 'Switzerland', value: 'CH' }, + { label: 'Sweden', value: 'SE' }, + { label: 'Ireland', value: 'IE' }, + { label: 'Singapore', value: 'SG' }, + { label: 'Israel', value: 'IL' }, + { label: 'Saudi Arabia', value: 'SA' }, + { label: 'South Africa', value: 'ZA' }, + { label: 'United Arab Emirates', value: 'AE' }, + ], + }, + }), + preferred_location: Property.ShortText({ + displayName: 'Preferred Location', + description: + "From where you want the search query to originate. It is recommended to specify location at the city level in order to simulate a real user's search.", + required: false, + }), + preferred_language: Property.StaticDropdown({ + displayName: 'Preferred Language', + description: + "The language to use for the search. It's a two-letter language code.", + required: false, + defaultValue: 'Default', + options: { + options: [ + { label: 'Default', value: 'Default' }, + { label: 'English', value: 'en' }, + { label: 'Spanish', value: 'es' }, + { label: 'French', value: 'fr' }, + { label: 'German', value: 'de' }, + { label: 'Japanese', value: 'ja' }, + { label: 'Chinese', value: 'zh-cn' }, + { label: 'Hindi', value: 'hi' }, + { label: 'Portuguese', value: 'pt' }, + { label: 'Italian', value: 'it' }, + { label: 'Korean', value: 'ko' }, + { label: 'Dutch', value: 'nl' }, + { label: 'Arabic', value: 'ar' }, + { label: 'Swedish', value: 'sv' }, + { label: 'Hebrew', value: 'iw' }, + { label: 'Afrikaans', value: 'af' }, + ], + }, + }), + pagination: Property.Number({ + displayName: 'Pagination', + description: + "The result offset. It skips the given number of results. It's used for pagination.", + required: false, + defaultValue: 1, + }), + in_site_search: Property.ShortText({ + displayName: 'In-site Search', + description: + 'Returns the search results only from the specified website or domain. By default it searches the entire web.', + required: false, + }), + bypass_cached_content: Property.Checkbox({ + displayName: 'Bypass Cached Content', + description: + 'Our API caches URL contents for a certain amount of time. Set it to true to ignore the cached result and fetch the content from the URL directly.', + required: false, + defaultValue: false, + }), + eu_compliance: Property.Checkbox({ + displayName: 'EU Compliance', + description: + 'All infrastructure and data processing operations reside entirely within EU jurisdiction.', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { + query, + read_full_content, + json_response, + fetch_favicons, + preferred_country, + preferred_location, + preferred_language, + pagination, + in_site_search, + bypass_cached_content, + eu_compliance, + } = context.propsValue; + const { auth: apiKey } = context; + + const queryParams = new URLSearchParams(); + queryParams.append('q', query); + if (pagination && pagination > 1) { + queryParams.append('page', pagination.toString()); + } + + if (preferred_country && preferred_country !== 'Default') { + queryParams.append('gl', preferred_country); + } + + if (preferred_language && preferred_language !== 'Default') { + queryParams.append('hl', preferred_language); + } + + if (preferred_location) { + queryParams.append('location', preferred_location); + } + + const baseSearchUrl = eu_compliance + ? JinaAICommon.euReaderSearchUrl + : JinaAICommon.readerSearchUrl; + const finalUrl = `${baseSearchUrl}/?${queryParams.toString()}`; + + const headers: Record = {}; + + if (json_response) { + headers['Accept'] = 'application/json'; + } + + if (read_full_content) { + headers['X-Engine'] = 'direct'; + } else { + headers['X-Respond-With'] = 'no-content'; + } + + if (fetch_favicons) { + headers['X-With-Favicons'] = 'true'; + } + + if (bypass_cached_content) { + headers['X-No-Cache'] = 'true'; + } + + if (in_site_search) { + headers['X-Site'] = in_site_search; + } + + const responseBody = await JinaAICommon.makeRequest({ + url: finalUrl, + method: HttpMethod.GET, + auth: apiKey as string, + headers, + }); + + return responseBody; + }, +}); diff --git a/packages/pieces/community/jina-ai/src/lib/common/index.ts b/packages/pieces/community/jina-ai/src/lib/common/index.ts new file mode 100644 index 0000000..e820b2c --- /dev/null +++ b/packages/pieces/community/jina-ai/src/lib/common/index.ts @@ -0,0 +1,45 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +export const JinaAICommon = { + baseUrl: 'https://api.jina.ai/v1', + readerUrl: 'https://r.jina.ai', + readerSearchUrl: 'https://s.jina.ai', + euReaderUrl: 'https://eu-r-beta.jina.ai', + euReaderSearchUrl: 'https://eu-s-beta.jina.ai', + deepsearchUrl: 'https://deepsearch.jina.ai/v1/chat/completions', + classifierUrl: 'https://api.jina.ai/v1/classify', + classifierTrainUrl: 'https://api.jina.ai/v1/train', + + async makeRequest({ + url, + method, + auth, + body, + headers = {}, + }: { + url: string; + method: HttpMethod; + auth: string; + body?: Record; + headers?: Record; + }) { + const response = await httpClient.sendRequest({ + method, + url, + headers: { + Authorization: `Bearer ${auth}`, + 'Content-Type': 'application/json', + ...headers, + }, + body, + }); + + if (response.status < 200 || response.status >= 300) { + throw new Error( + `Jina AI API returned an error: ${response.status} ${response.body}` + ); + } + + return response.body; + }, +}; diff --git a/packages/pieces/community/jina-ai/tsconfig.json b/packages/pieces/community/jina-ai/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/jina-ai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/jina-ai/tsconfig.lib.json b/packages/pieces/community/jina-ai/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/jina-ai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/jira-cloud/.eslintrc.json b/packages/pieces/community/jira-cloud/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/jira-cloud/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/jira-cloud/README.md b/packages/pieces/community/jira-cloud/README.md new file mode 100644 index 0000000..a256509 --- /dev/null +++ b/packages/pieces/community/jira-cloud/README.md @@ -0,0 +1,7 @@ +# pieces-jira-cloud + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-jira-cloud` to build the library. diff --git a/packages/pieces/community/jira-cloud/package.json b/packages/pieces/community/jira-cloud/package.json new file mode 100644 index 0000000..d040120 --- /dev/null +++ b/packages/pieces/community/jira-cloud/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-jira-cloud", + "version": "0.1.5" +} \ No newline at end of file diff --git a/packages/pieces/community/jira-cloud/project.json b/packages/pieces/community/jira-cloud/project.json new file mode 100644 index 0000000..06cd8b4 --- /dev/null +++ b/packages/pieces/community/jira-cloud/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-jira-cloud", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/jira-cloud/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/jira-cloud", + "tsConfig": "packages/pieces/community/jira-cloud/tsconfig.lib.json", + "packageJson": "packages/pieces/community/jira-cloud/package.json", + "main": "packages/pieces/community/jira-cloud/src/index.ts", + "assets": [ + "packages/pieces/community/jira-cloud/*.md", + { + "input": "packages/pieces/community/jira-cloud/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-jira-cloud {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/jira-cloud/src/auth.ts b/packages/pieces/community/jira-cloud/src/auth.ts new file mode 100644 index 0000000..125d9e7 --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/auth.ts @@ -0,0 +1,66 @@ +import { + PieceAuth, + Property, + ShortTextProperty, + StaticPropsValue, +} from '@activepieces/pieces-framework'; +import { getUsers, sendJiraRequest } from './lib/common'; +import { HttpError, HttpMethod } from '@activepieces/pieces-common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const jiraCloudAuth = PieceAuth.CustomAuth({ + description: ` +You can generate your API token from: +***https://id.atlassian.com/manage-profile/security/api-tokens*** + `, + required: true, + props: { + instanceUrl: Property.ShortText({ + displayName: 'Instance URL', + description: + 'The link of your Jira instance (e.g https://example.atlassian.net)', + required: true, + }), + email: Property.ShortText({ + displayName: 'Email', + description: 'The email you use to login to Jira', + required: true, + }), + apiToken: PieceAuth.SecretText({ + displayName: 'API Token', + description: 'Your Jira API Token', + required: true, + }), + }, + validate: async ({ auth }) => { + try { + await propsValidation.validateZod(auth, { + instanceUrl: z.string().url(), + email: z.string().email(), + }); + + await sendJiraRequest({ + auth: auth, + method: HttpMethod.GET, + url: 'myself', + }); + return { + valid: true, + }; + } catch (e) { + const message = ((e as HttpError).response?.body as any)?.message; + return { + valid: false, + error: message ?? 'Invalid credentials', + }; + } + }, +}); + +export type JiraAuth = { + instanceUrl: string; + email: string; + apiToken: string; + +} \ No newline at end of file diff --git a/packages/pieces/community/jira-cloud/src/index.ts b/packages/pieces/community/jira-cloud/src/index.ts new file mode 100644 index 0000000..8597af8 --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/index.ts @@ -0,0 +1,62 @@ +import { createPiece } from '@activepieces/pieces-framework'; + +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceCategory } from '@activepieces/shared'; +import { JiraAuth, jiraCloudAuth } from './auth'; +import { createIssueAction } from './lib/actions/create-issue'; +import { searchIssues } from './lib/actions/search-issues'; +import { newIssue } from './lib/triggers/new-issue'; +import { updatedIssue } from './lib/triggers/updated-issue'; +import { updatedIssueStatus } from './lib/triggers/updated-issue-status'; +import { addCommentToIssueAction } from './lib/actions/add-comment-to-issue'; +import { addAttachmentToIssueAction } from './lib/actions/add-attachment-to-issue'; +import { updateIssueCommentAction } from './lib/actions/update-issue-comment'; +import { deleteIssueCommentAction } from './lib/actions/delete-issue-comment'; +import { updateIssueAction } from './lib/actions/update-issue'; +import { assignIssueAction } from './lib/actions/assign-issue'; +import { listIssueCommentsAction } from './lib/actions/list-issue-comments'; +import { findUserAction } from './lib/actions/find-user'; +import { addWatcherToIssueAction } from './lib/actions/add-watcher-to-issue'; +import { linkIssuesAction } from './lib/actions/link-issues'; +import { getIssueAttachmentAction } from './lib/actions/get-issue-attachment'; + +export const jiraCloud = createPiece({ + displayName: 'Jira Cloud', + description: 'Issue tracking and project management', + + auth: jiraCloudAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/jira.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: ['kishanprmr', 'MoShizzle', 'abuaboud', 'prasanna2000-max'], + actions: [ + createIssueAction, + updateIssueAction, + findUserAction, + searchIssues, + assignIssueAction, + addAttachmentToIssueAction, + getIssueAttachmentAction, + addWatcherToIssueAction, + addCommentToIssueAction, + updateIssueCommentAction, + linkIssuesAction, + listIssueCommentsAction, + deleteIssueCommentAction, + createCustomApiCallAction({ + baseUrl: (auth) => { + return `${(auth as JiraAuth).instanceUrl}/rest/api/3`; + }, + auth: jiraCloudAuth, + authMapping: async (auth) => { + const typedAuth = auth as JiraAuth; + return { + Authorization: `Basic ${Buffer.from(`${typedAuth.email}:${typedAuth.apiToken}`).toString( + 'base64', + )}`, + }; + }, + }), + ], + triggers: [newIssue, updatedIssue, updatedIssueStatus], +}); diff --git a/packages/pieces/community/jira-cloud/src/lib/actions/add-attachment-to-issue.ts b/packages/pieces/community/jira-cloud/src/lib/actions/add-attachment-to-issue.ts new file mode 100644 index 0000000..ed4b1c8 --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/actions/add-attachment-to-issue.ts @@ -0,0 +1,39 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { jiraCloudAuth } from '../../auth'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { sendJiraRequest } from '../common'; +import FormData from 'form-data'; +import { getProjectIdDropdown, getIssueIdDropdown } from '../common/props'; + +export const addAttachmentToIssueAction = createAction({ + auth: jiraCloudAuth, + name: 'add_issue_attachment', + displayName: 'Add Attachment to Issue', + description: 'Adds an attachment to an issue.', + props: { + projectId: getProjectIdDropdown(), + issueId: getIssueIdDropdown({ refreshers: ['projectId'] }), + attachment: Property.File({ + displayName: 'Attachment', + required: true, + }), + }, + async run(context) { + const { issueId, attachment } = context.propsValue; + const formData = new FormData(); + const fileBuffer = Buffer.from(attachment.base64, 'base64'); + formData.append('file', fileBuffer, attachment.filename); + + const response = await sendJiraRequest({ + method: HttpMethod.POST, + url: `issue/${issueId}/attachments`, + auth: context.auth, + headers: { + 'X-Atlassian-Token': 'no-check', + ...formData.getHeaders(), + }, + body: formData, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/jira-cloud/src/lib/actions/add-comment-to-issue.ts b/packages/pieces/community/jira-cloud/src/lib/actions/add-comment-to-issue.ts new file mode 100644 index 0000000..3cdbf4f --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/actions/add-comment-to-issue.ts @@ -0,0 +1,47 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { jiraCloudAuth } from '../../auth'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { sendJiraRequest } from '../common'; +import { getIssueIdDropdown, getProjectIdDropdown } from '../common/props'; + +export const addCommentToIssueAction = createAction({ + auth: jiraCloudAuth, + name: 'add_issue_comment', + displayName: 'Add Issue Comment', + description: 'Adds a comment to an issue.', + props: { + projectId: getProjectIdDropdown(), + issueId: getIssueIdDropdown({ refreshers: ['projectId'] }), + comment: Property.LongText({ + displayName: 'Comment Body', + required: true, + }), + }, + async run(context) { + const { issueId, comment } = context.propsValue; + const commentBody = { + version: 1, + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: comment, + }, + ], + }, + ], + }; + const response = await sendJiraRequest({ + method: HttpMethod.POST, + url: `issue/${issueId}/comment`, + auth: context.auth, + body: { + body: commentBody, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/jira-cloud/src/lib/actions/add-watcher-to-issue.ts b/packages/pieces/community/jira-cloud/src/lib/actions/add-watcher-to-issue.ts new file mode 100644 index 0000000..44c96b9 --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/actions/add-watcher-to-issue.ts @@ -0,0 +1,43 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { jiraCloudAuth } from '../../auth'; +import { getUsersDropdown, issueIdOrKeyProp } from '../common/props'; +import { isNil } from '@activepieces/shared'; +import { jiraApiCall } from '../common'; +import { HttpError, HttpMethod } from '@activepieces/pieces-common'; + +export const addWatcherToIssueAction = createAction({ + auth: jiraCloudAuth, + name: 'add-watcher-to-issue', + displayName: 'Add Watcher to Issue', + description: 'Adds a new watcher to an issue.', + props: { + issueId: issueIdOrKeyProp('Issue ID or Key', true), + userId: getUsersDropdown({ + displayName: 'User', + refreshers: [], + required: true, + }), + }, + async run(context) { + const { issueId, userId } = context.propsValue; + if (isNil(issueId)) { + throw new Error('Issue ID is required'); + } + if (isNil(userId)) { + throw new Error('User ID is required'); + } + + try { + const response = await jiraApiCall({ + auth: context.auth, + method: HttpMethod.POST, + resourceUri: `/issue/${issueId}/watchers`, + body: `"${userId}"`, + }); + + return { success: true }; + } catch (e) { + return { success: false, error: (e as HttpError).message }; + } + }, +}); diff --git a/packages/pieces/community/jira-cloud/src/lib/actions/assign-issue.ts b/packages/pieces/community/jira-cloud/src/lib/actions/assign-issue.ts new file mode 100644 index 0000000..04e06f5 --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/actions/assign-issue.ts @@ -0,0 +1,33 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { jiraCloudAuth } from '../../auth'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { sendJiraRequest } from '../common'; +import { getIssueIdDropdown, getProjectIdDropdown, getUsersDropdown } from '../common/props'; + +export const assignIssueAction = createAction({ + auth: jiraCloudAuth, + name: 'assign_issue', + displayName: 'Assign Issue', + description: 'Assigns an issue to a user.', + props: { + projectId: getProjectIdDropdown(), + issueId: getIssueIdDropdown({ refreshers: ['projectId'] }), + assignee: getUsersDropdown({ + displayName: 'Assignee', + refreshers: ['projectId'], + required: true, + }), + }, + async run(context) { + const { issueId, assignee } = context.propsValue; + const response = await sendJiraRequest({ + method: HttpMethod.PUT, + url: `issue/${issueId}/assignee`, + auth: context.auth, + body: { + accountId: assignee, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/jira-cloud/src/lib/actions/create-issue.ts b/packages/pieces/community/jira-cloud/src/lib/actions/create-issue.ts new file mode 100644 index 0000000..3f608b4 --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/actions/create-issue.ts @@ -0,0 +1,115 @@ +import { createAction, DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { JiraAuth, jiraCloudAuth } from '../../auth'; +import { + getProjectIdDropdown, + formatIssueFields, + issueTypeIdProp, + createPropertyDefinition, + transformCustomFields, +} from '../common/props'; +import { jiraApiCall, jiraPaginatedApiCall } from '../common'; +import { IssueFieldMetaData, VALID_CUSTOM_FIELD_TYPES } from '../common/types'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; + +export const createIssueAction = createAction({ + name: 'create_issue', + displayName: 'Create Issue', + description: 'Creates a new issue in a project.', + auth: jiraCloudAuth, + props: { + projectId: getProjectIdDropdown(), + issueTypeId: issueTypeIdProp('Issue Type'), + issueFields: Property.DynamicProperties({ + displayName: 'Fields', + required: true, + refreshers: ['projectId', 'issueTypeId'], + props: async ({ auth, projectId, issueTypeId }) => { + if (!auth || !issueTypeId || !projectId) { + return {}; + } + + const props: DynamicPropsValue = {}; + + const authValue = auth as JiraAuth; + const fields = await jiraPaginatedApiCall({ + auth: authValue, + method: HttpMethod.GET, + resourceUri: `/issue/createmeta/${projectId}/issuetypes/${issueTypeId}`, + propertyName: 'fields', + }); + + if (!fields || !Array.isArray(fields)) return {}; + + for (const field of fields) { + // skip invalid custom fields + if (field.schema.custom) { + const customFieldType = field.schema.custom.split(':')[1]; + if (!VALID_CUSTOM_FIELD_TYPES.includes(customFieldType)) { + continue; + } + } + if (['project', 'issuetype'].includes(field.key)) { + continue; + } + + props[field.key] = await createPropertyDefinition(authValue, field, field.required); + } + // Remove null props + return Object.fromEntries(Object.entries(props).filter(([_, prop]) => prop !== null)); + }, + }), + }, + async run(context) { + const { projectId, issueTypeId } = context.propsValue; + const inputIssueFields = context.propsValue.issueFields ?? {}; + + if (isNil(projectId) || isNil(issueTypeId)) { + throw new Error('Project ID and Issue Type ID are required'); + } + + const issueTypeFields = await jiraPaginatedApiCall({ + auth: context.auth, + method: HttpMethod.GET, + resourceUri: `/issue/createmeta/${projectId}/issuetypes/${issueTypeId}`, + propertyName: 'fields', + }); + + const formattedFields = formatIssueFields(issueTypeFields, inputIssueFields); + + const response = await jiraApiCall<{ id: string; key: string }>({ + auth: context.auth, + method: HttpMethod.POST, + resourceUri: `/issue`, + body: { + fields: { + issuetype: { + id: issueTypeId, + }, + project: { + id: projectId, + }, + ...formattedFields, + }, + }, + }); + + const issue = await jiraApiCall<{ + expand: string; + id: string; + key: string; + fields: Record; + }>({ + auth: context.auth, + method: HttpMethod.GET, + resourceUri: `/issue/${response.id}`, + }); + + const updatedIssueProperties = transformCustomFields(issueTypeFields, issue.fields); + + return { + ...issue, + fields: updatedIssueProperties, + }; + }, +}); diff --git a/packages/pieces/community/jira-cloud/src/lib/actions/delete-issue-comment.ts b/packages/pieces/community/jira-cloud/src/lib/actions/delete-issue-comment.ts new file mode 100644 index 0000000..82931e0 --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/actions/delete-issue-comment.ts @@ -0,0 +1,58 @@ +import { createAction, PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { jiraCloudAuth } from '../../auth'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { sendJiraRequest } from '../common'; +import { getIssueIdDropdown, getProjectIdDropdown } from '../common/props'; + +export const deleteIssueCommentAction = createAction({ + auth: jiraCloudAuth, + name: 'delete_issue_comment', + displayName: 'Delete Issue Comment', + description: 'Deletes a comment on a specific issue.', + props: { + projectId: getProjectIdDropdown(), + issueId: getIssueIdDropdown({ refreshers: ['projectId'] }), + commentId: Property.Dropdown({ + displayName: 'Comment ID', + refreshers: ['issueId'], + required: true, + options: async ({ auth, issueId }) => { + if (!auth || !issueId) { + return { + disabled: true, + placeholder: 'Please connect your account and select issue.', + options: [], + }; + } + const response = await sendJiraRequest({ + method: HttpMethod.GET, + url: `issue/${issueId}/comment`, + auth: auth as PiecePropValueSchema, + queryParams: { + orderBy: '-created', + expand: 'renderedBody', + }, + }); + + return { + disabled: false, + options: response.body.comments.map((comment: { id: string; renderedBody: string }) => { + return { + label: comment.renderedBody, + value: comment.id, + }; + }), + }; + }, + }), + }, + async run(context) { + const { issueId, commentId } = context.propsValue; + const response = await sendJiraRequest({ + method: HttpMethod.DELETE, + url: `issue/${issueId}/comment/${commentId}`, + auth: context.auth, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/jira-cloud/src/lib/actions/find-user.ts b/packages/pieces/community/jira-cloud/src/lib/actions/find-user.ts new file mode 100644 index 0000000..dd435b9 --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/actions/find-user.ts @@ -0,0 +1,32 @@ +import { createAction, Property } from "@activepieces/pieces-framework"; +import { jiraCloudAuth } from "../../auth"; +import { jiraApiCall } from "../common"; +import { HttpMethod } from "@activepieces/pieces-common"; + +export const findUserAction = createAction({ + auth:jiraCloudAuth, + name:'find-user', + displayName:'Find User', + description:'Finds an existing user.', + props:{ + keyword:Property.ShortText({ + displayName:'Keyword', + required:true, + }) + }, + async run(context){ + const response = await jiraApiCall>>({ + auth:context.auth, + method:HttpMethod.GET, + resourceUri:'/user/search', + query:{ + query:context.propsValue.keyword + } + }) + + return{ + found:response.length>0, + data:response + } + } +}) \ No newline at end of file diff --git a/packages/pieces/community/jira-cloud/src/lib/actions/get-issue-attachment.ts b/packages/pieces/community/jira-cloud/src/lib/actions/get-issue-attachment.ts new file mode 100644 index 0000000..4823d5e --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/actions/get-issue-attachment.ts @@ -0,0 +1,48 @@ +import { createAction, Property } from "@activepieces/pieces-framework"; +import { jiraCloudAuth } from "../../auth"; +import { jiraApiCall } from "../common"; +import { AuthenticationType, httpClient, HttpMethod } from "@activepieces/pieces-common"; + +export const getIssueAttachmentAction = createAction({ + auth: jiraCloudAuth, + name: 'get-issue-attachment', + displayName: 'Get Issue Attachment', + description: 'Retrieves an attachment from an issue.', + props: { + attachmentId: Property.ShortText({ + displayName: 'Attachment ID', + required: true + }) + }, + async run(context) { + const { attachmentId } = context.propsValue; + + // https://community.developer.atlassian.com/t/download-attachment-from-rest-api/40860/2 + const attachmentResponse = await jiraApiCall<{ filename: string, content: string }>({ + method: HttpMethod.GET, + resourceUri: `/attachment/${attachmentId}`, + auth: context.auth, + }) + + const { filename, content } = attachmentResponse; + + const response = await httpClient.sendRequest({ + url: content, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BASIC, + username: context.auth.email, + password: context.auth.apiToken, + }, + responseType:'arraybuffer' + }) + + return { + ...attachmentResponse, + file: await context.files.write({ + fileName: filename, + data: Buffer.from(response.body) + }) + } + } +}) \ No newline at end of file diff --git a/packages/pieces/community/jira-cloud/src/lib/actions/link-issues.ts b/packages/pieces/community/jira-cloud/src/lib/actions/link-issues.ts new file mode 100644 index 0000000..8088421 --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/actions/link-issues.ts @@ -0,0 +1,47 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { jiraCloudAuth } from '../../auth'; +import { issueIdOrKeyProp, issueLinkTypeIdProp } from '../common/props'; +import { isNil } from '@activepieces/shared'; +import { HttpError, HttpMethod } from '@activepieces/pieces-common'; +import { jiraApiCall } from '../common'; + +export const linkIssuesAction = createAction({ + auth: jiraCloudAuth, + name: 'link-issues', + displayName: 'Link Issues', + description: 'Creates a link between two issues.', + props: { + firstIssueId: issueIdOrKeyProp('First Issue', true), + issueLinkTypeId: issueLinkTypeIdProp('Link Type', true), + secondIssueId: issueIdOrKeyProp('Second Issue', true), + }, + async run(context) { + const { firstIssueId, issueLinkTypeId, secondIssueId } = context.propsValue; + + if (isNil(firstIssueId) || isNil(issueLinkTypeId) || isNil(secondIssueId)) { + throw new Error('First Issue, Link Type, and Second Issue are required'); + } + try { + const response = await jiraApiCall({ + method: HttpMethod.POST, + resourceUri: '/issueLink', + auth: context.auth, + body: { + type: { + id: issueLinkTypeId, + }, + inwardIssue: { + id: secondIssueId, + }, + outwardIssue: { + id: firstIssueId, + }, + }, + }); + + return { success: true }; + } catch (e) { + return { success: false, error: (e as HttpError).message }; + } + }, +}); diff --git a/packages/pieces/community/jira-cloud/src/lib/actions/list-issue-comments.ts b/packages/pieces/community/jira-cloud/src/lib/actions/list-issue-comments.ts new file mode 100644 index 0000000..448c56a --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/actions/list-issue-comments.ts @@ -0,0 +1,55 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { jiraCloudAuth } from '../../auth'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { sendJiraRequest } from '../common'; +import { getProjectIdDropdown, getIssueIdDropdown } from '../common/props'; + +export const listIssueCommentsAction = createAction({ + auth: jiraCloudAuth, + name: 'list_issue_comments', + displayName: 'List Issue Comments', + description: 'Returns all comments for an issue.', + props: { + projectId: getProjectIdDropdown(), + issueId: getIssueIdDropdown({ refreshers: ['projectId'] }), + orderBy: Property.StaticDropdown({ + displayName: 'Order By', + required: true, + defaultValue: '-created', + options: { + disabled: false, + options: [ + { + label: 'Created (Descending)', + value: '-created', + }, + { + label: 'Created (Ascending)', + value: '+created', + }, + ], + }, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Maximum number of results', + required: true, + defaultValue: 10, + }), + }, + async run(context) { + const { issueId, orderBy, limit } = context.propsValue; + + const response = await sendJiraRequest({ + method: HttpMethod.GET, + url: `issue/${issueId}/comment`, + auth: context.auth, + queryParams: { + orderBy: orderBy, + maxResults: limit.toString(), + expand: 'renderedBody', + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/jira-cloud/src/lib/actions/search-issues.ts b/packages/pieces/community/jira-cloud/src/lib/actions/search-issues.ts new file mode 100644 index 0000000..6c35509 --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/actions/search-issues.ts @@ -0,0 +1,45 @@ +import { + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { jiraCloudAuth } from '../../auth'; +import { searchIssuesByJql } from '../common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const searchIssues = createAction({ + name: 'search_issues', + displayName: 'Search Issues', + description: 'Search for issues with JQL', + auth: jiraCloudAuth, + props: { + jql: Property.LongText({ + displayName: 'JQL', + description: 'The JQL query to use in the search', + defaultValue: `type = story and created > '2023-12-13 14:00'`, + required: true, + }), + maxResults: Property.Number({ + displayName: 'Max Results', + defaultValue: 50, + required: true, + }), + sanitizeJql: Property.Checkbox({ + displayName: 'Sanitize JQL', + required: true, + defaultValue: true, + }), + }, + run: async ({ auth, propsValue }) => { + await propsValidation.validateZod(propsValue, { + maxResults: z.number().min(1).max(100), + }); + const { jql, maxResults, sanitizeJql } = propsValue; + return await searchIssuesByJql({ + auth, + jql, + maxResults: maxResults, + sanitizeJql, + }); + }, +}); diff --git a/packages/pieces/community/jira-cloud/src/lib/actions/update-issue-comment.ts b/packages/pieces/community/jira-cloud/src/lib/actions/update-issue-comment.ts new file mode 100644 index 0000000..0b94e1e --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/actions/update-issue-comment.ts @@ -0,0 +1,80 @@ +import { createAction, PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { jiraCloudAuth } from '../../auth'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { sendJiraRequest } from '../common'; +import { getIssueIdDropdown, getProjectIdDropdown } from '../common/props'; + +export const updateIssueCommentAction = createAction({ + auth: jiraCloudAuth, + name: 'update_issue_comment', + displayName: 'Update Issue Comment', + description: 'Updates a comment to a specific issue.', + props: { + projectId: getProjectIdDropdown(), + issueId: getIssueIdDropdown({ refreshers: ['projectId'] }), + commentId: Property.Dropdown({ + displayName: 'Comment ID', + refreshers: ['issueId'], + required: true, + options: async ({ auth, issueId }) => { + if (!auth || !issueId) { + return { + disabled: true, + placeholder: 'Please connect your account and select issue.', + options: [], + }; + } + const response = await sendJiraRequest({ + method: HttpMethod.GET, + url: `issue/${issueId}/comment`, + auth: auth as PiecePropValueSchema, + queryParams: { + orderBy: '-created', + expand: 'renderedBody', + }, + }); + + return { + disabled: false, + options: response.body.comments.map((comment: { id: string; renderedBody: string }) => { + return { + label: comment.renderedBody, + value: comment.id, + }; + }), + }; + }, + }), + comment: Property.LongText({ + displayName: 'Comment Body', + required: true, + }), + }, + async run(context) { + const { issueId, comment, commentId } = context.propsValue; + const commentBody = { + version: 1, + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: comment, + }, + ], + }, + ], + }; + const response = await sendJiraRequest({ + method: HttpMethod.PUT, + url: `issue/${issueId}/comment/${commentId}`, + auth: context.auth, + body: { + body: commentBody, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/jira-cloud/src/lib/actions/update-issue.ts b/packages/pieces/community/jira-cloud/src/lib/actions/update-issue.ts new file mode 100644 index 0000000..44e7207 --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/actions/update-issue.ts @@ -0,0 +1,136 @@ +import { DynamicPropsValue, Property, createAction } from '@activepieces/pieces-framework'; +import { JiraAuth, jiraCloudAuth } from '../../auth'; +import { + createPropertyDefinition, + formatIssueFields, + issueIdOrKeyProp, + issueStatusIdProp, + transformCustomFields, +} from '../common/props'; +import { jiraApiCall } from '../common'; +import { IssueFieldMetaData, VALID_CUSTOM_FIELD_TYPES } from '../common/types'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; + +export const updateIssueAction = createAction({ + name: 'update_issue', + displayName: 'Update Issue', + description: 'Updates an existing issue.', + auth: jiraCloudAuth, + props: { + issueId: issueIdOrKeyProp('Issue ID or Key', true), + statusId: issueStatusIdProp('Status', false), + issueFields: Property.DynamicProperties({ + displayName: 'Fields', + required: true, + refreshers: ['issueId'], + props: async ({ auth, issueId }) => { + if (!auth || !issueId) { + return {}; + } + + const props: DynamicPropsValue = {}; + + const authValue = auth as JiraAuth; + const response = await jiraApiCall<{ fields: { [x: string]: IssueFieldMetaData } }>({ + auth: authValue, + method: HttpMethod.GET, + resourceUri: `/issue/${issueId}/editmeta`, + }); + + if (!response.fields) return {}; + + for (const key in response.fields) { + const field = response.fields[key]; + + // skip invalid custom fields + if (field.schema.custom) { + const customFieldType = field.schema.custom.split(':')[1]; + if (!VALID_CUSTOM_FIELD_TYPES.includes(customFieldType)) { + continue; + } + } + + if (field.key === 'issuetype') { + props[field.key] = Property.StaticDropdown({ + displayName: field.name, + required: false, + options: { + disabled: false, + options: field.allowedValues + ? field.allowedValues.map((option) => ({ + label: option.name, + value: option.id, + })) + : [], + }, + }); + } else { + props[field.key] = await createPropertyDefinition(authValue, field, false); + } + } + // Remove null props + return Object.fromEntries(Object.entries(props).filter(([_, prop]) => prop !== null)); + }, + }), + }, + async run(context) { + const { issueId, statusId } = context.propsValue; + const inputIssueFields = context.propsValue.issueFields ?? {}; + + if (isNil(issueId)) { + throw new Error('Issue ID is required'); + } + + if (!isNil(statusId) && statusId !== '') { + await jiraApiCall({ + auth: context.auth, + method: HttpMethod.POST, + resourceUri: `/issue/${issueId}/transitions`, + body: { + transition: { + id: statusId, + }, + }, + }); + } + + const issueTypeFields = await jiraApiCall<{ fields: { [x: string]: IssueFieldMetaData } }>({ + auth: context.auth, + method: HttpMethod.GET, + resourceUri: `/issue/${issueId}/editmeta`, + }); + + const flattenedFields = Object.values(issueTypeFields.fields); + + const formattedFields = formatIssueFields(flattenedFields, inputIssueFields); + + const response = await jiraApiCall({ + auth: context.auth, + method: HttpMethod.PUT, + resourceUri: `/issue/${issueId}`, + body: { + fields: formattedFields, + }, + query: { returnIssue: 'true' }, + }); + + const issue = await jiraApiCall<{ + expand: string; + id: string; + key: string; + fields: Record; + }>({ + auth: context.auth, + method: HttpMethod.GET, + resourceUri: `/issue/${issueId}`, + }); + + const updatedIssueProperties = transformCustomFields(flattenedFields, issue.fields); + + return { + ...issue, + fields: updatedIssueProperties, + }; + }, +}); diff --git a/packages/pieces/community/jira-cloud/src/lib/common/index.ts b/packages/pieces/community/jira-cloud/src/lib/common/index.ts new file mode 100644 index 0000000..a37c0f0 --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/common/index.ts @@ -0,0 +1,366 @@ +import { + AuthenticationType, + HttpMessageBody, + HttpMethod, + HttpRequest, + QueryParams, + httpClient, +} from '@activepieces/pieces-common'; +import { JiraAuth } from '../../auth'; +import { isNil } from '@activepieces/shared'; + +export async function sendJiraRequest(request: HttpRequest & { auth: JiraAuth }) { + return httpClient.sendRequest({ + ...request, + url: `${request.auth.instanceUrl}/rest/api/3/${request.url}`, + authentication: { + type: AuthenticationType.BASIC, + username: request.auth.email, + password: request.auth.apiToken, + }, + }); +} + +export async function getUsers(auth: JiraAuth) { + const response = await sendJiraRequest({ + url: 'users/search', + method: HttpMethod.GET, + auth: auth, + queryParams: { + maxResults: '1000', + }, + }); + + return response.body as any[]; +} + +export async function getProjects(auth: JiraAuth): Promise { + + const response = await jiraPaginatedApiCall({ + auth, + method:HttpMethod.GET, + resourceUri:'/project/search', + propertyName:'values' + }) + + return response; +} + +export async function getIssueTypes({ auth, projectId }: { auth: JiraAuth; projectId: string }) { + const response = await sendJiraRequest({ + url: 'issuetype/project', + method: HttpMethod.GET, + auth: auth, + queryParams: { + projectId, + }, + }); + + return response.body as any[]; +} + +export async function getPriorities({ auth }: { auth: JiraAuth }) { + const response = await sendJiraRequest({ + url: 'priority', + method: HttpMethod.GET, + auth: auth, + }); + + return response.body as any[]; +} + +export async function executeJql({ + auth, + jql, + sanitizeJql, + url, + method, + queryParams, + body, +}: { + auth: JiraAuth; + jql: string; + sanitizeJql: boolean; + url: string; + method: HttpMethod; + queryParams?: QueryParams; + body?: HttpMessageBody; +}) { + let reqJql = jql; + if (sanitizeJql) { + const sanitizeResult = ( + await sendJiraRequest({ + auth: auth, + url: 'jql/sanitize', + method: HttpMethod.POST, + body: { + queries: [ + { + query: jql, + }, + ], + }, + }) + ).body as { + queries: { + initialQuery: string; + sanitizedQuery: string; + }[]; + }; + reqJql = sanitizeResult.queries[0].sanitizedQuery; + } + + const response = await sendJiraRequest({ + auth, + url, + method, + body: { + ...body, + jql: reqJql, + }, + queryParams, + }); + return response.body; +} + +export async function searchIssuesByJql({ + auth, + jql, + maxResults, + sanitizeJql, +}: { + auth: JiraAuth; + jql: string; + maxResults: number; + sanitizeJql: boolean; +}) { + return ( + (await executeJql({ + auth, + url: 'search', + method: HttpMethod.POST, + jql, + body: { + maxResults, + }, + sanitizeJql, + })) as { issues: any[] } + ).issues; +} + +export async function createJiraIssue(data: CreateIssueParams) { + const fields: any = { + project: { + id: data.projectId, + }, + summary: data.summary, + issuetype: { + id: data.issueTypeId, + }, + }; + if (data.assignee) fields.assignee = { id: data.assignee }; + if (data.priority) fields.priority = { id: data.priority }; + if (data.description) + fields.description = { + content: [ + { + content: [ + { + text: data.description, + type: 'text', + }, + ], + type: 'paragraph', + }, + ], + type: 'doc', + version: 1, + }; + + if (data.parentKey) { + fields.parent = { key: data.parentKey }; + } + + const response = await sendJiraRequest({ + url: 'issue', + method: HttpMethod.POST, + auth: data.auth, + body: { + fields: fields, + }, + }); + return response.body; +} + +export async function updateJiraIssue(data: UpdateIssueParams) { + const fields: any = {}; + if (data.summary) fields.summary = data.summary; + if (data.issueTypeId) fields.issuetype = { id: data.issueTypeId }; + if (data.assignee) fields.assignee = { id: data.assignee }; + if (data.priority) fields.priority = { id: data.priority }; + if (data.description) + fields.description = { + content: [ + { + content: [ + { + text: data.description, + type: 'text', + }, + ], + type: 'paragraph', + }, + ], + type: 'doc', + version: 1, + }; + + if (data.parentKey) { + fields.parent = { key: data.parentKey }; + } + + const response = await sendJiraRequest({ + url: `issue/${data.issueId}`, + method: HttpMethod.PUT, + auth: data.auth, + queryParams: { + returnIssue: 'true', + }, + body: { + fields: fields, + }, + }); + return response.body; +} +export interface JiraIssueType { + id: string; + description: string; + name: string; +} + +export interface JiraProject { + id: string; + key: string; + name: string; + expand: string; + self: string; + projectTypeKey: string; + simplified: boolean; + style: string; + isPrivate: boolean; + properties: any; +} + +export interface CreateIssueParams { + auth: JiraAuth; + projectId: string; + summary: string; + description?: string; + issueTypeId: string; + assignee?: string; + priority?: string; + parentKey?: string; +} + +export interface UpdateIssueParams { + auth: JiraAuth; + issueId?: string; + summary?: string; + description?: string; + issueTypeId: string; + assignee?: string; + priority?: string; + parentKey?: string; +} +export type RequestParams = Record; + +export type JiraApiCallParams = { + auth:JiraAuth, + method: HttpMethod; + resourceUri: string; + query?: RequestParams; + body?: any; +}; + +export async function jiraApiCall({ + auth, + method, + resourceUri, + query, + body, +}: JiraApiCallParams): Promise { + const baseUrl = `${auth.instanceUrl}/rest/api/3`; + const qs: QueryParams = {}; + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + qs[key] = String(value); + } + } + } + + const request: HttpRequest = { + method, + url: baseUrl + resourceUri, + queryParams: qs, + body, + authentication: { + type: AuthenticationType.BASIC, + username:auth.email, + password:auth.apiToken, + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} + +export async function jiraPaginatedApiCall({ + auth, + method, + resourceUri, + query, + body, + propertyName, +}: JiraApiCallParams & { propertyName: K }): Promise { + const qs = query ? query : {}; + + qs['startAt'] = 0; + qs['maxResults'] = 100; + + const resultData: T[] = []; + let hasMore = true; + + type PaginatedResponse = { + startAt: number; + maxResults: number; + total: number; + isLast?: boolean; + } & Record; + + do { + const response = await jiraApiCall>({ + auth, + method, + resourceUri, + query: qs, + body, + }); + + if (isNil(response[propertyName])) { + break; + } + + if (Array.isArray(response[propertyName])) { + resultData.push(...response[propertyName]); + } + + qs['startAt'] += 100; + hasMore = + response.isLast === undefined + ? response.startAt + response.maxResults < response.total + : !response.isLast; + } while (hasMore); + + return resultData; +} \ No newline at end of file diff --git a/packages/pieces/community/jira-cloud/src/lib/common/props.ts b/packages/pieces/community/jira-cloud/src/lib/common/props.ts new file mode 100644 index 0000000..d603dca --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/common/props.ts @@ -0,0 +1,640 @@ +import { + getIssueTypes, + getProjects, + getUsers, + jiraApiCall, + jiraPaginatedApiCall, + sendJiraRequest, +} from '.'; +import { DropdownOption, Property } from '@activepieces/pieces-framework'; +import { JiraAuth } from '../../auth'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { IssueFieldMetaData, IssueTypeMetadata } from './types'; +import { isNil } from '@activepieces/shared'; +import dayjs from 'dayjs'; + +export function getProjectIdDropdown(data?: DropdownParams) { + return Property.Dropdown({ + displayName: data?.displayName ?? 'Project ID or Key', + description: data?.description, + required: data?.required ?? true, + refreshers: data?.refreshers ?? [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + }; + } + + const projects = await getProjects(auth as JiraAuth); + return { + options: projects.map((project) => { + return { + label: project.name, + value: project.id, + }; + }), + }; + }, + }); +} + +export function getIssueIdDropdown(data?: DropdownParams) { + return Property.Dropdown({ + displayName: data?.displayName ?? 'Issue ID or Key', + description: data?.description, + required: data?.required ?? true, + refreshers: data?.refreshers ?? [], + options: async ({ auth, projectId }) => { + if (!auth || !projectId) { + return { + disabled: true, + options: [], + }; + } + let total = 0, + startAt = 0; + const options: DropdownOption[] = []; + do { + const response = await sendJiraRequest({ + method: HttpMethod.POST, + url: 'search', + auth: auth as JiraAuth, + body: { + fields: ['summary'], + jql: `project=${projectId}`, + startAt: startAt, + maxResults: 1, + }, + }); + const issueList = response.body as SearchIssuesResponse; + options.push( + ...issueList.issues.map((issue) => { + return { + label: `[${issue.key}] ${issue.fields.summary}`, + value: issue.id, + }; + }), + ); + startAt = issueList.startAt + issueList.maxResults; + total = issueList.total; + } while (startAt < total); + + return { + disabled: false, + options, + }; + }, + }); +} + +export function getIssueTypeIdDropdown(data?: DropdownParams) { + return Property.Dropdown({ + displayName: data?.displayName ?? 'Issue Type', + description: data?.description, + required: data?.required ?? true, + refreshers: data?.refreshers ?? ['projectId'], + options: async ({ auth, projectId }) => { + if (!auth || !projectId) { + return { + options: [], + }; + } + + const issueTypes = await getIssueTypes({ + auth: auth as JiraAuth, + projectId: projectId as string, + }); + return { + options: issueTypes.map((issueType) => { + return { + label: issueType.name, + value: issueType.id, + }; + }), + }; + }, + }); +} + +export function getUsersDropdown(data?: DropdownParams) { + return Property.Dropdown({ + displayName: data?.displayName ?? 'User', + description: data?.description, + required: data?.required ?? true, + refreshers: data?.refreshers ?? [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + }; + } + + const users = (await getUsers(auth as JiraAuth)).filter( + (user) => user.accountType === 'atlassian', + ); + return { + options: users.map((user) => { + return { + label: user.displayName, + value: user.accountId, + }; + }), + }; + }, + }); +} + +export interface DropdownParams { + required?: boolean; + refreshers?: string[]; + displayName?: string; + description?: string; +} + +export interface SearchIssuesResponse { + startAt: number; + maxResults: number; + total: number; + issues: Array<{ + id: string; + key: string; + fields: { + summary: string; + }; + }>; +} + +async function fetchGroupsOptions(auth: JiraAuth): Promise[]> { + const response = await jiraApiCall<{ + groups: Array<{ groupId: string; name: string }>; + }>({ + auth, + method: HttpMethod.GET, + resourceUri: `/groups/picker`, + }); + + const options: DropdownOption[] = []; + for (const group of response.groups) { + options.push({ + value: group.groupId, + label: group.name, + }); + } + + return options; +} + +async function fetchProjectVersionsOptions( + auth: JiraAuth, + projectId: string, +): Promise[]> { + const response = await jiraApiCall>({ + auth, + method: HttpMethod.GET, + resourceUri: `/project/${projectId}/versions`, + }); + + const options: DropdownOption[] = []; + for (const version of response) { + options.push({ + value: version.id, + label: version.name, + }); + } + + return options; +} + +async function fetchUsersOptions(auth: JiraAuth): Promise[]> { + const response = (await getUsers(auth)) as Array<{ + accountId: string; + accountType: string; + displayName: string; + }>; + + const options = response + .filter((user) => user.accountType === 'atlassian') + .map((user) => { + return { + label: user.displayName, + value: user.accountId, + }; + }); + + return options; +} + +export const issueTypeIdProp = (displayName: string, required = true) => + Property.Dropdown({ + displayName, + refreshers: ['projectId'], + required, + options: async ({ auth, projectId }) => { + if (!auth || !projectId) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first', + }; + } + + const authValue = auth as JiraAuth; + const response = await jiraPaginatedApiCall({ + auth: authValue, + resourceUri: `/issue/createmeta/${projectId}/issuetypes`, + propertyName: 'issueTypes', + method: HttpMethod.GET, + }); + + const options: DropdownOption[] = []; + + for (const issueType of response) { + options.push({ + value: issueType.id, + label: issueType.name, + }); + } + + return { + disabled: false, + options, + }; + }, + }); + +export const issueLinkTypeIdProp = (displayName: string, required = true) => + Property.Dropdown({ + displayName, + refreshers: [], + required, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first', + }; + } + + const authValue = auth as JiraAuth; + const response = await jiraApiCall<{ issueLinkTypes: Array<{ id: string; inward: string }> }>({ + auth: authValue, + resourceUri: `/issueLinkType`, + method: HttpMethod.GET, + }); + + const options: DropdownOption[] = []; + + for (const linkType of response.issueLinkTypes) { + options.push({ + value: linkType.id, + label: linkType.inward, + }); + } + + return { + disabled: false, + options, + }; + }, + }); + +export const issueIdOrKeyProp = (displayName: string, required = true) => + Property.Dropdown({ + displayName, + refreshers: [], + required, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first', + }; + } + const authValue = auth as JiraAuth; + const response = await jiraPaginatedApiCall<{ id: string; key: string }, 'issues'>({ + auth: authValue, + resourceUri: '/search', + propertyName: 'issues', + query: { fields: 'summary' }, + method: HttpMethod.GET, + }); + + const options: DropdownOption[] = []; + + for (const issue of response) { + options.push({ + value: issue.id, + label: issue.key, + }); + } + + return { + disabled: false, + options, + }; + }, + }); + +export const issueStatusIdProp = (displayName: string, required = true) => + Property.Dropdown({ + displayName, + refreshers: ['issueId'], + required, + options: async ({ auth, issueId }) => { + if (!auth || !issueId) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first and select an issue.', + }; + } + + const authValue = auth as JiraAuth; + const response = await jiraApiCall<{ transitions: Array<{ id: string; name: string }> }>({ + auth: authValue, + method: HttpMethod.GET, + resourceUri: `/issue/${issueId}/transitions`, + }); + + const options: DropdownOption[] = []; + + for (const status of response.transitions ?? []) { + options.push({ + value: status.id, + label: status.name, + }); + } + + return { + disabled: false, + options, + }; + }, + }); + +export async function createPropertyDefinition( + auth: JiraAuth, + field: IssueFieldMetaData, + isRequired = false, +) { + // Determine if the field is an array type + const isArray = field.schema.type === 'array'; + const fieldType = isArray ? field.schema.items : field.schema.type; + + switch (fieldType) { + case 'user': { + const userOptions = await fetchUsersOptions(auth); + return isArray + ? Property.StaticMultiSelectDropdown({ + displayName: field.name, + required: isRequired, + options: { disabled: false, options: userOptions }, + }) + : Property.StaticDropdown({ + displayName: field.name, + required: isRequired, + options: { disabled: false, options: userOptions }, + }); + } + case 'group': { + const groupOptions = await fetchGroupsOptions(auth); + return isArray + ? Property.StaticMultiSelectDropdown({ + displayName: field.name, + required: isRequired, + options: { disabled: false, options: groupOptions }, + }) + : Property.StaticDropdown({ + displayName: field.name, + required: isRequired, + options: { disabled: false, options: groupOptions }, + }); + } + case 'version': { + const versionOptions = field.allowedValues + ? field.allowedValues.map((option) => ({ + label: option.name, + value: option.id, + })) + : []; + return isArray + ? Property.StaticMultiSelectDropdown({ + displayName: field.name, + required: isRequired, + options: { disabled: false, options: versionOptions }, + }) + : Property.StaticDropdown({ + displayName: field.name, + required: isRequired, + options: { disabled: false, options: versionOptions }, + }); + } + case 'priority': { + const priorityOptions = field.allowedValues + ? field.allowedValues.map((option) => ({ + label: option.name, + value: option.id, + })) + : []; + + return Property.StaticDropdown({ + displayName: field.name, + required: isRequired, + options: { disabled: false, options: priorityOptions }, + }); + } + case 'option': { + const options = field.allowedValues + ? field.allowedValues.map((option) => ({ + label: option.value, + value: option.id, + })) + : []; + + return isArray + ? Property.StaticMultiSelectDropdown({ + displayName: field.name, + required: isRequired, + options: { disabled: false, options: options }, + }) + : Property.StaticDropdown({ + displayName: field.name, + required: isRequired, + options: { disabled: false, options: options }, + }); + } + case 'string': { + return isArray + ? Property.Array({ + displayName: field.name, + required: isRequired, + }) + : Property.LongText({ + displayName: field.name, + required: isRequired, + }); + } + case 'date': + return Property.DateTime({ + displayName: field.name, + description: 'Provide date in YYYY-MM-DD format.', + required: isRequired, + }); + + case 'datetime': + return Property.DateTime({ + displayName: field.name, + required: isRequired, + }); + + case 'number': + return Property.Number({ + displayName: field.name, + required: isRequired, + }); + + case 'project': + return Property.ShortText({ + displayName: field.name, + required: isRequired, + description: 'Provide project key.', + }); + + case 'issuelink': + return Property.ShortText({ + displayName: field.name, + required: isRequired, + description: 'Provide issue key.', + }); + default: + return null; + } +} + +function parseArray(value: Array | string): Array { + try { + if (Array.isArray(value)) { + return value; + } + + const parsedValue = JSON.parse(value); + if (Array.isArray(parsedValue)) { + return parsedValue; + } + + return []; + } catch (e) { + return []; + } +} + +// Function to format issue fields +// https://support.atlassian.com/cloud-automation/docs/advanced-field-editing-using-json/#Multi-user-picker-custom-field + +export function formatIssueFields( + fieldsMetadata: IssueFieldMetaData[], + fieldsInput: Record, +) { + const fieldsOutput: Record = {}; + + for (const field of fieldsMetadata) { + const key = field.key; + const fieldInputValue = fieldsInput[key]; + + // Skip if value is null, undefined, or empty string + if (isNil(fieldInputValue) || fieldInputValue === '') continue; + + switch (field.schema.type) { + case 'array': { + const parsedArrayValue = parseArray(fieldInputValue); + if (parsedArrayValue.length === 0) continue; + + fieldsOutput[key] = + field.schema.items === 'string' + ? parsedArrayValue // Keep as flat array of strings + : parsedArrayValue.map((item) => + field.schema.items === 'group' ? { groupId: item } : { id: item }, + ); + break; + } + + case 'user': + fieldsOutput[key] = { accountId: fieldInputValue }; + break; + case 'version': + case 'option': + case 'priority': + case 'issuetype': + fieldsOutput[key] = { id: fieldInputValue }; + break; + + case 'issuelink': + fieldsOutput[key] = { key: fieldInputValue }; + break; + + case 'group': + fieldsOutput[key] = { groupId: fieldInputValue }; + break; + + case 'date': + fieldsOutput[key] = dayjs(fieldInputValue).format('YYYY-MM-DD'); + break; + + case 'datetime': + fieldsOutput[key] = dayjs(fieldInputValue).toISOString(); + break; + + case 'number': + fieldsOutput[key] = Number(fieldInputValue); + break; + + case 'project': + fieldsOutput[key] = { key: fieldInputValue }; + break; + + case 'string': { + const isCustomTextArea = + field.schema.custom?.includes('textarea') || ['description', 'environment'].includes(key); + + if (isCustomTextArea) { + fieldsOutput[key] = { + type: 'doc', + version: 1, + content: [ + { + type: 'paragraph', + content: [{ text: fieldInputValue, type: 'text' }], + }, + ], + }; + } else { + fieldsOutput[key] = fieldInputValue; + } + break; + } + } + } + + return fieldsOutput; +} + +export function transformCustomFields( + fieldsMetadata: IssueFieldMetaData[], + fieldsInput: Record, +): Record { + const result: Record = {}; + + const fieldsMapping = fieldsMetadata.reduce((acc, field) => { + acc[field.key] = field.name; + return acc; + }, {} as Record); + + for (const [key, value] of Object.entries(fieldsInput)) { + result[key.startsWith('customfield_') ? fieldsMapping[key] ?? key : key] = value; + } + + return result; +} diff --git a/packages/pieces/community/jira-cloud/src/lib/common/types.ts b/packages/pieces/community/jira-cloud/src/lib/common/types.ts new file mode 100644 index 0000000..6533858 --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/common/types.ts @@ -0,0 +1,40 @@ +export type IssueTypeMetadata = { + id:string, + name:string +} + +export type IssueFieldMetaData ={ + required:boolean, + name:string, + key:string, + fieldId:string, + schema:{ + type:"string"|"date"|"datetime"|"array"|"number"|"option"|"user"|"group"|"version"|"project"|"issuelink"|"priority"|"issuetype", // "option-with-child", + items:string, + custom?:string, + customId?:number, + }, + allowedValues?:Array<{value:string,id:string,name:string}> +} + +export const VALID_CUSTOM_FIELD_TYPES = [ + 'userpicker', + 'participants', + 'multiuserpicker', + 'multiversion', + 'version', + 'multigrouppicker', + 'grouppicker', + 'multicheckboxes', + 'multiselect', + 'datepicker', + 'datetime', + 'labels', + 'float', + 'textarea', + 'radiobuttons', + 'select', + 'textfield', + 'url', + 'project', +]; \ No newline at end of file diff --git a/packages/pieces/community/jira-cloud/src/lib/triggers/new-issue.ts b/packages/pieces/community/jira-cloud/src/lib/triggers/new-issue.ts new file mode 100644 index 0000000..cd04283 --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/triggers/new-issue.ts @@ -0,0 +1,70 @@ +import { + PiecePropValueSchema, + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { + Polling, + DedupeStrategy, + pollingHelper, +} from '@activepieces/pieces-common'; +import { jiraCloudAuth } from '../../auth'; +import { searchIssuesByJql } from '../common'; +import dayjs from 'dayjs'; + +const polling: Polling< + PiecePropValueSchema, + { jql?: string; sanitizeJql?: boolean } +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, lastFetchEpochMS, propsValue }) => { + const { jql, sanitizeJql } = propsValue; + const searchQuery = `${jql ? jql + ' AND ' : ''}created > '${dayjs( + lastFetchEpochMS + ).format('YYYY-MM-DD HH:mm')}'`; + const issues = await searchIssuesByJql({ + auth, + jql: searchQuery, + maxResults: 50, + sanitizeJql: sanitizeJql ?? false, + }); + return issues.map((issue) => ({ + epochMilliSeconds: Date.parse(issue.fields.created), + data: issue, + })); + }, +}; + +export const newIssue = createTrigger({ + name: 'new_issue', + displayName: 'New Issue', + description: 'Triggers when a new issue is created', + auth: jiraCloudAuth, + type: TriggerStrategy.POLLING, + props: { + jql: Property.LongText({ + displayName: 'JQL', + description: 'Use to filter issues watched', + required: false, + }), + sanitizeJql: Property.Checkbox({ + displayName: 'Sanitize JQL', + required: false, + defaultValue: true, + }), + }, + sampleData: {}, + async onEnable(context) { + await pollingHelper.onEnable(polling, context); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, +}); diff --git a/packages/pieces/community/jira-cloud/src/lib/triggers/updated-issue-status.ts b/packages/pieces/community/jira-cloud/src/lib/triggers/updated-issue-status.ts new file mode 100644 index 0000000..2b50706 --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/triggers/updated-issue-status.ts @@ -0,0 +1,70 @@ +import { + PiecePropValueSchema, + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { + Polling, + DedupeStrategy, + pollingHelper, +} from '@activepieces/pieces-common'; +import { jiraCloudAuth } from '../../auth'; +import { searchIssuesByJql } from '../common'; +import dayjs from 'dayjs'; + +const polling: Polling< + PiecePropValueSchema, + { jql?: string; sanitizeJql?: boolean } +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, lastFetchEpochMS, propsValue }) => { + const { jql, sanitizeJql } = propsValue; + const searchQuery = `${jql ? jql + ' AND ' : ''}updated > '${dayjs( + lastFetchEpochMS + ).format('YYYY-MM-DD HH:mm')}'`; + const issues = await searchIssuesByJql({ + auth, + jql: searchQuery, + maxResults: 50, + sanitizeJql: sanitizeJql ?? false, + }); + return issues.map((issue) => ({ + epochMilliSeconds: Date.parse(issue.fields.statuscategorychangedate), + data: issue, + })); + }, +}; + +export const updatedIssueStatus = createTrigger({ + name: 'updated_issue_status', + displayName: 'Updated Issue Status', + description: 'Triggers when an issue status is updated', + auth: jiraCloudAuth, + type: TriggerStrategy.POLLING, + props: { + jql: Property.LongText({ + displayName: 'JQL', + description: 'Use to filter issues watched', + required: false, + }), + sanitizeJql: Property.Checkbox({ + displayName: 'Sanitize JQL', + required: false, + defaultValue: true, + }), + }, + sampleData: {}, + async onEnable(context) { + await pollingHelper.onEnable(polling, context); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, +}); diff --git a/packages/pieces/community/jira-cloud/src/lib/triggers/updated-issue.ts b/packages/pieces/community/jira-cloud/src/lib/triggers/updated-issue.ts new file mode 100644 index 0000000..45fa515 --- /dev/null +++ b/packages/pieces/community/jira-cloud/src/lib/triggers/updated-issue.ts @@ -0,0 +1,70 @@ +import { + PiecePropValueSchema, + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { + Polling, + DedupeStrategy, + pollingHelper, +} from '@activepieces/pieces-common'; +import { jiraCloudAuth } from '../../auth'; +import { searchIssuesByJql } from '../common'; +import dayjs from 'dayjs'; + +const polling: Polling< + PiecePropValueSchema, + { jql?: string; sanitizeJql?: boolean } +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, lastFetchEpochMS, propsValue }) => { + const { jql, sanitizeJql } = propsValue; + const searchQuery = `${jql ? jql + ' AND ' : ''}updated > '${dayjs( + lastFetchEpochMS + ).format('YYYY-MM-DD HH:mm')}'`; + const issues = await searchIssuesByJql({ + auth, + jql: searchQuery, + maxResults: 50, + sanitizeJql: sanitizeJql ?? false, + }); + return issues.map((issue) => ({ + epochMilliSeconds: Date.parse(issue.fields.updated), + data: issue, + })); + }, +}; + +export const updatedIssue = createTrigger({ + name: 'updated_issue', + displayName: 'Updated Issue', + description: 'Triggers when an issue is updated', + auth: jiraCloudAuth, + type: TriggerStrategy.POLLING, + props: { + jql: Property.LongText({ + displayName: 'JQL', + description: 'Use to filter issues watched', + required: false, + }), + sanitizeJql: Property.Checkbox({ + displayName: 'Sanitize JQL', + required: false, + defaultValue: false, + }), + }, + sampleData: {}, + async onEnable(context) { + await pollingHelper.onEnable(polling, context); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, +}); diff --git a/packages/pieces/community/jira-cloud/tsconfig.json b/packages/pieces/community/jira-cloud/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/jira-cloud/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/jira-cloud/tsconfig.lib.json b/packages/pieces/community/jira-cloud/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/jira-cloud/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/jotform/.eslintrc.json b/packages/pieces/community/jotform/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/jotform/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/jotform/README.md b/packages/pieces/community/jotform/README.md new file mode 100644 index 0000000..ff74e31 --- /dev/null +++ b/packages/pieces/community/jotform/README.md @@ -0,0 +1,7 @@ +# pieces-jotform + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-jotform` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/jotform/package.json b/packages/pieces/community/jotform/package.json new file mode 100644 index 0000000..3b5f4d9 --- /dev/null +++ b/packages/pieces/community/jotform/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-jotform", + "version": "0.1.4" +} \ No newline at end of file diff --git a/packages/pieces/community/jotform/project.json b/packages/pieces/community/jotform/project.json new file mode 100644 index 0000000..a140f59 --- /dev/null +++ b/packages/pieces/community/jotform/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-jotform", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/jotform/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/jotform", + "tsConfig": "packages/pieces/community/jotform/tsconfig.lib.json", + "packageJson": "packages/pieces/community/jotform/package.json", + "main": "packages/pieces/community/jotform/src/index.ts", + "assets": [ + "packages/pieces/community/jotform/*.md", + { + "input": "packages/pieces/community/jotform/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/jotform/src/index.ts b/packages/pieces/community/jotform/src/index.ts new file mode 100644 index 0000000..2cae5b0 --- /dev/null +++ b/packages/pieces/community/jotform/src/index.ts @@ -0,0 +1,70 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { jotformCommon } from './lib/common'; +import { newSubmission } from './lib/triggers/new-submission'; + +const markdownDescription = ` +To obtain api key, follow the steps below: +1. Go to Settings -> API +2. Click on "Create New Key" button +3. Change the permissions to "Full Access" +4. Copy the API Key and paste it in the API Key field +`; + +export const jotformAuth = PieceAuth.CustomAuth({ + required: true, + description: markdownDescription, + props: { + apiKey: PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + }), + region: Property.StaticDropdown({ + displayName: 'Region', + required: true, + options: { + options: [ + { + label: 'US (api.jotform.com)', + value: 'us', + }, + { + label: 'EU (eu-api.jotform.com)', + value: 'eu', + }, + { + label: 'HIPAA (hipaa-api.jotform.com)', + value: 'hipaa', + }, + ], + }, + }), + }, +}); + +export const jotform = createPiece({ + displayName: 'Jotform', + description: 'Create online forms and surveys', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/jotform.svg', + categories: [PieceCategory.FORMS_AND_SURVEYS], + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud", "PFernandez98"], + auth: jotformAuth, + actions: [ + createCustomApiCallAction({ + baseUrl: (auth) => + jotformCommon.baseUrl((auth as { region: string }).region), + auth: jotformAuth, + authMapping: async (auth) => ({ + APIKEY: (auth as { apiKey: string }).apiKey, + }), + }), + ], + triggers: [newSubmission], +}); diff --git a/packages/pieces/community/jotform/src/lib/common/index.ts b/packages/pieces/community/jotform/src/lib/common/index.ts new file mode 100644 index 0000000..d655a09 --- /dev/null +++ b/packages/pieces/community/jotform/src/lib/common/index.ts @@ -0,0 +1,140 @@ +import { Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; + +export const jotformCommon = { + baseUrl: (region: string) => { + if (region === 'eu') { + return 'https://eu-api.jotform.com'; + } + if (region === 'hipaa') { + return 'https://hipaa-api.jotform.com'; + } + return 'https://api.jotform.com'; + }, + form: Property.Dropdown({ + displayName: 'Form', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Enter API Key', + }; + } + const authProp = auth as { apiKey: string; region: string }; + const options: any[] = await jotformCommon.getUserForms( + authProp.apiKey, + authProp.region + ); + return { + options: options, + placeholder: 'Choose form to connect', + }; + }, + }), + getUserForms: async (apiKey: string, region: string) => { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${jotformCommon.baseUrl(region)}/user/forms`, + headers: { + APIKEY: apiKey, + }, + }; + const response = await httpClient.sendRequest(request); + const newValues = response.body.content.map((form: JotformForm) => { + return { + label: form.title, + value: form.id, + }; + }); + + return newValues; + }, + + subscribeWebhook: async ( + formId: any, + webhookUrl: string, + authentication: { apiKey: string; region: string } + ) => { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${jotformCommon.baseUrl( + authentication.region + )}/form/${formId}/webhooks`, + headers: { + APIKEY: authentication.apiKey, + 'Content-Type': 'multipart/form-data', + }, + body: { + webhookURL: webhookUrl, + }, + }; + + await httpClient.sendRequest(request); + }, + + unsubscribeWebhook: async ( + formId: any, + webhookUrl: string, + authentication: { apiKey: string; region: string } + ) => { + const getWebhooksRequest: HttpRequest = { + method: HttpMethod.GET, + url: `${jotformCommon.baseUrl( + authentication.region + )}/form/${formId}/webhooks`, + headers: { + APIKEY: authentication.apiKey, + }, + }; + + const response = await httpClient.sendRequest(getWebhooksRequest); + let webhookId; + + Object.entries(response.body.content).forEach(([key, value]) => { + if (value == webhookUrl) { + webhookId = key; + } + }); + + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `${jotformCommon.baseUrl( + authentication.region + )}/form/${formId}/webhooks/${webhookId}`, + headers: { + APIKEY: authentication.apiKey, + }, + }; + + const deleteResponse = await httpClient.sendRequest(request); + return deleteResponse; + }, +}; + +export interface JotformForm { + id: string; + username: string; + title: string; + height: string; + status: string; + created_at: string; + updated_at: string; + last_submission: string; + new: string; + count: string; + type: string; + favorite: string; + archived: string; + url: string; +} + +export interface WebhookInformation { + jotformWebhook: string; +} diff --git a/packages/pieces/community/jotform/src/lib/triggers/new-submission.ts b/packages/pieces/community/jotform/src/lib/triggers/new-submission.ts new file mode 100644 index 0000000..ca9c2a7 --- /dev/null +++ b/packages/pieces/community/jotform/src/lib/triggers/new-submission.ts @@ -0,0 +1,48 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { WebhookInformation, jotformCommon } from '../common'; +import { jotformAuth } from '../..'; + +export const newSubmission = createTrigger({ + auth: jotformAuth, + name: 'new_submission', + displayName: 'New Submission', + description: 'Triggers when a new submission is submitted', + type: TriggerStrategy.WEBHOOK, + sampleData: {}, + props: { + formId: jotformCommon.form, + }, + //Set the webhook URL in Jotform and save the webhook URL in store for disable behavior + async onEnable(context) { + await jotformCommon.subscribeWebhook( + context.propsValue['formId'], + context.webhookUrl, + context.auth + ); + + await context.store?.put( + '_new_jotform_submission_trigger', + { + jotformWebhook: context.webhookUrl, + } + ); + }, + //Delete the webhook URL from Jotform + async onDisable(context) { + const response = await context.store?.get( + '_new_jotform_submission_trigger' + ); + + if (response !== null && response !== undefined) { + await jotformCommon.unsubscribeWebhook( + context.propsValue['formId'], + response.jotformWebhook, + context.auth + ); + } + }, + //Return new submission + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/jotform/tsconfig.json b/packages/pieces/community/jotform/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/jotform/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/jotform/tsconfig.lib.json b/packages/pieces/community/jotform/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/jotform/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/json/.eslintrc.json b/packages/pieces/community/json/.eslintrc.json new file mode 100644 index 0000000..359ff63 --- /dev/null +++ b/packages/pieces/community/json/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/json/README.md b/packages/pieces/community/json/README.md new file mode 100644 index 0000000..e14b800 --- /dev/null +++ b/packages/pieces/community/json/README.md @@ -0,0 +1,7 @@ +# pieces-json + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-json` to build the library. diff --git a/packages/pieces/community/json/package.json b/packages/pieces/community/json/package.json new file mode 100644 index 0000000..5cab9fa --- /dev/null +++ b/packages/pieces/community/json/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-json", + "version": "0.0.2" +} diff --git a/packages/pieces/community/json/project.json b/packages/pieces/community/json/project.json new file mode 100644 index 0000000..0f49443 --- /dev/null +++ b/packages/pieces/community/json/project.json @@ -0,0 +1,48 @@ +{ + "name": "pieces-json", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/json/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/json", + "tsConfig": "packages/pieces/community/json/tsconfig.lib.json", + "packageJson": "packages/pieces/community/json/package.json", + "main": "packages/pieces/community/json/src/index.ts", + "assets": [ + "packages/pieces/community/json/*.md", + { + "input": "packages/pieces/community/json/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-json {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ], + "options": { + "lintFilePatterns": [ + "packages/pieces/community/json/**/*.ts" + ] + } + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/json/src/index.ts b/packages/pieces/community/json/src/index.ts new file mode 100644 index 0000000..7d12518 --- /dev/null +++ b/packages/pieces/community/json/src/index.ts @@ -0,0 +1,21 @@ + +import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { convertTextToJson } from "./lib/actions/convert-text-to-json"; +import { convertJsonToText } from "./lib/actions/convert-json-to-text"; + +export const jsonAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'Please use **test-key** as value for API Key', +}); + +export const json = createPiece({ + displayName: "JSON", + description: "Convert JSON to text and vice versa", + auth: PieceAuth.None(), + minimumSupportedRelease: '0.30.0', + logoUrl: "https://cdn.activepieces.com/pieces/json.svg", + authors: ["leenmashni","abuaboud"], + actions: [convertJsonToText, convertTextToJson], + triggers: [], +}); diff --git a/packages/pieces/community/json/src/lib/actions/convert-json-to-text.ts b/packages/pieces/community/json/src/lib/actions/convert-json-to-text.ts new file mode 100644 index 0000000..ab8ce6a --- /dev/null +++ b/packages/pieces/community/json/src/lib/actions/convert-json-to-text.ts @@ -0,0 +1,19 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +export const convertJsonToText = createAction({ + name: 'convert_json_to_text', + displayName: 'Convert Json to Text', + description: '', + props: { + json: Property.Json({ + displayName: 'JSON', + defaultValue: {}, + required: true, + }), + }, + async run(context) { + const { json } = context.propsValue; + const result = JSON.stringify(json) + return result + }, +}); diff --git a/packages/pieces/community/json/src/lib/actions/convert-text-to-json.ts b/packages/pieces/community/json/src/lib/actions/convert-text-to-json.ts new file mode 100644 index 0000000..bcc43d6 --- /dev/null +++ b/packages/pieces/community/json/src/lib/actions/convert-text-to-json.ts @@ -0,0 +1,19 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +export const convertTextToJson = createAction({ + name: 'convert_text_to_json', + displayName: 'Convert Text to Json', + description: '', + props: { + text: Property.LongText({ + displayName: 'Text', + defaultValue: '', + required: true, + }), + }, + async run(context) { + const { text } = context.propsValue; + const result = JSON.parse(text) + return result + }, +}); diff --git a/packages/pieces/community/json/tsconfig.json b/packages/pieces/community/json/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/json/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/json/tsconfig.lib.json b/packages/pieces/community/json/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/json/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/kallabot-ai/.eslintrc.json b/packages/pieces/community/kallabot-ai/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/kallabot-ai/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/kallabot-ai/README.md b/packages/pieces/community/kallabot-ai/README.md new file mode 100644 index 0000000..498bdad --- /dev/null +++ b/packages/pieces/community/kallabot-ai/README.md @@ -0,0 +1,7 @@ +# pieces-kallabot-ai + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-kallabot-ai` to build the library. diff --git a/packages/pieces/community/kallabot-ai/package.json b/packages/pieces/community/kallabot-ai/package.json new file mode 100644 index 0000000..8597d0b --- /dev/null +++ b/packages/pieces/community/kallabot-ai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-kallabot-ai", + "version": "0.2.0" +} diff --git a/packages/pieces/community/kallabot-ai/project.json b/packages/pieces/community/kallabot-ai/project.json new file mode 100644 index 0000000..f8dc0e9 --- /dev/null +++ b/packages/pieces/community/kallabot-ai/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-kallabot-ai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/kallabot-ai/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/kallabot-ai", + "tsConfig": "packages/pieces/community/kallabot-ai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/kallabot-ai/package.json", + "main": "packages/pieces/community/kallabot-ai/src/index.ts", + "assets": [ + "packages/pieces/community/kallabot-ai/*.md", + { + "input": "packages/pieces/community/kallabot-ai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/kallabot-ai/src/index.ts b/packages/pieces/community/kallabot-ai/src/index.ts new file mode 100644 index 0000000..a79309c --- /dev/null +++ b/packages/pieces/community/kallabot-ai/src/index.ts @@ -0,0 +1,36 @@ +import { + createPiece, + PieceAuth, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { makeCallAction } from './lib/actions/make-call'; +import { getCallDetailsAction } from './lib/actions/get-call-details'; +import { callEventsTrigger } from './lib/triggers/call-events'; + +const authDescription = ` +Follow these steps to obtain your Kallbot API Key: + +1. Go to [Kallabot](https://kallabot.com/) and log in to your account. +2. Click on your profile picture in the top right corner. +3. Select **Settings** from the dropdown menu. +4. Navigate to the **API & Integrations** tab. +5. Copy the generated API key and paste it here. +`; + +export const kallabotAuth = PieceAuth.SecretText({ + description: authDescription, + displayName: 'API Key', + required: true +}); + +export const kallabotAi = createPiece({ + displayName: 'Kallabot', + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/kallabot-ai.png', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + authors: ['abdulrahmanmajid'], + auth: kallabotAuth, + actions: [makeCallAction, getCallDetailsAction], + triggers: [callEventsTrigger], + description: 'AI-powered voice agents and conversational interfaces.', +}); diff --git a/packages/pieces/community/kallabot-ai/src/lib/actions/get-call-details.ts b/packages/pieces/community/kallabot-ai/src/lib/actions/get-call-details.ts new file mode 100644 index 0000000..a9a4e56 --- /dev/null +++ b/packages/pieces/community/kallabot-ai/src/lib/actions/get-call-details.ts @@ -0,0 +1,68 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { kallabotAuth } from '../..'; + +export const getCallDetailsAction = createAction({ + name: 'get-call-details', + displayName: 'Get Call Details', + description: 'Retrieve details of a specific call by call SID.', + auth: kallabotAuth, + props: { + call_sid: Property.ShortText({ + displayName: 'Call SID', + description: 'The unique identifier of the call.', + required: true, + }) + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.kallabot.com/v1/call-details/${context.propsValue.call_sid}`, + headers: { + 'Authorization': `Bearer ${context.auth}`, + 'Content-Type': 'application/json' + } + }); + + // Mock response data for development/testing + const mockResponse = { + call_sid: context.propsValue.call_sid, + agent_id: "agent-123e4567-e89b-12d3-a456-426614174000", + account_id: "account-123e4567-e89b-12d3-a456-426614174000", + from_number: "+1234567890", + to_number: "+0987654321", + duration: 120.5, + recording_url: "https://api.twilio.com/2010-04-01/Accounts/AC123/Recordings/RE123.wav", + transcription: { + conversation: [ + { + speaker: "agent", + message: "Hello! This is regarding your recent order cancellation. How can I help you today?", + timestamp: "2024-01-15T10:30:05Z" + }, + { + speaker: "customer", + message: "Hi, yes I cancelled my order because I found a better price elsewhere.", + timestamp: "2024-01-15T10:30:15Z" + }, + { + speaker: "agent", + message: "I understand. Would you be interested if I could match that price for you?", + timestamp: "2024-01-15T10:30:25Z" + } + ], + sentiment: "neutral", + summary: "Customer cancelled order due to price. Interested in price matching discussion." + }, + status: "completed", + call_type: "outbound", + cost: 0.25, + created_at: "2024-01-15T10:30:00Z", + transferred: false, + transfer_info: null + }; + + // Return actual response in production, mock data for development + return process.env['NODE_ENV'] === 'development' ? mockResponse : response.body; + } +}); \ No newline at end of file diff --git a/packages/pieces/community/kallabot-ai/src/lib/actions/make-call.ts b/packages/pieces/community/kallabot-ai/src/lib/actions/make-call.ts new file mode 100644 index 0000000..3f66020 --- /dev/null +++ b/packages/pieces/community/kallabot-ai/src/lib/actions/make-call.ts @@ -0,0 +1,144 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { kallabotAuth } from '../..'; + +export const makeCallAction = createAction({ + name: 'make-call', + displayName: 'Make Call', + description: 'Initiate an outbound call using an AI voice agent.', + auth: kallabotAuth, + props: { + agent_id: Property.Dropdown({ + displayName: 'Agent', + description: 'Select the agent to make the call.', + required: true, + refreshers: [], + options: async ({ auth }) => { + try { + const response = await fetch('https://api.kallabot.com/v1/agents', { + headers: { + 'Authorization': `Bearer ${auth}`, + 'Content-Type': 'application/json' + } + }); + + const data = await response.json(); + + if (!data.agents || data.agents.length === 0) { + return { + options: [], + disabled: true, + placeholder: 'No agents found' + }; + } + + return { + options: data.agents.map((agent: any) => { + const truncatedId = `${agent.agent_id.substring(0, 8)}...`; + return { + label: `${agent.name} (${truncatedId})`, + value: agent.agent_id + }; + }) + }; + } catch (e) { + console.error('Error fetching agents:', e); + return { + options: [], + disabled: true, + placeholder: 'Failed to load agents' + }; + } + } + }), + recipient_phone_number: Property.ShortText({ + displayName: 'Recipient Phone Number', + description: 'The phone number to call in E.164 format (e.g., +1234567890).', + required: true, + }), + sender_phone_number: Property.Dropdown({ + displayName: 'Sender Phone Number', + description: 'Select the phone number to send the call from.', + required: true, + refreshers: [], + options: async ({ auth }) => { + try { + const response = await fetch('https://api.kallabot.com/account-phone-numbers', { + headers: { + 'Authorization': `Bearer ${auth}`, + 'Content-Type': 'application/json' + } + }); + + const data = await response.json(); + + if (!data.phone_numbers || data.phone_numbers.length === 0) { + return { + options: [], + disabled: true, + placeholder: 'No phone numbers available' + }; + } + + return { + options: data.phone_numbers.map((number: any) => ({ + label: `${number.phone_number} (${number.friendly_name})`, + value: number.phone_number + })) + }; + } catch (e) { + console.error('Error fetching phone numbers:', e); + return { + options: [], + disabled: true, + placeholder: 'Failed to load phone numbers' + }; + } + } + }), + + template_variables: Property.Object({ + displayName: 'Template Variables', + description: 'Variables to replace placeholders in agent prompts. Only required if your agent prompts contain variables in {{variable.name}} format. Example: if prompt has {{customer.name}}, provide {"customer": {"name": "John"}}', + required: false, + }), + webhook_url: Property.ShortText({ + displayName: 'Webhook URL', + description: 'Optional webhook URL to receive call completion events. Use this for "Respond and Wait for Next Webhook" flows.', + required: false, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.kallabot.com/call', + headers: { + 'Authorization': `Bearer ${context.auth}`, + 'Content-Type': 'application/json', + }, + body: { + agent_id: context.propsValue.agent_id, + recipient_phone_number: context.propsValue.recipient_phone_number, + sender_phone_number: context.propsValue.sender_phone_number, + template_variables: context.propsValue.template_variables, + webhook_url: context.propsValue.webhook_url, + }, + }); + + // Mock response data for development/testing + const mockResponse = { + status: "success", + message: "Call initiated successfully", + call_details: { + call_sid: "CA1234567890abcdef1234567890abcdef", + from_number: context.propsValue.sender_phone_number, + to_number: context.propsValue.recipient_phone_number, + created_at: new Date().toISOString(), + webhook_url: context.propsValue.webhook_url || context.propsValue.template_variables?.['webhook_url'] || null + } + }; + + // Return actual response in production, mock data for development + return process.env['NODE_ENV'] === 'development' ? mockResponse : response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/kallabot-ai/src/lib/triggers/call-events.ts b/packages/pieces/community/kallabot-ai/src/lib/triggers/call-events.ts new file mode 100644 index 0000000..35e8530 --- /dev/null +++ b/packages/pieces/community/kallabot-ai/src/lib/triggers/call-events.ts @@ -0,0 +1,93 @@ +import { + createTrigger, + TriggerStrategy, + Property, +} from '@activepieces/pieces-framework'; +import { MarkdownVariant } from '@activepieces/shared'; + +const webhookInstructions = `**Setup Instructions:** +1. Copy the webhook URL below. +2. In your Kallabot AI agent configuration, go to Actions/Webhooks settings . +3. Enable webhooks and paste the webhook URL. +4. Your agent will now send call data to this webhook when a call ends.`; + +const testInstructions = ` +**Test URL:** + +If you want to generate sample data without triggering the flow, append \`/test\` to your webhook URL. + +`; + +export const callEventsTrigger = createTrigger({ + name: 'catch_webhook', + displayName: 'Call Events Webhook', + description: + 'Receives webhook data when call events occur from Kallabot AI agents.', + requireAuth: false, + props: { + webhookUrl: Property.MarkDown({ + value: `**Webhook URL:** +\`\`\`text +{{webhookUrl}} +\`\`\``, + variant: MarkdownVariant.BORDERLESS, + }), + instructions: Property.MarkDown({ + value: webhookInstructions, + variant: MarkdownVariant.INFO, + }), + testInstructions: Property.MarkDown({ + value: testInstructions, + variant: MarkdownVariant.INFO, + }), + }, + sampleData: { + call_sid: 'CA1234567890abcdef1234567890abcdef', + agent_id: 'agent-123e4567-e89b-12d3-a456-426614174000', + agent_name: 'Customer Service Bot', + account_id: 'account-123e4567-e89b-12d3-a456-426614174000', + from_number: '+1234567890', + to_number: '+0987654321', + duration: 120, + cost: 0.25, + status: 'completed', + call_type: 'outbound', + recording_url: + 'https://api.twilio.com/2010-04-01/Accounts/AC123/Recordings/RE123.wav', + recording_sid: 'RE1234567890abcdef1234567890abcdef', + transcription: { + conversation: [ + { + speaker: 'agent', + message: + 'Hello! This is regarding your recent order cancellation. How can I help you today?', + timestamp: '2024-01-15T10:30:05Z', + }, + { + speaker: 'customer', + message: + 'Hi, yes I cancelled my order because I found a better price elsewhere.', + timestamp: '2024-01-15T10:30:15Z', + }, + ], + sentiment: 'neutral', + summary: + 'Customer cancelled order due to price. Interested in price matching discussion.', + }, + created_at: '2024-01-15T10:30:00Z', + webhook_url: 'https://your-webhook-url.com/endpoint', + }, + type: TriggerStrategy.WEBHOOK, + async onEnable() { + // No specific setup needed for webhook triggers + }, + async onDisable() { + // No cleanup needed for webhook triggers + }, + async run(context) { + const payloadBody = context.payload.body as any; + + // Return the webhook payload data + return [payloadBody]; + }, +}); diff --git a/packages/pieces/community/kallabot-ai/tsconfig.json b/packages/pieces/community/kallabot-ai/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/kallabot-ai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/kallabot-ai/tsconfig.lib.json b/packages/pieces/community/kallabot-ai/tsconfig.lib.json new file mode 100644 index 0000000..56b735c --- /dev/null +++ b/packages/pieces/community/kallabot-ai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} \ No newline at end of file diff --git a/packages/pieces/community/kimai/.eslintrc.json b/packages/pieces/community/kimai/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/kimai/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/kimai/README.md b/packages/pieces/community/kimai/README.md new file mode 100644 index 0000000..1ef85c3 --- /dev/null +++ b/packages/pieces/community/kimai/README.md @@ -0,0 +1,7 @@ +# pieces-kimai + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-kimai` to build the library. diff --git a/packages/pieces/community/kimai/package.json b/packages/pieces/community/kimai/package.json new file mode 100644 index 0000000..65d209d --- /dev/null +++ b/packages/pieces/community/kimai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-kimai", + "version": "0.1.5" +} \ No newline at end of file diff --git a/packages/pieces/community/kimai/project.json b/packages/pieces/community/kimai/project.json new file mode 100644 index 0000000..a1ca8d0 --- /dev/null +++ b/packages/pieces/community/kimai/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-kimai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/kimai/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/kimai", + "tsConfig": "packages/pieces/community/kimai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/kimai/package.json", + "main": "packages/pieces/community/kimai/src/index.ts", + "assets": [ + "packages/pieces/community/kimai/*.md", + { + "input": "packages/pieces/community/kimai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-kimai {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/kimai/src/index.ts b/packages/pieces/community/kimai/src/index.ts new file mode 100644 index 0000000..0018224 --- /dev/null +++ b/packages/pieces/community/kimai/src/index.ts @@ -0,0 +1,109 @@ +import { + createCustomApiCallAction, + HttpError, +} from '@activepieces/pieces-common'; +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { kimaiCreateTimesheetAction } from './lib/actions/create-timesheet'; +import { makeClient } from './lib/common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const kimaiAuth = PieceAuth.CustomAuth({ + description: ` + To configure API access: + + 1. Go to Kimai Web UI; + 2. Click on your user profile and then go to "API Access"; + 3. Configure an API password (different from user password). + `, + props: { + base_url: Property.ShortText({ + displayName: 'Server URL', + description: 'Kimai Instance URL (e.g. https://demo.kimai.org)', + required: true, + }), + user: Property.ShortText({ + displayName: 'Username', + description: 'Kimai Username/Email', + required: true, + }), + api_password: PieceAuth.SecretText({ + displayName: 'API Password', + description: 'Kimai API Password', + required: true, + }), + }, + validate: async ({ auth }) => { + if (auth) { + await propsValidation.validateZod(auth, { + base_url: z.string().url(), + }); + } + + if (!auth) { + return { + valid: false, + error: 'Configuration missing!', + }; + } + + const client = await makeClient(auth); + + try { + const pingResponse = await client.ping(); + if (pingResponse.message !== 'pong') { + return { + valid: false, + error: pingResponse.message, + }; + } + + return { + valid: true, + }; + } catch (e) { + if (e instanceof HttpError) { + if (e.response.body instanceof Object && 'message' in e.response.body) { + return { + valid: false, + error: e.response.body.message as string, + }; + } + } + + return { + valid: false, + error: 'Please check your server URL/credentials and try again.', + }; + } + }, + required: true, +}); + +export const kimai = createPiece({ + displayName: 'Kimai', + description: 'Open-source time tracking software', + + auth: kimaiAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/kimai.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: ["facferreira","kishanprmr","MoShizzle","abuaboud"], + actions: [ + kimaiCreateTimesheetAction, + createCustomApiCallAction({ + baseUrl: (auth) => (auth as { base_url: string }).base_url, + auth: kimaiAuth, + authMapping: async (auth) => ({ + 'X-AUTH-USER': (auth as { user: string }).user, + 'X-AUTH-TOKEN': (auth as { api_password: string }).api_password, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/kimai/src/lib/actions/create-timesheet.ts b/packages/pieces/community/kimai/src/lib/actions/create-timesheet.ts new file mode 100644 index 0000000..f613db3 --- /dev/null +++ b/packages/pieces/community/kimai/src/lib/actions/create-timesheet.ts @@ -0,0 +1,41 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { kimaiCommon, makeClient } from '../common'; +import { kimaiAuth } from '../..'; + +export const kimaiCreateTimesheetAction = createAction({ + auth: kimaiAuth, + name: 'create_timesheet', + description: 'Create a new timesheet', + displayName: 'Create Timesheet', + props: { + project: kimaiCommon.project, + activity: kimaiCommon.activity, + begin: Property.DateTime({ + description: 'Begin Date of Timesheet', + displayName: 'Begin Date', + required: true, + }), + end: Property.DateTime({ + description: 'End Date of Timesheet', + displayName: 'End Date', + required: false, + }), + description: Property.LongText({ + description: 'Description of Timesheet', + displayName: 'Description', + required: false, + }), + }, + async run({ auth, propsValue }) { + const { project, activity, begin, end, description } = propsValue; + + const client = await makeClient(auth); + return await client.createTimesheet({ + project, + activity, + begin, + end, + description, + }); + }, +}); diff --git a/packages/pieces/community/kimai/src/lib/common/client.ts b/packages/pieces/community/kimai/src/lib/common/client.ts new file mode 100644 index 0000000..14e52c3 --- /dev/null +++ b/packages/pieces/community/kimai/src/lib/common/client.ts @@ -0,0 +1,102 @@ +import { + HttpMethod, + HttpMessageBody, + httpClient, + HttpResponse, +} from '@activepieces/pieces-common'; + +type PingResponse = { + message: string; +}; + +type ProjectResponse = { + id: number; + name: string; +}; + +type ActivityResponse = { + id: number; + parentTitle?: string; + name: string; +}; + +type TimesheetCreateRequest = { + project: number; + activity: number; + begin: string; + end?: string; + description?: string; +}; + +type TimesheetResponse = { + id: number; + project: number; + activity: number; + begin: string; + end?: string; + description?: string; +}; + +export class KimaiClient { + constructor( + private baseUrl: string, + private user: string, + private apiPassword: string + ) { + // Remove trailing slash from base URL + this.baseUrl = baseUrl.replace(/\/$/, ''); + } + + async ping(): Promise { + return (await this.makeRequest(HttpMethod.GET, '/api/ping')) + .body; + } + + async getProjects(): Promise { + return ( + await this.makeRequest(HttpMethod.GET, '/api/projects') + ).body; + } + + async getActivities( + project: number | undefined = undefined + ): Promise { + return ( + await this.makeRequest( + HttpMethod.GET, + '/api/activities', + { + project: project, + } + ) + ).body; + } + + async createTimesheet( + createData: TimesheetCreateRequest + ): Promise { + return ( + await this.makeRequest( + HttpMethod.POST, + '/api/timesheets', + createData + ) + ).body; + } + + async makeRequest( + method: HttpMethod, + resourceUri: string, + body: any | undefined = undefined + ): Promise> { + return await httpClient.sendRequest({ + method: method, + url: `${this.baseUrl}${resourceUri}`, + headers: { + 'X-AUTH-USER': this.user, + 'X-AUTH-TOKEN': this.apiPassword, + }, + body: body, + }); + } +} diff --git a/packages/pieces/community/kimai/src/lib/common/index.ts b/packages/pieces/community/kimai/src/lib/common/index.ts new file mode 100644 index 0000000..adc2809 --- /dev/null +++ b/packages/pieces/community/kimai/src/lib/common/index.ts @@ -0,0 +1,85 @@ +import { PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { kimaiAuth } from '../..'; +import { KimaiClient } from './client'; + +export const kimaiCommon = { + project: Property.Dropdown({ + description: 'Kimai project', + displayName: 'Project', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + + const client = await makeClient( + auth as PiecePropValueSchema + ); + const projects = await client.getProjects(); + return { + disabled: false, + options: projects.map((project) => { + return { + label: project.name, + value: project.id, + }; + }), + }; + }, + }), + activity: Property.Dropdown({ + description: 'Kimai activity', + displayName: 'Activity', + required: true, + refreshers: ['project'], + options: async ({ auth, project }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + + if (!project) { + return { + disabled: true, + placeholder: 'Select project first', + options: [], + }; + } + + const client = await makeClient( + auth as PiecePropValueSchema + ); + const activities = await client.getActivities(project as number); + return { + disabled: false, + options: activities.map((activity) => { + const nameTokens = []; + if (activity.parentTitle) { + nameTokens.push(activity.parentTitle); + } + nameTokens.push(activity.name); + const name = nameTokens.join(' - '); + return { + label: name, + value: activity.id, + }; + }), + }; + }, + }), +}; + +export async function makeClient( + auth: PiecePropValueSchema +): Promise { + const client = new KimaiClient(auth.base_url, auth.user, auth.api_password); + return client; +} diff --git a/packages/pieces/community/kimai/tsconfig.json b/packages/pieces/community/kimai/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/kimai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/kimai/tsconfig.lib.json b/packages/pieces/community/kimai/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/kimai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/kizeo-forms/.eslintrc.json b/packages/pieces/community/kizeo-forms/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/kizeo-forms/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/kizeo-forms/README.md b/packages/pieces/community/kizeo-forms/README.md new file mode 100644 index 0000000..175ec93 --- /dev/null +++ b/packages/pieces/community/kizeo-forms/README.md @@ -0,0 +1,7 @@ +# pieces-kizeo-forms + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-kizeo-forms` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/kizeo-forms/package.json b/packages/pieces/community/kizeo-forms/package.json new file mode 100644 index 0000000..0c11f44 --- /dev/null +++ b/packages/pieces/community/kizeo-forms/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-kizeo-forms", + "version": "0.3.11" +} \ No newline at end of file diff --git a/packages/pieces/community/kizeo-forms/project.json b/packages/pieces/community/kizeo-forms/project.json new file mode 100644 index 0000000..df1a4ca --- /dev/null +++ b/packages/pieces/community/kizeo-forms/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-kizeo-forms", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/kizeo-forms/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/kizeo-forms", + "tsConfig": "packages/pieces/community/kizeo-forms/tsconfig.lib.json", + "packageJson": "packages/pieces/community/kizeo-forms/package.json", + "main": "packages/pieces/community/kizeo-forms/src/index.ts", + "assets": [ + "packages/pieces/community/kizeo-forms/*.md", + { + "input": "packages/pieces/community/kizeo-forms/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/kizeo-forms/src/index.ts b/packages/pieces/community/kizeo-forms/src/index.ts new file mode 100644 index 0000000..0278da3 --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/index.ts @@ -0,0 +1,70 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { CreateListItem } from './lib/actions/create-list-item'; +import { deleteListItem } from './lib/actions/delete-list-item'; +import { downloadCustomExportInItsOriginalFormat } from './lib/actions/download-custom-export-in-its-original-format'; +import { downloadStandardPDF } from './lib/actions/download-standard-pdf'; +import { editListItem } from './lib/actions/edit-list-item'; +import { getAllListItems } from './lib/actions/get-all-list-items'; +import { getDataDefinition } from './lib/actions/get-data-definition'; +import { getListDefinition } from './lib/actions/get-list-definition'; +import { getListItem } from './lib/actions/get-list-item'; +import { pushData } from './lib/actions/push-data'; +import { endpoint } from './lib/common'; +import { eventOnDataDeleted } from './lib/trigger/event-on-data-deleted'; +import { eventOnDataFinished } from './lib/trigger/event-on-data-finished'; +import { eventOnDataPushed } from './lib/trigger/event-on-data-pushed'; +import { eventOnDataPulled } from './lib/trigger/event-on-data-received'; +import { eventOnDataUpdated } from './lib/trigger/event-on-data-updated'; +import { eventOnData } from './lib/trigger/event-on-data.trigger'; + +const markdownDescription = ` +To connect to Kizeo Forms, you need an API Token provided by their support team. +`; + +export const kizeoFormsAuth = PieceAuth.SecretText({ + displayName: 'Kizeo Forms API Key', + required: true, + description: markdownDescription, +}); + +export const kizeoForms = createPiece({ + displayName: 'Kizeo Forms', + description: 'Create custom mobile forms', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/kizeo-forms.png', + authors: ["BastienMe","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + categories: [PieceCategory.FORMS_AND_SURVEYS], + auth: kizeoFormsAuth, + actions: [ + getDataDefinition, + pushData, + downloadStandardPDF, + downloadCustomExportInItsOriginalFormat, + getListDefinition, + getListItem, + getAllListItems, + CreateListItem, + editListItem, + deleteListItem, + createCustomApiCallAction({ + baseUrl: () => endpoint, + auth: kizeoFormsAuth, + authMapping: async (auth) => { + return { + Authorization: auth as string, + }; + }, + }), + ], + triggers: [ + eventOnData, + eventOnDataDeleted, + eventOnDataFinished, + eventOnDataPushed, + eventOnDataPulled, + eventOnDataUpdated, + ], +}); diff --git a/packages/pieces/community/kizeo-forms/src/lib/actions/create-list-item.ts b/packages/pieces/community/kizeo-forms/src/lib/actions/create-list-item.ts new file mode 100644 index 0000000..3ec810b --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/actions/create-list-item.ts @@ -0,0 +1,64 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { endpoint, kizeoFormsCommon } from '../common'; +import { kizeoFormsAuth } from '../..'; + +export const CreateListItem = createAction({ + auth: kizeoFormsAuth, + + name: 'create_list_item', + displayName: 'Create List Item', + description: 'Create a new list item in Kizeo Forms', + props: { + listId: kizeoFormsCommon.listId, + itemLabel: Property.ShortText({ + displayName: 'Item Label', + description: 'Label for the new list item', + required: true, + }), + properties: kizeoFormsCommon.listProperties, + }, + async run(context) { + const { listId, itemLabel, properties } = context.propsValue; + type Body = { + items: [ + { + label: string; + properties: Record; + } + ]; + }; + + const body: Body = { + items: [ + { + label: itemLabel, + properties: {}, + }, + ], + }; + for (let i = 0; i < Object.keys(properties).length; i++) { + const propertyId = Object.keys(properties)[i]; + const propertyValue = properties[Object.keys(properties)[i]]; + body.items[0].properties[propertyId] = parseFloat(propertyValue) + ? parseFloat(propertyValue) + : propertyValue; + } + + const response = await httpClient.sendRequest<{ data: unknown }>({ + method: HttpMethod.POST, + url: + endpoint + `public/v4/lists/${listId}/items?used-with-active-pieces=`, + headers: { + 'Content-Type': 'application/json', + Authorization: context.auth, + }, + body: body, + }); + + if (response.status === 201) { + return response.body; + } + return []; + }, +}); diff --git a/packages/pieces/community/kizeo-forms/src/lib/actions/delete-list-item.ts b/packages/pieces/community/kizeo-forms/src/lib/actions/delete-list-item.ts new file mode 100644 index 0000000..629fa15 --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/actions/delete-list-item.ts @@ -0,0 +1,38 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { endpoint, kizeoFormsCommon } from '../common'; +import { kizeoFormsAuth } from '../..'; + +export const deleteListItem = createAction({ + auth: kizeoFormsAuth, + + name: 'delete_list_item', + displayName: 'Delete List Item', + description: 'Delete a specific item from a list', + props: { + listId: kizeoFormsCommon.listId, + itemId: Property.ShortText({ + displayName: 'Item Id', + description: 'The ID of the item to delete', + required: true, + }), + }, + async run(context) { + const { listId, itemId } = context.propsValue; + const response = await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: + endpoint + + `public/v4/lists/${listId}/items/${itemId}?used-with-active-pieces=`, + headers: { + 'Content-Type': 'application/json', + Authorization: context.auth, + }, + }); + + if (response.status === 200) { + return response.body; + } + return []; + }, +}); diff --git a/packages/pieces/community/kizeo-forms/src/lib/actions/download-custom-export-in-its-original-format.ts b/packages/pieces/community/kizeo-forms/src/lib/actions/download-custom-export-in-its-original-format.ts new file mode 100644 index 0000000..7e579cd --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/actions/download-custom-export-in-its-original-format.ts @@ -0,0 +1,62 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { endpoint, kizeoFormsCommon } from '../common'; +import axios from 'axios'; +import { kizeoFormsAuth } from '../..'; + +export const downloadCustomExportInItsOriginalFormat = createAction({ + auth: kizeoFormsAuth, + name: 'download_custom_export_in_its_original_format', // Must be a unique across the piece, this shouldn't be changed. + displayName: 'Download custom export in its original format', + description: 'Download a custom export in its original format', + props: { + formId: kizeoFormsCommon.formId, + exportId: kizeoFormsCommon.exportId, + exportInPdf: Property.Checkbox({ + displayName: 'Export in PDF', + description: undefined, + required: true, + }), + dataId: Property.Number({ + displayName: 'Data Id', + description: undefined, + required: true, + }), + }, + async run(context) { + const { formId, dataId, exportId, exportInPdf } = context.propsValue; + let uri = ''; + let headers = {}; + if (exportInPdf) { + uri = + endpoint + + `v3/forms/${formId}/data/${dataId}/pdf?used-with-actives-pieces=`; + headers = { + 'Content-Type': 'application/pdf', + Authorization: context.auth, + }; + } else { + uri = + endpoint + + `v3/forms/${formId}/data/${dataId}/exports/${exportId}?used-with-n8n=`; + headers = { + Accept: + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + Authorization: context.auth, + }; + } + + const response = await axios.get(uri, { + headers: headers, + responseType: 'arraybuffer', + }); + + if (response.status === 200) { + return ( + 'data:application/octet-stream;base64,' + + Buffer.from(response.data).toString('base64') + ); + } + + return []; + }, +}); diff --git a/packages/pieces/community/kizeo-forms/src/lib/actions/download-standard-pdf.ts b/packages/pieces/community/kizeo-forms/src/lib/actions/download-standard-pdf.ts new file mode 100644 index 0000000..3bfb69f --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/actions/download-standard-pdf.ts @@ -0,0 +1,42 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { endpoint, kizeoFormsCommon } from '../common'; +import axios from 'axios'; +import { kizeoFormsAuth } from '../..'; + +export const downloadStandardPDF = createAction({ + auth: kizeoFormsAuth, + name: 'download_standard_pdf', // Must be a unique across the piece, this shouldn't be changed. + displayName: 'Download Standard PDF', + description: 'Get PDF data of a form', + props: { + formId: kizeoFormsCommon.formId, + dataId: Property.Number({ + displayName: 'Data Id', + description: undefined, + required: true, + }), + }, + async run(context) { + const { formId, dataId } = context.propsValue; + const response = await axios.get( + endpoint + + `v3/forms/${formId}/data/${dataId}/pdf?used-with-actives-pieces=`, + { + headers: { + 'Content-Type': 'application/pdf', + Authorization: context.auth, + }, + responseType: 'arraybuffer', + } + ); + + if (response.status === 200) { + return ( + 'data:application/pdf;base64,' + + Buffer.from(response.data).toString('base64') + ); + } + + return []; + }, +}); diff --git a/packages/pieces/community/kizeo-forms/src/lib/actions/edit-list-item.ts b/packages/pieces/community/kizeo-forms/src/lib/actions/edit-list-item.ts new file mode 100644 index 0000000..95d9f34 --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/actions/edit-list-item.ts @@ -0,0 +1,71 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { endpoint, kizeoFormsCommon } from '../common'; +import { kizeoFormsAuth } from '../..'; + +export const editListItem = createAction({ + auth: kizeoFormsAuth, + + name: 'edit_list_item', + displayName: 'Edit List Item', + description: 'Edit a specific item in a list', + props: { + listId: kizeoFormsCommon.listId, + itemId: Property.ShortText({ + displayName: 'Item Id', + description: 'The ID of the item to edit', + required: true, + }), + itemLabel: Property.ShortText({ + displayName: 'Item Label', + description: 'Label for the new list item', + required: true, + }), + properties: kizeoFormsCommon.listProperties, + }, + async run(context) { + const { listId, itemId, itemLabel, properties } = context.propsValue; + + type Body = { + items: [ + { + item_id: string; + label: string; + properties: Record; + } + ]; + }; + + const body: Body = { + items: [ + { + item_id: itemId, + label: itemLabel, + properties: {}, + }, + ], + }; + for (let i = 0; i < Object.keys(properties).length; i++) { + const propertyId = Object.keys(properties)[i]; + const propertyValue = properties[Object.keys(properties)[i]]; + body.items[0].properties[propertyId] = parseFloat(propertyValue) + ? parseFloat(propertyValue) + : propertyValue; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.PATCH, + url: endpoint + `public/v4/lists/${listId}/items?used-with-activepieces=`, + headers: { + 'Content-Type': 'application/json', + Authorization: context.auth, + }, + body: body, + }); + + if (response.status === 200) { + return response.body; + } + return []; + }, +}); diff --git a/packages/pieces/community/kizeo-forms/src/lib/actions/get-all-list-items.ts b/packages/pieces/community/kizeo-forms/src/lib/actions/get-all-list-items.ts new file mode 100644 index 0000000..7eedbd4 --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/actions/get-all-list-items.ts @@ -0,0 +1,73 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { endpoint, kizeoFormsCommon } from '../common'; +import { kizeoFormsAuth } from '../..'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const getAllListItems = createAction({ + auth: kizeoFormsAuth, + + name: 'get_all_list_items', + displayName: 'Get All List Items', + description: 'Get all items from a specific list', + props: { + search: Property.ShortText({ + displayName: 'Search', + description: 'Pattern to search', + required: false, + }), + offset: Property.Number({ + displayName: 'Offset', + required: false, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Max number of results to return', + required: false, + }), + sort: Property.ShortText({ + displayName: 'Sort', + description: 'Target for sorting', + required: false, + }), + direction: Property.ShortText({ + displayName: 'Direction', + description: 'Sorting: asc or desc', + required: false, + }), + listId: kizeoFormsCommon.listId, + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + limit: z.number().min(1).optional(), + }); + const { listId, search, offset, limit, sort, direction } = + context.propsValue; + + let parameters = ''; + if (search) parameters += `search=${search}&`; + if (offset) parameters += `offset=${offset}&`; + if (limit) parameters += `limit=${limit}&`; + if (sort) parameters += `sort=${sort}&`; + if (direction) parameters += `direction=${direction}&`; + + const response = await httpClient.sendRequest<{ data: unknown }>({ + method: HttpMethod.GET, + url: + endpoint + + `public/v4/lists/${listId}/items?${parameters}used-with-activepieces=`, + headers: { + 'Content-Type': 'application/json', + Authorization: context.auth, + }, + }); + if (response.status === 200) { + return response.body; + } + return []; + }, +}); diff --git a/packages/pieces/community/kizeo-forms/src/lib/actions/get-data-definition.ts b/packages/pieces/community/kizeo-forms/src/lib/actions/get-data-definition.ts new file mode 100644 index 0000000..98704c3 --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/actions/get-data-definition.ts @@ -0,0 +1,38 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { endpoint, kizeoFormsCommon } from '../common'; +import { kizeoFormsAuth } from '../..'; + +export const getDataDefinition = createAction({ + auth: kizeoFormsAuth, + + name: 'get_data_definition', // Must be a unique across the piece, this shouldn't be changed. + displayName: 'Get Data Definition', + description: 'Get the definition of a data', + props: { + formId: kizeoFormsCommon.formId, + dataId: Property.Number({ + displayName: 'Data Id', + description: undefined, + required: true, + }), + }, + async run(context) { + const { formId, dataId } = context.propsValue; + const response = await httpClient.sendRequest<{ data: unknown }>({ + method: HttpMethod.GET, + url: + endpoint + + `v3/forms/${formId}/data/${dataId}?format=4&used-with-actives-pieces=`, + headers: { + 'Content-Type': 'application/json', + Authorization: context.auth, + }, + }); + if (response.status === 200) { + return response.body.data; + } + + return []; + }, +}); diff --git a/packages/pieces/community/kizeo-forms/src/lib/actions/get-list-definition.ts b/packages/pieces/community/kizeo-forms/src/lib/actions/get-list-definition.ts new file mode 100644 index 0000000..1a860f1 --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/actions/get-list-definition.ts @@ -0,0 +1,33 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { endpoint, kizeoFormsCommon } from '../common'; +import { kizeoFormsAuth } from '../..'; + +export const getListDefinition = createAction({ + auth: kizeoFormsAuth, + + name: 'get_list_definition', + displayName: 'Get List Definition', + description: 'Get the definition of a list', + props: { + listId: kizeoFormsCommon.listId, + }, + async run(context) { + const { listId } = context.propsValue; + const response = await httpClient.sendRequest<{ list: unknown }>({ + method: HttpMethod.GET, + url: + endpoint + + `public/v4/lists/${listId}/definition?used-with-activepieces=`, + headers: { + 'Content-Type': 'application/json', + Authorization: context.auth, + }, + }); + if (response.status === 200) { + return response.body; + } + + return []; + }, +}); diff --git a/packages/pieces/community/kizeo-forms/src/lib/actions/get-list-item.ts b/packages/pieces/community/kizeo-forms/src/lib/actions/get-list-item.ts new file mode 100644 index 0000000..2ba2816 --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/actions/get-list-item.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { endpoint, kizeoFormsCommon } from '../common'; +import { kizeoFormsAuth } from '../..'; + +export const getListItem = createAction({ + auth: kizeoFormsAuth, + + name: 'get_list_item', + displayName: 'Get List Item', + description: 'Get a specific item from a list', + props: { + listId: kizeoFormsCommon.listId, + itemId: Property.ShortText({ + displayName: 'Item Id', + description: 'The ID of the item you want to retrieve from the list', + required: true, + }), + }, + async run(context) { + const { listId, itemId } = context.propsValue; + const response = await httpClient.sendRequest<{ data: unknown }>({ + method: HttpMethod.GET, + url: + endpoint + + `public/v4/lists/${listId}/items/${itemId}?used-with-activepieces=`, + headers: { + 'Content-Type': 'application/json', + Authorization: context.auth, + }, + }); + if (response.status === 200) { + return response.body; + } + return []; + }, +}); diff --git a/packages/pieces/community/kizeo-forms/src/lib/actions/push-data.ts b/packages/pieces/community/kizeo-forms/src/lib/actions/push-data.ts new file mode 100644 index 0000000..9d508b2 --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/actions/push-data.ts @@ -0,0 +1,49 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { endpoint, kizeoFormsCommon } from '../common'; +import { kizeoFormsAuth } from '../..'; + +export const pushData = createAction({ + auth: kizeoFormsAuth, + name: 'push_data', // Must be a unique across the piece, this shouldn't be changed. + displayName: 'Push Data', + description: 'Push a data to a form', + props: { + formId: kizeoFormsCommon.formId, + userId: kizeoFormsCommon.userId, + fields: kizeoFormsCommon.fields, + }, + async run(context) { + const { formId, userId, fields } = context.propsValue; + + type Body = { + recipient_user_id: string | undefined; + fields: Record; + }; + + const body: Body = { + recipient_user_id: userId, + fields: {}, + }; + + for (let i = 0; i < Object.keys(fields).length; i++) { + const fieldId = Object.keys(fields)[i]; + body.fields[fieldId] = { value: fields[Object.keys(fields)[i]] }; + } + + const response = await httpClient.sendRequest<{ data: unknown }>({ + method: HttpMethod.POST, + url: endpoint + `v3/forms/${formId}/push?used-with-actives-pieces=`, + headers: { + 'Content-Type': 'application/json', + Authorization: context.auth, + }, + body: body, + }); + if (response.status === 200) { + return response.body.data; + } + + return []; + }, +}); diff --git a/packages/pieces/community/kizeo-forms/src/lib/common/index.ts b/packages/pieces/community/kizeo-forms/src/lib/common/index.ts new file mode 100644 index 0000000..7b7aabb --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/common/index.ts @@ -0,0 +1,393 @@ +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { + KizeoFormsDataUsers, + KizeoFormsExports, + KizeoFormsForms, + KizeoFormsList, + KizeoFormsLists, +} from './models'; + +export const endpoint = 'https://forms.kizeo.com/rest/'; +export const kizeoFormsCommon = { + formId: Property.Dropdown({ + displayName: 'Form', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account', + }; + } + + try { + const forms: KizeoFormsForms[] = await kizeoFormsCommon.fetchForms({ + token: auth as string, + }); + + if (forms) { + return { + disabled: false, + options: forms.map((forms) => ({ + value: forms.id, + label: forms.name, + })), + }; + } + } catch (e) { + console.debug(e); + + return { + disabled: true, + options: [], + placeholder: 'Please check your permission scope', + }; + } + + return { + disabled: true, + options: [], + }; + }, + }), + userId: Property.Dropdown({ + displayName: 'User', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account', + }; + } + + try { + const dataUsers: KizeoFormsDataUsers = + await kizeoFormsCommon.fetchUsers({ + token: auth as string, + }); + if (dataUsers) { + return { + disabled: false, + options: dataUsers.users.map((users) => ({ + value: users.id, + label: users.first_name + ' ' + users.last_name, + })), + }; + } + } catch (e) { + console.debug(e); + + return { + disabled: true, + options: [], + placeholder: 'Please check your permission scope', + }; + } + + return { + disabled: true, + options: [], + }; + }, + }), + exportId: Property.Dropdown({ + displayName: 'Export', + required: true, + refreshers: ['formId'], + options: async ({ auth, formId }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account', + }; + } + if (!formId) { + return { + disabled: true, + options: [], + placeholder: 'Please select a form', + }; + } + try { + const exportList: KizeoFormsExports[] = + await kizeoFormsCommon.fetchExports({ + token: auth as string, + formId: formId as string, + }); + if (exportList) { + return { + disabled: false, + options: exportList.map((exportItem) => ({ + value: exportItem.id, + label: exportItem.name, + })), + }; + } + } catch (e) { + console.debug(e); + + return { + disabled: true, + options: [], + placeholder: 'Please check your permission scope', + }; + } + + return { + disabled: true, + options: [], + }; + }, + }), + fields: Property.DynamicProperties({ + displayName: 'Form', + required: true, + refreshers: ['formId'], + + props: async ({ auth, formId }) => { + if (!auth) return {}; + if (!formId) return {}; + + const fields: DynamicPropsValue = {}; + + try { + const form: KizeoFormsForms = await kizeoFormsCommon.fetchForm({ + token: auth as unknown as string, + formId: formId as unknown as string, + }); + + const results: Record = form.fields; + + for (let i = 0; i < Object.keys(results).length; i++) { + const fieldId = Object.keys(results)[i]; + + if (Object.values(results)[i].type === 'text') { + fields[fieldId] = Property.ShortText({ + displayName: Object.values(results)[i].caption, + required: Object.values(results)[i].required, + }); + } + } + } catch (e) { + console.debug(e); + } + + return fields; + }, + }), + listId: Property.Dropdown({ + displayName: 'List', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account', + }; + } + + try { + const lists: KizeoFormsLists[] = await kizeoFormsCommon.fetchLists({ + token: auth as string, + }); + + if (lists) { + return { + disabled: false, + options: lists.map((lists) => ({ + value: lists.id, + label: lists.name, + })), + }; + } + } catch (e) { + console.debug(e); + + return { + disabled: true, + options: [], + placeholder: 'Please check your permission scope', + }; + } + + return { + disabled: true, + options: [], + }; + }, + }), + listProperties: Property.DynamicProperties({ + displayName: 'List', + required: true, + refreshers: ['listId'], + + props: async ({ auth, listId }) => { + if (!auth) return {}; + if (!listId) return {}; + + const properties: DynamicPropsValue = {}; + + try { + const list: KizeoFormsList = await kizeoFormsCommon.fetchList({ + token: auth as unknown as string, + listId: listId as unknown as string, + }); + + const results: Record = list.properties_definition; + + for (let i = 0; i < Object.keys(results).length; i++) { + const propertyId = Object.keys(results)[i]; + + if (Object.values(results)[i].type === 'string') { + properties[propertyId] = Property.ShortText({ + displayName: Object.values(results)[i].display_name, + required: false, + }); + } else { + properties[propertyId] = Property.Number({ + displayName: Object.values(results)[i].display_name, + required: false, + }); + } + } + } catch (e) { + console.debug(e); + } + + return properties; + }, + }), + + async fetchForms({ token }: { token: string }): Promise { + const response = await httpClient.sendRequest<{ forms: KizeoFormsForms[] }>( + { + method: HttpMethod.GET, + url: endpoint + `v3/forms?used-with-activepieces=`, + headers: { + 'Content-Type': 'application/json', + Authorization: token, + }, + } + ); + + if (response.status === 200) { + return response.body.forms; + } + + return []; + }, + async fetchUsers({ token }: { token: string }): Promise { + const response = await httpClient.sendRequest<{ + data: KizeoFormsDataUsers; + }>({ + method: HttpMethod.GET, + url: endpoint + `v3/users?used-with-activepieces=`, + headers: { + 'Content-Type': 'application/json', + Authorization: token, + }, + }); + + if (response.status === 200) { + return response.body.data; + } + return { users: [] }; + }, + async fetchForm({ + token, + formId, + }: { + token: string; + formId: string; + }): Promise { + const response = await httpClient.sendRequest<{ form: KizeoFormsForms }>({ + method: HttpMethod.GET, + url: endpoint + `v3/forms/${formId}?used-with-activepieces=`, + headers: { + 'Content-Type': 'application/json', + Authorization: token, + }, + }); + + if (response.status === 200) { + return response.body.form; + } else { + throw new Error(`Failed to fetch form ${formId}`); + } + }, + async fetchExports({ + token, + formId, + }: { + token: string; + formId: string; + }): Promise { + const response = await httpClient.sendRequest<{ + exports: KizeoFormsExports[]; + }>({ + method: HttpMethod.GET, + url: endpoint + `v3/forms/${formId}/exports?used-with-activepieces=`, + headers: { + 'Content-Type': 'application/json', + Authorization: token, + }, + }); + if (response.status === 200) { + return response.body.exports; + } else { + throw new Error(`Failed to fetch exports ${formId}`); + } + }, + async fetchLists({ token }: { token: string }): Promise { + const response = await httpClient.sendRequest<{ lists: KizeoFormsLists[] }>( + { + method: HttpMethod.GET, + url: endpoint + `v3/lists?used-with-activepieces=`, + headers: { + 'Content-Type': 'application/json', + Authorization: token, + }, + } + ); + + if (response.status === 200) { + return response.body.lists; + } + + return []; + }, + async fetchList({ + token, + listId, + }: { + token: string; + listId: string; + }): Promise { + const response = await httpClient.sendRequest<{ list: KizeoFormsList }>({ + method: HttpMethod.GET, + url: + endpoint + + `public/v4/lists/${listId}/definition?used-with-activepieces=`, + headers: { + 'Content-Type': 'application/json', + Authorization: token, + }, + }); + + if (response.status === 200) { + const list: KizeoFormsList = response.body as unknown as KizeoFormsList; + return list; + } else { + throw new Error(`Failed to fetch form ${listId}`); + } + }, +}; diff --git a/packages/pieces/community/kizeo-forms/src/lib/common/models.ts b/packages/pieces/community/kizeo-forms/src/lib/common/models.ts new file mode 100644 index 0000000..098f65e --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/common/models.ts @@ -0,0 +1,42 @@ +export interface KizeoFormsForms { + id: string; + name: string; + fields: Record; +} +export interface KizeoFormsFields { + caption: string; + type: string; + required: boolean; +} +export interface KizeoFormsDataExports { + exports: KizeoFormsExports[]; +} +export interface KizeoFormsExports { + id: string; + name: string; +} +export interface KizeoFormsDataUsers { + users: KizeoFormsUsers[]; +} +export interface KizeoFormsUsers { + id: string; + login: string; + first_name: string; + last_name: string; +} +export interface KizeoFormsLists { + id: string; + name: string; +} +export interface KizeoFormsList { + list_name: string; + properties_definition: Record; + order_by: Array<{ id: string; type: string }>; + group_by: string[]; +} + +export interface KizeoFormsListProperty { + display_name: string; + type: string; + id: string; +} diff --git a/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data-deleted.ts b/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data-deleted.ts new file mode 100644 index 0000000..1200853 --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data-deleted.ts @@ -0,0 +1,156 @@ +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { endpoint, kizeoFormsCommon } from '../common'; +import { kizeoFormsAuth } from '../..'; + +const triggerNameInStore = 'event_on_data_deleted_trigger'; +export const eventOnDataDeleted = createTrigger({ + auth: kizeoFormsAuth, + name: 'event_on_data_deleted', + displayName: 'Event On Data Deleted', + description: 'Handle EventOnData delete event via webhooks', + props: { + format: Property.StaticDropdown({ + displayName: 'Output Format', + description: 'Select the output format', + required: true, + defaultValue: 'simple', + options: { + options: [ + { + label: 'Simple format', + value: 'simple', + }, + { + label: 'Advanced format', + value: 'advanced', + }, + ], + }, + }), + formId: kizeoFormsCommon.formId, + }, + sampleData: { + id: '1', + eventType: '[delete]', + data: { + format: '4', + answer_time: '2023-04-11T13:59:23+02:00', + update_answer_time: '2023-04-11T13:59:23+02:00', + id_tel: 'web2', + form_id: '1', + origin: 'web', + app_version: 'webapp', + media: [], + fields: {}, + id: '1', + user_id: '1', + recipient_id: '1', + parent_data_id: null, + }, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const { formId } = context.propsValue; + const webhookUrl = context.webhookUrl; + // eslint-disable-next-line no-useless-escape + const match = webhookUrl.match(/\/webhooks\/([^\/]+)/); + let workflowId = 'FlowId'; + if (match) { + workflowId = match[1]; + } + const request: HttpRequest = { + method: HttpMethod.POST, + url: + endpoint + + `public/v4/forms/${formId}/third_party_webhooks?used-with-actives-pieces=`, + body: { + on_events: ['delete'], + url: webhookUrl, + http_verb: 'POST', + body_content_choice: 'json_v4', + third_party: 'ActivePieces ', + third_party_id: workflowId, + }, + headers: { + Authorization: context.auth, + }, + queryParams: {}, + }; + const { body } = await httpClient.sendRequest<{ id: string }>(request); + await context.store?.put(triggerNameInStore, { + webhookId: body.id, + }); + }, + async onDisable(context) { + const { formId } = context.propsValue; + const response = await context.store?.get( + triggerNameInStore + ); + if (response !== null && response !== undefined) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: + endpoint + + `public/v4/forms/${formId}/third_party_webhooks/${response.webhookId}?used-with-actives-pieces=`, + headers: { + Authorization: context.auth, + }, + }; + await httpClient.sendRequest(request); + } + }, + async run(context) { + if (!context.payload.body) { + return []; + } + const body = context.payload.body as BodyDataType; + const formattedData: FormattedData = { + id: body.id, + }; + if (context.propsValue.format === 'simple') { + for (const fieldKey in body.data.fields) { + if ( + body.data.fields[fieldKey].result && + body.data.fields[fieldKey].result?.value !== undefined + ) { + const newFieldKey = fieldKey; + formattedData[newFieldKey] = body.data.fields[fieldKey].result?.value; + } + } + return [formattedData]; + } + + return [body]; + }, +}); + +interface FormattedData { + id: string; + [key: string]: any; +} + +interface BodyDataType { + id: string; + data: { + fields: { + [key: string]: { + result?: { + value: any; + }; + }; + }; + }; +} + +interface KizeoFormsWebhookInformation { + webhookId: string; +} diff --git a/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data-finished.ts b/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data-finished.ts new file mode 100644 index 0000000..cec0be3 --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data-finished.ts @@ -0,0 +1,156 @@ +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { endpoint, kizeoFormsCommon } from '../common'; +import { kizeoFormsAuth } from '../..'; + +const triggerNameInStore = 'event_on_data_finished_trigger'; +export const eventOnDataFinished = createTrigger({ + auth: kizeoFormsAuth, + name: 'event_on_data_finished', + displayName: 'Event On Data Finished', + description: 'Handle EventOnData save event via webhooks', + props: { + format: Property.StaticDropdown({ + displayName: 'Output Format', + description: 'Select the output format', + required: true, + defaultValue: 'simple', + options: { + options: [ + { + label: 'Simple format', + value: 'simple', + }, + { + label: 'Advanced format', + value: 'advanced', + }, + ], + }, + }), + formId: kizeoFormsCommon.formId, + }, + sampleData: { + id: '1', + eventType: '[finished]', + data: { + format: '4', + answer_time: '2023-04-11T13:59:23+02:00', + update_answer_time: '2023-04-11T13:59:23+02:00', + id_tel: 'web2', + form_id: '1', + origin: 'web', + app_version: 'webapp', + media: [], + fields: {}, + id: '1', + user_id: '1', + recipient_id: '1', + parent_data_id: null, + }, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const { formId } = context.propsValue; + const webhookUrl = context.webhookUrl; + // eslint-disable-next-line no-useless-escape + const match = webhookUrl.match(/\/webhooks\/([^\/]+)/); + let workflowId = 'FlowId'; + if (match) { + workflowId = match[1]; + } + const request: HttpRequest = { + method: HttpMethod.POST, + url: + endpoint + + `public/v4/forms/${formId}/third_party_webhooks?used-with-actives-pieces=`, + body: { + on_events: ['finished'], + url: webhookUrl, + http_verb: 'POST', + body_content_choice: 'json_v4', + third_party: 'ActivePieces ', + third_party_id: workflowId, + }, + headers: { + Authorization: context.auth, + }, + queryParams: {}, + }; + const { body } = await httpClient.sendRequest<{ id: string }>(request); + await context.store?.put(triggerNameInStore, { + webhookId: body.id, + }); + }, + async onDisable(context) { + const { formId } = context.propsValue; + const response = await context.store?.get( + triggerNameInStore + ); + if (response !== null && response !== undefined) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: + endpoint + + `public/v4/forms/${formId}/third_party_webhooks/${response.webhookId}?used-with-actives-pieces=`, + headers: { + Authorization: context.auth, + }, + }; + await httpClient.sendRequest(request); + } + }, + async run(context) { + if (!context.payload.body) { + return []; + } + const body = context.payload.body as BodyDataType; + const formattedData: FormattedData = { + id: body.id, + }; + if (context.propsValue.format === 'simple') { + for (const fieldKey in body.data.fields) { + if ( + body.data.fields[fieldKey].result && + body.data.fields[fieldKey].result?.value !== undefined + ) { + const newFieldKey = fieldKey; + formattedData[newFieldKey] = body.data.fields[fieldKey].result?.value; + } + } + return [formattedData]; + } + + return [body]; + }, +}); + +interface FormattedData { + id: string; + [key: string]: any; +} + +interface BodyDataType { + id: string; + data: { + fields: { + [key: string]: { + result?: { + value: any; + }; + }; + }; + }; +} + +interface KizeoFormsWebhookInformation { + webhookId: string; +} diff --git a/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data-pushed.ts b/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data-pushed.ts new file mode 100644 index 0000000..84afe23 --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data-pushed.ts @@ -0,0 +1,156 @@ +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { endpoint, kizeoFormsCommon } from '../common'; +import { kizeoFormsAuth } from '../..'; + +const triggerNameInStore = 'event_on_data_pushed_trigger'; +export const eventOnDataPushed = createTrigger({ + auth: kizeoFormsAuth, + name: 'event_on_data_pushed', + displayName: 'Event On Data Pushed', + description: 'Handle EventOnData push event via webhooks', + props: { + format: Property.StaticDropdown({ + displayName: 'Output Format', + description: 'Select the output format', + required: true, + defaultValue: 'simple', + options: { + options: [ + { + label: 'Simple format', + value: 'simple', + }, + { + label: 'Advanced format', + value: 'advanced', + }, + ], + }, + }), + formId: kizeoFormsCommon.formId, + }, + sampleData: { + id: '1', + eventType: '[push]', + data: { + format: '4', + answer_time: '2023-04-11T13:59:23+02:00', + update_answer_time: '2023-04-11T13:59:23+02:00', + id_tel: 'web2', + form_id: '1', + origin: 'web', + app_version: 'webapp', + media: [], + fields: {}, + id: '1', + user_id: '1', + recipient_id: '1', + parent_data_id: null, + }, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const { formId } = context.propsValue; + const webhookUrl = context.webhookUrl; + // eslint-disable-next-line no-useless-escape + const match = webhookUrl.match(/\/webhooks\/([^\/]+)/); + let workflowId = 'FlowId'; + if (match) { + workflowId = match[1]; + } + const request: HttpRequest = { + method: HttpMethod.POST, + url: + endpoint + + `public/v4/forms/${formId}/third_party_webhooks?used-with-actives-pieces=`, + body: { + on_events: ['push'], + url: webhookUrl, + http_verb: 'POST', + body_content_choice: 'json_v4', + third_party: 'ActivePieces ', + third_party_id: workflowId, + }, + headers: { + Authorization: context.auth, + }, + queryParams: {}, + }; + const { body } = await httpClient.sendRequest<{ id: string }>(request); + await context.store?.put(triggerNameInStore, { + webhookId: body.id, + }); + }, + async onDisable(context) { + const { formId } = context.propsValue; + const response = await context.store?.get( + triggerNameInStore + ); + if (response !== null && response !== undefined) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: + endpoint + + `public/v4/forms/${formId}/third_party_webhooks/${response.webhookId}?used-with-actives-pieces=`, + headers: { + Authorization: context.auth, + }, + }; + await httpClient.sendRequest(request); + } + }, + async run(context) { + if (!context.payload.body) { + return []; + } + const body = context.payload.body as BodyDataType; + const formattedData: FormattedData = { + id: body.id, + }; + if (context.propsValue.format === 'simple') { + for (const fieldKey in body.data.fields) { + if ( + body.data.fields[fieldKey].result && + body.data.fields[fieldKey].result?.value !== undefined + ) { + const newFieldKey = fieldKey; + formattedData[newFieldKey] = body.data.fields[fieldKey].result?.value; + } + } + return [formattedData]; + } + + return [body]; + }, +}); + +interface FormattedData { + id: string; + [key: string]: any; +} + +interface BodyDataType { + id: string; + data: { + fields: { + [key: string]: { + result?: { + value: any; + }; + }; + }; + }; +} + +interface KizeoFormsWebhookInformation { + webhookId: string; +} diff --git a/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data-received.ts b/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data-received.ts new file mode 100644 index 0000000..fc0bd72 --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data-received.ts @@ -0,0 +1,156 @@ +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { endpoint, kizeoFormsCommon } from '../common'; +import { kizeoFormsAuth } from '../..'; + +const triggerNameInStore = 'event_on_data_received_trigger'; +export const eventOnDataPulled = createTrigger({ + auth: kizeoFormsAuth, + name: 'event_on_data_received', + displayName: 'Event On Data Received', + description: 'Handle EventOnData receive event via webhooks', + props: { + format: Property.StaticDropdown({ + displayName: 'Output Format', + description: 'Select the output format', + required: true, + defaultValue: 'simple', + options: { + options: [ + { + label: 'Simple format', + value: 'simple', + }, + { + label: 'Advanced format', + value: 'advanced', + }, + ], + }, + }), + formId: kizeoFormsCommon.formId, + }, + sampleData: { + id: '1', + eventType: '[pull]', + data: { + format: '4', + answer_time: '2023-04-11T13:59:23+02:00', + update_answer_time: '2023-04-11T13:59:23+02:00', + id_tel: 'web2', + form_id: '1', + origin: 'web', + app_version: 'webapp', + media: [], + fields: {}, + id: '1', + user_id: '1', + recipient_id: '1', + parent_data_id: null, + }, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const { formId } = context.propsValue; + const webhookUrl = context.webhookUrl; + // eslint-disable-next-line no-useless-escape + const match = webhookUrl.match(/\/webhooks\/([^\/]+)/); + let workflowId = 'FlowId'; + if (match) { + workflowId = match[1]; + } + const request: HttpRequest = { + method: HttpMethod.POST, + url: + endpoint + + `public/v4/forms/${formId}/third_party_webhooks?used-with-actives-pieces=`, + body: { + on_events: ['pull'], + url: webhookUrl, + http_verb: 'POST', + body_content_choice: 'json_v4', + third_party: 'ActivePieces ', + third_party_id: workflowId, + }, + headers: { + Authorization: context.auth, + }, + queryParams: {}, + }; + const { body } = await httpClient.sendRequest<{ id: string }>(request); + await context.store?.put(triggerNameInStore, { + webhookId: body.id, + }); + }, + async onDisable(context) { + const { formId } = context.propsValue; + const response = await context.store?.get( + triggerNameInStore + ); + if (response !== null && response !== undefined) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: + endpoint + + `public/v4/forms/${formId}/third_party_webhooks/${response.webhookId}?used-with-actives-pieces=`, + headers: { + Authorization: context.auth, + }, + }; + await httpClient.sendRequest(request); + } + }, + async run(context) { + if (!context.payload.body) { + return []; + } + const body = context.payload.body as BodyDataType; + const formattedData: FormattedData = { + id: body.id, + }; + if (context.propsValue.format === 'simple') { + for (const fieldKey in body.data.fields) { + if ( + body.data.fields[fieldKey].result && + body.data.fields[fieldKey].result?.value !== undefined + ) { + const newFieldKey = fieldKey; + formattedData[newFieldKey] = body.data.fields[fieldKey].result?.value; + } + } + return [formattedData]; + } + + return [body]; + }, +}); + +interface FormattedData { + id: string; + [key: string]: any; +} + +interface BodyDataType { + id: string; + data: { + fields: { + [key: string]: { + result?: { + value: any; + }; + }; + }; + }; +} + +interface KizeoFormsWebhookInformation { + webhookId: string; +} diff --git a/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data-updated.ts b/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data-updated.ts new file mode 100644 index 0000000..692a2eb --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data-updated.ts @@ -0,0 +1,156 @@ +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { endpoint, kizeoFormsCommon } from '../common'; +import { kizeoFormsAuth } from '../..'; + +const triggerNameInStore = 'event_on_data_updated_trigger'; +export const eventOnDataUpdated = createTrigger({ + auth: kizeoFormsAuth, + name: 'event_on_data_updated', + displayName: 'Event On Data Updated', + description: 'Handle EventOnData update event via webhooks', + props: { + format: Property.StaticDropdown({ + displayName: 'Output Format', + description: 'Select the output format', + required: true, + defaultValue: 'simple', + options: { + options: [ + { + label: 'Simple format', + value: 'simple', + }, + { + label: 'Advanced format', + value: 'advanced', + }, + ], + }, + }), + formId: kizeoFormsCommon.formId, + }, + sampleData: { + id: '1', + eventType: '[finished]', + data: { + format: '4', + answer_time: '2023-04-11T13:59:23+02:00', + update_answer_time: '2023-04-11T13:59:23+02:00', + id_tel: 'web2', + form_id: '1', + origin: 'web', + app_version: 'webapp', + media: [], + fields: {}, + id: '1', + user_id: '1', + recipient_id: '1', + parent_data_id: null, + }, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const { formId } = context.propsValue; + const webhookUrl = context.webhookUrl; + // eslint-disable-next-line no-useless-escape + const match = webhookUrl.match(/\/webhooks\/([^\/]+)/); + let workflowId = 'FlowId'; + if (match) { + workflowId = match[1]; + } + const request: HttpRequest = { + method: HttpMethod.POST, + url: + endpoint + + `public/v4/forms/${formId}/third_party_webhooks?used-with-actives-pieces=`, + body: { + on_events: ['update'], + url: webhookUrl, + http_verb: 'POST', + body_content_choice: 'json_v4', + third_party: 'ActivePieces ', + third_party_id: workflowId, + }, + headers: { + Authorization: context.auth, + }, + queryParams: {}, + }; + const { body } = await httpClient.sendRequest<{ id: string }>(request); + await context.store?.put(triggerNameInStore, { + webhookId: body.id, + }); + }, + async onDisable(context) { + const { formId } = context.propsValue; + const response = await context.store?.get( + triggerNameInStore + ); + if (response !== null && response !== undefined) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: + endpoint + + `public/v4/forms/${formId}/third_party_webhooks/${response.webhookId}?used-with-actives-pieces=`, + headers: { + Authorization: context.auth, + }, + }; + await httpClient.sendRequest(request); + } + }, + async run(context) { + if (!context.payload.body) { + return []; + } + const body = context.payload.body as BodyDataType; + const formattedData: FormattedData = { + id: body.id, + }; + if (context.propsValue.format === 'simple') { + for (const fieldKey in body.data.fields) { + if ( + body.data.fields[fieldKey].result && + body.data.fields[fieldKey].result?.value !== undefined + ) { + const newFieldKey = fieldKey; + formattedData[newFieldKey] = body.data.fields[fieldKey].result?.value; + } + } + return [formattedData]; + } + + return [body]; + }, +}); + +interface FormattedData { + id: string; + [key: string]: any; +} + +interface BodyDataType { + id: string; + data: { + fields: { + [key: string]: { + result?: { + value: any; + }; + }; + }; + }; +} + +interface KizeoFormsWebhookInformation { + webhookId: string; +} diff --git a/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data.trigger.ts b/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data.trigger.ts new file mode 100644 index 0000000..dfb05ba --- /dev/null +++ b/packages/pieces/community/kizeo-forms/src/lib/trigger/event-on-data.trigger.ts @@ -0,0 +1,325 @@ +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { endpoint, kizeoFormsCommon } from '../common'; +import { kizeoFormsAuth } from '../..'; + +const triggerNameInStore = 'event_on_data_trigger'; +export const eventOnData = createTrigger({ + auth: kizeoFormsAuth, + name: 'event_on_data', + displayName: 'Event On Data', + description: 'Handle EventOnData events via webhooks', + props: { + format: Property.StaticDropdown({ + displayName: 'Output Format', + description: 'Select the output format', + required: true, + defaultValue: 'simple', + options: { + options: [ + { + label: 'Simple format', + value: 'simple', + }, + { + label: 'Advanced format', + value: 'advanced', + }, + ], + }, + }), + formId: kizeoFormsCommon.formId, + event1: Property.StaticDropdown({ + displayName: 'Events', + description: 'Which events will trigger this hook', + required: true, + options: { + options: [ + { + label: 'Data Deleted', + value: 'delete', + }, + { + label: 'Data Saved', + value: 'finished', + }, + { + label: 'Data Updated', + value: 'update', + }, + { + label: 'Push Received', + value: 'pull', + }, + { + label: 'Push Send', + value: 'push', + }, + ], + }, + }), + event2: Property.StaticDropdown({ + displayName: 'Events', + description: 'Which events will trigger this hook', + required: false, + options: { + options: [ + { + label: '', + value: '', + }, + { + label: 'Data Deleted', + value: 'delete', + }, + { + label: 'Data Saved', + value: 'finished', + }, + { + label: 'Data Updated', + value: 'update', + }, + { + label: 'Push Received', + value: 'pull', + }, + { + label: 'Push Send', + value: 'push', + }, + ], + }, + }), + event3: Property.StaticDropdown({ + displayName: 'Events', + description: 'Which events will trigger this hook', + required: false, + options: { + options: [ + { + label: '', + value: '', + }, + { + label: 'Data Deleted', + value: 'delete', + }, + { + label: 'Data Saved', + value: 'finished', + }, + { + label: 'Data Updated', + value: 'update', + }, + { + label: 'Push Received', + value: 'pull', + }, + { + label: 'Push Send', + value: 'push', + }, + ], + }, + }), + event4: Property.StaticDropdown({ + displayName: 'Events', + description: 'Which events will trigger this hook', + required: false, + options: { + options: [ + { + label: '', + value: '', + }, + { + label: 'Data Deleted', + value: 'delete', + }, + { + label: 'Data Saved', + value: 'finished', + }, + { + label: 'Data Updated', + value: 'update', + }, + { + label: 'Push Received', + value: 'pull', + }, + { + label: 'Push Send', + value: 'push', + }, + ], + }, + }), + event5: Property.StaticDropdown({ + displayName: 'Events', + description: 'Which events will trigger this hook', + required: false, + options: { + options: [ + { + label: '', + value: '', + }, + { + label: 'Data Deleted', + value: 'delete', + }, + { + label: 'Data Saved', + value: 'finished', + }, + { + label: 'Data Updated', + value: 'update', + }, + { + label: 'Push Received', + value: 'pull', + }, + { + label: 'Push Send', + value: 'push', + }, + ], + }, + }), + }, + sampleData: { + id: '1', + eventType: '[finished, pull]', + data: { + format: '4', + answer_time: '2023-04-11T13:59:23+02:00', + update_answer_time: '2023-04-11T13:59:23+02:00', + id_tel: 'web2', + form_id: '1', + origin: 'web', + app_version: 'webapp', + media: [], + fields: {}, + id: '1', + user_id: '1', + recipient_id: '1', + parent_data_id: null, + }, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const { formId, event1, event2, event3, event4, event5 } = + context.propsValue; + const onEvents = [ + event1, + event2 !== '' ? event2 : null, + event3 !== '' ? event3 : null, + event4 !== '' ? event4 : null, + event5 !== '' ? event5 : null, + ].filter(Boolean); + const webhookUrl = context.webhookUrl; + // eslint-disable-next-line no-useless-escape + const match = webhookUrl.match(/\/webhooks\/([^\/]+)/); + let workflowId = 'FlowId'; + if (match) { + workflowId = match[1]; + } + const request: HttpRequest = { + method: HttpMethod.POST, + url: + endpoint + + `public/v4/forms/${formId}/third_party_webhooks?used-with-actives-pieces=`, + body: { + on_events: onEvents, + url: webhookUrl, + http_verb: 'POST', + body_content_choice: 'json_v4', + third_party: 'ActivePieces ', + third_party_id: workflowId, + }, + headers: { + Authorization: context.auth, + }, + queryParams: {}, + }; + const { body } = await httpClient.sendRequest<{ id: string }>(request); + await context.store?.put(triggerNameInStore, { + webhookId: body.id, + }); + }, + async onDisable(context) { + const { formId } = context.propsValue; + const response = await context.store?.get( + triggerNameInStore + ); + if (response !== null && response !== undefined) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: + endpoint + + `public/v4/forms/${formId}/third_party_webhooks/${response.webhookId}?used-with-actives-pieces=`, + headers: { + Authorization: context.auth, + }, + }; + await httpClient.sendRequest(request); + } + }, + async run(context) { + if (!context.payload.body) { + return []; + } + const body = context.payload.body as BodyDataType; + const formattedData: FormattedData = { + id: body.id, + }; + if (context.propsValue.format === 'simple') { + for (const fieldKey in body.data.fields) { + if ( + body.data.fields[fieldKey].result && + body.data.fields[fieldKey].result?.value !== undefined + ) { + const newFieldKey = fieldKey; + formattedData[newFieldKey] = body.data.fields[fieldKey].result?.value; + } + } + return [formattedData]; + } + + return [body]; + }, +}); + +interface FormattedData { + id: string; + [key: string]: any; +} + +interface BodyDataType { + id: string; + data: { + fields: { + [key: string]: { + result?: { + value: any; + }; + }; + }; + }; +} + +interface KizeoFormsWebhookInformation { + webhookId: string; +} diff --git a/packages/pieces/community/kizeo-forms/tsconfig.json b/packages/pieces/community/kizeo-forms/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/kizeo-forms/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/kizeo-forms/tsconfig.lib.json b/packages/pieces/community/kizeo-forms/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/kizeo-forms/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/kommo/.eslintrc.json b/packages/pieces/community/kommo/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/kommo/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/kommo/README.md b/packages/pieces/community/kommo/README.md new file mode 100644 index 0000000..9765989 --- /dev/null +++ b/packages/pieces/community/kommo/README.md @@ -0,0 +1,7 @@ +# pieces-kommo + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-kommo` to build the library. diff --git a/packages/pieces/community/kommo/package.json b/packages/pieces/community/kommo/package.json new file mode 100644 index 0000000..058e4f6 --- /dev/null +++ b/packages/pieces/community/kommo/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-kommo", + "version": "0.0.1" +} diff --git a/packages/pieces/community/kommo/project.json b/packages/pieces/community/kommo/project.json new file mode 100644 index 0000000..2d9e318 --- /dev/null +++ b/packages/pieces/community/kommo/project.json @@ -0,0 +1,45 @@ +{ + "name": "pieces-kommo", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/kommo/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/kommo", + "tsConfig": "packages/pieces/community/kommo/tsconfig.lib.json", + "packageJson": "packages/pieces/community/kommo/package.json", + "main": "packages/pieces/community/kommo/src/index.ts", + "assets": [ + "packages/pieces/community/kommo/*.md" + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/kommo/src/index.ts b/packages/pieces/community/kommo/src/index.ts new file mode 100644 index 0000000..25fcad9 --- /dev/null +++ b/packages/pieces/community/kommo/src/index.ts @@ -0,0 +1,52 @@ +import { createPiece, PieceAuth, PiecePropValueSchema } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { leadStatusChangedTrigger, newContactAddedTrigger, newLeadCreatedTrigger, newTaskCreatedTrigger } from "./lib/triggers"; +import { findLeadAction, updateContactAction, createLeadAction, createContactAction, findContactAction, findCompanyAction, updateLeadAction } from "./lib/actions"; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; + +const markdownDescription = ` +Please follow [Generate Long Live Token](https://developers.kommo.com/docs/long-lived-token) guide for generating token. + +Your Kommo account subdomain (e.g., "mycompany" if your URL is mycompany.kommo.com). + +`; + +export const kommoAuth = PieceAuth.CustomAuth({ + description: markdownDescription, + required: true, + props: { + subdomain: PieceAuth.SecretText({ + displayName: 'Subdomain', + required: true, + }), + apiToken: PieceAuth.SecretText({ + displayName: 'Token', + required: true, + }), + }, +}); + +export const kommo = createPiece({ + displayName: 'Kommo', + auth: kommoAuth, + logoUrl: 'https://cdn.activepieces.com/pieces/kommo.png', + categories: [PieceCategory.COMMUNICATION, PieceCategory.SALES_AND_CRM], + authors: ['krushnarout', 'kishanprmr'], + actions: [findLeadAction, updateContactAction, createLeadAction, updateLeadAction, createContactAction, findContactAction, findCompanyAction, + createCustomApiCallAction({ + auth: kommoAuth, + baseUrl: (auth) => { + const authValue = auth as PiecePropValueSchema; + return `https://${authValue.subdomain}.kommo.com/api/v4` + }, + authMapping: async (auth) => { + const authValue = auth as PiecePropValueSchema; + return { + Authorization: `Bearer ${authValue.apiToken}` + } + + } + }) + ], + triggers: [leadStatusChangedTrigger, newContactAddedTrigger, newLeadCreatedTrigger, newTaskCreatedTrigger], +}); diff --git a/packages/pieces/community/kommo/src/lib/actions/create-new-contact.ts b/packages/pieces/community/kommo/src/lib/actions/create-new-contact.ts new file mode 100644 index 0000000..f699abe --- /dev/null +++ b/packages/pieces/community/kommo/src/lib/actions/create-new-contact.ts @@ -0,0 +1,103 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common'; +import { kommoAuth } from '../../index'; +import { userDropdown } from '../common/props'; + +interface KommoCustomFieldValue { + field_id?: number; + field_code?: string; + values: Array<{ value: string | number; enum_id?: number }>; +} + +export const createContactAction = createAction({ + auth: kommoAuth, + name: 'create_contact', + displayName: 'Create New Contact', + description: 'Add a new contact.', + props: { + name: Property.ShortText({ + displayName: 'Full Name', + required: true, + }), + first_name: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + last_name: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + required: false, + }), + phone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + responsible_user_id: userDropdown(), + tags_to_add: Property.Array({ + displayName: 'Tags to Add', + required: false, + }) + }, + async run(context) { + const { + name, + first_name, + last_name, + email, + phone, + responsible_user_id, + } = context.propsValue; + const tagsToAdd = context.propsValue.tags_to_add ?? []; + + + const { subdomain, apiToken } = context.auth as { + subdomain: string; + apiToken: string; + }; + + const customFields: KommoCustomFieldValue[] = []; + + if (email) { + customFields.push({ + field_code: 'EMAIL', + values: [{ value: email }], + }); + } + + if (phone) { + customFields.push({ + field_code: 'PHONE', + values: [{ value: phone }], + }); + } + + + const contactPayload: Record = { + ...(customFields.length > 0 ? { custom_fields_values: customFields } : {}), + }; + + + if (name) contactPayload['name'] = name; + if (first_name) contactPayload['first_name'] = first_name; + if (last_name) contactPayload['last_name'] = last_name; + if (responsible_user_id) contactPayload['responsible_user_id'] = responsible_user_id; + + + if (tagsToAdd.length > 0) { + contactPayload['tags_to_add'] = tagsToAdd.map((tag) => ({ name: tag })) + } + + const result = await makeRequest( + { apiToken, subdomain }, + HttpMethod.POST, + `/contacts`, + [contactPayload] + ); + + return result; + }, +}); diff --git a/packages/pieces/community/kommo/src/lib/actions/create-new-lead.ts b/packages/pieces/community/kommo/src/lib/actions/create-new-lead.ts new file mode 100644 index 0000000..55340bd --- /dev/null +++ b/packages/pieces/community/kommo/src/lib/actions/create-new-lead.ts @@ -0,0 +1,62 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common'; +import { kommoAuth } from '../../index'; +import { + pipelineDropdown, + statusDropdown, + userDropdown, + lossReasonDropdown, +} from '../common/props'; + +export const createLeadAction = createAction({ + auth: kommoAuth, + name: 'create_lead', + displayName: 'Create New Lead', + description: 'Creates a new lead.', + props: { + name: Property.ShortText({ + displayName: 'Lead Name', + required: true, + }), + price: Property.Number({ + displayName: 'Price', + required: false, + }), + pipelineId: pipelineDropdown(true), + statusId: statusDropdown(true), + responsible_user_id: userDropdown(), + loss_reason_id: lossReasonDropdown(), + tags_to_add: Property.Array({ + displayName: 'Tags to Add', + description: 'List of tags to add.', + required: false, + }), + }, + async run(context) { + const { name, price, statusId, pipelineId, loss_reason_id, responsible_user_id } = + context.propsValue; + + const tagsToAdd = context.propsValue.tags_to_add ?? []; + + const { apiToken, subdomain } = context.auth; + + const body: Record = { + name, + }; + + if (price) body['price'] = price; + if (statusId) body['status_id'] = statusId; + if (pipelineId) body['pipeline_id'] = pipelineId; + if (loss_reason_id) body['loss_reason_id'] = loss_reason_id; + if (responsible_user_id) body['responsible_user_id'] = responsible_user_id; + + if (tagsToAdd.length > 0) { + body['tags_to_add'] = tagsToAdd.map((tag) => ({ name: tag })); + } + + const response = await makeRequest({ apiToken, subdomain }, HttpMethod.POST, '/leads', [body]); + + return response; + }, +}); diff --git a/packages/pieces/community/kommo/src/lib/actions/find-company.ts b/packages/pieces/community/kommo/src/lib/actions/find-company.ts new file mode 100644 index 0000000..741f468 --- /dev/null +++ b/packages/pieces/community/kommo/src/lib/actions/find-company.ts @@ -0,0 +1,38 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common'; +import { kommoAuth } from '../../index'; + +export const findCompanyAction = createAction({ + auth: kommoAuth, + name: 'find_company', + displayName: 'Find Company', + description: 'Find an existing company.', + props: { + query: Property.ShortText({ + displayName: 'Query', + required: true, + description: 'Search query (Searches through the filled fields of the company).' + }), + }, + async run(context) { + const { query } = context.propsValue; + const { subdomain, apiToken } = context.auth as { + subdomain: string; + apiToken: string; + }; + + const result = await makeRequest( + { apiToken, subdomain }, + HttpMethod.GET, + `/companies?query=${encodeURIComponent(query || '')}` + ); + + const companies = result?._embedded?.companies ?? []; + + return { + found: companies.length > 0, + result: companies + }; + }, +}); diff --git a/packages/pieces/community/kommo/src/lib/actions/find-contact.ts b/packages/pieces/community/kommo/src/lib/actions/find-contact.ts new file mode 100644 index 0000000..67c50c8 --- /dev/null +++ b/packages/pieces/community/kommo/src/lib/actions/find-contact.ts @@ -0,0 +1,35 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { kommoAuth } from '../../index'; +import { makeRequest } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const findContactAction = createAction({ + auth: kommoAuth, + name: 'find_contact', + displayName: 'Find Contact', + description: 'Finds an existing contact.', + props: { + query: Property.ShortText({ + displayName: 'Query', + required: true, + description: 'Search query (Searches through the filled fields of the contact).' + }), + }, + async run(context) { + const { query } = context.propsValue; + const { subdomain, apiToken } = context.auth + + const result = await makeRequest( + { apiToken, subdomain }, + HttpMethod.GET, + `/contacts?query=${encodeURIComponent(query)}` + ); + + const contacts = result?._embedded?.contacts ?? []; + + return { + found: contacts.length > 0, + result: contacts + }; + }, +}); diff --git a/packages/pieces/community/kommo/src/lib/actions/find-lead.ts b/packages/pieces/community/kommo/src/lib/actions/find-lead.ts new file mode 100644 index 0000000..d94b539 --- /dev/null +++ b/packages/pieces/community/kommo/src/lib/actions/find-lead.ts @@ -0,0 +1,34 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common'; +import { kommoAuth } from '../../index'; + +export const findLeadAction = createAction({ + auth: kommoAuth, + name: 'find_lead', + displayName: 'Find Lead', + description: "Finds an existing lead.", + props: { + query: Property.ShortText({ + displayName: 'Query', + required: true, + description: 'Search query (Searches through the filled fields of the lead).' + }), + }, + async run(context) { + const { subdomain, apiToken } = context.auth + + const result = await makeRequest( + { apiToken, subdomain }, + HttpMethod.GET, + `/leads?query=${encodeURIComponent(context.propsValue.query)}` + ); + + const leads = result?._embedded?.leads ?? []; + + return { + found: leads.length > 0, + result: leads + }; + }, +}); diff --git a/packages/pieces/community/kommo/src/lib/actions/index.ts b/packages/pieces/community/kommo/src/lib/actions/index.ts new file mode 100644 index 0000000..699194d --- /dev/null +++ b/packages/pieces/community/kommo/src/lib/actions/index.ts @@ -0,0 +1,9 @@ +import { findLeadAction } from './find-lead'; +import { updateContactAction } from './update-contact'; +import { createLeadAction } from './create-new-lead'; +import { createContactAction } from './create-new-contact'; +import { findContactAction } from './find-contact'; +import { findCompanyAction } from './find-company'; +import { updateLeadAction } from './update-lead' + +export { findLeadAction, updateContactAction, createLeadAction, createContactAction, findContactAction, findCompanyAction, updateLeadAction }; \ No newline at end of file diff --git a/packages/pieces/community/kommo/src/lib/actions/update-contact.ts b/packages/pieces/community/kommo/src/lib/actions/update-contact.ts new file mode 100644 index 0000000..d6d19a6 --- /dev/null +++ b/packages/pieces/community/kommo/src/lib/actions/update-contact.ts @@ -0,0 +1,110 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { kommoAuth } from '../../index'; +import { makeRequest } from '../common'; +import { contactDropdown, userDropdown } from '../common/props'; + +interface KommoCustomFieldValue { + field_id?: number; + field_code?: string; + values: Array<{ value: string | number; enum_id?: number }>; +} + +export const updateContactAction = createAction({ + auth: kommoAuth, + name: 'update_contact', + displayName: 'Update Contact', + description: 'Updates an existing contact.', + props: { + contactId: contactDropdown, + name: Property.ShortText({ + displayName: 'Full Name', + required: false, + }), + first_name: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + last_name: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + required: false, + }), + phone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + responsible_user_id: userDropdown(), + tags_to_add: Property.Array({ + displayName: 'Tags to Add', + description: 'List of tag names or IDs to add.', + required: false, + }), + tags_to_delete: Property.Array({ + displayName: 'Tags to Delete', + description: 'List of tag names or IDs to remove.', + required: false, + }) + }, + async run(context) { + const { + contactId, + name, + first_name, + last_name, + email, + phone, + responsible_user_id, + } = context.propsValue; + + const tagsToAdd = context.propsValue.tags_to_add ?? []; + const tagsToDelete = context.propsValue.tags_to_delete ?? []; + + const { subdomain, apiToken } = context.auth; + + const customFields: KommoCustomFieldValue[] = []; + + if (email) { + customFields.push({ + field_code: 'EMAIL', + values: [{ value: email }], + }); + } + + if (phone) { + customFields.push({ + field_code: 'PHONE', + values: [{ value: phone }], + }); + } + + const updatePayload: Record = { + ...(customFields.length > 0 ? { custom_fields_values: customFields } : {}), + }; + + if (name) updatePayload['name'] = name; + if (first_name) updatePayload['first_name'] = first_name; + if (last_name) updatePayload['last_name'] = last_name; + if (responsible_user_id) updatePayload['responsible_user_id'] = responsible_user_id; + + if (tagsToAdd.length > 0) { + updatePayload['tags_to_add'] = tagsToAdd.map((tag) => ({ name: tag })) + } + + if (tagsToDelete.length > 0) { + updatePayload['tags_to_delete'] = tagsToDelete.map((tag) => ({ name: tag })) + } + + const result = await makeRequest( + { subdomain, apiToken }, + HttpMethod.PATCH, + `/contacts/${contactId}`, + updatePayload + ); + + return result; + }, +}); diff --git a/packages/pieces/community/kommo/src/lib/actions/update-lead.ts b/packages/pieces/community/kommo/src/lib/actions/update-lead.ts new file mode 100644 index 0000000..29436eb --- /dev/null +++ b/packages/pieces/community/kommo/src/lib/actions/update-lead.ts @@ -0,0 +1,78 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common'; +import { kommoAuth } from '../../index'; +import { pipelineDropdown, statusDropdown, userDropdown, lossReasonDropdown, leadDropdown } from '../common/props'; + +export const updateLeadAction = createAction({ + auth: kommoAuth, + name: 'update_lead', + displayName: 'Update Lead', + description: 'Update existing lead info.', + props: { + leadId: leadDropdown, + name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + price: Property.Number({ + displayName: 'Price', + required: false, + }), + pipelineId: pipelineDropdown(false), + statusId: statusDropdown(false), + responsible_user_id: userDropdown(false), + loss_reason_id: lossReasonDropdown(false), + tags_to_add: Property.Array({ + displayName: 'Tags to Add', + required: false, + }), + tags_to_delete: Property.Array({ + displayName: 'Tags to Delete', + required: false, + }), + }, + async run(context) { + const { + leadId, + name, + price, + statusId, + pipelineId, + responsible_user_id, + loss_reason_id, + } = context.propsValue; + + const tagsToAdd = context.propsValue.tags_to_add ?? []; + const tagsToDelete = context.propsValue.tags_to_delete ?? []; + + + const { subdomain, apiToken } = context.auth; + + const updatePayload: Record = {}; + + if (name) updatePayload['name'] = name; + if (price) updatePayload['price'] = price; + if (statusId) updatePayload['status_id'] = statusId; + if (pipelineId) updatePayload['pipeline_id'] = pipelineId; + if (loss_reason_id) updatePayload['loss_reason_id'] = loss_reason_id; + if (responsible_user_id) updatePayload['responsible_user_id'] = responsible_user_id; + + if (tagsToAdd.length > 0) { + updatePayload['tags_to_add'] = tagsToAdd.map((tag) => ({ name: tag })) + } + + if (tagsToDelete.length > 0) { + updatePayload['tags_to_delete'] = tagsToDelete.map((tag) => ({ name: tag })) + } + + const result = await makeRequest( + { apiToken, subdomain }, + HttpMethod.PATCH, + `/leads/${leadId}`, + updatePayload + ); + + return result; + }, +}); diff --git a/packages/pieces/community/kommo/src/lib/common/index.ts b/packages/pieces/community/kommo/src/lib/common/index.ts new file mode 100644 index 0000000..bc5fd72 --- /dev/null +++ b/packages/pieces/community/kommo/src/lib/common/index.ts @@ -0,0 +1,27 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +interface KommoAuth { + subdomain: string; + apiToken: string; +} + +export async function makeRequest( + auth: KommoAuth, + method: HttpMethod, + path: string, + body?: unknown +) { + const url = `https://${auth.subdomain}.kommo.com/api/v4${path}`; + + const response = await httpClient.sendRequest({ + method, + url, + headers: { + Authorization: `Bearer ${auth.apiToken}`, + 'Content-Type': 'application/json', + }, + body, + }); + + return response.body; +} diff --git a/packages/pieces/community/kommo/src/lib/common/props.ts b/packages/pieces/community/kommo/src/lib/common/props.ts new file mode 100644 index 0000000..265db77 --- /dev/null +++ b/packages/pieces/community/kommo/src/lib/common/props.ts @@ -0,0 +1,218 @@ +import { Property, DropdownOption } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from './index'; + +interface KommoAuth { + subdomain: string; + apiToken: string; +} + +export const pipelineDropdown = (required = false) => Property.Dropdown({ + displayName: 'Pipeline', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your Kommo account', + options: [], + }; + } + + const { subdomain, apiToken } = auth as KommoAuth; + const pipelines = await makeRequest({ subdomain, apiToken }, HttpMethod.GET, '/leads/pipelines'); + + const options: DropdownOption[] = (pipelines._embedded?.pipelines || []).map( + (pipeline: any) => ({ + label: pipeline.name, + value: pipeline.id + }) + ); + + return { + disabled: false, + options, + }; + }, +}); + +export const statusDropdown = (required = false) => Property.Dropdown({ + displayName: 'Status', + required, + refreshers: ['pipelineId'], + options: async ({ auth, pipelineId }) => { + if (!auth || !pipelineId) { + return { + disabled: true, + placeholder: 'Select a pipeline first', + options: [], + }; + } + + const { subdomain, apiToken } = auth as KommoAuth; + const statuses = await makeRequest( + { subdomain, apiToken }, + HttpMethod.GET, + `/leads/pipelines/${pipelineId}/statuses` + ); + + const options: DropdownOption[] = (statuses._embedded?.statuses || []).map( + (status: any) => ({ + label: status.name, + value: status.id, + }) + ); + + return { + disabled: false, + options, + }; + }, +}); + +export const userDropdown = (required = false) => Property.Dropdown({ + displayName: 'Unique identified of a responsible user', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your Kommo account', + options: [], + }; + } + + const { subdomain, apiToken } = auth as KommoAuth; + const users = await makeRequest({ subdomain, apiToken }, HttpMethod.GET, '/users'); + + const options: DropdownOption[] = (users._embedded?.users || []).map((user: any) => ({ + label: `${user.name} (${user.email})`, + value: user.id, + })); + + return { + disabled: false, + options, + }; + }, +}); + +export const lossReasonDropdown = (required = false) => Property.Dropdown({ + displayName: 'Loss Reason', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your Kommo account', + options: [], + }; + } + + const { subdomain, apiToken } = auth as KommoAuth; + const reasons = await makeRequest({ subdomain, apiToken }, HttpMethod.GET, '/leads/loss_reasons'); + + const options: DropdownOption[] = (reasons._embedded?.loss_reasons || []).map( + (reason: any) => ({ + label: reason.name, + value: reason.id, + }) + ); + + return { + disabled: false, + options, + }; + }, +}); + +export const leadDropdown = Property.Dropdown({ + displayName: 'Lead', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your Kommo account', + options: [], + }; + } + + const { subdomain, apiToken } = auth as KommoAuth; + const leads = await makeRequest({ subdomain, apiToken }, HttpMethod.GET, '/leads'); + + const options: DropdownOption[] = (leads._embedded?.leads || []).map((lead: any) => ({ + label: lead.name, + value: lead.id, + })); + + return { + disabled: false, + options, + }; + }, +}); + +export const companyDropdown = Property.Dropdown({ + displayName: 'Company', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your Kommo account', + options: [], + }; + } + + const { subdomain, apiToken } = auth as KommoAuth; + const companies = await makeRequest({ subdomain, apiToken }, HttpMethod.GET, '/companies'); + + const options: DropdownOption[] = (companies._embedded?.companies || []).map( + (company: any) => ({ + label: company.name, + value: company.id.toString(), + }) + ); + + return { + disabled: false, + options, + }; + }, +}); + +export const contactDropdown = Property.Dropdown({ + displayName: 'Contact', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your Kommo account', + options: [], + }; + } + + const { subdomain, apiToken } = auth as KommoAuth; + const contacts = await makeRequest({ subdomain, apiToken }, HttpMethod.GET, '/contacts'); + + const options: DropdownOption[] = (contacts._embedded?.contacts || []).map( + (contact: any) => ({ + label: contact.name, + value: contact.id, + }) + ); + + return { + disabled: false, + options, + }; + }, +}); diff --git a/packages/pieces/community/kommo/src/lib/triggers/index.ts b/packages/pieces/community/kommo/src/lib/triggers/index.ts new file mode 100644 index 0000000..3577171 --- /dev/null +++ b/packages/pieces/community/kommo/src/lib/triggers/index.ts @@ -0,0 +1,6 @@ +import { leadStatusChangedTrigger } from './lead-status-changed'; +import { newContactAddedTrigger } from './new-contact-added'; +import { newLeadCreatedTrigger } from './new-lead-created'; +import { newTaskCreatedTrigger } from './new-task-created'; + +export { leadStatusChangedTrigger, newContactAddedTrigger, newLeadCreatedTrigger, newTaskCreatedTrigger }; \ No newline at end of file diff --git a/packages/pieces/community/kommo/src/lib/triggers/lead-status-changed.ts b/packages/pieces/community/kommo/src/lib/triggers/lead-status-changed.ts new file mode 100644 index 0000000..606b71f --- /dev/null +++ b/packages/pieces/community/kommo/src/lib/triggers/lead-status-changed.ts @@ -0,0 +1,102 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { kommoAuth } from '../../index'; +import { makeRequest } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const leadStatusChangedTrigger = createTrigger({ + auth: kommoAuth, + name: 'lead_status_changed', + displayName: 'Lead Status Changed', + description: 'Triggers when a lead status is changed.', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const { subdomain, apiToken } = context.auth as { subdomain: string; apiToken: string }; + + const webhook = await makeRequest( + { subdomain, apiToken }, + HttpMethod.POST, + `/webhooks`, + { + destination: context.webhookUrl, + settings: ['status_lead'] + } + ); + + await context.store.put('webhookId', webhook.id); + }, + + async onDisable(context) { + const { subdomain, apiToken } = context.auth as { subdomain: string; apiToken: string }; + const webhookId = await context.store.get('webhookId'); + + if (webhookId) { + await makeRequest( + { subdomain, apiToken }, + HttpMethod.DELETE, + `/webhooks`, + {destination:context.webhookUrl} + ); + } + }, + + async run(context) { + const { subdomain, apiToken } = context.auth as { subdomain: string; apiToken: string }; + + const payload = context.payload.body as { leads: { status: { id: string }[] } } + const leadId = payload.leads.status[0].id; + if (!leadId) return []; + + const response = await makeRequest({ apiToken, subdomain }, HttpMethod.GET, `/leads/${leadId}`) + return [response] + }, + async test(context) { + const { subdomain, apiToken } = context.auth; + + const response = await makeRequest({ subdomain, apiToken }, HttpMethod.GET, '/leads?limit=5&order[updated_at]=desc'); + + const leads = response?._embedded?.leads ?? []; + + return leads; + }, + sampleData: { + "id": 256988, + "name": "John Doe", + "price": 100, + "responsible_user_id": 13290567, + "group_id": 0, + "status_id": 86521115, + "pipeline_id": 11273979, + "loss_reason_id": null, + "created_by": 13290567, + "updated_by": 13290567, + "created_at": 1748800059, + "updated_at": 1748800060, + "closed_at": null, + "closest_task_at": null, + "is_deleted": false, + "custom_fields_values": null, + "score": null, + "account_id": 34678947, + "labor_cost": null, + "is_price_computed": false, + "_links": { + "self": { + "href": "" + } + }, + "_embedded": { + "tags": [], + "companies": [ + { + "id": 722828, + "_links": { + "self": { + "href": "" + } + } + } + ] + } + } +}); diff --git a/packages/pieces/community/kommo/src/lib/triggers/new-contact-added.ts b/packages/pieces/community/kommo/src/lib/triggers/new-contact-added.ts new file mode 100644 index 0000000..1affab7 --- /dev/null +++ b/packages/pieces/community/kommo/src/lib/triggers/new-contact-added.ts @@ -0,0 +1,98 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { kommoAuth } from '../../index'; +import { makeRequest } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const newContactAddedTrigger = createTrigger({ + auth: kommoAuth, + name: 'new_contact_added', + displayName: 'New Contact Added', + description: 'Triggers when a new contact is added.', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const { subdomain, apiToken } = context.auth as { subdomain: string; apiToken: string }; + + const webhook = await makeRequest( + { subdomain, apiToken }, + HttpMethod.POST, + `/webhooks`, + { + destination: context.webhookUrl, + settings: ['add_contact'] + } + ); + + await context.store.put('webhookId', webhook.id); + }, + + async onDisable(context) { + const { subdomain, apiToken } = context.auth; + const webhookId = await context.store.get('webhookId'); + + if (webhookId) { + await makeRequest( + { subdomain, apiToken }, + HttpMethod.DELETE, + `/webhooks`, + {destination:context.webhookUrl} + ); + } + }, + + async run(context) { + const { subdomain, apiToken } = context.auth as { subdomain: string; apiToken: string }; + + const payload = context.payload.body as { contacts: { add: { id: string }[] } } + const contactId = payload.contacts.add[0].id; + if (!contactId) return []; + + + const response = await makeRequest({ apiToken, subdomain }, HttpMethod.GET, `/contacts/${contactId}`) + return [response] + }, + async test(context) { + const { subdomain, apiToken } = context.auth; + + const response = await makeRequest({ subdomain, apiToken }, HttpMethod.GET, '/contacts?limit=5&order[updated_at]=desc'); + + const contacts = response?._embedded?.contacts ?? []; + + return contacts; + }, + sampleData: { + "id": 722830, + "name": "John Doe", + "first_name": "", + "last_name": "", + "responsible_user_id": 13290567, + "group_id": 0, + "created_by": 13290567, + "updated_by": 13290567, + "created_at": 1748800060, + "updated_at": 1748800060, + "closest_task_at": null, + "is_deleted": false, + "is_unsorted": false, + "custom_fields_values": null, + "account_id": 34678947, + "_links": { + "self": { + "href": "" + } + }, + "_embedded": { + "tags": [], + "companies": [ + { + "id": 722828, + "_links": { + "self": { + "href": "" + } + } + } + ] + } + } +}); diff --git a/packages/pieces/community/kommo/src/lib/triggers/new-lead-created.ts b/packages/pieces/community/kommo/src/lib/triggers/new-lead-created.ts new file mode 100644 index 0000000..3caf88f --- /dev/null +++ b/packages/pieces/community/kommo/src/lib/triggers/new-lead-created.ts @@ -0,0 +1,103 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { kommoAuth } from '../../index'; +import { makeRequest } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const newLeadCreatedTrigger = createTrigger({ + auth: kommoAuth, + name: 'new_lead_created', + displayName: 'New Lead Created', + description: 'Triggers when a new lead is created.', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const { subdomain, apiToken } = context.auth; + + const webhook = await makeRequest( + { subdomain, apiToken }, + HttpMethod.POST, + `/webhooks`, + { + destination: context.webhookUrl, + settings: ['add_lead'] + } + ); + + await context.store.put('webhookId', webhook.id); + }, + + async onDisable(context) { + const { subdomain, apiToken } = context.auth; + const webhookId = await context.store.get('webhookId'); + + if (webhookId) { + await makeRequest( + { subdomain, apiToken }, + HttpMethod.DELETE, + `/webhooks`, + {destination:context.webhookUrl} + ); + } + }, + + async run(context) { + const { subdomain, apiToken } = context.auth as { subdomain: string; apiToken: string }; + + const payload = context.payload.body as { leads: { add: { id: string }[] } } + const leadId = payload.leads.add[0].id; + if (!leadId) return []; + + + const response = await makeRequest({ apiToken, subdomain }, HttpMethod.GET, `/leads/${leadId}`) + return [response] + }, + async test(context) { + const { subdomain, apiToken } = context.auth; + + const response = await makeRequest({ subdomain, apiToken }, HttpMethod.GET, '/leads?limit=5&order[updated_at]=desc'); + + const leads = response?._embedded?.leads ?? []; + + return leads; + }, + sampleData: { + "id": 256988, + "name": "John Doe", + "price": 100, + "responsible_user_id": 13290567, + "group_id": 0, + "status_id": 86521115, + "pipeline_id": 11273979, + "loss_reason_id": null, + "created_by": 13290567, + "updated_by": 13290567, + "created_at": 1748800059, + "updated_at": 1748800060, + "closed_at": null, + "closest_task_at": null, + "is_deleted": false, + "custom_fields_values": null, + "score": null, + "account_id": 34678947, + "labor_cost": null, + "is_price_computed": false, + "_links": { + "self": { + "href": "" + } + }, + "_embedded": { + "tags": [], + "companies": [ + { + "id": 722828, + "_links": { + "self": { + "href": "" + } + } + } + ] + } + } +}); diff --git a/packages/pieces/community/kommo/src/lib/triggers/new-task-created.ts b/packages/pieces/community/kommo/src/lib/triggers/new-task-created.ts new file mode 100644 index 0000000..9e5e8b9 --- /dev/null +++ b/packages/pieces/community/kommo/src/lib/triggers/new-task-created.ts @@ -0,0 +1,77 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { kommoAuth } from '../../index'; +import { makeRequest } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const newTaskCreatedTrigger = createTrigger({ + auth: kommoAuth, + name: 'new_task_created', + displayName: 'New Task Created', + description: 'Triggered when a new task is created.', + type: TriggerStrategy.WEBHOOK, + props: {}, + async onEnable(context) { + const { subdomain, apiToken } = context.auth as { subdomain: string; apiToken: string }; + + const webhook = await makeRequest( + { subdomain, apiToken }, + HttpMethod.POST, + `/webhooks`, + { + destination: context.webhookUrl, + settings: ['add_task'] + } + ); + + await context.store.put('webhookId', webhook.id); + }, + + async onDisable(context) { + const { subdomain, apiToken } = context.auth as { subdomain: string; apiToken: string }; + const webhookId = await context.store.get('webhookId'); + + if (webhookId) { + await makeRequest( + { subdomain, apiToken }, + HttpMethod.DELETE, + `/webhooks`, + {destination:context.webhookUrl} + ); + } + }, + + async run(context) { + const { subdomain, apiToken } = context.auth as { subdomain: string; apiToken: string }; + + const payload = context.payload.body as { task: { add: { id: number }[] } } + const taskId = payload.task.add[0].id; + if (!taskId) return []; + + + const response = await makeRequest({ apiToken, subdomain }, HttpMethod.GET, `/tasks/${taskId}`) + return [response] + }, + sampleData: { + "id": 12040, + "created_by": 13290567, + "updated_by": 13290567, + "created_at": 1748805952, + "updated_at": 1748805952, + "responsible_user_id": 13290567, + "group_id": 0, + "entity_id": 722830, + "entity_type": "contacts", + "duration": 0, + "is_completed": false, + "task_type_id": 1, + "text": "Test", + "result": [], + "complete_till": 1748975340, + "account_id": 34678947, + "_links": { + "self": { + "href": "" + } + } + } +}); diff --git a/packages/pieces/community/kommo/tsconfig.json b/packages/pieces/community/kommo/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/kommo/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/kommo/tsconfig.lib.json b/packages/pieces/community/kommo/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/kommo/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/krisp-call/.eslintrc.json b/packages/pieces/community/krisp-call/.eslintrc.json new file mode 100644 index 0000000..eac708a --- /dev/null +++ b/packages/pieces/community/krisp-call/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/krisp-call/README.md b/packages/pieces/community/krisp-call/README.md new file mode 100644 index 0000000..219ce22 --- /dev/null +++ b/packages/pieces/community/krisp-call/README.md @@ -0,0 +1,7 @@ +# pieces-krisp-call + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-krisp-call` to build the library. diff --git a/packages/pieces/community/krisp-call/package.json b/packages/pieces/community/krisp-call/package.json new file mode 100644 index 0000000..8d78a67 --- /dev/null +++ b/packages/pieces/community/krisp-call/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-krisp-call", + "version": "0.0.4" +} \ No newline at end of file diff --git a/packages/pieces/community/krisp-call/project.json b/packages/pieces/community/krisp-call/project.json new file mode 100644 index 0000000..3c2ef82 --- /dev/null +++ b/packages/pieces/community/krisp-call/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-krisp-call", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/krisp-call/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/krisp-call", + "tsConfig": "packages/pieces/community/krisp-call/tsconfig.lib.json", + "packageJson": "packages/pieces/community/krisp-call/package.json", + "main": "packages/pieces/community/krisp-call/src/index.ts", + "assets": [ + "packages/pieces/community/krisp-call/*.md", + { + "input": "packages/pieces/community/krisp-call/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-KrispCall {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/krisp-call/src/index.ts b/packages/pieces/community/krisp-call/src/index.ts new file mode 100644 index 0000000..3edddee --- /dev/null +++ b/packages/pieces/community/krisp-call/src/index.ts @@ -0,0 +1,49 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { addContact } from './lib/actions/add-contact'; +import { deleteContacts } from './lib/actions/delete-contacts'; +import { sendSms } from './lib/actions/send-sms'; +import { sendMms } from './lib/actions/send-mms'; +import { triggers } from './lib/triggers'; +import { PieceCategory } from '@activepieces/shared'; + +export const krispcallAuth = PieceAuth.CustomAuth({ + props: { + apiKey: PieceAuth.SecretText({ + displayName: 'API key', + required: true, + }), + }, + validate: async ({ auth }) => { + try { + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://automationapi.krispcall.com/api/v1/platform/activepiece/me', + headers: { + 'X-API-KEY': auth.apiKey, + }, + }); + return { valid: true }; + } catch (error: any) { + return { valid: false, error: error.message }; + } + }, + required: true, +}); + +export type krispcallAuth = { + apiKey: string; +}; + +export const KrispCall = createPiece({ + displayName: 'KrispCall', + description: + 'KrispCall is a cloud telephony system for modern businesses, offering advanced features for high-growth startups and modern enterprises.', + categories: [PieceCategory.COMMUNICATION], + auth: krispcallAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/krispcall.svg', + authors: ['deependra321'], + actions: [addContact, deleteContacts, sendSms, sendMms], + triggers: triggers, +}); diff --git a/packages/pieces/community/krisp-call/src/lib/actions/add-contact.ts b/packages/pieces/community/krisp-call/src/lib/actions/add-contact.ts new file mode 100644 index 0000000..960d816 --- /dev/null +++ b/packages/pieces/community/krisp-call/src/lib/actions/add-contact.ts @@ -0,0 +1,54 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { krispcallAuth } from '../..'; + +export const addContact = createAction({ + name: 'addContact', + displayName: 'Add Contact', + auth: krispcallAuth, + description: 'Add contact in Krispcall', + props: { + name: Property.ShortText({ + displayName: 'Name', + description: 'Enter your name', + required: false, + }), + number: Property.Number({ + displayName: 'Contact number', + description: 'Enter contact number', + required: true, + }), + address: Property.ShortText({ + displayName: 'Address', + description: 'Enter your address', + required: false, + }), + company: Property.ShortText({ + displayName: 'Company', + description: 'Enter your company', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + description: 'Enter your email', + required: false, + }), + }, + async run({ auth, propsValue }) { + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://automationapi.krispcall.com/api/v1/platform/activepiece/add-contact', + headers: { + 'X-API-KEY': auth.apiKey, + }, + body: { + name: propsValue.name, + number: propsValue.number, + company: propsValue.company, + email: propsValue.email, + address: propsValue.address, + }, + }); + return res.body; + }, +}); diff --git a/packages/pieces/community/krisp-call/src/lib/actions/delete-contacts.ts b/packages/pieces/community/krisp-call/src/lib/actions/delete-contacts.ts new file mode 100644 index 0000000..8a708bd --- /dev/null +++ b/packages/pieces/community/krisp-call/src/lib/actions/delete-contacts.ts @@ -0,0 +1,30 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { krispcallAuth } from '../..'; + +export const deleteContacts = createAction({ + name: 'deleteContacts', + displayName: 'Delete Contacts', + auth: krispcallAuth, + description: 'Delete contacts from krispcall.', + props: { + contacts: Property.Array({ + displayName: 'Contacts', + description: 'Enter contact which you want to delete.', + required: false, + }), + }, + async run({ auth, propsValue }) { + const res = await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: 'https://automationapi.krispcall.com/api/v1/platform/activepiece/delete-contacts', + headers: { + 'X-API-KEY': auth.apiKey, + }, + body: { + contacts: propsValue.contacts, + }, + }); + return res.body; + }, +}); diff --git a/packages/pieces/community/krisp-call/src/lib/actions/send-mms.ts b/packages/pieces/community/krisp-call/src/lib/actions/send-mms.ts new file mode 100644 index 0000000..fad0e6d --- /dev/null +++ b/packages/pieces/community/krisp-call/src/lib/actions/send-mms.ts @@ -0,0 +1,86 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { krispcallAuth } from '../..'; +import { Property, PiecePropValueSchema } from '@activepieces/pieces-framework'; + +interface Item { + name: string; + id: string; + number: string; +} + +export const sendMms = createAction({ + name: 'sendMms', + displayName: 'Send MMS', + auth: krispcallAuth, + description: 'Send mms in krispcall.', + props: { + from_number: Property.Dropdown({ + displayName: 'from number', + description: 'Select an number', + required: true, + refreshers: ['auth'], + refreshOnSearch: false, + options: async ({ auth }) => { + try { + const authVaue = auth as PiecePropValueSchema; + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://automationapi.krispcall.com/api/v1/platform/activepiece/get-numbers', + headers: { + 'X-API-KEY': authVaue.apiKey, + }, + }); + const mappedOptions = res?.body?.map((item) => { + return { + label: item.name, + value: item.number, + }; + }); + + return { + disabled: false, + options: mappedOptions, + }; + } catch (error) { + // Handle error + console.error(error); + return { disabled: true, options: [] }; // Return empty options array or handle error accordingly + } + }, + }), + to_number: Property.Number({ + displayName: 'To Number', + description: 'Enter the number to which you want to send sms.', + required: true, + }), + content: Property.ShortText({ + displayName: 'content', + description: 'Enter your message here.', + required: false, + }), + medias: Property.Array({ + displayName: 'Medias url', + description: 'Enter medias urls', + required: true, + }), + }, + async run({ auth, propsValue }) { + console.log(auth.apiKey); + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://automationapi.krispcall.com/api/v1/platform/activepiece/send-mms', + headers: { + 'X-API-KEY': auth.apiKey, + }, + + body: { + from_number: propsValue.from_number, + to_number: propsValue.to_number, + content: propsValue.content, + medias: propsValue.medias, + }, + }); + return res.body; + }, +}); diff --git a/packages/pieces/community/krisp-call/src/lib/actions/send-sms.ts b/packages/pieces/community/krisp-call/src/lib/actions/send-sms.ts new file mode 100644 index 0000000..5a3f397 --- /dev/null +++ b/packages/pieces/community/krisp-call/src/lib/actions/send-sms.ts @@ -0,0 +1,80 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { krispcallAuth } from '../..'; +import { Property, PiecePropValueSchema } from '@activepieces/pieces-framework'; + +interface Item { + name: string; + id: string; + number: string; +} + +export const sendSms = createAction({ + name: 'sendSms', + displayName: 'Send SMS', + auth: krispcallAuth, + description: 'Send sms in Krispcall.', + props: { + from_number: Property.Dropdown({ + displayName: 'From Number', + description: 'Select an Number', + required: true, + refreshers: ['auth'], + refreshOnSearch: false, + options: async ({ auth }) => { + try { + const authVaue = auth as PiecePropValueSchema; + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://automationapi.krispcall.com/api/v1/platform/activepiece/get-numbers', + headers: { + 'X-API-KEY': authVaue.apiKey, + }, + }); + const mappedOptions = res?.body?.map((item) => { + return { + label: item.name, + value: item.number, + }; + }); + + return { + disabled: false, + options: mappedOptions, + }; + } catch (error) { + // Handle error + console.error(error); + return { disabled: true, options: [] }; // Return empty options array or handle error accordingly + } + }, + }), + to_number: Property.Number({ + displayName: 'To Number', + description: 'Enter the number to which you want to send sms.', + required: true, + }), + content: Property.ShortText({ + displayName: 'content', + description: 'Enter your message here.', + required: true, + }), + }, + async run({ auth, propsValue }) { + console.log(auth.apiKey); + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://automationapi.krispcall.com/api/v1/platform/activepiece/send-sms', + headers: { + 'X-API-KEY': auth.apiKey, + }, + + body: { + from_number: propsValue.from_number, + to_number: propsValue.to_number, + content: propsValue.content, + }, + }); + return res.body; + }, +}); diff --git a/packages/pieces/community/krisp-call/src/lib/triggers/index.ts b/packages/pieces/community/krisp-call/src/lib/triggers/index.ts new file mode 100644 index 0000000..5377dd2 --- /dev/null +++ b/packages/pieces/community/krisp-call/src/lib/triggers/index.ts @@ -0,0 +1,114 @@ +import { krispcallAuth } from '../../index'; +import { HttpMethod, httpClient } from "@activepieces/pieces-common"; +import { TriggerStrategy, createTrigger } from "@activepieces/pieces-framework" + +export const triggers = [ + { + name: 'newVoicemail', + displayName: 'New Voicemail', + description: 'Trigger when a new voicemail is received.', + action: 'new_voicemail', + sampleData: { + id: '', + from: '+9779821110987', + duration: '5 seconds', + call_time: '2000-10-31T01:30:00.000-05:00', + voicemail_audio: 'voicemail.mp4', + }, + }, + { + name: 'newMms', + displayName: 'New MMS/SMS', + description: 'Trigger when a new MMS/SMS is received.', + action: 'new_sms_or_mms', + sampleData: { + "id": "YiW2nyxqtJPYqkRKbrcJQ7", + "from_number": "+16466813538", + "to_number": "+12517327005", + "content": "Last testing", + "media_link": "https://api.twilio.com/2010-04-01/Accounts/LINK/Media/SOMETHING" + }, + }, + { + name: 'newContact', + displayName: 'New Contact', + description: 'Trigger when a new contact is added.', + action: 'new_contact', + sampleData: { + id: '1', + email: 'john@example.com', + company: 'KrispCall', + address: 'Singapore', + name: 'John Smith', + contactNumber: '+9779834509123', + }, + }, + { + name: 'newCallLog', + displayName: 'New Call Log', + description: 'Trigger when a new call log is recorded.', + action: 'new_call_log', + sampleData: { + id: '101', + callFrom: "+11234567890", + callTo: "+11234567891", + direction: "Outgoing", + duration : "0hr 01min 30sec", + outcome:"Completed", + callRecording: "http://example.com/recording.mp3", + }, + }, + { + name: 'OutboundSMS/MMS', + displayName: 'Outbound MMS/SMS', + description: 'Trigger when a new MMS/SMS is sent.', + action: 'outbound_sms_or_mms', + sampleData: { + "id": "YiW2nyxqtJPYqkRKbrcJQ7", + "from_number": "+16466813538", + "to_number": "+12517327005", + "content": "Last testing", + "media_link": "https://api.twilio.com/2010-04-01/Accounts/LINK/Media/SOMETHING" + }, + }, +].map(trigger => { + return createTrigger({ + name: trigger.name, + displayName: trigger.displayName, + auth: krispcallAuth, + description: trigger.description, + props: {}, + sampleData: trigger.sampleData, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://automationapi.krispcall.com/api/v1/platform/activepiece/subscribe', + body: { + hookUrl: context.webhookUrl, + action: trigger.action, + }, + headers: { + 'X-API-KEY': context.auth.apiKey, + }, + }); + await context.store.put('_webhook_id', response.body.id); + }, + async onDisable(context) { + const webhookId = await context.store.get('_webhook_id'); + await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: 'https://automationapi.krispcall.com/api/v1/platform/activepiece/unsubscribe', + body: { + hookUrl: webhookId, + }, + headers: { + 'X-API-KEY': context.auth.apiKey, + }, + }); + }, + async run(context) { + return [context.payload.body]; + }, + }) +}) diff --git a/packages/pieces/community/krisp-call/tsconfig.json b/packages/pieces/community/krisp-call/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/krisp-call/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/krisp-call/tsconfig.lib.json b/packages/pieces/community/krisp-call/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/krisp-call/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/lead-connector/.eslintrc.json b/packages/pieces/community/lead-connector/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/lead-connector/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/lead-connector/README.md b/packages/pieces/community/lead-connector/README.md new file mode 100644 index 0000000..462aac3 --- /dev/null +++ b/packages/pieces/community/lead-connector/README.md @@ -0,0 +1,7 @@ +# pieces-lead-connector + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-lead-connector` to build the library. diff --git a/packages/pieces/community/lead-connector/package.json b/packages/pieces/community/lead-connector/package.json new file mode 100644 index 0000000..fe3324d --- /dev/null +++ b/packages/pieces/community/lead-connector/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-lead-connector", + "version": "0.1.2" +} diff --git a/packages/pieces/community/lead-connector/project.json b/packages/pieces/community/lead-connector/project.json new file mode 100644 index 0000000..0e1bf13 --- /dev/null +++ b/packages/pieces/community/lead-connector/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-lead-connector", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/lead-connector/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/lead-connector", + "tsConfig": "packages/pieces/community/lead-connector/tsconfig.lib.json", + "packageJson": "packages/pieces/community/lead-connector/package.json", + "main": "packages/pieces/community/lead-connector/src/index.ts", + "assets": [ + "packages/pieces/community/lead-connector/*.md", + { + "input": "packages/pieces/community/lead-connector/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-lead-connector {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/lead-connector/src/index.ts b/packages/pieces/community/lead-connector/src/index.ts new file mode 100644 index 0000000..c64f136 --- /dev/null +++ b/packages/pieces/community/lead-connector/src/index.ts @@ -0,0 +1,110 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + createPiece, + OAuth2PropertyValue, + PieceAuth, +} from '@activepieces/pieces-framework'; +import { baseUrl, getContacts, leadConnectorHeaders } from './lib/common'; + +import { PieceCategory } from '@activepieces/shared'; +import { addContactToCampaignAction } from './lib/actions/add-contact-to-campaign'; +import { addContactToWorkflowAction } from './lib/actions/add-contact-to-workflow'; +import { addNoteToContactAction } from './lib/actions/add-note-to-contact'; +import { createContact } from './lib/actions/create-contact'; +import { createOpportunityAction } from './lib/actions/create-opportunity'; +import { createTaskAction } from './lib/actions/create-task'; +import { searchContactsAction } from './lib/actions/search-contacts'; +import { updateContactAction } from './lib/actions/update-contact'; +import { updateOpportunityAction } from './lib/actions/update-opportunity'; +import { updateTaskAction } from './lib/actions/update-task'; +import { contactUpdated } from './lib/triggers/contact-updated'; +import { newContact } from './lib/triggers/new-contact'; +import { newFormSubmission } from './lib/triggers/new-form-submission'; +import { newOpportunity } from './lib/triggers/new-opportunity'; + +const markdownDescription = ` +1. Go to the [Marketplace](https://marketplace.gohighlevel.com/) and sign up for a developer account. +2. Navigate to **My Apps** and click on **Create App**. +3. Provide app name.Then select **Private** as App Type, **Sub-Account** as Distribution Type. Click **Create App** Button. +4. Add following scopes. + - campaigns.readonly + - contacts.write + - contacts.readonly + - locations.readonly + - locations/tags.readonly + - locations/tags.write + - opportunities.readonly + - opportunities.write + - users.readonly + - workflows.readonly + - forms.readonly +5. Add redirect URLs. +6. Create new Client key with valid name.Copy Client ID and Client Secret. +`; + +export const leadConnectorAuth = PieceAuth.OAuth2({ + authUrl: 'https://marketplace.gohighlevel.com/oauth/chooselocation', + tokenUrl: 'https://services.leadconnectorhq.com/oauth/token', + scope: [ + 'campaigns.readonly', + 'contacts.write', + 'contacts.readonly', + 'locations.readonly', + 'locations/tags.readonly', + 'locations/tags.write', + 'opportunities.readonly', + 'opportunities.write', + 'users.readonly', + 'workflows.readonly', + 'forms.readonly', + ], + description: markdownDescription, + required: true, + async validate({ auth }) { + try { + await getContacts(auth); + + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API key.', + }; + } + }, +}); + +export const leadConnector = createPiece({ + displayName: 'LeadConnector', + description: 'Lead Connector - Go High Level', + auth: leadConnectorAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/lead-connector.png', + categories: [PieceCategory.SALES_AND_CRM], + authors: ['kishanprmr', 'MoShizzle', 'abuaboud'], + actions: [ + createContact, + updateContactAction, + addContactToCampaignAction, + addContactToWorkflowAction, + addNoteToContactAction, + searchContactsAction, + createOpportunityAction, + updateOpportunityAction, + createTaskAction, + updateTaskAction, + createCustomApiCallAction({ + baseUrl: () => baseUrl, + auth: leadConnectorAuth, + authMapping: async (auth) => { + return { + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + ...leadConnectorHeaders, + }; + }, + }), + ], + triggers: [newContact, contactUpdated, newFormSubmission, newOpportunity], +}); diff --git a/packages/pieces/community/lead-connector/src/lib/actions/add-contact-to-campaign.ts b/packages/pieces/community/lead-connector/src/lib/actions/add-contact-to-campaign.ts new file mode 100644 index 0000000..3c07b7f --- /dev/null +++ b/packages/pieces/community/lead-connector/src/lib/actions/add-contact-to-campaign.ts @@ -0,0 +1,64 @@ +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { addContactToCampaign, getCampaigns, getContacts } from '../common'; +import { leadConnectorAuth } from '../..'; + +export const addContactToCampaignAction = createAction({ + auth: leadConnectorAuth, + name: 'add_contact_to_campaign', + displayName: 'Add Contact to Campaign', + description: 'Add an existing contact to a campaign.', + props: { + contact: Property.Dropdown({ + displayName: 'Contact', + description: 'The contact to use.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + }; + + const contacts = await getContacts(auth as OAuth2PropertyValue); + return { + options: contacts.map((contact) => { + return { + label: contact.contactName, + value: contact.id, + }; + }), + }; + }, + }), + campaign: Property.Dropdown({ + displayName: 'Campaign', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + }; + } + + const campaigns = await getCampaigns(auth as OAuth2PropertyValue); + return { + options: campaigns.map((campaign: any) => { + return { + label: campaign.name, + value: campaign.id, + }; + }), + }; + }, + }), + }, + + async run({ auth, propsValue }) { + const { contact, campaign } = propsValue; + + return await addContactToCampaign(auth.access_token, contact, campaign); + }, +}); diff --git a/packages/pieces/community/lead-connector/src/lib/actions/add-contact-to-workflow.ts b/packages/pieces/community/lead-connector/src/lib/actions/add-contact-to-workflow.ts new file mode 100644 index 0000000..ec95563 --- /dev/null +++ b/packages/pieces/community/lead-connector/src/lib/actions/add-contact-to-workflow.ts @@ -0,0 +1,64 @@ +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { addContactToWorkflow, getContacts, getWorkflows } from '../common'; +import { leadConnectorAuth } from '../..'; + +export const addContactToWorkflowAction = createAction({ + auth: leadConnectorAuth, + name: 'add_contact_to_workflow', + displayName: 'Add Contact to Workflow', + description: 'Add an existing contact to a workflow.', + props: { + contact: Property.Dropdown({ + displayName: 'Contact', + description: 'The contact to use.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + }; + + const contacts = await getContacts(auth as OAuth2PropertyValue); + return { + options: contacts.map((contact) => { + return { + label: contact.contactName, + value: contact.id, + }; + }), + }; + }, + }), + workflow: Property.Dropdown({ + displayName: 'Workflow', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + }; + } + + const campaigns = await getWorkflows(auth as OAuth2PropertyValue); + return { + options: campaigns.map((campaign: any) => { + return { + label: campaign.name, + value: campaign.id, + }; + }), + }; + }, + }), + }, + + async run({ auth, propsValue }) { + const { contact, workflow } = propsValue; + + return await addContactToWorkflow(auth.access_token, contact, workflow); + }, +}); diff --git a/packages/pieces/community/lead-connector/src/lib/actions/add-note-to-contact.ts b/packages/pieces/community/lead-connector/src/lib/actions/add-note-to-contact.ts new file mode 100644 index 0000000..37dd7ba --- /dev/null +++ b/packages/pieces/community/lead-connector/src/lib/actions/add-note-to-contact.ts @@ -0,0 +1,74 @@ +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { + addNoteToContact, + getContacts, + getUsers, +} from '../common'; +import { leadConnectorAuth } from '../..'; + +export const addNoteToContactAction = createAction({ + auth: leadConnectorAuth, + name: 'add_note_to_contact', + displayName: 'Add Note to Contact', + description: 'Add a new note to a contact.', + props: { + contact: Property.Dropdown({ + displayName: 'Contact', + description: 'The contact to use.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + }; + + const contacts = await getContacts(auth as OAuth2PropertyValue); + return { + options: contacts.map((contact) => { + return { + label: contact.contactName, + value: contact.id, + }; + }), + }; + }, + }), + note: Property.ShortText({ + displayName: 'Note', + required: true, + }), + user: Property.Dropdown({ + displayName: 'User', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + }; + + const users = await getUsers(auth as OAuth2PropertyValue); + return { + options: users.map((user: any) => { + return { + label: `${user.firstName} ${user.lastName}`, + value: user.id, + }; + }), + }; + }, + }), + }, + + async run({ auth, propsValue }) { + const { contact, note, user } = propsValue; + + return await addNoteToContact(auth.access_token, contact, { + body: note, + userId: user, + }); + }, +}); diff --git a/packages/pieces/community/lead-connector/src/lib/actions/create-contact.ts b/packages/pieces/community/lead-connector/src/lib/actions/create-contact.ts new file mode 100644 index 0000000..079e864 --- /dev/null +++ b/packages/pieces/community/lead-connector/src/lib/actions/create-contact.ts @@ -0,0 +1,175 @@ +import { + createAction, + OAuth2PropertyValue, + Property, +} from '@activepieces/pieces-framework'; +import { + addContact, + Country, + getCountries, + getTags, + getTimezones, + LeadConnectorContactDto, +} from '../common'; +import { leadConnectorAuth } from '../..'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const createContact = createAction({ + auth: leadConnectorAuth, + name: 'create_contact', + displayName: 'Create Contact', + description: 'Create a new contact.', + props: { + firstName: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + required: false, + }), + phone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + companyName: Property.ShortText({ + displayName: 'Company Name', + required: false, + }), + website: Property.ShortText({ + displayName: 'Website', + required: false, + }), + tags: Property.MultiSelectDropdown({ + displayName: 'Tags', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + }; + + const tags = await getTags(auth as OAuth2PropertyValue); + return { + options: tags.map((tag) => { + return { + label: tag.name, + value: tag.name, + }; + }), + }; + }, + }), + source: Property.ShortText({ + displayName: 'Source', + required: false, + }), + country: Property.Dropdown({ + displayName: 'Country', + description: + 'When using a dynamic value, make sure to use the ISO-2 country code, and not the country name.', + required: false, + refreshers: [], + options: async () => { + const countries = await getCountries(); + return { + options: countries.map((country: Country) => { + return { + label: country.name, + value: country.iso2Code, + }; + }), + }; + }, + }), + city: Property.ShortText({ + displayName: 'City', + required: false, + }), + state: Property.ShortText({ + displayName: 'State', + required: false, + }), + address: Property.LongText({ + displayName: 'Address', + required: false, + }), + postalCode: Property.ShortText({ + displayName: 'Postal Code', + required: false, + }), + timezone: Property.Dropdown({ + displayName: 'Time Zone', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + }; + + const timezones = await getTimezones(auth as OAuth2PropertyValue); + return { + options: timezones.map((timezone) => { + return { + label: timezone, + value: timezone, + }; + }), + }; + }, + }), + }, + + async run({ auth, propsValue }) { + await propsValidation.validateZod(propsValue, { + email: z.string().email().optional(), + phone: z.string().regex(/^\+?[1-9]\d{1,14}$/).optional(), + website: z.string().url().optional(), + }); + + const { + firstName, + lastName, + email, + phone, + companyName, + website, + tags, + source, + country, + city, + state, + address, + postalCode, + timezone, + } = propsValue; + + const contact: LeadConnectorContactDto = { + firstName: firstName, + lastName: lastName, + email: email, + phone: phone, + companyName: companyName, + website: website, + tags: tags, + source: source, + country: country, + city: city, + state: state, + address1: address, + postalCode: postalCode, + timezone: timezone, + }; + + return await addContact(auth, contact); + }, +}); diff --git a/packages/pieces/community/lead-connector/src/lib/actions/create-opportunity.ts b/packages/pieces/community/lead-connector/src/lib/actions/create-opportunity.ts new file mode 100644 index 0000000..ea7fb04 --- /dev/null +++ b/packages/pieces/community/lead-connector/src/lib/actions/create-opportunity.ts @@ -0,0 +1,174 @@ +import { + createAction, + OAuth2PropertyValue, + Property, +} from '@activepieces/pieces-framework'; +import { + createOpportunity, + getContacts, + getPipeline, + getPipelines, + getUsers, + LeadConnectorOpportunityStatus, +} from '../common'; +import { leadConnectorAuth } from '../..'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const createOpportunityAction = createAction({ + auth: leadConnectorAuth, + name: 'create_opportunity', + displayName: 'Create Opportunity', + description: 'Create a new opportunity.', + props: { + pipeline: Property.Dropdown({ + displayName: 'Pipeline', + description: 'The ID of the pipeline to use.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + }; + } + + const pipelines = await getPipelines(auth as OAuth2PropertyValue); + return { + options: pipelines.map((pipeline: any) => { + return { + label: pipeline.name, + value: pipeline.id, + }; + }), + }; + }, + }), + stage: Property.Dropdown({ + displayName: 'Stage', + description: 'The stage of the pipeline to use.', + required: true, + refreshers: ['pipeline'], + options: async ({ auth, pipeline }) => { + if (!auth) { + return { + disabled: true, + options: [], + }; + } + + const pipelineObj = await getPipeline( + auth as OAuth2PropertyValue, + pipeline as string + ); + return { + options: pipelineObj + ? pipelineObj.stages.map((stage: any) => { + return { + label: stage.name, + value: stage.id, + }; + }) + : [], + }; + }, + }), + title: Property.ShortText({ + displayName: 'Title', + required: true, + }), + contact: Property.Dropdown({ + displayName: 'Contact', + description: 'The contact to use.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + }; + + const contacts = await getContacts(auth as OAuth2PropertyValue); + return { + options: contacts.map((contact) => { + return { + label: contact.contactName, + value: contact.id, + }; + }), + }; + }, + }), + status: Property.Dropdown({ + displayName: 'Status', + required: true, + refreshers: [], + options: async () => { + const statuses = Object.values(LeadConnectorOpportunityStatus); + + return { + options: statuses.map((status) => { + return { + label: status.charAt(0).toUpperCase() + status.slice(1), + value: status, + }; + }), + }; + }, + }), + assignedTo: Property.Dropdown({ + displayName: 'Assigned To', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + }; + + const users = await getUsers(auth as OAuth2PropertyValue); + return { + options: users.map((user: any) => { + return { + label: `${user.firstName} ${user.lastName}`, + value: user.id, + }; + }), + }; + }, + }), + monetaryValue: Property.Number({ + displayName: 'Monetary Value', + required: false, + }), + }, + + async run({ auth, propsValue }) { + await propsValidation.validateZod(propsValue, { + monetaryValue: z.number().optional(), + }); + + const { + pipeline, + stage, + contact, + status, + title, + assignedTo, + monetaryValue, + } = propsValue; + + return await createOpportunity(auth, { + pipelineStageId: stage, + contactId: contact, + status: status, + name: title, + pipelineId: pipeline, + assignedTo: assignedTo, + monetaryValue: monetaryValue, + }); + }, +}); diff --git a/packages/pieces/community/lead-connector/src/lib/actions/create-task.ts b/packages/pieces/community/lead-connector/src/lib/actions/create-task.ts new file mode 100644 index 0000000..c5e4b81 --- /dev/null +++ b/packages/pieces/community/lead-connector/src/lib/actions/create-task.ts @@ -0,0 +1,93 @@ +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { + createTask, + getContacts, + getUsers, +} from '../common'; +import { leadConnectorAuth } from '../..'; + +export const createTaskAction = createAction({ + auth: leadConnectorAuth, + name: 'create_task', + displayName: 'Create Task', + description: 'Create a new task.', + props: { + contact: Property.Dropdown({ + displayName: 'Contact', + description: 'The contact to use.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + }; + + const contacts = await getContacts(auth as OAuth2PropertyValue); + + return { + options: contacts.map((contact) => { + return { + label: contact.contactName, + value: contact.id, + }; + }), + }; + }, + }), + title: Property.ShortText({ + displayName: 'Title', + required: true, + }), + dueDate: Property.DateTime({ + displayName: 'Due Date', + required: true, + }), + description: Property.ShortText({ + displayName: 'Description', + required: false, + }), + assignedTo: Property.Dropdown({ + displayName: 'Assigned To', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + }; + + const users = await getUsers(auth as OAuth2PropertyValue); + return { + options: users.map((user: any) => { + return { + label: `${user.firstName} ${user.lastName}`, + value: user.id, + }; + }), + }; + }, + }), + completed: Property.Checkbox({ + displayName: 'Completed', + required: true, + defaultValue: false, + }), + }, + + async run({ auth, propsValue }) { + const { contact, title, dueDate, description, assignedTo, completed } = + propsValue; + + return await createTask(auth.access_token, contact, { + title: title, + // Needs to be ISO string without milliseconds + dueDate: new Date(dueDate).toISOString().split('.')[0] + 'Z', + body: description, + assignedTo: assignedTo, + completed, + }); + }, +}); diff --git a/packages/pieces/community/lead-connector/src/lib/actions/search-contacts.ts b/packages/pieces/community/lead-connector/src/lib/actions/search-contacts.ts new file mode 100644 index 0000000..dd8ef87 --- /dev/null +++ b/packages/pieces/community/lead-connector/src/lib/actions/search-contacts.ts @@ -0,0 +1,25 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getContacts } from '../common'; +import { leadConnectorAuth } from '../..'; + +export const searchContactsAction = createAction({ + auth: leadConnectorAuth, + name: 'search_contacts', + displayName: 'Search Contacts', + description: 'Search for contacts with a search query.', + props: { + query: Property.ShortText({ + displayName: 'Search Query', + description: 'The value you want to search for.', + required: true, + }), + }, + + async run({ auth, propsValue }) { + const { query } = propsValue; + + return await getContacts(auth, { + query: query, + }); + }, +}); diff --git a/packages/pieces/community/lead-connector/src/lib/actions/update-contact.ts b/packages/pieces/community/lead-connector/src/lib/actions/update-contact.ts new file mode 100644 index 0000000..f08aa75 --- /dev/null +++ b/packages/pieces/community/lead-connector/src/lib/actions/update-contact.ts @@ -0,0 +1,181 @@ +import { + createAction, + OAuth2PropertyValue, + Property, +} from '@activepieces/pieces-framework'; +import { + Country, + getCountries, + getTags, + getTimezones, + LeadConnectorContactDto, + updateContact, +} from '../common'; +import { leadConnectorAuth } from '../..'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const updateContactAction = createAction({ + auth: leadConnectorAuth, + name: 'update_contact', + displayName: 'Update Contact', + description: 'Update an existing contact.', + props: { + id: Property.ShortText({ + displayName: 'Contact ID', + description: 'The ID of the contact.', + required: true, + }), + firstName: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + required: false, + }), + phone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + companyName: Property.ShortText({ + displayName: 'Company Name', + required: false, + }), + website: Property.ShortText({ + displayName: 'Website', + required: false, + }), + tags: Property.MultiSelectDropdown({ + displayName: 'Tags', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + }; + + const tags = await getTags(auth as OAuth2PropertyValue); + return { + options: tags.map((tag) => { + return { + label: tag.name, + value: tag.name, + }; + }), + }; + }, + }), + source: Property.ShortText({ + displayName: 'Source', + required: false, + }), + country: Property.Dropdown({ + displayName: 'Country', + description: + 'When using a dynamic value, make sure to use the ISO-2 country code, and not the country name.', + required: false, + refreshers: [], + options: async () => { + const countries = await getCountries(); + return { + options: countries.map((country: Country) => { + return { + label: country.name, + value: country.iso2Code, + }; + }), + }; + }, + }), + city: Property.ShortText({ + displayName: 'City', + required: false, + }), + state: Property.ShortText({ + displayName: 'State', + required: false, + }), + address: Property.LongText({ + displayName: 'Address', + required: false, + }), + postalCode: Property.ShortText({ + displayName: 'Postal Code', + required: false, + }), + timezone: Property.Dropdown({ + displayName: 'Time Zone', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + }; + + const timezones = await getTimezones(auth as OAuth2PropertyValue); + return { + options: timezones.map((timezone) => { + return { + label: timezone, + value: timezone, + }; + }), + }; + }, + }), + }, + + async run({ auth, propsValue }) { + await propsValidation.validateZod(propsValue, { + email: z.string().email().optional(), + phone: z.string().optional(), + website: z.string().url().optional(), + }); + + const { + id, + firstName, + lastName, + email, + phone, + companyName, + website, + tags, + source, + country, + city, + state, + address, + postalCode, + timezone, + } = propsValue; + + const contact: LeadConnectorContactDto = { + firstName: firstName, + lastName: lastName, + email: email, + phone: phone, + companyName: companyName, + website: website, + tags: tags, + source: source, + country: country, + city: city, + state: state, + address1: address, + postalCode: postalCode, + timezone: timezone, + }; + + return await updateContact(auth.access_token, id, contact); + }, +}); diff --git a/packages/pieces/community/lead-connector/src/lib/actions/update-opportunity.ts b/packages/pieces/community/lead-connector/src/lib/actions/update-opportunity.ts new file mode 100644 index 0000000..e138617 --- /dev/null +++ b/packages/pieces/community/lead-connector/src/lib/actions/update-opportunity.ts @@ -0,0 +1,211 @@ +import { + createAction, + OAuth2PropertyValue, + Property, +} from '@activepieces/pieces-framework'; +import { + getContacts, + getOpportunities, + getOpportunity, + getPipeline, + getPipelines, + getUsers, + LeadConnectorOpportunityStatus, + updateOpportunity, +} from '../common'; +import { leadConnectorAuth } from '../..'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const updateOpportunityAction = createAction({ + auth: leadConnectorAuth, + name: 'update_opportunity', + displayName: 'Update Opportunity', + description: 'Updates an existing opportunity.', + props: { + pipeline: Property.Dropdown({ + displayName: 'Pipeline', + description: 'The ID of the pipeline to use.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + }; + } + + const pipelines = await getPipelines(auth as OAuth2PropertyValue); + return { + options: pipelines.map((pipeline: any) => { + return { + label: pipeline.name, + value: pipeline.id, + }; + }), + }; + }, + }), + opportunity: Property.Dropdown({ + displayName: 'Opportunity', + required: true, + refreshers: ['pipeline'], + options: async ({ auth, pipeline }) => { + if (!auth) { + return { + disabled: true, + options: [], + }; + } + + const opportunities = await getOpportunities( + auth as OAuth2PropertyValue, + pipeline as string + ); + return { + options: opportunities.map((opportunity: any) => { + return { + label: opportunity.name, + value: opportunity.id, + }; + }), + }; + }, + }), + stage: Property.Dropdown({ + displayName: 'Stage', + description: 'The stage of the pipeline to use.', + required: false, + refreshers: ['pipeline'], + options: async ({ auth, pipeline }) => { + if (!auth) { + return { + disabled: true, + options: [], + }; + } + + const pipelineObj = await getPipeline( + auth as OAuth2PropertyValue, + pipeline as string + ); + return { + options: pipelineObj + ? pipelineObj.stages.map((stage: any) => { + return { + label: stage.name, + value: stage.id, + }; + }) + : [], + }; + }, + }), + title: Property.ShortText({ + displayName: 'Title', + required: false, + }), + contact: Property.Dropdown({ + displayName: 'Contact', + description: 'The contact to use.', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + }; + + const contacts = await getContacts(auth as OAuth2PropertyValue); + return { + options: contacts.map((contact) => { + return { + label: contact.contactName, + value: contact.id, + }; + }), + }; + }, + }), + status: Property.Dropdown({ + displayName: 'Status', + required: false, + refreshers: [], + options: async () => { + const statuses = Object.values(LeadConnectorOpportunityStatus); + + return { + options: statuses.map((status) => { + return { + label: status.charAt(0).toUpperCase() + status.slice(1), + value: status, + }; + }), + }; + }, + }), + assignedTo: Property.Dropdown({ + displayName: 'Assigned To', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + }; + + const users = await getUsers(auth as OAuth2PropertyValue); + return { + options: users.map((user: any) => { + return { + label: `${user.firstName} ${user.lastName}`, + value: user.id, + }; + }), + }; + }, + }), + monetaryValue: Property.Number({ + displayName: 'Monetary Value', + required: false, + }), + }, + + async run({ auth, propsValue }) { + await propsValidation.validateZod(propsValue, { + monetaryValue: z.number().optional(), + }); + + const { + pipeline, + opportunity, + stage, + contact, + status, + title, + assignedTo, + monetaryValue, + } = propsValue; + + let originalData: any; + if (!title || !stage || !status) + originalData = await getOpportunity( + auth.access_token, + pipeline, + opportunity + ); + + return await updateOpportunity(auth.access_token, opportunity, { + pipelineId: pipeline ?? originalData.pipelineId, + pipelineStageId: stage ?? originalData.pipelineStageId, + contactId: contact, + status: status ?? originalData.status, + name: title ?? originalData.name, + assignedTo: assignedTo, + monetaryValue: monetaryValue, + }); + }, +}); diff --git a/packages/pieces/community/lead-connector/src/lib/actions/update-task.ts b/packages/pieces/community/lead-connector/src/lib/actions/update-task.ts new file mode 100644 index 0000000..6e0fd51 --- /dev/null +++ b/packages/pieces/community/lead-connector/src/lib/actions/update-task.ts @@ -0,0 +1,139 @@ +import { + createAction, + OAuth2PropertyValue, + Property, +} from '@activepieces/pieces-framework'; +import { + getContacts, + getTask, + getTasks, + getUsers, + updateTask, +} from '../common'; +import { leadConnectorAuth } from '../..'; + +export const updateTaskAction = createAction({ + auth: leadConnectorAuth, + name: 'update_task', + displayName: 'Update Task', + description: 'Update a task.', + props: { + contact: Property.Dropdown({ + displayName: 'Contact', + description: 'The contact to use.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + }; + + const contacts = await getContacts(auth as OAuth2PropertyValue); + + return { + options: contacts.map((contact) => { + return { + label: contact.contactName, + value: contact.id, + }; + }), + }; + }, + }), + task: Property.Dropdown({ + displayName: 'Task', + required: true, + refreshers: ['contact'], + options: async ({ auth, contact }) => { + if (!auth || !contact) + return { + disabled: true, + options: [], + }; + + const tasks = await getTasks( + (auth as OAuth2PropertyValue).access_token, + contact as string + ); + return { + options: tasks.map((task: any) => { + return { + label: task.title, + value: task.id, + }; + }), + }; + }, + }), + title: Property.ShortText({ + displayName: 'Title', + required: false, + }), + dueDate: Property.DateTime({ + displayName: 'Due Date', + required: false, + }), + description: Property.ShortText({ + displayName: 'Description', + required: false, + }), + assignedTo: Property.Dropdown({ + displayName: 'Assigned To', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + }; + + const users = await getUsers(auth as OAuth2PropertyValue); + return { + options: users.map((user: any) => { + return { + label: `${user.firstName} ${user.lastName}`, + value: user.id, + }; + }), + }; + }, + }), + completed: Property.Checkbox({ + displayName: 'Completed', + required: false, + defaultValue: false, + }), + }, + + async run({ auth, propsValue }) { + const { + contact, + task, + title, + dueDate, + description, + assignedTo, + completed, + } = propsValue; + + // let originalData: any; + // if (!title || !dueDate) + // originalData = await getTask(auth.access_token, contact, task); + + return await updateTask(auth.access_token, contact, task, { + title: title, //?? originalData.title, + // Needs to be ISO string without milliseconds + dueDate: dueDate, // ? formatDate(dueDate) : formatDate(originalData.dueDate), + body: description, + assignedTo: assignedTo, + completed, + }); + }, +}); + +function formatDate(date: string) { + return new Date(date).toISOString().split('.')[0] + 'Z'; +} diff --git a/packages/pieces/community/lead-connector/src/lib/common/index.ts b/packages/pieces/community/lead-connector/src/lib/common/index.ts new file mode 100644 index 0000000..80a251b --- /dev/null +++ b/packages/pieces/community/lead-connector/src/lib/common/index.ts @@ -0,0 +1,571 @@ +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import jwt from 'jsonwebtoken'; + +export const baseUrl = 'https://services.leadconnectorhq.com'; + +export const leadConnectorHeaders = { + Version: '2021-07-28', +}; + +export async function getCampaigns(auth: OAuth2PropertyValue): Promise { + const result = await httpClient.sendRequest({ + url: `${baseUrl}/campaigns/`, + method: HttpMethod.GET, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + queryParams: { + // status: 'published', + locationId: auth.data['locationId'], + }, + }); + + return result.body['campaigns']; +} + +export async function getWorkflows(auth: OAuth2PropertyValue): Promise { + const result = await httpClient.sendRequest({ + url: `${baseUrl}/workflows/`, + method: HttpMethod.GET, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + queryParams: { + locationId: auth.data['locationId'], + }, + }); + + return result.body['workflows']; +} + +export async function getTimezones( + auth: OAuth2PropertyValue +): Promise { + const result = await httpClient.sendRequest({ + url: `${baseUrl}/locations/${auth.data['locationId']}/timezones`, + method: HttpMethod.GET, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }); + + return result.body['timeZones']; +} + +export async function getTags(auth: OAuth2PropertyValue): Promise { + const result = await httpClient.sendRequest({ + url: `${baseUrl}/locations/${auth.data['locationId']}/tags`, + method: HttpMethod.GET, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }); + + return result.body['tags']; +} + +export async function addContact( + auth: OAuth2PropertyValue, + contact: LeadConnectorContactDto +) { + contact.locationId = auth.data['locationId']; + + const result = await httpClient.sendRequest({ + url: `${baseUrl}/contacts/`, + method: HttpMethod.POST, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + body: contact, + }); + + return result.body['contact']; +} + +export async function updateContact( + auth: string, + id: string, + data: LeadConnectorContactDto +) { + const result = await httpClient.sendRequest({ + url: `${baseUrl}/contacts/${id}`, + method: HttpMethod.PUT, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + body: data, + }); + + return result.body['contact']; +} + +export async function getCountries(): Promise { + const result: any = await httpClient.sendRequest({ + url: `http://api.worldbank.org/v2/country?format=json&per_page=300`, + method: HttpMethod.GET, + }); + const countries = result.body[1] as Country[]; + + // FREE PALESTINE + countries.splice( + countries.findIndex((country) => country.id == 'ISR'), + 1, + { + id: 'PSE', + name: 'Palestine', + iso2Code: 'PS', + } + ); + return countries; +} + +export async function getContacts( + auth: OAuth2PropertyValue, + filters?: { + startAfterId?: string; + sortOrder?: 'asc' | 'desc'; + sortBy?: 'date_added' | 'date_updated'; + query?: string; + } +): Promise { + const queryParams: any = { + limit: '100', + locationId: auth.data['locationId'], + }; + + if (filters?.startAfterId) queryParams.startAfterId = filters.startAfterId; + if (filters?.sortOrder) queryParams.order = filters.sortOrder; + if (filters?.sortBy) queryParams.sortBy = filters.sortBy; + if (filters?.query) queryParams.query = filters.query; + + const response = await httpClient.sendRequest({ + url: `${baseUrl}/contacts/`, + method: HttpMethod.GET, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + queryParams: queryParams, + }); + const result = response.body['contacts'] as LeadConnectorContact[]; + return result; +} + +export async function getForms( + auth: OAuth2PropertyValue +): Promise { + const response = await httpClient.sendRequest({ + url: `${baseUrl}/forms/`, + method: HttpMethod.GET, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + queryParams: { + locationId: auth.data['locationId'], + }, + }); + const result = response.body['forms'] as LeadConnectorForm[]; + return result; +} + +export async function getFormSubmissions( + auth: OAuth2PropertyValue, + formId: string +): Promise { + const response = await httpClient.sendRequest({ + url: `${baseUrl}/forms/submissions/`, + method: HttpMethod.GET, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + queryParams: { + limit: '100', + formId, + locationId: auth.data['locationId'], + }, + }); + + return response.body['submissions']; +} + +export async function addContactToCampaign( + auth: string, + contact: string, + campaign: string +) { + const response = await httpClient.sendRequest({ + url: `${baseUrl}/contacts/${contact}/campaigns/${campaign}`, + method: HttpMethod.POST, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + }); + + return response.body; +} + +export async function addContactToWorkflow( + auth: string, + contact: string, + workflow: string +) { + const response = await httpClient.sendRequest({ + url: `${baseUrl}/contacts/${contact}/workflow/${workflow}`, + method: HttpMethod.POST, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + }); + + return response.body; +} + +export async function addNoteToContact( + auth: string, + contact: string, + data: { + body: string; + userId: string; + } +) { + const response = await httpClient.sendRequest({ + url: `${baseUrl}/contacts/${contact}/notes/`, + method: HttpMethod.POST, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + body: data, + }); + + return response.body; +} + +export async function getPipelines(auth: OAuth2PropertyValue): Promise { + const response = await httpClient.sendRequest({ + url: `${baseUrl}/opportunities/pipelines`, + method: HttpMethod.GET, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + queryParams: { + locationId: auth.data['locationId'], + }, + }); + const result = response.body['pipelines']; + return result; +} + +export async function getPipeline( + auth: OAuth2PropertyValue, + pipelineId: string +): Promise { + const pipelines = await getPipelines(auth); + return pipelines.find((pipeline: any) => pipeline.id == pipelineId); +} + +export async function getOpportunities( + auth: OAuth2PropertyValue, + pipeline: string, + filters?: { + startAfterId?: string; + } +): Promise { + const queryParams: any = { + limit: '100', + location_id: auth.data['locationId'], + pipeline_id: pipeline, + }; + if (filters?.startAfterId) queryParams.startAfterId = filters.startAfterId; + + const response = await httpClient.sendRequest({ + url: `${baseUrl}/opportunities/search`, + method: HttpMethod.GET, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + queryParams: queryParams, + }); + const result = response.body['opportunities']; + return result; +} + +export async function getOpportunity( + auth: string, + pipeline: string, + opportunity: string +): Promise { + const response = await httpClient.sendRequest({ + url: `${baseUrl}/opportunities/${opportunity}`, + method: HttpMethod.GET, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + }); + + const result = response.body; + return result; +} + +export async function createOpportunity( + auth: OAuth2PropertyValue, + data: LeadConnectorOpportunityDto +) { + data.locationId = auth.data['locationId']; + const result = await httpClient.sendRequest({ + url: `${baseUrl}/opportunities/`, + method: HttpMethod.POST, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + body: data, + }); + + return result.body; +} + +export async function updateOpportunity( + auth: string, + opportunity: string, + data: LeadConnectorOpportunityDto +) { + const result = await httpClient.sendRequest({ + url: `${baseUrl}/opportunities/${opportunity}`, + method: HttpMethod.PUT, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + body: data, + }); + + return result.body; +} + +export async function getUsers(auth: OAuth2PropertyValue): Promise { + const response = await httpClient.sendRequest({ + url: `${baseUrl}/users/search`, + method: HttpMethod.GET, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + queryParams: { + locationId: auth.data['locationId'], + companyId: auth.data['companyId'], + }, + }); + const result = response.body['users']; + return result; +} + +export async function getTasks(auth: string, contact: string): Promise { + const response = await httpClient.sendRequest({ + url: `${baseUrl}/contacts/${contact}/tasks`, + method: HttpMethod.GET, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + }); + const result = response.body['tasks']; + return result; +} + +export async function getTask( + auth: string, + contact: string, + task: string +): Promise { + const response = await httpClient.sendRequest({ + url: `${baseUrl}/contacts/${contact}/tasks/${task}`, + method: HttpMethod.GET, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + }); + const result = response.body; + return result; +} + +export async function createTask( + auth: string, + contact: string, + task: LeadConnectorTaskDto +) { + const result = await httpClient.sendRequest({ + url: `${baseUrl}/contacts/${contact}/tasks`, + method: HttpMethod.POST, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + body: task, + }); + + return result.body; +} + +export async function updateTask( + auth: string, + contact: string, + task: string, + data: Partial +) { + const result = await httpClient.sendRequest({ + url: `${baseUrl}/contacts/${contact}/tasks/${task}`, + method: HttpMethod.PUT, + headers: leadConnectorHeaders, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + body: data, + }); + + return result.body; +} + +export function getLocationIdFromToken(auth: OAuth2PropertyValue): { + authClass: string; + authClassId: string; +} { + const result = jwt.decode(auth.access_token); + + return result as { authClass: string; authClassId: string }; +} + +export interface LeadConnectorContact { + id: string; + locationId: string; + firstName: string; + lastName: string; + contactName: string; + email: string; + phone: string; + companyName: string; + website: string; + tags: string[]; + source: string; + country: string; + city: string; + state: string; + address: string; + postalCode: string; + timezone: string; + dnd: boolean; + type: string; + customField: any[]; + dateAdded: string; + dateUpdated: string; +} + +export interface LeadConnectorContactDto { + locationId?: string; + firstName?: string; + lastName?: string; + email?: string; + phone?: string; + companyName?: string; + website?: string; + tags?: string[]; + source?: string; + country?: string; + city?: string; + state?: string; + address1?: string; + postalCode?: string; + timezone?: string; +} + +export interface LeadConnectorForm { + id: string; + name: string; +} + +export interface LeadConnectorTaskDto { + title: string; + dueDate: string; + body?: string; + assignedTo?: string; + completed?: boolean; +} + +export interface LeadConnectorOpportunityDto { + name: string; + pipelineId: string; + pipelineStageId: string; + locationId?: string; + contactId?: string; + status: LeadConnectorOpportunityStatus; + monetaryValue?: number; + assignedTo?: string; +} + +export enum LeadConnectorTaskStatus { + COMPLETED = 'completed', + INCOMPLETED = 'incompleted', +} + +export enum LeadConnectorOpportunityStatus { + OPEN = 'open', + WON = 'won', + LOST = 'lost', + ABANDONED = 'abandoned', +} + +export interface Country { + id: string; + name: string; + iso2Code: string; +} + +export interface LeadConnectorLocation { + id: string; + name: string; + phone: string; + email: string; + country: string; + timezone: string; +} diff --git a/packages/pieces/community/lead-connector/src/lib/triggers/contact-updated.ts b/packages/pieces/community/lead-connector/src/lib/triggers/contact-updated.ts new file mode 100644 index 0000000..0966a96 --- /dev/null +++ b/packages/pieces/community/lead-connector/src/lib/triggers/contact-updated.ts @@ -0,0 +1,68 @@ +import { OAuth2PropertyValue, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { leadConnectorAuth } from '../..'; +import { getContacts } from '../common'; + +const polling: Polling = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth }) => { + const currentValues = + (await getContacts(auth, { + sortBy: 'date_updated', + sortOrder: 'asc', + })) ?? []; + + return currentValues.map((contact) => { + return { + epochMilliSeconds: new Date(contact.dateUpdated).getTime(), + data: contact, + }; + }); + }, +}; + +export const contactUpdated = createTrigger({ + auth: leadConnectorAuth, + name: 'contact_updated', + displayName: 'Contact Created or Updated', + description: 'Trigger when a contact is created or updated.', + props: {}, + type: TriggerStrategy.POLLING, + sampleData: {}, + + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, + test: async (context) => { + return await pollingHelper.test(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, +}); diff --git a/packages/pieces/community/lead-connector/src/lib/triggers/new-contact.ts b/packages/pieces/community/lead-connector/src/lib/triggers/new-contact.ts new file mode 100644 index 0000000..357b34d --- /dev/null +++ b/packages/pieces/community/lead-connector/src/lib/triggers/new-contact.ts @@ -0,0 +1,58 @@ +import { OAuth2PropertyValue, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { leadConnectorAuth } from '../..'; +import { getContacts } from '../common'; + +const polling: Polling = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ auth, lastItemId }) => { + const currentValues = + (await getContacts(auth, { + startAfterId: lastItemId as string, + sortOrder: 'asc', + })) ?? []; + + return currentValues.map((contact) => { + return { + id: contact.id, + data: contact, + }; + }); + }, +}; + +export const newContact = createTrigger({ + auth: leadConnectorAuth, + name: 'new_contact', + displayName: 'New Contact', + description: 'Trigger when a new contact is added.', + props: {}, + type: TriggerStrategy.POLLING, + sampleData: {}, + + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, context); + }, + test: async (context) => { + return await pollingHelper.test(polling, context); + }, +}); diff --git a/packages/pieces/community/lead-connector/src/lib/triggers/new-form-submission.ts b/packages/pieces/community/lead-connector/src/lib/triggers/new-form-submission.ts new file mode 100644 index 0000000..0108448 --- /dev/null +++ b/packages/pieces/community/lead-connector/src/lib/triggers/new-form-submission.ts @@ -0,0 +1,90 @@ +import { OAuth2PropertyValue, Property, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { leadConnectorAuth } from '../..'; +import { getFormSubmissions, getForms } from '../common'; + +const polling: Polling = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ auth, propsValue }) => { + const currentValues = + (await getFormSubmissions(auth, propsValue.form)) ?? []; + + return currentValues.map((submission) => { + return { + id: submission.id, + data: submission, + }; + }); + }, +}; + +export const newFormSubmission = createTrigger({ + auth: leadConnectorAuth, + name: 'new_form_submission', + displayName: 'New Form Submission', + description: 'Trigger when a form is submitted.', + props: { + form: Property.Dropdown({ + displayName: 'Form', + description: 'The form you want to use.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + }; + + const forms = await getForms(auth as OAuth2PropertyValue); + + return { + options: forms.map((form) => { + return { + label: form.name, + value: form.id, + }; + }), + }; + }, + }), + }, + type: TriggerStrategy.POLLING, + sampleData: {}, + + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, + test: async (context) => { + return await pollingHelper.test(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, +}); diff --git a/packages/pieces/community/lead-connector/src/lib/triggers/new-opportunity.ts b/packages/pieces/community/lead-connector/src/lib/triggers/new-opportunity.ts new file mode 100644 index 0000000..7f1a335 --- /dev/null +++ b/packages/pieces/community/lead-connector/src/lib/triggers/new-opportunity.ts @@ -0,0 +1,92 @@ +import { OAuth2PropertyValue, Property, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { leadConnectorAuth } from '../..'; +import { getOpportunities, getPipelines } from '../common'; + +const polling: Polling = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ auth, propsValue, lastItemId }) => { + const currentValues = + (await getOpportunities(auth, propsValue.pipeline, { + startAfterId: lastItemId as string | undefined, + })) ?? []; + + return currentValues.map((opportunity: any) => { + return { + id: opportunity.id, + data: opportunity, + }; + }); + }, +}; + +export const newOpportunity = createTrigger({ + auth: leadConnectorAuth, + name: 'new_opportunity', + displayName: 'New Opportunity', + description: 'Trigger when a new opportunity is added.', + props: { + pipeline: Property.Dropdown({ + displayName: 'Pipeline', + description: 'The ID of the pipeline to use.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + }; + } + + const pipelines = await getPipelines(auth as OAuth2PropertyValue); + return { + options: pipelines.map((pipeline: any) => { + return { + label: pipeline.name, + value: pipeline.id, + }; + }), + }; + }, + }), + }, + type: TriggerStrategy.POLLING, + sampleData: {}, + + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, + test: async (context) => { + return await pollingHelper.test(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, +}); diff --git a/packages/pieces/community/lead-connector/tsconfig.json b/packages/pieces/community/lead-connector/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/lead-connector/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/lead-connector/tsconfig.lib.json b/packages/pieces/community/lead-connector/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/lead-connector/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/lever/.eslintrc.json b/packages/pieces/community/lever/.eslintrc.json new file mode 100644 index 0000000..d7449bb --- /dev/null +++ b/packages/pieces/community/lever/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {}, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/lever/README.md b/packages/pieces/community/lever/README.md new file mode 100644 index 0000000..2e098f8 --- /dev/null +++ b/packages/pieces/community/lever/README.md @@ -0,0 +1,7 @@ +# pieces-lever + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-lever` to build the library. diff --git a/packages/pieces/community/lever/package.json b/packages/pieces/community/lever/package.json new file mode 100644 index 0000000..7ec3340 --- /dev/null +++ b/packages/pieces/community/lever/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-lever", + "version": "0.0.5" +} diff --git a/packages/pieces/community/lever/project.json b/packages/pieces/community/lever/project.json new file mode 100644 index 0000000..7fe806e --- /dev/null +++ b/packages/pieces/community/lever/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-lever", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/lever/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/lever", + "tsConfig": "packages/pieces/community/lever/tsconfig.lib.json", + "packageJson": "packages/pieces/community/lever/package.json", + "main": "packages/pieces/community/lever/src/index.ts", + "assets": [ + "packages/pieces/community/lever/*.md", + { + "input": "packages/pieces/community/lever/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-lever {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/lever/src/index.ts b/packages/pieces/community/lever/src/index.ts new file mode 100644 index 0000000..e77700d --- /dev/null +++ b/packages/pieces/community/lever/src/index.ts @@ -0,0 +1,55 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { getOpportunity } from './lib/actions/get-opportunity'; +import { updateOpportunityStage } from './lib/actions/update-opportunity-stage'; +import { listOpportunityForms } from './lib/actions/list-opportunity-forms'; +import { listOpportunityFeedback } from './lib/actions/list-opportunity-feedback'; +import { addFeedbackToOpportunity } from './lib/actions/add-feedback-to-opportunity'; +import { PieceCategory } from '@activepieces/shared'; + +export const LEVER_BASE_URL = 'https://api.lever.co/v1'; + +export const leverAuth = PieceAuth.CustomAuth({ + props: { + apiKey: PieceAuth.SecretText({ + displayName: 'API key', + required: true, + }), + }, + required: true, +}); + +export type LeverAuth = { + apiKey: string; +}; +export const lever = createPiece({ + displayName: 'Lever', + auth: leverAuth, + description: + 'Lever is a modern, collaborative recruiting platform that powers a more human approach to hiring.', + categories: [PieceCategory.HUMAN_RESOURCES], + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/lever.png', + authors: ['AdamSelene'], + actions: [ + getOpportunity, + updateOpportunityStage, + listOpportunityForms, + listOpportunityFeedback, + addFeedbackToOpportunity, + createCustomApiCallAction({ + baseUrl: () => { + return LEVER_BASE_URL; + }, + auth: leverAuth, + authMapping: async (auth) => { + const { apiKey } = auth as LeverAuth; + return { + Authorization: + 'Basic ' + Buffer.from(`${apiKey}:`).toString('base64'), + }; + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/lever/src/lib/actions/add-feedback-to-opportunity.ts b/packages/pieces/community/lever/src/lib/actions/add-feedback-to-opportunity.ts new file mode 100644 index 0000000..7090ae2 --- /dev/null +++ b/packages/pieces/community/lever/src/lib/actions/add-feedback-to-opportunity.ts @@ -0,0 +1,355 @@ +import { + createAction, + DynamicPropsValue, + Property, +} from '@activepieces/pieces-framework'; +import { LEVER_BASE_URL, LeverAuth, leverAuth } from '../..'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { LeverFieldMapping } from '../common'; + +export const addFeedbackToOpportunity = createAction({ + name: 'addFeedbackToOpportunity', + displayName: 'Add feedback to opportunity', + description: 'Provide feedback to a candidate after an interview', + auth: leverAuth, + props: { + performAs: Property.Dropdown({ + displayName: 'Feedback author', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect first.', + options: [], + }; + } + + const users = []; + let cursor = undefined; + do { + const queryParams: Record = { + include: 'name', + }; + if (cursor) { + queryParams['offset'] = cursor; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${LEVER_BASE_URL}/users`, + queryParams: queryParams, + authentication: { + type: AuthenticationType.BASIC, + username: (auth as LeverAuth).apiKey, + password: '', + }, + }); + cursor = response.body.next; + const usersPage = response.body.data.map( + (user: { id: string; name: string }) => { + return { + label: user.name, + value: user.id, + }; + } + ); + users.push(...usersPage); + } while (cursor !== undefined); + + return { + options: users, + }; + }, + }), + opportunityId: Property.ShortText({ + displayName: 'Opportunity ID', + required: true, + }), + panelId: Property.Dropdown({ + displayName: 'Interview panel', + description: 'If you select one, you must select an interview too', + required: false, + refreshers: ['auth', 'opportunityId'], + options: async ({ auth, opportunityId }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect first.', + options: [], + }; + } + if (!opportunityId) { + return { + disabled: true, + placeholder: 'Please select a candidate (opportunity).', + options: [], + }; + } + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${LEVER_BASE_URL}/opportunities/${opportunityId}/panels?expand=stage&include=id&include=stage&include=start`, + authentication: { + type: AuthenticationType.BASIC, + username: (auth as LeverAuth).apiKey, + password: '', + }, + }); + return { + options: response.body.data.map( + (panel: { id: string; start: number; stage: { text: string } }) => { + const interviewDate = new Date(panel.start); + return { + label: `${interviewDate.toLocaleDateString()} - ${ + panel.stage.text + }`, + value: panel.id, + }; + } + ), + }; + }, + }), + interviewId: Property.Dropdown({ + displayName: 'Interview', + description: 'Mandatory is you select an interview panel', + required: false, + refreshers: ['auth', 'opportunityId', 'panelId'], + options: async ({ auth, opportunityId, panelId }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect first.', + options: [], + }; + } + if (!opportunityId) { + return { + disabled: true, + placeholder: 'Please select a candidate (opportunity).', + options: [], + }; + } + if (!panelId) { + return { + disabled: true, + placeholder: 'Please select an interview panel.', + options: [], + }; + } + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${LEVER_BASE_URL}/opportunities/${opportunityId}/panels/${panelId}?include=interviews`, + authentication: { + type: AuthenticationType.BASIC, + username: (auth as LeverAuth).apiKey, + password: '', + }, + }); + return { + options: response.body.data.interviews.map( + (interview: { id: string; subject: string }) => { + return { label: interview.subject, value: interview.id }; + } + ), + }; + }, + }), + feedbackTemplateId: Property.Dropdown({ + displayName: 'Feedback template', + description: 'Ignored if you select an interview panel and an interview', + required: false, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect first.', + options: [], + }; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${LEVER_BASE_URL}/feedback_templates`, + authentication: { + type: AuthenticationType.BASIC, + username: (auth as LeverAuth).apiKey, + password: '', + }, + }); + return { + options: response.body.data.map( + (template: { id: string; text: string }) => { + return { + label: template.text, + value: template.id, + }; + } + ), + }; + }, + }), + feedbackFields: Property.DynamicProperties({ + displayName: 'Fields', + required: true, + refreshers: [ + 'auth', + 'opportunityId', + 'panelId', + 'interviewId', + 'feedbackTemplateId', + ], + props: async ({ + auth, + opportunityId, + panelId, + interviewId, + feedbackTemplateId, + }) => { + if ( + !auth || + !opportunityId || + !(feedbackTemplateId || (panelId && interviewId)) + ) { + return { + disabled: true, + placeholder: + 'Please connect your Lever account first and select an interview or a feedback template', + options: [], + }; + } + const fields: DynamicPropsValue = {}; + const templateId = + panelId && interviewId + ? await getFeedbackTemplateForInterview( + opportunityId, + panelId, + interviewId, + auth as LeverAuth + ) + : feedbackTemplateId; + + try { + const feedbackTemplateResponse = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${LEVER_BASE_URL}/feedback_templates/${templateId}`, + authentication: { + type: AuthenticationType.BASIC, + username: (auth as LeverAuth).apiKey, + password: '', + }, + }); + feedbackTemplateResponse.body.data.fields.map( + (field: { + id: string; + text: string; + description: string; + required: boolean; + type: string; + options?: { text: string; optionId: string }[]; + scores?: { text: string; description: string }[]; + }) => { + const mappedField = + LeverFieldMapping[field.type] || LeverFieldMapping['default']; + mappedField.buildActivepieceType(fields, field); + } + ); + } catch (e) { + console.error( + 'Unexpected error while building dynamic properties', + e + ); + } + return fields; + }, + }), + }, + async run({ auth, propsValue }) { + const templateId = + propsValue.panelId && propsValue.interviewId + ? await getFeedbackTemplateForInterview( + propsValue.opportunityId, + propsValue.panelId, + propsValue.interviewId, + auth as LeverAuth + ) + : propsValue.feedbackTemplateId; + + const feedbackTemplateResponse = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${LEVER_BASE_URL}/feedback_templates/${templateId}`, + authentication: { + type: AuthenticationType.BASIC, + username: (auth as LeverAuth).apiKey, + password: '', + }, + }); + + const templateFields = feedbackTemplateResponse.body.data.fields; + const groupedValues = Object.entries(propsValue.feedbackFields).reduce< + Record + >((values, [fieldId, fieldValue]: [string, DynamicPropsValue]) => { + const canonicalId = fieldId.substring(0, 36); + values[canonicalId] = values[canonicalId] ?? []; + values[canonicalId].push(fieldValue); + return values; + }, {}); + + const payload = { + baseTemplateId: templateId, + panel: propsValue.panelId, + interview: propsValue.interviewId, + fieldValues: Object.entries(groupedValues).map(([fieldId, values]) => { + const templateField = templateFields.find( + (tf: { id: string }) => tf.id === fieldId + ); + const mappedField = + templateField.type in LeverFieldMapping + ? LeverFieldMapping[templateField.type] + : LeverFieldMapping['default']; + return mappedField.buildLeverType(fieldId, values); + }), + }; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${LEVER_BASE_URL}/opportunities/${propsValue.opportunityId}/feedback?perform_as=${propsValue.performAs}`, + authentication: { + type: AuthenticationType.BASIC, + username: auth.apiKey, + password: '', + }, + body: payload, + }); + + return response.body.data; + }, +}); + +async function getFeedbackTemplateForInterview( + opportunityId: string | DynamicPropsValue, + panelId: string | DynamicPropsValue, + interviewId: string | DynamicPropsValue, + auth: LeverAuth +) { + const interviewResponse = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${LEVER_BASE_URL}/opportunities/${opportunityId}/panels/${panelId}?include=interviews`, + authentication: { + type: AuthenticationType.BASIC, + username: auth.apiKey, + password: '', + }, + }); + const interview = interviewResponse.body.data.interviews.find( + (interview: { id: string }) => + interview.id === (interviewId as unknown as string) + ); + return interview.feedbackTemplate; +} diff --git a/packages/pieces/community/lever/src/lib/actions/get-opportunity.ts b/packages/pieces/community/lever/src/lib/actions/get-opportunity.ts new file mode 100644 index 0000000..3e6177c --- /dev/null +++ b/packages/pieces/community/lever/src/lib/actions/get-opportunity.ts @@ -0,0 +1,42 @@ +import qs from 'qs'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { LEVER_BASE_URL, leverAuth } from '../..'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; + +export const getOpportunity = createAction({ + name: 'getOpportunity', + displayName: 'Get opportunity', + description: + "Retrieve a single opportunity, i.e. an individual's unique candidacy or journey for a given job position", + auth: leverAuth, + props: { + opportunityId: Property.ShortText({ + displayName: 'Opportunity ID', + required: true, + }), + expand: Property.Array({ + displayName: 'Expand', + required: false, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${LEVER_BASE_URL}/opportunities/${ + propsValue.opportunityId + }?${decodeURIComponent( + qs.stringify({ expand: propsValue.expand }, { arrayFormat: 'repeat' }) + )}`, + authentication: { + type: AuthenticationType.BASIC, + username: auth.apiKey, + password: '', + }, + }); + return response.body.data; + }, +}); diff --git a/packages/pieces/community/lever/src/lib/actions/list-opportunity-feedback.ts b/packages/pieces/community/lever/src/lib/actions/list-opportunity-feedback.ts new file mode 100644 index 0000000..e88a7cb --- /dev/null +++ b/packages/pieces/community/lever/src/lib/actions/list-opportunity-feedback.ts @@ -0,0 +1,70 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { LEVER_BASE_URL, LeverAuth, leverAuth } from '../..'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; + +export const listOpportunityFeedback = createAction({ + name: 'listOpportunityFeedback', + displayName: 'List opportunity feedback', + description: + 'Get all feedback for a given opportunity, optionally for a given template', + auth: leverAuth, + props: { + opportunityId: Property.ShortText({ + displayName: 'Opportunity ID', + required: true, + }), + template: Property.Dropdown({ + displayName: 'Feedback template', + required: false, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect first.', + options: [], + }; + } + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${LEVER_BASE_URL}/feedback_templates?include=text`, + authentication: { + type: AuthenticationType.BASIC, + username: (auth as LeverAuth).apiKey, + password: '', + }, + }); + return { + options: response.body.data.map( + (template: { text: string; id: string }) => { + return { label: template.text, value: template.id }; + } + ), + }; + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${LEVER_BASE_URL}/opportunities/${propsValue.opportunityId}/feedback`, + authentication: { + type: AuthenticationType.BASIC, + username: auth.apiKey, + password: '', + }, + }); + const feedback = response.body.data; + if (propsValue.template) { + return feedback.filter( + (form: { baseTemplateId: string }) => + form.baseTemplateId === propsValue.template + ); + } + return feedback; + }, +}); diff --git a/packages/pieces/community/lever/src/lib/actions/list-opportunity-forms.ts b/packages/pieces/community/lever/src/lib/actions/list-opportunity-forms.ts new file mode 100644 index 0000000..a78cbea --- /dev/null +++ b/packages/pieces/community/lever/src/lib/actions/list-opportunity-forms.ts @@ -0,0 +1,70 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { LEVER_BASE_URL, LeverAuth, leverAuth } from '../..'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; + +export const listOpportunityForms = createAction({ + name: 'listOpportunityForms', + displayName: 'List opportunity forms', + description: + 'Get all forms for a given opportunity, optionally for a given form template', + auth: leverAuth, + props: { + opportunityId: Property.ShortText({ + displayName: 'Opportunity ID', + required: true, + }), + template: Property.Dropdown({ + displayName: 'Form template', + required: false, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect first.', + options: [], + }; + } + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${LEVER_BASE_URL}/form_templates?include=text`, + authentication: { + type: AuthenticationType.BASIC, + username: (auth as LeverAuth).apiKey, + password: '', + }, + }); + return { + options: response.body.data.map( + (template: { text: string; id: string }) => { + return { label: template.text, value: template.id }; + } + ), + }; + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${LEVER_BASE_URL}/opportunities/${propsValue.opportunityId}/forms`, + authentication: { + type: AuthenticationType.BASIC, + username: auth.apiKey, + password: '', + }, + }); + const forms = response.body.data; + if (propsValue.template) { + return forms.filter( + (form: { baseTemplateId: string }) => + form.baseTemplateId === propsValue.template + ); + } + return forms; + }, +}); diff --git a/packages/pieces/community/lever/src/lib/actions/update-opportunity-stage.ts b/packages/pieces/community/lever/src/lib/actions/update-opportunity-stage.ts new file mode 100644 index 0000000..9bb1ba8 --- /dev/null +++ b/packages/pieces/community/lever/src/lib/actions/update-opportunity-stage.ts @@ -0,0 +1,64 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { LEVER_BASE_URL, LeverAuth, leverAuth } from '../..'; + +export const updateOpportunityStage = createAction({ + name: 'updateOpportunityStage', + displayName: 'Update opportunity stage', + description: "Change an Opportunity's current stage", + auth: leverAuth, + props: { + opportunityId: Property.ShortText({ + displayName: 'Opportunity ID', + required: true, + }), + stage: Property.Dropdown({ + displayName: 'Stage', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect first.', + options: [], + }; + } + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${LEVER_BASE_URL}/stages`, + authentication: { + type: AuthenticationType.BASIC, + username: (auth as LeverAuth).apiKey, + password: '', + }, + }); + return { + options: response.body.data.map( + (stage: { text: string; id: string }) => { + return { label: stage.text, value: stage.id }; + } + ), + }; + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: `${LEVER_BASE_URL}/opportunities/${propsValue.opportunityId}/stage`, + authentication: { + type: AuthenticationType.BASIC, + username: auth.apiKey, + password: '', + }, + body: { stage: propsValue.stage }, + }); + + return response.body.data; + }, +}); diff --git a/packages/pieces/community/lever/src/lib/common.ts b/packages/pieces/community/lever/src/lib/common.ts new file mode 100644 index 0000000..18c3936 --- /dev/null +++ b/packages/pieces/community/lever/src/lib/common.ts @@ -0,0 +1,176 @@ +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; + +export type LeverFieldType = { + id: string; + text: string; + description: string; + required: boolean; + type: string; + options?: { text: string; optionId: string }[]; + scores?: { text: string; description: string }[]; +}; + +export const LeverFieldMapping: Record< + string, + { + buildActivepieceType: ( + fields: DynamicPropsValue, + field: LeverFieldType + ) => void; + buildLeverType: ( + id: string, + propsValues: DynamicPropsValue[] + ) => { + id: string; + value: + | string + | string[] + | number + | number[] + | { score: number; comment?: string }[]; + }; + } +> = { + default: { + buildActivepieceType: (fields, field) => + (fields[field.id] = Property.ShortText({ + displayName: field.text, + description: field.description, + required: field.required, + })), + buildLeverType: (id, propsValues) => ({ + id, + value: propsValues[0] as unknown as string, + }), + }, + textarea: { + buildActivepieceType: (fields, field) => + (fields[field.id] = Property.LongText({ + displayName: field.text, + description: field.description, + required: field.required, + })), + buildLeverType: (id, propsValues) => ({ + id, + value: propsValues[0] as unknown as string, + }), + }, + 'yes-no': { + buildActivepieceType: (fields, field) => + (fields[field.id] = Property.Checkbox({ + displayName: field.text, + description: field.description, + required: field.required, + })), + buildLeverType: (id, propsValues) => { + const value = propsValues[0] as unknown as boolean; + return { + id, + value: value === true ? 'yes' : value === false ? 'no' : 'null', + }; + }, + }, + dropdown: { + buildActivepieceType: (fields, field) => + (fields[field.id] = Property.StaticDropdown({ + displayName: field.text, + description: field.description, + required: field.required, + options: { + disabled: false, + options: + field.options?.map((option: { text: string; optionId: string }) => { + return { value: option.text, label: option.text }; + }) || [], + }, + })), + buildLeverType: (id, propsValues) => ({ + id, + value: propsValues[0] as unknown as string, + }), + }, + 'multiple-choice': { + buildActivepieceType: (fields, field) => + (fields[field.id] = Property.StaticDropdown({ + displayName: field.text, + description: field.description, + required: field.required, + options: { + disabled: false, + options: + field.options?.map((option: { text: string; optionId: string }) => { + return { value: option.text, label: option.text }; + }) || [], + }, + })), + buildLeverType: (id, propsValues) => ({ + id, + value: propsValues[0] as unknown as string, + }), + }, + 'multiple-select': { + buildActivepieceType: (fields, field) => + (fields[field.id] = Property.StaticMultiSelectDropdown({ + displayName: field.text, + description: field.description, + required: field.required, + options: { + disabled: false, + options: + field.options?.map((option: { text: string; optionId: string }) => { + return { value: option.text, label: option.text }; + }) || [], + }, + })), + buildLeverType: (id, propsValues) => ({ + id, + value: propsValues[0] as unknown as string[], + }), + }, + 'score-system': { + buildActivepieceType: (fields, field) => + (fields[field.id] = Property.StaticDropdown({ + displayName: field.text, + description: field.description, + required: field.required, + options: { + options: + field.options?.map((option) => { + return { value: option.text, label: option.text }; + }) || [], + }, + })), + buildLeverType: (id, propsValues) => ({ + id, + value: propsValues[0] as unknown as string, + }), + }, + scorecard: { + buildActivepieceType: (fields, field) => + field.scores?.map((score, index) => { + fields[`${field.id}-${index}`] = Property.StaticDropdown({ + displayName: score.text, + description: score.description, + required: field.required, + options: { + options: [ + { label: 'n.a.', value: 0 }, + { label: '👎👎', value: 1 }, + { label: '👎', value: 2 }, + { label: '👍', value: 3 }, + { label: '👍👍', value: 4 }, + ], + }, + }); + }), + buildLeverType: (id, propsValues) => ({ + id, + value: propsValues.map((propsValue: DynamicPropsValue) => { + return { + score: propsValue as unknown as number, + comment: '', + }; + }), + }), + }, +}; diff --git a/packages/pieces/community/lever/tsconfig.json b/packages/pieces/community/lever/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/lever/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/lever/tsconfig.lib.json b/packages/pieces/community/lever/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/lever/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/line/.eslintrc.json b/packages/pieces/community/line/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/line/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/line/README.md b/packages/pieces/community/line/README.md new file mode 100644 index 0000000..b3270b1 --- /dev/null +++ b/packages/pieces/community/line/README.md @@ -0,0 +1,7 @@ +# pieces-line + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-line` to build the library. diff --git a/packages/pieces/community/line/package.json b/packages/pieces/community/line/package.json new file mode 100644 index 0000000..0029d2e --- /dev/null +++ b/packages/pieces/community/line/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-line", + "version": "0.0.4" +} \ No newline at end of file diff --git a/packages/pieces/community/line/project.json b/packages/pieces/community/line/project.json new file mode 100644 index 0000000..9770d6f --- /dev/null +++ b/packages/pieces/community/line/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-line", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/line/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/line", + "tsConfig": "packages/pieces/community/line/tsconfig.lib.json", + "packageJson": "packages/pieces/community/line/package.json", + "main": "packages/pieces/community/line/src/index.ts", + "assets": [ + "packages/pieces/community/line/*.md", + { + "input": "packages/pieces/community/line/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-line {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/line/src/index.ts b/packages/pieces/community/line/src/index.ts new file mode 100644 index 0000000..9bde9f3 --- /dev/null +++ b/packages/pieces/community/line/src/index.ts @@ -0,0 +1,32 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { pushMessage } from './lib/actions/push-message'; +import { newMessage } from './lib/trigger/new-message'; + +export const lineAuth2 = PieceAuth.SecretText({ + displayName: 'Bot Token', + required: true, +}); + +export const line = createPiece({ + displayName: 'Line Bot', + description: 'Build chatbots for LINE', + + auth: lineAuth2, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/line.png', + categories: [PieceCategory.COMMUNICATION], + authors: ["kishanprmr","MoShizzle","abuaboud"], + actions: [ + pushMessage, + createCustomApiCallAction({ + baseUrl: () => 'https://api.line.me/v2', + auth: lineAuth2, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers: [newMessage], +}); diff --git a/packages/pieces/community/line/src/lib/actions/push-message.ts b/packages/pieces/community/line/src/lib/actions/push-message.ts new file mode 100644 index 0000000..c87aabf --- /dev/null +++ b/packages/pieces/community/line/src/lib/actions/push-message.ts @@ -0,0 +1,44 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { lineAuth2 } from '../..'; +import { + AuthenticationType, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; + +export const pushMessage = createAction({ + name: 'push_message', // Must be a unique across the piece, this shouldn't be changed. + auth: lineAuth2, + displayName: 'Push Message', + description: 'Push message to the line account', + props: { + userId: Property.ShortText({ + displayName: 'User Id', + description: 'The user id can be obtained from the webhook payload', + required: true, + }), + text: Property.ShortText({ + displayName: 'Text', + required: true, + }), + }, + async run(context) { + return httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.line.me/v2/bot/message/push`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + body: { + to: context.propsValue.userId, + messages: [ + { + type: 'text', + text: context.propsValue.text, + }, + ], + }, + }); + }, +}); diff --git a/packages/pieces/community/line/src/lib/trigger/new-message.ts b/packages/pieces/community/line/src/lib/trigger/new-message.ts new file mode 100644 index 0000000..09986d6 --- /dev/null +++ b/packages/pieces/community/line/src/lib/trigger/new-message.ts @@ -0,0 +1,43 @@ +import { + createTrigger, + PieceAuth, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; + +const markdown = ` +- Create Line bot account from Developer Console +- Go to the "Messaging API" section. +- In the webhook settings, paste this URL: + \`{{webhookUrl}}\` +- Publish Activepieces flow first then click "Verify" button +`; + +export const newMessage = createTrigger({ + name: 'new-message', + displayName: 'New Message', + auth: PieceAuth.None(), + description: 'Triggers when a new message is received', + props: { + md: Property.MarkDown({ + value: markdown, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: {}, + async onEnable(context) { + // Empty + }, + async onDisable(context) { + // Empty + }, + async run(context) { + const { events } = context.payload.body as { events: unknown[] }; + if (!events) { + return []; + } + return events.filter( + (event: any) => event.mode === 'active' && event.type === 'message' + ); + }, +}); diff --git a/packages/pieces/community/line/tsconfig.json b/packages/pieces/community/line/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/line/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/line/tsconfig.lib.json b/packages/pieces/community/line/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/line/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/linear/.eslintrc.json b/packages/pieces/community/linear/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/linear/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/linear/README.md b/packages/pieces/community/linear/README.md new file mode 100644 index 0000000..0e905d1 --- /dev/null +++ b/packages/pieces/community/linear/README.md @@ -0,0 +1,7 @@ +# pieces-linear + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-linear` to build the library. diff --git a/packages/pieces/community/linear/package-lock.json b/packages/pieces/community/linear/package-lock.json new file mode 100644 index 0000000..dd41e5b --- /dev/null +++ b/packages/pieces/community/linear/package-lock.json @@ -0,0 +1,97 @@ +{ + "name": "@activepieces/piece-linear", + "version": "0.1.8", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-linear", + "version": "0.1.8", + "dependencies": { + "@linear/sdk": "7.0.1" + } + }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@linear/sdk": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@linear/sdk/-/sdk-7.0.1.tgz", + "integrity": "sha512-+t5yW3gHZHOvMCpK4wn7ucC3BATkody3MSL0QxrtL6JaqNPMfdDHYEI8J34A38IBpCudvNCugi5jqOewvwt+jg==", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.0", + "graphql": "^15.4.0", + "isomorphic-unfetch": "^3.1.0" + }, + "engines": { + "node": ">=12.x", + "yarn": "1.x" + } + }, + "node_modules/graphql": { + "version": "15.10.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.10.1.tgz", + "integrity": "sha512-BL/Xd/T9baO6NFzoMpiMD7YUZ62R6viR5tp/MULVEnbYJXZA//kRNW7J0j1w/wXArgL0sCxhDfK5dczSKn3+cg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/isomorphic-unfetch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz", + "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==", + "dependencies": { + "node-fetch": "^2.6.1", + "unfetch": "^4.2.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/packages/pieces/community/linear/package.json b/packages/pieces/community/linear/package.json new file mode 100644 index 0000000..208a84b --- /dev/null +++ b/packages/pieces/community/linear/package.json @@ -0,0 +1,7 @@ +{ + "name": "@activepieces/piece-linear", + "version": "0.1.8", + "dependencies": { + "@linear/sdk": "7.0.1" + } +} diff --git a/packages/pieces/community/linear/project.json b/packages/pieces/community/linear/project.json new file mode 100644 index 0000000..c430eec --- /dev/null +++ b/packages/pieces/community/linear/project.json @@ -0,0 +1,69 @@ +{ + "name": "pieces-linear", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/linear/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/linear", + "tsConfig": "packages/pieces/community/linear/tsconfig.lib.json", + "packageJson": "packages/pieces/community/linear/package.json", + "main": "packages/pieces/community/linear/src/index.ts", + "assets": [ + "packages/pieces/community/linear/*.md", + { + "input": "packages/pieces/community/linear/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + }, + "dependsOn": ["prebuild", "^build"] + }, + "prebuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/pieces/community/linear", + "command": "npm ci" + } + }, + "build-with-deps": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "nx run pieces-linear:prebuild", + "nx run pieces-linear:build", + "nx run pieces-linear:postbuild" + ], + "parallel": false + } + }, + "postbuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "dist/packages/pieces/community/linear", + "command": "npm install" + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-linear {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/linear/src/index.ts b/packages/pieces/community/linear/src/index.ts new file mode 100644 index 0000000..110149a --- /dev/null +++ b/packages/pieces/community/linear/src/index.ts @@ -0,0 +1,54 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { linearCreateComment } from './lib/actions/comments/create-comment'; +import { linearCreateIssue } from './lib/actions/issues/create-issue'; +import { linearUpdateIssue } from './lib/actions/issues/update-issue'; +import { linearCreateProject } from './lib/actions/projects/create-project'; +import { linearUpdateProject } from './lib/actions/projects/update-project'; +import { linearRawGraphqlQuery } from './lib/actions/raw-graphql-query'; +import { linearNewIssue } from './lib/triggers/new-issue'; +import { linearUpdatedIssue } from './lib/triggers/updated-issue'; +import { linearRemovedIssue } from './lib/triggers/removed-issue'; + +const markdown = ` +To obtain your API key, follow these steps: + +1. Go to settings by clicking your profile-pic (top-left) +2. Go to API section inside My Account. +3. On Personal API keys, give label and press create key.`; + +export const linearAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: markdown, + validate: async ({ auth }) => { + if (auth.startsWith('lin_api_')) { + return { + valid: true, + }; + } + return { + valid: false, + error: 'Invalid API Key', + }; + }, +}); +export const linear = createPiece({ + displayName: 'Linear', + description: 'Issue tracking for modern software teams', + + auth: linearAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/linear.png', + authors: ['lldiegon', 'kishanprmr', 'abuaboud'], + categories: [PieceCategory.PRODUCTIVITY], + actions: [ + linearCreateIssue, + linearUpdateIssue, + linearCreateProject, + linearUpdateProject, + linearCreateComment, + linearRawGraphqlQuery, + ], + triggers: [linearNewIssue, linearUpdatedIssue, linearRemovedIssue], +}); diff --git a/packages/pieces/community/linear/src/lib/actions/comments/create-comment.ts b/packages/pieces/community/linear/src/lib/actions/comments/create-comment.ts new file mode 100644 index 0000000..978dd5f --- /dev/null +++ b/packages/pieces/community/linear/src/lib/actions/comments/create-comment.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { linearAuth } from '../../..'; +import { props } from '../../common/props'; +import { makeClient } from '../../common/client'; +import { LinearDocument } from '@linear/sdk'; + +export const linearCreateComment = createAction({ + auth: linearAuth, + name: 'linear_create_comment', + displayName: 'Create Comment', + description: 'Create a new comment on an issue in Linear workspace', + props: { + team_id: props.team_id(), + user_id: props.assignee_id(), + issue_id: props.issue_id(), + body: Property.LongText({ + displayName: 'Comment Body', + description: 'The content of the comment', + required: true, + }), + }, + async run({ auth, propsValue }) { + const comment: LinearDocument.CommentCreateInput = { + issueId: propsValue.issue_id!, + body: propsValue.body, + }; + + const client = makeClient(auth as string); + const result = await client.createComment(comment); + if (result.success) { + const createdComment = await result.comment; + return { + success: result.success, + lastSyncId: result.lastSyncId, + comment: createdComment, + }; + } else { + throw new Error(`Unexpected error: ${result}`) + } + }, +}); diff --git a/packages/pieces/community/linear/src/lib/actions/issues/create-issue.ts b/packages/pieces/community/linear/src/lib/actions/issues/create-issue.ts new file mode 100644 index 0000000..b7c0bc2 --- /dev/null +++ b/packages/pieces/community/linear/src/lib/actions/issues/create-issue.ts @@ -0,0 +1,52 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { linearAuth } from '../../..'; +import { props } from '../../common/props'; +import { makeClient } from '../../common/client'; +import { LinearDocument } from '@linear/sdk'; + +export const linearCreateIssue = createAction({ + auth: linearAuth, + name: 'linear_create_issue', + displayName: 'Create Issue', + description: 'Create a new issue in Linear workspace', + props: { + team_id: props.team_id(), + title: Property.ShortText({ + displayName: 'Title', + required: true, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + state_id: props.status_id(), + labels: props.labels(), + assignee_id: props.assignee_id(), + priority_id: props.priority_id(), + template_id: props.template_id() + }, + async run({ auth, propsValue }) { + const issue: LinearDocument.IssueCreateInput = { + teamId: propsValue.team_id!, + title: propsValue.title, + description: propsValue.description, + assigneeId: propsValue.assignee_id, + stateId: propsValue.state_id, + priority: propsValue.priority_id, + labelIds: propsValue.labels, + templateId: propsValue.template_id + }; + const client = makeClient(auth as string); + const result = await client.createIssue(issue); + if (result.success) { + const createdIssue = await result.issue; + return { + success: result.success, + lastSyncId: result.lastSyncId, + issue: createdIssue, + }; + } else { + throw new Error(`Unexpected error: ${result}`) + } + }, +}); diff --git a/packages/pieces/community/linear/src/lib/actions/issues/update-issue.ts b/packages/pieces/community/linear/src/lib/actions/issues/update-issue.ts new file mode 100644 index 0000000..bec983c --- /dev/null +++ b/packages/pieces/community/linear/src/lib/actions/issues/update-issue.ts @@ -0,0 +1,51 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { linearAuth } from '../../..'; +import { props } from '../../common/props'; +import { makeClient } from '../../common/client'; +import { LinearDocument } from '@linear/sdk'; + +export const linearUpdateIssue = createAction({ + auth: linearAuth, + name: 'linear_update_issue', + displayName: 'Update Issue', + description: 'Update a issue in Linear Workspace', + props: { + team_id: props.team_id(), + issue_id: props.issue_id(), + title: Property.ShortText({ + displayName: 'Title', + required: false, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + state_id: props.status_id(), + labels: props.labels(), + assignee_id: props.assignee_id(), + priority_id: props.priority_id(), + }, + async run({ auth, propsValue }) { + const issueId = propsValue.issue_id!; + const issue: LinearDocument.IssueUpdateInput = { + title: propsValue.title, + description: propsValue.description, + assigneeId: propsValue.assignee_id, + stateId: propsValue.state_id, + priority: propsValue.priority_id, + labelIds: propsValue.labels, + }; + const client = makeClient(auth as string); + const result = await client.updateIssue(issueId, issue); + if (result.success) { + const updatedIssue = await result.issue; + return { + success: result.success, + lastSyncId: result.lastSyncId, + issue: updatedIssue, + }; + } else { + throw new Error(`Unexpected error: ${result}`) + } + }, +}); diff --git a/packages/pieces/community/linear/src/lib/actions/projects/create-project.ts b/packages/pieces/community/linear/src/lib/actions/projects/create-project.ts new file mode 100644 index 0000000..ffdfeaa --- /dev/null +++ b/packages/pieces/community/linear/src/lib/actions/projects/create-project.ts @@ -0,0 +1,63 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { linearAuth } from '../../..'; +import { props } from '../../common/props'; +import { makeClient } from '../../common/client'; +import { LinearDocument } from '@linear/sdk'; + +export const linearCreateProject = createAction({ + auth: linearAuth, + name: 'linear_create_project', + displayName: 'Create Project', + description: 'Create a new project in Linear workspace', + props: { + team_id: props.team_id(), + name: Property.ShortText({ + displayName: 'Project Name', + required: true, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + icon: Property.ShortText({ + displayName: 'Icon', + required: false, + }), + color: Property.ShortText({ + displayName: 'Color', + required: false, + }), + startDate: Property.DateTime({ + displayName: 'Start Date', + required: false, + }), + targetDate: Property.DateTime({ + displayName: 'Target Date', + required: false, + }), + }, + async run({ auth, propsValue }) { + const project: LinearDocument.ProjectCreateInput = { + teamIds: [propsValue.team_id!], + name: propsValue.name, + description: propsValue.description, + icon: propsValue.icon, + color: propsValue.color, + startDate: propsValue.startDate, + targetDate: propsValue.targetDate, + }; + + const client = makeClient(auth as string); + const result = await client.createProject(project); + if (result.success) { + const createdProject = await result.project; + return { + success: result.success, + lastSyncId: result.lastSyncId, + project: createdProject, + }; + } else { + throw new Error(`Unexpected error: ${result}`) + } + }, +}); diff --git a/packages/pieces/community/linear/src/lib/actions/projects/update-project.ts b/packages/pieces/community/linear/src/lib/actions/projects/update-project.ts new file mode 100644 index 0000000..9280d9d --- /dev/null +++ b/packages/pieces/community/linear/src/lib/actions/projects/update-project.ts @@ -0,0 +1,64 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { linearAuth } from '../../..'; +import { props } from '../../common/props'; +import { makeClient } from '../../common/client'; +import { LinearDocument } from '@linear/sdk'; + +export const linearUpdateProject = createAction({ + auth: linearAuth, + name: 'linear_update_project', + displayName: 'Update Project', + description: 'Update a existing project in Linear workspace', + props: { + team_id: props.team_id(), + project_id: props.project_id(), + name: Property.ShortText({ + displayName: 'Project Name', + required: true, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + icon: Property.ShortText({ + displayName: 'Icon', + required: false, + }), + color: Property.ShortText({ + displayName: 'Color', + required: false, + }), + startDate: Property.DateTime({ + displayName: 'Start Date', + required: false, + }), + targetDate: Property.DateTime({ + displayName: 'Target Date', + required: false, + }), + }, + async run({ auth, propsValue }) { + const project: LinearDocument.ProjectUpdateInput = { + teamIds: [propsValue.team_id!], + name: propsValue.name, + description: propsValue.description, + icon: propsValue.icon, + color: propsValue.color, + startDate: propsValue.startDate, + targetDate: propsValue.targetDate, + }; + + const client = makeClient(auth as string); + const result = await client.updateProject(propsValue.project_id!, project); + if (result.success) { + const updatedProject = await result.project; + return { + success: result.success, + lastSyncId: result.lastSyncId, + project: updatedProject, + }; + } else { + throw new Error(`Unexpected error: ${result}`) + } + }, +}); diff --git a/packages/pieces/community/linear/src/lib/actions/raw-graphql-query.ts b/packages/pieces/community/linear/src/lib/actions/raw-graphql-query.ts new file mode 100644 index 0000000..a3cfe79 --- /dev/null +++ b/packages/pieces/community/linear/src/lib/actions/raw-graphql-query.ts @@ -0,0 +1,22 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { linearAuth } from '../..'; +import { makeClient } from '../common/client'; + +export const linearRawGraphqlQuery = createAction({ + name: 'rawGraphqlQuery', + displayName: 'Raw GraphQL query', + description: 'Perform a raw GraphQL query', + auth: linearAuth, + props: { + query: Property.LongText({ displayName: 'Query', required: true }), + variables: Property.Object({ displayName: 'Parameters', required: false }), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth as string); + const result = await client.rawRequest( + propsValue.query, + propsValue.variables + ); + return result; + }, +}); diff --git a/packages/pieces/community/linear/src/lib/common/client.ts b/packages/pieces/community/linear/src/lib/common/client.ts new file mode 100644 index 0000000..a8e82fd --- /dev/null +++ b/packages/pieces/community/linear/src/lib/common/client.ts @@ -0,0 +1,72 @@ +import { LinearClient, LinearDocument } from '@linear/sdk'; + +export class LinearClientWrapper { + private client: LinearClient; + constructor(apiKey: string) { + this.client = new LinearClient({ apiKey: apiKey }); + } + async createIssue(input: LinearDocument.IssueCreateInput) { + return this.client.createIssue(input); + } + async listIssueStates( + variables: LinearDocument.WorkflowStatesQueryVariables + ) { + return this.client.workflowStates(variables); + } + async listIssuePriorities() { + return this.client.issuePriorityValues; + } + async listUsers(variables: LinearDocument.UsersQueryVariables) { + return this.client.users(variables); + } + async listIssueLabels(variables: LinearDocument.IssueLabelsQueryVariables) { + return this.client.issueLabels(variables); + } + async listTeams(variables: LinearDocument.TeamsQueryVariables = {}) { + return this.client.teams(variables); + } + async listIssues(variables: LinearDocument.IssuesQueryVariables = {}) { + return this.client.issues(variables); + } + async updateIssue(issueId: string, input: LinearDocument.IssueUpdateInput) { + return this.client.updateIssue(issueId, input); + } + async createProject(input: LinearDocument.ProjectCreateInput) { + return this.client.createProject(input); + } + async listProjects(variables: LinearDocument.ProjectsQueryVariables = {}) { + return this.client.projects(variables); + } + async updateProject( + projectId: string, + input: LinearDocument.ProjectUpdateInput + ) { + return this.client.updateProject(projectId, input); + } + async createComment(input: LinearDocument.CommentCreateInput) { + return this.client.createComment(input); + } + async createWebhook(input: LinearDocument.WebhookCreateInput) { + return this.client.createWebhook(input); + } + async listWebhooks(variables: LinearDocument.WebhooksQueryVariables = {}) { + return this.client.webhooks(variables); + } + async deleteWebhook(webhookId: string) { + return this.client.deleteWebhook(webhookId); + } + async listTeamsTemplates( + teamId: string, + variables: Omit + ) { + const team = await this.client.team(teamId); + return team.templates(variables); + } + async rawRequest(query: string, variables?: Record) { + return this.client.client.rawRequest(query, variables); + } +} + +export function makeClient(apiKey: string): LinearClientWrapper { + return new LinearClientWrapper(apiKey); +} diff --git a/packages/pieces/community/linear/src/lib/common/props.ts b/packages/pieces/community/linear/src/lib/common/props.ts new file mode 100644 index 0000000..239397a --- /dev/null +++ b/packages/pieces/community/linear/src/lib/common/props.ts @@ -0,0 +1,336 @@ +import { DropdownOption, Property } from '@activepieces/pieces-framework'; +import { makeClient } from './client'; +import { LinearDocument } from '@linear/sdk'; + +export const props = { + team_id: (required = true) => + Property.Dropdown({ + description: + 'The team for which the issue, project or comment will be created', + displayName: 'Team', + required, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + const client = makeClient(auth as string); + const options: DropdownOption[] = []; + + let hasNextPage = false; + let cursor; + + do { + const teams = await client.listTeams({ + orderBy: LinearDocument.PaginationOrderBy.UpdatedAt, + first: 100, + after: cursor, + }); + + for (const team of teams.nodes) { + options.push({ label: team.name, value: team.id }); + } + + hasNextPage = teams.pageInfo.hasNextPage; + cursor = teams.pageInfo.endCursor; + } while (hasNextPage); + + return { + disabled: false, + options, + }; + }, + }), + status_id: (required = false) => + Property.Dropdown({ + description: 'Status of the Issue', + displayName: 'Status', + required, + refreshers: ['auth', 'team_id'], + options: async ({ auth, team_id }) => { + if (!auth || !team_id) { + return { + disabled: true, + placeholder: 'connect your account first and select team', + options: [], + }; + } + const client = makeClient(auth as string); + const options: DropdownOption[] = []; + + let hasNextPage = false; + let cursor; + + do { + const filter: LinearDocument.WorkflowStatesQueryVariables = { + filter: { + team: { + id: { + eq: team_id as string, + }, + }, + }, + first: 100, + after: cursor, + }; + const statusList = await client.listIssueStates(filter); + + for (const status of statusList.nodes) { + options.push({ label: status.name, value: status.id }); + } + + hasNextPage = statusList.pageInfo.hasNextPage; + cursor = statusList.pageInfo.endCursor; + } while (hasNextPage); + + return { + disabled: false, + options, + }; + }, + }), + labels: (required = false) => + Property.MultiSelectDropdown({ + description: 'Labels for the Issue', + displayName: 'Labels', + required, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + const client = makeClient(auth as string); + const options: DropdownOption[] = []; + + let hasNextPage = false; + let cursor; + + do { + const labels = await client.listIssueLabels({ + orderBy: LinearDocument.PaginationOrderBy.UpdatedAt, + first: 100, + after: cursor, + }); + + for (const label of labels.nodes) { + options.push({ label: label.name, value: label.id }); + } + + hasNextPage = labels.pageInfo.hasNextPage; + cursor = labels.pageInfo.endCursor; + } while (hasNextPage); + + return { + disabled: false, + options, + }; + }, + }), + assignee_id: (required = false) => + Property.Dropdown({ + description: 'Assignee of the Issue / Comment', + displayName: 'Assignee', + required, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + const client = makeClient(auth as string); + const options: DropdownOption[] = []; + + let hasNextPage = false; + let cursor; + + do { + const users = await client.listUsers({ + orderBy: LinearDocument.PaginationOrderBy.UpdatedAt, + first: 100, + after: cursor, + }); + + for (const user of users.nodes) { + options.push({ label: user.name, value: user.id }); + } + + hasNextPage = users.pageInfo.hasNextPage; + cursor = users.pageInfo.endCursor; + } while (hasNextPage); + + return { + disabled: false, + options, + }; + }, + }), + priority_id: (required = false) => + Property.Dropdown({ + description: 'Priority of the Issue', + displayName: 'Priority', + required, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + const client = makeClient(auth as string); + const priorities = await client.listIssuePriorities(); + + return { + disabled: false, + options: priorities.map((priority: { label: any; priority: any }) => { + return { + label: priority.label, + value: priority.priority, + }; + }), + }; + }, + }), + issue_id: (required = true) => + Property.Dropdown({ + displayName: 'Issue', + required, + description: 'ID of Linear Issue', + refreshers: ['team_id'], + options: async ({ auth, team_id }) => { + if (!auth || !team_id) { + return { + disabled: true, + placeholder: 'connect your account first and select team', + options: [], + }; + } + const client = makeClient(auth as string); + const filter: LinearDocument.IssuesQueryVariables = { + first: 50, + filter: { + team: { + id: { + eq: team_id as string, + }, + }, + }, + orderBy: LinearDocument.PaginationOrderBy.UpdatedAt, + }; + const issues = await client.listIssues(filter); + return { + disabled: false, + options: issues.nodes.map((issue: { title: any; id: any }) => { + return { + label: issue.title, + value: issue.id, + }; + }), + }; + }, + }), + + project_id: (required = true) => + Property.Dropdown({ + displayName: 'Project', + required, + description: 'ID of Linear Project', + refreshers: ['team_id'], + options: async ({ auth, team_id }) => { + if (!auth || !team_id) { + return { + disabled: true, + placeholder: 'connect your account first and select team', + options: [], + }; + } + const client = makeClient(auth as string); + const options: DropdownOption[] = []; + + let hasNextPage = false; + let cursor; + + do { + const projects = await client.listProjects({ + orderBy: LinearDocument.PaginationOrderBy.UpdatedAt, + first: 100, + after: cursor, + }); + + for (const project of projects.nodes) { + options.push({ label: project.name, value: project.id }); + } + + hasNextPage = projects.pageInfo.hasNextPage; + cursor = projects.pageInfo.endCursor; + } while (hasNextPage); + + return { + disabled: false, + options, + }; + }, + }), + template_id: (required = false) => + Property.Dropdown({ + displayName: 'Template', + required, + description: 'ID of Template', + refreshers: ['auth', 'team_id'], + options: async ({ auth, team_id }) => { + if (!auth || !team_id) { + return { + disabled: true, + placeholder: 'connect your account first and select team', + options: [], + }; + } + const client = makeClient(auth as string); + const options: DropdownOption[] = []; + + let hasNextPage = false; + let cursor; + + do { + const filter: Omit< + LinearDocument.Team_TemplatesQueryVariables, + 'id' + > = { + first: 100, + after: cursor, + orderBy: LinearDocument.PaginationOrderBy.UpdatedAt, + }; + const templatesConnection = await client.listTeamsTemplates( + team_id as string, + filter + ); + + const templates = await templatesConnection.nodes; + + for (const template of templates) { + options.push({ label: template.name, value: template.id }); + } + + hasNextPage = templatesConnection.pageInfo.hasNextPage; + cursor = templatesConnection.pageInfo.endCursor; + } while (hasNextPage); + + return { + disabled: false, + options, + }; + }, + }), +}; diff --git a/packages/pieces/community/linear/src/lib/triggers/new-issue.ts b/packages/pieces/community/linear/src/lib/triggers/new-issue.ts new file mode 100644 index 0000000..ba26c9a --- /dev/null +++ b/packages/pieces/community/linear/src/lib/triggers/new-issue.ts @@ -0,0 +1,101 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { linearAuth } from '../..'; +import { makeClient } from '../common/client'; +import { props } from '../common/props'; + +export const linearNewIssue = createTrigger({ + auth: linearAuth, + name: 'new_issue', + displayName: 'New Issue', + description: 'Triggers when Linear receives a new issue', + props: { + team_id: props.team_id(), + }, + sampleData: { + // Sample data structure based on Linear's webhook payload for issues + action: 'create', + data: { + id: 'issue_1', + identifier: '1', + title: 'Test issue', + description: 'This is a test issue', + priority: 'priority_1', + priorityLabel: 'High', + state: 'state_1', + stateLabel: 'In Progress', + team: { + id: 'team_1', + name: 'Test team', + key: 'test-team', + description: 'This is a test team', + archived: false, + createdAt: '2023-09-05T12:00:00.000Z', + updatedAt: '2023-09-05T12:00:00.000Z', + }, + creator: { + id: 'user_1', + name: 'Test user', + email: 'test@gmail.com', + avatarUrl: 'https://avatars.githubusercontent.com/u/1?v=4', + createdAt: '2023-09-05T12:00:00.000Z', + updatedAt: '2023-09-05T12:00:00.000Z', + }, + assignee: { + id: 'user_1', + name: 'Test user', + email: 'test@gmail.com', + avatarUrl: 'https://avatars.githubusercontent.com/u/1?v=4', + createdAt: '2023-09-05T12:00:00.000Z', + updatedAt: '2023-09-05T12:00:00.000Z', + }, + labels: [ + { + id: 'label_1', + name: 'Test label', + color: '#000000', + createdAt: '2023-09-05T12:00:00.000Z', + updatedAt: '2023-09-05T12:00:00.000Z', + }, + ], + createdAt: '2023-09-05T12:00:00.000Z', + updatedAt: '2023-09-05T12:00:00.000Z', + }, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const client = makeClient(context.auth as string); + const webhook = await client.createWebhook({ + label: 'ActivePieces New Issue', + url: context.webhookUrl, + teamId: context.propsValue['team_id'], + resourceTypes: ['Issue'], + }); + if (webhook.success && webhook.webhook) { + await context.store?.put('_new_issue_trigger', { + webhookId: (await webhook.webhook).id, + }); + } else { + console.error('Failed to create the webhook'); + } + }, + async onDisable(context) { + const client = makeClient(context.auth as string); + const response = await context.store?.get( + '_new_issue_trigger' + ); + if (response && response.webhookId) { + await client.deleteWebhook(response.webhookId); + } + }, + async run(context) { + const body = context.payload.body as { action: string; data: unknown }; + if (body.action === 'create') { + return [body.data]; + } + return []; + }, +}); + +interface WebhookInformation { + webhookId: string; +} diff --git a/packages/pieces/community/linear/src/lib/triggers/removed-issue.ts b/packages/pieces/community/linear/src/lib/triggers/removed-issue.ts new file mode 100644 index 0000000..5e3c8b1 --- /dev/null +++ b/packages/pieces/community/linear/src/lib/triggers/removed-issue.ts @@ -0,0 +1,101 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { linearAuth } from '../..'; +import { makeClient } from '../common/client'; +import { props } from '../common/props'; + +export const linearRemovedIssue = createTrigger({ + auth: linearAuth, + name: 'removed_issue', + displayName: 'Removed Issue', + description: 'Triggers when an existing Linear issue is removed', + props: { + team_id: props.team_id() + }, + sampleData: { + // Sample data structure based on Linear's webhook payload for issues + action: 'remove', + data: { + id: 'issue_1', + identifier: '1', + title: 'Test issue', + description: 'This is a test issue', + priority: 'priority_1', + priorityLabel: 'High', + state: 'state_1', + stateLabel: 'In Progress', + team: { + id: 'team_1', + name: 'Test team', + key: 'test-team', + description: 'This is a test team', + archived: false, + createdAt: '2023-09-05T12:00:00.000Z', + updatedAt: '2023-09-05T12:00:00.000Z', + }, + creator: { + id: 'user_1', + name: 'Test user', + email: 'test@gmail.com', + avatarUrl: 'https://avatars.githubusercontent.com/u/1?v=4', + createdAt: '2023-09-05T12:00:00.000Z', + updatedAt: '2023-09-05T12:00:00.000Z', + }, + assignee: { + id: 'user_1', + name: 'Test user', + email: 'test@gmail.com', + avatarUrl: 'https://avatars.githubusercontent.com/u/1?v=4', + createdAt: '2023-09-05T12:00:00.000Z', + updatedAt: '2023-09-05T12:00:00.000Z', + }, + labels: [ + { + id: 'label_1', + name: 'Test label', + color: '#000000', + createdAt: '2023-09-05T12:00:00.000Z', + updatedAt: '2023-09-05T12:00:00.000Z', + }, + ], + createdAt: '2023-09-05T12:00:00.000Z', + updatedAt: '2023-09-05T12:00:00.000Z', + }, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const client = makeClient(context.auth as string); + const webhook = await client.createWebhook({ + label: 'ActivePieces Updated Issue', + url: context.webhookUrl, + teamId: context.propsValue['team_id'], + resourceTypes: ['Issue'] + }); + if (webhook.success && webhook.webhook) { + await context.store?.put('_removed_issue_trigger', { + webhookId: (await webhook.webhook).id + }); + } else { + console.error('Failed to create the webhook'); + } + }, + async onDisable(context) { + const client = makeClient(context.auth as string); + const response = await context.store?.get( + '_removed_issue_trigger' + ); + if (response && response.webhookId) { + await client.deleteWebhook(response.webhookId); + } + }, + async run(context) { + const body = context.payload.body as { action: string; data: unknown}; + if (body.action === 'remove') { + return [body.data]; + } + return []; + } +}); + +interface WebhookInformation { + webhookId: string; +} diff --git a/packages/pieces/community/linear/src/lib/triggers/updated-issue.ts b/packages/pieces/community/linear/src/lib/triggers/updated-issue.ts new file mode 100644 index 0000000..65effe5 --- /dev/null +++ b/packages/pieces/community/linear/src/lib/triggers/updated-issue.ts @@ -0,0 +1,124 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { linearAuth } from '../..'; +import { makeClient } from '../common/client'; +import { props } from '../common/props'; + +export const linearUpdatedIssue = createTrigger({ + auth: linearAuth, + name: 'updated_issue', + displayName: 'Updated Issue', + description: 'Triggers when an existing Linear issue is updated', + props: { + team_id: props.team_id(false) + }, + sampleData: { + // Sample data structure based on Linear's webhook payload for issues + action: 'update', + data: { + id: 'issue_1', + identifier: '1', + title: 'Test issue updated', + description: 'This is a test issue (updated)', + priority: 'priority_1', + priorityLabel: 'High', + state: 'state_2', + stateLabel: 'In Review', + team: { + id: 'team_2', + name: 'Test team', + key: 'test-team', + description: 'This is another test team', + archived: false, + createdAt: '2023-09-05T12:00:00.000Z', + updatedAt: '2023-09-06T12:00:00.000Z' + }, + creator: { + id: 'user_1', + name: 'Test user', + email: 'test@gmail.com', + avatarUrl: 'https://avatars.githubusercontent.com/u/1?v=4', + createdAt: '2023-09-05T12:00:00.000Z', + updatedAt: '2023-09-06T12:00:00.000Z' + }, + assignee: { + id: 'user_1', + name: 'Test user', + email: 'test@gmail.com', + avatarUrl: 'https://avatars.githubusercontent.com/u/1?v=4', + createdAt: '2023-09-05T12:00:00.000Z', + updatedAt: '2023-09-05T12:00:00.000Z' + }, + labels: [ + { + id: 'label_1', + name: 'Test label', + color: '#000000', + createdAt: '2023-09-05T12:00:00.000Z', + updatedAt: '2023-09-05T12:00:00.000Z' + }, + { + id: 'label_1', + name: 'Test label 2', + color: '#000000', + createdAt: '2023-09-05T12:00:00.000Z', + updatedAt: '2023-09-06T12:00:00.000Z' + } + ], + createdAt: '2023-09-05T12:00:00.000Z', + updatedAt: '2023-09-06T12:00:00.000Z' + }, + updatedFrom: { + 'updatedAt': '2023-09-06T12:00:00.000Z', + 'sortOrder': -14.61, + 'startedAt': null, + 'stateId': 'state_1' + } + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const client = makeClient(context.auth as string); + + // Create webhook configuration + const webhookConfig: any = { + label: 'ActivePieces Updated Issue', + url: context.webhookUrl, + allPublicTeams: true, + resourceTypes: ['Issue'] + }; + + // Only add teamId if it's provided + if (context.propsValue['team_id']) { + webhookConfig.teamId = context.propsValue['team_id']; + } + + const webhook = await client.createWebhook(webhookConfig); + + if (webhook.success && webhook.webhook) { + await context.store?.put('_updated_issue_trigger', { + webhookId: (await webhook.webhook).id + }); + } else { + console.error('Failed to create the webhook'); + } + }, + async onDisable(context) { + const client = makeClient(context.auth as string); + const response = await context.store?.get( + '_updated_issue_trigger' + ); + if (response && response.webhookId) { + await client.deleteWebhook(response.webhookId); + } + }, + async run(context) { + const body = context.payload.body as { action: string; data: unknown}; + if (body.action === 'update') { + return [body.data]; + } + return []; + } +}); + +interface WebhookInformation { + webhookId: string; +} diff --git a/packages/pieces/community/linear/tsconfig.json b/packages/pieces/community/linear/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/linear/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/linear/tsconfig.lib.json b/packages/pieces/community/linear/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/linear/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/linka/.eslintrc.json b/packages/pieces/community/linka/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/linka/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/linka/README.md b/packages/pieces/community/linka/README.md new file mode 100644 index 0000000..7a7e69a --- /dev/null +++ b/packages/pieces/community/linka/README.md @@ -0,0 +1,7 @@ +# pieces-linka + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-linka` to build the library. diff --git a/packages/pieces/community/linka/package.json b/packages/pieces/community/linka/package.json new file mode 100644 index 0000000..e642f74 --- /dev/null +++ b/packages/pieces/community/linka/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-linka", + "version": "0.0.1" +} diff --git a/packages/pieces/community/linka/project.json b/packages/pieces/community/linka/project.json new file mode 100644 index 0000000..519721e --- /dev/null +++ b/packages/pieces/community/linka/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-linka", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/linka/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/linka", + "tsConfig": "packages/pieces/community/linka/tsconfig.lib.json", + "packageJson": "packages/pieces/community/linka/package.json", + "main": "packages/pieces/community/linka/src/index.ts", + "assets": [ + "packages/pieces/community/linka/*.md", + { + "input": "packages/pieces/community/linka/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/linka/src/index.ts b/packages/pieces/community/linka/src/index.ts new file mode 100644 index 0000000..fd43cc2 --- /dev/null +++ b/packages/pieces/community/linka/src/index.ts @@ -0,0 +1,72 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { addOrUpdateContactExtended } from './lib/actions/add-or-update-contact-extended'; +import { addOrUpdateContact } from './lib/actions/add-or-update-contact'; +import { addOrUpdateSubscription } from './lib/actions/add-or-update-subscription'; +import { createInvoice } from './lib/actions/create-invoice'; +import { createProduct } from './lib/actions/create-product'; +import { getContactDetails } from './lib/actions/get-contact-details'; +import { newLead } from './lib/triggers/new-lead'; +import { newPayment } from './lib/triggers/new-payment'; +import { newSubscription } from './lib/triggers/new-subscription'; +import { PieceCategory } from '@activepieces/shared'; + +const markdownDescription = ` + Follow these instructions to get your Linka API Key: + + 1. Visit the following website: https://crm.linka.ai/ or the beta website: https://beta.linka.ai/ + 2. Once on the website, locate and click on the admin to obtain your Linka API Key. +`; + +export const linkaAuth = PieceAuth.CustomAuth({ + description: markdownDescription, + required: true, + props: { + base_url: Property.StaticDropdown({ + displayName: 'Base URL', + description: 'Select the base environment URL', + required: true, + options: { + disabled: false, + options: [ + { + label: 'Linka Live (crm.linka.ai)', + value: 'https://crm.linka.ai/', + }, + { + label: 'Linka Beta (beta.linka.ai)', + value: 'https://beta.linka.ai/', + }, + ], + }, + }), + api_key: PieceAuth.SecretText({ + displayName: 'Secret API Key', + description: 'Enter the API Key', + required: true, + }), + }, +}); + +export const linka = createPiece({ + displayName: 'Linka', + description: + 'Linka white-label B2B marketplace platform powers communities and digital storefronts', + auth: linkaAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/linka.png', + authors: ['Trayshmhirk', 'OmarSayed'], + categories: [PieceCategory.SALES_AND_CRM], + actions: [ + addOrUpdateContact, + addOrUpdateContactExtended, + addOrUpdateSubscription, + createInvoice, + createProduct, + getContactDetails, + ], + triggers: [newLead, newPayment, newSubscription], +}); diff --git a/packages/pieces/community/linka/src/lib/actions/add-or-update-contact-extended.ts b/packages/pieces/community/linka/src/lib/actions/add-or-update-contact-extended.ts new file mode 100644 index 0000000..3214fda --- /dev/null +++ b/packages/pieces/community/linka/src/lib/actions/add-or-update-contact-extended.ts @@ -0,0 +1,1292 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { linkaAuth } from '../../'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const addOrUpdateContactExtended = createAction({ + name: 'addOrUpdateContactExtended', + displayName: 'Add or Update Contact (Extended)', + description: 'Adds or updates a contact (extended version)', + auth: linkaAuth, + props: { + matchExisting: Property.StaticDropdown({ + displayName: 'Match Existing Contact', + description: + 'If "Yes", will try to find an existing record using Email and Full Name and update it.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + overrideLists: Property.StaticDropdown({ + displayName: 'Override Lists', + description: + 'If "Yes", will override lists of contact details in update mode instead of merging them - lists, tags, emails, phones, links, addresses, photos.', + required: false, + defaultValue: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + ignoreInvalidValues: Property.StaticDropdown({ + displayName: 'Ignore Invalid Values', + description: + 'If "Yes", will save the record even if there are some validation errors.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + contactId: Property.Number({ + displayName: 'Contact ID', + description: 'Sperse Contact ID. Will be used for looking a client', + defaultValue: 0, + required: false, + }), + contactXref: Property.ShortText({ + displayName: 'Contact XREF', + description: + 'This is string external reference that can be passed during creation and then if sent again it will update the record.', + required: false, + }), + importType: Property.StaticDropdown({ + displayName: 'Contact Type', + required: true, + defaultValue: 'Lead', + options: { + disabled: false, + options: [ + { + label: 'Lead', + value: 'Lead', + }, + { + label: 'Client', + value: 'Client', + }, + { + label: 'Partner', + value: 'Partner', + }, + ], + }, + }), + // user info + createUser: Property.StaticDropdown({ + displayName: 'Create User', + description: + 'If "Yes" then User will be created. Personal email will be used as User Name.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + sendWelcomeEmail: Property.StaticDropdown({ + displayName: 'Send Welcome Email', + description: + 'If "Yes" then Welcome Email will be sent to the newly created user.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + userPassword: Property.ShortText({ + displayName: 'User Password', + description: + 'If password is not passed then it will be automatically generated.', + required: false, + }), + // fullname + namePrefix: Property.ShortText({ + displayName: 'Prefix', + description: 'The title used to address the contact.', + required: false, + }), + firstName: Property.ShortText({ + displayName: 'First Name', + description: 'Required if Last Name and Company Name fields are empty.', + required: true, + }), + middleName: Property.ShortText({ + displayName: 'Middle Name', + description: "The contact's middle name.", + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + description: 'Required if First Name and Company Name fields are empty.', + required: true, + }), + nickName: Property.ShortText({ + displayName: 'Nick Name', + description: "The contact's nick name.", + required: false, + }), + nameSuffix: Property.ShortText({ + displayName: 'Suffix', + description: 'Additional information about the contact e.g PhD.', + required: false, + }), + // personal info + gender: Property.StaticDropdown({ + displayName: 'Gender', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Male', + value: 'Male', + }, + { + label: 'Female', + value: 'Female', + }, + ], + }, + }), + dob: Property.ShortText({ + displayName: 'Date of Birth', + description: 'Valid date format YYYY-MM-DD or MM-DD-YYYY.', + required: false, + }), + bankCode: Property.ShortText({ + displayName: 'Bank Code', + description: "The contact's 4-letter personality code.", + required: false, + }), + ssn: Property.ShortText({ + displayName: 'SSN', + description: "The contact's social security number.", + required: false, + }), + drivingLicense: Property.ShortText({ + displayName: 'Driving License', + required: false, + }), + drivingLicenseState: Property.ShortText({ + displayName: 'Driving License State', + required: false, + }), + isActiveMilitaryDuty: Property.StaticDropdown({ + displayName: 'Is Active Military Duty', + description: + 'Possible values are: Yes, No. If nothing is chosen that means "Unknown".', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + isUSCitizen: Property.StaticDropdown({ + displayName: 'Is US Citizen', + description: + 'Possible values are: Yes, No. If nothing is chosen that means "Unknown".', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + preferredToD: Property.StaticDropdown({ + displayName: 'Preferred Time of Day', + description: 'Preferred Time of Day to contact with Client', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Morning', + value: 'Morning', + }, + { + label: 'Afternoon', + value: 'Afternoon', + }, + { + label: 'Evening', + value: 'Evening', + }, + { + label: 'Anytime', + value: 'Anytime', + }, + ], + }, + }), + personAffiliateCode: Property.ShortText({ + displayName: 'Person Affiliate Code', + description: + 'Affiliate Code is used for the current person detection as a source of new leads. Alphanumeric characters, underscore and hyphen are allowed.', + required: false, + }), + // email + email1: Property.LongText({ + displayName: 'Personal Email', + description: "The contact's personal email.", + required: false, + }), + email2: Property.LongText({ + displayName: 'Alternative Personal email', + description: "The contact's alternative personal email.", + required: false, + }), + email3: Property.LongText({ + displayName: 'Other Personal email', + description: "The contact's additional email.", + required: false, + }), + workEmail1: Property.LongText({ + displayName: 'Work Email', + description: "The contact's work email.", + required: false, + }), + workEmail2: Property.LongText({ + displayName: 'Alternative Work Email', + description: "The contact's alternative work email.", + required: false, + }), + workEmail3: Property.LongText({ + displayName: 'Other Work Email', + description: "The contact's other work email.", + required: false, + }), + // phone + mobilePhone: Property.ShortText({ + displayName: 'Mobile Phone', + description: "The contact's primary phone number.", + required: false, + }), + mobilePhoneExt: Property.ShortText({ + displayName: 'Mobile Phone Ext', + description: "The contact's primary phone number extension.", + required: false, + }), + homePhone: Property.ShortText({ + displayName: 'Home Phone', + description: "The contact's home phone number.", + required: false, + }), + homePhoneExt: Property.ShortText({ + displayName: 'Home Phone Ext', + description: "The contact's home phone number extension.", + required: false, + }), + workPhone1: Property.ShortText({ + displayName: 'Work Phone', + description: "The contact's work phone number.", + required: false, + }), + workPhone1Ext: Property.ShortText({ + displayName: 'Work Phone Ext', + description: "The contact's work phone number extension.", + required: false, + }), + workPhone2: Property.ShortText({ + displayName: 'Work Phone 2', + description: "The contact's alternative work phone number.", + required: false, + }), + workPhone2Ext: Property.ShortText({ + displayName: 'Work Phone 2 Ext', + description: "The contact's alternative work phone number extension.", + required: false, + }), + // home address + home_street: Property.LongText({ + displayName: 'Home Street', + description: + "The contact's full street address (can include apartment or unit number).", + required: false, + }), + home_addressLine2: Property.LongText({ + displayName: 'Address 2', + required: false, + }), + home_city: Property.LongText({ + displayName: 'City', + description: "The contact's city of residence.", + required: false, + }), + home_stateName: Property.ShortText({ + displayName: 'State Name', + description: "The contact's state of residence.", + required: false, + }), + home_stateId: Property.ShortText({ + displayName: 'State Code', + description: "The contact's state code.", + required: false, + }), + home_zip: Property.ShortText({ + displayName: 'ZIP Code', + description: "The contact's zip/postal code.", + required: false, + }), + home_countryName: Property.ShortText({ + displayName: 'Country Name', + description: "The contact's country of residence.", + required: false, + }), + home_countryId: Property.ShortText({ + displayName: 'Country Code', + description: "The contact's country code.", + required: false, + }), + // work address + work_street: Property.LongText({ + displayName: 'Work Street', + description: "The contact's work address.", + required: false, + }), + work_addressLine2: Property.LongText({ + displayName: 'Address 2', + required: false, + }), + work_city: Property.LongText({ + displayName: 'City', + description: "The contact's work city.", + required: false, + }), + work_stateName: Property.ShortText({ + displayName: 'State Name', + description: "The contact's work state.", + required: false, + }), + work_stateId: Property.ShortText({ + displayName: 'State Code', + description: "The contact's work state code.", + required: false, + }), + work_zip: Property.ShortText({ + displayName: 'ZIP Code', + description: "The contact's work zip code.", + required: false, + }), + work_countryName: Property.ShortText({ + displayName: 'Country Name', + description: "The contact's work country.", + required: false, + }), + work_countryId: Property.ShortText({ + displayName: 'Country Code', + description: "The contact's work country code.", + required: false, + }), + // links + webSiteUrl: Property.LongText({ + displayName: 'Website', + description: "The contact's company website URL.", + required: false, + }), + linkedInUrl: Property.LongText({ + displayName: 'LinkedIn', + description: "The contact's LinkedIn profile id.", + required: false, + }), + photoUrl: Property.LongText({ + displayName: 'Photo URL', + description: "The contact's person photo URL.", + required: false, + }), + facebookUrl: Property.LongText({ + displayName: 'Facebook', + description: "The contact's Facebook profile id.", + required: false, + }), + instagramUrl: Property.LongText({ + displayName: 'Instagram', + description: "The contact's Instagram profile id.", + required: false, + }), + twitterUrl: Property.LongText({ + displayName: 'Twitter', + description: "The contact's Twitter profile id.", + required: false, + }), + googlePlusUrl: Property.LongText({ + displayName: 'Google Reviews', + description: "The contact's Google reviews.", + required: false, + }), + angelListUrl: Property.LongText({ + displayName: 'AngelList', + description: "The contact's AngelList profile id.", + required: false, + }), + zoomUrl: Property.LongText({ + displayName: 'Zoom', + description: "The contact's Zoom id.", + required: false, + }), + // content + experience: Property.LongText({ + displayName: 'Content', + description: "The contact's professional experience.", + required: false, + }), + profileSummary: Property.LongText({ + displayName: 'Profile Summary', + description: "The contact's profile summary.", + required: false, + }), + notes: Property.LongText({ + displayName: 'Notes', + description: 'Additional notes about the contact', + required: false, + }), + followUpDate: Property.ShortText({ + displayName: 'Follow Up Date', + description: + 'Valid date format YYYY-MM-DD HH:MM:SS. If date is defined then new Follow Up Task will be created for this contact', + required: false, + }), + assignedUser: Property.ShortText({ + displayName: 'Assigned User', + description: + 'Preferably, Sperse User Email should be passed as it is unique within Sperse account but User Name can be also passed', + required: false, + }), + leadDealAmount: Property.Number({ + displayName: 'Deal Amount', + description: 'Estimated deal/opportunity amount.', + required: false, + }), + // custom fields + customField1: Property.LongText({ + displayName: 'Custom Field 1', + description: 'Additional custom data for the contact record.', + required: false, + }), + customField2: Property.LongText({ + displayName: 'Custom Field 2', + required: false, + }), + customField3: Property.LongText({ + displayName: 'Custom Field 3', + required: false, + }), + customField4: Property.LongText({ + displayName: 'Custom Field 4', + required: false, + }), + customField5: Property.LongText({ + displayName: 'Custom Field 5', + required: false, + }), + // business info + companyName: Property.LongText({ + displayName: 'Company Name', + description: + "Name of the contact's company (This field is mandatory if the First Name and Last Name fields are empty).", + required: false, + }), + organizationType: Property.StaticDropdown({ + displayName: 'Organization Type', + required: false, + options: { + disabled: false, + options: [ + { + label: 'LLP', + value: 'LLP', + }, + { + label: 'LLC', + value: 'LLC', + }, + { + label: 'Inc', + value: 'Inc', + }, + { + label: 'LP', + value: 'LP', + }, + { + label: 'Partnership', + value: 'Partnership', + }, + { + label: 'Sole Proprietership', + value: 'Sole Proprietership', + }, + { + label: 'Trust', + value: 'Trust', + }, + { + label: 'LLLP', + value: 'LLLP', + }, + { + label: 'Other', + value: 'Other', + }, + ], + }, + }), + isEmployed: Property.StaticDropdown({ + displayName: 'Is Employed', + description: 'Pass yes if the client is employed in this Organization.', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + jobTitle: Property.ShortText({ + displayName: 'Job Title', + description: "The contact's job title.", + required: false, + }), + employmentStartDate: Property.ShortText({ + displayName: 'Employment Start Date', + description: 'Valid date format YYYY-MM-DD or MM-DD-YYYY', + required: false, + }), + employeeCount: Property.ShortText({ + displayName: 'Employee Count', + required: false, + }), + dateFounded: Property.ShortText({ + displayName: 'Date Founded', + description: 'Valid date format YYYY-MM-DD or MM-DD-YYYY', + required: false, + }), + ein: Property.LongText({ + displayName: 'EIN', + required: false, + }), + annualRevenue: Property.Number({ + displayName: 'Annual Revenue', + required: false, + }), + industry: Property.ShortText({ + displayName: 'Industry', + description: "The company's industry.", + required: false, + }), + companyPhone: Property.ShortText({ + displayName: 'Company Phone', + required: false, + }), + companyPhoneExt: Property.ShortText({ + displayName: 'Company Phone Extension', + required: false, + }), + companyFaxNumber: Property.LongText({ + displayName: 'Company Fax Number', + required: false, + }), + companyEmail: Property.LongText({ + displayName: 'Company Email', + required: false, + }), + companyWebSiteUrl: Property.LongText({ + displayName: 'Company Website', + required: false, + }), + companyFacebookUrl: Property.LongText({ + displayName: 'Company Facebook', + required: false, + }), + companyLinkedInUrl: Property.LongText({ + displayName: 'Company LinkedIn', + required: false, + }), + companyInstagramUrl: Property.LongText({ + displayName: 'Company Instagram', + required: false, + }), + companyTwitterUrl: Property.LongText({ + displayName: 'Company Twitter', + required: false, + }), + companyGooglePlusUrl: Property.LongText({ + displayName: 'Company Google Reviews', + required: false, + }), + companyCrunchbaseUrl: Property.LongText({ + displayName: 'Company Crunchbase', + required: false, + }), + companyBBBUrl: Property.LongText({ + displayName: 'Company BBB URL', + required: false, + }), + companyZoomUrl: Property.LongText({ + displayName: 'Company Zoom', + required: false, + }), + companyCalendlyUrl: Property.LongText({ + displayName: 'Company Calendly', + required: false, + }), + companyLogoUrl: Property.LongText({ + displayName: 'Company Logo URL', + required: false, + }), + companyAffiliateCode: Property.ShortText({ + displayName: 'Company Affiliate Code', + required: false, + }), + // company full Address + company_street: Property.LongText({ + displayName: 'Company Street', + required: false, + }), + company_addressLine2: Property.LongText({ + displayName: 'Address 2', + required: false, + }), + company_city: Property.ShortText({ + displayName: 'City', + required: false, + }), + company_stateName: Property.ShortText({ + displayName: 'State Name', + required: false, + }), + company_stateId: Property.ShortText({ + displayName: 'State Code', + required: false, + }), + company_zip: Property.ShortText({ + displayName: 'Company Zip Code', + required: false, + }), + company_countryName: Property.LongText({ + displayName: 'Company Country Name', + required: false, + }), + company_countryId: Property.ShortText({ + displayName: 'Company Country Code', + required: false, + }), + // tracking info + leadSource: Property.ShortText({ + displayName: 'Source Code', + description: + 'The first known source the contact used to find your website. You can set this automatically and update manually later.', + required: false, + }), + channelId: Property.ShortText({ + displayName: 'Channel Code', + description: 'The channel/medium the contact used to find your website.', + required: false, + }), + affiliateCode: Property.ShortText({ + displayName: 'Affiliate Code', + description: + 'The affiliate/referer partner through which the contact signed up.', + required: false, + }), + campaignId: Property.ShortText({ + displayName: 'Campaign ID', + required: false, + }), + gclId: Property.ShortText({ + displayName: 'Google Click ID', + required: false, + }), + refererURL: Property.LongText({ + displayName: 'Referer URL', + description: + 'The webpage where the contact clicked a link that sent them to your website.', + required: false, + }), + applicantId: Property.ShortText({ + displayName: 'Applicant ID', + required: false, + }), + applicationId: Property.ShortText({ + displayName: 'Application ID', + required: false, + }), + ipAddress: Property.LongText({ + displayName: 'IP Address', + required: false, + }), + userAgent: Property.ShortText({ + displayName: 'User Agent', + required: false, + }), + siteId: Property.ShortText({ + displayName: 'Site ID', + required: false, + }), + siteUrl: Property.LongText({ + displayName: 'Site URL', + required: false, + }), + dateCreated: Property.ShortText({ + displayName: 'Date Created', + description: 'Valid date format YYYY-MM-DD HH:MM:SS', + required: false, + }), + entryUrl: Property.LongText({ + displayName: 'Entry URL', + description: + 'The first page of visit through which the contact visited your website.', + required: false, + }), + // contact Attributes + leadStageName: Property.ShortText({ + displayName: 'Lead Stage Name', + description: + 'The first page of visit through which the contact visited your website.', + required: false, + }), + star: Property.ShortText({ + displayName: 'Star', + description: + 'String Value. Supports the following items: Yellow, Blue, Green, Purple, Red. Other values will be skipped.', + required: false, + }), + rating: Property.ShortText({ + displayName: 'Rating', + description: 'This is the static array from 1 to 10', + required: false, + }), + // UtmParameters + utmSource: Property.LongText({ + displayName: 'UTM Source', + required: false, + }), + utmMedium: Property.LongText({ + displayName: 'UTM Medium', + required: false, + }), + utmCampaign: Property.LongText({ + displayName: 'UTM Campaign', + required: false, + }), + utmTerm: Property.LongText({ + displayName: 'UTM Term', + required: false, + }), + utmContent: Property.LongText({ + displayName: 'UTM Content', + required: false, + }), + utmKeyword: Property.LongText({ + displayName: 'UTM Keyword', + required: false, + }), + utmAdGroup: Property.LongText({ + displayName: 'UTM AdGroup', + required: false, + }), + utmName: Property.LongText({ + displayName: 'UTM Name', + required: false, + }), + // requestCustomInfo + requestCustomField1: Property.LongText({ + displayName: 'Request Custom Field 1', + required: false, + }), + requestCustomField2: Property.LongText({ + displayName: 'Request Custom Field 2', + required: false, + }), + requestCustomField3: Property.LongText({ + displayName: 'Request Custom Field 3', + required: false, + }), + requestCustomField4: Property.LongText({ + displayName: 'Request Custom Field 4', + required: false, + }), + requestCustomField5: Property.LongText({ + displayName: 'Request Custom Field 5', + required: false, + }), + // subscription1 + sub1_productCode: Property.ShortText({ + displayName: 'Product Code', + description: + 'Product Code from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 1.', + required: false, + }), + sub1_paymentPeriodType: Property.StaticDropdown({ + displayName: 'Product Payment Period Type', + description: + 'The chosen Period Type has to be set for the Product on Sperse side', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + ], + }, + }), + sub1_systemType: Property.ShortText({ + displayName: 'System Type', + description: + 'Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 1.', + required: false, + }), + sub1_code: Property.ShortText({ + displayName: 'Code', + description: + 'Code of subscription service from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 1.', + required: false, + }), + sub1_name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + sub1_level: Property.ShortText({ + displayName: 'Level', + description: 'Code of subscription service level from the Sperse CRM.', + required: false, + }), + sub1_startDate: Property.ShortText({ + displayName: 'Start Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub1_endDate: Property.ShortText({ + displayName: 'End Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub1_amount: Property.Number({ + displayName: 'Amount', + required: false, + }), + // subscription2 + sub2_productCode: Property.ShortText({ + displayName: 'Product Code', + description: + 'Product Code from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 2.', + required: false, + }), + sub2_paymentPeriodType: Property.StaticDropdown({ + displayName: 'Product Payment Period Type', + description: + 'The chosen Period Type has to be set for the Product on Sperse side', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + ], + }, + }), + sub2_systemType: Property.ShortText({ + displayName: 'System Type', + description: + 'Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 2.', + required: false, + }), + sub2_code: Property.ShortText({ + displayName: 'Code', + description: + 'Code of subscription service from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 2.', + required: false, + }), + sub2_name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + sub2_level: Property.ShortText({ + displayName: 'Level', + description: 'Code of subscription service level from the Sperse CRM.', + required: false, + }), + sub2_startDate: Property.ShortText({ + displayName: 'Start Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub2_endDate: Property.ShortText({ + displayName: 'End Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub2_amount: Property.Number({ + displayName: 'Amount', + required: false, + }), + // subscription3 + sub3_productCode: Property.ShortText({ + displayName: 'Product Code', + description: + 'Product Code from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 3.', + required: false, + }), + sub3_paymentPeriodType: Property.StaticDropdown({ + displayName: 'Product Payment Period Type', + description: + 'The chosen Period Type has to be set for the Product on Sperse side', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + ], + }, + }), + sub3_systemType: Property.ShortText({ + displayName: 'System Type', + description: + 'Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 3.', + required: false, + }), + sub3_code: Property.ShortText({ + displayName: 'Code', + description: + 'Code of subscription service from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 3.', + required: false, + }), + sub3_name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + sub3_level: Property.ShortText({ + displayName: 'Level', + description: 'Code of subscription service level from the Sperse CRM.', + required: false, + }), + sub3_startDate: Property.ShortText({ + displayName: 'Start Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub3_endDate: Property.ShortText({ + displayName: 'End Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub3_amount: Property.Number({ + displayName: 'Amount', + required: false, + }), + }, + async run(context) { + const customer = { + importType: context.propsValue.importType, + ignoreInvalidValues: context.propsValue.ignoreInvalidValues, + matchExisting: context.propsValue.matchExisting, + overrideLists: context.propsValue.overrideLists, + createUser: context.propsValue.createUser, + sendWelcomeEmail: context.propsValue.sendWelcomeEmail, + contactId: context.propsValue.contactId, + contactXref: context.propsValue.contactXref, + userPassword: context.propsValue.userPassword, + personalInfo: { + fullName: { + namePrefix: context.propsValue.namePrefix, + firstName: context.propsValue.firstName, + middleName: context.propsValue.middleName, + lastName: context.propsValue.lastName, + nameSuffix: context.propsValue.nickName, + nickName: context.propsValue.nameSuffix, + }, + doB: context.propsValue.dob, + mobilePhone: context.propsValue.mobilePhone, + mobilePhoneExt: context.propsValue.mobilePhoneExt, + homePhone: context.propsValue.homePhone, + homePhoneExt: context.propsValue.homePhoneExt, + ssn: context.propsValue.ssn, + bankCode: context.propsValue.bankCode, + email1: context.propsValue.email1, + email2: context.propsValue.email2, + email3: context.propsValue.email3, + preferredToD: context.propsValue.preferredToD, + drivingLicense: context.propsValue.drivingLicense, + drivingLicenseState: context.propsValue.drivingLicenseState, + isActiveMilitaryDuty: context.propsValue.isActiveMilitaryDuty, + gender: context.propsValue.gender, + fullAddress: { + street: context.propsValue.home_street, + addressLine2: context.propsValue.home_addressLine2, + city: context.propsValue.home_city, + stateName: context.propsValue.home_stateName, + stateId: context.propsValue.home_stateId, + zip: context.propsValue.home_zip, + countryName: context.propsValue.home_countryName, + countryId: context.propsValue.home_countryId, + }, + isUSCitizen: context.propsValue.isUSCitizen, + webSiteUrl: context.propsValue.webSiteUrl, + facebookUrl: context.propsValue.facebookUrl, + linkedInUrl: context.propsValue.linkedInUrl, + instagramUrl: context.propsValue.instagramUrl, + twitterUrl: context.propsValue.twitterUrl, + googlePlusUrl: context.propsValue.googlePlusUrl, + angelListUrl: context.propsValue.angelListUrl, + zoomUrl: context.propsValue.zoomUrl, + photoUrl: context.propsValue.photoUrl, + experience: context.propsValue.experience, + profileSummary: context.propsValue.profileSummary, + affiliateCode: context.propsValue.personAffiliateCode, + customFields: { + customField1: context.propsValue.customField1, + customField2: context.propsValue.customField2, + customField3: context.propsValue.customField3, + customField4: context.propsValue.customField4, + customField5: context.propsValue.customField5, + }, + }, + businessInfo: { + companyName: context.propsValue.companyName, + organizationType: context.propsValue.organizationType, + jobTitle: context.propsValue.isEmployed, + isEmployed: context.propsValue.jobTitle, + employmentStartDate: context.propsValue.employmentStartDate, + employeeCount: context.propsValue.employeeCount, + dateFounded: context.propsValue.dateFounded, + ein: context.propsValue.ein, + annualRevenue: context.propsValue.annualRevenue, + industry: context.propsValue.industry, + companyPhone: context.propsValue.companyPhone, + companyPhoneExt: context.propsValue.companyPhoneExt, + companyFaxNumber: context.propsValue.companyFaxNumber, + companyEmail: context.propsValue.companyEmail, + companyFullAddress: { + street: context.propsValue.company_street, + addressLine2: context.propsValue.company_addressLine2, + city: context.propsValue.company_city, + stateName: context.propsValue.company_stateName, + stateId: context.propsValue.company_stateId, + zip: context.propsValue.company_zip, + countryName: context.propsValue.company_countryName, + countryId: context.propsValue.company_countryId, + }, + companyWebSiteUrl: context.propsValue.companyWebSiteUrl, + companyFacebookUrl: context.propsValue.companyFacebookUrl, + companyLinkedInUrl: context.propsValue.companyLinkedInUrl, + companyInstagramUrl: context.propsValue.companyInstagramUrl, + companyTwitterUrl: context.propsValue.companyTwitterUrl, + companyGooglePlusUrl: context.propsValue.companyGooglePlusUrl, + companyCrunchbaseUrl: context.propsValue.companyCrunchbaseUrl, + companyBBBUrl: context.propsValue.companyBBBUrl, + companyCalendlyUrl: context.propsValue.companyZoomUrl, + companyZoomUrl: context.propsValue.companyCalendlyUrl, + companyLogoUrl: context.propsValue.companyLogoUrl, + workPhone1: context.propsValue.workPhone1, + workPhone1Ext: context.propsValue.workPhone1Ext, + workPhone2: context.propsValue.workPhone2, + workPhone2Ext: context.propsValue.workPhone2Ext, + workEmail1: context.propsValue.workEmail1, + workEmail2: context.propsValue.workEmail2, + workEmail3: context.propsValue.workEmail3, + workFullAddress: { + street: context.propsValue.work_street, + addressLine2: context.propsValue.work_addressLine2, + city: context.propsValue.work_city, + stateName: context.propsValue.work_stateName, + stateId: context.propsValue.work_stateId, + zip: context.propsValue.work_zip, + countryName: context.propsValue.work_countryName, + countryId: context.propsValue.work_countryId, + }, + affiliateCode: context.propsValue.companyAffiliateCode, + }, + assignedUser: context.propsValue.assignedUser, + followUpDate: context.propsValue.followUpDate, + notes: context.propsValue.notes, + dateCreated: context.propsValue.dateCreated, + leadStageName: context.propsValue.leadStageName, + leadSource: context.propsValue.leadSource, + leadDealAmount: context.propsValue.leadDealAmount, + affiliateCode: context.propsValue.affiliateCode, + campaignId: context.propsValue.campaignId, + channelId: context.propsValue.channelId, + gclId: context.propsValue.gclId, + refererUrl: context.propsValue.refererURL, + entryUrl: context.propsValue.entryUrl, + applicantId: context.propsValue.applicantId, + applicationId: context.propsValue.applicationId, + ipAddress: context.propsValue.ipAddress, + userAgent: context.propsValue.userAgent, + siteId: context.propsValue.siteId, + siteUrl: context.propsValue.siteUrl, + utmSource: context.propsValue.utmSource, + utmMedium: context.propsValue.utmMedium, + utmCampaign: context.propsValue.utmCampaign, + utmTerm: context.propsValue.utmTerm, + utmContent: context.propsValue.utmContent, + utmKeyword: context.propsValue.utmKeyword, + utmAdGroup: context.propsValue.utmAdGroup, + utmName: context.propsValue.utmName, + requestCustomInfo: { + customField1: context.propsValue.requestCustomField1, + customField2: context.propsValue.requestCustomField2, + customField3: context.propsValue.requestCustomField3, + customField4: context.propsValue.requestCustomField4, + customField5: context.propsValue.requestCustomField5, + }, + subscription1: { + productCode: context.propsValue.sub1_productCode, + paymentPeriodType: context.propsValue.sub1_paymentPeriodType, + systemType: context.propsValue.sub1_systemType, + code: context.propsValue.sub1_code, + name: context.propsValue.sub1_name, + level: context.propsValue.sub1_level, + startDate: context.propsValue.sub1_startDate, + endDate: context.propsValue.sub1_endDate, + amount: context.propsValue.sub1_amount, + }, + subscription2: { + productCode: context.propsValue.sub2_productCode, + paymentPeriodType: context.propsValue.sub2_paymentPeriodType, + systemType: context.propsValue.sub2_systemType, + code: context.propsValue.sub2_code, + name: context.propsValue.sub2_name, + level: context.propsValue.sub2_level, + startDate: context.propsValue.sub2_startDate, + endDate: context.propsValue.sub2_endDate, + amount: context.propsValue.sub2_amount, + }, + subscription3: { + productCode: context.propsValue.sub3_productCode, + paymentPeriodType: context.propsValue.sub3_paymentPeriodType, + systemType: context.propsValue.sub3_systemType, + code: context.propsValue.sub3_code, + name: context.propsValue.sub3_name, + level: context.propsValue.sub3_level, + startDate: context.propsValue.sub3_startDate, + endDate: context.propsValue.sub3_endDate, + amount: context.propsValue.sub3_amount, + }, + classificationInfo: { + rating: context.propsValue.rating, + }, + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.base_url}/api/services/CRM/Import/ImportContact`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...customer, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/linka/src/lib/actions/add-or-update-contact.ts b/packages/pieces/community/linka/src/lib/actions/add-or-update-contact.ts new file mode 100644 index 0000000..93170b3 --- /dev/null +++ b/packages/pieces/community/linka/src/lib/actions/add-or-update-contact.ts @@ -0,0 +1,375 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { linkaAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const addOrUpdateContact = createAction({ + name: 'addOrUpdateContact', + displayName: 'Add or Update Contact', + description: 'Creates a new contact.', + auth: linkaAuth, + props: { + importType: Property.StaticDropdown({ + displayName: 'Contact Type', + required: true, + defaultValue: 'Lead', + options: { + disabled: false, + options: [ + { + label: 'Lead', + value: 'Lead', + }, + { + label: 'Client', + value: 'Client', + }, + { + label: 'Partner', + value: 'Partner', + }, + ], + }, + }), + matchExisting: Property.StaticDropdown({ + displayName: 'Match Existing Contact', + description: + 'If "Yes", will try to find an existing record using Email and Full Name and update it.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + contactId: Property.Number({ + displayName: 'Contact ID', + description: 'Sperse Contact ID. Will be used for looking a client', + defaultValue: 0, + required: false, + }), + // fullname + fullName: Property.ShortText({ + displayName: 'Full Name', + description: "The contact's full name.", + required: false, + }), + namePrefix: Property.ShortText({ + displayName: 'Prefix', + description: 'The title used to address the contact.', + required: false, + }), + firstName: Property.ShortText({ + displayName: 'First Name', + description: 'Required if Last Name and Company Name fields are empty.', + required: true, + }), + middleName: Property.ShortText({ + displayName: 'Middle Name', + description: "The contact's middle name.", + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + description: 'Required if First Name and Company Name fields are empty.', + required: true, + }), + nickName: Property.ShortText({ + displayName: 'Nick Name', + description: "The contact's nick name.", + required: false, + }), + nameSuffix: Property.ShortText({ + displayName: 'Suffix', + description: 'Additional information about the contact e.g PhD.', + required: false, + }), + // personal info + gender: Property.StaticDropdown({ + displayName: 'Gender', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Male', + value: 'Male', + }, + { + label: 'Female', + value: 'Female', + }, + ], + }, + }), + dob: Property.ShortText({ + displayName: 'Date of Birth', + description: 'Valid date format YYYY-MM-DD or MM-DD-YYYY.', + required: false, + }), + bankCode: Property.ShortText({ + displayName: 'Bank Code', + description: "The contact's 4-letter personality code.", + required: false, + }), + ssn: Property.ShortText({ + displayName: 'SSN', + description: "The contact's social security number.", + required: false, + }), + // business info + companyName: Property.ShortText({ + displayName: 'Company Name', + description: + "Name of the contact's company (This field is mandatory if the First Name and Last Name fields are empty).", + required: false, + }), + jobTitle: Property.ShortText({ + displayName: 'Job Title', + description: "The contact's job title.", + required: false, + }), + industry: Property.ShortText({ + displayName: 'Industry', + description: "The company's industry.", + required: false, + }), + // email + workEmail1: Property.LongText({ + displayName: 'Work Email', + description: "The contact's work email.", + required: false, + }), + email1: Property.LongText({ + displayName: 'Personal Email', + description: "The contact's personal email.", + required: false, + }), + email2: Property.LongText({ + displayName: 'Other email', + description: "The contact's additional email.", + required: false, + }), + // phone + workPhone1: Property.ShortText({ + displayName: 'Work Phone', + description: "The contact's work/primary phone number.", + required: false, + }), + homePhone: Property.ShortText({ + displayName: 'Home Phone', + description: "The contact's home phone number.", + required: false, + }), + mobilePhone: Property.ShortText({ + displayName: 'Mobile Phone', + description: "The contact's mobile phone number.", + required: false, + }), + // links + webSiteUrl: Property.ShortText({ + displayName: 'Website', + description: "The contact's company website URL.", + required: false, + }), + linkedInUrl: Property.ShortText({ + displayName: 'LinkedIn', + description: "The contact's LinkedIn profile id.", + required: false, + }), + photoUrl: Property.ShortText({ + displayName: 'Photo URL', + description: "The contact's person photo URL.", + required: false, + }), + // Full Address + street: Property.ShortText({ + displayName: 'Street', + description: + "The contact's full street address (can include apartment or unit number).", + required: false, + }), + addressLine2: Property.ShortText({ + displayName: 'Address 2', + required: false, + }), + city: Property.ShortText({ + displayName: 'City', + description: "The contact's city of residence.", + required: false, + }), + stateName: Property.ShortText({ + displayName: 'State Name', + description: "The contact's state of residence.", + required: false, + }), + stateId: Property.ShortText({ + displayName: 'State Code', + description: "The contact's state code.", + required: false, + }), + zip: Property.ShortText({ + displayName: 'Zip Code', + description: "The contact's zip/postal code.", + required: false, + }), + countryName: Property.ShortText({ + displayName: 'Country Name', + description: "The contact's country of residence.", + required: false, + }), + countryId: Property.ShortText({ + displayName: 'Country Code', + description: "The contact's country code.", + required: false, + }), + // content + experience: Property.LongText({ + displayName: 'Content', + description: "The contact's professional experience.", + required: false, + }), + profileSummary: Property.LongText({ + displayName: 'Profile Summary', + description: "The contact's profile summary.", + required: false, + }), + notes: Property.LongText({ + displayName: 'notes', + description: 'Additional notes about the contact', + required: false, + }), + followUpDate: Property.ShortText({ + displayName: 'Follow Up Date', + description: + 'Valid date format YYYY-MM-DD HH:MM:SS. If date is defined then new Follow Up Task will be created for this contact', + required: false, + }), + assignedUser: Property.ShortText({ + displayName: 'Assigned User', + description: + 'Preferably, Sperse User Email should be passed as it is unique within Sperse account but User Name can be also passed', + required: false, + }), + leadDealAmount: Property.Number({ + displayName: 'Deal Amount', + description: 'Estimated deal/opportunity amount.', + required: false, + }), + // tracking info + leadSource: Property.ShortText({ + displayName: 'Source Code', + description: + 'The first known source the contact used to find your website. You can set this automatically and update manually later.', + required: false, + }), + channelId: Property.ShortText({ + displayName: 'Channel Code', + description: 'The channel/medium the contact used to find your website.', + required: false, + }), + affiliateCode: Property.ShortText({ + displayName: 'Affiliate Code', + description: + 'The affiliate/referer partner through which the contact signed up.', + required: false, + }), + refererURL: Property.ShortText({ + displayName: 'Referer URL', + description: + 'The webpage where the contact clicked a link that sent them to your website.', + required: false, + }), + entryUrl: Property.ShortText({ + displayName: 'Entry URL', + description: + 'The first page of visit through which the contact visited your website.', + required: false, + }), + }, + async run(context) { + const contact = { + importType: context.propsValue.importType, + matchExisting: context.propsValue.matchExisting, + contactId: context.propsValue.contactId, + personalInfo: { + fullName: { + namePrefix: context.propsValue.namePrefix, + firstName: context.propsValue.firstName, + middleName: context.propsValue.middleName, + lastName: context.propsValue.lastName, + nameSuffix: context.propsValue.nameSuffix, + nickName: context.propsValue.nickName, + }, + doB: context.propsValue.dob, + mobilePhone: context.propsValue.mobilePhone, + homePhone: context.propsValue.homePhone, + ssn: context.propsValue.ssn, + bankCode: context.propsValue.bankCode, + email1: context.propsValue.email1, + email2: context.propsValue.email2, + gender: context.propsValue.gender, + fullAddress: { + street: context.propsValue.street, + addressLine2: context.propsValue.addressLine2, + city: context.propsValue.city, + stateName: context.propsValue.stateName, + stateId: context.propsValue.stateId, + zip: context.propsValue.zip, + countryName: context.propsValue.countryName, + countryId: context.propsValue.countryId, + }, + webSiteUrl: context.propsValue.webSiteUrl, + linkedInUrl: context.propsValue.linkedInUrl, + photoUrl: context.propsValue.photoUrl, + + experience: context.propsValue.experience, + profileSummary: context.propsValue.profileSummary, + }, + + businessInfo: { + companyName: context.propsValue.companyName, + jobTitle: context.propsValue.jobTitle, + industry: context.propsValue.industry, + workPhone1: context.propsValue.workPhone1, + workEmail1: context.propsValue.workEmail1, + }, + + assignedUser: context.propsValue.assignedUser, + followUpDate: context.propsValue.followUpDate, + notes: context.propsValue.notes, + leadDealAmount: context.propsValue.leadDealAmount, + + leadSource: context.propsValue.leadSource, + channelId: context.propsValue.channelId, + affiliateCode: context.propsValue.affiliateCode, + refererUrl: context.propsValue.refererURL, + entryUrl: context.propsValue.entryUrl, + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.base_url}/api/services/CRM/Import/ImportContact`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...contact, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/linka/src/lib/actions/add-or-update-subscription.ts b/packages/pieces/community/linka/src/lib/actions/add-or-update-subscription.ts new file mode 100644 index 0000000..e5dfe0d --- /dev/null +++ b/packages/pieces/community/linka/src/lib/actions/add-or-update-subscription.ts @@ -0,0 +1,110 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { linkaAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const addOrUpdateSubscription = createAction({ + name: 'addOrUpdateSubscription', + displayName: 'Add or Update Subscription', + description: 'Creates a new subscription.', + auth: linkaAuth, + props: { + contactId: Property.Number({ + displayName: 'Contact ID', + defaultValue: 0, + required: false, + }), + productId: Property.Number({ + displayName: 'Product ID', + required: false, + }), + contactXref: Property.ShortText({ + displayName: 'External Contact ID', + description: + 'ContactXref have to be specified and correct to look up the correct contact', + required: false, + }), + productCode: Property.ShortText({ + displayName: 'Product Code', + description: + 'Product Code (Unique product identifier). ProductCode have to be specified and correct to look up the correct product', + required: true, + }), + paymentPeriodType: Property.StaticDropdown({ + displayName: 'Payment Period Type', + description: + 'The chosen Period Type has to be set for the Product on Sperse side', + required: true, + defaultValue: 'Monthly', + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + ], + }, + }), + hasRecurringBilling: Property.StaticDropdown({ + displayName: 'Is it Recurring Billing', + required: false, + defaultValue: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + }, + async run(context) { + const subscription = { + contactId: context.propsValue.contactId, + contactXref: context.propsValue.contactXref, + products: [ + { + productId: context.propsValue.productId, + productCode: context.propsValue.productCode, + paymentPeriodType: context.propsValue.paymentPeriodType, + hasRecurringBilling: context.propsValue.hasRecurringBilling, + }, + ], + productId: context.propsValue.productId, + productCode: context.propsValue.productCode, + paymentPeriodType: context.propsValue.paymentPeriodType, + hasRecurringBilling: context.propsValue.hasRecurringBilling, + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: `${context.auth.base_url}/api/services/CRM/OrderSubscription/Update`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...subscription, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/linka/src/lib/actions/create-invoice.ts b/packages/pieces/community/linka/src/lib/actions/create-invoice.ts new file mode 100644 index 0000000..fc058a9 --- /dev/null +++ b/packages/pieces/community/linka/src/lib/actions/create-invoice.ts @@ -0,0 +1,474 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { linkaAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +// Helper function to get current date in the required format +const getCurrentDateInISOFormat = () => { + return new Date().toISOString(); // Returns current date in format: YYYY-MM-DDTHH:MM:SSZ +}; + +export const createInvoice = createAction({ + name: 'createInvoice', + displayName: 'Create Invoice', + description: 'Creates a new invoice in the CRM.', + auth: linkaAuth, + props: { + contactId: Property.Number({ + displayName: 'Contact ID', + description: 'Sperse Contact ID. Will be used for looking a client', + defaultValue: 0, + required: false, + }), + contactXref: Property.ShortText({ + displayName: 'External Contact ID', + description: + 'External Contact Reference (ID) . Will be used for looking a client', + required: false, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + required: true, + defaultValue: 'Draft', + options: { + disabled: false, + options: [ + { + label: 'Draft', + value: 'Draft', + }, + { + label: 'Final', + value: 'Final', + }, + { + label: 'Paid', + value: 'Paid', + }, + { + label: 'Sent', + value: 'Sent', + }, + ], + }, + }), + invoiceNo: Property.Number({ + displayName: 'Invoice No.', + defaultValue: 0, + required: true, + }), + date: Property.DateTime({ + displayName: 'Date of the Invoice', + description: 'should be like this: 2024-06-11T11:11:41Z', + defaultValue: getCurrentDateInISOFormat(), + required: true, + }), + dueDate: Property.DateTime({ + displayName: 'Due Date of the Invoice', + description: 'should be like this: 2024-06-11T11:11:41Z', + required: true, + }), + currencyId: Property.StaticDropdown({ + displayName: 'Currency Id', + required: true, + defaultValue: 'USD', + options: { + disabled: false, + options: [ + { + label: 'USD', + value: 'USD', + }, + { + label: 'JPY', + value: 'JPY', + }, + { + label: 'IND', + value: 'IND', + }, + { + label: 'EUR', + value: 'EUR', + }, + { + label: 'GBP', + value: 'GBP', + }, + { + label: 'AUD', + value: 'AUD', + }, + { + label: 'CAD', + value: 'CAD', + }, + { + label: 'CHF', + value: 'CHF', + }, + { + label: 'CNY', + value: 'CNY', + }, + { + label: 'SEK', + value: 'SEK', + }, + { + label: 'NZD', + value: 'NZD', + }, + ], + }, + }), + grandTotal: Property.Number({ + displayName: 'Grand Total', + defaultValue: 0, + required: false, + }), + discountTotal: Property.Number({ + displayName: 'Discount Total', + defaultValue: 0, + required: false, + }), + shippingTotal: Property.Number({ + displayName: 'Shipping Total', + defaultValue: 0, + required: false, + }), + taxTotal: Property.Number({ + displayName: 'Tax Total', + defaultValue: 0, + required: false, + }), + // billing + bCompany: Property.ShortText({ + displayName: 'Billing Company', + required: false, + }), + bFirstName: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + bLastName: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + bPhone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + bEmail: Property.ShortText({ + displayName: 'Email', + required: false, + }), + bCountryId: Property.ShortText({ + displayName: 'Country Id', + required: false, + }), + bStateId: Property.ShortText({ + displayName: 'State Id', + required: false, + }), + bStateName: Property.ShortText({ + displayName: 'State Name', + required: false, + }), + bCity: Property.ShortText({ + displayName: 'City', + required: false, + }), + bZip: Property.ShortText({ + displayName: 'Zip', + required: false, + }), + bAddress1: Property.LongText({ + displayName: 'Billing Address 1', + required: false, + }), + bAddress2: Property.LongText({ + displayName: 'Billing Address 2', + required: false, + }), + // shipping + sCompany: Property.ShortText({ + displayName: 'Shipping Company', + required: false, + }), + sFirstName: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + sLastName: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + sPhone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + sEmail: Property.ShortText({ + displayName: 'Email', + required: false, + }), + sCountryId: Property.ShortText({ + displayName: 'Country Id', + required: false, + }), + sStateId: Property.ShortText({ + displayName: 'State Id', + required: false, + }), + sStateName: Property.ShortText({ + displayName: 'State Name', + required: false, + }), + sCity: Property.ShortText({ + displayName: 'City', + required: false, + }), + sZip: Property.ShortText({ + displayName: 'Zip', + required: false, + }), + sAddress1: Property.LongText({ + displayName: 'Shipping Address 1', + required: false, + }), + sAddress2: Property.LongText({ + displayName: 'Shipping Address 2', + required: false, + }), + // + note: Property.LongText({ + displayName: 'Invoice Note', + required: false, + }), + invoiceDescription: Property.LongText({ + displayName: 'Invoice Description', + required: true, + }), + // line + quantity: Property.Number({ + displayName: 'Quantity', + defaultValue: 0, + required: true, + }), + rate: Property.Number({ + displayName: 'Rate', + defaultValue: 0, + required: false, + }), + itemTotal: Property.Number({ + displayName: 'Total Item Price', + defaultValue: 0, + required: false, + }), + commissionableAmount: Property.Number({ + displayName: 'Commissionable Amount', + defaultValue: 0, + required: false, + }), + unitId: Property.StaticDropdown({ + displayName: 'Unit Id', + required: false, + defaultValue: 'Unit', + options: { + disabled: false, + options: [ + { + label: 'Unit', + value: 'Unit', + }, + { + label: 'Day', + value: 'Day', + }, + { + label: 'Month', + value: 'Month', + }, + { + label: 'Year', + value: 'Year', + }, + { + label: 'Hour', + value: 'Hour', + }, + { + label: 'Kilogram', + value: 'Kilogram', + }, + { + label: 'Zone', + value: 'Zone', + }, + { + label: 'Package', + value: 'Package', + }, + { + label: 'Pound', + value: 'Pound', + }, + { + label: 'Piece', + value: 'Piece', + }, + { + label: 'Feet', + value: 'Feet', + }, + { + label: 'Custom', + value: 'Custom', + }, + ], + }, + }), + productCode: Property.ShortText({ + displayName: 'Product Code', + description: 'Product Code. We will look up the product', + required: false, + }), + itemDescription: Property.ShortText({ + displayName: 'Description', + required: false, + }), + sortOrder: Property.Number({ + displayName: 'Sort Order', + defaultValue: 0, + required: false, + }), + // transactions + transactionDate: Property.DateTime({ + displayName: 'Transaction Date', + description: 'should be like this: 2024-06-11T11:11:41Z', + defaultValue: getCurrentDateInISOFormat(), + required: true, + }), + transactionDescription: Property.ShortText({ + displayName: 'Transaction Description', + required: false, + }), + amount: Property.Number({ + displayName: 'Amount', + defaultValue: 0, + required: false, + }), + gatewayName: Property.ShortText({ + displayName: 'Gateway Name', + required: false, + }), + gatewayTransactionId: Property.ShortText({ + displayName: 'Gateway Transaction Id', + required: false, + }), + historicalData: Property.StaticDropdown({ + displayName: 'Historical Data', + description: + 'Pass true if this is not actual transaction. Should be False by default', + required: true, + defaultValue: false, + options: { + disabled: false, + options: [ + { + label: 'true', + value: true, + }, + { + label: 'false', + value: false, + }, + ], + }, + }), + }, + async run(context) { + // construct + const invoice = { + contactId: context.propsValue.contactId, + contactXref: context.propsValue.contactXref, + status: context.propsValue.status, + number: context.propsValue.invoiceNo, + date: context.propsValue.date, + dueDate: context.propsValue.dueDate, + currencyId: context.propsValue.currencyId, + grandTotal: context.propsValue.grandTotal, + discountTotal: context.propsValue.discountTotal, + shippingTotal: context.propsValue.shippingTotal, + taxTotal: context.propsValue.taxTotal, + billingAddress: { + countryId: context.propsValue.bCountryId, + stateId: context.propsValue.bStateId, + stateName: context.propsValue.bStateName, + city: context.propsValue.bCity, + zip: context.propsValue.bZip, + address1: context.propsValue.bAddress1, + address2: context.propsValue.bAddress2, + firstName: context.propsValue.bFirstName, + lastName: context.propsValue.bLastName, + company: context.propsValue.bCompany, + email: context.propsValue.bEmail, + phone: context.propsValue.bPhone, + }, + shippingAddress: { + countryId: context.propsValue.sCountryId, + stateId: context.propsValue.sStateId, + stateName: context.propsValue.sStateName, + city: context.propsValue.sCity, + zip: context.propsValue.sZip, + address1: context.propsValue.sAddress1, + address2: context.propsValue.sAddress2, + firstName: context.propsValue.sFirstName, + lastName: context.propsValue.sLastName, + company: context.propsValue.sCompany, + email: context.propsValue.sEmail, + phone: context.propsValue.sPhone, + }, + description: context.propsValue.invoiceDescription, + note: context.propsValue.note, + lines: [ + { + quantity: context.propsValue.quantity, + rate: context.propsValue.rate, + total: context.propsValue.itemTotal, + commissionableAmount: context.propsValue.commissionableAmount, + unitId: context.propsValue.unitId, + productCode: context.propsValue.productCode, + description: context.propsValue.itemDescription, + sortOrder: context.propsValue.sortOrder, + }, + ], + transactions: [ + { + date: context.propsValue.transactionDate, + description: context.propsValue.transactionDescription, + amount: context.propsValue.amount, + gatewayName: context.propsValue.gatewayName, + gatewayTransactionId: context.propsValue.gatewayTransactionId, + }, + ], + historicalData: context.propsValue.historicalData, + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.base_url}/api/services/CRM/Import/ImportInvoice`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...invoice, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/linka/src/lib/actions/create-product.ts b/packages/pieces/community/linka/src/lib/actions/create-product.ts new file mode 100644 index 0000000..0f0445c --- /dev/null +++ b/packages/pieces/community/linka/src/lib/actions/create-product.ts @@ -0,0 +1,318 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { linkaAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const createProduct = createAction({ + name: 'createProduct', + displayName: 'Create Product', + description: 'Creates a new product in the CRM', + auth: linkaAuth, + props: { + productType: Property.StaticDropdown({ + displayName: 'Product Type', + required: true, + defaultValue: 'General', + options: { + disabled: false, + options: [ + { + label: 'General', + value: 'General', + }, + { + label: 'Event', + value: 'Event', + }, + { + label: 'Subscription', + value: 'Subscription', + }, + { + label: 'Digital', + value: 'Digital', + }, + ], + }, + }), + name: Property.ShortText({ + displayName: 'Product Name', + required: true, + }), + code: Property.ShortText({ + displayName: 'SKU', + required: true, + description: 'Sku is the product code', + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + descriptionHTML: Property.LongText({ + displayName: 'Description HTML', + required: false, + description: 'Javascript and media tags are not allowed', + }), + logoURL: Property.LongText({ + displayName: 'Logo Url', + required: false, + }), + groupName: Property.ShortText({ + displayName: 'Group Name', + required: false, + }), + price: Property.Number({ + displayName: 'Price', + required: false, + description: 'Required for General , Digital and Event Product Type', + defaultValue: 0, + }), + currencyId: Property.StaticDropdown({ + displayName: 'Currency Id', + required: false, + defaultValue: 'USD', + options: { + disabled: false, + options: [ + { + label: 'USD', + value: 'USD', + }, + { + label: 'JPY', + value: 'JPY', + }, + { + label: 'IND', + value: 'IND', + }, + { + label: 'EUR', + value: 'EUR', + }, + { + label: 'GBP', + value: 'GBP', + }, + { + label: 'AUD', + value: 'AUD', + }, + { + label: 'CAD', + value: 'CAD', + }, + { + label: 'CHF', + value: 'CHF', + }, + { + label: 'CNY', + value: 'CNY', + }, + { + label: 'SEK', + value: 'SEK', + }, + { + label: 'NZD', + value: 'NZD', + }, + ], + }, + }), + unit: Property.StaticDropdown({ + displayName: 'Unit', + required: false, + description: 'Required for General and Digital Product Type', + defaultValue: 'Day', + options: { + disabled: false, + options: [ + { + label: 'Day', + value: 'Day', + }, + { + label: 'Feet', + value: 'Feet', + }, + { + label: 'Hour', + value: 'Hour', + }, + { + label: 'Kilogram', + value: 'Kilogram', + }, + { + label: 'Pound', + value: 'Pound', + }, + { + label: 'Month', + value: 'Month', + }, + { + label: 'Package', + value: 'Package', + }, + { + label: 'Piece', + value: 'Piece', + }, + { + label: 'Unit', + value: 'Unit', + }, + { + label: 'Year', + value: 'Year', + }, + { + label: 'Zone', + value: 'Zone', + }, + { + label: 'Custom', + value: 'Custom', + }, + ], + }, + }), + frequency: Property.StaticDropdown({ + displayName: 'Payment Cycle', + required: false, + description: 'Required for General and Digital Product Type', + defaultValue: 'Monthly', + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + { + label: 'OneTime', + value: 'OneTime', + }, + { + label: 'Custom', + value: 'Custom', + }, + ], + }, + }), + fee: Property.Number({ + displayName: 'Subscription fee', + required: false, + defaultValue: 0, + }), + cycles: Property.Number({ + displayName: 'No of cycles', + required: false, + description: 'Required for all except LifeTime or OneTime plan', + defaultValue: 0, + }), + signupFee: Property.Number({ + displayName: 'Signup fee', + required: false, + description: 'Required for all except LifeTime or OneTime plan', + defaultValue: 0, + }), + customPeriodType: Property.StaticDropdown({ + displayName: 'Custom Period Type', + required: false, + description: 'Required for Custom or OneTime Plan', + defaultValue: 'Days', + options: { + disabled: false, + options: [ + { + label: 'Days', + value: 'Days', + }, + { + label: 'Weeks', + value: 'Weeks', + }, + { + label: 'Months', + value: 'Months', + }, + { + label: 'Years', + value: 'Years', + }, + ], + }, + }), + customPeriodCount: Property.Number({ + displayName: 'Custom No of Period', + required: false, + description: 'Required for Custom or OneTime Plan', + defaultValue: 0, + }), + trialDayCount: Property.Number({ + displayName: 'Custom No of Period', + required: false, + description: 'Required for OneTime plan', + defaultValue: 0, + }), + gracePeriodDayCount: Property.Number({ + displayName: 'Grace Period Count', + required: false, + defaultValue: 0, + }), + }, + async run(context) { + const product = { + code: context.propsValue.code, + name: context.propsValue.name, + logoUrl: context.propsValue.logoURL, + description: context.propsValue.description, + descriptionHtml: context.propsValue.descriptionHTML, + groupName: context.propsValue.groupName, + type: context.propsValue.productType, + price: context.propsValue.price, + currencyId: context.propsValue.currencyId, + unit: context.propsValue.unit, + productSubscriptionOptions: [ + { + frequency: context.propsValue.frequency, + signupFee: context.propsValue.signupFee, + fee: context.propsValue.fee, + trialDayCount: context.propsValue.trialDayCount, + customPeriodCount: context.propsValue.customPeriodCount, + customPeriodType: context.propsValue.customPeriodType, + cycles: context.propsValue.cycles, + gracePeriodDayCount: context.propsValue.gracePeriodDayCount, + }, + ], + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.base_url}/api/services/CRM/Import/ImportProduct`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...product, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/linka/src/lib/actions/get-contact-details.ts b/packages/pieces/community/linka/src/lib/actions/get-contact-details.ts new file mode 100644 index 0000000..8aa2c32 --- /dev/null +++ b/packages/pieces/community/linka/src/lib/actions/get-contact-details.ts @@ -0,0 +1,73 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { linkaAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getContactDetails = createAction({ + name: 'getContactDetails', + displayName: 'Get Contact Details', + description: 'Get Contact Details', + auth: linkaAuth, + props: { + contactId: Property.Number({ + displayName: 'Contact Id', + required: false, + }), + xref: Property.ShortText({ + displayName: 'Contact Xref', + required: false, + }), + affiliateCode: Property.ShortText({ + displayName: 'Affiliate Code', + required: false, + }), + userId: Property.Number({ + displayName: 'User Id', + required: false, + description: 'Id of the logged in user (not contact id)', + }), + userEmail: Property.LongText({ + displayName: 'User Email', + required: false, + description: 'Email of the logged in user', + }), + }, + async run(context) { + const contact = { + contactId: context.propsValue.contactId, + xref: context.propsValue.xref, + affiliateCode: context.propsValue.affiliateCode, + userId: context.propsValue.userId, + userEmail: context.propsValue.userEmail, + }; + + // Filter out keys with undefined values + const filteredContact: Record = Object.fromEntries( + Object.entries(contact).filter(([, value]) => value !== undefined) + ) as Record; // Cast to ensure it's the correct type + + // Create query parameters from the filtered contact object + const queryParams = new URLSearchParams( + Object.entries(filteredContact).map(([key, value]) => [ + key, + String(value), + ]) + ); + + // Send GET request with query parameters in the URL + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${ + context.auth.base_url + }/api/services/CRM/Contact/GetContactData?${queryParams.toString()}`, + headers: { + 'api-key': context.auth.api_key, + 'Content-Type': 'application/json', + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/linka/src/lib/common/common.ts b/packages/pieces/community/linka/src/lib/common/common.ts new file mode 100644 index 0000000..9c6f0e4 --- /dev/null +++ b/packages/pieces/community/linka/src/lib/common/common.ts @@ -0,0 +1,46 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +export const linkaCommon = { + subscribeWebhook: async ( + eventName: string, + baseUrl: string, + apiKey: string, + webhookUrl: string + ) => { + const request = { + method: HttpMethod.POST, + url: `${baseUrl}/api/services/Platform/Event/Subscribe`, + headers: { + 'api-key': apiKey, + 'Content-Type': 'application/json', + }, + body: { + eventName: eventName, + targetUrl: webhookUrl, + }, + }; + + const res = await httpClient.sendRequest(request); + + const { id: webhookId } = res.body.result; + + return webhookId; + }, + + unsubscribeWebhook: async ( + baseUrl: string, + apiKey: string, + webhookId: number + ) => { + const request = { + method: HttpMethod.POST, + url: `${baseUrl}/api/services/Platform/Event/Unsubscribe?id=${webhookId}`, + headers: { + 'api-key': apiKey, + 'Content-Type': 'application/json', + }, + }; + + return await httpClient.sendRequest(request); + }, +}; diff --git a/packages/pieces/community/linka/src/lib/triggers/new-lead.ts b/packages/pieces/community/linka/src/lib/triggers/new-lead.ts new file mode 100644 index 0000000..3295353 --- /dev/null +++ b/packages/pieces/community/linka/src/lib/triggers/new-lead.ts @@ -0,0 +1,232 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { linkaAuth } from '../..'; +import { linkaCommon } from '../common/common'; + +export const newLead = createTrigger({ + auth: linkaAuth, + name: 'newLead', + displayName: 'New Lead', + description: 'Triggers when a new lead is created', + props: {}, + type: TriggerStrategy.WEBHOOK, + sampleData: { + id: 100500, + group: 'P', + personalInfo: { + fullName: { + namePrefix: 'Mr', + firstName: 'John', + middleName: 'G', + lastName: 'Smith', + nameSuffix: 'Jr', + nickName: 'Johnny', + }, + doB: '1992-06-30T00:00:00Z', + mobilePhone: '+12057899877', + mobilePhoneExt: '123', + homePhone: '+12057985632', + homePhoneExt: '123', + phone1: null, + phoneExt1: null, + phone2: null, + phoneExt2: null, + preferredToD: 'Anytime', + timeZone: 'Pacific Standard Time', + ssn: '123456456', + bankCode: 'BANK', + email1: 'personalemail1@gmal.com', + email2: 'personalemail2@hotmail.com', + email3: 'personalemail3@yahoo.com', + email4: null, + email5: null, + drivingLicense: 'ASDF4566G', + drivingLicenseState: 'NY', + isActiveMilitaryDuty: true, + gender: 'Male', + fullAddress: { + street: '999-901 Emancipation Ave', + addressLine2: null, + neighborhood: null, + city: 'Houston', + stateName: 'Texas', + stateId: 'TX', + zip: '77003', + countryName: 'United States of America', + countryId: 'US', + startDate: '2018-04-30T00:00:00Z', + isOwner: true, + }, + fullAddress2: null, + fullAddress3: null, + isUSCitizen: true, + webSiteUrl: 'www.myprofile.com', + facebookUrl: 'www.fb.com/j.smith', + linkedInUrl: 'https://www.linkedin.com/j.smith', + instagramUrl: 'https://www.instagram.com/j.smith', + twitterUrl: 'https://twitter.com/j.smith', + googlePlusUrl: 'https://googleplus.com/j.smith', + angelListUrl: 'https://angel.co/', + zoomUrl: 'https://zoom.com', + otherLinkUrl: 'https://otherlink.com', + photoUrl: 'https://www.myprofile.com/profile-pictures/myphoto.png', + experience: + 'Improvements made to two existing products, designed five completely new products for four customers.', + profileSummary: + 'Highly skilled and results-oriented professional with solid academic preparation holding a Juris Doctor degree and extensive experience in intelligence and special operations seeks position in risk management.', + interests: ['World Economy', 'Baseball'], + affiliateCode: 'PA001', + isActive: null, + customFields: { + customField1: null, + customField2: null, + customField3: null, + customField4: null, + customField5: null, + }, + }, + businessInfo: { + companyName: 'MyCompany LLC', + organizationType: 'Inc', + jobTitle: 'Chief Executive Officer', + isEmployed: true, + employmentStartDate: '2021-12-30T00:00:00Z', + employeeCount: 500, + dateFounded: '2021-06-30T00:00:00Z', + ein: '35-8896524', + annualRevenue: 6000000.0, + industry: 'Sport and Entertainment', + companyPhone: '+12057784563', + companyPhoneExt: '123', + companyFaxNumber: '+12057324598', + companyEmail: 'companyemail1@company.com', + companyFullAddress: { + street: '1500 Canton st', + addressLine2: null, + neighborhood: null, + city: 'Dallas', + stateName: 'Texas', + stateId: 'TX', + zip: '75201', + countryName: 'United States of America', + countryId: 'US', + startDate: '2018-04-30T00:00:00Z', + isOwner: false, + }, + companyWebSiteUrl: 'www.mycompany.com', + companyFacebookUrl: 'www.fb.com/mycompany', + companyLinkedInUrl: 'https://www.linkedin.com/mycompany', + companyInstagramUrl: 'https://www.instagram.com/mycompany', + companyTwitterUrl: 'https://twitter.com/mycompany', + companyGooglePlusUrl: 'https://googleplus.com/mycompany', + companyCrunchbaseUrl: 'https://www.crunchbase.com/mycompany', + companyBBBUrl: 'https://www.bbb.org/en/us/overview-of-bbb-ratings', + companyPinterestUrl: 'https://www.pinterest.com/mycompany', + companyDomainUrl: 'https://www.domain.com/mycompany', + companyAlexaUrl: 'https://www.alexa.com/mycompany', + companyOpenCorporatesUrl: 'https://www.opencorporates.com/mycompany', + companyGlassDoorUrl: 'https://www.classdoor.com/mycompany', + companyTrustpilotUrl: 'https://www.trustpilot.com/mycompany', + companyFollowersUrl: 'https://www.followers.com/mycompany', + companyYoutubeUrl: 'https://www.youtube.com/mycompany', + companyYelpUrl: 'https://www.yelp.com/mycompany', + companyRSSUrl: 'https://www.rss.com/mycompany', + companyNavUrl: 'https://www.nav.com/mycompany', + companyAngelListUrl: 'https://www.angelist.com/mycompany', + companyCalendlyUrl: 'https://www.calendly.com/mycompany', + companyZoomUrl: 'https://zoom.com/mycompany', + companyOtherLinkUrl: 'https://www.otherlink.com/mycompany', + companyLogoUrl: 'https://www.mycompany.com/images/companylogo/logo.png', + workPhone1: '+12057412354', + workPhone1Ext: '123', + workPhone2: '+12057741236', + workPhone2Ext: '123', + workEmail1: 'workemail1@company.com', + workEmail2: 'workemail2@company.com', + workEmail3: 'workemail3@company.com', + workFullAddress: { + street: '1502-1702 Strawberry Rd', + addressLine2: null, + neighborhood: null, + city: 'Pasadena', + stateName: 'Texas', + stateId: 'TX', + zip: '77502', + countryName: 'United States of America', + countryId: 'US', + startDate: '2020-05-30T00:00:00Z', + isOwner: false, + }, + affiliateCode: 'CA0001', + }, + dateCreated: '2022-06-23T13:32:54.9451405Z', + ipAddress: '192.168.0.1', + trackingInfo: { + sourceCode: 'Code', + channelCode: 'ChannelCode', + affiliateCode: '414CODE', + refererUrl: 'http://www.refererurl.com/referpage.html', + entryUrl: 'https://entryurl.com/start-now/?ref=wow', + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36', + clientIp: '192.168.0.1', + }, + applicationInfo: { + applicationId: '771XYZ23234G', + applicantId: '771XYZSD', + applicantUserId: 10229, + clickId: '2B69283E-9433-4CE6-A24E-D8809C050B70', + requestedLoanAmount: 89.0, + incomeType: 'Benefits', + netMonthlyIncome: 899.0, + payFrequency: 'Monthly', + payNextDate: '2022-07-30T13:32:54.945358Z', + bankName: 'Bank Name', + bankAccountType: 'Checking', + monthsAtBank: 24, + bankAccountNumber: '26005325777412', + creditScoreRating: 'Excellent', + loanReason: 'AutoPurchase', + creditCardDebtAmount: 56898.0, + }, + classificationInfo: { + rating: '10', + lists: ['My List 1', 'My List 2', 'My List 3'], + tags: ['My Tag 1', 'My Tag 2', 'My Tag 3'], + partnerTypeName: 'My Partner Type', + }, + eventTime: '2022-06-30T13:32:54Z', + }, + + async onEnable(context) { + const webhookId = await linkaCommon.subscribeWebhook( + 'LeadCreated', + context.auth.base_url, + context.auth.api_key, + context.webhookUrl + ); + + await context.store?.put('_new_lead_trigger', { + webhookId: webhookId, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_new_lead_trigger' + ); + + if (response !== null && response !== undefined) { + await linkaCommon.unsubscribeWebhook( + context.auth.base_url, + context.auth.api_key, + response.webhookId + ); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); + +interface WebhookInformation { + webhookId: number; +} diff --git a/packages/pieces/community/linka/src/lib/triggers/new-payment.ts b/packages/pieces/community/linka/src/lib/triggers/new-payment.ts new file mode 100644 index 0000000..ab71d19 --- /dev/null +++ b/packages/pieces/community/linka/src/lib/triggers/new-payment.ts @@ -0,0 +1,85 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { linkaAuth } from '../..'; +import { linkaCommon } from '../common/common'; + +export const newPayment = createTrigger({ + auth: linkaAuth, + name: 'newPayment', + displayName: 'New Payment', + description: 'Triggers when a new payment is created', + props: {}, + type: TriggerStrategy.WEBHOOK, + sampleData: { + contact: { + email: 's@gmail.com', + fullName: 'Test User', + id: 636, + phone: '98877676565', + }, + invoice: { + currencyId: 'USD', + date: '2024-06-10T00:00:00Z', + description: 'just a description', + discountTotal: 0, + grandTotal: 100, + id: 457, + number: 'INV - 20240610 - 746C', + shippingTotal: 0, + taxTotal: 0, + lines: [ + { + description: 'Me Spacial', + productId: 810, + quantity: 1, + rate: 100, + total: 100, + unitId: 'Month', + }, + ], + }, + transaction: { + amount: 100, + currencyId: 'USD', + date: '2024-06-10T09:36:01.832Z', + gatewayName: null, + gatewayTransactionId: null, + id: 403, + isSuccessful: true, + type: 'Sale', + }, + eventTime: '2024-06-10T09:36:08', + eventType: 'Payment.Created', + }, + async onEnable(context) { + const webhookId = await linkaCommon.subscribeWebhook( + 'Payment.Created', + context.auth.base_url, + context.auth.api_key, + context.webhookUrl + ); + + await context.store?.put('_new_payment_trigger', { + webhookId: webhookId, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_new_payment_trigger' + ); + + if (response !== null && response !== undefined) { + await linkaCommon.unsubscribeWebhook( + context.auth.base_url, + context.auth.api_key, + response.webhookId + ); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); + +interface WebhookInformation { + webhookId: number; +} diff --git a/packages/pieces/community/linka/src/lib/triggers/new-subscription.ts b/packages/pieces/community/linka/src/lib/triggers/new-subscription.ts new file mode 100644 index 0000000..5811f1c --- /dev/null +++ b/packages/pieces/community/linka/src/lib/triggers/new-subscription.ts @@ -0,0 +1,61 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { linkaAuth } from '../..'; +import { linkaCommon } from '../common/common'; + +export const newSubscription = createTrigger({ + auth: linkaAuth, + name: 'newSubscription', + displayName: 'New Subscription', + description: 'triggers when a new subscription is created', + props: {}, + type: TriggerStrategy.WEBHOOK, + sampleData: { + eventType: 'Subscription.CreatedOrUpdated', + contactId: 3994, + fullname: 'Frank Micheal', + email: 'tray@sperse.com', + id: '536778', + name: 'Subscription_Name', + startDate: '2024-06-30T09:29:43.6271352Z', + endDate: '2025-06-30T09:29:43.6271352Z', + amount: 100, + frequency: 'Annual', + trialDayCount: '4', + gracePeriodCount: '10', + statusId: 'A', + cancelationReason: '', + eventTime: '2024-09-06T00:29:07', + }, + async onEnable(context) { + const webhookId = await linkaCommon.subscribeWebhook( + 'Subscription.CreatedOrUpdated', + context.auth.base_url, + context.auth.api_key, + context.webhookUrl + ); + + await context.store?.put('_new_subscription_trigger', { + webhookId: webhookId, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_new_subscription_trigger' + ); + + if (response !== null && response !== undefined) { + await linkaCommon.unsubscribeWebhook( + context.auth.base_url, + context.auth.api_key, + response.webhookId + ); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); + +interface WebhookInformation { + webhookId: number; +} diff --git a/packages/pieces/community/linka/tsconfig.json b/packages/pieces/community/linka/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/linka/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/linka/tsconfig.lib.json b/packages/pieces/community/linka/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/linka/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/linkedin/.eslintrc.json b/packages/pieces/community/linkedin/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/linkedin/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/linkedin/README.md b/packages/pieces/community/linkedin/README.md new file mode 100644 index 0000000..19f62f6 --- /dev/null +++ b/packages/pieces/community/linkedin/README.md @@ -0,0 +1,7 @@ +# pieces-linkedin + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-linkedin` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/linkedin/package.json b/packages/pieces/community/linkedin/package.json new file mode 100644 index 0000000..9864a29 --- /dev/null +++ b/packages/pieces/community/linkedin/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-linkedin", + "version": "0.1.10" +} \ No newline at end of file diff --git a/packages/pieces/community/linkedin/project.json b/packages/pieces/community/linkedin/project.json new file mode 100644 index 0000000..7574b67 --- /dev/null +++ b/packages/pieces/community/linkedin/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-linkedin", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/linkedin/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/linkedin", + "tsConfig": "packages/pieces/community/linkedin/tsconfig.lib.json", + "packageJson": "packages/pieces/community/linkedin/package.json", + "main": "packages/pieces/community/linkedin/src/index.ts", + "assets": [ + "packages/pieces/community/linkedin/*.md", + { + "input": "packages/pieces/community/linkedin/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/linkedin/src/index.ts b/packages/pieces/community/linkedin/src/index.ts new file mode 100644 index 0000000..2e8d735 --- /dev/null +++ b/packages/pieces/community/linkedin/src/index.ts @@ -0,0 +1,52 @@ +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; + +import { PieceCategory } from '@activepieces/shared'; +import { createCompanyUpdate } from './lib/actions/create-company-update'; +import { createShareUpdate } from './lib/actions/create-share-update'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { linkedinCommon } from './lib/common'; + +export const linkedinAuth = PieceAuth.OAuth2({ + authUrl: 'https://www.linkedin.com/oauth/v2/authorization', + tokenUrl: 'https://www.linkedin.com/oauth/v2/accessToken', + required: true, + scope: [ + 'w_member_social', + 'w_organization_social', + 'rw_organization_admin', + 'openid', + 'email', + 'profile', + ], +}); + +export const linkedin = createPiece({ + displayName: 'LinkedIn', + description: 'Connect and network with professionals', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/linkedin.png', + categories: [PieceCategory.MARKETING], + authors: ["aasimsani","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + auth: linkedinAuth, + actions: [ + createShareUpdate, + createCompanyUpdate, + createCustomApiCallAction({ + auth: linkedinAuth, + baseUrl: () => { + return linkedinCommon.baseUrl; + }, + authMapping: async (auth) => { + return { + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }; + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/linkedin/src/lib/actions/create-company-update.ts b/packages/pieces/community/linkedin/src/lib/actions/create-company-update.ts new file mode 100644 index 0000000..8070f7d --- /dev/null +++ b/packages/pieces/community/linkedin/src/lib/actions/create-company-update.ts @@ -0,0 +1,71 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { Image, linkedinCommon, santizeText } from '../common'; +import { linkedinAuth } from '../..'; + +export const createCompanyUpdate = createAction({ + auth: linkedinAuth, + name: 'create_company_update', + displayName: 'Create Company Update', + description: 'Create a new company update for Company Page', + props: { + company: linkedinCommon.company, + imageUrl: linkedinCommon.imageUrl, + text: linkedinCommon.text, + link: linkedinCommon.link, + linkTitle: linkedinCommon.linkTitle, + linkDescription: linkedinCommon.linkDescription, + }, + + run: async (context) => { + const imageUrl = context.propsValue.imageUrl; + const bodyConfig: { + urn: string; + text: string; + link?: string | undefined; + linkDescription?: string | undefined; + linkTitle?: string | undefined; + visibility: string; + image?: Image | undefined; + } = { + urn: `organization:${context.propsValue.company}`, + text: santizeText(context.propsValue.text), + link: context.propsValue.link, + linkDescription: context.propsValue.linkDescription, + linkTitle: context.propsValue.linkTitle, + visibility: 'PUBLIC', + }; + + if (imageUrl) { + bodyConfig.image = await linkedinCommon.uploadImage( + context.auth.access_token, + `organization:${context.propsValue.company}`, + imageUrl + ); + } + + const requestBody = linkedinCommon.generatePostRequestBody(bodyConfig); + const createPostHeaders: any = linkedinCommon.linkedinHeaders; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${linkedinCommon.baseUrl}/rest/posts`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + headers: createPostHeaders, + body: requestBody, + }; + + const response = await httpClient.sendRequest(request); + return { + success: true, + }; + }, +}); diff --git a/packages/pieces/community/linkedin/src/lib/actions/create-share-update.ts b/packages/pieces/community/linkedin/src/lib/actions/create-share-update.ts new file mode 100644 index 0000000..f8b341a --- /dev/null +++ b/packages/pieces/community/linkedin/src/lib/actions/create-share-update.ts @@ -0,0 +1,69 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { Image, linkedinCommon, santizeText } from '../common'; +import jwt, { JwtPayload } from 'jsonwebtoken'; +import { linkedinAuth } from '../..'; + +export const createShareUpdate = createAction({ + auth: linkedinAuth, + name: 'create_share_update', + displayName: 'Create Share Update', + description: 'Create a share update on LinkedIn', + props: { + text: linkedinCommon.text, + visibility: linkedinCommon.visibility, + imageUrl: linkedinCommon.imageUrl, + link: linkedinCommon.link, + linkTitle: linkedinCommon.linkTitle, + linkDescription: linkedinCommon.linkDescription, + }, + + run: async (context) => { + + const token = context.auth.data.id_token; + const decoded: JwtPayload = jwt.decode(token) as JwtPayload; + const imageUrl = context.propsValue.imageUrl; + const { text, link, linkDescription, linkTitle, visibility } = + context.propsValue; + let image: Image | undefined; + if (imageUrl) { + image = await linkedinCommon.uploadImage( + context.auth.access_token, + `person:${decoded.sub}`, + imageUrl + ); + } + + const requestBody = linkedinCommon.generatePostRequestBody({ + urn: `person:${decoded.sub}`, + text: santizeText(text), + link, + linkDescription, + linkTitle, + visibility, + image, + }); + const createPostHeaders: any = linkedinCommon.linkedinHeaders; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${linkedinCommon.baseUrl}/rest/posts`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + headers: createPostHeaders, + body: requestBody, + }; + + const response = await httpClient.sendRequest(request); + return { + success: true, + }; + }, +}); diff --git a/packages/pieces/community/linkedin/src/lib/common/index.ts b/packages/pieces/community/linkedin/src/lib/common/index.ts new file mode 100644 index 0000000..a8f61d1 --- /dev/null +++ b/packages/pieces/community/linkedin/src/lib/common/index.ts @@ -0,0 +1,276 @@ +import { ApFile, Property } from '@activepieces/pieces-framework'; +import { + HttpMethod, + httpClient, + AuthenticationType, +} from '@activepieces/pieces-common'; + +import FormData from 'form-data'; + +export const santizeText = (text: string) => { + // LinkedIn Posts API has a list of characters that need to be escaped since it's type is "LittleText" + // https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api?view=li-lms-2023-11&tabs=http + // https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/little-text-format?view=li-lms-2023-11 + // eslint-disable-next-line no-useless-escape + return text.replace(/[\(*\)\[\]\{\}<>@|~_]/gm, (x: string) => '\\' + x); +}; + +export const linkedinCommon = { + baseUrl: 'https://api.linkedin.com', + linkedinHeaders: { + 'X-Restli-Protocol-Version': '2.0.0', + 'LinkedIn-Version': '202411', + }, + text: Property.LongText({ + displayName: 'Text', + required: true, + }), + imageUrl: Property.File({ + displayName: 'Image', + required: false, + }), + link: Property.ShortText({ + displayName: 'Content - URL', + required: false, + }), + linkTitle: Property.ShortText({ + displayName: 'Content - Title', + required: false, + }), + linkDescription: Property.ShortText({ + displayName: 'Content - Description', + required: false, + }), + visibility: Property.Dropdown({ + displayName: 'Visibility', + refreshers: [], + required: true, + options: async () => { + return { + options: [ + { + label: 'Public', + value: 'PUBLIC', + }, + { + label: 'Connections Only', + value: 'CONNECTIONS', + }, + ], + }; + }, + }), + + company: Property.Dropdown({ + displayName: 'Company Page', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account', + options: [], + }; + } + const authProp = auth as { access_token: string }; + + const companies: any = await linkedinCommon.getCompanies( + authProp.access_token + ); + const options = []; + for (const company in companies) { + options.push({ + label: companies[company].localizedName, + value: companies[company].id, + }); + } + + return { + options: options, + }; + }, + }), + + getCompanies: async (accessToken: string) => { + const companies = ( + await httpClient.sendRequest({ + url: `${linkedinCommon.baseUrl}/v2/organizationalEntityAcls`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + queryParams: { + q: 'roleAssignee', + }, + }) + ).body; + + const companyIds = companies.elements.map( + (company: { organizationalTarget: string }) => { + return company.organizationalTarget.substr( + company.organizationalTarget.lastIndexOf(':') + 1 + ); + } + ); + + const response = await fetch(`${linkedinCommon.baseUrl}/rest/organizations?ids=List(${companyIds.join(',')})`, { + method: 'GET', + headers: { + ...linkedinCommon.linkedinHeaders, + 'Authorization': `Bearer ${accessToken}` + } + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const companySearch = await response.json(); + + return companySearch.results; + }, + + generatePostRequestBody: (data: { + urn: string; + text: any; + link?: string | undefined; + linkTitle?: string | undefined; + linkDescription?: string | undefined; + visibility: string; + image?: Image | undefined; + }) => { + const requestObject: Post = { + author: `urn:li:${data.urn}`, + lifecycleState: 'PUBLISHED', + commentary: data.text, + distribution: { + feedDistribution: 'MAIN_FEED', + }, + visibility: data.visibility, + isReshareDisabledByAuthor: false, + }; + + if (data.link) { + requestObject.content = { + article: { + source: data.link, + title: data.linkTitle, + description: data.linkDescription, + thumbnail: data.image?.value.image, + }, + }; + } else if (data.image) { + requestObject.content = { + media: { + id: data.image.value.image, + }, + }; + } + + return requestObject; + }, + + uploadImage: async ( + accessToken: string, + urn: string, + image: ApFile + ): Promise => { + const uploadData = ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${linkedinCommon.baseUrl}/v2/images`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + queryParams: { + action: 'initializeUpload', + }, + body: { + initializeUploadRequest: { + owner: `urn:li:${urn}`, + }, + }, + }) + ).body as Image; + + const uploadFormData = new FormData(); + const { filename, base64 } = image; + uploadFormData.append('file', Buffer.from(base64, 'base64'), filename); + + await httpClient.sendRequest({ + url: uploadData.value.uploadUrl, + method: HttpMethod.POST, + body: uploadFormData, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + return uploadData; + }, +}; +export interface UgcPost { + author: string; + lifecycleState: string; + specificContent: { + 'com.linkedin.ugc.ShareContent': { + shareCommentary: { + text?: string; + }; + shareMediaCategory: string; + media?: [ + { + status: string; + description?: { + text: string; + }; + originalUrl?: string; + thumbnail?: string; + title?: { + text: string; + }; + } + ]; + }; + }; + visibility: { + 'com.linkedin.ugc.MemberNetworkVisibility': string; + }; +} + +export interface Post { + author: string; + commentary: string; + lifecycleState: string; + visibility: string; + distribution: { + feedDistribution: string; + }; + content?: { + article?: { + source: string; + thumbnail?: string | undefined; + title?: string | undefined; + description?: string | undefined; + }; + media?: { + id: string; + }; + }; + isReshareDisabledByAuthor: boolean; +} + +export interface Image { + value: { + uploadUrlExpiresAt: number; + uploadUrl: string; + image: string; + }; +} diff --git a/packages/pieces/community/linkedin/tsconfig.json b/packages/pieces/community/linkedin/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/linkedin/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/linkedin/tsconfig.lib.json b/packages/pieces/community/linkedin/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/linkedin/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/llmrails/.eslintrc.json b/packages/pieces/community/llmrails/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/llmrails/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/llmrails/README.md b/packages/pieces/community/llmrails/README.md new file mode 100644 index 0000000..7897806 --- /dev/null +++ b/packages/pieces/community/llmrails/README.md @@ -0,0 +1,7 @@ +# pieces-llmrails + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-llmrails` to build the library. diff --git a/packages/pieces/community/llmrails/package.json b/packages/pieces/community/llmrails/package.json new file mode 100644 index 0000000..eca8816 --- /dev/null +++ b/packages/pieces/community/llmrails/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-llmrails", + "version": "0.0.7" +} \ No newline at end of file diff --git a/packages/pieces/community/llmrails/project.json b/packages/pieces/community/llmrails/project.json new file mode 100644 index 0000000..f83d490 --- /dev/null +++ b/packages/pieces/community/llmrails/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-llmrails", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/llmrails/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/llmrails", + "tsConfig": "packages/pieces/community/llmrails/tsconfig.lib.json", + "packageJson": "packages/pieces/community/llmrails/package.json", + "main": "packages/pieces/community/llmrails/src/index.ts", + "assets": [ + "packages/pieces/community/llmrails/*.md", + { + "input": "packages/pieces/community/llmrails/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-llmrails {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/llmrails/src/index.ts b/packages/pieces/community/llmrails/src/index.ts new file mode 100644 index 0000000..d6cfe61 --- /dev/null +++ b/packages/pieces/community/llmrails/src/index.ts @@ -0,0 +1,51 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { datastoreSearch } from './lib/actions/datastore-search'; + +const markdownDescription = ` +Follow these instructions to get your LLMRails API Key: + +1. Visit the following website: https://console.llmrails.com/api-keys. +2. Once on the website, click on create a key. +3. Once you have created a key, copy it and use it for the Api key field on the site. +`; + +export const llmrailsAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: markdownDescription, + required: true, + validate: async ({ auth }) => { + if (auth.startsWith('api_')) { + return { + valid: true, + }; + } + return { + valid: false, + error: 'Invalid Api Key', + }; + }, +}); + +export const llmrails = createPiece({ + displayName: 'LLMRails', + description: 'LLM Rails Platform', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/llmrails.png', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + authors: ["w95","kishanprmr","MoShizzle","abuaboud"], + auth: llmrailsAuth, + actions: [ + datastoreSearch, + createCustomApiCallAction({ + baseUrl: () => 'https://api.llmrails.com/v1', + auth: llmrailsAuth, + authMapping: async (auth) => ({ + 'X-API-KEY': auth as string, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/llmrails/src/lib/actions/datastore-search.ts b/packages/pieces/community/llmrails/src/lib/actions/datastore-search.ts new file mode 100644 index 0000000..86c207e --- /dev/null +++ b/packages/pieces/community/llmrails/src/lib/actions/datastore-search.ts @@ -0,0 +1,57 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { llmrailsAuth } from '../..'; + +export const datastoreSearch = createAction({ + auth: llmrailsAuth, + name: 'search', + displayName: 'Datastore search', + description: 'Search in datastore', + props: { + datastoreId: Property.ShortText({ + displayName: 'Datastore ID', + required: true, + }), + text: Property.ShortText({ + displayName: 'Text', + required: true, + description: 'Search query', + }), + hybrid: Property.Checkbox({ + displayName: 'Hybrid search', + description: 'Hybrid search is combining dense and sparse embeddings.', + required: true, + defaultValue: true, + }), + summarize: Property.Checkbox({ + displayName: 'Summarize', + description: 'Summarizes the datastore search results', + required: true, + defaultValue: true, + }), + }, + async run(context) { + const datastoreId = context.propsValue.datastoreId; + + const request: HttpRequest = { + url: `https://api.llmrails.com/v1/datastores/${datastoreId}/search`, + method: HttpMethod.POST, + body: { + text: context.propsValue.text, + summarize: context.propsValue.summarize, + hybrid: context.propsValue.hybrid, + }, + headers: { + 'X-API-KEY': context.auth, + Accept: 'application/json', + }, + }; + const response = await httpClient.sendRequest(request); + + return response.body; + }, +}); diff --git a/packages/pieces/community/llmrails/tsconfig.json b/packages/pieces/community/llmrails/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/llmrails/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/llmrails/tsconfig.lib.json b/packages/pieces/community/llmrails/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/llmrails/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/localai/.babelrc b/packages/pieces/community/localai/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/localai/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/localai/.eslintrc.json b/packages/pieces/community/localai/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/localai/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/localai/README.md b/packages/pieces/community/localai/README.md new file mode 100644 index 0000000..3eebdbe --- /dev/null +++ b/packages/pieces/community/localai/README.md @@ -0,0 +1,7 @@ +# pieces-localai + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-localai` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/localai/package.json b/packages/pieces/community/localai/package.json new file mode 100644 index 0000000..dd6face --- /dev/null +++ b/packages/pieces/community/localai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-localai", + "version": "0.0.7" +} \ No newline at end of file diff --git a/packages/pieces/community/localai/project.json b/packages/pieces/community/localai/project.json new file mode 100644 index 0000000..4a15424 --- /dev/null +++ b/packages/pieces/community/localai/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-localai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/localai/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/localai", + "tsConfig": "packages/pieces/community/localai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/localai/package.json", + "main": "packages/pieces/community/localai/src/index.ts", + "assets": [ + "packages/pieces/community/localai/*.md", + { + "input": "packages/pieces/community/localai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/localai/src/index.ts b/packages/pieces/community/localai/src/index.ts new file mode 100644 index 0000000..b1d3515 --- /dev/null +++ b/packages/pieces/community/localai/src/index.ts @@ -0,0 +1,47 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { askLocalAI } from './lib/actions/send-prompt'; + +export const localaiAuth = PieceAuth.CustomAuth({ + props: { + base_url: Property.ShortText({ + displayName: 'Server URL', + description: 'LocalAI Instance URL', + required: true, + }), + access_token: Property.ShortText({ + displayName: 'Access Token', + description: 'LocalAI Access Token', + required: false, + }), + }, + required: true, +}); +export const openai = createPiece({ + displayName: 'LocalAI', + description: + 'The free, Self-hosted, community-driven and local-first. Drop-in replacement for OpenAI running on consumer-grade hardware. No GPU required.', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/localai.jpeg', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + auth: localaiAuth, + actions: [ + askLocalAI, + createCustomApiCallAction({ + baseUrl: (auth) => (auth as { base_url: string }).base_url, + auth: localaiAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${ + (auth as { access_token: string }).access_token || '' + }`, + }), + }), + ], + authors: ["hkboujrida","kishanprmr","MoShizzle","abuaboud"], + triggers: [], +}); diff --git a/packages/pieces/community/localai/src/lib/actions/send-prompt.ts b/packages/pieces/community/localai/src/lib/actions/send-prompt.ts new file mode 100644 index 0000000..ccd3fe1 --- /dev/null +++ b/packages/pieces/community/localai/src/lib/actions/send-prompt.ts @@ -0,0 +1,224 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import OpenAI from 'openai'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { localaiAuth } from '../..'; +import { json } from 'stream/consumers'; + +const billingIssueMessage = `Error Occurred: 429 \n + +1. Set LocalAI API Url. \n +2. Generate a new API key (optional). \n +3. Attempt the process again. \n + +For guidance, visit: https://localai.io/`; + +const unaurthorizedMessage = `Error Occurred: 401 \n + +Ensure that your API key is valid. \n +`; + +export const askLocalAI = createAction({ + auth: localaiAuth, + name: 'ask_localai', + displayName: 'Ask LocalAI', + description: 'Ask LocalAI anything you want!', + props: { + model: Property.Dropdown({ + displayName: 'Model', + required: true, + description: + 'The model which will generate the completion. Some models are suitable for natural language tasks, others specialize in code.', + refreshers: [], + defaultValue: 'gpt-3.5-turbo', + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Enter your api key first', + options: [], + }; + } + try { + const response = await httpClient.sendRequest<{ + data: { id: string }[]; + }>({ + url: (auth).base_url + '/models', + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: (auth).access_token as string, + }, + }); + return { + disabled: false, + options: response.body.data.map((model) => { + return { + label: model.id, + value: model.id, + }; + }), + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: "Couldn't Load Models", + }; + } + }, + }), + prompt: Property.LongText({ + displayName: 'Question', + required: true, + }), + temperature: Property.Number({ + displayName: 'Temperature', + required: false, + description: + 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.', + }), + maxTokens: Property.Number({ + displayName: 'Maximum Tokens', + required: false, + description: + "The maximum number of tokens to generate. Requests can use up to 2,048 or 4,096 tokens shared between prompt and completion, don't set the value to maximum and leave some tokens for the input. The exact limit varies by model. (One token is roughly 4 characters for normal English text)", + }), + topP: Property.Number({ + displayName: 'Top P', + required: false, + description: + 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.', + }), + frequencyPenalty: Property.Number({ + displayName: 'Frequency penalty', + required: false, + description: + "Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.", + }), + presencePenalty: Property.Number({ + displayName: 'Presence penalty', + required: false, + description: + "Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the mode's likelihood to talk about new topics.", + }), + roles: Property.Json({ + displayName: 'Roles', + required: false, + description: 'Array of roles to specify more accurate response', + defaultValue: [ + { role: 'system', content: 'You are a helpful assistant.' }, + ], + }), + }, + async run({ auth, propsValue }) { + const openai = new OpenAI({ + baseURL: auth.base_url, + apiKey: auth.access_token, + }); + let billingIssue = false; + let unaurthorized = false; + let model = 'gpt-3.5-turbo'; + if (propsValue.model) { + model = propsValue.model; + } + let temperature = 0.9; + if (propsValue.temperature) { + temperature = Number(propsValue.temperature); + } + let maxTokens = 2048; + if (propsValue.maxTokens) { + maxTokens = Number(propsValue.maxTokens); + } + let topP = 1; + if (propsValue.topP) { + topP = Number(propsValue.topP); + } + let frequencyPenalty = 0.0; + if (propsValue.frequencyPenalty) { + frequencyPenalty = Number(propsValue.frequencyPenalty); + } + let presencePenalty = 0.6; + if (propsValue.presencePenalty) { + presencePenalty = Number(propsValue.presencePenalty); + } + + const rolesArray = propsValue.roles + ? (propsValue.roles as unknown as any[]) + : []; + const roles = rolesArray.map((item) => { + const rolesEnum = ['system', 'user', 'assistant']; + if (!rolesEnum.includes(item.role)) { + throw new Error( + 'The only available roles are: [system, user, assistant]' + ); + } + + return { + role: item.role, + content: item.content, + }; + }); + + const maxRetries = 4; + let retries = 0; + let response: string | undefined; + while (retries < maxRetries) { + try { + response = ( + await openai.chat.completions.create({ + model: model, + messages: [ + ...roles, + { + role: 'user', + content: propsValue['prompt'], + }, + ], + temperature: temperature, + max_tokens: maxTokens, + top_p: topP, + frequency_penalty: frequencyPenalty, + presence_penalty: presencePenalty, + }) + )?.choices[0]?.message?.content?.trim(); + break; // Break out of the loop if the request is successful + } catch (error: any) { + if (error?.message?.includes('code 429')) { + billingIssue = true; + if (retries + 1 === maxRetries) { + throw error; + } + // Calculate the time delay for the next retry using exponential backoff + const delay = Math.pow(6, retries) * 1000; + console.log(`Retrying in ${delay} milliseconds...`); + await sleep(delay); // Wait for the calculated delay + retries++; + break; + } else { + if (error?.message?.includes('code 401')) { + unaurthorized = true; + } + throw error; + } + } + } + if (billingIssue) { + throw new Error(billingIssueMessage); + } + if (unaurthorized) { + throw new Error(unaurthorizedMessage); + } + return response; + }, +}); + +function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/packages/pieces/community/localai/tsconfig.json b/packages/pieces/community/localai/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/localai/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/localai/tsconfig.lib.json b/packages/pieces/community/localai/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/localai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/lusha/.eslintrc.json b/packages/pieces/community/lusha/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/lusha/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/lusha/README.md b/packages/pieces/community/lusha/README.md new file mode 100644 index 0000000..b972dd1 --- /dev/null +++ b/packages/pieces/community/lusha/README.md @@ -0,0 +1,7 @@ +# pieces-lusha + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-lusha` to build the library. diff --git a/packages/pieces/community/lusha/package.json b/packages/pieces/community/lusha/package.json new file mode 100644 index 0000000..74c11c5 --- /dev/null +++ b/packages/pieces/community/lusha/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-lusha", + "version": "0.0.2" +} diff --git a/packages/pieces/community/lusha/project.json b/packages/pieces/community/lusha/project.json new file mode 100644 index 0000000..493e083 --- /dev/null +++ b/packages/pieces/community/lusha/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-lusha", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/lusha/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/lusha", + "tsConfig": "packages/pieces/community/lusha/tsconfig.lib.json", + "packageJson": "packages/pieces/community/lusha/package.json", + "main": "packages/pieces/community/lusha/src/index.ts", + "assets": [ + "packages/pieces/community/lusha/*.md", + { + "input": "packages/pieces/community/lusha/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/lusha/src/index.ts b/packages/pieces/community/lusha/src/index.ts new file mode 100644 index 0000000..4461f24 --- /dev/null +++ b/packages/pieces/community/lusha/src/index.ts @@ -0,0 +1,33 @@ +import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { searchCompanies } from "./lib/actions/companies/search"; +import { enrichCompanies } from "./lib/actions/companies/enrich"; + +export const lushaAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'Please enter the API Key obtained from Lusha.', +}); + +export const lusha = createPiece({ + displayName: "Lusha", + auth: lushaAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: "https://cdn.activepieces.com/pieces/lusha.png", + authors: ["Kevinyu-alan"], + actions: [ + searchCompanies, + enrichCompanies, + createCustomApiCallAction({ + baseUrl: () => { + return 'https://api.lusha.com'; + }, + auth: lushaAuth, + authMapping: async (auth) => ({ + 'x-app': 'activepieces', + 'api_key': auth as string, + }), + }) + ], + triggers: [], +}); \ No newline at end of file diff --git a/packages/pieces/community/lusha/src/lib/actions/companies/enrich.ts b/packages/pieces/community/lusha/src/lib/actions/companies/enrich.ts new file mode 100644 index 0000000..743df44 --- /dev/null +++ b/packages/pieces/community/lusha/src/lib/actions/companies/enrich.ts @@ -0,0 +1,46 @@ +import { createAction, Property } from "@activepieces/pieces-framework"; + +export const enrichCompanies = createAction({ + name: 'enrich_companies', + displayName: 'Enrich Companies', + description: 'Enrich companies details using requestId and company IDs from search results', + props: { + searchResults: Property.Json({ + displayName: 'Search Results', + description: 'The results from the Search Companies step', + required: true, + }), + }, + async run(context) { + const searchResults = context.propsValue.searchResults as { + requestId: string; + data: Array<{ id: string }>; + }; + + const requestId = searchResults.requestId; + const companyIds = searchResults.data.map(item => item.id); + + const payload = { + requestId: requestId, + companiesIds: companyIds + }; + + const response = await fetch('https://api.lusha.com/prospecting/company/enrich', { + method: 'POST', + headers: { + 'x-app': 'activepieces', + 'x-api-key': context.auth as string, + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify(payload) + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`HTTP error! status: ${response.status}, body: ${errorText}`); + } + + return await response.json(); + } +}); \ No newline at end of file diff --git a/packages/pieces/community/lusha/src/lib/actions/companies/search.ts b/packages/pieces/community/lusha/src/lib/actions/companies/search.ts new file mode 100644 index 0000000..c3a5ce6 --- /dev/null +++ b/packages/pieces/community/lusha/src/lib/actions/companies/search.ts @@ -0,0 +1,99 @@ +import { createAction, Property } from "@activepieces/pieces-framework"; + +export const searchCompanies = createAction({ + name: 'search_companies', + displayName: 'Search Companies', + description: 'Search for companies with filters and pagination', + props: { + resultLimit: Property.Number({ + displayName: 'Maximum Results', + description: 'Maximum number of companies to return. The maximum is 10000 results.', + required: true, + defaultValue: 10000 + }), + requestBody: Property.Json({ + displayName: 'Request Body', + description: 'The JSON body for the search request. Do not include pagination (pages), it will be added automatically.', + required: true, + defaultValue: { + "filters": { + "contacts": { + "include": { + "countries": ["ES"] + } + }, + "companies": { + "include": { + "sizes": [ + { + "max": 50, + "min": 11 + } + ], + "locations": [ + { + "country": "Spain" + } + ], + "mainIndustriesIds": [17, 14] + } + } + } + } + }) + }, + async run(context) { + const { resultLimit, requestBody } = context.propsValue; + + if (resultLimit > 10000) + throw new Error(`The maximum of result is 10000`); + + // the maximum number of result is 10000 + let allResults: any[] = [] + let currentPage = 0 + let hasMorePages = true + + while (hasMorePages && allResults.length < resultLimit) { + const payload = { + ...requestBody, + pages: { + page: currentPage, + size: 40 // the maximum for the Lusha API + } + }; + + const response = await fetch('https://api.lusha.com/prospecting/company/search', { + method: 'POST', + headers: { + 'x-app': 'activepieces', + 'x-api-key': context.auth as string, + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify(payload) + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`HTTP error! status: ${response.status}, body: ${errorText}`); + } + + const responseData = await response.json(); + + if (!responseData.data || responseData.data.length === 0) { + hasMorePages = false; + } else { + const remainingNeeded = resultLimit - allResults.length; + if (remainingNeeded < 40) { + allResults = allResults.concat(responseData.data.slice(0, remainingNeeded)); + } else { + allResults = allResults.concat(responseData.data); + } + } + + currentPage++; + } + + return allResults; + } +}); diff --git a/packages/pieces/community/lusha/tsconfig.json b/packages/pieces/community/lusha/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/lusha/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/lusha/tsconfig.lib.json b/packages/pieces/community/lusha/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/lusha/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/mailchain/.eslintrc.json b/packages/pieces/community/mailchain/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/mailchain/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/mailchain/README.md b/packages/pieces/community/mailchain/README.md new file mode 100644 index 0000000..17232f8 --- /dev/null +++ b/packages/pieces/community/mailchain/README.md @@ -0,0 +1,7 @@ +# pieces-mailchain + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-mailchain` to build the library. diff --git a/packages/pieces/community/mailchain/package-lock.json b/packages/pieces/community/mailchain/package-lock.json new file mode 100644 index 0000000..202a9f9 --- /dev/null +++ b/packages/pieces/community/mailchain/package-lock.json @@ -0,0 +1,1249 @@ +{ + "name": "@activepieces/piece-mailchain", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-mailchain", + "version": "0.0.1", + "dependencies": { + "@mailchain/sdk": "^0.31.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.1", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ethersproject/abstract-provider": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/networks": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/web": "^5.8.0" + } + }, + "node_modules/@ethersproject/abstract-signer": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0" + } + }, + "node_modules/@ethersproject/address": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/rlp": "^5.8.0" + } + }, + "node_modules/@ethersproject/base64": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@ethersproject/bytes": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.7.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/keccak256": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT" + }, + "node_modules/@ethersproject/networks": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/properties": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/rlp": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/signing-key": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "bn.js": "^5.2.1", + "elliptic": "6.6.1", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/rlp": "^5.8.0", + "@ethersproject/signing-key": "^5.8.0" + } + }, + "node_modules/@ethersproject/web": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/base64": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@mailchain/addressing": { + "version": "0.31.0", + "license": "Apache-2.0", + "dependencies": { + "@ethersproject/transactions": "^5.7.0", + "@mailchain/crypto": "0.31.0", + "@mailchain/encoding": "0.31.0", + "@noble/hashes": "^1.3.0", + "lodash": "^4.17.21" + } + }, + "node_modules/@mailchain/api": { + "version": "0.31.0", + "license": "Apache-2.0", + "dependencies": { + "@mailchain/crypto": "0.31.0", + "@mailchain/encoding": "0.31.0", + "@noble/hashes": "^1.3.0", + "axios": "1.6.0", + "canonicalize": "^1.0.8", + "lodash": "^4.17.21" + } + }, + "node_modules/@mailchain/crypto": { + "version": "0.31.0", + "license": "Apache-2.0", + "dependencies": { + "@ethersproject/hash": "5.7.0", + "@ethersproject/signing-key": "5.6.2", + "@mailchain/encoding": "0.31.0", + "@noble/curves": "^0.8.2", + "@noble/hashes": "^1.3.0", + "@scure/bip39": "^1.2.0", + "bn.js": "^5.2.1", + "ed2curve": "^0.3.0", + "secp256k1": "^4.0.2", + "tweetnacl": "^1.0.3" + }, + "engines": { + "node": ">=15.0.0" + } + }, + "node_modules/@mailchain/crypto/node_modules/@ethersproject/signing-key": { + "version": "5.6.2", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/properties": "^5.6.0", + "bn.js": "^5.2.1", + "elliptic": "6.5.4", + "hash.js": "1.1.7" + } + }, + "node_modules/@mailchain/crypto/node_modules/elliptic": { + "version": "6.5.4", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/@mailchain/crypto/node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.2", + "license": "MIT" + }, + "node_modules/@mailchain/encoding": { + "version": "0.31.0", + "license": "Apache-2.0", + "dependencies": { + "@noble/hashes": "^1.3.0", + "@scure/base": "^1.1.1" + } + }, + "node_modules/@mailchain/internal": { + "version": "0.31.0", + "license": "Apache-2.0", + "dependencies": { + "@mailchain/addressing": "0.31.0", + "@mailchain/api": "0.31.0", + "@mailchain/crypto": "0.31.0", + "@mailchain/encoding": "0.31.0", + "@mailchain/keyring": "0.31.0", + "@mailchain/message-composer": "0.31.0", + "@mailchain/signatures": "0.31.0", + "@noble/hashes": "^1.3.0", + "axios": "1.6.0", + "canonicalize": "^1.0.8", + "did-jwt-vc": "3.1.0", + "did-resolver": "^4.1.0", + "email-addresses": "5.0.0", + "emailjs-mime-parser": "^2.0.7", + "lodash": "^4.17.21", + "long": "^5.2.1", + "protobufjs": "^7.2.5", + "striptags": "^3.2.0" + } + }, + "node_modules/@mailchain/keyring": { + "version": "0.31.0", + "license": "Apache-2.0", + "dependencies": { + "@mailchain/addressing": "0.31.0", + "@mailchain/crypto": "0.31.0", + "@mailchain/encoding": "0.31.0" + } + }, + "node_modules/@mailchain/message-composer": { + "version": "0.31.0", + "license": "Apache-2.0", + "dependencies": { + "@mailchain/encoding": "0.31.0", + "date-fns": "^2.29.2" + }, + "engines": { + "node": ">=15.x" + } + }, + "node_modules/@mailchain/sdk": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@mailchain/sdk/-/sdk-0.31.0.tgz", + "integrity": "sha512-kZXB0X+okxTCzCA/SSc1pxTMhAHAKDlW49KCqxG/ei6mIHFf2dajpWpGPB7Dr0aMLimmA/6H4h7vA50A26qQ9Q==", + "dependencies": { + "@mailchain/addressing": "0.31.0", + "@mailchain/crypto": "0.31.0", + "@mailchain/encoding": "0.31.0", + "@mailchain/internal": "0.31.0", + "@mailchain/keyring": "0.31.0" + }, + "engines": { + "node": ">=15.0.0" + } + }, + "node_modules/@mailchain/signatures": { + "version": "0.31.0", + "license": "Apache-2.0", + "dependencies": { + "@ethersproject/hash": "5.7.0", + "@mailchain/addressing": "0.31.0", + "@mailchain/crypto": "0.31.0", + "@mailchain/encoding": "0.31.0", + "@noble/hashes": "^1.3.0", + "canonicalize": "^1.0.8", + "lodash": "^4.17.21" + } + }, + "node_modules/@noble/curves": { + "version": "0.8.3", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.0" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.3.0", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "license": "BSD-3-Clause" + }, + "node_modules/@scure/base": { + "version": "1.2.5", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@stablelib/aead": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/@stablelib/binary": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "@stablelib/int": "^1.0.1" + } + }, + "node_modules/@stablelib/bytes": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/@stablelib/chacha": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "@stablelib/binary": "^1.0.1", + "@stablelib/wipe": "^1.0.1" + } + }, + "node_modules/@stablelib/chacha20poly1305": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "@stablelib/aead": "^1.0.1", + "@stablelib/binary": "^1.0.1", + "@stablelib/chacha": "^1.0.1", + "@stablelib/constant-time": "^1.0.1", + "@stablelib/poly1305": "^1.0.1", + "@stablelib/wipe": "^1.0.1" + } + }, + "node_modules/@stablelib/constant-time": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/@stablelib/ed25519": { + "version": "1.0.3", + "license": "MIT", + "dependencies": { + "@stablelib/random": "^1.0.2", + "@stablelib/sha512": "^1.0.1", + "@stablelib/wipe": "^1.0.1" + } + }, + "node_modules/@stablelib/hash": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/@stablelib/int": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/@stablelib/keyagreement": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "@stablelib/bytes": "^1.0.1" + } + }, + "node_modules/@stablelib/poly1305": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "@stablelib/constant-time": "^1.0.1", + "@stablelib/wipe": "^1.0.1" + } + }, + "node_modules/@stablelib/random": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "@stablelib/binary": "^1.0.1", + "@stablelib/wipe": "^1.0.1" + } + }, + "node_modules/@stablelib/sha256": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "@stablelib/binary": "^1.0.1", + "@stablelib/hash": "^1.0.1", + "@stablelib/wipe": "^1.0.1" + } + }, + "node_modules/@stablelib/sha512": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "@stablelib/binary": "^1.0.1", + "@stablelib/hash": "^1.0.1", + "@stablelib/wipe": "^1.0.1" + } + }, + "node_modules/@stablelib/wipe": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/@stablelib/x25519": { + "version": "1.0.3", + "license": "MIT", + "dependencies": { + "@stablelib/keyagreement": "^1.0.1", + "@stablelib/random": "^1.0.2", + "@stablelib/wipe": "^1.0.1" + } + }, + "node_modules/@stablelib/xchacha20": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "@stablelib/binary": "^1.0.1", + "@stablelib/chacha": "^1.0.1", + "@stablelib/wipe": "^1.0.1" + } + }, + "node_modules/@stablelib/xchacha20poly1305": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "@stablelib/aead": "^1.0.1", + "@stablelib/chacha20poly1305": "^1.0.1", + "@stablelib/constant-time": "^1.0.1", + "@stablelib/wipe": "^1.0.1", + "@stablelib/xchacha20": "^1.0.1" + } + }, + "node_modules/@types/node": { + "version": "22.15.16", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.0", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/bech32": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==" + }, + "node_modules/brorand": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/canonicalize": { + "version": "1.0.8", + "license": "Apache-2.0" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/date-fns": { + "version": "2.30.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/did-jwt": { + "version": "6.11.6", + "license": "Apache-2.0", + "dependencies": { + "@stablelib/ed25519": "^1.0.2", + "@stablelib/random": "^1.0.1", + "@stablelib/sha256": "^1.0.1", + "@stablelib/x25519": "^1.0.2", + "@stablelib/xchacha20poly1305": "^1.0.1", + "bech32": "^2.0.0", + "canonicalize": "^2.0.0", + "did-resolver": "^4.0.0", + "elliptic": "^6.5.4", + "js-sha3": "^0.8.0", + "multiformats": "^9.6.5", + "uint8arrays": "^3.0.0" + } + }, + "node_modules/did-jwt-vc": { + "version": "3.1.0", + "license": "ISC", + "dependencies": { + "did-jwt": "^6.6.0", + "did-resolver": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/did-jwt/node_modules/canonicalize": { + "version": "2.1.0", + "license": "Apache-2.0", + "bin": { + "canonicalize": "bin/canonicalize.js" + } + }, + "node_modules/did-resolver": { + "version": "4.1.0", + "license": "Apache-2.0" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ed2curve": { + "version": "0.3.0", + "license": "Unlicense", + "dependencies": { + "tweetnacl": "1.x.x" + } + }, + "node_modules/elliptic": { + "version": "6.6.1", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.2", + "license": "MIT" + }, + "node_modules/email-addresses": { + "version": "5.0.0", + "license": "MIT" + }, + "node_modules/emailjs-addressparser": { + "version": "2.0.3", + "license": "MIT" + }, + "node_modules/emailjs-base64": { + "version": "1.1.4", + "license": "MIT" + }, + "node_modules/emailjs-mime-codec": { + "version": "2.0.9", + "license": "MIT", + "dependencies": { + "emailjs-base64": "^1.1.4", + "ramda": "^0.26.1", + "text-encoding": "^0.7.0" + } + }, + "node_modules/emailjs-mime-parser": { + "version": "2.0.7", + "license": "MIT", + "dependencies": { + "emailjs-addressparser": "^2.0.1", + "emailjs-mime-codec": "^2.0.8", + "ramda": "^0.26.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/long": { + "version": "5.3.2", + "license": "Apache-2.0" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/multiformats": { + "version": "9.9.0", + "license": "(Apache-2.0 AND MIT)" + }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/protobufjs": { + "version": "7.4.0", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/ramda": { + "version": "0.26.1", + "license": "MIT" + }, + "node_modules/secp256k1": { + "version": "4.0.4", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "elliptic": "^6.5.7", + "node-addon-api": "^5.0.0", + "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/striptags": { + "version": "3.2.0", + "license": "MIT" + }, + "node_modules/text-encoding": { + "version": "0.7.0", + "license": "(Unlicense OR Apache-2.0)" + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "license": "Unlicense" + }, + "node_modules/uint8arrays": { + "version": "3.1.1", + "license": "MIT", + "dependencies": { + "multiformats": "^9.4.2" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "license": "MIT" + } + } +} diff --git a/packages/pieces/community/mailchain/package.json b/packages/pieces/community/mailchain/package.json new file mode 100644 index 0000000..4b6e7e5 --- /dev/null +++ b/packages/pieces/community/mailchain/package.json @@ -0,0 +1,7 @@ +{ + "name": "@activepieces/piece-mailchain", + "version": "0.0.1", + "dependencies": { + "@mailchain/sdk": "^0.31.0" + } +} diff --git a/packages/pieces/community/mailchain/project.json b/packages/pieces/community/mailchain/project.json new file mode 100644 index 0000000..dcc916d --- /dev/null +++ b/packages/pieces/community/mailchain/project.json @@ -0,0 +1,76 @@ +{ + "name": "pieces-mailchain", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/mailchain/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/mailchain", + "tsConfig": "packages/pieces/community/mailchain/tsconfig.lib.json", + "packageJson": "packages/pieces/community/mailchain/package.json", + "main": "packages/pieces/community/mailchain/src/index.ts", + "assets": [ + "packages/pieces/community/mailchain/*.md", + { + "input": "packages/pieces/community/mailchain/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + }, + "dependsOn": ["prebuild", "^build"] + }, + "prebuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/pieces/community/mailchain", + "command": "npm ci" + } + }, + "build-with-deps": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "nx run pieces-mailchain:prebuild", + "nx run pieces-mailchain:build", + "nx run pieces-mailchain:postbuild" + ], + "parallel": false + } + }, + "postbuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "dist/packages/pieces/community/mailchain", + "command": "npm install" + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/mailchain/src/index.ts b/packages/pieces/community/mailchain/src/index.ts new file mode 100644 index 0000000..0afe302 --- /dev/null +++ b/packages/pieces/community/mailchain/src/index.ts @@ -0,0 +1,18 @@ +import { createPiece } from '@activepieces/pieces-framework'; +import { mailchainCommon } from './lib/common/common'; +import { getAuthenticatedUser } from './lib/actions/get-authenticated-user'; +import { sendEmail } from './lib/actions/send-email'; + +export const mailchain = createPiece({ + displayName: 'Mailchain', + description: + 'Mailchain is a simple, secure, and decentralized communications protocol that enables blockchain-based email.', + auth: mailchainCommon.auth, + minimumSupportedRelease: '0.20.0', + categories: [], + logoUrl: + 'https://cdn.activepieces.com/pieces/mailchain.png', + authors: ['ahmad-swanblocks'], + actions: [getAuthenticatedUser, sendEmail], + triggers: [], +}); \ No newline at end of file diff --git a/packages/pieces/community/mailchain/src/lib/actions/get-authenticated-user.ts b/packages/pieces/community/mailchain/src/lib/actions/get-authenticated-user.ts new file mode 100644 index 0000000..d80fc29 --- /dev/null +++ b/packages/pieces/community/mailchain/src/lib/actions/get-authenticated-user.ts @@ -0,0 +1,27 @@ +import { createAction } from '@activepieces/pieces-framework'; +// eslint-disable-next-line @nx/enforce-module-boundaries +import { Mailchain } from '@mailchain/sdk'; +import { mailchainCommon } from '../common/common'; + +export const getAuthenticatedUser = createAction({ + name: 'getAuthenticatedUser', + displayName: 'Get Authenticated User', + description: 'Get the authenticated user to the Mailchain Protocol', + auth: mailchainCommon.auth, + requireAuth: true, + props: {}, + async run({ auth }) { + try { + const secretRecoveryPhrase = auth; + + const mailchain = + Mailchain.fromSecretRecoveryPhrase(secretRecoveryPhrase); + + const user = await mailchain.user(); + return user; + } catch (error) { + console.error('Error getting authenticated user (mailchain)', error); + throw error; + } + }, +}); diff --git a/packages/pieces/community/mailchain/src/lib/actions/send-email.ts b/packages/pieces/community/mailchain/src/lib/actions/send-email.ts new file mode 100644 index 0000000..d81b507 --- /dev/null +++ b/packages/pieces/community/mailchain/src/lib/actions/send-email.ts @@ -0,0 +1,47 @@ +import { createAction } from '@activepieces/pieces-framework'; +// eslint-disable-next-line @nx/enforce-module-boundaries +import { Mailchain } from '@mailchain/sdk'; +import { mailchainCommon } from '../common/common'; + +export const sendEmail = createAction({ + name: 'sendEmail', + displayName: 'Send Email', + description: 'Send an email to blockchain or mailchain addresses', + auth: mailchainCommon.auth, + requireAuth: true, + props: { + markdown: mailchainCommon.markdown, + to: mailchainCommon.to, + subject: mailchainCommon.subject, + content: mailchainCommon.content, + }, + async run({ auth, propsValue: { to, subject, content } }) { + try { + const secretRecoveryPhrase = auth; + + const mailchain = + Mailchain.fromSecretRecoveryPhrase(secretRecoveryPhrase); + + const user = await mailchain.user(); + console.log(`username: ${user.username}, address: ${user.address}`); + + const { data, error } = await mailchain.sendMail({ + from: user.address, // sender address + to: to as string[], // list of recipients (blockchain or mailchain addresses) + subject: subject, + content: { + text: content, + html: content, + }, + }); + if (error) { + console.error('Error sending email (mailchain)', error); + throw error; + } + return data; + } catch (error) { + console.error('Error sending email (mailchain)', error); + throw error; + } + }, +}); diff --git a/packages/pieces/community/mailchain/src/lib/common/common.ts b/packages/pieces/community/mailchain/src/lib/common/common.ts new file mode 100644 index 0000000..a00bd4b --- /dev/null +++ b/packages/pieces/community/mailchain/src/lib/common/common.ts @@ -0,0 +1,43 @@ +import { PieceAuth, Property } from '@activepieces/pieces-framework'; + +const message = `**Getting Started with Mailchain:** + +To send emails using the Mailchain Protocol, you need to first create an account on [Mailchain](https://app.mailchain.com/). Once your account is set up, you will receive a Mailchain address. You can also connect your wallet to associate a blockchain address with your Mailchain account. + +**Recipients:** + +When sending an email, you can use either a Mailchain address or a blockchain address as the recipient. For blockchain addresses, you can leverage supported web3 domain services, including ENS, Unstoppable Domains, Lens, Coinbase, and many more. + +**Example Usage:** +- **Mailchain Address:** 'yourname@mailchain.com' +- **Blockchain Address:** 'yourname.eth' (ENS), 'yourname.crypto' (Unstoppable Domains), etc. + +To manage your inbox and send emails, visit [Mailchain's Web App](https://app.mailchain.com/). +`; + +export const mailchainCommon = { + auth: PieceAuth.SecretText({ + displayName: 'Secret Recovery Phrase', + description: + 'The secret recovery phrase (25-word mnemonic phrase) to authenticate with the Mailchain Protocol. You can obtain this phrase when setting up your Mailchain account.', + required: true, + }), + markdown: Property.MarkDown({ + value: message, + }), + to: Property.Array({ + displayName: 'To', + description: 'The blockchain or mailchain addresses to send the email to', + required: true, + }), + subject: Property.ShortText({ + displayName: 'Subject', + description: 'The subject of the email', + required: true, + }), + content: Property.LongText({ + displayName: 'Content', + description: 'The content of the email', + required: true, + }), +}; diff --git a/packages/pieces/community/mailchain/tsconfig.json b/packages/pieces/community/mailchain/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/mailchain/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/mailchain/tsconfig.lib.json b/packages/pieces/community/mailchain/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/mailchain/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/mailchimp/.babelrc b/packages/pieces/community/mailchimp/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/mailchimp/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/mailchimp/.eslintrc.json b/packages/pieces/community/mailchimp/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/mailchimp/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/mailchimp/README.md b/packages/pieces/community/mailchimp/README.md new file mode 100644 index 0000000..a36cdae --- /dev/null +++ b/packages/pieces/community/mailchimp/README.md @@ -0,0 +1,7 @@ +# pieces-mailchimp + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-mailchimp` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/mailchimp/package.json b/packages/pieces/community/mailchimp/package.json new file mode 100644 index 0000000..bf4a2bc --- /dev/null +++ b/packages/pieces/community/mailchimp/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-mailchimp", + "version": "0.3.6" +} \ No newline at end of file diff --git a/packages/pieces/community/mailchimp/project.json b/packages/pieces/community/mailchimp/project.json new file mode 100644 index 0000000..9a1f3ac --- /dev/null +++ b/packages/pieces/community/mailchimp/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-mailchimp", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/mailchimp/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/mailchimp", + "tsConfig": "packages/pieces/community/mailchimp/tsconfig.lib.json", + "packageJson": "packages/pieces/community/mailchimp/package.json", + "main": "packages/pieces/community/mailchimp/src/index.ts", + "assets": [ + "packages/pieces/community/mailchimp/*.md", + { + "input": "packages/pieces/community/mailchimp/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/mailchimp/src/index.ts b/packages/pieces/community/mailchimp/src/index.ts new file mode 100644 index 0000000..1a86ce9 --- /dev/null +++ b/packages/pieces/community/mailchimp/src/index.ts @@ -0,0 +1,37 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { addMemberToList } from './lib/actions/add-member-to-list'; +import { addNoteToSubscriber } from './lib/actions/add-note-to-subscriber'; +import { removeSubscriberFromTag } from './lib/actions/remove-subscriber-from-tag'; +import { updateSubscriberInList } from './lib/actions/update-subscriber-status'; + +import { PieceCategory } from '@activepieces/shared'; +import { addSubscriberToTag } from './lib/actions/add-subscriber-to-tag'; +import { mailChimpSubscribeTrigger } from './lib/triggers/subscribe-trigger'; +import { mailChimpUnsubscriberTrigger } from './lib/triggers/unsubscribe-trigger'; + +export const mailchimpAuth = PieceAuth.OAuth2({ + description: '', + authUrl: 'https://login.mailchimp.com/oauth2/authorize', + tokenUrl: 'https://login.mailchimp.com/oauth2/token', + required: true, + scope: [], +}); + +export const mailchimp = createPiece({ + displayName: 'Mailchimp', + description: 'All-in-One integrated marketing platform', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/mailchimp.png', + authors: ["abdullahranginwala","TaskMagicKyle","kishanprmr","AbdulTheActivePiecer","khaledmashaly","abuaboud"], + categories: [PieceCategory.MARKETING], + auth: mailchimpAuth, + actions: [ + addMemberToList, + addNoteToSubscriber, + addSubscriberToTag, + removeSubscriberFromTag, + updateSubscriberInList, + ], + triggers: [mailChimpSubscribeTrigger, mailChimpUnsubscriberTrigger], +}); diff --git a/packages/pieces/community/mailchimp/src/lib/actions/add-member-to-list.ts b/packages/pieces/community/mailchimp/src/lib/actions/add-member-to-list.ts new file mode 100644 index 0000000..c8cf61a --- /dev/null +++ b/packages/pieces/community/mailchimp/src/lib/actions/add-member-to-list.ts @@ -0,0 +1,65 @@ +import { mailchimpCommon } from '../common'; +import { MailChimpWebhookType } from '../common/types'; +import mailchimp, { Status } from '@mailchimp/mailchimp_marketing'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mailchimpAuth } from '../..'; + +export const addMemberToList = createAction({ + auth: mailchimpAuth, + name: 'add_member_to_list', + displayName: 'Add Member to an Audience (List)', + description: 'Add a member to an existing Mailchimp audience (list)', + props: { + first_name: Property.ShortText({ + displayName: 'First Name', + description: 'First name of the new contact', + required: false, + }), + last_name: Property.ShortText({ + displayName: 'Last Name', + description: 'Last name of the new contact', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + description: 'Email of the new contact', + required: true, + }), + list_id: mailchimpCommon.mailChimpListIdDropdown, + status: Property.StaticDropdown({ + displayName: 'Status', + required: true, + options: { + disabled: false, + options: [ + { label: 'Subscribed', value: 'subscribed' }, + { label: 'Unsubscribed', value: 'unsubscribed' }, + { label: 'Cleaned', value: MailChimpWebhookType.CLEANED }, + { label: 'Pending', value: MailChimpWebhookType.PENDING }, + { label: 'Transactional', value: MailChimpWebhookType.TRANSACTIONAL }, + ], + }, + }), + }, + async run(context) { + const access_token = context.auth.access_token; + const mailChimpServerPrefix = + await mailchimpCommon.getMailChimpServerPrefix(access_token); + mailchimp.setConfig({ + accessToken: access_token, + server: mailChimpServerPrefix, + }); + try { + return await mailchimp.lists.addListMember(context.propsValue.list_id!, { + email_address: context.propsValue.email!, + status: context.propsValue.status, + merge_fields: { + FNAME: context.propsValue.first_name || '', + LNAME: context.propsValue.last_name || '', + }, + }); + } catch (e) { + throw new Error(JSON.stringify(e)); + } + }, +}); diff --git a/packages/pieces/community/mailchimp/src/lib/actions/add-note-to-subscriber.ts b/packages/pieces/community/mailchimp/src/lib/actions/add-note-to-subscriber.ts new file mode 100644 index 0000000..4a13de4 --- /dev/null +++ b/packages/pieces/community/mailchimp/src/lib/actions/add-note-to-subscriber.ts @@ -0,0 +1,62 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +import { mailchimpAuth } from '../..'; +import { mailchimpCommon } from '../common'; + +export const addNoteToSubscriber = createAction({ + auth: mailchimpAuth, + name: 'add_note_to_subscriber', + displayName: 'Add Note to Subscriber', + description: 'Add a note to a subscriber', + props: { + list_id: mailchimpCommon.mailChimpListIdDropdown, + email: Property.ShortText({ + displayName: 'Email', + description: 'Email of the subscriber', + required: true, + }), + note: Property.LongText({ + displayName: 'Note', + description: 'Note to add to the subscriber', + required: true, + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + note: z.string().max(1000), + }); + + const { list_id, email, note } = context.propsValue; + const { access_token } = context.auth; + + const subscriberHash = mailchimpCommon.getMD5EmailHash(email); + const serverPrefix = await mailchimpCommon.getMailChimpServerPrefix( + access_token + ); + const url = `https://${serverPrefix}.api.mailchimp.com/3.0/lists/${list_id}/members/${subscriberHash}/notes`; + + const request: HttpRequest = { + method: HttpMethod.POST, + url, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: access_token, + }, + body: { note }, + }; + const response = await httpClient.sendRequest(request); + + return response.body; + }, +}); diff --git a/packages/pieces/community/mailchimp/src/lib/actions/add-subscriber-to-tag.ts b/packages/pieces/community/mailchimp/src/lib/actions/add-subscriber-to-tag.ts new file mode 100644 index 0000000..8a2aa4f --- /dev/null +++ b/packages/pieces/community/mailchimp/src/lib/actions/add-subscriber-to-tag.ts @@ -0,0 +1,66 @@ +import { mailchimpCommon } from '../common'; +import crypto from 'crypto'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { mailchimpAuth } from '../..'; + +export const addSubscriberToTag = createAction({ + auth: mailchimpAuth, + name: 'add_subscriber_to_tag', + displayName: 'Add Subscriber to a tag', + description: + 'Adds a subscriber to a tag. This will fail if the user is not subscribed to the audience.', + props: { + list_id: mailchimpCommon.mailChimpListIdDropdown, + email: Property.ShortText({ + displayName: 'Email', + description: 'Email of the subscriber', + required: true, + }), + tag_names: Property.Array({ + displayName: 'Tag Name', + description: 'Tag name to add to the subscriber', + required: true, + }), + }, + async run(context) { + const { list_id, email, tag_names } = context.propsValue; + const { access_token } = context.auth; + + const subscriberHash = crypto + .createHash('md5') + .update(email.toLowerCase()) + .digest('hex'); + const serverPrefix = await mailchimpCommon.getMailChimpServerPrefix( + access_token + ); + const url = `https://${serverPrefix}.api.mailchimp.com/3.0/lists/${list_id}/members/${subscriberHash}/tags`; + + console.log('HELLO ' + url); + const tags = tag_names.map((tag_name) => ({ + name: tag_name, + status: 'active', + })); + + const request: HttpRequest = { + method: HttpMethod.POST, + url, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: access_token, + }, + body: { tags }, + }; + + await httpClient.sendRequest(request); + + return { + success: true, + }; + }, +}); diff --git a/packages/pieces/community/mailchimp/src/lib/actions/remove-subscriber-from-tag.ts b/packages/pieces/community/mailchimp/src/lib/actions/remove-subscriber-from-tag.ts new file mode 100644 index 0000000..e55c0b5 --- /dev/null +++ b/packages/pieces/community/mailchimp/src/lib/actions/remove-subscriber-from-tag.ts @@ -0,0 +1,64 @@ +import { mailchimpCommon } from '../common'; +import { + HttpRequest, + HttpMethod, + httpClient, + AuthenticationType, +} from '@activepieces/pieces-common'; +import crypto from 'crypto'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mailchimpAuth } from '../..'; + +export const removeSubscriberFromTag = createAction({ + auth: mailchimpAuth, + name: 'remove_subscriber_to_tag', + displayName: 'Remove Subscriber from a tag', + description: 'Removes a subscriber from a tag', + props: { + list_id: mailchimpCommon.mailChimpListIdDropdown, + email: Property.ShortText({ + displayName: 'Email', + description: 'Email of the subscriber', + required: true, + }), + tag_names: Property.Array({ + displayName: 'Tag Name', + description: 'Tag name to add to the subscriber', + required: true, + }), + }, + async run(context) { + const { list_id, email, tag_names } = context.propsValue; + const { access_token } = context.auth; + + const subscriberHash = crypto + .createHash('md5') + .update(email.toLowerCase()) + .digest('hex'); + const serverPrefix = await mailchimpCommon.getMailChimpServerPrefix( + access_token + ); + const url = `https://${serverPrefix}.api.mailchimp.com/3.0/lists/${list_id}/members/${subscriberHash}/tags`; + + const tags = tag_names.map((tag_name) => ({ + name: tag_name, + status: 'inactive', // equal to removing a tag + })); + + const request: HttpRequest = { + method: HttpMethod.POST, + url, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: access_token, + }, + body: { tags }, + }; + + await httpClient.sendRequest(request); + + return { + success: true, + }; + }, +}); diff --git a/packages/pieces/community/mailchimp/src/lib/actions/update-subscriber-status.ts b/packages/pieces/community/mailchimp/src/lib/actions/update-subscriber-status.ts new file mode 100644 index 0000000..d6e51e2 --- /dev/null +++ b/packages/pieces/community/mailchimp/src/lib/actions/update-subscriber-status.ts @@ -0,0 +1,51 @@ +import { mailchimpCommon } from '../common'; +import mailchimp from '@mailchimp/mailchimp_marketing'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mailchimpAuth } from '../..'; + +export const updateSubscriberInList = createAction({ + auth: mailchimpAuth, + name: 'update_member_in_list', + displayName: 'Update Member in an Audience (List)', + description: 'Update a member in an existing Mailchimp audience (list)', + props: { + email: Property.ShortText({ + displayName: 'Email', + description: 'Email of the new contact', + required: true, + }), + list_id: mailchimpCommon.mailChimpListIdDropdown, + status: Property.StaticDropdown< + 'subscribed' | 'unsubscribed' | 'cleaned' | 'pending' | 'transactional' + >({ + displayName: 'Status', + required: true, + options: { + disabled: false, + options: [ + { label: 'Subscribed', value: 'subscribed' }, + { label: 'Unsubscribed', value: 'unsubscribed' }, + { label: 'Cleaned', value: 'cleaned' }, + { label: 'Pending', value: 'pending' }, + { label: 'Transactional', value: 'transactional' }, + ], + }, + }), + }, + async run(context) { + const access_token = context.auth.access_token; + const mailChimpServerPrefix = + await mailchimpCommon.getMailChimpServerPrefix(access_token); + mailchimp.setConfig({ + accessToken: access_token, + server: mailChimpServerPrefix, + }); + return await mailchimp.lists.updateListMember( + context.propsValue.list_id!, + context.propsValue.email!, + { + status: context.propsValue.status!, + } + ); + }, +}); diff --git a/packages/pieces/community/mailchimp/src/lib/common/index.ts b/packages/pieces/community/mailchimp/src/lib/common/index.ts new file mode 100644 index 0000000..16d2b5c --- /dev/null +++ b/packages/pieces/community/mailchimp/src/lib/common/index.ts @@ -0,0 +1,134 @@ +import { Property, OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import mailchimp from '@mailchimp/mailchimp_marketing'; +import { AuthenticationType } from '@activepieces/pieces-common'; +import crypto from 'crypto'; + +export const mailchimpCommon = { + mailChimpListIdDropdown: Property.Dropdown({ + displayName: 'Audience', + refreshers: [], + description: 'Audience you want to add the contact to', + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please select a connection', + }; + } + + const authProp = auth as OAuth2PropertyValue; + const listResponse = (await mailchimpCommon.getUserLists( + authProp + )) as any; + + const options = listResponse.lists.map((list: any) => ({ + label: list.name, + value: list.id, + })); + + return { + disabled: false, + options, + }; + }, + }), + getUserLists: async (authProp: OAuth2PropertyValue) => { + const access_token = authProp.access_token; + const mailChimpServerPrefix = + await mailchimpCommon.getMailChimpServerPrefix(access_token!); + mailchimp.setConfig({ + accessToken: access_token, + server: mailChimpServerPrefix, + }); + + // mailchimp types are not complete this is from the docs. + return await (mailchimp as any).lists.getAllLists({ + fields: ['lists.id', 'lists.name', 'total_items'], + count: 1000, + }); + }, + getMailChimpServerPrefix: async (access_token: string) => { + const mailChimpMetaDataRequest: HttpRequest<{ dc: string }> = { + method: HttpMethod.GET, + url: 'https://login.mailchimp.com/oauth2/metadata', + headers: { + Authorization: `OAuth ${access_token}`, + }, + }; + return (await httpClient.sendRequest(mailChimpMetaDataRequest)).body['dc']; + }, + enableWebhookRequest: async ({ + server, + token, + listId, + webhookUrl, + events, + }: EnableTriggerRequestParams): Promise => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://${server}.api.mailchimp.com/3.0/lists/${listId}/webhooks`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + body: { + url: webhookUrl, + events, + sources: { + user: true, + admin: true, + api: true, + }, + }, + }); + + const { id: webhookId } = response.body; + return webhookId; + }, + disableWebhookRequest: async ({ + server, + token, + listId, + webhookId, + }: DisableTriggerRequestParams): Promise => { + await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: `https://${server}.api.mailchimp.com/3.0/lists/${listId}/webhooks/${webhookId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + }); + }, + getMD5EmailHash: (email: string) => { + return crypto.createHash('md5').update(email.toLowerCase()).digest('hex'); + }, +}; + +type TriggerRequestParams = { + server: string; + token: string; + listId: string; +}; + +type EnableTriggerRequestParams = TriggerRequestParams & { + webhookUrl: string; + events: object; +}; + +type DisableTriggerRequestParams = TriggerRequestParams & { + webhookId: string; +}; + +type EnableTriggerResponse = { + id: string; + url: string; + list_id: string; +}; diff --git a/packages/pieces/community/mailchimp/src/lib/common/types.ts b/packages/pieces/community/mailchimp/src/lib/common/types.ts new file mode 100644 index 0000000..9ff88c3 --- /dev/null +++ b/packages/pieces/community/mailchimp/src/lib/common/types.ts @@ -0,0 +1,67 @@ +export enum MailChimpWebhookType { + /** + * triggers when a list subscriber is added. + */ + SUBSCRIBE = 'subscribe', + + /** + * triggers when a list member unsubscribes. + */ + UNSUBSCRIBE = 'unsubscribe', + + /** + * triggers when a subscriber's profile is updated. + */ + PROFILE = 'profile', + + /** + * triggers when a subscriber's email address is cleaned from the list. + */ + CLEANED = 'cleaned', + + /** + * triggers when a subscriber's email address is changed. + */ + UP_EMAIL = 'upemail', + + /** + * triggers when a campaign is sent or cancelled. + */ + CAMPAIGN = 'campaign', + + /** + * triggers when a subscriber's email address is changed. + */ + PENDING = 'pending', + + /** + * transactional + */ + TRANSACTIONAL = 'transactional', +} + +export enum MailChimpEmailType { + HTML = 'html', + TEXT = 'text', +} + +export type MailChimpWebhookRequest = { + type: Type; + fired_at: string; + data: Data; +}; + +export type MailChimpSubscribeWebhookData = { + id: string; + list_id: string; + email: string; + email_type: MailChimpEmailType; + ip_opt: string; + ip_signup: string; + merges: Record; +}; + +export type MailChimpSubscribeWebhookRequest = MailChimpWebhookRequest< + MailChimpWebhookType.SUBSCRIBE, + MailChimpSubscribeWebhookData +>; diff --git a/packages/pieces/community/mailchimp/src/lib/triggers/subscribe-trigger.ts b/packages/pieces/community/mailchimp/src/lib/triggers/subscribe-trigger.ts new file mode 100644 index 0000000..e03e8a9 --- /dev/null +++ b/packages/pieces/community/mailchimp/src/lib/triggers/subscribe-trigger.ts @@ -0,0 +1,90 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { mailchimpCommon } from '../common'; +import { MailChimpSubscribeWebhookRequest } from '../common/types'; +import { mailchimpAuth } from '../..'; + +const WEBHOOK_DATA_STORE_KEY = 'mail_chimp_webhook_data'; + +type WebhookData = { + id: string; + listId: string; +}; + +export const mailChimpSubscribeTrigger = createTrigger({ + auth: mailchimpAuth, + name: 'subscribe', + displayName: 'Member Subscribed to Audience', + description: 'Runs when an Audience subscriber is added.', + type: TriggerStrategy.WEBHOOK, + props: { + list_id: mailchimpCommon.mailChimpListIdDropdown, + }, + sampleData: { + type: 'subscribe', + fired_at: '2009-03-26 21:35:57', + data: { + id: '8a25ff1d98', + list_id: 'a6b5da1054', + email: 'api@mailchimp.com', + email_type: 'html', + ip_opt: '10.20.10.30', + ip_signup: '10.20.10.30', + merges: { + EMAIL: 'api@mailchimp.com', + FNAME: 'Mailchimp', + LNAME: 'API', + INTERESTS: 'Group1,Group2', + }, + }, + }, + + async onEnable(context): Promise { + const accessToken = getAccessTokenOrThrow(context.auth); + + const server = await mailchimpCommon.getMailChimpServerPrefix(accessToken); + + const enabledWebhookId = await mailchimpCommon.enableWebhookRequest({ + server, + listId: context.propsValue.list_id!, + token: accessToken, + webhookUrl: context.webhookUrl!, + events: { subscribe: true }, + }); + + await context.store?.put(WEBHOOK_DATA_STORE_KEY, { + id: enabledWebhookId, + listId: context.propsValue.list_id!, + }); + }, + + async onDisable(context): Promise { + const webhookData = await context.store?.get( + WEBHOOK_DATA_STORE_KEY + ); + + if (webhookData === undefined || webhookData === null) { + return; + } + + const token = getAccessTokenOrThrow(context.auth); + const server = await mailchimpCommon.getMailChimpServerPrefix(token); + + await mailchimpCommon.disableWebhookRequest({ + server, + token, + listId: webhookData.listId, + webhookId: webhookData.id, + }); + }, + + async run(context): Promise { + const request = context.payload.body as MailChimpSubscribeWebhookRequest; + + if (request === undefined) { + return []; + } + + return [request]; + }, +}); diff --git a/packages/pieces/community/mailchimp/src/lib/triggers/unsubscribe-trigger.ts b/packages/pieces/community/mailchimp/src/lib/triggers/unsubscribe-trigger.ts new file mode 100644 index 0000000..6660fee --- /dev/null +++ b/packages/pieces/community/mailchimp/src/lib/triggers/unsubscribe-trigger.ts @@ -0,0 +1,92 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { mailchimpCommon } from '../common'; +import { MailChimpSubscribeWebhookRequest } from '../common/types'; +import { mailchimpAuth } from '../..'; + +const WEBHOOK_DATA_STORE_KEY = 'mail_chimp_webhook_data'; + +type WebhookData = { + id: string; + listId: string; +}; + +export const mailChimpUnsubscriberTrigger = createTrigger({ + auth: mailchimpAuth, + name: 'unsubscribe', + displayName: 'Member Unsubscribed to Audience', + description: 'Runs when a member unsubscribes.', + type: TriggerStrategy.WEBHOOK, + props: { + list_id: mailchimpCommon.mailChimpListIdDropdown, + }, + sampleData: { + type: 'unsubscribe', + fired_at: '2009-03-26 21:35:57', + data: { + id: '8a25ff1d98', + list_id: 'a6b5da1054', + email: 'api@mailchimp.com', + email_type: 'html', + ip_opt: '10.20.10.30', + ip_signup: '10.20.10.30', + merges: { + EMAIL: 'api@mailchimp.com', + FNAME: 'Mailchimp', + LNAME: 'API', + INTERESTS: 'Group1,Group2', + }, + }, + }, + + async onEnable(context): Promise { + const accessToken = getAccessTokenOrThrow(context.auth); + + const server = await mailchimpCommon.getMailChimpServerPrefix(accessToken); + + console.log(context.webhookUrl); + + const enabledWebhookId = await mailchimpCommon.enableWebhookRequest({ + server, + listId: context.propsValue.list_id!, + token: accessToken, + webhookUrl: context.webhookUrl!, + events: { subscribe: true }, + }); + + await context.store?.put(WEBHOOK_DATA_STORE_KEY, { + id: enabledWebhookId, + listId: context.propsValue.list_id!, + }); + }, + + async onDisable(context): Promise { + const webhookData = await context.store?.get( + WEBHOOK_DATA_STORE_KEY + ); + + if (webhookData === undefined || webhookData === null) { + return; + } + + const token = getAccessTokenOrThrow(context.auth); + const server = await mailchimpCommon.getMailChimpServerPrefix(token); + + await mailchimpCommon.disableWebhookRequest({ + server, + token, + listId: webhookData.listId, + webhookId: webhookData.id, + }); + }, + + async run(context): Promise { + const request = context.payload.body as MailChimpSubscribeWebhookRequest; + + if (request === undefined) { + return []; + } + + return [request]; + }, +}); diff --git a/packages/pieces/community/mailchimp/tsconfig.json b/packages/pieces/community/mailchimp/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/mailchimp/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/mailchimp/tsconfig.lib.json b/packages/pieces/community/mailchimp/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/mailchimp/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/mailer-lite/.eslintrc.json b/packages/pieces/community/mailer-lite/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/mailer-lite/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/mailer-lite/README.md b/packages/pieces/community/mailer-lite/README.md new file mode 100644 index 0000000..639b04b --- /dev/null +++ b/packages/pieces/community/mailer-lite/README.md @@ -0,0 +1,7 @@ +# pieces-mailer-lite + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-mailer-lite` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/mailer-lite/package-lock.json b/packages/pieces/community/mailer-lite/package-lock.json new file mode 100644 index 0000000..e2588a0 --- /dev/null +++ b/packages/pieces/community/mailer-lite/package-lock.json @@ -0,0 +1,12 @@ +{ + "name": "@activepieces/piece-mailer-lite", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-mailer-lite", + "version": "0.0.1" + } + } +} diff --git a/packages/pieces/community/mailer-lite/package.json b/packages/pieces/community/mailer-lite/package.json new file mode 100644 index 0000000..448982c --- /dev/null +++ b/packages/pieces/community/mailer-lite/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-mailer-lite", + "version": "0.5.2" +} \ No newline at end of file diff --git a/packages/pieces/community/mailer-lite/project.json b/packages/pieces/community/mailer-lite/project.json new file mode 100644 index 0000000..eae0e4a --- /dev/null +++ b/packages/pieces/community/mailer-lite/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-mailer-lite", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/mailer-lite/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/mailer-lite", + "tsConfig": "packages/pieces/community/mailer-lite/tsconfig.lib.json", + "packageJson": "packages/pieces/community/mailer-lite/package.json", + "main": "packages/pieces/community/mailer-lite/src/index.ts", + "assets": [ + "packages/pieces/community/mailer-lite/*.md", + { + "input": "packages/pieces/community/mailer-lite/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/mailer-lite/src/index.ts b/packages/pieces/community/mailer-lite/src/index.ts new file mode 100644 index 0000000..5ea575a --- /dev/null +++ b/packages/pieces/community/mailer-lite/src/index.ts @@ -0,0 +1,48 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createOrUpdateSubscriber } from './lib/actions/create-or-update-subscription'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { triggers } from './triggers/triggers'; +import { addSubscriberToGroupAction } from './lib/actions/add-subscriber-to-group'; +import { removeSubscriberFromGroupAction } from './lib/actions/remove-subscriber-from-group'; +import { findSubscriberAction } from './lib/actions/find-subscriber'; + +const markdownDescription = ` +To obtain your API key, follow these steps: + +1. Log in to your MailerLite account. +2. Visit the [API page](https://dashboard.mailerlite.com/integrations/api). +3. Click the **Generate new token** button. +4. Copy the generated API key. +`; + +export const mailerLiteAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: markdownDescription, + required: true, +}); + +export const mailerLite = createPiece({ + displayName: 'MailerLite', + description: 'Email marketing software', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/mailer-lite.png', + categories: [PieceCategory.MARKETING], + authors: ["Willianwg","kanarelo","kishanprmr","khaledmashaly","abuaboud"], + auth: mailerLiteAuth, + actions: [ + addSubscriberToGroupAction, + createOrUpdateSubscriber, + findSubscriberAction, + removeSubscriberFromGroupAction, + createCustomApiCallAction({ + baseUrl: () => 'https://connect.mailerlite.com/', + auth: mailerLiteAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers, +}); diff --git a/packages/pieces/community/mailer-lite/src/lib/actions/add-subscriber-to-group.ts b/packages/pieces/community/mailer-lite/src/lib/actions/add-subscriber-to-group.ts new file mode 100644 index 0000000..ffd5120 --- /dev/null +++ b/packages/pieces/community/mailer-lite/src/lib/actions/add-subscriber-to-group.ts @@ -0,0 +1,23 @@ +import MailerLite from '@mailerlite/mailerlite-nodejs'; +import { createAction } from '@activepieces/pieces-framework'; +import { mailerLiteAuth } from '../..'; +import { mailerLiteCommon } from '../common'; + +export const addSubscriberToGroupAction = createAction({ + auth: mailerLiteAuth, + name: 'add_subscriber_to_group', + displayName: 'Add Subscriber to a Group', + description: 'Adds existing subscriber to a specific group.', + props: { + subscriberId: mailerLiteCommon.subscriberId(true), + subscriberGroupId: mailerLiteCommon.subscriberGroupId(true), + }, + async run(context) { + const client = new MailerLite({ api_key: context.auth }); + const response = await client.groups.assignSubscriber( + context.propsValue.subscriberId!, + context.propsValue.subscriberGroupId!, + ); + return response.data; + }, +}); diff --git a/packages/pieces/community/mailer-lite/src/lib/actions/create-or-update-subscription.ts b/packages/pieces/community/mailer-lite/src/lib/actions/create-or-update-subscription.ts new file mode 100644 index 0000000..74a5b0b --- /dev/null +++ b/packages/pieces/community/mailer-lite/src/lib/actions/create-or-update-subscription.ts @@ -0,0 +1,88 @@ +import MailerLite from '@mailerlite/mailerlite-nodejs'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mailerLiteAuth } from '../..'; +import { mailerLiteCommon } from '../common'; +import dayjs from 'dayjs'; + +export const createOrUpdateSubscriber = createAction({ + auth: mailerLiteAuth, + name: 'add_or_update_subscriber', + displayName: 'Add or Update subscriber', + description: 'Create a new subscriber or updates an existing one if the email already exists.', + props: { + email: Property.ShortText({ + displayName: 'Email', + description: 'Email of the new contact', + required: true, + }), + subscriberFields: mailerLiteCommon.subscriberFields, + status: Property.StaticDropdown({ + displayName: 'Status', + required: false, + description: 'If empty, status Active is used by default.', + defaultValue: 'active', + options: { + disabled: false, + options: [ + { + label: 'Active', + value: 'active', + }, + { + label: 'Unsubscribed', + value: 'unsubscribed', + }, + { + label: 'Unconfirmed', + value: 'unconfirmed', + }, + { + label: 'Bounced', + value: 'bounced', + }, + { + label: 'Junk', + value: 'junk', + }, + ], + }, + }), + subscriberGroupId: mailerLiteCommon.subscriberGroupIds(false), + subscribed_at: Property.DateTime({ + displayName: 'Subscribed On', + required: false, + }), + opted_in_at: Property.DateTime({ + displayName: 'Opt-in Date', + required: false, + }), + ip_address: Property.ShortText({ + displayName: 'Signup IP address', + required: false, + }), + optin_ip: Property.ShortText({ + displayName: 'Opt-in IP address', + required: false, + }), + }, + async run(context) { + const client = new MailerLite({ api_key: context.auth }); + const response = await client.subscribers.createOrUpdate({ + email: context.propsValue.email, + fields: context.propsValue.subscriberFields, + groups: context.propsValue.subscriberGroupId, + status: context.propsValue.status as status, + subscribed_at: context.propsValue.subscribed_at + ? dayjs(context.propsValue.subscribed_at).format('YYYY-MM-DD HH:mm:ss') + : undefined, + opted_in_at: context.propsValue.opted_in_at + ? dayjs(context.propsValue.opted_in_at).format('YYYY-MM-DD HH:mm:ss') + : undefined, + ip_address: context.propsValue.ip_address, + optin_ip: context.propsValue.optin_ip, + }); + return response.data; + }, +}); + +type status = 'active' | 'unsubscribed' | 'unconfirmed' | 'bounced' | 'junk'; diff --git a/packages/pieces/community/mailer-lite/src/lib/actions/find-subscriber.ts b/packages/pieces/community/mailer-lite/src/lib/actions/find-subscriber.ts new file mode 100644 index 0000000..fe35ff7 --- /dev/null +++ b/packages/pieces/community/mailer-lite/src/lib/actions/find-subscriber.ts @@ -0,0 +1,21 @@ +import MailerLite from '@mailerlite/mailerlite-nodejs'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { mailerLiteAuth } from '../..'; + +export const findSubscriberAction = createAction({ + auth: mailerLiteAuth, + name: 'find_subscriber', + displayName: 'Find a Subscriber', + description: 'Search for subscriber by email or name.', + props: { + searchValue: Property.ShortText({ + displayName: 'Subscriber ID or Email', + required: true, + }), + }, + async run(context) { + const client = new MailerLite({ api_key: context.auth }); + const response = await client.subscribers.find(context.propsValue.searchValue); + return response.data; + }, +}); diff --git a/packages/pieces/community/mailer-lite/src/lib/actions/remove-subscriber-from-group.ts b/packages/pieces/community/mailer-lite/src/lib/actions/remove-subscriber-from-group.ts new file mode 100644 index 0000000..237f7bf --- /dev/null +++ b/packages/pieces/community/mailer-lite/src/lib/actions/remove-subscriber-from-group.ts @@ -0,0 +1,23 @@ +import MailerLite from '@mailerlite/mailerlite-nodejs'; +import { createAction } from '@activepieces/pieces-framework'; +import { mailerLiteAuth } from '../..'; +import { mailerLiteCommon } from '../common'; + +export const removeSubscriberFromGroupAction = createAction({ + auth: mailerLiteAuth, + name: 'remove_subscriber_from_group', + displayName: 'Remove Subscriber from a Group', + description: 'Removes subscriber from a specific group.', + props: { + subscriberId: mailerLiteCommon.subscriberId(true), + subscriberGroupId: mailerLiteCommon.subscriberGroupId(true), + }, + async run(context) { + const client = new MailerLite({ api_key: context.auth }); + const response = await client.groups.unAssignSubscriber( + context.propsValue.subscriberId!, + context.propsValue.subscriberGroupId!, + ); + return response.data; + }, +}); diff --git a/packages/pieces/community/mailer-lite/src/lib/common/index.ts b/packages/pieces/community/mailer-lite/src/lib/common/index.ts new file mode 100644 index 0000000..f5e7e9c --- /dev/null +++ b/packages/pieces/community/mailer-lite/src/lib/common/index.ts @@ -0,0 +1,142 @@ +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import MailerLite from '@mailerlite/mailerlite-nodejs'; + +export const mailerLiteCommon = { + subscriberFields: Property.DynamicProperties({ + displayName: 'Fields', + refreshers: [], + required: true, + props: async ({ auth }) => { + if (!auth) return {}; + + const props: DynamicPropsValue = {}; + + const client = new MailerLite({ api_key: auth as unknown as string }); + const response = await client.fields.get({ page: 1, limit: 100 }); + + for (const field of response.data.data) { + switch (field.type) { + case 'number': + props[field.key] = Property.Number({ + displayName: field.name, + required: false, + }); + break; + case 'text': + props[field.key] = Property.LongText({ + displayName: field.name, + required: false, + }); + break; + case 'date': + props[field.key] = Property.DateTime({ + displayName: field.name, + required: false, + description: 'Provide YYYY-MM-DD format.', + }); + break; + } + } + return props; + }, + }), + subscriberGroupIds: (required = false) => + Property.MultiSelectDropdown({ + displayName: 'Group IDs', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first.', + options: [], + }; + } + + const client = new MailerLite({ api_key: auth as string }); + const response = await client.groups.get({ + page: 1, + limit: 100, + sort: '-created_at', + }); + + return { + disabled: false, + options: response.data.data.map((group) => { + return { + label: group.name, + value: group.id, + }; + }), + }; + }, + }), + subscriberGroupId: (required = false) => + Property.Dropdown({ + displayName: 'Group ID', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first.', + options: [], + }; + } + + const client = new MailerLite({ api_key: auth as string }); + const response = await client.groups.get({ + page: 1, + limit: 100, + sort: '-created_at', + }); + + return { + disabled: false, + options: response.data.data.map((group) => { + return { + label: group.name, + value: group.id, + }; + }), + }; + }, + }), + subscriberId: (required = false) => + Property.Dropdown({ + displayName: 'Subscriber ID', + refreshers: [], + required, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first.', + options: [], + }; + } + const client = new MailerLite({ api_key: auth as string }); + const subscribers: { label: string; value: string }[] = []; + let cursor; + do { + const response = await client.subscribers.get({ + limit: 25, + cursor: cursor, + }); + subscribers.push( + ...response.data.data.map((subscriber) => ({ + label: subscriber.email, + value: subscriber.id, + })), + ); + cursor = response.data.meta.next_cursor; + } while (cursor !== null); + return { + disabled: false, + options: subscribers, + }; + }, + }), +}; diff --git a/packages/pieces/community/mailer-lite/src/triggers/triggers.ts b/packages/pieces/community/mailer-lite/src/triggers/triggers.ts new file mode 100644 index 0000000..a70a0ce --- /dev/null +++ b/packages/pieces/community/mailer-lite/src/triggers/triggers.ts @@ -0,0 +1,149 @@ +import MailerLite from '@mailerlite/mailerlite-nodejs'; +import { createTrigger, Property, TriggerStrategy } from '@activepieces/pieces-framework'; +import { mailerLiteAuth } from '..'; + +const subscriberSample = { + id: '112375610569918142', + email: 'aa@gmail.com', + status: 'active', + source: 'manual', + sent: null, + opens_count: null, + clicks_count: null, + open_rate: 0, + click_rate: 0, + ip_address: null, + subscribed_at: '2024-02-05T21:48:53.000000Z', + unsubscribed_at: null, + created_at: '2024-02-05T21:48:53.000000Z', + updated_at: '2024-02-05T21:48:53.000000Z', + deleted_at: null, + forget_at: null, + fields: { + name: 'ad', + last_name: null, + company: null, + country: null, + city: null, + phone: null, + state: null, + z_i_p: null, + }, + opted_in_at: null, + option_ip: null, +}; + +export const triggers = [ + { + name: 'subscriber.created', + displayName: 'Subscriber Created', + description: 'Triggers when a subscriber was created on your mailing list.', + sampleData: subscriberSample, + }, + { + name: 'subscriber.updated', + displayName: 'Subscriber Fields Updated', + description: 'Triggers when the subscriber fields have been updated.', + sampleData: subscriberSample, + }, + { + name: 'subscriber.unsubscribed', + displayName: 'Subscriber Unsubscribed', + description: 'Triggers when a subscriber has unsubscribed from your mailing list.', + sampleData: { + id: '112374478518880188', + email: 'example@gmail.com', + status: 'unsubscribed', + source: 'manual', + sent: 0, + opens_count: 0, + clicks_count: 0, + open_rate: 0, + click_rate: 0, + ip_address: null, + subscribed_at: '2024-02-05 21:30:54', + unsubscribed_at: '2024-02-05 21:54:58', + created_at: '2024-02-05 21:30:53', + updated_at: '2024-02-05 21:54:58', + opted_in_at: null, + option_ip: null, + email_changed_at: null, + }, + }, + { + name: 'subscriber.added_to_group', + displayName: 'Subscriber Added to Group', + description: 'Triggers when a subscriber is added to a group.', + sampleData: { + type: 'subscriber.added_to_group', + subscriber: subscriberSample, + group: { id: '108162245463115431', name: "[M]'s Network" }, + }, + }, +].map(register); + +export function register({ + name, + displayName, + description, + sampleData, +}: { + name: string; + displayName: string; + description: string; + sampleData: unknown; +}) { + return createTrigger({ + auth: mailerLiteAuth, + name, + displayName, + description, + props: { + name: Property.ShortText({ + displayName: 'Webhook Name', + required: true, + }), + }, + sampleData: sampleData, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const mailerLite = new MailerLite({ api_key: context.auth }); + mailerLite.webhooks + .create({ + name: context.propsValue.name, + events: [name], + url: context.webhookUrl, + }) + .then(async (response) => { + await context.store.put(name, response.data); + }) + .catch((error) => { + if (error.response) console.log(error.response.data); + }); + }, + async onDisable(context) { + const webhook = await context.store.get(name); + + if (webhook?.data.id) { + const mailerLite = new MailerLite({ api_key: context.auth }); + mailerLite.webhooks.delete(webhook?.data.id); + } + }, + async run(context) { + return [context.payload.body]; + }, + }); +} + +interface Webhook { + data: { + id: string; + name: string; + url: string; + events: string[]; + enabled: boolean; + secret: string; + created_at: string; + updated_at: string; + }; +} diff --git a/packages/pieces/community/mailer-lite/tsconfig.json b/packages/pieces/community/mailer-lite/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/mailer-lite/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/mailer-lite/tsconfig.lib.json b/packages/pieces/community/mailer-lite/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/mailer-lite/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/maileroo/.eslintrc.json b/packages/pieces/community/maileroo/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/maileroo/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/maileroo/README.md b/packages/pieces/community/maileroo/README.md new file mode 100644 index 0000000..37e489a --- /dev/null +++ b/packages/pieces/community/maileroo/README.md @@ -0,0 +1,7 @@ +# pieces-maileroo + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-maileroo` to build the library. diff --git a/packages/pieces/community/maileroo/package.json b/packages/pieces/community/maileroo/package.json new file mode 100644 index 0000000..78fd6a7 --- /dev/null +++ b/packages/pieces/community/maileroo/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-maileroo", + "version": "0.0.2" +} diff --git a/packages/pieces/community/maileroo/project.json b/packages/pieces/community/maileroo/project.json new file mode 100644 index 0000000..6d380a6 --- /dev/null +++ b/packages/pieces/community/maileroo/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-maileroo", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/maileroo/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/maileroo", + "tsConfig": "packages/pieces/community/maileroo/tsconfig.lib.json", + "packageJson": "packages/pieces/community/maileroo/package.json", + "main": "packages/pieces/community/maileroo/src/index.ts", + "assets": [ + "packages/pieces/community/maileroo/*.md", + { + "input": "packages/pieces/community/maileroo/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-maileroo {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/maileroo/src/index.ts b/packages/pieces/community/maileroo/src/index.ts new file mode 100644 index 0000000..801965a --- /dev/null +++ b/packages/pieces/community/maileroo/src/index.ts @@ -0,0 +1,118 @@ +import { HttpError } from '@activepieces/pieces-common'; +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import FormData from 'form-data'; +import { sendEmail } from './lib/actions/send-email'; +import { sendFromTemplate } from './lib/actions/send-from-template'; +import { checkEmail, sendFormData } from './lib/common/send-utils'; +import { verifyEmail } from './lib/actions/verify-email'; + +const markdown = ` +For Sending API key, follow these steps: +1. Navigate to [Domains](https://app.maileroo.com/domains). +2. Click on the domain you want to use. +3. Click on the **Create sending key** under the API section. +4. Click **New Sending Key**. +5. Copy the key under the **Sending Key** column. + +For Verification API key, follow these steps: +1. Navigate to [Verification API](https://app.maileroo.com/verifications). +2. Copy the key under the **Verification API** section. +`; + +export const mailerooAuth = PieceAuth.CustomAuth({ + required: true, + description: markdown, + props: { + keyType: Property.StaticDropdown({ + displayName: 'Type', + defaultValue: 'sending', + options: { + options: [ + { + label: 'Sending Key', + value: 'sending', + }, + { + label: 'Verification Key', + value: 'verification', + }, + ], + }, + required: true, + }), + apiKey: PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + }), + }, + validate: async ({ auth }) => { + // This wont' matter as we are just testing the API key validity + const PLACEHOLDER_STRING = 'placeholder'; + + if (auth.keyType === 'sending') { + try { + const formData = new FormData(); + formData.append('from', PLACEHOLDER_STRING); + formData.append('to', PLACEHOLDER_STRING); + formData.append('subject', PLACEHOLDER_STRING); + formData.append('plain', PLACEHOLDER_STRING); + + await sendFormData('send', formData, auth.apiKey); + } catch (e) { + const status = (e as HttpError).response.status; + + // It is safe to assume that that other 4xx status codes means the API key is valid + if (status === 401) { + return { + valid: false, + error: 'Invalid API Sending key', + }; + } else if (status >= 500) { + return { + valid: false, + error: 'An error occurred while validating the API key', + }; + } + } + + return { + valid: true, + }; + } else { + // Need a different implementation because the response for verification key is different + const result = await checkEmail(PLACEHOLDER_STRING, auth.apiKey); + + if (result.status === 200 && result.body.error_code !== '0401') { + return { + valid: true, + }; + } else { + return { + error: 'Invalid Verification API key', + valid: false, + }; + } + } + }, +}); + +export const maileroo = createPiece({ + displayName: 'Maileroo', + auth: mailerooAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/maileroo.png', + categories: [ + PieceCategory.MARKETING, + PieceCategory.BUSINESS_INTELLIGENCE, + PieceCategory.COMMUNICATION, + ], + description: 'Email Delivery Service with Real-Time Analytics and Reporting', + authors: ['codegino'], + actions: [sendEmail, sendFromTemplate, verifyEmail], + triggers: [], +}); diff --git a/packages/pieces/community/maileroo/src/lib/actions/send-email.ts b/packages/pieces/community/maileroo/src/lib/actions/send-email.ts new file mode 100644 index 0000000..9d50452 --- /dev/null +++ b/packages/pieces/community/maileroo/src/lib/actions/send-email.ts @@ -0,0 +1,53 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { mailerooAuth } from '../../'; +import { + createCommonProps, + createFormData, + sendFormData, +} from '../common/send-utils'; + +export const sendEmail = createAction({ + auth: mailerooAuth, + name: 'sendEmail', + displayName: 'Send Email', + description: 'Sends an email.', + props: { + ...createCommonProps(), + content_type: Property.Dropdown<'text' | 'html'>({ + displayName: 'Content Type', + refreshers: [], + required: true, + defaultValue: 'html', + options: async () => { + return { + disabled: false, + options: [ + { label: 'Plain Text', value: 'text' }, + { label: 'HTML', value: 'html' }, + ], + }; + }, + }), + content: Property.ShortText({ + displayName: 'Content', + description: 'HTML is only allowed if you selected HTML as type', + required: true, + }), + }, + async run(context) { + const formData = createFormData(context.propsValue); + + const { content_type, content } = context.propsValue; + + if (content_type === 'text') { + formData.append('plain', content); + } else if (content_type === 'html') { + formData.append('html', content); + } + + const res = await sendFormData('send', formData, context.auth.apiKey); + + return res.body; + }, +}); diff --git a/packages/pieces/community/maileroo/src/lib/actions/send-from-template.ts b/packages/pieces/community/maileroo/src/lib/actions/send-from-template.ts new file mode 100644 index 0000000..5f10ae9 --- /dev/null +++ b/packages/pieces/community/maileroo/src/lib/actions/send-from-template.ts @@ -0,0 +1,45 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { mailerooAuth } from '../../'; +import { + createCommonProps, + createFormData, + sendFormData, +} from '../common/send-utils'; + +export const sendFromTemplate = createAction({ + auth: mailerooAuth, + name: 'sendFromTemplate', + displayName: 'Send Email using Template', + description: 'Sends an email from an existing template.', + props: { + ...createCommonProps(), + template_id: Property.Number({ + displayName: 'Template ID', + description: 'The ID of the template to use', + required: true, + }), + template_data: Property.Object({ + displayName: 'Template Data', + description: + 'Data to fill in the template. The string `{{name}}` in the template body will be replaced with the value of `name`', + required: true, + }), + }, + async run(context) { + const formData = createFormData(context.propsValue); + + const { template_id, template_data } = context.propsValue; + + formData.append('template_id', template_id); + formData.append('template_data', JSON.stringify(template_data)); + + const res = await sendFormData( + 'send-template', + formData, + context.auth.apiKey + ); + + return res.body; + }, +}); diff --git a/packages/pieces/community/maileroo/src/lib/actions/verify-email.ts b/packages/pieces/community/maileroo/src/lib/actions/verify-email.ts new file mode 100644 index 0000000..3a1ddfd --- /dev/null +++ b/packages/pieces/community/maileroo/src/lib/actions/verify-email.ts @@ -0,0 +1,25 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { checkEmail } from '../common/send-utils'; +import { mailerooAuth } from '../..'; + +export const verifyEmail = createAction({ + auth: mailerooAuth, + name: 'verifyEmail', + displayName: 'Verify Email', + description: 'Verifies an email address.', + props: { + content: Property.ShortText({ + displayName: 'Email', + description: 'Email to verify', + required: true, + }), + }, + async run(context) { + const result = await checkEmail( + context.propsValue.content, + context.auth.apiKey + ); + + return result.body; + }, +}); diff --git a/packages/pieces/community/maileroo/src/lib/common/send-utils.ts b/packages/pieces/community/maileroo/src/lib/common/send-utils.ts new file mode 100644 index 0000000..bd72b75 --- /dev/null +++ b/packages/pieces/community/maileroo/src/lib/common/send-utils.ts @@ -0,0 +1,99 @@ +import { Property, StaticPropsValue } from '@activepieces/pieces-framework'; +import FormData from 'form-data'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const createCommonProps = () => { + return { + to: Property.Array({ + displayName: 'To', + description: 'Emails of the recipients(Supports comma-separated emails)', + required: true, + }), + from_name: Property.ShortText({ + displayName: 'Sender Name', + description: 'Sender name', + required: true, + }), + from: Property.ShortText({ + displayName: 'Sender Email(Your SMTP Email)', + description: 'Sender email. This should come from your SMTP accounts.', + required: true, + }), + subject: Property.ShortText({ + displayName: 'Subject', + description: 'Subject of the email', + required: true, + }), + bcc: Property.Array({ + displayName: 'BCC', + description: 'List of emails in bcc', + required: false, + }), + cc: Property.Array({ + displayName: 'CC', + description: 'List of emails in cc', + required: false, + }), + reply_to: Property.ShortText({ + displayName: 'Reply To', + description: 'Email to receive replies on (defaults to sender)', + required: false, + }), + attachment: Property.File({ + displayName: 'Attachment', + description: 'File to attach to the email you want to send', + required: false, + }), + }; +}; + +export const createFormData = ( + propsValue: StaticPropsValue> +): FormData => { + const { to, from, from_name, reply_to, subject, cc, bcc } = propsValue; + + const formData = new FormData(); + formData.append('from', `${from_name} <${from}>`); + formData.append('to', to.join(',')); + formData.append('subject', subject); + formData.append('reply_to', reply_to ?? from); + + if (cc) { + formData.append('cc', cc.join(',')); + } + if (bcc) { + formData.append('bcc', bcc.join(',')); + } + + return formData; +}; + +export const sendFormData = async ( + url: string, + formData: FormData, + auth: string +) => { + return httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://smtp.maileroo.com/${url}`, + body: formData, + headers: { + ['X-API-Key']: auth, + ...formData.getHeaders(), + }, + }); +}; + +export const checkEmail = async (email: string, apiKey: string) => { + return httpClient.sendRequest({ + url: 'https://verify.maileroo.net/check', + method: HttpMethod.POST, + body: { + email_address: email, + }, + headers: { + 'X-API-Key': apiKey, + 'Content-Type': 'application/json', + }, + }); +}; diff --git a/packages/pieces/community/maileroo/tsconfig.json b/packages/pieces/community/maileroo/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/maileroo/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/maileroo/tsconfig.lib.json b/packages/pieces/community/maileroo/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/maileroo/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/mailjet/.eslintrc.json b/packages/pieces/community/mailjet/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/mailjet/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/mailjet/README.md b/packages/pieces/community/mailjet/README.md new file mode 100644 index 0000000..7e92451 --- /dev/null +++ b/packages/pieces/community/mailjet/README.md @@ -0,0 +1,7 @@ +# pieces-mailjet + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-mailjet` to build the library. diff --git a/packages/pieces/community/mailjet/package.json b/packages/pieces/community/mailjet/package.json new file mode 100644 index 0000000..a0b935e --- /dev/null +++ b/packages/pieces/community/mailjet/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-mailjet", + "version": "0.0.1" +} diff --git a/packages/pieces/community/mailjet/project.json b/packages/pieces/community/mailjet/project.json new file mode 100644 index 0000000..24d2224 --- /dev/null +++ b/packages/pieces/community/mailjet/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-mailjet", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/mailjet/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/mailjet", + "tsConfig": "packages/pieces/community/mailjet/tsconfig.lib.json", + "packageJson": "packages/pieces/community/mailjet/package.json", + "main": "packages/pieces/community/mailjet/src/index.ts", + "assets": [ + "packages/pieces/community/mailjet/*.md", + { + "input": "packages/pieces/community/mailjet/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-mailjet {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/mailjet/src/index.ts b/packages/pieces/community/mailjet/src/index.ts new file mode 100644 index 0000000..58b19c1 --- /dev/null +++ b/packages/pieces/community/mailjet/src/index.ts @@ -0,0 +1,28 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { sendEmail } from './lib/actions/send-email'; + +export const mailjetAuth = PieceAuth.BasicAuth({ + description: 'Enter your api credentials', + required: true, + username: { + displayName: 'API Key', + description: 'Enter your API Key here' + }, + password: { + displayName: 'API Secret', + description: 'Enter your API Secret here' + } +}); + +export const mailjet = createPiece({ + displayName: 'Mailjet', + description: 'Email delivery service for sending transactional and marketing emails', + auth: mailjetAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/mailjet.svg', + categories: [PieceCategory.COMMUNICATION], + authors: ['christian-schab'], + actions: [sendEmail], + triggers: [] +}); diff --git a/packages/pieces/community/mailjet/src/lib/actions/send-email.ts b/packages/pieces/community/mailjet/src/lib/actions/send-email.ts new file mode 100644 index 0000000..412668d --- /dev/null +++ b/packages/pieces/community/mailjet/src/lib/actions/send-email.ts @@ -0,0 +1,88 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { AuthenticationType, httpClient, HttpMethod, HttpRequest } from '@activepieces/pieces-common'; +import { mailjetAuth } from '../../'; + +export const sendEmail = createAction({ + auth: mailjetAuth, + name: 'send_email', + displayName: 'Send Email', + description: 'Send a text, HTML or template email through Mailjet', + props: { + fromEmail: Property.ShortText({ + displayName: 'From (Email)', + description: 'Sender email, must be verified in Mailjet', + required: true + }), + fromName: Property.ShortText({ + displayName: 'From (Name)', + required: false + }), + toEmails: Property.Array({ + displayName: 'Emails of recipients', + required: true + }), + subject: Property.ShortText({ + displayName: 'Subject', + description: undefined, + required: true + }), + textPart: Property.LongText({ + displayName: 'Text part', + description: undefined, + required: false + }), + htmlPart: Property.LongText({ + displayName: 'HTML part', + description: undefined, + required: false + }), + templateId: Property.Number({ + displayName: 'Template Id', + description: 'Template Id (number) defined in Mailjet', + required: false + }), + templateVariables: Property.Object({ + displayName: 'Template variables', + description: undefined, + required: false + }) + }, + async run(configValue) { + const { propsValue, auth } = configValue; + + const message = { + From: { + Email: propsValue.fromEmail, + Name: propsValue.fromName || propsValue.fromEmail + }, + To: propsValue.toEmails.map(to => ({ + Email: to, + Name: to + })), + Subject: propsValue.subject, + TextPart: propsValue.textPart, + TemplateID: propsValue.templateId, + TemplateLanguage: !!propsValue.templateId, + Variables: propsValue.templateVariables + }; + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://api.mailjet.com/v3.1/send`, + body: JSON.stringify({ messages: [message] }), + authentication: { + type: AuthenticationType.BASIC, + username: auth.username, + password: auth.password + }, + queryParams: {} + }; + + const response = await httpClient.sendRequest(request); + + if (response.status !== 200) { + throw new Error(`Failed to communicate with Mailjet`); + } else { + return response.body.Messages[0]; + } + } +}); diff --git a/packages/pieces/community/mailjet/tsconfig.json b/packages/pieces/community/mailjet/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/mailjet/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/mailjet/tsconfig.lib.json b/packages/pieces/community/mailjet/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/mailjet/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/manychat/.eslintrc.json b/packages/pieces/community/manychat/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/manychat/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/manychat/README.md b/packages/pieces/community/manychat/README.md new file mode 100644 index 0000000..4df455d --- /dev/null +++ b/packages/pieces/community/manychat/README.md @@ -0,0 +1,7 @@ +# pieces-manychat + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-manychat` to build the library. diff --git a/packages/pieces/community/manychat/package.json b/packages/pieces/community/manychat/package.json new file mode 100644 index 0000000..04c739a --- /dev/null +++ b/packages/pieces/community/manychat/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-manychat", + "version": "0.0.1" +} diff --git a/packages/pieces/community/manychat/project.json b/packages/pieces/community/manychat/project.json new file mode 100644 index 0000000..35fa17e --- /dev/null +++ b/packages/pieces/community/manychat/project.json @@ -0,0 +1,45 @@ +{ + "name": "pieces-manychat", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/manychat/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/manychat", + "tsConfig": "packages/pieces/community/manychat/tsconfig.lib.json", + "packageJson": "packages/pieces/community/manychat/package.json", + "main": "packages/pieces/community/manychat/src/index.ts", + "assets": [ + "packages/pieces/community/manychat/*.md" + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/manychat/src/index.ts b/packages/pieces/community/manychat/src/index.ts new file mode 100644 index 0000000..791ef14 --- /dev/null +++ b/packages/pieces/community/manychat/src/index.ts @@ -0,0 +1,57 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { findUserByCustomFieldAction } from './lib/actions/find-user-by-custom-field'; +import { createSubscriberAction } from './lib/actions/create-subscriber'; +import { sendContentToUserAction } from './lib/actions/send-content-to-user'; +import { setCustomFieldAction } from './lib/actions/set-custom-fields'; +import { removeTagFromUserAction } from './lib/actions/remove-tag-from-user'; +import { addTagToUserAction } from './lib/actions/add-tag-to-user'; +import { findUserByNameAction } from './lib/actions/find-user-by-name'; +import { PieceCategory } from '@activepieces/shared'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { BASE_URL } from './lib/common/props'; + +export const manychatAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: `You can create an API key by navigating to **Setting -> Extensions -> API**.`, + validate: async ({ auth }) => { + try { + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${BASE_URL}/page/getInfo`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API Key', + }; + } + }, +}); + +export const manychat = createPiece({ + displayName: 'Manychat', + description: 'Automations for Instagram, WhatsApp, TikTok, and Messenger marketing.', + categories: [PieceCategory.MARKETING], + auth: manychatAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/manychat.png', + authors: ['neo773', 'kishanprmr'], + actions: [ + addTagToUserAction, + createSubscriberAction, + findUserByCustomFieldAction, + findUserByNameAction, + removeTagFromUserAction, + sendContentToUserAction, + setCustomFieldAction, + ], + triggers: [], +}); diff --git a/packages/pieces/community/manychat/src/lib/actions/add-tag-to-user.ts b/packages/pieces/community/manychat/src/lib/actions/add-tag-to-user.ts new file mode 100644 index 0000000..2499dda --- /dev/null +++ b/packages/pieces/community/manychat/src/lib/actions/add-tag-to-user.ts @@ -0,0 +1,49 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { manychatAuth } from '../../index'; +import { BASE_URL, subscriberId, tagIdDropdown } from '../common/props'; + +export const addTagToUserAction = createAction({ + auth: manychatAuth, + name: 'addTagToUser', + displayName: 'Add Tag to User', + description: 'Adds a tag to a user.', + props: { + subscriberId: subscriberId, + tagId: tagIdDropdown, + }, + async run({ auth, propsValue }) { + const { subscriberId, tagId } = propsValue; + + const addTagResponse = await httpClient.sendRequest<{ status: string }>({ + url: `${BASE_URL}/subscriber/addTag`, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + body: { + subscriber_id: subscriberId, + tag_id: tagId, + }, + }); + + if (addTagResponse.body.status !== 'success') { + throw Error(`Unexpected Error occured : ${JSON.stringify(addTagResponse.body)}`); + } + + const userResponse = await httpClient.sendRequest<{ data: Record }>({ + method: HttpMethod.GET, + url: `${BASE_URL}/subscriber/getInfo`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + queryParams: { + subscriber_id: `${subscriberId}`, + }, + }); + + return userResponse.body.data; + }, +}); diff --git a/packages/pieces/community/manychat/src/lib/actions/create-subscriber.ts b/packages/pieces/community/manychat/src/lib/actions/create-subscriber.ts new file mode 100644 index 0000000..a44aab0 --- /dev/null +++ b/packages/pieces/community/manychat/src/lib/actions/create-subscriber.ts @@ -0,0 +1,93 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { manychatAuth } from '../../index'; +import { BASE_URL } from '../common/props'; +import { isNil } from '@activepieces/shared'; + +export const createSubscriberAction = createAction({ + auth: manychatAuth, + name: 'createSubscriber', + displayName: 'Create Subscriber', + description: 'Creates a Unified or a Whatsapp subscriber.', + props: { + first_name: Property.ShortText({ + displayName: 'First Name', + required: true, + }), + last_name: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + phone: Property.ShortText({ + displayName: 'Phone Number', + required: false, + description: 'Fill in at least one field: phone or email or whatsapp phone.', + }), + whatsapp_phone: Property.ShortText({ + displayName: 'WhatsApp Phone', + required: false, + description: 'Fill in at least one field: phone or email or whatsapp phone.', + }), + email: Property.ShortText({ + displayName: 'Email', + required: false, + description: 'Fill in at least one field: phone or email or whatsapp phone.', + }), + gender: Property.ShortText({ + displayName: 'Gender', + required: false, + }), + has_opt_in_sms: Property.Checkbox({ + displayName: 'SMS Opt-in', + required: false, + }), + has_opt_in_email: Property.Checkbox({ + displayName: 'Email Opt-in', + required: false, + }), + consent_phrase: Property.ShortText({ + displayName: 'Consent Phrase', + description: 'The consent phrase provided by the subscriber.', + required: false, + }), + }, + async run({ auth, propsValue }) { + const { + first_name, + last_name, + phone, + whatsapp_phone, + email, + gender, + has_opt_in_email, + has_opt_in_sms, + consent_phrase, + } = propsValue; + if (isNil(phone) && isNil(whatsapp_phone) && isNil(email)) { + throw Error( + 'To create a subscriber you must fill in at least one field: phone or email or whatsapp_phone.', + ); + } + const response = await httpClient.sendRequest<{ status: string; data: Record }>({ + method: HttpMethod.POST, + url: `${BASE_URL}/subscriber/createSubscriber`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + body: { + first_name, + last_name, + phone, + whatsapp_phone, + email, + gender, + has_opt_in_sms, + has_opt_in_email, + consent_phrase, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/manychat/src/lib/actions/find-user-by-custom-field.ts b/packages/pieces/community/manychat/src/lib/actions/find-user-by-custom-field.ts new file mode 100644 index 0000000..68a3ac9 --- /dev/null +++ b/packages/pieces/community/manychat/src/lib/actions/find-user-by-custom-field.ts @@ -0,0 +1,76 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { BASE_URL } from '../common/props'; +import { manychatAuth } from '../../index'; + +export const findUserByCustomFieldAction = createAction({ + auth: manychatAuth, + name: 'findUserByCustomField', + displayName: 'Find User by Custom Field', + description: 'Finds a user by custom field.', + props: { + field: Property.Dropdown({ + displayName: 'Custom Field', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + placeholder: 'Please connect your account.', + disabled: true, + options: [], + }; + } + + const response = await httpClient.sendRequest<{ + data: Array<{ id: number; name: string; type: string }>; + }>({ + url: `${BASE_URL}/page/getCustomFields`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + return { + disabled: false, + options: response.body.data + .filter((field) => ['text', 'number'].includes(field.type)) + .map((field) => ({ + label: field.name, + value: field.id, + })), + }; + }, +}), + value: Property.ShortText({ + displayName: 'Value', + description: 'The value to search for.', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { field, value } = propsValue; + + const response = await httpClient.sendRequest<{ + status: string; + data: Array>; + }>({ + method: HttpMethod.GET, + url: `${BASE_URL}/subscriber/findByCustomField`, + queryParams: { + field_id: field.toString(), + field_value: value, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + }); + + return { + found: response.body.status === 'success' && response.body.data.length > 0, + result: response.body.data, + }; + }, +}); diff --git a/packages/pieces/community/manychat/src/lib/actions/find-user-by-name.ts b/packages/pieces/community/manychat/src/lib/actions/find-user-by-name.ts new file mode 100644 index 0000000..b4853cd --- /dev/null +++ b/packages/pieces/community/manychat/src/lib/actions/find-user-by-name.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { BASE_URL } from '../common/props'; +import { manychatAuth } from '../../index'; + +export const findUserByNameAction = createAction({ + auth: manychatAuth, + name: 'findUserByName', + displayName: 'Find User by Name', + description: 'Finds a user by name.', + props: { + name: Property.ShortText({ + displayName: 'Name', + description: "Provide user's full name to search.", + required: true, + }), + }, + async run({ auth, propsValue }) { + const { name } = propsValue; + + const response = await httpClient.sendRequest<{ + status: string; + data: Array>; + }>({ + method: HttpMethod.GET, + url: `${BASE_URL}/subscriber/findByName`, + queryParams: { + name: name, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + }); + + return { + found: response.body.status === 'success' && response.body.data.length > 0, + result: response.body.data, + }; + }, +}); diff --git a/packages/pieces/community/manychat/src/lib/actions/remove-tag-from-user.ts b/packages/pieces/community/manychat/src/lib/actions/remove-tag-from-user.ts new file mode 100644 index 0000000..a506cb5 --- /dev/null +++ b/packages/pieces/community/manychat/src/lib/actions/remove-tag-from-user.ts @@ -0,0 +1,49 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { BASE_URL, subscriberId, tagIdDropdown } from '../common/props'; +import { manychatAuth } from '../../index'; + +export const removeTagFromUserAction = createAction({ + name: 'removeTagFromUser', + displayName: 'Remove Tag from User', + description: 'Remove a tag from a user.', + auth:manychatAuth, + props: { + subscriberId: subscriberId, + tagId: tagIdDropdown, + }, + async run({ auth, propsValue }) { + const { subscriberId, tagId } = propsValue; + + const removeTagResponse = await httpClient.sendRequest<{ status: string }>({ + url: `${BASE_URL}/subscriber/removeTag`, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + body: { + subscriber_id: subscriberId, + tag_id: tagId, + }, + }); + + if (removeTagResponse.body.status !== 'success') { + throw Error(`Unexpected Error occured : ${JSON.stringify(removeTagResponse.body)}`); + } + + const userResponse = await httpClient.sendRequest<{ data: Record }>({ + method: HttpMethod.GET, + url: `${BASE_URL}/subscriber/getInfo`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + queryParams: { + subscriber_id: subscriberId.toString(), + }, + }); + + return userResponse.body.data; + }, +}); diff --git a/packages/pieces/community/manychat/src/lib/actions/send-content-to-user.ts b/packages/pieces/community/manychat/src/lib/actions/send-content-to-user.ts new file mode 100644 index 0000000..be62635 --- /dev/null +++ b/packages/pieces/community/manychat/src/lib/actions/send-content-to-user.ts @@ -0,0 +1,154 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { subscriberId } from '../common/props'; +import { manychatAuth } from '../../index'; + +export const sendContentToUserAction = createAction({ + auth: manychatAuth, + name: 'sendContentToUser', + displayName: 'Send Content to User', + description: 'Sends a content to a user.', + props: { + subscriber_id: subscriberId, + platform: Property.StaticDropdown({ + displayName: 'Platform', + description: 'The platform to send the content to.', + required: false, + options: { + options: [ + { label: 'Facebook', value: 'facebook' }, + { label: 'Instagram', value: 'instagram' }, + { label: 'WhatsApp', value: 'whatsapp' }, + { label: 'Telegram', value: 'telegram' }, + ], + }, + }), + content_type: Property.StaticDropdown({ + displayName: 'Content Type', + description: 'The type of content to send.', + required: true, + options: { + options: [ + { label: 'Text', value: 'text' }, + { label: 'Image', value: 'image' }, + { label: 'Video', value: 'video' }, + { label: 'Audio', value: 'audio' }, + { label: 'File', value: 'file' }, + ], + }, + }), + text_content: Property.LongText({ + displayName: 'Text Content', + description: 'The text content to send. Required when content type is Text.', + required: false, + defaultValue: '', + }), + media_url: Property.ShortText({ + displayName: 'Media URL', + description: + 'URL of the media to send (image, video, audio, or file). Required for media content types.', + required: false, + }), + message_tag: Property.StaticDropdown({ + displayName: 'Message Tag', + description: 'The tag to use for the message.', + required: false, + options: { + options: [ + { label: 'Account Update', value: 'ACCOUNT_UPDATE' }, + { label: 'Confirmed Event Update', value: 'CONFIRMED_EVENT_UPDATE' }, + { label: 'Human Agent', value: 'HUMAN_AGENT' }, + { label: 'Post Purchase Update', value: 'POST_PURCHASE_UPDATE' }, + { label: 'Business Productivity', value: 'BUSINESS_PRODUCTIVITY' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const { subscriber_id, platform, content_type, text_content, media_url, message_tag } = + propsValue; + + // Validation + if (content_type === 'text' && !text_content) { + throw new Error('Text content is required when content type is Text'); + } + + if (['image', 'video', 'audio', 'file'].includes(content_type) && !media_url) { + throw new Error(`Media URL is required when content type is ${content_type}`); + } + + // Build the content object based on the content type + const messages = []; + + switch (content_type) { + case 'text': + messages.push({ + type: 'text', + text: text_content, + }); + break; + case 'image': + messages.push({ + type: 'image', + url: media_url, + }); + break; + case 'video': + messages.push({ + type: 'video', + url: media_url, + }); + break; + case 'audio': + messages.push({ + type: 'audio', + url: media_url, + }); + break; + case 'file': + messages.push({ + type: 'file', + url: media_url, + }); + break; + } + + // Prepare the content object + const content: Record = { + version: 'v2', + content: { + messages: messages, + actions: [], + quick_replies: [], + }, + }; + + // Add platform type if specified + if (platform && platform !== 'facebook') { + content['content']['type'] = platform; + } + + // Prepare the request body + const requestBody: Record = { + subscriber_id: subscriber_id, + data: content, + }; + + if (message_tag) { + requestBody['message_tag'] = message_tag; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.manychat.com/fb/sending/sendContent', + headers: { + accept: 'application/json', + Authorization: `Bearer ${auth}`, + 'Content-Type': 'application/json', + }, + body: requestBody, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/manychat/src/lib/actions/set-custom-fields.ts b/packages/pieces/community/manychat/src/lib/actions/set-custom-fields.ts new file mode 100644 index 0000000..8a78ce7 --- /dev/null +++ b/packages/pieces/community/manychat/src/lib/actions/set-custom-fields.ts @@ -0,0 +1,126 @@ +import { createAction, DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { BASE_URL, subscriberId } from '../common/props'; +import { manychatAuth } from '../../index'; + +export const setCustomFieldAction = createAction({ + auth: manychatAuth, + name: 'setCustomField', + displayName: 'Set Custom Field', + description: 'Ass or Updates a custom field value for a user.', + props: { + subscriber_id: subscriberId, + field_id: Property.Dropdown({ + displayName: 'Custom Field', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + placeholder: 'Please connect your account.', + disabled: true, + options: [], + }; + } + + const response = await httpClient.sendRequest<{ + data: Array<{ id: number; name: string; type: string }>; + }>({ + url: `${BASE_URL}/page/getCustomFields`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + return { + disabled: false, + options: response.body.data + .filter((field) => field.type !== 'array') + .map((field) => ({ + label: field.name, + value: `${field.id}:::${field.type}`, + })), + }; + }, + }), + field_value: Property.DynamicProperties({ + displayName: 'Field Value', + required: true, + refreshers: ['field_id'], + props: async ({ auth, field_id }) => { + if (!auth || !field_id) return {}; + + const fields: DynamicPropsValue = {}; + + const fieldType = (field_id as unknown as string).split(':::')[1]; + + switch (fieldType) { + case 'text': + fields['value'] = Property.ShortText({ + displayName: 'Value', + required: true, + }); + break; + case 'number': + fields['value'] = Property.Number({ + displayName: 'Value', + required: true, + }); + break; + case 'date': + case 'datetime': + fields['value'] = Property.DateTime({ + displayName: 'Value', + required: true, + }); + break; + case 'boolean': + fields['value'] = Property.Checkbox({ + displayName: 'Value', + required: true, + }); + break; + default: + break; + } + return fields; + }, + }), + }, + async run({ auth, propsValue }) { + const { subscriber_id, field_id, field_value } = propsValue; + + const setCustomFieldResponse = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/subscriber/setCustomField`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + body: { + subscriber_id, + field_id: field_id.split(':::')[0], + field_value: field_value['value'], + }, + }); + + if (setCustomFieldResponse.body.status !== 'success') { + throw Error(`Unexpected Error occured : ${JSON.stringify(setCustomFieldResponse.body)}`); + } + + const userResponse = await httpClient.sendRequest<{ data: Record }>({ + method: HttpMethod.GET, + url: `${BASE_URL}/subscriber/getInfo`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + queryParams: { + subscriber_id: `${subscriber_id}`, + }, + }); + + return userResponse.body.data; + }, +}); diff --git a/packages/pieces/community/manychat/src/lib/common/props.ts b/packages/pieces/community/manychat/src/lib/common/props.ts new file mode 100644 index 0000000..74cae35 --- /dev/null +++ b/packages/pieces/community/manychat/src/lib/common/props.ts @@ -0,0 +1,43 @@ +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { Property } from '@activepieces/pieces-framework'; + +export const BASE_URL = 'https://api.manychat.com/fb'; + +export const subscriberId = Property.Number({ + displayName: 'User ID', + description: `Please refer [Manychat Guide](https://help.manychat.com/hc/en-us/articles/14959510331420-How-to-generate-a-token-for-the-Manychat-API-and-where-to-get-parameters#h_01JCR55RJ0B0PQW6HDW6RW0A42) to obtain user/contact ID.`, + required: true, +}); + +export const tagIdDropdown = Property.Dropdown({ + displayName: 'Tag', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + placeholder: 'Please connect your account.', + disabled: true, + options: [], + }; + } + + const response = await httpClient.sendRequest<{ data: Array<{ id: number; name: string }> }>({ + url: `${BASE_URL}/page/getTags`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + + return { + disabled: false, + options: response.body.data.map((tag) => ({ + label: tag.name, + value: tag.id, + })), + }; + }, +}); + diff --git a/packages/pieces/community/manychat/tsconfig.json b/packages/pieces/community/manychat/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/manychat/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/manychat/tsconfig.lib.json b/packages/pieces/community/manychat/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/manychat/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/mastodon/.eslintrc.json b/packages/pieces/community/mastodon/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/mastodon/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/mastodon/README.md b/packages/pieces/community/mastodon/README.md new file mode 100644 index 0000000..ea6f9f5 --- /dev/null +++ b/packages/pieces/community/mastodon/README.md @@ -0,0 +1,7 @@ +# pieces-mastodon + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-mastodon` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/mastodon/package.json b/packages/pieces/community/mastodon/package.json new file mode 100644 index 0000000..ede8320 --- /dev/null +++ b/packages/pieces/community/mastodon/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-mastodon", + "version": "0.4.4" +} \ No newline at end of file diff --git a/packages/pieces/community/mastodon/project.json b/packages/pieces/community/mastodon/project.json new file mode 100644 index 0000000..2a9e598 --- /dev/null +++ b/packages/pieces/community/mastodon/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-mastodon", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/mastodon/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/mastodon", + "tsConfig": "packages/pieces/community/mastodon/tsconfig.lib.json", + "packageJson": "packages/pieces/community/mastodon/package.json", + "main": "packages/pieces/community/mastodon/src/index.ts", + "assets": [ + "packages/pieces/community/mastodon/*.md", + { + "input": "packages/pieces/community/mastodon/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/mastodon/src/index.ts b/packages/pieces/community/mastodon/src/index.ts new file mode 100644 index 0000000..5f0faa6 --- /dev/null +++ b/packages/pieces/community/mastodon/src/index.ts @@ -0,0 +1,63 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { postStatus } from './lib/actions/post-status'; + +const markdownDescription = ` +**Base Url**: The base url of your Mastodon instance (e.g \`https://mastodon.social\`) + +**Access Token**: To get your access token, follow the steps below: + +1. Go to your **Profile** -> **Preferences** -> **Development** -> **New Application** +2. Fill the Information +3. Click on **Create Application** +4. Copy access token from **Your access token** +`; + +export const mastodonAuth = PieceAuth.CustomAuth({ + description: markdownDescription, + props: { + base_url: Property.ShortText({ + displayName: 'Base URL', + description: 'The base URL of your Mastodon instance', + defaultValue: 'https://mastodon.social/', + required: true, + }), + access_token: Property.ShortText({ + displayName: 'Access Token', + description: + 'The access token for your Mastodon account, check the documentation for how to get this', + required: true, + }), + }, + required: true, +}); + +export const mastodon = createPiece({ + displayName: 'Mastodon', + description: 'Open-source decentralized social network', + + logoUrl: 'https://cdn.activepieces.com/pieces/mastodon.png', + categories: [PieceCategory.COMMUNICATION], + minimumSupportedRelease: '0.30.0', + authors: ["denieler","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + auth: mastodonAuth, + actions: [ + postStatus, + createCustomApiCallAction({ + baseUrl: (auth) => + (auth as { base_url: string }).base_url.replace(/\/$/, '') + '/api/v1', + auth: mastodonAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${ + (auth as { access_token: string }).access_token + }`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/mastodon/src/lib/actions/post-status.ts b/packages/pieces/community/mastodon/src/lib/actions/post-status.ts new file mode 100644 index 0000000..6b7c4ee --- /dev/null +++ b/packages/pieces/community/mastodon/src/lib/actions/post-status.ts @@ -0,0 +1,72 @@ +import FormData from 'form-data'; +import { Property, createAction, ApFile } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { mastodonAuth } from '../..'; + +const uploadMedia = async (media: ApFile, baseUrl: string, token: string) => { + const formData = new FormData(); + formData.append('file', Buffer.from(media.base64, 'base64'), media.filename); + + const postMediaResponse = await httpClient.sendRequest({ + url: `${baseUrl}/api/v2/media`, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + headers: { + 'Content-type': 'multipart/form-data', + }, + body: formData, + }); + + return postMediaResponse.body.id; +}; + +export const postStatus = createAction({ + auth: mastodonAuth, + name: 'post_status', + displayName: 'Post Status', + description: 'Post a status to Mastodon', + props: { + status: Property.LongText({ + displayName: 'Status', + description: 'The text of your status', + required: true, + }), + media: Property.File({ + displayName: 'Media URL or File', + description: 'The media attachment for your status', + required: false, + }), + }, + async run(context) { + const token = context.auth.access_token; + const status = context.propsValue.status; + const media = context.propsValue.media; + // Remove trailing slash from base_url + const baseUrl = context.auth.base_url.replace(/\/$/, ''); + + let mediaId: string | undefined = undefined; + if (media) { + mediaId = await uploadMedia(media, baseUrl, token); + } + + return await httpClient.sendRequest({ + url: `${baseUrl}/api/v1/statuses`, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + body: { + status, + ...(mediaId ? { media_ids: [mediaId] } : {}), + }, + }); + }, +}); diff --git a/packages/pieces/community/mastodon/tsconfig.json b/packages/pieces/community/mastodon/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/mastodon/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/mastodon/tsconfig.lib.json b/packages/pieces/community/mastodon/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/mastodon/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/math-helper/.eslintrc.json b/packages/pieces/community/math-helper/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/math-helper/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/math-helper/README.md b/packages/pieces/community/math-helper/README.md new file mode 100644 index 0000000..7fc6955 --- /dev/null +++ b/packages/pieces/community/math-helper/README.md @@ -0,0 +1,7 @@ +# pieces-math-helper + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-math-helper` to build the library. diff --git a/packages/pieces/community/math-helper/package.json b/packages/pieces/community/math-helper/package.json new file mode 100644 index 0000000..6f1c6d9 --- /dev/null +++ b/packages/pieces/community/math-helper/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-math-helper", + "version": "0.0.11" +} \ No newline at end of file diff --git a/packages/pieces/community/math-helper/project.json b/packages/pieces/community/math-helper/project.json new file mode 100644 index 0000000..bab0708 --- /dev/null +++ b/packages/pieces/community/math-helper/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-math-helper", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/math-helper/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/math-helper", + "tsConfig": "packages/pieces/community/math-helper/tsconfig.lib.json", + "packageJson": "packages/pieces/community/math-helper/package.json", + "main": "packages/pieces/community/math-helper/src/index.ts", + "assets": [ + "packages/pieces/community/math-helper/*.md", + { + "input": "packages/pieces/community/math-helper/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-math {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/math-helper/src/index.ts b/packages/pieces/community/math-helper/src/index.ts new file mode 100644 index 0000000..bfbfe4e --- /dev/null +++ b/packages/pieces/community/math-helper/src/index.ts @@ -0,0 +1,31 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { addition } from './lib/actions/addition'; +import { division } from './lib/actions/division'; +import { generateRandom } from './lib/actions/generateRandom'; +import { modulo } from './lib/actions/modulo'; +import { multiplication } from './lib/actions/multiplication'; +import { subtraction } from './lib/actions/subtraction'; + +const markdownDescription = ` +Perform mathematical operations. +`; + +export const math = createPiece({ + displayName: 'Math Helper', + description: markdownDescription, + auth: PieceAuth.None(), + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/math-helper.svg', + categories: [PieceCategory.CORE], + authors: ["kishanprmr","MoShizzle","abuaboud"], + actions: [ + addition, + subtraction, + multiplication, + division, + modulo, + generateRandom, + ], + triggers: [], +}); diff --git a/packages/pieces/community/math-helper/src/lib/actions/addition.ts b/packages/pieces/community/math-helper/src/lib/actions/addition.ts new file mode 100644 index 0000000..429ead1 --- /dev/null +++ b/packages/pieces/community/math-helper/src/lib/actions/addition.ts @@ -0,0 +1,37 @@ +import { + createAction, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; + +export const addition = createAction({ + name: 'addition_math', + auth: PieceAuth.None(), + displayName: 'Addition', + description: 'Add the first number and the second number', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + first_number: Property.Number({ + displayName: 'First Number', + description: undefined, + required: true, + }), + second_number: Property.Number({ + displayName: 'Second Number', + description: undefined, + required: true, + }), + }, + async run(context) { + return ( + context.propsValue['first_number'] + context.propsValue['second_number'] + ); + }, +}); diff --git a/packages/pieces/community/math-helper/src/lib/actions/division.ts b/packages/pieces/community/math-helper/src/lib/actions/division.ts new file mode 100644 index 0000000..abd95f8 --- /dev/null +++ b/packages/pieces/community/math-helper/src/lib/actions/division.ts @@ -0,0 +1,44 @@ +import { + createAction, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const division = createAction({ + name: 'division_math', + auth: PieceAuth.None(), + displayName: 'Division', + description: 'Divide first number by the second number', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + first_number: Property.Number({ + displayName: 'First Number', + description: undefined, + required: true, + }), + second_number: Property.Number({ + displayName: 'Second Number', + description: undefined, + required: true, + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + second_number: z.number().refine(val => val !== 0, { + message: "Second number cannot be zero" + }), + }); + return ( + context.propsValue['first_number'] / context.propsValue['second_number'] + ); + }, +}); diff --git a/packages/pieces/community/math-helper/src/lib/actions/generateRandom.ts b/packages/pieces/community/math-helper/src/lib/actions/generateRandom.ts new file mode 100644 index 0000000..5b4a3ed --- /dev/null +++ b/packages/pieces/community/math-helper/src/lib/actions/generateRandom.ts @@ -0,0 +1,37 @@ +import { + PieceAuth, + Property, + createAction, +} from '@activepieces/pieces-framework'; + +export const generateRandom = createAction({ + name: 'generateRandom_math', + auth: PieceAuth.None(), + displayName: 'Generate Random Number', + description: 'Generate random number between two numbers (inclusive)', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + first_number: Property.Number({ + displayName: 'First Number', + description: undefined, + required: true, + }), + second_number: Property.Number({ + displayName: 'Second Number', + description: undefined, + required: true, + }), + }, + async run(context) { + const min = context.propsValue['first_number']; + const max = context.propsValue['second_number']; + return Math.floor(Math.random() * (max - min + 1) + min); + }, +}); diff --git a/packages/pieces/community/math-helper/src/lib/actions/modulo.ts b/packages/pieces/community/math-helper/src/lib/actions/modulo.ts new file mode 100644 index 0000000..c234de0 --- /dev/null +++ b/packages/pieces/community/math-helper/src/lib/actions/modulo.ts @@ -0,0 +1,37 @@ +import { + createAction, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; + +export const modulo = createAction({ + name: 'modulo_math', + auth: PieceAuth.None(), + displayName: 'Modulo', + description: 'Get the remainder of the first number divided by second number', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + first_number: Property.Number({ + displayName: 'First Number', + description: undefined, + required: true, + }), + second_number: Property.Number({ + displayName: 'Second Number', + description: undefined, + required: true, + }), + }, + async run(context) { + return ( + context.propsValue['first_number'] % context.propsValue['second_number'] + ); + }, +}); diff --git a/packages/pieces/community/math-helper/src/lib/actions/multiplication.ts b/packages/pieces/community/math-helper/src/lib/actions/multiplication.ts new file mode 100644 index 0000000..6fdae24 --- /dev/null +++ b/packages/pieces/community/math-helper/src/lib/actions/multiplication.ts @@ -0,0 +1,37 @@ +import { + PieceAuth, + Property, + createAction, +} from '@activepieces/pieces-framework'; + +export const multiplication = createAction({ + name: 'multiplication_math', + auth: PieceAuth.None(), + displayName: 'Multiplication', + description: 'Multiply first number by the second number', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + first_number: Property.Number({ + displayName: 'First Number', + description: undefined, + required: true, + }), + second_number: Property.Number({ + displayName: 'Second Number', + description: undefined, + required: true, + }), + }, + async run(context) { + return ( + context.propsValue['first_number'] * context.propsValue['second_number'] + ); + }, +}); diff --git a/packages/pieces/community/math-helper/src/lib/actions/subtraction.ts b/packages/pieces/community/math-helper/src/lib/actions/subtraction.ts new file mode 100644 index 0000000..d10ab5f --- /dev/null +++ b/packages/pieces/community/math-helper/src/lib/actions/subtraction.ts @@ -0,0 +1,37 @@ +import { + createAction, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; + +export const subtraction = createAction({ + name: 'subtraction_math', + auth: PieceAuth.None(), + displayName: 'Subtraction', + description: 'Subtract the first number from the second number', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + first_number: Property.Number({ + displayName: 'First Number', + description: undefined, + required: true, + }), + second_number: Property.Number({ + displayName: 'Second Number', + description: undefined, + required: true, + }), + }, + async run(context) { + return ( + context.propsValue['second_number'] - context.propsValue['first_number'] + ); + }, +}); diff --git a/packages/pieces/community/math-helper/tsconfig.json b/packages/pieces/community/math-helper/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/math-helper/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/math-helper/tsconfig.lib.json b/packages/pieces/community/math-helper/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/math-helper/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/matomo/.eslintrc.json b/packages/pieces/community/matomo/.eslintrc.json new file mode 100644 index 0000000..15ede17 --- /dev/null +++ b/packages/pieces/community/matomo/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*", "package.json"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/matomo/README.md b/packages/pieces/community/matomo/README.md new file mode 100644 index 0000000..1607415 --- /dev/null +++ b/packages/pieces/community/matomo/README.md @@ -0,0 +1,7 @@ +# pieces-matomo + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-matomo` to build the library. diff --git a/packages/pieces/community/matomo/package.json b/packages/pieces/community/matomo/package.json new file mode 100644 index 0000000..a614205 --- /dev/null +++ b/packages/pieces/community/matomo/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-matomo", + "version": "0.0.5" +} \ No newline at end of file diff --git a/packages/pieces/community/matomo/project.json b/packages/pieces/community/matomo/project.json new file mode 100644 index 0000000..88c4a56 --- /dev/null +++ b/packages/pieces/community/matomo/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-matomo", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/matomo/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/matomo", + "tsConfig": "packages/pieces/community/matomo/tsconfig.lib.json", + "packageJson": "packages/pieces/community/matomo/package.json", + "main": "packages/pieces/community/matomo/src/index.ts", + "assets": [ + "packages/pieces/community/matomo/*.md", + { + "input": "packages/pieces/community/matomo/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-matomo {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/matomo/src/index.ts b/packages/pieces/community/matomo/src/index.ts new file mode 100644 index 0000000..9eadc25 --- /dev/null +++ b/packages/pieces/community/matomo/src/index.ts @@ -0,0 +1,29 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { addAnnotationAction } from './lib/actions/add-annotation'; +import { matomoAuth } from './lib/auth'; + +export const matomo = createPiece({ + displayName: 'Matomo', + description: 'Open source alternative to Google Analytics', + + auth: matomoAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/matomo.png', + categories: [PieceCategory.BUSINESS_INTELLIGENCE], + authors: ["joeworkman","kishanprmr","MoShizzle","abuaboud"], + actions: [ + addAnnotationAction, + createCustomApiCallAction({ + baseUrl: (auth) => (auth as { domain: string }).domain, + auth: matomoAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as { tokenAuth: string }).tokenAuth}`, + }), + }), + ], + triggers: [], +}); + +// Matomo API Docs: https://developer.matomo.org/api-reference/reporting-api diff --git a/packages/pieces/community/matomo/src/lib/actions/add-annotation.ts b/packages/pieces/community/matomo/src/lib/actions/add-annotation.ts new file mode 100644 index 0000000..590a423 --- /dev/null +++ b/packages/pieces/community/matomo/src/lib/actions/add-annotation.ts @@ -0,0 +1,34 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { addAnnotation } from '../api'; +import { matomoAuth } from '../auth'; + +export const addAnnotationAction = createAction({ + name: 'add_annotation', + auth: matomoAuth, + displayName: 'Add Annotation', + description: 'Add an annotation to a Matomo site', + props: { + note: Property.ShortText({ + displayName: 'Note', + description: 'The note to add', + required: true, + }), + date: Property.DateTime({ + displayName: 'Date', + description: 'The date to add the note to. Format: YYYY-MM-DD', + required: true, + }), + starred: Property.Checkbox({ + displayName: 'Starred', + description: 'Whether or not the note should be starred', + required: true, + }), + }, + async run(context) { + return await addAnnotation(context.auth, { + note: context.propsValue.note, + date: context.propsValue.date, + starred: context.propsValue.starred ? '1' : '0', + }); + }, +}); diff --git a/packages/pieces/community/matomo/src/lib/api.ts b/packages/pieces/community/matomo/src/lib/api.ts new file mode 100644 index 0000000..9d1e9cc --- /dev/null +++ b/packages/pieces/community/matomo/src/lib/api.ts @@ -0,0 +1,54 @@ +import { + httpClient, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; +import { MatomoAuthType } from './auth'; + +const matomoAPI = async ( + api : string, + auth : MatomoAuthType, + queryParams : QueryParams = {} +) => { + queryParams['module'] = 'API'; + queryParams['format'] = 'JSON'; + queryParams['method'] = api; + queryParams['idSite'] = auth.siteId; + + const formData = new FormData(); + formData.append('token_auth', auth.tokenAuth); + + const request: HttpRequest = { + method : HttpMethod.POST, + url : `${auth.domain}`, + queryParams : queryParams, + body : formData, + headers : { + 'Content-Type': 'multipart/form-data', + }, + }; + const response = await httpClient.sendRequest(request); + + if ( + response.status !== 200 || + (response.body['result'] && response.body['result'] === 'error') + ) { + throw new Error(`Matomo API request failed: ${response.body['message']}`); + } + + return { + success: true, + data: response.body, + }; +}; + +export async function getVersion(auth: MatomoAuthType) { + const api = 'API.getMatomoVersion'; + return matomoAPI(api, auth); +} + +export async function addAnnotation(auth: MatomoAuthType, data: QueryParams) { + const api = 'Annotations.add'; + return matomoAPI(api, auth, data); +} diff --git a/packages/pieces/community/matomo/src/lib/auth.ts b/packages/pieces/community/matomo/src/lib/auth.ts new file mode 100644 index 0000000..d0f31ed --- /dev/null +++ b/packages/pieces/community/matomo/src/lib/auth.ts @@ -0,0 +1,75 @@ +import { + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; +import { getVersion } from './api'; + +export type MatomoAuthType = { + tokenAuth: string; + domain: string; + siteId: string; +}; + +const description = ` +Authenticate with Matomo. + +Your Matomo domain is the URL of your Matomo account, e.g. https://matomo.example.com + +Your Token Auth key can be found in your Matomo account under Settings > API. + +Your Site ID can be found in your Matomo account under Administration > Websites. +`; + +export const matomoAuth = PieceAuth.CustomAuth({ + description: description, + props: { + domain: Property.ShortText({ + displayName: 'Matomo Domain', + description: + 'The domain of your Matomo account: https://matomo.example.com', + required: true, + }), + tokenAuth: PieceAuth.SecretText({ + displayName: 'Token Auth', + description: 'The Token Auth key from your Matomo account', + required: true, + }), + siteId: Property.ShortText({ + displayName: 'Site ID', + description: + 'The site ID that will be associated to this connection. Site IDs can be found on the main Websites page.', + required: true, + }), + }, + validate: async ({ auth }) => { + try { + await validateAuth(auth); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: (e as Error)?.message, + }; + } + }, + required: true, +}); + +const validateAuth = async (auth: MatomoAuthType) => { + await propsValidation.validateZod(auth, { + domain: z.string().url(), + tokenAuth: z.string().regex(/^[a-zA-Z0-9]+$/), + siteId: z.string().regex(/^[0-9]+$/), + }); + + const response = await getVersion(auth); + if (response.success !== true) { + throw new Error( + 'Authentication failed. Please check your setup and try again.' + ); + } +}; diff --git a/packages/pieces/community/matomo/tsconfig.json b/packages/pieces/community/matomo/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/matomo/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/matomo/tsconfig.lib.json b/packages/pieces/community/matomo/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/matomo/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/matrix/.eslintrc.json b/packages/pieces/community/matrix/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/matrix/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/matrix/README.md b/packages/pieces/community/matrix/README.md new file mode 100644 index 0000000..53bb2ce --- /dev/null +++ b/packages/pieces/community/matrix/README.md @@ -0,0 +1,7 @@ +# pieces-matrix + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-matrix` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/matrix/package.json b/packages/pieces/community/matrix/package.json new file mode 100644 index 0000000..080c4dd --- /dev/null +++ b/packages/pieces/community/matrix/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-matrix", + "version": "0.3.5" +} diff --git a/packages/pieces/community/matrix/project.json b/packages/pieces/community/matrix/project.json new file mode 100644 index 0000000..ace53a2 --- /dev/null +++ b/packages/pieces/community/matrix/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-matrix", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/matrix/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/matrix", + "tsConfig": "packages/pieces/community/matrix/tsconfig.lib.json", + "packageJson": "packages/pieces/community/matrix/package.json", + "main": "packages/pieces/community/matrix/src/index.ts", + "assets": [ + "packages/pieces/community/matrix/*.md", + { + "input": "packages/pieces/community/matrix/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/matrix/src/index.ts b/packages/pieces/community/matrix/src/index.ts new file mode 100644 index 0000000..3c0bcb0 --- /dev/null +++ b/packages/pieces/community/matrix/src/index.ts @@ -0,0 +1,56 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { sendMessage } from './lib/actions/send-message'; + +export const matrixAuth = PieceAuth.CustomAuth({ + description: ` + To obtain access token & Home server: + + 1. Log in to the account you want to get the access token for on Element. + 2. Click on the name in the top left corner of the screen, then select "Settings" from the dropdown menu. + 3. In the Settings dialog, click the "Help & About" tab on the left side of the screen. + 4. Scroll to the bottom of the page and click on the "click to reveal" part of the "Access Token" section. + 5. Copy your access token & Home Server URL and paste them into the fields below. + `, + props: { + base_url: Property.ShortText({ + displayName: 'Home Server', + required: true, + }), + access_token: PieceAuth.SecretText({ + displayName: 'Access Token', + required: true, + }), + }, + required: true, +}); + +export const matrix = createPiece({ + displayName: 'Matrix', + description: + 'Open standard for interoperable, decentralized, real-time communication', + + logoUrl: 'https://cdn.activepieces.com/pieces/matrix.png', + categories: [PieceCategory.COMMUNICATION], + minimumSupportedRelease: '0.30.0', + authors: ["MyWay","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + auth: matrixAuth, + actions: [ + sendMessage, + createCustomApiCallAction({ + baseUrl: (auth) => (auth as { base_url: string }).base_url, + auth: matrixAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${ + (auth as { access_token: string }).access_token + }`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/matrix/src/lib/actions/send-message.ts b/packages/pieces/community/matrix/src/lib/actions/send-message.ts new file mode 100644 index 0000000..09038ad --- /dev/null +++ b/packages/pieces/community/matrix/src/lib/actions/send-message.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getRoomId, sendMessage as sendMatrixMessage } from '../common/common'; +import { matrixAuth } from '../..'; + +export const sendMessage = createAction({ + auth: matrixAuth, + name: 'send_message', + displayName: 'Send Message', + description: 'Send a message to a room', + props: { + room_alias: Property.ShortText({ + displayName: 'Room Alias', + description: + 'Copy it from room settings -> advanced -> room addresses -> main address', + required: true, + }), + message: Property.LongText({ + displayName: 'Message', + description: 'The message to send (markdown)', + required: true, + }), + }, + async run({ auth, propsValue }) { + const baseUrl = auth.base_url.replace(/\/$/, ''); + const accessToken = auth.access_token; + const roomId = ( + await getRoomId(baseUrl, propsValue.room_alias, accessToken) + ).body.room_id; + + return await sendMatrixMessage( + baseUrl, + roomId, + accessToken, + propsValue.message + ); + }, +}); diff --git a/packages/pieces/community/matrix/src/lib/common/common.ts b/packages/pieces/community/matrix/src/lib/common/common.ts new file mode 100644 index 0000000..8d2e3c8 --- /dev/null +++ b/packages/pieces/community/matrix/src/lib/common/common.ts @@ -0,0 +1,51 @@ +import { + httpClient, + HttpMethod, + AuthenticationType, + HttpResponse, +} from '@activepieces/pieces-common'; +import { marked } from 'marked'; + +export async function getRoomId( + baseUrl: string, + roomAlias: string, + accessToken: string +): Promise { + const response = httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${baseUrl}/_matrix/client/r0/directory/room/${encodeURIComponent( + roomAlias + )}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }); + + return response; +} + +export async function sendMessage( + baseUrl: string, + roomId: string, + accessToken: string, + message: string +): Promise { + const response = httpClient.sendRequest({ + method: HttpMethod.POST, + url: + `${baseUrl}/_matrix/client/r0/rooms/` + roomId + '/send/m.room.message', + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + body: { + msgtype: 'm.text', + body: message, + format: 'org.matrix.custom.html', + formatted_body: marked(message), + }, + }); + + return response; +} diff --git a/packages/pieces/community/matrix/src/lib/matrix.mdx b/packages/pieces/community/matrix/src/lib/matrix.mdx new file mode 100644 index 0000000..1458c00 --- /dev/null +++ b/packages/pieces/community/matrix/src/lib/matrix.mdx @@ -0,0 +1,22 @@ +--- +title: 'Matrix' +description: '' +--- + +# How to obtain Internal room Id: + +1- Right click on the room you want to get the ID from +2- Click on "Advanced" +3- Copy the Internal Room Id + +--- + +## Triggers + +TRIGGERS + +--- + +## Actions + +ACTIONS diff --git a/packages/pieces/community/matrix/tsconfig.json b/packages/pieces/community/matrix/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/matrix/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/matrix/tsconfig.lib.json b/packages/pieces/community/matrix/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/matrix/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/mattermost/.eslintrc.json b/packages/pieces/community/mattermost/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/mattermost/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/mattermost/README.md b/packages/pieces/community/mattermost/README.md new file mode 100644 index 0000000..a7ab893 --- /dev/null +++ b/packages/pieces/community/mattermost/README.md @@ -0,0 +1,7 @@ +# pieces-mattermost + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-mattermost` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/mattermost/package.json b/packages/pieces/community/mattermost/package.json new file mode 100644 index 0000000..c7c7266 --- /dev/null +++ b/packages/pieces/community/mattermost/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-mattermost", + "version": "0.3.4" +} \ No newline at end of file diff --git a/packages/pieces/community/mattermost/project.json b/packages/pieces/community/mattermost/project.json new file mode 100644 index 0000000..197ddd2 --- /dev/null +++ b/packages/pieces/community/mattermost/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-mattermost", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/mattermost/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/mattermost", + "tsConfig": "packages/pieces/community/mattermost/tsconfig.lib.json", + "packageJson": "packages/pieces/community/mattermost/package.json", + "main": "packages/pieces/community/mattermost/src/index.ts", + "assets": [ + "packages/pieces/community/mattermost/*.md", + { + "input": "packages/pieces/community/mattermost/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/mattermost/src/index.ts b/packages/pieces/community/mattermost/src/index.ts new file mode 100644 index 0000000..8807538 --- /dev/null +++ b/packages/pieces/community/mattermost/src/index.ts @@ -0,0 +1,55 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { sendMessage } from './lib/actions/send-message'; + +const markdownDescription = ` +**Workspace URL**: The url of mattermost instance (e.g \`https://activepieces.mattermost.com\`) + +**Bot Token**: Obtain it from settings > integrations > bot accounts > add bot account +`; + +export const mattermostAuth = PieceAuth.CustomAuth({ + description: markdownDescription, + required: true, + props: { + workspace_url: Property.ShortText({ + displayName: 'Workspace URL', + description: + 'The workspace URL of the Mattermost instance (e.g https://activepieces.mattermost.com)', + required: true, + }), + token: Property.ShortText({ + displayName: 'Bot Token', + description: 'The bot token to use to authenticate', + required: true, + }), + }, +}); + +export const mattermost = createPiece({ + displayName: 'Mattermost', + description: 'Open-source, self-hosted Slack alternative', + + logoUrl: 'https://cdn.activepieces.com/pieces/mattermost.png', + minimumSupportedRelease: '0.30.0', + categories: [PieceCategory.COMMUNICATION], + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + auth: mattermostAuth, + actions: [ + sendMessage, + createCustomApiCallAction({ + baseUrl: (auth) => + (auth as { workspace_url: string }).workspace_url + '/api/v4', + auth: mattermostAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as { token: string }).token}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/mattermost/src/lib/actions/send-message.ts b/packages/pieces/community/mattermost/src/lib/actions/send-message.ts new file mode 100644 index 0000000..10c05c0 --- /dev/null +++ b/packages/pieces/community/mattermost/src/lib/actions/send-message.ts @@ -0,0 +1,57 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpError, + HttpMethod, +} from '@activepieces/pieces-common'; +import { mattermostAuth } from '../..'; + +export const sendMessage = createAction({ + auth: mattermostAuth, + name: 'send_message', + displayName: 'Send Message', + description: 'Send a message to a Mattermost channel', + props: { + channel_id: Property.ShortText({ + displayName: 'Channel ID', + description: + 'The channel to send the message to, get that ID by clicking on info near start call butto', + required: true, + }), + text: Property.LongText({ + displayName: 'Message Text', + description: 'The text of the message to send', + required: true, + }), + }, + async run(context) { + // Remove trailing slash from workspace URL + const baseUrl = context.auth.workspace_url.replace(/\/$/, ''); + try { + return await httpClient.sendRequest({ + url: `${baseUrl}/api/v4/posts`, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.token, + }, + body: { + channel_id: context.propsValue.channel_id, + message: context.propsValue.text, + }, + }); + } catch (e: HttpError | unknown) { + if (e instanceof HttpError) { + const httpError = e as HttpError; + console.log(httpError); + if (httpError?.response.status === 403) { + throw new Error( + 'Please make sure you have the correct bot token and channel ID.' + ); + } + } + throw e; + } + }, +}); diff --git a/packages/pieces/community/mattermost/tsconfig.json b/packages/pieces/community/mattermost/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/mattermost/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/mattermost/tsconfig.lib.json b/packages/pieces/community/mattermost/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/mattermost/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/mautic/.eslintrc.json b/packages/pieces/community/mautic/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/mautic/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/mautic/README.md b/packages/pieces/community/mautic/README.md new file mode 100644 index 0000000..43ab2fe --- /dev/null +++ b/packages/pieces/community/mautic/README.md @@ -0,0 +1,33 @@ +# pieces-mautic + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-mautic` to execute the lint via [ESLint](https://eslint.org/). + +## For Testing + +Create a Sandbox account by following instructions [here](https://www.mautic.org/demo) + +# List of API Used + +## Contact API + +### [Contact Creation API](https://developer.mautic.org/#create-contact) + +### [Contact Search API](https://developer.mautic.org/#list-contacts) + +### [Contact Update API](https://developer.mautic.org/#edit-contact) + +## Company API + +### [Company Creation API](https://developer.mautic.org/#create-companyhttps://developer.mautic.org/#create-company) + +### [Company Search API](https://developer.mautic.org/#list-contact-companies) + +### [Company Update API](https://developer.mautic.org/#edit-company) + +## Metadata API + +### [List Fields](https://developer.mautic.org/#list-contact-fields) diff --git a/packages/pieces/community/mautic/package-lock.json b/packages/pieces/community/mautic/package-lock.json new file mode 100644 index 0000000..db2d832 --- /dev/null +++ b/packages/pieces/community/mautic/package-lock.json @@ -0,0 +1,12 @@ +{ + "name": "@activepieces/piece-mautic", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-mautic", + "version": "0.0.1" + } + } +} diff --git a/packages/pieces/community/mautic/package.json b/packages/pieces/community/mautic/package.json new file mode 100644 index 0000000..b0000e9 --- /dev/null +++ b/packages/pieces/community/mautic/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-mautic", + "version": "0.4.4" +} \ No newline at end of file diff --git a/packages/pieces/community/mautic/project.json b/packages/pieces/community/mautic/project.json new file mode 100644 index 0000000..5c5cb38 --- /dev/null +++ b/packages/pieces/community/mautic/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-mautic", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/mautic/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/mautic", + "tsConfig": "packages/pieces/community/mautic/tsconfig.lib.json", + "packageJson": "packages/pieces/community/mautic/package.json", + "main": "packages/pieces/community/mautic/src/index.ts", + "assets": [ + "packages/pieces/community/mautic/*.md", + { + "input": "packages/pieces/community/mautic/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/mautic/src/index.ts b/packages/pieces/community/mautic/src/index.ts new file mode 100644 index 0000000..78cb331 --- /dev/null +++ b/packages/pieces/community/mautic/src/index.ts @@ -0,0 +1,83 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + PieceAuth, + PiecePropValueSchema, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { + createCompany, + createContact, + searchCompany, + searchContact, + updateCompany, + updateContact, +} from './lib/actions'; +import { triggers } from './lib/triggers'; + +const markdownDescription = ` +Follow these steps: + +1. **Enter the Base URL:** Open your Mautic instance and copy the URL from the address bar. If your dashboard link is "https://mautic.ddev.site/s/dashboard", set your base URL as "https://mautic.ddev.site/". + +2. **Enable Basic Authentication:** Log in to Mautic, go to **Settings** > **Configuration** > **API Settings**, and ensure that Basic Authentication is enabled. + +`; + +export const mauticAuth = PieceAuth.CustomAuth({ + description: markdownDescription, + props: { + base_url: Property.ShortText({ + displayName: 'Base URL', + required: true, + }), + username: Property.ShortText({ + displayName: 'Username', + required: true, + }), + password: PieceAuth.SecretText({ + displayName: 'Password', + required: true, + }), + }, + required: true, +}); + +export const mautic = createPiece({ + displayName: 'Mautic', + description: 'Open-source marketing automation software', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/mautic.png', + authors: ["bibhuty-did-this","kanarelo","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + categories: [PieceCategory.MARKETING], + auth: mauticAuth, + actions: [ + createContact, + searchContact, + updateContact, + createCompany, + searchCompany, + updateCompany, + createCustomApiCallAction({ + auth: mauticAuth, + baseUrl: (auth) => { + const { base_url } = auth as PiecePropValueSchema; + return `${base_url.endsWith('/') ? base_url : base_url + '/'}api/`; + }, + authMapping: async (auth) => { + const { username, password } = auth as PiecePropValueSchema< + typeof mauticAuth + >; + return { + Authorization: + 'Basic ' + + Buffer.from(`${username}:${password}`).toString('base64'), + 'Content-Type': 'application/json', + }; + }, + }), + ], + triggers, +}); diff --git a/packages/pieces/community/mautic/src/lib/actions/create-company.ts b/packages/pieces/community/mautic/src/lib/actions/create-company.ts new file mode 100644 index 0000000..449f702 --- /dev/null +++ b/packages/pieces/community/mautic/src/lib/actions/create-company.ts @@ -0,0 +1,34 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { mauticCommon } from '../common'; +import { mauticAuth } from '../..'; + +export const createCompany = createAction({ + auth: mauticAuth, + description: 'Creates a new company in Mautic CRM', // Must be a unique across the piece, this shouldn't be changed. + displayName: 'Create Company', + name: 'create_mautic_company', + props: { + fields: mauticCommon.companyFields, + }, + run: async function (context) { + const { base_url, username, password } = context.auth; + const request: HttpRequest = { + method: HttpMethod.POST, + url: + (base_url.endsWith('/') ? base_url : base_url + '/') + + 'api/companies/new', + body: JSON.stringify(context.propsValue.fields), + headers: { + Authorization: + 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64'), + 'Content-Type': 'application/json', + }, + }; + return await httpClient.sendRequest(request); + }, +}); diff --git a/packages/pieces/community/mautic/src/lib/actions/create-contact.ts b/packages/pieces/community/mautic/src/lib/actions/create-contact.ts new file mode 100644 index 0000000..1bbba8b --- /dev/null +++ b/packages/pieces/community/mautic/src/lib/actions/create-contact.ts @@ -0,0 +1,34 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { mauticCommon } from '../common'; +import { mauticAuth } from '../..'; + +export const createContact = createAction({ + auth: mauticAuth, + description: 'Creates a new contact in Mautic CRM', // Must be a unique across the piece, this shouldn't be changed. + displayName: 'Create Contact', + name: 'create_mautic_contact', + props: { + fields: mauticCommon.contactFields, + }, + run: async function (context) { + const { base_url, username, password } = context.auth; + const request: HttpRequest = { + method: HttpMethod.POST, + url: + (base_url.endsWith('/') ? base_url : base_url + '/') + + 'api/contacts/new', + body: JSON.stringify(context.propsValue.fields), + headers: { + Authorization: + 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64'), + 'Content-Type': 'application/json', + }, + }; + return await httpClient.sendRequest(request); + }, +}); diff --git a/packages/pieces/community/mautic/src/lib/actions/index.ts b/packages/pieces/community/mautic/src/lib/actions/index.ts new file mode 100644 index 0000000..d915524 --- /dev/null +++ b/packages/pieces/community/mautic/src/lib/actions/index.ts @@ -0,0 +1,6 @@ +export { createContact } from './create-contact'; +export { searchContact } from './search-contact'; +export { updateContact } from './update-contact'; +export { createCompany } from './create-company'; +export { searchCompany } from './search-company'; +export { updateCompany } from './update-company'; diff --git a/packages/pieces/community/mautic/src/lib/actions/search-company.ts b/packages/pieces/community/mautic/src/lib/actions/search-company.ts new file mode 100644 index 0000000..92c5f6c --- /dev/null +++ b/packages/pieces/community/mautic/src/lib/actions/search-company.ts @@ -0,0 +1,28 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mauticCommon, searchEntity } from '../common'; +import { mauticAuth } from '../..'; + +export const searchCompany = createAction({ + auth: mauticAuth, + description: 'Search for a company in Mautic CRM', // Must be a unique across the piece, this shouldn't be changed. + displayName: 'Search Company', + name: 'search_mautic_company', + props: { + fields: mauticCommon.companyFields, + }, + run: async function (context) { + const { base_url, username, password } = context.auth; + const url = + (base_url.endsWith('/') ? base_url : base_url + '/') + 'api/companies'; + const fields = context.propsValue.fields; + const keys = Object.keys(fields); + let searchParams = '?'; + for (const key of keys) { + if (fields[key]) { + searchParams += `search=${key}:${fields[key]}&`; + } + } + const response = await searchEntity(url, searchParams, username, password); + return Object.values(response.body.companies)[0]; + }, +}); diff --git a/packages/pieces/community/mautic/src/lib/actions/search-contact.ts b/packages/pieces/community/mautic/src/lib/actions/search-contact.ts new file mode 100644 index 0000000..9a1ec5d --- /dev/null +++ b/packages/pieces/community/mautic/src/lib/actions/search-contact.ts @@ -0,0 +1,30 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mauticCommon, searchEntity } from '../common'; +import { mauticAuth } from '../..'; + +export const searchContact = createAction({ + auth: mauticAuth, + description: 'Search for a contact in Mautic CRM', // Must be a unique across the piece, this shouldn't be changed. + displayName: 'Search Contact', + name: 'search_mautic_contact', + props: { + fields: mauticCommon.contactFields, + }, + run: async function (context) { + const { base_url, username, password } = context.auth; + const url = + (base_url.endsWith('/') ? base_url : base_url + '/') + 'api/contacts'; + const fields = context.propsValue.fields; + const keys = Object.keys(fields); + let count = 0; + let searchParams = '?'; + for (const key of keys) { + if (fields[key]) { + searchParams += `where[${count}][col]=${key}&where[${count}][expr]=eq&where[${count}][val]=${fields[key]}&`; + ++count; + } + } + const response = await searchEntity(url, searchParams, username, password); + return Object.values(response.body.contacts)[0]; + }, +}); diff --git a/packages/pieces/community/mautic/src/lib/actions/update-company.ts b/packages/pieces/community/mautic/src/lib/actions/update-company.ts new file mode 100644 index 0000000..6cd35d7 --- /dev/null +++ b/packages/pieces/community/mautic/src/lib/actions/update-company.ts @@ -0,0 +1,44 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { mauticCommon } from '../common'; +import { mauticAuth } from '../..'; + +export const updateCompany = createAction({ + auth: mauticAuth, + description: 'Update a company in Mautic CRM', // Must be a unique across the piece, this shouldn't be changed. + displayName: 'Update Company With Contact Id', + name: 'update_mautic_company', + props: { + id: mauticCommon.id, + fields: mauticCommon.companyFields, + }, + run: async function (context) { + const { base_url, username, password } = context.auth; + // This is intentionally done because for `null` data Mautic doesn't change data for contacts but + // for the same it changes data for companies. This step is taken to ensure both behave the same. + const fields = context.propsValue.fields; + const keys = Object.keys(fields); + for (const key of keys) { + if (!fields[key]) { + delete fields[key]; + } + } + const request: HttpRequest = { + method: HttpMethod.PATCH, + url: `${ + base_url.endsWith('/') ? base_url : base_url + '/' + }api/companies/${context.propsValue.id}/edit`, + body: JSON.stringify(fields), + headers: { + Authorization: + 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64'), + 'Content-Type': 'application/json', + }, + }; + return await httpClient.sendRequest(request); + }, +}); diff --git a/packages/pieces/community/mautic/src/lib/actions/update-contact.ts b/packages/pieces/community/mautic/src/lib/actions/update-contact.ts new file mode 100644 index 0000000..840fb41 --- /dev/null +++ b/packages/pieces/community/mautic/src/lib/actions/update-contact.ts @@ -0,0 +1,35 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { mauticCommon } from '../common'; +import { mauticAuth } from '../..'; + +export const updateContact = createAction({ + auth: mauticAuth, + description: 'Update a contact in Mautic CRM', // Must be a unique across the piece, this shouldn't be changed. + displayName: 'Update Contact With Contact Id', + name: 'update_mautic_contact', + props: { + id: mauticCommon.id, + fields: mauticCommon.contactFields, + }, + run: async function (context) { + const { base_url, username, password } = context.auth; + const request: HttpRequest = { + method: HttpMethod.PATCH, + url: `${base_url.endsWith('/') ? base_url : base_url + '/'}api/contacts/${ + context.propsValue.id + }/edit`, + body: JSON.stringify(context.propsValue.fields), + headers: { + Authorization: + 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64'), + 'Content-Type': 'application/json', + }, + }; + return await httpClient.sendRequest(request); + }, +}); diff --git a/packages/pieces/community/mautic/src/lib/common/helper.ts b/packages/pieces/community/mautic/src/lib/common/helper.ts new file mode 100644 index 0000000..bcf0548 --- /dev/null +++ b/packages/pieces/community/mautic/src/lib/common/helper.ts @@ -0,0 +1,124 @@ +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; + +// Function Section +export const mapMauticToActivepiecesProperty = ( + type: string, + fieldMetadata: { + displayName: string; + required: boolean; + }, + properties: object +) => { + switch (type) { + case 'lookup': + case 'text': + case 'email': + case 'tel': + case 'region': + case 'country': + case 'locale': + case 'timezone': + case 'url': + return Property.ShortText(fieldMetadata); + case 'date': + case 'datetime': + return Property.DateTime(fieldMetadata); + case 'number': + return Property.Number(fieldMetadata); + case 'boolean': + return Property.StaticDropdown({ + ...fieldMetadata, + options: { + options: [ + { value: 'no', label: 'No' }, + { value: 'yes', label: 'Yes' }, + ], + }, + }); + case 'multiselect': + return Property.StaticMultiSelectDropdown({ + ...fieldMetadata, + options: { + options: Object.values(properties)[0], + }, + }); + case 'select': + return Property.StaticDropdown({ + ...fieldMetadata, + options: { + options: Object.values(properties)[0], + }, + }); + default: + console.error(`No support of type ${type}`); + return null; + } +}; + +export const fetchDynamicFieldsFromMetadata = async ( + baseUrl: string, + username: string, + password: string, + type: 'contact' | 'company' | 'lead' +) => { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${ + baseUrl.endsWith('/') ? baseUrl : baseUrl + '/' + }api/fields/${type}?limit=1000`, + headers: { + Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString( + 'base64' + )}`, + 'Content-Type': 'application/json', + }, + }; + + const result = await httpClient.sendRequest(request); + if (result.status == 200) { + return Object.values(result.body.fields).reduce( + (fields: DynamicPropsValue, field) => { + const { + label: displayName, + alias, + type, + properties, + } = field as Record; + const fieldMetadata = { + displayName, + required: false, + }; + if (!type) return {}; + const f = mapMauticToActivepiecesProperty( + type, + fieldMetadata, + properties + ); + if (f) { + fields[alias] = f; + } + return fields; + }, + {} + ); + } + throw Error(`Unable to fetch ${type} metadata`); +}; + +export const getFields = (type: 'contact' | 'company' | 'lead') => + Property.DynamicProperties({ + displayName: 'All Fields', + description: 'List of all possible fields present', + required: true, + refreshers: [], + props: async ({ auth }) => { + if (!auth) return {}; + const { base_url, username, password } = auth; + return fetchDynamicFieldsFromMetadata(base_url, username, password, type); + }, + }); diff --git a/packages/pieces/community/mautic/src/lib/common/index.ts b/packages/pieces/community/mautic/src/lib/common/index.ts new file mode 100644 index 0000000..066a601 --- /dev/null +++ b/packages/pieces/community/mautic/src/lib/common/index.ts @@ -0,0 +1,41 @@ +import { Property } from '@activepieces/pieces-framework'; +import { getFields } from './helper'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; + +export const mauticCommon = { + contactFields: { ...getFields('contact'), ...getFields('lead') }, + companyFields: getFields('company'), + id: Property.ShortText({ + displayName: 'Id of the entity', + required: true, + }), +}; + +export const searchEntity = async ( + url: string, + searchParams: string, + username: string, + password: string +) => { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${url}${searchParams}`, + headers: { + Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString( + 'base64' + )}`, + 'Content-Type': 'application/json', + }, + }; + const response: Record = await httpClient.sendRequest(request); + const length = response.body.total; + if (!length || length != 1) + throw Error( + 'The query is not perfect enough to get single result. Please refine' + ); + return response; +}; diff --git a/packages/pieces/community/mautic/src/lib/triggers/index.ts b/packages/pieces/community/mautic/src/lib/triggers/index.ts new file mode 100644 index 0000000..a1ed22f --- /dev/null +++ b/packages/pieces/community/mautic/src/lib/triggers/index.ts @@ -0,0 +1,453 @@ +import { HttpMethod, HttpRequest, httpClient } from '@activepieces/pieces-common'; +import { mauticAuth } from '../../index'; +import { Property, TriggerStrategy, createTrigger } from "@activepieces/pieces-framework"; + +const contactTestData = { + "contact": { + "id": 38186, + "points": 0, + "color": null, + "fields": { + "core": { + "points": { + "id": "9", + "label": "Points", + "alias": "points", + "type": "number", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": "0" + }, + "title": { + "id": "1", + "label": "Title", + "alias": "title", + "type": "lookup", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": "" + }, + "firstname": { + "id": "2", + "label": "First Name", + "alias": "firstname", + "type": "text", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": "Test" + }, + "lastname": { + "id": "3", + "label": "Last Name", + "alias": "lastname", + "type": "text", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": "Contact" + }, + "company": { + "id": "4", + "label": "Company", + "alias": "company", + "type": "text", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": "" + }, + "position": { + "id": "5", + "label": "Position", + "alias": "position", + "type": "text", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": "" + }, + "email": { + "id": "6", + "label": "Email", + "alias": "email", + "type": "email", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": "Test@email.com" + }, + "phone": { + "id": "8", + "label": "Phone", + "alias": "phone", + "type": "tel", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": "" + }, + "mobile": { + "id": "7", + "label": "Mobile", + "alias": "mobile", + "type": "tel", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": "" + }, + "address1": { + "id": "11", + "label": "Address Line 1", + "alias": "address1", + "type": "text", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": "" + }, + "address2": { + "id": "12", + "label": "Address Line 2", + "alias": "address2", + "type": "text", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": "" + }, + "city": { + "id": "13", + "label": "City", + "alias": "city", + "type": "text", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": "" + }, + "state": { + "id": "14", + "label": "State", + "alias": "state", + "type": "region", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": "" + }, + "zipcode": { + "id": "15", + "label": "Zip Code", + "alias": "zipcode", + "type": "text", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": "" + }, + "country": { + "id": "16", + "label": "Country", + "alias": "country", + "type": "country", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": "" + }, + "fax": { + "id": "10", + "label": "Fax", + "alias": "fax", + "type": "tel", + "group": "core", + "object": "lead", + "is_fixed": "0", + "value": null + }, + "preferred_locale": { + "id": "17", + "label": "Preferred Locale", + "alias": "preferred_locale", + "type": "locale", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": null + }, + "attribution_date": { + "id": "18", + "label": "Attribution Date", + "alias": "attribution_date", + "type": "datetime", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": null + }, + "attribution": { + "id": "19", + "label": "Attribution", + "alias": "attribution", + "type": "number", + "group": "core", + "object": "lead", + "is_fixed": "1", + "value": null + }, + "website": { + "id": "20", + "label": "Website", + "alias": "website", + "type": "url", + "group": "core", + "object": "lead", + "is_fixed": "0", + "value": null + }, + "boolean": { + "id": "43", + "label": "Boolean", + "alias": "boolean", + "type": "boolean", + "group": "core", + "object": "lead", + "is_fixed": "0", + "value": null + }, + "multiple_contact": { + "id": "44", + "label": "Multiple Contact", + "alias": "multiple_contact", + "type": "multiselect", + "group": "core", + "object": "lead", + "is_fixed": "0", + "value": null + } + }, + "social": { + "facebook": { + "id": "21", + "label": "Facebook", + "alias": "facebook", + "type": "text", + "group": "social", + "object": "lead", + "is_fixed": "0", + "value": null + }, + "foursquare": { + "id": "22", + "label": "Foursquare", + "alias": "foursquare", + "type": "text", + "group": "social", + "object": "lead", + "is_fixed": "0", + "value": null + }, + "instagram": { + "id": "24", + "label": "Instagram", + "alias": "instagram", + "type": "text", + "group": "social", + "object": "lead", + "is_fixed": "0", + "value": null + }, + "linkedin": { + "id": "25", + "label": "LinkedIn", + "alias": "linkedin", + "type": "text", + "group": "social", + "object": "lead", + "is_fixed": "0", + "value": null + }, + "skype": { + "id": "26", + "label": "Skype", + "alias": "skype", + "type": "text", + "group": "social", + "object": "lead", + "is_fixed": "0", + "value": null + }, + "twitter": { + "id": "27", + "label": "Twitter", + "alias": "twitter", + "type": "text", + "group": "social", + "object": "lead", + "is_fixed": "0", + "value": null + } + }, + "personal": [], + "professional": [] + } + }, + "channel": "email", + "old_status": "contactable", + "new_status": "manual", + "timestamp": "2017-12-01T00:05:18-06:00" +} + +export const triggers = [ + { + name: "lead_post_save_update", + displayName: "Contact Updated", + description: "Triggers when a contact is updated.", + sampleData: { + "mautic.lead_post_save_update": [contactTestData] + }, + eventType: "mautic.lead_post_save_update", + }, + { + name: "lead_company_change", + displayName: "Contact Company Subscription Change", + description: "Triggers when a commpany is added or removed to/from contact.", + sampleData: { + "mautic.lead_company_change": [contactTestData] + }, + eventType: "mautic.lead_company_change", + }, + { + name: "lead_channel_subscription_changed", + displayName: "Contact Channel Subscription Change", + description: "Triggers when a contact's channel subscription status changes.", + sampleData: { + "mautic.lead_channel_subscription_changed": [contactTestData] + }, + eventType: "mautic.lead_channel_subscription_changed", + }, + { + name: "lead_post_save_new", + displayName: "New Contact", + description: "Triggers when a new contact is created.", + sampleData: { + "mautic.lead_post_save_new": [contactTestData] + }, + eventType: "mautic.lead_post_save_new", + }, +] + .map((props) => registerTrigger(props)); + +function registerTrigger({ + name, + displayName, + eventType, + description, + sampleData +}: { + name: string; + displayName: string; + eventType: string; + description: string; + sampleData: unknown; +}) { + return createTrigger({ + auth: mauticAuth, + name: `mautic_${name}_trigger`, + displayName, + description, + props: { + name: Property.ShortText({ + displayName: "Webhook Name", + description: "The name the webhook will be searchable by in mautic the webhooks page.", + required: true + }), + description: Property.LongText({ + displayName: "Description", + description: "A short description of the webhook", + required: true + }) + }, + sampleData, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const { base_url, username, password } = context.auth + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${(base_url.endsWith('/') ? base_url : base_url + '/')}api/hooks/new`, + body: { + name: context.propsValue.name, + description: context.propsValue.description, + webhookUrl: context.webhookUrl, + eventsOrderbyDir: "ASC", + triggers: [eventType] + }, + headers: { + 'Content-Type': 'application/json', + 'Authorization': + 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64'), + }, + queryParams: {}, + } + + const response = await httpClient.sendRequest(request); + await context.store.put(`mautic_${name}_trigger`, response.body); + }, + async onDisable(context) { + const { base_url, username, password } = context.auth + + const webhook = await context.store.get(`mautic_${name}_trigger`); + if (webhook != null) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `${(base_url.endsWith('/') ? base_url : base_url + '/')}api/hooks/${webhook.hook.id}/delete`, + headers: { + 'Content-Type': 'application/json', + 'Authorization': + 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64'), + }, + }; + const response = await httpClient.sendRequest(request); + console.debug(`mautic.trigger.onDisable`, response); + } + }, + async run(context) { + return [context.payload.body]; + }, + }); +} + + +interface WebhookInformation { + hook: { + isPublished: boolean + dateAdded: string + dateModified: string + createdBy: number + createdByUser: string + modifiedBy: unknown | null + modifiedByUser: string + id: number + name: string + description: string + webhookUrl: string + secret: string + eventsOrderbyDir: string + category: { + id: number + createdByUser: string + modifiedByUser: string + title: string + alias: string + description: string | null + color: string | null + bundle: string + } + triggers: string[] + } +} diff --git a/packages/pieces/community/mautic/tsconfig.json b/packages/pieces/community/mautic/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/mautic/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/mautic/tsconfig.lib.json b/packages/pieces/community/mautic/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/mautic/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/mcp/.eslintrc.json b/packages/pieces/community/mcp/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/mcp/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/mcp/README.md b/packages/pieces/community/mcp/README.md new file mode 100644 index 0000000..3a31bc6 --- /dev/null +++ b/packages/pieces/community/mcp/README.md @@ -0,0 +1,7 @@ +# pieces-mcp + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-mcp` to build the library. diff --git a/packages/pieces/community/mcp/package.json b/packages/pieces/community/mcp/package.json new file mode 100644 index 0000000..879613e --- /dev/null +++ b/packages/pieces/community/mcp/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-mcp", + "version": "0.0.5" +} diff --git a/packages/pieces/community/mcp/project.json b/packages/pieces/community/mcp/project.json new file mode 100644 index 0000000..73972cb --- /dev/null +++ b/packages/pieces/community/mcp/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-mcp", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/mcp/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/mcp", + "tsConfig": "packages/pieces/community/mcp/tsconfig.lib.json", + "packageJson": "packages/pieces/community/mcp/package.json", + "main": "packages/pieces/community/mcp/src/index.ts", + "assets": [ + "packages/pieces/community/mcp/*.md", + { + "input": "packages/pieces/community/mcp/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/mcp/src/index.ts b/packages/pieces/community/mcp/src/index.ts new file mode 100644 index 0000000..4b99be6 --- /dev/null +++ b/packages/pieces/community/mcp/src/index.ts @@ -0,0 +1,16 @@ +import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { replyToMcpClient } from "./lib/actions/reply-to-mcp-client"; +import { mcpTool } from "./lib/triggers/mcp-tool"; +import { PieceCategory } from "@activepieces/shared"; + +export const mcp = createPiece({ + displayName: "MCP", + auth: PieceAuth.None(), + minimumSupportedRelease: '0.50.2', + logoUrl: "https://cdn.activepieces.com/pieces/mcp.svg", + authors: ['Gamal72', 'hazemadelkhalel'], + description: 'Connect to your hosted MCP Server using any MCP client to communicate with tools', + actions: [replyToMcpClient], + triggers: [mcpTool], + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE] +}); diff --git a/packages/pieces/community/mcp/src/lib/actions/reply-to-mcp-client.ts b/packages/pieces/community/mcp/src/lib/actions/reply-to-mcp-client.ts new file mode 100644 index 0000000..ce293e6 --- /dev/null +++ b/packages/pieces/community/mcp/src/lib/actions/reply-to-mcp-client.ts @@ -0,0 +1,93 @@ +import { + DynamicPropsValue, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { StopResponse } from '@activepieces/shared'; +import { StatusCodes } from 'http-status-codes'; + + +export const replyToMcpClient = createAction({ + name: 'reply_to_mcp_client', + displayName: 'Reply to MCP Client', + description: 'Return a response to the MCP client that called the tool.', + props: { + note: Property.MarkDown({ + value: '**Important**: Make sure your MCP trigger has (Wait for Response) turned on.' + }), + mode: Property.StaticDropdown({ + displayName: 'Mode', + description: 'Choose Simple for key-value or Advanced for JSON.', + required: true, + defaultValue: 'simple', + options: { + disabled: false, + options: [ + { + label: 'Simple', + value: 'simple', + }, + { + + label: 'Advanced', + value: 'advanced', + }, + ], + }, + }), + response: Property.DynamicProperties({ + displayName: 'Response', + required: true, + refreshers: ['mode'], + props: async (propsValue) => { + const mode = propsValue['mode'] as unknown as string; + const fields: DynamicPropsValue = {}; + if (mode === 'simple') { + fields['response'] = Property.Object({ + displayName: 'Response', + required: true, + }); + } else { + fields['response'] = Property.Json({ + displayName: 'Response', + required: true, + }); + } + return fields; + }, + }), + respond: Property.StaticDropdown({ + displayName: 'Flow Execution', + required: false, + defaultValue: 'stop', + options: { + disabled: false, + options: [ + { label: 'Stop', value: 'stop' }, + { label: 'Respond and Continue', value: 'respond' }, + ], + }, + }), + }, + + async run(context) { + const { response, respond } = context.propsValue; + + const stopResponse: StopResponse = { + status: StatusCodes.OK, + headers: {}, + body: response + }; + + if (respond === 'respond') { + context.run.respond({ + response: stopResponse, + }); + } else { + context.run.stop({ + response: stopResponse, + }); + } + return stopResponse; + }, +}); diff --git a/packages/pieces/community/mcp/src/lib/triggers/mcp-tool.ts b/packages/pieces/community/mcp/src/lib/triggers/mcp-tool.ts new file mode 100644 index 0000000..266b18a --- /dev/null +++ b/packages/pieces/community/mcp/src/lib/triggers/mcp-tool.ts @@ -0,0 +1,84 @@ +import { + createTrigger, + Property, + TriggerStrategy, + } from '@activepieces/pieces-framework'; +import { McpPropertyType } from '@activepieces/shared'; + + +export const mcpTool = createTrigger({ + name: 'mcp_tool', + displayName: 'MCP Tool', + description: 'Creates a tool that MCP clients can call to execute this flow', + props: { + toolName: Property.ShortText({ + displayName: 'Name', + description: 'Used to call this tool from MCP clients like Claude Desktop, Cursor, or Windsurf', + required: true, + }), + toolDescription: Property.LongText({ + displayName: 'Description', + description: 'Used to describe what this tool does and when to use it', + required: true, + }), + inputSchema: Property.Array({ + displayName: 'Parameters', + description: 'Define the input parameters that this tool accepts. Parameters will be shown to users when calling the tool.', + required: false, + defaultValue: [ + { + name: '', + type: McpPropertyType.TEXT, + required: true, + description: '', + }, + ], + properties: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + type: Property.StaticDropdown({ + displayName: 'Type', + required: true, + defaultValue: McpPropertyType.TEXT, + options: { + options: Object.values(McpPropertyType).map((type) => ({ + value: type, + label: type, + })), + }, + }), + required: Property.Checkbox({ + displayName: 'Required', + required: true, + defaultValue: true, + }), + }, + }), + returnsResponse: Property.Checkbox({ + displayName: 'Wait for Response', + description: 'Keep the MCP client waiting until it receives a response via the Reply to MCP Client action', + defaultValue: false, + required: true, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: null, + async onEnable() { + // ignore + }, + async onDisable() { + // ignore + }, + async run(context) { + return [context.payload]; + }, + async test() { + return [{}]; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mcp/tsconfig.json b/packages/pieces/community/mcp/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/mcp/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/mcp/tsconfig.lib.json b/packages/pieces/community/mcp/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/mcp/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/medullar/.eslintrc.json b/packages/pieces/community/medullar/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/medullar/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/medullar/README.md b/packages/pieces/community/medullar/README.md new file mode 100644 index 0000000..eee3bcd --- /dev/null +++ b/packages/pieces/community/medullar/README.md @@ -0,0 +1,369 @@ +# pieces-medullar + +Library of [Activepieces](https://www.activepieces.com/) for [Medullar Solutions](https://www.medullar.com) + +## Overview + +`pieces-medullar` is a library designed to integrate Medullar Solutions with the Activepieces framework, enabling seamless automation and extensibility. + +## Features + +- **Extensibility**: Easily integrate with other services. +- **Type-Safe**: Built with TypeScript for robust type checking. +- **Community-Driven**: Contributions are welcome to enhance its capabilities. + +## Contributing + +We welcome contributions! Please refer to the [Contributing Guide](https://www.activepieces.com/docs/contributing/overview) for more details. + +## License + +This library is released under the [MIT License](https://opensource.org/licenses/MIT). + +## API Documentation + +### Base URL + +`https://api.medullar.com` + +### Authentication + +All endpoints require authentication. Include a valid access token in the `Authorization` header: + +`Authorization: Bearer ` + +### Endpoints + +#### Get User Details + +**GET** `/auth/v1/users/me/` + +Retrieve details about the currently authenticated user. + +* URL: `/auth/v1/users/me/` +* Method: GET +* Auth required: Yes +* Example Response + +```json +{ + "id": 360, + "uuid": "7e282906-b487-46d3-9e43-8a4fe5b27407", + "username": "marc@medullar.com", + "email": "marc@medullar.com", + "first_name": "Marc", + "last_name": "Llopart", + "language": "en", + "role": "super_admin", + "status": "active", + "onboarding_status": "completed", + "other_data": {}, + "salesforce_metadata": {}, + "is_active": true, + "date_joined": "2025-03-06T13:49:50Z", + "last_login": "2025-05-08T08:19:43.969447Z", + "created_at": "2025-03-06T13:49:50.011260Z", + "updated_at": "2025-04-02T16:23:57.557045Z", + "devices": null, + "company": { + "id": 26567447, + "uuid": "3d49382e-19cb-431b-90a0-dc8f617436f5", + "name": "Medullar Solutions", + "stripe_promotion_code": "" + }, + "num_days_until_trial_expiration_date": -1 +} +``` + +#### Get User Medullar Spaces + +**GET** `/explorator/v1/spaces/?user={user_uuid}&limit=1000&offset=0` + +Retrieve a list of Medullar spaces associated with a specific user. + +* URL: `/explorator/v1/spaces/` +* Method: GET +* Query Parameters: + * user: UUID of the user + * limit: Number of results to return + * offset: Pagination offset +* Auth required: Yes +* Example Response + +```json +{ + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id": 496, + "uuid": "4d6a15d0-246f-447a-b53e-5dd9101d53c6", + "name": "Internal Strategy", + "is_my_space": true, + "context": "Quarterly strategy space including roadmap planning and executive updates.", + "questions": [], + "expiration_date": "9999-12-31T00:00:00Z", + "created_at": "2025-05-05T09:18:28.087134Z", + "updated_at": "2025-05-05T09:18:28.087145Z", + "records": null, + "users": null, + "record_count": 0, + "company": { + "id": 47, + "uuid": "3d49382e-19cb-431b-90a0-dc8f617436f5", + "name": "Medullar Solutions", + "created_at": "2024-02-01T14:05:20.245119Z", + "updated_at": "2025-05-07T15:51:56.077837Z" + }, + "role": "owner" + } + ] +} +``` + +#### Get Chats in a Space + +**GET** `/explorator/v1/chats/?space={spaceId}&limit=1000&offset=0` + +Get a list of chats within a given Medullar space. + +* URL: `/explorator/v1/chats/` +* Method: GET +* Query Parameters: + * space: ID of the space + * limit, offset: Pagination controls +* Auth required: Yes +* Example response + +```json +{ + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id": 676, + "uuid": "7904e660-ed82-47f1-a8cb-635458c0c0aa", + "name": "automated", + "description": "", + "created_at": "2025-05-07T08:39:35.156881Z", + "updated_at": "2025-05-07T08:39:35.484367Z", + "space": { + "uuid": "1fdda622-c253-4325-bb82-95efd47051fb", + "name": "hello" + } + } + ] +} +``` + +#### Create a new record in a Space + +**POST** `/explorator/v1/records/` + +Add a new record to a Medullar space. + +* URL: `/explorator/v1/records/` +* Method: POST +* Auth required: Yes +* Body Parameters (JSON): + +```json +{ + "user": { + "uuid": "7e282906-b487-46d3-9e43-8a4fe5b27407" + }, + "spaces": [ + { + "uuid": "1fdda622-c253-4325-bb82-95efd47051fb" + } + ], + "data": { + "content": "let's add text to a space", + "url": "" + }, + "source": "text", + "company": { + "uuid": "3d49382e-19cb-431b-90a0-dc8f617436f5" + } +} +``` + +source can be `text` `url` `file` `image` + +* Example response + +```json +{ + "id": 2859, + "uuid": "e50e8d79-02e8-483f-a516-3f93a9f665e9", + "name": "let's add text to a space...", + "source": "text", + "data": { + "content": "let's add text to a space", + "url": "" + }, + "content_type": "text", + "action": "add_to_space", + "hash_string": "8664fdeef5dd7b78166f72d2dada6d375a5baebeffe75", + "status": "pending", + "summary": "", + "started_at": null, + "finished_at": null, + "processing_time": null, + "num_retries": 0, + "worker_task_id": "", + "created_at": "2025-05-08T08:33:25.224377Z", + "updated_at": "2025-05-08T08:33:25.228917Z", + "company": { + "id": 47, + "uuid": "3d49382e-19cb-431b-90a0-dc8f617436f5", + "name": "Medullar Solutions", + "created_at": "2024-02-01T14:05:20.245119Z", + "updated_at": "2025-05-07T15:51:56.077837Z" + }, + "user": { + "uuid": "7e282906-b487-46d3-9e43-8a4fe5b27407" + } +} +``` + +#### Create a New Chat in a Space + +**POST** `/explorator/v1/chats/` + +Create a new chat thread within a Medullar space. + +* URL: `/explorator/v1/chats/` +* Method: POST +* Auth required: Yes +* Body Parameters (JSON): + +```json +{ + "space": { + "uuid": "1fdda622-c253-4325-bb82-95efd47051fb" + }, + "name": "New Chat" +} +``` + +* Example response + +```json +{ + "id": 698, + "uuid": "9d5eec19-1e1f-4f7a-90ae-415077a8ae63", + "name": "New Chat", + "description": "", + "created_at": "2025-05-08T08:38:26.426639Z", + "updated_at": "2025-05-08T08:38:26.426649Z", + "space": { + "uuid": "1fdda622-c253-4325-bb82-95efd47051fb", + "name": "hello" + } +} +``` + +#### Post a Message in a Chat + +**POST** `/explorator/v1/messages/?chat={chatId}` + +Post a new message to an existing chat in a Medullar space. + +* URL: /explorator/v1/messages/ +* Query Parameter: chat - ID of the chat +* Method: POST +* Auth required: Yes + +* Body Parameters (JSON): + +```json +{ + "chat": { + "uuid": "9d5eec19-1e1f-4f7a-90ae-415077a8ae63" + }, + "text": "@medullar new chat", + "user_email": "marc@medullar.com", + "user_uuid": "7e282906-b487-46d3-9e43-8a4fe5b27407", + "is_bot": false, + "created_at": "2025-05-08T08:38:26.504Z", + "is_reasoning_selected": false, + "selected_mode": "single_agent", + "user_name": "marc" +} +``` + +* Example response + +``` +{ + "id": 2319, + "uuid": "68fafef2-949a-409c-b7cd-21445f5a241b", + "text": "@medullar new chat", + "user_uuid": "7e282906-b487-46d3-9e43-8a4fe5b27407", + "user_email": "marc@medullar.com", + "user_first_name": "Marc", + "user_last_name": "Llopart", + "is_bot": false, + "created_at": "2025-05-08T08:38:26.713567Z", + "updated_at": "2025-05-08T08:38:26.713577Z", + "is_reasoning_selected": false, + "selected_mode": "single_agent", + "source": "external_api", + "chat": { + "uuid": "9d5eec19-1e1f-4f7a-90ae-415077a8ae63", + "name": "New Chat" + } +} +``` + +source should be `external_api` if you want to get the response in the API call, `internal_api` will return the response in a websocket. + +#### Create a New Space + +**POST** `/explorator/v1/spaces/` + +Create a new Medullar space. + +* URL: `/explorator/v1/spaces/` +* Method: POST +* Auth required: Yes + +* Body Parameters (JSON): + +```json +{ + "name": "test space", + "company": { + "uuid": "3d49382e-19cb-431b-90a0-dc8f617436f5" + } +} +``` + +* Example response + +```json +{ + "id": 501, + "uuid": "415ab840-19b8-49dc-9aa0-d13a84ac5858", + "name": "test space", + "is_my_space": false, + "context": "", + "questions": [], + "expiration_date": "9999-12-31T00:00:00Z", + "created_at": "2025-05-08T08:41:35.786369Z", + "updated_at": "2025-05-08T08:41:35.786379Z", + "records": null, + "users": null, + "company": { + "id": 47, + "uuid": "3d49382e-19cb-431b-90a0-dc8f617436f5", + "name": "Medullar Solutions", + "created_at": "2024-02-01T14:05:20.245119Z", + "updated_at": "2025-05-07T15:51:56.077837Z" + }, + "role": "owner" +} +``` diff --git a/packages/pieces/community/medullar/package.json b/packages/pieces/community/medullar/package.json new file mode 100644 index 0000000..07ce826 --- /dev/null +++ b/packages/pieces/community/medullar/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-medullar", + "version": "0.1.1" +} diff --git a/packages/pieces/community/medullar/project.json b/packages/pieces/community/medullar/project.json new file mode 100644 index 0000000..ddad106 --- /dev/null +++ b/packages/pieces/community/medullar/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-medullar", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/medullar/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/medullar", + "tsConfig": "packages/pieces/community/medullar/tsconfig.lib.json", + "packageJson": "packages/pieces/community/medullar/package.json", + "main": "packages/pieces/community/medullar/src/index.ts", + "assets": [ + "packages/pieces/community/medullar/*.md", + { + "input": "packages/pieces/community/medullar/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/medullar/src/index.ts b/packages/pieces/community/medullar/src/index.ts new file mode 100644 index 0000000..70fb936 --- /dev/null +++ b/packages/pieces/community/medullar/src/index.ts @@ -0,0 +1,29 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { createSpace } from './lib/actions/create-space'; +import { listSpaces } from './lib/actions/list-spaces'; +import { addSpaceRecord } from './lib/actions/add-space-record'; +import { askSpace } from './lib/actions/ask-space'; +import { PieceCategory } from '@activepieces/shared'; + +export const medullarAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'Please use your personal **API KEY**. You can generate one in your [Medullar account profile](https://my.medullar.com/my-account), under the **API Keys** section.', +}); + +export const medullar = createPiece({ + displayName: 'Medullar', + description: + 'AI-powered discovery & insight platform that acts as your extended digital mind', + auth: medullarAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: + 'https://cdn.activepieces.com/pieces/medullar.png', + authors: ['mllopart'], + actions: [createSpace, listSpaces, addSpaceRecord, askSpace], + triggers: [], + categories: [ + PieceCategory.ARTIFICIAL_INTELLIGENCE, + PieceCategory.PRODUCTIVITY, + ], +}); diff --git a/packages/pieces/community/medullar/src/lib/actions/add-space-record.ts b/packages/pieces/community/medullar/src/lib/actions/add-space-record.ts new file mode 100644 index 0000000..88a2102 --- /dev/null +++ b/packages/pieces/community/medullar/src/lib/actions/add-space-record.ts @@ -0,0 +1,86 @@ +import { medullarAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { getUser, medullarCommon, medullarPropsCommon } from '../common'; + +export const addSpaceRecord = createAction({ + auth: medullarAuth, + name: 'addSpaceRecord', + displayName: 'Add Space Record', + description: 'Adds a Record into a Space', + props: { + spaceId: medullarPropsCommon.spaceId, + sourceType: Property.StaticDropdown({ + displayName: 'Source Type', + description: 'Select source type', + required: true, + options: { + options: [ + { + label: 'URL', + value: 'url', + }, + { + label: 'Text', + value: 'text', + }, + { + label: 'Image', + value: 'image', + }, + { + label: 'File', + value: 'file', + }, + ], + }, + }), + content: Property.LongText({ + displayName: 'Content', + description: 'Optional. Content of the record', + required: false, + }), + url: Property.ShortText({ + displayName: 'URL', + description: 'Optional. URL of the record', + required: false, + }), + }, + async run(context) { + const userData = await getUser(context.auth); + + let url = ' '; + let content = ' '; + if (context.propsValue['url'] != null) url = context.propsValue['url']; + if (context.propsValue['content'] != null) + content = context.propsValue['content']; + + const spaceResponse = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${medullarCommon.exploratorUrl}/records/`, + body: { + spaces: [ + { + uuid: context.propsValue['spaceId'], + }, + ], + company: { + uuid: userData.company.uuid, + }, + user: { + uuid: userData.uuid, + }, + source: context.propsValue['sourceType'], + data: { + content: content, + url: url, + }, + }, + headers: { + Authorization: `Bearer ${context.auth}`, + }, + }); + + return spaceResponse.body; + }, +}); diff --git a/packages/pieces/community/medullar/src/lib/actions/ask-space.ts b/packages/pieces/community/medullar/src/lib/actions/ask-space.ts new file mode 100644 index 0000000..9c81012 --- /dev/null +++ b/packages/pieces/community/medullar/src/lib/actions/ask-space.ts @@ -0,0 +1,90 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { medullarAuth } from '../../index'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { getUser, medullarCommon, medullarPropsCommon } from '../common'; + +export const askSpace = createAction({ + auth: medullarAuth, + name: 'askSpace', + displayName: 'Ask Space', + description: 'Ask anything to a Space', + props: { + spaceId: medullarPropsCommon.spaceId, + chatId: medullarPropsCommon.chatId, + selectedMode: Property.StaticDropdown({ + displayName: 'Chat Mode', + description: 'Select chat mode.', + required: true, + options: { + options: [ + { + label: 'Single AI Agent', + value: 'single_agent', + }, + { + label: 'Basic AI Chat', + value: 'chat', + }, + ], + }, + }), + isReasoning: Property.Checkbox({ + displayName: 'Deep Analysis', + description: + 'Optional. Enable Deep Analysis to get more accurate results but slower response time', + required: false, + defaultValue: false, + }), + text: Property.LongText({ + displayName: 'Message', + description: 'Message to send to the Space', + required: true, + }), + }, + async run(context) { + const userData = await getUser(context.auth); + let chatId = context.propsValue['chatId']; + + if (chatId == null) { + // if no chatId is selected, create a new one + const chatResponse = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${medullarCommon.exploratorUrl}/chats/`, + body: { + name: 'automated', + space: { + uuid: context.propsValue['spaceId'], + }, + }, + headers: { + Authorization: `Bearer ${context.auth}`, + }, + }); + chatId = chatResponse.body.uuid; + } + + const messageResponse = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${medullarCommon.exploratorUrl}/messages/?chat=${chatId}`, + body: { + name: 'automated', + chat: { + uuid: chatId, + }, + text: context.propsValue['text'], + user_email: userData.email, + user_uuid: userData.uuid, + user_name: userData.name, + is_bot: false, + is_reasoning_selected: context.propsValue['isReasoning'], + selected_mode: context.propsValue['selectedMode'], + source: 'external_api', + }, + headers: { + Authorization: `Bearer ${context.auth}`, + }, + }); + + return messageResponse.body; + }, +}); diff --git a/packages/pieces/community/medullar/src/lib/actions/create-space.ts b/packages/pieces/community/medullar/src/lib/actions/create-space.ts new file mode 100644 index 0000000..70113d1 --- /dev/null +++ b/packages/pieces/community/medullar/src/lib/actions/create-space.ts @@ -0,0 +1,36 @@ +import { medullarAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { medullarCommon, getUser } from '../common'; + +export const createSpace = createAction({ + auth: medullarAuth, + name: 'createSpace', + displayName: 'Create new Space', + description: 'Create a new Space.', + props: { + space_name: Property.ShortText({ + displayName: 'Space Name', + required: true, + }), + }, + async run(context) { + const userData = await getUser(context.auth); + + const spaceResponse = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${medullarCommon.exploratorUrl}/spaces/`, + body: { + name: context.propsValue['space_name'], + company: { + uuid: userData.company.uuid, + }, + }, + headers: { + Authorization: `Bearer ${context.auth}`, + }, + }); + + return spaceResponse.body; + }, +}); diff --git a/packages/pieces/community/medullar/src/lib/actions/list-spaces.ts b/packages/pieces/community/medullar/src/lib/actions/list-spaces.ts new file mode 100644 index 0000000..d62cd38 --- /dev/null +++ b/packages/pieces/community/medullar/src/lib/actions/list-spaces.ts @@ -0,0 +1,15 @@ +import { medullarAuth } from '../../index'; +import { createAction } from '@activepieces/pieces-framework'; +import { getUserSpaces } from '../common'; + +export const listSpaces = createAction({ + auth: medullarAuth, + name: 'listSpaces', + displayName: 'List Spaces', + description: 'List all user Spaces', + props: {}, + async run(context) { + const spaceListResponse = await getUserSpaces(context.auth); + return spaceListResponse; + }, +}); diff --git a/packages/pieces/community/medullar/src/lib/common/index.ts b/packages/pieces/community/medullar/src/lib/common/index.ts new file mode 100644 index 0000000..8f5d839 --- /dev/null +++ b/packages/pieces/community/medullar/src/lib/common/index.ts @@ -0,0 +1,124 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { Property } from '@activepieces/pieces-framework'; + +export const medullarCommon = { + baseUrl: 'https://api.medullar.com', + authUrl: 'https://api.medullar.com/auth/v1', + exploratorUrl: 'https://api.medullar.com/explorator/v1', +}; + +export async function getUser(authentication: string) { + const userResponse = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${medullarCommon.authUrl}/users/me/`, + headers: { + Authorization: `Bearer ${authentication}`, + }, + }); + + const userData = userResponse.body; + + if (!userData) { + throw new Error('User data not found.'); + } + + if (!userData.company) { + throw new Error('User does not belong to any company.'); + } + + return userData; +} + +export async function getUserSpaces(authentication: string) { + const userData = await getUser(authentication); + + const spaceListResponse = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${medullarCommon.exploratorUrl}/spaces/?user=${userData.uuid}&limit=1000&offset=0`, + headers: { + Authorization: `Bearer ${authentication}`, + }, + }); + + return spaceListResponse.body.results; +} + +export const medullarPropsCommon = { + spaceId: Property.Dropdown({ + displayName: 'Space', + description: 'Select an Space', + required: true, + refreshers: ['auth'], + refreshOnSearch: false, + options: async ({ auth }) => { + const authentication = auth as string; + if (!authentication) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + + const spaceListResponse = await getUserSpaces(authentication); + + return { + disabled: false, + options: spaceListResponse + .flat() + .map((list: { name: string; uuid: string }) => { + return { + label: list.name, + value: list.uuid, + }; + }), + }; + }, + }), + chatId: Property.Dropdown({ + displayName: 'Chat', + description: + 'Optional. Select a Chat where messages will be stored, if not selected, a default chat will be created with the name `automated`', + required: false, + refreshers: ['auth', 'spaceId'], + refreshOnSearch: false, + options: async ({ auth, spaceId }) => { + const authentication = auth as string; + if (!authentication) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + + if (!spaceId) { + return { + disabled: true, + options: [], + placeholder: 'Please select a Space first', + }; + } + + const chatListResponse = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${medullarCommon.exploratorUrl}/chats/?space=${spaceId}&limit=1000&offset=0`, + headers: { + Authorization: `Bearer ${authentication}`, + }, + }); + + return { + disabled: false, + options: chatListResponse.body.results + .flat() + .map((list: { name: string; uuid: string }) => { + return { + label: list.name, + value: list.uuid, + }; + }), + }; + }, + }), +}; diff --git a/packages/pieces/community/medullar/tsconfig.json b/packages/pieces/community/medullar/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/medullar/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/medullar/tsconfig.lib.json b/packages/pieces/community/medullar/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/medullar/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/mem/.eslintrc.json b/packages/pieces/community/mem/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/mem/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/mem/README.md b/packages/pieces/community/mem/README.md new file mode 100644 index 0000000..aa7bdae --- /dev/null +++ b/packages/pieces/community/mem/README.md @@ -0,0 +1,7 @@ +# pieces-mem + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-mem` to build the library. diff --git a/packages/pieces/community/mem/package.json b/packages/pieces/community/mem/package.json new file mode 100644 index 0000000..a886c93 --- /dev/null +++ b/packages/pieces/community/mem/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-mem", + "version": "0.0.1" +} diff --git a/packages/pieces/community/mem/project.json b/packages/pieces/community/mem/project.json new file mode 100644 index 0000000..13b4ff4 --- /dev/null +++ b/packages/pieces/community/mem/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-mem", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/mem/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/mem", + "tsConfig": "packages/pieces/community/mem/tsconfig.lib.json", + "packageJson": "packages/pieces/community/mem/package.json", + "main": "packages/pieces/community/mem/src/index.ts", + "assets": [ + "packages/pieces/community/mem/*.md", + { + "input": "packages/pieces/community/mem/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/mem/src/index.ts b/packages/pieces/community/mem/src/index.ts new file mode 100644 index 0000000..b58336e --- /dev/null +++ b/packages/pieces/community/mem/src/index.ts @@ -0,0 +1,36 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createMemAction } from './lib/actions/create-mem'; +import { createNoteAction } from './lib/actions/create-note'; +import { deleteNoteAction } from './lib/actions/delete-note'; + +export const memAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: `You can obtain your API key by navigating to **Integrations→ API**.`, +}); + +export const mem = createPiece({ + displayName: 'Mem', + description: 'Capture and organize your thoughts using Mem.ai', + auth: memAuth, + logoUrl: 'https://cdn.activepieces.com/pieces/mem.png', + authors: ['krushnarout', 'kishanprmr'], + categories: [PieceCategory.PRODUCTIVITY], + actions: [ + createMemAction, + createNoteAction, + deleteNoteAction, + createCustomApiCallAction({ + auth: memAuth, + baseUrl: () => 'https://api.mem.ai/v2', + authMapping: async (auth) => { + return { + Authorization: `Bearer ${auth}`, + }; + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/mem/src/lib/actions/create-mem.ts b/packages/pieces/community/mem/src/lib/actions/create-mem.ts new file mode 100644 index 0000000..eb00a99 --- /dev/null +++ b/packages/pieces/community/mem/src/lib/actions/create-mem.ts @@ -0,0 +1,47 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { memAuth } from '../../index'; +import { makeRequest } from '../common'; + +export const createMemAction = createAction({ + auth: memAuth, + name: 'create_mem', + displayName: 'Create Mem', + description: 'Save any content to Mem.ai for intelligent processing and future reference.', + props: { + input: Property.LongText({ + displayName: 'Input', + required: true, + description: 'Raw content you want to remember - HTML, emails, transcripts, or simple notes.', + }), + instructions: Property.LongText({ + displayName: 'Instructions', + required: false, + description: 'Optional guidance on how you want this content processed or remembered.', + }), + context: Property.LongText({ + displayName: 'Context', + required: false, + description: 'Additional background information to relate this to your existing knowledge.', + }), + }, + async run(context) { + const { input, instructions, context: contextInfo } = context.propsValue; + const apiKey = context.auth as string; + + const body = { + input, + ...(instructions ? { instructions } : {}), + ...(contextInfo ? { context: contextInfo } : {}), + }; + + const result = await makeRequest( + apiKey, + HttpMethod.POST, + `/mem-it`, + body + ); + + return result; + }, +}); diff --git a/packages/pieces/community/mem/src/lib/actions/create-note.ts b/packages/pieces/community/mem/src/lib/actions/create-note.ts new file mode 100644 index 0000000..213ed9e --- /dev/null +++ b/packages/pieces/community/mem/src/lib/actions/create-note.ts @@ -0,0 +1,52 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { memAuth } from '../../index'; +import { makeRequest } from '../common'; + +export const createNoteAction = createAction({ + auth: memAuth, + name: 'create_note', + displayName: 'Create Note', + description: 'Log a plain-text Markdown note into Mem, optionally with formatting, templates, collections, and timestamps.', + props: { + content: Property.LongText({ + displayName: 'Content', + required: true, + description: 'Markdown-formatted content. First line is treated as the note title.', + }), + id: Property.ShortText({ + displayName: 'Note ID', + required: false, + description: 'Optional UUID to assign to the note.', + }), + add_to_collections: Property.Array({ + displayName: 'Add to Collections', + required: false, + description: 'Collection titles or IDs to assign this note to. New collections will be created if they don’t exist.', + }), + }, + async run(context) { + const { + content, + id, + add_to_collections, + } = context.propsValue; + + const apiKey = context.auth as string; + + const body: Record = { + content, + ...(id ? { id } : {}), + ...(add_to_collections ? { add_to_collections } : {}), + }; + + const result = await makeRequest( + apiKey, + HttpMethod.POST, + '/notes', + body + ); + + return result; + }, +}); diff --git a/packages/pieces/community/mem/src/lib/actions/delete-note.ts b/packages/pieces/community/mem/src/lib/actions/delete-note.ts new file mode 100644 index 0000000..c3f6715 --- /dev/null +++ b/packages/pieces/community/mem/src/lib/actions/delete-note.ts @@ -0,0 +1,30 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { makeRequest } from '../common'; +import { memAuth } from '../../index'; + +export const deleteNoteAction = createAction({ + auth: memAuth, + name: 'delete_note', + displayName: 'Delete Note', + description: 'Delete a note in Mem by its ID.', + props: { + note_id: Property.ShortText({ + displayName: 'Note ID', + required: true, + description: 'The ID of the note to delete.', + }), + }, + async run(context) { + const { note_id } = context.propsValue; + const apiKey = context.auth as string; + + const result = await makeRequest( + apiKey, + HttpMethod.DELETE, + `/notes/${note_id}` + ); + + return result; + }, +}); diff --git a/packages/pieces/community/mem/src/lib/common/index.ts b/packages/pieces/community/mem/src/lib/common/index.ts new file mode 100644 index 0000000..253fe49 --- /dev/null +++ b/packages/pieces/community/mem/src/lib/common/index.ts @@ -0,0 +1,22 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +export async function makeRequest( + apiKey: string, + method: HttpMethod, + path: string, + body?: unknown +) { + const url = `https://api.mem.ai/v2${path}`; + + const response = await httpClient.sendRequest({ + method, + url, + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + body, + }); + + return response.body; +} diff --git a/packages/pieces/community/mem/tsconfig.json b/packages/pieces/community/mem/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/mem/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/mem/tsconfig.lib.json b/packages/pieces/community/mem/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/mem/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/mempool-space/.eslintrc.json b/packages/pieces/community/mempool-space/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/mempool-space/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/README.md b/packages/pieces/community/mempool-space/README.md new file mode 100644 index 0000000..5962a69 --- /dev/null +++ b/packages/pieces/community/mempool-space/README.md @@ -0,0 +1,7 @@ +# pieces-mempool-space + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-mempool-space` to build the library. diff --git a/packages/pieces/community/mempool-space/package.json b/packages/pieces/community/mempool-space/package.json new file mode 100644 index 0000000..7de1914 --- /dev/null +++ b/packages/pieces/community/mempool-space/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-mempool-space", + "version": "0.0.1" +} diff --git a/packages/pieces/community/mempool-space/project.json b/packages/pieces/community/mempool-space/project.json new file mode 100644 index 0000000..b107f03 --- /dev/null +++ b/packages/pieces/community/mempool-space/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-mempool-space", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/mempool-space/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/mempool-space", + "tsConfig": "packages/pieces/community/mempool-space/tsconfig.lib.json", + "packageJson": "packages/pieces/community/mempool-space/package.json", + "main": "packages/pieces/community/mempool-space/src/index.ts", + "assets": [ + "packages/pieces/community/mempool-space/*.md", + { + "input": "packages/pieces/community/mempool-space/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/index.ts b/packages/pieces/community/mempool-space/src/index.ts new file mode 100644 index 0000000..ccbccf9 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/index.ts @@ -0,0 +1,106 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; + +// General Actions +import { getDifficultyAdjustment } from './lib/actions/general/get-difficulty-adjustment'; +import { getPrice } from './lib/actions/general/get-price'; +import { getHistoricalPrice } from './lib/actions/general/get-historical-price'; + +// Address Actions +import { getAddressDetails } from './lib/actions/addresses/get-address-details'; +import { getAddressTransactions } from './lib/actions/addresses/get-address-transactions'; +import { getAddressTransactionsChain } from './lib/actions/addresses/get-address-transactions-chain'; +import { getAddressTransactionsMempool } from './lib/actions/addresses/get-address-transactions-mempool'; +import { getAddressUtxo } from './lib/actions/addresses/get-address-utxo'; +import { validateAddress } from './lib/actions/addresses/validate-address'; + +// Fees Actions +import { getMempoolBlocksFees } from './lib/actions/fees/get-mempool-blocks-fees'; +import { getRecommendedFees } from './lib/actions/fees/get-recommended-fees'; + +// Block Actions +import { getBlock } from './lib/actions/blocks/get-block'; +import { getBlockHeader } from './lib/actions/blocks/get-block-header'; +import { getBlockHeight } from './lib/actions/blocks/get-block-height'; +import { getBlockTimestamp } from './lib/actions/blocks/get-block-timestamp'; +import { getBlockRaw } from './lib/actions/blocks/get-block-raw'; +import { getBlockStatus } from './lib/actions/blocks/get-block-status'; +import { getBlockTipHeight } from './lib/actions/blocks/get-block-tip-height'; +import { getBlockTipHash } from './lib/actions/blocks/get-block-tip-hash'; +import { getBlockTransactionId } from './lib/actions/blocks/get-block-transaction-id'; +import { getBlockTransactionIds } from './lib/actions/blocks/get-block-transaction-ids'; +import { getBlockTransactions } from './lib/actions/blocks/get-block-transactions'; +import { getBlocksBulk } from './lib/actions/blocks/get-blocks-bulk'; + +// Transaction Actions +import { getTransaction } from './lib/actions/transactions/get-transaction'; +import { getTransactionHex } from './lib/actions/transactions/get-transaction-hex'; +import { getTransactionMerkleblockProof } from './lib/actions/transactions/get-transaction-merkleblock-proof'; +import { getTransactionMerkleProof } from './lib/actions/transactions/get-transaction-merkle-proof'; +import { getTransactionOutspend } from './lib/actions/transactions/get-transaction-outspend'; +import { getTransactionOutspends } from './lib/actions/transactions/get-transaction-outspends'; +import { getTransactionRaw } from './lib/actions/transactions/get-transaction-raw'; +import { getTransactionRbfTimeline } from './lib/actions/transactions/get-transaction-rbf-timeline'; +import { getTransactionStatus } from './lib/actions/transactions/get-transaction-status'; +import { getTransactionTimes } from './lib/actions/transactions/get-transaction-times'; +import { postTransaction } from './lib/actions/transactions/post-transaction'; + +export const mempoolSpace = createPiece({ + displayName: 'Mempool', + description: 'The mempool.space website invented the concept of visualizing a Bitcoin node\'s mempool as projected blocks.', + logoUrl: 'https://cdn.activepieces.com/pieces/mempool-space.png', + minimumSupportedRelease: '0.20.0', + authors: ['reemayoush'], + auth: PieceAuth.None(), + actions: [ + // General Actions + getDifficultyAdjustment, + getPrice, + getHistoricalPrice, + + // Address Actions + getAddressDetails, + getAddressTransactions, + getAddressTransactionsChain, + getAddressTransactionsMempool, + getAddressUtxo, + validateAddress, + + // Fees Actions + getMempoolBlocksFees, + getRecommendedFees, + + // Block Actions + getBlock, + getBlockHeader, + getBlockHeight, + getBlockTimestamp, + getBlockRaw, + getBlockStatus, + getBlockTipHeight, + getBlockTipHash, + getBlockTransactionId, + getBlockTransactionIds, + getBlockTransactions, + getBlocksBulk, + + // Transaction Actions + getTransaction, + getTransactionHex, + getTransactionMerkleblockProof, + getTransactionMerkleProof, + getTransactionOutspend, + getTransactionOutspends, + getTransactionRaw, + getTransactionRbfTimeline, + getTransactionStatus, + getTransactionTimes, + postTransaction, + + createCustomApiCallAction({ + baseUrl: () => 'https://mempool.space/api', + auth: PieceAuth.None(), + }), + ], + triggers: [] +}); diff --git a/packages/pieces/community/mempool-space/src/lib/actions/addresses/get-address-details.ts b/packages/pieces/community/mempool-space/src/lib/actions/addresses/get-address-details.ts new file mode 100644 index 0000000..72b9397 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/addresses/get-address-details.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getAddressDetails = createAction({ + name: 'get_address_details', + displayName: 'Get Address Details', + description: 'Returns address details including chain and mempool stats', + // category: 'Addresses', + props: { + address: Property.ShortText({ + displayName: 'Address', + description: 'The Bitcoin address to look up', + required: true + }) + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/address/${propsValue.address}`, + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/addresses/get-address-transactions-chain.ts b/packages/pieces/community/mempool-space/src/lib/actions/addresses/get-address-transactions-chain.ts new file mode 100644 index 0000000..df29282 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/addresses/get-address-transactions-chain.ts @@ -0,0 +1,35 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getAddressTransactionsChain = createAction({ + name: 'get_address_transactions_chain', + displayName: 'Get Address Transactions (Chain)', + description: 'Returns confirmed transaction history (25 transactions per page)', + // category: 'Addresses', + props: { + address: Property.ShortText({ + displayName: 'Address', + description: 'The Bitcoin address to look up transactions for', + required: true + }), + last_txid: Property.ShortText({ + displayName: 'Last Transaction ID', + description: 'Optional: Last transaction ID for pagination', + required: false + }) + }, + async run({ propsValue }) { + const queryParams: Record = {}; + if (propsValue.last_txid) { + queryParams['last_txid'] = propsValue.last_txid; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/address/${propsValue.address}/txs/chain`, + queryParams + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/addresses/get-address-transactions-mempool.ts b/packages/pieces/community/mempool-space/src/lib/actions/addresses/get-address-transactions-mempool.ts new file mode 100644 index 0000000..ff5279b --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/addresses/get-address-transactions-mempool.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getAddressTransactionsMempool = createAction({ + name: 'get_address_transactions_mempool', + displayName: 'Get Address Transactions (Mempool)', + description: 'Returns unconfirmed transactions (up to 50)', + // category: 'Addresses', + props: { + address: Property.ShortText({ + displayName: 'Address', + description: 'The Bitcoin address to look up mempool transactions for', + required: true + }) + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/address/${propsValue.address}/txs/mempool`, + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/addresses/get-address-transactions.ts b/packages/pieces/community/mempool-space/src/lib/actions/addresses/get-address-transactions.ts new file mode 100644 index 0000000..b1c30e2 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/addresses/get-address-transactions.ts @@ -0,0 +1,35 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getAddressTransactions = createAction({ + name: 'get_address_transactions', + displayName: 'Get Address Transactions', + description: 'Returns transaction history (up to 50 mempool + 25 confirmed transactions)', + // category: 'Addresses', + props: { + address: Property.ShortText({ + displayName: 'Address', + description: 'The Bitcoin address to look up transactions for', + required: true + }), + after_txid: Property.ShortText({ + displayName: 'After Transaction ID', + description: 'Optional: Get transactions after this transaction ID (for pagination)', + required: false + }) + }, + async run({ propsValue }) { + const queryParams: Record = {}; + if (propsValue.after_txid) { + queryParams['after_txid'] = propsValue.after_txid; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/address/${propsValue.address}/txs`, + queryParams + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/addresses/get-address-utxo.ts b/packages/pieces/community/mempool-space/src/lib/actions/addresses/get-address-utxo.ts new file mode 100644 index 0000000..f8e10d4 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/addresses/get-address-utxo.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getAddressUtxo = createAction({ + name: 'get_address_utxo', + displayName: 'Get Address UTXO', + description: 'Returns unspent transaction outputs for an address', + // category: 'Addresses', + props: { + address: Property.ShortText({ + displayName: 'Address', + description: 'The Bitcoin address to look up UTXOs for', + required: true + }) + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/address/${propsValue.address}/utxo`, + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/addresses/validate-address.ts b/packages/pieces/community/mempool-space/src/lib/actions/addresses/validate-address.ts new file mode 100644 index 0000000..25c3ca9 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/addresses/validate-address.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const validateAddress = createAction({ + name: 'validate_address', + displayName: 'Validate Address', + description: 'Validates a Bitcoin address', + // category: 'Addresses', + props: { + address: Property.ShortText({ + displayName: 'Address', + description: 'The Bitcoin address to validate', + required: true + }) + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/v1/validate-address/${propsValue.address}`, + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-header.ts b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-header.ts new file mode 100644 index 0000000..88f0803 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-header.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getBlockHeader = createAction({ + name: 'get_block_header', + displayName: 'Get Block Header', + description: 'Returns the hex-encoded block header', + // category: 'Blocks', + props: { + hash: Property.ShortText({ + displayName: 'Block Hash', + description: 'The hash of the block to look up', + required: true + }) + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/block/${propsValue.hash}/header`, + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-height.ts b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-height.ts new file mode 100644 index 0000000..ac41fb1 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-height.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getBlockHeight = createAction({ + name: 'get_block_height', + displayName: 'Get Block Height', + description: 'Returns the hash of the block currently at specified height', + // category: 'Blocks', + props: { + height: Property.Number({ + displayName: 'Block Height', + description: 'The height of the block to look up', + required: true + }) + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/block-height/${propsValue.height}`, + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-raw.ts b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-raw.ts new file mode 100644 index 0000000..cfc3010 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-raw.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getBlockRaw = createAction({ + name: 'get_block_raw', + displayName: 'Get Block Raw', + description: 'Returns the raw block representation in binary', + // category: 'Blocks', + props: { + hash: Property.ShortText({ + displayName: 'Block Hash', + description: 'The hash of the block to look up', + required: true + }) + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/block/${propsValue.hash}/raw`, + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-status.ts b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-status.ts new file mode 100644 index 0000000..5820d87 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-status.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getBlockStatus = createAction({ + name: 'get_block_status', + displayName: 'Get Block Status', + description: 'Returns the confirmation status of a block', + // category: 'Blocks', + props: { + hash: Property.ShortText({ + displayName: 'Block Hash', + description: 'The hash of the block to check status', + required: true + }) + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/block/${propsValue.hash}/status`, + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-timestamp.ts b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-timestamp.ts new file mode 100644 index 0000000..7a26a4f --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-timestamp.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getBlockTimestamp = createAction({ + name: 'get_block_timestamp', + displayName: 'Get Block Timestamp', + description: 'Returns the height and hash of the block closest to the given timestamp', + // category: 'Blocks', + props: { + timestamp: Property.Number({ + displayName: 'Timestamp', + description: 'Unix timestamp to find the closest block to', + required: true + }) + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/v1/mining/blocks/timestamp/${propsValue.timestamp}`, + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-tip-hash.ts b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-tip-hash.ts new file mode 100644 index 0000000..e657043 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-tip-hash.ts @@ -0,0 +1,18 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getBlockTipHash = createAction({ + name: 'get_block_tip_hash', + displayName: 'Get Block Tip Hash', + description: 'Returns the hash of the last block', + // category: 'Blocks', + props: {}, + async run() { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/blocks/tip/hash`, + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-tip-height.ts b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-tip-height.ts new file mode 100644 index 0000000..2f45dcc --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-tip-height.ts @@ -0,0 +1,18 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getBlockTipHeight = createAction({ + name: 'get_block_tip_height', + displayName: 'Get Block Tip Height', + description: 'Returns the height of the last block', + // category: 'Blocks', + props: {}, + async run() { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/blocks/tip/height`, + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-transaction-id.ts b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-transaction-id.ts new file mode 100644 index 0000000..26d1456 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-transaction-id.ts @@ -0,0 +1,29 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getBlockTransactionId = createAction({ + name: 'get_block_transaction_id', + displayName: 'Get Block Transaction ID', + description: 'Returns the transaction at specified index within the block', + // category: 'Blocks', + props: { + hash: Property.ShortText({ + displayName: 'Block Hash', + description: 'The hash of the block', + required: true + }), + index: Property.Number({ + displayName: 'Transaction Index', + description: 'The index of the transaction within the block', + required: true + }) + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/block/${propsValue.hash}/txid/${propsValue.index}`, + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-transaction-ids.ts b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-transaction-ids.ts new file mode 100644 index 0000000..4408e8d --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-transaction-ids.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getBlockTransactionIds = createAction({ + name: 'get_block_transaction_ids', + displayName: 'Get Block Transaction IDs', + description: 'Returns a list of all transaction IDs in the block', + // category: 'Blocks', + props: { + hash: Property.ShortText({ + displayName: 'Block Hash', + description: 'The hash of the block', + required: true + }) + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/block/${propsValue.hash}/txids`, + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-transactions.ts b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-transactions.ts new file mode 100644 index 0000000..11de7c5 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block-transactions.ts @@ -0,0 +1,34 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getBlockTransactions = createAction({ + name: 'get_block_transactions', + displayName: 'Get Block Transactions', + description: 'Returns a list of transactions in the block (up to 25 transactions)', + // category: 'Blocks', + props: { + hash: Property.ShortText({ + displayName: 'Block Hash', + description: 'The hash of the block', + required: true + }), + startIndex: Property.Number({ + displayName: 'Start Index', + description: 'Optional: Starting index for pagination', + required: false + }) + }, + async run({ propsValue }) { + let url = `${MEMPOOL_API_BASE_URL}/api/block/${propsValue.hash}/txs`; + if (propsValue.startIndex !== undefined) { + url += `/${propsValue.startIndex}`; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: url, + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block.ts b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block.ts new file mode 100644 index 0000000..445e0a2 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-block.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getBlock = createAction({ + name: 'get_block', + displayName: 'Get Block', + description: 'Returns detailed block information', + // category: 'Blocks', + props: { + hash: Property.ShortText({ + displayName: 'Block Hash', + description: 'The hash of the block to look up', + required: true + }) + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/block/${propsValue.hash}`, + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-blocks-bulk.ts b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-blocks-bulk.ts new file mode 100644 index 0000000..42bb15f --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/blocks/get-blocks-bulk.ts @@ -0,0 +1,33 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getBlocksBulk = createAction({ + name: 'get_blocks_bulk', + displayName: 'Get Blocks (Bulk)', + description: 'Returns details on the range of blocks between minHeight and maxHeight, inclusive, up to 10 blocks', + // category: 'Blocks', + props: { + minHeight: Property.Number({ + displayName: 'Minimum Height', + description: 'The starting block height', + required: true + }), + maxHeight: Property.Number({ + displayName: 'Maximum Height', + description: 'Optional: The ending block height. If not specified, defaults to the current tip', + required: false + }) + }, + async run({ propsValue }) { + const url = propsValue.maxHeight !== undefined ? + `${MEMPOOL_API_BASE_URL}/api/v1/blocks-bulk/${propsValue.minHeight}/${propsValue.maxHeight}` : + `${MEMPOOL_API_BASE_URL}/api/v1/blocks-bulk/${propsValue.minHeight}`; + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: url, + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/fees/get-mempool-blocks-fees.ts b/packages/pieces/community/mempool-space/src/lib/actions/fees/get-mempool-blocks-fees.ts new file mode 100644 index 0000000..088ebfa --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/fees/get-mempool-blocks-fees.ts @@ -0,0 +1,18 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getMempoolBlocksFees = createAction({ + name: 'get_mempool_blocks_fees', + displayName: 'Get Mempool Blocks Fees', + description: 'Returns current mempool as projected blocks with fee rates and sizes', + // category: 'Fees', + props: {}, + async run() { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/v1/fees/mempool-blocks`, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/mempool-space/src/lib/actions/fees/get-recommended-fees.ts b/packages/pieces/community/mempool-space/src/lib/actions/fees/get-recommended-fees.ts new file mode 100644 index 0000000..aafcedf --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/fees/get-recommended-fees.ts @@ -0,0 +1,18 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getRecommendedFees = createAction({ + name: 'get_recommended_fees', + displayName: 'Get Recommended Fees', + description: 'Returns recommended fee rates for different transaction confirmation targets', + // category: 'Fees', + props: {}, + async run() { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/v1/fees/recommended`, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/mempool-space/src/lib/actions/general/get-difficulty-adjustment.ts b/packages/pieces/community/mempool-space/src/lib/actions/general/get-difficulty-adjustment.ts new file mode 100644 index 0000000..c976456 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/general/get-difficulty-adjustment.ts @@ -0,0 +1,19 @@ +import { createAction, PieceAuth } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getDifficultyAdjustment = createAction({ + name: 'get_difficulty_adjustment', + displayName: 'Get Difficulty Adjustment', + description: 'Returns details about Bitcoin difficulty adjustment', + // category: 'General', + props: {}, + async run() { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/v1/difficulty-adjustment`, + }); + + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/general/get-historical-price.ts b/packages/pieces/community/mempool-space/src/lib/actions/general/get-historical-price.ts new file mode 100644 index 0000000..c685f4a --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/general/get-historical-price.ts @@ -0,0 +1,50 @@ +import { createAction, PieceAuth, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getHistoricalPrice = createAction({ + name: 'get_historical_price', + displayName: 'Get Historical Price', + description: 'Returns bitcoin historical price in main currencies', + // category: 'General', + props: { + currency: Property.StaticDropdown({ + displayName: 'Currency', + description: 'Select the currency for historical price', + required: true, + options: { + options: [ + { label: 'US Dollar', value: 'USD' }, + { label: 'Euro', value: 'EUR' }, + { label: 'British Pound', value: 'GBP' }, + { label: 'Canadian Dollar', value: 'CAD' }, + { label: 'Swiss Franc', value: 'CHF' }, + { label: 'Australian Dollar', value: 'AUD' }, + { label: 'Japanese Yen', value: 'JPY' } + ] + } + }), + dateTime: Property.DateTime({ + displayName: 'Price lookup date', + description: 'Provide YYYY-MM-DD format.', + required: true + }) + }, + async run({ propsValue }) { + const queryParams: Record = {}; + if (propsValue.currency) { + queryParams['currency'] = propsValue.currency; + } + if (propsValue.dateTime) { + const date = new Date(propsValue.dateTime); + queryParams['timestamp'] = date.getTime().toString(); + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/v1/historical-price`, + queryParams + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/general/get-price.ts b/packages/pieces/community/mempool-space/src/lib/actions/general/get-price.ts new file mode 100644 index 0000000..730e9a7 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/general/get-price.ts @@ -0,0 +1,18 @@ +import { createAction, PieceAuth } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getPrice = createAction({ + name: 'get_price', + displayName: 'Get Price', + description: 'Returns bitcoin latest price in main currencies', + // category: 'General', + props: {}, + async run() { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/v1/prices`, + }); + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-hex.ts b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-hex.ts new file mode 100644 index 0000000..65a552d --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-hex.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getTransactionHex = createAction({ + name: 'get_transaction_hex', + displayName: 'Get Transaction Hex', + description: 'Get the raw transaction in hex format', + // category: 'Transactions', + props: { + txid: Property.ShortText({ + displayName: 'Transaction ID', + description: 'The transaction ID to get hex data for', + required: true, + }), + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/tx/${propsValue.txid}/hex`, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-merkle-proof.ts b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-merkle-proof.ts new file mode 100644 index 0000000..b42bdbe --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-merkle-proof.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getTransactionMerkleProof = createAction({ + name: 'get_transaction_merkle_proof', + displayName: 'Get Transaction Merkle Proof', + description: 'Get the merkle proof for a transaction', + // category: 'Transactions', + props: { + txid: Property.ShortText({ + displayName: 'Transaction ID', + description: 'The transaction ID to get merkle proof for', + required: true, + }), + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/tx/${propsValue.txid}/merkle-proof`, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-merkleblock-proof.ts b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-merkleblock-proof.ts new file mode 100644 index 0000000..7f55273 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-merkleblock-proof.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getTransactionMerkleblockProof = createAction({ + name: 'get_transaction_merkleblock_proof', + displayName: 'Get Transaction Merkleblock Proof', + description: 'Get the merkle block proof for a transaction', + // category: 'Transactions', + props: { + txid: Property.ShortText({ + displayName: 'Transaction ID', + description: 'The transaction ID to get merkle block proof for', + required: true, + }), + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/tx/${propsValue.txid}/merkleblock-proof`, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-outspend.ts b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-outspend.ts new file mode 100644 index 0000000..e83e0ac --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-outspend.ts @@ -0,0 +1,29 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getTransactionOutspend = createAction({ + name: 'get_transaction_outspend', + displayName: 'Get Transaction Output Spend Status', + description: 'Get the spending status of a specific transaction output', + // category: 'Transactions', + props: { + txid: Property.ShortText({ + displayName: 'Transaction ID', + description: 'The transaction ID to check output spending status', + required: true, + }), + vout: Property.Number({ + displayName: 'Output Index', + description: 'The index of the output to check (0-based)', + required: true, + }), + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/tx/${propsValue.txid}/outspend/${propsValue.vout}`, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-outspends.ts b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-outspends.ts new file mode 100644 index 0000000..ca621b5 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-outspends.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getTransactionOutspends = createAction({ + name: 'get_transaction_outspends', + displayName: 'Get All Transaction Output Spend Statuses', + description: 'Get the spending status of all outputs in a transaction', + // category: 'Transactions', + props: { + txid: Property.ShortText({ + displayName: 'Transaction ID', + description: 'The transaction ID to check all output spending statuses', + required: true, + }), + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/tx/${propsValue.txid}/outspends`, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-raw.ts b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-raw.ts new file mode 100644 index 0000000..691c6b3 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-raw.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getTransactionRaw = createAction({ + name: 'get_transaction_raw', + displayName: 'Get Raw Transaction', + description: 'Get the raw transaction in hex format', + // category: 'Transactions', + props: { + txid: Property.ShortText({ + displayName: 'Transaction ID', + description: 'The transaction ID to get raw data for', + required: true, + }), + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/tx/${propsValue.txid}/raw`, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-rbf-timeline.ts b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-rbf-timeline.ts new file mode 100644 index 0000000..efdd88d --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-rbf-timeline.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getTransactionRbfTimeline = createAction({ + name: 'get_transaction_rbf_timeline', + displayName: 'Get Transaction RBF Timeline', + description: 'Get the Replace-By-Fee (RBF) timeline for a transaction', + // category: 'Transactions', + props: { + txid: Property.ShortText({ + displayName: 'Transaction ID', + description: 'The transaction ID to get RBF timeline for', + required: true, + }), + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/tx/${propsValue.txid}/rbf-timeline`, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-status.ts b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-status.ts new file mode 100644 index 0000000..f561e0a --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-status.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getTransactionStatus = createAction({ + name: 'get_transaction_status', + displayName: 'Get Transaction Status', + description: 'Get the confirmation status of a transaction', + // category: 'Transactions', + props: { + txid: Property.ShortText({ + displayName: 'Transaction ID', + description: 'The transaction ID to check status for', + required: true, + }), + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/tx/${propsValue.txid}/status`, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-times.ts b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-times.ts new file mode 100644 index 0000000..ba0add9 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction-times.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getTransactionTimes = createAction({ + name: 'get_transaction_times', + displayName: 'Get Transaction Times', + description: 'Get timing information for a transaction including first seen and block entry times', + // category: 'Transactions', + props: { + txid: Property.ShortText({ + displayName: 'Transaction ID', + description: 'The transaction ID to get timing information for', + required: true, + }), + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/tx/${propsValue.txid}/times`, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction.ts b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction.ts new file mode 100644 index 0000000..21e2140 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/transactions/get-transaction.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const getTransaction = createAction({ + name: 'get_transaction', + displayName: 'Get Transaction', + description: 'Retrieve transaction details by transaction ID', + // category: 'Transactions', + props: { + txid: Property.ShortText({ + displayName: 'Transaction ID', + description: 'The transaction ID to retrieve', + required: true, + }), + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${MEMPOOL_API_BASE_URL}/api/tx/${propsValue.txid}`, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/mempool-space/src/lib/actions/transactions/post-transaction.ts b/packages/pieces/community/mempool-space/src/lib/actions/transactions/post-transaction.ts new file mode 100644 index 0000000..6e8e7fc --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/actions/transactions/post-transaction.ts @@ -0,0 +1,25 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { MEMPOOL_API_BASE_URL } from '../../common'; + +export const postTransaction = createAction({ + name: 'post_transaction', + displayName: 'Post Transaction', + description: 'Submit a raw transaction to the network', + // category: 'Transactions', + props: { + rawTx: Property.LongText({ + displayName: 'Raw Transaction', + description: 'The raw transaction in hex format to broadcast', + required: true, + }), + }, + async run({ propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${MEMPOOL_API_BASE_URL}/api/tx`, + body: propsValue.rawTx, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/mempool-space/src/lib/common/index.ts b/packages/pieces/community/mempool-space/src/lib/common/index.ts new file mode 100644 index 0000000..7858bb6 --- /dev/null +++ b/packages/pieces/community/mempool-space/src/lib/common/index.ts @@ -0,0 +1 @@ +export const MEMPOOL_API_BASE_URL = 'https://mempool.space'; diff --git a/packages/pieces/community/mempool-space/tsconfig.json b/packages/pieces/community/mempool-space/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/mempool-space/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/mempool-space/tsconfig.lib.json b/packages/pieces/community/mempool-space/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/mempool-space/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/messagebird/.eslintrc.json b/packages/pieces/community/messagebird/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/messagebird/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/messagebird/README.md b/packages/pieces/community/messagebird/README.md new file mode 100644 index 0000000..805bcfc --- /dev/null +++ b/packages/pieces/community/messagebird/README.md @@ -0,0 +1,7 @@ +# pieces-messagebird + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-messagebird` to build the library. diff --git a/packages/pieces/community/messagebird/package.json b/packages/pieces/community/messagebird/package.json new file mode 100644 index 0000000..4c192e5 --- /dev/null +++ b/packages/pieces/community/messagebird/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-messagebird", + "version": "0.1.1" +} diff --git a/packages/pieces/community/messagebird/project.json b/packages/pieces/community/messagebird/project.json new file mode 100644 index 0000000..6aa74c1 --- /dev/null +++ b/packages/pieces/community/messagebird/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-messagebird", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/messagebird/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/messagebird", + "tsConfig": "packages/pieces/community/messagebird/tsconfig.lib.json", + "packageJson": "packages/pieces/community/messagebird/package.json", + "main": "packages/pieces/community/messagebird/src/index.ts", + "assets": [ + "packages/pieces/community/messagebird/*.md", + { + "input": "packages/pieces/community/messagebird/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-messagebird {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/messagebird/src/index.ts b/packages/pieces/community/messagebird/src/index.ts new file mode 100644 index 0000000..ee0391b --- /dev/null +++ b/packages/pieces/community/messagebird/src/index.ts @@ -0,0 +1,16 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { sendSMSAction } from './lib/actions/send-sms.action'; +import { PieceCategory } from '@activepieces/shared'; +import { birdAuth } from './lib/auth'; + +export const messagebird = createPiece({ + displayName: 'Bird', + description: 'Unified CRM for Marketing, Service & Payments', + auth: birdAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/messagebird.png', + categories: [PieceCategory.MARKETING, PieceCategory.COMMUNICATION], + authors: ['kishanprmr', 'geekyme'], + actions: [sendSMSAction], + triggers: [], +}); diff --git a/packages/pieces/community/messagebird/src/lib/actions/send-sms.action.ts b/packages/pieces/community/messagebird/src/lib/actions/send-sms.action.ts new file mode 100644 index 0000000..6ecf113 --- /dev/null +++ b/packages/pieces/community/messagebird/src/lib/actions/send-sms.action.ts @@ -0,0 +1,69 @@ +import { birdAuth } from '../auth'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; + +export const sendSMSAction = createAction({ + auth: birdAuth, + name: 'send-sms', + displayName: 'Send SMS', + description: 'Sends an SMS message via Bird Channels API.', + props: { + recipient: Property.ShortText({ + displayName: 'Recipient', + description: 'The phone number to send the message to (with country code)', + required: true, + }), + message: Property.LongText({ + displayName: 'Message', + description: 'The body of the SMS message', + required: true, + }), + reference: Property.ShortText({ + displayName: 'Reference', + description: 'Your own identifier for the message (optional)', + required: false, + }), + scheduledFor: Property.DateTime({ + displayName: 'Scheduled For (UTC timestamp)', + description: 'Message to be sent at a specific datetime eg. 2025-04-27T15:08:18.613Z. If not set, the message will be sent immediately.', + required: false, + }), + }, + async run(context) { + const { recipient, message, reference, scheduledFor } = context.propsValue; + const auth = context.auth as { apiKey: string; workspaceId: string; channelId: string }; + + // Format request for Bird Channels API + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://api.bird.com/workspaces/${auth.workspaceId}/channels/${auth.channelId}/messages`, + headers: { + 'Authorization': `Bearer ${auth.apiKey}`, + 'Content-Type': 'application/json' + }, + body: { + receiver: { + contacts: [ + { + identifierValue: recipient + } + ] + }, + body: { + type: 'text', + text: { + text: message + } + }, + ...(reference && { reference }), + ...(scheduledFor && { scheduledFor }), + }, + }; + + return await httpClient.sendRequest(request); + }, +}); diff --git a/packages/pieces/community/messagebird/src/lib/auth.ts b/packages/pieces/community/messagebird/src/lib/auth.ts new file mode 100644 index 0000000..e48addc --- /dev/null +++ b/packages/pieces/community/messagebird/src/lib/auth.ts @@ -0,0 +1,28 @@ +import { PieceAuth } from '@activepieces/pieces-framework'; + +export interface BirdAuthValue { + apiKey: string; + workspaceId: string; + channelId: string; +} + +export const birdAuth = PieceAuth.CustomAuth({ + props: { + apiKey: PieceAuth.SecretText({ + displayName: 'API Key', + description: 'Bird API Access Key from Settings > Security > Access Keys', + required: true, + }), + workspaceId: PieceAuth.SecretText({ + displayName: 'Workspace ID', + description: 'Bird Workspace ID found in your workspace URL', + required: true, + }), + channelId: PieceAuth.SecretText({ + displayName: 'Channel ID', + description: 'Your SMS channel ID from Bird dashboard', + required: true, + }), + }, + required: true, +}); \ No newline at end of file diff --git a/packages/pieces/community/messagebird/tsconfig.json b/packages/pieces/community/messagebird/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/messagebird/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/messagebird/tsconfig.lib.json b/packages/pieces/community/messagebird/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/messagebird/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/metabase/.eslintrc.json b/packages/pieces/community/metabase/.eslintrc.json new file mode 100644 index 0000000..d7449bb --- /dev/null +++ b/packages/pieces/community/metabase/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {}, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/metabase/README.md b/packages/pieces/community/metabase/README.md new file mode 100644 index 0000000..035eb43 --- /dev/null +++ b/packages/pieces/community/metabase/README.md @@ -0,0 +1,7 @@ +# pieces-metabase + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-metabase` to build the library. diff --git a/packages/pieces/community/metabase/package-lock.json b/packages/pieces/community/metabase/package-lock.json new file mode 100644 index 0000000..3fbf41a --- /dev/null +++ b/packages/pieces/community/metabase/package-lock.json @@ -0,0 +1,12 @@ +{ + "name": "@activepieces/piece-metabase", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-metabase", + "version": "0.0.1" + } + } +} diff --git a/packages/pieces/community/metabase/package.json b/packages/pieces/community/metabase/package.json new file mode 100644 index 0000000..1dca111 --- /dev/null +++ b/packages/pieces/community/metabase/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-metabase", + "version": "0.1.11" +} diff --git a/packages/pieces/community/metabase/project.json b/packages/pieces/community/metabase/project.json new file mode 100644 index 0000000..e65ff1a --- /dev/null +++ b/packages/pieces/community/metabase/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-metabase", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/metabase/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/metabase", + "tsConfig": "packages/pieces/community/metabase/tsconfig.lib.json", + "packageJson": "packages/pieces/community/metabase/package.json", + "main": "packages/pieces/community/metabase/src/index.ts", + "assets": [ + "packages/pieces/community/metabase/*.md", + { + "input": "packages/pieces/community/metabase/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-metabase {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/metabase/src/index.ts b/packages/pieces/community/metabase/src/index.ts new file mode 100644 index 0000000..8ab39a0 --- /dev/null +++ b/packages/pieces/community/metabase/src/index.ts @@ -0,0 +1,79 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { getQuestion } from './lib/actions/get-question'; +import { getQuestionPngPreview } from './lib/actions/get-png-rendering'; +import { getDashboardQuestions } from './lib/actions/get-dashboard'; +import { queryMetabaseApi } from './lib/common'; +import { HttpMethod, is_chromium_installed } from '@activepieces/pieces-common'; +import { getGraphQuestion } from './lib/actions/get-graph-question'; + +const baseProps = { + baseUrl: Property.ShortText({ + displayName: 'Metabase API base URL', + required: true, + }), + apiKey: PieceAuth.SecretText({ + displayName: 'API key', + description: + 'Generate one on your Metabase instance (settings -> authentication -> API keys)', + required: true, + }), +}; + +const authProps = is_chromium_installed() + ? { + ...baseProps, + embeddingKey: Property.ShortText({ + displayName: 'Embedding key', + description: + 'Needed if you want to generate a graph of a question (settings -> embedding -> static embedding).', + required: false, + }), + } + : baseProps; + +export const metabaseAuth = PieceAuth.CustomAuth({ + description: 'Metabase authentication requires a baseUrl and a password.', + required: true, + props: authProps, + + validate: async ({ auth }) => { + try { + await queryMetabaseApi( + { + endpoint: 'login-history/current', + method: HttpMethod.GET, + }, + auth + ); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API Key or base URL', + }; + } + }, +}); + +export const metabase = createPiece({ + displayName: 'Metabase', + description: 'The simplest way to ask questions and learn from data', + + auth: metabaseAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/metabase.png', + authors: ['AdamSelene', 'abuaboud', 'valentin-mourtialon', 'Kevinyu-alan'], + actions: [ + getQuestion, + getQuestionPngPreview, + getDashboardQuestions, + ...(is_chromium_installed() ? [getGraphQuestion] : []), + ], + triggers: [], +}); diff --git a/packages/pieces/community/metabase/src/lib/actions/get-dashboard.ts b/packages/pieces/community/metabase/src/lib/actions/get-dashboard.ts new file mode 100644 index 0000000..e8375c2 --- /dev/null +++ b/packages/pieces/community/metabase/src/lib/actions/get-dashboard.ts @@ -0,0 +1,156 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { queryMetabaseApi } from '../common'; +import { metabaseAuth } from '../..'; +import { HttpMethod } from '@activepieces/pieces-common'; + +interface MetabaseParam { + id: string; + name?: string; + slug?: string; + type?: string; + values_source_config?: { + values?: unknown[]; + }; +} +interface CardResult { + name: string; + data?: unknown; + error?: string; +} +interface DashboardParameter { + id: string; + name: string; + slug: string; + type: string; + possible_values?: unknown[]; +} + +interface DashboardResult { + dashboard_name: string; + available_parameters: DashboardParameter[]; + cards_results: Record; +} + +export const getDashboardQuestions = createAction({ + name: 'getDashboardQuestions', + auth: metabaseAuth, + requireAuth: true, + displayName: 'Get Dashboard Questions', + description: + 'Execute all questions across all tabs in a Metabase dashboard and return their consolidated results in a single response', + props: { + dashboardId: Property.ShortText({ + displayName: 'Metabase Dashboard ID', + required: true, + }), + parameters: Property.Object({ + displayName: 'Parameters (slug name -> value)', + description: 'Dashboard parameters to apply to all questions', + required: false, + }), + }, + async run({ auth, propsValue }) { + const dashboardId = propsValue.dashboardId.split('-')[0]; + + const dashboardData = await queryMetabaseApi( + { endpoint: `dashboard/${dashboardId}`, method: HttpMethod.GET }, + auth + ); + + if (dashboardData.error) { + throw new Error(`Error fetching dashboard: ${dashboardData.error}`); + } + + const dashboardParameters = dashboardData.parameters || []; + + const result: DashboardResult = { + dashboard_name: dashboardData.name || 'Unknown Dashboard', + available_parameters: dashboardParameters.map((param: MetabaseParam) => ({ + id: param.id, + name: param.name || 'Unknown', + slug: param.slug || '', + type: param.type || '', + possible_values: param.values_source_config?.values || [], + })), + cards_results: {}, + }; + + // Execute each card and collect results + for (const dashcard of dashboardData.dashcards || []) { + const cardId = dashcard.card_id; + const dashcardId = dashcard.id; + + if (!cardId) continue; + + let cardName = 'Unknown'; + if (dashcard.card && typeof dashcard.card === 'object') { + cardName = dashcard.card.name || `Card ${cardId}`; + } + + const cardParameters = []; + + // Map dashboard parameters to card parameters using parameter_mappings + if (propsValue.parameters && dashcard.parameter_mappings) { + for (const mapping of dashcard.parameter_mappings) { + const paramId = mapping.parameter_id; + + // Find corresponding dashboard parameter + const dashParam = dashboardParameters.find( + (p: { id: string }) => p.id === paramId + ); + + if ( + dashParam && + dashParam.slug && + propsValue.parameters[dashParam.slug] !== undefined + ) { + cardParameters.push({ + id: paramId, + target: mapping.target, + type: dashParam.type, + value: propsValue.parameters[dashParam.slug], + }); + } + } + } + + try { + const cardResult = await queryMetabaseApi( + { + endpoint: `dashboard/${dashboardId}/dashcard/${dashcardId}/card/${cardId}/query/json`, + method: HttpMethod.POST, + body: { + parameters: cardParameters, + }, + }, + auth + ); + + const cardIdStr = String(cardId); + result.cards_results[cardIdStr] = { + name: cardName, + data: cardResult, + }; + } catch (error: unknown) { + let errorMessage = 'Unknown error'; + + if ( + error && + typeof error === 'object' && + 'message' in error && + typeof error.message === 'string' + ) { + errorMessage = error.message; + } + + const cardIdStr = String(cardId); + result.cards_results[cardIdStr] = { + name: cardName, + error: `Failed to execute: ${errorMessage}`, + }; + } + } + + return result; + }, +}); diff --git a/packages/pieces/community/metabase/src/lib/actions/get-graph-question.ts b/packages/pieces/community/metabase/src/lib/actions/get-graph-question.ts new file mode 100644 index 0000000..ef00d69 --- /dev/null +++ b/packages/pieces/community/metabase/src/lib/actions/get-graph-question.ts @@ -0,0 +1,132 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { metabaseAuth } from '../..'; +import jwt from 'jsonwebtoken'; +import { chromium } from 'playwright'; + +export const getGraphQuestion = createAction({ + name: 'getGraphQuestion', + auth: metabaseAuth, + requireAuth: true, + displayName: 'Get the graph of the question', + description: 'Get the graph of a Metabase question and save it as a png', + props: { + questionId: Property.ShortText({ + displayName: 'Metabase question ID', + required: true, + }), + parameters: Property.Object({ + displayName: 'Parameters (slug name -> value)', + required: false, + }), + graphName: Property.ShortText({ + displayName: 'The name of the graph (without the extension)', + required: false, + }), + waitTime: Property.Number({ + displayName: 'Wait Time (seconds)', + description: + 'How long to wait for the graph to render completely in seconds', + required: true, + defaultValue: 5, + }), + }, + async run({ auth, propsValue, files }) { + if ('embeddingKey' in auth && !auth.embeddingKey) + return 'An embedding key is needed.'; + + if (propsValue.waitTime <= 0) + return 'The wait time needs to be superior to 0'; + + const questionId = propsValue.questionId.split('-')[0]; + const numericQuestionId = parseInt(questionId); + + const payload = { + resource: { question: numericQuestionId }, + params: propsValue.parameters, + exp: Math.round(Date.now() / 1000) + 10 * 60, + }; + + // @ts-expect-error we expect an embedding key if the user can use this action. + const token = jwt.sign(payload, auth.embeddingKey); + const graphName = propsValue.graphName + ? propsValue.graphName + '.png' + : `metabase_question_${questionId}.png`; + + const iframeUrl = + auth.baseUrl + '/embed/question/' + token + '#bordered=true&titled=true'; + + const browser = await chromium.launch({ + headless: true, + chromiumSandbox: false, + executablePath: '/usr/bin/chromium', + }); + + try { + const context = await browser.newContext({ + viewport: { + width: 1600, + height: 1200, + }, + deviceScaleFactor: 2, + }); + + const page = await context.newPage(); + + const response = await page.goto(iframeUrl, { + waitUntil: 'networkidle', + timeout: 30000, + }); + + if (!response || !response.ok()) { + throw new Error( + `Page load failed with status: ${response ? response.status() : 400}` + ); + } + + // we wait so the graph can load + await page.waitForTimeout(propsValue.waitTime * 1000); + + const mainElement = await page.$('main'); + let screenshotBuffer; + if (mainElement) { + screenshotBuffer = await mainElement.screenshot({ + path: graphName, + type: 'png', + }); + } else { + screenshotBuffer = await page.screenshot({ + path: graphName, + type: 'png', + clip: { + x: 0, + y: 0, + width: 1600, + height: 1120, // so it doesn't screenshot the metabase banner + }, + }); + } + + const fileUrl = await files.write({ + fileName: graphName, + data: screenshotBuffer, + }); + + return { + file: { + filename: graphName, + base64Content: screenshotBuffer.toString('base64'), + download: fileUrl, + }, + iframeUrl, + }; + } catch (error) { + console.error( + 'Please verify that either your embedding key and question id are valid or that the question is embedded and published.' + ); + console.error('Error capturing Metabase chart:', error); + throw error; + } finally { + await browser.close(); + } + }, +}); diff --git a/packages/pieces/community/metabase/src/lib/actions/get-png-rendering.ts b/packages/pieces/community/metabase/src/lib/actions/get-png-rendering.ts new file mode 100644 index 0000000..7920f54 --- /dev/null +++ b/packages/pieces/community/metabase/src/lib/actions/get-png-rendering.ts @@ -0,0 +1,48 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { metabaseAuth } from '../..'; +import { queryMetabaseApi } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const getQuestionPngPreview = createAction({ + name: 'getQuestionPngPreview', + auth: metabaseAuth, + requireAuth: true, + displayName: 'Get Question PNG Preview', + description: + 'Get PNG preview rendering (low resolution) of a Metabase card/question.', + props: { + questionId: Property.ShortText({ + displayName: 'Metabase question ID', + required: true, + }), + }, + async run({ auth, propsValue, files }) { + const questionId = propsValue.questionId.split('-')[0]; + + const response = await queryMetabaseApi( + { + endpoint: `pulse/preview_card_png/${questionId}`, + method: HttpMethod.GET, + headers: { + Accept: 'image/png', + }, + responseType: 'arraybuffer', + }, + auth + ); + + if (response.error) { + throw new Error(response.error); + } + + const fileUrl = await files.write({ + fileName: `metabase_question_${questionId}.png`, + data: Buffer.from(response, 'base64'), + }); + + return { + fileName: `metabase_question_${questionId}.png`, + file: fileUrl, + }; + }, +}); diff --git a/packages/pieces/community/metabase/src/lib/actions/get-question.ts b/packages/pieces/community/metabase/src/lib/actions/get-question.ts new file mode 100644 index 0000000..2fee970 --- /dev/null +++ b/packages/pieces/community/metabase/src/lib/actions/get-question.ts @@ -0,0 +1,69 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { queryMetabaseApi } from '../common'; +import { metabaseAuth } from '../..'; +import { HttpMethod } from '@activepieces/pieces-common'; + +interface MetabaseParam { + id: string; + target: unknown[]; + type: string[]; + slug: string; +} + +export const getQuestion = createAction({ + name: 'getQuestion', + auth: metabaseAuth, + requireAuth: true, + displayName: 'Get question', + description: 'Fetch the results of a Metabase question', + props: { + questionId: Property.ShortText({ + displayName: 'Metabase question ID', + required: true, + }), + parameters: Property.Object({ + displayName: 'Parameters (slug name -> value)', + required: false, + }), + }, + async run({ auth, propsValue }) { + const questionId = propsValue.questionId.split('-')[0]; + const card = await queryMetabaseApi( + { endpoint: `card/${questionId}`, method: HttpMethod.GET }, + auth + ); + const parameters = card['parameters'] as MetabaseParam[]; + + const response = await queryMetabaseApi( + { + endpoint: `card/${questionId}/query`, + method: HttpMethod.POST, + body: { + collection_preview: false, + ignore_cache: false, + parameters: parameters + .filter( + (param) => + propsValue.parameters && + propsValue.parameters[param.slug] !== undefined + ) + .map((param) => { + return { + id: param.id, + target: param.target, + type: param.type, + value: + propsValue.parameters && propsValue.parameters[param.slug], + }; + }), + }, + }, + auth + ); + if (response.error) { + throw new Error(response.error); + } else { + return response; + } + }, +}); diff --git a/packages/pieces/community/metabase/src/lib/common.ts b/packages/pieces/community/metabase/src/lib/common.ts new file mode 100644 index 0000000..f819711 --- /dev/null +++ b/packages/pieces/community/metabase/src/lib/common.ts @@ -0,0 +1,38 @@ +import { + httpClient, + HttpHeaders, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; +import { + CustomAuthProps, + StaticPropsValue, +} from '@activepieces/pieces-framework'; + +export async function queryMetabaseApi( + params: { + endpoint: string; + method: HttpMethod; + queryParams?: QueryParams; + headers?: HttpHeaders; + body?: object; + responseType?: 'arraybuffer' | 'json' | 'blob' | 'text'; + }, + auth: StaticPropsValue +) { + const request: HttpRequest = { + method: params.method, + url: `${auth.baseUrl}/api/${params.endpoint}`, + queryParams: params.queryParams, + headers: { + ...params.headers, + 'Content-Type': 'application/json', + 'X-API-KEY': auth.apiKey as string, + }, + body: JSON.stringify(params.body), + responseType: params.responseType, + }; + const response = await httpClient.sendRequest(request); + return response.body; +} diff --git a/packages/pieces/community/metabase/tsconfig.json b/packages/pieces/community/metabase/tsconfig.json new file mode 100644 index 0000000..b7e15e6 --- /dev/null +++ b/packages/pieces/community/metabase/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true + }, + "files": [], + "include": [], + "exclude": ["node_modules"], + + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], +} diff --git a/packages/pieces/community/metabase/tsconfig.lib.json b/packages/pieces/community/metabase/tsconfig.lib.json new file mode 100644 index 0000000..1f55ea5 --- /dev/null +++ b/packages/pieces/community/metabase/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts", "node_modules"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/.eslintrc.json b/packages/pieces/community/microsoft-dynamics-365-business-central/.eslintrc.json new file mode 100644 index 0000000..eac708a --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/README.md b/packages/pieces/community/microsoft-dynamics-365-business-central/README.md new file mode 100644 index 0000000..d82a83a --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/README.md @@ -0,0 +1,2 @@ +# pieces-microsoft-dynamics-365-business-central + diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/package.json b/packages/pieces/community/microsoft-dynamics-365-business-central/package.json new file mode 100644 index 0000000..5972fc8 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-microsoft-dynamics-365-business-central", + "version": "0.0.6" +} diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/project.json b/packages/pieces/community/microsoft-dynamics-365-business-central/project.json new file mode 100644 index 0000000..a511843 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-microsoft-dynamics-365-business-central", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/microsoft-dynamics-365-business-central/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/microsoft-dynamics-365-business-central", + "tsConfig": "packages/pieces/community/microsoft-dynamics-365-business-central/tsconfig.lib.json", + "packageJson": "packages/pieces/community/microsoft-dynamics-365-business-central/package.json", + "main": "packages/pieces/community/microsoft-dynamics-365-business-central/src/index.ts", + "assets": [ + "packages/pieces/community/microsoft-dynamics-365-business-central/*.md", + { + "input": "packages/pieces/community/microsoft-dynamics-365-business-central/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-microsoft-dynamics-365-business-central {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/index.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/index.ts new file mode 100644 index 0000000..08b712c --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/index.ts @@ -0,0 +1,62 @@ +import { + createPiece, + OAuth2PropertyValue, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { createRecordAction } from './lib/actions/create-record.action'; +import { getRecordAction } from './lib/actions/get-record.action'; +import { updateRecordAction } from './lib/actions/update-record.action'; +import { deleteRecordAction } from './lib/actions/delete-record.action'; +import { newOrUpdatedRecordTrigger } from './lib/triggers/new-or-updated-record.trigger'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { searchRecordsAction } from './lib/actions/search-records.action'; +import { PieceCategory } from '@activepieces/shared'; + +export const businessCentralAuth = PieceAuth.OAuth2({ + props: { + environment: Property.ShortText({ + displayName: 'Environment', + description: `Name of the environment to connect to, e.g. 'Production' or 'Sandbox'. Environment names can be found in the Business Central Admin Center.`, + required: true, + defaultValue: 'Production', + }), + }, + required: true, + scope: [ + 'https://api.businesscentral.dynamics.com/user_impersonation', + 'https://api.businesscentral.dynamics.com/Financials.ReadWrite.All', + ], + authUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', + tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', +}); + +export const microsoftDynamics365BusinessCentral = createPiece({ + displayName: 'Microsoft Dynamics 365 Business Central', + auth: businessCentralAuth, + description: 'All-in-one business management solution by Microsoft.', + categories: [PieceCategory.SALES_AND_CRM], + minimumSupportedRelease: '0.27.1', + logoUrl: + 'https://cdn.activepieces.com/pieces/microsoft-dynamics-365-business-central.png', + authors: ['kishanprmr'], + actions: [ + createRecordAction, + deleteRecordAction, + getRecordAction, + updateRecordAction, + searchRecordsAction, + createCustomApiCallAction({ + auth: businessCentralAuth, + baseUrl: (auth) => { + return `https://api.businesscentral.dynamics.com/v2.0/${ + (auth as OAuth2PropertyValue).props?.['environment'] + }/api/v2.0`; + }, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [newOrUpdatedRecordTrigger], +}); diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/actions/create-record.action.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/actions/create-record.action.ts new file mode 100644 index 0000000..0f010eb --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/actions/create-record.action.ts @@ -0,0 +1,59 @@ +import { businessCentralAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { commonProps, formatRecordFields } from '../common'; +import { makeClient } from '../common/client'; +import { ACTION_ENTITY_DROPDOWN_OPTIONS } from '../common/constants'; + +export const createRecordAction = createAction({ + auth: businessCentralAuth, + name: 'create-record', + displayName: 'Create Record', + description: 'Creates a new record.', + props: { + company_id: commonProps.company_id, + record_type: Property.StaticDropdown({ + displayName: 'Record Type', + required: true, + options: { + disabled: false, + options: ACTION_ENTITY_DROPDOWN_OPTIONS, + }, + }), + record_fields: commonProps.record_fields, + }, + async run(context) { + const companyId = context.propsValue.company_id; + const recordType = context.propsValue.record_type; + const recordFields = context.propsValue.record_fields; + + const formattedRecordFields = formatRecordFields(recordFields, recordType); + + const client = makeClient(context.auth); + + let endpoint; + + switch (recordType) { + case 'itemVariants': + endpoint = `/companies(${companyId})/items(${recordFields['itemId']})/${recordType}`; + delete formattedRecordFields['itemId']; + break; + case 'salesInvoiceLines': + endpoint = `/companies(${companyId})/salesInvoices(${recordFields['salesInvoiceId']})/${recordType}`; + delete formattedRecordFields['salesInvoiceId']; + break; + case 'salesOrderLines': + endpoint = `/companies(${companyId})/salesOrders(${recordFields['salesOrderId']})/${recordType}`; + delete formattedRecordFields['salesOrderId']; + break; + case 'salesQuoteLines': + endpoint = `/companies(${companyId})/salesQuotes(${recordFields['salesQuoteId']})/${recordType}`; + delete formattedRecordFields['salesQuoteId']; + break; + default: + endpoint = `/companies(${companyId})/${recordType}`; + break; + } + + return await client.createRecord(endpoint, formattedRecordFields); + }, +}); diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/actions/delete-record.action.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/actions/delete-record.action.ts new file mode 100644 index 0000000..ffe3609 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/actions/delete-record.action.ts @@ -0,0 +1,34 @@ +import { businessCentralAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { commonProps } from '../common'; +import { makeClient } from '../common/client'; +import { ACTION_ENTITY_DROPDOWN_OPTIONS } from '../common/constants'; + +export const deleteRecordAction = createAction({ + auth: businessCentralAuth, + name: 'delete-record', + displayName: 'Delete Record', + description: 'Deletes an existing record.', + props: { + company_id: commonProps.company_id, + record_type: Property.StaticDropdown({ + displayName: 'Record Type', + required: true, + options: { + disabled: false, + options: ACTION_ENTITY_DROPDOWN_OPTIONS, + }, + }), + record_id: commonProps.record_id, + }, + async run(context) { + const companyId = context.propsValue.company_id; + const recordType = context.propsValue.record_type; + const recordId = context.propsValue.record_id; + + const client = makeClient(context.auth); + const endpoint = `/companies(${companyId})/${recordType}(${recordId})`; + + return await client.deleteRecord(endpoint); + }, +}); diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/actions/get-record.action.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/actions/get-record.action.ts new file mode 100644 index 0000000..5157375 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/actions/get-record.action.ts @@ -0,0 +1,35 @@ +import { businessCentralAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { commonProps } from '../common'; +import { makeClient } from '../common/client'; +import { ACTION_ENTITY_DROPDOWN_OPTIONS } from '../common/constants'; + +export const getRecordAction = createAction({ + auth: businessCentralAuth, + name: 'get-record', + displayName: 'Get Record', + description: `Retrieves the details of a record by it's ID.`, + props: { + company_id: commonProps.company_id, + record_type: Property.StaticDropdown({ + displayName: 'Record Type', + required: true, + options: { + disabled: false, + options: ACTION_ENTITY_DROPDOWN_OPTIONS, + }, + }), + record_id: commonProps.record_id, + }, + async run(context) { + const companyId = context.propsValue.company_id; + const recordType = context.propsValue.record_type; + const recordId = context.propsValue.record_id; + + const client = makeClient(context.auth); + + const endpoint = `/companies(${companyId})/${recordType}(${recordId})`; + + return await client.getRecord(endpoint); + }, +}); diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/actions/search-records.action.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/actions/search-records.action.ts new file mode 100644 index 0000000..04a424e --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/actions/search-records.action.ts @@ -0,0 +1,46 @@ +import { businessCentralAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { commonProps } from '../common'; +import { makeClient } from '../common/client'; +import { TRIGGER_ENTITY_DROPDOWN_OPTIONS } from '../common/constants'; + +export const searchRecordsAction = createAction({ + auth: businessCentralAuth, + name: 'search-records', + displayName: 'Search Records', + description: 'Retrieves a list of records.', + props: { + company_id: commonProps.company_id, + record_type: Property.StaticDropdown({ + displayName: 'Record Type', + required: true, + options: { + disabled: false, + options: TRIGGER_ENTITY_DROPDOWN_OPTIONS, + }, + }), + markdown: Property.MarkDown({ + value: `You can search on any and all of the property fields below. We'll return only exact matches (capitalization matters!) on all values provided.`, + }), + record_filter_fields: commonProps.record_filter_fields, + }, + async run(context) { + const companyId = context.propsValue.company_id; + const recordType = context.propsValue.record_type; + const recordFilterFields = context.propsValue.record_filter_fields; + + const filterFieldsArray = []; + + for (const key in recordFilterFields) { + if (recordFilterFields[key]) { + filterFieldsArray.push(`${key} eq '${recordFilterFields[key]}'`); + } + } + + const client = makeClient(context.auth); + + return await client.filterRecords(companyId, recordType, { + $filter: filterFieldsArray.join(' and '), + }); + }, +}); diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/actions/update-record.action.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/actions/update-record.action.ts new file mode 100644 index 0000000..2a9701b --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/actions/update-record.action.ts @@ -0,0 +1,60 @@ +import { businessCentralAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { commonProps, formatRecordFields } from '../common'; +import { makeClient } from '../common/client'; +import { ACTION_ENTITY_DROPDOWN_OPTIONS } from '../common/constants'; + +export const updateRecordAction = createAction({ + auth: businessCentralAuth, + name: 'update-record', + displayName: 'Update Record', + description: 'Updates an existing record.', + props: { + company_id: commonProps.company_id, + record_type: Property.StaticDropdown({ + displayName: 'Record Type', + required: true, + options: { + disabled: false, + options: ACTION_ENTITY_DROPDOWN_OPTIONS, + }, + }), + record_id: commonProps.record_id, + record_fields: commonProps.record_fields, + }, + async run(context) { + const companyId = context.propsValue.company_id; + const recordType = context.propsValue.record_type; + const recordId = context.propsValue.record_id; + const recordFields = context.propsValue.record_fields; + + const formattedRecordFields = formatRecordFields(recordFields, recordType); + + const client = makeClient(context.auth); + let endpoint; + + switch (recordType) { + case 'itemVariants': + endpoint = `/companies(${companyId})/items(${recordFields['itemId']})/${recordType}(${recordId})`; + delete formattedRecordFields['itemId']; + break; + case 'salesInvoiceLines': + endpoint = `/companies(${companyId})/salesInvoices(${recordFields['salesInvoiceId']})/${recordType}(${recordId})`; + delete formattedRecordFields['salesInvoiceId']; + break; + case 'salesOrderLines': + endpoint = `/companies(${companyId})/salesOrders(${recordFields['salesOrderId']})/${recordType}(${recordId})`; + delete formattedRecordFields['salesOrderId']; + break; + case 'salesQuoteLines': + endpoint = `/companies(${companyId})/salesQuotes(${recordFields['salesQuoteId']})/${recordType}(${recordId})`; + delete formattedRecordFields['salesQuoteId']; + break; + default: + endpoint = `/companies(${companyId})/${recordType}(${recordId})`; + break; + } + + return await client.updateRecord(endpoint, formattedRecordFields); + }, +}); diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/client.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/client.ts new file mode 100644 index 0000000..f6454f6 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/client.ts @@ -0,0 +1,120 @@ +import { businessCentralAuth } from '../../'; +import { + HttpMessageBody, + HttpMethod, + QueryParams, + httpClient, + HttpRequest, + AuthenticationType, + HttpHeaders, +} from '@activepieces/pieces-common'; +import { PiecePropValueSchema } from '@activepieces/pieces-framework'; + +interface ListAPIResponse { + '@odata.context': string; + value: Array; +} + +interface CompanyResponse { + id: string; + name: string; +} + +export type filterParams = Record< + string, + string | number | string[] | undefined +>; + +export class BusinessCentralAPIClient { + constructor(private environment: string, private accessToken: string) {} + + async makeRequest( + method: HttpMethod, + resourceUri: string, + query?: filterParams, + body: any | undefined = undefined + ): Promise { + const baseUrl = `https://api.businesscentral.dynamics.com/v2.0/${this.environment}/api/v2.0`; + const params: QueryParams = {}; + const headers: HttpHeaders = {}; + + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + params[key] = String(value); + } + } + } + + if (method === HttpMethod.PATCH || method === HttpMethod.DELETE) { + headers['If-Match'] = '*'; + } + + const request: HttpRequest = { + method: method, + url: baseUrl + resourceUri, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: this.accessToken, + }, + headers, + queryParams: params, + body: body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + } + + async listCompanies(): Promise> { + return await this.makeRequest(HttpMethod.GET, '/companies'); + } + + async createRecord(endpoint: string, request: Record) { + return await this.makeRequest( + HttpMethod.POST, + endpoint, + undefined, + request + ); + } + + async updateRecord(endpoint: string, request: Record) { + return await this.makeRequest( + HttpMethod.PATCH, + endpoint, + undefined, + request + ); + } + + async getRecord(endpoint: string) { + return await this.makeRequest(HttpMethod.GET, endpoint); + } + + async deleteRecord(endpoint: string) { + return await this.makeRequest(HttpMethod.DELETE, endpoint); + } + + async filterRecords( + companyId: string, + recordType: string, + params: filterParams + ): Promise>> { + return await this.makeRequest( + HttpMethod.GET, + `/companies(${companyId})/${recordType}`, + params + ); + } +} + +export function makeClient( + auth: PiecePropValueSchema +) { + const client = new BusinessCentralAPIClient( + auth.props?.['environment'], + auth.access_token + ); + return client; +} diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/constants.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/constants.ts new file mode 100644 index 0000000..a3da793 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/constants.ts @@ -0,0 +1,98 @@ +export const ACTION_ENTITY_DROPDOWN_OPTIONS = [ + { + label: 'Bank Accounts', + value: 'bankAccounts', + }, + { + label: 'Contacts', + value: 'contacts', + }, + { + label: 'Currencies', + value: 'currencies', + }, + { + label: 'Customers', + value: 'customers', + }, + { + label: 'Dispute Status', + value: 'disputeStatus', + }, + { + label: 'Employees', + value: 'employees', + }, + { + label: 'Item Categories', + value: 'itemCategories', + }, + { + label: 'Items', + value: 'items', + }, + { + label: 'Item Variants', + value: 'itemVariants', + }, + { + label: 'Journals', + value: 'journals', + }, + { + label: 'Locations', + value: 'locations', + }, + { + label: 'Payment Methods', + value: 'paymentMethods', + }, + { + label: 'Payment Terms', + value: 'paymentTerms', + }, + { + label: 'Projects', + value: 'projects', + }, + { + label: 'Sales Invoice Lines', + value: 'salesInvoiceLines', + }, + { + label: 'Sales Invoices', + value: 'salesInvoices', + }, + { + label: 'Sales Order Lines', + value: 'salesOrderLines', + }, + { + label: 'Sales Orders', + value: 'salesOrders', + }, + { + label: 'Sales Quote Lines', + value: 'salesQuoteLines', + }, + { + label: 'Sales Quotes', + value: 'salesQuotes', + }, + { + label: 'Shipment Methods', + value: 'shipmentMethods', + }, + { + label: 'Vendors', + value: 'vendors', + }, +]; + +export const TRIGGER_ENTITY_DROPDOWN_OPTIONS = + ACTION_ENTITY_DROPDOWN_OPTIONS.filter( + (option) => + !['salesQuoteLines', 'salesOrderLines', 'salesInvoiceLines'].includes( + option.value + ) + ); diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/index.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/index.ts new file mode 100644 index 0000000..f39f391 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/index.ts @@ -0,0 +1,377 @@ +import { businessCentralAuth } from '../..'; +import { + DropdownOption, + DynamicPropsValue, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { makeClient } from './client'; +import { ACTION_ENTITY_DROPDOWN_OPTIONS } from './constants'; +import { customersEntityProps } from './props/customers.entity'; +import { bankAccountsEntityProps } from './props/bankAccounts.entity'; +import { contactsEntityProps } from './props/contacts.entity'; +import { + currenciesEntityNumberProps, + currenciesEntityProps, +} from './props/currencies.entity'; +import { disputeStatusEntityProps } from './props/disputeStatus.entity'; +import { employeesEntityProps } from './props/employees.entity'; +import { vendorsEntityProps } from './props/vendors.entity'; +import { journalsEntityProps } from './props/journals.entity'; +import { locationsEntityProps } from './props/locations.entity'; +import { paymentMethodsEntityProps } from './props/paymentMethods.entity'; +import { + paymentTermsEntityNumberProps, + paymentTermsEntityProps, +} from './props/paymentTerms.entity'; +import { projectsEntityProps } from './props/projects.entity'; +import { itemCategoriesEntityProps } from './props/itemCategories.entity'; +import { itemsEntityNumberProps, itemsEntityProps } from './props/items.entity'; +import { itemVariantsEntityProps } from './props/itemVariants.entity'; +import { + salesOrderLinesEntityNumberProps, + salesOrdersLinesEntityProps, +} from './props/salesOrderLines.entity'; +import { + salesInvoiceLinesEntityNumberProps, + salesInvoiceLinesEntityProps, +} from './props/salesInvoiceLines.entity'; +import { + salesQuoteLinesEntityNumberProps, + salesQuoteLinesEntityProps, +} from './props/salesQuoteLines.entity'; +import { shipmentMethodsEntityProps } from './props/shipmentMethods.entity'; +import { EntityProp } from './types'; +import { salesInvoicesEntityProps } from './props/salesInvoices.entity'; +import { salesOrdersEntityProps } from './props/salesOrders.entity'; +import { salesQuotesEntityProps } from './props/salesQuotes.entity'; + +export const commonProps = { + company_id: Property.Dropdown({ + displayName: 'Company', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect account first', + }; + } + + const authValue = auth as PiecePropValueSchema< + typeof businessCentralAuth + >; + const client = makeClient(authValue); + + const res = await client.listCompanies(); + const options: DropdownOption[] = []; + + for (const company of res.value) { + options.push({ label: company.name, value: company.id }); + } + + return { + disabled: false, + options, + }; + }, + }), + record_id: Property.ShortText({ + displayName: 'Record ID', + required: true, + }), + record_type: Property.StaticDropdown({ + displayName: 'Record Type', + required: true, + options: { + disabled: false, + options: ACTION_ENTITY_DROPDOWN_OPTIONS, + }, + }), + record_fields: Property.DynamicProperties({ + displayName: 'Record Fields', + refreshers: ['company_id', 'record_type'], + required: true, + props: async ({ auth, company_id, record_type }) => { + if (!auth) return {}; + if (!company_id) return {}; + if (!record_type) return {}; + + const recordType = record_type as unknown as string; + const companyId = company_id as unknown as string; + const authValue = auth as PiecePropValueSchema< + typeof businessCentralAuth + >; + const client = makeClient(authValue); + + const fields: DynamicPropsValue = {}; + + // fetch entity prop schema + const entitySchema = getEntityPropSchema(recordType); + + for (const prop of entitySchema) { + switch (prop.type) { + case 'text': + fields[prop.name] = Property.ShortText({ + displayName: prop.displayName, + description: prop.description, + required: prop.isRequired, + }); + break; + case 'multi_text': + fields[prop.name] = Property.LongText({ + displayName: prop.displayName, + description: prop.description, + required: prop.isRequired, + }); + break; + case 'date': + fields[prop.name] = Property.DateTime({ + displayName: prop.displayName, + description: prop.description, + required: prop.isRequired, + }); + break; + case 'number': + fields[prop.name] = Property.Number({ + displayName: prop.displayName, + description: prop.description, + required: prop.isRequired, + }); + break; + case 'boolean': + fields[prop.name] = Property.Checkbox({ + displayName: prop.displayName, + description: prop.description, + required: prop.isRequired, + }); + break; + case 'static_select': + fields[prop.name] = Property.StaticDropdown({ + displayName: prop.displayName, + description: prop.description, + required: prop.isRequired, + options: { + disabled: false, + options: prop.options ?? [], + }, + }); + break; + case 'static_multi_select': + fields[prop.name] = Property.StaticMultiSelectDropdown({ + displayName: prop.displayName, + description: prop.description, + required: prop.isRequired, + options: { + disabled: false, + options: prop.options ?? [], + }, + }); + break; + case 'dynamic_multi_select': + case 'dynamic_select': + { + const propType = + prop.type === 'dynamic_select' + ? Property.StaticDropdown + : Property.StaticMultiSelectDropdown; + + const response = await client.filterRecords( + companyId, + prop.options.sourceFieldSlug, + { + $select: `${prop.options.labelField},id`, + } + ); + const options: DropdownOption[] = []; + + for (const option of response.value) { + options.push({ + label: option[prop.options.labelField] as string, + value: option['id'] as string, + }); + } + fields[prop.name] = propType({ + displayName: prop.displayName, + description: prop.description, + required: prop.isRequired, + options: { + disabled: false, + options, + }, + }); + } + break; + default: + break; + } + } + return fields; + }, + }), + record_filter_fields: Property.DynamicProperties({ + displayName: 'Filter Fields', + refreshers: ['company_id', 'record_type'], + required: true, + props: async ({ auth, company_id, record_type }) => { + if (!auth) return {}; + if (!company_id) return {}; + if (!record_type) return {}; + + const recordType = record_type as unknown as string; + + const fields: DynamicPropsValue = {}; + + // fetch entity prop schema + const entitySchema = getEntityPropSchema(recordType); + + // currently only support text fields + for (const prop of entitySchema) { + switch (prop.type) { + case 'text': + fields[prop.name] = Property.ShortText({ + displayName: prop.displayName, + description: prop.description, + required: prop.isRequired, + }); + break; + case 'multi_text': + fields[prop.name] = Property.LongText({ + displayName: prop.displayName, + description: prop.description, + required: prop.isRequired, + }); + break; + default: + break; + } + } + return fields; + }, + }), +}; + +export function formatRecordFields( + recordFields: DynamicPropsValue, + recordType: string +) { + const numberFields = []; + switch (recordType) { + case 'currencies': + numberFields.push(...currenciesEntityNumberProps); + break; + case 'items': + numberFields.push(...itemsEntityNumberProps); + break; + case 'salesInvoiceLines': + numberFields.push(...salesInvoiceLinesEntityNumberProps); + break; + case 'salesOrders': + numberFields.push(...salesOrderLinesEntityNumberProps); + break; + case 'salesOrderLines': + numberFields.push(...salesOrderLinesEntityNumberProps); + break; + case 'salesQuoteLines': + numberFields.push(...salesQuoteLinesEntityNumberProps); + break; + case 'paymentTerms': + numberFields.push(...paymentTermsEntityNumberProps); + break; + default: + break; + } + + const formattedRecordFields: DynamicPropsValue = {}; + + for (const key in recordFields) { + if (recordFields[key] !== undefined) { + if (numberFields.includes(key)) { + formattedRecordFields[key] = Number(recordFields[key]); + } else { + formattedRecordFields[key] = recordFields[key]; + } + } + } + + return formattedRecordFields; +} + +export function getEntityPropSchema(recordType: string): EntityProp[] { + let entitySchema: EntityProp[] = []; + + // fetch entity prop schema + switch (recordType) { + case 'bankAccounts': + entitySchema = bankAccountsEntityProps; + break; + case 'contacts': + entitySchema = contactsEntityProps; + break; + case 'currencies': + entitySchema = currenciesEntityProps; + break; + case 'customers': + entitySchema = customersEntityProps; + break; + case 'disputeStatus': + entitySchema = disputeStatusEntityProps; + break; + case 'employees': + entitySchema = employeesEntityProps; + break; + case 'itemCategories': + entitySchema = itemCategoriesEntityProps; + break; + case 'items': + entitySchema = itemsEntityProps; + break; + case 'itemVariants': + entitySchema = itemVariantsEntityProps; + break; + case 'journals': + entitySchema = journalsEntityProps; + break; + case 'locations': + entitySchema = locationsEntityProps; + break; + case 'paymentTerms': + entitySchema = paymentTermsEntityProps; + break; + case 'paymentMethods': + entitySchema = paymentMethodsEntityProps; + break; + case 'projects': + entitySchema = projectsEntityProps; + break; + case 'salesInvoiceLines': + entitySchema = salesInvoiceLinesEntityProps; + break; + case 'salesInvoices': + entitySchema = salesInvoicesEntityProps; + break; + case 'salesOrderLines': + entitySchema = salesOrdersLinesEntityProps; + break; + case 'salesOrders': + entitySchema = salesOrdersEntityProps; + break; + case 'salesQuoteLines': + entitySchema = salesQuoteLinesEntityProps; + break; + case 'salesQuotes': + entitySchema = salesQuotesEntityProps; + break; + case 'shipmentMethods': + entitySchema = shipmentMethodsEntityProps; + break; + case 'vendors': + entitySchema = vendorsEntityProps; + break; + default: + break; + } + return entitySchema; +} diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/bankAccounts.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/bankAccounts.entity.ts new file mode 100644 index 0000000..eaac264 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/bankAccounts.entity.ts @@ -0,0 +1,34 @@ +import { EntityProp } from '../types'; + +export const bankAccountsEntityProps: EntityProp[] = [ + { + name: 'number', + displayName: 'Number', + type: 'text', + isRequired: false, + }, + { + name: 'displayName', + displayName: 'Display Name', + type: 'text', + isRequired: false, + }, + { + name: 'bankAccountNumber', + displayName: 'Bank Account Number', + type: 'text', + isRequired: false, + }, + { + name: 'blocked', + displayName: 'Blocked ?', + type: 'boolean', + isRequired: false, + }, + { + name: 'iban', + displayName: 'IBAN', + type: 'text', + isRequired: false, + }, +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/contacts.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/contacts.entity.ts new file mode 100644 index 0000000..ae6f006 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/contacts.entity.ts @@ -0,0 +1,110 @@ +import { EntityProp } from '../types'; + +export const contactsEntityProps: EntityProp[] = [ + { + name: 'number', + displayName: 'Number', + type: 'text', + isRequired: false, + }, + { + name: 'displayName', + displayName: 'Display Name', + type: 'text', + isRequired: false, + }, + { + name: 'type', + displayName: 'Type', + type: 'static_select', + isRequired: false, + options: [ + { + label: 'Person', + value: 'Person', + }, + { + label: 'Company', + value: 'Company', + }, + ], + }, + { + name: 'jobTitle', + displayName: 'Job Title', + type: 'text', + isRequired: false, + }, + { + name: 'addressLine1', + displayName: 'Address Line 1', + type: 'text', + isRequired: false, + }, + { + name: 'addressLine2', + displayName: 'Address Line 2', + type: 'text', + isRequired: false, + }, + { + name: 'city', + displayName: 'City', + type: 'text', + isRequired: false, + }, + { + name: 'state', + displayName: 'State', + type: 'text', + isRequired: false, + }, + { + name: 'country', + displayName: 'Country', + type: 'text', + isRequired: false, + }, + { + name: 'postalCode', + displayName: 'Postal Code', + type: 'text', + isRequired: false, + }, + { + name: 'phoneNumber', + displayName: 'Phone Number', + type: 'text', + isRequired: false, + }, + { + name: 'mobilePhoneNumber', + displayName: 'Mobile Number', + type: 'text', + isRequired: false, + }, + { + name: 'email', + displayName: 'Email', + type: 'text', + isRequired: false, + }, + { + name: 'website', + displayName: 'Website', + type: 'text', + isRequired: false, + }, + { + name: 'privacyBlocked', + displayName: 'Privacy Blocked?', + type: 'boolean', + isRequired: false, + }, + { + name: 'taxRegistrationNumber', + displayName: 'Tax Registration Number', + type: 'text', + isRequired: false, + }, +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/currencies.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/currencies.entity.ts new file mode 100644 index 0000000..868101c --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/currencies.entity.ts @@ -0,0 +1,32 @@ +import { EntityProp } from '../types'; + +export const currenciesEntityProps: EntityProp[] = [ + { + name: 'displayName', + displayName: 'Display Name', + type: 'text', + isRequired: false, + }, + { + name: 'code', + displayName: 'Code', + type: 'text', + isRequired: false, + }, + { + name: 'amountDecimalPlaces', + displayName: 'Amount Decimal Places', + description: + 'Specifies the number of decimal places the system will display on amounts for this currency.', + type: 'text', + isRequired: false, + }, + { + name: 'amountRoundingPrecision', + displayName: 'Amount Rounding Precision', + type: 'number', + isRequired: false, + }, +]; + +export const currenciesEntityNumberProps = ['amountRoundingPrecision']; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/customers.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/customers.entity.ts new file mode 100644 index 0000000..64c3159 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/customers.entity.ts @@ -0,0 +1,126 @@ +import { EntityProp } from '../types'; + +export const customersEntityProps: EntityProp[] = [ + { + name: 'number', + displayName: 'Number', + type: 'text', + isRequired: false, + }, + { + name: 'displayName', + displayName: 'Display Name', + type: 'text', + isRequired: false, + }, + { + name: 'type', + displayName: 'Type', + type: 'static_select', + isRequired: false, + options: [ + { + label: 'Person', + value: 'Person', + }, + { + label: 'Company', + value: 'Company', + }, + ], + }, + { + name: 'addressLine1', + displayName: 'Address Line 1', + type: 'text', + isRequired: false, + }, + { + name: 'addressLine2', + displayName: 'Address Line 2', + type: 'text', + isRequired: false, + }, + { + name: 'city', + displayName: 'City', + type: 'text', + isRequired: false, + }, + { + name: 'state', + displayName: 'State', + type: 'text', + isRequired: false, + }, + { + name: 'country', + displayName: 'Country', + type: 'text', + isRequired: false, + }, + { + name: 'email', + displayName: 'Email', + type: 'text', + isRequired: false, + }, + { + name: 'website', + displayName: 'Website', + type: 'text', + isRequired: false, + }, + { + name: 'taxLiable', + displayName: 'Tax Liable?', + type: 'boolean', + isRequired: false, + }, + { + name: 'currencyId', + displayName: 'Currency ID', + type: 'dynamic_select', + isRequired: false, + options: { + labelField: 'code', + sourceFieldSlug: 'currencies', + }, + }, + { + name: 'currencyCode', + displayName: 'Currency Code', + type: 'text', + isRequired: false, + }, + { + name: 'paymentTermsId', + displayName: 'Payment Terms ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'paymentTerms', + labelField: 'code', + }, + }, + { + name: 'shipmentMethodId', + displayName: 'Shipment Method ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'shipmentMethods', + labelField: 'code', + }, + }, + { + name: 'paymentMethodId', + displayName: 'Payment Method ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'paymentMethods', + labelField: 'code', + }, + }, +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/disputeStatus.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/disputeStatus.entity.ts new file mode 100644 index 0000000..07df630 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/disputeStatus.entity.ts @@ -0,0 +1,18 @@ +import { EntityProp } from '../types'; + +export const disputeStatusEntityProps: EntityProp[] = [ + { + name: 'code', + displayName: 'Code', + description: 'The code of the dispute status.', + type: 'text', + isRequired: false, + }, + { + name: 'displayName', + displayName: 'Display Name', + description: `Specifies the dispute status's name. This name will appear on all sales documents for the dispute status.`, + type: 'text', + isRequired: false, + }, +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/employees.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/employees.entity.ts new file mode 100644 index 0000000..358da60 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/employees.entity.ts @@ -0,0 +1,127 @@ +import { EntityProp } from '../types'; + +export const employeesEntityProps: EntityProp[] = [ + { + name: 'number', + displayName: 'Number', + type: 'text', + isRequired: false, + }, + { + name: 'givenName', + displayName: 'First Name', + type: 'text', + isRequired: false, + }, + { + name: 'middleName', + displayName: 'Middle Name', + type: 'text', + isRequired: false, + }, + { + name: 'surname', + displayName: 'Last Name', + type: 'text', + isRequired: false, + }, + { + name: 'jobTitle', + displayName: 'Job Title', + type: 'text', + isRequired: false, + }, + { + name: 'addressLine1', + displayName: 'Address Line 1', + type: 'text', + isRequired: false, + }, + { + name: 'addressLine2', + displayName: 'Address Line 2', + type: 'text', + isRequired: false, + }, + { + name: 'city', + displayName: 'City', + type: 'text', + isRequired: false, + }, + { + name: 'state', + displayName: 'State', + type: 'text', + isRequired: false, + }, + { + name: 'country', + displayName: 'Country', + type: 'text', + isRequired: false, + }, + { + name: 'postalCode', + displayName: 'Postal Code', + type: 'text', + isRequired: false, + }, + { + name: 'phoneNumber', + displayName: 'Phone Number', + type: 'text', + isRequired: false, + }, + { + name: 'mobilePhone', + displayName: 'Mobile Number', + type: 'text', + isRequired: false, + }, + { + name: 'email', + displayName: 'Email', + type: 'text', + isRequired: false, + }, + { + name: 'personalEmail', + displayName: 'Personal Email', + type: 'text', + isRequired: false, + }, + { + name: 'status', + displayName: 'Status', + type: 'static_select', + description: 'Specifies the status of the employee.', + isRequired: false, + options: [ + { + label: 'Active', + value: 'Active', + }, + { + label: 'Inactive', + value: 'Inactive', + }, + { + label: 'Terminated', + value: 'Terminated', + }, + ], + }, + { + name: 'birthDate', + displayName: 'Birth Date', + type: 'date', + isRequired: false, + }, + { + name: 'employmentDate', + displayName: 'Employment Date', + type: 'date', + isRequired: false, + }, +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/itemCategories.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/itemCategories.entity.ts new file mode 100644 index 0000000..a1a074d --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/itemCategories.entity.ts @@ -0,0 +1,20 @@ +import { EntityProp } from '../types'; + +export const itemCategoriesEntityProps: EntityProp[] = [ + { + name: 'code', + displayName: 'Code', + description: 'The code of the item category.', + + type: 'text', + isRequired: false, + }, + { + name: 'displayName', + displayName: 'Display Name', + description: + "Specifies the item category's name. This name will appear on all sales documents for the item category.", + type: 'text', + isRequired: false, + }, +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/itemVariants.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/itemVariants.entity.ts new file mode 100644 index 0000000..7bc9536 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/itemVariants.entity.ts @@ -0,0 +1,28 @@ +import { EntityProp } from '../types'; + +export const itemVariantsEntityProps: EntityProp[] = [ + { + name: 'itemId', + displayName: 'Item ID', + isRequired: false, + type: 'dynamic_select', + options: { + sourceFieldSlug: 'items', + labelField: 'number', + }, + }, + { + name: 'code', + displayName: 'Code', + description: 'The code of the item variant.', + type: 'text', + isRequired: false, + }, + { + name: 'description', + displayName: 'Description', + description: 'Specifies the description of the item variant.', + type: 'text', + isRequired: false, + }, +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/items.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/items.entity.ts new file mode 100644 index 0000000..00edcdd --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/items.entity.ts @@ -0,0 +1,109 @@ +import { EntityProp } from '../types'; + +export const itemsEntityProps: EntityProp[] = [ + { + name: 'number', + displayName: 'Number', + type: 'text', + isRequired: false, + }, + { + name: 'displayName', + displayName: 'Display Name', + type: 'text', + isRequired: false, + }, + { + name: 'type', + displayName: 'Type', + type: 'static_select', + isRequired: false, + options: [ + { label: 'Inventory', value: 'Inventory' }, + { label: 'Service', value: 'Service' }, + { label: 'Non-Inventory', value: 'Non-Inventory' }, + ], + }, + { + name: 'blocked', + displayName: 'Blocked?', + type: 'boolean', + isRequired: false, + }, + { + name: 'itemCategoryId', + displayName: 'Item Category ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'itemCategories', + labelField: 'code', + }, + }, + { + name: 'itemCategoryCode', + displayName: 'Item Category Code', + type: 'text', + isRequired: false, + }, + { + name: 'gtin', + displayName: 'GTIN', + type: 'text', + isRequired: false, + }, + { + name: 'unitPrice', + displayName: 'Unit Price', + type: 'number', + isRequired: false, + }, + { + name: 'priceIncludesTax', + displayName: 'Price Includes Tax?', + description: + 'Specifies that the unitPrice includes tax. Set to true, if unitPrice includes tax.', + type: 'boolean', + isRequired: false, + }, + { + name: 'unitCost', + displayName: 'Unit Cost', + type: 'number', + isRequired: false, + }, + { + name: 'baseUnitOfMeasureId', + displayName: 'Basic Unit of Measure ID', + isRequired: false, + type: 'dynamic_select', + options: { + sourceFieldSlug: 'unitsOfMeasure', + labelField: 'code', + }, + }, + { + name: 'baseUnitOfMeasureCode', + displayName: 'Basic Unit of Measure Code', + isRequired: false, + type: 'text', + }, + { + name: 'taxGroupId', + displayName: 'Tax Group ID', + isRequired: false, + type: 'dynamic_select', + options: { + sourceFieldSlug: 'taxGroups', + labelField: 'code', + }, + }, + { + name: 'taxGroupCode', + displayName: 'Tax Group Code', + isRequired: false, + type: 'text', + }, +]; + +export const itemsEntityNumberProps = ['unitPrice', 'unitCost']; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/journals.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/journals.entity.ts new file mode 100644 index 0000000..39f9175 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/journals.entity.ts @@ -0,0 +1,25 @@ +import { EntityProp } from '../types'; + +export const journalsEntityProps: EntityProp[] = [ + { + name: 'code', + displayName: 'Code', + description: 'The code of the journal.', + type: 'text', + isRequired: false, + }, + { + name: 'displayName', + displayName: 'Display Name', + description: + "Specifies the journal's name. This name will appear on all sales documents for the journal.", + type: 'text', + isRequired: false, + }, + { + name: 'balancingAccountNumber', + displayName: 'Balancing Account Number', + type: 'text', + isRequired: false, + }, +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/locations.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/locations.entity.ts new file mode 100644 index 0000000..8b03aaa --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/locations.entity.ts @@ -0,0 +1,72 @@ +import { EntityProp } from '../types'; + +export const locationsEntityProps: EntityProp[] = [ + { + name: 'code', + displayName: 'Code', + type: 'text', + isRequired: false, + }, + { + name: 'displayName', + displayName: 'Display Name', + description: + "Specifies the location's name. This name will appear on all sales documents for the location.", + type: 'text', + isRequired: false, + }, + { + name: 'addressLine1', + displayName: 'Address Line 1', + type: 'text', + isRequired: false, + }, + { + name: 'addressLine2', + displayName: 'Address Line 2', + type: 'text', + isRequired: false, + }, + { + name: 'city', + displayName: 'City', + type: 'text', + isRequired: false, + }, + { + name: 'state', + displayName: 'State', + type: 'text', + isRequired: false, + }, + { + name: 'country', + displayName: 'Country', + type: 'text', + isRequired: false, + }, + { + name: 'postalCode', + displayName: 'Postal Code', + type: 'text', + isRequired: false, + }, + { + name: 'phoneNumber', + displayName: 'Phone Number', + type: 'text', + isRequired: false, + }, + { + name: 'email', + displayName: 'Email', + type: 'text', + isRequired: false, + }, + { + name: 'website', + displayName: 'Website', + type: 'text', + isRequired: false, + }, +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/paymentMethods.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/paymentMethods.entity.ts new file mode 100644 index 0000000..eb022b2 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/paymentMethods.entity.ts @@ -0,0 +1,20 @@ +import { EntityProp } from '../types'; + +export const paymentMethodsEntityProps: EntityProp[] = [ + { + name: 'code', + displayName: 'Code', + description: 'The code of the payment method.', + + type: 'text', + isRequired: false, + }, + { + name: 'displayName', + displayName: 'Display Name', + description: + "Specifies the payment method's name. This name will appear on all sales documents for the payment method.", + type: 'text', + isRequired: false, + }, +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/paymentTerms.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/paymentTerms.entity.ts new file mode 100644 index 0000000..f2e8153 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/paymentTerms.entity.ts @@ -0,0 +1,51 @@ +import { EntityProp } from '../types'; + +export const paymentTermsEntityProps: EntityProp[] = [ + { + name: 'code', + displayName: 'Code', + description: 'The code of the payment term.', + type: 'text', + isRequired: false, + }, + { + name: 'displayName', + displayName: 'Display Name', + description: + "Specifies the payment term's name. This name will appear on all sales documents for the payment term.", + type: 'text', + isRequired: false, + }, + { + name: 'dueDateCalculation', + displayName: 'Due Date Calculation', + description: + 'Specifies the formula that is used to calculate the date that a payment must be made.', + type: 'text', + isRequired: false, + }, + { + name: 'discountDateCalculation', + displayName: 'Discount Date Calculation', + description: + 'Specifies the formula that is used to calculate the date that a payment must be made in order to obtain a discount.', + type: 'text', + isRequired: false, + }, + { + name: 'discountPercent', + displayName: 'Discount Percent', + type: 'number', + isRequired: false, + }, + { + name: 'calculateDiscountOnCreditMemos', + displayName: 'Calc. Pmt. Disc. on Credit Memos', + type: 'boolean', + description: + 'Specifies if the discount should be applied to payment term. True indicates a discount will be given, false indicates a discount will not be given.', + isRequired: false, + }, +]; + +export const paymentTermsEntityNumberProps = ['discountPercent']; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/projects.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/projects.entity.ts new file mode 100644 index 0000000..401d80e --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/projects.entity.ts @@ -0,0 +1,20 @@ +import { EntityProp } from '../types'; + +export const projectsEntityProps: EntityProp[] = [ + { + name: 'number', + displayName: 'Number', + description: 'Specifies the number of the project.', + + type: 'text', + isRequired: false, + }, + { + name: 'displayName', + displayName: 'Display name', + description: + "Specifies the project's name. This name will appear on all sales documents for the project.", + type: 'text', + isRequired: false, + }, +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesInvoiceLines.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesInvoiceLines.entity.ts new file mode 100644 index 0000000..b60c347 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesInvoiceLines.entity.ts @@ -0,0 +1,141 @@ +import { EntityProp } from '../types'; + +//https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/api-reference/v2.0/resources/dynamics_salesinvoiceline + +export const salesInvoiceLinesEntityProps: EntityProp[] = [ + { + name: 'salesInvoiceId', + displayName: 'Sales Invoice ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'salesInvoices', + labelField: 'number', + }, + }, + { + name: 'lineType', + displayName: 'Type', + type: 'static_select', + isRequired: false, + options: [ + // { + // label: 'Comment', + // value: 'Comment', + // }, + // { + // label: 'Account', + // value: 'Account', + // }, + { + label: 'Item', + value: 'Item', + }, + // { + // label: 'Resource', + // value: 'Resource', + // }, + // { + // label: 'Value', + // value: 'Value', + // }, + // { + // label: 'Charge', + // value: 'Charge', + // }, + // { + // label: 'Fixed Asset', + // value: 'Fixed Asset', + // }, + ], + }, + { + name: 'itemId', + displayName: 'Item ID', + isRequired: false, + type: 'dynamic_select', + options: { + sourceFieldSlug: 'items', + labelField: 'number', + }, + }, + { + name: 'sequence', + displayName: 'Sequence Number', + isRequired: false, + type: 'text', + }, + { + name: 'lineObjectNumber', + displayName: 'Line Object Number', + description: + 'The number of the object (account or item) of the sales invoice line.', + type: 'text', + isRequired: false, + }, + { + name: 'description', + displayName: 'Description', + type: 'text', + isRequired: false, + }, + { + name: 'unitOfMeasureId', + displayName: 'Unit of Measure ID', + isRequired: false, + type: 'dynamic_select', + options: { + sourceFieldSlug: 'unitsOfMeasure', + labelField: 'code', + }, + }, + { + name: 'unitOfMeasureCode', + displayName: 'Unit of Measure Code', + isRequired: false, + type: 'text', + }, + { + name: 'quantity', + displayName: 'Quantity', + isRequired: false, + type: 'number', + }, + { + name: 'unitPrice', + displayName: 'Unit Price', + isRequired: false, + type: 'number', + }, + { + name: 'discountAmount', + displayName: 'Discount Amount', + type: 'number', + isRequired: false, + }, + { + name: 'discountPercent', + displayName: 'Discount Percent', + type: 'number', + isRequired: false, + }, + { + name: 'taxCode', + displayName: 'Tax Code', + type: 'text', + isRequired: false, + }, + { + name: 'shipmentDate', + displayName: 'Shipment Date', + isRequired: false, + type: 'date', + }, +]; + +export const salesInvoiceLinesEntityNumberProps = [ + 'quantity', + 'unitPrice', + 'discountPercent', + 'discountAmount', +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesInvoices.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesInvoices.entity.ts new file mode 100644 index 0000000..f4078b2 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesInvoices.entity.ts @@ -0,0 +1,198 @@ +import { EntityProp } from '../types'; + +// https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/api-reference/v2.0/resources/dynamics_salesinvoice + +export const salesInvoicesEntityProps: EntityProp[] = [ + { + name: 'invoiceDate', + displayName: 'Invoice Date', + type: 'date', + isRequired: false, + }, + { + name: 'postingDate', + displayName: 'Posting Date', + type: 'date', + isRequired: false, + }, + { + name: 'dueDate', + displayName: 'Due Date', + type: 'date', + isRequired: false, + }, + { + name: 'customerId', + displayName: 'Customer ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'customers', + labelField: 'displayName', + }, + }, + { + name: 'customerNumber', + displayName: 'Customer Number', + type: 'text', + isRequired: false, + }, + { + name: 'billToCustomerId', + displayName: 'Bill to Customer ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'customers', + labelField: 'displayName', + }, + }, + { + name: 'billToCustomerNumber', + displayName: 'Bill to Customer Number', + type: 'text', + isRequired: false, + }, + + { + name: 'shipToName', + displayName: 'Ship to Name', + type: 'text', + isRequired: false, + }, + { + name: 'shipToContact', + displayName: 'Ship to Contact', + type: 'text', + isRequired: false, + }, + { + name: 'shipToAddressLine1', + displayName: 'Ship to Address Line 1', + type: 'text', + isRequired: false, + }, + { + name: 'shipToAddressLine2', + displayName: 'Ship to Address Line 2', + type: 'text', + isRequired: false, + }, + { + name: 'shipToCity', + displayName: 'Ship to City', + type: 'text', + isRequired: false, + }, + { + name: 'shipToCountry', + displayName: 'Ship to Country', + type: 'text', + isRequired: false, + }, + { + name: 'shipToState', + displayName: 'Ship to State', + type: 'text', + isRequired: false, + }, + { + name: 'shipToPostCode', + displayName: 'Ship to Postalcode', + type: 'text', + isRequired: false, + }, + { + name: 'sellToAddressLine1', + displayName: 'Sell to Address Line 1', + type: 'text', + isRequired: false, + }, + { + name: 'sellToAddressLine2', + displayName: 'Sell to Address Line 2', + type: 'text', + isRequired: false, + }, + { + name: 'sellToCity', + displayName: 'Sell to City', + type: 'text', + isRequired: false, + }, + { + name: 'sellToCountry', + displayName: 'Sell to Country', + type: 'text', + isRequired: false, + }, + { + name: 'sellToState', + displayName: 'Sell to State', + type: 'text', + isRequired: false, + }, + { + name: 'sellToPostCode', + displayName: 'Sell to Postalcode', + type: 'text', + isRequired: false, + }, + { + name: 'currencyId', + displayName: 'Currency ID', + type: 'dynamic_select', + isRequired: false, + options: { + labelField: 'code', + sourceFieldSlug: 'currencies', + }, + }, + { + name: 'currencyCode', + displayName: 'Currency Code', + type: 'text', + isRequired: false, + }, + { + name: 'paymentTermsId', + displayName: 'Payment Terms ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'paymentTerms', + labelField: 'code', + }, + }, + { + name: 'shipmentMethodId', + displayName: 'Shipment Method ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'shipmentMethods', + labelField: 'code', + }, + }, + { + name: 'salesperson', + displayName: 'Sales Person Code', + description: 'The salesperson code for the sales invoice.', + isRequired: false, + type: 'text', + }, + { + name: 'phoneNumber', + displayName: 'Phone Number', + description: "Specifies the sales invoice's telephone number.", + type: 'text', + isRequired: false, + }, + { + name: 'email', + displayName: 'Email', + description: "Specifies the sales invoice's email address.", + type: 'text', + isRequired: false, + }, +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesOrderLines.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesOrderLines.entity.ts new file mode 100644 index 0000000..c5972a8 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesOrderLines.entity.ts @@ -0,0 +1,158 @@ +import { EntityProp } from '../types'; + +// https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/api-reference/v2.0/resources/dynamics_salesorderline + +export const salesOrdersLinesEntityProps: EntityProp[] = [ + { + name: 'salesOrderId', + displayName: 'Sales Order ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'salesOrders', + labelField: 'number', + }, + }, + { + name: 'lineType', + displayName: 'Type', + type: 'static_select', + isRequired: false, + options: [ + // { + // label: 'Comment', + // value: 'Comment', + // }, + // { + // label: 'Account', + // value: 'Account', + // }, + { + label: 'Item', + value: 'Item', + }, + // { + // label: 'Resource', + // value: 'Resource', + // }, + // { + // label: 'Value', + // value: 'Value', + // }, + // { + // label: 'Charge', + // value: 'Charge', + // }, + // { + // label: 'Fixed Asset', + // value: 'Fixed Asset', + // }, + ], + }, + { + name: 'itemId', + displayName: 'Item ID', + isRequired: false, + type: 'dynamic_select', + options: { + sourceFieldSlug: 'items', + labelField: 'number', + }, + }, + { + name: 'sequence', + displayName: 'Sequence Number', + isRequired: false, + type: 'text', + }, + { + name: 'lineObjectNumber', + displayName: 'Line Object Number', + description: + 'The number of the object (account or item) of the sales order line.', + type: 'text', + isRequired: false, + }, + { + name: 'description', + displayName: 'Description', + type: 'text', + isRequired: false, + }, + { + name: 'unitOfMeasureId', + displayName: 'Unit of Measure ID', + isRequired: false, + type: 'dynamic_select', + options: { + sourceFieldSlug: 'unitsOfMeasure', + labelField: 'code', + }, + }, + { + name: 'unitOfMeasureCode', + displayName: 'Unit of Measure Code', + isRequired: false, + type: 'text', + }, + { + name: 'quantity', + displayName: 'Quantity', + isRequired: false, + type: 'number', + }, + { + name: 'unitPrice', + displayName: 'Unit Price', + isRequired: false, + type: 'number', + }, + { + name: 'discountAmount', + displayName: 'Discount Amount', + type: 'number', + isRequired: false, + }, + { + name: 'discountPercent', + displayName: 'Discount Percent', + type: 'number', + isRequired: false, + }, + { + name: 'taxCode', + displayName: 'Tax Code', + type: 'text', + isRequired: false, + }, + { + name: 'shipmentDate', + displayName: 'Shipment Date', + isRequired: false, + type: 'date', + }, + { + name: 'invoiceQuantity', + displayName: 'Invoice Quantity', + type: 'number', + isRequired: false, + description: + 'The quantity of items from the sales order line to be invoiced.', + }, + { + name: 'shipQuantity', + displayName: 'Ship Quantity', + type: 'number', + isRequired: false, + description: 'The quantity of items from the order to be shipped.', + }, +]; + +export const salesOrderLinesEntityNumberProps = [ + 'quantity', + 'unitPrice', + 'discountPercent', + 'discountAmount', + 'invoicedQuantity', + 'shipQuantity', +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesOrders.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesOrders.entity.ts new file mode 100644 index 0000000..a2d5973 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesOrders.entity.ts @@ -0,0 +1,218 @@ +import { EntityProp } from '../types'; + +export const salesOrdersEntityProps: EntityProp[] = [ + { + name: 'orderDate', + displayName: 'Order Date', + type: 'date', + isRequired: false, + }, + { + name: 'postingDate', + displayName: 'Posting Date', + type: 'date', + isRequired: false, + }, + { + name: 'customerId', + displayName: 'Customer ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'customers', + labelField: 'displayName', + }, + }, + { + name: 'customerNumber', + displayName: 'Customer Number', + type: 'text', + isRequired: false, + }, + { + name: 'billToCustomerId', + displayName: 'Bill to Customer ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'customers', + labelField: 'displayName', + }, + }, + { + name: 'billToCustomerNumber', + displayName: 'Bill to Customer Number', + type: 'text', + isRequired: false, + }, + { + name: 'shipToName', + displayName: 'Ship to Name', + type: 'text', + isRequired: false, + }, + { + name: 'shipToContact', + displayName: 'Ship to Contact', + type: 'text', + isRequired: false, + }, + { + name: 'shipToAddressLine1', + displayName: 'Ship to Address Line 1', + type: 'text', + isRequired: false, + }, + { + name: 'shipToAddressLine2', + displayName: 'Ship to Address Line 2', + type: 'text', + isRequired: false, + }, + { + name: 'shipToCity', + displayName: 'Ship to City', + type: 'text', + isRequired: false, + }, + { + name: 'shipToCountry', + displayName: 'Ship to Country', + type: 'text', + isRequired: false, + }, + { + name: 'shipToState', + displayName: 'Ship to State', + type: 'text', + isRequired: false, + }, + { + name: 'shipToPostCode', + displayName: 'Ship to Postalcode', + type: 'text', + isRequired: false, + }, + { + name: 'sellToAddressLine1', + displayName: 'Sell to Address Line 1', + type: 'text', + isRequired: false, + }, + { + name: 'sellToAddressLine2', + displayName: 'Sell to Address Line 2', + type: 'text', + isRequired: false, + }, + { + name: 'sellToCity', + displayName: 'Sell to City', + type: 'text', + isRequired: false, + }, + { + name: 'sellToCountry', + displayName: 'Sell to Country', + type: 'text', + isRequired: false, + }, + { + name: 'sellToState', + displayName: 'Sell to State', + type: 'text', + isRequired: false, + }, + { + name: 'sellToPostCode', + displayName: 'Sell to Postalcode', + type: 'text', + isRequired: false, + }, + { + name: 'currencyId', + displayName: 'Currency ID', + type: 'dynamic_select', + isRequired: false, + options: { + labelField: 'code', + sourceFieldSlug: 'currencies', + }, + }, + { + name: 'currencyCode', + displayName: 'Currency Code', + type: 'text', + isRequired: false, + }, + { + name: 'pricesIncludeTax', + displayName: 'Prices include Tax?', + isRequired: false, + type: 'boolean', + }, + { + name: 'paymentTermsId', + displayName: 'Payment Terms ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'paymentTerms', + labelField: 'code', + }, + }, + { + name: 'shipmentMethodId', + displayName: 'Shipment Method ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'shipmentMethods', + labelField: 'code', + }, + }, + { + name: 'salesperson', + displayName: 'Sales Person Code', + description: 'The salesperson code for the sales order.', + isRequired: false, + type: 'text', + }, + { + name: 'partialShipping', + displayName: + 'Specifies whether partial shipping of items is preferred or not.', + type: 'boolean', + isRequired: false, + }, + { + name: 'requestedDeliveryDate', + displayName: 'Requested Delivery Date', + type: 'date', + isRequired: false, + }, + { + name: 'phoneNumber', + displayName: 'Phone Number', + description: "Specifies the sales order's telephone number.", + type: 'text', + isRequired: false, + }, + { + name: 'email', + displayName: 'Email', + description: "Specifies the sales order's email address.", + type: 'text', + isRequired: false, + }, + { + name: 'fullyShipped', + displayName: 'Fully Shipped?', + description: + 'Specifies whether the items of the sales order were fully shipped or not.', + type: 'boolean', + isRequired: false, + }, +]; + +export const salesOrderLinesEntityNumberProps = ['discountAmount']; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesQuoteLines.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesQuoteLines.entity.ts new file mode 100644 index 0000000..3c2a311 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesQuoteLines.entity.ts @@ -0,0 +1,134 @@ +import { EntityProp } from '../types'; + +//https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/api-reference/v2.0/resources/dynamics_salesquoteline + +export const salesQuoteLinesEntityProps: EntityProp[] = [ + { + name: 'salesQuoteId', + displayName: 'Sales Quote ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'salesQuotes', + labelField: 'number', + }, + }, + { + name: 'lineType', + displayName: 'Type', + type: 'static_select', + isRequired: false, + options: [ + // { + // label: 'Comment', + // value: 'Comment', + // }, + // { + // label: 'Account', + // value: 'Account', + // }, + { + label: 'Item', + value: 'Item', + }, + // { + // label: 'Resource', + // value: 'Resource', + // }, + // { + // label: 'Value', + // value: 'Value', + // }, + // { + // label: 'Charge', + // value: 'Charge', + // }, + // { + // label: 'Fixed Asset', + // value: 'Fixed Asset', + // }, + ], + }, + { + name: 'itemId', + displayName: 'Item ID', + isRequired: false, + type: 'dynamic_select', + options: { + sourceFieldSlug: 'items', + labelField: 'number', + }, + }, + { + name: 'sequence', + displayName: 'Sequence Number', + isRequired: false, + type: 'text', + }, + { + name: 'lineObjectNumber', + displayName: 'Line Object Number', + description: + 'The number of the object (account or item) of the sales quote line.', + type: 'text', + isRequired: false, + }, + { + name: 'description', + displayName: 'Description', + type: 'text', + isRequired: false, + }, + { + name: 'unitOfMeasureId', + displayName: 'Unit of Measure ID', + isRequired: false, + type: 'dynamic_select', + options: { + sourceFieldSlug: 'unitsOfMeasure', + labelField: 'code', + }, + }, + { + name: 'unitOfMeasureCode', + displayName: 'Unit of Measure Code', + isRequired: false, + type: 'text', + }, + { + name: 'quantity', + displayName: 'Quantity', + isRequired: false, + type: 'number', + }, + { + name: 'unitPrice', + displayName: 'Unit Price', + isRequired: false, + type: 'number', + }, + { + name: 'discountAmount', + displayName: 'Discount Amount', + type: 'number', + isRequired: false, + }, + { + name: 'discountPercent', + displayName: 'Discount Percent', + type: 'number', + isRequired: false, + }, + { + name: 'taxCode', + displayName: 'Tax Code', + type: 'text', + isRequired: false, + }, +]; +export const salesQuoteLinesEntityNumberProps = [ + 'quantity', + 'unitPrice', + 'discountPercent', + 'discountAmount', +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesQuotes.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesQuotes.entity.ts new file mode 100644 index 0000000..39b1bc4 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/salesQuotes.entity.ts @@ -0,0 +1,203 @@ +import { EntityProp } from '../types'; + +// https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/api-reference/v2.0/api/dynamics_salesquote_create + +export const salesQuotesEntityProps: EntityProp[] = [ + { + name: 'number', + displayName: 'Number', + type: 'text', + isRequired: false, + }, + { + name: 'documentDate', + displayName: 'Quote Date', + type: 'date', + isRequired: false, + }, + { + name: 'postingDate', + displayName: 'Posting Date', + type: 'date', + isRequired: false, + }, + { + name: 'customerId', + displayName: 'Customer ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'customers', + labelField: 'displayName', + }, + }, + { + name: 'customerNumber', + displayName: 'Customer Number', + type: 'text', + isRequired: false, + }, + { + name: 'billToCustomerId', + displayName: 'Bill to Customer ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'customers', + labelField: 'displayName', + }, + }, + { + name: 'billToCustomerNumber', + displayName: 'Bill to Customer Number', + type: 'text', + isRequired: false, + }, + { + name: 'shipToName', + displayName: 'Ship to Name', + type: 'text', + isRequired: false, + }, + { + name: 'shipToContact', + displayName: 'Ship to Contact', + type: 'text', + isRequired: false, + }, + { + name: 'shipToAddressLine1', + displayName: 'Ship to Address Line 1', + type: 'text', + isRequired: false, + }, + { + name: 'shipToAddressLine2', + displayName: 'Ship to Address Line 2', + type: 'text', + isRequired: false, + }, + { + name: 'shipToCity', + displayName: 'Ship to City', + type: 'text', + isRequired: false, + }, + { + name: 'shipToCountry', + displayName: 'Ship to Country', + type: 'text', + isRequired: false, + }, + { + name: 'shipToState', + displayName: 'Ship to State', + type: 'text', + isRequired: false, + }, + { + name: 'shipToPostCode', + displayName: 'Ship to Postalcode', + type: 'text', + isRequired: false, + }, + { + name: 'sellToAddressLine1', + displayName: 'Sell to Address Line 1', + type: 'text', + isRequired: false, + }, + { + name: 'sellToAddressLine2', + displayName: 'Sell to Address Line 2', + type: 'text', + isRequired: false, + }, + { + name: 'sellToCity', + displayName: 'Sell to City', + type: 'text', + isRequired: false, + }, + { + name: 'sellToCountry', + displayName: 'Sell to Country', + type: 'text', + isRequired: false, + }, + { + name: 'sellToState', + displayName: 'Sell to State', + type: 'text', + isRequired: false, + }, + { + name: 'sellToPostCode', + displayName: 'Sell to Postalcode', + type: 'text', + isRequired: false, + }, + { + name: 'currencyId', + displayName: 'Currency ID', + type: 'dynamic_select', + isRequired: false, + options: { + labelField: 'code', + sourceFieldSlug: 'currencies', + }, + }, + { + name: 'currencyCode', + displayName: 'Currency Code', + type: 'text', + isRequired: false, + }, + { + name: 'paymentTermsId', + displayName: 'Payment Terms ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'paymentTerms', + labelField: 'code', + }, + }, + { + name: 'shipmentMethodId', + displayName: 'Shipment Method ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'shipmentMethods', + labelField: 'code', + }, + }, + { + name: 'salesperson', + displayName: 'Sales Person Code', + description: 'The salesperson code for the sales quote.', + isRequired: false, + type: 'text', + }, + { + name: 'phoneNumber', + displayName: 'Phone Number', + description: "Specifies the sales quote's telephone number.", + type: 'text', + isRequired: false, + }, + { + name: 'email', + displayName: 'Email', + description: "Specifies the sales quote's email address.", + type: 'text', + isRequired: false, + }, + { + name: 'validUntilDate', + displayName: 'Valid Until Date', + isRequired: false, + type: 'date', + }, +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/shipmentMethods.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/shipmentMethods.entity.ts new file mode 100644 index 0000000..3accd8b --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/shipmentMethods.entity.ts @@ -0,0 +1,19 @@ +import { EntityProp } from '../types'; + +export const shipmentMethodsEntityProps: EntityProp[] = [ + { + name: 'displayName', + displayName: 'Display Name', + description: + "Specifies the shipment method's name. This name will appear on all sales documents for the shipment method.", + type: 'text', + isRequired: false, + }, + { + name: 'code', + displayName: 'Code', + description: 'TThe code of the shipment method.', + type: 'text', + isRequired: false, + }, +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/vendors.entity.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/vendors.entity.ts new file mode 100644 index 0000000..1378950 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/props/vendors.entity.ts @@ -0,0 +1,112 @@ +import { EntityProp } from '../types'; + +export const vendorsEntityProps: EntityProp[] = [ + { + name: 'number', + displayName: 'Number', + type: 'text', + isRequired: false, + }, + { + name: 'displayName', + displayName: 'Display Name', + type: 'text', + isRequired: false, + }, + { + name: 'addressLine1', + displayName: 'Address Line 1', + type: 'text', + isRequired: false, + }, + { + name: 'addressLine2', + displayName: 'Address Line 2', + type: 'text', + isRequired: false, + }, + { + name: 'city', + displayName: 'City', + type: 'text', + isRequired: false, + }, + { + name: 'state', + displayName: 'State', + type: 'text', + isRequired: false, + }, + { + name: 'country', + displayName: 'Country', + type: 'text', + isRequired: false, + }, + { + name: 'postalCode', + displayName: 'Postal Code', + type: 'text', + isRequired: false, + }, + { + name: 'email', + displayName: 'Email', + type: 'text', + isRequired: false, + }, + { + name: 'website', + displayName: 'Website', + type: 'text', + isRequired: false, + }, + { + name: 'taxLiable', + displayName: 'Tax Liable?', + type: 'boolean', + isRequired: false, + }, + { + name: 'taxRegistrationNumber', + displayName: 'Tax Registration Number', + type: 'text', + isRequired: false, + }, + { + name: 'currencyId', + displayName: 'Currency ID', + type: 'dynamic_select', + isRequired: false, + options: { + labelField: 'code', + sourceFieldSlug: 'currencies', + }, + }, + { + name: 'currencyCode', + displayName: 'Currency Code', + type: 'text', + isRequired: false, + }, + { + name: 'paymentTermsId', + displayName: 'Payment Terms ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'paymentTerms', + labelField: 'code', + }, + }, + { + name: 'paymentMethodId', + displayName: 'Payment Method ID', + type: 'dynamic_select', + isRequired: false, + options: { + sourceFieldSlug: 'paymentMethods', + labelField: 'code', + }, + }, +]; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/types.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/types.ts new file mode 100644 index 0000000..b62a982 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/common/types.ts @@ -0,0 +1,62 @@ +interface BaseField { + name: string; + displayName: string; + description?: string; + isRequired: boolean; +} + +interface TextField extends BaseField { + type: 'text'; +} + +interface MultiTextField extends BaseField { + type: 'multi_text'; +} + +interface NumberField extends BaseField { + type: 'number'; +} +interface BooleanField extends BaseField { + type: 'boolean'; +} + +interface DateField extends BaseField { + type: 'date'; +} + +interface StaticSelectField extends BaseField { + type: 'static_select'; + options: Array<{ label: string; value: string }>; +} + +interface StaticMultiSelectField extends BaseField { + type: 'static_multi_select'; + options: Array<{ label: string; value: string }>; +} + +interface DynamicSingleSelectField extends BaseField { + type: 'dynamic_select'; + options: { + sourceFieldSlug: string; + labelField: string; + }; +} + +interface DynamicMultiSelectField extends BaseField { + type: 'dynamic_multi_select'; + options: { + sourceFieldSlug: string; + labelField: string; + }; +} + +export type EntityProp = + | TextField + | MultiTextField + | NumberField + | BooleanField + | DateField + | StaticMultiSelectField + | StaticSelectField + | DynamicMultiSelectField + | DynamicSingleSelectField; diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/triggers/new-or-updated-record.trigger.ts b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/triggers/new-or-updated-record.trigger.ts new file mode 100644 index 0000000..5e1e96e --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/src/lib/triggers/new-or-updated-record.trigger.ts @@ -0,0 +1,98 @@ +import { businessCentralAuth } from '../../'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { commonProps } from '../common'; +import { filterParams, makeClient } from '../common/client'; +import dayjs from 'dayjs'; +import { TRIGGER_ENTITY_DROPDOWN_OPTIONS } from '../common/constants'; + +const polling: Polling< + PiecePropValueSchema, + { company_id: string; record_type: string } +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + const filter: filterParams = {}; + + if (lastFetchEpochMS) { + filter['$filter'] = `lastModifiedDateTime gt ${dayjs( + lastFetchEpochMS + ).toISOString()}`; + } else { + filter['$top'] = 10; + } + + const client = makeClient(auth); + const response = await client.filterRecords( + propsValue.company_id, + propsValue.record_type, + filter + ); + + return response.value.map((item: any) => { + return { + epochMilliSeconds: dayjs(item['lastModifiedDateTime']).valueOf(), + data: item, + }; + }); + }, +}; + +export const newOrUpdatedRecordTrigger = createTrigger({ + auth: businessCentralAuth, + name: 'new-or-updated-record', + displayName: 'New or Updated Record', + description: 'Triggers when a new record is added or modified.', + type: TriggerStrategy.POLLING, + sampleData: {}, + props: { + company_id: commonProps.company_id, + record_type: Property.StaticDropdown({ + displayName: 'Record Type', + required: true, + options: { + disabled: false, + options: TRIGGER_ENTITY_DROPDOWN_OPTIONS, + }, + }), + }, + async test(ctx) { + return await pollingHelper.test(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + files: ctx.files, + }); + }, + async onEnable(ctx) { + await pollingHelper.onEnable(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + }); + }, + async onDisable(ctx) { + await pollingHelper.onDisable(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + }); + }, + async run(ctx) { + return await pollingHelper.poll(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + files: ctx.files, + }); + }, +}); diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/tsconfig.json b/packages/pieces/community/microsoft-dynamics-365-business-central/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/microsoft-dynamics-365-business-central/tsconfig.lib.json b/packages/pieces/community/microsoft-dynamics-365-business-central/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-365-business-central/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/microsoft-dynamics-crm/.eslintrc.json b/packages/pieces/community/microsoft-dynamics-crm/.eslintrc.json new file mode 100644 index 0000000..eac708a --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-crm/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/microsoft-dynamics-crm/README.md b/packages/pieces/community/microsoft-dynamics-crm/README.md new file mode 100644 index 0000000..b863e2a --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-crm/README.md @@ -0,0 +1 @@ +# pieces-microsoft-dynamics-crm diff --git a/packages/pieces/community/microsoft-dynamics-crm/package.json b/packages/pieces/community/microsoft-dynamics-crm/package.json new file mode 100644 index 0000000..c72bb1d --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-crm/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-microsoft-dynamics-crm", + "version": "0.1.2" +} diff --git a/packages/pieces/community/microsoft-dynamics-crm/project.json b/packages/pieces/community/microsoft-dynamics-crm/project.json new file mode 100644 index 0000000..6fe7179 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-crm/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-microsoft-dynamics-crm", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/microsoft-dynamics-crm/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/microsoft-dynamics-crm", + "tsConfig": "packages/pieces/community/microsoft-dynamics-crm/tsconfig.lib.json", + "packageJson": "packages/pieces/community/microsoft-dynamics-crm/package.json", + "main": "packages/pieces/community/microsoft-dynamics-crm/src/index.ts", + "assets": [ + "packages/pieces/community/microsoft-dynamics-crm/*.md", + { + "input": "packages/pieces/community/microsoft-dynamics-crm/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-microsoft-dynamics-crm {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/microsoft-dynamics-crm/src/index.ts b/packages/pieces/community/microsoft-dynamics-crm/src/index.ts new file mode 100644 index 0000000..91efb9c --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-crm/src/index.ts @@ -0,0 +1,80 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + createPiece, + OAuth2PropertyValue, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { createRecordAction } from './lib/actions/create-record'; +import { deleteRecordAction } from './lib/actions/delete-record'; +import { getRecordAction } from './lib/actions/get-record'; +import { updateRecordAction } from './lib/actions/update-record'; +import { PieceCategory } from '@activepieces/shared'; + +export const dynamicsCRMAuth = PieceAuth.OAuth2({ + props: { + hostUrl: Property.ShortText({ + displayName: 'Host URL (without trailing slash)', + description: + 'Host URL without trailing slash.For example **https://demo.crm.dynamics.com**', + required: true, + }), + tenantId: Property.ShortText({ + displayName: 'Tenant ID', + description: 'You can find this in the Azure portal.', + defaultValue: 'common', + required: true, + }), + proxyUrl: Property.ShortText({ + displayName: 'Proxy URL with Port', + description: + 'Keep empty if not needed. Optional proxy URL used for establishing connections when proxying requests is needed. For example: **https://proxy.com:8080**.', + required: false, + }), + }, + required: true, + scope: ['{hostUrl}/.default', 'openid', 'email', 'profile', 'offline_access'], + authUrl: 'https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize', + tokenUrl: 'https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token', +}); + +export function getBaseUrl(host: string, proxyUrl?: string): string { + if (proxyUrl && proxyUrl !== '') { + return proxyUrl; + } + return host; +} + +export const microsoftDynamicsCrm = createPiece({ + displayName: 'Microsoft Dynamics CRM', + auth: dynamicsCRMAuth, + description: + 'Customer relationship management software package developed by Microsoft.', + minimumSupportedRelease: '0.27.1', + logoUrl: 'https://cdn.activepieces.com/pieces/microsoft-dynamics-crm.png', + authors: ['kishanprmr'], + categories: [PieceCategory.SALES_AND_CRM], + actions: [ + createRecordAction, + deleteRecordAction, + getRecordAction, + updateRecordAction, + createCustomApiCallAction({ + auth: dynamicsCRMAuth, + baseUrl: (auth) => { + const props = (auth as OAuth2PropertyValue).props as { + hostUrl: string; + proxyUrl: string; + }; + return `${getBaseUrl( + props?.['hostUrl'], + props.proxyUrl + )}/api/data/v9.2`; + }, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/microsoft-dynamics-crm/src/lib/actions/create-record.ts b/packages/pieces/community/microsoft-dynamics-crm/src/lib/actions/create-record.ts new file mode 100644 index 0000000..17d6955 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-crm/src/lib/actions/create-record.ts @@ -0,0 +1,30 @@ +import { + PiecePropValueSchema, + createAction, +} from '@activepieces/pieces-framework'; +import { dynamicsCRMAuth } from '../../'; +import { DynamicsCRMCommon, makeClient } from '../common'; + +export const createRecordAction = createAction({ + auth: dynamicsCRMAuth, + name: 'dynamics_crm_create_record', + displayName: 'Create Record', + description: 'Creates a new record.', + props: { + entityType: DynamicsCRMCommon.entityType( + 'Select or map the entity for which you want to create the record.' + ), + fields: DynamicsCRMCommon.entityFields(true), + }, + async run(context) { + const { entityType, fields } = context.propsValue; + + const entityUrlPath = entityType as string; + + const client = makeClient( + context.auth as PiecePropValueSchema + ); + + return await client.createRecord(entityUrlPath, fields); + }, +}); diff --git a/packages/pieces/community/microsoft-dynamics-crm/src/lib/actions/delete-record.ts b/packages/pieces/community/microsoft-dynamics-crm/src/lib/actions/delete-record.ts new file mode 100644 index 0000000..e332057 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-crm/src/lib/actions/delete-record.ts @@ -0,0 +1,30 @@ +import { + PiecePropValueSchema, + createAction, +} from '@activepieces/pieces-framework'; +import { dynamicsCRMAuth } from '../../'; +import { DynamicsCRMCommon, makeClient } from '../common'; + +export const deleteRecordAction = createAction({ + auth: dynamicsCRMAuth, + name: 'dynamics_crm_delete_record', + displayName: 'Delete Record', + description: 'Deletes an existing record.', + props: { + entityType: DynamicsCRMCommon.entityType( + 'Select or map the entity name whose records you want to delete.' + ), + recordId: DynamicsCRMCommon.recordId, + }, + async run(context) { + const { entityType, recordId } = context.propsValue; + + const entityUrlPath = entityType as string; + + const client = makeClient( + context.auth as PiecePropValueSchema + ); + + return await client.deleteRecord(entityUrlPath, recordId); + }, +}); diff --git a/packages/pieces/community/microsoft-dynamics-crm/src/lib/actions/get-record.ts b/packages/pieces/community/microsoft-dynamics-crm/src/lib/actions/get-record.ts new file mode 100644 index 0000000..75f0356 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-crm/src/lib/actions/get-record.ts @@ -0,0 +1,30 @@ +import { + PiecePropValueSchema, + createAction, +} from '@activepieces/pieces-framework'; +import { dynamicsCRMAuth } from '../../'; +import { DynamicsCRMCommon, makeClient } from '../common'; + +export const getRecordAction = createAction({ + auth: dynamicsCRMAuth, + name: 'dynamics_crm_get_record', + displayName: 'Get Record', + description: 'Retrieves an existing record.', + props: { + entityType: DynamicsCRMCommon.entityType( + 'Select or map the entity name whose records you want to retrieve.' + ), + recordId: DynamicsCRMCommon.recordId, + }, + async run(context) { + const { entityType, recordId } = context.propsValue; + + const entityUrlPath = entityType as string; + + const client = makeClient( + context.auth as PiecePropValueSchema + ); + + return await client.getRecord(entityUrlPath, recordId); + }, +}); diff --git a/packages/pieces/community/microsoft-dynamics-crm/src/lib/actions/update-record.ts b/packages/pieces/community/microsoft-dynamics-crm/src/lib/actions/update-record.ts new file mode 100644 index 0000000..ae17c50 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-crm/src/lib/actions/update-record.ts @@ -0,0 +1,31 @@ +import { + PiecePropValueSchema, + createAction, +} from '@activepieces/pieces-framework'; +import { dynamicsCRMAuth } from '../../'; +import { DynamicsCRMCommon, makeClient } from '../common'; + +export const updateRecordAction = createAction({ + auth: dynamicsCRMAuth, + name: 'dynamics_crm_update_record', + displayName: 'Update Record', + description: 'Updates an existing record.', + props: { + entityType: DynamicsCRMCommon.entityType( + 'Select or map the entity for which you want to update the record.' + ), + recordId: DynamicsCRMCommon.recordId, + fields: DynamicsCRMCommon.entityFields(false), + }, + async run(context) { + const { entityType, recordId, fields } = context.propsValue; + + const entityUrlPath = entityType as string; + + const client = makeClient( + context.auth as PiecePropValueSchema + ); + + return await client.updatedRecord(entityUrlPath, recordId, fields); + }, +}); diff --git a/packages/pieces/community/microsoft-dynamics-crm/src/lib/common/client.ts b/packages/pieces/community/microsoft-dynamics-crm/src/lib/common/client.ts new file mode 100644 index 0000000..85c185d --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-crm/src/lib/common/client.ts @@ -0,0 +1,169 @@ +import { + AuthenticationType, + HttpHeaders, + HttpMessageBody, + HttpMethod, + QueryParams, + httpClient, +} from '@activepieces/pieces-common'; +import { EntityAttributeType } from './constants'; +import { + EntityAttributeOptionsResponse, + EntityAttributeResponse, + EntityTypeAttributesResponse, + EntityTypeResponse, +} from './types'; +import { getBaseUrl } from '../..'; + +export class DynamicsCRMClient { + constructor( + private hostUrl: string, + private accessToken: string, + private proxyUrl?: string + ) {} + async makeRequest( + method: HttpMethod, + resourceUri: string, + headers?: HttpHeaders, + query?: QueryParams, + body: any | undefined = undefined + ): Promise { + const baseUrl = getBaseUrl(this.hostUrl.replace(/\/$/, ''), this.proxyUrl); + const res = await httpClient.sendRequest({ + method: method, + url: `${baseUrl}/api/data/v9.2` + resourceUri, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: this.accessToken, + }, + headers: { + Accept: 'application/json', + 'OData-MaxVersion': '4.0', + 'OData-Version': '4.0', + 'Content-Type': 'application/json', + ...headers, + }, + queryParams: query, + body: body, + }); + return res.body; + } + + async createRecord(entityUrlPath: string, request: unknown) { + return await this.makeRequest( + HttpMethod.POST, + `/${entityUrlPath}`, + { + // return created data in response + // docs: https://learn.microsoft.com/en-us/previous-versions/dynamicscrm-2016/developers-guide/gg328090(v=crm.8)#create-with-data-returned + Prefer: 'return=representation', + }, + undefined, + request + ); + } + + async updatedRecord( + entityUrlPath: string, + recordId: string, + request: unknown + ) { + return await this.makeRequest( + HttpMethod.PATCH, + `/${entityUrlPath}(${recordId})`, + { + // return created data in response + // docs: https://learn.microsoft.com/en-us/previous-versions/dynamicscrm-2016/developers-guide/mt607664(v=crm.8)#update-with-data-returned + Prefer: 'return=representation', + }, + undefined, + request + ); + } + + async getRecord(entityUrlPath: string, recordId: string) { + return await this.makeRequest( + HttpMethod.GET, + `/${entityUrlPath}(${recordId})` + ); + } + + async deleteRecord(entityUrlPath: string, recordId: string) { + return await this.makeRequest( + HttpMethod.DELETE, + `/${entityUrlPath}(${recordId})` + ); + } + async fetchEntityTypes(): Promise { + // fetch entity type data + return await this.makeRequest( + HttpMethod.GET, + `/EntityDefinitions`, + undefined, + { + $select: ['EntitySetName', 'LogicalName'].join(','), + } + ); + } + async fetchEntityTypeAttributes( + entitySetName: string + ): Promise { + return await this.makeRequest( + HttpMethod.GET, + `/EntityDefinitions`, + undefined, + { + $select: [ + 'PrimaryIdAttribute', + 'PrimaryNameAttribute', + 'LogicalName', + ].join(','), + $filter: `EntitySetName eq '${entitySetName}'`, + } + ); + } + async fetchEntityAttributes( + entityName: string + ): Promise { + // fetch entity attribute data + // docs: https://learn.microsoft.com/en-us/previous-versions/dynamicscrm-2016/developers-guide/mt788314(v=crm.8)#retrieve-metadata-items-by-name + return await this.makeRequest( + HttpMethod.GET, + `/EntityDefinitions(LogicalName='${entityName}')/Attributes`, + undefined, + { + $select: [ + 'AttributeType', + 'LogicalName', + 'Description', + 'DisplayName', + 'IsPrimaryName', + 'IsValidForCreate', + ].join(','), + } + ); + } + + async fetchOptionFieldValues( + entityName: string, + entityAttributeName: string, + entityAttributeType: EntityAttributeType + ): Promise<{ label: string; value: string | number }[]> { + const res = await this.makeRequest( + HttpMethod.GET, + `/EntityDefinitions(LogicalName='${entityName}')/Attributes(LogicalName='${entityAttributeName}')/Microsoft.Dynamics.CRM.${entityAttributeType}AttributeMetadata`, + undefined, + { + $select: 'LogicalName', + $expand: 'OptionSet($select=Options),GlobalOptionSet($select=Options)', + } + ); + const optionSet = res.OptionSet ?? res.GlobalOptionSet; + const options: { label: string; value: string | number }[] = + optionSet?.Options?.map(({ Value, Label }) => ({ + value: Value, + label: Label.UserLocalizedLabel.Label ?? String(Value), + })) || []; + return options; + } +} diff --git a/packages/pieces/community/microsoft-dynamics-crm/src/lib/common/constants.ts b/packages/pieces/community/microsoft-dynamics-crm/src/lib/common/constants.ts new file mode 100644 index 0000000..92e002b --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-crm/src/lib/common/constants.ts @@ -0,0 +1,21 @@ +// docs: https://learn.microsoft.com/en-us/previous-versions/dynamicscrm-2016/developers-guide/mt608066(v=crm.8) + +export enum EntityAttributeType { + PICKLIST = 'Picklist', + VIRTUAL = 'Virtual', + UNIQUE_IDENTIFIER = 'Uniqueidentifier', + STRING = 'String', + MEMO = 'Memo', + MONEY = 'Money', + DOUBLE = 'Double', + INTEGER = 'Integer', + LOOKUP = 'Lookup', + DATETIME = 'DateTime', + BOOLEAN = 'Boolean', + BIGINT = 'BigInt', + DECIMAL = 'Decimal', + OWNER = 'Owner', + ENTITY_NAME = 'EntityName', + STATE = 'State', + STATUS = 'Status', +} diff --git a/packages/pieces/community/microsoft-dynamics-crm/src/lib/common/index.ts b/packages/pieces/community/microsoft-dynamics-crm/src/lib/common/index.ts new file mode 100644 index 0000000..79b3d81 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-crm/src/lib/common/index.ts @@ -0,0 +1,226 @@ +import { + AuthenticationType, + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; +import { + DynamicPropsValue, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { dynamicsCRMAuth, getBaseUrl } from '../../'; +import { DynamicsCRMClient } from './client'; +import { EntityAttributeType } from './constants'; + +export function makeClient(auth: PiecePropValueSchema) { + const client = new DynamicsCRMClient( + auth.props?.['hostUrl'], + auth.access_token, + auth.props?.['proxyUrl'] + ); + return client; +} + +export const DynamicsCRMCommon = { + entityType: (description: string) => + Property.Dropdown({ + displayName: 'Entity Type', + refreshers: [], + description, + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: '', + }; + } + + const client = makeClient( + auth as PiecePropValueSchema + ); + + const res = await client.fetchEntityTypes(); + + return { + disabled: false, + options: res.value.map((val) => { + return { + label: val.EntitySetName, + value: val.EntitySetName, + }; + }), + }; + }, + }), + recordId: Property.Dropdown({ + displayName: 'Record ID', + refreshers: ['entityType'], + required: true, + options: async ({ auth, entityType }) => { + if (!auth || !entityType) { + return { + disabled: true, + options: [], + placeholder: 'Please select entity type first.', + }; + } + + const client = makeClient( + auth as PiecePropValueSchema + ); + + const res = await client.fetchEntityTypeAttributes(entityType as string); + + if (!res.value[0]) { + return { + disabled: true, + options: [], + placeholder: 'Please select entity type first.', + }; + } + + const entityUrlPath = entityType as string; + const entityPrimaryKey = res.value[0].PrimaryIdAttribute; + const entityprimaryNameAttribute = res.value[0].PrimaryNameAttribute; + + const authValue = auth as PiecePropValueSchema; + + type Response = { + '@odata.context': string; + value: Array<{ + [K in + | typeof entityprimaryNameAttribute + | typeof entityPrimaryKey]: string; + }>; + }; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${getBaseUrl( + authValue.props?.['hostUrl'], + authValue.props?.['proxyUrl'] + )}/api/data/v9.2/${entityUrlPath}`, + queryParams: { + $select: entityprimaryNameAttribute, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authValue.access_token, + }, + headers: { + Accept: 'application/json', + 'OData-MaxVersion': '4.0', + 'OData-Version': '4.0', + 'Content-Type': 'application/json', + }, + }; + + const { body } = await httpClient.sendRequest(request); + + return { + disabled: false, + options: body.value.map((val) => { + return { + label: val[entityprimaryNameAttribute] ?? val[entityPrimaryKey], + value: val[entityPrimaryKey], + }; + }), + }; + }, + }), + entityFields: (isCreate = true) => + Property.DynamicProperties({ + displayName: 'Entity Fields', + refreshers: ['auth', 'entityType'], + required: true, + props: async ({ auth, entityType }) => { + if (!auth) return {}; + if (!entityType) return {}; + + const fields: DynamicPropsValue = {}; + + const client = makeClient( + auth as PiecePropValueSchema + ); + + const typeRes = await client.fetchEntityTypeAttributes( + entityType as unknown as string + ); + + if (!typeRes.value[0]) { + return { + disabled: true, + options: [], + placeholder: 'Please select entity type first.', + }; + } + + const res = await client.fetchEntityAttributes( + typeRes.value[0].LogicalName + ); + + for (const field of res.value) { + if ( + field.IsValidForCreate && + ![ + EntityAttributeType.ENTITY_NAME, + EntityAttributeType.LOOKUP, + EntityAttributeType.MEMO, + EntityAttributeType.MONEY, + EntityAttributeType.OWNER, + EntityAttributeType.VIRTUAL, + EntityAttributeType.UNIQUE_IDENTIFIER, + ].includes(field.AttributeType) + ) { + const params = { + displayName: + field.DisplayName?.UserLocalizedLabel?.Label ?? + field.LogicalName, + description: field.Description?.UserLocalizedLabel?.Label ?? '', + required: field.IsPrimaryName && isCreate, + }; + switch (field.AttributeType) { + case EntityAttributeType.BIGINT: + case EntityAttributeType.DECIMAL: + case EntityAttributeType.DOUBLE: + case EntityAttributeType.INTEGER: + fields[field.LogicalName] = Property.Number(params); + break; + case EntityAttributeType.DATETIME: + fields[field.LogicalName] = Property.DateTime(params); + break; + case EntityAttributeType.BOOLEAN: + fields[field.LogicalName] = Property.Checkbox(params); + break; + case EntityAttributeType.STRING: + fields[field.LogicalName] = Property.ShortText(params); + break; + case EntityAttributeType.PICKLIST: + case EntityAttributeType.STATE: + case EntityAttributeType.STATUS: { + const options = await client.fetchOptionFieldValues( + typeRes.value[0].LogicalName, + field.LogicalName, + field.AttributeType + ); + fields[field.LogicalName] = Property.StaticDropdown({ + ...params, + options: { + disabled: false, + options: options, + }, + }); + break; + } + default: + break; + } + } + } + return fields; + }, + }), +}; diff --git a/packages/pieces/community/microsoft-dynamics-crm/src/lib/common/types.ts b/packages/pieces/community/microsoft-dynamics-crm/src/lib/common/types.ts new file mode 100644 index 0000000..2c4c909 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-crm/src/lib/common/types.ts @@ -0,0 +1,85 @@ +import { EntityAttributeType } from './constants'; + +export type EntityTypeResponse = { + '@odata.context': string; + value: { + '@odata.type': string; + LogicalName: string; + EntitySetName: string; + }[]; +}; + +export type EntityTypeAttributesResponse = { + '@odata.context': string; + value: { + '@odata.type': string; + PrimaryIdAttribute: string; + PrimaryNameAttribute: string; + LogicalName: string; + }[]; +}; +export type EntityAttributeResponse = { + '@odata.context': string; + value: { + '@odata.type': string; + AttributeType: EntityAttributeType; + LogicalName: string; + MetadataId: string; + IsValidForCreate: boolean; + IsPrimaryName: boolean; + Description: { + UserLocalizedLabel: { + Label: string; + LanguageCode: number; + IsManaged: boolean; + MetadataId: string; + HasChanged: boolean; + } | null; + } | null; + DisplayName: { + UserLocalizedLabel: { + Label: string; + LanguageCode: number; + IsManaged: boolean; + MetadataId: string; + HasChanged: boolean; + } | null; + } | null; + }[]; +}; + +export type EntityAttributeOptionsResponse = { + '@odata.context': string; + LogicalName: string; + MetadataId: string; + OptionSet: { + MetadataId: string; + Options: { + Value: number; + Label: { + UserLocalizedLabel: { + Label: string; + LanguageCode: number; + IsManaged: boolean; + MetadataId: string; + HasChanged: boolean; + }; + }; + }[]; + } | null; + GlobalOptionSet: { + MetadataId: string; + Options: { + Value: number; + Label: { + UserLocalizedLabel: { + Label: string; + LanguageCode: number; + IsManaged: boolean; + MetadataId: string; + HasChanged: boolean; + }; + }; + }[]; + } | null; +}; diff --git a/packages/pieces/community/microsoft-dynamics-crm/tsconfig.json b/packages/pieces/community/microsoft-dynamics-crm/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-crm/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/microsoft-dynamics-crm/tsconfig.lib.json b/packages/pieces/community/microsoft-dynamics-crm/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/microsoft-dynamics-crm/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/microsoft-excel-365/.eslintrc.json b/packages/pieces/community/microsoft-excel-365/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/microsoft-excel-365/README.md b/packages/pieces/community/microsoft-excel-365/README.md new file mode 100644 index 0000000..3929531 --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/README.md @@ -0,0 +1,7 @@ +# pieces-microsoft-excel-365 + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-microsoft-excel-365` to build the library. diff --git a/packages/pieces/community/microsoft-excel-365/package.json b/packages/pieces/community/microsoft-excel-365/package.json new file mode 100644 index 0000000..d84000d --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-microsoft-excel-365", + "version": "0.0.7" +} \ No newline at end of file diff --git a/packages/pieces/community/microsoft-excel-365/project.json b/packages/pieces/community/microsoft-excel-365/project.json new file mode 100644 index 0000000..af9389a --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-microsoft-excel-365", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/microsoft-excel-365/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/microsoft-excel-365", + "tsConfig": "packages/pieces/community/microsoft-excel-365/tsconfig.lib.json", + "packageJson": "packages/pieces/community/microsoft-excel-365/package.json", + "main": "packages/pieces/community/microsoft-excel-365/src/index.ts", + "assets": [ + "packages/pieces/community/microsoft-excel-365/*.md", + { + "input": "packages/pieces/community/microsoft-excel-365/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-microsoft-excel-365 {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/microsoft-excel-365/src/index.ts b/packages/pieces/community/microsoft-excel-365/src/index.ts new file mode 100644 index 0000000..1a985af --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/index.ts @@ -0,0 +1,70 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + createPiece, + OAuth2PropertyValue, + PieceAuth, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { addWorksheetAction } from './lib/actions/add-worksheet'; +import { appendRowAction } from './lib/actions/append-row'; +import { appendTableRowsAction } from './lib/actions/append-table-rows'; +import { clearWorksheetAction } from './lib/actions/clear-worksheet'; +import { convertToRangeAction } from './lib/actions/convert-to-range'; +import { createTableAction } from './lib/actions/create-table'; +import { deleteTableAction } from './lib/actions/delete-table'; +import { deleteWorkbookAction } from './lib/actions/delete-workbook'; +import { deleteWorksheetAction } from './lib/actions/delete-worksheet'; +import { getTableColumnsAction } from './lib/actions/get-table-columns'; +import { getTableRowsAction } from './lib/actions/get-table-rows'; +import { getWorkbooksAction } from './lib/actions/get-workbooks'; +import { getWorksheetRowsAction } from './lib/actions/get-worksheet-rows'; +import { getWorksheetsAction } from './lib/actions/get-worksheets'; +import { lookupTableColumnAction } from './lib/actions/lookup-table-column'; +import { updateRowAction } from './lib/actions/update-row'; +import { excelCommon } from './lib/common/common'; +import { readNewRows } from './lib/trigger/new-row-added'; + +export const excelAuth = PieceAuth.OAuth2({ + description: 'Authentication for Microsoft Excel 365', + authUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', + tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + required: true, + scope: ['Files.ReadWrite', 'offline_access'], +}); + +export const microsoftExcel = createPiece({ + displayName: 'Microsoft Excel 365', + description: 'Spreadsheet software by Microsoft', + + auth: excelAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/microsoft-excel-365.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: ["BastienMe","kishanprmr","MoShizzle","abuaboud"], + actions: [ + appendRowAction, + getWorksheetsAction, + getWorksheetRowsAction, + updateRowAction, + clearWorksheetAction, + deleteWorksheetAction, + getWorkbooksAction, + deleteWorkbookAction, + addWorksheetAction, + getTableRowsAction, + getTableColumnsAction, + createTableAction, + deleteTableAction, + lookupTableColumnAction, + appendTableRowsAction, + convertToRangeAction, + createCustomApiCallAction({ + baseUrl: () => excelCommon.baseUrl, + auth: excelAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [readNewRows], +}); diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/actions/add-worksheet.ts b/packages/pieces/community/microsoft-excel-365/src/lib/actions/add-worksheet.ts new file mode 100644 index 0000000..6ad179b --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/actions/add-worksheet.ts @@ -0,0 +1,45 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, + HttpRequest, +} from '@activepieces/pieces-common'; +import { excelAuth } from '../../index'; +import { excelCommon } from '../common/common'; + +export const addWorksheetAction = createAction({ + auth: excelAuth, + name: 'add_worksheet', + description: 'Add a worksheet to a workbook', + displayName: 'Add a Worksheet to a Workbook', + props: { + workbook_id: excelCommon.workbook_id, + worksheet_name: Property.ShortText({ + displayName: 'Worksheet Name', + description: 'The name of the new worksheet', + required: false, + defaultValue: 'Sheet', + }), + }, + async run({ propsValue, auth }) { + const workbook_id = propsValue['workbook_id']; + const worksheet_name = propsValue['worksheet_name']; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${excelCommon.baseUrl}/items/${workbook_id}/workbook/worksheets`, + body: { + name: worksheet_name, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth['access_token'], + }, + }; + + const response = await httpClient.sendRequest(request); + + return response.body; + }, +}); diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/actions/append-row.ts b/packages/pieces/community/microsoft-excel-365/src/lib/actions/append-row.ts new file mode 100644 index 0000000..994a8de --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/actions/append-row.ts @@ -0,0 +1,66 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, + HttpRequest, +} from '@activepieces/pieces-common'; +import { excelAuth } from '../../index'; +import { excelCommon, objectToArray } from '../common/common'; + +export const appendRowAction = createAction({ + auth: excelAuth, + name: 'append_row', + description: 'Append row of values to a worksheet', + displayName: 'Append Row to Worksheet', + props: { + workbook_id: excelCommon.workbook_id, + worksheet_id: excelCommon.worksheet_id, + first_row_headers: Property.Checkbox({ + displayName: 'Does the first row contain headers?', + description: 'If the first row is headers', + required: true, + defaultValue: false, + }), + values: excelCommon.values, + }, + async run({ propsValue, auth }) { + const workbookId = propsValue['workbook_id']; + const worksheetId = propsValue['worksheet_id']; + const values = propsValue.first_row_headers + ? objectToArray(propsValue['values']) + : Object.values(propsValue['values']); + + const lastUsedRow = await excelCommon.getLastUsedRow( + workbookId, + worksheetId, + auth['access_token'], + ); + const lastUsedColumn = excelCommon.numberToColumnName(Object.values(values).length); + + const rangeFrom = `A${lastUsedRow + 1}`; + const rangeTo = `${lastUsedColumn}${lastUsedRow + 1}`; + + const url = `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${rangeFrom}:${rangeTo}')`; + + const requestBody = { + values: [values], + }; + + const request: HttpRequest = { + method: HttpMethod.PATCH, + url: url, + body: requestBody, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth['access_token'], + }, + headers: { + 'Content-Type': 'application/json', + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, +}); diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/actions/append-table-rows.ts b/packages/pieces/community/microsoft-excel-365/src/lib/actions/append-table-rows.ts new file mode 100644 index 0000000..6fc77aa --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/actions/append-table-rows.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { excelAuth } from '../..'; +import { excelCommon } from '../common/common'; + +export const appendTableRowsAction = createAction({ + auth: excelAuth, + name: 'append_table_rows', + description: 'Append rows to a table', + displayName: 'Append Rows to a Table', + props: { + workbook_id: excelCommon.workbook_id, + worksheet_id: excelCommon.worksheet_id, + table_id: excelCommon.table_id, + values: excelCommon.table_values, + }, + async run({ propsValue, auth }) { + const workbookId = propsValue['workbook_id']; + const worksheetId = propsValue['worksheet_id']; + const tableId = propsValue['table_id']; + const valuesToAppend = [Object.values(propsValue['values'])]; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`, + body: { + values: valuesToAppend, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth['access_token'], + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/actions/clear-worksheet.ts b/packages/pieces/community/microsoft-excel-365/src/lib/actions/clear-worksheet.ts new file mode 100644 index 0000000..334093b --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/actions/clear-worksheet.ts @@ -0,0 +1,54 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { excelAuth } from '../../index'; +import { excelCommon } from '../common/common'; + +export const clearWorksheetAction = createAction({ + auth: excelAuth, + name: 'clear_worksheet', + description: 'Clear a worksheet', + displayName: 'Clear Worksheet', + props: { + workbook_id: excelCommon.workbook_id, + worksheet_id: excelCommon.worksheet_id, + range: Property.ShortText({ + displayName: 'Range', + description: + 'The range in A1 notation (e.g., A2:B2) to clear in the worksheet, if not provided, clear the entire worksheet', + required: false, + }), + }, + async run({ propsValue, auth }) { + const workbookId = propsValue['workbook_id']; + const worksheetId = propsValue['worksheet_id']; + const range = propsValue['range']; + + let url = `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/`; + + // If range is not provided, clear the entire worksheet + if (!range) { + url += 'usedRange(valuesOnly=true)/clear'; + } else { + url += `range(address = '${range}')/clear`; + } + + const request = { + method: HttpMethod.POST, + url: url, + body: { + applyTo: 'contents', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN as const, + token: auth['access_token'], + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, +}); diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/actions/convert-to-range.ts b/packages/pieces/community/microsoft-excel-365/src/lib/actions/convert-to-range.ts new file mode 100644 index 0000000..743409a --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/actions/convert-to-range.ts @@ -0,0 +1,36 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { excelCommon } from '../common/common'; +import { excelAuth } from '../../index'; + +export const convertToRangeAction = createAction({ + auth: excelAuth, + name: 'convert_to_range', + description: 'Converts a table to a range', + displayName: 'Convert to Range', + props: { + workbook_id: excelCommon.workbook_id, + worksheet_id: excelCommon.worksheet_id, + table_id: excelCommon.table_id, + }, + async run({ propsValue, auth }) { + const workbookId = propsValue['workbook_id']; + const worksheetId = propsValue['worksheet_id']; + const tableId = propsValue['table_id']; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/convertToRange`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth['access_token'], + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/actions/create-table.ts b/packages/pieces/community/microsoft-excel-365/src/lib/actions/create-table.ts new file mode 100644 index 0000000..b1f49c6 --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/actions/create-table.ts @@ -0,0 +1,93 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { excelAuth } from '../../index'; +import { excelCommon } from '../common/common'; + +export const createTableAction = createAction({ + auth: excelAuth, + name: 'create_table', + description: 'Create a table in a worksheet', + displayName: 'Create Table', + props: { + workbook_id: excelCommon.workbook_id, + worksheet_id: excelCommon.worksheet_id, + selectRange: Property.Dropdown({ + displayName: 'Select Range', + description: 'How to select the range for the table', + required: true, + options: async () => { + return { + disabled: false, + options: [ + { + label: 'Automatically', + value: 'auto', + }, + { + label: 'Manually', + value: 'manual', + }, + ], + defaultValue: 'auto', + }; + }, + refreshers: [], + }), + range: Property.ShortText({ + displayName: 'Range', + description: + 'The range of cells in A1 notation (e.g., A2:B2) that will be converted to a table', + required: false, + defaultValue: 'A1:B2', + }), + hasHeaders: Property.Checkbox({ + displayName: 'Has Headers', + description: 'Whether the range has column labels', + required: true, + defaultValue: true, + }), + }, + async run({ propsValue, auth }) { + const workbookId = propsValue['workbook_id']; + const worksheetId = propsValue['worksheet_id']; + const selectRange = propsValue['selectRange']; + const hasHeaders = propsValue['hasHeaders']; + + let range: string | undefined; + if (selectRange === 'auto') { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth['access_token'], + }, + queryParams: { + select: 'address', + }, + }); + range = response.body['address'].split('!')[1]; + } else { + range = propsValue['range']; + } + + const result = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/add`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth['access_token'], + }, + body: { + address: range, + hasHeaders, + }, + }); + + return result.body; + }, +}); diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/actions/delete-table.ts b/packages/pieces/community/microsoft-excel-365/src/lib/actions/delete-table.ts new file mode 100644 index 0000000..6a61657 --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/actions/delete-table.ts @@ -0,0 +1,36 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { excelAuth } from '../../index'; +import { excelCommon } from '../common/common'; + +export const deleteTableAction = createAction({ + auth: excelAuth, + name: 'delete_table', + description: 'Delete a table from a worksheet', + displayName: 'Delete Table', + props: { + workbook_id: excelCommon.workbook_id, + worksheet_id: excelCommon.worksheet_id, + table_id: excelCommon.table_id, + }, + async run({ propsValue, auth }) { + const workbookId = propsValue['workbook_id']; + const worksheetId = propsValue['worksheet_id']; + const tableId = propsValue['table_id']; + + const response = await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth['access_token'], + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/actions/delete-workbook.ts b/packages/pieces/community/microsoft-excel-365/src/lib/actions/delete-workbook.ts new file mode 100644 index 0000000..89361f7 --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/actions/delete-workbook.ts @@ -0,0 +1,35 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, + HttpRequest, +} from '@activepieces/pieces-common'; +import { excelAuth } from '../../index'; +import { excelCommon } from '../common/common'; + +export const deleteWorkbookAction = createAction({ + auth: excelAuth, + name: 'delete_workbook', + description: 'Delete a workbook', + displayName: 'Delete Workbook', + props: { + workbook_id: excelCommon.workbook_id, + }, + async run({ propsValue, auth }) { + const workbookId = propsValue['workbook_id']; + const accessToken = auth['access_token']; + + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `${excelCommon.baseUrl}/items/${workbookId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }; + + await httpClient.sendRequest(request); + return { success: true }; + }, +}); diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/actions/delete-worksheet.ts b/packages/pieces/community/microsoft-excel-365/src/lib/actions/delete-worksheet.ts new file mode 100644 index 0000000..5e8a820 --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/actions/delete-worksheet.ts @@ -0,0 +1,35 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { excelAuth } from '../../index'; +import { excelCommon } from '../common/common'; + +export const deleteWorksheetAction = createAction({ + auth: excelAuth, + name: 'delete_worksheet', + description: 'Delete a worksheet in a workbook', + displayName: 'Delete Worksheet', + props: { + workbook_id: excelCommon.workbook_id, + worksheet_id: excelCommon.worksheet_id, + }, + async run({ propsValue, auth }) { + const workbookId = propsValue['workbook_id']; + const worksheetId = propsValue['worksheet_id']; + + const request = { + method: HttpMethod.DELETE, + url: `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN as const, + token: auth['access_token'], + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, +}); diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/actions/get-table-columns.ts b/packages/pieces/community/microsoft-excel-365/src/lib/actions/get-table-columns.ts new file mode 100644 index 0000000..8680a8d --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/actions/get-table-columns.ts @@ -0,0 +1,52 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { excelCommon } from '../common/common'; +import { excelAuth } from '../..'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; + +export const getTableColumnsAction = createAction({ + auth: excelAuth, + name: 'get_table_columns', + description: 'List columns of a table in a worksheet', + displayName: 'Get Table Columns', + props: { + workbook_id: excelCommon.workbook_id, + worksheet_id: excelCommon.worksheet_id, + table: excelCommon.table_id, + limit: Property.Number({ + displayName: 'Limit', + description: 'Limit the number of columns retrieved', + required: false, + }), + }, + async run({ propsValue, auth }) { + const workbookId = propsValue['workbook_id']; + const worksheetId = propsValue['worksheet_id']; + const tableId = propsValue['table']; + const limit = propsValue['limit']; + + let url = `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`; + + if (limit) { + url += `?$top=${limit}`; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: url, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth['access_token'], + }, + }); + + const columnNames = response.body['value'].map( + (column: { name: any }) => column.name + ); + + return columnNames; + }, +}); diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/actions/get-table-rows.ts b/packages/pieces/community/microsoft-excel-365/src/lib/actions/get-table-rows.ts new file mode 100644 index 0000000..cb378b0 --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/actions/get-table-rows.ts @@ -0,0 +1,52 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { excelCommon } from '../common/common'; +import { excelAuth } from '../..'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; + +export const getTableRowsAction = createAction({ + auth: excelAuth, + name: 'get_table_rows', + description: 'List rows of a table in a worksheet', + displayName: 'Get Table Rows', + props: { + workbook_id: excelCommon.workbook_id, + worksheet_id: excelCommon.worksheet_id, + table: excelCommon.table_id, + limit: Property.Number({ + displayName: 'Limit', + description: 'Limit the number of rows retrieved', + required: false, + }), + }, + async run({ propsValue, auth }) { + const workbookId = propsValue['workbook_id']; + const worksheetId = propsValue['worksheet_id']; + const tableId = propsValue['table']; + const limit = propsValue['limit']; + + let url = `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`; + + if (limit) { + url += `?$top=${limit}`; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: url, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth['access_token'], + }, + }); + + const rowsValues = response.body['value'].map( + (row: { values: any[] }) => row.values[0] + ); + + return rowsValues; + }, +}); diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/actions/get-workbooks.ts b/packages/pieces/community/microsoft-excel-365/src/lib/actions/get-workbooks.ts new file mode 100644 index 0000000..68f8a14 --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/actions/get-workbooks.ts @@ -0,0 +1,56 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { excelAuth } from '../../index'; +import { excelCommon } from '../common/common'; + +export const getWorkbooksAction = createAction({ + auth: excelAuth, + name: 'get_workbooks', + description: 'Retrieve a list of workbooks', + displayName: 'Get Workbooks', + props: { + limit: Property.Number({ + displayName: 'Limit', + description: + 'Limits the number of workbooks returned, returns all workbooks if empty', + required: false, + }), + }, + async run({ propsValue, auth }) { + const limit = propsValue['limit']; + + const queryParams: any = { + $filter: + "file ne null and file/mimeType eq 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'", + }; + + if (limit !== null && limit !== undefined) { + queryParams.$top = limit.toString(); + } + + const request = { + method: HttpMethod.GET, + url: `${excelCommon.baseUrl}/root/search(q='.xlsx')`, + authentication: { + type: AuthenticationType.BEARER_TOKEN as const, + token: auth['access_token'], + }, + queryParams: queryParams, + }; + + const response = await httpClient.sendRequest(request); + const workbooks = response.body['value'].map( + (item: { id: any; name: any; webUrl: any }) => ({ + id: item.id, + name: item.name, + webUrl: item.webUrl, + }) + ); + + return workbooks; + }, +}); diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/actions/get-worksheet-rows.ts b/packages/pieces/community/microsoft-excel-365/src/lib/actions/get-worksheet-rows.ts new file mode 100644 index 0000000..7e53f28 --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/actions/get-worksheet-rows.ts @@ -0,0 +1,73 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { excelCommon } from '../common/common'; +import { excelAuth } from '../../index'; + +export const getWorksheetRowsAction = createAction({ + auth: excelAuth, + name: 'get_worksheet_rows', + description: 'Retrieve rows from a worksheet', + displayName: 'Get Worksheet Rows', + props: { + workbook_id: excelCommon.workbook_id, + worksheet_id: excelCommon.worksheet_id, + range: Property.ShortText({ + displayName: 'Range', + description: 'Range of the rows to retrieve (e.g., A2:B2)', + required: false, + }), + headerRow: Property.Number({ + displayName: 'Header Row', + description: 'Row number of the header', + required: false, + }), + firstDataRow: Property.Number({ + displayName: 'First Data Row', + description: 'Row number of the first data row', + required: false, + }), + }, + async run({ propsValue, auth }) { + const workbookId = propsValue['workbook_id']; + const worksheetId = propsValue['worksheet_id']; + const range = propsValue['range']; + const headerRow = propsValue['headerRow']; + const firstDataRow = propsValue['firstDataRow']; + + let url = `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/`; + + if (!range) { + url += 'usedRange(valuesOnly=true)'; + } else { + url += `range(address = '${range}')`; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: url, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth['access_token'], + }, + }); + + const rows = response.body['values']; + if (headerRow && firstDataRow) { + return rows.slice(firstDataRow - 1).map((row: any[]) => { + const obj: { [key: string]: any } = {}; + rows[headerRow - 1].forEach( + (header: any, colIndex: string | number) => { + obj[String(header)] = row[Number(colIndex)]; + } + ); + return obj; + }); + } + + return rows; + }, +}); diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/actions/get-worksheets.ts b/packages/pieces/community/microsoft-excel-365/src/lib/actions/get-worksheets.ts new file mode 100644 index 0000000..f04f023 --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/actions/get-worksheets.ts @@ -0,0 +1,59 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { excelAuth } from '../../index'; +import { excelCommon } from '../common/common'; + +export const getWorksheetsAction = createAction({ + auth: excelAuth, + name: 'get_worksheets', + description: 'Retrieve worksheets from a workbook', + displayName: 'Get Worksheets', + props: { + workbook: excelCommon.workbook_id, + returnAll: Property.Checkbox({ + displayName: 'Return All', + description: 'If checked, all worksheets will be returned', + required: false, + defaultValue: false, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Limit the number of worksheets returned', + required: false, + defaultValue: 10, + }), + }, + async run({ propsValue, auth }) { + const workbookId = propsValue['workbook']; + const returnAll = propsValue['returnAll']; + const limit = propsValue['limit']; + + const endpoint = `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets`; + const headers = { + Authorization: `Bearer ${auth['access_token']}`, + 'Content-Type': 'application/json', + }; + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: endpoint, + headers: headers, + }); + + if (response.status !== 200) { + throw new Error(`Failed to retrieve worksheet: ${response.body}`); + } + + const worksheets = response.body['value']; + + if (returnAll) { + return worksheets; + } else { + const limitedWorksheets = []; + for (let i = 0; i < Math.min(worksheets['length'], limit ?? 0); i++) { + limitedWorksheets.push(worksheets[i]); + } + return limitedWorksheets; + } + }, +}); diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/actions/lookup-table-column.ts b/packages/pieces/community/microsoft-excel-365/src/lib/actions/lookup-table-column.ts new file mode 100644 index 0000000..c322ae5 --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/actions/lookup-table-column.ts @@ -0,0 +1,88 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { excelAuth } from '../..'; +import { excelCommon } from '../common/common'; + +export const lookupTableColumnAction = createAction({ + auth: excelAuth, + name: 'lookup_table_column', + description: 'Lookup a value in a table column in a worksheet', + displayName: 'Lookup Table Column', + props: { + workbook_id: excelCommon.workbook_id, + worksheet_id: excelCommon.worksheet_id, + table_id: excelCommon.table_id, + lookup_column: Property.ShortText({ + displayName: 'Lookup Column', + description: 'The column name to lookup the value in', + required: true, + }), + lookup_value: Property.ShortText({ + displayName: 'Lookup Value', + description: 'The value to lookup', + required: true, + }), + return_all_matches: Property.Checkbox({ + displayName: 'Return All Matches', + description: 'If checked, all matching rows will be returned', + required: false, + defaultValue: false, + }), + }, + async run({ propsValue, auth }) { + const workbookId = propsValue['workbook_id']; + const worksheetId = propsValue['worksheet_id']; + const tableName = propsValue['table_id']; + const lookupColumn = propsValue['lookup_column']; + const lookupValue = propsValue['lookup_value']; + const returnAllMatches = propsValue['return_all_matches']; + + const rowsUrl = `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableName}/rows`; + const rowsResponse = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: rowsUrl, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth['access_token'], + }, + }); + + const columnsUrl = `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableName}/columns`; + const columnsResponse = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: columnsUrl, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth['access_token'], + }, + }); + + const columns = columnsResponse.body['value']; + const columnIndex = columns.findIndex( + (column: any) => column.name === lookupColumn + ); + + if (columnIndex === -1) { + throw new Error(`Column "${lookupColumn}" not found in the table.`); + } + + const rows = rowsResponse.body['value']; + const matchedRows = rows.filter( + (row: any) => row.values[0][columnIndex] === lookupValue + ); + + const matchedValues = matchedRows.map( + (row: { values: any[] }) => row.values[0] + ); + + if (returnAllMatches) { + return matchedValues; + } else { + return matchedValues[0] || null; + } + }, +}); diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/actions/update-row.ts b/packages/pieces/community/microsoft-excel-365/src/lib/actions/update-row.ts new file mode 100644 index 0000000..80e7717 --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/actions/update-row.ts @@ -0,0 +1,58 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod, AuthenticationType } from '@activepieces/pieces-common'; +import { excelAuth } from '../../index'; +import { excelCommon, objectToArray } from '../common/common'; + +export const updateRowAction = createAction({ + auth: excelAuth, + name: 'update_row', + description: 'Update a row in a worksheet', + displayName: 'Update Worksheet Rows', + props: { + workbook_id: excelCommon.workbook_id, + worksheet_id: excelCommon.worksheet_id, + row_number: Property.Number({ + displayName: 'Row number', + description: 'The row number to update', + required: true, + }), + first_row_headers: Property.Checkbox({ + displayName: 'Does the first row contain headers?', + description: 'If the first row is headers', + required: true, + defaultValue: false, + }), + values: excelCommon.values, + }, + async run({ propsValue, auth }) { + const workbookId = propsValue['workbook_id']; + const worksheetId = propsValue['worksheet_id']; + const rowNumber = propsValue['row_number']; + const values = propsValue.first_row_headers + ? objectToArray(propsValue['values']) + : Object.values(propsValue['values']); + + const requestBody = { + values: [values], + }; + + const lastUsedColumn = excelCommon.numberToColumnName(Object.values(values).length); + + const rangeFrom = `A${rowNumber}`; + const rangeTo = `${lastUsedColumn}${rowNumber}`; + + const request = { + method: HttpMethod.PATCH, + url: `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${rangeFrom}:${rangeTo}')`, + body: requestBody, + authentication: { + type: AuthenticationType.BEARER_TOKEN as const, + token: auth['access_token'], + }, + }; + + const response = await httpClient.sendRequest(request); + + return response.body; + }, +}); diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/common/common.ts b/packages/pieces/community/microsoft-excel-365/src/lib/common/common.ts new file mode 100644 index 0000000..64991b8 --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/common/common.ts @@ -0,0 +1,309 @@ +import { Property, OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, + HttpRequest, + getAccessTokenOrThrow, +} from '@activepieces/pieces-common'; + +export const excelCommon = { + baseUrl: 'https://graph.microsoft.com/v1.0/me/drive', + workbook_id: Property.Dropdown({ + displayName: 'Workbook', + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + } + const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue; + const workbooks: { id: string; name: string }[] = ( + await httpClient.sendRequest<{ value: { id: string; name: string }[] }>({ + method: HttpMethod.GET, + url: `${excelCommon.baseUrl}/items/root/search(q='.xlsx')?$select=id,name`, + // queryParams: { + // filter: + // "file ne null and file/mimeType eq 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'", + // }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp['access_token'], + }, + }) + ).body.value; + return { + disabled: false, + options: workbooks.map((workbook: { id: string; name: string }) => { + return { + label: workbook.name, + value: workbook.id, + }; + }), + }; + }, + refreshers: [], + }), + worksheet_id: Property.Dropdown({ + displayName: 'Worksheet', + required: true, + refreshers: ['workbook_id'], + options: async ({ auth, workbook_id }) => { + if (!auth || !workbook_id) { + return { + disabled: true, + options: [], + placeholder: 'Please select a workbook first', + }; + } + const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue; + const worksheets: { id: string; name: string }[] = ( + await httpClient.sendRequest<{ value: { id: string; name: string }[] }>({ + method: HttpMethod.GET, + url: `${excelCommon.baseUrl}/items/${workbook_id}/workbook/worksheets?$select=id,name`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp['access_token'], + }, + }) + ).body.value; + + return { + disabled: false, + options: worksheets.map((worksheet: { id: string; name: string }) => { + return { + label: worksheet.name, + value: worksheet.name, + }; + }), + }; + }, + }), + table_id: Property.Dropdown({ + displayName: 'Table', + required: true, + refreshers: ['workbook_id', 'worksheet_id'], + options: async ({ auth, workbook_id, worksheet_id }) => { + if (!auth || !workbook_id || !worksheet_id) { + return { + disabled: true, + options: [], + placeholder: 'Please select a workbook and worksheet first', + }; + } + const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue; + const tables: { id: string; name: string }[] = ( + await httpClient.sendRequest<{ value: { id: string; name: string }[] }>({ + method: HttpMethod.GET, + url: `${excelCommon.baseUrl}/items/${workbook_id}/workbook/worksheets/${worksheet_id}/tables`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp['access_token'], + }, + }) + ).body.value; + + return { + disabled: false, + options: tables.map((table: { id: string; name: string }) => { + return { + label: table.name, + value: table.id, + }; + }), + }; + }, + }), + values: Property.DynamicProperties({ + displayName: 'Values', + description: 'The values to insert', + required: true, + refreshers: ['workbook_id', 'worksheet_id', 'first_row_headers'], + props: async ({ auth, workbook_id, worksheet_id, first_row_headers }) => { + if ( + !auth || + (workbook_id ?? '').toString().length === 0 || + (worksheet_id ?? '').toString().length === 0 + ) { + return {}; + } + + const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue; + + if (!first_row_headers) { + return { + values: Property.Array({ + displayName: 'Values', + required: true, + }), + }; + } + const firstRow = await excelCommon.getHeaders( + workbook_id as unknown as string, + authProp['access_token'], + worksheet_id as unknown as string, + ); + + const properties: { + [key: string]: any; + } = {}; + for (const key in firstRow) { + properties[key] = Property.ShortText({ + displayName: firstRow[key].toString(), + description: firstRow[key].toString(), + required: false, + defaultValue: '', + }); + } + return properties; + }, + }), + table_values: Property.DynamicProperties({ + displayName: 'Values', + description: 'The values to insert', + required: true, + refreshers: ['workbook_id', 'worksheet_id', 'table_id'], + props: async ({ auth, workbook_id, worksheet_id, table_id }) => { + if ( + !auth || + (workbook_id ?? '').toString().length === 0 || + (worksheet_id ?? '').toString().length === 0 || + (worksheet_id ?? '').toString().length === 0 + ) { + return {}; + } + + const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue; + + const headers = await excelCommon.getTableHeaders( + workbook_id as unknown as string, + authProp['access_token'], + worksheet_id as unknown as string, + table_id as unknown as string, + ); + + const properties: { + [key: string]: any; + } = {}; + for (const key in headers) { + properties[key] = Property.ShortText({ + displayName: headers[key].toString(), + description: headers[key].toString(), + required: false, + defaultValue: '', + }); + } + return properties; + }, + }), + getHeaders: async function (workbookId: string, accessToken: string, worksheetId: string) { + const response = await httpClient.sendRequest<{ values: string[][] }>({ + method: HttpMethod.GET, + url: `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange(valuesOnly=true)`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }); + + return response.body.values[0]; + }, + getTableHeaders: async function ( + workbookId: string, + accessToken: string, + worksheetId: string, + tableId: string, + ) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }); + + const columnNames = response.body['value'].map((column: { name: any }) => column.name); + return columnNames; + }, + getLastUsedRow: async function ( + workbookId: string, + worksheetId: string, + accessToken: string, + ): Promise { + const url = `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange`; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: url, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }; + + const response = await httpClient.sendRequest(request); + const usedRange = response.body['address'].split('!')[1]; + const [, lastCell] = usedRange.split(':'); + const lastRow = parseInt(lastCell.match(/\d+/)[0], 10); + + return lastRow; + }, + getLastUsedColumn: async function ( + workbookId: string, + worksheetId: string, + accessToken: string, + ): Promise { + const url = `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange`; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: url, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }; + + const response = await httpClient.sendRequest(request); + const usedRange = response.body['address'].split('!')[1]; + const [, lastCell] = usedRange.split(':'); + const lastColumnLetter = lastCell.match(/[A-Z]+/)[0]; + + return lastColumnLetter; + }, + getAllRows: async function (workbookId: string, worksheetId: string, accessToken: string) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${excelCommon.baseUrl}/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange(valuesOnly=true)`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }); + + const rows = response.body['values']; + return rows; + }, + numberToColumnName: function (num: number): string { + let columnName = ''; + while (num > 0) { + const modulo = (num - 1) % 26; + columnName = String.fromCharCode(65 + modulo) + columnName; + num = Math.floor((num - modulo) / 26); + } + return columnName; + }, +}; + +export function objectToArray(obj: { [x: string]: any }) { + const maxIndex = Math.max(...Object.keys(obj).map(Number)); + const arr = new Array(maxIndex + 1).fill(null); + for (const key in obj) { + arr[Number(key)] = obj[key]; + } + return arr; +} diff --git a/packages/pieces/community/microsoft-excel-365/src/lib/trigger/new-row-added.ts b/packages/pieces/community/microsoft-excel-365/src/lib/trigger/new-row-added.ts new file mode 100644 index 0000000..3bf6df5 --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/src/lib/trigger/new-row-added.ts @@ -0,0 +1,112 @@ +import { + OAuth2PropertyValue, + Property, + createTrigger, +} from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { excelCommon } from '../common/common'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; +import { excelAuth } from '../..'; + +const polling: Polling< + OAuth2PropertyValue, + { + workbook_id: string; + worksheet_id: string; + max_rows_to_poll: number | undefined; + } +> = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ auth, propsValue, lastItemId }) => { + const fetchedValues = + (await excelCommon.getAllRows( + propsValue.workbook_id, + propsValue.worksheet_id, + auth.access_token + )) ?? []; + + const currentValues = fetchedValues.map((row: any[], rowIndex: number) => { + const rowObject: any = {}; + row.forEach((cell: any, cellIndex: number) => { + const columnName = String.fromCharCode(65 + cellIndex); + rowObject[columnName] = cell; + }); + return { + row: rowIndex + 1, + values: rowObject, + }; + }); + + const items = currentValues + .filter((f: any) => Object.keys(f.values).length > 0) + .map((item: any, index: number) => ({ + id: index + 1, + data: item, + })) + .filter( + (f: any) => isNil(lastItemId) || f.data.row > (lastItemId as number) + ); + + return items.reverse(); + }, +}; + +export const readNewRows = createTrigger({ + auth: excelAuth, + name: 'new_row', + displayName: 'New Row', + description: + 'Trigger when a new row is added, and it can include existing rows as well.', + props: { + workbook_id: excelCommon.workbook_id, + worksheet_id: excelCommon.worksheet_id, + max_rows_to_poll: Property.Number({ + displayName: 'Max Rows to Poll', + description: + 'The maximum number of rows to poll, the rest will be polled on the next run.', + required: false, + defaultValue: 10, + }), + }, + type: TriggerStrategy.POLLING, + sampleData: {}, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + maxItemsToPoll: Math.max( + 1, + Math.min(10, context.propsValue.max_rows_to_poll ?? 10) + ), + propsValue: context.propsValue, + files: context.files, + }); + }, + test: async (context) => { + return await pollingHelper.test(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, +}); diff --git a/packages/pieces/community/microsoft-excel-365/tsconfig.json b/packages/pieces/community/microsoft-excel-365/tsconfig.json new file mode 100644 index 0000000..75013d0 --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "importHelpers": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/microsoft-excel-365/tsconfig.lib.json b/packages/pieces/community/microsoft-excel-365/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/microsoft-excel-365/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/microsoft-onedrive/.eslintrc.json b/packages/pieces/community/microsoft-onedrive/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/microsoft-onedrive/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/microsoft-onedrive/README.md b/packages/pieces/community/microsoft-onedrive/README.md new file mode 100644 index 0000000..0655b96 --- /dev/null +++ b/packages/pieces/community/microsoft-onedrive/README.md @@ -0,0 +1,7 @@ +# pieces-microsoft-onedrive + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-microsoft-onedrive` to build the library. diff --git a/packages/pieces/community/microsoft-onedrive/package.json b/packages/pieces/community/microsoft-onedrive/package.json new file mode 100644 index 0000000..8904216 --- /dev/null +++ b/packages/pieces/community/microsoft-onedrive/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-microsoft-onedrive", + "version": "0.0.12" +} \ No newline at end of file diff --git a/packages/pieces/community/microsoft-onedrive/project.json b/packages/pieces/community/microsoft-onedrive/project.json new file mode 100644 index 0000000..d0d587e --- /dev/null +++ b/packages/pieces/community/microsoft-onedrive/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-microsoft-onedrive", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/microsoft-onedrive/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/microsoft-onedrive", + "tsConfig": "packages/pieces/community/microsoft-onedrive/tsconfig.lib.json", + "packageJson": "packages/pieces/community/microsoft-onedrive/package.json", + "main": "packages/pieces/community/microsoft-onedrive/src/index.ts", + "assets": [ + "packages/pieces/community/microsoft-onedrive/*.md", + { + "input": "packages/pieces/community/microsoft-onedrive/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-microsoft-onedrive {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/microsoft-onedrive/src/index.ts b/packages/pieces/community/microsoft-onedrive/src/index.ts new file mode 100644 index 0000000..c15e969 --- /dev/null +++ b/packages/pieces/community/microsoft-onedrive/src/index.ts @@ -0,0 +1,46 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + createPiece, + OAuth2PropertyValue, + PieceAuth, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { downloadFile } from './lib/actions/download-file'; +import { listFiles } from './lib/actions/list-files'; +import { listFolders } from './lib/actions/list-folders'; +import { uploadFile } from './lib/actions/upload-file'; +import { oneDriveCommon } from './lib/common/common'; +import { newFile } from './lib/triggers/new-file'; + +export const oneDriveAuth = PieceAuth.OAuth2({ + description: 'Authentication for Microsoft OneDrive', + authUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', + tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + required: true, + scope: ['Files.ReadWrite', 'offline_access'], +}); + +export const microsoftOneDrive = createPiece({ + displayName: 'Microsoft OneDrive', + description: 'Cloud storage by Microsoft', + + auth: oneDriveAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/oneDrive.png', + categories: [PieceCategory.CONTENT_AND_FILES], + authors: ["BastienMe","kishanprmr","MoShizzle","abuaboud","ikus060"], + actions: [ + uploadFile, + downloadFile, + listFiles, + listFolders, + createCustomApiCallAction({ + baseUrl: () => oneDriveCommon.baseUrl, + auth: oneDriveAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [newFile], +}); diff --git a/packages/pieces/community/microsoft-onedrive/src/lib/actions/download-file.ts b/packages/pieces/community/microsoft-onedrive/src/lib/actions/download-file.ts new file mode 100644 index 0000000..042ee24 --- /dev/null +++ b/packages/pieces/community/microsoft-onedrive/src/lib/actions/download-file.ts @@ -0,0 +1,67 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { oneDriveAuth } from '../../'; +import { oneDriveCommon } from '../common/common'; + +export const downloadFile = createAction({ + auth: oneDriveAuth, + name: 'download_file', + description: 'Download a file from your Microsoft OneDrive', + displayName: 'Download file', + props: { + fileId: Property.ShortText({ + displayName: 'File ID', + description: 'The ID of the file to download', + required: true, + }), + }, + async run(context) { + const fileId = context.propsValue.fileId; + + const fileDetails = await httpClient.sendRequest<{name:string}>({ + method:HttpMethod.GET, + url:`${oneDriveCommon.baseUrl}/items/${fileId}?$select=name`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }) + + const result = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${oneDriveCommon.baseUrl}/items/${fileId}/content`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + responseType:'arraybuffer' + }); + + const desiredHeaders = [ + 'content-length', + 'content-type', + 'content-location', + 'expires', + ]; + const filteredHeaders: any = {}; + + if (result.headers) { + for (const key of desiredHeaders) { + filteredHeaders[key] = result.headers[key]; + } + } + + return { + ...filteredHeaders, + data:await context.files.write({ + fileName: fileDetails.body.name, + data: Buffer.from(result.body), + }) + + } + }, +}); diff --git a/packages/pieces/community/microsoft-onedrive/src/lib/actions/list-files.ts b/packages/pieces/community/microsoft-onedrive/src/lib/actions/list-files.ts new file mode 100644 index 0000000..9034ff0 --- /dev/null +++ b/packages/pieces/community/microsoft-onedrive/src/lib/actions/list-files.ts @@ -0,0 +1,46 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { oneDriveAuth } from '../../'; +import { oneDriveCommon } from '../common/common'; +import { Client, PageCollection } from '@microsoft/microsoft-graph-client'; +import { DriveItem } from '@microsoft/microsoft-graph-types'; + +export const listFiles = createAction({ + auth: oneDriveAuth, + name: 'list_files', + description: 'List files in a OneDrive folder', + displayName: 'List Files', + props: { + markdown:oneDriveCommon.parentFolderInfo, + parentFolder: oneDriveCommon.parentFolder, + }, + async run(context) { + const endpoint = context.propsValue.parentFolder + ? `/me/drive/items/${context.propsValue.parentFolder}/children` + : `/me/drive/items/root/children`; + + const files = []; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(context.auth.access_token), + }, + }); + let response: PageCollection = await client.api(endpoint).get(); + + while (response.value.length > 0) { + for (const item of response.value as DriveItem[]) { + if (item.file) { + files.push(item); + } + } + + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + + return files; + }, +}); diff --git a/packages/pieces/community/microsoft-onedrive/src/lib/actions/list-folders.ts b/packages/pieces/community/microsoft-onedrive/src/lib/actions/list-folders.ts new file mode 100644 index 0000000..f00efea --- /dev/null +++ b/packages/pieces/community/microsoft-onedrive/src/lib/actions/list-folders.ts @@ -0,0 +1,33 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { oneDriveAuth } from '../../'; +import { oneDriveCommon } from '../common/common'; + +export const listFolders = createAction({ + auth: oneDriveAuth, + name: 'list_folders', + description: 'List folders in a OneDrive folder', + displayName: 'List Folders', + props: { + markdown:oneDriveCommon.parentFolderInfo, + parentFolder: oneDriveCommon.parentFolder, + }, + async run(context) { + const parentId = context.propsValue.parentFolder ?? 'root'; + + const result = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${oneDriveCommon.baseUrl}/items/${parentId}/children?$filter=folder ne null`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return result.body['value']; + }, +}); diff --git a/packages/pieces/community/microsoft-onedrive/src/lib/actions/upload-file.ts b/packages/pieces/community/microsoft-onedrive/src/lib/actions/upload-file.ts new file mode 100644 index 0000000..b1ddcd5 --- /dev/null +++ b/packages/pieces/community/microsoft-onedrive/src/lib/actions/upload-file.ts @@ -0,0 +1,110 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { oneDriveAuth } from '../../'; +import mime from 'mime-types'; +import { oneDriveCommon } from '../common/common'; + +const CHUNK_SIZE = 10485760; // Use 10MiB per chunk + +export const uploadFile = createAction({ + auth: oneDriveAuth, + name: 'upload_onedrive_file', + description: 'Upload a file to your Microsoft OneDrive with chunked upload if the file is larger than 4MiB', + displayName: 'Upload file', + props: { + fileName: Property.ShortText({ + displayName: 'File name', + description: 'The name the file should be saved as (e.g. file.txt)', + required: true, + }), + file: Property.File({ + displayName: 'File', + description: 'The file URL or base64 to upload', + required: true, + }), + markdown:oneDriveCommon.parentFolderInfo, + parentId: oneDriveCommon.parentFolder, + }, + async run(context) { + const fileData = context.propsValue.file; + const mimeTypeLookup = mime.lookup( + fileData.extension ? fileData.extension : '' + ); + const mimeType = mimeTypeLookup + ? mimeTypeLookup + : 'application/octet-stream'; // Fallback to a default MIME type + const encodedFilename = encodeURIComponent(context.propsValue.fileName); + const parentId = context.propsValue.parentId ?? 'root'; + + if (fileData.data.length <= 4 * 1024 * 1024) { + // If file is smaller than 4MiB, use simple upload + const base64Data = Buffer.from(fileData.base64, 'base64'); + const result = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: `${oneDriveCommon.baseUrl}/items/${parentId}:/${encodedFilename}:/content`, + body: base64Data, + headers: { + 'Content-Type': mimeType, + 'Content-length': base64Data.length.toString(), + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + return result.body; + } else { + // For files larger than 4MiB, use chunked upload + const session = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${oneDriveCommon.baseUrl}/items/${parentId}:/${encodedFilename}:/createUploadSession`, + body: { + item: { + '@microsoft.graph.conflictBehavior': 'replace', + name: context.propsValue.fileName, + }, + }, + headers: { + 'Content-Type': 'application/json', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + + const uploadUrl = session.body.uploadUrl; + let start = 0; + let end = CHUNK_SIZE - 1; + const fileSize = fileData.data.length; + let result; + while (start < fileSize) { + if (end >= fileSize) { + end = fileSize - 1; + } + + const chunk = fileData.data.slice(start, end + 1); + + result = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: uploadUrl, + body: chunk, + headers: { + 'Content-Length': chunk.length.toString(), + 'Content-Range': `bytes ${start}-${end}/${fileSize}`, + }, + }); + + start += CHUNK_SIZE; + end += CHUNK_SIZE; + } + + return result?.body; + } + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/microsoft-onedrive/src/lib/common/common.ts b/packages/pieces/community/microsoft-onedrive/src/lib/common/common.ts new file mode 100644 index 0000000..11fdb14 --- /dev/null +++ b/packages/pieces/community/microsoft-onedrive/src/lib/common/common.ts @@ -0,0 +1,149 @@ +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { MarkdownVariant } from '@activepieces/shared'; +import dayjs from 'dayjs'; + +export const oneDriveCommon = { + baseUrl: 'https://graph.microsoft.com/v1.0/me/drive', + + parentFolder: Property.Dropdown({ + displayName: 'Parent Folder', + required: false, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + } + + const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue; + let folders: { id: string; label: string }[] = []; + + try { + folders = await getFoldersRecursively(authProp, 'root', ''); + } catch (e) { + throw new Error(`Failed to get folders\nError:${e}`); + } + + return { + disabled: false, + options: folders.map((folder: { id: string; label: string }) => { + return { + label: folder.label, + value: folder.id, + }; + }), + }; + }, + }), + parentFolderInfo : Property.MarkDown({ + value: + `**Note**: If you can't find the folder in the dropdown list (which fetches up to 1000 folders), please click on the **(F)** and type the folder ID directly.\n + + you can find the folder ID in the OneDrive URL after **?id=**, e.g., "onedrive.live.com/?id=**folder-id**&cid=some-other-id" + + `, + variant:MarkdownVariant.INFO + }), + + async getFiles( + auth: OAuth2PropertyValue, + search?: { + parentFolder?: string; + createdTime?: string | number | Date; + createdTimeOp?: string; + } + ) { + let url = `${this.baseUrl}/items/root/children?$filter=folder eq null`; + if (search?.parentFolder) { + url = `${this.baseUrl}/items/${search.parentFolder}/children?$filter=folder eq null`; + } + + const response = await httpClient.sendRequest<{ + value: { id: string; name: string; createdDateTime: string }[]; + }>({ + method: HttpMethod.GET, + url: url, + queryParams: { + $select: 'id,name,createdDateTime', + $orderby: 'createdDateTime asc', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }); + + const files = response.body.value; + + if (search?.createdTime) { + const compareDate = dayjs(search.createdTime); + return files.filter((file) => { + const fileDate = dayjs(file.createdDateTime); + const comparison = + search.createdTimeOp === '<' + ? fileDate.isBefore(compareDate) + : fileDate.isAfter(compareDate); + return comparison; + }); + } + + return files; + }, +}; + +async function getFoldersRecursively( + auth: OAuth2PropertyValue, + folderId: string, + parentPath = '', + result: { label: string; id: string }[] = [] +) { + // Stop recursion if limit is reached + if (result.length >= 1000) { + return result; + } + + const url = `${oneDriveCommon.baseUrl}/items/${folderId}/children?$select=id,name,folder`; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }); + + + const items = response.body.value; + + const folders = items.filter((item) => item.folder); + + for (const folder of folders) { + const path = parentPath ? `${parentPath}/${folder.name}` : folder.name; + result.push({ label: path, id: folder.id }); + + if (folder.folder?.childCount && folder.folder.childCount > 0) { + await getFoldersRecursively(auth, folder.id, path, result); + } + } + } catch (e) { + throw new Error(`Failed to get folders\nError: ${e}`); + } + + return result; +} + +interface getFoldersResponse { + '@odata.nextLink'?: string; + '@odata.deltaLink'?: string; + value: { id: string; name: string; folder?: { childCount: number } }[]; +} diff --git a/packages/pieces/community/microsoft-onedrive/src/lib/triggers/new-file.ts b/packages/pieces/community/microsoft-onedrive/src/lib/triggers/new-file.ts new file mode 100644 index 0000000..bb6b759 --- /dev/null +++ b/packages/pieces/community/microsoft-onedrive/src/lib/triggers/new-file.ts @@ -0,0 +1,132 @@ +import { PiecePropValueSchema, Property, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import dayjs from 'dayjs'; +import { oneDriveAuth } from '../..'; +import { oneDriveCommon } from '../common/common'; +import { Client, PageCollection } from '@microsoft/microsoft-graph-client'; +import { DriveItem } from '@microsoft/microsoft-graph-types'; + +type Props = { + parentFolder?: string; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(auth.access_token), + }, + }); + + const files = []; + + const endpoint = propsValue.parentFolder + ? `/me/drive/items/${propsValue.parentFolder}/children` + : `/me/drive/items/root/children`; + let response: PageCollection = await client.api(endpoint).get(); + while (response.value.length > 0) { + for (const item of response.value as DriveItem[]) { + if (item.file) { + files.push(item); + } + } + + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + + files.sort((a, b) => { + const aDate = dayjs(a.createdDateTime); + const bDate = dayjs(b.createdDateTime); + return bDate.diff(aDate); + }); + + return files.map((file) => ({ + epochMilliSeconds: dayjs(file.createdDateTime).valueOf(), + data: file, + })); + }, +}; + +export const newFile = createTrigger({ + auth: oneDriveAuth, + name: 'new_file', + displayName: 'New File', + description: 'Trigger when a new file is uploaded.', + props: { + markdown:oneDriveCommon.parentFolderInfo, + parentFolder: oneDriveCommon.parentFolder, + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + id: '123456', + name: 'example.jpg', + createdDateTime: '2023-10-20T10:16:35.5Z', + cTag: '07NkI9QUVCNEY1QzU9ITEySi4yNTD', + eTag: '331E4899BE5BFA2!sccbdc3441b454cc0a13be0f6be58ca3d', + lastModifiedDateTime: '2023-10-20T10:16:35.5Z', + size: 53431, + createdBy: { + application: { + id: '00000000-0000-0000-0000-0000481710a4', + displayName: '4c5b-b112-36a304b66dad', + }, + user: { + email: 'john@outlook.com', + id: '0331E4899BE5BFA2', + displayName: 'John Doe', + }, + }, + lastModifiedBy: { + application: { + id: '00000000-0000-0000-0000-0000481710a4', + displayName: '36a304b66dad', + }, + user: { + email: 'john@outlook.com', + id: '0331E4899BE5BFA2', + displayName: 'John Doe', + }, + }, + parentReference: { + driveType: 'personal', + driveId: 'E4899BE5BFA2', + id: '48dd8265f06fd5e8024d', + name: 'child', + path: '/drive/root:/parent/child', + siteId: '043b2233-0eed-436a', + }, + file: { + mimeType: 'image/jpeg', + }, + fileSystemInfo: { + createdDateTime: '2025-01-22T09:30:10Z', + lastModifiedDateTime: '2025-01-22T09:30:12Z', + }, + }, +}); diff --git a/packages/pieces/community/microsoft-onedrive/tsconfig.json b/packages/pieces/community/microsoft-onedrive/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/microsoft-onedrive/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/microsoft-onedrive/tsconfig.lib.json b/packages/pieces/community/microsoft-onedrive/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/microsoft-onedrive/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/microsoft-outlook-calendar/.eslintrc.json b/packages/pieces/community/microsoft-outlook-calendar/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/microsoft-outlook-calendar/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/microsoft-outlook-calendar/README.md b/packages/pieces/community/microsoft-outlook-calendar/README.md new file mode 100644 index 0000000..e0febea --- /dev/null +++ b/packages/pieces/community/microsoft-outlook-calendar/README.md @@ -0,0 +1,7 @@ +# pieces-microsoft-outlook-calendar + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-microsoft-outlook-calendar` to build the library. diff --git a/packages/pieces/community/microsoft-outlook-calendar/package.json b/packages/pieces/community/microsoft-outlook-calendar/package.json new file mode 100644 index 0000000..11ef33a --- /dev/null +++ b/packages/pieces/community/microsoft-outlook-calendar/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-microsoft-outlook-calendar", + "version": "0.0.2" +} diff --git a/packages/pieces/community/microsoft-outlook-calendar/project.json b/packages/pieces/community/microsoft-outlook-calendar/project.json new file mode 100644 index 0000000..9f68afb --- /dev/null +++ b/packages/pieces/community/microsoft-outlook-calendar/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-microsoft-outlook-calendar", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/microsoft-outlook-calendar/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/microsoft-outlook-calendar", + "tsConfig": "packages/pieces/community/microsoft-outlook-calendar/tsconfig.lib.json", + "packageJson": "packages/pieces/community/microsoft-outlook-calendar/package.json", + "main": "packages/pieces/community/microsoft-outlook-calendar/src/index.ts", + "assets": [ + "packages/pieces/community/microsoft-outlook-calendar/*.md", + { + "input": "packages/pieces/community/microsoft-outlook-calendar/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/microsoft-outlook-calendar/src/index.ts b/packages/pieces/community/microsoft-outlook-calendar/src/index.ts new file mode 100644 index 0000000..80e240a --- /dev/null +++ b/packages/pieces/community/microsoft-outlook-calendar/src/index.ts @@ -0,0 +1,46 @@ +import { + createPiece, + OAuth2PropertyValue, + PieceAuth, +} from '@activepieces/pieces-framework'; +import { createEventAction } from './lib/actions/create-event'; +import { listEventsAction } from './lib/actions/list-events'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { outlookCalendarCommon } from './lib/common/common'; +import { deleteEventAction } from './lib/actions/delete-event'; +import { PieceCategory } from '@activepieces/shared'; + +export const outlookCalendarAuth = PieceAuth.OAuth2({ + description: 'Authentication for Microsoft Outlook', + authUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', + tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + required: true, + scope: ['User.Read', 'Calendars.ReadWrite', 'offline_access'], +}); + +export const microsoftOutlookCalendar = createPiece({ + displayName: "Microsoft Outlook Calendar", + description: 'Calendar software by Microsoft', + auth: outlookCalendarAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: "https://cdn.activepieces.com/pieces/microsoft-outlook.png", + categories: [PieceCategory.PRODUCTIVITY], + authors: ['antonyvigouret'], + actions: [ + createEventAction, + deleteEventAction, + listEventsAction, + createCustomApiCallAction({ + auth: outlookCalendarAuth, + baseUrl() { + return outlookCalendarCommon.baseUrl; + }, + authMapping: async (auth) => { + return { + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }; + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/microsoft-outlook-calendar/src/lib/actions/create-event.ts b/packages/pieces/community/microsoft-outlook-calendar/src/lib/actions/create-event.ts new file mode 100644 index 0000000..2ac615f --- /dev/null +++ b/packages/pieces/community/microsoft-outlook-calendar/src/lib/actions/create-event.ts @@ -0,0 +1,73 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { outlookCalendarAuth } from '../..'; +import { outlookCalendarCommon } from '../common/common'; +import dayjs from 'dayjs'; + +export const createEventAction = createAction({ + auth: outlookCalendarAuth, + name: 'create_event', + description: 'Create a new event in a calendar', + displayName: 'Create a new event in a calendar', + props: { + calendarId: outlookCalendarCommon.calendarDropdown, + title: Property.ShortText({ + displayName: 'Title of the event', + required: true, + }), + start: Property.DateTime({ + displayName: 'Start date time of the event', + required: true, + }), + end: Property.DateTime({ + displayName: 'End date time of the event', + description: "By default it'll be 30 min post start time", + required: false, + }), + timezone: outlookCalendarCommon.timezoneDropdown, + location: Property.ShortText({ + displayName: 'Location', + required: false, + }), + }, + async run({ propsValue, auth }) { + const startDateTime = dayjs(propsValue.start).format('YYYY-MM-DDTHH:mm:ss'); + const endTime = propsValue.end + ? propsValue.end + : dayjs(startDateTime).add(30, 'm'); + const endDateTime = dayjs(endTime).format('YYYY-MM-DDTHH:mm:ss'); + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${outlookCalendarCommon.baseUrl}/calendars/${propsValue.calendarId}/events`, + body: { + subject: propsValue.title, + body: {}, + start: { + dateTime: startDateTime, + timeZone: propsValue.timezone, + }, + end: { + dateTime: endDateTime, + timeZone: propsValue.timezone, + }, + location: { + displayName: propsValue.location, + }, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth['access_token'], + }, + }; + + const response = await httpClient.sendRequest(request); + + return response.body; + }, +}); diff --git a/packages/pieces/community/microsoft-outlook-calendar/src/lib/actions/delete-event.ts b/packages/pieces/community/microsoft-outlook-calendar/src/lib/actions/delete-event.ts new file mode 100644 index 0000000..00651e3 --- /dev/null +++ b/packages/pieces/community/microsoft-outlook-calendar/src/lib/actions/delete-event.ts @@ -0,0 +1,38 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { outlookCalendarAuth } from '../..'; +import { outlookCalendarCommon } from '../common/common'; + +export const deleteEventAction = createAction({ + auth: outlookCalendarAuth, + name: 'delete_event', + description: 'Delete an event in a calendar', + displayName: 'Delete an event in a calendar', + props: { + calendarId: outlookCalendarCommon.calendarDropdown, + eventId: Property.ShortText({ + displayName: 'Event ID', + required: true, + }), + }, + async run({ propsValue, auth }) { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `${outlookCalendarCommon.baseUrl}/calendars/${propsValue.calendarId}/events/${propsValue.eventId}`, + body: {}, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth['access_token'], + }, + }; + + const response = await httpClient.sendRequest(request); + + return response.body; + }, +}); diff --git a/packages/pieces/community/microsoft-outlook-calendar/src/lib/actions/list-events.ts b/packages/pieces/community/microsoft-outlook-calendar/src/lib/actions/list-events.ts new file mode 100644 index 0000000..aff6b52 --- /dev/null +++ b/packages/pieces/community/microsoft-outlook-calendar/src/lib/actions/list-events.ts @@ -0,0 +1,45 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { outlookCalendarAuth } from '../..'; +import { outlookCalendarCommon } from '../common/common'; + +export const listEventsAction = createAction({ + auth: outlookCalendarAuth, + name: 'list_events', + description: 'List events in a calendar', + displayName: 'List events in a calendar', + props: { + calendarId: outlookCalendarCommon.calendarDropdown, + filter: Property.LongText({ + displayName: 'Filter', + required: false, + description: + 'Search query filter, see: https://learn.microsoft.com/en-us/graph/filter-query-parameter', + }), + }, + async run({ propsValue, auth }) { + const queryParams: Record = {}; + + if (propsValue.filter) queryParams['$filter'] = propsValue.filter; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${outlookCalendarCommon.baseUrl}/calendars/${propsValue.calendarId}/events`, + queryParams, + body: {}, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth['access_token'], + }, + }; + + const response = await httpClient.sendRequest(request); + + return response.body; + }, +}); diff --git a/packages/pieces/community/microsoft-outlook-calendar/src/lib/common/common.ts b/packages/pieces/community/microsoft-outlook-calendar/src/lib/common/common.ts new file mode 100644 index 0000000..66570b6 --- /dev/null +++ b/packages/pieces/community/microsoft-outlook-calendar/src/lib/common/common.ts @@ -0,0 +1,84 @@ +import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; + +export const outlookCalendarCommon = { + baseUrl: 'https://graph.microsoft.com/v1.0/me', + calendarDropdown: Property.Dropdown({ + displayName: 'Calendar', + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + } + const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue; + const calendars: { id: string; name: string }[] = ( + await httpClient.sendRequest<{ value: { id: string; name: string }[] }>( + { + method: HttpMethod.GET, + url: `${outlookCalendarCommon.baseUrl}/calendars`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp['access_token'], + }, + } + ) + ).body.value; + return { + disabled: false, + options: calendars.map((calendar: { id: string; name: string }) => { + return { + label: calendar.name, + value: calendar.id, + }; + }), + }; + }, + refreshers: [], + }), + timezoneDropdown: Property.Dropdown({ + displayName: 'Timezone', + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + } + const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue; + const timezones: { displayName: string; alias: string }[] = ( + await httpClient.sendRequest<{ + value: { displayName: string; alias: string }[]; + }>({ + method: HttpMethod.GET, + url: `${outlookCalendarCommon.baseUrl}/outlook/supportedTimeZones`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp['access_token'], + }, + }) + ).body.value; + return { + disabled: false, + options: timezones.map( + (timezone: { displayName: string; alias: string }) => { + return { + label: timezone.displayName, + value: timezone.alias, + }; + } + ), + }; + }, + refreshers: [], + }), +}; diff --git a/packages/pieces/community/microsoft-outlook-calendar/tsconfig.json b/packages/pieces/community/microsoft-outlook-calendar/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/microsoft-outlook-calendar/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/microsoft-outlook-calendar/tsconfig.lib.json b/packages/pieces/community/microsoft-outlook-calendar/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/microsoft-outlook-calendar/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/microsoft-outlook/.eslintrc.json b/packages/pieces/community/microsoft-outlook/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/microsoft-outlook/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/microsoft-outlook/README.md b/packages/pieces/community/microsoft-outlook/README.md new file mode 100644 index 0000000..3f0ecc7 --- /dev/null +++ b/packages/pieces/community/microsoft-outlook/README.md @@ -0,0 +1,7 @@ +# pieces-microsoft-outlook + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-microsoft-outlook` to build the library. diff --git a/packages/pieces/community/microsoft-outlook/package.json b/packages/pieces/community/microsoft-outlook/package.json new file mode 100644 index 0000000..1b2a748 --- /dev/null +++ b/packages/pieces/community/microsoft-outlook/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-microsoft-outlook", + "version": "0.1.0" +} diff --git a/packages/pieces/community/microsoft-outlook/project.json b/packages/pieces/community/microsoft-outlook/project.json new file mode 100644 index 0000000..85b9065 --- /dev/null +++ b/packages/pieces/community/microsoft-outlook/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-microsoft-outlook", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/microsoft-outlook/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/microsoft-outlook", + "tsConfig": "packages/pieces/community/microsoft-outlook/tsconfig.lib.json", + "packageJson": "packages/pieces/community/microsoft-outlook/package.json", + "main": "packages/pieces/community/microsoft-outlook/src/index.ts", + "assets": [ + "packages/pieces/community/microsoft-outlook/*.md", + { + "input": "packages/pieces/community/microsoft-outlook/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/microsoft-outlook/src/index.ts b/packages/pieces/community/microsoft-outlook/src/index.ts new file mode 100644 index 0000000..f180e12 --- /dev/null +++ b/packages/pieces/community/microsoft-outlook/src/index.ts @@ -0,0 +1,29 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece, OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { downloadAttachmentAction } from './lib/actions/download-email-attachment'; +import { sendEmailAction } from './lib/actions/send-email'; +import { microsoftOutlookAuth } from './lib/common/auth'; +import { newEmailTrigger } from './lib/triggers/new-email'; +import { replyEmailAction } from './lib/actions/reply-email'; +export const microsoftOutlook = createPiece({ + displayName: 'Microsoft Outlook', + auth: microsoftOutlookAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/outlook.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: ['lucaslimasouza', 'kishanprmr'], + actions: [ + sendEmailAction, + downloadAttachmentAction, + replyEmailAction, + createCustomApiCallAction({ + auth: microsoftOutlookAuth, + baseUrl: () => 'https://graph.microsoft.com/v1.0/', + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [newEmailTrigger], +}); diff --git a/packages/pieces/community/microsoft-outlook/src/lib/actions/download-email-attachment.ts b/packages/pieces/community/microsoft-outlook/src/lib/actions/download-email-attachment.ts new file mode 100644 index 0000000..b026763 --- /dev/null +++ b/packages/pieces/community/microsoft-outlook/src/lib/actions/download-email-attachment.ts @@ -0,0 +1,48 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client, PageCollection } from '@microsoft/microsoft-graph-client'; +import { FileAttachment } from '@microsoft/microsoft-graph-types'; +import { microsoftOutlookAuth } from '../common/auth'; + +export const downloadAttachmentAction = createAction({ + auth: microsoftOutlookAuth, + name: 'downloadAttachment', + displayName: 'Download Attachment', + description: 'Download attachments from a specific email message.', + props: { + messageId: Property.ShortText({ + displayName: 'Message ID', + description: 'The ID of the email message containing the attachment.', + required: true, + }), + }, + async run(context) { + const { messageId } = context.propsValue; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(context.auth.access_token), + }, + }); + + const response: PageCollection = await client + .api(`/me/messages/${messageId}/attachments`) + .get(); + + const attachments = []; + + for (const attachment of response.value as FileAttachment[]) { + if (attachment.name && attachment.contentBytes) { + attachments.push({ + ...attachment, + file: await context.files.write({ + fileName: attachment.name || 'test.png', + data: Buffer.from(attachment.contentBytes, 'base64'), + }), + }); + } + } + + + return attachments; + }, +}); diff --git a/packages/pieces/community/microsoft-outlook/src/lib/actions/reply-email.ts b/packages/pieces/community/microsoft-outlook/src/lib/actions/reply-email.ts new file mode 100644 index 0000000..86bb0d4 --- /dev/null +++ b/packages/pieces/community/microsoft-outlook/src/lib/actions/reply-email.ts @@ -0,0 +1,124 @@ +import { ApFile, createAction, Property } from '@activepieces/pieces-framework'; +import { microsoftOutlookAuth } from '../common/auth'; +import { BodyType, Message } from '@microsoft/microsoft-graph-types'; +import { Client } from '@microsoft/microsoft-graph-client'; + +export const replyEmailAction = createAction({ + auth: microsoftOutlookAuth, + name: 'reply-email', + displayName: 'Reply to Email', + description: 'Reply to an outlook email.', + props: { + messageId: Property.ShortText({ + displayName: 'Message ID', + required: true, + }), + bodyFormat: Property.StaticDropdown({ + displayName: 'Body Format', + required: true, + defaultValue: 'text', + options: { + disabled: false, + options: [ + { label: 'HTML', value: 'html' }, + { label: 'Text', value: 'text' }, + ], + }, + }), + replyBody: Property.LongText({ + displayName: 'Reply Body', + required: true, + }), + ccRecipients: Property.Array({ + displayName: 'CC Recipients', + required: false, + }), + bccRecipients: Property.Array({ + displayName: 'BCC Recipients', + required: false, + }), + attachments: Property.Array({ + displayName: 'Attachments', + required: false, + defaultValue: [], + properties: { + file: Property.File({ + displayName: 'File', + required: true, + }), + fileName: Property.ShortText({ + displayName: 'File Name', + required: false, + }), + }, + }), + draft: Property.Checkbox({ + displayName: 'Create Draft', + description: 'If enabled, creates draft without sending.', + required: true, + defaultValue: false, + }), + }, + async run(context) { + const { replyBody, bodyFormat, messageId, draft } = context.propsValue; + const ccRecipients = context.propsValue.ccRecipients as string[]; + const bccRecipients = context.propsValue.bccRecipients as string[]; + const attachments = context.propsValue.attachments as Array<{ + file: ApFile; + fileName: string; + }>; + const mailPayload: Message = { + body: { + content: replyBody, + contentType: bodyFormat as BodyType, + }, + ccRecipients: ccRecipients.map((mail) => ({ + emailAddress: { + address: mail, + }, + })), + bccRecipients: bccRecipients.map((mail) => ({ + emailAddress: { + address: mail, + }, + })), + attachments: attachments.map((attachment) => ({ + '@odata.type': '#microsoft.graph.fileAttachment', + name: attachment.fileName || attachment.file.filename, + contentBytes: attachment.file.base64, + })), + }; + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(context.auth.access_token), + }, + }); + try { + const response: Message = await client + .api(`/me/messages/${messageId}/createReply`) + .post({ + message: mailPayload, + }); + const draftId = response.id; + if (!draft) { + await client.api(`/me/messages/${draftId}/send`).post({}); + return { + success: true, + message: 'Reply sent successfully.', + draftId: draftId, + }; + } + return { + success: true, + message: 'Draft created successfully.', + draftId: draftId, + draftLink: `https://outlook.office.com/mail/drafts/id/${draftId}`, + }; + } catch (error) { + console.error('Reply Email Error:', error); + const errorMessage = + error instanceof Error ? error.message : 'Unknown error'; + throw new Error(errorMessage); + } + }, +}); diff --git a/packages/pieces/community/microsoft-outlook/src/lib/actions/send-email.ts b/packages/pieces/community/microsoft-outlook/src/lib/actions/send-email.ts new file mode 100644 index 0000000..ed475b7 --- /dev/null +++ b/packages/pieces/community/microsoft-outlook/src/lib/actions/send-email.ts @@ -0,0 +1,112 @@ +import { ApFile, createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@microsoft/microsoft-graph-client'; +import { BodyType, Message } from '@microsoft/microsoft-graph-types'; + +import { microsoftOutlookAuth } from '../common/auth'; + +export const sendEmailAction = createAction({ + auth: microsoftOutlookAuth, + name: 'send-email', + displayName: 'Send Email', + description: 'Sends an email using Microsoft Outlook.', + props: { + recipients: Property.Array({ + displayName: 'To Email(s)', + required: true, + }), + ccRecipients: Property.Array({ + displayName: 'CC Email(s)', + required: false, + defaultValue: [], + }), + bccRecipients: Property.Array({ + displayName: 'BCC Email(s)', + required: false, + defaultValue: [], + }), + subject: Property.ShortText({ + displayName: 'Subject', + required: true, + }), + bodyFormat: Property.StaticDropdown({ + displayName: 'Body Format', + required: true, + defaultValue: 'text', + options: { + disabled: false, + options: [ + { label: 'HTML', value: 'html' }, + { label: 'Text', value: 'text' }, + ], + }, + }), + body: Property.LongText({ + displayName: 'Body', + required: true, + }), + attachments: Property.Array({ + displayName: 'Attachments', + required: false, + defaultValue: [], + properties: { + file: Property.File({ + displayName: 'File', + required: true, + }), + fileName: Property.ShortText({ + displayName: 'File Name', + required: false, + }), + }, + }), + }, + async run(context) { + const recipients = context.propsValue.recipients as string[]; + const ccRecipients = context.propsValue.ccRecipients as string[]; + const bccRecipients = context.propsValue.bccRecipients as string[]; + const attachments = context.propsValue.attachments as Array<{ file: ApFile; fileName: string }>; + + const { subject, body, bodyFormat } = context.propsValue; + + const mailPayload: Message = { + subject, + body: { + content: body, + contentType: bodyFormat as BodyType, + }, + toRecipients: recipients.map((mail) => ({ + emailAddress: { + address: mail, + }, + })), + ccRecipients: ccRecipients.map((mail) => ({ + emailAddress: { + address: mail, + }, + })), + bccRecipients: bccRecipients.map((mail) => ({ + emailAddress: { + address: mail, + }, + })), + attachments: attachments.map((attachment) => ({ + '@odata.type': '#microsoft.graph.fileAttachment', + name: attachment.fileName || attachment.file.filename, + contentBytes: attachment.file.base64, + })), + }; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(context.auth.access_token), + }, + }); + + const response = await client.api('/me/sendMail').post({ + message: mailPayload, + saveToSentItems: 'true', + }); + + return response; + }, +}); diff --git a/packages/pieces/community/microsoft-outlook/src/lib/common/auth.ts b/packages/pieces/community/microsoft-outlook/src/lib/common/auth.ts new file mode 100644 index 0000000..2c8c165 --- /dev/null +++ b/packages/pieces/community/microsoft-outlook/src/lib/common/auth.ts @@ -0,0 +1,23 @@ +import { OAuth2PropertyValue, PieceAuth } from '@activepieces/pieces-framework'; +import { Client } from '@microsoft/microsoft-graph-client'; + +export const microsoftOutlookAuth = PieceAuth.OAuth2({ + authUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', + tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + required: true, + scope: ['Mail.ReadWrite', 'Mail.Send', 'Calendars.Read', 'offline_access', 'User.Read'], + validate: async ({ auth }) => { + try { + const authValue = auth as OAuth2PropertyValue; + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(authValue.access_token), + }, + }); + await client.api('/me').get(); + return { valid: true }; + } catch (error) { + return { valid: false, error: 'Invalid Credentials.' }; + } + }, +}); diff --git a/packages/pieces/community/microsoft-outlook/src/lib/triggers/new-email.ts b/packages/pieces/community/microsoft-outlook/src/lib/triggers/new-email.ts new file mode 100644 index 0000000..e17ffb7 --- /dev/null +++ b/packages/pieces/community/microsoft-outlook/src/lib/triggers/new-email.ts @@ -0,0 +1,86 @@ +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { + PiecePropValueSchema, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { Client, PageCollection } from '@microsoft/microsoft-graph-client'; +import { Message } from '@microsoft/microsoft-graph-types'; +import dayjs from 'dayjs'; +import { microsoftOutlookAuth } from '../common/auth'; + +const polling: Polling, Record> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, lastFetchEpochMS }) => { + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(auth.access_token), + }, + }); + + const messages = []; + + const filter = + lastFetchEpochMS === 0 + ? '$top=10' + : `$filter=receivedDateTime gt ${dayjs(lastFetchEpochMS).toISOString()}`; + + let response: PageCollection = await client + .api(`/me/mailFolders/inbox/messages?${filter}`) + .orderby('receivedDateTime desc') + .get(); + + if (lastFetchEpochMS === 0) { + for (const message of response.value as Message[]) { + messages.push(message); + } + } else { + while (response.value.length > 0) { + for (const message of response.value as Message[]) { + messages.push(message); + } + + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + } + + return messages.map((message) => ({ + epochMilliSeconds: dayjs(message.receivedDateTime).valueOf(), + data: message, + })); + }, +}; + +export const newEmailTrigger = createTrigger({ + auth: microsoftOutlookAuth, + name: 'newEmail', + displayName: 'New Email', + description: 'Triggers when a new email is received in the inbox.', + props: {}, + sampleData: {}, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, +}); diff --git a/packages/pieces/community/microsoft-outlook/tsconfig.json b/packages/pieces/community/microsoft-outlook/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/microsoft-outlook/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/microsoft-outlook/tsconfig.lib.json b/packages/pieces/community/microsoft-outlook/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/microsoft-outlook/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/microsoft-sharepoint/.eslintrc.json b/packages/pieces/community/microsoft-sharepoint/.eslintrc.json new file mode 100644 index 0000000..eac708a --- /dev/null +++ b/packages/pieces/community/microsoft-sharepoint/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/microsoft-sharepoint/README.md b/packages/pieces/community/microsoft-sharepoint/README.md new file mode 100644 index 0000000..dcd5e0d --- /dev/null +++ b/packages/pieces/community/microsoft-sharepoint/README.md @@ -0,0 +1,2 @@ +# pieces-microsoft-sharepoint + diff --git a/packages/pieces/community/microsoft-sharepoint/package.json b/packages/pieces/community/microsoft-sharepoint/package.json new file mode 100644 index 0000000..72ed50b --- /dev/null +++ b/packages/pieces/community/microsoft-sharepoint/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-microsoft-sharepoint", + "version": "0.0.6" +} diff --git a/packages/pieces/community/microsoft-sharepoint/project.json b/packages/pieces/community/microsoft-sharepoint/project.json new file mode 100644 index 0000000..154a8a4 --- /dev/null +++ b/packages/pieces/community/microsoft-sharepoint/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-microsoft-sharepoint", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/microsoft-sharepoint/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/microsoft-sharepoint", + "tsConfig": "packages/pieces/community/microsoft-sharepoint/tsconfig.lib.json", + "packageJson": "packages/pieces/community/microsoft-sharepoint/package.json", + "main": "packages/pieces/community/microsoft-sharepoint/src/index.ts", + "assets": [ + "packages/pieces/community/microsoft-sharepoint/*.md", + { + "input": "packages/pieces/community/microsoft-sharepoint/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-microsoft-sharepoint {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/microsoft-sharepoint/src/index.ts b/packages/pieces/community/microsoft-sharepoint/src/index.ts new file mode 100644 index 0000000..241d2aa --- /dev/null +++ b/packages/pieces/community/microsoft-sharepoint/src/index.ts @@ -0,0 +1,55 @@ +import { + createPiece, + PieceAuth, + OAuth2PropertyValue, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createFolderAction } from './lib/actions/create-folder'; +import { createListAction } from './lib/actions/create-list'; +import { createListItemAction } from './lib/actions/create-list-item'; +import { updateListItemAction } from './lib/actions/update-list-item'; +import { deleteListItemAction } from './lib/actions/delete-list-item'; +import { findListItemAction } from './lib/actions/search-list-item'; +import { uploadFile } from './lib/actions/upload-file'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; + +export const microsoftSharePointAuth = PieceAuth.OAuth2({ + description: 'Authentication for Microsoft SharePoint', + authUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', + tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + required: true, + scope: [ + 'openid', + 'email', + 'profile', + 'offline_access', + 'Sites.Manage.All', + 'Files.ReadWrite', + ], +}); + +export const microsoftSharePoint = createPiece({ + displayName: 'Microsoft SharePoint', + auth: microsoftSharePointAuth, + minimumSupportedRelease: '0.27.1', + logoUrl: 'https://cdn.activepieces.com/pieces/microsoft-sharepoint.png', + categories: [PieceCategory.CONTENT_AND_FILES], + authors: ['kishanprmr'], + actions: [ + createFolderAction, + createListAction, + createListItemAction, + updateListItemAction, + deleteListItemAction, + findListItemAction, + uploadFile, + createCustomApiCallAction({ + auth: microsoftSharePointAuth, + baseUrl: () => 'https://graph.microsoft.com/v1.0/sites', + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/microsoft-sharepoint/src/lib/actions/create-folder.ts b/packages/pieces/community/microsoft-sharepoint/src/lib/actions/create-folder.ts new file mode 100644 index 0000000..0f67543 --- /dev/null +++ b/packages/pieces/community/microsoft-sharepoint/src/lib/actions/create-folder.ts @@ -0,0 +1,41 @@ +import { microsoftSharePointAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { microsoftSharePointCommon } from '../common'; +import { Client } from '@microsoft/microsoft-graph-client'; + +export const createFolderAction = createAction({ + auth: microsoftSharePointAuth, + name: 'microsoft_sharepoint_create_folder', + displayName: 'Create Folder', + description: 'Creates a new folder at path you specify.', + props: { + siteId: microsoftSharePointCommon.siteId, + driveId: microsoftSharePointCommon.driveId, + parentFolder: Property.ShortText({ + displayName: 'Parent Folder', + description: `Parent folder,like "/demo/" or "/docs/assignment/".Leave it default if you want to create folder at the root (**/**) level.`, + required: true, + defaultValue: '/', + }), + folderName: Property.ShortText({ + displayName: 'Folder Name', + required: true, + }), + }, + async run(context) { + const { driveId, parentFolder, folderName } = context.propsValue; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(context.auth.access_token), + }, + }); + + const folderPath = parentFolder + folderName; + + // https://stackoverflow.com/questions/66631136/creating-nested-folder-in-sharepoint-with-graph-api-fails + return await client.api(`/drives/${driveId}/root:${folderPath}`).patch({ + folder: {}, + }); + }, +}); diff --git a/packages/pieces/community/microsoft-sharepoint/src/lib/actions/create-list-item.ts b/packages/pieces/community/microsoft-sharepoint/src/lib/actions/create-list-item.ts new file mode 100644 index 0000000..6be6c66 --- /dev/null +++ b/packages/pieces/community/microsoft-sharepoint/src/lib/actions/create-list-item.ts @@ -0,0 +1,40 @@ +import { microsoftSharePointAuth } from '../../'; +import { createAction } from '@activepieces/pieces-framework'; +import { microsoftSharePointCommon } from '../common'; +import { Client } from '@microsoft/microsoft-graph-client'; + +export const createListItemAction = createAction({ + auth: microsoftSharePointAuth, + name: 'microsoft_sharepoint_create_list_item', + displayName: 'Create List Item', + description: 'Creates a new item in a list.', + props: { + siteId: microsoftSharePointCommon.siteId, + listId: microsoftSharePointCommon.listId, + listColumns: microsoftSharePointCommon.listColumns, + }, + async run(context) { + const { siteId, listId, listColumns } = context.propsValue; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(context.auth.access_token), + }, + }); + const fieldWithArrayValues: Record = {}; + + Object.entries(listColumns).forEach(([key, value]) => { + // https://learn.microsoft.com/en-us/answers/questions/1517379/upload-multiple-choice-fields-item-in-sharepoint-w + if (Array.isArray(value)) { + fieldWithArrayValues[`${key}@odata.type`] = 'Collection(Edm.String)'; + } + }); + const itemInput = { + fields: { ...listColumns, ...fieldWithArrayValues }, + }; + + return await client + .api(`/sites/${siteId}/lists/${listId}/items`) + .post(itemInput); + }, +}); diff --git a/packages/pieces/community/microsoft-sharepoint/src/lib/actions/create-list.ts b/packages/pieces/community/microsoft-sharepoint/src/lib/actions/create-list.ts new file mode 100644 index 0000000..fef659e --- /dev/null +++ b/packages/pieces/community/microsoft-sharepoint/src/lib/actions/create-list.ts @@ -0,0 +1,39 @@ +import { microsoftSharePointAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { microsoftSharePointCommon } from '../common'; +import { Client } from '@microsoft/microsoft-graph-client'; +import { List } from '@microsoft/microsoft-graph-types'; + +export const createListAction = createAction({ + auth: microsoftSharePointAuth, + name: 'microsoft_sharepoint_create_list', + displayName: 'Create List', + description: 'Creates a new list.', + props: { + siteId: microsoftSharePointCommon.siteId, + displayName: Property.ShortText({ + displayName: 'List Name', + required: true, + }), + description: Property.ShortText({ + displayName: 'List Description', + required: true, + }), + }, + async run(context) { + const { siteId, displayName, description } = context.propsValue; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(context.auth.access_token), + }, + }); + + const listInput: List = { + displayName, + description, + }; + + return await client.api(`/sites/${siteId}/lists`).post(listInput); + }, +}); diff --git a/packages/pieces/community/microsoft-sharepoint/src/lib/actions/delete-list-item.ts b/packages/pieces/community/microsoft-sharepoint/src/lib/actions/delete-list-item.ts new file mode 100644 index 0000000..7e40261 --- /dev/null +++ b/packages/pieces/community/microsoft-sharepoint/src/lib/actions/delete-list-item.ts @@ -0,0 +1,29 @@ +import { microsoftSharePointAuth } from '../../'; +import { createAction } from '@activepieces/pieces-framework'; +import { microsoftSharePointCommon } from '../common'; +import { Client } from '@microsoft/microsoft-graph-client'; + +export const deleteListItemAction = createAction({ + auth: microsoftSharePointAuth, + name: 'microsoft_sharepoint_delete_list_item', + displayName: 'Delete List Item', + description: 'Deletes an existing item from a list.', + props: { + siteId: microsoftSharePointCommon.siteId, + listId: microsoftSharePointCommon.listId, + listItemId: microsoftSharePointCommon.listItemId, + }, + async run(context) { + const { siteId, listId, listItemId } = context.propsValue; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(context.auth.access_token), + }, + }); + + return await client + .api(`/sites/${siteId}/lists/${listId}/items/${listItemId}`) + .delete(); + }, +}); diff --git a/packages/pieces/community/microsoft-sharepoint/src/lib/actions/search-list-item.ts b/packages/pieces/community/microsoft-sharepoint/src/lib/actions/search-list-item.ts new file mode 100644 index 0000000..7cc9b76 --- /dev/null +++ b/packages/pieces/community/microsoft-sharepoint/src/lib/actions/search-list-item.ts @@ -0,0 +1,38 @@ +import { microsoftSharePointAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { microsoftSharePointCommon } from '../common'; +import { Client } from '@microsoft/microsoft-graph-client'; + +export const findListItemAction = createAction({ + auth: microsoftSharePointAuth, + name: 'microsoft_sharepoint_search_list_item', + displayName: 'Find List Item', + description: 'Finds a item in a list based on name.', + props: { + siteId: microsoftSharePointCommon.siteId, + listId: microsoftSharePointCommon.listId, + searchValue: Property.ShortText({ + displayName: 'Title', + description: 'Item title to search', + required: true, + }), + }, + async run(context) { + const { siteId, listId, searchValue } = context.propsValue; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(context.auth.access_token), + }, + }); + + // Escaping single quotes + const title = searchValue.replaceAll("'", "''"); + return await client + .api( + `/sites/${siteId}/lists/${listId}/items?$expand=fields&filter=fields/Title eq '${title}'` + ) + .headers({ Prefer: 'HonorNonIndexedQueriesWarningMayFailRandomly' }) + .get(); + }, +}); diff --git a/packages/pieces/community/microsoft-sharepoint/src/lib/actions/update-list-item.ts b/packages/pieces/community/microsoft-sharepoint/src/lib/actions/update-list-item.ts new file mode 100644 index 0000000..e8124ec --- /dev/null +++ b/packages/pieces/community/microsoft-sharepoint/src/lib/actions/update-list-item.ts @@ -0,0 +1,39 @@ +import { microsoftSharePointAuth } from '../../'; +import { createAction } from '@activepieces/pieces-framework'; +import { microsoftSharePointCommon } from '../common'; +import { Client } from '@microsoft/microsoft-graph-client'; + +export const updateListItemAction = createAction({ + auth: microsoftSharePointAuth, + name: 'microsoft_sharepoint_update_list_item', + displayName: 'Update List Item', + description: 'Updates an existing item in a list.', + props: { + siteId: microsoftSharePointCommon.siteId, + listId: microsoftSharePointCommon.listId, + listItemId: microsoftSharePointCommon.listItemId, + listColumns: microsoftSharePointCommon.listColumns, + }, + async run(context) { + const { siteId, listId, listItemId, listColumns } = context.propsValue; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(context.auth.access_token), + }, + }); + const fieldWithArrayValues: Record = {}; + + Object.entries(listColumns).forEach(([key, value]) => { + // https://learn.microsoft.com/en-us/answers/questions/1517379/upload-multiple-choice-fields-item-in-sharepoint-w + if (Array.isArray(value)) { + fieldWithArrayValues[`${key}@odata.type`] = 'Collection(Edm.String)'; + } + }); + const itemInput = { ...listColumns, ...fieldWithArrayValues }; + + return await client + .api(`/sites/${siteId}/lists/${listId}/items/${listItemId}/fields`) + .patch(itemInput); + }, +}); diff --git a/packages/pieces/community/microsoft-sharepoint/src/lib/actions/upload-file.ts b/packages/pieces/community/microsoft-sharepoint/src/lib/actions/upload-file.ts new file mode 100644 index 0000000..c596ada --- /dev/null +++ b/packages/pieces/community/microsoft-sharepoint/src/lib/actions/upload-file.ts @@ -0,0 +1,46 @@ +import { microsoftSharePointAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { microsoftSharePointCommon } from '../common'; +import { Client } from '@microsoft/microsoft-graph-client'; + +export const uploadFile = createAction({ + auth: microsoftSharePointAuth, + name: 'microsoft_sharepoint_upload_file', + displayName: 'Upload File', + description: 'Uploads a new file at path you specify.', + props: { + siteId: microsoftSharePointCommon.siteId, + driveId: microsoftSharePointCommon.driveId, + file: Property.File({ + displayName: "File", + description: "The file or url you want to upload", + required: true, + }), + parentFolder: Property.ShortText({ + displayName: 'Parent Folder', + description: `Parent folder, like "/demo/" or "/docs/assignment/".Leave it default if you want to create folder at the root (**CHANGE THIS BACK//**) level.`, + required: true, + defaultValue: '/', + }), + fileName: Property.ShortText({ + displayName: 'File Name', + required: true, + }), + }, + async run(context) { + const { siteId, driveId, file, parentFolder, fileName } = context.propsValue; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(context.auth.access_token), + }, + }); + + const parentIdResponse = await client.api(`/sites/${siteId}/drives/${driveId}/root:${parentFolder}`).get() + const parentId = parentIdResponse.id ?? "test"; + + const uploadResponse = await client.api(`/sites/${siteId}/drives/${driveId}/items/${parentId}:/${fileName}:/content`).put(file.data) + + return uploadResponse + } +}); diff --git a/packages/pieces/community/microsoft-sharepoint/src/lib/common/index.ts b/packages/pieces/community/microsoft-sharepoint/src/lib/common/index.ts new file mode 100644 index 0000000..6018f4d --- /dev/null +++ b/packages/pieces/community/microsoft-sharepoint/src/lib/common/index.ts @@ -0,0 +1,299 @@ +import { microsoftSharePointAuth } from '../../'; +import { + DropdownOption, + DynamicPropsValue, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { Client, PageCollection } from '@microsoft/microsoft-graph-client'; +import { + Site, + Drive, + List, + ListItem, + ColumnDefinition, +} from '@microsoft/microsoft-graph-types'; + +export const microsoftSharePointCommon = { + siteId: Property.Dropdown({ + displayName: 'Site ID', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first.', + options: [], + }; + } + + const authValue = auth as PiecePropValueSchema< + typeof microsoftSharePointAuth + >; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(authValue.access_token), + }, + }); + + const options: DropdownOption[] = []; + + // https://sharepoint.stackexchange.com/questions/238094/how-could-i-get-all-root-level-sites-excluding-sub-site-using-microsoft-graph + let response: PageCollection = await client + .api('/sites?search=*&$select=displayName,id,name') + // .search('*') + // .select('id,name,displayName,webUrl') + .get(); + + while (response.value.length > 0) { + for (const site of response.value as Site[]) { + options.push({ label: site.displayName!, value: site.id! }); + } + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + + return { + disabled: false, + options, + }; + }, + }), + driveId: Property.Dropdown({ + displayName: 'Drive ID', + required: true, + refreshers: ['siteId'], + options: async ({ auth, siteId }) => { + if (!auth || !siteId) { + return { + disabled: true, + placeholder: 'Please connect your account first and select site.', + options: [], + }; + } + + const authValue = auth as PiecePropValueSchema< + typeof microsoftSharePointAuth + >; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(authValue.access_token), + }, + }); + + const options: DropdownOption[] = []; + + let response: PageCollection = await client + .api(`/sites/${siteId}/drives`) + .select('id,name') + .get(); + + while (response.value.length > 0) { + for (const drive of response.value as Drive[]) { + options.push({ label: drive.name!, value: drive.id! }); + } + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + + return { + disabled: false, + options, + }; + }, + }), + listId: Property.Dropdown({ + displayName: 'List ID', + required: true, + refreshers: ['siteId'], + options: async ({ auth, siteId }) => { + if (!auth || !siteId) { + return { + disabled: true, + placeholder: 'Please connect your account first and select site.', + options: [], + }; + } + + const authValue = auth as PiecePropValueSchema< + typeof microsoftSharePointAuth + >; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(authValue.access_token), + }, + }); + + const options: DropdownOption[] = []; + + let response: PageCollection = await client + .api(`/sites/${siteId}/lists`) + .select('displayName,id') + .get(); + + while (response.value.length > 0) { + for (const list of response.value as List[]) { + options.push({ label: list.displayName!, value: list.id! }); + } + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + + return { + disabled: false, + options, + }; + }, + }), + listColumns: Property.DynamicProperties({ + displayName: 'List Columns', + refreshers: ['siteId', 'listId'], + required: true, + props: async ({ auth, siteId, listId }) => { + if (!auth || !siteId || !listId) return {}; + + const fields: DynamicPropsValue = {}; + + const authValue = auth as PiecePropValueSchema< + typeof microsoftSharePointAuth + >; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(authValue.access_token), + }, + }); + + const columns: ColumnDefinition[] = []; + + let response: PageCollection = await client + .api(`/sites/${siteId}/lists/${listId}/columns`) + .get(); + + while (response.value.length > 0) { + for (const column of response.value as ColumnDefinition[]) { + if (!column.readOnly) { + columns.push(column); + } + } + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + for (const column of columns) { + const params = { + displayName: column.displayName!, + description: column.description ?? '', + required: false, + }; + if (column.boolean) { + fields[column.name!] = Property.Checkbox(params); + } else if (column.text) { + fields[column.name!] = Property.LongText(params); + } else if (column.dateTime) { + fields[column.name!] = Property.DateTime(params); + } else if (column.choice) { + if (column.choice.displayAs === 'checkBoxes') { + fields[column.name!] = Property.StaticMultiSelectDropdown({ + ...params, + options: { + disabled: false, + options: column.choice?.choices + ? column.choice.choices.map((choice: string) => ({ + label: choice, + value: choice, + })) + : [], + }, + }); + } else { + fields[column.name!] = Property.StaticDropdown({ + ...params, + options: { + disabled: false, + options: column.choice?.choices + ? column.choice.choices.map((choice: string) => ({ + label: choice, + value: choice, + })) + : [], + }, + }); + } + } else if (column.number) { + fields[column.name!] = Property.Number(params); + } else if (column.currency) { + fields[column.name!] = Property.Number(params); + } + } + + return fields; + }, + }), + listItemId: Property.Dropdown({ + displayName: 'List Item ID', + required: true, + refreshers: ['siteId', 'listId'], + options: async ({ auth, siteId, listId }) => { + if (!auth || !siteId || !listId) { + return { + disabled: true, + placeholder: 'Please connect your account first and select site.', + options: [], + }; + } + + const authValue = auth as PiecePropValueSchema< + typeof microsoftSharePointAuth + >; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(authValue.access_token), + }, + }); + + const options: DropdownOption[] = []; + + let response: PageCollection = await client + .api( + `/sites/${siteId}/lists/${listId}/items?$select=id&$expand=fields($select=Title)` + ) + .get(); + + while (response.value.length > 0) { + for (const item of response.value as ListItem[]) { + options.push({ + label: (item.fields as any).Title ?? `Item ${item.id}`, + value: item.id!, + }); + } + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + + return { + disabled: false, + options, + }; + }, + }), +}; diff --git a/packages/pieces/community/microsoft-sharepoint/tsconfig.json b/packages/pieces/community/microsoft-sharepoint/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/microsoft-sharepoint/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/microsoft-sharepoint/tsconfig.lib.json b/packages/pieces/community/microsoft-sharepoint/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/microsoft-sharepoint/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/microsoft-teams/.eslintrc.json b/packages/pieces/community/microsoft-teams/.eslintrc.json new file mode 100644 index 0000000..359ff63 --- /dev/null +++ b/packages/pieces/community/microsoft-teams/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/microsoft-teams/README.md b/packages/pieces/community/microsoft-teams/README.md new file mode 100644 index 0000000..13e5ec3 --- /dev/null +++ b/packages/pieces/community/microsoft-teams/README.md @@ -0,0 +1,7 @@ +# pieces-microsoft-teams + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-microsoft-teams` to build the library. diff --git a/packages/pieces/community/microsoft-teams/package.json b/packages/pieces/community/microsoft-teams/package.json new file mode 100644 index 0000000..68b9b2a --- /dev/null +++ b/packages/pieces/community/microsoft-teams/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-microsoft-teams", + "version": "0.1.0" +} diff --git a/packages/pieces/community/microsoft-teams/project.json b/packages/pieces/community/microsoft-teams/project.json new file mode 100644 index 0000000..cba19d9 --- /dev/null +++ b/packages/pieces/community/microsoft-teams/project.json @@ -0,0 +1,48 @@ +{ + "name": "pieces-microsoft-teams", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/microsoft-teams/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/microsoft-teams", + "tsConfig": "packages/pieces/community/microsoft-teams/tsconfig.lib.json", + "packageJson": "packages/pieces/community/microsoft-teams/package.json", + "main": "packages/pieces/community/microsoft-teams/src/index.ts", + "assets": [ + "packages/pieces/community/microsoft-teams/*.md", + { + "input": "packages/pieces/community/microsoft-teams/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-microsoft-teams {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ], + "options": { + "lintFilePatterns": [ + "packages/pieces/community/microsoft-teams/**/*.ts" + ] + } + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/microsoft-teams/src/index.ts b/packages/pieces/community/microsoft-teams/src/index.ts new file mode 100644 index 0000000..6cdc439 --- /dev/null +++ b/packages/pieces/community/microsoft-teams/src/index.ts @@ -0,0 +1,70 @@ +import { + createPiece, + PieceAuth, + PiecePropValueSchema, + OAuth2PropertyValue, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createChannelAction } from './lib/actions/create-channel'; +import { sendChannelMessageAction } from './lib/actions/send-channel-message'; +import { Client } from '@microsoft/microsoft-graph-client'; +import { sendChatMessageAction } from './lib/actions/send-chat-message'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { newChannelMessageTrigger } from './lib/triggers/new-channel-message'; + +export const microsoftTeamsAuth = PieceAuth.OAuth2({ + required: true, + scope: [ + 'openid', + 'email', + 'profile', + 'offline_access', + 'User.Read', + 'Channel.Create', + 'Channel.ReadBasic.All', + 'ChannelMessage.Send', + 'Team.ReadBasic.All', + 'Chat.ReadWrite', + 'ChannelMessage.Read.All', + 'User.ReadBasic.All', + 'Presence.Read.All', + ], + authUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', + tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + validate: async ({ auth }) => { + try { + const authValue = auth as PiecePropValueSchema; + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(authValue.access_token), + }, + }); + await client.api('/me').get(); + return { valid: true }; + } catch (error) { + return { valid: false, error: 'Invalid Credentials.' }; + } + }, +}); + +export const microsoftTeams = createPiece({ + displayName: 'Microsoft Teams', + auth: microsoftTeamsAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/microsoft-teams.png', + categories: [PieceCategory.BUSINESS_INTELLIGENCE, PieceCategory.COMMUNICATION], + authors: ['kishanprmr'], + actions: [ + createChannelAction, + sendChannelMessageAction, + sendChatMessageAction, + createCustomApiCallAction({ + auth: microsoftTeamsAuth, + baseUrl: () => 'https://graph.microsoft.com/v1.0/teams', + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [newChannelMessageTrigger], +}); diff --git a/packages/pieces/community/microsoft-teams/src/lib/actions/create-channel.ts b/packages/pieces/community/microsoft-teams/src/lib/actions/create-channel.ts new file mode 100644 index 0000000..f2afe6c --- /dev/null +++ b/packages/pieces/community/microsoft-teams/src/lib/actions/create-channel.ts @@ -0,0 +1,38 @@ +import { microsoftTeamsAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@microsoft/microsoft-graph-client'; +import { microsoftTeamsCommon } from '../common'; + +export const createChannelAction = createAction({ + auth: microsoftTeamsAuth, + name: 'microsoft_teams_create_channel', + displayName: 'Create Channel', + description: 'Create a new channel in Microsoft Teams.', + props: { + teamId: microsoftTeamsCommon.teamId, + channelDisplayName: Property.ShortText({ + displayName: 'Channel Name', + required: true, + }), + channelDescription: Property.LongText({ + displayName: 'Channel Description', + required: false, + }), + }, + async run(context) { + const { teamId, channelDescription, channelDisplayName } = context.propsValue; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(context.auth.access_token), + }, + }); + + const channel = { + displayName: channelDisplayName, + description: channelDescription, + }; + + return await client.api(`/teams/${teamId}/channels`).post(channel); + }, +}); diff --git a/packages/pieces/community/microsoft-teams/src/lib/actions/send-channel-message.ts b/packages/pieces/community/microsoft-teams/src/lib/actions/send-channel-message.ts new file mode 100644 index 0000000..0aca297 --- /dev/null +++ b/packages/pieces/community/microsoft-teams/src/lib/actions/send-channel-message.ts @@ -0,0 +1,56 @@ +import { microsoftTeamsAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@microsoft/microsoft-graph-client'; +import { microsoftTeamsCommon } from '../common'; + +export const sendChannelMessageAction = createAction({ + auth: microsoftTeamsAuth, + name: 'microsoft_teams_send_channel_message', + displayName: 'Send Channel Message', + description: "Sends a message to a teams's channel.", + props: { + teamId: microsoftTeamsCommon.teamId, + channelId: microsoftTeamsCommon.channelId, + contentType: Property.StaticDropdown({ + displayName: 'Content Type', + required: true, + defaultValue: 'text', + options: { + disabled: false, + options: [ + { + label: 'Text', + value: 'text', + }, + { + label: 'HTML', + value: 'html', + }, + ], + }, + }), + content: Property.LongText({ + displayName: 'Message', + required: true, + }), + }, + async run(context) { + const { teamId, channelId, contentType, content } = context.propsValue; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(context.auth.access_token), + }, + }); + + //https://learn.microsoft.com/en-us/graph/api/channel-post-messages?view=graph-rest-1.0&tabs=http + const chatMessage = { + body: { + content: content, + contentType: contentType, + }, + }; + + return await client.api(`/teams/${teamId}/channels/${channelId}/messages`).post(chatMessage); + }, +}); diff --git a/packages/pieces/community/microsoft-teams/src/lib/actions/send-chat-message.ts b/packages/pieces/community/microsoft-teams/src/lib/actions/send-chat-message.ts new file mode 100644 index 0000000..62889b8 --- /dev/null +++ b/packages/pieces/community/microsoft-teams/src/lib/actions/send-chat-message.ts @@ -0,0 +1,54 @@ +import { microsoftTeamsAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@microsoft/microsoft-graph-client'; +import { microsoftTeamsCommon } from '../common'; + +export const sendChatMessageAction = createAction({ + auth: microsoftTeamsAuth, + name: 'microsoft_teams_send_chat_message', + displayName: 'Send Chat Message', + description: 'Sends a message in an existing chat.', + props: { + chatId: microsoftTeamsCommon.chatId, + contentType: Property.StaticDropdown({ + displayName: 'Content Type', + required: true, + defaultValue: 'text', + options: { + disabled: false, + options: [ + { + label: 'Text', + value: 'text', + }, + { + label: 'HTML', + value: 'html', + }, + ], + }, + }), + content: Property.LongText({ + displayName: 'Message', + required: true, + }), + }, + async run(context) { + const { chatId, contentType, content } = context.propsValue; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(context.auth.access_token), + }, + }); + + const chatMessage = { + body: { + content: content, + contentType: contentType, + }, + }; + + return await client.api(`/chats/${chatId}/messages`).post(chatMessage); + }, +}); diff --git a/packages/pieces/community/microsoft-teams/src/lib/common/index.ts b/packages/pieces/community/microsoft-teams/src/lib/common/index.ts new file mode 100644 index 0000000..a2f01c3 --- /dev/null +++ b/packages/pieces/community/microsoft-teams/src/lib/common/index.ts @@ -0,0 +1,144 @@ +import { DropdownOption, PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; + +import { Client, PageCollection } from '@microsoft/microsoft-graph-client'; +import { Team, Channel, Chat, ConversationMember } from '@microsoft/microsoft-graph-types'; +import { microsoftTeamsAuth } from '../../'; + +export const microsoftTeamsCommon = { + teamId: Property.Dropdown({ + displayName: 'Team ID', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first.', + options: [], + }; + } + const authValue = auth as PiecePropValueSchema; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(authValue.access_token), + }, + }); + const options: DropdownOption[] = []; + + // Pagination : https://learn.microsoft.com/en-us/graph/sdks/paging?view=graph-rest-1.0&tabs=typescript#manually-requesting-subsequent-pages + // List Joined Channels : https://learn.microsoft.com/en-us/graph/api/user-list-joinedteams?view=graph-rest-1.0&tabs=http + let response: PageCollection = await client.api('/me/joinedTeams').get(); + while (response.value.length > 0) { + for (const team of response.value as Team[]) { + options.push({ label: team.displayName!, value: team.id! }); + } + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + return { + disabled: false, + options: options, + }; + }, + }), + channelId: Property.Dropdown({ + displayName: 'Channel ID', + refreshers: ['teamId'], + required: true, + options: async ({ auth, teamId }) => { + if (!auth || !teamId) { + return { + disabled: true, + placeholder: 'Please connect your account first and select team.', + options: [], + }; + } + const authValue = auth as PiecePropValueSchema; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(authValue.access_token), + }, + }); + const options: DropdownOption[] = []; + + // Pagination : https://learn.microsoft.com/en-us/graph/sdks/paging?view=graph-rest-1.0&tabs=typescript#manually-requesting-subsequent-pages + // List Channels : https://learn.microsoft.com/en-us/graph/api/channel-list?view=graph-rest-1.0&tabs=http + let response: PageCollection = await client.api(`/teams/${teamId}/channels`).get(); + while (response.value.length > 0) { + for (const channel of response.value as Channel[]) { + options.push({ label: channel.displayName!, value: channel.id! }); + } + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + return { + disabled: false, + options: options, + }; + }, + }), + chatId: Property.Dropdown({ + displayName: 'Chat ID', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first and select team.', + options: [], + }; + } + const authValue = auth as PiecePropValueSchema; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(authValue.access_token), + }, + }); + const options: DropdownOption[] = []; + + // Pagination : https://learn.microsoft.com/en-us/graph/sdks/paging?view=graph-rest-1.0&tabs=typescript#manually-requesting-subsequent-pages + // List Chats : https://learn.microsoft.com/en-us/graph/api/chat-list?view=graph-rest-1.0&tabs=http + let response: PageCollection = await client.api('/chats').expand('members').get(); + while (response.value.length > 0) { + for (const chat of response.value as Chat[]) { + const chatName = + chat.topic ?? + chat.members + ?.filter((member: ConversationMember) => member.displayName) + .map((member: ConversationMember) => member.displayName) + .join(','); + options.push({ + label: `(${CHAT_TYPE[chat.chatType!]} Chat) ${chatName || '(no title)'}`, + value: chat.id!, + }); + } + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + return { + disabled: false, + options: options, + }; + }, + }), +}; + +const CHAT_TYPE = { + oneOnOne: '1 : 1', + group: 'Group', + meeting: 'Meeting', + unknownFutureValue: 'Unknown', +}; diff --git a/packages/pieces/community/microsoft-teams/src/lib/triggers/new-channel-message.ts b/packages/pieces/community/microsoft-teams/src/lib/triggers/new-channel-message.ts new file mode 100644 index 0000000..008f73c --- /dev/null +++ b/packages/pieces/community/microsoft-teams/src/lib/triggers/new-channel-message.ts @@ -0,0 +1,142 @@ +import { microsoftTeamsAuth } from '../../index'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { microsoftTeamsCommon } from '../common'; +import { Client, PageCollection } from '@microsoft/microsoft-graph-client'; +import { ChatMessage } from '@microsoft/microsoft-graph-types'; +import dayjs from 'dayjs'; + +import { isNil } from '@activepieces/shared'; +type Props = { + teamId: string; + channelId: string; +}; + +export const newChannelMessageTrigger = createTrigger({ + auth: microsoftTeamsAuth, + name: 'new-channel-message', + displayName: 'New Channel Message', + description: 'Triggers when a new message is posted in a channel.', + props: { + teamId: microsoftTeamsCommon.teamId, + channelId: microsoftTeamsCommon.channelId, + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + replyToId: null, + etag: '1747831213175', + messageType: 'message', + createdDateTime: '2025-05-21T12:40:13.175Z', + lastModifiedDateTime: '2025-05-21T12:40:13.175Z', + lastEditedDateTime: null, + deletedDateTime: null, + subject: 'Test', + summary: null, + chatId: null, + importance: 'normal', + locale: 'en-us', + webUrl:'', + policyViolation: null, + eventDetail: null, + id: '1747831213175', + from: { + application: null, + device: null, + user: { + '@odata.type': '#microsoft.graph.teamworkUserIdentity', + id: '90b3720d-f459-42c1-a02e-a1ecb068', + displayName: 'Activepieces', + userIdentityType: 'aadUser', + tenantId: '9b37335a-d996-4a8d-9ae4-a3a04c94', + }, + }, + body: { + contentType: 'html', + content: '

Test Message

', + }, + channelIdentity: { + teamId: '99cb9-7ebe-43ee-a69b-5f77ce8a4b4e', + channelId: '19:LiZnIkTo_1FmFY9OTsfym0q3bwo-y2UfV9FaYA1@thread.tacv2', + }, + attachments: [], + mentions: [], + reactions: [], + }, +}); + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS, store }) { + const { teamId, channelId } = propsValue; + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(auth.access_token), + }, + }); + + const messages: ChatMessage[] = []; + + if (lastFetchEpochMS === 0) { + const response: PageCollection = await client + .api(`/teams/${teamId}/channels/${channelId}/messages`) + .top(5) + .get(); + + if (!isNil(response.value)) { + messages.push(...response.value); + } + } else { + const requestUrl = + (await store.get('deltalink')) ?? + `/teams/${teamId}/channels/${channelId}/messages/delta`; + let nextLink: string | null = requestUrl; + + // https://learn.microsoft.com/en-us/graph/api/chatmessage-delta?view=graph-rest-1.0&tabs=http + while (nextLink) { + const response: PageCollection = await client.api(nextLink).get(); + const channelMessages = response.value as ChatMessage[]; + + if (Array.isArray(channelMessages)) { + messages.push(...channelMessages); + } + + nextLink = response['@odata.nextLink'] ?? null; + + if (response['@odata.deltaLink']) { + await store.put('deltalink', response['@odata.deltaLink']); + } + } + } + + return messages.map((message: ChatMessage) => { + return { + epochMilliSeconds: dayjs(message.createdDateTime).valueOf(), + data: message, + }; + }); + }, +}; diff --git a/packages/pieces/community/microsoft-teams/tsconfig.json b/packages/pieces/community/microsoft-teams/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/microsoft-teams/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/microsoft-teams/tsconfig.lib.json b/packages/pieces/community/microsoft-teams/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/microsoft-teams/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/microsoft-todo/.eslintrc.json b/packages/pieces/community/microsoft-todo/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/microsoft-todo/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/microsoft-todo/README.md b/packages/pieces/community/microsoft-todo/README.md new file mode 100644 index 0000000..ceb8035 --- /dev/null +++ b/packages/pieces/community/microsoft-todo/README.md @@ -0,0 +1,7 @@ +# pieces-microsoft-todo + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-microsoft-todo` to build the library. diff --git a/packages/pieces/community/microsoft-todo/package.json b/packages/pieces/community/microsoft-todo/package.json new file mode 100644 index 0000000..a126caf --- /dev/null +++ b/packages/pieces/community/microsoft-todo/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-microsoft-todo", + "version": "0.0.1" +} diff --git a/packages/pieces/community/microsoft-todo/project.json b/packages/pieces/community/microsoft-todo/project.json new file mode 100644 index 0000000..4235c73 --- /dev/null +++ b/packages/pieces/community/microsoft-todo/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-microsoft-todo", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/microsoft-todo/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/microsoft-todo", + "tsConfig": "packages/pieces/community/microsoft-todo/tsconfig.lib.json", + "packageJson": "packages/pieces/community/microsoft-todo/package.json", + "main": "packages/pieces/community/microsoft-todo/src/index.ts", + "assets": [ + "packages/pieces/community/microsoft-todo/*.md", + { + "input": "packages/pieces/community/microsoft-todo/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/microsoft-todo/src/index.ts b/packages/pieces/community/microsoft-todo/src/index.ts new file mode 100644 index 0000000..9270b01 --- /dev/null +++ b/packages/pieces/community/microsoft-todo/src/index.ts @@ -0,0 +1,44 @@ +import { createPiece, OAuth2PropertyValue, PieceAuth } from '@activepieces/pieces-framework'; +import { createTask } from './lib/actions/create-task'; +import { createTaskListAction } from './lib/actions/create-task-list'; +import { updateTaskAction } from './lib/actions/update-task'; +import { findTaskListByNameAction } from './lib/actions/find-task-list-by-name'; +import { findTaskByTitleAction } from './lib/actions/find-task-by-title'; +import { newTaskCreatedTrigger } from './lib/triggers/new-task-created'; +import { newOrUpdatedTaskTrigger } from './lib/triggers/task-updated'; +import { PieceCategory } from '@activepieces/shared'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; + +export const microsoftToDoAuth = PieceAuth.OAuth2({ + description: + 'Authenticate with your Microsoft Account. You will need to register an application in the Microsoft Entra admin center.Add **Tasks.ReadWrite**,**User.Read**, **offline_access** scopes.', + authUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', + tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + required: true, + scope: ['Tasks.ReadWrite', 'User.Read', 'offline_access'], +}); + +export const microsoftTodo = createPiece({ + displayName: 'Microsoft To Do', + description: 'Cloud based task management application.', + categories: [PieceCategory.PRODUCTIVITY], + auth: microsoftToDoAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/microsoft-todo.png', + authors: ['onyedikachi-david'], + actions: [ + createTask, + createTaskListAction, + updateTaskAction, + findTaskListByNameAction, + findTaskByTitleAction, + createCustomApiCallAction({ + baseUrl: () => 'https://graph.microsoft.com/v1.0/me/todo', + auth: microsoftToDoAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [newTaskCreatedTrigger, newOrUpdatedTaskTrigger], +}); diff --git a/packages/pieces/community/microsoft-todo/src/lib/actions/create-task-list.ts b/packages/pieces/community/microsoft-todo/src/lib/actions/create-task-list.ts new file mode 100644 index 0000000..2cf098d --- /dev/null +++ b/packages/pieces/community/microsoft-todo/src/lib/actions/create-task-list.ts @@ -0,0 +1,32 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { microsoftToDoAuth } from '../../index'; +import { Client } from '@microsoft/microsoft-graph-client'; + +export const createTaskListAction = createAction({ + auth: microsoftToDoAuth, + name: 'create_task_list', + displayName: 'Create Task List', + description: 'Create a new task list.', + props: { + displayName: Property.ShortText({ + displayName: 'Title', + description: 'The name for the new task list.', + required: true, + }), + }, + async run(context) { + const { auth, propsValue } = context; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(auth.access_token), + }, + }); + + const response = await client.api('/me/todo/lists').post({ + displayName: propsValue.displayName, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/microsoft-todo/src/lib/actions/create-task.ts b/packages/pieces/community/microsoft-todo/src/lib/actions/create-task.ts new file mode 100644 index 0000000..8780e79 --- /dev/null +++ b/packages/pieces/community/microsoft-todo/src/lib/actions/create-task.ts @@ -0,0 +1,151 @@ +import { Property, createAction, OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { getTaskListsDropdown } from '../common'; +import { microsoftToDoAuth } from '../../index'; +import { Client } from '@microsoft/microsoft-graph-client'; +import { Importance, TaskStatus, TodoTask } from '@microsoft/microsoft-graph-types'; + +export const createTask = createAction({ + auth: microsoftToDoAuth, + name: 'create_task', + displayName: 'Create Task', + description: 'Creates a new task.', + props: { + task_list_id: Property.Dropdown({ + displayName: 'Task List', + description: 'The task list to create the task in.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!(auth as OAuth2PropertyValue)?.access_token) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + return await getTaskListsDropdown(auth as OAuth2PropertyValue); + }, + }), + title: Property.ShortText({ + displayName: 'Title', + description: 'The title of the task.', + required: true, + }), + body_content: Property.LongText({ + displayName: 'Body Content', + description: 'The body or notes for the task.', + required: false, + }), + importance: Property.StaticDropdown({ + displayName: 'Importance', + description: 'The importance of the task.', + required: false, + options: { + options: [ + { label: 'Low', value: 'low' }, + { label: 'Normal', value: 'normal' }, + { label: 'High', value: 'high' }, + ], + }, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + description: 'The status of the task.', + required: false, + options: { + options: [ + { label: 'Not Started', value: 'notStarted' }, + { label: 'In Progress', value: 'inProgress' }, + { label: 'Completed', value: 'completed' }, + { label: 'Waiting On Others', value: 'waitingOnOthers' }, + { label: 'Deferred', value: 'deferred' }, + ], + }, + defaultValue: 'notStarted', + }), + due_date_time: Property.DateTime({ + displayName: 'Due Date', + required: false, + }), + reminder_date_time: Property.DateTime({ + displayName: 'Reminder Date', + required: false, + }), + start_date_time: Property.DateTime({ + displayName: 'Start Date', + description: 'The date and time the task is scheduled to start.', + required: false, + }), + categories: Property.ShortText({ + displayName: 'Categories', + description: 'Comma-separated categories for the task (e.g., Work, Personal).', + required: false, + }), + }, + async run(context) { + const { auth, propsValue } = context; + const { + task_list_id, + title, + body_content, + importance, + status, + due_date_time, + reminder_date_time, + start_date_time, + categories, + } = propsValue; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(auth.access_token), + }, + }); + + const taskBody: TodoTask = { + title, + }; + + if (body_content) { + taskBody.body = { + content: body_content, + contentType: 'text', + }; + } + + if (importance) { + taskBody.importance = importance as Importance; + } + + if (status) { + taskBody.status = status as TaskStatus; + } + + if (due_date_time) { + taskBody.dueDateTime = { + dateTime: due_date_time, + timeZone: 'UTC', + }; + } + + if (reminder_date_time) { + taskBody.isReminderOn = true; + taskBody.reminderDateTime = { dateTime: reminder_date_time, timeZone: 'UTC' }; + } + + if (start_date_time) { + taskBody.startDateTime = { dateTime: start_date_time, timeZone: 'UTC' }; + } + + if (categories) { + taskBody.categories = categories + .split(',') + .map((c) => c.trim()) + .filter((c) => c.length > 0); + } + + const response = await client.api(`/me/todo/lists/${task_list_id}/tasks`).post(taskBody); + + return response; + }, +}); diff --git a/packages/pieces/community/microsoft-todo/src/lib/actions/find-task-by-title.ts b/packages/pieces/community/microsoft-todo/src/lib/actions/find-task-by-title.ts new file mode 100644 index 0000000..5770648 --- /dev/null +++ b/packages/pieces/community/microsoft-todo/src/lib/actions/find-task-by-title.ts @@ -0,0 +1,90 @@ +import { Property, createAction, OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { getTaskListsDropdown } from '../common'; +import { microsoftToDoAuth } from '../../index'; +import { Client, PageCollection } from '@microsoft/microsoft-graph-client'; + +import { TodoTask } from '@microsoft/microsoft-graph-types'; + +export const findTaskByTitleAction = createAction({ + auth: microsoftToDoAuth, + name: 'find_task_by_title', + displayName: 'Find Task', + description: 'Finds tasks by title.', + props: { + task_list_id: Property.Dropdown({ + displayName: 'Task List', + description: 'Select a specific task list to search within.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!(auth as OAuth2PropertyValue)?.access_token) { + return { disabled: true, placeholder: 'Connect your account first', options: [] }; + } + return await getTaskListsDropdown(auth as OAuth2PropertyValue); + }, + }), + title: Property.ShortText({ + displayName: 'Task Title', + description: 'The title (or partial title) of the task to find.', + required: true, + }), + + match_type: Property.StaticDropdown({ + displayName: 'Match Type', + description: 'How to match the task title.', + required: false, + defaultValue: 'contains', + options: { + options: [ + { label: 'Contains', value: 'contains' }, + { label: 'Starts With', value: 'startsWith' }, + { label: 'Exact Match', value: 'exact' }, + ], + }, + }), + }, + async run(context) { + const { auth, propsValue } = context; + const { title, task_list_id, match_type } = propsValue; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(auth.access_token), + }, + }); + + let titleFilterString = ''; + switch (match_type) { + case 'startsWith': + titleFilterString = `startsWith(title, '${title}')`; + break; + case 'exact': + titleFilterString = `title eq '${title}'`; + break; + case 'contains': + default: + titleFilterString = `contains(title, '${title}')`; + break; + } + + const result = []; + + let response: PageCollection = await client + .api(`/me/todo/lists/${task_list_id}/tasks`) + .filter(titleFilterString) + .get(); + + while (response.value.length > 0) { + for (const task of response.value as TodoTask[]) { + result.push(task); + } + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + + return result; + }, +}); diff --git a/packages/pieces/community/microsoft-todo/src/lib/actions/find-task-list-by-name.ts b/packages/pieces/community/microsoft-todo/src/lib/actions/find-task-list-by-name.ts new file mode 100644 index 0000000..7c75529 --- /dev/null +++ b/packages/pieces/community/microsoft-todo/src/lib/actions/find-task-list-by-name.ts @@ -0,0 +1,72 @@ +import { Property, createAction, OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { microsoftToDoAuth } from '../../index'; +import { Client, PageCollection } from '@microsoft/microsoft-graph-client'; +import { TodoTaskList } from '@microsoft/microsoft-graph-types'; + +export const findTaskListByNameAction = createAction({ + auth: microsoftToDoAuth, + name: 'find_task_list_by_name', + displayName: 'Find Task List', + description: 'Finds a task list by its name.', + props: { + name: Property.ShortText({ + displayName: 'Title', + description: 'The name (or partial name) of the task list to find.', + required: true, + }), + match_type: Property.StaticDropdown({ + displayName: 'Match Type', + description: 'How to match the list name.', + required: false, + defaultValue: 'contains', + options: { + options: [ + { label: 'Contains', value: 'contains' }, + { label: 'Starts With', value: 'startsWith' }, + { label: 'Exact Match', value: 'exact' }, + ], + }, + }), + }, + async run(context) { + const { auth, propsValue } = context; + const { name, match_type } = propsValue; + + let filterString = ''; + switch (match_type) { + case 'startsWith': + filterString = `startsWith(displayName, '${name}')`; + break; + case 'exact': + filterString = `displayName eq '${name}'`; + break; + case 'contains': + default: + filterString = `contains(displayName, '${name}')`; + break; + } + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(auth.access_token), + }, + }); + + const result = []; + + let response: PageCollection = await client.api('/me/todo/lists').filter(filterString).get(); + + while (response.value.length > 0) { + for (const list of response.value as TodoTaskList[]) { + result.push(list); + } + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + + return result; + }, +}); diff --git a/packages/pieces/community/microsoft-todo/src/lib/actions/update-task.ts b/packages/pieces/community/microsoft-todo/src/lib/actions/update-task.ts new file mode 100644 index 0000000..5602ae7 --- /dev/null +++ b/packages/pieces/community/microsoft-todo/src/lib/actions/update-task.ts @@ -0,0 +1,182 @@ +import { Property, createAction, OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { getTaskListsDropdown, getTasksInListDropdown } from '../common'; +import { microsoftToDoAuth } from '../../index'; +import { Client } from '@microsoft/microsoft-graph-client'; +import { Importance, TaskStatus, TodoTask } from '@microsoft/microsoft-graph-types'; + +export const updateTaskAction = createAction({ + auth: microsoftToDoAuth, + name: 'update_task', + displayName: 'Update Task', + description: 'Update an existing task.', + props: { + task_list_id: Property.Dropdown({ + displayName: 'Task List', + description: 'The task list containing the task to update.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!(auth as OAuth2PropertyValue)?.access_token) { + return { disabled: true, placeholder: 'Connect your account first', options: [] }; + } + return await getTaskListsDropdown(auth as OAuth2PropertyValue); + }, + }), + task_id: Property.Dropdown({ + displayName: 'Task', + description: 'The task to update.', + required: true, + refreshers: ['task_list_id'], + options: async ({ auth, task_list_id }) => { + const authValue = auth as OAuth2PropertyValue; + if (!authValue?.access_token || !task_list_id) { + return { + disabled: true, + placeholder: !authValue?.access_token + ? 'Connect your account first' + : 'Select a task list first', + options: [], + }; + } + return await getTasksInListDropdown(authValue, task_list_id as string); + }, + }), + title: Property.ShortText({ + displayName: 'Title', + description: 'The title of the task.', + required: false, + }), + body_content: Property.LongText({ + displayName: 'Body Content', + description: 'The body or notes for the task.', + required: false, + }), + importance: Property.StaticDropdown({ + displayName: 'Importance', + description: 'The importance of the task.', + required: false, + options: { + options: [ + { label: 'Low', value: 'low' }, + { label: 'Normal', value: 'normal' }, + { label: 'High', value: 'high' }, + ], + }, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + description: 'The status of the task.', + required: false, + options: { + options: [ + { label: 'Not Started', value: 'notStarted' }, + { label: 'In Progress', value: 'inProgress' }, + { label: 'Completed', value: 'completed' }, + { label: 'Waiting On Others', value: 'waitingOnOthers' }, + { label: 'Deferred', value: 'deferred' }, + ], + }, + }), + due_date_time: Property.DateTime({ + displayName: 'Due Date', + required: false, + }), + reminder_date_time: Property.DateTime({ + displayName: 'Reminder Date', + required: false, + }), + start_date_time: Property.DateTime({ + displayName: 'Start Date', + description: 'The date and time the task is scheduled to start.', + required: false, + }), + categories: Property.ShortText({ + displayName: 'Categories', + description: 'Comma-separated categories for the task (e.g., Work, Personal).', + required: false, + }), + }, + async run(context) { + const { auth, propsValue } = context; + const { + task_list_id, + task_id, + title, + body_content, + importance, + status, + due_date_time, + reminder_date_time, + start_date_time, + categories, + } = propsValue; + + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(auth.access_token), + }, + }); + + const taskBody: TodoTask = { + title, + }; + + if (body_content) { + taskBody.body = { + content: body_content, + contentType: 'text', + }; + } + + if (importance) { + taskBody.importance = importance as Importance; + } + + if (status) { + taskBody.status = status as TaskStatus; + } + + if (due_date_time) { + taskBody.dueDateTime = { + dateTime: due_date_time, + timeZone: 'UTC', + }; + } + + if (reminder_date_time) { + taskBody.isReminderOn = true; + taskBody.reminderDateTime = { dateTime: reminder_date_time, timeZone: 'UTC' }; + } + + if (start_date_time) { + taskBody.startDateTime = { dateTime: start_date_time, timeZone: 'UTC' }; + } + + if (categories && categories !== '') { + taskBody.categories = categories + .split(',') + .map((c) => c.trim()) + .filter((c) => c.length > 0); + } + + // Only send request if there's something to update + if (Object.keys(taskBody).length === 0) { + // Optionally return the existing task or a message, or fetch and return task if ID is present + // For now, just return a message or do nothing if nothing to update. + // However, a PATCH with empty body might be treated as bad request by some APIs. + // Microsoft Graph usually ignores fields not present, so an empty body PATCH might be a no-op. + // Best to ensure at least one field is being modified or return early. + // Let's assume for now the user intends a no-op if all update fields are blank, + // but this might need a more specific behavior (e.g. fetch current task data). + // For safety, if requestBody is empty, we could fetch the task and return it. + // For now, let's proceed with the PATCH, it should be a no-op by MS Graph if body is empty. + throw new Error('Please provide any field to update.'); + } + + const response = await client + .api(`/me/todo/lists/${task_list_id}/tasks/${task_id}`) + .update(taskBody); + + return response; + }, +}); diff --git a/packages/pieces/community/microsoft-todo/src/lib/common/index.ts b/packages/pieces/community/microsoft-todo/src/lib/common/index.ts new file mode 100644 index 0000000..b0a2a2e --- /dev/null +++ b/packages/pieces/community/microsoft-todo/src/lib/common/index.ts @@ -0,0 +1,99 @@ +import { OAuth2PropertyValue, DropdownOption } from '@activepieces/pieces-framework'; +import { Client, PageCollection } from '@microsoft/microsoft-graph-client'; +import { TodoTaskList, TodoTask } from '@microsoft/microsoft-graph-types'; + +export async function getTaskListsDropdown(auth: OAuth2PropertyValue): Promise<{ + disabled: boolean; + options: DropdownOption[]; + placeholder?: string; +}> { + if (!auth || !auth.access_token) { + return { + disabled: true, + options: [], + placeholder: 'Connect your account first', + }; + } + + try { + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(auth.access_token), + }, + }); + + const options: DropdownOption[] = []; + let response: PageCollection = await client.api(`/me/todo/lists`).get(); + + while (response.value.length > 0) { + for (const list of response.value as TodoTaskList[]) { + options.push({ label: list.displayName!, value: list.id! }); + } + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + + return { + disabled: false, + options: options, + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: 'An unexpected error occurred while fetching task lists.', + }; + } +} + +export async function getTasksInListDropdown( + auth: OAuth2PropertyValue, + taskListId: string, +): Promise<{ + disabled: boolean; + options: DropdownOption[]; + placeholder?: string; +}> { + if (!auth || !auth.access_token) { + return { disabled: true, options: [], placeholder: 'Connect your account first' }; + } + if (!taskListId) { + return { disabled: true, options: [], placeholder: 'Task List ID is required' }; + } + + try { + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(auth.access_token), + }, + }); + + const options: DropdownOption[] = []; + + let response: PageCollection = await client.api(`/me/todo/lists/${taskListId}/tasks`).get(); + while (response.value.length > 0) { + for (const task of response.value as TodoTask[]) { + if (task.id && task.title) { + options.push({ label: task.title, value: task.id }); + } + } + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + + return { disabled: false, options: options }; + } catch (error) { + return { disabled: true, options: [], placeholder: 'Error fetching tasks.' }; + } +} + +export const microsoftTodoCommon = { + getTaskListsDropdown, + getTasksInListDropdown, +}; diff --git a/packages/pieces/community/microsoft-todo/src/lib/triggers/new-task-created.ts b/packages/pieces/community/microsoft-todo/src/lib/triggers/new-task-created.ts new file mode 100644 index 0000000..edcaa32 --- /dev/null +++ b/packages/pieces/community/microsoft-todo/src/lib/triggers/new-task-created.ts @@ -0,0 +1,111 @@ +import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { getTaskListsDropdown } from '../common'; +import { microsoftToDoAuth } from '../../index'; +import { Client, PageCollection } from '@microsoft/microsoft-graph-client'; +import dayjs from 'dayjs'; +import { TodoTask } from '@microsoft/microsoft-graph-types'; + +const polling: Polling = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, store, lastFetchEpochMS }) => { + const taskListId = propsValue.task_list_id; + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(auth.access_token), + }, + }); + + const tasks = []; + + const filter = + lastFetchEpochMS === 0 + ? '$top=10' + : `$filter=createdDateTime gt ${dayjs(lastFetchEpochMS).toISOString()}`; + + let response: PageCollection = await client + .api(`/me/todo/lists/${taskListId}/tasks?${filter}`) + .get(); + + if (lastFetchEpochMS === 0) { + for (const task of response.value as TodoTask[]) { + tasks.push(task); + } + } else { + while (response.value.length > 0) { + for (const task of response.value as TodoTask[]) { + tasks.push(task); + } + + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + } + + return tasks.map((task) => ({ + epochMilliSeconds: dayjs(task.createdDateTime).valueOf(), + data: task, + })); + }, +}; + +export const newTaskCreatedTrigger = createTrigger({ + name: 'new_task_created', + displayName: 'New Task', + description: 'Triggers when a new task is created.', + auth: microsoftToDoAuth, + props: { + task_list_id: Property.Dropdown({ + displayName: 'Task List', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!(auth as OAuth2PropertyValue)?.access_token) { + return { disabled: true, placeholder: 'Connect your account first', options: [] }; + } + return await getTaskListsDropdown(auth as OAuth2PropertyValue); + }, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + '@odata.etag': 'W/"vVwdQvxCiE6779iYhchMrAAGgwrb4w=="', + importance: 'normal', + isReminderOn: false, + status: 'notStarted', + title: 'fdfdfd', + createdDateTime: '2025-05-08T12:40:55.3080693Z', + lastModifiedDateTime: '2025-05-08T12:40:55.3533435Z', + hasAttachments: false, + categories: [], + id: 'AQMkADAwATM3ZmYAZS0xNGVmLWNiZmYALTAwAi0wMAoARgAAAw8tTPoZEYtLvE5mK48wuvIHAL1cHUL8QohOu_-YmIXITKwABoMc5_AAAAC9XB1C-EKITrvv2JiFyEysAAaDHTv2AAAA', + body: { + content: '', + contentType: 'text', + }, + }, +}); diff --git a/packages/pieces/community/microsoft-todo/src/lib/triggers/task-completed.ts b/packages/pieces/community/microsoft-todo/src/lib/triggers/task-completed.ts new file mode 100644 index 0000000..a57af44 --- /dev/null +++ b/packages/pieces/community/microsoft-todo/src/lib/triggers/task-completed.ts @@ -0,0 +1,113 @@ +import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { getTaskListsDropdown } from '../common'; +import { microsoftToDoAuth } from '../../index'; +import { Client, PageCollection } from '@microsoft/microsoft-graph-client'; +import dayjs from 'dayjs'; +import { TodoTask } from '@microsoft/microsoft-graph-types'; + +// https://learn.microsoft.com/en-us/answers/questions/567068/to-do-completeddatetime-and-timezone +//Due to inconsistencies in completedDateTime, this trigger is currently unavailable. +const polling: Polling = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + const taskListId = propsValue.task_list_id; + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(auth.access_token), + }, + }); + + const tasks = []; + + const filter = lastFetchEpochMS === 0 ? '$top=10' : `$filter=status eq 'completed'`; + + let response: PageCollection = await client + .api(`/me/todo/lists/${taskListId}/tasks?${filter}`) + .get(); + + if (lastFetchEpochMS === 0) { + for (const task of response.value as TodoTask[]) { + tasks.push(task); + } + } else { + while (response.value.length > 0) { + for (const task of response.value as TodoTask[]) { + tasks.push(task); + } + + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + } + + return tasks.map((task) => ({ + epochMilliSeconds: dayjs(task.completedDateTime?.dateTime).valueOf(), + data: task, + })); + }, +}; +export const taskCompletedTrigger = createTrigger({ + name: 'task_completed', + displayName: 'Task Completed', + description: 'Triggers when a task is completed.', + auth: microsoftToDoAuth, + props: { + task_list_id: Property.Dropdown({ + displayName: 'Task List', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!(auth as OAuth2PropertyValue)?.access_token) { + return { disabled: true, placeholder: 'Connect your account first', options: [] }; + } + return await getTaskListsDropdown(auth as OAuth2PropertyValue); + }, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + '@odata.etag': 'W/"vVwdQvxCiE6779iYhchMrAAGgwrltg=="', + importance: 'normal', + isReminderOn: false, + status: 'completed', + title: 'Test Task', + createdDateTime: '2025-05-08T14:05:53.4572708Z', + lastModifiedDateTime: '2025-05-08T14:41:50.2593794Z', + hasAttachments: false, + categories: ['Blue category'], + id: 'AQMkADAwATM3ZmYAZS0xNGVmLWNiZmYALTAwAi0wMAoARgAAAw8tTPoZEYtLvE5mK48wuvIHAL1cHUL8QohOu_-YmIXITKwABoMc598AAAC9XB1C-EKITrvv2JiFyEysAAaDHUmqAAAA', + body: { + content: '', + contentType: 'text', + }, + completedDateTime: { + dateTime: '2025-05-07T18:30:00.0000000', + timeZone: 'UTC', + }, + }, +}); diff --git a/packages/pieces/community/microsoft-todo/src/lib/triggers/task-updated.ts b/packages/pieces/community/microsoft-todo/src/lib/triggers/task-updated.ts new file mode 100644 index 0000000..320d784 --- /dev/null +++ b/packages/pieces/community/microsoft-todo/src/lib/triggers/task-updated.ts @@ -0,0 +1,111 @@ +import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { getTaskListsDropdown } from '../common'; +import { microsoftToDoAuth } from '../../index'; +import { Client, PageCollection } from '@microsoft/microsoft-graph-client'; +import dayjs from 'dayjs'; +import { TodoTask } from '@microsoft/microsoft-graph-types'; + +const polling: Polling = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, store, lastFetchEpochMS }) { + const taskListId = propsValue.task_list_id; + const client = Client.initWithMiddleware({ + authProvider: { + getAccessToken: () => Promise.resolve(auth.access_token), + }, + }); + + const tasks = []; + + const filter = + lastFetchEpochMS === 0 + ? '$top=10' + : `$filter=lastModifiedDateTime gt ${dayjs(lastFetchEpochMS).toISOString()}`; + + let response: PageCollection = await client + .api(`/me/todo/lists/${taskListId}/tasks?${filter}`) + .get(); + + if (lastFetchEpochMS === 0) { + for (const task of response.value as TodoTask[]) { + tasks.push(task); + } + } else { + while (response.value.length > 0) { + for (const task of response.value as TodoTask[]) { + tasks.push(task); + } + + if (response['@odata.nextLink']) { + response = await client.api(response['@odata.nextLink']).get(); + } else { + break; + } + } + } + + return tasks.map((task) => ({ + epochMilliSeconds: dayjs(task.lastModifiedDateTime).valueOf(), + data: task, + })); + }, +}; + +export const newOrUpdatedTaskTrigger = createTrigger({ + name: 'new_or_updated_task', + displayName: 'New or Updated Task', + description: 'Triggers when a new task is created or an existing task is updated.', + auth: microsoftToDoAuth, + props: { + task_list_id: Property.Dropdown({ + displayName: 'Task List', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!(auth as OAuth2PropertyValue)?.access_token) { + return { disabled: true, placeholder: 'Connect your account first', options: [] }; + } + return await getTaskListsDropdown(auth as OAuth2PropertyValue); + }, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + '@odata.etag': 'W/"vVwdQvxCiE6779iYhchMrAAGgwrb4w=="', + importance: 'normal', + isReminderOn: false, + status: 'notStarted', + title: 'fdfdfd', + createdDateTime: '2025-05-08T12:40:55.3080693Z', + lastModifiedDateTime: '2025-05-08T12:40:55.3533435Z', + hasAttachments: false, + categories: [], + id: 'AQMkADAwATM3ZmYAZS0xNGVmLWNiZmYALTAwAi0wMAoARgAAAw8tTPoZEYtLvE5mK48wuvIHAL1cHUL8QohOu_-YmIXITKwABoMc5_AAAAC9XB1C-EKITrvv2JiFyEysAAaDHTv2AAAA', + body: { + content: '', + contentType: 'text', + }, + }, +}); diff --git a/packages/pieces/community/microsoft-todo/tsconfig.json b/packages/pieces/community/microsoft-todo/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/microsoft-todo/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/microsoft-todo/tsconfig.lib.json b/packages/pieces/community/microsoft-todo/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/microsoft-todo/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/mindee/.eslintrc.json b/packages/pieces/community/mindee/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/mindee/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/mindee/README.md b/packages/pieces/community/mindee/README.md new file mode 100644 index 0000000..a6060e3 --- /dev/null +++ b/packages/pieces/community/mindee/README.md @@ -0,0 +1,7 @@ +# pieces-mindee + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-mindee` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/mindee/package.json b/packages/pieces/community/mindee/package.json new file mode 100644 index 0000000..5fe622a --- /dev/null +++ b/packages/pieces/community/mindee/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-mindee", + "version": "0.1.4" +} \ No newline at end of file diff --git a/packages/pieces/community/mindee/project.json b/packages/pieces/community/mindee/project.json new file mode 100644 index 0000000..0fb762b --- /dev/null +++ b/packages/pieces/community/mindee/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-mindee", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/mindee/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/mindee", + "tsConfig": "packages/pieces/community/mindee/tsconfig.lib.json", + "packageJson": "packages/pieces/community/mindee/package.json", + "main": "packages/pieces/community/mindee/src/index.ts", + "assets": [ + "packages/pieces/community/mindee/*.md", + { + "input": "packages/pieces/community/mindee/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/mindee/src/index.ts b/packages/pieces/community/mindee/src/index.ts new file mode 100644 index 0000000..2ee442a --- /dev/null +++ b/packages/pieces/community/mindee/src/index.ts @@ -0,0 +1,37 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { mindeePredictDocumentAction } from './lib/actions/predict-document'; + +export const mindeeAuth = PieceAuth.SecretText({ + displayName: 'Api Key', + description: ` + #### To obtain access your Api Key + 1. Sign up and log in to Mindee + 2. Go to [API Key page](https://platform.mindee.com/api-keys) + 3. Copy the Key and paste below. + `, + required: true, +}); + +export const mindee = createPiece({ + displayName: 'Mindee', + description: 'Document automation API', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/mindee.png', + categories: [PieceCategory.COMMUNICATION], + authors: ["kanarelo","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + auth: mindeeAuth, + actions: [ + mindeePredictDocumentAction, + createCustomApiCallAction({ + baseUrl: () => 'https://api.mindee.net/v1', + auth: mindeeAuth, + authMapping: async (auth) => ({ + Authorization: `Token ${auth}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/mindee/src/lib/actions/predict-document.ts b/packages/pieces/community/mindee/src/lib/actions/predict-document.ts new file mode 100644 index 0000000..df3f33a --- /dev/null +++ b/packages/pieces/community/mindee/src/lib/actions/predict-document.ts @@ -0,0 +1,82 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createReadStream } from 'fs'; +import FormData from 'form-data'; +import { mindeeAuth } from '../..'; + +export const mindeePredictDocumentAction = createAction({ + auth: mindeeAuth, + name: 'mindee_predict_document', + displayName: 'Extract Document', + description: 'Parse details of a document using OCR.', + props: { + account_name: Property.ShortText({ + displayName: 'Account Name', + description: + 'Refers to your username or organization name with which you signed up with.', + required: true, + defaultValue: 'mindee', + }), + api_name: Property.StaticDropdown({ + displayName: 'API Name', + description: 'Refers to the name of the API your are using.', + required: true, + defaultValue: 'full', + options: { + disabled: false, + options: [ + { + value: 'bank_account_details/v1', + label: 'Bank Account Details OCR', + }, + { value: 'expense_reports/v4', label: 'Receipt OCR' }, + { value: 'passport/v1', label: 'Passport OCR' }, + { value: 'invoices/v3', label: 'Invoice OCR' }, + { value: 'proof_of_address/v1', label: 'Proof of Address OCR' }, + { value: 'financial_document/v1', label: 'Financial Documents OCR' }, + ], + }, + }), + file: Property.LongText({ + displayName: 'File URL', + description: + 'Remote file URL or Base64 string. We currently support .pdf (slower), .jpg, .png, .webp, .tiff and .heic formats', + required: true, + }), + }, + run: async ({ auth, propsValue: { api_name, account_name, file } }) => { + let headers, + body = {}; + + try { + const form = new FormData(); + + if (['https:', 'http:'].includes(new URL(file).protocol)) + form.append('document', await getRemoteFile(file)); + else form.append('document', createReadStream(file)); + + body = form; + headers = { ...form.getHeaders() }; + } catch (_) { + body = { document: file }; + headers = { 'Content-Type': 'application/json' }; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.mindee.net/v1/products/${account_name}/${api_name}/predict`, + headers: { + Authorization: `Token ${auth as string}`, + ...headers, + }, + body: body, + }); + + return response.body; + }, +}); + +async function getRemoteFile(url: string): Promise { + const response = await fetch(url); + return await response.arrayBuffer(); +} diff --git a/packages/pieces/community/mindee/tsconfig.json b/packages/pieces/community/mindee/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/mindee/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/mindee/tsconfig.lib.json b/packages/pieces/community/mindee/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/mindee/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/mixpanel/.eslintrc.json b/packages/pieces/community/mixpanel/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/mixpanel/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/mixpanel/README.md b/packages/pieces/community/mixpanel/README.md new file mode 100644 index 0000000..8b392af --- /dev/null +++ b/packages/pieces/community/mixpanel/README.md @@ -0,0 +1,7 @@ +# pieces-mixpanel + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-mixpanel` to build the library. diff --git a/packages/pieces/community/mixpanel/package.json b/packages/pieces/community/mixpanel/package.json new file mode 100644 index 0000000..709b239 --- /dev/null +++ b/packages/pieces/community/mixpanel/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-mixpanel", + "version": "0.0.5" +} \ No newline at end of file diff --git a/packages/pieces/community/mixpanel/project.json b/packages/pieces/community/mixpanel/project.json new file mode 100644 index 0000000..7837d2d --- /dev/null +++ b/packages/pieces/community/mixpanel/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-mixpanel", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/mixpanel/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/mixpanel", + "tsConfig": "packages/pieces/community/mixpanel/tsconfig.lib.json", + "packageJson": "packages/pieces/community/mixpanel/package.json", + "main": "packages/pieces/community/mixpanel/src/index.ts", + "assets": [ + "packages/pieces/community/mixpanel/*.md", + { + "input": "packages/pieces/community/mixpanel/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-mixpanel {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/mixpanel/src/index.ts b/packages/pieces/community/mixpanel/src/index.ts new file mode 100644 index 0000000..9590515 --- /dev/null +++ b/packages/pieces/community/mixpanel/src/index.ts @@ -0,0 +1,35 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { trackEvent } from './lib/actions/track-event'; + +export const mixpanelAuth = PieceAuth.SecretText({ + displayName: 'Mixpanel token', + required: true, + description: ` + The Mixpanel token associated with your project. You can find your Mixpanel token in the project settings dialog in the Mixpanel app. + `, +}); + +export const mixpanel = createPiece({ + displayName: 'Mixpanel', + description: 'Simple and powerful product analytics that helps everyone make better decisions', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/mixpanel.png', + authors: ["yann120","kishanprmr","MoShizzle","abuaboud"], + auth: mixpanelAuth, + categories: [PieceCategory.BUSINESS_INTELLIGENCE], + actions: [ + trackEvent, + createCustomApiCallAction({ + baseUrl: () => 'https://api.mixpanel.com', + auth: mixpanelAuth, + authMapping: async (auth) => ({ + Authorization: `Basic ${Buffer.from(auth as string).toString( + 'base64' + )}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/mixpanel/src/lib/actions/track-event.ts b/packages/pieces/community/mixpanel/src/lib/actions/track-event.ts new file mode 100644 index 0000000..767752f --- /dev/null +++ b/packages/pieces/community/mixpanel/src/lib/actions/track-event.ts @@ -0,0 +1,67 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { mixpanelAuth } from '../../index'; + +const API_URL = 'https://api.mixpanel.com'; + +export const trackEvent = createAction({ + name: 'track_event', + auth: mixpanelAuth, + displayName: 'Track Event', + description: 'Send an Event to Mixpanel.', + props: { + event: Property.ShortText({ + displayName: 'Event', + description: + "A name for this Event. For example, 'Brand Mentioned in Tweet' or 'Payment Made'.", + required: true, + }), + distinct_id: Property.ShortText({ + displayName: 'Distinct ID (Profile ID)', + description: + 'A way to uniquely identify your users (or more generally, profiles). If you are sending Profiles to Mixpanel in addition to events, this property value should be identical to the Distinct ID property attached to the Profile so that you can connect events to people records.', + required: false, + }), + event_properties: Property.Object({ + displayName: 'Event Properties', + description: + "Event Properties are bits of extra information that you send along with your Events describing the details of that action. They are usually specific to the Event they’re describing and don’t apply universally to other Events. Leveraging Event Properties allows you to conduct deeper analysis to better understand user behavior for a specific action. For example, a 'Song Added to Playlist' event could have 'Artist' and 'Playlist' as the properties. Properties are sent as key-value pairs where the key is the property name and the value is the property value.", + required: false, + }), + }, + async run(context) { + const projectToken = context.auth; + const { event, distinct_id, event_properties } = context.propsValue; + + const eventPayload = [ + { + event, + properties: { + time: Math.floor(Date.now() / 1000), + distinct_id, + ...event_properties, + }, + }, + ]; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${API_URL}/import`, + body: eventPayload, + headers: { + 'Content-Type': 'application/json', + }, + authentication: { + type: AuthenticationType.BASIC, + username: projectToken, + password: '', + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/mixpanel/tsconfig.json b/packages/pieces/community/mixpanel/tsconfig.json new file mode 100644 index 0000000..a87e91a --- /dev/null +++ b/packages/pieces/community/mixpanel/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json", + "composite": true + } + ] +} diff --git a/packages/pieces/community/mixpanel/tsconfig.lib.json b/packages/pieces/community/mixpanel/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/mixpanel/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/monday/.eslintrc.json b/packages/pieces/community/monday/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/monday/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/monday/README.md b/packages/pieces/community/monday/README.md new file mode 100644 index 0000000..ee32176 --- /dev/null +++ b/packages/pieces/community/monday/README.md @@ -0,0 +1,7 @@ +# pieces-monday + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-monday` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/monday/package.json b/packages/pieces/community/monday/package.json new file mode 100644 index 0000000..850c17e --- /dev/null +++ b/packages/pieces/community/monday/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-monday", + "version": "0.2.12" +} diff --git a/packages/pieces/community/monday/project.json b/packages/pieces/community/monday/project.json new file mode 100644 index 0000000..ca7b4c5 --- /dev/null +++ b/packages/pieces/community/monday/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-monday", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/monday/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/monday", + "tsConfig": "packages/pieces/community/monday/tsconfig.lib.json", + "packageJson": "packages/pieces/community/monday/package.json", + "main": "packages/pieces/community/monday/src/index.ts", + "assets": [ + "packages/pieces/community/monday/*.md", + { + "input": "packages/pieces/community/monday/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/monday/src/index.ts b/packages/pieces/community/monday/src/index.ts new file mode 100644 index 0000000..430b56a --- /dev/null +++ b/packages/pieces/community/monday/src/index.ts @@ -0,0 +1,57 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createColumnAction } from './lib/actions/create-column'; +import { createGroupAction } from './lib/actions/create-group'; +import { createItemAction } from './lib/actions/create-item'; +import { createUpdateAction } from './lib/actions/create-update'; +import { getBoardItemValuesAction } from './lib/actions/get-board-values'; +import { getItemsColumnValuesAction } from './lib/actions/get-column-values'; +import { updateColumnValuesOfItemAction } from './lib/actions/update-column-values-of-item'; +import { updateItemNameAction } from './lib/actions/update-item-name'; +import { newItemInBoardTrigger } from './lib/triggers/new-item-in-board'; +import { specificColumnValueUpdatedTrigger } from './lib/triggers/specific-column-updated'; +import { uploadFileToColumnAction } from './lib/actions/upload-file-to-column'; + +const markdown = ` +1.Log into your monday.com account.\n +2.Click on your avatar/profile picture in the top right corner.\n +3.Select **Administration** (this requires you to have admin permissions).\n +4.Go to the **API** section.\n +5.Copy your personal token`; + +export const mondayAuth = PieceAuth.SecretText({ + displayName: 'API v2 Token', + description: markdown, + required: true, +}); + +export const monday = createPiece({ + displayName: 'monday.com', + description: 'Work operating system for businesses', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/monday.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: [ + 'kanarelo', + 'haseebrehmanpc', + 'kishanprmr', + 'MoShizzle', + 'AbdulTheActivePiecer', + 'khaledmashaly', + 'abuaboud', + ], + auth: mondayAuth, + actions: [ + createColumnAction, + createGroupAction, + createItemAction, + createUpdateAction, + getBoardItemValuesAction, + getItemsColumnValuesAction, + updateColumnValuesOfItemAction, + updateItemNameAction, + uploadFileToColumnAction, + ], + triggers: [newItemInBoardTrigger, specificColumnValueUpdatedTrigger], +}); diff --git a/packages/pieces/community/monday/src/lib/actions/create-column.ts b/packages/pieces/community/monday/src/lib/actions/create-column.ts new file mode 100644 index 0000000..16cc010 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/create-column.ts @@ -0,0 +1,37 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../..'; +import { makeClient, mondayCommon } from '../common'; +import { COLUMN_TYPE_OPTIONS } from '../common/constants'; + +export const createColumnAction = createAction({ + auth: mondayAuth, + name: 'monday_create_column', + displayName: 'Create Column', + description: 'Creates a new column in board.', + props: { + workspace_id: mondayCommon.workspace_id(true), + board_id: mondayCommon.board_id(true), + column_title: Property.ShortText({ + displayName: 'Column Title', + required: true, + }), + column_type: Property.StaticDropdown({ + displayName: 'Column Type', + required: true, + options: { + disabled: false, + options: COLUMN_TYPE_OPTIONS, + }, + }), + }, + async run(context) { + const { board_id, column_title, column_type } = context.propsValue; + + const client = makeClient(context.auth as string); + return await client.createColumn({ + boardId: board_id as string, + columnTitle: column_title as string, + columnType: column_type as string, + }); + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/create-group.ts b/packages/pieces/community/monday/src/lib/actions/create-group.ts new file mode 100644 index 0000000..d08c607 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/create-group.ts @@ -0,0 +1,27 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../..'; +import { makeClient, mondayCommon } from '../common'; + +export const createGroupAction = createAction({ + auth: mondayAuth, + name: 'monday_create_group', + displayName: 'Create Group', + description: 'Creates a new group in board.', + props: { + workspace_id: mondayCommon.workspace_id(true), + board_id: mondayCommon.board_id(true), + group_name: Property.ShortText({ + displayName: 'Group Name', + required: true, + }), + }, + async run(context) { + const { board_id, group_name } = context.propsValue; + + const client = makeClient(context.auth as string); + return await client.createGroup({ + boardId: board_id as string, + groupName: group_name as string, + }); + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/create-item.ts b/packages/pieces/community/monday/src/lib/actions/create-item.ts new file mode 100644 index 0000000..c2085a2 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/create-item.ts @@ -0,0 +1,70 @@ +import { + DynamicPropsValue, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { mondayAuth } from '../..'; +import { makeClient, mondayCommon } from '../common'; +import { + convertPropValueToMondayColumnValue, + generateColumnIdTypeMap, +} from '../common/helper'; + +export const createItemAction = createAction({ + auth: mondayAuth, + name: 'monday_create_item', + displayName: 'Create Item', + description: 'Creates a new item inside a board.', + props: { + workspace_id: mondayCommon.workspace_id(true), + board_id: mondayCommon.board_id(true), + group_id: mondayCommon.group_id(false), + item_name: Property.ShortText({ + displayName: 'Item Name', + description: 'Item Name', + required: true, + }), + column_values: mondayCommon.columnValues, + create_labels_if_missing: Property.Checkbox({ + displayName: 'Create Labels if Missing', + description: + 'Creates status/dropdown labels if they are missing. This requires permission to change the board structure.', + defaultValue: false, + required: false, + }), + }, + async run(context) { + const { board_id, item_name, create_labels_if_missing } = + context.propsValue; + const group_id = context.propsValue.group_id!; + const columnValuesInput = context.propsValue.column_values; + const mondayColumnValues: DynamicPropsValue = {}; + + const client = makeClient(context.auth as string); + const res = await client.listBoardColumns({ + boardId: board_id as unknown as string, + }); + const columns = res.data.boards[0]?.columns; + + // map board column id with column type + const columnIdTypeMap = generateColumnIdTypeMap(columns); + + Object.keys(columnValuesInput).forEach((key) => { + if (columnValuesInput[key] !== '') { + const columnType: string = columnIdTypeMap[key]; + mondayColumnValues[key] = convertPropValueToMondayColumnValue( + columnType, + columnValuesInput[key] + ); + } + }); + + return await client.createItem({ + itemName: item_name, + boardId: board_id, + groupId: group_id, + columnValues: JSON.stringify(mondayColumnValues), + createLabels: create_labels_if_missing ?? false, + }); + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/create-update.ts b/packages/pieces/community/monday/src/lib/actions/create-update.ts new file mode 100644 index 0000000..969294f --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/create-update.ts @@ -0,0 +1,29 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../..'; +import { makeClient } from '../common'; + +export const createUpdateAction = createAction({ + auth: mondayAuth, + name: 'monday_create_update', + displayName: 'Create Update', + description: 'Creates a new update.', + props: { + item_id: Property.ShortText({ + displayName: 'Item ID', + required: true, + }), + body: Property.LongText({ + displayName: 'Body', + required: true, + }), + }, + async run(context) { + const { item_id, body } = context.propsValue; + + const client = makeClient(context.auth as string); + return await client.createUpdate({ + itemId: item_id as string, + body: body as string, + }); + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/get-board-values.ts b/packages/pieces/community/monday/src/lib/actions/get-board-values.ts new file mode 100644 index 0000000..65e497e --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/get-board-values.ts @@ -0,0 +1,40 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../..'; +import { makeClient, mondayCommon } from '../common'; +import { parseMondayColumnValue } from '../common/helper'; + +export const getBoardItemValuesAction = createAction({ + auth: mondayAuth, + name: 'monday_get_board_values', + displayName: 'Get Board Values', + description: "Gets a list of board's items.", + props: { + workspace_id: mondayCommon.workspace_id(true), + board_id: mondayCommon.board_id(true), + column_ids: mondayCommon.columnIds(false), + }, + async run(context) { + const { board_id, column_ids } = context.propsValue; + + const client = makeClient(context.auth as string); + const res = await client.getBoardItemValues({ + boardId: board_id as string, + columnIds: column_ids as string[], + }); + const items = res.data.boards[0].items_page.items; + + const result = []; + for (const item of items) { + const transformedValues: Record = { + id: item.id, + name: item.name, + }; + for (const column of item.column_values) { + transformedValues[column.id] = parseMondayColumnValue(column); + } + result.push(transformedValues); + } + + return result; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/get-column-values.ts b/packages/pieces/community/monday/src/lib/actions/get-column-values.ts new file mode 100644 index 0000000..5b2da8e --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/get-column-values.ts @@ -0,0 +1,36 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../..'; +import { makeClient, mondayCommon } from '../common'; +import { parseMondayColumnValue } from '../common/helper'; + +export const getItemsColumnValuesAction = createAction({ + auth: mondayAuth, + name: 'monday_get_item_column_values', + displayName: "Get an Item's Column Values", + description: 'Gets column values of an item.', + props: { + workspace_id: mondayCommon.workspace_id(true), + board_id: mondayCommon.board_id(true), + item_id: mondayCommon.item_id(true), + column_ids: mondayCommon.columnIds(false), + }, + async run(context) { + const { board_id, item_id, column_ids } = context.propsValue; + + const client = makeClient(context.auth as string); + const res = await client.getItemColumnValues({ + boardId: board_id as string, + itemId: item_id as string, + columnIds: column_ids as string[], + }); + const item = res.data.boards[0].items_page.items[0]; + const transformedValues: Record = { + id: item.id, + name: item.name, + }; + for (const column of item.column_values) { + transformedValues[column.id] = parseMondayColumnValue(column); + } + return transformedValues; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/update-column-values-of-item.ts b/packages/pieces/community/monday/src/lib/actions/update-column-values-of-item.ts new file mode 100644 index 0000000..e3801cd --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/update-column-values-of-item.ts @@ -0,0 +1,53 @@ +import { + DynamicPropsValue, + createAction, +} from '@activepieces/pieces-framework'; +import { mondayAuth } from '../..'; +import { makeClient, mondayCommon } from '../common'; +import { + convertPropValueToMondayColumnValue, + generateColumnIdTypeMap, +} from '../common/helper'; + +export const updateColumnValuesOfItemAction = createAction({ + auth: mondayAuth, + name: 'monday_update_column_values_of_item', + displayName: 'Update Column Values of Specific Item', + description: 'Updates multiple columns values of specific item.', + props: { + workspace_id: mondayCommon.workspace_id(true), + board_id: mondayCommon.board_id(true), + item_id: mondayCommon.item_id(true), + column_values: mondayCommon.columnValues, + }, + async run(context) { + const { board_id, item_id } = context.propsValue; + const columnValuesInput = context.propsValue.column_values; + const mondayColumnValues: DynamicPropsValue = {}; + + const client = makeClient(context.auth as string); + const res = await client.listBoardColumns({ + boardId: board_id as unknown as string, + }); + const columns = res.data.boards[0]?.columns; + + // map board column id with column type + const columnIdTypeMap = generateColumnIdTypeMap(columns); + + Object.keys(columnValuesInput).forEach((key) => { + if (columnValuesInput[key] !== '') { + const columnType: string = columnIdTypeMap[key]; + mondayColumnValues[key] = convertPropValueToMondayColumnValue( + columnType, + columnValuesInput[key] + ); + } + }); + + return await client.updateItem({ + boardId: board_id, + itemId: item_id, + columnValues: JSON.stringify(mondayColumnValues), + }); + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/update-item-name.ts b/packages/pieces/community/monday/src/lib/actions/update-item-name.ts new file mode 100644 index 0000000..7ad9a50 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/update-item-name.ts @@ -0,0 +1,29 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../..'; +import { makeClient, mondayCommon } from '../common'; + +export const updateItemNameAction = createAction({ + auth: mondayAuth, + name: 'monday_update_item_name', + displayName: 'Update Item Name', + description: 'Updates an item name.', + props: { + workspace_id: mondayCommon.workspace_id(true), + board_id: mondayCommon.board_id(true), + item_id: mondayCommon.item_id(true), + name: Property.ShortText({ + displayName: 'New Item name', + required: true, + }), + }, + async run(context) { + const { board_id, item_id, name } = context.propsValue; + + const client = makeClient(context.auth as string); + return await client.updateItem({ + boardId: board_id, + itemId: item_id, + columnValues: JSON.stringify({ name: name }), + }); + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/upload-file-to-column.ts b/packages/pieces/community/monday/src/lib/actions/upload-file-to-column.ts new file mode 100644 index 0000000..554c955 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/upload-file-to-column.ts @@ -0,0 +1,104 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { makeClient, mondayCommon } from '../common'; +import { MondayColumnType } from '../common/constants'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import FormData from 'form-data'; +import { mondayAuth } from '../../'; + +export const uploadFileToColumnAction = createAction({ + auth: mondayAuth, + name: 'monday_upload_file_to_column', + displayName: 'Upload File to Column', + description: 'Upload a file to a column in Monday.', + props: { + workspace_id: mondayCommon.workspace_id(true), + board_id: mondayCommon.board_id(true), + item_id: mondayCommon.item_id(true), + file_column_id: Property.Dropdown({ + displayName: 'File Column ID', + required: true, + refreshers: ['board_id'], + options: async ({ auth, board_id }) => { + if (!auth || !board_id) { + return { + disabled: true, + placeholder: + 'connect your account first and select workspace board.', + options: [], + }; + } + const client = makeClient(auth as string); + const res = await client.listBoardColumns({ + boardId: board_id as string, + }); + return { + disabled: false, + options: res.data.boards[0].columns + .filter((column) => column.type === MondayColumnType.FILE) + .map((column) => { + return { + label: column.title, + value: column.id, + }; + }), + }; + }, + }), + file: Property.File({ + displayName: 'File', + description: 'The file URL or base64 to upload.', + required: true, + }), + file_name: Property.ShortText({ + displayName: 'File Name', + required: true, + }), + }, + async run(context) { + const itemId = context.propsValue.item_id; + const fileColumnId = context.propsValue.file_column_id; + const fileName = context.propsValue.file_name; + const file = context.propsValue.file; + + const formData = new FormData(); + + formData.append( + 'query', + `mutation($item_id: ID!, $column_id: String!, $file: File!) + { + add_file_to_column(item_id: $item_id, column_id: $column_id, file: $file) + { + id + url + name + file_size + file_extension + created_at + } + }` + ); + formData.append( + 'variables', + JSON.stringify({ item_id: itemId, column_id: fileColumnId }) + ); + formData.append('map', JSON.stringify({ file: 'variables.file' })); + formData.append( + 'file', + Buffer.from(file.base64, 'base64'), + fileName || file.filename + ); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.monday.com/v2/file', + headers: { + 'API-Version': '2024-01', + Authorization: context.auth, + ...formData.getHeaders(), + }, + body: formData, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/common/client.ts b/packages/pieces/community/monday/src/lib/common/client.ts new file mode 100644 index 0000000..5078d97 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/common/client.ts @@ -0,0 +1,101 @@ +import mondaySdk from 'monday-sdk-js'; +import { MondayClientSdk } from 'monday-sdk-js/types/client-sdk.interface'; +import { Board, MondayColumn, User } from './models'; +import { mondayGraphQLMutations } from './mutations'; +import { mondayGraphQLQueries } from './queries'; +export class mondayClient { + private client: MondayClientSdk; + constructor(apiKey: string) { + this.client = mondaySdk(); + this.client.setToken(apiKey); + this.client.setApiVersion('2023-10'); + } + async listWorkspcaes() { + return await this.client.api<{ + workspaces: { id: string; name: string }[]; + }>(mondayGraphQLQueries.listWorkspaces); + } + async listWorkspaceBoards(variables: object) { + return await this.client.api<{ + boards: { id: string; name: string; type: string }[]; + }>(mondayGraphQLQueries.listWorkspaceBoards, { variables: variables }); + } + async listBoardGroups(variables: object) { + return await this.client.api<{ boards: Board[] }>( + mondayGraphQLQueries.listBoardGroups, + { variables: variables } + ); + } + async listBoardColumns(variables: object) { + return await this.client.api<{ boards: { columns: MondayColumn[] }[] }>( + mondayGraphQLQueries.listBoardColumns, + { variables: variables } + ); + } + async listBoardItems(variables: object) { + return await this.client.api<{ boards: Board[] }>( + mondayGraphQLQueries.listBoardItems, + { variables: variables } + ); + } + async createItem(variables: object) { + return await this.client.api(mondayGraphQLMutations.createItem, { + variables: variables, + }); + } + async updateItem(variables: object) { + return await this.client.api(mondayGraphQLMutations.updateItem, { + variables: variables, + }); + } + async createWebhook(variables: object) { + return await this.client.api<{ id: string; board_id: string }>( + mondayGraphQLMutations.createWebhook, + { + variables: variables, + } + ); + } + async deleteWebhook(variables: object) { + return await this.client.api(mondayGraphQLMutations.deleteWebhook, { + variables: variables, + }); + } + async listUsers() { + return await this.client.api<{ users: User[] }>( + mondayGraphQLQueries.listUsers + ); + } + async getBoardItemValues(variables: object) { + return await this.client.api<{ boards: Board[] }>( + mondayGraphQLQueries.getBoardItemValues, + { variables: variables } + ); + } + async getItemColumnValues(variables: object) { + return await this.client.api<{ boards: Board[] }>( + mondayGraphQLQueries.getItemColumnValues, + { + variables: variables, + } + ); + } + async createColumn(variables: object) { + return await this.client.api<{ id: string }>( + mondayGraphQLMutations.createColumn, + { variables: variables } + ); + } + async createGroup(variables: object) { + return await this.client.api<{ id: string }>( + mondayGraphQLMutations.createGroup, + { variables: variables } + ); + } + async createUpdate(variables: object) { + return await this.client.api<{ id: string }>( + mondayGraphQLMutations.createUpdate, + { variables: variables } + ); + } +} diff --git a/packages/pieces/community/monday/src/lib/common/constants.ts b/packages/pieces/community/monday/src/lib/common/constants.ts new file mode 100644 index 0000000..0441775 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/common/constants.ts @@ -0,0 +1,199 @@ +export const enum MondayColumnType { + AUTO_NUMBER = 'auto_number', + BOARD_RELATION = 'board_relation', + BUTTON = 'button', + CHECKBOX = 'checkbox', + COLOR_PICKER = 'color_picker', + COUNTRY = 'country', + CREATION_LOG = 'creation_log', + DATE = 'date', + DEPENDENCY = 'dependency', + DOC = 'doc', + DROPDOWN = 'dropdown', + EMAIL = 'email', + FILE = 'file', + FORMULA = 'formula', + HOUR = 'hour', + ITEM_ASSIGNEES = 'item_assignees', + ITEM_ID = 'item_id', + LAST_UPDATED = 'last_updated', + LINK = 'link', + LOCATION = 'location', + LONG_TEXT = 'long_text', + MIRROR = 'mirror', + NAME = 'name', + NUMBERS = 'numbers', + PHONE = 'phone', + PEOPLE = 'people', + PROGRESS = 'progress', + RATING = 'rating', + STATUS = 'status', + SUBTASKS = 'subtasks', + TAGS = 'tags', + TEAM = 'team', + TEXT = 'text', + TIMELINE = 'timeline', + TIME_TRACKING = 'time_tracking', + VOTE = 'vote', + WEEK = 'week', + WORLD_CLOCK = 'world_clock', + UNSUPPORTED = 'unsupported', +} + +export const enum MondayWebhookEventType { + CHANGE_COLUMN_VALUE = 'change_column_value', + CHANGE_STATUS_COLUMN_VALUE = 'change_status_column_value', + CHANGE_SUBITEM_COLUMN_VALUE = 'change_subitem_column_value', + CHANGE_SPECIFIC_COLUMN_VALUE = 'change_specific_column_value', + CHANGE_NAME = 'change_name', + CREATE_ITEM = 'create_item', + ITEM_ARCHIVED = 'item_archived', + ITEM_DELETED = 'item_deleted', + ITEM_MOVED_TO_ANY_GROUP = 'item_moved_to_any_group', + ITEM_MOVED_TO_SPECIFIC_GROUP = 'item_moved_to_specific_group', + ITEM_RESTORED = 'item_restored', + CREATE_SUBITEM = 'create_subitem', + CHANGE_SUBITEM_NAME = 'change_subitem_name', + MOVE_SUBITEM = 'move_subitem', + SUBITEM_ARCHIVED = 'subitem_archived', + SUBITEM_DELETED = 'subitem_deleted', + CREATE_COLUMN = 'create_column', + CREATE_UPDATE = 'create_update', + EDIT_UPDATE = 'edit_update', + DELETE_UPDATE = 'delete_update', + CREATE_SUBITEM_UPDATE = 'create_subitem_update', +} +export const enum BoardType { + BOARD = 'board', + SUB_ITEMS_BOARD = 'sub_items_board', +} +export const MondayNotWritableColumnType = [ + MondayColumnType.UNSUPPORTED, + MondayColumnType.AUTO_NUMBER, + MondayColumnType.NAME, + MondayColumnType.COLOR_PICKER, + MondayColumnType.BUTTON, + MondayColumnType.MIRROR, + MondayColumnType.SUBTASKS, + MondayColumnType.ITEM_ID, + MondayColumnType.CREATION_LOG, + MondayColumnType.FILE, + MondayColumnType.FORMULA, + MondayColumnType.DOC, + MondayColumnType.LAST_UPDATED, + MondayColumnType.PROGRESS, + MondayColumnType.TAGS, + MondayColumnType.TIME_TRACKING, + MondayColumnType.VOTE, +]; + +export const COLUMN_TYPE_OPTIONS = [ + { + label: 'Auto Number', + value: MondayColumnType.AUTO_NUMBER, + }, + { + label: 'Color Picker', + value: MondayColumnType.COLOR_PICKER, + }, + { + label: 'Checkbox', + value: MondayColumnType.CHECKBOX, + }, + { + label: 'Country', + value: MondayColumnType.COUNTRY, + }, + { + label: 'Creation Log', + value: MondayColumnType.CREATION_LOG, + }, + { + label: 'Date', + value: MondayColumnType.DATE, + }, + { + label: 'Dropdown', + value: MondayColumnType.DROPDOWN, + }, + { + label: 'Email', + value: MondayColumnType.EMAIL, + }, + { + label: 'Hour', + value: MondayColumnType.HOUR, + }, + { + label: 'Item ID', + value: MondayColumnType.ITEM_ID, + }, + { + label: 'Last Updated', + value: MondayColumnType.LAST_UPDATED, + }, + { + label: 'Link', + value: MondayColumnType.LINK, + }, + { + label: 'Location', + value: MondayColumnType.LOCATION, + }, + { + label: 'Long Text', + value: MondayColumnType.LONG_TEXT, + }, + { + label: 'Numbers', + value: MondayColumnType.NUMBERS, + }, + { + label: 'People', + value: MondayColumnType.PEOPLE, + }, + { + label: 'Phone', + value: MondayColumnType.PHONE, + }, + { + label: 'Progress Tracking', + value: MondayColumnType.PROGRESS, + }, + { + label: 'Rating', + value: MondayColumnType.RATING, + }, + { + label: 'Status', + value: MondayColumnType.STATUS, + }, + { + label: 'Tags', + value: MondayColumnType.TAGS, + }, + { + label: 'Text', + value: MondayColumnType.TEXT, + }, + { + label: 'Timeline', + value: MondayColumnType.TIMELINE, + }, + { + label: 'Time Tracking', + value: MondayColumnType.TIME_TRACKING, + }, + { + label: 'Vote', + value: MondayColumnType.VOTE, + }, + { + label: 'Week', + value: MondayColumnType.WEEK, + }, + { + label: 'World Clock', + value: MondayColumnType.WORLD_CLOCK, + }, +]; diff --git a/packages/pieces/community/monday/src/lib/common/helper.ts b/packages/pieces/community/monday/src/lib/common/helper.ts new file mode 100644 index 0000000..84b3657 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/common/helper.ts @@ -0,0 +1,374 @@ +import { + DynamicPropsValue, + Property, +} from '@activepieces/pieces-framework'; +import { isEmpty } from '@activepieces/shared'; +import dayjs from 'dayjs'; +import { MondayColumnType } from './constants'; +import { ColumnValue, MondayColumn } from './models'; + +type ColumnIdTypeMap = { + [key: string]: string; +}; +export function generateColumnIdTypeMap( + columns: MondayColumn[] +): ColumnIdTypeMap { + const result: ColumnIdTypeMap = {}; + for (const column of columns) { + result[column.id] = column.type; + } + return result; +} + +// creates activepiece prop type for monday column +export const convertMondayColumnToActivepiecesProp = (column: MondayColumn) => { + switch (column.type) { + case MondayColumnType.CHECKBOX: + return Property.Checkbox({ + displayName: column.title, + required: false, + }); + case MondayColumnType.BOARD_RELATION: + return Property.ShortText({ + displayName: column.title, + description: + 'A list of item IDs to connect with. The items must be on boards that are connected to the column. Example: [125345, 5846475]', + required: false, + }); + case MondayColumnType.COUNTRY: + return Property.ShortText({ + displayName: column.title, + required: false, + description: `The ISO 2-letter code and the country's name are separated by a dash. Example: US-United States`, + }); + case MondayColumnType.DATE: + return Property.DateTime({ + displayName: column.title, + required: false, + description: 'Use YYYY-MM-DD HH:mm:ss format.', + }); + case MondayColumnType.DEPENDENCY: + return Property.ShortText({ + displayName: column.title, + description: + 'A list of item IDs from the same board. Example: [188392, 20339]', + required: false, + }); + case MondayColumnType.DROPDOWN: { + const labels: { id: string; name: string }[] = JSON.parse( + column.settings_str + ).labels; + return Property.StaticMultiSelectDropdown({ + displayName: column.title, + required: false, + options: { + disabled: false, + options: + labels.length > 0 + ? labels.map((label) => { + return { + label: label.name, + value: label.name, + }; + }) + : [], + }, + }); + } + case MondayColumnType.EMAIL: + case MondayColumnType.LINK: + case MondayColumnType.TEXT: + return Property.ShortText({ + displayName: column.title, + required: false, + }); + case MondayColumnType.HOUR: + return Property.ShortText({ + displayName: column.title, + required: false, + description: `Represent time in 24-hour format, like '16:30' or '2:00', ensuring removal of leading zeroes from data (e.g., send '9' instead of '09').`, + }); + case MondayColumnType.LOCATION: + return Property.ShortText({ + displayName: column.title, + required: false, + description: `Enter location details in the following format: **latitude|longitude|address(optional)**. For example: "37.7749|-122.4194|San Francisco, CA, USA."`, + }); + case MondayColumnType.LONG_TEXT: + return Property.LongText({ + displayName: column.title, + required: false, + }); + case MondayColumnType.NUMBERS: + return Property.Number({ + displayName: column.title, + required: false, + }); + case MondayColumnType.PHONE: + return Property.ShortText({ + displayName: column.title, + required: false, + description: `Enter your phone number along with the country's ISO 2-letter code, separated by a dash. For ezample, 1234567890-US.`, + }); + case MondayColumnType.RATING: + return Property.Number({ + displayName: column.title, + required: false, + description: `A number between 1 and 5.For example, 3.`, + }); + case MondayColumnType.STATUS: { + const labels = JSON.parse(column.settings_str).labels; + const options: { label: string; value: string }[] = []; + Object.keys(labels).forEach((key) => { + if (labels[key] !== '') { + options.push({ value: labels[key], label: labels[key] }); + } + }); + return Property.StaticDropdown({ + displayName: column.title, + required: false, + options: { + disabled: false, + options: options, + }, + }); + } + case MondayColumnType.TIMELINE: + return Property.ShortText({ + displayName: column.title, + required: false, + description: `Enter the start and end dates in the YYYY-MM-DD format, separated by a symbol of semicolon(;) symbol. For example: '2022-01-01;2022-12-31`, + }); + case MondayColumnType.WEEK: + return Property.ShortText({ + displayName: column.title, + required: false, + description: `Enter the start and end dates in the YYYY-MM-DD format, separated by a symbol of semicolon(;) symbol. The dates must be 7 days apart (inclusive of the first and last date).\n For example: '2019-06-10;2019-06-16`, + + }); + case MondayColumnType.WORLD_CLOCK: + return Property.ShortText({ + displayName: column.title, + required: false, + description: `Enter the timezone in the 'Continent/City' format, for example, Europe/London.`, + }); + default: + return null; + } +}; +export const convertPropValueToMondayColumnValue = ( + columnType: string, + propValue: DynamicPropsValue +) => { + switch (columnType) { + case MondayColumnType.CHECKBOX: + return { + checked: propValue ? 'true' : 'false', + }; + case MondayColumnType.BOARD_RELATION: + case MondayColumnType.DEPENDENCY: + return { + item_ids: JSON.parse(propValue as unknown as string), + }; + case MondayColumnType.COUNTRY: + return { + countryCode: propValue.split('-')[0], + countryName: propValue.split('-')[1], + }; + case MondayColumnType.DATE: { + let datevalue = dayjs(propValue as unknown as string); + if (!datevalue.isValid()) { + datevalue = dayjs(); + } + return { + date: datevalue.format('YYYY-MM-DD'), + time: datevalue.format('HH:mm:ss'), + }; + } + case MondayColumnType.DROPDOWN: + return { + labels: propValue, + }; + case MondayColumnType.EMAIL: + return { + email: propValue, + text: propValue, + }; + case MondayColumnType.HOUR: { + const [hour, minute] = propValue.split(':'); + return { + hour: Number(hour) ?? 0, + minute: Number(minute) ?? 0, + }; + } + case MondayColumnType.LINK: + return { + url: propValue, + text: propValue, + }; + case MondayColumnType.LOCATION: { + const [lat, lng, address] = propValue.split('|'); + return { + lat: lat ?? '', + lng: lng ?? '', + address: address ?? '', + }; + } + case MondayColumnType.LONG_TEXT: + return { + text: propValue, + }; + case MondayColumnType.NUMBERS: + return String(propValue); + case MondayColumnType.PEOPLE: { + const res: { id: string; kind: string }[] = []; + if (Array.isArray(propValue)) { + propValue.forEach((person) => { + res.push({ id: person, kind: 'person' }); + }); + } + return { + personsAndTeams: res, + }; + } + case MondayColumnType.PHONE: { + const [phone, countryCode] = propValue.split('-'); + return { + phone: `+${phone}`, + countryShortName: countryCode, + }; + } + case MondayColumnType.RATING: + return { + rating: Number(propValue), + }; + case MondayColumnType.STATUS: + return { + label: propValue, + }; + case MondayColumnType.TEXT: + return propValue; + case MondayColumnType.TIMELINE: + return { + from: propValue.split(';')[0], + to: propValue.split(';')[1], + }; + case MondayColumnType.WEEK: + return { + startDate: propValue.split(';')[0], + endDate: propValue.split(';')[1], + }; + case MondayColumnType.WORLD_CLOCK: + return { + timezone: propValue, + }; + default: + return null; + } +}; + +export const parseMondayColumnValue = (columnValue: ColumnValue) => { + switch (columnValue.type) { + case MondayColumnType.BUTTON: + return columnValue.label; + case MondayColumnType.CHECKBOX: + return JSON.parse(columnValue.value)?.checked ?? false; + case MondayColumnType.BOARD_RELATION: + return columnValue.linked_item_ids ?? []; + case MondayColumnType.DEPENDENCY: + return JSON.parse(columnValue.linked_item_ids ?? '[]'); + case MondayColumnType.SUBTASKS: { + const res: number[] = []; + if (!isEmpty(JSON.parse(columnValue.value))) { + JSON.parse(columnValue.value).linkedPulseIds.map( + (item: { linkedPulseId: number }) => { + res.push(item.linkedPulseId); + } + ); + } + return res; + } + case MondayColumnType.COLOR_PICKER: + return JSON.parse(columnValue.value)?.color.hex ?? null; + case MondayColumnType.COUNTRY: + return JSON.parse(columnValue.value)?.countryName ?? null; + case MondayColumnType.CREATION_LOG: + return JSON.parse(columnValue.value)?.created_at ?? null; + case MondayColumnType.DATE: { + if (isEmpty(columnValue.value)) { + return null; + } + const dateTime = JSON.parse(columnValue.value); + return `${dateTime.date} ${dateTime.time}`; + } + case MondayColumnType.DOC: + return JSON.parse(columnValue.value)?.files[0].linkToFile ?? null; + case MondayColumnType.DROPDOWN: + return JSON.parse(columnValue.value)?.ids ?? []; + case MondayColumnType.EMAIL: + return JSON.parse(columnValue.value)?.email ?? null; + case MondayColumnType.FILE: + return columnValue.text; + case MondayColumnType.HOUR: { + if (isEmpty(columnValue.value)) { + return null; + } + const hourTime = JSON.parse(columnValue.value); + return `${hourTime.hour}:${hourTime.minute}`; + } + case MondayColumnType.ITEM_ID: + return JSON.parse(columnValue.value)?.item_id ?? null; + case MondayColumnType.LAST_UPDATED: + return JSON.parse(columnValue.value).updated_at; + case MondayColumnType.LINK: + return JSON.parse(columnValue.value)?.url ?? null; + case MondayColumnType.LOCATION: + return JSON.parse(columnValue.value)?.address ?? null; + case MondayColumnType.LONG_TEXT: + return JSON.parse(columnValue.value)?.text ?? null; + case MondayColumnType.MIRROR: + return null; + case MondayColumnType.NUMBERS: + return Number(JSON.parse(columnValue.value)); + case MondayColumnType.PEOPLE: { + const people: number[] = []; + if (!isEmpty(columnValue.value)) { + JSON.parse(columnValue.value).personsAndTeams.map( + (item: { id: number; kind: string }) => { + people.push(item.id); + } + ); + } + return people; + } + case MondayColumnType.PHONE: + return JSON.parse(columnValue.value)?.phone ?? null; + case MondayColumnType.RATING: + return JSON.parse(columnValue.value)?.rating ?? null; + case MondayColumnType.STATUS: + return columnValue.label; + case MondayColumnType.TAGS: + return columnValue.tags.map((item: { name: string }) => item.name); + case MondayColumnType.TEXT: + return JSON.parse(columnValue.value); + case MondayColumnType.TIMELINE: { + if (isEmpty(columnValue.value)) { + return null; + } + const timeline = JSON.parse(columnValue.value); + return { from: timeline.from, to: timeline.to }; + } + case MondayColumnType.TIME_TRACKING: + return JSON.parse(columnValue.value)?.duration ?? null; + case MondayColumnType.VOTE: + return columnValue?.vote_count ?? 0; + case MondayColumnType.WEEK: { + return { + startDate: columnValue.start_date, + endDate: columnValue.end_date, + }; + } + case MondayColumnType.WORLD_CLOCK: + return JSON.parse(columnValue.value)?.timezone ?? null; + } +}; diff --git a/packages/pieces/community/monday/src/lib/common/index.ts b/packages/pieces/community/monday/src/lib/common/index.ts new file mode 100644 index 0000000..1849758 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/common/index.ts @@ -0,0 +1,211 @@ +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { mondayClient } from './client'; +import { MondayColumnType, MondayNotWritableColumnType } from './constants'; +import { convertMondayColumnToActivepiecesProp } from './helper'; + +export function makeClient(apiKey: string): mondayClient { + return new mondayClient(apiKey); +} + +export const mondayCommon = { + workspace_id: (required = true) => + Property.Dropdown({ + displayName: 'Workspace ID', + required: required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + + const client = makeClient(auth as string); + const res = await client.listWorkspcaes(); + return { + disabled: false, + options: res.data.workspaces.map((workspace) => { + return { + label: workspace.name, + value: workspace.id, + }; + }), + }; + }, + }), + board_id: (required = true) => + Property.Dropdown({ + displayName: 'Board ID', + required: required, + refreshers: ['workspace_id'], + options: async ({ auth, workspace_id }) => { + if (!auth || !workspace_id) { + return { + disabled: true, + placeholder: + 'connect your account first and select your workspace.', + options: [], + }; + } + + const client = makeClient(auth as string); + const res = await client.listWorkspaceBoards({ + workspaceId: workspace_id as string, + }); + + return { + disabled: false, + options: res.data.boards + .filter((board) => board.type === 'board') + .map((board) => { + return { + label: board.name, + value: board.id, + }; + }), + }; + }, + }), + group_id: (required = false) => + Property.Dropdown({ + displayName: 'Board Group ID', + required: required, + refreshers: ['board_id'], + options: async ({ auth, board_id }) => { + if (!auth || !board_id) { + return { + disabled: true, + placeholder: + 'connect your account first and select workspace board.', + options: [], + }; + } + const client = makeClient(auth as string); + const res = await client.listBoardGroups({ + boardId: board_id as string, + }); + return { + disabled: false, + options: + res.data.boards.length > 0 + ? res.data.boards[0]?.groups.map((group) => ({ + label: group.title, + value: group.id, + })) + : [], + }; + }, + }), + item_id: (required = true) => + Property.Dropdown({ + displayName: 'Item ID', + required: required, + refreshers: ['board_id'], + options: async ({ auth, board_id }) => { + if (!auth || !board_id) { + return { + disabled: true, + placeholder: + 'connect your account first and select workspace board.', + options: [], + }; + } + const client = makeClient(auth as string); + const res = await client.listBoardItems({ + boardId: board_id as string, + }); + + const items = res.data.boards[0]?.items_page.items; + return { + disabled: false, + options: items.map((item) => { + return { + label: item.name, + value: item.id, + }; + }), + }; + }, + }), + columnIds: (required = true) => + Property.MultiSelectDropdown({ + displayName: 'Column IDs', + description: + 'Limit data output by specifying column IDs; leave empty to display all columns.', + required, + refreshers: ['board_id'], + options: async ({ auth, board_id }) => { + if (!auth || !board_id) { + return { + disabled: true, + placeholder: + 'connect your account first and select workspace board.', + options: [], + }; + } + const client = makeClient(auth as string); + const res = await client.listBoardColumns({ + boardId: board_id as string, + }); + return { + disabled: false, + options: res.data.boards[0].columns.map((column) => { + return { + label: column.title, + value: column.id, + }; + }), + }; + }, + }), + columnValues: Property.DynamicProperties({ + displayName: 'Columns', + required: true, + refreshers: ['board_id'], + props: async ({ auth, board_id }) => { + if (!auth || !board_id) { + return { + disabled: true, + placeholder: 'connect your account first and select workspace board.', + options: [], + }; + } + const fields: DynamicPropsValue = {}; + try { + const client = makeClient(auth as unknown as string); + const res = await client.listBoardColumns({ + boardId: board_id as unknown as string, + }); + const columns = res.data.boards[0]?.columns; + for (const column of columns) { + if (!MondayNotWritableColumnType.includes(column.type)) { + if (column.type === MondayColumnType.PEOPLE) { + const userData = await client.listUsers(); + fields[column.id] = Property.StaticMultiSelectDropdown({ + displayName: column.title, + required: false, + options: { + disabled: false, + options: userData.data.users.map((user) => { + return { + label: `${user.name} (${user.email})`, + value: user.id, + }; + }), + }, + }); + } else { + const prop = convertMondayColumnToActivepiecesProp(column); + if (prop != null) fields[column.id] = prop; + } + } + } + } catch (e) { + console.debug(e); + } + return fields; + }, + }), +}; diff --git a/packages/pieces/community/monday/src/lib/common/models.ts b/packages/pieces/community/monday/src/lib/common/models.ts new file mode 100644 index 0000000..6fa5ef9 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/common/models.ts @@ -0,0 +1,80 @@ +import { BoardType, MondayColumnType } from './constants'; + +export interface MondayColumn { + id: string; + title: string; + type: MondayColumnType; + description?: string; + settings_str: string; +} +export interface ColumnValue { + id: string; + value: string; + text: string; + type: MondayColumnType; + [key: string]: any; +} + +export type WorkspaceResponse = { + data: { workspaces: Workspace[] }; + account_id: number; +}; + +export interface Workspace { + id: string; + name: string; +} +export interface Group { + id: string; + title: string; +} +export interface SubItem { + id: string; + board: Board; + group: Group; + subscribers: User[]; + name: string; + email: string; + created_at: string; +} +export interface Item { + id: string; + board: Board; + group: Group; + name: string; + email: string; + created_at: string; + column_values: ColumnValue[]; + subitems: SubItem[]; +} +export interface Board { + id: string; + name: string; + groups: Group[]; + type: BoardType; + items_page: { items: Item[] }; +} + +export interface Update { + body: string; + id: string; + created_at: string; + creator: { + name: string; + id: string; + }; +} + +export interface User { + id: string; + name: string; + email: string; + created_at: string; +} + +export type BoardResponse = { data: { boards: Board[] }; account_id: number }; + +export interface WebhookInformation { + id: string; + board_id: string; +} diff --git a/packages/pieces/community/monday/src/lib/common/mutations.ts b/packages/pieces/community/monday/src/lib/common/mutations.ts new file mode 100644 index 0000000..0bb8597 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/common/mutations.ts @@ -0,0 +1,81 @@ +export const mondayGraphQLMutations = { + createItem: ` + mutation createItem( + $itemName: String! + $boardId: ID! + $groupId: String + $columnValues: JSON + $createLabels: Boolean + ) { + create_item( + item_name: $itemName + board_id: $boardId + group_id: $groupId + column_values: $columnValues + create_labels_if_missing: $createLabels + ) { + id + } + }`, + updateItem: ` + mutation updateItem($itemId: ID!, $boardId: ID!, $columnValues: JSON!) { + change_multiple_column_values( + item_id: $itemId + board_id: $boardId + column_values: $columnValues + ) { + id + name + } + }`, + createWebhook: ` + mutation createWebhook( + $boardId: ID! + $url: String! + $event: WebhookEventType! + $config: JSON + ) { + create_webhook( + board_id: $boardId + url: $url + event: $event + config: $config + ) { + id + board_id + } + }`, + deleteWebhook: ` + mutation deleteWebhook($webhookId: ID!) { + delete_webhook(id: $webhookId) { + id + board_id + } + }`, + createColumn: ` + mutation createColumn( + $boardId: ID! + $columnTitle: String! + $columnType: ColumnType! + ) { + create_column( + board_id: $boardId + title: $columnTitle + column_type: $columnType + ) { + id + } + }`, + createGroup: ` + mutation createGroup($boardId: ID!, $groupName: String!) { + create_group(board_id: $boardId, group_name: $groupName) { + id + } + }`, + createUpdate: ` + mutation createUpdate($itemId: ID!, $body: String!) { + create_update(item_id: $itemId, body: $body) { + id + } + }`, +}; diff --git a/packages/pieces/community/monday/src/lib/common/queries.ts b/packages/pieces/community/monday/src/lib/common/queries.ts new file mode 100644 index 0000000..11043ca --- /dev/null +++ b/packages/pieces/community/monday/src/lib/common/queries.ts @@ -0,0 +1,158 @@ +export const mondayGraphQLQueries = { + listWorkspaces: ` + query listWorkspaces($limit: Int) + { + workspaces(limit: $limit) + { + id + name + } + }`, + listWorkspaceBoards: ` + query listWorkspaceBoards($workspaceId: ID) + { + boards(workspace_ids: [$workspaceId], order_by: created_at) + { + id + name + type + } + }`, + listBoardGroups: ` + query listGroups($boardId: ID!) + { + boards(ids: [$boardId]) + { + groups{ + id + title + } + } + }`, + listBoardColumns: ` + query listBoardColumns($boardId: ID!) + { + boards(ids: [$boardId]) + { + columns{ + id + title + type + settings_str + description + } + } + }`, + listBoardItems: ` + query listBoardItems($boardId: ID!) + { + boards(ids: [$boardId]) + { + items_page + { + items{ + id + name + } + } + } + }`, + listUsers: ` + query listUsers + { + users(newest_first: true) + { + id + name + email + } + }`, + getItemColumnValues: ` + query getItemColumnValues($boardId: ID!,$itemId: ID!,$columnIds: [String!]) + { + boards(ids: [$boardId]) + { + items_page(query_params: {ids: [$itemId]}) + { + items{ + id + name + column_values(ids: $columnIds){ + id + type + value + text + ... on ButtonValue{ + label + } + ... on StatusValue{ + label + } + ... on VoteValue{ + vote_count + } + ... on TagsValue{ + tags{ + name + } + } + ... on BoardRelationValue { + linked_item_ids + } + ... on DependencyValue { + linked_item_ids + } + ... on WeekValue { + start_date + end_date + } + } + } + } + } + }`, + getBoardItemValues: ` + query getItemColumnValues($boardId: ID!,$columnIds: [String!]) + { + boards(ids: [$boardId]) + { + items_page(query_params: {order_by: {column_id: "__last_updated__",direction: desc}}) + { + items{ + id + name + column_values(ids: $columnIds){ + id + type + value + text + ... on ButtonValue{ + label + } + ... on StatusValue{ + label + } + ... on VoteValue{ + vote_count + } + ... on TagsValue{ + tags{ + name + } + } + ... on BoardRelationValue { + linked_item_ids + } + ... on DependencyValue { + linked_item_ids + } + ... on WeekValue { + start_date + end_date + } + } + } + } + } + }`, +}; diff --git a/packages/pieces/community/monday/src/lib/triggers/new-item-in-board.ts b/packages/pieces/community/monday/src/lib/triggers/new-item-in-board.ts new file mode 100644 index 0000000..efda66b --- /dev/null +++ b/packages/pieces/community/monday/src/lib/triggers/new-item-in-board.ts @@ -0,0 +1,118 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { mondayAuth } from '../..'; +import { makeClient, mondayCommon } from '../common'; +import { MondayWebhookEventType } from '../common/constants'; +import { parseMondayColumnValue } from '../common/helper'; +import { WebhookInformation } from '../common/models'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; +export const newItemInBoardTrigger = createTrigger({ + auth: mondayAuth, + name: 'monday_new_item_in_board', + displayName: 'New Item in Board', + description: 'Triggers when a new item is created in board.', + props: { + workspace_id: mondayCommon.workspace_id(true), + board_id: mondayCommon.board_id(true), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: { + event: { + userId: 9603417, + originalTriggerUuid: null, + boardId: 1771812698, + pulseId: 1772099344, + pulseName: 'Create_item webhook', + groupId: 'topics', + groupName: 'Group Title', + groupColor: '#579bfc', + isTopGroup: true, + columnValues: {}, + app: 'monday', + type: 'create_pulse', + triggerTime: '2021-10-11T09:07:28.210Z', + subscriptionId: 73759690, + triggerUuid: 'b5ed2e17c530f43668de130142445cba', + }, + }, + async onEnable(context) { + const { board_id } = context.propsValue; + + const client = makeClient(context.auth as string); + const res = await client.createWebhook({ + boardId: board_id, + url: context.webhookUrl, + event: MondayWebhookEventType.CREATE_ITEM, + }); + await context.store.put( + 'monday_new_item_trigger', + res.data + ); + }, + async onDisable(context) { + const webhook = await context.store.get( + 'monday_new_item_trigger' + ); + if (webhook != null) { + const client = makeClient(context.auth as string); + await client.deleteWebhook({ webhookId: webhook.id }); + } + }, + async run(context) { + const payload = context.payload.body as MondayWebhookPayload; + const transformedValues: Record = {}; + try { + const client = makeClient(context.auth as string); + const res = await client.getItemColumnValues({ + boardId: payload.event.boardId, + itemId: payload.event.pulseId, + }); + const item = res.data.boards[0].items_page.items[0]; + for (const column of item.column_values) { + transformedValues[column.id] = parseMondayColumnValue(column); + } + } catch (e) { + console.error(e); + } + + const enriched = [ + { + ...payload, + columnValues: transformedValues, + }, + ]; + return enriched; + }, + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.BODY_PARAM_PRESENT, + paramName: 'challenge', + }, + async onHandshake(context) { + return { + status: 200, + body: { challenge: (context.payload.body as any)['challenge'] }, + }; + }, +}); + +interface MondayWebhookPayload { + event: { + userId: number; + originalTriggerUuid: null; + boardId: number; + pulseId: number; + pulseName: string; + groupId: string; + groupName: string; + groupColor: string; + isTopGroup: boolean; + columnValues: Record; + app: string; + type: 'create_pulse'; + triggerTime: string; + subscriptionId: number; + triggerUuid: string; + }; +} diff --git a/packages/pieces/community/monday/src/lib/triggers/specific-column-updated.ts b/packages/pieces/community/monday/src/lib/triggers/specific-column-updated.ts new file mode 100644 index 0000000..bba9b94 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/triggers/specific-column-updated.ts @@ -0,0 +1,123 @@ +import { + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { mondayAuth } from '../..'; +import { makeClient, mondayCommon } from '../common'; +import { + MondayNotWritableColumnType, + MondayWebhookEventType, +} from '../common/constants'; +import { WebhookInformation } from '../common/models'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; +export const specificColumnValueUpdatedTrigger = createTrigger({ + auth: mondayAuth, + name: 'monday_specific_column_updated', + displayName: 'Specific Column Value Updated in Board', + description: 'Triggers when a specific column value is updated in board.', + props: { + workspace_id: mondayCommon.workspace_id(true), + board_id: mondayCommon.board_id(true), + column_id: Property.Dropdown({ + displayName: 'Column ID', + required: true, + refreshers: ['board_id'], + options: async ({ auth, board_id }) => { + if (!auth || !board_id) { + return { + disabled: true, + placeholder: + 'connect your account first and select workspace board.', + options: [], + }; + } + const client = makeClient(auth as string); + const res = await client.listBoardColumns({ + boardId: board_id as string, + }); + return { + disabled: false, + options: res.data.boards[0].columns + .filter( + (column) => !MondayNotWritableColumnType.includes(column.type) + ) + .map((column) => { + return { + label: column.title, + value: column.id, + }; + }), + }; + }, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: { + event: { + app: 'monday', + type: 'update_column_value', + triggerTime: '2024-01-08T04:41:55.245Z', + subscriptionId: 3209024, + userId: 53812737, + originalTriggerUuid: null, + boardId: 1835745535, + groupId: 'topics', + pulseId: 1835700420, + pulseName: 'Sample Item', + columnId: 'country', + columnType: 'country', + columnTitle: 'Country', + value: { + countryCode: 'AW', + countryName: 'Aruba', + changed_at: '2024-01-08T04:42:00.109Z', + }, + previousValue: { + changed_at: '2024-01-08T04:41:39.461Z', + countryCode: 'IO', + countryName: 'British Indian Ocean Territory', + }, + changedAt: 1704688953.239433, + isTopGroup: true, + triggerUuid: '72a1ec82ea678e03b55b050711b71e9d', + }, + }, + async onEnable(context) { + const { board_id, column_id } = context.propsValue; + + const client = makeClient(context.auth as string); + const res = await client.createWebhook({ + boardId: board_id, + url: context.webhookUrl, + event: MondayWebhookEventType.CHANGE_SPECIFIC_COLUMN_VALUE, + config: JSON.stringify({ columnId: column_id }), + }); + await context.store.put( + 'monday_specific_column_updated', + res.data + ); + }, + async onDisable(context) { + const webhook = await context.store.get( + 'monday_specific_column_updated' + ); + if (webhook != null) { + const client = makeClient(context.auth as string); + await client.deleteWebhook({ webhookId: webhook.id }); + } + }, + async run(context) { + return [context.payload.body]; + }, + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.BODY_PARAM_PRESENT, + paramName: 'challenge', + }, + async onHandshake(context) { + return { + status: 200, + body: { challenge: (context.payload.body as any)['challenge'] }, + }; + }, +}); diff --git a/packages/pieces/community/monday/tsconfig.json b/packages/pieces/community/monday/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/monday/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/monday/tsconfig.lib.json b/packages/pieces/community/monday/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/monday/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/mongodb/.eslintrc.json b/packages/pieces/community/mongodb/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/mongodb/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/mongodb/README.md b/packages/pieces/community/mongodb/README.md new file mode 100644 index 0000000..c156583 --- /dev/null +++ b/packages/pieces/community/mongodb/README.md @@ -0,0 +1,7 @@ +# pieces-mongodb + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-mongodb` to build the library. diff --git a/packages/pieces/community/mongodb/package-lock.json b/packages/pieces/community/mongodb/package-lock.json new file mode 100644 index 0000000..35e7106 --- /dev/null +++ b/packages/pieces/community/mongodb/package-lock.json @@ -0,0 +1,162 @@ +{ + "name": "@activepieces/piece-mongodb", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-mongodb", + "version": "0.0.1", + "dependencies": { + "mongodb": "^6.15.0" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.2.tgz", + "integrity": "sha512-EB0O3SCSNRUFk66iRCpI+cXzIjdswfCs7F6nOC3RAGJ7xr5YhaicvsRwJ9eyzYvYRlCSDUO/c7g4yNulxKC1WA==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/bson": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.3.tgz", + "integrity": "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, + "node_modules/mongodb": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.15.0.tgz", + "integrity": "sha512-ifBhQ0rRzHDzqp9jAQP6OwHSH7dbYIQjD3SbJs9YYk9AikKEettW/9s/tbSFDTpXcRbF+u1aLrhHxDFaYtZpFQ==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.3", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/packages/pieces/community/mongodb/package.json b/packages/pieces/community/mongodb/package.json new file mode 100644 index 0000000..a5da66e --- /dev/null +++ b/packages/pieces/community/mongodb/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-mongodb", + "version": "0.0.1" +} diff --git a/packages/pieces/community/mongodb/project.json b/packages/pieces/community/mongodb/project.json new file mode 100644 index 0000000..3544d35 --- /dev/null +++ b/packages/pieces/community/mongodb/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-mongodb", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/mongodb/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/mongodb", + "tsConfig": "packages/pieces/community/mongodb/tsconfig.lib.json", + "packageJson": "packages/pieces/community/mongodb/package.json", + "main": "packages/pieces/community/mongodb/src/index.ts", + "assets": [ + "packages/pieces/community/mongodb/*.md", + { + "input": "packages/pieces/community/mongodb/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/mongodb/src/index.ts b/packages/pieces/community/mongodb/src/index.ts new file mode 100644 index 0000000..39f509e --- /dev/null +++ b/packages/pieces/community/mongodb/src/index.ts @@ -0,0 +1,96 @@ +import { + PiecePropValueSchema, + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +import actions from './lib/actions'; +import { mongodbConnect } from './lib/common'; + +export const mongodbAuth = PieceAuth.CustomAuth({ + validate: async ({ auth }) => { + try { + await validateAuth(auth); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: (e as Error)?.message, + }; + } + }, + props: { + host: Property.ShortText({ + displayName: 'Host', + required: true, + description: + 'The hostname or address of the MongoDB server (e.g., localhost:27017 or cluster.example.mongodb.net)', + }), + useAtlasUrl: Property.Checkbox({ + displayName: 'Use MongoDB Atlas URL Format', + description: + 'Enable this if connecting to MongoDB Atlas (uses mongodb+srv:// protocol)', + required: true, + defaultValue: false, + }), + database: Property.ShortText({ + displayName: 'Database', + required: false, + description: + 'The MongoDB database to connect to (can be specified per action if left empty)', + }), + username: Property.ShortText({ + displayName: 'Username', + required: true, + description: 'The username to use for connecting to the MongoDB server', + }), + password: PieceAuth.SecretText({ + displayName: 'Password', + description: 'The password to use to identify at the MongoDB server', + required: true, + }), + authSource: Property.ShortText({ + displayName: 'Auth Source', + required: false, + description: 'The database to authenticate against (default: admin)', + defaultValue: 'admin', + }), + }, + required: true, +}); + +const validateAuth = async (auth: PiecePropValueSchema) => { + await propsValidation.validateZod(auth, { + host: z.string().min(1), + useAtlasUrl: z.boolean(), + database: z.string().optional(), + username: z.string().min(1), + password: z.string().optional(), + authSource: z.string().optional(), + }); + + const client = await mongodbConnect(auth); + + await client.db('admin').command({ ping: 1 }); + + await client.close(); + + console.log('MongoDB validation successful'); +}; + +export const mongodb = createPiece({ + displayName: 'MongoDB', + auth: mongodbAuth, + minimumSupportedRelease: '0.36.1', + categories: [PieceCategory.DEVELOPER_TOOLS], + logoUrl: 'https://cdn.activepieces.com/pieces/mongodb.png', + authors: ['denieler'], + actions, + triggers: [], +}); diff --git a/packages/pieces/community/mongodb/src/lib/actions.ts b/packages/pieces/community/mongodb/src/lib/actions.ts new file mode 100644 index 0000000..be68568 --- /dev/null +++ b/packages/pieces/community/mongodb/src/lib/actions.ts @@ -0,0 +1,17 @@ +import findDocuments from './actions/find-documents'; +import insertDocuments from './actions/insert-documents'; +import updateDocuments from './actions/update-documents'; +import deleteDocuments from './actions/delete-documents'; +import findAndUpdateDocuments from './actions/find-and-update-documents'; +import findAndReplaceDocuments from './actions/find-and-replace-documents'; +import aggregateDocuments from './actions/aggregate-documents'; + +export default [ + findDocuments, + insertDocuments, + updateDocuments, + deleteDocuments, + findAndUpdateDocuments, + findAndReplaceDocuments, + aggregateDocuments, +]; diff --git a/packages/pieces/community/mongodb/src/lib/actions/aggregate-documents.ts b/packages/pieces/community/mongodb/src/lib/actions/aggregate-documents.ts new file mode 100644 index 0000000..87792e9 --- /dev/null +++ b/packages/pieces/community/mongodb/src/lib/actions/aggregate-documents.ts @@ -0,0 +1,56 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mongodbAuth } from '../..'; +import { mongodbCommon, mongodbConnect } from '../common'; + +export default createAction({ + auth: mongodbAuth, + name: 'aggregate_documents', + displayName: 'Aggregate Documents', + description: 'Perform aggregation operations on documents in a collection', + props: { + database: mongodbCommon.database, + collection: mongodbCommon.collection(), + pipeline: Property.Json({ + displayName: 'Aggregation Pipeline', + description: 'Array of aggregation stages (e.g., [{"$match": {"status": "active"}}, {"$group": {"_id": "$category", "count": {"$sum": 1}}}])', + required: true, + }), + }, + async run(context) { + const client = await mongodbConnect(context.auth); + + try { + if (!context.propsValue.collection) { + throw new Error('Collection is required'); + } + + if (!context.propsValue.pipeline) { + throw new Error('Aggregation pipeline is required'); + } + + // Use the database from auth + // Use the database from props or auth + const databaseName = context.propsValue.database || context.auth.database; + if (!databaseName) { + throw new Error('Database is required. Please specify it in the connection settings or in this action.'); + } + const db = client.db(databaseName); + const collection = db.collection(context.propsValue.collection); + + const pipeline = context.propsValue.pipeline; + + if (!Array.isArray(pipeline)) { + throw new Error('Aggregation pipeline must be an array of stages'); + } + + const result = await collection.aggregate(pipeline).toArray(); + + return { + result, + count: result.length + }; + } finally { + await client.close(); + } + }, +}); diff --git a/packages/pieces/community/mongodb/src/lib/actions/delete-documents.ts b/packages/pieces/community/mongodb/src/lib/actions/delete-documents.ts new file mode 100644 index 0000000..a0cdeea --- /dev/null +++ b/packages/pieces/community/mongodb/src/lib/actions/delete-documents.ts @@ -0,0 +1,45 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mongodbAuth } from '../..'; +import { mongodbCommon, mongodbConnect } from '../common'; + +export default createAction({ + auth: mongodbAuth, + name: 'delete_documents', + displayName: 'Delete Documents', + description: 'Delete documents from a collection', + props: { + database: mongodbCommon.database, + collection: mongodbCommon.collection(), + filter: Property.Json({ + displayName: 'Filter', + description: 'MongoDB query to select documents to delete (e.g., {"status": "archived"})', + required: true, + }), + }, + async run(context) { + const client = await mongodbConnect(context.auth); + + try { + if (!context.propsValue.collection) { + throw new Error('Collection is required'); + } + + const databaseName = context.propsValue.database || context.auth.database; + if (!databaseName) { + throw new Error('Database is required. Please specify it in the connection settings or in this action.'); + } + const db = client.db(databaseName); + const collection = db.collection(context.propsValue.collection); + + const filter = context.propsValue.filter || {}; + + const result = await collection.deleteMany(filter); + + return { + deletedCount: result.deletedCount, + }; + } finally { + await client.close(); + } + }, +}); diff --git a/packages/pieces/community/mongodb/src/lib/actions/find-and-replace-documents.ts b/packages/pieces/community/mongodb/src/lib/actions/find-and-replace-documents.ts new file mode 100644 index 0000000..c9da091 --- /dev/null +++ b/packages/pieces/community/mongodb/src/lib/actions/find-and-replace-documents.ts @@ -0,0 +1,103 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mongodbAuth } from '../..'; +import { mongodbCommon, mongodbConnect } from '../common'; + +export default createAction({ + auth: mongodbAuth, + name: 'find_and_replace_documents', + displayName: 'Find and Replace Documents', + description: 'Replace documents that match a filter with a new document', + props: { + database: mongodbCommon.database, + collection: mongodbCommon.collection(), + filter: Property.Json({ + displayName: 'Filter', + description: 'MongoDB query to select documents to replace (e.g., {"_id": "123"})', + required: true, + }), + replacement: Property.Json({ + displayName: 'Replacement Document', + description: 'New document that will replace the matched documents', + required: true, + }), + upsert: Property.Checkbox({ + displayName: 'Upsert', + description: 'Insert the document if no documents match the filter', + required: false, + defaultValue: false, + }), + returnDocument: Property.StaticDropdown({ + displayName: 'Return Document', + description: 'Which version of the document to return', + required: false, + defaultValue: 'after', + options: { + options: [ + { label: 'Before Update', value: 'before' }, + { label: 'After Update', value: 'after' }, + ], + }, + }), + }, + async run(context) { + const client = await mongodbConnect(context.auth); + + try { + if (!context.propsValue.collection) { + throw new Error('Collection is required'); + } + + if (!context.propsValue.filter) { + throw new Error('Filter is required'); + } + + if (!context.propsValue.replacement) { + throw new Error('Replacement document is required'); + } + + const databaseName = context.propsValue.database || context.auth.database; + if (!databaseName) { + throw new Error('Database is required. Please specify it in the connection settings or in this action.'); + } + const db = client.db(databaseName); + const collection = db.collection(context.propsValue.collection); + + const filter = context.propsValue.filter; + const replacement = context.propsValue.replacement; + const upsert = context.propsValue.upsert || false; + const returnDocumentPreference = context.propsValue.returnDocument || 'after'; + + let beforeDocument = null; + if (returnDocumentPreference === 'before') { + beforeDocument = await collection.findOne(filter); + } + + const replaceResult = await collection.replaceOne( + filter, + replacement, + { + upsert + } + ); + + let afterDocument = null; + if (returnDocumentPreference === 'after' || (!beforeDocument && replaceResult.matchedCount > 0)) { + const query = replaceResult.upsertedId + ? { _id: replaceResult.upsertedId } + : filter; + afterDocument = await collection.findOne(query); + } + + const document = returnDocumentPreference === 'before' ? beforeDocument : afterDocument; + + return { + matchedCount: replaceResult.matchedCount, + modifiedCount: replaceResult.modifiedCount, + upsertedId: replaceResult.upsertedId, + document, + }; + } finally { + await client.close(); + } + }, +}); diff --git a/packages/pieces/community/mongodb/src/lib/actions/find-and-update-documents.ts b/packages/pieces/community/mongodb/src/lib/actions/find-and-update-documents.ts new file mode 100644 index 0000000..56ad0bc --- /dev/null +++ b/packages/pieces/community/mongodb/src/lib/actions/find-and-update-documents.ts @@ -0,0 +1,82 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mongodbAuth } from '../..'; +import { mongodbCommon, mongodbConnect } from '../common'; + +export default createAction({ + auth: mongodbAuth, + name: 'find_and_update_documents', + displayName: 'Find and Update Documents', + description: 'Find documents and update them, returning the updated documents', + props: { + database: mongodbCommon.database, + collection: mongodbCommon.collection(), + filter: Property.Json({ + displayName: 'Filter', + description: 'MongoDB query to select documents to update (e.g., {"status": "pending"})', + required: true, + }), + update: Property.Json({ + displayName: 'Update', + description: 'MongoDB update operations (e.g., {"$set": {"status": "completed"}})', + required: true, + }), + upsert: Property.Checkbox({ + displayName: 'Upsert', + description: 'Insert a document if no documents match the filter', + required: false, + defaultValue: false, + }), + returnUpdated: Property.Checkbox({ + displayName: 'Return Updated Documents', + description: 'Return the documents after updates are applied', + required: false, + defaultValue: true, + }), + }, + async run(context) { + const client = await mongodbConnect(context.auth); + + try { + if (!context.propsValue.collection) { + throw new Error('Collection is required'); + } + + if (!context.propsValue.filter) { + throw new Error('Filter is required'); + } + + if (!context.propsValue.update) { + throw new Error('Update is required'); + } + + const databaseName = context.propsValue.database || context.auth.database; + if (!databaseName) { + throw new Error('Database is required. Please specify it in the connection settings or in this action.'); + } + const db = client.db(databaseName); + const collection = db.collection(context.propsValue.collection); + + const filter = context.propsValue.filter; + const update = context.propsValue.update; + const upsert = context.propsValue.upsert || false; + const returnUpdated = context.propsValue.returnUpdated !== false; + + const updateResult = await collection.updateMany(filter, update, { upsert }); + + let documents: Record[] = []; + if (returnUpdated) { + documents = await collection.find(filter).toArray(); + } + + return { + matchedCount: updateResult.matchedCount, + modifiedCount: updateResult.modifiedCount, + upsertedCount: updateResult.upsertedCount, + upsertedId: updateResult.upsertedId, + documents: returnUpdated ? documents : undefined, + }; + } finally { + await client.close(); + } + }, +}); diff --git a/packages/pieces/community/mongodb/src/lib/actions/find-documents.ts b/packages/pieces/community/mongodb/src/lib/actions/find-documents.ts new file mode 100644 index 0000000..fa792d1 --- /dev/null +++ b/packages/pieces/community/mongodb/src/lib/actions/find-documents.ts @@ -0,0 +1,91 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mongodbAuth } from '../..'; +import { mongodbCommon, mongodbConnect } from '../common'; + +export default createAction({ + auth: mongodbAuth, + name: 'find_documents', + displayName: 'Find Documents', + description: 'Find documents in a collection', + props: { + database: mongodbCommon.database, + collection: mongodbCommon.collection(), + query: Property.Json({ + displayName: 'Query', + description: 'MongoDB query to filter documents (e.g., {"status": "active"})', + required: false, + defaultValue: {}, + }), + projection: Property.Json({ + displayName: 'Projection', + description: 'Fields to include or exclude (e.g., {"name": 1, "_id": 0})', + required: false, + defaultValue: {}, + }), + sort: Property.Json({ + displayName: 'Sort', + description: 'Sort criteria (e.g., {"createdAt": -1})', + required: false, + defaultValue: {}, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Maximum number of documents to return', + required: false, + }), + skip: Property.Number({ + displayName: 'Skip', + description: 'Number of documents to skip', + required: false, + defaultValue: 0, + }), + }, + async run(context) { + const client = await mongodbConnect(context.auth); + + try { + if (!context.propsValue.collection) { + throw new Error('Collection is required'); + } + + const databaseName = context.propsValue.database || context.auth.database; + if (!databaseName) { + throw new Error('Database is required. Please specify it in the connection settings or in this action.'); + } + const db = client.db(databaseName); + const collection = db.collection(context.propsValue.collection); + + const query = context.propsValue.query || {}; + const projection = context.propsValue.projection || {}; + const sort = context.propsValue.sort || {}; + const limit = context.propsValue.limit || 0; + const skip = context.propsValue.skip || 0; + + let cursor = collection.find(query, { projection }); + + if (Object.keys(sort).length > 0) { + // MongoDB sort needs to be handled with care due to typing constraints + // We'll use a type assertion but in a safer way than 'any' + // The MongoDB driver expects a document with field names and sort direction (1 or -1) + cursor = cursor.sort(sort as Record); + } + + if (skip > 0) { + cursor = cursor.skip(skip); + } + + if (limit > 0) { + cursor = cursor.limit(limit); + } + + const documents = await cursor.toArray(); + + return { + documents, + count: documents.length + }; + } finally { + await client.close(); + } + }, +}); diff --git a/packages/pieces/community/mongodb/src/lib/actions/insert-documents.ts b/packages/pieces/community/mongodb/src/lib/actions/insert-documents.ts new file mode 100644 index 0000000..fe6709d --- /dev/null +++ b/packages/pieces/community/mongodb/src/lib/actions/insert-documents.ts @@ -0,0 +1,63 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mongodbAuth } from '../..'; +import { mongodbCommon, mongodbConnect } from '../common'; + +export default createAction({ + auth: mongodbAuth, + name: 'insert_documents', + displayName: 'Insert Documents', + description: 'Insert one or more documents into a collection', + props: { + database: mongodbCommon.database, + collection: mongodbCommon.collection(), + documents: Property.Json({ + displayName: 'Documents', + description: 'Document(s) to insert. Can be a single document object or an array of documents.', + required: true, + }), + }, + async run(context) { + const client = await mongodbConnect(context.auth); + + try { + if (!context.propsValue.collection) { + throw new Error('Collection is required'); + } + + if (!context.propsValue.documents) { + throw new Error('Documents are required'); + } + + const databaseName = context.propsValue.database || context.auth.database; + if (!databaseName) { + throw new Error('Database is required. Please specify it in the connection settings or in this action.'); + } + const db = client.db(databaseName); + const collection = db.collection(context.propsValue.collection); + + const documents = context.propsValue.documents; + let result; + + if (Array.isArray(documents)) { + // Insert many documents + if (documents.length === 0) { + return { insertedCount: 0, insertedIds: [] }; + } + result = await collection.insertMany(documents); + return { + insertedCount: result.insertedCount, + insertedIds: result.insertedIds, + }; + } else { + // Insert a single document + result = await collection.insertOne(documents); + return { + insertedId: result.insertedId, + acknowledged: result.acknowledged, + }; + } + } finally { + await client.close(); + } + }, +}); diff --git a/packages/pieces/community/mongodb/src/lib/actions/update-documents.ts b/packages/pieces/community/mongodb/src/lib/actions/update-documents.ts new file mode 100644 index 0000000..3d846ab --- /dev/null +++ b/packages/pieces/community/mongodb/src/lib/actions/update-documents.ts @@ -0,0 +1,69 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mongodbAuth } from '../..'; +import { mongodbCommon, mongodbConnect } from '../common'; + +export default createAction({ + auth: mongodbAuth, + name: 'update_documents', + displayName: 'Update Documents', + description: 'Update multiple documents in a collection', + props: { + database: mongodbCommon.database, + collection: mongodbCommon.collection(), + filter: Property.Json({ + displayName: 'Filter', + description: 'MongoDB query to select documents to update (e.g., {"status": "pending"})', + required: true, + }), + update: Property.Json({ + displayName: 'Update', + description: 'MongoDB update operations (e.g., {"$set": {"status": "completed"}})', + required: true, + }), + upsert: Property.Checkbox({ + displayName: 'Upsert', + description: 'Insert a document if no documents match the filter', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const client = await mongodbConnect(context.auth); + + try { + if (!context.propsValue.collection) { + throw new Error('Collection is required'); + } + + if (!context.propsValue.filter) { + throw new Error('Filter is required'); + } + + if (!context.propsValue.update) { + throw new Error('Update is required'); + } + + const databaseName = context.propsValue.database || context.auth.database; + if (!databaseName) { + throw new Error('Database is required. Please specify it in the connection settings or in this action.'); + } + const db = client.db(databaseName); + const collection = db.collection(context.propsValue.collection); + + const filter = context.propsValue.filter; + const update = context.propsValue.update; + const upsert = context.propsValue.upsert || false; + + const result = await collection.updateMany(filter, update, { upsert }); + + return { + matchedCount: result.matchedCount, + modifiedCount: result.modifiedCount, + upsertedCount: result.upsertedCount, + upsertedId: result.upsertedId, + }; + } finally { + await client.close(); + } + }, +}); diff --git a/packages/pieces/community/mongodb/src/lib/common/index.ts b/packages/pieces/community/mongodb/src/lib/common/index.ts new file mode 100644 index 0000000..a4d6fbf --- /dev/null +++ b/packages/pieces/community/mongodb/src/lib/common/index.ts @@ -0,0 +1,127 @@ +import { PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { MongoClient, Db, ServerApiVersion } from 'mongodb'; +import { mongodbAuth } from '../..'; + +export async function mongodbConnect( + auth: PiecePropValueSchema +): Promise { + try { + if (!auth.host || !auth.username || !auth.password) { + throw new Error('Host, username, and password are required'); + } + + // Build connection string based on whether it's Atlas or standard MongoDB + const authSource = auth.authSource || 'admin'; + const protocol = auth.useAtlasUrl ? 'mongodb+srv://' : 'mongodb://'; + + let connectionString = `${protocol}${auth.username}:${encodeURIComponent( + auth.password + )}@${auth.host}`; + + if (auth.database) { + connectionString += `/${auth.database}`; + } + + connectionString += `?authSource=${authSource}`; + + // Add common MongoDB Atlas parameters if using Atlas URL format + const finalConnectionString = auth.useAtlasUrl + ? `${connectionString}&retryWrites=true&w=majority` + : connectionString; + + const client = new MongoClient(finalConnectionString, { + connectTimeoutMS: 10000, // 10 seconds timeout for connection + serverSelectionTimeoutMS: 10000, // 10 seconds timeout for server selection + serverApi: { + version: ServerApiVersion.v1, + strict: true, + deprecationErrors: true, + }, + }); + + await client.connect(); + + // For validation purposes, just ping the server + await client.db('admin').command({ ping: 1 }); + + console.log('MongoDB connection successful'); + return client; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : 'Unknown error'; + throw new Error(`Failed to connect to MongoDB: ${errorMessage}`); + } +} + +export async function getDatabase( + client: MongoClient, + databaseName?: string +): Promise { + return client.db(databaseName); +} + +export async function getCollections( + client: MongoClient, + databaseName: string +): Promise { + try { + const db = client.db(databaseName); + const collections = await db.listCollections().toArray(); + return collections.map((collection) => collection.name); + } catch (error) { + console.error('Error getting collections:', error); + return []; + } +} + +export const mongodbCommon = { + database: Property.ShortText({ + displayName: 'Database', + description: + 'The MongoDB database to connect to (from your authentication)', + required: false, + }), + collection: (required = true) => + Property.Dropdown({ + displayName: 'Collection', + required, + refreshers: ['database'], + options: async ({ auth, database }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect to MongoDB first', + options: [], + }; + } + + const typedAuth = auth as PiecePropValueSchema; + const databaseName = database as string || typedAuth.database; + + if (!databaseName) { + return { + disabled: true, + placeholder: 'Database is required in authentication or action', + options: [], + }; + } + + const client = await mongodbConnect(typedAuth); + + try { + const collections = await getCollections(client, databaseName); + return { + disabled: false, + options: collections.map((collection: string) => { + return { + label: collection, + value: collection, + }; + }), + }; + } finally { + await client.close(); + } + }, + }), +}; diff --git a/packages/pieces/community/mongodb/tsconfig.json b/packages/pieces/community/mongodb/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/mongodb/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/mongodb/tsconfig.lib.json b/packages/pieces/community/mongodb/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/mongodb/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/motion/.eslintrc.json b/packages/pieces/community/motion/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/motion/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/motion/README.md b/packages/pieces/community/motion/README.md new file mode 100644 index 0000000..a45bb68 --- /dev/null +++ b/packages/pieces/community/motion/README.md @@ -0,0 +1,7 @@ +# pieces-motion + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-motion` to build the library. diff --git a/packages/pieces/community/motion/package.json b/packages/pieces/community/motion/package.json new file mode 100644 index 0000000..29bd3ce --- /dev/null +++ b/packages/pieces/community/motion/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-motion", + "version": "0.0.1" +} diff --git a/packages/pieces/community/motion/project.json b/packages/pieces/community/motion/project.json new file mode 100644 index 0000000..fa5526e --- /dev/null +++ b/packages/pieces/community/motion/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-motion", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/motion/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/motion", + "tsConfig": "packages/pieces/community/motion/tsconfig.lib.json", + "packageJson": "packages/pieces/community/motion/package.json", + "main": "packages/pieces/community/motion/src/index.ts", + "assets": [ + "packages/pieces/community/motion/*.md", + { + "input": "packages/pieces/community/motion/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/motion/src/index.ts b/packages/pieces/community/motion/src/index.ts new file mode 100644 index 0000000..edcd097 --- /dev/null +++ b/packages/pieces/community/motion/src/index.ts @@ -0,0 +1,67 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { createTask } from './lib/actions/create-task'; +import { updateTask } from './lib/actions/update-task'; +import { createProject } from './lib/actions/create-project'; +import { getTask } from './lib/actions/get-task'; +import { taskCreated } from './lib/triggers/task-created'; +import { moveTask } from './lib/actions/move-task'; +import { PieceCategory } from '@activepieces/shared'; +import { findTask } from './lib/actions/find-task'; +import { + createCustomApiCallAction, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { BASE_URL } from './lib/common/props'; + +export const motionAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: `You can obtain API key from [API Settings](https://app.usemotion.com/web/settings/api).`, + required: true, + validate: async ({ auth }) => { + try { + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${BASE_URL}/workspaces`, + headers: { + 'X-API-Key': auth, + }, + }); + + return { + valid: true, + }; + } catch { + return { + valid: false, + error: 'Invalid API key.', + }; + } + }, +}); + +export const motion = createPiece({ + displayName: 'Motion', + logoUrl: 'https://cdn.activepieces.com/pieces/motion.png', + categories: [PieceCategory.PRODUCTIVITY], + auth: motionAuth, + authors: ['Sanket6652', 'kishanprmr'], + actions: [ + createTask, + updateTask, + createProject, + getTask, + moveTask, + findTask, + createCustomApiCallAction({ + auth: motionAuth, + baseUrl: () => BASE_URL, + authMapping: async (auth) => { + return { + 'X-API-Key': auth as string, + }; + }, + }), + ], + triggers: [taskCreated], +}); diff --git a/packages/pieces/community/motion/src/lib/actions/create-project.ts b/packages/pieces/community/motion/src/lib/actions/create-project.ts new file mode 100644 index 0000000..1511585 --- /dev/null +++ b/packages/pieces/community/motion/src/lib/actions/create-project.ts @@ -0,0 +1,52 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { motionAuth } from '../../index'; +import { BASE_URL, priority, workspaceId } from '../common/props'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const createProject = createAction({ + auth: motionAuth, + name: 'create-project', + displayName: 'Create Project', + description: 'Create a new project in Motion', + props: { + workspaceId: workspaceId('Workspace ID'), + name: Property.ShortText({ + displayName: 'Project Name', + required: true, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + dueDate: Property.DateTime({ + displayName: 'Due Date', + description: 'ISO 8601 Due date on the project', + required: false, + }), + priority: priority, + labels: Property.Array({ + displayName: 'Labels', + required: false, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/projects`, + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': auth, + }, + body: { + name: propsValue.name, + workspaceId: propsValue.workspaceId, + description: propsValue.description, + dueDate: propsValue.dueDate, + priority: propsValue.priority, + labels: (propsValue.labels as string[]) || [], + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/motion/src/lib/actions/create-task.ts b/packages/pieces/community/motion/src/lib/actions/create-task.ts new file mode 100644 index 0000000..8357569 --- /dev/null +++ b/packages/pieces/community/motion/src/lib/actions/create-task.ts @@ -0,0 +1,72 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { motionAuth } from '../../index'; +import { + BASE_URL, + priority, + projectId, + statusId, + userId, + workspaceId, +} from '../common/props'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + + +export const createTask = createAction({ + auth: motionAuth, + name: 'create-task', + displayName: 'Create Task', + description: 'Creates a new task.', + props: { + workspaceId: workspaceId('Workspace ID'), + name: Property.ShortText({ + displayName: 'Task Name', + required: true, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + dueDate: Property.DateTime({ + displayName: 'Due Date', + required: false, + }), + duration: Property.Number({ + displayName: 'Duration', + description: 'Duration in minutes.', + required: false, + }), + statusId: statusId, + priority: priority, + projectId: projectId, + assigneeId: userId, + labels: Property.Array({ + displayName: 'Labels', + description: 'The names of the labels to be added to the task.', + required: false, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/tasks`, + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': auth, + }, + body: { + name: propsValue.name, + workspaceId: propsValue.workspaceId, + description: propsValue.description, + dueDate: propsValue.dueDate, + duration: propsValue.duration, + status: propsValue.statusId, + priority: propsValue.priority, + projectId: propsValue.projectId, + assigneeId: propsValue.assigneeId, + labels: (propsValue.labels as string[]) || [], + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/motion/src/lib/actions/find-task.ts b/packages/pieces/community/motion/src/lib/actions/find-task.ts new file mode 100644 index 0000000..7068649 --- /dev/null +++ b/packages/pieces/community/motion/src/lib/actions/find-task.ts @@ -0,0 +1,86 @@ +import { motionAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + BASE_URL, + projectId, + statusId, + userId, + workspaceId, +} from '../common/props'; +import { + httpClient, + HttpMethod, + QueryParams, +} from '@activepieces/pieces-common'; + +export const findTask = createAction({ + auth: motionAuth, + name: 'find-task', + displayName: 'Find Task', + description: 'Finds an existing task.', + props: { + workspaceId: workspaceId('Workspace ID'), + includeAllStatuses: Property.Checkbox({ + displayName: 'Include All Statuses', + required: false, + }), + name: Property.ShortText({ + displayName: 'Task Name', + required: true, + }), + status: statusId, + assigneeId: userId, + projectId: projectId, + }, + async run(context) { + const { + workspaceId, + includeAllStatuses, + name, + status, + assigneeId, + projectId, + } = context.propsValue; + + const result = []; + + let nextCursor: string | undefined; + + const qs: QueryParams = { + name, + workspaceId, + includeAllStatuses: includeAllStatuses ? 'true' : 'false', + }; + if (status) qs['status'] = status; + if (projectId) qs['projectId'] = projectId; + if (assigneeId) qs['assigneeId'] = assigneeId; + + do { + if (nextCursor) { + qs['cursor'] = nextCursor; + } + + const response = await httpClient.sendRequest<{ + tasks: { id: string; name: string }[]; + meta: { pageSize: number; nextCursor?: string }; + }>({ + method: HttpMethod.GET, + url: `${BASE_URL}/tasks`, + headers: { + 'X-API-Key': context.auth as string, + }, + queryParams: qs, + }); + + const tasks = response.body.tasks ?? []; + result.push(...tasks); + + nextCursor = response.body.meta.nextCursor; + } while (nextCursor); + + return { + found: result.length > 0, + result, + }; + }, +}); diff --git a/packages/pieces/community/motion/src/lib/actions/get-task.ts b/packages/pieces/community/motion/src/lib/actions/get-task.ts new file mode 100644 index 0000000..3ead759 --- /dev/null +++ b/packages/pieces/community/motion/src/lib/actions/get-task.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { motionAuth } from '../../index'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { BASE_URL } from '../common/props'; + +export const getTask = createAction({ + auth: motionAuth, + name: 'get-task', + displayName: 'Get Task', + description: 'Get details of a specific task by ID.', + props: { + taskId: Property.ShortText({ + displayName: 'Task ID', + required: true, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest( { + method: HttpMethod.GET, + url:`${BASE_URL}/tasks/${propsValue.taskId}`, + headers: { + 'X-API-Key': auth, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/motion/src/lib/actions/move-task.ts b/packages/pieces/community/motion/src/lib/actions/move-task.ts new file mode 100644 index 0000000..5cdb779 --- /dev/null +++ b/packages/pieces/community/motion/src/lib/actions/move-task.ts @@ -0,0 +1,33 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { motionAuth } from '../../index'; +import { BASE_URL, taskId, workspaceId } from '../common/props'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const moveTask = createAction({ + auth: motionAuth, + name: 'moveTask', + displayName: 'Move Task', + description: 'Moves a task to a different workspace.', + props: { + workspaceId:workspaceId('Current Workspace'), + taskId:taskId, + newWorkspaceId: workspaceId('Target Workspace') + }, + async run({ auth, propsValue }) { + const { taskId, newWorkspaceId } = propsValue; + + const response = await httpClient.sendRequest({ + method: HttpMethod.PATCH, + url:`${BASE_URL}/tasks/${taskId}/move`, + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': auth, + }, + body: { + workspaceId:newWorkspaceId + } + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/motion/src/lib/actions/update-task.ts b/packages/pieces/community/motion/src/lib/actions/update-task.ts new file mode 100644 index 0000000..9b41583 --- /dev/null +++ b/packages/pieces/community/motion/src/lib/actions/update-task.ts @@ -0,0 +1,83 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { motionAuth } from '../../index'; +import { + BASE_URL, + priority, + projectId, + statusId, + taskId, + userId, + workspaceId, +} from '../common/props'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const updateTask = createAction({ + auth: motionAuth, + name: 'update-task', + displayName: 'Update Task', + description: 'Update an existing task in Motion', + props: { + workspaceId: workspaceId('Workspace ID'), + taskId: taskId, + name: Property.ShortText({ + displayName: 'Task Name', + required: false, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + dueDate: Property.DateTime({ + displayName: 'Due Date', + required: false, + }), + duration: Property.Number({ + displayName: 'Duration', + description: 'Duration in minutes.', + required: false, + }), + statusId: statusId, + priority: priority, + projectId: projectId, + assigneeId: userId, + labels: Property.Array({ + displayName: 'Labels', + description: 'The names of the labels to be added to the task', + required: false, + }), + }, + async run({ auth, propsValue }) { + const { + name, + workspaceId, + description, + dueDate, + duration, + statusId, + priority, + assigneeId, + } = propsValue; + const labels = propsValue.labels ?? []; + const response = await httpClient.sendRequest({ + method: HttpMethod.PATCH, + url: `${BASE_URL}/tasks/${propsValue.taskId}`, + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': auth, + }, + body: { + name, + workspaceId, + description, + priority, + dueDate, + duration, + staus: statusId, + assigneeId, + labels: labels.length > 0 ? labels : undefined, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/motion/src/lib/common/props.ts b/packages/pieces/community/motion/src/lib/common/props.ts new file mode 100644 index 0000000..a6eadf4 --- /dev/null +++ b/packages/pieces/community/motion/src/lib/common/props.ts @@ -0,0 +1,212 @@ +import { DropdownOption, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + QueryParams, +} from '@activepieces/pieces-common'; + +export const BASE_URL = 'https://api.usemotion.com/v1'; + +export const workspaceId =(displayName:string)=> Property.Dropdown({ + displayName, + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account.', + options: [], + }; + } + + const response = await httpClient.sendRequest<{ + workspaces: { id: string; name: string }[]; + }>({ + method: HttpMethod.GET, + url: `${BASE_URL}/workspaces`, + headers: { + 'X-API-Key': auth as string, + }, + }); + + return { + disabled: false, + options: response.body.workspaces.map((workspace) => ({ + label: workspace.name, + value: workspace.id, + })), + }; + }, +}); + +export const statusId = Property.Dropdown({ + displayName: 'Status', + refreshers: ['workspaceId'], + required: false, + options: async ({ auth, workspaceId }) => { + if (!auth || !workspaceId) { + return { + disabled: true, + placeholder: 'Please connect your account and select workspace.', + options: [], + }; + } + + const response = await httpClient.sendRequest<{ name: string }[]>({ + method: HttpMethod.GET, + url: `${BASE_URL}/statuses`, + headers: { + 'X-API-Key': auth as string, + }, + queryParams: { + workspaceId: workspaceId as string, + }, + }); + + return { + disabled: false, + options: response.body.map((status) => ({ + label: status.name, + value: status.name, + })), + }; + }, +}); + +export const projectId = Property.Dropdown({ + displayName: 'Project', + refreshers: ['workspaceId'], + required: false, + options: async ({ auth, workspaceId }) => { + if (!auth || !workspaceId) { + return { + disabled: true, + placeholder: 'Please connect your account and select workspace.', + options: [], + }; + } + + const response = await httpClient.sendRequest<{ + projects: { id: string; name: string }[]; + }>({ + method: HttpMethod.GET, + url: `${BASE_URL}/projects`, + headers: { + 'X-API-Key': auth as string, + }, + queryParams: { + workspaceId: workspaceId as string, + }, + }); + + return { + disabled: false, + options: response.body.projects.map((project) => ({ + label: project.name, + value: project.id, + })), + }; + }, +}); + +export const userId = Property.Dropdown({ + displayName: 'Assignee', + refreshers: ['workspaceId'], + required: false, + options: async ({ auth, workspaceId }) => { + if (!auth || !workspaceId) { + return { + disabled: true, + placeholder: 'Please connect your account and select workspace.', + options: [], + }; + } + + const response = await httpClient.sendRequest<{ + users: { id: string; name: string }[]; + }>({ + method: HttpMethod.GET, + url: `${BASE_URL}/users`, + headers: { + 'X-API-Key': auth as string, + }, + queryParams: { + workspaceId: workspaceId as string, + }, + }); + + return { + disabled: false, + options: response.body.users.map((user) => ({ + label: user.name, + value: user.id, + })), + }; + }, +}); + +export const taskId = Property.Dropdown({ + displayName: 'Task ID', + refreshers: ['workspaceId'], + required: true, + options: async ({ auth, workspaceId }) => { + if (!auth || !workspaceId) { + return { + disabled: true, + placeholder: 'Please connect your account and select workspace.', + options: [], + }; + } + + let nextCursor: string | undefined; + + const options: DropdownOption[] = []; + const qs: QueryParams = { workspaceId: workspaceId as string }; + + do { + if (nextCursor) { + qs['cursor'] = nextCursor; + } + + const response = await httpClient.sendRequest<{ + tasks: { id: string; name: string }[]; + meta: { pageSize: number; nextCursor?: string }; + }>({ + method: HttpMethod.GET, + url: `${BASE_URL}/tasks`, + headers: { + 'X-API-Key': auth as string, + }, + queryParams: qs, + }); + + const tasks = response.body.tasks ?? []; + for (const { id, name } of tasks) { + options.push({ label: name, value: id }); + } + + nextCursor = response.body.meta.nextCursor; + } while (nextCursor); + + return { + disabled: false, + options, + }; + }, +}); + +export const priority = Property.StaticDropdown({ + displayName: 'Priority', + required: false, + options: { + disabled: false, + options: [ + { label: 'ASAP', value: 'ASAP' }, + { label: 'HIGH', value: 'HIGH' }, + { label: 'MEDIUM', value: 'MEDIUM' }, + { label: 'LOW', value: 'LOW' }, + ], + + }, + }) \ No newline at end of file diff --git a/packages/pieces/community/motion/src/lib/triggers/task-created.ts b/packages/pieces/community/motion/src/lib/triggers/task-created.ts new file mode 100644 index 0000000..d3a8ef4 --- /dev/null +++ b/packages/pieces/community/motion/src/lib/triggers/task-created.ts @@ -0,0 +1,128 @@ +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { motionAuth } from '../../index'; +import { + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, + QueryParams, +} from '@activepieces/pieces-common'; +import { BASE_URL, workspaceId } from '../common/props'; +import dayjs from 'dayjs'; + +const polling: Polling< + PiecePropValueSchema, + { workspaceId: string } +> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue }) { + const result = []; + + let nextCursor: string | undefined; + + const qs: QueryParams = { + workspaceId: propsValue.workspaceId, + }; + + do { + if (nextCursor) { + qs['cursor'] = nextCursor; + } + + const response = await httpClient.sendRequest<{ + tasks: { id: string; name: string; createdTime: string }[]; + meta: { pageSize: number; nextCursor?: string }; + }>({ + method: HttpMethod.GET, + url: `${BASE_URL}/tasks`, + headers: { + 'X-API-Key': auth as string, + }, + queryParams: qs, + }); + + const tasks = response.body.tasks ?? []; + result.push(...tasks); + + nextCursor = response.body.meta.nextCursor; + } while (nextCursor); + + return result.map((task) => { + return { + epochMilliSeconds: dayjs(task.createdTime).valueOf(), + data: task, + }; + }); + }, +}; + +export const taskCreated = createTrigger({ + auth: motionAuth, + name: 'task-created', + displayName: 'Task Created', + description: 'Triggers when a new task is created.', + type: TriggerStrategy.POLLING, + props: { + workspaceId: workspaceId('Workspace ID'), + }, + + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + id: 'task_123', + name: 'Sample Task', + description: 'This is a sample task', + duration: 60, + dueDate: '2024-03-20T15:00:00Z', + deadlineType: 'HARD', + completed: false, + creator: { + id: 'user_123', + name: 'John Doe', + email: 'john@example.com', + }, + workspace: { + id: 'workspace_123', + name: 'My Workspace', + }, + status: { + name: 'In Progress', + isDefaultStatus: false, + isResolvedStatus: false, + }, + priority: 'HIGH', + labels: [{ name: 'Important' }, { name: 'Urgent' }], + assignees: [ + { + id: 'user_456', + name: 'Jane Smith', + email: 'jane@example.com', + }, + ], + createdTime: '2024-03-19T10:00:00Z', + updatedTime: '2024-03-19T10:00:00Z', + }, +}); diff --git a/packages/pieces/community/motion/tsconfig.json b/packages/pieces/community/motion/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/motion/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/motion/tsconfig.lib.json b/packages/pieces/community/motion/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/motion/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/moxie-crm/.eslintrc.json b/packages/pieces/community/moxie-crm/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/moxie-crm/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/moxie-crm/README.md b/packages/pieces/community/moxie-crm/README.md new file mode 100644 index 0000000..ce4717d --- /dev/null +++ b/packages/pieces/community/moxie-crm/README.md @@ -0,0 +1,7 @@ +# pieces-moxie-crm + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-moxie-crm` to build the library. diff --git a/packages/pieces/community/moxie-crm/package.json b/packages/pieces/community/moxie-crm/package.json new file mode 100644 index 0000000..94dd508 --- /dev/null +++ b/packages/pieces/community/moxie-crm/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-moxie-crm", + "version": "0.0.9" +} diff --git a/packages/pieces/community/moxie-crm/project.json b/packages/pieces/community/moxie-crm/project.json new file mode 100644 index 0000000..035870a --- /dev/null +++ b/packages/pieces/community/moxie-crm/project.json @@ -0,0 +1,48 @@ +{ + "name": "pieces-moxie-crm", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/moxie-crm/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/moxie-crm", + "tsConfig": "packages/pieces/community/moxie-crm/tsconfig.lib.json", + "packageJson": "packages/pieces/community/moxie-crm/package.json", + "main": "packages/pieces/community/moxie-crm/src/index.ts", + "assets": [ + "packages/pieces/community/moxie-crm/*.md", + { + "input": "packages/pieces/community/moxie-crm/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-moxie-crm {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ], + "options": { + "lintFilePatterns": [ + "packages/pieces/community/moxie-crm/**/*.ts" + ] + } + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/moxie-crm/src/index.ts b/packages/pieces/community/moxie-crm/src/index.ts new file mode 100644 index 0000000..cb0da19 --- /dev/null +++ b/packages/pieces/community/moxie-crm/src/index.ts @@ -0,0 +1,58 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { moxieCreateClientAction } from './lib/actions/create-client'; +import { moxieCreateProjectAction } from './lib/actions/create-project'; +import { moxieCreateTaskAction } from './lib/actions/create-task'; +import { moxieCRMTriggers } from './lib/triggers'; +export const moxieCRMAuth = PieceAuth.CustomAuth({ + required: true, + description: ` + To obtain your Moxie CRM token, follow these steps: + + 1. Log in to your Moxie CRM account and click on **Workspace Settings** (Bottom left). + 2. Click on **Connected Apps** and navigate to **Integrations** tab. + 3. Now, under "Custom Integration", click on **Enable Custom Integration**. + 4. Copy **API Key** and **Base URL** and click on Save button. + `, + props: { + apiKey: PieceAuth.SecretText({ + displayName: 'API Key', + description: 'The API Key of the Moxie CRM account', + required: true, + }), + baseUrl: Property.ShortText({ + displayName: 'Base URL', + description: 'The Base URL of the Moxie CRM account', + required: true, + }), + }, +}); + +export const moxieCrm = createPiece({ + displayName: 'Moxie', + description: 'CRM build for the freelancers.', + + auth: moxieCRMAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/moxie-crm.png', + authors: ["kishanprmr","MoShizzle","abuaboud"], + categories: [PieceCategory.SALES_AND_CRM], + actions: [ + moxieCreateClientAction, + moxieCreateTaskAction, + moxieCreateProjectAction, + createCustomApiCallAction({ + baseUrl: (auth) => (auth as { baseUrl: string }).baseUrl, + auth: moxieCRMAuth, + authMapping: async (auth) => ({ + 'X-API-KEY': (auth as { apiKey: string }).apiKey, + }), + }), + ], + triggers: moxieCRMTriggers, +}); diff --git a/packages/pieces/community/moxie-crm/src/lib/actions/create-client.ts b/packages/pieces/community/moxie-crm/src/lib/actions/create-client.ts new file mode 100644 index 0000000..476a615 --- /dev/null +++ b/packages/pieces/community/moxie-crm/src/lib/actions/create-client.ts @@ -0,0 +1,117 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { makeClient } from '../common'; +import { moxieCRMAuth } from '../..'; + +export const moxieCreateClientAction = createAction({ + auth: moxieCRMAuth, + name: 'moxie_create_client', + displayName: 'Create a Client', + description: 'Create a new client record in moxie CRM.', + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + clientType: Property.StaticDropdown({ + displayName: 'Client Type', + required: true, + defaultValue: 'Client', + options: { + disabled: false, + options: [ + { + label: 'Client', + value: 'Client', + }, + { + label: 'Prospect', + value: 'Prospect', + }, + ], + }, + }), + initials: Property.ShortText({ + displayName: 'Initials', + required: false, + }), + address1: Property.ShortText({ + displayName: 'Address 1', + required: false, + }), + address2: Property.ShortText({ + displayName: 'Address 2', + required: false, + }), + city: Property.ShortText({ + displayName: 'City', + required: false, + }), + locality: Property.ShortText({ + displayName: 'Locality', + required: false, + }), + postal: Property.ShortText({ + displayName: 'Postal', + required: false, + }), + country: Property.ShortText({ + displayName: 'Country', + required: false, + description: 'ISO 3166-1 alpha-2 country code', + }), + website: Property.ShortText({ + displayName: 'Website', + required: false, + }), + phone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + color: Property.ShortText({ + displayName: 'Color', + required: false, + }), + taxId: Property.ShortText({ + displayName: 'Tax ID', + required: false, + }), + leadSource: Property.ShortText({ + displayName: 'Lead Source', + required: false, + }), + archive: Property.Checkbox({ + displayName: 'Archive ?', + required: true, + defaultValue: false, + }), + payInstructions: Property.LongText({ + displayName: 'Pay Instructions', + required: false, + }), + hourlyAmount: Property.Number({ + displayName: 'Hourly Amount', + required: false, + }), + roundingIncrement: Property.Number({ + displayName: 'Rounding Increment', + required: false, + }), + currency: Property.ShortText({ + displayName: 'Currency', + required: false, + description: 'Valid 3-Letter ISO 4217 currency code.', + }), + stripeClientId: Property.ShortText({ + displayName: 'Stripe Client ID', + required: false, + }), + notes: Property.LongText({ + displayName: 'Notes', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = await makeClient(auth); + return await client.createClient(propsValue); + }, +}); diff --git a/packages/pieces/community/moxie-crm/src/lib/actions/create-project.ts b/packages/pieces/community/moxie-crm/src/lib/actions/create-project.ts new file mode 100644 index 0000000..ad12ba0 --- /dev/null +++ b/packages/pieces/community/moxie-crm/src/lib/actions/create-project.ts @@ -0,0 +1,165 @@ +import { + Property, + createAction, + PiecePropValueSchema, +} from '@activepieces/pieces-framework'; +import { makeClient, reformatDate } from '../common'; +import { moxieCRMAuth } from '../..'; + +export const moxieCreateProjectAction = createAction({ + auth: moxieCRMAuth, + name: 'moxie_create_project', + description: 'Creates a new project in moxie CRM.', + displayName: 'Create a Project', + props: { + name: Property.ShortText({ + displayName: 'Project Name', + required: true, + }), + clientName: Property.Dropdown({ + displayName: 'Client', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + + const client = await makeClient( + auth as PiecePropValueSchema + ); + const clients = await client.listClients(); + return { + disabled: false, + options: clients.map((client) => { + return { + label: client.name, + value: client.name, + }; + }), + }; + }, + }), + startDate: Property.DateTime({ + displayName: 'Start Date', + description: 'Please enter date in YYYY-MM-DD format.', + required: false, + }), + dueDate: Property.DateTime({ + displayName: 'Due Date', + description: 'Please enter date in YYYY-MM-DD format.', + required: false, + }), + portalAccess: Property.StaticDropdown({ + displayName: 'Client Portal Access', + description: 'One of: None, Overview, Full access, or Read only.', + required: true, + defaultValue: 'Read Only', + options: { + options: [ + { + label: 'Not Visible', + value: 'None', + }, + { + label: 'Overview only', + value: 'Overview', + }, + { + label: 'Read only project collaboration', + value: 'Read only', + }, + { + label: 'Full project collaboration', + value: 'Full access', + }, + ], + }, + }), + showTimeWorkedInPortal: Property.Checkbox({ + displayName: 'Show time worked in portal ?', + required: false, + defaultValue: true, + }), + feeType: Property.StaticDropdown({ + displayName: 'Fee Type', + description: 'One of: Hourly, Fixed Price, Retainer, Per Item.', + required: true, + options: { + options: [ + { + label: 'Hourly', + value: 'Hourly', + }, + { + label: 'Fixed Price', + value: 'Fixed Price', + }, + { + label: 'Retainer', + value: 'Retainer', + }, + { + label: 'Per Item', + value: 'Per Item', + }, + ], + }, + }), + amount: Property.Number({ + displayName: 'Amount', + required: false, + defaultValue: 0, + }), + estimateMax: Property.Number({ + displayName: 'Estimate maximum Amount', + required: false, + defaultValue: 0, + }), + estimateMin: Property.Number({ + displayName: 'Estimate minimum Amount', + required: false, + defaultValue: 0, + }), + taxable: Property.Checkbox({ + displayName: 'Is amount taxable ?', + required: false, + defaultValue: false, + }), + }, + async run({ auth, propsValue }) { + const { + name, + clientName, + portalAccess, + showTimeWorkedInPortal, + feeType, + amount, + estimateMax, + estimateMin, + taxable, + } = propsValue; + const dueDate = reformatDate(propsValue.dueDate) as string; + const startDate = reformatDate(propsValue.startDate) as string; + const client = await makeClient(auth); + return await client.createProject({ + name, + clientName, + startDate, + dueDate, + portalAccess, + showTimeWorkedInPortal, + feeSchedule: { + feeType, + amount, + estimateMax, + estimateMin, + taxable, + }, + }); + }, +}); diff --git a/packages/pieces/community/moxie-crm/src/lib/actions/create-task.ts b/packages/pieces/community/moxie-crm/src/lib/actions/create-task.ts new file mode 100644 index 0000000..a5102e7 --- /dev/null +++ b/packages/pieces/community/moxie-crm/src/lib/actions/create-task.ts @@ -0,0 +1,162 @@ +import { + Property, + createAction, + PiecePropValueSchema, +} from '@activepieces/pieces-framework'; +import { makeClient, reformatDate } from '../common'; +import { moxieCRMAuth } from '../..'; + +export const moxieCreateTaskAction = createAction({ + auth: moxieCRMAuth, + name: 'moxie_create_task', + displayName: 'Create a Task', + description: 'Create a task in project.', + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + clientName: Property.Dropdown({ + displayName: 'Client Name', + description: 'Exact match of a client name in your CRM', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + + const client = await makeClient( + auth as PiecePropValueSchema + ); + const clients = await client.listClients(); + return { + disabled: false, + options: clients.map((client) => { + return { + label: client.name, + value: client.name, + }; + }), + }; + }, + }), + projectName: Property.Dropdown({ + displayName: 'Project Name', + description: 'Exact match of a project that is owned by the client.', + required: true, + refreshers: ['clientName'], + options: async ({ auth, clientName }) => { + if (!auth || !clientName) { + return { + disabled: true, + placeholder: 'Connect your account first and select client', + options: [], + }; + } + const client = await makeClient( + auth as PiecePropValueSchema + ); + const projects = await client.searchProjects(clientName as string); + return { + disabled: false, + options: projects.map((project) => { + return { + label: project.name, + value: project.name, + }; + }), + }; + }, + }), + status: Property.Dropdown({ + displayName: 'Status', + required: true, + defaultValue: 'Not Started', + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + const client = await makeClient( + auth as PiecePropValueSchema + ); + const stages = await client.listProjectTaskStages(); + return { + disabled: false, + options: stages.map((stage) => { + return { + label: stage.label, + value: stage.label, + }; + }), + }; + }, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + startDate: Property.DateTime({ + displayName: 'Start Date', + required: false, + description: 'ISO 8601 format date i.e. 2023-07-20', + }), + dueDate: Property.DateTime({ + displayName: 'Due Date', + required: false, + description: 'ISO 8601 format date i.e. 2023-07-20', + }), + + priority: Property.Number({ + displayName: 'Priority', + required: false, + description: 'Numeric priority for sorting in kanban.', + }), + tasks: Property.Array({ + displayName: 'Subtasks', + required: false, + }), + assignedTo: Property.Array({ + displayName: 'Assigned To', + required: false, + description: 'email addresses of users in the workspace.', + }), + customValues: Property.Object({ + displayName: 'Custom Values', + required: false, + }), + }, + async run({ auth, propsValue }) { + const { name, clientName, projectName, status, description, priority } = + propsValue; + const dueDate = reformatDate(propsValue.dueDate) as string; + const startDate = reformatDate(propsValue.startDate) as string; + const tasks = (propsValue.tasks as string[]) || []; + const assignedTo = (propsValue.assignedTo as string[]) || []; + const customValues = + (propsValue.customValues as Record) || {}; + const client = await makeClient(auth); + return await client.createTask({ + name, + clientName, + projectName, + status, + description, + dueDate, + startDate, + priority, + tasks, + assignedTo, + customValues, + }); + }, +}); diff --git a/packages/pieces/community/moxie-crm/src/lib/common/client.ts b/packages/pieces/community/moxie-crm/src/lib/common/client.ts new file mode 100644 index 0000000..5130249 --- /dev/null +++ b/packages/pieces/community/moxie-crm/src/lib/common/client.ts @@ -0,0 +1,104 @@ +import { + HttpMethod, + HttpMessageBody, + httpClient, + HttpResponse, + QueryParams, +} from '@activepieces/pieces-common'; +import { + ContactCreateRequest, + ClientCreateRequest, + ClientListResponse, + TaskCreateRequest, + ProjectCreateRequest, + ProjectSearchResponse, + ProjectTaskStageListResponse, +} from './models'; + +export class MoxieCRMClient { + constructor(private baseUrl: string, private apiKey: string) { + // Remove trailing slash from base URL + this.baseUrl = baseUrl.replace(/\/$/, ''); + } + async makeRequest( + method: HttpMethod, + resourceUri: string, + body: any | undefined = undefined, + query?: QueryParams + ): Promise> { + return await httpClient.sendRequest({ + method: method, + url: `${this.baseUrl}${resourceUri}`, + headers: { + 'X-API-KEY': this.apiKey, + }, + body: body, + queryParams: query, + }); + } + async createContact(request: ContactCreateRequest) { + return ( + await this.makeRequest( + HttpMethod.POST, + '/action/contacts/create', + request + ) + ).body; + } + async createClient(request: ClientCreateRequest) { + return ( + await this.makeRequest(HttpMethod.POST, '/action/clients/create', request) + ).body; + } + async listClients(): Promise { + return ( + await this.makeRequest( + HttpMethod.GET, + '/action/clients/list' + ) + ).body; + } + async listInvoiceTemplates(): Promise { + return ( + await this.makeRequest( + HttpMethod.GET, + '/action/invoiceTemplates/list' + ) + ).body; + } + + async createProject(request: ProjectCreateRequest) { + return ( + await this.makeRequest( + HttpMethod.POST, + '/action/projects/create', + request + ) + ).body; + } + async createTask(request: TaskCreateRequest) { + return ( + await this.makeRequest(HttpMethod.POST, '/action/tasks/create', request) + ).body; + } + + async searchProjects(clientName: string): Promise { + return ( + await this.makeRequest( + HttpMethod.GET, + '/action/projects/search', + undefined, + { query: clientName } + ) + ).body; + } + + async listProjectTaskStages(): Promise { + return ( + await this.makeRequest( + HttpMethod.GET, + '/action/taskStages/list' + ) + ).body; + } +} diff --git a/packages/pieces/community/moxie-crm/src/lib/common/index.ts b/packages/pieces/community/moxie-crm/src/lib/common/index.ts new file mode 100644 index 0000000..b2eb864 --- /dev/null +++ b/packages/pieces/community/moxie-crm/src/lib/common/index.ts @@ -0,0 +1,16 @@ +import { PiecePropValueSchema } from '@activepieces/pieces-framework'; + +import { moxieCRMAuth } from '../../'; +import { MoxieCRMClient } from './client'; + +export async function makeClient( + auth: PiecePropValueSchema +): Promise { + const client = new MoxieCRMClient(auth.baseUrl, auth.apiKey); + return client; +} + +export function reformatDate(s?: string): string | undefined { + if (!s) return undefined; + return s.split('T', 2)[0]; +} diff --git a/packages/pieces/community/moxie-crm/src/lib/common/models.ts b/packages/pieces/community/moxie-crm/src/lib/common/models.ts new file mode 100644 index 0000000..cf78471 --- /dev/null +++ b/packages/pieces/community/moxie-crm/src/lib/common/models.ts @@ -0,0 +1,83 @@ +export type ContactCreateRequest = { + first: string; + last: string; + email?: string; + phone?: string; + notes?: string; + clientName?: string; + defaultContact?: boolean; + portalAccess?: boolean; + invoiceContact?: boolean; +}; + +export type ClientListResponse = { + id: string; + name: string; +}; + +export type ProjectSearchResponse = { + id: string; + name: string; +}; + +export type ProjectTaskStageListResponse = { + id: string; + label: string; + hexColor: string; + complete: boolean; + clientApproval: boolean; +}; + +export type ProjectCreateRequest = { + name: string; + clientName: string; + startDate?: string; + dueDate?: string; + portalAccess: string; + showTimeWorkedInPortal?: boolean; + feeSchedule: { + feeType: string; + amount?: number; + estimateMax?: number; + estimateMin?: number; + taxable?: boolean; + }; +}; + +export type ClientCreateRequest = { + name: string; + clientType: string; + initials?: string; + address1?: string; + address2?: string; + city?: string; + locality?: string; + postal?: string; + country?: string; + website?: string; + phone?: string; + color?: string; + taxId?: string; + leadSource?: string; + archive: boolean; + payInstructions?: string; + hourlyAmount?: number; + roundingIncrement?: number; + currency?: string; + stripeClientId?: string; + notes?: string; +}; + +export type TaskCreateRequest = { + name: string; + clientName: string; + projectName: string; + status: string; + description?: string; + dueDate?: string; + startDate?: string; + priority?: number; + tasks?: string[]; + assignedTo?: string[]; + customValues?: Record; +}; diff --git a/packages/pieces/community/moxie-crm/src/lib/triggers/index.ts b/packages/pieces/community/moxie-crm/src/lib/triggers/index.ts new file mode 100644 index 0000000..7f7f7c8 --- /dev/null +++ b/packages/pieces/community/moxie-crm/src/lib/triggers/index.ts @@ -0,0 +1,855 @@ +import { moxieCRMRegisterTrigger } from './register-trigger'; +export const enum MoxieCRMEventType { + CLIENT_CREATED = 'ClientCreate', + CLIENT_UPDATED = 'ClientUpdate', + CLIENT_DELETED = 'ClientDelete', + PROJECT_CREATED = 'ProjectCreate', + PROJECT_UPDATED = 'ProjectUpdate', + PROJECT_COMPLETED = 'ProjectComplete', + TASK_CREATED = 'DeliverableCreate', + TASK_UPDATED = 'DeliverableUpdate', + TASK_DELETED = 'DeliverableDelete', + TASK_APPROVAL = 'DeliverableApproval', + FORM_SUBMITTED = 'FormCompleted', + TIME_ENTRY_CREATED = 'TimerCreate', + TIME_ENTRY_UPDATED = 'TimerUpdate', + TIME_ENTRY_DELETED = 'TimerDelete', + MEETING_SCHEDULED = 'MeetingScheduled', + MEETING_UPDATED = 'MeetingUpdated', + MEETING_CANCELLED = 'MeetingCancelled', + OPPORTUNITY_CREATED = 'OpportunityCreate', + OPPORTUNITY_UPDATED = 'OpportunityUpdate', + OPPORTUNITY_DELETED = 'OpportunityDelete', + INVOICE_SENT = 'InvoiceSent', + PAYMENT_RECEIVED = 'PaymentReceived', +} + +const MoxieCRMWebhookSampleData = { + PROJECT_EVENT_SAMPLE_DATA: { + id: '6434749e852ec5116d546759', + accountId: 10016, + sampleData: false, + clientId: '64230b34bd4bbd275c1f1739', + name: 'Design & Development of Website', + description: null, + active: true, + startDate: '2023-05-16', + dueDate: '2023-06-06', + dateCreated: '2023-04-10T20:42:06.572Z', + client: { + accountId: 10016, + sampleData: false, + id: '64230b34bd4bbd275c1f1739', + clientType: 'Client', + name: 'Moxie', + initials: 'MOX1', + locality: 'CO', + country: null, + color: '#3BDBBE', + projects: [], + hourlyAmount: 0, + archive: false, + currency: 'AUD', + logo: 'https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://www.withmoxie.com&size=64', + leadSource: 'Google', + contact: null, + }, + leadGenArchived: false, + feeSchedule: { + feeType: 'Fixed Price', + amount: 5000, + retainerSchedule: null, + estimateMax: null, + estimateMin: null, + retainerStart: null, + retainerTiming: 'Advanced', + retainerOverageRate: null, + taxable: false, + fromProposalId: null, + fromProposalSignedDate: null, + updatedDate: '2023-04-10T20:42:16.141Z', + updatedBy: 'G. Mina', + }, + proposalId: null, + proposalName: null, + hexColor: '#3BDBBEFF', + portalAccess: 'Overview', + showTimeWorkedInPortal: true, + files: [], + deliverables: [], + }, + CLIENT_EVENT_SAMPLE_DATA: { + id: '63c5ea0c840e3207033931b5', + accountId: 10016, + name: 'Moxie', + clientType: 'Client', + initials: 'MOX', + address1: '123 Any Street', + address2: 'Suite 100', + city: 'Anytown', + locality: 'NY', + postal: '12345', + country: 'US', + website: 'www.withmoxie.com', + phone: '+18887231235', + color: '#CE62E9', + logo: 'https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://www.withmoxie.com&size=64', + s3LogoFile: null, + taxId: '1212121212', + leadSource: 'PPC', + archive: false, + leadGenArchived: false, + paymentTerms: { + paymentDays: 7, + latePaymentFee: 5, + depositAmount: 50, + depositType: 'Percentage', + whoPaysCardFees: 'Freelancer', + fromProposalId: '640b60752a524d1c45b6c528', + fromProposalSignedDate: '2023-03-14T14:41:01.908Z', + updatedDate: '2023-04-04T16:17:27.315Z', + updatedBy: 'G. Mina', + }, + payInstructions: null, + hourlyAmount: 100, + roundingIncrement: 1, + currency: 'USD', + lastInvoiceRunDate: null, + nextInvoiceRunDate: null, + importRecordId: null, + integrationKeys: { + quickbooksId: null, + xeroId: null, + }, + files: [], + comments: [], + created: '2023-01-17T00:21:32.663Z', + sampleData: false, + stripeClientId: 'cus_NFTM1mAFkgtfUI', + notes: null, + notifyOnCreate: null, + contacts: [ + { + id: '63d431ab3813ca3d0789d2cc', + accountId: 10016, + clientId: '63c5ea0c840e3207033931b5', + clientPortalUserId: -66, + firstName: 'Jeffrey', + lastName: 'Marna', + role: null, + phone: null, + email: 'geoff.mina@withmoxie.com', + mobile: null, + notes: null, + defaultContact: true, + invoiceContact: false, + portalAccess: true, + importRecordId: null, + sampleData: null, + }, + ], + }, + PROJECT_TASK_EVENT_SAMPLE_DATA: { + id: '64b3eba1249a076683fe3560', + clientId: '6490580de30ecf51c2c22ffa', + projectId: '649c83111c9cbe6ba1d4cabe', + project: { + id: '649c83111c9cbe6ba1d4cabe', + accountId: 10016, + sampleData: false, + clientId: '6490580de30ecf51c2c22ffa', + name: 'Hourly Project', + description: null, + active: true, + startDate: null, + dueDate: null, + dateCreated: '2023-06-28T18:59:29.587Z', + client: null, + leadGenArchived: false, + feeSchedule: { + feeType: 'Hourly', + amount: 150, + retainerSchedule: null, + estimateMax: null, + estimateMin: null, + retainerStart: null, + retainerTiming: 'Advanced', + retainerOverageRate: null, + taxable: false, + fromProposalId: null, + fromProposalSignedDate: null, + updatedDate: null, + updatedBy: null, + }, + proposalId: null, + proposalName: null, + hexColor: '#ffffff00', + portalAccess: 'Overview', + showTimeWorkedInPortal: true, + files: [], + deliverables: [], + }, + client: { + accountId: 10016, + sampleData: false, + id: '6490580de30ecf51c2c22ffa', + clientType: 'Client', + name: 'Moxie', + initials: null, + locality: 'NY', + country: 'US', + color: '#8EA3B8', + projects: [], + hourlyAmount: 150, + archive: false, + currency: 'USD', + logo: null, + leadSource: null, + contact: { + id: '6490580de30ecf51c2c22ffb', + accountId: 10016, + clientId: '6490580de30ecf51c2c22ffa', + clientPortalUserId: -79, + firstName: 'Geoffrey', + lastName: 'Mina', + role: null, + phone: '+15555551212', + email: 'geoff.mina@withmoxie.com', + mobile: null, + notes: null, + defaultContact: true, + invoiceContact: false, + portalAccess: true, + importRecordId: null, + sampleData: null, + }, + }, + name: 'Another new task to build website and victory', + statusId: '05b26dd1-0668-4dcc-b438-87ac93d4eb15', + status: 'In Progress', + priority: 2, + description: 'This is the description of the task', + assignedTo: null, + assignedToList: [16, 222], + approvalRequired: false, + product: null, + quantity: 0, + invoiceId: null, + invoiceNumber: null, + customValues: [ + { + fieldId: '4591275b-1c76-4eae-9257-0438fe1d2354', + fieldName: 'Text Input', + value: 'Text input', + }, + { + fieldId: 'a7e49f95-e0f4-416e-9bbf-586d98e1a55e', + fieldName: 'Numeric Input', + value: 123, + }, + { + fieldId: '0a95ed1a-f084-4e13-be02-6065bea93aed', + fieldName: 'Currency Input', + value: 2000, + }, + { + fieldId: '399ac82b-e56e-4499-b4be-db7d3f81f0fc', + fieldName: 'Radio Input', + value: 'Two', + }, + { + fieldId: '006ec49b-68e1-4ebd-9bdc-d18776551616', + fieldName: 'Checkbox Input', + value: 'Three', + }, + { + fieldId: 'd255032b-4f82-4f1b-8b61-4bccc66370c4', + fieldName: 'Phase', + value: 'Phase 1', + }, + { + fieldId: '4a281477-bed5-4f56-9e89-0bcb3758b3f8', + fieldName: 'Shoot Date', + value: '2023-07-07', + }, + { + fieldId: '3fb9c4f2-8093-4315-b2cd-748a3526b497', + fieldName: 'Recurs', + value: 'Yes', + }, + ], + comments: [ + { + id: '81eec0c6-4b6b-46f5-a412-cdc66c8571c8', + author: 'Geoffrey Mina', + authorId: '16', + comment: 'Comments in the task show up here.', + clientComment: false, + edited: false, + privateComment: false, + sendEmail: false, + timestamp: '2023-07-20T10:51:32.086Z', + }, + ], + startDate: '2023-07-01', + dueDate: '2023-07-31', + tasks: [ + { + id: 'e105201a27f34a26a37ef54e6b0f522b', + description: 'One', + complete: true, + }, + { + id: '6c436ea963844783b569639f4c26768e', + description: 'Two', + complete: true, + }, + { + id: 'bf62b9745301466397d09ab9aaa4bd69', + description: 'Three', + complete: false, + }, + { + id: 'dd932850c2094d879c7128cecc468d83', + description: 'Four', + complete: false, + }, + ], + archived: false, + kanbanSort: 2, + }, + FORM_EVENT_SAMPLE_DATA: { + id: '64b8233d6ce6226305f24b47', + accountId: 10016, + client: null, + formName: 'test-new-client', + businessName: 'Moxie', + firstName: 'Geoffrey', + lastName: 'Mina', + phone: '+444445551212', + email: 'hello@withmoxie.com', + role: null, + address1: null, + address2: null, + city: null, + locality: null, + postal: null, + country: null, + website: null, + leadSource: null, + sourceUrl: + 'https://hello.withmoxie.dev/00/hectic-lab/test-new-client?inPortal=true', + Field6: 'Answer to your first question', + Field7: 'Another to your other question', + submittedAt: '2023-07-19T17:54:05.257Z', + }, + TIME_ENTRY_EVENT_SAMPLE_DATA: { + id: '64b857b0b17c7c727001331c', + accountId: 10016, + sampleData: false, + userId: 16, + timerStart: '2023-07-19T21:37:35.684Z', + timerEnd: '2023-07-19T21:37:52.431Z', + userFullName: 'Geoffrey Mina', + notes: 'These are some notes', + clientId: '6490580de30ecf51c2c22ffa', + projectId: '649976d658c17d4f29b068ee', + deliverableId: '649976e158c17d4f29b068ef', + clientName: 'Moxie', + projectName: 'Fun project for client', + deliverableName: 'Task 1', + timestamp: null, + timestampUpdated: null, + invoiceId: null, + invoiceNumber: null, + importRecordId: null, + feeSchedule: null, + duration: 16, + }, + MEETING_EVENT_SAMPLE_DATA: { + id: '64b824df6ce6226305f24b53', + accountId: 10016, + ownerUserId: null, + sampleData: false, + meetingId: '63076a1320e0d10001e6cb63', + clientId: '63c5ea0c840e3207033931b5', + meetingStatus: 'Scheduled', + meetingName: 'Geoff Mina / 30 minute meeting', + schedulerName: '30 minute meeting', + confirmedTime: { + start: '2023-07-19T22:30:00.000Z', + end: '2023-07-19T23:00:00.000Z', + }, + scheduledTimezone: 'America/New_York', + scheduledLocale: 'en-US', + formData: { + firstName: 'Geoff', + lastName: 'Mina', + email: 'geoff.mina@withmoxie.com', + phone: '+445555555555', + role: null, + businessName: null, + website: null, + address1: null, + address2: null, + city: null, + locality: null, + postal: null, + country: null, + sourceUrl: null, + opportunityId: null, + templateId: null, + cardTokenId: null, + leadSource: null, + answers: [], + }, + location: { + type: 'Google', + }, + zoomMeeting: null, + googleMeeting: { + googleUserId: 16, + eventId: '_6oq64e1i6hi6cdj3ckr34chm6co3aphi6hh3acq0d1im6t39cdgn0s1ecdnmq', + hangoutLink: 'https://meet.google.com/swq-azkw-zkt', + htmlLink: + 'https://www.google.com/calendar/event?eid=XzZvcTY0ZTFpNmhpNmNkajNja3IzNGNobTZjbzNhcGhpNmhoM2FjcTBkMWltNnQzOWNkZ24wczFlY2RubXEgZ2VvZmYubWluYUBoZWN0aWMudXM', + googleUser: { + name: 'Geoff Mina', + given_name: 'Geoff', + family_name: 'Mina', + picture: + 'https://lh3.googleusercontent.com/a/AGNmyxYxJL9h8SnY6z2oXg7fNHAdov1TJ4--RQtiAMn8=s96-c', + email: 'geoff.mina@hectic.us', + email_verified: true, + locale: 'en', + }, + entryPoints: [ + { + entryPointType: 'video', + label: 'meet.google.com/swq-azkw-zkt', + uri: 'https://meet.google.com/swq-azkw-zkt', + }, + { + entryPointType: 'more', + pin: '4419810087411', + uri: 'https://tel.meet/swq-azkw-zkt?pin=4419810087411', + }, + { + entryPointType: 'phone', + label: '+1 646-504-7945', + pin: '978184485', + regionCode: 'US', + uri: 'tel:+1-646-504-7945', + }, + ], + }, + microsoftEvent: null, + connectedICalUid: null, + notes: null, + cancellationReason: null, + leadGenArchived: false, + opportunityId: null, + files: [], + meetingWith: 'Moxie', + incomeRecordId: null, + icalUid: '64b824df6ce6226305f24b53@hecticapp.com', + }, + OPPORTUNITY_EVENT_SAMPLE_DATA: { + id: '642dfde9fd537145d22edbaa', + accountId: 10016, + clientId: '5f7b3335b50f2a000189217a', + statusId: '30180ce4-ba0a-4b5d-b92c-d2733e11b514', + kanbanSort: 1, + name: 'New Opportunity', + description: null, + sentiment: 2, + value: 1500, + timePeriod: 'OneTime', + periods: 1, + estCloseDate: '2023-07-31', + actualCloseDate: null, + formData: { + firstName: 'Geoff', + lastName: 'Mina', + email: 'geoff.mina@withmoxie.com', + phone: '555-555-5554', + role: 'Executive', + businessName: 'Moxie', + website: 'www.withmoxie.com', + address1: '123 Any Stree', + address2: 'Suite 100', + city: 'Boulder', + locality: 'CO', + postal: '80301', + country: 'US', + sourceUrl: 'https://hello.hecticapp.dev/00/hectic-lab/fancy-new-form-v2', + opportunityId: null, + templateId: '642356ec35707318aa08bd18', + cardTokenId: null, + leadSource: 'Google', + answers: [ + { + id: '516e2366-a52f-4d69-8059-d2df8e692f12', + fieldKey: 'Field9', + fieldType: 'TextInput', + question: 'Enter question text', + answer: 'Blah', + }, + { + id: 'e30eb7c2-2b69-4a88-a973-92321c5c147d', + fieldKey: 'Field16', + fieldType: 'Checkbox', + question: 'Choose an option', + answer: 'Option 1, Option 2', + }, + { + id: 'ba71978a-67df-4335-9a70-7bfad8185788', + fieldKey: 'Field13', + fieldType: 'Radio', + question: 'Choose an option', + answer: 'Option 2', + }, + { + id: '4dd4471d-04c0-4795-8859-5f4d3b0fb283', + fieldKey: 'Field7', + fieldType: 'DateInput', + question: 'Select a date', + answer: '2023-04-06', + }, + { + id: 'a201bb30-48c4-4408-a35e-0ff619a660f9', + fieldKey: 'Field15', + fieldType: 'TextArea', + question: 'Enter question text', + answer: 'Blah', + }, + { + id: '973d4b26-2629-4819-92ed-7f8c73268982', + fieldKey: 'Field8', + fieldType: 'FileInput', + question: 'Upload your file', + answer: '["4BFE0272-D8BC-46CA-A8C8-079BB34B1BA0.jpeg"]', + }, + ], + }, + archive: false, + initialWorkflow: true, + toDos: [ + { + id: '4ed64532451f4421a063a7b61f2016b7', + item: 'Make Phone Call', + complete: false, + dueDate: '2023-07-20', + dateCompleted: null, + relativeDueDate: { + duration: null, + timeUnit: null, + }, + }, + { + id: '404fd07b56494676b2dc3aaee7a3b30f', + item: 'Send Email', + complete: false, + dueDate: '2023-07-23', + dateCompleted: null, + relativeDueDate: { + duration: null, + timeUnit: null, + }, + }, + ], + comments: [ + { + id: null, + author: 'System', + authorId: '0', + comment: 'Auto created from form: Fancy New Form V2', + clientComment: false, + edited: false, + privateComment: false, + sendEmail: false, + timestamp: '2023-04-05T23:02:00.618Z', + }, + ], + files: [ + { + fileName: 'Screenshot 2023-07-19 at 6.11.40 PM.png', + fileType: 'PNG', + timestamp: '2023-07-20T10:54:30.759Z', + fileIconUrl: + 'https://struxture-www-assets.s3.us-east-2.amazonaws.com/file-icons/png.png', + }, + { + fileName: 'Screenshot 2023-07-19 at 1.50.59 PM.png', + fileType: 'PNG', + timestamp: '2023-07-20T10:54:30.991Z', + fileIconUrl: + 'https://struxture-www-assets.s3.us-east-2.amazonaws.com/file-icons/png.png', + }, + ], + workflow: [ + { + id: 'c595dc6c-b7ea-4dfd-ac10-0883320e1eb8', + itemId: '642dfde2fd537145d22edba5', + itemType: 'Form', + properties: {}, + timestamp: '2023-04-05T23:01:54.206Z', + }, + ], + customValues: [ + { + fieldId: '5e9d7171-1988-431d-a1c8-491e4ae71613', + fieldName: 'Custom Field', + value: 'Custom Field Value', + }, + ], + history: [], + statusLabel: 'Contract', + }, + INVOICE_EVENT_SAMPLE_DATA: { + id: '64ae5aea99f38e74fc78ae46', + invoiceNumber: 74, + invoiceNumberFormatted: 'E-2023-74', + accountId: 10016, + clientId: '6490580de30ecf51c2c22ffa', + dateCreated: '2023-07-16', + dateSent: '2023-07-12', + dateDue: '2023-08-11', + dateDueCalculated: null, + datePaid: '2023-07-12', + clientInfo: { + id: '6490580de30ecf51c2c22ffa', + name: 'E164', + initials: null, + address1: '123 Any Street', + address2: null, + city: 'Anytown', + locality: 'NY', + postal: '12020', + country: 'US', + phone: '+15555551212', + color: '#8EA3B8', + taxId: null, + website: null, + contact: { + id: '6490580de30ecf51c2c22ffb', + accountId: 10016, + clientId: '6490580de30ecf51c2c22ffa', + clientPortalUserId: -79, + firstName: 'Geoffrey', + lastName: 'Mina', + role: null, + phone: '+12224445234', + email: 'user@email.com', + mobile: null, + notes: null, + defaultContact: true, + invoiceContact: false, + portalAccess: true, + importRecordId: null, + sampleData: null, + }, + roundingIncrement: 1, + customInfo: false, + }, + status: 'PARTIAL', + invoiceType: 'STANDARD', + subTotal: 14950, + convenienceFee: 0, + lateFee: 0, + discountAmount: 0, + creditApplied: 0, + tax: 225, + total: 15175, + localTotal: 15175, + paymentTotal: 78.75, + localPaymentTotal: 78.75, + amountDue: 15096.25, + localAmountDue: 15096.25, + currency: 'USD', + integrationKeys: { + quickbooksId: null, + xeroId: null, + }, + viewOnlineUrl: 'https://clients.domain.com/invoice?token=', + payments: [ + { + id: '29e34d7b-ef69-4059-bbe9-7b214c38a011', + amount: 78.75, + pending: false, + paidBy: 'G. Mina', + paymentProvider: 'CHECK', + currency: 'USD', + referenceNumber: null, + memo: null, + datePaid: '2023-07-12', + timestamp: '2023-07-12T06:00:00.000Z', + integratedPayment: false, + forcePaidInFull: false, + integrationKeys: { + quickbooksId: null, + xeroId: null, + }, + isFailedPayment: false, + localAmount: 78.75, + }, + ], + }, +}; + +export const moxieCRMTriggers = [ + { + name: 'client_created', + displayName: 'Client Created', + description: 'Triggered when a new client is created.', + eventType: MoxieCRMEventType.CLIENT_CREATED, + sampleData: MoxieCRMWebhookSampleData.CLIENT_EVENT_SAMPLE_DATA, + }, + { + name: 'client_updated', + displayName: 'Client Updated', + description: 'Triggered when an existing client is updated.', + eventType: MoxieCRMEventType.CLIENT_UPDATED, + sampleData: MoxieCRMWebhookSampleData.CLIENT_EVENT_SAMPLE_DATA, + }, + { + name: 'client_deleted', + displayName: 'Client Deleted', + description: 'Triggered when an existing client is deleted.', + eventType: MoxieCRMEventType.CLIENT_DELETED, + sampleData: MoxieCRMWebhookSampleData.CLIENT_EVENT_SAMPLE_DATA, + }, + { + name: 'project_created', + displayName: 'Project Created', + description: 'Triggered when a new project is created.', + eventType: MoxieCRMEventType.PROJECT_CREATED, + sampleData: MoxieCRMWebhookSampleData.PROJECT_EVENT_SAMPLE_DATA, + }, + { + name: 'project_updated', + displayName: 'Project Updated', + description: 'Triggered when an existing project is updated.', + eventType: MoxieCRMEventType.PROJECT_UPDATED, + sampleData: MoxieCRMWebhookSampleData.PROJECT_EVENT_SAMPLE_DATA, + }, + { + name: 'project_completed', + displayName: 'Project Completed', + description: 'Triggered when an existing project is completed.', + eventType: MoxieCRMEventType.PROJECT_COMPLETED, + sampleData: MoxieCRMWebhookSampleData.PROJECT_EVENT_SAMPLE_DATA, + }, + { + name: 'task_created', + displayName: 'Task Created', + description: 'Triggered when a new task is created.', + eventType: MoxieCRMEventType.TASK_CREATED, + sampleData: MoxieCRMWebhookSampleData.PROJECT_TASK_EVENT_SAMPLE_DATA, + }, + { + name: 'task_updated', + displayName: 'Task Updated', + description: 'Triggered when an existing task is updated.', + eventType: MoxieCRMEventType.TASK_UPDATED, + sampleData: MoxieCRMWebhookSampleData.PROJECT_TASK_EVENT_SAMPLE_DATA, + }, + { + name: 'task_deleted', + displayName: 'Task Deleted', + description: 'Triggered when an existing task is deleted.', + eventType: MoxieCRMEventType.TASK_DELETED, + sampleData: MoxieCRMWebhookSampleData.PROJECT_TASK_EVENT_SAMPLE_DATA, + }, + { + name: 'client_task_approval', + displayName: 'Client Task Approval', + description: 'Triggered when a task is moved to client approval.', + eventType: MoxieCRMEventType.TASK_APPROVAL, + sampleData: MoxieCRMWebhookSampleData.PROJECT_TASK_EVENT_SAMPLE_DATA, + }, + { + name: 'form_submitted', + displayName: 'Form Submitted', + description: 'Triggered when a new form is submitted.', + eventType: MoxieCRMEventType.FORM_SUBMITTED, + sampleData: MoxieCRMWebhookSampleData.FORM_EVENT_SAMPLE_DATA, + }, + { + name: 'time_entry_created', + displayName: 'Time Entry Created', + description: 'Triggered when a new time entry is created.', + eventType: MoxieCRMEventType.TIME_ENTRY_CREATED, + sampleData: MoxieCRMWebhookSampleData.TIME_ENTRY_EVENT_SAMPLE_DATA, + }, + { + name: 'time_entry_updated', + displayName: 'Time Entry Updated', + description: 'Triggered when an existing time entry is updated.', + eventType: MoxieCRMEventType.TIME_ENTRY_UPDATED, + sampleData: MoxieCRMWebhookSampleData.TIME_ENTRY_EVENT_SAMPLE_DATA, + }, + { + name: 'time_entry_deleted', + displayName: 'Time Entry Deleted', + description: 'Triggered when an existing time entry is deleted.', + eventType: MoxieCRMEventType.TIME_ENTRY_DELETED, + sampleData: MoxieCRMWebhookSampleData.TIME_ENTRY_EVENT_SAMPLE_DATA, + }, + { + name: 'meeting_scheduled', + displayName: 'Meeting Scheduled', + description: 'Triggered when a new meeting is scheduled.', + eventType: MoxieCRMEventType.MEETING_SCHEDULED, + sampleData: MoxieCRMWebhookSampleData.MEETING_EVENT_SAMPLE_DATA, + }, + { + name: 'meeting_updated', + displayName: 'Meeting Updated', + description: 'Triggered when an existing meeting is updated.', + eventType: MoxieCRMEventType.MEETING_UPDATED, + sampleData: MoxieCRMWebhookSampleData.MEETING_EVENT_SAMPLE_DATA, + }, + { + name: 'meeting_cancelled', + displayName: 'Meeting Cancelled', + description: 'Triggered when a meeting is cancelled.', + eventType: MoxieCRMEventType.MEETING_CANCELLED, + sampleData: MoxieCRMWebhookSampleData.MEETING_EVENT_SAMPLE_DATA, + }, + { + name: 'opportunity_created', + displayName: 'Opportunity Created', + description: 'Triggered when a new pipeline opportunity is created.', + eventType: MoxieCRMEventType.OPPORTUNITY_CREATED, + sampleData: MoxieCRMWebhookSampleData.OPPORTUNITY_EVENT_SAMPLE_DATA, + }, + { + name: 'opportunity_updated', + displayName: 'Opportunity Updated', + description: 'Triggered when an existing opportunity is updated.', + eventType: MoxieCRMEventType.OPPORTUNITY_UPDATED, + sampleData: MoxieCRMWebhookSampleData.OPPORTUNITY_EVENT_SAMPLE_DATA, + }, + { + name: 'opportunity_deleted', + displayName: 'Opportunity Deleted', + description: 'Triggered when an existing pipeline opportunity is deleted.', + eventType: MoxieCRMEventType.OPPORTUNITY_DELETED, + sampleData: MoxieCRMWebhookSampleData.OPPORTUNITY_EVENT_SAMPLE_DATA, + }, + { + name: 'invoice_sent', + displayName: 'Invoice Sent', + description: 'Triggered when an invoice is sent.', + eventType: MoxieCRMEventType.INVOICE_SENT, + sampleData: MoxieCRMWebhookSampleData.INVOICE_EVENT_SAMPLE_DATA, + }, + { + name: 'payment_received', + displayName: 'Payment Received', + description: 'Triggered when a payment is received.', + eventType: MoxieCRMEventType.PAYMENT_RECEIVED, + sampleData: MoxieCRMWebhookSampleData.INVOICE_EVENT_SAMPLE_DATA, + }, +].map((props) => moxieCRMRegisterTrigger(props)); diff --git a/packages/pieces/community/moxie-crm/src/lib/triggers/register-trigger.ts b/packages/pieces/community/moxie-crm/src/lib/triggers/register-trigger.ts new file mode 100644 index 0000000..4d687f9 --- /dev/null +++ b/packages/pieces/community/moxie-crm/src/lib/triggers/register-trigger.ts @@ -0,0 +1,52 @@ +import { + TriggerStrategy, + createTrigger, + Property, +} from '@activepieces/pieces-framework'; +import { MoxieCRMEventType } from '.'; +import { moxieCRMAuth } from '../../'; +export const moxieCRMRegisterTrigger = ({ + name, + displayName, + description, + eventType, + sampleData, +}: { + name: string; + displayName: string; + description: string; + eventType: MoxieCRMEventType; + sampleData: unknown; +}) => + createTrigger({ + auth: moxieCRMAuth, + name: `moxie_trigger_${name}`, + displayName, + description, + props: { + md: Property.MarkDown({ + value: ` + - Go to the **Workspace Settngs -> Connected Apps -> Integration** section. + - Under **Custom Integration** , click on **Add Rest Hook**. + - Click on **Integrations** section. + - In the endpoint field, paste the following URL: + \`\`\`text + {{webhookUrl}} + \`\`\` + + - Select the event as **\`${displayName}\`** and click on **Save**. + `, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: sampleData, + async onEnable(context) { + // Empty + }, + async onDisable(context) { + // Empty + }, + async run(context) { + return [context.payload.body]; + }, + }); diff --git a/packages/pieces/community/moxie-crm/tsconfig.json b/packages/pieces/community/moxie-crm/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/moxie-crm/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/moxie-crm/tsconfig.lib.json b/packages/pieces/community/moxie-crm/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/moxie-crm/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/mysql/.eslintrc.json b/packages/pieces/community/mysql/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/mysql/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/mysql/README.md b/packages/pieces/community/mysql/README.md new file mode 100644 index 0000000..8f61f47 --- /dev/null +++ b/packages/pieces/community/mysql/README.md @@ -0,0 +1,7 @@ +# pieces-mysql + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-mysql` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/mysql/package.json b/packages/pieces/community/mysql/package.json new file mode 100644 index 0000000..c5e9b1f --- /dev/null +++ b/packages/pieces/community/mysql/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-mysql", + "version": "0.1.7" +} \ No newline at end of file diff --git a/packages/pieces/community/mysql/project.json b/packages/pieces/community/mysql/project.json new file mode 100644 index 0000000..28c5496 --- /dev/null +++ b/packages/pieces/community/mysql/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-mysql", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/mysql/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/mysql", + "tsConfig": "packages/pieces/community/mysql/tsconfig.lib.json", + "packageJson": "packages/pieces/community/mysql/package.json", + "main": "packages/pieces/community/mysql/src/index.ts", + "assets": [ + "packages/pieces/community/mysql/*.md", + { + "input": "packages/pieces/community/mysql/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/mysql/src/index.ts b/packages/pieces/community/mysql/src/index.ts new file mode 100644 index 0000000..68a57e6 --- /dev/null +++ b/packages/pieces/community/mysql/src/index.ts @@ -0,0 +1,52 @@ +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import actions from './lib/actions'; + +export const mysqlAuth = PieceAuth.CustomAuth({ + props: { + host: Property.ShortText({ + displayName: 'Host', + required: true, + description: 'The hostname or address of the mysql server', + }), + port: Property.Number({ + displayName: 'Port', + defaultValue: 3306, + description: 'The port to use for connecting to the mysql server', + required: true, + }), + user: Property.ShortText({ + displayName: 'Username', + required: true, + description: 'The username to use for connecting to the mysql server', + }), + password: PieceAuth.SecretText({ + displayName: 'Password', + description: 'The password to use to identify at the mysql server', + required: true, + }), + database: Property.ShortText({ + displayName: 'Database', + description: 'The name of the database to use. Required if you are not using the "Execute Query" Action', + required: false, + }), + }, + required: true, +}); + +export const mysql = createPiece({ + displayName: 'MySQL', + description: "The world's most popular open-source database", + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/mysql.png', + categories: [PieceCategory.DEVELOPER_TOOLS], + authors: ["JanHolger","kishanprmr","khaledmashaly","abuaboud"], + auth: mysqlAuth, + actions, + triggers: [], +}); diff --git a/packages/pieces/community/mysql/src/lib/actions/delete-row.ts b/packages/pieces/community/mysql/src/lib/actions/delete-row.ts new file mode 100644 index 0000000..d870e3b --- /dev/null +++ b/packages/pieces/community/mysql/src/lib/actions/delete-row.ts @@ -0,0 +1,39 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mysqlCommon, mysqlConnect, sanitizeColumnName, warningMarkdown } from '../common'; +import { mysqlAuth } from '../..'; +import sqlstring from 'sqlstring'; + +export default createAction({ + auth: mysqlAuth, + name: 'delete_row', + displayName: 'Delete Row', + description: 'Deletes one or more rows from a table', + props: { + markdown: warningMarkdown, + timezone: mysqlCommon.timezone, + table: mysqlCommon.table(), + search_column: Property.ShortText({ + displayName: 'Search Column', + required: true, + }), + search_value: Property.ShortText({ + displayName: 'Search Value', + required: true, + }), + }, + async run(context) { + const tableName = sanitizeColumnName(context.propsValue.table); + const searchColumn = sanitizeColumnName(context.propsValue.search_column); + const searchValue = context.propsValue.search_value; + + const queryString = `DELETE FROM ${tableName} WHERE ${searchColumn}=?;`; + + const connection = await mysqlConnect(context.auth, context.propsValue); + try { + const result = await connection.query(queryString, [searchValue]); + return result; + } finally { + await connection.end(); + } + }, +}); diff --git a/packages/pieces/community/mysql/src/lib/actions/execute-query.ts b/packages/pieces/community/mysql/src/lib/actions/execute-query.ts new file mode 100644 index 0000000..d2bab17 --- /dev/null +++ b/packages/pieces/community/mysql/src/lib/actions/execute-query.ts @@ -0,0 +1,36 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mysqlCommon, mysqlConnect, warningMarkdown } from '../common'; +import { mysqlAuth } from '../..'; + +export default createAction({ + auth: mysqlAuth, + name: 'execute_query', + displayName: 'Execute Query', + description: 'Executes a query on the mysql database and returns the results', + props: { + markdown: warningMarkdown, + timezone: mysqlCommon.timezone, + query: Property.ShortText({ + displayName: 'Query', + description: 'The query string to execute, use ? for arguments to avoid SQL injection.', + required: true, + }), + args: Property.Array({ + displayName: 'Arguments', + description: 'Arguments to use in the query, if any. Should be in the same order as the ? in the query string..', + required: false, + }), + }, + async run(context) { + const conn = await mysqlConnect(context.auth, context.propsValue); + try { + const results = await conn.query( + context.propsValue.query, + context.propsValue.args || [] + ); + return Array.isArray(results) ? { results } : results; + } finally { + await conn.end(); + } + }, +}); diff --git a/packages/pieces/community/mysql/src/lib/actions/find-rows.ts b/packages/pieces/community/mysql/src/lib/actions/find-rows.ts new file mode 100644 index 0000000..31477ad --- /dev/null +++ b/packages/pieces/community/mysql/src/lib/actions/find-rows.ts @@ -0,0 +1,47 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mysqlCommon, mysqlConnect, sanitizeColumnName, warningMarkdown } from '../common'; +import { mysqlAuth } from '../..'; + +export default createAction({ + auth: mysqlAuth, + name: 'find_rows', + displayName: 'Find Rows', + description: 'Reads rows from a table', + props: { + markdown: warningMarkdown, + timezone: mysqlCommon.timezone, + table: mysqlCommon.table(), + condition: Property.ShortText({ + displayName: 'Condition', + description: 'SQL condition, can also include logic operators, etc.', + required: true, + }), + args: Property.Array({ + displayName: 'Arguments', + description: 'Arguments can be used using ? in the condition', + required: false, + }), + columns: Property.Array({ + displayName: 'Columns', + description: 'Specify the columns you want to select', + required: false, + }), + }, + async run(context) { + const columns = (context.propsValue.columns as string[]) || ['*']; + const qsColumns = columns + .map((c) => sanitizeColumnName(c)) + .join(','); + + const qs = `SELECT ${qsColumns} FROM ${sanitizeColumnName(context.propsValue.table)} WHERE ${context.propsValue.condition};`; + + const conn = await mysqlConnect(context.auth, context.propsValue); + + try { + const results = await conn.query(qs, context.propsValue.args); + return { results }; + } finally { + await conn.end(); + } + }, +}); diff --git a/packages/pieces/community/mysql/src/lib/actions/get-tables.ts b/packages/pieces/community/mysql/src/lib/actions/get-tables.ts new file mode 100644 index 0000000..dc43392 --- /dev/null +++ b/packages/pieces/community/mysql/src/lib/actions/get-tables.ts @@ -0,0 +1,20 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mysqlConnect, mysqlGetTableNames } from '../common'; +import { mysqlAuth } from '../..'; + +export default createAction({ + auth: mysqlAuth, + name: 'get_tables', + displayName: 'Get Tables', + description: 'Returns a list of tables in the database', + props: {}, + async run(context) { + const conn = await mysqlConnect(context.auth, context.propsValue); + try { + const tables = await mysqlGetTableNames(conn); + return { tables }; + } finally { + await conn.end(); + } + }, +}); diff --git a/packages/pieces/community/mysql/src/lib/actions/index.ts b/packages/pieces/community/mysql/src/lib/actions/index.ts new file mode 100644 index 0000000..eef26ac --- /dev/null +++ b/packages/pieces/community/mysql/src/lib/actions/index.ts @@ -0,0 +1,15 @@ +import executeQuery from './execute-query'; +import getTables from './get-tables'; +import insertRow from './insert-row'; +import updateRows from './update-row'; +import deleteRows from './delete-row'; +import selectRows from './find-rows'; + +export default [ + selectRows, + insertRow, + updateRows, + deleteRows, + getTables, + executeQuery, +]; diff --git a/packages/pieces/community/mysql/src/lib/actions/insert-row.ts b/packages/pieces/community/mysql/src/lib/actions/insert-row.ts new file mode 100644 index 0000000..ae13717 --- /dev/null +++ b/packages/pieces/community/mysql/src/lib/actions/insert-row.ts @@ -0,0 +1,33 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mysqlCommon, mysqlConnect, sanitizeColumnName, warningMarkdown } from '../common'; +import { mysqlAuth } from '../..'; +import sqlstring from 'sqlstring'; + +export default createAction({ + auth: mysqlAuth, + name: 'insert_row', + displayName: 'Insert Row', + description: 'Inserts a new row into a table', + props: { + timezone: mysqlCommon.timezone, + table: mysqlCommon.table(), + values: Property.Object({ + displayName: 'Values', + required: true, + }), + }, + async run(context) { + const fields = Object.keys(context.propsValue.values); + const qsFields = fields.map((f) => sanitizeColumnName(f)).join(','); + const qsValues = fields.map(() => '?').join(','); + const qs = `INSERT INTO ${sanitizeColumnName(context.propsValue.table)} (${qsFields}) VALUES (${qsValues});`; + const conn = await mysqlConnect(context.auth, context.propsValue); + try { + const values = fields.map((f) => context.propsValue.values[f]); + const result = await conn.query(qs, values); + return result; + } finally { + await conn.end(); + } + }, +}); diff --git a/packages/pieces/community/mysql/src/lib/actions/update-row.ts b/packages/pieces/community/mysql/src/lib/actions/update-row.ts new file mode 100644 index 0000000..b8ba0f9 --- /dev/null +++ b/packages/pieces/community/mysql/src/lib/actions/update-row.ts @@ -0,0 +1,43 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mysqlCommon, mysqlConnect, sanitizeColumnName } from '../common'; +import { mysqlAuth } from '../..'; +import sqlstring from 'sqlstring'; + +export default createAction({ + auth: mysqlAuth, + name: 'update_row', + displayName: 'Update Row', + description: 'Updates one or more rows in a table', + props: { + timezone: mysqlCommon.timezone, + table: mysqlCommon.table(), + values: Property.Object({ + displayName: 'Values', + required: true, + }), + search_column: Property.ShortText({ + displayName: 'Search Column', + required: true, + }), + search_value: Property.ShortText({ + displayName: 'Search Value', + required: true, + }), + }, + async run(context) { + const fields = Object.keys(context.propsValue.values); + const qsValues = fields.map((f) => sanitizeColumnName(f) + '=?').join(','); + const qs = `UPDATE ${sanitizeColumnName(context.propsValue.table)} SET ${qsValues} WHERE ${sqlstring.escape(context.propsValue.search_column)}=?;`; + const conn = await mysqlConnect(context.auth, context.propsValue); + try { + const values = fields.map((f) => context.propsValue.values[f]); + const result = await conn.query(qs, [ + ...values, + context.propsValue.search_value, + ]); + return result; + } finally { + await conn.end(); + } + }, +}); diff --git a/packages/pieces/community/mysql/src/lib/common/index.ts b/packages/pieces/community/mysql/src/lib/common/index.ts new file mode 100644 index 0000000..f2aa197 --- /dev/null +++ b/packages/pieces/community/mysql/src/lib/common/index.ts @@ -0,0 +1,81 @@ +import { + PiecePropValueSchema, + Property, + StaticPropsValue, +} from '@activepieces/pieces-framework'; +import { Connection, createConnection } from 'promise-mysql'; +import { mysqlAuth } from '../..'; +import sqlstring from 'sqlstring'; + +export const warningMarkdown = Property.MarkDown({ + value: ` + **DO NOT** use dynamic input directly in the query string or column names. + \n + Use **?** in the query and dynamic values in args/values for parameterized queries to prevent **SQL injection**.` +}); + +export async function mysqlConnect( + auth: PiecePropValueSchema, + propsValue: StaticPropsValue +): Promise { + const conn = await createConnection({ + host: auth.host, + port: auth.port || 3306, + user: auth.user, + password: auth.password, + database: auth.database || undefined, + timezone: propsValue.timezone, + }); + return conn; +} + +export async function mysqlGetTableNames(conn: Connection): Promise { + const result = await conn.query('SHOW TABLES;'); + return result.map((row: Record) => row[Object.keys(row)[0]]); +} + +export const mysqlCommon = { + timezone: Property.ShortText({ + displayName: 'Timezone', + description: 'Timezone for the MySQL server to use', + required: false, + }), + table: (required = true) => + Property.Dropdown({ + displayName: 'Table', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect to your database first', + options: [], + }; + } + const conn = await mysqlConnect( + auth as PiecePropValueSchema, + { auth } + ); + const tables = await mysqlGetTableNames(conn); + await conn.end(); + return { + disabled: false, + options: tables.map((table) => { + return { + label: table, + value: table, + }; + }), + }; + }, + }), +}; + + +export function sanitizeColumnName(name: string | undefined): string { + if ( name == '*') { + return name; + } + return sqlstring.escapeId(name); +} diff --git a/packages/pieces/community/mysql/tsconfig.json b/packages/pieces/community/mysql/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/mysql/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/mysql/tsconfig.lib.json b/packages/pieces/community/mysql/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/mysql/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/netsuite/.eslintrc.json b/packages/pieces/community/netsuite/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/netsuite/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/netsuite/README.md b/packages/pieces/community/netsuite/README.md new file mode 100644 index 0000000..5916a8b --- /dev/null +++ b/packages/pieces/community/netsuite/README.md @@ -0,0 +1,7 @@ +# pieces-netsuite + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-netsuite` to build the library. diff --git a/packages/pieces/community/netsuite/package.json b/packages/pieces/community/netsuite/package.json new file mode 100644 index 0000000..50c29ab --- /dev/null +++ b/packages/pieces/community/netsuite/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-netsuite", + "version": "0.0.1" +} diff --git a/packages/pieces/community/netsuite/project.json b/packages/pieces/community/netsuite/project.json new file mode 100644 index 0000000..67237c4 --- /dev/null +++ b/packages/pieces/community/netsuite/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-netsuite", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/netsuite/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/netsuite", + "tsConfig": "packages/pieces/community/netsuite/tsconfig.lib.json", + "packageJson": "packages/pieces/community/netsuite/package.json", + "main": "packages/pieces/community/netsuite/src/index.ts", + "assets": [ + "packages/pieces/community/netsuite/*.md", + { + "input": "packages/pieces/community/netsuite/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/netsuite/src/index.ts b/packages/pieces/community/netsuite/src/index.ts new file mode 100644 index 0000000..558188d --- /dev/null +++ b/packages/pieces/community/netsuite/src/index.ts @@ -0,0 +1,45 @@ +import { PieceAuth, createPiece, Property } from '@activepieces/pieces-framework'; +import { getVendor } from './lib/actions/get-vendor'; +import { getCustomer } from './lib/actions/get-customer'; +import { PieceCategory } from '@activepieces/shared'; + +export const netsuiteAuth = PieceAuth.CustomAuth({ + required: true, + props: { + accountId: Property.ShortText({ + displayName: 'Account ID', + required: true, + description: 'Your NetSuite account ID', + }), + consumerKey: Property.ShortText({ + displayName: 'Consumer Key', + required: true, + description: 'Your NetSuite consumer key', + }), + consumerSecret: PieceAuth.SecretText({ + displayName: 'Consumer Secret', + required: true, + description: 'Your NetSuite consumer secret', + }), + tokenId: Property.ShortText({ + displayName: 'Token ID', + required: true, + description: 'Your NetSuite token ID', + }), + tokenSecret: PieceAuth.SecretText({ + displayName: 'Token Secret', + required: true, + description: 'Your NetSuite token secret', + }), + }, +}); + +export const netsuite = createPiece({ + displayName: 'NetSuite', + logoUrl: 'https://cdn.activepieces.com/pieces/netsuite.png', + categories:[PieceCategory.SALES_AND_CRM], + auth: netsuiteAuth, + authors: ["geekyme"], + actions: [getVendor, getCustomer], + triggers: [], +}); \ No newline at end of file diff --git a/packages/pieces/community/netsuite/src/lib/actions/get-customer.ts b/packages/pieces/community/netsuite/src/lib/actions/get-customer.ts new file mode 100644 index 0000000..6c8a1be --- /dev/null +++ b/packages/pieces/community/netsuite/src/lib/actions/get-customer.ts @@ -0,0 +1,47 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { netsuiteAuth } from '../..'; +import { createOAuthHeader } from '../oauth'; + +export const getCustomer = createAction({ + name: 'getCustomer', + auth: netsuiteAuth, + displayName: 'Get Customer', + description: 'Gets customer details from NetSuite.', + props: { + customerId: Property.ShortText({ + displayName: 'Customer ID', + required: true, + description: 'The ID of the customer to retrieve.', + }), + }, + async run(context) { + const { accountId, consumerKey, consumerSecret, tokenId, tokenSecret } = context.auth; + const { customerId } = context.propsValue; + + const baseUrl = `https://${accountId}.suitetalk.api.netsuite.com/services/rest/record/v1/customer/${customerId}`; + const httpMethod = HttpMethod.GET; + + const authHeader = createOAuthHeader( + accountId, + consumerKey, + consumerSecret, + tokenId, + tokenSecret, + baseUrl, + httpMethod + ); + + const response = await httpClient.sendRequest({ + method: httpMethod, + url: baseUrl, + headers: { + Authorization: authHeader, + 'prefer': 'transient', + 'Cookie': 'NS_ROUTING_VERSION=LAGGING', + }, + }); + + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/netsuite/src/lib/actions/get-vendor.ts b/packages/pieces/community/netsuite/src/lib/actions/get-vendor.ts new file mode 100644 index 0000000..0e2be22 --- /dev/null +++ b/packages/pieces/community/netsuite/src/lib/actions/get-vendor.ts @@ -0,0 +1,47 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { netsuiteAuth } from '../..'; +import { createOAuthHeader } from '../oauth'; + +export const getVendor = createAction({ + name: 'getVendor', + auth: netsuiteAuth, + displayName: 'Get Vendor', + description: 'Gets vendor details from NetSuite.', + props: { + vendorId: Property.ShortText({ + displayName: 'Vendor ID', + required: true, + description: 'The ID of the vendor to retrieve.', + }), + }, + async run(context) { + const { accountId, consumerKey, consumerSecret, tokenId, tokenSecret } = context.auth; + const { vendorId } = context.propsValue; + + const baseUrl = `https://${accountId}.suitetalk.api.netsuite.com/services/rest/record/v1/vendor/${vendorId}`; + const httpMethod = HttpMethod.GET; + + const authHeader = createOAuthHeader( + accountId, + consumerKey, + consumerSecret, + tokenId, + tokenSecret, + baseUrl, + httpMethod + ); + + const response = await httpClient.sendRequest({ + method: httpMethod, + url: baseUrl, + headers: { + Authorization: authHeader, + 'prefer': 'transient', + 'Cookie': 'NS_ROUTING_VERSION=LAGGING', + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/netsuite/src/lib/oauth.ts b/packages/pieces/community/netsuite/src/lib/oauth.ts new file mode 100644 index 0000000..bf4d029 --- /dev/null +++ b/packages/pieces/community/netsuite/src/lib/oauth.ts @@ -0,0 +1,85 @@ +import crypto from 'crypto'; +import { createHmac } from 'crypto'; +import { URL } from 'url'; + +const SIGN_METHOD = 'HMAC-SHA256'; +const OAUTH_VERSION = '1.0'; + +export function generateNonce(): string { + const length = 11; + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + return Array.from({ length }, () => possible[Math.floor(Math.random() * possible.length)]).join(''); +} + +export function generateSignature( + baseUrl: string, + httpMethod: string, + oauthNonce: string, + timestamp: number, + consumerKey: string, + consumerSecret: string, + tokenId: string, + tokenSecret: string +): string { + const params = new URLSearchParams({ + oauth_consumer_key: consumerKey, + oauth_nonce: oauthNonce, + oauth_signature_method: SIGN_METHOD, + oauth_timestamp: timestamp.toString(), + oauth_token: tokenId, + oauth_version: OAUTH_VERSION, + }); + + const signatureBaseString = [ + httpMethod, + encodeURIComponent(baseUrl), + encodeURIComponent(params.toString()), + ].join('&'); + + const signingKey = [ + encodeURIComponent(consumerSecret), + encodeURIComponent(tokenSecret), + ].join('&'); + + const hmac = createHmac('sha256', signingKey); + hmac.update(signatureBaseString); + const signature = hmac.digest('base64'); + + return encodeURIComponent(signature); +} + +export function createOAuthHeader( + accountId: string, + consumerKey: string, + consumerSecret: string, + tokenId: string, + tokenSecret: string, + baseUrl: string, + httpMethod: string +): string { + const oauthNonce = generateNonce(); + const timestamp = Math.floor(Date.now() / 1000); + const signature = generateSignature( + baseUrl, + httpMethod, + oauthNonce, + timestamp, + consumerKey, + consumerSecret, + tokenId, + tokenSecret + ); + + const headerParams = [ + `realm="${accountId}"`, + `oauth_token="${tokenId}"`, + `oauth_consumer_key="${consumerKey}"`, + `oauth_nonce="${oauthNonce}"`, + `oauth_timestamp="${timestamp}"`, + `oauth_signature_method="${SIGN_METHOD}"`, + `oauth_version="${OAUTH_VERSION}"`, + `oauth_signature="${signature}"`, + ].join(','); + + return `OAuth ${headerParams}`; +} \ No newline at end of file diff --git a/packages/pieces/community/netsuite/tsconfig.json b/packages/pieces/community/netsuite/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/netsuite/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/netsuite/tsconfig.lib.json b/packages/pieces/community/netsuite/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/netsuite/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/nifty/.eslintrc.json b/packages/pieces/community/nifty/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/nifty/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/nifty/README.md b/packages/pieces/community/nifty/README.md new file mode 100644 index 0000000..01bdfb3 --- /dev/null +++ b/packages/pieces/community/nifty/README.md @@ -0,0 +1,7 @@ +# pieces-nifty + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-nifty` to build the library. diff --git a/packages/pieces/community/nifty/package.json b/packages/pieces/community/nifty/package.json new file mode 100644 index 0000000..ba5c694 --- /dev/null +++ b/packages/pieces/community/nifty/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-nifty", + "version": "0.0.6" +} \ No newline at end of file diff --git a/packages/pieces/community/nifty/project.json b/packages/pieces/community/nifty/project.json new file mode 100644 index 0000000..4ed59c5 --- /dev/null +++ b/packages/pieces/community/nifty/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-nifty", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/nifty/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/nifty", + "tsConfig": "packages/pieces/community/nifty/tsconfig.lib.json", + "packageJson": "packages/pieces/community/nifty/package.json", + "main": "packages/pieces/community/nifty/src/index.ts", + "assets": [ + "packages/pieces/community/nifty/*.md", + { + "input": "packages/pieces/community/nifty/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs piece-nitfy {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/nifty/src/index.ts b/packages/pieces/community/nifty/src/index.ts new file mode 100644 index 0000000..fc9ad6c --- /dev/null +++ b/packages/pieces/community/nifty/src/index.ts @@ -0,0 +1,50 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + createPiece, + OAuth2PropertyValue, + PieceAuth, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createTask } from './lib/actions/create-task'; + +const mddescription = ` +# How to add a new connection +1. Login to your nifty account at https://niftypm.com/ +2. From your account settings, click on App Center +3. After that click on Integrate with API +4. Then Create a new app +5. Select the Name and Description you want +6. copy the redirect url from the piece and fill the url field ( without https:// ) +7. check out Milestones , Subtasks , Projects , Statuses , Tasks and Portfolios +8. copy the client id and client secret and paste them in the piece +`; + +export const niftyAuth = PieceAuth.OAuth2({ + authUrl: 'https://nifty.pm/authorize', + tokenUrl: 'https://openapi.niftypm.com/oauth/token', + required: true, + description: mddescription, + scope: ['task', 'project', 'subtask', 'milestone', 'subteam'], +}); + +export const nifty = createPiece({ + displayName: 'Nifty', + description: 'Project management made simple', + + auth: niftyAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/nifty.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: ["kishanprmr","MoShizzle","abuaboud"], + actions: [ + createTask, + createCustomApiCallAction({ + baseUrl: () => 'https://openapi.niftypm.com/api/v1.0', + auth: niftyAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/nifty/src/lib/actions/create-task.ts b/packages/pieces/community/nifty/src/lib/actions/create-task.ts new file mode 100644 index 0000000..7f1f69c --- /dev/null +++ b/packages/pieces/community/nifty/src/lib/actions/create-task.ts @@ -0,0 +1,44 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { nitfyCommon, callNitfyApi } from '../common'; +import { niftyAuth } from '../../index'; + +export const createTask = createAction({ + name: 'create_task', + auth: niftyAuth, + displayName: 'Create Task', + description: 'Create a task in nitfy', + props: { + portfolio: nitfyCommon.portfolio, + project: nitfyCommon.project, + status: nitfyCommon.status, + milestone: nitfyCommon.milestone, + task_name: Property.ShortText({ + displayName: 'Task Name', + required: true, + }), + task_description: Property.LongText({ + displayName: 'Task Description', + required: false, + }), + }, + async run(context) { + const authentication = context.auth; + const accessToken = authentication.access_token; + const status = context.propsValue.status; + const task_name = context.propsValue.task_name; + const task_description = context.propsValue.task_description; + const milestone = context.propsValue.milestone; + + const response = ( + await callNitfyApi(HttpMethod.POST, 'tasks', accessToken, { + name: task_name, + task_group_id: status, + description: task_description, + milestone_id: milestone, + }) + ).body; + + return [response]; + }, +}); diff --git a/packages/pieces/community/nifty/src/lib/common/index.ts b/packages/pieces/community/nifty/src/lib/common/index.ts new file mode 100644 index 0000000..b30e225 --- /dev/null +++ b/packages/pieces/community/nifty/src/lib/common/index.ts @@ -0,0 +1,212 @@ +import { Property, OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { + HttpMethod, + HttpMessageBody, + HttpResponse, + httpClient, + AuthenticationType, +} from '@activepieces/pieces-common'; + +export const nitfyCommon = { + portfolio: Property.Dropdown({ + displayName: 'Portfolio', + required: true, + refreshers: [], + options: async ({ auth }) => { + const authentication = auth as OAuth2PropertyValue; + if (!authentication) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + const accessToken = authentication.access_token; + + const response = ( + await callNitfyApi<{ + subteams: { + id: string; + name: string; + }[]; + items: boolean; + hasMore: boolean; + }>(HttpMethod.GET, 'subteams', accessToken, undefined) + ).body; + + return { + disabled: false, + options: response.subteams.map((team) => { + return { + label: team.name, + value: team.id, + }; + }), + }; + }, + }), + project: Property.Dropdown({ + displayName: 'Project', + required: true, + refreshers: ['portfolio'], + options: async ({ auth, portfolio }) => { + const authentication = auth as OAuth2PropertyValue; + if (!authentication) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + if (!portfolio) { + return { + disabled: true, + placeholder: 'Select portfolio first', + options: [], + }; + } + + const accessToken = authentication.access_token; + const response = ( + await callNitfyApi<{ + projects: { + id: string; + name: string; + subteam: string; + }[]; + hasMore: boolean; + }>(HttpMethod.GET, `projects`, accessToken, undefined) + ).body; + + response.projects = response.projects.filter((project) => { + return project.subteam == portfolio; + }); + + return { + disabled: false, + options: response.projects.map((project) => { + return { + label: project.name, + value: project.id, + }; + }), + }; + }, + }), + status: Property.Dropdown({ + displayName: 'Status', + required: true, + refreshers: ['project'], + options: async ({ auth, project }) => { + const authentication = auth as OAuth2PropertyValue; + if (!authentication) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + if (!project) { + return { + disabled: true, + placeholder: 'Select portfolio first', + options: [], + }; + } + + const accessToken = authentication.access_token; + + const response = ( + await callNitfyApi<{ + items: { + id: string; + name: string; + }[]; + hasMore: boolean; + }>( + HttpMethod.GET, + `taskgroups?project_id=${project}&archived=false`, + accessToken, + undefined + ) + ).body; + + return { + disabled: false, + options: response.items.map((list) => { + return { + label: list.name, + value: list.id, + }; + }), + }; + }, + }), + milestone: Property.Dropdown({ + displayName: 'Milestone', + required: true, + refreshers: ['project'], + options: async ({ auth, project }) => { + const authentication = auth as OAuth2PropertyValue; + if (!authentication) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + if (!project) { + return { + disabled: true, + placeholder: 'Select project first', + options: [], + }; + } + + const accessToken = authentication.access_token; + + const response = ( + await callNitfyApi<{ + items: { + id: string; + name: string; + task_group: string; + }[]; + hasMore: boolean; + }>( + HttpMethod.GET, + `milestones?project_id=${project}&is_list=true`, + accessToken, + undefined + ) + ).body; + + return { + disabled: false, + options: response.items.map((list) => { + return { + label: list.name, + value: list.id, + }; + }), + }; + }, + }), +}; + +export async function callNitfyApi( + method: HttpMethod, + apiUrl: string, + accessToken: string, + body: any | undefined +): Promise> { + return await httpClient.sendRequest({ + method: method, + url: `https://openapi.niftypm.com/api/v1.0/${apiUrl}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + body: body, + }); +} diff --git a/packages/pieces/community/nifty/tsconfig.json b/packages/pieces/community/nifty/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/nifty/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/nifty/tsconfig.lib.json b/packages/pieces/community/nifty/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/nifty/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/nocodb/.eslintrc.json b/packages/pieces/community/nocodb/.eslintrc.json new file mode 100644 index 0000000..359ff63 --- /dev/null +++ b/packages/pieces/community/nocodb/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/nocodb/README.md b/packages/pieces/community/nocodb/README.md new file mode 100644 index 0000000..ed76584 --- /dev/null +++ b/packages/pieces/community/nocodb/README.md @@ -0,0 +1,7 @@ +# pieces-nocodb + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-nocodb` to build the library. diff --git a/packages/pieces/community/nocodb/package.json b/packages/pieces/community/nocodb/package.json new file mode 100644 index 0000000..a930431 --- /dev/null +++ b/packages/pieces/community/nocodb/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-nocodb", + "version": "0.2.0" +} diff --git a/packages/pieces/community/nocodb/project.json b/packages/pieces/community/nocodb/project.json new file mode 100644 index 0000000..ef3f241 --- /dev/null +++ b/packages/pieces/community/nocodb/project.json @@ -0,0 +1,48 @@ +{ + "name": "pieces-nocodb", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/nocodb/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/nocodb", + "tsConfig": "packages/pieces/community/nocodb/tsconfig.lib.json", + "packageJson": "packages/pieces/community/nocodb/package.json", + "main": "packages/pieces/community/nocodb/src/index.ts", + "assets": [ + "packages/pieces/community/nocodb/*.md", + { + "input": "packages/pieces/community/nocodb/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-nocodb {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ], + "options": { + "lintFilePatterns": [ + "packages/pieces/community/nocodb/**/*.ts" + ] + } + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/nocodb/src/index.ts b/packages/pieces/community/nocodb/src/index.ts new file mode 100644 index 0000000..d66043f --- /dev/null +++ b/packages/pieces/community/nocodb/src/index.ts @@ -0,0 +1,55 @@ +import { createPiece, PieceAuth, Property } from '@activepieces/pieces-framework'; +import { createRecordAction } from './lib/actions/create-record'; +import { deleteRecordAction } from './lib/actions/delete-record'; +import { updateRecordAction } from './lib/actions/update-record'; +import { getRecordAction } from './lib/actions/get-record'; +import { searchRecordsAction } from './lib/actions/search-records'; + +export const nocodbAuth = PieceAuth.CustomAuth({ + description: ` + 1. Log in to your NocoDB Account. + 2. Click on your profile-pic(bottom-left) and navigate to **Account Settings->Tokens**. + 3. Create new token with any name and copy API Token. + 4. Your Base URL is where your app is hosted.`, + props: { + baseUrl: Property.ShortText({ + displayName: 'NocoDB Base URL', + required: true, + defaultValue: 'https://app.nocodb.com', + }), + apiToken: PieceAuth.SecretText({ + displayName: 'API Token', + required: true, + }), + version: Property.StaticDropdown({ + displayName: 'API Version', + description: 'Required only for self-hosted instances. Not needed for the cloud version.', + required: false, + defaultValue: 0, + options: { + options: [ + { label: 'Before v0.90.0', value: 1 }, + { label: 'v0.90.0 to v0.199.0', value: 2 }, + { label: 'v0.200.0 Onwards', value: 3 } + ] + } + }), + }, + required: true, +}); + +export const nocodb = createPiece({ + displayName: 'NocoDB', + auth: nocodbAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/nocodb.png', + authors: ['kishanprmr'], + actions: [ + createRecordAction, + deleteRecordAction, + updateRecordAction, + getRecordAction, + searchRecordsAction, + ], + triggers: [], +}); diff --git a/packages/pieces/community/nocodb/src/lib/actions/create-record.ts b/packages/pieces/community/nocodb/src/lib/actions/create-record.ts new file mode 100644 index 0000000..b0b2b3b --- /dev/null +++ b/packages/pieces/community/nocodb/src/lib/actions/create-record.ts @@ -0,0 +1,31 @@ +import { nocodbAuth } from '../../'; +import { createAction, DynamicPropsValue } from '@activepieces/pieces-framework'; +import { makeClient, nocodbCommon } from '../common'; + +export const createRecordAction = createAction({ + auth: nocodbAuth, + name: 'nocodb-create-record', + displayName: 'Create a Record', + description: 'Creates a new record in the given table.', + props: { + workspaceId: nocodbCommon.workspaceId, + baseId: nocodbCommon.baseId, + tableId: nocodbCommon.tableId, + tableColumns: nocodbCommon.tableColumns, + }, + async run(context) { + const { tableId, tableColumns } = context.propsValue; + const recordInput: DynamicPropsValue = {}; + + Object.entries(tableColumns).forEach(([key, value]) => { + if (Array.isArray(value)) { + recordInput[key] = value.join(','); + } else { + recordInput[key] = value; + } + }); + + const client = makeClient(context.auth); + return await client.createRecord(tableId, recordInput, context.auth.version || 3); + }, +}); diff --git a/packages/pieces/community/nocodb/src/lib/actions/delete-record.ts b/packages/pieces/community/nocodb/src/lib/actions/delete-record.ts new file mode 100644 index 0000000..a0b67ef --- /dev/null +++ b/packages/pieces/community/nocodb/src/lib/actions/delete-record.ts @@ -0,0 +1,25 @@ +import { nocodbAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { makeClient, nocodbCommon } from '../common'; + +export const deleteRecordAction = createAction({ + auth: nocodbAuth, + name: 'nocodb-delete-record', + displayName: 'Delete a Record', + description: 'Deletes a record with the given Record ID.', + props: { + workspaceId: nocodbCommon.workspaceId, + baseId: nocodbCommon.baseId, + tableId: nocodbCommon.tableId, + recordId: Property.Number({ + displayName: 'Record ID', + required: true, + }), + }, + async run(context) { + const { tableId, recordId } = context.propsValue; + + const client = makeClient(context.auth); + return await client.deleteRecord(tableId, recordId, context.auth.version || 3); + }, +}); diff --git a/packages/pieces/community/nocodb/src/lib/actions/get-record.ts b/packages/pieces/community/nocodb/src/lib/actions/get-record.ts new file mode 100644 index 0000000..48df12d --- /dev/null +++ b/packages/pieces/community/nocodb/src/lib/actions/get-record.ts @@ -0,0 +1,25 @@ +import { nocodbAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { makeClient, nocodbCommon } from '../common'; + +export const getRecordAction = createAction({ + auth: nocodbAuth, + name: 'nocodb-get-record', + displayName: 'Get a Record', + description: 'Gets a record by the Record ID.', + props: { + workspaceId: nocodbCommon.workspaceId, + baseId: nocodbCommon.baseId, + tableId: nocodbCommon.tableId, + recordId: Property.Number({ + displayName: 'Record ID', + required: true, + }), + }, + async run(context) { + const { tableId, recordId } = context.propsValue; + + const client = makeClient(context.auth); + return await client.getRecord(tableId, recordId, context.auth.version || 3); + }, +}); diff --git a/packages/pieces/community/nocodb/src/lib/actions/search-records.ts b/packages/pieces/community/nocodb/src/lib/actions/search-records.ts new file mode 100644 index 0000000..fde8990 --- /dev/null +++ b/packages/pieces/community/nocodb/src/lib/actions/search-records.ts @@ -0,0 +1,45 @@ +import { nocodbAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { makeClient, nocodbCommon } from '../common'; + +export const searchRecordsAction = createAction({ + auth: nocodbAuth, + name: 'nocodb-search-records', + displayName: 'Search Records', + description: 'Returns a list of records matching the where condition.', + props: { + workspaceId: nocodbCommon.workspaceId, + baseId: nocodbCommon.baseId, + tableId: nocodbCommon.tableId, + columnId: nocodbCommon.columnId, + whereCondition: Property.LongText({ + displayName: 'Where', + required: false, + description: `Enables you to define specific conditions for filtering records.See docs [here](https://docs.nocodb.com/0.109.7/developer-resources/rest-apis/#comparison-operators).`, + }), + limit: Property.Number({ + displayName: 'Limit', + required: true, + defaultValue: 10, + description: 'Enables you to set a limit on the number of records you want to retrieve.', + }), + sort: Property.LongText({ + displayName: 'Sort', + required: false, + description: `Comma separated field names without space.Example: **field1,-field2** will sort the records first by 'field1' in ascending order and then by 'field2' in descending order.`, + }), + }, + async run(context) { + const { tableId, columnId, limit, whereCondition, sort } = context.propsValue; + + const client = makeClient(context.auth); + const response = await client.listRecords(tableId, { + fields: columnId ? columnId.join(',') : undefined, + where: whereCondition, + sort, + offset: 0, + limit, + }, context.auth.version || 3); + return response.list; + }, +}); diff --git a/packages/pieces/community/nocodb/src/lib/actions/update-record.ts b/packages/pieces/community/nocodb/src/lib/actions/update-record.ts new file mode 100644 index 0000000..9b06b69 --- /dev/null +++ b/packages/pieces/community/nocodb/src/lib/actions/update-record.ts @@ -0,0 +1,37 @@ +import { nocodbAuth } from '../../'; +import { createAction, DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { makeClient, nocodbCommon } from '../common'; + +export const updateRecordAction = createAction({ + auth: nocodbAuth, + name: 'nocodb-update-record', + displayName: 'Update a Record', + description: 'Updates an existing record with the given Record ID.', + props: { + workspaceId: nocodbCommon.workspaceId, + baseId: nocodbCommon.baseId, + tableId: nocodbCommon.tableId, + recordId: Property.Number({ + displayName: 'Record ID', + required: true, + }), + tableColumns: nocodbCommon.tableColumns, + }, + async run(context) { + const { tableId, recordId, tableColumns } = context.propsValue; + const recordInput: DynamicPropsValue = { + Id: recordId, + }; + + Object.entries(tableColumns).forEach(([key, value]) => { + if (Array.isArray(value)) { + recordInput[key] = value.join(','); + } else { + recordInput[key] = value; + } + }); + + const client = makeClient(context.auth); + return await client.updateRecord(tableId, recordInput, context.auth.version || 3); + }, +}); diff --git a/packages/pieces/community/nocodb/src/lib/common/client.ts b/packages/pieces/community/nocodb/src/lib/common/client.ts new file mode 100644 index 0000000..dec1f46 --- /dev/null +++ b/packages/pieces/community/nocodb/src/lib/common/client.ts @@ -0,0 +1,148 @@ +import { + HttpMessageBody, + HttpMethod, + QueryParams, + httpClient, + HttpRequest, +} from '@activepieces/pieces-common'; +import { + BaseResponse, + GetTableResponse, + ListAPIResponse, + ListRecordsParams, + TableResponse, + WorkspaceResponse, +} from './types'; + +export class NocoDBClient { + constructor(private hostUrl: string, private apiToken: string) {} + + async makeRequest( + method: HttpMethod, + resourceUri: string, + query?: Record, + body: Record | undefined = undefined, + ): Promise { + const baseUrl = this.hostUrl.replace(/\/$/, ''); + const params: QueryParams = {}; + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + params[key] = String(value); + } + } + } + const request: HttpRequest = { + method: method, + url: baseUrl + '/api' + resourceUri, + headers: { + 'xc-token': this.apiToken, + }, + queryParams: params, + body: body, + }; + const response = await httpClient.sendRequest(request); + return response.body; + } + + async listWorkspaces(): Promise> { + return await this.makeRequest>( + HttpMethod.GET, + '/v1/workspaces/', + ); + } + + async listBases(workspaceId?: string, version = 3): Promise> { + if (workspaceId && workspaceId !== 'none') { + // Cloud version + const endpoint = `/v1/workspaces/${workspaceId}/bases/`; + return await this.makeRequest>( + HttpMethod.GET, + endpoint, + ); + } else { + // Self-hosted version + const endpoint = version === 3 ? '/v2/meta/bases/' : '/v1/db/meta/projects/'; + return await this.makeRequest>( + HttpMethod.GET, + endpoint, + ); + } + } + + async listTables(baseId: string, version = 3): Promise> { + const endpoint = version === 3 + ? `/v2/meta/bases/${baseId}/tables` + : `/v1/db/meta/projects/${baseId}/tables`; + return await this.makeRequest>( + HttpMethod.GET, + endpoint, + ); + } + + async getTable(tableId: string, version = 3): Promise { + const endpoint = version === 3 + ? `/v2/meta/tables/${tableId}/` + : `/v1/db/meta/tables/${tableId}/`; + return await this.makeRequest(HttpMethod.GET, endpoint); + } + + async createRecord(tableId: string, recordInput: Record, version = 3) { + const endpoint = version === 3 + ? `/v2/tables/${tableId}/records` + : `/v1/db/data/noco/${tableId}`; + return await this.makeRequest( + HttpMethod.POST, + endpoint, + undefined, + recordInput + ); + } + + async getRecord(tableId: string, recordId: number, version = 3) { + const endpoint = version === 3 + ? `/v2/tables/${tableId}/records/${recordId}` + : `/v1/db/data/noco/${tableId}/${recordId}`; + return await this.makeRequest(HttpMethod.GET, endpoint); + } + + async updateRecord(tableId: string, recordInput: Record, version = 3) { + const endpoint = version === 3 + ? `/v2/tables/${tableId}/records/` + : `/v1/db/data/noco/${tableId}`; + return await this.makeRequest( + HttpMethod.PATCH, + endpoint, + undefined, + recordInput, + ); + } + + async deleteRecord(tableId: string, recordId: number, version = 3) { + const endpoint = version === 3 + ? `/v2/tables/${tableId}/records/` + : `/v1/db/data/noco/${tableId}/${recordId}`; + const body = version === 3 ? { Id: recordId } : undefined; + return await this.makeRequest( + HttpMethod.DELETE, + endpoint, + undefined, + body + ); + } + + async listRecords( + tableId: string, + params: ListRecordsParams, + version = 3 + ): Promise>> { + const endpoint = version === 3 + ? `/v2/tables/${tableId}/records/` + : `/v1/db/data/noco/${tableId}`; + return await this.makeRequest>>( + HttpMethod.GET, + endpoint, + params, + ); + } +} diff --git a/packages/pieces/community/nocodb/src/lib/common/index.ts b/packages/pieces/community/nocodb/src/lib/common/index.ts new file mode 100644 index 0000000..f18da82 --- /dev/null +++ b/packages/pieces/community/nocodb/src/lib/common/index.ts @@ -0,0 +1,262 @@ +import { nocodbAuth } from '../../'; +import { DynamicPropsValue, PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { NocoDBClient } from './client'; + +export function makeClient(auth: PiecePropValueSchema) { + return new NocoDBClient(auth.baseUrl, auth.apiToken); +} + +export const nocodbCommon = { + workspaceId: Property.Dropdown({ + displayName: 'Workspace ID', + refreshers: [], + required: false, + description: 'For self-hosted instances,select "No Workspace".', + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first.', + options: [], + }; + } + + const client = makeClient(auth as PiecePropValueSchema); + try + { + const response = await client.listWorkspaces(); + return { + disabled: false, + options: response.list.map((workspace) => { + return { + label: workspace.title, + value: workspace.id, + }; + }), + }; + + } + catch(error) + { + return{ + disabled:false, + options:[{ + label:'No Workspace', + value:'none' + }] + } + } + + + }, + }), + baseId: Property.Dropdown({ + displayName: 'Base ID', + refreshers: ['workspaceId'], + required: true, + options: async ({ auth, workspaceId }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first.', + options: [], + }; + } + + try { + const client = makeClient(auth as PiecePropValueSchema); + const response = await client.listBases(workspaceId as string || undefined, (auth as PiecePropValueSchema).version || 3); + + return { + disabled: false, + options: response.list.map((base) => { + return { + label: base.title, + value: base.id, + }; + }), + }; + } catch (error) { + console.error('Error fetching bases:', error); + return { + disabled: true, + placeholder: 'Error fetching bases. Please check your connection and version.', + options: [], + }; + } + }, + }), + tableId: Property.Dropdown({ + displayName: 'Table ID', + refreshers: ['workspaceId', 'baseId'], + required: true, + options: async ({ auth, baseId }) => { + if (!auth || !baseId) { + return { + disabled: true, + placeholder: 'Please connect your account first and select base.', + options: [], + }; + } + + const client = makeClient(auth as PiecePropValueSchema); + const response = await client.listTables(baseId as string, (auth as PiecePropValueSchema).version || 3); + + return { + disabled: false, + options: response.list.map((table) => { + return { + label: table.title, + value: table.id, + }; + }), + }; + }, + }), + columnId: Property.MultiSelectDropdown({ + displayName: 'Fields', + description: + 'Allows you to specify the fields that you wish to include in your API response. By default, all the fields are included in the response.', + refreshers: ['workspaceId', 'baseId', 'tableId'], + required: false, + options: async ({ auth, baseId, tableId }) => { + if (!auth || !baseId || !tableId) { + return { + disabled: true, + placeholder: 'Please connect your account first and select base.', + options: [], + }; + } + + const client = makeClient(auth as PiecePropValueSchema); + const response = await client.getTable(tableId as unknown as string, (auth as PiecePropValueSchema).version || 3); + + return { + disabled: false, + options: response.columns.map((column) => { + return { + label: column.title, + value: column.title, + }; + }), + }; + }, + }), + tableColumns: Property.DynamicProperties({ + displayName: 'Table Columns', + refreshers: ['tableId'], + required: true, + props: async ({ auth, tableId }) => { + if (!auth) return {}; + if (!tableId) return {}; + + const fields: DynamicPropsValue = {}; + + const client = makeClient(auth as PiecePropValueSchema); + const response = await client.getTable(tableId as unknown as string, (auth as PiecePropValueSchema).version || 3); + + for (const column of response.columns) { + switch (column.uidt) { + case 'SingleLineText': + case 'PhoneNumber': + case 'Email': + case 'URL': + fields[column.title] = Property.ShortText({ + displayName: column.title, + required: false, + }); + break; + case 'LongText': + fields[column.title] = Property.LongText({ + displayName: column.title, + required: false, + }); + break; + case 'Number': + case 'Decimal': + case 'Percent': + case 'Rating': + case 'Currency': + case 'Year': + fields[column.title] = Property.Number({ + displayName: column.title, + required: false, + }); + break; + case 'Checkbox': + fields[column.title] = Property.Checkbox({ + displayName: column.title, + required: true, + }); + break; + case 'MultiSelect': + fields[column.title] = Property.StaticMultiSelectDropdown({ + displayName: column.title, + required: false, + options: { + disabled: false, + options: column.colOptions + ? column.colOptions.options.map((option) => { + return { + label: option.title, + value: option.title, + }; + }) + : [], + }, + }); + break; + case 'SingleSelect': + fields[column.title] = Property.StaticDropdown({ + displayName: column.title, + required: false, + options: { + disabled: false, + options: column.colOptions + ? column.colOptions.options.map((option) => { + return { + label: option.title, + value: option.title, + }; + }) + : [], + }, + }); + break; + case 'Date': + fields[column.title] = Property.ShortText({ + displayName: column.title, + required: false, + description: column.meta?.['date_format'] + ? `Please provide date in ${column.meta['date_format']} format.` + : '', + }); + break; + case 'Time': + fields[column.title] = Property.ShortText({ + displayName: column.title, + required: false, + description: 'Please provide time in HH:mm:ss format.', + }); + break; + case 'DateTime': + fields[column.title] = Property.DateTime({ + displayName: column.title, + required: false, + }); + break; + case 'JSON': + fields[column.title] = Property.Json({ + displayName: column.title, + required: false, + }); + break; + default: + break; + } + } + + return fields; + }, + }), +}; diff --git a/packages/pieces/community/nocodb/src/lib/common/types.ts b/packages/pieces/community/nocodb/src/lib/common/types.ts new file mode 100644 index 0000000..22bd951 --- /dev/null +++ b/packages/pieces/community/nocodb/src/lib/common/types.ts @@ -0,0 +1,122 @@ +export interface ListAPIResponse { + list: T[]; + pageInfo: { + totalRows: number; + page: number; + pageSize: number; + isFirstPage: boolean; + isLastPage: boolean; + }; +} + +export interface WorkspaceResponse { + id: string; + title: string; + description: string; + deleted: boolean; + deleted_at: string; + status: number; + order: number; +} + +export interface BaseResponse { + id: string; + title: string; + description: string; + deleted: boolean; + created_at: string; + updated_at: string; + status: number; + order: number; + type: string; +} + +export interface TableResponse { + id: string; + source_id: string; + description: string; + base_id: string; + table_name: string; + title: string; + type: string; + created_at: string; + updated_at: string; + order: number; + enabled: boolean; +} + +type ColumnType = + | 'Attachment' + | 'AutoNumber' + | 'Barcode' + | 'Button' + | 'Checkbox' + | 'Collaborator' + | 'Count' + | 'CreatedTime' + | 'Currency' + | 'Date' + | 'DateTime' + | 'Decimal' + | 'Duration' + | 'Email' + | 'Formula' + | 'ForeignKey' + | 'GeoData' + | 'Geometry' + | 'ID' + | 'JSON' + | 'LastModifiedTime' + | 'LongText' + | 'LinkToAnotherRecord' + | 'Lookup' + | 'MultiSelect' + | 'Number' + | 'Percent' + | 'PhoneNumber' + | 'Rating' + | 'Rollup' + | 'SingleLineText' + | 'SingleSelect' + | 'SpecificDBType' + | 'Time' + | 'URL' + | 'Year' + | 'QrCode' + | 'Links' + | 'User' + | 'CreatedBy' + | 'LastModifiedBy'; + +export interface ColumnResponse { + id: string; + title: string; + column_name: string; + uidt: ColumnType; + colOptions?: { + options: Array<{ title: string; id: string }>; + }; + meta: Record | null; +} + +export interface GetTableResponse { + id: string; + source_id: string; + table_name: string; + title: string; + type: string; + created_at: string; + updated_at: string; + order: number; + enabled: boolean; + columns: Array; +} + +export interface ListRecordsParams extends Record { + fields?: string; + sort?: string; + where?: string; + offset: number; + limit: number; + viewId?: string; +} diff --git a/packages/pieces/community/nocodb/tsconfig.json b/packages/pieces/community/nocodb/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/nocodb/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/nocodb/tsconfig.lib.json b/packages/pieces/community/nocodb/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/nocodb/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/notion/.eslintrc.json b/packages/pieces/community/notion/.eslintrc.json new file mode 100644 index 0000000..d7449bb --- /dev/null +++ b/packages/pieces/community/notion/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {}, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/notion/README.md b/packages/pieces/community/notion/README.md new file mode 100644 index 0000000..8f6e968 --- /dev/null +++ b/packages/pieces/community/notion/README.md @@ -0,0 +1,7 @@ +# pieces-notion + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-notion` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/notion/package.json b/packages/pieces/community/notion/package.json new file mode 100644 index 0000000..2e0801d --- /dev/null +++ b/packages/pieces/community/notion/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-notion", + "version": "0.3.3" +} diff --git a/packages/pieces/community/notion/project.json b/packages/pieces/community/notion/project.json new file mode 100644 index 0000000..229b3c6 --- /dev/null +++ b/packages/pieces/community/notion/project.json @@ -0,0 +1,36 @@ +{ + "name": "pieces-notion", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/notion/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/notion", + "tsConfig": "packages/pieces/community/notion/tsconfig.lib.json", + "packageJson": "packages/pieces/community/notion/package.json", + "main": "packages/pieces/community/notion/src/index.ts", + "assets": [ + "packages/pieces/community/notion/*.md", + { + "input": "packages/pieces/community/notion/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/notion/src/index.ts b/packages/pieces/community/notion/src/index.ts new file mode 100644 index 0000000..b8d88ce --- /dev/null +++ b/packages/pieces/community/notion/src/index.ts @@ -0,0 +1,60 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2AuthorizationMethod, + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { appendToPage } from './lib/actions/append-to-page'; +import { createDatabaseItem } from './lib/actions/create-database-item'; +import { createPage } from './lib/actions/create-page'; +import { updateDatabaseItem } from './lib/actions/update-database-item'; +import { newDatabaseItem } from './lib/triggers/new-database-item'; +import { updatedDatabaseItem } from './lib/triggers/updated-database-item'; +import { findDatabaseItem } from './lib/actions/find-item'; +import { getPageOrBlockChildren } from './lib/actions/get-page-or-block-children'; + +export const notionAuth = PieceAuth.OAuth2({ + authUrl: 'https://api.notion.com/v1/oauth/authorize', + tokenUrl: 'https://api.notion.com/v1/oauth/token', + scope: [], + extra: { + owner: 'user', + }, + authorizationMethod: OAuth2AuthorizationMethod.HEADER, + required: true, +}); + +export const notion = createPiece({ + displayName: 'Notion', + description: 'The all-in-one workspace', + logoUrl: 'https://cdn.activepieces.com/pieces/notion.png', + categories: [PieceCategory.PRODUCTIVITY], + minimumSupportedRelease: '0.30.0', + authors: [ + 'ShayPunter', + 'kishanprmr', + 'MoShizzle', + 'khaledmashaly', + 'abuaboud', + 'AdamSelene', + ], + auth: notionAuth, + actions: [ + createDatabaseItem, + updateDatabaseItem, + findDatabaseItem, + createPage, + appendToPage, + getPageOrBlockChildren, + createCustomApiCallAction({ + baseUrl: () => 'https://api.notion.com/v1', + auth: notionAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [newDatabaseItem, updatedDatabaseItem], +}); diff --git a/packages/pieces/community/notion/src/lib/actions/append-to-page.ts b/packages/pieces/community/notion/src/lib/actions/append-to-page.ts new file mode 100644 index 0000000..8c1643d --- /dev/null +++ b/packages/pieces/community/notion/src/lib/actions/append-to-page.ts @@ -0,0 +1,39 @@ +import { + createAction, + OAuth2PropertyValue, + Property, +} from '@activepieces/pieces-framework'; +import { Client } from '@notionhq/client'; + +import { notionAuth } from '../..'; +import { notionCommon } from '../common'; +import { markdownToBlocks } from '@tryfabric/martian'; + +export const appendToPage = createAction({ + auth: notionAuth, + name: 'append_to_page', + displayName: 'Append to Page', + description: 'Appends content to the end of a page.', + props: { + pageId: notionCommon.page, + content: Property.LongText({ + displayName: 'Content', + description: + 'The content you want to append. You can use markdown formatting.', + required: true, + }), + }, + async run(context) { + const { pageId, content } = context.propsValue; + + const notion = new Client({ + auth: (context.auth as OAuth2PropertyValue).access_token, + notionVersion: '2022-02-22', + }); + + return await notion.blocks.children.append({ + block_id: pageId as string, + children: markdownToBlocks(content), + }); + }, +}); diff --git a/packages/pieces/community/notion/src/lib/actions/create-database-item.ts b/packages/pieces/community/notion/src/lib/actions/create-database-item.ts new file mode 100644 index 0000000..00e9de0 --- /dev/null +++ b/packages/pieces/community/notion/src/lib/actions/create-database-item.ts @@ -0,0 +1,77 @@ +import { + createAction, + DynamicPropsValue, + OAuth2PropertyValue, + Property, +} from '@activepieces/pieces-framework'; +import { Client } from '@notionhq/client'; +import { NotionFieldMapping } from '../common/models'; +import { notionAuth } from '../..'; +import { notionCommon } from '../common'; + +export const createDatabaseItem = createAction({ + auth: notionAuth, + name: 'create_database_item', + displayName: 'Create Database Item', + description: 'Creates an item in a database.', + props: { + database_id: notionCommon.database_id, + databaseFields: notionCommon.databaseFields, + content: Property.LongText({ + displayName: 'Content', + description: 'The content you want to append to your item.', + required: false, + }), + }, + async run(context) { + const database_id = context.propsValue.database_id!; + const databaseFields = context.propsValue.databaseFields!; + const content = context.propsValue.content; + const notionFields: DynamicPropsValue = {}; + const notion = new Client({ + auth: (context.auth as OAuth2PropertyValue).access_token, + notionVersion: '2022-02-22', + }); + const { properties } = await notion.databases.retrieve({ + database_id: database_id as unknown as string, + }); + + Object.keys(databaseFields).forEach((key) => { + if (databaseFields[key] !== '') { + const fieldType: string = properties[key]?.type; + if (fieldType) { + notionFields[key] = NotionFieldMapping[fieldType].buildNotionType( + databaseFields[key] + ); + } + } + }); + + const children: any[] = []; + // Add content to page + if (content) + children.push({ + object: 'block', + type: 'paragraph', + paragraph: { + rich_text: [ + { + type: 'text', + text: { + content: content, + }, + }, + ], + }, + }); + + return await notion.pages.create({ + parent: { + type: 'database_id', + database_id: database_id, + }, + properties: notionFields, + children: children, + }); + }, +}); diff --git a/packages/pieces/community/notion/src/lib/actions/create-page.ts b/packages/pieces/community/notion/src/lib/actions/create-page.ts new file mode 100644 index 0000000..2853006 --- /dev/null +++ b/packages/pieces/community/notion/src/lib/actions/create-page.ts @@ -0,0 +1,77 @@ +import { + createAction, + OAuth2PropertyValue, + Property, +} from '@activepieces/pieces-framework'; +import { Client } from '@notionhq/client'; + +import { notionAuth } from '../..'; +import { notionCommon } from '../common'; + +export const createPage = createAction({ + auth: notionAuth, + name: 'createPage', + displayName: 'Create Page', + description: 'Create a page under a parent page.', + props: { + pageId: notionCommon.page, + title: Property.ShortText({ + displayName: 'Title', + description: 'The title of the page.', + required: false, + }), + content: Property.LongText({ + displayName: 'Content', + description: 'The content of the page.', + required: false, + }), + }, + + async run(context) { + const { pageId, title, content } = context.propsValue; + + const notion = new Client({ + auth: (context.auth as OAuth2PropertyValue).access_token, + notionVersion: '2022-02-22', + }); + + const pageProperties: any = { + title: { + title: [ + { + text: { + content: title ?? '', + }, + }, + ], + }, + }; + + const children: any[] = []; + // Add content to page + if (content) + children.push({ + object: 'block', + type: 'paragraph', + paragraph: { + rich_text: [ + { + type: 'text', + text: { + content: content, + }, + }, + ], + }, + }); + + const page = await notion.pages.create({ + parent: { + page_id: pageId as string, + }, + properties: pageProperties, + children: children, + }); + return page; + }, +}); diff --git a/packages/pieces/community/notion/src/lib/actions/find-item.ts b/packages/pieces/community/notion/src/lib/actions/find-item.ts new file mode 100644 index 0000000..aa9025c --- /dev/null +++ b/packages/pieces/community/notion/src/lib/actions/find-item.ts @@ -0,0 +1,91 @@ +import { notionAuth } from '../../'; +import { createAction } from '@activepieces/pieces-framework'; +import { notionCommon } from '../common'; +import { Client } from '@notionhq/client'; + +export const findDatabaseItem = createAction({ + auth: notionAuth, + name: 'notion-find-database-item', + displayName: 'Find Database Item', + description: 'Searches for an item in database by field.', + props: { + database_id: notionCommon.database_id, + filterDatabaseFields: notionCommon.filterDatabaseFields, + }, + async run(context) { + const databaseId = context.propsValue.database_id; + const filterFields = context.propsValue.filterDatabaseFields; + + const notion = new Client({ + auth: context.auth.access_token, + notionVersion: '2022-02-22', + }); + + const { properties } = await notion.databases.retrieve({ + database_id: databaseId as string, + }); + + const filterArray = []; + + for (const fieldKey in filterFields) { + const fieldValue = filterFields[fieldKey]; + const fieldType = properties[fieldKey].type; + if (fieldValue === '' || fieldValue === undefined) { + continue; + } + switch (fieldType) { + case 'number': + filterArray.push({ + property: fieldKey, + number: { equals: Number(fieldValue) }, + }); + break; + case 'rich_text': + filterArray.push({ + property: fieldKey, + rich_text: { equals: fieldValue }, + }); + break; + case 'email': + filterArray.push({ + property: fieldKey, + email: { equals: fieldValue }, + }); + break; + case 'select': + filterArray.push({ + property: fieldKey, + select: { equals: fieldValue }, + }); + break; + case 'phone_number': + filterArray.push({ + property: fieldKey, + phone_number: { equals: fieldValue }, + }); + break; + case 'url': + filterArray.push({ property: fieldKey, url: { equals: fieldValue } }); + break; + case 'title': + filterArray.push({ + property: fieldKey, + title: { equals: fieldValue }, + }); + break; + } + } + + const { results } = await notion.databases.query({ + database_id: databaseId as string, + filter: { + and: filterArray, + }, + }); + + return { + success: results.length > 0, + results, + }; + }, +}); diff --git a/packages/pieces/community/notion/src/lib/actions/get-page-or-block-children.ts b/packages/pieces/community/notion/src/lib/actions/get-page-or-block-children.ts new file mode 100644 index 0000000..8360ca2 --- /dev/null +++ b/packages/pieces/community/notion/src/lib/actions/get-page-or-block-children.ts @@ -0,0 +1,107 @@ +import { + createAction, + DynamicPropsValue, + OAuth2PropertyValue, + Property, +} from '@activepieces/pieces-framework'; +import { NotionToMarkdown } from 'notion-to-md'; +import { notionAuth } from '../..'; +import { Client, collectPaginatedAPI, isFullBlock } from '@notionhq/client'; +import { PartialBlockObjectResponse } from '@notionhq/client/build/src/api-endpoints'; + +export const getPageOrBlockChildren = createAction({ + auth: notionAuth, + name: 'getPageOrBlockChildren', + displayName: 'Get block content', + description: 'Retrieve the actual content of a page (represented by blocks).', + props: { + parentId: Property.ShortText({ + displayName: 'Page or parent block ID', + required: true, + }), + markdown: Property.Checkbox({ + displayName: 'Markdown', + description: 'Convert Notion JSON blocks to Markdown', + required: true, + defaultValue: false, + }), + dynamic: Property.DynamicProperties({ + displayName: 'Dynamic properties', + refreshers: ['markdown'], + required: true, + props: async ({ markdown }) => { + if (markdown) { + return {}; + } + const fields: DynamicPropsValue = { + depth: Property.Number({ + displayName: 'Depth', + description: 'Recursively retrieve children up to this depth', + required: true, + defaultValue: 1, + }), + }; + return fields; + }, + }), + }, + async run(context) { + const notion = new Client({ + auth: (context.auth as OAuth2PropertyValue).access_token, + notionVersion: '2022-02-22', + }); + + if (context.propsValue.markdown) { + const n2m = new NotionToMarkdown({ + notionClient: notion, + config: { parseChildPages: false }, + }); + return n2m.toMarkdownString( + await n2m.pageToMarkdown(context.propsValue.parentId) + ).parent; + } else { + return getBlockChildrenRecursively( + notion, + context.propsValue.parentId, + context.propsValue.dynamic['depth'], + 0 + ); + } + }, +}); + +async function getBlockChildrenRecursively( + notion: Client, + blockId: string, + depth: number, + currentDepth = 0 +) { + if (currentDepth >= depth) { + return []; + } + + // Retrieve the block's children + const children = await collectPaginatedAPI(notion.blocks.children.list, { + block_id: blockId, + }); + + // Recursively retrieve children of each child block + for (const child of children) { + if (!isFullBlock(child) || !child.has_children) { + continue; + } + const childChildren = await getBlockChildrenRecursively( + notion, + child.id, + depth, + currentDepth + 1 + ); + (child as BlockObjectResponseWithChildren).children = childChildren; + } + + return children; +} + +type BlockObjectResponseWithChildren = PartialBlockObjectResponse & { + children?: BlockObjectResponseWithChildren[]; +}; diff --git a/packages/pieces/community/notion/src/lib/actions/update-database-item.ts b/packages/pieces/community/notion/src/lib/actions/update-database-item.ts new file mode 100644 index 0000000..01d0831 --- /dev/null +++ b/packages/pieces/community/notion/src/lib/actions/update-database-item.ts @@ -0,0 +1,50 @@ +import { + createAction, + DynamicPropsValue, + OAuth2PropertyValue, +} from '@activepieces/pieces-framework'; +import { Client } from '@notionhq/client'; +import { NotionFieldMapping } from '../common/models'; + +import { notionAuth } from '../..'; +import { notionCommon } from '../common'; +export const updateDatabaseItem = createAction({ + auth: notionAuth, + name: 'update_database_item', + displayName: 'Update Database Item', + description: 'Updates an item in database', + props: { + database_id: notionCommon.database_id, + database_item_id: notionCommon.database_item_id, + databaseFields: notionCommon.databaseFields, + }, + async run(context) { + const { database_id, database_item_id, databaseFields } = + context.propsValue; + + if (!database_item_id) throw new Error('Item ID is required'); + + const notionFields: DynamicPropsValue = {}; + + const notion = new Client({ + auth: (context.auth as OAuth2PropertyValue).access_token, + notionVersion: '2022-02-22', + }); + const { properties } = await notion.databases.retrieve({ + database_id: database_id as unknown as string, + }); + + Object.keys(databaseFields).forEach((key) => { + if (databaseFields[key] !== '') { + const fieldType: string = properties[key].type; + notionFields[key] = NotionFieldMapping[fieldType].buildNotionType( + databaseFields[key] + ); + } + }); + return await notion.pages.update({ + page_id: database_item_id, + properties: notionFields, + }); + }, +}); diff --git a/packages/pieces/community/notion/src/lib/common/index.ts b/packages/pieces/community/notion/src/lib/common/index.ts new file mode 100644 index 0000000..1e6ad1e --- /dev/null +++ b/packages/pieces/community/notion/src/lib/common/index.ts @@ -0,0 +1,296 @@ +import { + OAuth2PropertyValue, + Property, + DynamicPropsValue, +} from '@activepieces/pieces-framework'; +import { Client } from '@notionhq/client'; +import { NotionFieldMapping } from './models'; + +export const notionCommon = { + baseUrl: 'https://api.notion.com/v1', + database_id: Property.Dropdown({ + displayName: 'Database', + required: true, + description: 'Select the database you want to use', + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your Notion account first', + options: [], + }; + } + const notion = new Client({ + auth: (auth as OAuth2PropertyValue).access_token, + notionVersion: '2022-02-22', + }); + const databases = await notion.search({ + filter: { + property: 'object', + value: 'database', + }, + }); + return { + placeholder: 'Select a database', + options: databases.results + .filter((f: any) => f.title.length > 0) + .map((database: any) => ({ + label: database.title?.[0]?.plain_text ?? 'Unknown title', + value: database.id, + })), + }; + }, + }), + database_item_id: Property.Dropdown({ + displayName: 'Database Item', + description: 'Select the item you want to update', + required: true, + refreshers: ['database_id'], + options: async ({ auth, database_id }) => { + if (!auth || !database_id) { + return { + disabled: true, + placeholder: + 'Please connect your Notion account first and select database', + options: [], + }; + } + const notion = new Client({ + auth: (auth as OAuth2PropertyValue).access_token, + notionVersion: '2022-02-22', + }); + const { results } = await notion.databases.query({ + database_id: database_id as string, + filter_properties: ['title'], + }); + return { + disabled: false, + options: results.map((item: any) => { + const property: any = Object.values(item.properties)[0]; + return { + label: property.title[0]?.plain_text ?? 'No Title', + value: item.id, + }; + }), + }; + }, + }), + databaseFields: Property.DynamicProperties({ + displayName: 'Fields', + required: true, + refreshers: ['database_id'], + props: async ({ auth, database_id }) => { + if (!auth || !database_id) { + return { + disabled: true, + placeholder: + 'Please connect your Notion account first and select database', + options: [], + }; + } + const fields: DynamicPropsValue = {}; + try { + const notion = new Client({ + auth: (auth as OAuth2PropertyValue).access_token, + notionVersion: '2022-02-22', + }); + const { properties } = await notion.databases.retrieve({ + database_id: database_id as unknown as string, + }); + for (const key in properties) { + const property = properties[key]; + if ( + [ + 'rollup', + 'button', + 'files', + 'verification', + 'formula', + 'unique_id', + 'relation', + 'created_by', + 'created_time', + 'last_edited_by', + 'last_edited_time', + ].includes(property.type) + ) { + continue; + } + if (property.type === 'people') { + const { results } = await notion.users.list({ page_size: 100 }); + fields[property.name] = Property.StaticMultiSelectDropdown({ + displayName: property.name, + required: false, + options: { + disabled: false, + options: results + .filter( + (user) => user.type === 'person' && user.name !== null + ) + .map((option: { id: string; name: any }) => { + return { + label: option.name, + value: option.id, + }; + }), + }, + }); + } else { + fields[property.name] = + NotionFieldMapping[property.type].buildActivepieceType(property); + } + } + } catch (e) { + console.debug(e); + } + return fields; + }, + }), + filterDatabaseFields: Property.DynamicProperties({ + displayName: 'Fields', + required: true, + refreshers: ['database_id'], + props: async ({ auth, database_id }) => { + if (!auth || !database_id) { + return { + disabled: true, + placeholder: + 'Please connect your Notion account first and select database', + options: [], + }; + } + const fields: DynamicPropsValue = {}; + try { + const notion = new Client({ + auth: (auth as OAuth2PropertyValue).access_token, + notionVersion: '2022-02-22', + }); + const { properties } = await notion.databases.retrieve({ + database_id: database_id as unknown as string, + }); + + for (const key in properties) { + const property = properties[key]; + if ( + [ + 'rollup', + 'button', + 'files', + 'verification', + 'status', + 'multi_select', + 'formula', + 'unique_id', + 'relation', + 'checkbox', + 'created_by', + 'created_time', + 'last_edited_by', + 'last_edited_time', + ].includes(property.type) + ) { + continue; + } + fields[property.name] = + NotionFieldMapping[property.type].buildActivepieceType(property); + } + } catch (e) { + console.debug(e); + } + return fields; + }, + }), + + page: Property.Dropdown({ + displayName: 'Page', + required: true, + description: + 'Select the page you want to use. Only your most recently edited 100 pages will appear.', + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your Notion account first', + options: [], + }; + } + const pages = await getPages(auth as OAuth2PropertyValue); + + return { + placeholder: 'Select a page', + options: pages.map((page: any) => ({ + label: + page.properties.Name?.title[0]?.plain_text ?? + page.properties.title?.title[0]?.text?.content ?? + 'No Title', + value: page.id, + })), + }; + }, + }), +}; + +export async function getPages( + auth: OAuth2PropertyValue, + search?: { + editedAfter?: Date; + createdAfter?: Date; + }, + sort?: { + property: string; + direction: 'ascending' | 'descending'; + } +): Promise { + const notion = new Client({ + auth: auth.access_token, + notionVersion: '2022-02-22', + }); + + let filter: any = { + property: 'object', + value: 'page', + }; + if (search?.editedAfter) + filter = { + and: [ + { + property: 'object', + value: 'page', + }, + { + timestamp: 'last_edited_time', + last_edited_time: { + after: search.editedAfter, + }, + }, + ], + }; + if (search?.createdAfter) + filter = { + and: [ + { + property: 'object', + value: 'page', + }, + { + timestamp: 'created_time', + created_time: { + after: search.createdAfter, + }, + }, + ], + }; + + const sortObj: any = { + direction: sort?.direction ?? 'descending', + timestamp: sort?.property ?? 'last_edited_time', + }; + + const pages = await notion.search({ + filter: filter, + sort: sortObj, + }); + return pages.results as any[]; +} diff --git a/packages/pieces/community/notion/src/lib/common/models.ts b/packages/pieces/community/notion/src/lib/common/models.ts new file mode 100644 index 0000000..3c9cb6a --- /dev/null +++ b/packages/pieces/community/notion/src/lib/common/models.ts @@ -0,0 +1,472 @@ +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +type SelectColor = + | 'default' + | 'gray' + | 'brown' + | 'orange' + | 'yellow' + | 'green' + | 'blue' + | 'purple' + | 'pink' + | 'red'; + +type DateDatabaseProperty = { + id: string; + name: string; + type: 'date'; + date: Record; +}; +type CheckboxDatabaseProperty = { + id: string; + name: string; + type: 'checkbox'; + checkbox: Record; +}; +type CreatedByDatabaseProperty = { + id: string; + name: string; + type: 'created_by'; +}; +type CreatedTimeDatabaseProperty = { + id: string; + name: string; + type: 'created_time'; + created_time: Record; +}; +type EmailDatabaseProperty = { + id: string; + name: string; + type: 'email'; + email: Record; +}; +type FilesDatabaseProperty = { + id: string; + name: string; + type: 'files'; + files: Record; +}; +type FormulaDatabaseProperty = { + id: string; + name: string; + type: 'formula'; + formula: { + expression: string; + }; +}; +type LastEditedByDatabaseProperty = { + id: string; + name: string; + type: 'last_edited_by'; + last_edited_by: Record; +}; +type LastEditedTimeDatabaseProperty = { + id: string; + name: string; + type: 'last_edited_time'; + last_edited_time: Record; +}; +type MultiSelectDatabaseProperty = { + id: string; + name: string; + type: 'multi_select'; + multi_select: { + options: { + id: string; + name: string; + color: SelectColor; + }[]; + }; +}; +type NumberFormat = + | 'number' + | 'number_with_commas' + | 'percent' + | 'dollar' + | 'canadian_dollar' + | 'singapore_dollar' + | 'euro' + | 'pound' + | 'yen' + | 'ruble' + | 'rupee' + | 'won' + | 'yuan' + | 'real' + | 'lira' + | 'rupiah' + | 'franc' + | 'hong_kong_dollar' + | 'new_zealand_dollar' + | 'krona' + | 'norwegian_krone' + | 'mexican_peso' + | 'rand' + | 'new_taiwan_dollar' + | 'danish_krone' + | 'zloty' + | 'baht' + | 'forint' + | 'koruna' + | 'shekel' + | 'chilean_peso' + | 'philippine_peso' + | 'dirham' + | 'colombian_peso' + | 'riyal' + | 'ringgit' + | 'leu' + | 'argentine_peso' + | 'uruguayan_peso' + | 'peruvian_sol'; +type NumberDatabaseProperty = { + id: string; + name: string; + type: 'number'; + number: { + format: NumberFormat; + }; +}; +type PeopleDatabaseProperty = { + id: string; + name: string; + type: 'people'; + people: Record; +}; +type PhoneNumberDatabaseProperty = { + id: string; + name: string; + type: 'phone_number'; + phone_number: Record; +}; +type RelationDatabaseProperty = { + id: string; + name: string; + type: 'relation'; + relation: { + database_id: string; + synced_property_id: string; + synced_property_name: string; + }; +}; +type RichTextDatabaseProperty = { + id: string; + name: string; + type: 'rich_text'; + rich_text: Record; +}; +type RollupFunction = + | 'count' + | 'count_values' + | 'empty' + | 'not_empty' + | 'unique' + | 'show_unique' + | 'percent_empty' + | 'percent_not_empty' + | 'sum' + | 'average' + | 'median' + | 'min' + | 'max' + | 'range' + | 'earliest_date' + | 'latest_date' + | 'date_range' + | 'checked' + | 'unchecked' + | 'percent_checked' + | 'percent_unchecked' + | 'count_per_group' + | 'percent_per_group' + | 'show_original'; + +type RollupDatabaseProperty = { + type: 'rollup'; + rollup: { + rollup_property_name: string; + relation_property_name: string; + rollup_property_id: string; + relation_property_id: string; + function: RollupFunction; + }; + id: string; + name: string; +}; +type SelectDatabaseProperty = { + id: string; + name: string; + type: 'select'; + select: { + options: { + id: string; + name: string; + color: SelectColor; + }[]; + }; +}; +type StatusDatabaseProperty = { + id: string; + name: string; + type: 'status'; + status: { + options: { + id: string; + name: string; + color: SelectColor; + }[]; + groups: { + id: string; + name: string; + color: SelectColor; + option_ids: Array; + }; + }; +}; +type TitleDatabaseProperty = { + type: 'title'; + title: Record; + id: string; + name: string; +}; +type UrlDatabaseProperty = { + type: 'url'; + url: Record; + id: string; + name: string; +}; +export type DatabaseProperty = + | NumberDatabaseProperty + | FormulaDatabaseProperty + | SelectDatabaseProperty + | MultiSelectDatabaseProperty + | StatusDatabaseProperty + | RelationDatabaseProperty + | RollupDatabaseProperty + | TitleDatabaseProperty + | RichTextDatabaseProperty + | UrlDatabaseProperty + | PeopleDatabaseProperty + | FilesDatabaseProperty + | EmailDatabaseProperty + | PhoneNumberDatabaseProperty + | DateDatabaseProperty + | CheckboxDatabaseProperty + | CreatedByDatabaseProperty + | CreatedTimeDatabaseProperty + | LastEditedByDatabaseProperty + | LastEditedTimeDatabaseProperty; + +export interface NotionDatabase { + object: 'database'; + id: string; + created_time: string; + created_by: { + object: 'user'; + id: string; + }; + last_edited_time: string; + last_edited_by: { + object: 'user'; + id: string; + }; + is_inline: boolean; + archived: boolean; + url: string; + public_url: string | null; + cover: + | { + type: 'external'; + external: { + url: string; + }; + } + | null + | { + type: 'file'; + file: { + url: string; + expiry_time: string; + }; + } + | null; + properties: Record; + parent: + | { + type: 'database_id'; + database_id: string; + } + | { + type: 'page_id'; + page_id: string; + } + | { + type: 'block_id'; + block_id: string; + } + | { + type: 'workspace'; + workspace: true; + }; +} + +export const NotionFieldMapping: Record = { + checkbox: { + buildActivepieceType: (property: CheckboxDatabaseProperty) => + Property.Checkbox({ + displayName: property.name, + required: false, + }), + buildNotionType: (property: DynamicPropsValue) => ({ + checkbox: property, + }), + }, + date: { + buildActivepieceType: (property: DateDatabaseProperty) => + Property.DateTime({ + displayName: property.name, + required: false, + }), + buildNotionType: (property: DynamicPropsValue) => ({ + date: { + start: property, + }, + }), + }, + email: { + buildActivepieceType: (property: EmailDatabaseProperty) => + Property.ShortText({ + displayName: property.name, + required: false, + }), + buildNotionType: (property: DynamicPropsValue) => ({ + email: property, + }), + }, + // formula: Property.ShortText, + select: { + buildActivepieceType: (property: SelectDatabaseProperty) => + Property.StaticDropdown({ + displayName: property.name, + required: false, + options: { + disabled: false, + options: property.select.options?.map((option) => { + return { + label: option.name, + value: option.name, + }; + }), + }, + }), + buildNotionType: (property: DynamicPropsValue) => ({ + select: { + name: property, + }, + }), + }, + multi_select: { + buildActivepieceType: (property: MultiSelectDatabaseProperty) => + Property.StaticMultiSelectDropdown({ + displayName: property.name, + required: false, + options: { + disabled: false, + options: property.multi_select.options?.map((option) => { + return { + label: option.name, + value: option.name, + }; + }), + }, + }), + buildNotionType: (property: DynamicPropsValue) => ({ + multi_select: property.map((name: any) => ({ name: name })), + }), + }, + status: { + buildActivepieceType: (property: StatusDatabaseProperty) => + Property.StaticDropdown({ + displayName: property.name, + required: false, + options: { + disabled: false, + options: property.status.options?.map((option) => { + return { + label: option.name, + value: option.name, + }; + }), + }, + }), + buildNotionType: (property: DynamicPropsValue) => ({ + status: { + name: property, + }, + }), + }, + number: { + buildActivepieceType: (property: NumberDatabaseProperty) => + Property.Number({ + displayName: property.name, + required: false, + }), + buildNotionType: (property: DynamicPropsValue) => ({ + number: Number(property), + }), + }, + phone_number: { + buildActivepieceType: (property: PhoneNumberDatabaseProperty) => + Property.Number({ + displayName: property.name, + required: false, + }), + buildNotionType: (property: DynamicPropsValue) => ({ + phone_number: property, + }), + }, + rich_text: { + buildActivepieceType: (property: RichTextDatabaseProperty) => + Property.LongText({ displayName: property.name, required: false }), + buildNotionType: (property: DynamicPropsValue) => ({ + rich_text: [ + { + type: 'text', + text: { + content: property, + }, + }, + ], + }), + }, + title: { + buildActivepieceType: (property: TitleDatabaseProperty) => + Property.ShortText({ displayName: property.name, required: false }), + buildNotionType: (property: DynamicPropsValue) => ({ + title: [ + { + type: 'text', + text: { + content: property, + }, + }, + ], + }), + }, + url: { + buildActivepieceType: (property: UrlDatabaseProperty) => + Property.ShortText({ + displayName: property.name, + required: false, + }), + buildNotionType: (property: DynamicPropsValue) => ({ + url: property, + }), + }, + people: { + buildActivepieceType: undefined, + buildNotionType: (property: DynamicPropsValue) => ({ + people: property.map((id: any) => ({ id: id })), + }), + }, +}; diff --git a/packages/pieces/community/notion/src/lib/triggers/new-database-item.ts b/packages/pieces/community/notion/src/lib/triggers/new-database-item.ts new file mode 100644 index 0000000..06571fc --- /dev/null +++ b/packages/pieces/community/notion/src/lib/triggers/new-database-item.ts @@ -0,0 +1,194 @@ +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { + createTrigger, + TriggerStrategy, + OAuth2PropertyValue, +} from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import { notionCommon } from '../common'; +import { Client } from '@notionhq/client'; +import { notionAuth } from '../..'; +import { isNil } from '@activepieces/shared'; + +export const newDatabaseItem = createTrigger({ + auth: notionAuth, + name: 'new_database_item', + displayName: 'New Database Item', + description: 'Triggers when an item is added to a database.', + props: { + database_id: notionCommon.database_id, + }, + sampleData: { + id: 'd23872cd-c106-4afa-b33d-d3fd66064ccb', + url: 'https://www.notion.so/Take-Fig-on-a-walk-d23872cdc1064afab33dd3fd66064ccb', + icon: { + type: 'emoji', + emoji: '🐶', + }, + cover: null, + object: 'page', + parent: { + type: 'database_id', + database_id: 'fe1eb968-50b6-4d96-83ca-4d19b96f488e', + }, + archived: false, + created_by: { + id: 'f3806fae-a281-4f4e-8563-c816c3e8bd40', + object: 'user', + }, + properties: { + Name: { + id: 'title', + type: 'title', + title: [ + { + href: null, + text: { + link: null, + content: 'Take Fig on a walk', + }, + type: 'text', + plain_text: 'Take Fig on a walk', + annotations: { + bold: false, + code: false, + color: 'default', + italic: false, + underline: false, + strikethrough: false, + }, + }, + ], + }, + Status: { + id: '%5EOE%40', + type: 'select', + select: { + id: '2', + name: 'Doing', + color: 'yellow', + }, + }, + 'Date Created': { + id: "'Y6%3C", + type: 'created_time', + created_time: '2023-03-02T01:43:00.000Z', + }, + }, + created_time: '2023-03-02T01:43:00.000Z', + last_edited_by: { + id: 'f3806fae-a281-4f4e-8563-c816c3e8bd40', + object: 'user', + }, + last_edited_time: '2023-03-02T01:43:00.000Z', + }, + type: TriggerStrategy.POLLING, + async test(ctx) { + return await pollingHelper.test(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + files: ctx.files, + }); + }, + async onEnable(ctx) { + await pollingHelper.onEnable(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + }); + }, + async onDisable(ctx) { + await pollingHelper.onDisable(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + }); + }, + async run(ctx) { + return await pollingHelper.poll(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + files: ctx.files, + }); + }, +}); + +const polling: Polling< + OAuth2PropertyValue, + { database_id: string | undefined } +> = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ auth, propsValue, lastItemId }) => { + const lastItem = lastItemId as string; + let lastCreatedDate: string | null; + + if (lastItem) { + const lastUpdatedEpochMS = Number(lastItem.split('|')[1]); + lastCreatedDate = dayjs(lastUpdatedEpochMS).toISOString(); + } else { + lastCreatedDate = lastItem; + } + + const items = await getResponse( + auth, + propsValue.database_id!, + lastCreatedDate + ); + return items.map((item: any) => { + const object = item as { created_time: string; id: string }; + return { + id: object.id + '|' + dayjs(object.created_time).valueOf(), + data: item, + }; + }); + }, +}; + +const getResponse = async ( + authentication: OAuth2PropertyValue, + database_id: string, + startDate: string | null +) => { + const notion = new Client({ + auth: authentication.access_token, + notionVersion: '2022-02-22', + }); + let cursor; + let hasMore = true; + const results = []; + + do { + const response = await notion.databases.query({ + start_cursor: cursor, + database_id, + filter: + startDate == null + ? undefined + : { + timestamp: 'created_time', + created_time: { + on_or_after: startDate, + }, + }, + sorts: [ + { + timestamp: 'created_time', + direction: 'descending', + }, + ], + }); + + hasMore = response.has_more; + cursor = response.next_cursor ?? undefined; + + results.push(...response.results); + } while (hasMore && !isNil(startDate)); + + return results; +}; diff --git a/packages/pieces/community/notion/src/lib/triggers/updated-database-item.ts b/packages/pieces/community/notion/src/lib/triggers/updated-database-item.ts new file mode 100644 index 0000000..ec0a27e --- /dev/null +++ b/packages/pieces/community/notion/src/lib/triggers/updated-database-item.ts @@ -0,0 +1,195 @@ +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { + createTrigger, + TriggerStrategy, + OAuth2PropertyValue, +} from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import { notionCommon } from '../common'; +import { Client } from '@notionhq/client'; +import { notionAuth } from '../..'; +import { isNil } from '@activepieces/shared'; + +export const updatedDatabaseItem = createTrigger({ + auth: notionAuth, + name: 'updated_database_item', + displayName: 'Updated Database Item', + description: 'Triggers when an item is updated in a database.', + props: { + database_id: notionCommon.database_id, + }, + sampleData: { + id: 'd23872cd-c106-4afa-b33d-d3fd66064ccb', + url: 'https://www.notion.so/Take-Fig-on-a-walk-d23872cdc1064afab33dd3fd66064ccb', + icon: { + type: 'emoji', + emoji: '🐶', + }, + cover: null, + object: 'page', + parent: { + type: 'database_id', + database_id: 'fe1eb968-50b6-4d96-83ca-4d19b96f488e', + }, + archived: false, + created_by: { + id: 'f3806fae-a281-4f4e-8563-c816c3e8bd40', + object: 'user', + }, + properties: { + Name: { + id: 'title', + type: 'title', + title: [ + { + href: null, + text: { + link: null, + content: 'Take Fig on a walk', + }, + type: 'text', + plain_text: 'Take Fig on a walk', + annotations: { + bold: false, + code: false, + color: 'default', + italic: false, + underline: false, + strikethrough: false, + }, + }, + ], + }, + Status: { + id: '%5EOE%40', + type: 'select', + select: { + id: '2', + name: 'Doing', + color: 'yellow', + }, + }, + 'Date Created': { + id: "'Y6%3C", + type: 'created_time', + created_time: '2023-03-02T01:43:00.000Z', + }, + }, + created_time: '2023-03-02T01:43:00.000Z', + last_edited_by: { + id: 'f3806fae-a281-4f4e-8563-c816c3e8bd40', + object: 'user', + }, + last_edited_time: '2023-03-02T01:43:00.000Z', + }, + type: TriggerStrategy.POLLING, + async test(ctx) { + return await pollingHelper.test(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + files: ctx.files, + }); + }, + async onEnable(ctx) { + await pollingHelper.onEnable(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + }); + }, + async onDisable(ctx) { + await pollingHelper.onDisable(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + }); + }, + async run(ctx) { + return await pollingHelper.poll(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + files: ctx.files, + }); + }, +}); + +const polling: Polling< + OAuth2PropertyValue, + { database_id: string | undefined } +> = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ auth, propsValue, lastItemId }) => { + const lastItem = lastItemId as string; + let lastUpdatedDate: string | null; + + if (lastItem) { + const lastUpdatedEpochMS = Number(lastItem.split('|')[1]); + lastUpdatedDate = dayjs(lastUpdatedEpochMS).toISOString(); + } else { + lastUpdatedDate = lastItem; + } + + const items = await getResponse( + auth, + propsValue.database_id!, + lastUpdatedDate + ); + + return items.map((item: any) => { + const object = item as { last_edited_time: string; id: string }; + return { + id: object.id + '|' + dayjs(object.last_edited_time).valueOf(), + data: item, + }; + }); + }, +}; + +const getResponse = async ( + authentication: OAuth2PropertyValue, + database_id: string, + startDate: string | null +) => { + const notion = new Client({ + auth: authentication.access_token, + notionVersion: '2022-02-22', + }); + + let cursor; + let hasMore = true; + const results = []; + do { + const response = await notion.databases.query({ + start_cursor: cursor, + database_id, + filter: + startDate == null + ? undefined + : { + timestamp: 'last_edited_time', + last_edited_time: { + on_or_after: startDate, + }, + }, + sorts: [ + { + timestamp: 'last_edited_time', + direction: 'descending', + }, + ], + }); + + hasMore = response.has_more; + cursor = response.next_cursor ?? undefined; + + results.push(...response.results); + } while (hasMore && !isNil(startDate)); + + return results; +}; diff --git a/packages/pieces/community/notion/tsconfig.json b/packages/pieces/community/notion/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/notion/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/notion/tsconfig.lib.json b/packages/pieces/community/notion/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/notion/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/ntfy/.eslintrc.json b/packages/pieces/community/ntfy/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/ntfy/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/ntfy/README.md b/packages/pieces/community/ntfy/README.md new file mode 100644 index 0000000..f6b3738 --- /dev/null +++ b/packages/pieces/community/ntfy/README.md @@ -0,0 +1,7 @@ +# pieces-ntfy + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-ntfy` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/ntfy/package.json b/packages/pieces/community/ntfy/package.json new file mode 100644 index 0000000..ff75219 --- /dev/null +++ b/packages/pieces/community/ntfy/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-ntfy", + "version": "0.1.7" +} \ No newline at end of file diff --git a/packages/pieces/community/ntfy/project.json b/packages/pieces/community/ntfy/project.json new file mode 100644 index 0000000..ab2854e --- /dev/null +++ b/packages/pieces/community/ntfy/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-ntfy", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/ntfy/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/ntfy", + "tsConfig": "packages/pieces/community/ntfy/tsconfig.lib.json", + "packageJson": "packages/pieces/community/ntfy/package.json", + "main": "packages/pieces/community/ntfy/src/index.ts", + "assets": [ + "packages/pieces/community/ntfy/*.md", + { + "input": "packages/pieces/community/ntfy/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/ntfy/src/index.ts b/packages/pieces/community/ntfy/src/index.ts new file mode 100644 index 0000000..e0f16dd --- /dev/null +++ b/packages/pieces/community/ntfy/src/index.ts @@ -0,0 +1,57 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { sendNotification } from './lib/actions/send-notification'; + +export const ntfyAuth = PieceAuth.CustomAuth({ + description: ` + To obtain a token: + + 1. Log in to your Ntfy instance. + 2. Click on Account + 3. Go under, on Access tokens and click on the button icon to copy your Token or CREATE ACCESS TOKEN if you do not have + 4. Please pay attention to the expiration time when copying/creating a Token. + 4. Copy your access token & and paste them into the fields below. + `, + props: { + base_url: Property.ShortText({ + displayName: 'Server URL', + description: 'Ntfy Instance URL', + required: true, + }), + access_token: PieceAuth.SecretText({ + displayName: 'Access Token', + description: 'Ntfy Access Token', + required: false, + }), + }, + required: true, +}); + +export const ntfy = createPiece({ + displayName: 'ntfy', + description: 'Notification management made easy', + + logoUrl: 'https://cdn.activepieces.com/pieces/ntfy.png', + minimumSupportedRelease: '0.30.0', + categories: [PieceCategory.COMMUNICATION], + auth: ntfyAuth, + authors: ["MyWay","facferreira","la3rence","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + actions: [ + sendNotification, + createCustomApiCallAction({ + baseUrl: (auth) => (auth as { base_url: string }).base_url, + auth: ntfyAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${ + (auth as { access_token: string }).access_token + }`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/ntfy/src/lib/actions/send-notification.ts b/packages/pieces/community/ntfy/src/lib/actions/send-notification.ts new file mode 100644 index 0000000..fa0bc88 --- /dev/null +++ b/packages/pieces/community/ntfy/src/lib/actions/send-notification.ts @@ -0,0 +1,107 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { ntfyAuth } from '../..'; + +const encodeToRFC2047 = (text: string) => { + return `=?UTF-8?B?${Buffer.from(text, 'utf-8').toString('base64')}?=`; +}; + +export const sendNotification = createAction({ + auth: ntfyAuth, + name: 'send_notification', + displayName: 'Send Notification', + description: 'Send a notification to ntfy', + props: { + topic: Property.ShortText({ + displayName: 'Topic', + description: 'The topic/channel to send the notification to, e.g. test1', + required: true, + }), + title: Property.ShortText({ + displayName: 'Title', + description: 'The title of the notification', + required: false, + }), + message: Property.LongText({ + displayName: 'Message', + description: 'The message to send', + required: true, + }), + priority: Property.ShortText({ + displayName: 'Priority', + description: + 'The priority of the notification (1-5). 1 is lowest priority.', + required: false, + }), + tags: Property.Array({ + displayName: 'Tags', + description: 'The tags for the notification.', + required: false, + }), + icon: Property.ShortText({ + displayName: 'Icon', + description: + 'The absolute URL to your icon, e.g. https://example.com/communityIcon_xnt6chtnr2j21.png', + required: false, + }), + actions: Property.LongText({ + displayName: 'Actions', + description: + 'Add Action buttons to notifications, see https://docs.ntfy.sh/publish/#action-buttons', + required: false, + }), + click: Property.ShortText({ + displayName: 'Click', + description: + 'You can define which URL to open when a notification is clicked, see https://docs.ntfy.sh/publish/#click-action', + required: false, + }), + delay: Property.ShortText({ + displayName: 'Delay', + description: + "Let ntfy send messages at a later date, e.g. 'tomorrow, 10am', see https://docs.ntfy.sh/publish/#scheduled-delivery", + required: false, + }), + }, + async run({ auth, propsValue }) { + const baseUrl = auth.base_url.replace(/\/$/, ''); + const accessToken = auth.access_token; + + const topic = propsValue.topic; + let title = propsValue.title; + let message = propsValue.message; + title = encodeToRFC2047(title as string); + message = encodeToRFC2047(message as string); + const priority = propsValue.priority; + const tags = propsValue.tags; + const icon = propsValue.icon; + const actions = propsValue.actions; + const click = propsValue.click; + const delay = propsValue.delay; + + return await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${baseUrl}/${topic}`, + ...(accessToken && { + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }), + headers: { + 'X-Message': message, + 'X-Title': title, + 'X-Priority': priority, + 'X-Tags': tags?.join(','), + 'X-Icon': icon, + 'X-Actions': actions, + 'X-Click': click, + 'X-Delay': delay, + }, + }); + }, +}); diff --git a/packages/pieces/community/ntfy/tsconfig.json b/packages/pieces/community/ntfy/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/ntfy/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/ntfy/tsconfig.lib.json b/packages/pieces/community/ntfy/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/ntfy/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/odoo/.eslintrc.json b/packages/pieces/community/odoo/.eslintrc.json new file mode 100644 index 0000000..359ff63 --- /dev/null +++ b/packages/pieces/community/odoo/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/odoo/README.md b/packages/pieces/community/odoo/README.md new file mode 100644 index 0000000..bd59979 --- /dev/null +++ b/packages/pieces/community/odoo/README.md @@ -0,0 +1,7 @@ +# pieces-odoo + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-odoo` to build the library. diff --git a/packages/pieces/community/odoo/package-lock.json b/packages/pieces/community/odoo/package-lock.json new file mode 100644 index 0000000..ec8e7d6 --- /dev/null +++ b/packages/pieces/community/odoo/package-lock.json @@ -0,0 +1,236 @@ +{ + "name": "@activepieces/piece-odoo", + "version": "0.0.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-odoo", + "version": "0.0.2", + "dependencies": { + "url": "^0.11.3", + "xmlrpc": "^1.3.2" + }, + "devDependencies": { + "@types/xmlrpc": "^1.3.10" + } + }, + "node_modules/@types/node": { + "version": "20.11.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", + "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/xmlrpc": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@types/xmlrpc/-/xmlrpc-1.3.10.tgz", + "integrity": "sha512-0jU+htwq8NGHqcz9pZzD76/Kpe1dpkDGFH696UoTONkwteRHA/2nzBAanqN7EppdkO+DwYYZd9M8IaOcnDqeVQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" + }, + "node_modules/qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/set-function-length": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", + "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "dependencies": { + "define-data-property": "^1.1.1", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.2", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/url": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.11.2" + } + }, + "node_modules/xmlbuilder": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", + "integrity": "sha512-eKRAFz04jghooy8muekqzo8uCSVNeyRedbuJrp0fovbLIi7wlsYtdUn3vBAAPq2Y3/0xMz2WMEUQ8yhVVO9Stw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlrpc": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/xmlrpc/-/xmlrpc-1.3.2.tgz", + "integrity": "sha512-jQf5gbrP6wvzN71fgkcPPkF4bF/Wyovd7Xdff8d6/ihxYmgETQYSuTc+Hl+tsh/jmgPLro/Aro48LMFlIyEKKQ==", + "dependencies": { + "sax": "1.2.x", + "xmlbuilder": "8.2.x" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.0.0" + } + } + } +} diff --git a/packages/pieces/community/odoo/package.json b/packages/pieces/community/odoo/package.json new file mode 100644 index 0000000..77c45d7 --- /dev/null +++ b/packages/pieces/community/odoo/package.json @@ -0,0 +1,11 @@ +{ + "name": "@activepieces/piece-odoo", + "version": "0.0.6", + "dependencies": { + "url": "^0.11.3", + "xmlrpc": "^1.3.2" + }, + "devDependencies": { + "@types/xmlrpc": "^1.3.10" + } +} \ No newline at end of file diff --git a/packages/pieces/community/odoo/project.json b/packages/pieces/community/odoo/project.json new file mode 100644 index 0000000..ae5f142 --- /dev/null +++ b/packages/pieces/community/odoo/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-odoo", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/odoo/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/odoo", + "tsConfig": "packages/pieces/community/odoo/tsconfig.lib.json", + "packageJson": "packages/pieces/community/odoo/package.json", + "main": "packages/pieces/community/odoo/src/index.ts", + "assets": [ + "packages/pieces/community/odoo/*.md", + { + "input": "packages/pieces/community/odoo/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-odoo {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/odoo/src/commom/index.ts b/packages/pieces/community/odoo/src/commom/index.ts new file mode 100644 index 0000000..e614faf --- /dev/null +++ b/packages/pieces/community/odoo/src/commom/index.ts @@ -0,0 +1,332 @@ +import { Client, createClient, createSecureClient } from "xmlrpc"; + +import { + AbstractExecuteParams, + ExecuteKwParams, + GetAllCompaniesParams, + GetAllContactsParams, + GetCompanyParams, + GetContactParams, + GetRecordsParams, + CreateRecordParams, + UpdateRecordParams, + OdooConfig, + RenderReportParams, + SaveCompanyParams, + SaveContactParams, +} from "./type"; + +class Odoo { + config: OdooConfig; + host: string; + port: number; + db: string; + username: string; + password: string; + secure: boolean; + uid: number; + + constructor(config: OdooConfig) { + this.config = config; + + const { hostname, port, protocol } = new URL(config.url); + + this.host = hostname; + this.port = config.port || Number(port); + this.db = config.db; + this.username = config.username; + this.password = config.password; + this.secure = true; + + if (protocol !== "https:") { + this.secure = false; + } + this.uid = 0; + } + + private getClient(path: string): Client { + const createClientFn = this.secure ? createSecureClient : createClient; + + return createClientFn({ + host: this.host, + port: this.port, + path, + }); + } + + private methodCall(client: Client, method: string, params: any[] = []) { + return new Promise((resolve, reject) => { + client.methodCall(method, params, (err, value) => { + if (err) { + console.log(err); + return reject(err); + } + + if (value.length == 1) + return resolve(value[0]) + else if (value.length > 0) + return resolve(value); + else if (['read', 'search', 'search_read'].includes(params[4])) + return reject("Not found") + else + return resolve(value); + }); + }); + } + + connect(): Promise { + const client = this.getClient("/xmlrpc/2/common"); + + return new Promise((resolve, reject) => { + client.methodCall( + "authenticate", + [this.db, this.username, this.password, {}], + (error, value) => { + if (error) { + return reject(error); + } + + if (!value) { + return reject(new Error("No UID returned from authentication.")); + } + + this.uid = value; + + return resolve(this.uid); + } + ); + }); + } + + async execute({ client, endpoint, params }: AbstractExecuteParams) { + try { + const value = await this.methodCall(client, endpoint, [ + this.db, + this.uid, + this.password, + ...params, + ]); + + return Promise.resolve(value as T); + } catch (error) { + return Promise.reject(error); + } + } + + async execute_kw({ + model, + method, + params, + }: ExecuteKwParams): Promise { + const client = this.getClient("/xmlrpc/2/object"); + + return this.execute({ + client, + endpoint: "execute_kw", + params: [model, method, ...params], + }); + } + + async exec_workflow({ + model, + method, + params, + }: ExecuteKwParams): Promise { + const client = this.getClient("/xmlrpc/2/object"); + + return this.execute({ + client, + endpoint: "exec_workflow", + params: [model, method, ...params], + }); + } + + async render_report({ + report, + params, + }: RenderReportParams): Promise { + const client = this.getClient("/xmlrpc/2/report"); + + return this.execute({ + client, + endpoint: "render_report", + params: [report, ...params], + }); + } + + async getAllContacts({ + fields = ['name', 'phone', 'email', 'company_name', 'function'], + isCompany = false + }: GetAllContactsParams): Promise { + return this.execute_kw({ + model: "res.partner", + method: "search_read", + params: [ + [[["is_company", "=", isCompany]], + fields] + ] + }) + } + + async getAllCompanies({ + fields = ['name', 'phone', 'email', 'country_id'], + }: GetAllCompaniesParams): Promise { + return this.getAllContacts({ + fields: fields, + isCompany: true + }) + } + + async getContact({ + name, + fields = ['name', 'phone', 'email', 'company_name', 'function'], + isCompany = false + }: GetContactParams): Promise { + return this.execute_kw({ + model: "res.partner", + method: "search_read", + params: [ + [[["is_company", "=", isCompany], ["name", "like", name]], + fields] + ] + }) + } + + async getCompany({ + name, + fields = ['name', 'phone', 'email', 'country_id'], + }: GetCompanyParams): Promise { + return this.getContact({ + name: name, + fields: fields, + isCompany: true + }) + } + + async getRecords({model, domain, fields, offset, limit}: GetRecordsParams): Promise { + return this.execute_kw({ + model: model, + method: "search_read", + params: [ + [domain, fields, offset, limit] + ] + }) + } + + async createRecord({ + model, + fields, + }: CreateRecordParams): Promise { + return this.execute_kw({ + model, + method: 'create', + params: [[fields]], + }); + } + + async updateRecord({ + model, + recordId, + fields, + }: UpdateRecordParams): Promise { + return this.execute_kw({ + model, + method: 'write', + params: [[[recordId], fields]], + }); + } + + async saveContact({ + name, + phone, + email, + company, + title, + company_id, + address, + }: SaveContactParams): Promise { + + const contact = { + name: name, + phone: phone, + email: email, + company_name: company, + company_id: company_id || null, + function: title, + }; + + try { + const record = await this.execute_kw({ + model: "res.partner", + method: "search", + params: [ + [[["is_company", "=", false], ["name", "=", name]]] + ] + }) + + this.execute_kw({ + model: "res.partner", + method: "write", + params: [[record, contact]], + }); + + return Promise.resolve(record as T) + } catch (err) { + if (err === "Not found") { + return this.execute_kw({ + model: "res.partner", + method: "create", + params: [[[contact]]], + }); + } else + return Promise.reject("Error") + + } + } + + async saveCompany({ + name, + phone, + email, + address, + }: SaveCompanyParams): Promise { + + const contact = { + name: name, + phone: phone, + email: email, + is_company: true + }; + + try { + const record = await this.execute_kw({ + model: "res.partner", + method: "search", + params: [ + [[["is_company", "=", true], ["name", "=", name]]] + ] + }) + + this.execute_kw({ + model: "res.partner", + method: "write", + params: [[record, contact]], + }); + + return Promise.resolve(record as T) + } catch (err) { + if (err === "Not found") { + return this.execute_kw({ + model: "res.partner", + method: "create", + params: [[[contact]]], + }); + } else + return Promise.reject("Error") + + } + } + +} + +export default Odoo; \ No newline at end of file diff --git a/packages/pieces/community/odoo/src/commom/type.ts b/packages/pieces/community/odoo/src/commom/type.ts new file mode 100644 index 0000000..f6a83b6 --- /dev/null +++ b/packages/pieces/community/odoo/src/commom/type.ts @@ -0,0 +1,96 @@ +import { Client } from "xmlrpc"; + +export type OdooConfig = { + url: string; + port?: number; + db: string; + username: string; + password: string; + secure?: boolean; +}; + +export type ExecuteKwParams = { + model: string; + method: string; + params: any[]; +}; + +export type AbstractExecuteParams = { + endpoint: string; + client: Client; + params: any[]; +}; + +export type RenderReportParams = { + report: string; + params: any[]; +}; + +export type GetAllContactsParams = { + fields?: any[]; + isCompany?: boolean; +}; + +export type GetAllCompaniesParams = { + fields?: any[]; +}; + + +export type GetContactParams = { + name: string; + fields?: any[]; + isCompany?: boolean; +}; + +export type GetCompanyParams = { + name: string; + fields?: any[]; +}; + +export type GetRecordsParams = { + model: string; + domain?: any[]; + fields?: any[]; + offset?: number; + limit?: number; +}; + +export type CreateRecordParams = { + model: string; + fields: Record; +}; + +export type UpdateRecordParams = { + model: string; + recordId: number; + fields: Record; +}; + +export type SaveContactParams = { + name: string; + phone: string; + email: string; + company: string; + company_id?: number; + title: string; + address?: { + street?: string; + city?: string; + state?: string; + zip?: string; + country?: string; + }, +}; + +export type SaveCompanyParams = { + name: string; + phone: string; + email: string; + address?: { + street?: string; + city?: string; + state?: string; + zip?: string; + country?: string; + }, +}; \ No newline at end of file diff --git a/packages/pieces/community/odoo/src/index.ts b/packages/pieces/community/odoo/src/index.ts new file mode 100644 index 0000000..44ff097 --- /dev/null +++ b/packages/pieces/community/odoo/src/index.ts @@ -0,0 +1,70 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import Odoo from './commom/index'; +import actions from './lib/actions'; + +export const odooAuth = PieceAuth.CustomAuth({ + props: { + base_url: Property.ShortText({ + displayName: 'Odoo URL', + description: 'Enter the base URL', + required: true, + }), + database: Property.ShortText({ + displayName: 'Odoo Database', + description: 'Enter the database name', + required: true, + }), + username: Property.ShortText({ + displayName: 'Odoo Username', + description: 'Enter the username', + required: true, + }), + api_key: PieceAuth.SecretText({ + displayName: 'Odoo API Key', + description: 'Enter the API Key', + required: true, + }), + }, + // Optional Validation + validate: async ({ auth }) => { + const { base_url, database, username, api_key } = auth; + + const odoo = new Odoo({ + url: base_url, + port: 443, + db: database, + username: username, + password: api_key, + }); + + try { + await odoo.connect(); + + return { + valid: true, + }; + } catch (err) { + return { + valid: false, + error: + 'Connection failed. Please check your credentials and try again.', + }; + } + }, + required: true, +}); + +export const odoo = createPiece({ + displayName: 'Odoo', + description: 'Open source all-in-one management software', + auth: odooAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/odoo.png', + authors: ["mariomeyer","kishanprmr","abuaboud"], + actions, + triggers: [], +}); diff --git a/packages/pieces/community/odoo/src/lib/actions/create-company.ts b/packages/pieces/community/odoo/src/lib/actions/create-company.ts new file mode 100644 index 0000000..c0be245 --- /dev/null +++ b/packages/pieces/community/odoo/src/lib/actions/create-company.ts @@ -0,0 +1,51 @@ +import { createAction, Property } from "@activepieces/pieces-framework"; +import Odoo from "../../commom/index"; +import { odooAuth } from "../.."; + +export default createAction({ + name: 'create_company', // Must be a unique across the piece, this shouldn't be changed. + auth: odooAuth, + displayName: 'Create company', + description: 'Create/Update company on Odoo', + props: { + // Properties to ask from the user, in this ask we will take number of + name: Property.ShortText({ + displayName: 'Company Name', + description: undefined, + required: true, + }), + phone: Property.ShortText({ + displayName: 'Company Phone', + description: undefined, + required: true, + }), + email: Property.ShortText({ + displayName: 'Company E-mail', + description: undefined, + required: true, + }) + }, + async run(context) { + const odoo = new Odoo({ + url: context.auth.base_url, + port: 443, + db: context.auth.database, + username: context.auth.username, + password: context.auth.api_key, + }) + + try { + const uid = await odoo.connect(); + console.log("Connected to Odoo server. Uid: ", uid); + const c = await odoo.saveCompany({ + name: context.propsValue['name'], + phone: context.propsValue['phone'], + email: context.propsValue['email'], + }); + return `Company ${c} created!` + } catch (err) { + return err; + } + }, +}); + diff --git a/packages/pieces/community/odoo/src/lib/actions/create-contact.ts b/packages/pieces/community/odoo/src/lib/actions/create-contact.ts new file mode 100644 index 0000000..3fc951a --- /dev/null +++ b/packages/pieces/community/odoo/src/lib/actions/create-contact.ts @@ -0,0 +1,62 @@ +import { createAction, Property } from "@activepieces/pieces-framework"; +import Odoo from "../../commom/index"; +import { odooAuth } from "../.."; + +export default createAction({ + name: 'create_contact', // Must be a unique across the piece, this shouldn't be changed. + auth: odooAuth, + displayName: 'Create contact', + description: 'Create/Update contact on Odoo', + props: { + // Properties to ask from the user, in this ask we will take number of + name: Property.ShortText({ + displayName: 'Contact Name', + description: undefined, + required: true, + }), + phone: Property.ShortText({ + displayName: 'Contact Phone', + description: undefined, + required: true, + }), + email: Property.ShortText({ + displayName: 'Contact E-mail', + description: undefined, + required: true, + }), + company: Property.ShortText({ + displayName: 'Company Name', + description: undefined, + required: true, + }), + title: Property.ShortText({ + displayName: 'Job Title', + description: undefined, + required: true, + }), + }, + async run(context) { + const odoo = new Odoo({ + url: context.auth.base_url, + port: 443, + db: context.auth.database, + username: context.auth.username, + password: context.auth.api_key, + }) + + try { + await odoo.connect(); + const c = await odoo.saveContact({ + name: context.propsValue['name'], + phone: context.propsValue['name'], + email: context.propsValue['name'], + company: context.propsValue['company'], + title: context.propsValue['title'] + }); + return `Contact ${c} created!` + } catch (err) { + return err + } + } +}); + diff --git a/packages/pieces/community/odoo/src/lib/actions/create-record.ts b/packages/pieces/community/odoo/src/lib/actions/create-record.ts new file mode 100644 index 0000000..7dba3ec --- /dev/null +++ b/packages/pieces/community/odoo/src/lib/actions/create-record.ts @@ -0,0 +1,47 @@ +import { createAction, Property } from "@activepieces/pieces-framework"; +import Odoo from "../../commom/index"; +import { odooAuth } from "../.."; + +export default createAction({ + name: 'create_record', + auth: odooAuth, + displayName: 'Custom Create Record', + description: 'Create a new record in the specified model', + props: { + model: Property.ShortText({ + displayName: 'Model', + description: "Model name. e.g.: res.partner", + required: true, + defaultValue: 'res.partner', + }), + fields: Property.Json({ + displayName: 'Fields and Values', + description: 'JSON object of field names and their corresponding values', + required: true, + defaultValue: { + "name": "New Partner", + "email": "newpartner@example.com" + }, + }) + }, + async run(context) { + const odoo = new Odoo({ + url: context.auth.base_url, + port: 443, + db: context.auth.database, + username: context.auth.username, + password: context.auth.api_key, + }); + + try { + await odoo.connect(); + const fields = context.propsValue.fields; + const model = context.propsValue.model; + const recordId = await odoo.createRecord({ model, fields }); + return { id: recordId }; + } catch (err) { + return err; + } + } +}); + diff --git a/packages/pieces/community/odoo/src/lib/actions/get-contacts.ts b/packages/pieces/community/odoo/src/lib/actions/get-contacts.ts new file mode 100644 index 0000000..48de60e --- /dev/null +++ b/packages/pieces/community/odoo/src/lib/actions/get-contacts.ts @@ -0,0 +1,56 @@ +import { createAction, Property } from "@activepieces/pieces-framework"; +import Odoo from "../../commom/index"; +import { odooAuth } from "../.."; + +export default createAction({ + name: 'get_contacts', // Must be a unique across the piece, this shouldn't be changed. + auth: odooAuth, + displayName: 'Get contacts', + description: 'Get contacts on Odoo', + props: { + // Properties to ask from the user, in this ask we will take number of + type: Property.StaticDropdown({ + displayName: 'Type', + description: "Select which contact type to get", + required: true, + options: { + options: [ + { + label: "Contact", + value: false, + }, + { + label: "Company", + value: true, + }, + ], + } + }), + name: Property.ShortText({ + displayName: 'Contact Name', + description: "Would you like to search any specific name?", + required: false, + }), + }, + async run(context) { + const odoo = new Odoo({ + url: context.auth.base_url, + port: 443, + db: context.auth.database, + username: context.auth.username, + password: context.auth.api_key, + }) + + try { + await odoo.connect(); + + if (context.propsValue['name']) + return await odoo.getContact({ name: context.propsValue['name'], isCompany: context.propsValue['type'] }) + else + return await odoo.getAllContacts({ isCompany: context.propsValue['type'] }) + } catch (err) { + return err + } + } +}); + diff --git a/packages/pieces/community/odoo/src/lib/actions/get-records.ts b/packages/pieces/community/odoo/src/lib/actions/get-records.ts new file mode 100644 index 0000000..c33cbaf --- /dev/null +++ b/packages/pieces/community/odoo/src/lib/actions/get-records.ts @@ -0,0 +1,65 @@ +import { createAction, Property } from "@activepieces/pieces-framework"; +import Odoo from "../../commom/index"; +import { odooAuth } from "../.."; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export default createAction({ + name: 'get_records', + auth: odooAuth, + displayName: 'Custom Search and read records', + description: 'Records can be listed and filtered', + props: { + // Properties to ask from the user, in this ask we will take number of + model: Property.ShortText({ + displayName: 'Model', + description: "Model name. e.g.: res.partner", + required: true, + defaultValue: 'res.partner', + }), + domain: Property.Json({ + displayName: 'Search domains', + required: false, + description: 'A domain is a list of criteria, each criterion being a triple of (field_name, operator, value). See https://www.odoo.com/documentation/17.0/developer/reference/backend/orm.html#reference-orm-domains for details.', + defaultValue: [ + ["is_company", "=", true], + ], + }), + fields: Property.Array({ + displayName: 'Fields', + description: 'Returns the requested fields of the records. When undefined, returns all fields.', + required: false, + }), + offset: Property.Number({ + displayName: 'Offset', + required: false, + }), + limit: Property.Number({ + displayName: 'Limit', + required: false, + }) + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + limit: z.number().min(1).optional(), + }); + + const odoo = new Odoo({ + url: context.auth.base_url, + port: 443, + db: context.auth.database, + username: context.auth.username, + password: context.auth.api_key, + }) + + try { + await odoo.connect(); + const domainArray = context.propsValue.domain + ? (context.propsValue.domain as unknown as any[]) + : []; + return await odoo.getRecords({model: context.propsValue.model, domain: domainArray, fields: context.propsValue.fields, offset: context.propsValue.offset, limit: context.propsValue.limit}) + } catch (err) { + return err + } + } +}); diff --git a/packages/pieces/community/odoo/src/lib/actions/index.ts b/packages/pieces/community/odoo/src/lib/actions/index.ts new file mode 100644 index 0000000..f3a664b --- /dev/null +++ b/packages/pieces/community/odoo/src/lib/actions/index.ts @@ -0,0 +1,15 @@ +import createContact from './create-contact'; +import createCompany from './create-company'; +import getContacts from './get-contacts'; +import getRecords from './get-records'; +import createRecord from './create-record'; +import updateRecord from './update-record'; + +export default [ + getContacts, + createContact, + createCompany, + getRecords, + createRecord, + updateRecord, +]; diff --git a/packages/pieces/community/odoo/src/lib/actions/update-record.ts b/packages/pieces/community/odoo/src/lib/actions/update-record.ts new file mode 100644 index 0000000..1345a86 --- /dev/null +++ b/packages/pieces/community/odoo/src/lib/actions/update-record.ts @@ -0,0 +1,51 @@ +import { createAction, Property } from "@activepieces/pieces-framework"; +import Odoo from "../../commom/index"; +import { odooAuth } from "../.."; + +export default createAction({ + name: 'update_record', + auth: odooAuth, + displayName: 'Custom Update Record', + description: 'Update an existing record in the specified model', + props: { + model: Property.ShortText({ + displayName: 'Model', + description: "Model name. e.g.: res.partner", + required: true, + defaultValue: 'res.partner', + }), + recordId: Property.Number({ + displayName: 'Record ID', + description: 'ID of the record to update', + required: true, + }), + fields: Property.Json({ + displayName: 'Fields and Values', + description: 'JSON object of field names and their corresponding values', + required: true, + defaultValue: { + "email": "updatedemail@example.com" + }, + }) + }, + async run(context) { + const odoo = new Odoo({ + url: context.auth.base_url, + port: 443, + db: context.auth.database, + username: context.auth.username, + password: context.auth.api_key, + }); + + try { + await odoo.connect(); + const fields = context.propsValue.fields; + const recordId = context.propsValue.recordId; + const model = context.propsValue.model; + const result = await odoo.updateRecord({ model, recordId, fields }); + return { success: result }; + } catch (err) { + return err; + } + } +}); diff --git a/packages/pieces/community/odoo/tsconfig.json b/packages/pieces/community/odoo/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/odoo/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/odoo/tsconfig.lib.json b/packages/pieces/community/odoo/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/odoo/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/onfleet/.eslintrc.json b/packages/pieces/community/onfleet/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/onfleet/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/onfleet/README.md b/packages/pieces/community/onfleet/README.md new file mode 100644 index 0000000..561f010 --- /dev/null +++ b/packages/pieces/community/onfleet/README.md @@ -0,0 +1,7 @@ +# pieces-onfleet + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-onfleet` to build the library. diff --git a/packages/pieces/community/onfleet/package.json b/packages/pieces/community/onfleet/package.json new file mode 100644 index 0000000..82f5deb --- /dev/null +++ b/packages/pieces/community/onfleet/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-onfleet", + "version": "0.0.5" +} \ No newline at end of file diff --git a/packages/pieces/community/onfleet/project.json b/packages/pieces/community/onfleet/project.json new file mode 100644 index 0000000..fa68666 --- /dev/null +++ b/packages/pieces/community/onfleet/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-onfleet", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/onfleet/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/onfleet", + "tsConfig": "packages/pieces/community/onfleet/tsconfig.lib.json", + "packageJson": "packages/pieces/community/onfleet/package.json", + "main": "packages/pieces/community/onfleet/src/index.ts", + "assets": [ + "packages/pieces/community/onfleet/*.md", + { + "input": "packages/pieces/community/onfleet/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-onfleet {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/onfleet/src/index.ts b/packages/pieces/community/onfleet/src/index.ts new file mode 100644 index 0000000..8b52cc3 --- /dev/null +++ b/packages/pieces/community/onfleet/src/index.ts @@ -0,0 +1,140 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; + +import { cloneTask } from './lib/actions/clone-task'; +import { completeTask } from './lib/actions/complete-task'; +import { createAdmin } from './lib/actions/create-admin'; +import { createDestination } from './lib/actions/create-destination'; +import { createHub } from './lib/actions/create-hub'; +import { createRecipient } from './lib/actions/create-recipient'; +import { createTask } from './lib/actions/create-task'; +import { createTeam } from './lib/actions/create-team'; +import { createWorker } from './lib/actions/create-worker'; +import { deleteAdmin } from './lib/actions/delete-admin'; +import { deleteTask } from './lib/actions/delete-task'; +import { deleteTeam } from './lib/actions/delete-team'; +import { deleteWorker } from './lib/actions/delete-worker'; +import { getAdmins } from './lib/actions/get-admins'; +import { getContainer } from './lib/actions/get-container'; +import { getDelegateeDetails } from './lib/actions/get-delegatee-details'; +import { getDestination } from './lib/actions/get-destination'; +import { getHubs } from './lib/actions/get-hubs'; +import { getOrganization } from './lib/actions/get-organization'; +import { getRecipient } from './lib/actions/get-recipient'; +import { getTask } from './lib/actions/get-task'; +import { getTasks } from './lib/actions/get-tasks'; +import { getTeam } from './lib/actions/get-team'; +import { getTeams } from './lib/actions/get-teams'; +import { getWorker } from './lib/actions/get-worker'; +import { getWorkerSchedule } from './lib/actions/get-worker-schedule'; +import { updateAdmin } from './lib/actions/update-admin'; +import { updateHub } from './lib/actions/update-hub'; +import { updateRecipient } from './lib/actions/update-recipient'; +import { updateTask } from './lib/actions/update-task'; +import { updateTeam } from './lib/actions/update-team'; +import { updateWorker } from './lib/actions/update-worker'; + +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceCategory } from '@activepieces/shared'; +import { autoDispatchCompleted } from './lib/triggers/auto-dispatch-completed'; +import { smsRecipientOptOut } from './lib/triggers/sms-recipient-opt-out'; +import { smsRecipientResponseMissed } from './lib/triggers/sms-recipient-response-missed'; +import { taskArrival } from './lib/triggers/task-arrival'; +import { taskAssigned } from './lib/triggers/task-assigned'; +import { taskCloned } from './lib/triggers/task-cloned'; +import { taskCompleted } from './lib/triggers/task-completed'; +import { taskCreated } from './lib/triggers/task-created'; +import { taskDelayed } from './lib/triggers/task-delayed'; +import { taskDeleted } from './lib/triggers/task-deleted'; +import { taskEta } from './lib/triggers/task-eta'; +import { taskFailed } from './lib/triggers/task-failed'; +import { taskStarted } from './lib/triggers/task-started'; +import { taskUnassigned } from './lib/triggers/task-unassigned'; +import { taskUpdated } from './lib/triggers/task-updated'; +import { workerCreated } from './lib/triggers/worker-created'; +import { workerDeleted } from './lib/triggers/worker-deleted'; +import { workerDutyChange } from './lib/triggers/worker-duty-change'; + +const authDescription = ` +To get an API key, follow the steps below: +1. Go to settings -> API & Webhooks. +2. Click the plus sign under API Keys. +3. Enter API key name and click create key. +4. Copy the generated key to the input below. +`; +export const onfleetAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: authDescription, + required: true, +}); + +export const onfleet = createPiece({ + displayName: 'Onfleet', + description: 'Last mile delivery software', + + auth: onfleetAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/onfleet.png', + categories: [], + authors: ["kishanprmr","MoShizzle","abuaboud"], + actions: [ + createRecipient, + updateRecipient, + getRecipient, + createTask, + deleteTask, + completeTask, + cloneTask, + updateTask, + getTask, + getTasks, + createDestination, + getDestination, + getHubs, + createHub, + updateHub, + getOrganization, + getDelegateeDetails, + createAdmin, + updateAdmin, + getAdmins, + deleteAdmin, + createWorker, + deleteWorker, + getWorker, + getWorkerSchedule, + updateWorker, + createTeam, + deleteTeam, + getTeam, + getTeams, + updateTeam, + getContainer, + createCustomApiCallAction({ + baseUrl: () => 'https://onfleet.com/api/v2', + auth: onfleetAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers: [ + taskArrival, + taskAssigned, + taskCloned, + taskCompleted, + taskCreated, + taskDelayed, + taskDeleted, + taskEta, + taskFailed, + taskStarted, + taskUnassigned, + taskUpdated, + workerCreated, + workerDeleted, + workerDutyChange, + autoDispatchCompleted, + smsRecipientOptOut, + smsRecipientResponseMissed, + ], +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/clone-task.ts b/packages/pieces/community/onfleet/src/lib/actions/clone-task.ts new file mode 100644 index 0000000..492ea55 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/clone-task.ts @@ -0,0 +1,23 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const cloneTask = createAction({ + auth: onfleetAuth, + name: 'clone_task', + displayName: 'Clone Task', + description: 'Clones a task', + props: { + task: Property.ShortText({ + displayName: 'Task ID', + description: 'ID of the task you want to clone', + required: true, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.tasks.clone(context.propsValue.task); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/complete-task.ts b/packages/pieces/community/onfleet/src/lib/actions/complete-task.ts new file mode 100644 index 0000000..35821cd --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/complete-task.ts @@ -0,0 +1,38 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const completeTask = createAction({ + auth: onfleetAuth, + name: 'complete_task', + displayName: 'Force Complete Task', + description: 'Force completes a task', + props: { + task: Property.ShortText({ + displayName: 'Task ID', + description: 'The ID of the task you want to complete', + required: true, + }), + success: Property.Checkbox({ + displayName: 'Complete as Success', + description: 'Whether to complete the task as a success or not', + required: true, + defaultValue: true, + }), + notes: Property.ShortText({ + displayName: 'Notes', + required: false, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.tasks.forceComplete(context.propsValue.task, { + completionDetails: { + success: context.propsValue.success, + notes: context.propsValue.notes, + }, + }); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/create-admin.ts b/packages/pieces/community/onfleet/src/lib/actions/create-admin.ts new file mode 100644 index 0000000..9e9e9ed --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/create-admin.ts @@ -0,0 +1,42 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const createAdmin = createAction({ + auth: onfleetAuth, + name: 'create_admin', + displayName: 'Create Administrator', + description: 'Create a new administrator', + props: { + name: Property.ShortText({ + displayName: 'Name', + description: 'Full name', + required: true, + }), + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + phone: Property.ShortText({ + displayName: 'Phone Number', + required: false, + }), + isReadOnly: Property.Checkbox({ + displayName: 'Read Only', + description: 'Whether this administrator can perform write operations.', + required: false, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.administrators.create({ + name: context.propsValue.name, + email: context.propsValue.email, + phone: context.propsValue.phone, + isReadOnly: context.propsValue.isReadOnly, + }); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/create-destination.ts b/packages/pieces/community/onfleet/src/lib/actions/create-destination.ts new file mode 100644 index 0000000..d07b1a3 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/create-destination.ts @@ -0,0 +1,54 @@ +import { + DynamicPropsValue, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const createDestination = createAction({ + auth: onfleetAuth, + name: 'create_destination', + displayName: 'Create Destination', + description: 'Create a new destination', + props: { + destination: common.destination, + unparsedDestination: common.unparsedDestination, + notes: Property.ShortText({ + displayName: 'Notes', + required: false, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + let address; + if (context.propsValue.unparsedDestination) { + address = { + number: '', + street: '', + city: '', + country: '', + unparsed: context.propsValue.destination['unparsedAddress'], + }; + } else { + address = { + number: context.propsValue.destination['number'], + street: context.propsValue.destination['street'], + apartment: context.propsValue.destination['apartment'], + city: context.propsValue.destination['city'], + country: context.propsValue.destination['country'], + state: context.propsValue.destination['state'], + postalCode: context.propsValue.destination['postalCode'], + name: context.propsValue.destination['name'], + }; + } + + return await onfleetApi.destinations.create({ + address: address, + notes: context.propsValue.notes, + }); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/create-hub.ts b/packages/pieces/community/onfleet/src/lib/actions/create-hub.ts new file mode 100644 index 0000000..4e9a98d --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/create-hub.ts @@ -0,0 +1,58 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const createHub = createAction({ + auth: onfleetAuth, + name: 'create_hub', + displayName: 'Create Hub', + description: 'Create a new hub', + props: { + name: Property.ShortText({ + displayName: 'Name', + description: 'Name of the hub', + required: true, + }), + destination: common.destination, + unparsedDestination: common.unparsedDestination, + teams: common.teams, + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + let address; + if (context.propsValue.unparsedDestination) { + address = { + number: '', + street: '', + city: '', + country: '', + unparsed: context.propsValue.destination['unparsedAddress'], + }; + } else { + address = { + number: context.propsValue.destination['number'], + street: context.propsValue.destination['street'], + apartment: context.propsValue.destination['apartment'], + city: context.propsValue.destination['city'], + country: context.propsValue.destination['country'], + state: context.propsValue.destination['state'], + postalCode: context.propsValue.destination['postalCode'], + name: context.propsValue.destination['name'], + }; + } + + const options: any = { + address: address, + name: context.propsValue.name, + }; + + if (context.propsValue.teams) { + options.teams = context.propsValue.teams; + } + + return await onfleetApi.hubs.create(options); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/create-recipient.ts b/packages/pieces/community/onfleet/src/lib/actions/create-recipient.ts new file mode 100644 index 0000000..176abdc --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/create-recipient.ts @@ -0,0 +1,59 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const createRecipient = createAction({ + auth: onfleetAuth, + name: 'create_recipient', + displayName: 'Create Recipient', + description: 'Creates a recipient', + props: { + name: Property.ShortText({ + displayName: 'Name', + description: "The recipient's full name", + required: true, + }), + phone: Property.ShortText({ + displayName: 'Phone', + description: + "A unique, valid phone number as per the organization's country if there's no leading + sign. If a phone number has a leading + sign, it will disregard the organization's country setting.", + required: true, + }), + notes: Property.ShortText({ + displayName: 'Notes', + required: false, + }), + skipSMSNotifications: Property.Checkbox({ + displayName: 'Skip SMS Notifications', + required: false, + defaultValue: false, + }), + skipPhoneNumberValidation: Property.Checkbox({ + displayName: 'Skip Phone Number Validation', + required: false, + defaultValue: false, + }), + useLongCodeForText: Property.Checkbox({ + displayName: 'Use Long Code for Text - Canadian Organizations Only', + description: + 'Checking this option will default the Onfleet system to use a toll-free long code number for SMS communication.', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + const recipient: any = { + name: context.propsValue['name'], + phone: context.propsValue['phone'], + notes: context.propsValue['notes'] ?? undefined, + skipSMSNotifications: context.propsValue['skipSMSNotifications'], + skipPhoneNumberValidation: + context.propsValue['skipPhoneNumberValidation'], + useLongCodeForText: context.propsValue['useLongCodeForText'], + }; + + return await onfleetApi.recipients.create(recipient); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/create-task.ts b/packages/pieces/community/onfleet/src/lib/actions/create-task.ts new file mode 100644 index 0000000..d91ade6 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/create-task.ts @@ -0,0 +1,184 @@ +import { + DynamicPropsValue, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +import Onfleet from '@onfleet/node-onfleet'; +import dayjs from 'dayjs'; + +export const createTask = createAction({ + auth: onfleetAuth, + name: 'create_task', + displayName: 'Create Task', + description: 'Creates a task', + props: { + merchant: Property.ShortText({ + displayName: 'Merchant ID', + description: 'ID of the organization that will be displayed on the task', + required: false, + }), + executor: Property.ShortText({ + displayName: 'Executor ID', + description: 'ID of the organization that will be executing the task', + required: false, + }), + destination: common.destination, + unparsedDestination: common.unparsedDestination, + recipient: Property.DynamicProperties({ + displayName: 'Destination', + description: 'The task destination', + required: true, + refreshers: ['useRecipientID'], + props: async ({ useRecipientID }) => { + let fields: DynamicPropsValue = {}; + if (useRecipientID) { + fields = { + id: Property.ShortText({ + displayName: 'Recipient ID', + required: true, + }), + }; + } else { + fields = { + name: Property.ShortText({ + displayName: 'Name', + description: "The recipient's full name", + required: true, + }), + phone: Property.ShortText({ + displayName: 'Phone', + description: + "A unique, valid phone number as per the organization's country if there's no leading + sign. If a phone number has a leading + sign, it will disregard the organization's country setting.", + required: true, + }), + notes: Property.ShortText({ + displayName: 'Notes', + required: false, + }), + skipSMSNotifications: Property.Checkbox({ + displayName: 'Skip SMS Notifications', + required: false, + defaultValue: false, + }), + }; + } + + return fields; + }, + }), + useRecipientID: Property.Checkbox({ + displayName: 'Use Recipient ID', + description: 'Check this box if you want to use an ID for the recipient', + required: true, + defaultValue: false, + }), + completeAfter: Property.DateTime({ + displayName: 'Complete After', + description: 'The earliest time the task should be completed', + required: false, + }), + completeBefore: Property.DateTime({ + displayName: 'Complete Before', + description: 'The latest time the task should be completed', + required: false, + }), + pickupTask: Property.Checkbox({ + displayName: 'Pickup', + description: 'Whether the task is pickup', + required: false, + }), + quantity: Property.Number({ + displayName: 'Quantity', + description: 'The number of units to be dropped off', + required: false, + }), + recipientName: Property.ShortText({ + displayName: 'Recipient Name Override', + description: 'Override the recipient name for this task only', + required: false, + }), + recipientNotes: Property.ShortText({ + displayName: 'Recipient Notes Override', + description: 'Override the recipient notes for this task only', + required: false, + }), + recipientSkipSMSNotifications: Property.Checkbox({ + displayName: 'Recipient Skip SMS Override', + description: 'Override the recipient skip SMS option for this task only', + required: false, + }), + serviceTime: Property.Number({ + displayName: 'Service Time', + description: + "The number of minutes to be spent by the worker on arrival at this task's destination", + required: false, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + let address; + if (context.propsValue.unparsedDestination) { + address = { + number: '', + street: '', + city: '', + country: '', + unparsed: context.propsValue.destination['unparsedAddress'], + }; + } else { + address = { + number: context.propsValue.destination['number'], + street: context.propsValue.destination['street'], + apartment: context.propsValue.destination['apartment'], + city: context.propsValue.destination['city'], + country: context.propsValue.destination['country'], + state: context.propsValue.destination['state'], + postalCode: context.propsValue.destination['postalCode'], + name: context.propsValue.destination['name'], + }; + } + + let recipients; + if (context.propsValue.useRecipientID) { + recipients = [context.propsValue.recipient['id']]; + } else { + recipients = [ + { + name: context.propsValue.recipient['name'], + phone: context.propsValue.recipient['phone'], + notes: context.propsValue.recipient['notes'], + skipSMSNotifications: + context.propsValue.recipient['skipSMSNotifications'], + }, + ]; + } + + const completeAfter = context.propsValue.completeAfter + ? dayjs(context.propsValue.completeAfter).valueOf() + : undefined; + const completeBefore = context.propsValue.completeBefore + ? dayjs(context.propsValue.completeBefore).valueOf() + : undefined; + + return await onfleetApi.tasks.create({ + destination: { + address: address, + }, + recipients: recipients, + merchant: context.propsValue.merchant, + executor: context.propsValue.executor, + pickupTask: context.propsValue.pickupTask, + quantity: context.propsValue.quantity, + recipientName: context.propsValue.recipientName, + recipientNotes: context.propsValue.recipientNotes, + recipientSkipSMSNotifications: + context.propsValue.recipientSkipSMSNotifications, + serviceTime: context.propsValue.serviceTime, + completeAfter: completeAfter, + completeBefore: completeBefore, + }); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/create-team.ts b/packages/pieces/community/onfleet/src/lib/actions/create-team.ts new file mode 100644 index 0000000..720859f --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/create-team.ts @@ -0,0 +1,39 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const createTeam = createAction({ + auth: onfleetAuth, + name: 'create_team', + displayName: 'Create Team', + description: 'Create a new team', + props: { + name: Property.ShortText({ + displayName: 'Name', + description: 'Name of the team', + required: true, + }), + workers: common.workers, + managers: common.managers, + hub: common.hubOptional, + enableSelfAssignment: Property.Checkbox({ + displayName: 'Enable Self Assignment', + description: + 'Allows Drivers to Self Assign Tasks that are in the Team unassigned container.', + required: false, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.teams.create({ + name: context.propsValue.name, + workers: context.propsValue.workers, + managers: context.propsValue.managers, + hub: context.propsValue.hub, + enableSelfAssignment: context.propsValue.enableSelfAssignment, + }); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/create-worker.ts b/packages/pieces/community/onfleet/src/lib/actions/create-worker.ts new file mode 100644 index 0000000..b5bb39a --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/create-worker.ts @@ -0,0 +1,44 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const createWorker = createAction({ + auth: onfleetAuth, + name: 'create_worker', + displayName: 'Create Worker', + description: 'Create a new worker', + props: { + name: Property.ShortText({ + displayName: 'Name', + description: 'Name of the worker', + required: true, + }), + phone: Property.ShortText({ + displayName: 'Phone', + required: true, + }), + teams: common.teamsRequired, + capacity: Property.Number({ + displayName: 'Capacity', + description: 'The maximum number of units this worker can carry', + required: false, + }), + displayName: Property.ShortText({ + displayName: 'Display Name', + required: false, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.workers.create({ + name: context.propsValue.name, + phone: context.propsValue.phone, + teams: context.propsValue.teams as string[], + capacity: context.propsValue.capacity, + displayName: context.propsValue.displayName, + }); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/delete-admin.ts b/packages/pieces/community/onfleet/src/lib/actions/delete-admin.ts new file mode 100644 index 0000000..6c86473 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/delete-admin.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const deleteAdmin = createAction({ + auth: onfleetAuth, + name: 'delete_admin', + displayName: 'Delete Administrator', + description: 'Delete an existing administrator', + props: { + admin: common.admin, + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.administrators.deleteOne( + context.propsValue.admin as string + ); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/delete-task.ts b/packages/pieces/community/onfleet/src/lib/actions/delete-task.ts new file mode 100644 index 0000000..7e0bc7c --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/delete-task.ts @@ -0,0 +1,23 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const deleteTask = createAction({ + auth: onfleetAuth, + name: 'delete_task', + displayName: 'Delete Task', + description: 'Deletes a task', + props: { + task: Property.ShortText({ + displayName: 'Task ID', + description: 'The ID of the task you want to delete', + required: true, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.tasks.deleteOne(context.propsValue.task); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/delete-team.ts b/packages/pieces/community/onfleet/src/lib/actions/delete-team.ts new file mode 100644 index 0000000..0c95e20 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/delete-team.ts @@ -0,0 +1,20 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const deleteTeam = createAction({ + auth: onfleetAuth, + name: 'delete_team', + displayName: 'Delete Team', + description: 'Delete an existing team', + props: { + team: common.team, + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.teams.deleteOne(context.propsValue.team as string); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/delete-worker.ts b/packages/pieces/community/onfleet/src/lib/actions/delete-worker.ts new file mode 100644 index 0000000..841531b --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/delete-worker.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const deleteWorker = createAction({ + auth: onfleetAuth, + name: 'delete_worker', + displayName: 'Delete Worker', + description: 'Delete an existing worker', + props: { + worker: common.worker, + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.workers.deleteOne( + context.propsValue.worker as string + ); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/get-admins.ts b/packages/pieces/community/onfleet/src/lib/actions/get-admins.ts new file mode 100644 index 0000000..21cd848 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/get-admins.ts @@ -0,0 +1,14 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +export const getAdmins = createAction({ + auth: onfleetAuth, + name: 'get_admins', + displayName: 'Get Administrators', + description: 'Get many administrators', + props: {}, + async run(context) { + return await common.getAdmins(context.auth); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/get-container.ts b/packages/pieces/community/onfleet/src/lib/actions/get-container.ts new file mode 100644 index 0000000..f35a6e7 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/get-container.ts @@ -0,0 +1,51 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const getContainer = createAction({ + auth: onfleetAuth, + name: 'get_container', + displayName: 'Get Container', + description: 'Get a specific container', + props: { + containerType: Property.Dropdown< + 'organizations' | 'workers' | 'teams', + true + >({ + displayName: 'Container Type', + required: true, + refreshers: [], + options: async () => { + return { + options: [ + { + label: 'Organizations', + value: 'organizations', + }, + { + label: 'Teams', + value: 'teams', + }, + { + label: 'Workers', + value: 'workers', + }, + ], + }; + }, + }), + containerId: Property.ShortText({ + displayName: 'Container ID', + required: true, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.containers.get( + context.propsValue.containerId, + context.propsValue.containerType + ); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/get-delegatee-details.ts b/packages/pieces/community/onfleet/src/lib/actions/get-delegatee-details.ts new file mode 100644 index 0000000..6f69c15 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/get-delegatee-details.ts @@ -0,0 +1,23 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const getDelegateeDetails = createAction({ + auth: onfleetAuth, + name: 'get_delegatee_details', + displayName: 'Get Delegatee Details', + description: 'Get details of a connected organization', + props: { + organization: Property.ShortText({ + displayName: 'Organization ID', + description: 'ID of the connected organization', + required: true, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.organization.get(context.propsValue.organization); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/get-destination.ts b/packages/pieces/community/onfleet/src/lib/actions/get-destination.ts new file mode 100644 index 0000000..d122630 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/get-destination.ts @@ -0,0 +1,23 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const getDestination = createAction({ + auth: onfleetAuth, + name: 'get_destination', + displayName: 'Get Destination', + description: 'Get a specific destination', + props: { + destination: Property.ShortText({ + displayName: 'Destination ID', + description: 'The ID of the destination you want to delete', + required: true, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.destinations.get(context.propsValue.destination); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/get-hubs.ts b/packages/pieces/community/onfleet/src/lib/actions/get-hubs.ts new file mode 100644 index 0000000..b094768 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/get-hubs.ts @@ -0,0 +1,17 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const getHubs = createAction({ + auth: onfleetAuth, + name: 'get_hubs', + displayName: 'Get Hubs', + description: 'Get many hubs', + props: {}, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.hubs.get(); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/get-organization.ts b/packages/pieces/community/onfleet/src/lib/actions/get-organization.ts new file mode 100644 index 0000000..33bdac4 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/get-organization.ts @@ -0,0 +1,17 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const getOrganization = createAction({ + auth: onfleetAuth, + name: 'get_organization', + displayName: 'Get Organization', + description: 'Get your organization details', + props: {}, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.organization.get(); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/get-recipient.ts b/packages/pieces/community/onfleet/src/lib/actions/get-recipient.ts new file mode 100644 index 0000000..7ab91c1 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/get-recipient.ts @@ -0,0 +1,23 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const getRecipient = createAction({ + auth: onfleetAuth, + name: 'get_recipient', + displayName: 'Get Recipient', + description: 'Gets a single recipient', + props: { + id: Property.ShortText({ + displayName: 'Recipient ID', + description: "The recipient's ID", + required: true, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.recipients.get(context.propsValue['id']); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/get-task.ts b/packages/pieces/community/onfleet/src/lib/actions/get-task.ts new file mode 100644 index 0000000..56415ac --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/get-task.ts @@ -0,0 +1,23 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const getTask = createAction({ + auth: onfleetAuth, + name: 'get_task', + displayName: 'Get Task', + description: 'Get a specific task', + props: { + task: Property.ShortText({ + displayName: 'Task ID', + description: 'The ID of the task you want to delete', + required: true, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.tasks.get(context.propsValue.task); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/get-tasks.ts b/packages/pieces/community/onfleet/src/lib/actions/get-tasks.ts new file mode 100644 index 0000000..e33a0fe --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/get-tasks.ts @@ -0,0 +1,69 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; + +import Onfleet from '@onfleet/node-onfleet'; +import dayjs from 'dayjs'; + +export const getTasks = createAction({ + auth: onfleetAuth, + name: 'get_tasks', + displayName: 'Get Tasks', + description: 'Get many task', + props: { + from: Property.DateTime({ + displayName: 'From', + required: true, + }), + to: Property.DateTime({ + displayName: 'To', + required: false, + }), + state: Property.MultiSelectDropdown({ + displayName: 'State', + required: false, + refreshers: [], + options: async () => { + return { + disabled: false, + options: [ + { + label: 'Unassigned', + value: 0, + }, + { + label: 'Assigned', + value: 1, + }, + { + label: 'Active', + value: 2, + }, + { + label: 'Completed', + value: 3, + }, + ], + }; + }, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + const from = context.propsValue.from + ? dayjs(context.propsValue.from).valueOf() + : undefined; + const to = context.propsValue.to + ? dayjs(context.propsValue.to).valueOf() + : undefined; + + const options: any = {}; + + if (from) options.from = from; + if (to) options.to = to; + if (context.propsValue.state) + options.state = context.propsValue.state.join(','); + + return await onfleetApi.tasks.get(options); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/get-team.ts b/packages/pieces/community/onfleet/src/lib/actions/get-team.ts new file mode 100644 index 0000000..f893ec6 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/get-team.ts @@ -0,0 +1,20 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const getTeam = createAction({ + auth: onfleetAuth, + name: 'get_team', + displayName: 'Get Team', + description: 'Gets an existing team', + props: { + team: common.team, + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.teams.get(context.propsValue.team as string); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/get-teams.ts b/packages/pieces/community/onfleet/src/lib/actions/get-teams.ts new file mode 100644 index 0000000..a281218 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/get-teams.ts @@ -0,0 +1,18 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const getTeams = createAction({ + auth: onfleetAuth, + name: 'get_teams', + displayName: 'Get Teams', + description: 'Gets many existing team', + props: {}, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.teams.get(); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/get-worker-schedule.ts b/packages/pieces/community/onfleet/src/lib/actions/get-worker-schedule.ts new file mode 100644 index 0000000..c0fb941 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/get-worker-schedule.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const getWorkerSchedule = createAction({ + auth: onfleetAuth, + name: 'get_worker_schedule', + displayName: 'Get Worker Schedule', + description: "Get an existing worker's schedule", + props: { + worker: common.worker, + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.workers.getSchedule( + context.propsValue.worker as string + ); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/get-worker.ts b/packages/pieces/community/onfleet/src/lib/actions/get-worker.ts new file mode 100644 index 0000000..abf2064 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/get-worker.ts @@ -0,0 +1,20 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const getWorker = createAction({ + auth: onfleetAuth, + name: 'get_worker', + displayName: 'Get Worker', + description: "Get an existing worker's details", + props: { + worker: common.worker, + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + return await onfleetApi.workers.get(context.propsValue.worker as string); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/update-admin.ts b/packages/pieces/community/onfleet/src/lib/actions/update-admin.ts new file mode 100644 index 0000000..01ba022 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/update-admin.ts @@ -0,0 +1,32 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const updateAdmin = createAction({ + auth: onfleetAuth, + name: 'update_admin', + displayName: 'Update Administrator', + description: 'Update an existing administrator', + props: { + admin: common.admin, + name: Property.ShortText({ + displayName: 'Name', + description: 'Full name', + required: false, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + const options: any = {}; + + if (context.propsValue.name) options.name = context.propsValue.name; + + return await onfleetApi.administrators.update( + context.propsValue.admin as string, + options + ); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/update-hub.ts b/packages/pieces/community/onfleet/src/lib/actions/update-hub.ts new file mode 100644 index 0000000..7944c25 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/update-hub.ts @@ -0,0 +1,33 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const updateHub = createAction({ + auth: onfleetAuth, + name: 'update_hub', + displayName: 'Update Hub', + description: 'Update an existing hub', + props: { + hub: common.hub, + name: Property.ShortText({ + displayName: 'Name', + description: 'Name of the hub', + required: false, + }), + teams: common.teams, + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + const options: any = {}; + + if (context.propsValue.name) options.name = context.propsValue.name; + if (context.propsValue.teams) options.teams = context.propsValue.teams; + + return await onfleetApi.hubs.update( + context.propsValue.hub as string, + options + ); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/update-recipient.ts b/packages/pieces/community/onfleet/src/lib/actions/update-recipient.ts new file mode 100644 index 0000000..92e9818 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/update-recipient.ts @@ -0,0 +1,45 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const updateRecipient = createAction({ + auth: onfleetAuth, + name: 'update_recipient', + displayName: 'Update Recipient', + description: 'Updates a recipient', + props: { + id: Property.ShortText({ + displayName: 'Recipient ID', + description: 'The ID of the recipient you want to update', + required: true, + }), + name: Property.ShortText({ + displayName: 'Name', + description: "The recipient's full name", + required: false, + }), + notes: Property.ShortText({ + displayName: 'Notes', + required: false, + }), + skipSMSNotifications: Property.Checkbox({ + displayName: 'Skip SMS Notifications', + required: false, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + const recipient: any = { + name: context.propsValue['name'] ?? undefined, + notes: context.propsValue['notes'] ?? undefined, + skipSMSNotifications: + context.propsValue['skipSMSNotifications'] ?? undefined, + }; + + return await onfleetApi.recipients.update( + context.propsValue['id'], + recipient + ); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/update-task.ts b/packages/pieces/community/onfleet/src/lib/actions/update-task.ts new file mode 100644 index 0000000..37d05a6 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/update-task.ts @@ -0,0 +1,75 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; + +import Onfleet from '@onfleet/node-onfleet'; +import dayjs from 'dayjs'; + +export const updateTask = createAction({ + auth: onfleetAuth, + name: 'update_task', + displayName: 'Update Task', + description: 'Updates a task', + props: { + task: Property.ShortText({ + displayName: 'Task ID', + description: 'ID of the task you want to update', + required: true, + }), + merchant: Property.ShortText({ + displayName: 'Merchant ID', + description: 'ID of the organization that will be displayed on the task', + required: false, + }), + executor: Property.ShortText({ + displayName: 'Executor ID', + description: 'ID of the organization that will be executing the task', + required: false, + }), + completeAfter: Property.DateTime({ + displayName: 'Complete After', + description: 'The earliest time the task should be completed', + required: false, + }), + completeBefore: Property.DateTime({ + displayName: 'Complete Before', + description: 'The latest time the task should be completed', + required: false, + }), + pickupTask: Property.Checkbox({ + displayName: 'Pickup', + description: 'Whether the task is pickup', + required: false, + }), + quantity: Property.Number({ + displayName: 'Quantity', + description: 'The number of units to be dropped off', + required: false, + }), + serviceTime: Property.Number({ + displayName: 'Service Time', + description: + "The number of minutes to be spent by the worker on arrival at this task's destination", + required: false, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + const completeAfter = context.propsValue.completeAfter + ? dayjs(context.propsValue.completeAfter).valueOf() + : undefined; + const completeBefore = context.propsValue.completeBefore + ? dayjs(context.propsValue.completeBefore).valueOf() + : undefined; + + return await onfleetApi.tasks.update(context.propsValue.task, { + merchant: context.propsValue.merchant, + executor: context.propsValue.executor, + pickupTask: context.propsValue.pickupTask, + quantity: context.propsValue.quantity, + serviceTime: context.propsValue.serviceTime, + completeAfter: completeAfter, + completeBefore: completeBefore, + }); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/update-team.ts b/packages/pieces/community/onfleet/src/lib/actions/update-team.ts new file mode 100644 index 0000000..6f6a4e0 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/update-team.ts @@ -0,0 +1,45 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const updateTeam = createAction({ + auth: onfleetAuth, + name: 'update_team', + displayName: 'Update Team', + description: 'Update an existing team', + props: { + team: common.team, + name: Property.ShortText({ + displayName: 'Name', + description: 'Name of the team', + required: false, + }), + workers: common.workersOptional, + managers: common.managersOptional, + hub: common.hubOptional, + enableSelfAssignment: Property.Checkbox({ + displayName: 'Enable Self Assignment', + description: + 'Allows Drivers to Self Assign Tasks that are in the Team unassigned container.', + required: false, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + const options: any = {}; + if (context.propsValue.name) options.name = context.propsValue.name; + if (context.propsValue.workers) + options.workers = context.propsValue.workers; + if (context.propsValue.hub) options.hub = context.propsValue.hub; + if (context.propsValue.enableSelfAssignment) + options.enableSelfAssignment = context.propsValue.enableSelfAssignment; + + return await onfleetApi.teams.update( + context.propsValue.team as string, + options + ); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/actions/update-worker.ts b/packages/pieces/community/onfleet/src/lib/actions/update-worker.ts new file mode 100644 index 0000000..994feb7 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/actions/update-worker.ts @@ -0,0 +1,47 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { onfleetAuth } from '../..'; +import { common } from '../common'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const updateWorker = createAction({ + auth: onfleetAuth, + name: 'update_worker', + displayName: 'Update Worker', + description: 'Update an existing worker', + props: { + worker: common.worker, + name: Property.ShortText({ + displayName: 'Name', + description: 'Name of the worker', + required: false, + }), + teams: common.teams, + capacity: Property.Number({ + displayName: 'Capacity', + description: 'The maximum number of units this worker can carry', + required: false, + }), + displayName: Property.ShortText({ + displayName: 'Display Name', + required: false, + }), + }, + async run(context) { + const onfleetApi = new Onfleet(context.auth); + + const options: any = {}; + + if (context.propsValue.name) options.name = context.propsValue.name; + if (context.propsValue.teams) options.teams = context.propsValue.teams; + if (context.propsValue.capacity) + options.capacity = context.propsValue.capacity; + if (context.propsValue.displayName) + options.displayName = context.propsValue.displayName; + + return await onfleetApi.workers.update( + context.propsValue.worker as string, + options + ); + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/common/index.ts b/packages/pieces/community/onfleet/src/lib/common/index.ts new file mode 100644 index 0000000..5f6c6cc --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/common/index.ts @@ -0,0 +1,412 @@ +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; + +import Onfleet from '@onfleet/node-onfleet'; + +export const common = { + destination: Property.DynamicProperties({ + displayName: 'Destination', + description: 'The task destination', + required: true, + refreshers: ['unparsedDestination'], + props: async (propsValue) => { + let fields: DynamicPropsValue = {}; + if (propsValue['unparsedDestination']) { + fields = { + unparsedAddress: Property.ShortText({ + displayName: 'Address', + required: true, + }), + }; + } else { + fields = { + number: Property.ShortText({ + displayName: 'Number', + required: true, + }), + street: Property.ShortText({ + displayName: 'Street Name', + required: true, + }), + apartment: Property.ShortText({ + displayName: 'Apartment', + required: true, + }), + city: Property.ShortText({ + displayName: 'City', + required: true, + }), + country: Property.ShortText({ + displayName: 'Country', + required: true, + }), + state: Property.ShortText({ + displayName: 'State', + required: false, + }), + postalCode: Property.ShortText({ + displayName: 'Postal Code', + required: false, + }), + name: Property.ShortText({ + displayName: 'Destination Name', + required: false, + }), + }; + } + + return fields; + }, + }), + unparsedDestination: Property.Checkbox({ + displayName: 'Unparsed Destination', + description: + 'Check this box if the destination is a single unparsed string', + required: true, + defaultValue: false, + }), + + teams: Property.MultiSelectDropdown({ + displayName: 'Teams', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Enter API Key', + }; + } + const teams = await common.getTeams(auth as string); + const options: any[] = teams.map((team: any) => { + return { + label: team.name, + value: team.id, + }; + }); + + return { + options: options, + placeholder: 'Choose team', + }; + }, + }), + teamsRequired: Property.MultiSelectDropdown({ + displayName: 'Teams', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Enter API Key', + }; + } + const teams = await common.getTeams(auth as string); + const options: any[] = teams.map((team: any) => { + return { + label: team.name, + value: team.id, + }; + }); + + return { + options: options, + placeholder: 'Choose team', + }; + }, + }), + team: Property.Dropdown({ + displayName: 'Team', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Enter API Key', + }; + } + const teams = await common.getTeams(auth as string); + const options: any[] = teams.map((team: any) => { + return { + label: team.name, + value: team.id, + }; + }); + + return { + options: options, + placeholder: 'Choose team', + }; + }, + }), + + hub: Property.Dropdown({ + displayName: 'Hub', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + placeholder: 'Enter API Key or use an expression', + }; + } + const hubs = await common.getHubs(auth as string); + const options: any[] = hubs.map((hub: any) => { + return { + label: hub.name, + value: hub.id, + }; + }); + + return { + options: options, + placeholder: 'Choose hub', + }; + }, + }), + hubOptional: Property.Dropdown({ + displayName: 'Hub', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + placeholder: 'Enter API Key or use an expression', + }; + } + const hubs = await common.getHubs(auth as string); + const options: any[] = hubs.map((hub: any) => { + return { + label: hub.name, + value: hub.id, + }; + }); + + return { + options: options, + placeholder: 'Choose hub', + }; + }, + }), + + admin: Property.Dropdown({ + displayName: 'Administrator', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + placeholder: 'Enter API Key or use an expression', + }; + } + const admins = await common.getAdmins(auth as string); + const options: any[] = admins.map((admin: any) => { + return { + label: admin.name, + value: admin.id, + }; + }); + + return { + options: options, + placeholder: 'Choose admin', + }; + }, + }), + managers: Property.MultiSelectDropdown({ + displayName: 'Managers', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + placeholder: 'Enter API Key or use an expression', + }; + } + const admins = await common.getAdmins(auth as string); + const options: any[] = admins.map((admin: any) => { + return { + label: admin.name, + value: admin.id, + }; + }); + + return { + options: options, + placeholder: 'Choose managers', + }; + }, + }), + managersOptional: Property.MultiSelectDropdown({ + displayName: 'Managers', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + placeholder: 'Enter API Key or use an expression', + }; + } + const admins = await common.getAdmins(auth as string); + const options: any[] = admins.map((admin: any) => { + return { + label: admin.name, + value: admin.id, + }; + }); + + return { + options: options, + placeholder: 'Choose managers', + }; + }, + }), + + worker: Property.Dropdown({ + displayName: 'Worker', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + placeholder: 'Enter API Key or use an expression', + }; + } + const workers = await common.getWorkers(auth as string); + const options: any[] = workers.map((worker: any) => { + return { + label: worker.name, + value: worker.id, + }; + }); + + return { + options: options, + placeholder: 'Choose worker', + }; + }, + }), + + workers: Property.MultiSelectDropdown({ + displayName: 'Workers', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + placeholder: 'Enter API Key or use an expression', + }; + } + const workers = await common.getWorkers(auth as string); + const options: any[] = workers.map((worker: any) => { + return { + label: worker.name, + value: worker.id, + }; + }); + + return { + options: options, + placeholder: 'Choose workers', + }; + }, + }), + workersOptional: Property.MultiSelectDropdown({ + displayName: 'Workers', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + options: [], + placeholder: 'Enter API Key or use an expression', + }; + } + const workers = await common.getWorkers(auth as string); + const options: any[] = workers.map((worker: any) => { + return { + label: worker.name, + value: worker.id, + }; + }); + + return { + options: options, + placeholder: 'Choose workers', + }; + }, + }), + + async getTeams(apiKey: string) { + const onfleetApi = new Onfleet(apiKey); + + return await onfleetApi.teams.get(); + }, + + async getHubs(apiKey: string) { + const onfleetApi = new Onfleet(apiKey); + + return await onfleetApi.hubs.get(); + }, + + async getAdmins(apiKey: string) { + const onfleetApi = new Onfleet(apiKey); + + return await onfleetApi.administrators.get(); + }, + + async getWorkers(apiKey: string) { + const onfleetApi = new Onfleet(apiKey); + + return await onfleetApi.workers.get(); + }, + + async subscribeWebhook(apiKey: string, webhookUrl: string, triggerId: any) { + const onfleetApi = new Onfleet(apiKey); + + return ( + await onfleetApi.webhooks.create({ + url: webhookUrl, + trigger: triggerId, + }) + ).id; + }, + + async unsubscribeWebhook(apiKey: string, webhookId: string) { + const onfleetApi = new Onfleet(apiKey); + + return await onfleetApi.webhooks.deleteOne(webhookId); + }, +}; + +export enum OnfleetWebhookTriggers { + TASK_STARTED = 0, + TASK_ETA = 1, + TASK_ARRIVAL = 2, + TASK_COMPLETED = 3, + TASK_FAILED = 4, + WORKER_DUTY_CHANGE = 5, + TASK_CREATED = 6, + TASK_UPDATED = 7, + TASK_DELETED = 8, + TASK_ASSIGNED = 9, + TASK_UNASSIGNED = 10, + TASK_DELAYED = 12, + TASK_CLONED = 13, + SMS_RECIPIENT_MISSED = 14, + WORKER_CREATED = 15, + WORKER_DELETED = 16, + SMS_RECIPIENT_OPT_OUT = 17, + AUTO_DISPATCH_COMPLETED = 18, +} diff --git a/packages/pieces/community/onfleet/src/lib/triggers/auto-dispatch-completed.ts b/packages/pieces/community/onfleet/src/lib/triggers/auto-dispatch-completed.ts new file mode 100644 index 0000000..93cac2d --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/auto-dispatch-completed.ts @@ -0,0 +1,178 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; + +export const autoDispatchCompleted = createTrigger({ + auth: onfleetAuth, + name: 'auto_dispatch_completed', + displayName: 'Auto Dispatch Completed', + description: 'Triggers when team auto-dispatch calculation is completed', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.AUTO_DISPATCH_COMPLETED + ); + + await context.store?.put('_auto_dispatch_completed_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get( + '_auto_dispatch_completed_trigger' + ); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: { + actionContext: { + apiKeyScopeId: '34522acbaf4558bee7474e594aa2ba0c', + id: 'vjw*RDMKDljKVDve1Vtcplgu', + type: 'API', + }, + adminId: null, + data: { + dispatch: { + id: 'XaSPx65XPOTiyzu7hbjlgTxN', + options: { + maxAllowedDelay: 10, + maxTasksPerRoute: 50, + routeEnd: 'teams://DEFAULT', + scheduleTimeWindow: [1659727323264, 1659748923264], + serviceTime: 4, + taskTimeWindow: [1659712923264, 1659741723264], + }, + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + plan: { + routes: [ + { + routeId: 'ZxcnkJi~79nonYaMTQ960Mg2', + stops: [ + { + arrivalTime: 1659729556337, + departTime: 1659729796337, + id: 'LdvrBX7fADEvlNuFUZJu8d9S', + type: 'TASK', + }, + { + arrivalTime: 1659733290968, + departTime: 1659733530968, + id: '~JA*OXe7f6sLzy~zo6brH6xp', + type: 'TASK', + }, + { + arrivalTime: 1659737162319, + departTime: 1659737402319, + id: 'bn50Lcsu8rqETDWJTIdecufy', + type: 'TASK', + }, + { + arrivalTime: 1659738493969, + departTime: 1659738733969, + id: 'Sef4w3TakeQk6dQJBhQDYglsO', + type: 'TASK', + }, + ], + type: 'WORKER', + }, + ], + unplanned: [], + }, + processingDetails: { + endTime: 1659727327650, + startTime: 1659727323428, + status: 'success', + }, + tasks: [ + { + additionalQuantities: { + quantityA: 0, + quantityB: 0, + quantityC: 0, + }, + completeAfter: null, + completeBefore: null, + id: 'Sef4w3TakeQk6dQJBhQDYglsO', + pickupTask: false, + quantity: 0, + shortId: '2770e3e3', + }, + { + additionalQuantities: { + quantityA: 0, + quantityB: 0, + quantityC: 0, + }, + completeAfter: 1659726000000, + completeBefore: 1659751200000, + id: 'LdvrBX7fADEvlNuFUZJu8d9S', + pickupTask: false, + quantity: 0, + shortId: 'ce6439b7', + }, + { + additionalQuantities: { + quantityA: 0, + quantityB: 0, + quantityC: 0, + }, + completeAfter: 1659726000000, + completeBefore: 1659747600000, + id: 'bn50Lcsu8rqETDWJTIdecufy', + pickupTask: false, + quantity: 0, + shortId: '6d87d2bf', + }, + { + additionalQuantities: { + quantityA: 0, + quantityB: 0, + quantityC: 0, + }, + completeAfter: 1659726000000, + completeBefore: 1659754800000, + id: '~JA*OXe7f6sLzy~zo6brH6xp', + pickupTask: false, + quantity: 0, + shortId: '2e2f201c', + }, + ], + team: 'K3FXFtJj2FtaO2~H60evRrDc', + }, + }, + dispatchId: 'XaSPx65XPOTiyzu7hbjlgTxN', + taskId: null, + time: 1659727327697, + triggerId: 18, + triggerName: 'autoDispatchJobCompleted', + workerId: null, + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/triggers/sms-recipient-opt-out.ts b/packages/pieces/community/onfleet/src/lib/triggers/sms-recipient-opt-out.ts new file mode 100644 index 0000000..4fcd219 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/sms-recipient-opt-out.ts @@ -0,0 +1,72 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; + +export const smsRecipientOptOut = createTrigger({ + auth: onfleetAuth, + name: 'sms_recipient_opt_out', + displayName: 'SMS Recipient Opt Out', + description: 'Triggers when a recipient opts out of SMS notifications', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.SMS_RECIPIENT_OPT_OUT + ); + + await context.store?.put('_sms_recipient_opt_out_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get( + '_sms_recipient_opt_out_trigger' + ); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: { + recipient: { + id: '7LecFRKJw7ExfyhBsi9h0NXW', + name: 'Joe Smith', + phone: '+17145555768', + }, + timestamp: 1632432776621, + SMS: 'STOP', + triggerId: 17, + triggerName: 'SMSRecipientOptOut', + taskId: null, + workerId: null, + adminId: null, + data: {}, + actionContext: null, + time: 1632432776640, + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/triggers/sms-recipient-response-missed.ts b/packages/pieces/community/onfleet/src/lib/triggers/sms-recipient-response-missed.ts new file mode 100644 index 0000000..57a3c0a --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/sms-recipient-response-missed.ts @@ -0,0 +1,56 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; +export const smsRecipientResponseMissed = createTrigger({ + auth: onfleetAuth, + name: 'sms_recipient_response_missed', + displayName: 'SMS Recipient Response Missed', + description: + 'Triggers when a recipient responds to a notification via SMS, but the organization is unable to handle it at that time', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.SMS_RECIPIENT_MISSED + ); + + await context.store?.put('_sms_recipient_response_missed_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get( + '_sms_recipient_response_missed_trigger' + ); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: {}, +}); diff --git a/packages/pieces/community/onfleet/src/lib/triggers/task-arrival.ts b/packages/pieces/community/onfleet/src/lib/triggers/task-arrival.ts new file mode 100644 index 0000000..a00b926 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/task-arrival.ts @@ -0,0 +1,142 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; +export const taskArrival = createTrigger({ + auth: onfleetAuth, + name: 'task_arrival', + displayName: 'Task Arrival', + description: + 'Triggers when a task worker arriving at or closer than threshold value provided, in meters', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.TASK_ARRIVAL + ); + + await context.store?.put('_task_arrival_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get('_task_arrival_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: { + taskId: 'hV2lAmBLs~76oXR4jYBjQbgY', + distance: 134.6184612940922, + triggerId: 2, + triggerName: 'taskArrival', + workerId: null, + adminId: null, + data: { + task: { + id: 'hV2lAmBLs~76oXR4jYBjQbgY', + timeCreated: 1615502820000, + timeLastModified: 1615504576163, + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + shortId: 'a685d01d', + trackingURL: 'https://onf.lt/a685d01d24', + worker: 'COwfwH~Zogm1LXIZYbPlLAyw', + merchant: 'nYrkNP6jZMSKgBwG9qG7ci3J', + executor: 'nYrkNP6jZMSKgBwG9qG7ci3J', + creator: 'vjw*RDMKDljKVDve1Vtcplgu', + dependencies: [], + state: 2, + completeAfter: 1615492800000, + completeBefore: 1615505400000, + pickupTask: false, + notes: '', + completionDetails: { + failureNotes: '', + failureReason: 'NONE', + events: [], + actions: [], + time: null, + firstLocation: [], + lastLocation: [], + unavailableAttachments: [], + }, + feedback: [], + metadata: [], + overrides: {}, + quantity: 0, + serviceTime: 0, + identity: { + failedScanCount: 0, + checksum: null, + }, + appearance: { + triangleColor: null, + }, + container: { + type: 'WORKER', + worker: 'COwfwH~Zogm1LXIZYbPlLAyw', + }, + trackingViewed: false, + estimatedCompletionTime: 1615506156730, + estimatedArrivalTime: 1615506156730, + recipients: [ + { + id: '7LecFRKJw7ExfyhBsi9h0NXW', + timeCreated: 1592005264000, + timeLastModified: 1615502820526, + name: 'Brodie Lee', + phone: '+17145555678', + notes: 'Notes do not change *edited* more new notes', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + skipSMSNotifications: false, + metadata: [], + }, + ], + destination: { + id: '3NACfr4SVlCi8s~vPgKskAip', + timeCreated: 1615502820000, + timeLastModified: 1615502820514, + location: [-117.895446, 33.9131177], + address: { + apartment: '', + state: 'California', + postalCode: '92821', + number: '338', + street: 'South Redwood Avenue', + city: 'Brea', + country: 'United States', + }, + notes: '', + metadata: [], + }, + }, + }, + actionContext: null, + time: 1615505822024, + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/triggers/task-assigned.ts b/packages/pieces/community/onfleet/src/lib/triggers/task-assigned.ts new file mode 100644 index 0000000..d2105f0 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/task-assigned.ts @@ -0,0 +1,181 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; +export const taskAssigned = createTrigger({ + auth: onfleetAuth, + name: 'task_assigned', + displayName: 'Task Assigned', + description: 'Triggers when a task is assigned', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.TASK_ASSIGNED + ); + + await context.store?.put('_task_assigned_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get('_task_assigned_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: { + actionContext: { + id: 'vjw*RDMKDljKVDve1Vtcplgu', + type: 'ADMIN', + }, + adminId: 'vjw*RDMKDljKVDve1Vtcplgu', + data: { + task: { + appearance: { + triangleColor: null, + }, + completeAfter: 1612987200000, + completeBefore: 1613008800000, + completionDetails: { + actions: [], + events: [], + failureNotes: '', + failureReason: 'NONE', + firstLocation: [], + lastLocation: [], + time: null, + unavailableAttachments: [], + }, + container: { + type: 'WORKER', + worker: 'COwfwH~Zogm1LXIZYbPlLAyw', + }, + creator: 'vjw*RDMKDljKVDve1Vtcplgu', + delayTime: null, + dependencies: [], + destination: { + address: { + apartment: '', + city: 'Anaheim', + country: 'United States', + name: 'Honda Center', + number: '2695', + postalCode: '92806', + state: 'California', + street: 'East Katella Avenue', + }, + id: 'I5rMyWx4YHDcMGIwfD3TL8nf', + location: [-117.8764687, 33.8078476], + metadata: [], + notes: 'This is a destination note', + timeCreated: 1613002583000, + timeLastModified: 1613002583913, + }, + estimatedArrivalTime: null, + estimatedCompletionTime: null, + executor: 'nYrkNP6jZMSKgBwG9qG7ci3J', + feedback: [], + id: 'txiK2xHBIaUwAKB~BJrjscKu', + identity: { + checksum: null, + failedScanCount: 0, + }, + merchant: 'nYrkNP6jZMSKgBwG9qG7ci3J', + metadata: [], + notes: 'This is a Task note', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + overrides: {}, + pickupTask: false, + quantity: 1, + recipients: [ + { + id: 'A~pBTrc5~dTMBBImswg7U4YT', + metadata: [], + name: 'Test User One', + notes: 'This is a recipient note', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + phone: '+17145554231', + skipSMSNotifications: false, + timeCreated: 1613002583000, + timeLastModified: 1613002583931, + }, + ], + serviceTime: 3, + shortId: 'fab829cf', + state: 1, + timeCreated: 1613002583000, + timeLastModified: 1613004164514, + trackingURL: 'https://onf.lt/fab829cf81', + trackingViewed: false, + worker: 'COwfwH~Zogm1LXIZYbPlLAyw', + }, + worker: { + accountStatus: 'ACCEPTED', + activeTask: null, + capacity: 3, + delayTime: null, + displayName: '', + hasRecentlyUsedSpoofedLocations: false, + id: 'COwfwH~Zogm1LXIZYbPlLAyw', + imageUrl: null, + location: [-117.8901118, 33.893365], + metadata: [], + name: 'Shured Shuanger', + onDuty: true, + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + phone: '+17145555678', + tasks: ['txiK2xHBIaUwAKB~BJrjscKu'], + teams: ['K3FXFtJj2FtaO2~H60evRrDc'], + timeCreated: 1585254830000, + timeLastModified: 1613004164511, + timeLastSeen: 1613004141332, + timezone: 'America/Los_Angeles', + userData: { + appVersion: '2.1.11.1', + batteryLevel: 0.64, + deviceDescription: 'Google Pixel 2 (Android 11)', + platform: 'ANDROID', + }, + vehicle: { + color: '', + description: '', + id: 'Dib0eZfs*uJhJmWHKL~tExub', + licensePlate: '', + timeLastModified: 1612226873144, + type: 'CAR', + }, + }, + }, + taskId: 'txiK2xHBIaUwAKB~BJrjscKu', + time: 1613004164575, + triggerId: 9, + triggerName: 'taskAssigned', + workerId: 'COwfwH~Zogm1LXIZYbPlLAyw', + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/triggers/task-cloned.ts b/packages/pieces/community/onfleet/src/lib/triggers/task-cloned.ts new file mode 100644 index 0000000..d57ff78 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/task-cloned.ts @@ -0,0 +1,146 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; +export const taskCloned = createTrigger({ + auth: onfleetAuth, + name: 'task_cloned', + displayName: 'Task Cloned', + description: 'Triggers when a task is cloned', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.TASK_CLONED + ); + + await context.store?.put('_task_cloned_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get('_task_cloned_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: { + actionContext: { + id: 'vjw*RDMKDljKVDve1Vtcplgu', + type: 'ADMIN', + }, + adminId: 'vjw*RDMKDljKVDve1Vtcplgu', + data: { + task: { + appearance: { + triangleColor: null, + }, + completeAfter: null, + completeBefore: 1613008800000, + completionDetails: { + actions: [], + events: [], + failureNotes: '', + failureReason: 'NONE', + firstLocation: [], + lastLocation: [], + time: null, + unavailableAttachments: [], + }, + container: { + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + type: 'ORGANIZATION', + }, + creator: 'vjw*RDMKDljKVDve1Vtcplgu', + delayTime: null, + dependencies: [], + destination: { + address: { + apartment: '', + city: 'Anaheim', + country: 'United States', + name: 'Honda Center', + number: '2695', + postalCode: '92806', + state: 'California', + street: 'East Katella Avenue', + }, + id: 'pJcfO7NRJaor~Tl8ggBHrveJ', + location: [-117.8764687, 33.8078476], + metadata: [], + notes: 'This is a destination note', + timeCreated: 1613004115000, + timeLastModified: 1613004115635, + }, + estimatedArrivalTime: null, + estimatedCompletionTime: null, + executor: 'nYrkNP6jZMSKgBwG9qG7ci3J', + feedback: [], + id: '3C0W9uLyWC5R4V5fuj7bzJpk', + identity: { + checksum: null, + failedScanCount: 0, + }, + merchant: 'nYrkNP6jZMSKgBwG9qG7ci3J', + metadata: [], + notes: 'This is a Task note', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + overrides: {}, + pickupTask: false, + quantity: 1, + recipients: [ + { + id: 'A~pBTrc5~dTMBBImswg7U4YT', + metadata: [], + name: 'Test User One', + notes: 'This is a recipient note', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + phone: '+17145554231', + skipSMSNotifications: false, + timeCreated: 1613002583000, + timeLastModified: 1613002583931, + }, + ], + serviceTime: 3, + shortId: '9ad82b23', + sourceTaskId: 'txiK2xHBIaUwAKB~BJrjscKu', + state: 0, + timeCreated: 1613004116000, + timeLastModified: 1613004116038, + trackingURL: 'https://onf.lt/9ad82b2380', + trackingViewed: false, + worker: null, + }, + }, + taskId: '3C0W9uLyWC5R4V5fuj7bzJpk', + time: 1613004116101, + triggerId: 13, + triggerName: 'taskCloned', + workerId: null, + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/triggers/task-completed.ts b/packages/pieces/community/onfleet/src/lib/triggers/task-completed.ts new file mode 100644 index 0000000..7c725ba --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/task-completed.ts @@ -0,0 +1,152 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; +export const taskCompleted = createTrigger({ + auth: onfleetAuth, + name: 'task_completed', + displayName: 'Task Completed', + description: 'Triggers when a task is completed', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.TASK_COMPLETED + ); + + await context.store?.put('_task_completed_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get('_task_completed_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: { + actionContext: { + id: 'COwfwH~Zogm1LXIZYbPlLAyw', + type: 'WORKER', + }, + adminId: null, + data: { + task: { + appearance: { + triangleColor: null, + }, + completeAfter: null, + completeBefore: 1613008800000, + completionDetails: { + actions: [], + events: [ + { + name: 'start', + time: 1613004620434, + }, + ], + failureNotes: '', + failureReason: 'NONE', + firstLocation: [], + lastLocation: [], + notes: 'this is a completed note', + photoUploadId: null, + photoUploadIds: [], + signatureUploadId: null, + success: true, + time: 1613004642071, + unavailableAttachments: [], + }, + creator: 'vjw*RDMKDljKVDve1Vtcplgu', + delayTime: null, + dependencies: [], + destination: { + address: { + apartment: '', + city: 'Anaheim', + country: 'United States', + name: 'Honda Center', + number: '2695', + postalCode: '92806', + state: 'California', + street: 'East Katella Avenue', + }, + id: 'ycrp3Omwm0qhS2F725DaLsfM', + location: [-117.8764687, 33.8078476], + metadata: [], + notes: 'This is a destination note', + timeCreated: 1613004583000, + timeLastModified: 1613004583735, + }, + estimatedArrivalTime: null, + estimatedCompletionTime: null, + executor: 'nYrkNP6jZMSKgBwG9qG7ci3J', + feedback: [], + id: 'WGpUvHMTSrwZh*lqtTIt9iSW', + identity: { + checksum: null, + failedScanCount: 0, + }, + merchant: 'nYrkNP6jZMSKgBwG9qG7ci3J', + metadata: [], + notes: 'This is a Task note', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + overrides: {}, + pickupTask: false, + quantity: 1, + recipients: [ + { + id: 'A~pBTrc5~dTMBBImswg7U4YT', + metadata: [], + name: 'Test User One', + notes: 'This is a recipient note', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + phone: '+17145554231', + skipSMSNotifications: false, + timeCreated: 1613002583000, + timeLastModified: 1613002583931, + }, + ], + serviceTime: 3, + shortId: 'b7f82a36', + sourceTaskId: 'txiK2xHBIaUwAKB~BJrjscKu', + state: 3, + timeCreated: 1613004583000, + timeLastModified: 1613004642099, + trackingURL: 'https://onf.lt/b7f82a36cf', + trackingViewed: false, + worker: 'COwfwH~Zogm1LXIZYbPlLAyw', + }, + }, + taskId: 'WGpUvHMTSrwZh*lqtTIt9iSW', + time: 1613004642136, + triggerId: 3, + triggerName: 'taskCompleted', + workerId: 'COwfwH~Zogm1LXIZYbPlLAyw', + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/triggers/task-created.ts b/packages/pieces/community/onfleet/src/lib/triggers/task-created.ts new file mode 100644 index 0000000..2b46731 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/task-created.ts @@ -0,0 +1,146 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; + +export const taskCreated = createTrigger({ + auth: onfleetAuth, + name: 'task_created', + displayName: 'Task Created', + description: 'Triggers when a task is created', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.TASK_CREATED + ); + + await context.store?.put('_task_created_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get('_task_created_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: { + actionContext: { + id: 'vjw*RDMKDljKVDve1Vtcplgu', + type: 'ADMIN', + }, + adminId: 'vjw*RDMKDljKVDve1Vtcplgu', + data: { + task: { + appearance: { + triangleColor: null, + }, + completeAfter: 1612987200000, + completeBefore: 1613008800000, + completionDetails: { + actions: [], + events: [], + failureNotes: '', + failureReason: 'NONE', + firstLocation: [], + lastLocation: [], + time: null, + unavailableAttachments: [], + }, + container: { + type: 'WORKER', + worker: 'COwfwH~Zogm1LXIZYbPlLAyw', + }, + creator: 'vjw*RDMKDljKVDve1Vtcplgu', + delayTime: null, + dependencies: [], + destination: { + address: { + apartment: '', + city: 'Anaheim', + country: 'United States', + name: 'Honda Center', + number: '2695', + postalCode: '92806', + state: 'California', + street: 'East Katella Avenue', + }, + id: 'I5rMyWx4YHDcMGIwfD3TL8nf', + location: [-117.8764687, 33.8078476], + metadata: [], + notes: 'This is a destination note', + timeCreated: 1613002583000, + timeLastModified: 1613002583913, + }, + estimatedArrivalTime: null, + estimatedCompletionTime: null, + executor: 'nYrkNP6jZMSKgBwG9qG7ci3J', + feedback: [], + id: 'txiK2xHBIaUwAKB~BJrjscKu', + identity: { + checksum: null, + failedScanCount: 0, + }, + merchant: 'nYrkNP6jZMSKgBwG9qG7ci3J', + metadata: [], + notes: 'This is a Task note', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + overrides: {}, + pickupTask: false, + quantity: 1, + recipients: [ + { + id: 'A~pBTrc5~dTMBBImswg7U4YT', + metadata: [], + name: 'Test User One', + notes: 'This is a recipient note', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + phone: '+17145554231', + skipSMSNotifications: false, + timeCreated: 1613002583000, + timeLastModified: 1613002583931, + }, + ], + serviceTime: 3, + shortId: 'fab829cf', + state: 1, + timeCreated: 1613002583000, + timeLastModified: 1613002583970, + trackingURL: 'https://onf.lt/fab829cf81', + trackingViewed: false, + worker: 'COwfwH~Zogm1LXIZYbPlLAyw', + }, + }, + taskId: 'txiK2xHBIaUwAKB~BJrjscKu', + time: 1613002584051, + triggerId: 6, + triggerName: 'taskCreated', + workerId: null, + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/triggers/task-delayed.ts b/packages/pieces/community/onfleet/src/lib/triggers/task-delayed.ts new file mode 100644 index 0000000..31c9a49 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/task-delayed.ts @@ -0,0 +1,142 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; +export const taskDelayed = createTrigger({ + auth: onfleetAuth, + name: 'task_delayed', + displayName: 'Task Delayed', + description: 'Triggers when a task is delayed', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.TASK_DELAYED + ); + + await context.store?.put('_task_delayed_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get('_task_delayed_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: { + actionContext: null, + adminId: null, + data: { + task: { + appearance: { + triangleColor: null, + }, + completeAfter: 1613160000000, + completeBefore: 1613179800000, + completionDetails: { + actions: [], + events: [], + failureNotes: '', + failureReason: 'NONE', + firstLocation: [], + lastLocation: [], + time: null, + unavailableAttachments: [], + }, + container: { + type: 'WORKER', + worker: 'COwfwH~Zogm1LXIZYbPlLAyw', + }, + creator: 'vjw*RDMKDljKVDve1Vtcplgu', + dependencies: [], + destination: { + address: { + apartment: '', + city: 'Irvine', + country: 'United States', + name: 'University of California Irvine, Irvine, CA, USA', + number: '', + postalCode: '92697', + state: 'California', + street: '', + }, + id: '134VHJhnXUqOmaFdISY0r6BD', + location: [-117.8442962, 33.6404952], + metadata: [], + notes: '', + timeCreated: 1613177955000, + timeLastModified: 1613177955586, + }, + estimatedArrivalTime: 1613180322638, + estimatedCompletionTime: 1613180322638, + executor: 'nYrkNP6jZMSKgBwG9qG7ci3J', + feedback: [], + id: 'zIeGIBZQZhTRHaK6V6V74Fpg', + identity: { + checksum: null, + failedScanCount: 0, + }, + merchant: 'nYrkNP6jZMSKgBwG9qG7ci3J', + metadata: [], + notes: '', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + overrides: {}, + pickupTask: false, + quantity: 0, + recipients: [ + { + id: '7LecFRKJw7ExfyhBsi9h0NXW', + metadata: [], + name: 'Brodie Lee', + notes: 'Notes do not change *edited* more new notes', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + phone: '+17145555678', + skipSMSNotifications: false, + timeCreated: 1592005264000, + timeLastModified: 1613177955599, + }, + ], + serviceTime: 0, + shortId: 'a79d22fb', + state: 2, + timeCreated: 1613177955000, + timeLastModified: 1613178053658, + trackingURL: 'https://onf.lt/a79d22fb77', + trackingViewed: false, + worker: 'COwfwH~Zogm1LXIZYbPlLAyw', + }, + }, + delay: 522.6378813476563, + taskId: 'zIeGIBZQZhTRHaK6V6V74Fpg', + time: 1613178058862, + triggerId: 12, + triggerName: 'taskDelayed', + workerId: null, + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/triggers/task-deleted.ts b/packages/pieces/community/onfleet/src/lib/triggers/task-deleted.ts new file mode 100644 index 0000000..067f2e5 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/task-deleted.ts @@ -0,0 +1,143 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; +export const taskDeleted = createTrigger({ + auth: onfleetAuth, + name: 'task_deleted', + displayName: 'Task Deleted', + description: 'Triggers when a task is deleted', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.TASK_DELETED + ); + + await context.store?.put('_task_deleted_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get('_task_deleted_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: { + actionContext: { + id: 'vjw*RDMKDljKVDve1Vtcplgu', + type: 'ADMIN', + }, + adminId: 'vjw*RDMKDljKVDve1Vtcplgu', + data: { + task: { + appearance: { + triangleColor: null, + }, + completeAfter: null, + completeBefore: 1613008800000, + completionDetails: { + actions: [], + events: [], + failureNotes: '', + failureReason: 'NONE', + firstLocation: [], + lastLocation: [], + time: null, + unavailableAttachments: [], + }, + container: { + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + type: 'ORGANIZATION', + }, + creator: 'vjw*RDMKDljKVDve1Vtcplgu', + dependencies: [], + destination: { + address: { + apartment: '', + city: 'Anaheim', + country: 'United States', + name: 'Honda Center', + number: '2695', + postalCode: '92806', + state: 'California', + street: 'East Katella Avenue', + }, + id: 'pJcfO7NRJaor~Tl8ggBHrveJ', + location: [-117.8764687, 33.8078476], + metadata: [], + notes: 'This is a destination note', + timeCreated: 1613004115000, + timeLastModified: 1613004115635, + }, + executor: 'nYrkNP6jZMSKgBwG9qG7ci3J', + feedback: [], + id: '3C0W9uLyWC5R4V5fuj7bzJpk', + identity: { + checksum: null, + failedScanCount: 0, + }, + merchant: 'nYrkNP6jZMSKgBwG9qG7ci3J', + metadata: [], + notes: 'This is a Task note', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + overrides: {}, + pickupTask: false, + quantity: 1, + recipients: [ + { + id: 'A~pBTrc5~dTMBBImswg7U4YT', + metadata: [], + name: 'Test User One', + notes: 'This is a recipient note', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + phone: '+17145554231', + skipSMSNotifications: false, + timeCreated: 1613002583000, + timeLastModified: 1613002583931, + }, + ], + serviceTime: 3, + shortId: '9ad82b23', + sourceTaskId: 'txiK2xHBIaUwAKB~BJrjscKu', + state: 0, + timeCreated: 1613004116000, + timeLastModified: 1613004116038, + trackingURL: 'https://onf.lt/9ad82b2380', + trackingViewed: false, + worker: null, + }, + }, + taskId: '3C0W9uLyWC5R4V5fuj7bzJpk', + time: 1613004265027, + triggerId: 8, + triggerName: 'taskDeleted', + workerId: null, + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/triggers/task-eta.ts b/packages/pieces/community/onfleet/src/lib/triggers/task-eta.ts new file mode 100644 index 0000000..761e4d4 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/task-eta.ts @@ -0,0 +1,142 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; +export const taskEta = createTrigger({ + auth: onfleetAuth, + name: 'task_eta', + displayName: 'Task ETA', + description: + 'Triggers when a task worker ETA less than or equal to threshold value provided, in seconds', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.TASK_ETA + ); + + await context.store?.put('_task_eta_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get('_task_eta_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: { + taskId: 'hV2lAmBLs~76oXR4jYBjQbgY', + etaSeconds: 298.2603148875159, + triggerId: 1, + triggerName: 'taskEta', + workerId: null, + adminId: null, + data: { + task: { + id: 'hV2lAmBLs~76oXR4jYBjQbgY', + timeCreated: 1615502820000, + timeLastModified: 1615504576163, + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + shortId: 'a685d01d', + trackingURL: 'https://onf.lt/a685d01d24', + worker: 'COwfwH~Zogm1LXIZYbPlLAyw', + merchant: 'nYrkNP6jZMSKgBwG9qG7ci3J', + executor: 'nYrkNP6jZMSKgBwG9qG7ci3J', + creator: 'vjw*RDMKDljKVDve1Vtcplgu', + dependencies: [], + state: 2, + completeAfter: 1615492800000, + completeBefore: 1615505400000, + pickupTask: false, + notes: '', + completionDetails: { + failureNotes: '', + failureReason: 'NONE', + events: [], + actions: [], + time: null, + firstLocation: [], + lastLocation: [], + unavailableAttachments: [], + }, + feedback: [], + metadata: [], + overrides: {}, + quantity: 0, + serviceTime: 0, + identity: { + failedScanCount: 0, + checksum: null, + }, + appearance: { + triangleColor: null, + }, + container: { + type: 'WORKER', + worker: 'COwfwH~Zogm1LXIZYbPlLAyw', + }, + trackingViewed: false, + estimatedCompletionTime: 1615506153703, + estimatedArrivalTime: 1615506153703, + recipients: [ + { + id: '7LecFRKJw7ExfyhBsi9h0NXW', + timeCreated: 1592005264000, + timeLastModified: 1615502820526, + name: 'Brodie Lee', + phone: '+17145555678', + notes: 'Notes do not change *edited* more new notes', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + skipSMSNotifications: false, + metadata: [], + }, + ], + destination: { + id: '3NACfr4SVlCi8s~vPgKskAip', + timeCreated: 1615502820000, + timeLastModified: 1615502820514, + location: [-117.895446, 33.9131177], + address: { + apartment: '', + state: 'California', + postalCode: '92821', + number: '338', + street: 'South Redwood Avenue', + city: 'Brea', + country: 'United States', + }, + notes: '', + metadata: [], + }, + }, + }, + actionContext: null, + time: 1615505708224, + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/triggers/task-failed.ts b/packages/pieces/community/onfleet/src/lib/triggers/task-failed.ts new file mode 100644 index 0000000..7fcfd03 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/task-failed.ts @@ -0,0 +1,151 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; +export const taskFailed = createTrigger({ + auth: onfleetAuth, + name: 'task_failed', + displayName: 'Task Failed', + description: 'Triggers when a task has failed', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.TASK_FAILED + ); + + await context.store?.put('_task_failed_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get('_task_failed_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: { + actionContext: { + id: 'COwfwH~Zogm1LXIZYbPlLAyw', + type: 'WORKER', + }, + adminId: null, + data: { + task: { + appearance: { + triangleColor: null, + }, + completeAfter: 1612987200000, + completeBefore: 1613008800000, + completionDetails: { + actions: [], + events: [ + { + name: 'start', + time: 1613004361594, + }, + ], + failureNotes: '', + failureReason: 'UNABLE_TO_LOCATE', + firstLocation: [], + lastLocation: [], + notes: 'this is a failure note', + photoUploadId: null, + photoUploadIds: [], + signatureUploadId: null, + success: false, + time: 1613004459779, + unavailableAttachments: [], + }, + creator: 'vjw*RDMKDljKVDve1Vtcplgu', + delayTime: null, + dependencies: [], + destination: { + address: { + apartment: '', + city: 'Anaheim', + country: 'United States', + name: 'Honda Center', + number: '2695', + postalCode: '92806', + state: 'California', + street: 'East Katella Avenue', + }, + id: 'I5rMyWx4YHDcMGIwfD3TL8nf', + location: [-117.8764687, 33.8078476], + metadata: [], + notes: 'This is a destination note', + timeCreated: 1613002583000, + timeLastModified: 1613002583913, + }, + estimatedArrivalTime: null, + estimatedCompletionTime: null, + executor: 'nYrkNP6jZMSKgBwG9qG7ci3J', + feedback: [], + id: 'txiK2xHBIaUwAKB~BJrjscKu', + identity: { + checksum: null, + failedScanCount: 0, + }, + merchant: 'nYrkNP6jZMSKgBwG9qG7ci3J', + metadata: [], + notes: 'This is a Task note', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + overrides: {}, + pickupTask: false, + quantity: 1, + recipients: [ + { + id: 'A~pBTrc5~dTMBBImswg7U4YT', + metadata: [], + name: 'Test User One', + notes: 'This is a recipient note', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + phone: '+17145554231', + skipSMSNotifications: false, + timeCreated: 1613002583000, + timeLastModified: 1613002583931, + }, + ], + serviceTime: 3, + shortId: 'fab829cf', + state: 3, + timeCreated: 1613002583000, + timeLastModified: 1613004459863, + trackingURL: 'https://onf.lt/fab829cf81', + trackingViewed: false, + worker: 'COwfwH~Zogm1LXIZYbPlLAyw', + }, + }, + taskId: 'txiK2xHBIaUwAKB~BJrjscKu', + time: 1613004460070, + triggerId: 4, + triggerName: 'taskFailed', + workerId: 'COwfwH~Zogm1LXIZYbPlLAyw', + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/triggers/task-started.ts b/packages/pieces/community/onfleet/src/lib/triggers/task-started.ts new file mode 100644 index 0000000..5f64101 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/task-started.ts @@ -0,0 +1,145 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; + +export const taskStarted = createTrigger({ + auth: onfleetAuth, + name: 'task_started', + displayName: 'Task Started', + description: 'Triggers when a task is started', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.TASK_STARTED + ); + + await context.store?.put('_task_started_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get('_task_started_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: { + actionContext: { + id: 'COwfwH~Zogm1LXIZYbPlLAyw', + type: 'WORKER', + }, + adminId: null, + data: { + task: { + appearance: { + triangleColor: null, + }, + completeAfter: 1612987200000, + completeBefore: 1613008800000, + completionDetails: { + actions: [], + events: [], + failureNotes: '', + failureReason: 'NONE', + firstLocation: [], + lastLocation: [], + time: null, + unavailableAttachments: [], + }, + container: { + type: 'WORKER', + worker: 'COwfwH~Zogm1LXIZYbPlLAyw', + }, + creator: 'vjw*RDMKDljKVDve1Vtcplgu', + dependencies: [], + destination: { + address: { + apartment: '', + city: 'Anaheim', + country: 'United States', + name: 'Honda Center', + number: '2695', + postalCode: '92806', + state: 'California', + street: 'East Katella Avenue', + }, + id: 'I5rMyWx4YHDcMGIwfD3TL8nf', + location: [-117.8764687, 33.8078476], + metadata: [], + notes: 'This is a destination note', + timeCreated: 1613002583000, + timeLastModified: 1613002583913, + }, + estimatedArrivalTime: null, + estimatedCompletionTime: null, + executor: 'nYrkNP6jZMSKgBwG9qG7ci3J', + feedback: [], + id: 'txiK2xHBIaUwAKB~BJrjscKu', + identity: { + checksum: null, + failedScanCount: 0, + }, + merchant: 'nYrkNP6jZMSKgBwG9qG7ci3J', + metadata: [], + notes: 'This is a Task note', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + overrides: {}, + pickupTask: false, + quantity: 1, + recipients: [ + { + id: 'A~pBTrc5~dTMBBImswg7U4YT', + metadata: [], + name: 'Test User One', + notes: 'This is a recipient note', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + phone: '+17145554231', + skipSMSNotifications: false, + timeCreated: 1613002583000, + timeLastModified: 1613002583931, + }, + ], + serviceTime: 3, + shortId: 'fab829cf', + state: 2, + timeCreated: 1613002583000, + timeLastModified: 1613004361613, + trackingURL: 'https://onf.lt/fab829cf81', + trackingViewed: false, + worker: 'COwfwH~Zogm1LXIZYbPlLAyw', + }, + }, + taskId: 'txiK2xHBIaUwAKB~BJrjscKu', + time: 1613004361894, + triggerId: 0, + triggerName: 'taskStarted', + workerId: 'COwfwH~Zogm1LXIZYbPlLAyw', + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/triggers/task-unassigned.ts b/packages/pieces/community/onfleet/src/lib/triggers/task-unassigned.ts new file mode 100644 index 0000000..43066fe --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/task-unassigned.ts @@ -0,0 +1,145 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; +export const taskUnassigned = createTrigger({ + auth: onfleetAuth, + name: 'task_unassigned', + displayName: 'Task Unassigned', + description: 'Triggers when a task is unassigned', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.TASK_UNASSIGNED + ); + + await context.store?.put('_task_unassigned_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get('_task_unassigned_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: { + actionContext: { + id: 'vjw*RDMKDljKVDve1Vtcplgu', + type: 'ADMIN', + }, + adminId: 'vjw*RDMKDljKVDve1Vtcplgu', + data: { + task: { + appearance: { + triangleColor: null, + }, + completeAfter: 1612987200000, + completeBefore: 1613008800000, + completionDetails: { + actions: [], + events: [], + failureNotes: '', + failureReason: 'NONE', + firstLocation: [], + lastLocation: [], + time: null, + unavailableAttachments: [], + }, + container: { + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + type: 'ORGANIZATION', + }, + creator: 'vjw*RDMKDljKVDve1Vtcplgu', + delayTime: null, + dependencies: [], + destination: { + address: { + apartment: '', + city: 'Anaheim', + country: 'United States', + name: 'Honda Center', + number: '2695', + postalCode: '92806', + state: 'California', + street: 'East Katella Avenue', + }, + id: 'I5rMyWx4YHDcMGIwfD3TL8nf', + location: [-117.8764687, 33.8078476], + metadata: [], + notes: 'This is a destination note', + timeCreated: 1613002583000, + timeLastModified: 1613002583913, + }, + estimatedArrivalTime: null, + estimatedCompletionTime: null, + executor: 'nYrkNP6jZMSKgBwG9qG7ci3J', + feedback: [], + id: 'txiK2xHBIaUwAKB~BJrjscKu', + identity: { + checksum: null, + failedScanCount: 0, + }, + merchant: 'nYrkNP6jZMSKgBwG9qG7ci3J', + metadata: [], + notes: 'This is a Task note', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + overrides: {}, + pickupTask: false, + quantity: 1, + recipients: [ + { + id: 'A~pBTrc5~dTMBBImswg7U4YT', + metadata: [], + name: 'Test User One', + notes: 'This is a recipient note', + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + phone: '+17145554231', + skipSMSNotifications: false, + timeCreated: 1613002583000, + timeLastModified: 1613002583931, + }, + ], + serviceTime: 3, + shortId: 'fab829cf', + state: 0, + timeCreated: 1613002583000, + timeLastModified: 1613003963558, + trackingURL: 'https://onf.lt/fab829cf81', + trackingViewed: false, + worker: null, + }, + }, + taskId: 'txiK2xHBIaUwAKB~BJrjscKu', + time: 1613003963647, + triggerId: 10, + triggerName: 'taskUnassigned', + workerId: null, + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/triggers/task-updated.ts b/packages/pieces/community/onfleet/src/lib/triggers/task-updated.ts new file mode 100644 index 0000000..b1f824b --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/task-updated.ts @@ -0,0 +1,172 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; + +export const taskUpdated = createTrigger({ + auth: onfleetAuth, + name: 'task_updated', + displayName: 'Task Updated', + description: 'Triggers when a task is updated', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.TASK_UPDATED + ); + + await context.store?.put('_task_updated_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get('_task_updated_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: { + taskId: 'w7CcGpzsMnEiUg1AqgxQbPE~', + workerId: 'ZxcnkJi~79nonYaMTQ960Mg2', + actionContext: { + type: 'ADMIN', + id: 'vjw*RDMKDljKVDve1Vtcplgu', + }, + triggerId: 7, + triggerName: 'taskUpdated', + adminId: 'vjw*RDMKDljKVDve1Vtcplgu', + data: { + task: { + id: 'w7CcGpzsMnEiUg1AqgxQbPE~', + timeCreated: 1627329316000, + timeLastModified: 1627329522544, + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + shortId: 'c9ed4d00', + trackingURL: 'https://onf.lt/c9ed4d00', + worker: 'ZxcnkJi~79nonYaMTQ960Mg2', + merchant: 'nYrkNP6jZMSKgBwG9qG7ci3J', + executor: 'nYrkNP6jZMSKgBwG9qG7ci3J', + creator: 'vjw*RDMKDljKVDve1Vtcplgu', + dependencies: [], + state: 1, + completeAfter: null, + completeBefore: null, + pickupTask: false, + notes: 'This is updated Notes', + completionDetails: { + failureNotes: '', + failureReason: 'NONE', + events: [], + actions: [], + time: null, + firstLocation: [], + lastLocation: [], + unavailableAttachments: [], + }, + feedback: [], + metadata: [], + overrides: {}, + quantity: 0, + serviceTime: 0, + identity: { + failedScanCount: 0, + checksum: null, + }, + appearance: { + triangleColor: null, + }, + container: { + type: 'WORKER', + worker: 'ZxcnkJi~79nonYaMTQ960Mg2', + }, + trackingViewed: false, + recipients: [], + estimatedCompletionTime: 1627330894582, + estimatedArrivalTime: 1627330592582, + destination: { + id: '7i9PoiinkxWtWbytv1HLY9SS', + timeCreated: 1627329316000, + timeLastModified: 1627329522522, + location: [-117.8764687, 33.8078476], + address: { + apartment: '', + state: 'California', + postalCode: '92806', + number: '2695', + street: 'East Katella Avenue', + city: 'Anaheim', + country: 'United States', + name: 'Honda Center', + }, + notes: '', + metadata: [], + googlePlaceId: null, + warnings: [], + }, + delayTime: null, + }, + worker: { + id: 'ZxcnkJi~79nonYaMTQ960Mg2', + timeCreated: 1618618787000, + timeLastModified: 1627329496627, + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + name: 'Red Ranger', + displayName: '', + phone: '+17145555768', + activeTask: null, + tasks: ['w7CcGpzsMnEiUg1AqgxQbPE~'], + onDuty: true, + timeLastSeen: 1627329498940, + capacity: 0, + userData: { + appVersion: '2.1.13.2', + batteryLevel: 0.65, + deviceDescription: 'Google Pixel 2 (Android 11)', + platform: 'ANDROID', + }, + accountStatus: 'ACCEPTED', + metadata: [], + timezone: 'America/Los_Angeles', + imageUrl: null, + teams: ['K3FXFtJj2FtaO2~H60evRrDc'], + delayTime: null, + location: [-117.8954515, 33.9131014], + hasRecentlyUsedSpoofedLocations: false, + vehicle: { + id: 'vSRLJ80Aw3DljIh1Rj9obLtn', + type: 'CAR', + description: '', + licensePlate: '', + color: '', + timeLastModified: 1625065516261, + }, + }, + }, + time: 1627329522593, + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/triggers/worker-created.ts b/packages/pieces/community/onfleet/src/lib/triggers/worker-created.ts new file mode 100644 index 0000000..1673cc1 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/worker-created.ts @@ -0,0 +1,94 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; +export const workerCreated = createTrigger({ + auth: onfleetAuth, + name: 'worker_created', + displayName: 'Worker Created', + description: 'Triggers when a worker is created', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.WORKER_CREATED + ); + + await context.store?.put('_worker_created_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get('_worker_created_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: { + adminId: 'i18uIpm5NNNw6nBL8QMW1JM7', + workerId: 'sccpOkp3SassNmJxHjm1UFc5', + actionContext: { + id: 'i18uIpm5NNNw6nBL8QMW1JM7', + type: 'ADMIN', + }, + triggerId: 15, + triggerName: 'workerCreated', + taskId: null, + data: { + worker: { + id: 'sccpOkp3SassNmJxHjm1UFc5', + timeCreated: 1623274200000, + timeLastModified: 1623274200799, + organization: '1MWYTEQf6jioThhHhH4~KmVI', + name: 'John Smith', + displayName: '', + phone: '+17145555768', + activeTask: null, + tasks: [], + onDuty: false, + timeLastSeen: null, + capacity: 0, + userData: {}, + accountStatus: 'INVITED', + metadata: [], + timezone: null, + imageUrl: null, + teams: ['QNwu7xmlvGHzAYXk2zmZocD2'], + vehicle: { + id: '3O7k6AmNVc5U8~AkgNRVxGTm', + type: 'CAR', + description: '1996 Honda Accord', + licensePlate: null, + color: 'Green', + timeLastModified: 1623274200789, + }, + }, + }, + time: 1623274200840, + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/triggers/worker-deleted.ts b/packages/pieces/community/onfleet/src/lib/triggers/worker-deleted.ts new file mode 100644 index 0000000..ddd7907 --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/worker-deleted.ts @@ -0,0 +1,95 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; + +export const workerDeleted = createTrigger({ + auth: onfleetAuth, + name: 'worker_deleted', + displayName: 'Worker Deleted', + description: 'Triggers when a worker is deleted', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.WORKER_DELETED + ); + + await context.store?.put('_worker_deleted_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get('_worker_deleted_trigger'); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: { + actionContext: { + type: 'ADMIN', + id: 'i18uIpm5NNNw6nBL8QMW1JM7', + }, + workerId: 'sccpOkp3SassNmJxHjm1UFc5', + triggerId: 16, + triggerName: 'workerDeleted', + taskId: null, + adminId: 'i18uIpm5NNNw6nBL8QMW1JM7', + data: { + worker: { + id: 'sccpOkp3SassNmJxHjm1UFc5', + timeCreated: 1623274200000, + timeLastModified: 1623274200799, + organization: '1MWYTEQf6jioThhHhH4~KmVI', + name: 'John Smith', + displayName: '', + phone: '+17145555768', + activeTask: null, + tasks: [], + onDuty: false, + timeLastSeen: null, + capacity: 0, + userData: {}, + accountStatus: 'INVITED', + metadata: [], + timezone: null, + imageUrl: null, + teams: ['QNwu7xmlvGHzAYXk2zmZocD2'], + vehicle: { + id: '3O7k6AmNVc5U8~AkgNRVxGTm', + type: 'CAR', + description: '1996 Honda Accord', + licensePlate: null, + color: 'Green', + timeLastModified: 1623274200789, + }, + }, + }, + time: 1623274403564, + }, +}); diff --git a/packages/pieces/community/onfleet/src/lib/triggers/worker-duty-change.ts b/packages/pieces/community/onfleet/src/lib/triggers/worker-duty-change.ts new file mode 100644 index 0000000..a811bfa --- /dev/null +++ b/packages/pieces/community/onfleet/src/lib/triggers/worker-duty-change.ts @@ -0,0 +1,102 @@ +import { + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { common, OnfleetWebhookTriggers } from '../common'; +import { onfleetAuth } from '../..'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; +export const workerDutyChange = createTrigger({ + auth: onfleetAuth, + name: 'worker_duty_change', + displayName: 'Worker Duty Change', + description: 'Triggers when a worker status changes', + type: TriggerStrategy.WEBHOOK, + props: {}, + //Create the webhook and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await common.subscribeWebhook( + context.auth, + context.webhookUrl, + OnfleetWebhookTriggers.WORKER_DUTY_CHANGE + ); + + await context.store?.put('_worker_duty_change_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook + async onDisable(context) { + const response: any = await context.store?.get( + '_worker_duty_change_trigger' + ); + + if (response !== null && response !== undefined) { + await common.unsubscribeWebhook(context.auth, response.webhookId); + } + }, + //Return task + async run(context) { + return [context.payload.body]; + }, + + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.QUERY_PRESENT, + paramName: 'check', + }, + + async onHandshake(context) { + return { + status: 200, + body: context.payload.queryParams['check'], + }; + }, + + sampleData: { + actionContext: null, + adminId: null, + data: { + worker: { + accountStatus: 'ACCEPTED', + activeTask: null, + capacity: 3, + delayTime: null, + displayName: '', + hasRecentlyUsedSpoofedLocations: false, + id: 'COwfwH~Zogm1LXIZYbPlLAyw', + imageUrl: null, + location: null, + metadata: [], + name: 'Shured Shuanger', + onDuty: true, + organization: 'nYrkNP6jZMSKgBwG9qG7ci3J', + phone: '+17145555678', + tasks: ['txiK2xHBIaUwAKB~BJrjscKu'], + teams: ['K3FXFtJj2FtaO2~H60evRrDc'], + timeCreated: 1585254830000, + timeLastModified: 1613003712585, + timeLastSeen: 1613003870027, + timezone: 'America/Los_Angeles', + userData: { + appVersion: '2.1.11.1', + batteryLevel: 0.64, + deviceDescription: 'Google Pixel 2 (Android 11)', + platform: 'ANDROID', + }, + vehicle: { + color: '', + description: '', + id: 'Dib0eZfs*uJhJmWHKL~tExub', + licensePlate: '', + timeLastModified: 1612226873144, + type: 'CAR', + }, + }, + }, + status: 1, + taskId: null, + time: 1613003870062, + triggerId: 5, + triggerName: 'workerDuty', + workerId: 'COwfwH~Zogm1LXIZYbPlLAyw', + }, +}); diff --git a/packages/pieces/community/onfleet/tsconfig.json b/packages/pieces/community/onfleet/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/onfleet/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/onfleet/tsconfig.lib.json b/packages/pieces/community/onfleet/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/onfleet/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/open-router/.eslintrc.json b/packages/pieces/community/open-router/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/open-router/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/open-router/README.md b/packages/pieces/community/open-router/README.md new file mode 100644 index 0000000..8c04448 --- /dev/null +++ b/packages/pieces/community/open-router/README.md @@ -0,0 +1,7 @@ +# pieces-open-router + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-open-router` to build the library. diff --git a/packages/pieces/community/open-router/package.json b/packages/pieces/community/open-router/package.json new file mode 100644 index 0000000..23d7778 --- /dev/null +++ b/packages/pieces/community/open-router/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-open-router", + "version": "0.0.8" +} \ No newline at end of file diff --git a/packages/pieces/community/open-router/project.json b/packages/pieces/community/open-router/project.json new file mode 100644 index 0000000..81a8a9b --- /dev/null +++ b/packages/pieces/community/open-router/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-open-router", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/open-router/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/open-router", + "tsConfig": "packages/pieces/community/open-router/tsconfig.lib.json", + "packageJson": "packages/pieces/community/open-router/package.json", + "main": "packages/pieces/community/open-router/src/index.ts", + "assets": [ + "packages/pieces/community/open-router/*.md", + { + "input": "packages/pieces/community/open-router/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-open-router {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/open-router/src/index.ts b/packages/pieces/community/open-router/src/index.ts new file mode 100644 index 0000000..53ee27c --- /dev/null +++ b/packages/pieces/community/open-router/src/index.ts @@ -0,0 +1,68 @@ +import { + AuthenticationType, + createCustomApiCallAction, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { askOpenRouterAction } from './lib/actions/ask-open-router'; + +const markdownDescription = ` +Follow these instructions to get your OpenAI API Key: + +1. Visit the following website: https://openrouter.ai/keys. +2. Once on the website, click on create a key. +3. Once you have created a key, copy it and use it for the Api key field on the site. +`; +export const openRouterAuth = PieceAuth.SecretText({ + description: markdownDescription, + displayName: 'Api Key', + required: true, + validate: async ({ auth }) => { + // we send a get request to https://openrouter.ai/api/v1/auth/key with the key as a header + // if the response is 200, then the key is valid + // if the response is 401, then the key is invalid + try { + const request: HttpRequest = { + url: 'https://openrouter.ai/api/v1/auth/key', + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth, + }, + }; + await httpClient.sendRequest(request); + return { + valid: true, + }; + } catch (error) { + return { + valid: false, + error: 'Invalid API Key', + }; + } + }, +}); + +export const openRouter = createPiece({ + displayName: 'OpenRouter', + description: 'Use any AI model to generate code, text, or images via OpenRouter.ai.', + auth: openRouterAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/open-router.png', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + authors: ["Salem-Alaa","kishanprmr","MoShizzle","abuaboud"], + actions: [ + askOpenRouterAction, + createCustomApiCallAction({ + baseUrl: () => 'https://openrouter.ai/api/v1', + auth: openRouterAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/open-router/src/lib/actions/ask-open-router.ts b/packages/pieces/community/open-router/src/lib/actions/ask-open-router.ts new file mode 100644 index 0000000..57dc6e2 --- /dev/null +++ b/packages/pieces/community/open-router/src/lib/actions/ask-open-router.ts @@ -0,0 +1,117 @@ +import { openRouterAuth } from '../../index'; +import { + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { openRouterModels, promptResponse } from '../common'; +import { + AuthenticationType, + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const askOpenRouterAction = createAction({ + name: 'ask-lmm', + displayName: 'Ask LLM', + description: 'Ask any model supported by Open Router.', + auth: openRouterAuth, + props: { + model: Property.Dropdown({ + displayName: 'Model', + description: + 'The model which will generate the completion. Some models are suitable for natural language tasks, others specialize in code.', + required: true, + refreshers: [], + defaultValue: 'pygmalionai/mythalion-13b', + options: async ({ auth }) => { + const request: HttpRequest = { + url: 'https://openrouter.ai/api/v1/models', + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }; + try { + const response = await httpClient.sendRequest( + request + ); + + const options = response.body.data.map((model) => { + return { + label: model.id, + value: model.id, + }; + }); + return { + options: options, + disabled: false, + }; + } catch (error) { + return { + options: [], + disabled: true, + placeholder: `Couldn't Load Models:\n${error}`, + }; + } + }, + }), + prompt: Property.LongText({ + displayName: 'Prompt', + required: true, + description: 'The prompt to send to the model.', + }), + temperature: Property.Number({ + displayName: 'Temperature', + required: false, + description: + 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.', + }), + maxTokens: Property.Number({ + displayName: 'Maximum Tokens', + required: false, + description: + "The maximum number of tokens to generate. Requests can use up to 2,048 or 4,096 tokens shared between prompt and completion, don't set the value to maximum and leave some tokens for the input. The exact limit varies by model. (One token is roughly 4 characters for normal English text)", + }), + topP: Property.Number({ + displayName: 'Top P', + required: false, + description: + 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.', + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + temperature: z.number().min(0).max(1.0).optional(), + topP: z.number().min(0).max(1.0).optional(), + }); + + const openRouterModel = context.propsValue.model; + const prompt = context.propsValue.prompt; + const request: HttpRequest = { + url: 'https://openrouter.ai/api/v1/chat/completions', + method: HttpMethod.POST, + body: { + prompt: prompt, + model: openRouterModel, + temperature: context.propsValue.temperature, + max_tokens: context.propsValue.maxTokens, + top_p: context.propsValue.topP, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + headers: { + 'HTTP-Referer': 'https://openrouter.ai/playground', + }, + }; + const response = await httpClient.sendRequest(request); + const responseText = response.body.choices[0].text; + const trimmedResponse = responseText.trim(); + return trimmedResponse; + }, +}); diff --git a/packages/pieces/community/open-router/src/lib/common/index.ts b/packages/pieces/community/open-router/src/lib/common/index.ts new file mode 100644 index 0000000..29a3993 --- /dev/null +++ b/packages/pieces/community/open-router/src/lib/common/index.ts @@ -0,0 +1,13 @@ +export interface promptResponse { + choices: { + text: string; + }[]; + model: string; + id: string; +} + +export interface openRouterModels { + data: { + id: string; + }[]; +} diff --git a/packages/pieces/community/open-router/tsconfig.json b/packages/pieces/community/open-router/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/open-router/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/open-router/tsconfig.lib.json b/packages/pieces/community/open-router/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/open-router/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/openai/.babelrc b/packages/pieces/community/openai/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/openai/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/openai/.eslintrc.json b/packages/pieces/community/openai/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/openai/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/openai/README.md b/packages/pieces/community/openai/README.md new file mode 100644 index 0000000..3313c2e --- /dev/null +++ b/packages/pieces/community/openai/README.md @@ -0,0 +1,7 @@ +# pieces-openai + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-openai` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/openai/package-lock.json b/packages/pieces/community/openai/package-lock.json new file mode 100644 index 0000000..63108bb --- /dev/null +++ b/packages/pieces/community/openai/package-lock.json @@ -0,0 +1,20 @@ +{ + "name": "@activepieces/piece-openai", + "version": "0.5.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-openai", + "version": "0.5.2", + "dependencies": { + "tiktoken": "1.0.11" + } + }, + "node_modules/tiktoken": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.11.tgz", + "integrity": "sha512-aMJcn9NGmb6zDXkCweJLnACyIyjdiYIk1odAfnCUvin7O1QsV1rQP1hatGDMhQovxkeSJhFeU7QuGkbDHGciDQ==" + } + } +} diff --git a/packages/pieces/community/openai/package.json b/packages/pieces/community/openai/package.json new file mode 100644 index 0000000..2077bb8 --- /dev/null +++ b/packages/pieces/community/openai/package.json @@ -0,0 +1,7 @@ +{ + "name": "@activepieces/piece-openai", + "version": "0.4.3", + "dependencies": { + "tiktoken": "1.0.11" + } +} diff --git a/packages/pieces/community/openai/project.json b/packages/pieces/community/openai/project.json new file mode 100644 index 0000000..1156b18 --- /dev/null +++ b/packages/pieces/community/openai/project.json @@ -0,0 +1,63 @@ +{ + "name": "pieces-openai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/openai/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/openai", + "tsConfig": "packages/pieces/community/openai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/openai/package.json", + "main": "packages/pieces/community/openai/src/index.ts", + "assets": [ + "packages/pieces/community/openai/*.md", + { + "input": "packages/pieces/community/openai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + }, + "dependsOn": ["prebuild", "^build"] + }, + "prebuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/pieces/community/openai", + "command": "npm ci" + } + }, + "build-with-deps": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "nx run pieces-openai:prebuild", + "nx run pieces-openai:build", + "nx run pieces-openai:postbuild" + ], + "parallel": false + } + }, + "postbuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "dist/packages/pieces/community/openai", + "command": "npm install" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/openai/src/index.ts b/packages/pieces/community/openai/src/index.ts new file mode 100644 index 0000000..1aa6f3f --- /dev/null +++ b/packages/pieces/community/openai/src/index.ts @@ -0,0 +1,85 @@ +import { + AuthenticationType, + HttpMethod, + createCustomApiCallAction, + httpClient, +} from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory, SUPPORTED_AI_PROVIDERS } from '@activepieces/shared'; +import { askAssistant } from './lib/actions/ask-assistant'; +import { generateImage } from './lib/actions/generate-image'; +import { askOpenAI } from './lib/actions/send-prompt'; +import { textToSpeech } from './lib/actions/text-to-speech'; +import { transcribeAction } from './lib/actions/transcriptions'; +import { translateAction } from './lib/actions/translation'; +import { visionPrompt } from './lib/actions/vision-prompt'; +import { baseUrl } from './lib/common/common'; +import { extractStructuredDataAction } from './lib/actions/extract-structure-data.action'; + +export const openaiAuth = PieceAuth.SecretText({ + description: SUPPORTED_AI_PROVIDERS.find(p => p.provider === 'openai')?.markdown, + displayName: 'API Key', + required: true, + validate: async (auth) => { + try { + await httpClient.sendRequest<{ + data: { id: string }[]; + }>({ + url: `${baseUrl}/models`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.auth as string, + }, + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API key', + }; + } + }, +}); + +export const openai = createPiece({ + displayName: 'OpenAI', + description: 'Use the many tools ChatGPT has to offer.', + minimumSupportedRelease: '0.63.0', + logoUrl: 'https://cdn.activepieces.com/pieces/openai.png', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + auth: openaiAuth, + actions: [ + askOpenAI, + askAssistant, + generateImage, + visionPrompt, + textToSpeech, + transcribeAction, + translateAction, + extractStructuredDataAction, + createCustomApiCallAction({ + auth: openaiAuth, + baseUrl: () => baseUrl, + authMapping: async (auth) => { + return { + Authorization: `Bearer ${auth}`, + }; + }, + }), + ], + authors: [ + 'aboudzein', + 'astorozhevsky', + 'Willianwg', + 'Nilesh', + 'Salem-Alaa', + 'kishanprmr', + 'MoShizzle', + 'khaledmashaly', + 'abuaboud', + ], + triggers: [], +}); diff --git a/packages/pieces/community/openai/src/lib/actions/ask-assistant.ts b/packages/pieces/community/openai/src/lib/actions/ask-assistant.ts new file mode 100644 index 0000000..770adfb --- /dev/null +++ b/packages/pieces/community/openai/src/lib/actions/ask-assistant.ts @@ -0,0 +1,122 @@ +import { + createAction, + Property, + StoreScope, +} from '@activepieces/pieces-framework'; +import OpenAI from 'openai'; +import { openaiAuth } from '../..'; +import { sleep } from '../common/common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const askAssistant = createAction({ + auth: openaiAuth, + name: 'ask_assistant', + displayName: 'Ask Assistant', + description: 'Ask a GPT assistant anything you want!', + props: { + assistant: Property.Dropdown({ + displayName: 'Assistant', + required: true, + description: 'The assistant which will generate the completion.', + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Enter your API key first', + options: [], + }; + } + try { + const openai = new OpenAI({ + apiKey: auth as string, + }); + const assistants = await openai.beta.assistants.list(); + + return { + disabled: false, + options: assistants.data.map((assistant: any) => { + return { + label: assistant.name, + value: assistant.id, + }; + }), + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: "Couldn't load assistants, API key is invalid", + }; + } + }, + }), + prompt: Property.LongText({ + displayName: 'Question', + required: true, + }), + memoryKey: Property.ShortText({ + displayName: 'Memory Key', + description: + 'A memory key that will keep the chat history shared across runs and flows. Keep it empty to leave your assistant without memory of previous messages.', + required: false, + }), + }, + async run({ auth, propsValue, store }) { + await propsValidation.validateZod(propsValue, { + memoryKey: z.string().max(128).optional(), + }); + + const openai = new OpenAI({ + apiKey: auth, + }); + const { assistant, prompt, memoryKey } = propsValue; + const runCheckDelay = 1000; + let response: any; + let thread: any; + + if (memoryKey) { + // Get existing thread ID or create a new thread for this memory key + thread = await store.get(memoryKey, StoreScope.PROJECT); + if (!thread) { + thread = await openai.beta.threads.create(); + + store.put(memoryKey, thread, StoreScope.PROJECT); + } + } else { + thread = await openai.beta.threads.create(); + } + + const message = await openai.beta.threads.messages.create(thread.id, { + role: 'user', + content: prompt, + }); + + const run = await openai.beta.threads.runs.create(thread.id, { + assistant_id: assistant, + }); + // Wait at least 400ms for inference to finish before checking to save requests + await sleep(400); + + while (!response) { + const runCheck = await openai.beta.threads.runs.retrieve( + thread.id, + run.id + ); + if (runCheck.status == 'completed') { + const messages = await openai.beta.threads.messages.list(thread.id); + // Return only messages that are newer than the user's latest message + response = messages.data.splice( + 0, + messages.data.findIndex((m) => m.id == message.id) + ); + break; + } + + await sleep(runCheckDelay); + } + + return response; + }, +}); diff --git a/packages/pieces/community/openai/src/lib/actions/extract-structure-data.action.ts b/packages/pieces/community/openai/src/lib/actions/extract-structure-data.action.ts new file mode 100644 index 0000000..4539d10 --- /dev/null +++ b/packages/pieces/community/openai/src/lib/actions/extract-structure-data.action.ts @@ -0,0 +1,146 @@ +import { openaiAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import OpenAI from 'openai'; +import { notLLMs } from '../common/common'; + +export const extractStructuredDataAction = createAction({ + auth: openaiAuth, + name: 'extract-structured-data', + displayName: 'Extract Structured Data from Text', + description: 'Returns structured data from provided unstructured text.', + props: { + model: Property.Dropdown({ + displayName: 'Model', + required: true, + refreshers: [], + defaultValue: 'gpt-3.5-turbo', + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Enter your API key first', + options: [], + }; + } + try { + const openai = new OpenAI({ + apiKey: auth as string, + }); + const response = await openai.models.list(); + // We need to get only LLM models + const models = response.data.filter((model) => !notLLMs.includes(model.id)); + return { + disabled: false, + options: models.map((model) => { + return { + label: model.id, + value: model.id, + }; + }), + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: "Couldn't load models, API key is invalid", + }; + } + }, + }), + text: Property.LongText({ + displayName: 'Unstructured Text', + required: true, + }), + params: Property.Array({ + displayName: 'Data Definition', + required: true, + properties: { + propName: Property.ShortText({ + displayName: 'Name', + description: + 'Provide the name of the value you want to extract from the unstructured text. The name should be unique and short. ', + required: true, + }), + propDescription: Property.LongText({ + displayName: 'Description', + description: + 'Brief description of the data, this hints for the AI on what to look for', + required: false, + }), + propDataType: Property.StaticDropdown({ + displayName: 'Data Type', + description: 'Type of parameter.', + required: true, + defaultValue: 'string', + options: { + disabled: false, + options: [ + { label: 'Text', value: 'string' }, + { label: 'Number', value: 'number' }, + { label: 'Boolean', value: 'boolean' }, + ], + }, + }), + propIsRequired: Property.Checkbox({ + displayName: 'Fail if Not present?', + required: true, + defaultValue: false, + }), + }, + }), + }, + async run(context) { + const { model, text } = context.propsValue; + const paramInputArray = context.propsValue.params as ParamInput[]; + const functionParams: Record = {}; + const requiredFunctionParams: string[] = []; + for (const param of paramInputArray) { + functionParams[param.propName] = { + type: param.propDataType, + description: param.propDescription ?? param.propName, + }; + if (param.propIsRequired) { + requiredFunctionParams.push(param.propName); + } + } + const prompt = 'Extract the following data from the provided text' + const openai = new OpenAI({ + apiKey: context.auth, + }); + + const response = await openai.chat.completions.create({ + model: model, + messages: [{ role: 'user', content: text }], + tools: [ + { + type: 'function', + function: { + name: 'extract_structured_data', + description: prompt, + parameters: { + type: 'object', + properties: functionParams, + required: requiredFunctionParams, + }, + }, + }, + ], + }); + + const toolCallsResponse = response.choices[0].message.tool_calls; + if (toolCallsResponse) { + return JSON.parse(toolCallsResponse[0].function.arguments); + } else { + throw new Error(JSON.stringify({ + message: "OpenAI couldn't extract the fields from the above text." + })); + } + }, +}); + +interface ParamInput { + propName: string; + propDescription: string; + propDataType: string; + propIsRequired: boolean; +} diff --git a/packages/pieces/community/openai/src/lib/actions/generate-image.ts b/packages/pieces/community/openai/src/lib/actions/generate-image.ts new file mode 100644 index 0000000..bf2b6d3 --- /dev/null +++ b/packages/pieces/community/openai/src/lib/actions/generate-image.ts @@ -0,0 +1,116 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import OpenAI from 'openai'; +import { openaiAuth } from '../..'; + +export const generateImage = createAction({ + auth: openaiAuth, + name: 'generate_image', + displayName: 'Generate Image', + description: 'Generate an image using text-to-image models', + props: { + model: Property.Dropdown({ + displayName: 'Model', + required: true, + description: 'The model which will generate the image.', + defaultValue: 'dall-e-3', + refreshers: [], + options: async () => { + return { + options: [ + { + label: 'dall-e-3', + value: 'dall-e-3', + }, + { + label: 'dall-e-2', + value: 'dall-e-2', + }, + ], + }; + }, + }), + prompt: Property.LongText({ + displayName: 'Prompt', + required: true, + }), + resolution: Property.Dropdown({ + displayName: 'Resolution', + description: 'The resolution to generate the image in.', + required: false, + refreshers: ['model'], + defaultValue: '1024x1024', + options: async ({ model }) => { + let options = [ + { + label: '1024x1024', + value: '1024x1024', + }, + { + label: '512x512', + value: '512x512', + }, + { + label: '256x256', + value: '256x256', + }, + ]; + if (model == 'dall-e-3') + options = [ + { + label: '1024x1024', + value: '1024x1024', + }, + { + label: '1024x1792', + value: '1024x1792', + }, + { + label: '1792x1024', + value: '1792x1024', + }, + ]; + + return { + options: options, + }; + }, + }), + quality: Property.Dropdown({ + displayName: 'Quality', + required: false, + description: 'Standard is faster, HD has better details.', + defaultValue: 'standard', + refreshers: [], + options: async () => { + return { + options: [ + { + label: 'standard', + value: 'standard', + }, + { + label: 'hd', + value: 'hd', + }, + ], + }; + }, + }), + }, + async run({ auth, propsValue }) { + const openai = new OpenAI({ + apiKey: auth, + }); + + const { quality, resolution, model, prompt } = propsValue; + + const image = await openai.images.generate({ + model: model, + prompt: prompt, + quality: quality as any, + size: resolution as any, + }); + + return image; + }, +}); diff --git a/packages/pieces/community/openai/src/lib/actions/send-prompt.ts b/packages/pieces/community/openai/src/lib/actions/send-prompt.ts new file mode 100644 index 0000000..9934d6d --- /dev/null +++ b/packages/pieces/community/openai/src/lib/actions/send-prompt.ts @@ -0,0 +1,198 @@ +import { + createAction, + Property, + StoreScope, +} from '@activepieces/pieces-framework'; +import OpenAI from 'openai'; +import { openaiAuth } from '../..'; +import { + calculateMessagesTokenSize, + exceedsHistoryLimit, + notLLMs, + reduceContextSize, +} from '../common/common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const askOpenAI = createAction({ + auth: openaiAuth, + name: 'ask_chatgpt', + displayName: 'Ask ChatGPT', + description: 'Ask ChatGPT anything you want!', + props: { + model: Property.Dropdown({ + displayName: 'Model', + required: true, + description: + 'The model which will generate the completion. Some models are suitable for natural language tasks, others specialize in code.', + refreshers: [], + defaultValue: 'gpt-3.5-turbo', + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Enter your API key first', + options: [], + }; + } + try { + const openai = new OpenAI({ + apiKey: auth as string, + }); + const response = await openai.models.list(); + // We need to get only LLM models + const models = response.data.filter( + (model) => !notLLMs.includes(model.id) + ); + return { + disabled: false, + options: models.map((model) => { + return { + label: model.id, + value: model.id, + }; + }), + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: "Couldn't load models, API key is invalid", + }; + } + }, + }), + prompt: Property.LongText({ + displayName: 'Question', + required: true, + }), + temperature: Property.Number({ + displayName: 'Temperature', + required: false, + description: + 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.', + defaultValue: 0.9, + }), + maxTokens: Property.Number({ + displayName: 'Maximum Tokens', + required: true, + description: + "The maximum number of tokens to generate. Requests can use up to 2,048 or 4,096 tokens shared between prompt and completion depending on the model. Don't set the value to maximum and leave some tokens for the input. (One token is roughly 4 characters for normal English text)", + defaultValue: 2048, + }), + topP: Property.Number({ + displayName: 'Top P', + required: false, + description: + 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.', + defaultValue: 1, + }), + frequencyPenalty: Property.Number({ + displayName: 'Frequency penalty', + required: false, + description: + "Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.", + defaultValue: 0, + }), + presencePenalty: Property.Number({ + displayName: 'Presence penalty', + required: false, + description: + "Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the mode's likelihood to talk about new topics.", + defaultValue: 0.6, + }), + memoryKey: Property.ShortText({ + displayName: 'Memory Key', + description: + 'A memory key that will keep the chat history shared across runs and flows. Keep it empty to leave ChatGPT without memory of previous messages.', + required: false, + }), + roles: Property.Json({ + displayName: 'Roles', + required: false, + description: 'Array of roles to specify more accurate response', + defaultValue: [ + { role: 'system', content: 'You are a helpful assistant.' }, + ], + }), + }, + async run({ auth, propsValue, store }) { + await propsValidation.validateZod(propsValue, { + temperature: z.number().min(0).max(1).optional(), + memoryKey: z.string().max(128).optional(), + }); + const openai = new OpenAI({ + apiKey: auth, + }); + const { + model, + temperature, + maxTokens, + topP, + frequencyPenalty, + presencePenalty, + prompt, + memoryKey, + } = propsValue; + + let messageHistory: any[] | null = []; + // If memory key is set, retrieve messages stored in history + if (memoryKey) { + messageHistory = (await store.get(memoryKey, StoreScope.PROJECT)) ?? []; + } + + // Add user prompt to message history + messageHistory.push({ + role: 'user', + content: prompt, + }); + + // Add system instructions if set by user + const rolesArray = propsValue.roles ? (propsValue.roles as any) : []; + const roles = rolesArray.map((item: any) => { + const rolesEnum = ['system', 'user', 'assistant']; + if (!rolesEnum.includes(item.role)) { + throw new Error( + 'The only available roles are: [system, user, assistant]' + ); + } + + return { + role: item.role, + content: item.content, + }; + }); + + // Send prompt + const completion = await openai.chat.completions.create({ + model: model, + messages: [...roles, ...messageHistory], + temperature: temperature, + top_p: topP, + frequency_penalty: frequencyPenalty, + presence_penalty: presencePenalty, + max_completion_tokens: maxTokens, + }); + + // Add response to message history + messageHistory = [...messageHistory, completion.choices[0].message]; + + // Check message history token size + // System limit is 32K tokens, we can probably make it bigger but this is a safe spot + const tokenLength = await calculateMessagesTokenSize(messageHistory, model); + if (memoryKey) { + // If tokens exceed 90% system limit or 90% of model limit - maxTokens, reduce history token size + if (exceedsHistoryLimit(tokenLength, model, maxTokens)) { + messageHistory = await reduceContextSize( + messageHistory, + model, + maxTokens + ); + } + // Store history + await store.put(memoryKey, messageHistory, StoreScope.PROJECT); + } + + return completion.choices[0].message.content; + }, +}); diff --git a/packages/pieces/community/openai/src/lib/actions/text-to-speech.ts b/packages/pieces/community/openai/src/lib/actions/text-to-speech.ts new file mode 100644 index 0000000..0c0ad1d --- /dev/null +++ b/packages/pieces/community/openai/src/lib/actions/text-to-speech.ts @@ -0,0 +1,106 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import OpenAI from 'openai'; +import { openaiAuth } from '../..'; +import { streamToBuffer } from '../common/common'; + +type Voice = 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer'; +type ResponseFormat = 'mp3' | 'opus' | 'aac' | 'flac' | 'wav' | 'pcm'; +type Model = 'tts-1' | 'tts-1-hd'; + +export const textToSpeech = createAction({ + auth: openaiAuth, + name: 'text_to_speech', + displayName: 'Text-to-Speech', + description: 'Generate an audio recording from text', + props: { + text: Property.LongText({ + displayName: 'Text', + description: 'The text you want to hear.', + required: true, + }), + model: Property.StaticDropdown({ + displayName: 'Model', + required: true, + description: 'The model which will generate the audio.', + defaultValue: 'tts-1', + options: { + disabled: false, + options: [ + { + label: 'tts-1', + value: 'tts-1', + }, + { + label: 'tts-1-hd', + value: 'tts-1-hd', + }, + ], + }, + }), + speed: Property.Number({ + displayName: 'Speed', + description: 'The speed of the audio. Minimum is 0.25 and maximum is 4.00.', + defaultValue: 1.0, + required: false, + }), + voice: Property.StaticDropdown({ + displayName: 'Voice', + description: 'The voice to generate the audio in.', + required: true, + defaultValue: 'alloy', + options: { + disabled: false, + options: [ + { label: 'alloy', value: 'alloy' }, + { label: 'echo', value: 'echo' }, + { label: 'fable', value: 'fable' }, + { label: 'onyx', value: 'onyx' }, + { label: 'nova', value: 'nova' }, + { label: 'shimmer', value: 'shimmer' }, + ], + }, + }), + format: Property.StaticDropdown({ + displayName: 'Output Format', + required: true, + description: 'The format you want the audio file in.', + defaultValue: 'mp3', + options: { + disabled: false, + options: [ + { label: 'mp3', value: 'mp3' }, + { label: 'opus', value: 'opus' }, + { label: 'aac', value: 'aac' }, + { label: 'flac', value: 'flac' }, + ], + }, + }), + fileName: Property.ShortText({ + displayName: 'File Name', + description: 'The name of the output audio file (without extension).', + required: false, + defaultValue: 'audio', + }), + }, + async run({ auth, propsValue, files }) { + const openai = new OpenAI({ + apiKey: auth, + }); + + const { voice, format, model, text, speed, fileName } = propsValue; + + const audio = await openai.audio.speech.create({ + model: model as Model, + input: text, + response_format: format as ResponseFormat, + voice: voice as Voice, + speed: speed, + }); + const result = await streamToBuffer(audio.body); + + return files.write({ + fileName: `${fileName || 'audio'}.${format}`, + data: result as Buffer, + }); + }, +}); diff --git a/packages/pieces/community/openai/src/lib/actions/transcriptions.ts b/packages/pieces/community/openai/src/lib/actions/transcriptions.ts new file mode 100644 index 0000000..7d3f5be --- /dev/null +++ b/packages/pieces/community/openai/src/lib/actions/transcriptions.ts @@ -0,0 +1,70 @@ +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { openaiAuth } from '../..'; +import FormData from 'form-data'; +import mime from 'mime-types'; +import { Languages, baseUrl } from '../common/common'; + +export const transcribeAction = createAction({ + name: 'transcribe', + displayName: 'Transcribe Audio', + description: 'Transcribe audio to text using whisper-1 model', + auth: openaiAuth, + props: { + audio: Property.File({ + displayName: 'Audio', + required: true, + description: 'Audio file to transcribe', + }), + language: Property.StaticDropdown({ + displayName: 'Language of the Audio', + description: 'Language of the audio file the default is en (English).', + required: false, + options: { + options: Languages, + }, + defaultValue: 'en', + }), + }, + run: async (context) => { + const fileData = context.propsValue.audio; + const mimeType = mime.lookup(fileData.extension ? fileData.extension : ''); + let language = context.propsValue.language; + // if language is not in languages list, default to english + if (!Languages.some((l) => l.value === language)) { + language = 'en'; + } + + const form = new FormData(); + form.append('file', fileData.data, { + filename: fileData.filename, + contentType: mimeType as string, + }); + form.append('model', 'whisper-1'); + form.append('language', language); + + const headers = { + Authorization: `Bearer ${context.auth}`, + }; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${baseUrl}/audio/transcriptions`, + body: form, + headers: { + ...form.getHeaders(), + ...headers, + }, + }; + try { + const response = await httpClient.sendRequest(request); + return response.body; + } catch (e) { + throw new Error(`Error while execution:\n${e}`); + } + }, +}); diff --git a/packages/pieces/community/openai/src/lib/actions/translation.ts b/packages/pieces/community/openai/src/lib/actions/translation.ts new file mode 100644 index 0000000..9753d55 --- /dev/null +++ b/packages/pieces/community/openai/src/lib/actions/translation.ts @@ -0,0 +1,54 @@ +import { + HttpRequest, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { openaiAuth } from '../..'; +import FormData from 'form-data'; +import mime from 'mime-types'; +import { baseUrl } from '../common/common'; + +export const translateAction = createAction({ + name: 'translate', + displayName: 'Translate Audio', + description: 'Translate audio to text using whisper-1 model', + auth: openaiAuth, + props: { + audio: Property.File({ + displayName: 'Audio', + required: true, + description: 'Audio file to translate', + }), + }, + run: async (context) => { + const fileData = context.propsValue.audio; + const mimeType = mime.lookup(fileData.extension ? fileData.extension : ''); + const form = new FormData(); + form.append('file', fileData.data, { + filename: fileData.filename, + contentType: mimeType as string, + }); + form.append('model', 'whisper-1'); + + const headers = { + Authorization: `Bearer ${context.auth}`, + }; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${baseUrl}/audio/translations`, + body: form, + headers: { + ...form.getHeaders(), + ...headers, + }, + }; + try { + const response = await httpClient.sendRequest(request); + return response.body; + } catch (e) { + throw new Error(`Error while execution:\n${e}`); + } + }, +}); diff --git a/packages/pieces/community/openai/src/lib/actions/vision-prompt.ts b/packages/pieces/community/openai/src/lib/actions/vision-prompt.ts new file mode 100644 index 0000000..08937a8 --- /dev/null +++ b/packages/pieces/community/openai/src/lib/actions/vision-prompt.ts @@ -0,0 +1,151 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import OpenAI from 'openai'; +import { openaiAuth } from '../..'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const visionPrompt = createAction({ + auth: openaiAuth, + name: 'vision_prompt', + displayName: 'Vision Prompt', + description: 'Ask GPT a question about an image', + props: { + image: Property.File({ + displayName: 'Image', + description: "The image URL or file you want GPT's vision to read.", + required: true, + }), + prompt: Property.LongText({ + displayName: 'Question', + description: 'What do you want ChatGPT to tell you about the image?', + required: true, + }), + detail: Property.Dropdown({ + displayName: 'Detail', + required: false, + description: + 'Control how the model processes the image and generates textual understanding.', + defaultValue: 'auto', + refreshers: [], + options: async () => { + return { + options: [ + { + label: 'low', + value: 'low', + }, + { + label: 'high', + value: 'high', + }, + { + label: 'auto', + value: 'auto', + }, + ], + }; + }, + }), + temperature: Property.Number({ + displayName: 'Temperature', + required: false, + description: + 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.', + defaultValue: 0.9, + }), + maxTokens: Property.Number({ + displayName: 'Maximum Tokens', + required: false, + description: + "The maximum number of tokens to generate. Requests can use up to 2,048 or 4,096 tokens shared between prompt and completion, don't set the value to maximum and leave some tokens for the input. The exact limit varies by model. (One token is roughly 4 characters for normal English text)", + defaultValue: 2048, + }), + topP: Property.Number({ + displayName: 'Top P', + required: false, + description: + 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.', + defaultValue: 1, + }), + frequencyPenalty: Property.Number({ + displayName: 'Frequency penalty', + required: false, + description: + "Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.", + defaultValue: 0, + }), + presencePenalty: Property.Number({ + displayName: 'Presence penalty', + required: false, + description: + "Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the mode's likelihood to talk about new topics.", + defaultValue: 0.6, + }), + roles: Property.Json({ + displayName: 'Roles', + required: false, + description: 'Array of roles to specify more accurate response', + defaultValue: [ + { role: 'system', content: 'You are a helpful assistant.' }, + ], + }), + }, + async run({ auth, propsValue }) { + await propsValidation.validateZod(propsValue, { + temperature: z.number().min(0).max(1), + }); + + const openai = new OpenAI({ + apiKey: auth, + }); + const { temperature, maxTokens, topP, frequencyPenalty, presencePenalty } = + propsValue; + + const rolesArray = propsValue.roles ? (propsValue.roles as any) : []; + const roles = rolesArray.map((item: any) => { + const rolesEnum = ['system', 'user', 'assistant']; + if (!rolesEnum.includes(item.role)) { + throw new Error( + 'The only available roles are: [system, user, assistant]' + ); + } + + return { + role: item.role, + content: item.content, + }; + }); + + const completion = await openai.chat.completions.create({ + model: 'gpt-4o', + messages: [ + ...roles, + { + role: 'user', + content: [ + { + type: 'text', + text: propsValue['prompt'], + }, + { + type: 'image_url', + image_url: { + url: `data:image/${propsValue.image.extension};base64,${propsValue.image.base64}`, + }, + }, + ], + }, + ], + temperature: temperature, + max_tokens: maxTokens, + top_p: topP, + frequency_penalty: frequencyPenalty, + presence_penalty: presencePenalty, + }); + + return completion.choices[0].message.content; + }, +}); diff --git a/packages/pieces/community/openai/src/lib/common/common.ts b/packages/pieces/community/openai/src/lib/common/common.ts new file mode 100644 index 0000000..873d577 --- /dev/null +++ b/packages/pieces/community/openai/src/lib/common/common.ts @@ -0,0 +1,215 @@ +import { encoding_for_model } from 'tiktoken'; + +export const baseUrl = 'https://api.openai.com/v1'; + +export const Languages = [ + { value: 'es', label: 'Spanish' }, + { value: 'it', label: 'Italian' }, + { value: 'en', label: 'English' }, + { value: 'pt', label: 'Portuguese' }, + { value: 'de', label: 'German' }, + { value: 'ja', label: 'Japanese' }, + { value: 'pl', label: 'Polish' }, + { value: 'ar', label: 'Arabic' }, + { value: 'af', label: 'Afrikaans' }, + { value: 'az', label: 'Azerbaijani' }, + { value: 'bg', label: 'Bulgarian' }, + { value: 'bs', label: 'Bosnian' }, + { value: 'ca', label: 'Catalan' }, + { value: 'cs', label: 'Czech' }, + { value: 'da', label: 'Danish' }, + { value: 'el', label: 'Greek' }, + { value: 'et', label: 'Estonian' }, + { value: 'fa', label: 'Persian' }, + { value: 'fi', label: 'Finnish' }, + { value: 'tl', label: 'Tagalog' }, + { value: 'fr', label: 'French' }, + { value: 'gl', label: 'Galician' }, + { value: 'he', label: 'Hebrew' }, + { value: 'hi', label: 'Hindi' }, + { value: 'hr', label: 'Croatian' }, + { value: 'hu', label: 'Hungarian' }, + { value: 'hy', label: 'Armenian' }, + { value: 'id', label: 'Indonesian' }, + { value: 'is', label: 'Icelandic' }, + { value: 'kk', label: 'Kazakh' }, + { value: 'kn', label: 'Kannada' }, + { value: 'ko', label: 'Korean' }, + { value: 'lt', label: 'Lithuanian' }, + { value: 'lv', label: 'Latvian' }, + { value: 'ma', label: 'Maori' }, + { value: 'mk', label: 'Macedonian' }, + { value: 'mr', label: 'Marathi' }, + { value: 'ms', label: 'Malay' }, + { value: 'ne', label: 'Nepali' }, + { value: 'nl', label: 'Dutch' }, + { value: 'no', label: 'Norwegian' }, + { value: 'ro', label: 'Romanian' }, + { value: 'ru', label: 'Russian' }, + { value: 'sk', label: 'Slovak' }, + { value: 'sl', label: 'Slovenian' }, + { value: 'sr', label: 'Serbian' }, + { value: 'sv', label: 'Swedish' }, + { value: 'sw', label: 'Swahili' }, + { value: 'ta', label: 'Tamil' }, + { value: 'th', label: 'Thai' }, + { value: 'tr', label: 'Turkish' }, + { value: 'uk', label: 'Ukrainian' }, + { value: 'ur', label: 'Urdu' }, + { value: 'vi', label: 'Vietnamese' }, + { value: 'zh', label: 'Chinese (Simplified)' }, + { value: 'cy', label: 'Welsh' }, + { value: 'be', label: 'Belarusian' }, +]; + +export const billingIssueMessage = `Error Occurred: 429 \n +1. Ensure that billing is enabled on your OpenAI platform. \n +2. Generate a new API key. \n +3. Attempt the process again. \n +For guidance, visit: https://beta.openai.com/account/billing`; + +export const unauthorizedMessage = `Error Occurred: 401 \n +Ensure that your API key is valid. \n`; + +export const sleep = (ms: number) => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; + +export const streamToBuffer = (stream: any) => { + const chunks: any[] = []; + return new Promise((resolve, reject) => { + stream.on('data', (chunk: any) => chunks.push(Buffer.from(chunk))); + stream.on('error', (err: any) => reject(err)); + stream.on('end', () => resolve(Buffer.concat(chunks))); + }); +}; + +export const calculateTokensFromString = (string: string, model: string) => { + try { + const encoder = encoding_for_model(model as any); + const tokens = encoder.encode(string); + encoder.free(); + + return tokens.length; + } catch (e) { + // Model not supported by tiktoken, every 4 chars is a token + return Math.round(string.length / 4); + } +}; + +export const calculateMessagesTokenSize = async ( + messages: any[], + model: string +) => { + let tokenLength = 0; + await Promise.all( + messages.map((message: any) => { + return new Promise((resolve) => { + tokenLength += calculateTokensFromString(message.content, model); + resolve(tokenLength); + }); + }) + ); + + return tokenLength; +}; + +export const reduceContextSize = async ( + messages: any[], + model: string, + maxTokens: number +) => { + // TODO: Summarize context instead of cutoff + const cutoffSize = Math.round(messages.length * 0.1); + const cutoffMessages = messages.splice(cutoffSize, messages.length - 1); + + if ( + (await calculateMessagesTokenSize(cutoffMessages, model)) > + maxTokens / 1.5 + ) { + reduceContextSize(cutoffMessages, model, maxTokens); + } + + return cutoffMessages; +}; + +export const exceedsHistoryLimit = ( + tokenLength: number, + model: string, + maxTokens: number +) => { + if ( + tokenLength >= tokenLimit / 1.1 || + tokenLength >= (modelTokenLimit(model) - maxTokens) / 1.1 + ) { + return true; + } + + return false; +}; + +export const tokenLimit = 32000; + +export const modelTokenLimit = (model: string) => { + switch (model) { + case 'gpt-4-1106-preview': + return 128000; + case 'gpt-4-vision-preview': + return 128000; + case 'gpt-4': + return 8192; + case 'gpt-4-32k': + return 32768; + case 'gpt-4-0613': + return 8192; + case 'gpt-4-32k-0613': + return 32768; + case 'gpt-4-0314': + return 8192; + case 'gpt-4-32k-0314': + return 32768; + case 'gpt-3.5-turbo-1106': + return 16385; + case 'gpt-3.5-turbo': + return 4096; + case 'gpt-3.5-turbo-16k': + return 16385; + case 'gpt-3.5-turbo-instruct': + return 4096; + case 'gpt-3.5-turbo-0613': + return 4096; + case 'gpt-3.5-turbo-16k-0613': + return 16385; + case 'gpt-3.5-turbo-0301': + return 4096; + case 'text-davinci-003': + return 4096; + case 'text-davinci-002': + return 4096; + case 'code-davinci-002': + return 8001; + case 'text-moderation-latest': + return 32768; + case 'text-moderation-stable': + return 32768; + default: + return 2048; + } +}; + +// List of non-text models to filter out in Ask GPT action +export const notLLMs = [ + 'gpt-4o-realtime-preview-2024-10-01', + 'gpt-4o-realtime-preview', + 'babbage-002', + 'davinci-002', + 'tts-1-hd-1106', + 'whisper-1', + 'canary-whisper', + 'canary-tts', + 'tts-1', + 'tts-1-hd', + 'tts-1-1106', + 'dall-e-3', + 'dall-e-2', +]; diff --git a/packages/pieces/community/openai/tsconfig.json b/packages/pieces/community/openai/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/openai/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/openai/tsconfig.lib.json b/packages/pieces/community/openai/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/openai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/pastebin/.eslintrc.json b/packages/pieces/community/pastebin/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/pastebin/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/pastebin/README.md b/packages/pieces/community/pastebin/README.md new file mode 100644 index 0000000..d36e853 --- /dev/null +++ b/packages/pieces/community/pastebin/README.md @@ -0,0 +1,7 @@ +# pieces-pastebin + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-pastebin` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/pastebin/package.json b/packages/pieces/community/pastebin/package.json new file mode 100644 index 0000000..0b1f1ee --- /dev/null +++ b/packages/pieces/community/pastebin/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-pastebin", + "version": "0.1.3" +} \ No newline at end of file diff --git a/packages/pieces/community/pastebin/project.json b/packages/pieces/community/pastebin/project.json new file mode 100644 index 0000000..116128f --- /dev/null +++ b/packages/pieces/community/pastebin/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-pastebin", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/pastebin/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/pastebin", + "tsConfig": "packages/pieces/community/pastebin/tsconfig.lib.json", + "packageJson": "packages/pieces/community/pastebin/package.json", + "main": "packages/pieces/community/pastebin/src/index.ts", + "assets": [ + "packages/pieces/community/pastebin/*.md", + { + "input": "packages/pieces/community/pastebin/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/pastebin/src/index.ts b/packages/pieces/community/pastebin/src/index.ts new file mode 100644 index 0000000..5cf450a --- /dev/null +++ b/packages/pieces/community/pastebin/src/index.ts @@ -0,0 +1,48 @@ +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import actions from './lib/actions'; + +const markdownDescription = ` +Here are the simple steps to get your credentials: + +1. Make an account, If you don't have one yet. +2. Go to **https://pastebin.com/doc_api**. +3. Copy your unique Developer API Key and paste it. +4. Provide your username and password if you want to create **private pastes** under your account. +`; + +export const pastebinAuth = PieceAuth.CustomAuth({ + required: true, + description: markdownDescription, + props: { + token: PieceAuth.SecretText({ + displayName: 'Developer Key', + required: true, + }), + username: Property.ShortText({ + displayName: 'Username', + required: false, + }), + password: PieceAuth.SecretText({ + displayName: 'Password', + required: false, + }), + }, +}); + +export const pastebin = createPiece({ + displayName: 'Pastebin', + description: 'Simple and secure text sharing', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/pastebin.png', + authors: ["JanHolger","kishanprmr","khaledmashaly","abuaboud"], + categories: [], + auth: pastebinAuth, + actions, + triggers: [], +}); diff --git a/packages/pieces/community/pastebin/src/lib/actions/create-paste.ts b/packages/pieces/community/pastebin/src/lib/actions/create-paste.ts new file mode 100644 index 0000000..4aea68c --- /dev/null +++ b/packages/pieces/community/pastebin/src/lib/actions/create-paste.ts @@ -0,0 +1,70 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { makeClient, pastebinCommon } from '../common'; +import { PasteExpiry, PastePrivacy } from '../common/client'; +import { pastebinAuth } from '../..'; + +export default createAction({ + auth: pastebinAuth, + name: 'create_paste', + displayName: 'Create Paste', + description: 'Creates a new paste', + props: { + content: Property.LongText({ + displayName: 'Content', + required: true, + }), + format: pastebinCommon.paste_format(false), + name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + privacy: Property.StaticDropdown({ + displayName: 'Privacy', + required: false, + options: { + options: [ + { label: 'Public', value: PastePrivacy.PUBLIC }, + { label: 'Unlisted', value: PastePrivacy.UNLISTED }, + { label: 'Private', value: PastePrivacy.PRIVATE }, + ], + }, + }), + expiry: Property.StaticDropdown({ + displayName: 'Expiry', + required: false, + options: { + options: [ + { label: 'Never', value: PasteExpiry.NEVER }, + { label: '10 Minutes', value: PasteExpiry.TEN_MINUTES }, + { label: '1 Hour', value: PasteExpiry.ONE_HOUR }, + { label: '1 Day', value: PasteExpiry.ONE_DAY }, + { label: '1 Week', value: PasteExpiry.ONE_WEEK }, + { label: '2 Weeks', value: PasteExpiry.TWO_WEEKS }, + { label: '1 Month', value: PasteExpiry.ONE_MONTH }, + { label: '6 Months', value: PasteExpiry.SIX_MONTHS }, + { label: '1 Year', value: PasteExpiry.ONE_YEAR }, + ], + }, + }), + folder: Property.ShortText({ + displayName: 'Folder', + required: false, + }), + }, + async run(context) { + const client = await makeClient(context.auth); + const url = await client.createPaste({ + paste_code: context.propsValue.content, + paste_format: context.propsValue.format, + paste_name: context.propsValue.name, + paste_private: context.propsValue.privacy, + paste_expiry_date: context.propsValue.expiry, + folder_key: context.propsValue.folder, + }); + const id = url.split('/').reduce((c, v) => v); + return { + id, + url, + }; + }, +}); diff --git a/packages/pieces/community/pastebin/src/lib/actions/get-paste-content.ts b/packages/pieces/community/pastebin/src/lib/actions/get-paste-content.ts new file mode 100644 index 0000000..b2a2cf3 --- /dev/null +++ b/packages/pieces/community/pastebin/src/lib/actions/get-paste-content.ts @@ -0,0 +1,23 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { makeClient, pastebinCommon } from '../common'; +import { pastebinAuth } from '../..'; + +export default createAction({ + auth: pastebinAuth, + name: 'get_paste_content', + displayName: 'Get Paste Content', + description: 'Retrieves the content of a paste', + props: { + paste_id: Property.ShortText({ + displayName: 'Paste ID', + required: true, + }), + }, + async run(context) { + const client = await makeClient(context.auth); + const content = await client.getPasteContent(context.propsValue.paste_id); + return { + content, + }; + }, +}); diff --git a/packages/pieces/community/pastebin/src/lib/actions/index.ts b/packages/pieces/community/pastebin/src/lib/actions/index.ts new file mode 100644 index 0000000..fdc7444 --- /dev/null +++ b/packages/pieces/community/pastebin/src/lib/actions/index.ts @@ -0,0 +1,4 @@ +import createPaste from './create-paste'; +import getPasteContent from './get-paste-content'; + +export default [createPaste, getPasteContent]; diff --git a/packages/pieces/community/pastebin/src/lib/common/client.ts b/packages/pieces/community/pastebin/src/lib/common/client.ts new file mode 100644 index 0000000..d6610b5 --- /dev/null +++ b/packages/pieces/community/pastebin/src/lib/common/client.ts @@ -0,0 +1,83 @@ +import axios from 'axios'; + +export enum PastePrivacy { + PUBLIC = '0', + UNLISTED = '1', + PRIVATE = '2', +} + +export enum PasteExpiry { + NEVER = 'N', + TEN_MINUTES = '10M', + ONE_HOUR = '1H', + ONE_DAY = '1D', + ONE_WEEK = '1W', + TWO_WEEKS = '2W', + ONE_MONTH = '1M', + SIX_MONTHS = '6M', + ONE_YEAR = '1Y', +} + +export interface PasteCreateRequest { + paste_code: string; + paste_private?: PastePrivacy; + paste_name?: string; + paste_expiry_date?: string; + paste_format?: string; + folder_key?: string; +} + +export class PastebinClient { + private user_key?: string; + + constructor(private token: string) {} + + setUserKey(user_key: string) { + this.user_key = user_key; + } + + async makeRequest( + script: string, + option: string, + body: Record + ): Promise { + const req = new URLSearchParams({ + api_option: option, + api_dev_key: this.token, + }); + if (this.user_key) { + req.append('api_user_key', this.user_key); + } + Object.keys(body) + .filter((k) => body[k] !== undefined && body[k] !== null) + .forEach((k) => req.append('api_' + k, body[k])); + const res = await axios.post('https://pastebin.com/api/' + script, req, { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded', + }, + }); + return res.data; + } + + async login(username: string, password: string): Promise { + return await this.makeRequest('api_login.php', 'login', { + user_name: username, + user_password: password, + }); + } + + async createPaste(request: PasteCreateRequest): Promise { + return await this.makeRequest('api_post.php', 'paste', request); + } + + async getPasteContent(id: string): Promise { + if (this.user_key) { + return await this.makeRequest('api_raw.php', 'show_paste', { + paste_key: id, + }); + } else { + return (await axios.get('https://pastebin.com/raw/' + id)).data; + } + } +} diff --git a/packages/pieces/community/pastebin/src/lib/common/index.ts b/packages/pieces/community/pastebin/src/lib/common/index.ts new file mode 100644 index 0000000..a8d00dd --- /dev/null +++ b/packages/pieces/community/pastebin/src/lib/common/index.ts @@ -0,0 +1,292 @@ +import { PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { PastebinClient } from './client'; +import { pastebinAuth } from '../..'; + +export const pastebinCommon = { + paste_format: (required = true) => + Property.StaticDropdown({ + displayName: 'Format', + required, + options: { + options: [ + { label: '4CS4cs', value: '4cs' }, + { label: '6502 ACME Cross Asse...', value: '6502acme' }, + { label: '6502 Kick Assembler', value: '6502kickass' }, + { label: '6502 TASM/64TASS', value: '6502tasm' }, + { label: 'ABAP', value: 'abap' }, + { label: 'ActionScript', value: 'actionscript' }, + { label: 'ActionScript 3', value: 'actionscript3' }, + { label: 'Ada', value: 'ada' }, + { label: 'AIMMS', value: 'aimms' }, + { label: 'ALGOL 68', value: 'algol68' }, + { label: 'Apache Log', value: 'apache' }, + { label: 'AppleScript', value: 'applescript' }, + { label: 'APT Sources', value: 'apt_sources' }, + { label: 'Arduino', value: 'arduino' }, + { label: 'ARM', value: 'arm' }, + { label: 'ASM (NASM)', value: 'asm' }, + { label: 'ASP', value: 'asp' }, + { label: 'Asymptote', value: 'asymptote' }, + { label: 'autoconf', value: 'autoconf' }, + { label: 'Autohotkey', value: 'autohotkey' }, + { label: 'AutoIt', value: 'autoit' }, + { label: 'Avisynth', value: 'avisynth' }, + { label: 'Awk', value: 'awk' }, + { label: 'BASCOM AVR', value: 'bascomavr' }, + { label: 'Bash', value: 'bash' }, + { label: 'Basic4GL', value: 'basic4gl' }, + { label: 'Batch', value: 'dos' }, + { label: 'BibTeX', value: 'bibtex' }, + { label: 'Blitz3D', value: 'b3d' }, + { label: 'Blitz Basic', value: 'blitzbasic' }, + { label: 'BlitzMax', value: 'bmx' }, + { label: 'BNF', value: 'bnf' }, + { label: 'BOO', value: 'boo' }, + { label: 'BrainFuck', value: 'bf' }, + { label: 'C', value: 'c' }, + { label: 'C#', value: 'csharp' }, + { label: 'C (WinAPI)', value: 'c_winapi' }, + { label: 'C++', value: 'cpp' }, + { label: 'C++ (WinAPI)', value: 'cpp-winapi' }, + { label: 'C++ (with Qt extensi...', value: 'cpp-qt' }, + { label: 'C: Loadrunner', value: 'c_loadrunner' }, + { label: 'CAD DCL', value: 'caddcl' }, + { label: 'CAD Lisp', value: 'cadlisp' }, + { label: 'Ceylon', value: 'ceylon' }, + { label: 'CFDG', value: 'cfdg' }, + { label: 'C for Macs', value: 'c_mac' }, + { label: 'ChaiScript', value: 'chaiscript' }, + { label: 'Chapel', value: 'chapel' }, + { label: 'C Intermediate Langu...', value: 'cil' }, + { label: 'Clojure', value: 'clojure' }, + { label: 'Clone C', value: 'klonec' }, + { label: 'Clone C++', value: 'klonecpp' }, + { label: 'CMake', value: 'cmake' }, + { label: 'COBOL', value: 'cobol' }, + { label: 'CoffeeScript', value: 'coffeescript' }, + { label: 'ColdFusion', value: 'cfm' }, + { label: 'CSS', value: 'css' }, + { label: 'Cuesheet', value: 'cuesheet' }, + { label: 'D', value: 'd' }, + { label: 'Dart', value: 'dart' }, + { label: 'DCL', value: 'dcl' }, + { label: 'DCPU-16', value: 'dcpu16' }, + { label: 'DCS', value: 'dcs' }, + { label: 'Delphi', value: 'delphi' }, + { label: 'Delphi Prism (Oxygen...', value: 'oxygene' }, + { label: 'Diff', value: 'diff' }, + { label: 'DIV', value: 'div' }, + { label: 'DOT', value: 'dot' }, + { label: 'E', value: 'e' }, + { label: 'Easytrieve', value: 'ezt' }, + { label: 'ECMAScript', value: 'ecmascript' }, + { label: 'Eiffel', value: 'eiffel' }, + { label: 'Email', value: 'email' }, + { label: 'EPC', value: 'epc' }, + { label: 'Erlang', value: 'erlang' }, + { label: 'Euphoria', value: 'euphoria' }, + { label: 'F#', value: 'fsharp' }, + { label: 'Falcon', value: 'falcon' }, + { label: 'Filemaker', value: 'filemaker' }, + { label: 'FO Language', value: 'fo' }, + { label: 'Formula One', value: 'f1' }, + { label: 'Fortran', value: 'fortran' }, + { label: 'FreeBasic', value: 'freebasic' }, + { label: 'FreeSWITCH', value: 'freeswitch' }, + { label: 'GAMBAS', value: 'gambas' }, + { label: 'Game Maker', value: 'gml' }, + { label: 'GDB', value: 'gdb' }, + { label: 'GDScript', value: 'gdscript' }, + { label: 'Genero', value: 'genero' }, + { label: 'Genie', value: 'genie' }, + { label: 'GetText', value: 'gettext' }, + { label: 'Go', value: 'go' }, + { label: 'Godot GLSL', value: 'godot-glsl' }, + { label: 'Groovy', value: 'groovy' }, + { label: 'GwBasic', value: 'gwbasic' }, + { label: 'Haskell', value: 'haskell' }, + { label: 'Haxe', value: 'haxe' }, + { label: 'HicEst', value: 'hicest' }, + { label: 'HQ9 Plus', value: 'hq9plus' }, + { label: 'HTML', value: 'html4strict' }, + { label: 'HTML 5', value: 'html5' }, + { label: 'Icon', value: 'icon' }, + { label: 'IDL', value: 'idl' }, + { label: 'INI file', value: 'ini' }, + { label: 'Inno Script', value: 'inno' }, + { label: 'INTERCAL', value: 'intercal' }, + { label: 'IO', value: 'io' }, + { label: 'ISPF Panel Definition...', value: 'ispfpanel' }, + { label: 'J', value: 'j' }, + { label: 'Java', value: 'java' }, + { label: 'Java 5', value: 'java5' }, + { label: 'JavaScript', value: 'javascript' }, + { label: 'JCL', value: 'jcl' }, + { label: 'jQuery', value: 'jquery' }, + { label: 'JSON', value: 'json' }, + { label: 'Julia', value: 'julia' }, + { label: 'KiXtart', value: 'kixtart' }, + { label: 'Kotlin', value: 'kotlin' }, + { label: 'KSP (Kontakt Script)', value: 'ksp' }, + { label: 'Latex', value: 'latex' }, + { label: 'LDIF', value: 'ldif' }, + { label: 'Liberty BASIC', value: 'lb' }, + { label: 'Linden Scripting', value: 'lsl2' }, + { label: 'Lisp', value: 'lisp' }, + { label: 'LLVM', value: 'llvm' }, + { label: 'Loco Basic', value: 'locobasic' }, + { label: 'Logtalk', value: 'logtalk' }, + { label: 'LOL Code', value: 'lolcode' }, + { label: 'Lotus Formulas', value: 'lotusformulas' }, + { label: 'Lotus Script', value: 'lotusscript' }, + { label: 'LScript', value: 'lscript' }, + { label: 'Lua', value: 'lua' }, + { label: 'M68000 Assembler', value: 'm68k' }, + { label: 'MagikSF', value: 'magiksf' }, + { label: 'Make', value: 'make' }, + { label: 'MapBasic', value: 'mapbasic' }, + { label: 'Markdown', value: 'markdown' }, + { label: 'MatLab', value: 'matlab' }, + { label: 'Mercury', value: 'mercury' }, + { label: 'MetaPost', value: 'metapost' }, + { label: 'mIRC', value: 'mirc' }, + { label: 'MIX Assembler', value: 'mmix' }, + { label: 'MK-61/52', value: 'mk-61' }, + { label: 'Modula 2', value: 'modula2' }, + { label: 'Modula 3', value: 'modula3' }, + { label: 'Motorola 68000 HiSof...', value: '68000devpac' }, + { label: 'MPASM', value: 'mpasm' }, + { label: 'MXML', value: 'mxml' }, + { label: 'MySQL', value: 'mysql' }, + { label: 'Nagios', value: 'nagios' }, + { label: 'NetRexx', value: 'netrexx' }, + { label: 'newLISP', value: 'newlisp' }, + { label: 'Nginx', value: 'nginx' }, + { label: 'Nim', value: 'nim' }, + { label: 'NullSoft Installer', value: 'nsis' }, + { label: 'Oberon 2', value: 'oberon2' }, + { label: 'Objeck Programming L...', value: 'objeck' }, + { label: 'Objective C', value: 'objc' }, + { label: 'OCaml', value: 'ocaml' }, + { label: 'OCaml Brief', value: 'ocaml-brief' }, + { label: 'Octave', value: 'octave' }, + { label: 'OpenBSD PACKET FILTE...', value: 'pf' }, + { label: 'OpenGL Shading', value: 'glsl' }, + { label: 'Open Object Rexx', value: 'oorexx' }, + { label: 'Openoffice BASIC', value: 'oobas' }, + { label: 'Oracle 8', value: 'oracle8' }, + { label: 'Oracle 11', value: 'oracle11' }, + { label: 'Oz', value: 'oz' }, + { label: 'ParaSail', value: 'parasail' }, + { label: 'PARI/GP', value: 'parigp' }, + { label: 'Pascal', value: 'pascal' }, + { label: 'Pawn', value: 'pawn' }, + { label: 'PCRE', value: 'pcre' }, + { label: 'Per', value: 'per' }, + { label: 'Perl', value: 'perl' }, + { label: 'Perl 6', value: 'perl6' }, + { label: 'Phix', value: 'phix' }, + { label: 'PHP', value: 'php' }, + { label: 'PHP Brief', value: 'php-brief' }, + { label: 'Pic 16', value: 'pic16' }, + { label: 'Pike', value: 'pike' }, + { label: 'Pixel Bender', value: 'pixelbender' }, + { label: 'PL/I', value: 'pli' }, + { label: 'PL/SQL', value: 'plsql' }, + { label: 'PostgreSQL', value: 'postgresql' }, + { label: 'PostScript', value: 'postscript' }, + { label: 'POV-Ray', value: 'povray' }, + { label: 'PowerBuilder', value: 'powerbuilder' }, + { label: 'PowerShell', value: 'powershell' }, + { label: 'ProFTPd', value: 'proftpd' }, + { label: 'Progress', value: 'progress' }, + { label: 'Prolog', value: 'prolog' }, + { label: 'Properties', value: 'properties' }, + { label: 'ProvideX', value: 'providex' }, + { label: 'Puppet', value: 'puppet' }, + { label: 'PureBasic', value: 'purebasic' }, + { label: 'PyCon', value: 'pycon' }, + { label: 'Python', value: 'python' }, + { label: 'Python for S60', value: 'pys60' }, + { label: 'q/kdb+', value: 'q' }, + { label: 'QBasic', value: 'qbasic' }, + { label: 'QML', value: 'qml' }, + { label: 'R', value: 'rsplus' }, + { label: 'Racket', value: 'racket' }, + { label: 'Rails', value: 'rails' }, + { label: 'RBScript', value: 'rbs' }, + { label: 'REBOL', value: 'rebol' }, + { label: 'REG', value: 'reg' }, + { label: 'Rexx', value: 'rexx' }, + { label: 'Robots', value: 'robots' }, + { label: 'Roff Manpage', value: 'roff' }, + { label: 'RPM Spec', value: 'rpmspec' }, + { label: 'Ruby', value: 'ruby' }, + { label: 'Ruby Gnuplot', value: 'gnuplot' }, + { label: 'Rust', value: 'rust' }, + { label: 'SAS', value: 'sas' }, + { label: 'Scala', value: 'scala' }, + { label: 'Scheme', value: 'scheme' }, + { label: 'Scilab', value: 'scilab' }, + { label: 'SCL', value: 'scl' }, + { label: 'SdlBasic', value: 'sdlbasic' }, + { label: 'Smalltalk', value: 'smalltalk' }, + { label: 'Smarty', value: 'smarty' }, + { label: 'SPARK', value: 'spark' }, + { label: 'SPARQL', value: 'sparql' }, + { label: 'SQF', value: 'sqf' }, + { label: 'SQL', value: 'sql' }, + { label: 'SSH Config', value: 'sshconfig' }, + { label: 'StandardML', value: 'standardml' }, + { label: 'StoneScript', value: 'stonescript' }, + { label: 'SuperCollider', value: 'sclang' }, + { label: 'Swift', value: 'swift' }, + { label: 'SystemVerilog', value: 'systemverilog' }, + { label: 'T-SQL', value: 'tsql' }, + { label: 'TCL', value: 'tcl' }, + { label: 'Tera Term', value: 'teraterm' }, + { label: 'TeXgraph', value: 'texgraph' }, + { label: 'thinBasic', value: 'thinbasic' }, + { label: 'TypeScript', value: 'typescript' }, + { label: 'TypoScript', value: 'typoscript' }, + { label: 'Unicon', value: 'unicon' }, + { label: 'UnrealScript', value: 'uscript' }, + { label: 'UPC', value: 'upc' }, + { label: 'Urbi', value: 'urbi' }, + { label: 'Vala', value: 'vala' }, + { label: 'VB.NET', value: 'vbnet' }, + { label: 'VBScript', value: 'vbscript' }, + { label: 'Vedit', value: 'vedit' }, + { label: 'VeriLog', value: 'verilog' }, + { label: 'VHDL', value: 'vhdl' }, + { label: 'VIM', value: 'vim' }, + { label: 'VisualBasic', value: 'vb' }, + { label: 'VisualFoxPro', value: 'visualfoxpro' }, + { label: 'Visual Pro Log', value: 'visualprolog' }, + { label: 'WhiteSpace', value: 'whitespace' }, + { label: 'WHOIS', value: 'whois' }, + { label: 'Winbatch', value: 'winbatch' }, + { label: 'XBasic', value: 'xbasic' }, + { label: 'XML', value: 'xml' }, + { label: 'Xojo', value: 'xojo' }, + { label: 'Xorg Config', value: 'xorg_conf' }, + { label: 'XPP', value: 'xpp' }, + { label: 'YAML', value: 'yaml' }, + { label: 'YARA', value: 'yara' }, + { label: 'Z80 Assembler', value: 'z80' }, + { label: 'ZXBasic', value: 'zxbasic' }, + ], + }, + }), +}; + +export async function makeClient( + auth: PiecePropValueSchema +): Promise { + const client = new PastebinClient(auth.token); + if (auth.username && auth.password) { + const userKey = await client.login(auth.username, auth.password); + client.setUserKey(userKey); + } + return client; +} diff --git a/packages/pieces/community/pastebin/tsconfig.json b/packages/pieces/community/pastebin/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/pastebin/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/pastebin/tsconfig.lib.json b/packages/pieces/community/pastebin/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/pastebin/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/pastefy/.eslintrc.json b/packages/pieces/community/pastefy/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/pastefy/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/pastefy/README.md b/packages/pieces/community/pastefy/README.md new file mode 100644 index 0000000..ac67ee1 --- /dev/null +++ b/packages/pieces/community/pastefy/README.md @@ -0,0 +1,7 @@ +# pieces-pastefy + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-pastefy` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/pastefy/package.json b/packages/pieces/community/pastefy/package.json new file mode 100644 index 0000000..b59fab0 --- /dev/null +++ b/packages/pieces/community/pastefy/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-pastefy", + "version": "0.1.4" +} \ No newline at end of file diff --git a/packages/pieces/community/pastefy/project.json b/packages/pieces/community/pastefy/project.json new file mode 100644 index 0000000..576b2fe --- /dev/null +++ b/packages/pieces/community/pastefy/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-pastefy", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/pastefy/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/pastefy", + "tsConfig": "packages/pieces/community/pastefy/tsconfig.lib.json", + "packageJson": "packages/pieces/community/pastefy/package.json", + "main": "packages/pieces/community/pastefy/src/index.ts", + "assets": [ + "packages/pieces/community/pastefy/*.md", + { + "input": "packages/pieces/community/pastefy/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/pastefy/src/index.ts b/packages/pieces/community/pastefy/src/index.ts new file mode 100644 index 0000000..b938833 --- /dev/null +++ b/packages/pieces/community/pastefy/src/index.ts @@ -0,0 +1,57 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import actions from './lib/actions'; +import triggers from './lib/triggers'; + +const markdown = ` +Create an account and obtain the API Key from Pastefy. +`; + +export const pastefyAuth = PieceAuth.CustomAuth({ + description: markdown, + required: true, + props: { + instance_url: Property.ShortText({ + displayName: 'Pastefy Instance URL', + required: false, + defaultValue: 'https://pastefy.app', + }), + token: PieceAuth.SecretText({ + displayName: 'API-Token', + required: false, + }), + }, +}); + +export const pastefy = createPiece({ + displayName: 'Pastefy', + description: 'Sharing code snippets platform', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/pastefy.png', + categories: [], + authors: ["JanHolger","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + auth: pastefyAuth, + actions: [ + ...actions, + createCustomApiCallAction({ + baseUrl: (auth) => { + const typedAuth = auth as { instance_url: string }; + return typedAuth.instance_url + '/api/v2'; + }, + auth: pastefyAuth, + authMapping: async (auth) => { + const typedAuth = auth as { token?: string }; + return { + Authorization: typedAuth.token + ? `Bearer ${typedAuth.token}` + : undefined, + }; + }, + }), + ], + triggers: triggers, +}); diff --git a/packages/pieces/community/pastefy/src/lib/actions/create-folder.ts b/packages/pieces/community/pastefy/src/lib/actions/create-folder.ts new file mode 100644 index 0000000..a3b977e --- /dev/null +++ b/packages/pieces/community/pastefy/src/lib/actions/create-folder.ts @@ -0,0 +1,25 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { makeClient, pastefyCommon } from '../common'; +import { pastefyAuth } from '../..'; + +export default createAction({ + auth: pastefyAuth, + name: 'create_folder', + displayName: 'Create Folder', + description: 'Creates a new folder', + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + parent_id: pastefyCommon.folder_id(false, 'Parent Folder'), + }, + async run(context) { + const client = makeClient(context.auth, context.propsValue); + const res = await client.createFolder({ + name: context.propsValue.name as string, + parent: context.propsValue.parent_id, + }); + return res.folder; + }, +}); diff --git a/packages/pieces/community/pastefy/src/lib/actions/create-paste.ts b/packages/pieces/community/pastefy/src/lib/actions/create-paste.ts new file mode 100644 index 0000000..56a7af1 --- /dev/null +++ b/packages/pieces/community/pastefy/src/lib/actions/create-paste.ts @@ -0,0 +1,54 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { formatDate, makeClient, pastefyCommon } from '../common'; +import { pastefyAuth } from '../..'; +import CryptoJS from 'crypto-js'; + +export default createAction({ + auth: pastefyAuth, + + name: 'create_paste', + displayName: 'Create Paste', + description: 'Creates a new paste', + props: { + content: Property.LongText({ + displayName: 'Content', + required: true, + }), + title: Property.ShortText({ + displayName: 'Title', + required: false, + }), + password: Property.ShortText({ + displayName: 'Encryption Password', + description: 'Encrypts the paste with this password', + required: false, + }), + folder_id: pastefyCommon.folder_id(false), + visibility: pastefyCommon.visibility(false), + expiry: Property.DateTime({ + displayName: 'Expiry Date', + required: false, + }), + }, + async run(context) { + const client = makeClient(context.auth, context.propsValue); + const password = context.propsValue.password; + let content = context.propsValue.content; + let title = context.propsValue.title; + if (password) { + content = CryptoJS.AES.encrypt(content, password).toString(); + if (title) { + title = CryptoJS.AES.encrypt(title, password).toString(); + } + } + const res = await client.createPaste({ + title, + content, + encrypted: !!password, + folder: context.propsValue.folder_id, + visibility: context.propsValue.visibility, + expire_at: formatDate(context.propsValue.expiry), + }); + return res.paste; + }, +}); diff --git a/packages/pieces/community/pastefy/src/lib/actions/delete-folder.ts b/packages/pieces/community/pastefy/src/lib/actions/delete-folder.ts new file mode 100644 index 0000000..4582b71 --- /dev/null +++ b/packages/pieces/community/pastefy/src/lib/actions/delete-folder.ts @@ -0,0 +1,20 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { makeClient, pastefyCommon } from '../common'; +import { pastefyAuth } from '../..'; + +export default createAction({ + auth: pastefyAuth, + name: 'delete_folder', + displayName: 'Delete Folder', + description: 'Deletes a folder', + props: { + folder_id: pastefyCommon.folder_id(true), + }, + async run(context) { + const client = makeClient(context.auth, context.propsValue); + const res = await client.deleteFolder( + context.propsValue.folder_id as string + ); + return res; + }, +}); diff --git a/packages/pieces/community/pastefy/src/lib/actions/delete-paste.ts b/packages/pieces/community/pastefy/src/lib/actions/delete-paste.ts new file mode 100644 index 0000000..46d2faa --- /dev/null +++ b/packages/pieces/community/pastefy/src/lib/actions/delete-paste.ts @@ -0,0 +1,21 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { makeClient } from '../common'; +import { pastefyAuth } from '../..'; + +export default createAction({ + auth: pastefyAuth, + name: 'delete_paste', + displayName: 'Delete Paste', + description: 'Deletes a paste', + props: { + paste_id: Property.ShortText({ + displayName: 'Paste ID', + required: true, + }), + }, + async run(context) { + const client = makeClient(context.auth, context.propsValue); + const res = await client.deletePaste(context.propsValue.paste_id); + return res; + }, +}); diff --git a/packages/pieces/community/pastefy/src/lib/actions/edit-paste.ts b/packages/pieces/community/pastefy/src/lib/actions/edit-paste.ts new file mode 100644 index 0000000..a990383 --- /dev/null +++ b/packages/pieces/community/pastefy/src/lib/actions/edit-paste.ts @@ -0,0 +1,59 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { formatDate, makeClient, pastefyCommon } from '../common'; +import { pastefyAuth } from '../..'; +import CryptoJS from 'crypto-js'; + +export default createAction({ + auth: pastefyAuth, + name: 'edit_paste', + displayName: 'Edit Paste', + description: 'Edits an existing private paste', + props: { + paste_id: Property.ShortText({ + displayName: 'Paste ID', + required: true, + }), + content: Property.LongText({ + displayName: 'Content', + required: false, + }), + title: Property.ShortText({ + displayName: 'Title', + required: false, + }), + password: Property.ShortText({ + displayName: 'Encryption Password', + description: 'Encrypts the paste with this password', + required: false, + }), + folder_id: pastefyCommon.folder_id(false), + visibility: pastefyCommon.visibility(false), + expiry: Property.DateTime({ + displayName: 'Expiry Date', + required: false, + }), + }, + async run(context) { + const client = makeClient(context.auth, context.propsValue); + const password = context.propsValue.password; + let content = context.propsValue.content; + let title = context.propsValue.title; + if (password) { + if (content) { + content = CryptoJS.AES.encrypt(content, password).toString(); + } + if (title) { + title = CryptoJS.AES.encrypt(title, password).toString(); + } + } + const res = await client.editPaste(context.propsValue.paste_id, { + title, + content, + encrypted: !!password, + folder: context.propsValue.folder_id, + visibility: context.propsValue.visibility, + expire_at: formatDate(context.propsValue.expiry), + }); + return res; + }, +}); diff --git a/packages/pieces/community/pastefy/src/lib/actions/get-folder-hierarchy.ts b/packages/pieces/community/pastefy/src/lib/actions/get-folder-hierarchy.ts new file mode 100644 index 0000000..8b7e61d --- /dev/null +++ b/packages/pieces/community/pastefy/src/lib/actions/get-folder-hierarchy.ts @@ -0,0 +1,20 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { makeClient, pastefyCommon } from '../common'; +import { pastefyAuth } from '../..'; + +export default createAction({ + auth: pastefyAuth, + name: 'get_folder_hierarchy', + displayName: 'Get Folder Hierarchy', + description: 'Retrieves a hierarchy of all folders', + props: { + parent_id: pastefyCommon.folder_id(false, 'Start Folder'), + }, + async run(context) { + const client = makeClient(context.auth, context.propsValue); + const hierarchy = await client.getFolderHierarchy( + context.propsValue.parent_id + ); + return hierarchy; + }, +}); diff --git a/packages/pieces/community/pastefy/src/lib/actions/get-folder.ts b/packages/pieces/community/pastefy/src/lib/actions/get-folder.ts new file mode 100644 index 0000000..6fceb35 --- /dev/null +++ b/packages/pieces/community/pastefy/src/lib/actions/get-folder.ts @@ -0,0 +1,20 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { makeClient, pastefyCommon } from '../common'; +import { pastefyAuth } from '../..'; + +export default createAction({ + auth: pastefyAuth, + name: 'get_folder', + displayName: 'Get Folder', + description: 'Retrieves information about a folder', + props: { + folder_id: pastefyCommon.folder_id(true), + }, + async run(context) { + const client = makeClient(context.auth, context.propsValue); + const folder = await client.getFolder( + context.propsValue.folder_id as string + ); + return folder; + }, +}); diff --git a/packages/pieces/community/pastefy/src/lib/actions/get-paste.ts b/packages/pieces/community/pastefy/src/lib/actions/get-paste.ts new file mode 100644 index 0000000..113d101 --- /dev/null +++ b/packages/pieces/community/pastefy/src/lib/actions/get-paste.ts @@ -0,0 +1,38 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { makeClient } from '../common'; +import { pastefyAuth } from '../..'; +import CryptoJS from 'crypto-js'; + +export default createAction({ + auth: pastefyAuth, + name: 'get_paste', + displayName: 'Get Paste', + description: 'Retrieves a paste', + props: { + paste_id: Property.ShortText({ + displayName: 'Paste ID', + required: true, + }), + password: Property.ShortText({ + displayName: 'Encryption Password', + description: 'Decrypts the paste with this password', + required: false, + }), + }, + async run(context) { + const client = makeClient(context.auth, context.propsValue); + const password = context.propsValue.password; + const paste = await client.getPaste(context.propsValue.paste_id); + if (paste.encrypted && password) { + paste.content = CryptoJS.AES.decrypt(paste.content, password).toString( + CryptoJS.enc.Utf8 + ); + if (paste.title) { + paste.title = CryptoJS.AES.decrypt(paste.title, password).toString( + CryptoJS.enc.Utf8 + ); + } + } + return paste; + }, +}); diff --git a/packages/pieces/community/pastefy/src/lib/actions/index.ts b/packages/pieces/community/pastefy/src/lib/actions/index.ts new file mode 100644 index 0000000..27f498f --- /dev/null +++ b/packages/pieces/community/pastefy/src/lib/actions/index.ts @@ -0,0 +1,19 @@ +import createFolder from './create-folder'; +import createPaste from './create-paste'; +import deleteFolder from './delete-folder'; +import deletePaste from './delete-paste'; +import editPaste from './edit-paste'; +import getFolder from './get-folder'; +import getFolderHierarchy from './get-folder-hierarchy'; +import getPaste from './get-paste'; + +export default [ + createPaste, + getPaste, + editPaste, + deletePaste, + createFolder, + getFolder, + getFolderHierarchy, + deleteFolder, +]; diff --git a/packages/pieces/community/pastefy/src/lib/common/client.ts b/packages/pieces/community/pastefy/src/lib/common/client.ts new file mode 100644 index 0000000..5fe344c --- /dev/null +++ b/packages/pieces/community/pastefy/src/lib/common/client.ts @@ -0,0 +1,156 @@ +import { + Authentication, + AuthenticationType, + HttpMessageBody, + HttpMethod, + QueryParams, + httpClient, +} from '@activepieces/pieces-common'; +import { + Folder, + FolderCreateRequest, + FolderCreateResponse, + FolderGetRequest, + FolderHierarchy, + FolderListRequest, +} from './models/folder'; +import { ActionResponse, prepareQueryRequest } from './models/common'; +import { + Paste, + PasteCreateRequest, + PasteCreateResponse, + PasteEditRequest, + PasteListRequest, + PasteShareRequest, +} from './models/paste'; + +function ensureSuccessfulResponse(res: T): T { + if (!res.success) { + throw 'Request failed'; + } + return res; +} + +export class PastefyClient { + constructor( + private apiKey?: string, + private instanceUrl = 'https://pastefy.app' + ) {} + + async makeRequest( + method: HttpMethod, + url: string, + query?: QueryParams, + body?: object + ): Promise { + const authentication: Authentication | undefined = this.apiKey + ? { + type: AuthenticationType.BEARER_TOKEN, + token: this.apiKey, + } + : undefined; + const res = await httpClient.sendRequest({ + method, + url: this.instanceUrl + '/api/v2' + url, + queryParams: query, + body, + authentication, + }); + return res.body; + } + + async createFolder( + request: FolderCreateRequest + ): Promise { + return ensureSuccessfulResponse( + await this.makeRequest(HttpMethod.POST, '/folder', undefined, request) + ); + } + + async listFolders(request: FolderListRequest): Promise { + return await this.makeRequest( + HttpMethod.GET, + '/folder', + prepareQueryRequest(request) + ); + } + + async getFolder(id: string, request?: FolderGetRequest): Promise { + return await this.makeRequest( + HttpMethod.GET, + '/folder/' + id, + prepareQueryRequest(request) + ); + } + + async getFolderHierarchy(parentId?: string): Promise { + const folders = await this.listFolders({ + page_size: 99999, + filter: { + parent: parentId || 'null', + }, + }); + const hierarchies: FolderHierarchy[] = []; + for (const folder of folders) { + hierarchies.push({ + id: folder.id, + name: folder.name, + children: await this.getFolderHierarchy(folder.id), + }); + } + return hierarchies; + } + + async deleteFolder(id: string): Promise { + return ensureSuccessfulResponse( + await this.makeRequest(HttpMethod.DELETE, '/folder/' + id) + ); + } + + async createPaste(request: PasteCreateRequest): Promise { + return ensureSuccessfulResponse( + await this.makeRequest(HttpMethod.POST, '/paste', undefined, request) + ); + } + + async listPastes(request: PasteListRequest): Promise { + return await this.makeRequest( + HttpMethod.GET, + '/paste', + prepareQueryRequest(request) + ); + } + + async getPaste(id: string): Promise { + return await this.makeRequest(HttpMethod.GET, '/paste/' + id); + } + + async editPaste( + id: string, + request: PasteEditRequest + ): Promise { + return ensureSuccessfulResponse( + await this.makeRequest(HttpMethod.PUT, '/paste/' + id, undefined, request) + ); + } + + async deletePaste(id: string): Promise { + return ensureSuccessfulResponse( + await this.makeRequest(HttpMethod.DELETE, '/paste/' + id) + ); + } + + async sharePaste( + id: string, + request: PasteShareRequest + ): Promise { + return ensureSuccessfulResponse( + await this.makeRequest( + HttpMethod.POST, + '/paste/' + id + '/friend', + undefined, + request + ) + ); + } +} diff --git a/packages/pieces/community/pastefy/src/lib/common/index.ts b/packages/pieces/community/pastefy/src/lib/common/index.ts new file mode 100644 index 0000000..ed8f9d0 --- /dev/null +++ b/packages/pieces/community/pastefy/src/lib/common/index.ts @@ -0,0 +1,90 @@ +import { + PiecePropValueSchema, + PiecePropertyMap, + Property, + StaticPropsValue, +} from '@activepieces/pieces-framework'; +import { PastefyClient } from './client'; +import { FolderHierarchy } from './models/folder'; +import { PasteVisibility } from './models/paste'; +import { pastefyAuth } from '../..'; + +interface FlatFolder { + id: string; + name: string; +} + +function flattenFolderHierarchy(hierarchy: FolderHierarchy[]): FlatFolder[] { + const folders: FlatFolder[] = []; + for (const h of hierarchy) { + folders.push({ id: h.id, name: h.name }); + flattenFolderHierarchy(h.children).forEach((e) => { + folders.push({ + id: e.id, + name: h.name + ' / ' + e.name, + }); + }); + } + return folders; +} + +export function formatDate(date?: string): string | undefined { + if (!date) return date; + return date + .replace('T', ' ') + .replace('Z', '') + .replace(/\.[0-9]{3}/, ''); +} + +export const pastefyCommon = { + folder_id: (required = true, displayName = 'Folder') => + Property.Dropdown({ + description: 'A folder', + displayName: displayName, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'setup authentication first', + options: [], + }; + } + const client = makeClient( + auth as PiecePropValueSchema, + { auth } + ); + const folders = await client.getFolderHierarchy(); + + return { + disabled: false, + options: flattenFolderHierarchy(folders).map((folder) => { + return { + label: folder.name, + value: folder.id, + }; + }), + }; + }, + }), + visibility: (required = true) => + Property.StaticDropdown({ + displayName: 'Visibility', + required, + options: { + options: [ + { label: 'Public', value: PasteVisibility.PUBLIC }, + { label: 'Unlisted', value: PasteVisibility.UNLISTED }, + { label: 'Private', value: PasteVisibility.PRIVATE }, + ], + }, + }), +}; + +export function makeClient( + auth: PiecePropValueSchema, + propsValue: StaticPropsValue +): PastefyClient { + return new PastefyClient(auth.token || undefined, propsValue.instance_url); +} diff --git a/packages/pieces/community/pastefy/src/lib/common/models/common.ts b/packages/pieces/community/pastefy/src/lib/common/models/common.ts new file mode 100644 index 0000000..99e5847 --- /dev/null +++ b/packages/pieces/community/pastefy/src/lib/common/models/common.ts @@ -0,0 +1,48 @@ +import { QueryParams } from '@activepieces/pieces-common'; + +export interface ActionResponse { + success: boolean; +} + +export interface ListRequest { + page?: number; + page_size?: number; + search?: string; + filter?: Record; +} + +function emptyValueFilter( + accessor: (key: string) => any +): (key: string) => boolean { + return (key: string) => { + const val = accessor(key); + return ( + val !== null && + val !== undefined && + (typeof val != 'string' || val.length > 0) + ); + }; +} + +export function prepareQueryRequest( + request?: ListRequest | Record +): QueryParams { + const params: QueryParams = {}; + if (!request) return params; + const requestObj = request as Record; + Object.keys(request) + .filter((k) => k != 'filter') + .filter(emptyValueFilter((k) => requestObj[k])) + .forEach((k: string) => { + params[k] = (request as Record)[k].toString(); + }); + if (request.filter) { + const filter = request.filter; // For some reason required to pass the unidentified check + Object.keys(request.filter) + .filter(emptyValueFilter((k) => filter[k])) + .forEach((k) => { + params['filter[' + k + ']'] = filter[k].toString(); + }); + } + return params; +} diff --git a/packages/pieces/community/pastefy/src/lib/common/models/folder.ts b/packages/pieces/community/pastefy/src/lib/common/models/folder.ts new file mode 100644 index 0000000..0c248c0 --- /dev/null +++ b/packages/pieces/community/pastefy/src/lib/common/models/folder.ts @@ -0,0 +1,43 @@ +import { ActionResponse, ListRequest } from './common'; +import { Paste } from './paste'; + +export interface Folder { + exists: boolean; + id: string; + name: string; + user_id: string; + children?: Folder[]; + pastes?: Paste[]; + created: string; +} + +export interface FolderCreateRequest { + name: string; + parent?: string; +} + +export interface FolderCreateResponse extends ActionResponse { + folder: Folder; +} + +export interface FolderEditRequest { + name?: string; +} + +export interface FolderEditResponse extends ActionResponse { + folder: Folder; +} + +export interface FolderGetRequest { + hide_children?: string; +} + +export interface FolderListRequest extends ListRequest { + user_id?: string; +} + +export interface FolderHierarchy { + id: string; + name: string; + children: FolderHierarchy[]; +} diff --git a/packages/pieces/community/pastefy/src/lib/common/models/paste.ts b/packages/pieces/community/pastefy/src/lib/common/models/paste.ts new file mode 100644 index 0000000..4231fbe --- /dev/null +++ b/packages/pieces/community/pastefy/src/lib/common/models/paste.ts @@ -0,0 +1,75 @@ +import { ActionResponse } from './common'; + +export enum PasteType { + PASTE = 'PASTE', + MULTI_PASTE = 'MULTI_PASTE', +} + +export enum PasteVisibility { + PUBLIC = 'PUBLIC', + UNLISTED = 'UNLISTED', + PRIVATE = 'PRIVATE', +} + +export interface Paste { + exists: boolean; + id: string; + content: string; + title: string; + encrypted: boolean; + folder: string; + user_id?: string; + visibility: PasteVisibility; + forked_from?: string; + raw_url: string; + type: PasteType; + created_at: string; + expire_at?: string; +} + +export interface PasteCreateRequest { + title?: string; + content: string; + encrypted?: boolean; + folder?: string; + expire_at?: string; + forked_from?: string; + visibility?: PasteVisibility; + type?: PasteType; +} + +export interface PasteCreateResponse extends ActionResponse { + paste: Paste; +} + +export interface PasteEditRequest { + title?: string; + content?: string; + encrypted?: boolean; + folder?: string; + type?: PasteType; + visibility?: PasteVisibility; + expire_at?: string; +} + +export interface PasteEditResponse extends ActionResponse { + paste: Paste; +} + +export interface PasteShareRequest { + friend: string; +} + +export interface PasteListRequest { + page?: number; + page_size?: number; + search?: string; + shorten_content?: boolean; +} + +export interface PasteListTrendingRequest { + page?: number; + page_size?: number; + trending?: boolean; + shorten_content?: boolean; +} diff --git a/packages/pieces/community/pastefy/src/lib/common/models/user.ts b/packages/pieces/community/pastefy/src/lib/common/models/user.ts new file mode 100644 index 0000000..e82c751 --- /dev/null +++ b/packages/pieces/community/pastefy/src/lib/common/models/user.ts @@ -0,0 +1,21 @@ +export enum UserType { + USER, + ADMIN, + BLOCKED, + AWAITING_ACCESS, +} + +export interface User { + name: string; + avatar?: string; + displayName: string; +} + +export interface DetailedUser extends User { + id: string; + color: string; + profile_picture?: string; + logged_in: true; + auth_type: string; + type: UserType; +} diff --git a/packages/pieces/community/pastefy/src/lib/triggers/index.ts b/packages/pieces/community/pastefy/src/lib/triggers/index.ts new file mode 100644 index 0000000..42f4bad --- /dev/null +++ b/packages/pieces/community/pastefy/src/lib/triggers/index.ts @@ -0,0 +1,3 @@ +import pasteChanged from './paste-changed'; + +export default [pasteChanged]; diff --git a/packages/pieces/community/pastefy/src/lib/triggers/paste-changed.ts b/packages/pieces/community/pastefy/src/lib/triggers/paste-changed.ts new file mode 100644 index 0000000..1b7caa8 --- /dev/null +++ b/packages/pieces/community/pastefy/src/lib/triggers/paste-changed.ts @@ -0,0 +1,72 @@ +import { + createTrigger, + Property, + StoreScope, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { makeClient } from '../common'; +import { createHash } from 'crypto'; +import { pastefyAuth } from '../..'; + +export default createTrigger({ + auth: pastefyAuth, + name: 'paste_changed', + displayName: 'Paste Changed', + description: 'Triggers when the content (or title) of the paste changes', + type: TriggerStrategy.POLLING, + props: { + paste_id: Property.ShortText({ + displayName: 'Paste ID', + required: true, + }), + include_title: Property.Checkbox({ + displayName: 'Include Title', + required: false, + }), + }, + sampleData: {}, + onEnable: async (context) => { + const client = makeClient(context.auth, context.propsValue); + const paste = await client.getPaste(context.propsValue.paste_id); + const hash = createHash('md5') + .update( + paste.content + (context.propsValue.include_title ? paste.title : '') + ) + .digest('hex'); + await context.store.put( + 'paste_changed_trigger_hash', + hash, + StoreScope.FLOW + ); + }, + onDisable: async (context) => { + await context.store.delete('paste_changed_trigger_hash', StoreScope.FLOW); + }, + run: async (context) => { + const oldHash = await context.store.get( + 'paste_changed_trigger_hash', + StoreScope.FLOW + ); + const client = makeClient(context.auth, context.propsValue); + const paste = await client.getPaste(context.propsValue.paste_id); + const newHash = createHash('md5') + .update( + paste.content + (context.propsValue.include_title ? paste.title : '') + ) + .digest('hex'); + if (oldHash != newHash) { + await context.store.put( + 'paste_changed_trigger_hash', + newHash, + StoreScope.FLOW + ); + return [paste]; + } + return []; + }, + test: async (context) => { + const client = makeClient(context.auth, context.propsValue); + const paste = await client.getPaste(context.propsValue.paste_id); + return [paste]; + }, +}); diff --git a/packages/pieces/community/pastefy/tsconfig.json b/packages/pieces/community/pastefy/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/pastefy/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/pastefy/tsconfig.lib.json b/packages/pieces/community/pastefy/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/pastefy/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/pdf-co/.eslintrc.json b/packages/pieces/community/pdf-co/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/pdf-co/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/pdf-co/README.md b/packages/pieces/community/pdf-co/README.md new file mode 100644 index 0000000..705b775 --- /dev/null +++ b/packages/pieces/community/pdf-co/README.md @@ -0,0 +1,7 @@ +# pieces-pdf-co + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-pdf-co` to build the library. diff --git a/packages/pieces/community/pdf-co/package.json b/packages/pieces/community/pdf-co/package.json new file mode 100644 index 0000000..19d7f6f --- /dev/null +++ b/packages/pieces/community/pdf-co/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-pdf-co", + "version": "0.0.1" +} diff --git a/packages/pieces/community/pdf-co/project.json b/packages/pieces/community/pdf-co/project.json new file mode 100644 index 0000000..298f7e7 --- /dev/null +++ b/packages/pieces/community/pdf-co/project.json @@ -0,0 +1,45 @@ +{ + "name": "pieces-pdf-co", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/pdf-co/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/pdf-co", + "tsConfig": "packages/pieces/community/pdf-co/tsconfig.lib.json", + "packageJson": "packages/pieces/community/pdf-co/package.json", + "main": "packages/pieces/community/pdf-co/src/index.ts", + "assets": [ + "packages/pieces/community/pdf-co/*.md" + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/pdf-co/src/index.ts b/packages/pieces/community/pdf-co/src/index.ts new file mode 100644 index 0000000..27f5a6f --- /dev/null +++ b/packages/pieces/community/pdf-co/src/index.ts @@ -0,0 +1,38 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { + searchAndReplaceText, + addTextToPdf, + addImageToPdf, + convertHtmlToPdf, + extractTextFromPdf, + convertPdfToStructuredFormat, + extractTablesFromPdf, + addBarcodeToPdf, +} from './lib/actions'; +import { PieceCategory } from '@activepieces/shared'; + +export const pdfCoAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: `To get your PDF.co API key please [click here to create your account](https://app.pdf.co/).`, + required: true, +}); + +export const pdfCo = createPiece({ + displayName: 'PDF.co', + description: 'Automate PDF conversion, editing, extraction', + categories: [PieceCategory.PRODUCTIVITY, PieceCategory.CONTENT_AND_FILES], + logoUrl: 'https://cdn.activepieces.com/pieces/pdf-co.png', + auth: pdfCoAuth, + authors: ['onyedikachi-david', 'kishanprmr'], + actions: [ + addBarcodeToPdf, + addImageToPdf, + addTextToPdf, + convertHtmlToPdf, + convertPdfToStructuredFormat, + extractTablesFromPdf, + extractTextFromPdf, + searchAndReplaceText, + ], + triggers: [], +}); diff --git a/packages/pieces/community/pdf-co/src/lib/actions/add-barcode-to-pdf.ts b/packages/pieces/community/pdf-co/src/lib/actions/add-barcode-to-pdf.ts new file mode 100644 index 0000000..701b094 --- /dev/null +++ b/packages/pieces/community/pdf-co/src/lib/actions/add-barcode-to-pdf.ts @@ -0,0 +1,223 @@ +import { Property, DropdownOption, createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod, HttpError } from '@activepieces/pieces-common'; +import { + PdfCoSuccessResponse, + PdfCoErrorResponse, + PdfCoImageAnnotation, + PdfCoAddImagesRequestBody, +} from '../common/types'; +import { pdfCoAuth } from '../../index'; +import { BASE_URL, commonProps } from '../common/props'; + +// Interface for /barcode/generate request +interface BarcodeGenerateRequestBody { + value: string; + type?: string; + async: boolean; + inline: boolean; // Must be false to get URL + name?: string; + decorationImage?: string; + profiles?: Record; +} + +// Interface for /barcode/generate success response (when inline=false) +interface BarcodeGenerateSuccessResponse { + url: string; // URL to the generated barcode image + error: false; + status: number; + name: string; + duration: number; + remainingCredits: number; + credits: number; +} + +// Supported Barcode Types +const barcodeTypes: DropdownOption[] = [ + { label: 'QR Code (Default)', value: 'QRCode' }, + { label: 'DataMatrix', value: 'DataMatrix' }, + { label: 'Code 128', value: 'Code128' }, + { label: 'Code 39', value: 'Code39' }, + { label: 'PDF417', value: 'PDF417' }, + { label: 'EAN-13', value: 'EAN13' }, + { label: 'UPC-A', value: 'UPCA' }, +]; + +export const addBarcodeToPdf = createAction({ + name: 'add_barcode_to_pdf', + displayName: 'Add Barcode to PDF', + description: 'Generate a barcode image and add it to a specific location on a PDF.', + auth: pdfCoAuth, + props: { + sourcePdfUrl: Property.ShortText({ + displayName: 'Source PDF URL', + description: 'URL of the PDF file to add the barcode to.', + required: true, + }), + barcodeValue: Property.ShortText({ + displayName: 'Barcode Value', + description: 'The text or data to encode in the barcode.', + required: true, + }), + barcodeType: Property.StaticDropdown({ + displayName: 'Barcode Type', + description: 'Select the type of barcode to generate.', + required: true, + options: { disabled: false, options: barcodeTypes, placeholder: 'Select Barcode Type' }, + }), + x: Property.Number({ + displayName: 'X Coordinate', + description: 'X coordinate (from top-left corner) to place the barcode.', + required: true, + }), + y: Property.Number({ + displayName: 'Y Coordinate', + description: 'Y coordinate (from top-left corner) to place the barcode.', + required: true, + }), + width: Property.Number({ + displayName: 'Width (optional)', + description: + 'Optional width for the barcode image on the PDF (in points). Aspect ratio is kept by default.', + required: false, + }), + height: Property.Number({ + displayName: 'Height (optional)', + description: + 'Optional height for the barcode image on the PDF (in points). Aspect ratio is kept by default.', + required: false, + }), + pages: Property.ShortText({ + displayName: 'Pages', + description: + 'Comma-separated page numbers or ranges to add the barcode (e.g., "0,2,5-10"). Leave empty for all pages.', + required: false, + }), + ...commonProps, + }, + async run(context) { + const { auth, propsValue } = context; + const { + sourcePdfUrl, + barcodeValue, + barcodeType, + x, + y, + width, + height, + pages, + fileName, + pdfPassword, + httpPassword, + httpUsername, + expiration, + } = propsValue; + + let barcodeImageUrl = ''; + + // --- Step 1: Generate Barcode --- + const generateBarcodeBody: BarcodeGenerateRequestBody = { + value: barcodeValue, + type: barcodeType, + async: false, + inline: false, // Need the URL + }; + + try { + const generateResponse = await httpClient.sendRequest< + BarcodeGenerateSuccessResponse | PdfCoErrorResponse + >({ + method: HttpMethod.POST, + url: `${BASE_URL}/barcode/generate`, + headers: { + 'x-api-key': auth as string, + 'Content-Type': 'application/json', + }, + body: generateBarcodeBody, + }); + + if (generateResponse.body.error) { + const errorBody = generateResponse.body as PdfCoErrorResponse; + throw new Error( + `PDF.co Barcode Generation Error: Status ${errorBody.status}. ${ + errorBody.message || 'Unknown error.' + }`, + ); + } + + barcodeImageUrl = (generateResponse.body as BarcodeGenerateSuccessResponse).url; + if (!barcodeImageUrl) { + throw new Error('Failed to get barcode image URL from PDF.co response.'); + } + } catch (error) { + if (error instanceof HttpError) { + const responseBody = error.response?.body as PdfCoErrorResponse | undefined; + throw new Error( + `HTTP Error generating barcode: ${error.message}. ${ + responseBody?.message + ? 'Server message: ' + responseBody.message + : 'Raw response: ' + JSON.stringify(responseBody) + }`, + ); + } + throw error; // Re-throw other errors + } + + // --- Step 2: Add Barcode Image to PDF --- + const imageAnnotation: PdfCoImageAnnotation = { + url: barcodeImageUrl, + x: x, + y: y, + width, + height, + pages, + }; + + const addImageBody: PdfCoAddImagesRequestBody = { + url: sourcePdfUrl, + images: [imageAnnotation], + async: false, + inline: false, // Get final PDF URL + name: fileName, + expiration, + httppassword: httpPassword, + httpusername: httpUsername, + password: pdfPassword, + }; + + try { + const addResponse = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/pdf/edit/add`, + headers: { + 'x-api-key': auth as string, + 'Content-Type': 'application/json', + }, + body: addImageBody, + }); + + if (addResponse.body.error) { + const errorBody = addResponse.body as PdfCoErrorResponse; + throw new Error( + `PDF.co Add Image Error: Status ${errorBody.status}. ${ + errorBody.message || 'Unknown error.' + }`, + ); + } + + // Return the successful response containing the final PDF URL + return addResponse.body as PdfCoSuccessResponse; + } catch (error) { + if (error instanceof HttpError) { + const responseBody = error.response?.body as PdfCoErrorResponse | undefined; + throw new Error( + `HTTP Error adding barcode image to PDF: ${error.message}. ${ + responseBody?.message + ? 'Server message: ' + responseBody.message + : 'Raw response: ' + JSON.stringify(responseBody) + }`, + ); + } + throw error; // Re-throw other errors + } + }, +}); diff --git a/packages/pieces/community/pdf-co/src/lib/actions/add-image-to-pdf.ts b/packages/pieces/community/pdf-co/src/lib/actions/add-image-to-pdf.ts new file mode 100644 index 0000000..abd1561 --- /dev/null +++ b/packages/pieces/community/pdf-co/src/lib/actions/add-image-to-pdf.ts @@ -0,0 +1,142 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod, HttpError } from '@activepieces/pieces-common'; +import { + PdfCoSuccessResponse, + PdfCoErrorResponse, + PdfCoImageAnnotation, + PdfCoAddImagesRequestBody, +} from '../common/types'; +import { pdfCoAuth } from '../../index'; +import { BASE_URL, commonProps } from '../common/props'; + +export const addImageToPdf = createAction({ + name: 'add_image_to_pdf', + displayName: 'Add Image to PDF', + description: 'Add image to a PDF document.', + auth: pdfCoAuth, + props: { + url: Property.ShortText({ + displayName: 'Source PDF URL', + description: 'URL of the PDF file to modify.', + required: true, + }), + imageUrl: Property.ShortText({ + displayName: 'Image URL', + required: true, + }), + xCoordinate: Property.Number({ + displayName: 'X Coordinate', + description: 'X coordinate (from top-left corner) to place the image.', + required: true, + }), + yCoordinate: Property.Number({ + displayName: 'Y Coordinate', + required: true, + description: 'Y coordinate (from top-left corner) to place the image.', + }), + width: Property.Number({ + displayName: 'Width', + description: + 'Optional width for the image on the PDF (in points). Aspect ratio is kept by default.', + required: false, + }), + height: Property.Number({ + displayName: 'Height', + description: + 'Optional height for the image on the PDF (in points). Aspect ratio is kept by default.', + required: false, + }), + pages: Property.ShortText({ + displayName: 'Target Pages', + description: + 'Specify page indices as comma-separated values or ranges to process (e.g. "0, 1, 2-" or "1, 2, 3-7").', + required: false, + }), + ...commonProps, + }, + async run(context) { + const { auth, propsValue } = context; + const { + url, + imageUrl, + fileName, + pdfPassword, + xCoordinate, + pages, + yCoordinate, + width, + height, + expiration, + httpPassword, + httpUsername, + } = propsValue; + + const imageAnnotationPayload: PdfCoImageAnnotation = { + url: imageUrl, + x: xCoordinate, + y: yCoordinate, + pages, + height, + width, + }; + + const requestBody: PdfCoAddImagesRequestBody = { + url: url, + images: [imageAnnotationPayload], + async: false, + name: fileName, + expiration, + httppassword: httpPassword, + httpusername: httpUsername, + password: pdfPassword, + inline: false, + }; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/pdf/edit/add`, + headers: { + 'x-api-key': auth as string, + 'Content-Type': 'application/json', + }, + body: requestBody, + }); + + console.log(JSON.stringify(response, null, 2)); + + if (response.body.error) { + const errorBody = response.body as PdfCoErrorResponse; + let errorMessage = `PDF.co API Error (Add Image): Status ${errorBody.status}.`; + if (errorBody.message) { + errorMessage += ` Message: ${errorBody.message}.`; + } else { + errorMessage += ` An unspecified error occurred.`; + } + errorMessage += ` Raw response: ${JSON.stringify(errorBody)}`; + throw new Error(errorMessage); + } + + const successBody = response.body as PdfCoSuccessResponse; + return { + outputUrl: successBody.url, + pageCount: successBody.pageCount, + outputName: successBody.name, + creditsUsed: successBody.credits, + remainingCredits: successBody.remainingCredits, + }; + } catch (error) { + if (error instanceof HttpError) { + const responseBody = error.response?.body as PdfCoErrorResponse | undefined; + let detailedMessage = `HTTP Error calling PDF.co API (Add Image): ${error.message}.`; + if (responseBody && responseBody.message) { + detailedMessage += ` Server message: ${responseBody.message}.`; + } else if (responseBody) { + detailedMessage += ` Server response: ${JSON.stringify(responseBody)}.`; + } + throw new Error(detailedMessage); + } + throw error; + } + }, +}); diff --git a/packages/pieces/community/pdf-co/src/lib/actions/add-text-to-pdf.ts b/packages/pieces/community/pdf-co/src/lib/actions/add-text-to-pdf.ts new file mode 100644 index 0000000..8c05d7e --- /dev/null +++ b/packages/pieces/community/pdf-co/src/lib/actions/add-text-to-pdf.ts @@ -0,0 +1,218 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod, HttpError } from '@activepieces/pieces-common'; +import { PdfCoSuccessResponse, PdfCoErrorResponse } from '../common/types'; +import { pdfCoAuth } from '../../index'; +import { BASE_URL, commonProps } from '../common/props'; + +// Interface for a single text annotation object based on PDF.co docs +interface PdfCoTextAnnotation { + text: string; + x: number; + y: number; + pages?: string; + size?: number; + fontName?: string; + color?: string; + link?: string; + width?: number; + height?: number; + fontBold?: boolean; + fontUnderline?: boolean; + fontStrikeout?: boolean; + alignment?: string; + type?: 'text' | 'textField' | 'TextFieldMultiline' | 'checkbox'; + id?: string; + transparent?: boolean; + RotationAngle?: number; +} + +// Interface for the main request body for /pdf/edit/add +interface PdfCoAddAnnotationsRequestBody { + url: string; + annotations: PdfCoTextAnnotation[]; + async: boolean; + name?: string; + password?: string; + expiration?: number; + inline?: boolean; + profiles?: Record; // JSON object for profiles + httpusername?: string; + httppassword?: string; +} + +export const addTextToPdf = createAction({ + name: 'add_text_to_pdf', + displayName: 'Add Text to PDF', + description: 'Adds text to PDF.', + auth: pdfCoAuth, + props: { + url: Property.ShortText({ + displayName: 'Source PDF URL', + description: 'URL of the PDF file to modify.', + required: true, + }), + text: Property.LongText({ + displayName: 'Text to Add', + required: true, + }), + xCoordinate: Property.Number({ + displayName: 'X Coordinate', + required: true, + }), + yCoordinate: Property.Number({ + displayName: 'Y Coordinate', + required: true, + }), + fontSize: Property.Number({ + displayName: 'Font Size', + required: false, + }), + color: Property.ShortText({ + displayName: 'Color', + defaultValue: '#000000', + required: false, + }), + fontBold: Property.Checkbox({ + displayName: 'Bold Font ?', + required: false, + }), + fontStrikeout: Property.Checkbox({ + displayName: 'Stikeout Font ?', + required: false, + }), + fontUnderline: Property.Checkbox({ + displayName: 'Underline Font ?', + required: false, + }), + fontName: Property.ShortText({ + displayName: 'Font Name', + defaultValue: 'Arial', + required: false, + }), + pages: Property.ShortText({ + displayName: 'Target Pages', + description: + 'Specify page indices as comma-separated values or ranges to process (e.g. "0, 1, 2-" or "1, 2, 3-7").', + required: false, + }), + textBoxHeight: Property.Number({ + displayName: 'Text Box Height', + required: false, + }), + textBoxWidth: Property.Number({ + displayName: 'Text Box Width', + required: false, + }), + textBoxAlignment: Property.StaticDropdown({ + displayName: 'Text Box Alignment', + required: false, + defaultValue: 'left', + options: { + disabled: false, + options: [ + { label: 'left', value: 'left' }, + { label: 'right', value: 'right' }, + { label: 'center', value: 'center' }, + ], + }, + }), + ...commonProps, + }, + async run(context) { + const { auth, propsValue } = context; + const { + url, + xCoordinate, + yCoordinate, + fontSize, + fontName, + fontBold, + fontStrikeout, + fontUnderline, + color, + pages, + textBoxAlignment, + textBoxHeight, + textBoxWidth, + text, + fileName, + pdfPassword, + httpPassword, + httpUsername, + expiration, + } = propsValue; + + const textAnnotationPayload: PdfCoTextAnnotation = { + x: xCoordinate, + y: yCoordinate, + text, + type: 'text', + color, + pages, + width: textBoxWidth, + height: textBoxHeight, + alignment: textBoxAlignment, + size: fontSize, + fontName, + fontBold, + fontStrikeout, + fontUnderline, + }; + + const requestBody: PdfCoAddAnnotationsRequestBody = { + url: url, + annotations: [textAnnotationPayload], + async: false, + name: fileName, + expiration, + httppassword: httpPassword, + httpusername: httpUsername, + password: pdfPassword, + }; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/pdf/edit/add`, + headers: { + 'x-api-key': auth, + 'Content-Type': 'application/json', + }, + body: requestBody, + }); + + if (response.body.error) { + const errorBody = response.body as PdfCoErrorResponse; + let errorMessage = `PDF.co API Error (Add Text): Status ${errorBody.status}.`; + if (errorBody.message) { + errorMessage += ` Message: ${errorBody.message}.`; + } else { + errorMessage += ` An unspecified error occurred.`; + } + errorMessage += ` Raw response: ${JSON.stringify(errorBody)}`; + throw new Error(errorMessage); + } + + const successBody = response.body as PdfCoSuccessResponse; + return { + outputUrl: successBody.url, + pageCount: successBody.pageCount, + outputName: successBody.name, + creditsUsed: successBody.credits, + remainingCredits: successBody.remainingCredits, + }; + } catch (error) { + if (error instanceof HttpError) { + const responseBody = error.response?.body as PdfCoErrorResponse | undefined; + let detailedMessage = `HTTP Error calling PDF.co API (Add Text): ${error.message}.`; + if (responseBody && responseBody.message) { + detailedMessage += ` Server message: ${responseBody.message}.`; + } else if (responseBody) { + detailedMessage += ` Server response: ${JSON.stringify(responseBody)}.`; + } + throw new Error(detailedMessage); + } + throw error; + } + }, +}); diff --git a/packages/pieces/community/pdf-co/src/lib/actions/convert-html-to-pdf.ts b/packages/pieces/community/pdf-co/src/lib/actions/convert-html-to-pdf.ts new file mode 100644 index 0000000..babb13a --- /dev/null +++ b/packages/pieces/community/pdf-co/src/lib/actions/convert-html-to-pdf.ts @@ -0,0 +1,191 @@ +import { Property, createAction } from "@activepieces/pieces-framework"; +import { httpClient, HttpMethod, HttpError } from "@activepieces/pieces-common"; +import { PdfCoSuccessResponse, PdfCoErrorResponse } from "../common/types"; +import { pdfCoAuth } from "../../index"; +import { BASE_URL } from "../common/props"; +interface PdfConvertFromHtmlRequestBody { + html: string; + async: boolean; + name?: string; + margins?: string; // e.g., "10px", "5mm 5mm 5mm 5mm" + paperSize?: string; // "A4", "Letter", "200mm 300mm", etc. + orientation?: 'Portrait' | 'Landscape'; + printBackground?: boolean; + mediaType?: 'print' | 'screen' | 'none'; + header?: string; // HTML content + footer?: string; // HTML content + expiration?: number; + profiles?: Record; + DoNotWaitFullLoad?:boolean +} + +export const convertHtmlToPdf = createAction({ + name: 'convert_html_to_pdf', + displayName: 'Convert HTML to PDF', + description: 'Convert HTML code into a downloadable PDF document.', + auth: pdfCoAuth, + props: { + html: Property.LongText({ + displayName: 'HTML Content', + description: 'The HTML code to convert to PDF.', + required: true, + }), + name: Property.ShortText({ + displayName: 'Output File Name', + description: 'Desired name for the output PDF file (e.g., "result.pdf").', + required: false, + }), + margins: Property.ShortText({ + displayName: 'Margins', + description: 'CSS style margins (e.g., "10px", "5mm 5mm 5mm 5mm" for top, right, bottom, left).', + required: false, + }), + paperSize: Property.StaticDropdown({ + displayName: 'Paper Size', + description: 'Select a paper size. For custom sizes, input the value directly (e.g., \'200mm 300mm\') if your desired size isn\'t listed. Refer to PDF.co docs.', + required: false, + options: { + disabled: false, + placeholder: 'Select paper size or input custom', + options: [ + { label: "A4 (Default)", value: "A4" }, + { label: "Letter", value: "Letter" }, + { label: "Legal", value: "Legal" }, + { label: "Tabloid", value: "Tabloid" }, + { label: "Ledger", value: "Ledger" }, + { label: "A0", value: "A0" }, + { label: "A1", value: "A1" }, + { label: "A2", value: "A2" }, + { label: "A3", value: "A3" }, + { label: "A5", value: "A5" }, + { label: "A6", value: "A6" }, + ] + } + }), + orientation: Property.StaticDropdown ({ + displayName: 'Orientation', + description: 'Set page orientation.', + required: false, + options: { + disabled: false, + placeholder: 'Portrait (Default)', + options: [ + { label: "Portrait (Default)", value: "Portrait" }, + { label: "Landscape", value: "Landscape" }, + ] , + } + }), + printBackground: Property.Checkbox({ + displayName: 'Print Background ?', + description: 'Set to true to print background graphics and colors (default is true).', + required: false, + defaultValue: true, + }), + mediaType: Property.StaticDropdown({ + displayName: 'Media Type', + description: 'CSS media type to emulate.', + required: false, + options: { + disabled: false, + placeholder: 'print (Default)', + options: [ + { label: "print (Default)", value: "print" }, + { label: "screen", value: "screen" }, + { label: "none", value: "none" }, + ], + } + }), + header: Property.LongText({ + displayName: 'Header HTML', + description: 'HTML content for the page header.', + required: false, + }), + footer: Property.LongText({ + displayName: 'Footer HTML', + description: 'HTML content for the page footer.', + required: false, + }), + doNotWaitFullLoad:Property.Checkbox({ + displayName:'Do not wait till full page load ?', + required:false + }), + expiration: Property.Number({ + displayName: 'Output Link Expiration (minutes)', + description: 'Set the expiration time for the output link in minutes (default is 60).', + required: false, + }), + profiles: Property.Json({ + displayName: 'Profiles', + description: 'JSON object for additional configurations.', + required: false, + }) + }, + async run(context) { + const { auth, propsValue } = context; + + const requestBody: PdfConvertFromHtmlRequestBody = { + html: propsValue.html, + async: false, + DoNotWaitFullLoad:propsValue.doNotWaitFullLoad + }; + + if (propsValue.name !== undefined && propsValue.name !== '') requestBody.name = propsValue.name; + if (propsValue.margins !== undefined && propsValue.margins !== '') requestBody.margins = propsValue.margins; + if (propsValue.paperSize !== undefined) requestBody.paperSize = propsValue.paperSize; + if (propsValue.orientation !== undefined) requestBody.orientation = propsValue.orientation as 'Portrait' | 'Landscape'; + if (propsValue.printBackground !== undefined) requestBody.printBackground = propsValue.printBackground; + if (propsValue.mediaType !== undefined) requestBody.mediaType = propsValue.mediaType as 'print' | 'screen' | 'none'; + if (propsValue.header !== undefined && propsValue.header !== '') requestBody.header = propsValue.header; + if (propsValue.footer !== undefined && propsValue.footer !== '') requestBody.footer = propsValue.footer; + if (propsValue.expiration !== undefined) requestBody.expiration = propsValue.expiration; + if (propsValue.profiles !== undefined && typeof propsValue.profiles === 'object' && propsValue.profiles !== null) { + requestBody.profiles = propsValue.profiles as Record; + } + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/pdf/convert/from/html`, + headers: { + 'x-api-key': auth as string, + 'Content-Type': 'application/json', + }, + body: requestBody, + }); + + if (response.body.error) { + const errorBody = response.body as PdfCoErrorResponse; + let errorMessage = `PDF.co API Error (Convert HTML to PDF): Status ${errorBody.status}.`; + if (errorBody.message) { + errorMessage += ` Message: ${errorBody.message}.`; + } else { + errorMessage += ` An unspecified error occurred.`; + } + errorMessage += ` Raw response: ${JSON.stringify(errorBody)}`; + throw new Error(errorMessage); + } + + const successBody = response.body as PdfCoSuccessResponse; + return { + outputUrl: successBody.url, + pageCount: successBody.pageCount, + outputName: successBody.name, + creditsUsed: successBody.credits, + remainingCredits: successBody.remainingCredits, + }; + + } catch (error) { + if (error instanceof HttpError) { + const responseBody = error.response?.body as (PdfCoErrorResponse | undefined); + let detailedMessage = `HTTP Error calling PDF.co API (Convert HTML to PDF): ${error.message}.`; + if (responseBody && responseBody.message) { + detailedMessage += ` Server message: ${responseBody.message}.`; + } else if (responseBody) { + detailedMessage += ` Server response: ${JSON.stringify(responseBody)}.`; + } + throw new Error(detailedMessage); + } + throw error; + } + }, +}); diff --git a/packages/pieces/community/pdf-co/src/lib/actions/convert-pdf-to-structured-format.ts b/packages/pieces/community/pdf-co/src/lib/actions/convert-pdf-to-structured-format.ts new file mode 100644 index 0000000..c0a7163 --- /dev/null +++ b/packages/pieces/community/pdf-co/src/lib/actions/convert-pdf-to-structured-format.ts @@ -0,0 +1,149 @@ +import { Property, DropdownOption, createAction } from "@activepieces/pieces-framework"; +import { httpClient, HttpMethod, HttpError } from "@activepieces/pieces-common"; +import { PdfCoSuccessResponse, PdfCoErrorResponse } from "../common/types"; +import { pdfCoAuth } from "../../index"; +import { BASE_URL, commonProps } from "../common/props"; +// Interface for the request body (common params for CSV/JSON/XML conversion) +interface PdfConvertToStructuredFormatRequestBody { + url: string; + async: boolean; + inline: boolean; // Should typically be false to get output URL + name?: string; + pages?: string; + password?: string; + lang?: string; + expiration?: number; + profiles?: Record; + httpusername?: string; + httppassword?: string; +} + +export const convertPdfToStructuredFormat = createAction({ + name: 'convert_pdf_to_structured_format', + displayName: 'Convert PDF to JSON/CSV/XML', + description: 'Convert PDF content into structured formats (JSON, CSV, or XML).', + auth: pdfCoAuth, + props: { + url: Property.ShortText({ + displayName: 'Source PDF URL', + description: 'URL of the PDF file to convert.', + required: true, + }), + outputFormat: Property.StaticDropdown ({ + displayName: 'Output Format', + description: 'Select the desired structured output format.', + required: true, + options: { + disabled:false, + options: [ + { label: "JSON", value: "json" }, + { label: "CSV", value: "csv" }, + { label: "XML", value: "xml" }, + ] as DropdownOption<'json' | 'csv' | 'xml'>[], + + } + }), + pages: Property.ShortText({ + displayName: 'Pages', + description: 'Comma-separated page numbers or ranges (e.g., "0,2,5-10"). Leave empty for all pages.', + required: false, + }), + lang: Property.ShortText({ + displayName: 'OCR Language', + description: 'Language for OCR if processing scanned documents (e.g., "eng", "deu", "eng+deu"). See PDF.co docs for list.', + required: false, + }), + profiles: Property.Json({ + displayName: 'Profiles', + description: 'JSON object for additional configurations.', + required: false, + }), + ...commonProps + }, + async run(context) { + const { auth, propsValue } = context; + const { + url, + outputFormat, + pages, + lang, + pdfPassword, + fileName, + httpPassword, + httpUsername, + expiration, + profiles + } = propsValue; + + let endpoint = ''; + switch (outputFormat) { + case 'json': + endpoint = `${BASE_URL}/pdf/convert/to/json2`; + break; + case 'csv': + endpoint = `${BASE_URL}/pdf/convert/to/csv`; + break; + case 'xml': + endpoint = `${BASE_URL}/pdf/convert/to/xml`; + break; + default: + throw new Error(`Unsupported output format: ${outputFormat}`); + } + + const requestBody: PdfConvertToStructuredFormatRequestBody = { + url: url, + async: false, + httppassword:httpPassword, + httpusername:httpUsername, + inline: false, // Ensure we get the URL to the output file + }; + + if (pages !== undefined && pages !== '') requestBody.pages = pages; + if (lang !== undefined && lang !== '') requestBody.lang = lang; + if (pdfPassword !== undefined && pdfPassword !== '') requestBody.password = pdfPassword; + if (fileName !== undefined && fileName !== '') requestBody.name = fileName; + if (expiration !== undefined) requestBody.expiration = expiration; + if (profiles !== undefined && typeof profiles === 'object' && profiles !== null) { + requestBody.profiles = profiles as Record; + } + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: endpoint, + headers: { + 'x-api-key': auth as string, + 'Content-Type': 'application/json', + }, + body: requestBody, + }); + + if (response.body.error) { + const errorBody = response.body as PdfCoErrorResponse; + let errorMessage = `PDF.co API Error (Convert PDF to ${outputFormat.toUpperCase()}): Status ${errorBody.status}.`; + if (errorBody.message) { + errorMessage += ` Message: ${errorBody.message}.`; + } else { + errorMessage += ` An unspecified error occurred.`; + } + errorMessage += ` Raw response: ${JSON.stringify(errorBody)}`; + throw new Error(errorMessage); + } + + return response.body; + + } catch (error) { + if (error instanceof HttpError) { + const responseBody = error.response?.body as (PdfCoErrorResponse | undefined); + let detailedMessage = `HTTP Error calling PDF.co API (Convert PDF to ${outputFormat.toUpperCase()}): ${error.message}.`; + if (responseBody && responseBody.message) { + detailedMessage += ` Server message: ${responseBody.message}.`; + } else if (responseBody) { + detailedMessage += ` Server response: ${JSON.stringify(responseBody)}.`; + } + throw new Error(detailedMessage); + } + throw error; + } + }, +}); diff --git a/packages/pieces/community/pdf-co/src/lib/actions/extract-tables-from-pdf.ts b/packages/pieces/community/pdf-co/src/lib/actions/extract-tables-from-pdf.ts new file mode 100644 index 0000000..7984e40 --- /dev/null +++ b/packages/pieces/community/pdf-co/src/lib/actions/extract-tables-from-pdf.ts @@ -0,0 +1,157 @@ +import { Property, createAction } from "@activepieces/pieces-framework"; +import { httpClient, HttpMethod, HttpError } from "@activepieces/pieces-common"; +import { PdfCoErrorResponse } from "../common/types"; // Use common error response type +import { pdfCoAuth } from "../../index"; +import { BASE_URL, commonProps } from "../common/props"; +// Interface for the Document Parser API request body +interface PdfDocumentParserRequestBody { + url: string; + templateId: string; // Making this required for table extraction + async: boolean; + inline: boolean; // Set to true to get data in response + outputFormat?: 'JSON' | 'CSV' | 'XML'; // Keep JSON for this action + name?: string; + pages?: string; + password?: string; + expiration?: number; + profiles?: Record; + httpusername?: string; + httppassword?: string; +} + +// Interface for the structure within the successful response's 'body' field +interface PdfDocumentParserResult { + objects: Array<{ + name: string; + objectType: 'field' | 'table' | string; // Can be field, table, etc. + value?: unknown; // For fields + rows?: unknown[][]; // For tables + pageIndex?: number; + rectangle?: number[]; + [key: string]: unknown; // Allow other properties + }>; + templateName: string; + templateVersion: string; + timestamp: string; +} + +// Interface for the overall successful response from the Document Parser endpoint +interface PdfDocumentParserSuccessResponse { + body: PdfDocumentParserResult; + pageCount: number; + error: false; + status: number; + name: string; // Name of the generated output (e.g., sample-invoice.json) + remainingCredits: number; + credits: number; + url?: string; // URL if inline=false +} + +export const extractTablesFromPdf = createAction({ + name: 'extract_tables_from_pdf', + displayName: 'Extract Tables from PDF (using Template)', + description: 'Extracts table data from a PDF using a predefined PDF.co Document Parser template.', + auth: pdfCoAuth, // Inherits auth from the piece + props: { + url: Property.ShortText({ + displayName: 'Source PDF URL', + description: 'URL of the PDF file to extract tables from.', + required: true, + }), + templateId: Property.ShortText({ + displayName: 'Template ID', + description: 'The ID of your Document Parser template (created in PDF.co dashboard) designed to extract the table(s).', + required: true, + }), + pages: Property.ShortText({ + displayName: 'Pages', + description: 'Comma-separated page numbers or ranges (e.g., "0,2,5-10"). Overrides template settings if provided.', + required: false, + }), + profiles: Property.Json({ + displayName: 'Profiles', + description: 'JSON object for additional configurations.', + required: false, + }), + ...commonProps + }, + async run(context) { + const { auth, propsValue } = context; + const { + url, + templateId, + pages, + pdfPassword, + fileName, + httpPassword, + httpUsername, + expiration, + profiles + } = propsValue; + + const requestBody: PdfDocumentParserRequestBody = { + url: url, + templateId: templateId, + httppassword:httpPassword, + httpusername:httpUsername, + async: false, + inline: true, // Get the parsed data directly + outputFormat: 'JSON', // We want JSON to process tables + }; + + if (pages !== undefined && pages !== '') requestBody.pages = pages; + if (pdfPassword !== undefined && pdfPassword !== '') requestBody.password = pdfPassword; + if (fileName !== undefined && fileName !== '') requestBody.name = fileName; + if (expiration !== undefined) requestBody.expiration = expiration; + if (profiles !== undefined && typeof profiles === 'object' && profiles !== null) { + requestBody.profiles = profiles as Record; + } + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/pdf/documentparser`, + headers: { + 'x-api-key': auth as string, + 'Content-Type': 'application/json', + }, + body: requestBody, + }); + + if (response.body.error) { + const errorBody = response.body as PdfCoErrorResponse; + let errorMessage = `PDF.co API Error (Extract Tables): Status ${errorBody.status}.`; + if (errorBody.message) { + errorMessage += ` Message: ${errorBody.message}.`; + } else { + errorMessage += ` An unspecified error occurred.`; + } + errorMessage += ` Raw response: ${JSON.stringify(errorBody)}`; + throw new Error(errorMessage); + } + + const successBody = response.body as PdfDocumentParserSuccessResponse; + + // Filter the results to return only table objects + const tables = successBody.body.objects.filter(obj => obj.objectType === 'table'); + + return { + extractedTables: tables, // Array of table objects found by the template + templateNameUsed: successBody.body.templateName, + }; + + } catch (error) { + if (error instanceof HttpError) { + const responseBody = error.response?.body as (PdfCoErrorResponse | undefined); + let detailedMessage = `HTTP Error calling PDF.co API (Extract Tables): ${error.message}.`; + if (responseBody && responseBody.message) { + detailedMessage += ` Server message: ${responseBody.message}.`; + } else if (responseBody) { + detailedMessage += ` Server response: ${JSON.stringify(responseBody)}.`; + } + throw new Error(detailedMessage); + } + throw error; + } + }, +}); diff --git a/packages/pieces/community/pdf-co/src/lib/actions/extract-text-from-pdf.ts b/packages/pieces/community/pdf-co/src/lib/actions/extract-text-from-pdf.ts new file mode 100644 index 0000000..52c2476 --- /dev/null +++ b/packages/pieces/community/pdf-co/src/lib/actions/extract-text-from-pdf.ts @@ -0,0 +1,128 @@ +import { Property, createAction } from "@activepieces/pieces-framework"; +import { httpClient, HttpMethod, HttpError } from "@activepieces/pieces-common"; +import { pdfCoAuth } from "../../index"; +import { BASE_URL, commonProps } from "../common/props"; + +interface PdfCoExtractTextSuccessResponse { + body: string; // The extracted text content + pageCount: number; + error: false; + status: number; + name: string; // Output file name (e.g., sample.txt) + remainingCredits: number; + credits: number; + url?: string; // URL to output file if inline=false +} + +// Define a type for the expected error response body (can use common one if it matches) +interface PdfCoErrorResponse { + error: true; + status: number; + message?: string; + [key: string]: unknown; +} + +// Interface for the request body +interface PdfConvertToTextSimpleRequestBody { + url: string; + async: boolean; + inline: boolean; // Keep true to get text directly in response body + name?: string; + pages?: string; + password?: string; + httpusername?: string; + httppassword?: string; +} + +export const extractTextFromPdf = createAction({ + name: 'extract_text_from_pdf', + displayName: 'Extract Plain Text from PDF', + description: 'Extracts plain text content from a PDF document.', + auth: pdfCoAuth, + props: { + url: Property.ShortText({ + displayName: 'Source PDF URL', + description: 'URL of the PDF file to extract text from.', + required: true, + }), + pages: Property.ShortText({ + displayName: 'Pages', + description: 'Comma-separated page numbers or ranges (e.g., "0,2,5-10"). Leave empty for all pages.', + required: false, + }), + password: commonProps.pdfPassword, + outputName: commonProps.fileName, + httpUsername:commonProps.httpUsername, + httpPassword:commonProps.httpPassword + }, + async run(context) { + const { auth, propsValue } = context; + const { + url, + pages, + password, + outputName, + httpPassword, + httpUsername + } = propsValue; + + const requestBody: PdfConvertToTextSimpleRequestBody = { + url: url, + async: false, + httpusername:httpUsername, + httppassword:httpPassword, + inline: true, // Get text directly in response.body.body + }; + + if (pages !== undefined && pages !== '') requestBody.pages = pages; + if (password !== undefined && password !== '') requestBody.password = password; + if (outputName !== undefined && outputName !== '') requestBody.name = outputName; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/pdf/convert/to/text-simple`, + headers: { + 'x-api-key': auth as string, + 'Content-Type': 'application/json', + }, + body: requestBody, + }); + + if (response.body.error) { + const errorBody = response.body as PdfCoErrorResponse; + let errorMessage = `PDF.co API Error (Extract Text): Status ${errorBody.status}.`; + if (errorBody.message) { + errorMessage += ` Message: ${errorBody.message}.`; + } else { + errorMessage += ` An unspecified error occurred.`; + } + errorMessage += ` Raw response: ${JSON.stringify(errorBody)}`; + throw new Error(errorMessage); + } + + const successBody = response.body as PdfCoExtractTextSuccessResponse; + + return { + extractedText: successBody.body, + pageCount: successBody.pageCount, + outputName: successBody.name, + creditsUsed: successBody.credits, + remainingCredits: successBody.remainingCredits, + }; + + } catch (error) { + if (error instanceof HttpError) { + const responseBody = error.response?.body as (PdfCoErrorResponse | undefined); + let detailedMessage = `HTTP Error calling PDF.co API (Extract Text): ${error.message}.`; + if (responseBody && responseBody.message) { + detailedMessage += ` Server message: ${responseBody.message}.`; + } else if (responseBody) { + detailedMessage += ` Server response: ${JSON.stringify(responseBody)}.`; + } + throw new Error(detailedMessage); + } + throw error; + } + }, +}); diff --git a/packages/pieces/community/pdf-co/src/lib/actions/index.ts b/packages/pieces/community/pdf-co/src/lib/actions/index.ts new file mode 100644 index 0000000..50e8bcd --- /dev/null +++ b/packages/pieces/community/pdf-co/src/lib/actions/index.ts @@ -0,0 +1,8 @@ +export * from './search-and-replace-text'; +export * from './add-text-to-pdf'; +export * from './add-image-to-pdf'; +export * from './convert-html-to-pdf'; +export * from './extract-text-from-pdf'; +export * from './convert-pdf-to-structured-format'; +export * from './extract-tables-from-pdf'; +export * from './add-barcode-to-pdf'; diff --git a/packages/pieces/community/pdf-co/src/lib/actions/search-and-replace-text.ts b/packages/pieces/community/pdf-co/src/lib/actions/search-and-replace-text.ts new file mode 100644 index 0000000..02c8970 --- /dev/null +++ b/packages/pieces/community/pdf-co/src/lib/actions/search-and-replace-text.ts @@ -0,0 +1,132 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod, HttpError } from '@activepieces/pieces-common'; +import { PdfCoSuccessResponse, PdfCoErrorResponse } from '../common/types'; +import { pdfCoAuth } from '../../index'; +import { BASE_URL, commonProps } from '../common/props'; + +interface PdfCoSearchAndReplaceRequestBody { + url: string; + searchStrings: string[]; + replaceStrings: string[]; + async: boolean; + caseSensitive?: boolean; + regex?: boolean; + pages?: string; + name?: string; + expiration?: number; + httpusername?: string; + httppassword?: string; + password?: string; +} + +export const searchAndReplaceText = createAction({ + name: 'search_and_replace_text', + displayName: 'Search and Replace Text in PDF', + description: 'Search for specific text or patterns in a PDF and replace it with new text.', + auth: pdfCoAuth, + props: { + url: Property.ShortText({ + displayName: 'PDF URL', + description: 'URL to the source PDF file.', + required: true, + }), + searchStrings: Property.Array({ + displayName: 'Text to Locate', + required: true, + }), + replaceStrings: Property.Array({ + displayName: 'Replacement Text', + required: true, + }), + caseSensitive: Property.Checkbox({ + displayName: 'Case Sensitive', + description: 'Set to true for case-sensitive search, false otherwise.', + required: false, + defaultValue: true, + }), + regex: Property.Checkbox({ + displayName: 'Use Regular Expressions ?', + description: 'Set to true to use regular expressions for search texts.', + required: false, + defaultValue: false, + }), + pages: Property.ShortText({ + displayName: 'Pages', + description: + 'Comma-separated page numbers or ranges (e.g., "0,2,5-10"). Leave empty for all pages.', + required: false, + }), + ...commonProps, + }, + async run(context) { + const { auth, propsValue } = context; + const { + url, + searchStrings, + replaceStrings, + caseSensitive, + regex, + pages, + fileName, + httpPassword, + httpUsername, + pdfPassword, + expiration, + } = propsValue; + + const requestBody: PdfCoSearchAndReplaceRequestBody = { + url: url, + searchStrings: searchStrings as string[], + replaceStrings: replaceStrings as string[], + async: false, + caseSensitive: caseSensitive, + regex, + pages, + name: fileName, + expiration, + httppassword: httpPassword, + httpusername: httpUsername, + password: pdfPassword, + }; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/pdf/edit/replace-text`, + headers: { + 'x-api-key': auth, + 'Content-Type': 'application/json', + }, + body: requestBody, + }); + + if (response.body.error) { + const errorBody = response.body as PdfCoErrorResponse; + let errorMessage = `PDF.co API Error: Status ${errorBody.status}.`; + if (errorBody.message) { + errorMessage += ` Message: ${errorBody.message}.`; + } else { + errorMessage += ` An unspecified error occurred.`; + } + errorMessage += ` Check input parameters, API key, and PDF.co dashboard for more details. Raw response: ${JSON.stringify( + errorBody, + )}`; + throw new Error(errorMessage); + } + + return response.body; + } catch (error) { + if (error instanceof HttpError) { + const responseBody = error.response?.body as PdfCoErrorResponse | undefined; + let detailedMessage = `HTTP Error calling PDF.co API: ${error.message}.`; + if (responseBody && responseBody.message) { + detailedMessage += ` Server message: ${responseBody.message}.`; + } else if (responseBody) { + detailedMessage += ` Server response: ${JSON.stringify(responseBody)}.`; + } + throw new Error(detailedMessage); + } + throw error; + } + }, +}); diff --git a/packages/pieces/community/pdf-co/src/lib/common/props.ts b/packages/pieces/community/pdf-co/src/lib/common/props.ts new file mode 100644 index 0000000..a46ed63 --- /dev/null +++ b/packages/pieces/community/pdf-co/src/lib/common/props.ts @@ -0,0 +1,32 @@ +import { Property } from '@activepieces/pieces-framework'; + +export const BASE_URL = 'https://api.pdf.co/v1'; + +export const commonProps = { + fileName: Property.ShortText({ + displayName: 'File Name', + description: 'Desired name for the output PDF file (e.g., "result.pdf").', + required: false, + }), + expiration: Property.Number({ + displayName: 'Expiration Time in Minutes', + description: + 'Set the expiration time for the output link in minutes (default is 60 i.e 60 minutes or 1 hour).', + required: false, + }), + pdfPassword: Property.ShortText({ + displayName: 'Source PDF Password', + description: 'Password if the source PDF is protected.', + required: false, + }), + httpUsername: Property.ShortText({ + displayName: 'HTTP Username', + description: 'HTTP auth username if required to access source url.', + required: false, + }), + httpPassword: Property.ShortText({ + displayName: 'HTTP Password', + description: 'HTTP auth password if required to access source url.', + required: false, + }), +}; diff --git a/packages/pieces/community/pdf-co/src/lib/common/types.ts b/packages/pieces/community/pdf-co/src/lib/common/types.ts new file mode 100644 index 0000000..86986fb --- /dev/null +++ b/packages/pieces/community/pdf-co/src/lib/common/types.ts @@ -0,0 +1,45 @@ + +// Define a type for the expected successful response body (shared structure) +export interface PdfCoSuccessResponse { + url: string; + pageCount: number; + error: false; + status: number; + name: string; + remainingCredits: number; + credits: number; +} + +// Define a type for the expected error response body (shared structure) +export interface PdfCoErrorResponse { + error: true; + status: number; + message?: string; + [key: string]: unknown; +} + + // Interface for the image object needed by /pdf/edit/add +export interface PdfCoImageAnnotation { + url: string; + x: number; + y: number; + pages?: string; + width?: number; + height?: number; + link?: string; + keepAspectRatio?: boolean; +} + +// Interface for the /pdf/edit/add request body (when adding images) +export interface PdfCoAddImagesRequestBody { + url: string; // Source PDF URL + images: PdfCoImageAnnotation[]; + async: boolean; + inline: boolean; // Should be false to get final PDF url + name?: string; + password?: string; + expiration?: number; + profiles?: Record; + httpusername?: string; + httppassword?: string; +} \ No newline at end of file diff --git a/packages/pieces/community/pdf-co/tsconfig.json b/packages/pieces/community/pdf-co/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/pdf-co/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/pdf-co/tsconfig.lib.json b/packages/pieces/community/pdf-co/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/pdf-co/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/pdf/.eslintrc.json b/packages/pieces/community/pdf/.eslintrc.json new file mode 100644 index 0000000..eac708a --- /dev/null +++ b/packages/pieces/community/pdf/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/pdf/README.md b/packages/pieces/community/pdf/README.md new file mode 100644 index 0000000..9c6e980 --- /dev/null +++ b/packages/pieces/community/pdf/README.md @@ -0,0 +1,7 @@ +# pieces-pdf + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-pdf` to build the library. diff --git a/packages/pieces/community/pdf/package-lock.json b/packages/pieces/community/pdf/package-lock.json new file mode 100644 index 0000000..69fdba9 --- /dev/null +++ b/packages/pieces/community/pdf/package-lock.json @@ -0,0 +1,1054 @@ +{ + "name": "@activepieces/piece-pdf", + "version": "0.2.4", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-pdf", + "version": "0.2.4", + "dependencies": { + "jimp": "^0.22.12", + "pdf-lib": "1.17.1", + "pdf-parse": "1.1.1" + }, + "devDependencies": { + "@types/pdf-parse": "1.1.4" + } + }, + "node_modules/@jimp/bmp": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.12.tgz", + "integrity": "sha512-aeI64HD0npropd+AR76MCcvvRaa+Qck6loCOS03CkkxGHN5/r336qTM5HPUdHKMDOGzqknuVPA8+kK1t03z12g==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "bmp-js": "^0.1.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/core": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.12.tgz", + "integrity": "sha512-l0RR0dOPyzMKfjUW1uebzueFEDtCOj9fN6pyTYWWOM/VS4BciXQ1VVrJs8pO3kycGYZxncRKhCoygbNr8eEZQA==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "any-base": "^1.1.0", + "buffer": "^5.2.0", + "exif-parser": "^0.1.12", + "file-type": "^16.5.4", + "isomorphic-fetch": "^3.0.0", + "pixelmatch": "^4.0.2", + "tinycolor2": "^1.6.0" + } + }, + "node_modules/@jimp/custom": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.12.tgz", + "integrity": "sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHV/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1Q==", + "dependencies": { + "@jimp/core": "^0.22.12" + } + }, + "node_modules/@jimp/gif": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.22.12.tgz", + "integrity": "sha512-y6BFTJgch9mbor2H234VSjd9iwAhaNf/t3US5qpYIs0TSbAvM02Fbc28IaDETj9+4YB4676sz4RcN/zwhfu1pg==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "gifwrap": "^0.10.1", + "omggif": "^1.0.9" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/jpeg": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.12.tgz", + "integrity": "sha512-Rq26XC/uQWaQKyb/5lksCTCxXhtY01NJeBN+dQv5yNYedN0i7iYu+fXEoRsfaJ8xZzjoANH8sns7rVP4GE7d/Q==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "jpeg-js": "^0.4.4" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-blit": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz", + "integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-blur": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.12.tgz", + "integrity": "sha512-S0vJADTuh1Q9F+cXAwFPlrKWzDj2F9t/9JAbUvaaDuivpyWuImEKXVz5PUZw2NbpuSHjwssbTpOZ8F13iJX4uw==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-circle": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.22.12.tgz", + "integrity": "sha512-SWVXx1yiuj5jZtMijqUfvVOJBwOifFn0918ou4ftoHgegc5aHWW5dZbYPjvC9fLpvz7oSlptNl2Sxr1zwofjTg==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-color": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.12.tgz", + "integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "tinycolor2": "^1.6.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-contain": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.22.12.tgz", + "integrity": "sha512-Eo3DmfixJw3N79lWk8q/0SDYbqmKt1xSTJ69yy8XLYQj9svoBbyRpSnHR+n9hOw5pKXytHwUW6nU4u1wegHNoQ==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5", + "@jimp/plugin-scale": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-cover": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.22.12.tgz", + "integrity": "sha512-z0w/1xH/v/knZkpTNx+E8a7fnasQ2wHG5ze6y5oL2dhH1UufNua8gLQXlv8/W56+4nJ1brhSd233HBJCo01BXA==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-crop": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5", + "@jimp/plugin-scale": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-crop": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz", + "integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-displace": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.22.12.tgz", + "integrity": "sha512-qpRM8JRicxfK6aPPqKZA6+GzBwUIitiHaZw0QrJ64Ygd3+AsTc7BXr+37k2x7QcyCvmKXY4haUrSIsBug4S3CA==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-dither": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.22.12.tgz", + "integrity": "sha512-jYgGdSdSKl1UUEanX8A85v4+QUm+PE8vHFwlamaKk89s+PXQe7eVE3eNeSZX4inCq63EHL7cX580dMqkoC3ZLw==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-fisheye": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.12.tgz", + "integrity": "sha512-LGuUTsFg+fOp6KBKrmLkX4LfyCy8IIsROwoUvsUPKzutSqMJnsm3JGDW2eOmWIS/jJpPaeaishjlxvczjgII+Q==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-flip": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.22.12.tgz", + "integrity": "sha512-m251Rop7GN8W0Yo/rF9LWk6kNclngyjIJs/VXHToGQ6EGveOSTSQaX2Isi9f9lCDLxt+inBIb7nlaLLxnvHX8Q==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-rotate": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-gaussian": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.12.tgz", + "integrity": "sha512-sBfbzoOmJ6FczfG2PquiK84NtVGeScw97JsCC3rpQv1PHVWyW+uqWFF53+n3c8Y0P2HWlUjflEla2h/vWShvhg==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-invert": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.22.12.tgz", + "integrity": "sha512-N+6rwxdB+7OCR6PYijaA/iizXXodpxOGvT/smd/lxeXsZ/empHmFFFJ/FaXcYh19Tm04dGDaXcNF/dN5nm6+xQ==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-mask": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.22.12.tgz", + "integrity": "sha512-4AWZg+DomtpUA099jRV8IEZUfn1wLv6+nem4NRJC7L/82vxzLCgXKTxvNvBcNmJjT9yS1LAAmiJGdWKXG63/NA==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-normalize": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.22.12.tgz", + "integrity": "sha512-0So0rexQivnWgnhacX4cfkM2223YdExnJTTy6d06WbkfZk5alHUx8MM3yEzwoCN0ErO7oyqEWRnEkGC+As1FtA==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-print": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.22.12.tgz", + "integrity": "sha512-c7TnhHlxm87DJeSnwr/XOLjJU/whoiKYY7r21SbuJ5nuH+7a78EW1teOaj5gEr2wYEd7QtkFqGlmyGXY/YclyQ==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "load-bmfont": "^1.4.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-resize": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz", + "integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-rotate": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz", + "integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5", + "@jimp/plugin-crop": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-scale": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz", + "integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-shadow": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.22.12.tgz", + "integrity": "sha512-FX8mTJuCt7/3zXVoeD/qHlm4YH2bVqBuWQHXSuBK054e7wFRnRnbSLPUqAwSeYP3lWqpuQzJtgiiBxV3+WWwTg==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blur": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-threshold": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.12.tgz", + "integrity": "sha512-4x5GrQr1a/9L0paBC/MZZJjjgjxLYrqSmWd+e+QfAEPvmRxdRoQ5uKEuNgXnm9/weHQBTnQBQsOY2iFja+XGAw==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-color": ">=0.8.0", + "@jimp/plugin-resize": ">=0.8.0" + } + }, + "node_modules/@jimp/plugins": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.22.12.tgz", + "integrity": "sha512-yBJ8vQrDkBbTgQZLty9k4+KtUQdRjsIDJSPjuI21YdVeqZxYywifHl4/XWILoTZsjTUASQcGoH0TuC0N7xm3ww==", + "dependencies": { + "@jimp/plugin-blit": "^0.22.12", + "@jimp/plugin-blur": "^0.22.12", + "@jimp/plugin-circle": "^0.22.12", + "@jimp/plugin-color": "^0.22.12", + "@jimp/plugin-contain": "^0.22.12", + "@jimp/plugin-cover": "^0.22.12", + "@jimp/plugin-crop": "^0.22.12", + "@jimp/plugin-displace": "^0.22.12", + "@jimp/plugin-dither": "^0.22.12", + "@jimp/plugin-fisheye": "^0.22.12", + "@jimp/plugin-flip": "^0.22.12", + "@jimp/plugin-gaussian": "^0.22.12", + "@jimp/plugin-invert": "^0.22.12", + "@jimp/plugin-mask": "^0.22.12", + "@jimp/plugin-normalize": "^0.22.12", + "@jimp/plugin-print": "^0.22.12", + "@jimp/plugin-resize": "^0.22.12", + "@jimp/plugin-rotate": "^0.22.12", + "@jimp/plugin-scale": "^0.22.12", + "@jimp/plugin-shadow": "^0.22.12", + "@jimp/plugin-threshold": "^0.22.12", + "timm": "^1.6.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/png": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.12.tgz", + "integrity": "sha512-Mrp6dr3UTn+aLK8ty/dSKELz+Otdz1v4aAXzV5q53UDD2rbB5joKVJ/ChY310B+eRzNxIovbUF1KVrUsYdE8Hg==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "pngjs": "^6.0.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/tiff": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.12.tgz", + "integrity": "sha512-E1LtMh4RyJsoCAfAkBRVSYyZDTtLq9p9LUiiYP0vPtXyxX4BiYBUYihTLSBlCQg5nF2e4OpQg7SPrLdJ66u7jg==", + "dependencies": { + "utif2": "^4.0.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/types": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.12.tgz", + "integrity": "sha512-wwKYzRdElE1MBXFREvCto5s699izFHNVvALUv79GXNbsOVqlwlOxlWJ8DuyOGIXoLP4JW/m30YyuTtfUJgMRMA==", + "dependencies": { + "@jimp/bmp": "^0.22.12", + "@jimp/gif": "^0.22.12", + "@jimp/jpeg": "^0.22.12", + "@jimp/png": "^0.22.12", + "@jimp/tiff": "^0.22.12", + "timm": "^1.6.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "dependencies": { + "regenerator-runtime": "^0.13.3" + } + }, + "node_modules/@pdf-lib/standard-fonts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", + "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", + "dependencies": { + "pako": "^1.0.6" + } + }, + "node_modules/@pdf-lib/upng": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", + "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", + "dependencies": { + "pako": "^1.0.10" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, + "node_modules/@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==" + }, + "node_modules/@types/pdf-parse": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/pdf-parse/-/pdf-parse-1.1.4.tgz", + "integrity": "sha512-+gbBHbNCVGGYw1S9lAIIvrHW47UYOhMIFUsJcMkMrzy1Jf0vulBN3XQIjPgnoOXveMuHnF3b57fXROnY/Or7eg==", + "dev": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==" + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/centra": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/centra/-/centra-2.7.0.tgz", + "integrity": "sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg==", + "dependencies": { + "follow-redirects": "^1.15.6" + } + }, + "node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" + }, + "node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/gifwrap": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", + "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", + "dependencies": { + "image-q": "^4.0.0", + "omggif": "^1.0.10" + } + }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/image-q": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", + "dependencies": { + "@types/node": "16.9.1" + } + }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/jimp": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.12.tgz", + "integrity": "sha512-R5jZaYDnfkxKJy1dwLpj/7cvyjxiclxU3F4TrI/J4j2rS0niq6YDUMoPn5hs8GDpO+OZGo7Ky057CRtWesyhfg==", + "dependencies": { + "@jimp/custom": "^0.22.12", + "@jimp/plugins": "^0.22.12", + "@jimp/types": "^0.22.12", + "regenerator-runtime": "^0.13.3" + } + }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==" + }, + "node_modules/load-bmfont": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.2.tgz", + "integrity": "sha512-qElWkmjW9Oq1F9EI5Gt7aD9zcdHb9spJCW1L/dmPf7KzCCEJxq8nhHz5eCgI9aMf7vrG/wyaCqdsI+Iy9ZTlog==", + "dependencies": { + "buffer-equal": "0.0.1", + "mime": "^1.3.4", + "parse-bmfont-ascii": "^1.0.3", + "parse-bmfont-binary": "^1.0.5", + "parse-bmfont-xml": "^1.1.4", + "phin": "^3.7.1", + "xhr": "^2.0.1", + "xtend": "^4.0.0" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/node-ensure": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz", + "integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==" + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==" + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", + "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.5.0" + } + }, + "node_modules/parse-headers": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.6.tgz", + "integrity": "sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==" + }, + "node_modules/pdf-lib": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz", + "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==", + "dependencies": { + "@pdf-lib/standard-fonts": "^1.0.0", + "@pdf-lib/upng": "^1.0.1", + "pako": "^1.0.11", + "tslib": "^1.11.1" + } + }, + "node_modules/pdf-parse": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.1.tgz", + "integrity": "sha512-v6ZJ/efsBpGrGGknjtq9J/oC8tZWq0KWL5vQrk2GlzLEQPUDB1ex+13Rmidl1neNN358Jn9EHZw5y07FFtaC7A==", + "dependencies": { + "debug": "^3.1.0", + "node-ensure": "^0.0.0" + }, + "engines": { + "node": ">=6.8.1" + } + }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/phin": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/phin/-/phin-3.7.1.tgz", + "integrity": "sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ==", + "dependencies": { + "centra": "^2.7.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/pixelmatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", + "dependencies": { + "pngjs": "^3.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readable-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", + "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", + "dependencies": { + "readable-stream": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/timm": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", + "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==" + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + }, + "node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/utif2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", + "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", + "dependencies": { + "pako": "^1.0.11" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "dependencies": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==" + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + } + } +} diff --git a/packages/pieces/community/pdf/package.json b/packages/pieces/community/pdf/package.json new file mode 100644 index 0000000..258ae3f --- /dev/null +++ b/packages/pieces/community/pdf/package.json @@ -0,0 +1,12 @@ +{ + "name": "@activepieces/piece-pdf", + "version": "0.2.4", + "dependencies": { + "jimp": "^0.22.12", + "pdf-parse": "1.1.1", + "pdf-lib": "1.17.1" + }, + "devDependencies": { + "@types/pdf-parse": "1.1.4" + } +} diff --git a/packages/pieces/community/pdf/project.json b/packages/pieces/community/pdf/project.json new file mode 100644 index 0000000..e183ec6 --- /dev/null +++ b/packages/pieces/community/pdf/project.json @@ -0,0 +1,64 @@ +{ + "name": "pieces-pdf", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/pdf/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/pdf", + "tsConfig": "packages/pieces/community/pdf/tsconfig.lib.json", + "packageJson": "packages/pieces/community/pdf/package.json", + "main": "packages/pieces/community/pdf/src/index.ts", + "assets": [ + "packages/pieces/community/pdf/*.md", + { + "input": "packages/pieces/community/pdf/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + + }, + "dependsOn": ["prebuild", "^build"] + }, + "prebuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/pieces/community/pdf", + "command": "npm ci" + } + }, + "build-with-deps": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "nx run pieces-pdf:prebuild", + "nx run pieces-pdf:build", + "nx run pieces-pdf:postbuild" + ], + "parallel": false + } + }, + "postbuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "dist/packages/pieces/community/pdf", + "command": "npm install" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/pdf/src/index.ts b/packages/pieces/community/pdf/src/index.ts new file mode 100644 index 0000000..0f34df8 --- /dev/null +++ b/packages/pieces/community/pdf/src/index.ts @@ -0,0 +1,16 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { extractText } from './lib/actions/extract-text'; +import { convertToImage } from './lib/actions/convert-to-image'; +import { textToPdf } from './lib/actions/text-to-pdf'; +import { imageToPdf } from './lib/actions/image-to-pdf'; + +export const PDF = createPiece({ + displayName: 'PDF', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.34.2', + logoUrl: 'https://cdn.activepieces.com/pieces/pdf.svg', + authors: ['nyamkamunhjin', 'abuaboud','AbdulTheActivepiecer','jmgb27'], + actions: [extractText, convertToImage, textToPdf,imageToPdf], + triggers: [], +}); + diff --git a/packages/pieces/community/pdf/src/lib/actions/convert-to-image.ts b/packages/pieces/community/pdf/src/lib/actions/convert-to-image.ts new file mode 100644 index 0000000..119f09f --- /dev/null +++ b/packages/pieces/community/pdf/src/lib/actions/convert-to-image.ts @@ -0,0 +1,133 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { promises as fs } from 'fs'; +import { tmpdir } from 'os'; +import { join } from 'path'; +import { exec } from 'child_process'; +import { promisify } from 'util'; +import { nanoid } from 'nanoid'; +import Jimp from 'jimp'; + +const execPromise = promisify(exec); +const pdftoppmPath = '/usr/bin/pdftoppm'; + +const MAX_FILE_SIZE = 16 * 1024 * 1024; + +async function isPdftoppmInstalled(): Promise { + const { stdout, stderr } = await execPromise(`command -v ${pdftoppmPath}`); + return !stderr && stdout.trim() === pdftoppmPath; +} +async function convertPdfToImages(dataBuffer: Buffer): Promise { + const tempDir = tmpdir(); + const uniqueId = nanoid(); + const inputFilePath = join(tempDir, `input-${uniqueId}.pdf`); + const outputDir = join(tempDir, `output-${uniqueId}`); + try { + await fs.mkdir(outputDir); + await fs.writeFile(inputFilePath, dataBuffer); + + const { stderr } = await execPromise(`${pdftoppmPath} -png ${inputFilePath} ${join(outputDir, 'output')}`); + if (stderr) { + throw new Error(stderr); + } + + const files = await fs.readdir(outputDir); + const imageBuffers = []; + for (const file of files) { + const filePath = join(outputDir, file); + const imageBuffer = await fs.readFile(filePath); + await fs.unlink(filePath); + imageBuffers.push(imageBuffer); + } + + return imageBuffers; + } finally { + await fs.unlink(inputFilePath).catch(() => void 0); + await fs.rm(outputDir, { recursive: true, force: true }).catch(() => void 0); + } +} + +async function concatImagesVertically(imageBuffers: Buffer[]): Promise { + const images = await Promise.all(imageBuffers.map(buffer => Jimp.read(buffer))); + const totalHeight = images.reduce((sum, image) => sum + image.getHeight(), 0); + const maxWidth = Math.max(...images.map(image => image.getWidth())); + + const finalImage = new Jimp(maxWidth, totalHeight); + let yOffset = 0; + + for (const image of images) { + finalImage.composite(image, 0, yOffset); + yOffset += image.getHeight(); + } + + return finalImage.getBufferAsync(Jimp.MIME_PNG); +} + +export const convertToImage = createAction({ + name: 'convertToImage', + displayName: 'Convert to Image', + description: 'Convert a PDF file or URL to an image', + props: { + file: Property.File({ + displayName: 'PDF File or URL', + required: true, + }), + imageOutputType: Property.StaticDropdown({ + displayName: 'Output Image Type', + required: true, + options: { + options: [ + { label: 'Single Combined Image', value: 'single' }, + { label: 'Separate Image for Each Page', value: 'multiple' }, + ], + }, + defaultValue: 'multiple', + }), + }, + errorHandlingOptions: { + continueOnFailure: { + defaultValue: false, + }, + retryOnFailure: { + hide: true + }, + }, + async run(context) { + if (!await isPdftoppmInstalled()) { + throw new Error(`${pdftoppmPath} is not installed`); + } + + const file = context.propsValue.file; + const returnConcatenatedImage = context.propsValue.imageOutputType === 'single'; + // To prevent a DOS attack, we limit the file size to 16MB + if (file.data.buffer.byteLength > MAX_FILE_SIZE) { + throw new Error(`File size exceeds the limit of ${MAX_FILE_SIZE / (1024 * 1024)} MB.`); + } + + const dataBuffer = Buffer.from(file.data.buffer); + + const imageBuffers = await convertPdfToImages(dataBuffer); + + if (returnConcatenatedImage) { + const finalImageBuffer = await concatImagesVertically(imageBuffers); + const imageLink = await context.files.write({ + data: finalImageBuffer, + fileName: `converted_image.png`, + }); + + return { + image: imageLink, + }; + } else { + const imageLinks = await Promise.all(imageBuffers.map((imageBuffer, index) => + context.files.write({ + data: imageBuffer, + fileName: `converted_image_page_${index + 1}.png`, + }) + )); + + return { + images: imageLinks, + }; + } + }, +}); diff --git a/packages/pieces/community/pdf/src/lib/actions/extract-text.ts b/packages/pieces/community/pdf/src/lib/actions/extract-text.ts new file mode 100644 index 0000000..0fb7c84 --- /dev/null +++ b/packages/pieces/community/pdf/src/lib/actions/extract-text.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import pdfParse from 'pdf-parse'; + +export const extractText = createAction({ + name: 'extractText', + displayName: 'Extract Text', + description: 'Extract text from PDF file or url', + props: { + file: Property.File({ + displayName: 'PDF File or URL', + required: true, + }), + }, + errorHandlingOptions: { + continueOnFailure: { + defaultValue: false, + }, + retryOnFailure: { + hide: true + }, + }, + async run(context) { + const file = context.propsValue.file; + const dataBuffer = Buffer.from(file.data.buffer); + const pdfData = await pdfParse(dataBuffer); + return pdfData.text; + }, +}); diff --git a/packages/pieces/community/pdf/src/lib/actions/image-to-pdf.ts b/packages/pieces/community/pdf/src/lib/actions/image-to-pdf.ts new file mode 100644 index 0000000..dcb4766 --- /dev/null +++ b/packages/pieces/community/pdf/src/lib/actions/image-to-pdf.ts @@ -0,0 +1,210 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { PDFDocument, PDFImage, RotationTypes, PageSizes } from 'pdf-lib'; + +export const imageToPdf = createAction({ + name: 'imageToPdf', + displayName: 'Image to PDF', + description: 'Convert image to PDF', + props: { + image: Property.File({ + displayName: 'image', + description: + 'Image has to be png, jpeg or jpg and it will be scaled down to fit the page when image is larger than an A4 page', + required: true, + }), + }, + errorHandlingOptions: { + continueOnFailure: { + defaultValue: false, + }, + retryOnFailure: { + hide: true, + }, + }, + async run(context) { + try { + const image = context.propsValue.image; + const imageExtension = image.extension?.toLowerCase(); + + const pdfDoc = await PDFDocument.create(); + const page = pdfDoc.addPage(); + const [pageWidth, pageHeight] = PageSizes.A4; + page.setSize(pageWidth, pageHeight); + + const xMargin = 30; + const yMargin = 30; + const [maxCardWidth, maxCardHeight] = [pageWidth - xMargin * 2, pageHeight - yMargin * 2]; + + let result: PDFImage | null = null; + + if (imageExtension === 'png') { + result = await pdfDoc.embedPng(image.data); + } else if (imageExtension === 'jpg' || imageExtension === 'jpeg') { + result = await pdfDoc.embedJpg(image.data); + } else { + throw new Error(`Unsupported image format: ${imageExtension}`); + } + + if (result === null) { + throw new Error('Failed to embed image'); + } + + const exifOrientation = getImageOrientation(image.data.buffer); + const orientationCorrection = getOrientationCorrection(exifOrientation); + + let scaledImage, correctedWidth, correctedHeight; + switch (exifOrientation) { + case ImageOrientation.FlipHorizontalRotate90: + case ImageOrientation.Rotate90: + case ImageOrientation.FlipVerticalRotate90: + case ImageOrientation.Rotate270: + // The uploaded image is rotated +/- 90 degrees + scaledImage = result.scaleToFit(maxCardHeight, maxCardWidth); + correctedWidth = scaledImage.height; + correctedHeight = scaledImage.width; + break; + default: + scaledImage = result.scaleToFit(maxCardWidth, maxCardHeight); + correctedWidth = scaledImage.width; + correctedHeight = scaledImage.height; + } + + let xShift, yShift; + const yOffset = pageHeight - yMargin; + switch (exifOrientation) { + case ImageOrientation.FlipHorizontal: + xShift = pageWidth - xMargin - correctedWidth; + yShift = yOffset - correctedHeight; + break; + case ImageOrientation.Rotate180: + xShift = xMargin + correctedWidth; + yShift = yOffset; + break; + case ImageOrientation.FlipVertical: + xShift = pageWidth - xMargin; + yShift = yOffset; + break; + case ImageOrientation.FlipHorizontalRotate90: + xShift = xMargin + correctedWidth; + yShift = pageHeight - yOffset; + break; + case ImageOrientation.Rotate90: + xShift = xMargin; + yShift = yOffset; + break; + case ImageOrientation.FlipVerticalRotate90: + xShift = xMargin; + yShift = pageHeight - yOffset + correctedHeight; + break; + case ImageOrientation.Rotate270: + xShift = xMargin + correctedWidth; + yShift = yOffset - correctedHeight; + break; + default: + xShift = xMargin; + yShift = yOffset - correctedHeight; + } + + page.drawImage(result, { + x: xShift, + y: yShift, + height: scaledImage.height, + width: scaledImage.width, + rotate: { angle: orientationCorrection.degrees, type: RotationTypes.Degrees }, + }); + + const pdfBytes = await pdfDoc.save(); + const base64Pdf = Buffer.from(pdfBytes).toString('base64'); + + return context.files.write({ + data: Buffer.from(base64Pdf, 'base64'), + fileName: `${image.filename}.pdf`, + }); + } catch (error) { + throw new Error(`Failed to convert text to PDF: ${(error as Error).message}`); + } + }, +}); + +// https://sirv.com/help/articles/rotate-photos-to-be-upright/#exif-orientation-values +enum ImageOrientation { + Normal = 1, // "Image is in normal orientation, no rotation or flipping" + Rotate90 = 6, // "Image is rotated 90 degrees" + Rotate180 = 3, // "Image is rotated 180 degrees" + Rotate270 = 8, // "Image is rotated 270 degrees" + FlipHorizontal = 2, // "Image is flipped horizontally" + FlipVertical = 4, // "Image is flipped horizontally and rotated 180 degrees" + FlipHorizontalRotate90 = 5, // "Image is rotated 90 degrees and flipped horizontally" + FlipVerticalRotate90 = 7, // "Image is rotated 270 degrees and flipped horizontally" + Unknown= -1 + +} + +// https://github.com/Hopding/pdf-lib/issues/1284 +// https://stackoverflow.com/questions/7584794/accessing-jpeg-exif-rotation-data-in-javascript-on-the-client-side/32490603#32490603 +function getImageOrientation(file: ArrayBuffer): ImageOrientation { + const view = new DataView(file); + + const length = view.byteLength; + let offset = 2; + + while (offset < length) { + if (view.getUint16(offset + 2, false) <= 8) return ImageOrientation.Unknown; + const marker = view.getUint16(offset, false); + offset += 2; + + // If EXIF buffer segment exists find the orientation + if (marker == 0xffe1) { + if (view.getUint32((offset += 2), false) != 0x45786966) { + return ImageOrientation.Unknown; + } + + const little = view.getUint16((offset += 6), false) == 0x4949; + offset += view.getUint32(offset + 4, little); + const tags = view.getUint16(offset, little); + offset += 2; + for (let i = 0; i < tags; i++) { + if (view.getUint16(offset + i * 12, little) == 0x0112) { + const orientation = view.getUint16(offset + i * 12 + 8, little); + switch (orientation) { + case 1: return ImageOrientation.Normal; + case 3: return ImageOrientation.Rotate180; + case 6: return ImageOrientation.Rotate90; + case 8: return ImageOrientation.Rotate270; + case 2: return ImageOrientation.FlipHorizontal; + case 4: return ImageOrientation.FlipVertical; + case 5: return ImageOrientation.FlipHorizontalRotate90; + case 7: return ImageOrientation.FlipVerticalRotate90; + default: return ImageOrientation.Unknown; + } + } + } + } else if ((marker & 0xff00) != 0xff00) { + break; + } else { + offset += view.getUint16(offset, false); + } + } + return ImageOrientation.Unknown; +} + +function getOrientationCorrection(orientation: number): { degrees: number; mirrored?: 'x' | 'y' } { + switch (orientation) { + case ImageOrientation.FlipHorizontal: + return { degrees: 0, mirrored: 'x' }; + case ImageOrientation.Rotate180: + return { degrees: -180 }; + case ImageOrientation.FlipVertical: + return { degrees: 180, mirrored: 'x' }; + case ImageOrientation.FlipHorizontalRotate90: + return { degrees: 90, mirrored: 'y' }; + case ImageOrientation.Rotate90: + return { degrees: -90 }; + case ImageOrientation.FlipVerticalRotate90: + return { degrees: -90, mirrored: 'y' }; + case ImageOrientation.Rotate270: + return { degrees: 90 }; + default: + return { degrees: 0 }; + } +} diff --git a/packages/pieces/community/pdf/src/lib/actions/text-to-pdf.ts b/packages/pieces/community/pdf/src/lib/actions/text-to-pdf.ts new file mode 100644 index 0000000..71666a4 --- /dev/null +++ b/packages/pieces/community/pdf/src/lib/actions/text-to-pdf.ts @@ -0,0 +1,103 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { PDFDocument, StandardFonts } from 'pdf-lib'; + +export const textToPdf = createAction({ + name: 'textToPdf', + displayName: 'Text to PDF', + description: 'Convert text to PDF', + props: { + text: Property.LongText({ + displayName: 'text', + description: 'Enter text to convert', + required: true, + }), + }, + errorHandlingOptions: { + continueOnFailure: { + defaultValue: false, + }, + retryOnFailure: { + hide: true, + }, + }, + async run(context) { + const text = context.propsValue.text; + + const pageSize: [number, number] = [595, 842]; // Standard A4 size + const margin = 50; + const topMargin = 70; + const fontSize = 12; + const lineSpacing = 5; + const paragraphSpacing = 8; + const fontType: StandardFonts = StandardFonts.Helvetica; + + try { + const pdfDoc = await PDFDocument.create(); + let page = pdfDoc.addPage(pageSize); + const { width, height } = page.getSize(); + + const font = await pdfDoc.embedFont(fontType); + + const lineHeight = font.heightAtSize(fontSize) + lineSpacing; + const maxWidth = width - margin * 2; + + const paragraphs = text.split('\n'); + let yPosition = height - topMargin; + + paragraphs.forEach((paragraph) => { + const words = paragraph.split(' '); + let line = ''; + + words.forEach((word) => { + const testLine = line + word + ' '; + const testLineWidth = font.widthOfTextAtSize(testLine, fontSize); + + if (testLineWidth > maxWidth) { + page.drawText(line.trim(), { + x: margin, + y: yPosition, + size: fontSize, + font, + }); + line = word + ' '; + yPosition -= lineHeight; + + if (yPosition < margin + lineHeight) { + page = pdfDoc.addPage(pageSize); + yPosition = height - topMargin; + } + } else { + line = testLine; + } + }); + + if (line.trim()) { + page.drawText(line.trim(), { + x: margin, + y: yPosition, + size: fontSize, + font, + }); + yPosition -= lineHeight; + } + + yPosition -= paragraphSpacing; + + if (yPosition < margin + lineHeight) { + page = pdfDoc.addPage(pageSize); + yPosition = height - topMargin; + } + }); + + const pdfBytes = await pdfDoc.save(); + const base64Pdf = Buffer.from(pdfBytes).toString('base64'); + + return context.files.write({ + data: Buffer.from(base64Pdf, 'base64'), + fileName: 'text.pdf', + }); + } catch (error) { + throw new Error(`Failed to convert text to PDF: ${(error as Error).message}`); + } + }, +}); diff --git a/packages/pieces/community/pdf/tsconfig.json b/packages/pieces/community/pdf/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/pdf/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/pdf/tsconfig.lib.json b/packages/pieces/community/pdf/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/pdf/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/perplexity-ai/.eslintrc.json b/packages/pieces/community/perplexity-ai/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/perplexity-ai/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/perplexity-ai/README.md b/packages/pieces/community/perplexity-ai/README.md new file mode 100644 index 0000000..019059d --- /dev/null +++ b/packages/pieces/community/perplexity-ai/README.md @@ -0,0 +1,7 @@ +# pieces-perplexity-ai + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-perplexity-ai` to build the library. diff --git a/packages/pieces/community/perplexity-ai/package.json b/packages/pieces/community/perplexity-ai/package.json new file mode 100644 index 0000000..0a844dd --- /dev/null +++ b/packages/pieces/community/perplexity-ai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-perplexity-ai", + "version": "0.2.1" +} diff --git a/packages/pieces/community/perplexity-ai/project.json b/packages/pieces/community/perplexity-ai/project.json new file mode 100644 index 0000000..b2e5a29 --- /dev/null +++ b/packages/pieces/community/perplexity-ai/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-perplexity-ai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/perplexity-ai/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/perplexity-ai", + "tsConfig": "packages/pieces/community/perplexity-ai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/perplexity-ai/package.json", + "main": "packages/pieces/community/perplexity-ai/src/index.ts", + "assets": [ + "packages/pieces/community/perplexity-ai/*.md", + { + "input": "packages/pieces/community/perplexity-ai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/perplexity-ai/src/index.ts b/packages/pieces/community/perplexity-ai/src/index.ts new file mode 100644 index 0000000..71dd980 --- /dev/null +++ b/packages/pieces/community/perplexity-ai/src/index.ts @@ -0,0 +1,23 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createChatCompletionAction } from './lib/actions/create-chat-completion.action'; + +export const perplexityAiAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: ` + Navigate to [API Settings](https://www.perplexity.ai/settings/api) and create new API key. + `, +}); + +export const perplexityAi = createPiece({ + displayName: 'Perplexity AI', + auth: perplexityAiAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/perplexity-ai.png', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + description: 'AI powered search engine', + authors: ['kishanprmr','AbdulTheActivePiecer'], + actions: [createChatCompletionAction], + triggers: [], +}); diff --git a/packages/pieces/community/perplexity-ai/src/lib/actions/create-chat-completion.action.ts b/packages/pieces/community/perplexity-ai/src/lib/actions/create-chat-completion.action.ts new file mode 100644 index 0000000..1a620cb --- /dev/null +++ b/packages/pieces/community/perplexity-ai/src/lib/actions/create-chat-completion.action.ts @@ -0,0 +1,151 @@ +import { perplexityAiAuth } from '../../'; +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; + +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; + +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const createChatCompletionAction = createAction({ + auth: perplexityAiAuth, + name: 'ask-ai', + displayName: 'Ask AI', + description: + 'Enables users to generate prompt completion based on a specified model.', + props: { + model: Property.StaticDropdown({ + displayName: 'Model', + required: true, + defaultValue:'sonar-pro', + options: { + disabled: false, + options: [ + // https://docs.perplexity.ai/guides/model-cards + { + label:'sonar-reasoning-pro', + value:'sonar-reasoning-pro' + }, + { + label:'sonar-reasoning', + value:'sonar-reasoning' + }, + { + label:'sonar-pro', + value:'sonar-pro' + }, + { + label:'sonar', + value:'sonar' + } + ], + }, + }), + prompt: Property.LongText({ + displayName: 'Question', + required: true, + }), + temperature: Property.Number({ + displayName: 'Temperature', + required: false, + description: + 'The amount of randomness in the response.Higher values are more random, and lower values are more deterministic.', + defaultValue: 0.2, + }), + max_tokens: Property.Number({ + displayName: 'Maximum Tokens', + required: false, + description: `Please refer [guide](https://docs.perplexity.ai/guides/model-cards) for each model token limit.`, + }), + top_p: Property.Number({ + displayName: 'Top P', + required: false, + description: + 'The nucleus sampling threshold, valued between 0 and 1 inclusive. For each subsequent token, the model considers the results of the tokens with top_p probability mass.', + defaultValue: 0.9, + }), + presence_penalty: Property.Number({ + displayName: 'Presence penalty', + required: false, + description: + "Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the mode's likelihood to talk about new topics.", + defaultValue: 0, + }), + frequency_penalty: Property.Number({ + displayName: 'Frequency penalty', + required: false, + description: + "A multiplicative penalty greater than 0. Values greater than 1.0 penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.", + defaultValue: 1.0, + }), + roles: Property.Json({ + displayName: 'Roles', + required: false, + description: + 'Array of roles to specify more accurate response.After the (optional) system message, user and assistant roles should alternate with user then assistant, ending in user.', + defaultValue: [ + { role: 'system', content: 'You are a helpful assistant.' }, + ], + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + temperature: z.number().min(0).max(2).optional(), + }); + + const rolesArray = context.propsValue.roles + ? (context.propsValue.roles as any) + : []; + const roles = rolesArray.map((item: any) => { + const rolesEnum = ['system', 'user', 'assistant']; + if (!rolesEnum.includes(item.role)) { + throw new Error( + 'The only available roles are: [system, user, assistant]' + ); + } + + return { + role: item.role, + content: item.content, + }; + }); + + roles.push({ role: 'user', content: context.propsValue.prompt }); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.perplexity.ai/chat/completions', + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + headers: { + 'Content-Type': 'application/json', + }, + body: { + model: context.propsValue.model, + messages: roles, + temperature: context.propsValue.temperature, + top_p: context.propsValue.top_p, + presence_penalty: context.propsValue.presence_penalty, + frequency_penalty: context.propsValue.frequency_penalty, + }, + }); + + if (response.status === 200) { + + return { + result:response.body.choices[0].message.content, + citations:response.body.citations + } + } + + return response.body; + }, +}); diff --git a/packages/pieces/community/perplexity-ai/tsconfig.json b/packages/pieces/community/perplexity-ai/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/perplexity-ai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/perplexity-ai/tsconfig.lib.json b/packages/pieces/community/perplexity-ai/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/perplexity-ai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/personal-ai/.eslintrc.json b/packages/pieces/community/personal-ai/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/personal-ai/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/personal-ai/README.md b/packages/pieces/community/personal-ai/README.md new file mode 100644 index 0000000..f20a56d --- /dev/null +++ b/packages/pieces/community/personal-ai/README.md @@ -0,0 +1,7 @@ +# pieces-personal-ai + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-personal-ai` to build the library. diff --git a/packages/pieces/community/personal-ai/package.json b/packages/pieces/community/personal-ai/package.json new file mode 100644 index 0000000..b52deae --- /dev/null +++ b/packages/pieces/community/personal-ai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-personal-ai", + "version": "0.0.1" +} diff --git a/packages/pieces/community/personal-ai/project.json b/packages/pieces/community/personal-ai/project.json new file mode 100644 index 0000000..40ef293 --- /dev/null +++ b/packages/pieces/community/personal-ai/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-personal-ai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/personal-ai/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/personal-ai", + "tsConfig": "packages/pieces/community/personal-ai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/personal-ai/package.json", + "main": "packages/pieces/community/personal-ai/src/index.ts", + "assets": [ + "packages/pieces/community/personal-ai/*.md", + { + "input": "packages/pieces/community/personal-ai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/personal-ai/src/index.ts b/packages/pieces/community/personal-ai/src/index.ts new file mode 100644 index 0000000..db8779e --- /dev/null +++ b/packages/pieces/community/personal-ai/src/index.ts @@ -0,0 +1,45 @@ + +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; + +import { createMemory } from './lib/actions/memory/create-memory'; +import { createMessage } from './lib/actions/messaging/create-message'; +import { createChatGPTInstruction } from './lib/actions/ai_interaction/create-chatgpt-instruction'; +import { createCustomTraining } from './lib/actions/ai_interaction/create-custom-training'; +import { getConversation } from './lib/actions/messaging/get-conversation'; +import { uploadDocument } from './lib/actions/documents/upload-document'; +import { uploadFile } from './lib/actions/documents/upload-file'; +import { uploadUrl } from './lib/actions/documents/upload-url'; +import { updateDocument } from './lib/actions/documents/update-document'; +import { getDocument } from './lib/actions/documents/get-document'; + +export const BASE_URL = 'https://api.personal.ai'; + +export const personalAiAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: 'API Key for authentication', + required: true, +}) + +export const aiAssistant = createPiece({ + displayName: 'Personal AI', + description: 'Manage memory storage, messaging, and documents through AI integration.', + logoUrl: 'https://cdn.activepieces.com/pieces/personal-ai.png', + auth: personalAiAuth, + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + minimumSupportedRelease: '0.20.0', + authors: ['reemayoush'], + actions: [ + createMemory, + createMessage, + createChatGPTInstruction, + createCustomTraining, + getConversation, + uploadDocument, + uploadFile, + uploadUrl, + updateDocument, + getDocument, + ], + triggers: [], +}); diff --git a/packages/pieces/community/personal-ai/src/lib/actions/ai_interaction/create-chatgpt-instruction.ts b/packages/pieces/community/personal-ai/src/lib/actions/ai_interaction/create-chatgpt-instruction.ts new file mode 100644 index 0000000..d6fb226 --- /dev/null +++ b/packages/pieces/community/personal-ai/src/lib/actions/ai_interaction/create-chatgpt-instruction.ts @@ -0,0 +1,79 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { BASE_URL, personalAiAuth } from '../../../index'; + +export const createChatGPTInstruction = createAction({ + auth:personalAiAuth, + name: 'create_chatgpt_instruction', + displayName: 'Send ChatGPT Instruction', + description: 'Send an instruction to AI assistant using ChatGPT integration.', + // category: 'AI Interaction', + props: { + text: Property.LongText({ + displayName: 'Instruction Text', + description: 'The instruction or prompt to send to ChatGPT', + required: true, + }), + context: Property.LongText({ + displayName: 'Context', + description: 'Additional context for the AI response', + required: false, + }), + domainName: Property.ShortText({ + displayName: 'Domain Name', + description: 'The domain identifier for the AI profile', + required: false, + }), + userName: Property.ShortText({ + displayName: 'User Name', + description: 'Name of the user sending the request', + required: false, + }), + sessionId: Property.ShortText({ + displayName: 'Session ID', + description: 'Use the same sessionId to continue conversation on that session', + required: false, + }), + sourceName: Property.ShortText({ + displayName: 'Source Name', + description: 'Name of the source app of the inbound instruction', + required: false, + }), + isStack: Property.Checkbox({ + displayName: 'Add to Memory', + description: 'Flag to also add the user instruction to memory', + required: false, + defaultValue: false, + }), + isDraft: Property.Checkbox({ + displayName: 'Create Draft', + description: 'Flag to create a copilot message for the AI', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { auth, propsValue: { text, context: messageContext, domainName, userName, sessionId, sourceName, isStack, isDraft } } = context; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/v1/instruction?cmd=chatgpt`, + headers: { + 'Content-Type': 'application/json', + 'x-api-key': auth as string, + }, + body: { + Text: text, + ...(messageContext && { Context: messageContext }), + ...(domainName && { DomainName: domainName }), + ...(userName && { UserName: userName }), + ...(sessionId && { SessionId: sessionId }), + ...(sourceName && { SourceName: sourceName }), + ...(isStack !== undefined && { is_stack: isStack }), + ...(isDraft !== undefined && { is_draft: isDraft }), + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/personal-ai/src/lib/actions/ai_interaction/create-custom-training.ts b/packages/pieces/community/personal-ai/src/lib/actions/ai_interaction/create-custom-training.ts new file mode 100644 index 0000000..1005a31 --- /dev/null +++ b/packages/pieces/community/personal-ai/src/lib/actions/ai_interaction/create-custom-training.ts @@ -0,0 +1,79 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { BASE_URL, personalAiAuth } from '../../../index'; + +export const createCustomTraining = createAction({ + auth:personalAiAuth, + name: 'create_custom_training', + displayName: 'Send Custom Training', + description: 'Send a custom training instruction to AI assistant.', + // category: 'AI Interaction', + props: { + text: Property.LongText({ + displayName: 'Training Text', + description: 'The training instruction or prompt to send', + required: true, + }), + context: Property.LongText({ + displayName: 'Context', + description: 'Additional context for the AI response', + required: false, + }), + domainName: Property.ShortText({ + displayName: 'Domain Name', + description: 'The domain identifier for the AI profile', + required: false, + }), + userName: Property.ShortText({ + displayName: 'User Name', + description: 'Name of the user sending the request', + required: false, + }), + sessionId: Property.ShortText({ + displayName: 'Session ID', + description: 'Use the same sessionId to continue conversation on that session', + required: false, + }), + sourceName: Property.ShortText({ + displayName: 'Source Name', + description: 'Name of the source app of the inbound training', + required: false, + }), + isStack: Property.Checkbox({ + displayName: 'Add to Memory', + description: 'Flag to also add the training instruction to memory', + required: false, + defaultValue: false, + }), + isDraft: Property.Checkbox({ + displayName: 'Create Draft', + description: 'Flag to create a copilot message for the AI', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { auth, propsValue: { text, context: messageContext, domainName, userName, sessionId, sourceName, isStack, isDraft } } = context; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/v1/training?cmd=custom`, + headers: { + 'Content-Type': 'application/json', + 'x-api-key': auth as string, + }, + body: { + Text: text, + ...(messageContext && { Context: messageContext }), + ...(domainName && { DomainName: domainName }), + ...(userName && { UserName: userName }), + ...(sessionId && { SessionId: sessionId }), + ...(sourceName && { SourceName: sourceName }), + ...(isStack !== undefined && { is_stack: isStack }), + ...(isDraft !== undefined && { is_draft: isDraft }), + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/personal-ai/src/lib/actions/documents/get-document.ts b/packages/pieces/community/personal-ai/src/lib/actions/documents/get-document.ts new file mode 100644 index 0000000..6483ddf --- /dev/null +++ b/packages/pieces/community/personal-ai/src/lib/actions/documents/get-document.ts @@ -0,0 +1,48 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { BASE_URL, personalAiAuth } from '../../../index'; + +export const getDocument = createAction({ + auth:personalAiAuth, + name: 'get_document', + displayName: 'Get Document', + description: 'Retrieve a document from AI assistant.', + // category: 'Documents', + props: { + documentId: Property.ShortText({ + displayName: 'Document ID', + description: 'The unique identifier of the document to retrieve', + required: true, + }), + domainName: Property.ShortText({ + displayName: 'Domain Name', + description: 'The domain identifier for the AI profile', + required: false, + }), + includeContent: Property.Checkbox({ + displayName: 'Include Content', + description: 'Flag to include the document content in the response', + required: false, + defaultValue: true, + }), + }, + async run(context) { + const { auth, propsValue: { documentId, domainName, includeContent } } = context; + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${BASE_URL}/get-document`, + headers: { + 'Content-Type': 'application/json', + 'x-api-key': auth as string, + }, + queryParams: { + DocumentId: documentId, + ...(domainName && { DomainName: domainName }), + ...(includeContent !== undefined && { IncludeContent: includeContent.toString() }), + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/personal-ai/src/lib/actions/documents/update-document.ts b/packages/pieces/community/personal-ai/src/lib/actions/documents/update-document.ts new file mode 100644 index 0000000..524c6e5 --- /dev/null +++ b/packages/pieces/community/personal-ai/src/lib/actions/documents/update-document.ts @@ -0,0 +1,78 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { BASE_URL, personalAiAuth } from '../../../index'; + +export const updateDocument = createAction({ + auth:personalAiAuth, + name: 'update_document', + displayName: 'Update Document', + description: 'Update an existing document in AI assistant.', + // category: 'Documents', + props: { + documentId: Property.ShortText({ + displayName: 'Document ID', + description: 'The unique identifier of the document to update', + required: true, + }), + text: Property.LongText({ + displayName: 'Document Text', + description: 'The updated text content of the document', + required: true, + }), + title: Property.ShortText({ + displayName: 'Document Title', + description: 'Updated title of the document', + required: false, + }), + domainName: Property.ShortText({ + displayName: 'Domain Name', + description: 'The domain identifier for the AI profile', + required: false, + }), + tags: Property.ShortText({ + displayName: 'Tags', + description: 'Comma delimited list of tags for the document', + required: false, + }), + sourceName: Property.ShortText({ + displayName: 'Source Name', + description: 'Name of the source or application', + required: false, + }), + createdTime: Property.ShortText({ + displayName: 'Created Time', + description: 'Time (including timezone) of the document creation (e.g., Wed, 19 Sep 2023 13:31:00 PDT)', + required: false, + }), + isStack: Property.Checkbox({ + displayName: 'Add to Memory', + description: 'Flag to also add the document to memory', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { auth, propsValue: { documentId, text, title, domainName, tags, sourceName, createdTime, isStack } } = context; + + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: `${BASE_URL}/update-document`, + headers: { + 'Content-Type': 'application/json', + 'x-api-key': auth as string, + }, + body: { + DocumentId: documentId, + Text: text, + ...(title && { Title: title }), + ...(domainName && { DomainName: domainName }), + ...(tags && { Tags: tags }), + ...(sourceName && { SourceName: sourceName }), + ...(createdTime && { CreatedTime: createdTime }), + ...(isStack !== undefined && { is_stack: isStack }), + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/personal-ai/src/lib/actions/documents/upload-document.ts b/packages/pieces/community/personal-ai/src/lib/actions/documents/upload-document.ts new file mode 100644 index 0000000..156dcd6 --- /dev/null +++ b/packages/pieces/community/personal-ai/src/lib/actions/documents/upload-document.ts @@ -0,0 +1,72 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { BASE_URL, personalAiAuth } from '../../../index'; + +export const uploadDocument = createAction({ + auth:personalAiAuth, + name: 'upload_document', + displayName: 'Upload Document', + description: 'Upload a text document to AI assistant.', + // category: 'Documents', + props: { + text: Property.LongText({ + displayName: 'Document Text', + description: 'The text content of the document to upload', + required: true, + }), + title: Property.ShortText({ + displayName: 'Document Title', + description: 'Title of the document', + required: true, + }), + domainName: Property.ShortText({ + displayName: 'Domain Name', + description: 'The domain identifier for the AI profile', + required: false, + }), + tags: Property.ShortText({ + displayName: 'Tags', + description: 'Comma delimited list of tags for the document', + required: false, + }), + sourceName: Property.ShortText({ + displayName: 'Source Name', + description: 'Name of the source or application', + required: false, + }), + createdTime: Property.ShortText({ + displayName: 'Created Time', + description: 'Time (including timezone) of the document creation (e.g., Wed, 19 Sep 2023 13:31:00 PDT)', + required: false, + }), + isStack: Property.Checkbox({ + displayName: 'Add to Memory', + description: 'Flag to also add the document to memory', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { auth, propsValue: { text, title, domainName, tags, sourceName, createdTime, isStack } } = context; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/v1/upload-text`, + headers: { + 'Content-Type': 'application/json', + 'x-api-key': auth as string, + }, + body: { + Text: text, + Title: title, + ...(domainName && { DomainName: domainName }), + ...(tags && { Tags: tags }), + ...(sourceName && { SourceName: sourceName }), + ...(createdTime && { CreatedTime: createdTime }), + ...(isStack !== undefined && { is_stack: isStack }), + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/personal-ai/src/lib/actions/documents/upload-file.ts b/packages/pieces/community/personal-ai/src/lib/actions/documents/upload-file.ts new file mode 100644 index 0000000..d101bd6 --- /dev/null +++ b/packages/pieces/community/personal-ai/src/lib/actions/documents/upload-file.ts @@ -0,0 +1,73 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { BASE_URL, personalAiAuth } from '../../../index'; + +export const uploadFile = createAction({ + auth:personalAiAuth, + name: 'upload_file', + displayName: 'Upload File', + description: 'Upload a file to AI assistant.', + // category: 'Documents', + props: { + file: Property.File({ + displayName: 'File', + description: 'The file to upload', + required: true, + }), + fileName: Property.ShortText({ + displayName: 'File Name', + description: 'Name of the file to be uploaded', + required: true, + }), + domainName: Property.ShortText({ + displayName: 'Domain Name', + description: 'The domain identifier for the AI profile', + required: false, + }), + tags: Property.ShortText({ + displayName: 'Tags', + description: 'Comma delimited list of tags for the file', + required: false, + }), + sourceName: Property.ShortText({ + displayName: 'Source Name', + description: 'Name of the source or application', + required: false, + }), + createdTime: Property.ShortText({ + displayName: 'Created Time', + description: 'Time (including timezone) of the file creation (e.g., Wed, 19 Sep 2023 13:31:00 PDT)', + required: false, + }), + isStack: Property.Checkbox({ + displayName: 'Add to Memory', + description: 'Flag to also add the file content to memory', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { auth, propsValue: { file, fileName, domainName, tags, sourceName, createdTime, isStack } } = context; + + // Create form data for file upload + const formData = new FormData(); + const blob = new Blob([file.data], { type: 'application/octet-stream' }); + formData.append('file', blob, fileName); + if (domainName) formData.append('DomainName', domainName); + if (tags) formData.append('Tags', tags); + if (sourceName) formData.append('SourceName', sourceName); + if (createdTime) formData.append('CreatedTime', createdTime); + if (isStack !== undefined) formData.append('is_stack', isStack.toString()); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/upload-file`, + headers: { + 'x-api-key': auth as string, + }, + body: formData, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/personal-ai/src/lib/actions/documents/upload-url.ts b/packages/pieces/community/personal-ai/src/lib/actions/documents/upload-url.ts new file mode 100644 index 0000000..d178b87 --- /dev/null +++ b/packages/pieces/community/personal-ai/src/lib/actions/documents/upload-url.ts @@ -0,0 +1,72 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { BASE_URL, personalAiAuth } from '../../../index'; + +export const uploadUrl = createAction({ + auth:personalAiAuth, + name: 'upload_url', + displayName: 'Upload URL Content', + description: 'Upload content from a URL to AI assistant.', + // category: 'Documents', + props: { + url: Property.ShortText({ + displayName: 'URL', + description: 'The URL of the content to upload', + required: true, + }), + title: Property.ShortText({ + displayName: 'Title', + description: 'Title for the uploaded content', + required: true, + }), + domainName: Property.ShortText({ + displayName: 'Domain Name', + description: 'The domain identifier for the AI profile', + required: false, + }), + tags: Property.ShortText({ + displayName: 'Tags', + description: 'Comma delimited list of tags for the content', + required: false, + }), + sourceName: Property.ShortText({ + displayName: 'Source Name', + description: 'Name of the source or application', + required: false, + }), + createdTime: Property.ShortText({ + displayName: 'Created Time', + description: 'Time (including timezone) of the content creation (e.g., Wed, 19 Sep 2023 13:31:00 PDT)', + required: false, + }), + isStack: Property.Checkbox({ + displayName: 'Add to Memory', + description: 'Flag to also add the content to memory', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { auth, propsValue: { url, title, domainName, tags, sourceName, createdTime, isStack } } = context; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/upload-url`, + headers: { + 'Content-Type': 'application/json', + 'x-api-key': auth as string, + }, + body: { + Url: url, + Title: title, + ...(domainName && { DomainName: domainName }), + ...(tags && { Tags: tags }), + ...(sourceName && { SourceName: sourceName }), + ...(createdTime && { CreatedTime: createdTime }), + ...(isStack !== undefined && { is_stack: isStack }), + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/personal-ai/src/lib/actions/memory/create-memory.ts b/packages/pieces/community/personal-ai/src/lib/actions/memory/create-memory.ts new file mode 100644 index 0000000..c7e1fa5 --- /dev/null +++ b/packages/pieces/community/personal-ai/src/lib/actions/memory/create-memory.ts @@ -0,0 +1,65 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { BASE_URL, personalAiAuth } from '../../../index'; + +export const createMemory = createAction({ + auth:personalAiAuth, + name: 'create_memory', + displayName: 'Create Memory', + description: 'Upload memories to your AI assistant stack.', + // category: 'Memory', + props: { + text: Property.LongText({ + displayName: 'Memory Text', + description: 'Plain text memories to upload to your stack', + required: true, + }), + sourceName: Property.ShortText({ + displayName: 'Source Name', + description: 'The source or application of memory to help you recall where it is from', + required: true, + }), + createdTime: Property.ShortText({ + displayName: 'Created Time', + description: 'Time (including timezone) of the memory (e.g., Wed, 19 Sep 2023 13:31:00 PDT)', + required: false, + }), + rawFeedText: Property.LongText({ + displayName: 'Raw Feed Text', + description: 'The formatted text that can be stored as it is', + required: false, + }), + domainName: Property.ShortText({ + displayName: 'Domain Name', + description: 'The domain identifier for the AI persona', + required: false, + }), + tags: Property.ShortText({ + displayName: 'Tags', + description: 'Comma delimited list of tags for the memory', + required: false, + }), + }, + async run(context) { + const { auth, propsValue: { text, sourceName, createdTime, rawFeedText, domainName, tags } } = context; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/v1/memory`, + headers: { + 'Content-Type': 'application/json', + 'x-api-key': auth as string, + }, + body: { + Text: text, + SourceName: sourceName, + ...(createdTime && { CreatedTime: createdTime }), + ...(rawFeedText && { RawFeedText: rawFeedText }), + ...(domainName && { DomainName: domainName }), + ...(tags && { Tags: tags }), + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/personal-ai/src/lib/actions/messaging/create-message.ts b/packages/pieces/community/personal-ai/src/lib/actions/messaging/create-message.ts new file mode 100644 index 0000000..6b2b3d2 --- /dev/null +++ b/packages/pieces/community/personal-ai/src/lib/actions/messaging/create-message.ts @@ -0,0 +1,79 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { BASE_URL, personalAiAuth } from '../../../index'; + +export const createMessage = createAction({ + auth:personalAiAuth, + name: 'create_message', + displayName: 'Send Message', + description: 'Send a message to the AI assistant for a response.', + // category: 'Messaging', + props: { + text: Property.LongText({ + displayName: 'Message Text', + description: 'Message to send to your AI for a response', + required: true, + }), + context: Property.LongText({ + displayName: 'Context', + description: 'Additional context for the AI response (Similar to Reply function)', + required: false, + }), + domainName: Property.ShortText({ + displayName: 'Domain Name', + description: 'The domain identifier for the AI profile', + required: false, + }), + userName: Property.ShortText({ + displayName: 'User Name', + description: 'Name of the user sending the request', + required: false, + }), + sessionId: Property.ShortText({ + displayName: 'Session ID', + description: 'Use the same sessionId to continue conversation on that session', + required: false, + }), + sourceName: Property.ShortText({ + displayName: 'Source Name', + description: 'Name of the source app of the inbound message', + required: false, + }), + isStack: Property.Checkbox({ + displayName: 'Add to Memory', + description: 'Flag to also add the user message to memory', + required: false, + defaultValue: false, + }), + isDraft: Property.Checkbox({ + displayName: 'Create Draft', + description: 'Flag to create a copilot message for the AI', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { auth, propsValue: { text, context: messageContext, domainName, userName, sessionId, sourceName, isStack, isDraft } } = context; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/v1/message`, + headers: { + 'Content-Type': 'application/json', + 'x-api-key': auth as string, + }, + body: { + Text: text, + ...(messageContext && { Context: messageContext }), + ...(domainName && { DomainName: domainName }), + ...(userName && { UserName: userName }), + ...(sessionId && { SessionId: sessionId }), + ...(sourceName && { SourceName: sourceName }), + ...(isStack !== undefined && { is_stack: isStack }), + ...(isDraft !== undefined && { is_draft: isDraft }), + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/personal-ai/src/lib/actions/messaging/get-conversation.ts b/packages/pieces/community/personal-ai/src/lib/actions/messaging/get-conversation.ts new file mode 100644 index 0000000..ef622c7 --- /dev/null +++ b/packages/pieces/community/personal-ai/src/lib/actions/messaging/get-conversation.ts @@ -0,0 +1,71 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { BASE_URL, personalAiAuth } from '../../../index'; + +export const getConversation = createAction({ + auth:personalAiAuth, + name: 'get_conversation', + displayName: 'Get Conversation History', + description: 'Retrieve conversation history from AI assistant.', + // category: 'Messaging', + props: { + channelId: Property.ShortText({ + displayName: 'Channel ID', + description: 'The unique identifier for the conversation channel', + required: true, + }), + domainName: Property.ShortText({ + displayName: 'Domain Name', + description: 'The domain identifier for the AI profile', + required: false, + }), + userName: Property.ShortText({ + displayName: 'User Name', + description: 'Name of the user requesting the conversation', + required: false, + }), + sessionId: Property.ShortText({ + displayName: 'Session ID', + description: 'Filter conversation by specific session ID', + required: false, + }), + sourceName: Property.ShortText({ + displayName: 'Source Name', + description: 'Filter conversation by source application', + required: false, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Maximum number of messages to return', + required: false, + }), + skip: Property.Number({ + displayName: 'Skip', + description: 'Number of messages to skip (for pagination)', + required: false, + }), + }, + async run(context) { + const { auth, propsValue: { channelId, domainName, userName, sessionId, sourceName, limit, skip } } = context; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${BASE_URL}/v1/conversation`, + headers: { + 'Content-Type': 'application/json', + 'x-api-key': auth as string, + }, + body: { + ChannelId: channelId, + ...(domainName && { DomainName: domainName }), + ...(userName && { UserName: userName }), + ...(sessionId && { SessionId: sessionId }), + ...(sourceName && { SourceName: sourceName }), + ...(limit !== undefined && { Limit: limit }), + ...(skip !== undefined && { Skip: skip }), + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/personal-ai/tsconfig.json b/packages/pieces/community/personal-ai/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/personal-ai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/personal-ai/tsconfig.lib.json b/packages/pieces/community/personal-ai/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/personal-ai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/photoroom/.eslintrc.json b/packages/pieces/community/photoroom/.eslintrc.json new file mode 100644 index 0000000..d7449bb --- /dev/null +++ b/packages/pieces/community/photoroom/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {}, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/photoroom/README.md b/packages/pieces/community/photoroom/README.md new file mode 100644 index 0000000..a75b233 --- /dev/null +++ b/packages/pieces/community/photoroom/README.md @@ -0,0 +1,7 @@ +# pieces-photoroom + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-photoroom` to build the library. diff --git a/packages/pieces/community/photoroom/package.json b/packages/pieces/community/photoroom/package.json new file mode 100644 index 0000000..b9775b0 --- /dev/null +++ b/packages/pieces/community/photoroom/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-photoroom", + "version": "0.0.1" +} diff --git a/packages/pieces/community/photoroom/project.json b/packages/pieces/community/photoroom/project.json new file mode 100644 index 0000000..aaa73fb --- /dev/null +++ b/packages/pieces/community/photoroom/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-photoroom", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/photoroom/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/photoroom", + "tsConfig": "packages/pieces/community/photoroom/tsconfig.lib.json", + "packageJson": "packages/pieces/community/photoroom/package.json", + "main": "packages/pieces/community/photoroom/src/index.ts", + "assets": [ + "packages/pieces/community/photoroom/*.md", + { + "input": "packages/pieces/community/photoroom/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-photoroom {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/photoroom/src/index.ts b/packages/pieces/community/photoroom/src/index.ts new file mode 100644 index 0000000..a8b3950 --- /dev/null +++ b/packages/pieces/community/photoroom/src/index.ts @@ -0,0 +1,22 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { removeBackground } from './lib/actions/remove-background'; + +export const photoroomAuth = PieceAuth.CustomAuth({ + required: true, + props: { + apiKey: PieceAuth.SecretText({ + displayName: 'API key', + required: true, + }), + }, +}); + +export const photoroom = createPiece({ + displayName: 'Photoroom', + auth: photoroomAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/photoroom.png', + authors: ['AdamSelene', 'Charles-Go'], + actions: [removeBackground], + triggers: [], +}); diff --git a/packages/pieces/community/photoroom/src/lib/actions/remove-background.ts b/packages/pieces/community/photoroom/src/lib/actions/remove-background.ts new file mode 100644 index 0000000..6c297d7 --- /dev/null +++ b/packages/pieces/community/photoroom/src/lib/actions/remove-background.ts @@ -0,0 +1,38 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { photoroomAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const removeBackground = createAction({ + name: 'removeBackground', + displayName: 'Remove background', + description: 'Remove the background of the image given as input', + auth: photoroomAuth, + props: { + file: Property.File({ displayName: 'Image file', required: true }), + filename: Property.ShortText({ + displayName: 'Generated filename', + required: true, + }), + }, + async run({ auth, propsValue, files }) { + const form = new FormData(); + form.append('image_file', new Blob([propsValue.file.data])); + const response = await httpClient.sendRequest({ + url: `https://sdk.photoroom.com/v1/segment`, + method: HttpMethod.POST, + headers: { + 'x-api-key': auth.apiKey, + 'Content-Type': 'multipart/form-data', + }, + body: form, + }); + const imageUrl = await files.write({ + fileName: propsValue.filename, + data: Buffer.from(response.body.result_b64, 'base64'), + }); + return { + fileName: propsValue.filename, + url: imageUrl, + }; + }, +}); diff --git a/packages/pieces/community/photoroom/tsconfig.json b/packages/pieces/community/photoroom/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/photoroom/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/photoroom/tsconfig.lib.json b/packages/pieces/community/photoroom/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/photoroom/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/pipedrive/.babelrc b/packages/pieces/community/pipedrive/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/pipedrive/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/pipedrive/.eslintrc.json b/packages/pieces/community/pipedrive/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/pipedrive/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/pipedrive/README.md b/packages/pieces/community/pipedrive/README.md new file mode 100644 index 0000000..9d78b6d --- /dev/null +++ b/packages/pieces/community/pipedrive/README.md @@ -0,0 +1,7 @@ +# pieces-pipedrive + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-pipedrive` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/pipedrive/package.json b/packages/pieces/community/pipedrive/package.json new file mode 100644 index 0000000..7d04801 --- /dev/null +++ b/packages/pieces/community/pipedrive/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-pipedrive", + "version": "0.6.0" +} diff --git a/packages/pieces/community/pipedrive/project.json b/packages/pieces/community/pipedrive/project.json new file mode 100644 index 0000000..c0edeac --- /dev/null +++ b/packages/pieces/community/pipedrive/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-pipedrive", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/pipedrive/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/pipedrive", + "tsConfig": "packages/pieces/community/pipedrive/tsconfig.lib.json", + "packageJson": "packages/pieces/community/pipedrive/package.json", + "main": "packages/pieces/community/pipedrive/src/index.ts", + "assets": [ + "packages/pieces/community/pipedrive/*.md", + { + "input": "packages/pieces/community/pipedrive/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/pipedrive/src/index.ts b/packages/pieces/community/pipedrive/src/index.ts new file mode 100644 index 0000000..93b6f5d --- /dev/null +++ b/packages/pieces/community/pipedrive/src/index.ts @@ -0,0 +1,123 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { OAuth2PropertyValue, PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { newActivity } from './lib/trigger/new-activity'; +import { newDeal } from './lib/trigger/new-deal'; +import { newPerson } from './lib/trigger/new-person'; +import { updatedDeal } from './lib/trigger/updated-deal'; +import { updatedPerson } from './lib/trigger/updated-person'; +import { newLeadTrigger } from './lib/trigger/new-lead'; +import { newOrganizationTrigger } from './lib/trigger/new-organization'; +import { updatedOrganizationTrigger } from './lib/trigger/updated-organization'; +import { updatedDealStageTrigger } from './lib/trigger/updated-deal-stage'; +import { createPersonAction } from './lib/actions/create-person'; +import { updatePersonAction } from './lib/actions/update-person'; +import { createOrganizationAction } from './lib/actions/create-organization'; +import { updateOrganizationAction } from './lib/actions/update-organization'; +import { createLeadAction } from './lib/actions/create-lead'; +import { updateLeadAction } from './lib/actions/update-lead'; +import { createDealAction } from './lib/actions/create-deal'; +import { updateDealAction } from './lib/actions/update-deal'; +import { createProductAction } from './lib/actions/create-product'; +import { addProductToDealAction } from './lib/actions/add-product-to-deal'; +import { addLabelToPersonAction } from './lib/actions/add-label-to-person'; +import { createActivityAction } from './lib/actions/create-activity'; +import { updateActivityAction } from './lib/actions/update-activity'; +import { attachFileAction } from './lib/actions/attach-file'; +import { addFollowerAction } from './lib/actions/add-follower'; +import { createNoteAction } from './lib/actions/create-note'; +import { getNoteAction } from './lib/actions/get-note'; +import { findUserAction } from './lib/actions/find-user'; +import { findProductAction } from './lib/actions/find-product'; +import { organizationMatchingFilterTrigger } from './lib/trigger/organization-matching-filter'; +import { personMatchingFilterTrigger } from './lib/trigger/person-matching-filter'; +import { activityMatchingFilterTrigger } from './lib/trigger/activity-matching-filter'; +import { dealMatchingFilterTrigger } from './lib/trigger/deal-matching-filter'; +import { newNoteTrigger } from './lib/trigger/new-note'; +import { findDealsAssociatedWithPersonAction } from './lib/actions/find-deals-associated-with-person'; +import { findProductsAction } from './lib/actions/find-products'; +import { getProductAction } from './lib/actions/get-product'; +import { findNotesAction } from './lib/actions/find-notes'; +import { findOrganizationAction } from './lib/actions/find-organization'; +import { findPersonAction } from './lib/actions/find-person'; +import { findDealAction } from './lib/actions/find-deal'; +import { findActivityAction } from './lib/actions/find-activity'; + +export const pipedriveAuth = PieceAuth.OAuth2({ + description: '', + authUrl: 'https://oauth.pipedrive.com/oauth/authorize', + tokenUrl: 'https://oauth.pipedrive.com/oauth/token', + required: true, + scope: [ + 'admin', + 'contacts:full', + 'users:read', + 'deals:full', + 'activities:full', + 'leads:full', + 'products:full', + ], +}); + +export const pipedrive = createPiece({ + displayName: 'Pipedrive', + description: 'Sales CRM and pipeline management software', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/pipedrive.png', + categories: [PieceCategory.SALES_AND_CRM], + auth: pipedriveAuth, + actions: [ + addFollowerAction, + getNoteAction, + createNoteAction, + addLabelToPersonAction, + addProductToDealAction, + attachFileAction, + createActivityAction, + updateActivityAction, + createDealAction, + updateDealAction, + createLeadAction, + updateLeadAction, + createOrganizationAction, + updateOrganizationAction, + createPersonAction, + updatePersonAction, + createProductAction, + findDealsAssociatedWithPersonAction, + findProductAction, + findProductsAction, + findNotesAction, + getProductAction, + findOrganizationAction, + findPersonAction, + findDealAction, + findActivityAction, + findUserAction, + createCustomApiCallAction({ + baseUrl: () => 'https://api.pipedrive.com/v1', + auth: pipedriveAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + authors: ['ashrafsamhouri', 'kishanprmr', 'MoShizzle', 'khaledmashaly', 'abuaboud'], + triggers: [ + newPerson, + newDeal, + newActivity, + newNoteTrigger, + updatedPerson, + updatedDeal, + updatedDealStageTrigger, + newLeadTrigger, + newOrganizationTrigger, + updatedOrganizationTrigger, + activityMatchingFilterTrigger, + dealMatchingFilterTrigger, + personMatchingFilterTrigger, + organizationMatchingFilterTrigger, + ], +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/add-follower.ts b/packages/pieces/community/pipedrive/src/lib/actions/add-follower.ts new file mode 100644 index 0000000..b17b3d5 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/add-follower.ts @@ -0,0 +1,82 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { ownerIdProp } from '../common/props'; +import { pipedriveApiCall } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const addFollowerAction = createAction({ + auth: pipedriveAuth, + name: 'add-follower', + displayName: 'Add Follower', + description: 'Adds a follower to a deal, person, organization or product.', + props: { + followerId: ownerIdProp('Follower', true), + entity: Property.StaticDropdown({ + displayName: 'Target Object', + description: 'Type of object to add the follower to.', + required: true, + options: { + disabled: false, + options: [ + { + label: 'Deal', + value: 'deal', + }, + { + label: 'Person', + value: 'person', + }, + { + label: 'Organization', + value: 'organization', + }, + { + label: 'Product', + value: 'product', + }, + ], + }, + }), + entityId: Property.ShortText({ + displayName: 'Target Object ID', + description: 'ID of the object to add the follower to.', + required:true + }), + }, + async run(context) { + const { followerId, entity, entityId } = context.propsValue; + + let endpoint = ''; + + switch (entity) { + case 'deal': + endpoint = `/deals/${entityId}/followers`; + break; + case 'organization': + endpoint = `/organizations/${entityId}/followers`; + break; + case 'person': + endpoint = `/persons/${entityId}/followers`; + break; + case 'product': + endpoint = `/products/${entityId}/followers`; + break; + } + + if (!endpoint) { + throw new Error(`Invalid object type: ${entity}`); + } + + const response = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.POST, + resourceUri: endpoint, + body: { + user_id: followerId, + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/add-label-to-person.ts b/packages/pieces/community/pipedrive/src/lib/actions/add-label-to-person.ts new file mode 100644 index 0000000..44e5f72 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/add-label-to-person.ts @@ -0,0 +1,58 @@ +import { pipedriveAuth } from '../../index'; +import { createAction } from '@activepieces/pieces-framework'; +import { labelIdsProp, personIdProp } from '../common/props'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { GetField, PersonCreateResponse } from '../common/types'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const addLabelToPersonAction = createAction({ + auth: pipedriveAuth, + name: 'add-labels-to-person', + displayName: 'Add Labels to Person', + description: 'Adds an existing labels to an existing person.', + props: { + personId: personIdProp(true), + labelIds: labelIdsProp('person', 'label_ids', true), + }, + async run(context) { + const { personId } = context.propsValue; + const labelIds = (context.propsValue.labelIds as number[]) ?? []; + + const personDefaultFields: Record = {}; + + if (labelIds.length > 0) { + personDefaultFields.label_ids = labelIds; + } + + const updatedPersonResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.PUT, + resourceUri: `/persons/${personId}`, + body: { + ...personDefaultFields, + }, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/personFields', + }); + + const updatedPersonProperties = pipedriveTransformCustomFields( + customFieldsResponse, + updatedPersonResponse.data, + ); + + return { + ...updatedPersonResponse, + data: updatedPersonProperties, + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/add-product-to-deal.ts b/packages/pieces/community/pipedrive/src/lib/actions/add-product-to-deal.ts new file mode 100644 index 0000000..a1864c6 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/add-product-to-deal.ts @@ -0,0 +1,113 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { dealIdProp, productIdProp } from '../common/props'; +import { pipedriveApiCall } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const addProductToDealAction = createAction({ + auth: pipedriveAuth, + name: 'add-product-to-deal', + displayName: 'Add Product to Deal', + description: 'Adds a product to a deal.', + props: { + dealId: dealIdProp(true), + productId: productIdProp(true), + price: Property.Number({ + displayName: 'Price', + required: true, + }), + quantity: Property.Number({ + displayName: 'Quantity', + required: true, + }), + discount: Property.Number({ + displayName: 'Discount', + required: false, + }), + discountType: Property.StaticDropdown({ + displayName: 'Discount Type', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Percentage', + value: 'percentage', + }, + { + label: 'Amount', + value: 'amount', + }, + ], + }, + }), + comments: Property.LongText({ + displayName: 'Comments', + required: false, + }), + enableProduct: Property.Checkbox({ + displayName: 'Enable Product?', + required: false, + defaultValue: true, + }), + taxMethod: Property.StaticDropdown({ + displayName: 'Tax Method', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Exclusive', + value: 'exclusive', + }, + { + label: 'Inclusive', + value: 'inclusive', + }, + { + label: 'None', + value: 'none', + }, + ], + }, + }), + taxPercentage: Property.Number({ + displayName: 'Tax Percentage', + required: false, + }), + }, + async run(context) { + const { + productId, + dealId, + price, + quantity, + discountType, + discount, + comments, + enableProduct, + taxPercentage, + taxMethod, + } = context.propsValue; + + const response = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.POST, + resourceUri: `/deals/${dealId}/products`, + body: { + product_id: productId, + item_price: price, + quantity, + discount_type: discountType, + discount, + comments, + enable_product: enableProduct, + tax: taxPercentage, + tax_method: taxMethod, + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/attach-file.ts b/packages/pieces/community/pipedrive/src/lib/actions/attach-file.ts new file mode 100644 index 0000000..d09ad48 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/attach-file.ts @@ -0,0 +1,58 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { dealIdProp, organizationIdProp, personIdProp, productIdProp } from '../common/props'; +import FormData from 'form-data'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const attachFileAction = createAction({ + auth: pipedriveAuth, + name: 'attach-file', + displayName: 'Attach File', + description: 'Uploads a file and attaches it to a deal,person,organization,activity or product.', + props: { + file: Property.File({ + displayName: 'File', + required: true, + }), + fileName: Property.ShortText({ + displayName: 'File Name', + required: true, + }), + dealId: dealIdProp(false), + personId: personIdProp(false), + organizationId: organizationIdProp(false), + productId: productIdProp(false), + activityId: Property.Number({ + displayName: 'Activity ID', + required: false, + }), + }, + async run(context) { + const { file, fileName, dealId, personId, organizationId, productId, activityId } = + context.propsValue; + + const formatData = new FormData(); + + formatData.append('file', file.data, fileName); + if (dealId) formatData.append('deal_id', dealId); + if (personId) formatData.append('person_id', personId); + if (organizationId) formatData.append('org_id', organizationId); + if (productId) formatData.append('product_id', productId); + if (activityId) formatData.append('activity_id', activityId); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.data['api_domain']}/api/v1/files`, + body: formatData, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + headers: { + ...formatData.getHeaders(), + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/create-activity.ts b/packages/pieces/community/pipedrive/src/lib/actions/create-activity.ts new file mode 100644 index 0000000..67a3bff --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/create-activity.ts @@ -0,0 +1,71 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { activityCommonProps } from '../common/props'; +import { pipedriveApiCall } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; +import dayjs from 'dayjs'; + +export const createActivityAction = createAction({ + auth: pipedriveAuth, + name: 'create-activity', + displayName: 'Create Activity', + description: 'Creates a new activity.', + props: { + subject: Property.ShortText({ + displayName: 'Subject', + required: true, + }), + ...activityCommonProps, + }, + async run(context) { + const { + subject, + organizationId, + personId, + dealId, + leadId, + assignTo, + type, + dueDate, + dueTime, + duration, + idDone, + isBusy, + note, + publicDescription, + } = context.propsValue; + + const activityDefaultFields: Record = { + subject, + org_id: organizationId, + person_id: personId, + deal_id: dealId, + lead_id: leadId, + note, + public_description: publicDescription, + type, + user_id: assignTo, + due_time: dueTime, + duration, + done: idDone ? 1 : 0, + }; + + if (isBusy) { + activityDefaultFields.busy_flag = isBusy === 'busy' ? true : false; + } + + if (dueDate) { + activityDefaultFields.due_date = dayjs(dueDate).format('YYYY-MM-DD'); + } + + const response = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.POST, + resourceUri: '/activities', + body: activityDefaultFields, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/create-deal.ts b/packages/pieces/community/pipedrive/src/lib/actions/create-deal.ts new file mode 100644 index 0000000..bf40640 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/create-deal.ts @@ -0,0 +1,103 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { dealCommonProps } from '../common/props'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { GetField, OrganizationCreateResponse } from '../common/types'; +import dayjs from 'dayjs'; + +export const createDealAction = createAction({ + auth: pipedriveAuth, + name: 'create-deal', + displayName: 'Create Deal', + description: 'Creates a new deal.', + props: { + title: Property.ShortText({ + displayName: 'Title', + required: true, + }), + ...dealCommonProps, + }, + async run(context) { + const { + title, + dealValue, + dealValueCurrency, + expectedCloseDate, + visibleTo, + probability, + stageId, + status, + pipelineId, + ownerId, + organizationId, + personId, + creationTime, + } = context.propsValue; + + const labelIds = (context.propsValue.labelIds as number[]) ?? []; + const customFields = context.propsValue.customfields ?? {}; + + const dealDefaultFields: Record = { + title, + pipeline_id: pipelineId, + stage_id: stageId, + status, + add_time: creationTime, + probability, + visible_to: visibleTo, + user_id: ownerId, + org_id: organizationId, + person_id: personId, + value: dealValue, + currency: dealValueCurrency, + }; + + if (labelIds.length > 0) { + dealDefaultFields.label = labelIds; + } + + if (expectedCloseDate) { + dealDefaultFields.expected_close_date = dayjs(expectedCloseDate).format('YYYY-MM-DD'); + } + + const dealCustomFields: Record = {}; + + Object.entries(customFields).forEach(([key, value]) => { + // Format values if they are arrays + dealCustomFields[key] = Array.isArray(value) ? value.join(',') : value; + }); + + const createdDealResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.POST, + resourceUri: '/deals', + body: { + ...dealDefaultFields, + ...dealCustomFields, + }, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/dealFields', + }); + + const updatedLeadProperties = pipedriveTransformCustomFields( + customFieldsResponse, + createdDealResponse.data, + ); + + return { + ...createdDealResponse, + data: updatedLeadProperties, + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/create-lead.ts b/packages/pieces/community/pipedrive/src/lib/actions/create-lead.ts new file mode 100644 index 0000000..8be5010 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/create-lead.ts @@ -0,0 +1,111 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { leadCommonProps } from '../common/props'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { GetField, OrganizationCreateResponse } from '../common/types'; +import { HttpMethod } from '@activepieces/pieces-common'; +import dayjs from 'dayjs'; + +export const createLeadAction = createAction({ + auth: pipedriveAuth, + name: 'create-lead', + displayName: 'Create Lead', + description: 'Creates a new lead.', + props: { + title: Property.ShortText({ + displayName: 'Title', + required: true, + }), + ...leadCommonProps, + }, + async run(context) { + const { + title, + ownerId, + channel, + organizationId, + personId, + expectedCloseDate, + visibleTo, + leadValue, + leadValueCurrency, + } = context.propsValue; + + if (!personId && !organizationId) { + throw new Error( + 'Neither an Organization nor a Person were provided. One of them must be provided in order to create a lead.', + ); + } + + const labelIds = (context.propsValue.labelIds as string[]) ?? []; + const customFields = context.propsValue.customfields ?? {}; + + const leadDefaultFields: Record = { + title, + owner_id: ownerId, + organization_id: organizationId, + person_id: personId, + channel: channel, + visible_to: visibleTo, + }; + + if (labelIds.length > 0) { + leadDefaultFields.label_ids = labelIds; + } + + if(expectedCloseDate) + { + leadDefaultFields.expected_close_date= dayjs(expectedCloseDate).format('YYYY-MM-DD') + + } + + if (leadValue) { + if (!leadValueCurrency) { + throw new Error('lead Value Currency is required when lead Value is provided'); + } + leadDefaultFields.value = { + amount: leadValue, + currency: leadValueCurrency, + }; + } + + const leadCustomFields: Record = {}; + + Object.entries(customFields).forEach(([key, value]) => { + // Format values if they are arrays + leadCustomFields[key] = Array.isArray(value) ? value.join(',') : value; + }); + + const createdLeadResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.POST, + resourceUri: '/leads', + body: { + ...leadDefaultFields, + ...leadCustomFields, + }, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/dealFields', + }); + + const updatedLeadProperties = pipedriveTransformCustomFields( + customFieldsResponse, + createdLeadResponse.data, + ); + + return { + ...createdLeadResponse, + data: updatedLeadProperties, + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/create-note.ts b/packages/pieces/community/pipedrive/src/lib/actions/create-note.ts new file mode 100644 index 0000000..08b490f --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/create-note.ts @@ -0,0 +1,81 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { dealIdProp, leadIdProp, organizationIdProp, personIdProp } from '../common/props'; +import { pipedriveApiCall } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const createNoteAction = createAction({ + auth: pipedriveAuth, + name: 'create-note', + displayName: 'Create Note', + description: 'Creates a new note.', + props: { + content: Property.LongText({ + displayName: 'Content', + required: true, + }), + dealId: dealIdProp(false), + pinnedToDeal: Property.Checkbox({ + displayName: 'Pin note to deal?', + required: false, + defaultValue: false, + }), + personId: personIdProp(false), + pinnedToPerson: Property.Checkbox({ + displayName: 'Pin note to person?', + required: false, + defaultValue: false, + }), + organizationId: organizationIdProp(false), + pinnedToOrganization: Property.Checkbox({ + displayName: 'Pin note to organization?', + required: false, + defaultValue: false, + }), + leadId: leadIdProp(false), + pinnedToLead: Property.Checkbox({ + displayName: 'Pin note to lead?', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { + content, + dealId, + personId, + organizationId, + leadId, + pinnedToDeal, + pinnedToPerson, + pinnedToOrganization, + pinnedToLead, + } = context.propsValue; + + if(!dealId && !personId && !organizationId && !leadId){ + throw new Error("Note must be associated with at least one organization, person, deal, lead or project."); + } + + const noteDefaultFields: Record = { + content, + pinned_to_deal_flag: pinnedToDeal ? 1 : 0, + pinned_to_person_flag: pinnedToPerson ? 1 : 0, + pinned_to_organization_flag: pinnedToOrganization ? 1 : 0, + pinned_to_lead_flag: pinnedToLead ? 1 : 0, + lead_id: leadId, + person_id: personId, + org_id: organizationId, + deal_id: dealId, + }; + + const response = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.POST, + resourceUri: '/notes', + body: noteDefaultFields, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/create-organization.ts b/packages/pieces/community/pipedrive/src/lib/actions/create-organization.ts new file mode 100644 index 0000000..37dfeed --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/create-organization.ts @@ -0,0 +1,76 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { organizationCommonProps } from '../common/props'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { GetField, OrganizationCreateResponse } from '../common/types'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const createOrganizationAction = createAction({ + auth: pipedriveAuth, + name: 'create-organization', + displayName: 'Create Organization', + description: 'Creates a new organization.', + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + ...organizationCommonProps, + }, + async run(context) { + const { name, ownerId, address, visibleTo } = context.propsValue; + + const labelIds = (context.propsValue.labelIds as number[]) ?? []; + const customFields = context.propsValue.customfields ?? {}; + + const organizationDefaultFields: Record = { + name: name, + owner_id: ownerId, + visible_to: visibleTo, + address: address, + }; + + if (labelIds.length > 0) { + organizationDefaultFields.label_ids = labelIds; + } + + const organizationCustomFields: Record = {}; + + Object.entries(customFields).forEach(([key, value]) => { + // Format values if they are arrays + organizationCustomFields[key] = Array.isArray(value) ? value.join(',') : value; + }); + + const createdOrganizationResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.POST, + resourceUri: '/organizations', + body: { + ...organizationDefaultFields, + ...organizationCustomFields, + }, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/organizationFields', + }); + + const updatedOrganizationProperties = pipedriveTransformCustomFields( + customFieldsResponse, + createdOrganizationResponse.data, + ); + + return { + ...createdOrganizationResponse, + data: updatedOrganizationProperties, + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/create-person.ts b/packages/pieces/community/pipedrive/src/lib/actions/create-person.ts new file mode 100644 index 0000000..58ce4e7 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/create-person.ts @@ -0,0 +1,89 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { personCommonProps } from '../common/props'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { GetField, PersonCreateResponse } from '../common/types'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const createPersonAction = createAction({ + auth: pipedriveAuth, + name: 'create-person', + displayName: 'Create Person', + description: 'Creates a new person.', + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + ...personCommonProps, + }, + async run(context) { + const { name, ownerId, organizationId, marketing_status, visibleTo, firstName, lastName } = + context.propsValue; + const phone = (context.propsValue.phone as string[]) ?? []; + const email = (context.propsValue.email as string[]) ?? []; + const labelIds = (context.propsValue.labelIds as number[]) ?? []; + const customFields = context.propsValue.customfields ?? {}; + + const personDefaultFields: Record = { + name: name, + owner_id: ownerId, + org_id: organizationId, + marketing_status: marketing_status, + visible_to: visibleTo, + first_name: firstName, + last_name: lastName, + }; + + if (phone.length > 0) { + personDefaultFields.phone = phone; + } + + if (email.length > 0) { + personDefaultFields.email = email; + } + + if (labelIds.length > 0) { + personDefaultFields.label_ids = labelIds; + } + + const personCustomFields: Record = {}; + + Object.entries(customFields).forEach(([key, value]) => { + // Format values if they are arrays + personCustomFields[key] = Array.isArray(value) ? value.join(',') : value; + }); + + const createdPersonResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.POST, + resourceUri: '/persons', + body: { + ...personDefaultFields, + ...personCustomFields, + }, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/personFields', + }); + + const updatedPersonProperties = pipedriveTransformCustomFields( + customFieldsResponse, + createdPersonResponse.data, + ); + + return { + ...createdPersonResponse, + data: updatedPersonProperties, + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/create-product.ts b/packages/pieces/community/pipedrive/src/lib/actions/create-product.ts new file mode 100644 index 0000000..fb0cce1 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/create-product.ts @@ -0,0 +1,138 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { customFieldsProp, ownerIdProp, visibleToProp } from '../common/props'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { GetField, OrganizationCreateResponse } from '../common/types'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const createProductAction = createAction({ + auth: pipedriveAuth, + name: 'create-product', + displayName: 'Create Product', + description: 'Creates a new product.', + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + code: Property.ShortText({ + displayName: 'Code', + required: false, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + unit: Property.ShortText({ + displayName: 'Unit', + required: false, + }), + tax: Property.Number({ + displayName: 'Tax percentage', + required: false, + }), + isActive: Property.Checkbox({ + displayName: 'Is Active ?', + required: false, + }), + ownerId: ownerIdProp('Owner',false), + currency: Property.ShortText({ + displayName: 'Currency', + required: false, + description: 'Please enter currency code.', + }), + price: Property.Number({ + displayName: 'Price', + required: false, + }), + cost: Property.Number({ + displayName: 'Cost', + required: false, + }), + overheadCost: Property.Number({ + displayName: 'Overhead Cost', + required: false, + }), + visibleTo: visibleToProp, + customfields: customFieldsProp('product'), + }, + async run(context) { + const { + name, + code, + description, + unit, + tax, + isActive, + ownerId, + currency, + price, + cost, + overheadCost, + visibleTo, + } = context.propsValue; + + const customFields = context.propsValue.customfields ?? {}; + + const productDefaultFields: Record = { + name, + code, + description, + unit, + tax, + active_flag: isActive, + prices: [ + { + price: price ?? 0, + currency: currency ?? 'USD', + cost: cost ?? 0, + overhead_cost: overheadCost ?? 0, + }, + ], + visible_to: visibleTo, + }; + + if (ownerId) { + productDefaultFields.owner_id = ownerId; + } + + const productCustomFields: Record = {}; + + Object.entries(customFields).forEach(([key, value]) => { + // Format values if they are arrays + productCustomFields[key] = Array.isArray(value) ? value.join(',') : value; + }); + + const createdProductResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.POST, + resourceUri: '/products', + body: { + ...productDefaultFields, + ...productCustomFields, + }, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/productFields', + }); + + const updatedProductProperties = pipedriveTransformCustomFields( + customFieldsResponse, + createdProductResponse.data, + ); + + return { + ...createdProductResponse, + data: updatedProductProperties, + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/find-activity.ts b/packages/pieces/community/pipedrive/src/lib/actions/find-activity.ts new file mode 100644 index 0000000..a518644 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/find-activity.ts @@ -0,0 +1,83 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { activityTypeIdProp, filterIdProp, ownerIdProp } from '../common/props'; +import { pipedrivePaginatedApiCall } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; + +export const findActivityAction = createAction({ + auth: pipedriveAuth, + name: 'find-activity', + displayName: 'Find Activity', + description: 'Finds an activity by subject.', + props: { + subject: Property.ShortText({ + displayName: 'Subject', + required: true, + }), + exactMatch: Property.Checkbox({ + displayName: 'Exact Match', + required: false, + defaultValue: true, + }), + assignTo: ownerIdProp('Assign To', false), + type: activityTypeIdProp(false), + filterId: filterIdProp('activity', false), + status: Property.StaticDropdown({ + displayName: 'Status', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Done', + value: 1, + }, + { + label: 'Not Done', + value: 0, + }, + ], + }, + }), + }, + async run(context) { + const { subject, assignTo, type, filterId, status, exactMatch } = context.propsValue; + + const response = await pipedrivePaginatedApiCall<{ id: number; subject: string }>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/activities', + query: { + sort: 'update_time DESC', + filter_id: filterId, + user_id: assignTo, + type, + done: status, + }, + }); + + if (isNil(response) || response.length === 0) { + return { + found: false, + data: [], + }; + } + + const result = []; + + for (const activity of response) { + if (exactMatch && activity.subject === subject) { + result.push(activity); + } else if (!exactMatch && activity.subject.toLowerCase().includes(subject.toLowerCase())) { + result.push(activity); + } + } + + return { + found: result.length > 0, + data: result, + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/find-deal.ts b/packages/pieces/community/pipedrive/src/lib/actions/find-deal.ts new file mode 100644 index 0000000..88df4c5 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/find-deal.ts @@ -0,0 +1,114 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { pipedriveAuth } from '../../index'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { GetField } from '../common/types'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; +import { searchFieldProp, searchFieldValueProp } from '../common/props'; + +export const findDealAction = createAction({ + auth: pipedriveAuth, + name: 'find-deal', + displayName: 'Find Deal', + description: 'Finds a deal by any field.', + props: { + searchField: searchFieldProp('deal'), + searchFieldValue: searchFieldValueProp('deal'), + }, + async run(context) { + const { searchField } = context.propsValue; + const fieldValue = context.propsValue.searchFieldValue['field_value']; + + if (isNil(fieldValue)) { + throw new Error('Please enter a value for the field'); + } + + // create Filter + const filter = await pipedriveApiCall<{ data: { id: number } }>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.POST, + resourceUri: '/filters', + body: { + name: 'Activepieces Find Deal Filter', + type: 'deals', + conditions: { + glue: 'and', + conditions: [ + { + glue: 'and', + conditions: [ + { + object: 'deal', + field_id: searchField, + operator: '=', + value: fieldValue, + }, + ], + }, + { + glue: 'or', + conditions: [ + { + object: 'deal', + field_id: searchField, + operator: 'IS NOT NULL', + value: null, + }, + ], + }, + ], + }, + }, + }); + + // search for deals using the filter + const deals = await pipedriveApiCall<{ data: { id: number }[] }>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/deals', + query: { + filter_id: filter.data.id, + limit: 1, + sort: 'update_time DESC', + }, + }); + + // delete the filter + await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.DELETE, + resourceUri: `/filters/${filter.data.id}`, + }); + + if (isNil(deals.data) || deals.data.length === 0) { + return { + found: false, + data: [], + }; + } + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/dealFields', + }); + + const updatedDealProperties = pipedriveTransformCustomFields( + customFieldsResponse, + deals.data[0], + ); + + return { + found: true, + data: [updatedDealProperties], + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/find-deals-associated-with-person.ts b/packages/pieces/community/pipedrive/src/lib/actions/find-deals-associated-with-person.ts new file mode 100644 index 0000000..28f9795 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/find-deals-associated-with-person.ts @@ -0,0 +1,53 @@ +import { pipedriveAuth } from '../../index'; +import { createAction } from '@activepieces/pieces-framework'; +import { personIdProp } from '../common/props'; +import { pipedrivePaginatedApiCall, pipedriveTransformCustomFields } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; +import { GetField } from '../common/types'; + +export const findDealsAssociatedWithPersonAction = createAction({ + auth: pipedriveAuth, + name: 'find-deals-associated-with-person', + displayName: 'Find Deals Associated With Person', + description: 'Finds multiple deals related to a specific person.', + props: { + personId: personIdProp(true), + }, + async run(context) { + const { personId } = context.propsValue; + + const deals = await pipedrivePaginatedApiCall>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: `/persons/${personId}/deals`, + query: { sort: 'update_time DESC' }, + }); + + if (isNil(deals) || deals.length == 0) { + return { + found: false, + data: [], + }; + } + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/dealFields', + }); + + const result = []; + for (const deal of deals) { + const updatedDealProperties = pipedriveTransformCustomFields(customFieldsResponse, deal); + result.push(updatedDealProperties); + } + + return { + found: true, + data: result, + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/find-notes.ts b/packages/pieces/community/pipedrive/src/lib/actions/find-notes.ts new file mode 100644 index 0000000..5c11da8 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/find-notes.ts @@ -0,0 +1,67 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { pipedrivePaginatedApiCall } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; + +export const findNotesAction = createAction({ + auth: pipedriveAuth, + name: 'find-notes', + displayName: 'Find Notes', + description: 'Finds notes by Deal,Lead,Person, or Organization ID.', + props: { + objectType: Property.StaticDropdown({ + displayName: 'Search By', + required: true, + options: { + disabled: false, + options: [ + { + label: 'Deal', + value: 'deal_id', + }, + { + label: 'Lead', + value: 'lead_id', + }, + { + label: 'Person', + value: 'person_id', + }, + { + label: 'Organization', + value: 'org_id', + }, + ], + }, + }), + objectId: Property.ShortText({ + displayName: 'ID', + required: true, + }), + }, + async run(context) { + const response = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: `/notes`, + query: { + sort: 'update_time DESC', + [context.propsValue.objectType]:context.propsValue.objectId + }, + }); + + if (isNil(response) || response.length === 0) { + return { + found: false, + data: [], + }; + } + + return { + found: response.length > 0, + data: response, + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/find-organization.ts b/packages/pieces/community/pipedrive/src/lib/actions/find-organization.ts new file mode 100644 index 0000000..848fae8 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/find-organization.ts @@ -0,0 +1,114 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { pipedriveAuth } from '../../index'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { GetField } from '../common/types'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; +import { searchFieldProp, searchFieldValueProp } from '../common/props'; + +export const findOrganizationAction = createAction({ + auth: pipedriveAuth, + name: 'find-organization', + displayName: 'Find Organization', + description: 'Finds an organization.', + props: { + searchField: searchFieldProp('organization'), + searchFieldValue: searchFieldValueProp('organization'), + }, + async run(context) { + const { searchField } = context.propsValue; + const fieldValue = context.propsValue.searchFieldValue['field_value']; + + if (isNil(fieldValue)) { + throw new Error('Please enter a value for the field'); + } + + // create Filter + const filter = await pipedriveApiCall<{ data: { id: number } }>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.POST, + resourceUri: '/filters', + body: { + name: 'Activepieces Find Organization Filter', + type: 'org', + conditions: { + glue: 'and', + conditions: [ + { + glue: 'and', + conditions: [ + { + object: 'organization', + field_id: searchField, + operator: '=', + value: fieldValue, + }, + ], + }, + { + glue: 'or', + conditions: [ + { + object: 'organization', + field_id: searchField, + operator: 'IS NOT NULL', + value: null, + }, + ], + }, + ], + }, + }, + }); + + // search for organizations using the filter + const organizations = await pipedriveApiCall<{ data: { id: number }[] }>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/organizations', + query: { + filter_id: filter.data.id, + limit: 1, + sort: 'update_time DESC', + }, + }); + + // delete the filter + await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.DELETE, + resourceUri: `/filters/${filter.data.id}`, + }); + + if (isNil(organizations.data) || organizations.data.length === 0) { + return { + found: false, + data: [], + }; + } + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/organizationFields', + }); + + const updatedOrganizationProperties = pipedriveTransformCustomFields( + customFieldsResponse, + organizations.data[0], + ); + + return { + found: true, + data: [updatedOrganizationProperties], + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/find-person.ts b/packages/pieces/community/pipedrive/src/lib/actions/find-person.ts new file mode 100644 index 0000000..9460361 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/find-person.ts @@ -0,0 +1,114 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { pipedriveAuth } from '../../index'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { GetField } from '../common/types'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; +import { searchFieldProp, searchFieldValueProp } from '../common/props'; + +export const findPersonAction = createAction({ + auth: pipedriveAuth, + name: 'find-person', + displayName: 'Find Person', + description: 'Finds a person.', + props: { + searchField: searchFieldProp('person'), + searchFieldValue: searchFieldValueProp('person'), + }, + async run(context) { + const { searchField } = context.propsValue; + const fieldValue = context.propsValue.searchFieldValue['field_value']; + + if (isNil(fieldValue)) { + throw new Error('Please enter a value for the field'); + } + + // create Filter + const filter = await pipedriveApiCall<{ data: { id: number } }>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.POST, + resourceUri: '/filters', + body: { + name: 'Activepieces Find Person Filter', + type: 'people', + conditions: { + glue: 'and', + conditions: [ + { + glue: 'and', + conditions: [ + { + object: 'person', + field_id: searchField, + operator: '=', + value: fieldValue, + }, + ], + }, + { + glue: 'or', + conditions: [ + { + object: 'person', + field_id: searchField, + operator: 'IS NOT NULL', + value: null, + }, + ], + }, + ], + }, + }, + }); + + // search for persons using the filter + const persons = await pipedriveApiCall<{ data: { id: number }[] }>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/persons', + query: { + filter_id: filter.data.id, + limit: 1, + sort: 'update_time DESC', + }, + }); + + // delete the filter + await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.DELETE, + resourceUri: `/filters/${filter.data.id}`, + }); + + if (isNil(persons.data) || persons.data.length === 0) { + return { + found: false, + data: [], + }; + } + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/personFields', + }); + + const updatedPersonProperties = pipedriveTransformCustomFields( + customFieldsResponse, + persons.data[0], + ); + + return { + found: true, + data: [updatedPersonProperties], + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/find-product.ts b/packages/pieces/community/pipedrive/src/lib/actions/find-product.ts new file mode 100644 index 0000000..1240ca9 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/find-product.ts @@ -0,0 +1,69 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { GetDealResponse, GetField } from '../common/types'; + +export const findProductAction = createAction({ + auth: pipedriveAuth, + name: 'find-product', + displayName: 'Find Product', + description: 'Find a product by name.', + props: { + searchTerm: Property.ShortText({ + displayName: 'Search Term', + required: true, + }), + }, + async run(context) { + const response = await pipedriveApiCall<{ + success: boolean; + data: { items: Array<{ item: { id: number } }> }; + }>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/products/search', + query: { + term: context.propsValue.searchTerm, + fields: 'name', + limit: 1, + }, + }); + + if (response.data.items.length === 0) { + return { + found: false, + data: [], + }; + } + + const productResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: `/products/${response.data.items[0].item.id}`, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/productFields', + }); + + const updatedProductProperties = pipedriveTransformCustomFields( + customFieldsResponse, + productResponse.data, + ); + + return { + found: true, + data: [updatedProductProperties], + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/find-products.ts b/packages/pieces/community/pipedrive/src/lib/actions/find-products.ts new file mode 100644 index 0000000..3566a52 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/find-products.ts @@ -0,0 +1,88 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { GetField } from '../common/types'; +import { isNil } from '@activepieces/shared'; + +export const findProductsAction = createAction({ + auth: pipedriveAuth, + name: 'find-products', + displayName: 'Find Products', + description: 'Finds a product or products by name or product code.', + props: { + field: Property.StaticDropdown({ + displayName: 'Field to search by', + required: true, + defaultValue: 'name', + options: { + disabled: false, + options: [ + { + label: 'Name', + value: 'name', + }, + { + label: 'Product Code', + value: 'code', + }, + ], + }, + }), + fieldValue: Property.ShortText({ + displayName: 'Field Value', + required: true, + }), + }, + async run(context) { + const products = []; + let hasMoreItems = true; + + do { + const qs = { + term: context.propsValue.fieldValue, + fields: context.propsValue.field, + limit: 500, + start: 0, + exact_match: 'true', + }; + const response = await pipedriveApiCall<{ + success: boolean; + data: { items: Array<{ item: { id: number } }> }; + additional_data: { + pagination: { + start: number; + limit: number; + more_items_in_collection: boolean; + next_start: number; + }; + }; + }>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/products/search', + query: qs, + }); + + if (isNil(response.data.items)) break; + + for(const product of response.data.items) + { + products.push(product.item); + } + + hasMoreItems = response.additional_data.pagination.more_items_in_collection; + qs.start = response.additional_data.pagination.next_start; + } while (hasMoreItems); + + return { + found: products.length > 0, + data: products, + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/find-user.ts b/packages/pieces/community/pipedrive/src/lib/actions/find-user.ts new file mode 100644 index 0000000..d729bca --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/find-user.ts @@ -0,0 +1,55 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { pipedriveApiCall } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const findUserAction = createAction({ + auth: pipedriveAuth, + name: 'find-user', + displayName: 'Find User', + description: 'Find a user by name or email.', + props: { + field: Property.StaticDropdown({ + displayName: 'Field to search by', + required: true, + options: { + disabled: false, + options: [ + { + label: 'Name', + value: 'name', + }, + { + label: 'Email', + value: 'email', + }, + ], + }, + }), + fieldValue: Property.ShortText({ + displayName: 'Field Value', + required: true, + }), + }, + async run(context) { + const { field, fieldValue } = context.propsValue; + + const response = await pipedriveApiCall<{ success: boolean; data: Array> }>( + { + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/users/find', + query: { + term: fieldValue, + search_by_email: field == 'email' ? 1 : 0, + }, + }, + ); + + return { + found: response.data.length === 0? false: true, + data: response.data + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/get-note.ts b/packages/pieces/community/pipedrive/src/lib/actions/get-note.ts new file mode 100644 index 0000000..832bab4 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/get-note.ts @@ -0,0 +1,35 @@ +import { pipedriveAuth } from "../../index"; +import { createAction, Property } from "@activepieces/pieces-framework"; +import { pipedriveApiCall } from "../common"; +import { HttpMethod } from "@activepieces/pieces-common"; + +export const getNoteAction = createAction({ + auth:pipedriveAuth, + name:'get-note', + displayName:'Retrieve a Note', + description:' Finds a note by ID.', + props:{ + noteId:Property.Number({ + displayName:'Note ID', + required:true + }) + }, + async run(context){ + try + { + const response = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri:`/notes/${context.propsValue.noteId}`, + }); + return response; + } + catch(error) + { + return { + success:false, + } + } + } +}) \ No newline at end of file diff --git a/packages/pieces/community/pipedrive/src/lib/actions/get-product.ts b/packages/pieces/community/pipedrive/src/lib/actions/get-product.ts new file mode 100644 index 0000000..0f258a9 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/get-product.ts @@ -0,0 +1,54 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { GetDealResponse, GetField } from '../common/types'; + +export const getProductAction = createAction({ + auth: pipedriveAuth, + name: 'get-product', + displayName: 'Retrieve a Product', + description: ' Finds a product by ID.', + props: { + productId: Property.Number({ + displayName: 'Product ID', + required: true, + }), + }, + async run(context) { + try { + const response = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: `/products/${context.propsValue.productId}`, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/productFields', + }); + + const updatedProductProperties = pipedriveTransformCustomFields( + customFieldsResponse, + response.data, + ); + + return { + found: true, + data: [updatedProductProperties], + }; + } catch (error) { + return { + found: false, + data:[] + }; + } + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/update-activity.ts b/packages/pieces/community/pipedrive/src/lib/actions/update-activity.ts new file mode 100644 index 0000000..0f55edc --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/update-activity.ts @@ -0,0 +1,76 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { activityCommonProps } from '../common/props'; +import { pipedriveApiCall } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; +import dayjs from 'dayjs'; + +export const updateActivityAction = createAction({ + auth: pipedriveAuth, + name: 'update-activity', + displayName: 'Update Activity', + description: 'Updates an existing activity.', + props: { + activityId: Property.Number({ + displayName: 'Activity', + required: true, + }), + subject: Property.ShortText({ + displayName: 'Subject', + required: false, + }), + ...activityCommonProps, + }, + async run(context) { + const { + activityId, + subject, + organizationId, + personId, + dealId, + leadId, + assignTo, + type, + dueDate, + dueTime, + duration, + idDone, + isBusy, + note, + publicDescription, + } = context.propsValue; + + const activityDefaultFields: Record = { + subject, + org_id: organizationId, + person_id: personId, + deal_id: dealId, + lead_id: leadId, + note, + public_description: publicDescription, + type, + user_id: assignTo, + due_time: dueTime, + duration, + done: idDone ? 1 : 0, + }; + + if (isBusy) { + activityDefaultFields.busy_flag = isBusy === 'busy' ? true : false; + } + + if (dueDate) { + activityDefaultFields.due_date = dayjs(dueDate).format('YYYY-MM-DD'); + } + + const response = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.PUT, + resourceUri: `/activities/${activityId}`, + body: activityDefaultFields, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/update-deal.ts b/packages/pieces/community/pipedrive/src/lib/actions/update-deal.ts new file mode 100644 index 0000000..852947b --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/update-deal.ts @@ -0,0 +1,105 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { dealCommonProps, dealIdProp } from '../common/props'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { GetField, OrganizationCreateResponse } from '../common/types'; +import dayjs from 'dayjs'; + +export const updateDealAction = createAction({ + auth: pipedriveAuth, + name: 'update-deal', + displayName: 'Update Deal', + description: 'Updates an existing deal.', + props: { + dealId: dealIdProp(true), + title: Property.ShortText({ + displayName: 'Title', + required: false, + }), + ...dealCommonProps, + }, + async run(context) { + const { + dealId, + title, + dealValue, + dealValueCurrency, + expectedCloseDate, + visibleTo, + probability, + stageId, + status, + pipelineId, + ownerId, + organizationId, + personId, + creationTime, + } = context.propsValue; + + const labelIds = (context.propsValue.labelIds as number[]) ?? []; + const customFields = context.propsValue.customfields ?? {}; + + const dealDefaultFields: Record = { + title, + pipeline_id: pipelineId, + stage_id: stageId, + status, + add_time: creationTime, + probability, + visible_to: visibleTo, + user_id: ownerId, + org_id: organizationId, + person_id: personId, + value: dealValue, + currency: dealValueCurrency, + }; + + if (labelIds.length > 0) { + dealDefaultFields.label = labelIds; + } + + if (expectedCloseDate) { + dealDefaultFields.expected_close_date = dayjs(expectedCloseDate).format('YYYY-MM-DD'); + } + + const dealCustomFields: Record = {}; + + Object.entries(customFields).forEach(([key, value]) => { + // Format values if they are arrays + dealCustomFields[key] = Array.isArray(value) && value.length > 0 ? value.join(',') : value; + }); + + const updatedDealResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.PUT, + resourceUri: `/deals/${dealId}`, + body: { + ...dealDefaultFields, + ...dealCustomFields, + }, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/dealFields', + }); + + const updatedLeadProperties = pipedriveTransformCustomFields( + customFieldsResponse, + updatedDealResponse.data, + ); + + return { + ...updatedDealResponse, + data: updatedLeadProperties, + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/update-lead.ts b/packages/pieces/community/pipedrive/src/lib/actions/update-lead.ts new file mode 100644 index 0000000..a3c35d8 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/update-lead.ts @@ -0,0 +1,105 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { leadCommonProps, leadIdProp } from '../common/props'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { GetField, OrganizationCreateResponse } from '../common/types'; +import { HttpMethod } from '@activepieces/pieces-common'; +import dayjs from 'dayjs'; + +export const updateLeadAction = createAction({ + auth: pipedriveAuth, + name: 'update-lead', + displayName: 'Update Lead', + description: 'Updates an existing lead.', + props: { + leadId: leadIdProp(true), + title: Property.ShortText({ + displayName: 'Title', + required: false, + }), + ...leadCommonProps, + }, + async run(context) { + const { + title, + ownerId, + leadId, + channel, + organizationId, + personId, + expectedCloseDate, + visibleTo, + leadValue, + leadValueCurrency, + } = context.propsValue; + + const labelIds = (context.propsValue.labelIds as string[]) ?? []; + const customFields = context.propsValue.customfields ?? {}; + + const leadDefaultFields: Record = { + title, + owner_id: ownerId, + organization_id: organizationId, + person_id: personId, + channel: channel, + visible_to: visibleTo, + }; + + if (labelIds.length > 0) { + leadDefaultFields.label_ids = labelIds; + } + + if (expectedCloseDate) { + leadDefaultFields.expected_close_date = dayjs(expectedCloseDate).format('YYYY-MM-DD'); + } + + if (leadValue) { + if (!leadValueCurrency) { + throw new Error('lead Value Currency is required when lead Value is provided'); + } + leadDefaultFields.value = { + amount: leadValue, + currency: leadValueCurrency, + }; + } + + const leadCustomFields: Record = {}; + + Object.entries(customFields).forEach(([key, value]) => { + // Format values if they are arrays + leadCustomFields[key] = Array.isArray(value) && value.length > 0 ? value.join(',') : value; + }); + + const updatedLeadResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.PATCH, + resourceUri: `/leads/${leadId}`, + body: { + ...leadDefaultFields, + ...leadCustomFields, + }, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/dealFields', + }); + + const updatedLeadProperties = pipedriveTransformCustomFields( + customFieldsResponse, + updatedLeadResponse.data, + ); + + return { + ...updatedLeadResponse, + data: updatedLeadProperties, + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/update-organization.ts b/packages/pieces/community/pipedrive/src/lib/actions/update-organization.ts new file mode 100644 index 0000000..6125d7d --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/update-organization.ts @@ -0,0 +1,78 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { organizationCommonProps, organizationIdProp } from '../common/props'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { GetField, OrganizationCreateResponse } from '../common/types'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const updateOrganizationAction = createAction({ + auth: pipedriveAuth, + name: 'update-organization', + displayName: 'Update Organization', + description: 'Updates an existing organization.', + props: { + organizationId: organizationIdProp(true), + name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + ...organizationCommonProps, + }, + async run(context) { + const { name, ownerId, address, visibleTo, organizationId } = context.propsValue; + + const labelIds = (context.propsValue.labelIds as number[]) ?? []; + const customFields = context.propsValue.customfields ?? {}; + + const organizationDefaultFields: Record = { + name: name, + owner_id: ownerId, + visible_to: visibleTo, + address: address, + }; + + if (labelIds.length > 0) { + organizationDefaultFields.label_ids = labelIds; + } + + const organizationCustomFields: Record = {}; + + Object.entries(customFields).forEach(([key, value]) => { + // Format values if they are arrays + organizationCustomFields[key] = + Array.isArray(value) && value.length > 0 ? value.join(',') : value; + }); + + const updatedOrganizationResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.PUT, + resourceUri: `/organizations/${organizationId}`, + body: { + ...organizationDefaultFields, + ...organizationCustomFields, + }, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/organizationFields', + }); + + const updatedOrganizationProperties = pipedriveTransformCustomFields( + customFieldsResponse, + updatedOrganizationResponse.data, + ); + + return { + ...updatedOrganizationResponse, + data: updatedOrganizationProperties, + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/actions/update-person.ts b/packages/pieces/community/pipedrive/src/lib/actions/update-person.ts new file mode 100644 index 0000000..04c1f5a --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/actions/update-person.ts @@ -0,0 +1,98 @@ +import { pipedriveAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { personCommonProps, personIdProp } from '../common/props'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { GetField, PersonCreateResponse } from '../common/types'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const updatePersonAction = createAction({ + auth: pipedriveAuth, + name: 'update-person', + displayName: 'Update Person', + description: 'Updates an existing person.', + props: { + personId: personIdProp(true), + name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + ...personCommonProps, + }, + async run(context) { + const { + name, + ownerId, + personId, + organizationId, + marketing_status, + visibleTo, + firstName, + lastName, + } = context.propsValue; + const phone = (context.propsValue.phone as string[]) ?? []; + const email = (context.propsValue.email as string[]) ?? []; + const labelIds = (context.propsValue.labelIds as number[]) ?? []; + const customFields = context.propsValue.customfields ?? {}; + + const personDefaultFields: Record = { + name: name, + owner_id: ownerId, + org_id: organizationId, + marketing_status: marketing_status, + visible_to: visibleTo, + first_name: firstName, + last_name: lastName, + }; + + if (phone.length > 0) { + personDefaultFields.phone = phone; + } + + if (email.length > 0) { + personDefaultFields.email = email; + } + + if (labelIds.length > 0) { + personDefaultFields.label_ids = labelIds; + } + + const personCustomFields: Record = {}; + + Object.entries(customFields).forEach(([key, value]) => { + // Format values if they are arrays + personCustomFields[key] = Array.isArray(value) && value.length > 0 ? value.join(',') : value; + }); + + const updatedPersonResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.PUT, + resourceUri: `/persons/${personId}`, + body: { + ...personDefaultFields, + ...personCustomFields, + }, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/personFields', + }); + + const updatedPersonProperties = pipedriveTransformCustomFields( + customFieldsResponse, + updatedPersonResponse.data, + ); + + return { + ...updatedPersonResponse, + data: updatedPersonProperties, + }; + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/common/index.ts b/packages/pieces/community/pipedrive/src/lib/common/index.ts new file mode 100644 index 0000000..1909430 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/common/index.ts @@ -0,0 +1,186 @@ +import { + AuthenticationType, + httpClient, + HttpError, + HttpMessageBody, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; +import { GetField, PaginatedResponse, RequestParams } from './types'; +import { isNil } from '@activepieces/shared'; + +export const pipedriveCommon = { + subscribeWebhook: async ( + object: string, + action: string, + webhookUrl: string, + apiDomain: string, + accessToken: string, + ) => { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${apiDomain}/api/v1/webhooks`, + body: { + event_object: object, + event_action: action, + subscription_url: webhookUrl, + version: '1.0', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + queryParams: {}, + }; + + const { body: webhook } = await httpClient.sendRequest<{ + data: { id: string }; + }>(request); + return webhook; + }, + unsubscribeWebhook: async (webhookId: string, apiDomain: string, accessToken: string) => { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `${apiDomain}/api/v1/webhooks/${webhookId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }; + return await httpClient.sendRequest(request); + }, +}; + +export type PipedriveApiCallParams = { + accessToken: string; + apiDomain: string; + method: HttpMethod; + resourceUri: string; + query?: RequestParams; + body?: any; +}; + +export async function pipedriveApiCall({ + accessToken, + apiDomain, + method, + resourceUri, + query, + body, +}: PipedriveApiCallParams): Promise { + const baseUrl = `${apiDomain}/api/v1`; + const qs: QueryParams = {}; + let data: any; + + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + qs[key] = String(value); + } + } + } + if (body) { + data = Object.entries(body).reduce((acc, [key, value]) => { + if (!isNil(value)) { + acc[key] = value; + } + return acc; + }, {} as Record); + } + const request: HttpRequest = { + method, + url: baseUrl + resourceUri, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + queryParams: qs, + body: data, + }; + + try { + const response = await httpClient.sendRequest(request); + return response.body; + } catch (error) { + if (error instanceof HttpError) { + if (error.response.status === 403) { + throw new Error('Please reconnect your Pipedrive account.'); + } + } + throw error; + } +} + +export async function pipedrivePaginatedApiCall({ + accessToken, + apiDomain, + method, + resourceUri, + query, + body, +}: PipedriveApiCallParams): Promise { + const qs = query ? query : {}; + + qs.start = 0; + qs.limit = 500; + + const resultData: T[] = []; + let hasMoreItems = true; + + do { + const response = await pipedriveApiCall>({ + accessToken, + apiDomain, + method, + resourceUri, + query: qs, + body, + }); + + if (isNil(response.data)) { + break; + } + + resultData.push(...response.data); + qs.start = response.additional_data.pagination.next_start; + hasMoreItems = response.additional_data.pagination.more_items_in_collection; + } while (hasMoreItems); + + return resultData; +} + +export function pipedriveTransformCustomFields( + CustomFields: GetField[], + responseData: Record, +): Record { + const updatedResponseData = { ...responseData }; + + for (const field of CustomFields) { + if (!field.edit_flag) { + continue; + } + const oldKey = field.key; + const newKey = field.name; + const fieldType = field.field_type; + + if (oldKey in responseData) { + if (responseData[oldKey] === null || responseData[oldKey] === undefined) { + updatedResponseData[newKey] = null; + } else if (fieldType === 'enum') { + updatedResponseData[newKey] = + field.options?.find((option) => option.id.toString() === responseData[oldKey])?.label || + null; + } else if (fieldType === 'set') { + const values: string[] = responseData[oldKey].split(','); + updatedResponseData[newKey] = values.map( + (item) => field.options?.find((option) => option.id.toString() === item)?.label || null, + ); + } else { + updatedResponseData[newKey] = responseData[oldKey]; + } + delete updatedResponseData[oldKey]; + } + } + return updatedResponseData; +} diff --git a/packages/pieces/community/pipedrive/src/lib/common/props.ts b/packages/pieces/community/pipedrive/src/lib/common/props.ts new file mode 100644 index 0000000..2264ff6 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/common/props.ts @@ -0,0 +1,895 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { pipedriveApiCall, pipedrivePaginatedApiCall } from '.'; +import { pipedriveAuth } from '../../index'; +import { + DropdownOption, + DynamicPropsValue, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { GetField, StageWithPipelineInfo } from './types'; +import { isNil } from '@activepieces/shared'; + +export async function fetchFiltersOptions( + auth: PiecePropValueSchema, + type: string, +): Promise[]> { + const filters = await pipedriveApiCall<{ data: Array<{ id: number; name: string }> }>({ + accessToken: auth.access_token, + apiDomain: auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/filters', + query: { + type: type, + }, + }); + + const options: DropdownOption[] = []; + for (const filter of filters.data) { + options.push({ + label: filter.name, + value: filter.id, + }); + } + + return options; +} + +export async function fetchActivityTypesOptions( + auth: PiecePropValueSchema, +): Promise[]> { + const activityTypes = await pipedriveApiCall<{ + data: Array<{ key_string: string; name: string }>; + }>({ + accessToken: auth.access_token, + apiDomain: auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/activityTypes:(key_string,name)', + }); + + const options: DropdownOption[] = []; + for (const type of activityTypes.data) { + options.push({ + label: type.name, + value: type.key_string, + }); + } + + return options; +} + +export async function fetchPipelinesOptions( + auth: PiecePropValueSchema, +): Promise[]> { + const pipelines = await pipedriveApiCall<{ data: Array<{ id: number; name: string }> }>({ + accessToken: auth.access_token, + apiDomain: auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/pipelines:(id,name)', + }); + + const options: DropdownOption[] = []; + for (const pipeline of pipelines.data) { + options.push({ + label: pipeline.name, + value: pipeline.id, + }); + } + + return options; +} + +export async function fetchPersonsOptions( + auth: PiecePropValueSchema, +): Promise[]> { + const persons = await pipedriveApiCall<{ data: Array<{ id: number; name: string }> }>({ + accessToken: auth.access_token, + apiDomain: auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/persons:(id,name)', + query: { + sort: 'update_time DESC', + }, + }); + + const options: DropdownOption[] = []; + for (const person of persons.data) { + options.push({ + label: person.name, + value: person.id, + }); + } + + return options; +} + +export async function fetchOwnersOptions( + auth: PiecePropValueSchema, +): Promise[]> { + const users = await pipedriveApiCall<{ data: Array<{ id: number; email: string }> }>({ + accessToken: auth.access_token, + apiDomain: auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/users:(id,email)', + query: { + sort: 'update_time DESC', + }, + }); + + const options: DropdownOption[] = []; + for (const user of users.data) { + options.push({ + label: user.email, + value: user.id, + }); + } + + return options; +} + +export function createPropertyDefinition(property: GetField) { + switch (property.field_type) { + case 'varchar': + case 'varchar_auto': + return Property.ShortText({ + displayName: property.name, + required: false, + }); + case 'text': + case 'address': + return Property.LongText({ + displayName: property.name, + required: false, + }); + case 'enum': + return Property.StaticDropdown({ + displayName: property.name, + required: false, + options: { + disabled: false, + options: property.options + ? property.options.map((option) => { + return { + label: option.label, + value: option.id.toString(), + }; + }) + : [], + }, + }); + case 'set': + return Property.StaticMultiSelectDropdown({ + displayName: property.name, + required: false, + options: { + disabled: false, + options: property.options + ? property.options.map((option) => { + return { + label: option.label, + value: option.id.toString(), + }; + }) + : [], + }, + }); + case 'double': + case 'monetary': + return Property.Number({ + displayName: property.name, + required: false, + }); + case 'time': + case 'timerange': + return Property.ShortText({ + displayName: property.name, + description: 'Please enter time in HH:mm:ss format.', + required: false, + }); + case 'int': + return Property.Number({ + displayName: property.name, + required: false, + }); + case 'date': + case 'daterange': + return Property.DateTime({ + displayName: property.name, + description: 'Please enter date in YYYY-MM-DD format.', + required: false, + }); + + default: + return null; + } +} + +export async function retrieveObjectCustomProperties( + auth: PiecePropValueSchema, + objectType: string, +) { + let endpoint = ''; + + switch (objectType) { + case 'person': + endpoint = '/personFields'; + break; + case 'deal': + case 'lead': + endpoint = '/dealFields'; + break; + case 'organization': + endpoint = '/organizationFields'; + break; + case 'product': + endpoint = '/productFields'; + break; + } + + const customFields = await pipedrivePaginatedApiCall({ + accessToken: auth.access_token, + apiDomain: auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: endpoint, + }); + + const props: DynamicPropsValue = {}; + + for (const field of customFields) { + if (!field.edit_flag) { + continue; + } + const propertyDefinition = createPropertyDefinition(field); + if (propertyDefinition) { + props[field.key] = propertyDefinition; + } + } + return props; +} + +export const searchFieldProp = (objectType: string) => + Property.Dropdown({ + displayName: 'Field to search by', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account.', + }; + } + const authValue = auth as PiecePropValueSchema; + + let endpoint = ''; + + switch (objectType) { + case 'person': + endpoint = '/personFields'; + break; + case 'deal': + case 'lead': + endpoint = '/dealFields'; + break; + case 'organization': + endpoint = '/organizationFields'; + break; + case 'product': + endpoint = '/productFields'; + break; + } + + const response = await pipedrivePaginatedApiCall({ + accessToken: authValue.access_token, + apiDomain: authValue.data['api_domain'], + method: HttpMethod.GET, + resourceUri: endpoint, + }); + + const options: DropdownOption[] = []; + + for (const field of response) { + if (!isNil(field.id)) { + options.push({ + label: field.name, + value: field.id, + }); + } + } + + return { + disabled: false, + options, + }; + }, + }); + +export const searchFieldValueProp = (objectType: string) => + Property.DynamicProperties({ + displayName: 'Field Value', + required: true, + refreshers: ['searchField'], + props: async ({ auth, searchField }) => { + if (!auth || !searchField) return {}; + + const authValue = auth as PiecePropValueSchema; + const props: DynamicPropsValue = {}; + + let endpoint = ''; + + switch (objectType) { + case 'person': + endpoint = '/personFields'; + break; + case 'deal': + case 'lead': + endpoint = '/dealFields'; + break; + case 'organization': + endpoint = '/organizationFields'; + break; + case 'product': + endpoint = '/productFields'; + break; + } + + const response = await pipedriveApiCall<{ data: GetField }>({ + accessToken: authValue.access_token, + apiDomain: authValue.data['api_domain'], + method: HttpMethod.GET, + resourceUri: `${endpoint}/${searchField}`, + }); + + const propertyDefinition = + response.data.field_type === 'set' + ? Property.StaticDropdown({ + displayName: response.data.name, + required: false, + options: { + disabled: false, + options: response.data.options + ? response.data.options.map((option) => { + return { + label: option.label, + value: option.id.toString(), + }; + }) + : [], + }, + }) + : createPropertyDefinition(response.data); + + if (propertyDefinition) { + props['field_value'] = propertyDefinition; + } else { + props['field_value'] = Property.ShortText({ + displayName: response.data.name, + required: false, + }); + } + return props; + }, + }); + +export const customFieldsProp = (objectType: string) => + Property.DynamicProperties({ + displayName: 'Custom Fields', + refreshers: [], + required: false, + props: async ({ auth }) => { + if (!auth) return {}; + + const authValue = auth as PiecePropValueSchema; + return await retrieveObjectCustomProperties(authValue, objectType); + }, + }); + +export const ownerIdProp = (displayName: string, required = false) => + Property.Dropdown({ + displayName, + refreshers: [], + required, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account.', + }; + } + const authValue = auth as PiecePropValueSchema; + const options = await fetchOwnersOptions(authValue); + + return { + disabled: false, + options, + }; + }, + }); + +export const filterIdProp = (type: string, required = false) => + Property.Dropdown({ + displayName: 'Filter', + refreshers: [], + required, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account.', + }; + } + const authValue = auth as PiecePropValueSchema; + const options = await fetchFiltersOptions(authValue, type); + + return { + disabled: false, + options, + }; + }, + }); + +export const organizationIdProp = (required = false) => + Property.Number({ + displayName:'Organization ID', + description:'You can use Find Organization action to retrieve org ID.', + required + }) + +export const dealPipelineIdProp = (required = false) => + Property.Dropdown({ + displayName: 'Pipeline', + refreshers: [], + required, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account.', + }; + } + const authValue = auth as PiecePropValueSchema; + const options = await fetchPipelinesOptions(authValue); + + return { + disabled: false, + options, + }; + }, + }); + +export const dealStageIdProp = (required = false) => + Property.Dropdown({ + displayName: 'Stage', + description: 'If a stage is chosen above, the pipeline field will be ignored.', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + placeholder: 'Please connect your account.', + disabled: true, + options: [], + }; + } + + const authValue = auth as PiecePropValueSchema; + const response = await pipedrivePaginatedApiCall({ + accessToken: authValue.access_token, + apiDomain: authValue.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/stages', + }); + + const options: DropdownOption[] = []; + for (const stage of response) { + options.push({ + label: `${stage.name} (${stage.pipeline_name})`, + value: stage.id, + }); + } + + return { + disabled: false, + options, + }; + }, + }); + +export const personIdProp = (required = false) => + Property.Number({ + displayName:'Person ID', + description:'You can use Find Person action to retrieve person ID.', + required + }) + + +export const labelIdsProp = (objectType: string, labelFieldName: string, required = false) => + Property.MultiSelectDropdown({ + displayName: 'Label', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account.', + }; + } + const authValue = auth as PiecePropValueSchema; + + let endpoint = ''; + + switch (objectType) { + case 'person': + endpoint = '/personFields:(key,name,options)'; + break; + case 'deal': + endpoint = '/dealFields:(key,name,options)'; + break; + case 'organization': + endpoint = '/organizationFields:(key,name,options)'; + break; + } + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: authValue.access_token, + apiDomain: authValue.data['api_domain'], + method: HttpMethod.GET, + resourceUri: endpoint, + }); + + const labelField = customFieldsResponse.find((field) => field.key === labelFieldName); + const options: DropdownOption[] = []; + if (labelField) { + for (const option of labelField.options ?? []) { + options.push({ + label: option.label, + value: option.id, + }); + } + } + + return { + disabled: false, + options, + }; + }, + }); + +export const leadlabeIdsProp = (required = false) => + Property.MultiSelectDropdown({ + displayName: 'Label', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account.', + }; + } + const authValue = auth as PiecePropValueSchema; + const customFieldsResponse = await pipedriveApiCall<{ + data: Array<{ id: string; name: string; color: string }>; + }>({ + accessToken: authValue.access_token, + apiDomain: authValue.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/leadLabels', + }); + + const options: DropdownOption[] = []; + for (const option of customFieldsResponse.data) { + options.push({ + label: `${option.name} (${option.color})`, + value: option.id, + }); + } + + return { + disabled: false, + options, + }; + }, + }); + +export const dealIdProp = (required = false) => + Property.Number({ + displayName:'Deal ID', + description:'You can use Find Deal action to retrieve deal ID.', + required + }) + +export const productIdProp = (required = false) => + Property.Number({ + displayName:'Product ID', + description:'You can use Find Product action to retrieve product ID.', + required + }) + + +export const visibleToProp = Property.StaticDropdown({ + displayName: 'Visible To', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Item Owner', + value: 1, + }, + { + label: 'All Users', + value: 3, + }, + ], + }, +}); + +export const leadIdProp = (required = false) => + Property.ShortText({ + displayName: 'Lead ID', + required, + }); + +export const activityTypeIdProp = (required = false) => + Property.Dropdown({ + displayName: 'Activity Type', + refreshers: [], + required, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account.', + }; + } + const authValue = auth as PiecePropValueSchema; + const options = await fetchActivityTypesOptions(authValue); + + return { + disabled: false, + options, + }; + }, + }); + +export const dealCommonProps = { + creationTime: Property.DateTime({ + displayName: 'Creation Time', + required: false, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Open', + value: 'open', + }, + { + label: 'Won', + value: 'won', + }, + { + label: 'Lost', + value: 'lost', + }, + { + label: 'Deleted', + value: 'deleted', + }, + ], + }, + }), + stageId: dealStageIdProp(false), + pipelineId: dealPipelineIdProp(false), + ownerId: ownerIdProp('Owner', false), + organizationId: organizationIdProp(false), + personId: personIdProp(false), + labelIds: labelIdsProp('deal', 'label', false), + probability: Property.Number({ + displayName: 'Probability', + required: false, + }), + expectedCloseDate: Property.DateTime({ + displayName: 'Expected Close Date', + required: false, + description: 'Please enter date in YYYY-MM-DD format.', + }), + dealValue: Property.Number({ + displayName: 'Value', + required: false, + }), + dealValueCurrency: Property.ShortText({ + displayName: 'Currency', + required: false, + description: 'Please enter currency code.', + }), + visibleTo: visibleToProp, + customfields: customFieldsProp('deal'), +}; + +export const leadCommonProps = { + ownerId: ownerIdProp('Owner', false), + organizationId: organizationIdProp(false), + personId: personIdProp(false), + labelIds: leadlabeIdsProp(false), + expectedCloseDate: Property.DateTime({ + displayName: 'Expected Close Date', + required: false, + description: 'Please enter date in YYYY-MM-DD format.', + }), + visibleTo: visibleToProp, + channel: Property.MultiSelectDropdown({ + displayName: 'Channel', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account.', + }; + } + const authValue = auth as PiecePropValueSchema; + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: authValue.access_token, + apiDomain: authValue.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/dealFields:(key,name,options)', + }); + + const channelField = customFieldsResponse.find((field) => field.key === 'channel'); + const options: DropdownOption[] = []; + if (channelField) { + for (const option of channelField.options ?? []) { + options.push({ + label: option.label, + value: option.id, + }); + } + } + + return { + disabled: false, + options, + }; + }, + }), + leadValue: Property.Number({ + displayName: 'Lead Value', + required: false, + }), + leadValueCurrency: Property.ShortText({ + displayName: 'Lead Value Currency', + required: false, + description: 'Please enter currency code.', + }), + customfields: customFieldsProp('lead'), +}; + +export const organizationCommonProps = { + ownerId: ownerIdProp('Owner', false), + visibleTo: visibleToProp, + labelIds: labelIdsProp('organization', 'label_ids', false), + address: Property.LongText({ + displayName: 'Address', + required: false, + }), + customfields: customFieldsProp('organization'), +}; + +export const personCommonProps = { + ownerId: ownerIdProp('Owner', false), + organizationId: organizationIdProp(false), + email: Property.Array({ + displayName: 'Email', + required: false, + }), + phone: Property.Array({ + displayName: 'Phone', + required: false, + }), + labelIds: labelIdsProp('person', 'label_ids', false), + firstName: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + visibleTo: visibleToProp, + marketing_status: Property.StaticDropdown({ + displayName: 'Marketing Status', + description: 'Marketing opt-in status', + required: false, + options: { + disabled: false, + options: [ + { + label: 'No Consent', + value: 'no_consent', + }, + { + label: 'Unsubscribed', + value: 'unsubscribed', + }, + { + label: 'Subscribed', + value: 'subscribed', + }, + { + label: 'Archived', + value: 'archived', + }, + ], + }, + }), + customfields: customFieldsProp('person'), +}; + +export const activityCommonProps = { + organizationId: organizationIdProp(false), + personId: personIdProp(false), + dealId: dealIdProp(false), + leadId: leadIdProp(false), + assignTo: ownerIdProp('Assign To', false), + type: activityTypeIdProp(false), + dueDate: Property.DateTime({ + displayName: 'Due Date', + required: false, + description: 'Please enter date in YYYY-MM-DD format.', + }), + dueTime: Property.ShortText({ + displayName: 'Due Time', + required: false, + description: 'Please enter time in HH:MM format.', + }), + duration: Property.ShortText({ + displayName: 'Duration', + required: false, + description: 'Please enter time in HH:MM format.', + }), + idDone: Property.Checkbox({ + displayName: 'Mark as Done?', + required: false, + defaultValue: false, + }), + isBusy: Property.StaticDropdown({ + displayName: 'Free or Busy', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Free', + value: 'free', + }, + { + label: 'Busy', + value: 'busy', + }, + ], + }, + }), + note: Property.LongText({ + displayName: 'Note', + required: false, + }), + publicDescription: Property.LongText({ + displayName: 'Public Description', + required: false, + }), +}; diff --git a/packages/pieces/community/pipedrive/src/lib/common/types.ts b/packages/pieces/community/pipedrive/src/lib/common/types.ts new file mode 100644 index 0000000..703805d --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/common/types.ts @@ -0,0 +1,103 @@ +export type GetField = { + id: string; + name: string; + key:string, + edit_flag:boolean + field_type:"varchar"|"text"|"enum"|"set"|"varchar_auto"|"double"|"monetary"|"user"|"org"|"people"|"phone"|"time"|"int"|"timerange"|"date"|"daterange"|"address", + options?:Array<{id:number,label:string}> +}; + +type AdditionalData = { + start: number; + limit: number; + more_items_in_collection: boolean; +}; + +export type FieldsResponse = { + success: boolean; + data: GetField[]; + additional_data: AdditionalData; +}; + +export type StageWithPipelineInfo = { + id: number; + name: string; + pipeline_id: number; + pipeline_name: string; +}; + +export type GetStagesResponse = { + success: boolean; + data: StageWithPipelineInfo[]; +}; + +export type ListDealsResponse = { + success: boolean; + data: Record[]; + additional_data: AdditionalData; +}; + +export type GetDealResponse= +{ + success: boolean; + data: Record; + additional_data: AdditionalData; +} + + +export type ListActivitiesResponse = +{ + success: boolean; + data: Record[]; + additional_data: AdditionalData; +} + +export type PersonListResponse = +{ + success: boolean; + data: Record[]; + additional_data: AdditionalData; +} + +export type PersonCreateResponse = +{ + success: boolean; + data: Record; + additional_data: AdditionalData; +} + +export type OrganizationCreateResponse = +{ + success: boolean; + data: Record; + additional_data: AdditionalData; +} + +export type PaginatedResponse = +{ + success: boolean; + data: T[]; + additional_data: { + pagination: { + start: number; + limit: number; + more_items_in_collection: boolean; + next_start: number; + }; + }; +} + +export type RequestParams = Record; + +export type WebhookCreateResponse = { + status:string, + success:boolean, + data:{ + id:number + } +} + +export type LeadListResponse = { + success: boolean; + data: Record[]; +} \ No newline at end of file diff --git a/packages/pieces/community/pipedrive/src/lib/trigger/activity-matching-filter.ts b/packages/pieces/community/pipedrive/src/lib/trigger/activity-matching-filter.ts new file mode 100644 index 0000000..b26493a --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/trigger/activity-matching-filter.ts @@ -0,0 +1,180 @@ +import { pipedriveAuth } from '../../index'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { filterIdProp } from '../common/props'; +import { pipedriveApiCall, pipedrivePaginatedApiCall } from '../common'; +import { LeadListResponse } from '../common/types'; +import { isNil } from '@activepieces/shared'; + +export const activityMatchingFilterTrigger = createTrigger({ + auth: pipedriveAuth, + name: 'activity-matching-filter', + displayName: 'Activity Matching Filter', + description: 'Trigges when an activity newly matches a Pipedrive filter for the first time.', + type: TriggerStrategy.POLLING, + props: { + filterId: filterIdProp('activity', true), + }, + async onEnable(context) { + const ids: number[] = []; + + const response = await pipedrivePaginatedApiCall<{ id: number }>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/activities:(id)', + query: { sort: 'update_time DESC', filter_id: context.propsValue.filterId, user_id: 0 }, + }); + + if (!isNil(response)) { + response.forEach((activity) => { + ids.push(activity.id); + }); + } + + await context.store.put('activities', JSON.stringify(ids)); + }, + async onDisable(context) { + await context.store.delete('activities'); + }, + async test(context) { + const activities = []; + + const response = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/activities', + query: { + limit: 10, + sort: 'update_time DESC', + filter_id: context.propsValue.filterId, + user_id: 0, + }, + }); + + if (isNil(response.data)) { + return []; + } + + for (const activity of response.data) { + activities.push(activity); + } + + return activities; + }, + async run(context) { + const existingIds = (await context.store.get('activities')) ?? '[]'; + const parsedExistingIds = JSON.parse(existingIds) as number[]; + + const response = await pipedrivePaginatedApiCall<{ id: number }>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/activities', + query: { sort: 'update_time DESC', filter_id: context.propsValue.filterId, user_id: 0 }, + }); + + if (isNil(response) || response.length === 0) { + return []; + } + + // Filter valid activities + const newActivities = response.filter((activity) => !parsedExistingIds.includes(activity.id)); + + const newIds = newActivities.map((activity) => activity.id); + + if (newIds.length === 0) { + return []; + } + + // Store new IDs + await context.store.put('activities', JSON.stringify([...newIds, ...parsedExistingIds])); + + return newActivities; + }, + sampleData: { + id: 8, + company_id: 22122, + user_id: 1234, + done: false, + type: 'deadline', + reference_type: 'scheduler-service', + reference_id: 7, + conference_meeting_client: '871b8bc88d3a1202', + conference_meeting_url: 'https://pipedrive.zoom.us/link', + conference_meeting_id: '01758746701', + due_date: '2020-06-09', + due_time: '10:00', + duration: '01:00', + busy_flag: true, + add_time: '2020-06-08 12:37:56', + marked_as_done_time: '2020-08-08 08:08:38', + last_notification_time: '2020-08-08 12:37:56', + last_notification_user_id: 7655, + notification_language_id: 1, + subject: 'Deadline', + public_description: 'This is a description', + calendar_sync_include_context: '', + location: 'Mustamäe tee 3, Tallinn, Estonia', + org_id: 5, + person_id: 1101, + deal_id: 300, + lead_id: '46c3b0e1-db35-59ca-1828-4817378dff71', + active_flag: true, + update_time: '2020-08-08 12:37:56', + update_user_id: 5596, + gcal_event_id: '', + google_calendar_id: '', + google_calendar_etag: '', + source_timezone: '', + rec_rule: 'RRULE:FREQ=WEEKLY;BYDAY=WE', + rec_rule_extension: '', + rec_master_activity_id: 1, + series: [], + note: 'A note for the activity', + created_by_user_id: 1234, + location_subpremise: '', + location_street_number: '3', + location_route: 'Mustamäe tee', + location_sublocality: 'Kristiine', + location_locality: 'Tallinn', + location_admin_area_level_1: 'Harju maakond', + location_admin_area_level_2: '', + location_country: 'Estonia', + location_postal_code: '10616', + location_formatted_address: 'Mustamäe tee 3, 10616 Tallinn, Estonia', + attendees: [ + { + email_address: 'attendee@pipedrivemail.com', + is_organizer: 0, + name: 'Attendee', + person_id: 25312, + status: 'noreply', + user_id: null, + }, + ], + participants: [ + { + person_id: 17985, + primary_flag: false, + }, + { + person_id: 1101, + primary_flag: true, + }, + ], + org_name: 'Organization', + person_name: 'Person', + deal_title: 'Deal', + owner_name: 'Creator', + person_dropbox_bcc: 'company@pipedrivemail.com', + deal_dropbox_bcc: 'company+deal300@pipedrivemail.com', + assigned_to_user_id: 1235, + file: { + id: '376892,', + clean_name: 'Audio 10:55:07.m4a', + url: 'https://pipedrive-files.s3-eu-west-1.amazonaws.com/Audio-recording.m4a', + }, + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/trigger/deal-matching-filter.ts b/packages/pieces/community/pipedrive/src/lib/trigger/deal-matching-filter.ts new file mode 100644 index 0000000..091e485 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/trigger/deal-matching-filter.ts @@ -0,0 +1,299 @@ +import { pipedriveAuth } from '../../index'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { createTrigger, Property, TriggerStrategy } from '@activepieces/pieces-framework'; +import { filterIdProp } from '../common/props'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { GetField, LeadListResponse } from '../common/types'; +import { isNil } from '@activepieces/shared'; + +export const dealMatchingFilterTrigger = createTrigger({ + auth: pipedriveAuth, + name: 'deal-matching-filter', + displayName: 'Deal Matching Filter', + description: 'Trigges when a deal newly matches a Pipedrive filter for the first time.', + type: TriggerStrategy.POLLING, + props: { + filterId: filterIdProp('deals', true), + status: Property.StaticDropdown({ + displayName: 'Status', + required: false, + defaultValue: 'all_not_deleted', + options: { + disabled: false, + options: [ + { + label: 'Open', + value: 'open', + }, + { + label: 'Won', + value: 'won', + }, + { + label: 'Lost', + value: 'lost', + }, + { + label: 'Deleted', + value: 'deleted', + }, + { + label: 'All(Not Deleted)', + value: 'all_not_deleted', + }, + ], + }, + }), + }, + async onEnable(context) { + const ids: number[] = []; + + const response = await pipedrivePaginatedApiCall<{ id: number }>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/deals:(id)', + query: { + sort: 'update_time DESC', + filter_id: context.propsValue.filterId, + status: context.propsValue.status, + }, + }); + + if (!isNil(response)) { + response.forEach((deal) => { + ids.push(deal.id); + }); + } + + await context.store.put('deals', JSON.stringify(ids)); + }, + async onDisable(context) { + await context.store.delete('deals'); + }, + async test(context) { + const deals = []; + + const response = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/deals', + query: { + limit: 10, + sort: 'update_time DESC', + filter_id: context.propsValue.filterId, + status: context.propsValue.status, + }, + }); + + if (isNil(response.data)) { + return []; + } + + for (const deal of response.data) { + deals.push(deal); + } + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/dealFields', + }); + + const result = []; + + for (const deal of deals) { + const updatedDealProperties = pipedriveTransformCustomFields(customFieldsResponse, deal); + result.push(updatedDealProperties); + } + + return result; + }, + async run(context) { + const existingIds = (await context.store.get('deals')) ?? '[]'; + const parsedExistingIds = JSON.parse(existingIds) as number[]; + + const response = await pipedrivePaginatedApiCall<{ id: number }>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/deals', + query: { + sort: 'update_time DESC', + filter_id: context.propsValue.filterId, + status: context.propsValue.status, + }, + }); + + if (isNil(response) || response.length === 0) { + return []; + } + + // Filter valid deals + const newDeals = response.filter((deal) => !parsedExistingIds.includes(deal.id)); + + const newIds = newDeals.map((deal) => deal.id); + + if (newIds.length === 0) { + return []; + } + + // Store new IDs + await context.store.put('deals', JSON.stringify([...newIds, ...parsedExistingIds])); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/dealFields', + }); + + const result = []; + + // Transform valid deal fields + for (const deal of newDeals) { + const updatedDealProperties = pipedriveTransformCustomFields(customFieldsResponse, deal); + result.push(updatedDealProperties); + } + + return result; + }, + sampleData: { + id: 1, + creator_user_id: { + id: 8877, + name: 'Creator', + email: 'john.doe@pipedrive.com', + has_pic: false, + pic_hash: null, + active_flag: true, + value: 8877, + }, + user_id: { + id: 8877, + name: 'Creator', + email: 'john.doe@pipedrive.com', + has_pic: false, + pic_hash: null, + active_flag: true, + value: 8877, + }, + person_id: { + active_flag: true, + name: 'Person', + email: [ + { + label: 'work', + value: 'person@pipedrive.com', + primary: true, + }, + ], + phone: [ + { + label: 'work', + value: '37244499911', + primary: true, + }, + ], + value: 1101, + }, + org_id: { + name: 'Organization', + people_count: 2, + owner_id: 8877, + address: '', + active_flag: true, + cc_email: 'org@pipedrivemail.com', + value: 5, + }, + stage_id: 2, + title: 'Deal One', + value: 5000, + currency: 'EUR', + add_time: '2019-05-29 04:21:51', + update_time: '2019-11-28 16:19:50', + stage_change_time: '2019-11-28 15:41:22', + active: true, + deleted: false, + status: 'open', + probability: null, + next_activity_date: '2019-11-29', + next_activity_time: '11:30:00', + next_activity_id: 128, + last_activity_id: null, + last_activity_date: null, + lost_reason: null, + visible_to: '1', + close_time: null, + pipeline_id: 1, + won_time: '2019-11-27 11:40:36', + first_won_time: '2019-11-27 11:40:36', + lost_time: '', + products_count: 0, + files_count: 0, + notes_count: 2, + followers_count: 0, + email_messages_count: 4, + activities_count: 1, + done_activities_count: 0, + undone_activities_count: 1, + participants_count: 1, + expected_close_date: '2019-06-29', + last_incoming_mail_time: '2019-05-29 18:21:42', + last_outgoing_mail_time: '2019-05-30 03:45:35', + label: 11, + stage_order_nr: 2, + person_name: 'Person', + org_name: 'Organization', + next_activity_subject: 'Call', + next_activity_type: 'call', + next_activity_duration: '00:30:00', + next_activity_note: 'Note content', + formatted_value: '€5,000', + weighted_value: 5000, + formatted_weighted_value: '€5,000', + weighted_value_currency: 'EUR', + rotten_time: null, + owner_name: 'Creator', + cc_email: 'company+deal1@pipedrivemail.com', + org_hidden: false, + person_hidden: false, + average_time_to_won: { + y: 0, + m: 0, + d: 0, + h: 0, + i: 20, + s: 49, + total_seconds: 1249, + }, + average_stage_progress: 4.99, + age: { + y: 0, + m: 6, + d: 14, + h: 8, + i: 57, + s: 26, + total_seconds: 17139446, + }, + stay_in_pipeline_stages: { + times_in_stages: { + '1': 15721267, + '2': 1288449, + '3': 4368, + '4': 3315, + '5': 26460, + }, + order_of_stages: [1, 2, 3, 4, 5], + }, + last_activity: null, + next_activity: null, + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/trigger/new-activity.ts b/packages/pieces/community/pipedrive/src/lib/trigger/new-activity.ts new file mode 100644 index 0000000..864f228 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/trigger/new-activity.ts @@ -0,0 +1,150 @@ +import { createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { pipedriveCommon } from '../common'; +import { pipedriveAuth } from '../..'; +import { httpClient, HttpMethod,AuthenticationType } from '@activepieces/pieces-common'; +import { ListActivitiesResponse } from '../common/types'; + +export const newActivity = createTrigger({ + auth: pipedriveAuth, + name: 'new_activity', + displayName: 'New Activity', + description: 'Triggers when a new activity is added', + props: {}, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const webhook = await pipedriveCommon.subscribeWebhook( + 'activity', + 'added', + context.webhookUrl!, + context.auth.data['api_domain'], + context.auth.access_token + ); + await context.store?.put('_new_activity_trigger', { + webhookId: webhook.data.id, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_new_activity_trigger' + ); + if (response !== null && response !== undefined) { + await pipedriveCommon.unsubscribeWebhook( + response.webhookId, + context.auth.data['api_domain'], + context.auth.access_token + ); + } + }, + async test(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${context.auth.data['api_domain']}/api/v1/activities`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + queryParams:{ + limit:'5' + } + }); + + return response.body.data; + }, + async run(context) { + const payloadBody = context.payload.body as PayloadBody; + return [payloadBody.current]; + }, + sampleData: { + id: 8, + company_id: 22122, + user_id: 1234, + done: false, + type: 'deadline', + reference_type: 'scheduler-service', + reference_id: 7, + conference_meeting_client: '871b8bc88d3a1202', + conference_meeting_url: 'https://pipedrive.zoom.us/link', + conference_meeting_id: '01758746701', + due_date: '2020-06-09', + due_time: '10:00', + duration: '01:00', + busy_flag: true, + add_time: '2020-06-08 12:37:56', + marked_as_done_time: '2020-08-08 08:08:38', + last_notification_time: '2020-08-08 12:37:56', + last_notification_user_id: 7655, + notification_language_id: 1, + subject: 'Deadline', + public_description: 'This is a description', + calendar_sync_include_context: '', + location: 'Mustamäe tee 3, Tallinn, Estonia', + org_id: 5, + person_id: 1101, + deal_id: 300, + lead_id: '46c3b0e1-db35-59ca-1828-4817378dff71', + active_flag: true, + update_time: '2020-08-08 12:37:56', + update_user_id: 5596, + gcal_event_id: '', + google_calendar_id: '', + google_calendar_etag: '', + source_timezone: '', + rec_rule: 'RRULE:FREQ=WEEKLY;BYDAY=WE', + rec_rule_extension: '', + rec_master_activity_id: 1, + series: [], + note: 'A note for the activity', + created_by_user_id: 1234, + location_subpremise: '', + location_street_number: '3', + location_route: 'Mustamäe tee', + location_sublocality: 'Kristiine', + location_locality: 'Tallinn', + location_admin_area_level_1: 'Harju maakond', + location_admin_area_level_2: '', + location_country: 'Estonia', + location_postal_code: '10616', + location_formatted_address: 'Mustamäe tee 3, 10616 Tallinn, Estonia', + attendees: [ + { + email_address: 'attendee@pipedrivemail.com', + is_organizer: 0, + name: 'Attendee', + person_id: 25312, + status: 'noreply', + user_id: null, + }, + ], + participants: [ + { + person_id: 17985, + primary_flag: false, + }, + { + person_id: 1101, + primary_flag: true, + }, + ], + org_name: 'Organization', + person_name: 'Person', + deal_title: 'Deal', + owner_name: 'Creator', + person_dropbox_bcc: 'company@pipedrivemail.com', + deal_dropbox_bcc: 'company+deal300@pipedrivemail.com', + assigned_to_user_id: 1235, + file: { + id: '376892,', + clean_name: 'Audio 10:55:07.m4a', + url: 'https://pipedrive-files.s3-eu-west-1.amazonaws.com/Audio-recording.m4a', + }, + }, +}); + +interface WebhookInformation { + webhookId: string; +} + +type PayloadBody = { + current: unknown; +}; diff --git a/packages/pieces/community/pipedrive/src/lib/trigger/new-deal.ts b/packages/pieces/community/pipedrive/src/lib/trigger/new-deal.ts new file mode 100644 index 0000000..4091b72 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/trigger/new-deal.ts @@ -0,0 +1,237 @@ +import { createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { + pipedriveApiCall, + pipedriveCommon, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { pipedriveAuth } from '../..'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { GetDealResponse, GetField, ListDealsResponse } from '../common/types'; +import { isNil } from '@activepieces/shared'; + +export const newDeal = createTrigger({ + auth: pipedriveAuth, + name: 'new_deal', + displayName: 'New Deal', + description: 'Triggers when a new deal is created.', + props: {}, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const webhook = await pipedriveCommon.subscribeWebhook( + 'deal', + 'added', + context.webhookUrl!, + context.auth.data['api_domain'], + context.auth.access_token, + ); + await context.store?.put('_new_deal_trigger', { + webhookId: webhook.data.id, + }); + }, + async onDisable(context) { + const response = await context.store?.get('_new_deal_trigger'); + if (response !== null && response !== undefined) { + await pipedriveCommon.unsubscribeWebhook( + response.webhookId, + context.auth.data['api_domain'], + context.auth.access_token, + ); + } + }, + async test(context) { + const dealsResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/deals', + query: { limit: 5, sort: 'update_time DESC' }, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/dealFields', + }); + + if (isNil(dealsResponse.data)) { + return []; + } + + const result = []; + + for (const deal of dealsResponse.data) { + const updatedDealProperties = pipedriveTransformCustomFields(customFieldsResponse, deal); + result.push(updatedDealProperties); + } + + return result; + }, + async run(context) { + const payloadBody = context.payload.body as PayloadBody; + + const dealResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: `/deals/${payloadBody.current.id}`, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/dealFields', + }); + + const updatedDealProperties = pipedriveTransformCustomFields( + customFieldsResponse, + dealResponse.data, + ); + + return [updatedDealProperties]; + }, + sampleData: { + id: 1, + creator_user_id: { + id: 8877, + name: 'Creator', + email: 'john.doe@pipedrive.com', + has_pic: false, + pic_hash: null, + active_flag: true, + value: 8877, + }, + user_id: { + id: 8877, + name: 'Creator', + email: 'john.doe@pipedrive.com', + has_pic: false, + pic_hash: null, + active_flag: true, + value: 8877, + }, + person_id: { + active_flag: true, + name: 'Person', + email: [ + { + label: 'work', + value: 'person@pipedrive.com', + primary: true, + }, + ], + phone: [ + { + label: 'work', + value: '37244499911', + primary: true, + }, + ], + value: 1101, + }, + org_id: { + name: 'Organization', + people_count: 2, + owner_id: 8877, + address: '', + active_flag: true, + cc_email: 'org@pipedrivemail.com', + value: 5, + }, + stage_id: 2, + title: 'Deal One', + value: 5000, + currency: 'EUR', + add_time: '2019-05-29 04:21:51', + update_time: '2019-11-28 16:19:50', + stage_change_time: '2019-11-28 15:41:22', + active: true, + deleted: false, + status: 'open', + probability: null, + next_activity_date: '2019-11-29', + next_activity_time: '11:30:00', + next_activity_id: 128, + last_activity_id: null, + last_activity_date: null, + lost_reason: null, + visible_to: '1', + close_time: null, + pipeline_id: 1, + won_time: '2019-11-27 11:40:36', + first_won_time: '2019-11-27 11:40:36', + lost_time: '', + products_count: 0, + files_count: 0, + notes_count: 2, + followers_count: 0, + email_messages_count: 4, + activities_count: 1, + done_activities_count: 0, + undone_activities_count: 1, + participants_count: 1, + expected_close_date: '2019-06-29', + last_incoming_mail_time: '2019-05-29 18:21:42', + last_outgoing_mail_time: '2019-05-30 03:45:35', + label: 11, + stage_order_nr: 2, + person_name: 'Person', + org_name: 'Organization', + next_activity_subject: 'Call', + next_activity_type: 'call', + next_activity_duration: '00:30:00', + next_activity_note: 'Note content', + formatted_value: '€5,000', + weighted_value: 5000, + formatted_weighted_value: '€5,000', + weighted_value_currency: 'EUR', + rotten_time: null, + owner_name: 'Creator', + cc_email: 'company+deal1@pipedrivemail.com', + org_hidden: false, + person_hidden: false, + average_time_to_won: { + y: 0, + m: 0, + d: 0, + h: 0, + i: 20, + s: 49, + total_seconds: 1249, + }, + average_stage_progress: 4.99, + age: { + y: 0, + m: 6, + d: 14, + h: 8, + i: 57, + s: 26, + total_seconds: 17139446, + }, + stay_in_pipeline_stages: { + times_in_stages: { + '1': 15721267, + '2': 1288449, + '3': 4368, + '4': 3315, + '5': 26460, + }, + order_of_stages: [1, 2, 3, 4, 5], + }, + last_activity: null, + next_activity: null, + }, +}); + +interface WebhookInformation { + webhookId: string; +} + +type PayloadBody = { + current: Record; + previous: Record; +}; diff --git a/packages/pieces/community/pipedrive/src/lib/trigger/new-lead.ts b/packages/pieces/community/pipedrive/src/lib/trigger/new-lead.ts new file mode 100644 index 0000000..46e03d2 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/trigger/new-lead.ts @@ -0,0 +1,133 @@ +import { pipedriveAuth } from '../../'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { GetDealResponse, GetField, LeadListResponse } from '../common/types'; +import { isNil } from '@activepieces/shared'; + +export const newLeadTrigger = createTrigger({ + auth: pipedriveAuth, + name: 'new-lead', + displayName: 'New Lead', + description: 'Triggers when a new lead is created.', + props: {}, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const response = await httpClient.sendRequest<{ data: { id: string } }>({ + method: HttpMethod.POST, + url: `${context.auth.data['api_domain']}/api/v1/webhooks`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + body: { + event_object: 'lead', + event_action: 'create', + subscription_url: context.webhookUrl, + version: '2.0', + }, + }); + + await context.store?.put<{ + webhookId: string; + }>('_new_lead_trigger', { + webhookId: response.body.data.id, + }); + }, + async onDisable(context) { + const response = await context.store?.get<{ + webhookId: string; + }>('_new_lead_trigger'); + if (response !== null && !isNil(response.webhookId)) { + await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: `${context.auth.data['api_domain']}/api/v1/webhooks/${response.webhookId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }); + } + }, + async test(context) { + const response = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/leads', + query: { limit: 10, sort: 'update_time DESC' }, + }); + + if (isNil(response.data)) { + return []; + } + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/dealFields', + }); + + const result = []; + + for (const lead of response.data) { + const updatedLeadProperties = pipedriveTransformCustomFields(customFieldsResponse, lead); + result.push(updatedLeadProperties); + } + + return result; + }, + async run(context) { + const payloadBody = context.payload.body as { + data: Record; + previous: Record; + }; + + const leadResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: `/leads/${payloadBody.data.id}`, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/dealFields', + }); + + const updatedLeadProperties = pipedriveTransformCustomFields( + customFieldsResponse, + leadResponse.data, + ); + + return [updatedLeadProperties]; + }, + sampleData: { + id: 'f3c23480-c9b1-11ef-bc83-2b8218e028ef', + title: 'Test lead', + owner_id: 22701301, + creator_id: 22701301, + label_ids: ['a0e5f330-d2a7-4181-a6e3-a44d634b7bf7', '8a0e6918-1eee-4e56-a615-c81d712a6a77'], + value: null, + expected_close_date: null, + person_id: 2, + organization_id: 1, + is_archived: false, + source_name: 'Manually created', + origin: 'ManuallyCreated', + origin_id: null, + channel: 1, + channel_id: null, + was_seen: true, + next_activity_id: null, + add_time: '2025-01-03T09:06:00.776Z', + update_time: '2025-01-03T09:06:00.776Z', + visible_to: '3', + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/trigger/new-note.ts b/packages/pieces/community/pipedrive/src/lib/trigger/new-note.ts new file mode 100644 index 0000000..45be037 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/trigger/new-note.ts @@ -0,0 +1,91 @@ +import { pipedriveAuth } from '../../'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { pipedriveApiCall, pipedriveCommon } from '../common'; +import { LeadListResponse } from '../common/types'; +import { isNil } from '@activepieces/shared'; + +export const newNoteTrigger = createTrigger({ + auth: pipedriveAuth, + name: 'new-note', + displayName: 'New Note', + description: 'Triggers when a new note is created.', + props: {}, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const webhook = await pipedriveCommon.subscribeWebhook( + 'note', + 'added', + context.webhookUrl!, + context.auth.data['api_domain'], + context.auth.access_token, + ); + await context.store?.put<{ + webhookId: string; + }>('_new_note_trigger', { + webhookId: webhook.data.id, + }); + }, + async onDisable(context) { + const response = await context.store?.get<{ + webhookId: string; + }>('_new_note_trigger'); + if (response !== null && response !== undefined) { + await pipedriveCommon.unsubscribeWebhook( + response.webhookId, + context.auth.data['api_domain'], + context.auth.access_token, + ); + } + }, + async test(context) { + const response = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/notes', + query: { limit: 10, sort: 'update_time DESC' }, + }); + + if (isNil(response.data)) { + return []; + } + + return response.data; + }, + async run(context) { + const payloadBody = context.payload.body as { + current: Record; + previous: Record; + }; + + return [payloadBody.current]; + }, + sampleData: { + id: 1, + user_id: 22701301, + deal_id: null, + person_id: 1, + org_id: 1, + lead_id: null, + content: 'Note', + add_time: '2024-12-04 06:48:26', + update_time: '2024-12-04 06:48:26', + active_flag: true, + pinned_to_deal_flag: false, + pinned_to_person_flag: false, + pinned_to_organization_flag: false, + pinned_to_lead_flag: false, + last_update_user_id: null, + organization: { name: 'Pipedrive' }, + person: { name: 'John' }, + deal: null, + lead: null, + user: { + email: 'test@gmail.com', + name: 'John', + icon_url: null, + is_you: true, + }, + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/trigger/new-organization.ts b/packages/pieces/community/pipedrive/src/lib/trigger/new-organization.ts new file mode 100644 index 0000000..1d1051e --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/trigger/new-organization.ts @@ -0,0 +1,160 @@ +import { pipedriveAuth } from '../../'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { + pipedriveApiCall, + pipedriveCommon, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { GetDealResponse, GetField, LeadListResponse } from '../common/types'; +import { isNil } from '@activepieces/shared'; + +export const newOrganizationTrigger = createTrigger({ + auth: pipedriveAuth, + name: 'new-organization', + displayName: 'New Organization', + description: 'Triggers when a new organization is created.', + props: {}, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const webhook = await pipedriveCommon.subscribeWebhook( + 'organization', + 'added', + context.webhookUrl!, + context.auth.data['api_domain'], + context.auth.access_token, + ); + await context.store?.put<{ + webhookId: string; + }>('_new_organization_trigger', { + webhookId: webhook.data.id, + }); + }, + async onDisable(context) { + const response = await context.store?.get<{ + webhookId: string; + }>('_new_organization_trigger'); + if (response !== null && response !== undefined) { + await pipedriveCommon.unsubscribeWebhook( + response.webhookId, + context.auth.data['api_domain'], + context.auth.access_token, + ); + } + }, + async test(context) { + const response = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/organizations', + query: { limit: 10, sort: 'update_time DESC' }, + }); + + if (isNil(response.data)) { + return []; + } + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/organizationFields', + }); + + const result = []; + + for (const org of response.data) { + const updatedOrgProperties = pipedriveTransformCustomFields(customFieldsResponse, org); + result.push(updatedOrgProperties); + } + + return result; + }, + async run(context) { + const payloadBody = context.payload.body as { + current: Record; + previous: Record; + }; + + const orgResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: `/organizations/${payloadBody.current.id}`, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/organizationFields', + }); + + const updatedOrgProperties = pipedriveTransformCustomFields( + customFieldsResponse, + orgResponse.data, + ); + + return [updatedOrgProperties]; + }, + sampleData: { + id: 1, + company_id: 13937255, + owner_id: { + id: 22701301, + name: 'john', + email: 'john@test.com', + has_pic: 0, + pic_hash: null, + active_flag: true, + value: 22701301, + }, + name: 'Pipedrive', + open_deals_count: 3, + related_open_deals_count: 1, + closed_deals_count: 0, + related_closed_deals_count: 0, + email_messages_count: 0, + people_count: 3, + activities_count: 1, + done_activities_count: 0, + undone_activities_count: 1, + files_count: 0, + notes_count: 4, + followers_count: 1, + won_deals_count: 0, + related_won_deals_count: 0, + lost_deals_count: 0, + related_lost_deals_count: 0, + active_flag: true, + picture_id: null, + country_code: null, + first_char: 'a', + update_time: '2024-12-14 11:03:19', + delete_time: null, + add_time: '2024-12-04 03:49:06', + visible_to: '3', + next_activity_date: '2024-12-04', + next_activity_time: null, + next_activity_id: 4, + last_activity_id: null, + last_activity_date: null, + label: null, + label_ids: [], + address: null, + address_subpremise: null, + address_street_number: null, + address_route: null, + address_sublocality: null, + address_locality: null, + address_admin_area_level_1: null, + address_admin_area_level_2: null, + address_country: null, + address_postal_code: null, + address_formatted_address: null, + owner_name: 'John', + cc_email: null, + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/trigger/new-person.ts b/packages/pieces/community/pipedrive/src/lib/trigger/new-person.ts new file mode 100644 index 0000000..c139538 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/trigger/new-person.ts @@ -0,0 +1,194 @@ +import { createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { + pipedriveApiCall, + pipedriveCommon, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { pipedriveAuth } from '../..'; +import { GetField, PersonListResponse } from '../common/types'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; + +export const newPerson = createTrigger({ + auth: pipedriveAuth, + name: 'new_person', + displayName: 'New Person', + description: 'Triggers when a new person is created', + props: {}, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const webhook = await pipedriveCommon.subscribeWebhook( + 'person', + 'added', + context.webhookUrl!, + context.auth.data['api_domain'], + context.auth.access_token, + ); + await context.store?.put('_new_person_trigger', { + webhookId: webhook.data.id, + }); + }, + async onDisable(context) { + const response = await context.store?.get('_new_person_trigger'); + if (response !== null && response !== undefined) { + await pipedriveCommon.unsubscribeWebhook( + response.webhookId, + context.auth.data['api_domain'], + context.auth.access_token, + ); + } + }, + async test(context) { + const personsResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/persons', + query: { limit: 5, sort: 'update_time DESC' }, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/personFields', + }); + + if (isNil(personsResponse.data)) { + return []; + } + + const result = []; + + for (const person of personsResponse.data) { + const updatedPersonProperties = pipedriveTransformCustomFields(customFieldsResponse, person); + result.push(updatedPersonProperties); + } + + return result; + }, + async run(context) { + // Pipedrive will always return a list of Persons even if we are looking up a specific person + const payloadBody = context.payload.body as PayloadBody; + const personResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: `/persons/${payloadBody.current.id}`, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/personFields', + }); + + const updatedPersonProperties = pipedriveTransformCustomFields( + customFieldsResponse, + personResponse.data, + ); + + return [updatedPersonProperties]; + }, + sampleData: { + id: 1, + company_id: 12, + owner_id: { + id: 123, + name: 'Jane Doe', + email: 'jane@pipedrive.com', + has_pic: 1, + pic_hash: '2611ace8ac6a3afe2f69ed56f9e08c6b', + active_flag: true, + value: 123, + }, + org_id: { + name: 'Org Name', + people_count: 1, + owner_id: 123, + address: 'Mustamäe tee 3a, 10615 Tallinn', + active_flag: true, + cc_email: 'org@pipedrivemail.com', + value: 1234, + }, + name: 'Will Smith', + first_name: 'Will', + last_name: 'Smith', + open_deals_count: 2, + related_open_deals_count: 2, + closed_deals_count: 3, + related_closed_deals_count: 3, + participant_open_deals_count: 1, + participant_closed_deals_count: 1, + email_messages_count: 1, + activities_count: 1, + done_activities_count: 1, + undone_activities_count: 2, + files_count: 2, + notes_count: 2, + followers_count: 3, + won_deals_count: 3, + related_won_deals_count: 3, + lost_deals_count: 1, + related_lost_deals_count: 1, + active_flag: true, + phone: [ + { + value: '12345', + primary: true, + label: 'work', + }, + ], + email: [ + { + value: '12345@email.com', + primary: true, + label: 'work', + }, + ], + primary_email: '12345@email.com', + first_char: 'w', + update_time: '2020-05-08 05:30:20', + add_time: '2017-10-18 13:23:07', + visible_to: '3', + marketing_status: 'no_consent', + picture_id: { + item_type: 'person', + item_id: 25, + active_flag: true, + add_time: '2020-09-08 08:17:52', + update_time: '0000-00-00 00:00:00', + added_by_user_id: 967055, + pictures: { + '128': + 'https://pipedrive-profile-pics.s3.example.com/f8893852574273f2747bf6ef09d11cfb4ac8f269_128.jpg', + '512': + 'https://pipedrive-profile-pics.s3.example.com/f8893852574273f2747bf6ef09d11cfb4ac8f269_512.jpg', + }, + value: 4, + }, + next_activity_date: '2019-11-29', + next_activity_time: '11:30:00', + next_activity_id: 128, + last_activity_id: 34, + last_activity_date: '2019-11-28', + last_incoming_mail_time: '2019-05-29 18:21:42', + last_outgoing_mail_time: '2019-05-30 03:45:35', + label: 1, + org_name: 'Organization name', + owner_name: 'Jane Doe', + cc_email: 'org@pipedrivemail.com', + }, +}); + +interface WebhookInformation { + webhookId: string; +} + +type PayloadBody = { + current: Record; + previous: Record; +}; diff --git a/packages/pieces/community/pipedrive/src/lib/trigger/organization-matching-filter.ts b/packages/pieces/community/pipedrive/src/lib/trigger/organization-matching-filter.ts new file mode 100644 index 0000000..01daf3f --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/trigger/organization-matching-filter.ts @@ -0,0 +1,186 @@ +import { pipedriveAuth } from '../../index'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { filterIdProp } from '../common/props'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { GetField, LeadListResponse } from '../common/types'; +import { isNil } from '@activepieces/shared'; + +export const organizationMatchingFilterTrigger = createTrigger({ + auth: pipedriveAuth, + name: 'organization-matching-filter', + displayName: 'Organization Matching Filter', + description: 'Trigges when an organization newly matches a Pipedrive filter for the first time.', + type: TriggerStrategy.POLLING, + props: { + filterId: filterIdProp('org', true), + }, + async onEnable(context) { + const ids: number[] = []; + const response = await pipedrivePaginatedApiCall<{ id: number }>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/organizations:(id)', + query: { sort: 'update_time DESC', filter_id: context.propsValue.filterId }, + }); + if (!isNil(response)) { + response.forEach((organization) => { + ids.push(organization.id); + }); + } + await context.store.put('organizations', JSON.stringify(ids)); + }, + async onDisable(context) { + await context.store.delete('organizations'); + }, + async test(context) { + const organizations = []; + + const response = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/organizations', + query: { + limit: 10, + sort: 'update_time DESC', + filter_id: context.propsValue.filterId, + }, + }); + + if (isNil(response.data)) { + return []; + } + + for (const org of response.data) { + organizations.push(org); + } + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/organizationFields', + }); + + const result = []; + + for (const org of organizations) { + const updatedOrgProperties = pipedriveTransformCustomFields(customFieldsResponse, org); + result.push(updatedOrgProperties); + } + + return result; + }, + async run(context) { + const existingIds = (await context.store.get('organizations')) ?? '[]'; + const parsedExistingIds = JSON.parse(existingIds) as number[]; + + const response = await pipedrivePaginatedApiCall<{ id: number }>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/organizations', + query: { sort: 'update_time DESC', filter_id: context.propsValue.filterId }, + }); + + if (isNil(response) || response.length === 0) { + return []; + } + + // Filter valid organizations + const newOrganizations = response.filter( + (organization) => !parsedExistingIds.includes(organization.id), + ); + + const newIds = newOrganizations.map((organization) => organization.id); + + if (newIds.length === 0) { + return []; + } + + + // Store new IDs + await context.store.put('organizations', JSON.stringify([...newIds,...parsedExistingIds])); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/organizationFields', + }); + + const result = []; + + // Transform valid organizations fields + for (const org of newOrganizations) { + const updatedOrgProperties = pipedriveTransformCustomFields(customFieldsResponse, org); + result.push(updatedOrgProperties); + } + + return result; + }, + sampleData: { + id: 1, + company_id: 13937255, + owner_id: { + id: 22701301, + name: 'john', + email: 'john@test.com', + has_pic: 0, + pic_hash: null, + active_flag: true, + value: 22701301, + }, + name: 'Pipedrive', + open_deals_count: 3, + related_open_deals_count: 1, + closed_deals_count: 0, + related_closed_deals_count: 0, + email_messages_count: 0, + people_count: 3, + activities_count: 1, + done_activities_count: 0, + undone_activities_count: 1, + files_count: 0, + notes_count: 4, + followers_count: 1, + won_deals_count: 0, + related_won_deals_count: 0, + lost_deals_count: 0, + related_lost_deals_count: 0, + active_flag: true, + picture_id: null, + country_code: null, + first_char: 'a', + update_time: '2024-12-14 11:03:19', + delete_time: null, + add_time: '2024-12-04 03:49:06', + visible_to: '3', + next_activity_date: '2024-12-04', + next_activity_time: null, + next_activity_id: 4, + last_activity_id: null, + last_activity_date: null, + label: null, + label_ids: [], + address: null, + address_subpremise: null, + address_street_number: null, + address_route: null, + address_sublocality: null, + address_locality: null, + address_admin_area_level_1: null, + address_admin_area_level_2: null, + address_country: null, + address_postal_code: null, + address_formatted_address: null, + owner_name: 'John', + cc_email: null, + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/trigger/person-matching-filter.ts b/packages/pieces/community/pipedrive/src/lib/trigger/person-matching-filter.ts new file mode 100644 index 0000000..69bd47a --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/trigger/person-matching-filter.ts @@ -0,0 +1,218 @@ +import { pipedriveAuth } from '../../index'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { filterIdProp } from '../common/props'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { GetField, LeadListResponse } from '../common/types'; +import { isNil } from '@activepieces/shared'; + +export const personMatchingFilterTrigger = createTrigger({ + auth: pipedriveAuth, + name: 'person-matching-filter', + displayName: 'Person Matching Filter', + description: 'Trigges when a person newly matches a Pipedrive filter for the first time.', + type: TriggerStrategy.POLLING, + props: { + filterId: filterIdProp('people', true), + }, + async onEnable(context) { + const ids: number[] = []; + const response = await pipedrivePaginatedApiCall<{ id: number }>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/persons:(id)', + query: { sort: 'update_time DESC', filter_id: context.propsValue.filterId }, + }); + if (!isNil(response)) { + response.forEach((person) => { + ids.push(person.id); + }); + } + await context.store.put('persons', JSON.stringify(ids)); + }, + async onDisable(context) { + await context.store.delete('persons'); + }, + async test(context) { + const persons = []; + + const response = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/persons', + query: { + limit: 10, + sort: 'update_time DESC', + filter_id: context.propsValue.filterId, + }, + }); + + if (isNil(response.data)) { + return []; + } + + for (const person of response.data) { + persons.push(person); + } + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/personFields', + }); + + const result = []; + + for (const person of persons) { + const updatedPersonProperties = pipedriveTransformCustomFields(customFieldsResponse, person); + result.push(updatedPersonProperties); + } + + return result; + }, + async run(context) { + const existingIds = (await context.store.get('persons')) ?? '[]'; + const parsedExistingIds = JSON.parse(existingIds) as number[]; + + const response = await pipedrivePaginatedApiCall<{ id: number }>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/persons', + query: { sort: 'update_time DESC', filter_id: context.propsValue.filterId }, + }); + + if (isNil(response) || response.length === 0) { + return []; + } + + // Filter valid persons + const newPersons = response.filter( + (person) => !parsedExistingIds.includes(person.id), + ); + + const newIds = newPersons.map((person) => person.id); + + if (newIds.length === 0) { + return []; + } + + + // Store new IDs + await context.store.put('persons', JSON.stringify([...newIds,...parsedExistingIds])); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/personFields', + }); + + const result = []; + + // Transform valid persons fields + for (const person of newPersons) { + const updatedPersonProperties = pipedriveTransformCustomFields(customFieldsResponse, person); + result.push(updatedPersonProperties); + } + + + return result; + }, + sampleData: { + id: 1, + company_id: 12, + owner_id: { + id: 123, + name: 'Jane Doe', + email: 'jane@pipedrive.com', + has_pic: 1, + pic_hash: '2611ace8ac6a3afe2f69ed56f9e08c6b', + active_flag: true, + value: 123, + }, + org_id: { + name: 'Org Name', + people_count: 1, + owner_id: 123, + address: 'Mustamäe tee 3a, 10615 Tallinn', + active_flag: true, + cc_email: 'org@pipedrivemail.com', + value: 1234, + }, + name: 'Will Smith', + first_name: 'Will', + last_name: 'Smith', + open_deals_count: 2, + related_open_deals_count: 2, + closed_deals_count: 3, + related_closed_deals_count: 3, + participant_open_deals_count: 1, + participant_closed_deals_count: 1, + email_messages_count: 1, + activities_count: 1, + done_activities_count: 1, + undone_activities_count: 2, + files_count: 2, + notes_count: 2, + followers_count: 3, + won_deals_count: 3, + related_won_deals_count: 3, + lost_deals_count: 1, + related_lost_deals_count: 1, + active_flag: true, + phone: [ + { + value: '12345', + primary: true, + label: 'work', + }, + ], + email: [ + { + value: '12345@email.com', + primary: true, + label: 'work', + }, + ], + primary_email: '12345@email.com', + first_char: 'w', + update_time: '2020-05-08 05:30:20', + add_time: '2017-10-18 13:23:07', + visible_to: '3', + marketing_status: 'no_consent', + picture_id: { + item_type: 'person', + item_id: 25, + active_flag: true, + add_time: '2020-09-08 08:17:52', + update_time: '0000-00-00 00:00:00', + added_by_user_id: 967055, + pictures: { + '128': + 'https://pipedrive-profile-pics.s3.example.com/f8893852574273f2747bf6ef09d11cfb4ac8f269_128.jpg', + '512': + 'https://pipedrive-profile-pics.s3.example.com/f8893852574273f2747bf6ef09d11cfb4ac8f269_512.jpg', + }, + value: 4, + }, + next_activity_date: '2019-11-29', + next_activity_time: '11:30:00', + next_activity_id: 128, + last_activity_id: 34, + last_activity_date: '2019-11-28', + last_incoming_mail_time: '2019-05-29 18:21:42', + last_outgoing_mail_time: '2019-05-30 03:45:35', + label: 1, + org_name: 'Organization name', + owner_name: 'Jane Doe', + cc_email: 'org@pipedrivemail.com', + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/trigger/updated-deal-stage.ts b/packages/pieces/community/pipedrive/src/lib/trigger/updated-deal-stage.ts new file mode 100644 index 0000000..48df53f --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/trigger/updated-deal-stage.ts @@ -0,0 +1,326 @@ +import { pipedriveAuth } from '../../index'; +import { + createTrigger, + DropdownOption, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { + pipedriveApiCall, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { + GetDealResponse, + GetField, + ListDealsResponse, + RequestParams, + StageWithPipelineInfo, + WebhookCreateResponse, +} from '../common/types'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; + +export const updatedDealStageTrigger = createTrigger({ + auth: pipedriveAuth, + name: 'updated-deal-stage', + displayName: 'Updated Deal Stage', + description: "Triggers when a deal's stage is updated.", + type: TriggerStrategy.WEBHOOK, + props: { + stage_id: Property.Dropdown({ + displayName: 'Stage in Pipeline', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + placeholder: 'please connect your account.', + disabled: true, + options: [], + }; + } + + const authValue = auth as PiecePropValueSchema; + const response = await pipedrivePaginatedApiCall({ + accessToken: authValue.access_token, + apiDomain: authValue.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/stages', + }); + + const options: DropdownOption[] = []; + for (const stage of response) { + options.push({ + label: `${stage.name} (${stage.pipeline_name})`, + value: stage.id, + }); + } + + return { + disabled: false, + options, + }; + }, + }), + }, + async onEnable(context) { + const response = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.POST, + resourceUri: '/webhooks', + body: { + subscription_url: context.webhookUrl, + event_object: 'deal', + event_action: 'updated', + version:'1.0' + }, + }); + + await context.store.put('updated-deal-stage-trigger', response.data.id); + }, + async onDisable(context) { + const webhook = await context.store.get('updated-deal-stage-trigger'); + if (webhook) { + await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.DELETE, + resourceUri: `/webhooks/${webhook}`, + }); + } + }, + async test(context) { + const stageId = context.propsValue.stage_id; + + const qs: RequestParams = { + limit: 10, + sort: 'update_time DESC', + }; + + if (stageId) { + qs['stage_id'] = stageId.toString(); + } + + const dealsResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/deals', + query: qs, + }); + + if (isNil(dealsResponse.data)) { + return []; + } + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/dealFields', + }); + + const result = []; + + for (const deal of dealsResponse.data) { + const updatedDealProperties = pipedriveTransformCustomFields(customFieldsResponse, deal); + const stageResponse = await pipedriveApiCall<{data:Record}>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: `/stages/${updatedDealProperties.stage_id}`, + }); + + updatedDealProperties['stage'] = stageResponse.data; + result.push(updatedDealProperties); + } + + return result; + }, + async run(context) { + const stageId = context.propsValue.stage_id; + + + const payloadBody = context.payload.body as PayloadBody; + const currentDealData = payloadBody.current; + const previousDealData = payloadBody.previous; + + if (currentDealData.stage_id !== previousDealData.stage_id) { + + if (stageId && currentDealData.stage_id !== stageId) { + return []; + } + + const dealResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: `/deals/${payloadBody.current.id}`, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/dealFields', + }); + + const updatedDealProperties = pipedriveTransformCustomFields( + customFieldsResponse, + dealResponse.data, + ); + + const stageResponse = await pipedriveApiCall<{data:Record}>({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: `/stages/${currentDealData.stage_id}`, + }); + + updatedDealProperties['stage'] = stageResponse.data; + + return [updatedDealProperties]; + } + return []; + }, + sampleData: { + id: 1, + creator_user_id: { + id: 8877, + name: 'Creator', + email: 'john.doe@pipedrive.com', + has_pic: false, + pic_hash: null, + active_flag: true, + value: 8877, + }, + user_id: { + id: 8877, + name: 'Creator', + email: 'john.doe@pipedrive.com', + has_pic: false, + pic_hash: null, + active_flag: true, + value: 8877, + }, + person_id: { + active_flag: true, + name: 'Person', + email: [ + { + label: 'work', + value: 'person@pipedrive.com', + primary: true, + }, + ], + phone: [ + { + label: 'work', + value: '37244499911', + primary: true, + }, + ], + value: 1101, + }, + org_id: { + name: 'Organization', + people_count: 2, + owner_id: 8877, + address: '', + active_flag: true, + cc_email: 'org@pipedrivemail.com', + value: 5, + }, + stage_id: 2, + title: 'Deal One', + value: 5000, + currency: 'EUR', + add_time: '2019-05-29 04:21:51', + update_time: '2019-11-28 16:19:50', + stage_change_time: '2019-11-28 15:41:22', + active: true, + deleted: false, + status: 'open', + probability: null, + next_activity_date: '2019-11-29', + next_activity_time: '11:30:00', + next_activity_id: 128, + last_activity_id: null, + last_activity_date: null, + lost_reason: null, + visible_to: '1', + close_time: null, + pipeline_id: 1, + won_time: '2019-11-27 11:40:36', + first_won_time: '2019-11-27 11:40:36', + lost_time: '', + products_count: 0, + files_count: 0, + notes_count: 2, + followers_count: 0, + email_messages_count: 4, + activities_count: 1, + done_activities_count: 0, + undone_activities_count: 1, + participants_count: 1, + expected_close_date: '2019-06-29', + last_incoming_mail_time: '2019-05-29 18:21:42', + last_outgoing_mail_time: '2019-05-30 03:45:35', + label: 11, + stage_order_nr: 2, + person_name: 'Person', + org_name: 'Organization', + next_activity_subject: 'Call', + next_activity_type: 'call', + next_activity_duration: '00:30:00', + next_activity_note: 'Note content', + formatted_value: '€5,000', + weighted_value: 5000, + formatted_weighted_value: '€5,000', + weighted_value_currency: 'EUR', + rotten_time: null, + owner_name: 'Creator', + cc_email: 'company+deal1@pipedrivemail.com', + org_hidden: false, + person_hidden: false, + average_time_to_won: { + y: 0, + m: 0, + d: 0, + h: 0, + i: 20, + s: 49, + total_seconds: 1249, + }, + average_stage_progress: 4.99, + age: { + y: 0, + m: 6, + d: 14, + h: 8, + i: 57, + s: 26, + total_seconds: 17139446, + }, + stay_in_pipeline_stages: { + times_in_stages: { + '1': 15721267, + '2': 1288449, + '3': 4368, + '4': 3315, + '5': 26460, + }, + order_of_stages: [1, 2, 3, 4, 5], + }, + last_activity: null, + next_activity: null, + }, +}); + +type PayloadBody = { + current: Record; + previous: Record; +}; diff --git a/packages/pieces/community/pipedrive/src/lib/trigger/updated-deal.ts b/packages/pieces/community/pipedrive/src/lib/trigger/updated-deal.ts new file mode 100644 index 0000000..e3a1482 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/trigger/updated-deal.ts @@ -0,0 +1,390 @@ +import { + createTrigger, + DropdownOption, + DynamicPropsValue, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { + pipedriveApiCall, + pipedriveCommon, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { pipedriveAuth } from '../..'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { + FieldsResponse, + ListDealsResponse, + GetDealResponse, + GetStagesResponse, + RequestParams, + GetField, +} from '../common/types'; +import { isNil } from '@activepieces/shared'; + +export const updatedDeal = createTrigger({ + auth: pipedriveAuth, + name: 'updated_deal', + displayName: 'Updated Deal', + description: 'Triggers when a deal is updated', + props: { + filter_by: Property.StaticDropdown({ + displayName: 'Filter by', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Deal Status', + value: 'status', + }, + + { + label: 'Stage in Pipeline', + value: 'stage_id', + }, + ], + }, + }), + filter_by_field_value: Property.DynamicProperties({ + displayName: 'Field Values', + required: false, + refreshers: ['filter_by'], + props: async ({ auth, filter_by }) => { + if (!auth || !filter_by) return {}; + + const props: DynamicPropsValue = {}; + const authValue = auth as PiecePropValueSchema; + const filterBy = filter_by as unknown as string; + + if (filterBy === 'status') { + props['field_value'] = Property.StaticDropdown({ + displayName: 'Deal Status', + required: true, + options: { + disabled: false, + options: [ + { label: 'Open', value: 'open' }, + { label: 'Won', value: 'won' }, + { label: 'Lost', value: 'lost' }, + { label: 'Deleted', value: 'deleted' }, + ], + }, + }); + } + if (filterBy === 'stage_id') { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${authValue.data['api_domain']}/api/v1/stages`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authValue.access_token, + }, + }); + props['field_value'] = Property.StaticDropdown({ + displayName: 'Stage in Pipeline', + required: true, + options: { + disabled: false, + options: response.body.data.map((stage) => { + return { + label: stage.name, + value: stage.id, + }; + }), + }, + }); + } + return props; + }, + }), + field_to_watch: Property.Dropdown({ + displayName: 'Field to watch for Changes On', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + placeholder: 'Connect your account', + disabled: true, + options: [], + }; + } + + const authValue = auth as PiecePropValueSchema; + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${authValue.data['api_domain']}/api/v1/dealFields`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authValue.access_token, + }, + }); + + const options: DropdownOption[] = []; + + for (const field of response.body.data) { + options.push({ + label: field.name, + value: field.key, + }); + } + + return { + disabled: false, + options, + }; + }, + }), + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const webhook = await pipedriveCommon.subscribeWebhook( + 'deal', + 'updated', + context.webhookUrl!, + context.auth.data['api_domain'], + context.auth.access_token, + ); + await context.store?.put('_updated_deal_trigger', { + webhookId: webhook.data.id, + }); + }, + async onDisable(context) { + const response = await context.store?.get('_updated_deal_trigger'); + if (response !== null && response !== undefined) { + await pipedriveCommon.unsubscribeWebhook( + response.webhookId, + context.auth.data['api_domain'], + context.auth.access_token, + ); + } + }, + async test(context) { + const filterBy = context.propsValue.filter_by; + const filterByValue = context.propsValue.filter_by_field_value!['field_value']; + + const qs: RequestParams = { + limit: 10, + sort: 'update_time DESC', + }; + + if (filterBy && filterByValue) { + qs[filterBy] = filterByValue; + } + + const dealsResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/deals', + query: qs, + }); + + if (isNil(dealsResponse.data)) { + return []; + } + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/dealFields', + }); + + const result = []; + + for (const deal of dealsResponse.data) { + const updatedDealProperties = pipedriveTransformCustomFields(customFieldsResponse, deal); + result.push(updatedDealProperties); + } + + return result; + }, + async run(context) { + const filterBy = context.propsValue.filter_by; + const filterByValue = context.propsValue.filter_by_field_value!['field_value']; + const fieldToWatch = context.propsValue.field_to_watch; + + const payloadBody = context.payload.body as PayloadBody; + const currentDealData = payloadBody.current; + const previousDealData = payloadBody.previous; + + // No filters and no field to watch specified + const noFilterAndNoField = !filterBy && !fieldToWatch; + const isFieldChanged = + fieldToWatch && currentDealData[fieldToWatch] !== previousDealData[fieldToWatch]; + const isFilterMatched = filterBy && currentDealData[filterBy] === filterByValue; + + if ( + noFilterAndNoField || + (!filterBy && isFieldChanged) || + (isFilterMatched && (!fieldToWatch || isFieldChanged)) + ) { + const dealResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: `/deals/${payloadBody.current.id}`, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/dealFields', + }); + + const updatedDealProperties = pipedriveTransformCustomFields( + customFieldsResponse, + dealResponse.data, + ); + + return [updatedDealProperties]; + } + return []; + }, + sampleData: { + id: 1, + creator_user_id: { + id: 8877, + name: 'Creator', + email: 'john.doe@pipedrive.com', + has_pic: false, + pic_hash: null, + active_flag: true, + value: 8877, + }, + user_id: { + id: 8877, + name: 'Creator', + email: 'john.doe@pipedrive.com', + has_pic: false, + pic_hash: null, + active_flag: true, + value: 8877, + }, + person_id: { + active_flag: true, + name: 'Person', + email: [ + { + label: 'work', + value: 'person@pipedrive.com', + primary: true, + }, + ], + phone: [ + { + label: 'work', + value: '37244499911', + primary: true, + }, + ], + value: 1101, + }, + org_id: { + name: 'Organization', + people_count: 2, + owner_id: 8877, + address: '', + active_flag: true, + cc_email: 'org@pipedrivemail.com', + value: 5, + }, + stage_id: 2, + title: 'Deal One', + value: 5000, + currency: 'EUR', + add_time: '2019-05-29 04:21:51', + update_time: '2019-11-28 16:19:50', + stage_change_time: '2019-11-28 15:41:22', + active: true, + deleted: false, + status: 'open', + probability: null, + next_activity_date: '2019-11-29', + next_activity_time: '11:30:00', + next_activity_id: 128, + last_activity_id: null, + last_activity_date: null, + lost_reason: null, + visible_to: '1', + close_time: null, + pipeline_id: 1, + won_time: '2019-11-27 11:40:36', + first_won_time: '2019-11-27 11:40:36', + lost_time: '', + products_count: 0, + files_count: 0, + notes_count: 2, + followers_count: 0, + email_messages_count: 4, + activities_count: 1, + done_activities_count: 0, + undone_activities_count: 1, + participants_count: 1, + expected_close_date: '2019-06-29', + last_incoming_mail_time: '2019-05-29 18:21:42', + last_outgoing_mail_time: '2019-05-30 03:45:35', + label: 11, + stage_order_nr: 2, + person_name: 'Person', + org_name: 'Organization', + next_activity_subject: 'Call', + next_activity_type: 'call', + next_activity_duration: '00:30:00', + next_activity_note: 'Note content', + formatted_value: '€5,000', + weighted_value: 5000, + formatted_weighted_value: '€5,000', + weighted_value_currency: 'EUR', + rotten_time: null, + owner_name: 'Creator', + cc_email: 'company+deal1@pipedrivemail.com', + org_hidden: false, + person_hidden: false, + average_time_to_won: { + y: 0, + m: 0, + d: 0, + h: 0, + i: 20, + s: 49, + total_seconds: 1249, + }, + average_stage_progress: 4.99, + age: { + y: 0, + m: 6, + d: 14, + h: 8, + i: 57, + s: 26, + total_seconds: 17139446, + }, + stay_in_pipeline_stages: { + times_in_stages: { + '1': 15721267, + '2': 1288449, + '3': 4368, + '4': 3315, + '5': 26460, + }, + order_of_stages: [1, 2, 3, 4, 5], + }, + last_activity: null, + next_activity: null, + }, +}); + +interface WebhookInformation { + webhookId: string; +} + +type PayloadBody = { + current: Record; + previous: Record; +}; diff --git a/packages/pieces/community/pipedrive/src/lib/trigger/updated-organization.ts b/packages/pieces/community/pipedrive/src/lib/trigger/updated-organization.ts new file mode 100644 index 0000000..df6f3ee --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/trigger/updated-organization.ts @@ -0,0 +1,159 @@ +import { pipedriveAuth } from '../../'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { + pipedriveApiCall, + pipedriveCommon, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { GetDealResponse, GetField, LeadListResponse } from '../common/types'; +import { isNil } from '@activepieces/shared'; + +export const updatedOrganizationTrigger = createTrigger({ + auth: pipedriveAuth, + name: 'updated-organization', + displayName: 'Updated Organization', + description: 'Triggers when an existing organization is updated.', + props: {}, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const webhook = await pipedriveCommon.subscribeWebhook( + 'organization', + 'updated', + context.webhookUrl!, + context.auth.data['api_domain'], + context.auth.access_token, + ); + await context.store?.put<{ + webhookId: string; + }>('_updated_organization_trigger', { + webhookId: webhook.data.id, + }); + }, + async onDisable(context) { + const response = await context.store?.get<{ + webhookId: string; + }>('_updated_organization_trigger'); + if (response !== null && response !== undefined) { + await pipedriveCommon.unsubscribeWebhook( + response.webhookId, + context.auth.data['api_domain'], + context.auth.access_token, + ); + } + }, + async test(context) { + const response = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/organizations', + query: { limit: 10, sort: 'update_time DESC' }, + }); + + if (isNil(response.data)) { + return []; + } + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/organizationFields', + }); + + const result = []; + + for (const org of response.data) { + const updatedOrgProperties = pipedriveTransformCustomFields(customFieldsResponse, org); + result.push(updatedOrgProperties); + } + + return result; + }, + async run(context) { + const payloadBody = context.payload.body as { + current: Record; + previous: Record; + }; + + const orgResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: `/organizations/${payloadBody.current.id}`, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/organizationFields', + }); + + const updatedOrgProperties = pipedriveTransformCustomFields( + customFieldsResponse, + orgResponse.data, + ); + + return [updatedOrgProperties]; + }, + sampleData: { + id: 1, + company_id: 13937255, + owner_id: { + id: 22701301, + name: 'john', + email: 'john@test.com', + has_pic: 0, + pic_hash: null, + active_flag: true, + value: 22701301, + }, + name: 'Pipedrive', + open_deals_count: 3, + related_open_deals_count: 1, + closed_deals_count: 0, + related_closed_deals_count: 0, + email_messages_count: 0, + people_count: 3, + activities_count: 1, + done_activities_count: 0, + undone_activities_count: 1, + files_count: 0, + notes_count: 4, + followers_count: 1, + won_deals_count: 0, + related_won_deals_count: 0, + lost_deals_count: 0, + related_lost_deals_count: 0, + active_flag: true, + picture_id: null, + country_code: null, + first_char: 'a', + update_time: '2024-12-14 11:03:19', + delete_time: null, + add_time: '2024-12-04 03:49:06', + visible_to: '3', + next_activity_date: '2024-12-04', + next_activity_time: null, + next_activity_id: 4, + last_activity_id: null, + last_activity_date: null, + label: null, + label_ids: [], + address: null, + address_subpremise: null, + address_street_number: null, + address_route: null, + address_sublocality: null, + address_locality: null, + address_admin_area_level_1: null, + address_admin_area_level_2: null, + address_country: null, + address_postal_code: null, + address_formatted_address: null, + owner_name: 'John', + cc_email: null, + }, +}); diff --git a/packages/pieces/community/pipedrive/src/lib/trigger/updated-person.ts b/packages/pieces/community/pipedrive/src/lib/trigger/updated-person.ts new file mode 100644 index 0000000..2a5ad58 --- /dev/null +++ b/packages/pieces/community/pipedrive/src/lib/trigger/updated-person.ts @@ -0,0 +1,195 @@ +import { createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { + pipedriveApiCall, + pipedriveCommon, + pipedrivePaginatedApiCall, + pipedriveTransformCustomFields, +} from '../common'; +import { pipedriveAuth } from '../..'; +import { GetField, PersonListResponse } from '../common/types'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; + +export const updatedPerson = createTrigger({ + auth: pipedriveAuth, + name: 'updated_person', + displayName: 'Updated Person', + description: 'Triggers when a person is updated', + props: {}, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const webhook = await pipedriveCommon.subscribeWebhook( + 'person', + 'updated', + context.webhookUrl!, + context.auth.data['api_domain'], + context.auth.access_token, + ); + await context.store?.put('_updated_person_trigger', { + webhookId: webhook.data.id, + }); + }, + + async onDisable(context) { + const response = await context.store?.get('_updated_person_trigger'); + if (response !== null && response !== undefined) { + const webhook = await pipedriveCommon.unsubscribeWebhook( + response.webhookId, + context.auth.data['api_domain'], + context.auth.access_token, + ); + } + }, + async test(context) { + const personsResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/persons', + query: { limit: 5, sort: 'update_time DESC' }, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/personFields', + }); + + if (isNil(personsResponse.data)) { + return []; + } + + const result = []; + + for (const person of personsResponse.data) { + const updatedPersonProperties = pipedriveTransformCustomFields(customFieldsResponse, person); + result.push(updatedPersonProperties); + } + + return result; + }, + async run(context) { + // Pipedrive will always return a list of Persons even if we are looking up a specific person + const payloadBody = context.payload.body as PayloadBody; + const personResponse = await pipedriveApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: `/persons/${payloadBody.current.id}`, + }); + + const customFieldsResponse = await pipedrivePaginatedApiCall({ + accessToken: context.auth.access_token, + apiDomain: context.auth.data['api_domain'], + method: HttpMethod.GET, + resourceUri: '/personFields', + }); + + const updatedPersonProperties = pipedriveTransformCustomFields( + customFieldsResponse, + personResponse.data, + ); + + return [updatedPersonProperties]; + }, + sampleData: { + id: 1, + company_id: 12, + owner_id: { + id: 123, + name: 'Jane Doe', + email: 'jane@pipedrive.com', + has_pic: 1, + pic_hash: '2611ace8ac6a3afe2f69ed56f9e08c6b', + active_flag: true, + value: 123, + }, + org_id: { + name: 'Org Name', + people_count: 1, + owner_id: 123, + address: 'Mustamäe tee 3a, 10615 Tallinn', + active_flag: true, + cc_email: 'org@pipedrivemail.com', + value: 1234, + }, + name: 'Will Smith', + first_name: 'Will', + last_name: 'Smith', + open_deals_count: 2, + related_open_deals_count: 2, + closed_deals_count: 3, + related_closed_deals_count: 3, + participant_open_deals_count: 1, + participant_closed_deals_count: 1, + email_messages_count: 1, + activities_count: 1, + done_activities_count: 1, + undone_activities_count: 2, + files_count: 2, + notes_count: 2, + followers_count: 3, + won_deals_count: 3, + related_won_deals_count: 3, + lost_deals_count: 1, + related_lost_deals_count: 1, + active_flag: true, + phone: [ + { + value: '12345', + primary: true, + label: 'work', + }, + ], + email: [ + { + value: '12345@email.com', + primary: true, + label: 'work', + }, + ], + primary_email: '12345@email.com', + first_char: 'w', + update_time: '2020-05-08 05:30:20', + add_time: '2017-10-18 13:23:07', + visible_to: '3', + marketing_status: 'no_consent', + picture_id: { + item_type: 'person', + item_id: 25, + active_flag: true, + add_time: '2020-09-08 08:17:52', + update_time: '0000-00-00 00:00:00', + added_by_user_id: 967055, + pictures: { + '128': + 'https://pipedrive-profile-pics.s3.example.com/f8893852574273f2747bf6ef09d11cfb4ac8f269_128.jpg', + '512': + 'https://pipedrive-profile-pics.s3.example.com/f8893852574273f2747bf6ef09d11cfb4ac8f269_512.jpg', + }, + value: 4, + }, + next_activity_date: '2019-11-29', + next_activity_time: '11:30:00', + next_activity_id: 128, + last_activity_id: 34, + last_activity_date: '2019-11-28', + last_incoming_mail_time: '2019-05-29 18:21:42', + last_outgoing_mail_time: '2019-05-30 03:45:35', + label: 1, + org_name: 'Organization name', + owner_name: 'Jane Doe', + cc_email: 'org@pipedrivemail.com', + }, +}); + +interface WebhookInformation { + webhookId: string; +} + +type PayloadBody = { + current: Record; + previous: Record; +}; diff --git a/packages/pieces/community/pipedrive/tsconfig.json b/packages/pieces/community/pipedrive/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/pipedrive/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/pipedrive/tsconfig.lib.json b/packages/pieces/community/pipedrive/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/pipedrive/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/poper/.eslintrc.json b/packages/pieces/community/poper/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/poper/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/poper/README.md b/packages/pieces/community/poper/README.md new file mode 100644 index 0000000..9deb0c9 --- /dev/null +++ b/packages/pieces/community/poper/README.md @@ -0,0 +1,7 @@ +# pieces-poper + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-poper` to build the library. diff --git a/packages/pieces/community/poper/package.json b/packages/pieces/community/poper/package.json new file mode 100644 index 0000000..01eca6f --- /dev/null +++ b/packages/pieces/community/poper/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-poper", + "version": "0.1.1" +} diff --git a/packages/pieces/community/poper/project.json b/packages/pieces/community/poper/project.json new file mode 100644 index 0000000..f0164ac --- /dev/null +++ b/packages/pieces/community/poper/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-poper", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/poper/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/poper", + "tsConfig": "packages/pieces/community/poper/tsconfig.lib.json", + "packageJson": "packages/pieces/community/poper/package.json", + "main": "packages/pieces/community/poper/src/index.ts", + "assets": [ + "packages/pieces/community/poper/*.md", + { + "input": "packages/pieces/community/poper/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-poper {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/poper/src/index.ts b/packages/pieces/community/poper/src/index.ts new file mode 100644 index 0000000..313d92c --- /dev/null +++ b/packages/pieces/community/poper/src/index.ts @@ -0,0 +1,16 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { newLead } from './lib/triggers/new-lead'; +import { PieceCategory } from '@activepieces/shared'; + +export const poper = createPiece({ + displayName: 'Poper', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.30.0', + categories: [PieceCategory.MARKETING], + description: + 'AI Driven Pop-up Builder that can convert visitors into customers,increase subscriber count, and skyrocket sales.', + logoUrl: 'https://cdn.activepieces.com/pieces/poper.png', + authors: ['thirstycode'], + actions: [], + triggers: [newLead], +}); diff --git a/packages/pieces/community/poper/src/lib/triggers/new-lead.ts b/packages/pieces/community/poper/src/lib/triggers/new-lead.ts new file mode 100644 index 0000000..4032ff2 --- /dev/null +++ b/packages/pieces/community/poper/src/lib/triggers/new-lead.ts @@ -0,0 +1,37 @@ +import { + createTrigger, + TriggerStrategy, + Property, +} from '@activepieces/pieces-framework'; + +const message = ` +1. Log in to your [Poper Account](https://app.poper.ai/). +2. Click on the popup for which you want to set up a trigger. +3. On the left-side menu, click on Integrations and search for **Webhook**. +4. Enter an appropriate webhook name and paste the following URL: + \`\`\`text + {{webhookUrl}} + \`\`\` +`; + +export const newLead = createTrigger({ + name: 'newLead', + displayName: 'New Lead', + description: 'Triggers when a new lead is obtained from popup.', + props: { + markdown: Property.MarkDown({ + value: message, + }), + }, + sampleData: {}, + type: TriggerStrategy.WEBHOOK, + async onEnable() { + // ignore + }, + async onDisable() { + // ignore + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/poper/tsconfig.json b/packages/pieces/community/poper/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/poper/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/poper/tsconfig.lib.json b/packages/pieces/community/poper/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/poper/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/postgres/.eslintrc.json b/packages/pieces/community/postgres/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/postgres/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/postgres/README.md b/packages/pieces/community/postgres/README.md new file mode 100644 index 0000000..f10de20 --- /dev/null +++ b/packages/pieces/community/postgres/README.md @@ -0,0 +1,7 @@ +# pieces-postgres + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-postgres` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/postgres/package.json b/packages/pieces/community/postgres/package.json new file mode 100644 index 0000000..65fe5ea --- /dev/null +++ b/packages/pieces/community/postgres/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-postgres", + "version": "0.1.9" +} \ No newline at end of file diff --git a/packages/pieces/community/postgres/project.json b/packages/pieces/community/postgres/project.json new file mode 100644 index 0000000..ce628dc --- /dev/null +++ b/packages/pieces/community/postgres/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-postgres", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/postgres/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/postgres", + "tsConfig": "packages/pieces/community/postgres/tsconfig.lib.json", + "packageJson": "packages/pieces/community/postgres/package.json", + "main": "packages/pieces/community/postgres/src/index.ts", + "assets": [ + "packages/pieces/community/postgres/*.md", + { + "input": "packages/pieces/community/postgres/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/postgres/src/index.ts b/packages/pieces/community/postgres/src/index.ts new file mode 100644 index 0000000..08a359c --- /dev/null +++ b/packages/pieces/community/postgres/src/index.ts @@ -0,0 +1,93 @@ +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { runQuery } from './lib/actions/run-query'; +import { newRow } from './lib/triggers/new-row'; +import { pgClient } from './lib/common'; + +export const postgresAuth = PieceAuth.CustomAuth({ + props: { + host: Property.ShortText({ + displayName: 'Host', + required: true, + description: + ' A string indicating the hostname of the PostgreSQL server to connect to.', + }), + port: Property.Number({ + displayName: 'Port', + defaultValue: 5432, + description: + 'An integer indicating the port of the PostgreSQL server to connect to.', + required: true, + }), + user: Property.ShortText({ + displayName: 'User', + required: true, + description: + 'A string indicating the user to authenticate as when connecting to the PostgreSQL server.', + }), + password: PieceAuth.SecretText({ + displayName: 'Password', + description: + 'A string indicating the password to use for authentication.', + required: true, + }), + database: Property.ShortText({ + displayName: 'Database', + description: + 'A string indicating the name of the database to connect to.', + required: true, + }), + enable_ssl: Property.Checkbox({ + displayName: 'Enable SSL', + description: 'Connect to the postgres database over SSL', + required: true, + defaultValue: true, + }), + reject_unauthorized: Property.Checkbox({ + displayName: 'Verify server certificate', + description: + 'Verify the server certificate against trusted CAs or a CA provided in the certificate field below. This will fail if the database server is using a self signed certificate.', + required: true, + defaultValue: false, + }), + certificate: Property.LongText({ + displayName: 'Certificate', + description: + 'The CA certificate to use for verification of server certificate.', + defaultValue: '', + required: false, + }), + }, + required: true, + validate: async ({ auth }) => { + try { + const client = await pgClient(auth); + await client.end(); + } + catch (e) { + return { + valid: false, + error: JSON.stringify(e) + }; + } + return { + valid: true, + }; + } +}); + +export const postgres = createPiece({ + displayName: 'Postgres', + description: "The world's most advanced open-source relational database", + minimumSupportedRelease: '0.30.0', + categories: [PieceCategory.DEVELOPER_TOOLS], + logoUrl: 'https://cdn.activepieces.com/pieces/postgres.png', + authors: ["AbdullahBitar", "Willianwg", "dentych", "kishanprmr", "AbdulTheActivePiecer", "khaledmashaly", "abuaboud"], + auth: postgresAuth, + actions: [runQuery], + triggers: [newRow], +}); diff --git a/packages/pieces/community/postgres/src/lib/actions/run-query.ts b/packages/pieces/community/postgres/src/lib/actions/run-query.ts new file mode 100644 index 0000000..c2afafb --- /dev/null +++ b/packages/pieces/community/postgres/src/lib/actions/run-query.ts @@ -0,0 +1,67 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import pg from 'pg'; +import { postgresAuth } from '../..'; +import { pgClient } from '../common'; + +export const runQuery = createAction({ + auth: postgresAuth, + name: 'run-query', + displayName: 'Run Query', + description: 'Run Query', + props: { + markdown: Property.MarkDown({ + value: ` + **DO NOT** insert dynamic input directly into the query string. Instead, use $1, $2, $3 and add them in args for parameterized queries to prevent **SQL injection.**` + }), + + query: Property.ShortText({ + displayName: 'Query', + description: 'Please use $1, $2, etc. for parameterized queries to avoid SQL injection.', + required: true, + }), + args: Property.Array({ + displayName: 'Arguments', + description: 'Arguments to be used in the query', + required: false, + }), + query_timeout: Property.Number({ + displayName: 'Query Timeout', + description: + 'An integer indicating the maximum number of milliseconds to wait for a query to complete before timing out.', + required: false, + defaultValue: 30000, + }), + connection_timeout_ms: Property.Number({ + displayName: 'Connection Timeout (ms)', + description: + 'An integer indicating the maximum number of milliseconds to wait for a connection to be established before timing out.', + required: false, + defaultValue: 30000, + }), + application_name: Property.ShortText({ + displayName: 'Application Name', + description: + 'A string indicating the name of the client application connecting to the server.', + required: false, + }), + }, + async run(context) { + const client = await pgClient(context.auth, context.propsValue.query_timeout, context.propsValue.application_name, context.propsValue.connection_timeout_ms); + const { query } = context.propsValue; + const queryWithMetadata = ` + /* Source : /projects/${context.project.id}/flows/${context.flows.current.id}/runs/${context.run.id} */ + ${query} + ` + const args = context.propsValue.args || []; + return new Promise((resolve, reject) => { + client.query(queryWithMetadata, args, function (error: any, results: { rows: unknown }) { + if (error) { + client.end(); + return reject(error); + } + resolve(results.rows); + client.end(); + }); + }); + }, +}); diff --git a/packages/pieces/community/postgres/src/lib/common.ts b/packages/pieces/community/postgres/src/lib/common.ts new file mode 100644 index 0000000..0d5025d --- /dev/null +++ b/packages/pieces/community/postgres/src/lib/common.ts @@ -0,0 +1,36 @@ +import { PiecePropValueSchema } from "@activepieces/pieces-framework"; +import { postgresAuth } from ".."; +import { Client } from "pg"; + +export const pgClient = async (auth: PiecePropValueSchema, query_timeout = 30000, application_name: string | undefined = undefined , connectionTimeoutMillis = 30000) => { + const { + host, + user, + database, + password, + port, + enable_ssl, + reject_unauthorized: rejectUnauthorized, + certificate, + } = auth; + + const sslConf = { + rejectUnauthorized: rejectUnauthorized, + ca: certificate && certificate.length > 0 ? certificate : undefined, + }; + const client = new Client({ + host, + port: Number(port), + user, + password, + database, + ssl: enable_ssl ? sslConf : undefined, + query_timeout: Number(query_timeout), + statement_timeout: Number(query_timeout), + application_name, + connectionTimeoutMillis: Number(connectionTimeoutMillis), + }); + await client.connect(); + + return client; +} diff --git a/packages/pieces/community/postgres/src/lib/triggers/new-row.ts b/packages/pieces/community/postgres/src/lib/triggers/new-row.ts new file mode 100644 index 0000000..ba440fe --- /dev/null +++ b/packages/pieces/community/postgres/src/lib/triggers/new-row.ts @@ -0,0 +1,200 @@ + +import { createTrigger, TriggerStrategy, PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import crypto from 'crypto'; +import { postgresAuth } from '../..'; +import { pgClient } from '../common'; +import format from 'pg-format'; +import dayjs from 'dayjs'; + +type OrderDirection = 'ASC' | 'DESC'; +const polling: Polling, { + table: { + table_schema: string, + table_name: string + }, order_by: string, order_direction: OrderDirection | undefined +}> = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ auth, propsValue, lastItemId }) => { + const client = await pgClient(auth) + try { + const lastItem = lastItemId as string; + const query = constructQuery({ table: propsValue.table, order_by: propsValue.order_by, lastItem: lastItem, order_direction: propsValue.order_direction }) + const result = await client.query(query); + const items = result.rows.map(function (row) { + const rowHash = crypto.createHash('md5').update(JSON.stringify(row)).digest('hex'); + const isTimestamp = dayjs(row[propsValue.order_by]).isValid(); + const orderValue = isTimestamp ? dayjs(row[propsValue.order_by]).toISOString() : row[propsValue.order_by]; + return { + id: orderValue + '|' + rowHash, + data: row, + } + }); + + return items; + } finally { + await client.end(); + } + } +}; + +function constructQuery({ table, order_by, lastItem, order_direction }: { table: { table_name: string, table_schema: string }, order_by: string, order_direction: OrderDirection | undefined, lastItem: string }): string { + const lastOrderKey = (lastItem ? lastItem.split('|')[0] : null); + if (lastOrderKey === null) { + switch (order_direction) { + case 'ASC': + return format(`SELECT * FROM %I.%I ORDER BY %I ASC LIMIT 5`, table.table_schema, table.table_name, order_by); + case 'DESC': + return format(`SELECT * FROM %I.%I ORDER BY %I DESC LIMIT 5`, table.table_schema, table.table_name, order_by); + default: + throw new Error(JSON.stringify({ + message: 'Invalid order direction', + order_direction: order_direction, + })); + } + } else { + switch (order_direction) { + case 'ASC': + return format(`SELECT * FROM %I.%I WHERE %I <= %L ORDER BY %I ASC`, table.table_schema, table.table_name, order_by, lastOrderKey, order_by); + case 'DESC': + return format(`SELECT * FROM %I.%I WHERE %I >= %L ORDER BY %I DESC`, table.table_schema, table.table_name, order_by, lastOrderKey, order_by); + default: + throw new Error(JSON.stringify({ + message: 'Invalid order direction', + order_direction: order_direction, + })); + } + } +} + +export const newRow = createTrigger({ + name: 'new-row', + auth: postgresAuth, + displayName: 'New Row', + description: 'triggered when a new row is added', + props: { + description: Property.MarkDown({ + value: `**NOTE:** The trigger fetches the latest rows using the provided order by column (newest first), and then will keep polling until the previous last row is reached.`, + }), + table: Property.Dropdown({ + displayName: 'Table name', + required: true, + refreshers: ['auth'], + refreshOnSearch: false, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + } + const authProps = auth as PiecePropValueSchema; + const client = await pgClient(authProps) + try { + const result = await client.query( + `SELECT table_schema, table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE'` + ); + const options = result.rows.map(row => ({ + label: `${row.table_schema}.${row.table_name}`, + value: { + table_schema: row.table_schema, + table_name: row.table_name, + }, + })); + return { + disabled: false, + options, + }; + } finally { + await client.end(); + } + } + }), + order_by: Property.Dropdown({ + displayName: 'Column to order by', + description: 'Use something like a created timestamp or an auto-incrementing ID.', + required: true, + refreshers: ['table'], + refreshOnSearch: false, + options: async ({ auth, table }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + } + if (!table) { + return { + disabled: true, + options: [], + placeholder: 'Please select a table', + }; + } + const authProps = auth as PiecePropValueSchema; + const client = await pgClient(authProps) + try { + const { table_name, table_schema } = table as { table_schema: string, table_name: string }; + const query = ` + SELECT column_name + FROM information_schema.columns + WHERE table_schema = $1 + AND table_name = $2 + `; + const params = [table_schema, table_name]; + const result = await client.query(query, params); + + const options = result.rows.map(f => { + return { + label: f.column_name, + value: f.column_name, + }; + }) + return { + disabled: false, + options, + }; + } finally { + await client.end(); + } + } + }), + order_direction: Property.StaticDropdown({ + displayName: 'Order Direction', + description: 'The direction to sort by such that the newest rows are fetched first.', + required: true, + options: { + options: [ + { + label: 'Ascending', + value: 'ASC', + }, + { + label: 'Descending', + value: 'DESC', + }, + ] + }, + defaultValue: 'DESC', + }), + }, + sampleData: {}, + type: TriggerStrategy.POLLING, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async onEnable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onEnable(polling, { store, propsValue, auth }); + }, + + async onDisable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onDisable(polling, { store, propsValue, auth }); + }, + + async run(context) { + return await pollingHelper.poll(polling, context); + }, +}); diff --git a/packages/pieces/community/postgres/tsconfig.json b/packages/pieces/community/postgres/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/postgres/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/postgres/tsconfig.lib.json b/packages/pieces/community/postgres/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/postgres/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/posthog/.babelrc b/packages/pieces/community/posthog/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/posthog/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/posthog/.eslintrc.json b/packages/pieces/community/posthog/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/posthog/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/posthog/README.md b/packages/pieces/community/posthog/README.md new file mode 100644 index 0000000..8080403 --- /dev/null +++ b/packages/pieces/community/posthog/README.md @@ -0,0 +1,7 @@ +# pieces-posthog + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-posthog` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/posthog/package.json b/packages/pieces/community/posthog/package.json new file mode 100644 index 0000000..4bc55bd --- /dev/null +++ b/packages/pieces/community/posthog/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-posthog", + "version": "0.3.4" +} \ No newline at end of file diff --git a/packages/pieces/community/posthog/project.json b/packages/pieces/community/posthog/project.json new file mode 100644 index 0000000..82279c7 --- /dev/null +++ b/packages/pieces/community/posthog/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-posthog", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/posthog/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/posthog", + "tsConfig": "packages/pieces/community/posthog/tsconfig.lib.json", + "packageJson": "packages/pieces/community/posthog/package.json", + "main": "packages/pieces/community/posthog/src/index.ts", + "assets": [ + "packages/pieces/community/posthog/*.md", + { + "input": "packages/pieces/community/posthog/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/posthog/src/index.ts b/packages/pieces/community/posthog/src/index.ts new file mode 100644 index 0000000..4e0ec45 --- /dev/null +++ b/packages/pieces/community/posthog/src/index.ts @@ -0,0 +1,38 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { posthogCreateEvent } from './lib/actions/create-event'; +import { posthogCreateProject } from './lib/actions/create-project'; + +const authenticationMarkdown = ` +[Click here](https://posthog.com/docs/api/overview#personal-api-keys-recommended) to learn how to obtain your Personal API key. +`; + +export const posthogAuth = PieceAuth.SecretText({ + displayName: 'Personal API Key', + description: authenticationMarkdown, + required: true, +}); + +export const posthog = createPiece({ + displayName: 'PostHog', + description: 'Open-source product analytics', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/posthog.png', + categories: [PieceCategory.BUSINESS_INTELLIGENCE], + auth: posthogAuth, + actions: [ + posthogCreateEvent, + posthogCreateProject, + createCustomApiCallAction({ + baseUrl: () => 'https://app.posthog.com', + auth: posthogAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + triggers: [], +}); diff --git a/packages/pieces/community/posthog/src/lib/actions/create-event.ts b/packages/pieces/community/posthog/src/lib/actions/create-event.ts new file mode 100644 index 0000000..2518b3e --- /dev/null +++ b/packages/pieces/community/posthog/src/lib/actions/create-event.ts @@ -0,0 +1,92 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { EventBody, EventCaptureResponse } from '../common/models'; +import { posthogAuth } from '../..'; + +export const posthogCreateEvent = createAction({ + auth: posthogAuth, + name: 'create_event', + displayName: 'Create Event', + description: 'Create an event inside a project', + props: { + event: Property.ShortText({ + displayName: 'Event name', + description: 'The event name', + required: true, + }), + event_type: Property.StaticDropdown({ + displayName: 'Event type', + required: true, + options: { + options: [ + { label: 'Alias', value: 'alias' }, + { label: 'Capture', value: 'capture' }, + { label: 'Identify', value: 'screen' }, + { label: 'Page', value: 'page' }, + { label: 'Screen', value: 'screen' }, + ], + }, + }), + distinct_id: Property.ShortText({ + displayName: 'Distinct Id', + description: "User's Distinct Id", + required: true, + }), + properties: Property.Object({ + displayName: 'Properties', + description: 'The event properties', + required: false, + }), + context: Property.Object({ + displayName: 'Context', + description: 'The event context,', + required: false, + }), + message_id: Property.ShortText({ + displayName: 'Message ID', + description: 'The message id,', + required: false, + }), + category: Property.ShortText({ + displayName: 'Category', + description: 'The event category.', + required: false, + }), + }, + async run(context) { + const body: EventBody = { + event: context.propsValue.event, + type: context.propsValue.event_type!, + api_key: context.auth, + messageId: context.propsValue.message_id, + context: context.propsValue.context || {}, + properties: context.propsValue.properties || {}, + distinct_id: context.propsValue.distinct_id, + category: context.propsValue.category, + }; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://app.posthog.com/capture/`, + body, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + }; + + const result = await httpClient.sendRequest(request); + console.debug('Event creation response', result); + + if (result.status === 200) { + return result.body; + } + + return result; + }, +}); diff --git a/packages/pieces/community/posthog/src/lib/actions/create-project.ts b/packages/pieces/community/posthog/src/lib/actions/create-project.ts new file mode 100644 index 0000000..1a2f918 --- /dev/null +++ b/packages/pieces/community/posthog/src/lib/actions/create-project.ts @@ -0,0 +1,62 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { ProjectCreateRequest, ProjectCreateResponse } from '../common/models'; +import { posthogAuth } from '../..'; + +export const posthogCreateProject = createAction({ + auth: posthogAuth, + name: 'create_project', + displayName: 'Create Project', + description: 'Create a posthog project', + props: { + name: Property.ShortText({ + displayName: 'Name', + description: 'Project Name', + required: false, + }), + slack_incoming_webhook: Property.ShortText({ + displayName: 'Slack Incoming Webhook', + description: 'Slack incoming webhook', + required: false, + }), + anonymize_ips: Property.Checkbox({ + displayName: 'Anonymize IPs', + description: 'Whether to anonymize incoming IP addresses.', + required: false, + }), + is_demo: Property.Checkbox({ + displayName: 'Is demo project', + description: 'If this is a demo project', + required: false, + }), + }, + async run(context) { + const body: ProjectCreateRequest = { + ...context.propsValue, + }; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://app.posthog.com/api/projects/`, + body, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + }; + + const result = await httpClient.sendRequest(request); + console.debug('Project creation response', result); + + if (result.status === 200) { + return result.body; + } + + return result; + }, +}); diff --git a/packages/pieces/community/posthog/src/lib/common/models.ts b/packages/pieces/community/posthog/src/lib/common/models.ts new file mode 100644 index 0000000..27c97cf --- /dev/null +++ b/packages/pieces/community/posthog/src/lib/common/models.ts @@ -0,0 +1,66 @@ +import { HttpMessageBody } from '@activepieces/pieces-common'; + +export interface EventBody extends HttpMessageBody { + api_key?: string; + timestamp?: string; + category?: string; + distinct_id?: string; + context?: Record; + properties?: Record; + type: string; + event: string; + name?: string; + messageId?: string; +} + +export interface EventCaptureResponse { + status?: number; + type?: string; + code?: string; + detail?: string; + attr?: string; +} + +export interface ProjectCreateRequest { + name?: string; + api_key?: string; + project_id?: string; + slack_incoming_webhook?: string; + anonymize_ips?: boolean; + is_demo?: boolean; +} + +export interface ProjectCreateResponse { + id: 0; + uuid: string; + organization: string; + api_token: string; + app_urls: string[]; + name: string; + slack_incoming_webhook: string; + created_at: string; + updated_at: string; + anonymize_ips: boolean; + completed_snippet_onboarding: boolean; + ingested_event: boolean; + test_account_filters: Record; + test_account_filters_default_checked: boolean; + path_cleaning_filters: Record; + is_demo: boolean; + timezone: string; + data_attributes: Record; + person_display_name_properties: string[]; + correlation_config: Record; + session_recording_opt_in: boolean; + capture_console_log_opt_in: boolean; + capture_performance_opt_in: boolean; + effective_membership_level: number; + access_control: boolean; + has_group_types: boolean; + primary_dashboard: number; + live_events_columns: string[]; + recording_domains: string[]; + person_on_events_querying_enabled: boolean; + groups_on_events_querying_enabled: boolean; + inject_web_apps: boolean; +} diff --git a/packages/pieces/community/posthog/tsconfig.json b/packages/pieces/community/posthog/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/posthog/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/posthog/tsconfig.lib.json b/packages/pieces/community/posthog/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/posthog/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/pushover/.eslintrc.json b/packages/pieces/community/pushover/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/pushover/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/pushover/README.md b/packages/pieces/community/pushover/README.md new file mode 100644 index 0000000..efcf492 --- /dev/null +++ b/packages/pieces/community/pushover/README.md @@ -0,0 +1,7 @@ +# pieces-pushover + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-pushover` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/pushover/package.json b/packages/pieces/community/pushover/package.json new file mode 100644 index 0000000..f6817dc --- /dev/null +++ b/packages/pieces/community/pushover/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-pushover", + "version": "0.1.4" +} \ No newline at end of file diff --git a/packages/pieces/community/pushover/project.json b/packages/pieces/community/pushover/project.json new file mode 100644 index 0000000..fc53a29 --- /dev/null +++ b/packages/pieces/community/pushover/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-pushover", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/pushover/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/pushover", + "tsConfig": "packages/pieces/community/pushover/tsconfig.lib.json", + "packageJson": "packages/pieces/community/pushover/package.json", + "main": "packages/pieces/community/pushover/src/index.ts", + "assets": [ + "packages/pieces/community/pushover/*.md", + { + "input": "packages/pieces/community/pushover/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/pushover/src/index.ts b/packages/pieces/community/pushover/src/index.ts new file mode 100644 index 0000000..0114fbb --- /dev/null +++ b/packages/pieces/community/pushover/src/index.ts @@ -0,0 +1,45 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { sendNotification } from './lib/actions/send-notification'; + +export const pushoverAuth = PieceAuth.CustomAuth({ + description: ` + To obtain the api token: + + 1. Log in to Pushover. + 2. Click on your Application or on Create an Application/API Token + 3. Copy the API Token/Key. + + To obtain the user key: + 1. Log in to Pushover + 2. Copy your Your User Key + + Note if you want to send the message to your group, you should specify a group key instead of the user key + `, + props: { + api_token: PieceAuth.SecretText({ + displayName: 'Api Token', + description: 'Pushover Api Token', + required: true, + }), + user_key: PieceAuth.SecretText({ + displayName: 'User Key', + description: 'Pushover User Key', + required: true, + }), + }, + required: true, +}); + +export const pushover = createPiece({ + displayName: 'Pushover', + description: 'Simple push notification service', + + logoUrl: 'https://cdn.activepieces.com/pieces/pushover.png', + categories: [PieceCategory.COMMUNICATION], + minimumSupportedRelease: '0.30.0', + authors: ["MyWay","Vitalini","kishanprmr","khaledmashaly","abuaboud"], + auth: pushoverAuth, + actions: [sendNotification], + triggers: [], +}); diff --git a/packages/pieces/community/pushover/src/lib/actions/send-notification.ts b/packages/pieces/community/pushover/src/lib/actions/send-notification.ts new file mode 100644 index 0000000..6522fbd --- /dev/null +++ b/packages/pieces/community/pushover/src/lib/actions/send-notification.ts @@ -0,0 +1,103 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { pushoverAuth } from '../..'; + +export const sendNotification = createAction({ + auth: pushoverAuth, + name: 'send_notification', + displayName: 'Send Notification', + description: 'Send a notification to Pushover', + props: { + title: Property.ShortText({ + displayName: 'Title', + description: 'The title of the notification', + required: false, + }), + message: Property.LongText({ + displayName: 'Message', + description: 'The message to send', + required: true, + }), + html: Property.Checkbox({ + displayName: 'Enable HTML', + description: 'To enable HTML parsing', + required: false, + }), + priority: Property.Number({ + displayName: 'Priority', + description: + 'The priority of the notification (-2 to 2). -2 is lowest priority. If set to 2, you should also specify Retry and Expire.', + required: false, + }), + retry: Property.Number({ + displayName: 'Retry', + description: + 'Works only if priority is set to 2. Specifies how often (in seconds) the Pushover servers will send the same notification to the user.', + required: false, + }), + expire: Property.Number({ + displayName: 'Expire', + description: + 'Works only if priority is set to 2. Specifies how many seconds your notification will continue to be retried for (every retry seconds).', + required: false, + }), + url: Property.ShortText({ + displayName: 'URL', + description: 'A supplementary URL to show with your message.', + required: false, + }), + url_title: Property.ShortText({ + displayName: 'URL Title', + description: + 'A title for the URL specified as the url input parameter, otherwise just the URL is shown.', + required: false, + }), + timestamp: Property.ShortText({ + displayName: 'Timestamp', + description: + 'a Unix timestamp of a time to display instead of when our API received it.', + required: false, + }), + device: Property.ShortText({ + displayName: 'Device', + description: + 'The name of one of your devices to send just to that device instead of all devices.', + required: false, + }), + }, + async run({ auth, propsValue }) { + const baseUrl = 'https://api.pushover.net/1/messages.json'; + const apiToken = auth.api_token; + const userKey = auth.user_key; + + const title = propsValue.title; + const message = propsValue.message; + const html = propsValue.html; + const priority = propsValue.priority; + const url = propsValue.url; + const url_title = propsValue.url_title; + const timestamp = propsValue.timestamp; + const device = propsValue.device; + const retry = propsValue.retry; + const expire = propsValue.expire; + + return await httpClient.sendRequest({ + method: HttpMethod.POST, + url: baseUrl, + body: { + token: apiToken, + user: userKey, + title, + message, + html: html ? 1 : 0, + url, + url_title, + timestamp, + device, + ...(priority && { priority: +priority }), + ...(retry && { retry: +retry }), + ...(expire && { expire: +expire }), + }, + }); + }, +}); diff --git a/packages/pieces/community/pushover/tsconfig.json b/packages/pieces/community/pushover/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/pushover/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/pushover/tsconfig.lib.json b/packages/pieces/community/pushover/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/pushover/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/pylon/.eslintrc.json b/packages/pieces/community/pylon/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/pylon/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/pylon/README.md b/packages/pieces/community/pylon/README.md new file mode 100644 index 0000000..9d59dbe --- /dev/null +++ b/packages/pieces/community/pylon/README.md @@ -0,0 +1,7 @@ +# pieces-pylon + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-pylon` to build the library. diff --git a/packages/pieces/community/pylon/package.json b/packages/pieces/community/pylon/package.json new file mode 100644 index 0000000..546b071 --- /dev/null +++ b/packages/pieces/community/pylon/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-pylon", + "version": "0.0.1" +} diff --git a/packages/pieces/community/pylon/project.json b/packages/pieces/community/pylon/project.json new file mode 100644 index 0000000..6ebc84e --- /dev/null +++ b/packages/pieces/community/pylon/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-pylon", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/pylon/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/pylon", + "tsConfig": "packages/pieces/community/pylon/tsconfig.lib.json", + "packageJson": "packages/pieces/community/pylon/package.json", + "main": "packages/pieces/community/pylon/src/index.ts", + "assets": [ + "packages/pieces/community/pylon/*.md", + { + "input": "packages/pieces/community/pylon/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/pylon/src/index.ts b/packages/pieces/community/pylon/src/index.ts new file mode 100644 index 0000000..78407ba --- /dev/null +++ b/packages/pieces/community/pylon/src/index.ts @@ -0,0 +1,27 @@ + +import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; + +const auth = PieceAuth.SecretText({ + displayName: "API Key", + required: true, +}) +export const pylon = createPiece({ + displayName: "Pylon", + auth, + minimumSupportedRelease: '0.36.1', + logoUrl: "https://cdn.activepieces.com/pieces/pylon.png", + authors: [], + actions: [ + createCustomApiCallAction({ + auth: auth, + baseUrl: () => 'https://api.usepylon.com', + authMapping: async (auth) => { + return { + Authorization: `Bearer ${auth}`, + }; + }, + }) + ], + triggers: [], +}); diff --git a/packages/pieces/community/pylon/tsconfig.json b/packages/pieces/community/pylon/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/pylon/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/pylon/tsconfig.lib.json b/packages/pieces/community/pylon/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/pylon/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/qdrant/.eslintrc.json b/packages/pieces/community/qdrant/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/qdrant/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/qdrant/README.md b/packages/pieces/community/qdrant/README.md new file mode 100644 index 0000000..b528aa4 --- /dev/null +++ b/packages/pieces/community/qdrant/README.md @@ -0,0 +1,7 @@ +# pieces-qdrant + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-qdrant` to build the library. diff --git a/packages/pieces/community/qdrant/package.json b/packages/pieces/community/qdrant/package.json new file mode 100644 index 0000000..9f26aad --- /dev/null +++ b/packages/pieces/community/qdrant/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-qdrant", + "version": "0.2.0" +} \ No newline at end of file diff --git a/packages/pieces/community/qdrant/project.json b/packages/pieces/community/qdrant/project.json new file mode 100644 index 0000000..315e8c7 --- /dev/null +++ b/packages/pieces/community/qdrant/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-qdrant", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/qdrant/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/qdrant", + "tsConfig": "packages/pieces/community/qdrant/tsconfig.lib.json", + "packageJson": "packages/pieces/community/qdrant/package.json", + "main": "packages/pieces/community/qdrant/src/index.ts", + "assets": [ + "packages/pieces/community/qdrant/*.md", + { + "input": "packages/pieces/community/qdrant/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-qdrant {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/qdrant/src/index.ts b/packages/pieces/community/qdrant/src/index.ts new file mode 100644 index 0000000..bd48fca --- /dev/null +++ b/packages/pieces/community/qdrant/src/index.ts @@ -0,0 +1,61 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { addPointsToCollection } from './lib/actions/add-points'; +import { deleteCollection } from './lib/actions/delete-collection'; +import { deletePoints } from './lib/actions/delete-points'; +import { collectionInfos } from './lib/actions/get-collection-infos'; +import { collectionList } from './lib/actions/get-collection-list'; +import { getPoints } from './lib/actions/get-points'; +import { searchPoints } from './lib/actions/search-points'; + +const qdrantConnectionDescription = ` +### Using Qdrant cloud +1. Connect to your [Qdrant cloud account](https://cloud.qdrant.io) +2. Create a new cluster if it's the first time you use Qdrant +3. Go to Data Access Control and create a new api key and copy it +4. Go to clusters, click on the arrow \`>\` and copy the Cluster URL. + +### Using self-hosted Qdrant +Try to create your own qdrant instance using the [documentation guides](https://qdrant.tech/documentation/guides/) +`; + +export const qdrantAuth = PieceAuth.CustomAuth({ + description: qdrantConnectionDescription, + props: { + serverAddress: Property.ShortText({ + displayName: 'Server Address', + required: true, + description: 'The url of the Qdrant instance.', + }), + key: PieceAuth.SecretText({ + displayName: 'API KEY', + required: true, + description: 'Enter the API Key of your Qdrant account', + }), + }, + required: true, +}); + +export const qdrant = createPiece({ + displayName: 'Qdrant', + description: 'Make any action on your qdrant vector database', + auth: qdrantAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/qdrant.png', + authors: ["ArmanGiau3","kishanprmr","abuaboud"], + categories: [PieceCategory.DEVELOPER_TOOLS], + actions: [ + addPointsToCollection, + collectionList, + collectionInfos, + deleteCollection, + deletePoints, + getPoints, + searchPoints, + ], + triggers: [], +}); diff --git a/packages/pieces/community/qdrant/src/lib/actions/add-points.ts b/packages/pieces/community/qdrant/src/lib/actions/add-points.ts new file mode 100644 index 0000000..1c88b3a --- /dev/null +++ b/packages/pieces/community/qdrant/src/lib/actions/add-points.ts @@ -0,0 +1,118 @@ +import { QdrantClient } from '@qdrant/js-client-rest'; +import { qdrantAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { randomUUID } from 'crypto'; +import { collectionName, decodeEmbeddings } from '../common'; + +export const addPointsToCollection = createAction({ + auth: qdrantAuth, + requireAuth: true, + name: 'add_points_to_collection', + displayName: 'Add points to collection', + description: + 'Insert a point (= embedding or vector + other infos) to a specific collection, if the collection does not exist it will be created', + props: { + collectionName, + embeddings: Property.File({ + displayName: 'Embeddings', + description: 'Embeddings (= vectors) for the points', + required: true, + }), + embeddingsIds: Property.Array({ + displayName: 'Embeddings Ids', + description: + 'The ids of the embeddings for the points. If not provided, the ids will be generated automatically', + required: false, + }), + distance: Property.StaticDropdown({ + displayName: 'Calculation Method of distance', + description: + "The calculation method helps to rank vectors when you want to find the closest points, the method to use depends on the model who's created the embeddings, see the documentation of your model", + defaultValue: 'Cosine', + options: { + options: [ + { label: 'Cosine', value: 'Cosine' }, + { label: 'Euclidean', value: 'Euclid' }, + { label: 'Dot', value: 'Dot' }, + ], + }, + required: true, + }), + payload: Property.Json({ + displayName: 'Additional Payload', + description: `Please follow [payload documentation](https://qdrant.tech/documentation/concepts/payload/) to add additional information to the points.`, + required: false, + }), + storage: Property.StaticDropdown({ + displayName: 'Storage', + description: 'Define where points will be stored', + options: { + options: [ + { label: 'on Disk', value: 'Disk' }, + { label: 'On Memory', value: 'Memory' }, + ], + }, + defaultValue: 'Disk', + required: false, + }), + }, + run: async ({ auth, propsValue }) => { + const client = new QdrantClient({ + apiKey: auth.key, + url: auth.serverAddress, + }); + const embeddings = decodeEmbeddings(propsValue.embeddings.data); + + const numberOfEmbeddings = embeddings.length; + const embeddingsLen = embeddings[0].length; + + for (const embedding of embeddings) { + if (embedding.length != embeddingsLen) + throw new Error( + 'Embeddings must have the same length (=number of dimensions)' + ); + } + + const embeddingsIds = (propsValue.embeddingsIds as string[]) ?? []; + + const autoEmbeddingsIds = embeddingsIds.length === 0; + + if (!autoEmbeddingsIds && embeddingsIds.length !== numberOfEmbeddings) + throw new Error( + 'The number of embeddings Ids and the number of embeddings must be the same' + ); + + const payload = propsValue.payload ?? {}; + const points = []; + + for (let i = 0; i < numberOfEmbeddings; i++) { + const localPayload = { ...payload }; + points.push({ + id: autoEmbeddingsIds ? randomUUID() : embeddingsIds[i], + payload: localPayload, + vector: Array.from(embeddings[i]), + }); + } + + + const collections = (await client.getCollections()).collections; + const collectionName = propsValue.collectionName as string; + if (!collections.find((collection) => collection.name === collectionName)) { + await client.createCollection(collectionName, { + vectors: { + size: embeddingsLen, + distance: propsValue.distance as 'Dot' | 'Cosine' | 'Euclid', + on_disk: propsValue.storage === 'Disk', + }, + on_disk_payload: propsValue.storage === 'Disk', + }); + } + + const response = await client.upsert(collectionName, { + points, + wait: true, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/qdrant/src/lib/actions/delete-collection.ts b/packages/pieces/community/qdrant/src/lib/actions/delete-collection.ts new file mode 100644 index 0000000..be5d8e7 --- /dev/null +++ b/packages/pieces/community/qdrant/src/lib/actions/delete-collection.ts @@ -0,0 +1,23 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { qdrantAuth } from '../..'; +import { QdrantClient } from '@qdrant/js-client-rest'; +import { collectionName } from '../common'; + +export const deleteCollection = createAction({ + auth: qdrantAuth, + name: 'delete_collection', + displayName: 'Delete Collection', + description: 'Delete a collection of your database', + props: { + collectionName, + }, + run: async ({ auth, propsValue }) => { + const client = new QdrantClient({ + apiKey: auth.key, + url: auth.serverAddress, + }); + const collectionName = propsValue.collectionName as string; + const response = await client.deleteCollection(collectionName); + return response; + }, +}); diff --git a/packages/pieces/community/qdrant/src/lib/actions/delete-points.ts b/packages/pieces/community/qdrant/src/lib/actions/delete-points.ts new file mode 100644 index 0000000..cac9190 --- /dev/null +++ b/packages/pieces/community/qdrant/src/lib/actions/delete-points.ts @@ -0,0 +1,37 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { collectionName, convertToFilter, seclectPointsProps } from '../common'; +import { qdrantAuth } from '../..'; +import { QdrantClient } from '@qdrant/js-client-rest'; + +export const deletePoints = createAction({ + auth: qdrantAuth, + name: 'delete_points', + displayName: 'Delete Points', + description: 'Delete points of a specific collection', + props: { + collectionName, + ...seclectPointsProps, + }, + run: async ({ auth, propsValue }) => { + const client = new QdrantClient({ + apiKey: auth.key, + url: auth.serverAddress, + }); + + const collectionName = propsValue.collectionName as string; + if (propsValue.getPointsBy === 'Ids') { + const ids = JSON.parse(propsValue.infosToGetPoint['ids']); + return await client.delete(collectionName, { + points: ids instanceof Array ? ids : [ids], + }); + } + const filter = convertToFilter( + propsValue.infosToGetPoint as { must: any; must_not: any } + ); + // console.log(JSON.stringify(filter)) + return await client.delete(collectionName, { + filter, + wait: true, + }); + }, +}); diff --git a/packages/pieces/community/qdrant/src/lib/actions/get-collection-infos.ts b/packages/pieces/community/qdrant/src/lib/actions/get-collection-infos.ts new file mode 100644 index 0000000..7ba5404 --- /dev/null +++ b/packages/pieces/community/qdrant/src/lib/actions/get-collection-infos.ts @@ -0,0 +1,23 @@ +import { QdrantClient } from '@qdrant/js-client-rest'; +import { qdrantAuth } from '../..'; +import { createAction } from '@activepieces/pieces-framework'; +import { collectionName } from '../common'; + +export const collectionInfos = createAction({ + auth: qdrantAuth, + name: 'collection_infos', + displayName: 'Get Collection Infos', + description: 'Get the all the infos of a specific collection', + props: { + collectionName, + }, + run: async ({ auth, propsValue }) => { + const client = new QdrantClient({ + apiKey: auth.key, + url: auth.serverAddress, + }); + const collectionName = propsValue.collectionName as string; + const collectionInfos = await client.getCollection(collectionName); + return collectionInfos; + }, +}); diff --git a/packages/pieces/community/qdrant/src/lib/actions/get-collection-list.ts b/packages/pieces/community/qdrant/src/lib/actions/get-collection-list.ts new file mode 100644 index 0000000..78dbfa4 --- /dev/null +++ b/packages/pieces/community/qdrant/src/lib/actions/get-collection-list.ts @@ -0,0 +1,19 @@ +import { QdrantClient } from '@qdrant/js-client-rest'; +import { qdrantAuth } from '../..'; +import { createAction } from '@activepieces/pieces-framework'; + +export const collectionList = createAction({ + auth: qdrantAuth, + name: 'collection_list', + displayName: 'Get Collection List', + description: 'Get the list of all the collections of your database', + props: {}, + run: async ({ auth }) => { + const client = new QdrantClient({ + apiKey: auth.key, + url: auth.serverAddress, + }); + const collections = await client.getCollections(); + return collections; + }, +}); diff --git a/packages/pieces/community/qdrant/src/lib/actions/get-points.ts b/packages/pieces/community/qdrant/src/lib/actions/get-points.ts new file mode 100644 index 0000000..ece361f --- /dev/null +++ b/packages/pieces/community/qdrant/src/lib/actions/get-points.ts @@ -0,0 +1,42 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { seclectPointsProps, convertToFilter, collectionName } from '../common'; +import { qdrantAuth } from '../..'; +import { QdrantClient } from '@qdrant/js-client-rest'; + +export const getPoints = createAction({ + auth: qdrantAuth, + name: 'get_points', + displayName: 'Get Points', + description: 'Get the points of a specific collection', + props: { + collectionName, + ...seclectPointsProps, + }, + run: async ({ auth, propsValue }) => { + const client = new QdrantClient({ + apiKey: auth.key, + url: auth.serverAddress, + }); + const collectionName = propsValue.collectionName as string; + + if (propsValue.getPointsBy === 'Ids') { + let ids = JSON.parse(propsValue.infosToGetPoint['ids']); + try { + ids = JSON.parse(ids); + } catch { + null; + } + return await client.retrieve(collectionName, { + ids: ids instanceof Array ? ids : [ids], + }); + } else { + const filtering = propsValue.infosToGetPoint as { + must: any; + must_not: any; + }; + return await client.scroll(collectionName, { + filter: convertToFilter(filtering), + }); + } + }, +}); diff --git a/packages/pieces/community/qdrant/src/lib/actions/search-points.ts b/packages/pieces/community/qdrant/src/lib/actions/search-points.ts new file mode 100644 index 0000000..676c418 --- /dev/null +++ b/packages/pieces/community/qdrant/src/lib/actions/search-points.ts @@ -0,0 +1,72 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + collectionName, + convertToFilter, + decodeEmbeddings, + filteringProps, +} from '../common'; +import { qdrantAuth } from '../..'; +import { QdrantClient } from '@qdrant/js-client-rest'; + +export const searchPoints = createAction({ + auth: qdrantAuth, + name: 'search_points', + displayName: 'Search Points', + description: 'Search for points closest to your given vector (= embedding)', + props: { + collectionName, + vector: Property.File({ + displayName: 'Embedding', + required: true, + description: 'The vector (= embedding) you want to search for.', + }), + ...filteringProps, + negativeVector: Property.File({ + displayName: 'Negative Vector', + required: false, + description: 'The vector (= embedding) you want to be the farthest.', + }), + limitResult: Property.Number({ + displayName: 'Limit Result', + required: false, + description: 'The max number of results you want to get.', + defaultValue: 20, + }), + }, + run: async ({ auth, propsValue }) => { + const client = new QdrantClient({ + apiKey: auth.key, + url: auth.serverAddress, + }); + const { must, must_not } = propsValue; + + const filter = convertToFilter({ + must, + must_not, + }); + + let vector = Array.from(decodeEmbeddings(propsValue.vector.data)[0]); + const negativeVector = propsValue.negativeVector + ? Array.from(decodeEmbeddings(propsValue.vector.data)[0]) + : undefined; + + if ( + !(vector instanceof Array) || + (negativeVector != undefined && !(negativeVector instanceof Array)) + ) + throw new Error('Vectors should be arrays of numbers'); + + const limit = propsValue.limitResult ?? 20; + + if (negativeVector) { + // math func on: https://qdrant.tech/documentation/concepts/search/?selector=aHRtbCA%2BIGJvZHkgPiBkaXY6bnRoLW9mLXR5cGUoMSkgPiBzZWN0aW9uID4gZGl2ID4gZGl2ID4gZGl2ID4gYXJ0aWNsZSA%2BIGgyOm50aC1vZi10eXBlKDUp + vector = vector.map((vec, i) => vec * 2 + negativeVector[i]); + } + return await client.search(propsValue.collectionName, { + vector, + filter, + limit, + with_payload: true, + }); + }, +}); diff --git a/packages/pieces/community/qdrant/src/lib/common/index.ts b/packages/pieces/community/qdrant/src/lib/common/index.ts new file mode 100644 index 0000000..e3d97e9 --- /dev/null +++ b/packages/pieces/community/qdrant/src/lib/common/index.ts @@ -0,0 +1,141 @@ +import { Property } from '@activepieces/pieces-framework'; + +export const decodeEmbeddings = (embeddingsString: Buffer) => { + let embeddings = embeddingsString.toString('utf-8') as + | string[] + | number[][] + | number[] + | string; + + if (embeddings[0] === '[') { + try { + embeddings = JSON.parse(embeddings as string); + } catch { + null; + } + } + + if ( + typeof embeddings[0] === 'number' || + (typeof embeddings[0] === 'string' && embeddings[0].length === 1) + ) { + embeddings = [embeddings] as string[] | number[][]; + } + + if (embeddings.length === 0) + throw new Error('Embeddings must contain one element minimum'); + + if (typeof embeddings[0] === 'string') { + return (embeddings as string[]).map( + (embedding) => + new Float32Array( + new Uint8Array(Buffer.from(embedding, 'base64')).buffer + ) + ); + } else { + return (embeddings as number[][]).map( + (embedding) => new Float32Array(embedding) + ); + } +}; + +export const filteringProps = { + must: Property.Object({ + displayName: 'Must Have', + description: + 'If the point have this property in his payload it will be selected', + required: false, + }), + must_not: Property.Object({ + displayName: 'Must Not Have', + description: + 'If the point have this property in his payload it will not be selected', + required: false, + }), +}; + +export const seclectPointsProps = { + getPointsBy: Property.StaticDropdown({ + displayName: 'Choose Points By', + description: 'The method to use to get the points', + options: { + options: [ + { label: 'Ids', value: 'Ids' }, + { label: 'Filtering', value: 'Filtering' }, + ], + }, + defaultValue: 'Ids', + required: true, + }), + infosToGetPoint: Property.DynamicProperties({ + displayName: 'By ids or filtering', + description: 'The infos to select points', + refreshers: ['getPointsBy'], + props: async (propsValue) => { + const { getPointsBy } = propsValue as unknown as { + getPointsBy: 'Ids' | 'Filtering'; + }; + if (getPointsBy === 'Ids') + return { + ids: Property.ShortText({ + displayName: 'Ids', + description: 'The ids of the points to choose', + required: true, + }), + }; + return filteringProps as any; + }, + required: true, + }), +}; + +export const convertToFilter = (infosToGetPoint: { + must: any; + must_not: any; +}) => { + type Tfilter = ( + | { has_id: (string | number)[] } + | { key: string; match: { value: any } | { any: any[] } } + )[]; + const filter = { must: [], must_not: [] } as { + must: Tfilter; + must_not: Tfilter; + }; + + for (const getKey in infosToGetPoint) { + for (const key in infosToGetPoint[getKey as keyof typeof filter]) { + let value = infosToGetPoint[getKey as keyof typeof filter][key]; + + if (['{', '['].includes(value[0])) { + try { + value = JSON.parse(value); + } catch { + null; + } + } + + if (['id', 'ids'].includes(key.toLocaleLowerCase())) { + filter[getKey as keyof typeof filter].push({ + has_id: value instanceof Array ? value : [value], + }); + continue; + } + + if (value instanceof Array) { + filter[getKey as keyof typeof filter].push({ + key, + match: { any: value }, + }); + } + + filter[getKey as keyof typeof filter].push({ key, match: { value } }); + } + } + return filter; +}; + +export const collectionName = Property.ShortText({ + displayName: 'Collection Name', + description: 'The name of the collection needed for this action', + required: true, +}); diff --git a/packages/pieces/community/qdrant/tsconfig.json b/packages/pieces/community/qdrant/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/qdrant/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/qdrant/tsconfig.lib.json b/packages/pieces/community/qdrant/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/qdrant/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/qrcode/.eslintrc.json b/packages/pieces/community/qrcode/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/qrcode/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/qrcode/README.md b/packages/pieces/community/qrcode/README.md new file mode 100644 index 0000000..e643648 --- /dev/null +++ b/packages/pieces/community/qrcode/README.md @@ -0,0 +1,7 @@ +# pieces-qrcode + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-qrcode` to build the library. diff --git a/packages/pieces/community/qrcode/package-lock.json b/packages/pieces/community/qrcode/package-lock.json new file mode 100644 index 0000000..30beb70 --- /dev/null +++ b/packages/pieces/community/qrcode/package-lock.json @@ -0,0 +1,39 @@ +{ + "name": "@activepieces/piece-qrcode", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-qrcode", + "version": "0.0.1", + "devDependencies": { + "@types/qrcode": "^1.5.5" + } + }, + "node_modules/@types/node": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/qrcode": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.5.tgz", + "integrity": "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + } + } +} diff --git a/packages/pieces/community/qrcode/package.json b/packages/pieces/community/qrcode/package.json new file mode 100644 index 0000000..fb91e25 --- /dev/null +++ b/packages/pieces/community/qrcode/package.json @@ -0,0 +1,7 @@ +{ + "name": "@activepieces/piece-qrcode", + "version": "0.0.1", + "devDependencies": { + "@types/qrcode": "^1.5.5" + } +} diff --git a/packages/pieces/community/qrcode/project.json b/packages/pieces/community/qrcode/project.json new file mode 100644 index 0000000..38794b3 --- /dev/null +++ b/packages/pieces/community/qrcode/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-qrcode", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/qrcode/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/qrcode", + "tsConfig": "packages/pieces/community/qrcode/tsconfig.lib.json", + "packageJson": "packages/pieces/community/qrcode/package.json", + "main": "packages/pieces/community/qrcode/src/index.ts", + "assets": [ + "packages/pieces/community/qrcode/*.md", + { + "input": "packages/pieces/community/qrcode/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/qrcode/src/index.ts b/packages/pieces/community/qrcode/src/index.ts new file mode 100644 index 0000000..3572a13 --- /dev/null +++ b/packages/pieces/community/qrcode/src/index.ts @@ -0,0 +1,18 @@ + + import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; + import { PieceCategory } from '@activepieces/shared'; + import { outputQrcodeAction } from './lib/actions/output-qrcode-action' + + export const qrcode = createPiece({ + displayName: 'QR Code', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.30.0', + logoUrl: "https://cdn.activepieces.com/pieces/qrcode.png", + categories: [PieceCategory.CORE], + authors: ['Meng-Yuan Huang'], + actions: [ + outputQrcodeAction, + ], + triggers: [], + }); + \ No newline at end of file diff --git a/packages/pieces/community/qrcode/src/lib/actions/output-qrcode-action.ts b/packages/pieces/community/qrcode/src/lib/actions/output-qrcode-action.ts new file mode 100644 index 0000000..eefc70d --- /dev/null +++ b/packages/pieces/community/qrcode/src/lib/actions/output-qrcode-action.ts @@ -0,0 +1,24 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { toBuffer } from 'qrcode'; + +export const outputQrcodeAction = createAction({ + name: 'text_to_qrcode', + displayName: 'Text to QR Code', + description: 'Convert text to QR code', + props: { + text: Property.LongText({ + displayName: 'Content', + required: true, + }), + }, + async run(context) { + const { text } = context.propsValue; + + const qrcodeBuffer = await toBuffer(text); + + return await context.files.write({ + fileName: 'qr-code.png', + data: qrcodeBuffer, + }); + }, +}); diff --git a/packages/pieces/community/qrcode/tsconfig.json b/packages/pieces/community/qrcode/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/qrcode/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/qrcode/tsconfig.lib.json b/packages/pieces/community/qrcode/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/qrcode/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/queue/.eslintrc.json b/packages/pieces/community/queue/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/queue/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/queue/README.md b/packages/pieces/community/queue/README.md new file mode 100644 index 0000000..b7e36b1 --- /dev/null +++ b/packages/pieces/community/queue/README.md @@ -0,0 +1,7 @@ +# pieces-queue + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-queue` to build the library. diff --git a/packages/pieces/community/queue/package.json b/packages/pieces/community/queue/package.json new file mode 100644 index 0000000..7d6d8a2 --- /dev/null +++ b/packages/pieces/community/queue/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-queue", + "version": "0.0.6" +} diff --git a/packages/pieces/community/queue/project.json b/packages/pieces/community/queue/project.json new file mode 100644 index 0000000..afe1b18 --- /dev/null +++ b/packages/pieces/community/queue/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-queue", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/queue/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/queue", + "tsConfig": "packages/pieces/community/queue/tsconfig.lib.json", + "packageJson": "packages/pieces/community/queue/package.json", + "main": "packages/pieces/community/queue/src/index.ts", + "assets": [ + "packages/pieces/community/queue/*.md", + { + "input": "packages/pieces/community/queue/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-queue {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/queue/src/index.ts b/packages/pieces/community/queue/src/index.ts new file mode 100644 index 0000000..a56f5a8 --- /dev/null +++ b/packages/pieces/community/queue/src/index.ts @@ -0,0 +1,15 @@ +import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { pushToQueue } from "./lib/actions/push-to-queue"; +import { pullFromQueue } from "./lib/actions/pull-from-queue"; +import { clearQueue } from "./lib/actions/clear-queue"; + +export const queue = createPiece({ + displayName: "Queue", + description: "A piece that allows you to push items into a queue, providing a way to throttle requests or process data in a First-In-First-Out (FIFO) manner.", + auth: PieceAuth.None(), + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/queue.svg', + authors: ['AbdullahBitar'], + actions: [pushToQueue, pullFromQueue, clearQueue], + triggers: [], +}); diff --git a/packages/pieces/community/queue/src/lib/actions/clear-queue.ts b/packages/pieces/community/queue/src/lib/actions/clear-queue.ts new file mode 100644 index 0000000..9072109 --- /dev/null +++ b/packages/pieces/community/queue/src/lib/actions/clear-queue.ts @@ -0,0 +1,40 @@ +import { + Property, + StoreScope, + createAction, +} from '@activepieces/pieces-framework'; +import { constructQueueName } from '../common'; + + +const notes = `**Note:** +- This deletes all items inside the queue permanently. +- The testing step work in isolation and doesn't affect the actual queue after publishing. +` +export const clearQueue = createAction({ + name: 'clear-queue', + description: 'Clears all items inside a queue', + displayName: 'Clear queue', + props: { + info: Property.MarkDown({ + value: notes, + }), + queueName: Property.ShortText({ + displayName: 'Queue Name', + required: true, + }) + }, + async run(context) { + const queueName = constructQueueName(context.propsValue.queueName, false) + await context.store.delete(queueName, StoreScope.PROJECT) + return { + success: true + } + }, + async test(context) { + const queueName = constructQueueName(context.propsValue.queueName, true) + await context.store.delete(queueName, StoreScope.PROJECT) + return { + success: true + } + } +}); diff --git a/packages/pieces/community/queue/src/lib/actions/pull-from-queue.ts b/packages/pieces/community/queue/src/lib/actions/pull-from-queue.ts new file mode 100644 index 0000000..eb4c6e6 --- /dev/null +++ b/packages/pieces/community/queue/src/lib/actions/pull-from-queue.ts @@ -0,0 +1,55 @@ +import { + Property, + Store, + StoreScope, + createAction, +} from '@activepieces/pieces-framework'; +import { constructQueueName, formatStorageError } from '../common'; + +const notes = `**Note:** +- You can pull items from other flows. The queue name should be unique across all flows. +- The testing step work in isolation and doesn't affect the actual queue after publishing. +` +export const pullFromQueue = createAction({ + name: 'pull-from-queue', + description: 'Pull items from queue', + displayName: 'Pull items from queue', + props: { + info: Property.MarkDown({ + value: notes, + }), + queueName: Property.ShortText({ + displayName: 'Queue Name', + required: true, + }), + numOfItems: Property.Number({ + displayName: 'Number of items', + required: true, + }) + }, + async run(context) { + const items = await poll({ store: context.store, queueName: context.propsValue.queueName, numOfItems: context.propsValue.numOfItems, testing: false }) + return items + }, + async test(context) { + const items = await poll({ store: context.store, queueName: context.propsValue.queueName, numOfItems: context.propsValue.numOfItems, testing: true }) + return items + } +}); + +async function poll({ store, queueName, numOfItems, testing }: { store: Store, queueName: string, numOfItems: number, testing: boolean }) { + const key = constructQueueName(queueName, testing) + const allItems = await store.get(key, StoreScope.PROJECT) || [] + const neededItems = allItems.splice(0, numOfItems) + try { + await store.put(key, allItems, StoreScope.PROJECT) + } catch (e: unknown) { + const name = (e as Error)?.name; + if (name === 'StorageLimitError') { + throw formatStorageError(e) + } else { + throw e + } + } + return neededItems +} diff --git a/packages/pieces/community/queue/src/lib/actions/push-to-queue.ts b/packages/pieces/community/queue/src/lib/actions/push-to-queue.ts new file mode 100644 index 0000000..431b2af --- /dev/null +++ b/packages/pieces/community/queue/src/lib/actions/push-to-queue.ts @@ -0,0 +1,52 @@ +import { + Property, + Store, + StoreScope, + createAction, +} from '@activepieces/pieces-framework'; +import { constructQueueName, formatStorageError } from '../common'; + +const notes = `**Note:** +- You can push items from other flows. The queue name should be unique across all flows. +- The testing step work in isolation and doesn't affect the actual queue after publishing. +` +export const pushToQueue = createAction({ + name: 'push-to-queue', + description: 'Push item to queue', + displayName: 'Push to Queue', + props: { + info: Property.MarkDown({ + value: notes, + }), + queueName: Property.ShortText({ + displayName: 'Queue Name', + required: true, + }), + items: Property.Array({ + displayName: 'Items', + required: true, + }), + }, + async run(context) { + return push({ store: context.store, queueName: context.propsValue.queueName, items: context.propsValue.items, testing: false }) + }, + async test(context) { + return push({ store: context.store, queueName: context.propsValue.queueName, items: context.propsValue.items, testing: true }) + } +}); + +async function push({ store, queueName, items, testing }: { store: Store, queueName: string, items: unknown[], testing: boolean }) { + const key = constructQueueName(queueName, testing) + const existingQueueItems = await store.get(key, StoreScope.PROJECT) || [] + const updatedQueueItems = [...existingQueueItems, ...items] + try { + return await store.put(key, updatedQueueItems, StoreScope.PROJECT) + } catch (e: unknown) { + const name = (e as Error)?.name; + if (name === 'StorageLimitError') { + throw formatStorageError(e) + } else { + throw e + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/queue/src/lib/common.ts b/packages/pieces/community/queue/src/lib/common.ts new file mode 100644 index 0000000..b745a48 --- /dev/null +++ b/packages/pieces/community/queue/src/lib/common.ts @@ -0,0 +1,15 @@ + +export function constructQueueName(queueName: string, testing: boolean) { + return `_queue_rmAPlFmX0s_${testing ? 'testing_' : ''}${queueName}` +} + +export function formatStorageError(e: unknown) { + const size = (e as { maxStorageSizeInBytes: number }).maxStorageSizeInBytes; + return new Error(JSON.stringify({ + message: `Queue write operation failed. The total size of the queue has exceeded the maximum limit of ${Math.floor(size / 1024)} kb.`, + details: { + maxSizeInKb: Math.floor(size / 1024), + suggestion: "Consider consume faster from the queue or reduce the size of the items being added to the queue." + } + })); +} \ No newline at end of file diff --git a/packages/pieces/community/queue/tsconfig.json b/packages/pieces/community/queue/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/queue/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/queue/tsconfig.lib.json b/packages/pieces/community/queue/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/queue/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/quickzu/.eslintrc.json b/packages/pieces/community/quickzu/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/quickzu/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/quickzu/README.md b/packages/pieces/community/quickzu/README.md new file mode 100644 index 0000000..a9c722d --- /dev/null +++ b/packages/pieces/community/quickzu/README.md @@ -0,0 +1,7 @@ +# pieces-quickzu + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-quickzu` to build the library. diff --git a/packages/pieces/community/quickzu/package.json b/packages/pieces/community/quickzu/package.json new file mode 100644 index 0000000..059318a --- /dev/null +++ b/packages/pieces/community/quickzu/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-quickzu", + "version": "0.0.4" +} \ No newline at end of file diff --git a/packages/pieces/community/quickzu/project.json b/packages/pieces/community/quickzu/project.json new file mode 100644 index 0000000..6a4627c --- /dev/null +++ b/packages/pieces/community/quickzu/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-quickzu", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/quickzu/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/quickzu", + "tsConfig": "packages/pieces/community/quickzu/tsconfig.lib.json", + "packageJson": "packages/pieces/community/quickzu/package.json", + "main": "packages/pieces/community/quickzu/src/index.ts", + "assets": [ + "packages/pieces/community/quickzu/*.md", + { + "input": "packages/pieces/community/quickzu/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-quickzu {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/quickzu/src/index.ts b/packages/pieces/community/quickzu/src/index.ts new file mode 100644 index 0000000..cae0cc7 --- /dev/null +++ b/packages/pieces/community/quickzu/src/index.ts @@ -0,0 +1,56 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { updateBusinessTimeAction } from './lib/actions/business-settings/update-business-time'; +import { createCategoryAction } from './lib/actions/categories/create-category'; +import { deleteCategoryAction } from './lib/actions/categories/delete-category'; +import { listCategoriesAction } from './lib/actions/categories/list-categories'; +import { updateCategoryAction } from './lib/actions/categories/update-category'; +import { createProductDiscountAction } from './lib/actions/discounts/create-product-discount'; +import { createPromoCodeAction } from './lib/actions/discounts/create-promo-code'; +import { getOrderDetailsAction } from './lib/actions/orders/get-order-details'; +import { listLiveOrdersAction } from './lib/actions/orders/list-live-orders'; +import { listOrdersAction } from './lib/actions/orders/list-orders'; +import { updateOrderStatusAction } from './lib/actions/orders/update-order-status'; +import { addProductAction } from './lib/actions/products/create-product'; +import { deleteProductAction } from './lib/actions/products/delete-product'; +import { listProductsAction } from './lib/actions/products/list-products'; +import { updateProductAction } from './lib/actions/products/update-product'; +import { orderCreatedTrigger } from './lib/triggers/order-created'; + +const authHelpDescription = ` +1. Login to your Quickzu Dashboard. +2. Go to **https://app.quickzu.com/dash/settings/api-webhooks**. +3. Copy **API Token** to the clipboard and paste it.`; + +export const quickzuAuth = PieceAuth.SecretText({ + displayName: 'API Token', + description: authHelpDescription, + required: true, +}); + +export const quickzu = createPiece({ + displayName: 'Quickzu', + description: 'Streamline ordering from whatsapp', + + auth: quickzuAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/quickzu.png', + authors: ["kishanprmr","abuaboud"], + actions: [ + addProductAction, + updateProductAction, + deleteProductAction, + listProductsAction, + createCategoryAction, + updateCategoryAction, + deleteCategoryAction, + listCategoriesAction, + getOrderDetailsAction, + listOrdersAction, + listLiveOrdersAction, + updateOrderStatusAction, + createProductDiscountAction, + createPromoCodeAction, + updateBusinessTimeAction, + ], + triggers: [orderCreatedTrigger], +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/business-settings/update-business-time.ts b/packages/pieces/community/quickzu/src/lib/actions/business-settings/update-business-time.ts new file mode 100644 index 0000000..d19bfe1 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/business-settings/update-business-time.ts @@ -0,0 +1,73 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient } from '../../common'; +import { BusinessTimingInput } from '../../common/types'; + +export const updateBusinessTimeAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_update_business_time', + displayName: 'Update Business Time', + description: 'Updates business hours.', + props: { + items: Property.Array({ + displayName: 'Business Hours', + required: true, + properties: { + weekday: Property.StaticDropdown({ + displayName: 'Day', + required: true, + options: { + disabled: false, + options: [ + { label: 'Sunday', value: '0' }, + { label: 'Monday', value: '1' }, + { label: 'Tuesday', value: '2' }, + { label: 'Wednesday', value: '3' }, + { label: 'Thursday', value: '4' }, + { label: 'Friday', value: '5' }, + { label: 'Saturday', value: '6' }, + ], + }, + }), + start: Property.ShortText({ + displayName: 'Start Time', + description: 'Please use 24:00 hour format', + required: true, + }), + end: Property.ShortText({ + displayName: 'Start Time', + description: 'Please use 24:00 hour format', + required: true, + }), + status: Property.Checkbox({ + displayName: 'Status', + required: true, + defaultValue: true, + }), + }, + }), + }, + async run(context) { + const items = context.propsValue.items as BusinessDayHour[]; + const input: BusinessTimingInput = { + timing: {}, + }; + for (const day of items) { + input.timing[day.weekday] = { + start: day.start, + end: day.end, + status: day.status, + }; + } + + const client = makeClient(context.auth); + return await client.updateBusinessTime(input); + }, +}); + +type BusinessDayHour = { + weekday: string; + start: string; + end: string; + status: boolean; +}; diff --git a/packages/pieces/community/quickzu/src/lib/actions/categories/create-category.ts b/packages/pieces/community/quickzu/src/lib/actions/categories/create-category.ts new file mode 100644 index 0000000..76b8565 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/categories/create-category.ts @@ -0,0 +1,28 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient } from '../../common'; + +export const createCategoryAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_create_category', + displayName: 'Create Category', + description: 'Creates a new category in store.', + props: { + name: Property.ShortText({ + displayName: 'Category Name', + required: true, + }), + status: Property.Checkbox({ + displayName: 'Category Status', + required: true, + defaultValue: true, + }), + }, + async run(context) { + const { name, status } = context.propsValue; + + const client = makeClient(context.auth); + + return await client.createCategory({ name, status }); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/categories/delete-category.ts b/packages/pieces/community/quickzu/src/lib/actions/categories/delete-category.ts new file mode 100644 index 0000000..19b793d --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/categories/delete-category.ts @@ -0,0 +1,19 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient, quickzuCommon } from '../../common'; + +export const deleteCategoryAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_delete_category', + displayName: 'Delete Category', + description: 'Deletes an existing category from store.', + props: { + categoryId: quickzuCommon.categoryId(true), + }, + async run(context) { + const { categoryId } = context.propsValue; + + const client = makeClient(context.auth); + return await client.deleteCategory(categoryId!); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/categories/list-categories.ts b/packages/pieces/community/quickzu/src/lib/actions/categories/list-categories.ts new file mode 100644 index 0000000..fc2f2a3 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/categories/list-categories.ts @@ -0,0 +1,23 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../../'; +import { makeClient } from '../../common'; + +export const listCategoriesAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_list_categories', + displayName: 'List Categories', + description: 'Retrieves all categories from store.', + props: { + term: Property.ShortText({ + displayName: 'Search Term', + description: 'Category name that need to be search.', + required: false, + }), + }, + async run(context) { + const { term } = context.propsValue; + + const client = makeClient(context.auth); + return client.listCategories(term); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/categories/update-category.ts b/packages/pieces/community/quickzu/src/lib/actions/categories/update-category.ts new file mode 100644 index 0000000..363288a --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/categories/update-category.ts @@ -0,0 +1,29 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient, quickzuCommon } from '../../common'; + +export const updateCategoryAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_update_category', + displayName: 'Update Category', + description: 'Updates an existing category in store.', + props: { + categoryId: quickzuCommon.categoryId(true), + name: Property.ShortText({ + displayName: 'Category Name', + required: false, + }), + status: Property.Checkbox({ + displayName: 'Category Status', + required: true, + defaultValue: false, + }), + }, + async run(context) { + const { categoryId, name, status } = context.propsValue; + + const client = makeClient(context.auth); + + return await client.updateCategory(categoryId!, { name, status }); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/discounts/create-product-discount.ts b/packages/pieces/community/quickzu/src/lib/actions/discounts/create-product-discount.ts new file mode 100644 index 0000000..82266a6 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/discounts/create-product-discount.ts @@ -0,0 +1,168 @@ +import { + DynamicPropsValue, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient } from '../../common'; +import { + DiscountFilterType, + DiscountMethod, + DiscountValueType, +} from '../../common/constants'; +import { ProductDiscountInput } from '../../common/types'; + +export const createProductDiscountAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_create_product_discount', + displayName: 'Create Product Discount', + description: 'Creates a new discount for category or product level.', + props: { + title: Property.ShortText({ + displayName: 'Promotion / Discount Title', + required: true, + }), + start_date: Property.DateTime({ + displayName: 'Valid From', + description: 'Please use YYYY-MM-DD format.', + required: true, + }), + end_date: Property.DateTime({ + displayName: 'Valid To', + description: 'Please use YYYY-MM-DD format.', + required: true, + }), + filter_type: Property.StaticDropdown({ + displayName: 'Filter', + description: 'Choose what gets discount (products/categories).', + required: true, + options: { + disabled: false, + options: Object.values(DiscountFilterType).map((value) => { + return { + label: value.toLowerCase(), + value: value, + }; + }), + }, + }), + selectedFilterValues: Property.DynamicProperties({ + displayName: 'Select Option', + refreshers: ['filter_type'], + required: true, + props: async ({ auth, filter_type }) => { + if (!auth) return {}; + if (!filter_type) return {}; + + const fields: DynamicPropsValue = {}; + const discountFilterType = filter_type as unknown as DiscountFilterType; + + const client = makeClient(auth as unknown as string); + + switch (discountFilterType) { + case DiscountFilterType.CATEGORIES: { + const res = await client.listCategories(); + fields['values'] = Property.StaticMultiSelectDropdown({ + displayName: 'Categories', + required: true, + description: 'Categories eligible for a discount.', + options: { + disabled: false, + options: res.data.map((category) => { + return { + label: category.name, + value: category._id, + }; + }), + }, + }); + break; + } + case DiscountFilterType.PRODUCTS: { + const res = await client.listProducts(); + fields['values'] = Property.StaticMultiSelectDropdown({ + displayName: 'Products', + required: true, + description: 'Products eligible for a discount.', + options: { + disabled: false, + options: res.data.map((product) => { + return { + label: product.name, + value: product._id, + }; + }), + }, + }); + break; + } + case DiscountFilterType.ALL_PRODUCTS: { + fields['values'] = []; + break; + } + } + return fields; + }, + }), + type: Property.StaticDropdown({ + displayName: 'Discount', + required: true, + options: { + disabled: false, + options: Object.values(DiscountValueType).map((value) => { + return { + label: value.toLowerCase(), + value: value, + }; + }), + }, + }), + value: Property.ShortText({ + displayName: 'Discount Value', + required: true, + }), + is_enabled: Property.Checkbox({ + displayName: 'Enabled ?', + required: true, + defaultValue: true, + }), + is_visible: Property.Checkbox({ + displayName: 'Visibility', + required: true, + defaultValue: true, + }), + }, + async run(context) { + const { + title, + start_date, + end_date, + filter_type, + selectedFilterValues, + type, + value, + is_enabled, + is_visible, + } = context.propsValue; + + const input: ProductDiscountInput = { + discount_method: DiscountMethod.ITEM_LEVEL, + title, + start_date, + end_date, + filter_type, + type, + value, + is_enabled, + is_visible, + }; + if (filter_type === DiscountFilterType.CATEGORIES) { + input.selectedCategories = selectedFilterValues['values']; + } else if (filter_type === DiscountFilterType.PRODUCTS) { + input.selectedProducts = selectedFilterValues['values']; + } + + const client = makeClient(context.auth); + return await client.createProductDiscount(input); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/discounts/create-promo-code.ts b/packages/pieces/community/quickzu/src/lib/actions/discounts/create-promo-code.ts new file mode 100644 index 0000000..0b7767e --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/discounts/create-promo-code.ts @@ -0,0 +1,188 @@ +import { + DynamicPropsValue, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient } from '../../common'; +import { + DiscountFilterType, + DiscountMethod, + DiscountValueType, +} from '../../common/constants'; +import { ProductDiscountInput } from '../../common/types'; + +export const createPromoCodeAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_create_promo_code', + displayName: 'Create Promo/Coupon Code', + description: 'Creates a new promo code for category or product level.', + props: { + title: Property.ShortText({ + displayName: 'Promotion / Discount Title', + required: true, + }), + start_date: Property.DateTime({ + displayName: 'Valid From', + description: 'Please use YYYY-MM-DD format.', + required: true, + }), + end_date: Property.DateTime({ + displayName: 'Valid To', + description: 'Please use YYYY-MM-DD format.', + required: true, + }), + filter_type: Property.StaticDropdown({ + displayName: 'Filter', + description: 'Choose what gets discount (products/categories).', + required: true, + options: { + disabled: false, + options: Object.values(DiscountFilterType).map((value) => { + return { + label: value.toLowerCase(), + value: value, + }; + }), + }, + }), + selectedFilterValues: Property.DynamicProperties({ + displayName: 'Select Option', + refreshers: ['filter_type'], + required: true, + props: async ({ auth, filter_type }) => { + if (!auth) return {}; + if (!filter_type) return {}; + + const fields: DynamicPropsValue = {}; + const discountFilterType = filter_type as unknown as DiscountFilterType; + + const client = makeClient(auth as unknown as string); + + switch (discountFilterType) { + case DiscountFilterType.CATEGORIES: { + const res = await client.listCategories(); + fields['values'] = Property.StaticMultiSelectDropdown({ + displayName: 'Categories', + required: true, + description: 'Categories eligible for a discount.', + options: { + disabled: false, + options: res.data.map((category) => { + return { + label: category.name, + value: category._id, + }; + }), + }, + }); + break; + } + case DiscountFilterType.PRODUCTS: { + const res = await client.listProducts(); + fields['values'] = Property.StaticMultiSelectDropdown({ + displayName: 'Products', + required: true, + description: 'Products eligible for a discount.', + options: { + disabled: false, + options: res.data.map((product) => { + return { + label: product.name, + value: product._id, + }; + }), + }, + }); + break; + } + case DiscountFilterType.ALL_PRODUCTS: { + fields['values'] = []; + break; + } + } + return fields; + }, + }), + promo_code: Property.ShortText({ + displayName: 'Promo Code', + required: true, + }), + minimum_cart_value: Property.Number({ + displayName: 'Minimum Order Amount', + required: true, + }), + max_users_limit: Property.Number({ + displayName: 'Maximum Promo Code Limit', + required: true, + }), + type: Property.StaticDropdown({ + displayName: 'Discount', + required: true, + options: { + disabled: false, + options: Object.values(DiscountValueType) + .filter((value) => value !== DiscountValueType.FIXED_PRICE) + .map((value) => { + return { + label: value.toLowerCase(), + value: value, + }; + }), + }, + }), + value: Property.ShortText({ + displayName: 'Discount Value', + required: true, + }), + is_enabled: Property.Checkbox({ + displayName: 'Enabled ?', + required: true, + defaultValue: true, + }), + is_visible: Property.Checkbox({ + displayName: 'Visibility', + required: true, + defaultValue: true, + }), + }, + async run(context) { + const { + title, + start_date, + end_date, + filter_type, + selectedFilterValues, + type, + promo_code, + max_users_limit, + minimum_cart_value, + value, + is_enabled, + is_visible, + } = context.propsValue; + + const input: ProductDiscountInput = { + discount_method: DiscountMethod.SUB_TOTAL, + title, + start_date, + end_date, + filter_type, + max_users_limit, + minimum_cart_value, + promo_code, + type, + value, + is_enabled, + is_visible, + }; + if (filter_type === DiscountFilterType.CATEGORIES) { + input.selectedCategories = selectedFilterValues['values']; + } else if (filter_type === DiscountFilterType.PRODUCTS) { + input.selectedProducts = selectedFilterValues['values']; + } + + const client = makeClient(context.auth); + return await client.createProductDiscount(input); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/orders/get-order-details.ts b/packages/pieces/community/quickzu/src/lib/actions/orders/get-order-details.ts new file mode 100644 index 0000000..b160a0b --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/orders/get-order-details.ts @@ -0,0 +1,19 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient, quickzuCommon } from '../../common'; + +export const getOrderDetailsAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_get_order_details', + displayName: 'Get Order Details', + description: 'Retrieves order details from store.', + props: { + orderId: quickzuCommon.orderId(true), + }, + async run(context) { + const { orderId } = context.propsValue; + + const client = makeClient(context.auth); + return await client.getOrderDetails(orderId!); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/orders/list-live-orders.ts b/packages/pieces/community/quickzu/src/lib/actions/orders/list-live-orders.ts new file mode 100644 index 0000000..330bf21 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/orders/list-live-orders.ts @@ -0,0 +1,24 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient } from '../../common'; + +export const listLiveOrdersAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_list_live_orders', + displayName: 'List Live Orders', + description: 'Retrieves live orders of store.', + props: { + limit: Property.Number({ + displayName: + 'Number of live orders that need to be fetched per order status', + required: true, + defaultValue: 15, + }), + }, + async run(context) { + const { limit } = context.propsValue; + + const client = makeClient(context.auth); + return await client.listLiveOrders(limit); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/orders/list-orders.ts b/packages/pieces/community/quickzu/src/lib/actions/orders/list-orders.ts new file mode 100644 index 0000000..9a891cf --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/orders/list-orders.ts @@ -0,0 +1,28 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient } from '../../common'; + +export const listOrdersAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_list_orders', + displayName: 'List Orders', + description: 'Retrieves orders of store.', + props: { + page: Property.Number({ + displayName: 'Current page number', + required: true, + defaultValue: 1, + }), + limit: Property.Number({ + displayName: 'Number of orders per page', + required: true, + defaultValue: 20, + }), + }, + async run(context) { + const { page, limit } = context.propsValue; + + const client = makeClient(context.auth); + return await client.listOrders(page, limit); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/orders/update-order-status.ts b/packages/pieces/community/quickzu/src/lib/actions/orders/update-order-status.ts new file mode 100644 index 0000000..f2cd7b5 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/orders/update-order-status.ts @@ -0,0 +1,33 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient, quickzuCommon } from '../../common'; +import { OrderStatus } from '../../common/constants'; + +export const updateOrderStatusAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_update_order_status', + displayName: 'Update Order Status', + description: 'Updates status of order in store.', + props: { + orderId: quickzuCommon.orderId(true), + status: Property.StaticDropdown({ + displayName: 'Order Status', + required: true, + options: { + disabled: false, + options: Object.values(OrderStatus).map((value) => { + return { + label: value, + value: value, + }; + }), + }, + }), + }, + async run(context) { + const { orderId, status } = context.propsValue; + + const client = makeClient(context.auth); + return await client.updateOrderStatus(orderId!, status); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/products/create-product.ts b/packages/pieces/community/quickzu/src/lib/actions/products/create-product.ts new file mode 100644 index 0000000..7fdddd2 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/products/create-product.ts @@ -0,0 +1,104 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient, quickzuCommon } from '../../common'; +import { ProductUnit } from '../../common/constants'; + +export const addProductAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_add_product', + displayName: 'Add Product', + description: 'Adds new product to store.', + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + desc: Property.LongText({ + displayName: 'Description', + required: false, + }), + categoryId: quickzuCommon.categoryId(true), + mrp: Property.Number({ + displayName: 'MRP Price', + required: true, + }), + price: Property.Number({ + displayName: 'Selling Price', + required: false, + description: 'Selling price should be equal or less than MRP.', + }), + unit: Property.StaticDropdown({ + displayName: 'Unit', + required: true, + defaultValue: 'kilogram', + options: { + disabled: false, + options: Object.values(ProductUnit).map((val) => { + return { + label: val, + value: val, + }; + }), + }, + }), + value_per_unit: Property.Number({ + displayName: 'Unit Value', + required: true, + defaultValue: 1, + }), + availability: Property.Checkbox({ + displayName: 'Availability', + required: true, + defaultValue: true, + }), + exclude_tax: Property.Checkbox({ + displayName: 'Exclude Tax', + required: true, + defaultValue: false, + }), + enable_variants: Property.Checkbox({ + displayName: 'Enable Variants', + required: true, + defaultValue: false, + }), + status: Property.Checkbox({ + displayName: 'Status', + required: true, + defaultValue: true, + }), + }, + async run(context) { + const { + name, + desc, + categoryId, + mrp, + price, + unit, + value_per_unit, + availability, + exclude_tax, + enable_variants, + status, + } = context.propsValue; + + const client = makeClient(context.auth); + + return await client.createProduct({ + name, + desc, + category: categoryId!, + mrp, + price, + unit, + value_per_unit, + availability, + exclude_tax, + enable_variants, + status, + meta: { + nonveg: false, + }, + }); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/products/delete-product.ts b/packages/pieces/community/quickzu/src/lib/actions/products/delete-product.ts new file mode 100644 index 0000000..d9a0513 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/products/delete-product.ts @@ -0,0 +1,19 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient, quickzuCommon } from '../../common'; + +export const deleteProductAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_delete_product', + displayName: 'Delete Product', + description: 'Deletes an existing product from store.', + props: { + productId: quickzuCommon.productId(true), + }, + async run(context) { + const { productId } = context.propsValue; + + const client = makeClient(context.auth); + return await client.deleteProduct(productId!); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/products/list-products.ts b/packages/pieces/community/quickzu/src/lib/actions/products/list-products.ts new file mode 100644 index 0000000..05470df --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/products/list-products.ts @@ -0,0 +1,23 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient, quickzuCommon } from '../../common'; + +export const listProductsAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_list_products', + displayName: 'List Products', + description: 'Retrieves all or single product details from store.', + props: { + term: Property.ShortText({ + displayName: 'Product name that need to be search.', + required: false, + }), + categoryId: quickzuCommon.categoryId(false), + }, + async run(context) { + const { term, categoryId } = context.propsValue; + + const client = makeClient(context.auth); + return await client.listProducts(term, categoryId); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/products/update-product.ts b/packages/pieces/community/quickzu/src/lib/actions/products/update-product.ts new file mode 100644 index 0000000..b952928 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/products/update-product.ts @@ -0,0 +1,100 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient, quickzuCommon } from '../../common'; +import { ProductUnit } from '../../common/constants'; + +export const updateProductAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_update_product', + displayName: 'Update Product', + description: 'Updates an existing product in store.', + props: { + productId: quickzuCommon.productId(true), + name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + desc: Property.LongText({ + displayName: 'Description', + required: false, + }), + categoryId: quickzuCommon.categoryId(false), + mrp: Property.Number({ + displayName: 'MRP Price', + required: false, + }), + price: Property.Number({ + displayName: 'Selling Price', + required: false, + description: 'Selling price should be equal or less than MRP.', + }), + unit: Property.StaticDropdown({ + displayName: 'Unit', + required: false, + options: { + disabled: false, + options: Object.values(ProductUnit).map((val) => { + return { + label: val, + value: val, + }; + }), + }, + }), + value_per_unit: Property.Number({ + displayName: 'Unit Value', + required: false, + }), + availability: Property.Checkbox({ + displayName: 'Availability', + required: false, + }), + exclude_tax: Property.Checkbox({ + displayName: 'Exclude Tax', + required: false, + }), + enable_variants: Property.Checkbox({ + displayName: 'Enable Variants', + required: false, + }), + status: Property.Checkbox({ + displayName: 'Status', + required: false, + }), + }, + async run(context) { + const { + name, + productId, + desc, + categoryId, + mrp, + price, + unit, + value_per_unit, + availability, + exclude_tax, + enable_variants, + status, + } = context.propsValue; + + const client = makeClient(context.auth); + + return await client.updateProduct(productId!, { + name, + desc, + category: categoryId!, + mrp, + price, + unit, + value_per_unit, + availability, + exclude_tax, + enable_variants, + status, + meta: { + nonveg: false, + }, + }); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/common/client.ts b/packages/pieces/community/quickzu/src/lib/common/client.ts new file mode 100644 index 0000000..57842b5 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/common/client.ts @@ -0,0 +1,182 @@ +import { + HttpMessageBody, + HttpMethod, + QueryParams, + httpClient, +} from '@activepieces/pieces-common'; +import { OrderStatus } from './constants'; +import { + BusinessTimingInput, + Category, + CategoryInput, + ListAPIResponse, + Product, + ProductDiscountInput, + ProductInput, +} from './types'; + +function emptyValueFilter( + accessor: (key: string) => any +): (key: string) => boolean { + return (key: string) => { + const val = accessor(key); + return ( + val !== null && + val !== undefined && + (typeof val != 'string' || val.length > 0) + ); + }; +} + +export function prepareQuery(request?: Record): QueryParams { + const params: QueryParams = {}; + if (!request) return params; + Object.keys(request) + .filter(emptyValueFilter((k) => request[k])) + .forEach((k: string) => { + params[k] = (request as Record)[k].toString(); + }); + return params; +} + +export class QuickzuAPIClient { + constructor(private apiToken: string) {} + async makeRequest( + method: HttpMethod, + resourceUri: string, + query?: QueryParams, + body: any | undefined = undefined + ): Promise { + // const baseUrl = this.apiTableUrl.replace(/\/$/, ''); + const res = await httpClient.sendRequest({ + method: method, + url: `https://app.quickzu.com/api` + resourceUri, + headers: { + Authorization: this.apiToken, + }, + queryParams: query, + body: body, + }); + return res.body; + } + async createCategory(categoryInput: CategoryInput) { + return await this.makeRequest( + HttpMethod.POST, + '/seller/categories/', + undefined, + categoryInput + ); + } + async updateCategory( + categoryId: string, + categoryInput: Partial + ) { + return await this.makeRequest( + HttpMethod.PUT, + `/seller/categories/${categoryId}`, + undefined, + categoryInput + ); + } + async deleteCategory(categoryId: string) { + return await this.makeRequest( + HttpMethod.DELETE, + `/seller/categories/${categoryId}` + ); + } + async listCategories(term?: string): Promise> { + return await this.makeRequest>( + HttpMethod.GET, + '/seller/categories', + prepareQuery({ + term: term, + }) + ); + } + async createProduct(productInput: ProductInput) { + return await this.makeRequest( + HttpMethod.POST, + '/seller/products', + undefined, + productInput + ); + } + async updateProduct(productId: string, productInput: Partial) { + return await this.makeRequest( + HttpMethod.PUT, + `/seller/products/${productId}`, + undefined, + productInput + ); + } + async deleteProduct(productId: string) { + return await this.makeRequest( + HttpMethod.DELETE, + `/seller/products/${productId}` + ); + } + async listProducts( + term?: string, + categoryId?: string + ): Promise> { + return await this.makeRequest>( + HttpMethod.GET, + '/seller/products', + prepareQuery({ + term: term, + category: categoryId, + }) + ); + } + async getOrderDetails(orderId: string) { + return await this.makeRequest(HttpMethod.GET, `/seller/orders/${orderId}`); + } + async updateOrderStatus(orderId: string, status: OrderStatus) { + return await this.makeRequest( + HttpMethod.PUT, + `/seller/orders/${orderId}`, + undefined, + { + status: status, + } + ); + } + async listOrders(page: number, limit: number) { + return await this.makeRequest( + HttpMethod.GET, + '/seller/orders/', + prepareQuery({ page: page, limit: limit }) + ); + } + async listLiveOrders(limit: number) { + return await this.makeRequest( + HttpMethod.GET, + '/seller/orders/live', + prepareQuery({ limit: limit }) + ); + } + async createProductDiscount(discountInput: ProductDiscountInput) { + return await this.makeRequest( + HttpMethod.POST, + '/seller/discounts', + undefined, + discountInput + ); + } + async createPromoCode(promocodeInput: ProductDiscountInput) { + return await this.makeRequest( + HttpMethod.POST, + '/seller/discounts', + undefined, + promocodeInput + ); + } + async updateBusinessTime(timingInput: BusinessTimingInput) { + return await this.makeRequest( + HttpMethod.PUT, + '/seller/settings', + undefined, + timingInput + ); + } +} diff --git a/packages/pieces/community/quickzu/src/lib/common/constants.ts b/packages/pieces/community/quickzu/src/lib/common/constants.ts new file mode 100644 index 0000000..73875fb --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/common/constants.ts @@ -0,0 +1,48 @@ +export enum OrderStatus { + PENDING = 'pending', + PAID = 'paid', + ACCEPTED = 'accepted', + CANCELLED = 'cancelled', + DONE = 'done', +} +export enum DiscountFilterType { + PRODUCTS = 'PRODUCTS', + CATEGORIES = 'CATEGORIES', + ALL_PRODUCTS = 'ALL_PRODUCTS', +} + +export enum DiscountValueType { + PERCENTAGE = 'PERCENTAGE', + FLAT = 'FLAT', + FIXED_PRICE = 'FIXED_PRICE', +} + +export enum DiscountMethod { + ITEM_LEVEL = 'ITEM_LEVEL', + SUB_TOTAL = 'SUB_TOTAL', +} + +export enum ProductUnit { + PIECE = 'piece', + KILOGRAM = 'kilogram', + GRAM = 'Gram', + POUND = 'pound', + LITRE = 'litre', + MILI_LITRE = 'mili litre', + DOZEN = 'dozen', + FEET = 'feet', + METER = 'meter', + SQUARE_FEET = 'square feet', + SQUARE_METER = 'square meter', + SET = 'set', + HOUR = 'hour', + DAY = 'day', + SERVICE = 'service', + COMBO = 'combo', + BOX = 'box', + PACK = 'pack', + BOTTLE = 'bottle', + WHOLE = 'whole', + SLICE = 'slice', + BULK = 'bulk', +} diff --git a/packages/pieces/community/quickzu/src/lib/common/index.ts b/packages/pieces/community/quickzu/src/lib/common/index.ts new file mode 100644 index 0000000..a06a09c --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/common/index.ts @@ -0,0 +1,94 @@ +import { PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../'; +import { QuickzuAPIClient } from './client'; + +export function makeClient(auth: PiecePropValueSchema) { + const client = new QuickzuAPIClient(auth); + return client; +} + +export const quickzuCommon = { + categoryId: (required = false) => + Property.Dropdown({ + displayName: 'Category', + refreshers: [], + required, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + const client = makeClient(auth as string); + const res = await client.listCategories(); + + return { + disabled: false, + options: res.data.map((category) => { + return { + label: category.name, + value: category._id, + }; + }), + }; + }, + }), + productId: (required = false) => + Property.Dropdown({ + displayName: 'Product', + refreshers: [], + required, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + const client = makeClient(auth as string); + const res = await client.listProducts(); + + return { + disabled: false, + options: res.data.map((product) => { + return { + label: product.name, + value: product._id, + }; + }), + }; + }, + }), + orderId: (required = false) => + Property.Dropdown({ + displayName: 'Order', + refreshers: [], + required, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + const client = makeClient(auth as string); + const res = await client.listOrders(1, 20); + + return { + disabled: false, + options: res['data'].map( + (order: { _id: string; order_id: number }) => { + return { + label: order.order_id.toString(), + value: order._id, + }; + } + ), + }; + }, + }), +}; diff --git a/packages/pieces/community/quickzu/src/lib/common/types.ts b/packages/pieces/community/quickzu/src/lib/common/types.ts new file mode 100644 index 0000000..9f50719 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/common/types.ts @@ -0,0 +1,92 @@ +import { + DiscountFilterType, + DiscountMethod, + DiscountValueType, +} from './constants'; + +export type ListAPIResponse = { + data: Array; + total: number; + success: boolean; +}; + +export type Category = { + status: boolean; + index: number; + _id: string; + name: string; + shop: string; + __v: number; +}; + +export type CategoryInput = { + name: string; + status: boolean; +}; + +export type Product = { + desc: string; + value_per_unit: number; + status: boolean; + availability: boolean; + enable_variants: boolean; + stock_enabled: boolean; + available_stock: number; + sku: string; + exclude_tax: boolean; + index: number; + _id: string; + name: string; + mrp: number; + price: number; + category: Pick; + unit: string; + meta: { nonveg: boolean }; + picture: string; + shop: string; + __v: number; + picture_thumb: string; + id: string; +}; + +export type ProductInput = { + name: string; + desc?: string; + value_per_unit: number; + status: boolean; + availability: boolean; + enable_variants: boolean; + exclude_tax: boolean; + mrp: number; + price?: number; + category: string; + unit: string; + meta: { nonveg: boolean }; +}; + +export type ProductDiscountInput = { + discount_method: DiscountMethod; + filter_type: DiscountFilterType; + is_enabled: boolean; + is_visible: boolean; + start_date: string; + end_date: string; + max_users_limit?: number; + minimum_cart_value?: number; + selectedCategories?: string[]; + selectedProducts?: string[]; + promo_code?: string; + title: string; + type: DiscountValueType; + value: string; +}; + +export type BusinessTimingInput = { + timing: { + [key: string]: { + status: boolean; + start: string; + end: string; + }; + }; +}; diff --git a/packages/pieces/community/quickzu/src/lib/triggers/order-created.ts b/packages/pieces/community/quickzu/src/lib/triggers/order-created.ts new file mode 100644 index 0000000..959b478 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/triggers/order-created.ts @@ -0,0 +1,253 @@ +import { + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; +const markdown = ` +- Go to the **Settings->API and Webhooks** section. +- In the webhook settings, paste this URL: + \`{{webhookUrl}}\` +- Click on **Save**. +`; +const sampleData = { + payload: { + id: '65cc96cfcf7028f638e20b0c', + data: { + id: '65cc96cfcf7028f638e20b0c', + __v: 0, + _id: '65cc96cfcf7028f638e20b0c', + area: '63862620938a5552a9e6b', + shop: '5fbe833cef26b83b8b7c3', + status: 'pending', + currency: 'INR', + customer: { + phone: '9039101337', + address: 'okay mumbai', + full_name: 'Mohit', + }, + order_id: 233312, + products: [ + { + id: '65cc96cfcf70280fbde20b0d', + _id: '65cc96cfcf70280fbde20b0d', + qty: 1, + addon: [], + amount: 10, + product: { + id: '5fbe8677ef26b83b8f53b7cf', + __v: 0, + _id: '5fbe8677ef26b83b8f53b7cf', + mrp: 10, + sku: '', + desc: '', + meta: { nonveg: false }, + name: 'Shrewsbury Cookies', + shop: '5fbe833cef26b8f53b7c3', + unit: 'gram', + index: 0, + price: 10, + addons: [], + status: true, + options: [], + category: '5fbe83acef26bb8f53b7c7', + variants: [], + exclude_tax: false, + availability: true, + stock_enabled: false, + value_per_unit: 200, + available_stock: -1, + enable_variants: true, + }, + variant: [], + createdAt: '2024-02-14T10:32:47.953Z', + updatedAt: '2024-02-14T10:32:47.953Z', + }, + ], + coupon_id: '', + createdAt: '2024-02-14T10:32:47.953Z', + sub_total: 29, + updatedAt: '2024-02-14T10:32:47.953Z', + order_type: 'delivery', + coupon_code: '', + instruction: 'spicy food', + payment_mode: 'paylater', + taxes_amount: 2.9, + total_amount: '31.90', + currency_data: { + code: 'INR', + name: 'Indian Rupee', + symbol: 'Rs', + rounding: 0, + name_plural: 'Indian rupees', + symbol_native: '₹', + decimal_digits: 2, + }, + payment_status: 'unpaid', + transaction_id: '', + delivery_charges: 0, + stripe_session_id: '', + subtotal_discount: 0, + item_level_discount: 0, + razorpay_session_id: '', + }, + shop: { + id: '5fbe833c6b83b8f53b7c3', + __v: 0, + _id: '5fbe833c6b83b8f53b7c3', + css: '', + apps: { analytics: '' }, + desc: 'pastries, sandwiches, cake', + name: 'Classic Cakes', + alias: 'ccake', + header: '', + timing: { + '0': { end: '23:59', start: '00:00', status: true }, + '1': { end: '23:59', start: '00:00', status: false }, + '2': { end: '23:59', start: '00:00', status: true }, + '3': { end: '23:59', start: '00:00', status: true }, + '4': { end: '23:59', start: '00:00', status: true }, + '5': { end: '23:59', start: '00:00', status: true }, + '6': { end: '23:59', start: '00:00', status: true }, + createdAt: '2024-01-13T17:54:46.861Z', + updatedAt: '2024-01-13T17:54:46.861Z', + }, + address: '78 Washington St, Hoboken', + country: 'IN', + dine_in: true, + domains: [], + message: {}, + pick_up: true, + category: 'restaurant', + currency: 'INR', + delivery: { + cost: 0, + free: 0, + status: true, + min_order: 0, + is_free_delivery: true, + }, + language: 'en', + template: 'five', + createdAt: '2020-11-25T16:15:56.049Z', + is_closed: false, + seller_id: { + id: '5fbe8101ef26b83b8f53b7c1', + __v: 0, + _id: '5fbe8101ef26b83b8f53b7c1', + email: 'focix@getnada.com', + language: 'en-us', + createdAt: '2020-11-25T16:06:25.546Z', + full_name: 'focix', + updatedAt: '2023-07-01T14:02:27.589Z', + account_type: 'seller', + is_email_verified: true, + }, + updatedAt: '2024-01-29T10:22:46.775Z', + is_blocked: false, + receive_on: 'whatsapp', + appointment: false, + is_onlymenu: false, + payment_inst: 'Pay Offline via UPI/CASH etc', + currency_data: { + code: 'INR', + name: 'Indian Rupee', + symbol: 'Rs', + rounding: 0, + name_plural: 'Indian rupees', + symbol_native: '₹', + decimal_digits: 2, + }, + payment_modes: { + stripe: { + logo: '/public/assets/imgs/stripe.svg', + name: 'stripe', + enabled: true, + test_mode: true, + description: '', + display_name: 'Stripe', + }, + razorpay: { + logo: '/public/assets/imgs/razorpay.svg', + name: 'razorpay', + key_id: 'as', + enabled: false, + test_mode: false, + secret_key: 'da', + description: '', + test_key_id: '', + display_name: 'Razorpay', + test_secret_key: '', + }, + }, + use_area_list: true, + is_maintenance: false, + manual_payments: true, + payment_inst_title: 'Pay Later', + term_condition_text: 'Bhgghhhbsbsbbsbnzja bhbb', + condition_option_enabled: true, + invoice_footer_thankyou_msg: '', + }, + text: "Hi, I'd like to place an order", + overiew: { + TAXES: 'INR 2.90', + TOTAL: 'INR 31.90', + DELIVERY: 'INR 0.00', + SUB_TOTAL: 'INR 29.00', + ORDER_TYPE: 'Delivery', + STORE_LINK: 'ccake.quickzu.com', + STORE_NAME: 'Classic Cakes', + PICKUP_TIME: '', + ORDER_NUMBER: 233312, + TABLE_NUMBER: '', + CUSTOMER_NAME: 'Mohit', + CUSTOMER_PHONE: '9039101337', + PAYMENT_METHOD: 'PAYLATER', + APPOINTMENT_TIME: '', + CUSTOMER_ADDRESS: 'okay mumbai', + ORDER_INSTRUCTION: 'spicy food', + SUBTOTAL_DISCOUNT: 'INR 0.00', + ITEM_LEVEL_DISCOUNT: 'INR 0.00', + PAYMENT_INSTRUCTION: '\nPay Offline via UPI/CASH etc', + }, + hyperlink: '', + total_amount: '31.90', + }, + resource: 'order', + operation: 'create', +}; + +export const orderCreatedTrigger = createTrigger({ + auth: quickzuAuth, + name: 'quickzu_order_created_trigger', + displayName: 'Order Created/Updated', + description: + 'Triggers when a new order is created or a order status is changed in store.', + type: TriggerStrategy.WEBHOOK, + sampleData: sampleData, + props: { + md: Property.MarkDown({ + value: markdown, + }), + }, + async onEnable(context) { + // Empty + }, + async onDisable(context) { + // Empty + }, + async run(context) { + return [context.payload]; + }, + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.BODY_PARAM_PRESENT, + paramName: 'test', + }, + async onHandshake(context) { + return { + status: 200, + body: {}, + }; + }, +}); diff --git a/packages/pieces/community/quickzu/tsconfig.json b/packages/pieces/community/quickzu/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/quickzu/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/quickzu/tsconfig.lib.json b/packages/pieces/community/quickzu/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/quickzu/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/rabbitmq/.eslintrc.json b/packages/pieces/community/rabbitmq/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/rabbitmq/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/rabbitmq/README.md b/packages/pieces/community/rabbitmq/README.md new file mode 100644 index 0000000..f3f34fa --- /dev/null +++ b/packages/pieces/community/rabbitmq/README.md @@ -0,0 +1,7 @@ +# pieces-rabbitmq + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-rabbitmq` to build the library. diff --git a/packages/pieces/community/rabbitmq/package-lock.json b/packages/pieces/community/rabbitmq/package-lock.json new file mode 100644 index 0000000..1f8e058 --- /dev/null +++ b/packages/pieces/community/rabbitmq/package-lock.json @@ -0,0 +1,154 @@ +{ + "name": "@activepieces/piece-rabbitmq", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-rabbitmq", + "version": "0.0.1", + "dependencies": { + "amqplib": "^0.10.4" + }, + "devDependencies": { + "@types/amqplib": "^0.10.5" + } + }, + "node_modules/@acuminous/bitsyntax": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz", + "integrity": "sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==", + "dependencies": { + "buffer-more-ints": "~1.0.0", + "debug": "^4.3.4", + "safe-buffer": "~5.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/@types/amqplib": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@types/amqplib/-/amqplib-0.10.5.tgz", + "integrity": "sha512-/cSykxROY7BWwDoi4Y4/jLAuZTshZxd8Ey1QYa/VaXriMotBDoou7V/twJiOSHzU6t1Kp1AHAUXGCgqq+6DNeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", + "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/amqplib": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.4.tgz", + "integrity": "sha512-DMZ4eCEjAVdX1II2TfIUpJhfKAuoCeDIo/YyETbfAqehHTXxxs7WOOd+N1Xxr4cKhx12y23zk8/os98FxlZHrw==", + "license": "MIT", + "dependencies": { + "@acuminous/bitsyntax": "^0.1.2", + "buffer-more-ints": "~1.0.0", + "readable-stream": "1.x >=1.1.9", + "url-parse": "~1.5.10" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/buffer-more-ints": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", + "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + } + } +} diff --git a/packages/pieces/community/rabbitmq/package.json b/packages/pieces/community/rabbitmq/package.json new file mode 100644 index 0000000..283883d --- /dev/null +++ b/packages/pieces/community/rabbitmq/package.json @@ -0,0 +1,10 @@ +{ + "name": "@activepieces/piece-rabbitmq", + "version": "0.0.2", + "dependencies": { + "amqplib": "^0.10.4" + }, + "devDependencies": { + "@types/amqplib": "^0.10.5" + } +} diff --git a/packages/pieces/community/rabbitmq/project.json b/packages/pieces/community/rabbitmq/project.json new file mode 100644 index 0000000..6248272 --- /dev/null +++ b/packages/pieces/community/rabbitmq/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-rabbitmq", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/rabbitmq/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/rabbitmq", + "tsConfig": "packages/pieces/community/rabbitmq/tsconfig.lib.json", + "packageJson": "packages/pieces/community/rabbitmq/package.json", + "main": "packages/pieces/community/rabbitmq/src/index.ts", + "assets": [ + "packages/pieces/community/rabbitmq/*.md", + { + "input": "packages/pieces/community/rabbitmq/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/rabbitmq/src/index.ts b/packages/pieces/community/rabbitmq/src/index.ts new file mode 100644 index 0000000..8c24db6 --- /dev/null +++ b/packages/pieces/community/rabbitmq/src/index.ts @@ -0,0 +1,53 @@ +import { createPiece, PieceAuth, Property } from '@activepieces/pieces-framework'; +import { messageReceived } from './lib/triggers/message-received'; +import { sendMessageToExchange } from './lib/actions/send-message-to-exchange'; +import { sendMessageToQueue } from './lib/actions/send-message-to-queue'; + +export const rabbitmqAuth = PieceAuth.CustomAuth({ + description: "Rabbitmq Auth", + required: true, + props: { + host: Property.ShortText({ + displayName: "Host", + description: "Host", + required: true, + }), + username: Property.ShortText({ + displayName: "Username", + description: "Username", + required: true, + }), + password: PieceAuth.SecretText({ + displayName: "Password", + description: "Password", + required: true, + }), + port: Property.Number({ + displayName: "Port", + description: "Port", + required: true, + }), + vhost: Property.ShortText({ + displayName: "Virtual Host", + description: "Virtual Host", + required: false, + }), + }, +}); + +export const rabbitmq = createPiece({ + displayName: "RabbitMQ", + auth: rabbitmqAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: "https://cdn.activepieces.com/pieces/rabbitmq.png", + authors: [ + "alinperghel" + ], + actions: [ + sendMessageToExchange, + sendMessageToQueue, + ], + triggers: [ + messageReceived, + ], +}); diff --git a/packages/pieces/community/rabbitmq/src/lib/actions/send-message-to-exchange.ts b/packages/pieces/community/rabbitmq/src/lib/actions/send-message-to-exchange.ts new file mode 100644 index 0000000..a46134a --- /dev/null +++ b/packages/pieces/community/rabbitmq/src/lib/actions/send-message-to-exchange.ts @@ -0,0 +1,62 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { rabbitmqAuth } from '../..'; +import { rabbitmqConnect } from '../common'; + +export const sendMessageToExchange = createAction({ + auth: rabbitmqAuth, + name: 'sendMessageToExchange', + displayName: 'sendMessageToExchange', + description: 'Send a message on a RabbitMQ exchange', + props: { + exchange: Property.ShortText({ + displayName: 'Exchange', + description: 'The name of the exchange to send the message to', + required: true, + }), + routingKey: Property.ShortText({ + displayName: 'Routing Key (Optional)', + description: 'The routing key to use when sending the message', + required: false, + defaultValue: '', + }), + data: Property.Json({ + displayName: 'Data', + description: 'The data to send', + required: true, + defaultValue: { + "key": "value", + "nested": { "key": "value" }, + "array": ["value1", "value2"] + }, + }), + }, + async run(context) { + let connection; + let channel; + try { + const exchange = context.propsValue.exchange; + const routingKey = context.propsValue.routingKey || ''; + + connection = await rabbitmqConnect(context.auth); + channel = await connection.createChannel(); + + await channel.checkExchange(exchange); + + const result = channel.publish( + exchange, + routingKey, + Buffer.from(JSON.stringify(context.propsValue.data)) + ); + + if (!result) { + throw new Error('Failed to send message to exchange'); + } + return result; + } finally { + if (channel) + await channel.close(); + if (connection) + await connection.close(); + } + } +}); diff --git a/packages/pieces/community/rabbitmq/src/lib/actions/send-message-to-queue.ts b/packages/pieces/community/rabbitmq/src/lib/actions/send-message-to-queue.ts new file mode 100644 index 0000000..44cb8db --- /dev/null +++ b/packages/pieces/community/rabbitmq/src/lib/actions/send-message-to-queue.ts @@ -0,0 +1,55 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { rabbitmqAuth } from '../..'; +import { rabbitmqConnect } from '../common'; + +export const sendMessageToQueue = createAction({ + auth: rabbitmqAuth, + name: 'sendMessageToQueue', + displayName: 'sendMessageToQueue', + description: 'Send a message on a RabbitMQ queue', + props: { + queue: Property.ShortText({ + displayName: 'Queue', + description: 'The name of the exchange to send the message to', + required: true, + }), + data: Property.Json({ + displayName: 'Data', + description: 'The data to send', + required: true, + defaultValue: { + "key": "value", + "nested": { "key": "value" }, + "array": ["value1", "value2"] + }, + }), + }, + async run(context) { + const queue = context.propsValue.queue; + let connection; + let channel; + try { + connection = await rabbitmqConnect(context.auth); + channel = await connection.createChannel(); + + await channel.checkQueue(queue); + + const result = channel.sendToQueue( + queue, + Buffer.from(JSON.stringify(context.propsValue.data)) + ); + + if (!result) { + throw new Error('Failed to send message to exchange'); + } + return result; + } finally { + if (channel) { + await channel.close(); + } + if (connection) { + await connection.close(); + } + } + } +}); diff --git a/packages/pieces/community/rabbitmq/src/lib/common/index.ts b/packages/pieces/community/rabbitmq/src/lib/common/index.ts new file mode 100644 index 0000000..e8eed11 --- /dev/null +++ b/packages/pieces/community/rabbitmq/src/lib/common/index.ts @@ -0,0 +1,24 @@ +import { PiecePropValueSchema } from '@activepieces/pieces-framework'; +import { rabbitmqAuth } from '../..'; +import amqp, { Connection } from 'amqplib'; + +export async function rabbitmqConnect( + auth: PiecePropValueSchema, +): Promise { + return amqp.connect(createAmqpURI(auth), (err: Error, conn: Connection) => { + if (err) { + throw err; + } + + return conn; + }); +} + +function createAmqpURI(auth: PiecePropValueSchema): string { + const uri = `amqp://${auth.username}:${auth.password}@${auth.host}:${auth.port}`; + + if (!auth.vhost) { + return uri; + } + return `${uri}/${auth.vhost}`; +} diff --git a/packages/pieces/community/rabbitmq/src/lib/triggers/message-received.ts b/packages/pieces/community/rabbitmq/src/lib/triggers/message-received.ts new file mode 100644 index 0000000..3fcf07e --- /dev/null +++ b/packages/pieces/community/rabbitmq/src/lib/triggers/message-received.ts @@ -0,0 +1,87 @@ +import { + createTrigger, + TriggerStrategy, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { rabbitmqAuth } from '../../index'; +import { rabbitmqConnect } from '../common'; +import dayjs from 'dayjs'; + +const polling: Polling, { + queue: string, + maxMessagesPerPoll: number, +}> = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ auth, propsValue }) => { + const connection = await rabbitmqConnect(auth); + const channel = await connection.createChannel(); + const messages = []; + + try { + const queueInfo = await channel.checkQueue(propsValue.queue); + + if (queueInfo.messageCount === 0) { + return []; + } + + for (let i = 0; i < propsValue.maxMessagesPerPoll; i++) { + const message = await channel.get(propsValue.queue); + if (!message) { + break; + } + messages.push({ + id: dayjs().toISOString(), + data: JSON.parse(message.content.toString()), + }); + channel.ack(message); + } + } finally { + await channel.close(); + await connection.close(); + } + + return messages; + }, +}; + +export const messageReceived = createTrigger({ + auth: rabbitmqAuth, + name: 'messageReceived', + displayName: 'Message Received', + description: 'Triggers when a message is received on a RabbitMQ queue', + props: { + queue: Property.ShortText({ + displayName: 'Queue', + description: 'The name of the queue to listen to', + required: true, + }), + maxMessagesPerPoll: Property.Number({ + displayName: 'Max Messages Per Poll', + description: 'The maximum number of messages to fetch per poll', + required: true, + defaultValue: 50, + }), + }, + sampleData: {}, + type: TriggerStrategy.POLLING, + async test(context) { + const { store, auth, propsValue } = context; + return await pollingHelper.test(polling, { store, auth, propsValue, files: context.files }); + }, + async onEnable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onEnable(polling, { store, auth, propsValue }); + }, + + async onDisable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onDisable(polling, { store, auth, propsValue }); + }, + + async run(context) { + const { store, auth, propsValue } = context; + return await pollingHelper.poll(polling, { store, auth, propsValue, files: context.files }); + }, +}); diff --git a/packages/pieces/community/rabbitmq/tsconfig.json b/packages/pieces/community/rabbitmq/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/rabbitmq/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/rabbitmq/tsconfig.lib.json b/packages/pieces/community/rabbitmq/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/rabbitmq/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/razorpay/.eslintrc.json b/packages/pieces/community/razorpay/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/razorpay/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/razorpay/README.md b/packages/pieces/community/razorpay/README.md new file mode 100644 index 0000000..17ab75a --- /dev/null +++ b/packages/pieces/community/razorpay/README.md @@ -0,0 +1,7 @@ +# pieces-razorpay + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-razorpay` to build the library. diff --git a/packages/pieces/community/razorpay/package.json b/packages/pieces/community/razorpay/package.json new file mode 100644 index 0000000..7841840 --- /dev/null +++ b/packages/pieces/community/razorpay/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-razorpay", + "version": "0.0.1" +} diff --git a/packages/pieces/community/razorpay/project.json b/packages/pieces/community/razorpay/project.json new file mode 100644 index 0000000..b2f8a5d --- /dev/null +++ b/packages/pieces/community/razorpay/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-razorpay", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/razorpay/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/razorpay", + "tsConfig": "packages/pieces/community/razorpay/tsconfig.lib.json", + "packageJson": "packages/pieces/community/razorpay/package.json", + "main": "packages/pieces/community/razorpay/src/index.ts", + "assets": [ + "packages/pieces/community/razorpay/*.md", + { + "input": "packages/pieces/community/razorpay/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/razorpay/src/index.ts b/packages/pieces/community/razorpay/src/index.ts new file mode 100644 index 0000000..dd71263 --- /dev/null +++ b/packages/pieces/community/razorpay/src/index.ts @@ -0,0 +1,45 @@ + import { createCustomApiCallAction } from "@activepieces/pieces-common"; + import { createPiece, PieceAuth, Property } from "@activepieces/pieces-framework"; + import { generateRazorpayAuthHeader, RazorpayCredentials, razorpayURL } from "./lib/common/utils"; + import { createPaymentlink } from "./lib/actions/create-payment-link"; + + export const razorpayAuth = PieceAuth.CustomAuth({ + description: ` + Enter your Key ID and Key Secret + + Login to your Dashboard with appropriate credentials. + Select the mode (Test or Live) for which you want to generate the API key + Navigate to Settings > API Keys > Generate Key to generate keys for the selected mode. + + The Key ID and Key Secret appear in a pop-out window. + `, + required: true, + props: { + keyID: Property.ShortText({ + displayName: 'Key ID', + required: true, + }), + keySecret: PieceAuth.SecretText({ + displayName: 'Key Secret', + required: true, + }), + } + }) + + export const razorpay = createPiece({ + displayName: "Razorpay", + auth: razorpayAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: "https://cdn.activepieces.com/pieces/razorpay.png", + authors: ['drona2938'], + actions: [ + createCustomApiCallAction({ + baseUrl: () => razorpayURL.apiURL, + auth: razorpayAuth, + authMapping: (auth) => generateRazorpayAuthHeader(auth as RazorpayCredentials), + }), + createPaymentlink + ], + triggers: [], + }); + \ No newline at end of file diff --git a/packages/pieces/community/razorpay/src/lib/actions/create-payment-link.ts b/packages/pieces/community/razorpay/src/lib/actions/create-payment-link.ts new file mode 100644 index 0000000..e75ccb8 --- /dev/null +++ b/packages/pieces/community/razorpay/src/lib/actions/create-payment-link.ts @@ -0,0 +1,108 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { generateRazorpayAuthHeader, RazorpayCredentials } from '../common/utils'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { razorpayAuth } from '../..'; + + + +export const createPaymentlink = createAction({ + name: 'create-payment-link', + auth: razorpayAuth, + displayName: 'Create Payment Link', + description: 'Create a payment link', + props: { + amount: Property.Number({ + displayName: 'Amount', + required: true, + }), + currency: Property.ShortText({ + displayName: 'Currency', + required: true, + defaultValue: 'INR', + }), + reference_id: Property.ShortText({ + displayName: 'Reference ID', + required: false, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + customer_name: Property.ShortText({ + displayName: 'Customer Name', + required: false, + }), + customer_contact: Property.ShortText({ + displayName: 'Customer Contact', + required: true, + defaultValue: '+91', + }), + notify_sms: Property.Checkbox({ + displayName: 'Notify via SMS', + description: 'Send notification via SMS', + required: false, + defaultValue: true, + }), + customer_email: Property.ShortText({ + displayName: 'Customer Email', + required: false, + }), + notify_email: Property.Checkbox({ + displayName: 'Notify via Email', + description: 'Send notification via Email', + required: false, + defaultValue: true, + }), + metafield_notes: Property.ShortText({ + displayName: 'Notes', + required: false, + }), + callback_url: Property.ShortText({ + displayName: 'Callback URL', + required: false, + }), + callback_method: Property.ShortText({ + displayName: 'Callback Method', + required: false, + }), + }, + async run(context) { + // Convert amount from rupee format to the format expected by Razorpay + const amountWithoutDecimal = Math.round(context.propsValue.amount * 100); + + const paymentLinkData = { + amount: amountWithoutDecimal, + currency: context.propsValue.currency, + reference_id: context.propsValue.reference_id, + description: context.propsValue.description, + customer: { + name: context.propsValue.customer_name, + contact: context.propsValue.customer_contact, + email: context.propsValue.customer_email, + }, + notify: { + sms: context.propsValue.notify_sms, + email: context.propsValue.notify_email, + }, + notes: { + policy_name: context.propsValue.metafield_notes, + }, + callback_url: context.propsValue.callback_url, + callback_method: context.propsValue.callback_method, + }; + + const authHeader = await generateRazorpayAuthHeader(context.auth as RazorpayCredentials); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.razorpay.com/v1/payment_links', + headers: { + ...authHeader, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(paymentLinkData), + }); + + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/razorpay/src/lib/common/utils.ts b/packages/pieces/community/razorpay/src/lib/common/utils.ts new file mode 100644 index 0000000..2d5c6d1 --- /dev/null +++ b/packages/pieces/community/razorpay/src/lib/common/utils.ts @@ -0,0 +1,17 @@ + export type RazorpayCredentials = { + keyID: string; + keySecret: string; + }; + + export const generateRazorpayAuthHeader = (credentials: RazorpayCredentials): Promise<{ Authorization: string }> => { + const { keyID, keySecret } = credentials; + const encodedCredentials = Buffer.from(`${keyID}:${keySecret}`).toString('base64'); + return Promise.resolve({ + Authorization: `Basic ${encodedCredentials}`, + }); + }; + + + export const razorpayURL = { + apiURL: 'https://api.razorpay.com/v1/' + }; \ No newline at end of file diff --git a/packages/pieces/community/razorpay/tsconfig.json b/packages/pieces/community/razorpay/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/razorpay/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/razorpay/tsconfig.lib.json b/packages/pieces/community/razorpay/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/razorpay/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/reachinbox/.eslintrc.json b/packages/pieces/community/reachinbox/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/reachinbox/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/reachinbox/README.md b/packages/pieces/community/reachinbox/README.md new file mode 100644 index 0000000..518cd10 --- /dev/null +++ b/packages/pieces/community/reachinbox/README.md @@ -0,0 +1,7 @@ +# pieces-reachinbox + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-reachinbox` to build the library. diff --git a/packages/pieces/community/reachinbox/package.json b/packages/pieces/community/reachinbox/package.json new file mode 100644 index 0000000..fa6ff33 --- /dev/null +++ b/packages/pieces/community/reachinbox/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-reachinbox", + "version": "0.0.2" +} diff --git a/packages/pieces/community/reachinbox/project.json b/packages/pieces/community/reachinbox/project.json new file mode 100644 index 0000000..122ef69 --- /dev/null +++ b/packages/pieces/community/reachinbox/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-reachinbox", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/reachinbox/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist\\{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/reachinbox", + "tsConfig": "packages/pieces/community/reachinbox/tsconfig.lib.json", + "packageJson": "packages/pieces/community/reachinbox/package.json", + "main": "packages/pieces/community/reachinbox/src/index.ts", + "assets": [ + "packages/pieces/community/reachinbox/*.md", + { + "input": "packages/pieces/community/reachinbox/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist\\{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/reachinbox/src/index.ts b/packages/pieces/community/reachinbox/src/index.ts new file mode 100644 index 0000000..80e3cc3 --- /dev/null +++ b/packages/pieces/community/reachinbox/src/index.ts @@ -0,0 +1,84 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { addLeads } from './lib/actions/add-leads'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { campaignCompleted } from './lib/triggers/campaign-completed'; +import { emailBounced } from './lib/triggers/email-bounced'; +import { emailOpened } from './lib/triggers/email-opened'; +import { emailSent } from './lib/triggers/email-sent'; +import { leadInterested } from './lib/triggers/lead-interested'; +import { leadNotInterested } from './lib/triggers/lead-not-interested'; +import { replyReceived } from './lib/triggers/reply-received'; +import { addBlocklist } from './lib/actions/add-blocklist'; +import { addEmail } from './lib/actions/add-email'; +import { enableWarmup } from './lib/actions/enable-warmup'; +import { getCampaignAnalytics } from './lib/actions/get-campaign-analytics'; +import { getSummary } from './lib/actions/get-summary'; +import { pauseCampaign } from './lib/actions/pause-campaign'; +import { pauseWarmup } from './lib/actions/pause-warmup'; +import { removeEmail } from './lib/actions/remove-email'; +import { setSchedule } from './lib/actions/set-schedule'; +import { startCampaign } from './lib/actions/start-campaign'; +import { updateLead } from './lib/actions/update-lead'; +import { PieceCategory } from '@activepieces/shared'; + +/** + * Define the API Key authentication using PieceAuth.SecretText + */ + +export const ReachinboxAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: 'Enter your ReachInbox API key', + required: true, + validate: async ({ auth }) => { + // Validate the API key format (UUID pattern: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX) + const uuidRegex = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + if (uuidRegex.test(auth)) { + return { valid: true }; + } + return { + valid: false, + error: + 'Invalid API Key. It should follow the UUID format (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).', + }; + }, +}); + +export const reachinbox = createPiece({ + displayName: 'Reachinbox', + auth: ReachinboxAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/reachinbox.png', + categories: [PieceCategory.MARKETING], + authors: ['support@reachinbox.ai', 'ManojKumard', 'Mitrajit'], + actions: [ + addLeads, + addBlocklist, + addEmail, + enableWarmup, + getCampaignAnalytics, + getSummary, + pauseCampaign, + pauseWarmup, + removeEmail, + setSchedule, + startCampaign, + updateLead, + createCustomApiCallAction({ + baseUrl: () => 'https://api.reachinbox.ai/api/v1/', + auth: ReachinboxAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers: [ + campaignCompleted, + emailBounced, + emailOpened, + emailSent, + leadInterested, + leadNotInterested, + replyReceived, + ], +}); diff --git a/packages/pieces/community/reachinbox/src/lib/actions/add-blocklist.ts b/packages/pieces/community/reachinbox/src/lib/actions/add-blocklist.ts new file mode 100644 index 0000000..0ea17b8 --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/actions/add-blocklist.ts @@ -0,0 +1,72 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { ReachinboxAuth } from '../..'; // Ensure proper authentication setup +import { reachinboxCommon } from '../common'; + +export const addBlocklist = createAction({ + auth: ReachinboxAuth, + name: 'addBlocklist', + displayName: 'Add Blocklist', + description: 'Add email addresses, domains, and keywords to the blocklist.', + props: { + emails: Property.Array({ + displayName: 'Email Addresses', + description: + 'Enter the email addresses to block (e.g., ["abc@gmail.com"])', + required: false, + }), + domains: Property.Array({ + displayName: 'Domains', + description: 'Enter the domains to block (e.g., ["example.com"])', + required: false, + }), + keywords: Property.Array({ + displayName: 'Keywords', + description: 'Enter keywords to block (e.g., ["spam", "blacklist"])', + required: false, + }), + }, + async run(context) { + const { emails, domains, keywords } = context.propsValue; + + // Ensure at least one of emails, domains, or keywords is provided + if (!emails?.length && !domains?.length && !keywords?.length) { + throw new Error( + 'Please provide at least one email, domain, or keyword to block.' + ); + } + + const body = { + emails: emails || [], + domains: domains || [], + keywords: keywords || [], + }; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${reachinboxCommon.baseUrl}blocklist/add`, + headers: { + Authorization: `Bearer ${context.auth}`, + 'Content-Type': 'application/json', + }, + body, + }); + + if (response.status === 200) { + return { + success: true, + message: response.body.message || 'Blocklist updated successfully.', + }; + } else { + throw new Error(`Failed to update blocklist: ${response.body.message}`); + } + } catch (error: unknown) { + if (error instanceof Error) { + throw new Error(`Error updating blocklist: ${error.message}`); + } else { + throw new Error('Unknown error occurred while updating the blocklist.'); + } + } + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/actions/add-email.ts b/packages/pieces/community/reachinbox/src/lib/actions/add-email.ts new file mode 100644 index 0000000..54cb88b --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/actions/add-email.ts @@ -0,0 +1,78 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { fetchCampaigns, reachinboxCommon } from '../common/index'; +import { ReachinboxAuth } from '../..'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +export const addEmail = createAction({ + auth: ReachinboxAuth, + name: 'addEmail', + displayName: 'Add Email', + description: 'Add an email to a specific account.', + props: { + campaignId: Property.Dropdown({ + displayName: 'Select Campaign', + description: + 'Choose a campaign from the list or enter the campaign ID manually.', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + const campaigns = await fetchCampaigns(auth as string); + + return { + options: campaigns.map((campaign) => ({ + label: campaign.name, + value: campaign.id, + })), + disabled: campaigns.length === 0, + }; + }, + }), + email: Property.ShortText({ + displayName: 'Email', + description: 'Enter the email address to add to the campaign.', + required: true, + }), + }, + async run(context) { + const { campaignId, email } = context.propsValue; + + if (!campaignId || !email) { + throw new Error('Campaign ID and Email are required.'); + } + + const url = `${reachinboxCommon.baseUrl}campaigns/add-email`; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: url, + headers: { + Authorization: `Bearer ${context.auth as string}`, + 'Content-Type': 'application/json', + }, + body: { + campaignId: campaignId, + email: email, + }, + }); + + if (response.status === 200) { + return { + success: true, + message: response.body.message || 'Email added successfully.', + }; + } else if (response.status === 404) { + return { + success: false, + message: + response.body.message || + 'Campaign not found or no emails were added to the campaign.', + }; + } else { + throw new Error(`Failed to add email: ${response.body.message}`); + } + } catch (error: any) { + throw new Error(`Failed to add email: ${error.message}`); + } + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/actions/add-leads.ts b/packages/pieces/community/reachinbox/src/lib/actions/add-leads.ts new file mode 100644 index 0000000..4a006bb --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/actions/add-leads.ts @@ -0,0 +1,123 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + fetchCampaigns, + addLeadsToCampaign, + reachinboxCommon, +} from '../common/index'; +import { reachinbox, ReachinboxAuth } from '../..'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +// Define the structure for custom variables +interface CustomVariable { + key: string; + value: string; +} + +export const addLeads = createAction({ + auth: ReachinboxAuth, + name: 'addLeads', + displayName: 'Add Leads', + description: + 'Add leads to campaigns dynamically by selecting a campaign, entering lead details, and including custom variables.', + props: { + campaignId: Property.Dropdown({ + displayName: 'Select Campaign', + description: 'Choose a campaign from the list.', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + const campaigns = await fetchCampaigns(auth as string); + + return { + options: campaigns.map((campaign) => ({ + label: campaign.name, + value: campaign.id.toString(), + })), + disabled: campaigns.length === 0, + }; + }, + }), + email: Property.ShortText({ + displayName: 'Email Address', + description: 'Enter the email address for the lead', + required: true, + }), + firstName: Property.ShortText({ + displayName: 'First Name', + description: 'Enter the first name for the lead', + required: true, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + description: 'Enter the last name for the lead', + required: false, + }), + customVariables: Property.Array({ + displayName: 'Custom Variables', + description: 'Add custom variables as key-value pairs for the lead.', + properties: { + key: Property.ShortText({ + displayName: 'Key', + description: 'Enter the key for the custom variable', + required: true, + }), + value: Property.ShortText({ + displayName: 'Value', + description: 'Enter the value for the custom variable', + required: true, + }), + }, + required: false, + defaultValue: [], + }), + }, + async run(context) { + const { campaignId, email, firstName, lastName } = context.propsValue; + + // Safely cast customVariables to CustomVariable[], default to an empty array if undefined + const customVariables: CustomVariable[] = (context.propsValue + .customVariables || []) as CustomVariable[]; + + // Process the custom variables into a key-value object for each lead + const customVariablesObject: Record = {}; + customVariables.forEach((variable: CustomVariable) => { + customVariablesObject[variable.key] = variable.value; + }); + + // Include the custom variables in the lead data + const body = { + campaignId, + leads: [{ email, firstName, lastName, ...customVariablesObject }], + newCoreVariables: [ + 'firstName', + ...customVariables.map((varObj: CustomVariable) => varObj.key), + ], + duplicates: [], + }; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${reachinboxCommon.baseUrl}leads/add`, + headers: { + Authorization: `Bearer ${context.auth}`, + 'Content-Type': 'application/json', + }, + body, + }); + + if (response.status === 200) { + return { + success: true, + message: response.body.message || 'Leads added successfully.', + leadCount: response.body.leadCount, + }; + } else { + throw new Error(`Failed to add leads: ${response.body.message}`); + } + } catch (error) { + console.error('Error adding leads:', error); + throw new Error('Failed to add leads to the campaign.'); + } + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/actions/enable-warmup.ts b/packages/pieces/community/reachinbox/src/lib/actions/enable-warmup.ts new file mode 100644 index 0000000..ca0cccb --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/actions/enable-warmup.ts @@ -0,0 +1,93 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { ReachinboxAuth } from '../..'; +import { reachinboxCommon } from '../common'; + +// Define the shape of the account object +interface EmailAccount { + id: number; + email: string; + warmupEnabled: boolean; + isDisconnected: boolean; +} + +export const enableWarmup = createAction({ + auth: ReachinboxAuth, + name: 'enableWarmup', + displayName: 'Enable Warmup', + description: + 'Enable warmup for specific email accounts where it is currently disabled.', + props: { + accountId: Property.Dropdown({ + displayName: 'Select Email Accounts to Enable Warmup', + description: 'Choose email accounts that have warmup disabled.', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + try { + // Fetch email accounts + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${reachinboxCommon.baseUrl}account/all`, + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + + const accounts: EmailAccount[] = + response.body?.data?.emailsConnected || []; + + // Filter accounts where warmup is not enabled and isDisconnected is false + const accountsToEnableWarmup = accounts.filter( + (account: EmailAccount) => + !account.warmupEnabled && !account.isDisconnected + ); + + // Map the filtered accounts to dropdown options + const options = accountsToEnableWarmup.map( + (account: EmailAccount) => ({ + label: `${account.email} (ID: ${account.id})`, + value: account.id.toString(), + }) + ); + + return { + options, + disabled: options.length === 0, + }; + } catch (error) { + console.error('Error fetching email accounts:', error); + return { options: [], disabled: true }; + } + }, + }), + }, + async run(context) { + const { accountId } = context.propsValue; + + // Prepare the body for enabling warmup + const body = { + ids: [accountId], + }; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${reachinboxCommon.baseUrl}account/warmup/enable`, + headers: { + Authorization: `Bearer ${context.auth}`, + 'Content-Type': 'application/json', + }, + body, + }); + + return { + success: true, + message: `Warmup enabled for account ID: ${accountId}`, + }; + } catch (error) { + console.error('Error enabling warmup:', error); + throw new Error(`Failed to enable warmup for account ID: ${accountId}`); + } + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/actions/get-campaign-analytics.ts b/packages/pieces/community/reachinbox/src/lib/actions/get-campaign-analytics.ts new file mode 100644 index 0000000..fd3e74a --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/actions/get-campaign-analytics.ts @@ -0,0 +1,129 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { fetchCampaigns, reachinboxCommon } from '../common/index'; +import { ReachinboxAuth } from '../..'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +// Define the structure of the analytics response +interface CampaignAnalyticsResponse { + status: number; + message: string; + data: { + totalSent: number; + opened: number; + openRate: string; + clicked: number; + clickRate: string; + replied: number; + replyRate: string; + campaignStatus: string; + result: Array<{ + date: string; + sent: number; + totalOpens: string; + uniqueOpens: string; + linksClicked: string; + totalReplies: string; + }>; + stepAnalytics: Array<{ + step: string; + uniqueOpens: number; + linkClicked: number; + totalReplies: number; + openedPercentage: number; + clickedPercentage: number; + repliedPercentage: number; + variants: Array<{ + variant: string; + sent: number; + uniqueOpens: number; + linkClicked: number; + totalReplies: number; + openedPercentage: number; + clickedPercentage: number; + repliedPercentage: number; + }>; + }>; + activity: Array<{ + step: number; + fromEmail: string; + toEmail: string; + activity: string; + timestamp: string; + }>; + }; +} + +export const getCampaignAnalytics = createAction({ + auth: ReachinboxAuth, + name: 'getCampaignAnalytics', + displayName: 'Get Campaign Analytics', + description: + 'Fetch analytics data for a selected campaign based on a date range.', + props: { + campaignId: Property.Dropdown({ + displayName: 'Select Campaign', + description: + 'Choose a campaign from the list or enter the campaign ID manually.', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + const campaigns = await fetchCampaigns(auth as string); + + return { + options: campaigns.map((campaign) => ({ + label: campaign.name, + value: campaign.id.toString(), + })), + disabled: campaigns.length === 0, + }; + }, + }), + startDate: Property.ShortText({ + displayName: 'Start Date', + description: 'Enter the start date (YYYY-MM-DD). E.g. 2023-10-11', + required: true, // Made mandatory again + }), + endDate: Property.ShortText({ + displayName: 'End Date', + description: 'Enter the end date (YYYY-MM-DD). E.g. 2023-10-20', + required: true, // Made mandatory again + }), + }, + async run(context) { + const { campaignId, startDate, endDate } = context.propsValue; + + // Validate date format if needed + + const url = `${reachinboxCommon.baseUrl}campaign/analytics?campaignId=${campaignId}&startDate=${startDate}&endDate=${endDate}`; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: url, + headers: { + Authorization: `Bearer ${context.auth as string}`, + }, + }); + + if (response.body.status === 200) { + return { + success: true, + message: response.body.message, + data: response.body.data, + }; + } else { + throw new Error(`Failed to fetch analytics: ${response.body.message}`); + } + } catch (error: any) { + if (error.response?.status === 404) { + return { + success: false, + message: + 'Campaign not found or analytics data unavailable for the provided date range.', + }; + } else { + throw new Error(`Failed to fetch analytics: ${error.message}`); + } + } + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/actions/get-summary.ts b/packages/pieces/community/reachinbox/src/lib/actions/get-summary.ts new file mode 100644 index 0000000..e42d737 --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/actions/get-summary.ts @@ -0,0 +1,81 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { ReachinboxAuth } from '../..'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { reachinboxCommon } from '../common/index'; + +// Define the structure of the summary response +interface SummaryAnalyticsResponse { + status: number; + message: string; + data: { + totalSent: number; + open: number; + openRate: string; + click: number; + clickRate: string; + reply: number; + replyRate: string; + result: Array<{ + date: string; + sent: number; + totalOpens: string; + uniqueOpens: string; + linksClicked: string; + totalReplies: string; + }>; + }; +} + +export const getSummary = createAction({ + auth: ReachinboxAuth, + name: 'getSummary', + displayName: 'Get Summary', + description: 'Get a summary of campaign analytics within a date range.', + props: { + startDate: Property.ShortText({ + displayName: 'Start Date', + description: 'Enter the start date (YYYY-MM-DD). E.g. 2023-10-11', + required: true, + }), + endDate: Property.ShortText({ + displayName: 'End Date', + description: 'Enter the end date (YYYY-MM-DD). E.g. 2023-12-20', + required: true, + }), + }, + async run(context) { + const { startDate, endDate } = context.propsValue; + + // Build the URL with the startDate and endDate query parameters + const url = `https://api.reachinbox.ai/api/v1/analytics/summary?startDate=${startDate}&endDate=${endDate}`; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: url, + headers: { + Authorization: `Bearer ${context.auth as string}`, + }, + }); + + if (response.body.status === 200) { + return { + success: true, + message: response.body.message, + data: response.body.data, + }; + } else { + throw new Error(`Failed to fetch summary: ${response.body.message}`); + } + } catch (error: any) { + if (error.response?.status === 404) { + return { + success: false, + message: 'Summary data not found for the provided date range.', + }; + } else { + throw new Error(`Failed to fetch summary: ${error.message}`); + } + } + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/actions/pause-campaign.ts b/packages/pieces/community/reachinbox/src/lib/actions/pause-campaign.ts new file mode 100644 index 0000000..98b8f35 --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/actions/pause-campaign.ts @@ -0,0 +1,75 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { ReachinboxAuth } from '../..'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { fetchCampaigns, reachinboxCommon } from '../common/index'; + +// Define the structure of the pause response +interface PauseCampaignResponse { + status: number; + message: string; +} + +export const pauseCampaign = createAction({ + auth: ReachinboxAuth, + name: 'pauseCampaign', + displayName: 'Pause Campaign', + description: 'Pause a selected campaign.', + props: { + campaignId: Property.Dropdown({ + displayName: 'Select Campaign', + description: 'Choose a campaign to pause.', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + const campaigns = await fetchCampaigns(auth as string); + + return { + options: campaigns.map((campaign) => ({ + label: campaign.name, + value: campaign.id.toString(), + })), + disabled: campaigns.length === 0, + }; + }, + }), + }, + async run(context) { + const { campaignId } = context.propsValue; + + // Build the URL for pausing the campaign + const url = `${reachinboxCommon.baseUrl}campaigns/pause`; + + // Make a POST request to pause the selected campaign + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: url, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${context.auth as string}`, + }, + body: { + campaignId, // Pass the selected campaign ID in the request body + }, + }); + + if (response.body.status === 200) { + return { + success: true, + message: response.body.message, + }; + } else { + throw new Error(`Failed to pause campaign: ${response.body.message}`); + } + } catch (error: any) { + if (error.response?.status === 404) { + return { + success: false, + message: 'Campaign not found or unable to pause.', + }; + } else { + throw new Error(`Failed to pause campaign: ${error.message}`); + } + } + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/actions/pause-warmup.ts b/packages/pieces/community/reachinbox/src/lib/actions/pause-warmup.ts new file mode 100644 index 0000000..5d91654 --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/actions/pause-warmup.ts @@ -0,0 +1,88 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { ReachinboxAuth } from '../..'; +import { reachinboxCommon } from '../common'; + +// Define the shape of the account object +interface EmailAccount { + id: number; + email: string; + warmupEnabled: boolean; +} + +export const pauseWarmup = createAction({ + auth: ReachinboxAuth, + name: 'pauseWarmup', + displayName: 'Pause Warmup', + description: 'Pause warmup for selected email accounts.', + props: { + accountId: Property.Dropdown({ + displayName: 'Select Email Accounts to Pause Warmup', + description: 'Choose email accounts that are currently warming up.', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + try { + // Fetch email accounts + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${reachinboxCommon.baseUrl}account/all`, + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + + const accounts: EmailAccount[] = + response.body?.data?.emailsConnected || []; + + // Filter accounts with warmupEnabled = true + const warmupAccounts = accounts.filter( + (account: EmailAccount) => account.warmupEnabled + ); + + // Map the warmup accounts to dropdown options + const options = warmupAccounts.map((account: EmailAccount) => ({ + label: `${account.email} (ID: ${account.id})`, + value: account.id.toString(), + })); + + return { + options, + disabled: options.length === 0, + }; + } catch (error) { + console.error('Error fetching email accounts:', error); + return { options: [], disabled: true }; + } + }, + }), + }, + async run(context) { + const { accountId } = context.propsValue; + + // Prepare the body for pausing warmup + const body = { + ids: [accountId], + }; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${reachinboxCommon.baseUrl}account/warmup/pause`, + headers: { + Authorization: `Bearer ${context.auth}`, + 'Content-Type': 'application/json', + }, + body, + }); + + return { + success: true, + message: `Warmup paused for account ID: ${accountId}`, + }; + } catch (error) { + console.error('Error pausing warmup:', error); + throw new Error(`Failed to pause warmup for account ID: ${accountId}`); + } + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/actions/remove-email.ts b/packages/pieces/community/reachinbox/src/lib/actions/remove-email.ts new file mode 100644 index 0000000..5bb2c10 --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/actions/remove-email.ts @@ -0,0 +1,79 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { ReachinboxAuth } from '../..'; +import { reachinboxCommon } from '../common'; + +// Define the shape of the account object +interface EmailAccount { + id: number; + email: string; +} + +export const removeEmail = createAction({ + auth: ReachinboxAuth, + name: 'removeEmail', + displayName: 'Remove Email', + description: 'Remove an email account from the system.', + props: { + accountId: Property.Dropdown({ + displayName: 'Select Email Account to Remove', + description: 'Choose an email account to remove.', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + try { + // Fetch email accounts + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${reachinboxCommon.baseUrl}account/all`, + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + + const accounts: EmailAccount[] = + response.body?.data?.emailsConnected || []; + + // Map the fetched accounts to dropdown options + const options = accounts.map((account: EmailAccount) => ({ + label: `${account.email} (ID: ${account.id})`, + value: account.id.toString(), + })); + + return { + options, + disabled: options.length === 0, + }; + } catch (error) { + console.error('Error fetching email accounts:', error); + return { options: [], disabled: true }; + } + }, + }), + }, + async run(context) { + const { accountId } = context.propsValue; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: `${reachinboxCommon.baseUrl}account/delete/${accountId}`, + headers: { + Authorization: `Bearer ${context.auth}`, + }, + }); + + if (response.status === 200) { + return { + success: true, + message: `Email account with ID ${accountId} was successfully removed.`, + }; + } else { + throw new Error(`Failed to remove email account with ID: ${accountId}`); + } + } catch (error) { + console.error('Error removing email account:', error); + throw new Error(`Error removing email account with ID: ${accountId}`); + } + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/actions/set-schedule.ts b/packages/pieces/community/reachinbox/src/lib/actions/set-schedule.ts new file mode 100644 index 0000000..a81d215 --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/actions/set-schedule.ts @@ -0,0 +1,171 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { fetchCampaigns, reachinboxCommon } from '../common/index'; +import { ReachinboxAuth } from '../..'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +export const setSchedule = createAction({ + auth: ReachinboxAuth, + name: 'setSchedule', + displayName: 'Set Schedule', + description: 'Update the schedule for a specific Campaign', + props: { + campaignId: Property.Dropdown({ + displayName: 'Select Campaign', + description: + 'Choose a campaign from the list or enter the campaign ID manually.', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + const campaigns = await fetchCampaigns(auth as string); + + return { + options: campaigns.map((campaign) => ({ + label: campaign.name, + value: campaign.id.toString(), + })), + disabled: campaigns.length === 0, + }; + }, + }), + scheduleName: Property.ShortText({ + displayName: 'Schedule Name', + description: 'Enter the schedule name here (e.g., New schedule).', + required: true, + }), + startDate: Property.DateTime({ + displayName: 'Start Date', + description: 'Enter the start date (e.g., 2023-08-01T00:00:00.000Z).', + required: true, + }), + endDate: Property.DateTime({ + displayName: 'End Date', + description: 'Enter the end date (e.g., 2024-08-25T00:00:00.000Z).', + required: true, + }), + startTime: Property.ShortText({ + displayName: 'Start Time', + description: 'Enter the start time (e.g., 09:00).', + required: true, + }), + endTime: Property.ShortText({ + displayName: 'End Time', + description: 'Enter the end time (e.g., 12:00).', + required: true, + }), + timezone: Property.ShortText({ + displayName: 'Timezone', + description: 'Enter the timezone (e.g., America/Detroit).', + required: true, + }), + sunday: Property.Checkbox({ + displayName: 'Sunday', + description: 'Choose "Yes" if you want to set a schedule for Sunday.', + required: true, + defaultValue: false, + }), + monday: Property.Checkbox({ + displayName: 'Monday', + description: 'Choose "Yes" if you want to set a schedule for Monday.', + required: true, + defaultValue: false, + }), + tuesday: Property.Checkbox({ + displayName: 'Tuesday', + description: 'Choose "Yes" if you want to set a schedule for Tuesday.', + required: true, + defaultValue: false, + }), + wednesday: Property.Checkbox({ + displayName: 'Wednesday', + description: 'Choose "Yes" if you want to set a schedule for Wednesday.', + required: true, + defaultValue: false, + }), + thursday: Property.Checkbox({ + displayName: 'Thursday', + description: 'Choose "Yes" if you want to set a schedule for Thursday.', + required: true, + defaultValue: false, + }), + friday: Property.Checkbox({ + displayName: 'Friday', + description: 'Choose "Yes" if you want to set a schedule for Friday.', + required: true, + defaultValue: false, + }), + saturday: Property.Checkbox({ + displayName: 'Saturday', + description: 'Choose "Yes" if you want to set a schedule for Saturday.', + required: true, + defaultValue: false, + }), + }, + async run(context) { + const { + campaignId, + scheduleName, + startDate, + endDate, + startTime, + endTime, + timezone, + sunday, + monday, + tuesday, + wednesday, + thursday, + friday, + saturday, + } = context.propsValue; + + const schedules = [ + { + name: scheduleName, + timing: { + from: startTime, + to: endTime, + }, + days: { + '0': sunday, + '1': monday, + '2': tuesday, + '3': wednesday, + '4': thursday, + '5': friday, + '6': saturday, + }, + timezone: timezone, + }, + ]; + + const url = `${reachinboxCommon.baseUrl}campaigns/set-schedule`; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: url, + headers: { + Authorization: `Bearer ${context.auth as string}`, + 'Content-Type': 'application/json', + }, + body: { + campaignId: campaignId, + startDate: startDate, + endDate: endDate, + schedules: schedules, + }, + }); + + if (response.status === 200) { + return { + success: true, + message: response.body.message || 'Schedule updated successfully.', + }; + } else { + throw new Error(`Failed to update schedule: ${response.body.message}`); + } + } catch (error: any) { + throw new Error(`Failed to update schedule: ${error.message}`); + } + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/actions/start-campaign.ts b/packages/pieces/community/reachinbox/src/lib/actions/start-campaign.ts new file mode 100644 index 0000000..6355179 --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/actions/start-campaign.ts @@ -0,0 +1,65 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { fetchCampaigns, reachinboxCommon } from '../common/index'; +import { ReachinboxAuth } from '../..'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +export const startCampaign = createAction({ + auth: ReachinboxAuth, + name: 'startCampaign', + displayName: 'Start Campaign', + description: 'Starts a Campaign', + props: { + campaignId: Property.Dropdown({ + displayName: 'Select Campaign', + description: + 'Choose a campaign from the list or enter the campaign ID manually.', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + const campaigns = await fetchCampaigns(auth as string); + + return { + options: campaigns.map((campaign) => ({ + label: campaign.name, + value: campaign.id, + })), + disabled: campaigns.length === 0, + }; + }, + }), + }, + async run(context) { + const { campaignId } = context.propsValue; + + if (!campaignId) { + throw new Error('Campaign ID is required.'); + } + + const url = `${reachinboxCommon.baseUrl}campaigns/start`; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: url, + headers: { + Authorization: `Bearer ${context.auth as string}`, + 'Content-Type': 'application/json', + }, + body: { + campaignId: campaignId, + }, + }); + + if (response.status === 200) { + return { + success: true, + message: response.body.message || 'Campaign started successfully.', + }; + } else { + throw new Error(`Failed to start campaign: ${response.body.message}`); + } + } catch (error: any) { + throw new Error(`Failed to start campaign: ${error.message}`); + } + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/actions/update-lead.ts b/packages/pieces/community/reachinbox/src/lib/actions/update-lead.ts new file mode 100644 index 0000000..1f44fcd --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/actions/update-lead.ts @@ -0,0 +1,158 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { fetchCampaigns, reachinboxCommon } from '../common/index'; +import { ReachinboxAuth } from '../..'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +// Define the structure for custom variables +interface CustomVariable { + key: string; + value: string; +} + +// Define the updateLead action +export const updateLead = createAction({ + auth: ReachinboxAuth, + name: 'updateLead', + displayName: 'Update Lead', + description: 'Updates a Lead.', + props: { + campaignId: Property.Dropdown({ + displayName: 'Select Campaign', + description: + 'Choose a campaign from the list or enter the campaign ID manually.', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + const campaigns = await fetchCampaigns(auth as string); + return { + options: campaigns.map((campaign) => ({ + label: campaign.name, + value: campaign.id, + })), + disabled: campaigns.length === 0, + }; + }, + }), + leadId: Property.Dropdown({ + displayName: 'Select Lead', + description: 'Choose a lead from the selected campaign.', + required: true, + refreshers: ['campaignId'], + options: async ({ auth, campaignId }) => { + if (!campaignId) { + return { options: [], disabled: true }; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${reachinboxCommon.baseUrl}leads?campaignId=${campaignId}&lastLead=false`, + headers: { + Authorization: `Bearer ${auth as string}`, + }, + }); + + if (response.status !== 200) { + throw new Error('Failed to fetch leads.'); + } + + const leads = Array.isArray(response.body.data.leads) + ? response.body.data.leads + : []; + + return { + options: leads.map((lead: { email: string; id: string }) => ({ + label: lead.email, + value: lead.id, + })), + disabled: leads.length === 0, + }; + }, + }), + email: Property.ShortText({ + displayName: 'Email', + description: 'Enter the new email address for the lead.', + required: true, + }), + firstName: Property.ShortText({ + displayName: 'First Name', + description: 'Enter the new first name for the lead.', + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + description: 'Enter the new last name for the lead.', + required: false, + }), + customVariables: Property.Array({ + displayName: 'Custom Variables', + description: 'Add custom variables as key-value pairs for the lead.', + properties: { + key: Property.ShortText({ + displayName: 'Key', + description: 'Enter the key for the custom variable', + required: true, + }), + value: Property.ShortText({ + displayName: 'Value', + description: 'Enter the value for the custom variable', + required: true, + }), + }, + required: false, + defaultValue: [], + }), + }, + async run(context) { + const { campaignId, leadId, email, firstName, lastName, customVariables } = + context.propsValue; + + if (!campaignId || !leadId) { + throw new Error('Campaign ID and Lead ID are required.'); + } + + // Safely cast customVariables to CustomVariable[], default to an empty array if undefined + const customVariablesArray: CustomVariable[] = (customVariables || + []) as CustomVariable[]; + + // Process the custom variables into a key-value object for the lead attributes + const customVariablesObject: Record = {}; + customVariablesArray.forEach((variable: CustomVariable) => { + customVariablesObject[variable.key] = variable.value; + }); + + // Include the custom variables in the lead update request + const url = `${reachinboxCommon.baseUrl}leads/update`; + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: url, + headers: { + Authorization: `Bearer ${context.auth as string}`, + 'Content-Type': 'application/json', + }, + body: { + campaignId: campaignId, + leadId: leadId, + email: email, + attributes: { + firstName: firstName || '', + lastName: lastName || '', + ...customVariablesObject, // Add custom variables dynamically + }, + }, + }); + + if (response.status === 200) { + return { + success: true, + message: response.body.message || 'Lead updated successfully.', + }; + } else { + throw new Error(`Failed to update lead: ${response.body.message}`); + } + } catch (error: any) { + throw new Error(`Failed to update lead: ${error.message}`); + } + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/common/index.ts b/packages/pieces/community/reachinbox/src/lib/common/index.ts new file mode 100644 index 0000000..c42241a --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/common/index.ts @@ -0,0 +1,95 @@ +export const reachinboxCommon = { + baseUrl: 'https://api.reachinbox.ai/api/v1/', +}; + +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +export interface Campaign { + id: number; + name: string; + coreVariables: string[]; + dailyLimit: number; + mailsSentToday: number; + accountsToUse: string[]; + tracking: boolean; + linkTracking: boolean; + isActive: boolean; + hasStarted: boolean; + lastEmailUsed: number; + delay: number; + randomDelay: number; + stopOnReply: boolean; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + sent: number; + totalOpens: string | null; + totalUniqueOpen: string; + totalReplies: string; + emails: { + sent: number; + totalOpens: number; + totalReplies: number; + }; + status: string; +} + +// Fetch campaigns from the ReachInbox API +export const fetchCampaigns = async (auth: string): Promise => { + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.reachinbox.ai/api/v1/campaigns/all?sort=newest&offset=0&limit=50', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + + return response.body?.data?.rows || []; + } catch (error) { + console.error('Error fetching campaigns:', error); + throw new Error('Failed to fetch campaigns.'); + } +}; + +// Define the structure for adding leads +export interface AddLeadsRequestBody { + campaignId: string; + leads: { + email: string; + firstName: string; + lastName?: string; + }[]; + newCoreVariables: string[]; + duplicates: any[]; +} + +// Define the response structure for adding leads +export interface AddLeadsResponse { + success: boolean; + message: string; + leadCount: number; +} + +// Add leads to a campaign +export const addLeadsToCampaign = async ( + auth: string, + body: AddLeadsRequestBody +): Promise => { + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.reachinbox.ai/api/v1/leads/add', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${auth}`, + }, + body, + }); + + return response.body; + } catch (error) { + console.error('Error adding leads:', error); + throw new Error('Failed to add leads to campaign.'); + } +}; diff --git a/packages/pieces/community/reachinbox/src/lib/triggers/campaign-completed.ts b/packages/pieces/community/reachinbox/src/lib/triggers/campaign-completed.ts new file mode 100644 index 0000000..478c6a1 --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/triggers/campaign-completed.ts @@ -0,0 +1,58 @@ +import { + createTrigger, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; + +const campaignCompletedMessage = ` + Follow the below steps: + + 1. Login to the ReachInbox dashboard. + 2. Go to the "Profile" section and navigate to the "Settings" tab. + 3. Click on the "Integrations" and go to the "Webhooks". Click on the "Add Webhook" button. + 4. Copy the following webhook URL and paste it into the "Webhook URL" field. + \`\`\`text + {{webhookUrl}} + \`\`\` + + 5. Select the event type as "Campaign Completed". + 6. Click on the "Test Trigger" button to simulate a test and capture the webhook response here. + `; + +export const campaignCompleted = createTrigger({ + name: 'campaignCompleted', + displayName: 'Campaign Completed', + description: 'Triggers when a campaign is completed.', + props: { + markdown: Property.MarkDown({ + value: campaignCompletedMessage, + }), + }, + sampleData: { + email_id: 1, + lead_id: 1, + lead_email: 'recipient@example.com', + email_account: 'sender@example.com', + step_number: 1, + message_id: '', + timestamp: '2024-03-18T08:15:51.000Z', + campaign_id: 1, + campaign_name: 'Test Name', + event: 'SEQUENCE_COMPLETED', + user_webhook_id: '1', + lead_first_name: 'Lead First Name', + lead_last_name: 'Lead Last Name', + email_sent_body: 'Sent Email body', + email_replied_body: 'Sent Replied body', + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + // Implement webhook subscription logic here + }, + async onDisable(context) { + // Implement webhook unsubscription logic here + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/triggers/email-bounced.ts b/packages/pieces/community/reachinbox/src/lib/triggers/email-bounced.ts new file mode 100644 index 0000000..bd414f2 --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/triggers/email-bounced.ts @@ -0,0 +1,59 @@ +import { + createTrigger, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; + +const emailBouncedMessage = ` + + Follow the below steps: + + 1. Login to the ReachInbox dashboard. + 2. Go to the "Profile" section and navigate to the "Settings" tab. + 3. Click on the "Integrations" and go to the "Webhooks". Click on the "Add Webhook" button. + 4. Copy the following webhook URL and paste it into the "Webhook URL" field. + \`\`\`text + {{webhookUrl}} + \`\`\` + + 5. Select the event type as "Email Bounced". + 6. Click on the "Test Trigger" button to simulate a test and capture the webhook response here. + `; + +export const emailBounced = createTrigger({ + name: 'emailBounced', + displayName: 'Email Bounced', + description: 'Triggers when an email is bounced.', + props: { + markdown: Property.MarkDown({ + value: emailBouncedMessage, + }), + }, + sampleData: { + email_id: 1, + lead_id: 1, + lead_email: 'recipient@example.com', + email_account: 'sender@example.com', + step_number: 1, + message_id: '', + timestamp: '2024-03-18T08:15:51.000Z', + campaign_id: 1, + campaign_name: 'Test Name', + event: 'EMAIL_BOUNCED', + user_webhook_id: '1', + lead_first_name: 'Lead First Name', + lead_last_name: 'Lead Last Name', + email_sent_body: 'Sent Email body', + email_replied_body: 'Sent Replied body', + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + // Implement webhook subscription logic here + }, + async onDisable(context) { + // Implement webhook unsubscription logic here + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/triggers/email-opened.ts b/packages/pieces/community/reachinbox/src/lib/triggers/email-opened.ts new file mode 100644 index 0000000..691209c --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/triggers/email-opened.ts @@ -0,0 +1,58 @@ +import { + createTrigger, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; + +const emailOpenedMessage = ` + Follow the below steps: + + 1. Login to the ReachInbox dashboard. + 2. Go to the "Profile" section and navigate to the "Settings" tab. + 3. Click on the "Integrations" and go to the "Webhooks". Click on the "Add Webhook" button. + 4. Copy the following webhook URL and paste it into the "Webhook URL" field. + \`\`\`text + {{webhookUrl}} + \`\`\` + + 5. Select the event type as "Email Opened". + 6. Click on the "Test Trigger" button to simulate a test and capture the webhook response here. + `; + +export const emailOpened = createTrigger({ + name: 'emailOpened', + displayName: 'Email Opened', + description: 'Triggers when an email is opened.', + props: { + markdown: Property.MarkDown({ + value: emailOpenedMessage, + }), + }, + sampleData: { + email_id: 1, + lead_id: 1, + lead_email: 'recipient@example.com', + email_account: 'sender@example.com', + step_number: 1, + message_id: '', + timestamp: '2024-03-18T08:15:51.000Z', + campaign_id: 1, + campaign_name: 'Test Name', + event: 'EMAIL_OPENED', + user_webhook_id: '1', + lead_first_name: 'Lead First Name', + lead_last_name: 'Lead Last Name', + email_sent_body: 'Sent Email body', + email_replied_body: 'Sent Replied body', + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + // Implement webhook subscription logic here + }, + async onDisable(context) { + // Implement webhook unsubscription logic here + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/triggers/email-sent.ts b/packages/pieces/community/reachinbox/src/lib/triggers/email-sent.ts new file mode 100644 index 0000000..bac8c74 --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/triggers/email-sent.ts @@ -0,0 +1,65 @@ +import { + createTrigger, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; + +const message = ` + +Follow the below steps: + +1. Login to the ReachInbox dashboard. +2. Go to the "Profile" section and navigate to the "Settings" tab. +3. Click on the "Integrations" and go to the "Webhooks". Click on the "Add Webhook" button. +4. Copy the following webhook URL and paste it into the "Webhook URL" field. + \`\`\`text + {{webhookUrl}} + \`\`\` + +5. Select the event type as "Email Sent". +6. Click on the "Test Trigger" button to simulate a test and capture the webhook response here. +`; + +export const emailSent = createTrigger({ + name: 'emailSent', + displayName: 'Email Sent', + description: 'Triggers when an email is successfully sent.', + props: { + markdown: Property.MarkDown({ + value: message, + }), + }, + sampleData: { + email_id: 1, + lead_id: 1, + lead_email: 'recipient@example.com', + email_account: 'sender@example.com', + step_number: 1, + message_id: '', + timestamp: '2024-03-18T08:15:51.000Z', + campaign_id: 1, + campaign_name: 'Test Name', + event: 'EMAIL_SENT', + user_webhook_id: '1', + lead_first_name: 'Lead First Name', + lead_last_name: 'Lead Last Name', + email_sent_body: 'Sent Email body', + email_replied_body: 'Sent Replied body', + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + // Here you would implement logic to enable the webhook with the external service + // Possibly create a webhook subscription by sending the webhook URL (context.webhookUrl) to the external service + }, + + async onDisable(context) { + // Here you would implement logic to disable or delete the webhook subscription from the external service + // Likely sending a DELETE request to the service to remove the webhook + }, + + async run(context) { + // This will handle the incoming webhook event and return the payload data + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/triggers/lead-interested.ts b/packages/pieces/community/reachinbox/src/lib/triggers/lead-interested.ts new file mode 100644 index 0000000..0142e42 --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/triggers/lead-interested.ts @@ -0,0 +1,62 @@ +import { + createTrigger, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; + +const leadInterestedMessage = ` + + + Follow the below steps: + + + 1. Login to the ReachInbox dashboard. + 2. Go to the "Profile" section and navigate to the "Settings" tab. + 3. Click on the "Integrations" and go to the "Webhooks". Click on the "Add Webhook" button. + 4. Copy the following webhook URL and paste it into the "Webhook URL" field. + \`\`\`text + {{webhookUrl}} + \`\`\` + + + 5. Select the event type as "Lead Interested". + 6. Click on the "Test Trigger" button to simulate a test and capture the webhook response here. + `; + +export const leadInterested = createTrigger({ + name: 'leadInterested', + displayName: 'Lead Interested', + description: 'Triggers when a lead is set to interested.', + props: { + markdown: Property.MarkDown({ + value: leadInterestedMessage, + }), + }, + sampleData: { + email_id: 1, + lead_id: 1, + lead_email: 'recipient@example.com', + email_account: 'sender@example.com', + step_number: 1, + message_id: '', + timestamp: '2024-03-18T08:15:51.000Z', + campaign_id: 1, + campaign_name: 'Test Name', + event: 'LEAD_INTERESTED', + user_webhook_id: '1', + lead_first_name: 'Lead First Name', + lead_last_name: 'Lead Last Name', + email_sent_body: 'Sent Email body', + email_replied_body: 'Sent Replied body', + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + // Implement webhook subscription logic here + }, + async onDisable(context) { + // Implement webhook unsubscription logic here + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/triggers/lead-not-interested.ts b/packages/pieces/community/reachinbox/src/lib/triggers/lead-not-interested.ts new file mode 100644 index 0000000..b1e38d2 --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/triggers/lead-not-interested.ts @@ -0,0 +1,60 @@ +import { + createTrigger, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; + +const leadNotInterestedMessage = ` + + Follow the below steps: + + 1. Login to the ReachInbox dashboard. + 2. Go to the "Profile" section and navigate to the "Settings" tab. + 3. Click on the "Integrations" and go to the "Webhooks". Click on the "Add Webhook" button. + 4. Copy the following webhook URL and paste it into the "Webhook URL" field. + \`\`\`text + {{webhookUrl}} + \`\`\` + + + 5. Select the event type as "Lead Not Interested". + 6. Click on the "Test Trigger" button to simulate a test and capture the webhook response here. + `; + +export const leadNotInterested = createTrigger({ + name: 'leadNotInterested', + displayName: 'Lead Not Interested', + description: 'Triggers when a lead is set to not interested.', + props: { + markdown: Property.MarkDown({ + value: leadNotInterestedMessage, + }), + }, + sampleData: { + email_id: 1, + lead_id: 1, + lead_email: 'recipient@example.com', + email_account: 'sender@example.com', + step_number: 1, + message_id: '', + timestamp: '2024-03-18T08:15:51.000Z', + campaign_id: 1, + campaign_name: 'Test Name', + event: 'LEAD_NOT_INTERESTED', + user_webhook_id: '1', + lead_first_name: 'Lead First Name', + lead_last_name: 'Lead Last Name', + email_sent_body: 'Sent Email body', + email_replied_body: 'Sent Replied body', + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + // Implement webhook subscription logic here + }, + async onDisable(context) { + // Implement webhook unsubscription logic here + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/reachinbox/src/lib/triggers/reply-received.ts b/packages/pieces/community/reachinbox/src/lib/triggers/reply-received.ts new file mode 100644 index 0000000..2a93c2b --- /dev/null +++ b/packages/pieces/community/reachinbox/src/lib/triggers/reply-received.ts @@ -0,0 +1,59 @@ +import { + createTrigger, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; + +const replyReceivedMessage = ` + + Follow the below steps: + + 1. Login to the ReachInbox dashboard. + 2. Go to the "Profile" section and navigate to the "Settings" tab. + 3. Click on the "Integrations" and go to the "Webhooks". Click on the "Add Webhook" button. + 4. Copy the following webhook URL and paste it into the "Webhook URL" field. + \`\`\`text + {{webhookUrl}} + \`\`\` + + 5. Select the event type as "Reply Received". + 6. Click on the "Test Trigger" button to simulate a test and capture the webhook response here. + `; + +export const replyReceived = createTrigger({ + name: 'replyReceived', + displayName: 'Reply Received', + description: 'Triggers when a reply to an email is received.', + props: { + markdown: Property.MarkDown({ + value: replyReceivedMessage, + }), + }, + sampleData: { + email_id: 1, + lead_id: 1, + lead_email: 'recipient@example.com', + email_account: 'sender@example.com', + step_number: 1, + message_id: '', + timestamp: '2024-03-18T08:15:51.000Z', + campaign_id: 1, + campaign_name: 'Test Name', + event: 'REPLY_RECEIVED', + user_webhook_id: '1', + lead_first_name: 'Lead First Name', + lead_last_name: 'Lead Last Name', + email_sent_body: 'Sent Email body', + email_replied_body: 'Sent Replied body', + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + // Implement webhook subscription logic here + }, + async onDisable(context) { + // Implement webhook unsubscription logic here + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/reachinbox/tsconfig.json b/packages/pieces/community/reachinbox/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/reachinbox/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/reachinbox/tsconfig.lib.json b/packages/pieces/community/reachinbox/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/reachinbox/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/reddit/.eslintrc.json b/packages/pieces/community/reddit/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/reddit/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/reddit/README.md b/packages/pieces/community/reddit/README.md new file mode 100644 index 0000000..73e043c --- /dev/null +++ b/packages/pieces/community/reddit/README.md @@ -0,0 +1,7 @@ +# pieces-reddit + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-reddit` to build the library. diff --git a/packages/pieces/community/reddit/package.json b/packages/pieces/community/reddit/package.json new file mode 100644 index 0000000..d25f26f --- /dev/null +++ b/packages/pieces/community/reddit/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-reddit", + "version": "0.0.1" +} diff --git a/packages/pieces/community/reddit/project.json b/packages/pieces/community/reddit/project.json new file mode 100644 index 0000000..755a5a0 --- /dev/null +++ b/packages/pieces/community/reddit/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-reddit", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/reddit/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/reddit", + "tsConfig": "packages/pieces/community/reddit/tsconfig.lib.json", + "packageJson": "packages/pieces/community/reddit/package.json", + "main": "packages/pieces/community/reddit/src/index.ts", + "assets": [ + "packages/pieces/community/reddit/*.md", + { + "input": "packages/pieces/community/reddit/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/reddit/src/index.ts b/packages/pieces/community/reddit/src/index.ts new file mode 100644 index 0000000..1267b5b --- /dev/null +++ b/packages/pieces/community/reddit/src/index.ts @@ -0,0 +1,75 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece, PieceAuth, OAuth2AuthorizationMethod, Property, OAuth2PropertyValue } from "@activepieces/pieces-framework"; +import { retrieveRedditPost } from './lib/actions/retrieve-reddit-post'; +import { getRedditPostDetails } from './lib/actions/get-reddit-post-details'; +import { createRedditPost } from './lib/actions/create-reddit-post'; +import { createRedditComment } from './lib/actions/create-reddit-comment'; +import { fetchPostComments} from './lib/actions/fetch-post-comments'; +import { editRedditPost } from './lib/actions/edit-reddit-post'; +import { editRedditComment } from './lib/actions/edit-reddit-comment'; +import { deleteRedditPost } from './lib/actions/delete-reddit-post'; +import { deleteRedditComment } from './lib/actions/delete-reddit-comment'; +import { PieceCategory } from '@activepieces/shared'; +import { OAuth2GrantType } from '@activepieces/shared'; + +const markdown = ` +To obtain your Reddit API credentials: + +1. Go to https://www.reddit.com/prefs/apps. +2. Click "create another app..." at the bottom. +3. Select "script" as the app type. +4. Fill in the required information: + - name: Your app name + - description: Brief description + - about url: Can be left blank + - redirect uri: as shown in Redirect URL field +5. Click "create app". +6. Note down the client ID (under the app name) and client secret. +`; + +export const redditAuth = PieceAuth.OAuth2({ + description: markdown, + authUrl: 'https://www.reddit.com/api/v1/authorize', + tokenUrl: 'https://www.reddit.com/api/v1/access_token', + required: true, + scope: ['identity', 'read', 'submit', 'edit', 'history', 'flair'], + authorizationMethod: OAuth2AuthorizationMethod.HEADER, + extra: { + grantType: OAuth2GrantType.AUTHORIZATION_CODE, + responseType: 'code' + } +}); + +export const reddit = createPiece({ + displayName: 'Reddit', + description: 'Interact with Reddit - fetch and submit posts.', + logoUrl: 'https://cdn.activepieces.com/pieces/reddit.png', + minimumSupportedRelease: '0.36.1', + categories: [PieceCategory.COMMUNICATION], + authors: ['bhaviksingla1403'], + auth: redditAuth, + actions: [ + retrieveRedditPost, + getRedditPostDetails, + createRedditPost, + createRedditComment, + fetchPostComments, + editRedditPost, + editRedditComment, + deleteRedditPost, + deleteRedditComment, + createCustomApiCallAction({ + auth: redditAuth, + baseUrl: () => { + return 'https://oauth.reddit.com'; + }, + authMapping: async (auth) => { + return { + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + 'User-Agent': 'ActivePieces/1.0.0' + }; + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/reddit/src/lib/actions/create-reddit-comment.ts b/packages/pieces/community/reddit/src/lib/actions/create-reddit-comment.ts new file mode 100644 index 0000000..cb865f3 --- /dev/null +++ b/packages/pieces/community/reddit/src/lib/actions/create-reddit-comment.ts @@ -0,0 +1,57 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { redditAuth } from '../../index'; + +export const createRedditComment = createAction({ + auth: redditAuth, + name: 'createRedditComment', + displayName: 'Create Comment', + description: 'Comment on a Reddit post or reply to a comment.', + props: { + parent_id: Property.ShortText({ + displayName: 'Parent ID', + description: 'ID of the post (t3_*) or comment (t1_*) to reply to.', + required: true, + }), + content: Property.LongText({ + displayName: 'Comment Text', + description: 'Text of the comment.', + required: true, + }), + }, + async run(context) { + let parentId = context.propsValue.parent_id.trim(); + + // If it's just a post ID, prefix it + if (!parentId.startsWith('t1_') && !parentId.startsWith('t3_')) { + parentId = `t3_${parentId}`; + } + + const url = 'https://oauth.reddit.com/api/comment'; + const payload = new URLSearchParams({ + thing_id: parentId, + text: context.propsValue.content, + api_type: 'json', + }); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url, + headers: { + 'Authorization': `Bearer ${context.auth.access_token}`, + 'User-Agent': 'ActivePieces Reddit Client', + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: payload.toString(), + }); + + if (response.status !== 200) { + return { + error: `Failed to create comment: ${response.status}`, + details: response.body, + }; + } + + return response.body; + }, +}); diff --git a/packages/pieces/community/reddit/src/lib/actions/create-reddit-post.ts b/packages/pieces/community/reddit/src/lib/actions/create-reddit-post.ts new file mode 100644 index 0000000..a9b4843 --- /dev/null +++ b/packages/pieces/community/reddit/src/lib/actions/create-reddit-post.ts @@ -0,0 +1,60 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { redditAuth } from '../../index'; + +export const createRedditPost = createAction({ + auth: redditAuth, + name: 'createRedditPost', + displayName: 'Create Post', + description: 'Submit a new self (text) post to a subreddit.', + props: { + subreddit: Property.ShortText({ + displayName: 'Subreddit', + description: 'The subreddit to post in (without r/).', + required: true, + }), + title: Property.ShortText({ + displayName: 'Title', + description: 'Title of the Reddit post.', + required: true, + }), + content: Property.LongText({ + displayName: 'Content', + description: 'Text content of the post.', + required: true, + }), + }, + async run(context) { + const { subreddit, title, content } = context.propsValue; + + const url = 'https://oauth.reddit.com/api/submit'; + + const payload = new URLSearchParams({ + api_type: 'json', + sr: subreddit, + title, + text: content, + kind: 'self', + }); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url, + headers: { + 'Authorization': `Bearer ${context.auth.access_token}`, + 'User-Agent': 'ActivePieces Reddit Client', + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: payload.toString(), + }); + + if (response.status !== 200) { + return { + error: `Failed to create post: ${response.status}`, + details: response.body, + }; + } + + return response.body; + }, +}); diff --git a/packages/pieces/community/reddit/src/lib/actions/delete-reddit-comment.ts b/packages/pieces/community/reddit/src/lib/actions/delete-reddit-comment.ts new file mode 100644 index 0000000..875bfa7 --- /dev/null +++ b/packages/pieces/community/reddit/src/lib/actions/delete-reddit-comment.ts @@ -0,0 +1,49 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { redditAuth } from '../../index'; + +export const deleteRedditComment = createAction({ + auth: redditAuth, + name: 'deleteRedditComment', + displayName: 'Delete Comment', + description: 'Delete a specific Reddit comment by ID.', + props: { + comment_id: Property.ShortText({ + displayName: 'Comment ID', + description: 'ID of the Reddit comment to delete (e.g., "def456" or "t1_def456").', + required: true, + }), + }, + async run(context) { + let commentId = context.propsValue.comment_id.trim(); + if (commentId.startsWith('t1_')) { + commentId = commentId.slice(3); + } + + const url = 'https://oauth.reddit.com/api/del'; + const payload = new URLSearchParams({ + api_type: 'json', + id: `t1_${commentId}`, + }); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url, + headers: { + 'Authorization': `Bearer ${context.auth.access_token}`, + 'User-Agent': 'ActivePieces Reddit Client', + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: payload.toString(), + }); + + if (response.status !== 200) { + return { + error: `Failed to delete comment: ${response.status}`, + details: response.body, + }; + } + + return { success: true, response: response.body }; + }, +}); diff --git a/packages/pieces/community/reddit/src/lib/actions/delete-reddit-post.ts b/packages/pieces/community/reddit/src/lib/actions/delete-reddit-post.ts new file mode 100644 index 0000000..5bbcc73 --- /dev/null +++ b/packages/pieces/community/reddit/src/lib/actions/delete-reddit-post.ts @@ -0,0 +1,49 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { redditAuth } from '../../index'; + +export const deleteRedditPost = createAction({ + auth: redditAuth, + name: 'deleteRedditPost', + displayName: 'Delete Post', + description: 'Delete a specific Reddit post by ID.', + props: { + post_id: Property.ShortText({ + displayName: 'Post ID', + description: 'ID of the Reddit post to delete (e.g., "abc123" or "t3_abc123").', + required: true, + }), + }, + async run(context) { + let postId = context.propsValue.post_id.trim(); + if (postId.startsWith('t3_')) { + postId = postId.slice(3); + } + + const url = 'https://oauth.reddit.com/api/del'; + const payload = new URLSearchParams({ + api_type: 'json', + id: `t3_${postId}`, + }); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url, + headers: { + 'Authorization': `Bearer ${context.auth.access_token}`, + 'User-Agent': 'ActivePieces Reddit Client', + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: payload.toString(), + }); + + if (response.status !== 200) { + return { + error: `Failed to delete post: ${response.status}`, + details: response.body, + }; + } + + return { success: true, response: response.body }; + }, +}); diff --git a/packages/pieces/community/reddit/src/lib/actions/edit-reddit-comment.ts b/packages/pieces/community/reddit/src/lib/actions/edit-reddit-comment.ts new file mode 100644 index 0000000..063c356 --- /dev/null +++ b/packages/pieces/community/reddit/src/lib/actions/edit-reddit-comment.ts @@ -0,0 +1,55 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { redditAuth } from '../../index'; + +export const editRedditComment = createAction({ + auth: redditAuth, + name: 'editRedditComment', + displayName: 'Edit Comment', + description: 'Edits the content of an existing Reddit comment.', + props: { + comment_id: Property.ShortText({ + displayName: 'Comment ID', + description: 'ID of the Reddit comment to edit (e.g., "def456" or "t1_def456").', + required: true, + }), + content: Property.LongText({ + displayName: 'New Comment Content', + description: 'Updated text content for the comment.', + required: true, + }), + }, + async run(context) { + let commentId = context.propsValue.comment_id.trim(); + if (commentId.startsWith('t1_')) { + commentId = commentId.slice(3); + } + + const url = 'https://oauth.reddit.com/api/editusertext'; + const payload = new URLSearchParams({ + api_type: 'json', + thing_id: `t1_${commentId}`, + text: context.propsValue.content, + }); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url, + headers: { + 'Authorization': `Bearer ${context.auth.access_token}`, + 'User-Agent': 'ActivePieces Reddit Client', + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: payload.toString(), + }); + + if (response.status !== 200) { + return { + error: `Failed to edit comment: ${response.status}`, + details: response.body, + }; + } + + return response.body; + }, +}); diff --git a/packages/pieces/community/reddit/src/lib/actions/edit-reddit-post.ts b/packages/pieces/community/reddit/src/lib/actions/edit-reddit-post.ts new file mode 100644 index 0000000..03602f5 --- /dev/null +++ b/packages/pieces/community/reddit/src/lib/actions/edit-reddit-post.ts @@ -0,0 +1,55 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { redditAuth } from '../../index'; + +export const editRedditPost = createAction({ + auth: redditAuth, + name: 'editRedditPost', + displayName: 'Edit Post', + description: 'Edits the content of an existing Reddit post.', + props: { + post_id: Property.ShortText({ + displayName: 'Post ID', + description: 'ID of the Reddit post to edit (e.g., "abc123" or "t3_abc123").', + required: true, + }), + content: Property.LongText({ + displayName: 'New Post Content', + description: 'Updated text content for the post.', + required: true, + }), + }, + async run(context) { + let postId = context.propsValue.post_id.trim(); + if (postId.startsWith('t3_')) { + postId = postId.slice(3); + } + + const url = 'https://oauth.reddit.com/api/editusertext'; + const payload = new URLSearchParams({ + api_type: 'json', + thing_id: `t3_${postId}`, + text: context.propsValue.content, + }); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url, + headers: { + 'Authorization': `Bearer ${context.auth.access_token}`, + 'User-Agent': 'ActivePieces Reddit Client', + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: payload.toString(), + }); + + if (response.status !== 200) { + return { + error: `Failed to edit post: ${response.status}`, + details: response.body, + }; + } + + return response.body; + }, +}); diff --git a/packages/pieces/community/reddit/src/lib/actions/fetch-post-comments.ts b/packages/pieces/community/reddit/src/lib/actions/fetch-post-comments.ts new file mode 100644 index 0000000..4ee99db --- /dev/null +++ b/packages/pieces/community/reddit/src/lib/actions/fetch-post-comments.ts @@ -0,0 +1,93 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { redditAuth } from '../../index'; + +export const fetchPostComments = createAction({ + auth: redditAuth, + name: 'fetchPostComments', + displayName: 'Fetch Post Comments', + description: 'Fetch comments from a specific Reddit post.', + props: { + post_id: Property.ShortText({ + displayName: 'Post ID', + description: 'The ID of the Reddit post (e.g. "abc123" or "t3_abc123").', + required: true, + }), + sort: Property.StaticDropdown({ + displayName: 'Sort By', + description: 'Sorting method for comments', + defaultValue: 'new', + required: false, + options: { + options: [ + { label: 'New', value: 'new' }, + { label: 'Top', value: 'top' }, + { label: 'Hot', value: 'hot' }, + { label: 'Best', value: 'best' }, + { label: 'Old', value: 'old' }, + { label: 'Controversial', value: 'controversial' }, + ], + }, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Maximum number of top-level comments to fetch', + defaultValue: 10, + required: false, + }), + }, + async run(context) { + let postId = context.propsValue.post_id.trim(); + if (postId.startsWith('t3_')) { + postId = postId.slice(3); + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://oauth.reddit.com/comments/${postId}`, + headers: { + 'Authorization': `Bearer ${context.auth.access_token}`, + 'User-Agent': 'ActivePieces Reddit Client', + 'Content-Type': 'application/json', + }, + queryParams: { + sort: context.propsValue.sort ?? 'new', + limit: (context.propsValue.limit ?? 10).toString(), + }, + }); + + if (response.status !== 200) { + return { + error: `Failed to retrieve comments: ${response.status}`, + details: response.body, + }; + } + + function processComments(comments: any[]): any[] { + return comments + .filter(c => c.kind === 't1') + .map(c => { + const d = c.data; + return { + id: d.id, + author: d.author, + body: d.body, + score: d.score, + created_utc: d.created_utc, + permalink: d.permalink, + edited: d.edited, + is_submitter: d.is_submitter, + stickied: d.stickied, + replies: d.replies && typeof d.replies === 'object' + ? processComments(d.replies.data.children) + : [], + }; + }); + } + + const data = response.body; + const commentsTree = data[1]?.data?.children ?? []; + + return processComments(commentsTree); + }, +}); diff --git a/packages/pieces/community/reddit/src/lib/actions/get-reddit-post-details.ts b/packages/pieces/community/reddit/src/lib/actions/get-reddit-post-details.ts new file mode 100644 index 0000000..1d8d148 --- /dev/null +++ b/packages/pieces/community/reddit/src/lib/actions/get-reddit-post-details.ts @@ -0,0 +1,87 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { redditAuth } from '../../index'; + +export const getRedditPostDetails = createAction({ + auth: redditAuth, + name: 'getRedditPostDetails', + displayName: 'Get Post Details', + description: 'Fetch detailed information about a specific Reddit post using its ID.', + props: { + post_id: Property.ShortText({ + displayName: 'Post ID', + description: 'The ID of the Reddit post (e.g. "t3_abc123" or "abc123")', + required: true, + }), + }, + async run(context) { + let postId = context.propsValue.post_id.trim(); + if (postId.startsWith('t3_')) { + postId = postId.slice(3); + } + + const url = `https://oauth.reddit.com/api/info?id=t3_${postId}`; + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url, + headers: { + 'Authorization': `Bearer ${context.auth.access_token}`, + 'User-Agent': 'ActivePieces Reddit Client', + 'Content-Type': 'application/json', + }, + timeout: 5000, + }); + + if (response.status !== 200) { + return { + error: `Failed to retrieve post details: ${response.status}`, + details: response.body, + }; + } + + const children = response.body?.data?.children ?? []; + + if (children.length === 0) { + return { error: 'No post found with the given ID' }; + } + + const data = children[0].data; + + const result: Record = { + id: data.id, + title: data.title, + author: data.author, + author_fullname: data.author_fullname, + subreddit: data.subreddit, + subreddit_id: data.subreddit_id, + selftext: data.selftext, + selftext_html: data.selftext_html, + score: data.score, + upvote_ratio: data.upvote_ratio, + created_utc: data.created_utc, + permalink: data.permalink, + url: data.url, + domain: data.domain, + num_comments: data.num_comments, + is_self: data.is_self, + is_video: data.is_video, + is_original_content: data.is_original_content, + over_18: data.over_18, + spoiler: data.spoiler, + locked: data.locked, + stickied: data.stickied, + post_hint: data.post_hint, + }; + + if (data.media) { + result['media'] = data.media; + } + + if (data.gallery_data) { + result['gallery_data'] = data.gallery_data; + } + + return result; + }, +}); diff --git a/packages/pieces/community/reddit/src/lib/actions/retrieve-reddit-post.ts b/packages/pieces/community/reddit/src/lib/actions/retrieve-reddit-post.ts new file mode 100644 index 0000000..ca86ad6 --- /dev/null +++ b/packages/pieces/community/reddit/src/lib/actions/retrieve-reddit-post.ts @@ -0,0 +1,66 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { redditAuth } from '../../'; + +export const retrieveRedditPost = createAction({ + auth: redditAuth, + name: 'retrieveRedditPost', + displayName: 'Retrieve Post', + description: 'Fetch top posts in a subreddit with optional size limit.', + props: { + post_category: Property.StaticDropdown({ + displayName: 'Post Category', + description: 'Select the category of posts to retrieve', + required: true, + defaultValue: 'hot', + options: { + options: [ + { label: 'Hot', value: 'hot' }, + { label: 'New', value: 'new' }, + { label: 'Top', value: 'top' }, + { label: 'Rising', value: 'rising' }, + { label: 'Controversial', value: 'controversial' }, + ], + }, + }), + subreddit: Property.ShortText({ + displayName: 'Subreddit', + description: 'The subreddit to fetch posts from', + required: true, + }), + size: Property.Number({ + displayName: 'Number of Posts', + description: 'Number of posts to fetch (max 100)', + required: false, + defaultValue: 10, + }), + }, + async run(context) { + const baseUrl = `https://oauth.reddit.com/r/${context.propsValue.subreddit}/${context.propsValue.post_category}`; + const limit = context.propsValue.size || 10; + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: baseUrl, + queryParams: { + limit: limit.toString(), + }, + headers: { + 'Authorization': `Bearer ${context.auth.access_token}`, + 'User-Agent': 'ActivePieces Reddit Client', + 'Content-Type': 'application/json', + }, + timeout: 5000, + }); + + if (response.status !== 200) { + throw new Error(`Reddit API error: ${response.status} ${JSON.stringify(response.body)}`); + } + + return response.body; + }, +}); diff --git a/packages/pieces/community/reddit/tsconfig.json b/packages/pieces/community/reddit/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/reddit/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/reddit/tsconfig.lib.json b/packages/pieces/community/reddit/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/reddit/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/reoon-verifier/.eslintrc.json b/packages/pieces/community/reoon-verifier/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/reoon-verifier/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/reoon-verifier/README.md b/packages/pieces/community/reoon-verifier/README.md new file mode 100644 index 0000000..72b2f43 --- /dev/null +++ b/packages/pieces/community/reoon-verifier/README.md @@ -0,0 +1,7 @@ +# pieces-reoon-verifier + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-reoon-verifier` to build the library. diff --git a/packages/pieces/community/reoon-verifier/package.json b/packages/pieces/community/reoon-verifier/package.json new file mode 100644 index 0000000..ab66c16 --- /dev/null +++ b/packages/pieces/community/reoon-verifier/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-reoon-verifier", + "version": "0.0.2" +} diff --git a/packages/pieces/community/reoon-verifier/project.json b/packages/pieces/community/reoon-verifier/project.json new file mode 100644 index 0000000..8562a77 --- /dev/null +++ b/packages/pieces/community/reoon-verifier/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-reoon-verifier", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/reoon-verifier/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/reoon-verifier", + "tsConfig": "packages/pieces/community/reoon-verifier/tsconfig.lib.json", + "packageJson": "packages/pieces/community/reoon-verifier/package.json", + "main": "packages/pieces/community/reoon-verifier/src/index.ts", + "assets": [ + "packages/pieces/community/reoon-verifier/*.md", + { + "input": "packages/pieces/community/reoon-verifier/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-reoon-verifier {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/reoon-verifier/src/index.ts b/packages/pieces/community/reoon-verifier/src/index.ts new file mode 100644 index 0000000..8715aec --- /dev/null +++ b/packages/pieces/community/reoon-verifier/src/index.ts @@ -0,0 +1,57 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { verifyEmail } from './lib/actions/verify-email'; +import { verifySingleEmail } from './lib/common/send-util'; +import { HttpError } from '@activepieces/pieces-common'; +import { bulkEmailVerification } from './lib/actions/bulk-email-verification'; +import { PieceCategory } from '@activepieces/shared'; +import { bulkVerificationResult } from './lib/actions/bulk-email-verification-status'; + +const description = ` +To obtain a Reoon API key, follow these steps: +1. Navigate to [API Setting](https://emailverifier.reoon.com/api-settings). +2. Click on the **Create New API Key** button. +3. Enter a valid title and copy the API key. +`; + +export const reoonEmailVerifyAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description, + validate: async (auth) => { + try { + await verifySingleEmail('placeholder@test', 'quick', auth.auth); + } catch (e) { + const status = (e as HttpError).response.status; + + // Other 4xx status codes mean the API key is valid + if (status === 401) { + return { + valid: false, + error: 'Your API key is invalid or has expired', + }; + } else if (status >= 500) { + return { + valid: false, + error: 'An error occurred from the Reoon Email Verifier API', + }; + } + } + + return { + valid: true, + }; + }, +}); + +export const reoonEmailVerify = createPiece({ + displayName: 'Reoon Email Verifier', + auth: reoonEmailVerifyAuth, + minimumSupportedRelease: '0.30.0', + categories: [PieceCategory.MARKETING], + logoUrl: 'https://cdn.activepieces.com/pieces/reoon-verifier.png', + description: + 'Email validation service that cleans invalid, temporary & unsafe email addresses.', + authors: ['AnneMariel95'], + actions: [verifyEmail, bulkEmailVerification, bulkVerificationResult], + triggers: [], +}); diff --git a/packages/pieces/community/reoon-verifier/src/lib/actions/bulk-email-verification-status.ts b/packages/pieces/community/reoon-verifier/src/lib/actions/bulk-email-verification-status.ts new file mode 100644 index 0000000..67c4899 --- /dev/null +++ b/packages/pieces/community/reoon-verifier/src/lib/actions/bulk-email-verification-status.ts @@ -0,0 +1,25 @@ +import { reoonEmailVerifyAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { verifyEmailsResult } from '../common/send-util'; + +export const bulkVerificationResult = createAction({ + auth: reoonEmailVerifyAuth, + name: 'bulkVerificationResult', + displayName: 'Get Bulk Verification Result', + description: 'Retrieves result of bulk verification email by task ID.', + props: { + task_id: Property.ShortText({ + displayName: 'Task ID', + description: + 'Provide the task ID for the bulk verification task. You can fetch this from the `Create Bulk Email Verification` action.', + required: true, + }), + }, + async run(context) { + const { task_id } = context.propsValue; + + const response = await verifyEmailsResult(task_id, context.auth); + + return response.body; + }, +}); diff --git a/packages/pieces/community/reoon-verifier/src/lib/actions/bulk-email-verification.ts b/packages/pieces/community/reoon-verifier/src/lib/actions/bulk-email-verification.ts new file mode 100644 index 0000000..e3f73bf --- /dev/null +++ b/packages/pieces/community/reoon-verifier/src/lib/actions/bulk-email-verification.ts @@ -0,0 +1,40 @@ +import { reoonEmailVerifyAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { verifyEmails } from '../common/send-util'; + +export const bulkEmailVerification = createAction({ + auth: reoonEmailVerifyAuth, + name: 'bulkEmailVerificationTask', + displayName: 'Create Bulk Email Verification', + description: 'Creates bulk email verification task.', + props: { + taskName: Property.ShortText({ + displayName: 'Task Name', + description: 'Name of the verification task', + required: true, + }), + emails: Property.Array({ + displayName: 'Emails', + description: + 'Emails to verify (You can also provide multiple emails separated by comma)', + required: true, + }), + }, + async run(context) { + const emails = context.propsValue.emails as string[]; + + // Each email field could be a comma separated list of emails so we need to split them + const emailsToVerify = emails.reduce( + (acc: string[], email: string) => [...acc, ...email.split(',')], + [] + ); + + const res = await verifyEmails( + emailsToVerify, + context.propsValue.taskName, + context.auth + ); + + return res.body; + }, +}); diff --git a/packages/pieces/community/reoon-verifier/src/lib/actions/verify-email.ts b/packages/pieces/community/reoon-verifier/src/lib/actions/verify-email.ts new file mode 100644 index 0000000..d3dd938 --- /dev/null +++ b/packages/pieces/community/reoon-verifier/src/lib/actions/verify-email.ts @@ -0,0 +1,44 @@ +import { reoonEmailVerifyAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { VerifyEmailMode, verifySingleEmail } from '../common/send-util'; + +export const verifyEmail = createAction({ + auth: reoonEmailVerifyAuth, + name: 'verifyEmail', + displayName: 'Verify Email', + description: 'Verify a single email', + props: { + email: Property.ShortText({ + displayName: 'Email', + description: 'Email to verify', + required: true, + }), + mode: Property.StaticDropdown({ + displayName: 'Mode', + defaultValue: 'power', + description: + 'Verification mode (Power mode is more accurate but a bit slower)', + options: { + placeholder: 'Select a mode', + options: [ + { + label: 'Quick', + value: 'quick', + }, + { + label: 'Power', + value: 'power', + }, + ], + }, + required: true, + }), + }, + async run(context) { + return verifySingleEmail( + context.propsValue.email, + context.propsValue.mode, + context.auth + ).then((res) => res.body); + }, +}); diff --git a/packages/pieces/community/reoon-verifier/src/lib/common/send-util.ts b/packages/pieces/community/reoon-verifier/src/lib/common/send-util.ts new file mode 100644 index 0000000..c292766 --- /dev/null +++ b/packages/pieces/community/reoon-verifier/src/lib/common/send-util.ts @@ -0,0 +1,41 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export type VerifyEmailMode = 'power' | 'quick'; + +export const verifySingleEmail = ( + email: string, + mode: string, + apiKey: string +) => { + return httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://emailverifier.reoon.com/api/v1/verify?email=${email}&key=${apiKey}&mode=${mode}`, + }); +}; + +export const verifyEmails = ( + emails: string[], + taskName: string, + apiKey: string +) => { + return httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://emailverifier.reoon.com/api/v1/create-bulk-verification-task`, + body: { + name: taskName, + emails, + key: apiKey, + }, + }); +}; + +export async function verifyEmailsResult(taskId: string, apiKey: string) { + return await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://emailverifier.reoon.com/api/v1/get-result-bulk-verification-task', + queryParams: { + key: apiKey, + task_id: taskId, + }, + }); +} diff --git a/packages/pieces/community/reoon-verifier/tsconfig.json b/packages/pieces/community/reoon-verifier/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/reoon-verifier/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/reoon-verifier/tsconfig.lib.json b/packages/pieces/community/reoon-verifier/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/reoon-verifier/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/resend/.eslintrc.json b/packages/pieces/community/resend/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/resend/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/resend/README.md b/packages/pieces/community/resend/README.md new file mode 100644 index 0000000..de54d89 --- /dev/null +++ b/packages/pieces/community/resend/README.md @@ -0,0 +1,7 @@ +# pieces-resend + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-resend` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/resend/package.json b/packages/pieces/community/resend/package.json new file mode 100644 index 0000000..8204914 --- /dev/null +++ b/packages/pieces/community/resend/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-resend", + "version": "0.1.4" +} \ No newline at end of file diff --git a/packages/pieces/community/resend/project.json b/packages/pieces/community/resend/project.json new file mode 100644 index 0000000..5487f05 --- /dev/null +++ b/packages/pieces/community/resend/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-resend", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/resend/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/resend", + "tsConfig": "packages/pieces/community/resend/tsconfig.lib.json", + "packageJson": "packages/pieces/community/resend/package.json", + "main": "packages/pieces/community/resend/src/index.ts", + "assets": [ + "packages/pieces/community/resend/*.md", + { + "input": "packages/pieces/community/resend/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/resend/src/index.ts b/packages/pieces/community/resend/src/index.ts new file mode 100644 index 0000000..09862a0 --- /dev/null +++ b/packages/pieces/community/resend/src/index.ts @@ -0,0 +1,31 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { sendEmail } from './lib/actions/send-email'; + +export const resendAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, +}); + +export const resend = createPiece({ + displayName: 'Resend', + description: 'Email for developers', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/resend.png', + categories: [PieceCategory.BUSINESS_INTELLIGENCE, PieceCategory.MARKETING], + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + auth: resendAuth, + actions: [ + sendEmail, + createCustomApiCallAction({ + baseUrl: () => 'https://api.resend.com', + auth: resendAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/resend/src/lib/actions/send-email.ts b/packages/pieces/community/resend/src/lib/actions/send-email.ts new file mode 100644 index 0000000..f2e5a57 --- /dev/null +++ b/packages/pieces/community/resend/src/lib/actions/send-email.ts @@ -0,0 +1,107 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpMethod, + AuthenticationType, + httpClient, +} from '@activepieces/pieces-common'; +import { resendAuth } from '../..'; + +export const sendEmail = createAction({ + auth: resendAuth, + name: 'send_email', + displayName: 'Send Email', + description: 'Send a text or HTML email', + props: { + to: Property.Array({ + displayName: 'To', + description: 'Emails of the recipients', + required: true, + }), + from_name: Property.ShortText({ + displayName: 'Sender Name', + description: 'Sender name', + required: true, + }), + from: Property.ShortText({ + displayName: 'Sender Email (From)', + description: 'Sender email', + required: true, + }), + bcc: Property.Array({ + displayName: 'BCC', + description: 'List of emails in bcc', + required: false, + }), + cc: Property.Array({ + displayName: 'CC', + description: 'List of emails in cc', + required: false, + }), + reply_to: Property.ShortText({ + displayName: 'Reply To', + description: 'Email to receive replies on (defaults to sender)', + required: false, + }), + subject: Property.ShortText({ + displayName: 'Subject', + description: undefined, + required: true, + }), + content_type: Property.Dropdown<'text' | 'html'>({ + displayName: 'Content Type', + refreshers: [], + required: true, + defaultValue: 'html', + options: async () => { + return { + disabled: false, + options: [ + { label: 'Plain Text', value: 'text' }, + { label: 'HTML', value: 'html' }, + ], + }; + }, + }), + content: Property.ShortText({ + displayName: 'Content', + description: 'HTML is only allowed if you selected HTML as type', + required: true, + }), + }, + async run(context) { + const { + to, + from, + from_name, + reply_to, + subject, + content_type, + content, + cc, + bcc, + } = context.propsValue; + const requestBody: Record = { + to, + from: from_name ? `${from_name} <${from}>` : from, + reply_to: reply_to ?? from, + cc, + bcc, + subject: subject, + }; + if (content_type === 'text') { + requestBody['text'] = content; + } else if (content_type === 'html') { + requestBody['html'] = content; + } + return await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.resend.com/emails`, + body: requestBody, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + queryParams: {}, + }); + }, +}); diff --git a/packages/pieces/community/resend/tsconfig.json b/packages/pieces/community/resend/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/resend/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/resend/tsconfig.lib.json b/packages/pieces/community/resend/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/resend/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/respaid/.eslintrc.json b/packages/pieces/community/respaid/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/respaid/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/respaid/README.md b/packages/pieces/community/respaid/README.md new file mode 100644 index 0000000..1032d6a --- /dev/null +++ b/packages/pieces/community/respaid/README.md @@ -0,0 +1,7 @@ +# pieces-respaid + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-respaid` to build the library. diff --git a/packages/pieces/community/respaid/package.json b/packages/pieces/community/respaid/package.json new file mode 100644 index 0000000..cc9d3a4 --- /dev/null +++ b/packages/pieces/community/respaid/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-respaid", + "version": "0.0.7" +} diff --git a/packages/pieces/community/respaid/project.json b/packages/pieces/community/respaid/project.json new file mode 100644 index 0000000..a2768ff --- /dev/null +++ b/packages/pieces/community/respaid/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-respaid", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/respaid/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/respaid", + "tsConfig": "packages/pieces/community/respaid/tsconfig.lib.json", + "packageJson": "packages/pieces/community/respaid/package.json", + "main": "packages/pieces/community/respaid/src/index.ts", + "assets": [ + "packages/pieces/community/respaid/*.md", + { + "input": "packages/pieces/community/respaid/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/respaid/src/index.ts b/packages/pieces/community/respaid/src/index.ts new file mode 100644 index 0000000..6506c22 --- /dev/null +++ b/packages/pieces/community/respaid/src/index.ts @@ -0,0 +1,20 @@ +import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { respaidActions } from "./lib/actions"; +import { respaidTriggers } from "./lib/triggers"; + +export const respaidAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'You can find API Key in your Respaid account', +}); + +export const respaid = createPiece({ + displayName: "Respaid", + auth: respaidAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: "https://cdn.activepieces.com/pieces/respaid.jpg", + authors: [], + actions: respaidActions, + triggers: respaidTriggers, +}); + \ No newline at end of file diff --git a/packages/pieces/community/respaid/src/lib/actions/create_new_campaign.ts b/packages/pieces/community/respaid/src/lib/actions/create_new_campaign.ts new file mode 100644 index 0000000..05c547d --- /dev/null +++ b/packages/pieces/community/respaid/src/lib/actions/create_new_campaign.ts @@ -0,0 +1,74 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { respaidAuth } from '../..'; +import { respaidCommon } from '../common'; + + +export const createNewCampaign = createAction({ + name: 'create_new_campaign', + displayName: 'Create New Campaign', + description: 'Action for creating a new campaign.', + auth: respaidAuth, + props: { + campaign_name: Property.ShortText({ + displayName: 'Campaign Name', + required: true, + }), + is_agency_collection: Property.Checkbox({ + displayName: 'Agency collection?', + required: false, + defaultValue: false, + }), + importData: Property.Json({ + displayName: 'Import Data (Array of Invoices)', + required: true, + description: `Provide an array of invoice objects with the following example structure: + [{ + "unique_identifier": "123", + "company_name": "Company XYZ", + "email": "john@example.com", + "invoice_number": "INV123", + "invoice_date": "01/01/2025", + "description": "Invoice for service" + "due_amount": 1000, + "invoicing_entity_name": "Creditor ABC", + "invoicing_entity_address": "456 Avenue, City", + "full_name": "John Doe", + "phone_number": "1234567890", + "address": "123 Street, City", + }]`, + }), + }, + async run({ auth, propsValue }) { + if (!Array.isArray(propsValue.importData)) { + throw new Error('Import Data must be an array of objects.'); + } + + const requestBody = { + campaign_name: propsValue.campaign_name, + is_agency_collection: propsValue.is_agency_collection, + import: propsValue.importData.map(invoice => ({ + unique_identifier: invoice.unique_identifier, + full_name: invoice.full_name, + company_name: invoice.company_name, + email: invoice.email, + phone_number: invoice.phone_number, + address: invoice.address, + due_amount: invoice.due_amount, + invoicing_entity_name: invoice.invoicing_entity_name, + invoicing_entity_address: invoice.invoicing_entity_address, + invoice_number: invoice.invoice_number, + invoice_date: invoice.invoice_date, + description: invoice.description, + })), + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${respaidCommon.baseUrl}/actions/import_campaign`, + headers: respaidCommon.getHeadersStructure(auth), + body: ({ type: 'active_pieces', import: JSON.stringify(requestBody) }), + }); + return res.body; + }, +}); diff --git a/packages/pieces/community/respaid/src/lib/actions/index.ts b/packages/pieces/community/respaid/src/lib/actions/index.ts new file mode 100644 index 0000000..9af150a --- /dev/null +++ b/packages/pieces/community/respaid/src/lib/actions/index.ts @@ -0,0 +1,11 @@ +import { createNewCampaign } from "./create_new_campaign"; +import { stopCollectionClientPaidDirectly } from "./stop_collection_client_paid_directly"; +import { stopCollectionForDirectInstalmentPayment } from "./stop_collection_for_direct_instalment_payment"; +import { stopCollectionForDirectPartialPayment } from "./stop_collection_for_direct_partial_payment"; + +export const respaidActions = [ + createNewCampaign, + stopCollectionClientPaidDirectly, + stopCollectionForDirectPartialPayment, + stopCollectionForDirectInstalmentPayment +] \ No newline at end of file diff --git a/packages/pieces/community/respaid/src/lib/actions/stop_collection_client_paid_directly.ts b/packages/pieces/community/respaid/src/lib/actions/stop_collection_client_paid_directly.ts new file mode 100644 index 0000000..cc47569 --- /dev/null +++ b/packages/pieces/community/respaid/src/lib/actions/stop_collection_client_paid_directly.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { respaidAuth } from '../..'; +import { respaidCommon, respaidActionsCommon } from '../common'; + + +export const stopCollectionClientPaidDirectly = createAction({ + name: 'stop_collection_client_paid_directly', + displayName: 'Stop Collection for Direct Full Payment', + description: 'Stops the collection process for a case and mark it as paid directly to the creditor.', + auth: respaidAuth, + props: { + unique_identifier: Property.ShortText({ + displayName: 'Unique Identifier', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + required: false, + }), + invoice_number: Property.ShortText({ + displayName: 'Invoice Number', + required: false, + }), + }, + async run({ auth, propsValue }) { + respaidActionsCommon.validateProps(propsValue); + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${respaidCommon.baseUrl}/actions/stop_collection_client_paid_directly`, + headers: respaidCommon.getHeadersStructure(auth), + body: respaidActionsCommon.getPayloadBodyStructure(propsValue), + }); + return res.body; + }, +}); diff --git a/packages/pieces/community/respaid/src/lib/actions/stop_collection_for_direct_instalment_payment.ts b/packages/pieces/community/respaid/src/lib/actions/stop_collection_for_direct_instalment_payment.ts new file mode 100644 index 0000000..0c03d8b --- /dev/null +++ b/packages/pieces/community/respaid/src/lib/actions/stop_collection_for_direct_instalment_payment.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { respaidAuth } from '../..'; +import { respaidCommon, respaidActionsCommon } from '../common'; + + +export const stopCollectionForDirectInstalmentPayment = createAction({ + name: 'stop_collection_for_direct_instalment_payment', + displayName: 'Stop Collection for Direct Instalment Payment', + description: 'Stops the collection process for a case when an instalment plan is set up with the creditor.', + auth: respaidAuth, + props: { + unique_identifier: Property.ShortText({ + displayName: 'Unique Identifier', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + required: false, + }), + invoice_number: Property.ShortText({ + displayName: 'Invoice Number', + required: false, + }), + }, + async run({ auth, propsValue }) { + respaidActionsCommon.validateProps(propsValue); + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${respaidCommon.baseUrl}/actions/stop_collection_for_direct_instalment_payment`, + headers: respaidCommon.getHeadersStructure(auth), + body: respaidActionsCommon.getPayloadBodyStructure(propsValue), + }); + return res.body; + }, +}); diff --git a/packages/pieces/community/respaid/src/lib/actions/stop_collection_for_direct_partial_payment.ts b/packages/pieces/community/respaid/src/lib/actions/stop_collection_for_direct_partial_payment.ts new file mode 100644 index 0000000..bd0b323 --- /dev/null +++ b/packages/pieces/community/respaid/src/lib/actions/stop_collection_for_direct_partial_payment.ts @@ -0,0 +1,42 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { respaidAuth } from '../..'; +import { respaidCommon, respaidActionsCommon } from '../common'; + + +export const stopCollectionForDirectPartialPayment = createAction({ + name: 'stop_collection_for_direct_partial_payment', + displayName: 'Stop Collection for Direct Partial Payment', + description: 'Stops the collection process for a case and mark it as partially paid directly to the creditor.', + auth: respaidAuth, + props: { + unique_identifier: Property.ShortText({ + displayName: 'Unique Identifier', + required: false, + }), + amount: Property.ShortText({ + displayName: 'Amount', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + required: false, + }), + invoice_number: Property.ShortText({ + displayName: 'Invoice Number', + required: false, + }), + }, + async run({ auth, propsValue }) { + respaidActionsCommon.validateProps(propsValue); + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${respaidCommon.baseUrl}/actions/stop_collection_for_direct_partial_payment`, + headers: respaidCommon.getHeadersStructure(auth), + body: respaidActionsCommon.getPayloadBodyStructure(propsValue), + }); + + return res.body; + }, +}); diff --git a/packages/pieces/community/respaid/src/lib/common/index.ts b/packages/pieces/community/respaid/src/lib/common/index.ts new file mode 100644 index 0000000..af27f39 --- /dev/null +++ b/packages/pieces/community/respaid/src/lib/common/index.ts @@ -0,0 +1,81 @@ +import { TriggerHookContext, TriggerStrategy, SecretTextProperty } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from "@activepieces/pieces-common"; + +interface ActionPayloadProps { + unique_identifier?: string; + invoice_number?: string; + email?: string; + amount?: string; +} + +export const respaidCommon = { + baseUrl: 'https://backend.widr.app/api/workflow', + getHeadersStructure: (auth: string) => ({ + 'Content-Type': 'application/json', + Accept: 'application/json', + 'X-API-KEY': auth + }), +}; + +export const respaidActionsCommon = { + getPayloadBodyStructure: (propsValue: ActionPayloadProps) => ({ + type: 'active_pieces', + payload: JSON.stringify({ + ...(propsValue.unique_identifier && { unique_identifier: propsValue.unique_identifier }), + ...(propsValue.invoice_number && { invoice_number: propsValue.invoice_number }), + ...(propsValue.amount && { amount: propsValue.amount }), + ...(propsValue.email && { email: propsValue.email }), + }) + }), + validateProps: (propsValue: ActionPayloadProps) => { + const { unique_identifier, email } = propsValue; + if (!unique_identifier && !email) { + throw new Error('You must provide either a unique_identifier OR email.'); + } + } +} + + +export const respaidTriggersCommon = { + onEnable: (eventType: string) => async(context: TriggerHookContext, Record, TriggerStrategy.WEBHOOK>) => { + try { + console.log('Trigger enabled, subscribing to webhook'); + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${respaidCommon.baseUrl}/webhook/subscribe`, + headers: respaidCommon.getHeadersStructure(context.auth), + body: { + type: 'active_pieces', + event_type: eventType, + target_url: context.webhookUrl, + }, + }); + } catch (error) { + console.error('Error subscribing to webhook:', error); + throw new Error('Failed to subscribe to webhook'); + } + }, + onDisable: (eventType: string) => async(context: TriggerHookContext, Record, TriggerStrategy.WEBHOOK>) => { + try { + console.log('Trigger disabled, unsubscribing from webhook'); + await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: `${respaidCommon.baseUrl}/webhook/unsubscribe`, + headers: respaidCommon.getHeadersStructure(context.auth), + body: { + type: 'active_pieces', + event_type: eventType, + target_url: context.webhookUrl, + }, + }); + } catch (error) { + console.error('Error unsubscribing from webhook:', error); + throw new Error('Failed to unsubscribe to webhook'); + } + }, + getPayload: (context: TriggerHookContext, Record, TriggerStrategy.WEBHOOK>) => { + return typeof context.payload.body === 'string' + ? JSON.parse(context.payload.body) + : context.payload; + } +} diff --git a/packages/pieces/community/respaid/src/lib/triggers/index.ts b/packages/pieces/community/respaid/src/lib/triggers/index.ts new file mode 100644 index 0000000..26b4d2c --- /dev/null +++ b/packages/pieces/community/respaid/src/lib/triggers/index.ts @@ -0,0 +1,23 @@ +import { newCampaignCreation } from "./new_campaign_creation"; +import { newCancelledCase } from "./new_cancelled_case"; +import { newDisputedCase } from "./new_disputed_case"; +import { newPayout } from "./new_payout"; +import { newSuccessfulCollectionPaidToCreditor } from "./new_successful_collection_paid_to_creditor"; +import { newSuccessfulInstallmentPaymentViaRespaid } from "./new_successful_installment_payment_via_respaid"; +import { newSuccessfulCollectionViaLegalOfficer } from "./new_successful_collection_via_legal_officer"; +import { newSuccessfulPartialPaymentToCreditor } from "./new_successful_partial_payment_to_creditor"; +import { newSuccessfulPartialPaymentViaRespaid } from "./new_successful_partial_payment_via_respaid"; +import { newSuccessfulCollectionViaRespaid } from "./new_successful_collection_via_respaid"; + +export const respaidTriggers = [ + newCampaignCreation, + newCancelledCase, + newDisputedCase, + newPayout, + newSuccessfulCollectionPaidToCreditor, + newSuccessfulInstallmentPaymentViaRespaid, + newSuccessfulCollectionViaLegalOfficer, + newSuccessfulPartialPaymentToCreditor, + newSuccessfulPartialPaymentViaRespaid, + newSuccessfulCollectionViaRespaid +] \ No newline at end of file diff --git a/packages/pieces/community/respaid/src/lib/triggers/new_campaign_creation.ts b/packages/pieces/community/respaid/src/lib/triggers/new_campaign_creation.ts new file mode 100644 index 0000000..e922387 --- /dev/null +++ b/packages/pieces/community/respaid/src/lib/triggers/new_campaign_creation.ts @@ -0,0 +1,50 @@ + +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { respaidAuth } from '../../index'; +import { respaidTriggersCommon } from '../common'; + +interface NewCampaignTriggerPayload { + request_id?: string; + is_campaign_created?: boolean; + valid_files?: { + unique_identifier: string; + sequence_code: string; + }[]; + invalid_files?: { + invalid_email: Record; + }[]; + file_processing_report?: string; +} + +export const newCampaignCreation = createTrigger({ + name: 'new_campaign_creation', + displayName: 'New Campaign Creation Result', + description: "Triggers when the campaign is created.", + auth: respaidAuth, + props: {}, + sampleData: { + "request_id": "1234", + "is_campaign_created": true, + "valid_files": [{ + "unique_identifier": "1", + "sequence_code": 'SQ###1' + }, { + "unique_identifier": "2", + "sequence_code": 'SQ###2' + }], + "invalid_files": [{ + "invalid_email": { + "unique_identifier_3": "3", + "unique_identifier_4": "4" + } + }], + "file_processing_report": 'https://link_excel.com' + }, + type: TriggerStrategy.WEBHOOK, + onEnable: respaidTriggersCommon.onEnable('new_campaign_creation'), + onDisable: respaidTriggersCommon.onDisable('new_campaign_creation'), + async run(context) { + const payload = respaidTriggersCommon.getPayload(context); + return [payload as NewCampaignTriggerPayload]; + }, +}) \ No newline at end of file diff --git a/packages/pieces/community/respaid/src/lib/triggers/new_cancelled_case.ts b/packages/pieces/community/respaid/src/lib/triggers/new_cancelled_case.ts new file mode 100644 index 0000000..3782b06 --- /dev/null +++ b/packages/pieces/community/respaid/src/lib/triggers/new_cancelled_case.ts @@ -0,0 +1,43 @@ + +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { respaidAuth } from '../../index'; +import { respaidTriggersCommon } from '../common'; + +interface NewCancelledCaseTriggerPayload { + unique_identifier?: string; + name?: string; + company_name?: string; + email?: string; + phone_number?: string; + invoice_number?: string; + amount?: number; + currency?: string; + reason?: string; +} + + +export const newCancelledCase = createTrigger({ + name: 'new_cancelled_case', + displayName: 'New Cancelled Case', + description: "Triggers when a collection process for a given sequence (case) was cancelled.", + auth: respaidAuth, + props: {}, + sampleData: { + "unique_identifier": "123", + "name": "John Doe", + "company_name": "Company XYZ", + "email": "john@example.com", + "phone_number": "1234567890", + "invoice_number": "INV123", + "amount": 1000, + "currency": "usd", + "reason": "Issue with invoice" + }, + type: TriggerStrategy.WEBHOOK, + onEnable: respaidTriggersCommon.onEnable('new_cancelled_case'), + onDisable: respaidTriggersCommon.onDisable('new_cancelled_case'), + async run(context) { + const payload = respaidTriggersCommon.getPayload(context); + return [payload as NewCancelledCaseTriggerPayload]; + }, +}) \ No newline at end of file diff --git a/packages/pieces/community/respaid/src/lib/triggers/new_disputed_case.ts b/packages/pieces/community/respaid/src/lib/triggers/new_disputed_case.ts new file mode 100644 index 0000000..e27b239 --- /dev/null +++ b/packages/pieces/community/respaid/src/lib/triggers/new_disputed_case.ts @@ -0,0 +1,48 @@ + +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { respaidAuth } from '../../index'; +import { respaidTriggersCommon } from '../common'; + + +interface NewDisputedCaseTriggerPayload { + unique_identifier?: string; + name?: string; + company_name?: string; + email?: string; + phone_number?: string; + invoice_number?: string; + amount?: number; + currency?: string; + context?: string; + attachment?: string; +} + +export const newDisputedCase = createTrigger({ + name: 'new_disputed_case', + displayName: 'New Disputed Case', + description: "Triggers when a collection process was disputed by the debtor.", + auth: respaidAuth, + props: {}, + sampleData: { + "unique_identifier": "123", + "name": "John Doe", + "company_name": "Company XYZ", + "email": "john@example.com", + "phone_number": "1234567890", + "invoice_number": "INV123", + "amount": 1000, + "currency": "usd", + "context": "Q: In order to stop the proceedings against you and not increase the amount of the debt, we can offer you\n" + + "A: Payment in instalments of up to 5 months (activation of instalments within 1 working day).\n" + + "Q: Do you agree to sign the following mandate?\n" + + "A: I agree", + "attachment": "https://link_excel.com/" + }, + type: TriggerStrategy.WEBHOOK, + onEnable: respaidTriggersCommon.onEnable('new_disputed_case'), + onDisable: respaidTriggersCommon.onDisable('new_disputed_case'), + async run(context) { + const payload = respaidTriggersCommon.getPayload(context); + return [payload as NewDisputedCaseTriggerPayload]; + }, +}) \ No newline at end of file diff --git a/packages/pieces/community/respaid/src/lib/triggers/new_payout.ts b/packages/pieces/community/respaid/src/lib/triggers/new_payout.ts new file mode 100644 index 0000000..a9467b7 --- /dev/null +++ b/packages/pieces/community/respaid/src/lib/triggers/new_payout.ts @@ -0,0 +1,56 @@ + +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { respaidAuth } from '../../index'; +import { respaidTriggersCommon } from '../common'; + +type Payment = Partial<{ + reference: string; + unique_identifier: string; + name: string; + company_name: string; + email: string; + phone_number: string; + invoice_number: string; + amount: number; + fees: number; + currency: string; + paid_at: string; +}> + +type PayoutTriggerPayload = Partial<{ + date: string; + payout_id: string; + payments: Payment[]; +}> + +export const newPayout = createTrigger({ + name: 'new_payout', + displayName: 'New Payout', + description: "Triggers when a payout is successfully sent to your bank account.", + auth: respaidAuth, + props: {}, + sampleData: { + "payout_id": "1234", + "date": "2025-03-02T00:00:00+0000", + "payments": [{ + "reference": "XXX123", + "unique_identifier": "123", + "name": "John Doe", + "company_name": "Company XYZ", + "email": "john@example.com", + "phone_number": "1234567890", + "invoice_number": "INV123", + "amount": 1000, + "fees": 2.5, + "currency": "usd", + "paid_at": "2025-03-02T00:00:00+0000" + }] + }, + type: TriggerStrategy.WEBHOOK, + onEnable: respaidTriggersCommon.onEnable('new_payout'), + onDisable: respaidTriggersCommon.onDisable('new_payout'), + async run(context) { + const payload = respaidTriggersCommon.getPayload(context); + return [payload as PayoutTriggerPayload]; + }, +}) \ No newline at end of file diff --git a/packages/pieces/community/respaid/src/lib/triggers/new_successful_collection_paid_to_creditor.ts b/packages/pieces/community/respaid/src/lib/triggers/new_successful_collection_paid_to_creditor.ts new file mode 100644 index 0000000..7b7b7e9 --- /dev/null +++ b/packages/pieces/community/respaid/src/lib/triggers/new_successful_collection_paid_to_creditor.ts @@ -0,0 +1,42 @@ + +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { respaidAuth } from '../../index'; +import { respaidTriggersCommon } from '../common'; + +interface NewPaidTriggerPayload { + unique_identifier?: string; + name?: string; + company_name?: string; + email?: string; + phone_number?: string; + invoice_number?: string; + amount?: number; + currency?: string; + paid_at?: string; +} + +export const newSuccessfulCollectionPaidToCreditor = createTrigger({ + name: 'new_successful_collection_paid_to_creditor', + displayName: 'New Successful Collection Paid to Creditor', + description: "Triggers when a debt is paid directly to the creditor.", + auth: respaidAuth, + props: {}, + sampleData: { + "unique_identifier": "123", + "name": "John Doe", + "company_name": "Company XYZ", + "email": "john@example.com", + "phone_number": "1234567890", + "invoice_number": "INV123", + "amount": 1000, + "currency": "usd", + "paid_at": "2025-03-02T00:00:00+0000" + }, + type: TriggerStrategy.WEBHOOK, + onEnable: respaidTriggersCommon.onEnable('new_successful_collection_paid_to_creditor'), + onDisable: respaidTriggersCommon.onDisable('new_successful_collection_paid_to_creditor'), + async run(context) { + const payload = respaidTriggersCommon.getPayload(context); + return [payload as NewPaidTriggerPayload]; + }, +}) \ No newline at end of file diff --git a/packages/pieces/community/respaid/src/lib/triggers/new_successful_collection_via_legal_officer.ts b/packages/pieces/community/respaid/src/lib/triggers/new_successful_collection_via_legal_officer.ts new file mode 100644 index 0000000..2350050 --- /dev/null +++ b/packages/pieces/community/respaid/src/lib/triggers/new_successful_collection_via_legal_officer.ts @@ -0,0 +1,42 @@ + +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { respaidAuth } from '../../index'; +import { respaidTriggersCommon } from '../common'; + +interface NewPaidTriggerPayload { + unique_identifier?: string; + name?: string; + company_name?: string; + email?: string; + phone_number?: string; + invoice_number?: string; + amount?: number; + currency?: string; + paid_at?: string; +} + +export const newSuccessfulCollectionViaLegalOfficer = createTrigger({ + name: 'new_successful_collection_via_legal_officer', + displayName: 'New Successful Collection via Legal Officer', + description: "Triggers when a debt is paid to the Legal Officer responsible for the collection campaign.", + auth: respaidAuth, + props: {}, + sampleData: { + "unique_identifier": "123", + "name": "John Doe", + "company_name": "Company XYZ", + "email": "john@example.com", + "phone_number": "1234567890", + "invoice_number": "INV123", + "amount": 1000, + "currency": "usd", + "paid_at": "2025-03-02T00:00:00+0000" + }, + type: TriggerStrategy.WEBHOOK, + onEnable: respaidTriggersCommon.onEnable('new_successful_collection_via_legal_officer'), + onDisable: respaidTriggersCommon.onDisable('new_successful_collection_via_legal_officer'), + async run(context) { + const payload = respaidTriggersCommon.getPayload(context); + return [payload as NewPaidTriggerPayload]; + }, +}) \ No newline at end of file diff --git a/packages/pieces/community/respaid/src/lib/triggers/new_successful_collection_via_respaid.ts b/packages/pieces/community/respaid/src/lib/triggers/new_successful_collection_via_respaid.ts new file mode 100644 index 0000000..2eb1c92 --- /dev/null +++ b/packages/pieces/community/respaid/src/lib/triggers/new_successful_collection_via_respaid.ts @@ -0,0 +1,45 @@ + +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { respaidAuth } from '../../index'; +import { respaidTriggersCommon } from '../common'; + + +interface NewPaidTriggerPayload { + unique_identifier?: string; + name?: string; + company_name?: string; + email?: string; + phone_number?: string; + invoice_number?: string; + amount?: number; + currency?: string; + payment_mode?: string | null; + paid_at?: string; +} + +export const newSuccessfulCollectionViaRespaid = createTrigger({ + name: 'new_successful_collection_via_respaid', + displayName: 'New Successful Collection via Respaid', + description: "Triggers when a debt is paid online via Respaid's payment link.", + auth: respaidAuth, + props: {}, + sampleData: { + "unique_identifier": "123", + "name": "John Doe", + "company_name": "Company XYZ", + "email": "john@example.com", + "phone_number": "1234567890", + "invoice_number": "INV123", + "amount": 1000, + "currency": "usd", + "payment_mode": "one-shot", + "paid_at": "2025-03-02T00:00:00+0000" + }, + type: TriggerStrategy.WEBHOOK, + onEnable: respaidTriggersCommon.onEnable('new_successful_collection_via_respaid'), + onDisable: respaidTriggersCommon.onDisable('new_successful_collection_via_respaid'), + async run(context) { + const payload = respaidTriggersCommon.getPayload(context); + return [payload as NewPaidTriggerPayload]; + }, +}) \ No newline at end of file diff --git a/packages/pieces/community/respaid/src/lib/triggers/new_successful_installment_payment_via_respaid.ts b/packages/pieces/community/respaid/src/lib/triggers/new_successful_installment_payment_via_respaid.ts new file mode 100644 index 0000000..9530fd1 --- /dev/null +++ b/packages/pieces/community/respaid/src/lib/triggers/new_successful_installment_payment_via_respaid.ts @@ -0,0 +1,50 @@ + +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { respaidAuth } from '../../index'; +import { respaidTriggersCommon } from '../common'; + +interface NewPaidTriggerPayload { + unique_identifier?: string; + name?: string; + company_name?: string; + email?: string; + phone_number?: string; + invoice_number?: string; + amount?: number; + paid_amount?: number; + installments_number?: number; + current_installment_step?: number; + balance?: number; + currency?: string; + paid_at?: string; +} + +export const newSuccessfulInstallmentPaymentViaRespaid = createTrigger({ + name: 'new_successful_installment_payment_via_respaid', + displayName: 'New Successful Installment Payment via Respaid', + description: "Triggers when one of the installment payments is made for a given case within a collection's payment plan.", + auth: respaidAuth, + props: {}, + sampleData: { + "unique_identifier": "123", + "name": "John Doe", + "company_name": "Company XYZ", + "email": "john@example.com", + "phone_number": "1234567890", + "invoice_number": "INV123", + "amount": 1000, + "paid_amount": 250, + "installments_number": 4, + "current_installment_step": 1, + "balance": 750, + "currency": "usd", + "paid_at": "2025-03-02T00:00:00+0000" + }, + type: TriggerStrategy.WEBHOOK, + onEnable: respaidTriggersCommon.onEnable('new_successful_installment_payment_via_respaid'), + onDisable: respaidTriggersCommon.onDisable('new_successful_installment_payment_via_respaid'), + async run(context) { + const payload = respaidTriggersCommon.getPayload(context); + return [payload as NewPaidTriggerPayload]; + }, +}) \ No newline at end of file diff --git a/packages/pieces/community/respaid/src/lib/triggers/new_successful_partial_payment_to_creditor.ts b/packages/pieces/community/respaid/src/lib/triggers/new_successful_partial_payment_to_creditor.ts new file mode 100644 index 0000000..82f2927 --- /dev/null +++ b/packages/pieces/community/respaid/src/lib/triggers/new_successful_partial_payment_to_creditor.ts @@ -0,0 +1,46 @@ + +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { respaidAuth } from '../../index'; +import { respaidTriggersCommon } from '../common'; + +interface NewPaidTriggerPayload { + unique_identifier?: string; + name?: string; + company_name?: string; + email?: string; + phone_number?: string; + invoice_number?: string; + amount?: number; + paid_amount?: number; + balance?: number; + currency?: string; + paid_at?: string; +} + +export const newSuccessfulPartialPaymentToCreditor = createTrigger({ + name: 'new_successful_partial_payment_to_creditor', + displayName: 'New Successful Partial Payment to Creditor', + description: "Triggers when the debt is partially paid directly to the creditor.", + auth: respaidAuth, + props: {}, + sampleData: { + "unique_identifier": "123", + "name": "John Doe", + "company_name": "Company XYZ", + "email": "john@example.com", + "phone_number": "1234567890", + "invoice_number": "INV123", + "amount": 1000, + "paid_amount": 250, + "balance": 750, + "currency": "usd", + "paid_at": "2025-03-02T00:00:00+0000" + }, + type: TriggerStrategy.WEBHOOK, + onEnable: respaidTriggersCommon.onEnable('new_successful_partial_payment_to_creditor'), + onDisable: respaidTriggersCommon.onDisable('new_successful_partial_payment_to_creditor'), + async run(context) { + const payload = respaidTriggersCommon.getPayload(context); + return [payload as NewPaidTriggerPayload]; + }, +}) \ No newline at end of file diff --git a/packages/pieces/community/respaid/src/lib/triggers/new_successful_partial_payment_via_respaid.ts b/packages/pieces/community/respaid/src/lib/triggers/new_successful_partial_payment_via_respaid.ts new file mode 100644 index 0000000..2749f13 --- /dev/null +++ b/packages/pieces/community/respaid/src/lib/triggers/new_successful_partial_payment_via_respaid.ts @@ -0,0 +1,46 @@ + +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { respaidAuth } from '../../index'; +import { respaidTriggersCommon } from '../common'; + +interface NewPaidTriggerPayload { + unique_identifier?: string; + name?: string; + company_name?: string; + email?: string; + phone_number?: string; + invoice_number?: string; + amount?: number; + paid_amount?: number; + balance?: number; + currency?: string; + paid_at?: string; +} + +export const newSuccessfulPartialPaymentViaRespaid = createTrigger({ + name: 'new_successful_partial_payment_via_respaid', + displayName: 'New Successful Partial Payment via Respaid', + description: "Triggers when the debt is partially paid via Respaid's payment link.", + auth: respaidAuth, + props: {}, + sampleData: { + "unique_identifier": "123", + "name": "John Doe", + "company_name": "Company XYZ", + "email": "john@example.com", + "phone_number": "1234567890", + "invoice_number": "INV123", + "amount": 1000, + "paid_amount": 250, + "balance": 750, + "currency": "usd", + "paid_at": "2025-03-02T00:00:00+0000" + }, + type: TriggerStrategy.WEBHOOK, + onEnable: respaidTriggersCommon.onEnable('new_successful_partial_payment_via_respaid'), + onDisable: respaidTriggersCommon.onDisable('new_successful_partial_payment_via_respaid'), + async run(context) { + const payload = respaidTriggersCommon.getPayload(context); + return [payload as NewPaidTriggerPayload]; + }, +}) \ No newline at end of file diff --git a/packages/pieces/community/respaid/tsconfig.json b/packages/pieces/community/respaid/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/respaid/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/respaid/tsconfig.lib.json b/packages/pieces/community/respaid/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/respaid/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/retable/.eslintrc.json b/packages/pieces/community/retable/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/retable/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/retable/README.md b/packages/pieces/community/retable/README.md new file mode 100644 index 0000000..77b5289 --- /dev/null +++ b/packages/pieces/community/retable/README.md @@ -0,0 +1,7 @@ +# pieces-retable + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-retable` to build the library. diff --git a/packages/pieces/community/retable/package.json b/packages/pieces/community/retable/package.json new file mode 100644 index 0000000..270ad3b --- /dev/null +++ b/packages/pieces/community/retable/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-retable", + "version": "0.0.4" +} \ No newline at end of file diff --git a/packages/pieces/community/retable/project.json b/packages/pieces/community/retable/project.json new file mode 100644 index 0000000..2f9cf71 --- /dev/null +++ b/packages/pieces/community/retable/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-retable", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/retable/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/retable", + "tsConfig": "packages/pieces/community/retable/tsconfig.lib.json", + "packageJson": "packages/pieces/community/retable/package.json", + "main": "packages/pieces/community/retable/src/index.ts", + "assets": [ + "packages/pieces/community/retable/*.md", + { + "input": "packages/pieces/community/retable/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-retable {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/retable/src/index.ts b/packages/pieces/community/retable/src/index.ts new file mode 100644 index 0000000..05cc168 --- /dev/null +++ b/packages/pieces/community/retable/src/index.ts @@ -0,0 +1,59 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { retableCreateProjectAction } from './lib/actions/create-project'; +import { retableCreateWorkspaceAction } from './lib/actions/create-workspace'; +import { retableGetAllProjectsAction } from './lib/actions/get-all-projects'; +import { retableGetAllRetablesAction } from './lib/actions/get-all-retables'; +import { retableGetAllWorkspacesAction } from './lib/actions/get-all-workspaces'; +import { retableCreateRecordAction } from './lib/actions/insert-record'; +import { retableCommon } from './lib/common'; +const markdown = ` +To obtain your API key, follow these steps: + +1. Go to Account Overview by clicking your profile-pic (top-right). +2. Go to API section and enable API key. +3. Copy API key.`; + +export const retableAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: markdown, + validate: async ({ auth }) => { + if (auth.startsWith('RTBLv1-')) { + return { + valid: true, + }; + } + return { + valid: false, + error: 'Invalid API Key', + }; + }, +}); +export const retable = createPiece({ + displayName: 'Retable', + description: 'Turn your spreadsheets into smart database apps', + + auth: retableAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/retable.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: ["kishanprmr","MoShizzle","abuaboud"], + actions: [ + retableCreateRecordAction, + retableGetAllWorkspacesAction, + retableGetAllProjectsAction, + retableGetAllRetablesAction, + retableCreateWorkspaceAction, + retableCreateProjectAction, + createCustomApiCallAction({ + baseUrl: () => retableCommon.baseUrl, + auth: retableAuth, + authMapping: async (auth) => ({ + ApiKey: auth as string, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/retable/src/lib/actions/create-project.ts b/packages/pieces/community/retable/src/lib/actions/create-project.ts new file mode 100644 index 0000000..5f1d534 --- /dev/null +++ b/packages/pieces/community/retable/src/lib/actions/create-project.ts @@ -0,0 +1,44 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +import { retableAuth } from '../..'; +import { retableCommon } from '../common'; + +export const retableCreateProjectAction = createAction({ + auth: retableAuth, + name: 'retable_create_project', + displayName: 'Create a Project', + description: 'Creates a project in the given workspace', + props: { + workspace_id: retableCommon.workspace_id(), + name: Property.ShortText({ + displayName: 'Project Name', + required: true, + }), + desc: Property.LongText({ + displayName: 'Project Description', + required: false, + }), + color: Property.ShortText({ + displayName: 'Project Color', + required: false, + }), + }, + async run(context) { + const { workspace_id, name, desc, color } = context.propsValue; + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${retableCommon.baseUrl}/workspace/${workspace_id}/project`, + headers: { + ApiKey: context.auth as string, + }, + body: { + name: name, + description: desc, + color: color, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/retable/src/lib/actions/create-workspace.ts b/packages/pieces/community/retable/src/lib/actions/create-workspace.ts new file mode 100644 index 0000000..33e0a9c --- /dev/null +++ b/packages/pieces/community/retable/src/lib/actions/create-workspace.ts @@ -0,0 +1,38 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +import { retableAuth } from '../..'; +import { retableCommon } from '../common'; + +export const retableCreateWorkspaceAction = createAction({ + auth: retableAuth, + name: 'retable_create_workspace', + displayName: 'Create a Workspace', + description: 'Creates a workspace', + props: { + name: Property.ShortText({ + displayName: 'Workspace Name', + required: true, + }), + desc: Property.LongText({ + displayName: 'Workspace Description', + required: false, + }), + }, + async run(context) { + const { name, desc } = context.propsValue; + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${retableCommon.baseUrl}/workspace`, + headers: { + ApiKey: context.auth as string, + }, + body: { + name: name, + description: desc, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/retable/src/lib/actions/get-all-projects.ts b/packages/pieces/community/retable/src/lib/actions/get-all-projects.ts new file mode 100644 index 0000000..9f26d81 --- /dev/null +++ b/packages/pieces/community/retable/src/lib/actions/get-all-projects.ts @@ -0,0 +1,27 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +import { retableAuth } from '../..'; +import { retableCommon } from '../common'; + +export const retableGetAllProjectsAction = createAction({ + auth: retableAuth, + name: 'retable_get_projects', + displayName: 'Get Projects', + description: 'Gets all projects in given workspace', + props: { + workspace_id: retableCommon.workspace_id(), + }, + async run(context) { + const { workspace_id } = context.propsValue; + return ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${retableCommon.baseUrl}/workspace/${workspace_id}/project`, + headers: { + ApiKey: context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/retable/src/lib/actions/get-all-retables.ts b/packages/pieces/community/retable/src/lib/actions/get-all-retables.ts new file mode 100644 index 0000000..2804382 --- /dev/null +++ b/packages/pieces/community/retable/src/lib/actions/get-all-retables.ts @@ -0,0 +1,28 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +import { retableAuth } from '../..'; +import { retableCommon } from '../common'; + +export const retableGetAllRetablesAction = createAction({ + auth: retableAuth, + name: 'retable_get_retables', + displayName: 'Get Retables', + description: 'Gets all retables in given project', + props: { + workspace_id: retableCommon.workspace_id(), + project_id: retableCommon.project_id(), + }, + async run(context) { + const { project_id } = context.propsValue; + return ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${retableCommon.baseUrl}/project/${project_id}/retable`, + headers: { + ApiKey: context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/retable/src/lib/actions/get-all-workspaces.ts b/packages/pieces/community/retable/src/lib/actions/get-all-workspaces.ts new file mode 100644 index 0000000..0fbeb6b --- /dev/null +++ b/packages/pieces/community/retable/src/lib/actions/get-all-workspaces.ts @@ -0,0 +1,24 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +import { retableAuth } from '../..'; +import { retableCommon } from '../common'; + +export const retableGetAllWorkspacesAction = createAction({ + auth: retableAuth, + name: 'retable_get_workspaces', + displayName: 'Get Workspaces', + description: 'Gets all workspaces', + props: {}, + async run(context) { + return ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${retableCommon.baseUrl}/workspace`, + headers: { + ApiKey: context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/retable/src/lib/actions/insert-record.ts b/packages/pieces/community/retable/src/lib/actions/insert-record.ts new file mode 100644 index 0000000..17dcc9e --- /dev/null +++ b/packages/pieces/community/retable/src/lib/actions/insert-record.ts @@ -0,0 +1,46 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +import { retableAuth } from '../..'; +import { retableCommon } from '../common'; + +export const retableCreateRecordAction = createAction({ + auth: retableAuth, + name: 'retable_create_record', + displayName: 'Create Retable Record', + description: 'Adds a record into a retable', + props: { + workspace_id: retableCommon.workspace_id(), + project_id: retableCommon.project_id(), + retable_id: retableCommon.retable_id(), + fields: retableCommon.fields, + }, + async run(context) { + const { retable_id } = context.propsValue; + const fields = context.propsValue.fields; + const outputData = Object.entries(fields) + .map(([column_id, cell_value]) => { + if (cell_value !== '') { + return { + column_id, + cell_value, + }; + } + return null; // Skip empty cell values + }) + .filter((entry) => entry !== null); + + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${retableCommon.baseUrl}/retable/${retable_id}/data`, + headers: { + ApiKey: context.auth as string, + }, + body: { + data: [{ columns: outputData }], + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/retable/src/lib/common/index.ts b/packages/pieces/community/retable/src/lib/common/index.ts new file mode 100644 index 0000000..9e4815f --- /dev/null +++ b/packages/pieces/community/retable/src/lib/common/index.ts @@ -0,0 +1,164 @@ +import { Property, DynamicPropsValue } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +import { + RetableFieldMapping, + RetableField, + RetableNotSupportedFields, + RetableWorkspace, + RetableProject, + RetableTable, +} from './models'; +import { isNil } from '@activepieces/shared'; + +export const retableCommon = { + baseUrl: 'https://api.retable.io/v1/public', + workspace_id: (required = true) => + Property.Dropdown({ + displayName: 'Workspace', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account', + }; + } + const response = await httpClient.sendRequest<{ + data: { + workspaces: RetableWorkspace[]; + }; + }>({ + method: HttpMethod.GET, + url: `${retableCommon.baseUrl}/workspace`, + headers: { + ApiKey: auth as string, + }, + }); + return { + disabled: false, + options: response.body.data.workspaces.map((workspace) => { + return { + label: workspace.name, + value: workspace.id, + }; + }), + }; + }, + }), + project_id: (required = true) => + Property.Dropdown({ + displayName: 'Project', + required, + refreshers: ['workspace_id'], + options: async ({ auth, workspace_id }) => { + if (!auth || !workspace_id) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account and select workspace', + }; + } + + const response = await httpClient.sendRequest<{ + data: { + projects: RetableProject[]; + }; + }>({ + method: HttpMethod.GET, + url: `${retableCommon.baseUrl}/workspace/${ + workspace_id as string + }/project`, + headers: { + ApiKey: auth as string, + }, + }); + return { + disabled: false, + options: response.body.data.projects.map((project) => { + return { + label: project.name, + value: project.id, + }; + }), + }; + }, + }), + retable_id: (required = true) => + Property.Dropdown({ + displayName: 'Retable', + required, + refreshers: ['project_id'], + options: async ({ auth, project_id }) => { + if (!auth || !project_id) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account and select project', + }; + } + const response = await httpClient.sendRequest<{ + data: { + retables: RetableTable[]; + }; + }>({ + method: HttpMethod.GET, + url: `${retableCommon.baseUrl}/project/${ + project_id as string + }/retable`, + headers: { + ApiKey: auth as string, + }, + }); + return { + disabled: false, + options: response.body.data.retables.map((retable) => { + return { + label: retable.title, + value: retable.id, + }; + }), + }; + }, + }), + fields: Property.DynamicProperties({ + displayName: 'Fields', + required: true, + refreshers: ['retable_id'], + props: async ({ auth, retable_id }) => { + if (!auth || !retable_id) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account and select retable', + }; + } + const fields: DynamicPropsValue = {}; + const retable = await httpClient.sendRequest<{ data: RetableTable }>({ + method: HttpMethod.GET, + url: `${retableCommon.baseUrl}/retable/${retable_id}`, + headers: { + ApiKey: auth as unknown as string, + }, + }); + retable.body.data.columns.forEach((field: RetableField) => { + if (!RetableNotSupportedFields.includes(field.type)) { + const params = { + displayName: field.title, + required: false, + }; + if (isNil(RetableFieldMapping[field.type])) { + fields[field.column_id] = Property.ShortText({ + ...params, + }); + } else { + fields[field.column_id] = RetableFieldMapping[field.type](params); + } + } + }); + return fields; + }, + }), +}; diff --git a/packages/pieces/community/retable/src/lib/common/models.ts b/packages/pieces/community/retable/src/lib/common/models.ts new file mode 100644 index 0000000..dd9f4c3 --- /dev/null +++ b/packages/pieces/community/retable/src/lib/common/models.ts @@ -0,0 +1,82 @@ +import { Property } from '@activepieces/pieces-framework'; +export interface RetableWorkspace { + id: string; + name: string; +} + +export interface RetableProject { + id: string; + name: string; +} + +export interface RetableField { + column_id: string; + title: string; + type: RetableFieldType; + created_at?: string; +} +export interface RetableTable { + id: string; + title: string; + description?: string; + columns: RetableField[]; + project_id: string; + workspace_id: string; +} + +export type RetableFieldType = + | 'url' + | 'updated_by' + // | 'Attachment' + // | 'Image' + | 'updated_at' + | 'created_by' + | 'created_at' + | 'user' + | 'url' + | 'formula' + | 'currency' + | 'phonenumber' + | 'email' + | 'color' + | 'calendar' + | 'dropdown' + | 'percent' + | 'checkbox' + | 'number' + | 'rating' + | 'text'; + +export const RetableFieldMapping = { + text: Property.ShortText, + updated_by: Property.ShortText, + updated_at: Property.ShortText, + created_by: Property.ShortText, + created_at: Property.ShortText, + user: Property.ShortText, + url: Property.ShortText, + formula: Property.ShortText, + rating: Property.ShortText, + dropdown: Property.ShortText, + percent: Property.ShortText, + email: Property.ShortText, + phonenumber: Property.ShortText, + currency: Property.ShortText, + color: Property.ShortText, + calendar: Property.ShortText, + checkbox: Property.Checkbox, + number: Property.ShortText, +}; + +export const RetableNotSupportedFields = [ + 'attachment', + 'image', + 'updated_by', + 'updated_at', + 'created_by', + 'created_at', + 'user', + 'vote', + 'qr_code', + 'richtext', +]; diff --git a/packages/pieces/community/retable/tsconfig.json b/packages/pieces/community/retable/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/retable/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/retable/tsconfig.lib.json b/packages/pieces/community/retable/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/retable/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/retune/.eslintrc.json b/packages/pieces/community/retune/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/retune/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/retune/README.md b/packages/pieces/community/retune/README.md new file mode 100644 index 0000000..72edd14 --- /dev/null +++ b/packages/pieces/community/retune/README.md @@ -0,0 +1,7 @@ +# pieces-retune + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-retune` to build the library. diff --git a/packages/pieces/community/retune/package.json b/packages/pieces/community/retune/package.json new file mode 100644 index 0000000..f9b8a28 --- /dev/null +++ b/packages/pieces/community/retune/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-retune", + "version": "0.0.6" +} \ No newline at end of file diff --git a/packages/pieces/community/retune/project.json b/packages/pieces/community/retune/project.json new file mode 100644 index 0000000..8d2eb10 --- /dev/null +++ b/packages/pieces/community/retune/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-retune", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/retune/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/retune", + "tsConfig": "packages/pieces/community/retune/tsconfig.lib.json", + "packageJson": "packages/pieces/community/retune/package.json", + "main": "packages/pieces/community/retune/src/index.ts", + "assets": [ + "packages/pieces/community/retune/*.md", + { + "input": "packages/pieces/community/retune/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-retune {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/retune/src/index.ts b/packages/pieces/community/retune/src/index.ts new file mode 100644 index 0000000..43f3b9e --- /dev/null +++ b/packages/pieces/community/retune/src/index.ts @@ -0,0 +1,87 @@ +import { + createCustomApiCallAction, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { askChatbot } from './lib/actions/ask-chatbot'; + +const markdownDescription = ` +Follow these instructions to get your re:tune chat ID and API Key: + +1. Visit the chatbots page: https://retune.so/chats +2. Once on the website, locate and click on the chatbot you want to use +3. From the URL, copy the chat ID you want to use; +e.g from this: https://retune.so/chat/acewocwe-123123-123123-123123/ your chat ID is "acewocwe-123123-123123-123123" +4. To get the API key, go to https://retune.so/settings +5. Scroll to the bottom to find "Re:tune API Keys" and copy your key below +`; + +export const retuneAuth = PieceAuth.CustomAuth({ + description: markdownDescription, + required: true, + props: { + // There is no way to programmatically get the user's chatbots, so we have this + chatId: Property.ShortText({ + displayName: 'Chat ID', + description: 'The ID of the chat you want to use.', + required: true, + }), + apiKey: PieceAuth.SecretText({ + displayName: 'API Key', + description: 'Your re:tune API key.', + required: true, + }), + }, + + validate: async (auth) => { + try { + await httpClient.sendRequest<{ + data: { id: string }[]; + }>({ + url: `https://retune.so/api/chat/${auth.auth.chatId}/threads`, + method: HttpMethod.POST, + headers: { + 'X-Workspace-API-Key': auth.auth.apiKey, + }, + body: {}, + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API key', + }; + } + }, +}); + +export const retune = createPiece({ + displayName: 're:tune', + description: + 'Everything you need to transform your business with AI, from custom chatbots to autonomous agents.', + + auth: retuneAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/retune.png', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + authors: ["kishanprmr","MoShizzle","abuaboud"], + actions: [ + askChatbot, + createCustomApiCallAction({ + baseUrl: () => 'https://retune.so/api', + auth: retuneAuth, + authMapping: async (auth) => ({ + 'X-Workspace-API-Key': (auth as { apiKey: string }).apiKey, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/retune/src/lib/actions/ask-chatbot.ts b/packages/pieces/community/retune/src/lib/actions/ask-chatbot.ts new file mode 100644 index 0000000..548ce10 --- /dev/null +++ b/packages/pieces/community/retune/src/lib/actions/ask-chatbot.ts @@ -0,0 +1,59 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { retuneAuth } from '../../index'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +export const askChatbot = createAction({ + auth: retuneAuth, + name: 'ask_chatbot', + displayName: 'Ask Chatbot', + description: 'Sends a message to an existing thread with a chatbot.', + props: { + thread: Property.Dropdown({ + displayName: 'Thread', + description: 'The thread you want to send the message to.', + required: true, + refreshers: [], + options: async ({ auth }) => { + const options = await httpClient.sendRequest({ + url: `https://retune.so/api/chat/${(auth as any).chatId}/threads`, + method: HttpMethod.POST, + headers: { + 'X-Workspace-API-Key': (auth as any).apiKey, + }, + body: {}, + }); + + return { + options: options.body['threads'].map((item: any) => { + return { + label: item.name ?? item.id, + value: item.id, + }; + }), + }; + }, + }), + message: Property.ShortText({ + displayName: 'Message', + description: 'The message you want to send.', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { thread, message } = propsValue; + + const response = await httpClient.sendRequest({ + url: `https://retune.so/api/chat/${auth.chatId}/response`, + method: HttpMethod.POST, + headers: { + 'X-Workspace-API-Key': auth.apiKey, + }, + body: { + threadId: thread, + input: message, + }, + }); + + return (response.body as any).response.value; + }, +}); diff --git a/packages/pieces/community/retune/tsconfig.json b/packages/pieces/community/retune/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/retune/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/retune/tsconfig.lib.json b/packages/pieces/community/retune/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/retune/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/returning-ai/.eslintrc.json b/packages/pieces/community/returning-ai/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/returning-ai/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/returning-ai/README.md b/packages/pieces/community/returning-ai/README.md new file mode 100644 index 0000000..4bd670c --- /dev/null +++ b/packages/pieces/community/returning-ai/README.md @@ -0,0 +1,7 @@ +# pieces-returning-ai + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-returning-ai` to build the library. diff --git a/packages/pieces/community/returning-ai/package.json b/packages/pieces/community/returning-ai/package.json new file mode 100644 index 0000000..5e329cd --- /dev/null +++ b/packages/pieces/community/returning-ai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-returning-ai", + "version": "0.0.4" +} diff --git a/packages/pieces/community/returning-ai/project.json b/packages/pieces/community/returning-ai/project.json new file mode 100644 index 0000000..ecd7f20 --- /dev/null +++ b/packages/pieces/community/returning-ai/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-returning-ai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/returning-ai/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/returning-ai", + "tsConfig": "packages/pieces/community/returning-ai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/returning-ai/package.json", + "main": "packages/pieces/community/returning-ai/src/index.ts", + "assets": [ + "packages/pieces/community/returning-ai/*.md", + { + "input": "packages/pieces/community/returning-ai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/returning-ai/src/index.ts b/packages/pieces/community/returning-ai/src/index.ts new file mode 100644 index 0000000..e2f8bce --- /dev/null +++ b/packages/pieces/community/returning-ai/src/index.ts @@ -0,0 +1,20 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { replyMessage } from './lib/actions/reply-message'; +import { sendMessage } from './lib/actions/send-message'; +import { reactMessage } from './lib/actions/react-message'; + +export const returningAiAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'Add api key from returning.ai', +}); + +export const returningAi = createPiece({ + displayName: 'Returning AI', + auth: returningAiAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: `https://cdn.activepieces.com/pieces/returning-ai.png`, + authors: ['mg-wunna'], + actions: [sendMessage, replyMessage, reactMessage], + triggers: [], +}); diff --git a/packages/pieces/community/returning-ai/src/lib/actions/react-message.ts b/packages/pieces/community/returning-ai/src/lib/actions/react-message.ts new file mode 100644 index 0000000..faa1c8d --- /dev/null +++ b/packages/pieces/community/returning-ai/src/lib/actions/react-message.ts @@ -0,0 +1,70 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getApiEndpoint } from '../common'; +import { returningAiAuth } from '../../index'; + +/** + * This action allows you to react to a specific message as a chosen user in a channel. + * The reaction will appear as if the selected user added it directly to the message. + * + * When using this action, you need to provide: + * 1. The username or email of the sender (who will appear to have reacted) + * 2. The ID of the message being reacted to + * 3. The emoji reaction to add (e.g. :sparkling_heart:) + * + * The API will handle adding the reaction appropriately to the message. + * + * @example + * ``` + * // Example response + * { + * "status": "success", + * "message": "Reaction added successfully" + * } + * ``` + * + * @link https://dev.returning.ai/api-16179164 + */ +export const reactMessage = createAction({ + auth: returningAiAuth, + name: 'reactMessage', + displayName: 'React to Message', + description: 'Add an emoji reaction to a specific message as a chosen user.', + props: { + description: Property.MarkDown({ + value: + 'This action allows you to add an emoji reaction to a message in any accessible channel on behalf of a specific user. The reaction will appear as if the selected user added it directly to the message.', + }), + user: Property.ShortText({ + displayName: 'Sender (Username or email)', + description: 'The user account that will add the reaction', + required: true, + }), + messageId: Property.ShortText({ + displayName: 'React to (Message ID)', + description: 'The ID of the message to react to', + required: true, + }), + emoji: Property.ShortText({ + displayName: 'Reaction emoji', + description: 'The emoji to react with (e.g. :sparkling_heart:)', + required: true, + }), + }, + async run({ propsValue, auth }) { + const authToken = auth as string; + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${getApiEndpoint(authToken)}/apis/v1/messages/react`, + headers: { + Authorization: `Bearer ${authToken.includes(':') ? authToken.split(':')[1] : authToken}`, + }, + body: { + messageId: propsValue.messageId, + emoji: propsValue.emoji, + sender: propsValue.user, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/returning-ai/src/lib/actions/reply-message.ts b/packages/pieces/community/returning-ai/src/lib/actions/reply-message.ts new file mode 100644 index 0000000..01c001b --- /dev/null +++ b/packages/pieces/community/returning-ai/src/lib/actions/reply-message.ts @@ -0,0 +1,70 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getApiEndpoint } from '../common'; +import { returningAiAuth } from '../../index'; + +/** + * This action allows you to reply to a specific message as a chosen user in a channel. + * The reply will appear as if the selected user posted it directly in response to the original message. + * + * When using this action, you need to provide: + * 1. The username or email of the sender (who will appear to have sent the reply) + * 2. The ID of the message being replied to + * 3. The content of the reply message + * + * The API will handle threading the reply appropriately in the channel's conversation. + * + * @example + * ``` + * // Example response + * { + * "status": "success", + * "message": "Message reply successfully" + * } + * ``` + * + * @link https://dev.returning.ai/api-16179164 + */ +export const replyMessage = createAction({ + auth:returningAiAuth, + name: 'replyMessage', + displayName: 'Reply Message', + description: 'Reply to a specific message as a chosen user in a channel.', + props: { + description: Property.MarkDown({ + value: + 'This action allows you to reply to a message in any accessible channel on behalf of a specific user. The reply will appear as if the selected user posted it directly in response to the original message.', + }), + user: Property.ShortText({ + displayName: 'Sender (Username or email)', + description: 'The user account that will send the message', + required: true, + }), + messageId: Property.ShortText({ + displayName: 'Reply to (Message ID)', + description: 'The ID of the message to reply to. ', + required: true, + }), + message: Property.LongText({ + displayName: 'Message', + description: 'The content of the reply to be posted.', + required: true, + }), + }, + async run({ propsValue, auth }) { + const authToken = auth as string; + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${getApiEndpoint(authToken)}/apis/v1/messages/reply`, + headers: { + Authorization: `Bearer ${authToken.includes(':') ? authToken.split(':')[1] : authToken}`, + }, + body: { + messageId: propsValue.messageId, + message: propsValue.message, + sender: propsValue.user, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/returning-ai/src/lib/actions/send-message.ts b/packages/pieces/community/returning-ai/src/lib/actions/send-message.ts new file mode 100644 index 0000000..b408f9a --- /dev/null +++ b/packages/pieces/community/returning-ai/src/lib/actions/send-message.ts @@ -0,0 +1,148 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property, DynamicPropsValue } from '@activepieces/pieces-framework'; +import { getApiEndpoint } from '../common'; +import { returningAiAuth } from '../../index'; + +/** + * This action allows you to send messages to any accessible channel as a specific user. + * The message will appear as if the selected user posted it directly. + * + * When using this action, you need to provide: + * 1. The username or email of the sender (who will appear to have sent the message) + * 2. The channel where the message will be posted + * 3. The content of the message + * + * The API will handle posting the message to the specified channel with the appropriate sender. + * + * @example + * ``` + * // Example response + * { + * "status": "success", + * "message": "Message sent successfully" + * } + * ``` + * + * @link https://dev.returning.ai/api-15023884 + */ +export const sendMessage = createAction({ + auth:returningAiAuth, + name: 'sendMessage', + displayName: 'Send Channel Message', + description: 'Posts a message to a specified channel as a chosen user', + props: { + description: Property.MarkDown({ + value: + 'This action allows you to send messages to any accessible channel as a specific user. The message will appear as if the selected user posted it directly.', + }), + user: Property.ShortText({ + displayName: 'Sender (Username or email)', + description: 'The user account that will send the message', + required: true, + }), + channel: Property.Dropdown({ + displayName: 'Channel', + description: 'The channel where the message will be posted', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + const authToken = auth as string; + + if (!authToken) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + } + + /** + * Fetches the list of available channels from the Returning.ai API + * @link https://dev.returning.ai/api-15023851 + */ + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${getApiEndpoint(authToken)}/apis/v1/channels`, + headers: { + Authorization: `Bearer ${authToken.includes(':') ? authToken.split(':')[1] : authToken}`, + }, + }); + + if (response.body.status === 'success') { + return { + options: response.body.data.map( + (channel: { + _id: string; + topic: string; + channelType: string; + }) => ({ + label: channel.topic, + value: JSON.stringify({ + id: channel._id, + type: channel.channelType, + }), + }) + ), + }; + } else { + return { + disabled: true, + options: [], + placeholder: response.body.message, + }; + } + }, + }), + dynamicFields: Property.DynamicProperties({ + displayName: 'Channel Fields', + description: 'Additional fields based on channel type', + required: true, + refreshers: ['channel'], + props: async ({ channel }) => { + const properties: Record = {}; + + if (channel) { + const channelData = JSON.parse(channel as unknown as string); + if (channelData.type === 'forum') { + properties['topicId'] = Property.ShortText({ + displayName: 'Topic ID', + description: + 'The topic ID of the forum channel where the message will be posted', + required: true, + }); + } + } + + return properties; + }, + }), + message: Property.LongText({ + displayName: 'Message', + description: 'The message to be posted', + required: true, + }), + }, + async run({ propsValue, auth }) { + const authToken = auth as string; + const channelData = JSON.parse(propsValue.channel as string); + const dynamicFields = propsValue.dynamicFields as DynamicPropsValue; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${getApiEndpoint(authToken)}/apis/v1/messages/send`, + headers: { + Authorization: `Bearer ${authToken.includes(':') ? authToken.split(':')[1] : authToken}`, + }, + body: { + channelId: channelData.id, + message: propsValue.message, + sender: propsValue.user, + ...(channelData.type === 'forum' && { + forumTopicId: dynamicFields['topicId'], + }), + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/returning-ai/src/lib/common/index.ts b/packages/pieces/community/returning-ai/src/lib/common/index.ts new file mode 100644 index 0000000..e6967bb --- /dev/null +++ b/packages/pieces/community/returning-ai/src/lib/common/index.ts @@ -0,0 +1,15 @@ +export function getApiEndpoint(authToken?: string) { + if (!authToken) return 'https://integration.returning.ai'; + + if (authToken.includes('local:')) { + return 'http://localhost:3333'; + } else if (authToken.includes('pre-staging:')) { + return 'https://sgtr-integration.returning.ai'; + } else if (authToken.includes('staging:')) { + return 'https://staging-integration.returning.ai'; + } else if (authToken.includes('playground:')) { + return 'https://playground-integration.returning.ai'; + } else { + return 'https://integration.returning.ai'; + } +} diff --git a/packages/pieces/community/returning-ai/tsconfig.json b/packages/pieces/community/returning-ai/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/returning-ai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/returning-ai/tsconfig.lib.json b/packages/pieces/community/returning-ai/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/returning-ai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/robolly/.eslintrc.json b/packages/pieces/community/robolly/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/robolly/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/robolly/README.md b/packages/pieces/community/robolly/README.md new file mode 100644 index 0000000..54c6e38 --- /dev/null +++ b/packages/pieces/community/robolly/README.md @@ -0,0 +1,7 @@ +# pieces-robolly + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-robolly` to build the library. diff --git a/packages/pieces/community/robolly/package.json b/packages/pieces/community/robolly/package.json new file mode 100644 index 0000000..121b783 --- /dev/null +++ b/packages/pieces/community/robolly/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-robolly", + "version": "0.0.6" +} \ No newline at end of file diff --git a/packages/pieces/community/robolly/project.json b/packages/pieces/community/robolly/project.json new file mode 100644 index 0000000..0641ca1 --- /dev/null +++ b/packages/pieces/community/robolly/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-robolly", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/robolly/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/robolly", + "tsConfig": "packages/pieces/community/robolly/tsconfig.lib.json", + "packageJson": "packages/pieces/community/robolly/package.json", + "main": "packages/pieces/community/robolly/src/index.ts", + "assets": [ + "packages/pieces/community/robolly/*.md", + { + "input": "packages/pieces/community/robolly/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-robolly {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/robolly/src/index.ts b/packages/pieces/community/robolly/src/index.ts new file mode 100644 index 0000000..526ec9b --- /dev/null +++ b/packages/pieces/community/robolly/src/index.ts @@ -0,0 +1,45 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { generateImage } from './lib/actions/generate-image.action'; + +const markdownDescription = ` +Follow these instructions to get your API Key: +1. Visit the following website: https://robolly.com/dashboard/account/ +2. Once on the website, locate and copy your API Key. +Please, take into consideration: We don't test your API Key validity in order to save you some generations, so make sure this is the correct one. +`; + +export const robollyAuth = PieceAuth.SecretText({ + description: markdownDescription, + displayName: 'API Key', + required: true, + validate: async () => { + return { + valid: true, + }; + }, +}); + +export const robolly = createPiece({ + displayName: 'Robolly', + description: + 'Robolly is the all‑in‑one service for personalized image, video & PDF generation with API', + + auth: robollyAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/robolly.png', + categories: [PieceCategory.MARKETING], + authors: ["pfernandez98","kishanprmr","MoShizzle","abuaboud"], + actions: [ + generateImage, + createCustomApiCallAction({ + baseUrl: () => 'https://api.robolly.com', + auth: robollyAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/robolly/src/lib/actions/generate-image.action.ts b/packages/pieces/community/robolly/src/lib/actions/generate-image.action.ts new file mode 100644 index 0000000..f243703 --- /dev/null +++ b/packages/pieces/community/robolly/src/lib/actions/generate-image.action.ts @@ -0,0 +1,141 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { HttpMethod, httpClient } from "@activepieces/pieces-common"; +import { DynamicPropsValue, Property, createAction } from "@activepieces/pieces-framework"; + +export const generateImage = createAction({ + description: 'Generate an image using Robolly', + displayName: 'Generate Image', + name: 'generate_image', + props: { + template_id: Property.Dropdown({ + displayName: 'Template', + required: true, + description: 'Select your template. (If you want to use Template ID. Click on the "(x)" above this field. Template ID can be found by opening a template and going to “Render”. Being there copy the template ID from the top right.)', + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Enter your API key first', + options: [] + }; + } + try { + const request = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.robolly.com/v1/templates`, + headers: { + 'Authorization': `Bearer ${auth}` + } + }); + + return { + disabled: false, + options: request.body['templates'].map((template: any) => { + return { + label: template.name, + value: template.id + }; + }) + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: "Couldn't load templates, API key is invalid" + }; + } + } + }), + format: Property.StaticDropdown({ + displayName: 'Format', + required: true, + description: 'The format of the image to generate.', + defaultValue: 'jpg', + options: { + "options" : [ + { + "label": "JPG", + "value": 'jpg', + }, + { + "label": "PNG", + "value": 'png', + }, + { + "label": "PDF", + "value": 'pdf', + } + ] + } + }), + fields: Property.DynamicProperties({ + displayName: 'Values', + description: 'The values to apply to the fields in the template.', + required: true, + refreshers: ["template_id"], + props: async ({ auth, template_id }) => { + if (!auth) return {}; + if (!template_id) return {}; + + const fields: DynamicPropsValue = {}; + + const request = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.robolly.com/v1/templates/${template_id}/accepted-modifications`, + headers: { + 'Authorization': `Bearer ${auth}` + } + }); + + request.body['acceptedModifications'].map((field: any) => { + fields[field.key] = Property.ShortText({ + displayName: field.key, + description: `Type: ${field.type}`, + required: false + }) + }) + return fields + } + }), + modifications: Property.Object({ + displayName: 'Extra Modifications', + description: 'The extra modifications to apply to the image. See "Detailed dynamic modifications" in https://robolly.com/docs/api-reference/', + required: false, + + }) + }, + async run({ auth, propsValue }){ + + const fields = propsValue.fields; + + const queryParams: Record = { + }; + + queryParams['json'] = "" + + for (const key in propsValue.modifications) { + const value = propsValue.modifications[key]; + queryParams[key as string] = value as string; + } + + Object.keys(fields).forEach(k => { + if (fields[k] !== '') { + queryParams[k] = fields[k] + } + }) + + const request = await httpClient.sendRequest({ + method: HttpMethod.GET, + queryParams: queryParams, + url: `https://api.robolly.com/templates/${propsValue.template_id}/render/${propsValue.format}`, + headers: { + 'Authorization': `Bearer ${auth}` + }, + body: propsValue.modifications + }); + + + return request.body; + } +}); diff --git a/packages/pieces/community/robolly/tsconfig.json b/packages/pieces/community/robolly/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/robolly/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/robolly/tsconfig.lib.json b/packages/pieces/community/robolly/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/robolly/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/rss/.babelrc b/packages/pieces/community/rss/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/rss/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/rss/.eslintrc.json b/packages/pieces/community/rss/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/rss/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/rss/README.md b/packages/pieces/community/rss/README.md new file mode 100644 index 0000000..18b00eb --- /dev/null +++ b/packages/pieces/community/rss/README.md @@ -0,0 +1,7 @@ +# pieces-rss + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-rss` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/rss/package.json b/packages/pieces/community/rss/package.json new file mode 100644 index 0000000..844f521 --- /dev/null +++ b/packages/pieces/community/rss/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-rss", + "version": "0.4.0" +} \ No newline at end of file diff --git a/packages/pieces/community/rss/project.json b/packages/pieces/community/rss/project.json new file mode 100644 index 0000000..4670d91 --- /dev/null +++ b/packages/pieces/community/rss/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-rss", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/rss/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/rss", + "tsConfig": "packages/pieces/community/rss/tsconfig.lib.json", + "packageJson": "packages/pieces/community/rss/package.json", + "main": "packages/pieces/community/rss/src/index.ts", + "assets": [ + "packages/pieces/community/rss/*.md", + { + "input": "packages/pieces/community/rss/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/rss/src/index.ts b/packages/pieces/community/rss/src/index.ts new file mode 100644 index 0000000..beb689d --- /dev/null +++ b/packages/pieces/community/rss/src/index.ts @@ -0,0 +1,18 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { rssNewItemTrigger } from './lib/triggers/new-item-trigger'; +import { rssNewItemListTrigger } from './lib/triggers/new-item-list-triggers'; + +export const rssFeed = createPiece({ + displayName: 'RSS Feed', + description: 'Stay updated with RSS feeds', + authors: ["Abdallah-Alwarawreh","kishanprmr","khaledmashaly","abuaboud", "Kevinyu-alan"], + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/rss.png', + categories: [], + auth: PieceAuth.None(), + actions: [], + triggers: [ + rssNewItemTrigger, + rssNewItemListTrigger + ], +}); diff --git a/packages/pieces/community/rss/src/lib/common/getId.ts b/packages/pieces/community/rss/src/lib/common/getId.ts new file mode 100644 index 0000000..9267280 --- /dev/null +++ b/packages/pieces/community/rss/src/lib/common/getId.ts @@ -0,0 +1,14 @@ +// Some RSS feeds use the id field, some use the guid field, and some use neither. +export function getId(item: { id: string; guid: string }) { + if (item === undefined) { + return undefined; + } + if (item.guid) { + return item.guid; + } + if (item.id) { + return item.id; + } + return JSON.stringify(item); + } + \ No newline at end of file diff --git a/packages/pieces/community/rss/src/lib/common/props.ts b/packages/pieces/community/rss/src/lib/common/props.ts new file mode 100644 index 0000000..2f8dea2 --- /dev/null +++ b/packages/pieces/community/rss/src/lib/common/props.ts @@ -0,0 +1,14 @@ +import { Property } from '@activepieces/pieces-framework'; + +export const rssFeedUrl = Property.ShortText({ + displayName: 'RSS Feed URL', + description: 'Single RSS feed URL', + required: true, +}); + +export const rssFeedUrls = Property.Array({ + displayName: 'RSS Feed URLs', + description: 'List of RSS feed URLs', + required: true, + defaultValue: [], +}); \ No newline at end of file diff --git a/packages/pieces/community/rss/src/lib/common/sampleData.ts b/packages/pieces/community/rss/src/lib/common/sampleData.ts new file mode 100644 index 0000000..5a0e240 --- /dev/null +++ b/packages/pieces/community/rss/src/lib/common/sampleData.ts @@ -0,0 +1,113 @@ +export const sampleData = { + title: 'AWS Cloud Quest: Container Services', + description: + '

This is the DIY challenge of the Container Services in AWS Cloud Quest.

\n\n

', + summary: + '

This is the DIY challenge of the Container Services in AWS Cloud Quest.', + date: '2023-03-08T21:57:48.000Z', + pubdate: '2023-03-08T21:57:48.000Z', + pubDate: '2023-03-08T21:57:48.000Z', + link: 'https://dev.to/arc/aws-cloud-quest-container-services-1hi7', + guid: 'https://dev.to/arc/aws-cloud-quest-container-services-1hi7', + author: 'architec', + comments: null, + origlink: null, + image: {}, + source: {}, + categories: ['aws'], + enclosures: [], + 'rss:@': {}, + 'rss:title': { + '@': {}, + '#': 'AWS Cloud Quest: Container Services', + }, + 'dc:creator': { + '@': {}, + '#': 'architec', + }, + 'rss:pubdate': { + '@': {}, + '#': 'Wed, 08 Mar 2023 21:57:48 +0000', + }, + 'rss:link': { + '@': {}, + '#': 'https://dev.to/arc/aws-cloud-quest-container-services-1hi7', + }, + permalink: 'https://dev.to/arc/aws-cloud-quest-container-services-1hi7', + 'rss:guid': { + '@': {}, + '#': 'https://dev.to/arc/aws-cloud-quest-container-services-1hi7', + }, + 'rss:description': { + '@': {}, + '#': '

This is the DIY challenge of the Container Services in AWS Cloud Quest.

\n\n

Image description

\n\n

\n \n \n DIY Steps:\n

\n\n
    \n
  1. Repeat step 28-42
  2. \n
', + }, + 'rss:category': { + '@': {}, + '#': 'aws', + }, + meta: { + '#ns': [ + { + 'xmlns:atom': 'http://www.w3.org/2005/Atom', + }, + { + 'xmlns:dc': 'http://purl.org/dc/elements/1.1/', + }, + ], + '@': [ + { + 'xmlns:atom': 'http://www.w3.org/2005/Atom', + }, + { + 'xmlns:dc': 'http://purl.org/dc/elements/1.1/', + }, + ], + '#xml': { + version: '1.0', + encoding: 'UTF-8', + }, + '#type': 'rss', + '#version': '2.0', + title: 'DEV Community', + description: 'The most recent home feed on DEV Community.', + date: null, + pubdate: null, + pubDate: null, + link: 'https://dev.to/', + xmlurl: 'https://dev.to/feed/', + xmlUrl: 'https://dev.to/feed/', + author: null, + language: 'en', + favicon: null, + copyright: null, + generator: null, + cloud: {}, + image: {}, + categories: [], + 'rss:@': {}, + 'rss:title': { + '@': {}, + '#': 'DEV Community', + }, + 'rss:description': { + '@': {}, + '#': 'The most recent home feed on DEV Community.', + }, + 'rss:link': { + '@': {}, + '#': 'https://dev.to/', + }, + 'atom:link': { + '@': { + rel: 'self', + type: 'application/rss+xml', + href: 'https://dev.to/feed/', + }, + }, + 'rss:language': { + '@': {}, + '#': 'en', + }, + }, + } \ No newline at end of file diff --git a/packages/pieces/community/rss/src/lib/triggers/new-item-list-triggers.ts b/packages/pieces/community/rss/src/lib/triggers/new-item-list-triggers.ts new file mode 100644 index 0000000..169d3eb --- /dev/null +++ b/packages/pieces/community/rss/src/lib/triggers/new-item-list-triggers.ts @@ -0,0 +1,176 @@ +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { + PieceAuthProperty, + PiecePropValueSchema, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { rssFeedUrls } from '../common/props'; +import FeedParser from 'feedparser'; +import axios from 'axios'; +import { isNil } from '@activepieces/shared'; +import dayjs from 'dayjs'; +import { getId } from '../common/getId'; +import { sampleData } from '../common/sampleData'; + +type PollingProps = { + rss_feed_urls: string[]; +}; + +export const rssNewItemListTrigger = createTrigger({ + name: 'new-item-list', + displayName: 'New Items in Multiple Feeds', + description: 'Runs when a new item is added in one of the RSS feed', + type: TriggerStrategy.POLLING, + sampleData: sampleData, + props: { + rss_feed_urls: rssFeedUrls, + }, + async test({ auth, propsValue, store, files }): Promise { + return await pollingHelper.test(polling, { + auth, + store: store, + propsValue: propsValue as PollingProps, + files: files, + }); + }, + async onEnable({ auth, propsValue, store }): Promise { + await pollingHelper.onEnable(polling, { + auth, + store: store, + propsValue: propsValue as PollingProps, + }); + }, + + async onDisable({ auth, propsValue, store }): Promise { + const lastFetchDate = await store.get('_lastRssPublishDate'); + if (!isNil(lastFetchDate)) { + await store.delete('_lastRssPublishDate'); + } + await pollingHelper.onDisable(polling, { + auth, + store: store, + propsValue: propsValue as PollingProps, + }); + }, + + async run({ auth, propsValue, store, files }): Promise { + const lastFetchDate = await store.get('_lastRssPublishDate'); + const newItems = ( + await pollingHelper.poll(polling, { + auth, + store: store, + propsValue: propsValue as PollingProps, + files: files, + }) + ).filter((f) => { + if (isNil(lastFetchDate)) { + return true; + } + const newItem = f as { pubdate: string; pubDate: string }; + const newDate = newItem.pubdate ?? newItem.pubDate; + if (isNil(newDate)) { + return true; + } + return dayjs(newDate).unix() > lastFetchDate; + }); + let newFetchDateUnix = lastFetchDate; + for (const item of newItems) { + const newItem = item as { pubdate: string; pubDate: string }; + const newDate = newItem.pubdate ?? newItem.pubDate; + if (!isNil(newDate)) { + const newDateUnix = dayjs(newDate).unix(); + if (isNil(newFetchDateUnix) || newDateUnix > newFetchDateUnix) { + newFetchDateUnix = newDateUnix; + } + } + } + if (!isNil(newFetchDateUnix)) { + await store.put('_lastRssPublishDate', newFetchDateUnix); + } + return newItems.sort((a, b) => { + const aDate = + (a as { pubdate: string; pubDate: string }).pubdate ?? + (a as { pubdate: string; pubDate: string }).pubDate; + const bDate = + (b as { pubdate: string; pubDate: string }).pubdate ?? + (b as { pubdate: string; pubDate: string }).pubDate; + if (aDate && bDate) { + const aUnix = dayjs(aDate).unix(); + const bUnix = dayjs(bDate).unix(); + if (aUnix === bUnix) { + return newItems.indexOf(a) - newItems.indexOf(b); + } else { + return bUnix - aUnix; + } + } else { + return newItems.indexOf(a) - newItems.indexOf(b); + } + }); + }, +}); + +async function getRssItemsFromMultipleUrls(urls: string[]): Promise { + const allItems: any[] = []; + await Promise.all( + urls.map(async ( url ) => { + try { + const items = await getRssItems(url); + allItems.push(...items); + } catch (error) { + console.error(`Error fetching RSS feed from ${url}:`, error); + } + }) + ); + return allItems; +} + +const polling: Polling, PollingProps> = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ store, propsValue }) => { + const items = await getRssItemsFromMultipleUrls(propsValue.rss_feed_urls); + return items.map((item) => ({ + id: getId(item), + data: item, + })); + }, +}; + +function getRssItems(url: string): Promise { + return new Promise((resolve, reject) => { + axios + .get(url, { + responseType: 'stream', + }) + .then((response) => { + const feedparser = new FeedParser({ + addmeta: true, + }); + response.data.pipe(feedparser); + const items: any[] = []; + + feedparser.on('readable', () => { + let item = feedparser.read(); + while (item) { + items.push(item); + item = feedparser.read(); + } + }); + + feedparser.on('end', () => { + resolve(items); + }); + + feedparser.on('error', (error: any) => { + reject(error); + }); + }) + .catch((error) => { + reject(error); + }); + }); +} diff --git a/packages/pieces/community/rss/src/lib/triggers/new-item-trigger.ts b/packages/pieces/community/rss/src/lib/triggers/new-item-trigger.ts new file mode 100644 index 0000000..cd008cc --- /dev/null +++ b/packages/pieces/community/rss/src/lib/triggers/new-item-trigger.ts @@ -0,0 +1,167 @@ +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { + PieceAuthProperty, + PiecePropValueSchema, + Store, + StoreScope, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { rssFeedUrl } from '../common/props'; +import FeedParser from 'feedparser'; +import axios from 'axios'; +import { isNil } from '@activepieces/shared'; +import dayjs from 'dayjs'; +import { getId } from '../common/getId'; +import { sampleData } from '../common/sampleData'; + +export const rssNewItemTrigger = createTrigger({ + name: 'new-item', + displayName: 'New Item In Feed', + description: 'Runs when a new item is added in the RSS feed', + type: TriggerStrategy.POLLING, + sampleData: sampleData, + props: { + rss_feed_url: rssFeedUrl, + }, + async test({ auth, propsValue, store, files }): Promise { + return await pollingHelper.test(polling, { + auth, + store: store, + propsValue: propsValue, + files: files, + }); + }, + async onEnable({ auth, propsValue, store }): Promise { + await pollingHelper.onEnable(polling, { + auth, + store: store, + propsValue: propsValue, + }); + }, + + async onDisable({ auth, propsValue, store }): Promise { + const lastFetchDate = await store.get('_lastRssPublishDate'); + if (!isNil(lastFetchDate)) { + await store.delete('_lastRssPublishDate'); + } + await pollingHelper.onDisable(polling, { + auth, + store: store, + propsValue: propsValue, + }); + }, + + async run({ auth, propsValue, store, files }): Promise { + const lastFetchDate = await store.get('_lastRssPublishDate'); + const newItems = ( + await pollingHelper.poll(polling, { + auth, + store: store, + propsValue: propsValue, + files: files, + }) + ).filter((f) => { + if (isNil(lastFetchDate)) { + return true; + } + const newItem = f as { pubdate: string; pubDate: string }; + const newDate = newItem.pubdate ?? newItem.pubDate; + if (isNil(newDate)) { + return true; + } + return dayjs(newDate).unix() > lastFetchDate; + }); + let newFetchDateUnix = lastFetchDate; + for (const item of newItems) { + const newItem = item as { pubdate: string; pubDate: string }; + const newDate = newItem.pubdate ?? newItem.pubDate; + if (!isNil(newDate)) { + const newDateUnix = dayjs(newDate).unix(); + if (isNil(newFetchDateUnix) || newDateUnix > newFetchDateUnix) { + newFetchDateUnix = newDateUnix; + } + } + } + if (!isNil(newFetchDateUnix)) { + await store.put('_lastRssPublishDate', newFetchDateUnix); + } + return newItems.sort((a, b) => { + const aDate = + (a as { pubdate: string; pubDate: string }).pubdate ?? + (a as { pubdate: string; pubDate: string }).pubDate; + const bDate = + (b as { pubdate: string; pubDate: string }).pubdate ?? + (b as { pubdate: string; pubDate: string }).pubDate; + if (aDate && bDate) { + const aUnix = dayjs(aDate).unix(); + const bUnix = dayjs(bDate).unix(); + if (aUnix === bUnix) { + return newItems.indexOf(a) - newItems.indexOf(b); + } else { + return bUnix - aUnix; + } + } else { + return newItems.indexOf(a) - newItems.indexOf(b); + } + }); + }, +}); + +const polling: Polling< + PiecePropValueSchema, + { rss_feed_url: string } +> = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ + propsValue, + }: { + store: Store; + propsValue: { rss_feed_url: string }; + }) => { + const items = await getRssItems(propsValue.rss_feed_url); + return items.map((item) => ({ + id: getId(item), + data: item, + })); + }, +}; + +function getRssItems(url: string): Promise { + return new Promise((resolve, reject) => { + axios + .get(url, { + responseType: 'stream', + }) + .then((response) => { + const feedparser = new FeedParser({ + addmeta: true, + }); + response.data.pipe(feedparser); + const items: any[] = []; + + feedparser.on('readable', () => { + let item = feedparser.read(); + while (item) { + items.push(item); + item = feedparser.read(); + } + }); + + feedparser.on('end', () => { + resolve(items); + }); + + feedparser.on('error', (error: any) => { + reject(error); + }); + }) + .catch((error) => { + reject(error); + }); + }); +} diff --git a/packages/pieces/community/rss/tsconfig.json b/packages/pieces/community/rss/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/rss/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/rss/tsconfig.lib.json b/packages/pieces/community/rss/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/rss/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/saastic/.eslintrc.json b/packages/pieces/community/saastic/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/saastic/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/saastic/README.md b/packages/pieces/community/saastic/README.md new file mode 100644 index 0000000..5af9b0f --- /dev/null +++ b/packages/pieces/community/saastic/README.md @@ -0,0 +1,7 @@ +# pieces-saastic + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-saastic` to build the library. diff --git a/packages/pieces/community/saastic/package.json b/packages/pieces/community/saastic/package.json new file mode 100644 index 0000000..8e3cbba --- /dev/null +++ b/packages/pieces/community/saastic/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-saastic", + "version": "0.0.7" +} \ No newline at end of file diff --git a/packages/pieces/community/saastic/project.json b/packages/pieces/community/saastic/project.json new file mode 100644 index 0000000..cecb123 --- /dev/null +++ b/packages/pieces/community/saastic/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-saastic", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/saastic/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/saastic", + "tsConfig": "packages/pieces/community/saastic/tsconfig.lib.json", + "packageJson": "packages/pieces/community/saastic/package.json", + "main": "packages/pieces/community/saastic/src/index.ts", + "assets": [ + "packages/pieces/community/saastic/*.md", + { + "input": "packages/pieces/community/saastic/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-saastic {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/saastic/src/index.ts b/packages/pieces/community/saastic/src/index.ts new file mode 100644 index 0000000..12fe6a2 --- /dev/null +++ b/packages/pieces/community/saastic/src/index.ts @@ -0,0 +1,62 @@ +import { + AuthenticationType, + createCustomApiCallAction, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createCharge } from './lib/actions/create-charge'; +import { createCustomer } from './lib/actions/create-customer'; + +export const saasticAuth = PieceAuth.SecretText({ + description: + ' You can find your project’s API key here: https://saastic.com/settings/developers', + displayName: 'Api Key', + required: true, + validate: async (auth) => { + try { + await httpClient.sendRequest<{ + data: { id: string }[]; + }>({ + url: 'https://api.saastic.com/beacon/customers', + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.auth as string, + }, + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API token', + }; + } + }, +}); + +export const saastic = createPiece({ + displayName: 'Saastic', + description: 'Revenue and churn analytics for Stripe', + + auth: saasticAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/saastic.png', + categories: [PieceCategory.MARKETING], + authors: ["joselupianez","kishanprmr","MoShizzle","abuaboud"], + actions: [ + createCustomer, + createCharge, + createCustomApiCallAction({ + baseUrl: () => 'https://api.saastic.com', + auth: saasticAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/saastic/src/lib/actions/create-charge.ts b/packages/pieces/community/saastic/src/lib/actions/create-charge.ts new file mode 100644 index 0000000..2a83c70 --- /dev/null +++ b/packages/pieces/community/saastic/src/lib/actions/create-charge.ts @@ -0,0 +1,73 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { + HttpMethod, + AuthenticationType, + httpClient, + HttpRequest, + propsValidation, +} from '@activepieces/pieces-common'; +import { z } from 'zod'; +import { saasticCommon } from '../common'; +import { saasticAuth } from '../..'; + +export const createCharge = createAction({ + auth: saasticAuth, + name: 'create_charge', + displayName: 'Create a Customer Charge', + description: 'Creates a customer charge.', + + props: { + email: Property.LongText({ + displayName: 'Email', + description: "The customer's email address.", + required: true, + }), + amount: Property.Number({ + displayName: 'Amount', + description: 'The amount charged in the smallest currency unit.', + required: false, + }), + currency: Property.ShortText({ + displayName: 'Currency', + description: 'The ISO currency code.', + required: false, + defaultValue: 'USD', + }), + charged_at: Property.DateTime({ + displayName: 'Charge date', + description: 'The date the customer was charged.', + required: false, + }), + }, + + async run(context) { + await propsValidation.validateZod(context.propsValue, { + email: z.string().email(), + }); + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${saasticCommon.baseUrl}/charges`, + body: { + email: context.propsValue.email || '', + amount: context.propsValue.amount || '', + currency: context.propsValue.currency || '', + charged_at: context.propsValue.charged_at || '', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + queryParams: {}, + }; + + await httpClient.sendRequest(request); + + return { + success: true, + }; + }, +}); diff --git a/packages/pieces/community/saastic/src/lib/actions/create-customer.ts b/packages/pieces/community/saastic/src/lib/actions/create-customer.ts new file mode 100644 index 0000000..5164c01 --- /dev/null +++ b/packages/pieces/community/saastic/src/lib/actions/create-customer.ts @@ -0,0 +1,78 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { + HttpMethod, + AuthenticationType, + httpClient, + HttpRequest, + propsValidation, +} from '@activepieces/pieces-common'; +import { z } from 'zod'; +import { saasticCommon } from '../common'; +import { saasticAuth } from '../..'; + +export const createCustomer = createAction({ + auth: saasticAuth, + name: 'create_customer', + displayName: 'Create or Update a Customer', + description: 'Create or update a customer.', + + props: { + first_name: Property.LongText({ + displayName: 'First Name', + description: "The customer's first name.", + required: true, + }), + last_name: Property.LongText({ + displayName: 'Last Name', + description: "The customer's last name.", + required: true, + }), + email: Property.LongText({ + displayName: 'Email', + description: "The customer's email address.", + required: true, + }), + phone: Property.LongText({ + displayName: 'Phone', + description: "The customer's phone number. Ex: +15555555555", + required: false, + }), + signed_up_at: Property.DateTime({ + displayName: 'Signup Date', + description: 'The date the customer signed up.', + required: false, + }), + }, + + async run(context) { + await propsValidation.validateZod(context.propsValue, { + email: z.string().email(), + signed_up_at: z.string().datetime().optional(), + }); + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${saasticCommon.baseUrl}/customers`, + body: { + first_name: context.propsValue.first_name || '', + last_name: context.propsValue.last_name || '', + email: context.propsValue.email || '', + phone: context.propsValue.phone || '', + signed_up_at: context.propsValue.signed_up_at || '', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + queryParams: {}, + }; + await httpClient.sendRequest(request); + + return { + success: true, + }; + }, +}); diff --git a/packages/pieces/community/saastic/src/lib/common/index.ts b/packages/pieces/community/saastic/src/lib/common/index.ts new file mode 100644 index 0000000..286561b --- /dev/null +++ b/packages/pieces/community/saastic/src/lib/common/index.ts @@ -0,0 +1,3 @@ +export const saasticCommon = { + baseUrl: 'https://api.saastic.com/beacon', +}; diff --git a/packages/pieces/community/saastic/tsconfig.json b/packages/pieces/community/saastic/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/saastic/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/saastic/tsconfig.lib.json b/packages/pieces/community/saastic/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/saastic/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/saleor/.eslintrc.json b/packages/pieces/community/saleor/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/saleor/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/saleor/README.md b/packages/pieces/community/saleor/README.md new file mode 100644 index 0000000..03cf0fa --- /dev/null +++ b/packages/pieces/community/saleor/README.md @@ -0,0 +1,7 @@ +# pieces-saleor + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-saleor` to build the library. diff --git a/packages/pieces/community/saleor/package.json b/packages/pieces/community/saleor/package.json new file mode 100644 index 0000000..2f58e85 --- /dev/null +++ b/packages/pieces/community/saleor/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-saleor", + "version": "0.0.1" +} diff --git a/packages/pieces/community/saleor/project.json b/packages/pieces/community/saleor/project.json new file mode 100644 index 0000000..215eb89 --- /dev/null +++ b/packages/pieces/community/saleor/project.json @@ -0,0 +1,45 @@ +{ + "name": "pieces-saleor", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/saleor/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/saleor", + "tsConfig": "packages/pieces/community/saleor/tsconfig.lib.json", + "packageJson": "packages/pieces/community/saleor/package.json", + "main": "packages/pieces/community/saleor/src/index.ts", + "assets": [ + "packages/pieces/community/saleor/*.md" + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/saleor/src/index.ts b/packages/pieces/community/saleor/src/index.ts new file mode 100644 index 0000000..c7f0b21 --- /dev/null +++ b/packages/pieces/community/saleor/src/index.ts @@ -0,0 +1,35 @@ +import { createPiece, PieceAuth, Property } from "@activepieces/pieces-framework"; +import { saleorRawGraphqlQuery } from "./lib/actions/raw-graphql-query"; +import { getOrder } from "./lib/actions/get-order"; +import { addOrderNote } from "./lib/actions/add-note-to-order"; + +export const saleorAuth = PieceAuth.CustomAuth({ + description: 'Saleor', + required: true, + props: { + apiUrl: Property.ShortText({ + displayName: 'Saleor API URL', + required: true, + description: 'Your Saleor GraphQL API endpoint' + }), + token: Property.ShortText({ + displayName: 'Saleor token', + required: true, + description: 'Your token' + }), + } +}) + +export const saleor = createPiece({ + displayName: "Saleor", + auth: saleorAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: "https://cdn.activepieces.com/pieces/saleor.png", + authors: ["Kevinyu-alan"], + actions: [ + saleorRawGraphqlQuery, + getOrder, + addOrderNote + ], + triggers: [], +}); diff --git a/packages/pieces/community/saleor/src/lib/actions/add-note-to-order.ts b/packages/pieces/community/saleor/src/lib/actions/add-note-to-order.ts new file mode 100644 index 0000000..f9164ae --- /dev/null +++ b/packages/pieces/community/saleor/src/lib/actions/add-note-to-order.ts @@ -0,0 +1,58 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { saleorAuth } from '../..'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const addOrderNote = createAction({ + name: 'addOrderNote', + displayName: 'Add Order Note', + description: 'Add a note to a Saleor order', + auth: saleorAuth, + props: { + orderId: Property.ShortText({ + displayName: 'Order ID', + description: 'The ID of the order', + required: true + }), + message: Property.LongText({ + displayName: 'Note Message', + description: 'The content of the note', + required: true + }) + }, + async run({auth, propsValue}) { + const { orderId, message } = propsValue; + const { token, apiUrl } = auth; + + const query = ` + mutation AddOrderNote($orderId: ID!, $message: String!) { + orderNoteAdd( + order: $orderId, + input: { + message: $message, + } + ) { + order { + id + number + } + } + } + ` + + const response = await httpClient.sendRequest({ + url: apiUrl, + method: HttpMethod.POST, + body: JSON.stringify({ + query: query, + variables: { orderId, message } + }), + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: token, + }, + }); + + return response; + } +}); \ No newline at end of file diff --git a/packages/pieces/community/saleor/src/lib/actions/get-order.ts b/packages/pieces/community/saleor/src/lib/actions/get-order.ts new file mode 100644 index 0000000..054a1f5 --- /dev/null +++ b/packages/pieces/community/saleor/src/lib/actions/get-order.ts @@ -0,0 +1,72 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { saleorAuth } from '../..'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getOrder = createAction({ + name: 'getOrder', + displayName: 'Get an order', + description: 'Get an order according to the ID', + auth: saleorAuth, + props: { + orderId: Property.ShortText({ + displayName: 'The ID of the order', + required: true + }), + }, + async run({auth, propsValue}) { + const orderId = propsValue.orderId + const { token, apiUrl } = auth; + + const query = ` + query GetOrder($orderId: ID!) { + order(id: $orderId) { + id + number + created + status + + total { + gross { + amount + currency + } + } + + user { + email + firstName + lastName + } + + lines { + productName + variantName + quantity + unitPrice { + gross { + amount + currency + } + } + } + } + } + ` + + const response = await httpClient.sendRequest({ + url: apiUrl, + method: HttpMethod.POST, + body: JSON.stringify({ + query: query, + variables: { orderId } + }), + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: token, + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/saleor/src/lib/actions/raw-graphql-query.ts b/packages/pieces/community/saleor/src/lib/actions/raw-graphql-query.ts new file mode 100644 index 0000000..8171811 --- /dev/null +++ b/packages/pieces/community/saleor/src/lib/actions/raw-graphql-query.ts @@ -0,0 +1,34 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { saleorAuth } from '../..'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const saleorRawGraphqlQuery = createAction({ + name: 'rawGraphqlQuery', + displayName: 'Raw GraphQL query', + description: 'Perform a raw GraphQL query', + auth: saleorAuth, + props: { + query: Property.LongText({ displayName: 'Query', required: true }), + variables: Property.Object({ displayName: 'Parameters', required: false }), + }, + async run({auth, propsValue}) { + const { query, variables } = propsValue; + const { token, apiUrl } = auth; + + const response = await httpClient.sendRequest({ + url: apiUrl, + method: HttpMethod.POST, + body: JSON.stringify({ + query: query, + variables: variables, + }), + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: token, + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/saleor/tsconfig.json b/packages/pieces/community/saleor/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/saleor/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/saleor/tsconfig.lib.json b/packages/pieces/community/saleor/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/saleor/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/salesforce/.eslintrc.json b/packages/pieces/community/salesforce/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/salesforce/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/salesforce/README.md b/packages/pieces/community/salesforce/README.md new file mode 100644 index 0000000..7596bfa --- /dev/null +++ b/packages/pieces/community/salesforce/README.md @@ -0,0 +1,7 @@ +# pieces-salesforce + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-salesforce` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/salesforce/package.json b/packages/pieces/community/salesforce/package.json new file mode 100644 index 0000000..272aa32 --- /dev/null +++ b/packages/pieces/community/salesforce/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-salesforce", + "version": "0.1.10" +} diff --git a/packages/pieces/community/salesforce/project.json b/packages/pieces/community/salesforce/project.json new file mode 100644 index 0000000..2e03867 --- /dev/null +++ b/packages/pieces/community/salesforce/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-salesforce", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/salesforce/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/salesforce", + "tsConfig": "packages/pieces/community/salesforce/tsconfig.lib.json", + "packageJson": "packages/pieces/community/salesforce/package.json", + "main": "packages/pieces/community/salesforce/src/index.ts", + "assets": [ + "packages/pieces/community/salesforce/*.md", + { + "input": "packages/pieces/community/salesforce/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/salesforce/src/index.ts b/packages/pieces/community/salesforce/src/index.ts new file mode 100644 index 0000000..b8e8b55 --- /dev/null +++ b/packages/pieces/community/salesforce/src/index.ts @@ -0,0 +1,78 @@ +import { + PieceAuth, + Property, + createPiece, + OAuth2PropertyValue, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createNewObject } from './lib/action/create-new-object'; +import { runQuery } from './lib/action/run-sf-query'; +import { UpdateObjectById } from './lib/action/update-object-by-id'; +import { upsertByExternalId } from './lib/action/upsert-by-external-id'; +import { upsertByExternalIdBulk } from './lib/action/upsert-by-external-id-bulk'; +import { newRecord } from './lib/trigger/new-record'; +import { newOrUpdatedRecord } from './lib/trigger/new-updated-record'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { ntfyAuth } from '@activepieces/piece-ntfy'; + +export const salesforceAuth = PieceAuth.OAuth2({ + props: { + environment: Property.StaticDropdown({ + displayName: 'Environment', + description: 'Choose environment', + required: true, + options: { + options: [ + { + label: 'Production', + value: 'login', + }, + { + label: 'Development', + value: 'test', + }, + ], + }, + defaultValue: 'login', + }), + }, + + required: true, + description: 'Authenticate with Salesforce Production', + authUrl: 'https://{environment}.salesforce.com/services/oauth2/authorize', + tokenUrl: 'https://{environment}.salesforce.com/services/oauth2/token', + scope: ['refresh_token', 'full'], +}); + +export const salesforce = createPiece({ + displayName: 'Salesforce', + description: 'CRM software solutions and enterprise cloud computing', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/salesforce.png', + authors: [ + 'HKudria', + 'tanoggy', + 'landonmoir', + 'kishanprmr', + 'khaledmashaly', + 'abuaboud', + ], + categories: [PieceCategory.SALES_AND_CRM], + auth: salesforceAuth, + actions: [ + runQuery, + createNewObject, + UpdateObjectById, + upsertByExternalId, + upsertByExternalIdBulk, + createCustomApiCallAction({ + baseUrl: (auth) => (auth as OAuth2PropertyValue).data['instance_url'], + auth: salesforceAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [newRecord, newOrUpdatedRecord], +}); diff --git a/packages/pieces/community/salesforce/src/lib/action/create-new-object.ts b/packages/pieces/community/salesforce/src/lib/action/create-new-object.ts new file mode 100644 index 0000000..8f53666 --- /dev/null +++ b/packages/pieces/community/salesforce/src/lib/action/create-new-object.ts @@ -0,0 +1,32 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { callSalesforceApi, salesforcesCommon } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { salesforceAuth } from '../..'; + +export const createNewObject = createAction({ + auth: salesforceAuth, + name: 'create_new_object', + displayName: 'Create Object (Advanced)', + description: 'Create new object', + props: { + object: salesforcesCommon.object, + data: Property.Json({ + displayName: 'Data', + description: 'Select mapped object', + required: true, + defaultValue: {}, + }), + }, + async run(context) { + const { data, object } = context.propsValue; + const response = await callSalesforceApi( + HttpMethod.POST, + context.auth, + `/services/data/v56.0/sobjects/${object}`, + { + ...data, + } + ); + return response; + }, +}); diff --git a/packages/pieces/community/salesforce/src/lib/action/run-sf-query.ts b/packages/pieces/community/salesforce/src/lib/action/run-sf-query.ts new file mode 100644 index 0000000..fcef6b4 --- /dev/null +++ b/packages/pieces/community/salesforce/src/lib/action/run-sf-query.ts @@ -0,0 +1,26 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { salesforcesCommon } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { querySalesforceApi } from '../common'; +import { salesforceAuth } from '../..'; + +export const runQuery = createAction({ + auth: salesforceAuth, + name: 'run_query', + displayName: 'Run Query (Advanced)', + description: 'Run a salesforce query', + props: { + query: Property.ShortText({ + displayName: 'Query', + description: 'Enter the query', + required: true, + }), + }, + async run(context) { + const { query } = context.propsValue; + const response = await await querySalesforceApi<{ + records: { CreatedDate: string }[]; + }>(HttpMethod.GET, context.auth, query); + return response; + }, +}); diff --git a/packages/pieces/community/salesforce/src/lib/action/update-object-by-id.ts b/packages/pieces/community/salesforce/src/lib/action/update-object-by-id.ts new file mode 100644 index 0000000..ab01259 --- /dev/null +++ b/packages/pieces/community/salesforce/src/lib/action/update-object-by-id.ts @@ -0,0 +1,38 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { callSalesforceApi, salesforcesCommon } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { salesforceAuth } from '../..'; + +export const UpdateObjectById = createAction({ + auth: salesforceAuth, + + name: 'update_object_by_id', + displayName: 'Update Object (Advanced)', + description: 'Update object by Id', + props: { + object: salesforcesCommon.object, + id: Property.ShortText({ + displayName: 'Id', + description: 'Select the Id', + required: true, + }), + data: Property.Json({ + displayName: 'Data', + description: 'Select mapped object', + required: true, + defaultValue: {}, + }), + }, + async run(context) { + const { object, id, data } = context.propsValue; + const response = await callSalesforceApi( + HttpMethod.PATCH, + context.auth, + `/services/data/v56.0/sobjects/${object}/${id}`, + { + ...data, + } + ); + return response; + }, +}); diff --git a/packages/pieces/community/salesforce/src/lib/action/upsert-by-external-id-bulk.ts b/packages/pieces/community/salesforce/src/lib/action/upsert-by-external-id-bulk.ts new file mode 100644 index 0000000..69e7f4d --- /dev/null +++ b/packages/pieces/community/salesforce/src/lib/action/upsert-by-external-id-bulk.ts @@ -0,0 +1,78 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { + createBulkJob, + getBulkJobInfo, + notifyBulkJobComplete, + salesforcesCommon, + uploadToBulkJob, +} from '../common'; + +import { HttpMethod } from '@activepieces/pieces-common'; +import { salesforceAuth } from '../..'; + +export const upsertByExternalIdBulk = createAction({ + auth: salesforceAuth, + name: 'upsert_by_external_id_bulk', + displayName: 'Bulk Upsert (Advanced)', + description: 'Bulk upsert a record by external id', + props: { + object: salesforcesCommon.object, + external_field: Property.ShortText({ + displayName: 'External Field', + description: 'Select the External Field', + required: true, + }), + records: Property.LongText({ + displayName: 'Records', + description: 'Select the Records (CSV format)', + required: true, + }), + }, + async run(context) { + const records = context.propsValue.records; + let jobId; + if (!records) { + throw new Error('Expect CSV of records to upsert'); + } + const { object, external_field } = context.propsValue; + + // create bulk job + const create = await createBulkJob(HttpMethod.POST, context.auth, { + object: object, + externalIdFieldName: external_field, + contentType: 'CSV', + operation: 'upsert', + lineEnding: 'CRLF', + }); + if (create.status == 200) { + jobId = create.body.id; + } else { + throw new Error(`job creation failed: ${JSON.stringify(create)}`); + } + // upload records to bulk job + await uploadToBulkJob(HttpMethod.PUT, context.auth, jobId, records).catch( + (e) => { + throw new Error(`job upload failed: ${JSON.stringify(e)}`); + } + ); + + // notify upload complete + await notifyBulkJobComplete( + HttpMethod.PATCH, + context.auth, + { state: 'UploadComplete' }, + jobId + ).catch((e) => { + throw new Error(`job failed: ${JSON.stringify(e)}`); + }); + + const response = await getBulkJobInfo( + HttpMethod.GET, + context.auth, + jobId + ).catch((e) => { + throw new Error(`job failed: ${JSON.stringify(e)}`); + }); + return response; + }, +}); diff --git a/packages/pieces/community/salesforce/src/lib/action/upsert-by-external-id.ts b/packages/pieces/community/salesforce/src/lib/action/upsert-by-external-id.ts new file mode 100644 index 0000000..e7d2c8d --- /dev/null +++ b/packages/pieces/community/salesforce/src/lib/action/upsert-by-external-id.ts @@ -0,0 +1,46 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { callSalesforceApi, salesforcesCommon } from '../common'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { salesforceAuth } from '../..'; + +export const upsertByExternalId = createAction({ + auth: salesforceAuth, + name: 'upsert_by_external_id', + displayName: 'Batch Upsert (Advanced)', + description: 'Batch upsert a record by external id', + props: { + object: salesforcesCommon.object, + external_field: Property.ShortText({ + displayName: 'External Field', + description: 'Select the External Field', + required: true, + }), + records: Property.Json({ + displayName: 'Records', + description: 'Select the Records', + required: true, + defaultValue: { + records: [], + }, + }), + }, + async run(context) { + const records = context.propsValue?.records?.records; + if (!records) { + throw new Error( + 'Expect records field inside json to be an array with records to upsert' + ); + } + const { object, external_field } = context.propsValue; + const response = await callSalesforceApi( + HttpMethod.PATCH, + context.auth, + `/services/data/v55.0/composite/sobjects/${object}/${external_field}`, + { + allOrNone: false, + ...context.propsValue.records, + } + ); + return response; + }, +}); diff --git a/packages/pieces/community/salesforce/src/lib/common/index.ts b/packages/pieces/community/salesforce/src/lib/common/index.ts new file mode 100644 index 0000000..3c0cd1f --- /dev/null +++ b/packages/pieces/community/salesforce/src/lib/common/index.ts @@ -0,0 +1,199 @@ +import { + AuthenticationType, + HttpMessageBody, + HttpMethod, + HttpResponse, + httpClient, +} from '@activepieces/pieces-common'; +import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; + +export const salesforcesCommon = { + object: Property.Dropdown({ + displayName: 'Object', + required: true, + description: 'Select the Object', + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + const options = await getSalesforceObjects(auth as OAuth2PropertyValue); + return { + disabled: false, + options: options.body['sobjects'] + .map((object: any) => { + return { + label: object.label, + value: object.name, + }; + }) + .sort((a: { label: string }, b: { label: string }) => + a.label.localeCompare(b.label) + ) + .filter((object: { label: string }) => !object.label.startsWith('_')), + }; + }, + }), + field: Property.Dropdown({ + displayName: 'Field', + description: 'Select the Field', + required: true, + refreshers: ['object'], + options: async ({ auth, object }) => { + if (auth === undefined || !object) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + const options = await getSalesforceFields( + auth as OAuth2PropertyValue, + object as string + ); + return { + disabled: false, + options: options.body['fields'].map((field: any) => { + return { + label: field.label, + value: field.name, + }; + }), + }; + }, + }), +}; + +export async function callSalesforceApi( + method: HttpMethod, + authentication: OAuth2PropertyValue, + url: string, + body: Record | undefined +): Promise> { + return await httpClient.sendRequest({ + method: method, + url: `${authentication.data['instance_url']}${url}`, + body, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authentication['access_token'], + }, + }); +} + +export async function querySalesforceApi( + method: HttpMethod, + authentication: OAuth2PropertyValue, + query: string +): Promise> { + return await httpClient.sendRequest({ + method: method, + url: `${authentication.data['instance_url']}/services/data/v56.0/query`, + queryParams: { + q: query, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authentication['access_token'], + }, + }); +} + +export async function createBulkJob( + method: HttpMethod, + authentication: OAuth2PropertyValue, + jobDetails: HttpMessageBody +): Promise> { + return await httpClient.sendRequest({ + method: method, + url: `${authentication.data['instance_url']}/services/data/v58.0/jobs/ingest/`, + body: jobDetails, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authentication['access_token'], + }, + }); +} + +export async function uploadToBulkJob( + method: HttpMethod, + authentication: OAuth2PropertyValue, + jobId: string, + csv: string +): Promise> { + return await httpClient.sendRequest({ + method: method, + url: `${authentication.data['instance_url']}/services/data/v58.0/jobs/ingest/${jobId}/batches`, + headers: { + 'Content-Type': 'text/csv', + }, + body: csv as unknown as HttpMessageBody, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authentication['access_token'], + }, + }); +} + +export async function notifyBulkJobComplete( + method: HttpMethod, + authentication: OAuth2PropertyValue, + message: HttpMessageBody, + jobId: string +): Promise> { + return await httpClient.sendRequest({ + method: method, + url: `${authentication.data['instance_url']}/services/data/v58.0/jobs/ingest/${jobId}`, + body: message, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authentication['access_token'], + }, + }); +} + +export async function getBulkJobInfo( + method: HttpMethod, + authentication: OAuth2PropertyValue, + jobId: string +): Promise> { + return await httpClient.sendRequest({ + method: method, + url: `${authentication.data['instance_url']}/services/data/v58.0/jobs/ingest/${jobId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authentication['access_token'], + }, + }); +} + +async function getSalesforceObjects( + authentication: OAuth2PropertyValue +): Promise> { + return await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${authentication.data['instance_url']}/services/data/v56.0/sobjects`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authentication['access_token'], + }, + }); +} +// Write function to list all fields name inside salesforce object +async function getSalesforceFields( + authentication: OAuth2PropertyValue, + object: string +): Promise> { + return await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${authentication.data['instance_url']}/services/data/v56.0/sobjects/${object}/describe`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authentication['access_token'], + }, + }); +} diff --git a/packages/pieces/community/salesforce/src/lib/trigger/new-record.ts b/packages/pieces/community/salesforce/src/lib/trigger/new-record.ts new file mode 100644 index 0000000..aab3fbd --- /dev/null +++ b/packages/pieces/community/salesforce/src/lib/trigger/new-record.ts @@ -0,0 +1,119 @@ +import { + DedupeStrategy, + HttpMethod, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { querySalesforceApi, salesforcesCommon } from '../common'; + +import dayjs from 'dayjs'; +import { salesforceAuth } from '../..'; + +export const newRecord = createTrigger({ + auth: salesforceAuth, + name: 'new_record', + displayName: 'New Record', + description: 'Triggers when there is new record', + props: { + object: salesforcesCommon.object, + conditions: Property.LongText({ + displayName: 'Conditions (Advanced)', + description: 'Enter a SOQL query where clause i. e. IsDeleted = TRUE', + required: false, + }), + }, + sampleData: {}, + type: TriggerStrategy.POLLING, + async test(ctx) { + return await pollingHelper.test(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + files: ctx.files, + }); + }, + async onEnable(ctx) { + await pollingHelper.onEnable(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + }); + }, + async onDisable(ctx) { + await pollingHelper.onDisable(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + }); + }, + async run(ctx) { + return await pollingHelper.poll(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + files: ctx.files, + }); + }, +}); + +const polling: Polling< + OAuth2PropertyValue, + { object: string | undefined; conditions: string | undefined } +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + const items = await getRecords( + auth, + propsValue.object!, + dayjs(lastFetchEpochMS).toISOString(), + propsValue.conditions + ); + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.CreatedDate).valueOf(), + data: item, + })); + }, +}; + +const getRecords = async ( + authentication: OAuth2PropertyValue, + object: string, + startDate: string, + conditions: string | undefined +) => { + const response = await querySalesforceApi<{ + records: { CreatedDate: string }[]; + }>( + HttpMethod.GET, + authentication, + constructQuery(object, 200, 0, startDate, conditions) + ); + return response.body['records']; +}; + +function constructQuery( + object: string, + limit: number, + offset: number, + startDate: string, + conditions: string | undefined +) { + return ` + SELECT + FIELDS(ALL) + FROM + ${object} + WHERE CreatedDate > ${startDate} ${ + conditions != undefined ? `AND ${conditions}` : '' + } + ORDER BY CreatedDate ASC + LIMIT ${limit} + OFFSET ${offset} + `; +} diff --git a/packages/pieces/community/salesforce/src/lib/trigger/new-updated-record.ts b/packages/pieces/community/salesforce/src/lib/trigger/new-updated-record.ts new file mode 100644 index 0000000..613f72d --- /dev/null +++ b/packages/pieces/community/salesforce/src/lib/trigger/new-updated-record.ts @@ -0,0 +1,114 @@ +import { + DedupeStrategy, + HttpMethod, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { querySalesforceApi, salesforcesCommon } from '../common'; + +import dayjs from 'dayjs'; +import { salesforceAuth } from '../..'; + +export const newOrUpdatedRecord = createTrigger({ + auth: salesforceAuth, + name: 'new_or_updated_record', + displayName: 'New or Updated Record', + description: 'Triggers when there is new or updated record', + props: { + object: salesforcesCommon.object, + conditions: Property.LongText({ + displayName: 'Conditions (Advanced)', + description: 'Enter a SOQL query where clause i. e. IsDeleted = TRUE', + required: false, + }), + }, + sampleData: {}, + type: TriggerStrategy.POLLING, + async test(ctx) { + return await pollingHelper.test(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + files: ctx.files, + }); + }, + async onEnable(ctx) { + await pollingHelper.onEnable(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + }); + }, + async onDisable(ctx) { + await pollingHelper.onDisable(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + }); + }, + async run(ctx) { + return await pollingHelper.poll(polling, ctx); + }, +}); + +const polling: Polling< + OAuth2PropertyValue, + { object: string | undefined; conditions: string | undefined } +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + const items = await getRecords( + auth, + propsValue.object!, + dayjs(lastFetchEpochMS).toISOString(), + propsValue.conditions + ); + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.LastModifiedDate).valueOf(), + data: item, + })); + }, +}; + +const getRecords = async ( + authentication: OAuth2PropertyValue, + object: string, + startDate: string, + conditions: string | undefined +) => { + const response = await querySalesforceApi<{ + records: { LastModifiedDate: string }[]; + }>( + HttpMethod.GET, + authentication, + constructQuery(object, 200, 0, startDate, conditions) + ); + return response.body['records']; +}; + +function constructQuery( + object: string, + limit: number, + offset: number, + startDate: string, + conditions: string | undefined +) { + return ` + SELECT + FIELDS(ALL) + FROM + ${object} + WHERE LastModifiedDate > ${startDate} ${ + conditions != undefined ? `AND ${conditions}` : '' + } + ORDER BY LastModifiedDate ASC + LIMIT ${limit} + OFFSET ${offset} + `; +} diff --git a/packages/pieces/community/salesforce/tsconfig.json b/packages/pieces/community/salesforce/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/salesforce/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/salesforce/tsconfig.lib.json b/packages/pieces/community/salesforce/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/salesforce/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/scenario/.eslintrc.json b/packages/pieces/community/scenario/.eslintrc.json new file mode 100644 index 0000000..d7449bb --- /dev/null +++ b/packages/pieces/community/scenario/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {}, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/scenario/README.md b/packages/pieces/community/scenario/README.md new file mode 100644 index 0000000..d5e82df --- /dev/null +++ b/packages/pieces/community/scenario/README.md @@ -0,0 +1,7 @@ +# pieces-scenario + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-scenario` to build the library. diff --git a/packages/pieces/community/scenario/package.json b/packages/pieces/community/scenario/package.json new file mode 100644 index 0000000..9d3298a --- /dev/null +++ b/packages/pieces/community/scenario/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-scenario", + "version": "0.0.1" +} diff --git a/packages/pieces/community/scenario/project.json b/packages/pieces/community/scenario/project.json new file mode 100644 index 0000000..064801a --- /dev/null +++ b/packages/pieces/community/scenario/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-scenario", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/scenario/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/scenario", + "tsConfig": "packages/pieces/community/scenario/tsconfig.lib.json", + "packageJson": "packages/pieces/community/scenario/package.json", + "main": "packages/pieces/community/scenario/src/index.ts", + "assets": [ + "packages/pieces/community/scenario/*.md", + { + "input": "packages/pieces/community/scenario/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/scenario/src/index.ts b/packages/pieces/community/scenario/src/index.ts new file mode 100644 index 0000000..f9f058d --- /dev/null +++ b/packages/pieces/community/scenario/src/index.ts @@ -0,0 +1,45 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { BasicAuthConnectionValue } from '@activepieces/shared'; + +export const scenarioAuth = PieceAuth.BasicAuth({ + description: + 'Follow [these instructions](https://docs.scenario.com/docs/get-api-key) to get your API key', + required: true, + username: Property.ShortText({ + displayName: 'API access key', + description: 'Starts with "api_"', + required: true, + }), + password: PieceAuth.SecretText({ + displayName: 'API secret', + required: true, + }), +}); + +export const scenario = createPiece({ + displayName: 'Scenario', + auth: scenarioAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/scenario.png', + authors: ['AdamSelene'], + actions: [ + createCustomApiCallAction({ + baseUrl: () => `https://api.cloud.scenario.com/v1/`, + auth: scenarioAuth, + authMapping: async (auth) => { + const { username, password } = auth as BasicAuthConnectionValue; + return { + Authorization: `Basic ${Buffer.from( + `${username}:${password}` + ).toString('base64')}`, + }; + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/scenario/tsconfig.json b/packages/pieces/community/scenario/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/scenario/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/scenario/tsconfig.lib.json b/packages/pieces/community/scenario/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/scenario/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/schedule/.eslintrc.json b/packages/pieces/community/schedule/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/schedule/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/schedule/README.md b/packages/pieces/community/schedule/README.md new file mode 100644 index 0000000..0ec3b5c --- /dev/null +++ b/packages/pieces/community/schedule/README.md @@ -0,0 +1,7 @@ +# pieces-schedule + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-schedule` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/schedule/package.json b/packages/pieces/community/schedule/package.json new file mode 100644 index 0000000..30c2b91 --- /dev/null +++ b/packages/pieces/community/schedule/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-schedule", + "version": "0.1.5" +} \ No newline at end of file diff --git a/packages/pieces/community/schedule/project.json b/packages/pieces/community/schedule/project.json new file mode 100644 index 0000000..4e19068 --- /dev/null +++ b/packages/pieces/community/schedule/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-schedule", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/schedule/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/schedule", + "tsConfig": "packages/pieces/community/schedule/tsconfig.lib.json", + "packageJson": "packages/pieces/community/schedule/package.json", + "main": "packages/pieces/community/schedule/src/index.ts", + "assets": [ + "packages/pieces/community/schedule/*.md", + { + "input": "packages/pieces/community/schedule/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/schedule/src/index.ts b/packages/pieces/community/schedule/src/index.ts new file mode 100644 index 0000000..9dbe6c1 --- /dev/null +++ b/packages/pieces/community/schedule/src/index.ts @@ -0,0 +1,27 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { cronExpressionTrigger } from './lib/triggers/cron-expression.trigger'; +import { everyDayTrigger } from './lib/triggers/every-day.trigger'; +import { everyHourTrigger } from './lib/triggers/every-hour.trigger'; +import { everyMonthTrigger } from './lib/triggers/every-month.trigger'; +import { everyWeekTrigger } from './lib/triggers/every-week.trigger'; +import { everyXMinutesTrigger } from './lib/triggers/every-x-minutes.trigger'; + +export const schedule = createPiece({ + displayName: 'Schedule', + logoUrl: 'https://cdn.activepieces.com/pieces/schedule.png', + description: 'Trigger flow with fixed schedule', + categories: [PieceCategory.CORE], + auth: PieceAuth.None(), + minimumSupportedRelease: '0.30.0', + authors: ["kishanprmr","AbdulTheActivePiecer","khaledmashaly","abuaboud"], + actions: [], + triggers: [ + everyXMinutesTrigger, + everyHourTrigger, + everyDayTrigger, + everyWeekTrigger, + everyMonthTrigger, + cronExpressionTrigger, + ], +}); diff --git a/packages/pieces/community/schedule/src/lib/common/index.ts b/packages/pieces/community/schedule/src/lib/common/index.ts new file mode 100644 index 0000000..1189bfc --- /dev/null +++ b/packages/pieces/community/schedule/src/lib/common/index.ts @@ -0,0 +1,1717 @@ +export const WEEK_DAYS = [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', +]; +export const MONTH_DAYS = [...Array(31).keys()]; +const ELEVEN_HOURS = [...Array(11).keys()]; +export const DAY_HOURS = [ + 'Midnight', + ...ELEVEN_HOURS.map((h) => h + 1 + ' am'), + 'Noon', + ...ELEVEN_HOURS.map((h) => h + 1 + ' pm'), +]; +export function validateHours(hours: number) { + if (!Number.isInteger(hours)) { + return 0; + } + const hourOfTheDay = Math.min(Math.max(hours, 0), 23); + return hourOfTheDay; +} +export function validateWeekDays(days: number) { + if (!Number.isInteger(days)) { + return 0; + } + const dayOfTheWeek = Math.min(Math.max(0, days), 6); + return dayOfTheWeek; +} +export function validateMonthDays(days: number) { + if (!Number.isInteger(days)) { + return 0; + } + const dayofTheMonth = Math.min(Math.max(0, days), 31); + return dayofTheMonth; +} + +export const timezoneOptions = [ + { + label: '(GMT-11:00) Pacific, Midway', + value: 'Pacific/Midway', + }, + { + label: '(GMT-11:00) Pacific, Niue', + value: 'Pacific/Niue', + }, + { + label: '(GMT-11:00) Pacific, Pago Pago', + value: 'Pacific/Pago_Pago', + }, + { + label: '(GMT-10:00) Pacific, Honolulu', + value: 'Pacific/Honolulu', + }, + { + label: '(GMT-10:00) Pacific, Rarotonga', + value: 'Pacific/Rarotonga', + }, + { + label: '(GMT-10:00) Pacific, Tahiti', + value: 'Pacific/Tahiti', + }, + { + label: '(GMT-09:30) Pacific, Marquesas', + value: 'Pacific/Marquesas', + }, + { + label: '(GMT-09:00) America, Adak', + value: 'America/Adak', + }, + { + label: '(GMT-09:00) Pacific, Gambier', + value: 'Pacific/Gambier', + }, + { + label: '(GMT-08:00) America, Anchorage', + value: 'America/Anchorage', + }, + { + label: '(GMT-08:00) America, Juneau', + value: 'America/Juneau', + }, + { + label: '(GMT-08:00) America, Metlakatla', + value: 'America/Metlakatla', + }, + { + label: '(GMT-08:00) America, Nome', + value: 'America/Nome', + }, + { + label: '(GMT-08:00) America, Sitka', + value: 'America/Sitka', + }, + { + label: '(GMT-08:00) America, Yakutat', + value: 'America/Yakutat', + }, + { + label: '(GMT-08:00) Pacific, Pitcairn', + value: 'Pacific/Pitcairn', + }, + { + label: '(GMT-07:00) America, Creston', + value: 'America/Creston', + }, + { + label: '(GMT-07:00) America, Dawson', + value: 'America/Dawson', + }, + { + label: '(GMT-07:00) America, Dawson Creek', + value: 'America/Dawson_Creek', + }, + { + label: '(GMT-07:00) America, Fort Nelson', + value: 'America/Fort_Nelson', + }, + { + label: '(GMT-07:00) America, Hermosillo', + value: 'America/Hermosillo', + }, + { + label: '(GMT-07:00) America, Los Angeles', + value: 'America/Los_Angeles', + }, + { + label: '(GMT-07:00) America, Mazatlan', + value: 'America/Mazatlan', + }, + { + label: '(GMT-07:00) America, Phoenix', + value: 'America/Phoenix', + }, + { + label: '(GMT-07:00) America, Tijuana', + value: 'America/Tijuana', + }, + { + label: '(GMT-07:00) America, Vancouver', + value: 'America/Vancouver', + }, + { + label: '(GMT-07:00) America, Whitehorse', + value: 'America/Whitehorse', + }, + { + label: '(GMT-06:00) America, Bahia Banderas', + value: 'America/Bahia_Banderas', + }, + { + label: '(GMT-06:00) America, Belize', + value: 'America/Belize', + }, + { + label: '(GMT-06:00) America, Boise', + value: 'America/Boise', + }, + { + label: '(GMT-06:00) America, Cambridge Bay', + value: 'America/Cambridge_Bay', + }, + { + label: '(GMT-06:00) America, Chihuahua', + value: 'America/Chihuahua', + }, + { + label: '(GMT-06:00) America, Ciudad Juarez', + value: 'America/Ciudad_Juarez', + }, + { + label: '(GMT-06:00) America, Costa Rica', + value: 'America/Costa_Rica', + }, + { + label: '(GMT-06:00) America, Denver', + value: 'America/Denver', + }, + { + label: '(GMT-06:00) America, Edmonton', + value: 'America/Edmonton', + }, + { + label: '(GMT-06:00) America, El Salvador', + value: 'America/El_Salvador', + }, + { + label: '(GMT-06:00) America, Guatemala', + value: 'America/Guatemala', + }, + { + label: '(GMT-06:00) America, Inuvik', + value: 'America/Inuvik', + }, + { + label: '(GMT-06:00) America, Managua', + value: 'America/Managua', + }, + { + label: '(GMT-06:00) America, Merida', + value: 'America/Merida', + }, + { + label: '(GMT-06:00) America, Mexico City', + value: 'America/Mexico_City', + }, + { + label: '(GMT-06:00) America, Monterrey', + value: 'America/Monterrey', + }, + { + label: '(GMT-06:00) America, Regina', + value: 'America/Regina', + }, + { + label: '(GMT-06:00) America, Swift Current', + value: 'America/Swift_Current', + }, + { + label: '(GMT-06:00) America, Tegucigalpa', + value: 'America/Tegucigalpa', + }, + { + label: '(GMT-06:00) Pacific, Easter', + value: 'Pacific/Easter', + }, + { + label: '(GMT-06:00) Pacific, Galapagos', + value: 'Pacific/Galapagos', + }, + { + label: '(GMT-05:00) America, Atikokan', + value: 'America/Atikokan', + }, + { + label: '(GMT-05:00) America, Bogota', + value: 'America/Bogota', + }, + { + label: '(GMT-05:00) America, Cancun', + value: 'America/Cancun', + }, + { + label: '(GMT-05:00) America, Cayman', + value: 'America/Cayman', + }, + { + label: '(GMT-05:00) America, Chicago', + value: 'America/Chicago', + }, + { + label: '(GMT-05:00) America, Eirunepe', + value: 'America/Eirunepe', + }, + { + label: '(GMT-05:00) America, Guayaquil', + value: 'America/Guayaquil', + }, + { + label: '(GMT-05:00) America, Indiana, Knox', + value: 'America/Indiana/Knox', + }, + { + label: '(GMT-05:00) America, Indiana, Tell City', + value: 'America/Indiana/Tell_City', + }, + { + label: '(GMT-05:00) America, Jamaica', + value: 'America/Jamaica', + }, + { + label: '(GMT-05:00) America, Lima', + value: 'America/Lima', + }, + { + label: '(GMT-05:00) America, Matamoros', + value: 'America/Matamoros', + }, + { + label: '(GMT-05:00) America, Menominee', + value: 'America/Menominee', + }, + { + label: '(GMT-05:00) America, North Dakota, Beulah', + value: 'America/North_Dakota/Beulah', + }, + { + label: '(GMT-05:00) America, North Dakota, Center', + value: 'America/North_Dakota/Center', + }, + { + label: '(GMT-05:00) America, North Dakota, New Salem', + value: 'America/North_Dakota/New_Salem', + }, + { + label: '(GMT-05:00) America, Ojinaga', + value: 'America/Ojinaga', + }, + { + label: '(GMT-05:00) America, Panama', + value: 'America/Panama', + }, + { + label: '(GMT-05:00) America, Rankin Inlet', + value: 'America/Rankin_Inlet', + }, + { + label: '(GMT-05:00) America, Resolute', + value: 'America/Resolute', + }, + { + label: '(GMT-05:00) America, Rio Branco', + value: 'America/Rio_Branco', + }, + { + label: '(GMT-05:00) America, Winnipeg', + value: 'America/Winnipeg', + }, + { + label: '(GMT-04:00) America, Anguilla', + value: 'America/Anguilla', + }, + { + label: '(GMT-04:00) America, Antigua', + value: 'America/Antigua', + }, + { + label: '(GMT-04:00) America, Aruba', + value: 'America/Aruba', + }, + { + label: '(GMT-04:00) America, Asuncion', + value: 'America/Asuncion', + }, + { + label: '(GMT-04:00) America, Barbados', + value: 'America/Barbados', + }, + { + label: '(GMT-04:00) America, Blanc-Sablon', + value: 'America/Blanc-Sablon', + }, + { + label: '(GMT-04:00) America, Boa Vista', + value: 'America/Boa_Vista', + }, + { + label: '(GMT-04:00) America, Campo Grande', + value: 'America/Campo_Grande', + }, + { + label: '(GMT-04:00) America, Caracas', + value: 'America/Caracas', + }, + { + label: '(GMT-04:00) America, Cuiaba', + value: 'America/Cuiaba', + }, + { + label: '(GMT-04:00) America, Curacao', + value: 'America/Curacao', + }, + { + label: '(GMT-04:00) America, Detroit', + value: 'America/Detroit', + }, + { + label: '(GMT-04:00) America, Dominica', + value: 'America/Dominica', + }, + { + label: '(GMT-04:00) America, Grand Turk', + value: 'America/Grand_Turk', + }, + { + label: '(GMT-04:00) America, Grenada', + value: 'America/Grenada', + }, + { + label: '(GMT-04:00) America, Guadeloupe', + value: 'America/Guadeloupe', + }, + { + label: '(GMT-04:00) America, Guyana', + value: 'America/Guyana', + }, + { + label: '(GMT-04:00) America, Havana', + value: 'America/Havana', + }, + { + label: '(GMT-04:00) America, Indiana, Indianapolis', + value: 'America/Indiana/Indianapolis', + }, + { + label: '(GMT-04:00) America, Indiana, Marengo', + value: 'America/Indiana/Marengo', + }, + { + label: '(GMT-04:00) America, Indiana, Petersburg', + value: 'America/Indiana/Petersburg', + }, + { + label: '(GMT-04:00) America, Indiana, Vevay', + value: 'America/Indiana/Vevay', + }, + { + label: '(GMT-04:00) America, Indiana, Vincennes', + value: 'America/Indiana/Vincennes', + }, + { + label: '(GMT-04:00) America, Indiana, Winamac', + value: 'America/Indiana/Winamac', + }, + { + label: '(GMT-04:00) America, Iqaluit', + value: 'America/Iqaluit', + }, + { + label: '(GMT-04:00) America, Kentucky, Louisville', + value: 'America/Kentucky/Louisville', + }, + { + label: '(GMT-04:00) America, Kentucky, Monticello', + value: 'America/Kentucky/Monticello', + }, + { + label: '(GMT-04:00) America, Kralendijk', + value: 'America/Kralendijk', + }, + { + label: '(GMT-04:00) America, La Paz', + value: 'America/La_Paz', + }, + { + label: '(GMT-04:00) America, Lower Princes', + value: 'America/Lower_Princes', + }, + { + label: '(GMT-04:00) America, Manaus', + value: 'America/Manaus', + }, + { + label: '(GMT-04:00) America, Marigot', + value: 'America/Marigot', + }, + { + label: '(GMT-04:00) America, Martinique', + value: 'America/Martinique', + }, + { + label: '(GMT-04:00) America, Montserrat', + value: 'America/Montserrat', + }, + { + label: '(GMT-04:00) America, Nassau', + value: 'America/Nassau', + }, + { + label: '(GMT-04:00) America, New York', + value: 'America/New_York', + }, + { + label: '(GMT-04:00) America, Port of Spain', + value: 'America/Port_of_Spain', + }, + { + label: '(GMT-04:00) America, Port-au-Prince', + value: 'America/Port-au-Prince', + }, + { + label: '(GMT-04:00) America, Porto Velho', + value: 'America/Porto_Velho', + }, + { + label: '(GMT-04:00) America, Puerto Rico', + value: 'America/Puerto_Rico', + }, + { + label: '(GMT-04:00) America, Santiago', + value: 'America/Santiago', + }, + { + label: '(GMT-04:00) America, Santo Domingo', + value: 'America/Santo_Domingo', + }, + { + label: '(GMT-04:00) America, St. Barthelemy', + value: 'America/St_Barthelemy', + }, + { + label: '(GMT-04:00) America, St. Kitts', + value: 'America/St_Kitts', + }, + { + label: '(GMT-04:00) America, St. Lucia', + value: 'America/St_Lucia', + }, + { + label: '(GMT-04:00) America, St. Thomas', + value: 'America/St_Thomas', + }, + { + label: '(GMT-04:00) America, St. Vincent', + value: 'America/St_Vincent', + }, + { + label: '(GMT-04:00) America, Toronto', + value: 'America/Toronto', + }, + { + label: '(GMT-04:00) America, Tortola', + value: 'America/Tortola', + }, + { + label: '(GMT-03:00) America, Araguaina', + value: 'America/Araguaina', + }, + { + label: '(GMT-03:00) America, Argentina, Buenos Aires', + value: 'America/Argentina/Buenos_Aires', + }, + { + label: '(GMT-03:00) America, Argentina, Catamarca', + value: 'America/Argentina/Catamarca', + }, + { + label: '(GMT-03:00) America, Argentina, Cordoba', + value: 'America/Argentina/Cordoba', + }, + { + label: '(GMT-03:00) America, Argentina, Jujuy', + value: 'America/Argentina/Jujuy', + }, + { + label: '(GMT-03:00) America, Argentina, La Rioja', + value: 'America/Argentina/La_Rioja', + }, + { + label: '(GMT-03:00) America, Argentina, Mendoza', + value: 'America/Argentina/Mendoza', + }, + { + label: '(GMT-03:00) America, Argentina, Rio Gallegos', + value: 'America/Argentina/Rio_Gallegos', + }, + { + label: '(GMT-03:00) America, Argentina, Salta', + value: 'America/Argentina/Salta', + }, + { + label: '(GMT-03:00) America, Argentina, San Juan', + value: 'America/Argentina/San_Juan', + }, + { + label: '(GMT-03:00) America, Argentina, San Luis', + value: 'America/Argentina/San_Luis', + }, + { + label: '(GMT-03:00) America, Argentina, Tucuman', + value: 'America/Argentina/Tucuman', + }, + { + label: '(GMT-03:00) America, Argentina, Ushuaia', + value: 'America/Argentina/Ushuaia', + }, + { + label: '(GMT-03:00) America, Bahia', + value: 'America/Bahia', + }, + { + label: '(GMT-03:00) America, Belem', + value: 'America/Belem', + }, + { + label: '(GMT-03:00) America, Cayenne', + value: 'America/Cayenne', + }, + { + label: '(GMT-03:00) America, Fortaleza', + value: 'America/Fortaleza', + }, + { + label: '(GMT-03:00) America, Glace Bay', + value: 'America/Glace_Bay', + }, + { + label: '(GMT-03:00) America, Goose Bay', + value: 'America/Goose_Bay', + }, + { + label: '(GMT-03:00) America, Halifax', + value: 'America/Halifax', + }, + { + label: '(GMT-03:00) America, Maceio', + value: 'America/Maceio', + }, + { + label: '(GMT-03:00) America, Moncton', + value: 'America/Moncton', + }, + { + label: '(GMT-03:00) America, Montevideo', + value: 'America/Montevideo', + }, + { + label: '(GMT-03:00) America, Paramaribo', + value: 'America/Paramaribo', + }, + { + label: '(GMT-03:00) America, Punta Arenas', + value: 'America/Punta_Arenas', + }, + { + label: '(GMT-03:00) America, Recife', + value: 'America/Recife', + }, + { + label: '(GMT-03:00) America, Santarem', + value: 'America/Santarem', + }, + { + label: '(GMT-03:00) America, Sao Paulo', + value: 'America/Sao_Paulo', + }, + { + label: '(GMT-03:00) America, Thule', + value: 'America/Thule', + }, + { + label: '(GMT-03:00) Antarctica, Palmer', + value: 'Antarctica/Palmer', + }, + { + label: '(GMT-03:00) Antarctica, Rothera', + value: 'Antarctica/Rothera', + }, + { + label: '(GMT-03:00) Atlantic, Bermuda', + value: 'Atlantic/Bermuda', + }, + { + label: '(GMT-03:00) Atlantic, Stanley', + value: 'Atlantic/Stanley', + }, + { + label: '(GMT-02:30) America, St. Johns', + value: 'America/St_Johns', + }, + { + label: '(GMT-02:00) America, Miquelon', + value: 'America/Miquelon', + }, + { + label: '(GMT-02:00) America, Noronha', + value: 'America/Noronha', + }, + { + label: '(GMT-02:00) America, Nuuk', + value: 'America/Nuuk', + }, + { + label: '(GMT-02:00) Atlantic, South Georgia', + value: 'Atlantic/South_Georgia', + }, + { + label: '(GMT-01:00) Atlantic, Cape Verde', + value: 'Atlantic/Cape_Verde', + }, + { + label: '(GMT) Africa, Abidjan', + value: 'Africa/Abidjan', + }, + { + label: '(GMT) Africa, Accra', + value: 'Africa/Accra', + }, + { + label: '(GMT) Africa, Bamako', + value: 'Africa/Bamako', + }, + { + label: '(GMT) Africa, Banjul', + value: 'Africa/Banjul', + }, + { + label: '(GMT) Africa, Bissau', + value: 'Africa/Bissau', + }, + { + label: '(GMT) Africa, Conakry', + value: 'Africa/Conakry', + }, + { + label: '(GMT) Africa, Dakar', + value: 'Africa/Dakar', + }, + { + label: '(GMT) Africa, Freetown', + value: 'Africa/Freetown', + }, + { + label: '(GMT) Africa, Lome', + value: 'Africa/Lome', + }, + { + label: '(GMT) Africa, Monrovia', + value: 'Africa/Monrovia', + }, + { + label: '(GMT) Africa, Nouakchott', + value: 'Africa/Nouakchott', + }, + { + label: '(GMT) Africa, Ouagadougou', + value: 'Africa/Ouagadougou', + }, + { + label: '(GMT) Africa, Sao Tome', + value: 'Africa/Sao_Tome', + }, + { + label: '(GMT) America, Danmarkshavn', + value: 'America/Danmarkshavn', + }, + { + label: '(GMT) America, Scoresbysund', + value: 'America/Scoresbysund', + }, + { + label: '(GMT) Atlantic, Azores', + value: 'Atlantic/Azores', + }, + { + label: '(GMT) Atlantic, Reykjavik', + value: 'Atlantic/Reykjavik', + }, + { + label: '(GMT) Atlantic, St. Helena', + value: 'Atlantic/St_Helena', + }, + { + label: '(GMT) UTC', + value: 'UTC', + }, + { + label: '(GMT+01:00) Africa, Algiers', + value: 'Africa/Algiers', + }, + { + label: '(GMT+01:00) Africa, Bangui', + value: 'Africa/Bangui', + }, + { + label: '(GMT+01:00) Africa, Brazzaville', + value: 'Africa/Brazzaville', + }, + { + label: '(GMT+01:00) Africa, Casablanca', + value: 'Africa/Casablanca', + }, + { + label: '(GMT+01:00) Africa, Douala', + value: 'Africa/Douala', + }, + { + label: '(GMT+01:00) Africa, El Aaiun', + value: 'Africa/El_Aaiun', + }, + { + label: '(GMT+01:00) Africa, Kinshasa', + value: 'Africa/Kinshasa', + }, + { + label: '(GMT+01:00) Africa, Lagos', + value: 'Africa/Lagos', + }, + { + label: '(GMT+01:00) Africa, Libreville', + value: 'Africa/Libreville', + }, + { + label: '(GMT+01:00) Africa, Luanda', + value: 'Africa/Luanda', + }, + { + label: '(GMT+01:00) Africa, Malabo', + value: 'Africa/Malabo', + }, + { + label: '(GMT+01:00) Africa, Ndjamena', + value: 'Africa/Ndjamena', + }, + { + label: '(GMT+01:00) Africa, Niamey', + value: 'Africa/Niamey', + }, + { + label: '(GMT+01:00) Africa, Porto-Novo', + value: 'Africa/Porto-Novo', + }, + { + label: '(GMT+01:00) Africa, Tunis', + value: 'Africa/Tunis', + }, + { + label: '(GMT+01:00) Atlantic, Canary', + value: 'Atlantic/Canary', + }, + { + label: '(GMT+01:00) Atlantic, Faroe', + value: 'Atlantic/Faroe', + }, + { + label: '(GMT+01:00) Atlantic, Madeira', + value: 'Atlantic/Madeira', + }, + { + label: '(GMT+01:00) Europe, Dublin', + value: 'Europe/Dublin', + }, + { + label: '(GMT+01:00) Europe, Guernsey', + value: 'Europe/Guernsey', + }, + { + label: '(GMT+01:00) Europe, Isle of Man', + value: 'Europe/Isle_of_Man', + }, + { + label: '(GMT+01:00) Europe, Jersey', + value: 'Europe/Jersey', + }, + { + label: '(GMT+01:00) Europe, Lisbon', + value: 'Europe/Lisbon', + }, + { + label: '(GMT+01:00) Europe, London', + value: 'Europe/London', + }, + { + label: '(GMT+02:00) Africa, Blantyre', + value: 'Africa/Blantyre', + }, + { + label: '(GMT+02:00) Africa, Bujumbura', + value: 'Africa/Bujumbura', + }, + { + label: '(GMT+02:00) Africa, Ceuta', + value: 'Africa/Ceuta', + }, + { + label: '(GMT+02:00) Africa, Gaborone', + value: 'Africa/Gaborone', + }, + { + label: '(GMT+02:00) Africa, Harare', + value: 'Africa/Harare', + }, + { + label: '(GMT+02:00) Africa, Johannesburg', + value: 'Africa/Johannesburg', + }, + { + label: '(GMT+02:00) Africa, Juba', + value: 'Africa/Juba', + }, + { + label: '(GMT+02:00) Africa, Khartoum', + value: 'Africa/Khartoum', + }, + { + label: '(GMT+02:00) Africa, Kigali', + value: 'Africa/Kigali', + }, + { + label: '(GMT+02:00) Africa, Lubumbashi', + value: 'Africa/Lubumbashi', + }, + { + label: '(GMT+02:00) Africa, Lusaka', + value: 'Africa/Lusaka', + }, + { + label: '(GMT+02:00) Africa, Maputo', + value: 'Africa/Maputo', + }, + { + label: '(GMT+02:00) Africa, Maseru', + value: 'Africa/Maseru', + }, + { + label: '(GMT+02:00) Africa, Mbabane', + value: 'Africa/Mbabane', + }, + { + label: '(GMT+02:00) Africa, Tripoli', + value: 'Africa/Tripoli', + }, + { + label: '(GMT+02:00) Africa, Windhoek', + value: 'Africa/Windhoek', + }, + { + label: '(GMT+02:00) Antarctica, Troll', + value: 'Antarctica/Troll', + }, + { + label: '(GMT+02:00) Arctic, Longyearbyen', + value: 'Arctic/Longyearbyen', + }, + { + label: '(GMT+02:00) Europe, Amsterdam', + value: 'Europe/Amsterdam', + }, + { + label: '(GMT+02:00) Europe, Andorra', + value: 'Europe/Andorra', + }, + { + label: '(GMT+02:00) Europe, Belgrade', + value: 'Europe/Belgrade', + }, + { + label: '(GMT+02:00) Europe, Berlin', + value: 'Europe/Berlin', + }, + { + label: '(GMT+02:00) Europe, Bratislava', + value: 'Europe/Bratislava', + }, + { + label: '(GMT+02:00) Europe, Brussels', + value: 'Europe/Brussels', + }, + { + label: '(GMT+02:00) Europe, Budapest', + value: 'Europe/Budapest', + }, + { + label: '(GMT+02:00) Europe, Busingen', + value: 'Europe/Busingen', + }, + { + label: '(GMT+02:00) Europe, Copenhagen', + value: 'Europe/Copenhagen', + }, + { + label: '(GMT+02:00) Europe, Gibraltar', + value: 'Europe/Gibraltar', + }, + { + label: '(GMT+02:00) Europe, Kaliningrad', + value: 'Europe/Kaliningrad', + }, + { + label: '(GMT+02:00) Europe, Ljubljana', + value: 'Europe/Ljubljana', + }, + { + label: '(GMT+02:00) Europe, Luxembourg', + value: 'Europe/Luxembourg', + }, + { + label: '(GMT+02:00) Europe, Madrid', + value: 'Europe/Madrid', + }, + { + label: '(GMT+02:00) Europe, Malta', + value: 'Europe/Malta', + }, + { + label: '(GMT+02:00) Europe, Monaco', + value: 'Europe/Monaco', + }, + { + label: '(GMT+02:00) Europe, Oslo', + value: 'Europe/Oslo', + }, + { + label: '(GMT+02:00) Europe, Paris', + value: 'Europe/Paris', + }, + { + label: '(GMT+02:00) Europe, Podgorica', + value: 'Europe/Podgorica', + }, + { + label: '(GMT+02:00) Europe, Prague', + value: 'Europe/Prague', + }, + { + label: '(GMT+02:00) Europe, Rome', + value: 'Europe/Rome', + }, + { + label: '(GMT+02:00) Europe, San Marino', + value: 'Europe/San_Marino', + }, + { + label: '(GMT+02:00) Europe, Sarajevo', + value: 'Europe/Sarajevo', + }, + { + label: '(GMT+02:00) Europe, Skopje', + value: 'Europe/Skopje', + }, + { + label: '(GMT+02:00) Europe, Stockholm', + value: 'Europe/Stockholm', + }, + { + label: '(GMT+02:00) Europe, Tirane', + value: 'Europe/Tirane', + }, + { + label: '(GMT+02:00) Europe, Vaduz', + value: 'Europe/Vaduz', + }, + { + label: '(GMT+02:00) Europe, Vatican', + value: 'Europe/Vatican', + }, + { + label: '(GMT+02:00) Europe, Vienna', + value: 'Europe/Vienna', + }, + { + label: '(GMT+02:00) Europe, Warsaw', + value: 'Europe/Warsaw', + }, + { + label: '(GMT+02:00) Europe, Zagreb', + value: 'Europe/Zagreb', + }, + { + label: '(GMT+02:00) Europe, Zurich', + value: 'Europe/Zurich', + }, + { + label: '(GMT+03:00) Africa, Addis Ababa', + value: 'Africa/Addis_Ababa', + }, + { + label: '(GMT+03:00) Africa, Asmara', + value: 'Africa/Asmara', + }, + { + label: '(GMT+03:00) Africa, Cairo', + value: 'Africa/Cairo', + }, + { + label: '(GMT+03:00) Africa, Dar es Salaam', + value: 'Africa/Dar_es_Salaam', + }, + { + label: '(GMT+03:00) Africa, Djibouti', + value: 'Africa/Djibouti', + }, + { + label: '(GMT+03:00) Africa, Kampala', + value: 'Africa/Kampala', + }, + { + label: '(GMT+03:00) Africa, Mogadishu', + value: 'Africa/Mogadishu', + }, + { + label: '(GMT+03:00) Africa, Nairobi', + value: 'Africa/Nairobi', + }, + { + label: '(GMT+03:00) Antarctica, Syowa', + value: 'Antarctica/Syowa', + }, + { + label: '(GMT+03:00) Asia, Aden', + value: 'Asia/Aden', + }, + { + label: '(GMT+03:00) Asia, Amman', + value: 'Asia/Amman', + }, + { + label: '(GMT+03:00) Asia, Baghdad', + value: 'Asia/Baghdad', + }, + { + label: '(GMT+03:00) Asia, Bahrain', + value: 'Asia/Bahrain', + }, + { + label: '(GMT+03:00) Asia, Beirut', + value: 'Asia/Beirut', + }, + { + label: '(GMT+03:00) Asia, Damascus', + value: 'Asia/Damascus', + }, + { + label: '(GMT+03:00) Asia, Famagusta', + value: 'Asia/Famagusta', + }, + { + label: '(GMT+03:00) Asia, Gaza', + value: 'Asia/Gaza', + }, + { + label: '(GMT+03:00) Asia, Hebron', + value: 'Asia/Hebron', + }, + { + label: '(GMT+03:00) Asia, Jerusalem', + value: 'Asia/Jerusalem', + }, + { + label: '(GMT+03:00) Asia, Kuwait', + value: 'Asia/Kuwait', + }, + { + label: '(GMT+03:00) Asia, Nicosia', + value: 'Asia/Nicosia', + }, + { + label: '(GMT+03:00) Asia, Qatar', + value: 'Asia/Qatar', + }, + { + label: '(GMT+03:00) Asia, Riyadh', + value: 'Asia/Riyadh', + }, + { + label: '(GMT+03:00) Europe, Athens', + value: 'Europe/Athens', + }, + { + label: '(GMT+03:00) Europe, Bucharest', + value: 'Europe/Bucharest', + }, + { + label: '(GMT+03:00) Europe, Chisinau', + value: 'Europe/Chisinau', + }, + { + label: '(GMT+03:00) Europe, Helsinki', + value: 'Europe/Helsinki', + }, + { + label: '(GMT+03:00) Europe, Istanbul', + value: 'Europe/Istanbul', + }, + { + label: '(GMT+03:00) Europe, Kirov', + value: 'Europe/Kirov', + }, + { + label: '(GMT+03:00) Europe, Kyiv', + value: 'Europe/Kyiv', + }, + { + label: '(GMT+03:00) Europe, Mariehamn', + value: 'Europe/Mariehamn', + }, + { + label: '(GMT+03:00) Europe, Minsk', + value: 'Europe/Minsk', + }, + { + label: '(GMT+03:00) Europe, Moscow', + value: 'Europe/Moscow', + }, + { + label: '(GMT+03:00) Europe, Riga', + value: 'Europe/Riga', + }, + { + label: '(GMT+03:00) Europe, Simferopol', + value: 'Europe/Simferopol', + }, + { + label: '(GMT+03:00) Europe, Sofia', + value: 'Europe/Sofia', + }, + { + label: '(GMT+03:00) Europe, Tallinn', + value: 'Europe/Tallinn', + }, + { + label: '(GMT+03:00) Europe, Vilnius', + value: 'Europe/Vilnius', + }, + { + label: '(GMT+03:00) Europe, Volgograd', + value: 'Europe/Volgograd', + }, + { + label: '(GMT+03:00) Indian, Antananarivo', + value: 'Indian/Antananarivo', + }, + { + label: '(GMT+03:00) Indian, Comoro', + value: 'Indian/Comoro', + }, + { + label: '(GMT+03:00) Indian, Mayotte', + value: 'Indian/Mayotte', + }, + { + label: '(GMT+03:30) Asia, Tehran', + value: 'Asia/Tehran', + }, + { + label: '(GMT+04:00) Asia, Baku', + value: 'Asia/Baku', + }, + { + label: '(GMT+04:00) Asia, Dubai', + value: 'Asia/Dubai', + }, + { + label: '(GMT+04:00) Asia, Muscat', + value: 'Asia/Muscat', + }, + { + label: '(GMT+04:00) Asia, Tbilisi', + value: 'Asia/Tbilisi', + }, + { + label: '(GMT+04:00) Asia, Yerevan', + value: 'Asia/Yerevan', + }, + { + label: '(GMT+04:00) Europe, Astrakhan', + value: 'Europe/Astrakhan', + }, + { + label: '(GMT+04:00) Europe, Samara', + value: 'Europe/Samara', + }, + { + label: '(GMT+04:00) Europe, Saratov', + value: 'Europe/Saratov', + }, + { + label: '(GMT+04:00) Europe, Ulyanovsk', + value: 'Europe/Ulyanovsk', + }, + { + label: '(GMT+04:00) Indian, Mahe', + value: 'Indian/Mahe', + }, + { + label: '(GMT+04:00) Indian, Mauritius', + value: 'Indian/Mauritius', + }, + { + label: '(GMT+04:00) Indian, Reunion', + value: 'Indian/Reunion', + }, + { + label: '(GMT+04:30) Asia, Kabul', + value: 'Asia/Kabul', + }, + { + label: '(GMT+05:00) Antarctica, Mawson', + value: 'Antarctica/Mawson', + }, + { + label: '(GMT+05:00) Asia, Aqtau', + value: 'Asia/Aqtau', + }, + { + label: '(GMT+05:00) Asia, Aqtobe', + value: 'Asia/Aqtobe', + }, + { + label: '(GMT+05:00) Asia, Ashgabat', + value: 'Asia/Ashgabat', + }, + { + label: '(GMT+05:00) Asia, Atyrau', + value: 'Asia/Atyrau', + }, + { + label: '(GMT+05:00) Asia, Dushanbe', + value: 'Asia/Dushanbe', + }, + { + label: '(GMT+05:00) Asia, Karachi', + value: 'Asia/Karachi', + }, + { + label: '(GMT+05:00) Asia, Oral', + value: 'Asia/Oral', + }, + { + label: '(GMT+05:00) Asia, Qyzylorda', + value: 'Asia/Qyzylorda', + }, + { + label: '(GMT+05:00) Asia, Samarkand', + value: 'Asia/Samarkand', + }, + { + label: '(GMT+05:00) Asia, Tashkent', + value: 'Asia/Tashkent', + }, + { + label: '(GMT+05:00) Asia, Yekaterinburg', + value: 'Asia/Yekaterinburg', + }, + { + label: '(GMT+05:00) Indian, Kerguelen', + value: 'Indian/Kerguelen', + }, + { + label: '(GMT+05:00) Indian, Maldives', + value: 'Indian/Maldives', + }, + { + label: '(GMT+05:30) Asia, Colombo', + value: 'Asia/Colombo', + }, + { + label: '(GMT+05:30) Asia, Kolkata', + value: 'Asia/Kolkata', + }, + { + label: '(GMT+05:45) Asia, Kathmandu', + value: 'Asia/Kathmandu', + }, + { + label: '(GMT+06:00) Antarctica, Vostok', + value: 'Antarctica/Vostok', + }, + { + label: '(GMT+06:00) Asia, Almaty', + value: 'Asia/Almaty', + }, + { + label: '(GMT+06:00) Asia, Bishkek', + value: 'Asia/Bishkek', + }, + { + label: '(GMT+06:00) Asia, Dhaka', + value: 'Asia/Dhaka', + }, + { + label: '(GMT+06:00) Asia, Omsk', + value: 'Asia/Omsk', + }, + { + label: '(GMT+06:00) Asia, Qostanay', + value: 'Asia/Qostanay', + }, + { + label: '(GMT+06:00) Asia, Thimphu', + value: 'Asia/Thimphu', + }, + { + label: '(GMT+06:00) Asia, Urumqi', + value: 'Asia/Urumqi', + }, + { + label: '(GMT+06:00) Indian, Chagos', + value: 'Indian/Chagos', + }, + { + label: '(GMT+06:30) Asia, Yangon', + value: 'Asia/Yangon', + }, + { + label: '(GMT+06:30) Indian, Cocos', + value: 'Indian/Cocos', + }, + { + label: '(GMT+07:00) Antarctica, Davis', + value: 'Antarctica/Davis', + }, + { + label: '(GMT+07:00) Asia, Bangkok', + value: 'Asia/Bangkok', + }, + { + label: '(GMT+07:00) Asia, Barnaul', + value: 'Asia/Barnaul', + }, + { + label: '(GMT+07:00) Asia, Ho Chi Minh', + value: 'Asia/Ho_Chi_Minh', + }, + { + label: '(GMT+07:00) Asia, Hovd', + value: 'Asia/Hovd', + }, + { + label: '(GMT+07:00) Asia, Jakarta', + value: 'Asia/Jakarta', + }, + { + label: '(GMT+07:00) Asia, Krasnoyarsk', + value: 'Asia/Krasnoyarsk', + }, + { + label: '(GMT+07:00) Asia, Novokuznetsk', + value: 'Asia/Novokuznetsk', + }, + { + label: '(GMT+07:00) Asia, Novosibirsk', + value: 'Asia/Novosibirsk', + }, + { + label: '(GMT+07:00) Asia, Phnom Penh', + value: 'Asia/Phnom_Penh', + }, + { + label: '(GMT+07:00) Asia, Pontianak', + value: 'Asia/Pontianak', + }, + { + label: '(GMT+07:00) Asia, Tomsk', + value: 'Asia/Tomsk', + }, + { + label: '(GMT+07:00) Asia, Vientiane', + value: 'Asia/Vientiane', + }, + { + label: '(GMT+07:00) Indian, Christmas', + value: 'Indian/Christmas', + }, + { + label: '(GMT+08:00) Asia, Brunei', + value: 'Asia/Brunei', + }, + { + label: '(GMT+08:00) Asia, Choibalsan', + value: 'Asia/Choibalsan', + }, + { + label: '(GMT+08:00) Asia, Hong Kong', + value: 'Asia/Hong_Kong', + }, + { + label: '(GMT+08:00) Asia, Irkutsk', + value: 'Asia/Irkutsk', + }, + { + label: '(GMT+08:00) Asia, Kuala Lumpur', + value: 'Asia/Kuala_Lumpur', + }, + { + label: '(GMT+08:00) Asia, Kuching', + value: 'Asia/Kuching', + }, + { + label: '(GMT+08:00) Asia, Macau', + value: 'Asia/Macau', + }, + { + label: '(GMT+08:00) Asia, Makassar', + value: 'Asia/Makassar', + }, + { + label: '(GMT+08:00) Asia, Manila', + value: 'Asia/Manila', + }, + { + label: '(GMT+08:00) Asia, Shanghai', + value: 'Asia/Shanghai', + }, + { + label: '(GMT+08:00) Asia, Singapore', + value: 'Asia/Singapore', + }, + { + label: '(GMT+08:00) Asia, Taipei', + value: 'Asia/Taipei', + }, + { + label: '(GMT+08:00) Asia, Ulaanbaatar', + value: 'Asia/Ulaanbaatar', + }, + { + label: '(GMT+08:00) Australia, Perth', + value: 'Australia/Perth', + }, + { + label: '(GMT+08:45) Australia, Eucla', + value: 'Australia/Eucla', + }, + { + label: '(GMT+09:00) Asia, Chita', + value: 'Asia/Chita', + }, + { + label: '(GMT+09:00) Asia, Dili', + value: 'Asia/Dili', + }, + { + label: '(GMT+09:00) Asia, Jayapura', + value: 'Asia/Jayapura', + }, + { + label: '(GMT+09:00) Asia, Khandyga', + value: 'Asia/Khandyga', + }, + { + label: '(GMT+09:00) Asia, Pyongyang', + value: 'Asia/Pyongyang', + }, + { + label: '(GMT+09:00) Asia, Seoul', + value: 'Asia/Seoul', + }, + { + label: '(GMT+09:00) Asia, Tokyo', + value: 'Asia/Tokyo', + }, + { + label: '(GMT+09:00) Asia, Yakutsk', + value: 'Asia/Yakutsk', + }, + { + label: '(GMT+09:00) Pacific, Palau', + value: 'Pacific/Palau', + }, + { + label: '(GMT+09:30) Australia, Adelaide', + value: 'Australia/Adelaide', + }, + { + label: '(GMT+09:30) Australia, Broken Hill', + value: 'Australia/Broken_Hill', + }, + { + label: '(GMT+09:30) Australia, Darwin', + value: 'Australia/Darwin', + }, + { + label: '(GMT+10:00) Antarctica, DumontDUrville', + value: 'Antarctica/DumontDUrville', + }, + { + label: '(GMT+10:00) Antarctica, Macquarie', + value: 'Antarctica/Macquarie', + }, + { + label: '(GMT+10:00) Asia, Ust-Nera', + value: 'Asia/Ust-Nera', + }, + { + label: '(GMT+10:00) Asia, Vladivostok', + value: 'Asia/Vladivostok', + }, + { + label: '(GMT+10:00) Australia, Brisbane', + value: 'Australia/Brisbane', + }, + { + label: '(GMT+10:00) Australia, Hobart', + value: 'Australia/Hobart', + }, + { + label: '(GMT+10:00) Australia, Lindeman', + value: 'Australia/Lindeman', + }, + { + label: '(GMT+10:00) Australia, Melbourne', + value: 'Australia/Melbourne', + }, + { + label: '(GMT+10:00) Australia, Sydney', + value: 'Australia/Sydney', + }, + { + label: '(GMT+10:00) Pacific, Chuuk', + value: 'Pacific/Chuuk', + }, + { + label: '(GMT+10:00) Pacific, Guam', + value: 'Pacific/Guam', + }, + { + label: '(GMT+10:00) Pacific, Port Moresby', + value: 'Pacific/Port_Moresby', + }, + { + label: '(GMT+10:00) Pacific, Saipan', + value: 'Pacific/Saipan', + }, + { + label: '(GMT+10:30) Australia, Lord Howe', + value: 'Australia/Lord_Howe', + }, + { + label: '(GMT+11:00) Antarctica, Casey', + value: 'Antarctica/Casey', + }, + { + label: '(GMT+11:00) Asia, Magadan', + value: 'Asia/Magadan', + }, + { + label: '(GMT+11:00) Asia, Sakhalin', + value: 'Asia/Sakhalin', + }, + { + label: '(GMT+11:00) Asia, Srednekolymsk', + value: 'Asia/Srednekolymsk', + }, + { + label: '(GMT+11:00) Pacific, Bougainville', + value: 'Pacific/Bougainville', + }, + { + label: '(GMT+11:00) Pacific, Efate', + value: 'Pacific/Efate', + }, + { + label: '(GMT+11:00) Pacific, Guadalcanal', + value: 'Pacific/Guadalcanal', + }, + { + label: '(GMT+11:00) Pacific, Kosrae', + value: 'Pacific/Kosrae', + }, + { + label: '(GMT+11:00) Pacific, Norfolk', + value: 'Pacific/Norfolk', + }, + { + label: '(GMT+11:00) Pacific, Noumea', + value: 'Pacific/Noumea', + }, + { + label: '(GMT+11:00) Pacific, Pohnpei', + value: 'Pacific/Pohnpei', + }, + { + label: '(GMT+12:00) Antarctica, McMurdo', + value: 'Antarctica/McMurdo', + }, + { + label: '(GMT+12:00) Asia, Anadyr', + value: 'Asia/Anadyr', + }, + { + label: '(GMT+12:00) Asia, Kamchatka', + value: 'Asia/Kamchatka', + }, + { + label: '(GMT+12:00) Pacific, Auckland', + value: 'Pacific/Auckland', + }, + { + label: '(GMT+12:00) Pacific, Fiji', + value: 'Pacific/Fiji', + }, + { + label: '(GMT+12:00) Pacific, Funafuti', + value: 'Pacific/Funafuti', + }, + { + label: '(GMT+12:00) Pacific, Kwajalein', + value: 'Pacific/Kwajalein', + }, + { + label: '(GMT+12:00) Pacific, Majuro', + value: 'Pacific/Majuro', + }, + { + label: '(GMT+12:00) Pacific, Nauru', + value: 'Pacific/Nauru', + }, + { + label: '(GMT+12:00) Pacific, Tarawa', + value: 'Pacific/Tarawa', + }, + { + label: '(GMT+12:00) Pacific, Wake', + value: 'Pacific/Wake', + }, + { + label: '(GMT+12:00) Pacific, Wallis', + value: 'Pacific/Wallis', + }, + { + label: '(GMT+12:45) Pacific, Chatham', + value: 'Pacific/Chatham', + }, + { + label: '(GMT+13:00) Pacific, Apia', + value: 'Pacific/Apia', + }, + { + label: '(GMT+13:00) Pacific, Fakaofo', + value: 'Pacific/Fakaofo', + }, + { + label: '(GMT+13:00) Pacific, Kanton', + value: 'Pacific/Kanton', + }, + { + label: '(GMT+13:00) Pacific, Tongatapu', + value: 'Pacific/Tongatapu', + }, + { + label: '(GMT+14:00) Pacific, Kiritimati', + value: 'Pacific/Kiritimati', + }, +]; diff --git a/packages/pieces/community/schedule/src/lib/triggers/cron-expression.trigger.ts b/packages/pieces/community/schedule/src/lib/triggers/cron-expression.trigger.ts new file mode 100644 index 0000000..2d0b1e6 --- /dev/null +++ b/packages/pieces/community/schedule/src/lib/triggers/cron-expression.trigger.ts @@ -0,0 +1,39 @@ +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { createTrigger, Property } from '@activepieces/pieces-framework'; +import { timezoneOptions } from '../common'; + +export const cronExpressionTrigger = createTrigger({ + name: 'cron_expression', + displayName: 'Cron Expression', + description: 'Trigger based on cron expression', + props: { + cronExpression: Property.ShortText({ + displayName: 'Cron Expression', + description: 'Cron expression to trigger', + required: true, + defaultValue: '0/5 * * * *', + }), + timezone: Property.StaticDropdown({ + displayName: 'Timezone', + options: { + options: timezoneOptions, + }, + required: true, + defaultValue: 'UTC', + }), + }, + type: TriggerStrategy.POLLING, + sampleData: {}, + onEnable: async (ctx) => { + ctx.setSchedule({ + cronExpression: ctx.propsValue.cronExpression, + timezone: ctx.propsValue.timezone, + }); + }, + run(context) { + return Promise.resolve([{}]); + }, + onDisable: async () => { + console.log('onDisable'); + }, +}); diff --git a/packages/pieces/community/schedule/src/lib/triggers/every-day.trigger.ts b/packages/pieces/community/schedule/src/lib/triggers/every-day.trigger.ts new file mode 100644 index 0000000..f81ab68 --- /dev/null +++ b/packages/pieces/community/schedule/src/lib/triggers/every-day.trigger.ts @@ -0,0 +1,64 @@ +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { createTrigger, Property } from '@activepieces/pieces-framework'; +import { DAY_HOURS, timezoneOptions, validateHours } from '../common'; + +export const everyDayTrigger = createTrigger({ + name: 'every_day', + displayName: 'Every Day', + description: 'Triggers the current flow every day', + type: TriggerStrategy.POLLING, + sampleData: {}, + props: { + hour_of_the_day: Property.StaticDropdown({ + displayName: 'Hour of the day', + options: { + options: DAY_HOURS.map((h, idx) => { + return { + label: h, + value: idx, + }; + }), + }, + required: true, + defaultValue: 0, + }), + timezone: Property.StaticDropdown({ + displayName: 'Timezone', + options: { + options: timezoneOptions, + }, + required: true, + defaultValue: 'UTC', + }), + run_on_weekends: Property.Checkbox({ + displayName: 'Run on weekends (Sat,Sun)', + required: true, + defaultValue: false, + }), + }, + onEnable: async (ctx) => { + const hourOfTheDay = validateHours(ctx.propsValue.hour_of_the_day); + const cronExpression = ctx.propsValue.run_on_weekends + ? `0 ${hourOfTheDay} * * *` + : `0 ${hourOfTheDay} * * 1-5`; + ctx.setSchedule({ + cronExpression: cronExpression, + timezone: ctx.propsValue.timezone, + }); + }, + run(ctx) { + const hourOfTheDay = validateHours(ctx.propsValue.hour_of_the_day); + return Promise.resolve([ + { + hour_of_the_day: hourOfTheDay, + timezone: ctx.propsValue.timezone, + cron_expression: ctx.propsValue.run_on_weekends + ? `0 ${hourOfTheDay} * * *` + : `0 ${hourOfTheDay} * * 1-5`, + }, + ]); + }, + onDisable: async () => { + console.log('onDisable'); + }, +}); diff --git a/packages/pieces/community/schedule/src/lib/triggers/every-hour.trigger.ts b/packages/pieces/community/schedule/src/lib/triggers/every-hour.trigger.ts new file mode 100644 index 0000000..ff8e2ed --- /dev/null +++ b/packages/pieces/community/schedule/src/lib/triggers/every-hour.trigger.ts @@ -0,0 +1,40 @@ +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { createTrigger, Property } from '@activepieces/pieces-framework'; + +export const everyHourTrigger = createTrigger({ + name: 'every_hour', + displayName: 'Every Hour', + description: 'Triggers the current flow every hour', + type: TriggerStrategy.POLLING, + sampleData: {}, + props: { + run_on_weekends: Property.Checkbox({ + displayName: 'Run on weekends (Sat,Sun)', + required: true, + defaultValue: false, + }), + }, + onEnable: async (ctx) => { + const cronExpression = ctx.propsValue.run_on_weekends + ? `0 * * * *` + : `0 * * * 1-5`; + ctx.setSchedule({ + cronExpression: cronExpression, + timezone: 'UTC', + }); + }, + run(ctx) { + const cronExpression = ctx.propsValue.run_on_weekends + ? `0 * * * *` + : `0 * * * 1-5`; + return Promise.resolve([ + { + cron_expression: cronExpression, + timezone: 'UTC', + }, + ]); + }, + onDisable: async () => { + console.log('onDisable'); + }, +}); diff --git a/packages/pieces/community/schedule/src/lib/triggers/every-month.trigger.ts b/packages/pieces/community/schedule/src/lib/triggers/every-month.trigger.ts new file mode 100644 index 0000000..421bb90 --- /dev/null +++ b/packages/pieces/community/schedule/src/lib/triggers/every-month.trigger.ts @@ -0,0 +1,76 @@ +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { createTrigger, Property } from '@activepieces/pieces-framework'; +import { + DAY_HOURS, + MONTH_DAYS, + timezoneOptions, + validateHours, + validateMonthDays, +} from '../common'; + +export const everyMonthTrigger = createTrigger({ + name: 'every_month', + displayName: 'Every Month', + description: 'Triggers the current flow every month', + type: TriggerStrategy.POLLING, + sampleData: {}, + props: { + day_of_the_month: Property.StaticDropdown({ + displayName: 'Day of the month', + options: { + options: MONTH_DAYS.map((d, idx) => { + return { + label: (1 + d).toString(), + value: idx + 1, + }; + }), + }, + required: true, + }), + hour_of_the_day: Property.StaticDropdown({ + displayName: 'Hour of the day', + options: { + options: DAY_HOURS.map((d, idx) => { + return { + label: d, + value: idx, + }; + }), + }, + required: true, + }), + timezone: Property.StaticDropdown({ + displayName: 'Timezone', + options: { + options: timezoneOptions, + }, + required: true, + defaultValue: 'UTC', + }), + }, + onEnable: async (ctx) => { + const hourOfTheDay = validateHours(ctx.propsValue.hour_of_the_day); + const dayOfTheMonth = validateMonthDays(ctx.propsValue.day_of_the_month); + const cronExpression = `0 ${hourOfTheDay} ${dayOfTheMonth} * *`; + ctx.setSchedule({ + cronExpression: cronExpression, + timezone: ctx.propsValue.timezone, + }); + }, + run(ctx) { + const hourOfTheDay = validateHours(ctx.propsValue.hour_of_the_day); + const dayOfTheMonth = validateMonthDays(ctx.propsValue.day_of_the_month); + const cronExpression = `0 ${hourOfTheDay} ${dayOfTheMonth} * *`; + return Promise.resolve([ + { + hour_of_the_day: hourOfTheDay, + day_of_the_month: dayOfTheMonth, + cron_expression: cronExpression, + timezone: ctx.propsValue.timezone, + }, + ]); + }, + onDisable: async () => { + console.log('onDisable'); + }, +}); diff --git a/packages/pieces/community/schedule/src/lib/triggers/every-week.trigger.ts b/packages/pieces/community/schedule/src/lib/triggers/every-week.trigger.ts new file mode 100644 index 0000000..fd50139 --- /dev/null +++ b/packages/pieces/community/schedule/src/lib/triggers/every-week.trigger.ts @@ -0,0 +1,76 @@ +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { createTrigger, Property } from '@activepieces/pieces-framework'; +import { + DAY_HOURS, + validateWeekDays, + validateHours, + WEEK_DAYS, + timezoneOptions, +} from '../common'; + +export const everyWeekTrigger = createTrigger({ + name: 'every_week', + displayName: 'Every Week', + description: 'Triggers the current flow every week', + type: TriggerStrategy.POLLING, + sampleData: {}, + props: { + day_of_the_week: Property.StaticDropdown({ + displayName: 'Day of the week', + options: { + options: WEEK_DAYS.map((d, idx) => { + return { + label: d, + value: idx, + }; + }), + }, + required: true, + }), + hour_of_the_day: Property.StaticDropdown({ + displayName: 'Hour of the day', + options: { + options: DAY_HOURS.map((h, idx) => { + return { + label: h, + value: idx, + }; + }), + }, + required: true, + }), + timezone: Property.StaticDropdown({ + displayName: 'Timezone', + options: { + options: timezoneOptions, + }, + required: true, + defaultValue: 'UTC', + }), + }, + onEnable: async (ctx) => { + const hourOfTheDay = validateHours(ctx.propsValue.hour_of_the_day); + const dayOfTheWeek = validateWeekDays(ctx.propsValue.day_of_the_week); + const cronExpression = `0 ${hourOfTheDay} * * ${dayOfTheWeek}`; + ctx.setSchedule({ + cronExpression: cronExpression, + timezone: ctx.propsValue.timezone, + }); + }, + run(ctx) { + const hourOfTheDay = validateHours(ctx.propsValue.hour_of_the_day); + const dayOfTheWeek = validateWeekDays(ctx.propsValue.day_of_the_week); + const cronExpression = `0 ${hourOfTheDay} * * ${dayOfTheWeek}`; + return Promise.resolve([ + { + hour_of_the_day: hourOfTheDay, + day_of_the_week: dayOfTheWeek, + cron_expression: cronExpression, + timezone: ctx.propsValue.timezone, + }, + ]); + }, + onDisable: async () => { + console.log('onDisable'); + }, +}); diff --git a/packages/pieces/community/schedule/src/lib/triggers/every-x-minutes.trigger.ts b/packages/pieces/community/schedule/src/lib/triggers/every-x-minutes.trigger.ts new file mode 100644 index 0000000..0aedf9f --- /dev/null +++ b/packages/pieces/community/schedule/src/lib/triggers/every-x-minutes.trigger.ts @@ -0,0 +1,47 @@ +import { + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; + +export const everyXMinutesTrigger = createTrigger({ + name: 'every_x_minutes', + displayName: 'Every X Minutes', + description: 'Triggers the current flow every X minutes', + type: TriggerStrategy.POLLING, + sampleData: {}, + props: { + minutes: Property.StaticDropdown({ + displayName: 'Minutes', + description: 'Valid value between 1 to 59.', + required: true, + defaultValue: 1, + options: { + disabled: false, + options: Array.from({ length: 59 }, (_, index) => ({ + label: `${index + 1} minute${index !== 0 ? 's' : ''}`, + value: index + 1, + })), + }, + }), + }, + onEnable: async (ctx) => { + const cronExpression = `*/${ctx.propsValue.minutes} * * * *`; + ctx.setSchedule({ + cronExpression: cronExpression, + timezone: 'UTC', + }); + }, + run(ctx) { + const cronExpression = `*/${ctx.propsValue.minutes} * * * *`; + return Promise.resolve([ + { + cron_expression: cronExpression, + timezone: 'UTC', + }, + ]); + }, + onDisable: async () => { + console.log('onDisable'); + }, +}); diff --git a/packages/pieces/community/schedule/tsconfig.json b/packages/pieces/community/schedule/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/schedule/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/schedule/tsconfig.lib.json b/packages/pieces/community/schedule/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/schedule/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/scrapegrapghai/.eslintrc.json b/packages/pieces/community/scrapegrapghai/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/scrapegrapghai/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/scrapegrapghai/README.md b/packages/pieces/community/scrapegrapghai/README.md new file mode 100644 index 0000000..ed64cb1 --- /dev/null +++ b/packages/pieces/community/scrapegrapghai/README.md @@ -0,0 +1,33 @@ +# ScrapeGraphAI + +## Description +ScrapeGraphAI is a powerful web scraping and content extraction API. This piece enables integration with ScrapeGraphAI's API to perform smart scraping, local scraping, and markdown conversion. + +## Actions +1. **Smart Scraper**: Advanced web scraping with AI-powered content extraction + - Automatic content detection + - Clean HTML output + - Support for dynamic websites + - Configurable options for content types + +2. **Local Scraper**: Fast and lightweight web scraping + - Basic content extraction + - Static website support + - Configurable selectors + - Resource-efficient + +3. **Markdownify**: Convert web content to clean Markdown + - Clean and formatted markdown output + - Preserves content structure + - Handles various content types + - Customizable output options + +## Authentication +This piece requires an API key from ScrapeGraphAI. To obtain your API key: +1. Visit https://scrapegraphai.com +2. Sign up for an account +3. Navigate to your dashboard +4. Copy your API key + +## Requirements +- ScrapeGraphAI API Key diff --git a/packages/pieces/community/scrapegrapghai/package.json b/packages/pieces/community/scrapegrapghai/package.json new file mode 100644 index 0000000..df73597 --- /dev/null +++ b/packages/pieces/community/scrapegrapghai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-scrapegrapghai", + "version": "0.0.1" +} diff --git a/packages/pieces/community/scrapegrapghai/project.json b/packages/pieces/community/scrapegrapghai/project.json new file mode 100644 index 0000000..c098020 --- /dev/null +++ b/packages/pieces/community/scrapegrapghai/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-scrapegrapghai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/scrapegrapghai/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/scrapegrapghai", + "tsConfig": "packages/pieces/community/scrapegrapghai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/scrapegrapghai/package.json", + "main": "packages/pieces/community/scrapegrapghai/src/index.ts", + "assets": [ + "packages/pieces/community/scrapegrapghai/*.md", + { + "input": "packages/pieces/community/scrapegrapghai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/scrapegrapghai/src/index.ts b/packages/pieces/community/scrapegrapghai/src/index.ts new file mode 100644 index 0000000..5cfef28 --- /dev/null +++ b/packages/pieces/community/scrapegrapghai/src/index.ts @@ -0,0 +1,68 @@ +import { createCustomApiCallAction, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { smartScraper } from './lib/actions/smart-scraper'; +import { localScraper } from './lib/actions/local-scraper'; +import { markdownify } from './lib/actions/markdownify'; + +const markdownDescription = ` +Follow these steps to obtain your ScrapeGraphAI API Key: + +1. Visit [ScrapeGraphAI](https://scrapegraphai.com) and create an account. +2. Log in and navigate to your dashboard. +3. Locate and copy your API key from the dashboard. +`; + +export const scrapegraphaiAuth = PieceAuth.SecretText({ + description: markdownDescription, + displayName: 'API Key', + required: true, + validate: async ({ auth }) => { + try { + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.scrapegraphai.com/v1/smartscraper', + headers: { + 'Content-Type': 'application/json', + 'SGAI-APIKEY': auth, + }, + body: { + user_prompt: 'test', + website_url: 'https://www.example.com', + }, + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API Key', + }; + } + }, +}); + +export const scrapegraphai = createPiece({ + displayName: 'ScrapeGraphAI', + description: 'AI-powered web scraping and content extraction.', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/scrapegraphai.jpg', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + authors: ["OsamaHaikal"], + auth: scrapegraphaiAuth, + actions: [ + smartScraper, + localScraper, + markdownify, + createCustomApiCallAction({ + baseUrl: () => 'https://api.scrapegraphai.com/v1', + auth: scrapegraphaiAuth, + authMapping: async (auth) => ({ + 'SGAI-APIKEY': `${auth}`, + }), + }), + ], + triggers: [], +}); + \ No newline at end of file diff --git a/packages/pieces/community/scrapegrapghai/src/lib/actions/local-scraper.ts b/packages/pieces/community/scrapegrapghai/src/lib/actions/local-scraper.ts new file mode 100644 index 0000000..09b94cf --- /dev/null +++ b/packages/pieces/community/scrapegrapghai/src/lib/actions/local-scraper.ts @@ -0,0 +1,44 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { scrapegraphaiAuth } from '../../index'; + +export const localScraper = createAction({ + name: 'local_scraper', + displayName: 'Local Scraper', + description: 'Extract content from HTML content using AI by providing a natural language prompt.', + auth: scrapegraphaiAuth, + props: { + website_html: Property.LongText({ + displayName: 'HTML Content', + description: 'The HTML content to process (max 2MB).', + required: true, + }), + user_prompt: Property.LongText({ + displayName: 'Extraction Prompt', + description: 'Describe what information you want to extract in natural language.', + required: true, + }), + output_schema: Property.Json({ + displayName: 'Output Schema', + description: 'Optional schema to structure the output data.', + required: false, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.scrapegraphai.com/v1/localscraper', + headers: { + 'Content-Type': 'application/json', + 'SGAI-APIKEY': auth, + }, + body: { + website_html: propsValue.website_html, + user_prompt: propsValue.user_prompt, + output_schema: propsValue.output_schema, + }, + }); + + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/scrapegrapghai/src/lib/actions/markdownify.ts b/packages/pieces/community/scrapegrapghai/src/lib/actions/markdownify.ts new file mode 100644 index 0000000..3445bd9 --- /dev/null +++ b/packages/pieces/community/scrapegrapghai/src/lib/actions/markdownify.ts @@ -0,0 +1,32 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { scrapegraphaiAuth } from '../../index'; + +export const markdownify = createAction({ + name: 'markdownify', + displayName: 'Convert to Markdown', + description: 'Convert any webpage into clean, readable Markdown format.', + auth: scrapegraphaiAuth, + props: { + website_url: Property.ShortText({ + displayName: 'Website URL', + description: 'The webpage URL to convert to Markdown', + required: true, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.scrapegraphai.com/v1/markdownify', + headers: { + 'Content-Type': 'application/json', + 'SGAI-APIKEY': auth, + }, + body: { + website_url: propsValue.website_url, + }, + }); + + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/scrapegrapghai/src/lib/actions/smart-scraper.ts b/packages/pieces/community/scrapegrapghai/src/lib/actions/smart-scraper.ts new file mode 100644 index 0000000..1585c78 --- /dev/null +++ b/packages/pieces/community/scrapegrapghai/src/lib/actions/smart-scraper.ts @@ -0,0 +1,44 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { scrapegraphaiAuth } from '../../index'; + +export const smartScraper = createAction({ + name: 'smart_scraper', + displayName: 'Smart Scraper', + description: 'Extract content from a webpage using AI by providing a natural language prompt.', + auth: scrapegraphaiAuth, + props: { + website_url: Property.ShortText({ + displayName: 'Website URL', + description: 'The webpage URL to scrape.', + required: true, + }), + user_prompt: Property.LongText({ + displayName: 'Extraction Prompt', + description: 'Describe what information you want to extract in natural language.', + required: true, + }), + output_schema: Property.Json({ + displayName: 'Output Schema', + description: 'Optional schema to structure the output data.', + required: false, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.scrapegraphai.com/v1/smartscraper', + headers: { + 'Content-Type': 'application/json', + 'SGAI-APIKEY': auth, + }, + body: { + website_url: propsValue.website_url, + user_prompt: propsValue.user_prompt, + output_schema: propsValue.output_schema, + }, + }); + + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/scrapegrapghai/tsconfig.json b/packages/pieces/community/scrapegrapghai/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/scrapegrapghai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/scrapegrapghai/tsconfig.lib.json b/packages/pieces/community/scrapegrapghai/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/scrapegrapghai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/segment/.eslintrc.json b/packages/pieces/community/segment/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/segment/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/segment/README.md b/packages/pieces/community/segment/README.md new file mode 100644 index 0000000..aed08e2 --- /dev/null +++ b/packages/pieces/community/segment/README.md @@ -0,0 +1,7 @@ +# pieces-segment + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-segment` to build the library. diff --git a/packages/pieces/community/segment/package.json b/packages/pieces/community/segment/package.json new file mode 100644 index 0000000..1043e3a --- /dev/null +++ b/packages/pieces/community/segment/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-segment", + "version": "0.0.1" +} diff --git a/packages/pieces/community/segment/project.json b/packages/pieces/community/segment/project.json new file mode 100644 index 0000000..3c01ecb --- /dev/null +++ b/packages/pieces/community/segment/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-segment", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/segment/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/segment", + "tsConfig": "packages/pieces/community/segment/tsconfig.lib.json", + "packageJson": "packages/pieces/community/segment/package.json", + "main": "packages/pieces/community/segment/src/index.ts", + "assets": [ + "packages/pieces/community/segment/*.md", + { + "input": "packages/pieces/community/segment/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-segment {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/segment/src/index.ts b/packages/pieces/community/segment/src/index.ts new file mode 100644 index 0000000..b511203 --- /dev/null +++ b/packages/pieces/community/segment/src/index.ts @@ -0,0 +1,20 @@ + +import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { identifyUser } from "./lib/actions/identify-user"; + +export const segmentAuth = PieceAuth.SecretText({ + displayName: 'Analytics Key', + required: true, + description: 'Copy and paste your analytics write key here', +}); + + +export const segment = createPiece({ + displayName: "Segment", + auth: segmentAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: "https://cdn.activepieces.com/pieces/segment.png", + authors: ['abuaboud'], + actions: [identifyUser], + triggers: [], +}); diff --git a/packages/pieces/community/segment/src/lib/actions/identify-user.ts b/packages/pieces/community/segment/src/lib/actions/identify-user.ts new file mode 100644 index 0000000..231ef3b --- /dev/null +++ b/packages/pieces/community/segment/src/lib/actions/identify-user.ts @@ -0,0 +1,32 @@ +import { segmentAuth } from '../../.'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { Analytics } from '@segment/analytics-node' + +export const identifyUser = createAction({ + name: 'identifyUser', + displayName: 'Identify User', + description: '', + props: { + userId: Property.ShortText({ + displayName: 'User ID', + required: true, + }), + traits: Property.Object({ + displayName: 'Traits', + description: 'The traits to associate with the user', + required: true, + }), + }, + auth: segmentAuth, + async run(context) { + const analytics = new Analytics({ writeKey: context.auth }) + analytics.identify({ + userId: context.propsValue.userId, + traits: context.propsValue.traits, + }) + return { + success: true, + + } + }, +}); diff --git a/packages/pieces/community/segment/tsconfig.json b/packages/pieces/community/segment/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/segment/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/segment/tsconfig.lib.json b/packages/pieces/community/segment/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/segment/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/sendfox/.eslintrc.json b/packages/pieces/community/sendfox/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/sendfox/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/sendfox/README.md b/packages/pieces/community/sendfox/README.md new file mode 100644 index 0000000..701bd89 --- /dev/null +++ b/packages/pieces/community/sendfox/README.md @@ -0,0 +1,7 @@ +# pieces-sendfox + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-sendfox` to build the library. diff --git a/packages/pieces/community/sendfox/package.json b/packages/pieces/community/sendfox/package.json new file mode 100644 index 0000000..9a45eec --- /dev/null +++ b/packages/pieces/community/sendfox/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-sendfox", + "version": "0.0.7" +} \ No newline at end of file diff --git a/packages/pieces/community/sendfox/project.json b/packages/pieces/community/sendfox/project.json new file mode 100644 index 0000000..046d0e3 --- /dev/null +++ b/packages/pieces/community/sendfox/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-sendfox", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/sendfox/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/sendfox", + "tsConfig": "packages/pieces/community/sendfox/tsconfig.lib.json", + "packageJson": "packages/pieces/community/sendfox/package.json", + "main": "packages/pieces/community/sendfox/src/index.ts", + "assets": [ + "packages/pieces/community/sendfox/*.md", + { + "input": "packages/pieces/community/sendfox/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-sendfox {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/sendfox/src/common/index.ts b/packages/pieces/community/sendfox/src/common/index.ts new file mode 100644 index 0000000..74c918c --- /dev/null +++ b/packages/pieces/community/sendfox/src/common/index.ts @@ -0,0 +1,104 @@ +import { Property, OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { + HttpMethod, + HttpMessageBody, + HttpResponse, + httpClient, + AuthenticationType, +} from '@activepieces/pieces-common'; + +export async function getLists(accessToken: string) { + const list_of_lists = []; + let last_page = 1; + for (let i = 1; i <= last_page; i++) { + const response = ( + await callsendfoxApi<{ + current_page: number; + data: { + id: number; + name: string; + }[]; + last_page: number; + }>(HttpMethod.GET, `lists?page=${i}`, accessToken, undefined) + ).body; + last_page = response.last_page; + const lists = response.data; + list_of_lists.push(lists); + } + return list_of_lists; +} +export async function getcontacts(accessToken: string) { + let list_of_contacts = []; + let last_page = 1; + for (let i = 1; i <= last_page; i++) { + const response = ( + await callsendfoxApi<{ + current_page: number; + data: { + id: number; + first_name: string; + last_name: string; + }[]; + last_page: number; + }>(HttpMethod.GET, `contacts?page=${i}`, accessToken, undefined) + ).body; + last_page = response.last_page; + const contacts = response.data; + list_of_contacts.push(contacts); + } + + // convert the list of contacts to have name as first_name+last_name + list_of_contacts = list_of_contacts.flat().map((contact) => { + return { + id: contact.id, + name: contact.first_name + ' ' + contact.last_name, + }; + }); + return list_of_contacts; +} + +export const sendfoxCommon = { + lists: Property.Dropdown({ + displayName: 'Lists', + required: false, + refreshers: ['auth'], + options: async ({ auth }) => { + const authentication = auth as string; + if (!authentication) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + const accessToken = authentication; + const list_of_lists = await getLists(accessToken); + return { + disabled: false, + options: list_of_lists.flat().map((list) => { + return { + label: list.name, + value: list.id, + }; + }), + }; + }, + }), +}; + +export async function callsendfoxApi( + method: HttpMethod, + apiUrl: string, + accessToken: string, + body: any | undefined +): Promise> { + return await httpClient.sendRequest({ + method: method, + url: `https://api.sendfox.com/${apiUrl}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + body: body, + }); +} diff --git a/packages/pieces/community/sendfox/src/index.ts b/packages/pieces/community/sendfox/src/index.ts new file mode 100644 index 0000000..547b4b9 --- /dev/null +++ b/packages/pieces/community/sendfox/src/index.ts @@ -0,0 +1,36 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createContact } from './lib/actions/create-contact'; +import { createList } from './lib/actions/create-list'; +import { unsubscribe } from './lib/actions/unsubscribe-contact'; +export const sendfoxAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: + 'To obtain your personal token, follow these steps:\n1. Log in to your SendFox account.\n2. Visit https://sendfox.com/account/oauth to create one\n3. From OAuth Apps click on Create New Token.\n4. Enter any name you want then click create.\n5. Copy and paste your token here.', + required: true, +}); + +export const sendfox = createPiece({ + displayName: 'SendFox', + description: 'Email marketing made simple', + + auth: sendfoxAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/sendfox.png', + categories: [PieceCategory.MARKETING], + authors: ["Salem-Alaa","kishanprmr","MoShizzle","abuaboud"], + actions: [ + createList, + unsubscribe, + createContact, + createCustomApiCallAction({ + baseUrl: () => 'https://api.sendfox.com', + auth: sendfoxAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/sendfox/src/lib/actions/create-contact.ts b/packages/pieces/community/sendfox/src/lib/actions/create-contact.ts new file mode 100644 index 0000000..f7598ca --- /dev/null +++ b/packages/pieces/community/sendfox/src/lib/actions/create-contact.ts @@ -0,0 +1,49 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { sendfoxAuth } from '../../index'; +import { callsendfoxApi, sendfoxCommon } from '../../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const createContact = createAction({ + name: 'create-contact', + auth: sendfoxAuth, + displayName: 'Create Contact', + description: 'Create a new contact', + props: { + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + firstname: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + lastname: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + list: sendfoxCommon.lists, + }, + async run(context) { + const authentication = context.auth; + const accessToken = authentication; + const email = context.propsValue.email; + const firstname = context.propsValue.firstname; + const lastname = context.propsValue.lastname; + const list = context.propsValue.list; + + const request_body: { [key: string]: any } = { email: email }; + if (firstname) request_body['first_name'] = firstname; + if (lastname) request_body['last_name'] = lastname; + if (list) request_body['lists'] = [list]; + + const response = ( + await callsendfoxApi( + HttpMethod.POST, + 'contacts', + accessToken, + request_body + ) + ).body; + return response; + }, +}); diff --git a/packages/pieces/community/sendfox/src/lib/actions/create-list.ts b/packages/pieces/community/sendfox/src/lib/actions/create-list.ts new file mode 100644 index 0000000..2cdc360 --- /dev/null +++ b/packages/pieces/community/sendfox/src/lib/actions/create-list.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { sendfoxAuth } from '../../index'; +import { callsendfoxApi } from '../../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const createList = createAction({ + name: 'create-list', + auth: sendfoxAuth, + displayName: 'Create List', + description: 'Create a new list', + props: { + task_name: Property.ShortText({ + displayName: 'List Name', + required: true, + }), + }, + async run(context) { + const authentication = context.auth; + const accessToken = authentication; + const task_name = context.propsValue.task_name; + const response = ( + await callsendfoxApi(HttpMethod.POST, 'lists', accessToken, { + name: task_name, + }) + ).body; + return [response]; + }, +}); diff --git a/packages/pieces/community/sendfox/src/lib/actions/unsubscribe-contact.ts b/packages/pieces/community/sendfox/src/lib/actions/unsubscribe-contact.ts new file mode 100644 index 0000000..bc1ef0d --- /dev/null +++ b/packages/pieces/community/sendfox/src/lib/actions/unsubscribe-contact.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { sendfoxAuth } from '../../index'; +import { callsendfoxApi } from '../../common'; +import { HttpMethod } from '@activepieces/pieces-common'; + +export const unsubscribe = createAction({ + name: 'unsubscribe', + auth: sendfoxAuth, + displayName: 'Unsubscribe Contact', + description: 'Unsubscribe a contact', + props: { + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + }, + async run(context) { + const authentication = context.auth; + const accessToken = authentication; + const email = context.propsValue.email; + const response = ( + await callsendfoxApi(HttpMethod.PATCH, 'unsubscribe', accessToken, { + email: email, + }) + ).body; + return [response]; + }, +}); diff --git a/packages/pieces/community/sendfox/tsconfig.json b/packages/pieces/community/sendfox/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/sendfox/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/sendfox/tsconfig.lib.json b/packages/pieces/community/sendfox/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/sendfox/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/sendgrid/.babelrc b/packages/pieces/community/sendgrid/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/sendgrid/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/sendgrid/.eslintrc.json b/packages/pieces/community/sendgrid/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/sendgrid/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/sendgrid/README.md b/packages/pieces/community/sendgrid/README.md new file mode 100644 index 0000000..7c22160 --- /dev/null +++ b/packages/pieces/community/sendgrid/README.md @@ -0,0 +1,7 @@ +# pieces-sendgrid + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-sendgrid` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/sendgrid/package.json b/packages/pieces/community/sendgrid/package.json new file mode 100644 index 0000000..ff3eb04 --- /dev/null +++ b/packages/pieces/community/sendgrid/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-sendgrid", + "version": "0.3.4" +} \ No newline at end of file diff --git a/packages/pieces/community/sendgrid/project.json b/packages/pieces/community/sendgrid/project.json new file mode 100644 index 0000000..ab6826f --- /dev/null +++ b/packages/pieces/community/sendgrid/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-sendgrid", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/sendgrid/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/sendgrid", + "tsConfig": "packages/pieces/community/sendgrid/tsconfig.lib.json", + "packageJson": "packages/pieces/community/sendgrid/package.json", + "main": "packages/pieces/community/sendgrid/src/index.ts", + "assets": [ + "packages/pieces/community/sendgrid/*.md", + { + "input": "packages/pieces/community/sendgrid/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/sendgrid/src/index.ts b/packages/pieces/community/sendgrid/src/index.ts new file mode 100644 index 0000000..12a2c06 --- /dev/null +++ b/packages/pieces/community/sendgrid/src/index.ts @@ -0,0 +1,36 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { sendDynamicTemplate } from './lib/actions/send-dynamic-template'; +import { sendEmail } from './lib/actions/send-email'; +import { sendgridCommon } from './lib/common'; + +export const sendgridAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'API key acquired from your SendGrid settings', +}); + +export const sendgrid = createPiece({ + displayName: 'SendGrid', + description: + 'Email delivery service for sending transactional and marketing emails', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/sendgrid.png', + authors: ["ashrafsamhouri","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + categories: [PieceCategory.COMMUNICATION, PieceCategory.MARKETING], + auth: sendgridAuth, + actions: [ + sendEmail, + sendDynamicTemplate, + createCustomApiCallAction({ + baseUrl: () => sendgridCommon.baseUrl, + auth: sendgridAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/sendgrid/src/lib/actions/send-dynamic-template.ts b/packages/pieces/community/sendgrid/src/lib/actions/send-dynamic-template.ts new file mode 100644 index 0000000..c0f640b --- /dev/null +++ b/packages/pieces/community/sendgrid/src/lib/actions/send-dynamic-template.ts @@ -0,0 +1,74 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpMethod, + AuthenticationType, + httpClient, +} from '@activepieces/pieces-common'; +import { sendgridCommon } from '../common'; +import { sendgridAuth } from '../..'; + +export const sendDynamicTemplate = createAction({ + auth: sendgridAuth, + name: 'send_dynamic_template', + displayName: 'Send Dynamic Template', + description: 'Send an email using a dynamic template', + props: { + to: Property.Array({ + displayName: 'To', + description: 'Emails of the recipients', + required: true, + }), + from_name: Property.ShortText({ + displayName: 'From (Name)', + description: 'Sender name', + required: false, + }), + from: Property.ShortText({ + displayName: 'From (Email)', + description: 'Sender email, must be on your SendGrid', + required: true, + }), + template_id: Property.ShortText({ + displayName: 'Template Id', + description: 'Dynamic template id', + required: true, + }), + template_data: Property.Json({ + displayName: 'Template Data', + description: 'Dynamic template data', + required: true, + }), + reply_to: Property.ShortText({ + displayName: 'Reply To', + description: 'Email to receive replies on (defaults to sender)', + required: false, + }), + }, + async run(context) { + const { to, from, template_id, template_data, reply_to, from_name } = + context.propsValue; + const message = { + personalizations: to.map((email) => ({ + to: [{ email: (email as string).trim() }], + dynamic_template_data: template_data, + })), + from: { email: from, name: from_name }, + reply_to: { email: reply_to ?? from }, + template_id, + }; + + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${sendgridCommon.baseUrl}/mail/send`, + body: message, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + queryParams: {}, + }); + return { + success: true, + }; + }, +}); diff --git a/packages/pieces/community/sendgrid/src/lib/actions/send-email.ts b/packages/pieces/community/sendgrid/src/lib/actions/send-email.ts new file mode 100644 index 0000000..54f1a44 --- /dev/null +++ b/packages/pieces/community/sendgrid/src/lib/actions/send-email.ts @@ -0,0 +1,105 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpMethod, + AuthenticationType, + httpClient, + HttpRequest, +} from '@activepieces/pieces-common'; +import { sendgridCommon } from '../common'; +import { sendgridAuth } from '../..'; + +export const sendEmail = createAction({ + auth: sendgridAuth, + name: 'send_email', + displayName: 'Send Email', + description: 'Send a text or HTML email', + props: { + to: Property.Array({ + displayName: 'To', + description: 'Emails of the recipients', + required: true, + }), + from: Property.ShortText({ + displayName: 'From (Email)', + description: 'Sender email, must be on your SendGrid', + required: true, + }), + from_name: Property.ShortText({ + displayName: 'From (Name)', + description: 'Sender name', + required: false, + }), + reply_to: Property.ShortText({ + displayName: 'Reply To', + description: 'Email to receive replies on (defaults to sender)', + required: false, + }), + subject: Property.ShortText({ + displayName: 'Subject', + description: undefined, + required: true, + }), + content_type: Property.Dropdown<'text' | 'html'>({ + displayName: 'Content Type', + refreshers: [], + required: true, + options: async () => { + return { + disabled: false, + options: [ + { label: 'Plain Text', value: 'text' }, + { label: 'HTML', value: 'html' }, + ], + }; + }, + }), + content: Property.ShortText({ + displayName: 'Content', + description: 'HTML is only allowed if you selected HTML as type', + required: true, + }), + }, + async run(context) { + const { to, from, from_name, reply_to, subject, content_type, content } = + context.propsValue; + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${sendgridCommon.baseUrl}/mail/send`, + body: { + personalizations: to.map((x) => { + return { + to: [ + { + email: (x as string).trim(), + }, + ], + }; + }), + from: { + email: from, + name: from_name, + }, + reply_to: { + email: reply_to ?? from, + }, + subject: subject, + content: [ + { + type: content_type == 'text' ? 'text/plain' : 'text/html', + value: content, + }, + ], + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + queryParams: {}, + }; + await httpClient.sendRequest(request); + + return { + success: true, + }; + }, +}); diff --git a/packages/pieces/community/sendgrid/src/lib/common/index.ts b/packages/pieces/community/sendgrid/src/lib/common/index.ts new file mode 100644 index 0000000..9bf406a --- /dev/null +++ b/packages/pieces/community/sendgrid/src/lib/common/index.ts @@ -0,0 +1,3 @@ +export const sendgridCommon = { + baseUrl: 'https://api.sendgrid.com/v3', +}; diff --git a/packages/pieces/community/sendgrid/tsconfig.json b/packages/pieces/community/sendgrid/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/sendgrid/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/sendgrid/tsconfig.lib.json b/packages/pieces/community/sendgrid/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/sendgrid/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/sendinblue/.eslintrc.json b/packages/pieces/community/sendinblue/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/sendinblue/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/sendinblue/README.md b/packages/pieces/community/sendinblue/README.md new file mode 100644 index 0000000..1e41030 --- /dev/null +++ b/packages/pieces/community/sendinblue/README.md @@ -0,0 +1,7 @@ +# pieces-sendinblue + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-sendinblue` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/sendinblue/package.json b/packages/pieces/community/sendinblue/package.json new file mode 100644 index 0000000..9a1d8e3 --- /dev/null +++ b/packages/pieces/community/sendinblue/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-sendinblue", + "version": "0.1.6" +} \ No newline at end of file diff --git a/packages/pieces/community/sendinblue/project.json b/packages/pieces/community/sendinblue/project.json new file mode 100644 index 0000000..43f3e81 --- /dev/null +++ b/packages/pieces/community/sendinblue/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-sendinblue", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/sendinblue/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/sendinblue", + "tsConfig": "packages/pieces/community/sendinblue/tsconfig.lib.json", + "packageJson": "packages/pieces/community/sendinblue/package.json", + "main": "packages/pieces/community/sendinblue/src/index.ts", + "assets": [ + "packages/pieces/community/sendinblue/*.md", + { + "input": "packages/pieces/community/sendinblue/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/sendinblue/src/index.ts b/packages/pieces/community/sendinblue/src/index.ts new file mode 100644 index 0000000..c28b9cf --- /dev/null +++ b/packages/pieces/community/sendinblue/src/index.ts @@ -0,0 +1,32 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createOrUpdateContact } from './lib/actions/create-or-update-contact'; + +export const sendinblueAuth = PieceAuth.SecretText({ + displayName: 'Project API key', + description: 'Your project API key', + required: true, +}); + +export const sendinblue = createPiece({ + displayName: 'Brevo', + description: + 'Formerly Sendinblue, is a SaaS solution for relationship marketing', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/brevo.png', + authors: ["kanarelo","BLaidzX","Salem-Alaa","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + categories: [PieceCategory.MARKETING], + auth: sendinblueAuth, + actions: [ + createOrUpdateContact, + createCustomApiCallAction({ + baseUrl: () => 'https://api.sendinblue.com/v3', + auth: sendinblueAuth, + authMapping: async (auth) => ({ + 'api-key': auth as string, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/sendinblue/src/lib/actions/create-or-update-contact.ts b/packages/pieces/community/sendinblue/src/lib/actions/create-or-update-contact.ts new file mode 100644 index 0000000..ba5fbf9 --- /dev/null +++ b/packages/pieces/community/sendinblue/src/lib/actions/create-or-update-contact.ts @@ -0,0 +1,107 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { sendinblueAuth } from '../..'; + +export const createOrUpdateContact = createAction({ + auth: sendinblueAuth, + name: 'create_or_update_contact', + displayName: 'Create or Update Contact', + description: 'Create or update an existing contact', + props: { + email: Property.ShortText({ + displayName: 'Email', + description: `Email address of the user. Mandatory if "SMS" field is not passed in "attributes" parameter. Mobile Number in SMS field should be passed with proper country code. For example: {"SMS":"+91xxxxxxxxxx"} or {"SMS":"0091xxxxxxxxxx"}`, + required: true, + }), + ext_id: Property.ShortText({ + displayName: 'External ID', + description: `Pass your own Id to create a contact.`, + required: false, + }), + attributes: Property.Object({ + displayName: 'Attributes', + description: `Pass the set of attributes and their values. The attribute's parameter should be passed in capital letter while creating a contact. These attributes must be present in your SendinBlue account. For eg: + {"FNAME":"Elly", "LNAME":"Roger"}`, + required: false, + defaultValue: { + FIRST_NAME: '', + LAST_NAME: '', + SMS: '', + CIV: '', + DOB: '', + ADDRESS: '', + ZIP_CODE: '', + CITY: '', + AREA: '', + }, + }), + email_blacklisted: Property.Checkbox({ + displayName: 'Email Blacklisted?', + description: `Set this field to blacklist the contact for emails (emailBlacklisted = true)`, + required: false, + defaultValue: false, + }), + sms_blacklisted: Property.Checkbox({ + displayName: 'SMS Blacklisted?', + description: `Set this field to blacklist the contact for SMS (smsBlacklisted = true)`, + required: false, + defaultValue: false, + }), + list_ids: Property.Array({ + displayName: 'List IDs', + description: `Ids of the lists to add the contact to.`, + required: false, + defaultValue: [], + }), + smtp_blacklist_sender: Property.Checkbox({ + displayName: 'SMTP Blacklist Sender', + description: `transactional email forbidden sender for contact. Use only for email Contact ( only available if updateEnabled = true )`, + required: false, + defaultValue: false, + }), + }, + async run(context) { + let listIds: number[] = []; + if (context.propsValue.list_ids) { + listIds = context.propsValue.list_ids.map((listId) => { + return parseInt(listId as unknown as string); + }); + } + const contact = { + email: context.propsValue.email, + ext_id: context.propsValue.ext_id, + attributes: context.propsValue.attributes, + emailBlacklisted: context.propsValue.email_blacklisted, + smsBlacklisted: context.propsValue.sms_blacklisted, + listIds: listIds, + smtpBlacklistSender: context.propsValue.smtp_blacklist_sender, + updateEnabled: true, + }; + const identifier = context.propsValue.email; + + // filter out undefined values + const body = Object.fromEntries( + Object.entries(contact).filter(([_, value]) => Boolean(value)) + ); + + console.log('Contact update request ' + identifier); + const updateResponse = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.sendinblue.com/v3/contacts`, + body, + headers: { + 'api-key': context.auth, + }, + }); + console.debug('Contact update response', updateResponse); + + const contactREsponse = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.sendinblue.com/v3/contacts/${encodeURI(identifier)}`, + headers: { + 'api-key': context.auth, + }, + }); + return contactREsponse.body; + }, +}); diff --git a/packages/pieces/community/sendinblue/tsconfig.json b/packages/pieces/community/sendinblue/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/sendinblue/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/sendinblue/tsconfig.lib.json b/packages/pieces/community/sendinblue/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/sendinblue/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/sendy/.eslintrc.json b/packages/pieces/community/sendy/.eslintrc.json new file mode 100644 index 0000000..15ede17 --- /dev/null +++ b/packages/pieces/community/sendy/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*", "package.json"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/sendy/README.md b/packages/pieces/community/sendy/README.md new file mode 100644 index 0000000..cf1842f --- /dev/null +++ b/packages/pieces/community/sendy/README.md @@ -0,0 +1,7 @@ +# pieces-sendy + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-sendy` to build the library. diff --git a/packages/pieces/community/sendy/package.json b/packages/pieces/community/sendy/package.json new file mode 100644 index 0000000..b8b4427 --- /dev/null +++ b/packages/pieces/community/sendy/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-sendy", + "version": "0.0.10" +} \ No newline at end of file diff --git a/packages/pieces/community/sendy/project.json b/packages/pieces/community/sendy/project.json new file mode 100644 index 0000000..3f36c46 --- /dev/null +++ b/packages/pieces/community/sendy/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-sendy", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/sendy/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/sendy", + "tsConfig": "packages/pieces/community/sendy/tsconfig.lib.json", + "packageJson": "packages/pieces/community/sendy/package.json", + "main": "packages/pieces/community/sendy/src/index.ts", + "assets": [ + "packages/pieces/community/sendy/*.md", + { + "input": "packages/pieces/community/sendy/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-sendy {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/sendy/src/index.ts b/packages/pieces/community/sendy/src/index.ts new file mode 100644 index 0000000..4af72d9 --- /dev/null +++ b/packages/pieces/community/sendy/src/index.ts @@ -0,0 +1,38 @@ +import { createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { countAction } from './lib/actions/count-subscriber'; +import { createCampaignAction } from './lib/actions/create-campaign'; +import { deleteAction } from './lib/actions/delete-subscriber'; +import { getListsAction } from './lib/actions/get-brand-lists'; +import { getBrandsAction } from './lib/actions/get-brands'; +import { statusAction } from './lib/actions/get-subscription-status'; +import { subscribeAction } from './lib/actions/subscribe'; +import { subscribeMultipleAction } from './lib/actions/subscribe-multiple'; +import { unsubscribeAction } from './lib/actions/unsubscribe'; +import { unsubscribeMultipleAction } from './lib/actions/unsubscribe-multiple'; +import { sendyAuth } from './lib/auth'; + +export const sendy = createPiece({ + displayName: 'Sendy', + description: 'Self-hosted email marketing software', + auth: sendyAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/sendy.png', + categories: [PieceCategory.MARKETING], + authors: ["joeworkman","kishanprmr","abuaboud"], + actions: [ + countAction, + createCampaignAction, + deleteAction, + getBrandsAction, + getListsAction, + statusAction, + subscribeAction, + subscribeMultipleAction, + unsubscribeAction, + unsubscribeMultipleAction, + ], + triggers: [], +}); + +// Sendy APi docs: https://sendy.co/api diff --git a/packages/pieces/community/sendy/src/lib/actions/count-subscriber.ts b/packages/pieces/community/sendy/src/lib/actions/count-subscriber.ts new file mode 100644 index 0000000..4c0bbbe --- /dev/null +++ b/packages/pieces/community/sendy/src/lib/actions/count-subscriber.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { count } from '../api'; +import { buildListDropdown } from '../props'; +import { sendyAuth, SendyAuthType } from '../auth'; + +export const countAction = createAction({ + name: 'count_subscribers', + auth: sendyAuth, + displayName: 'Count Active Subscribers', + description: 'Get the active subscriber count for a list', + props: { + list: Property.Dropdown({ + displayName: 'List', + description: 'Select the list to get the status from', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => + await buildListDropdown(auth as SendyAuthType), + }), + }, + async run(context) { + return await count(context.auth, { + list_id: context.propsValue.list, + }); + }, +}); diff --git a/packages/pieces/community/sendy/src/lib/actions/create-campaign.ts b/packages/pieces/community/sendy/src/lib/actions/create-campaign.ts new file mode 100644 index 0000000..eebb8ca --- /dev/null +++ b/packages/pieces/community/sendy/src/lib/actions/create-campaign.ts @@ -0,0 +1,644 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { createCampaign } from '../api'; +import { buildListDropdown } from '../props'; +import { sendyAuth, SendyAuthType } from '../auth'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const createCampaignAction = createAction({ + name: 'create_campaign', + auth: sendyAuth, + displayName: 'Create Campaign', + description: 'Create a new campaign', + props: { + fromEmail: Property.ShortText({ + displayName: 'From Email', + description: 'The email address the campaign will be sent from', + required: true, + }), + fromName: Property.ShortText({ + displayName: 'From Name', + description: 'The name the campaign will be sent from', + required: true, + }), + replyTo: Property.ShortText({ + displayName: 'Reply To', + description: 'The email address that will be used for replies', + required: true, + }), + title: Property.ShortText({ + displayName: 'Title', + description: 'The title of the campaign', + required: true, + }), + subject: Property.ShortText({ + displayName: 'Subject', + description: 'The subject of the campaign email', + required: true, + }), + plainText: Property.LongText({ + displayName: 'Plain Text', + description: 'The plain text version of the campaign email', + required: false, + }), + html: Property.LongText({ + displayName: 'HTML', + description: 'The HTML version of the campaign email', + required: true, + }), + lists: Property.MultiSelectDropdown({ + displayName: 'Lists', + description: 'Select lists to send the campaign to', + required: false, + refreshers: ['auth'], + options: async ({ auth }) => + await buildListDropdown(auth as SendyAuthType), + }), + excludeLists: Property.MultiSelectDropdown({ + displayName: 'Exclude Lists', + description: 'Select lists to exclude from the campaign send', + required: false, + refreshers: ['auth'], + options: async ({ auth }) => + await buildListDropdown(auth as SendyAuthType), + }), + segments: Property.ShortText({ + displayName: 'Segment IDs', + description: + 'Comma separated list of segment IDs to send the campaign to', + required: false, + }), + excludeSegments: Property.ShortText({ + displayName: 'Exclude Segment IDs', + description: + 'Comma separated list of segment IDs to exclude from the campaign send', + required: false, + }), + queryString: Property.ShortText({ + displayName: 'Query String', + description: 'The query string to append to the campaign URL', + required: false, + }), + trackOpens: Property.StaticDropdown({ + displayName: 'Track Opens', + description: 'Enable open tracking', + required: true, + defaultValue: '1', + options: { + options: [ + { label: 'Disable', value: '0' }, + { label: 'Enable', value: '1' }, + { label: 'Anonymous', value: '2' }, + ], + }, + }), + trackClicks: Property.StaticDropdown({ + displayName: 'Track Clicks', + description: 'Enable click tracking', + required: true, + defaultValue: '1', + options: { + options: [ + { label: 'Disable', value: '0' }, + { label: 'Enable', value: '1' }, + { label: 'Anonymous', value: '2' }, + ], + }, + }), + autoSend: Property.Checkbox({ + displayName: 'Automatically Send', + description: 'Automatically send the campaign after creation', + required: false, + defaultValue: false, + }), + schedule: Property.DateTime({ + displayName: 'Schedule the Campaign', + description: + 'Schedule the campaign to be sent at a specific date and time. Date/time format: 2023-06-09T12:00:00Z', + required: false, + }), + timezone: Property.StaticDropdown({ + displayName: 'Timezone', + description: 'The timezone to use for the schedule', + required: false, + options: { + options: [ + { label: 'Africa/Abidjan', value: 'Africa/Abidjan' }, + { label: 'Africa/Accra', value: 'Africa/Accra' }, + { label: 'Africa/Addis_Ababa', value: 'Africa/Addis_Ababa' }, + { label: 'Africa/Algiers', value: 'Africa/Algiers' }, + { label: 'Africa/Asmara', value: 'Africa/Asmara' }, + { label: 'Africa/Bamako', value: 'Africa/Bamako' }, + { label: 'Africa/Bangui', value: 'Africa/Bangui' }, + { label: 'Africa/Banjul', value: 'Africa/Banjul' }, + { label: 'Africa/Bissau', value: 'Africa/Bissau' }, + { label: 'Africa/Blantyre', value: 'Africa/Blantyre' }, + { label: 'Africa/Brazzaville', value: 'Africa/Brazzaville' }, + { label: 'Africa/Bujumbura', value: 'Africa/Bujumbura' }, + { label: 'Africa/Cairo', value: 'Africa/Cairo' }, + { label: 'Africa/Casablanca', value: 'Africa/Casablanca' }, + { label: 'Africa/Ceuta', value: 'Africa/Ceuta' }, + { label: 'Africa/Conakry', value: 'Africa/Conakry' }, + { label: 'Africa/Dakar', value: 'Africa/Dakar' }, + { label: 'Africa/Dar_es_Salaam', value: 'Africa/Dar_es_Salaam' }, + { label: 'Africa/Djibouti', value: 'Africa/Djibouti' }, + { label: 'Africa/Douala', value: 'Africa/Douala' }, + { label: 'Africa/El_Aaiun', value: 'Africa/El_Aaiun' }, + { label: 'Africa/Freetown', value: 'Africa/Freetown' }, + { label: 'Africa/Gaborone', value: 'Africa/Gaborone' }, + { label: 'Africa/Harare', value: 'Africa/Harare' }, + { label: 'Africa/Johannesburg', value: 'Africa/Johannesburg' }, + { label: 'Africa/Juba', value: 'Africa/Juba' }, + { label: 'Africa/Kampala', value: 'Africa/Kampala' }, + { label: 'Africa/Khartoum', value: 'Africa/Khartoum' }, + { label: 'Africa/Kigali', value: 'Africa/Kigali' }, + { label: 'Africa/Kinshasa', value: 'Africa/Kinshasa' }, + { label: 'Africa/Lagos', value: 'Africa/Lagos' }, + { label: 'Africa/Libreville', value: 'Africa/Libreville' }, + { label: 'Africa/Lome', value: 'Africa/Lome' }, + { label: 'Africa/Luanda', value: 'Africa/Luanda' }, + { label: 'Africa/Lubumbashi', value: 'Africa/Lubumbashi' }, + { label: 'Africa/Lusaka', value: 'Africa/Lusaka' }, + { label: 'Africa/Malabo', value: 'Africa/Malabo' }, + { label: 'Africa/Maputo', value: 'Africa/Maputo' }, + { label: 'Africa/Maseru', value: 'Africa/Maseru' }, + { label: 'Africa/Mbabane', value: 'Africa/Mbabane' }, + { label: 'Africa/Mogadishu', value: 'Africa/Mogadishu' }, + { label: 'Africa/Monrovia', value: 'Africa/Monrovia' }, + { label: 'Africa/Nairobi', value: 'Africa/Nairobi' }, + { label: 'Africa/Ndjamena', value: 'Africa/Ndjamena' }, + { label: 'Africa/Niamey', value: 'Africa/Niamey' }, + { label: 'Africa/Nouakchott', value: 'Africa/Nouakchott' }, + { label: 'Africa/Ouagadougou', value: 'Africa/Ouagadougou' }, + { label: 'Africa/Porto-Novo', value: 'Africa/Porto-Novo' }, + { label: 'Africa/Sao_Tome', value: 'Africa/Sao_Tome' }, + { label: 'Africa/Tripoli', value: 'Africa/Tripoli' }, + { label: 'Africa/Tunis', value: 'Africa/Tunis' }, + { label: 'Africa/Windhoek', value: 'Africa/Windhoek' }, + { label: 'America/Adak', value: 'America/Adak' }, + { label: 'America/Anchorage', value: 'America/Anchorage' }, + { label: 'America/Anguilla', value: 'America/Anguilla' }, + { label: 'America/Antigua', value: 'America/Antigua' }, + { label: 'America/Araguaina', value: 'America/Araguaina' }, + { + label: 'America/Argentina/Buenos_Aires', + value: 'America/Argentina/Buenos_Aires', + }, + { + label: 'America/Argentina/Catamarca', + value: 'America/Argentina/Catamarca', + }, + { + label: 'America/Argentina/Cordoba', + value: 'America/Argentina/Cordoba', + }, + { + label: 'America/Argentina/Jujuy', + value: 'America/Argentina/Jujuy', + }, + { + label: 'America/Argentina/La_Rioja', + value: 'America/Argentina/La_Rioja', + }, + { + label: 'America/Argentina/Mendoza', + value: 'America/Argentina/Mendoza', + }, + { + label: 'America/Argentina/Rio_Gallegos', + value: 'America/Argentina/Rio_Gallegos', + }, + { + label: 'America/Argentina/Salta', + value: 'America/Argentina/Salta', + }, + { + label: 'America/Argentina/San_Juan', + value: 'America/Argentina/San_Juan', + }, + { + label: 'America/Argentina/San_Luis', + value: 'America/Argentina/San_Luis', + }, + { + label: 'America/Argentina/Tucuman', + value: 'America/Argentina/Tucuman', + }, + { + label: 'America/Argentina/Ushuaia', + value: 'America/Argentina/Ushuaia', + }, + { label: 'America/Aruba', value: 'America/Aruba' }, + { label: 'America/Asuncion', value: 'America/Asuncion' }, + { label: 'America/Atikokan', value: 'America/Atikokan' }, + { label: 'America/Bahia', value: 'America/Bahia' }, + { label: 'America/Bahia_Banderas', value: 'America/Bahia_Banderas' }, + { label: 'America/Barbados', value: 'America/Barbados' }, + { label: 'America/Belem', value: 'America/Belem' }, + { label: 'America/Belize', value: 'America/Belize' }, + { label: 'America/Blanc-Sablon', value: 'America/Blanc-Sablon' }, + { label: 'America/Boa_Vista', value: 'America/Boa_Vista' }, + { label: 'America/Bogota', value: 'America/Bogota' }, + { label: 'America/Boise', value: 'America/Boise' }, + { label: 'America/Cambridge_Bay', value: 'America/Cambridge_Bay' }, + { label: 'America/Campo_Grande', value: 'America/Campo_Grande' }, + { label: 'America/Cancun', value: 'America/Cancun' }, + { label: 'America/Caracas', value: 'America/Caracas' }, + { label: 'America/Cayenne', value: 'America/Cayenne' }, + { label: 'America/Cayman', value: 'America/Cayman' }, + { label: 'America/Chicago', value: 'America/Chicago' }, + { label: 'America/Chihuahua', value: 'America/Chihuahua' }, + { label: 'America/Ciudad_Juarez', value: 'America/Ciudad_Juarez' }, + { label: 'America/Costa_Rica', value: 'America/Costa_Rica' }, + { label: 'America/Creston', value: 'America/Creston' }, + { label: 'America/Cuiaba', value: 'America/Cuiaba' }, + { label: 'America/Curacao', value: 'America/Curacao' }, + { label: 'America/Danmarkshavn', value: 'America/Danmarkshavn' }, + { label: 'America/Dawson', value: 'America/Dawson' }, + { label: 'America/Dawson_Creek', value: 'America/Dawson_Creek' }, + { label: 'America/Denver', value: 'America/Denver' }, + { label: 'America/Detroit', value: 'America/Detroit' }, + { label: 'America/Dominica', value: 'America/Dominica' }, + { label: 'America/Edmonton', value: 'America/Edmonton' }, + { label: 'America/Eirunepe', value: 'America/Eirunepe' }, + { label: 'America/El_Salvador', value: 'America/El_Salvador' }, + { label: 'America/Fort_Nelson', value: 'America/Fort_Nelson' }, + { label: 'America/Fortaleza', value: 'America/Fortaleza' }, + { label: 'America/Glace_Bay', value: 'America/Glace_Bay' }, + { label: 'America/Goose_Bay', value: 'America/Goose_Bay' }, + { label: 'America/Grand_Turk', value: 'America/Grand_Turk' }, + { label: 'America/Grenada', value: 'America/Grenada' }, + { label: 'America/Guadeloupe', value: 'America/Guadeloupe' }, + { label: 'America/Guatemala', value: 'America/Guatemala' }, + { label: 'America/Guayaquil', value: 'America/Guayaquil' }, + { label: 'America/Guyana', value: 'America/Guyana' }, + { label: 'America/Halifax', value: 'America/Halifax' }, + { label: 'America/Havana', value: 'America/Havana' }, + { label: 'America/Hermosillo', value: 'America/Hermosillo' }, + { + label: 'America/Indiana/Indianapolis', + value: 'America/Indiana/Indianapolis', + }, + { label: 'America/Indiana/Knox', value: 'America/Indiana/Knox' }, + { + label: 'America/Indiana/Marengo', + value: 'America/Indiana/Marengo', + }, + { + label: 'America/Indiana/Petersburg', + value: 'America/Indiana/Petersburg', + }, + { + label: 'America/Indiana/Tell_City', + value: 'America/Indiana/Tell_City', + }, + { label: 'America/Indiana/Vevay', value: 'America/Indiana/Vevay' }, + { + label: 'America/Indiana/Vincennes', + value: 'America/Indiana/Vincennes', + }, + { + label: 'America/Indiana/Winamac', + value: 'America/Indiana/Winamac', + }, + { label: 'America/Inuvik', value: 'America/Inuvik' }, + { label: 'America/Iqaluit', value: 'America/Iqaluit' }, + { label: 'America/Jamaica', value: 'America/Jamaica' }, + { label: 'America/Juneau', value: 'America/Juneau' }, + { + label: 'America/Kentucky/Louisville', + value: 'America/Kentucky/Louisville', + }, + { + label: 'America/Kentucky/Monticello', + value: 'America/Kentucky/Monticello', + }, + { label: 'America/Kralendijk', value: 'America/Kralendijk' }, + { label: 'America/La_Paz', value: 'America/La_Paz' }, + { label: 'America/Lima', value: 'America/Lima' }, + { label: 'America/Los_Angeles', value: 'America/Los_Angeles' }, + { label: 'America/Lower_Princes', value: 'America/Lower_Princes' }, + { label: 'America/Maceio', value: 'America/Maceio' }, + { label: 'America/Managua', value: 'America/Managua' }, + { label: 'America/Manaus', value: 'America/Manaus' }, + { label: 'America/Marigot', value: 'America/Marigot' }, + { label: 'America/Martinique', value: 'America/Martinique' }, + { label: 'America/Matamoros', value: 'America/Matamoros' }, + { label: 'America/Mazatlan', value: 'America/Mazatlan' }, + { label: 'America/Menominee', value: 'America/Menominee' }, + { label: 'America/Merida', value: 'America/Merida' }, + { label: 'America/Metlakatla', value: 'America/Metlakatla' }, + { label: 'America/Mexico_City', value: 'America/Mexico_City' }, + { label: 'America/Miquelon', value: 'America/Miquelon' }, + { label: 'America/Moncton', value: 'America/Moncton' }, + { label: 'America/Monterrey', value: 'America/Monterrey' }, + { label: 'America/Montevideo', value: 'America/Montevideo' }, + { label: 'America/Montserrat', value: 'America/Montserrat' }, + { label: 'America/Nassau', value: 'America/Nassau' }, + { label: 'America/New_York', value: 'America/New_York' }, + { label: 'America/Nome', value: 'America/Nome' }, + { label: 'America/Noronha', value: 'America/Noronha' }, + { + label: 'America/North_Dakota/Beulah', + value: 'America/North_Dakota/Beulah', + }, + { + label: 'America/North_Dakota/Center', + value: 'America/North_Dakota/Center', + }, + { + label: 'America/North_Dakota/New_Salem', + value: 'America/North_Dakota/New_Salem', + }, + { label: 'America/Nuuk', value: 'America/Nuuk' }, + { label: 'America/Ojinaga', value: 'America/Ojinaga' }, + { label: 'America/Panama', value: 'America/Panama' }, + { label: 'America/Paramaribo', value: 'America/Paramaribo' }, + { label: 'America/Phoenix', value: 'America/Phoenix' }, + { label: 'America/Port-au-Prince', value: 'America/Port-au-Prince' }, + { label: 'America/Port_of_Spain', value: 'America/Port_of_Spain' }, + { label: 'America/Porto_Velho', value: 'America/Porto_Velho' }, + { label: 'America/Puerto_Rico', value: 'America/Puerto_Rico' }, + { label: 'America/Punta_Arenas', value: 'America/Punta_Arenas' }, + { label: 'America/Rankin_Inlet', value: 'America/Rankin_Inlet' }, + { label: 'America/Recife', value: 'America/Recife' }, + { label: 'America/Regina', value: 'America/Regina' }, + { label: 'America/Resolute', value: 'America/Resolute' }, + { label: 'America/Rio_Branco', value: 'America/Rio_Branco' }, + { label: 'America/Santarem', value: 'America/Santarem' }, + { label: 'America/Santiago', value: 'America/Santiago' }, + { label: 'America/Santo_Domingo', value: 'America/Santo_Domingo' }, + { label: 'America/Sao_Paulo', value: 'America/Sao_Paulo' }, + { label: 'America/Scoresbysund', value: 'America/Scoresbysund' }, + { label: 'America/Sitka', value: 'America/Sitka' }, + { label: 'America/St_Barthelemy', value: 'America/St_Barthelemy' }, + { label: 'America/St_Johns', value: 'America/St_Johns' }, + { label: 'America/St_Kitts', value: 'America/St_Kitts' }, + { label: 'America/St_Lucia', value: 'America/St_Lucia' }, + { label: 'America/St_Thomas', value: 'America/St_Thomas' }, + { label: 'America/St_Vincent', value: 'America/St_Vincent' }, + { label: 'America/Swift_Current', value: 'America/Swift_Current' }, + { label: 'America/Tegucigalpa', value: 'America/Tegucigalpa' }, + { label: 'America/Thule', value: 'America/Thule' }, + { label: 'America/Tijuana', value: 'America/Tijuana' }, + { label: 'America/Toronto', value: 'America/Toronto' }, + { label: 'America/Tortola', value: 'America/Tortola' }, + { label: 'America/Vancouver', value: 'America/Vancouver' }, + { label: 'America/Whitehorse', value: 'America/Whitehorse' }, + { label: 'America/Winnipeg', value: 'America/Winnipeg' }, + { label: 'America/Yakutat', value: 'America/Yakutat' }, + { label: 'Antarctica/Casey', value: 'Antarctica/Casey' }, + { label: 'Antarctica/Davis', value: 'Antarctica/Davis' }, + { + label: 'Antarctica/DumontDUrville', + value: 'Antarctica/DumontDUrville', + }, + { label: 'Antarctica/Macquarie', value: 'Antarctica/Macquarie' }, + { label: 'Antarctica/Mawson', value: 'Antarctica/Mawson' }, + { label: 'Antarctica/McMurdo', value: 'Antarctica/McMurdo' }, + { label: 'Antarctica/Palmer', value: 'Antarctica/Palmer' }, + { label: 'Antarctica/Rothera', value: 'Antarctica/Rothera' }, + { label: 'Antarctica/Syowa', value: 'Antarctica/Syowa' }, + { label: 'Antarctica/Troll', value: 'Antarctica/Troll' }, + { label: 'Antarctica/Vostok', value: 'Antarctica/Vostok' }, + { label: 'Arctic/Longyearbyen', value: 'Arctic/Longyearbyen' }, + { label: 'Asia/Aden', value: 'Asia/Aden' }, + { label: 'Asia/Almaty', value: 'Asia/Almaty' }, + { label: 'Asia/Amman', value: 'Asia/Amman' }, + { label: 'Asia/Anadyr', value: 'Asia/Anadyr' }, + { label: 'Asia/Aqtau', value: 'Asia/Aqtau' }, + { label: 'Asia/Aqtobe', value: 'Asia/Aqtobe' }, + { label: 'Asia/Ashgabat', value: 'Asia/Ashgabat' }, + { label: 'Asia/Atyrau', value: 'Asia/Atyrau' }, + { label: 'Asia/Baghdad', value: 'Asia/Baghdad' }, + { label: 'Asia/Bahrain', value: 'Asia/Bahrain' }, + { label: 'Asia/Baku', value: 'Asia/Baku' }, + { label: 'Asia/Bangkok', value: 'Asia/Bangkok' }, + { label: 'Asia/Barnaul', value: 'Asia/Barnaul' }, + { label: 'Asia/Beirut', value: 'Asia/Beirut' }, + { label: 'Asia/Bishkek', value: 'Asia/Bishkek' }, + { label: 'Asia/Brunei', value: 'Asia/Brunei' }, + { label: 'Asia/Chita', value: 'Asia/Chita' }, + { label: 'Asia/Choibalsan', value: 'Asia/Choibalsan' }, + { label: 'Asia/Colombo', value: 'Asia/Colombo' }, + { label: 'Asia/Damascus', value: 'Asia/Damascus' }, + { label: 'Asia/Dhaka', value: 'Asia/Dhaka' }, + { label: 'Asia/Dili', value: 'Asia/Dili' }, + { label: 'Asia/Dubai', value: 'Asia/Dubai' }, + { label: 'Asia/Dushanbe', value: 'Asia/Dushanbe' }, + { label: 'Asia/Famagusta', value: 'Asia/Famagusta' }, + { label: 'Asia/Gaza', value: 'Asia/Gaza' }, + { label: 'Asia/Hebron', value: 'Asia/Hebron' }, + { label: 'Asia/Ho_Chi_Minh', value: 'Asia/Ho_Chi_Minh' }, + { label: 'Asia/Hong_Kong', value: 'Asia/Hong_Kong' }, + { label: 'Asia/Hovd', value: 'Asia/Hovd' }, + { label: 'Asia/Irkutsk', value: 'Asia/Irkutsk' }, + { label: 'Asia/Jakarta', value: 'Asia/Jakarta' }, + { label: 'Asia/Jayapura', value: 'Asia/Jayapura' }, + { label: 'Asia/Jerusalem', value: 'Asia/Jerusalem' }, + { label: 'Asia/Kabul', value: 'Asia/Kabul' }, + { label: 'Asia/Kamchatka', value: 'Asia/Kamchatka' }, + { label: 'Asia/Karachi', value: 'Asia/Karachi' }, + { label: 'Asia/Kathmandu', value: 'Asia/Kathmandu' }, + { label: 'Asia/Khandyga', value: 'Asia/Khandyga' }, + { label: 'Asia/Kolkata', value: 'Asia/Kolkata' }, + { label: 'Asia/Krasnoyarsk', value: 'Asia/Krasnoyarsk' }, + { label: 'Asia/Kuala_Lumpur', value: 'Asia/Kuala_Lumpur' }, + { label: 'Asia/Kuching', value: 'Asia/Kuching' }, + { label: 'Asia/Kuwait', value: 'Asia/Kuwait' }, + { label: 'Asia/Macau', value: 'Asia/Macau' }, + { label: 'Asia/Magadan', value: 'Asia/Magadan' }, + { label: 'Asia/Makassar', value: 'Asia/Makassar' }, + { label: 'Asia/Manila', value: 'Asia/Manila' }, + { label: 'Asia/Muscat', value: 'Asia/Muscat' }, + { label: 'Asia/Nicosia', value: 'Asia/Nicosia' }, + { label: 'Asia/Novokuznetsk', value: 'Asia/Novokuznetsk' }, + { label: 'Asia/Novosibirsk', value: 'Asia/Novosibirsk' }, + { label: 'Asia/Omsk', value: 'Asia/Omsk' }, + { label: 'Asia/Oral', value: 'Asia/Oral' }, + { label: 'Asia/Phnom_Penh', value: 'Asia/Phnom_Penh' }, + { label: 'Asia/Pontianak', value: 'Asia/Pontianak' }, + { label: 'Asia/Pyongyang', value: 'Asia/Pyongyang' }, + { label: 'Asia/Qatar', value: 'Asia/Qatar' }, + { label: 'Asia/Qostanay', value: 'Asia/Qostanay' }, + { label: 'Asia/Qyzylorda', value: 'Asia/Qyzylorda' }, + { label: 'Asia/Riyadh', value: 'Asia/Riyadh' }, + { label: 'Asia/Sakhalin', value: 'Asia/Sakhalin' }, + { label: 'Asia/Samarkand', value: 'Asia/Samarkand' }, + { label: 'Asia/Seoul', value: 'Asia/Seoul' }, + { label: 'Asia/Shanghai', value: 'Asia/Shanghai' }, + { label: 'Asia/Singapore', value: 'Asia/Singapore' }, + { label: 'Asia/Srednekolymsk', value: 'Asia/Srednekolymsk' }, + { label: 'Asia/Taipei', value: 'Asia/Taipei' }, + { label: 'Asia/Tashkent', value: 'Asia/Tashkent' }, + { label: 'Asia/Tbilisi', value: 'Asia/Tbilisi' }, + { label: 'Asia/Tehran', value: 'Asia/Tehran' }, + { label: 'Asia/Thimphu', value: 'Asia/Thimphu' }, + { label: 'Asia/Tokyo', value: 'Asia/Tokyo' }, + { label: 'Asia/Tomsk', value: 'Asia/Tomsk' }, + { label: 'Asia/Ulaanbaatar', value: 'Asia/Ulaanbaatar' }, + { label: 'Asia/Urumqi', value: 'Asia/Urumqi' }, + { label: 'Asia/Ust-Nera', value: 'Asia/Ust-Nera' }, + { label: 'Asia/Vientiane', value: 'Asia/Vientiane' }, + { label: 'Asia/Vladivostok', value: 'Asia/Vladivostok' }, + { label: 'Asia/Yakutsk', value: 'Asia/Yakutsk' }, + { label: 'Asia/Yangon', value: 'Asia/Yangon' }, + { label: 'Asia/Yekaterinburg', value: 'Asia/Yekaterinburg' }, + { label: 'Asia/Yerevan', value: 'Asia/Yerevan' }, + { label: 'Atlantic/Azores', value: 'Atlantic/Azores' }, + { label: 'Atlantic/Bermuda', value: 'Atlantic/Bermuda' }, + { label: 'Atlantic/Canary', value: 'Atlantic/Canary' }, + { label: 'Atlantic/Cape_Verde', value: 'Atlantic/Cape_Verde' }, + { label: 'Atlantic/Faroe', value: 'Atlantic/Faroe' }, + { label: 'Atlantic/Madeira', value: 'Atlantic/Madeira' }, + { label: 'Atlantic/Reykjavik', value: 'Atlantic/Reykjavik' }, + { label: 'Atlantic/South_Georgia', value: 'Atlantic/South_Georgia' }, + { label: 'Atlantic/St_Helena', value: 'Atlantic/St_Helena' }, + { label: 'Atlantic/Stanley', value: 'Atlantic/Stanley' }, + { label: 'Australia/Adelaide', value: 'Australia/Adelaide' }, + { label: 'Australia/Brisbane', value: 'Australia/Brisbane' }, + { label: 'Australia/Broken_Hill', value: 'Australia/Broken_Hill' }, + { label: 'Australia/Darwin', value: 'Australia/Darwin' }, + { label: 'Australia/Eucla', value: 'Australia/Eucla' }, + { label: 'Australia/Hobart', value: 'Australia/Hobart' }, + { label: 'Australia/Lindeman', value: 'Australia/Lindeman' }, + { label: 'Australia/Lord_Howe', value: 'Australia/Lord_Howe' }, + { label: 'Australia/Melbourne', value: 'Australia/Melbourne' }, + { label: 'Australia/Perth', value: 'Australia/Perth' }, + { label: 'Australia/Sydney', value: 'Australia/Sydney' }, + { label: 'Europe/Amsterdam', value: 'Europe/Amsterdam' }, + { label: 'Europe/Andorra', value: 'Europe/Andorra' }, + { label: 'Europe/Astrakhan', value: 'Europe/Astrakhan' }, + { label: 'Europe/Athens', value: 'Europe/Athens' }, + { label: 'Europe/Belgrade', value: 'Europe/Belgrade' }, + { label: 'Europe/Berlin', value: 'Europe/Berlin' }, + { label: 'Europe/Bratislava', value: 'Europe/Bratislava' }, + { label: 'Europe/Brussels', value: 'Europe/Brussels' }, + { label: 'Europe/Bucharest', value: 'Europe/Bucharest' }, + { label: 'Europe/Budapest', value: 'Europe/Budapest' }, + { label: 'Europe/Busingen', value: 'Europe/Busingen' }, + { label: 'Europe/Chisinau', value: 'Europe/Chisinau' }, + { label: 'Europe/Copenhagen', value: 'Europe/Copenhagen' }, + { label: 'Europe/Dublin', value: 'Europe/Dublin' }, + { label: 'Europe/Gibraltar', value: 'Europe/Gibraltar' }, + { label: 'Europe/Guernsey', value: 'Europe/Guernsey' }, + { label: 'Europe/Helsinki', value: 'Europe/Helsinki' }, + { label: 'Europe/Isle_of_Man', value: 'Europe/Isle_of_Man' }, + { label: 'Europe/Istanbul', value: 'Europe/Istanbul' }, + { label: 'Europe/Jersey', value: 'Europe/Jersey' }, + { label: 'Europe/Kaliningrad', value: 'Europe/Kaliningrad' }, + { label: 'Europe/Kirov', value: 'Europe/Kirov' }, + { label: 'Europe/Kyiv', value: 'Europe/Kyiv' }, + { label: 'Europe/Lisbon', value: 'Europe/Lisbon' }, + { label: 'Europe/Ljubljana', value: 'Europe/Ljubljana' }, + { label: 'Europe/London', value: 'Europe/London' }, + { label: 'Europe/Luxembourg', value: 'Europe/Luxembourg' }, + { label: 'Europe/Madrid', value: 'Europe/Madrid' }, + { label: 'Europe/Malta', value: 'Europe/Malta' }, + { label: 'Europe/Mariehamn', value: 'Europe/Mariehamn' }, + { label: 'Europe/Minsk', value: 'Europe/Minsk' }, + { label: 'Europe/Monaco', value: 'Europe/Monaco' }, + { label: 'Europe/Moscow', value: 'Europe/Moscow' }, + { label: 'Europe/Oslo', value: 'Europe/Oslo' }, + { label: 'Europe/Paris', value: 'Europe/Paris' }, + { label: 'Europe/Podgorica', value: 'Europe/Podgorica' }, + { label: 'Europe/Prague', value: 'Europe/Prague' }, + { label: 'Europe/Riga', value: 'Europe/Riga' }, + { label: 'Europe/Rome', value: 'Europe/Rome' }, + { label: 'Europe/Samara', value: 'Europe/Samara' }, + { label: 'Europe/San_Marino', value: 'Europe/San_Marino' }, + { label: 'Europe/Sarajevo', value: 'Europe/Sarajevo' }, + { label: 'Europe/Saratov', value: 'Europe/Saratov' }, + { label: 'Europe/Simferopol', value: 'Europe/Simferopol' }, + { label: 'Europe/Skopje', value: 'Europe/Skopje' }, + { label: 'Europe/Sofia', value: 'Europe/Sofia' }, + { label: 'Europe/Stockholm', value: 'Europe/Stockholm' }, + { label: 'Europe/Tallinn', value: 'Europe/Tallinn' }, + { label: 'Europe/Tirane', value: 'Europe/Tirane' }, + { label: 'Europe/Ulyanovsk', value: 'Europe/Ulyanovsk' }, + { label: 'Europe/Vaduz', value: 'Europe/Vaduz' }, + { label: 'Europe/Vatican', value: 'Europe/Vatican' }, + { label: 'Europe/Vienna', value: 'Europe/Vienna' }, + { label: 'Europe/Vilnius', value: 'Europe/Vilnius' }, + { label: 'Europe/Volgograd', value: 'Europe/Volgograd' }, + { label: 'Europe/Warsaw', value: 'Europe/Warsaw' }, + { label: 'Europe/Zagreb', value: 'Europe/Zagreb' }, + { label: 'Europe/Zurich', value: 'Europe/Zurich' }, + { label: 'Indian/Antananarivo', value: 'Indian/Antananarivo' }, + { label: 'Indian/Chagos', value: 'Indian/Chagos' }, + { label: 'Indian/Christmas', value: 'Indian/Christmas' }, + { label: 'Indian/Cocos', value: 'Indian/Cocos' }, + { label: 'Indian/Comoro', value: 'Indian/Comoro' }, + { label: 'Indian/Kerguelen', value: 'Indian/Kerguelen' }, + { label: 'Indian/Mahe', value: 'Indian/Mahe' }, + { label: 'Indian/Maldives', value: 'Indian/Maldives' }, + { label: 'Indian/Mauritius', value: 'Indian/Mauritius' }, + { label: 'Indian/Mayotte', value: 'Indian/Mayotte' }, + { label: 'Indian/Reunion', value: 'Indian/Reunion' }, + { label: 'Pacific/Apia', value: 'Pacific/Apia' }, + { label: 'Pacific/Auckland', value: 'Pacific/Auckland' }, + { label: 'Pacific/Bougainville', value: 'Pacific/Bougainville' }, + { label: 'Pacific/Chatham', value: 'Pacific/Chatham' }, + { label: 'Pacific/Chuuk', value: 'Pacific/Chuuk' }, + { label: 'Pacific/Easter', value: 'Pacific/Easter' }, + { label: 'Pacific/Efate', value: 'Pacific/Efate' }, + { label: 'Pacific/Fakaofo', value: 'Pacific/Fakaofo' }, + { label: 'Pacific/Fiji', value: 'Pacific/Fiji' }, + { label: 'Pacific/Funafuti', value: 'Pacific/Funafuti' }, + { label: 'Pacific/Galapagos', value: 'Pacific/Galapagos' }, + { label: 'Pacific/Gambier', value: 'Pacific/Gambier' }, + { label: 'Pacific/Guadalcanal', value: 'Pacific/Guadalcanal' }, + { label: 'Pacific/Guam', value: 'Pacific/Guam' }, + { label: 'Pacific/Honolulu', value: 'Pacific/Honolulu' }, + { label: 'Pacific/Kanton', value: 'Pacific/Kanton' }, + { label: 'Pacific/Kiritimati', value: 'Pacific/Kiritimati' }, + { label: 'Pacific/Kosrae', value: 'Pacific/Kosrae' }, + { label: 'Pacific/Kwajalein', value: 'Pacific/Kwajalein' }, + { label: 'Pacific/Majuro', value: 'Pacific/Majuro' }, + { label: 'Pacific/Marquesas', value: 'Pacific/Marquesas' }, + { label: 'Pacific/Midway', value: 'Pacific/Midway' }, + { label: 'Pacific/Nauru', value: 'Pacific/Nauru' }, + { label: 'Pacific/Niue', value: 'Pacific/Niue' }, + { label: 'Pacific/Norfolk', value: 'Pacific/Norfolk' }, + { label: 'Pacific/Noumea', value: 'Pacific/Noumea' }, + { label: 'Pacific/Pago_Pago', value: 'Pacific/Pago_Pago' }, + { label: 'Pacific/Palau', value: 'Pacific/Palau' }, + { label: 'Pacific/Pitcairn', value: 'Pacific/Pitcairn' }, + { label: 'Pacific/Pohnpei', value: 'Pacific/Pohnpei' }, + { label: 'Pacific/Port_Moresby', value: 'Pacific/Port_Moresby' }, + { label: 'Pacific/Rarotonga', value: 'Pacific/Rarotonga' }, + { label: 'Pacific/Saipan', value: 'Pacific/Saipan' }, + { label: 'Pacific/Tahiti', value: 'Pacific/Tahiti' }, + { label: 'Pacific/Tarawa', value: 'Pacific/Tarawa' }, + { label: 'Pacific/Tongatapu', value: 'Pacific/Tongatapu' }, + { label: 'Pacific/Wake', value: 'Pacific/Wake' }, + { label: 'Pacific/Wallis', value: 'Pacific/Wallis' }, + ], + }, + }), + }, + async run(context) { + return await createCampaign(context.auth, { + list_ids: context.propsValue.lists?.join(','), + title: context.propsValue.title, + from_name: context.propsValue.fromName, + from_email: context.propsValue.fromEmail, + reply_to: context.propsValue.replyTo, + subject: context.propsValue.subject, + plain_text: context.propsValue.plainText, + html_text: context.propsValue.html, + exclude_list_ids: context.propsValue.excludeLists?.join(','), + segment_ids: context.propsValue.segments, + exclude_segment_ids: context.propsValue.excludeSegments, + query_string: context.propsValue.queryString, + track_clicks: context.propsValue.trackClicks, + track_opens: context.propsValue.trackOpens, + send_campaign: context.propsValue.autoSend ? '1' : '0', + schedule_date_time: context.propsValue.schedule, + schedule_timezone: context.propsValue.timezone, + }); + }, +}); diff --git a/packages/pieces/community/sendy/src/lib/actions/delete-subscriber.ts b/packages/pieces/community/sendy/src/lib/actions/delete-subscriber.ts new file mode 100644 index 0000000..9a9f9d5 --- /dev/null +++ b/packages/pieces/community/sendy/src/lib/actions/delete-subscriber.ts @@ -0,0 +1,41 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; +import { deleteSubscriber } from '../api'; +import { buildListDropdown } from '../props'; +import { sendyAuth, SendyAuthType } from '../auth'; + +export const deleteAction = createAction({ + name: 'delete_subscriber', + auth: sendyAuth, + displayName: 'Delete Subscriber', + description: 'Delete a subscriber from a list', + props: { + list: Property.Dropdown({ + displayName: 'List', + description: 'Select the list to delete from', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => + await buildListDropdown(auth as SendyAuthType), + }), + email: Property.ShortText({ + displayName: 'Email', + description: "The user's email", + required: true, + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + email: z.string().email(), + }); + + return await deleteSubscriber(context.auth, { + list_id: context.propsValue.list, + email: context.propsValue.email, + }); + }, +}); diff --git a/packages/pieces/community/sendy/src/lib/actions/get-brand-lists.ts b/packages/pieces/community/sendy/src/lib/actions/get-brand-lists.ts new file mode 100644 index 0000000..d4113d2 --- /dev/null +++ b/packages/pieces/community/sendy/src/lib/actions/get-brand-lists.ts @@ -0,0 +1,23 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getLists } from '../api'; +import { sendyAuth } from '../auth'; + +export const getListsAction = createAction({ + name: 'get_brand_lists', + auth: sendyAuth, + displayName: 'Get Lists for a Brand', + description: 'Get the Lists for a Brand', + props: { + includeHidden: Property.Checkbox({ + displayName: 'Include Hidden Lists', + description: 'Include hidden lists in the results', + required: false, + defaultValue: false, + }), + }, + async run(context) { + return await getLists(context.auth, { + include_hidden: context.propsValue.includeHidden ? 'yes' : 'no', + }); + }, +}); diff --git a/packages/pieces/community/sendy/src/lib/actions/get-brands.ts b/packages/pieces/community/sendy/src/lib/actions/get-brands.ts new file mode 100644 index 0000000..9c8c742 --- /dev/null +++ b/packages/pieces/community/sendy/src/lib/actions/get-brands.ts @@ -0,0 +1,14 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { getBrands } from '../api'; +import { sendyAuth } from '../auth'; + +export const getBrandsAction = createAction({ + name: 'get_brands', + auth: sendyAuth, + displayName: 'Get Brands', + description: 'Get a list of brands from Sendy', + props: {}, + async run(context) { + return await getBrands(context.auth); + }, +}); diff --git a/packages/pieces/community/sendy/src/lib/actions/get-subscription-status.ts b/packages/pieces/community/sendy/src/lib/actions/get-subscription-status.ts new file mode 100644 index 0000000..95c1d88 --- /dev/null +++ b/packages/pieces/community/sendy/src/lib/actions/get-subscription-status.ts @@ -0,0 +1,41 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { status } from '../api'; +import { buildListDropdown } from '../props'; +import { sendyAuth, SendyAuthType } from '../auth'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const statusAction = createAction({ + name: 'get_subscription_status', + auth: sendyAuth, + displayName: 'Get Subscription Status', + description: 'Get the subscription status of a user', + props: { + list: Property.Dropdown({ + displayName: 'List', + description: 'Select the list to get the status from', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => + await buildListDropdown(auth as SendyAuthType), + }), + email: Property.ShortText({ + displayName: 'Email', + description: "The user's email", + required: true, + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + email: z.string().email(), + }); + + return await status(context.auth, { + list_id: context.propsValue.list, + email: context.propsValue.email, + }); + }, +}); diff --git a/packages/pieces/community/sendy/src/lib/actions/subscribe-multiple.ts b/packages/pieces/community/sendy/src/lib/actions/subscribe-multiple.ts new file mode 100644 index 0000000..4db0fcf --- /dev/null +++ b/packages/pieces/community/sendy/src/lib/actions/subscribe-multiple.ts @@ -0,0 +1,88 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { subscribe } from '../api'; +import { buildListDropdown } from '../props'; +import { sendyAuth, SendyAuthType } from '../auth'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const subscribeMultipleAction = createAction({ + name: 'subscribe_multiple_lists', + auth: sendyAuth, + displayName: 'Subscribe Multiple Lists', + description: 'Add a new subscriber to a multiple lists', + props: { + lists: Property.MultiSelectDropdown({ + displayName: 'Lists', + description: 'Select the lists to subscribe to', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => + await buildListDropdown(auth as SendyAuthType), + }), + email: Property.ShortText({ + displayName: 'Email', + description: "The user's email", + required: true, + }), + name: Property.ShortText({ + displayName: 'Name', + description: "The user's name", + required: false, + }), + country: Property.ShortText({ + displayName: 'Country', + description: "The user's 2 letter country code", + required: false, + }), + ipaddress: Property.ShortText({ + displayName: 'IP Address', + description: "The user's IP address", + required: false, + }), + referrer: Property.ShortText({ + displayName: 'Referrer', + description: 'The URL where the user signed up from', + required: false, + }), + gdpr: Property.Checkbox({ + displayName: 'GDPR compliant', + description: + "If you're signing up EU users in a GDPR compliant manner, set to true", + required: false, + defaultValue: true, + }), + silent: Property.Checkbox({ + displayName: 'Silent', + description: + "set to true if your list is 'Double opt-in' but you want to bypass that and signup the user to the list as 'Single Opt-in instead' ", + required: false, + defaultValue: false, + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + email: z.string().email(), + referrer: z.string().url().optional(), + }); + + const returnValues: any[] = []; + + for (const list of context.propsValue.lists) { + const rc = await subscribe(context.auth, { + list: list, + email: context.propsValue.email, + name: context.propsValue.name, + country: context.propsValue.country, + ipaddress: context.propsValue.ipaddress, + referrer: context.propsValue.referrer, + gdpr: context.propsValue.gdpr, + silent: context.propsValue.silent, + }); + returnValues.push(rc); + } + return returnValues; + }, +}); diff --git a/packages/pieces/community/sendy/src/lib/actions/subscribe.ts b/packages/pieces/community/sendy/src/lib/actions/subscribe.ts new file mode 100644 index 0000000..9314147 --- /dev/null +++ b/packages/pieces/community/sendy/src/lib/actions/subscribe.ts @@ -0,0 +1,82 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { subscribe } from '../api'; +import { buildListDropdown } from '../props'; +import { sendyAuth, SendyAuthType } from '../auth'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const subscribeAction = createAction({ + name: 'subscribe', + auth: sendyAuth, + displayName: 'Subscribe Updated', + description: 'Add a new subscriber to a list', + props: { + list: Property.Dropdown({ + displayName: 'List', + description: 'Select the list to subscribe to', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => + await buildListDropdown(auth as SendyAuthType), + }), + email: Property.ShortText({ + displayName: 'Email', + description: "The user's email", + required: true, + }), + name: Property.ShortText({ + displayName: 'Name', + description: "The user's name", + required: false, + }), + country: Property.ShortText({ + displayName: 'Country', + description: "The user's 2 letter country code", + required: false, + }), + ipaddress: Property.ShortText({ + displayName: 'IP Address', + description: "The user's IP address", + required: false, + }), + referrer: Property.ShortText({ + displayName: 'Referrer', + description: 'The URL where the user signed up from', + required: false, + }), + gdpr: Property.Checkbox({ + displayName: 'GDPR compliant', + description: + "If you're signing up EU users in a GDPR compliant manner, set to true", + required: false, + defaultValue: true, + }), + silent: Property.Checkbox({ + displayName: 'Silent', + description: + "set to true if your list is 'Double opt-in' but you want to bypass that and signup the user to the list as 'Single Opt-in instead' ", + required: false, + defaultValue: false, + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + email: z.string().email(), + referrer: z.string().url(), + }); + + return await subscribe(context.auth, { + list: context.propsValue.list, + email: context.propsValue.email, + name: context.propsValue.name, + country: context.propsValue.country, + ipaddress: context.propsValue.ipaddress, + referrer: context.propsValue.referrer, + gdpr: context.propsValue.gdpr, + silent: context.propsValue.silent, + }); + }, +}); diff --git a/packages/pieces/community/sendy/src/lib/actions/unsubscribe-multiple.ts b/packages/pieces/community/sendy/src/lib/actions/unsubscribe-multiple.ts new file mode 100644 index 0000000..e1fe14c --- /dev/null +++ b/packages/pieces/community/sendy/src/lib/actions/unsubscribe-multiple.ts @@ -0,0 +1,47 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { unsubscribe } from '../api'; +import { buildListDropdown } from '../props'; +import { sendyAuth, SendyAuthType } from '../auth'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const unsubscribeMultipleAction = createAction({ + name: 'unsubscribe_multiple', + auth: sendyAuth, + displayName: 'Unsubscribe Multiple Lists', + description: 'Unsubscribe a subscriber from multiple lists', + props: { + lists: Property.MultiSelectDropdown({ + displayName: 'Lists', + description: 'Select the lists to subscribe to', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => + await buildListDropdown(auth as SendyAuthType), + }), + email: Property.ShortText({ + displayName: 'Email', + description: "The user's email", + required: true, + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + email: z.string().email(), + }); + + const returnValues: any[] = []; + + for (const list of context.propsValue.lists) { + const rc = await unsubscribe(context.auth, { + list: list, + email: context.propsValue.email, + }); + returnValues.push(rc); + } + return returnValues; + }, +}); diff --git a/packages/pieces/community/sendy/src/lib/actions/unsubscribe.ts b/packages/pieces/community/sendy/src/lib/actions/unsubscribe.ts new file mode 100644 index 0000000..fb5e4cb --- /dev/null +++ b/packages/pieces/community/sendy/src/lib/actions/unsubscribe.ts @@ -0,0 +1,41 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { unsubscribe } from '../api'; +import { buildListDropdown } from '../props'; +import { sendyAuth, SendyAuthType } from '../auth'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const unsubscribeAction = createAction({ + name: 'unsubscribe', + auth: sendyAuth, + displayName: 'Unsubscribe', + description: 'Unsubscribe a subscriber from a list', + props: { + list: Property.Dropdown({ + displayName: 'List', + description: 'Select the list to unsubscribe from', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => + await buildListDropdown(auth as SendyAuthType), + }), + email: Property.ShortText({ + displayName: 'Email', + description: "The user's email", + required: true, + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + email: z.string().email(), + }); + + return await unsubscribe(context.auth, { + list: context.propsValue.list, + email: context.propsValue.email, + }); + }, +}); diff --git a/packages/pieces/community/sendy/src/lib/api.ts b/packages/pieces/community/sendy/src/lib/api.ts new file mode 100644 index 0000000..9e7b10f --- /dev/null +++ b/packages/pieces/community/sendy/src/lib/api.ts @@ -0,0 +1,119 @@ +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { SendyAuthType } from './auth'; + +type KeyValuePair = { [key: string]: string | boolean | undefined }; + +const isSuccess = (text: string) => { + // The following terms are found in success messages from the Sendy API + const terms = [ + 'created', + 'scheduled', + 'subscribed', + 'unconfirmed', + 'bounced', + 'complained', + 'true', + '1', + ]; + const lowercase = text.toLowerCase(); + return terms.some((term) => lowercase.includes(term.toLowerCase())); +}; + +const sendyPostAPI = async ( + api: string, + auth: SendyAuthType, + body: KeyValuePair = {} +) => { + const { apiKey, domain, brandId } = auth; + + body['api_key'] = apiKey; + body['brand_id'] = brandId; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${domain}${api}`, + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: body, + }; + const response = await httpClient.sendRequest(request); + + let data = []; + let success = false; + let text = 'Success'; + + // If the response is a JSON object, then we know that the request was successful + if (typeof response.body === 'object') { + data = Object.keys(response.body).map((key) => response.body[key]); + success = true; + } else { + text = response.body as string; + if (isSuccess(text.toString())) success = true; + } + + return { + success: success, + text: text, + data: data, + }; +}; + +export async function getBrands(auth: SendyAuthType) { + const api = '/api/brands/get-brands.php'; + return sendyPostAPI(api, auth); +} + +export async function getLists(auth: SendyAuthType, data: KeyValuePair = {}) { + const api = '/api/lists/get-lists.php'; + return sendyPostAPI(api, auth, data); +} + +export async function subscribe(auth: SendyAuthType, data: KeyValuePair) { + const api = '/subscribe'; + data['boolean'] = 'true'; // plain text response + return sendyPostAPI(api, auth, data); +} + +export async function subscribeMultiple( + auth: SendyAuthType, + data: KeyValuePair +) { + const api = '/subscribe'; + data['boolean'] = 'true'; // plain text response + return sendyPostAPI(api, auth, data); +} + +export async function unsubscribe(auth: SendyAuthType, data: KeyValuePair) { + const api = '/unsubscribe'; + data['boolean'] = 'true'; // plain text response + return sendyPostAPI(api, auth, data); +} + +// delete is a reserved word +export async function deleteSubscriber( + auth: SendyAuthType, + data: KeyValuePair +) { + const api = '/api/subscribers/delete.php'; + return sendyPostAPI(api, auth, data); +} + +export async function status(auth: SendyAuthType, data: KeyValuePair) { + const api = '/api/subscribers/subscription-status.php'; + return sendyPostAPI(api, auth, data); +} + +export async function count(auth: SendyAuthType, data: KeyValuePair) { + const api = '/api/subscribers/active-subscriber-count.php'; + const response = await sendyPostAPI(api, auth, data); + if (typeof response.text === 'number') response.success = true; + return response; +} + +export async function createCampaign(auth: SendyAuthType, data: KeyValuePair) { + const api = '/api/campaigns/create.php'; + return sendyPostAPI(api, auth, data); +} diff --git a/packages/pieces/community/sendy/src/lib/auth.ts b/packages/pieces/community/sendy/src/lib/auth.ts new file mode 100644 index 0000000..31f5313 --- /dev/null +++ b/packages/pieces/community/sendy/src/lib/auth.ts @@ -0,0 +1,70 @@ +import { + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { getLists } from './api'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export type SendyAuthType = { apiKey: string; domain: string; brandId: string }; + +const markdownDescription = ` +Your sendy domain should be the base URL of your Sendy installation. Example: https://sendy.example.com + +Follow these instructions to get your Sendy API Key: + +1. Visit the Settings page of your Sendy domain: _https://sendy-domain.com_/settings +2. Once on the website, locate and click on the API Key and copy it. + +Get your Brand ID from the main Brands page. The ID is the first column in the table. +`; + +export const sendyAuth = PieceAuth.CustomAuth({ + description: markdownDescription, + props: { + domain: Property.ShortText({ + displayName: 'Sendy Domain', + description: 'The domain of your Sendy account', + required: true, + }), + apiKey: PieceAuth.SecretText({ + displayName: 'API Key', + description: 'The API key for your Sendy account', + required: true, + }), + brandId: Property.ShortText({ + displayName: 'Brand ID', + description: + 'The brand ID that will be associated to this connection. Brand IDs can be found on the main Brands page.', + required: true, + }), + }, + validate: async ({ auth }) => { + try { + await propsValidation.validateZod(auth, { + domain: z.string().url(), + apiKey: z.string().min(1).regex(/^\S+$/), + brandId: z.string().regex(/^[0-9]+$/), + }); + await validateAuth(auth); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: (e as Error)?.message, + }; + } + }, + required: true, +}); + +const validateAuth = async (auth: SendyAuthType) => { + const response = await getLists(auth); + if (response.success !== true) { + throw new Error( + `Authentication failed. Please check your domain and API key and try again. ${response.text}` + ); + } +}; diff --git a/packages/pieces/community/sendy/src/lib/props.ts b/packages/pieces/community/sendy/src/lib/props.ts new file mode 100644 index 0000000..516a30d --- /dev/null +++ b/packages/pieces/community/sendy/src/lib/props.ts @@ -0,0 +1,36 @@ +import { SendyAuthType } from './auth'; +import { getBrands, getLists } from './api'; + +export async function buildBrandDropdown(auth: SendyAuthType) { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please authenticate first', + }; + } + const response = await getBrands(auth as SendyAuthType); + const options = response.data.map((brand) => { + return { label: brand.name, value: brand.id }; + }); + return { + options: options, + }; +} + +export async function buildListDropdown(auth: SendyAuthType) { + if (!auth) { + return { + options: [], + disabled: true, + placeholder: 'Please authenticate first', + }; + } + const response = await getLists(auth as SendyAuthType); + const options = response.data.map((list) => { + return { label: list.name, value: list.id }; + }); + return { + options: options, + }; +} diff --git a/packages/pieces/community/sendy/tsconfig.json b/packages/pieces/community/sendy/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/sendy/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/sendy/tsconfig.lib.json b/packages/pieces/community/sendy/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/sendy/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/serp-api/.eslintrc.json b/packages/pieces/community/serp-api/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/serp-api/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/serp-api/README.md b/packages/pieces/community/serp-api/README.md new file mode 100644 index 0000000..cb635d0 --- /dev/null +++ b/packages/pieces/community/serp-api/README.md @@ -0,0 +1,7 @@ +# pieces-serp-api + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-serp-api` to build the library. diff --git a/packages/pieces/community/serp-api/package.json b/packages/pieces/community/serp-api/package.json new file mode 100644 index 0000000..ec8e02c --- /dev/null +++ b/packages/pieces/community/serp-api/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-serp-api", + "version": "0.0.1" +} diff --git a/packages/pieces/community/serp-api/project.json b/packages/pieces/community/serp-api/project.json new file mode 100644 index 0000000..81cc4db --- /dev/null +++ b/packages/pieces/community/serp-api/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-serp-api", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/serp-api/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/serp-api", + "tsConfig": "packages/pieces/community/serp-api/tsconfig.lib.json", + "packageJson": "packages/pieces/community/serp-api/package.json", + "main": "packages/pieces/community/serp-api/src/index.ts", + "assets": [ + "packages/pieces/community/serp-api/*.md", + { + "input": "packages/pieces/community/serp-api/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/serp-api/src/index.ts b/packages/pieces/community/serp-api/src/index.ts new file mode 100644 index 0000000..ff1ebfd --- /dev/null +++ b/packages/pieces/community/serp-api/src/index.ts @@ -0,0 +1,81 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { googleNewsSearch } from './lib/actions/google-news-search'; +import { googleSearch } from './lib/actions/google-search'; +import { googleTrendsSearch } from './lib/actions/google-trends-search'; +import { youtubeSearch } from './lib/actions/youtube-search'; +import { SerpApiClient } from './lib/services/serp-api-client'; +import { SerpApiValidator } from './lib/utils/validators'; + +export const serpApiAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: `You can obtain your API key from [Dashboard](https://serpapi.com/dashboard).`, + required: true, + validate: async ({ auth }) => { + try { + // Validate API key format first + const formatValidation = SerpApiValidator.validateApiKey(auth); + if (!formatValidation.isValid) { + return { + valid: false, + error: `Invalid API key format: ${formatValidation.errors.join(', ')}`, + }; + } + + // Test API key with actual request + const client = new SerpApiClient({ + defaultTimeout: 10000, + defaultRetries: 1, + }); + + const isValid = await client.validateApiKey(auth); + + if (!isValid) { + return { + valid: false, + error: 'Invalid API key. Please check your SerpApi API key and ensure it has sufficient credits.', + }; + } + + return { + valid: true, + }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + + if (errorMessage.includes('timeout')) { + return { + valid: false, + error: 'API validation timed out. Please check your network connection and try again.', + }; + } + + if (errorMessage.includes('network') || errorMessage.includes('ENOTFOUND')) { + return { + valid: false, + error: 'Network error occurred. Please check your internet connection.', + }; + } + + return { + valid: false, + error: `API key validation failed: ${errorMessage}`, + }; + } + }, +}); + +export const serpApi = createPiece({ + displayName: 'SerpApi', + description: 'Search Google, YouTube, News, and Trends with powerful filtering and analysis capabilities', + auth: serpApiAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/serp-api.png', + authors: ['AnkitSharmaOnGithub'], + actions: [ + googleSearch, + googleNewsSearch, + youtubeSearch, + googleTrendsSearch, + ], + triggers: [], +}); diff --git a/packages/pieces/community/serp-api/src/lib/actions/google-news-search.ts b/packages/pieces/community/serp-api/src/lib/actions/google-news-search.ts new file mode 100644 index 0000000..4637fae --- /dev/null +++ b/packages/pieces/community/serp-api/src/lib/actions/google-news-search.ts @@ -0,0 +1,132 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { serpApiAuth } from '../../index'; +import { COUNTRY_OPTIONS } from '../constants/countries'; +import { LANGUAGE_OPTIONS } from '../constants/languages'; +import { SerpApiClient } from '../services/serp-api-client'; +import { GoogleNewsSearchConfig, SerpApiEngine } from '../types'; + +export const googleNewsSearch = createAction({ + auth: serpApiAuth, + name: 'google_news_search', + displayName: 'Google News Search', + description: 'Track recent news articles for keywords or brands to monitor media mentions and trending topics.', + + props: { + query: Property.ShortText({ + displayName: 'Search Query', + description: 'News search query', + required: true, + }), + + hl: Property.StaticDropdown({ + displayName: 'Host Language (hl)', + description: 'Language for search results', + required: false, + defaultValue: 'en', + options: { + options: LANGUAGE_OPTIONS, + }, + }), + + gl: Property.StaticDropdown({ + displayName: 'Country (gl)', + description: 'Country for search results', + required: false, + options: { + options: COUNTRY_OPTIONS, + }, + }), + + no_cache: Property.StaticDropdown({ + displayName: 'No Cache', + description: 'Force fresh results', + required: false, + defaultValue: 'false', + options: { + options: [ + { label: 'false (Allow cache)', value: 'false' }, + { label: 'true (Force fresh results)', value: 'true' }, + ], + }, + }), + + async: Property.StaticDropdown({ + displayName: 'Async', + description: 'Submit search asynchronously', + required: false, + defaultValue: 'false', + options: { + options: [ + { label: 'false (Synchronous)', value: 'false' }, + { label: 'true (Asynchronous)', value: 'true' }, + ], + }, + }), + + output: Property.StaticDropdown({ + displayName: 'Output', + description: 'Output format for results', + required: false, + defaultValue: 'json', + options: { + options: [ + { label: 'JSON (Structured)', value: 'json' }, + { label: 'HTML (Raw)', value: 'html' }, + ], + }, + }), + }, + + async run({ auth, propsValue }) { + try { + const client = new SerpApiClient({ + defaultTimeout: 30000, + defaultRetries: 3, + defaultRetryDelay: 1000, + enableLogging: false, + }); + + const searchConfig: GoogleNewsSearchConfig = { + api_key: auth, + engine: SerpApiEngine.GOOGLE_NEWS, + q: propsValue.query, + hl: propsValue.hl, + gl: propsValue.gl, + }; + + // Add optional parameters + if (propsValue.gl) { + (searchConfig as any).gl = propsValue.gl; + } + + if (propsValue.no_cache) { + (searchConfig as any).no_cache = propsValue.no_cache; + } + + if (propsValue.async) { + (searchConfig as any).async = propsValue.async; + } + + if (propsValue.output) { + (searchConfig as any).output = propsValue.output; + } + + const response = await client.executeSearch(searchConfig); + + return { + success: true, + ...response, + }; + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + + return { + success: false, + error: errorMessage, + error_type: error instanceof Error ? error.constructor.name : 'UnknownError', + timestamp: new Date().toISOString(), + search_query: propsValue.query, + }; + } + }, +}); diff --git a/packages/pieces/community/serp-api/src/lib/actions/google-search.ts b/packages/pieces/community/serp-api/src/lib/actions/google-search.ts new file mode 100644 index 0000000..53f0942 --- /dev/null +++ b/packages/pieces/community/serp-api/src/lib/actions/google-search.ts @@ -0,0 +1,260 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { serpApiAuth } from '../../index'; +import { COUNTRY_OPTIONS } from '../constants/countries'; +import { GOOGLE_DOMAIN_OPTIONS } from '../constants/google-domains'; +import { LANGUAGE_OPTIONS } from '../constants/languages'; +import { SerpApiClient } from '../services/serp-api-client'; +import { GoogleSearchConfig, SerpApiEngine } from '../types'; + +export const googleSearch = createAction({ + auth: serpApiAuth, + name: 'google_search', + displayName: 'Google Search', + description: 'Retrieves organic search results for specific keywords with advanced filtering options for SEO monitoring and competitor analysis.', + + props: { + query: Property.ShortText({ + displayName: 'Search Query', + description: 'Search query to execute', + required: true, + }), + + location_search: Property.ShortText({ + displayName: 'Location Search', + description: 'Type location name to search', + required: false, + }), + + location: Property.Dropdown({ + displayName: 'Location', + description: 'Geographic location for results', + required: false, + refreshers: ['location_search'], + options: async ({ auth, location_search }) => { + if (!auth || !location_search) { + return { + disabled: true, + options: [], + placeholder: 'Type a location to search...', + }; + } + + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://serpapi.com/locations.json', + queryParams: { + q: location_search as string, + limit: '10', + }, + }); + + if (response.body && Array.isArray(response.body)) { + return { + disabled: false, + options: response.body.map((location: any) => ({ + label: `${location.name}${location.country ? `, ${location.country}` : ''}`, + value: location.canonical_name || location.name, + })), + }; + } + + return { + disabled: false, + options: [], + placeholder: 'No locations found', + }; + } catch (error) { + console.error('Error fetching locations:', error); + return { + disabled: false, + options: [], + placeholder: 'Error searching locations. Please check your API key.', + }; + } + }, + }), + + hl: Property.StaticDropdown({ + displayName: 'Host Language (hl)', + description: 'Language for search results', + required: false, + defaultValue: 'en', + options: { + options: LANGUAGE_OPTIONS, + }, + }), + + google_domain: Property.StaticDropdown({ + displayName: 'Google Domain', + description: 'Google domain to use', + required: false, + defaultValue: 'google.com', + options: { + options: GOOGLE_DOMAIN_OPTIONS, + }, + }), + + gl: Property.StaticDropdown({ + displayName: 'Country (gl)', + description: 'Country for search results', + required: false, + options: { + options: COUNTRY_OPTIONS, + }, + }), + + no_cache: Property.StaticDropdown({ + displayName: 'No Cache', + description: 'Force fresh results', + required: false, + defaultValue: 'false', + options: { + options: [ + { label: 'false (Allow cache)', value: 'false' }, + { label: 'true (Force fresh results)', value: 'true' }, + ], + }, + }), + + async: Property.StaticDropdown({ + displayName: 'Async', + description: 'Submit search asynchronously', + required: false, + defaultValue: 'false', + options: { + options: [ + { label: 'false (Synchronous)', value: 'false' }, + { label: 'true (Asynchronous)', value: 'true' }, + ], + }, + }), + + num_results: Property.Number({ + displayName: 'Number of Results', + description: 'Number of results to return (1-100)', + required: false, + defaultValue: 10, + }), + + start: Property.Number({ + displayName: 'Start Position', + description: 'Starting position for pagination (0-990)', + required: false, + defaultValue: 0, + }), + + safe: Property.StaticDropdown({ + displayName: 'Safe Search', + description: 'Safe search filtering level', + required: false, + defaultValue: 'active', + options: { + options: [ + { label: 'Active (Enabled)', value: 'active' }, + { label: 'Off (Disabled)', value: 'off' }, + ], + }, + }), + + device: Property.StaticDropdown({ + displayName: 'Device Type', + description: 'Device type for search simulation', + required: false, + defaultValue: 'desktop', + options: { + options: [ + { label: 'Desktop', value: 'desktop' }, + { label: 'Mobile', value: 'mobile' }, + { label: 'Tablet', value: 'tablet' }, + ], + }, + }), + + filter: Property.StaticDropdown({ + displayName: 'Result Filter', + description: 'Filter duplicate results', + required: false, + defaultValue: '0', + options: { + options: [ + { label: 'Show similar results', value: '0' }, + { label: 'Hide similar results', value: '1' }, + ], + }, + }), + }, + + async run({ auth, propsValue }) { + try { + // Create SerpApi client with optimal settings + const client = new SerpApiClient({ + defaultTimeout: 30000, + defaultRetries: 3, + defaultRetryDelay: 1000, + enableLogging: false, + }); + + // Build search configuration + const searchConfig: GoogleSearchConfig = { + api_key: auth, + engine: SerpApiEngine.GOOGLE, + q: propsValue.query, + hl: propsValue.hl, + num: propsValue.num_results, + start: propsValue.start, + safe: propsValue.safe as 'active' | 'off', + device: propsValue.device as 'desktop' | 'mobile' | 'tablet', + filter: propsValue.filter as '0' | '1', + }; + + // Add optional location if provided + if (propsValue.location) { + (searchConfig as any).location = propsValue.location; + } + + // Add optional google_domain if provided + if (propsValue.google_domain) { + (searchConfig as any).google_domain = propsValue.google_domain; + } + + // Add optional gl (country) if provided + if (propsValue.gl) { + (searchConfig as any).gl = propsValue.gl; + } + + // Add optional no_cache and async if provided + if (propsValue.no_cache) { + (searchConfig as any).no_cache = propsValue.no_cache; + } + + if (propsValue.async) { + (searchConfig as any).async = propsValue.async; + } + + // Execute search with comprehensive error handling + const response = await client.executeSearch(searchConfig, { + timeout: 30000, + retries: 3, + retryDelay: 1000, + }); + + // Return structured response with metadata + return { + success: true, + ...response, + }; + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + + return { + success: false, + error: errorMessage, + error_type: error instanceof Error ? error.constructor.name : 'UnknownError', + timestamp: new Date().toISOString(), + search_query: propsValue.query, + }; + } + }, +}); diff --git a/packages/pieces/community/serp-api/src/lib/actions/google-trends-search.ts b/packages/pieces/community/serp-api/src/lib/actions/google-trends-search.ts new file mode 100644 index 0000000..f6f15f8 --- /dev/null +++ b/packages/pieces/community/serp-api/src/lib/actions/google-trends-search.ts @@ -0,0 +1,587 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { serpApiAuth } from '../../index'; +import { SerpApiClient } from '../services/serp-api-client'; +import { GoogleTrendsSearchConfig, SerpApiEngine } from '../types'; + +export const googleTrendsSearch = createAction({ + auth: serpApiAuth, + name: 'google_trends_search', + displayName: 'Google Trends Search', + description: 'Discover trending keywords over time to inform content strategy and market research with geographic insights.', + + props: { + query: Property.ShortText({ + displayName: 'Search Query', + description: 'The keyword or topic to analyze trends for (e.g., "artificial intelligence", "cryptocurrency", "climate change")', + required: true, + }), + + hl: Property.StaticDropdown({ + displayName: 'Host Language (hl)', + description: 'Language for search results', + required: false, + defaultValue: 'en', + options: { + options: [ + { label: 'English', value: 'en' }, + { label: 'Afrikaans', value: 'af' }, + { label: 'Akan', value: 'ak' }, + { label: 'Albanian', value: 'sq' }, + { label: 'Amharic', value: 'am' }, + { label: 'Arabic', value: 'ar' }, + { label: 'Armenian', value: 'hy' }, + { label: 'Azerbaijani', value: 'az' }, + { label: 'Basque', value: 'eu' }, + { label: 'Belarusian', value: 'be' }, + { label: 'Bemba', value: 'bem' }, + { label: 'Bengali', value: 'bn' }, + { label: 'Bihari', value: 'bh' }, + { label: 'Bosnian', value: 'bs' }, + { label: 'Breton', value: 'br' }, + { label: 'Bulgarian', value: 'bg' }, + { label: 'Bhutanese', value: 'bt' }, + { label: 'Cambodian', value: 'km' }, + { label: 'Catalan', value: 'ca' }, + { label: 'Cherokee', value: 'chr' }, + { label: 'Chichewa', value: 'ny' }, + { label: 'Chinese (Simplified)', value: 'zh-cn' }, + { label: 'Chinese (Traditional)', value: 'zh-tw' }, + { label: 'Corsican', value: 'co' }, + { label: 'Croatian', value: 'hr' }, + { label: 'Czech', value: 'cs' }, + { label: 'Danish', value: 'da' }, + { label: 'Dutch', value: 'nl' }, + { label: 'Esperanto', value: 'eo' }, + { label: 'Estonian', value: 'et' }, + { label: 'Ewe', value: 'ee' }, + { label: 'Faroese', value: 'fo' }, + { label: 'Filipino', value: 'tl' }, + { label: 'Finnish', value: 'fi' }, + { label: 'French', value: 'fr' }, + { label: 'Frisian', value: 'fy' }, + { label: 'Ga', value: 'gaa' }, + { label: 'Galician', value: 'gl' }, + { label: 'Georgian', value: 'ka' }, + { label: 'German', value: 'de' }, + { label: 'Greek', value: 'el' }, + { label: 'Greenlandic', value: 'kl' }, + { label: 'Guarani', value: 'gn' }, + { label: 'Gujarati', value: 'gu' }, + { label: 'Haitian Creole', value: 'ht' }, + { label: 'Hausa', value: 'ha' }, + { label: 'Hawaiian', value: 'haw' }, + { label: 'Hebrew', value: 'he' }, + { label: 'Hindi', value: 'hi' }, + { label: 'Hungarian', value: 'hu' }, + { label: 'Icelandic', value: 'is' }, + { label: 'Igbo', value: 'ig' }, + { label: 'Indonesian', value: 'id' }, + { label: 'Interlingua', value: 'ia' }, + { label: 'Irish', value: 'ga' }, + { label: 'Italian', value: 'it' }, + { label: 'Japanese', value: 'ja' }, + { label: 'Javanese', value: 'jw' }, + { label: 'Kannada', value: 'kn' }, + { label: 'Kazakh', value: 'kk' }, + { label: 'Kinyarwanda', value: 'rw' }, + { label: 'Kirundi', value: 'rn' }, + { label: 'Kongo', value: 'kg' }, + { label: 'Korean', value: 'ko' }, + { label: 'Krio (Sierra Leone)', value: 'kri' }, + { label: 'Kurdish', value: 'ku' }, + { label: 'Kurdish (Soranî)', value: 'ckb' }, + { label: 'Kyrgyz', value: 'ky' }, + { label: 'Laothian', value: 'lo' }, + { label: 'Latin', value: 'la' }, + { label: 'Latvian', value: 'lv' }, + { label: 'Lingala', value: 'ln' }, + { label: 'Lithuanian', value: 'lt' }, + { label: 'Lozi', value: 'loz' }, + { label: 'Luganda', value: 'lg' }, + { label: 'Luo', value: 'ach' }, + { label: 'Macedonian', value: 'mk' }, + { label: 'Malagasy', value: 'mg' }, + { label: 'Malay', value: 'ms' }, + { label: 'Malayalam', value: 'ml' }, + { label: 'Maltese', value: 'mt' }, + { label: 'Maldives', value: 'mv' }, + { label: 'Maori', value: 'mi' }, + { label: 'Marathi', value: 'mr' }, + { label: 'Mauritian Creole', value: 'mfe' }, + { label: 'Moldavian', value: 'mo' }, + { label: 'Mongolian', value: 'mn' }, + { label: 'Montenegrin', value: 'sr-me' }, + { label: 'Myanmar', value: 'my' }, + { label: 'Nepali', value: 'ne' }, + { label: 'Nigerian Pidgin', value: 'pcm' }, + { label: 'Northern Sotho', value: 'nso' }, + { label: 'Norwegian', value: 'no' }, + { label: 'Norwegian (Nynorsk)', value: 'nn' }, + { label: 'Occitan', value: 'oc' }, + { label: 'Oriya', value: 'or' }, + { label: 'Oromo', value: 'om' }, + { label: 'Pashto', value: 'ps' }, + { label: 'Persian', value: 'fa' }, + { label: 'Polish', value: 'pl' }, + { label: 'Portuguese', value: 'pt' }, + { label: 'Portuguese (Brazil)', value: 'pt-br' }, + { label: 'Portuguese (Portugal)', value: 'pt-pt' }, + { label: 'Punjabi', value: 'pa' }, + { label: 'Quechua', value: 'qu' }, + { label: 'Romanian', value: 'ro' }, + { label: 'Romansh', value: 'rm' }, + { label: 'Runyakitara', value: 'nyn' }, + { label: 'Russian', value: 'ru' }, + { label: 'Samoa', value: 'ws' }, + { label: 'Scots Gaelic', value: 'gd' }, + { label: 'Serbian', value: 'sr' }, + { label: 'Serbo-Croatian', value: 'sh' }, + { label: 'Sesotho', value: 'st' }, + { label: 'Setswana', value: 'tn' }, + { label: 'Seychellois Creole', value: 'crs' }, + { label: 'Shona', value: 'sn' }, + { label: 'Sindhi', value: 'sd' }, + { label: 'Sinhalese', value: 'si' }, + { label: 'Slovak', value: 'sk' }, + { label: 'Slovenian', value: 'sl' }, + { label: 'Somali', value: 'so' }, + { label: 'Spanish', value: 'es' }, + { label: 'Spanish (Latin American)', value: 'es-419' }, + { label: 'Sundanese', value: 'su' }, + { label: 'Swahili', value: 'sw' }, + { label: 'Swedish', value: 'sv' }, + { label: 'Tajik', value: 'tg' }, + { label: 'Tamil', value: 'ta' }, + { label: 'Tatar', value: 'tt' }, + { label: 'Telugu', value: 'te' }, + { label: 'Thai', value: 'th' }, + { label: 'Tigrinya', value: 'ti' }, + { label: 'Tonga', value: 'to' }, + { label: 'Tshiluba', value: 'lua' }, + { label: 'Tumbuka', value: 'tum' }, + { label: 'Turkish', value: 'tr' }, + { label: 'Turkmen', value: 'tk' }, + { label: 'Twi', value: 'tw' }, + { label: 'Uighur', value: 'ug' }, + { label: 'Ukrainian', value: 'uk' }, + { label: 'Urdu', value: 'ur' }, + { label: 'Uzbek', value: 'uz' }, + { label: 'Vanuatu', value: 'vu' }, + { label: 'Vietnamese', value: 'vi' }, + { label: 'Welsh', value: 'cy' }, + { label: 'Wolof', value: 'wo' }, + { label: 'Xhosa', value: 'xh' }, + { label: 'Yiddish', value: 'yi' }, + { label: 'Yoruba', value: 'yo' }, + { label: 'Zulu', value: 'zu' }, + ], + }, + }), + + geo: Property.StaticDropdown({ + displayName: 'Geographic Location', + description: 'Location from where you want the search to originate.', + required: false, + defaultValue: '', + options: { + options: [ + { label: 'Worldwide', value: '' }, + { label: 'Afghanistan', value: 'AF' }, + { label: 'Albania', value: 'AL' }, + { label: 'Algeria', value: 'DZ' }, + { label: 'American Samoa', value: 'AS' }, + { label: 'Andorra', value: 'AD' }, + { label: 'Angola', value: 'AO' }, + { label: 'Anguilla', value: 'AI' }, + { label: 'Antarctica', value: 'AQ' }, + { label: 'Antigua & Barbuda', value: 'AG' }, + { label: 'Argentina', value: 'AR' }, + { label: 'Armenia', value: 'AM' }, + { label: 'Aruba', value: 'AW' }, + { label: 'Australia', value: 'AU' }, + { label: 'Austria', value: 'AT' }, + { label: 'Azerbaijan', value: 'AZ' }, + { label: 'Bahamas', value: 'BS' }, + { label: 'Bahrain', value: 'BH' }, + { label: 'Bangladesh', value: 'BD' }, + { label: 'Barbados', value: 'BB' }, + { label: 'Belarus', value: 'BY' }, + { label: 'Belgium', value: 'BE' }, + { label: 'Belize', value: 'BZ' }, + { label: 'Benin', value: 'BJ' }, + { label: 'Bermuda', value: 'BM' }, + { label: 'Bhutan', value: 'BT' }, + { label: 'Bolivia', value: 'BO' }, + { label: 'Bosnia & Herzegovina', value: 'BA' }, + { label: 'Botswana', value: 'BW' }, + { label: 'Brazil', value: 'BR' }, + { label: 'British Virgin Islands', value: 'VG' }, + { label: 'Brunei', value: 'BN' }, + { label: 'Bulgaria', value: 'BG' }, + { label: 'Burkina Faso', value: 'BF' }, + { label: 'Burundi', value: 'BI' }, + { label: 'Cambodia', value: 'KH' }, + { label: 'Cameroon', value: 'CM' }, + { label: 'Canada', value: 'CA' }, + { label: 'Cape Verde', value: 'CV' }, + { label: 'Cayman Islands', value: 'KY' }, + { label: 'Central African Republic', value: 'CF' }, + { label: 'Chad', value: 'TD' }, + { label: 'Chile', value: 'CL' }, + { label: 'China', value: 'CN' }, + { label: 'Colombia', value: 'CO' }, + { label: 'Comoros', value: 'KM' }, + { label: 'Congo - Brazzaville', value: 'CG' }, + { label: 'Congo - Kinshasa', value: 'CD' }, + { label: 'Cook Islands', value: 'CK' }, + { label: 'Costa Rica', value: 'CR' }, + { label: 'Croatia', value: 'HR' }, + { label: 'Cuba', value: 'CU' }, + { label: 'Cyprus', value: 'CY' }, + { label: 'Czechia', value: 'CZ' }, + { label: "Côte d'Ivoire", value: 'CI' }, + { label: 'Denmark', value: 'DK' }, + { label: 'Djibouti', value: 'DJ' }, + { label: 'Dominica', value: 'DM' }, + { label: 'Dominican Republic', value: 'DO' }, + { label: 'Ecuador', value: 'EC' }, + { label: 'Egypt', value: 'EG' }, + { label: 'El Salvador', value: 'SV' }, + { label: 'Equatorial Guinea', value: 'GQ' }, + { label: 'Eritrea', value: 'ER' }, + { label: 'Estonia', value: 'EE' }, + { label: 'Eswatini', value: 'SZ' }, + { label: 'Ethiopia', value: 'ET' }, + { label: 'Falkland Islands', value: 'FK' }, + { label: 'Faroe Islands', value: 'FO' }, + { label: 'Fiji', value: 'FJ' }, + { label: 'Finland', value: 'FI' }, + { label: 'France', value: 'FR' }, + { label: 'French Guiana', value: 'GF' }, + { label: 'French Polynesia', value: 'PF' }, + { label: 'Gabon', value: 'GA' }, + { label: 'Gambia', value: 'GM' }, + { label: 'Georgia', value: 'GE' }, + { label: 'Germany', value: 'DE' }, + { label: 'Ghana', value: 'GH' }, + { label: 'Gibraltar', value: 'GI' }, + { label: 'Greece', value: 'GR' }, + { label: 'Greenland', value: 'GL' }, + { label: 'Grenada', value: 'GD' }, + { label: 'Guadeloupe', value: 'GP' }, + { label: 'Guam', value: 'GU' }, + { label: 'Guatemala', value: 'GT' }, + { label: 'Guernsey', value: 'GG' }, + { label: 'Guinea', value: 'GN' }, + { label: 'Guinea-Bissau', value: 'GW' }, + { label: 'Guyana', value: 'GY' }, + { label: 'Haiti', value: 'HT' }, + { label: 'Honduras', value: 'HN' }, + { label: 'Hong Kong SAR China', value: 'HK' }, + { label: 'Hungary', value: 'HU' }, + { label: 'Iceland', value: 'IS' }, + { label: 'India', value: 'IN' }, + { label: 'Indonesia', value: 'ID' }, + { label: 'Iran', value: 'IR' }, + { label: 'Iraq', value: 'IQ' }, + { label: 'Ireland', value: 'IE' }, + { label: 'Isle of Man', value: 'IM' }, + { label: 'Israel', value: 'IL' }, + { label: 'Italy', value: 'IT' }, + { label: 'Jamaica', value: 'JM' }, + { label: 'Japan', value: 'JP' }, + { label: 'Jersey', value: 'JE' }, + { label: 'Jordan', value: 'JO' }, + { label: 'Kazakhstan', value: 'KZ' }, + { label: 'Kenya', value: 'KE' }, + { label: 'Kiribati', value: 'KI' }, + { label: 'Kuwait', value: 'KW' }, + { label: 'Kyrgyzstan', value: 'KG' }, + { label: 'Laos', value: 'LA' }, + { label: 'Latvia', value: 'LV' }, + { label: 'Lebanon', value: 'LB' }, + { label: 'Lesotho', value: 'LS' }, + { label: 'Liberia', value: 'LR' }, + { label: 'Libya', value: 'LY' }, + { label: 'Liechtenstein', value: 'LI' }, + { label: 'Lithuania', value: 'LT' }, + { label: 'Luxembourg', value: 'LU' }, + { label: 'Macao SAR China', value: 'MO' }, + { label: 'Madagascar', value: 'MG' }, + { label: 'Malawi', value: 'MW' }, + { label: 'Malaysia', value: 'MY' }, + { label: 'Maldives', value: 'MV' }, + { label: 'Mali', value: 'ML' }, + { label: 'Malta', value: 'MT' }, + { label: 'Marshall Islands', value: 'MH' }, + { label: 'Martinique', value: 'MQ' }, + { label: 'Mauritania', value: 'MR' }, + { label: 'Mauritius', value: 'MU' }, + { label: 'Mayotte', value: 'YT' }, + { label: 'Mexico', value: 'MX' }, + { label: 'Micronesia', value: 'FM' }, + { label: 'Moldova', value: 'MD' }, + { label: 'Monaco', value: 'MC' }, + { label: 'Mongolia', value: 'MN' }, + { label: 'Montenegro', value: 'ME' }, + { label: 'Montserrat', value: 'MS' }, + { label: 'Morocco', value: 'MA' }, + { label: 'Mozambique', value: 'MZ' }, + { label: 'Myanmar (Burma)', value: 'MM' }, + { label: 'Namibia', value: 'NA' }, + { label: 'Nauru', value: 'NR' }, + { label: 'Nepal', value: 'NP' }, + { label: 'Netherlands', value: 'NL' }, + { label: 'New Caledonia', value: 'NC' }, + { label: 'New Zealand', value: 'NZ' }, + { label: 'Nicaragua', value: 'NI' }, + { label: 'Niger', value: 'NE' }, + { label: 'Nigeria', value: 'NG' }, + { label: 'Niue', value: 'NU' }, + { label: 'Norfolk Island', value: 'NF' }, + { label: 'North Korea', value: 'KP' }, + { label: 'North Macedonia', value: 'MK' }, + { label: 'Northern Mariana Islands', value: 'MP' }, + { label: 'Norway', value: 'NO' }, + { label: 'Oman', value: 'OM' }, + { label: 'Pakistan', value: 'PK' }, + { label: 'Palau', value: 'PW' }, + { label: 'Palestinian Territories', value: 'PS' }, + { label: 'Panama', value: 'PA' }, + { label: 'Papua New Guinea', value: 'PG' }, + { label: 'Paraguay', value: 'PY' }, + { label: 'Peru', value: 'PE' }, + { label: 'Philippines', value: 'PH' }, + { label: 'Pitcairn Islands', value: 'PN' }, + { label: 'Poland', value: 'PL' }, + { label: 'Portugal', value: 'PT' }, + { label: 'Puerto Rico', value: 'PR' }, + { label: 'Qatar', value: 'QA' }, + { label: 'Romania', value: 'RO' }, + { label: 'Russia', value: 'RU' }, + { label: 'Rwanda', value: 'RW' }, + { label: 'Réunion', value: 'RE' }, + { label: 'Samoa', value: 'WS' }, + { label: 'San Marino', value: 'SM' }, + { label: 'Saudi Arabia', value: 'SA' }, + { label: 'Senegal', value: 'SN' }, + { label: 'Serbia', value: 'RS' }, + { label: 'Seychelles', value: 'SC' }, + { label: 'Sierra Leone', value: 'SL' }, + { label: 'Singapore', value: 'SG' }, + { label: 'Sint Maarten', value: 'SX' }, + { label: 'Slovakia', value: 'SK' }, + { label: 'Slovenia', value: 'SI' }, + { label: 'Solomon Islands', value: 'SB' }, + { label: 'Somalia', value: 'SO' }, + { label: 'South Africa', value: 'ZA' }, + { label: 'South Korea', value: 'KR' }, + { label: 'South Sudan', value: 'SS' }, + { label: 'Spain', value: 'ES' }, + { label: 'Sri Lanka', value: 'LK' }, + { label: 'St. Barthélemy', value: 'BL' }, + { label: 'St. Helena', value: 'SH' }, + { label: 'St. Kitts & Nevis', value: 'KN' }, + { label: 'St. Lucia', value: 'LC' }, + { label: 'St. Martin', value: 'MF' }, + { label: 'St. Pierre & Miquelon', value: 'PM' }, + { label: 'St. Vincent & Grenadines', value: 'VC' }, + { label: 'Sudan', value: 'SD' }, + { label: 'Suriname', value: 'SR' }, + { label: 'Svalbard & Jan Mayen', value: 'SJ' }, + { label: 'Sweden', value: 'SE' }, + { label: 'Switzerland', value: 'CH' }, + { label: 'Syria', value: 'SY' }, + { label: 'São Tomé & Príncipe', value: 'ST' }, + { label: 'Taiwan', value: 'TW' }, + { label: 'Tajikistan', value: 'TJ' }, + { label: 'Tanzania', value: 'TZ' }, + { label: 'Thailand', value: 'TH' }, + { label: 'Timor-Leste', value: 'TL' }, + { label: 'Togo', value: 'TG' }, + { label: 'Tokelau', value: 'TK' }, + { label: 'Tonga', value: 'TO' }, + { label: 'Trinidad & Tobago', value: 'TT' }, + { label: 'Tunisia', value: 'TN' }, + { label: 'Turkey', value: 'TR' }, + { label: 'Turkmenistan', value: 'TM' }, + { label: 'Turks & Caicos Islands', value: 'TC' }, + { label: 'Tuvalu', value: 'TV' }, + { label: 'U.S. Outlying Islands', value: 'UM' }, + { label: 'U.S. Virgin Islands', value: 'VI' }, + { label: 'Uganda', value: 'UG' }, + { label: 'Ukraine', value: 'UA' }, + { label: 'United Arab Emirates', value: 'AE' }, + { label: 'United Kingdom', value: 'GB' }, + { label: 'United States', value: 'US' }, + { label: 'Uruguay', value: 'UY' }, + { label: 'Uzbekistan', value: 'UZ' }, + { label: 'Vanuatu', value: 'VU' }, + { label: 'Vatican City', value: 'VA' }, + { label: 'Venezuela', value: 'VE' }, + { label: 'Vietnam', value: 'VN' }, + { label: 'Wallis & Futuna', value: 'WF' }, + { label: 'Western Sahara', value: 'EH' }, + { label: 'Yemen', value: 'YE' }, + { label: 'Zambia', value: 'ZM' }, + { label: 'Zimbabwe', value: 'ZW' }, + ], + }, + }), + + data_type: Property.StaticDropdown({ + displayName: 'Data Type', + description: 'Type of trends data to retrieve', + required: false, + defaultValue: 'TIMESERIES', + options: { + options: [ + { label: 'Interest Over Time', value: 'TIMESERIES' }, + { label: 'Compared breakdown by region', value: 'GEO_MAP' }, + { label: 'Interest by Region', value: 'GEO_MAP_0' }, + { label: 'Related Topics', value: 'RELATED_TOPICS' }, + { label: 'Related Queries', value: 'RELATED_QUERIES' }, + ], + }, + }), + + time_period: Property.StaticDropdown({ + displayName: 'Time Period', + description: 'Time range for the trends data', + required: false, + defaultValue: 'today 12-m', + options: { + options: [ + { label: 'Past Hour', value: 'now 1-H' }, + { label: 'Past 4 Hours', value: 'now 4-H' }, + { label: 'Past Day', value: 'now 1-d' }, + { label: 'Past 7 Days', value: 'now 7-d' }, + { label: 'Past 30 Days', value: 'today 1-m' }, + { label: 'Past 90 Days', value: 'today 3-m' }, + { label: 'Past 12 Months', value: 'today 12-m' }, + { label: 'Past 5 Years', value: 'today 5-y' }, + { label: 'Since 2004', value: 'all' }, + ], + }, + }), + + category: Property.Number({ + displayName: 'Category', + description: 'Google Trends category ID (0 for all categories)', + required: false, + defaultValue: 0, + }), + + property_filter: Property.StaticDropdown({ + displayName: 'Property Filter (grop)', + description: 'Filter by Google property', + required: false, + defaultValue: '', + options: { + options: [ + { label: 'Web Search', value: '' }, + { label: 'Image Search', value: 'images' }, + { label: 'News Search', value: 'news' }, + { label: 'Google Shopping', value: 'froogle' }, + { label: 'YouTube Search', value: 'youtube' }, + ], + }, + }), + + no_cache: Property.StaticDropdown({ + displayName: 'No Cache', + description: 'Force SerpApi to fetch the Google results even if a cached version is already present.', + required: false, + defaultValue: 'false', + options: { + options: [ + { label: 'false (Allow cache)', value: 'false' }, + { label: 'true (Force fresh results)', value: 'true' }, + ], + }, + }), + + async: Property.StaticDropdown({ + displayName: 'Async', + description: 'Defines the way you want to submit your search to SerpApi. It can be set to false (default) to open an HTTP connection and keep it open until you got your search results, or true to just submit your search to SerpApi and retrieve them later.', + required: false, + defaultValue: 'false', + options: { + options: [ + { label: 'false (Synchronous)', value: 'false' }, + { label: 'true (Asynchronous)', value: 'true' }, + ], + }, + }), + + output: Property.StaticDropdown({ + displayName: 'Output', + description: 'Defines the final output format you want.', + required: false, + defaultValue: 'json', + options: { + options: [ + { label: 'JSON (Structured)', value: 'json' }, + { label: 'HTML (Raw)', value: 'html' }, + ], + }, + }), + }, + + async run({ auth, propsValue }) { + try { + const client = new SerpApiClient({ + defaultTimeout: 30000, + defaultRetries: 3, + defaultRetryDelay: 1000, + enableLogging: false, + }); + + const searchConfig: GoogleTrendsSearchConfig = { + api_key: auth, + engine: SerpApiEngine.GOOGLE_TRENDS, + q: propsValue.query, + hl: propsValue.hl, + data_type: propsValue.data_type as 'TIMESERIES' | 'GEO_MAP' | 'RELATED_TOPICS' | 'RELATED_QUERIES', + geo: propsValue.geo, + date: propsValue.time_period, + cat: propsValue.category, + }; + + // Add property filter if specified + if (propsValue.property_filter) { + searchConfig.gprop = propsValue.property_filter; + } + + // Add optional parameters + if (propsValue.no_cache) { + (searchConfig as any).no_cache = propsValue.no_cache; + } + + if (propsValue.async) { + (searchConfig as any).async = propsValue.async; + } + + if (propsValue.output) { + (searchConfig as any).output = propsValue.output; + } + + const response = await client.executeSearch(searchConfig); + + return { + success: true, + ...response, + }; + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + + return { + success: false, + error: errorMessage, + error_type: error instanceof Error ? error.constructor.name : 'UnknownError', + timestamp: new Date().toISOString(), + search_query: propsValue.query, + }; + } + }, +}); diff --git a/packages/pieces/community/serp-api/src/lib/actions/youtube-search.ts b/packages/pieces/community/serp-api/src/lib/actions/youtube-search.ts new file mode 100644 index 0000000..a083922 --- /dev/null +++ b/packages/pieces/community/serp-api/src/lib/actions/youtube-search.ts @@ -0,0 +1,136 @@ +/** + * @fileoverview YouTube Search action for SerpApi + * Provides comprehensive YouTube search functionality with quality and duration filters + */ + +import { createAction, Property } from '@activepieces/pieces-framework'; +import { serpApiAuth } from '../../index'; +import { COUNTRY_OPTIONS } from '../constants/countries'; +import { LANGUAGE_OPTIONS } from '../constants/languages'; +import { SerpApiClient } from '../services/serp-api-client'; +import { SerpApiEngine, YouTubeSearchConfig } from '../types'; + +export const youtubeSearch = createAction({ + auth: serpApiAuth, + name: 'youtube_search', + displayName: 'YouTube Search', + description: 'Retrieve top video content results from YouTube for specific keywords or topics with advanced filtering.', + props: { + query: Property.ShortText({ + displayName: 'Search Query', + description: 'YouTube search query', + required: true, + }), + + hl: Property.StaticDropdown({ + displayName: 'Host Language (hl)', + description: 'Language for search results', + required: false, + defaultValue: 'en', + options: { + options: LANGUAGE_OPTIONS, + }, + }), + + gl: Property.StaticDropdown({ + displayName: 'Country (gl)', + description: 'Country for search results', + required: false, + options: { + options: COUNTRY_OPTIONS, + }, + }), + + no_cache: Property.StaticDropdown({ + displayName: 'No Cache', + description: 'Force fresh results', + required: false, + defaultValue: 'false', + options: { + options: [ + { label: 'false (Allow cache)', value: 'false' }, + { label: 'true (Force fresh results)', value: 'true' }, + ], + }, + }), + + async: Property.StaticDropdown({ + displayName: 'Async', + description: 'Submit search asynchronously', + required: false, + defaultValue: 'false', + options: { + options: [ + { label: 'false (Synchronous)', value: 'false' }, + { label: 'true (Asynchronous)', value: 'true' }, + ], + }, + }), + + output: Property.StaticDropdown({ + displayName: 'Output', + description: 'Output format for results', + required: false, + defaultValue: 'json', + options: { + options: [ + { label: 'JSON (Structured)', value: 'json' }, + { label: 'HTML (Raw)', value: 'html' }, + ], + }, + }), + }, + + async run({ auth, propsValue }) { + try { + const client = new SerpApiClient({ + defaultTimeout: 30000, + defaultRetries: 3, + defaultRetryDelay: 1000, + enableLogging: false, + }); + + const searchConfig: YouTubeSearchConfig = { + api_key: auth, + engine: SerpApiEngine.YOUTUBE, + search_query: propsValue.query, + hl: propsValue.hl, + gl: propsValue.gl, + }; + + // Add optional parameters + if (propsValue.gl) { + (searchConfig as any).gl = propsValue.gl; + } + + if (propsValue.no_cache) { + (searchConfig as any).no_cache = propsValue.no_cache; + } + + if (propsValue.async) { + (searchConfig as any).async = propsValue.async; + } + + if (propsValue.output) { + (searchConfig as any).output = propsValue.output; + } + + const response = await client.executeSearch(searchConfig); + + return { + success: true, + ...response + }; + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + + return { + success: false, + error: errorMessage, + error_type: error instanceof Error ? error.constructor.name : 'UnknownError', + timestamp: new Date().toISOString(), + search_query: propsValue.query, + }; + } + }, +}); diff --git a/packages/pieces/community/serp-api/src/lib/constants/countries.ts b/packages/pieces/community/serp-api/src/lib/constants/countries.ts new file mode 100644 index 0000000..f9c6039 --- /dev/null +++ b/packages/pieces/community/serp-api/src/lib/constants/countries.ts @@ -0,0 +1,246 @@ +export const COUNTRY_OPTIONS = [ + { label: 'us (United States)', value: 'us' }, + { label: 'af (Afghanistan)', value: 'af' }, + { label: 'al (Albania)', value: 'al' }, + { label: 'dz (Algeria)', value: 'dz' }, + { label: 'as (American Samoa)', value: 'as' }, + { label: 'ad (Andorra)', value: 'ad' }, + { label: 'ao (Angola)', value: 'ao' }, + { label: 'ai (Anguilla)', value: 'ai' }, + { label: 'aq (Antarctica)', value: 'aq' }, + { label: 'ag (Antigua and Barbuda)', value: 'ag' }, + { label: 'ar (Argentina)', value: 'ar' }, + { label: 'am (Armenia)', value: 'am' }, + { label: 'aw (Aruba)', value: 'aw' }, + { label: 'au (Australia)', value: 'au' }, + { label: 'at (Austria)', value: 'at' }, + { label: 'az (Azerbaijan)', value: 'az' }, + { label: 'bs (Bahamas)', value: 'bs' }, + { label: 'bh (Bahrain)', value: 'bh' }, + { label: 'bd (Bangladesh)', value: 'bd' }, + { label: 'bb (Barbados)', value: 'bb' }, + { label: 'by (Belarus)', value: 'by' }, + { label: 'be (Belgium)', value: 'be' }, + { label: 'bz (Belize)', value: 'bz' }, + { label: 'bj (Benin)', value: 'bj' }, + { label: 'bm (Bermuda)', value: 'bm' }, + { label: 'bt (Bhutan)', value: 'bt' }, + { label: 'bo (Bolivia)', value: 'bo' }, + { label: 'ba (Bosnia and Herzegovina)', value: 'ba' }, + { label: 'bw (Botswana)', value: 'bw' }, + { label: 'bv (Bouvet Island)', value: 'bv' }, + { label: 'br (Brazil)', value: 'br' }, + { label: 'io (British Indian Ocean Territory)', value: 'io' }, + { label: 'bn (Brunei Darussalam)', value: 'bn' }, + { label: 'bg (Bulgaria)', value: 'bg' }, + { label: 'bf (Burkina Faso)', value: 'bf' }, + { label: 'bi (Burundi)', value: 'bi' }, + { label: 'kh (Cambodia)', value: 'kh' }, + { label: 'cm (Cameroon)', value: 'cm' }, + { label: 'ca (Canada)', value: 'ca' }, + { label: 'cv (Cape Verde)', value: 'cv' }, + { label: 'ky (Cayman Islands)', value: 'ky' }, + { label: 'cf (Central African Republic)', value: 'cf' }, + { label: 'td (Chad)', value: 'td' }, + { label: 'cl (Chile)', value: 'cl' }, + { label: 'cn (China)', value: 'cn' }, + { label: 'cx (Christmas Island)', value: 'cx' }, + { label: 'cc (Cocos (Keeling) Islands)', value: 'cc' }, + { label: 'co (Colombia)', value: 'co' }, + { label: 'km (Comoros)', value: 'km' }, + { label: 'cg (Congo)', value: 'cg' }, + { label: 'cd (Congo, the Democratic Republic of the)', value: 'cd' }, + { label: 'ck (Cook Islands)', value: 'ck' }, + { label: 'cr (Costa Rica)', value: 'cr' }, + { label: 'ci (Cote D\'ivoire)', value: 'ci' }, + { label: 'hr (Croatia)', value: 'hr' }, + { label: 'cu (Cuba)', value: 'cu' }, + { label: 'cy (Cyprus)', value: 'cy' }, + { label: 'cz (Czech Republic)', value: 'cz' }, + { label: 'dk (Denmark)', value: 'dk' }, + { label: 'dj (Djibouti)', value: 'dj' }, + { label: 'dm (Dominica)', value: 'dm' }, + { label: 'do (Dominican Republic)', value: 'do' }, + { label: 'ec (Ecuador)', value: 'ec' }, + { label: 'eg (Egypt)', value: 'eg' }, + { label: 'sv (El Salvador)', value: 'sv' }, + { label: 'gq (Equatorial Guinea)', value: 'gq' }, + { label: 'er (Eritrea)', value: 'er' }, + { label: 'ee (Estonia)', value: 'ee' }, + { label: 'et (Ethiopia)', value: 'et' }, + { label: 'fk (Falkland Islands (Malvinas))', value: 'fk' }, + { label: 'fo (Faroe Islands)', value: 'fo' }, + { label: 'fj (Fiji)', value: 'fj' }, + { label: 'fi (Finland)', value: 'fi' }, + { label: 'fr (France)', value: 'fr' }, + { label: 'gf (French Guiana)', value: 'gf' }, + { label: 'pf (French Polynesia)', value: 'pf' }, + { label: 'tf (French Southern Territories)', value: 'tf' }, + { label: 'ga (Gabon)', value: 'ga' }, + { label: 'gm (Gambia)', value: 'gm' }, + { label: 'ge (Georgia)', value: 'ge' }, + { label: 'de (Germany)', value: 'de' }, + { label: 'gh (Ghana)', value: 'gh' }, + { label: 'gi (Gibraltar)', value: 'gi' }, + { label: 'gr (Greece)', value: 'gr' }, + { label: 'gl (Greenland)', value: 'gl' }, + { label: 'gd (Grenada)', value: 'gd' }, + { label: 'gp (Guadeloupe)', value: 'gp' }, + { label: 'gu (Guam)', value: 'gu' }, + { label: 'gt (Guatemala)', value: 'gt' }, + { label: 'gg (Guernsey)', value: 'gg' }, + { label: 'gn (Guinea)', value: 'gn' }, + { label: 'gw (Guinea-Bissau)', value: 'gw' }, + { label: 'gy (Guyana)', value: 'gy' }, + { label: 'ht (Haiti)', value: 'ht' }, + { label: 'hm (Heard Island and Mcdonald Islands)', value: 'hm' }, + { label: 'va (Holy See (Vatican City State))', value: 'va' }, + { label: 'hn (Honduras)', value: 'hn' }, + { label: 'hk (Hong Kong)', value: 'hk' }, + { label: 'hu (Hungary)', value: 'hu' }, + { label: 'is (Iceland)', value: 'is' }, + { label: 'in (India)', value: 'in' }, + { label: 'id (Indonesia)', value: 'id' }, + { label: 'ir (Iran, Islamic Republic of)', value: 'ir' }, + { label: 'iq (Iraq)', value: 'iq' }, + { label: 'ie (Ireland)', value: 'ie' }, + { label: 'im (Isle of Man)', value: 'im' }, + { label: 'il (Israel)', value: 'il' }, + { label: 'it (Italy)', value: 'it' }, + { label: 'je (Jersey)', value: 'je' }, + { label: 'jm (Jamaica)', value: 'jm' }, + { label: 'jp (Japan)', value: 'jp' }, + { label: 'jo (Jordan)', value: 'jo' }, + { label: 'kz (Kazakhstan)', value: 'kz' }, + { label: 'ke (Kenya)', value: 'ke' }, + { label: 'ki (Kiribati)', value: 'ki' }, + { label: 'kp (Korea, Democratic People\'s Republic of)', value: 'kp' }, + { label: 'kr (Korea, Republic of)', value: 'kr' }, + { label: 'kw (Kuwait)', value: 'kw' }, + { label: 'kg (Kyrgyzstan)', value: 'kg' }, + { label: 'la (Lao People\'s Democratic Republic)', value: 'la' }, + { label: 'lv (Latvia)', value: 'lv' }, + { label: 'lb (Lebanon)', value: 'lb' }, + { label: 'ls (Lesotho)', value: 'ls' }, + { label: 'lr (Liberia)', value: 'lr' }, + { label: 'ly (Libyan Arab Jamahiriya)', value: 'ly' }, + { label: 'li (Liechtenstein)', value: 'li' }, + { label: 'lt (Lithuania)', value: 'lt' }, + { label: 'lu (Luxembourg)', value: 'lu' }, + { label: 'mo (Macao)', value: 'mo' }, + { label: 'mk (Macedonia, the Former Yugosalv Republic of)', value: 'mk' }, + { label: 'mg (Madagascar)', value: 'mg' }, + { label: 'mw (Malawi)', value: 'mw' }, + { label: 'my (Malaysia)', value: 'my' }, + { label: 'mv (Maldives)', value: 'mv' }, + { label: 'ml (Mali)', value: 'ml' }, + { label: 'mt (Malta)', value: 'mt' }, + { label: 'mh (Marshall Islands)', value: 'mh' }, + { label: 'mq (Martinique)', value: 'mq' }, + { label: 'mr (Mauritania)', value: 'mr' }, + { label: 'mu (Mauritius)', value: 'mu' }, + { label: 'yt (Mayotte)', value: 'yt' }, + { label: 'mx (Mexico)', value: 'mx' }, + { label: 'fm (Micronesia, Federated States of)', value: 'fm' }, + { label: 'md (Moldova, Republic of)', value: 'md' }, + { label: 'mc (Monaco)', value: 'mc' }, + { label: 'mn (Mongolia)', value: 'mn' }, + { label: 'me (Montenegro)', value: 'me' }, + { label: 'ms (Montserrat)', value: 'ms' }, + { label: 'ma (Morocco)', value: 'ma' }, + { label: 'mz (Mozambique)', value: 'mz' }, + { label: 'mm (Myanmar)', value: 'mm' }, + { label: 'na (Namibia)', value: 'na' }, + { label: 'nr (Nauru)', value: 'nr' }, + { label: 'np (Nepal)', value: 'np' }, + { label: 'nl (Netherlands)', value: 'nl' }, + { label: 'an (Netherlands Antilles)', value: 'an' }, + { label: 'nc (New Caledonia)', value: 'nc' }, + { label: 'nz (New Zealand)', value: 'nz' }, + { label: 'ni (Nicaragua)', value: 'ni' }, + { label: 'ne (Niger)', value: 'ne' }, + { label: 'ng (Nigeria)', value: 'ng' }, + { label: 'nu (Niue)', value: 'nu' }, + { label: 'nf (Norfolk Island)', value: 'nf' }, + { label: 'mp (Northern Mariana Islands)', value: 'mp' }, + { label: 'no (Norway)', value: 'no' }, + { label: 'om (Oman)', value: 'om' }, + { label: 'pk (Pakistan)', value: 'pk' }, + { label: 'pw (Palau)', value: 'pw' }, + { label: 'ps (Palestinian Territory, Occupied)', value: 'ps' }, + { label: 'pa (Panama)', value: 'pa' }, + { label: 'pg (Papua New Guinea)', value: 'pg' }, + { label: 'py (Paraguay)', value: 'py' }, + { label: 'pe (Peru)', value: 'pe' }, + { label: 'ph (Philippines)', value: 'ph' }, + { label: 'pn (Pitcairn)', value: 'pn' }, + { label: 'pl (Poland)', value: 'pl' }, + { label: 'pt (Portugal)', value: 'pt' }, + { label: 'pr (Puerto Rico)', value: 'pr' }, + { label: 'qa (Qatar)', value: 'qa' }, + { label: 're (Reunion)', value: 're' }, + { label: 'ro (Romania)', value: 'ro' }, + { label: 'ru (Russian Federation)', value: 'ru' }, + { label: 'rw (Rwanda)', value: 'rw' }, + { label: 'sh (Saint Helena)', value: 'sh' }, + { label: 'kn (Saint Kitts and Nevis)', value: 'kn' }, + { label: 'lc (Saint Lucia)', value: 'lc' }, + { label: 'pm (Saint Pierre and Miquelon)', value: 'pm' }, + { label: 'vc (Saint Vincent and the Grenadines)', value: 'vc' }, + { label: 'ws (Samoa)', value: 'ws' }, + { label: 'sm (San Marino)', value: 'sm' }, + { label: 'st (Sao Tome and Principe)', value: 'st' }, + { label: 'sa (Saudi Arabia)', value: 'sa' }, + { label: 'sn (Senegal)', value: 'sn' }, + { label: 'rs (Serbia)', value: 'rs' }, + { label: 'sc (Seychelles)', value: 'sc' }, + { label: 'sl (Sierra Leone)', value: 'sl' }, + { label: 'sg (Singapore)', value: 'sg' }, + { label: 'sk (Slovakia)', value: 'sk' }, + { label: 'si (Slovenia)', value: 'si' }, + { label: 'sb (Solomon Islands)', value: 'sb' }, + { label: 'so (Somalia)', value: 'so' }, + { label: 'za (South Africa)', value: 'za' }, + { label: 'gs (South Georgia and the South Sandwich Islands)', value: 'gs' }, + { label: 'es (Spain)', value: 'es' }, + { label: 'lk (Sri Lanka)', value: 'lk' }, + { label: 'sd (Sudan)', value: 'sd' }, + { label: 'sr (Suriname)', value: 'sr' }, + { label: 'sj (Svalbard and Jan Mayen)', value: 'sj' }, + { label: 'sz (Swaziland)', value: 'sz' }, + { label: 'se (Sweden)', value: 'se' }, + { label: 'ch (Switzerland)', value: 'ch' }, + { label: 'sy (Syrian Arab Republic)', value: 'sy' }, + { label: 'tw (Taiwan, Province of China)', value: 'tw' }, + { label: 'tj (Tajikistan)', value: 'tj' }, + { label: 'tz (Tanzania, United Republic of)', value: 'tz' }, + { label: 'th (Thailand)', value: 'th' }, + { label: 'tl (Timor-Leste)', value: 'tl' }, + { label: 'tg (Togo)', value: 'tg' }, + { label: 'tk (Tokelau)', value: 'tk' }, + { label: 'to (Tonga)', value: 'to' }, + { label: 'tt (Trinidad and Tobago)', value: 'tt' }, + { label: 'tn (Tunisia)', value: 'tn' }, + { label: 'tr (Turkiye)', value: 'tr' }, + { label: 'tm (Turkmenistan)', value: 'tm' }, + { label: 'tc (Turks and Caicos Islands)', value: 'tc' }, + { label: 'tv (Tuvalu)', value: 'tv' }, + { label: 'ug (Uganda)', value: 'ug' }, + { label: 'ua (Ukraine)', value: 'ua' }, + { label: 'ae (United Arab Emirates)', value: 'ae' }, + { label: 'uk (United Kingdom)', value: 'uk' }, + { label: 'gb (United Kingdom)', value: 'gb' }, + { label: 'um (United States Minor Outlying Islands)', value: 'um' }, + { label: 'uy (Uruguay)', value: 'uy' }, + { label: 'uz (Uzbekistan)', value: 'uz' }, + { label: 'vu (Vanuatu)', value: 'vu' }, + { label: 've (Venezuela)', value: 've' }, + { label: 'vn (Viet Nam)', value: 'vn' }, + { label: 'vg (Virgin Islands, British)', value: 'vg' }, + { label: 'vi (Virgin Islands, U.S.)', value: 'vi' }, + { label: 'wf (Wallis and Futuna)', value: 'wf' }, + { label: 'eh (Western Sahara)', value: 'eh' }, + { label: 'ye (Yemen)', value: 'ye' }, + { label: 'zm (Zambia)', value: 'zm' }, + { label: 'zw (Zimbabwe)', value: 'zw' }, +]; \ No newline at end of file diff --git a/packages/pieces/community/serp-api/src/lib/constants/google-domains.ts b/packages/pieces/community/serp-api/src/lib/constants/google-domains.ts new file mode 100644 index 0000000..f29ee2f --- /dev/null +++ b/packages/pieces/community/serp-api/src/lib/constants/google-domains.ts @@ -0,0 +1,187 @@ +export const GOOGLE_DOMAIN_OPTIONS = [ + { label: 'google.com (United States)', value: 'google.com' }, + { label: 'google.ad (Andorra)', value: 'google.ad' }, + { label: 'google.ae (United Arab Emirates)', value: 'google.ae' }, + { label: 'google.al (Albania)', value: 'google.al' }, + { label: 'google.am (Armenia)', value: 'google.am' }, + { label: 'google.as (American Samoa)', value: 'google.as' }, + { label: 'google.at (Austria)', value: 'google.at' }, + { label: 'google.az (Azerbaijan)', value: 'google.az' }, + { label: 'google.ba (Bosnia and Herzegovina)', value: 'google.ba' }, + { label: 'google.be (Belgium)', value: 'google.be' }, + { label: 'google.bf (Burkina Faso)', value: 'google.bf' }, + { label: 'google.bg (Bulgaria)', value: 'google.bg' }, + { label: 'google.bi (Burundi)', value: 'google.bi' }, + { label: 'google.bj (Benin)', value: 'google.bj' }, + { label: 'google.bs (Bahamas)', value: 'google.bs' }, + { label: 'google.bt (Bhutan)', value: 'google.bt' }, + { label: 'google.by (Belarus)', value: 'google.by' }, + { label: 'google.ca (Canada)', value: 'google.ca' }, + { label: 'google.cd (Democratic Republic of the Congo)', value: 'google.cd' }, + { label: 'google.cf (Central African Republic)', value: 'google.cf' }, + { label: 'google.cg (Republic of the Congo)', value: 'google.cg' }, + { label: 'google.ch (Switzerland)', value: 'google.ch' }, + { label: 'google.ci (Ivory Coast)', value: 'google.ci' }, + { label: 'google.cl (Chile)', value: 'google.cl' }, + { label: 'google.cm (Cameroon)', value: 'google.cm' }, + { label: 'google.co.ao (Angola)', value: 'google.co.ao' }, + { label: 'google.co.bw (Botswana)', value: 'google.co.bw' }, + { label: 'google.co.ck (Cook Islands)', value: 'google.co.ck' }, + { label: 'google.co.cr (Costa Rica)', value: 'google.co.cr' }, + { label: 'google.co.id (Indonesia)', value: 'google.co.id' }, + { label: 'google.co.il (Israel)', value: 'google.co.il' }, + { label: 'google.co.in (India)', value: 'google.co.in' }, + { label: 'google.co.jp (Japan)', value: 'google.co.jp' }, + { label: 'google.co.ke (Kenya)', value: 'google.co.ke' }, + { label: 'google.co.kr (South Korea)', value: 'google.co.kr' }, + { label: 'google.co.ls (Lesotho)', value: 'google.co.ls' }, + { label: 'google.co.ma (Morocco)', value: 'google.co.ma' }, + { label: 'google.co.mz (Mozambique)', value: 'google.co.mz' }, + { label: 'google.co.nz (New Zealand)', value: 'google.co.nz' }, + { label: 'google.co.th (Thailand)', value: 'google.co.th' }, + { label: 'google.co.tz (Tanzania)', value: 'google.co.tz' }, + { label: 'google.co.ug (Uganda)', value: 'google.co.ug' }, + { label: 'google.co.uk (United Kingdom)', value: 'google.co.uk' }, + { label: 'google.co.uz (Uzbekistan)', value: 'google.co.uz' }, + { label: 'google.co.ve (Venezuela)', value: 'google.co.ve' }, + { label: 'google.co.vi (United States Virgin Islands)', value: 'google.co.vi' }, + { label: 'google.co.za (South Africa)', value: 'google.co.za' }, + { label: 'google.co.zm (Zambia)', value: 'google.co.zm' }, + { label: 'google.co.zw (Zimbabwe)', value: 'google.co.zw' }, + { label: 'google.com.af (Afghanistan)', value: 'google.com.af' }, + { label: 'google.com.ag (Antigua and Barbuda)', value: 'google.com.ag' }, + { label: 'google.com.ai (Anguilla)', value: 'google.com.ai' }, + { label: 'google.com.ar (Argentina)', value: 'google.com.ar' }, + { label: 'google.com.au (Australia)', value: 'google.com.au' }, + { label: 'google.com.bd (Bangladesh)', value: 'google.com.bd' }, + { label: 'google.com.bh (Bahrain)', value: 'google.com.bh' }, + { label: 'google.com.bn (Brunei)', value: 'google.com.bn' }, + { label: 'google.com.bo (Bolivia)', value: 'google.com.bo' }, + { label: 'google.com.br (Brazil)', value: 'google.com.br' }, + { label: 'google.com.bz (Belize)', value: 'google.com.bz' }, + { label: 'google.com.co (Colombia)', value: 'google.com.co' }, + { label: 'google.com.cu (Cuba)', value: 'google.com.cu' }, + { label: 'google.com.cy (Cyprus)', value: 'google.com.cy' }, + { label: 'google.com.do (Dominican Republic)', value: 'google.com.do' }, + { label: 'google.com.ec (Ecuador)', value: 'google.com.ec' }, + { label: 'google.com.eg (Egypt)', value: 'google.com.eg' }, + { label: 'google.com.et (Ethiopia)', value: 'google.com.et' }, + { label: 'google.com.fj (Fiji)', value: 'google.com.fj' }, + { label: 'google.com.gh (Ghana)', value: 'google.com.gh' }, + { label: 'google.com.gi (Gibraltar)', value: 'google.com.gi' }, + { label: 'google.com.gt (Guatemala)', value: 'google.com.gt' }, + { label: 'google.com.hk (Hong Kong)', value: 'google.com.hk' }, + { label: 'google.com.jm (Jamaica)', value: 'google.com.jm' }, + { label: 'google.com.kh (Cambodia)', value: 'google.com.kh' }, + { label: 'google.com.kw (Kuwait)', value: 'google.com.kw' }, + { label: 'google.com.lb (Lebanon)', value: 'google.com.lb' }, + { label: 'google.com.ly (Libya)', value: 'google.com.ly' }, + { label: 'google.com.mm (Myanmar)', value: 'google.com.mm' }, + { label: 'google.com.mt (Malta)', value: 'google.com.mt' }, + { label: 'google.com.mx (Mexico)', value: 'google.com.mx' }, + { label: 'google.com.my (Malaysia)', value: 'google.com.my' }, + { label: 'google.com.na (Namibia)', value: 'google.com.na' }, + { label: 'google.com.ng (Nigeria)', value: 'google.com.ng' }, + { label: 'google.com.ni (Nicaragua)', value: 'google.com.ni' }, + { label: 'google.com.np (Nepal)', value: 'google.com.np' }, + { label: 'google.com.om (Oman)', value: 'google.com.om' }, + { label: 'google.com.pa (Panama)', value: 'google.com.pa' }, + { label: 'google.com.pe (Peru)', value: 'google.com.pe' }, + { label: 'google.com.pg (Papua New Guinea)', value: 'google.com.pg' }, + { label: 'google.com.ph (Philippines)', value: 'google.com.ph' }, + { label: 'google.com.pk (Pakistan)', value: 'google.com.pk' }, + { label: 'google.com.pr (Puerto Rico)', value: 'google.com.pr' }, + { label: 'google.com.py (Paraguay)', value: 'google.com.py' }, + { label: 'google.com.qa (Qatar)', value: 'google.com.qa' }, + { label: 'google.com.sa (Saudi Arabia)', value: 'google.com.sa' }, + { label: 'google.com.sb (Solomon Islands)', value: 'google.com.sb' }, + { label: 'google.com.sg (Singapore)', value: 'google.com.sg' }, + { label: 'google.com.sl (Sierra Leone)', value: 'google.com.sl' }, + { label: 'google.com.sv (El Salvador)', value: 'google.com.sv' }, + { label: 'google.com.tj (Tajikistan)', value: 'google.com.tj' }, + { label: 'google.com.tr (Turkey)', value: 'google.com.tr' }, + { label: 'google.com.tw (Taiwan)', value: 'google.com.tw' }, + { label: 'google.com.ua (Ukraine)', value: 'google.com.ua' }, + { label: 'google.com.uy (Uruguay)', value: 'google.com.uy' }, + { label: 'google.com.vc (Saint Vincent and the Grenadines)', value: 'google.com.vc' }, + { label: 'google.com.vn (Vietnam)', value: 'google.com.vn' }, + { label: 'google.cv (Cape Verde)', value: 'google.cv' }, + { label: 'google.cz (Czech Republic)', value: 'google.cz' }, + { label: 'google.de (Germany)', value: 'google.de' }, + { label: 'google.dj (Djibouti)', value: 'google.dj' }, + { label: 'google.dk (Denmark)', value: 'google.dk' }, + { label: 'google.dm (Dominica)', value: 'google.dm' }, + { label: 'google.dz (Algeria)', value: 'google.dz' }, + { label: 'google.ee (Estonia)', value: 'google.ee' }, + { label: 'google.es (Spain)', value: 'google.es' }, + { label: 'google.fi (Finland)', value: 'google.fi' }, + { label: 'google.fm (Micronesia, Federated States of)', value: 'google.fm' }, + { label: 'google.fr (France)', value: 'google.fr' }, + { label: 'google.ga (Gabon)', value: 'google.ga' }, + { label: 'google.ge (Georgia)', value: 'google.ge' }, + { label: 'google.gl (Greenland)', value: 'google.gl' }, + { label: 'google.gm (Gambia)', value: 'google.gm' }, + { label: 'google.gp (Guadeloupe)', value: 'google.gp' }, + { label: 'google.gr (Greece)', value: 'google.gr' }, + { label: 'google.gy (Guyana)', value: 'google.gy' }, + { label: 'google.hn (Honduras)', value: 'google.hn' }, + { label: 'google.hr (Croatia)', value: 'google.hr' }, + { label: 'google.ht (Haiti)', value: 'google.ht' }, + { label: 'google.hu (Hungary)', value: 'google.hu' }, + { label: 'google.ie (Ireland)', value: 'google.ie' }, + { label: 'google.iq (Iraq)', value: 'google.iq' }, + { label: 'google.is (Iceland)', value: 'google.is' }, + { label: 'google.it (Italy)', value: 'google.it' }, + { label: 'google.je (Jersey)', value: 'google.je' }, + { label: 'google.jo (Jordan)', value: 'google.jo' }, + { label: 'google.kg (Kyrgyzstan)', value: 'google.kg' }, + { label: 'google.ki (Kiribati)', value: 'google.ki' }, + { label: 'google.kz (Kazakhstan)', value: 'google.kz' }, + { label: 'google.la (Laos)', value: 'google.la' }, + { label: 'google.li (Liechtenstein)', value: 'google.li' }, + { label: 'google.lk (Sri Lanka)', value: 'google.lk' }, + { label: 'google.lt (Lithuania)', value: 'google.lt' }, + { label: 'google.lu (Luxembourg)', value: 'google.lu' }, + { label: 'google.lv (Latvia)', value: 'google.lv' }, + { label: 'google.md (Moldova)', value: 'google.md' }, + { label: 'google.mg (Madagascar)', value: 'google.mg' }, + { label: 'google.mk (Macedonia)', value: 'google.mk' }, + { label: 'google.ml (Mali)', value: 'google.ml' }, + { label: 'google.mn (Mongolia)', value: 'google.mn' }, + { label: 'google.ms (Montserrat)', value: 'google.ms' }, + { label: 'google.mu (Mauritius)', value: 'google.mu' }, + { label: 'google.mv (Maldives)', value: 'google.mv' }, + { label: 'google.mw (Malawi)', value: 'google.mw' }, + { label: 'google.ne (Niger)', value: 'google.ne' }, + { label: 'google.nl (Netherlands)', value: 'google.nl' }, + { label: 'google.no (Norway)', value: 'google.no' }, + { label: 'google.nr (Nauru)', value: 'google.nr' }, + { label: 'google.nu (Niue)', value: 'google.nu' }, + { label: 'google.pl (Poland)', value: 'google.pl' }, + { label: 'google.ps (Palestine)', value: 'google.ps' }, + { label: 'google.pt (Portugal)', value: 'google.pt' }, + { label: 'google.ro (Romania)', value: 'google.ro' }, + { label: 'google.rs (Serbia)', value: 'google.rs' }, + { label: 'google.ru (Russia)', value: 'google.ru' }, + { label: 'google.rw (Rwanda)', value: 'google.rw' }, + { label: 'google.sc (Seychelles)', value: 'google.sc' }, + { label: 'google.se (Sweden)', value: 'google.se' }, + { label: 'google.sh (Saint Helena, Ascension and Tristan da Cunha)', value: 'google.sh' }, + { label: 'google.si (Slovenia)', value: 'google.si' }, + { label: 'google.sk (Slovakia)', value: 'google.sk' }, + { label: 'google.sm (San Marino)', value: 'google.sm' }, + { label: 'google.sn (Senegal)', value: 'google.sn' }, + { label: 'google.so (Somalia)', value: 'google.so' }, + { label: 'google.sr (Suriname)', value: 'google.sr' }, + { label: 'google.td (Chad)', value: 'google.td' }, + { label: 'google.tg (Togo)', value: 'google.tg' }, + { label: 'google.tk (Tokelau)', value: 'google.tk' }, + { label: 'google.tl (Timor-Leste)', value: 'google.tl' }, + { label: 'google.tm (Turkmenistan)', value: 'google.tm' }, + { label: 'google.tn (Tunisia)', value: 'google.tn' }, + { label: 'google.to (Tonga)', value: 'google.to' }, + { label: 'google.tt (Trinidad and Tobago)', value: 'google.tt' }, + { label: 'google.vg (British Virgin Islands)', value: 'google.vg' }, + { label: 'google.vu (Vanuatu)', value: 'google.vu' }, + { label: 'google.ws (Samoa)', value: 'google.ws' }, +]; \ No newline at end of file diff --git a/packages/pieces/community/serp-api/src/lib/constants/languages.ts b/packages/pieces/community/serp-api/src/lib/constants/languages.ts new file mode 100644 index 0000000..75137ef --- /dev/null +++ b/packages/pieces/community/serp-api/src/lib/constants/languages.ts @@ -0,0 +1,153 @@ +export const LANGUAGE_OPTIONS = [ + { label: 'English', value: 'en' }, + { label: 'Afrikaans', value: 'af' }, + { label: 'Akan', value: 'ak' }, + { label: 'Albanian', value: 'sq' }, + { label: 'Amharic', value: 'am' }, + { label: 'Arabic', value: 'ar' }, + { label: 'Armenian', value: 'hy' }, + { label: 'Azerbaijani', value: 'az' }, + { label: 'Basque', value: 'eu' }, + { label: 'Belarusian', value: 'be' }, + { label: 'Bemba', value: 'bem' }, + { label: 'Bengali', value: 'bn' }, + { label: 'Bihari', value: 'bh' }, + { label: 'Bosnian', value: 'bs' }, + { label: 'Breton', value: 'br' }, + { label: 'Bulgarian', value: 'bg' }, + { label: 'Bhutanese', value: 'bt' }, + { label: 'Cambodian', value: 'km' }, + { label: 'Catalan', value: 'ca' }, + { label: 'Cherokee', value: 'chr' }, + { label: 'Chichewa', value: 'ny' }, + { label: 'Chinese (Simplified)', value: 'zh-cn' }, + { label: 'Chinese (Traditional)', value: 'zh-tw' }, + { label: 'Corsican', value: 'co' }, + { label: 'Croatian', value: 'hr' }, + { label: 'Czech', value: 'cs' }, + { label: 'Danish', value: 'da' }, + { label: 'Dutch', value: 'nl' }, + { label: 'Esperanto', value: 'eo' }, + { label: 'Estonian', value: 'et' }, + { label: 'Ewe', value: 'ee' }, + { label: 'Faroese', value: 'fo' }, + { label: 'Filipino', value: 'tl' }, + { label: 'Finnish', value: 'fi' }, + { label: 'French', value: 'fr' }, + { label: 'Frisian', value: 'fy' }, + { label: 'Ga', value: 'gaa' }, + { label: 'Galician', value: 'gl' }, + { label: 'Georgian', value: 'ka' }, + { label: 'German', value: 'de' }, + { label: 'Greek', value: 'el' }, + { label: 'Greenlandic', value: 'kl' }, + { label: 'Guarani', value: 'gn' }, + { label: 'Gujarati', value: 'gu' }, + { label: 'Haitian Creole', value: 'ht' }, + { label: 'Hausa', value: 'ha' }, + { label: 'Hawaiian', value: 'haw' }, + { label: 'Hebrew', value: 'he' }, + { label: 'Hindi', value: 'hi' }, + { label: 'Hungarian', value: 'hu' }, + { label: 'Icelandic', value: 'is' }, + { label: 'Igbo', value: 'ig' }, + { label: 'Indonesian', value: 'id' }, + { label: 'Interlingua', value: 'ia' }, + { label: 'Irish', value: 'ga' }, + { label: 'Italian', value: 'it' }, + { label: 'Japanese', value: 'ja' }, + { label: 'Javanese', value: 'jw' }, + { label: 'Kannada', value: 'kn' }, + { label: 'Kazakh', value: 'kk' }, + { label: 'Kinyarwanda', value: 'rw' }, + { label: 'Kirundi', value: 'rn' }, + { label: 'Kongo', value: 'kg' }, + { label: 'Korean', value: 'ko' }, + { label: 'Krio (Sierra Leone)', value: 'kri' }, + { label: 'Kurdish', value: 'ku' }, + { label: 'Kurdish (Soranî)', value: 'ckb' }, + { label: 'Kyrgyz', value: 'ky' }, + { label: 'Laothian', value: 'lo' }, + { label: 'Latin', value: 'la' }, + { label: 'Latvian', value: 'lv' }, + { label: 'Lingala', value: 'ln' }, + { label: 'Lithuanian', value: 'lt' }, + { label: 'Lozi', value: 'loz' }, + { label: 'Luganda', value: 'lg' }, + { label: 'Luo', value: 'ach' }, + { label: 'Macedonian', value: 'mk' }, + { label: 'Malagasy', value: 'mg' }, + { label: 'Malay', value: 'ms' }, + { label: 'Malayalam', value: 'ml' }, + { label: 'Maltese', value: 'mt' }, + { label: 'Maldives', value: 'mv' }, + { label: 'Maori', value: 'mi' }, + { label: 'Marathi', value: 'mr' }, + { label: 'Mauritian Creole', value: 'mfe' }, + { label: 'Moldavian', value: 'mo' }, + { label: 'Mongolian', value: 'mn' }, + { label: 'Montenegrin', value: 'sr-me' }, + { label: 'Myanmar', value: 'my' }, + { label: 'Nepali', value: 'ne' }, + { label: 'Nigerian Pidgin', value: 'pcm' }, + { label: 'Northern Sotho', value: 'nso' }, + { label: 'Norwegian', value: 'no' }, + { label: 'Norwegian (Nynorsk)', value: 'nn' }, + { label: 'Occitan', value: 'oc' }, + { label: 'Oriya', value: 'or' }, + { label: 'Oromo', value: 'om' }, + { label: 'Pashto', value: 'ps' }, + { label: 'Persian', value: 'fa' }, + { label: 'Polish', value: 'pl' }, + { label: 'Portuguese', value: 'pt' }, + { label: 'Portuguese (Brazil)', value: 'pt-br' }, + { label: 'Portuguese (Portugal)', value: 'pt-pt' }, + { label: 'Punjabi', value: 'pa' }, + { label: 'Quechua', value: 'qu' }, + { label: 'Romanian', value: 'ro' }, + { label: 'Romansh', value: 'rm' }, + { label: 'Runyakitara', value: 'nyn' }, + { label: 'Russian', value: 'ru' }, + { label: 'Samoa', value: 'ws' }, + { label: 'Scots Gaelic', value: 'gd' }, + { label: 'Serbian', value: 'sr' }, + { label: 'Serbo-Croatian', value: 'sh' }, + { label: 'Sesotho', value: 'st' }, + { label: 'Setswana', value: 'tn' }, + { label: 'Seychellois Creole', value: 'crs' }, + { label: 'Shona', value: 'sn' }, + { label: 'Sindhi', value: 'sd' }, + { label: 'Sinhalese', value: 'si' }, + { label: 'Slovak', value: 'sk' }, + { label: 'Slovenian', value: 'sl' }, + { label: 'Somali', value: 'so' }, + { label: 'Spanish', value: 'es' }, + { label: 'Spanish (Latin American)', value: 'es-419' }, + { label: 'Sundanese', value: 'su' }, + { label: 'Swahili', value: 'sw' }, + { label: 'Swedish', value: 'sv' }, + { label: 'Tajik', value: 'tg' }, + { label: 'Tamil', value: 'ta' }, + { label: 'Tatar', value: 'tt' }, + { label: 'Telugu', value: 'te' }, + { label: 'Thai', value: 'th' }, + { label: 'Tigrinya', value: 'ti' }, + { label: 'Tonga', value: 'to' }, + { label: 'Tshiluba', value: 'lua' }, + { label: 'Tumbuka', value: 'tum' }, + { label: 'Turkish', value: 'tr' }, + { label: 'Turkmen', value: 'tk' }, + { label: 'Twi', value: 'tw' }, + { label: 'Uighur', value: 'ug' }, + { label: 'Ukrainian', value: 'uk' }, + { label: 'Urdu', value: 'ur' }, + { label: 'Uzbek', value: 'uz' }, + { label: 'Vanuatu', value: 'vu' }, + { label: 'Vietnamese', value: 'vi' }, + { label: 'Welsh', value: 'cy' }, + { label: 'Wolof', value: 'wo' }, + { label: 'Xhosa', value: 'xh' }, + { label: 'Yiddish', value: 'yi' }, + { label: 'Yoruba', value: 'yo' }, + { label: 'Zulu', value: 'zu' }, +]; \ No newline at end of file diff --git a/packages/pieces/community/serp-api/src/lib/services/serp-api-client.ts b/packages/pieces/community/serp-api/src/lib/services/serp-api-client.ts new file mode 100644 index 0000000..c16a3e9 --- /dev/null +++ b/packages/pieces/community/serp-api/src/lib/services/serp-api-client.ts @@ -0,0 +1,325 @@ +import { httpClient, HttpMethod, HttpRequest } from '@activepieces/pieces-common'; +import { + GoogleSearchConfig, + RequestOptions, + SerpApiConfig, + SerpApiEngine, + SerpApiResponse, +} from '../types'; +import { SerpApiValidator } from '../utils/validators'; + + +// Configuration for the SerpApi client +export interface SerpApiClientConfig { + /** Base URL for SerpApi */ + baseUrl?: string; + /** Default timeout for requests */ + defaultTimeout?: number; + /** Default number of retries */ + defaultRetries?: number; + /** Default delay between retries (ms) */ + defaultRetryDelay?: number; + /** Enable request/response logging */ + enableLogging?: boolean; +} + +// Creates an appropriate error based on the HTTP status code and response +function createErrorFromResponse(statusCode: number, response: any, message?: string): Error { + const defaultMessage = message || 'API request failed'; + const responseMessage = response?.error || response?.message; + const finalMessage = responseMessage || defaultMessage; + + const error = new Error(finalMessage); + (error as any).statusCode = statusCode; + (error as any).response = response; + + switch (statusCode) { + case 401: + (error as any).code = 'SERP_API_AUTH_ERROR'; + break; + case 400: + (error as any).code = 'SERP_API_VALIDATION_ERROR'; + break; + case 402: + (error as any).code = 'SERP_API_QUOTA_EXCEEDED_ERROR'; + break; + case 408: + (error as any).code = 'SERP_API_TIMEOUT_ERROR'; + break; + case 429: + (error as any).code = 'SERP_API_RATE_LIMIT_ERROR'; + (error as any).retryAfter = response?.retry_after || response?.retryAfter; + break; + default: + (error as any).code = 'SERP_API_RESPONSE_ERROR'; + break; + } + + return error; +} + +// Creates a network error from a connection/network issue +function createNetworkError(originalError: Error): Error { + const error = new Error(`Network error: ${originalError.message}`); + (error as any).code = 'SERP_API_NETWORK_ERROR'; + (error as any).originalError = originalError.message; + return error; +} + +// Creates a timeout error +function createTimeoutError(timeout: number): Error { + const error = new Error(`Request timed out after ${timeout}ms`); + (error as any).code = 'SERP_API_TIMEOUT_ERROR'; + (error as any).statusCode = 408; + (error as any).timeout = timeout; + return error; +} + +// SerpApi client +export class SerpApiClient { + private readonly baseUrl: string; + private readonly defaultTimeout: number; + private readonly defaultRetries: number; + private readonly defaultRetryDelay: number; + private readonly enableLogging: boolean; + + // Creates a new SerpApi client instance + constructor(config: SerpApiClientConfig = {}) { + this.baseUrl = config.baseUrl || 'https://serpapi.com/search'; + this.defaultTimeout = config.defaultTimeout || 30000; + this.defaultRetries = config.defaultRetries || 3; + this.defaultRetryDelay = config.defaultRetryDelay || 1000; + this.enableLogging = config.enableLogging || false; + } + + // Executes a search request with comprehensive error handling and retry logic + async executeSearch( + config: SerpApiConfig, + options: RequestOptions = {} + ): Promise> { + // Validate and sanitize input + const validatedConfig = SerpApiValidator.validateAndSanitize(config); + + // Apply request options with defaults + const requestOptions: Required = { + timeout: options.timeout || this.defaultTimeout, + retries: options.retries || this.defaultRetries, + retryDelay: options.retryDelay || this.defaultRetryDelay, + }; + + return this.executeWithRetry(validatedConfig, requestOptions); + } + + // Validates API key by making a test request + async validateApiKey(apiKey: string): Promise { + try { + const testConfig: GoogleSearchConfig = { + api_key: apiKey, + engine: SerpApiEngine.GOOGLE, + q: 'test', + num: 1, + }; + + await this.makeHttpRequest(testConfig, { timeout: 10000, retries: 1, retryDelay: 0 }); + return true; + } catch (error: any) { + if (error.code === 'SERP_API_AUTH_ERROR') { + return false; + } + // Other errors might indicate network issues, not invalid API key + throw error; + } + } + + // Executes request with retry logic + private async executeWithRetry( + config: SerpApiConfig, + options: Required + ): Promise> { + let lastError: Error | null = null; + + for (let attempt = 1; attempt <= options.retries + 1; attempt++) { + try { + const response = await this.makeHttpRequest(config, options); + + if (this.enableLogging) { + console.log(`SerpApi request successful on attempt ${attempt}:`, { + engine: config.engine, + query: config.q, + responseTime: response.search_metadata?.total_time_taken, + }); + } + + return response; + } catch (error) { + lastError = error as Error; + + // Don't retry on certain error types + if (this.shouldNotRetry(error as Error)) { + throw error; + } + + // Don't retry on last attempt + if (attempt <= options.retries) { + const delay = this.calculateRetryDelay(attempt, options.retryDelay); + + if (this.enableLogging) { + console.warn(`SerpApi request failed on attempt ${attempt}, retrying in ${delay}ms:`, { + error: (error as Error).message, + attempt, + maxRetries: options.retries, + }); + } + + await this.sleep(delay); + } + } + } + + const error = new Error('Request failed after all retry attempts'); + (error as any).code = 'SERP_API_RESPONSE_ERROR'; + (error as any).statusCode = 500; + (error as any).attempts = options.retries + 1; + throw lastError || error; + } + + // Makes the actual HTTP request to SerpApi + private async makeHttpRequest( + config: SerpApiConfig, + options: Required + ): Promise> { + try { + // Build query parameters, filtering out undefined values + const queryParams = this.buildQueryParams(config); + + const httpRequest: HttpRequest = { + method: HttpMethod.GET, + url: this.baseUrl, + queryParams, + timeout: options.timeout, + }; + + if (this.enableLogging) { + console.log('Making SerpApi request:', { + url: this.baseUrl, + params: this.sanitizeParamsForLogging(queryParams), + }); + } + + const response = await httpClient.sendRequest(httpRequest); + + // Handle API-level errors in successful HTTP responses + if (response.body?.error) { + throw createErrorFromResponse( + response.status || 400, + response.body, + response.body.error + ); + } + + return this.transformResponse(response.body); + } catch (error) { + throw this.handleHttpError(error as Error); + } + } + + // Builds query parameters from config, filtering undefined values + private buildQueryParams(config: SerpApiConfig): Record { + const params: Record = { ...config }; + + // Remove undefined values and convert all to strings + const cleanParams: Record = {}; + for (const [key, value] of Object.entries(params)) { + if (value !== undefined && value !== null) { + cleanParams[key] = String(value); + } + } + + return cleanParams; + } + + // Transforms raw API response into structured format + private transformResponse(rawResponse: any): SerpApiResponse { + return { + data: rawResponse as T, + }; + } + + // Handles and transforms HTTP errors into SerpApi errors + private handleHttpError(error: Error): Error { + // Handle timeout errors + if (error.message.includes('timeout') || error.message.includes('ETIMEDOUT')) { + return createTimeoutError(this.defaultTimeout); + } + + // Handle network errors + if (error.message.includes('ENOTFOUND') || error.message.includes('ECONNREFUSED')) { + return createNetworkError(error); + } + + // Handle HTTP response errors + if ((error as any).status) { + return createErrorFromResponse( + (error as any).status, + (error as any).body, + error.message + ); + } + + // Default to network error for unknown errors + return createNetworkError(error); + } + + // Determines if an error should not be retried + private shouldNotRetry(error: Error): boolean { + const errorCode = (error as any).code; + + // Don't retry authentication errors + if (errorCode === 'SERP_API_AUTH_ERROR') { + return true; + } + + // Don't retry validation errors + if (errorCode === 'SERP_API_VALIDATION_ERROR') { + return true; + } + + // Don't retry quota exceeded errors + if (errorCode === 'SERP_API_QUOTA_EXCEEDED_ERROR') { + return true; + } + + return false; + } + + // Calculates retry delay with exponential backoff + private calculateRetryDelay(attempt: number, baseDelay: number): number { + // Exponential backoff: baseDelay * 2^(attempt-1) + return baseDelay * Math.pow(2, attempt - 1); + } + + // Sleep utility for retry delays + private sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + // Sanitizes parameters for logging (removes sensitive data) + private sanitizeParamsForLogging(params: Record): Record { + const sanitized = { ...params }; + if (sanitized['api_key']) { + sanitized['api_key'] = '***REDACTED***'; + } + return sanitized; + } + + // Returns client configuration information + getClientInfo(): SerpApiClientConfig { + return { + baseUrl: this.baseUrl, + defaultTimeout: this.defaultTimeout, + defaultRetries: this.defaultRetries, + defaultRetryDelay: this.defaultRetryDelay, + enableLogging: this.enableLogging, + }; + } +} diff --git a/packages/pieces/community/serp-api/src/lib/types/index.ts b/packages/pieces/community/serp-api/src/lib/types/index.ts new file mode 100644 index 0000000..ee9c58f --- /dev/null +++ b/packages/pieces/community/serp-api/src/lib/types/index.ts @@ -0,0 +1,198 @@ +/** + * @fileoverview Type definitions for SerpApi integration + * Provides comprehensive type safety and clear contracts for all SerpApi operations + */ + +/** + * Base configuration for all SerpApi requests + */ +export interface BaseSerpApiConfig { + /** API key for authentication */ + api_key: string; + /** Search engine to use */ + engine: SerpApiEngine; + /** Search query */ + q?: string; + /** Language code (e.g., 'en', 'es', 'fr') */ + hl?: string; + /** Country code (e.g., 'us', 'uk', 'ca') */ + gl?: string; + /** Number of results to return */ + num?: number; + /** Starting position for pagination */ + start?: number; +} + +/** + * Supported SerpApi search engines + */ +export enum SerpApiEngine { + GOOGLE = 'google', + GOOGLE_NEWS = 'google_news', + YOUTUBE = 'youtube', + GOOGLE_TRENDS = 'google_trends', +} + +/** + * Time period filters for various search types + */ +export enum TimePeriod { + PAST_HOUR = 'h', + PAST_DAY = 'd', + PAST_WEEK = 'w', + PAST_MONTH = 'm', + PAST_YEAR = 'y', +} + +/** + * Google Trends specific time periods + */ +export enum GoogleTrendsTimePeriod { + PAST_HOUR = 'now 1-H', + PAST_4_HOURS = 'now 4-H', + PAST_DAY = 'now 1-d', + PAST_7_DAYS = 'now 7-d', + PAST_30_DAYS = 'today 1-m', + PAST_90_DAYS = 'today 3-m', + PAST_12_MONTHS = 'today 12-m', + PAST_5_YEARS = 'today 5-y', + ALL_TIME = 'all', +} + +/** + * Google search specific configuration + */ +export interface GoogleSearchConfig extends BaseSerpApiConfig { + engine: SerpApiEngine.GOOGLE; + /** Safe search setting */ + safe?: 'active' | 'off'; + /** Device type for search */ + device?: 'desktop' | 'mobile' | 'tablet'; + /** Location for search */ + location?: string; + /** Filter parameter */ + filter?: '0' | '1'; +} + +/** + * Google News search specific configuration + */ +export interface GoogleNewsSearchConfig extends BaseSerpApiConfig { + engine: SerpApiEngine.GOOGLE_NEWS; + /** News search modifier */ + /** Time-based search parameter */ + tbs?: string; + /** Location for news search */ + location?: string; +} + +/** + * YouTube search specific configuration + */ +export interface YouTubeSearchConfig extends BaseSerpApiConfig { + engine: SerpApiEngine.YOUTUBE; + /** YouTube search query parameter */ + search_query: string; + /** Sort parameter for YouTube */ + sp?: string; +} + +/** + * Google Trends search specific configuration + */ +export interface GoogleTrendsSearchConfig extends BaseSerpApiConfig { + engine: SerpApiEngine.GOOGLE_TRENDS; + /** Data type for trends */ + data_type?: 'TIMESERIES' | 'GEO_MAP' | 'RELATED_TOPICS' | 'RELATED_QUERIES'; + /** Geographic location */ + geo?: string; + /** Date range */ + date?: string; + /** Category ID */ + cat?: number; + /** Google property filter */ + gprop?: string; +} + +/** + * Union type for all possible SerpApi configurations + */ +export type SerpApiConfig = + | GoogleSearchConfig + | GoogleNewsSearchConfig + | YouTubeSearchConfig + | GoogleTrendsSearchConfig; + +/** + * Sort options for different search types + */ +export enum SortBy { + RELEVANCE = 'relevance', + DATE = 'date', + UPLOAD_DATE = 'upload_date', + VIEW_COUNT = 'view_count', + RATING = 'rating', +} + +/** + * YouTube specific filters + */ +export enum YouTubeDuration { + ANY = '', + SHORT = 'short', + MEDIUM = 'medium', + LONG = 'long', +} + +export enum YouTubeQuality { + ANY = '', + HIGH = 'high', +} + +/** + * Error response from SerpApi + */ +export interface SerpApiError { + error: string; + message?: string; + code?: number; +} + +/** + * Standard response wrapper for SerpApi + */ +export interface SerpApiResponse { + search_metadata?: { + id: string; + status: string; + json_endpoint: string; + created_at: string; + processed_at: string; + total_time_taken: number; + }; + search_parameters?: Record; + search_information?: { + organic_results_state: string; + query_displayed: string; + total_results?: number; + }; + data?: T; + error?: string; +} + +/** + * Validation result interface + */ +export interface ValidationResult { + isValid: boolean; + errors: string[]; +} + +/** + * Request options for HTTP calls + */ +export interface RequestOptions { + timeout?: number; + retries?: number; + retryDelay?: number; +} \ No newline at end of file diff --git a/packages/pieces/community/serp-api/src/lib/utils/validators.ts b/packages/pieces/community/serp-api/src/lib/utils/validators.ts new file mode 100644 index 0000000..2fc7ee1 --- /dev/null +++ b/packages/pieces/community/serp-api/src/lib/utils/validators.ts @@ -0,0 +1,546 @@ +import { SerpApiConfig, SerpApiEngine, ValidationResult } from '../types'; + +// Validation constants +export const VALIDATION_CONSTANTS = { + MAX_RESULTS: 100, + MIN_RESULTS: 1, + MAX_START_POSITION: 990, + MIN_START_POSITION: 0, + VALID_DEVICE_TYPES: ['desktop', 'mobile', 'tablet'], + VALID_SAFE_SEARCH: ['active', 'off'], + VALID_BOOLEAN_STRINGS: ['true', 'false'], + VALID_FILTER_VALUES: ['0', '1'], + VALID_UPLOAD_DATE: ['any', 'hour', 'today', 'week', 'month', 'year'], + MAX_GOOGLE_DOMAIN_LENGTH: 50, +} as const; + +// Core validation utility class +export class SerpApiValidator { + // Validates an API key format and structure + static validateApiKey(apiKey: string): ValidationResult { + const errors: string[] = []; + + if (!apiKey) { + errors.push('API key is required'); + } else if (typeof apiKey !== 'string') { + errors.push('API key must be a string'); + } + + return { + isValid: errors.length === 0, + errors, + }; + } + + /** + * Validates a search query + */ + static validateQuery(query: string): ValidationResult { + const errors: string[] = []; + + if (!query) { + errors.push('Search query is required'); + } else if (typeof query !== 'string') { + errors.push('Search query must be a string'); + } else { + const trimmedQuery = query.trim(); + if (trimmedQuery.length === 0) { + errors.push('Search query cannot be empty or only whitespace'); + } + } + + return { + isValid: errors.length === 0, + errors, + }; + } + + /** + * Validates language code + */ + static validateLanguageCode(language?: string): ValidationResult { + const errors: string[] = []; + + if (language !== undefined) { + if (typeof language !== 'string') { + errors.push('Language code must be a string'); + } else if (language.length !== 2) { + errors.push('Language code must be exactly 2 characters (ISO 639-1 format)'); + } else if (!/^[a-z]{2}$/.test(language)) { + errors.push('Language code must contain only lowercase letters'); + } + } + + return { + isValid: errors.length === 0, + errors, + }; + } + + /** + * Validates country code + */ + static validateCountryCode(country?: string): ValidationResult { + const errors: string[] = []; + + if (country !== undefined) { + if (typeof country !== 'string') { + errors.push('Country code must be a string'); + } else if (country.length !== 2) { + errors.push('Country code must be exactly 2 characters (ISO 3166-1 alpha-2 format)'); + } else if (!/^[a-z]{2}$/.test(country)) { + errors.push('Country code must contain only lowercase letters'); + } + } + + return { + isValid: errors.length === 0, + errors, + }; + } + + /** + * Validates number of results + */ + static validateNumResults(num?: number): ValidationResult { + const errors: string[] = []; + + if (num !== undefined) { + if (typeof num !== 'number') { + errors.push('Number of results must be a number'); + } else if (!Number.isInteger(num)) { + errors.push('Number of results must be an integer'); + } else if (num < VALIDATION_CONSTANTS.MIN_RESULTS) { + errors.push(`Number of results must be at least ${VALIDATION_CONSTANTS.MIN_RESULTS}`); + } else if (num > VALIDATION_CONSTANTS.MAX_RESULTS) { + errors.push(`Number of results cannot exceed ${VALIDATION_CONSTANTS.MAX_RESULTS}`); + } + } + + return { + isValid: errors.length === 0, + errors, + }; + } + + /** + * Validates start position for pagination + */ + static validateStartPosition(start?: number): ValidationResult { + const errors: string[] = []; + + if (start !== undefined) { + if (typeof start !== 'number') { + errors.push('Start position must be a number'); + } else if (!Number.isInteger(start)) { + errors.push('Start position must be an integer'); + } else if (start < VALIDATION_CONSTANTS.MIN_START_POSITION) { + errors.push(`Start position cannot be negative`); + } else if (start > VALIDATION_CONSTANTS.MAX_START_POSITION) { + errors.push(`Start position cannot exceed ${VALIDATION_CONSTANTS.MAX_START_POSITION}`); + } + } + + return { + isValid: errors.length === 0, + errors, + }; + } + + /** + * Validates device type + */ + static validateDeviceType(device?: string): ValidationResult { + const errors: string[] = []; + + if (device !== undefined) { + if (typeof device !== 'string') { + errors.push('Device type must be a string'); + } else if (!VALIDATION_CONSTANTS.VALID_DEVICE_TYPES.includes(device as any)) { + errors.push(`Device type must be one of: ${VALIDATION_CONSTANTS.VALID_DEVICE_TYPES.join(', ')}`); + } + } + + return { + isValid: errors.length === 0, + errors, + }; + } + + /** + * Validates safe search setting + */ + static validateSafeSearch(safe?: string): ValidationResult { + const errors: string[] = []; + + if (safe !== undefined) { + if (typeof safe !== 'string') { + errors.push('Safe search setting must be a string'); + } else if (!VALIDATION_CONSTANTS.VALID_SAFE_SEARCH.includes(safe as any)) { + errors.push(`Safe search must be one of: ${VALIDATION_CONSTANTS.VALID_SAFE_SEARCH.join(', ')}`); + } + } + + return { + isValid: errors.length === 0, + errors, + }; + } + + /** + * Validates location parameter + */ + static validateLocation(location?: string): ValidationResult { + const errors: string[] = []; + + if (location !== undefined) { + if (typeof location !== 'string') { + errors.push('Location must be a string'); + } else if (location.trim().length === 0) { + errors.push('Location cannot be empty or only whitespace'); + } + } + + return { + isValid: errors.length === 0, + errors, + }; + } + + /** + * Validates Google domain parameter + */ + static validateGoogleDomain(googleDomain?: string): ValidationResult { + const errors: string[] = []; + + if (googleDomain !== undefined) { + if (typeof googleDomain !== 'string') { + errors.push('Google domain must be a string'); + } else if (googleDomain.trim().length === 0) { + errors.push('Google domain cannot be empty or only whitespace'); + } else if (googleDomain.length > VALIDATION_CONSTANTS.MAX_GOOGLE_DOMAIN_LENGTH) { + errors.push(`Google domain is too long (maximum ${VALIDATION_CONSTANTS.MAX_GOOGLE_DOMAIN_LENGTH} characters)`); + } else if (!/^[a-zA-Z0-9.-]+$/.test(googleDomain)) { + errors.push('Google domain contains invalid characters (only alphanumeric, dots, and dashes allowed)'); + } + } + + return { + isValid: errors.length === 0, + errors, + }; + } + + /** + * Validates boolean string parameters (no_cache, async) + */ + static validateBooleanString(value?: string, fieldName = 'parameter'): ValidationResult { + const errors: string[] = []; + + if (value !== undefined) { + if (typeof value !== 'string') { + errors.push(`${fieldName} must be a string`); + } else if (!VALIDATION_CONSTANTS.VALID_BOOLEAN_STRINGS.includes(value as 'true' | 'false')) { + errors.push(`${fieldName} must be one of: ${VALIDATION_CONSTANTS.VALID_BOOLEAN_STRINGS.join(', ')}`); + } + } + + return { + isValid: errors.length === 0, + errors, + }; + } + + /** + * Validates filter parameter + */ + static validateFilter(filter?: string): ValidationResult { + const errors: string[] = []; + + if (filter !== undefined) { + if (typeof filter !== 'string') { + errors.push('Filter must be a string'); + } else if (!VALIDATION_CONSTANTS.VALID_FILTER_VALUES.includes(filter as '0' | '1')) { + errors.push(`Filter must be one of: ${VALIDATION_CONSTANTS.VALID_FILTER_VALUES.join(', ')}`); + } + } + + return { + isValid: errors.length === 0, + errors, + }; + } + + /** + * Validates YouTube sort parameter + */ + static validateYouTubeSort(sortBy?: string): ValidationResult { + const errors: string[] = []; + + if (sortBy !== undefined) { + if (typeof sortBy !== 'string') { + errors.push('Sort parameter must be a string'); + } + } + + return { + isValid: errors.length === 0, + errors, + }; + } + + /** + * Validates YouTube duration parameter + */ + static validateYouTubeDuration(duration?: string): ValidationResult { + const errors: string[] = []; + + if (duration !== undefined) { + if (typeof duration !== 'string') { + errors.push('Duration parameter must be a string'); + } + } + + return { + isValid: errors.length === 0, + errors, + }; + } + + /** + * Validates YouTube quality parameter + */ + static validateYouTubeQuality(quality?: string): ValidationResult { + const errors: string[] = []; + + if (quality !== undefined) { + if (typeof quality !== 'string') { + errors.push('Quality parameter must be a string'); + } + } + + return { + isValid: errors.length === 0, + errors, + }; + } + + /** + * Validates YouTube upload date parameter + */ + static validateUploadDate(uploadDate?: string): ValidationResult { + const errors: string[] = []; + + if (uploadDate !== undefined) { + if (typeof uploadDate !== 'string') { + errors.push('Upload date parameter must be a string'); + } else if (!VALIDATION_CONSTANTS.VALID_UPLOAD_DATE.includes(uploadDate as 'any' | 'hour' | 'today' | 'week' | 'month' | 'year')) { + errors.push(`Upload date parameter must be one of: ${VALIDATION_CONSTANTS.VALID_UPLOAD_DATE.join(', ')}`); + } + } + + return { + isValid: errors.length === 0, + errors, + }; + } + + /** + * Validates mutual exclusivity of no_cache and async parameters + */ + static validateCacheAsyncMutualExclusivity(noCache?: string, async?: string): ValidationResult { + const errors: string[] = []; + + if (noCache === 'true' && async === 'true') { + errors.push('no_cache and async parameters cannot both be set to true'); + } + + return { + isValid: errors.length === 0, + errors, + }; + } + + /** + * Validates a complete SerpApi configuration + */ + static validateConfig(config: Partial): ValidationResult { + const allErrors: string[] = []; + + // Validate required fields + if (!config.api_key) { + allErrors.push('API key is required'); + } else { + const apiKeyValidation = this.validateApiKey(config.api_key); + allErrors.push(...apiKeyValidation.errors); + } + + if (!config.engine) { + allErrors.push('Engine is required'); + } else if (!Object.values(SerpApiEngine).includes(config.engine)) { + allErrors.push(`Engine must be one of: ${Object.values(SerpApiEngine).join(', ')}`); + } + + // Validate optional common fields + if (config.hl) { + const languageValidation = this.validateLanguageCode(config.hl); + allErrors.push(...languageValidation.errors); + } + + if (config.gl) { + const countryValidation = this.validateCountryCode(config.gl); + allErrors.push(...countryValidation.errors); + } + + if (config.num) { + const numValidation = this.validateNumResults(config.num); + allErrors.push(...numValidation.errors); + } + + if (config.start) { + const startValidation = this.validateStartPosition(config.start); + allErrors.push(...startValidation.errors); + } + + // Validate additional parameters + const configAny = config as any; + + if (configAny.location) { + const locationValidation = this.validateLocation(configAny.location); + allErrors.push(...locationValidation.errors); + } + + if (configAny.google_domain) { + const googleDomainValidation = this.validateGoogleDomain(configAny.google_domain); + allErrors.push(...googleDomainValidation.errors); + } + + if (configAny.no_cache) { + const noCacheValidation = this.validateBooleanString(configAny.no_cache, 'no_cache'); + allErrors.push(...noCacheValidation.errors); + } + + if (configAny.async) { + const asyncValidation = this.validateBooleanString(configAny.async, 'async'); + allErrors.push(...asyncValidation.errors); + } + + if (configAny.filter) { + const filterValidation = this.validateFilter(configAny.filter); + allErrors.push(...filterValidation.errors); + } + + if (configAny.safe) { + const safeValidation = this.validateSafeSearch(configAny.safe); + allErrors.push(...safeValidation.errors); + } + + if (configAny.device) { + const deviceValidation = this.validateDeviceType(configAny.device); + allErrors.push(...deviceValidation.errors); + } + + // Validate mutual exclusivity + const mutualExclusivityValidation = this.validateCacheAsyncMutualExclusivity( + configAny.no_cache, + configAny.async + ); + allErrors.push(...mutualExclusivityValidation.errors); + + return { + isValid: allErrors.length === 0, + errors: allErrors, + }; + } + + /** + * Sanitizes and normalizes input parameters + */ + static sanitizeConfig(config: Partial): Partial { + const sanitized = { ...config }; + + // Trim and normalize string fields + if (sanitized.hl) { + sanitized.hl = sanitized.hl.toLowerCase().trim(); + } + + if (sanitized.gl) { + sanitized.gl = sanitized.gl.toLowerCase().trim(); + } + + if (sanitized.api_key) { + sanitized.api_key = sanitized.api_key.trim(); + } + + // Sanitize additional parameters + const sanitizedAny = sanitized as any; + + if (sanitizedAny.location) { + sanitizedAny.location = sanitizedAny.location.trim(); + } + + if (sanitizedAny.google_domain) { + sanitizedAny.google_domain = sanitizedAny.google_domain.toLowerCase().trim(); + } + + if (sanitizedAny.no_cache) { + sanitizedAny.no_cache = sanitizedAny.no_cache.toLowerCase().trim(); + } + + if (sanitizedAny.async) { + sanitizedAny.async = sanitizedAny.async.toLowerCase().trim(); + } + + if (sanitizedAny.filter) { + sanitizedAny.filter = sanitizedAny.filter.trim(); + } + + if (sanitizedAny.safe) { + sanitizedAny.safe = sanitizedAny.safe.toLowerCase().trim(); + } + + if (sanitizedAny.device) { + sanitizedAny.device = sanitizedAny.device.toLowerCase().trim(); + } + + // Ensure numbers are properly typed + if (sanitized.num && typeof sanitized.num === 'string') { + const parsed = parseInt(sanitized.num, 10); + if (!isNaN(parsed)) { + sanitized.num = parsed; + } + } + + if (sanitized.start && typeof sanitized.start === 'string') { + const parsed = parseInt(sanitized.start, 10); + if (!isNaN(parsed)) { + sanitized.start = parsed; + } + } + + // Remove undefined values + Object.keys(sanitized).forEach(key => { + if (sanitized[key as keyof typeof sanitized] === undefined) { + delete sanitized[key as keyof typeof sanitized]; + } + }); + + return sanitized; + } + + /** + * Validates and sanitizes config, throwing error if invalid + */ + static validateAndSanitize(config: Partial): SerpApiConfig { + const sanitized = this.sanitizeConfig(config); + const validation = this.validateConfig(sanitized); + + if (!validation.isValid) { + throw new Error( + `Configuration validation failed: ${validation.errors.join(', ')}`, + ); + } + + return sanitized as SerpApiConfig; + } +} diff --git a/packages/pieces/community/serp-api/tsconfig.json b/packages/pieces/community/serp-api/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/serp-api/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/serp-api/tsconfig.lib.json b/packages/pieces/community/serp-api/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/serp-api/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/sessions-us/.eslintrc.json b/packages/pieces/community/sessions-us/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/sessions-us/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/sessions-us/README.md b/packages/pieces/community/sessions-us/README.md new file mode 100644 index 0000000..5d06107 --- /dev/null +++ b/packages/pieces/community/sessions-us/README.md @@ -0,0 +1,7 @@ +# pieces-sessions-us + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-sessions-us` to build the library. diff --git a/packages/pieces/community/sessions-us/package.json b/packages/pieces/community/sessions-us/package.json new file mode 100644 index 0000000..1be69ba --- /dev/null +++ b/packages/pieces/community/sessions-us/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-sessions-us", + "version": "0.0.5" +} \ No newline at end of file diff --git a/packages/pieces/community/sessions-us/project.json b/packages/pieces/community/sessions-us/project.json new file mode 100644 index 0000000..276fbb7 --- /dev/null +++ b/packages/pieces/community/sessions-us/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-sessions-us", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/sessions-us/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/sessions-us", + "tsConfig": "packages/pieces/community/sessions-us/tsconfig.lib.json", + "packageJson": "packages/pieces/community/sessions-us/package.json", + "main": "packages/pieces/community/sessions-us/src/index.ts", + "assets": [ + "packages/pieces/community/sessions-us/*.md", + { + "input": "packages/pieces/community/sessions-us/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-sessions-us {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/sessions-us/src/index.ts b/packages/pieces/community/sessions-us/src/index.ts new file mode 100644 index 0000000..1730062 --- /dev/null +++ b/packages/pieces/community/sessions-us/src/index.ts @@ -0,0 +1,86 @@ +import { + createCustomApiCallAction, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; + +import { PieceCategory } from '@activepieces/shared'; +import { createEvent } from './lib/actions/create-event'; +import { createSession } from './lib/actions/create-session'; +import { publishEvent } from './lib/actions/publish-event'; +import { bookingCreated } from './lib/triggers/booking-created'; +import { bookingEnded } from './lib/triggers/booking-ended'; +import { bookingStarted } from './lib/triggers/booking-started'; +import { eventCreated } from './lib/triggers/event-created'; +import { eventEnded } from './lib/triggers/event-ended'; +import { eventNewRegistration } from './lib/triggers/event-new-registration'; +import { eventPublished } from './lib/triggers/event-published'; +import { eventStarted } from './lib/triggers/event-started'; +import { sessionCreated } from './lib/triggers/session-created'; +import { sessionEnded } from './lib/triggers/session-ended'; +import { sessionStarted } from './lib/triggers/session-started'; +import { takeawayReady } from './lib/triggers/takeaway-ready'; +import { transcriptReady } from './lib/triggers/transcript-ready'; + +export const sessionAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + validate: async ({ auth }) => { + try { + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.app.sessions.us/api/sessions', + headers: { + accept: 'application/json', + 'x-api-key': `${auth}`, + }, + }); + return { + valid: true, + }; + } catch (e) { + return { + error: 'Invalid API Key', + valid: false, + }; + } + }, +}); + +export const sessionsUs = createPiece({ + displayName: 'Sessions.us', + description: 'Video conferencing platform for businesses and professionals', + auth: sessionAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/sessions-us.png', + authors: ["kishanprmr","MoShizzle","abuaboud"], + categories: [PieceCategory.BUSINESS_INTELLIGENCE], + actions: [ + createSession, + createEvent, + publishEvent, + createCustomApiCallAction({ + baseUrl: () => 'https://api.app.sessions.us/api', + auth: sessionAuth, + authMapping: async (auth) => ({ + 'x-api-key': `${auth}`, + }), + }), + ], + triggers: [ + bookingCreated, + bookingStarted, + bookingEnded, + eventCreated, + eventPublished, + eventStarted, + eventEnded, + eventNewRegistration, + sessionCreated, + sessionStarted, + sessionEnded, + takeawayReady, + transcriptReady, + ], +}); diff --git a/packages/pieces/community/sessions-us/src/lib/actions/create-event.ts b/packages/pieces/community/sessions-us/src/lib/actions/create-event.ts new file mode 100644 index 0000000..dce55e6 --- /dev/null +++ b/packages/pieces/community/sessions-us/src/lib/actions/create-event.ts @@ -0,0 +1,72 @@ +import { baseUrl, getTimezones, slugify } from '../common'; +import { sessionAuth } from '../..'; +import { + HttpMethod, + httpClient, + HttpRequest, +} from '@activepieces/pieces-common'; +import { Property, createAction } from '@activepieces/pieces-framework'; + +export const createEvent = createAction({ + auth: sessionAuth, + name: 'create_event', + displayName: 'Create Event', + description: 'Quickly create an event.', + props: { + name: Property.ShortText({ + displayName: 'Event Name', + description: 'The name of the event', + required: true, + }), + startAt: Property.DateTime({ + displayName: 'Starts At', + description: 'Select a date and time', + required: true, + defaultValue: new Date().toISOString(), + }), + plannedEnd: Property.DateTime({ + displayName: 'Ends At', + description: 'Select a date and time', + required: true, + defaultValue: new Date().toISOString(), + }), + timezone: Property.Dropdown({ + displayName: 'Timezone', + description: 'The timezone which the session will take place.', + required: true, + refreshers: [], + options: async () => { + const timezones = await getTimezones(); + + return { + options: timezones.map((timezone) => { + return { + label: timezone, + value: timezone, + }; + }), + }; + }, + }), + }, + + async run({ propsValue, auth }) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${baseUrl}/events`, + headers: { + 'x-api-key': auth, + }, + body: { + name: propsValue['name'], + slug: slugify(propsValue['name']), + startAt: propsValue['startAt'], + plannedEnd: propsValue['plannedEnd'], + timeZone: propsValue['timezone'], + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, +}); diff --git a/packages/pieces/community/sessions-us/src/lib/actions/create-session.ts b/packages/pieces/community/sessions-us/src/lib/actions/create-session.ts new file mode 100644 index 0000000..f451ddd --- /dev/null +++ b/packages/pieces/community/sessions-us/src/lib/actions/create-session.ts @@ -0,0 +1,75 @@ +import { baseUrl, getTimezones } from '../common'; +import { sessionAuth } from '../..'; +import { + HttpMethod, + httpClient, + HttpRequest, +} from '@activepieces/pieces-common'; +import { Property, createAction } from '@activepieces/pieces-framework'; + +export const createSession = createAction({ + auth: sessionAuth, + name: 'create_session', + displayName: 'Create Session', + description: 'Quickly create a session.', + props: { + name: Property.ShortText({ + displayName: 'Session Name', + description: 'The name of the session', + required: true, + }), + startAt: Property.DateTime({ + displayName: 'Starts At', + description: 'Select a date and time', + required: true, + defaultValue: new Date().toISOString(), + }), + plannedEnd: Property.DateTime({ + displayName: 'Ends At', + description: 'Select a date and time', + required: true, + defaultValue: new Date().toISOString(), + }), + timezone: Property.Dropdown({ + displayName: 'Timezone', + description: 'The timezone which the session will take place.', + required: true, + refreshers: [], + options: async () => { + const timezones = await getTimezones(); + + return { + options: timezones.map((timezone) => { + return { + label: timezone, + value: timezone, + }; + }), + }; + }, + }), + }, + + async run({ propsValue, auth }) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${baseUrl}/sessions`, + headers: { + 'x-api-key': auth, + }, + body: { + name: propsValue['name'], + startAt: propsValue['startAt'], + plannedEnd: propsValue['plannedEnd'], + timeZone: propsValue['timezone'], + }, + }; + + const response = await httpClient.sendRequest(request); + return { + id: response.body['id'], + name: response.body['name'], + link: response.body['sessionLink'], + }; + }, +}); diff --git a/packages/pieces/community/sessions-us/src/lib/actions/publish-event.ts b/packages/pieces/community/sessions-us/src/lib/actions/publish-event.ts new file mode 100644 index 0000000..d970d2b --- /dev/null +++ b/packages/pieces/community/sessions-us/src/lib/actions/publish-event.ts @@ -0,0 +1,48 @@ +import { baseUrl, getEvents } from '../common'; +import { sessionAuth } from '../..'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { Property, createAction } from '@activepieces/pieces-framework'; + +export const publishEvent = createAction({ + auth: sessionAuth, + name: 'publish_event', + displayName: 'Publish Event', + description: 'Quickly publish an event.', + props: { + event: Property.Dropdown({ + displayName: 'Event', + description: 'The event you want to publish.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + }; + } + + const events = await getEvents(auth as string); + return { + options: events.map((event) => { + return { + label: event.session.name, + value: event.id, + }; + }), + }; + }, + }), + }, + + async run({ propsValue, auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${baseUrl}/events/${propsValue.event}/publish`, + headers: { + 'x-api-key': auth, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/sessions-us/src/lib/common/index.ts b/packages/pieces/community/sessions-us/src/lib/common/index.ts new file mode 100644 index 0000000..22f7452 --- /dev/null +++ b/packages/pieces/community/sessions-us/src/lib/common/index.ts @@ -0,0 +1,174 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { + createTrigger, + Property, + Trigger, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { sessionAuth } from '../..'; + +export const baseUrl = 'https://api.app.sessions.us/api'; + +export const properties = { + permission: Property.StaticDropdown({ + displayName: 'Permission', + description: + 'Personal applies for the user only, organization applies to every event that is made by a user of the organization.', + required: true, + defaultValue: 'personal', + options: { + options: [ + { + label: 'Personal', + value: 'personal', + }, + { + label: 'Organization', + value: 'organization', + }, + ], + }, + }), +}; + +export async function getTimezones(): Promise { + const timezones = await httpClient.sendRequest({ + url: 'http://worldtimeapi.org/api/timezone', + method: HttpMethod.GET, + }); + + return timezones.body as string[]; +} + +export async function getEvents( + auth: string +): Promise<{ id: string; session: { name: string } }[]> { + const response = await httpClient.sendRequest< + { id: string; session: { name: string } }[] + >({ + method: HttpMethod.GET, + url: `${baseUrl}/events`, + headers: { + 'x-api-key': auth, + }, + }); + + return response.body; +} + +export function slugify(string: string) { + // Remove leading and trailing whitespaces + const trimmedStr = string.trim(); + + // Replace spaces with dashes, remove special characters, and convert to lowercase + const slug = trimmedStr + .toLowerCase() + .replace(/[^\w\s-]/g, '') // Remove non-word characters (alphanumeric, underscores, and dashes) + .replace(/\s+/g, '-') // Replace spaces with dashes + .replace(/-+/g, '-'); // Replace consecutive dashes with a single dash + + return slug; +} + +export async function createWebhook( + trigger: SessionsUsWebhookTrigger, + auth: string, + webhookUrl: string, + permission: string +): Promise<{ id: string }> { + const response = await httpClient.sendRequest({ + url: `${baseUrl}/webhooks`, + method: HttpMethod.POST, + headers: { + 'x-api-key': auth, + }, + body: { + url: webhookUrl, + trigger: trigger, + permission: permission, + // If the API key used to create this webhook is deleted, deletes the webhook + linkPublicKey: true, + // This needs to be ACTIVE_PIECES as set up by the Sessions.us team, makes the webhook not editable from the frontend + integration: 'ACTIVE_PIECES', + }, + }); + + return response.body as { id: string }; +} + +export async function deleteWebhook(webhookId: string, auth: string) { + const response = await httpClient.sendRequest({ + url: `${baseUrl}/webhooks/${webhookId}`, + method: HttpMethod.DELETE, + headers: { + 'x-api-key': auth, + }, + }); + + return response.body; +} + +export enum SessionsUsWebhookTrigger { + SESSION_CREATED = 'SESSION_CREATED', + SESSION_STARTED = 'SESSION_STARTED', + SESSION_ENDED = 'SESSION_ENDED', + BOOKING_CREATED = 'BOOKING_CREATED', + BOOKING_STARTED = 'BOOKING_STARTED', + BOOKING_ENDED = 'BOOKING_ENDED', + EVENT_CREATED = 'EVENT_CREATED', + EVENT_STARTED = 'EVENT_STARTED', + EVENT_ENDED = 'EVENT_ENDED', + EVENT_PUBLISHED = 'EVENT_PUBLISHED', + EVENT_NEW_REGISTRATION = 'EVENT_NEW_REGISTRATION', + TRANSCRIPT_READY = 'TRANSCRIPT_READY', + TAKEAWAY_READY = 'TAKEAWAY_READY', +} + +export function createSessionsUsWebhookTrigger( + data: CreateWebhookTriggerDto +): Trigger { + return createTrigger({ + auth: sessionAuth, + name: data.name, + displayName: data.displayName, + description: data.description, + type: TriggerStrategy.WEBHOOK, + sampleData: data.sampleData ?? {}, + props: { + permission: properties.permission, + }, + async onEnable({ auth, store, webhookUrl, propsValue }) { + const webhookId = await createWebhook( + data.trigger, + auth, + webhookUrl, + propsValue.permission + ); + + await store.put(data.storeKey, { + webhookId: webhookId.id, + }); + }, + async onDisable({ auth, store }) { + const webhookId: { + webhookId: string; + } | null = await store.get(data.storeKey); + if (webhookId) { + await deleteWebhook(webhookId.webhookId, auth); + } + }, + async run({ payload }) { + const body = payload.body as { trigger: string; data: unknown }; + return [body.data]; + }, + }); +} + +export interface CreateWebhookTriggerDto { + name: string; + displayName: string; + description: string; + sampleData?: unknown; + trigger: SessionsUsWebhookTrigger; + storeKey: string; +} diff --git a/packages/pieces/community/sessions-us/src/lib/triggers/booking-created.ts b/packages/pieces/community/sessions-us/src/lib/triggers/booking-created.ts new file mode 100644 index 0000000..7571735 --- /dev/null +++ b/packages/pieces/community/sessions-us/src/lib/triggers/booking-created.ts @@ -0,0 +1,57 @@ +import { + createSessionsUsWebhookTrigger, + SessionsUsWebhookTrigger, +} from '../common'; + +export const bookingCreated = createSessionsUsWebhookTrigger({ + name: 'booking_created', + displayName: 'Booking Created', + description: 'Triggered when a new booking is created.', + trigger: SessionsUsWebhookTrigger.BOOKING_CREATED, + storeKey: 'sessions_booking_created_trigger', + sampleData: { + session: { + id: '2f8547a5-5c36-49ea-bc21-c61e337d89a3', + name: 'Talk with Active Pieces', + description: '', + quickSession: false, + room: null, + createdAt: '2023-11-30T10:21:41.372Z', + startAt: '2023-11-30T10:30:00.000Z', + actualStart: '2023-11-30T10:34:37.799Z', + endedAt: null, + booking: { + id: '3df6dbdb-9a6c-41e6-89b2-d7a38233163e', + name: 'Talk with Active Pieces', + participantName: 'Active Pieces', + participantEmail: 'example@gmail.com', + guests: [], + }, + event: null, + participants: [ + { + id: '2021014e-5e28-498f-9fe3-3428ce40c9c4', + isOwner: false, + guest: { + id: '529e6bc0-af38-4365-a0b2-5530d8207ecb', + email: 'example@gmail.com', + lastName: null, + firstName: 'Active Pieces', + }, + user: null, + }, + { + id: '529b967f-de3d-4dec-a865-35637c294f9c', + isOwner: true, + guest: null, + user: { + id: '9090bd7c-0cf9-4716-837e-43f3821a65c4', + email: 'email@example.com', + lastName: 'Pieces', + firstName: 'Active', + }, + }, + ], + }, + }, +}); diff --git a/packages/pieces/community/sessions-us/src/lib/triggers/booking-ended.ts b/packages/pieces/community/sessions-us/src/lib/triggers/booking-ended.ts new file mode 100644 index 0000000..42007ff --- /dev/null +++ b/packages/pieces/community/sessions-us/src/lib/triggers/booking-ended.ts @@ -0,0 +1,57 @@ +import { + createSessionsUsWebhookTrigger, + SessionsUsWebhookTrigger, +} from '../common'; + +export const bookingEnded = createSessionsUsWebhookTrigger({ + name: 'booking_ended', + displayName: 'Booking Ended', + description: 'Triggered when a booking ends.', + trigger: SessionsUsWebhookTrigger.BOOKING_ENDED, + storeKey: 'sessions_booking_ended_trigger', + sampleData: { + session: { + id: '2f8547a5-5c36-49ea-bc21-c61e337d89a3', + name: 'Talk with Active Pieces', + description: '', + quickSession: false, + room: null, + createdAt: '2023-11-30T10:21:41.372Z', + startAt: '2023-11-30T10:30:00.000Z', + actualStart: '2023-11-30T10:34:37.799Z', + endedAt: null, + booking: { + id: '3df6dbdb-9a6c-41e6-89b2-d7a38233163e', + name: 'Talk with Active Pieces', + participantName: 'Active Pieces', + participantEmail: 'example@gmail.com', + guests: [], + }, + event: null, + participants: [ + { + id: '2021014e-5e28-498f-9fe3-3428ce40c9c4', + isOwner: false, + guest: { + id: '529e6bc0-af38-4365-a0b2-5530d8207ecb', + email: 'example@gmail.com', + lastName: null, + firstName: 'Active Pieces', + }, + user: null, + }, + { + id: '529b967f-de3d-4dec-a865-35637c294f9c', + isOwner: true, + guest: null, + user: { + id: '9090bd7c-0cf9-4716-837e-43f3821a65c4', + email: 'email@example.com', + lastName: 'Pieces', + firstName: 'Active', + }, + }, + ], + }, + }, +}); diff --git a/packages/pieces/community/sessions-us/src/lib/triggers/booking-started.ts b/packages/pieces/community/sessions-us/src/lib/triggers/booking-started.ts new file mode 100644 index 0000000..866dcb2 --- /dev/null +++ b/packages/pieces/community/sessions-us/src/lib/triggers/booking-started.ts @@ -0,0 +1,57 @@ +import { + createSessionsUsWebhookTrigger, + SessionsUsWebhookTrigger, +} from '../common'; + +export const bookingStarted = createSessionsUsWebhookTrigger({ + name: 'booking_started', + displayName: 'Booking Started', + description: 'Triggered when a booking starts.', + trigger: SessionsUsWebhookTrigger.BOOKING_STARTED, + storeKey: 'sessions_booking_started_trigger', + sampleData: { + session: { + id: '2f8547a5-5c36-49ea-bc21-c61e337d89a3', + name: 'Talk with Active Pieces', + description: '', + quickSession: false, + room: null, + createdAt: '2023-11-30T10:21:41.372Z', + startAt: '2023-11-30T10:30:00.000Z', + actualStart: '2023-11-30T10:34:37.799Z', + endedAt: null, + booking: { + id: '3df6dbdb-9a6c-41e6-89b2-d7a38233163e', + name: 'Talk with Active Pieces', + participantName: 'Active Pieces', + participantEmail: 'example@gmail.com', + guests: [], + }, + event: null, + participants: [ + { + id: '2021014e-5e28-498f-9fe3-3428ce40c9c4', + isOwner: false, + guest: { + id: '529e6bc0-af38-4365-a0b2-5530d8207ecb', + email: 'example@gmail.com', + lastName: null, + firstName: 'Active Pieces', + }, + user: null, + }, + { + id: '529b967f-de3d-4dec-a865-35637c294f9c', + isOwner: true, + guest: null, + user: { + id: '9090bd7c-0cf9-4716-837e-43f3821a65c4', + email: 'email@example.com', + lastName: 'Pieces', + firstName: 'Active', + }, + }, + ], + }, + }, +}); diff --git a/packages/pieces/community/sessions-us/src/lib/triggers/event-created.ts b/packages/pieces/community/sessions-us/src/lib/triggers/event-created.ts new file mode 100644 index 0000000..a374cbf --- /dev/null +++ b/packages/pieces/community/sessions-us/src/lib/triggers/event-created.ts @@ -0,0 +1,43 @@ +import { + createSessionsUsWebhookTrigger, + SessionsUsWebhookTrigger, +} from '../common'; + +export const eventCreated = createSessionsUsWebhookTrigger({ + name: 'event_created', + displayName: 'Event Created', + description: 'Triggered when a new event is created.', + trigger: SessionsUsWebhookTrigger.EVENT_CREATED, + storeKey: 'sessions_event_created_trigger', + sampleData: { + session: { + id: '8208f783-fba9-4045-ae6e-dea64f5ab7ea', + name: 'My Best Event', + description: null, + quickSession: false, + room: null, + createdAt: '2023-11-30T10:46:13.675Z', + startAt: '2023-11-30T11:00:00.000Z', + actualStart: null, + endedAt: null, + booking: null, + event: { + id: '52259a5f-f706-41d9-8b58-1b8796bd0ffc', + slug: 'f34bd8a5-a7d9-47f2-aee9-031d10a76b75-83897/my-best-event', + }, + participants: [ + { + id: 'd98f984f-982b-4a17-8a3d-093a4a774b49', + isOwner: true, + guest: null, + user: { + id: '9090bd7c-0cf9-4716-837e-43f3821a65c4', + email: 'email@example.com', + lastName: 'Pieces', + firstName: 'Active', + }, + }, + ], + }, + }, +}); diff --git a/packages/pieces/community/sessions-us/src/lib/triggers/event-ended.ts b/packages/pieces/community/sessions-us/src/lib/triggers/event-ended.ts new file mode 100644 index 0000000..2fd5bbc --- /dev/null +++ b/packages/pieces/community/sessions-us/src/lib/triggers/event-ended.ts @@ -0,0 +1,43 @@ +import { + createSessionsUsWebhookTrigger, + SessionsUsWebhookTrigger, +} from '../common'; + +export const eventEnded = createSessionsUsWebhookTrigger({ + name: 'event_ended', + displayName: 'Event Ended', + description: 'Triggered when an event ends.', + trigger: SessionsUsWebhookTrigger.EVENT_ENDED, + storeKey: 'sessions_event_ended_trigger', + sampleData: { + session: { + id: '8208f783-fba9-4045-ae6e-dea64f5ab7ea', + name: 'My Best Event', + description: null, + quickSession: false, + room: null, + createdAt: '2023-11-30T10:46:13.675Z', + startAt: '2023-11-30T11:00:00.000Z', + actualStart: null, + endedAt: null, + booking: null, + event: { + id: '52259a5f-f706-41d9-8b58-1b8796bd0ffc', + slug: 'f34bd8a5-a7d9-47f2-aee9-031d10a76b75-83897/my-best-event', + }, + participants: [ + { + id: 'd98f984f-982b-4a17-8a3d-093a4a774b49', + isOwner: true, + guest: null, + user: { + id: '9090bd7c-0cf9-4716-837e-43f3821a65c4', + email: 'email@example.com', + lastName: 'Pieces', + firstName: 'Active', + }, + }, + ], + }, + }, +}); diff --git a/packages/pieces/community/sessions-us/src/lib/triggers/event-new-registration.ts b/packages/pieces/community/sessions-us/src/lib/triggers/event-new-registration.ts new file mode 100644 index 0000000..57c5dc1 --- /dev/null +++ b/packages/pieces/community/sessions-us/src/lib/triggers/event-new-registration.ts @@ -0,0 +1,31 @@ +import { + createSessionsUsWebhookTrigger, + SessionsUsWebhookTrigger, +} from '../common'; + +export const eventNewRegistration = createSessionsUsWebhookTrigger({ + name: 'event_new_registration', + displayName: 'Event New Registration', + description: 'Triggered when a new registration for an event occurs.', + trigger: SessionsUsWebhookTrigger.EVENT_NEW_REGISTRATION, + storeKey: 'sessions_event_new_registration_trigger', + sampleData: { + eventId: '52259a5f-f706-41d9-8b58-1b8796bd0ffc', + sessionId: '8208f783-fba9-4045-ae6e-dea64f5ab7ea', + registeredParticipant: { + id: '5164d6de-5f72-4990-8a42-8b5c9b7d5670', + email: 'email@example.com', + firstName: 'Active Pieces', + form: [ + { + question: 'Name', + answer: 'Active Pieces', + }, + { + question: 'Email', + answer: 'email@example.com', + }, + ], + }, + }, +}); diff --git a/packages/pieces/community/sessions-us/src/lib/triggers/event-published.ts b/packages/pieces/community/sessions-us/src/lib/triggers/event-published.ts new file mode 100644 index 0000000..20e2030 --- /dev/null +++ b/packages/pieces/community/sessions-us/src/lib/triggers/event-published.ts @@ -0,0 +1,43 @@ +import { + createSessionsUsWebhookTrigger, + SessionsUsWebhookTrigger, +} from '../common'; + +export const eventPublished = createSessionsUsWebhookTrigger({ + name: 'event_published', + displayName: 'Event Published', + description: 'Triggered when an event is published.', + trigger: SessionsUsWebhookTrigger.EVENT_PUBLISHED, + storeKey: 'sessions_event_published_trigger', + sampleData: { + session: { + id: '8208f783-fba9-4045-ae6e-dea64f5ab7ea', + name: 'My Best Event', + description: null, + quickSession: false, + room: null, + createdAt: '2023-11-30T10:46:13.675Z', + startAt: '2023-11-30T11:00:00.000Z', + actualStart: null, + endedAt: null, + booking: null, + event: { + id: '52259a5f-f706-41d9-8b58-1b8796bd0ffc', + slug: 'f34bd8a5-a7d9-47f2-aee9-031d10a76b75-83897/my-best-event', + }, + participants: [ + { + id: 'd98f984f-982b-4a17-8a3d-093a4a774b49', + isOwner: true, + guest: null, + user: { + id: '9090bd7c-0cf9-4716-837e-43f3821a65c4', + email: 'email@example.com', + lastName: 'Pieces', + firstName: 'Active', + }, + }, + ], + }, + }, +}); diff --git a/packages/pieces/community/sessions-us/src/lib/triggers/event-started.ts b/packages/pieces/community/sessions-us/src/lib/triggers/event-started.ts new file mode 100644 index 0000000..89b12b3 --- /dev/null +++ b/packages/pieces/community/sessions-us/src/lib/triggers/event-started.ts @@ -0,0 +1,43 @@ +import { + createSessionsUsWebhookTrigger, + SessionsUsWebhookTrigger, +} from '../common'; + +export const eventStarted = createSessionsUsWebhookTrigger({ + name: 'event_started', + displayName: 'Event Started', + description: 'Triggered when an event starts.', + trigger: SessionsUsWebhookTrigger.EVENT_STARTED, + storeKey: 'sessions_event_started_trigger', + sampleData: { + session: { + id: '8208f783-fba9-4045-ae6e-dea64f5ab7ea', + name: 'My Best Event', + description: null, + quickSession: false, + room: null, + createdAt: '2023-11-30T10:46:13.675Z', + startAt: '2023-11-30T11:00:00.000Z', + actualStart: null, + endedAt: null, + booking: null, + event: { + id: '52259a5f-f706-41d9-8b58-1b8796bd0ffc', + slug: 'f34bd8a5-a7d9-47f2-aee9-031d10a76b75-83897/my-best-event', + }, + participants: [ + { + id: 'd98f984f-982b-4a17-8a3d-093a4a774b49', + isOwner: true, + guest: null, + user: { + id: '9090bd7c-0cf9-4716-837e-43f3821a65c4', + email: 'email@example.com', + lastName: 'Pieces', + firstName: 'Active', + }, + }, + ], + }, + }, +}); diff --git a/packages/pieces/community/sessions-us/src/lib/triggers/session-created.ts b/packages/pieces/community/sessions-us/src/lib/triggers/session-created.ts new file mode 100644 index 0000000..fcfa3a5 --- /dev/null +++ b/packages/pieces/community/sessions-us/src/lib/triggers/session-created.ts @@ -0,0 +1,40 @@ +import { + createSessionsUsWebhookTrigger, + SessionsUsWebhookTrigger, +} from '../common'; + +export const sessionCreated = createSessionsUsWebhookTrigger({ + name: 'session_created', + displayName: 'Session Created', + description: 'Triggered when a new session is created.', + trigger: SessionsUsWebhookTrigger.SESSION_CREATED, + storeKey: 'sessions_session_created_trigger', + sampleData: { + session: { + id: '5645d810-4e29-4c2c-b9a5-84d71a6429dd', + name: 'My Talking Session', + description: '', + quickSession: false, + room: null, + createdAt: '2023-11-30T10:30:16.535Z', + startAt: '2023-11-30T10:31:00.000Z', + actualStart: null, + endedAt: null, + booking: null, + event: null, + participants: [ + { + id: '0f2aced6-e099-424b-9448-4dd94d9ba3a0', + isOwner: true, + guest: null, + user: { + id: '9090bd7c-0cf9-4716-837e-43f3821a65c4', + email: 'email@example.com', + lastName: 'Pieces', + firstName: 'Active', + }, + }, + ], + }, + }, +}); diff --git a/packages/pieces/community/sessions-us/src/lib/triggers/session-ended.ts b/packages/pieces/community/sessions-us/src/lib/triggers/session-ended.ts new file mode 100644 index 0000000..ba02c94 --- /dev/null +++ b/packages/pieces/community/sessions-us/src/lib/triggers/session-ended.ts @@ -0,0 +1,40 @@ +import { + createSessionsUsWebhookTrigger, + SessionsUsWebhookTrigger, +} from '../common'; + +export const sessionEnded = createSessionsUsWebhookTrigger({ + name: 'session_ended', + displayName: 'Session Ended', + description: 'Triggered when a session has ended.', + trigger: SessionsUsWebhookTrigger.SESSION_ENDED, + storeKey: 'sessions_session_ended_trigger', + sampleData: { + session: { + id: '5645d810-4e29-4c2c-b9a5-84d71a6429dd', + name: 'My Talking Session', + description: '', + quickSession: false, + room: null, + createdAt: '2023-11-30T10:30:16.535Z', + startAt: '2023-11-30T10:31:00.000Z', + actualStart: null, + endedAt: null, + booking: null, + event: null, + participants: [ + { + id: '0f2aced6-e099-424b-9448-4dd94d9ba3a0', + isOwner: true, + guest: null, + user: { + id: '9090bd7c-0cf9-4716-837e-43f3821a65c4', + email: 'email@example.com', + lastName: 'Pieces', + firstName: 'Active', + }, + }, + ], + }, + }, +}); diff --git a/packages/pieces/community/sessions-us/src/lib/triggers/session-started.ts b/packages/pieces/community/sessions-us/src/lib/triggers/session-started.ts new file mode 100644 index 0000000..02539ef --- /dev/null +++ b/packages/pieces/community/sessions-us/src/lib/triggers/session-started.ts @@ -0,0 +1,40 @@ +import { + createSessionsUsWebhookTrigger, + SessionsUsWebhookTrigger, +} from '../common'; + +export const sessionStarted = createSessionsUsWebhookTrigger({ + name: 'session_started', + displayName: 'Session Started', + description: 'Triggered when a session starts.', + trigger: SessionsUsWebhookTrigger.SESSION_STARTED, + storeKey: 'sessions_session_started_trigger', + sampleData: { + session: { + id: '5645d810-4e29-4c2c-b9a5-84d71a6429dd', + name: 'My Talking Session', + description: '', + quickSession: false, + room: null, + createdAt: '2023-11-30T10:30:16.535Z', + startAt: '2023-11-30T10:31:00.000Z', + actualStart: null, + endedAt: null, + booking: null, + event: null, + participants: [ + { + id: '0f2aced6-e099-424b-9448-4dd94d9ba3a0', + isOwner: true, + guest: null, + user: { + id: '9090bd7c-0cf9-4716-837e-43f3821a65c4', + email: 'email@example.com', + lastName: 'Pieces', + firstName: 'Active', + }, + }, + ], + }, + }, +}); diff --git a/packages/pieces/community/sessions-us/src/lib/triggers/takeaway-ready.ts b/packages/pieces/community/sessions-us/src/lib/triggers/takeaway-ready.ts new file mode 100644 index 0000000..c0414ea --- /dev/null +++ b/packages/pieces/community/sessions-us/src/lib/triggers/takeaway-ready.ts @@ -0,0 +1,36 @@ +import { + createSessionsUsWebhookTrigger, + SessionsUsWebhookTrigger, +} from '../common'; + +export const takeawayReady = createSessionsUsWebhookTrigger({ + name: 'takeaway_ready', + displayName: 'Takeaway Ready', + description: 'Triggered when a takeaway becomes available.', + trigger: SessionsUsWebhookTrigger.TAKEAWAY_READY, + storeKey: 'sessions_takeaway_ready_trigger', + sampleData: { + session: { + id: 'e5ebb4a1-a5a5-4a10-b18b-e1a89ddac27e', + takeawaysText: 'Hello', + takeawaysRaw: { + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { + textAlign: 'left', + indent: 0, + }, + content: [ + { + type: 'text', + text: 'Hello', + }, + ], + }, + ], + }, + }, + }, +}); diff --git a/packages/pieces/community/sessions-us/src/lib/triggers/transcript-ready.ts b/packages/pieces/community/sessions-us/src/lib/triggers/transcript-ready.ts new file mode 100644 index 0000000..e8b5439 --- /dev/null +++ b/packages/pieces/community/sessions-us/src/lib/triggers/transcript-ready.ts @@ -0,0 +1,30 @@ +import { + createSessionsUsWebhookTrigger, + SessionsUsWebhookTrigger, +} from '../common'; + +export const transcriptReady = createSessionsUsWebhookTrigger({ + name: 'transcript_ready', + displayName: 'Transcript Ready', + description: 'Triggered when a session has ended.', + trigger: SessionsUsWebhookTrigger.TRANSCRIPT_READY, + storeKey: 'sessions_transcript_ready_trigger', + sampleData: { + session: { + id: '8208f783-fba9-4045-ae6e-dea64f5ab7ea', + transcripts: [ + { + content: [ + { + text: 'Hello, my name is...', + language: 'en', + }, + ], + participantId: 'd98f984f-982b-4a17-8a3d-093a4a774b49', + sourceTimestamp: '2023-11-30T10:50:26.363Z', + sourceLanguage: 'en', + }, + ], + }, + }, +}); diff --git a/packages/pieces/community/sessions-us/tsconfig.json b/packages/pieces/community/sessions-us/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/sessions-us/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/sessions-us/tsconfig.lib.json b/packages/pieces/community/sessions-us/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/sessions-us/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/seven/.eslintrc.json b/packages/pieces/community/seven/.eslintrc.json new file mode 100644 index 0000000..15ede17 --- /dev/null +++ b/packages/pieces/community/seven/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*", "package.json"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/seven/README.md b/packages/pieces/community/seven/README.md new file mode 100644 index 0000000..57ce250 --- /dev/null +++ b/packages/pieces/community/seven/README.md @@ -0,0 +1,24 @@ + + +Official seven piece for [Activepieces](https://www.activepieces.com/) + +## Installation +### Dashboard +Settings -> My Pieces -> Install Piece -> type in `seven` + +### Package Manager +`npm i @seven.io/activepieces` + +## Actions +- Send SMS +- Text-To-Speech Call +- Lookup + +## Triggers +- Inbound SMS + +## Support + +Need help? Feel free to [contact us](https://www.seven.io/en/company/contact/). + +[![MIT](https://img.shields.io/badge/License-MIT-teal.svg)](LICENSE) diff --git a/packages/pieces/community/seven/package.json b/packages/pieces/community/seven/package.json new file mode 100644 index 0000000..9717179 --- /dev/null +++ b/packages/pieces/community/seven/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-seven", + "version": "0.0.1" +} diff --git a/packages/pieces/community/seven/project.json b/packages/pieces/community/seven/project.json new file mode 100644 index 0000000..9383713 --- /dev/null +++ b/packages/pieces/community/seven/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-seven", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/seven/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/seven", + "tsConfig": "packages/pieces/community/seven/tsconfig.lib.json", + "packageJson": "packages/pieces/community/seven/package.json", + "main": "packages/pieces/community/seven/src/index.ts", + "assets": [ + "packages/pieces/community/seven/*.md", + { + "input": "packages/pieces/community/seven/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-seven {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/seven/src/action/lookup.ts b/packages/pieces/community/seven/src/action/lookup.ts new file mode 100644 index 0000000..4d54855 --- /dev/null +++ b/packages/pieces/community/seven/src/action/lookup.ts @@ -0,0 +1,44 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { sevenAuth } from '../index'; +import { callSevenApi } from '../common'; + +export const lookup = createAction({ + auth: sevenAuth, + name: 'lookup', + displayName: 'Lookup Phone Numbers', + description: 'Get information about CNAM, HLR, MNP, RCS capabilities and Number formats.', + props: { + type: Property.StaticDropdown({ + options: { + options: [ + {label: 'CNAM', value: 'cnam'}, + {label: 'HLR', value: 'hlr'}, + {label: 'Format', value: 'format'}, + {label: 'MNP', value: 'mnp'}, + {label: 'RCS capabilities', value: 'rcs'} + ] + }, + displayName: 'Type', + required: true + }), + numbers: Property.Array({ + description: 'The phone numbers to look up.', + displayName: 'Numbers', + required: true + }), + }, + async run(context) { + const { numbers, type } = context.propsValue; + + const response= await callSevenApi({ + queryParams: { + number: numbers.join(','), + }, + method: HttpMethod.GET + }, `lookup/${type}`, context.auth as string); + + return response.body; + + } +}); diff --git a/packages/pieces/community/seven/src/action/send-voice-call.ts b/packages/pieces/community/seven/src/action/send-voice-call.ts new file mode 100644 index 0000000..a6b443e --- /dev/null +++ b/packages/pieces/community/seven/src/action/send-voice-call.ts @@ -0,0 +1,43 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { sevenAuth } from '../index'; +import { callSevenApi } from '../common'; + +export const sendVoiceCallAction = createAction({ + auth: sevenAuth, + name: 'send-voice-call', + displayName: 'Send Voice Call', + description: 'Creates a new Text-To-Speech call to a number.', + props: { + to: Property.ShortText({ + description: 'Recipient number(s) of the voice calls.', + displayName: 'To', + required: true + }), + from: Property.ShortText({ + displayName: 'From', + required: false + }), + text: Property.LongText({ + displayName: 'Message Body', + description: 'Text message to be read out.', + required: true + }), + + }, + async run(context) { + const { from, text, to } = context.propsValue; + + const response= await callSevenApi({ + body: { + from, + text, + to + }, + method: HttpMethod.POST + }, 'voice', context.auth as string); + + return response.body; + + } +}); diff --git a/packages/pieces/community/seven/src/action/sms-send.ts b/packages/pieces/community/seven/src/action/sms-send.ts new file mode 100644 index 0000000..ed08609 --- /dev/null +++ b/packages/pieces/community/seven/src/action/sms-send.ts @@ -0,0 +1,53 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { sevenAuth } from '../index'; +import { callSevenApi } from '../common'; + +export const sendSmsAction = createAction({ + auth: sevenAuth, + name: 'send-sms', + displayName: 'Send SMS', + description: 'Sends an SMS to one or more recipients.', + props: { + to: Property.Array({ + displayName: 'To', + description: 'Recipient numbers of the SMS.', + required: true + }), + delay: Property.DateTime({ + displayName: 'Delay', + required: false + }), + flash: Property.Checkbox({ + displayName: 'Flash SMS ?', + required: false + }), + from: Property.ShortText({ + displayName: 'From', + required: false + }), + text: Property.LongText({ + displayName: 'Message Body', + description: 'The body of the message to send.', + required: true + }), + + }, + async run(context) { + const { delay, flash, from, text, to } = context.propsValue; + + const response = await callSevenApi({ + body: { + delay: delay ? new Date(delay).toISOString().replace('T', ' ').substring(0, 19) : undefined, + flash, + from, + text, + to + }, + method: HttpMethod.POST + }, 'sms', context.auth as string); + + return response.body; + + } +}); diff --git a/packages/pieces/community/seven/src/common/index.ts b/packages/pieces/community/seven/src/common/index.ts new file mode 100644 index 0000000..9f244d9 --- /dev/null +++ b/packages/pieces/community/seven/src/common/index.ts @@ -0,0 +1,23 @@ +import { + type HttpResponse, + type HttpRequest, + httpClient +} from '@activepieces/pieces-common'; + +export const callSevenApi = async ( + httpRequest: Omit, + path: string, + apiKey: string +): Promise> => { + return await httpClient.sendRequest({ + ...httpRequest, + headers: { + ...httpRequest.headers, + Accept: 'application/json', + 'Content-Type': 'application/json', + SentWith: 'Activepieces', + 'X-Api-Key': apiKey + }, + url: `https://gateway.seven.io/api/${path}` + }); +}; diff --git a/packages/pieces/community/seven/src/index.ts b/packages/pieces/community/seven/src/index.ts new file mode 100644 index 0000000..2a1dc18 --- /dev/null +++ b/packages/pieces/community/seven/src/index.ts @@ -0,0 +1,25 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { sendSmsAction } from './action/sms-send'; +import { sendVoiceCallAction } from './action/send-voice-call'; +import { lookup } from './action/lookup'; +import { smsInbound } from './trigger/sms-inbound'; +import { PieceCategory } from '@activepieces/shared'; + +export const sevenAuth = PieceAuth.SecretText({ + description: + 'You can find your API key in [Developer Menu](https://app.seven.io/developer).', + displayName: 'API key', + required: true, +}); + +export const seven = createPiece({ + displayName: 'seven', + description: 'Business Messaging Gateway', + auth: sevenAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/seven.jpg', + categories: [PieceCategory.MARKETING], + authors: ['seven-io'], + actions: [sendSmsAction, sendVoiceCallAction, lookup], + triggers: [smsInbound], +}); diff --git a/packages/pieces/community/seven/src/trigger/sms-inbound.ts b/packages/pieces/community/seven/src/trigger/sms-inbound.ts new file mode 100644 index 0000000..bb3e7d9 --- /dev/null +++ b/packages/pieces/community/seven/src/trigger/sms-inbound.ts @@ -0,0 +1,81 @@ +import { createTrigger, Property, TriggerStrategy } from '@activepieces/pieces-framework'; +import { callSevenApi } from '../common'; +import { sevenAuth } from '../index'; +import { HttpMethod } from '@activepieces/pieces-common'; + +interface SubscribeHookResponse { + id: number | null; + success: boolean; +} + +interface UnsubscribeHookResponse { + success: boolean; +} + +interface SevenWebhookInformation { + webhookId: number; +} + +const triggerNameInStore = 'seven_new_sms_trigger'; + +export const smsInbound = createTrigger({ + auth: sevenAuth, + description: 'Triggers when a new SMS message is received', + displayName: 'New Incoming SMS', + name: 'new_incoming_sms', + props: { + from: Property.ShortText({ + displayName: 'Phone Number', + description: 'Optionally limit inbound SMS to this particular phone number.', + required: false + }) + }, + sampleData: { + data: { + id: '681590', + sender: 'SMS', + system: '491771783130', + text: 'Hello. I am an example for demonstrating a webhook payload.', + time: '1605878104' + }, + webhook_event: 'sms_mo', + webhook_timestamp: '2020-12-02 11:55:44' + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const { from = '' } = context.propsValue; + const { body } = await callSevenApi({ + body: { + event_filter: from, + event_type: 'sms_mo', + target_url: context.webhookUrl + }, + method: HttpMethod.POST + }, 'hooks', context.auth as string); + + if (!body.success) return; + + await context.store?.put(triggerNameInStore, { + webhookId: body.id! + }); + }, + async onDisable(context) { + const info = await context.store?.get(triggerNameInStore); + if (!info) return; + + const { body } = await callSevenApi({ + body: { + action: 'unsubscribe', + id: info.webhookId + }, + method: HttpMethod.POST + }, 'hooks', context.auth as string); + + if (!body.success) return; + + await context.store.put(triggerNameInStore, null); + }, + async run(context) { + return [context.payload.body]; + } +}); diff --git a/packages/pieces/community/seven/tsconfig.json b/packages/pieces/community/seven/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/seven/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/seven/tsconfig.lib.json b/packages/pieces/community/seven/tsconfig.lib.json new file mode 100644 index 0000000..de0831d --- /dev/null +++ b/packages/pieces/community/seven/tsconfig.lib.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": [ + "node" + ] + }, + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts" + ], + "include": [ + "src/**/*.ts" + ] +} diff --git a/packages/pieces/community/sftp/.eslintrc.json b/packages/pieces/community/sftp/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/sftp/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/sftp/README.md b/packages/pieces/community/sftp/README.md new file mode 100644 index 0000000..859cf8c --- /dev/null +++ b/packages/pieces/community/sftp/README.md @@ -0,0 +1,7 @@ +# pieces-sftp + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-sftp` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/sftp/package.json b/packages/pieces/community/sftp/package.json new file mode 100644 index 0000000..a838bae --- /dev/null +++ b/packages/pieces/community/sftp/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-sftp", + "version": "0.3.1" +} diff --git a/packages/pieces/community/sftp/project.json b/packages/pieces/community/sftp/project.json new file mode 100644 index 0000000..11920f7 --- /dev/null +++ b/packages/pieces/community/sftp/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-sftp", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/sftp/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/sftp", + "tsConfig": "packages/pieces/community/sftp/tsconfig.lib.json", + "packageJson": "packages/pieces/community/sftp/package.json", + "main": "packages/pieces/community/sftp/src/index.ts", + "assets": [ + "packages/pieces/community/sftp/*.md", + { + "input": "packages/pieces/community/sftp/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/sftp/src/index.ts b/packages/pieces/community/sftp/src/index.ts new file mode 100644 index 0000000..60c5895 --- /dev/null +++ b/packages/pieces/community/sftp/src/index.ts @@ -0,0 +1,162 @@ +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { isNil, PieceCategory } from '@activepieces/shared'; +import Client from 'ssh2-sftp-client'; +import { Client as FTPClient } from 'basic-ftp'; +import { createFile } from './lib/actions/create-file'; +import { uploadFileAction } from './lib/actions/upload-file'; +import { readFileContent } from './lib/actions/read-file'; +import { newOrModifiedFile } from './lib/triggers/new-modified-file'; +import { deleteFolderAction } from './lib/actions/delete-folder'; +import { deleteFileAction } from './lib/actions/delete-file'; +import { listFolderContentsAction } from './lib/actions/list-files'; +import { createFolderAction } from './lib/actions/create-folder'; +import { renameFileOrFolderAction } from './lib/actions/rename-file-or-folder'; + +export async function getProtocolBackwardCompatibility(protocol: string | undefined) { + if (isNil(protocol)) { + return 'sftp'; + } + return protocol; +} +export async function getClient(auth: { protocol: string | undefined, host: string, port: number, allow_unauthorized_certificates: boolean | undefined, username: string, password: string }) { + const { protocol, host, port, allow_unauthorized_certificates, username, password } = auth; + const protocolBackwardCompatibility = await getProtocolBackwardCompatibility(protocol); + if (protocolBackwardCompatibility === 'sftp') { + const sftp = new Client(); + await sftp.connect({ + host, + port, + username, + password, + timeout: 10000, + }); + return sftp as T; + } else { + const ftpClient = new FTPClient(); + await ftpClient.access({ + host, + port, + user: username, + password, + secure: protocolBackwardCompatibility === 'ftps', + secureOptions: { + rejectUnauthorized: !(allow_unauthorized_certificates ?? false), + } + }); + return ftpClient as T; + } +} + +export async function endClient(client: Client | FTPClient, protocol: string | undefined) { + const protocolBackwardCompatibility = await getProtocolBackwardCompatibility(protocol); + if (protocolBackwardCompatibility === 'sftp') { + await (client as Client).end(); + } else { + (client as FTPClient).close(); + } +} + +export const sftpAuth = PieceAuth.CustomAuth({ + props: { + protocol: Property.StaticDropdown({ + displayName: 'Protocol', + description: 'The protocol to use', + required: false, + options: { + options: [ + { value: 'sftp', label: 'SFTP' }, + { value: 'ftp', label: 'FTP' }, + { value: 'ftps', label: 'FTPS' } + ], + }, + }), + allow_unauthorized_certificates: Property.Checkbox({ + displayName: 'Allow Unauthorized Certificates', + description: + 'Allow connections to servers with self-signed certificates', + defaultValue: false, + required: false, + }), + host: Property.ShortText({ + displayName: 'Host', + description: 'The host of the server', + required: true, + }), + port: Property.Number({ + displayName: 'Port', + description: 'The port of the server', + required: true, + defaultValue: 22, + }), + username: Property.ShortText({ + displayName: 'Username', + description: 'The username to authenticate with', + required: true, + }), + password: PieceAuth.SecretText({ + displayName: 'Password', + description: 'The password to authenticate with', + required: true, + }), + }, + validate: async ({ auth }) => { + let client: Client | FTPClient | null = null; + const protocolBackwardCompatibility = await getProtocolBackwardCompatibility(auth.protocol); + try { + switch (protocolBackwardCompatibility) { + case 'sftp': { + client = await getClient(auth); + break; + } + default: { + client = await getClient(auth); + break; + } + } + return { + valid: true, + }; + } catch (err) { + return { + valid: false, + error: (err as Error)?.message, + }; + } finally { + if (client) { + await endClient(client, auth.protocol); + } + } + }, + required: true, +}); + +export const ftpSftp = createPiece({ + displayName: 'FTP/SFTP', + description: 'Connect to FTP, FTPS or SFTP servers', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/sftp.svg', + categories: [PieceCategory.CORE, PieceCategory.DEVELOPER_TOOLS], + authors: [ + 'Abdallah-Alwarawreh', + 'kishanprmr', + 'AbdulTheActivePiecer', + 'khaledmashaly', + 'abuaboud', + ], + auth: sftpAuth, + actions: [ + createFile, + uploadFileAction, + readFileContent, + deleteFileAction, + createFolderAction, + deleteFolderAction, + listFolderContentsAction, + renameFileOrFolderAction, + ], + triggers: [newOrModifiedFile], +}); diff --git a/packages/pieces/community/sftp/src/lib/actions/create-file.ts b/packages/pieces/community/sftp/src/lib/actions/create-file.ts new file mode 100644 index 0000000..64e8d3c --- /dev/null +++ b/packages/pieces/community/sftp/src/lib/actions/create-file.ts @@ -0,0 +1,74 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import Client from 'ssh2-sftp-client'; +import { Client as FTPClient } from 'basic-ftp'; +import { endClient, getClient, getProtocolBackwardCompatibility, sftpAuth } from '../..'; +import { Readable } from 'stream'; + +async function createFileWithSFTP(client: Client, fileName: string, fileContent: string) { + const remotePathExists = await client.exists(fileName); + if (!remotePathExists) { + // Extract the directory path from the fileName + const remoteDirectory = fileName.substring(0, fileName.lastIndexOf('/')); + // Create the directory if it doesn't exist + await client.mkdir(remoteDirectory, true); + } + await client.put(Buffer.from(fileContent), fileName); +} + +async function createFileWithFTP(client: FTPClient, fileName: string, fileContent: string) { + // Extract the directory path from the fileName + const remoteDirectory = fileName.substring(0, fileName.lastIndexOf('/')); + // Create the directory if it doesn't exist + await client.ensureDir(remoteDirectory); + // Upload the file content + const buffer = Buffer.from(fileContent); + await client.uploadFrom(Readable.from(buffer), fileName); +} + +export const createFile = createAction({ + auth: sftpAuth, + name: 'create_file', + displayName: 'Create File from Text', + description: 'Create a new file in the given path', + props: { + fileName: Property.ShortText({ + displayName: 'File Path', + required: true, + }), + fileContent: Property.LongText({ + displayName: 'File content', + required: true, + }), + }, + async run(context) { + const fileName = context.propsValue['fileName']; + const fileContent = context.propsValue['fileContent']; + const protocolBackwardCompatibility = await getProtocolBackwardCompatibility(context.auth.protocol); + const client = await getClient(context.auth); + + try { + switch (protocolBackwardCompatibility) { + case 'ftps': + case 'ftp': + await createFileWithFTP(client as FTPClient, fileName, fileContent); + break; + default: + case 'sftp': + await createFileWithSFTP(client as Client, fileName, fileContent); + break; + } + + return { + status: 'success', + }; + } catch (err) { + console.error(err); + return { + status: 'error', + error: err, + }; + } finally { + await endClient(client, context.auth.protocol); + } + }, +}); diff --git a/packages/pieces/community/sftp/src/lib/actions/create-folder.ts b/packages/pieces/community/sftp/src/lib/actions/create-folder.ts new file mode 100644 index 0000000..d59957c --- /dev/null +++ b/packages/pieces/community/sftp/src/lib/actions/create-folder.ts @@ -0,0 +1,53 @@ + import { endClient, getClient, getProtocolBackwardCompatibility, sftpAuth } from '../../index'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import Client from 'ssh2-sftp-client'; +import { Client as FTPClient } from 'basic-ftp'; + +export const createFolderAction = createAction({ + auth: sftpAuth, + name: 'createFolder', + displayName: 'Create Folder', + description: 'Creates a folder at given path.', + props: { + folderPath: Property.ShortText({ + displayName: 'Folder Path', + required: true, + description: 'The new folder path e.g. `./myfolder`. For FTP/FTPS, it will create nested folders if necessary.', + }), + recursive: Property.Checkbox({ + displayName: 'Recursive', + defaultValue: false, + required: false, + description: 'For SFTP only: Create parent directories if they do not exist', + }), + }, + async run(context) { + const client = await getClient(context.auth); + const directoryPath = context.propsValue.folderPath; + const recursive = context.propsValue.recursive ?? false; + const protocolBackwardCompatibility = await getProtocolBackwardCompatibility(context.auth.protocol); + try { + switch (protocolBackwardCompatibility) { + case 'ftps': + case 'ftp': + await (client as FTPClient).ensureDir(directoryPath); + break; + default: + case 'sftp': + await (client as Client).mkdir(directoryPath, recursive); + break; + } + + return { + status: 'success', + }; + } catch (err) { + return { + status: 'error', + error: err, + }; + } finally { + await endClient(client, context.auth.protocol); + } + }, +}); diff --git a/packages/pieces/community/sftp/src/lib/actions/delete-file.ts b/packages/pieces/community/sftp/src/lib/actions/delete-file.ts new file mode 100644 index 0000000..42384c2 --- /dev/null +++ b/packages/pieces/community/sftp/src/lib/actions/delete-file.ts @@ -0,0 +1,55 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { endClient, getClient, getProtocolBackwardCompatibility, sftpAuth } from '../..'; +import { Client as FTPClient } from 'basic-ftp'; +import Client from 'ssh2-sftp-client'; + +async function deleteFileFromFTP(client: FTPClient, filePath: string) { + await client.remove(filePath); +} + +async function deleteFileFromSFTP(client: Client, filePath: string) { + await client.delete(filePath); +} + +export const deleteFileAction = createAction({ + auth: sftpAuth, + name: 'deleteFile', + displayName: 'Delete file', + description: 'Deletes a file at given path.', + props: { + filePath: Property.ShortText({ + displayName: 'File Path', + required: true, + description: 'The path of the file to delete e.g. `./myfolder/test.mp3`', + }), + }, + async run(context) { + const client = await getClient(context.auth); + const filePath = context.propsValue.filePath; + const protocolBackwardCompatibility = await getProtocolBackwardCompatibility(context.auth.protocol); + try { + switch (protocolBackwardCompatibility) { + case 'ftps': + case 'ftp': + await deleteFileFromFTP(client as FTPClient, filePath); + break; + default: + case 'sftp': + await deleteFileFromSFTP(client as Client, filePath); + break; + } + + return { + status: 'success', + }; + } catch (err) { + console.error(err); + return { + status: 'error', + error: err, + }; + } finally { + await endClient(client, context.auth.protocol); + } + }, +}); diff --git a/packages/pieces/community/sftp/src/lib/actions/delete-folder.ts b/packages/pieces/community/sftp/src/lib/actions/delete-folder.ts new file mode 100644 index 0000000..0992194 --- /dev/null +++ b/packages/pieces/community/sftp/src/lib/actions/delete-folder.ts @@ -0,0 +1,67 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import Client from 'ssh2-sftp-client'; +import { Client as FTPClient } from 'basic-ftp'; +import { endClient, getClient, getProtocolBackwardCompatibility, sftpAuth } from '../..'; + +async function deleteFolderFTP(client: FTPClient, directoryPath: string, recursive: boolean) { + if (recursive) { + await client.removeDir(directoryPath); + } else { + await client.removeEmptyDir(directoryPath); + } +} + +async function deleteFolderSFTP(client: Client, directoryPath: string, recursive: boolean) { + await client.rmdir(directoryPath, recursive); +} + +export const deleteFolderAction = createAction({ + auth: sftpAuth, + name: 'deleteFolder', + displayName: 'Delete Folder', + description: 'Deletes an existing folder at given path.', + props: { + folderPath: Property.ShortText({ + displayName: 'Folder Path', + required: true, + description: 'The path of the folder to delete e.g. `./myfolder`', + }), + recursive: Property.Checkbox({ + displayName: 'Recursive', + defaultValue: false, + required: false, + description: + 'Enable this option to delete the folder and all its contents, including subfolders and files.', + }), + }, + async run(context) { + const client = await getClient(context.auth); + const directoryPath = context.propsValue.folderPath; + const recursive = context.propsValue.recursive ?? false; + const protocolBackwardCompatibility = await getProtocolBackwardCompatibility(context.auth.protocol); + try { + switch (protocolBackwardCompatibility) { + case 'ftps': + case 'ftp': + await deleteFolderFTP(client as FTPClient, directoryPath, recursive); + break; + default: + case 'sftp': + await deleteFolderSFTP(client as Client, directoryPath, recursive); + break; + } + + return { + status: 'success', + }; + } catch (err) { + console.error(err); + return { + status: 'error', + error: err, + }; + } finally { + await endClient(client, context.auth.protocol); + } + }, +}); diff --git a/packages/pieces/community/sftp/src/lib/actions/list-files.ts b/packages/pieces/community/sftp/src/lib/actions/list-files.ts new file mode 100644 index 0000000..41d41fa --- /dev/null +++ b/packages/pieces/community/sftp/src/lib/actions/list-files.ts @@ -0,0 +1,74 @@ +import { endClient, sftpAuth } from '../../index'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import Client from 'ssh2-sftp-client'; +import { Client as FTPClient } from 'basic-ftp'; +import { getClient, getProtocolBackwardCompatibility } from '../..'; + +async function listSFTP(client: Client, directoryPath: string) { + const contents = await client.list(directoryPath); + await client.end(); + return contents; +} + +async function listFTP(client: FTPClient, directoryPath: string) { + const contents = await client.list(directoryPath); + return contents.map(item => ({ + type: item.type === 1 ? 'd' : '-', + name: item.name, + size: item.size, + modifyTime: item.modifiedAt, + accessTime: item.modifiedAt, // FTP doesn't provide access time + rights: { + user: item.permissions || '', + group: '', + other: '' + }, + owner: '', + group: '' + })); +} + +export const listFolderContentsAction = createAction({ + auth: sftpAuth, + name: 'listFolderContents', + displayName: 'List Folder Contents', + description: 'Lists the contents of a given folder.', + props: { + directoryPath: Property.ShortText({ + displayName: 'Directory Path', + required: true, + description: 'The path of the folder to list e.g. `./myfolder`', + }), + }, + async run(context) { + const client = await getClient(context.auth); + const directoryPath = context.propsValue.directoryPath; + const protocolBackwardCompatibility = await getProtocolBackwardCompatibility(context.auth.protocol); + try { + let contents; + switch (protocolBackwardCompatibility) { + case 'ftps': + case 'ftp': + contents = await listFTP(client as FTPClient, directoryPath); + break; + default: + case 'sftp': + contents = await listSFTP(client as Client, directoryPath); + break; + } + + return { + status: 'success', + contents: contents, + }; + } catch (err) { + return { + status: 'error', + contents: null, + error: err, + }; + } finally { + await endClient(client, context.auth.protocol); + } + }, +}); diff --git a/packages/pieces/community/sftp/src/lib/actions/read-file.ts b/packages/pieces/community/sftp/src/lib/actions/read-file.ts new file mode 100644 index 0000000..0b76cc8 --- /dev/null +++ b/packages/pieces/community/sftp/src/lib/actions/read-file.ts @@ -0,0 +1,70 @@ +import { endClient, sftpAuth } from '../../index'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import Client from 'ssh2-sftp-client'; +import { Client as FTPClient } from 'basic-ftp'; +import { getClient, getProtocolBackwardCompatibility } from '../..'; +import { Writable } from 'stream'; + +async function readFTP(client: FTPClient, filePath: string) { + const chunks: Buffer[] = []; + const writeStream = new Writable({ + write(chunk: Buffer, _encoding: string, callback: () => void) { + chunks.push(chunk); + callback(); + } + }); + await client.downloadTo(writeStream, filePath); + return Buffer.concat(chunks); +} + +async function readSFTP(client: Client, filePath: string) { + const fileContent = await client.get(filePath); + await client.end(); + return fileContent as Buffer; +} + +export const readFileContent = createAction({ + auth: sftpAuth, + name: 'read_file_content', + displayName: 'Read File Content', + description: 'Read the content of a file.', + props: { + filePath: Property.ShortText({ + displayName: 'File Path', + required: true, + }), + }, + async run(context) { + const client = await getClient(context.auth); + const filePath = context.propsValue['filePath']; + const fileName = filePath.split('/').pop() ?? filePath; + const protocolBackwardCompatibility = await getProtocolBackwardCompatibility(context.auth.protocol); + try { + let fileContent: Buffer; + switch (protocolBackwardCompatibility) { + case 'ftps': + case 'ftp': + fileContent = await readFTP(client as FTPClient, filePath); + break; + default: + case 'sftp': + fileContent = await readSFTP(client as Client, filePath); + break; + } + + return { + file: await context.files.write({ + fileName: fileName, + data: fileContent, + }), + }; + } catch (err) { + return { + success: false, + error: err, + }; + } finally { + await endClient(client, context.auth.protocol); + } + }, +}); diff --git a/packages/pieces/community/sftp/src/lib/actions/rename-file-or-folder.ts b/packages/pieces/community/sftp/src/lib/actions/rename-file-or-folder.ts new file mode 100644 index 0000000..9926da8 --- /dev/null +++ b/packages/pieces/community/sftp/src/lib/actions/rename-file-or-folder.ts @@ -0,0 +1,69 @@ +import { endClient, sftpAuth } from '../../index'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import Client from 'ssh2-sftp-client'; +import { Client as FTPClient } from 'basic-ftp'; +import { getClient, getProtocolBackwardCompatibility } from '../..'; +import { MarkdownVariant } from '@activepieces/shared'; + +async function renameFTP(client: FTPClient, oldPath: string, newPath: string) { + await client.rename(oldPath, newPath); +} + +async function renameSFTP(client: Client, oldPath: string, newPath: string) { + await client.rename(oldPath, newPath); + await client.end(); +} + +export const renameFileOrFolderAction = createAction({ + auth: sftpAuth, + name: 'renameFileOrFolder', + displayName: 'Rename File or Folder', + description: 'Renames a file or folder at given path.', + props: { + information: Property.MarkDown({ + value: 'Depending on the server you can also use this to move a file to another directory, as long as the directory exists.', + variant: MarkdownVariant.INFO, + }), + oldPath: Property.ShortText({ + displayName: 'Old Path', + required: true, + description: + 'The path of the file or folder to rename e.g. `./myfolder/test.mp3`', + }), + newPath: Property.ShortText({ + displayName: 'New Path', + required: true, + description: + 'The new path of the file or folder e.g. `./myfolder/new-name.mp3`', + }), + }, + async run(context) { + const client = await getClient(context.auth); + const oldPath = context.propsValue.oldPath; + const newPath = context.propsValue.newPath; + const protocolBackwardCompatibility = await getProtocolBackwardCompatibility(context.auth.protocol); + try { + switch (protocolBackwardCompatibility) { + case 'ftps': + case 'ftp': + await renameFTP(client as FTPClient, oldPath, newPath); + break; + default: + case 'sftp': + await renameSFTP(client as Client, oldPath, newPath); + break; + } + + return { + status: 'success', + }; + } catch (err) { + return { + status: 'error', + error: err, + }; + } finally { + await endClient(client, context.auth.protocol); + } + }, +}); diff --git a/packages/pieces/community/sftp/src/lib/actions/upload-file.ts b/packages/pieces/community/sftp/src/lib/actions/upload-file.ts new file mode 100644 index 0000000..96332d8 --- /dev/null +++ b/packages/pieces/community/sftp/src/lib/actions/upload-file.ts @@ -0,0 +1,68 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import Client from 'ssh2-sftp-client'; +import { Client as FTPClient } from 'basic-ftp'; +import { endClient, getClient, getProtocolBackwardCompatibility, sftpAuth } from '../..'; +import { Readable } from 'stream'; + +async function uploadFileToFTP(client: FTPClient, fileName: string, fileContent: { data: any }) { + const remoteDirectory = fileName.substring(0, fileName.lastIndexOf('/')); + await client.ensureDir(remoteDirectory); + await client.uploadFrom(Readable.from(fileContent.data), fileName); +} + +async function uploadFileToSFTP(client: Client, fileName: string, fileContent: { data: any }) { + const remotePathExists = await client.exists(fileName); + if (!remotePathExists) { + const remoteDirectory = fileName.substring(0, fileName.lastIndexOf('/')); + await client.mkdir(remoteDirectory, true); + } + await client.put(fileContent.data, fileName); + await client.end(); +} + +export const uploadFileAction = createAction({ + auth: sftpAuth, + name: 'upload_file', + displayName: 'Upload File', + description: 'Upload a file to the given path.', + props: { + fileName: Property.ShortText({ + displayName: 'File Path', + required: true, + description: + 'The path on the sftp server to store the file. e.g. `./myfolder/test.mp3`', + }), + fileContent: Property.File({ + displayName: 'File content', + required: true, + }), + }, + async run(context) { + const client = await getClient(context.auth); + const fileName = context.propsValue['fileName']; + const fileContent = context.propsValue['fileContent']; + const protocolBackwardCompatibility = await getProtocolBackwardCompatibility(context.auth.protocol); + try { + switch (protocolBackwardCompatibility) { + case 'ftps': + case 'ftp': + await uploadFileToFTP(client as FTPClient, fileName, fileContent); + break; + default: + case 'sftp': + await uploadFileToSFTP(client as Client, fileName, fileContent); + break; + } + return { + status: 'success', + }; + } catch (error) { + console.error(error); + return { + status: 'error', + }; + } finally { + await endClient(client, context.auth.protocol); + } + }, +}); diff --git a/packages/pieces/community/sftp/src/lib/triggers/new-modified-file.ts b/packages/pieces/community/sftp/src/lib/triggers/new-modified-file.ts new file mode 100644 index 0000000..222f0fb --- /dev/null +++ b/packages/pieces/community/sftp/src/lib/triggers/new-modified-file.ts @@ -0,0 +1,86 @@ +import { PiecePropValueSchema, Property, createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { sftpAuth, getClient, getProtocolBackwardCompatibility, endClient } from '../..'; +import dayjs from 'dayjs'; +import Client from 'ssh2-sftp-client'; +import { Client as FTPClient, FileInfo as FTPFileInfo } from 'basic-ftp'; + +function getModifyTime(file: Client.FileInfo | FTPFileInfo, protocol: string): number { + return protocol === 'sftp' ? + (file as Client.FileInfo).modifyTime : + dayjs((file as FTPFileInfo).modifiedAt).valueOf(); +} + +const polling: Polling, { path: string; ignoreHiddenFiles?: boolean }> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + let client: Client | FTPClient | null = null; + try { + const protocolBackwardCompatibility = await getProtocolBackwardCompatibility(auth.protocol); + client = await getClient(auth); + const files = await client.list(propsValue.path); + + const filteredFiles = files.filter(file => { + const modTime = getModifyTime(file, protocolBackwardCompatibility); + return dayjs(modTime).valueOf() > lastFetchEpochMS; + }); + + const finalFiles: (Client.FileInfo | FTPFileInfo)[] = propsValue.ignoreHiddenFiles ? + filteredFiles.filter(file => !file.name.startsWith('.')) : + filteredFiles; + + return finalFiles.map(file => { + const modTime = getModifyTime(file, protocolBackwardCompatibility); + + return { + data: { + ...file, + path: `${propsValue.path}/${file.name}`, + }, + epochMilliSeconds: dayjs(modTime).valueOf(), + }; + }); + } catch (err) { + return []; + } finally { + if (client) { + await endClient(client, auth.protocol); + } + } + }, +}; + +export const newOrModifiedFile = createTrigger({ + auth: sftpAuth, + name: 'new_file', + displayName: 'New File', + description: 'Trigger when a new file is created or modified.', + props: { + path: Property.ShortText({ + displayName: 'Path', + description: 'The path to watch for new files', + required: true, + defaultValue: './', + }), + ignoreHiddenFiles: Property.Checkbox({ + displayName: 'Ignore hidden files', + description: 'Ignore hidden files', + required: false, + defaultValue: false, + }), + }, + type: TriggerStrategy.POLLING, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, context); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, context); + }, + run: async (context) => { + return await pollingHelper.poll(polling, context); + }, + test: async (context) => { + return await pollingHelper.test(polling, context); + }, + sampleData: null, +}); diff --git a/packages/pieces/community/sftp/tsconfig.json b/packages/pieces/community/sftp/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/sftp/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/sftp/tsconfig.lib.json b/packages/pieces/community/sftp/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/sftp/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/shopify/.eslintrc.json b/packages/pieces/community/shopify/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/shopify/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/shopify/README.md b/packages/pieces/community/shopify/README.md new file mode 100644 index 0000000..0f489bd --- /dev/null +++ b/packages/pieces/community/shopify/README.md @@ -0,0 +1,7 @@ +# pieces-shopify + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-shopify` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/shopify/package.json b/packages/pieces/community/shopify/package.json new file mode 100644 index 0000000..1267fe2 --- /dev/null +++ b/packages/pieces/community/shopify/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-shopify", + "version": "0.1.12" +} \ No newline at end of file diff --git a/packages/pieces/community/shopify/project.json b/packages/pieces/community/shopify/project.json new file mode 100644 index 0000000..9303e8c --- /dev/null +++ b/packages/pieces/community/shopify/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-shopify", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/shopify/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/shopify", + "tsConfig": "packages/pieces/community/shopify/tsconfig.lib.json", + "packageJson": "packages/pieces/community/shopify/package.json", + "main": "packages/pieces/community/shopify/src/index.ts", + "assets": [ + "packages/pieces/community/shopify/*.md", + { + "input": "packages/pieces/community/shopify/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/shopify/src/index.ts b/packages/pieces/community/shopify/src/index.ts new file mode 100644 index 0000000..298060b --- /dev/null +++ b/packages/pieces/community/shopify/src/index.ts @@ -0,0 +1,150 @@ +import { + HttpMethod, + createCustomApiCallAction, +} from '@activepieces/pieces-common'; +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { adjustInventoryLevelAction } from './lib/actions/adjust-inventory-level'; +import { cancelOrderAction } from './lib/actions/cancel-order'; +import { closeOrderAction } from './lib/actions/close-order'; +import { createCollectAction } from './lib/actions/create-collect'; +import { createCustomerAction } from './lib/actions/create-customer'; +import { createDraftOrderAction } from './lib/actions/create-draft-order'; +import { createFulfillmentEventAction } from './lib/actions/create-fulfillment-event'; +import { createOrderAction } from './lib/actions/create-order'; +import { createProductAction } from './lib/actions/create-product'; +import { createTransactionAction } from './lib/actions/create-transaction'; +import { getAssetAction } from './lib/actions/get-asset'; +import { getCustomerAction } from './lib/actions/get-customer'; +import { getCustomersAction } from './lib/actions/get-customers'; +import { getCustomerOrdersAction } from './lib/actions/get-customer-orders'; +import { getFulfillmentAction } from './lib/actions/get-fulfillment'; +import { getFulfillmentsAction } from './lib/actions/get-fulfillments'; +import { getLocationsAction } from './lib/actions/get-locations'; +import { getProductAction } from './lib/actions/get-product'; +import { getProductVariantAction } from './lib/actions/get-product-variant'; +import { getProductsAction } from './lib/actions/get-products'; +import { getTransactionAction } from './lib/actions/get-transaction'; +import { getTransactionsAction } from './lib/actions/get-transactions'; +import { updateCustomerAction } from './lib/actions/update-customer'; +import { updateOrderAction } from './lib/actions/update-order'; +import { updateProductAction } from './lib/actions/update-product'; +import { uploadProductImageAction } from './lib/actions/upload-product-image'; +import { getBaseUrl, sendShopifyRequest } from './lib/common'; +import { newAbandonedCheckout } from './lib/triggers/new-abandoned-checkout'; +import { newCancelledOrder } from './lib/triggers/new-cancelled-order'; +import { newCustomer } from './lib/triggers/new-customer'; +import { newOrder } from './lib/triggers/new-order'; +import { newPaidOrder } from './lib/triggers/new-paid-order'; +import { updatedProduct } from './lib/triggers/updated-product'; + +const markdown = ` +**Shop Name**: + +You can find your shop name in the url For example, if the URL is \`https://example.myshopify.com/admin\`, then your shop name is **example**. + +**Admin Token**: + +1. Login to your Shopify account +2. Go to Settings -> Apps +3. Click on Develop apps +4. Create an App +5. Fill the app name +6. Click on Configure Admin API Scopes (Select the following scopes 'read_orders', 'write_orders', 'write_customers', 'read_customers', 'write_products', 'read_products', 'write_draft_orders', 'read_draft_orders') +7. Click on Install app +8. Copy the Admin Access Token +`; + +export const shopifyAuth = PieceAuth.CustomAuth({ + description: markdown, + required: true, + props: { + shopName: Property.ShortText({ + displayName: 'Shop Name', + required: true, + }), + adminToken: PieceAuth.SecretText({ + displayName: 'Admin Token', + required: true, + }), + }, + validate: async ({ auth }) => { + try { + await sendShopifyRequest({ + auth, + method: HttpMethod.GET, + url: '/shop.json', + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid Shop Name or Admin Token', + }; + } + }, +}); + +export const shopify = createPiece({ + displayName: 'Shopify', + description: 'Ecommerce platform for online stores', + logoUrl: 'https://cdn.activepieces.com/pieces/shopify.png', + authors: ["kishanprmr","MoShizzle","AbdulTheActivePiecer","khaledmashaly","abuaboud","ikus060"], + categories: [PieceCategory.COMMERCE], + minimumSupportedRelease: '0.30.0', + auth: shopifyAuth, + actions: [ + adjustInventoryLevelAction, + cancelOrderAction, + closeOrderAction, + createCollectAction, + createCustomerAction, + createDraftOrderAction, + createFulfillmentEventAction, + createOrderAction, + createProductAction, + createTransactionAction, + getAssetAction, + getCustomerAction, + getCustomersAction, + getCustomerOrdersAction, + getFulfillmentAction, + getFulfillmentsAction, + getLocationsAction, + getProductAction, + getProductVariantAction, + getProductsAction, + getTransactionAction, + getTransactionsAction, + updateCustomerAction, + updateOrderAction, + updateProductAction, + uploadProductImageAction, + createCustomApiCallAction({ + baseUrl: (auth) => { + return getBaseUrl((auth as { shopName: string }).shopName); + }, + auth: shopifyAuth, + authMapping: async (auth) => { + const typedAuth = auth as { adminToken: string }; + return { + 'X-Shopify-Access-Token': typedAuth.adminToken, + }; + }, + }), + ], + triggers: [ + newAbandonedCheckout, + newCancelledOrder, + newCustomer, + newOrder, + updatedProduct, + newPaidOrder, + ], +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/adjust-inventory-level.ts b/packages/pieces/community/shopify/src/lib/actions/adjust-inventory-level.ts new file mode 100644 index 0000000..12e01d8 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/adjust-inventory-level.ts @@ -0,0 +1,33 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { adjustInventoryLevel } from '../common'; + +export const adjustInventoryLevelAction = createAction({ + auth: shopifyAuth, + name: 'adjust_inventory_level', + displayName: 'Adjust Inventory Level', + description: `Adjust inventory level of an item at a location.`, + props: { + id: Property.Number({ + displayName: 'Inventory Item', + description: 'The ID of the inventory item.', + required: true, + }), + locationId: Property.Number({ + displayName: 'Location', + description: 'The ID of the location.', + required: true, + }), + adjustment: Property.Number({ + displayName: 'Adjustment', + description: + 'Positive values increase inventory, negative values decrease it.', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { id, locationId, adjustment } = propsValue; + + return await adjustInventoryLevel(id, locationId, adjustment, auth); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/cancel-order.ts b/packages/pieces/community/shopify/src/lib/actions/cancel-order.ts new file mode 100644 index 0000000..a81f9fd --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/cancel-order.ts @@ -0,0 +1,22 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { cancelOrder } from '../common'; + +export const cancelOrderAction = createAction({ + auth: shopifyAuth, + name: 'cancel_order', + displayName: 'Cancel Order', + description: `Cancel an order.`, + props: { + orderId: Property.Number({ + displayName: 'Order', + description: 'The ID of the order.', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { orderId } = propsValue; + + return await cancelOrder(orderId, auth); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/close-order.ts b/packages/pieces/community/shopify/src/lib/actions/close-order.ts new file mode 100644 index 0000000..9c0da5b --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/close-order.ts @@ -0,0 +1,22 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { closeOrder } from '../common'; + +export const closeOrderAction = createAction({ + auth: shopifyAuth, + name: 'close_order', + displayName: 'Close Order', + description: `Close an order.`, + props: { + orderId: Property.Number({ + displayName: 'Order', + description: 'The ID of the order.', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { orderId } = propsValue; + + return await closeOrder(orderId, auth); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/create-collect.ts b/packages/pieces/community/shopify/src/lib/actions/create-collect.ts new file mode 100644 index 0000000..4e4cd9b --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/create-collect.ts @@ -0,0 +1,33 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { createCollect } from '../common'; + +export const createCollectAction = createAction({ + auth: shopifyAuth, + name: 'create_collect', + displayName: 'Create Collect', + description: `Add a product to a collection.`, + props: { + id: Property.Number({ + displayName: 'Product', + description: 'The ID of the product.', + required: true, + }), + collectionId: Property.Number({ + displayName: 'Collection', + description: 'The ID of the collection.', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { id, collectionId } = propsValue; + + return await createCollect( + { + product_id: id, + collection_id: collectionId, + }, + auth + ); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/create-customer.ts b/packages/pieces/community/shopify/src/lib/actions/create-customer.ts new file mode 100644 index 0000000..b4c2ea9 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/create-customer.ts @@ -0,0 +1,71 @@ +import { + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { createCustomer } from '../common'; + +export const createCustomerAction = createAction({ + auth: shopifyAuth, + name: 'create_customer', + displayName: 'Create Customer', + description: 'Create a new customer.', + props: { + email: Property.ShortText({ + displayName: 'Email', + required: false, + }), + verifiedEmail: Property.Checkbox({ + displayName: 'Verified Email', + description: 'Whether the customer has verified their email.', + required: false, + defaultValue: true, + }), + sendEmailInvite: Property.Checkbox({ + displayName: 'Send Email Invite', + required: false, + defaultValue: false, + }), + firstName: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + phoneNumber: Property.ShortText({ + displayName: 'Phone Number', + required: false, + }), + tags: Property.ShortText({ + displayName: 'Tags', + description: 'A string of comma-separated tags for filtering and search', + required: false, + }), + }, + async run({ auth, propsValue }) { + const { + email, + verifiedEmail, + sendEmailInvite, + firstName, + lastName, + phoneNumber, + tags, + } = propsValue; + + return await createCustomer( + { + email, + verified_email: verifiedEmail, + send_email_invite: sendEmailInvite, + first_name: firstName, + last_name: lastName, + phone: phoneNumber, + tags, + }, + auth + ); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/create-draft-order.ts b/packages/pieces/community/shopify/src/lib/actions/create-draft-order.ts new file mode 100644 index 0000000..b7f3818 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/create-draft-order.ts @@ -0,0 +1,60 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { createDraftOrder } from '../common'; +import { ShopifyDraftOrder } from '../common/types'; + +export const createDraftOrderAction = createAction({ + auth: shopifyAuth, + name: 'create_draft_order', + displayName: 'Create Draft Order', + description: 'Create a new draft order.', + props: { + productId: Property.Number({ + displayName: 'Product', + description: 'The ID of the product to create the order with.', + required: false, + }), + variantId: Property.Number({ + displayName: 'Product Variant', + description: 'The ID of the variant to create the order with.', + required: false, + }), + title: Property.ShortText({ + displayName: 'Title', + required: false, + }), + quantity: Property.Number({ + displayName: 'Quantity', + required: false, + defaultValue: 1, + }), + price: Property.ShortText({ + displayName: 'Price', + required: false, + }), + customerId: Property.ShortText({ + displayName: 'Customer', + description: 'The ID of the customer to use.', + required: false, + }), + }, + async run({ auth, propsValue }) { + const { productId, variantId, title, quantity, price, customerId } = + propsValue; + + const draftOrder: Partial = { + line_items: [ + { + product_id: productId, + variant_id: variantId, + title, + quantity, + price, + }, + ], + }; + if (customerId) draftOrder.customer = { id: +customerId }; + + return await createDraftOrder(draftOrder, auth); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/create-fulfillment-event.ts b/packages/pieces/community/shopify/src/lib/actions/create-fulfillment-event.ts new file mode 100644 index 0000000..6b02a50 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/create-fulfillment-event.ts @@ -0,0 +1,59 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { createFulfillmentEvent } from '../common'; +import { ShopifyFulfillmentEventStatuses } from '../common/types'; + +export const createFulfillmentEventAction = createAction({ + auth: shopifyAuth, + name: 'create_fulfillment_event', + displayName: 'Create Fulfillment Event', + description: 'Create a new fulfillment event.', + props: { + orderId: Property.Number({ + displayName: 'Order', + description: 'The ID of the order.', + required: true, + }), + fulfillmentId: Property.Number({ + displayName: 'Fulfillment', + description: 'The ID of the fulfillment.', + required: true, + }), + status: Property.Dropdown({ + displayName: 'Status', + required: true, + refreshers: [], + options: async () => { + return { + options: Object.values(ShopifyFulfillmentEventStatuses).map( + (status) => { + return { + label: + status.charAt(0).toUpperCase() + + status.slice(1).replaceAll('_', ' '), + value: status, + }; + } + ), + }; + }, + }), + message: Property.ShortText({ + displayName: 'Message', + required: false, + }), + }, + async run({ auth, propsValue }) { + const { orderId, fulfillmentId, status, message } = propsValue; + + return await createFulfillmentEvent( + fulfillmentId, + orderId, + { + status, + message, + }, + auth + ); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/create-order.ts b/packages/pieces/community/shopify/src/lib/actions/create-order.ts new file mode 100644 index 0000000..c7a9bdf --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/create-order.ts @@ -0,0 +1,95 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { createOrder } from '../common'; +import { ShopifyOrder } from '../common/types'; + +export const createOrderAction = createAction({ + auth: shopifyAuth, + name: 'create_order', + displayName: 'Create Order', + description: 'Create a new order.', + props: { + productId: Property.Number({ + displayName: 'Product', + description: 'The ID of the product to create the order with.', + required: false, + }), + variantId: Property.Number({ + displayName: 'Product Variant', + description: 'The ID of the variant to create the order with.', + required: false, + }), + title: Property.ShortText({ + displayName: 'Title', + required: false, + }), + quantity: Property.Number({ + displayName: 'Quantity', + required: false, + defaultValue: 1, + }), + price: Property.ShortText({ + displayName: 'Price', + required: false, + }), + customerId: Property.ShortText({ + displayName: 'Customer', + description: 'The ID of the customer to use.', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + required: false, + }), + sendReceipt: Property.Checkbox({ + displayName: 'Send Receipt', + required: false, + defaultValue: false, + }), + sendFulfillmentReceipt: Property.Checkbox({ + displayName: 'Send Fulfillment Receipt', + required: false, + defaultValue: false, + }), + tags: Property.ShortText({ + displayName: 'Tags', + description: 'A string of comma-separated tags for filtering and search', + required: false, + }), + }, + async run({ auth, propsValue }) { + const { + productId, + variantId, + title, + quantity, + price, + customerId, + email, + sendReceipt, + sendFulfillmentReceipt, + tags, + } = propsValue; + + const order: Partial = { + line_items: [ + { + product_id: productId, + variant_id: variantId, + title, + quantity, + price, + }, + ], + }; + if (customerId) order.customer = { id: +customerId }; + if (email) { + order.email = email; + order.send_receipt = sendReceipt; + order.send_fulfillment_receipt = sendFulfillmentReceipt; + } + if (tags) order.tags = tags; + + return await createOrder(order, auth); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/create-product.ts b/packages/pieces/community/shopify/src/lib/actions/create-product.ts new file mode 100644 index 0000000..60bade1 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/create-product.ts @@ -0,0 +1,86 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { createProduct } from '../common'; +import { ShopifyImage, ShopifyProductStatuses } from '../common/types'; + +export const createProductAction = createAction({ + auth: shopifyAuth, + name: 'create_product', + displayName: 'Create Product', + description: 'Create a new product.', + props: { + title: Property.ShortText({ + displayName: 'Title', + required: true, + }), + bodyHtml: Property.LongText({ + displayName: 'Description', + description: 'Product description (supports HTML)', + required: false, + }), + productType: Property.ShortText({ + displayName: 'Product Type', + description: + 'A categorization for the product used for filtering and searching products', + required: false, + }), + productImage: Property.File({ + displayName: 'Product Image', + description: 'The public URL or the base64 image to use', + required: false, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + required: true, + defaultValue: ShopifyProductStatuses.DRAFT, + options: { + options: [ + { + label: 'Active', + value: ShopifyProductStatuses.ACTIVE, + }, + { + label: 'Draft', + value: ShopifyProductStatuses.DRAFT, + }, + { + label: 'Archived', + value: ShopifyProductStatuses.ARCHIVED, + }, + ], + }, + }), + vendor: Property.ShortText({ + displayName: 'Vendor', + required: false, + }), + tags: Property.ShortText({ + displayName: 'Tags', + description: 'A string of comma-separated tags for filtering and search', + required: false, + }), + }, + async run({ auth, propsValue }) { + const { title, bodyHtml, vendor, productType, tags, productImage } = + propsValue; + + const images: Partial[] = []; + if (productImage) { + images.push({ + attachment: productImage.base64, + }); + } + + return await createProduct( + { + title, + body_html: bodyHtml, + vendor, + product_type: productType, + tags, + images, + }, + auth + ); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/create-transaction.ts b/packages/pieces/community/shopify/src/lib/actions/create-transaction.ts new file mode 100644 index 0000000..30c5a5c --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/create-transaction.ts @@ -0,0 +1,79 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { createTransaction } from '../common'; +import { ShopifyTransactionKinds } from '../common/types'; + +export const createTransactionAction = createAction({ + auth: shopifyAuth, + name: 'create_transaction', + displayName: 'Create Transaction', + description: 'Create a new transaction.', + props: { + orderId: Property.Number({ + displayName: 'Order', + description: 'The ID of the order to create a transaction for.', + required: true, + }), + kind: Property.Dropdown({ + displayName: 'Type', + required: true, + refreshers: [], + options: async () => { + return { + options: Object.values(ShopifyTransactionKinds).map((kind) => { + return { + label: kind.charAt(0).toUpperCase() + kind.slice(1), + value: kind, + }; + }), + }; + }, + }), + currency: Property.ShortText({ + displayName: 'Currency', + required: false, + }), + amount: Property.ShortText({ + displayName: 'Amount', + required: false, + }), + authorization: Property.ShortText({ + displayName: 'Authorization Key', + required: false, + }), + parentId: Property.Number({ + displayName: 'Parent ID', + description: 'The ID of an associated transaction.', + required: false, + }), + source: Property.ShortText({ + displayName: 'Source', + description: + 'An optional origin of the transaction. Set to external to import a cash transaction for the associated order.', + required: false, + }), + test: Property.Checkbox({ + displayName: 'Test', + description: 'Whether the transaction is a test transaction.', + required: false, + defaultValue: false, + }), + }, + async run({ auth, propsValue }) { + const { orderId, kind, currency, amount, parentId, source, test } = + propsValue; + + return await createTransaction( + orderId, + { + amount, + currency, + kind, + parent_id: parentId, + source, + test, + }, + auth + ); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/get-asset.ts b/packages/pieces/community/shopify/src/lib/actions/get-asset.ts new file mode 100644 index 0000000..9694420 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/get-asset.ts @@ -0,0 +1,26 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { getAsset } from '../common'; + +export const getAssetAction = createAction({ + auth: shopifyAuth, + name: 'get_asset', + displayName: 'Get Asset', + description: `Get a theme's asset.`, + props: { + key: Property.ShortText({ + displayName: 'Asset Key', + required: true, + }), + themeId: Property.ShortText({ + displayName: 'Theme', + description: 'The ID of the theme.', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { key, themeId } = propsValue; + + return await getAsset(key, +themeId, auth); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/get-customer-orders.ts b/packages/pieces/community/shopify/src/lib/actions/get-customer-orders.ts new file mode 100644 index 0000000..52f8374 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/get-customer-orders.ts @@ -0,0 +1,21 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { getCustomerOrders } from '../common'; + +export const getCustomerOrdersAction = createAction({ + auth: shopifyAuth, + name: 'get_customer_orders', + displayName: 'Get Customer Orders', + description: `Get an existing customer's orders.`, + props: { + customerId: Property.ShortText({ + displayName: 'Customer ID', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { customerId } = propsValue; + + return await getCustomerOrders(customerId, auth); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/get-customer.ts b/packages/pieces/community/shopify/src/lib/actions/get-customer.ts new file mode 100644 index 0000000..81a4a1f --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/get-customer.ts @@ -0,0 +1,21 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { getCustomer } from '../common'; + +export const getCustomerAction = createAction({ + auth: shopifyAuth, + name: 'get_customer', + displayName: 'Get Customer', + description: `Get an existing customer's information.`, + props: { + customerId: Property.ShortText({ + displayName: 'Customer ID', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { customerId } = propsValue; + + return await getCustomer(customerId, auth); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/get-customers.ts b/packages/pieces/community/shopify/src/lib/actions/get-customers.ts new file mode 100644 index 0000000..d4963a9 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/get-customers.ts @@ -0,0 +1,15 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { getCustomers } from '../common'; + +export const getCustomersAction = createAction({ + auth: shopifyAuth, + name: 'get_customers', + displayName: 'Get Customers', + description: `Get an existing customers.`, + props: { + }, + async run({ auth }) { + return await getCustomers(auth); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/get-fulfillment.ts b/packages/pieces/community/shopify/src/lib/actions/get-fulfillment.ts new file mode 100644 index 0000000..d289a01 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/get-fulfillment.ts @@ -0,0 +1,27 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { getFulfillment } from '../common'; + +export const getFulfillmentAction = createAction({ + auth: shopifyAuth, + name: 'get_fulfillment', + displayName: 'Get Fulfillment', + description: `Get a fulfillment.`, + props: { + orderId: Property.Number({ + displayName: 'Order', + description: 'The ID of the order.', + required: true, + }), + fulfillmentId: Property.Number({ + displayName: 'Fulfillment', + description: 'The ID of the fulfillment.', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { orderId, fulfillmentId } = propsValue; + + return await getFulfillment(fulfillmentId, orderId, auth); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/get-fulfillments.ts b/packages/pieces/community/shopify/src/lib/actions/get-fulfillments.ts new file mode 100644 index 0000000..aedaa80 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/get-fulfillments.ts @@ -0,0 +1,22 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { getFulfillments } from '../common'; + +export const getFulfillmentsAction = createAction({ + auth: shopifyAuth, + name: 'get_fulfillments', + displayName: 'Get Fulfillments', + description: `Get an order's fulfillments.`, + props: { + orderId: Property.Number({ + displayName: 'Order', + description: 'The ID of the order.', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { orderId } = propsValue; + + return await getFulfillments(orderId, auth); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/get-locations.ts b/packages/pieces/community/shopify/src/lib/actions/get-locations.ts new file mode 100644 index 0000000..cd5412c --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/get-locations.ts @@ -0,0 +1,14 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { getLocations } from '../common'; + +export const getLocationsAction = createAction({ + auth: shopifyAuth, + name: 'get_locations', + displayName: 'Get Locations', + description: `Get locations.`, + props: {}, + async run({ auth }) { + return await getLocations(auth); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/get-product-variant.ts b/packages/pieces/community/shopify/src/lib/actions/get-product-variant.ts new file mode 100644 index 0000000..e2c8761 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/get-product-variant.ts @@ -0,0 +1,22 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { getProductVariant } from '../common'; + +export const getProductVariantAction = createAction({ + auth: shopifyAuth, + name: 'get_product_variant', + displayName: 'Get Product Variant', + description: `Get a product variant.`, + props: { + id: Property.ShortText({ + displayName: 'Product Variant', + description: 'The ID of the product variant.', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { id } = propsValue; + + return await getProductVariant(+id, auth); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/get-product.ts b/packages/pieces/community/shopify/src/lib/actions/get-product.ts new file mode 100644 index 0000000..2b937be --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/get-product.ts @@ -0,0 +1,22 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { getProduct } from '../common'; + +export const getProductAction = createAction({ + auth: shopifyAuth, + name: 'get_product', + displayName: 'Get Product', + description: `Get existing product.`, + props: { + id: Property.ShortText({ + displayName: 'Product', + description: 'The ID of the product.', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { id } = propsValue; + + return await getProduct(+id, auth); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/get-products.ts b/packages/pieces/community/shopify/src/lib/actions/get-products.ts new file mode 100644 index 0000000..b957304 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/get-products.ts @@ -0,0 +1,23 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { getProducts } from '../common'; + +export const getProductsAction = createAction({ + auth: shopifyAuth, + name: 'get_products', + displayName: 'Get Products', + description: `Get existing products by title.`, + props: { + title: Property.ShortText({ + displayName: 'Title', + required: false, + }), + }, + async run({ auth, propsValue }) { + const { title } = propsValue; + + return await getProducts(auth, { + title, + }); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/get-transaction.ts b/packages/pieces/community/shopify/src/lib/actions/get-transaction.ts new file mode 100644 index 0000000..6f71940 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/get-transaction.ts @@ -0,0 +1,27 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { getTransaction } from '../common'; + +export const getTransactionAction = createAction({ + auth: shopifyAuth, + name: 'get_transaction', + displayName: 'Get Transaction', + description: `Get an existing transaction's information.`, + props: { + orderId: Property.Number({ + displayName: 'Order', + description: 'The ID of the order.', + required: true, + }), + transactionId: Property.Number({ + displayName: 'Transaction', + description: 'The ID of the transaction', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { orderId, transactionId } = propsValue; + + return await getTransaction(transactionId, orderId, auth); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/get-transactions.ts b/packages/pieces/community/shopify/src/lib/actions/get-transactions.ts new file mode 100644 index 0000000..611007b --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/get-transactions.ts @@ -0,0 +1,22 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { getTransactions } from '../common'; + +export const getTransactionsAction = createAction({ + auth: shopifyAuth, + name: 'get_transactions', + displayName: 'Get Order Transactions', + description: `Get an order's transactions.`, + props: { + orderId: Property.Number({ + displayName: 'Order', + description: 'The ID of the order.', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { orderId } = propsValue; + + return await getTransactions(orderId, auth); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/update-customer.ts b/packages/pieces/community/shopify/src/lib/actions/update-customer.ts new file mode 100644 index 0000000..f5592b4 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/update-customer.ts @@ -0,0 +1,83 @@ +import { + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { updateCustomer } from '../common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const updateCustomerAction = createAction({ + auth: shopifyAuth, + name: 'update_customer', + displayName: 'Update Customer', + description: 'Update an existing customer.', + props: { + customerId: Property.ShortText({ + displayName: 'Customer ID', + required: true, + }), + email: Property.ShortText({ + displayName: 'Email', + required: false, + }), + verifiedEmail: Property.Checkbox({ + displayName: 'Verified Email', + description: 'Whether the customer has verified their email.', + required: false, + defaultValue: true, + }), + sendEmailInvite: Property.Checkbox({ + displayName: 'Send Email Invite', + required: false, + defaultValue: false, + }), + firstName: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + phoneNumber: Property.ShortText({ + displayName: 'Phone Number', + required: false, + }), + tags: Property.ShortText({ + displayName: 'Tags', + description: 'A string of comma-separated tags for filtering and search', + required: false, + }), + }, + async run({ auth, propsValue }) { + const { + customerId, + email, + verifiedEmail, + sendEmailInvite, + firstName, + lastName, + phoneNumber, + tags, + } = propsValue; + + await propsValidation.validateZod(propsValue, { + email: z.string().email().optional(), + }); + + return await updateCustomer( + customerId, + { + email, + verified_email: verifiedEmail, + send_email_invite: sendEmailInvite, + first_name: firstName, + last_name: lastName, + phone: phoneNumber, + tags, + }, + auth + ); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/update-order.ts b/packages/pieces/community/shopify/src/lib/actions/update-order.ts new file mode 100644 index 0000000..9d14023 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/update-order.ts @@ -0,0 +1,57 @@ +import { + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { updateOrder } from '../common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const updateOrderAction = createAction({ + auth: shopifyAuth, + name: 'update_order', + displayName: 'Update Order', + description: 'Update an existing order.', + props: { + id: Property.ShortText({ + displayName: 'Order', + description: 'The ID of the order.', + required: true, + }), + email: Property.ShortText({ + displayName: 'Email', + required: false, + }), + phoneNumber: Property.ShortText({ + displayName: 'Phone Number', + required: false, + }), + tags: Property.ShortText({ + displayName: 'Tags', + description: 'A string of comma-separated tags for filtering and search', + required: false, + }), + note: Property.ShortText({ + displayName: 'Note', + required: false, + }), + }, + async run({ auth, propsValue }) { + await propsValidation.validateZod(propsValue, { + email: z.string().email().optional(), + }); + + const { id, email, phoneNumber, tags, note } = propsValue; + + return await updateOrder( + +id, + { + email, + phone: phoneNumber, + tags, + note, + }, + auth + ); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/update-product.ts b/packages/pieces/community/shopify/src/lib/actions/update-product.ts new file mode 100644 index 0000000..8e2fb68 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/update-product.ts @@ -0,0 +1,92 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { updateProduct } from '../common'; +import { ShopifyImage, ShopifyProductStatuses } from '../common/types'; + +export const updateProductAction = createAction({ + auth: shopifyAuth, + name: 'update_product', + displayName: 'Update Product', + description: 'Update an existing product.', + props: { + id: Property.ShortText({ + displayName: 'Product', + description: 'The ID of the product.', + required: true, + }), + title: Property.ShortText({ + displayName: 'Title', + required: false, + }), + bodyHtml: Property.LongText({ + displayName: 'Description', + description: 'Product description (supports HTML)', + required: false, + }), + productType: Property.ShortText({ + displayName: 'Product Type', + description: + 'A categorization for the product used for filtering and searching products', + required: false, + }), + productImage: Property.File({ + displayName: 'Product Image', + description: 'The public URL or the base64 image to use', + required: false, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + required: true, + defaultValue: ShopifyProductStatuses.DRAFT, + options: { + options: [ + { + label: 'Active', + value: ShopifyProductStatuses.ACTIVE, + }, + { + label: 'Draft', + value: ShopifyProductStatuses.DRAFT, + }, + { + label: 'Archived', + value: ShopifyProductStatuses.ARCHIVED, + }, + ], + }, + }), + vendor: Property.ShortText({ + displayName: 'Vendor', + required: false, + }), + tags: Property.ShortText({ + displayName: 'Tags', + description: 'A string of comma-separated tags for filtering and search', + required: false, + }), + }, + async run({ auth, propsValue }) { + const { id, title, bodyHtml, vendor, productType, tags, productImage } = + propsValue; + + const images: Partial[] = []; + if (productImage) { + images.push({ + attachment: productImage.base64, + }); + } + + return await updateProduct( + +id, + { + title, + body_html: bodyHtml, + vendor, + product_type: productType, + tags, + images, + }, + auth + ); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/actions/upload-product-image.ts b/packages/pieces/community/shopify/src/lib/actions/upload-product-image.ts new file mode 100644 index 0000000..6aa43de --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/actions/upload-product-image.ts @@ -0,0 +1,36 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; +import { createProductImage } from '../common'; + +export const uploadProductImageAction = createAction({ + auth: shopifyAuth, + name: 'upload_product_image', + displayName: 'Upload Product Image', + description: 'Upload a new product image.', + props: { + id: Property.ShortText({ + displayName: 'Product', + description: 'The ID of the product.', + required: true, + }), + image: Property.File({ + displayName: 'Image', + description: 'The public URL or the base64 image to use', + required: true, + }), + position: Property.Number({ + displayName: 'Position', + description: '1 makes it the main image.', + required: false, + }), + }, + async run({ auth, propsValue }) { + const { id, image, position } = propsValue; + + return await createProductImage( + +id, + { attachment: image.base64, position }, + auth + ); + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/common/index.ts b/packages/pieces/community/shopify/src/lib/common/index.ts new file mode 100644 index 0000000..fd48118 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/common/index.ts @@ -0,0 +1,526 @@ +import { + HttpMessageBody, + HttpMethod, + HttpResponse, + QueryParams, + httpClient, +} from '@activepieces/pieces-common'; +import { + ShopifyAuth, + ShopifyCheckout, + ShopifyCollect, + ShopifyCustomer, + ShopifyDraftOrder, + ShopifyFulfillment, + ShopifyFulfillmentEvent, + ShopifyImage, + ShopifyOrder, + ShopifyProduct, + ShopifyProductVariant, + ShopifyTransaction, +} from './types'; + +export function getBaseUrl(shopName: string) { + return `https://${shopName}.myshopify.com/admin/api/2023-10`; +} + +export function sendShopifyRequest(data: { + url: string; + method: HttpMethod; + body?: HttpMessageBody; + queryParams?: QueryParams; + auth: ShopifyAuth; +}): Promise> { + return httpClient.sendRequest({ + url: `${getBaseUrl(data.auth.shopName)}${data.url}`, + method: data.method, + body: data.body, + queryParams: data.queryParams, + headers: { + 'X-Shopify-Access-Token': data.auth.adminToken, + }, + }); +} + +export async function createCustomer( + customer: Partial, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: '/customers.json', + method: HttpMethod.POST, + body: { + customer, + }, + }); + + return (response.body as { customer: ShopifyCustomer }).customer; +} + +export async function getCustomer( + id: string, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/customers/${id}.json`, + method: HttpMethod.GET, + }); + + return (response.body as { customer: ShopifyCustomer }).customer; +} + +export async function getCustomers( + auth: ShopifyAuth, +): Promise { + const queryParams: QueryParams = {}; + + let customers:ShopifyCustomer[] = []; + let hasNextPage = true; + + while (hasNextPage) { + + const response = await sendShopifyRequest({ + auth: auth, + url: `/customers.json`, + method: HttpMethod.GET, + queryParams, + }); + + customers = customers.concat((response.body as { customers: ShopifyCustomer[] }).customers); + + const linkHeader = response.headers?.['link']; + if (linkHeader && typeof linkHeader === 'string' && linkHeader.includes('rel="next"')) { + // Extract the URL for the next page from the Link header + const nextLink = linkHeader + .split(',') + .find((s) => s.includes('rel="next"')) + ?.match(/<(.*?)>/)?.[1]; + + if (nextLink) { + queryParams.page_info = new URL(nextLink).searchParams.get('page_info') || ''; + } else { + hasNextPage = false; + } + } else { + hasNextPage = false; + } + } + + return customers; + +} + +export async function updateCustomer( + id: string, + customer: Partial, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/customers/${id}.json`, + method: HttpMethod.PUT, + body: { + customer, + }, + }); + + return (response.body as { customer: ShopifyCustomer }).customer; +} + +export async function getCustomerOrders( + id: string, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/customers/${id}/orders.json`, + method: HttpMethod.GET, + }); + + return (response.body as { orders: ShopifyOrder[] }).orders; +} + +export async function getProducts( + auth: ShopifyAuth, + search: { + title?: string; + createdAtMin?: string; + updatedAtMin?: string; + } +): Promise { + const queryParams: QueryParams = {}; + const { title, createdAtMin, updatedAtMin } = search; + if (title) { + queryParams.title = title; + } + if (createdAtMin) { + queryParams.created_at_min = createdAtMin; + } + if (updatedAtMin) { + queryParams.updated_at_min = updatedAtMin; + } + + let products:ShopifyProduct[] = []; + let hasNextPage = true; + + while (hasNextPage) { + + const response = await sendShopifyRequest({ + auth: auth, + url: `/products.json`, + method: HttpMethod.GET, + queryParams, + }); + + products = products.concat((response.body as { products: ShopifyProduct[] }).products); + + const linkHeader = response.headers?.['link']; + if (linkHeader && typeof linkHeader === 'string' && linkHeader.includes('rel="next"')) { + // Extract the URL for the next page from the Link header + const nextLink = linkHeader + .split(',') + .find((s) => s.includes('rel="next"')) + ?.match(/<(.*?)>/)?.[1]; + + if (nextLink) { + queryParams.page_info = new URL(nextLink).searchParams.get('page_info') || ''; + } else { + hasNextPage = false; + } + } else { + hasNextPage = false; + } + } + + return products; +} + +export async function createProduct( + product: Partial, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/products.json`, + method: HttpMethod.POST, + body: { + product, + }, + }); + + return (response.body as { product: ShopifyProduct }).product; +} + +export async function updateProduct( + id: number, + product: Partial, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/products/${id}.json`, + method: HttpMethod.PUT, + body: { + product, + }, + }); + + return (response.body as { product: ShopifyProduct }).product; +} + +export async function createDraftOrder( + draftOrder: Partial, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/draft_orders.json`, + method: HttpMethod.POST, + body: { + draft_order: draftOrder, + }, + }); + + return (response.body as { draft_order: ShopifyDraftOrder }).draft_order; +} + +export async function createOrder( + order: Partial, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/orders.json`, + method: HttpMethod.POST, + body: { + order, + }, + }); + + return (response.body as { order: ShopifyOrder }).order; +} + +export async function updateOrder( + id: number, + order: Partial, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/orders/${id}.json`, + method: HttpMethod.PUT, + body: { + order, + }, + }); + + return (response.body as { order: ShopifyOrder }).order; +} + +export async function createTransaction( + orderId: number, + transaction: Partial, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/orders/${orderId}/transactions.json`, + method: HttpMethod.POST, + body: { + transaction, + }, + }); + + return (response.body as { transaction: ShopifyTransaction }).transaction; +} + +export async function getTransaction( + id: number, + orderId: number, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/orders/${orderId}/transactions/${id}.json`, + method: HttpMethod.GET, + }); + + return (response.body as { transaction: ShopifyTransaction }).transaction; +} + +export async function getTransactions( + orderId: number, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/orders/${orderId}/transactions.json`, + method: HttpMethod.GET, + }); + + return (response.body as { transactions: ShopifyTransaction[] }).transactions; +} + +export async function getFulfillments( + orderId: number, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/orders/${orderId}/fulfillments.json`, + method: HttpMethod.GET, + }); + + return (response.body as { fulfillments: ShopifyFulfillment[] }).fulfillments; +} + +export async function getFulfillment( + id: number, + orderId: number, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/orders/${orderId}/fulfillments/${id}.json`, + method: HttpMethod.GET, + }); + + return (response.body as { fulfillment: ShopifyFulfillment }).fulfillment; +} + +export async function getLocations(auth: ShopifyAuth): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/locations.json`, + method: HttpMethod.GET, + }); + + return (response.body as { locations: unknown[] }).locations; +} + +export async function createFulfillmentEvent( + id: number, + orderId: number, + event: Partial, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/orders/${orderId}/fulfillments/${id}/events.json`, + method: HttpMethod.POST, + body: { + event, + }, + }); + + return (response.body as { fulfillment_event: ShopifyFulfillmentEvent }) + .fulfillment_event; +} + +export async function closeOrder( + id: number, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/orders/${id}/close.json`, + method: HttpMethod.POST, + body: {}, + }); + + return (response.body as { order: ShopifyOrder }).order; +} + +export async function cancelOrder( + id: number, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/orders/${id}/cancel.json`, + method: HttpMethod.POST, + body: {}, + }); + + return (response.body as { order: ShopifyOrder }).order; +} + +export async function adjustInventoryLevel( + id: number, + locationId: number, + adjustment: number, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/inventory_levels/adjust.json`, + method: HttpMethod.POST, + body: { + inventory_item_id: id, + location_id: locationId, + available_adjustment: adjustment, + }, + }); + + return (response.body as { order: unknown }).order; +} + +export async function createCollect( + collect: Partial, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/collects.json`, + method: HttpMethod.POST, + body: { + collect, + }, + }); + + return (response.body as { collect: ShopifyCollect }).collect; +} + +export async function getAsset( + key: string, + themeId: number, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/themes/${themeId}/assets.json`, + queryParams: { + key, + }, + method: HttpMethod.GET, + }); + + return (response.body as { asset: unknown }).asset; +} + +export async function getProductVariant( + id: number, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/variants/${id}.json`, + method: HttpMethod.GET, + }); + + return (response.body as { variant: ShopifyProductVariant }).variant; +} + +export async function getProduct( + id: number, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/products/${id}.json`, + method: HttpMethod.GET, + }); + + return (response.body as { product: ShopifyProduct }).product; +} + +export async function createProductImage( + id: number, + image: Partial, + auth: ShopifyAuth +): Promise { + const response = await sendShopifyRequest({ + auth: auth, + url: `/products/${id}/images.json`, + method: HttpMethod.POST, + body: { + image, + }, + }); + + return (response.body as { image: ShopifyImage }).image; +} + +export async function getAbandonedCheckouts( + auth: ShopifyAuth, + search: { + sinceId: string; + } +): Promise { + const queryParams: QueryParams = {}; + const { sinceId } = search; + if (sinceId) { + queryParams.since_id = sinceId; + } + + const response = await sendShopifyRequest({ + auth: auth, + url: `/checkouts.json`, + method: HttpMethod.GET, + queryParams, + }); + + return (response.body as { checkouts: ShopifyCheckout[] }).checkouts; +} diff --git a/packages/pieces/community/shopify/src/lib/common/register-webhook.ts b/packages/pieces/community/shopify/src/lib/common/register-webhook.ts new file mode 100644 index 0000000..113f28c --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/common/register-webhook.ts @@ -0,0 +1,73 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { + createTrigger, + Trigger, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { shopifyAuth } from '../..'; + +export const createShopifyWebhookTrigger = ({ + name, + description, + displayName, + sampleData, + topic, +}: { + name: string; + description: string; + displayName: string; + topic: string; + sampleData: Record; +}): Trigger => + createTrigger({ + auth: shopifyAuth, + name, + description, + displayName, + props: {}, + sampleData: sampleData, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const shopName = context.auth.shopName; + const response = await httpClient.sendRequest<{ + webhook: { + id: string; + }; + }>({ + method: HttpMethod.POST, + url: `https://${shopName}.myshopify.com/admin/api/2023-01/webhooks.json`, + headers: { + 'X-Shopify-Access-Token': context.auth.adminToken, + }, + body: { + webhook: { + topic: topic, + address: context.webhookUrl, + format: 'json', + }, + }, + }); + await context.store?.put(`shopify_webhook_id`, response.body.webhook.id); + console.log('webhook created', response.body.webhook.id); + }, + async onDisable(context) { + const webhookId = await context.store.get(`shopify_webhook_id`); + const shopName = context.auth.shopName; + await httpClient.sendRequest<{ + webhook: { + id: string; + }; + }>({ + method: HttpMethod.DELETE, + url: `https://${shopName}.myshopify.com/admin/api/2023-01/webhooks/${webhookId}.json`, + headers: { + 'X-Shopify-Access-Token': context.auth.adminToken, + }, + }); + await context.store?.put(`shopify_webhook_id`, null); + }, + async run(context) { + console.debug('trigger running', context); + return [context.payload.body]; + }, + }); diff --git a/packages/pieces/community/shopify/src/lib/common/types.ts b/packages/pieces/community/shopify/src/lib/common/types.ts new file mode 100644 index 0000000..8969f43 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/common/types.ts @@ -0,0 +1,185 @@ +export type ShopifyAuth = { + shopName: string; + adminToken: string; +}; + +export type ShopifyCustomer = { + id: number; + email: string; + accepts_marketing: boolean; + created_at: string; + updated_at: string; + first_name: string; + last_name: string; + orders_count: number; + state: string; + total_spent: number; + last_order_id: number; + note: unknown; + verified_email: boolean; + tax_exempt: boolean; + tags: string; + last_order_name: unknown; + currency: string; + phone: string; + addresses: unknown[]; + accepts_marketing_updated_at: string; + marketing_opt_in_level: unknown; + tax_exemptions: unknown[]; + email_marketing_consent: unknown; + sms_marketing_consent: unknown; + send_email_invite: boolean; +}; + +export type ShopifyOrder = { + line_items: Partial[]; + customer: Partial; + financial_status: ShopifyOrderFinancialStatuses; + email: string; + send_receipt: boolean; + send_fulfillment_receipt: boolean; + fulfillment_status: string; + tags: string; + phone: string; + note: string; +}; + +export type ShopifyAddress = { + first_name: string; + address1: string; + phone: string; + city: string; + zip: string; + province: string; + country: string; + last_name: string; + address2: string; + company: unknown; + latitude: number; + longitude: number; + name: string; + country_code: string; + province_code: string; +}; + +export type ShopifyProduct = { + id: number; + title: string; + body_html: string; + vendor: string; + product_type: string; + status: ShopifyProductStatuses; + tags: string; + images: Partial[]; + created_at: string; + updated_at: string; +}; + +export type ShopifyImage = { + src: string; + attachment: string; + position: number; +}; + +export type ShopifyProductVariant = { + id: number; + product_id: number; + title: string; + price: string; + sku: string; + inventory_item_id: number; + inventory_quantity: number; + created_at: string; + updated_at: string; + [key: string]: string | number | unknown; +}; + +export type ShopifyLineItem = { + variant_id: number; + product_id: number; + quantity: number; + price: string; + title: string; +}; + +export type ShopifyDraftOrder = ShopifyOrder; + +export type ShopifyTransaction = { + order_id: number; + currency: string; + amount: string; + source: string; + kind: ShopifyTransactionKinds; + parent_id: number; + test: boolean; +}; + +export type ShopifyFulfillment = { + order_id: number; + status: ShopifyFulfillmentStatuses; + line_items: Partial[]; +}; + +export type ShopifyFulfillmentEvent = { + id: number; + order_id: number; + status: ShopifyFulfillmentEventStatuses; + message: string; +}; + +export type ShopifyCollect = { + product_id: number; + collection_id: number; +}; + +export type ShopifyCheckout = { + id: number; + abandoned_checkout_url: string; + completed_at: string; + created_at: string; + currency: string; + customer: Partial; +}; + +export enum ShopifyProductStatuses { + ACTIVE = 'active', + ARCHIVED = 'archived', + DRAFT = 'draft', +} + +export enum ShopifyOrderFinancialStatuses { + PENDING = 'pending', + PARTIALLY_PAID = 'partially_pad', +} + +export enum ShopifyTransactionKinds { + AUTHORIZATION = 'authorization', + SALE = 'sale', + CAPTURE = 'capture', + VOID = 'void', + REFUND = 'refund', +} + +export enum ShopifyFulfillmentStatuses { + PENDING = 'pending', + OPEN = 'open', + SUCCESS = 'success', + CANCELLED = 'cancelled', + ERROR = 'error', + FAILURE = 'failure', +} + +export enum ShopifyFulfillmentEventStatuses { + ATTEMPTED_DELIVERY = 'attempted_delivery', + CARRIER_PICKED_UP = 'carrier_picked_up', + CONFIRMED = 'confirmed', + DELAYED = 'delayed', + DELIVERED = 'delivered', + FAILURE = 'failure', + IN_TRANSIT = 'in_transit', + LABEL_PRINTED = 'label_printed', + LABEL_PURCHASED = 'label_purchased', + OUT_FOR_DELIVERY = 'out_for_delivery', + PICKED_UP = 'picked_up', + READY_FOR_PICKUP = 'ready_for_pickup', +} diff --git a/packages/pieces/community/shopify/src/lib/triggers/new-abandoned-checkout.ts b/packages/pieces/community/shopify/src/lib/triggers/new-abandoned-checkout.ts new file mode 100644 index 0000000..3b4b9bf --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/triggers/new-abandoned-checkout.ts @@ -0,0 +1,44 @@ +import { + Polling, + DedupeStrategy, + pollingHelper, +} from '@activepieces/pieces-common'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { getAbandonedCheckouts } from '../common'; +import { shopifyAuth } from '../..'; +import { ShopifyAuth } from '../common/types'; + +export const newAbandonedCheckout = createTrigger({ + name: 'new_abandoned_checkout', + auth: shopifyAuth, + displayName: 'New Abandoned Checkout', + description: 'Triggers when a checkout is abandoned.', + props: {}, + sampleData: {}, + type: TriggerStrategy.POLLING, + async onEnable({ auth, propsValue, store }) { + await pollingHelper.onEnable(polling, { auth, propsValue, store }); + }, + async onDisable({ auth, propsValue, store }) { + await pollingHelper.onEnable(polling, { auth, propsValue, store }); + }, + async run({ auth, propsValue, store, files }) { + return await pollingHelper.poll(polling, { auth, propsValue, store, files }); + }, + async test({ auth, propsValue, store, files }) { + return await pollingHelper.test(polling, { auth, propsValue, store, files }); + }, +}); + +const polling: Polling = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ auth, lastItemId }) => { + const checkouts = await getAbandonedCheckouts(auth, { + sinceId: lastItemId as string, + }); + return checkouts.map((checkout) => ({ + id: checkout.id, + data: checkout, + })); + }, +}; diff --git a/packages/pieces/community/shopify/src/lib/triggers/new-cancelled-order.ts b/packages/pieces/community/shopify/src/lib/triggers/new-cancelled-order.ts new file mode 100644 index 0000000..2da8565 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/triggers/new-cancelled-order.ts @@ -0,0 +1,753 @@ +import { createShopifyWebhookTrigger } from '../common/register-webhook'; + +export const newCancelledOrder = createShopifyWebhookTrigger({ + name: 'new_cancelled_order', + description: 'Triggered when order is cancelled', + topic: 'orders/cancelled', + displayName: 'New Cancelled Order', + sampleData: { + id: 5324790137142, + admin_graphql_api_id: 'gid://shopify/Order/5324790137142', + app_id: 16818700281, + browser_ip: '244.100.5.121', + buyer_accepts_marketing: false, + cancel_reason: 'other', + cancelled_at: '2023-03-24T18:08:18-04:00', + cart_token: null, + checkout_id: 36646023004470, + checkout_token: '751db9e69c75eb563f4d9b052e8cc0f7', + client_details: { + accept_language: null, + browser_height: null, + browser_ip: '244.100.5.121', + browser_width: null, + session_hash: null, + user_agent: 'Sunflower/production Cusco/1.12.1 Ruby/3.1.2', + }, + closed_at: '2023-03-24T18:08:18-04:00', + company: null, + confirmed: true, + contact_email: 'russel.winfield@example.com', + created_at: '2023-03-24T11:28:18-04:00', + currency: 'USD', + current_subtotal_price: '0.00', + current_subtotal_price_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + current_total_discounts: '0.00', + current_total_discounts_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + current_total_duties_set: null, + current_total_price: '0.00', + current_total_price_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + current_total_tax: '0.00', + current_total_tax_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + customer_locale: 'en', + device_id: null, + discount_codes: [ + { + code: '', + amount: '313.47', + type: 'percentage', + }, + ], + email: 'russel.winfield@example.com', + estimated_taxes: false, + financial_status: 'refunded', + fulfillment_status: null, + gateway: 'manual', + landing_site: null, + landing_site_ref: null, + location_id: null, + merchant_of_record_app_id: null, + name: '#1009', + note: null, + note_attributes: [], + number: 9, + order_number: 1009, + order_status_url: + 'https://activepieces-test.myshopify.com/74392404278/orders/85adf5f235cb50d1dfc203d1b8da9885/authenticate?key=f91a3b1892736757ffb6ba26d3e7ff70', + original_total_duties_set: null, + payment_gateway_names: ['manual'], + phone: null, + presentment_currency: 'USD', + processed_at: '2023-03-24T11:28:17-04:00', + processing_method: 'manual', + reference: '7b40196bcf0a1cad3f0954c4e058c229', + referring_site: null, + source_identifier: '7b40196bcf0a1cad3f0954c4e058c229', + source_name: '16818700289', + source_url: null, + subtotal_price: '1776.38', + subtotal_price_set: { + shop_money: { + amount: '1776.38', + currency_code: 'USD', + }, + presentment_money: { + amount: '1776.38', + currency_code: 'USD', + }, + }, + tags: 'Line Item Discount, Order Discount', + tax_lines: [], + taxes_included: false, + test: false, + token: '85adf5f235cb50d1dfc203d1b8da9885', + total_discounts: '323.47', + total_discounts_set: { + shop_money: { + amount: '323.47', + currency_code: 'USD', + }, + presentment_money: { + amount: '323.47', + currency_code: 'USD', + }, + }, + total_line_items_price: '2099.85', + total_line_items_price_set: { + shop_money: { + amount: '2099.85', + currency_code: 'USD', + }, + presentment_money: { + amount: '2099.85', + currency_code: 'USD', + }, + }, + total_outstanding: '0.00', + total_price: '1806.38', + total_price_set: { + shop_money: { + amount: '1806.38', + currency_code: 'USD', + }, + presentment_money: { + amount: '1806.38', + currency_code: 'USD', + }, + }, + total_shipping_price_set: { + shop_money: { + amount: '30.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '30.00', + currency_code: 'USD', + }, + }, + total_tax: '0.00', + total_tax_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + total_tip_received: '0.00', + total_weight: 13605, + updated_at: '2023-03-24T18:08:18-04:00', + user_id: null, + billing_address: { + first_name: 'Russell', + address1: '105 Victoria St', + phone: null, + city: 'Toronto', + zip: 'M5C1N7', + province: null, + country: 'Canada', + last_name: 'Winfield', + address2: null, + company: 'Company Name', + latitude: 43.6522608, + longitude: -79.3776862, + name: 'Russell Winfield', + country_code: 'CA', + province_code: null, + }, + customer: { + id: 6972527083830, + email: 'russel.winfield@example.com', + accepts_marketing: false, + created_at: '2023-03-24T11:28:11-04:00', + updated_at: '2023-03-24T11:28:19-04:00', + first_name: 'Russell', + last_name: 'Winfield', + state: 'disabled', + note: 'This customer is created with most available fields', + verified_email: true, + multipass_identifier: null, + tax_exempt: false, + phone: '+16135550135', + email_marketing_consent: { + state: 'not_subscribed', + opt_in_level: 'single_opt_in', + consent_updated_at: null, + }, + sms_marketing_consent: { + state: 'not_subscribed', + opt_in_level: 'unknown', + consent_updated_at: null, + consent_collected_from: 'OTHER', + }, + tags: 'VIP', + currency: 'USD', + accepts_marketing_updated_at: '2023-03-24T11:28:11-04:00', + marketing_opt_in_level: null, + tax_exemptions: [], + admin_graphql_api_id: 'gid://shopify/Customer/6972527083830', + default_address: { + id: 9218460975414, + customer_id: 6972527083830, + first_name: 'Russell', + last_name: 'Winfield', + company: 'Company Name', + address1: '105 Victoria St', + address2: null, + city: 'Toronto', + province: null, + country: 'Canada', + zip: 'M5C1N7', + phone: null, + name: 'Russell Winfield', + province_code: null, + country_code: 'CA', + country_name: 'Canada', + default: true, + }, + }, + discount_applications: [ + { + target_type: 'line_item', + type: 'manual', + value: '10.0', + value_type: 'fixed_amount', + allocation_method: 'each', + target_selection: 'explicit', + title: '', + description: null, + }, + { + target_type: 'line_item', + type: 'manual', + value: '15.0', + value_type: 'percentage', + allocation_method: 'across', + target_selection: 'all', + title: '', + description: null, + }, + ], + fulfillments: [], + line_items: [ + { + id: 13924936679734, + admin_graphql_api_id: 'gid://shopify/LineItem/13924936679734', + fulfillable_quantity: 0, + fulfillment_service: 'manual', + fulfillment_status: null, + gift_card: false, + grams: 4536, + name: 'The Complete Snowboard - Powder', + price: '699.95', + price_set: { + shop_money: { + amount: '699.95', + currency_code: 'USD', + }, + presentment_money: { + amount: '699.95', + currency_code: 'USD', + }, + }, + product_exists: true, + product_id: 8222223302966, + properties: [], + quantity: 1, + requires_shipping: true, + sku: '', + taxable: true, + title: 'The Complete Snowboard', + total_discount: '10.00', + total_discount_set: { + shop_money: { + amount: '10.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '10.00', + currency_code: 'USD', + }, + }, + variant_id: 44872881537334, + variant_inventory_management: 'shopify', + variant_title: 'Powder', + vendor: 'Snowboard Vendor', + tax_lines: [], + duties: [], + discount_allocations: [ + { + amount: '10.00', + amount_set: { + shop_money: { + amount: '10.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '10.00', + currency_code: 'USD', + }, + }, + discount_application_index: 0, + }, + { + amount: '103.50', + amount_set: { + shop_money: { + amount: '103.50', + currency_code: 'USD', + }, + presentment_money: { + amount: '103.50', + currency_code: 'USD', + }, + }, + discount_application_index: 1, + }, + ], + }, + { + id: 13924936712502, + admin_graphql_api_id: 'gid://shopify/LineItem/13924936712502', + fulfillable_quantity: 0, + fulfillment_service: 'manual', + fulfillment_status: null, + gift_card: false, + grams: 4536, + name: 'The Complete Snowboard - Electric', + price: '699.95', + price_set: { + shop_money: { + amount: '699.95', + currency_code: 'USD', + }, + presentment_money: { + amount: '699.95', + currency_code: 'USD', + }, + }, + product_exists: true, + product_id: 8222223302966, + properties: [], + quantity: 2, + requires_shipping: true, + sku: '', + taxable: true, + title: 'The Complete Snowboard', + total_discount: '0.00', + total_discount_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + variant_id: 44872881570102, + variant_inventory_management: 'shopify', + variant_title: 'Electric', + vendor: 'Snowboard Vendor', + tax_lines: [], + duties: [], + discount_allocations: [ + { + amount: '209.97', + amount_set: { + shop_money: { + amount: '209.97', + currency_code: 'USD', + }, + presentment_money: { + amount: '209.97', + currency_code: 'USD', + }, + }, + discount_application_index: 1, + }, + ], + }, + ], + payment_terms: null, + refunds: [ + { + id: 945108681014, + admin_graphql_api_id: 'gid://shopify/Refund/945108681014', + created_at: '2023-03-24T18:08:18-04:00', + note: null, + order_id: 5324790137142, + processed_at: '2023-03-24T18:08:18-04:00', + restock: false, + total_duties_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + user_id: 94873289014, + order_adjustments: [ + { + id: 278632759606, + amount: '-30.00', + amount_set: { + shop_money: { + amount: '-30.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '-30.00', + currency_code: 'USD', + }, + }, + kind: 'shipping_refund', + order_id: 5324790137142, + reason: 'Shipping refund', + refund_id: 945108681014, + tax_amount: '0.00', + tax_amount_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + }, + ], + transactions: [ + { + id: 6492880732470, + admin_graphql_api_id: + 'gid://shopify/OrderTransaction/6492880732470', + amount: '1806.38', + authorization: null, + created_at: '2023-03-24T18:08:18-04:00', + currency: 'USD', + device_id: null, + error_code: null, + gateway: 'manual', + kind: 'refund', + location_id: null, + message: 'Refunded 1806.38 from manual gateway', + order_id: 5324790137142, + parent_id: 6492602990902, + payment_id: '#1009.2', + processed_at: '2023-03-24T18:08:18-04:00', + receipt: {}, + source_name: '1830279', + status: 'success', + test: false, + user_id: null, + }, + ], + refund_line_items: [ + { + id: 554287071542, + line_item_id: 13924936679734, + location_id: null, + quantity: 1, + restock_type: 'no_restock', + subtotal: 586.45, + subtotal_set: { + shop_money: { + amount: '586.45', + currency_code: 'USD', + }, + presentment_money: { + amount: '586.45', + currency_code: 'USD', + }, + }, + total_tax: 0, + total_tax_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + line_item: { + id: 13924936679734, + admin_graphql_api_id: 'gid://shopify/LineItem/13924936679734', + fulfillable_quantity: 0, + fulfillment_service: 'manual', + fulfillment_status: null, + gift_card: false, + grams: 4536, + name: 'The Complete Snowboard - Powder', + price: '699.95', + price_set: { + shop_money: { + amount: '699.95', + currency_code: 'USD', + }, + presentment_money: { + amount: '699.95', + currency_code: 'USD', + }, + }, + product_exists: true, + product_id: 8222223302966, + properties: [], + quantity: 1, + requires_shipping: true, + sku: '', + taxable: true, + title: 'The Complete Snowboard', + total_discount: '10.00', + total_discount_set: { + shop_money: { + amount: '10.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '10.00', + currency_code: 'USD', + }, + }, + variant_id: 44872881537334, + variant_inventory_management: 'shopify', + variant_title: 'Powder', + vendor: 'Snowboard Vendor', + tax_lines: [], + duties: [], + discount_allocations: [ + { + amount: '10.00', + amount_set: { + shop_money: { + amount: '10.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '10.00', + currency_code: 'USD', + }, + }, + discount_application_index: 0, + }, + { + amount: '103.50', + amount_set: { + shop_money: { + amount: '103.50', + currency_code: 'USD', + }, + presentment_money: { + amount: '103.50', + currency_code: 'USD', + }, + }, + discount_application_index: 1, + }, + ], + }, + }, + { + id: 554287104310, + line_item_id: 13924936712502, + location_id: null, + quantity: 2, + restock_type: 'no_restock', + subtotal: 1189.93, + subtotal_set: { + shop_money: { + amount: '1189.93', + currency_code: 'USD', + }, + presentment_money: { + amount: '1189.93', + currency_code: 'USD', + }, + }, + total_tax: 0, + total_tax_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + line_item: { + id: 13924936712502, + admin_graphql_api_id: 'gid://shopify/LineItem/13924936712502', + fulfillable_quantity: 0, + fulfillment_service: 'manual', + fulfillment_status: null, + gift_card: false, + grams: 4536, + name: 'The Complete Snowboard - Electric', + price: '699.95', + price_set: { + shop_money: { + amount: '699.95', + currency_code: 'USD', + }, + presentment_money: { + amount: '699.95', + currency_code: 'USD', + }, + }, + product_exists: true, + product_id: 8222223302966, + properties: [], + quantity: 2, + requires_shipping: true, + sku: '', + taxable: true, + title: 'The Complete Snowboard', + total_discount: '0.00', + total_discount_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + variant_id: 44872881570102, + variant_inventory_management: 'shopify', + variant_title: 'Electric', + vendor: 'Snowboard Vendor', + tax_lines: [], + duties: [], + discount_allocations: [ + { + amount: '209.97', + amount_set: { + shop_money: { + amount: '209.97', + currency_code: 'USD', + }, + presentment_money: { + amount: '209.97', + currency_code: 'USD', + }, + }, + discount_application_index: 1, + }, + ], + }, + }, + ], + duties: [], + }, + ], + shipping_address: { + first_name: 'Russell', + address1: '105 Victoria St', + phone: null, + city: 'Toronto', + zip: 'M5C1N7', + province: null, + country: 'Canada', + last_name: 'Winfield', + address2: null, + company: 'Company Name', + latitude: 43.6522608, + longitude: -79.3776862, + name: 'Russell Winfield', + country_code: 'CA', + province_code: null, + }, + shipping_lines: [ + { + id: 4362548838710, + carrier_identifier: '071e9d6cd4a1d56acf60bc21aea1e689', + code: 'International Shipping', + delivery_category: null, + discounted_price: '30.00', + discounted_price_set: { + shop_money: { + amount: '30.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '30.00', + currency_code: 'USD', + }, + }, + phone: null, + price: '30.00', + price_set: { + shop_money: { + amount: '30.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '30.00', + currency_code: 'USD', + }, + }, + requested_fulfillment_service_id: null, + source: 'shopify', + title: 'International Shipping', + tax_lines: [], + discount_allocations: [], + }, + ], + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/triggers/new-customer.ts b/packages/pieces/community/shopify/src/lib/triggers/new-customer.ts new file mode 100644 index 0000000..0da3a5d --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/triggers/new-customer.ts @@ -0,0 +1,81 @@ +import { createShopifyWebhookTrigger } from '../common/register-webhook'; + +export const newCustomer = createShopifyWebhookTrigger({ + name: 'new_customer', + description: 'Triggered when a new customer is created', + topic: 'customers/create', + displayName: 'New Customer', + sampleData: { + id: 6972943892790, + email: 'john@activepieces.com', + accepts_marketing: false, + created_at: '2023-03-24T18:13:56-04:00', + updated_at: '2023-03-24T18:13:56-04:00', + first_name: 'John', + last_name: 'Doe', + orders_count: 0, + state: 'disabled', + total_spent: '0.00', + last_order_id: null, + note: 'Some notes', + verified_email: true, + multipass_identifier: null, + tax_exempt: false, + tags: 'tag, VIP', + last_order_name: null, + currency: 'USD', + addresses: [ + { + id: 9218699002161, + customer_id: 6972943892790, + first_name: 'John', + last_name: 'Doe', + company: 'Company Name', + address1: 'Delaware', + address2: '11, test', + city: 'Calforina', + province: 'Delaware', + country: 'United States', + zip: '19931', + name: 'Mohammad AbuAboud', + province_code: 'DE', + country_code: 'US', + country_name: 'United States', + default: true, + }, + ], + accepts_marketing_updated_at: '2023-03-24T18:13:56-04:00', + marketing_opt_in_level: null, + tax_exemptions: [], + email_marketing_consent: { + state: 'not_subscribed', + opt_in_level: 'single_opt_in', + consent_updated_at: null, + }, + sms_marketing_consent: { + state: 'not_subscribed', + opt_in_level: 'single_opt_in', + consent_updated_at: null, + consent_collected_from: 'SHOPIFY', + }, + admin_graphql_api_id: 'gid://shopify/Customer/6972943892790', + default_address: { + id: 9218699002166, + customer_id: 6972943892790, + first_name: 'Mohammad', + last_name: 'AbuAboud', + company: 'Company Name', + address1: 'Delaware', + address2: '11, test', + city: 'Calforina', + province: 'Delaware', + country: 'United States', + zip: '19931', + name: 'Mohammad AbuAboud', + province_code: 'DE', + country_code: 'US', + country_name: 'United States', + default: true, + }, + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/triggers/new-order.ts b/packages/pieces/community/shopify/src/lib/triggers/new-order.ts new file mode 100644 index 0000000..91714e3 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/triggers/new-order.ts @@ -0,0 +1,238 @@ +import { createShopifyWebhookTrigger } from '../common/register-webhook'; + +export const newOrder = createShopifyWebhookTrigger({ + name: 'new_order', + description: 'Triggered when a new order is created', + topic: 'orders/create', + displayName: 'New Order', + sampleData: { + id: 5324830114101, + admin_graphql_api_id: 'gid://shopify/Order/5324830114102', + app_id: 1354745, + browser_ip: '95.90.193.175', + buyer_accepts_marketing: false, + cancel_reason: null, + cancelled_at: null, + cart_token: null, + checkout_id: 36646099517750, + checkout_token: '2b9ba639fb81a1fb61a02be4c95459b5', + client_details: { + accept_language: null, + browser_height: null, + browser_ip: '95.90.193.175', + browser_width: null, + session_hash: null, + user_agent: + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36', + }, + closed_at: null, + company: null, + confirmed: true, + contact_email: null, + created_at: '2023-03-24T12:37:26-04:00', + currency: 'USD', + current_subtotal_price: '2629.95', + current_subtotal_price_set: { + shop_money: { + amount: '2629.95', + currency_code: 'USD', + }, + presentment_money: { + amount: '2629.95', + currency_code: 'USD', + }, + }, + current_total_discounts: '0.00', + current_total_discounts_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + current_total_duties_set: null, + current_total_price: '2629.95', + current_total_price_set: { + shop_money: { + amount: '2629.95', + currency_code: 'USD', + }, + presentment_money: { + amount: '2629.95', + currency_code: 'USD', + }, + }, + current_total_tax: '0.00', + current_total_tax_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + customer_locale: 'en', + device_id: null, + discount_codes: [], + email: '', + estimated_taxes: false, + financial_status: 'paid', + fulfillment_status: null, + gateway: 'manual', + landing_site: null, + landing_site_ref: null, + location_id: 80901996854, + merchant_of_record_app_id: null, + name: '#1010', + note: null, + note_attributes: [], + number: 10, + order_number: 1010, + order_status_url: + 'https://activepieces-test.myshopify.com/74392404278/orders/2979b599ca9a25049397820ac12aaf87/authenticate?key=42a8db0d2e048823fea486a316d0d231', + original_total_duties_set: null, + payment_gateway_names: ['manual'], + presentment_currency: 'USD', + processed_at: '2023-03-24T12:37:26-04:00', + processing_method: 'manual', + reference: 'f86c8158ece5ee11dfafc9b21f390184', + referring_site: null, + source_identifier: 'f86c8158ece5ee11dfafc9b21f390184', + source_name: 'shopify_draft_order', + source_url: null, + subtotal_price: '2629.95', + subtotal_price_set: { + shop_money: { + amount: '2629.95', + currency_code: 'USD', + }, + presentment_money: { + amount: '2629.95', + currency_code: 'USD', + }, + }, + tags: '', + tax_lines: [], + taxes_included: false, + test: false, + token: '2979b599ca9a25049397820ac12aaf87', + total_discounts: '0.00', + total_discounts_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + total_line_items_price: '2629.95', + total_line_items_price_set: { + shop_money: { + amount: '2629.95', + currency_code: 'USD', + }, + presentment_money: { + amount: '2629.95', + currency_code: 'USD', + }, + }, + total_outstanding: '0.00', + total_price: '2629.95', + total_price_set: { + shop_money: { + amount: '2629.95', + currency_code: 'USD', + }, + presentment_money: { + amount: '2629.95', + currency_code: 'USD', + }, + }, + total_shipping_price_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + total_tax: '0.00', + total_tax_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + total_tip_received: '0.00', + total_weight: 0, + updated_at: '2023-03-24T12:37:27-04:00', + user_id: 94873289014, + discount_applications: [], + fulfillments: [], + line_items: [ + { + id: 13925006311734, + admin_graphql_api_id: 'gid://shopify/LineItem/13925006311734', + fulfillable_quantity: 1, + fulfillment_service: 'snow-city-warehouse', + fulfillment_status: null, + gift_card: false, + grams: 0, + name: 'The 3p Fulfilled Snowboard', + price: '2629.95', + price_set: { + shop_money: { + amount: '2629.95', + currency_code: 'USD', + }, + presentment_money: { + amount: '2629.95', + currency_code: 'USD', + }, + }, + product_exists: true, + product_id: 8222223204662, + properties: [], + quantity: 1, + requires_shipping: true, + sku: 'sku-hosted-1', + taxable: true, + title: 'The 3p Fulfilled Snowboard', + total_discount: '0.00', + total_discount_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + variant_id: 44872881439030, + variant_inventory_management: 'shopify', + variant_title: null, + vendor: 'activepieces-test', + tax_lines: [], + duties: [], + discount_allocations: [], + }, + ], + payment_terms: null, + refunds: [], + shipping_lines: [], + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/triggers/new-paid-order.ts b/packages/pieces/community/shopify/src/lib/triggers/new-paid-order.ts new file mode 100644 index 0000000..4d8b3e3 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/triggers/new-paid-order.ts @@ -0,0 +1,238 @@ +import { createShopifyWebhookTrigger } from '../common/register-webhook'; + +export const newPaidOrder = createShopifyWebhookTrigger({ + name: 'new_paid_order', + description: 'Triggered when a paid order is created', + topic: 'orders/paid', + displayName: 'New Paid Order', + sampleData: { + id: 5324830114101, + admin_graphql_api_id: 'gid://shopify/Order/5324830114102', + app_id: 1354745, + browser_ip: '95.90.193.175', + buyer_accepts_marketing: false, + cancel_reason: null, + cancelled_at: null, + cart_token: null, + checkout_id: 36646099517750, + checkout_token: '2b9ba639fb81a1fb61a02be4c95459b5', + client_details: { + accept_language: null, + browser_height: null, + browser_ip: '95.90.193.175', + browser_width: null, + session_hash: null, + user_agent: + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36', + }, + closed_at: null, + company: null, + confirmed: true, + contact_email: null, + created_at: '2023-03-24T12:37:26-04:00', + currency: 'USD', + current_subtotal_price: '2629.95', + current_subtotal_price_set: { + shop_money: { + amount: '2629.95', + currency_code: 'USD', + }, + presentment_money: { + amount: '2629.95', + currency_code: 'USD', + }, + }, + current_total_discounts: '0.00', + current_total_discounts_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + current_total_duties_set: null, + current_total_price: '2629.95', + current_total_price_set: { + shop_money: { + amount: '2629.95', + currency_code: 'USD', + }, + presentment_money: { + amount: '2629.95', + currency_code: 'USD', + }, + }, + current_total_tax: '0.00', + current_total_tax_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + customer_locale: 'en', + device_id: null, + discount_codes: [], + email: '', + estimated_taxes: false, + financial_status: 'paid', + fulfillment_status: null, + gateway: 'manual', + landing_site: null, + landing_site_ref: null, + location_id: 80901996854, + merchant_of_record_app_id: null, + name: '#1010', + note: null, + note_attributes: [], + number: 10, + order_number: 1010, + order_status_url: + 'https://activepieces-test.myshopify.com/74392404278/orders/2979b599ca9a25049397820ac12aaf87/authenticate?key=42a8db0d2e048823fea486a316d0d231', + original_total_duties_set: null, + payment_gateway_names: ['manual'], + presentment_currency: 'USD', + processed_at: '2023-03-24T12:37:26-04:00', + processing_method: 'manual', + reference: 'f86c8158ece5ee11dfafc9b21f390184', + referring_site: null, + source_identifier: 'f86c8158ece5ee11dfafc9b21f390184', + source_name: 'shopify_draft_order', + source_url: null, + subtotal_price: '2629.95', + subtotal_price_set: { + shop_money: { + amount: '2629.95', + currency_code: 'USD', + }, + presentment_money: { + amount: '2629.95', + currency_code: 'USD', + }, + }, + tags: '', + tax_lines: [], + taxes_included: false, + test: false, + token: '2979b599ca9a25049397820ac12aaf87', + total_discounts: '0.00', + total_discounts_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + total_line_items_price: '2629.95', + total_line_items_price_set: { + shop_money: { + amount: '2629.95', + currency_code: 'USD', + }, + presentment_money: { + amount: '2629.95', + currency_code: 'USD', + }, + }, + total_outstanding: '0.00', + total_price: '2629.95', + total_price_set: { + shop_money: { + amount: '2629.95', + currency_code: 'USD', + }, + presentment_money: { + amount: '2629.95', + currency_code: 'USD', + }, + }, + total_shipping_price_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + total_tax: '0.00', + total_tax_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + total_tip_received: '0.00', + total_weight: 0, + updated_at: '2023-03-24T12:37:27-04:00', + user_id: 94873289014, + discount_applications: [], + fulfillments: [], + line_items: [ + { + id: 13925006311734, + admin_graphql_api_id: 'gid://shopify/LineItem/13925006311734', + fulfillable_quantity: 1, + fulfillment_service: 'snow-city-warehouse', + fulfillment_status: null, + gift_card: false, + grams: 0, + name: 'The 3p Fulfilled Snowboard', + price: '2629.95', + price_set: { + shop_money: { + amount: '2629.95', + currency_code: 'USD', + }, + presentment_money: { + amount: '2629.95', + currency_code: 'USD', + }, + }, + product_exists: true, + product_id: 8222223204662, + properties: [], + quantity: 1, + requires_shipping: true, + sku: 'sku-hosted-1', + taxable: true, + title: 'The 3p Fulfilled Snowboard', + total_discount: '0.00', + total_discount_set: { + shop_money: { + amount: '0.00', + currency_code: 'USD', + }, + presentment_money: { + amount: '0.00', + currency_code: 'USD', + }, + }, + variant_id: 44872881439030, + variant_inventory_management: 'shopify', + variant_title: null, + vendor: 'activepieces-test', + tax_lines: [], + duties: [], + discount_allocations: [], + }, + ], + payment_terms: null, + refunds: [], + shipping_lines: [], + }, +}); diff --git a/packages/pieces/community/shopify/src/lib/triggers/updated-product.ts b/packages/pieces/community/shopify/src/lib/triggers/updated-product.ts new file mode 100644 index 0000000..181be96 --- /dev/null +++ b/packages/pieces/community/shopify/src/lib/triggers/updated-product.ts @@ -0,0 +1,87 @@ +import { createShopifyWebhookTrigger } from '../common/register-webhook'; + +export const updatedProduct = createShopifyWebhookTrigger({ + name: 'updated_product', + description: 'Triggered when a product is updated.', + topic: 'products/update', + displayName: 'Updated Product', + sampleData: { + id: 8282295566587, + title: 'My AP Product', + body_html: 'Well this is nice', + vendor: 'Kofahi', + product_type: 'Test', + created_at: '2024-01-02T20:36:40+03:00', + handle: 'my-ap-product-1', + updated_at: '2024-01-02T20:36:40+03:00', + published_at: '2024-01-02T20:36:40+03:00', + template_suffix: null, + published_scope: 'global', + tags: '', + status: 'active', + variants: [ + { + id: 45134980382971, + product_id: 8282295566587, + title: 'Default Title', + price: '0.000', + sku: '', + position: 1, + inventory_policy: 'deny', + compare_at_price: null, + fulfillment_service: 'manual', + inventory_management: null, + option1: 'Default Title', + option2: null, + option3: null, + created_at: '2024-01-02T20:36:40+03:00', + updated_at: '2024-01-02T20:36:40+03:00', + taxable: true, + barcode: null, + grams: 0, + image_id: null, + weight: 0, + weight_unit: 'kg', + inventory_item_id: 47200507134203, + inventory_quantity: 0, + old_inventory_quantity: 0, + requires_shipping: true, + }, + ], + options: [ + { + id: 10640607805691, + product_id: 8282295566587, + name: 'Title', + position: 1, + values: ['Default Title'], + }, + ], + images: [ + { + id: 41679215657211, + product_id: 8282295566587, + position: 1, + created_at: '2024-01-02T20:36:40+03:00', + updated_at: '2024-01-02T20:36:40+03:00', + alt: null, + width: 500, + height: 500, + src: 'https://cdn.shopify.com/s/files/1/0676/6598/5787/products/416a37a613b156f9d23e4fa1fd5358d7.png?v=1704217000', + variant_ids: [], + }, + ], + image: { + id: 41679215657211, + product_id: 8282295566587, + position: 1, + created_at: '2024-01-02T20:36:40+03:00', + updated_at: '2024-01-02T20:36:40+03:00', + alt: null, + width: 500, + height: 500, + src: 'https://cdn.shopify.com/s/files/1/0676/6598/5787/products/416a37a613b156f9d23e4fa1fd5358d7.png?v=1704217000', + variant_ids: [], + }, + }, +}); diff --git a/packages/pieces/community/shopify/tsconfig.json b/packages/pieces/community/shopify/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/shopify/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/shopify/tsconfig.lib.json b/packages/pieces/community/shopify/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/shopify/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/simplepdf/.eslintrc.json b/packages/pieces/community/simplepdf/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/simplepdf/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/simplepdf/README.md b/packages/pieces/community/simplepdf/README.md new file mode 100644 index 0000000..27430a1 --- /dev/null +++ b/packages/pieces/community/simplepdf/README.md @@ -0,0 +1,7 @@ +# pieces-simplepdf + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-simplepdf` to build the library. diff --git a/packages/pieces/community/simplepdf/package.json b/packages/pieces/community/simplepdf/package.json new file mode 100644 index 0000000..f3d1c91 --- /dev/null +++ b/packages/pieces/community/simplepdf/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-simplepdf", + "version": "1.0.4" +} \ No newline at end of file diff --git a/packages/pieces/community/simplepdf/project.json b/packages/pieces/community/simplepdf/project.json new file mode 100644 index 0000000..36d3fb6 --- /dev/null +++ b/packages/pieces/community/simplepdf/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-simplepdf", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/simplepdf/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/simplepdf", + "tsConfig": "packages/pieces/community/simplepdf/tsconfig.lib.json", + "packageJson": "packages/pieces/community/simplepdf/package.json", + "main": "packages/pieces/community/simplepdf/src/index.ts", + "assets": [ + "packages/pieces/community/simplepdf/*.md", + { + "input": "packages/pieces/community/simplepdf/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-simplepdf {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/simplepdf/src/index.ts b/packages/pieces/community/simplepdf/src/index.ts new file mode 100644 index 0000000..55f174b --- /dev/null +++ b/packages/pieces/community/simplepdf/src/index.ts @@ -0,0 +1,15 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { simplePDFNewSubmission } from './lib/triggers/new-submission'; + +export const simplepdf = createPiece({ + displayName: 'SimplePDF', + description: 'PDF editing and generation tool', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/simplepdf.png', + authors: ["bendersej","kishanprmr","khaledmashaly","abuaboud"], + categories: [PieceCategory.CONTENT_AND_FILES], + actions: [], + triggers: [simplePDFNewSubmission], +}); diff --git a/packages/pieces/community/simplepdf/src/lib/triggers/new-submission.ts b/packages/pieces/community/simplepdf/src/lib/triggers/new-submission.ts new file mode 100644 index 0000000..8fddda2 --- /dev/null +++ b/packages/pieces/community/simplepdf/src/lib/triggers/new-submission.ts @@ -0,0 +1,58 @@ +import { + createTrigger, + PieceAuth, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; + +const markdown = ` +- Paste this URL in the webhook integration endpoint: +\`\`\`text +{{webhookUrl}} +\`\`\` +- Click update (keep other settings unchanged) +
+
+ +_[Read more about configuring webhooks](https://simplepdf.eu/help/how-to/configure-webhooks-pdf-form-submissions)_ +`; + +export const simplePDFNewSubmission = createTrigger({ + name: 'new-submission', + displayName: 'New Submission', + auth: PieceAuth.None(), + description: 'Triggers when a form receives a new submission', + props: { + md: Property.MarkDown({ + value: markdown, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: { + document: { + id: 'b7615a68-9e1f-4eac-bd20-5e80632a4d9e', + name: 'your_document.pdf', + }, + submission: { + id: '80146d5b-a068-490f-8eb9-fe393ba11396', + submitted_at: '2023-06-04T11:54:58.995Z', + url: 'https://cdn.simplepdf.eu/simple-pdf/assets/webhooks-playground.pdf', + }, + context: { + environment: 'production', + customer_id: '123', + }, + }, + async onEnable(context) { + // Empty + }, + async onDisable(context) { + // Empty + }, + async run(context) { + const payloadBody = context.payload.body as + | Record + | undefined; + return [payloadBody?.['data'] ?? {}]; + }, +}); diff --git a/packages/pieces/community/simplepdf/tsconfig.json b/packages/pieces/community/simplepdf/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/simplepdf/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/simplepdf/tsconfig.lib.json b/packages/pieces/community/simplepdf/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/simplepdf/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/skyvern/.eslintrc.json b/packages/pieces/community/skyvern/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/skyvern/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/skyvern/README.md b/packages/pieces/community/skyvern/README.md new file mode 100644 index 0000000..7704583 --- /dev/null +++ b/packages/pieces/community/skyvern/README.md @@ -0,0 +1,7 @@ +# pieces-skyvern + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-skyvern` to build the library. diff --git a/packages/pieces/community/skyvern/package.json b/packages/pieces/community/skyvern/package.json new file mode 100644 index 0000000..424b898 --- /dev/null +++ b/packages/pieces/community/skyvern/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-skyvern", + "version": "0.0.1" +} diff --git a/packages/pieces/community/skyvern/project.json b/packages/pieces/community/skyvern/project.json new file mode 100644 index 0000000..ce73d04 --- /dev/null +++ b/packages/pieces/community/skyvern/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-skyvern", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/skyvern/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/skyvern", + "tsConfig": "packages/pieces/community/skyvern/tsconfig.lib.json", + "packageJson": "packages/pieces/community/skyvern/package.json", + "main": "packages/pieces/community/skyvern/src/index.ts", + "assets": [ + "packages/pieces/community/skyvern/*.md", + { + "input": "packages/pieces/community/skyvern/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/skyvern/src/index.ts b/packages/pieces/community/skyvern/src/index.ts new file mode 100644 index 0000000..1826eec --- /dev/null +++ b/packages/pieces/community/skyvern/src/index.ts @@ -0,0 +1,34 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece } from '@activepieces/pieces-framework'; +import { cancelRunAction } from './lib/actions/cancel-run'; +import { findWorkflowAction } from './lib/actions/find-workflow'; +import { getRunAction } from './lib/actions/get-run'; +import { runAgentTaskAction } from './lib/actions/run-agent-task'; +import { runWorkflowAction } from './lib/actions/run-workflow'; +import { skyvernAuth } from './lib/common/auth'; +import { BASE_URL } from './lib/common/client'; + +export const skyvern = createPiece({ + displayName: 'Skyvern', + auth: skyvernAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/skyvern.jpg', + authors: ['rimjhimyadav','kishanprmr'], + actions: [ + runAgentTaskAction, + runWorkflowAction, + cancelRunAction, + getRunAction, + findWorkflowAction, + createCustomApiCallAction({ + auth: skyvernAuth, + baseUrl: () => BASE_URL, + authMapping: async (auth) => { + return { + 'x-api-key': auth as string, + }; + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/skyvern/src/lib/actions/cancel-run.ts b/packages/pieces/community/skyvern/src/lib/actions/cancel-run.ts new file mode 100644 index 0000000..9b3cf6d --- /dev/null +++ b/packages/pieces/community/skyvern/src/lib/actions/cancel-run.ts @@ -0,0 +1,28 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { skyvernAuth } from '../common/auth'; +import { skyvernApiCall } from '../common/client'; + +export const cancelRunAction = createAction({ + auth: skyvernAuth, + name: 'cancel-run', + displayName: 'Cancel Run', + description: 'Cancels a workflow or or task run by ID.', + props: { + runId: Property.ShortText({ + displayName: 'Workflow/Task Run ID', + required: true, + }), + }, + async run(context) { + const { runId } = context.propsValue; + + const response = await skyvernApiCall({ + apiKey: context.auth, + method: HttpMethod.POST, + resourceUri: `/runs/${runId}/cancel`, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/skyvern/src/lib/actions/find-workflow.ts b/packages/pieces/community/skyvern/src/lib/actions/find-workflow.ts new file mode 100644 index 0000000..7c4715b --- /dev/null +++ b/packages/pieces/community/skyvern/src/lib/actions/find-workflow.ts @@ -0,0 +1,50 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { isNil } from '@activepieces/shared'; +import { skyvernAuth } from '../common/auth'; +import { skyvernApiCall } from '../common/client'; +import { ListWorkflowResponse } from '../common/props'; + +export const findWorkflowAction = createAction({ + auth: skyvernAuth, + name: 'find-workflow', + displayName: 'Find Workflow', + description: 'Finds workflow based on title.', + props: { + title: Property.ShortText({ + displayName: 'Workflow Title', + required: true, + }), + }, + async run(context) { + const { title } = context.propsValue; + + let hasMore = true; + let page = 1; + const workflows = []; + + do { + const response = await skyvernApiCall({ + apiKey: context.auth as string, + method: HttpMethod.GET, + resourceUri: '/workflows', + query: { + page_size: 100, + page, + title, + }, + }); + + if (isNil(response) || !Array.isArray(response)) break; + workflows.push(...response); + + hasMore = response.length > 0; + page++; + } while (hasMore); + + return { + found: workflows.length > 0, + result: workflows, + }; + }, +}); diff --git a/packages/pieces/community/skyvern/src/lib/actions/get-run.ts b/packages/pieces/community/skyvern/src/lib/actions/get-run.ts new file mode 100644 index 0000000..0c6c84d --- /dev/null +++ b/packages/pieces/community/skyvern/src/lib/actions/get-run.ts @@ -0,0 +1,28 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { skyvernAuth } from '../common/auth'; +import { skyvernApiCall } from '../common/client'; + +export const getRunAction = createAction({ + auth: skyvernAuth, + name: 'get-run', + displayName: 'Get Workflow/Task Run', + description: 'Retrieves a workflow or task run by ID.', + props: { + runId: Property.ShortText({ + displayName: 'Workflow/Task Run ID', + required: true, + }), + }, + async run(context) { + const { runId } = context.propsValue; + + const response = await skyvernApiCall({ + apiKey: context.auth, + method: HttpMethod.GET, + resourceUri: `/runs/${runId}`, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/skyvern/src/lib/actions/run-agent-task.ts b/packages/pieces/community/skyvern/src/lib/actions/run-agent-task.ts new file mode 100644 index 0000000..1847fcf --- /dev/null +++ b/packages/pieces/community/skyvern/src/lib/actions/run-agent-task.ts @@ -0,0 +1,100 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { skyvernAuth } from '../common/auth'; +import { skyvernApiCall } from '../common/client'; + +export const runAgentTaskAction = createAction({ + auth: skyvernAuth, + name: 'run-agent-task', + displayName: 'Run Agent Task', + description: 'Runs task with specified prompt.', + props: { + prompt: Property.ShortText({ + displayName: 'Prompt', + description: 'The goal or task description for Skyvern to accomplish.', + required: true, + }), + url: Property.ShortText({ + displayName: 'URL', + description: 'The starting URL for the task.', + required: false, + }), + engine: Property.StaticDropdown({ + displayName: 'Engine', + required: false, + options: { + disabled: false, + options: [ + { label: 'Skyvern 1.0', value: 'skyvern-1.0' }, + { label: 'Skyvern 2.0', value: 'skyvern-2.0' }, + { label: 'OpenAI CUA', value: 'openai-cua' }, + { label: 'Anthropic CUA', value: 'anthropic-cua' }, + ], + }, + }), + webhookUrl: Property.ShortText({ + displayName: 'Webhook Callback URL', + description: 'URL to send task status updates to after a run is finished.', + required: false, + }), + proxyLocation: Property.StaticDropdown({ + displayName: 'Proxy Location', + required: false, + options: { + disabled: false, + options: [ + { label: 'Residential', value: 'RESIDENTIAL' }, + { label: 'Spain', value: 'RESIDENTIAL_ES' }, + { label: 'Ireland', value: 'RESIDENTIAL_IE' }, + { label: 'United Kingdom', value: 'RESIDENTIAL_GB' }, + { label: 'India', value: 'RESIDENTIAL_IN' }, + { label: 'Japan', value: 'RESIDENTIAL_JP' }, + { label: 'France', value: 'RESIDENTIAL_FR' }, + { label: 'Germany', value: 'RESIDENTIAL_DE' }, + { label: 'New Zealand', value: 'RESIDENTIAL_NZ' }, + { label: 'South Africa', value: 'RESIDENTIAL_ZA' }, + { label: 'Argentina', value: 'RESIDENTIAL_AR' }, + { label: 'ISP Proxy', value: 'RESIDENTIAL_ISP' }, + { label: 'California (US)', value: 'US-CA' }, + { label: 'New York (US)', value: 'US-NY' }, + { label: 'Texas (US)', value: 'US-TX' }, + { label: 'Florida (US)', value: 'US-FL' }, + { label: 'Washington (US)', value: 'US-WA' }, + { label: 'No Proxy', value: 'NONE' }, + ], + }, + }), + maxSteps: Property.Number({ + displayName: 'Max Steps', + required: false, + description: + 'Maximum number of steps the task can take. Task will fail if it exceeds this number.', + }), + dataExtractionSchema: Property.Json({ + displayName: 'Data Extraction Schema', + required: false, + description: 'The schema for data to be extracted from the webpage.', + }), + }, + async run(context) { + const { prompt, proxyLocation, url, webhookUrl, dataExtractionSchema, maxSteps, engine } = + context.propsValue; + + const response = await skyvernApiCall({ + apiKey: context.auth, + method: HttpMethod.POST, + resourceUri: '/run/tasks', + body: { + prompt, + url, + engine, + proxy_location: proxyLocation, + data_extraction_schema: dataExtractionSchema, + max_steps: maxSteps, + webhook_url: webhookUrl, + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/skyvern/src/lib/actions/run-workflow.ts b/packages/pieces/community/skyvern/src/lib/actions/run-workflow.ts new file mode 100644 index 0000000..e538a8e --- /dev/null +++ b/packages/pieces/community/skyvern/src/lib/actions/run-workflow.ts @@ -0,0 +1,69 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { skyvernAuth } from '../common/auth'; +import { skyvernApiCall } from '../common/client'; +import { workflowId, workflowParams } from '../common/props'; + +export const runWorkflowAction = createAction({ + auth: skyvernAuth, + name: 'run-workflow', + displayName: 'Run Workflow', + description: 'Runs the workflow.', + props: { + workflowId: workflowId, + title: Property.ShortText({ + displayName: 'Workflow Run Title', + required: false, + description: 'The title for this workflow run.', + }), + proxyLocation: Property.StaticDropdown({ + displayName: 'Proxy Location', + required: false, + options: { + disabled: false, + options: [ + { label: 'Residential', value: 'RESIDENTIAL' }, + { label: 'Spain', value: 'RESIDENTIAL_ES' }, + { label: 'Ireland', value: 'RESIDENTIAL_IE' }, + { label: 'United Kingdom', value: 'RESIDENTIAL_GB' }, + { label: 'India', value: 'RESIDENTIAL_IN' }, + { label: 'Japan', value: 'RESIDENTIAL_JP' }, + { label: 'France', value: 'RESIDENTIAL_FR' }, + { label: 'Germany', value: 'RESIDENTIAL_DE' }, + { label: 'New Zealand', value: 'RESIDENTIAL_NZ' }, + { label: 'South Africa', value: 'RESIDENTIAL_ZA' }, + { label: 'Argentina', value: 'RESIDENTIAL_AR' }, + { label: 'ISP Proxy', value: 'RESIDENTIAL_ISP' }, + { label: 'California (US)', value: 'US-CA' }, + { label: 'New York (US)', value: 'US-NY' }, + { label: 'Texas (US)', value: 'US-TX' }, + { label: 'Florida (US)', value: 'US-FL' }, + { label: 'Washington (US)', value: 'US-WA' }, + { label: 'No Proxy', value: 'NONE' }, + ], + }, + }), + webhookUrl: Property.ShortText({ + displayName: 'Webhook Callback URL', + required: false, + }), + parameters: workflowParams, + }, + async run(context) { + const { workflowId, webhookUrl, proxyLocation, parameters } = context.propsValue; + + const response = await skyvernApiCall({ + apiKey: context.auth, + method: HttpMethod.POST, + resourceUri: `/run/workflows`, + body: { + workflow_id: workflowId, + proxy_location: proxyLocation, + parameters, + webhook_url: webhookUrl, + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/skyvern/src/lib/common/auth.ts b/packages/pieces/community/skyvern/src/lib/common/auth.ts new file mode 100644 index 0000000..810864a --- /dev/null +++ b/packages/pieces/community/skyvern/src/lib/common/auth.ts @@ -0,0 +1,24 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { PieceAuth } from '@activepieces/pieces-framework'; +import { skyvernApiCall } from './client'; + +export const skyvernAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: `You can obtain your API key by navigating to [Settings](https://app.skyvern.com/settings).`, + required: true, + validate: async ({ auth }) => { + try { + await skyvernApiCall({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: '/workflows', + }); + return { valid: true }; + } catch { + return { + valid: false, + error: 'Invalid API Key.', + }; + } + }, +}); diff --git a/packages/pieces/community/skyvern/src/lib/common/client.ts b/packages/pieces/community/skyvern/src/lib/common/client.ts new file mode 100644 index 0000000..6cc4c5a --- /dev/null +++ b/packages/pieces/community/skyvern/src/lib/common/client.ts @@ -0,0 +1,48 @@ +import { + httpClient, + HttpMessageBody, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; + +export type SkyvernApiCallParams = { + apiKey: string; + method: HttpMethod; + resourceUri: string; + query?: Record; + body?: any; +}; + +export const BASE_URL = 'https://api.skyvern.com/v1'; + +export async function skyvernApiCall({ + apiKey, + method, + resourceUri, + query, + body, +}: SkyvernApiCallParams): Promise { + const qs: QueryParams = {}; + + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + qs[key] = String(value); + } + } + } + + const request: HttpRequest = { + method, + url: BASE_URL + resourceUri, + headers: { + 'x-api-key': apiKey, + }, + queryParams: qs, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} diff --git a/packages/pieces/community/skyvern/src/lib/common/props.ts b/packages/pieces/community/skyvern/src/lib/common/props.ts new file mode 100644 index 0000000..8a127e6 --- /dev/null +++ b/packages/pieces/community/skyvern/src/lib/common/props.ts @@ -0,0 +1,172 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { isNil } from '@activepieces/shared'; +import { skyvernApiCall } from './client'; + +export interface ListWorkflowResponse { + workflow_permanent_id: string; + title: string; + workflow_definition: { + parameters: { + parameter_type: string; + key: string; + workflow_parameter_type: string; + default_value: any; + }[]; + }; +} + +export const workflowId = Property.Dropdown({ + displayName: 'Workflow', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + let hasMore = true; + let page = 1; + const workflows = []; + + do { + const response = await skyvernApiCall({ + apiKey: auth as string, + method: HttpMethod.GET, + resourceUri: '/workflows', + query: { + page_size: 100, + page, + }, + }); + + if (isNil(response) || !Array.isArray(response)) break; + workflows.push(...response); + + hasMore = response.length > 0; + page++; + } while (hasMore); + + return { + disabled: false, + options: workflows.map((workflow) => ({ + label: workflow.title, + value: workflow.workflow_permanent_id, + })), + }; + }, +}); + +export const workflowParams = Property.DynamicProperties({ + displayName: 'Workflow Params', + refreshers: ['workflowId'], + required: false, + props: async ({ auth, workflowId }) => { + if (!auth || !workflowId) return {}; + + let hasMore = true; + let page = 1; + let matchedWorkflow: ListWorkflowResponse | undefined = undefined; + + do { + const response = await skyvernApiCall({ + apiKey: auth as unknown as string, + method: HttpMethod.GET, + resourceUri: '/workflows', + query: { + page_size: 100, + page, + }, + }); + + if (isNil(response) || !Array.isArray(response)) break; + + matchedWorkflow = response.find( + (workflow) => workflow.workflow_permanent_id === (workflowId as unknown as string), + ); + + if (matchedWorkflow) break; + + hasMore = response.length > 0; + page++; + } while (hasMore); + + if (isNil(matchedWorkflow)) return {}; + + const fields: DynamicPropsValue = {}; + + for (const field of matchedWorkflow.workflow_definition.parameters) { + if (field.parameter_type !== 'workflow') continue; + + const { key, workflow_parameter_type, default_value: defaultValue } = field; + const required = isNil(defaultValue); + const displayName = key; + + if (workflow_parameter_type === 'credential_id') { + const response = await skyvernApiCall<{ credential_id: string; name: string }[]>({ + apiKey: auth as unknown as string, + method: HttpMethod.GET, + resourceUri: '/credentials', + query: { + page_size: 100, + }, + }); + const credentials = response || []; + fields[field.key] = Property.StaticDropdown({ + displayName, + required, + defaultValue, + options: { + disabled: false, + options: credentials.map((cred) => ({ + label: cred.name, + value: cred.credential_id, + })), + }, + }); + + continue; + } + + switch (workflow_parameter_type) { + case 'string': + case 'file_url': + fields[field.key] = Property.ShortText({ + displayName, + required, + defaultValue, + }); + break; + case 'float': + case 'integer': + fields[field.key] = Property.Number({ + displayName, + required, + defaultValue, + }); + break; + case 'boolean': + fields[field.key] = Property.Checkbox({ + displayName, + required, + defaultValue, + }); + break; + case 'json': + fields[field.key] = Property.Json({ + displayName, + required, + defaultValue, + }); + break; + default: + break; + } + } + return fields; + }, +}); diff --git a/packages/pieces/community/skyvern/tsconfig.json b/packages/pieces/community/skyvern/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/skyvern/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/skyvern/tsconfig.lib.json b/packages/pieces/community/skyvern/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/skyvern/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/slack/.eslintrc.json b/packages/pieces/community/slack/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/slack/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/slack/README.md b/packages/pieces/community/slack/README.md new file mode 100644 index 0000000..6f1545e --- /dev/null +++ b/packages/pieces/community/slack/README.md @@ -0,0 +1,7 @@ +# pieces-slack + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-slack` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/slack/package.json b/packages/pieces/community/slack/package.json new file mode 100644 index 0000000..8b2ef5d --- /dev/null +++ b/packages/pieces/community/slack/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-slack", + "version": "0.9.3" +} diff --git a/packages/pieces/community/slack/project.json b/packages/pieces/community/slack/project.json new file mode 100644 index 0000000..6c793b3 --- /dev/null +++ b/packages/pieces/community/slack/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-slack", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/slack/src", + "projectType": "library", + "targets": { + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + }, + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/slack", + "tsConfig": "packages/pieces/community/slack/tsconfig.lib.json", + "packageJson": "packages/pieces/community/slack/package.json", + "main": "packages/pieces/community/slack/src/index.ts", + "assets": [ + "packages/pieces/community/slack/*.md", + { + "input": "packages/pieces/community/slack/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/slack/src/index.ts b/packages/pieces/community/slack/src/index.ts new file mode 100644 index 0000000..75a89a7 --- /dev/null +++ b/packages/pieces/community/slack/src/index.ts @@ -0,0 +1,203 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import crypto from 'node:crypto'; +import { requestActionDirectMessageAction } from './lib/actions/request-action-direct-message'; +import { requestActionMessageAction } from './lib/actions/request-action-message'; +import { requestApprovalDirectMessageAction } from './lib/actions/request-approval-direct-message'; +import { requestSendApprovalMessageAction } from './lib/actions/request-approval-message'; +import { slackSendDirectMessageAction } from './lib/actions/send-direct-message-action'; +import { slackSendMessageAction } from './lib/actions/send-message-action'; +import { newReactionAdded } from './lib/triggers/new-reaction-added'; +import { uploadFile } from './lib/actions/upload-file'; +import { searchMessages } from './lib/actions/search-messages'; +import { updateMessage } from './lib/actions/update-message'; +import { findUserByEmailAction } from './lib/actions/find-user-by-email'; +import { updateProfileAction } from './lib/actions/update-profile'; +import { createChannelAction } from './lib/actions/create-channel'; +import { channelCreated } from './lib/triggers/new-channel'; +import { addRectionToMessageAction } from './lib/actions/add-reaction-to-message'; +import { getChannelHistory } from './lib/actions/get-channel-history'; +import { findUserByHandleAction } from './lib/actions/find-user-by-handle'; +import { setUserStatusAction } from './lib/actions/set-user-status'; +import { newMention } from './lib/triggers/new-mention'; +import { markdownToSlackFormat } from './lib/actions/markdown-to-slack-format'; +import { newCommand } from './lib/triggers/new-command'; +import { getFileAction } from './lib/actions/get-file'; +import { newMessageTrigger } from './lib/triggers/new-message'; +import { newMessageInChannelTrigger } from './lib/triggers/new-message-in-channel'; +import { newDirectMessageTrigger } from './lib/triggers/new-direct-message'; +import { retrieveThreadMessages } from './lib/actions/retrieve-thread-messages'; +import { newMentionInDirectMessageTrigger } from './lib/triggers/new-mention-in-direct-message'; +import { newCommandInDirectMessageTrigger } from './lib/triggers/new-command-in-direct-message'; +import { setChannelTopicAction } from './lib/actions/set-channel-topic'; +import { getMessageAction } from './lib/actions/get-message'; +import { findUserByIdAction } from './lib/actions/find-user-by-id'; +import { newUserTrigger } from './lib/triggers/new-user'; +import { newSavedMessageTrigger } from './lib/triggers/new-saved-message'; +import { newTeamCustomEmojiTrigger } from './lib/triggers/new-team-custom-emoji'; +import { inviteUserToChannelAction } from './lib/actions/invite-user-to-channel'; + +export const slackAuth = PieceAuth.OAuth2({ + description: '', + authUrl: + 'https://slack.com/oauth/v2/authorize?user_scope=search:read,users.profile:write,reactions:read,im:history,stars:read,channels:write,groups:write,im:write,mpim:write,channels:write.invites,groups:write.invites,channels:history,groups:history,chat:write,users:read', + tokenUrl: 'https://slack.com/api/oauth.v2.access', + required: true, + scope: [ + 'channels:read', + 'channels:manage', + 'channels:history', + 'chat:write', + 'groups:read', + 'groups:write', + 'groups:history', + 'reactions:read', + 'mpim:read', + 'mpim:write', + 'mpim:history', + 'im:write', + 'im:read', + 'im:history', + 'users:read', + 'files:write', + 'files:read', + 'users:read.email', + 'reactions:write', + 'usergroups:read', + 'chat:write.customize', + 'links:read', + 'links:write', + 'emoji:read', + 'users.profile:read', + 'channels:write.invites', + 'groups:write.invites' + ], +}); + +export const slack = createPiece({ + displayName: 'Slack', + description: 'Channel-based messaging platform', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/slack.png', + categories: [PieceCategory.COMMUNICATION], + auth: slackAuth, + events: { + parseAndReply: ({ payload }) => { + const payloadBody = payload.body as PayloadBody; + if (payloadBody.challenge) { + return { + reply: { + body: payloadBody['challenge'], + headers: {}, + }, + }; + } + return { + event: payloadBody?.event?.type, + identifierValue: payloadBody.team_id, + }; + }, + verify: ({ webhookSecret, payload }) => { + // Construct the signature base string + const timestamp = payload.headers['x-slack-request-timestamp']; + const signature = payload.headers['x-slack-signature']; + const signatureBaseString = `v0:${timestamp}:${payload.rawBody}`; + const hmac = crypto.createHmac('sha256', webhookSecret as string); + hmac.update(signatureBaseString); + const computedSignature = `v0=${hmac.digest('hex')}`; + return signature === computedSignature; + }, + }, + authors: [ + 'rita-gorokhod', + 'AdamSelene', + 'Abdallah-Alwarawreh', + 'kishanprmr', + 'MoShizzle', + 'AbdulTheActivePiecer', + 'khaledmashaly', + 'abuaboud', + ], + actions: [ + addRectionToMessageAction, + slackSendDirectMessageAction, + slackSendMessageAction, + requestApprovalDirectMessageAction, + requestSendApprovalMessageAction, + requestActionDirectMessageAction, + requestActionMessageAction, + uploadFile, + getFileAction, + searchMessages, + findUserByEmailAction, + findUserByHandleAction, + findUserByIdAction, + updateMessage, + createChannelAction, + updateProfileAction, + getChannelHistory, + setUserStatusAction, + markdownToSlackFormat, + retrieveThreadMessages, + setChannelTopicAction, + getMessageAction, + inviteUserToChannelAction, + createCustomApiCallAction({ + baseUrl: () => { + return 'https://slack.com/api'; + }, + auth: slackAuth, + authMapping: async (auth, propsValue) => { + if (propsValue.useUserToken) { + return { + Authorization: `Bearer ${ + (auth as OAuth2PropertyValue).data['authed_user']?.access_token + }`, + }; + } else { + return { + Authorization: `Bearer ${ + (auth as OAuth2PropertyValue).access_token + }`, + }; + } + }, + extraProps: { + useUserToken: Property.Checkbox({ + displayName: 'Use user token', + description: 'Use user token instead of bot token', + required: true, + defaultValue: false, + }), + }, + }), + ], + triggers: [ + newMessageTrigger, + newMessageInChannelTrigger, + newDirectMessageTrigger, + newMention, + newMentionInDirectMessageTrigger, + newReactionAdded, + channelCreated, + newCommand, + newCommandInDirectMessageTrigger, + newUserTrigger, + newSavedMessageTrigger, + newTeamCustomEmojiTrigger, + ], +}); + +type PayloadBody = { + challenge: string; + event: { + type: string; + }; + team_id: string; +}; diff --git a/packages/pieces/community/slack/src/lib/actions/add-reaction-to-message.ts b/packages/pieces/community/slack/src/lib/actions/add-reaction-to-message.ts new file mode 100644 index 0000000..b3f637b --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/add-reaction-to-message.ts @@ -0,0 +1,49 @@ +import { slackAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { singleSelectChannelInfo, slackChannel } from '../common/props'; + +import { WebClient } from '@slack/web-api'; +import { processMessageTimestamp } from '../common/utils'; + +export const addRectionToMessageAction = createAction({ + auth: slackAuth, + name: 'slack-add-reaction-to-message', + displayName: 'Add Reaction to Message', + description: 'Add an emoji reaction to a message.', + + props: { + info: singleSelectChannelInfo, + channel: slackChannel(true), + ts: Property.ShortText({ + displayName: 'Message Timestamp', + description: + 'Please provide the timestamp of the message you wish to react, such as `1710304378.475129`. Alternatively, you can easily obtain the message link by clicking on the three dots next to the message and selecting the `Copy link` option.', + required: true, + }), + reaction: Property.ShortText({ + displayName: 'Reaction (emoji) name', + required: true, + description: 'e.g.`thumbsup`', + }), + }, + + async run(context) { + const { channel, ts, reaction } = context.propsValue; + + const slack = new WebClient(context.auth.access_token); + + const messageTimestamp = processMessageTimestamp(ts); + + if (messageTimestamp) { + const response = await slack.reactions.add({ + channel, + timestamp: messageTimestamp, + name: reaction, + }); + + return response; + } else { + throw new Error('Invalid Timestamp Value.'); + } + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/create-channel.ts b/packages/pieces/community/slack/src/lib/actions/create-channel.ts new file mode 100644 index 0000000..51bca28 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/create-channel.ts @@ -0,0 +1,28 @@ +import { slackAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { WebClient } from '@slack/web-api'; + +export const createChannelAction = createAction({ + auth: slackAuth, + name: 'slack-create-channel', + displayName: 'Create Channel', + description: 'Creates a new channel.', + props: { + channelName: Property.ShortText({ + displayName: 'Channel Name', + required: true, + }), + isPrivate: Property.Checkbox({ + displayName: 'Is Private?', + required: false, + defaultValue: false, + }), + }, + async run({ auth, propsValue }) { + const client = new WebClient(auth.access_token); + return await client.conversations.create({ + name: propsValue.channelName, + is_private: propsValue.isPrivate, + }); + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/find-user-by-email.ts b/packages/pieces/community/slack/src/lib/actions/find-user-by-email.ts new file mode 100644 index 0000000..41f6bb8 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/find-user-by-email.ts @@ -0,0 +1,23 @@ +import { slackAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { WebClient } from '@slack/web-api'; + +export const findUserByEmailAction = createAction({ + auth: slackAuth, + name: 'slack-find-user-by-email', + displayName: 'Find User by Email', + description: 'Finds a user by matching against their email address.', + props: { + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + }, + async run({ auth, propsValue }) { + const email = propsValue.email; + const client = new WebClient(auth.access_token); + return await client.users.lookupByEmail({ + email, + }); + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/find-user-by-handle.ts b/packages/pieces/community/slack/src/lib/actions/find-user-by-handle.ts new file mode 100644 index 0000000..cbeb0f5 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/find-user-by-handle.ts @@ -0,0 +1,35 @@ +import { slackAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { UsersListResponse, WebClient } from '@slack/web-api'; + +export const findUserByHandleAction = createAction({ + auth: slackAuth, + name: 'slack-find-user-by-handle', + displayName: 'Find User by Handle', + description: 'Finds a user by matching against their Slack handle.', + props: { + handle: Property.ShortText({ + displayName: 'Handle', + description: 'User handle (display name), without the leading @', + required: true, + }), + }, + async run({ auth, propsValue }) { + const handle = propsValue.handle.replace('@', ''); + const client = new WebClient(auth.access_token); + for await (const page of client.paginate('users.list', { + limit: 1000, // Only limits page size, not total number of results + })) { + const response = page as UsersListResponse; + if (response.members) { + const matchedMember = response.members.find( + (member) => member.profile?.display_name === handle + ); + if (matchedMember) { + return matchedMember; + } + } + } + throw new Error(`Could not find user with handle @${handle}`); + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/find-user-by-id.ts b/packages/pieces/community/slack/src/lib/actions/find-user-by-id.ts new file mode 100644 index 0000000..b3f9d9f --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/find-user-by-id.ts @@ -0,0 +1,22 @@ +import { slackAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { WebClient } from '@slack/web-api'; + +export const findUserByIdAction = createAction({ + auth: slackAuth, + name: 'find-user-by-id', + displayName: 'Find User by ID', + description: 'Finds a user by their ID.', + props: { + id: Property.ShortText({ + displayName: 'ID', + required: true, + }), + }, + async run({ auth, propsValue }) { + const client = new WebClient(auth.access_token); + return await client.users.profile.get({ + user: propsValue.id, + }); + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/get-channel-history.ts b/packages/pieces/community/slack/src/lib/actions/get-channel-history.ts new file mode 100644 index 0000000..68fc23f --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/get-channel-history.ts @@ -0,0 +1,61 @@ +import { ConversationsHistoryResponse, WebClient } from '@slack/web-api'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { slackAuth } from '../..'; +import { singleSelectChannelInfo, slackChannel } from '../common/props'; + +export const getChannelHistory = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'getChannelHistory', + auth: slackAuth, + displayName: 'Get channel history', + description: + 'Retrieve all messages from a specific channel ("conversation") between specified timestamps', + props: { + info: singleSelectChannelInfo, + channel: slackChannel(true), + oldest: Property.Number({ + displayName: 'Oldest', + description: + 'Only messages after this timestamp will be included in results', + required: false, + }), + latest: Property.Number({ + displayName: 'Latest', + description: + 'Only messages before this timestamp will be included in results. Default is the current time', + required: false, + }), + inclusive: Property.Checkbox({ + displayName: 'Inclusive', + description: + 'Include messages with oldest or latest timestamps in results. Ignored unless either timestamp is specified', + defaultValue: false, + required: true, + }), + includeAllMetadata: Property.Checkbox({ + displayName: 'Include all metadata', + description: 'Return all metadata associated with each message', + defaultValue: false, + required: true, + }), + }, + async run({ auth, propsValue }) { + const client = new WebClient(auth.access_token); + const messages = []; + await client.conversations.history({ channel: propsValue.channel }); + for await (const page of client.paginate('conversations.history', { + channel: propsValue.channel, + oldest: propsValue.oldest, + latest: propsValue.latest, + limit: 200, // page size, does not limit the total number of results + include_all_metadata: propsValue.includeAllMetadata, + inclusive: propsValue.inclusive, + })) { + const response = page as ConversationsHistoryResponse; + if (response.messages) { + messages.push(...response.messages); + } + } + return messages; + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/get-file.ts b/packages/pieces/community/slack/src/lib/actions/get-file.ts new file mode 100644 index 0000000..2993d91 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/get-file.ts @@ -0,0 +1,47 @@ +import { slackAuth } from '../../index'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { WebClient } from '@slack/web-api'; + +export const getFileAction = createAction({ + auth: slackAuth, + name: 'get-file', + displayName: 'Get File', + description: 'Return information about a given file ID.', + props: { + fileId: Property.ShortText({ + displayName: 'File ID', + required: true, + description: 'You can pass the file ID from the New Message Trigger payload.', + }), + }, + async run(context) { + const client = new WebClient(context.auth.access_token); + + const fileData = await client.files.info({ file: context.propsValue.fileId }); + + const fileDownloadUrl = fileData.file?.url_private_download; + + if (!fileDownloadUrl) { + throw new Error('Unable to find the download URL.'); + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: fileDownloadUrl, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + responseType: 'arraybuffer', + }); + + return { + ...fileData.file, + data: await context.files.write({ + fileName: fileData.file?.name || `file`, + data: Buffer.from(response.body), + }), + }; + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/get-message.ts b/packages/pieces/community/slack/src/lib/actions/get-message.ts new file mode 100644 index 0000000..fcbd72b --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/get-message.ts @@ -0,0 +1,36 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { slackAuth } from '../..'; +import { singleSelectChannelInfo, slackChannel } from '../common/props'; +import { processMessageTimestamp } from '../common/utils'; +import { WebClient } from '@slack/web-api'; + +export const getMessageAction = createAction({ + name: 'get-message', + displayName: 'Get Message by Timestamp', + description: `Retrieves a specific message from a channel history using the message's timestamp.`, + auth: slackAuth, + props: { + info: singleSelectChannelInfo, + channel: slackChannel(true), + ts: Property.ShortText({ + displayName: 'Message Timestamp', + description: + 'Please provide the timestamp of the message you wish to retrieve, such as `1710304378.475129`. Alternatively, you can easily obtain the message link by clicking on the three dots next to the message and selecting the `Copy link` option.', + required: true, + }), + }, + async run({ auth, propsValue }) { + const messageTimestamp = processMessageTimestamp(propsValue.ts); + if (!messageTimestamp) { + throw new Error('Invalid Timestamp Value.'); + } + const client = new WebClient(auth.access_token); + + return await client.conversations.history({ + channel: propsValue.channel, + latest: messageTimestamp, + limit: 1, + inclusive: true, + }); + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/invite-user-to-channel.ts b/packages/pieces/community/slack/src/lib/actions/invite-user-to-channel.ts new file mode 100644 index 0000000..314ba4c --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/invite-user-to-channel.ts @@ -0,0 +1,24 @@ +import { slackAuth } from '../../index'; +import { createAction } from '@activepieces/pieces-framework'; +import { singleSelectChannelInfo, slackChannel, userId } from '../common/props'; +import { WebClient } from '@slack/web-api'; + +export const inviteUserToChannelAction = createAction({ + auth: slackAuth, + name: 'invite-user-to-channel', + displayName: 'Invite User to Channel', + description: 'Invites an existing User to an existing channel.', + props: { + info: singleSelectChannelInfo, + channel: slackChannel(true), + userId, + }, + async run(context) { + const client = new WebClient(context.auth.access_token); + + return await client.conversations.invite({ + channel: context.propsValue.channel, + users: `${context.propsValue.userId}`, + }); + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/markdown-to-slack-format.ts b/packages/pieces/community/slack/src/lib/actions/markdown-to-slack-format.ts new file mode 100644 index 0000000..2828906 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/markdown-to-slack-format.ts @@ -0,0 +1,24 @@ +import { + createAction, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import slackifyMarkdown from 'slackify-markdown'; + +export const markdownToSlackFormat = createAction({ + name: 'markdownToSlackFormat', + displayName: 'Markdown to Slack format', + description: + "Convert Markdown-formatted text to Slack's pseudo - markdown syntax", + requireAuth: false, + props: { + markdown: Property.LongText({ + displayName: 'Markdown text', + required: true, + }), + }, + + async run({ propsValue }) { + return slackifyMarkdown(propsValue.markdown); + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/request-action-direct-message.ts b/packages/pieces/community/slack/src/lib/actions/request-action-direct-message.ts new file mode 100644 index 0000000..39f5ad3 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/request-action-direct-message.ts @@ -0,0 +1,32 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { slackAuth } from '../..'; +import { assertNotNullOrUndefined } from '@activepieces/shared'; +import { + profilePicture, + text, + userId, + username, + actions, +} from '../common/props'; +import { requestAction } from '../common/request-action'; + +export const requestActionDirectMessageAction = createAction({ + auth: slackAuth, + name: 'request_action_direct_message', + displayName: 'Request Action from A User', + description: + 'Send a message to a user and wait until the user selects an action', + props: { + userId, + text, + actions, + username, + profilePicture, + }, + async run(context) { + const { userId } = context.propsValue; + assertNotNullOrUndefined(userId, 'userId'); + + return await requestAction(userId, context); + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/request-action-message.ts b/packages/pieces/community/slack/src/lib/actions/request-action-message.ts new file mode 100644 index 0000000..2c5266f --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/request-action-message.ts @@ -0,0 +1,34 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { slackAuth } from '../..'; +import { assertNotNullOrUndefined } from '@activepieces/shared'; +import { + profilePicture, + text, + slackChannel, + username, + actions, + singleSelectChannelInfo, +} from '../common/props'; +import { requestAction } from '../common/request-action'; + +export const requestActionMessageAction = createAction({ + auth: slackAuth, + name: 'request_action_message', + displayName: 'Request Action in A Channel', + description: + 'Send a message in a channel and wait until an action is selected', + props: { + info: singleSelectChannelInfo, + channel: slackChannel(true), + text, + actions, + username, + profilePicture, + }, + async run(context) { + const { channel } = context.propsValue; + assertNotNullOrUndefined(channel, 'channel'); + + return await requestAction(channel, context); + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/request-approval-direct-message.ts b/packages/pieces/community/slack/src/lib/actions/request-approval-direct-message.ts new file mode 100644 index 0000000..d5e3c1e --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/request-approval-direct-message.ts @@ -0,0 +1,93 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { slackSendMessage } from '../common/utils'; +import { slackAuth } from '../..'; +import { + assertNotNullOrUndefined, + ExecutionType, + PauseType, +} from '@activepieces/shared'; +import { profilePicture, text, userId, username } from '../common/props'; + +export const requestApprovalDirectMessageAction = createAction({ + auth: slackAuth, + name: 'request_approval_direct_message', + displayName: 'Request Approval from A User', + description: + 'Send approval message to a user and then wait until the message is approved or disapproved', + props: { + userId, + text, + username, + profilePicture, + }, + async run(context) { + if (context.executionType === ExecutionType.BEGIN) { + context.run.pause({ + pauseMetadata: { + type: PauseType.WEBHOOK, + response: {}, + }, + }); + const token = context.auth.access_token; + const { userId, username, profilePicture } = context.propsValue; + + assertNotNullOrUndefined(token, 'token'); + assertNotNullOrUndefined(text, 'text'); + assertNotNullOrUndefined(userId, 'userId'); + const approvalLink = context.generateResumeUrl({ + queryParams: { action: 'approve' }, + }); + const disapprovalLink = context.generateResumeUrl({ + queryParams: { action: 'disapprove' }, + }); + + await slackSendMessage({ + token, + text: `${context.propsValue.text}\n\nApprove: ${approvalLink}\n\nDisapprove: ${disapprovalLink}`, + username, + profilePicture, + blocks: [ + { + type: 'section', + text: { + type: 'mrkdwn', + text: `${context.propsValue.text}`, + }, + }, + { + type: 'actions', + block_id: 'actions', + elements: [ + { + type: 'button', + text: { + type: 'plain_text', + text: 'Approve', + }, + style: 'primary', + url: approvalLink, + }, + { + type: 'button', + text: { + type: 'plain_text', + text: 'Disapprove', + }, + style: 'danger', + url: disapprovalLink, + }, + ], + }, + ], + conversationId: userId, + }); + return { + approved: false, // default approval is false + }; + } else { + return { + approved: context.resumePayload.queryParams['action'] === 'approve', + }; + } + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/request-approval-message.ts b/packages/pieces/community/slack/src/lib/actions/request-approval-message.ts new file mode 100644 index 0000000..c1d92a1 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/request-approval-message.ts @@ -0,0 +1,101 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { slackSendMessage } from '../common/utils'; +import { slackAuth } from '../..'; +import { + assertNotNullOrUndefined, + ExecutionType, + PauseType, +} from '@activepieces/shared'; +import { + profilePicture, + singleSelectChannelInfo, + slackChannel, + text, + username, +} from '../common/props'; + +export const requestSendApprovalMessageAction = createAction({ + auth: slackAuth, + name: 'request_approval_message', + displayName: 'Request Approval in a Channel', + description: + 'Send approval message to a channel and then wait until the message is approved or disapproved', + props: { + info: singleSelectChannelInfo, + channel: slackChannel(true), + text, + username, + profilePicture, + }, + async run(context) { + if (context.executionType === ExecutionType.BEGIN) { + context.run.pause({ + pauseMetadata: { + type: PauseType.WEBHOOK, + response: {}, + }, + }); + const token = context.auth.access_token; + const { channel, username, profilePicture } = context.propsValue; + + assertNotNullOrUndefined(token, 'token'); + assertNotNullOrUndefined(text, 'text'); + assertNotNullOrUndefined(channel, 'channel'); + const approvalLink = context.generateResumeUrl({ + queryParams: { action: 'approve' }, + }); + const disapprovalLink = context.generateResumeUrl({ + queryParams: { action: 'disapprove' }, + }); + + await slackSendMessage({ + token, + text: `${context.propsValue.text}\n\nApprove: ${approvalLink}\n\nDisapprove: ${disapprovalLink}`, + username, + profilePicture, + blocks: [ + { + type: 'section', + text: { + type: 'mrkdwn', + text: `${context.propsValue.text}`, + }, + }, + { + type: 'actions', + block_id: 'actions', + elements: [ + { + type: 'button', + text: { + type: 'plain_text', + text: 'Approve', + }, + style: 'primary', + url: approvalLink, + }, + { + type: 'button', + text: { + type: 'plain_text', + text: 'Disapprove', + }, + style: 'danger', + url: disapprovalLink, + }, + ], + }, + ], + conversationId: channel, + }); + + return { + approved: false, // default approval is false + }; + } else { + return { + approved: context.resumePayload.queryParams['action'] === 'approve', + }; + } + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/retrieve-thread-messages.ts b/packages/pieces/community/slack/src/lib/actions/retrieve-thread-messages.ts new file mode 100644 index 0000000..ece2180 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/retrieve-thread-messages.ts @@ -0,0 +1,32 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { slackAuth } from '../..'; +import { WebClient } from '@slack/web-api'; +import { slackChannel } from '../common/props'; +import { processMessageTimestamp } from '../common/utils'; + +export const retrieveThreadMessages = createAction({ + name: 'retrieveThreadMessages', + displayName: 'Retrieve Thread Messages', + description: 'Retrieves thread messages by channel and thread timestamp.', + auth: slackAuth, + props: { + channel: slackChannel(true), + threadTs: Property.ShortText({ + displayName: 'Thread ts', + description: + 'Provide the ts (timestamp) value of the **parent** message to retrieve replies of this message. Do not use the ts value of the reply itself; use its parent instead. For example `1710304378.475129`.Alternatively, you can easily obtain the message link by clicking on the three dots next to the parent message and selecting the `Copy link` option.', + required: true, + }), + }, + async run({ auth, propsValue }) { + const client = new WebClient(auth.access_token); + const messageTimestamp = processMessageTimestamp(propsValue.threadTs); + if (!messageTimestamp) { + throw new Error('Invalid Timestamp Value.'); + } + return await client.conversations.replies({ + channel: propsValue.channel, + ts: messageTimestamp, + }); + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/search-messages.ts b/packages/pieces/community/slack/src/lib/actions/search-messages.ts new file mode 100644 index 0000000..c7fc5ef --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/search-messages.ts @@ -0,0 +1,49 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { slackAuth } from '../..'; +import { WebClient } from '@slack/web-api'; + +export const searchMessages = createAction({ + name: 'searchMessages', + displayName: 'Search messages', + description: 'Searches for messages matching a query', + auth: slackAuth, + props: { + query: Property.ShortText({ + displayName: 'Search query', + required: true, + }), + }, + async run({ auth, propsValue }) { + const userToken = auth.data['authed_user']?.access_token; + if (userToken === undefined) { + throw new Error(JSON.stringify( + { + message: 'Missing user token, please re-authenticate' + } + )); + } + const client = new WebClient(userToken); + const matches = []; + + // We can't use the usual "for await ... of" syntax with client.paginate + // Because search.messages uses a bastardized version of cursor-based pagination + // Where you need to pass * as first cursor + // https://api.slack.com/methods/search.messages#arg_cursor + let cursor = '*'; + do { + const page = await client.search.messages({ + query: propsValue.query, + count: 100, + // @ts-expect-error TS2353 - SDK is not aware cursor is actually supported + cursor, + }); + if (page.messages?.matches) { + matches.push(...page.messages.matches); + } + // @ts-expect-error TS2353 - SDK is not aware next_cursor is actually returned + cursor = page.messages?.pagination?.next_cursor; + } while (cursor); + + return matches; + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/send-direct-message-action.ts b/packages/pieces/community/slack/src/lib/actions/send-direct-message-action.ts new file mode 100644 index 0000000..9fdee44 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/send-direct-message-action.ts @@ -0,0 +1,48 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { slackSendMessage } from '../common/utils'; +import { slackAuth } from '../../'; +import { assertNotNullOrUndefined } from '@activepieces/shared'; +import { + profilePicture, + text, + userId, + username, + blocks, +} from '../common/props'; +import { Block,KnownBlock } from '@slack/web-api'; + + +export const slackSendDirectMessageAction = createAction({ + auth: slackAuth, + name: 'send_direct_message', + displayName: 'Send Message To A User', + description: 'Send message to a user', + props: { + userId, + text, + username, + profilePicture, + blocks, + }, + async run(context) { + const token = context.auth.access_token; + const { text, userId, blocks } = context.propsValue; + + assertNotNullOrUndefined(token, 'token'); + assertNotNullOrUndefined(text, 'text'); + assertNotNullOrUndefined(userId, 'userId'); + + const blockList = blocks ?[{ type: 'section', text: { type: 'mrkdwn', text } }, ...(blocks as unknown as (KnownBlock | Block)[])] :undefined + + + return slackSendMessage({ + token, + text, + username: context.propsValue.username, + profilePicture: context.propsValue.profilePicture, + conversationId: userId, + blocks:blockList, + }); + }, +}); + diff --git a/packages/pieces/community/slack/src/lib/actions/send-message-action.ts b/packages/pieces/community/slack/src/lib/actions/send-message-action.ts new file mode 100644 index 0000000..a1d970b --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/send-message-action.ts @@ -0,0 +1,59 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + profilePicture, + slackChannel, + username, + blocks, + singleSelectChannelInfo, +} from '../common/props'; +import { processMessageTimestamp, slackSendMessage } from '../common/utils'; +import { slackAuth } from '../../'; +import { Block,KnownBlock } from '@slack/web-api'; + + +export const slackSendMessageAction = createAction({ + auth: slackAuth, + name: 'send_channel_message', + displayName: 'Send Message To A Channel', + description: 'Send message to a channel', + props: { + info: singleSelectChannelInfo, + channel: slackChannel(true), + text: Property.LongText({ + displayName: 'Message', + description: 'The text of your message', + required: true, + }), + username, + profilePicture, + file: Property.File({ + displayName: 'Attachment', + required: false, + }), + threadTs: Property.ShortText({ + displayName: 'Thread ts', + description: + 'Provide the ts (timestamp) value of the **parent** message to make this message a reply. Do not use the ts value of the reply itself; use its parent instead. For example `1710304378.475129`.Alternatively, you can easily obtain the message link by clicking on the three dots next to the parent message and selecting the `Copy link` option.', + required: false, + }), + blocks, + }, + async run(context) { + const token = context.auth.access_token; + const { text, channel, username, profilePicture, threadTs, file,blocks } = + context.propsValue; + + const blockList = blocks ?[{ type: 'section', text: { type: 'mrkdwn', text } }, ...(blocks as unknown as (KnownBlock | Block)[])] :undefined + + return slackSendMessage({ + token, + text, + username, + profilePicture, + conversationId: channel, + threadTs: threadTs ? processMessageTimestamp(threadTs) : undefined, + file, + blocks: blockList, + }); + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/set-channel-topic.ts b/packages/pieces/community/slack/src/lib/actions/set-channel-topic.ts new file mode 100644 index 0000000..4e77994 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/set-channel-topic.ts @@ -0,0 +1,28 @@ +import { slackAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { singleSelectChannelInfo, slackChannel } from '../common/props'; +import { WebClient } from '@slack/web-api'; + +export const setChannelTopicAction = createAction({ + auth: slackAuth, + name: 'set-channel-topic', + displayName: 'Set Channel Topic', + description: 'Sets the topic on a selected channel.', + props: { + info: singleSelectChannelInfo, + channel: slackChannel(true), + topic: Property.LongText({ + displayName: 'Topic', + required: true, + }), + }, + async run(context) { + const { channel, topic } = context.propsValue; + const client = new WebClient(context.auth.access_token); + + return await client.conversations.setTopic({ + channel, + topic, + }); + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/set-user-status.ts b/packages/pieces/community/slack/src/lib/actions/set-user-status.ts new file mode 100644 index 0000000..99a11f2 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/set-user-status.ts @@ -0,0 +1,43 @@ +import { slackAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { WebClient } from '@slack/web-api'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const setUserStatusAction = createAction({ + auth: slackAuth, + name: 'slack-set-user-status', + displayName: 'Set User Status', + description: "Sets a user's custom status", + props: { + text: Property.ShortText({ + displayName: 'Text', + required: true, + }), + emoji: Property.ShortText({ + displayName: 'Emoji', + required: false, + description: + 'Emoji shortname (standard or custom), e.g. :tada: or :train:', + }), + expiration: Property.Number({ + displayName: 'Expires at', + description: 'Unix timestamp - if not set, the status will not expire', + required: false, + }), + }, + async run({ auth, propsValue }) { + await propsValidation.validateZod(propsValue, { + text: z.string().max(100), + }); + + const client = new WebClient(auth.data['authed_user']?.access_token); + return await client.users.profile.set({ + profile: { + status_text: propsValue.text, + status_emoji: propsValue.emoji, + status_expiration: propsValue.expiration, + }, + }); + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/update-message.ts b/packages/pieces/community/slack/src/lib/actions/update-message.ts new file mode 100644 index 0000000..9cc8fda --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/update-message.ts @@ -0,0 +1,46 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { slackAuth } from '../..'; +import { blocks, singleSelectChannelInfo, slackChannel } from '../common/props'; +import { processMessageTimestamp } from '../common/utils'; +import { Block,KnownBlock, WebClient } from '@slack/web-api'; + +export const updateMessage = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'updateMessage', + displayName: 'Update message', + description: 'Update an existing message', + auth: slackAuth, + props: { + info: singleSelectChannelInfo, + channel: slackChannel(true), + ts: Property.ShortText({ + displayName: 'Message Timestamp', + description: + 'Please provide the timestamp of the message you wish to update, such as `1710304378.475129`. Alternatively, you can easily obtain the message link by clicking on the three dots next to the message and selecting the `Copy link` option.', + required: true, + }), + text: Property.LongText({ + displayName: 'Message', + description: 'The updated text of your message', + required: true, + }), + blocks, + }, + async run({ auth, propsValue }) { + const messageTimestamp = processMessageTimestamp(propsValue.ts); + if (!messageTimestamp) { + throw new Error('Invalid Timestamp Value.'); + } + const client = new WebClient(auth.access_token); + + + const blockList = propsValue.blocks ?[{ type: 'section', text: { type: 'mrkdwn', text:propsValue.text } }, ...(propsValue.blocks as unknown as (KnownBlock | Block)[])] :undefined + + return await client.chat.update({ + channel: propsValue.channel, + ts: messageTimestamp, + text: propsValue.text, + blocks: blockList, + }); + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/update-profile.ts b/packages/pieces/community/slack/src/lib/actions/update-profile.ts new file mode 100644 index 0000000..50c6fd7 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/update-profile.ts @@ -0,0 +1,42 @@ +import { slackAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { WebClient } from '@slack/web-api'; + +export const updateProfileAction = createAction({ + auth: slackAuth, + name: 'slack-update-profile', + displayName: 'Update Profile', + description: 'Update basic profile field such as name or title.', + props: { + firstName: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + description: `Changing a user's email address will send an email to both the old and new addresses, and also post a slackbot message to the user informing them of the change.`, + required: false, + }), + userId: Property.ShortText({ + displayName: 'User', + description: + 'ID of user to change. This argument may only be specified by admins on paid teams.You can use **Find User by Email** action to retrieve ID.', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = new WebClient(auth.data['authed_user']?.access_token); + return client.users.profile.set({ + profile: { + first_name: propsValue.firstName, + last_name: propsValue.lastName, + email: propsValue.email, + }, + user: propsValue.userId, + }); + }, +}); diff --git a/packages/pieces/community/slack/src/lib/actions/upload-file.ts b/packages/pieces/community/slack/src/lib/actions/upload-file.ts new file mode 100644 index 0000000..1fe064b --- /dev/null +++ b/packages/pieces/community/slack/src/lib/actions/upload-file.ts @@ -0,0 +1,38 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { slackAuth } from '../../index'; +import { WebClient } from '@slack/web-api'; +import { + slackChannel, +} from '../common/props'; + +export const uploadFile = createAction({ + auth: slackAuth, + name: 'uploadFile', + displayName: 'Upload file', + description: 'Upload file without sharing it to a channel or user', + props: { + file: Property.File({ + displayName: 'Attachment', + required: true, + }), + title: Property.ShortText({ + displayName: 'Title', + required: false, + }), + filename: Property.ShortText({ + displayName: 'Filename', + required: false, + }), + channel: slackChannel(false), + }, + async run(context) { + const token = context.auth.access_token; + const { file, title, filename, channel } = context.propsValue; + const client = new WebClient(token); + return await client.files.uploadV2({ + file_uploads: [{ file: file.data, filename: filename || file.filename }], + title: title, + channel_id: channel, + }); + }, +}); diff --git a/packages/pieces/community/slack/src/lib/common/props.ts b/packages/pieces/community/slack/src/lib/common/props.ts new file mode 100644 index 0000000..e4f763e --- /dev/null +++ b/packages/pieces/community/slack/src/lib/common/props.ts @@ -0,0 +1,143 @@ +import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { UsersListResponse, WebClient } from '@slack/web-api'; + +const slackChannelBotInstruction = ` + Please make sure add the bot to the channel by following these steps: + 1. Type /invite in the channel's chat. + 2. Click on Add apps to this channel. + 3. Search for and add the bot. + ` + +export const multiSelectChannelInfo = Property.MarkDown({ + value: slackChannelBotInstruction + + `\n**Note**: If you can't find the channel in the dropdown list (which fetches up to 2000 channels), please click on the **(F)** and type the channel ID directly in an array like this: \`{\`{ ['your_channel_id_1', 'your_channel_id_2', ...] \`}\`}`, +}); + +export const singleSelectChannelInfo = Property.MarkDown({ + value: slackChannelBotInstruction + + `\n**Note**: If you can't find the channel in the dropdown list (which fetches up to 2000 channels), please click on the **(F)** and type the channel ID directly. + `, +}); + +export const slackChannel = (required: R) => + Property.Dropdown({ + displayName: 'Channel', + description: + "You can get the Channel ID by right-clicking on the channel and selecting 'View Channel Details.'", + required, + refreshers: [], + async options({ auth }) { + if (!auth) { + return { + disabled: true, + placeholder: 'connect slack account', + options: [], + }; + } + const authentication = auth as OAuth2PropertyValue; + const accessToken = authentication['access_token']; + + const channels = await getChannels(accessToken); + + return { + disabled: false, + placeholder: 'Select channel', + options: channels, + }; + }, + }); + +export const username = Property.ShortText({ + displayName: 'Username', + description: 'The username of the bot', + required: false, +}); + +export const profilePicture = Property.ShortText({ + displayName: 'Profile Picture', + description: 'The profile picture of the bot', + required: false, +}); + +export const blocks = Property.Json({ + displayName: 'Block Kit blocks', + description: 'See https://api.slack.com/block-kit for specs', + required: false, +}); + +export const userId = Property.Dropdown({ + displayName: 'User', + required: true, + refreshers: [], + async options({ auth }) { + if (!auth) { + return { + disabled: true, + placeholder: 'connect slack account', + options: [], + }; + } + + const accessToken = (auth as OAuth2PropertyValue).access_token; + + const client = new WebClient(accessToken); + const users: { label: string; value: string }[] = []; + for await (const page of client.paginate('users.list', { + limit: 1000, // Only limits page size, not total number of results + })) { + const response = page as UsersListResponse; + if (response.members) { + users.push( + ...response.members + .filter((member) => !member.deleted) + .map((member) => { + return { label: member.name || '', value: member.id || '' }; + }) + ); + } + } + return { + disabled: false, + placeholder: 'Select User', + options: users, + }; + }, +}); + +export const text = Property.LongText({ + displayName: 'Message', + required: true, +}); + +export const actions = Property.Array({ + displayName: 'Action Buttons', + required: true, +}); + +export async function getChannels(accessToken: string) { + const client = new WebClient(accessToken); + const channels: { label: string; value: string }[] = []; + const CHANNELS_LIMIT = 2000; + + let cursor; + do { + const response = await client.conversations.list({ + types: 'public_channel,private_channel', + exclude_archived: true, + limit: 1000, + cursor, + }); + + if (response.channels) { + channels.push( + ...response.channels.map((channel) => { + return { label: channel.name || '', value: channel.id || '' }; + }) + ); + } + + cursor = response.response_metadata?.next_cursor; + } while (cursor && channels.length < CHANNELS_LIMIT); + + return channels; +} diff --git a/packages/pieces/community/slack/src/lib/common/request-action.ts b/packages/pieces/community/slack/src/lib/common/request-action.ts new file mode 100644 index 0000000..b9c7fdd --- /dev/null +++ b/packages/pieces/community/slack/src/lib/common/request-action.ts @@ -0,0 +1,89 @@ +import { slackSendMessage } from './utils'; +import { + assertNotNullOrUndefined, + ExecutionType, + PauseType, +} from '@activepieces/shared'; + +export const requestAction = async (conversationId: string, context: any) => { + const { actions } = context.propsValue; + assertNotNullOrUndefined(actions, 'actions'); + + if (!actions.length) { + throw new Error(`Must have at least one button action`); + } + + const actionTextToIds = actions.map((actionText: string) => { + if (!actionText) { + throw new Error(`Button text for the action cannot be empty`); + } + + return { + actionText, + actionId: encodeURI(actionText as string), + }; + }); + + if (context.executionType === ExecutionType.BEGIN) { + context.run.pause({ + pauseMetadata: { + type: PauseType.WEBHOOK, + actions: actionTextToIds.map((action: any) => action.actionId), + }, + }); + + const token = context.auth.access_token; + const { text, username, profilePicture } = context.propsValue; + + assertNotNullOrUndefined(token, 'token'); + assertNotNullOrUndefined(text, 'text'); + + const actionElements = actionTextToIds.map((action: any) => { + const actionLink = context.generateResumeUrl({ + queryParams: { action: action.actionId }, + }); + + return { + type: 'button', + text: { + type: 'plain_text', + text: action.actionText, + }, + style: 'primary', + url: actionLink, + }; + }); + + await slackSendMessage({ + token, + text: `${context.propsValue.text}`, + username, + profilePicture, + blocks: [ + { + type: 'section', + text: { + type: 'mrkdwn', + text: `${context.propsValue.text}`, + }, + }, + { + type: 'actions', + block_id: 'actions', + elements: actionElements, + }, + ], + conversationId: conversationId, + }); + + return { + action: 'N/A', + }; + } else { + const payload = context.resumePayload.queryParams as { action: string }; + + return { + action: decodeURI(payload.action), + }; + } +}; diff --git a/packages/pieces/community/slack/src/lib/common/utils.ts b/packages/pieces/community/slack/src/lib/common/utils.ts new file mode 100644 index 0000000..4761c8c --- /dev/null +++ b/packages/pieces/community/slack/src/lib/common/utils.ts @@ -0,0 +1,116 @@ +import { ApFile } from '@activepieces/pieces-framework'; +import { Block, WebClient } from '@slack/web-api'; + +export const slackSendMessage = async ({ + text, + conversationId, + username, + profilePicture, + blocks, + threadTs, + token, + file, +}: SlackSendMessageParams) => { + const client = new WebClient(token); + + if (file) { + return await client.files.uploadV2({ + channel_id: conversationId, + initial_comment: text, + thread_ts: threadTs, + file_uploads: [ + { + file: file.data, + filename: file.filename, + }, + ], + }); + } else { + return await client.chat.postMessage({ + text, + channel: conversationId, + username, + icon_url: profilePicture, + blocks: blocks as Block[], + thread_ts: threadTs, + }); + } +}; + +type SlackSendMessageParams = { + token: string; + conversationId: string; + username?: string; + profilePicture?: string; + blocks?: unknown[] | Record; + text: string; + file?: ApFile; + threadTs?: string; +}; + +export function processMessageTimestamp(input: string) { + // Regular expression to match a URL containing the timestamp + const urlRegex = /\/p(\d+)(\d{6})$/; + // Check if the input is a URL + const urlMatch = input.match(urlRegex); + if (urlMatch) { + const timestamp = `${urlMatch[1]}.${urlMatch[2]}`; + return timestamp; + } + + // Check if the input is already in the desired format + const timestampRegex = /^(\d+)\.(\d{6})$/; + const timestampMatch = input.match(timestampRegex); + if (timestampMatch) { + return input; + } + + return undefined; +} + +export function getFirstFiveOrAll(array: unknown[]) { + if (array.length <= 5) { + return array; + } else { + return array.slice(0, 5); + } +} + +/** +* Parse a message text to extract command and arguments +*/ +export function parseCommand( + text: string, + botUserId: string, + validCommands: string[] +): { command: string; args: string[] } | null { + if (!botUserId) { + return null; + } + + // Check if the message mentions the bot + const mentionRegex = new RegExp(`<@${botUserId}>\\s+(.+)`, 's'); + const mentionMatch = text.match(mentionRegex); + + if (!mentionMatch) { + return null; + } + + // Extract the text after the mention + const commandText = mentionMatch[1].trim(); + + // Split into command and arguments (first word is command, rest are args) + const parts = commandText.split(/\s+/); + const command = parts[0].toLowerCase(); + const args = parts.slice(1); + + // Check if it's a valid command + if (!validCommands.includes(command)) { + return null; + } + + return { + command, + args, + }; +} \ No newline at end of file diff --git a/packages/pieces/community/slack/src/lib/triggers/new-channel.ts b/packages/pieces/community/slack/src/lib/triggers/new-channel.ts new file mode 100644 index 0000000..a7afc88 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/triggers/new-channel.ts @@ -0,0 +1,66 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { slackAuth } from '../../'; +import { WebClient } from '@slack/web-api'; + +const sampleData = { + type: 'channel_created', + channel: { + id: 'C024BE91L', + name: 'fun', + created: 1360782804, + creator: 'U024BE7LH', + }, +}; + +export const channelCreated = createTrigger({ + auth: slackAuth, + name: 'channel_created', + displayName: 'Channel created', + description: 'Triggers when a channel is created', + props: {}, + type: TriggerStrategy.APP_WEBHOOK, + sampleData: sampleData, + onEnable: async (context) => { + // Older OAuth2 has team_id, newer has team.id + const teamId = + context.auth.data['team_id'] ?? context.auth.data['team']['id']; + context.app.createListeners({ + events: ['channel_created'], + identifierValue: teamId, + }); + }, + onDisable: async (context) => { + // Ignored + }, + test: async (context) => { + const client = new WebClient(context.auth.access_token); + const response = await client.conversations.list({ + exclude_archived: true, + limit: 10, + types: 'public_channel,private_channel', + }); + if (!response.channels) { + return []; + } + return response.channels.map((channel) => { + return { + type: 'channel_created', + channel: { + id: channel.id, + name: channel.name, + created: channel.created, + creator: channel.creator, + }, + }; + }); + }, + + run: async (context) => { + const payloadBody = context.payload.body as PayloadBody; + return [payloadBody.event]; + }, +}); + +type PayloadBody = { + event: object; +}; diff --git a/packages/pieces/community/slack/src/lib/triggers/new-command-in-direct-message.ts b/packages/pieces/community/slack/src/lib/triggers/new-command-in-direct-message.ts new file mode 100644 index 0000000..2f2ae84 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/triggers/new-command-in-direct-message.ts @@ -0,0 +1,101 @@ +import { + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { userId } from '../common/props'; +import { slackAuth } from '../../'; +import { parseCommand } from '../common/utils'; + +export const newCommandInDirectMessageTrigger = createTrigger({ + auth: slackAuth, + name: 'new-command-in-direct-message', + displayName: 'New Command in Direct Message', + description: + 'Triggers when a specific command is sent to the bot (e.g., @bot command arg1 arg2) via Direct Message.', + props: { + user: userId, + commands: Property.Array({ + displayName: 'Commands', + description: + 'List of valid commands that the bot should respond to (e.g., help, ocr, remind)', + required: true, + defaultValue: ['help'], + }), + ignoreBots: Property.Checkbox({ + displayName: 'Ignore Bot Messages ?', + required: true, + defaultValue: true, + }), + ignoreSelfMessages: Property.Checkbox({ + displayName: 'Ignore Message from Yourself ?', + required: true, + defaultValue: false, + }), + }, + type: TriggerStrategy.APP_WEBHOOK, + sampleData: undefined, + onEnable: async (context) => { + // Older OAuth2 has team_id, newer has team.id + const teamId = + context.auth.data['team_id'] ?? context.auth.data['team']['id']; + context.app.createListeners({ + events: ['message'], + identifierValue: teamId, + }); + }, + onDisable: async (context) => { + // Ignored + }, + + run: async (context) => { + const payloadBody = context.payload.body as PayloadBody; + const commands = (context.propsValue.commands as string[]) ?? []; + const user = context.propsValue.user as string; + const authUserId = context.auth.data['authed_user']?.id; + + + if (payloadBody.event.channel_type !== 'im') { + return []; + } + + // Check for bot messages if configured to ignore them + if ( + (context.propsValue.ignoreBots && payloadBody.event.bot_id) || + (context.propsValue.ignoreSelfMessages && payloadBody.event.user === authUserId) + ) { + return []; + } + + // Check for mention and parse command + if (user && payloadBody.event.text) { + const parsedCommand = parseCommand( + payloadBody.event.text, + user, + commands + ); + + if (parsedCommand && commands.includes(parsedCommand.command)) { + // Return event with parsed command + return [ + { + ...payloadBody.event, + parsed_command: parsedCommand, + }, + ]; + } + } + + return []; + }, +}); + +type PayloadBody = { + event: { + channel: string; + bot_id?: string; + text?: string; + channel_type: string; + user: string; + }; +}; diff --git a/packages/pieces/community/slack/src/lib/triggers/new-command.ts b/packages/pieces/community/slack/src/lib/triggers/new-command.ts new file mode 100644 index 0000000..a1bebf9 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/triggers/new-command.ts @@ -0,0 +1,126 @@ +import { + OAuth2PropertyValue, + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { getChannels, multiSelectChannelInfo, userId } from '../common/props'; +import { slackAuth } from '../../'; +import { parseCommand } from '../common/utils'; + +export const newCommand = createTrigger({ + auth: slackAuth, + name: 'new_command', + displayName: 'New Command in Channel', + description: + 'Triggers when a specific command is sent to the bot (e.g., @bot command arg1 arg2)', + props: { + info: multiSelectChannelInfo, + user: userId, + commands: Property.Array({ + displayName: 'Commands', + description: + 'List of valid commands that the bot should respond to (e.g., help, ocr, remind)', + required: true, + defaultValue: ['help'], + }), + channels: Property.MultiSelectDropdown({ + displayName: 'Channels', + description: + 'If no channel is selected, the flow will be triggered for commands in all channels', + required: false, + refreshers: [], + async options({ auth }) { + if (!auth) { + return { + disabled: true, + placeholder: 'connect slack account', + options: [], + }; + } + const authentication = auth as OAuth2PropertyValue; + const accessToken = authentication['access_token']; + const channels = await getChannels(accessToken); + return { + disabled: false, + placeholder: 'Select channel', + options: channels, + }; + }, + }), + ignoreBots: Property.Checkbox({ + displayName: 'Ignore Bot Messages ?', + required: true, + defaultValue: true, + }), + }, + type: TriggerStrategy.APP_WEBHOOK, + sampleData: undefined, + onEnable: async (context) => { + // Older OAuth2 has team_id, newer has team.id + const teamId = + context.auth.data['team_id'] ?? context.auth.data['team']['id']; + context.app.createListeners({ + events: ['message'], + identifierValue: teamId, + }); + }, + onDisable: async (context) => { + // Ignored + }, + + run: async (context) => { + const payloadBody = context.payload.body as PayloadBody; + const channels = (context.propsValue.channels as string[]) ?? []; + const commands = (context.propsValue.commands as string[]) ?? []; + const user = context.propsValue.user as string; + + + // check if it's channel message + if (!['channel','group'].includes(payloadBody.event.channel_type)) { + return []; + } + + // Check if we should process this channel + if ( + !(channels.length === 0 || channels.includes(payloadBody.event.channel)) + ) { + return []; + } + + // Check for bot messages if configured to ignore them + if (context.propsValue.ignoreBots && payloadBody.event.bot_id) { + return []; + } + + // Check for mention and parse command + if (user && payloadBody.event.text) { + const parsedCommand = parseCommand( + payloadBody.event.text, + user, + commands + ); + + if (parsedCommand && commands.includes(parsedCommand.command)) { + // Return event with parsed command + return [ + { + ...payloadBody.event, + parsed_command: parsedCommand, + }, + ]; + } + } + + return []; + }, +}); + +type PayloadBody = { + event: { + channel: string; + bot_id?: string; + text?: string; + channel_type:string + }; +}; diff --git a/packages/pieces/community/slack/src/lib/triggers/new-direct-message.ts b/packages/pieces/community/slack/src/lib/triggers/new-direct-message.ts new file mode 100644 index 0000000..a8ac0fe --- /dev/null +++ b/packages/pieces/community/slack/src/lib/triggers/new-direct-message.ts @@ -0,0 +1,63 @@ +import { Property, TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { slackAuth } from '../../'; + + + +export const newDirectMessageTrigger = createTrigger({ + auth: slackAuth, + name: 'new-direct-message', + displayName: 'New Direct Message', + description: 'Triggers when a message was posted in a direct message channel.', + props: { + ignoreBots: Property.Checkbox({ + displayName: 'Ignore Bot Messages ?', + required: true, + defaultValue: false, + }), + ignoreSelfMessages: Property.Checkbox({ + displayName: 'Ignore Message from Yourself ?', + required: true, + defaultValue: false, + }), + }, + type: TriggerStrategy.APP_WEBHOOK, + sampleData: undefined, + onEnable: async (context) => { + // Older OAuth2 has team_id, newer has team.id + const teamId = context.auth.data['team_id'] ?? context.auth.data['team']['id']; + context.app.createListeners({ + events: ['message'], + identifierValue: teamId, + }); + }, + onDisable: async (context) => { + // Ignored + }, + + async run(context) { + const payloadBody = context.payload.body as PayloadBody; + const userId = context.auth.data['authed_user']?.id; + + if (payloadBody.event.channel_type !== 'im') { + return []; + } + + // check for bot messages + if ( + (context.propsValue.ignoreBots && payloadBody.event.bot_id) || + (context.propsValue.ignoreSelfMessages && payloadBody.event.user === userId) + ) { + return []; + } + return [payloadBody.event]; + }, +}); + +type PayloadBody = { + event: { + channel: string; + bot_id?: string; + user: string; + channel_type: string; + }; +}; diff --git a/packages/pieces/community/slack/src/lib/triggers/new-mention-in-direct-message.ts b/packages/pieces/community/slack/src/lib/triggers/new-mention-in-direct-message.ts new file mode 100644 index 0000000..acd1651 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/triggers/new-mention-in-direct-message.ts @@ -0,0 +1,80 @@ +import { + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { slackAuth } from '../../'; +import { userId } from '../common/props'; + +export const newMentionInDirectMessageTrigger = createTrigger({ + auth: slackAuth, + name: 'new-mention-in-direct-message', + displayName: 'New Mention in Direct Message', + description: + 'Triggers when a username is mentioned in a direct message channel.', + props: { + user: userId, + ignoreBots: Property.Checkbox({ + displayName: 'Ignore Bot Messages ?', + required: true, + defaultValue: false, + }), + ignoreSelfMessages: Property.Checkbox({ + displayName: 'Ignore Message from Yourself ?', + required: true, + defaultValue: false, + }), + }, + type: TriggerStrategy.APP_WEBHOOK, + sampleData: undefined, + onEnable: async (context) => { + // Older OAuth2 has team_id, newer has team.id + const teamId = + context.auth.data['team_id'] ?? context.auth.data['team']['id']; + context.app.createListeners({ + events: ['message'], + identifierValue: teamId, + }); + }, + onDisable: async (context) => { + // Ignored + }, + + async run(context) { + const payloadBody = context.payload.body as PayloadBody; + const userId = context.auth.data['authed_user']?.id; + + if (payloadBody.event.channel_type !== 'im') { + return []; + } + + // check for bot messages + if ( + (context.propsValue.ignoreBots && payloadBody.event.bot_id) || + (context.propsValue.ignoreSelfMessages && + payloadBody.event.user === userId) + ) { + return []; + } + + //check for mention + if ( + context.propsValue.user && + payloadBody.event.text?.includes(`<@${context.propsValue.user}>`) + ) { + return [payloadBody.event]; + } + + return []; + }, +}); + +type PayloadBody = { + event: { + channel: string; + bot_id?: string; + user: string; + channel_type: string; + text?: string; + }; +}; diff --git a/packages/pieces/community/slack/src/lib/triggers/new-mention.ts b/packages/pieces/community/slack/src/lib/triggers/new-mention.ts new file mode 100644 index 0000000..bf412f2 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/triggers/new-mention.ts @@ -0,0 +1,98 @@ +import { + OAuth2PropertyValue, + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { getChannels, multiSelectChannelInfo, userId } from '../common/props'; +import { slackAuth } from '../../'; + +export const newMention = createTrigger({ + auth: slackAuth, + name: 'new_mention', + displayName: 'New Mention in Channel', + description: 'Triggers when a username is mentioned.', + props: { + info: multiSelectChannelInfo, + user: userId, + channels: Property.MultiSelectDropdown({ + displayName: 'Channels', + description: + 'If no channel is selected, the flow will be triggered for username mentions in all channels', + required: false, + refreshers: [], + async options({ auth }) { + if (!auth) { + return { + disabled: true, + placeholder: 'connect slack account', + options: [], + }; + } + const authentication = auth as OAuth2PropertyValue; + const accessToken = authentication['access_token']; + const channels = await getChannels(accessToken); + return { + disabled: false, + placeholder: 'Select channel', + options: channels, + }; + }, + }), + ignoreBots: Property.Checkbox({ + displayName: 'Ignore Bot Messages ?', + required: true, + defaultValue: false, + }), + }, + type: TriggerStrategy.APP_WEBHOOK, + sampleData: undefined, + onEnable: async (context) => { + // Older OAuth2 has team_id, newer has team.id + const teamId = + context.auth.data['team_id'] ?? context.auth.data['team']['id']; + context.app.createListeners({ + events: ['message'], + identifierValue: teamId, + }); + }, + onDisable: async (context) => { + // Ignored + }, + + run: async (context) => { + const payloadBody = context.payload.body as PayloadBody; + const channels = (context.propsValue.channels as string[]) ?? []; + + // check if it's channel message + if (!['channel','group'].includes(payloadBody.event.channel_type)) { + return []; + } + + + if (channels.length === 0 || channels.includes(payloadBody.event.channel)) { + // check for bot messages + if (context.propsValue.ignoreBots && payloadBody.event.bot_id) { + return []; + } + // check for mention + if ( + context.propsValue.user && + payloadBody.event.text?.includes(`<@${context.propsValue.user}>`) + ) { + return [payloadBody.event]; + } + } + + return []; + }, +}); + +type PayloadBody = { + event: { + channel: string; + bot_id?: string; + text?: string; + channel_type:string + }; +}; diff --git a/packages/pieces/community/slack/src/lib/triggers/new-message-in-channel.ts b/packages/pieces/community/slack/src/lib/triggers/new-message-in-channel.ts new file mode 100644 index 0000000..2b891b5 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/triggers/new-message-in-channel.ts @@ -0,0 +1,64 @@ +import { Property, TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { singleSelectChannelInfo, slackChannel } from '../common/props'; +import { slackAuth } from '../../'; +import { WebClient } from '@slack/web-api'; +import { isNil } from '@activepieces/shared'; +import { getFirstFiveOrAll } from '../common/utils'; + + + +export const newMessageInChannelTrigger = createTrigger({ + auth: slackAuth, + name: 'new-message-in-channel', + displayName: 'New Message Posted to Channel', + description: 'Triggers when a new message is posted to a specific #channel you choose.', + props: { + info: singleSelectChannelInfo, + channel: slackChannel(true), + ignoreBots: Property.Checkbox({ + displayName: 'Ignore Bot Messages ?', + required: true, + defaultValue: false, + }), + }, + type: TriggerStrategy.APP_WEBHOOK, + sampleData: undefined, + async onEnable(context) { + // Older OAuth2 has team_id, newer has team.id + const teamId = context.auth.data['team_id'] ?? context.auth.data['team']['id']; + context.app.createListeners({ + events: ['message'], + identifierValue: teamId, + }); + }, + async onDisable(context) { + // Ignored + }, + + async run(context) { + const payloadBody = context.payload.body as PayloadBody; + + // check if it's channel message + if (!['channel','group'].includes(payloadBody.event.channel_type)) { + return []; + } + + if (payloadBody.event.channel === context.propsValue.channel) { + // check for bot messages + if (context.propsValue.ignoreBots && payloadBody.event.bot_id) { + return []; + } + return [payloadBody.event]; + } + + return []; + }, +}); + +type PayloadBody = { + event: { + channel: string; + bot_id?: string; + channel_type:string + }; +}; diff --git a/packages/pieces/community/slack/src/lib/triggers/new-message.ts b/packages/pieces/community/slack/src/lib/triggers/new-message.ts new file mode 100644 index 0000000..5b92ba8 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/triggers/new-message.ts @@ -0,0 +1,52 @@ +import { Property, TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { slackAuth } from '../../'; + +export const newMessageTrigger = createTrigger({ + auth: slackAuth, + name: 'new-message', + displayName: 'New Public Message Posted Anywhere', + description: 'Triggers when a new message is posted to any channel.', + props: { + ignoreBots: Property.Checkbox({ + displayName: 'Ignore Bot Messages ?', + required: true, + defaultValue: false, + }), + }, + type: TriggerStrategy.APP_WEBHOOK, + sampleData: undefined, + onEnable: async (context) => { + // Older OAuth2 has team_id, newer has team.id + const teamId = context.auth.data['team_id'] ?? context.auth.data['team']['id']; + context.app.createListeners({ + events: ['message'], + identifierValue: teamId, + }); + }, + onDisable: async (context) => { + // Ignored + }, + + run: async (context) => { + const payloadBody = context.payload.body as PayloadBody; + + // check if it's channel message + if (!['channel','group'].includes(payloadBody.event.channel_type)) { + return []; + } + + // check for bot messages + if (context.propsValue.ignoreBots && payloadBody.event.bot_id) { + return []; + } + return [payloadBody.event]; + }, +}); + +type PayloadBody = { + event: { + channel: string; + bot_id?: string; + channel_type:string + }; +}; diff --git a/packages/pieces/community/slack/src/lib/triggers/new-reaction-added.ts b/packages/pieces/community/slack/src/lib/triggers/new-reaction-added.ts new file mode 100644 index 0000000..e4097f2 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/triggers/new-reaction-added.ts @@ -0,0 +1,64 @@ +import { + Property, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { slackAuth } from '../../'; +import { slackChannel } from '../common/props'; +import { WebClient } from '@slack/web-api'; + + +export const newReactionAdded = createTrigger({ + auth: slackAuth, + name: 'new_reaction_added', + displayName: 'New Reaction', + description: 'Triggers when a new reaction is added to a message', + props: { + emojis: Property.Array({ + displayName: 'Emojis (E.g fire, smile)', + description: 'Select emojis to trigger on', + required: false, + }), + channel: slackChannel(false), + }, + type: TriggerStrategy.APP_WEBHOOK, + sampleData: undefined, + onEnable: async (context) => { + // Older OAuth2 has team_id, newer has team.id + const teamId = + context.auth.data['team_id'] ?? context.auth.data['team']['id']; + context.app.createListeners({ + events: ['reaction_added'], + identifierValue: teamId, + }); + }, + onDisable: async (context) => { + // Ignored + }, + + + + run: async (context) => { + const payloadBody = context.payload.body as PayloadBody; + if (context.propsValue.emojis) { + if (!context.propsValue.emojis.includes(payloadBody.event.reaction)) { + return []; + } + } + if (context.propsValue.channel) { + if (payloadBody.event.item['channel'] !== context.propsValue.channel) { + return []; + } + } + return [payloadBody.event]; + }, +}); + +type PayloadBody = { + event: { + reaction: string; + item: { + channel: string; + }; + }; +}; diff --git a/packages/pieces/community/slack/src/lib/triggers/new-saved-message.ts b/packages/pieces/community/slack/src/lib/triggers/new-saved-message.ts new file mode 100644 index 0000000..8d4b21e --- /dev/null +++ b/packages/pieces/community/slack/src/lib/triggers/new-saved-message.ts @@ -0,0 +1,44 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { slackAuth } from '../../'; + +export const newSavedMessageTrigger = createTrigger({ + auth: slackAuth, + name: 'new-saved-message', + displayName: 'New Saved Message', + description: 'Triggers when you save a message.', + props: {}, + type: TriggerStrategy.APP_WEBHOOK, + sampleData: undefined, + onEnable: async (context) => { + // Older OAuth2 has team_id, newer has team.id + const teamId = context.auth.data['team_id'] ?? context.auth.data['team']['id']; + context.app.createListeners({ + events: ['star_added'], + identifierValue: teamId, + }); + }, + onDisable: async (context) => { + // Ignored + }, + + run: async (context) => { + const payloadBody = context.payload.body as PayloadBody; + + // check if it's saved message + if (payloadBody.event.type === 'star_added' && payloadBody.event.item.type ==='message') { + return [payloadBody.event.item]; + } + + return []; + }, +}); + +type PayloadBody = { + event: { + type: string; + event_ts: string; + item:{ + type:string + } + }; +}; diff --git a/packages/pieces/community/slack/src/lib/triggers/new-team-custom-emoji.ts b/packages/pieces/community/slack/src/lib/triggers/new-team-custom-emoji.ts new file mode 100644 index 0000000..12583a5 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/triggers/new-team-custom-emoji.ts @@ -0,0 +1,62 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { slackAuth } from '../../'; +import { WebClient } from '@slack/web-api'; + +const sampleData = { + id: 'heart', + image: 'https://emoji.slack-edge.com/T06BTHUEFFF/heart/84a171ae62daacc7.jpg', +}; + +export const newTeamCustomEmojiTrigger = createTrigger({ + auth: slackAuth, + name: 'new-team-custom-emoji', + displayName: 'New Team Custom Emoji', + description: 'Triggers when a custom emoji has been added to a team.', + props: {}, + type: TriggerStrategy.APP_WEBHOOK, + sampleData, + onEnable: async (context) => { + // Older OAuth2 has team_id, newer has team.id + const teamId = context.auth.data['team_id'] ?? context.auth.data['team']['id']; + context.app.createListeners({ + events: ['emoji_changed'], + identifierValue: teamId, + }); + }, + onDisable: async (context) => { + // Ignored + }, + + test: async (context) => { + const client = new WebClient(context.auth.access_token); + + const response = await client.emoji.list(); + + if (!response.emoji) return [sampleData]; + + return Object.entries(response.emoji).map(([id, image]) => ({ + id, + image, + })); + }, + + run: async (context) => { + const payloadBody = context.payload.body as PayloadBody; + + // check if it's emoji message + if (payloadBody.event.type !== 'emoji_changed' && payloadBody.event.subtype !== 'add') { + return []; + } + + return [{ id: payloadBody.event.name, image: payloadBody.event.value }]; + }, +}); + +type PayloadBody = { + event: { + type: string; + subtype: string; + name: string; + value: string; + }; +}; diff --git a/packages/pieces/community/slack/src/lib/triggers/new-user.ts b/packages/pieces/community/slack/src/lib/triggers/new-user.ts new file mode 100644 index 0000000..3ccb891 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/triggers/new-user.ts @@ -0,0 +1,103 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { slackAuth } from '../../'; +import { WebClient } from '@slack/web-api'; + +const sampleData = { + id: 'USLACKBOT', + team_id: 'T06BTHUEFFF', + name: 'slackbot', + deleted: false, + color: '757575', + real_name: 'Slackbot', + tz: 'America/Los_Angeles', + tz_label: 'Pacific Daylight Time', + tz_offset: -25200, + profile: { + title: '', + phone: '', + skype: '', + real_name: 'Slackbot', + real_name_normalized: 'Slackbot', + display_name: 'Slackbot', + display_name_normalized: 'Slackbot', + fields: {}, + status_text: '', + status_emoji: '', + status_emoji_display_info: [], + status_expiration: 0, + avatar_hash: 'sv41d8cd98f0', + always_active: true, + first_name: 'slackbot', + last_name: '', + image_24: 'https://a.slack-edge.com/80588/img/slackbot_24.png', + image_32: 'https://a.slack-edge.com/80588/img/slackbot_32.png', + image_48: 'https://a.slack-edge.com/80588/img/slackbot_48.png', + image_72: 'https://a.slack-edge.com/80588/img/slackbot_72.png', + image_192: 'https://a.slack-edge.com/80588/marketing/img/avatars/slackbot/avatar-slackbot.png', + image_512: 'https://a.slack-edge.com/80588/img/slackbot_512.png', + status_text_canonical: '', + team: 'T06BTHUEFFF', + }, + is_admin: false, + is_owner: false, + is_primary_owner: false, + is_restricted: false, + is_ultra_restricted: false, + is_bot: false, + is_app_user: false, + updated: 0, + is_email_confirmed: false, + who_can_share_contact_card: 'EVERYONE', +}; + +export const newUserTrigger = createTrigger({ + auth: slackAuth, + name: 'new-user', + displayName: 'New User', + description: 'Triggers when a new user is created / first joins your org.', + props: {}, + type: TriggerStrategy.APP_WEBHOOK, + sampleData, + onEnable: async (context) => { + // Older OAuth2 has team_id, newer has team.id + const teamId = context.auth.data['team_id'] ?? context.auth.data['team']['id']; + context.app.createListeners({ + events: ['team_join'], + identifierValue: teamId, + }); + }, + onDisable: async (context) => { + // Ignored + }, + + test: async (context) => { + const client = new WebClient(context.auth.access_token); + + const response = await client.users.list({limit:10}); + + if (!response.members) return [sampleData]; + + return response.members; + }, + + run: async (context) => { + const payloadBody = context.payload.body as PayloadBody; + + // check if it's emoji message + if (payloadBody.event.type !== 'team_join') { + return []; + } + + return [payloadBody.event.user]; + }, +}); + +type PayloadBody = { + event: { + type: string; + event_ts:string, + user:{ + id:string + } + }; +}; diff --git a/packages/pieces/community/slack/tsconfig.json b/packages/pieces/community/slack/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/slack/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/slack/tsconfig.lib.json b/packages/pieces/community/slack/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/slack/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/smaily/.eslintrc.json b/packages/pieces/community/smaily/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/smaily/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/smaily/README.md b/packages/pieces/community/smaily/README.md new file mode 100644 index 0000000..fbe73c6 --- /dev/null +++ b/packages/pieces/community/smaily/README.md @@ -0,0 +1,7 @@ +# pieces-smaily + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-smaily` to build the library. diff --git a/packages/pieces/community/smaily/package.json b/packages/pieces/community/smaily/package.json new file mode 100644 index 0000000..4547c29 --- /dev/null +++ b/packages/pieces/community/smaily/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-smaily", + "version": "0.0.2" +} diff --git a/packages/pieces/community/smaily/project.json b/packages/pieces/community/smaily/project.json new file mode 100644 index 0000000..803acd4 --- /dev/null +++ b/packages/pieces/community/smaily/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-smaily", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/smaily/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/smaily", + "tsConfig": "packages/pieces/community/smaily/tsconfig.lib.json", + "packageJson": "packages/pieces/community/smaily/package.json", + "main": "packages/pieces/community/smaily/src/index.ts", + "assets": [ + "packages/pieces/community/smaily/*.md", + { + "input": "packages/pieces/community/smaily/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/smaily/src/index.ts b/packages/pieces/community/smaily/src/index.ts new file mode 100644 index 0000000..5513ceb --- /dev/null +++ b/packages/pieces/community/smaily/src/index.ts @@ -0,0 +1,83 @@ +import { + createPiece, + PieceAuth, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { + AuthenticationType, + httpClient, + HttpMethod, createCustomApiCallAction + +} from '@activepieces/pieces-common'; +import { createOrUpdateSubscriberAction } from './lib/actions/create-or-update-subscriber.action'; +import { getSubscriberAction } from './lib/actions/get-subscriber.action'; + +export const smailyAuth = PieceAuth.CustomAuth({ + description: ` + 1. Click on profile pic (top right corner), navigate to **Preferences**. + 2. Go to **Integrations** tab and click on **Create a New User**. + 3. Copy generated domain, user and password.`, + required: true, + props: { + domain: Property.ShortText({ + displayName: 'Domain', + required: true, + }), + username: Property.ShortText({ + displayName: 'User Name', + required: true, + }), + password: Property.ShortText({ + displayName: 'Password', + required: true, + }), + }, + validate: async ({ auth }) => { + try { + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://${auth.domain}.sendsmaily.net/api/organizations/users.php`, + authentication: { + type: AuthenticationType.BASIC, + username: auth.username, + password: auth.password, + }, + }); + return { + valid: true, + }; + } catch { + return { + valid: false, + error: 'Please provide correct credentials.', + }; + } + }, +}); + +export const smaily = createPiece({ + displayName: 'Smaily', + auth: smailyAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/smaily.png', + categories: [PieceCategory.MARKETING], + authors: ['kishanprmr'], + actions: [createOrUpdateSubscriberAction, getSubscriberAction, + createCustomApiCallAction({ + auth:smailyAuth, + baseUrl: (auth)=>{ + return `https://${(auth as PiecePropValueSchema).domain}.sendsmaily.net/api` + }, + authMapping: async (auth) => ({ + Authorization: `Basic ${Buffer.from( + `${(auth as { username: string }).username}:${ + (auth as { password: string }).password + }` + ).toString('base64')}`, + }), + }) + ], + triggers: [], +}); diff --git a/packages/pieces/community/smaily/src/lib/actions/create-or-update-subscriber.action.ts b/packages/pieces/community/smaily/src/lib/actions/create-or-update-subscriber.action.ts new file mode 100644 index 0000000..2e5a16e --- /dev/null +++ b/packages/pieces/community/smaily/src/lib/actions/create-or-update-subscriber.action.ts @@ -0,0 +1,59 @@ +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { smailyAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; + +export const createOrUpdateSubscriberAction = createAction({ + auth: smailyAuth, + name: 'create-or-update-subscriber', + displayName: 'Create or Update Subscriber', + description: + 'Creates a new subscriber or update an existing subscriber by email.', + props: { + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + is_unsubscribed: Property.StaticDropdown({ + displayName: 'Subscription Status', + required: true, + defaultValue: 0, + options: { + disabled: false, + options: [ + { label: 'Unsubscribed', value: 1 }, + { label: 'Subscribed', value: 0 }, + ], + }, + }), + custom_fields: Property.Object({ + displayName: 'Custom Fields', + description: `Go to **Subscribers Tab-> All Subscribers -> Manage Fields** to get custom field names.`, + required: false, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://${context.auth.domain}.sendsmaily.net/api/contact.php`, + headers: { + 'Content-Type': 'application/json', + }, + authentication: { + type: AuthenticationType.BASIC, + username: context.auth.username, + password: context.auth.password, + }, + body: { + email: context.propsValue.email, + is_unsubscribed: context.propsValue.is_unsubscribed, + ...context.propsValue.custom_fields, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/smaily/src/lib/actions/get-subscriber.action.ts b/packages/pieces/community/smaily/src/lib/actions/get-subscriber.action.ts new file mode 100644 index 0000000..5a3335c --- /dev/null +++ b/packages/pieces/community/smaily/src/lib/actions/get-subscriber.action.ts @@ -0,0 +1,40 @@ +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { smailyAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; + +export const getSubscriberAction = createAction({ + auth: smailyAuth, + name: 'get-subscriber', + displayName: 'Get Subscriber', + description: + 'retrieves detailed subscriber information for a given email address.', + props: { + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + }, + async run(context) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://${context.auth.domain}.sendsmaily.net/api/contact.php`, + queryParams: { + email: context.propsValue.email, + }, + headers: { + 'Content-Type': 'application/json', + }, + authentication: { + type: AuthenticationType.BASIC, + username: context.auth.username, + password: context.auth.password, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/smaily/tsconfig.json b/packages/pieces/community/smaily/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/smaily/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/smaily/tsconfig.lib.json b/packages/pieces/community/smaily/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/smaily/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/smartsheet/.eslintrc.json b/packages/pieces/community/smartsheet/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/smartsheet/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/smartsheet/README.md b/packages/pieces/community/smartsheet/README.md new file mode 100644 index 0000000..7dfdc1a --- /dev/null +++ b/packages/pieces/community/smartsheet/README.md @@ -0,0 +1,7 @@ +# pieces-smartsheet + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-smartsheet` to build the library. diff --git a/packages/pieces/community/smartsheet/package.json b/packages/pieces/community/smartsheet/package.json new file mode 100644 index 0000000..0b624bb --- /dev/null +++ b/packages/pieces/community/smartsheet/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-smartsheet", + "version": "0.0.1" +} diff --git a/packages/pieces/community/smartsheet/project.json b/packages/pieces/community/smartsheet/project.json new file mode 100644 index 0000000..b840c44 --- /dev/null +++ b/packages/pieces/community/smartsheet/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-smartsheet", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/smartsheet/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/smartsheet", + "tsConfig": "packages/pieces/community/smartsheet/tsconfig.lib.json", + "packageJson": "packages/pieces/community/smartsheet/package.json", + "main": "packages/pieces/community/smartsheet/src/index.ts", + "assets": [ + "packages/pieces/community/smartsheet/*.md", + { + "input": "packages/pieces/community/smartsheet/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/smartsheet/src/index.ts b/packages/pieces/community/smartsheet/src/index.ts new file mode 100644 index 0000000..bc81c3d --- /dev/null +++ b/packages/pieces/community/smartsheet/src/index.ts @@ -0,0 +1,82 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { HttpMethod, HttpRequest, httpClient } from '@activepieces/pieces-common'; + +// Actions +import { addRowToSheet } from './lib/actions/add-row-to-sheet'; +import { updateRow } from './lib/actions/update-row'; +import { attachFileToRow } from './lib/actions/attach-file-to-row'; +import { findRowsByQuery } from './lib/actions/find-rows-by-query'; +import { findAttachmentByRowId } from './lib/actions/find-attachment-by-row-id'; +import { findSheetByName } from './lib/actions/find-sheet-by-name'; + +// Webhook Triggers +import { newRowAddedTrigger } from './lib/triggers/new-row-trigger'; +import { updatedRowTrigger } from './lib/triggers/updated-row-trigger'; +import { newAttachmentTrigger } from './lib/triggers/new-attachment-trigger'; +import { newCommentTrigger } from './lib/triggers/new-comment-trigger'; + +const markdownDescription = ` +To obtain your Smartsheet access token: + +1. Sign in to your Smartsheet account. +2. Click on your profile picture in the top right corner. +3. Select **Personal Settings** from the dropdown menu. +4. In the left panel, click **API Access**. +5. Click **Generate new access token**. +6. Enter a name for your token (e.g., "Activepieces Integration"). +7. Click **OK** to generate the token.. +8. Copy the access token and paste it into the connection field. +`; + +export const smartsheetAuth = PieceAuth.SecretText({ + displayName: 'Access Token', + description: markdownDescription, + required: true, + validate: async ({ auth }) => { + if (!auth) { + return { + valid: false, + error: 'Access token is required', + }; + } + try { + const request: HttpRequest = { + method: HttpMethod.GET, + url: 'https://api.smartsheet.com/2.0/sheets', + headers: { + Authorization: `Bearer ${auth}`, + 'Content-Type': 'application/json', + }, + }; + await httpClient.sendRequest(request); + return { valid: true }; + } catch (error) { + return { + valid: false, + error: + 'Invalid access token, insufficient permissions, or region mismatch. Ensure token is for api.smartsheet.com.', + }; + } + }, +}); + +export const smartsheet = createPiece({ + displayName: 'Smartsheet', + description: + 'Dynamic work execution platform for teams to plan, capture, manage, automate, and report on work at scale.', + auth: smartsheetAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/smartsheet.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: ['onyedikachi-david', 'kishanprmr'], + actions: [ + addRowToSheet, + updateRow, + attachFileToRow, + findRowsByQuery, + findAttachmentByRowId, + findSheetByName, + ], + triggers: [newRowAddedTrigger, updatedRowTrigger, newAttachmentTrigger, newCommentTrigger], +}); diff --git a/packages/pieces/community/smartsheet/src/lib/actions/add-row-to-sheet.ts b/packages/pieces/community/smartsheet/src/lib/actions/add-row-to-sheet.ts new file mode 100644 index 0000000..992bd93 --- /dev/null +++ b/packages/pieces/community/smartsheet/src/lib/actions/add-row-to-sheet.ts @@ -0,0 +1,102 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { smartsheetAuth } from '../../index'; +import { smartsheetCommon, addRowToSmartsheet } from '../common'; + +export const addRowToSheet = createAction({ + auth: smartsheetAuth, + name: 'add_row_to_sheet', + displayName: 'Add Row to Sheet', + description:'Adds new row to a sheet.', + props: { + sheet_id: smartsheetCommon.sheet_id(), + cells: smartsheetCommon.cells, + location_type: Property.StaticDropdown({ + displayName: 'Add Row to Top or Bottom', + required: true, + defaultValue: 'bottom', + options: { + options: [ + { label: 'Top of sheet', value: 'top' }, + { label: 'Bottom of sheet', value: 'bottom' }, + ], + }, + }), + }, + + async run(context) { + const { sheet_id, cells, location_type } = context.propsValue; + + // Transform dynamic cells data into proper Smartsheet format + const cellsData = cells as Record; + const transformedCells: any[] = []; + + for (const [key, value] of Object.entries(cellsData)) { + if (value === undefined || value === null || value === '') { + continue; // Skip empty values + } + + let columnId: number; + const cellObj: any = {}; + + if (key.startsWith('column_')) { + // Regular column value + columnId = parseInt(key.replace('column_', '')); + cellObj.columnId = columnId; + cellObj.value = value; + } else { + continue; // Skip unknown keys + } + + transformedCells.push(cellObj); + } + + if (transformedCells.length === 0) { + throw new Error('At least one cell value must be provided'); + } + + // Build the row object with location specifiers + const rowObj: any = { + cells: transformedCells, + }; + + // Add location specifiers based on location_type + switch (location_type) { + case 'top': + rowObj.toTop = true; + break; + case 'bottom': + rowObj.toBottom = true; + break; + } + + const rowPayload = [rowObj]; + + try { + const result = await addRowToSmartsheet( + context.auth as string, + sheet_id as string, + rowPayload, + ); + + return { + success: true, + row: result, + message: 'Row added successfully', + cells_processed: transformedCells.length, + }; + } catch (error: any) { + if (error.response?.status === 400) { + const errorBody = error.response.data; + throw new Error(`Bad Request: ${errorBody.message || 'Invalid row data or parameters'}`); + } else if (error.response?.status === 403) { + throw new Error('Insufficient permissions to add rows to this sheet'); + } else if (error.response?.status === 404) { + throw new Error('Sheet not found or you do not have access to it'); + } else if (error.response?.status === 429) { + throw new Error('Rate limit exceeded. Please try again later.'); + } + + throw new Error(`Failed to add row: ${error.message}`); + } + }, +}); diff --git a/packages/pieces/community/smartsheet/src/lib/actions/attach-file-to-row.ts b/packages/pieces/community/smartsheet/src/lib/actions/attach-file-to-row.ts new file mode 100644 index 0000000..f076e75 --- /dev/null +++ b/packages/pieces/community/smartsheet/src/lib/actions/attach-file-to-row.ts @@ -0,0 +1,204 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod, HttpRequest } from '@activepieces/pieces-common'; +import { smartsheetAuth } from '../../index'; +import { smartsheetCommon } from '../common'; + +export const attachFileToRow = createAction({ + auth: smartsheetAuth, + name: 'attach_file_to_row', + displayName: 'Attach File to Row', + description: 'Adds a file attachment to a row.', + props: { + sheet_id: smartsheetCommon.sheet_id(), + row_id: smartsheetCommon.row_id, + + attachment_type: Property.StaticDropdown({ + displayName: 'Attachment Type', + description: 'Type of attachment to add', + required: true, + defaultValue: 'FILE', + options: { + options: [ + { label: 'File Upload', value: 'FILE' }, + { label: 'URL Link', value: 'LINK' }, + { label: 'Box.com', value: 'BOX_COM' }, + { label: 'Dropbox', value: 'DROPBOX' }, + { label: 'Egnyte', value: 'EGNYTE' }, + { label: 'Evernote', value: 'EVERNOTE' }, + { label: 'Google Drive', value: 'GOOGLE_DRIVE' }, + { label: 'OneDrive', value: 'ONEDRIVE' }, + ], + }, + }), + + // For file uploads + file: Property.File({ + displayName: 'File', + description: 'The file to attach (required for FILE type)', + required: false, + }), + + // For URL attachments + url: Property.ShortText({ + displayName: 'URL', + description: 'The URL to attach (required for URL-based attachment types)', + required: false, + }), + + attachment_name: Property.ShortText({ + displayName: 'Attachment Name', + description: 'Name for the attachment (optional, will use file name or URL if not provided)', + required: false, + }), + + // Advanced options + attachment_sub_type: Property.StaticDropdown({ + displayName: 'Attachment Sub Type', + description: 'Sub type for Google Drive and Egnyte attachments', + required: false, + options: { + options: [ + { label: 'Document', value: 'DOCUMENT' }, + { label: 'Drawing', value: 'DRAWING' }, + { label: 'Folder', value: 'FOLDER' }, + { label: 'PDF', value: 'PDF' }, + { label: 'Presentation', value: 'PRESENTATION' }, + { label: 'Spreadsheet', value: 'SPREADSHEET' }, + ], + }, + }), + + mime_type: Property.ShortText({ + displayName: 'MIME Type', + description: 'MIME type of the attachment (optional, auto-detected for files)', + required: false, + }), + }, + + async run(context) { + const { + sheet_id, + row_id, + attachment_type, + file, + url, + attachment_name, + attachment_sub_type, + mime_type, + } = context.propsValue; + + // Validate input based on attachment type + if (attachment_type === 'FILE') { + if (!file) { + throw new Error('File is required when attachment type is FILE'); + } + } else { + if (!url) { + throw new Error('URL is required for URL-based attachment types'); + } + + // Validate URL format for specific types + if (attachment_type === 'BOX_COM' && !url.includes('box.com')) { + throw new Error('Box.com URLs should contain "box.com" in the domain'); + } + if (attachment_type === 'DROPBOX' && !url.includes('dropbox.com')) { + throw new Error('Dropbox URLs should contain "dropbox.com" in the domain'); + } + if (attachment_type === 'GOOGLE_DRIVE' && !url.includes('drive.google.com')) { + throw new Error('Google Drive URLs should contain "drive.google.com" in the domain'); + } + if (attachment_type === 'ONEDRIVE' && !url.includes('onedrive')) { + throw new Error('OneDrive URLs should contain "onedrive" in the domain'); + } + } + + const apiUrl = `${smartsheetCommon.baseUrl}/sheets/${sheet_id}/rows/${row_id}/attachments`; + + try { + let request: HttpRequest; + + if (attachment_type === 'FILE') { + // File upload using multipart/form-data + const formData = new FormData(); + + // Determine MIME type + const fileMimeType = mime_type || (file?.extension ? `application/${file.extension}` : 'application/octet-stream'); + const fileName = attachment_name || file?.filename || 'attachment'; + + // Create blob with proper MIME type + const blob = new Blob([file!.data], { type: fileMimeType }); + formData.append('file', blob, fileName); + + request = { + method: HttpMethod.POST, + url: apiUrl, + headers: { + 'Authorization': `Bearer ${context.auth}`, + // Don't set Content-Type for FormData, let the browser set it with boundary + }, + body: formData, + }; + } else { + // URL attachment using JSON + const attachmentData: any = { + attachmentType: attachment_type, + url: url, + }; + + if (attachment_name) { + attachmentData.name = attachment_name; + } + + if (attachment_sub_type) { + attachmentData.attachmentSubType = attachment_sub_type; + } + + if (mime_type) { + attachmentData.mimeType = mime_type; + } + + request = { + method: HttpMethod.POST, + url: apiUrl, + headers: { + 'Authorization': `Bearer ${context.auth}`, + 'Content-Type': 'application/json', + }, + body: attachmentData, + }; + } + + const response = await httpClient.sendRequest(request); + + return { + success: true, + attachment: response.body.result, + message: 'Attachment added successfully', + attachment_id: response.body.result?.id, + attachment_type: response.body.result?.attachmentType, + attachment_name: response.body.result?.name, + size_kb: response.body.result?.sizeInKb, + created_at: response.body.result?.createdAt, + created_by: response.body.result?.createdBy, + version: response.body.version, + }; + } catch (error: any) { + if (error.response?.status === 400) { + const errorBody = error.response.data; + throw new Error(`Bad Request: ${errorBody.message || 'Invalid attachment data or parameters'}`); + } else if (error.response?.status === 403) { + throw new Error('Insufficient permissions to add attachments to this sheet'); + } else if (error.response?.status === 404) { + throw new Error('Sheet or row not found or you do not have access to it'); + } else if (error.response?.status === 413) { + throw new Error('File size too large. Check Smartsheet file size limits for your plan.'); + } else if (error.response?.status === 415) { + throw new Error('Unsupported media type. Check file format restrictions.'); + } else if (error.response?.status === 429) { + throw new Error('Rate limit exceeded. Please try again later.'); + } + + throw new Error(`Failed to attach file: ${error.message}`); + } + }, +}); diff --git a/packages/pieces/community/smartsheet/src/lib/actions/find-attachment-by-row-id.ts b/packages/pieces/community/smartsheet/src/lib/actions/find-attachment-by-row-id.ts new file mode 100644 index 0000000..d41166b --- /dev/null +++ b/packages/pieces/community/smartsheet/src/lib/actions/find-attachment-by-row-id.ts @@ -0,0 +1,259 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod, HttpRequest } from '@activepieces/pieces-common'; +import { smartsheetAuth } from '../../index'; +import { smartsheetCommon } from '../common'; + +export const findAttachmentByRowId = createAction({ + auth: smartsheetAuth, + name: 'find_attachment_by_row_id', + displayName: 'List Row Attachments', + description: 'Get all attachments for a specific row in a Smartsheet, including row and discussion-level attachments with comprehensive pagination and filtering options', + props: { + sheet_id: smartsheetCommon.sheet_id(), + row_id: smartsheetCommon.row_id, + + // Pagination options + include_all: Property.Checkbox({ + displayName: 'Include All Results', + description: 'If true, include all results without pagination (overrides page and page size)', + required: false, + defaultValue: false, + }), + + page: Property.Number({ + displayName: 'Page Number', + description: 'Which page to return (defaults to 1, ignored if "Include All Results" is true)', + required: false, + defaultValue: 1, + }), + + page_size: Property.Number({ + displayName: 'Page Size', + description: 'Maximum number of items to return per page (defaults to 100, max 10000, ignored if "Include All Results" is true)', + required: false, + defaultValue: 100, + }), + + // Filtering options + attachment_type_filter: Property.StaticMultiSelectDropdown({ + displayName: 'Filter by Attachment Type', + description: 'Only return attachments of specific types (leave empty for all types)', + required: false, + options: { + options: [ + { label: 'Files', value: 'FILE' }, + { label: 'URLs/Links', value: 'LINK' }, + { label: 'Box.com', value: 'BOX_COM' }, + { label: 'Dropbox', value: 'DROPBOX' }, + { label: 'Egnyte', value: 'EGNYTE' }, + { label: 'Evernote', value: 'EVERNOTE' }, + { label: 'Google Drive', value: 'GOOGLE_DRIVE' }, + { label: 'OneDrive', value: 'ONEDRIVE' }, + { label: 'Trello', value: 'TRELLO' }, + ], + }, + }), + + parent_type_filter: Property.StaticMultiSelectDropdown({ + displayName: 'Filter by Parent Type', + description: 'Only return attachments from specific parent types (leave empty for all)', + required: false, + options: { + options: [ + { label: 'Row Attachments', value: 'ROW' }, + { label: 'Comment Attachments', value: 'COMMENT' }, + { label: 'Sheet Attachments', value: 'SHEET' }, + { label: 'Proof Attachments', value: 'PROOF' }, + ], + }, + }), + + min_file_size_kb: Property.Number({ + displayName: 'Minimum File Size (KB)', + description: 'Only return files with size greater than or equal to this value (applies to FILE type only)', + required: false, + }), + + max_file_size_kb: Property.Number({ + displayName: 'Maximum File Size (KB)', + description: 'Only return files with size less than or equal to this value (applies to FILE type only)', + required: false, + }), + }, + + async run(context) { + const { + sheet_id, + row_id, + include_all, + page, + page_size, + attachment_type_filter, + parent_type_filter, + min_file_size_kb, + max_file_size_kb, + } = context.propsValue; + + // Build query parameters + const queryParams: any = {}; + + if (include_all) { + queryParams.includeAll = true; + } else { + if (page && page > 1) { + queryParams.page = page; + } + if (page_size && page_size !== 100) { + queryParams.pageSize = Math.min(page_size, 10000); // Cap at API limit + } + } + + const apiUrl = `${smartsheetCommon.baseUrl}/sheets/${sheet_id}/rows/${row_id}/attachments`; + + try { + const request: HttpRequest = { + method: HttpMethod.GET, + url: apiUrl, + headers: { + 'Authorization': `Bearer ${context.auth}`, + 'Content-Type': 'application/json', + }, + queryParams, + }; + + const response = await httpClient.sendRequest(request); + const attachmentData = response.body; + + // Apply client-side filters + let filteredAttachments = attachmentData.data || []; + + // Filter by attachment type + if (attachment_type_filter && attachment_type_filter.length > 0) { + filteredAttachments = filteredAttachments.filter((attachment: any) => + attachment_type_filter.includes(attachment.attachmentType) + ); + } + + // Filter by parent type + if (parent_type_filter && parent_type_filter.length > 0) { + filteredAttachments = filteredAttachments.filter((attachment: any) => + parent_type_filter.includes(attachment.parentType) + ); + } + + // Filter by file size (only applies to FILE type) + if (min_file_size_kb !== undefined || max_file_size_kb !== undefined) { + filteredAttachments = filteredAttachments.filter((attachment: any) => { + if (attachment.attachmentType !== 'FILE' || !attachment.sizeInKb) { + return true; + } + + const size = attachment.sizeInKb; + if (min_file_size_kb !== undefined && size < min_file_size_kb) { + return false; + } + if (max_file_size_kb !== undefined && size > max_file_size_kb) { + return false; + } + return true; + }); + } + + // Organize attachments by type for better analysis + const attachmentsByType: any = {}; + const attachmentsByParent: any = {}; + let totalFileSize = 0; + + filteredAttachments.forEach((attachment: any) => { + // Group by attachment type + if (!attachmentsByType[attachment.attachmentType]) { + attachmentsByType[attachment.attachmentType] = []; + } + attachmentsByType[attachment.attachmentType].push(attachment); + + // Group by parent type + if (!attachmentsByParent[attachment.parentType]) { + attachmentsByParent[attachment.parentType] = []; + } + attachmentsByParent[attachment.parentType].push(attachment); + + // Calculate total file size for files + if (attachment.attachmentType === 'FILE' && attachment.sizeInKb) { + totalFileSize += attachment.sizeInKb; + } + }); + + return { + success: true, + + // Pagination info + pagination: { + page_number: attachmentData.pageNumber, + page_size: attachmentData.pageSize, + total_pages: attachmentData.totalPages, + total_count: attachmentData.totalCount, + filtered_count: filteredAttachments.length, + }, + + // Main results + attachments: filteredAttachments, + + // Organized results + attachments_by_type: attachmentsByType, + attachments_by_parent: attachmentsByParent, + + // Summary statistics + summary: { + total_attachments: filteredAttachments.length, + files_count: (attachmentsByType.FILE || []).length, + links_count: (attachmentsByType.LINK || []).length, + cloud_storage_count: filteredAttachments.length - + (attachmentsByType.FILE || []).length - + (attachmentsByType.LINK || []).length, + row_attachments: (attachmentsByParent.ROW || []).length, + comment_attachments: (attachmentsByParent.COMMENT || []).length, + total_file_size_kb: totalFileSize, + total_file_size_mb: Math.round(totalFileSize / 1024 * 100) / 100, + }, + + // Download info for files + download_info: filteredAttachments + .filter((att: any) => att.attachmentType === 'FILE' && att.url) + .map((att: any) => ({ + attachment_id: att.id, + name: att.name, + download_url: att.url, + url_expires_in_millis: att.urlExpiresInMillis, + url_expires_at: att.urlExpiresInMillis ? + new Date(Date.now() + att.urlExpiresInMillis).toISOString() : null, + size_kb: att.sizeInKb, + })), + + // Applied filters info + filters_applied: { + attachment_types: attachment_type_filter || [], + parent_types: parent_type_filter || [], + min_file_size_kb: min_file_size_kb, + max_file_size_kb: max_file_size_kb, + }, + + // Row and sheet info + row_id: row_id, + sheet_id: sheet_id, + }; + } catch (error: any) { + if (error.response?.status === 400) { + const errorBody = error.response.data; + throw new Error(`Bad Request: ${errorBody.message || 'Invalid request parameters'}`); + } else if (error.response?.status === 403) { + throw new Error('Insufficient permissions to access attachments for this row'); + } else if (error.response?.status === 404) { + throw new Error('Sheet or row not found, or you do not have access to it'); + } else if (error.response?.status === 429) { + throw new Error('Rate limit exceeded. Please try again later.'); + } + + throw new Error(`Failed to retrieve attachments: ${error.message}`); + } + }, +}); diff --git a/packages/pieces/community/smartsheet/src/lib/actions/find-rows-by-query.ts b/packages/pieces/community/smartsheet/src/lib/actions/find-rows-by-query.ts new file mode 100644 index 0000000..c077d2d --- /dev/null +++ b/packages/pieces/community/smartsheet/src/lib/actions/find-rows-by-query.ts @@ -0,0 +1,213 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod, HttpRequest } from '@activepieces/pieces-common'; +import { smartsheetAuth } from '../../index'; +import { smartsheetCommon } from '../common'; + +export const findRowsByQuery = createAction({ + auth: smartsheetAuth, + name: 'find_rows_by_query', + displayName: 'Find Row', + description: 'Finds rows in a specific sheet or across all accessible sheets using text queries with advanced filtering options.', + props: { + search_scope: Property.StaticDropdown({ + displayName: 'Search Scope', + description: 'Choose whether to search within a specific sheet or across all accessible sheets.', + required: true, + defaultValue: 'specific_sheet', + options: { + options: [ + { label: 'Specific Sheet', value: 'specific_sheet' }, + { label: 'All Accessible Sheets', value: 'all_sheets' }, + ], + }, + }), + + sheet_id: smartsheetCommon.sheet_id(false), + + query: Property.ShortText({ + displayName: 'Search Query', + description: 'Text to search for. Use double quotes for exact phrase matching (e.g., "project status")', + required: true, + }), + + // Advanced search options + search_scopes: Property.StaticMultiSelectDropdown({ + displayName: 'Search Scopes', + description: 'Specify what types of content to search in (leave empty to search all)', + required: false, + options: { + options: [ + { label: 'Cell Data', value: 'cellData' }, + { label: 'Comments', value: 'comments' }, + { label: 'Attachments', value: 'attachments' }, + { label: 'Sheet Names', value: 'sheetNames' }, + { label: 'Folder Names', value: 'folderNames' }, + { label: 'Report Names', value: 'reportNames' }, + { label: 'Dashboard Names', value: 'sightNames' }, + { label: 'Template Names', value: 'templateNames' }, + { label: 'Workspace Names', value: 'workspaceNames' }, + { label: 'Summary Fields', value: 'summaryFields' }, + ], + }, + }), + + include_favorites: Property.Checkbox({ + displayName: 'Include Favorite Flags', + description: 'Include information about which items are marked as favorites', + required: false, + defaultValue: false, + }), + + modified_since: Property.DateTime({ + displayName: 'Modified Since', + description: 'Only return results modified on or after this date/time', + required: false, + }), + + max_results: Property.Number({ + displayName: 'Max Results', + description: 'Maximum number of results to return (default: 50, max: 100)', + required: false, + defaultValue: 50, + }), + + object_types_filter: Property.StaticMultiSelectDropdown({ + displayName: 'Filter by Object Types', + description: 'Only return results of specific object types (leave empty for all types)', + required: false, + options: { + options: [ + { label: 'Rows', value: 'row' }, + { label: 'Sheets', value: 'sheet' }, + { label: 'Attachments', value: 'attachment' }, + { label: 'Comments/Discussions', value: 'discussion' }, + { label: 'Dashboards', value: 'dashboard' }, + { label: 'Reports', value: 'report' }, + { label: 'Folders', value: 'folder' }, + { label: 'Templates', value: 'template' }, + { label: 'Workspaces', value: 'workspace' }, + { label: 'Summary Fields', value: 'summaryField' }, + ], + }, + }), + }, + + async run(context) { + const { + search_scope, + sheet_id, + query, + search_scopes, + include_favorites, + modified_since, + max_results, + object_types_filter, + } = context.propsValue; + + // Validate sheet_id requirement for specific sheet search + if (search_scope === 'specific_sheet' && !sheet_id) { + throw new Error('Sheet ID is required when searching within a specific sheet'); + } + + // Build query parameters + const queryParams: any = { + query: query, + }; + + // Add search scopes if specified + if (search_scopes && search_scopes.length > 0) { + queryParams.scopes = search_scopes; + } + + // Add include favorites flag + if (include_favorites) { + queryParams.include = 'favoriteFlag'; + } + + // Add modified since filter + if (modified_since) { + queryParams.modifiedSince = new Date(modified_since as string).toISOString(); + } + + // Determine API endpoint + let apiUrl: string; + if (search_scope === 'specific_sheet') { + apiUrl = `${smartsheetCommon.baseUrl}/search/sheets/${sheet_id}`; + } else { + apiUrl = `${smartsheetCommon.baseUrl}/search`; + } + + try { + const request: HttpRequest = { + method: HttpMethod.GET, + url: apiUrl, + headers: { + 'Authorization': `Bearer ${context.auth}`, + 'Content-Type': 'application/json', + }, + queryParams, + }; + + const response = await httpClient.sendRequest(request); + const searchResults = response.body; + + // Filter by object types if specified + let filteredResults = searchResults.results || []; + if (object_types_filter && object_types_filter.length > 0) { + filteredResults = filteredResults.filter((result: any) => + object_types_filter.includes(result.objectType) + ); + } + + // Limit results if specified + const maxResults = Math.min(max_results || 50, 100); + if (filteredResults.length > maxResults) { + filteredResults = filteredResults.slice(0, maxResults); + } + + // Organize results by type for better usability + const resultsByType: any = {}; + filteredResults.forEach((result: any) => { + if (!resultsByType[result.objectType]) { + resultsByType[result.objectType] = []; + } + resultsByType[result.objectType].push(result); + }); + + return { + success: true, + total_count: searchResults.totalCount, + returned_count: filteredResults.length, + search_query: query, + search_scope: search_scope, + results: filteredResults, + results_by_type: resultsByType, + sheet_searched: search_scope === 'specific_sheet' ? sheet_id : 'all_accessible_sheets', + + // Summary statistics + summary: { + rows_found: (resultsByType.row || []).length, + sheets_found: (resultsByType.sheet || []).length, + attachments_found: (resultsByType.attachment || []).length, + discussions_found: (resultsByType.discussion || []).length, + other_objects_found: filteredResults.length - + (resultsByType.row || []).length - + (resultsByType.sheet || []).length - + (resultsByType.attachment || []).length - + (resultsByType.discussion || []).length, + }, + }; + } catch (error: any) { + if (error.response?.status === 400) { + const errorBody = error.response.data; + throw new Error(`Bad Request: ${errorBody.message || 'Invalid request parameters'}`); + } else if (error.response?.status === 403) { + throw new Error('Insufficient permissions to access sheets listing'); + } else if (error.response?.status === 429) { + throw new Error('Rate limit exceeded. Please try again later.'); + } + + throw new Error(`Failed to retrieve sheets: ${error.message}`); + } + }, +}); diff --git a/packages/pieces/community/smartsheet/src/lib/actions/find-sheet-by-name.ts b/packages/pieces/community/smartsheet/src/lib/actions/find-sheet-by-name.ts new file mode 100644 index 0000000..7e9ae92 --- /dev/null +++ b/packages/pieces/community/smartsheet/src/lib/actions/find-sheet-by-name.ts @@ -0,0 +1,395 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod, HttpRequest } from '@activepieces/pieces-common'; +import { smartsheetAuth } from '../../index'; +import { smartsheetCommon } from '../common'; + +export const findSheetByName = createAction({ + auth: smartsheetAuth, + name: 'find_sheet_by_name', + displayName: 'Find Sheet(s)', + description: 'Fetches existings sheets matching provided filter criteria.', + props: { + // Search options + sheet_name: Property.ShortText({ + displayName: 'Sheet Name Filter', + description: 'Filter sheets by name (partial or exact match). Leave empty to list all sheets.', + required: false, + }), + + exact_match: Property.Checkbox({ + displayName: 'Exact Name Match', + description: 'When filtering by name, require exact match instead of partial match', + required: false, + defaultValue: false, + }), + + // Pagination options + include_all: Property.Checkbox({ + displayName: 'Include All Results', + description: 'If true, include all results without pagination (overrides page and page size)', + required: false, + defaultValue: false, + }), + + page: Property.Number({ + displayName: 'Page Number', + description: 'Which page to return (defaults to 1, ignored if "Include All Results" is true)', + required: false, + defaultValue: 1, + }), + + page_size: Property.Number({ + displayName: 'Page Size', + description: 'Maximum number of items to return per page (defaults to 100, max 10000, ignored if "Include All Results" is true)', + required: false, + defaultValue: 100, + }), + + // Access and filtering options + access_api_level: Property.StaticDropdown({ + displayName: 'Access API Level', + description: 'API access level for viewing and filtering permissions', + required: false, + defaultValue: '0', + options: { + options: [ + { label: 'Viewer (default)', value: '0' }, + { label: 'Commenter', value: '1' }, + ], + }, + }), + + access_level_filter: Property.StaticMultiSelectDropdown({ + displayName: 'Filter by Access Level', + description: 'Only return sheets where you have specific access levels (leave empty for all)', + required: false, + options: { + options: [ + { label: 'Owner', value: 'OWNER' }, + { label: 'Admin', value: 'ADMIN' }, + { label: 'Editor (with sharing)', value: 'EDITOR_SHARE' }, + { label: 'Editor', value: 'EDITOR' }, + { label: 'Commenter', value: 'COMMENTER' }, + { label: 'Viewer', value: 'VIEWER' }, + ], + }, + }), + + modified_since: Property.DateTime({ + displayName: 'Modified Since', + description: 'Only return sheets modified on or after this date/time', + required: false, + }), + + // Additional data options + include_sheet_version: Property.Checkbox({ + displayName: 'Include Sheet Version', + description: 'Include current version number of each sheet', + required: false, + defaultValue: false, + }), + + include_source_info: Property.Checkbox({ + displayName: 'Include Source Information', + description: 'Include information about the source (template/sheet) each sheet was created from', + required: false, + defaultValue: false, + }), + + numeric_dates: Property.Checkbox({ + displayName: 'Numeric Dates', + description: 'Return dates as milliseconds since UNIX epoch instead of ISO strings', + required: false, + defaultValue: false, + }), + + // Advanced filtering + created_date_range: Property.StaticDropdown({ + displayName: 'Created Date Range', + description: 'Filter sheets by creation date range', + required: false, + options: { + options: [ + { label: 'All time', value: 'all' }, + { label: 'Last 7 days', value: 'week' }, + { label: 'Last 30 days', value: 'month' }, + { label: 'Last 90 days', value: 'quarter' }, + { label: 'Last 365 days', value: 'year' }, + ], + }, + }), + + sort_by: Property.StaticDropdown({ + displayName: 'Sort Results By', + description: 'How to sort the returned sheets', + required: false, + defaultValue: 'name', + options: { + options: [ + { label: 'Sheet Name', value: 'name' }, + { label: 'Creation Date (newest first)', value: 'created_desc' }, + { label: 'Creation Date (oldest first)', value: 'created_asc' }, + { label: 'Modified Date (newest first)', value: 'modified_desc' }, + { label: 'Modified Date (oldest first)', value: 'modified_asc' }, + { label: 'Access Level', value: 'access' }, + ], + }, + }), + }, + + async run(context) { + const { + sheet_name, + exact_match, + include_all, + page, + page_size, + access_api_level, + access_level_filter, + modified_since, + include_sheet_version, + include_source_info, + numeric_dates, + created_date_range, + sort_by, + } = context.propsValue; + + // Build query parameters + const queryParams: any = {}; + + // Pagination + if (include_all) { + queryParams.includeAll = true; + } else { + if (page && page > 1) { + queryParams.page = page; + } + if (page_size && page_size !== 100) { + queryParams.pageSize = Math.min(page_size, 10000); // Cap at API limit + } + } + + // Access level + if (access_api_level && access_api_level !== '0') { + queryParams.accessApiLevel = parseInt(access_api_level as string); + } + + // Modified since filter + if (modified_since) { + queryParams.modifiedSince = new Date(modified_since as string).toISOString(); + } + + // Include options + const includeOptions: string[] = []; + if (include_sheet_version) { + includeOptions.push('sheetVersion'); + } + if (include_source_info) { + includeOptions.push('source'); + } + if (includeOptions.length > 0) { + queryParams.include = includeOptions.join(','); + } + + // Numeric dates + if (numeric_dates) { + queryParams.numericDates = true; + } + + const apiUrl = `${smartsheetCommon.baseUrl}/sheets`; + + try { + const request: HttpRequest = { + method: HttpMethod.GET, + url: apiUrl, + headers: { + 'Authorization': `Bearer ${context.auth}`, + 'Content-Type': 'application/json', + }, + queryParams, + }; + + const response = await httpClient.sendRequest(request); + const sheetData = response.body; + + // Apply client-side filters + let filteredSheets = sheetData.data || []; + + // Filter by sheet name if specified + if (sheet_name) { + const searchName = (sheet_name as string).toLowerCase(); + filteredSheets = filteredSheets.filter((sheet: any) => { + const sheetName = sheet.name.toLowerCase(); + return exact_match ? + sheetName === searchName : + sheetName.includes(searchName); + }); + } + + // Filter by access level + if (access_level_filter && access_level_filter.length > 0) { + filteredSheets = filteredSheets.filter((sheet: any) => + access_level_filter.includes(sheet.accessLevel) + ); + } + + // Filter by creation date range + if (created_date_range && created_date_range !== 'all') { + const now = new Date(); + const cutoffDate = new Date(); + + switch (created_date_range) { + case 'week': { + cutoffDate.setDate(now.getDate() - 7); + break; + } + case 'month': { + cutoffDate.setDate(now.getDate() - 30); + break; + } + case 'quarter': { + cutoffDate.setDate(now.getDate() - 90); + break; + } + case 'year': { + cutoffDate.setDate(now.getDate() - 365); + break; + } + } + + filteredSheets = filteredSheets.filter((sheet: any) => { + const createdDate = new Date(sheet.createdAt); + return createdDate >= cutoffDate; + }); + } + + // Sort results + if (sort_by) { + filteredSheets.sort((a: any, b: any) => { + switch (sort_by) { + case 'name': + return a.name.localeCompare(b.name); + case 'created_desc': + return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); + case 'created_asc': + return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(); + case 'modified_desc': + return new Date(b.modifiedAt).getTime() - new Date(a.modifiedAt).getTime(); + case 'modified_asc': + return new Date(a.modifiedAt).getTime() - new Date(b.modifiedAt).getTime(); + case 'access': { + const accessOrder = ['OWNER', 'ADMIN', 'EDITOR_SHARE', 'EDITOR', 'COMMENTER', 'VIEWER']; + return accessOrder.indexOf(a.accessLevel) - accessOrder.indexOf(b.accessLevel); + } + default: + return 0; + } + }); + } + + // Organize sheets by access level for analysis + const sheetsByAccess: any = {}; + const sheetsBySource: any = {}; + + filteredSheets.forEach((sheet: any) => { + // Group by access level + if (!sheetsByAccess[sheet.accessLevel]) { + sheetsByAccess[sheet.accessLevel] = []; + } + sheetsByAccess[sheet.accessLevel].push(sheet); + + // Group by source type (if source info is included) + if (sheet.source) { + const sourceType = sheet.source.type || 'unknown'; + if (!sheetsBySource[sourceType]) { + sheetsBySource[sourceType] = []; + } + sheetsBySource[sourceType].push(sheet); + } + }); + + // Calculate date-based statistics + const now = new Date(); + const recentlyModified = filteredSheets.filter((sheet: any) => { + const modifiedDate = new Date(sheet.modifiedAt); + const daysDiff = (now.getTime() - modifiedDate.getTime()) / (1000 * 3600 * 24); + return daysDiff <= 7; + }).length; + + const recentlyCreated = filteredSheets.filter((sheet: any) => { + const createdDate = new Date(sheet.createdAt); + const daysDiff = (now.getTime() - createdDate.getTime()) / (1000 * 3600 * 24); + return daysDiff <= 7; + }).length; + + return { + success: true, + + // Pagination info + pagination: { + page_number: sheetData.pageNumber, + page_size: sheetData.pageSize, + total_pages: sheetData.totalPages, + total_count: sheetData.totalCount, + filtered_count: filteredSheets.length, + }, + + // Main results + sheets: filteredSheets, + + // Organized results + sheets_by_access_level: sheetsByAccess, + sheets_by_source_type: sheetsBySource, + + // Summary statistics + summary: { + total_sheets: filteredSheets.length, + owned_sheets: (sheetsByAccess.OWNER || []).length, + admin_sheets: (sheetsByAccess.ADMIN || []).length, + editor_sheets: ((sheetsByAccess.EDITOR || []).length + (sheetsByAccess.EDITOR_SHARE || []).length), + commenter_sheets: (sheetsByAccess.COMMENTER || []).length, + viewer_sheets: (sheetsByAccess.VIEWER || []).length, + recently_modified: recentlyModified, + recently_created: recentlyCreated, + sheets_with_source: Object.values(sheetsBySource).flat().length, + }, + + // Access level breakdown + access_breakdown: Object.keys(sheetsByAccess).map(level => ({ + access_level: level, + count: sheetsByAccess[level].length, + percentage: Math.round((sheetsByAccess[level].length / filteredSheets.length) * 100), + })), + + // Applied filters info + filters_applied: { + name_filter: sheet_name || null, + exact_match: exact_match, + access_levels: access_level_filter || [], + modified_since: modified_since || null, + created_date_range: created_date_range || 'all', + sort_by: sort_by || 'name', + }, + + // API options used + api_options: { + access_api_level: access_api_level, + include_sheet_version: include_sheet_version, + include_source_info: include_source_info, + numeric_dates: numeric_dates, + }, + }; + } catch (error: any) { + if (error.response?.status === 400) { + const errorBody = error.response.data; + throw new Error(`Bad Request: ${errorBody.message || 'Invalid request parameters'}`); + } else if (error.response?.status === 403) { + throw new Error('Insufficient permissions to access sheets listing'); + } else if (error.response?.status === 429) { + throw new Error('Rate limit exceeded. Please try again later.'); + } + + throw new Error(`Failed to retrieve sheets: ${error.message}`); + } + }, +}); diff --git a/packages/pieces/community/smartsheet/src/lib/actions/update-row.ts b/packages/pieces/community/smartsheet/src/lib/actions/update-row.ts new file mode 100644 index 0000000..ac1d0cf --- /dev/null +++ b/packages/pieces/community/smartsheet/src/lib/actions/update-row.ts @@ -0,0 +1,78 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { smartsheetAuth } from '../../index'; +import { smartsheetCommon, updateRowInSmartsheet } from '../common'; + +export const updateRow = createAction({ + auth: smartsheetAuth, + name: 'update_row', + displayName: 'Update Row', + description: 'Updates an existing row.', + props: { + sheet_id: smartsheetCommon.sheet_id(), + row_id: smartsheetCommon.row_id, + cells: smartsheetCommon.cells, + }, + + async run(context) { + const { sheet_id, row_id, cells } = context.propsValue; + + const rowObj: any = { + id: row_id, + }; + + // Transform dynamic cells data into proper Smartsheet format + const cellsData = cells as Record; + const transformedCells: any[] = []; + + for (const [key, value] of Object.entries(cellsData)) { + if (value === undefined || value === null || value === '') { + continue; // Skip empty values + } + + let columnId: number; + const cellObj: any = {}; + + if (key.startsWith('column_')) { + // Regular column value + columnId = parseInt(key.replace('column_', '')); + cellObj.columnId = columnId; + cellObj.value = value; + } else { + continue; // Skip unknown keys + } + + transformedCells.push(cellObj); + } + + // Only add cells array if we have cells to update + if (transformedCells.length > 0) { + rowObj.cells = transformedCells; + } + + try { + const result = await updateRowInSmartsheet(context.auth as string, sheet_id as string, [ + [rowObj], + ]); + + return { + success: true, + row: result, + message: 'Row updated successfully', + cells_processed: transformedCells.length, + }; + } catch (error: any) { + if (error.response?.status === 400) { + const errorBody = error.response.data; + throw new Error(`Bad Request: ${errorBody.message || 'Invalid row data or parameters'}`); + } else if (error.response?.status === 403) { + throw new Error('Insufficient permissions to update rows in this sheet'); + } else if (error.response?.status === 404) { + throw new Error('Sheet not found or you do not have access to it'); + } else if (error.response?.status === 429) { + throw new Error('Rate limit exceeded. Please try again later.'); + } + + throw new Error(`Failed to update row: ${error.message}`); + } + }, +}); diff --git a/packages/pieces/community/smartsheet/src/lib/common/index.ts b/packages/pieces/community/smartsheet/src/lib/common/index.ts new file mode 100644 index 0000000..9209bc9 --- /dev/null +++ b/packages/pieces/community/smartsheet/src/lib/common/index.ts @@ -0,0 +1,883 @@ +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpRequest, HttpMethod } from '@activepieces/pieces-common'; +import crypto from 'crypto'; + +export const smartsheetCommon = { + baseUrl: 'https://api.smartsheet.com/2.0', + + sheet_id:(required=true)=> Property.Dropdown({ + displayName: 'Sheet', + description: 'Select a sheet', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first.', + options: [], + }; + } + + try { + const sheets = await listSheets(auth as string); + + if (sheets.length === 0) { + return { + disabled: true, + placeholder: 'No sheets found in your account.', + options: [], + }; + } + + return { + options: sheets.map((sheet: SmartsheetSheet) => ({ + value: sheet.id.toString(), + label: sheet.name, + })), + }; + } catch (error) { + return { + disabled: true, + placeholder: 'Failed to load sheets - check your connection.', + options: [], + }; + } + }, + }), + + column_id: Property.Dropdown({ + displayName: 'Column', + description: 'Select a column', + required: true, + refreshers: ['sheet_id'], + options: async ({ auth, sheet_id }) => { + if (!auth) { + return { + disabled: true, + placeholder: '⚠️ Please authenticate with Smartsheet first', + options: [], + }; + } + + if (!sheet_id) { + return { + disabled: true, + placeholder: '📋 Please select a sheet first', + options: [], + }; + } + + try { + const columns = await getSheetColumns( + auth as unknown as string, + sheet_id as unknown as string, + ); + + if (columns.length === 0) { + return { + disabled: true, + placeholder: '📄 No columns found in this sheet', + options: [], + }; + } + + return { + options: columns.map((column: SmartsheetColumn) => ({ + value: column.id.toString(), + label: column.title, + })), + }; + } catch (error) { + return { + disabled: true, + placeholder: '❌ Failed to load columns - check your permissions', + options: [], + }; + } + }, + }), + + // Dynamic cell properties based on column types + cells: Property.DynamicProperties({ + displayName: 'Cells', + description: 'Cell data with properties based on column types', + required: true, + refreshers: ['sheet_id'], + props: async ({ auth, sheet_id }) => { + if (!auth || !sheet_id) return {}; + + const fields: DynamicPropsValue = {}; + + try { + const columns = await getSheetColumns( + auth as unknown as string, + sheet_id as unknown as string, + ); + + if (columns.length === 0) { + return {}; + } + + for (const column of columns) { + const baseProps = { + displayName: column.title, + required: false, + }; + + // Create cell properties based on column type + switch (column.type?.toLowerCase()) { + case 'TEXT_NUMBER': + fields[`column_${column.id}`] = Property.ShortText({ + ...baseProps, + }); + break; + case 'DATE': + fields[`column_${column.id}`] = Property.DateTime({ + ...baseProps, + description: `Date/time value for ${column.title}`, + }); + break; + case 'CHECKBOX': + fields[`column_${column.id}`] = Property.Checkbox({ + ...baseProps, + }); + break; + case 'PICKLIST': + case 'MULTI_PICKLIST': { + if (column.options && column.options.length > 0) { + const dropdownOptions = column.options.map((option) => ({ + label: option, + value: option, + })); + + if (column.type?.toLowerCase() === 'multi_picklist') { + fields[`column_${column.id}`] = Property.StaticMultiSelectDropdown({ + ...baseProps, + description: `Multiple selection for ${column.title}`, + options: { + options: dropdownOptions, + }, + }); + } else { + fields[`column_${column.id}`] = Property.StaticDropdown({ + ...baseProps, + description: `Select option for ${column.title}`, + options: { + options: dropdownOptions, + }, + }); + } + } else { + fields[`column_${column.id}`] = Property.ShortText({ + ...baseProps, + description: `Value for ${column.title}`, + }); + } + break; + } + case 'CONTACT_LIST': + case 'MULTI_CONTACT_LIST': + fields[`column_${column.id}`] = Property.ShortText({ + ...baseProps, + description: `Contact email(s) for ${column.title}. For multiple contacts, separate with commas.`, + }); + break; + case 'DURATION': + fields[`column_${column.id}`] = Property.ShortText({ + ...baseProps, + description: `For example, 4d 6h 30m`, + }); + break; + case 'PREDECESSOR': + fields[`column_${column.id}`] = Property.ShortText({ + ...baseProps, + description: `Predecessor row numbers for ${column.title}. Format: "1FS+2d,3SS" etc.`, + }); + break; + case 'ABSTRACT_DATETIME': + fields[`column_${column.id}`] = Property.DateTime({ + ...baseProps, + description: `Date/time value for ${column.title}`, + }); + break; + default: + fields[`column_${column.id}`] = Property.ShortText({ + ...baseProps, + description: `Value for ${column.title} (${column.type || 'unknown type'})`, + }); + break; + } + } + return fields; + } catch (error) { + console.error('Failed to fetch columns for dynamic properties:', error); + return {}; + } + }, + }), + + // Dynamic row selector + row_id: Property.Dropdown({ + displayName: 'Row', + required: true, + refreshers: ['sheet_id'], + options: async ({ auth, sheet_id }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first.', + options: [], + }; + } + + if (!sheet_id) { + return { + disabled: true, + placeholder: 'Please select a sheet first', + options: [], + }; + } + + try { + const sheet = await getSheet(auth as unknown as string, sheet_id as unknown as string); + const rows = sheet.rows || []; + + if (rows.length === 0) { + return { + disabled: true, + placeholder: 'No rows found in this sheet', + options: [], + }; + } + + return { + disabled:false, + options: rows.slice(0, 100).map((row: any) => { + // Get the primary column value for display + const primaryCell = row.cells?.find((cell: any) => + sheet.columns?.find((col: any) => col.id === cell.columnId && col.primary), + ); + const displayValue = + primaryCell?.displayValue || primaryCell?.value || `Row ${row.rowNumber}`; + + return { + value: row.id.toString(), + label: `${displayValue} (Row ${row.rowNumber})`, + }; + }), + }; + } catch (error) { + return { + disabled: true, + placeholder: 'Failed to load rows - check your permissions', + options: [], + }; + } + }, + }), + + // Dynamic sheet selector for hyperlinks + hyperlink_sheet_id: Property.Dropdown({ + displayName: 'Target Sheet', + description: 'Select a sheet to link to', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: '⚠️ Please authenticate with Smartsheet first', + options: [], + }; + } + + try { + const sheets = await listSheets(auth as unknown as string); + + if (sheets.length === 0) { + return { + disabled: true, + placeholder: '📂 No sheets found in your account', + options: [], + }; + } + + return { + options: sheets.map((sheet: SmartsheetSheet) => ({ + value: sheet.id.toString(), + label: sheet.name, + })), + }; + } catch (error) { + return { + disabled: true, + placeholder: '❌ Failed to load sheets - check your permissions', + options: [], + }; + } + }, + }), + + // Dynamic report selector for hyperlinks + hyperlink_report_id: Property.Dropdown({ + displayName: 'Target Report', + description: 'Select a report to link to', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: '⚠️ Please authenticate with Smartsheet first', + options: [], + }; + } + + try { + const reports = await listReports(auth as unknown as string); + + if (reports.length === 0) { + return { + disabled: true, + placeholder: '📊 No reports found in your account', + options: [], + }; + } + + return { + options: reports.map((report: SmartsheetReport) => ({ + value: report.id.toString(), + label: `${report.name}${report.isSummaryReport ? ' (Summary)' : ' (Row Report)'}`, + })), + }; + } catch (error) { + return { + disabled: true, + placeholder: '❌ Failed to load reports - check your permissions', + options: [], + }; + } + }, + }), + + // Dynamic column selector for search/filter operations + search_columns: Property.MultiSelectDropdown({ + displayName: 'Search Columns', + description: 'Select specific columns to search within (leave empty to search all columns)', + required: false, + refreshers: ['sheet_id'], + options: async ({ auth, sheet_id }) => { + if (!auth) { + return { + disabled: true, + placeholder: '⚠️ Please authenticate with Smartsheet first', + options: [], + }; + } + + if (!sheet_id) { + return { + disabled: true, + placeholder: '📋 Please select a sheet first', + options: [], + }; + } + + try { + const columns = await getSheetColumns( + auth as unknown as string, + sheet_id as unknown as string, + ); + const searchableColumns = columns.filter( + (column) => column.type?.toLowerCase() !== 'auto_number', + ); + + if (searchableColumns.length === 0) { + return { + disabled: true, + placeholder: '📄 No searchable columns found in this sheet', + options: [], + }; + } + + return { + options: searchableColumns.map((column: SmartsheetColumn) => ({ + value: column.id.toString(), + label: `${column.title} (${column.type || 'unknown'})`, + })), + }; + } catch (error) { + return { + disabled: true, + placeholder: '❌ Failed to load columns - check your permissions', + options: [], + }; + } + }, + }), +}; + +// Interfaces +export interface SmartsheetSheet { + id: number; + name: string; + accessLevel: string; + permalink: string; + createdAt: string; + modifiedAt: string; +} + +export interface SmartsheetColumn { + id: number; + index: number; + title: string; + type?: string; + primary?: boolean; + options?: string[]; + validation?: boolean; + width?: number; + hidden?: boolean; + locked?: boolean; + lockedForUser?: boolean; +} + +export interface SmartsheetRow { + id: number; + rowNumber: number; + siblingId?: number; + expanded?: boolean; + createdAt: string; + modifiedAt: string; + cells: SmartsheetCell[]; +} + +export interface SmartsheetCell { + columnId: number; + value?: any; + displayValue?: string; + formula?: string; +} + +export interface SmartsheetAttachment { + id: number; + name: string; + url: string; + attachmentType: string; + createdAt: string; + createdBy: { + name: string; + email: string; + }; +} + +export interface SmartsheetComment { + id: number; + text: string; + createdAt: string; + createdBy: { + name: string; + email: string; + }; +} + +export interface SmartsheetReport { + id: number; + name: string; + accessLevel: 'ADMIN' | 'COMMENTER' | 'EDITOR' | 'EDITOR_SHARE' | 'OWNER' | 'VIEWER'; + isSummaryReport: boolean; + ownerId: number; + createdAt: string; + modifiedAt: string; + permalink: string; + owner?: string; + totalRowCount?: number; + version?: number; +} + +export interface SmartsheetReportsResponse { + pageNumber: number; + pageSize: number | null; + totalPages: number; + totalCount: number; + data: SmartsheetReport[]; +} + +// Helper functions +export async function listSheets(accessToken: string): Promise { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${smartsheetCommon.baseUrl}/sheets`, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }; + + const response = await httpClient.sendRequest<{ data: SmartsheetSheet[] }>(request); + return response.body.data; +} + +export async function getSheet(accessToken: string, sheetId: string): Promise { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${smartsheetCommon.baseUrl}/sheets/${sheetId}`, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} + +export async function getSheetColumns( + accessToken: string, + sheetId: string, +): Promise { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${smartsheetCommon.baseUrl}/sheets/${sheetId}/columns?include=columnType`, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }; + + const response = await httpClient.sendRequest<{ data: SmartsheetColumn[] }>(request); + return response.body.data; +} + +export async function addRowToSmartsheet( + accessToken: string, + sheetId: string, + rowData: any, + queryParams: any = {}, +): Promise { + // Build query string from parameters + const queryString = new URLSearchParams(); + + if (queryParams.allowPartialSuccess) { + queryString.append('allowPartialSuccess', 'true'); + } + if (queryParams.overrideValidation) { + queryString.append('overrideValidation', 'true'); + } + if (queryParams.accessApiLevel) { + queryString.append('accessApiLevel', queryParams.accessApiLevel.toString()); + } + + const url = `${smartsheetCommon.baseUrl}/sheets/${sheetId}/rows${ + queryString.toString() ? '?' + queryString.toString() : '' + }`; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: url, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + body: rowData, + }; + + const response = await httpClient.sendRequest<{ result: SmartsheetRow[] }>(request); + return response.body.result[0]; +} + +export async function updateRowInSmartsheet( + accessToken: string, + sheetId: string, + rowData: any, + queryParams: any = {}, +): Promise { + // Build query string from parameters + const queryString = new URLSearchParams(); + + if (queryParams.allowPartialSuccess) { + queryString.append('allowPartialSuccess', 'true'); + } + if (queryParams.overrideValidation) { + queryString.append('overrideValidation', 'true'); + } + if (queryParams.accessApiLevel) { + queryString.append('accessApiLevel', queryParams.accessApiLevel.toString()); + } + + const url = `${smartsheetCommon.baseUrl}/sheets/${sheetId}/rows${ + queryString.toString() ? '?' + queryString.toString() : '' + }`; + + const request: HttpRequest = { + method: HttpMethod.PUT, + url: url, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + body: rowData, + }; + + const response = await httpClient.sendRequest<{ result: SmartsheetRow[] }>(request); + return response.body.result[0]; +} + +export async function getRowAttachments( + accessToken: string, + sheetId: string, + rowId: string, +): Promise { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${smartsheetCommon.baseUrl}/sheets/${sheetId}/rows/${rowId}/attachments`, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }; + + const response = await httpClient.sendRequest<{ + data: SmartsheetAttachment[]; + }>(request); + return response.body.data || []; +} + +export async function findSheetsByName( + accessToken: string, + name: string, +): Promise { + const sheets = await listSheets(accessToken); + return sheets.filter((sheet) => sheet.name.toLowerCase().includes(name.toLowerCase())); +} + +export async function listReports( + accessToken: string, + modifiedSince?: string, +): Promise { + // Build query parameters + const queryParams: any = {}; + if (modifiedSince) { + queryParams.modifiedSince = modifiedSince; + } + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${smartsheetCommon.baseUrl}/reports`, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + queryParams: Object.keys(queryParams).length > 0 ? queryParams : undefined, + }; + + const response = await httpClient.sendRequest(request); + return response.body.data || []; +} + +// Webhook management functions +export interface SmartsheetWebhook { + id: number; + name: string; + callbackUrl: string; + scope: string; + scopeObjectId: number; + events: string[]; + enabled: boolean; + status: string; + sharedSecret: string; +} + +export async function subscribeWebhook( + accessToken: string, + webhookUrl: string, + sheetId: string, + webhookName: string, +): Promise { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${smartsheetCommon.baseUrl}/webhooks`, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + body: { + name: webhookName, + callbackUrl: webhookUrl, + scope: 'sheet', + scopeObjectId: parseInt(sheetId), + events: ['*.*'], + version: 1, + }, + }; + + const response = await httpClient.sendRequest<{ result: SmartsheetWebhook }>(request); + return response.body.result; +} + +export async function enableWebhook( + accessToken: string, + webhookId: string, +): Promise { + const request: HttpRequest = { + method: HttpMethod.PUT, + url: `${smartsheetCommon.baseUrl}/webhooks/${webhookId}`, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + body: { + enabled: true, + }, + }; + + const response = await httpClient.sendRequest<{ result: SmartsheetWebhook }>(request); + return response.body.result; +} + +export async function unsubscribeWebhook(accessToken: string, webhookId: string): Promise { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `${smartsheetCommon.baseUrl}/webhooks/${webhookId}`, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }; + + await httpClient.sendRequest(request); +} + +export async function listWebhooks(accessToken: string): Promise { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${smartsheetCommon.baseUrl}/webhooks`, + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }; + + const response = await httpClient.sendRequest<{ data: SmartsheetWebhook[] }>(request); + return response.body.data || []; +} + + +export interface WebhookInformation { + webhookId: string; + sharedSecret: string; + webhookName: string; +} + +export async function findOrCreateWebhook( + accessToken: string, + webhookUrl: string, + sheetId: string, + triggerIdentifier: string, +): Promise { + const webhookName = `AP-${triggerIdentifier.slice(-8)}-Sheet${sheetId}`; + + const existingWebhooks = await listWebhooks(accessToken); + const existingWebhook = existingWebhooks.find( + (wh) => wh.callbackUrl === webhookUrl && wh.scopeObjectId.toString() === sheetId, + ); + + if (existingWebhook) { + if (existingWebhook.name !== webhookName) { + console.log( + `Found existing webhook ${existingWebhook.id} with different name: ${existingWebhook.name}. Expected: ${webhookName}`, + ); + } + if (!existingWebhook.enabled || existingWebhook.status !== 'ENABLED') { + return await enableWebhook(accessToken, existingWebhook.id.toString()); + } + return existingWebhook; + } + + const newWebhook = await subscribeWebhook(accessToken, webhookUrl, sheetId, webhookName); + + return await enableWebhook(accessToken, newWebhook.id.toString()); +} +export function verifyWebhookSignature( + webhookSecret?: string, + webhookSignatureHeader?: string, + webhookRawBody?: any, +): boolean { + if (!webhookSecret || !webhookSignatureHeader || !webhookRawBody) { + return false; + } + + try { + const hmac = crypto.createHmac('sha256', webhookSecret); + hmac.update(webhookRawBody); + const expectedSignature = hmac.digest('hex'); + + return crypto.timingSafeEqual( + Buffer.from(webhookSignatureHeader, 'hex'), + Buffer.from(expectedSignature, 'hex'), + ); + } catch (error) { + return false; + } +} + +export async function getSheetRowDetails( + accessToken: string, + sheetId: string, + rowId: string, +): Promise { + try { + const req: HttpRequest = { + method: HttpMethod.GET, + url: `${smartsheetCommon.baseUrl}/sheets/${sheetId}/rows/${rowId}`, + headers: { Authorization: `Bearer ${accessToken}` }, + }; + const response = await httpClient.sendRequest(req); + return response.body; + } catch (e: any) { + if (e.response?.status === 404) { + console.log(`Row ${rowId} on sheet ${sheetId} not found during detail fetch.`); + return null; + } + console.error(`Error fetching row ${rowId} from sheet ${sheetId}:`, e); + throw e; + } +} + +export async function getAttachmentFullDetails(accessToken: string, sheetId: string, attachmentId: string): Promise { + try { + const req: HttpRequest = { + method: HttpMethod.GET, + url: `${smartsheetCommon.baseUrl}/sheets/${sheetId}/attachments/${attachmentId}`, + headers: { 'Authorization': `Bearer ${accessToken}` } + }; + const response = await httpClient.sendRequest(req); + return response.body; + } catch (e: any) { + if (e.response?.status === 404) { + console.log(`Attachment ${attachmentId} on sheet ${sheetId} not found.`); + return null; + } + console.error(`Error fetching attachment ${attachmentId} from sheet ${sheetId}:`, e); + throw e; + } +} + +export async function getCommentFullDetails(accessToken: string, sheetId: string, discussionId: string, commentId: string): Promise { + try { + const req: HttpRequest = { + method: HttpMethod.GET, + url: `${smartsheetCommon.baseUrl}/sheets/${sheetId}/comments/${commentId}`, + headers: { 'Authorization': `Bearer ${accessToken}` } + }; + const response = await httpClient.sendRequest(req); + return response.body; + } catch (e: any) { + if (e.response?.status === 404) { + console.log(`Comment ${commentId} in discussion ${discussionId} on sheet ${sheetId} not found.`); + return null; + } + console.error(`Error fetching comment ${commentId} from sheet ${sheetId}:`, e); + throw e; + } +} \ No newline at end of file diff --git a/packages/pieces/community/smartsheet/src/lib/triggers/new-attachment-trigger.ts b/packages/pieces/community/smartsheet/src/lib/triggers/new-attachment-trigger.ts new file mode 100644 index 0000000..49ae810 --- /dev/null +++ b/packages/pieces/community/smartsheet/src/lib/triggers/new-attachment-trigger.ts @@ -0,0 +1,139 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { smartsheetAuth } from '../../index'; +import { + smartsheetCommon, + findOrCreateWebhook, + WebhookInformation, + verifyWebhookSignature, + getAttachmentFullDetails, +} from '../common'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; + +const TRIGGER_KEY = 'smartsheet_new_attachment_trigger'; + +export const newAttachmentTrigger = createTrigger({ + auth: smartsheetAuth, + name: 'new_attachment_', + displayName: 'New Attachment Added', + description: 'Triggers when a new attachment is added to a row or sheet.', + props: { + sheet_id: smartsheetCommon.sheet_id(), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: { + sheetId: '12345', + eventType: 'created', + objectType: 'attachment', + id: 78901, // Attachment ID + parentId: 67890, // e.g., Row ID if attached to a row + parentType: 'ROW', + timestamp: '2023-10-28T12:10:00Z', + userId: 54321, + attachmentData: { + /* ... full attachment data ... */ + }, + }, + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.BODY_PARAM_PRESENT, + paramName: 'challenge', + }, + async onHandshake(context) { + return { + status: 200, + body: { + smartsheetHookResponse: (context.payload.body as any)['challenge'], + }, + }; + }, + + async onEnable(context) { + const { sheet_id } = context.propsValue; + if (!sheet_id) throw new Error('Sheet ID is required to enable the webhook.'); + + const triggerIdentifier = context.webhookUrl.substring(context.webhookUrl.lastIndexOf('/') + 1); + const webhook = await findOrCreateWebhook( + context.auth as string, + context.webhookUrl, + sheet_id as string, + triggerIdentifier, + ); + + await context.store.put(TRIGGER_KEY, { + webhookId: webhook.id.toString(), + sharedSecret: webhook.sharedSecret, + webhookName: webhook.name, + }); + }, + + async onDisable(context) { + const { sheet_id } = context.propsValue; + if (!sheet_id) throw new Error('Sheet ID is required to enable the webhook.'); + + const triggerIdentifier = context.webhookUrl.substring(context.webhookUrl.lastIndexOf('/') + 1); + const webhook = await findOrCreateWebhook( + context.auth as string, + context.webhookUrl, + sheet_id as string, + triggerIdentifier, + ); + + await context.store.put(TRIGGER_KEY, { + webhookId: webhook.id.toString(), + sharedSecret: webhook.sharedSecret, + webhookName: webhook.name, + }); + }, + + async run(context) { + const payload = context.payload.body as any; + const headers = context.payload.headers as Record; + const webhookInfo = await context.store.get(TRIGGER_KEY); + + if (!webhookInfo) { + return []; + } + + if (headers && headers['smartsheet-hook-challenge']) { + return []; + } + + const webhookSecret = webhookInfo?.sharedSecret; + const webhookSignatureHeader = context.payload.headers['smartsheet-hmac-sha256']; + const rawBody = context.payload.rawBody; + + if (!verifyWebhookSignature(webhookSecret, webhookSignatureHeader, rawBody)) { + return []; + } + + if (payload.newWebhookStatus) { + return []; + } + if (!payload.events || !Array.isArray(payload.events) || payload.events.length === 0) { + return []; + } + + const newAttachmentEvents = []; + for (const event of payload.events) { + if (event.objectType === 'attachment' && event.eventType === 'created') { + const eventOutput: any = { ...event, sheetId: payload.scopeObjectId?.toString() }; + const objectSheetId = payload.scopeObjectId?.toString(); + if (objectSheetId) { + try { + eventOutput.attachmentData = await getAttachmentFullDetails( + context.auth as string, + objectSheetId, + event.id.toString(), + ); + } catch (error: any) { + eventOutput.fetchError = error.message; + } + } else { + eventOutput.fetchError = 'scopeObjectId missing'; + } + + newAttachmentEvents.push(eventOutput); + } + } + return newAttachmentEvents; + }, +}); diff --git a/packages/pieces/community/smartsheet/src/lib/triggers/new-comment-trigger.ts b/packages/pieces/community/smartsheet/src/lib/triggers/new-comment-trigger.ts new file mode 100644 index 0000000..1596423 --- /dev/null +++ b/packages/pieces/community/smartsheet/src/lib/triggers/new-comment-trigger.ts @@ -0,0 +1,138 @@ +import { createTrigger, TriggerStrategy, Property } from '@activepieces/pieces-framework'; +import { smartsheetAuth } from '../../index'; +import { + smartsheetCommon, + unsubscribeWebhook, + WebhookInformation, + findOrCreateWebhook, + verifyWebhookSignature, + getCommentFullDetails, +} from '../common'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; + +const TRIGGER_KEY = 'smartsheet_new_comment_trigger'; + +export const newCommentTrigger = createTrigger({ + auth: smartsheetAuth, + name: 'new_comment_webhook', + displayName: 'New Comment Added', + description: 'Triggers when a new comment is added to a discussion on a sheet.', + props: { + sheet_id: smartsheetCommon.sheet_id(), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: { + sheetId: '12345', + eventType: 'created', + objectType: 'comment', + id: 89012, // Comment ID + discussionId: 45678, + parentId: 67890, // e.g., Row ID comment is on + parentType: 'ROW', + timestamp: '2023-10-28T12:15:00Z', + userId: 54321, + commentData: { + /* ... full comment data ... */ + }, + }, + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.BODY_PARAM_PRESENT, + paramName: 'challenge', + }, + async onHandshake(context) { + return { + status: 200, + body: { + smartsheetHookResponse: (context.payload.body as any)['challenge'], + }, + }; + }, + + async onEnable(context) { + const { sheet_id } = context.propsValue; + if (!sheet_id) throw new Error('Sheet ID is required to enable the webhook.'); + + const triggerIdentifier = context.webhookUrl.substring(context.webhookUrl.lastIndexOf('/') + 1); + const webhook = await findOrCreateWebhook( + context.auth as string, + context.webhookUrl, + sheet_id as string, + triggerIdentifier, + ); + + await context.store.put(TRIGGER_KEY, { + webhookId: webhook.id.toString(), + sharedSecret: webhook.sharedSecret, + webhookName: webhook.name, + }); + }, + + async onDisable(context) { + const webhookInfo = await context.store.get(TRIGGER_KEY); + + if (webhookInfo && webhookInfo.webhookId) { + try { + await unsubscribeWebhook(context.auth as string, webhookInfo.webhookId); + } catch (error: any) { + if (error.response?.status !== 404) { + console.error(`Error unsubscribing webhook ${webhookInfo.webhookId}: ${error.message}`); + } + } + await context.store.delete(TRIGGER_KEY); + } + }, + + async run(context): Promise { + const payload = context.payload.body as any; + const headers = context.payload.headers as Record; + const webhookInfo = await context.store.get(TRIGGER_KEY); + + if (!webhookInfo) { + return []; + } + + if (headers && headers['smartsheet-hook-challenge']) { + return []; + } + + const webhookSecret = webhookInfo?.sharedSecret; + const webhookSignatureHeader = context.payload.headers['smartsheet-hmac-sha256']; + const rawBody = context.payload.rawBody; + + if (!verifyWebhookSignature(webhookSecret, webhookSignatureHeader, rawBody)) { + return []; + } + + if (payload.newWebhookStatus) { + return []; + } + if (!payload.events || !Array.isArray(payload.events) || payload.events.length === 0) { + return []; + } + + const newCommentEvents = []; + for (const event of payload.events) { + if (event.objectType === 'comment' && event.eventType === 'created') { + const eventOutput: any = { ...event, sheetId: payload.scopeObjectId?.toString() }; + const objectSheetId = payload.scopeObjectId?.toString(); + if (objectSheetId) { + try { + eventOutput.commentData = await getCommentFullDetails( + context.auth as string, + objectSheetId, + event.discussionId.toString(), + event.id.toString(), + ); + } catch (error: any) { + eventOutput.fetchError = error.message; + } + } else { + eventOutput.fetchError = 'scopeObjectId missing'; + } + + newCommentEvents.push(eventOutput); + } + } + return newCommentEvents; + }, +}); diff --git a/packages/pieces/community/smartsheet/src/lib/triggers/new-row-trigger.ts b/packages/pieces/community/smartsheet/src/lib/triggers/new-row-trigger.ts new file mode 100644 index 0000000..dffad4f --- /dev/null +++ b/packages/pieces/community/smartsheet/src/lib/triggers/new-row-trigger.ts @@ -0,0 +1,145 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { smartsheetAuth } from '../../index'; +import { + smartsheetCommon, + unsubscribeWebhook, + getSheetRowDetails, + WebhookInformation, + findOrCreateWebhook, + verifyWebhookSignature, +} from '../common'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; + +const TRIGGER_KEY = 'smartsheet_new_row_trigger'; + +export const newRowAddedTrigger = createTrigger({ + auth: smartsheetAuth, + name: 'new_row_added', + displayName: 'New Row Added', + description: 'Triggers when a new row is added.', + props: { + sheet_id: smartsheetCommon.sheet_id(), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: { + sheetId: '12345', + eventType: 'created', + objectType: 'row', + id: 67890, // Row ID + timestamp: '2023-10-28T12:00:00Z', + userId: 54321, + rowData: { + id: 67890, + sheetId: 12345, + rowNumber: 15, + createdAt: '2023-10-28T12:00:00Z', + modifiedAt: '2023-10-28T12:00:00Z', + cells: [ + { columnId: 111, value: 'New Task A', displayValue: 'New Task A' }, + { columnId: 222, value: 'Pending', displayValue: 'Pending' }, + ], + }, + }, + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.BODY_PARAM_PRESENT, + paramName: 'challenge', + }, + async onHandshake(context) { + return { + status: 200, + body: { + smartsheetHookResponse: (context.payload.body as any)['challenge'], + }, + }; + }, + + async onEnable(context) { + const { sheet_id } = context.propsValue; + if (!sheet_id) throw new Error('Sheet ID is required to enable the webhook.'); + + const triggerIdentifier = context.webhookUrl.substring(context.webhookUrl.lastIndexOf('/') + 1); + const webhook = await findOrCreateWebhook( + context.auth as string, + context.webhookUrl, + sheet_id as string, + triggerIdentifier, + ); + + await context.store.put(TRIGGER_KEY, { + webhookId: webhook.id.toString(), + sharedSecret: webhook.sharedSecret, + webhookName: webhook.name, + }); + }, + + async onDisable(context) { + const webhookInfo = await context.store.get(TRIGGER_KEY); + + if (webhookInfo && webhookInfo.webhookId) { + try { + await unsubscribeWebhook(context.auth as string, webhookInfo.webhookId); + } catch (error: any) { + if (error.response?.status !== 404) { + console.error(`Error unsubscribing webhook ${webhookInfo.webhookId}: ${error.message}`); + } + } + await context.store.delete(TRIGGER_KEY); + } + }, + + async run(context) { + const payload = context.payload.body as any; + const headers = context.payload.headers as Record; + const webhookInfo = await context.store.get(TRIGGER_KEY); + + if (!webhookInfo) { + return []; + } + + if (headers && headers['smartsheet-hook-challenge']) { + return []; + } + + const webhookSecret = webhookInfo?.sharedSecret; + const webhookSignatureHeader = context.payload.headers['smartsheet-hmac-sha256']; + const rawBody = context.payload.rawBody; + + if (!verifyWebhookSignature(webhookSecret, webhookSignatureHeader, rawBody)) { + return []; + } + + if (payload.newWebhookStatus) { + return []; + } + if (!payload.events || !Array.isArray(payload.events) || payload.events.length === 0) { + return []; + } + + const newRowEvents = []; + for (const event of payload.events) { + if (event.objectType === 'row' && event.eventType === 'created') { + const eventOutput: any = { ...event, sheetId: payload.scopeObjectId?.toString() }; + const objectSheetId = payload.scopeObjectId?.toString(); + if (objectSheetId) { + try { + eventOutput.rowData = await getSheetRowDetails( + context.auth as string, + objectSheetId, + event.id.toString(), + ); + } catch (error: any) { + console.warn( + `Failed to fetch full details for new row ID ${event.id}: ${error.message}`, + ); + eventOutput.fetchError = error.message; + } + } else { + eventOutput.fetchError = 'scopeObjectId missing, cannot fetch row details.'; + } + + newRowEvents.push(eventOutput); + } + } + return newRowEvents; + }, +}); diff --git a/packages/pieces/community/smartsheet/src/lib/triggers/updated-row-trigger.ts b/packages/pieces/community/smartsheet/src/lib/triggers/updated-row-trigger.ts new file mode 100644 index 0000000..de6c550 --- /dev/null +++ b/packages/pieces/community/smartsheet/src/lib/triggers/updated-row-trigger.ts @@ -0,0 +1,134 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { smartsheetAuth } from '../../index'; +import { + smartsheetCommon, + findOrCreateWebhook, + WebhookInformation, + getSheetRowDetails, + verifyWebhookSignature, + unsubscribeWebhook, +} from '../common'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; + +const TRIGGER_KEY = 'smartsheet_updated_row_trigger'; + +export const updatedRowTrigger = createTrigger({ + auth: smartsheetAuth, + name: 'updated_row', + displayName: 'Row Updated', + description: 'Triggers when an existing row is updated.', + props: { + sheet_id: smartsheetCommon.sheet_id(), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: { + sheetId: '12345', + eventType: 'updated', + objectType: 'row', + id: 67890, + columnId: 333, + timestamp: '2023-10-28T12:05:00Z', + userId: 54321, + rowData: { + /* ... full row data ... */ + }, + }, + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.BODY_PARAM_PRESENT, + paramName: 'challenge', + }, + async onHandshake(context) { + return { + status: 200, + body: { + smartsheetHookResponse: (context.payload.body as any)['challenge'], + }, + }; + }, + async onEnable(context) { + const { sheet_id } = context.propsValue; + if (!sheet_id) throw new Error('Sheet ID is required.'); + + const triggerIdentifier = context.webhookUrl.substring(context.webhookUrl.lastIndexOf('/') + 1); + const webhook = await findOrCreateWebhook( + context.auth as string, + context.webhookUrl, + sheet_id as string, + triggerIdentifier, + ); + + await context.store.put(TRIGGER_KEY, { + webhookId: webhook.id.toString(), + sharedSecret: webhook.sharedSecret, + webhookName: webhook.name, + }); + }, + + async onDisable(context) { + const webhookInfo = await context.store.get(TRIGGER_KEY); + + if (webhookInfo && webhookInfo.webhookId) { + try { + await unsubscribeWebhook(context.auth as string, webhookInfo.webhookId); + } catch (error: any) { + if (error.response?.status !== 404) { + console.error(`Error unsubscribing webhook ${webhookInfo.webhookId}: ${error.message}`); + } + } + await context.store.delete(TRIGGER_KEY); + } + }, + + async run(context): Promise { + const payload = context.payload.body as any; + const headers = context.payload.headers as Record; + const webhookInfo = await context.store.get(TRIGGER_KEY); + + if (!webhookInfo) { + return []; + } + + if (headers && headers['smartsheet-hook-challenge']) { + return []; + } + + const webhookSecret = webhookInfo?.sharedSecret; + const webhookSignatureHeader = context.payload.headers['smartsheet-hmac-sha256']; + const rawBody = context.payload.rawBody; + + if (!verifyWebhookSignature(webhookSecret, webhookSignatureHeader, rawBody)) { + return []; + } + + if (payload.newWebhookStatus) { + return []; + } + if (!payload.events || !Array.isArray(payload.events) || payload.events.length === 0) { + return []; + } + + const updatedRowEvents = []; + for (const event of payload.events) { + if (event.objectType === 'row' && event.eventType === 'updated') { + const eventOutput: any = { ...event, sheetId: payload.scopeObjectId?.toString() }; + const objectSheetId = payload.scopeObjectId?.toString(); + if (objectSheetId) { + try { + eventOutput.rowData = await getSheetRowDetails( + context.auth as string, + objectSheetId, + event.id.toString(), + ); + } catch (error: any) { + eventOutput.fetchError = error.message; + } + } else { + eventOutput.fetchError = 'scopeObjectId missing'; + } + + updatedRowEvents.push(eventOutput); + } + } + return updatedRowEvents; + }, +}); diff --git a/packages/pieces/community/smartsheet/tsconfig.json b/packages/pieces/community/smartsheet/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/smartsheet/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/smartsheet/tsconfig.lib.json b/packages/pieces/community/smartsheet/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/smartsheet/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/smartsuite/.eslintrc.json b/packages/pieces/community/smartsuite/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/smartsuite/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/smartsuite/README.md b/packages/pieces/community/smartsuite/README.md new file mode 100644 index 0000000..19996ee --- /dev/null +++ b/packages/pieces/community/smartsuite/README.md @@ -0,0 +1,7 @@ +# pieces-smartsuite + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-smartsuite` to build the library. diff --git a/packages/pieces/community/smartsuite/package.json b/packages/pieces/community/smartsuite/package.json new file mode 100644 index 0000000..fbfc7db --- /dev/null +++ b/packages/pieces/community/smartsuite/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-smartsuite", + "version": "0.0.1" +} diff --git a/packages/pieces/community/smartsuite/project.json b/packages/pieces/community/smartsuite/project.json new file mode 100644 index 0000000..163ae93 --- /dev/null +++ b/packages/pieces/community/smartsuite/project.json @@ -0,0 +1,31 @@ +{ + "name": "pieces-smartsuite", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/smartsuite/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/packages/pieces/community/smartsuite", + "tsConfig": "packages/pieces/community/smartsuite/tsconfig.lib.json", + "packageJson": "packages/pieces/community/smartsuite/package.json", + "main": "packages/pieces/community/smartsuite/src/index.ts", + "assets": ["packages/pieces/community/smartsuite/*.md", + { + "input": "packages/pieces/community/smartsuite/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + }], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + } + }, + "tags": [] +} diff --git a/packages/pieces/community/smartsuite/src/index.ts b/packages/pieces/community/smartsuite/src/index.ts new file mode 100644 index 0000000..df33381 --- /dev/null +++ b/packages/pieces/community/smartsuite/src/index.ts @@ -0,0 +1,51 @@ +import { + createPiece, + PiecePropValueSchema, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { smartsuiteAuth } from './lib/auth'; + +// Actions +import { createRecord } from './lib/actions/create-record'; +import { updateRecord } from './lib/actions/update-record'; +import { deleteRecord } from './lib/actions/delete-record'; +import { uploadFile } from './lib/actions/upload-file'; +import { findRecords } from './lib/actions/find-records'; +import { getRecord } from './lib/actions/get-record'; + +// Triggers +import { newRecord } from './lib/triggers/new-record'; +import { updatedRecord } from './lib/triggers/updated-record'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { SMARTSUITE_API_URL } from './lib/common/constants'; + +export const smartsuite = createPiece({ + displayName: 'SmartSuite', + description: + 'Collaborative work management platform combining databases with spreadsheets.', + logoUrl: 'https://cdn.activepieces.com/pieces/smartsuite.png', + categories: [PieceCategory.PRODUCTIVITY], + auth: smartsuiteAuth, + minimumSupportedRelease: '0.30.0', + authors: ['Kunal-Darekar', 'kishanprmr'], + actions: [ + createRecord, + updateRecord, + deleteRecord, + uploadFile, + findRecords, + getRecord, + createCustomApiCallAction({ + auth: smartsuiteAuth, + baseUrl: () => SMARTSUITE_API_URL, + authMapping: async (auth) => { + const authValue = auth as PiecePropValueSchema; + return { + Authorization: `Token ${authValue.apiKey}`, + 'ACCOUNT-ID': authValue.accountId, + }; + }, + }), + ], + triggers: [newRecord, updatedRecord], +}); diff --git a/packages/pieces/community/smartsuite/src/lib/actions/create-record.ts b/packages/pieces/community/smartsuite/src/lib/actions/create-record.ts new file mode 100644 index 0000000..6dca9a4 --- /dev/null +++ b/packages/pieces/community/smartsuite/src/lib/actions/create-record.ts @@ -0,0 +1,64 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { smartsuiteAuth } from '../auth'; +import { smartsuiteCommon, formatRecordFields, transformRecordFields } from '../common/props'; +import { smartSuiteApiCall, TableStucture } from '../common'; + +export const createRecord = createAction({ + name: 'create_record', + displayName: 'Create a Record', + description: 'Creates a new record in the specified table.', + auth: smartsuiteAuth, + props: { + solutionId: smartsuiteCommon.solutionId, + tableId: smartsuiteCommon.tableId, + fields: smartsuiteCommon.tableFields, + }, + async run({ auth, propsValue }) { + const { tableId, fields, solutionId } = propsValue; + + const tableResponse = await smartSuiteApiCall<{ + structure: TableStucture[]; + }>({ + apiKey: auth.apiKey, + accountId: auth.accountId, + method: HttpMethod.GET, + resourceUri: `/applications/${tableId}`, + }); + const tableSchema = tableResponse.structure; + + const formattedFields = formatRecordFields(tableSchema, fields); + + try { + const response = await smartSuiteApiCall>({ + apiKey: auth.apiKey, + accountId: auth.accountId, + method: HttpMethod.POST, + resourceUri: `/applications/${tableId}/records/`, + body: formattedFields, + }); + + const transformedFields = transformRecordFields(tableSchema, response); + + return transformedFields; + } catch (error: any) { + if (error.response?.status === 422) { + throw new Error( + `Invalid request: ${ + error.response?.body?.message || 'Missing required fields or invalid data' + }`, + ); + } + + if (error.response?.status === 403) { + throw new Error('You do not have permission to create records in this table'); + } + + if (error.response?.status === 404) { + throw new Error(`Solution or table not found: ${solutionId}/${tableId}`); + } + + throw new Error(`Failed to create record: ${error.message || 'Unknown error'}`); + } + }, +}); diff --git a/packages/pieces/community/smartsuite/src/lib/actions/delete-record.ts b/packages/pieces/community/smartsuite/src/lib/actions/delete-record.ts new file mode 100644 index 0000000..5bb5b08 --- /dev/null +++ b/packages/pieces/community/smartsuite/src/lib/actions/delete-record.ts @@ -0,0 +1,41 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { HttpMethod} from '@activepieces/pieces-common'; +import { smartsuiteAuth } from '../auth'; +import { smartsuiteCommon } from '../common/props'; +import { smartSuiteApiCall } from '../common'; + +export const deleteRecord = createAction({ + name: 'delete_record', + displayName: 'Delete a Record', + description: 'Deletes a record from the specified table', + auth: smartsuiteAuth, + props: { + solutionId: smartsuiteCommon.solutionId, + tableId: smartsuiteCommon.tableId, + recordId: smartsuiteCommon.recordId, + }, + async run({ auth, propsValue }) { + const { tableId, recordId } = propsValue; + + try { + const response = await smartSuiteApiCall>({ + apiKey: auth.apiKey, + accountId: auth.accountId, + method: HttpMethod.DELETE, + resourceUri: `/applications/${tableId}/records/${recordId}/`, + }); + + return response; + } catch (error: any) { + if (error.response?.status === 404) { + throw new Error(`Record with ID ${recordId} not found in table ${tableId}`); + } + + if (error.response?.status === 403) { + throw new Error('You do not have permission to delete this record'); + } + + throw new Error(`Failed to delete record: ${error.message || 'Unknown error'}`); + } + }, +}); diff --git a/packages/pieces/community/smartsuite/src/lib/actions/find-records.ts b/packages/pieces/community/smartsuite/src/lib/actions/find-records.ts new file mode 100644 index 0000000..dc58c9a --- /dev/null +++ b/packages/pieces/community/smartsuite/src/lib/actions/find-records.ts @@ -0,0 +1,136 @@ +import { + DropdownOption, + PiecePropValueSchema, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { smartsuiteAuth } from '../auth'; +import { smartsuiteCommon, transformRecordFields } from '../common/props'; +import { smartSuiteApiCall, TableStucture } from '../common'; + +export const findRecords = createAction({ + name: 'find_records', + displayName: 'Find Records', + description: 'Searches for records in the specified table based on criteria.', + auth: smartsuiteAuth, + props: { + solutionId: smartsuiteCommon.solutionId, + tableId: smartsuiteCommon.tableId, + searchField: Property.Dropdown({ + displayName: 'Search Field', + required: true, + refreshers: ['tableId'], + options: async ({ auth, tableId }) => { + if (!auth || !tableId) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + const { apiKey, accountId } = auth as PiecePropValueSchema; + + const response = await smartSuiteApiCall<{ + structure: TableStucture[]; + }>({ + apiKey, + accountId, + method: HttpMethod.GET, + resourceUri: `/applications/${tableId}`, + }); + + const options: DropdownOption[] = []; + + for (const field of response.structure) { + if (field.params.is_auto_generated || field.params.system) { + continue; + } + + options.push({ label: field.label, value: field.slug }); + } + return { + disabled: false, + options, + }; + }, + }), + searchValue: Property.ShortText({ + displayName: 'Search Value', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { solutionId, tableId, searchField, searchValue } = propsValue; + + try { + const tableResponse = await smartSuiteApiCall<{ + structure: TableStucture[]; + }>({ + apiKey: auth.apiKey, + accountId: auth.accountId, + method: HttpMethod.GET, + resourceUri: `/applications/${tableId}`, + }); + const tableSchema = tableResponse.structure; + + const matchedRecords = []; + + const qs = { limit: 100, offset: 0 }; + let hasMore = true; + + do { + const response = await smartSuiteApiCall<{ items: Record[] }>({ + accountId: auth.accountId, + apiKey: auth.apiKey, + method: HttpMethod.POST, + resourceUri: `/applications/${tableId}/records/list/`, + query: qs, + body: { + filter: { + operator: 'and', + fields: [ + { + field: searchField, + comparison: 'is', + value: searchValue, + }, + ], + }, + }, + }); + const items = response.items || []; + matchedRecords.push(...items); + + hasMore = items.length > 0; + if (hasMore) { + qs.offset = Number(qs.offset) + Number(qs.limit); + } + } while (hasMore); + + return { + found: matchedRecords.length > 0, + result: matchedRecords.map((record) => transformRecordFields(tableSchema, record)), + }; + } catch (error: any) { + if (error.response?.status === 400) { + throw new Error( + `Invalid filter or sort criteria: ${ + error.response?.body?.message || 'Please check your filter JSON format' + }`, + ); + } + + if (error.response?.status === 403) { + throw new Error('You do not have permission to access this table'); + } + + if (error.response?.status === 404) { + throw new Error(`Solution or table not found: ${solutionId}/${tableId}`); + } + + throw new Error(`Failed to find records: ${error.message || 'Unknown error'}`); + } + }, +}); diff --git a/packages/pieces/community/smartsuite/src/lib/actions/get-record.ts b/packages/pieces/community/smartsuite/src/lib/actions/get-record.ts new file mode 100644 index 0000000..957fb0b --- /dev/null +++ b/packages/pieces/community/smartsuite/src/lib/actions/get-record.ts @@ -0,0 +1,52 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { smartsuiteAuth } from '../auth'; +import { smartsuiteCommon, transformRecordFields } from '../common/props'; +import { smartSuiteApiCall, TableStucture } from '../common'; + +export const getRecord = createAction({ + name: 'get_record', + displayName: 'Get a Record', + description: 'Retrieves a specific record by ID', + auth: smartsuiteAuth, + props: { + solutionId: smartsuiteCommon.solutionId, + tableId: smartsuiteCommon.tableId, + recordId: smartsuiteCommon.recordId, + }, + async run({ auth, propsValue }) { + const { tableId, recordId } = propsValue; + + try { + const tableResponse = await smartSuiteApiCall<{ + structure: TableStucture[]; + }>({ + apiKey: auth.apiKey, + accountId: auth.accountId, + method: HttpMethod.GET, + resourceUri: `/applications/${tableId}`, + }); + const tableSchema = tableResponse.structure; + const response = await smartSuiteApiCall>({ + apiKey: auth.apiKey, + accountId: auth.accountId, + method: HttpMethod.GET, + resourceUri: `/applications/${tableId}/records/${recordId}/`, + }); + + const transformedFields = transformRecordFields(tableSchema, response); + + return transformedFields; + } catch (error: any) { + if (error.response?.status === 403) { + throw new Error('You do not have permission to access this record'); + } + + if (error.response?.status === 404) { + throw new Error(`Record with ID ${recordId} not found in table ${tableId}`); + } + + throw new Error(`Failed to get record: ${error.message || 'Unknown error'}`); + } + }, +}); diff --git a/packages/pieces/community/smartsuite/src/lib/actions/update-record.ts b/packages/pieces/community/smartsuite/src/lib/actions/update-record.ts new file mode 100644 index 0000000..0f36fe0 --- /dev/null +++ b/packages/pieces/community/smartsuite/src/lib/actions/update-record.ts @@ -0,0 +1,63 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { smartsuiteAuth } from '../auth'; +import { smartsuiteCommon, formatRecordFields, transformRecordFields } from '../common/props'; +import { smartSuiteApiCall, TableStucture } from '../common'; + +export const updateRecord = createAction({ + name: 'update_record', + displayName: 'Update a Record', + description: 'Updates an existing record in the specified table', + auth: smartsuiteAuth, + props: { + solutionId: smartsuiteCommon.solutionId, + tableId: smartsuiteCommon.tableId, + recordId: smartsuiteCommon.recordId, + fields: smartsuiteCommon.tableFields, + }, + async run({ auth, propsValue }) { + const { tableId, recordId, fields } = propsValue; + + const tableResponse = await smartSuiteApiCall<{ + structure: TableStucture[]; + }>({ + apiKey: auth.apiKey, + accountId: auth.accountId, + method: HttpMethod.GET, + resourceUri: `/applications/${tableId}`, + }); + const tableSchema = tableResponse.structure; + + const formattedFields = formatRecordFields(tableSchema, fields); + + try { + const response = await smartSuiteApiCall>({ + apiKey: auth.apiKey, + accountId: auth.accountId, + method: HttpMethod.PATCH, + resourceUri: `/applications/${tableId}/records/${recordId}/`, + body: formattedFields, + }); + + const transformedFields = transformRecordFields(tableSchema, response); + + return transformedFields; + } catch (error: any) { + if (error.response?.status === 422) { + throw new Error( + `Invalid request: ${error.response?.body?.message || 'Invalid data format'}`, + ); + } + + if (error.response?.status === 403) { + throw new Error('You do not have permission to update this record'); + } + + if (error.response?.status === 404) { + throw new Error(`Record with ID ${recordId} not found in table ${tableId}`); + } + + throw new Error(`Failed to update record: ${error.message || 'Unknown error'}`); + } + }, +}); diff --git a/packages/pieces/community/smartsuite/src/lib/actions/upload-file.ts b/packages/pieces/community/smartsuite/src/lib/actions/upload-file.ts new file mode 100644 index 0000000..18e4d39 --- /dev/null +++ b/packages/pieces/community/smartsuite/src/lib/actions/upload-file.ts @@ -0,0 +1,122 @@ +import { + DropdownOption, + PiecePropValueSchema, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { smartsuiteAuth } from '../auth'; +import { smartsuiteCommon, transformRecordFields } from '../common/props'; +import { smartSuiteApiCall, TableStucture } from '../common'; +import FormData from 'form-data'; + +export const uploadFile = createAction({ + name: 'upload_file', + displayName: 'Upload File', + description: 'Uploads a file and attaches it to a record.', + auth: smartsuiteAuth, + props: { + solutionId: smartsuiteCommon.solutionId, + tableId: smartsuiteCommon.tableId, + recordId: smartsuiteCommon.recordId, + field: Property.Dropdown({ + displayName: 'Search Field', + required: true, + refreshers: ['tableId'], + options: async ({ auth, tableId }) => { + if (!auth || !tableId) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + + const { apiKey, accountId } = auth as PiecePropValueSchema; + + const response = await smartSuiteApiCall<{ + structure: TableStucture[]; + }>({ + apiKey, + accountId, + method: HttpMethod.GET, + resourceUri: `/applications/${tableId}`, + }); + + const options: DropdownOption[] = []; + + for (const field of response.structure) { + if (field.field_type === 'filefield') { + options.push({ label: field.label, value: field.slug }); + } + } + return { + disabled: false, + options, + }; + }, + }), + file: Property.File({ + displayName: 'File', + description: 'The file to upload', + required: true, + }), + }, + async run({ auth, propsValue }) { + const { recordId, field, tableId, file } = propsValue; + try { + const formData = new FormData(); + + formData.append('files', Buffer.from(file.base64, 'base64'), file.filename); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://app.smartsuite.com/api/v1/recordfiles/${tableId}/${recordId}/${field}/`, + body: formData, + headers: { + ...formData.getHeaders(), + Authorization: `Token ${auth.apiKey}`, + 'ACCOUNT-ID': auth.accountId, + }, + }); + + const tableResponse = await smartSuiteApiCall<{ + structure: TableStucture[]; + }>({ + apiKey: auth.apiKey, + accountId: auth.accountId, + method: HttpMethod.GET, + resourceUri: `/applications/${tableId}`, + }); + const tableSchema = tableResponse.structure; + + const formattedFields = transformRecordFields(tableSchema, response.body); + + return formattedFields; + } catch (error: any) { + if (error.response?.status === 400) { + throw new Error( + `Invalid file format or size: ${ + error.response?.body?.message || 'File may be too large or in an unsupported format' + }`, + ); + } + + if (error.response?.status === 403) { + throw new Error('You do not have permission to upload files to this record'); + } + + if (error.response?.status === 404) { + throw new Error( + `Record with ID ${recordId} not found or field ${field} is not a file field`, + ); + } + + if (error.response?.status === 413) { + throw new Error('File is too large. SmartSuite has file size limits'); + } + + throw new Error(`Failed to upload file: ${error.message || 'Unknown error'}`); + } + }, +}); diff --git a/packages/pieces/community/smartsuite/src/lib/auth.ts b/packages/pieces/community/smartsuite/src/lib/auth.ts new file mode 100644 index 0000000..0ba1c0a --- /dev/null +++ b/packages/pieces/community/smartsuite/src/lib/auth.ts @@ -0,0 +1,40 @@ +import { PieceAuth, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { smartSuiteApiCall } from './common'; + +export const smartsuiteAuth = PieceAuth.CustomAuth({ + description: ` + You can obtain API key by navigate to **My Profile->API Key** from top-right corner. + + You can obtain Account ID from browser URL.For example, if smartsuite workspace URL is https://app.smartsuite.com/xyz/home, your Account ID is **xyz**.`, + required: true, + props: { + apiKey: PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + }), + accountId: Property.ShortText({ + displayName: 'Account ID', + required: true, + }), + }, + validate: async ({ auth }) => { + try { + await smartSuiteApiCall({ + apiKey: auth.apiKey, + accountId: auth.accountId, + method: HttpMethod.GET, + resourceUri: '/solutions', + }); + + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid Credentials.', + }; + } + }, +}); diff --git a/packages/pieces/community/smartsuite/src/lib/common/constants.ts b/packages/pieces/community/smartsuite/src/lib/common/constants.ts new file mode 100644 index 0000000..69647d0 --- /dev/null +++ b/packages/pieces/community/smartsuite/src/lib/common/constants.ts @@ -0,0 +1,18 @@ +export const SMARTSUITE_API_URL = 'https://app.smartsuite.com/api/v1'; +export const SMARTSUITE_WEBHOOKS_API_URL = 'https://webhooks.smartsuite.com/smartsuite.webhooks.engine.Webhooks'; + +export const WEBHOOK_EVENTS = { + RECORD_CREATED: 'RECORD_CREATED', + RECORD_UPDATED: 'RECORD_UPDATED', + RECORD_DELETED: 'RECORD_DELETED', +}; + +export const API_ENDPOINTS = { + // Webhooks + CREATE_WEBHOOK: '/CreateWebhook', + LIST_WEBHOOKS: '/ListWebhooks', + GET_WEBHOOK: '/GetWebhook', + UPDATE_WEBHOOK: '/UpdateWebhook', + DELETE_WEBHOOK: '/DeleteWebhook', + LIST_EVENTS: '/ListEvents', +}; diff --git a/packages/pieces/community/smartsuite/src/lib/common/index.ts b/packages/pieces/community/smartsuite/src/lib/common/index.ts new file mode 100644 index 0000000..ef881df --- /dev/null +++ b/packages/pieces/community/smartsuite/src/lib/common/index.ts @@ -0,0 +1,104 @@ +import { + httpClient, + HttpMessageBody, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; +import { SMARTSUITE_API_URL } from './constants'; +import { isNil } from '@activepieces/shared'; + +export type SmartSuiteApiCallParams = { + apiKey: string; + accountId: string; + method: HttpMethod; + resourceUri: string; + query?: Record; + body?: any; +}; + +export type PaginatedResponse = { + results: T[]; + count: number; + next?: string; + previous?: string; +}; + +export type TableStucture = { + slug: string; + label: string; + field_type: string; + params: { + is_auto_generated: boolean; + system: boolean; + choices?: { label: string; value: string }[]; + }; +}; + +export async function smartSuiteApiCall({ + apiKey, + accountId, + method, + resourceUri, + query, + body, +}: SmartSuiteApiCallParams): Promise { + const qs: QueryParams = {}; + + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + qs[key] = String(value); + } + } + } + + const request: HttpRequest = { + method, + url: SMARTSUITE_API_URL + resourceUri, + headers: { + Authorization: `Token ${apiKey}`, + 'ACCOUNT-ID': accountId, + }, + queryParams: qs, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} + +export async function smartSuitePaginatedApiCall({ + apiKey, + accountId, + method, + resourceUri, + query, + body, +}: SmartSuiteApiCallParams): Promise { + const qs = { ...(query || {}), limit: 100, offset: 0 }; + + const resultData: T[] = []; + let hasMore = true; + + do { + const response = await smartSuiteApiCall>({ + accountId, + apiKey, + method, + resourceUri, + query: qs, + body, + }); + + const items = response.results || []; + resultData.push(...items); + + hasMore = !!response.next && items.length > 0; + if (hasMore) { + qs.offset = Number(qs.offset) + Number(qs.limit); + } + } while (hasMore); + + return resultData; +} diff --git a/packages/pieces/community/smartsuite/src/lib/common/props.ts b/packages/pieces/community/smartsuite/src/lib/common/props.ts new file mode 100644 index 0000000..cb82869 --- /dev/null +++ b/packages/pieces/community/smartsuite/src/lib/common/props.ts @@ -0,0 +1,363 @@ +import { Property, DynamicPropsValue, PiecePropValueSchema } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { smartSuiteApiCall, smartSuitePaginatedApiCall, TableStucture } from '.'; +import { smartsuiteAuth } from '../auth'; +import { isNil } from '@activepieces/shared'; + +export const smartsuiteCommon = { + solutionId: Property.Dropdown({ + displayName: 'Solution', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please enter your API key first.', + }; + } + + const { apiKey, accountId } = auth as PiecePropValueSchema; + + try { + const response = await smartSuitePaginatedApiCall<{ + name: string; + id: string; + hidden: boolean; + }>({ + apiKey, + accountId, + method: HttpMethod.GET, + resourceUri: '/solutions', + }); + + return { + disabled: false, + options: response + .filter((solution) => !solution.hidden) + .map((solution) => { + return { + label: solution.name, + value: solution.id, + }; + }), + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: 'Error fetching solutions. Please check your API key.', + }; + } + }, + }), + + tableId: Property.Dropdown({ + displayName: 'Table', + required: true, + refreshers: ['solutionId'], + options: async ({ auth, solutionId }) => { + if (!auth || !solutionId) { + return { + disabled: true, + options: [], + placeholder: solutionId + ? 'Please select a solution first.' + : 'Please enter your API key first.', + }; + } + + const { apiKey, accountId } = auth as PiecePropValueSchema; + + try { + const response = await smartSuitePaginatedApiCall<{ + id: string; + name: string; + }>({ + apiKey, + accountId, + method: HttpMethod.GET, + resourceUri: '/applications', + query: { + solution: solutionId as string, + }, + }); + + return { + disabled: false, + options: response.map((table) => { + return { + label: table.name, + value: table.id, + }; + }), + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: 'Error fetching tables. Please check your permissions.', + }; + } + }, + }), + tableFields: Property.DynamicProperties({ + displayName: 'Fields', + required: true, + refreshers: ['solutionId', 'tableId'], + props: async ({ auth, solutionId, tableId }) => { + if (!auth || !solutionId || !tableId) { + return {}; + } + const { apiKey, accountId } = auth as PiecePropValueSchema; + + try { + const response = await smartSuiteApiCall<{ + structure: TableStucture[]; + }>({ + apiKey, + accountId, + method: HttpMethod.GET, + resourceUri: `/applications/${tableId}`, + }); + + const fieldProperties: DynamicPropsValue = {}; + + for (const field of response.structure) { + if ( + field.params.is_auto_generated || + field.params.system || + [ + 'countfield', + 'autonumberfield', + 'rollupfield', + 'votefield', + 'filefield', + 'fullnamefield', + 'addressfield', + 'daterangefield', + 'duedatefield', + 'userfield', + 'checklistfield', + 'signaturefield', + 'subitemsfield', + 'buttonfield', + 'lookupfield', + 'phonefield', + ].includes(field.field_type) + ) { + continue; + } + + switch (field.field_type) { + case 'recordtitlefield': + case 'textfield': + case 'emailfield': + case 'linkfield': + case 'durationfield': + case 'numberfield': + case 'colorpickerfield': + case 'percentfield': + case 'currencyfield': + fieldProperties[field.slug] = Property.ShortText({ + displayName: field.label, + required: false, + }); + break; + case 'timefield': + fieldProperties[field.slug] = Property.ShortText({ + displayName: field.label, + description: 'Provide value in HH:mm:ss format.', + required: false, + }); + break; + case 'richtextareafield': + case 'textareafield': + fieldProperties[field.slug] = Property.LongText({ + displayName: field.label, + required: false, + }); + break; + case 'numbersliderfield': + case 'percentcompletefield': + case 'ratingfield': + fieldProperties[field.slug] = Property.Number({ + displayName: field.label, + required: false, + }); + break; + case 'yesnofield': + fieldProperties[field.slug] = Property.Checkbox({ + displayName: field.label, + required: false, + }); + break; + case 'datefield': + fieldProperties[field.slug] = Property.DateTime({ + displayName: field.label, + required: false, + }); + break; + case 'statusfield': + case 'singleselectfield': + fieldProperties[field.slug] = Property.StaticDropdown({ + displayName: field.label, + required: false, + options: { + disabled: false, + options: field.params.choices + ? field.params.choices.map((choice) => ({ + label: choice.label, + value: choice.value, + })) + : [], + }, + }); + break; + case 'linkedrecordfield': + fieldProperties[field.slug] = Property.Array({ + displayName: field.label, + required: false, + description: 'Provide Record IDs to link.', + }); + break; + case 'multipleselectfield': + fieldProperties[field.slug] = Property.StaticMultiSelectDropdown({ + displayName: field.label, + required: false, + options: { + disabled: false, + options: field.params.choices + ? field.params.choices.map((choice) => ({ + label: choice.label, + value: choice.value, + })) + : [], + }, + }); + break; + } + } + + return fieldProperties; + } catch (error) { + return {}; + } + }, + }), + + recordId: Property.ShortText({ + displayName: 'Record ID', + required: true, + }), +}; + +export function formatRecordFields( + tableSchema: TableStucture[], + tableValues: Record, +): Record { + const formattedFields: Record = {}; + + const fieldMap: Record = tableSchema.reduce((acc, field) => { + acc[field.slug] = field.field_type; + return acc; + }, {} as Record); + + for (const [key, value] of Object.entries(tableValues)) { + if (isNil(value) || value === '') continue; + + const fieldType = fieldMap[key]; + switch (fieldType) { + case 'recordtitlefield': + case 'textfield': + case 'durationfield': + case 'timefield': + case 'textareafield': + case 'numberfield': + case 'numbersliderfield': + case 'percentfield': + case 'currencyfield': + case 'percentcompletefield': + case 'ratingfield': + case 'yesnofield': + case 'singleselectfield': + formattedFields[key] = value; + break; + case 'emailfield': + case 'linkfield': + formattedFields[key] = [value]; + break; + case 'colorpickerfield': + formattedFields[key] = [ + { + value, + }, + ]; + break; + case 'datefield': + formattedFields[key] = { + date: value, + include_time: false, + }; + break; + case 'statusfield': + formattedFields[key] = { + value, + }; + break; + case 'richtextareafield': + formattedFields[key] = { + data: { + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { + textAlign: 'left', + size: 'medium', + }, + content: [ + { + type: 'text', + text: value, + }, + ], + }, + ], + }, + }; + break; + case 'linkedrecordfield': + case 'multipleselectfield': { + if (Array.isArray(value) && value.length > 0) { + formattedFields[key] = value; + } + break; + } + default: + break; + } + } + + return formattedFields; +} + +export function transformRecordFields( + tableSchema: TableStucture[], + tableValues: Record, +) { + const fieldMap: Record = tableSchema.reduce((acc, field) => { + acc[field.slug] = field.label; + return acc; + }, {} as Record); + + const transformedFields: Record = {}; + + for (const [slug, value] of Object.entries(tableValues)) { + const label = fieldMap[slug] ?? slug; + transformedFields[label] = value; + } + + return transformedFields; +} diff --git a/packages/pieces/community/smartsuite/src/lib/triggers/new-record.ts b/packages/pieces/community/smartsuite/src/lib/triggers/new-record.ts new file mode 100644 index 0000000..d963a6f --- /dev/null +++ b/packages/pieces/community/smartsuite/src/lib/triggers/new-record.ts @@ -0,0 +1,441 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { smartsuiteAuth } from '../auth'; +import { smartsuiteCommon, transformRecordFields } from '../common/props'; +import { SMARTSUITE_WEBHOOKS_API_URL, API_ENDPOINTS, WEBHOOK_EVENTS } from '../common/constants'; +import { smartSuiteApiCall, TableStucture } from '../common'; + +export const newRecord = createTrigger({ + name: 'new_record', + displayName: 'New Record', + description: 'Triggers when a new record is created in the specified table', + type: TriggerStrategy.WEBHOOK, + auth: smartsuiteAuth, + props: { + solutionId: smartsuiteCommon.solutionId, + tableId: smartsuiteCommon.tableId, + }, + + async onEnable(context) { + const { solutionId, tableId } = context.propsValue; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${SMARTSUITE_WEBHOOKS_API_URL}${API_ENDPOINTS.CREATE_WEBHOOK}`, + body: { + webhook: { + filter: { + applications: { + application_ids: [tableId], + }, + }, + kinds: [WEBHOOK_EVENTS.RECORD_CREATED], + locator: { + account_id: context.auth.accountId, // This will be filled by SmartSuite based on the API key + solution_id: solutionId, + }, + notification_status: { + enabled: { + url: context.webhookUrl, + }, + }, + }, + }, + headers: { + Authorization: `Token ${context.auth.apiKey}`, + 'ACCOUNT-ID': context.auth.accountId, + }, + }); + await context.store.put('new_record', response.body.webhook.webhook_id); + }, + + async onDisable(context) { + const webhookId = await context.store.get('new_record'); + + if (webhookId) { + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${SMARTSUITE_WEBHOOKS_API_URL}${API_ENDPOINTS.DELETE_WEBHOOK}`, + body: { + webhook_id: webhookId, + }, + headers: { + Authorization: `Token ${context.auth.apiKey}`, + 'ACCOUNT-ID': context.auth.accountId, + }, + }); + } + }, + + async test(context) { + const { tableId } = context.propsValue; + const response = await smartSuiteApiCall<{ items: Record[] }>({ + accountId: context.auth.accountId, + apiKey: context.auth.apiKey, + method: HttpMethod.POST, + resourceUri: `/applications/${tableId}/records/list/`, + query: { limit: '5', offset: '0' }, + }); + const items = response.items || []; + + const tableResponse = await smartSuiteApiCall<{ + structure: TableStucture[]; + }>({ + apiKey: context.auth.apiKey, + accountId: context.auth.accountId, + method: HttpMethod.GET, + resourceUri: `/applications/${context.propsValue.tableId}`, + }); + const tableSchema = tableResponse.structure; + + return items.map((item) => transformRecordFields(tableSchema, item)); + }, + + async run(context) { + const webhookPayload = context.payload.body as { + webhookId: string; + locator: { + accountId: string; + solutionId: string; + }; + }; + + let pageToken = (await context.store.get('pageToken')) ?? ''; + + if (!webhookPayload || !webhookPayload.webhookId || !webhookPayload.locator) { + return []; + } + + const events = []; + + let hasMore = true; + + do { + const response = await httpClient.sendRequest<{ + events: { record_event_data: { data: Record } }[]; + next_page_token: string; + }>({ + method: HttpMethod.POST, + url: `${SMARTSUITE_WEBHOOKS_API_URL}${API_ENDPOINTS.LIST_EVENTS}`, + body: { + webhook_id: webhookPayload.webhookId, + page_size: '50', + page_token: pageToken, + }, + headers: { + Authorization: `Token ${context.auth.apiKey}`, + 'ACCOUNT-ID': context.auth.accountId, + }, + }); + + pageToken = response.body.next_page_token; + + const items = response.body.events ?? []; + + events.push(...items); + + hasMore = items.length > 0; + } while (hasMore); + + await context.store.put('pageToken', pageToken); + + const tableResponse = await smartSuiteApiCall<{ + structure: TableStucture[]; + }>({ + apiKey: context.auth.apiKey, + accountId: context.auth.accountId, + method: HttpMethod.GET, + resourceUri: `/applications/${context.propsValue.tableId}`, + }); + const tableSchema = tableResponse.structure; + + return events.map((event) => transformRecordFields(tableSchema, event.record_event_data.data)); + }, + + sampleData: { + Title: 'First Record', + Description: { + data: { + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { + textAlign: 'left', + size: 'medium', + }, + content: [ + { + type: 'text', + text: 'xxzxzxzxzxz', + }, + ], + }, + { + type: 'paragraph', + attrs: { + textAlign: 'left', + size: 'medium', + }, + content: [ + { + type: 'text', + text: 'xzxzxz', + }, + ], + }, + { + type: 'paragraph', + attrs: { + textAlign: 'left', + size: 'medium', + }, + content: [ + { + type: 'text', + text: 'xzxz', + }, + ], + }, + ], + }, + html: '

xxzxzxzxzxz

xzxzxz

xzxz

', + preview: 'xxzxzxzxzxz\n xzxzxz\n xzxz', + yjsData: '', + }, + Status: { + value: 'backlog', + updated_on: '2025-05-25T14:52:17.110000Z', + }, + 'First Created': { + on: '2025-05-25T14:52:16.977000Z', + by: '682c72c82336bf787bb5c7a0', + }, + 'Last Updated': { + on: '2025-05-26T10:52:14.987027Z', + by: '682c72c82336bf787bb5c7a0', + }, + 'Followed by': [], + 'Open Comments': 0, + 'Auto Number': 1, + 'text-field': 'text', + 'Text Area': 'area', + Number: '12.0', + 'Number Slider': 33, + Percent: '12.0', + Currency: '12', + 'Yes / No': true, + 'Single Select': '3u0Cl', + 'Multiple Select': ['bZfFn', 'LvQIv'], + Date: { + date: '2025-05-30T00:00:00Z', + include_time: false, + }, + 'Full Name': { + title: '', + first_name: 'john', + middle_name: '', + last_name: 'doe', + sys_root: 'john doe', + }, + Email: ['johndoe@gmail.com'], + Phone: [ + { + phone_country: 'IN', + phone_number: '', + phone_extension: '', + phone_type: 2, + sys_root: '', + sys_title: '', + }, + ], + Address: { + location_address: '', + location_address2: '', + location_zip: '', + location_country: '', + location_state: '', + location_city: '', + location_longitude: '72.2', + location_latitude: '21.12', + sys_root: '', + }, + Link: ['https://github.com'], + 'Files and Images': [ + { + handle: 'CeRMEqhySiFboDMkiZlx', + metadata: { + container: 'smart-suite-media', + filename: 'spotify.png', + key: 'PAfWmgwRnOhw6KJygepi_spotify.png', + mimetype: 'image/png', + size: 23422, + }, + transform_options: {}, + created_on: '2025-05-26T10:47:12.586000Z', + updated_on: '2025-05-26T10:47:12.586000Z', + description: '', + video_conversion_status: '', + video_thumbnail_handle: '', + converted_video_handle: '', + file_type: 'image', + icon: 'image', + }, + { + handle: 'GUQyUrvYQrpswR97N8jT', + metadata: { + container: 'smart-suite-media', + filename: 'zagomail.png', + key: 'CSHRsjZRVS6HIFvJEfqQ_zagomail.png', + mimetype: 'image/png', + size: 96995, + }, + transform_options: {}, + created_on: '2025-05-26T10:49:41.891000Z', + updated_on: '2025-05-26T10:49:41.891000Z', + description: '', + video_conversion_status: '', + video_thumbnail_handle: '', + converted_video_handle: '', + file_type: 'image', + icon: 'image', + }, + { + handle: 'eUhDadE9Q3yFz3XLCEgC', + metadata: { + container: 'smart-suite-media', + filename: 'coda.png', + key: 'BrE3gQheS108HgFd1HYT_coda.png', + mimetype: 'image/png', + size: 880, + }, + transform_options: {}, + created_on: '2025-05-26T10:52:14.981371Z', + updated_on: '2025-05-26T10:52:14.981377Z', + description: '', + video_conversion_status: '', + video_thumbnail_handle: '', + converted_video_handle: '', + file_type: 'image', + icon: 'image', + }, + ], + SmartDoc: { + data: {}, + html: '', + preview: '', + yjsData: 'AAA=', + }, + 'Link to Tasks': ['682c72c84386b2737cab1bd0', '682c72c84386b2737cab1bcf'], + Time: '00:15:00', + 'Date Range': { + from_date: { + date: '2025-05-25T00:00:00Z', + include_time: false, + }, + to_date: { + date: '2025-05-29T00:00:00Z', + include_time: false, + }, + }, + 'Percent Complete': 43, + 'Status 1': { + value: 'backlog', + updated_on: '2025-05-25T14:52:17.111000Z', + }, + 'Due Date': { + from_date: { + date: null, + include_time: false, + }, + to_date: { + date: '2025-05-30T00:00:00Z', + include_time: false, + }, + status_is_completed: false, + status_updated_on: '2025-05-25T14:52:17.110000Z', + }, + 'Assigned To': ['682c72c82336bf787bb5c7a0'], + Duration: '60.0', + 'Time Tracking Log': { + time_track_logs: [], + total_duration: 0, + }, + Checklist: { + items: [ + { + id: 'f93c6845-5823-4d73-945c-f10038125ae1', + content: { + data: { + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { + textAlign: 'left', + size: 'medium', + }, + content: [ + { + type: 'text', + text: 'ss', + }, + ], + }, + ], + }, + html: '

ss

', + preview: 'ss', + }, + completed: false, + assignee: null, + due_date: null, + completed_at: null, + }, + ], + total_items: 1, + completed_items: 0, + }, + Rating: 5, + Vote: { + total_votes: 0, + votes: [], + }, + Tag: ['68333302894412d3ffa5f55a'], + 'Record ID': '68332ea07e87b585dca5f3da', + Signature: { + text: '', + image_base64: '', + }, + Count: '2', + 'Sub-Items': { + count: 0, + items: [], + }, + Button: null, + 'Color Picker': [ + { + value: '#715E5E', + }, + ], + 'IP Address': [ + { + address: '192.121.0.0', + country_code: 'gb', + }, + ], + Rollup: '9.0', + Lookup: [['Phase Gate'], ['Market Research and Design Conceptualization']], + id: '68332ea07e87b585dca5f3da', + application_slug: 'sc4keiie', + application_id: '682c745daf3f33a521fc8c8c', + ranking: { + default: 'aaaaaaaouq', + }, + deleted_date: { + date: null, + include_time: false, + }, + deleted_by: null, + }, +}); diff --git a/packages/pieces/community/smartsuite/src/lib/triggers/updated-record.ts b/packages/pieces/community/smartsuite/src/lib/triggers/updated-record.ts new file mode 100644 index 0000000..9dafc0d --- /dev/null +++ b/packages/pieces/community/smartsuite/src/lib/triggers/updated-record.ts @@ -0,0 +1,445 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { smartsuiteAuth } from '../auth'; +import { smartsuiteCommon, transformRecordFields } from '../common/props'; +import { + SMARTSUITE_WEBHOOKS_API_URL, + API_ENDPOINTS, + WEBHOOK_EVENTS, +} from '../common/constants'; +import { smartSuiteApiCall, TableStucture } from '../common'; + +export const updatedRecord = createTrigger({ + name: 'updated_record', + displayName: 'Updated Record', + description: 'Triggers when a record is updated in the specified table.', + type: TriggerStrategy.WEBHOOK, + auth: smartsuiteAuth, + props: { + solutionId: smartsuiteCommon.solutionId, + tableId: smartsuiteCommon.tableId, + }, + + async onEnable(context) { + const { solutionId, tableId } = context.propsValue; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${SMARTSUITE_WEBHOOKS_API_URL}${API_ENDPOINTS.CREATE_WEBHOOK}`, + body: { + webhook: { + filter: { + applications: { + application_ids: [tableId], + }, + }, + kinds: [WEBHOOK_EVENTS.RECORD_UPDATED], + locator: { + account_id: context.auth.accountId, // This will be filled by SmartSuite based on the API key + solution_id: solutionId, + }, + notification_status: { + enabled: { + url: context.webhookUrl, + }, + }, + }, + }, + headers: { + Authorization: `Token ${context.auth.apiKey}`, + 'ACCOUNT-ID': context.auth.accountId, + }, + }); + await context.store.put('updated_record', response.body.webhook.webhook_id); + }, + + async onDisable(context) { + + const webhookId = await context.store.get('updated_record'); + + if (webhookId) { + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${SMARTSUITE_WEBHOOKS_API_URL}${API_ENDPOINTS.DELETE_WEBHOOK}`, + body: { + webhook_id: webhookId, + }, + headers: { + Authorization: `Token ${context.auth.apiKey}`, + 'ACCOUNT-ID': context.auth.accountId, + }, + }); + } + }, + async test(context) { + const { tableId } = context.propsValue; + const response = await smartSuiteApiCall<{ items: Record[] }>({ + accountId: context.auth.accountId, + apiKey: context.auth.apiKey, + method: HttpMethod.POST, + resourceUri: `/applications/${tableId}/records/list/`, + query: { limit: '5', offset: '0' }, + }); + const items = response.items || []; + + const tableResponse = await smartSuiteApiCall<{ + structure: TableStucture[]; + }>({ + apiKey: context.auth.apiKey, + accountId: context.auth.accountId, + method: HttpMethod.GET, + resourceUri: `/applications/${context.propsValue.tableId}`, + }); + const tableSchema = tableResponse.structure; + + return items.map((item) => transformRecordFields(tableSchema, item)); + }, + + async run(context) { + const webhookPayload = context.payload.body as { + webhookId: string; + locator: { + accountId: string; + solutionId: string; + }; + }; + + let pageToken = (await context.store.get('pageToken')) ?? ''; + + if (!webhookPayload || !webhookPayload.webhookId || !webhookPayload.locator) { + return []; + } + + const events = []; + + let hasMore = true; + + do { + const response = await httpClient.sendRequest<{ + events: { record_event_data: { data: Record } }[]; + next_page_token: string; + }>({ + method: HttpMethod.POST, + url: `${SMARTSUITE_WEBHOOKS_API_URL}${API_ENDPOINTS.LIST_EVENTS}`, + body: { + webhook_id: webhookPayload.webhookId, + page_size: '50', + page_token: pageToken, + }, + headers: { + Authorization: `Token ${context.auth.apiKey}`, + 'ACCOUNT-ID': context.auth.accountId, + }, + }); + + pageToken = response.body.next_page_token; + + const items = response.body.events ?? []; + + events.push(...items); + + hasMore = items.length > 0; + } while (hasMore); + + await context.store.put('pageToken', pageToken); + + const tableResponse = await smartSuiteApiCall<{ + structure: TableStucture[]; + }>({ + apiKey: context.auth.apiKey, + accountId: context.auth.accountId, + method: HttpMethod.GET, + resourceUri: `/applications/${context.propsValue.tableId}`, + }); + const tableSchema = tableResponse.structure; + + return events.map((event) => transformRecordFields(tableSchema, event.record_event_data.data)); + }, + + sampleData: { + Title: 'First Record', + Description: { + data: { + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { + textAlign: 'left', + size: 'medium', + }, + content: [ + { + type: 'text', + text: 'xxzxzxzxzxz', + }, + ], + }, + { + type: 'paragraph', + attrs: { + textAlign: 'left', + size: 'medium', + }, + content: [ + { + type: 'text', + text: 'xzxzxz', + }, + ], + }, + { + type: 'paragraph', + attrs: { + textAlign: 'left', + size: 'medium', + }, + content: [ + { + type: 'text', + text: 'xzxz', + }, + ], + }, + ], + }, + html: '

xxzxzxzxzxz

xzxzxz

xzxz

', + preview: 'xxzxzxzxzxz\n xzxzxz\n xzxz', + yjsData: '', + }, + Status: { + value: 'backlog', + updated_on: '2025-05-25T14:52:17.110000Z', + }, + 'First Created': { + on: '2025-05-25T14:52:16.977000Z', + by: '682c72c82336bf787bb5c7a0', + }, + 'Last Updated': { + on: '2025-05-26T10:52:14.987027Z', + by: '682c72c82336bf787bb5c7a0', + }, + 'Followed by': [], + 'Open Comments': 0, + 'Auto Number': 1, + 'text-field': 'text', + 'Text Area': 'area', + Number: '12.0', + 'Number Slider': 33, + Percent: '12.0', + Currency: '12', + 'Yes / No': true, + 'Single Select': '3u0Cl', + 'Multiple Select': ['bZfFn', 'LvQIv'], + Date: { + date: '2025-05-30T00:00:00Z', + include_time: false, + }, + 'Full Name': { + title: '', + first_name: 'john', + middle_name: '', + last_name: 'doe', + sys_root: 'john doe', + }, + Email: ['johndoe@gmail.com'], + Phone: [ + { + phone_country: 'IN', + phone_number: '', + phone_extension: '', + phone_type: 2, + sys_root: '', + sys_title: '', + }, + ], + Address: { + location_address: '', + location_address2: '', + location_zip: '', + location_country: '', + location_state: '', + location_city: '', + location_longitude: '72.2', + location_latitude: '21.12', + sys_root: '', + }, + Link: ['https://github.com'], + 'Files and Images': [ + { + handle: 'CeRMEqhySiFboDMkiZlx', + metadata: { + container: 'smart-suite-media', + filename: 'spotify.png', + key: 'PAfWmgwRnOhw6KJygepi_spotify.png', + mimetype: 'image/png', + size: 23422, + }, + transform_options: {}, + created_on: '2025-05-26T10:47:12.586000Z', + updated_on: '2025-05-26T10:47:12.586000Z', + description: '', + video_conversion_status: '', + video_thumbnail_handle: '', + converted_video_handle: '', + file_type: 'image', + icon: 'image', + }, + { + handle: 'GUQyUrvYQrpswR97N8jT', + metadata: { + container: 'smart-suite-media', + filename: 'zagomail.png', + key: 'CSHRsjZRVS6HIFvJEfqQ_zagomail.png', + mimetype: 'image/png', + size: 96995, + }, + transform_options: {}, + created_on: '2025-05-26T10:49:41.891000Z', + updated_on: '2025-05-26T10:49:41.891000Z', + description: '', + video_conversion_status: '', + video_thumbnail_handle: '', + converted_video_handle: '', + file_type: 'image', + icon: 'image', + }, + { + handle: 'eUhDadE9Q3yFz3XLCEgC', + metadata: { + container: 'smart-suite-media', + filename: 'coda.png', + key: 'BrE3gQheS108HgFd1HYT_coda.png', + mimetype: 'image/png', + size: 880, + }, + transform_options: {}, + created_on: '2025-05-26T10:52:14.981371Z', + updated_on: '2025-05-26T10:52:14.981377Z', + description: '', + video_conversion_status: '', + video_thumbnail_handle: '', + converted_video_handle: '', + file_type: 'image', + icon: 'image', + }, + ], + SmartDoc: { + data: {}, + html: '', + preview: '', + yjsData: 'AAA=', + }, + 'Link to Tasks': ['682c72c84386b2737cab1bd0', '682c72c84386b2737cab1bcf'], + Time: '00:15:00', + 'Date Range': { + from_date: { + date: '2025-05-25T00:00:00Z', + include_time: false, + }, + to_date: { + date: '2025-05-29T00:00:00Z', + include_time: false, + }, + }, + 'Percent Complete': 43, + 'Status 1': { + value: 'backlog', + updated_on: '2025-05-25T14:52:17.111000Z', + }, + 'Due Date': { + from_date: { + date: null, + include_time: false, + }, + to_date: { + date: '2025-05-30T00:00:00Z', + include_time: false, + }, + status_is_completed: false, + status_updated_on: '2025-05-25T14:52:17.110000Z', + }, + 'Assigned To': ['682c72c82336bf787bb5c7a0'], + Duration: '60.0', + 'Time Tracking Log': { + time_track_logs: [], + total_duration: 0, + }, + Checklist: { + items: [ + { + id: 'f93c6845-5823-4d73-945c-f10038125ae1', + content: { + data: { + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { + textAlign: 'left', + size: 'medium', + }, + content: [ + { + type: 'text', + text: 'ss', + }, + ], + }, + ], + }, + html: '

ss

', + preview: 'ss', + }, + completed: false, + assignee: null, + due_date: null, + completed_at: null, + }, + ], + total_items: 1, + completed_items: 0, + }, + Rating: 5, + Vote: { + total_votes: 0, + votes: [], + }, + Tag: ['68333302894412d3ffa5f55a'], + 'Record ID': '68332ea07e87b585dca5f3da', + Signature: { + text: '', + image_base64: '', + }, + Count: '2', + 'Sub-Items': { + count: 0, + items: [], + }, + Button: null, + 'Color Picker': [ + { + value: '#715E5E', + }, + ], + 'IP Address': [ + { + address: '192.121.0.0', + country_code: 'gb', + }, + ], + Rollup: '9.0', + Lookup: [['Phase Gate'], ['Market Research and Design Conceptualization']], + id: '68332ea07e87b585dca5f3da', + application_slug: 'sc4keiie', + application_id: '682c745daf3f33a521fc8c8c', + ranking: { + default: 'aaaaaaaouq', + }, + deleted_date: { + date: null, + include_time: false, + }, + deleted_by: null, + }, +}); diff --git a/packages/pieces/community/smartsuite/tsconfig.json b/packages/pieces/community/smartsuite/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/smartsuite/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/smartsuite/tsconfig.lib.json b/packages/pieces/community/smartsuite/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/smartsuite/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/smtp/.eslintrc.json b/packages/pieces/community/smtp/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/smtp/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/smtp/README.md b/packages/pieces/community/smtp/README.md new file mode 100644 index 0000000..61f38c5 --- /dev/null +++ b/packages/pieces/community/smtp/README.md @@ -0,0 +1,7 @@ +# pieces-smtp + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-smtp` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/smtp/package-lock.json b/packages/pieces/community/smtp/package-lock.json new file mode 100644 index 0000000..c3aedb8 --- /dev/null +++ b/packages/pieces/community/smtp/package-lock.json @@ -0,0 +1,30 @@ +{ + "name": "@activepieces/piece-smtp", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-smtp", + "version": "0.0.1", + "devDependencies": { + "@types/nodemailer": "^6.4.7" + } + }, + "node_modules/@types/node": { + "version": "18.15.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.9.tgz", + "integrity": "sha512-dUxhiNzBLr6IqlZXz6e/rN2YQXlFgOei/Dxy+e3cyXTJ4txSUbGT2/fmnD6zd/75jDMeW5bDee+YXxlFKHoV0A==", + "dev": true + }, + "node_modules/@types/nodemailer": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.7.tgz", + "integrity": "sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + } + } +} diff --git a/packages/pieces/community/smtp/package.json b/packages/pieces/community/smtp/package.json new file mode 100644 index 0000000..459c15d --- /dev/null +++ b/packages/pieces/community/smtp/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-smtp", + "version": "0.3.2" +} diff --git a/packages/pieces/community/smtp/project.json b/packages/pieces/community/smtp/project.json new file mode 100644 index 0000000..36c1735 --- /dev/null +++ b/packages/pieces/community/smtp/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-smtp", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/smtp/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/smtp", + "tsConfig": "packages/pieces/community/smtp/tsconfig.lib.json", + "packageJson": "packages/pieces/community/smtp/package.json", + "main": "packages/pieces/community/smtp/src/index.ts", + "assets": [ + "packages/pieces/community/smtp/*.md", + { + "input": "packages/pieces/community/smtp/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/smtp/src/index.ts b/packages/pieces/community/smtp/src/index.ts new file mode 100644 index 0000000..7641552 --- /dev/null +++ b/packages/pieces/community/smtp/src/index.ts @@ -0,0 +1,101 @@ +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { sendEmail } from './lib/actions/send-email'; +import { smtpCommon } from './lib/common'; + +const SMTPPorts = [25, 465, 587, 2525]; + +export const smtpAuth = PieceAuth.CustomAuth({ + required: true, + props: { + host: Property.ShortText({ + displayName: 'Host', + required: true, + }), + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + password: PieceAuth.SecretText({ + displayName: 'Password', + required: true, + }), + port: Property.StaticDropdown({ + displayName: 'Port', + required: true, + options: { + disabled: false, + options: SMTPPorts.map((port) => { + return { + label: port.toString(), + value: port, + }; + }), + }, + }), + TLS: Property.Checkbox({ + displayName: 'Require TLS?', + defaultValue: false, + required: true, + }), + }, + validate: async ({ auth }) => { + try { + const transporter = smtpCommon.createSMTPTransport(auth); + return new Promise((resolve, reject) => { + transporter.verify(function (error, success) { + if (error) { + resolve({ valid: false, error: JSON.stringify(error) }); + } else { + resolve({ valid: true }); + } + }); + }); + } catch (e) { + const castedError = (e as Record) + const code = castedError?.['code']; + switch (code) { + case 'EDNS': + return { + valid: false, + error: 'SMTP server not found or unreachable with error code: EDNS', + }; + case 'CONN': + return { + valid: false, + error: 'SMTP server connection failed with error code: CONN', + }; + default: + break; + } + return { + valid: false, + error: JSON.stringify(e), + }; + } + }, +}); + +export const smtp = createPiece({ + displayName: 'SMTP', + description: 'Send emails using Simple Mail Transfer Protocol', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/smtp.png', + categories: [PieceCategory.CORE], + authors: [ + 'tahboubali', + 'abaza738', + 'kishanprmr', + 'MoShizzle', + 'khaledmashaly', + 'abuaboud', + 'pfernandez98' + ], + auth: smtpAuth, + actions: [sendEmail], + triggers: [], +}); diff --git a/packages/pieces/community/smtp/src/lib/actions/send-email.ts b/packages/pieces/community/smtp/src/lib/actions/send-email.ts new file mode 100644 index 0000000..9f83e73 --- /dev/null +++ b/packages/pieces/community/smtp/src/lib/actions/send-email.ts @@ -0,0 +1,142 @@ +import { ApFile, Property, createAction } from '@activepieces/pieces-framework'; +import { smtpAuth } from '../..'; +import { smtpCommon } from '../common'; +import { Attachment, Headers } from 'nodemailer/lib/mailer'; +import mime from 'mime-types'; + +export const sendEmail = createAction({ + auth: smtpAuth, + name: 'send-email', + displayName: 'Send Email', + description: 'Send an email using a custom SMTP server.', + props: { + from: Property.ShortText({ + displayName: 'From Email', + required: true, + }), + senderName: Property.ShortText({ + displayName: "Sender Name", + required: false, + }), + to: Property.Array({ + displayName: 'To', + required: true, + }), + cc: Property.Array({ + displayName: 'CC', + required: false, + }), + replyTo: Property.ShortText({ + displayName: 'Reply To', + required: false, + }), + bcc: Property.Array({ + displayName: 'BCC', + required: false, + }), + subject: Property.ShortText({ + displayName: 'Subject', + required: true, + }), + body_type: Property.StaticDropdown({ + displayName: 'Body Type', + required: true, + defaultValue: 'plain_text', + options: { + disabled: false, + options: [ + { + label: 'plain text', + value: 'plain_text', + }, + { + label: 'html', + value: 'html', + }, + ], + }, + }), + body: Property.LongText({ + displayName: 'Body', + required: true, + }), + customHeaders: Property.Object({ + displayName: 'Custom Headers', + required: false, + }), + attachments: Property.Array({ + displayName: 'Attachments', + required: false, + properties: { + file: Property.File({ + displayName: 'File', + description: 'File to attach to the email you want to send', + required: true, + }), + name: Property.ShortText({ + displayName: 'Attachment Name', + description: 'In case you want to change the name of the attachment', + required: false, + }), + } + }), + }, + run: async ({ auth, propsValue }) => { + const transporter = smtpCommon.createSMTPTransport(auth); + + const attachments = propsValue['attachments'] as {file: ApFile; name: string | undefined; }[]; + + const attachment_data: Attachment[] = attachments.map(({file, name}) => { + const lookupResult = mime.lookup( + file.extension ? file.extension : '' + ); + return { + filename: name ?? file.filename, + content: file?.base64, + contentType: lookupResult ? lookupResult : undefined, + encoding: 'base64', + }; + }); + + const mailOptions = { + from: getFrom(propsValue.senderName, propsValue.from), + to: propsValue.to.join(','), + cc: propsValue.cc?.join(','), + inReplyTo: propsValue.replyTo, + bcc: propsValue.bcc?.join(','), + subject: propsValue.subject, + text: propsValue.body_type === 'plain_text' ? propsValue.body : undefined, + html: propsValue.body_type === 'html' ? propsValue.body : undefined, + attachments: attachment_data ? attachment_data : undefined, + headers: propsValue.customHeaders as Headers, + }; + + return await sendWithRetry(transporter, mailOptions); + }, +}); + +async function sendWithRetry(transporter: any, mailOptions: any) { + const maxRetries = 3; + let retryCount = 0; + + while (retryCount < maxRetries) { + try { + const info = await transporter.sendMail(mailOptions); + return info; + } catch (error: any) { + if ('code' in error && error.code === 'ECONNRESET' && retryCount < maxRetries - 1) { + retryCount++; + await new Promise(resolve => setTimeout(resolve, 3000)); + continue; + } + throw error; + } + } +} + +function getFrom(senderName: string|undefined, from: string) { + if (senderName) { + return `"${senderName}" <${from}>` + } + return from; +} diff --git a/packages/pieces/community/smtp/src/lib/common/index.ts b/packages/pieces/community/smtp/src/lib/common/index.ts new file mode 100644 index 0000000..e76a299 --- /dev/null +++ b/packages/pieces/community/smtp/src/lib/common/index.ts @@ -0,0 +1,30 @@ +import nodemailer from 'nodemailer'; + +export const smtpCommon = { + constructConfig(auth: smtpAuthParams) { + return { + host: auth.host, + port: auth.port, + requireTLS: auth.TLS, + auth: { + user: auth.email, + pass: auth.password, + }, + connectionTimeout: 60000, + secure: auth.port === 465, + }; + }, + createSMTPTransport(auth: smtpAuthParams) { + const smtpOptions = smtpCommon.constructConfig(auth); + const transporter = nodemailer.createTransport(smtpOptions); + return transporter; + }, +}; + +export type smtpAuthParams = { + host: string; + email: string; + password: string; + port: number; + TLS: boolean; +}; diff --git a/packages/pieces/community/smtp/tsconfig.json b/packages/pieces/community/smtp/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/smtp/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/smtp/tsconfig.lib.json b/packages/pieces/community/smtp/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/smtp/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/snowflake/.eslintrc.json b/packages/pieces/community/snowflake/.eslintrc.json new file mode 100644 index 0000000..3ce5c8f --- /dev/null +++ b/packages/pieces/community/snowflake/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {}, + "extends": ["plugin:prettier/recommended"], + "plugins": ["prettier"] + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/snowflake/README.md b/packages/pieces/community/snowflake/README.md new file mode 100644 index 0000000..5bbd5c0 --- /dev/null +++ b/packages/pieces/community/snowflake/README.md @@ -0,0 +1 @@ +# pieces-snowflake diff --git a/packages/pieces/community/snowflake/package.json b/packages/pieces/community/snowflake/package.json new file mode 100644 index 0000000..7c5617b --- /dev/null +++ b/packages/pieces/community/snowflake/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-snowflake", + "version": "0.0.10" +} diff --git a/packages/pieces/community/snowflake/project.json b/packages/pieces/community/snowflake/project.json new file mode 100644 index 0000000..c1e4120 --- /dev/null +++ b/packages/pieces/community/snowflake/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-snowflake", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/snowflake/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/snowflake", + "tsConfig": "packages/pieces/community/snowflake/tsconfig.lib.json", + "packageJson": "packages/pieces/community/snowflake/package.json", + "main": "packages/pieces/community/snowflake/src/index.ts", + "assets": [ + "packages/pieces/community/snowflake/*.md", + { + "input": "packages/pieces/community/snowflake/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-snowflake {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/snowflake/src/index.ts b/packages/pieces/community/snowflake/src/index.ts new file mode 100644 index 0000000..64b0236 --- /dev/null +++ b/packages/pieces/community/snowflake/src/index.ts @@ -0,0 +1,60 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { runMultipleQueries } from './lib/actions/run-multiple-queries'; +import { runQuery } from './lib/actions/run-query'; +import { PieceCategory } from '@activepieces/shared'; +import { insertRowAction } from './lib/actions/insert-row'; + +export const snowflakeAuth = PieceAuth.CustomAuth({ + props: { + account: Property.ShortText({ + displayName: 'Account', + required: true, + description: 'A string indicating the Snowflake account identifier.', + }), + username: Property.ShortText({ + displayName: 'Username', + required: true, + description: 'The login name for your Snowflake user.', + }), + password: PieceAuth.SecretText({ + displayName: 'Password', + description: 'Password for the user.', + required: true, + }), + database: Property.ShortText({ + displayName: 'Database', + description: + 'The default database to use for the session after connecting.', + required: false, + }), + role: Property.ShortText({ + displayName: 'Role', + description: + 'The default security role to use for the session after connecting.', + required: false, + }), + warehouse: Property.ShortText({ + displayName: 'Warehouse', + description: + 'The default virtual warehouse to use for the session after connecting. Used for performing queries, loading data, etc.', + required: false, + }), + }, + required: true, +}); +export const snowflake = createPiece({ + displayName: 'Snowflake', + description: 'Data warehouse built for the cloud', + + auth: snowflakeAuth, + minimumSupportedRelease: '0.27.1', + logoUrl: 'https://cdn.activepieces.com/pieces/snowflake.png', + categories: [PieceCategory.DEVELOPER_TOOLS], + authors: ['AdamSelene', 'abuaboud', 'valentin-mourtialon'], + actions: [runQuery, runMultipleQueries, insertRowAction], + triggers: [], +}); diff --git a/packages/pieces/community/snowflake/src/lib/actions/insert-row.ts b/packages/pieces/community/snowflake/src/lib/actions/insert-row.ts new file mode 100644 index 0000000..8ea0494 --- /dev/null +++ b/packages/pieces/community/snowflake/src/lib/actions/insert-row.ts @@ -0,0 +1,44 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { snowflakeAuth } from '../../'; +import { + configureConnection, + connect, + destroy, + execute, + snowflakeCommonProps, +} from '../common'; + +export const insertRowAction = createAction({ + name: 'insert-row', + displayName: 'Insert Row', + description: 'Insert a row into a table.', + auth: snowflakeAuth, + props: { + database: snowflakeCommonProps.database, + schema: snowflakeCommonProps.schema, + table: snowflakeCommonProps.table, + table_column_values: snowflakeCommonProps.table_column_values, + }, + async run(context) { + const tableName = context.propsValue.table; + const tableColumnValues = context.propsValue.table_column_values; + + const columns = Object.keys(tableColumnValues).join(','); + const valuePlaceholders = Object.keys(tableColumnValues) + .map(() => '?') + .join(', '); + const statement = `INSERT INTO ${tableName}(${columns}) VALUES(${valuePlaceholders})`; + + const connection = configureConnection(context.auth); + await connect(connection); + + const response = await execute( + connection, + statement, + Object.values(tableColumnValues) + ); + await destroy(connection); + + return response; + }, +}); diff --git a/packages/pieces/community/snowflake/src/lib/actions/run-multiple-queries.ts b/packages/pieces/community/snowflake/src/lib/actions/run-multiple-queries.ts new file mode 100644 index 0000000..bb6f730 --- /dev/null +++ b/packages/pieces/community/snowflake/src/lib/actions/run-multiple-queries.ts @@ -0,0 +1,166 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import snowflake, { Statement, SnowflakeError } from 'snowflake-sdk'; +import { snowflakeAuth } from '../../index'; + +type QueryResult = unknown[] | undefined; +type QueryResults = { query: string; result: QueryResult }[]; + +const DEFAULT_APPLICATION_NAME = 'ActivePieces'; +const DEFAULT_QUERY_TIMEOUT = 30000; + +export const runMultipleQueries = createAction({ + name: 'runMultipleQueries', + displayName: 'Run Multiple Queries', + description: 'Run Multiple Queries', + auth: snowflakeAuth, + props: { + sqlTexts: Property.Array({ + displayName: 'SQL queries', + description: + 'Array of SQL queries to execute in order, in the same transaction. Use :1, :2… placeholders to use binding parameters. ' + + 'Avoid using "?" to avoid unexpected behaviors when having multiple queries.', + required: true, + }), + binds: Property.Array({ + displayName: 'Parameters', + description: + 'Binding parameters shared across all queries to prevent SQL injection attacks. ' + + 'Use :1, :2, etc. to reference parameters in order. ' + + 'Avoid using "?" to avoid unexpected behaviors when having multiple queries. ' + + 'Unused parameters are allowed.', + required: false, + }), + useTransaction: Property.Checkbox({ + displayName: 'Use Transaction', + description: + 'When enabled, all queries will be executed in a single transaction. If any query fails, all changes will be rolled back.', + required: false, + defaultValue: false, + }), + timeout: Property.Number({ + displayName: 'Query timeout (ms)', + description: + 'An integer indicating the maximum number of milliseconds to wait for a query to complete before timing out.', + required: false, + defaultValue: DEFAULT_QUERY_TIMEOUT, + }), + application: Property.ShortText({ + displayName: 'Application name', + description: + 'A string indicating the name of the client application connecting to the server.', + required: false, + defaultValue: DEFAULT_APPLICATION_NAME, + }), + }, + + async run(context) { + const { username, password, role, database, warehouse, account } = + context.auth; + + const connection = snowflake.createConnection({ + application: context.propsValue.application, + timeout: context.propsValue.timeout, + username, + password, + role, + database, + warehouse, + account, + }); + + return new Promise((resolve, reject) => { + connection.connect(async function (err: SnowflakeError | undefined) { + if (err) { + reject(err); + return; + } + + const { sqlTexts, binds, useTransaction } = context.propsValue; + const queryResults: QueryResults = []; + + function handleError(err: SnowflakeError) { + if (useTransaction) { + connection.execute({ + sqlText: 'ROLLBACK', + complete: () => { + connection.destroy(() => { + reject(err); + }); + }, + }); + } else { + connection.destroy(() => { + reject(err); + }); + } + } + + async function executeQueriesSequentially() { + try { + if (useTransaction) { + await new Promise((resolveBegin, rejectBegin) => { + connection.execute({ + sqlText: 'BEGIN', + complete: (err: SnowflakeError | undefined) => { + if (err) rejectBegin(err); + else resolveBegin(); + }, + }); + }); + } + for (const sqlText of sqlTexts) { + const result = await new Promise( + (resolveQuery, rejectQuery) => { + connection.execute({ + sqlText: sqlText as string, + binds: binds as snowflake.Binds, + complete: ( + err: SnowflakeError | undefined, + stmt: Statement, + rows: QueryResult + ) => { + if (err) { + rejectQuery(err); + return; + } + resolveQuery(rows); + }, + }); + } + ); + + queryResults.push({ + query: sqlText as string, + result, + }); + } + + if (useTransaction) { + await new Promise((resolveCommit, rejectCommit) => { + connection.execute({ + sqlText: 'COMMIT', + complete: (err: SnowflakeError | undefined) => { + if (err) rejectCommit(err); + else resolveCommit(); + }, + }); + }); + } + + connection.destroy((err: SnowflakeError | undefined) => { + if (err) { + reject(err); + return; + } + resolve(queryResults); + }); + } catch (err) { + handleError(err as SnowflakeError); // Reject with the original error! + } + } + + executeQueriesSequentially(); + }); + }); + }, +}); diff --git a/packages/pieces/community/snowflake/src/lib/actions/run-query.ts b/packages/pieces/community/snowflake/src/lib/actions/run-query.ts new file mode 100644 index 0000000..30941de --- /dev/null +++ b/packages/pieces/community/snowflake/src/lib/actions/run-query.ts @@ -0,0 +1,81 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import snowflake from 'snowflake-sdk'; +import { snowflakeAuth } from '../../index'; + +const DEFAULT_APPLICATION_NAME = 'ActivePieces'; +const DEFAULT_QUERY_TIMEOUT = 30000; + +export const runQuery = createAction({ + name: 'runQuery', + displayName: 'Run Query', + description: 'Run Query', + auth: snowflakeAuth, + props: { + sqlText: Property.ShortText({ + displayName: 'SQL query', + description: 'Use :1, :2… or ? placeholders to use binding parameters.', + required: true, + }), + binds: Property.Array({ + displayName: 'Parameters', + description: + 'Binding parameters for the SQL query (to prevent SQL injection attacks)', + required: false, + }), + timeout: Property.Number({ + displayName: 'Query timeout (ms)', + description: + 'An integer indicating the maximum number of milliseconds to wait for a query to complete before timing out.', + required: false, + defaultValue: DEFAULT_QUERY_TIMEOUT, + }), + application: Property.ShortText({ + displayName: 'Application name', + description: + 'A string indicating the name of the client application connecting to the server.', + required: false, + defaultValue: DEFAULT_APPLICATION_NAME, + }), + }, + async run(context) { + const { username, password, role, database, warehouse, account } = + context.auth; + + const connection = snowflake.createConnection({ + application: context.propsValue.application, + timeout: context.propsValue.timeout, + username, + password, + role, + database, + warehouse, + account, + }); + + return new Promise((resolve, reject) => { + connection.connect(function (err, conn) { + if (err) { + reject(err); + } + }); + + const { sqlText, binds } = context.propsValue; + + connection.execute({ + sqlText, + binds: binds as snowflake.Binds, + complete: (err, stmt, rows) => { + if (err) { + reject(err); + } + connection.destroy((err, conn) => { + if (err) { + reject(err); + } + }); + resolve(rows); + }, + }); + }); + }, +}); diff --git a/packages/pieces/community/snowflake/src/lib/common/index.ts b/packages/pieces/community/snowflake/src/lib/common/index.ts new file mode 100644 index 0000000..dc10a46 --- /dev/null +++ b/packages/pieces/community/snowflake/src/lib/common/index.ts @@ -0,0 +1,238 @@ +import { + DynamicPropsValue, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { snowflakeAuth } from '../..'; +import snowflake from 'snowflake-sdk'; + +const DEFAULT_APPLICATION_NAME = 'ActivePieces'; +const DEFAULT_QUERY_TIMEOUT = 30000; + +export function configureConnection( + auth: PiecePropValueSchema +) { + return snowflake.createConnection({ + application: DEFAULT_APPLICATION_NAME, + timeout: DEFAULT_QUERY_TIMEOUT, + username: auth.username, + password: auth.password, + role: auth.role, + database: auth.database, + warehouse: auth.warehouse, + account: auth.account, + }); +} + +export async function connect(conn: snowflake.Connection) { + return await new Promise((resolve, reject) => { + conn.connect((error) => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); +} + +export async function destroy(conn: snowflake.Connection) { + return await new Promise((resolve, reject) => { + conn.destroy((error) => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); +} + +export async function execute( + conn: snowflake.Connection, + sqlText: string, + binds: snowflake.Binds +) { + return await new Promise((resolve, reject) => { + conn.execute({ + sqlText, + binds, + complete: (error, _, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }, + }); + }); +} + +export const snowflakeCommonProps = { + database: Property.Dropdown({ + displayName: 'Database', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first', + }; + } + + const authValue = auth as PiecePropValueSchema; + + const connection = configureConnection(authValue); + + await connect(connection); + + const response = await execute(connection, 'SHOW DATABASES', []); + + await destroy(connection); + + return { + disabled: false, + options: response + ? response.map((db: any) => { + return { + label: db.name, + value: db.name, + }; + }) + : [], + }; + }, + }), + schema: Property.Dropdown({ + displayName: 'Schema', + refreshers: ['database'], + required: true, + options: async ({ auth, database }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first', + }; + } + if (!database) { + return { + disabled: true, + options: [], + placeholder: 'Please select database first', + }; + } + + const authValue = auth as PiecePropValueSchema; + + const connection = configureConnection(authValue); + + await connect(connection); + + const response = await execute( + connection, + `SHOW SCHEMAS IN DATABASE ${database}`, + [] + ); + + await destroy(connection); + + return { + disabled: false, + options: response + ? response.map((schema: any) => { + return { + label: schema.name, + value: schema.name, + }; + }) + : [], + }; + }, + }), + table: Property.Dropdown({ + displayName: 'Table', + refreshers: ['database', 'schema'], + required: true, + options: async ({ auth, database, schema }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first', + }; + } + if (!database) { + return { + disabled: true, + options: [], + placeholder: 'Please select database first', + }; + } + if (!schema) { + return { + disabled: true, + options: [], + placeholder: 'Please select schema first', + }; + } + + const authValue = auth as PiecePropValueSchema; + + const connection = configureConnection(authValue); + + await connect(connection); + + const response = await execute( + connection, + `SHOW TABLES IN SCHEMA ${database}.${schema}`, + [] + ); + + await destroy(connection); + + return { + disabled: false, + options: response + ? response.map((table: any) => { + return { + label: table.name, + value: `${database}.${schema}.${table.name}`, + }; + }) + : [], + }; + }, + }), + table_column_values: Property.DynamicProperties({ + displayName: 'Rows', + required: true, + refreshers: ['database', 'schema', 'table'], + props: async ({ auth, table }) => { + if (!auth) return {}; + if (!table) return {}; + + const authValue = auth as PiecePropValueSchema; + + const connection = configureConnection(authValue); + await connect(connection); + const response = await execute(connection, `DESCRIBE TABLE ${table}`, []); + await destroy(connection); + + const fields: DynamicPropsValue = {}; + + if (response) { + for (const column of response) { + fields[column.name] = Property.ShortText({ + displayName: column.name, + required: false, + }); + } + } + + return fields; + }, + }), +}; diff --git a/packages/pieces/community/snowflake/tsconfig.json b/packages/pieces/community/snowflake/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/snowflake/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/snowflake/tsconfig.lib.json b/packages/pieces/community/snowflake/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/snowflake/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/soap/.eslintrc.json b/packages/pieces/community/soap/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/soap/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/soap/README.md b/packages/pieces/community/soap/README.md new file mode 100644 index 0000000..aef2008 --- /dev/null +++ b/packages/pieces/community/soap/README.md @@ -0,0 +1,7 @@ +# pieces-soap + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-soap` to build the library. diff --git a/packages/pieces/community/soap/package.json b/packages/pieces/community/soap/package.json new file mode 100644 index 0000000..232746a --- /dev/null +++ b/packages/pieces/community/soap/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-soap", + "version": "0.0.4" +} \ No newline at end of file diff --git a/packages/pieces/community/soap/project.json b/packages/pieces/community/soap/project.json new file mode 100644 index 0000000..53ba5c5 --- /dev/null +++ b/packages/pieces/community/soap/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-soap", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/soap/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/soap", + "tsConfig": "packages/pieces/community/soap/tsconfig.lib.json", + "packageJson": "packages/pieces/community/soap/package.json", + "main": "packages/pieces/community/soap/src/index.ts", + "assets": [ + "packages/pieces/community/soap/*.md", + { + "input": "packages/pieces/community/soap/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-soap {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/soap/src/index.ts b/packages/pieces/community/soap/src/index.ts new file mode 100644 index 0000000..fd5f460 --- /dev/null +++ b/packages/pieces/community/soap/src/index.ts @@ -0,0 +1,17 @@ +import { createPiece } from '@activepieces/pieces-framework'; +import { callMethod } from './lib/actions/call-method'; +import { soapAuth } from './lib/shared/auth'; + +export const soap = createPiece({ + displayName: 'SOAP', + description: + 'Simple Object Access Protocol for communication between applications', + + auth: soapAuth(), + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/soap.png', + authors: ["x7airworker","kishanprmr","abuaboud"], + categories: [], + actions: [callMethod], + triggers: [], +}); diff --git a/packages/pieces/community/soap/src/lib/actions/call-method.ts b/packages/pieces/community/soap/src/lib/actions/call-method.ts new file mode 100644 index 0000000..663cb7a --- /dev/null +++ b/packages/pieces/community/soap/src/lib/actions/call-method.ts @@ -0,0 +1,144 @@ +import * as soap from 'soap'; +import { + CustomAuthProps, + Property, + ShortTextProperty, + StaticDropdownProperty, + StaticMultiSelectDropdownProperty, + StaticPropsValue, + createAction, +} from '@activepieces/pieces-framework'; +import { soapAuth } from '../shared/auth'; + +type DynamicProp = + | ShortTextProperty + | StaticDropdownProperty + | StaticMultiSelectDropdownProperty; + +export const callMethod = createAction({ + name: 'call_method', + displayName: 'Call SOAP Method', + description: 'Call a SOAP from a given wsdl specification', + auth: soapAuth(), + props: { + wsdl: Property.ShortText({ + displayName: 'WSDL URL', + required: true, + }), + method: Property.Dropdown({ + description: 'The SOAP Method', + displayName: 'Method', + required: true, + refreshers: ['wsdl'], + options: async ({ wsdl }) => { + if (!wsdl) { + return { + disabled: true, + placeholder: 'Setup WSDL URL first', + options: [], + }; + } + + const client = await soap.createClientAsync(wsdl as string); + const spec = client.describe(); + const methods = Object.keys( + Object.values(Object.values(spec)[0] as object)[0] + ); + + return { + disabled: false, + options: methods.map((method) => { + return { + label: method, + value: method, + }; + }), + }; + }, + }), + args: Property.DynamicProperties({ + description: 'Arguments for the SOAP method', + displayName: 'Parameters', + required: true, + refreshers: ['wsdl', 'method'], + async props({ wsdl, method }) { + if (!wsdl || !method) { + return {}; + } + const client = await soap.createClientAsync(wsdl as unknown as string); + const spec = client.describe(); + const methods = Object.values(Object.values(spec)[0] as object)[0]; + + const properties: Record = {}; + + for (const key in methods[method as unknown as string]['input']) { + properties[key as string] = Property.ShortText({ + displayName: key, + required: true, + }); + } + + return properties; + }, + }), + parsed: Property.Checkbox({ + displayName: 'Parsed', + required: false, + defaultValue: false, + }), + }, + async run(ctx) { + const { wsdl, method, args, parsed } = ctx.propsValue; + + const client = await soap.createClientAsync(wsdl, { + forceSoap12Headers: true, + }); + + const auth = ctx.auth as StaticPropsValue; + switch (auth['type']) { + case 'WS': + client.setSecurity( + new soap.WSSecurity( + auth['username'] as string, + auth['password'] as string + ) + ); + break; + case 'Basic': + client.setSecurity( + new soap.BasicAuthSecurity( + auth['username'] as string, + auth['password'] as string + ) + ); + break; + case 'Header': // eslint-disable-next-line no-case-declarations + client.addSoapHeader(ctx.auth['customHeader'] as string); + break; + } + + const action = client[method + 'Async']; + + try { + const [actionRes] = await action(args); + + if (parsed || false) { + return actionRes; + } else { + return { + request: client.lastRequest, + response: client.lastResponse, + }; + } + } catch (e: any) { + return { + error: true, + status: e.response.status, + raw: { + request: client.lastRequest, + response: e.body, + }, + }; + } + }, +}); diff --git a/packages/pieces/community/soap/src/lib/shared/auth.ts b/packages/pieces/community/soap/src/lib/shared/auth.ts new file mode 100644 index 0000000..dc885ff --- /dev/null +++ b/packages/pieces/community/soap/src/lib/shared/auth.ts @@ -0,0 +1,48 @@ +import { PieceAuth, Property } from '@activepieces/pieces-framework'; + +export function soapAuth() { + return PieceAuth.CustomAuth({ + required: true, + props: { + type: Property.StaticDropdown({ + displayName: 'Authentication Type', + required: true, + options: { + options: [ + { + label: 'None', + value: 'None', + }, + { + label: 'WS Security', + value: 'WS', + }, + { + label: 'Basic Auth', + value: 'Basic', + }, + { + label: 'Custom Header', + value: 'Header', + }, + ], + }, + }), + username: Property.ShortText({ + displayName: 'Username', + description: 'The WS Security username', + required: false, + }), + password: Property.ShortText({ + displayName: 'Password', + description: 'The WS Security password', + required: false, + }), + customHeader: Property.LongText({ + displayName: 'Custom Header', + description: 'Custom Header Content', + required: false, + }), + }, + }); +} diff --git a/packages/pieces/community/soap/tsconfig.json b/packages/pieces/community/soap/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/soap/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/soap/tsconfig.lib.json b/packages/pieces/community/soap/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/soap/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/sperse/.eslintrc.json b/packages/pieces/community/sperse/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/sperse/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/sperse/README.md b/packages/pieces/community/sperse/README.md new file mode 100644 index 0000000..bdce84e --- /dev/null +++ b/packages/pieces/community/sperse/README.md @@ -0,0 +1,7 @@ +# pieces-sperse + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-sperse` to build the library. diff --git a/packages/pieces/community/sperse/package.json b/packages/pieces/community/sperse/package.json new file mode 100644 index 0000000..5155c18 --- /dev/null +++ b/packages/pieces/community/sperse/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-sperse", + "version": "0.0.2" +} diff --git a/packages/pieces/community/sperse/project.json b/packages/pieces/community/sperse/project.json new file mode 100644 index 0000000..2c230c9 --- /dev/null +++ b/packages/pieces/community/sperse/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-sperse", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/sperse/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/sperse", + "tsConfig": "packages/pieces/community/sperse/tsconfig.lib.json", + "packageJson": "packages/pieces/community/sperse/package.json", + "main": "packages/pieces/community/sperse/src/index.ts", + "assets": [ + "packages/pieces/community/sperse/*.md", + { + "input": "packages/pieces/community/sperse/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/sperse/src/index.ts b/packages/pieces/community/sperse/src/index.ts new file mode 100644 index 0000000..d5a7d08 --- /dev/null +++ b/packages/pieces/community/sperse/src/index.ts @@ -0,0 +1,76 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { addOrUpdateContactExtended } from './lib/actions/add-or-update-contact-extended'; +import { addOrUpdateContact } from './lib/actions/add-or-update-contact'; +import { addOrUpdateSubscription } from './lib/actions/add-or-update-subscription'; +import { createInvoice } from './lib/actions/create-invoice'; +import { createProduct } from './lib/actions/create-product'; +import { getContactDetails } from './lib/actions/get-contact-details'; +import { newLead } from './lib/triggers/new-lead'; +import { newPayment } from './lib/triggers/new-payment'; +import { newSubscription } from './lib/triggers/new-subscription'; + +const markdownDescription = ` + Follow these instructions to get your Sperse API Key: + + 1. Visit the following website: https://app.sperse.com/, or the beta website: https://beta.sperse.com, or the test website: https://testadmin.sperse.com + 2. Once on the website, locate and click on the admin to obtain your sperse API Key. +`; + +export const sperseAuth = PieceAuth.CustomAuth({ + description: markdownDescription, + required: true, + props: { + base_url: Property.StaticDropdown({ + displayName: 'Base URL', + description: 'Select the base environment URL', + required: true, + options: { + disabled: false, + options: [ + { + label: 'Sperse Live (app.sperse.com)', + value: 'https://app.sperse.com', + }, + { + label: 'Sperse Beta (beta.sperse.com)', + value: 'https://beta.sperse.com', + }, + { + label: 'Sperse Test (testadmin.sperse.com)', + value: 'https://testadmin.sperse.com', + }, + ], + }, + }), + api_key: PieceAuth.SecretText({ + displayName: 'Secret API Key', + description: 'Enter the API Key', + required: true, + }), + }, +}); + +export const sperse = createPiece({ + displayName: 'Sperse', + description: + 'Sperse CRM enables secure payment processing and affiliate marketing for online businesses', + auth: sperseAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/sperse.png', + categories: [PieceCategory.COMMERCE, PieceCategory.PAYMENT_PROCESSING], + authors: ['Trayshmhirk'], + actions: [ + addOrUpdateContact, + addOrUpdateContactExtended, + addOrUpdateSubscription, + createInvoice, + createProduct, + getContactDetails, + ], + triggers: [newLead, newPayment, newSubscription], +}); diff --git a/packages/pieces/community/sperse/src/lib/actions/add-or-update-contact-extended.ts b/packages/pieces/community/sperse/src/lib/actions/add-or-update-contact-extended.ts new file mode 100644 index 0000000..9d571d3 --- /dev/null +++ b/packages/pieces/community/sperse/src/lib/actions/add-or-update-contact-extended.ts @@ -0,0 +1,1292 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { sperseAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const addOrUpdateContactExtended = createAction({ + name: 'addOrUpdateContactExtended', + displayName: 'Add or Update Contact (Extended)', + description: 'Adds or updates a contact (extended version)', + auth: sperseAuth, + props: { + matchExisting: Property.StaticDropdown({ + displayName: 'Match Existing Contact', + description: + 'If "Yes", will try to find an existing record using Email and Full Name and update it.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + overrideLists: Property.StaticDropdown({ + displayName: 'Override Lists', + description: + 'If "Yes", will override lists of contact details in update mode instead of merging them - lists, tags, emails, phones, links, addresses, photos.', + required: false, + defaultValue: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + ignoreInvalidValues: Property.StaticDropdown({ + displayName: 'Ignore Invalid Values', + description: + 'If "Yes", will save the record even if there are some validation errors.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + contactId: Property.Number({ + displayName: 'Contact ID', + description: 'Sperse Contact ID. Will be used for looking a client', + defaultValue: 0, + required: false, + }), + contactXref: Property.ShortText({ + displayName: 'Contact XREF', + description: + 'This is string external reference that can be passed during creation and then if sent again it will update the record.', + required: false, + }), + importType: Property.StaticDropdown({ + displayName: 'Contact Type', + required: true, + defaultValue: 'Lead', + options: { + disabled: false, + options: [ + { + label: 'Lead', + value: 'Lead', + }, + { + label: 'Client', + value: 'Client', + }, + { + label: 'Partner', + value: 'Partner', + }, + ], + }, + }), + // user info + createUser: Property.StaticDropdown({ + displayName: 'Create User', + description: + 'If "Yes" then User will be created. Personal email will be used as User Name.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + sendWelcomeEmail: Property.StaticDropdown({ + displayName: 'Send Welcome Email', + description: + 'If "Yes" then Welcome Email will be sent to the newly created user.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + userPassword: Property.ShortText({ + displayName: 'User Password', + description: + 'If password is not passed then it will be automatically generated.', + required: false, + }), + // fullname + namePrefix: Property.ShortText({ + displayName: 'Prefix', + description: 'The title used to address the contact.', + required: false, + }), + firstName: Property.ShortText({ + displayName: 'First Name', + description: 'Required if Last Name and Company Name fields are empty.', + required: true, + }), + middleName: Property.ShortText({ + displayName: 'Middle Name', + description: "The contact's middle name.", + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + description: 'Required if First Name and Company Name fields are empty.', + required: true, + }), + nickName: Property.ShortText({ + displayName: 'Nick Name', + description: "The contact's nick name.", + required: false, + }), + nameSuffix: Property.ShortText({ + displayName: 'Suffix', + description: 'Additional information about the contact e.g PhD.', + required: false, + }), + // personal info + gender: Property.StaticDropdown({ + displayName: 'Gender', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Male', + value: 'Male', + }, + { + label: 'Female', + value: 'Female', + }, + ], + }, + }), + dob: Property.ShortText({ + displayName: 'Date of Birth', + description: 'Valid date format YYYY-MM-DD or MM-DD-YYYY.', + required: false, + }), + bankCode: Property.ShortText({ + displayName: 'Bank Code', + description: "The contact's 4-letter personality code.", + required: false, + }), + ssn: Property.ShortText({ + displayName: 'SSN', + description: "The contact's social security number.", + required: false, + }), + drivingLicense: Property.ShortText({ + displayName: 'Driving License', + required: false, + }), + drivingLicenseState: Property.ShortText({ + displayName: 'Driving License State', + required: false, + }), + isActiveMilitaryDuty: Property.StaticDropdown({ + displayName: 'Is Active Military Duty', + description: + 'Possible values are: Yes, No. If nothing is chosen that means "Unknown".', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + isUSCitizen: Property.StaticDropdown({ + displayName: 'Is US Citizen', + description: + 'Possible values are: Yes, No. If nothing is chosen that means "Unknown".', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + preferredToD: Property.StaticDropdown({ + displayName: 'Preferred Time of Day', + description: 'Preferred Time of Day to contact with Client', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Morning', + value: 'Morning', + }, + { + label: 'Afternoon', + value: 'Afternoon', + }, + { + label: 'Evening', + value: 'Evening', + }, + { + label: 'Anytime', + value: 'Anytime', + }, + ], + }, + }), + personAffiliateCode: Property.ShortText({ + displayName: 'Person Affiliate Code', + description: + 'Affiliate Code is used for the current person detection as a source of new leads. Alphanumeric characters, underscore and hyphen are allowed.', + required: false, + }), + // email + email1: Property.LongText({ + displayName: 'Personal Email', + description: "The contact's personal email.", + required: false, + }), + email2: Property.LongText({ + displayName: 'Alternative Personal email', + description: "The contact's alternative personal email.", + required: false, + }), + email3: Property.LongText({ + displayName: 'Other Personal email', + description: "The contact's additional email.", + required: false, + }), + workEmail1: Property.LongText({ + displayName: 'Work Email', + description: "The contact's work email.", + required: false, + }), + workEmail2: Property.LongText({ + displayName: 'Alternative Work Email', + description: "The contact's alternative work email.", + required: false, + }), + workEmail3: Property.LongText({ + displayName: 'Other Work Email', + description: "The contact's other work email.", + required: false, + }), + // phone + mobilePhone: Property.ShortText({ + displayName: 'Mobile Phone', + description: "The contact's primary phone number.", + required: false, + }), + mobilePhoneExt: Property.ShortText({ + displayName: 'Mobile Phone Ext', + description: "The contact's primary phone number extension.", + required: false, + }), + homePhone: Property.ShortText({ + displayName: 'Home Phone', + description: "The contact's home phone number.", + required: false, + }), + homePhoneExt: Property.ShortText({ + displayName: 'Home Phone Ext', + description: "The contact's home phone number extension.", + required: false, + }), + workPhone1: Property.ShortText({ + displayName: 'Work Phone', + description: "The contact's work phone number.", + required: false, + }), + workPhone1Ext: Property.ShortText({ + displayName: 'Work Phone Ext', + description: "The contact's work phone number extension.", + required: false, + }), + workPhone2: Property.ShortText({ + displayName: 'Work Phone 2', + description: "The contact's alternative work phone number.", + required: false, + }), + workPhone2Ext: Property.ShortText({ + displayName: 'Work Phone 2 Ext', + description: "The contact's alternative work phone number extension.", + required: false, + }), + // home address + home_street: Property.LongText({ + displayName: 'Home Street', + description: + "The contact's full street address (can include apartment or unit number).", + required: false, + }), + home_addressLine2: Property.LongText({ + displayName: 'Address 2', + required: false, + }), + home_city: Property.LongText({ + displayName: 'City', + description: "The contact's city of residence.", + required: false, + }), + home_stateName: Property.ShortText({ + displayName: 'State Name', + description: "The contact's state of residence.", + required: false, + }), + home_stateId: Property.ShortText({ + displayName: 'State Code', + description: "The contact's state code.", + required: false, + }), + home_zip: Property.ShortText({ + displayName: 'ZIP Code', + description: "The contact's zip/postal code.", + required: false, + }), + home_countryName: Property.ShortText({ + displayName: 'Country Name', + description: "The contact's country of residence.", + required: false, + }), + home_countryId: Property.ShortText({ + displayName: 'Country Code', + description: "The contact's country code.", + required: false, + }), + // work address + work_street: Property.LongText({ + displayName: 'Work Street', + description: "The contact's work address.", + required: false, + }), + work_addressLine2: Property.LongText({ + displayName: 'Address 2', + required: false, + }), + work_city: Property.LongText({ + displayName: 'City', + description: "The contact's work city.", + required: false, + }), + work_stateName: Property.ShortText({ + displayName: 'State Name', + description: "The contact's work state.", + required: false, + }), + work_stateId: Property.ShortText({ + displayName: 'State Code', + description: "The contact's work state code.", + required: false, + }), + work_zip: Property.ShortText({ + displayName: 'ZIP Code', + description: "The contact's work zip code.", + required: false, + }), + work_countryName: Property.ShortText({ + displayName: 'Country Name', + description: "The contact's work country.", + required: false, + }), + work_countryId: Property.ShortText({ + displayName: 'Country Code', + description: "The contact's work country code.", + required: false, + }), + // links + webSiteUrl: Property.LongText({ + displayName: 'Website', + description: "The contact's company website URL.", + required: false, + }), + linkedInUrl: Property.LongText({ + displayName: 'LinkedIn', + description: "The contact's LinkedIn profile id.", + required: false, + }), + photoUrl: Property.LongText({ + displayName: 'Photo URL', + description: "The contact's person photo URL.", + required: false, + }), + facebookUrl: Property.LongText({ + displayName: 'Facebook', + description: "The contact's Facebook profile id.", + required: false, + }), + instagramUrl: Property.LongText({ + displayName: 'Instagram', + description: "The contact's Instagram profile id.", + required: false, + }), + twitterUrl: Property.LongText({ + displayName: 'Twitter', + description: "The contact's Twitter profile id.", + required: false, + }), + googlePlusUrl: Property.LongText({ + displayName: 'Google Reviews', + description: "The contact's Google reviews.", + required: false, + }), + angelListUrl: Property.LongText({ + displayName: 'AngelList', + description: "The contact's AngelList profile id.", + required: false, + }), + zoomUrl: Property.LongText({ + displayName: 'Zoom', + description: "The contact's Zoom id.", + required: false, + }), + // content + experience: Property.LongText({ + displayName: 'Content', + description: "The contact's professional experience.", + required: false, + }), + profileSummary: Property.LongText({ + displayName: 'Profile Summary', + description: "The contact's profile summary.", + required: false, + }), + notes: Property.LongText({ + displayName: 'Notes', + description: 'Additional notes about the contact', + required: false, + }), + followUpDate: Property.ShortText({ + displayName: 'Follow Up Date', + description: + 'Valid date format YYYY-MM-DD HH:MM:SS. If date is defined then new Follow Up Task will be created for this contact', + required: false, + }), + assignedUser: Property.ShortText({ + displayName: 'Assigned User', + description: + 'Preferably, Sperse User Email should be passed as it is unique within Sperse account but User Name can be also passed', + required: false, + }), + leadDealAmount: Property.Number({ + displayName: 'Deal Amount', + description: 'Estimated deal/opportunity amount.', + required: false, + }), + // custom fields + customField1: Property.LongText({ + displayName: 'Custom Field 1', + description: 'Additional custom data for the contact record.', + required: false, + }), + customField2: Property.LongText({ + displayName: 'Custom Field 2', + required: false, + }), + customField3: Property.LongText({ + displayName: 'Custom Field 3', + required: false, + }), + customField4: Property.LongText({ + displayName: 'Custom Field 4', + required: false, + }), + customField5: Property.LongText({ + displayName: 'Custom Field 5', + required: false, + }), + // business info + companyName: Property.LongText({ + displayName: 'Company Name', + description: + "Name of the contact's company (This field is mandatory if the First Name and Last Name fields are empty).", + required: false, + }), + organizationType: Property.StaticDropdown({ + displayName: 'Organization Type', + required: false, + options: { + disabled: false, + options: [ + { + label: 'LLP', + value: 'LLP', + }, + { + label: 'LLC', + value: 'LLC', + }, + { + label: 'Inc', + value: 'Inc', + }, + { + label: 'LP', + value: 'LP', + }, + { + label: 'Partnership', + value: 'Partnership', + }, + { + label: 'Sole Proprietership', + value: 'Sole Proprietership', + }, + { + label: 'Trust', + value: 'Trust', + }, + { + label: 'LLLP', + value: 'LLLP', + }, + { + label: 'Other', + value: 'Other', + }, + ], + }, + }), + isEmployed: Property.StaticDropdown({ + displayName: 'Is Employed', + description: 'Pass yes if the client is employed in this Organization.', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + jobTitle: Property.ShortText({ + displayName: 'Job Title', + description: "The contact's job title.", + required: false, + }), + employmentStartDate: Property.ShortText({ + displayName: 'Employment Start Date', + description: 'Valid date format YYYY-MM-DD or MM-DD-YYYY', + required: false, + }), + employeeCount: Property.ShortText({ + displayName: 'Employee Count', + required: false, + }), + dateFounded: Property.ShortText({ + displayName: 'Date Founded', + description: 'Valid date format YYYY-MM-DD or MM-DD-YYYY', + required: false, + }), + ein: Property.LongText({ + displayName: 'EIN', + required: false, + }), + annualRevenue: Property.Number({ + displayName: 'Annual Revenue', + required: false, + }), + industry: Property.ShortText({ + displayName: 'Industry', + description: "The company's industry.", + required: false, + }), + companyPhone: Property.ShortText({ + displayName: 'Company Phone', + required: false, + }), + companyPhoneExt: Property.ShortText({ + displayName: 'Company Phone Extension', + required: false, + }), + companyFaxNumber: Property.LongText({ + displayName: 'Company Fax Number', + required: false, + }), + companyEmail: Property.LongText({ + displayName: 'Company Email', + required: false, + }), + companyWebSiteUrl: Property.LongText({ + displayName: 'Company Website', + required: false, + }), + companyFacebookUrl: Property.LongText({ + displayName: 'Company Facebook', + required: false, + }), + companyLinkedInUrl: Property.LongText({ + displayName: 'Company LinkedIn', + required: false, + }), + companyInstagramUrl: Property.LongText({ + displayName: 'Company Instagram', + required: false, + }), + companyTwitterUrl: Property.LongText({ + displayName: 'Company Twitter', + required: false, + }), + companyGooglePlusUrl: Property.LongText({ + displayName: 'Company Google Reviews', + required: false, + }), + companyCrunchbaseUrl: Property.LongText({ + displayName: 'Company Crunchbase', + required: false, + }), + companyBBBUrl: Property.LongText({ + displayName: 'Company BBB URL', + required: false, + }), + companyZoomUrl: Property.LongText({ + displayName: 'Company Zoom', + required: false, + }), + companyCalendlyUrl: Property.LongText({ + displayName: 'Company Calendly', + required: false, + }), + companyLogoUrl: Property.LongText({ + displayName: 'Company Logo URL', + required: false, + }), + companyAffiliateCode: Property.ShortText({ + displayName: 'Company Affiliate Code', + required: false, + }), + // company full Address + company_street: Property.LongText({ + displayName: 'Company Street', + required: false, + }), + company_addressLine2: Property.LongText({ + displayName: 'Address 2', + required: false, + }), + company_city: Property.ShortText({ + displayName: 'City', + required: false, + }), + company_stateName: Property.ShortText({ + displayName: 'State Name', + required: false, + }), + company_stateId: Property.ShortText({ + displayName: 'State Code', + required: false, + }), + company_zip: Property.ShortText({ + displayName: 'Company Zip Code', + required: false, + }), + company_countryName: Property.LongText({ + displayName: 'Company Country Name', + required: false, + }), + company_countryId: Property.ShortText({ + displayName: 'Company Country Code', + required: false, + }), + // tracking info + leadSource: Property.ShortText({ + displayName: 'Source Code', + description: + 'The first known source the contact used to find your website. You can set this automatically and update manually later.', + required: false, + }), + channelId: Property.ShortText({ + displayName: 'Channel Code', + description: 'The channel/medium the contact used to find your website.', + required: false, + }), + affiliateCode: Property.ShortText({ + displayName: 'Affiliate Code', + description: + 'The affiliate/referer partner through which the contact signed up.', + required: false, + }), + campaignId: Property.ShortText({ + displayName: 'Campaign ID', + required: false, + }), + gclId: Property.ShortText({ + displayName: 'Google Click ID', + required: false, + }), + refererURL: Property.LongText({ + displayName: 'Referer URL', + description: + 'The webpage where the contact clicked a link that sent them to your website.', + required: false, + }), + applicantId: Property.ShortText({ + displayName: 'Applicant ID', + required: false, + }), + applicationId: Property.ShortText({ + displayName: 'Application ID', + required: false, + }), + ipAddress: Property.LongText({ + displayName: 'IP Address', + required: false, + }), + userAgent: Property.ShortText({ + displayName: 'User Agent', + required: false, + }), + siteId: Property.ShortText({ + displayName: 'Site ID', + required: false, + }), + siteUrl: Property.LongText({ + displayName: 'Site URL', + required: false, + }), + dateCreated: Property.ShortText({ + displayName: 'Date Created', + description: 'Valid date format YYYY-MM-DD HH:MM:SS', + required: false, + }), + entryUrl: Property.LongText({ + displayName: 'Entry URL', + description: + 'The first page of visit through which the contact visited your website.', + required: false, + }), + // contact Attributes + leadStageName: Property.ShortText({ + displayName: 'Lead Stage Name', + description: + 'The first page of visit through which the contact visited your website.', + required: false, + }), + star: Property.ShortText({ + displayName: 'Star', + description: + 'String Value. Supports the following items: Yellow, Blue, Green, Purple, Red. Other values will be skipped.', + required: false, + }), + rating: Property.ShortText({ + displayName: 'Rating', + description: 'This is the static array from 1 to 10', + required: false, + }), + // UtmParameters + utmSource: Property.LongText({ + displayName: 'UTM Source', + required: false, + }), + utmMedium: Property.LongText({ + displayName: 'UTM Medium', + required: false, + }), + utmCampaign: Property.LongText({ + displayName: 'UTM Campaign', + required: false, + }), + utmTerm: Property.LongText({ + displayName: 'UTM Term', + required: false, + }), + utmContent: Property.LongText({ + displayName: 'UTM Content', + required: false, + }), + utmKeyword: Property.LongText({ + displayName: 'UTM Keyword', + required: false, + }), + utmAdGroup: Property.LongText({ + displayName: 'UTM AdGroup', + required: false, + }), + utmName: Property.LongText({ + displayName: 'UTM Name', + required: false, + }), + // requestCustomInfo + requestCustomField1: Property.LongText({ + displayName: 'Request Custom Field 1', + required: false, + }), + requestCustomField2: Property.LongText({ + displayName: 'Request Custom Field 2', + required: false, + }), + requestCustomField3: Property.LongText({ + displayName: 'Request Custom Field 3', + required: false, + }), + requestCustomField4: Property.LongText({ + displayName: 'Request Custom Field 4', + required: false, + }), + requestCustomField5: Property.LongText({ + displayName: 'Request Custom Field 5', + required: false, + }), + // subscription1 + sub1_productCode: Property.ShortText({ + displayName: 'Product Code', + description: + 'Product Code from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 1.', + required: false, + }), + sub1_paymentPeriodType: Property.StaticDropdown({ + displayName: 'Product Payment Period Type', + description: + 'The chosen Period Type has to be set for the Product on Sperse side', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + ], + }, + }), + sub1_systemType: Property.ShortText({ + displayName: 'System Type', + description: + 'Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 1.', + required: false, + }), + sub1_code: Property.ShortText({ + displayName: 'Code', + description: + 'Code of subscription service from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 1.', + required: false, + }), + sub1_name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + sub1_level: Property.ShortText({ + displayName: 'Level', + description: 'Code of subscription service level from the Sperse CRM.', + required: false, + }), + sub1_startDate: Property.ShortText({ + displayName: 'Start Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub1_endDate: Property.ShortText({ + displayName: 'End Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub1_amount: Property.Number({ + displayName: 'Amount', + required: false, + }), + // subscription2 + sub2_productCode: Property.ShortText({ + displayName: 'Product Code', + description: + 'Product Code from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 2.', + required: false, + }), + sub2_paymentPeriodType: Property.StaticDropdown({ + displayName: 'Product Payment Period Type', + description: + 'The chosen Period Type has to be set for the Product on Sperse side', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + ], + }, + }), + sub2_systemType: Property.ShortText({ + displayName: 'System Type', + description: + 'Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 2.', + required: false, + }), + sub2_code: Property.ShortText({ + displayName: 'Code', + description: + 'Code of subscription service from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 2.', + required: false, + }), + sub2_name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + sub2_level: Property.ShortText({ + displayName: 'Level', + description: 'Code of subscription service level from the Sperse CRM.', + required: false, + }), + sub2_startDate: Property.ShortText({ + displayName: 'Start Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub2_endDate: Property.ShortText({ + displayName: 'End Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub2_amount: Property.Number({ + displayName: 'Amount', + required: false, + }), + // subscription3 + sub3_productCode: Property.ShortText({ + displayName: 'Product Code', + description: + 'Product Code from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 3.', + required: false, + }), + sub3_paymentPeriodType: Property.StaticDropdown({ + displayName: 'Product Payment Period Type', + description: + 'The chosen Period Type has to be set for the Product on Sperse side', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + ], + }, + }), + sub3_systemType: Property.ShortText({ + displayName: 'System Type', + description: + 'Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 3.', + required: false, + }), + sub3_code: Property.ShortText({ + displayName: 'Code', + description: + 'Code of subscription service from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 3.', + required: false, + }), + sub3_name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + sub3_level: Property.ShortText({ + displayName: 'Level', + description: 'Code of subscription service level from the Sperse CRM.', + required: false, + }), + sub3_startDate: Property.ShortText({ + displayName: 'Start Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub3_endDate: Property.ShortText({ + displayName: 'End Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub3_amount: Property.Number({ + displayName: 'Amount', + required: false, + }), + }, + async run(context) { + const customer = { + importType: context.propsValue.importType, + ignoreInvalidValues: context.propsValue.ignoreInvalidValues, + matchExisting: context.propsValue.matchExisting, + overrideLists: context.propsValue.overrideLists, + createUser: context.propsValue.createUser, + sendWelcomeEmail: context.propsValue.sendWelcomeEmail, + contactId: context.propsValue.contactId, + contactXref: context.propsValue.contactXref, + userPassword: context.propsValue.userPassword, + personalInfo: { + fullName: { + namePrefix: context.propsValue.namePrefix, + firstName: context.propsValue.firstName, + middleName: context.propsValue.middleName, + lastName: context.propsValue.lastName, + nameSuffix: context.propsValue.nickName, + nickName: context.propsValue.nameSuffix, + }, + doB: context.propsValue.dob, + mobilePhone: context.propsValue.mobilePhone, + mobilePhoneExt: context.propsValue.mobilePhoneExt, + homePhone: context.propsValue.homePhone, + homePhoneExt: context.propsValue.homePhoneExt, + ssn: context.propsValue.ssn, + bankCode: context.propsValue.bankCode, + email1: context.propsValue.email1, + email2: context.propsValue.email2, + email3: context.propsValue.email3, + preferredToD: context.propsValue.preferredToD, + drivingLicense: context.propsValue.drivingLicense, + drivingLicenseState: context.propsValue.drivingLicenseState, + isActiveMilitaryDuty: context.propsValue.isActiveMilitaryDuty, + gender: context.propsValue.gender, + fullAddress: { + street: context.propsValue.home_street, + addressLine2: context.propsValue.home_addressLine2, + city: context.propsValue.home_city, + stateName: context.propsValue.home_stateName, + stateId: context.propsValue.home_stateId, + zip: context.propsValue.home_zip, + countryName: context.propsValue.home_countryName, + countryId: context.propsValue.home_countryId, + }, + isUSCitizen: context.propsValue.isUSCitizen, + webSiteUrl: context.propsValue.webSiteUrl, + facebookUrl: context.propsValue.facebookUrl, + linkedInUrl: context.propsValue.linkedInUrl, + instagramUrl: context.propsValue.instagramUrl, + twitterUrl: context.propsValue.twitterUrl, + googlePlusUrl: context.propsValue.googlePlusUrl, + angelListUrl: context.propsValue.angelListUrl, + zoomUrl: context.propsValue.zoomUrl, + photoUrl: context.propsValue.photoUrl, + experience: context.propsValue.experience, + profileSummary: context.propsValue.profileSummary, + affiliateCode: context.propsValue.personAffiliateCode, + customFields: { + customField1: context.propsValue.customField1, + customField2: context.propsValue.customField2, + customField3: context.propsValue.customField3, + customField4: context.propsValue.customField4, + customField5: context.propsValue.customField5, + }, + }, + businessInfo: { + companyName: context.propsValue.companyName, + organizationType: context.propsValue.organizationType, + jobTitle: context.propsValue.isEmployed, + isEmployed: context.propsValue.jobTitle, + employmentStartDate: context.propsValue.employmentStartDate, + employeeCount: context.propsValue.employeeCount, + dateFounded: context.propsValue.dateFounded, + ein: context.propsValue.ein, + annualRevenue: context.propsValue.annualRevenue, + industry: context.propsValue.industry, + companyPhone: context.propsValue.companyPhone, + companyPhoneExt: context.propsValue.companyPhoneExt, + companyFaxNumber: context.propsValue.companyFaxNumber, + companyEmail: context.propsValue.companyEmail, + companyFullAddress: { + street: context.propsValue.company_street, + addressLine2: context.propsValue.company_addressLine2, + city: context.propsValue.company_city, + stateName: context.propsValue.company_stateName, + stateId: context.propsValue.company_stateId, + zip: context.propsValue.company_zip, + countryName: context.propsValue.company_countryName, + countryId: context.propsValue.company_countryId, + }, + companyWebSiteUrl: context.propsValue.companyWebSiteUrl, + companyFacebookUrl: context.propsValue.companyFacebookUrl, + companyLinkedInUrl: context.propsValue.companyLinkedInUrl, + companyInstagramUrl: context.propsValue.companyInstagramUrl, + companyTwitterUrl: context.propsValue.companyTwitterUrl, + companyGooglePlusUrl: context.propsValue.companyGooglePlusUrl, + companyCrunchbaseUrl: context.propsValue.companyCrunchbaseUrl, + companyBBBUrl: context.propsValue.companyBBBUrl, + companyCalendlyUrl: context.propsValue.companyZoomUrl, + companyZoomUrl: context.propsValue.companyCalendlyUrl, + companyLogoUrl: context.propsValue.companyLogoUrl, + workPhone1: context.propsValue.workPhone1, + workPhone1Ext: context.propsValue.workPhone1Ext, + workPhone2: context.propsValue.workPhone2, + workPhone2Ext: context.propsValue.workPhone2Ext, + workEmail1: context.propsValue.workEmail1, + workEmail2: context.propsValue.workEmail2, + workEmail3: context.propsValue.workEmail3, + workFullAddress: { + street: context.propsValue.work_street, + addressLine2: context.propsValue.work_addressLine2, + city: context.propsValue.work_city, + stateName: context.propsValue.work_stateName, + stateId: context.propsValue.work_stateId, + zip: context.propsValue.work_zip, + countryName: context.propsValue.work_countryName, + countryId: context.propsValue.work_countryId, + }, + affiliateCode: context.propsValue.companyAffiliateCode, + }, + assignedUser: context.propsValue.assignedUser, + followUpDate: context.propsValue.followUpDate, + notes: context.propsValue.notes, + dateCreated: context.propsValue.dateCreated, + leadStageName: context.propsValue.leadStageName, + leadSource: context.propsValue.leadSource, + leadDealAmount: context.propsValue.leadDealAmount, + affiliateCode: context.propsValue.affiliateCode, + campaignId: context.propsValue.campaignId, + channelId: context.propsValue.channelId, + gclId: context.propsValue.gclId, + refererUrl: context.propsValue.refererURL, + entryUrl: context.propsValue.entryUrl, + applicantId: context.propsValue.applicantId, + applicationId: context.propsValue.applicationId, + ipAddress: context.propsValue.ipAddress, + userAgent: context.propsValue.userAgent, + siteId: context.propsValue.siteId, + siteUrl: context.propsValue.siteUrl, + utmSource: context.propsValue.utmSource, + utmMedium: context.propsValue.utmMedium, + utmCampaign: context.propsValue.utmCampaign, + utmTerm: context.propsValue.utmTerm, + utmContent: context.propsValue.utmContent, + utmKeyword: context.propsValue.utmKeyword, + utmAdGroup: context.propsValue.utmAdGroup, + utmName: context.propsValue.utmName, + requestCustomInfo: { + customField1: context.propsValue.requestCustomField1, + customField2: context.propsValue.requestCustomField2, + customField3: context.propsValue.requestCustomField3, + customField4: context.propsValue.requestCustomField4, + customField5: context.propsValue.requestCustomField5, + }, + subscription1: { + productCode: context.propsValue.sub1_productCode, + paymentPeriodType: context.propsValue.sub1_paymentPeriodType, + systemType: context.propsValue.sub1_systemType, + code: context.propsValue.sub1_code, + name: context.propsValue.sub1_name, + level: context.propsValue.sub1_level, + startDate: context.propsValue.sub1_startDate, + endDate: context.propsValue.sub1_endDate, + amount: context.propsValue.sub1_amount, + }, + subscription2: { + productCode: context.propsValue.sub2_productCode, + paymentPeriodType: context.propsValue.sub2_paymentPeriodType, + systemType: context.propsValue.sub2_systemType, + code: context.propsValue.sub2_code, + name: context.propsValue.sub2_name, + level: context.propsValue.sub2_level, + startDate: context.propsValue.sub2_startDate, + endDate: context.propsValue.sub2_endDate, + amount: context.propsValue.sub2_amount, + }, + subscription3: { + productCode: context.propsValue.sub3_productCode, + paymentPeriodType: context.propsValue.sub3_paymentPeriodType, + systemType: context.propsValue.sub3_systemType, + code: context.propsValue.sub3_code, + name: context.propsValue.sub3_name, + level: context.propsValue.sub3_level, + startDate: context.propsValue.sub3_startDate, + endDate: context.propsValue.sub3_endDate, + amount: context.propsValue.sub3_amount, + }, + classificationInfo: { + rating: context.propsValue.rating, + }, + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.base_url}/api/services/CRM/Import/ImportContact`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...customer, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/sperse/src/lib/actions/add-or-update-contact.ts b/packages/pieces/community/sperse/src/lib/actions/add-or-update-contact.ts new file mode 100644 index 0000000..3b5b03f --- /dev/null +++ b/packages/pieces/community/sperse/src/lib/actions/add-or-update-contact.ts @@ -0,0 +1,375 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { sperseAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const addOrUpdateContact = createAction({ + name: 'addOrUpdateContact', + displayName: 'Add or Update Contact', + description: 'Creates a new contact.', + auth: sperseAuth, + props: { + importType: Property.StaticDropdown({ + displayName: 'Contact Type', + required: true, + defaultValue: 'Lead', + options: { + disabled: false, + options: [ + { + label: 'Lead', + value: 'Lead', + }, + { + label: 'Client', + value: 'Client', + }, + { + label: 'Partner', + value: 'Partner', + }, + ], + }, + }), + matchExisting: Property.StaticDropdown({ + displayName: 'Match Existing Contact', + description: + 'If "Yes", will try to find an existing record using Email and Full Name and update it.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + contactId: Property.Number({ + displayName: 'Contact ID', + description: 'Sperse Contact ID. Will be used for looking a client', + defaultValue: 0, + required: false, + }), + // fullname + fullName: Property.ShortText({ + displayName: 'Full Name', + description: "The contact's full name.", + required: false, + }), + namePrefix: Property.ShortText({ + displayName: 'Prefix', + description: 'The title used to address the contact.', + required: false, + }), + firstName: Property.ShortText({ + displayName: 'First Name', + description: 'Required if Last Name and Company Name fields are empty.', + required: true, + }), + middleName: Property.ShortText({ + displayName: 'Middle Name', + description: "The contact's middle name.", + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + description: 'Required if First Name and Company Name fields are empty.', + required: true, + }), + nickName: Property.ShortText({ + displayName: 'Nick Name', + description: "The contact's nick name.", + required: false, + }), + nameSuffix: Property.ShortText({ + displayName: 'Suffix', + description: 'Additional information about the contact e.g PhD.', + required: false, + }), + // personal info + gender: Property.StaticDropdown({ + displayName: 'Gender', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Male', + value: 'Male', + }, + { + label: 'Female', + value: 'Female', + }, + ], + }, + }), + dob: Property.ShortText({ + displayName: 'Date of Birth', + description: 'Valid date format YYYY-MM-DD or MM-DD-YYYY.', + required: false, + }), + bankCode: Property.ShortText({ + displayName: 'Bank Code', + description: "The contact's 4-letter personality code.", + required: false, + }), + ssn: Property.ShortText({ + displayName: 'SSN', + description: "The contact's social security number.", + required: false, + }), + // business info + companyName: Property.ShortText({ + displayName: 'Company Name', + description: + "Name of the contact's company (This field is mandatory if the First Name and Last Name fields are empty).", + required: false, + }), + jobTitle: Property.ShortText({ + displayName: 'Job Title', + description: "The contact's job title.", + required: false, + }), + industry: Property.ShortText({ + displayName: 'Industry', + description: "The company's industry.", + required: false, + }), + // email + workEmail1: Property.LongText({ + displayName: 'Work Email', + description: "The contact's work email.", + required: false, + }), + email1: Property.LongText({ + displayName: 'Personal Email', + description: "The contact's personal email.", + required: false, + }), + email2: Property.LongText({ + displayName: 'Other email', + description: "The contact's additional email.", + required: false, + }), + // phone + workPhone1: Property.ShortText({ + displayName: 'Work Phone', + description: "The contact's work/primary phone number.", + required: false, + }), + homePhone: Property.ShortText({ + displayName: 'Home Phone', + description: "The contact's home phone number.", + required: false, + }), + mobilePhone: Property.ShortText({ + displayName: 'Mobile Phone', + description: "The contact's mobile phone number.", + required: false, + }), + // links + webSiteUrl: Property.ShortText({ + displayName: 'Website', + description: "The contact's company website URL.", + required: false, + }), + linkedInUrl: Property.ShortText({ + displayName: 'LinkedIn', + description: "The contact's LinkedIn profile id.", + required: false, + }), + photoUrl: Property.ShortText({ + displayName: 'Photo URL', + description: "The contact's person photo URL.", + required: false, + }), + // Full Address + street: Property.ShortText({ + displayName: 'Street', + description: + "The contact's full street address (can include apartment or unit number).", + required: false, + }), + addressLine2: Property.ShortText({ + displayName: 'Address 2', + required: false, + }), + city: Property.ShortText({ + displayName: 'City', + description: "The contact's city of residence.", + required: false, + }), + stateName: Property.ShortText({ + displayName: 'State Name', + description: "The contact's state of residence.", + required: false, + }), + stateId: Property.ShortText({ + displayName: 'State Code', + description: "The contact's state code.", + required: false, + }), + zip: Property.ShortText({ + displayName: 'Zip Code', + description: "The contact's zip/postal code.", + required: false, + }), + countryName: Property.ShortText({ + displayName: 'Country Name', + description: "The contact's country of residence.", + required: false, + }), + countryId: Property.ShortText({ + displayName: 'Country Code', + description: "The contact's country code.", + required: false, + }), + // content + experience: Property.LongText({ + displayName: 'Content', + description: "The contact's professional experience.", + required: false, + }), + profileSummary: Property.LongText({ + displayName: 'Profile Summary', + description: "The contact's profile summary.", + required: false, + }), + notes: Property.LongText({ + displayName: 'notes', + description: 'Additional notes about the contact', + required: false, + }), + followUpDate: Property.ShortText({ + displayName: 'Follow Up Date', + description: + 'Valid date format YYYY-MM-DD HH:MM:SS. If date is defined then new Follow Up Task will be created for this contact', + required: false, + }), + assignedUser: Property.ShortText({ + displayName: 'Assigned User', + description: + 'Preferably, Sperse User Email should be passed as it is unique within Sperse account but User Name can be also passed', + required: false, + }), + leadDealAmount: Property.Number({ + displayName: 'Deal Amount', + description: 'Estimated deal/opportunity amount.', + required: false, + }), + // tracking info + leadSource: Property.ShortText({ + displayName: 'Source Code', + description: + 'The first known source the contact used to find your website. You can set this automatically and update manually later.', + required: false, + }), + channelId: Property.ShortText({ + displayName: 'Channel Code', + description: 'The channel/medium the contact used to find your website.', + required: false, + }), + affiliateCode: Property.ShortText({ + displayName: 'Affiliate Code', + description: + 'The affiliate/referer partner through which the contact signed up.', + required: false, + }), + refererURL: Property.ShortText({ + displayName: 'Referer URL', + description: + 'The webpage where the contact clicked a link that sent them to your website.', + required: false, + }), + entryUrl: Property.ShortText({ + displayName: 'Entry URL', + description: + 'The first page of visit through which the contact visited your website.', + required: false, + }), + }, + async run(context) { + const contact = { + importType: context.propsValue.importType, + matchExisting: context.propsValue.matchExisting, + contactId: context.propsValue.contactId, + personalInfo: { + fullName: { + namePrefix: context.propsValue.namePrefix, + firstName: context.propsValue.firstName, + middleName: context.propsValue.middleName, + lastName: context.propsValue.lastName, + nameSuffix: context.propsValue.nameSuffix, + nickName: context.propsValue.nickName, + }, + doB: context.propsValue.dob, + mobilePhone: context.propsValue.mobilePhone, + homePhone: context.propsValue.homePhone, + ssn: context.propsValue.ssn, + bankCode: context.propsValue.bankCode, + email1: context.propsValue.email1, + email2: context.propsValue.email2, + gender: context.propsValue.gender, + fullAddress: { + street: context.propsValue.street, + addressLine2: context.propsValue.addressLine2, + city: context.propsValue.city, + stateName: context.propsValue.stateName, + stateId: context.propsValue.stateId, + zip: context.propsValue.zip, + countryName: context.propsValue.countryName, + countryId: context.propsValue.countryId, + }, + webSiteUrl: context.propsValue.webSiteUrl, + linkedInUrl: context.propsValue.linkedInUrl, + photoUrl: context.propsValue.photoUrl, + + experience: context.propsValue.experience, + profileSummary: context.propsValue.profileSummary, + }, + + businessInfo: { + companyName: context.propsValue.companyName, + jobTitle: context.propsValue.jobTitle, + industry: context.propsValue.industry, + workPhone1: context.propsValue.workPhone1, + workEmail1: context.propsValue.workEmail1, + }, + + assignedUser: context.propsValue.assignedUser, + followUpDate: context.propsValue.followUpDate, + notes: context.propsValue.notes, + leadDealAmount: context.propsValue.leadDealAmount, + + leadSource: context.propsValue.leadSource, + channelId: context.propsValue.channelId, + affiliateCode: context.propsValue.affiliateCode, + refererUrl: context.propsValue.refererURL, + entryUrl: context.propsValue.entryUrl, + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.base_url}/api/services/CRM/Import/ImportContact`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...contact, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/sperse/src/lib/actions/add-or-update-subscription.ts b/packages/pieces/community/sperse/src/lib/actions/add-or-update-subscription.ts new file mode 100644 index 0000000..b8b2929 --- /dev/null +++ b/packages/pieces/community/sperse/src/lib/actions/add-or-update-subscription.ts @@ -0,0 +1,110 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { sperseAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const addOrUpdateSubscription = createAction({ + name: 'addOrUpdateSubscription', + displayName: 'Add or Update Subscription', + description: 'Creates a new subscription.', + auth: sperseAuth, + props: { + contactId: Property.Number({ + displayName: 'Contact ID', + defaultValue: 0, + required: false, + }), + productId: Property.Number({ + displayName: 'Product ID', + required: false, + }), + contactXref: Property.ShortText({ + displayName: 'External Contact ID', + description: + 'ContactXref have to be specified and correct to look up the correct contact', + required: false, + }), + productCode: Property.ShortText({ + displayName: 'Product Code', + description: + 'Product Code (Unique product identifier). ProductCode have to be specified and correct to look up the correct product', + required: true, + }), + paymentPeriodType: Property.StaticDropdown({ + displayName: 'Payment Period Type', + description: + 'The chosen Period Type has to be set for the Product on Sperse side', + required: true, + defaultValue: 'Monthly', + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + ], + }, + }), + hasRecurringBilling: Property.StaticDropdown({ + displayName: 'Is it Recurring Billing', + required: false, + defaultValue: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + }, + async run(context) { + const subscription = { + contactId: context.propsValue.contactId, + contactXref: context.propsValue.contactXref, + products: [ + { + productId: context.propsValue.productId, + productCode: context.propsValue.productCode, + paymentPeriodType: context.propsValue.paymentPeriodType, + hasRecurringBilling: context.propsValue.hasRecurringBilling, + }, + ], + productId: context.propsValue.productId, + productCode: context.propsValue.productCode, + paymentPeriodType: context.propsValue.paymentPeriodType, + hasRecurringBilling: context.propsValue.hasRecurringBilling, + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: `${context.auth.base_url}/api/services/CRM/OrderSubscription/Update`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...subscription, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/sperse/src/lib/actions/create-invoice.ts b/packages/pieces/community/sperse/src/lib/actions/create-invoice.ts new file mode 100644 index 0000000..3d1d05f --- /dev/null +++ b/packages/pieces/community/sperse/src/lib/actions/create-invoice.ts @@ -0,0 +1,474 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { sperseAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +// Helper function to get current date in the required format +const getCurrentDateInISOFormat = () => { + return new Date().toISOString(); // Returns current date in format: YYYY-MM-DDTHH:MM:SSZ +}; + +export const createInvoice = createAction({ + name: 'createInvoice', + displayName: 'Create Invoice', + description: 'Creates a new invoice in the CRM.', + auth: sperseAuth, + props: { + contactId: Property.Number({ + displayName: 'Contact ID', + description: 'Sperse Contact ID. Will be used for looking a client', + defaultValue: 0, + required: false, + }), + contactXref: Property.ShortText({ + displayName: 'External Contact ID', + description: + 'External Contact Reference (ID) . Will be used for looking a client', + required: false, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + required: true, + defaultValue: 'Draft', + options: { + disabled: false, + options: [ + { + label: 'Draft', + value: 'Draft', + }, + { + label: 'Final', + value: 'Final', + }, + { + label: 'Paid', + value: 'Paid', + }, + { + label: 'Sent', + value: 'Sent', + }, + ], + }, + }), + invoiceNo: Property.Number({ + displayName: 'Invoice No.', + defaultValue: 0, + required: true, + }), + date: Property.DateTime({ + displayName: 'Date of the Invoice', + description: 'should be like this: 2024-06-11T11:11:41Z', + defaultValue: getCurrentDateInISOFormat(), + required: true, + }), + dueDate: Property.DateTime({ + displayName: 'Due Date of the Invoice', + description: 'should be like this: 2024-06-11T11:11:41Z', + required: true, + }), + currencyId: Property.StaticDropdown({ + displayName: 'Currency Id', + required: true, + defaultValue: 'USD', + options: { + disabled: false, + options: [ + { + label: 'USD', + value: 'USD', + }, + { + label: 'JPY', + value: 'JPY', + }, + { + label: 'IND', + value: 'IND', + }, + { + label: 'EUR', + value: 'EUR', + }, + { + label: 'GBP', + value: 'GBP', + }, + { + label: 'AUD', + value: 'AUD', + }, + { + label: 'CAD', + value: 'CAD', + }, + { + label: 'CHF', + value: 'CHF', + }, + { + label: 'CNY', + value: 'CNY', + }, + { + label: 'SEK', + value: 'SEK', + }, + { + label: 'NZD', + value: 'NZD', + }, + ], + }, + }), + grandTotal: Property.Number({ + displayName: 'Grand Total', + defaultValue: 0, + required: false, + }), + discountTotal: Property.Number({ + displayName: 'Discount Total', + defaultValue: 0, + required: false, + }), + shippingTotal: Property.Number({ + displayName: 'Shipping Total', + defaultValue: 0, + required: false, + }), + taxTotal: Property.Number({ + displayName: 'Tax Total', + defaultValue: 0, + required: false, + }), + // billing + bCompany: Property.ShortText({ + displayName: 'Billing Company', + required: false, + }), + bFirstName: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + bLastName: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + bPhone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + bEmail: Property.ShortText({ + displayName: 'Email', + required: false, + }), + bCountryId: Property.ShortText({ + displayName: 'Country Id', + required: false, + }), + bStateId: Property.ShortText({ + displayName: 'State Id', + required: false, + }), + bStateName: Property.ShortText({ + displayName: 'State Name', + required: false, + }), + bCity: Property.ShortText({ + displayName: 'City', + required: false, + }), + bZip: Property.ShortText({ + displayName: 'Zip', + required: false, + }), + bAddress1: Property.LongText({ + displayName: 'Billing Address 1', + required: false, + }), + bAddress2: Property.LongText({ + displayName: 'Billing Address 2', + required: false, + }), + // shipping + sCompany: Property.ShortText({ + displayName: 'Shipping Company', + required: false, + }), + sFirstName: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + sLastName: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + sPhone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + sEmail: Property.ShortText({ + displayName: 'Email', + required: false, + }), + sCountryId: Property.ShortText({ + displayName: 'Country Id', + required: false, + }), + sStateId: Property.ShortText({ + displayName: 'State Id', + required: false, + }), + sStateName: Property.ShortText({ + displayName: 'State Name', + required: false, + }), + sCity: Property.ShortText({ + displayName: 'City', + required: false, + }), + sZip: Property.ShortText({ + displayName: 'Zip', + required: false, + }), + sAddress1: Property.LongText({ + displayName: 'Shipping Address 1', + required: false, + }), + sAddress2: Property.LongText({ + displayName: 'Shipping Address 2', + required: false, + }), + // + note: Property.LongText({ + displayName: 'Invoice Note', + required: false, + }), + invoiceDescription: Property.LongText({ + displayName: 'Invoice Description', + required: true, + }), + // line + quantity: Property.Number({ + displayName: 'Quantity', + defaultValue: 0, + required: true, + }), + rate: Property.Number({ + displayName: 'Rate', + defaultValue: 0, + required: false, + }), + itemTotal: Property.Number({ + displayName: 'Total Item Price', + defaultValue: 0, + required: false, + }), + commissionableAmount: Property.Number({ + displayName: 'Commissionable Amount', + defaultValue: 0, + required: false, + }), + unitId: Property.StaticDropdown({ + displayName: 'Unit Id', + required: false, + defaultValue: 'Unit', + options: { + disabled: false, + options: [ + { + label: 'Unit', + value: 'Unit', + }, + { + label: 'Day', + value: 'Day', + }, + { + label: 'Month', + value: 'Month', + }, + { + label: 'Year', + value: 'Year', + }, + { + label: 'Hour', + value: 'Hour', + }, + { + label: 'Kilogram', + value: 'Kilogram', + }, + { + label: 'Zone', + value: 'Zone', + }, + { + label: 'Package', + value: 'Package', + }, + { + label: 'Pound', + value: 'Pound', + }, + { + label: 'Piece', + value: 'Piece', + }, + { + label: 'Feet', + value: 'Feet', + }, + { + label: 'Custom', + value: 'Custom', + }, + ], + }, + }), + productCode: Property.ShortText({ + displayName: 'Product Code', + description: 'Product Code. We will look up the product', + required: false, + }), + itemDescription: Property.ShortText({ + displayName: 'Description', + required: false, + }), + sortOrder: Property.Number({ + displayName: 'Sort Order', + defaultValue: 0, + required: false, + }), + // transactions + transactionDate: Property.DateTime({ + displayName: 'Transaction Date', + description: 'should be like this: 2024-06-11T11:11:41Z', + defaultValue: getCurrentDateInISOFormat(), + required: true, + }), + transactionDescription: Property.ShortText({ + displayName: 'Transaction Description', + required: false, + }), + amount: Property.Number({ + displayName: 'Amount', + defaultValue: 0, + required: false, + }), + gatewayName: Property.ShortText({ + displayName: 'Gateway Name', + required: false, + }), + gatewayTransactionId: Property.ShortText({ + displayName: 'Gateway Transaction Id', + required: false, + }), + historicalData: Property.StaticDropdown({ + displayName: 'Historical Data', + description: + 'Pass true if this is not actual transaction. Should be False by default', + required: true, + defaultValue: false, + options: { + disabled: false, + options: [ + { + label: 'true', + value: true, + }, + { + label: 'false', + value: false, + }, + ], + }, + }), + }, + async run(context) { + // construct + const invoice = { + contactId: context.propsValue.contactId, + contactXref: context.propsValue.contactXref, + status: context.propsValue.status, + number: context.propsValue.invoiceNo, + date: context.propsValue.date, + dueDate: context.propsValue.dueDate, + currencyId: context.propsValue.currencyId, + grandTotal: context.propsValue.grandTotal, + discountTotal: context.propsValue.discountTotal, + shippingTotal: context.propsValue.shippingTotal, + taxTotal: context.propsValue.taxTotal, + billingAddress: { + countryId: context.propsValue.bCountryId, + stateId: context.propsValue.bStateId, + stateName: context.propsValue.bStateName, + city: context.propsValue.bCity, + zip: context.propsValue.bZip, + address1: context.propsValue.bAddress1, + address2: context.propsValue.bAddress2, + firstName: context.propsValue.bFirstName, + lastName: context.propsValue.bLastName, + company: context.propsValue.bCompany, + email: context.propsValue.bEmail, + phone: context.propsValue.bPhone, + }, + shippingAddress: { + countryId: context.propsValue.sCountryId, + stateId: context.propsValue.sStateId, + stateName: context.propsValue.sStateName, + city: context.propsValue.sCity, + zip: context.propsValue.sZip, + address1: context.propsValue.sAddress1, + address2: context.propsValue.sAddress2, + firstName: context.propsValue.sFirstName, + lastName: context.propsValue.sLastName, + company: context.propsValue.sCompany, + email: context.propsValue.sEmail, + phone: context.propsValue.sPhone, + }, + description: context.propsValue.invoiceDescription, + note: context.propsValue.note, + lines: [ + { + quantity: context.propsValue.quantity, + rate: context.propsValue.rate, + total: context.propsValue.itemTotal, + commissionableAmount: context.propsValue.commissionableAmount, + unitId: context.propsValue.unitId, + productCode: context.propsValue.productCode, + description: context.propsValue.itemDescription, + sortOrder: context.propsValue.sortOrder, + }, + ], + transactions: [ + { + date: context.propsValue.transactionDate, + description: context.propsValue.transactionDescription, + amount: context.propsValue.amount, + gatewayName: context.propsValue.gatewayName, + gatewayTransactionId: context.propsValue.gatewayTransactionId, + }, + ], + historicalData: context.propsValue.historicalData, + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.base_url}/api/services/CRM/Import/ImportInvoice`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...invoice, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/sperse/src/lib/actions/create-product.ts b/packages/pieces/community/sperse/src/lib/actions/create-product.ts new file mode 100644 index 0000000..abc18eb --- /dev/null +++ b/packages/pieces/community/sperse/src/lib/actions/create-product.ts @@ -0,0 +1,318 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { sperseAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const createProduct = createAction({ + name: 'createProduct', + displayName: 'Create Product', + description: 'Creates a new product in the CRM', + auth: sperseAuth, + props: { + productType: Property.StaticDropdown({ + displayName: 'Product Type', + required: true, + defaultValue: 'General', + options: { + disabled: false, + options: [ + { + label: 'General', + value: 'General', + }, + { + label: 'Event', + value: 'Event', + }, + { + label: 'Subscription', + value: 'Subscription', + }, + { + label: 'Digital', + value: 'Digital', + }, + ], + }, + }), + name: Property.ShortText({ + displayName: 'Product Name', + required: true, + }), + code: Property.ShortText({ + displayName: 'SKU', + required: true, + description: 'Sku is the product code', + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + descriptionHTML: Property.LongText({ + displayName: 'Description HTML', + required: false, + description: 'Javascript and media tags are not allowed', + }), + logoURL: Property.LongText({ + displayName: 'Logo Url', + required: false, + }), + groupName: Property.ShortText({ + displayName: 'Group Name', + required: false, + }), + price: Property.Number({ + displayName: 'Price', + required: false, + description: 'Required for General , Digital and Event Product Type', + defaultValue: 0, + }), + currencyId: Property.StaticDropdown({ + displayName: 'Currency Id', + required: false, + defaultValue: 'USD', + options: { + disabled: false, + options: [ + { + label: 'USD', + value: 'USD', + }, + { + label: 'JPY', + value: 'JPY', + }, + { + label: 'IND', + value: 'IND', + }, + { + label: 'EUR', + value: 'EUR', + }, + { + label: 'GBP', + value: 'GBP', + }, + { + label: 'AUD', + value: 'AUD', + }, + { + label: 'CAD', + value: 'CAD', + }, + { + label: 'CHF', + value: 'CHF', + }, + { + label: 'CNY', + value: 'CNY', + }, + { + label: 'SEK', + value: 'SEK', + }, + { + label: 'NZD', + value: 'NZD', + }, + ], + }, + }), + unit: Property.StaticDropdown({ + displayName: 'Unit', + required: false, + description: 'Required for General and Digital Product Type', + defaultValue: 'Day', + options: { + disabled: false, + options: [ + { + label: 'Day', + value: 'Day', + }, + { + label: 'Feet', + value: 'Feet', + }, + { + label: 'Hour', + value: 'Hour', + }, + { + label: 'Kilogram', + value: 'Kilogram', + }, + { + label: 'Pound', + value: 'Pound', + }, + { + label: 'Month', + value: 'Month', + }, + { + label: 'Package', + value: 'Package', + }, + { + label: 'Piece', + value: 'Piece', + }, + { + label: 'Unit', + value: 'Unit', + }, + { + label: 'Year', + value: 'Year', + }, + { + label: 'Zone', + value: 'Zone', + }, + { + label: 'Custom', + value: 'Custom', + }, + ], + }, + }), + frequency: Property.StaticDropdown({ + displayName: 'Payment Cycle', + required: false, + description: 'Required for General and Digital Product Type', + defaultValue: 'Monthly', + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + { + label: 'OneTime', + value: 'OneTime', + }, + { + label: 'Custom', + value: 'Custom', + }, + ], + }, + }), + fee: Property.Number({ + displayName: 'Subscription fee', + required: false, + defaultValue: 0, + }), + cycles: Property.Number({ + displayName: 'No of cycles', + required: false, + description: 'Required for all except LifeTime or OneTime plan', + defaultValue: 0, + }), + signupFee: Property.Number({ + displayName: 'Signup fee', + required: false, + description: 'Required for all except LifeTime or OneTime plan', + defaultValue: 0, + }), + customPeriodType: Property.StaticDropdown({ + displayName: 'Custom Period Type', + required: false, + description: 'Required for Custom or OneTime Plan', + defaultValue: 'Days', + options: { + disabled: false, + options: [ + { + label: 'Days', + value: 'Days', + }, + { + label: 'Weeks', + value: 'Weeks', + }, + { + label: 'Months', + value: 'Months', + }, + { + label: 'Years', + value: 'Years', + }, + ], + }, + }), + customPeriodCount: Property.Number({ + displayName: 'Custom No of Period', + required: false, + description: 'Required for Custom or OneTime Plan', + defaultValue: 0, + }), + trialDayCount: Property.Number({ + displayName: 'Custom No of Period', + required: false, + description: 'Required for OneTime plan', + defaultValue: 0, + }), + gracePeriodDayCount: Property.Number({ + displayName: 'Grace Period Count', + required: false, + defaultValue: 0, + }), + }, + async run(context) { + const product = { + code: context.propsValue.code, + name: context.propsValue.name, + logoUrl: context.propsValue.logoURL, + description: context.propsValue.description, + descriptionHtml: context.propsValue.descriptionHTML, + groupName: context.propsValue.groupName, + type: context.propsValue.productType, + price: context.propsValue.price, + currencyId: context.propsValue.currencyId, + unit: context.propsValue.unit, + productSubscriptionOptions: [ + { + frequency: context.propsValue.frequency, + signupFee: context.propsValue.signupFee, + fee: context.propsValue.fee, + trialDayCount: context.propsValue.trialDayCount, + customPeriodCount: context.propsValue.customPeriodCount, + customPeriodType: context.propsValue.customPeriodType, + cycles: context.propsValue.cycles, + gracePeriodDayCount: context.propsValue.gracePeriodDayCount, + }, + ], + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.base_url}/api/services/CRM/Import/ImportProduct`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...product, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/sperse/src/lib/actions/get-contact-details.ts b/packages/pieces/community/sperse/src/lib/actions/get-contact-details.ts new file mode 100644 index 0000000..792c31f --- /dev/null +++ b/packages/pieces/community/sperse/src/lib/actions/get-contact-details.ts @@ -0,0 +1,73 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { sperseAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getContactDetails = createAction({ + name: 'getContactDetails', + displayName: 'Get Contact Details', + description: 'Get Contact Details', + auth: sperseAuth, + props: { + contactId: Property.Number({ + displayName: 'Contact Id', + required: false, + }), + xref: Property.ShortText({ + displayName: 'Contact Xref', + required: false, + }), + affiliateCode: Property.ShortText({ + displayName: 'Affiliate Code', + required: false, + }), + userId: Property.Number({ + displayName: 'User Id', + required: false, + description: 'Id of the logged in user (not contact id)', + }), + userEmail: Property.LongText({ + displayName: 'User Email', + required: false, + description: 'Email of the logged in user', + }), + }, + async run(context) { + const contact = { + contactId: context.propsValue.contactId, + xref: context.propsValue.xref, + affiliateCode: context.propsValue.affiliateCode, + userId: context.propsValue.userId, + userEmail: context.propsValue.userEmail, + }; + + // Filter out keys with undefined values + const filteredContact: Record = Object.fromEntries( + Object.entries(contact).filter(([, value]) => value !== undefined) + ) as Record; // Cast to ensure it's the correct type + + // Create query parameters from the filtered contact object + const queryParams = new URLSearchParams( + Object.entries(filteredContact).map(([key, value]) => [ + key, + String(value), + ]) + ); + + // Send GET request with query parameters in the URL + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${ + context.auth.base_url + }/api/services/CRM/Contact/GetContactData?${queryParams.toString()}`, + headers: { + 'api-key': context.auth.api_key, + 'Content-Type': 'application/json', + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/sperse/src/lib/common/common.ts b/packages/pieces/community/sperse/src/lib/common/common.ts new file mode 100644 index 0000000..23184d5 --- /dev/null +++ b/packages/pieces/community/sperse/src/lib/common/common.ts @@ -0,0 +1,46 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +export const sperseCommon = { + subscribeWebhook: async ( + eventName: string, + baseUrl: string, + apiKey: string, + webhookUrl: string + ) => { + const request = { + method: HttpMethod.POST, + url: `${baseUrl}/api/services/Platform/Event/Subscribe`, + headers: { + 'api-key': apiKey, + 'Content-Type': 'application/json', + }, + body: { + eventName: eventName, + targetUrl: webhookUrl, + }, + }; + + const res = await httpClient.sendRequest(request); + + const { id: webhookId } = res.body.result; + + return webhookId; + }, + + unsubscribeWebhook: async ( + baseUrl: string, + apiKey: string, + webhookId: number + ) => { + const request = { + method: HttpMethod.POST, + url: `${baseUrl}/api/services/Platform/Event/Unsubscribe?id=${webhookId}`, + headers: { + 'api-key': apiKey, + 'Content-Type': 'application/json', + }, + }; + + return await httpClient.sendRequest(request); + }, +}; diff --git a/packages/pieces/community/sperse/src/lib/triggers/new-lead.ts b/packages/pieces/community/sperse/src/lib/triggers/new-lead.ts new file mode 100644 index 0000000..814c2fe --- /dev/null +++ b/packages/pieces/community/sperse/src/lib/triggers/new-lead.ts @@ -0,0 +1,231 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { sperseAuth } from '../..'; +import { sperseCommon } from '../common/common'; + +export const newLead = createTrigger({ + auth: sperseAuth, + name: 'new_lead', + displayName: 'New Lead', + description: 'Triggers when a new lead is created', + props: {}, + type: TriggerStrategy.WEBHOOK, + sampleData: { + id: 100500, + group: 'P', + personalInfo: { + fullName: { + namePrefix: 'Mr', + firstName: 'John', + middleName: 'G', + lastName: 'Smith', + nameSuffix: 'Jr', + nickName: 'Johnny', + }, + doB: '1992-06-30T00:00:00Z', + mobilePhone: '+12057899877', + mobilePhoneExt: '123', + homePhone: '+12057985632', + homePhoneExt: '123', + phone1: null, + phoneExt1: null, + phone2: null, + phoneExt2: null, + preferredToD: 'Anytime', + timeZone: 'Pacific Standard Time', + ssn: '123456456', + bankCode: 'BANK', + email1: 'personalemail1@gmal.com', + email2: 'personalemail2@hotmail.com', + email3: 'personalemail3@yahoo.com', + email4: null, + email5: null, + drivingLicense: 'ASDF4566G', + drivingLicenseState: 'NY', + isActiveMilitaryDuty: true, + gender: 'Male', + fullAddress: { + street: '999-901 Emancipation Ave', + addressLine2: null, + neighborhood: null, + city: 'Houston', + stateName: 'Texas', + stateId: 'TX', + zip: '77003', + countryName: 'United States of America', + countryId: 'US', + startDate: '2018-04-30T00:00:00Z', + isOwner: true, + }, + fullAddress2: null, + fullAddress3: null, + isUSCitizen: true, + webSiteUrl: 'www.myprofile.com', + facebookUrl: 'www.fb.com/j.smith', + linkedInUrl: 'https://www.linkedin.com/j.smith', + instagramUrl: 'https://www.instagram.com/j.smith', + twitterUrl: 'https://twitter.com/j.smith', + googlePlusUrl: 'https://googleplus.com/j.smith', + angelListUrl: 'https://angel.co/', + zoomUrl: 'https://zoom.com', + otherLinkUrl: 'https://otherlink.com', + photoUrl: 'https://www.myprofile.com/profile-pictures/myphoto.png', + experience: + 'Improvements made to two existing products, designed five completely new products for four customers.', + profileSummary: + 'Highly skilled and results-oriented professional with solid academic preparation holding a Juris Doctor degree and extensive experience in intelligence and special operations seeks position in risk management.', + interests: ['World Economy', 'Baseball'], + affiliateCode: 'PA001', + isActive: null, + customFields: { + customField1: null, + customField2: null, + customField3: null, + customField4: null, + customField5: null, + }, + }, + businessInfo: { + companyName: 'MyCompany LLC', + organizationType: 'Inc', + jobTitle: 'Chief Executive Officer', + isEmployed: true, + employmentStartDate: '2021-12-30T00:00:00Z', + employeeCount: 500, + dateFounded: '2021-06-30T00:00:00Z', + ein: '35-8896524', + annualRevenue: 6000000.0, + industry: 'Sport and Entertainment', + companyPhone: '+12057784563', + companyPhoneExt: '123', + companyFaxNumber: '+12057324598', + companyEmail: 'companyemail1@company.com', + companyFullAddress: { + street: '1500 Canton st', + addressLine2: null, + neighborhood: null, + city: 'Dallas', + stateName: 'Texas', + stateId: 'TX', + zip: '75201', + countryName: 'United States of America', + countryId: 'US', + startDate: '2018-04-30T00:00:00Z', + isOwner: false, + }, + companyWebSiteUrl: 'www.mycompany.com', + companyFacebookUrl: 'www.fb.com/mycompany', + companyLinkedInUrl: 'https://www.linkedin.com/mycompany', + companyInstagramUrl: 'https://www.instagram.com/mycompany', + companyTwitterUrl: 'https://twitter.com/mycompany', + companyGooglePlusUrl: 'https://googleplus.com/mycompany', + companyCrunchbaseUrl: 'https://www.crunchbase.com/mycompany', + companyBBBUrl: 'https://www.bbb.org/en/us/overview-of-bbb-ratings', + companyPinterestUrl: 'https://www.pinterest.com/mycompany', + companyDomainUrl: 'https://www.domain.com/mycompany', + companyAlexaUrl: 'https://www.alexa.com/mycompany', + companyOpenCorporatesUrl: 'https://www.opencorporates.com/mycompany', + companyGlassDoorUrl: 'https://www.classdoor.com/mycompany', + companyTrustpilotUrl: 'https://www.trustpilot.com/mycompany', + companyFollowersUrl: 'https://www.followers.com/mycompany', + companyYoutubeUrl: 'https://www.youtube.com/mycompany', + companyYelpUrl: 'https://www.yelp.com/mycompany', + companyRSSUrl: 'https://www.rss.com/mycompany', + companyNavUrl: 'https://www.nav.com/mycompany', + companyAngelListUrl: 'https://www.angelist.com/mycompany', + companyCalendlyUrl: 'https://www.calendly.com/mycompany', + companyZoomUrl: 'https://zoom.com/mycompany', + companyOtherLinkUrl: 'https://www.otherlink.com/mycompany', + companyLogoUrl: 'https://www.mycompany.com/images/companylogo/logo.png', + workPhone1: '+12057412354', + workPhone1Ext: '123', + workPhone2: '+12057741236', + workPhone2Ext: '123', + workEmail1: 'workemail1@company.com', + workEmail2: 'workemail2@company.com', + workEmail3: 'workemail3@company.com', + workFullAddress: { + street: '1502-1702 Strawberry Rd', + addressLine2: null, + neighborhood: null, + city: 'Pasadena', + stateName: 'Texas', + stateId: 'TX', + zip: '77502', + countryName: 'United States of America', + countryId: 'US', + startDate: '2020-05-30T00:00:00Z', + isOwner: false, + }, + affiliateCode: 'CA0001', + }, + dateCreated: '2022-06-23T13:32:54.9451405Z', + ipAddress: '192.168.0.1', + trackingInfo: { + sourceCode: 'Code', + channelCode: 'ChannelCode', + affiliateCode: '414CODE', + refererUrl: 'http://www.refererurl.com/referpage.html', + entryUrl: 'https://entryurl.com/start-now/?ref=wow', + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36', + clientIp: '192.168.0.1', + }, + applicationInfo: { + applicationId: '771XYZ23234G', + applicantId: '771XYZSD', + applicantUserId: 10229, + clickId: '2B69283E-9433-4CE6-A24E-D8809C050B70', + requestedLoanAmount: 89.0, + incomeType: 'Benefits', + netMonthlyIncome: 899.0, + payFrequency: 'Monthly', + payNextDate: '2022-07-30T13:32:54.945358Z', + bankName: 'Bank Name', + bankAccountType: 'Checking', + monthsAtBank: 24, + bankAccountNumber: '26005325777412', + creditScoreRating: 'Excellent', + loanReason: 'AutoPurchase', + creditCardDebtAmount: 56898.0, + }, + classificationInfo: { + rating: '10', + lists: ['My List 1', 'My List 2', 'My List 3'], + tags: ['My Tag 1', 'My Tag 2', 'My Tag 3'], + partnerTypeName: 'My Partner Type', + }, + eventTime: '2022-06-30T13:32:54Z', + }, + async onEnable(context) { + const webhookId = await sperseCommon.subscribeWebhook( + 'LeadCreated', + context.auth.base_url, + context.auth.api_key, + context.webhookUrl + ); + + await context.store?.put('_new_lead_trigger', { + webhookId: webhookId, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_new_lead_trigger' + ); + + if (response !== null && response !== undefined) { + await sperseCommon.unsubscribeWebhook( + context.auth.base_url, + context.auth.api_key, + response.webhookId + ); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); + +interface WebhookInformation { + webhookId: number; +} diff --git a/packages/pieces/community/sperse/src/lib/triggers/new-payment.ts b/packages/pieces/community/sperse/src/lib/triggers/new-payment.ts new file mode 100644 index 0000000..0d1fef9 --- /dev/null +++ b/packages/pieces/community/sperse/src/lib/triggers/new-payment.ts @@ -0,0 +1,85 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { sperseAuth } from '../..'; +import { sperseCommon } from '../common/common'; + +export const newPayment = createTrigger({ + auth: sperseAuth, + name: 'new_payment', + displayName: 'New Payment', + description: 'Triggers when a new payment is created', + props: {}, + type: TriggerStrategy.WEBHOOK, + sampleData: { + contact: { + email: 's@gmail.com', + fullName: 'Test User', + id: 636, + phone: '98877676565', + }, + invoice: { + currencyId: 'USD', + date: '2024-06-10T00:00:00Z', + description: 'just a description', + discountTotal: 0, + grandTotal: 100, + id: 457, + number: 'INV - 20240610 - 746C', + shippingTotal: 0, + taxTotal: 0, + lines: [ + { + description: 'Me Spacial', + productId: 810, + quantity: 1, + rate: 100, + total: 100, + unitId: 'Month', + }, + ], + }, + transaction: { + amount: 100, + currencyId: 'USD', + date: '2024-06-10T09:36:01.832Z', + gatewayName: null, + gatewayTransactionId: null, + id: 403, + isSuccessful: true, + type: 'Sale', + }, + eventTime: '2024-06-10T09:36:08', + eventType: 'Payment.Created', + }, + async onEnable(context) { + const webhookId = await sperseCommon.subscribeWebhook( + 'Payment.Created', + context.auth.base_url, + context.auth.api_key, + context.webhookUrl + ); + + await context.store?.put('_new_payment_trigger', { + webhookId: webhookId, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_new_payment_trigger' + ); + + if (response !== null && response !== undefined) { + await sperseCommon.unsubscribeWebhook( + context.auth.base_url, + context.auth.api_key, + response.webhookId + ); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); + +interface WebhookInformation { + webhookId: number; +} diff --git a/packages/pieces/community/sperse/src/lib/triggers/new-subscription.ts b/packages/pieces/community/sperse/src/lib/triggers/new-subscription.ts new file mode 100644 index 0000000..8cafdd1 --- /dev/null +++ b/packages/pieces/community/sperse/src/lib/triggers/new-subscription.ts @@ -0,0 +1,61 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { sperseAuth } from '../..'; +import { sperseCommon } from '../common/common'; + +export const newSubscription = createTrigger({ + auth: sperseAuth, + name: 'new_subscription', + displayName: 'New Subscription', + description: 'Triggers when a new subscription is created', + props: {}, + type: TriggerStrategy.WEBHOOK, + sampleData: { + eventType: 'Subscription.CreatedOrUpdated', + contactId: 3994, + fullname: 'Frank Micheal', + email: 'tray@sperse.com', + id: '536778', + name: 'Subscription_Name', + startDate: '2024-06-30T09:29:43.6271352Z', + endDate: '2025-06-30T09:29:43.6271352Z', + amount: 100, + frequency: 'Annual', + trialDayCount: '4', + gracePeriodCount: '10', + statusId: 'A', + cancelationReason: '', + eventTime: '2024-09-06T00:29:07', + }, + async onEnable(context) { + const webhookId = await sperseCommon.subscribeWebhook( + 'Subscription.CreatedOrUpdated', + context.auth.base_url, + context.auth.api_key, + context.webhookUrl + ); + + await context.store?.put('_new_subscription_trigger', { + webhookId: webhookId, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_new_subscription_trigger' + ); + + if (response !== null && response !== undefined) { + await sperseCommon.unsubscribeWebhook( + context.auth.base_url, + context.auth.api_key, + response.webhookId + ); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); + +interface WebhookInformation { + webhookId: number; +} diff --git a/packages/pieces/community/sperse/tsconfig.json b/packages/pieces/community/sperse/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/sperse/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/sperse/tsconfig.lib.json b/packages/pieces/community/sperse/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/sperse/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/spotify/.eslintrc.json b/packages/pieces/community/spotify/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/spotify/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/spotify/README.md b/packages/pieces/community/spotify/README.md new file mode 100644 index 0000000..ba8f240 --- /dev/null +++ b/packages/pieces/community/spotify/README.md @@ -0,0 +1,7 @@ +# pieces-spotify + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-spotify` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/spotify/package.json b/packages/pieces/community/spotify/package.json new file mode 100644 index 0000000..56854a0 --- /dev/null +++ b/packages/pieces/community/spotify/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-spotify", + "version": "0.3.6" +} \ No newline at end of file diff --git a/packages/pieces/community/spotify/project.json b/packages/pieces/community/spotify/project.json new file mode 100644 index 0000000..2d68dd4 --- /dev/null +++ b/packages/pieces/community/spotify/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-spotify", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/spotify/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/spotify", + "tsConfig": "packages/pieces/community/spotify/tsconfig.lib.json", + "packageJson": "packages/pieces/community/spotify/package.json", + "main": "packages/pieces/community/spotify/src/index.ts", + "assets": [ + "packages/pieces/community/spotify/*.md", + { + "input": "packages/pieces/community/spotify/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/spotify/src/index.ts b/packages/pieces/community/spotify/src/index.ts new file mode 100644 index 0000000..6161b11 --- /dev/null +++ b/packages/pieces/community/spotify/src/index.ts @@ -0,0 +1,30 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + createPiece, +} from '@activepieces/pieces-framework'; +import actions from './lib/actions'; +import { spotifyCommon } from './lib/common'; +import triggers from './lib/triggers'; + +export const spotify = createPiece({ + displayName: 'Spotify', + description: 'Music for everyone', + + auth: spotifyCommon.authentication, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/spotify.png', + categories: [], + authors: ["JanHolger","kishanprmr","MoShizzle","abuaboud","jerboa88"], + actions: [ + ...actions, + createCustomApiCallAction({ + baseUrl: () => 'https://api.spotify.com/v1', + auth: spotifyCommon.authentication, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers, +}); diff --git a/packages/pieces/community/spotify/src/lib/actions/add-playlist-items.ts b/packages/pieces/community/spotify/src/lib/actions/add-playlist-items.ts new file mode 100644 index 0000000..acdbe25 --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/actions/add-playlist-items.ts @@ -0,0 +1,30 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { spotifyCommon, makeClient } from '../common'; + +export default createAction({ + name: 'add_playlist_items', + auth: spotifyCommon.authentication, + displayName: 'Add items to playlist', + description: 'Adds tracks or episodes to the playlist', + props: { + playlist_id: spotifyCommon.playlist_id(true), + items: Property.Array({ + displayName: 'Items', + description: "URI's of the items to add", + required: true, + }), + position: Property.Number({ + displayName: 'Position', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient({ + auth, + }); + await client.addItemsToPlaylist(propsValue.playlist_id as string, { + uris: propsValue.items as string[], + position: propsValue.position, + }); + }, +}); diff --git a/packages/pieces/community/spotify/src/lib/actions/create-playlist.ts b/packages/pieces/community/spotify/src/lib/actions/create-playlist.ts new file mode 100644 index 0000000..4b90b06 --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/actions/create-playlist.ts @@ -0,0 +1,38 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { spotifyCommon, makeClient } from '../common'; + +export default createAction({ + name: 'create_playlist', + displayName: 'Create Playlist', + description: 'Creates a new playlist for the current user', + auth: spotifyCommon.authentication, + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + description: Property.ShortText({ + displayName: 'Description', + required: false, + }), + public: Property.Checkbox({ + displayName: 'Public', + required: false, + }), + collaborative: Property.Checkbox({ + displayName: 'Collaborative', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient({ auth }); + const user = await client.getCurrentUser(); + const res = await client.createPlaylist(user.id, { + name: propsValue.name, + description: propsValue.description, + public: propsValue.public, + collaborative: propsValue.collaborative, + }); + return res; + }, +}); diff --git a/packages/pieces/community/spotify/src/lib/actions/get-playback-state.ts b/packages/pieces/community/spotify/src/lib/actions/get-playback-state.ts new file mode 100644 index 0000000..0be898a --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/actions/get-playback-state.ts @@ -0,0 +1,15 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { spotifyCommon, makeClient } from '../common'; + +export default createAction({ + name: 'get_playback_state', + displayName: 'Get Playback State', + description: 'Retrieves the current playback state of the player', + auth: spotifyCommon.authentication, + props: {}, + async run({ auth }) { + const client = makeClient({ auth }); + const res = await client.getPlaybackState(); + return res; + }, +}); diff --git a/packages/pieces/community/spotify/src/lib/actions/get-playlist-info.ts b/packages/pieces/community/spotify/src/lib/actions/get-playlist-info.ts new file mode 100644 index 0000000..c13d8f1 --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/actions/get-playlist-info.ts @@ -0,0 +1,16 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { spotifyCommon, makeClient } from '../common'; + +export default createAction({ + name: 'get_playlist_info', + displayName: 'Get Playlist Info', + description: 'Retrieves details of a playlist', + auth: spotifyCommon.authentication, + props: { + playlist_id: spotifyCommon.playlist_id(true), + }, + async run({ auth, propsValue }) { + const client = makeClient({ auth }); + return await client.getPlaylist(propsValue.playlist_id as string); + }, +}); diff --git a/packages/pieces/community/spotify/src/lib/actions/get-playlist-items.ts b/packages/pieces/community/spotify/src/lib/actions/get-playlist-items.ts new file mode 100644 index 0000000..5d37364 --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/actions/get-playlist-items.ts @@ -0,0 +1,38 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { spotifyCommon, makeClient } from '../common'; + +export default createAction({ + name: 'get_playlist_items', + displayName: 'Get Playlist Items', + description: 'Retrieves the list of items in the playlist', + auth: spotifyCommon.authentication, + props: { + playlist_id: spotifyCommon.playlist_id(true), + offset: Property.Number({ + displayName: 'Offset', + required: false, + }), + limit: Property.Number({ + displayName: 'Limit', + required: false, + }), + all: Property.Checkbox({ + displayName: 'All', + description: 'Fetches all items in a single request', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient({ auth }); + if (propsValue.all) { + const items = await client.getAllPlaylistItems( + propsValue.playlist_id as string + ); + return { total: items.length, items }; + } + return await client.getPlaylistItems(propsValue.playlist_id as string, { + limit: propsValue.limit, + offset: propsValue.offset, + }); + }, +}); diff --git a/packages/pieces/community/spotify/src/lib/actions/get-playlists.ts b/packages/pieces/community/spotify/src/lib/actions/get-playlists.ts new file mode 100644 index 0000000..bd7f16e --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/actions/get-playlists.ts @@ -0,0 +1,35 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { spotifyCommon, makeClient } from '../common'; + +export default createAction({ + name: 'get_playlists', + displayName: 'Get Playlists', + description: 'Retrieves the list of playlists that you created or followed', + auth: spotifyCommon.authentication, + props: { + offset: Property.Number({ + displayName: 'Offset', + required: false, + }), + limit: Property.Number({ + displayName: 'Limit', + required: false, + }), + all: Property.Checkbox({ + displayName: 'All', + description: 'Fetches all playlists in a single request', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient({ auth }); + if (propsValue.all) { + const items = await client.getAllCurrentUserPlaylists(); + return { total: items.length, items }; + } + return await client.getCurrentUserPlaylists({ + limit: propsValue.limit, + offset: propsValue.offset, + }); + }, +}); diff --git a/packages/pieces/community/spotify/src/lib/actions/get-saved-tracks.ts b/packages/pieces/community/spotify/src/lib/actions/get-saved-tracks.ts new file mode 100644 index 0000000..137f4b2 --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/actions/get-saved-tracks.ts @@ -0,0 +1,35 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { spotifyCommon, makeClient } from '../common'; + +export default createAction({ + name: 'get_saved_tracks', + displayName: 'Get Saved Tracks', + description: 'Retrieves the list of saved tracks for the current user', + auth: spotifyCommon.authentication, + props: { + offset: Property.Number({ + displayName: 'Offset', + required: false, + }), + limit: Property.Number({ + displayName: 'Limit', + required: false, + }), + all: Property.Checkbox({ + displayName: 'All', + description: 'Fetches all items in a single request', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient({ auth }); + if (propsValue.all) { + const items = await client.getAllSavedTracks(); + return { total: items.length, items }; + } + return await client.getSavedTracks({ + limit: propsValue.limit, + offset: propsValue.offset, + }); + }, +}); diff --git a/packages/pieces/community/spotify/src/lib/actions/index.ts b/packages/pieces/community/spotify/src/lib/actions/index.ts new file mode 100644 index 0000000..6b7225d --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/actions/index.ts @@ -0,0 +1,31 @@ +import search from './search'; +import getPlaybackState from './get-playback-state'; +import setVolume from './set-volume'; +import play from './play'; +import pause from './pause'; +import getPlaylistInfo from './get-playlist-info'; +import createPlaylist from './create-playlist'; +import updatePlaylist from './update-playlist'; +import addPlaylistItems from './add-playlist-items'; +import removePlaylistItems from './remove-playlist-items'; +import getPlaylistItems from './get-playlist-items'; +import getSavedTracks from './get-saved-tracks'; +import reorderPlaylist from './reorder-playlist'; +import getPlaylists from './get-playlists'; + +export default [ + search, + getPlaybackState, + play, + pause, + setVolume, + getPlaylists, + getPlaylistInfo, + getPlaylistItems, + getSavedTracks, + createPlaylist, + updatePlaylist, + addPlaylistItems, + removePlaylistItems, + reorderPlaylist, +]; diff --git a/packages/pieces/community/spotify/src/lib/actions/pause.ts b/packages/pieces/community/spotify/src/lib/actions/pause.ts new file mode 100644 index 0000000..87698ce --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/actions/pause.ts @@ -0,0 +1,19 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { spotifyCommon, makeClient } from '../common'; + +export default createAction({ + name: 'pause', + displayName: 'Pause', + description: 'Pauses the playback', + auth: spotifyCommon.authentication, + props: { + device_id: spotifyCommon.device_id(false), + }, + async run({ auth, propsValue }) { + const client = makeClient({ auth }); + const res = await client.pause({ + device_id: propsValue.device_id, + }); + return res; + }, +}); diff --git a/packages/pieces/community/spotify/src/lib/actions/play.ts b/packages/pieces/community/spotify/src/lib/actions/play.ts new file mode 100644 index 0000000..65b9351 --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/actions/play.ts @@ -0,0 +1,38 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { spotifyCommon, makeClient } from '../common'; + +export default createAction({ + name: 'play', + displayName: 'Play / Resume', + description: 'Resumes or starts playback', + auth: spotifyCommon.authentication, + props: { + device_id: spotifyCommon.device_id(false), + context_uri: Property.ShortText({ + displayName: 'Context URI', + description: + 'Spotify URI of the context to play (album, artist, playlist)', + required: false, + }), + tracks: Property.Array({ + displayName: 'Tracks', + description: 'List of spotify track uris to play', + required: false, + }), + position_ms: Property.Number({ + displayName: 'Position', + description: 'Position in milliseconds', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient({ auth }); + const res = await client.play({ + device_id: propsValue.device_id, + context_uri: propsValue.context_uri, + uris: propsValue.tracks as string[], + position_ms: propsValue.position_ms, + }); + return res; + }, +}); diff --git a/packages/pieces/community/spotify/src/lib/actions/remove-playlist-items.ts b/packages/pieces/community/spotify/src/lib/actions/remove-playlist-items.ts new file mode 100644 index 0000000..7d8bd2d --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/actions/remove-playlist-items.ts @@ -0,0 +1,23 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { spotifyCommon, makeClient } from '../common'; + +export default createAction({ + name: 'remove_playlist_items', + displayName: 'Remove items from playlist', + description: 'Removes tracks or episodes from the playlist', + auth: spotifyCommon.authentication, + props: { + playlist_id: spotifyCommon.playlist_id(true), + items: Property.Array({ + displayName: 'Items', + description: "URI's of the items to remove", + required: true, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient({ auth }); + await client.removeItemsFromPlaylist(propsValue.playlist_id as string, { + tracks: propsValue.items.map((uri) => ({ uri: uri as string })), + }); + }, +}); diff --git a/packages/pieces/community/spotify/src/lib/actions/reorder-playlist.ts b/packages/pieces/community/spotify/src/lib/actions/reorder-playlist.ts new file mode 100644 index 0000000..80a9c88 --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/actions/reorder-playlist.ts @@ -0,0 +1,32 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { spotifyCommon, makeClient } from '../common'; + +export default createAction({ + name: 'reorder_playlist', + displayName: 'Reorder playlist', + description: 'Reorders items in the playlist', + auth: spotifyCommon.authentication, + props: { + playlist_id: spotifyCommon.playlist_id(true), + from_position: Property.Number({ + displayName: 'From Position', + required: true, + }), + to_position: Property.Number({ + displayName: 'To Position', + required: true, + }), + amount: Property.Number({ + displayName: 'Amount of Items', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient({ auth }); + await client.reorderPlaylist(propsValue.playlist_id as string, { + range_start: propsValue.from_position, + range_length: propsValue.amount, + insert_before: propsValue.to_position, + }); + }, +}); diff --git a/packages/pieces/community/spotify/src/lib/actions/search.ts b/packages/pieces/community/spotify/src/lib/actions/search.ts new file mode 100644 index 0000000..41d7063 --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/actions/search.ts @@ -0,0 +1,52 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { spotifyCommon, makeClient } from '../common'; + +export default createAction({ + name: 'search', + displayName: 'Search', + description: 'Searches for tracks, artists, albums, etc.', + auth: spotifyCommon.authentication, + props: { + search_text: Property.ShortText({ + displayName: 'Search Text', + description: 'The word or phrase you are searching for', + required: true, + }), + types: Property.StaticMultiSelectDropdown({ + displayName: 'Object Types', + required: true, + options: { + options: [ + { label: 'Albums', value: '' }, + { label: 'Artists', value: 'artist' }, + { label: 'Playlists', value: 'playlist' }, + { label: 'Tracks', value: 'track' }, + ], + }, + }), + limit: Property.Number({ + displayName: 'Limit', + required: false, + }), + offset: Property.Number({ + displayName: 'Offset', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient({ auth }); + const res = await client.search({ + q: propsValue.search_text, + type: propsValue.types.join(','), + limit: propsValue.limit, + offset: propsValue.offset, + }); + const result = { + tracks: res.tracks?.items, + artists: res.artists?.items, + albums: res.albums?.items, + playlists: res.playlists?.items, + }; + return result; + }, +}); diff --git a/packages/pieces/community/spotify/src/lib/actions/set-volume.ts b/packages/pieces/community/spotify/src/lib/actions/set-volume.ts new file mode 100644 index 0000000..d1e72eb --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/actions/set-volume.ts @@ -0,0 +1,25 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { spotifyCommon, makeClient } from '../common'; + +export default createAction({ + name: 'set_volume', + displayName: 'Set Volume', + auth: spotifyCommon.authentication, + description: 'Sets the volume of the player', + props: { + volume: Property.Number({ + displayName: 'Volume', + description: 'Volume from 0 to 100', + required: true, + }), + device_id: spotifyCommon.device_id(false), + }, + async run({ auth, propsValue }) { + const client = makeClient({ auth }); + const res = await client.setVolume({ + volume_percent: propsValue.volume, + device_id: propsValue.device_id, + }); + return res; + }, +}); diff --git a/packages/pieces/community/spotify/src/lib/actions/update-playlist.ts b/packages/pieces/community/spotify/src/lib/actions/update-playlist.ts new file mode 100644 index 0000000..4d152fe --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/actions/update-playlist.ts @@ -0,0 +1,37 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { spotifyCommon, makeClient } from '../common'; + +export default createAction({ + name: 'update_playlist', + displayName: 'Update Playlist', + description: 'Updates details of the playlist', + auth: spotifyCommon.authentication, + props: { + playlist_id: spotifyCommon.playlist_id(true), + name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + description: Property.ShortText({ + displayName: 'Description', + required: false, + }), + public: Property.Checkbox({ + displayName: 'Public', + required: false, + }), + collaborative: Property.Checkbox({ + displayName: 'Collaborative', + required: false, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient({ auth }); + await client.updatePlaylist(propsValue.playlist_id as string, { + name: propsValue.name, + description: propsValue.description, + public: propsValue.public, + collaborative: propsValue.collaborative, + }); + }, +}); diff --git a/packages/pieces/community/spotify/src/lib/common/client.ts b/packages/pieces/community/spotify/src/lib/common/client.ts new file mode 100644 index 0000000..60e9288 --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/common/client.ts @@ -0,0 +1,257 @@ +import { + AuthenticationType, + HttpMessageBody, + HttpMethod, + QueryParams, + httpClient, +} from '@activepieces/pieces-common'; +import { SearchRequest, SearchResult } from './models/search'; +import { + DeviceListResponse, + PlaybackPauseRequest, + PlaybackPlayRequest, + PlaybackSeekRequest, + PlaybackState, + PlaybackVolumeRequest, +} from './models/playback'; +import { + Playlist, + PlaylistAddItemsRequest, + PlaylistCreateRequest, + PlaylistItem, + PlaylistRemoveItemsRequest, + PlaylistReorderItemsRequest, + PlaylistUpdateRequest, +} from './models/playlist'; +import { User } from './models/user'; +import { Pagination, PaginationRequest } from './models/common'; + +function emptyValueFilter( + accessor: (key: string) => any +): (key: string) => boolean { + return (key: string) => { + const val = accessor(key); + return ( + val !== null && + val !== undefined && + (typeof val != 'string' || val.length > 0) + ); + }; +} + +export function prepareQuery(request?: Record): QueryParams { + const params: QueryParams = {}; + if (!request) return params; + Object.keys(request) + .filter(emptyValueFilter((k) => request[k])) + .forEach((k: string) => { + params[k] = (request as Record)[k].toString(); + }); + return params; +} + +export class SpotifyWebApi { + constructor(private accessToken: string) {} + + async makeRequest( + method: HttpMethod, + url: string, + query?: QueryParams, + body?: object + ): Promise { + const res = await httpClient.sendRequest({ + method, + url: 'https://api.spotify.com/v1' + url, + queryParams: query, + body, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: this.accessToken, + }, + }); + return res.body; + } + + async search(request: SearchRequest): Promise { + const res = await this.makeRequest( + HttpMethod.GET, + '/search', + prepareQuery(request) + ); + return res; + } + + async getDevices(): Promise { + return await this.makeRequest( + HttpMethod.GET, + '/me/player/devices' + ); + } + + async getPlaybackState(): Promise { + return await this.makeRequest(HttpMethod.GET, '/me/player'); + } + + async setVolume(request: PlaybackVolumeRequest) { + await this.makeRequest( + HttpMethod.PUT, + '/me/player/volume', + prepareQuery(request) + ); + } + + async pause(request: PlaybackPauseRequest) { + await this.makeRequest( + HttpMethod.PUT, + '/me/player/pause', + prepareQuery(request) + ); + } + + async play(request: PlaybackPlayRequest) { + const query: QueryParams = {}; + if (request.device_id) query.device_id = request.device_id; + request.device_id = undefined; + await this.makeRequest(HttpMethod.PUT, '/me/player/play', query, request); + } + + async seek(request: PlaybackSeekRequest) { + await this.makeRequest( + HttpMethod.PUT, + '/me/player/seek', + prepareQuery(request) + ); + } + + async getCurrentUser(): Promise { + return await this.makeRequest(HttpMethod.GET, '/me'); + } + + async getCurrentUserPlaylists( + request?: PaginationRequest + ): Promise> { + return await this.makeRequest>( + HttpMethod.GET, + '/me/playlists', + prepareQuery(request) + ); + } + + async getAllCurrentUserPlaylists(): Promise { + const playlists: Playlist[] = []; + let total = 99999; + while (playlists.length < total) { + const res = await this.getCurrentUserPlaylists({ + limit: 50, + offset: playlists.length, + }); + total = res.total; + res.items.forEach((item) => playlists.push(item)); + } + return playlists; + } + + async createPlaylist( + userId: string, + request: PlaylistCreateRequest + ): Promise { + return await this.makeRequest( + HttpMethod.POST, + '/users/' + userId + '/playlists', + undefined, + request + ); + } + + async updatePlaylist(id: string, request: PlaylistUpdateRequest) { + await this.makeRequest( + HttpMethod.PUT, + '/playlists/' + id, + undefined, + request + ); + } + + async getPlaylist(id: string): Promise { + return await this.makeRequest(HttpMethod.GET, '/playlists/' + id); + } + + async getPlaylistItems( + id: string, + request?: PaginationRequest + ): Promise> { + return await this.makeRequest>( + HttpMethod.GET, + '/playlists/' + id + '/tracks', + prepareQuery(request) + ); + } + + async getAllPlaylistItems(id: string): Promise { + const items: PlaylistItem[] = []; + let total = 99999; + while (items.length < total) { + const res = await this.getPlaylistItems(id, { + limit: 50, + offset: items.length, + }); + total = res.total; + res.items.forEach((item) => items.push(item)); + } + return items; + } + + async getSavedTracks( + request?: PaginationRequest + ): Promise> { + return await this.makeRequest>( + HttpMethod.GET, + '/me/tracks', + prepareQuery(request) + ); + } + + async getAllSavedTracks(): Promise { + const items: PlaylistItem[] = []; + let total = 99999; + while (items.length < total) { + const res = await this.getSavedTracks({ + limit: 50, + offset: items.length, + }); + total = res.total; + res.items.forEach((item) => items.push(item)); + } + return items; + } + + async addItemsToPlaylist(id: string, request: PlaylistAddItemsRequest) { + await this.makeRequest( + HttpMethod.POST, + '/playlists/' + id + '/tracks', + undefined, + request + ); + } + + async removeItemsFromPlaylist( + id: string, + request: PlaylistRemoveItemsRequest + ) { + await this.makeRequest( + HttpMethod.DELETE, + '/playlists/' + id + '/tracks', + undefined, + request + ); + } + + async reorderPlaylist(id: string, request: PlaylistReorderItemsRequest) { + await this.makeRequest( + HttpMethod.PUT, + '/playlists/' + id + '/tracks', + undefined, + request + ); + } +} diff --git a/packages/pieces/community/spotify/src/lib/common/index.ts b/packages/pieces/community/spotify/src/lib/common/index.ts new file mode 100644 index 0000000..127c581 --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/common/index.ts @@ -0,0 +1,102 @@ +import { getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { SpotifyWebApi } from './client'; + +const markdownDescription = ` +To obtain a client ID and client secret for Spotify, follow these simple steps: + +1. Go to the [Spotify Developer Dashboard](https://developer.spotify.com/). +2. **Log in** to your Spotify account. +3. Click on the **"Create an App"** button. +4. Fill in the required information, such as the **App Name** and **App Description**. +5. Once your app is created, you will see the **client ID** and **client secret** on the app's dashboard. +6. **Copy** the client ID and client secret and **paste** them below. +`; + +export const spotifyCommon = { + authentication: PieceAuth.OAuth2({ + description: markdownDescription, + required: true, + authUrl: 'https://accounts.spotify.com/authorize', + tokenUrl: 'https://accounts.spotify.com/api/token', + scope: [ + 'user-read-playback-state', + 'user-modify-playback-state', + 'user-read-currently-playing', + 'user-read-playback-position', + 'user-read-recently-played', + 'playlist-read-private', + 'playlist-read-collaborative', + 'playlist-modify-private', + 'playlist-modify-public', + 'user-library-read', + ], + }), + device_id: (required = true) => + Property.Dropdown({ + displayName: 'Device', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'setup authentication first', + options: [], + }; + } + const client = makeClient({ + auth: auth as OAuth2PropertyValue, + }); + const res = await client.getDevices(); + return { + disabled: false, + options: res.devices.map((device) => { + return { + label: device.name, + value: device.id, + }; + }), + }; + }, + }), + playlist_id: (required = true) => + Property.Dropdown({ + displayName: 'Playlist', + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'setup authentication first', + options: [], + }; + } + const client = makeClient({ + auth: auth as OAuth2PropertyValue, + }); + const playlists = await client.getAllCurrentUserPlaylists(); + return { + disabled: false, + options: playlists.map((playlist) => { + return { + label: playlist.name, + value: playlist.id, + }; + }), + }; + }, + }), +}; + +export function makeClient(propsValue: { + auth: OAuth2PropertyValue; +}): SpotifyWebApi { + const token = getAccessTokenOrThrow(propsValue.auth); + return new SpotifyWebApi(token); +} diff --git a/packages/pieces/community/spotify/src/lib/common/models/album.ts b/packages/pieces/community/spotify/src/lib/common/models/album.ts new file mode 100644 index 0000000..dacd40a --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/common/models/album.ts @@ -0,0 +1,5 @@ +export interface Album { + id: string; + name: string; + uri: string; +} diff --git a/packages/pieces/community/spotify/src/lib/common/models/artist.ts b/packages/pieces/community/spotify/src/lib/common/models/artist.ts new file mode 100644 index 0000000..ffa2f8d --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/common/models/artist.ts @@ -0,0 +1,7 @@ +export interface Artist { + id: string; + name: string; + type: 'artist'; + uri: string; + genres: string[]; +} diff --git a/packages/pieces/community/spotify/src/lib/common/models/common.ts b/packages/pieces/community/spotify/src/lib/common/models/common.ts new file mode 100644 index 0000000..b06908d --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/common/models/common.ts @@ -0,0 +1,23 @@ +export interface HasURI { + uri: string; +} + +export interface SpotifyObject { + id: string; + uri: string; +} + +export interface Pagination { + href: string; + limit: number; + next: string; + offset: number; + previous: string; + total: number; + items: T[]; +} + +export interface PaginationRequest { + limit?: number; + offset?: number; +} diff --git a/packages/pieces/community/spotify/src/lib/common/models/playback.ts b/packages/pieces/community/spotify/src/lib/common/models/playback.ts new file mode 100644 index 0000000..77de81e --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/common/models/playback.ts @@ -0,0 +1,80 @@ +import { Track } from './track'; + +export enum RepeatState { + OFF = 'off', + TRACK = 'track', + CONTEXT = 'context', +} + +export enum PlayingType { + TRACK = 'track', + EPISODE = 'episode', + AD = 'ad', + UNKNOWN = 'unknown', +} + +export interface PlaybackActions { + interrupting_playback: boolean; + pausing: boolean; + resuming: boolean; + seeking: boolean; + skipping_next: boolean; + skipping_prev: boolean; + toggling_repeat_context: boolean; + toggling_shuffle: boolean; + toggling_repeat_track: boolean; + transferring_playback: boolean; +} + +export interface PlaybackState { + device: Device; + timestamp: number; + progress_ms?: number; + is_playing: boolean; + shuffle_state: boolean; + repeat_state: RepeatState; + item?: Track; + current_playing_type: PlayingType; + actions: PlaybackActions; +} + +export interface PlaybackVolumeRequest { + volume_percent: number; + device_id?: string; +} + +export interface PlaybackPauseRequest { + device_id?: string; +} + +export interface PlaybackPlayRequest { + device_id?: string; + context_uri?: string; + uris?: string[]; + position_ms?: number; +} + +export interface PlaybackSeekRequest { + device_id?: string; + position_ms: number; +} + +export enum DeviceType { + COMPUTER = 'computer', + SMARTPHONE = 'smartphone', + SPEAKER = 'speaker', +} + +export interface Device { + id: string; + is_active: boolean; + is_private_session: boolean; + is_restricted: boolean; + name: string; + type: DeviceType; + volume_percent: number; +} + +export interface DeviceListResponse { + devices: Device[]; +} diff --git a/packages/pieces/community/spotify/src/lib/common/models/playlist.ts b/packages/pieces/community/spotify/src/lib/common/models/playlist.ts new file mode 100644 index 0000000..6c96269 --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/common/models/playlist.ts @@ -0,0 +1,47 @@ +import { HasURI } from './common'; +import { Track } from './track'; +import { User } from './user'; + +export interface PlaylistItem { + added_at: string; + added_by?: User; + is_local: boolean; + track: Track; +} + +export interface Playlist { + id: string; + name: string; + type: 'playlist'; + uri: string; + owner?: User; +} + +export interface PlaylistCreateRequest { + name: string; + public?: boolean; + collaborative?: boolean; + description?: string; +} + +export interface PlaylistAddItemsRequest { + uris: string[]; + position?: number; +} + +export interface PlaylistRemoveItemsRequest { + tracks: HasURI[]; +} + +export interface PlaylistReorderItemsRequest { + range_start: number; + range_length?: number; + insert_before: number; +} + +export interface PlaylistUpdateRequest { + name?: string; + public?: boolean; + collaborative?: boolean; + description?: string; +} diff --git a/packages/pieces/community/spotify/src/lib/common/models/search.ts b/packages/pieces/community/spotify/src/lib/common/models/search.ts new file mode 100644 index 0000000..fcb8139 --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/common/models/search.ts @@ -0,0 +1,21 @@ +import { Artist } from './artist'; +import { Track } from './track'; +import { Album } from './album'; +import { Playlist } from './playlist'; +import { Pagination } from './common'; + +export interface SearchResult { + tracks?: Pagination; + artists?: Pagination; + playlists?: Pagination; + albums?: Pagination; +} + +export interface SearchRequest { + q: string; + type: string; + market?: string; + limit?: number; + offset?: number; + include_external?: string; +} diff --git a/packages/pieces/community/spotify/src/lib/common/models/track.ts b/packages/pieces/community/spotify/src/lib/common/models/track.ts new file mode 100644 index 0000000..dff5d00 --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/common/models/track.ts @@ -0,0 +1,5 @@ +export interface Track { + id: string; + name: string; + uri: string; +} diff --git a/packages/pieces/community/spotify/src/lib/common/models/user.ts b/packages/pieces/community/spotify/src/lib/common/models/user.ts new file mode 100644 index 0000000..f7c9f3a --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/common/models/user.ts @@ -0,0 +1,14 @@ +export enum Product { + OPEN = 'open', + FREE = 'free', + PREMIUM = 'premium', +} + +export interface User { + id: string; + email?: string; + display_name: string; + product: Product; + type: 'user'; + uri: string; +} diff --git a/packages/pieces/community/spotify/src/lib/triggers/index.ts b/packages/pieces/community/spotify/src/lib/triggers/index.ts new file mode 100644 index 0000000..ca16e36 --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/triggers/index.ts @@ -0,0 +1,3 @@ +import playlistItemsChanged from './playlist-items-changed'; + +export default [playlistItemsChanged]; diff --git a/packages/pieces/community/spotify/src/lib/triggers/playlist-items-changed.ts b/packages/pieces/community/spotify/src/lib/triggers/playlist-items-changed.ts new file mode 100644 index 0000000..000cb0a --- /dev/null +++ b/packages/pieces/community/spotify/src/lib/triggers/playlist-items-changed.ts @@ -0,0 +1,61 @@ +import { + createTrigger, + StoreScope, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { spotifyCommon, makeClient } from '../common'; +import { createHash } from 'crypto'; + +export default createTrigger({ + name: 'playlist_items_changed', + displayName: 'Playlist Items Changed', + description: 'Triggers when the items of a playlist change', + auth: spotifyCommon.authentication, + type: TriggerStrategy.POLLING, + props: { + playlist_id: spotifyCommon.playlist_id(true), + }, + sampleData: {}, + onEnable: async ({ store, auth, propsValue }) => { + const client = makeClient({ auth }); + const items = await client.getAllPlaylistItems( + propsValue.playlist_id as string + ); + const hash = createHash('md5') + .update(items.map((item) => item.track.id).join(',')) + .digest('hex'); + await store.put('playlist_changed_trigger_hash', hash, StoreScope.FLOW); + }, + onDisable: async ({ store, auth, propsValue }) => { + await store.delete('playlist_changed_trigger_hash', StoreScope.FLOW); + }, + run: async ({ store, auth, propsValue }) => { + const oldHash = await store.get( + 'playlist_changed_trigger_hash', + StoreScope.FLOW + ); + const client = makeClient({ auth }); + const items = await client.getAllPlaylistItems( + propsValue.playlist_id as string + ); + const newHash = createHash('md5') + .update(items.map((item) => item.track.id).join(',')) + .digest('hex'); + if (oldHash != newHash) { + await store.put( + 'playlist_changed_trigger_hash', + newHash, + StoreScope.FLOW + ); + return [{ total: items.length, items }]; + } + return []; + }, + test: async ({ auth, propsValue }) => { + const client = makeClient({ auth }); + const items = await client.getAllPlaylistItems( + propsValue.playlist_id as string + ); + return [{ total: items.length, items }]; + }, +}); diff --git a/packages/pieces/community/spotify/tsconfig.json b/packages/pieces/community/spotify/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/spotify/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/spotify/tsconfig.lib.json b/packages/pieces/community/spotify/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/spotify/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/square/.eslintrc.json b/packages/pieces/community/square/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/square/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/square/README.md b/packages/pieces/community/square/README.md new file mode 100644 index 0000000..72a12d9 --- /dev/null +++ b/packages/pieces/community/square/README.md @@ -0,0 +1,7 @@ +# pieces-square + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-square` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/square/package.json b/packages/pieces/community/square/package.json new file mode 100644 index 0000000..3279084 --- /dev/null +++ b/packages/pieces/community/square/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-square", + "version": "0.3.5" +} diff --git a/packages/pieces/community/square/project.json b/packages/pieces/community/square/project.json new file mode 100644 index 0000000..db86a84 --- /dev/null +++ b/packages/pieces/community/square/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-square", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/square/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/square", + "tsConfig": "packages/pieces/community/square/tsconfig.lib.json", + "packageJson": "packages/pieces/community/square/package.json", + "main": "packages/pieces/community/square/src/index.ts", + "assets": [ + "packages/pieces/community/square/*.md", + { + "input": "packages/pieces/community/square/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/square/src/index.ts b/packages/pieces/community/square/src/index.ts new file mode 100644 index 0000000..b0b907b --- /dev/null +++ b/packages/pieces/community/square/src/index.ts @@ -0,0 +1,58 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import crypto from 'crypto'; +import { triggers } from './lib/triggers'; + +export const squareAuth = PieceAuth.OAuth2({ + description: 'Authentication', + authUrl: 'https://connect.squareup.com/oauth2/authorize', + tokenUrl: 'https://connect.squareup.com/oauth2/token', + required: true, + scope: [ + 'MERCHANT_PROFILE_READ', + 'CUSTOMERS_READ', + 'CUSTOMERS_WRITE', + 'ITEMS_READ', + 'ITEMS_WRITE', + 'ORDERS_READ', + 'ORDERS_WRITE', + 'PAYMENTS_READ', + 'INVOICES_READ', + 'APPOINTMENTS_READ', + 'APPOINTMENTS_WRITE', + ], +}); + +export const square = createPiece({ + displayName: 'Square', + description: 'Payment solutions for every business', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/square.png', + categories: [PieceCategory.COMMERCE], + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + auth: squareAuth, + events: { + verify: ({ webhookSecret, payload, appWebhookUrl }) => { + const signature = payload.headers['x-square-hmacsha256-signature']; + const hmac = crypto.createHmac('sha256', webhookSecret as string); + hmac.update(appWebhookUrl + payload.rawBody); + const hash = hmac.digest('base64'); + return hash === signature; + }, + parseAndReply: ({ payload }) => { + const payloadBody = payload.body as Payload | undefined; + return { + event: payloadBody?.type, + identifierValue: payloadBody?.merchant_id, + }; + }, + }, + actions: [], + triggers, +}); + +type Payload = { + type: string; + merchant_id: string; +}; diff --git a/packages/pieces/community/square/src/lib/triggers/index.ts b/packages/pieces/community/square/src/lib/triggers/index.ts new file mode 100644 index 0000000..7ce2e26 --- /dev/null +++ b/packages/pieces/community/square/src/lib/triggers/index.ts @@ -0,0 +1,401 @@ +import { squareAuth } from '../../'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; + +const triggerData = [ + { + name: 'new_order', + displayName: 'New Order', + description: 'Triggered when a new order is created', + event: 'order.created', + sampleData: { + merchant_id: 'MLTZ79VE64YTN', + type: 'order.created', + event_id: '03441e3a-47f1-49a7-a64c-55ab26703f8d', + created_at: '2023-03-14T01:42:54.984089903Z', + data: { + type: 'order', + id: 'eA3vssLHKJrv9H0IdJCM3gNqfdcZY', + object: { + order_created: { + created_at: '2020-04-16T23:14:26.129Z', + location_id: 'FPYCBCHYMXFK1', + order_id: 'eA3vssLHKJrv9H0IdJCM3gNqfdcZY', + state: 'OPEN', + version: 1, + }, + }, + }, + }, + }, + { + name: 'order_updated', + displayName: 'Order Updated', + description: 'Triggered when an order is updated', + event: 'order.updated', + sampleData: { + merchant_id: 'MLTZ79VE64YTN', + type: 'order.updated', + event_id: '7e1d596e-ebf1-443d-87aa-a5f397bce1e5', + created_at: '2023-03-14T01:56:10.454184371Z', + data: { + type: 'order', + id: 'eA3vssLHKJrv9H0IdJCM3gNqfdcZY', + object: { + order_updated: { + created_at: '2020-04-16T23:14:26.129Z', + location_id: 'FPYCBCHYMXFK1', + order_id: 'eA3vssLHKJrv9H0IdJCM3gNqfdcZY', + state: 'OPEN', + updated_at: '2020-04-16T23:14:26.359Z', + version: 2, + }, + }, + }, + }, + }, + { + name: 'new_customer', + displayName: 'New Customer', + description: 'Triggered when a customer is created', + event: 'customer.created', + sampleData: { + merchant_id: 'MLTZ79VE64YTN', + type: 'customer.created', + event_id: '2985c7c7-2ccc-409e-8aba-998684732cab', + created_at: '2023-03-14T01:57:28.679389163Z', + data: { + type: 'customer', + id: 'QPTXM8PQNX3Q726ZYHPMNP46XC', + object: { + customer: { + address: { + address_line_1: '1018 40th Street', + administrative_district_level_1: 'CA', + locality: 'Oakland', + postal_code: '94608', + }, + birthday: '1962-03-04', + created_at: '2022-11-09T21:23:25.519Z', + creation_source: 'DIRECTORY', + email_address: 'jenkins+smorly@squareup.com', + family_name: 'Smorly', + given_name: 'Jenkins', + group_ids: ['JGJCW9S0G68NE.APPOINTMENTS'], + id: 'QPTXM8PQNX3Q726ZYHPMNP46XC', + phone_number: '+12126668929', + preferences: { + email_unsubscribed: false, + }, + updated_at: '2022-11-09T21:23:25Z', + version: 0, + }, + }, + }, + }, + }, + { + name: 'customer_updated', + displayName: 'Customer Updated', + description: 'Triggered when a customer is updated', + event: 'customer.updated', + sampleData: { + merchant_id: 'MLTZ79VE64YTN', + type: 'customer.updated', + event_id: 'f6e89469-de2f-4ae4-84c7-83a95681759a', + created_at: '2023-03-14T01:58:22.076902762Z', + data: { + type: 'customer', + id: 'A0AP25A6SCVTH8JES9BX01GXM4', + object: { + customer: { + created_at: '2022-07-09T18:23:01.795Z', + creation_source: 'THIRD_PARTY', + email_address: 'jenkins+smorly@squareup.com', + family_name: 'Smorly', + given_name: 'Jenkins', + id: 'A0AP25A6SCVTH8JES9BX01GXM4', + phone_number: '+13477947111', + preferences: { + email_unsubscribed: false, + }, + updated_at: '2022-11-09T21:38:30Z', + version: 1, + }, + }, + }, + }, + }, + { + name: 'new_appointment', + displayName: 'New Appointment', + description: 'Triggered when a new appointment is created', + event: 'booking.created', + sampleData: { + merchant_id: 'MLTZ79VE64YTN', + location_id: 'ES0RJRZYEC39A', + type: 'invoice.created', + event_id: 'ee17dc22-5e38-4aba-ad15-af8e25adcc93', + created_at: '2023-03-14T02:01:46.497709569Z', + data: { + type: 'invoice', + id: 'inv:0-ChCHu2mZEabLeeHahQnXDjZQECY', + object: { + invoice: { + accepted_payment_methods: { + bank_account: false, + buy_now_pay_later: false, + card: true, + square_gift_card: false, + }, + created_at: '2020-06-18T17:45:13Z', + custom_fields: [ + { + label: 'Event Reference Number', + placement: 'ABOVE_LINE_ITEMS', + value: 'Ref. #1234', + }, + { + label: 'Terms of Service', + placement: 'BELOW_LINE_ITEMS', + value: 'The terms of service are...', + }, + ], + delivery_method: 'EMAIL', + description: 'We appreciate your business!', + id: 'inv:0-ChCHu2mZEabLeeHahQnXDjZQECY', + invoice_number: 'inv-100', + location_id: 'ES0RJRZYEC39A', + order_id: 'CAISENgvlJ6jLWAzERDzjyHVybY', + payment_requests: [ + { + automatic_payment_source: 'NONE', + computed_amount_money: { + amount: 10000, + currency: 'USD', + }, + due_date: '2030-01-24', + reminders: [ + { + message: 'Your invoice is due tomorrow', + relative_scheduled_days: -1, + status: 'PENDING', + uid: 'beebd363-e47f-4075-8785-c235aaa7df11', + }, + ], + request_type: 'BALANCE', + tipping_enabled: true, + total_completed_amount_money: { + amount: 0, + currency: 'USD', + }, + uid: '2da7964f-f3d2-4f43-81e8-5aa220bf3355', + }, + ], + primary_recipient: { + customer_id: 'JDKYHBWT1D4F8MFH63DBMEN8Y4', + email_address: 'Amelia.Earhart@example.com', + family_name: 'Earhart', + given_name: 'Amelia', + phone_number: '1-212-555-4240', + }, + sale_or_service_date: '2030-01-24', + scheduled_at: '2030-01-13T10:00:00Z', + status: 'DRAFT', + store_payment_method_enabled: false, + timezone: 'America/Los_Angeles', + title: 'Event Planning Services', + updated_at: '2020-06-18T17:45:13Z', + version: 0, + }, + }, + }, + }, + }, + { + name: 'new_payment', + displayName: 'New Payment', + description: 'Triggered when a new payment is created', + event: 'payment.created', + sampleData: { + merchant_id: 'MLTZ79VE64YTN', + type: 'payment.created', + event_id: '11fb274d-6882-417a-879c-faec367e0665', + created_at: '2023-03-14T02:00:56.000119371Z', + data: { + type: 'payment', + id: 'KkAkhdMsgzn59SM8A89WgKwekxLZY', + object: { + payment: { + amount_money: { + amount: 100, + currency: 'USD', + }, + approved_money: { + amount: 100, + currency: 'USD', + }, + capabilities: [ + 'EDIT_TIP_AMOUNT', + 'EDIT_TIP_AMOUNT_UP', + 'EDIT_TIP_AMOUNT_DOWN', + ], + card_details: { + avs_status: 'AVS_ACCEPTED', + card: { + bin: '540988', + card_brand: 'MASTERCARD', + card_type: 'CREDIT', + exp_month: 11, + exp_year: 2022, + fingerprint: + 'sq-1-Tvruf3vPQxlvI6n0IcKYfBukrcv6IqWr8UyBdViWXU2yzGn5VMJvrsHMKpINMhPmVg', + last_4: '9029', + prepaid_type: 'NOT_PREPAID', + }, + card_payment_timeline: { + authorized_at: '2020-11-22T21:16:51.198Z', + }, + cvv_status: 'CVV_ACCEPTED', + entry_method: 'KEYED', + statement_description: 'SQ *DEFAULT TEST ACCOUNT', + status: 'AUTHORIZED', + }, + created_at: '2020-11-22T21:16:51.086Z', + delay_action: 'CANCEL', + delay_duration: 'PT168H', + delayed_until: '2020-11-29T21:16:51.086Z', + id: 'hYy9pRFVxpDsO1FB05SunFWUe9JZY', + location_id: 'S8GWD5R9QB376', + order_id: '03O3USaPaAaFnI6kkwB1JxGgBsUZY', + receipt_number: 'hYy9', + risk_evaluation: { + created_at: '2020-11-22T21:16:51.198Z', + risk_level: 'NORMAL', + }, + source_type: 'CARD', + status: 'APPROVED', + total_money: { + amount: 100, + currency: 'USD', + }, + updated_at: '2020-11-22T21:16:51.198Z', + version_token: 'FfQhQJf9r3VSQIgyWBk1oqhIwiznLwVwJbVVA0bdyEv6o', + }, + }, + }, + }, + }, + { + name: 'new_invoice', + displayName: 'New Invoice', + description: 'Triggered when a new invoice is created', + event: 'invoice.created', + sampleData: { + merchant_id: 'MLTZ79VE64YTN', + location_id: 'ES0RJRZYEC39A', + type: 'invoice.created', + event_id: 'ee17dc22-5e38-4aba-ad15-af8e25adcc93', + created_at: '2023-03-14T02:01:46.497709569Z', + data: { + type: 'invoice', + id: 'inv:0-ChCHu2mZEabLeeHahQnXDjZQECY', + object: { + invoice: { + accepted_payment_methods: { + bank_account: false, + buy_now_pay_later: false, + card: true, + square_gift_card: false, + }, + created_at: '2020-06-18T17:45:13Z', + custom_fields: [ + { + label: 'Event Reference Number', + placement: 'ABOVE_LINE_ITEMS', + value: 'Ref. #1234', + }, + { + label: 'Terms of Service', + placement: 'BELOW_LINE_ITEMS', + value: 'The terms of service are...', + }, + ], + delivery_method: 'EMAIL', + description: 'We appreciate your business!', + id: 'inv:0-ChCHu2mZEabLeeHahQnXDjZQECY', + invoice_number: 'inv-100', + location_id: 'ES0RJRZYEC39A', + order_id: 'CAISENgvlJ6jLWAzERDzjyHVybY', + payment_requests: [ + { + automatic_payment_source: 'NONE', + computed_amount_money: { + amount: 10000, + currency: 'USD', + }, + due_date: '2030-01-24', + reminders: [ + { + message: 'Your invoice is due tomorrow', + relative_scheduled_days: -1, + status: 'PENDING', + uid: 'beebd363-e47f-4075-8785-c235aaa7df11', + }, + ], + request_type: 'BALANCE', + tipping_enabled: true, + total_completed_amount_money: { + amount: 0, + currency: 'USD', + }, + uid: '2da7964f-f3d2-4f43-81e8-5aa220bf3355', + }, + ], + primary_recipient: { + customer_id: 'JDKYHBWT1D4F8MFH63DBMEN8Y4', + email_address: 'Amelia.Earhart@example.com', + family_name: 'Earhart', + given_name: 'Amelia', + phone_number: '1-212-555-4240', + }, + sale_or_service_date: '2030-01-24', + scheduled_at: '2030-01-13T10:00:00Z', + status: 'DRAFT', + store_payment_method_enabled: false, + timezone: 'America/Los_Angeles', + title: 'Event Planning Services', + updated_at: '2020-06-18T17:45:13Z', + version: 0, + }, + }, + }, + }, + }, +]; + +export const triggers = triggerData.map((trigger) => + createTrigger({ + auth: squareAuth, + name: trigger.name, + displayName: trigger.displayName, + description: trigger.description, + props: {}, + type: TriggerStrategy.APP_WEBHOOK, + sampleData: trigger.sampleData, + onEnable: async (context) => { + context.app.createListeners({ + events: [trigger.event], + identifierValue: context.auth.data['merchant_id'], + }); + }, + onDisable: async () => { + // Ignored + }, + test: async () => { + return [trigger.sampleData]; + }, + run: async (context) => { + return [context.payload.body]; + }, + }) +); diff --git a/packages/pieces/community/square/tsconfig.json b/packages/pieces/community/square/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/square/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/square/tsconfig.lib.json b/packages/pieces/community/square/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/square/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/stability-ai/.eslintrc.json b/packages/pieces/community/stability-ai/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/stability-ai/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/stability-ai/README.md b/packages/pieces/community/stability-ai/README.md new file mode 100644 index 0000000..a4b7270 --- /dev/null +++ b/packages/pieces/community/stability-ai/README.md @@ -0,0 +1,7 @@ +# pieces-stability-ai + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-stability-ai` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/stability-ai/package.json b/packages/pieces/community/stability-ai/package.json new file mode 100644 index 0000000..1a8e9a3 --- /dev/null +++ b/packages/pieces/community/stability-ai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-stability-ai", + "version": "0.1.10" +} \ No newline at end of file diff --git a/packages/pieces/community/stability-ai/project.json b/packages/pieces/community/stability-ai/project.json new file mode 100644 index 0000000..147ebd6 --- /dev/null +++ b/packages/pieces/community/stability-ai/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-stability-ai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/stability-ai/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/stability-ai", + "tsConfig": "packages/pieces/community/stability-ai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/stability-ai/package.json", + "main": "packages/pieces/community/stability-ai/src/index.ts", + "assets": [ + "packages/pieces/community/stability-ai/*.md", + { + "input": "packages/pieces/community/stability-ai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/stability-ai/src/index.ts b/packages/pieces/community/stability-ai/src/index.ts new file mode 100644 index 0000000..4880fe0 --- /dev/null +++ b/packages/pieces/community/stability-ai/src/index.ts @@ -0,0 +1,42 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { textToImage } from './lib/actions/text-to-image'; + +export const stabilityAiAuth = PieceAuth.CustomAuth({ + description: `Please visit https://platform.stability.ai/docs/getting-started/authentication to get your API Key`, + props: { + api_key: Property.ShortText({ + displayName: 'API Key', + required: true, + }), + }, + required: true, +}); + +export const stabilityAi = createPiece({ + displayName: 'Stability AI', + description: + 'Generative AI video model based on the image model Stable Diffusion.', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/stability-ai.png', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + authors: ["Willianwg","camilou","kishanprmr","MoShizzle","AbdulTheActivePiecer","khaledmashaly","abuaboud"], + auth: stabilityAiAuth, + actions: [ + textToImage, + createCustomApiCallAction({ + baseUrl: () => 'https://api.stability.ai/v1', + auth: stabilityAiAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as { api_key: string }).api_key}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/stability-ai/src/lib/actions/text-to-image.ts b/packages/pieces/community/stability-ai/src/lib/actions/text-to-image.ts new file mode 100644 index 0000000..289e918 --- /dev/null +++ b/packages/pieces/community/stability-ai/src/lib/actions/text-to-image.ts @@ -0,0 +1,286 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { stabilityAiAuth } from '../..'; + +export const textToImage = createAction({ + auth: stabilityAiAuth, + name: 'text-to-image', + displayName: 'Text to Image', + description: 'Generate an image using a text prompt', + props: { + prompt: Property.ShortText({ + displayName: 'Prompt', + required: true, + description: 'The text to transform in image.', + }), + cfg_scale: Property.Number({ + displayName: 'cfg_scale', + description: + 'How strictly the diffusion process adheres to the prompt text (higher values keep your image closer to your prompt) (MIN:0; MAX:35)', + required: false, + defaultValue: 7, + }), + height: Property.Number({ + displayName: 'height', + description: + 'Height of the image in pixels. Must be in increments of 64 and >= 128', + required: false, + }), + width: Property.Number({ + displayName: 'width', + description: + 'Width of the image in pixels. Must be in increments of 64 and >= 128', + required: false, + }), + samples: Property.ShortText({ + displayName: 'samples', + description: 'Number of images to generate (MAX:10)', + required: false, + }), + steps: Property.Number({ + displayName: 'steps', + description: 'Number of diffusion steps to run (MIN:10; MAX:150)', + required: false, + }), + weight: Property.Number({ + displayName: 'weight', + defaultValue: 1, + required: false, + }), + clip_guidance_preset: Property.StaticDropdown({ + displayName: 'clip_guidance_preset', + required: false, + options: { + options: [ + { + label: 'NONE', + value: 'NONE', + }, + { + label: 'FAST_BLUE', + value: 'FAST_BLUE', + }, + { + label: 'FAST_GREEN', + value: 'FAST_GREEN', + }, + { + label: 'SIMPLE', + value: 'SIMPLE', + }, + { + label: 'SLOW', + value: 'SLOW', + }, + { + label: 'SLOWER', + value: 'SLOWER', + }, + { + label: 'SLOWEST', + value: 'SLOWEST', + }, + ], + }, + }), + style_preset: Property.StaticDropdown({ + displayName: 'style_preset', + required: false, + description: + 'Pass in a style preset to guide the image model towards a particular style.', + options: { + options: [ + { + label: 'enhance', + value: 'enhance', + }, + { + label: 'anime', + value: 'anime', + }, + { + label: 'photographic', + value: 'photographic', + }, + { + label: 'digital-art', + value: 'digital-art', + }, + { + label: 'comic-book', + value: 'comic-book', + }, + { + label: 'fantasy-art', + value: 'fantasy-art', + }, + { + label: 'line-art', + value: 'line-art', + }, + { + label: 'analog-film', + value: 'analog-film', + }, + { + label: 'neon-punk', + value: 'neon-punk', + }, + { + label: 'isometric', + value: 'isometric', + }, + { + label: 'low-poly', + value: 'low-poly', + }, + { + label: 'origami', + value: 'origami', + }, + { + label: 'modeling-compound', + value: 'modeling-compound', + }, + { + label: 'cinematic', + value: 'cinematic', + }, + { + label: '3d-model', + value: '3d-model', + }, + { + label: 'pixel-art', + value: 'pixel-art', + }, + { + label: 'tile-texture', + value: 'tile-texture', + }, + ], + }, + }), + engine_id: Property.StaticDropdown({ + displayName: 'Engine ID', + required: true, + options: { + options: [ + { + label: 'stable-diffusion-xl-1024-v1-0', + value: 'stable-diffusion-xl-1024-v1-0', + }, + { + label: 'stable-diffusion-768-v2-1', + value: 'stable-diffusion-768-v2-1', + }, + { + label: 'stable-diffusion-512-v2-1', + value: 'stable-diffusion-512-v2-1', + }, + { + label: 'stable-diffusion-768-v2-0', + value: 'stable-diffusion-768-v2-0', + }, + { + label: 'stable-diffusion-512-v2-0', + value: 'stable-diffusion-512-v2-0', + }, + { + label: 'stable-diffusion-v1-5', + value: 'stable-diffusion-v1-5', + }, + { + label: 'stable-diffusion-v1', + value: 'stable-diffusion-v1', + }, + ], + }, + }), + }, + async run(context) { + const { + prompt, + cfg_scale, + clip_guidance_preset, + height, + width, + samples, + steps, + style_preset, + engine_id, + weight, + } = context.propsValue; + + const engineId = engine_id || 'stable-diffusion-v1-5'; + const apiHost = 'https://api.stability.ai'; + + const apiKey = context.auth.api_key; + + const requestBody = { + text_prompts: [ + { + text: prompt, + weight: Number(weight) || 1, + }, + ], + cfg_scale: Number(cfg_scale) || 7, + clip_guidance_preset: clip_guidance_preset || 'NONE', + height: Number(height) || getDefaultSize(engine_id), + width: Number(width) || getDefaultSize(engine_id), + samples: Number(samples) || 1, + steps: Number(steps) || 50, + style_preset, + }; + + const request: HttpRequest> = { + method: HttpMethod.POST, + url: `${apiHost}/v1/generation/${engineId}/text-to-image`, + headers: { + Authorization: `Bearer ${apiKey}`, + Accept: 'application/json', + }, + body: requestBody, + }; + + const { body } = await httpClient.sendRequest<{ + artifacts: { base64: string }[]; + }>(request); + + return Promise.all( + body.artifacts.map((artifact) => + context.files + .write({ + fileName: `image-${Date.now()}.png`, + data: Buffer.from(artifact.base64, 'base64'), + }) + .then((file) => ({ image: file })) + ) + ); + }, +}); + +function getDefaultSize(engineId: string) { + switch (engineId) { + case 'stable-diffusion-xl-1024-v1-0': + return 1024; + case 'stable-diffusion-768-v2-1': + return 768; + case 'stable-diffusion-512-v2-1': + return 512; + case 'stable-diffusion-768-v2-0': + return 768; + case 'stable-diffusion-512-v2-0': + return 512; + case 'stable-diffusion-v1-5': + return 512; + case 'stable-diffusion-v1': + return 512; + default: + return 512; + } +} diff --git a/packages/pieces/community/stability-ai/tsconfig.json b/packages/pieces/community/stability-ai/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/stability-ai/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/stability-ai/tsconfig.lib.json b/packages/pieces/community/stability-ai/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/stability-ai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/stable-diffusion-webui/.eslintrc.json b/packages/pieces/community/stable-diffusion-webui/.eslintrc.json new file mode 100644 index 0000000..d7449bb --- /dev/null +++ b/packages/pieces/community/stable-diffusion-webui/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {}, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/stable-diffusion-webui/README.md b/packages/pieces/community/stable-diffusion-webui/README.md new file mode 100644 index 0000000..a1f9ed1 --- /dev/null +++ b/packages/pieces/community/stable-diffusion-webui/README.md @@ -0,0 +1,9 @@ +# pieces-stable-diffusion-webui + +This library was generated with [Nx](https://nx.dev). + +Integration with [Stable Diffusion web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) + +## Building + +Run `nx build pieces-stable-diffusion-webui` to build the library. diff --git a/packages/pieces/community/stable-diffusion-webui/package.json b/packages/pieces/community/stable-diffusion-webui/package.json new file mode 100644 index 0000000..8d977fb --- /dev/null +++ b/packages/pieces/community/stable-diffusion-webui/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-stable-diffusion-webui", + "version": "0.0.4" +} \ No newline at end of file diff --git a/packages/pieces/community/stable-diffusion-webui/project.json b/packages/pieces/community/stable-diffusion-webui/project.json new file mode 100644 index 0000000..8c2e50b --- /dev/null +++ b/packages/pieces/community/stable-diffusion-webui/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-stable-diffusion-webui", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/stable-diffusion-webui/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/stable-diffusion-webui", + "tsConfig": "packages/pieces/community/stable-diffusion-webui/tsconfig.lib.json", + "packageJson": "packages/pieces/community/stable-diffusion-webui/package.json", + "main": "packages/pieces/community/stable-diffusion-webui/src/index.ts", + "assets": [ + "packages/pieces/community/stable-diffusion-webui/*.md", + { + "input": "packages/pieces/community/stable-diffusion-webui/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-stable-diffusion-webui {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/stable-diffusion-webui/src/index.ts b/packages/pieces/community/stable-diffusion-webui/src/index.ts new file mode 100644 index 0000000..5cf3943 --- /dev/null +++ b/packages/pieces/community/stable-diffusion-webui/src/index.ts @@ -0,0 +1,32 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { textToImage } from './lib/actions/text-to-image'; + +export const stableDiffusionAuth = PieceAuth.CustomAuth({ + required: true, + props: { + baseUrl: Property.ShortText({ + displayName: 'Stable Diffusion web UI API base URL', + required: true, + }), + }, +}); + +export type StableDiffusionAuthType = { + baseUrl: string; +}; + +export const stableDiffusion = createPiece({ + displayName: 'Stable Dffusion web UI', + description: 'A web interface for Stable Diffusion', + + auth: stableDiffusionAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/stable-diffusion-webui.png', + authors: ['AdamSelene', 'abuaboud'], + actions: [textToImage], + triggers: [], +}); diff --git a/packages/pieces/community/stable-diffusion-webui/src/lib/actions/text-to-image.ts b/packages/pieces/community/stable-diffusion-webui/src/lib/actions/text-to-image.ts new file mode 100644 index 0000000..4f6f2ea --- /dev/null +++ b/packages/pieces/community/stable-diffusion-webui/src/lib/actions/text-to-image.ts @@ -0,0 +1,99 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { randomBytes } from 'node:crypto'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { stableDiffusionAuth, StableDiffusionAuthType } from '../../index'; +import { kebabCase } from '@activepieces/shared'; + +export const textToImage = createAction({ + name: 'textToImage', + displayName: 'Text to Image', + description: '', + auth: stableDiffusionAuth, + props: { + prompt: Property.LongText({ + displayName: 'Prompt', + required: true, + }), + model: Property.Dropdown({ + displayName: 'Model', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + } + const { baseUrl } = auth as StableDiffusionAuthType; + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${baseUrl}/sdapi/v1/sd-models`, + headers: { + 'Content-Type': 'application/json', + }, + }; + const response = await httpClient.sendRequest(request); + const options = response.body + ?.map((model: { model_name: string }) => { + return { + label: model.model_name, + value: model.model_name, + }; + }) + ?.sort((a: { label: string }, b: { label: string }) => + a['label'].localeCompare(b['label']) + ); + return { + options: options, + }; + }, + }), + advancedParameters: Property.Object({ + displayName: 'Advanced parameters (key/value)', + required: false, + description: 'Refer to API documentation', + }), + }, + async run({ auth, propsValue, files }) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${auth.baseUrl}/sdapi/v1/txt2img`, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + ...propsValue.advancedParameters, + prompt: propsValue.prompt, + override_settings: { + sd_model_checkpoint: propsValue.model, + }, + override_settings_restore_afterwards: true, + }), + }; + const response = await httpClient.sendRequest(request); + const images = await Promise.all( + response.body['images']?.map(async (imageBase64: string) => { + const fileName = `${randomBytes(16).toString('hex')}-${kebabCase( + propsValue.prompt + ).slice(0, 42)}.png`; + const imageUrl = await files.write({ + fileName, + data: Buffer.from(imageBase64, 'base64'), + }); + return { + fileName, + url: imageUrl, + }; + }) + ); + return { + images, + }; + }, +}); diff --git a/packages/pieces/community/stable-diffusion-webui/tsconfig.json b/packages/pieces/community/stable-diffusion-webui/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/stable-diffusion-webui/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/stable-diffusion-webui/tsconfig.lib.json b/packages/pieces/community/stable-diffusion-webui/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/stable-diffusion-webui/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/store/.babelrc b/packages/pieces/community/store/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/store/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/store/.eslintrc.json b/packages/pieces/community/store/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/store/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/store/README.md b/packages/pieces/community/store/README.md new file mode 100644 index 0000000..6287f91 --- /dev/null +++ b/packages/pieces/community/store/README.md @@ -0,0 +1,7 @@ +# pieces-store + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-store` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/store/package.json b/packages/pieces/community/store/package.json new file mode 100644 index 0000000..fb8ac96 --- /dev/null +++ b/packages/pieces/community/store/package.json @@ -0,0 +1,5 @@ +{ + "name": "@activepieces/piece-store", + "version": "0.6.1" +} + diff --git a/packages/pieces/community/store/project.json b/packages/pieces/community/store/project.json new file mode 100644 index 0000000..efdc9d3 --- /dev/null +++ b/packages/pieces/community/store/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-store", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/store/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/store", + "tsConfig": "packages/pieces/community/store/tsconfig.lib.json", + "packageJson": "packages/pieces/community/store/package.json", + "main": "packages/pieces/community/store/src/index.ts", + "assets": [ + "packages/pieces/community/store/*.md", + { + "input": "packages/pieces/community/store/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/ar.json b/packages/pieces/community/store/src/i18n/ar.json new file mode 100644 index 0000000..38031bb --- /dev/null +++ b/packages/pieces/community/store/src/i18n/ar.json @@ -0,0 +1,27 @@ +{ + "Storage": "Storage", + "Store or retrieve data from key/value database": "Store or retrieve data from key/value database", + "Get": "Get", + "Put": "Put", + "Append": "Append", + "Remove": "Remove", + "Add To List": "Add To List", + "Remove from List": "Remove from List", + "Get a value from storage": "Get a value from storage", + "Put a value in storage": "Put a value in storage", + "Append to a value in storage": "Append to a value in storage", + "Remove a value from storage": "Remove a value from storage", + "Add Items to a list.": "Add Items to a list.", + "Remove Item from a list": "Remove Item from a list", + "Key": "Key", + "Default Value": "Default Value", + "Store Scope": "Store Scope", + "Value": "Value", + "Separator": "Separator", + "Ignore if value exists": "Ignore if value exists", + "The storage scope of the value.": "The storage scope of the value.", + "Separator between added values, use \\n for newlines": "Separator between added values, use \\n for newlines", + "Project": "Project", + "Flow": "Flow", + "Run": "Run" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/bg.json b/packages/pieces/community/store/src/i18n/bg.json new file mode 100644 index 0000000..38031bb --- /dev/null +++ b/packages/pieces/community/store/src/i18n/bg.json @@ -0,0 +1,27 @@ +{ + "Storage": "Storage", + "Store or retrieve data from key/value database": "Store or retrieve data from key/value database", + "Get": "Get", + "Put": "Put", + "Append": "Append", + "Remove": "Remove", + "Add To List": "Add To List", + "Remove from List": "Remove from List", + "Get a value from storage": "Get a value from storage", + "Put a value in storage": "Put a value in storage", + "Append to a value in storage": "Append to a value in storage", + "Remove a value from storage": "Remove a value from storage", + "Add Items to a list.": "Add Items to a list.", + "Remove Item from a list": "Remove Item from a list", + "Key": "Key", + "Default Value": "Default Value", + "Store Scope": "Store Scope", + "Value": "Value", + "Separator": "Separator", + "Ignore if value exists": "Ignore if value exists", + "The storage scope of the value.": "The storage scope of the value.", + "Separator between added values, use \\n for newlines": "Separator between added values, use \\n for newlines", + "Project": "Project", + "Flow": "Flow", + "Run": "Run" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/ca.json b/packages/pieces/community/store/src/i18n/ca.json new file mode 100644 index 0000000..38031bb --- /dev/null +++ b/packages/pieces/community/store/src/i18n/ca.json @@ -0,0 +1,27 @@ +{ + "Storage": "Storage", + "Store or retrieve data from key/value database": "Store or retrieve data from key/value database", + "Get": "Get", + "Put": "Put", + "Append": "Append", + "Remove": "Remove", + "Add To List": "Add To List", + "Remove from List": "Remove from List", + "Get a value from storage": "Get a value from storage", + "Put a value in storage": "Put a value in storage", + "Append to a value in storage": "Append to a value in storage", + "Remove a value from storage": "Remove a value from storage", + "Add Items to a list.": "Add Items to a list.", + "Remove Item from a list": "Remove Item from a list", + "Key": "Key", + "Default Value": "Default Value", + "Store Scope": "Store Scope", + "Value": "Value", + "Separator": "Separator", + "Ignore if value exists": "Ignore if value exists", + "The storage scope of the value.": "The storage scope of the value.", + "Separator between added values, use \\n for newlines": "Separator between added values, use \\n for newlines", + "Project": "Project", + "Flow": "Flow", + "Run": "Run" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/de.json b/packages/pieces/community/store/src/i18n/de.json new file mode 100644 index 0000000..60c41e2 --- /dev/null +++ b/packages/pieces/community/store/src/i18n/de.json @@ -0,0 +1,27 @@ +{ + "Storage": "Speicher", + "Store or retrieve data from key/value database": "Daten aus der Schlüssel/Wert-Datenbank speichern oder abrufen", + "Get": "Erhalten", + "Put": "Legen", + "Append": "Anhängen", + "Remove": "Entfernen", + "Add To List": "Zur Liste hinzufügen", + "Remove from List": "Aus Liste entfernen", + "Get a value from storage": "Einen Wert vom Speicher erhalten", + "Put a value in storage": "Geben Sie einen Wert in den Speicher ein", + "Append to a value in storage": "An einen Wert im Speicher anhängen", + "Remove a value from storage": "Einen Wert vom Speicher entfernen", + "Add Items to a list.": "Elemente zu einer Liste hinzufügen.", + "Remove Item from a list": "Element aus einer Liste entfernen", + "Key": "Schlüssel", + "Default Value": "Standardwert", + "Store Scope": "Store-Bereich", + "Value": "Wert", + "Separator": "Trennzeichen", + "Ignore if value exists": "Ignorieren, wenn Wert vorhanden ist", + "The storage scope of the value.": "Der Speicherbereich des Wertes.", + "Separator between added values, use \\n for newlines": "Trennzeichen zwischen den hinzugefügten Werten verwenden Sie \\n für Zeilenumbrüche", + "Project": "Projekt", + "Flow": "Ablauf", + "Run": "Ausführung" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/es.json b/packages/pieces/community/store/src/i18n/es.json new file mode 100644 index 0000000..3653e71 --- /dev/null +++ b/packages/pieces/community/store/src/i18n/es.json @@ -0,0 +1,27 @@ +{ + "Storage": "Almacenamiento", + "Store or retrieve data from key/value database": "Guardar o recuperar datos de la base de datos/valor", + "Get": "Obtener", + "Put": "Poner", + "Append": "Añadir", + "Remove": "Eliminar", + "Add To List": "Añadir a la lista", + "Remove from List": "Eliminar de la lista", + "Get a value from storage": "Obtener un valor del almacenamiento", + "Put a value in storage": "Poner un valor en el almacenamiento", + "Append to a value in storage": "Añadir a un valor en el almacenamiento", + "Remove a value from storage": "Eliminar un valor del almacenamiento", + "Add Items to a list.": "Añadir elementos a una lista.", + "Remove Item from a list": "Eliminar elemento de una lista", + "Key": "Clave", + "Default Value": "Valor por defecto", + "Store Scope": "Alcance de tienda", + "Value": "Valor", + "Separator": "Separador", + "Ignore if value exists": "Ignorar si el valor existe", + "The storage scope of the value.": "El alcance del almacenamiento del valor.", + "Separator between added values, use \\n for newlines": "Separador entre valores añadidos, use \\n para nuevas líneas", + "Project": "Projekt", + "Flow": "Flujo", + "Run": "Ejecución" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/fr.json b/packages/pieces/community/store/src/i18n/fr.json new file mode 100644 index 0000000..e2e6d49 --- /dev/null +++ b/packages/pieces/community/store/src/i18n/fr.json @@ -0,0 +1,27 @@ +{ + "Storage": "Stockage", + "Store or retrieve data from key/value database": "Stocker ou récupérer les données de la base de données clé/valeur", + "Get": "Obtenir", + "Put": "Mettre en place", + "Append": "Ajouter", + "Remove": "Retirer", + "Add To List": "Ajouter à la liste", + "Remove from List": "Retirer de la liste", + "Get a value from storage": "Récupérer une valeur depuis le stockage", + "Put a value in storage": "Mettre une valeur dans le stockage", + "Append to a value in storage": "Ajouter à une valeur dans le stockage", + "Remove a value from storage": "Supprimer une valeur du stockage", + "Add Items to a list.": "Ajouter des éléments à une liste.", + "Remove Item from a list": "Retirer un élément d'une liste", + "Key": "Clés", + "Default Value": "Valeur par défaut", + "Store Scope": "Portée du magasin", + "Value": "Valeur", + "Separator": "Séparateur", + "Ignore if value exists": "Ignorer si la valeur existe", + "The storage scope of the value.": "La portée de stockage de la valeur.", + "Separator between added values, use \\n for newlines": "Séparateur entre les valeurs ajoutées, utilisez \\n pour les nouvelles lignes", + "Project": "Projet", + "Flow": "Flow", + "Run": "Run" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/hi.json b/packages/pieces/community/store/src/i18n/hi.json new file mode 100644 index 0000000..38031bb --- /dev/null +++ b/packages/pieces/community/store/src/i18n/hi.json @@ -0,0 +1,27 @@ +{ + "Storage": "Storage", + "Store or retrieve data from key/value database": "Store or retrieve data from key/value database", + "Get": "Get", + "Put": "Put", + "Append": "Append", + "Remove": "Remove", + "Add To List": "Add To List", + "Remove from List": "Remove from List", + "Get a value from storage": "Get a value from storage", + "Put a value in storage": "Put a value in storage", + "Append to a value in storage": "Append to a value in storage", + "Remove a value from storage": "Remove a value from storage", + "Add Items to a list.": "Add Items to a list.", + "Remove Item from a list": "Remove Item from a list", + "Key": "Key", + "Default Value": "Default Value", + "Store Scope": "Store Scope", + "Value": "Value", + "Separator": "Separator", + "Ignore if value exists": "Ignore if value exists", + "The storage scope of the value.": "The storage scope of the value.", + "Separator between added values, use \\n for newlines": "Separator between added values, use \\n for newlines", + "Project": "Project", + "Flow": "Flow", + "Run": "Run" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/hu.json b/packages/pieces/community/store/src/i18n/hu.json new file mode 100644 index 0000000..38031bb --- /dev/null +++ b/packages/pieces/community/store/src/i18n/hu.json @@ -0,0 +1,27 @@ +{ + "Storage": "Storage", + "Store or retrieve data from key/value database": "Store or retrieve data from key/value database", + "Get": "Get", + "Put": "Put", + "Append": "Append", + "Remove": "Remove", + "Add To List": "Add To List", + "Remove from List": "Remove from List", + "Get a value from storage": "Get a value from storage", + "Put a value in storage": "Put a value in storage", + "Append to a value in storage": "Append to a value in storage", + "Remove a value from storage": "Remove a value from storage", + "Add Items to a list.": "Add Items to a list.", + "Remove Item from a list": "Remove Item from a list", + "Key": "Key", + "Default Value": "Default Value", + "Store Scope": "Store Scope", + "Value": "Value", + "Separator": "Separator", + "Ignore if value exists": "Ignore if value exists", + "The storage scope of the value.": "The storage scope of the value.", + "Separator between added values, use \\n for newlines": "Separator between added values, use \\n for newlines", + "Project": "Project", + "Flow": "Flow", + "Run": "Run" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/hy.json b/packages/pieces/community/store/src/i18n/hy.json new file mode 100644 index 0000000..38031bb --- /dev/null +++ b/packages/pieces/community/store/src/i18n/hy.json @@ -0,0 +1,27 @@ +{ + "Storage": "Storage", + "Store or retrieve data from key/value database": "Store or retrieve data from key/value database", + "Get": "Get", + "Put": "Put", + "Append": "Append", + "Remove": "Remove", + "Add To List": "Add To List", + "Remove from List": "Remove from List", + "Get a value from storage": "Get a value from storage", + "Put a value in storage": "Put a value in storage", + "Append to a value in storage": "Append to a value in storage", + "Remove a value from storage": "Remove a value from storage", + "Add Items to a list.": "Add Items to a list.", + "Remove Item from a list": "Remove Item from a list", + "Key": "Key", + "Default Value": "Default Value", + "Store Scope": "Store Scope", + "Value": "Value", + "Separator": "Separator", + "Ignore if value exists": "Ignore if value exists", + "The storage scope of the value.": "The storage scope of the value.", + "Separator between added values, use \\n for newlines": "Separator between added values, use \\n for newlines", + "Project": "Project", + "Flow": "Flow", + "Run": "Run" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/id.json b/packages/pieces/community/store/src/i18n/id.json new file mode 100644 index 0000000..38031bb --- /dev/null +++ b/packages/pieces/community/store/src/i18n/id.json @@ -0,0 +1,27 @@ +{ + "Storage": "Storage", + "Store or retrieve data from key/value database": "Store or retrieve data from key/value database", + "Get": "Get", + "Put": "Put", + "Append": "Append", + "Remove": "Remove", + "Add To List": "Add To List", + "Remove from List": "Remove from List", + "Get a value from storage": "Get a value from storage", + "Put a value in storage": "Put a value in storage", + "Append to a value in storage": "Append to a value in storage", + "Remove a value from storage": "Remove a value from storage", + "Add Items to a list.": "Add Items to a list.", + "Remove Item from a list": "Remove Item from a list", + "Key": "Key", + "Default Value": "Default Value", + "Store Scope": "Store Scope", + "Value": "Value", + "Separator": "Separator", + "Ignore if value exists": "Ignore if value exists", + "The storage scope of the value.": "The storage scope of the value.", + "Separator between added values, use \\n for newlines": "Separator between added values, use \\n for newlines", + "Project": "Project", + "Flow": "Flow", + "Run": "Run" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/it.json b/packages/pieces/community/store/src/i18n/it.json new file mode 100644 index 0000000..38031bb --- /dev/null +++ b/packages/pieces/community/store/src/i18n/it.json @@ -0,0 +1,27 @@ +{ + "Storage": "Storage", + "Store or retrieve data from key/value database": "Store or retrieve data from key/value database", + "Get": "Get", + "Put": "Put", + "Append": "Append", + "Remove": "Remove", + "Add To List": "Add To List", + "Remove from List": "Remove from List", + "Get a value from storage": "Get a value from storage", + "Put a value in storage": "Put a value in storage", + "Append to a value in storage": "Append to a value in storage", + "Remove a value from storage": "Remove a value from storage", + "Add Items to a list.": "Add Items to a list.", + "Remove Item from a list": "Remove Item from a list", + "Key": "Key", + "Default Value": "Default Value", + "Store Scope": "Store Scope", + "Value": "Value", + "Separator": "Separator", + "Ignore if value exists": "Ignore if value exists", + "The storage scope of the value.": "The storage scope of the value.", + "Separator between added values, use \\n for newlines": "Separator between added values, use \\n for newlines", + "Project": "Project", + "Flow": "Flow", + "Run": "Run" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/ja.json b/packages/pieces/community/store/src/i18n/ja.json new file mode 100644 index 0000000..4119dbc --- /dev/null +++ b/packages/pieces/community/store/src/i18n/ja.json @@ -0,0 +1,27 @@ +{ + "Storage": "ストレージ", + "Store or retrieve data from key/value database": "キー/値データベースからデータを保存または取得します", + "Get": "取得", + "Put": "Put", + "Append": "追加", + "Remove": "削除", + "Add To List": "リストに追加", + "Remove from List": "リストから削除", + "Get a value from storage": "ストレージから値を取得する", + "Put a value in storage": "ストレージに値を置く", + "Append to a value in storage": "ストレージ内の値に追加", + "Remove a value from storage": "ストレージから値を削除", + "Add Items to a list.": "リストにアイテムを追加します。", + "Remove Item from a list": "リストからアイテムを削除", + "Key": "キー", + "Default Value": "デフォルト値", + "Store Scope": "ストアスコープ", + "Value": "値", + "Separator": "区切り記号", + "Ignore if value exists": "値が存在する場合は無視", + "The storage scope of the value.": "値のストレージスコープ。", + "Separator between added values, use \\n for newlines": "値の間の区切り文字。改行には \\n を使用します。", + "Project": "プロジェクト", + "Flow": "Flow", + "Run": "Run" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/ko.json b/packages/pieces/community/store/src/i18n/ko.json new file mode 100644 index 0000000..38031bb --- /dev/null +++ b/packages/pieces/community/store/src/i18n/ko.json @@ -0,0 +1,27 @@ +{ + "Storage": "Storage", + "Store or retrieve data from key/value database": "Store or retrieve data from key/value database", + "Get": "Get", + "Put": "Put", + "Append": "Append", + "Remove": "Remove", + "Add To List": "Add To List", + "Remove from List": "Remove from List", + "Get a value from storage": "Get a value from storage", + "Put a value in storage": "Put a value in storage", + "Append to a value in storage": "Append to a value in storage", + "Remove a value from storage": "Remove a value from storage", + "Add Items to a list.": "Add Items to a list.", + "Remove Item from a list": "Remove Item from a list", + "Key": "Key", + "Default Value": "Default Value", + "Store Scope": "Store Scope", + "Value": "Value", + "Separator": "Separator", + "Ignore if value exists": "Ignore if value exists", + "The storage scope of the value.": "The storage scope of the value.", + "Separator between added values, use \\n for newlines": "Separator between added values, use \\n for newlines", + "Project": "Project", + "Flow": "Flow", + "Run": "Run" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/nl.json b/packages/pieces/community/store/src/i18n/nl.json new file mode 100644 index 0000000..43ded05 --- /dev/null +++ b/packages/pieces/community/store/src/i18n/nl.json @@ -0,0 +1,27 @@ +{ + "Storage": "Opslagruimte", + "Store or retrieve data from key/value database": "Gegevens opslaan of ophalen uit de key/value database", + "Get": "Verkrijg", + "Put": "Zet", + "Append": "Toevoegen", + "Remove": "Verwijderen", + "Add To List": "Toevoegen aan lijst", + "Remove from List": "Verwijderen uit lijst", + "Get a value from storage": "Haal een waarde op uit de opslag", + "Put a value in storage": "Zet een waarde in de opslag", + "Append to a value in storage": "Voeg toe aan een waarde in de opslag", + "Remove a value from storage": "Een waarde uit de opslag verwijderen", + "Add Items to a list.": "Voeg items toe aan een lijst.", + "Remove Item from a list": "Item uit een lijst verwijderen", + "Key": "Sleutel", + "Default Value": "Standaard waarde", + "Store Scope": "Sla Toepassingsgebied op", + "Value": "Waarde", + "Separator": "Scheidingsteken", + "Ignore if value exists": "Negeren als waarde bestaat", + "The storage scope of the value.": "Het opslagbereik van de waarde.", + "Separator between added values, use \\n for newlines": "Scheidingsteken tussen toegevoegde waarden, gebruik \\n voor nieuwe regels", + "Project": "Project", + "Flow": "Stroom", + "Run": "Uitvoeren" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/pl.json b/packages/pieces/community/store/src/i18n/pl.json new file mode 100644 index 0000000..38031bb --- /dev/null +++ b/packages/pieces/community/store/src/i18n/pl.json @@ -0,0 +1,27 @@ +{ + "Storage": "Storage", + "Store or retrieve data from key/value database": "Store or retrieve data from key/value database", + "Get": "Get", + "Put": "Put", + "Append": "Append", + "Remove": "Remove", + "Add To List": "Add To List", + "Remove from List": "Remove from List", + "Get a value from storage": "Get a value from storage", + "Put a value in storage": "Put a value in storage", + "Append to a value in storage": "Append to a value in storage", + "Remove a value from storage": "Remove a value from storage", + "Add Items to a list.": "Add Items to a list.", + "Remove Item from a list": "Remove Item from a list", + "Key": "Key", + "Default Value": "Default Value", + "Store Scope": "Store Scope", + "Value": "Value", + "Separator": "Separator", + "Ignore if value exists": "Ignore if value exists", + "The storage scope of the value.": "The storage scope of the value.", + "Separator between added values, use \\n for newlines": "Separator between added values, use \\n for newlines", + "Project": "Project", + "Flow": "Flow", + "Run": "Run" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/pt.json b/packages/pieces/community/store/src/i18n/pt.json new file mode 100644 index 0000000..f89ffb3 --- /dev/null +++ b/packages/pieces/community/store/src/i18n/pt.json @@ -0,0 +1,27 @@ +{ + "Storage": "Armazenamento", + "Store or retrieve data from key/value database": "Armazenar ou recuperar dados de banco de dados chave/valor", + "Get": "Receber", + "Put": "Colocar", + "Append": "Anexar", + "Remove": "Remover", + "Add To List": "Adicionar à lista", + "Remove from List": "Remover da lista", + "Get a value from storage": "Obter um valor do armazenamento", + "Put a value in storage": "Coloque um valor no armazenamento", + "Append to a value in storage": "Acrescentar a um valor no armazenamento", + "Remove a value from storage": "Remover um valor do armazenamento", + "Add Items to a list.": "Adicionar itens a uma lista.", + "Remove Item from a list": "Remover o item de uma lista", + "Key": "Chave", + "Default Value": "Valor Padrão", + "Store Scope": "Escopo de Loja", + "Value": "Valor", + "Separator": "Separador", + "Ignore if value exists": "Ignorar se o valor existe", + "The storage scope of the value.": "O escopo de armazenamento do valor.", + "Separator between added values, use \\n for newlines": "Separador entre valores adicionados, use \\n para novas linhas", + "Project": "Projecto", + "Flow": "Fluxo", + "Run": "Executar" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/ru.json b/packages/pieces/community/store/src/i18n/ru.json new file mode 100644 index 0000000..6146918 --- /dev/null +++ b/packages/pieces/community/store/src/i18n/ru.json @@ -0,0 +1,27 @@ +{ + "Storage": "Хранилище", + "Store or retrieve data from key/value database": "Хранить или получать данные из базы данных ключ/значение", + "Get": "Приобрести", + "Put": "Положить", + "Append": "Добавить", + "Remove": "Remove", + "Add To List": "Добавить в список", + "Remove from List": "Удалить из списка", + "Get a value from storage": "Получить значение из хранилища", + "Put a value in storage": "Поместите значение в хранилище", + "Append to a value in storage": "Добавить к значению в хранилище", + "Remove a value from storage": "Удалить значение из хранилища", + "Add Items to a list.": "Добавить элементы в список.", + "Remove Item from a list": "Удалить элемент из списка", + "Key": "Спецификация", + "Default Value": "Значение по умолчанию", + "Store Scope": "Область магазина", + "Value": "Значение", + "Separator": "Разделитель", + "Ignore if value exists": "Игнорировать, если значение существует", + "The storage scope of the value.": "Область хранения значения.", + "Separator between added values, use \\n for newlines": "Разделитель между добавленными значениями, используйте \\n для новых строк", + "Project": "Проект", + "Flow": "Flow", + "Run": "Run" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/sv.json b/packages/pieces/community/store/src/i18n/sv.json new file mode 100644 index 0000000..38031bb --- /dev/null +++ b/packages/pieces/community/store/src/i18n/sv.json @@ -0,0 +1,27 @@ +{ + "Storage": "Storage", + "Store or retrieve data from key/value database": "Store or retrieve data from key/value database", + "Get": "Get", + "Put": "Put", + "Append": "Append", + "Remove": "Remove", + "Add To List": "Add To List", + "Remove from List": "Remove from List", + "Get a value from storage": "Get a value from storage", + "Put a value in storage": "Put a value in storage", + "Append to a value in storage": "Append to a value in storage", + "Remove a value from storage": "Remove a value from storage", + "Add Items to a list.": "Add Items to a list.", + "Remove Item from a list": "Remove Item from a list", + "Key": "Key", + "Default Value": "Default Value", + "Store Scope": "Store Scope", + "Value": "Value", + "Separator": "Separator", + "Ignore if value exists": "Ignore if value exists", + "The storage scope of the value.": "The storage scope of the value.", + "Separator between added values, use \\n for newlines": "Separator between added values, use \\n for newlines", + "Project": "Project", + "Flow": "Flow", + "Run": "Run" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/translation.json b/packages/pieces/community/store/src/i18n/translation.json new file mode 100644 index 0000000..38031bb --- /dev/null +++ b/packages/pieces/community/store/src/i18n/translation.json @@ -0,0 +1,27 @@ +{ + "Storage": "Storage", + "Store or retrieve data from key/value database": "Store or retrieve data from key/value database", + "Get": "Get", + "Put": "Put", + "Append": "Append", + "Remove": "Remove", + "Add To List": "Add To List", + "Remove from List": "Remove from List", + "Get a value from storage": "Get a value from storage", + "Put a value in storage": "Put a value in storage", + "Append to a value in storage": "Append to a value in storage", + "Remove a value from storage": "Remove a value from storage", + "Add Items to a list.": "Add Items to a list.", + "Remove Item from a list": "Remove Item from a list", + "Key": "Key", + "Default Value": "Default Value", + "Store Scope": "Store Scope", + "Value": "Value", + "Separator": "Separator", + "Ignore if value exists": "Ignore if value exists", + "The storage scope of the value.": "The storage scope of the value.", + "Separator between added values, use \\n for newlines": "Separator between added values, use \\n for newlines", + "Project": "Project", + "Flow": "Flow", + "Run": "Run" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/uk.json b/packages/pieces/community/store/src/i18n/uk.json new file mode 100644 index 0000000..38031bb --- /dev/null +++ b/packages/pieces/community/store/src/i18n/uk.json @@ -0,0 +1,27 @@ +{ + "Storage": "Storage", + "Store or retrieve data from key/value database": "Store or retrieve data from key/value database", + "Get": "Get", + "Put": "Put", + "Append": "Append", + "Remove": "Remove", + "Add To List": "Add To List", + "Remove from List": "Remove from List", + "Get a value from storage": "Get a value from storage", + "Put a value in storage": "Put a value in storage", + "Append to a value in storage": "Append to a value in storage", + "Remove a value from storage": "Remove a value from storage", + "Add Items to a list.": "Add Items to a list.", + "Remove Item from a list": "Remove Item from a list", + "Key": "Key", + "Default Value": "Default Value", + "Store Scope": "Store Scope", + "Value": "Value", + "Separator": "Separator", + "Ignore if value exists": "Ignore if value exists", + "The storage scope of the value.": "The storage scope of the value.", + "Separator between added values, use \\n for newlines": "Separator between added values, use \\n for newlines", + "Project": "Project", + "Flow": "Flow", + "Run": "Run" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/vi.json b/packages/pieces/community/store/src/i18n/vi.json new file mode 100644 index 0000000..38031bb --- /dev/null +++ b/packages/pieces/community/store/src/i18n/vi.json @@ -0,0 +1,27 @@ +{ + "Storage": "Storage", + "Store or retrieve data from key/value database": "Store or retrieve data from key/value database", + "Get": "Get", + "Put": "Put", + "Append": "Append", + "Remove": "Remove", + "Add To List": "Add To List", + "Remove from List": "Remove from List", + "Get a value from storage": "Get a value from storage", + "Put a value in storage": "Put a value in storage", + "Append to a value in storage": "Append to a value in storage", + "Remove a value from storage": "Remove a value from storage", + "Add Items to a list.": "Add Items to a list.", + "Remove Item from a list": "Remove Item from a list", + "Key": "Key", + "Default Value": "Default Value", + "Store Scope": "Store Scope", + "Value": "Value", + "Separator": "Separator", + "Ignore if value exists": "Ignore if value exists", + "The storage scope of the value.": "The storage scope of the value.", + "Separator between added values, use \\n for newlines": "Separator between added values, use \\n for newlines", + "Project": "Project", + "Flow": "Flow", + "Run": "Run" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/i18n/zh.json b/packages/pieces/community/store/src/i18n/zh.json new file mode 100644 index 0000000..5f29f24 --- /dev/null +++ b/packages/pieces/community/store/src/i18n/zh.json @@ -0,0 +1,27 @@ +{ + "Storage": "存储", + "Store or retrieve data from key/value database": "存储或从密钥/值数据库检索数据", + "Get": "获取", + "Put": "放入...", + "Append": "追加文件", + "Remove": "删除", + "Add To List": "添加到列表", + "Remove from List": "从列表中删除", + "Get a value from storage": "从存储获取一个值", + "Put a value in storage": "将一个值放入存储", + "Append to a value in storage": "附加到存储中的值", + "Remove a value from storage": "从存储中删除一个值", + "Add Items to a list.": "添加项目到列表。", + "Remove Item from a list": "从列表中删除项目", + "Key": "关键字", + "Default Value": "默认值", + "Store Scope": "商店范围", + "Value": "值", + "Separator": "分隔符", + "Ignore if value exists": "忽略如果值存在", + "The storage scope of the value.": "值的存储范围。", + "Separator between added values, use \\n for newlines": "添加值之间的分隔符,换行符为 \\n", + "Project": "项目", + "Flow": "流", + "Run": "运行" +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/index.ts b/packages/pieces/community/store/src/index.ts new file mode 100644 index 0000000..c9264ec --- /dev/null +++ b/packages/pieces/community/store/src/index.ts @@ -0,0 +1,27 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { storageAddtoList } from './lib/actions/store-add-to-list'; +import { storageAppendAction } from './lib/actions/store-append-action'; +import { storageGetAction } from './lib/actions/store-get-action'; +import { storagePutAction } from './lib/actions/store-put-action'; +import { storageRemoveFromList } from './lib/actions/store-remove-from-list'; +import { storageRemoveValue } from './lib/actions/store-remove-value'; + +export const storage = createPiece({ + displayName: 'Storage', + description: 'Store or retrieve data from key/value database', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/store.png', + categories: [PieceCategory.CORE], + auth: PieceAuth.None(), + authors: ["JanHolger","fardeenpanjwani-codeglo","Abdallah-Alwarawreh","Salem-Alaa","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + actions: [ + storageGetAction, + storagePutAction, + storageAppendAction, + storageRemoveValue, + storageAddtoList, + storageRemoveFromList, + ], + triggers: [], +}); diff --git a/packages/pieces/community/store/src/lib/actions/common.ts b/packages/pieces/community/store/src/lib/actions/common.ts new file mode 100644 index 0000000..9891390 --- /dev/null +++ b/packages/pieces/community/store/src/lib/actions/common.ts @@ -0,0 +1,49 @@ +import { Property, StoreScope } from "@activepieces/pieces-framework" + +export enum PieceStoreScope { + PROJECT = 'COLLECTION', + FLOW = 'FLOW', + RUN = 'RUN', +} + +export function getScopeAndKey(params: Params): { scope: StoreScope, key: string } { + switch (params.scope) { + case PieceStoreScope.PROJECT: + return { scope: StoreScope.PROJECT, key: params.key } + case PieceStoreScope.FLOW: + return { scope: StoreScope.FLOW, key: params.key } + case PieceStoreScope.RUN: + return { scope: StoreScope.FLOW, key: `run_${params.runId}/${params.key}` } + } +} + +type Params = { + runId: string + key: string + scope: PieceStoreScope +} + +export const common = { + store_scope: Property.StaticDropdown({ + displayName: 'Store Scope', + description: 'The storage scope of the value.', + required: true, + options: { + options: [ + { + label: 'Project', + value: PieceStoreScope.PROJECT, + }, + { + label: 'Flow', + value: PieceStoreScope.FLOW, + }, + { + label: 'Run', + value: PieceStoreScope.RUN, + }, + ], + }, + defaultValue: PieceStoreScope.PROJECT, + }) +} \ No newline at end of file diff --git a/packages/pieces/community/store/src/lib/actions/store-add-to-list.ts b/packages/pieces/community/store/src/lib/actions/store-add-to-list.ts new file mode 100644 index 0000000..8b5d13e --- /dev/null +++ b/packages/pieces/community/store/src/lib/actions/store-add-to-list.ts @@ -0,0 +1,76 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; +import deepEqual from 'deep-equal'; +import { common, getScopeAndKey } from './common'; +export const storageAddtoList = createAction({ + name: 'add_to_list', + displayName: 'Add To List', + description: 'Add Items to a list.', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + key: Property.ShortText({ + displayName: 'Key', + required: true, + }), + value: Property.Array({ + displayName: 'Value', + required: true, + }), + ignore_if_exists: Property.Checkbox({ + displayName: 'Ignore if value exists', + required: false, + }), + store_scope: common.store_scope, + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + key: z.string().max(128), + }); + const { key, scope } = getScopeAndKey({ + runId: context.run.id, + key: context.propsValue['key'], + scope: context.propsValue.store_scope, + }); + const inputItems = context.propsValue.value ?? []; + let parsedInputItems: unknown[] = []; + try { + parsedInputItems = typeof inputItems === 'string' ? JSON.parse(inputItems) : inputItems; + if (!Array.isArray(parsedInputItems)) { + throw new Error(`Provided value is not a list.`); + } + } catch (err) { + throw new Error(`An unexpected error occurred: ${(err as Error).message}`); + } + // Get existing items from store + let items = (await context.store.get(key, scope)) ?? []; + try { + if (typeof items === 'string') { + items = JSON.parse(items); + } + if (!Array.isArray(items)) { + throw new Error(`Key ${context.propsValue['key']} is not a list.`); + } + } catch (err) { + throw new Error(`An unexpected error occurred: ${(err as Error).message}`); + } + if (context.propsValue['ignore_if_exists']) { + for (const newItem of parsedInputItems) { + const exists = items.some((existingItem) => deepEqual(existingItem, newItem)); + if (!exists) { + items.push(newItem); + } + } + } else { + items.push(...parsedInputItems); + } + return context.store.put(key, items, scope); + }, +}); diff --git a/packages/pieces/community/store/src/lib/actions/store-append-action.ts b/packages/pieces/community/store/src/lib/actions/store-append-action.ts new file mode 100644 index 0000000..bff9e18 --- /dev/null +++ b/packages/pieces/community/store/src/lib/actions/store-append-action.ts @@ -0,0 +1,62 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { common, getScopeAndKey } from './common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; + +export const storageAppendAction = createAction({ + name: 'append', + displayName: 'Append', + description: 'Append to a value in storage', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + key: Property.ShortText({ + displayName: 'Key', + required: true, + }), + value: Property.ShortText({ + displayName: 'Value', + required: true, + }), + separator: Property.ShortText({ + displayName: 'Separator', + description: 'Separator between added values, use \\n for newlines', + required: false, + }), + store_scope: common.store_scope, + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + key: z.string().max(128), + }); + + const { key, scope } = getScopeAndKey({ + runId: context.run.id, + key: context.propsValue['key'], + scope: context.propsValue.store_scope, + }); + const oldValue = (await context.store.get(key, scope)) || ''; + if (typeof oldValue !== 'string') { + throw new Error(`Key ${context.propsValue.key} is not a string`); + } + const appendValue = context.propsValue.value; + if (appendValue === '' || isNil(appendValue)) { + return oldValue; + } + let separator = context.propsValue.separator || ''; + separator = separator.replace(/\\n/g, '\n'); // Allow newline escape sequence + const newValue = + oldValue + (oldValue.length > 0 ? separator : '') + appendValue; + return await context.store.put(key, newValue, scope); + }, +}); diff --git a/packages/pieces/community/store/src/lib/actions/store-get-action.ts b/packages/pieces/community/store/src/lib/actions/store-get-action.ts new file mode 100644 index 0000000..c043fc2 --- /dev/null +++ b/packages/pieces/community/store/src/lib/actions/store-get-action.ts @@ -0,0 +1,68 @@ +import { + createAction, + Property, + StoreScope, +} from '@activepieces/pieces-framework'; +import { getScopeAndKey, PieceStoreScope } from './common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const storageGetAction = createAction({ + name: 'get', + displayName: 'Get', + description: 'Get a value from storage', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + key: Property.ShortText({ + displayName: 'Key', + required: true, + }), + defaultValue: Property.ShortText({ + displayName: 'Default Value', + required: false, + }), + store_scope: Property.StaticDropdown({ + displayName: 'Store Scope', + description: 'The storage scope of the value.', + required: true, + options: { + options: [ + { + label: 'Project', + value: PieceStoreScope.PROJECT, + }, + { + label: 'Flow', + value: PieceStoreScope.FLOW, + }, + { + label: 'Run', + value: PieceStoreScope.RUN, + }, + ], + }, + defaultValue: StoreScope.PROJECT, + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + key: z.string().max(128), + }); + + const { key, scope } = getScopeAndKey({ + runId: context.run.id, + key: context.propsValue['key'], + scope: context.propsValue.store_scope, + }); + return ( + (await context.store.get(key, scope)) ?? context.propsValue['defaultValue'] + ); + }, +}); diff --git a/packages/pieces/community/store/src/lib/actions/store-put-action.ts b/packages/pieces/community/store/src/lib/actions/store-put-action.ts new file mode 100644 index 0000000..2821dc6 --- /dev/null +++ b/packages/pieces/community/store/src/lib/actions/store-put-action.ts @@ -0,0 +1,48 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { common, getScopeAndKey } from './common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const storagePutAction = createAction({ + name: 'put', + displayName: 'Put', + description: 'Put a value in storage', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + key: Property.ShortText({ + displayName: 'Key', + required: true, + }), + value: Property.ShortText({ + displayName: 'Value', + required: true, + }), + store_scope: common.store_scope, + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + key: z.string().max(128), + }); + + const { key, scope } = getScopeAndKey({ + runId: context.run.id, + key: context.propsValue['key'], + scope: context.propsValue.store_scope, + }); + return await context.store.put( + key, + context.propsValue['value'], + scope + ); + }, +}); diff --git a/packages/pieces/community/store/src/lib/actions/store-remove-from-list.ts b/packages/pieces/community/store/src/lib/actions/store-remove-from-list.ts new file mode 100644 index 0000000..b0567fa --- /dev/null +++ b/packages/pieces/community/store/src/lib/actions/store-remove-from-list.ts @@ -0,0 +1,70 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; +import deepEqual from 'deep-equal'; +import { common, getScopeAndKey } from './common'; + +export const storageRemoveFromList = createAction({ + name: 'remove_from_list', + displayName: 'Remove from List', + description: 'Remove Item from a list', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + key: Property.ShortText({ + displayName: 'Key', + required: true, + }), + value: Property.ShortText({ + displayName: 'Value', + required: true, + }), + store_scope: common.store_scope, + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + key: z.string().max(128), + }); + + const { key, scope } = getScopeAndKey({ + runId: context.run.id, + key: context.propsValue['key'], + scope: context.propsValue.store_scope, + }); + const items = + (await context.store.get( + key, + scope, + )) ?? []; + if (!Array.isArray(items)) { + throw new Error(`Key ${context.propsValue['key']} is not an array`); + } + for (let i = 0; i < items.length; i++) { + if (deepEqual(items[i], context.propsValue['value'])) { + items.splice(i, 1); + return await context.store.put( + key, + items, + scope, + ); + } + } + if (items.includes(context.propsValue['value'])) { + items.splice(items.indexOf(context.propsValue['value']), 1); + } + return await context.store.put( + key, + items, + scope, + ); + }, +}); diff --git a/packages/pieces/community/store/src/lib/actions/store-remove-value.ts b/packages/pieces/community/store/src/lib/actions/store-remove-value.ts new file mode 100644 index 0000000..cad4734 --- /dev/null +++ b/packages/pieces/community/store/src/lib/actions/store-remove-value.ts @@ -0,0 +1,44 @@ +import { + createAction, + Property, + StoreScope, +} from '@activepieces/pieces-framework'; +import { common, getScopeAndKey } from './common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const storageRemoveValue = createAction({ + name: 'remove_value', + displayName: 'Remove', + description: 'Remove a value from storage', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + key: Property.ShortText({ + displayName: 'Key', + required: true, + }), + store_scope: common.store_scope, + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + key: z.string().max(128), + }); + + const { key, scope } = getScopeAndKey({ + runId: context.run.id, + key: context.propsValue['key'], + scope: context.propsValue.store_scope, + }); + await context.store.delete(key, scope); + return { + success: true, + }; + }, +}); diff --git a/packages/pieces/community/store/tsconfig.json b/packages/pieces/community/store/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/store/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/store/tsconfig.lib.json b/packages/pieces/community/store/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/store/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/straico/.eslintrc.json b/packages/pieces/community/straico/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/straico/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/straico/README.md b/packages/pieces/community/straico/README.md new file mode 100644 index 0000000..50a3fea --- /dev/null +++ b/packages/pieces/community/straico/README.md @@ -0,0 +1,7 @@ +# pieces-straico + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-straico` to build the library. diff --git a/packages/pieces/community/straico/package.json b/packages/pieces/community/straico/package.json new file mode 100644 index 0000000..9d1033f --- /dev/null +++ b/packages/pieces/community/straico/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-straico", + "version": "0.1.2" +} \ No newline at end of file diff --git a/packages/pieces/community/straico/project.json b/packages/pieces/community/straico/project.json new file mode 100644 index 0000000..f69131c --- /dev/null +++ b/packages/pieces/community/straico/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-straico", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/straico/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/straico", + "tsConfig": "packages/pieces/community/straico/tsconfig.lib.json", + "packageJson": "packages/pieces/community/straico/package.json", + "main": "packages/pieces/community/straico/src/index.ts", + "assets": [ + "packages/pieces/community/straico/*.md", + { + "input": "packages/pieces/community/straico/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-straico {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/straico/src/index.ts b/packages/pieces/community/straico/src/index.ts new file mode 100644 index 0000000..cdca6ee --- /dev/null +++ b/packages/pieces/community/straico/src/index.ts @@ -0,0 +1,98 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + createCustomApiCallAction, + httpClient, +} from '@activepieces/pieces-common'; +import { baseUrlv1 } from './lib/common/common'; +import { promptCompletion } from './lib/actions/prompt-completion'; +import { imageGeneration } from './lib/actions/image-generation'; +import { fileUpload } from './lib/actions/file-upload'; +import { createRag } from './lib/actions/rag-create'; +import { listRags } from './lib/actions/rag-list'; +import { getRagById } from './lib/actions/rag-get-by-id'; +import { updateRag } from './lib/actions/rag-update'; +import { deleteRag } from './lib/actions/rag-delete'; +import { ragPromptCompletion } from './lib/actions/rag-prompt-completion'; +import { agentCreate } from './lib/actions/agent-create'; +import { agentAddRag } from './lib/actions/agent-add-rag'; +import { agentList } from './lib/actions/agent-list'; +import { agentDelete } from './lib/actions/agent-delete'; +import { agentUpdate } from './lib/actions/agent-update'; +import { agentGet } from './lib/actions/agent-get'; +import { agentPromptCompletion } from './lib/actions/agent-prompt-completion'; +import { PieceCategory } from '@activepieces/shared'; + +const markdownDescription = ` +Follow these instructions to get your Straico API Key: + +1. Visit the following website: https://platform.straico.com/user-settings. +2. Once on the website, locate "Connect with Straico API" and click on the copy API Key. +`; + +export const straicoAuth = PieceAuth.SecretText({ + description: markdownDescription, + displayName: 'API Key', + required: true, + validate: async (auth) => { + try { + await httpClient.sendRequest<{ + data: { model: string }[]; + }>({ + url: `${baseUrlv1}/models`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.auth as string, + }, + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API key', + }; + } + }, +}); + +export const straico = createPiece({ + displayName: 'Straico', + auth: straicoAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/straico.png', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + description: 'All-in-one generative AI platform', + authors: ['dennisrongo'], + actions: [ + promptCompletion, + imageGeneration, + fileUpload, + createRag, + listRags, + getRagById, + updateRag, + deleteRag, + ragPromptCompletion, + agentCreate, + agentAddRag, + agentList, + agentDelete, + agentUpdate, + agentGet, + agentPromptCompletion, + createCustomApiCallAction({ + auth: straicoAuth, + baseUrl: () => baseUrlv1, + authMapping: async (auth) => { + return { + Authorization: `Bearer ${auth}`, + }; + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/straico/src/lib/actions/agent-add-rag.ts b/packages/pieces/community/straico/src/lib/actions/agent-add-rag.ts new file mode 100644 index 0000000..40b46a7 --- /dev/null +++ b/packages/pieces/community/straico/src/lib/actions/agent-add-rag.ts @@ -0,0 +1,102 @@ +import { straicoAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; + +import { baseUrlv0 } from '../common/common'; +import { agentIdDropdown } from '../common/props'; + +interface AgentAddRagResponse { + success: boolean; + data: { + uuid4: string; + user_id: string; + default_llm: string; + custom_prompt: string; + name: string; + description: string; + status: string; + tags: string[]; + last_interaction: null | string; + interaction_count: number; + visibility: string; + _id: string; + }; +} + +export const agentAddRag = createAction({ + auth: straicoAuth, + name: 'agent-add-rag', + displayName: 'Add RAG to Agent', + description: 'Adds a new RAG to an agent in the database for the user.', + props: { + agent_id: agentIdDropdown('Agent','The agent to add the RAG to.'), + rag_id: Property.Dropdown({ + displayName: 'RAG ID', + required: true, + description: 'The ID of the RAG to add to the agent', + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Enter your API key first', + options: [], + }; + } + try { + const rags = await httpClient.sendRequest<{ + success: boolean; + data: Array<{ + _id: string; + name: string; + }>; + }>({ + url: `${baseUrlv0}/rag/user`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + return { + disabled: false, + options: + rags.body?.data?.map((rag) => { + return { + label: rag.name, + value: rag._id, + }; + }) || [], + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: "Couldn't load RAGs, API key is invalid", + }; + } + }, + }), + }, + async run({ auth, propsValue }) { + const { agent_id, rag_id } = propsValue; + + const response = await httpClient.sendRequest({ + url: `${baseUrlv0}/agent/${agent_id}/rag`, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + body: { + rag: rag_id, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/straico/src/lib/actions/agent-create.ts b/packages/pieces/community/straico/src/lib/actions/agent-create.ts new file mode 100644 index 0000000..18fac70 --- /dev/null +++ b/packages/pieces/community/straico/src/lib/actions/agent-create.ts @@ -0,0 +1,137 @@ +import { straicoAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; + +import { baseUrlv0, baseUrlv1 } from '../common/common'; + +interface AgentCreateRequest { + name: string; + description: string; + custom_prompt: string; + default_llm: string; + tags?: string[]; +} + +interface AgentCreateResponse { + success: boolean; + data: { + uuid4: string; + user_id: string; + default_llm: string; + custom_prompt: string; + name: string; + description: string; + status: string; + tags: string[]; + last_interaction: null | string; + interaction_count: number; + visibility: string; + _id: string; + __v: number; + }; +} + +export const agentCreate = createAction({ + auth: straicoAuth, + name: 'agent-create', + displayName: 'Create Agent', + description: 'Creates a new agent in the database for the user.', + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + description: 'A name for the agent', + }), + description: Property.LongText({ + displayName: 'Description', + required: true, + description: 'A brief description of what the model does', + }), + custom_prompt: Property.LongText({ + displayName: 'Custom Prompt', + required: true, + description: 'A model that the agent will use for processing prompts', + }), + default_llm: Property.Dropdown({ + displayName: 'Default LLM', + required: true, + description: 'The language model which the agent will use for processing prompts', + refreshers: [], + defaultValue: 'openai/gpt-4o-mini', + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Enter your API key first', + options: [], + }; + } + try { + const models = await httpClient.sendRequest<{ + data: { + chat: Array<{ + name: string; + model: string; + }>; + }; + }>({ + url: `${baseUrlv1}/models`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + return { + disabled: false, + options: + models.body?.data?.chat?.map((model) => { + return { + label: model.name, + value: model.model, + }; + }) || [], + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: "Couldn't load models, API key is invalid", + }; + } + }, + }), + tags: Property.Array({ + displayName: 'Tags', + required: false, + description: 'An array of tags for the agent. Example: ["assistant","tag"]', + }), + }, + async run({ auth, propsValue }) { + const { name, description, custom_prompt, default_llm, tags } = propsValue; + + const requestBody: AgentCreateRequest = { + name, + description, + custom_prompt, + default_llm, + tags: tags as string[], + }; + + const response = await httpClient.sendRequest({ + url: `${baseUrlv0}/agent`, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + body: requestBody, + }); + + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/straico/src/lib/actions/agent-delete.ts b/packages/pieces/community/straico/src/lib/actions/agent-delete.ts new file mode 100644 index 0000000..dc4ccf6 --- /dev/null +++ b/packages/pieces/community/straico/src/lib/actions/agent-delete.ts @@ -0,0 +1,40 @@ +import { straicoAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { baseUrlv0 } from '../common/common'; +import { agentIdDropdown } from '../common/props'; + +export const agentDelete = createAction({ + auth: straicoAuth, + name: 'agent_delete', + displayName: 'Delete Agent', + description: 'Delete a specific agent by its ID', + props: { + agentId: agentIdDropdown('Agent','Select the agent to delete') + }, + async run({ auth, propsValue }) { + const { agentId } = propsValue; + + if (!agentId) { + throw new Error('Agent ID is required'); + } + + const response = await httpClient.sendRequest<{ + success: boolean; + message: string; + }>({ + url: `${baseUrlv0}/agent/${agentId}`, + method: HttpMethod.DELETE, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/straico/src/lib/actions/agent-get.ts b/packages/pieces/community/straico/src/lib/actions/agent-get.ts new file mode 100644 index 0000000..6767404 --- /dev/null +++ b/packages/pieces/community/straico/src/lib/actions/agent-get.ts @@ -0,0 +1,56 @@ +import { straicoAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { baseUrlv0 } from '../common/common'; +import { agentIdDropdown } from '../common/props'; + +interface AgentGetResponse { + _id: string; + uuid4: string; + user_id: string; + default_llm: string; + custom_prompt: string; + name: string; + description: string; + status: string; + tags: string[]; + last_interaction: null | string; + interaction_count: number; + visibility: string; + rag_association?: string; +} + +export const agentGet = createAction({ + auth: straicoAuth, + name: 'agent_get', + displayName: 'Get Agent Details', + description: 'Retrieve details of a specific agent', + props: { + agentId:agentIdDropdown('Agent','Select the agent to get details for.') + }, + async run({ auth, propsValue }) { + const { agentId } = propsValue; + + if (!agentId) { + throw new Error('Agent ID is required'); + } + + const response = await httpClient.sendRequest<{ + success: boolean; + data: AgentGetResponse; + }>({ + url: `${baseUrlv0}/agent/${agentId}`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + + return response.body.data; + }, +}); diff --git a/packages/pieces/community/straico/src/lib/actions/agent-list.ts b/packages/pieces/community/straico/src/lib/actions/agent-list.ts new file mode 100644 index 0000000..b7f0a4e --- /dev/null +++ b/packages/pieces/community/straico/src/lib/actions/agent-list.ts @@ -0,0 +1,47 @@ +import { straicoAuth } from '../../index'; +import { createAction } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { baseUrlv0 } from '../common/common'; + +interface Agent { + _id: string; + uuid4: string; + user_id: string; + default_llm: string; + custom_prompt: string; + name: string; + description: string; + status: string; + tags: string[]; + __v: number; + rag_association?: string; +} + +interface AgentListResponse { + success: boolean; + data: Agent[]; +} + +export const agentList = createAction({ + auth: straicoAuth, + name: 'agent-list', + displayName: 'List Agents', + description: 'Retrieves the list of agents created by and available to the user.', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + url: `${baseUrlv0}/agent`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + + return response.body.data; + }, +}); diff --git a/packages/pieces/community/straico/src/lib/actions/agent-prompt-completion.ts b/packages/pieces/community/straico/src/lib/actions/agent-prompt-completion.ts new file mode 100644 index 0000000..f9c5043 --- /dev/null +++ b/packages/pieces/community/straico/src/lib/actions/agent-prompt-completion.ts @@ -0,0 +1,119 @@ +import { straicoAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { baseUrlv0 } from '../common/common'; +import { agentIdDropdown } from '../common/props'; + +export const agentPromptCompletion = createAction({ + auth: straicoAuth, + name: 'agent_prompt_completion', + displayName: 'Agent Prompt Completion', + description: 'Prompt an agent with a message and get a response', + props: { + agentId: agentIdDropdown('Agent','Select the agent to prompt.'), + prompt: Property.LongText({ + displayName: 'Prompt', + required: true, + description: 'The text prompt for the agent', + }), + searchType: Property.StaticDropdown({ + displayName: 'Search Type', + required: false, + description: 'The search type to use for RAG model', + options: { + disabled:false, + options: [ + { label: 'Similarity', value: 'similarity' }, + { label: 'MMR', value: 'mmr' }, + { label: 'Similarity Score Threshold', value: 'similarity_score_threshold' }, + ], + + }, + }), + k: Property.Number({ + displayName: 'Number of Documents', + required: false, + description: 'Number of documents to return', + }), + fetchK: Property.Number({ + displayName: 'Fetch K', + required: false, + description: 'Amount of documents to pass to MMR algorithm', + }), + lambdaMult: Property.Number({ + displayName: 'Lambda Mult', + required: false, + description: 'Diversity of results returned by MMR (0 for minimum, 1 for maximum)', + }), + scoreThreshold: Property.Number({ + displayName: 'Score Threshold', + required: false, + description: 'Minimum relevance threshold for similarity_score_threshold', + }), + }, + async run({ auth, propsValue }) { + const { + agentId, + prompt, + searchType, + k, + fetchK, + lambdaMult, + scoreThreshold + } = propsValue; + + if (!agentId) { + throw new Error('Agent ID is required'); + } + + if (!prompt) { + throw new Error('Prompt is required'); + } + + const requestBody: Record = { + prompt, + }; + + const optionalParams = { + search_type: searchType, + k, + fetch_k: fetchK, + lambda_mult: lambdaMult, + score_threshold: scoreThreshold + }; + + Object.entries(optionalParams).forEach(([key, value]) => { + if (value !== undefined) { + requestBody[key] = value; + } + }); + + const response = await httpClient.sendRequest<{ + success: boolean; + data: { + answer: string; + references: Array<{ + page_content: string; + page: number; + }>; + file_name: string; + coins_used: number; + response: unknown; + }; + }>({ + url: `${baseUrlv0}/agent/${agentId}/prompt`, + method: HttpMethod.POST, + body: requestBody, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + + return response.body.data; + }, +}); diff --git a/packages/pieces/community/straico/src/lib/actions/agent-update.ts b/packages/pieces/community/straico/src/lib/actions/agent-update.ts new file mode 100644 index 0000000..6d686b7 --- /dev/null +++ b/packages/pieces/community/straico/src/lib/actions/agent-update.ts @@ -0,0 +1,229 @@ +import { straicoAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { baseUrlv0, baseUrlv1 } from '../common/common'; + +// Interface for agent update request body +interface AgentUpdateRequestBody { + name?: string; + description?: string; + custom_prompt?: string; + default_llm?: string; + status?: 'active' | 'inactive'; + visibility?: 'private' | 'public'; +} + +// Interface for agent update response data +interface AgentUpdateResponse { + _id: string; + uuid4: string; + user_id: string; + default_llm: string; + custom_prompt: string; + name: string; + description: string; + status: string; + tags: string[]; + last_interaction: null | string; + interaction_count: number; + visibility: string; + createdAt: string; + updatedAt: string; + __v: number; + rag_association?: string; +} + +export const agentUpdate = createAction({ + auth: straicoAuth, + name: 'agent_update', + displayName: 'Update Agent', + description: 'Update the details of a specific agent', + props: { + agentId: Property.Dropdown({ + displayName: 'Agent', + required: true, + description: 'Select the agent to update', + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please authenticate first', + options: [], + }; + } + + const response = await httpClient.sendRequest<{ + success: boolean; + data: Array<{ + _id: string; + name: string; + }>; + }>({ + url: `${baseUrlv0}/agent`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + + if (response.body.success && response.body.data) { + return { + options: response.body.data.map((agent) => { + return { + label: agent.name, + value: agent._id, + }; + }), + }; + } + + return { + disabled: true, + placeholder: 'No agents found', + options: [], + }; + }, + }), + name: Property.ShortText({ + displayName: 'Name', + required: false, + description: 'New name for the agent', + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + description: 'New description for the agent', + }), + customPrompt: Property.LongText({ + displayName: 'Custom Prompt', + required: false, + description: 'New custom prompt for the agent', + }), + defaultLlm: Property.Dropdown({ + displayName: 'Default LLM', + required: false, + description: 'New default LLM for the agent', + refreshers: ['auth'], + defaultValue: 'openai/gpt-4o-mini', + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Enter your API key first', + options: [], + }; + } + try { + const models = await httpClient.sendRequest<{ + data: { + chat: Array<{ + name: string; + model: string; + }>; + }; + }>({ + url: `${baseUrlv1}/models`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + return { + disabled: false, + options: + models.body?.data?.chat?.map((model) => { + return { + label: model.name, + value: model.model, + }; + }) || [], + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: "Couldn't load models, API key is invalid", + }; + } + }, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + required: false, + description: 'New status for the agent', + options: { + disabled:false, + options: [ + { label: 'Active', value: 'active' }, + { label: 'Inactive', value: 'inactive' }, + ], + + }, + }), + visibility: Property.StaticDropdown({ + displayName: 'Visibility', + required: false, + description: 'New visibility setting for the agent', + options: { + disabled:false, + options: [ + { label: 'Private', value: 'private' }, + { label: 'Public', value: 'public' }, + ], + + }, + }), + }, + async run({ auth, propsValue }) { + const { + agentId, + name, + description, + customPrompt, + defaultLlm, + status, + visibility + } = propsValue; + + if (!agentId) { + throw new Error('Agent ID is required'); + } + + const requestBody: AgentUpdateRequestBody = {}; + + // Add properties to request body only if they are defined + if (name !== undefined) requestBody.name = name; + if (description !== undefined) requestBody.description = description; + if (customPrompt !== undefined) requestBody.custom_prompt = customPrompt; + if (defaultLlm !== undefined) requestBody.default_llm = defaultLlm; + if (status !== undefined) requestBody.status = status as 'active' | 'inactive'; + if (visibility !== undefined) requestBody.visibility = visibility as 'private' | 'public'; + + // Only proceed if at least one property to update was provided + if (Object.keys(requestBody).length === 0) { + throw new Error('At least one property to update must be provided'); + } + + const response = await httpClient.sendRequest<{ + success: boolean; + data: AgentUpdateResponse; + }>({ + url: `${baseUrlv0}/agent/${agentId}`, + method: HttpMethod.PUT, + body: requestBody, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + + return response.body.data; + }, +}); diff --git a/packages/pieces/community/straico/src/lib/actions/file-upload.ts b/packages/pieces/community/straico/src/lib/actions/file-upload.ts new file mode 100644 index 0000000..e514803 --- /dev/null +++ b/packages/pieces/community/straico/src/lib/actions/file-upload.ts @@ -0,0 +1,58 @@ +import { straicoAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { baseUrlv0 } from '../common/common'; + +const SUPPORTED_FILE_TYPES = [ + 'pdf', 'docx', 'pptx', 'txt', 'xlsx', 'mp3', 'mp4', + 'html', 'csv', 'json', 'py', 'php', 'js', 'css', + 'cs', 'swift', 'kt', 'xml', 'ts', 'png', 'jpg', + 'jpeg', 'webp', 'gif' +]; + +export const fileUpload = createAction({ + auth: straicoAuth, + name: 'file_upload', + displayName: 'Upload File', + description: 'Upload a file to Straico API for processing.', + props: { + file: Property.File({ + displayName: 'File', + required: true, + description: 'The file to upload. Supported file types: pdf, docx, pptx, txt, xlsx, mp3, mp4, html, csv, json, py, php, js, css, cs, swift, kt, xml, ts, png, jpg, jpeg, webp, gif', + }), + }, + async run({ auth, propsValue }) { + const fileExtension = propsValue.file.filename.split('.').pop()?.toLowerCase(); + if (!fileExtension || !SUPPORTED_FILE_TYPES.includes(fileExtension)) { + throw new Error(`File type not supported. Supported types are: ${SUPPORTED_FILE_TYPES.join(', ')}`); + } + + const formData = new FormData(); + formData.append('file', new Blob([propsValue.file.data]), propsValue.file.filename); + + const response = await httpClient.sendRequest<{ + data: { + url: string; + }; + success: boolean; + }>({ + url: `${baseUrlv0}/file/upload`, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + body: formData, + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + return response.body.data.url; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/straico/src/lib/actions/image-generation.ts b/packages/pieces/community/straico/src/lib/actions/image-generation.ts new file mode 100644 index 0000000..8fa0bd9 --- /dev/null +++ b/packages/pieces/community/straico/src/lib/actions/image-generation.ts @@ -0,0 +1,106 @@ +import { straicoAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; + +import { baseUrlv0 } from '../common/common'; + +export const imageGeneration = createAction({ + auth: straicoAuth, + name: 'image_generation', + displayName: 'Image Generation', + description: 'Enables users to generate high-quality images based on textual descriptions.', + props: { + variations: Property.StaticDropdown({ + displayName: 'Number of Images', + required: true, + description: + 'Number of images to generate.', + defaultValue: 1, + options: { + disabled: false, + options: [ + { value: 1, label: '1' }, + { value: 2, label: '2' }, + { value: 3, label: '3' }, + { value: 4, label: '4' }, + ], + }, + }), + model: Property.StaticDropdown({ + displayName: 'Model', + required: true, + description: 'Select the image generation model.', + defaultValue: 'openai/dall-e-3', + options: { + disabled: false, + options: [ + { value: 'openai/dall-e-3', label: 'openai/dall-e-3' }, + { value: 'flux/1.1', label: 'flux/1.1' }, + { value: 'ideogram/V_2A', label: 'ideogram/V_2A' }, + { value: 'ideogram/V_2A_TURBO', label: 'ideogram/V_2A_TURBO' }, + { value: 'ideogram/V_2', label: 'ideogram/V_2' }, + { value: 'ideogram/V_2_TURBO', label: 'ideogram/V_2_TURBO' }, + { value: 'ideogram/V_1', label: 'ideogram/V_1' }, + { value: 'ideogram/V_1_TURBO', label: 'ideogram/V_1_TURBO' } + ], + }, + }), + size: Property.StaticDropdown({ + displayName: 'Image Dimensions', + required: true, + description: + 'The desired image dimensions.', + defaultValue: 1, + options: { + disabled: false, + options: [ + { value: 'square', label: 'square' }, + { value: 'landscape', label: 'landscape' }, + { value: 'portrait', label: 'portrait' } + ], + }, + }), + description: Property.LongText({ + displayName: 'Description', + required: true, + description: + 'A detailed textual description of the image to be generated.', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest<{ + data: { + zip: string; + images: string[]; + price: { + price_per_image: number; + quantity_images: number; + total: number; + }; + }; + success: boolean; + }>({ + url: `${baseUrlv0}/image/generation`, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + body: { + model: propsValue.model, + description: propsValue.description, + size: propsValue.size, + variations: propsValue.variations + }, + }); + + return { + images: response.body.data.images, + zip: response.body.data.zip, + }; + }, +}); diff --git a/packages/pieces/community/straico/src/lib/actions/prompt-completion.ts b/packages/pieces/community/straico/src/lib/actions/prompt-completion.ts new file mode 100644 index 0000000..90ddd86 --- /dev/null +++ b/packages/pieces/community/straico/src/lib/actions/prompt-completion.ts @@ -0,0 +1,161 @@ +import { straicoAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + httpClient, + propsValidation, +} from '@activepieces/pieces-common'; +import { z } from 'zod'; + +import { baseUrlv1 } from '../common/common'; + +export const promptCompletion = createAction({ + auth: straicoAuth, + name: 'prompt_completion', + displayName: 'Ask AI', + description: + 'Enables users to generate prompt completion based on a specified model.', + props: { + model: Property.Dropdown({ + displayName: 'Model', + required: true, + description: + 'The model which will generate the completion. Some models are suitable for natural language tasks, others specialize in code.', + refreshers: [], + defaultValue: 'openai/gpt-4o-mini', + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Enter your API key first', + options: [], + }; + } + try { + const models = await httpClient.sendRequest<{ + data: { + chat: Array<{ + name: string; + model: string; + }>; + }; + }>({ + url: `${baseUrlv1}/models`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + return { + disabled: false, + options: + models.body?.data?.chat?.map((model) => { + return { + label: model.name, + value: model.model, + }; + }) || [], + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: "Couldn't load models, API key is invalid", + }; + } + }, + }), + prompt: Property.LongText({ + displayName: 'Prompt', + required: true, + description: 'The prompt text for which completions are requested', + }), + fileUrls: Property.Array({ + displayName: 'File URLs', + required: false, + description: 'URLs of files to be processed by the model (maximum 4 URLs), previously uploaded via the File Upload endpoint', + }), + youtubeUrls: Property.Array({ + displayName: 'YouTube URLs', + required: false, + description: 'URLs of YouTube videos to be processed by the model (maximum 4 URLs)', + }), + imageUrls: Property.Array({ + displayName: 'Image URLs', + required: false, + description: 'URLs of images to be processed by the model, previously uploaded via the File Upload endpoint', + }), + displayTranscripts: Property.Checkbox({ + displayName: 'Display Transcripts', + required: false, + description: 'If true, returns transcripts of the files. Note: Either File URLs or YouTube URLs are required when this is enabled', + defaultValue: false, + }), + temperature: Property.Number({ + displayName: 'Temperature', + required: false, + description: 'This setting influences the variety in the model\'s responses (0-2)', + }), + maxTokens: Property.Number({ + displayName: 'Max Tokens', + required: false, + description: 'Set the limit for the number of tokens the model can generate in response', + }), + }, + async run({ auth, propsValue }) { + // Validate URLs length and displayTranscripts requirements + await propsValidation.validateZod(propsValue, { + fileUrls: z.array(z.string()).max(4, 'Maximum 4 file URLs allowed'), + youtubeUrls: z.array(z.string()).max(4, 'Maximum 4 YouTube URLs allowed'), + }); + + // Validate that displayTranscripts is only true when fileUrls or youtubeUrls are provided + if (propsValue.displayTranscripts === true && + (!propsValue.fileUrls?.length && !propsValue.youtubeUrls?.length)) { + throw new Error('Either File URLs or YouTube URLs are required when Display Transcripts is enabled'); + } + + const response = await httpClient.sendRequest<{ + data: { + completions: { + [key: string]: { + completion: { + choices: Array<{ + message: { + content: string; + }; + }>; + }; + }; + }; + transcripts: Array<{ text: string }>; + }; + }>({ + url: `${baseUrlv1}/prompt/completion`, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + body: { + models: [propsValue.model], + message: propsValue.prompt, + ...(propsValue.fileUrls?.length ? { file_urls: propsValue.fileUrls } : {}), + ...(propsValue.youtubeUrls?.length ? { youtube_urls: propsValue.youtubeUrls } : {}), + ...(propsValue.imageUrls?.length ? { images: propsValue.imageUrls } : {}), + ...((propsValue.displayTranscripts !== undefined && (propsValue.fileUrls?.length || propsValue.youtubeUrls?.length)) ? + { display_transcripts: propsValue.displayTranscripts } : {}), + ...(propsValue.temperature !== undefined ? { temperature: propsValue.temperature } : {}), + ...(propsValue.maxTokens !== undefined ? { max_tokens: propsValue.maxTokens } : {}), + }, + }); + + const modelResponse = response.body.data.completions[propsValue.model]; + return { + content: modelResponse.completion.choices[0].message.content, + transcripts: response.body.data?.transcripts || [] + }; + }, +}); diff --git a/packages/pieces/community/straico/src/lib/actions/rag-create.ts b/packages/pieces/community/straico/src/lib/actions/rag-create.ts new file mode 100644 index 0000000..e0b08ab --- /dev/null +++ b/packages/pieces/community/straico/src/lib/actions/rag-create.ts @@ -0,0 +1,162 @@ +import { straicoAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { baseUrlv0 } from '../common/common'; +import FormData from 'form-data'; + +export const createRag = createAction({ + auth: straicoAuth, + name: 'create_rag', + displayName: 'Create RAG', + description: 'Create a new RAG (Retrieval-Augmented Generation) base in the database.', + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + description: 'Represents the name of the RAG base', + }), + description: Property.LongText({ + displayName: 'Description', + required: true, + description: 'Represents the description of the agent', + }), + file: Property.File({ + displayName: 'File', + required: true, + description: + 'Represents the file to be attached. Accepted file extensions are: pdf, docx, csv, txt, xlsx, py', + }), + chunkingMethod: Property.Dropdown({ + displayName: 'Chunking Method', + required: false, + description: 'Represents the chunking method to be used for generating the RAG base. The default value is fixed_size', + defaultValue: 'fixed_size', + refreshers: [], + options: async () => { + return { + options: [ + { label: 'Fixed Size', value: 'fixed_size' }, + { label: 'Recursive', value: 'recursive' }, + { label: 'Markdown', value: 'markdown' }, + { label: 'Python', value: 'python' }, + { label: 'Semantic', value: 'semantic' }, + ], + disabled: false + }; + }, + }), + chunkSize: Property.Number({ + displayName: 'Chunk Size', + required: false, + description: 'The size of each chunk (default: 1000)', + defaultValue: 1000, + }), + chunkOverlap: Property.Number({ + displayName: 'Chunk Overlap', + required: false, + description: 'The overlap between chunks (default: 50)', + defaultValue: 50, + }), + separator: Property.ShortText({ + displayName: 'Separator', + required: false, + description: 'The separator to use for fixed_size chunking method' + }), + separators: Property.Array({ + displayName: 'Separators', + required: false, + description: 'The separators to use for recursive chunking method' + }), + breakpointThresholdType: Property.StaticDropdown({ + displayName: 'Breakpoint Threshold Type', + required: false, + description: 'The breakpoint threshold type for semantic chunking method', + options: { + disabled: false, + options: [ + { label: 'Percentile', value: 'percentile' }, + { label: 'Interquartile', value: 'interquartile' }, + { label: 'Standard Deviation', value: 'standard_deviation' }, + { label: 'Gradient', value: 'gradient' }, + ], + + }, + }), + bufferSize: Property.Number({ + displayName: 'Buffer Size', + required: false, + description: 'The buffer size for semantic chunking method' + }), + }, + async run({ auth, propsValue }) { + const { file, chunkingMethod, chunkSize, chunkOverlap, separator, separators, breakpointThresholdType, bufferSize } = propsValue; + + const formData = new FormData(); + formData.append('name', propsValue.name); + formData.append('description', propsValue.description); + formData.append('files', file.data, file.filename); + + if (chunkingMethod) { + formData.append('chunking_method', chunkingMethod); + } + + if (chunkSize !== undefined) { + formData.append('chunk_size', chunkSize.toString()); + } + + if (chunkOverlap !== undefined) { + formData.append('chunk_overlap', chunkOverlap.toString()); + } + + if (separator && separator.trim() !== '') { + formData.append('separator', separator); + } + + if (separators && separators.length > 0) { + for (const separator of separators as string[]) { + formData.append('separators', separator); + } + } + + if (breakpointThresholdType) { + formData.append('breakpoint_threshold_type', breakpointThresholdType); + } + + if (bufferSize !== undefined) { + formData.append('buffer_size', bufferSize.toString()); + } + + const response = await httpClient.sendRequest<{ + success: boolean; + data: { + _id: string; + user_id: string; + name: string; + rag_url: string; + original_filename: string; + chunking_method: string; + chunk_overlap: number; + created_at: string; + }; + total_coins: number; + total_words: number; + }>({ + url: `${baseUrlv0}/rag`, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + body: formData, + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/straico/src/lib/actions/rag-delete.ts b/packages/pieces/community/straico/src/lib/actions/rag-delete.ts new file mode 100644 index 0000000..31f5a98 --- /dev/null +++ b/packages/pieces/community/straico/src/lib/actions/rag-delete.ts @@ -0,0 +1,43 @@ +import { straicoAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { baseUrlv0 } from '../common/common'; + +export const deleteRag = createAction({ + auth: straicoAuth, + name: 'delete_rag', + displayName: 'Delete RAG', + description: 'Delete a specific RAG (Retrieval-Augmented Generation) base by its ID.', + props: { + ragId: Property.ShortText({ + displayName: 'RAG ID', + required: true, + description: 'The ID of the RAG base to delete', + }), + }, + async run({ auth, propsValue }) { + const { ragId } = propsValue; + + if (!ragId) { + throw new Error('RAG ID is required'); + } + + const response = await httpClient.sendRequest<{ + success: boolean; + message: string; + }>({ + url: `${baseUrlv0}/rag/${ragId}`, + method: HttpMethod.DELETE, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/straico/src/lib/actions/rag-get-by-id.ts b/packages/pieces/community/straico/src/lib/actions/rag-get-by-id.ts new file mode 100644 index 0000000..7ec0c97 --- /dev/null +++ b/packages/pieces/community/straico/src/lib/actions/rag-get-by-id.ts @@ -0,0 +1,55 @@ +import { straicoAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { baseUrlv0 } from '../common/common'; + +export const getRagById = createAction({ + auth: straicoAuth, + name: 'get_rag_by_id', + displayName: 'Get RAG by ID', + description: 'Retrieve a specific RAG (Retrieval-Augmented Generation) base by its ID.', + props: { + ragId: Property.ShortText({ + displayName: 'RAG ID', + required: true, + description: 'The ID of the RAG base to retrieve.', + }), + }, + async run({ auth, propsValue }) { + const { ragId } = propsValue; + + if (!ragId) { + throw new Error('RAG ID is required'); + } + + const response = await httpClient.sendRequest<{ + success: boolean; + data: { + _id: string; + user_id: string; + name: string; + tag_url: string; + original_filename: string; + chunking_method: string; + chunk_size: number; + chunk_overlap: number; + createdAt: string; + updatedAt: string; + __v: number; + }; + }>({ + url: `${baseUrlv0}/rag/${ragId}`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/straico/src/lib/actions/rag-list.ts b/packages/pieces/community/straico/src/lib/actions/rag-list.ts new file mode 100644 index 0000000..93ea7f3 --- /dev/null +++ b/packages/pieces/community/straico/src/lib/actions/rag-list.ts @@ -0,0 +1,43 @@ +import { straicoAuth } from '../../index'; +import { createAction } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { baseUrlv0 } from '../common/common'; + +export const listRags = createAction({ + auth: straicoAuth, + name: 'list_rags', + displayName: 'List RAGs', + description: 'List all RAG (Retrieval-Augmented Generation) bases for a user.', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest<{ + success: boolean; + data: { + _id: string; + user_id: string; + name: string; + tag_url: string; + original_filename: string; + chunking_method: string; + chunk_size: number; + chunk_overlap: number; + createdAt: string; + updatedAt: string; + __v: number; + }[]; + }>({ + url: `${baseUrlv0}/rag/user`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/straico/src/lib/actions/rag-prompt-completion.ts b/packages/pieces/community/straico/src/lib/actions/rag-prompt-completion.ts new file mode 100644 index 0000000..aae5160 --- /dev/null +++ b/packages/pieces/community/straico/src/lib/actions/rag-prompt-completion.ts @@ -0,0 +1,164 @@ +import { straicoAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { baseUrlv0, baseUrlv1 } from '../common/common'; + +export const ragPromptCompletion = createAction({ + auth: straicoAuth, + name: 'rag_prompt_completion', + displayName: 'RAG Prompt Completion', + description: 'Send a prompt to a specific RAG (Retrieval-Augmented Generation) model.', + props: { + ragId: Property.ShortText({ + displayName: 'RAG ID', + required: true, + description: 'The ID of the RAG base to query', + }), + prompt: Property.LongText({ + displayName: 'Prompt', + required: true, + description: 'A text prompt for the RAG model', + }), + model: Property.Dropdown({ + displayName: 'Model', + required: true, + description: 'The specific LLM to be used', + refreshers: [], + defaultValue: 'openai/gpt-4o-mini', + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Enter your API key first', + options: [], + }; + } + try { + const models = await httpClient.sendRequest<{ + data: { + chat: Array<{ + name: string; + model: string; + }>; + }; + }>({ + url: `${baseUrlv1}/models`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + + const modelOptions = models.body?.data?.chat?.map((model) => { + return { + label: model.name, + value: model.model, + }; + }) || []; + + return { + disabled: false, + options: modelOptions, + }; + } catch (error) { + return { + disabled: true, + options: [], + placeholder: "Couldn't load models, API key is invalid", + }; + } + }, + }), + searchType: Property.StaticDropdown({ + displayName: 'Search Type', + required: false, + description: 'Type of search to perform', + options: { + options: [ + { label: 'Similarity', value: 'similarity' }, + { label: 'MMR', value: 'mmr' }, + { label: 'Similarity Score Threshold', value: 'similarity_score_threshold' } + ] + }, + }), + k: Property.Number({ + displayName: 'Number of Documents', + required: false, + description: 'Number of documents to return', + }), + fetchK: Property.Number({ + displayName: 'Fetch K', + required: false, + description: 'Amount of documents to pass to MMR algorithm', + }), + lambdaMult: Property.Number({ + displayName: 'Lambda Mult', + required: false, + description: 'Diversity of results return by MMR (1 for minimum and 0 for maximum)', + }), + scoreThreshold: Property.Number({ + displayName: 'Score Threshold', + required: false, + description: 'Minimum relevance threshold for similarity_score_threshold', + }), + }, + async run({ auth, propsValue }) { + const { ragId, prompt, model, searchType, k, fetchK, lambdaMult, scoreThreshold } = propsValue; + + if (!ragId) { + throw new Error('RAG ID is required'); + } + + if (!prompt) { + throw new Error('Prompt is required'); + } + + const requestBody: Record = { + prompt, + model, + }; + + const optionalParams = { + search_type: searchType, + k, + fetch_k: fetchK, + lambda_mult: lambdaMult, + score_threshold: scoreThreshold + }; + + Object.entries(optionalParams).forEach(([key, value]) => { + if (value !== undefined) { + requestBody[key] = value; + } + }); + + const response = await httpClient.sendRequest<{ + success: boolean; + data: { + answer: string; + references: Array<{ + page_content: string; + page: number; + }>; + file_name: string; + coins_used: number; + response: Record; + }; + }>({ + url: `${baseUrlv0}/rag/${ragId}/prompt`, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + body: requestBody, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/straico/src/lib/actions/rag-update.ts b/packages/pieces/community/straico/src/lib/actions/rag-update.ts new file mode 100644 index 0000000..541dd1e --- /dev/null +++ b/packages/pieces/community/straico/src/lib/actions/rag-update.ts @@ -0,0 +1,76 @@ +import { straicoAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { baseUrlv0 } from '../common/common'; +import FormData from 'form-data'; + +export const updateRag = createAction({ + auth: straicoAuth, + name: 'update_rag', + displayName: 'Update RAG', + description: 'Update an existing RAG (Retrieval-Augmented Generation) base with additional files.', + props: { + ragId: Property.ShortText({ + displayName: 'RAG ID', + required: true, + description: 'The ID of the RAG base to update.', + }), + file: Property.File({ + displayName: 'File', + required: true, + description: + 'Represents the file to be attached. Accepted file extensions are: pdf, docx, csv, txt, xlsx, py.', + }), + }, + async run({ auth, propsValue }) { + const { ragId, file } = propsValue; + + if (!ragId) { + throw new Error('RAG ID is required'); + } + + const formData = new FormData(); + formData.append('files', file.data, file.filename); + + const response = await httpClient.sendRequest<{ + success: boolean; + data: { + _id: string; + user_id: string; + name: string; + description: string; + rag_url: string; + original_filename: string; + chunking_method: string; + chunk_size: number; + chunk_overlap: number; + buffer_size: number; + breakpoint_threshold_type: string; + separator: string; + separators: string[]; + createdAt: string; + updatedAt: string; + __v: number; + }; + total_coins: number; + total_words: number; + }>({ + url: `${baseUrlv0}/rag/${ragId}`, + method: HttpMethod.PUT, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + body: formData, + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/straico/src/lib/common/common.ts b/packages/pieces/community/straico/src/lib/common/common.ts new file mode 100644 index 0000000..c2238e4 --- /dev/null +++ b/packages/pieces/community/straico/src/lib/common/common.ts @@ -0,0 +1,2 @@ +export const baseUrlv0 = 'https://api.straico.com/v0'; +export const baseUrlv1 = 'https://api.straico.com/v1'; \ No newline at end of file diff --git a/packages/pieces/community/straico/src/lib/common/props.ts b/packages/pieces/community/straico/src/lib/common/props.ts new file mode 100644 index 0000000..0914383 --- /dev/null +++ b/packages/pieces/community/straico/src/lib/common/props.ts @@ -0,0 +1,51 @@ +import { AuthenticationType, httpClient, HttpMethod } from "@activepieces/pieces-common"; +import { Property } from "@activepieces/pieces-framework"; +import { baseUrlv0 } from "./common"; + +export const agentIdDropdown =(displayName:string, desc:string)=> Property.Dropdown({ + displayName, + required: true, + description: desc, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please authenticate first', + options: [], + }; + } + + const response = await httpClient.sendRequest<{ + success: boolean; + data: Array<{ + _id: string; + name: string; + }>; + }>({ + url: `${baseUrlv0}/agent`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth as string, + }, + }); + + if (response.body.success && response.body.data) { + return { + options: response.body.data.map((agent) => { + return { + label: agent.name, + value: agent._id, + }; + }), + }; + } + + return { + disabled: true, + placeholder: 'No agents found', + options: [], + }; + }, + }) \ No newline at end of file diff --git a/packages/pieces/community/straico/tsconfig.json b/packages/pieces/community/straico/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/straico/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/straico/tsconfig.lib.json b/packages/pieces/community/straico/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/straico/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/stripe/.babelrc b/packages/pieces/community/stripe/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/stripe/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/stripe/.eslintrc.json b/packages/pieces/community/stripe/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/stripe/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/stripe/README.md b/packages/pieces/community/stripe/README.md new file mode 100644 index 0000000..066a036 --- /dev/null +++ b/packages/pieces/community/stripe/README.md @@ -0,0 +1,7 @@ +# pieces-stripe + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-stripe` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/stripe/package.json b/packages/pieces/community/stripe/package.json new file mode 100644 index 0000000..d09d145 --- /dev/null +++ b/packages/pieces/community/stripe/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-stripe", + "version": "0.5.4" +} \ No newline at end of file diff --git a/packages/pieces/community/stripe/project.json b/packages/pieces/community/stripe/project.json new file mode 100644 index 0000000..2261f65 --- /dev/null +++ b/packages/pieces/community/stripe/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-stripe", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/stripe/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/stripe", + "tsConfig": "packages/pieces/community/stripe/tsconfig.lib.json", + "packageJson": "packages/pieces/community/stripe/package.json", + "main": "packages/pieces/community/stripe/src/index.ts", + "assets": [ + "packages/pieces/community/stripe/*.md", + { + "input": "packages/pieces/community/stripe/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/stripe/src/index.ts b/packages/pieces/community/stripe/src/index.ts new file mode 100644 index 0000000..0554ff4 --- /dev/null +++ b/packages/pieces/community/stripe/src/index.ts @@ -0,0 +1,47 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { stripeCreateCustomer } from './lib/actions/create-customer'; +import { stripeCreateInvoice } from './lib/actions/create-invoice'; +import { stripeRetrieveCustomer } from './lib/actions/retrieve-customer'; +import { stripeSearchCustomer } from './lib/actions/search-customer'; +import { stripeNewCustomer } from './lib/trigger/new-customer'; +import { stripeNewPayment } from './lib/trigger/new-payment'; +import { stripeNewSubscription } from './lib/trigger/new-subscription'; +import { stripePaymentFailed } from './lib/trigger/payment-failed'; + +export const stripeAuth = PieceAuth.SecretText({ + displayName: 'Secret API Key', + required: true, + description: 'Secret key acquired from your Stripe dashboard', +}); + +export const stripe = createPiece({ + displayName: 'Stripe', + description: 'Online payment processing for internet businesses', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/stripe.png', + authors: ["lldiegon","doskyft","kishanprmr","MoShizzle","AbdulTheActivePiecer","khaledmashaly","abuaboud"], + categories: [PieceCategory.COMMERCE, PieceCategory.PAYMENT_PROCESSING], + auth: stripeAuth, + actions: [ + stripeCreateCustomer, + stripeCreateInvoice, + stripeSearchCustomer, + stripeRetrieveCustomer, + createCustomApiCallAction({ + baseUrl: () => 'https://api.stripe.com/v1', + auth: stripeAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers: [ + stripeNewPayment, + stripeNewCustomer, + stripePaymentFailed, + stripeNewSubscription, + ], +}); diff --git a/packages/pieces/community/stripe/src/lib/actions/create-customer.ts b/packages/pieces/community/stripe/src/lib/actions/create-customer.ts new file mode 100644 index 0000000..a88e2ab --- /dev/null +++ b/packages/pieces/community/stripe/src/lib/actions/create-customer.ts @@ -0,0 +1,80 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { stripeAuth } from '../..'; + +export const stripeCreateCustomer = createAction({ + name: 'create_customer', + auth: stripeAuth, + displayName: 'Create Customer', + description: 'Create a customer in stripe', + props: { + email: Property.ShortText({ + displayName: 'Email', + description: undefined, + required: true, + }), + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + phone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + line1: Property.ShortText({ + displayName: 'Address Line 1', + required: false, + }), + postal_code: Property.ShortText({ + displayName: 'Postal Code', + required: false, + }), + city: Property.ShortText({ + displayName: 'City', + required: false, + }), + state: Property.ShortText({ + displayName: 'State', + required: false, + }), + country: Property.ShortText({ + displayName: 'Country', + required: false, + }), + }, + async run(context) { + const customer = { + email: context.propsValue.email, + name: context.propsValue.name, + description: context.propsValue.description, + phone: context.propsValue.phone, + address: { + line1: context.propsValue.line1, + postal_code: context.propsValue.postal_code, + city: context.propsValue.city, + state: context.propsValue.state, + country: context.propsValue.country, + }, + }; + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.stripe.com/v1/customers', + headers: { + Authorization: 'Bearer ' + context.auth, + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: { + email: customer.email, + name: customer.name, + description: customer.description, + phone: customer.phone, + address: customer.address, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/stripe/src/lib/actions/create-invoice.ts b/packages/pieces/community/stripe/src/lib/actions/create-invoice.ts new file mode 100644 index 0000000..d85c383 --- /dev/null +++ b/packages/pieces/community/stripe/src/lib/actions/create-invoice.ts @@ -0,0 +1,49 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { stripeAuth } from '../..'; + +export const stripeCreateInvoice = createAction({ + name: 'create_invoice', + auth: stripeAuth, + displayName: 'Create Invoice', + description: 'Create an Invoice in stripe', + props: { + customer_id: Property.ShortText({ + displayName: 'Customer ID', + description: 'Stripe Customer ID', + required: true, + }), + currency: Property.ShortText({ + displayName: 'Currency', + description: 'Currency for the invoice (e.g., USD)', + required: true, + }), + description: Property.LongText({ + displayName: 'Description', + description: 'Description for the invoice', + required: false, + }), + }, + async run(context) { + const invoice = { + customer: context.propsValue.customer_id, + currency: context.propsValue.currency, + description: context.propsValue.description, + }; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.stripe.com/v1/invoices', + headers: { + Authorization: 'Bearer ' + context.auth, + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: { + customer: invoice.customer, + currency: invoice.currency, + description: invoice.description, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/stripe/src/lib/actions/retrieve-customer.ts b/packages/pieces/community/stripe/src/lib/actions/retrieve-customer.ts new file mode 100644 index 0000000..1215b1b --- /dev/null +++ b/packages/pieces/community/stripe/src/lib/actions/retrieve-customer.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, +} from '@activepieces/pieces-common'; +import { stripeAuth } from '../..'; + +export const stripeRetrieveCustomer = createAction({ + name: 'retrieve_customer', + auth: stripeAuth, + displayName: 'Retrieve Customer', + description: 'Retrieve a customer in stripe by id', + props: { + id: Property.ShortText({ + displayName: 'ID', + description: undefined, + required: true, + }), + }, + async run(context) { + const customer = { + id: context.propsValue.id, + }; + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.stripe.com/v1/customers/${customer.id}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/stripe/src/lib/actions/search-customer.ts b/packages/pieces/community/stripe/src/lib/actions/search-customer.ts new file mode 100644 index 0000000..23c22ab --- /dev/null +++ b/packages/pieces/community/stripe/src/lib/actions/search-customer.ts @@ -0,0 +1,34 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { stripeAuth } from '../..'; + +export const stripeSearchCustomer = createAction({ + name: 'search_customer', + auth: stripeAuth, + displayName: 'Search Customer', + description: 'Search for a customer in stripe by email', + props: { + email: Property.ShortText({ + displayName: 'Email', + description: undefined, + required: true, + }), + }, + async run(context) { + const customer = { + email: context.propsValue.email, + }; + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.stripe.com/v1/customers/search', + headers: { + Authorization: 'Bearer ' + context.auth, + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: { + query: 'email:' + "'" + customer.email + "'", + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/stripe/src/lib/common/index.ts b/packages/pieces/community/stripe/src/lib/common/index.ts new file mode 100644 index 0000000..a88597c --- /dev/null +++ b/packages/pieces/community/stripe/src/lib/common/index.ts @@ -0,0 +1,51 @@ +import { + HttpRequest, + HttpMethod, + AuthenticationType, + httpClient, +} from '@activepieces/pieces-common'; + +export const stripeCommon = { + baseUrl: 'https://api.stripe.com/v1', + subscribeWebhook: async ( + eventName: string, + webhookUrl: string, + apiKey: string + ) => { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${stripeCommon.baseUrl}/webhook_endpoints`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: { + enabled_events: [eventName], + url: webhookUrl, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: apiKey, + }, + queryParams: {}, + }; + + const { body: webhook } = await httpClient.sendRequest<{ id: string }>( + request + ); + return webhook; + }, + unsubscribeWebhook: async (webhookId: string, apiKey: string) => { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `${stripeCommon.baseUrl}/webhook_endpoints/${webhookId}`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: apiKey, + }, + }; + return await httpClient.sendRequest(request); + }, +}; diff --git a/packages/pieces/community/stripe/src/lib/trigger/new-customer.ts b/packages/pieces/community/stripe/src/lib/trigger/new-customer.ts new file mode 100644 index 0000000..5ecede4 --- /dev/null +++ b/packages/pieces/community/stripe/src/lib/trigger/new-customer.ts @@ -0,0 +1,75 @@ +import { createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { stripeCommon } from '../common'; +import { stripeAuth } from '../..'; + +export const stripeNewCustomer = createTrigger({ + auth: stripeAuth, + name: 'new_customer', + displayName: 'New Customer', + description: 'Triggers when a new customer is created', + props: {}, + sampleData: { + id: 'cus_NGtyEf4hNGTj3p', + object: 'customer', + address: null, + balance: 0, + created: 1675180509, + currency: null, + default_currency: null, + default_source: null, + delinquent: false, + description: null, + discount: null, + email: 'jane@example.com', + invoice_prefix: 'B7162248', + invoice_settings: { + custom_fields: null, + default_payment_method: null, + footer: null, + rendering_options: null, + }, + livemode: false, + metadata: {}, + name: 'John Doe', + next_invoice_sequence: 1, + phone: null, + preferred_locales: [], + shipping: null, + tax_exempt: 'none', + test_clock: null, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const webhook = await stripeCommon.subscribeWebhook( + 'customer.created', + context.webhookUrl, + context.auth + ); + await context.store.put('_new_customer_trigger', { + webhookId: webhook.id, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_new_customer_trigger' + ); + if (response !== null && response !== undefined) { + await stripeCommon.unsubscribeWebhook(response.webhookId, context.auth); + } + }, + async run(context) { + const payloadBody = context.payload.body as PayloadBody; + return [payloadBody.data.object]; + }, +}); + +type PayloadBody = { + data: { + object: unknown; + }; +}; + +interface WebhookInformation { + webhookId: string; +} diff --git a/packages/pieces/community/stripe/src/lib/trigger/new-payment.ts b/packages/pieces/community/stripe/src/lib/trigger/new-payment.ts new file mode 100644 index 0000000..725dfda --- /dev/null +++ b/packages/pieces/community/stripe/src/lib/trigger/new-payment.ts @@ -0,0 +1,141 @@ +import { createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { stripeCommon } from '../common'; +import { stripeAuth } from '../..'; + +export const stripeNewPayment = createTrigger({ + auth: stripeAuth, + name: 'new_payment', + displayName: 'New Payment', + description: 'Triggers when a new payment is made', + props: {}, + type: TriggerStrategy.WEBHOOK, + sampleData: { + id: 'ch_3MWM7aKZ0dZRqLEK1soCKVrq', + object: 'charge', + amount: 10000, + amount_captured: 10000, + amount_refunded: 0, + application: null, + application_fee: null, + application_fee_amount: null, + balance_transaction: 'txn_3MWM7aKZ0dZRqLEK1VyE8QH1', + billing_details: { + address: { + city: null, + country: 'DE', + line1: null, + line2: null, + postal_code: null, + state: null, + }, + email: 'test@gmail.com', + name: 'Test user', + phone: null, + }, + calculated_statement_descriptor: 'WWW.ACTIVEPIECES.COM', + captured: true, + created: 1675180355, + currency: 'usd', + customer: 'cus_NGtvUQ18FJXcGI', + description: 'Subscription creation', + destination: null, + dispute: null, + disputed: false, + failure_balance_transaction: null, + failure_code: null, + failure_message: null, + fraud_details: {}, + invoice: 'in_1MWM7ZKZ0dZRqLEKQbrgSBnh', + livemode: false, + metadata: {}, + on_behalf_of: null, + order: null, + outcome: { + network_status: 'approved_by_network', + reason: null, + risk_level: 'normal', + risk_score: 64, + seller_message: 'Payment complete.', + type: 'authorized', + }, + paid: true, + payment_intent: 'pi_3MWM7aKZ0dZRqLEK1BsblcVI', + payment_method: 'pm_1MWM8MKZ0dZRqLEKnIH41f76', + payment_method_details: { + card: { + brand: 'visa', + checks: { + address_line1_check: null, + address_postal_code_check: null, + cvc_check: 'pass', + }, + country: 'US', + exp_month: 12, + exp_year: 2034, + fingerprint: 't8SMsmS4h2vvODpN', + funding: 'credit', + installments: null, + last4: '4242', + mandate: null, + network: 'visa', + three_d_secure: null, + wallet: null, + }, + type: 'card', + }, + receipt_email: null, + receipt_number: null, + receipt_url: + 'https://pay.stripe.com/receipts/invoices/CAcaFwoVYWNjdF8xS214ZEtLWjBkWlJxTEVLKMXy5J4GMgZcuppYWF06LBZEoiAhZ6H7EoJ3bN-BMHCXdaW-_i-ywhSIG9wPGTmtE0CdpD75s1hIyprK?s=ap', + refunded: false, + refunds: { + object: 'list', + data: [], + has_more: false, + total_count: 0, + url: '/v1/charges/ch_3MWM7aKZ0dZRqLEK1soCKVrq/refunds', + }, + review: null, + shipping: null, + source: null, + source_transfer: null, + statement_descriptor: null, + statement_descriptor_suffix: null, + status: 'succeeded', + transfer_data: null, + transfer_group: null, + }, + async onEnable(context) { + const webhook = await stripeCommon.subscribeWebhook( + 'charge.succeeded', + context.webhookUrl!, + context.auth + ); + await context.store?.put('_new_payment_trigger', { + webhookId: webhook.id, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_new_payment_trigger' + ); + if (response !== null && response !== undefined) { + await stripeCommon.unsubscribeWebhook(response.webhookId, context.auth); + } + }, + async run(context) { + const payloadBody = context.payload.body as PayloadBody; + return [payloadBody.data.object]; + }, +}); + +type PayloadBody = { + data: { + object: unknown; + }; +}; + +interface WebhookInformation { + webhookId: string; +} diff --git a/packages/pieces/community/stripe/src/lib/trigger/new-subscription.ts b/packages/pieces/community/stripe/src/lib/trigger/new-subscription.ts new file mode 100644 index 0000000..d932a13 --- /dev/null +++ b/packages/pieces/community/stripe/src/lib/trigger/new-subscription.ts @@ -0,0 +1,184 @@ +import { createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { stripeCommon } from '../common'; +import { stripeAuth } from '../..'; + +export const stripeNewSubscription = createTrigger({ + auth: stripeAuth, + name: 'new_subscription', + displayName: 'New Subscription', + description: 'Triggers when a new subscription is made', + props: {}, + type: TriggerStrategy.WEBHOOK, + sampleData: { + id: 'sub_1MWMJXKZ0dZRqLEKJX80JXfv', + object: 'subscription', + application: null, + application_fee_percent: null, + automatic_tax: { + enabled: true, + }, + billing_cycle_anchor: 1675181047, + billing_thresholds: null, + cancel_at: null, + cancel_at_period_end: false, + canceled_at: null, + collection_method: 'charge_automatically', + created: 1675181047, + currency: 'usd', + current_period_end: 1677600247, + current_period_start: 1675181047, + customer: 'cus_NGtvUQ18FJXcGI', + days_until_due: null, + default_payment_method: 'pm_1MWM8MKZ0dZRqLEKnIH41f76', + default_source: null, + default_tax_rates: [], + description: null, + discount: null, + ended_at: null, + items: { + object: 'list', + data: [ + { + id: 'si_NGu7pb7hS3Rps3', + object: 'subscription_item', + billing_thresholds: null, + created: 1675181048, + metadata: {}, + plan: { + id: 'price_1MWLz3KZ0dZRqLEK06gRMHCF', + object: 'plan', + active: true, + aggregate_usage: null, + amount: 10000, + amount_decimal: '10000', + billing_scheme: 'per_unit', + created: 1675179777, + currency: 'usd', + interval: 'month', + interval_count: 1, + livemode: false, + metadata: {}, + nickname: null, + product: 'prod_NGtm3AlvaGjaLN', + tiers_mode: null, + transform_usage: null, + trial_period_days: null, + usage_type: 'licensed', + }, + price: { + id: 'price_1MWLz3KZ0dZRqLEK06gRMHCF', + object: 'price', + active: true, + billing_scheme: 'per_unit', + created: 1675179777, + currency: 'usd', + custom_unit_amount: null, + livemode: false, + lookup_key: null, + metadata: {}, + nickname: null, + product: 'prod_NGtm3AlvaGjaLN', + recurring: { + aggregate_usage: null, + interval: 'month', + interval_count: 1, + trial_period_days: null, + usage_type: 'licensed', + }, + tax_behavior: 'exclusive', + tiers_mode: null, + transform_quantity: null, + type: 'recurring', + unit_amount: 10000, + unit_amount_decimal: '10000', + }, + quantity: 1, + subscription: 'sub_1MWMJXKZ0dZRqLEKJX80JXfv', + tax_rates: [], + }, + ], + has_more: false, + total_count: 1, + url: '/v1/subscription_items?subscription=sub_1MWMJXKZ0dZRqLEKJX80JXfv', + }, + latest_invoice: 'in_1MWMJXKZ0dZRqLEKIu4a51u7', + livemode: false, + metadata: {}, + next_pending_invoice_item_invoice: null, + on_behalf_of: null, + pause_collection: null, + payment_settings: { + payment_method_options: null, + payment_method_types: null, + save_default_payment_method: 'off', + }, + pending_invoice_item_interval: null, + pending_setup_intent: null, + pending_update: null, + plan: { + id: 'price_1MWLz3KZ0dZRqLEK06gRMHCF', + object: 'plan', + active: true, + aggregate_usage: null, + amount: 10000, + amount_decimal: '10000', + billing_scheme: 'per_unit', + created: 1675179777, + currency: 'usd', + interval: 'month', + interval_count: 1, + livemode: false, + metadata: {}, + nickname: null, + product: 'prod_NGtm3AlvaGjaLN', + tiers_mode: null, + transform_usage: null, + trial_period_days: null, + usage_type: 'licensed', + }, + quantity: 1, + schedule: null, + start_date: 1675181047, + status: 'active', + test_clock: null, + transfer_data: null, + trial_end: null, + trial_start: null, + }, + async onEnable(context) { + const webhook = await stripeCommon.subscribeWebhook( + 'customer.subscription.created', + context.webhookUrl!, + context.auth + ); + await context.store?.put( + '_new_customer_subscription_trigger', + { + webhookId: webhook.id, + } + ); + }, + async onDisable(context) { + const response = await context.store?.get( + '_new_customer_subscription_trigger' + ); + if (response !== null && response !== undefined) { + await stripeCommon.unsubscribeWebhook(response.webhookId, context.auth); + } + }, + async run(context) { + const payloadBody = context.payload.body as PayloadBody; + return [payloadBody.data.object]; + }, +}); + +type PayloadBody = { + data: { + object: unknown; + }; +}; + +interface WebhookInformation { + webhookId: string; +} diff --git a/packages/pieces/community/stripe/src/lib/trigger/payment-failed.ts b/packages/pieces/community/stripe/src/lib/trigger/payment-failed.ts new file mode 100644 index 0000000..0725aa6 --- /dev/null +++ b/packages/pieces/community/stripe/src/lib/trigger/payment-failed.ts @@ -0,0 +1,188 @@ +import { createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { stripeCommon } from '../common'; +import { stripeAuth } from '../..'; + +export const stripePaymentFailed = createTrigger({ + auth: stripeAuth, + name: 'payment_failed', + displayName: 'Payment Failed', + description: 'Triggers when a payment fails', + props: {}, + sampleData: { + id: 'ch_3MWMPQKZ0dZRqLEK063rxD7q', + object: 'charge', + amount: 100000, + amount_captured: 0, + amount_refunded: 0, + application: null, + application_fee: null, + application_fee_amount: null, + balance_transaction: null, + billing_details: { + address: { + city: null, + country: null, + line1: null, + line2: null, + postal_code: '12321', + state: null, + }, + email: null, + name: null, + phone: null, + }, + calculated_statement_descriptor: 'WWW.ACTIVEPIECES.COM', + captured: false, + created: 1675181413, + currency: 'usd', + customer: 'cus_NGtvUQ18FJXcGI', + description: 'Failed Payment', + destination: null, + dispute: null, + disputed: false, + failure_balance_transaction: null, + failure_code: 'card_declined', + failure_message: 'Your card was declined.', + fraud_details: {}, + invoice: null, + livemode: false, + metadata: {}, + on_behalf_of: null, + order: null, + outcome: { + network_status: 'declined_by_network', + reason: 'generic_decline', + risk_level: 'normal', + risk_score: 60, + seller_message: + 'The bank did not return any further details with this decline.', + type: 'issuer_declined', + }, + paid: false, + payment_intent: 'pi_3MWMPQKZ0dZRqLEK0Nsc6WhL', + payment_method: 'src_1MWMPQKZ0dZRqLEKuQ83wmZI', + payment_method_details: { + card: { + brand: 'visa', + checks: { + address_line1_check: null, + address_postal_code_check: 'pass', + cvc_check: 'pass', + }, + country: 'US', + exp_month: 12, + exp_year: 2031, + fingerprint: 'mtYxM2Q4edpEt8Pw', + funding: 'credit', + installments: null, + last4: '0341', + mandate: null, + network: 'visa', + three_d_secure: null, + wallet: null, + }, + type: 'card', + }, + receipt_email: null, + receipt_number: null, + receipt_url: null, + refunded: false, + refunds: { + object: 'list', + data: [], + has_more: false, + total_count: 0, + url: '/v1/charges/ch_3MWMPQKZ0dZRqLEK063rxD7q/refunds', + }, + review: null, + shipping: null, + source: { + id: 'src_1MWMPQKZ0dZRqLEKuQ83wmZI', + object: 'source', + amount: null, + card: { + exp_month: 12, + exp_year: 2031, + last4: '0341', + country: 'US', + brand: 'Visa', + address_zip_check: 'pass', + cvc_check: 'pass', + funding: 'credit', + fingerprint: 'mtYxM2Q4edpEt8Pw', + three_d_secure: 'optional', + name: null, + address_line1_check: null, + tokenization_method: null, + dynamic_last4: null, + }, + client_secret: 'src_client_secret_TlLkl6IvhCvmbx8Cz12YNDVb', + created: 1675181413, + currency: null, + flow: 'none', + livemode: false, + metadata: {}, + owner: { + address: { + city: null, + country: null, + line1: null, + line2: null, + postal_code: '12321', + state: null, + }, + email: null, + name: null, + phone: null, + verified_address: null, + verified_email: null, + verified_name: null, + verified_phone: null, + }, + statement_descriptor: null, + status: 'chargeable', + type: 'card', + usage: 'reusable', + }, + source_transfer: null, + statement_descriptor: 'www.activepieces.com', + statement_descriptor_suffix: null, + status: 'failed', + transfer_data: null, + transfer_group: null, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const webhook = await stripeCommon.subscribeWebhook( + 'charge.failed', + context.webhookUrl, + context.auth + ); + await context.store?.put('_payment_failed_trigger', { + webhookId: webhook.id, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_payment_failed_trigger' + ); + if (response !== null && response !== undefined) { + await stripeCommon.unsubscribeWebhook(response.webhookId, context.auth); + } + }, + async run(context) { + const payloadBody = context.payload.body as PayloadBody; + return [payloadBody.data.object]; + }, +}); + +type PayloadBody = { + data: { + object: unknown; + }; +}; + +interface WebhookInformation { + webhookId: string; +} diff --git a/packages/pieces/community/stripe/tsconfig.json b/packages/pieces/community/stripe/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/stripe/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/stripe/tsconfig.lib.json b/packages/pieces/community/stripe/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/stripe/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/subflows/.eslintrc.json b/packages/pieces/community/subflows/.eslintrc.json new file mode 100644 index 0000000..eac708a --- /dev/null +++ b/packages/pieces/community/subflows/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/subflows/README.md b/packages/pieces/community/subflows/README.md new file mode 100644 index 0000000..9b5f47c --- /dev/null +++ b/packages/pieces/community/subflows/README.md @@ -0,0 +1,7 @@ +# pieces-subflows + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-subflows` to build the library. diff --git a/packages/pieces/community/subflows/package.json b/packages/pieces/community/subflows/package.json new file mode 100644 index 0000000..5fc3c76 --- /dev/null +++ b/packages/pieces/community/subflows/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-subflows", + "version": "0.1.3" +} diff --git a/packages/pieces/community/subflows/project.json b/packages/pieces/community/subflows/project.json new file mode 100644 index 0000000..8c99315 --- /dev/null +++ b/packages/pieces/community/subflows/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-subflows", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/subflows/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/subflows", + "tsConfig": "packages/pieces/community/subflows/tsconfig.lib.json", + "packageJson": "packages/pieces/community/subflows/package.json", + "main": "packages/pieces/community/subflows/src/index.ts", + "assets": [ + "packages/pieces/community/subflows/*.md", + { + "input": "packages/pieces/community/subflows/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/subflows/src/index.ts b/packages/pieces/community/subflows/src/index.ts new file mode 100644 index 0000000..be7be99 --- /dev/null +++ b/packages/pieces/community/subflows/src/index.ts @@ -0,0 +1,17 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { callFlow } from './lib/actions/call-flow'; +import { callableFlow } from './lib/triggers/callable-flow'; +import { response } from './lib/actions/respond'; +import { PieceCategory } from '@activepieces/shared'; + +export const flows = createPiece({ + displayName: 'Sub Flows', + description: 'Trigger and call another sub flow.', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.32.4', + categories: [PieceCategory.CORE, PieceCategory.FLOW_CONTROL], + logoUrl: 'https://cdn.activepieces.com/pieces/flows.svg', + authors: ['hazemadelkhalel'], + actions: [callFlow, response], + triggers: [callableFlow], +}); diff --git a/packages/pieces/community/subflows/src/lib/actions/call-flow.ts b/packages/pieces/community/subflows/src/lib/actions/call-flow.ts new file mode 100644 index 0000000..bda1d83 --- /dev/null +++ b/packages/pieces/community/subflows/src/lib/actions/call-flow.ts @@ -0,0 +1,180 @@ +import { + createAction, + DynamicPropsValue, + Property, +} from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { ExecutionType, FlowStatus, isNil, PauseType, TriggerType } from '@activepieces/shared'; +import { CallableFlowRequest, CallableFlowResponse } from '../common'; + +type FlowValue = { + id: string; + exampleData: unknown; +}; + +export const callFlow = createAction({ + name: 'callFlow', + displayName: 'Call Flow', + description: 'Call a flow that has "Callable Flow" trigger', + props: { + flow: Property.Dropdown({ + displayName: 'Flow', + description: 'The flow to execute', + required: true, + options: async (_, context) => { + const allFlows = (await context.flows.list()).data; + const flows = allFlows.filter( + (flow) => + flow.status === FlowStatus.ENABLED && + flow.version.trigger.type === TriggerType.PIECE && + flow.version.trigger.settings.pieceName == + '@activepieces/piece-subflows' + ); + return { + options: flows.map((flow) => ({ + value: { + id: flow.id, + exampleData: flow.version.trigger.settings.input.exampleData, + }, + label: flow.version.displayName, + })), + }; + }, + refreshers: [], + }), + mode: Property.StaticDropdown({ + displayName: 'Mode', + required: true, + description: 'Choose Simple for key-value or Advanced for JSON.', + defaultValue: 'simple', + options: { + disabled: false, + options: [ + { + label: 'Simple', + value: 'simple', + }, + { + label: 'Advanced', + value: 'advanced', + }, + ], + }, + }), + flowProps: Property.DynamicProperties({ + description: '', + displayName: '', + required: true, + refreshers: ['flow', 'mode'], + props: async (propsValue) => { + const castedFlowValue = propsValue['flow'] as unknown as FlowValue; + const mode = propsValue['mode'] as unknown as string; + const fields: DynamicPropsValue = {}; + + + if (!isNil(castedFlowValue)) { + if (mode === 'simple') { + fields['payload'] = Property.Object({ + displayName: 'Payload', + required: true, + defaultValue: (castedFlowValue.exampleData as unknown as { sampleData: object }).sampleData, + }); + } + else{ + fields['payload'] = Property.Json({ + displayName: 'Payload', + description: + 'Provide the data to be passed to the flow', + required: true, + defaultValue: (castedFlowValue.exampleData as unknown as { sampleData: object }).sampleData, + }); + } + } + return fields; + }, + }), + waitForResponse: Property.Checkbox({ + displayName: 'Wait for Response', + required: false, + defaultValue: false, + }), + testingProps: Property.DynamicProperties({ + description: '', + displayName: '', + required: true, + refreshers: ['waitForResponse', 'mode'], + props: async (propsValue) => { + const fields: DynamicPropsValue = {}; + if (!propsValue['waitForResponse']) { + return fields; + } + + const mode = propsValue['mode'] as unknown as string; + + if (mode === 'simple') { + fields['data'] = Property.Object({ + displayName: 'Example Response (For Testing)', + required: true, + description: 'This data will be returned when testing this step, and is necessary to proceed with building the flow', + defaultValue: {}, + }); + } else { + fields['data'] = Property.Json({ + displayName: 'Example Response (For Testing)', + required: true, + description: 'This data will be returned when testing this step, and is necessary to proceed with building the flow', + defaultValue: {}, + }); + } + + return fields; + } + }) + }, + async test(context) { + return { + data: context.propsValue?.testingProps?.['data'] ?? {} + }; + }, + async run(context) { + if (context.executionType === ExecutionType.RESUME) { + const response = context.resumePayload.body as CallableFlowResponse; + return { + data: response.data + } + } + const payload = context.propsValue.flowProps['payload']; + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.serverUrl}v1/webhooks/${context.propsValue.flow?.id}`, + headers: { + 'Content-Type': 'application/json', + }, + body: { + data: payload, + callbackUrl: context.propsValue.waitForResponse ? context.generateResumeUrl({ + queryParams: {} + }) : undefined, + }, + }); + if (context.propsValue.waitForResponse) { + context.run.pause({ + pauseMetadata: { + type: PauseType.WEBHOOK, + response: {}, + } + }) + } + return response.body; + }, + errorHandlingOptions: { + continueOnFailure: { + defaultValue:false, + hide:false, + }, + retryOnFailure: { + defaultValue:false, + hide:false, + } + } +}); diff --git a/packages/pieces/community/subflows/src/lib/actions/respond.ts b/packages/pieces/community/subflows/src/lib/actions/respond.ts new file mode 100644 index 0000000..7174738 --- /dev/null +++ b/packages/pieces/community/subflows/src/lib/actions/respond.ts @@ -0,0 +1,72 @@ +import { DynamicPropsValue, Property, StoreScope, createAction } from '@activepieces/pieces-framework'; +import { callableFlowKey, CallableFlowResponse, MOCK_CALLBACK_IN_TEST_FLOW_URL } from '../common'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; + +export const response = createAction({ + name: 'returnResponse', + displayName: 'Return Response', + description: 'Return response to the original flow', + props: { + mode: Property.StaticDropdown({ + displayName: 'Mode', + description: 'Choose Simple for key-value or Advanced for JSON.', + required: true, + defaultValue: 'simple', + options: { + disabled: false, + options: [ + { + label: 'Simple', + value: 'simple', + }, + { + + label: 'Advanced', + value: 'advanced', + }, + ], + }, + }), + response: Property.DynamicProperties({ + displayName: 'Response', + required: true, + refreshers: ['mode'], + props: async (propsValue) => { + const mode = propsValue['mode'] as unknown as string; + const fields: DynamicPropsValue = {}; + if (mode === 'simple') { + fields['response'] = Property.Object({ + displayName: 'Response', + required: true, + }); + } else { + fields['response'] = Property.Json({ + displayName: 'Response', + required: true, + }); + } + return fields; + }, + }), + }, + async test(context) { + return context.propsValue.response['response']; + }, + async run(context) { + const response = context.propsValue.response['response']; + const callbackUrl = await context.store.get(callableFlowKey(context.run.id), StoreScope.FLOW); + const isNotTestFlow = callbackUrl !== MOCK_CALLBACK_IN_TEST_FLOW_URL; + if (isNotTestFlow && !isNil(callbackUrl)) { + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: callbackUrl, + body: { + data: response + }, + retries: 10, + }); + } + return response; + }, +}); diff --git a/packages/pieces/community/subflows/src/lib/common.ts b/packages/pieces/community/subflows/src/lib/common.ts new file mode 100644 index 0000000..ec0f160 --- /dev/null +++ b/packages/pieces/community/subflows/src/lib/common.ts @@ -0,0 +1,13 @@ + + +export const callableFlowKey = (runId: string) => `callableFlow_${runId}`; + +export type CallableFlowRequest = { + data: unknown; + callbackUrl: string; +} +export type CallableFlowResponse = { + data: unknown; +} + +export const MOCK_CALLBACK_IN_TEST_FLOW_URL = 'MOCK'; \ No newline at end of file diff --git a/packages/pieces/community/subflows/src/lib/triggers/callable-flow.ts b/packages/pieces/community/subflows/src/lib/triggers/callable-flow.ts new file mode 100644 index 0000000..0fd4e4e --- /dev/null +++ b/packages/pieces/community/subflows/src/lib/triggers/callable-flow.ts @@ -0,0 +1,81 @@ +import { + createTrigger, + DynamicPropsValue, + Property, + StoreScope, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { callableFlowKey, CallableFlowRequest, MOCK_CALLBACK_IN_TEST_FLOW_URL } from '../common'; + +export const callableFlow = createTrigger({ + name: 'callableFlow', + displayName: 'Callable Flow', + description: 'Waiting to be triggered from another flow', + props: { + mode: Property.StaticDropdown({ + displayName: 'Mode', + required: true, + description: 'Choose Simple for key-value or Advanced for JSON.', + defaultValue: 'simple', + options: { + disabled: false, + options: [ + { + label: 'Simple', + value: 'simple', + }, + { + label: 'Advanced', + value: 'advanced', + }, + ], + }, + }), + exampleData: Property.DynamicProperties({ + displayName: 'Sample Data', + description: 'The schema to be passed to the flow', + required: true, + refreshers: ['mode'], + props: async (propsValue) => { + const mode = propsValue['mode'] as unknown as string; + const fields: DynamicPropsValue = {}; + if (mode === 'simple') { + fields['sampleData'] = Property.Object({ + displayName: 'Sample Data', + required: true, + }); + } else { + fields['sampleData'] = Property.Json({ + displayName: 'Sample Data', + required: true, + }); + } + return fields; + }, + }), + }, + sampleData: null, + type: TriggerStrategy.WEBHOOK, + async onEnable() { + // ignore + }, + async onDisable() { + // ignore + }, + async test(context) { + const request: CallableFlowRequest = { + data: context.propsValue.exampleData['sampleData'], + callbackUrl: MOCK_CALLBACK_IN_TEST_FLOW_URL + } + return [request]; + }, + async run(context) { + return [context.payload.body]; + }, + async onStart(context) { + const request = context.payload as CallableFlowRequest; + if (request.callbackUrl) { + await context.store.put(callableFlowKey(context.run.id), request.callbackUrl, StoreScope.FLOW); + } + } +}); diff --git a/packages/pieces/community/subflows/tsconfig.json b/packages/pieces/community/subflows/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/subflows/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/subflows/tsconfig.lib.json b/packages/pieces/community/subflows/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/subflows/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/supabase/.eslintrc.json b/packages/pieces/community/supabase/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/supabase/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/supabase/README.md b/packages/pieces/community/supabase/README.md new file mode 100644 index 0000000..7bbe829 --- /dev/null +++ b/packages/pieces/community/supabase/README.md @@ -0,0 +1,7 @@ +# pieces-supabase + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-supabase` to build the library. diff --git a/packages/pieces/community/supabase/package.json b/packages/pieces/community/supabase/package.json new file mode 100644 index 0000000..c0d9792 --- /dev/null +++ b/packages/pieces/community/supabase/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-supabase", + "version": "0.0.7" +} \ No newline at end of file diff --git a/packages/pieces/community/supabase/project.json b/packages/pieces/community/supabase/project.json new file mode 100644 index 0000000..fa667ad --- /dev/null +++ b/packages/pieces/community/supabase/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-supabase", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/supabase/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/supabase", + "tsConfig": "packages/pieces/community/supabase/tsconfig.lib.json", + "packageJson": "packages/pieces/community/supabase/package.json", + "main": "packages/pieces/community/supabase/src/index.ts", + "assets": [ + "packages/pieces/community/supabase/*.md", + { + "input": "packages/pieces/community/supabase/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-supabase {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/supabase/src/index.ts b/packages/pieces/community/supabase/src/index.ts new file mode 100644 index 0000000..bc0667d --- /dev/null +++ b/packages/pieces/community/supabase/src/index.ts @@ -0,0 +1,46 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { uploadFile } from './lib/actions/upload-file'; + +const markdown = ` +Copy the **URL** and **Service API Key** from your Supabase project settings. +`; +export const supabaseAuth = PieceAuth.CustomAuth({ + required: true, + description: markdown, + props: { + url: Property.ShortText({ + displayName: 'URL', + required: true, + }), + apiKey: PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + }), + }, +}); +export const supabase = createPiece({ + displayName: 'Supabase', + description: 'The open-source Firebase alternative', + auth: supabaseAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/supabase.png', + categories: [PieceCategory.DEVELOPER_TOOLS], + authors: ["kishanprmr","MoShizzle","abuaboud"], + actions: [ + uploadFile, + createCustomApiCallAction({ + baseUrl: (auth) => (auth as { url: string }).url, + auth: supabaseAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as { apiKey: string }).apiKey}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/supabase/src/lib/actions/upload-file.ts b/packages/pieces/community/supabase/src/lib/actions/upload-file.ts new file mode 100644 index 0000000..705ea2b --- /dev/null +++ b/packages/pieces/community/supabase/src/lib/actions/upload-file.ts @@ -0,0 +1,44 @@ +import { supabaseAuth } from '../../index'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { createClient } from '@supabase/supabase-js'; + +export const uploadFile = createAction({ + auth: supabaseAuth, + name: 'upload-file', + displayName: 'Upload File', + description: 'Upload a file to Supabase Storage', + props: { + filePath: Property.ShortText({ + displayName: 'File path', + required: true, + }), + bucket: Property.ShortText({ + displayName: 'Bucket', + required: true, + }), + file: Property.File({ + displayName: 'Base64 or URL', + required: true, + }), + }, + async run(context) { + const { url, apiKey } = context.auth; + const { file, filePath, bucket } = context.propsValue; + const base64 = file.base64; + // Convert base64 to array buffer + const arrayBuffer = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); + const supabase = createClient(url, apiKey); + const { data, error } = await supabase.storage + .from(bucket) + .upload(filePath, arrayBuffer); + if (error) { + throw new Error(error.message); + } + const { data: pbData } = supabase.storage + .from(bucket) + .getPublicUrl(filePath); + return { + publicUrl: pbData.publicUrl, + }; + }, +}); diff --git a/packages/pieces/community/supabase/tsconfig.json b/packages/pieces/community/supabase/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/supabase/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/supabase/tsconfig.lib.json b/packages/pieces/community/supabase/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/supabase/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/supadata/.eslintrc.json b/packages/pieces/community/supadata/.eslintrc.json new file mode 100644 index 0000000..19a5cf9 --- /dev/null +++ b/packages/pieces/community/supadata/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/supadata/package.json b/packages/pieces/community/supadata/package.json new file mode 100644 index 0000000..2e4f293 --- /dev/null +++ b/packages/pieces/community/supadata/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-supadata", + "version": "0.0.1" +} \ No newline at end of file diff --git a/packages/pieces/community/supadata/project.json b/packages/pieces/community/supadata/project.json new file mode 100644 index 0000000..835923f --- /dev/null +++ b/packages/pieces/community/supadata/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-supadata", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/supadata/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/supadata", + "tsConfig": "packages/pieces/community/supadata/tsconfig.lib.json", + "packageJson": "packages/pieces/community/supadata/package.json", + "main": "packages/pieces/community/supadata/src/index.ts", + "assets": [ + "packages/pieces/community/supadata/*.md", + { + "input": "packages/pieces/community/supadata/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-supadata {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/supadata/src/index.ts b/packages/pieces/community/supadata/src/index.ts new file mode 100644 index 0000000..13bf6de --- /dev/null +++ b/packages/pieces/community/supadata/src/index.ts @@ -0,0 +1,46 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { getTranscriptAction } from './lib/actions/get-transcript'; +import { PieceCategory } from '@activepieces/shared'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { supadataConfig } from './lib/config'; + +const markdownDescription = ` +To obtain your free Supadata API Key, sign up at [Supadata](https://supadata.ai) and then copy the key available in the [dashboard](https://dash.supadata.ai). +`; + +export const supadataAuth = PieceAuth.SecretText({ + description: markdownDescription, + displayName: 'API Key', + required: true, + validate: async (auth) => { + try { + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${supadataConfig.baseUrl}/health`, + headers: { + [supadataConfig.accessTokenHeaderKey]: auth.auth, + }, + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API Key.', + }; + } + }, +}); + +export const supadata = createPiece({ + displayName: 'Supadata', + auth: supadataAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/supadata.svg', + authors: ['rafalzawadzki'], + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE, PieceCategory.DEVELOPER_TOOLS, PieceCategory.CONTENT_AND_FILES], + description: 'YouTube Transcripts', + actions: [getTranscriptAction], + triggers: [], +}); \ No newline at end of file diff --git a/packages/pieces/community/supadata/src/lib/actions/get-transcript.ts b/packages/pieces/community/supadata/src/lib/actions/get-transcript.ts new file mode 100644 index 0000000..62a2436 --- /dev/null +++ b/packages/pieces/community/supadata/src/lib/actions/get-transcript.ts @@ -0,0 +1,51 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, QueryParams, httpClient } from '@activepieces/pieces-common'; +import { supadataAuth } from '../..'; +import { supadataConfig } from '../config'; + +export const getTranscriptAction = createAction({ + name: 'get_transcript', + displayName: 'Get Transcript', + description: 'Fetches transcript of a YouTube video.', + auth: supadataAuth, + props: { + url: Property.ShortText({ + displayName: 'YouTube URL', + description: 'The URL of a single YouTube video.', + required: true, + }), + lang: Property.ShortText({ + displayName: 'Language Preference', + description: 'Preferred language of the transcript. If not available, the first available language will be returned.', + required: false, + }), + text: Property.Checkbox({ + displayName: 'Merge Text', + description: 'If true, the transcript will be merged into a single text instead of timestamped chunks.', + required: false, + defaultValue: true, + }) + }, + async run(context) { + const { url, text, lang } = context.propsValue; + const qs:QueryParams = { + url, + text: text ? 'true' : 'false', + } + + if (lang) { + qs['lang'] = lang; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${supadataConfig.baseUrl}/youtube/transcript`, + headers: { + [supadataConfig.accessTokenHeaderKey]: context.auth, + }, + queryParams: qs, + }); + + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/supadata/src/lib/config.ts b/packages/pieces/community/supadata/src/lib/config.ts new file mode 100644 index 0000000..70ec46b --- /dev/null +++ b/packages/pieces/community/supadata/src/lib/config.ts @@ -0,0 +1,4 @@ +export const supadataConfig = { + baseUrl: 'https://api.supadata.ai/v1', + accessTokenHeaderKey: 'x-api-key', +}; diff --git a/packages/pieces/community/supadata/tsconfig.json b/packages/pieces/community/supadata/tsconfig.json new file mode 100644 index 0000000..18bb11e --- /dev/null +++ b/packages/pieces/community/supadata/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/supadata/tsconfig.lib.json b/packages/pieces/community/supadata/tsconfig.lib.json new file mode 100644 index 0000000..e79ef0f --- /dev/null +++ b/packages/pieces/community/supadata/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} \ No newline at end of file diff --git a/packages/pieces/community/surrealdb/.eslintrc.json b/packages/pieces/community/surrealdb/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/surrealdb/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/surrealdb/README.md b/packages/pieces/community/surrealdb/README.md new file mode 100644 index 0000000..b69c85d --- /dev/null +++ b/packages/pieces/community/surrealdb/README.md @@ -0,0 +1,7 @@ +# pieces-surrealdb + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-surrealdb` to build the library. diff --git a/packages/pieces/community/surrealdb/package.json b/packages/pieces/community/surrealdb/package.json new file mode 100644 index 0000000..c8fd866 --- /dev/null +++ b/packages/pieces/community/surrealdb/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-surrealdb", + "version": "0.0.1" +} diff --git a/packages/pieces/community/surrealdb/project.json b/packages/pieces/community/surrealdb/project.json new file mode 100644 index 0000000..25e0549 --- /dev/null +++ b/packages/pieces/community/surrealdb/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-surrealdb", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/surrealdb/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/surrealdb", + "tsConfig": "packages/pieces/community/surrealdb/tsconfig.lib.json", + "packageJson": "packages/pieces/community/surrealdb/package.json", + "main": "packages/pieces/community/surrealdb/src/index.ts", + "assets": [ + "packages/pieces/community/surrealdb/*.md", + { + "input": "packages/pieces/community/surrealdb/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-surrealdb {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/surrealdb/src/index.ts b/packages/pieces/community/surrealdb/src/index.ts new file mode 100644 index 0000000..8611a43 --- /dev/null +++ b/packages/pieces/community/surrealdb/src/index.ts @@ -0,0 +1,69 @@ +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { runQuery } from './lib/actions/run-query'; +import { newRow } from './lib/triggers/new-row'; +import surrealClient from './lib/common'; + +export const surrealdbAuth = PieceAuth.CustomAuth({ + props: { + url: Property.ShortText({ + displayName: 'Connection URL', + required: true, + description: 'Connection string, e.g. http://1.2.3.5:8000.', + }), + database: Property.ShortText({ + displayName: 'Database', + description: + 'A string indicating the name of the database to connect to.', + required: true, + }), + namespace: Property.ShortText({ + displayName: 'Namespace', + required: true, + description: + 'As string indicating the namespace of the database to connect to.', + }), + username: Property.ShortText({ + displayName: 'Username', + required: true, + description: + 'As string indicating the username of the database to connect to.', + }), + password: Property.ShortText({ + displayName: 'Password', + required: true, + description: + 'As string indicating the password of the database to connect to.', + }), + }, + required: true, + validate: async ({ auth }) => { + try { + surrealClient.query(auth, 'INFO for db'); + } catch (e) { + return { + valid: false, + error: JSON.stringify(e), + }; + } + return { + valid: true, + }; + }, +}); + +export const surrealdb = createPiece({ + displayName: 'SurrealDB', + description: "Multi Model Database", + minimumSupportedRelease: '0.30.0', + categories: [PieceCategory.DEVELOPER_TOOLS], + logoUrl: 'https://cdn.activepieces.com/pieces/surrealdb.jpg', + authors: ['maarteNNNN'], + auth: surrealdbAuth, + actions: [runQuery], + triggers: [newRow], +}); diff --git a/packages/pieces/community/surrealdb/src/lib/actions/run-query.ts b/packages/pieces/community/surrealdb/src/lib/actions/run-query.ts new file mode 100644 index 0000000..d40a50d --- /dev/null +++ b/packages/pieces/community/surrealdb/src/lib/actions/run-query.ts @@ -0,0 +1,59 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { surrealdbAuth } from '../..'; +import surrealClient from '../common'; + +export const runQuery = createAction({ + auth: surrealdbAuth, + name: 'run-query', + displayName: 'Run Query', + description: 'Run a query in SurrealDB.', + props: { + markdown: Property.MarkDown({ + value: ` + **NOTE:** Prevent SQL injection by using parameterized queries. + `, + }), + query: Property.ShortText({ + displayName: 'Query', + description: 'Provide a SurrealDB query string to execute.', + required: true, + }), + queryMarkdown: Property.MarkDown({ + value: ` + **NOTE:** Query example: \`SELECT * FROM table_name WHERE name = $name\`. Then add the name parameter in the arguments. + `, + }), + args: Property.Object({ + displayName: 'Arguments', + description: "Add all arguments as names here, don't add the $ sign.", + required: false, + }), + query_timeout: Property.Number({ + displayName: 'Query Timeout (ms)', + description: + 'The maximum time to wait for a query to complete before timing out.', + required: false, + defaultValue: 30000, + }), + application_name: Property.ShortText({ + displayName: 'Application Name', + description: + 'An identifier for the client application executing the query.', + required: false, + }), + }, + + async run(context) { + try { + const { query, args } = context.propsValue; + const response = await surrealClient.query( + context.auth, + query, + args as Record + ); + return response.body; + } catch (error) { + throw new Error(`Query execution failed: ${(error as Error).message}`); + } + }, +}); diff --git a/packages/pieces/community/surrealdb/src/lib/common.ts b/packages/pieces/community/surrealdb/src/lib/common.ts new file mode 100644 index 0000000..8996001 --- /dev/null +++ b/packages/pieces/community/surrealdb/src/lib/common.ts @@ -0,0 +1,40 @@ +import { + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; +import { PiecePropValueSchema } from '@activepieces/pieces-framework'; +import { surrealdbAuth } from '..'; + +const query = async ( + auth: PiecePropValueSchema, + query: string, + args?: Record +) => { + const { url, username, password, namespace, database } = auth; + + const sqlUrl = new URL('/sql', url); + const request: HttpRequest = { + method: HttpMethod.POST, + url: sqlUrl.toString(), + headers: { + 'Content-Type': 'text/plain', + Accept: 'application/json', + Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString( + 'base64' + )}`, + 'surreal-ns': namespace, + 'surreal-db': database, + }, + queryParams: args, + body: query, + }; + + const response = await httpClient.sendRequest(request); + + return response; +}; + +export default { + query, +}; diff --git a/packages/pieces/community/surrealdb/src/lib/triggers/new-row.ts b/packages/pieces/community/surrealdb/src/lib/triggers/new-row.ts new file mode 100644 index 0000000..30abe8b --- /dev/null +++ b/packages/pieces/community/surrealdb/src/lib/triggers/new-row.ts @@ -0,0 +1,190 @@ +import { + createTrigger, + TriggerStrategy, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import dayjs from 'dayjs'; +import { surrealdbAuth } from '../..'; +import client from '../common'; +import crypto from 'crypto'; + +// replace auth with piece auth variable +const polling: Polling< + PiecePropValueSchema, + { + table: string; + order_by: string; + order_direction: 'ASC' | 'DESC' | undefined; + } +> = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ auth, propsValue, lastItemId }) => { + const lastItem = lastItemId as string; + const query = constructQuery({ + order_by: propsValue.order_by, + lastItem: lastItem, + order_direction: propsValue.order_direction, + }); + + const authProps = auth as PiecePropValueSchema; + const result = await client.query(authProps, query, { + table: propsValue.table, + }); + + const items = result.body[0].result.map(function ( + row: Record + ) { + const rowHash = crypto + .createHash('md5') + .update(JSON.stringify(row)) + .digest('hex'); + const isTimestamp = dayjs(row[propsValue.order_by]).isValid(); + const orderValue = isTimestamp + ? dayjs(row[propsValue.order_by]).toISOString() + : row[propsValue.order_by]; + return { + id: orderValue + '|' + rowHash, + data: row, + }; + }); + + return items; + }, +}; + +function constructQuery({ + order_by, + lastItem, + order_direction, +}: { + order_by: string; + order_direction: 'ASC' | 'DESC' | undefined; + lastItem: string; +}): string { + const lastOrderKey = lastItem ? lastItem.split('|')[0] : null; + if (lastOrderKey === null) { + switch (order_direction) { + case 'ASC': + return `SELECT * FROM type::table($table) ORDER BY ${order_by} ASC LIMIT 5`; + case 'DESC': + return `SELECT * FROM type::table($table) ORDER BY ${order_by} DESC LIMIT 5`; + default: + throw new Error( + JSON.stringify({ + message: 'Invalid order direction', + order_direction: order_direction, + }) + ); + } + } else { + switch (order_direction) { + case 'ASC': + return `SELECT * FROM type::table($table) WHERE ${order_by} <= '${lastOrderKey}' ORDER BY ${order_by} ASC`; + case 'DESC': + return `SELECT * FROM type::table($table) WHERE ${order_by} >= '${lastOrderKey}' ORDER BY ${order_by} DESC`; + default: + throw new Error( + JSON.stringify({ + message: 'Invalid order direction', + order_direction: order_direction, + }) + ); + } + } +} + +export const newRow = createTrigger({ + name: 'new-row', + displayName: 'New Row', + description: 'Triggers when a new row is added to the defined table.', + props: { + description: Property.MarkDown({ + value: `**NOTE:** The trigger fetches the latest rows using the provided order by column (newest first), and then will keep polling until the previous last row is reached. It's suggested to add a created_at timestamp. \`DEFINE FIELD OVERWRITE createdAt ON schedule VALUE time::now() READONLY;\``, + }), + table: Property.Dropdown({ + displayName: 'Table name', + required: true, + refreshers: ['auth'], + refreshOnSearch: false, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + } + const authProps = auth as PiecePropValueSchema; + try { + const result = await client.query(authProps, 'INFO FOR DB'); + const options = Object.keys(result.body[0].result.tables).map( + (row) => ({ + label: row, + value: row, + }) + ); + return { + disabled: false, + options, + }; + } catch (e) { + return { + disabled: true, + options: [], + placeholder: JSON.stringify(e), + }; + } + }, + }), + order_by: Property.ShortText({ + displayName: 'Column to order by', + description: 'Use something like a created timestamp.', + required: true, + defaultValue: 'created_at', + }), + order_direction: Property.StaticDropdown<'ASC' | 'DESC'>({ + displayName: 'Order Direction', + description: + 'The direction to sort by such that the newest rows are fetched first.', + required: true, + options: { + options: [ + { + label: 'Ascending', + value: 'ASC', + }, + { + label: 'Descending', + value: 'DESC', + }, + ], + }, + defaultValue: 'DESC', + }), + }, + sampleData: {}, + type: TriggerStrategy.POLLING, + auth: surrealdbAuth, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async onEnable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onEnable(polling, { store, propsValue, auth }); + }, + + async onDisable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onDisable(polling, { store, propsValue, auth }); + }, + + async run(context) { + return await pollingHelper.poll(polling, context); + }, +}); diff --git a/packages/pieces/community/surrealdb/tsconfig.json b/packages/pieces/community/surrealdb/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/surrealdb/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/surrealdb/tsconfig.lib.json b/packages/pieces/community/surrealdb/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/surrealdb/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/surveymonkey/.eslintrc.json b/packages/pieces/community/surveymonkey/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/surveymonkey/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/surveymonkey/README.md b/packages/pieces/community/surveymonkey/README.md new file mode 100644 index 0000000..c3fa05a --- /dev/null +++ b/packages/pieces/community/surveymonkey/README.md @@ -0,0 +1,7 @@ +# pieces-surveymonkey + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-surveymonkey` to build the library. diff --git a/packages/pieces/community/surveymonkey/package.json b/packages/pieces/community/surveymonkey/package.json new file mode 100644 index 0000000..6355299 --- /dev/null +++ b/packages/pieces/community/surveymonkey/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-surveymonkey", + "version": "0.0.5" +} \ No newline at end of file diff --git a/packages/pieces/community/surveymonkey/project.json b/packages/pieces/community/surveymonkey/project.json new file mode 100644 index 0000000..1fa411c --- /dev/null +++ b/packages/pieces/community/surveymonkey/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-surveymonkey", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/surveymonkey/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/surveymonkey", + "tsConfig": "packages/pieces/community/surveymonkey/tsconfig.lib.json", + "packageJson": "packages/pieces/community/surveymonkey/package.json", + "main": "packages/pieces/community/surveymonkey/src/index.ts", + "assets": [ + "packages/pieces/community/surveymonkey/*.md", + { + "input": "packages/pieces/community/surveymonkey/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-surveymonkey {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/surveymonkey/src/index.ts b/packages/pieces/community/surveymonkey/src/index.ts new file mode 100644 index 0000000..a00976b --- /dev/null +++ b/packages/pieces/community/surveymonkey/src/index.ts @@ -0,0 +1,42 @@ +import { + createPiece, + OAuth2PropertyValue, + PieceAuth, +} from '@activepieces/pieces-framework'; + +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceCategory } from '@activepieces/shared'; +import { newResponse } from './lib/triggers/new-response'; + +export const smAuth = PieceAuth.OAuth2({ + authUrl: 'https://api.surveymonkey.com/oauth/authorize', + tokenUrl: 'https://api.surveymonkey.com/oauth/token', + required: true, + scope: [ + 'responses_read', + 'responses_read_detail', + 'webhooks_read', + 'webhooks_write', + 'surveys_read', + ], +}); + +export const surveymonkey = createPiece({ + displayName: 'SurveyMonkey', + description: 'Receive survey responses from SurveyMonkey', + auth: smAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/surveymonkey.png', + categories: [PieceCategory.FORMS_AND_SURVEYS], + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + actions: [ + createCustomApiCallAction({ + baseUrl: () => 'https://api.surveymonkey.com/v3', + auth: smAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [newResponse], +}); diff --git a/packages/pieces/community/surveymonkey/src/lib/common/index.ts b/packages/pieces/community/surveymonkey/src/lib/common/index.ts new file mode 100644 index 0000000..9fad0e7 --- /dev/null +++ b/packages/pieces/community/surveymonkey/src/lib/common/index.ts @@ -0,0 +1,111 @@ +import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, + AuthenticationType, +} from '@activepieces/pieces-common'; + +export const smCommon = { + baseUrl: 'https://api.surveymonkey.com/v3', + survey: Property.Dropdown({ + displayName: 'Survey', + required: true, + refreshers: [], + options: async (context) => { + if (!context['auth']) { + return { + disabled: true, + options: [], + placeholder: 'Connect your account', + }; + } + + const authProp: any = context['auth']; + const options: any[] = await smCommon.getSurveys(authProp.access_token); + return { + options: options, + placeholder: 'Choose survey to connect', + }; + }, + }), + getSurveys: async (accessToken: string) => { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${smCommon.baseUrl}/surveys`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }; + const response = await httpClient.sendRequest(request); + const newValues = response.body['data'].map((survey: any) => { + return { + label: survey.title, + value: survey.id, + }; + }); + + return newValues; + }, + + subscribeWebhook: async ( + surveyId: string | number, + webhookUrl: string, + accessToken: string + ) => { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${smCommon.baseUrl}/webhooks`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + body: { + name: 'New Response Webhook', + event_type: 'response_created', + object_type: 'survey', + object_ids: [surveyId], + subscription_url: webhookUrl, + }, + }; + + const webhookData = await httpClient.sendRequest(request); + return webhookData.body['id']; + }, + + unsubscribeWebhook: async ( + webhookId: string | number, + accessToken: string + ) => { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `${smCommon.baseUrl}/webhooks/${webhookId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }; + + const deleteResponse = await httpClient.sendRequest(request); + return deleteResponse; + }, + + async getResponseDetails( + accessToken: string, + surveyId: string | number, + responseId: string | number + ) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${smCommon.baseUrl}/surveys/${surveyId}/responses/${responseId}/details`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, +}; diff --git a/packages/pieces/community/surveymonkey/src/lib/triggers/new-response.ts b/packages/pieces/community/surveymonkey/src/lib/triggers/new-response.ts new file mode 100644 index 0000000..d80730e --- /dev/null +++ b/packages/pieces/community/surveymonkey/src/lib/triggers/new-response.ts @@ -0,0 +1,53 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { smCommon } from '../common'; +import { smAuth } from '../..'; + +export const newResponse = createTrigger({ + auth: smAuth, + name: 'new_response', + displayName: 'New Response', + description: 'Triggers when a new response is submitted', + type: TriggerStrategy.WEBHOOK, + sampleData: {}, + props: { + survey: smCommon.survey, + }, + //Create the webhook in SurveyMonkey and save the webhook ID in store for disable behavior + async onEnable(context) { + const webhookId = await smCommon.subscribeWebhook( + context.propsValue.survey as number, + context.webhookUrl, + context.auth['access_token'] + ); + + await context.store?.put('_new_response_trigger', { + webhookId: webhookId, + }); + }, + //Delete the webhook from SurveyMonkey + async onDisable(context) { + const response: any = await context.store?.get('_new_response_trigger'); + + if (response !== null && response !== undefined) { + await smCommon.unsubscribeWebhook( + response.webhookId, + context.auth['access_token'] + ); + } + }, + //Return new response + async run(context) { + const payloadBody = context.payload.body as PayloadBody; + const responseData = await smCommon.getResponseDetails( + context.auth['access_token'], + context.propsValue['survey'] as number, + payloadBody.object_id + ); + + return [responseData]; + }, +}); + +type PayloadBody = { + object_id: string | number; +}; diff --git a/packages/pieces/community/surveymonkey/tsconfig.json b/packages/pieces/community/surveymonkey/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/surveymonkey/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/surveymonkey/tsconfig.lib.json b/packages/pieces/community/surveymonkey/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/surveymonkey/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/tables/.eslintrc.json b/packages/pieces/community/tables/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/tables/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/tables/README.md b/packages/pieces/community/tables/README.md new file mode 100644 index 0000000..094905c --- /dev/null +++ b/packages/pieces/community/tables/README.md @@ -0,0 +1,7 @@ +# pieces-tables + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-tables` to build the library. diff --git a/packages/pieces/community/tables/package.json b/packages/pieces/community/tables/package.json new file mode 100644 index 0000000..670e09b --- /dev/null +++ b/packages/pieces/community/tables/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-tables", + "version": "0.1.1" +} diff --git a/packages/pieces/community/tables/project.json b/packages/pieces/community/tables/project.json new file mode 100644 index 0000000..8aac277 --- /dev/null +++ b/packages/pieces/community/tables/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-tables", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/tables/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/tables", + "tsConfig": "packages/pieces/community/tables/tsconfig.lib.json", + "packageJson": "packages/pieces/community/tables/package.json", + "main": "packages/pieces/community/tables/src/index.ts", + "assets": [ + "packages/pieces/community/tables/*.md", + { + "input": "packages/pieces/community/tables/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/tables/src/index.ts b/packages/pieces/community/tables/src/index.ts new file mode 100644 index 0000000..054c076 --- /dev/null +++ b/packages/pieces/community/tables/src/index.ts @@ -0,0 +1,21 @@ +import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { createRecords } from "./lib/actions/create-records"; +import { PieceCategory } from "@activepieces/shared"; +import { deleteRecord } from "./lib/actions/delete-record"; +import { updateRecord } from "./lib/actions/update-record"; +import { getRecord } from "./lib/actions/get-record"; +import { findRecords } from "./lib/actions/find-records"; +import { newRecordTrigger } from "./lib/triggers/new-record"; +import { deletedRecordTrigger } from "./lib/triggers/deleted-record"; +import { updatedRecordTrigger } from "./lib/triggers/updated-record"; + +export const tables = createPiece({ + displayName: 'Tables', + logoUrl: 'https://cdn.activepieces.com/pieces/tables_piece.svg', + categories: [PieceCategory.CORE], + minimumSupportedRelease: '0.54.1', + authors: ['amrdb'], + auth: PieceAuth.None(), + actions: [createRecords, deleteRecord, updateRecord, getRecord, findRecords], + triggers: [newRecordTrigger, updatedRecordTrigger, deletedRecordTrigger], +}); diff --git a/packages/pieces/community/tables/src/lib/actions/create-records.ts b/packages/pieces/community/tables/src/lib/actions/create-records.ts new file mode 100644 index 0000000..d1131d2 --- /dev/null +++ b/packages/pieces/community/tables/src/lib/actions/create-records.ts @@ -0,0 +1,76 @@ +import { createAction, PieceAuth, Property } from '@activepieces/pieces-framework'; +import { AuthenticationType, httpClient, HttpMethod, propsValidation } from '@activepieces/pieces-common'; +import { CreateRecordsRequest } from '@activepieces/shared'; +import { tablesCommon } from '../common'; + +export const createRecords = createAction({ + name: 'tables-create-records', + displayName: 'Create Record(s)', + description: 'Insert one or more new records to a table.', + auth: PieceAuth.None(), + props: { + table_id: tablesCommon.table_id, + values: Property.DynamicProperties({ + displayName: 'Records', + description: 'The records to create.', + required: true, + refreshers: ['table_id'], + props: async ({ table_id }, context) => { + const tableExternalId = table_id as unknown as string; + if ((tableExternalId ?? '').toString().length === 0) { + return {}; + } + const tableId = await tablesCommon.convertTableExternalIdToId(tableExternalId, context); + + const fields = await tablesCommon.createFieldProperties({ tableId, context }); + if ('markdown' in fields) { + return fields; + } + + return { + values: Property.Array({ + displayName: 'Records', + description: 'Add one or more records to insert', + required: true, + properties: fields, + }), + }; + }, + }), + }, + async run(context) { + const { table_id: tableExternalId, values } = context.propsValue; + const tableId = await tablesCommon.convertTableExternalIdToId(tableExternalId, context); + + const records: CreateRecordsRequest['records'] = values['values'].map((record: Record) => + Object.entries(record) + .filter(([_, value]) => value !== null && value !== undefined && value !== '') + .map(([fieldId, value]) => ({ + fieldId, + value, + })) + ) + const tableFields = await tablesCommon.getTableFields({ tableId, context }); + const fieldValidations = tablesCommon.createFieldValidations(tableFields); + + for (const record of values['values']) { + const cleanedRecord = Object.fromEntries(Object.entries(record).filter(([_, value]) => value !== null && value !== undefined && value !== '')); + await propsValidation.validateZod(cleanedRecord, fieldValidations); + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.server.apiUrl}v1/records`, + body: { + records, + tableId, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.server.token, + }, + }); + + return response.body.map(tablesCommon.formatRecord); + }, +}); diff --git a/packages/pieces/community/tables/src/lib/actions/delete-record.ts b/packages/pieces/community/tables/src/lib/actions/delete-record.ts new file mode 100644 index 0000000..7148aef --- /dev/null +++ b/packages/pieces/community/tables/src/lib/actions/delete-record.ts @@ -0,0 +1,37 @@ +import { createAction, PieceAuth, Property } from '@activepieces/pieces-framework'; +import { tablesCommon } from '../common'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const deleteRecord = createAction({ + name: 'tables-delete-record', + displayName: 'Delete Record(s)', + description: 'Delete record(s) from a table', + auth: PieceAuth.None(), + props: { + table_id: tablesCommon.table_id, + records_ids: Property.Array({ + displayName: 'Records IDs', + required: true, + description: 'The IDs of the records to delete' + }), + }, + async run(context) { + const { records_ids } = context.propsValue; + + await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: `${context.server.apiUrl}v1/records/`, + body: { + ids: records_ids, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.server.token, + }, + }); + + return { + success: true + }; + }, +}); diff --git a/packages/pieces/community/tables/src/lib/actions/find-records.ts b/packages/pieces/community/tables/src/lib/actions/find-records.ts new file mode 100644 index 0000000..49cc0a1 --- /dev/null +++ b/packages/pieces/community/tables/src/lib/actions/find-records.ts @@ -0,0 +1,151 @@ +import { createAction, DynamicPropsValue, PieceAuth, Property, PropertyContext } from '@activepieces/pieces-framework'; +import { tablesCommon } from '../common'; +import { AuthenticationType, httpClient, HttpMethod, propsValidation } from '@activepieces/pieces-common'; +import { FieldType, Filter, FilterOperator, ListRecordsRequest, PopulatedRecord, SeekPage } from '@activepieces/shared'; +import { z } from 'zod'; +import qs from 'qs'; +type FieldInfo = { + id: string; + type: FieldType; + name: string; +}; + +export const findRecords = createAction({ + name: 'tables-find-records', + displayName: 'Find Records', + description: 'Find records in a table with filters.', + auth: PieceAuth.None(), + props: { + table_id: tablesCommon.table_id, + limit: Property.Number({ + displayName: 'Limit', + description: 'Maximum number of records to return (default no limit).', + required: false, + }), + filters: Property.DynamicProperties({ + displayName: 'Filters', + description: 'Filter conditions to apply', + required: false, + refreshers: ['table_id'], + props: async (propsValue, context) => { + const table_id = propsValue['table_id']; + if (!table_id || typeof table_id !== 'string') { + return { + filters: Property.Array({ + displayName: 'Filters', + required: false, + properties: {}, + }), + }; + } + + const convertedTableId = await tablesCommon.convertTableExternalIdToId(table_id, context); + const fields = await tablesCommon.getTableFields({ + tableId: convertedTableId, + context, + }); + + return { + filters: Property.Array({ + displayName: 'Filters', + required: false, + properties: { + field: Property.StaticDropdown({ + displayName: 'Field', + required: true, + options: { + options: fields.map((field) => ({ + label: field.name, + value: { id: field.id, type: field.type, name: field.name } as FieldInfo, + })), + }, + }), + operator: Property.StaticDropdown({ + displayName: 'Operator', + required: true, + options: { + options: [ + { label: 'Equals', value: FilterOperator.EQ }, + { label: 'Not Equals', value: FilterOperator.NEQ }, + { label: 'Greater Than', value: FilterOperator.GT }, + { label: 'Greater Than or Equal', value: FilterOperator.GTE }, + { label: 'Less Than', value: FilterOperator.LT }, + { label: 'Less Than or Equal', value: FilterOperator.LTE }, + { label: 'Contains', value: FilterOperator.CO }, + ], + }, + }), + value: Property.ShortText({ + displayName: 'Value', + required: true, + }), + }, + }), + }; + }, + }), + }, + async run(context) { + const { table_id: tableExternalId, limit, filters } = context.propsValue; + const tableId = await tablesCommon.convertTableExternalIdToId(tableExternalId, context); + const filtersArray: { field: FieldInfo; operator: FilterOperator; value: unknown }[] = filters?.['filters'] ?? []; + + for (const filter of filtersArray) { + const value = filter.value; + const fieldType = filter.field.type; + + let schema: Record; + switch (fieldType) { + case FieldType.NUMBER: + schema = { + value: z.union([z.number(), z.string().transform(val => { + const num = Number(val); + if (isNaN(num)) throw new Error(`Invalid number for field "${filter.field.name}"`); + return num; + })]), + }; + break; + case FieldType.DATE: + schema = { + value: z.union([z.date(), z.string().transform(val => { + const date = new Date(val); + if (isNaN(date.getTime())) throw new Error(`Invalid date for field "${filter.field.name}"`); + return date; + })]), + }; + break; + default: + schema = { + value: z.string(), + }; + } + + await propsValidation.validateZod({ value }, schema); + } + + const parsedFilters: Filter[] = filtersArray.map((filter) => ({ + fieldId: filter.field.id, + operator: filter.operator, + value: filter.value as string, + })); + + const request: ListRecordsRequest = { + tableId, + limit: limit ?? 999999999, + cursor: undefined, + filters: parsedFilters, + }; + + + const response = await httpClient.sendRequest>({ + method: HttpMethod.GET, + url: `${context.server.apiUrl}v1/records?${qs.stringify(request)}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.server.token, + }, + }); + + return response.body.data.map(tablesCommon.formatRecord); + }, +}); diff --git a/packages/pieces/community/tables/src/lib/actions/get-record.ts b/packages/pieces/community/tables/src/lib/actions/get-record.ts new file mode 100644 index 0000000..cb3370c --- /dev/null +++ b/packages/pieces/community/tables/src/lib/actions/get-record.ts @@ -0,0 +1,29 @@ +import { createAction, PieceAuth, Property } from '@activepieces/pieces-framework'; +import { tablesCommon } from '../common'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { PopulatedRecord } from '@activepieces/shared'; + +export const getRecord = createAction({ + name: 'tables-get-record', + displayName: 'Get Record', + description: 'Get single record by its id.', + auth: PieceAuth.None(), + props: { + table_id: tablesCommon.table_id, + record_id: tablesCommon.record_id, + }, + async run(context) { + const { record_id } = context.propsValue; + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${context.server.apiUrl}v1/records/${record_id}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.server.token, + }, + }); + + return tablesCommon.formatRecord(response.body as PopulatedRecord); + }, +}); diff --git a/packages/pieces/community/tables/src/lib/actions/update-record.ts b/packages/pieces/community/tables/src/lib/actions/update-record.ts new file mode 100644 index 0000000..552fee4 --- /dev/null +++ b/packages/pieces/community/tables/src/lib/actions/update-record.ts @@ -0,0 +1,63 @@ +import { createAction, PieceAuth, Property } from '@activepieces/pieces-framework'; +import { tablesCommon } from '../common'; +import { AuthenticationType, httpClient, HttpMethod, propsValidation } from '@activepieces/pieces-common'; +import { PopulatedRecord, UpdateRecordRequest } from '@activepieces/shared'; + +export const updateRecord = createAction({ + name: 'tables-update-record', + displayName: 'Update Record', + description: 'Update values in an existing record', + auth: PieceAuth.None(), + props: { + table_id: tablesCommon.table_id, + record_id: tablesCommon.record_id, + values: Property.DynamicProperties({ + displayName: 'Values', + description: 'The values to update. Leave empty to keep current value.', + required: true, + refreshers: ['table_id', 'record_id'], + props: async ({ table_id, record_id }, context) => { + const tableExternalId = table_id as unknown as string; + const tableId = await tablesCommon.convertTableExternalIdToId(tableExternalId, context); + const recordId = record_id as unknown as string; + if ((tableId ?? '').toString().length === 0 || (recordId ?? '').toString().length === 0) { + return {}; + } + + return tablesCommon.createFieldProperties({ tableId, context }); + }, + }), + }, + async run(context) { + const { table_id: tableExternalId, record_id, values } = context.propsValue; + const tableId = await tablesCommon.convertTableExternalIdToId(tableExternalId, context); + + const tableFields = await tablesCommon.getTableFields({ tableId, context }); + const fieldValidations = tablesCommon.createFieldValidations(tableFields); + await propsValidation.validateZod(values, fieldValidations); + + const cells: UpdateRecordRequest['cells'] = Object.entries(values) + .filter(([_, value]) => value !== null && value !== undefined && value !== '') + .map(([fieldId, value]) => ({ + fieldId, + value, + })); + + const request: UpdateRecordRequest = { + cells, + tableId, + }; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.server.apiUrl}v1/records/${record_id}`, + body: request, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.server.token, + }, + }); + + return tablesCommon.formatRecord(response.body as PopulatedRecord); + }, +}); diff --git a/packages/pieces/community/tables/src/lib/common/index.ts b/packages/pieces/community/tables/src/lib/common/index.ts new file mode 100644 index 0000000..1e2b60b --- /dev/null +++ b/packages/pieces/community/tables/src/lib/common/index.ts @@ -0,0 +1,316 @@ +import { AuthenticationType, httpClient, HttpMethod } from "@activepieces/pieces-common"; +import { DynamicPropsValue, Property } from "@activepieces/pieces-framework"; +import { assertNotNullOrUndefined, CreateTableWebhookRequest, Field, FieldType, MarkdownVariant, PopulatedRecord, SeekPage, StaticDropdownEmptyOption, Table, TableWebhookEventType, ListTablesRequest } from "@activepieces/shared"; +import { z } from 'zod'; +import qs from 'qs'; + +type FormattedRecord = { + id: string; + created: string; + updated: string; + cells: Record; +} +const getFieldTypeText = (fieldType: FieldType) => { + switch (fieldType) { + case FieldType.STATIC_DROPDOWN: + return 'Single Select'; + case FieldType.DATE: + return 'Date'; + case FieldType.NUMBER: + return 'Number'; + case FieldType.TEXT: + return 'Text'; + } +} +export const tablesCommon = { + table_id: Property.Dropdown({ + displayName: 'Table Name', + required: true, + refreshers: [], + refreshOnSearch: true, + options: async (_propsValue, context) => { + try { + const tables = await fetchAllTables(context); + if (!Array.isArray(tables) || tables.length === 0) { + return { + options: [], + disabled: true, + placeholder: 'No tables found. Please create a table first.', + }; + } + return { + options: tables.map((table: Table) => ({ label: table.name, value: table.externalId })), + }; + } catch (e) { + console.error('Error fetching tables:', e); + return { + options: [], + disabled: true, + placeholder: 'Error loading tables. Please try again.', + }; + } + }, + }), + + record_id: Property.ShortText({ + displayName: 'Record ID', + description: 'The ID of the record to do the action on.', + required: true, + }), + + async getTableFields({ tableId, context }: { tableId: string, context: { server: { apiUrl: string, token: string } } }) { + const fieldsResponse = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${context.server.apiUrl}v1/fields`, + queryParams: { + tableId, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.server.token, + }, + }); + + return fieldsResponse.body as Field[]; + }, + + createFieldValidations(tableFields: Field[]) { + const fieldValidations: Record = {}; + tableFields.forEach(field => { + switch (field.type) { + case FieldType.NUMBER: + fieldValidations[field.id] = z.union([z.number(), z.string().transform(val => { + const num = Number(val); + if (isNaN(num)) throw new Error(`Invalid number for field "${field.name}"`); + return num; + })]).optional(); + break; + case FieldType.DATE: + fieldValidations[field.id] = z.union([z.date(), z.string().transform(val => { + const date = new Date(val); + if (isNaN(date.getTime())) throw new Error(`Invalid date for field "${field.name}"`); + return date; + })]).optional(); + break; + default: + fieldValidations[field.id] = z.string().optional(); + } + }); + return fieldValidations; + }, + + async createFieldProperties({ tableId, context }: { tableId: string, context: { server: { apiUrl: string, token: string } } }): Promise { + const fields: DynamicPropsValue = {}; + + try { + const tableFields = await this.getTableFields({ tableId, context }); + if (!Array.isArray(tableFields) || tableFields.length === 0) { + fields['markdown'] = Property.MarkDown({ + value: `We couldn't find any fields in the selected table. Please add fields to the table first.`, + variant: MarkdownVariant.INFO, + }); + return fields; + } + + for (const field of tableFields) { + const description = getFieldTypeText(field.type); + + switch (field.type) { + case FieldType.NUMBER: + fields[field.id] = Property.Number({ + displayName: field.name, + description, + required: false, + }); + break; + case FieldType.DATE: + fields[field.id] = Property.DateTime({ + displayName: field.name, + description, + required: false, + }); + break; + case FieldType.STATIC_DROPDOWN: + fields[field.id] = Property.StaticDropdown({ + displayName: field.name, + description, + defaultValue:'', + required: false, + options: { + options:[StaticDropdownEmptyOption,...field.data.options.map(option => ({ label: option.value, value: option.value }))], + }, + }); + break; + default: + fields[field.id] = Property.ShortText({ + displayName: field.name, + description, + required: false, + defaultValue: '', + }); + break; + } + } + + return fields; + } catch (e) { + console.error('Error fetching fields:', e); + fields['markdown'] = Property.MarkDown({ + value: `We couldn't find any fields in the selected table. Please add fields to the table first.`, + variant: MarkdownVariant.INFO, + }); + + return fields; + } + }, + + async createWebhook({ + tableId, + events, + webhookUrl, + flowId, + server, + }: { + tableId: string; + events: TableWebhookEventType[]; + webhookUrl: string; + flowId: string; + server: { apiUrl: string, token: string }; + }) { + const request: CreateTableWebhookRequest = { + events, + webhookUrl, + flowId, + } + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${server.apiUrl}v1/tables/${tableId}/webhooks`, + body: request, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: server.token, + }, + }); + + return response.body; + }, + + async deleteWebhook({ + tableId, + webhookId, + server, + }: { + tableId: string; + webhookId: string; + server: { apiUrl: string, token: string }; + }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: `${server.apiUrl}v1/tables/${tableId}/webhooks/${webhookId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: server.token, + }, + }); + + return response.body; + }, + + async getRecentRecords({ + tableId, + limit = 5, + context + }: { + tableId: string, + limit?: number, + context: { server: { apiUrl: string, token: string } } + }) { + if ((tableId ?? '').toString().length === 0) { + throw new Error(JSON.stringify({ + message: 'Please add some records to the table before testing this trigger' + })) + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${context.server.apiUrl}v1/records?tableId=${tableId}&limit=${limit}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.server.token, + }, + }); + + return response.body.data.map(this.formatRecord); + + }, + formatRecord(record: PopulatedRecord | { record: PopulatedRecord }): FormattedRecord { + const actualRecord = 'record' in record ? record.record : record; + + return { + id: actualRecord.id, + created: actualRecord.created, + updated: actualRecord.updated, + cells: actualRecord.cells ? Object.fromEntries(Object.entries(actualRecord.cells).map(([fieldId, cell]) => { + return [fieldId, { + fieldName: cell.fieldName, + updated: cell.updated, + created: cell.created, + value: cell.value + }] + })) : {}, + } + }, + + async convertTableExternalIdToId(tableId: string, context: { server: { apiUrl: string, token: string } }) { + const list: ListTablesRequest = { + externalIds: [tableId], + } + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${context.server.apiUrl}v1/tables?${qs.stringify(list)}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.server.token, + }, + }); + const table = (res.body as SeekPage).data[0]; + assertNotNullOrUndefined(table, `Table with externalId ${tableId} not found`); + return table.id; + } +} + +const fetchAllTables = async (context: { server: { apiUrl: string, token: string } }): Promise => { + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${context.server.apiUrl}v1/tables?limit=100`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.server.token, + }, + }); + const resultBody = res.body as SeekPage
+ const tables = [...resultBody.data]; + if (!Array.isArray(tables) || tables.length === 0) { + return []; + } + let next = resultBody.next; + while (next) { + const nextPage = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${context.server.apiUrl}v1/tables?cursor=${next}&limit=100`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.server.token, + }, + }); + const nextPageBody = nextPage.body as SeekPage
+ tables.push(...nextPageBody.data) + next = nextPageBody.next + } + return tables; +} \ No newline at end of file diff --git a/packages/pieces/community/tables/src/lib/triggers/deleted-record.ts b/packages/pieces/community/tables/src/lib/triggers/deleted-record.ts new file mode 100644 index 0000000..c91ac25 --- /dev/null +++ b/packages/pieces/community/tables/src/lib/triggers/deleted-record.ts @@ -0,0 +1,66 @@ +import { createTrigger, PieceAuth, TriggerStrategy } from '@activepieces/pieces-framework'; +import { tablesCommon } from '../common'; +import { PopulatedRecord, TableWebhookEventType } from '@activepieces/shared'; + +export const deletedRecordTrigger = createTrigger({ + name: 'deletedRecord', + displayName: 'Record Deleted', + description: 'Triggers when a record is deleted from the selected table.', + auth: PieceAuth.None(), + props: { + table_id: tablesCommon.table_id, + }, + sampleData: {}, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const tableExternalId = context.propsValue.table_id; + if ((tableExternalId ?? '').toString().length === 0) { + return; + } + const tableId = await tablesCommon.convertTableExternalIdToId(tableExternalId, context); + + const { id: webhookId } = await tablesCommon.createWebhook({ + tableId, + events: [TableWebhookEventType.RECORD_DELETED], + webhookUrl: context.webhookUrl, + flowId: context.flows.current.id, + server: { + apiUrl: context.server.apiUrl, + token: context.server.token, + }, + }); + + context.store.put('webhookId', webhookId); + }, + async onDisable(context) { + const tableExternalId = context.propsValue.table_id; + if ((tableExternalId ?? '').toString().length === 0) { + return; + } + const tableId = await tablesCommon.convertTableExternalIdToId(tableExternalId, context); + + const webhookId = await context.store.get('webhookId'); + if (!webhookId) { + return; + } + + await tablesCommon.deleteWebhook({ + tableId, + webhookId: webhookId, + server: { + apiUrl: context.server.apiUrl, + token: context.server.token, + }, + }); + }, + async run(context) { + return [tablesCommon.formatRecord(context.payload.body as PopulatedRecord)] + }, + async test(context) { + const tableId = await tablesCommon.convertTableExternalIdToId(context.propsValue.table_id, context); + return tablesCommon.getRecentRecords({ + tableId, + context + }); + } +}) diff --git a/packages/pieces/community/tables/src/lib/triggers/new-record.ts b/packages/pieces/community/tables/src/lib/triggers/new-record.ts new file mode 100644 index 0000000..8f404c5 --- /dev/null +++ b/packages/pieces/community/tables/src/lib/triggers/new-record.ts @@ -0,0 +1,65 @@ +import { createTrigger, PieceAuth, PropertyType, Property, TriggerStrategy } from '@activepieces/pieces-framework'; +import { tablesCommon } from '../common'; +import { PopulatedRecord, TableWebhookEventType } from '@activepieces/shared'; + +export const newRecordTrigger = createTrigger({ + name: 'newRecord', + displayName: 'New Record Created', + description: 'Triggers when a new record is added to the selected table.', + auth: PieceAuth.None(), + props: { + table_id: tablesCommon.table_id, + }, + sampleData: {}, + type: TriggerStrategy.WEBHOOK, + async onEnable(context){ + const tableExternalId = context.propsValue.table_id; + if ((tableExternalId ?? '').toString().length === 0) { + return; + } + const tableId = await tablesCommon.convertTableExternalIdToId(tableExternalId, context); + + const { id: webhookId } = await tablesCommon.createWebhook({ + tableId, + events: [TableWebhookEventType.RECORD_CREATED], + webhookUrl: context.webhookUrl, + flowId: context.flows.current.id, + server: { + apiUrl: context.server.apiUrl, + token: context.server.token, + }, + }); + + context.store.put('webhookId', webhookId); + }, + async onDisable(context){ + const tableExternalId = context.propsValue.table_id; + if ((tableExternalId ?? '').toString().length === 0) { + return; + } + const tableId = await tablesCommon.convertTableExternalIdToId(tableExternalId, context); + const webhookId = await context.store.get('webhookId'); + if (!webhookId) { + return; + } + + await tablesCommon.deleteWebhook({ + tableId, + webhookId: webhookId, + server: { + apiUrl: context.server.apiUrl, + token: context.server.token, + }, + }); + }, + async run(context){ + return [tablesCommon.formatRecord(context.payload.body as PopulatedRecord)] + }, + async test(context) { + const tableId = await tablesCommon.convertTableExternalIdToId(context.propsValue.table_id, context); + return tablesCommon.getRecentRecords({ + tableId, + context + }); + } +}) \ No newline at end of file diff --git a/packages/pieces/community/tables/src/lib/triggers/updated-record.ts b/packages/pieces/community/tables/src/lib/triggers/updated-record.ts new file mode 100644 index 0000000..94abf28 --- /dev/null +++ b/packages/pieces/community/tables/src/lib/triggers/updated-record.ts @@ -0,0 +1,67 @@ +import { createTrigger, PieceAuth, TriggerStrategy } from '@activepieces/pieces-framework'; +import { tablesCommon } from '../common'; +import { PopulatedRecord, TableWebhookEventType } from '@activepieces/shared'; + +export const updatedRecordTrigger = createTrigger({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'updatedRecord', + displayName: 'Record Updated', + description: 'Triggers when a record is updated in the selected table.', + auth: PieceAuth.None(), + props: { + table_id: tablesCommon.table_id, + }, + sampleData: {}, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const tableExternalId = context.propsValue.table_id; + if ((tableExternalId ?? '').toString().length === 0) { + return; + } + const tableId = await tablesCommon.convertTableExternalIdToId(tableExternalId, context); + + const { id: webhookId } = await tablesCommon.createWebhook({ + tableId, + events: [TableWebhookEventType.RECORD_UPDATED], + webhookUrl: context.webhookUrl, + flowId: context.flows.current.id, + server: { + apiUrl: context.server.apiUrl, + token: context.server.token, + }, + }); + + context.store.put('webhookId', webhookId); + }, + async onDisable(context) { + const tableExternalId = context.propsValue.table_id; + if ((tableExternalId ?? '').toString().length === 0) { + return; + } + const tableId = await tablesCommon.convertTableExternalIdToId(tableExternalId, context); + + const webhookId = await context.store.get('webhookId'); + if (!webhookId) { + return; + } + + await tablesCommon.deleteWebhook({ + tableId, + webhookId: webhookId, + server: { + apiUrl: context.server.apiUrl, + token: context.server.token, + }, + }); + }, + async run(context) { + return [tablesCommon.formatRecord(context.payload.body as PopulatedRecord)] + }, + async test(context) { + const tableId = await tablesCommon.convertTableExternalIdToId(context.propsValue.table_id, context); + return tablesCommon.getRecentRecords({ + tableId, + context + }); + } +}) \ No newline at end of file diff --git a/packages/pieces/community/tables/tsconfig.json b/packages/pieces/community/tables/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/tables/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/tables/tsconfig.lib.json b/packages/pieces/community/tables/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/tables/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/tags/.eslintrc.json b/packages/pieces/community/tags/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/tags/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/tags/README.md b/packages/pieces/community/tags/README.md new file mode 100644 index 0000000..b93889e --- /dev/null +++ b/packages/pieces/community/tags/README.md @@ -0,0 +1,7 @@ +# pieces-tags + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-tags` to build the library. diff --git a/packages/pieces/community/tags/package.json b/packages/pieces/community/tags/package.json new file mode 100644 index 0000000..867706f --- /dev/null +++ b/packages/pieces/community/tags/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-tags", + "version": "0.0.6" +} \ No newline at end of file diff --git a/packages/pieces/community/tags/project.json b/packages/pieces/community/tags/project.json new file mode 100644 index 0000000..485058f --- /dev/null +++ b/packages/pieces/community/tags/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-tags", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/tags/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/tags", + "tsConfig": "packages/pieces/community/tags/tsconfig.lib.json", + "packageJson": "packages/pieces/community/tags/package.json", + "main": "packages/pieces/community/tags/src/index.ts", + "assets": [ + "packages/pieces/community/tags/*.md", + { + "input": "packages/pieces/community/tags/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-tags {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/tags/src/index.ts b/packages/pieces/community/tags/src/index.ts new file mode 100644 index 0000000..1ec2dd0 --- /dev/null +++ b/packages/pieces/community/tags/src/index.ts @@ -0,0 +1,15 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { addTag } from './lib/add-tag'; + +export const tags = createPiece({ + displayName: 'Tags', + description: 'Add custom tags to your run for filtration', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/tags.svg', + categories: [PieceCategory.CORE], + authors: ["kishanprmr","MoShizzle","abuaboud"], + actions: [addTag], + triggers: [], +}); diff --git a/packages/pieces/community/tags/src/lib/add-tag.ts b/packages/pieces/community/tags/src/lib/add-tag.ts new file mode 100644 index 0000000..f7ae6bd --- /dev/null +++ b/packages/pieces/community/tags/src/lib/add-tag.ts @@ -0,0 +1,40 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; + +const markdown = ` +This action add a tag to the current execution, this tag can be used to filter the execution in the **API** only at this moment. +
+
+**Note:** If you are looking to use it in the user interface, please open a feature request. +`; + +export const addTag = createAction({ + name: 'add_tag', + displayName: 'Add Tag', + description: 'Add a tag to the current execution', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + info: Property.MarkDown({ + value: markdown, + }), + name: Property.ShortText({ + displayName: 'Tag Name', + description: undefined, + required: true, + }), + }, + async run(ctx) { + await ctx.tags.add({ + name: ctx.propsValue.name, + }); + return { + success: true, + }; + }, +}); diff --git a/packages/pieces/community/tags/tsconfig.json b/packages/pieces/community/tags/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/tags/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/tags/tsconfig.lib.json b/packages/pieces/community/tags/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/tags/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/talkable/.eslintrc.json b/packages/pieces/community/talkable/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/talkable/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/talkable/README.md b/packages/pieces/community/talkable/README.md new file mode 100644 index 0000000..7ff9605 --- /dev/null +++ b/packages/pieces/community/talkable/README.md @@ -0,0 +1,35 @@ +# Talkable piece + +====================== + +Talkable’s marketing technology empowers e-commerce brands to acquire & retain high-value customers through referral & loyalty marketing programs. As a single-source solution for your referral marketing and loyalty marketing needs, brands are able to create seamless user experiences designed to increase brand engagement and build brand affinity. + +## Website + +[https://www.talkable.com/](https://www.talkable.com/) + +## API documentation + +[https://docs.talkable.com/api_v2.html](https://docs.talkable.com/api_v2.html) + +## Actions: + +- Find coupon +- Get Loyalty actions +- Create Event +- Create batch of events +- Create Purchase +- Create batch of purchases +- Refund origin +- Find Person +- Update Person data +- Anonymize Person (GDPR) +- Unsubscribe Person + +--- + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-talkable` to build the library. diff --git a/packages/pieces/community/talkable/package.json b/packages/pieces/community/talkable/package.json new file mode 100644 index 0000000..5f8aba0 --- /dev/null +++ b/packages/pieces/community/talkable/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-talkable", + "version": "0.1.1" +} diff --git a/packages/pieces/community/talkable/project.json b/packages/pieces/community/talkable/project.json new file mode 100644 index 0000000..23d70f4 --- /dev/null +++ b/packages/pieces/community/talkable/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-talkable", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/talkable/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/talkable", + "tsConfig": "packages/pieces/community/talkable/tsconfig.lib.json", + "packageJson": "packages/pieces/community/talkable/package.json", + "main": "packages/pieces/community/talkable/src/index.ts", + "assets": [ + "packages/pieces/community/talkable/*.md", + { + "input": "packages/pieces/community/talkable/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-talkable {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/talkable/src/index.ts b/packages/pieces/community/talkable/src/index.ts new file mode 100644 index 0000000..f25104f --- /dev/null +++ b/packages/pieces/community/talkable/src/index.ts @@ -0,0 +1,82 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { + anonymizePerson, + createEvent, + createEventsBatch, + createPurchase, + createPurchasesBatch, + findCoupon, + findPerson, + getLoyaltyRedeemActions, + refund, + unsubscribePerson, + updatePerson, + updateReferralStatus, + claimOffer, +} from './lib/actions'; + +const markdownDescription = ` +Follow these steps: + +1. **Log in to your Talkable account:** Open Talkable https://www.talkable.com/login. + +2. **Enter the Talkable site slug and API key:** Go to **All site Settings** > **API Settings**, and copy Site ID and API key. + +`; + +export const talkableAuth = PieceAuth.CustomAuth({ + description: markdownDescription, + props: { + site: Property.ShortText({ + displayName: 'Talkable site ID', + required: true, + }), + api_key: Property.ShortText({ + displayName: 'API key', + required: true, + }), + }, + required: true, +}); + +export const talkable = createPiece({ + displayName: 'Talkable', + description: 'Referral marketing programs that drive revenue', + + auth: talkableAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: + 'https://www.talkable.com/wp-content/uploads/2021/12/talkable-favicon.svg', + authors: ["Vitalini","kishanprmr","MoShizzle","abuaboud"], + categories: [PieceCategory.MARKETING], + actions: [ + findPerson, + findCoupon, + updatePerson, + anonymizePerson, + unsubscribePerson, + createPurchase, + createPurchasesBatch, + createEvent, + createEventsBatch, + refund, + getLoyaltyRedeemActions, + updateReferralStatus, + claimOffer, + createCustomApiCallAction({ + baseUrl: () => 'https://www.talkable.com/api/v2', + auth: talkableAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as { api_key: string }).api_key}`, + }), + }), + ], + triggers: [], +}); + diff --git a/packages/pieces/community/talkable/src/lib/actions/coupons/find-coupon.ts b/packages/pieces/community/talkable/src/lib/actions/coupons/find-coupon.ts new file mode 100644 index 0000000..aee4df3 --- /dev/null +++ b/packages/pieces/community/talkable/src/lib/actions/coupons/find-coupon.ts @@ -0,0 +1,34 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { talkableAuth } from '../../..'; + +export const findCoupon = createAction({ + name: 'find_coupon', // Must be a unique across the piece, this shouldn't be changed. + auth: talkableAuth, + displayName: 'Find coupon', + description: 'Find coupon code', + props: { + code: Property.ShortText({ + displayName: 'Coupon code', + description: undefined, + required: true, + }), + }, + async run(context) { + const TALKABLE_API_URL = 'https://www.talkable.com/api/v2'; + const { site, api_key } = context.auth; + const couponInfoResponse = await httpClient + .sendRequest({ + method: HttpMethod.GET, + url: `${TALKABLE_API_URL}/coupons/${context.propsValue['code']}`, + headers: { + Authorization: `Bearer ${api_key}`, + 'Content-Type': 'application/json', + }, + body: { + site_slug: site, + }, + }); + return couponInfoResponse.body; + }, +}); diff --git a/packages/pieces/community/talkable/src/lib/actions/index.ts b/packages/pieces/community/talkable/src/lib/actions/index.ts new file mode 100644 index 0000000..b51e22a --- /dev/null +++ b/packages/pieces/community/talkable/src/lib/actions/index.ts @@ -0,0 +1,19 @@ +// People +export { findPerson } from './people/find-person'; +export { updatePerson } from './people/update-person'; +export { anonymizePerson } from './people/anonymize-person'; +export { unsubscribePerson } from './people/unsubscribe-person'; +// Origins +export { createPurchase } from './origins/create-purchase'; +export { createEvent } from './origins/create-event'; +export { createEventsBatch } from './origins/create-events-batch'; +export { createPurchasesBatch } from './origins/create-purchases-batch'; +export { refund } from './origins/refund'; +// Loyalty +export { getLoyaltyRedeemActions } from './loyalty/get-loyalty-redeem-actions'; +// Referral +export { updateReferralStatus } from './referrals/update-referral-status'; +// Coupons +export { findCoupon } from './coupons/find-coupon'; +// Rewards +export { claimOffer } from './rewards/claim-offer'; diff --git a/packages/pieces/community/talkable/src/lib/actions/loyalty/get-loyalty-redeem-actions.ts b/packages/pieces/community/talkable/src/lib/actions/loyalty/get-loyalty-redeem-actions.ts new file mode 100644 index 0000000..a5741fc --- /dev/null +++ b/packages/pieces/community/talkable/src/lib/actions/loyalty/get-loyalty-redeem-actions.ts @@ -0,0 +1,34 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { talkableAuth } from '../../..'; + +export const getLoyaltyRedeemActions = createAction({ + name: 'get_loyalty_redeem_actions', // Must be a unique across the piece, this shouldn't be changed. + auth: talkableAuth, + displayName: 'Get loyalty actions', + description: 'Get array of loyalty actions', + props: { + person_email: Property.ShortText({ + displayName: 'Person email', + description: undefined, + required: true, + }), + }, + async run(context) { + const TALKABLE_API_URL = 'https://www.talkable.com/api/v2'; + const { site, api_key } = context.auth; + const getLoyaltyRedeemActionsResponse = await httpClient + .sendRequest({ + method: HttpMethod.GET, + url: `${TALKABLE_API_URL}/loyalty/members/${context.propsValue['person_email']}/redeem_actions`, + headers: { + Authorization: `Bearer ${api_key}`, + 'Content-Type': 'application/json', + }, + body: { + site_slug: site, + }, + }); + return getLoyaltyRedeemActionsResponse.body; + }, +}); diff --git a/packages/pieces/community/talkable/src/lib/actions/origins/create-event.ts b/packages/pieces/community/talkable/src/lib/actions/origins/create-event.ts new file mode 100644 index 0000000..3c27940 --- /dev/null +++ b/packages/pieces/community/talkable/src/lib/actions/origins/create-event.ts @@ -0,0 +1,147 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { talkableAuth } from '../../..'; + +export const createEvent = createAction({ + name: 'create_event', // Must be a unique across the piece, this shouldn't be changed. + auth: talkableAuth, + displayName: 'Create event', + description: 'Create event in Talkable', + props: { + email: Property.ShortText({ + displayName: 'Email', + description: undefined, + required: true, + }), + event_category: Property.ShortText({ + displayName: 'Event category', + description: undefined, + required: true, + }), + event_number: Property.ShortText({ + displayName: 'Event number', + description: undefined, + required: true, + }), + subtotal: Property.Number({ + displayName: 'Subtotal', + description: undefined, + required: true, + }), + first_name: Property.ShortText({ + displayName: 'First name', + description: undefined, + required: false, + }), + last_name: Property.ShortText({ + displayName: 'Last name', + description: undefined, + required: false, + }), + username: Property.ShortText({ + displayName: 'Username', + description: undefined, + required: false, + }), + customer_id: Property.ShortText({ + displayName: 'Customer id', + description: undefined, + required: false, + }), + custom_properties: Property.Object({ + displayName: 'Custom properties', + description: undefined, + required: false, + }), + phone_number: Property.ShortText({ + displayName: 'Phone number', + description: undefined, + required: false, + }), + campaign_tags: Property.ShortText({ + displayName: 'Campaign tags', + description: undefined, + required: false, + }), + sharing_channels: Property.Array({ + displayName: 'Sharing channels', + description: undefined, + required: false, + }), + ip_address: Property.ShortText({ + displayName: 'IP address', + description: undefined, + required: false, + }), + uuid: Property.ShortText({ + displayName: 'UUID', + description: undefined, + required: false, + }), + created_at: Property.ShortText({ + displayName: 'Created at', + description: undefined, + required: false, + }), + traffic_source: Property.ShortText({ + displayName: 'Traffic source', + description: undefined, + required: false, + }), + coupon_codes: Property.Array({ + displayName: 'Coupon codes', + description: undefined, + required: false, + }), + currency_iso_code: Property.ShortText({ + displayName: 'Currency iso code', + description: 'Required for multi-currency sites', + required: false, + defaultValue: 'USD', + }), + custom_field: Property.ShortText({ + displayName: 'Custom field', + description: undefined, + required: false, + }), + shipping_address: Property.ShortText({ + displayName: 'Shipping address', + description: undefined, + required: false, + }), + shipping_zip: Property.ShortText({ + displayName: 'Shipping zip', + description: undefined, + required: false, + }), + items: Property.Json({ + displayName: 'Items', + description: "You can pass items with event", + required: false, + defaultValue: [ + { price: 10, quantity: 1, product_id: 'SKU1' }, + ], + }), + }, + async run(context) { + const TALKABLE_API_URL = 'https://www.talkable.com/api/v2'; + const { site, api_key } = context.auth; + const createEventResponse = await httpClient + .sendRequest({ + method: HttpMethod.POST, + url: `${TALKABLE_API_URL}/origins/event`, + headers: { + Authorization: `Bearer ${api_key}`, + 'Content-Type': 'application/json', + }, + body: { + site_slug: site, + data: context.propsValue, + }, + }); + return createEventResponse.body; + }, +}); diff --git a/packages/pieces/community/talkable/src/lib/actions/origins/create-events-batch.ts b/packages/pieces/community/talkable/src/lib/actions/origins/create-events-batch.ts new file mode 100644 index 0000000..1e41038 --- /dev/null +++ b/packages/pieces/community/talkable/src/lib/actions/origins/create-events-batch.ts @@ -0,0 +1,79 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { talkableAuth } from '../../..'; + +export const createEventsBatch = createAction({ + name: 'create_events_batch', // Must be a unique across the piece, this shouldn't be changed. + auth: talkableAuth, + displayName: 'Create batch of events', + description: 'Create batch of events in Talkable', + props: { + create_offers: Property.Checkbox({ + displayName: 'Create offers', + description: 'Create offers for campaign', + required: false, + defaultValue: false, + }), + events: Property.Json({ + displayName: 'Events', + description: undefined, + required: false, + defaultValue: [ + { + email: 'user@store.com', + event_category: 'newsletter_subscription', + event_number: '42', + subtotal: 100.44, + first_name: 'John', + last_name: 'Doe', + username: 'johndoe1992', + customer_id: '1024', + custom_properties: { + country: 'US', + eye_color: 'brown', + person_occupation: 'marketing', + }, + custom_field: '', + phone_number: '+12025551111', + campaign_tags: 'post-purchase', + sharing_channels: ['facebook', 'custom'], + ip_address: '192.0.2.255', + uuid: '123e4567-e89b-32d1-a456-426614174000', + created_at: '2023-04-27T11:30:42.769-07:00', + traffic_source: 'in-store', + coupon_codes: ['C0001', 'C0002'], + currency_iso_code: 'USD', + shipping_address: + '456 White Finch St., North Augusta, South Carolina, 29860, United States', + shipping_zip: '29860', + items: [ + { + price: 100.44, + quantity: 1, + product_id: 'SUBSCRIPTION', + }, + ], + }, + ], + }), + }, + async run(context) { + const TALKABLE_API_URL = 'https://www.talkable.com/api/v2'; + const { site, api_key } = context.auth; + const createEventsBatch = await httpClient + .sendRequest({ + method: HttpMethod.POST, + url: `${TALKABLE_API_URL}/origins/batch/events`, + headers: { + Authorization: `Bearer ${api_key}`, + 'Content-Type': 'application/json', + }, + body: { + site_slug: site, + data: context.propsValue.events, + create_offers: context.propsValue.create_offers, + }, + }); + return createEventsBatch.body; + }, +}); diff --git a/packages/pieces/community/talkable/src/lib/actions/origins/create-purchase.ts b/packages/pieces/community/talkable/src/lib/actions/origins/create-purchase.ts new file mode 100644 index 0000000..1c11394 --- /dev/null +++ b/packages/pieces/community/talkable/src/lib/actions/origins/create-purchase.ts @@ -0,0 +1,142 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { talkableAuth } from '../../..'; + +export const createPurchase = createAction({ + name: 'create_purchase', // Must be a unique across the piece, this shouldn't be changed. + auth: talkableAuth, + displayName: 'Create purchase', + description: 'Create purchase in Talkable', + props: { + email: Property.ShortText({ + displayName: 'Email', + description: undefined, + required: true, + }), + order_number: Property.ShortText({ + displayName: 'Order number', + description: undefined, + required: true, + }), + subtotal: Property.Number({ + displayName: 'Subtotal', + description: undefined, + required: true, + }), + first_name: Property.ShortText({ + displayName: 'First name', + description: undefined, + required: false, + }), + last_name: Property.ShortText({ + displayName: 'Last name', + description: undefined, + required: false, + }), + username: Property.ShortText({ + displayName: 'Username', + description: undefined, + required: false, + }), + customer_id: Property.ShortText({ + displayName: 'Customer id', + description: undefined, + required: false, + }), + custom_properties: Property.Object({ + displayName: 'Custom properties', + description: undefined, + required: false, + }), + phone_number: Property.ShortText({ + displayName: 'Phone number', + description: undefined, + required: false, + }), + campaign_tags: Property.ShortText({ + displayName: 'Campaign tags', + description: undefined, + required: false, + }), + sharing_channels: Property.Array({ + displayName: 'Sharing channels', + description: undefined, + required: false, + }), + ip_address: Property.ShortText({ + displayName: 'IP address', + description: undefined, + required: false, + }), + uuid: Property.ShortText({ + displayName: 'UUID', + description: undefined, + required: false, + }), + created_at: Property.ShortText({ + displayName: 'Created at', + description: undefined, + required: false, + }), + traffic_source: Property.ShortText({ + displayName: 'Traffic source', + description: undefined, + required: false, + }), + coupon_codes: Property.Array({ + displayName: 'Coupon codes', + description: undefined, + required: false, + }), + currency_iso_code: Property.ShortText({ + displayName: 'Currency iso code', + description: 'Required for multi-currency sites', + required: false, + defaultValue: 'USD', + }), + custom_field: Property.ShortText({ + displayName: 'Custom field', + description: undefined, + required: false, + }), + shipping_address: Property.ShortText({ + displayName: 'Shipping address', + description: undefined, + required: false, + }), + shipping_zip: Property.ShortText({ + displayName: 'Shipping zip', + description: undefined, + required: false, + }), + items: Property.Json({ + displayName: 'Items', + description: "You can pass items with purchase", + required: false, + defaultValue: [ + { price: 10, quantity: 1, product_id: 'SKU1' }, + ], + }), + }, + async run(context) { + const TALKABLE_API_URL = 'https://www.talkable.com/api/v2'; + const { site, api_key } = context.auth; + const createPurchaseResponse = await httpClient + .sendRequest({ + method: HttpMethod.POST, + url: `${TALKABLE_API_URL}/origins/purchase`, + headers: { + Authorization: `Bearer ${api_key}`, + 'Content-Type': 'application/json', + }, + body: { + site_slug: site, + data: context.propsValue, + }, + }); + return createPurchaseResponse.body; + }, +}); diff --git a/packages/pieces/community/talkable/src/lib/actions/origins/create-purchases-batch.ts b/packages/pieces/community/talkable/src/lib/actions/origins/create-purchases-batch.ts new file mode 100644 index 0000000..66e9e0f --- /dev/null +++ b/packages/pieces/community/talkable/src/lib/actions/origins/create-purchases-batch.ts @@ -0,0 +1,78 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { talkableAuth } from '../../..'; + +export const createPurchasesBatch = createAction({ + name: 'create_purchases_batch', // Must be a unique across the piece, this shouldn't be changed. + auth: talkableAuth, + displayName: 'Create batch of purchases', + description: 'Create batch of purchases in Talkable', + props: { + create_offers: Property.Checkbox({ + displayName: 'Create offers', + description: 'Create offers for campaign', + required: false, + defaultValue: false, + }), + purchases: Property.Json({ + displayName: 'Purchases', + description: undefined, + required: false, + defaultValue: [ + { + email: 'customer@store.com', + order_number: '20', + subtotal: 100.44, + first_name: 'John', + last_name: 'Doe', + username: 'johndoe1992', + customer_id: '1024', + custom_properties: { + country: 'US', + eye_color: 'brown', + person_occupation: 'marketing', + }, + custom_field: '', + phone_number: '+12025551111', + campaign_tags: 'post-purchase', + sharing_channels: ['facebook', 'custom'], + ip_address: '192.0.2.255', + uuid: '123e4567-e89b-32d1-a456-426614174000', + created_at: '2023-04-27T11:30:42.769-07:00', + traffic_source: 'in-store', + coupon_codes: ['C0001', 'C0002'], + currency_iso_code: 'USD', + shipping_address: + '456 White Finch St., North Augusta, South Carolina, 29860, United States', + shipping_zip: '29860', + items: [ + { + price: 25.11, + quantity: 4, + product_id: 'TSHIRT', + }, + ], + }, + ], + }), + }, + async run(context) { + const TALKABLE_API_URL = 'https://www.talkable.com/api/v2'; + const { site, api_key } = context.auth; + const createPurchasesBatch = await httpClient + .sendRequest({ + method: HttpMethod.POST, + url: `${TALKABLE_API_URL}/origins/batch/purchases`, + headers: { + Authorization: `Bearer ${api_key}`, + 'Content-Type': 'application/json', + }, + body: { + site_slug: site, + data: context.propsValue.purchases, + create_offers: context.propsValue.create_offers, + }, + }); + return createPurchasesBatch.body; + }, +}); diff --git a/packages/pieces/community/talkable/src/lib/actions/origins/refund.ts b/packages/pieces/community/talkable/src/lib/actions/origins/refund.ts new file mode 100644 index 0000000..e5173b9 --- /dev/null +++ b/packages/pieces/community/talkable/src/lib/actions/origins/refund.ts @@ -0,0 +1,49 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { talkableAuth } from '../../..'; + +export const refund = createAction({ + name: 'refund', // Must be a unique across the piece, this shouldn't be changed. + auth: talkableAuth, + displayName: 'Refund purchase/event', + description: 'Mark origin as refund', + props: { + origin_slug: Property.ShortText({ + displayName: 'Order or event number', + description: undefined, + required: true, + }), + refund_subtotal: Property.Number({ + displayName: 'Refund subtotal', + description: undefined, + required: false, + }), + refunded_at: Property.DateTime({ + displayName: 'Refunded date', + description: undefined, + required: false, + }), + }, + async run(context) { + const TALKABLE_API_URL = 'https://www.talkable.com/api/v2'; + const { site, api_key } = context.auth; + const { origin_slug, refund_subtotal, refunded_at } = context.propsValue; + const refundResponse = await httpClient + .sendRequest({ + method: HttpMethod.POST, + url: `${TALKABLE_API_URL}/origins/${origin_slug}/refund`, + headers: { + Authorization: `Bearer ${api_key}`, + 'Content-Type': 'application/json', + }, + body: { + site_slug: site, + data: { + refunded_at, + refund_subtotal, + }, + }, + }); + return refundResponse.body; + }, +}); diff --git a/packages/pieces/community/talkable/src/lib/actions/people/anonymize-person.ts b/packages/pieces/community/talkable/src/lib/actions/people/anonymize-person.ts new file mode 100644 index 0000000..f5866fa --- /dev/null +++ b/packages/pieces/community/talkable/src/lib/actions/people/anonymize-person.ts @@ -0,0 +1,34 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { talkableAuth } from '../../..'; + +export const anonymizePerson = createAction({ + name: 'anonymize_person', // Must be a unique across the piece, this shouldn't be changed. + auth: talkableAuth, + displayName: 'Anonymize person', + description: 'Anonymize person by email', + props: { + email: Property.ShortText({ + displayName: 'Person email', + description: undefined, + required: true, + }), + }, + async run(context) { + const TALKABLE_API_URL = 'https://www.talkable.com/api/v2'; + const { site, api_key } = context.auth; + const personAnonymizeResponse = await httpClient + .sendRequest({ + method: HttpMethod.POST, + url: `${TALKABLE_API_URL}/people/${context.propsValue['email']}/anonymize`, + headers: { + Authorization: `Bearer ${api_key}`, + 'Content-Type': 'application/json', + }, + body: { + site_slug: site, + }, + }); + return personAnonymizeResponse.body; + }, +}); diff --git a/packages/pieces/community/talkable/src/lib/actions/people/find-person.ts b/packages/pieces/community/talkable/src/lib/actions/people/find-person.ts new file mode 100644 index 0000000..6a43a0a --- /dev/null +++ b/packages/pieces/community/talkable/src/lib/actions/people/find-person.ts @@ -0,0 +1,67 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { talkableAuth } from '../../..'; + +export const findPerson = createAction({ + name: 'find_person', // Must be a unique across the piece, this shouldn't be changed. + auth: talkableAuth, + displayName: 'Find person', + description: 'Find person by email', + props: { + email: Property.ShortText({ + displayName: 'Person email', + description: undefined, + required: true, + }), + scope: Property.StaticDropdown({ + displayName: 'Scope', + description: 'Select scope', + required: true, + options: { + options: [ + { + label: 'General information', + value: '/', + }, + { + label: 'Referrals as advocate', + value: '/referrals_as_advocate', + }, + { + label: 'Referrals as friend', + value: '/referrals_as_friend', + }, + { + label: 'Rewards', + value: '/rewards', + }, + { + label: 'Shares', + value: '/shares_by', + }, + { + label: 'Personal data', + value: '/personal_data', + }, + ], + }, + }), + }, + async run(context) { + const TALKABLE_API_URL = 'https://www.talkable.com/api/v2'; + const { site, api_key } = context.auth; + const personInfoResponse = await httpClient + .sendRequest({ + method: HttpMethod.GET, + url: `${TALKABLE_API_URL}/people/${context.propsValue['email']}${context.propsValue['scope']}`, + headers: { + Authorization: `Bearer ${api_key}`, + 'Content-Type': 'application/json', + }, + body: { + site_slug: site, + }, + }); + return personInfoResponse.body; + }, +}); diff --git a/packages/pieces/community/talkable/src/lib/actions/people/unsubscribe-person.ts b/packages/pieces/community/talkable/src/lib/actions/people/unsubscribe-person.ts new file mode 100644 index 0000000..da21b02 --- /dev/null +++ b/packages/pieces/community/talkable/src/lib/actions/people/unsubscribe-person.ts @@ -0,0 +1,34 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { talkableAuth } from '../../..'; + +export const unsubscribePerson = createAction({ + name: 'unsubscribe_person', // Must be a unique across the piece, this shouldn't be changed. + auth: talkableAuth, + displayName: 'Unsubscribe person', + description: 'Unsubscribe person by email', + props: { + email: Property.ShortText({ + displayName: 'Person email', + description: undefined, + required: true, + }), + }, + async run(context) { + const TALKABLE_API_URL = 'https://www.talkable.com/api/v2'; + const { site, api_key } = context.auth; + const personUnsubscribeResponse = await httpClient + .sendRequest({ + method: HttpMethod.POST, + url: `${TALKABLE_API_URL}/people/${context.propsValue['email']}/unsubscribe`, + headers: { + Authorization: `Bearer ${api_key}`, + 'Content-Type': 'application/json', + }, + body: { + site_slug: site, + }, + }); + return personUnsubscribeResponse.body; + }, +}); diff --git a/packages/pieces/community/talkable/src/lib/actions/people/update-person.ts b/packages/pieces/community/talkable/src/lib/actions/people/update-person.ts new file mode 100644 index 0000000..ebb022a --- /dev/null +++ b/packages/pieces/community/talkable/src/lib/actions/people/update-person.ts @@ -0,0 +1,126 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { talkableAuth } from '../../..'; + +export const updatePerson = createAction({ + name: 'update_person', // Must be a unique across the piece, this shouldn't be changed. + auth: talkableAuth, + displayName: 'Update person', + description: 'Update person by email', + props: { + email: Property.ShortText({ + displayName: 'Person email', + description: undefined, + required: true, + }), + first_name: Property.ShortText({ + displayName: 'Person first name', + description: undefined, + required: false, + }), + last_name: Property.ShortText({ + displayName: 'Person last name', + description: undefined, + required: false, + }), + phone_number: Property.ShortText({ + displayName: 'Person phone number', + description: undefined, + required: false, + }), + username: Property.ShortText({ + displayName: 'Person username', + description: undefined, + required: false, + }), + customer_id: Property.Number({ + displayName: 'Customer ID', + description: undefined, + required: false, + }), + custom_properties: Property.Object({ + displayName: 'Custom properties', + description: undefined, + required: false, + }), + gated_param_subscribed: Property.StaticDropdown({ + displayName: 'Opt-in status', + description: 'Opt-in status true/false', + required: false, + options: { + options: [ + { + label: 'true', + value: 'true', + }, + { + label: 'false', + value: 'false', + }, + ], + }, + }), + unsubscribed: Property.StaticDropdown({ + displayName: 'Unsubscribe status', + description: 'Unsubscribe status true/false', + required: false, + options: { + options: [ + { + label: 'true', + value: 'true', + }, + { + label: 'false', + value: 'false', + }, + ], + }, + }), + unsubscribed_at: Property.DateTime({ + displayName: 'Unsubscribed date', + description: undefined, + required: false, + }), + }, + async run(context) { + const TALKABLE_API_URL = 'https://www.talkable.com/api/v2'; + const { site, api_key } = context.auth; + const { + email, + first_name, + last_name, + phone_number, + username, + customer_id, + custom_properties, + gated_param_subscribed, + unsubscribed, + unsubscribed_at, + } = context.propsValue; + const personUpdateResponse = await httpClient + .sendRequest({ + method: HttpMethod.PUT, + url: `${TALKABLE_API_URL}/people/${email}`, + headers: { + Authorization: `Bearer ${api_key}`, + 'Content-Type': 'application/json', + }, + body: { + site_slug: site, + data: { + first_name, + last_name, + phone_number, + username, + customer_id, + custom_properties, + gated_param_subscribed, + unsubscribed, + unsubscribed_at, + }, + }, + }); + return personUpdateResponse.body; + }, +}); diff --git a/packages/pieces/community/talkable/src/lib/actions/referrals/update-referral-status.ts b/packages/pieces/community/talkable/src/lib/actions/referrals/update-referral-status.ts new file mode 100644 index 0000000..acd621c --- /dev/null +++ b/packages/pieces/community/talkable/src/lib/actions/referrals/update-referral-status.ts @@ -0,0 +1,47 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { talkableAuth } from '../../..'; + +export const updateReferralStatus = createAction({ + name: 'update-referral-status', // Must be a unique across the piece, this shouldn't be changed. + auth: talkableAuth, + displayName: 'Update referral status', + description: 'You can void or approve referral', + props: { + origin_slug: Property.ShortText({ + displayName: 'Order or event number', + description: undefined, + required: true, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + description: + 'Select referral status. Only "approved" or "voided" are accepted', + required: true, + options: { + options: [ + { label: 'approved', value: 'approved' }, + { label: 'voided', value: 'voided' }, + ], + }, + }), + }, + async run(context) { + const TALKABLE_API_URL = 'https://www.talkable.com/api/v2'; + const { site, api_key } = context.auth; + const updateReferralStatusResponse = await httpClient + .sendRequest({ + method: HttpMethod.POST, + url: `${TALKABLE_API_URL}/origins/${context.propsValue['origin_slug']}/referral`, + headers: { + Authorization: `Bearer ${api_key}`, + 'Content-Type': 'application/json', + }, + body: { + site_slug: site, + status: context.propsValue['status'], // we have only one status so it's hardcoded + }, + }); + return updateReferralStatusResponse.body; + }, +}); diff --git a/packages/pieces/community/talkable/src/lib/actions/rewards/claim-offer.ts b/packages/pieces/community/talkable/src/lib/actions/rewards/claim-offer.ts new file mode 100644 index 0000000..6862719 --- /dev/null +++ b/packages/pieces/community/talkable/src/lib/actions/rewards/claim-offer.ts @@ -0,0 +1,47 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { talkableAuth } from '../../..'; + +export const claimOffer = createAction({ + name: 'claim-offer', // Must be a unique across the piece, this shouldn't be changed. + auth: talkableAuth, + displayName: 'Share and claim offer', + description: "Using this action, you can share and get a friend's reward", + props: { + advocate_email: Property.ShortText({ + displayName: 'Advocate email', + description: "Example : advocate@example.com", + required: true, + }), + friend_email: Property.ShortText({ + displayName: 'Friend email', + description: "Example : friend@example.com", + required: true, + }), + campaign_tag: Property.ShortText({ + displayName: 'Campaign tag', + description: "Example : invite", + required: true, + }), + }, + async run(context) { + const TALKABLE_API_URL = 'https://www.talkable.com/api/v2'; + const { site, api_key } = context.auth; + const claimOffer = await httpClient + .sendRequest({ + method: HttpMethod.POST, + url: `${TALKABLE_API_URL}/offer_claims`, + headers: { + Authorization: `Bearer ${api_key}`, + 'Content-Type': 'application/json', + }, + body: { + site_slug: site, + advocate_email: context.propsValue['advocate_email'], + friend_email: context.propsValue['friend_email'], + campaign_tag: context.propsValue['campaign_tag'], + }, + }); + return claimOffer.body; + }, +}); diff --git a/packages/pieces/community/talkable/tsconfig.json b/packages/pieces/community/talkable/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/talkable/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/talkable/tsconfig.lib.json b/packages/pieces/community/talkable/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/talkable/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/tally/.eslintrc.json b/packages/pieces/community/tally/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/tally/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/tally/README.md b/packages/pieces/community/tally/README.md new file mode 100644 index 0000000..ac9f0a8 --- /dev/null +++ b/packages/pieces/community/tally/README.md @@ -0,0 +1,7 @@ +# pieces-tally + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-tally` to build the library. diff --git a/packages/pieces/community/tally/package.json b/packages/pieces/community/tally/package.json new file mode 100644 index 0000000..bb5f3ef --- /dev/null +++ b/packages/pieces/community/tally/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-tally", + "version": "0.1.3" +} \ No newline at end of file diff --git a/packages/pieces/community/tally/project.json b/packages/pieces/community/tally/project.json new file mode 100644 index 0000000..df901c0 --- /dev/null +++ b/packages/pieces/community/tally/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-tally", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/tally/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/tally", + "tsConfig": "packages/pieces/community/tally/tsconfig.lib.json", + "packageJson": "packages/pieces/community/tally/package.json", + "main": "packages/pieces/community/tally/src/index.ts", + "assets": [ + "packages/pieces/community/tally/*.md", + { + "input": "packages/pieces/community/tally/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-tally {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/tally/src/index.ts b/packages/pieces/community/tally/src/index.ts new file mode 100644 index 0000000..18d1ce9 --- /dev/null +++ b/packages/pieces/community/tally/src/index.ts @@ -0,0 +1,14 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { tallyFormsNewSubmission } from './lib/triggers/new-submission'; +export const tally = createPiece({ + displayName: 'Tally', + description: 'Receive form submissions from Tally forms', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.27.1', + logoUrl: 'https://cdn.activepieces.com/pieces/tally.png', + categories: [PieceCategory.FORMS_AND_SURVEYS], + authors: ["kishanprmr","abuaboud"], + actions: [], + triggers: [tallyFormsNewSubmission], +}); diff --git a/packages/pieces/community/tally/src/lib/triggers/new-submission.ts b/packages/pieces/community/tally/src/lib/triggers/new-submission.ts new file mode 100644 index 0000000..05a7aaf --- /dev/null +++ b/packages/pieces/community/tally/src/lib/triggers/new-submission.ts @@ -0,0 +1,45 @@ +import { + createTrigger, + PieceAuth, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; + +const markdown = ` +To set up the trigger for new form submissions, follow these steps: + +1. Go to the "Dashboard" section. +2. Select the form where you want the trigger to occur. +3. Click on the "Integrations" section. +4. Find the "Webhooks" integration and click on "Connect" to activate it. +5. In the webhook settings, paste the following URL: + \`\`\`text + {{webhookUrl}} + \`\`\` + + +6. Click on "Submit". +`; + +export const tallyFormsNewSubmission = createTrigger({ + name: 'new-submission', + displayName: 'New Submission', + auth: PieceAuth.None(), + description: 'Triggers when form receives a new submission', + props: { + md: Property.MarkDown({ + value: markdown, + }), + }, + type: TriggerStrategy.WEBHOOK, + sampleData: undefined, + async onEnable(context) { + // Empty + }, + async onDisable(context) { + // Empty + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/tally/tsconfig.json b/packages/pieces/community/tally/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/tally/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/tally/tsconfig.lib.json b/packages/pieces/community/tally/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/tally/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/tarvent/.eslintrc.json b/packages/pieces/community/tarvent/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/tarvent/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/tarvent/README.md b/packages/pieces/community/tarvent/README.md new file mode 100644 index 0000000..8288313 --- /dev/null +++ b/packages/pieces/community/tarvent/README.md @@ -0,0 +1,7 @@ +# pieces-tarvent + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-tarvent` to build the library. diff --git a/packages/pieces/community/tarvent/package.json b/packages/pieces/community/tarvent/package.json new file mode 100644 index 0000000..4e97893 --- /dev/null +++ b/packages/pieces/community/tarvent/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-tarvent", + "version": "0.0.4" +} diff --git a/packages/pieces/community/tarvent/project.json b/packages/pieces/community/tarvent/project.json new file mode 100644 index 0000000..c131a10 --- /dev/null +++ b/packages/pieces/community/tarvent/project.json @@ -0,0 +1,45 @@ +{ + "name": "pieces-tarvent", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/tarvent/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/tarvent", + "tsConfig": "packages/pieces/community/tarvent/tsconfig.lib.json", + "packageJson": "packages/pieces/community/tarvent/package.json", + "main": "packages/pieces/community/tarvent/src/index.ts", + "assets": [ + "packages/pieces/community/tarvent/*.md" + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/tarvent/src/index.ts b/packages/pieces/community/tarvent/src/index.ts new file mode 100644 index 0000000..8538f42 --- /dev/null +++ b/packages/pieces/community/tarvent/src/index.ts @@ -0,0 +1,54 @@ + +import { createPiece, PieceAuth, Property } from "@activepieces/pieces-framework"; +import { PieceCategory } from "@activepieces/shared"; +import { makeClient } from "./lib/common"; +import { campaignSendFinishedTrigger, contactAddedTrigger, contactBouncedTrigger, contactClickedTrigger, contactGroupUpdatedTrigger, contactNoteAddedTrigger, contactOpenedTrigger, contactRepliedTrigger, contactStatusUpdatedTrigger, contactTagUpdatedTrigger, contactUnsubscribedTrigger, contactUpdatedTrigger, formSubmittedTrigger, pagePerformedTrigger, surveySubmittedTrigger, transactionCreatedTrigger, transactionSentTrigger } from "./lib/triggers"; +import { updateContactTags, sendCampaign, createAudienceGroup, updateContactGroup, createContactNote, updateContactJourney, updateContactStatus, generateCustomEvent, updateJourneyStatus, createSuppressionFilter, getAudiences, getAudienceGroups, getCampaigns, getContact, getCustomEvent, getJourney, createContact, createTransaction } from "./lib/actions"; + +const authGuide = ` +To obtain your Tarvent Account ID and API Key, follow these steps: + +1. Log in to your Tarvent account. +2. Go to **Account->API Keys** section. +3. **Create an API key** and copy it. Make sure to give it the correct permissions. +4. The **Account ID** is available to copy at the top right +`; + +export const tarventAuth = PieceAuth.CustomAuth({ + required: true, + description: authGuide, + props: { + accountId: Property.ShortText({ + displayName: 'Account ID', + required: true, + }), + apiKey: Property.ShortText({ + displayName: 'API Key', + required: true, + }), + }, + validate: async ({ auth }) => { + try { + const client = makeClient(auth); + await client.authenticate(); + return { valid: true }; + } catch (error) { + return { + valid: false, + error: 'Invalid API credentials', + }; + } + }, +}); + +export const tarvent = createPiece({ + displayName: "Tarvent", + description: "Tarvent is an email marketing, automation, and email API platform that allows to you to send campaigns, manage contacts, automate your marketing, and more.", + auth: tarventAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/tarvent.png', + categories: [PieceCategory.MARKETING, PieceCategory.FORMS_AND_SURVEYS], + authors: ["derekjdev","206mph"], + actions: [createContact, updateContactTags, updateContactGroup, createContactNote, updateContactJourney, updateContactStatus, createAudienceGroup, updateJourneyStatus, createTransaction, sendCampaign, generateCustomEvent, getAudiences, getAudienceGroups, createSuppressionFilter, getCampaigns, getContact, getCustomEvent, getJourney], + triggers: [contactAddedTrigger, contactGroupUpdatedTrigger, contactUpdatedTrigger, contactStatusUpdatedTrigger, contactTagUpdatedTrigger, contactNoteAddedTrigger, contactUnsubscribedTrigger, formSubmittedTrigger, pagePerformedTrigger, surveySubmittedTrigger, contactClickedTrigger, contactOpenedTrigger, contactRepliedTrigger, contactBouncedTrigger,campaignSendFinishedTrigger, transactionCreatedTrigger, transactionSentTrigger], +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/create-audience-group.ts b/packages/pieces/community/tarvent/src/lib/actions/create-audience-group.ts new file mode 100644 index 0000000..318a2e5 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/create-audience-group.ts @@ -0,0 +1,46 @@ +import { tarventAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { makeClient, tarventCommon } from '../common'; +import { propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const createAudienceGroup = createAction({ + auth: tarventAuth, + name: 'tarvent_create_audience_group', + displayName: 'Create An Audience Group', + description: 'Creates an audience group in the selected audience.', + props: { + audienceId: tarventCommon.audienceId(true, 'Audience to create the group in.'), + name: tarventCommon.name('Group name', true, 'Enter the group name. (100 character limit)'), + description: tarventCommon.name('Group description', false, 'Use the description to describe what the group is for. NOTE: If the group is public this description will show up in forms that have the groups question.'), + isPublic: Property.StaticDropdown({ + displayName: 'Public group', + description: 'Select whether the group is public or not. Public groups are shown in forms with the groups question.', + required: true, + options: { + options: [ + { + label: 'True', + + value: 'true', + }, + { + label: 'False', + value: 'false', + }, + ], + }, + }), + }, + async run(context) { + const { audienceId, name, description, isPublic } = context.propsValue; + + await propsValidation.validateZod(context.propsValue, { + name: z.string().min(1).max(100, 'Name has to be less than 100 characters.'), + description: z.string().min(1).max(255, 'Description has to be less than 255 characters.'), + }); + + const client = makeClient(context.auth); + return await client.createAudienceGroup(audienceId, name, description, isPublic); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/create-contact-note.ts b/packages/pieces/community/tarvent/src/lib/actions/create-contact-note.ts new file mode 100644 index 0000000..92e8f87 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/create-contact-note.ts @@ -0,0 +1,30 @@ +import { tarventAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { makeClient, tarventCommon } from '../common'; +import { propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const createContactNote = createAction({ + auth: tarventAuth, + name: 'tarvent_create_contact_note', + displayName: 'Add Note To Contact', + description: 'Adds a note to a contact.', + props: { + contactId: tarventCommon.contactId, + note: Property.LongText({ + displayName: 'Note', + description: 'Enter the note you would like to add to the contact.', + required: true + }) + }, + async run(context) { + const { contactId, note } = context.propsValue; + + await propsValidation.validateZod(context.propsValue, { + note: z.string().min(1).max(255, 'Description has to be less than 256 characters.') + }); + + const client = makeClient(context.auth); + return await client.createContactNote(contactId, note); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/create-contact.ts b/packages/pieces/community/tarvent/src/lib/actions/create-contact.ts new file mode 100644 index 0000000..fb118b3 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/create-contact.ts @@ -0,0 +1,146 @@ +import { tarventAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { makeClient, tarventCommon } from '../common'; +import { propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const createContact = createAction({ + auth: tarventAuth, + name: 'tarvent_create_contact', + displayName: 'Create/Update Contact', + description: 'This action is used to create or update a contact in an audience.', + props: { + audienceId: tarventCommon.audienceId(true, ''), + email: Property.ShortText({ + displayName: 'Email', + description: 'Enter the contacts email. NOTE: If the audience uses a custom contact identifier and overwrite is enabled, then this will update the FIRST contact that matches the email.', + required: true, + defaultValue: '' + }), + updateAction: Property.StaticDropdown({ + displayName: 'Update existing contact', + description: 'Update the contact\'s profile if it already exists. Otherwise, return the "Duplicate" error.', + required: true, + options: { + options: [ + { + label: 'Update', + + value: 'Update', + }, + { + label: 'Return duplicate error', + value: 'ReturnError', + }, + ], + }, + }), + groupAction: Property.StaticDropdown({ + displayName: 'Replace existing groups', + description: 'Select whether to replace or only add to contact groups. NOTE: Add only will only add the contact to the groups they are not already in. Replace will remove the contact from all existing groups and add them to the selected groups.', + required: false, + options: { + options: [ + { + label: 'Replace', + + value: 'Replace', + }, + { + label: 'Add only', + value: 'Add', + }, + ], + }, + }), + groupIds: tarventCommon.audienceGroupIds(false, ''), + tagAction: Property.StaticDropdown({ + displayName: 'Replace existing tags', + description: 'Select whether to replace or only add to contact tags of an existing contact. NOTE: Add only will only add the tags the contact does not already have. Replace will replace all contact tags with the entered tags.', + required: false, + options: { + options: [ + { + label: 'Replace', + + value: 'Replace', + }, + { + label: 'Add only', + value: 'Add', + }, + ], + }, + }), + tagIds: tarventCommon.tagIds(false, `Select which tags you would like to add to the contact.`), + firstName: Property.ShortText({ + displayName: 'First name', + description: 'The contacts first name.', + required: false, + defaultValue: '' + }), + lastName: Property.ShortText({ + displayName: 'Last name', + description: 'The contacts last name.', + required: false, + defaultValue: '' + }), + streetAddress: Property.ShortText({ + displayName: 'Street address', + description: '', + required: false, + defaultValue: '' + }), + streetAddress2: Property.ShortText({ + displayName: 'Street address 2', + description: '', + required: false, + defaultValue: '' + }), + addressLocality: Property.ShortText({ + displayName: 'City (Locality)', + description: '', + required: false, + defaultValue: '' + }), + addressRegion: Property.ShortText({ + displayName: 'State (Region)', + description: '', + required: false, + defaultValue: '' + }), + postalCode: Property.ShortText({ + displayName: 'Zip code (Postal code)', + description: '', + required: false, + defaultValue: '' + }), + addressCountry: Property.ShortText({ + displayName: 'Country', + description: '', + required: false, + defaultValue: '' + }), + audienceDataFields: tarventCommon.audienceDataFields + }, + async run(context) { + const { audienceId, email, updateAction, groupAction, groupIds, tagAction, tagIds, firstName, lastName, + streetAddress, streetAddress2, addressLocality, addressRegion, postalCode, addressCountry, audienceDataFields } = context.propsValue; + + await propsValidation.validateZod(context.propsValue, { + email: z.string().max(100, 'Email has to be equal to or less than 100 characters.'), + firstName: z.string().max(100, 'First name has to be equal to or less than 100 characters.').optional(), + lastName: z.string().max(100, 'Last name has to be equal to or less than 100 characters.').optional(), + streetAddress: z.string().max(100, 'Street address has to be equal to or less than 100 characters.').optional(), + streetAddress2: z.string().max(100, 'Street address 2 has to be equal to or less than 100 characters.').optional(), + addressLocality: z.string().max(100, 'City (Locality) has to be equal to or less than 100 characters.').optional(), + addressRegion: z.string().max(100, 'State (Region) has to be equal to or less than 100 characters.').optional(), + postalCode: z.string().max(15, 'Zip code (Postal code) has to be equal to or less than 15 characters.').optional(), + addressCountry: z.string().max(100, 'Country has to be equal to or less than 100 characters.').optional(), + }); + + const client = makeClient(context.auth); + return await client.createContact(audienceId, email, updateAction, groupAction, tagAction, tagIds, groupIds, firstName, lastName, streetAddress, streetAddress2, addressLocality, addressRegion, postalCode, addressCountry, + audienceDataFields); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/create-suppression-filter.ts b/packages/pieces/community/tarvent/src/lib/actions/create-suppression-filter.ts new file mode 100644 index 0000000..13b9709 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/create-suppression-filter.ts @@ -0,0 +1,37 @@ +import { propsValidation } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { z } from 'zod'; +import { tarventAuth } from '../..'; +import { makeClient } from '../common'; + +export const createSuppressionFilter = createAction({ + auth: tarventAuth, + name: 'tarvent_create_suppression_filter', + displayName: 'Add Contact To Suppression List', + description: 'Creates a suppression filter in your account to suppress a contact.', + props: { + email: Property.ShortText({ + displayName: 'Email address', + description: 'Enter the email to add to the suppression list.', + required: true, + defaultValue: '', + }), + reason: Property.LongText({ + displayName: 'Suppression reason', + description: 'Use the description to describe why this contact is being suppressed.', + required: false, + defaultValue: '', + }) + }, + async run(context) { + const { email, reason } = context.propsValue; + + await propsValidation.validateZod(context.propsValue, { + email: z.string().min(1).max(100, 'Email has no more than 100 characters.'), + reason: z.string().min(0).max(255, 'Suppression reason has no more than 255 characters.'), + }); + + const client = makeClient(context.auth); + return await client.createSuppressionFilter(email, reason); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/create-transaction.ts b/packages/pieces/community/tarvent/src/lib/actions/create-transaction.ts new file mode 100644 index 0000000..7d46da9 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/create-transaction.ts @@ -0,0 +1,141 @@ +import { tarventAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { makeClient, tarventCommon } from '../common'; +import { propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const createTransaction = createAction({ + auth: tarventAuth, + name: 'tarvent_create_transaction', + displayName: 'Send A Transactional Email', + description: 'Sends a transactional email. NOTE: This will use your email API credits.', + props: { + groupName: tarventCommon.txGroupName(false, 'Choose an existing group name or use "Custom" to enter a new group name. This name is used for reporting.'), + fromEmail: Property.ShortText({ + displayName: 'From email', + description: 'Enter who this transaction is from.', + required: true, + defaultValue: '' + }), + fromName: Property.ShortText({ + displayName: 'From name', + description: 'Enter a friendly name for who this transaction is from.', + required: false, + defaultValue: '' + }), + toEmail: Property.ShortText({ + displayName: 'To email', + description: 'Enter the email that the transaction should be sent to.', + required: true, + defaultValue: '' + }), + ccEmail: Property.Array({ + displayName: 'CC Emails', + description: 'Enter emails that this transaction should be CC\'d to.', + properties: { + email: Property.ShortText({ + displayName: 'Email', + required: false, + defaultValue: '' + }) + }, + required: false, + defaultValue: [], + }), + bccEmail: Property.Array({ + displayName: 'BCC Emails', + description: 'Enter emails that this transaction should be BCC\'d to.', + properties: { + email: Property.ShortText({ + displayName: 'Email', + required: false, + defaultValue: '' + }) + }, + required: false, + defaultValue: [], + }), + subject: Property.ShortText({ + displayName: 'Subject line', + description: 'Enter a subject line for the transaction.', + required: true, + defaultValue: '' + }), + replyToEmail: Property.ShortText({ + displayName: 'Reply to email', + description: 'Enter the email that the replies should go to.', + required: true, + defaultValue: '' + }), + replyToName: Property.ShortText({ + displayName: 'Reply to name', + description: 'Enter a friendly name the replies should go to.', + required: false, + defaultValue: '' + }), + variables: Property.Object({ + displayName: 'Variables', + description: 'NOTE: Variable names (first column) can have Letters, numbers, underscores, and hyphens. Any other characters in the first column only will be removed.', + required: false, + defaultValue: '', + }), + templateId: tarventCommon.templateId(false, 'Select which template you\'d like to used for this transaction.'), + mimeType: Property.StaticDropdown({ + displayName: 'Message type', + description: 'If "Template" is specified, this will be ignored..', + required: false, + options: { + options: [ + { + label: 'HTML', + + value: 'HTML', + }, + { + label: 'Plain text', + value: 'TEXT', + }, + ], + } + }), + content: Property.LongText({ + displayName: 'Content', + description: 'To merge in variables, you must use the syntax [[Tx.VariableData.VariableName]] (ex. Variable name is FirstName, merge syntax would be [[Tx.VariableData.FirstName]]) If "Template" is specified, this will be ignored.', + required: false, + }), + ignoreSuppressCheck: Property.StaticDropdown({ + displayName: 'Ignore suppression filters', + description: 'Select if the suppression filters on your account should be ignored for this transaction.', + required: true, + options: { + options: [ + { + label: 'Ignore', + + value: 'true', + }, + { + label: 'Do not ignore', + value: 'false', + }, + ], + }, + defaultValue: 'false' + }) + }, + async run(context) { + const { groupName, fromEmail, fromName, toEmail, ccEmail, bccEmail, subject, replyToEmail, replyToName, variables, templateId, mimeType, content, ignoreSuppressCheck } = context.propsValue; + + await propsValidation.validateZod(context.propsValue, { + groupName: z.string().max(100, 'Group name has to be equal to or less than 100 characters.').optional(), + fromEmail: z.string().max(320, 'From email has to be equal to or less than 320 characters.'), + fromName: z.string().max(255, 'From name has to be equal to or less than 255 characters.').optional(), + subject: z.string().max(500, 'Subject has to be equal to or less than 500 characters.'), + replyToEmail: z.string().max(320, 'Reply to email has to be equal to or less than 320 characters.'), + replyToName: z.string().max(255, 'Reply to name has to be equal to or less than 255 characters.').optional(), + }); + + const client = makeClient(context.auth); + return await client.createTransaction(groupName, fromEmail, fromName, toEmail, ccEmail, bccEmail, subject, replyToEmail, replyToName, variables, templateId, mimeType, content, ignoreSuppressCheck); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/generate-custom-event.ts b/packages/pieces/community/tarvent/src/lib/actions/generate-custom-event.ts new file mode 100644 index 0000000..81a9999 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/generate-custom-event.ts @@ -0,0 +1,20 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; + +export const generateCustomEvent = createAction({ + auth: tarventAuth, + name: 'tarvent_generate_custom_event', + displayName: 'Generate A Custom Event', + description: 'Generate a custom event in your Tarvent account.', + props: { + customEventId: tarventCommon.customEventId(true, ''), + contactId: tarventCommon.contactId + }, + async run(context) { + const { customEventId, contactId } = context.propsValue; + + const client = makeClient(context.auth); + return await client.generateCustomEvent(contactId,customEventId); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/get-audience-groups.ts b/packages/pieces/community/tarvent/src/lib/actions/get-audience-groups.ts new file mode 100644 index 0000000..7f5783a --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/get-audience-groups.ts @@ -0,0 +1,26 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; + +export const getAudienceGroups = createAction({ + auth: tarventAuth, + name: 'tarvent_get_audience_groups', + displayName: 'Find Audience Group', + description: 'Finds an audience group by name.', + props: { + audienceId: tarventCommon.audienceId(true, ''), + name: Property.ShortText({ + displayName: 'Audience name', + description: 'Find an audience by searching using its name.', + required: false, + defaultValue: '', + + }) + }, + async run(context) { + const { audienceId, name } = context.propsValue; + + const client = makeClient(context.auth); + return await client.listAudienceGroupsAdv(audienceId, name); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/get-audiences.ts b/packages/pieces/community/tarvent/src/lib/actions/get-audiences.ts new file mode 100644 index 0000000..4042fe7 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/get-audiences.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient } from '../common'; + +export const getAudiences = createAction({ + auth: tarventAuth, + name: 'tarvent_get_audiences', + displayName: 'Find Audience', + description: 'Finds an audience by name or tags.', + props: { + name: Property.ShortText({ + displayName: 'Audience name', + description: 'Find an audience by searching using its name.', + required: false, + defaultValue: '', + + }), + tags: Property.LongText({ + displayName: 'Audience tags', + description: 'Find an audience by searching using its tags. To search using multiple tags, separate the tags with a comma.', + required: false, + defaultValue: '', + }) + }, + async run(context) { + const { name, tags } = context.propsValue; + + const client = makeClient(context.auth); + return await client.listAudiencesAdv(name, tags); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/get-campaigns.ts b/packages/pieces/community/tarvent/src/lib/actions/get-campaigns.ts new file mode 100644 index 0000000..410cfaf --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/get-campaigns.ts @@ -0,0 +1,68 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient } from '../common'; + +export const getCampaigns = createAction({ + auth: tarventAuth, + name: 'tarvent_get_campaigns', + displayName: 'Find Campaign', + description: 'Finds a campaign by name, status or tags.', + props: { + name: Property.ShortText({ + displayName: 'Campaign name', + description: 'Find a campaign by searching using its name.', + required: false, + defaultValue: '', + + }), + tags: Property.LongText({ + displayName: 'Campaign tags', + description: 'Find a campaign by searching using its tags. To search using multiple tags, separate the tags with a comma.', + required: false, + defaultValue: '', + }), + status: Property.StaticDropdown({ + displayName: 'Campaign status', + description: '', + required: false, + options: { + options: [ + { + label: 'Sent', + value: 'COMPLETED' + }, + { + label: 'Ready to send', + value: 'READY_TO_SEND' + }, + { + label: 'Draft', + value: 'NOT_SCHEDULED' + }, + { + label: 'Scheduled', + value: 'PENDING' + }, + { + label: 'Paused', + value: 'Paused' + }, + { + label: 'Stopped', + value: 'STOPPED' + }, + { + label: 'Pending multivariate winner', + value: 'SENDING_PENDING_AB_WINNER' + }, + ], + }, + }), + }, + async run(context) { + const { name, tags, status } = context.propsValue; + + const client = makeClient(context.auth); + return await client.listCampaignsAdv(name, tags, status); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/get-contact.ts b/packages/pieces/community/tarvent/src/lib/actions/get-contact.ts new file mode 100644 index 0000000..6c230ca --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/get-contact.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; +import { propsValidation } from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const getContact = createAction({ + auth: tarventAuth, + name: 'tarvent_get_contact', + displayName: 'Find Contact', + description: 'Finds a contact by your custom key data field (typically this is by email).', + props: { + audienceId: tarventCommon.audienceId(true, ''), + email: Property.ShortText({ + displayName: 'Contact email', + description: 'Search for a contact by email. If the audience uses a custom contact identifier, then the search returns the FIRST contact that matches the email address. To target a specific contact, please use the contact\'s ID.', + required: true, + defaultValue: '' + }) + }, + async run(context) { + const { audienceId, email } = context.propsValue; + + await propsValidation.validateZod(context.propsValue, { + email: z.string().min(1).max(255, 'Email has no more than 255 characters.'), + }); + + const client = makeClient(context.auth); + return await client.listContact(audienceId, email); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/get-custom-event.ts b/packages/pieces/community/tarvent/src/lib/actions/get-custom-event.ts new file mode 100644 index 0000000..c72396c --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/get-custom-event.ts @@ -0,0 +1,25 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient } from '../common'; + +export const getCustomEvent = createAction({ + auth: tarventAuth, + name: 'tarvent_get_custom_event', + displayName: 'Find Custom Event', + description: 'Finds a custom event by name.', + props: { + name: Property.ShortText({ + displayName: 'Custom event name', + description: 'Find a custom event by searching using its name.', + required: false, + defaultValue: '', + + }) + }, + async run(context) { + const { name } = context.propsValue; + + const client = makeClient(context.auth); + return await client.listCustomEventsAdv(name); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/get-journey.ts b/packages/pieces/community/tarvent/src/lib/actions/get-journey.ts new file mode 100644 index 0000000..3f39aa4 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/get-journey.ts @@ -0,0 +1,48 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient } from '../common'; + +export const getJourney = createAction({ + auth: tarventAuth, + name: 'tarvent_get_journey', + displayName: 'Find Journey', + description: 'Finds a journey by name, status or tags.', + props: { + name: Property.ShortText({ + displayName: 'Journey name', + description: 'Find a journey by searching using its name.', + required: false, + defaultValue: '', + + }), + tags: Property.LongText({ + displayName: 'Journey tags', + description: 'Find a journey by searching using its tags. To search using multiple tags, separate the tags with a comma.', + required: false, + defaultValue: '', + }), + status: Property.StaticDropdown({ + displayName: 'Journey status', + description: '', + required: false, + options: { + options: [ + { + label: 'Running', + value: 'RUNNING' + }, + { + label: 'Not running', + value: 'NOT_RUNNING' + } + ], + }, + }), + }, + async run(context) { + const { name, tags, status } = context.propsValue; + + const client = makeClient(context.auth); + return await client.listJourneysAdv(name, tags, status); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/index.ts b/packages/pieces/community/tarvent/src/lib/actions/index.ts new file mode 100644 index 0000000..563979b --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/index.ts @@ -0,0 +1,18 @@ +export * from './create-contact'; +export * from './create-transaction'; +export * from './send-campaign'; +export * from './create-audience-group'; +export * from './update-contact-tag'; +export * from './create-contact-note'; +export * from './update-contact-group'; +export * from './update-contact-journey'; +export * from './update-contact-status'; +export * from './generate-custom-event'; +export * from './update-journey-status'; +export * from './create-suppression-filter'; +export * from './get-audiences'; +export * from './get-audience-groups'; +export * from './get-campaigns'; +export * from './get-contact'; +export * from './get-custom-event'; +export * from './get-journey'; diff --git a/packages/pieces/community/tarvent/src/lib/actions/send-campaign.ts b/packages/pieces/community/tarvent/src/lib/actions/send-campaign.ts new file mode 100644 index 0000000..a0fdbe4 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/send-campaign.ts @@ -0,0 +1,21 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; + + + +export const sendCampaign = createAction({ + auth: tarventAuth, + name: 'tarvent_send_campaign', + displayName: 'Send Campaign', + description: 'Sends a copy of a campaign.', + props: { + campaignId: tarventCommon.campaignId(true, 'Select which campaign to send. **NOTE:** Make sure all campaign settings are correct (from, subject, content) before configuring this automation.', true), + }, + async run(context) { + const { campaignId } = context.propsValue; + + const client = makeClient(context.auth); + return await client.sendCampaignCopy(campaignId); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/update-contact-group.ts b/packages/pieces/community/tarvent/src/lib/actions/update-contact-group.ts new file mode 100644 index 0000000..535cf3b --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/update-contact-group.ts @@ -0,0 +1,39 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; + +export const updateContactGroup = createAction({ + auth: tarventAuth, + name: 'tarvent_update_contact_group', + displayName: 'Add/Remove Contact From Audience Group', + description: 'Adds or removes a contact from an audience group.', + props: { + audienceId: tarventCommon.audienceId(true, ''), + groupId: tarventCommon.audienceGroupId(true, ''), + contactId: tarventCommon.contactId, + action: Property.StaticDropdown({ + displayName: 'Add or remove', + description: 'Select whether to add or remove the contact from the group.', + required: true, + options: { + options: [ + { + label: 'Add', + + value: 'Add', + }, + { + label: 'Remove', + value: 'Remove', + }, + ], + }, + }), + }, + async run(context) { + const { audienceId, groupId, contactId, action } = context.propsValue; + + const client = makeClient(context.auth); + return await client.addRemoveContactGroup(action, contactId, audienceId, groupId); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/update-contact-journey.ts b/packages/pieces/community/tarvent/src/lib/actions/update-contact-journey.ts new file mode 100644 index 0000000..dcf71e4 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/update-contact-journey.ts @@ -0,0 +1,38 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; + +export const updateContactJourney = createAction({ + auth: tarventAuth, + name: 'tarvent_update_contact_journey', + displayName: 'Add/Remove A Contact From A Journey', + description: 'Adds or removes the contact from a journey.', + props: { + contactId: tarventCommon.contactId, + journeyId: tarventCommon.journeyId(true, 'Select which journey to start or stop.'), + action: Property.StaticDropdown({ + displayName: 'Add or remove', + description: 'Select whether to add or remove the contact from the journey.', + required: true, + options: { + options: [ + { + label: 'Add', + + value: 'Add', + }, + { + label: 'Remove', + value: 'Remove', + }, + ], + }, + }), + }, + async run(context) { + const { contactId, journeyId, action } = context.propsValue; + + const client = makeClient(context.auth); + return await client.addRemoveJourneyContact(action, contactId, journeyId); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/update-contact-status.ts b/packages/pieces/community/tarvent/src/lib/actions/update-contact-status.ts new file mode 100644 index 0000000..345c6c7 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/update-contact-status.ts @@ -0,0 +1,38 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; +import { ContactStatus } from '../common/types'; + +export const updateContactStatus = createAction({ + auth: tarventAuth, + name: 'tarvent_update_contact_status', + displayName: 'Subscribe/Unsubscribe Contact From Audience', + description: 'Subscribes or unsubscribe a contact in an audience.', + props: { + contactId: tarventCommon.contactId, + action: Property.StaticDropdown({ + displayName: 'Status', + description: 'Select whether to subscribe or unsubscribe the contact.', + required: true, + options: { + options: [ + { + label: 'Subscribe', + + value: 'ACTIVE', + }, + { + label: 'Unsubscribe', + value: 'OPT_OUT', + }, + ], + }, + }), + }, + async run(context) { + const { contactId, action } = context.propsValue; + + const client = makeClient(context.auth); + return await client.updateContactStatus(contactId, action as ContactStatus); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/update-contact-tag.ts b/packages/pieces/community/tarvent/src/lib/actions/update-contact-tag.ts new file mode 100644 index 0000000..7efe1f8 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/update-contact-tag.ts @@ -0,0 +1,40 @@ +import { tarventAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { makeClient, tarventCommon } from '../common'; + +export const updateContactTags = createAction({ + auth: tarventAuth, + name: 'tarvent_update_contact_tag', + displayName: 'Add/Remove Contact Tag', + description: 'Adds or removes a tag from contact.', + props: { + audienceId: tarventCommon.audienceId(true, 'If specified, the trigger will only fire if contact is in the selected audience.'), + contactId: tarventCommon.contactId, + action: Property.StaticDropdown({ + displayName: 'Tag action', + description: 'Select whether to add or remove tags.', + required: true, + defaultValue: 'Add', + options: { + options: [ + { + label: 'Add', + + value: 'Add', + }, + { + label: 'Remove', + value: 'remove', + }, + ], + }, + }), + tagIds: tarventCommon.tagIds(true, `Enter which tags you would like to add or remove.`), + }, + async run(context) { + const { audienceId, contactId, action, tagIds } = context.propsValue; + + const client = makeClient(context.auth); + return await client.updateContactTags(audienceId, contactId, action, tagIds); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/actions/update-journey-status.ts b/packages/pieces/community/tarvent/src/lib/actions/update-journey-status.ts new file mode 100644 index 0000000..6dbec28 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/actions/update-journey-status.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; + +export const updateJourneyStatus = createAction({ + auth: tarventAuth, + name: 'tarvent_update_journey_status', + displayName: 'Start/Stop Journey', + description: 'Starts or stops a journey.', + props: { + journeyId: tarventCommon.journeyId(true, 'Select which journey to start or stop'), + action: Property.StaticDropdown({ + displayName: 'Action', + description: 'Select whether to start or stop the journey.', + required: true, + options: { + options: [ + { + label: 'Start', + + value: 'Start', + }, + { + label: 'Stop', + value: 'Stop', + }, + ], + }, + }), + }, + async run(context) { + const { journeyId, action } = context.propsValue; + + const client = makeClient(context.auth); + return await client.updateJourneyStatus(action, journeyId); + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/common/client.ts b/packages/pieces/community/tarvent/src/lib/common/client.ts new file mode 100644 index 0000000..8ade7b2 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/common/client.ts @@ -0,0 +1,1099 @@ +import { + httpClient, + HttpMessageBody, + HttpMethod, + HttpRequest +} from '@activepieces/pieces-common'; +import { ContactStatus, CreateAudienceGroupResponse, CreateContactNoteResponse, CreateContactResponse, CreateGroupContactResponse, CreateSuppressionFilterResponse, CreateWebhookResponse, DeleteGroupContactResponse, ListAudienceDataFieldsResponse, ListAudienceFormsResponse, ListAudienceGroupAdvResponse, ListAudienceGroupResponse, ListAudiencesAdvResponse, ListAudiencesResponse, ListCampaignLinksResponse, ListCampaignsAdvResponse, ListCampaignsResponse, ListContactResponse, ListCustomEventsAdvResponse, ListCustomEventsResponse, ListJourneysAdvResponse, ListJourneysResponse, ListLandingPagesResponse, ListSurveysResponse, ListTagsResponse, ListTemplatesResponse, ListTxGroupNamesResponse } from './types'; + +export class TarventClient { + constructor(private accountId: string, private apiKey: string) { } + + async makeRequest( + body: unknown | undefined = undefined, + ): Promise { + const request: HttpRequest = { + method: HttpMethod.POST, + url: 'https://api.tarvent.com/graphql', + headers: { + 'x-api-key': this.apiKey, + 'account': this.accountId + }, + body: body, + }; + const res = await httpClient.sendRequest(request); + return res.body; + } + + async authenticate() { + // Call to get account audiences to check if authentication is valid + const body: unknown = { + "query": "query GetAudiences{\r\naudiences{\r\nnodes{\r\nid\r\nname}\r\n}\r\n}" + }; + return await this.makeRequest(body); + } + // #region Webhook create and delete + async createWebhook(context: any, webhookType: string): Promise { + const body: unknown = { + "query": "mutation CreateWebhook($name: String!, $description: String!, $events: [WebhookEventInput!]!, $callBackUrl: String!, $dataSettings: WebhookDataSetting, $filter: ConditionGroupInput) { createWebhook( name: $name description: $description events: $events callBackUrl: $callBackUrl dataSettings: $dataSettings filter: $filter ) { id } }", + "variables": { + "name": this.getWebhookName(), + "description": "", + "callBackUrl": context.webhookUrl, + "events": this.getWebhookEvent(webhookType, context.propsValue), + "filter": this.generateWebhookInput(webhookType, context.propsValue), + "dataSettings": context.propsValue.include, + "integrationType": "ZAPIER" + } + } + return await this.makeRequest(body); + } + + async deleteWebhook(webhookId: string): Promise { + const body: unknown = { + "query": "mutation DeleteWebhook($id: String!) { deleteWebhook(id: $id) }", + "variables": { + "id": webhookId + } + } + return await this.makeRequest(body); + } + //#endregion + + // #region Searches and entity lists + async listAudiences(): Promise { + const body: unknown = { + "query": "query GetAudience( $first: Int $after: String $last: Int $before: String $order: [AudienceInfoSortInput!] $where: AudienceInfoFilterInput ) { audiences( first: $first after: $after last: $last before: $before order: $order where: $where ) { nodes { id name } } }", + "variables": { + "first": 100, + "after": null, + "last": null, + "before": null, + "order": null, + "where": null + } + } + return await this.makeRequest(body); + } + + async listAudiencesAdv(name = '', tags = '', itemCount = 100): Promise { + const filters = []; + if (name && name !== '') filters.push({ name: { contains: name } }); + if (tags && tags !== '') filters.push({ tags: { some: { in: tags.split(',').map((t) => t.trim()) } } }); + + const body: unknown = { + "query": "query GetAudience( $first: Int $after: String $last: Int $before: String $order: [AudienceInfoSortInput!] $where: AudienceInfoFilterInput ) { audiences( first: $first after: $after last: $last before: $before order: $order where: $where ) { nodes { id name companyName streetAddress streetAddress2 addressLocality addressRegion postalCode addressCountry phone website totalContacts totalActiveContacts totalUnconfirmedContacts totalUndeliverableContacts totalUnsubscribedContacts totalComplaintContacts totalSuppressedContacts tags customKeyDataField { id labelText } createdUtc lastModifiedUtc } } }", + "variables": { + "first": itemCount, + "after": null, + "last": null, + "before": null, + "order": null, + "where": filters.length !== 0 ? { and: filters } : null + } + } + return await this.makeRequest(body); + } + + async listAudienceGroups(audienceId: string): Promise { + const body: unknown = { + "query": "query GetAudienceGroups($first: Int, $after: String, $last: Int, $before: String, $audienceId: String!, $order: [AudienceGroupInfoSortInput!], $where: AudienceGroupInfoFilterInput) { audienceGroups( first: $first after: $after last: $last before: $before audienceId: $audienceId order: $order where: $where ) { totalCount nodes { id name } } }", + "variables": { + "first": 50, + "audienceId": audienceId, + "order": [ + { + "name": "ASC" + } + ], + "where": { + "isDynamic": { + "eq": false + } + } + } + }; + return await this.makeRequest(body); + } + + async listAudienceGroupsAdv(audienceId: string | undefined, name = ''): Promise { + const filters: any = [{ isDynamic: { eq: false } }]; + if (name && name !== '') filters.push({ name: { contains: name } }); + const body: unknown = { + "query": "query GetAudienceGroups($first: Int, $after: String, $last: Int, $before: String, $audienceId: String!, $order: [AudienceGroupInfoSortInput!], $where: AudienceGroupInfoFilterInput) { audienceGroups( first: $first after: $after last: $last before: $before audienceId: $audienceId order: $order where: $where ) { totalCount nodes { id name description isPublic } } }", + "variables": { + "first": 100, + "audienceId": audienceId, + "order": [ + { + "name": "ASC" + } + ], + "where": { and: filters } + } + }; + return await this.makeRequest(body); + } + + async listAudienceDataFields(audienceId: unknown): Promise { + const body: unknown = { + query: "query GetDataFields($first: Int, $after: String, $last: Int, $before: String, $audienceId: String!, $order: [DataFieldSortInput!], $where: DataFieldFilterInput) { audienceDataFields(first: $first, after: $after, last: $last, before: $before, audienceId: $audienceId, order: $order, where: $where) { nodes { id dataType labelText required isSystem defaultValue mergeTag isPrimaryKey isGdprField category } } }", + variables: { + first: 200, + after: null, + last: null, + before: null, + audienceId, + order: null, + where: null + } + }; + return await this.makeRequest(body); + } + + async listAudienceForms(audienceId: string): Promise { + const body: unknown = { + query: `query GetForms($first: Int, $after: String, $last: Int, $before: String, $audienceId: String, $order: [FormInfoSortInput!], $where: FormInfoFilterInput) { + forms( + first: $first + after: $after + last: $last + before: $before + audienceId: $audienceId + order: $order + where: $where + ) { + nodes { + id + name + } + } + }`, + variables: { + first: 50, + audienceId, + order: [{ name: "ASC" }] + }, + }; + return await this.makeRequest(body); + } + + async listTags(): Promise { + const body: unknown = { + "query": "query GetTags($first: Int $after: String $last: Int $before: String $order: [TagSortInput!] $where: TagFilterInput) { tags(first: $first after: $after last: $last before: $before order: $order where: $where) { nodes { name } } }", + "variables": { + "first": 50, + "after": null, + "order": null, + "where": null + } + }; + return await this.makeRequest(body); + } + + async listCampaigns(ignoreStatus = false, isEvent = false): Promise { + const filters = []; + filters.push({ isArchived: { eq: false } }); + if (!ignoreStatus) { + filters.push({ + sendStatus: { + in: isEvent ? ['NOT_SCHEDULED', 'COMPLETED'] : ['NOT_SCHEDULED', 'READY_TO_SEND'] + }, + }); + } else { + filters.push({ + sendStatus: { + nin: ['PAUSED', 'PENDING', 'PROCESSING', 'QUEUED', 'SYSTEM_STOPPED'], + }, + }); + } + const body: unknown = { + query: `query GetCampaigns( + $first: Int + $after: String + $last: Int + $before: String + $order: [CampaignInfoSortInput!] + $where: CampaignInfoFilterInput + ) + { + campaigns( + first: $first + after: $after + last: $last + before: $before + order: $order + where: $where + ) { + totalCount + nodes { + id + name + } + } + }`, + variables: { + first: 100, + after: null, + order: [ + { + modifiedUtc: 'DESC', + }, + ], + where: { + and: filters, + }, + }, + }; + return await this.makeRequest(body); + } + + async listCampaignsAdv(name = '', tags = '', status = ''): Promise { + const filters = []; + if (name && name !== '') { + filters.push({ name: { contains: name } }); + } + if (tags && tags !== '') { + filters.push({ + tags: { + some: { in: tags.split(',').map((t) => t.trim()) }, + }, + }); + } + if (status && status.length !== 0) { + const allStatus: string[] = status.split(',').map((s) => s.trim()); + if (status.split(',').some((s) => s === 'PENDING')) { + allStatus.push('PROCESSING'); + allStatus.push('QUEUED'); + } + if (status.split(',').some((s) => s === 'STOPPED')) { + allStatus.push('SYSTEM_STOPPED'); + } + filters.push({ sendStatus: { in: allStatus } }); + } + const body: unknown = { + query: `query GetCampaign( $first: Int $after: String $last: Int $before: String $order: [CampaignInfoSortInput!] $where: CampaignInfoFilterInput ) { campaigns( first: $first after: $after last: $last before: $before order: $order where: $where ) { nodes { id tags name audienceId description mvWinType timeJumper sendStatus scheduledToSendUtc createdUtc modifiedUtc } } }`, + variables: { + first: 100, + after: null, + order: [ + { + modifiedUtc: 'DESC', + }, + ], + where: filters.length !== 0 ? { and: filters } : null + }, + }; + return await this.makeRequest(body); + } + + async listCampaignLinks(campaignId: string): Promise { + const body: unknown = { + query: `query GetCampaignLinks($id: String!) { + campaignLinks(id: $id) { + id + url + track + entityName + entityType + formType + } + }`, + variables: { + id: campaignId, + }, + }; + return await this.makeRequest(body); + } + + async listJourneys(): Promise { + const body: unknown = { + query: `query GetJourneys($first: Int, $after: String, $last: Int, $before: String, $order: [JourneyInfoSortInput!], $where: JourneyInfoFilterInput) { + journeys( + first: $first + after: $after + last: $last + before: $before + order: $order + where: $where + ) { + nodes { + id + name + } + pageInfo { + hasNextPage + endCursor + } + } + }`, + variables: { + first: 50, + after: null, + last: null, + before: null, + order: null, + where: null, + }, + }; + return await this.makeRequest(body); + } + + async listJourneysAdv(name = '', tags = '', status = ''): Promise { + const filters = []; + if (name && name !== '') { + filters.push({ name: { contains: name } }); + } + if (tags && tags !== '') { + filters.push({ + tags: { + some: { in: tags.split(',').map((t) => t.trim()) }, + }, + }); + } + if (status && status.length !== 0) { + const allStatus: string[] = status.split(',').map((s) => s.trim()); + filters.push({ status: { in: allStatus } }); + } + const body: unknown = { + query: `query GetJourneys($first: Int, $after: String, $last: Int, $before: String, $order: [JourneyInfoSortInput!], $where: JourneyInfoFilterInput) { journeys(first: $first, after: $after, last: $last, before: $before, order: $order, where: $where) { nodes { id tags name audienceId description reEntryType status totalEmailNodes totalNotificationEmailNodes totalSiteNotificationNodes totalSMSNodes createdUtc modifiedUtc } } }`, + variables: { + first: 1, + after: null, + last: null, + before: null, + order: null, + where: filters.length !== 0 ? { and: filters } : null, + }, + }; + return await this.makeRequest(body); + } + + async listContact(audienceId: string | undefined, email: string): Promise { + const body: unknown = { + query: `query GetContact($input: GetContactInput!) { contact(input: $input) { id email firstName lastName groups { id name } tags rating longitude latitude streetAddress streetAddress2 addressLocality addressRegion postalCode addressCountry timeZone language sendFormat status optInUtc confirmedUtc optOutUtc profileFields { id dataField { id labelText } value } modifiedUtc createdUtc } }`, + variables: { + input: { + audienceId: audienceId, + emailAddress: email.includes('@') ? email : null, + id: !email.includes('@') ? email : null + } + }, + }; + console.log(body); + return await this.makeRequest(body); + } + + async listLandingPages(): Promise { + const body: unknown = { + query: `query GetLandingPages($first: Int, $after: String, $last: Int, $before: String, $order: [LandingPageInfoSortInput!], $where: LandingPageInfoFilterInput) { + landingPages( + first: $first + after: $after + last: $last + before: $before + order: $order + where: $where + ) { + nodes { + id + name + } + } + }`, + variables: { + first: 50, + after: null, + last: null, + before: null, + order: null, + where: null, + }, + }; + return await this.makeRequest(body); + } + + async listSurveys(): Promise { + const body: unknown = { + query: `query GetSurveys($first: Int, $after: String, $last: Int, $before: String, $order: [SurveyInfoSortInput!], $where: SurveyInfoFilterInput) { + surveys( + first: $first + after: $after + last: $last + before: $before + order: $order + where: $where + ) { + nodes { + id + name + } + } + }`, + variables: { + first: 50, + after: null, + last: null, + before: null, + order: null, + where: null, + }, + }; + return await this.makeRequest(body); + } + + async listTemplates(): Promise { + const body: unknown = { + query: `query GetTemplates($first: Int, $after: String, $last: Int, $before: String, $order: [TemplateInformationSortInput!], $where: TemplateInformationFilterInput) { + templates( + first: $first + after: $after + last: $last + before: $before + order: $order + where: $where + ) { + nodes { + id + name + } + } + }`, + variables: { + first: 50, + after: null, + where: null + }, + }; + return await this.makeRequest(body); + } + + async listTxGroupNames(): Promise { + const body: unknown = { + query: `query GetTxGroupNames($where: StringOperationFilterInput) { transactionGroupNames(where: $where) }`, + variables: { + where: null + }, + }; + return await this.makeRequest(body); + } + + async listCustomEvents(): Promise { + const body: unknown = { + query: `query GetCustomEvents($first: Int, $after: String, $last: Int, $before: String, $order: [CustomApiEventSortInput!], $where: CustomApiEventFilterInput) { + customApiEvents( + first: $first + after: $after + last: $last + before: $before + order: $order + where: $where + ) { + nodes { + id + key + name + } + } + }`, + variables: { + first: 50, + after: null, + last: null, + before: null, + order: null, + where: null, + }, + }; + return await this.makeRequest(body); + } + + async listCustomEventsAdv(name = ''): Promise { + const filters = []; + if (name && name !== '') { + filters.push({ name: { contains: name } }); + } + const body: unknown = { + query: `query GetCustomEvents($first: Int, $after: String, $last: Int, $before: String, $order: [CustomApiEventSortInput!], $where: CustomApiEventFilterInput) { customApiEvents(first: $first, after: $after, last: $last, before: $before, order: $order, where: $where) { nodes { id key name createdUtc modifiedUtc } } }`, + variables: { + first: 10, + after: null, + last: null, + before: null, + order: null, + where: filters.length !== 0 ? { and: filters } : null, + }, + }; + return await this.makeRequest(body); + } + //#endregion + + // #region Creates and updates + // Create/update contact + async createContact(audienceId: string | undefined, emailAddress: string, updateExisting: string, groupAction: string | undefined, tagAction: string | undefined, tagIds: string[] | undefined, groupIds: string[] | undefined, firstName: string | undefined, lastName: string | undefined, streetAddress: string | undefined, streetAddress2: string | undefined, addressLocality: string | undefined, addressRegion: string | undefined, postalCode: string | undefined, addressCountry: string | undefined, profileFields: Record | undefined): Promise { + const profile = []; + for (const f in profileFields) { + if (profileFields[f]) { + profile.push({ + dataFieldId: f, + value: this.isISODate(profileFields[f].toString()) ? this.formatISODateToYMD(profileFields[f].toString()) : profileFields[f].toString(), + }); + } + } + const body: unknown = { + query: `mutation CreateContact($input: CreateContactInput!) { createContact(input: $input) { id email firstName lastName tags groups { id name } streetAddress streetAddress2 addressLocality addressRegion postalCode addressCountry profileFields { dataField { id labelText } value } modifiedUtc createdUtc } }`, + variables: { + input: { + email: emailAddress, + audienceId, + updateExisting: updateExisting === 'Update', + audienceGroupManagementType: groupAction === 'Replace' + ? 'REPLACE_EXISTING' + : 'ADD_NEW_GROUPS_ONLY', + tagManagementType: tagAction === 'Replace' + ? 'REPLACE_EXISTING' + : 'ADD_NEW_TAGS_ONLY', + firstName, + lastName, + streetAddress, + streetAddress2, + addressLocality, + addressRegion, + postalCode, + addressCountry, + profile, + tags: tagIds ? tagIds : null, + groupIds: groupIds ? groupIds : null, + sendFormat: 'MULTIPART', + }, + }, + }; + return await this.makeRequest(body); + } + + // Update contact tags + async updateContactTags(audienceId: string | undefined, contactId: string, action: string, tagIds: string[] | undefined): Promise { + const body: unknown = { + query: "mutation UpdateContactTags($input: UpdateContactTagsInput!) { updateContactTags(input: $input) }", + variables: { + input: { + id: contactId, + audienceId, + operator: action === 'Add' ? 'ADD' : 'REMOVE', + tags: tagIds + } + } + }; + return await this.makeRequest(body); + } + + // Create audience group + async createAudienceGroup(audienceId: string | undefined, name: string | undefined, description: string | undefined, isPublic: string): Promise { + const body: unknown = { + query: `mutation CreateAudienceGroup($input: CreateGroupInput!) { + createAudienceGroup(input: $input) { + id + name + description + isPublic + isDynamic + } + }`, + variables: { + input: { + name, + description, + isDynamic: false, + isPublic: isPublic === "false" ? false : true, + audienceId, + criteria: null + } + } + }; + return await this.makeRequest(body); + } + + // Send campaign copy + async sendCampaignCopy(campaignId: string | undefined): Promise { + const body: unknown = { + query: `mutation SendCampaignCopy($id: String!, $sendsUtc: [DateTime!]!) { sendCampaignCopy(id: $id, sendsUtc: $sendsUtc) }`, + variables: { + id: campaignId, + sendsUtc: [new Date().toISOString()] + } + }; + return await this.makeRequest(body); + } + + // Add note to contact + async createContactNote(contactId: string | undefined, note: string | undefined): Promise { + const body: unknown = { + query: `mutation CreateContactNote($input: CreateContactNoteInput!) { createContactNote(input: $input) { id } }`, + variables: { + input: { + contactId, + message: note + } + } + }; + return await this.makeRequest(body); + } + + // Add or remove contact from group + async addRemoveContactGroup(action: string | undefined, contactId: string | undefined, audienceId: string | undefined, groupId: string | undefined): Promise { + let body: unknown = undefined; + if (action === 'Add') { + body = { + query: `mutation CreateGroupContact($input: CreateGroupContactInput!) { createGroupContact(input: $input) { id } }`, + variables: { + input: { + id: contactId, + groupId: groupId, + audienceId: audienceId, + } + } + }; + } else { + body = { + query: `mutation DeleteGroupContact($input: DeleteGroupContactInput!) { deleteGroupContact(input: $input) { id } }`, + variables: { + input: { + id: contactId, + groupId: groupId, + audienceId: audienceId, + } + } + }; + } + return await this.makeRequest(body); + } + + // Add or remove contact from journey + async addRemoveJourneyContact(action: string | undefined, contactId: string | undefined, journeyId: string | undefined): Promise { + let body: unknown = undefined; + if (action === 'Add') { + body = { + query: `mutation EnterContactIntoJourney($id: String!, $journeyId: String!) { enterContactIntoJourney(id: $id, journeyId: $journeyId) }`, + variables: { + id: contactId, + journeyId + } + }; + } else { + body = { + query: `mutation ExitContactFromJourney($id: String!, $journeyId: String!) { exitContactFromJourney(id: $id, journeyId: $journeyId) }`, + variables: { + id: contactId, + journeyId + } + }; + } + return await this.makeRequest(body); + } + + // Update contact status + async updateContactStatus(contactId: string | undefined, status: ContactStatus | undefined): Promise { + const body: unknown = { + query: `mutation UpdateContactStatus($input: UpdateContactStatusInput!) { updateContactStatus(input: $input) }`, + variables: { + input: { + id: contactId, + status + } + } + }; + return await this.makeRequest(body); + } + + // Generate custom event + async generateCustomEvent(contactId: string | undefined, customEventId: string | undefined): Promise { + const body: unknown = { + query: `mutation CreateContactCustomEvent($id: String!, $key: String!) { createContactCustomEvent(id: $id, key: $key) }`, + variables: { + id: contactId, + key: customEventId + } + }; + return await this.makeRequest(body); + } + + // Update journey status + async updateJourneyStatus(action: string | undefined, journeyId: string | undefined): Promise { + let body: unknown = undefined; + if (action === 'Start') { + body = { + query: `mutation StartJourney($id: String!) { startJourney(id: $id) }`, + variables: { + id: journeyId + } + }; + } else { + body = { + query: `mutation StopJourney($id: String!) { stopJourney(id: $id) }`, + variables: { + id: journeyId + } + }; + } + return await this.makeRequest(body); + } + + // Add suppression filter + async createSuppressionFilter(emailAddress: string, reason: string | undefined): Promise { + const body: unknown = { + query: `mutation CreateSuppressionFilter($input: CreateSuppressionFilterInput!) { + createAccountSuppressionFilter(input: $input) { + id + localPart + domain + reason + } + }`, + variables: { + input: { + domain: emailAddress.split('@')[1], + localPart: emailAddress.split('@')[0], + reason + } + } + }; + return await this.makeRequest(body); + } + + // Create transaction + async createTransaction(groupName: string | undefined, fromEmail: string, fromName: string | undefined, toEmail: string, ccEmail: unknown[] | undefined, bccEmail: unknown[] | undefined, subject: string, replyToEmail: string | undefined, replyToName: string | undefined, variables: Record | undefined, templateId: string | undefined, mimeType: string | undefined, content: string | undefined, ignoreSuppressCheck: string): Promise { + const vars: { name: string, value: unknown }[] = []; + for (const prop in variables) { + if (prop && prop !== '') { + vars.push({ + name: prop.replace(/\s/g, '').replace(/[\W_-]+/g, ''), + value: variables[prop], + }); + } + } + + + const recips = []; + recips.push({ + emailAddress: toEmail, + type: 'TO', + variables: vars, + }); + ccEmail?.forEach((r: any) => { + recips.push({ + emailAddress: r.email, + type: 'CC', + variables: vars, + }); + }); + bccEmail?.forEach((r: any) => { + recips.push({ + emailAddress: r.email, + type: 'BCC', + variables: vars, + }); + }); + const body: unknown = { + query: `mutation createTransaction($input: CreateTransactionInput!) { createTransaction(input: $input) { emailAddress errorCode errorMsg requestId transactionId } }`, + variables: { + input: { + content: { + templateId: templateId ? templateId : null, + contentBodies: !templateId + ? [ + { + bodyContent: content?.replace(/\[\[/g, '{{').replace(/\]\]/g, '}}'), + mimeType: mimeType, + }, + ] + : null, + }, + groupName: groupName, + header: { + from: { + emailAddress: fromEmail, + name: fromName, + }, + replyTo: { + emailAddress: replyToEmail ? replyToEmail : '', + name: replyToName ? replyToName : '', + }, + subject, + }, + recipients: recips, + settings: { + ignoreSuppressCheck: ignoreSuppressCheck === 'true' ? true : false, + }, + }, + } + }; + return await this.makeRequest(body); + } + //#endregion + + // #region Utilities + getWebhookName(): string { + return `ActivePieces-${Date.now()}`; + } + + getWebhookEvent(type: string, parameters: any): Array<{ entityType: unknown; eventType: string }> { + switch (type) { + case 'contactAdded': + return [{ entityType: null, eventType: 'CONTACT_ADDED' }]; + case 'campaignSendFinished': + return [{ entityType: null, eventType: 'CAMPAIGN_SENT' }]; + case 'campaignSendStarted': + return [{ entityType: null, eventType: 'STARTED_CAMPAIGN_SEND' }]; + case 'contactBounced': { + let entityType = null; + if (parameters.entity !== 'BOTH') { + entityType = parameters.entity; + } + if (parameters.type === 'Any') { + return [ + { entityType, eventType: 'BLOCK' }, + { entityType, eventType: 'BLOCK_CONTENT' }, + { entityType, eventType: 'BLOCK_SENDER' }, + { entityType, eventType: 'HARD' }, + { entityType, eventType: 'SOFT' }, + { entityType, eventType: 'SOFT_DNS' }, + { entityType, eventType: 'SOFT_IP' }, + { entityType, eventType: 'SOFT_SENDER_AUTH' }, + ]; + } else { + return [{ entityType, eventType: parameters.type }]; + } + } + case 'contactClicked': { + let entityType = null; + if (parameters.entity !== 'BOTH') { + entityType = parameters.entity; + } + return [{ entityType, eventType: 'CLICK' }]; + } + case 'contactReplied': { + let entityType = null; + if (parameters.entity !== 'BOTH') { + entityType = parameters.entity; + } + return [{ entityType, eventType: 'REPLIED' }]; + } + case 'contactGroupUpdated': + return [{ + entityType: null, + eventType: + parameters.action === 'Add' ? 'GROUP_ADDED' : 'GROUP_REMOVED', + }]; + case 'contactNoteAdded': + return [{ entityType: null, eventType: 'NOTE_ADDED' }]; + case 'contactOpened': { + let entityType = null; + if (parameters.entity !== 'BOTH') { + entityType = parameters.entity; + } + return [{ entityType, eventType: 'OPEN' }]; + } + case 'contactStatusUpdated': { + if (parameters.status === 'Any') { + return [ + { entityType: null, eventType: 'SUBSCRIBED' }, + { entityType: null, eventType: 'UNSUBSCRIBED' }, + ]; + } else if (parameters.status === 'ACTIVE') { + return [ + { entityType: null, eventType: 'SUBSCRIBED' } + ]; + } else { + return [ + { entityType: null, eventType: 'UNSUBSCRIBED' }, + ]; + } + } + case 'contactTagUpdated': { + return [{ + entityType: null, + eventType: parameters.action === 'Add' ? 'TAG_ADDED' : 'TAG_REMOVED', + }]; + } + case 'contactUnsubscribed': + return [{ entityType: null, eventType: 'UNSUBSCRIBED' }]; + case 'contactUpdated': + return [{ entityType: null, eventType: 'FIELD_UPDATED' }]; + case 'formSubmitted': + return [ + { entityType: 'FORM', eventType: 'SUBMITTED_COMPLETE_FORM' }, + { entityType: 'FORM', eventType: 'SUBMITTED_PARTIAL_FORM' }, + ]; + case 'landingPageCtaPerformed': + return [{ entityType: 'PAGE', eventType: 'CTA' }]; + case 'surveySubmitted': + return [ + { entityType: 'SURVEY', eventType: 'SUBMITTED_COMPLETE_FORM' }, + { entityType: 'SURVEY', eventType: 'SUBMITTED_PARTIAL_FORM' }, + ]; + case 'transactionCreated': + return [{ entityType: null, eventType: 'CREATED' }]; + case 'transactionSent': + return [{ entityType: null, eventType: 'PROCESSED' }]; + default: return []; + } + } + + generateWebhookInput(type: string, parameters: any): any { + const conditions = []; + if (parameters.audienceId) { + conditions.push({ + dataFieldId: null, + fieldName: 'audience', + operator: 'IF_TRUE', + value: parameters.audienceId, + }); + } + if (parameters.groupId) { + conditions.push({ + dataFieldId: null, + fieldName: 'groups', + operator: 'CONTAINS', + value: parameters.groupId, + }); + } + if (parameters.tagId) { + conditions.push({ + dataFieldId: null, + fieldName: 'tags', + operator: 'CONTAINS', + value: parameters.tagId, + }); + } + + if (type === 'contactClicked' || type === 'contactOpened') { + let campaignString = ''; + let value = null; + if (parameters.campaignType === 'Any') { + campaignString = `Campaign.AnyLast5.${type === 'contactClicked' ? 'Clicked' : 'Opened' + }`; + } + if (parameters.campaignType === 'All') { + campaignString = `Campaign.AllLast5.${type === 'contactClicked' ? 'Clicked' : 'Opened' + }`; + } + if (parameters.campaignType === 'AnyX') { + campaignString = `Campaign.AnyWithinLast.${type === 'contactClicked' ? 'Clicked' : 'Opened' + }`; + value = `${parameters.campaignScope.campaignRange}${parameters.campaignScope.campaignPeriod}`; + } + if (parameters.campaignType === 'Specific') { + campaignString = `Campaign.${parameters.campaignScope.campaignId}.${type === 'contactClicked' ? 'Clicked' : 'Opened' + }${type === 'contactClicked' && parameters.campaignScope.linkId + ? '.' + parameters.campaignScope.linkId + : '' + }`; + } + + conditions.push({ + dataFieldId: null, + fieldName: campaignString, + operator: 'IF_TRUE', + value, + }); + } + + if (type === 'contactStatusUpdated' && parameters.status !== 'Any') { + conditions.push({ + dataFieldId: null, + fieldName: 'Status', + operator: 'EQUAL', + value: parameters.status, + }); + } + + if (type === 'formSubmitted' && parameters.formId) { + conditions.push({ + dataFieldId: null, + fieldName: `Form.${parameters.formId}.Submitted`, + operator: 'IF_TRUE', + value: null, + }); + } + + if (type === 'landingPageCtaPerformed' && parameters.pageId) { + conditions.push({ + dataFieldId: null, + fieldName: `Page.${parameters.pageId}.CTA`, + operator: 'IF_TRUE', + value: null, + }); + } + + if (type === 'surveySubmitted' && parameters.surveyId) { + conditions.push({ + dataFieldId: null, + fieldName: `Survey.${parameters.surveyId}.Submitted`, + operator: 'IF_TRUE', + value: null, + }); + } + + return conditions.length !== 0 + ? { + operator: 'AND', + conditions, + conditionGroups: [], + } + : null; + } + + getHelpText(fieldName: string, dataType: string, defaultValue: string, isGdpr = false): string { + switch (dataType) { + case 'BIRTHDAY': { + return `This will set the value for the ${fieldName} data field.`; + } + case 'DATE': { + return `This will set the value for the ${fieldName} data field.`; + } + case 'DATE_TIME': { + return `This will set the value for the ${fieldName} data field.`; + } + case 'NUMBER': { + if (isGdpr) { + return `Enter 1 to allow and 0 to not allow. This will set the value for the ${fieldName}* GDPR permission.`; + } else { + return `This will set the value for the ${fieldName} data field. Must contain only numbers. (255 character limit)`; + } + } + case 'ZIP_CODE': { + return `U.S. Only. This will set the value for the ${fieldName} data field. Format: ##### or #####-#### (10 character limit)`; + } + case 'PHONE': { + return `This will set the value for the ${fieldName} data field.`; + } + case 'COUNTRY': + case 'EMAIL': + case 'IMAGE': + case 'INT_PHONE': + case 'STATE': + case 'TEXT': + case 'URL': { + return `This will set the value for the ${fieldName} data field.${defaultValue ? ' Default value: ' + defaultValue : '' + } (255 character limit)`; + } + default: return ''; + } + }; + + isISODate(str: string): boolean { + const isoFormat = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?$/; + return isoFormat.test(str) && !isNaN(Date.parse(str)); + } + + formatISODateToYMD(isoString: string): string { + console.log(isoString); + const date = new Date(isoString); + const year = date.getFullYear(); + const month = date.getMonth() + 1; // Months are zero-based + const day = date.getDate(); + + return `${year}/${month}/${day}`; + } + //#endregion +} diff --git a/packages/pieces/community/tarvent/src/lib/common/constants.ts b/packages/pieces/community/tarvent/src/lib/common/constants.ts new file mode 100644 index 0000000..1f4a6cf --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/common/constants.ts @@ -0,0 +1,16 @@ +// export const WEBHOOK_SOURCES = ['public', 'admin', 'api', 'system']; + +export enum CUSTOM_FIELD_TYPE { + TEXT = 'text', + DROPDOWN = 'dropdown', + TEXTAREA = 'textarea', + NUMBER = 'number', + MONEY = 'currency', + DATE = 'date', + DATETIME = 'datetime', + LIST_BOX = 'listbox', + MULTISELECT = 'multiselect', + RADIO = 'radio', + CHECKBOX = 'checkbox', + HIDDEN = 'hidden', +} diff --git a/packages/pieces/community/tarvent/src/lib/common/index.ts b/packages/pieces/community/tarvent/src/lib/common/index.ts new file mode 100644 index 0000000..3bd21bc --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/common/index.ts @@ -0,0 +1,604 @@ +import { DynamicPropsValue, PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../../'; +import { TarventClient } from './client'; + +export function makeClient(auth: PiecePropValueSchema) { + const client = new TarventClient(auth.accountId, auth.apiKey); + return client; +} + +export const tarventCommon = { + customEventId: (required = false, description = '') => Property.Dropdown({ + displayName: 'Custom event', + description, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listCustomEvents(); + + return { + disabled: false, + options: res.data.customApiEvents.nodes.map((customApiEvents) => { + return { + label: customApiEvents.name, + value: customApiEvents.key, + }; + }), + }; + }, + }), + campaignId: (required = false, description = '', ignoreStatus = false, isEvent = false) => + Property.Dropdown({ + displayName: 'Campaign', + description, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listCampaigns(ignoreStatus, isEvent); + console.log(res); + return { + disabled: false, + options: res.data.campaigns.nodes.map((campaigns) => { + return { + label: campaigns.name, + value: campaigns.id, + }; + }), + }; + }, + }), + campaignLinkId: (required = false) => + Property.Dropdown({ + displayName: 'Campaign link', + description: 'Only used if campaign type is set to "Specific". If specified, the trigger will only fire if a contact clicks the selected link.', + required, + refreshers: ['campaignId'], + options: async ({ auth, campaignId }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listCampaignLinks(campaignId as string); + + return { + disabled: false, + options: res.data.campaignLinks.nodes.map((link) => { + return { + label: link.url, + value: link.id, + }; + }), + }; + }, + }), + journeyId: (required = false, description = '') => + Property.Dropdown({ + displayName: 'Journey', + description, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listJourneys(); + + return { + disabled: false, + options: res.data.journeys.nodes.map((journey) => { + return { + label: journey.name, + value: journey.id, + }; + }), + }; + }, + }), + campaignScope: Property.DynamicProperties({ + displayName: 'Campaign scope', + refreshers: ['campaignType'], + required: false, + props: async ({ campaignType }) => { + const prop: DynamicPropsValue = {}; + + if (campaignType as unknown === 'AnyX') { + prop['campaignRange'] = Property.Number({ + displayName: 'Range', + required: true, + }); + prop['campaignPeriod'] = Property.StaticDropdown({ + displayName: 'Period', + description: '', + required: true, + options: { + options: [ + { + label: 'Hour(s)', + + value: 'h', + }, + { + label: 'Day(s)', + value: 'd', + }, + { + label: 'Week(s)', + value: 'w', + }, + { + label: 'Month(s)', + value: 'm', + }, + ], + }, + defaultValue: 'd' + }); + } + + return prop; + }, + }), + audienceId: (required = false, description = '') => + Property.Dropdown({ + displayName: 'Audience', + description, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listAudiences(); + + return { + disabled: false, + options: res.data.audiences.nodes.map((audience) => { + return { + label: audience.name, + value: audience.id, + }; + }), + }; + }, + }), + audienceGroupId: (required = false, description = '') => + Property.Dropdown({ + displayName: 'Audience group', + description, + required, + refreshers: ['audienceId'], + options: async ({ auth, audienceId }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + if (!audienceId) { + return { + disabled: true, + placeholder: 'Please select an audience first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listAudienceGroups(audienceId as string); + + return { + disabled: false, + options: res.data.audienceGroups.nodes.map((group) => { + return { + label: group.name, + value: group.id, + }; + }), + }; + }, + }), + audienceFormId: (required = false, description = '') => + Property.Dropdown({ + displayName: 'Audience form', + description, + required, + refreshers: ['audienceId'], + options: async ({ auth, audienceId }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + if (!audienceId) { + return { + disabled: true, + placeholder: 'Please select an audience first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listAudienceForms(audienceId as string); + + return { + disabled: false, + options: res.data.forms.nodes.map((form) => { + return { + label: form.name, + value: form.id, + }; + }), + }; + }, + }), + audienceGroupIds: (required = false, description = '') => + Property.MultiSelectDropdown({ + displayName: 'Audience group', + description, + required, + refreshers: ['audienceId'], + options: async ({ auth, audienceId }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + if (!audienceId) { + return { + disabled: true, + placeholder: 'Please select an audience first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listAudienceGroups(audienceId as string); + + return { + disabled: false, + options: res.data.audienceGroups.nodes.map((group) => { + return { + label: group.name, + value: group.id, + }; + }), + }; + }, + }), + audienceDataFields: Property.DynamicProperties({ + displayName: 'Data fields', + refreshers: ['audienceId'], + required: false, + props: async ({ auth, audienceId }) => { + if (!auth) return {}; + + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listAudienceDataFields(audienceId as unknown); + + const fields: DynamicPropsValue = {}; + const fieldsFromApi = res.data.audienceDataFields.nodes; + + if (fieldsFromApi) { + fieldsFromApi.forEach((f) => { + if (!f.isSystem && !f.isGdprField) { + switch (f.dataType) { + case 'NUMBER': { + fields[f.id] = Property.Number({ + displayName: f.labelText, + required: f.required, + description: client.getHelpText( + f.labelText, + f.dataType, + f.defaultValue + ), + }); + break; + } + case 'DATE': + case 'DATE_TIME': { + fields[f.id] = Property.DateTime({ + displayName: f.labelText, + required: f.required, + description: client.getHelpText( + f.labelText, + f.dataType, + f.defaultValue + ), + }); + break; + } + default: { + fields[f.id] = Property.ShortText({ + displayName: f.labelText, + required: f.required, + description: client.getHelpText( + f.labelText, + f.dataType, + f.defaultValue + ), + }); + break; + } + } + + } + if (f.isGdprField) { + fields[f.id] = { + displayName: f.labelText.replace('GDPR_', '') + ' (GDPR Permission)', + required: f.required, + description: client.getHelpText( + f.labelText.replace('GDPR_', ''), + f.dataType, + f.defaultValue, + true + ), + }; + } + }); + } + return fields; + }, + }), + + contactId: Property.ShortText({ + displayName: 'Contact ID', + description: 'Find this in the edit contact dialog on the details page.', + required: true, + defaultValue: '', + }), + name: (displayName: string, required: boolean, description = '') => + Property.ShortText({ + displayName, + description, + required, + defaultValue: '', + }), + description: (displayName: string, required: boolean, description = '') => + Property.LongText({ + displayName, + description, + required, + defaultValue: '', + }), + include: Property.StaticDropdown({ + displayName: 'Include all contact data', + description: 'If not included, only the contact ID, email and unique identifier will be passed back.', + required: true, + defaultValue: 'BASIC', + options: { + options: [ + { + label: 'Include', + + value: 'EXTENDED', + }, + { + label: 'Do not include', + value: 'BASIC', + }, + ], + }, + }), + entity: Property.StaticDropdown({ + displayName: 'Email type', + description: 'Select if a campaign, transactional email or both should fire the trigger.', + required: true, + defaultValue: 'BOTH', + options: { + options: [ + { + label: 'Both', + + value: 'BOTH', + }, + { + label: 'Campaign', + value: 'CAMPAIGN', + }, + { + label: 'Transaction', + value: 'TRANSACTION', + }, + ], + }, + }), + tagId: (required = false, description = '') => + Property.Dropdown({ + displayName: 'Tags', + description, + required, + refreshers: [], + options: async ({ auth, searchField }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listTags(); + + console.log(res, searchField) + + return { + disabled: false, + options: res.data.tags.nodes.map((tag) => { + return { + label: tag.name, + value: tag.name, + }; + }), + }; + }, + }), + tagIds: (required = false, description = '') => + Property.MultiSelectDropdown({ + displayName: 'Tags', + description, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listTags(); + + return { + disabled: false, + options: res.data.tags.nodes.map((tag) => { + return { + label: tag.name, + value: tag.name, + }; + }), + }; + }, + }), + txGroupName: (required = false, description = '') => + Property.Dropdown({ + displayName: 'Transaction group name', + description, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listTxGroupNames(); + console.log(res); + return { + disabled: false, + options: res.data.transactionGroupNames ? res.data.transactionGroupNames.map((name) => { + return { + label: name, + value: name, + }; + }) : [], + }; + }, + }), + templateId: (required = false, description = '') => + Property.Dropdown({ + displayName: 'Template', + description, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listTemplates(); + + return { + disabled: false, + options: res.data.templates.nodes.map((template) => { + return { + label: template.name, + value: template.id, + }; + }), + }; + }, + }), + landingPageId: Property.Dropdown({ + displayName: 'Landing page', + description: 'If specified, the trigger will only fire if CTA (call-to-action) is performed on the selected landing page.', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listLandingPages(); + + return { + disabled: false, + options: res.data.landingPages.nodes.map((lp) => { + return { + label: lp.name, + value: lp.id, + }; + }), + }; + }, + }), + surveyId: Property.Dropdown({ + displayName: 'Survey', + description: 'If specified, the trigger will only fire if the selected survey is submitted.', + required: false, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first', + options: [], + }; + } + const client = makeClient(auth as PiecePropValueSchema); + const res = await client.listSurveys(); + + return { + disabled: false, + options: res.data.surveys.nodes.map((s) => { + return { + label: s.name, + value: s.id, + }; + }), + }; + }, + }), +}; diff --git a/packages/pieces/community/tarvent/src/lib/common/types.ts b/packages/pieces/community/tarvent/src/lib/common/types.ts new file mode 100644 index 0000000..7a6ced7 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/common/types.ts @@ -0,0 +1,371 @@ +export type CreateWebhookResponse = { + data: { + createWebhook: { + id: string; + } + } + +}; + +export type ListAudiencesResponse = { + data: { + audiences: { + nodes: { + name: string; + id: string; + }[] + } + }; +}; + +export type ListAudiencesAdvResponse = { + data: { + audiences: { + nodes: { + name: string; + id: string; + companyName: string; + streetAddress: string; + streetAddress2: string; + addressLocality: string; + addressRegion: string; + postalCode: string; + addressCountry: string; + phone: string; + website: string; + totalContacts: number; + totalActiveContacts: number; + totalUnconfirmedContacts: number; + totalUndeliverableContacts: number; + totalUnsubscribedContacts: number; + totalComplaintContacts: number; + totalSuppressedContacts: number; + tags: string[]; + customKeyDataField: { id: string, labelText: string }; + createdUtc: string; + lastModifiedUtc: string; + }[] + } + }; +}; + + +export type ListAudienceGroupResponse = { + data: { + audienceGroups: { + nodes: { + name: string; + id: string; + }[] + } + }; +}; + +export type ListAudienceGroupAdvResponse = { + data: { + audienceGroups: { + nodes: { + name: string; + id: string; + description: string; + isPublic: boolean; + }[] + } + }; +}; + +export type ListAudienceDataFieldsResponse = { + data: { + audienceDataFields: { + nodes: { + labelText: string; + id: string; + dataType: string; + required: boolean; + isSystem: boolean; + defaultValue: string; + mergeTag: string; + isPrimaryKey: boolean; + isGdprField: boolean; + category: boolean; + }[] + } + }; +}; + + +export type ListAudienceFormsResponse = { + data: { + forms: { + nodes: { + name: string; + id: string; + }[] + } + }; +}; + +export type ListTagsResponse = { + data: { + tags: { + nodes: { + name: string; + }[] + } + }; +}; + +export type ListCampaignsResponse = { + data: { + campaigns: { + nodes: { + id: string; + name: string; + }[] + } + }; +}; + +export type ListCampaignsAdvResponse = { + data: { + campaigns: { + nodes: { + id: string; + name: string; + tags: string[]; + audienceId: string; + description: string; + enableMvTesting: boolean; + mvWinType: string; + timeJumper: boolean; + sendStatus: string; + scheduledToSendUtc: string; + createdUtc: string; + modifiedUtc: string; + }[] + } + }; +}; + +export type ListCampaignLinksResponse = { + data: { + campaignLinks: { + nodes: { + id: string; + url: string; + track: boolean; + entityName: string; + entityType: string; + formType: string; + }[] + } + }; +}; + +export type ListJourneysResponse = { + data: { + journeys: { + nodes: { + id: string; + name: string; + }[], + pageInfo: { + hasNextPage: boolean; + endCursor: string | null; + } + } + }; +}; + +export type ListJourneysAdvResponse = { + data: { + journeys: { + nodes: { + id: string; + name: string; + tags: string[]; + audienceId: string; + description: string; + reEntryType: string; + status: string; + totalEmailNodes: string; + totalNotificationEmailNodes: string; + totalSiteNotificationNodes: string; + totalSMSNodes: string; + createdUtc: string; + modifiedUtc: string; + }[], + } + }; +}; + +export type ListContactResponse = { + data: { + contact: { + id: string; + email: string; + firstName: string; + lastName: string; + tags: string[]; + groups: { id: string, name: string }[]; + streetAddress: string; + streetAddress2: string; + addressLocality: string; + addressRegion: string; + postalCode: string; + addressCountry: string; + profileFields: { dataField: {id: string, labelText: string }, value: string }[]; + modifiedUtc: string; + createdUtc: string; + longitude: string; + latitude: string; + timeZone: string; + language: string; + sendFormat: string; + status: string; + optInUtc: string; + confirmedUtc: string; + optOUtUtc: string; + } + }; +}; + + +export type ListLandingPagesResponse = { + data: { + landingPages: { + nodes: { + id: string; + name: string; + }[] + } + }; +}; + +export type ListSurveysResponse = { + data: { + surveys: { + nodes: { + id: string; + name: string; + }[] + } + }; +}; + +export type ListTemplatesResponse = { + data: { + templates: { + nodes: { + id: string; + name: string; + }[] + } + }; +}; + +export type ListTxGroupNamesResponse = { + data: { + transactionGroupNames: string[] + }; +}; + +export type ListCustomEventsResponse = { + data: { + customApiEvents: { + nodes: { + id: string; + key: string; + name: string; + }[] + } + }; +}; + +export type ListCustomEventsAdvResponse = { + data: { + customApiEvents: { + nodes: { + id: string; + key: string; + name: string; + createdUtc: string; + modifiedUtc: string; + }[] + } + }; +}; + +export type CreateContactResponse = { + data: { + createContact: { + id: string; + email: string; + firstName: string; + lastName: string; + tags: string[]; + groups: { id: string, name: string }[]; + streetAddress: string; + streetAddress2: string; + addressLocality: string; + addressRegion: string; + postalCode: string; + addressCountry: string; + profileFields: { dataField: {id: string, labelText: string }, value: string }[]; + modifiedUtc: string; + createdUtc: string; + } + } +}; + +export type CreateAudienceGroupResponse = { + data: { + createAudienceGroup: { + id: string; + name: string; + description: string; + isPublic: boolean; + isDynamic: boolean; + } + } +}; + +export type CreateContactNoteResponse = { + data: { + createContactNote: { + id: string; + } + } +}; + +export type CreateGroupContactResponse = { + data: { + createGroupContact: { + id: string; + } + } +}; + +export type DeleteGroupContactResponse = { + data: { + deleteGroupContact: { + id: string; + } + } +}; + +export type CreateSuppressionFilterResponse = { + data: { + createAccountSuppressionFilter: { + id: string; + localPart: string; + domain: string; + reason: string; + } + } +}; + +export enum ContactStatus { + ACTIVE = 'ACTIVE', + OPT_OUT = 'OPT_OUT' +} diff --git a/packages/pieces/community/tarvent/src/lib/triggers/campaign-send-finished.ts b/packages/pieces/community/tarvent/src/lib/triggers/campaign-send-finished.ts new file mode 100644 index 0000000..b7913fd --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/campaign-send-finished.ts @@ -0,0 +1,88 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; +import { CreateWebhookResponse } from '../common/types'; + +export const campaignSendFinishedTrigger = createTrigger({ + auth: tarventAuth, + name: 'tarvent_campaign_send_finished', + displayName: 'Campaign Sent', + description: 'Triggers when a campaign has been sent to a contact. WARNING: This will fire for every contact the campaign is sent to, please be careful using this trigger.', + type: TriggerStrategy.WEBHOOK, + props: { + include: tarventCommon.include, + audienceId: tarventCommon.audienceId(false, 'If specified, the trigger will only fire if contact is in the selected audience.'), + groupId: tarventCommon.audienceGroupId(false, 'If specified, the trigger will only fire if contact is in the selected group.'), + tagId: tarventCommon.tagId(false, 'If specified, the trigger will only fire if contact has the selected tag.') + }, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.createWebhook(context, 'campaignSendFinished'); + await context.store.put('tarvent_campaign_send_finished', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'tarvent_campaign_send_finished', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.deleteWebhook(webhook.data.createWebhook.id); + } + }, + sampleData: { + id: '000000000000000000', + dateUtc: '2022-09-27T17:37:26.482913Z', + accountId: '000000000000000000', + eventType: 1003, + initiator: { + source: 2, + ip: '0.0.0.0', + protocol: 'IPv4', + httpVerb: 'POST', + device: 'Desktop', + software: 'Outlook', + os: 'Windows 11', + referrer: 'https://gmail.com', + }, + payload: { + audienceId: '000000000000000000', + contact: { + id: '000000000000000000', + key: 'Kayla@tarvent.com', + email: 'Kayla@tarvent.com', + status: 1, + rating: 3, + firstName: 'Kayla', + lastName: 'Johnson', + streetAddress: '165 Caprice Court', + streetAddress2: 'Suite A', + addressLocality: 'Castle Rock', + addressRegion: 'Colorado', + postalCode: '80109', + addressCountry: 'United States', + latitude: 39.38363820960583, + longitude: -104.86229586128452, + timeZone: 'Mountain Standard Time', + language: 'en', + sendFormat: 1, + optInUtc: '2022-08-28T17:37:26.6236851Z', + optInSource: 6, + optInIp: '0.0.0.0', + confirmedUtc: '2022-08-29T17:37:26.6236972Z', + confirmedIp: '0.0.0.0', + optOutUtc: null, + optOutSource: null, + optOutIp: null, + optOutReason: '', + groups: ['359949389556096655', '359949389556097786'], + tags: ['TarventTest', 'TarventTest2', 'TarventTest3'], + profileFields: null, + createdUtc: '2022-08-28T17:37:26.6237782Z', + modifiedUtc: '2022-09-17T17:37:26.6237797Z', + }, + }, + } +}); diff --git a/packages/pieces/community/tarvent/src/lib/triggers/contact-added.ts b/packages/pieces/community/tarvent/src/lib/triggers/contact-added.ts new file mode 100644 index 0000000..09535c6 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/contact-added.ts @@ -0,0 +1,95 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; +import { CreateWebhookResponse } from '../common/types'; + +export const contactAddedTrigger = createTrigger({ + auth: tarventAuth, + name: 'tarvent_contact_added', + displayName: 'Contact Added', + description: 'Triggers when a contact is added to the selected audience.', + type: TriggerStrategy.WEBHOOK, + props: { + include: tarventCommon.include, + audienceId: tarventCommon.audienceId(false, 'If specified, the trigger will only fire if contact is in the selected audience.'), + groupId: tarventCommon.audienceGroupId(false, 'If specified, the trigger will only fire if contact is in the selected group.'), + tagId: tarventCommon.tagId(false, 'If specified, the trigger will only fire if contact has the selected tag.') + }, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.createWebhook(context, 'contactAdded'); + await context.store.put('tarvent_contact_added', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'tarvent_contact_added', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.deleteWebhook(webhook.data.createWebhook.id); + } + }, + sampleData: { + "id": "000000000000000000", + "dateUtc": "2022-09-27T17:37:26.482913Z", + "accountId": "000000000000000000", + "eventType": 1003, + "initiator": { + "source": 2, + "ip": "0.0.0.0", + "protocol": "IPv4", + "httpVerb": "POST", + "device": "Desktop", + "software": "Outlook", + "os": "Windows 11", + "referrer": "https://gmail.com" + }, + "payload": { + "audienceId": "000000000000000000", + "contact": { + "id": "000000000000000000", + "key": "Kayla@tarvent.com", + "email": "Kayla@tarvent.com", + "status": 1, + "rating": 3, + "firstName": "Kayla", + "lastName": "Johnson", + "streetAddress": "165 Caprice Court", + "streetAddress2": "Suite A", + "addressLocality": "Castle Rock", + "addressRegion": "Colorado", + "postalCode": "80109", + "addressCountry": "United States", + "latitude": 39.38363820960583, + "longitude": -104.86229586128452, + "timeZone": "Mountain Standard Time", + "language": "en", + "sendFormat": 1, + "optInUtc": "2022-08-28T17:37:26.6236851Z", + "optInSource": 6, + "optInIp": "0.0.0.0", + "confirmedUtc": "2022-08-29T17:37:26.6236972Z", + "confirmedIp": "0.0.0.0", + "optOutUtc": null, + "optOutSource": null, + "optOutIp": null, + "optOutReason": "", + "groups": [ + "359949389556096655", + "359949389556097786" + ], + "tags": [ + "TarventTest", + "TarventTest2", + "TarventTest3" + ], + "profileFields": null, + "createdUtc": "2022-08-28T17:37:26.6237782Z", + "modifiedUtc": "2022-09-17T17:37:26.6237797Z" + } + } + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/triggers/contact-bounced.ts b/packages/pieces/community/tarvent/src/lib/triggers/contact-bounced.ts new file mode 100644 index 0000000..0204267 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/contact-bounced.ts @@ -0,0 +1,112 @@ +import { tarventAuth } from '../..'; +import { Property, TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { makeClient, tarventCommon } from '../common'; +import { CreateWebhookResponse } from '../common/types'; + +export const contactBouncedTrigger = createTrigger({ + auth: tarventAuth, + name: 'tarvent_contact_bounced', + displayName: 'Campaign Or Transactional Email Bounced', + description: 'Triggers when a campaign or transactional email bounces (is rejected) for a contact.', + type: TriggerStrategy.WEBHOOK, + props: { + include: tarventCommon.include, + entity: tarventCommon.entity, + type: Property.StaticDropdown({ + displayName: 'Bounce type', + description: 'Select which bounce type should fire the trigger.', + required: true, + defaultValue: 'Any', + options: { + options: [ + { label: 'Any', value: 'Any' }, + { label: 'Block', value: 'BLOCK' }, + { label: 'Content block', value: 'BLOCK_CONTENT' }, + { label: 'Sender block', value: 'BLOCK_SENDER' }, + { label: 'Hard bounce', value: 'HARD' }, + { label: 'Soft bounce', value: 'SOFT' }, + { label: 'DNS failure', value: 'SOFT_DNS' }, + { label: 'IP Block', value: 'SOFT_IP' }, + { label: 'Sender authentication', value: 'SOFT_SENDER_AUTH' } + ], + }, + }), + audienceId: tarventCommon.audienceId(false, 'If specified, the trigger will only fire if contact is in the selected audience.'), + groupId: tarventCommon.audienceGroupId(false, 'If specified, the trigger will only fire if contact is in the selected group.'), + tagId: tarventCommon.tagId(false, 'If specified, the trigger will only fire if contact has the selected tag.') + }, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.createWebhook(context, 'contactBounced'); + await context.store.put('tarvent_contact_bounced', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'tarvent_contact_bounced', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.deleteWebhook(webhook.data.createWebhook.id); + } + }, + sampleData: { + id: '000000000000000000', + dateUtc: '2022-09-27T17:37:26.482913Z', + accountId: '000000000000000000', + eventType: 1003, + initiator: { + source: 2, + ip: '0.0.0.0', + protocol: 'IPv4', + httpVerb: 'POST', + device: 'Desktop', + software: 'Outlook', + os: 'Windows 11', + referrer: 'https://gmail.com', + }, + payload: { + emailId: '000000000000000000', + bounce: { + type: 100, + response: 'smtp;550 5.1.1 The email account that you tried to reach does not exist.' + }, + contact: { + id: '000000000000000000', + key: 'Kayla@tarvent.com', + email: 'Kayla@tarvent.com', + status: 1, + rating: 3, + firstName: 'Kayla', + lastName: 'Johnson', + streetAddress: '165 Caprice Court', + streetAddress2: 'Suite A', + addressLocality: 'Castle Rock', + addressRegion: 'Colorado', + postalCode: '80109', + addressCountry: 'United States', + latitude: 39.38363820960583, + longitude: -104.86229586128452, + timeZone: 'Mountain Standard Time', + language: 'en', + sendFormat: 1, + optInUtc: '2022-08-28T17:37:26.6236851Z', + optInSource: 6, + optInIp: '0.0.0.0', + confirmedUtc: '2022-08-29T17:37:26.6236972Z', + confirmedIp: '0.0.0.0', + optOutUtc: null, + optOutSource: null, + optOutIp: null, + optOutReason: '', + groups: ['359949389556096655', '359949389556097786'], + tags: ['TarventTest', 'TarventTest2', 'TarventTest3'], + profileFields: null, + createdUtc: '2022-08-28T17:37:26.6237782Z', + modifiedUtc: '2022-09-17T17:37:26.6237797Z', + }, + }, + } +}); diff --git a/packages/pieces/community/tarvent/src/lib/triggers/contact-clicked.ts b/packages/pieces/community/tarvent/src/lib/triggers/contact-clicked.ts new file mode 100644 index 0000000..739df67 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/contact-clicked.ts @@ -0,0 +1,124 @@ +import { tarventAuth } from '../..'; +import { Property, TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { makeClient, tarventCommon } from '../common'; +import { CreateWebhookResponse } from '../common/types'; + +export const contactClickedTrigger = createTrigger({ + auth: tarventAuth, + name: 'tarvent_contact_clicked', + displayName: 'Campaign Or Transactional Link Clicked', + description: 'Triggers when a link within a campaign or transactional email is clicked by a recipient.', + type: TriggerStrategy.WEBHOOK, + props: { + include: tarventCommon.include, + entity: tarventCommon.entity, + campaignType: Property.StaticDropdown({ + displayName: 'Campaign', + description: 'Select what campaign(s) should fire the trigger.', + required: true, + defaultValue: 'BOTH', + options: { + options: [ + { + label: 'Any of the last 5 campaigns', + + value: 'Any', + }, + { + label: 'All of the last 5 campaigns', + + value: 'All', + }, + { + label: 'Any campaigns within last...', + value: 'AnyX', + }, + { + label: 'A specific campaign', + value: 'Specific', + }, + ], + }, + }), + campaignScope: tarventCommon.campaignScope, + campaignId: tarventCommon.campaignId(false, 'Only used if campaign type is set to "Specific"', false, true), + linkId: tarventCommon.campaignLinkId(false), + audienceId: tarventCommon.audienceId(false, 'If specified, the trigger will only fire if contact is in the selected audience.'), + groupId: tarventCommon.audienceGroupId(false, 'If specified, the trigger will only fire if contact is in the selected group.'), + tagId: tarventCommon.tagId(false, 'If specified, the trigger will only fire if contact has the selected tag.') + }, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.createWebhook(context, 'contactClicked'); + await context.store.put('tarvent_contact_clicked', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'tarvent_contact_clicked', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.deleteWebhook(webhook.data.createWebhook.id); + } + }, + sampleData: { + id: '000000000000000000', + dateUtc: '2022-09-27T17:37:26.482913Z', + accountId: '000000000000000000', + eventType: 1003, + initiator: { + source: 2, + ip: '0.0.0.0', + protocol: 'IPv4', + httpVerb: 'POST', + device: 'Desktop', + software: 'Outlook', + os: 'Windows 11', + referrer: 'https://gmail.com', + }, + payload: { + emailId: '000000000000000000', + link: { + id: '000000000000000000', + url: 'https://yourdomain.com/yourpage' + }, + contact: { + id: '000000000000000000', + key: 'Kayla@tarvent.com', + email: 'Kayla@tarvent.com', + status: 1, + rating: 3, + firstName: 'Kayla', + lastName: 'Johnson', + streetAddress: '165 Caprice Court', + streetAddress2: 'Suite A', + addressLocality: 'Castle Rock', + addressRegion: 'Colorado', + postalCode: '80109', + addressCountry: 'United States', + latitude: 39.38363820960583, + longitude: -104.86229586128452, + timeZone: 'Mountain Standard Time', + language: 'en', + sendFormat: 1, + optInUtc: '2022-08-28T17:37:26.6236851Z', + optInSource: 6, + optInIp: '0.0.0.0', + confirmedUtc: '2022-08-29T17:37:26.6236972Z', + confirmedIp: '0.0.0.0', + optOutUtc: null, + optOutSource: null, + optOutIp: null, + optOutReason: '', + groups: ['359949389556096655', '359949389556097786'], + tags: ['TarventTest', 'TarventTest2', 'TarventTest3'], + profileFields: null, + createdUtc: '2022-08-28T17:37:26.6237782Z', + modifiedUtc: '2022-09-17T17:37:26.6237797Z', + }, + }, + } +}); diff --git a/packages/pieces/community/tarvent/src/lib/triggers/contact-group-updated.ts b/packages/pieces/community/tarvent/src/lib/triggers/contact-group-updated.ts new file mode 100644 index 0000000..b0c5fc8 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/contact-group-updated.ts @@ -0,0 +1,113 @@ +import { Property, TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; +import { CreateWebhookResponse } from '../common/types'; + +export const contactGroupUpdatedTrigger = createTrigger({ + auth: tarventAuth, + name: 'tarvent_contact_group_updated', + displayName: 'Contact Added Or Removed From A Group', + description: 'Triggers when a contact is added or removed from a group.', + type: TriggerStrategy.WEBHOOK, + props: { + include: tarventCommon.include, + audienceId: tarventCommon.audienceId(false, 'If specified, the trigger will only fire if contact is in the selected audience.'), + groupId: tarventCommon.audienceGroupId(false, 'If specified, the trigger will only fire if contact is in the selected group.'), + tagId: tarventCommon.tagId(false, 'If specified, the trigger will only fire if contact has the selected tag.'), + action: Property.StaticDropdown({ + displayName: 'Contact action', + description: 'Select if the trigger should fire when a contact is added or removed.', + required: true, + options: { + options: [ + { + label: 'Added', + + value: 'Add', + }, + { + label: 'Removed', + value: 'Remove', + }, + ], + }, + }), + }, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.createWebhook(context, 'contactGroupUpdated'); + await context.store.put('tarvent_contact_group_updated', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'tarvent_contact_group_updated', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.deleteWebhook(webhook.data.createWebhook.id); + } + }, + sampleData: { + "id": "000000000000000000", + "dateUtc": "2022-09-27T17:37:26.482913Z", + "accountId": "000000000000000000", + "eventType": 1003, + "initiator": { + "source": 2, + "ip": "0.0.0.0", + "protocol": "IPv4", + "httpVerb": "POST", + "device": "Desktop", + "software": "Outlook", + "os": "Windows 11", + "referrer": "https://gmail.com" + }, + "payload": { + "audienceId": "000000000000000000", + "contact": { + "id": "000000000000000000", + "key": "Kayla@tarvent.com", + "email": "Kayla@tarvent.com", + "status": 1, + "rating": 3, + "firstName": "Kayla", + "lastName": "Johnson", + "streetAddress": "165 Caprice Court", + "streetAddress2": "Suite A", + "addressLocality": "Castle Rock", + "addressRegion": "Colorado", + "postalCode": "80109", + "addressCountry": "United States", + "latitude": 39.38363820960583, + "longitude": -104.86229586128452, + "timeZone": "Mountain Standard Time", + "language": "en", + "sendFormat": 1, + "optInUtc": "2022-08-28T17:37:26.6236851Z", + "optInSource": 6, + "optInIp": "0.0.0.0", + "confirmedUtc": "2022-08-29T17:37:26.6236972Z", + "confirmedIp": "0.0.0.0", + "optOutUtc": null, + "optOutSource": null, + "optOutIp": null, + "optOutReason": "", + "groups": [ + "359949389556096655", + "359949389556097786" + ], + "tags": [ + "TarventTest", + "TarventTest2", + "TarventTest3" + ], + "profileFields": null, + "createdUtc": "2022-08-28T17:37:26.6237782Z", + "modifiedUtc": "2022-09-17T17:37:26.6237797Z" + } + } + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/triggers/contact-node-added.ts b/packages/pieces/community/tarvent/src/lib/triggers/contact-node-added.ts new file mode 100644 index 0000000..eb66589 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/contact-node-added.ts @@ -0,0 +1,95 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; +import { CreateWebhookResponse } from '../common/types'; + +export const contactNoteAddedTrigger = createTrigger({ + auth: tarventAuth, + name: 'tarvent_contact_note_added', + displayName: 'Contact Note Added', + description: 'Triggers when a note is added to a contact.', + type: TriggerStrategy.WEBHOOK, + props: { + include: tarventCommon.include, + audienceId: tarventCommon.audienceId(false, 'If specified, the trigger will only fire if contact is in the selected audience.'), + groupId: tarventCommon.audienceGroupId(false, 'If specified, the trigger will only fire if contact is in the selected group.'), + tagId: tarventCommon.tagId(false, 'If specified, the trigger will only fire if contact has the selected tag.') + }, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.createWebhook(context, 'contactNoteAdded'); + await context.store.put('tarvent_contact_note_added', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'tarvent_contact_note_added', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.deleteWebhook(webhook.data.createWebhook.id); + } + }, + sampleData: { + "id": "000000000000000000", + "dateUtc": "2022-09-27T17:37:26.482913Z", + "accountId": "000000000000000000", + "eventType": 1003, + "initiator": { + "source": 2, + "ip": "0.0.0.0", + "protocol": "IPv4", + "httpVerb": "POST", + "device": "Desktop", + "software": "Outlook", + "os": "Windows 11", + "referrer": "https://gmail.com" + }, + "payload": { + "audienceId": "000000000000000000", + "contact": { + "id": "000000000000000000", + "key": "Kayla@tarvent.com", + "email": "Kayla@tarvent.com", + "status": 1, + "rating": 3, + "firstName": "Kayla", + "lastName": "Johnson", + "streetAddress": "165 Caprice Court", + "streetAddress2": "Suite A", + "addressLocality": "Castle Rock", + "addressRegion": "Colorado", + "postalCode": "80109", + "addressCountry": "United States", + "latitude": 39.38363820960583, + "longitude": -104.86229586128452, + "timeZone": "Mountain Standard Time", + "language": "en", + "sendFormat": 1, + "optInUtc": "2022-08-28T17:37:26.6236851Z", + "optInSource": 6, + "optInIp": "0.0.0.0", + "confirmedUtc": "2022-08-29T17:37:26.6236972Z", + "confirmedIp": "0.0.0.0", + "optOutUtc": null, + "optOutSource": null, + "optOutIp": null, + "optOutReason": "", + "groups": [ + "359949389556096655", + "359949389556097786" + ], + "tags": [ + "TarventTest", + "TarventTest2", + "TarventTest3" + ], + "profileFields": null, + "createdUtc": "2022-08-28T17:37:26.6237782Z", + "modifiedUtc": "2022-09-17T17:37:26.6237797Z" + } + } + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/triggers/contact-opened.ts b/packages/pieces/community/tarvent/src/lib/triggers/contact-opened.ts new file mode 100644 index 0000000..0cdf48f --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/contact-opened.ts @@ -0,0 +1,119 @@ +import { Property, TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; +import { CreateWebhookResponse } from '../common/types'; + +export const contactOpenedTrigger = createTrigger({ + auth: tarventAuth, + name: 'tarvent_contact_opened', + displayName: 'Campaign Or Transactional Email Opened', + description: 'Triggers when a campaign or transactional email is opened by a recipient.', + type: TriggerStrategy.WEBHOOK, + props: { + include: tarventCommon.include, + entity: tarventCommon.entity, + campaignType: Property.StaticDropdown({ + displayName: 'Campaign', + description: 'Select what campaign(s) should fire the trigger.', + required: true, + defaultValue: 'BOTH', + options: { + options: [ + { + label: 'Any of the last 5 campaigns', + + value: 'Any', + }, + { + label: 'All of the last 5 campaigns', + + value: 'All', + }, + { + label: 'Any campaigns within last...', + value: 'AnyX', + }, + { + label: 'A specific campaign', + value: 'Specific', + }, + ], + }, + }), + campaignScope: tarventCommon.campaignScope, + campaignId: tarventCommon.campaignId(false, 'Only used if campaign type is set to "Specific"', false, true), + audienceId: tarventCommon.audienceId(false, 'If specified, the trigger will only fire if contact is in the selected audience.'), + groupId: tarventCommon.audienceGroupId(false, 'If specified, the trigger will only fire if contact is in the selected group.'), + tagId: tarventCommon.tagId(false, 'If specified, the trigger will only fire if contact has the selected tag.') + }, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.createWebhook(context, 'contactOpened'); + await context.store.put('tarvent_contact_opened', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'tarvent_contact_opened', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.deleteWebhook(webhook.data.createWebhook.id); + } + }, + sampleData: { + id: '000000000000000000', + dateUtc: '2022-09-27T17:37:26.482913Z', + accountId: '000000000000000000', + eventType: 1003, + initiator: { + source: 2, + ip: '0.0.0.0', + protocol: 'IPv4', + httpVerb: 'POST', + device: 'Desktop', + software: 'Outlook', + os: 'Windows 11', + referrer: 'https://gmail.com', + }, + payload: { + emailId: '000000000000000000', + contact: { + id: '000000000000000000', + key: 'Kayla@tarvent.com', + email: 'Kayla@tarvent.com', + status: 1, + rating: 3, + firstName: 'Kayla', + lastName: 'Johnson', + streetAddress: '165 Caprice Court', + streetAddress2: 'Suite A', + addressLocality: 'Castle Rock', + addressRegion: 'Colorado', + postalCode: '80109', + addressCountry: 'United States', + latitude: 39.38363820960583, + longitude: -104.86229586128452, + timeZone: 'Mountain Standard Time', + language: 'en', + sendFormat: 1, + optInUtc: '2022-08-28T17:37:26.6236851Z', + optInSource: 6, + optInIp: '0.0.0.0', + confirmedUtc: '2022-08-29T17:37:26.6236972Z', + confirmedIp: '0.0.0.0', + optOutUtc: null, + optOutSource: null, + optOutIp: null, + optOutReason: '', + groups: ['359949389556096655', '359949389556097786'], + tags: ['TarventTest', 'TarventTest2', 'TarventTest3'], + profileFields: null, + createdUtc: '2022-08-28T17:37:26.6237782Z', + modifiedUtc: '2022-09-17T17:37:26.6237797Z', + }, + }, + } +}); diff --git a/packages/pieces/community/tarvent/src/lib/triggers/contact-replied.ts b/packages/pieces/community/tarvent/src/lib/triggers/contact-replied.ts new file mode 100644 index 0000000..8148de4 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/contact-replied.ts @@ -0,0 +1,93 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; +import { CreateWebhookResponse } from '../common/types'; + +export const contactRepliedTrigger = createTrigger({ + auth: tarventAuth, + name: 'tarvent_contact_replied', + displayName: 'Contact Replied', + description: 'Triggers when a contact replies.', + type: TriggerStrategy.WEBHOOK, + props: { + include: tarventCommon.include, + entity: tarventCommon.entity, + audienceId: tarventCommon.audienceId(false, 'If specified, the trigger will only fire if contact is in the selected audience.'), + groupId: tarventCommon.audienceGroupId(false, 'If specified, the trigger will only fire if contact is in the selected group.'), + tagId: tarventCommon.tagId(false, 'If specified, the trigger will only fire if contact has the selected tag.') + }, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.createWebhook(context, 'contactReplied'); + await context.store.put('tarvent_contact_replied', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'tarvent_contact_replied', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.deleteWebhook(webhook.data.createWebhook.id); + } + }, + sampleData: { + id: '000000000000000000', + dateUtc: '2022-09-27T17:37:26.482913Z', + accountId: '000000000000000000', + eventType: 1003, + initiator: { + source: 2, + ip: '0.0.0.0', + protocol: 'IPv4', + httpVerb: 'POST', + device: 'Desktop', + software: 'Outlook', + os: 'Windows 11', + referrer: 'https://gmail.com', + }, + payload: { + emailId: '000000000000000000', + bounce: { + type: 100, + response: 'smtp;550 5.1.1 The email account that you tried to reach does not exist.' + }, + contact: { + id: '000000000000000000', + key: 'Kayla@tarvent.com', + email: 'Kayla@tarvent.com', + status: 1, + rating: 3, + firstName: 'Kayla', + lastName: 'Johnson', + streetAddress: '165 Caprice Court', + streetAddress2: 'Suite A', + addressLocality: 'Castle Rock', + addressRegion: 'Colorado', + postalCode: '80109', + addressCountry: 'United States', + latitude: 39.38363820960583, + longitude: -104.86229586128452, + timeZone: 'Mountain Standard Time', + language: 'en', + sendFormat: 1, + optInUtc: '2022-08-28T17:37:26.6236851Z', + optInSource: 6, + optInIp: '0.0.0.0', + confirmedUtc: '2022-08-29T17:37:26.6236972Z', + confirmedIp: '0.0.0.0', + optOutUtc: null, + optOutSource: null, + optOutIp: null, + optOutReason: '', + groups: ['359949389556096655', '359949389556097786'], + tags: ['TarventTest', 'TarventTest2', 'TarventTest3'], + profileFields: null, + createdUtc: '2022-08-28T17:37:26.6237782Z', + modifiedUtc: '2022-09-17T17:37:26.6237797Z', + }, + }, + } +}); diff --git a/packages/pieces/community/tarvent/src/lib/triggers/contact-status-updated.ts b/packages/pieces/community/tarvent/src/lib/triggers/contact-status-updated.ts new file mode 100644 index 0000000..7fde0d4 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/contact-status-updated.ts @@ -0,0 +1,118 @@ +import { tarventAuth } from '../..'; +import { Property, TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { makeClient, tarventCommon } from '../common'; +import { CreateWebhookResponse } from '../common/types'; + +export const contactStatusUpdatedTrigger = createTrigger({ + auth: tarventAuth, + name: 'tarvent_contact_status_updated', + displayName: 'Contact Status Changed', + description: 'Triggers when a contact\'s status changes.', + type: TriggerStrategy.WEBHOOK, + props: { + include: tarventCommon.include, + status: Property.StaticDropdown({ + displayName: 'Select what status should fire this trigger.', + description: 'If not included, only the contact ID, email and unique identifier will be passed back.', + required: true, + defaultValue: 'Any', + options: { + options: [ + { + label: 'Any', + + value: 'Any', + }, + { + label: 'Subscribe', + value: 'ACTIVE', + }, + { + label: 'Unsubscribe', + value: 'OPT_OUT', + }, + ], + }, + }), + audienceId: tarventCommon.audienceId(false, 'If specified, the trigger will only fire if contact is in the selected audience.'), + groupId: tarventCommon.audienceGroupId(false, 'If specified, the trigger will only fire if contact is in the selected group.'), + tagId: tarventCommon.tagId(false, 'If specified, the trigger will only fire if contact has the selected tag.') + }, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.createWebhook(context, 'contactStatusUpdated'); + await context.store.put('tarvent_contact_status_updated', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'tarvent_contact_status_updated', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.deleteWebhook(webhook.data.createWebhook.id); + } + }, + sampleData: { + "id": "000000000000000000", + "dateUtc": "2022-09-27T17:37:26.482913Z", + "accountId": "000000000000000000", + "eventType": 1003, + "initiator": { + "source": 2, + "ip": "0.0.0.0", + "protocol": "IPv4", + "httpVerb": "POST", + "device": "Desktop", + "software": "Outlook", + "os": "Windows 11", + "referrer": "https://gmail.com" + }, + "payload": { + "audienceId": "000000000000000000", + "contact": { + "id": "000000000000000000", + "key": "Kayla@tarvent.com", + "email": "Kayla@tarvent.com", + "status": 1, + "rating": 3, + "firstName": "Kayla", + "lastName": "Johnson", + "streetAddress": "165 Caprice Court", + "streetAddress2": "Suite A", + "addressLocality": "Castle Rock", + "addressRegion": "Colorado", + "postalCode": "80109", + "addressCountry": "United States", + "latitude": 39.38363820960583, + "longitude": -104.86229586128452, + "timeZone": "Mountain Standard Time", + "language": "en", + "sendFormat": 1, + "optInUtc": "2022-08-28T17:37:26.6236851Z", + "optInSource": 6, + "optInIp": "0.0.0.0", + "confirmedUtc": "2022-08-29T17:37:26.6236972Z", + "confirmedIp": "0.0.0.0", + "optOutUtc": null, + "optOutSource": null, + "optOutIp": null, + "optOutReason": "", + "groups": [ + "359949389556096655", + "359949389556097786" + ], + "tags": [ + "TarventTest", + "TarventTest2", + "TarventTest3" + ], + "profileFields": null, + "createdUtc": "2022-08-28T17:37:26.6237782Z", + "modifiedUtc": "2022-09-17T17:37:26.6237797Z" + } + } + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/triggers/contact-tag-updated.ts b/packages/pieces/community/tarvent/src/lib/triggers/contact-tag-updated.ts new file mode 100644 index 0000000..afc642b --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/contact-tag-updated.ts @@ -0,0 +1,114 @@ +import { tarventAuth } from '../..'; +import { Property, TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { makeClient, tarventCommon } from '../common'; +import { CreateWebhookResponse } from '../common/types'; + +export const contactTagUpdatedTrigger = createTrigger({ + auth: tarventAuth, + name: 'tarvent_contact_tag_updated', + displayName: 'Contact Tag Added Or Removed', + description: 'Triggers when a tag is added or removed from a contact.', + type: TriggerStrategy.WEBHOOK, + props: { + include: tarventCommon.include, + action: Property.StaticDropdown({ + displayName: 'Tag action', + description: 'Select if the trigger should fire when a tag is added or removed.', + required: true, + defaultValue: 'Add', + options: { + options: [ + { + label: 'Added', + + value: 'Add', + }, + { + label: 'Removed', + value: 'Remove', + } + ], + }, + }), + audienceId: tarventCommon.audienceId(false, 'If specified, the trigger will only fire if contact is in the selected audience.'), + groupId: tarventCommon.audienceGroupId(false, 'If specified, the trigger will only fire if contact is in the selected group.'), + tagId: tarventCommon.tagId(false, 'If specified, the trigger will only fire if contact has the selected tag.') + }, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.createWebhook(context, 'contactTagUpdated'); + await context.store.put('tarvent_contact_tag_updated', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'tarvent_contact_tag_updated', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.deleteWebhook(webhook.data.createWebhook.id); + } + }, + sampleData: { + "id": "000000000000000000", + "dateUtc": "2022-09-27T17:37:26.482913Z", + "accountId": "000000000000000000", + "eventType": 1003, + "initiator": { + "source": 2, + "ip": "0.0.0.0", + "protocol": "IPv4", + "httpVerb": "POST", + "device": "Desktop", + "software": "Outlook", + "os": "Windows 11", + "referrer": "https://gmail.com" + }, + "payload": { + "audienceId": "000000000000000000", + "contact": { + "id": "000000000000000000", + "key": "Kayla@tarvent.com", + "email": "Kayla@tarvent.com", + "status": 1, + "rating": 3, + "firstName": "Kayla", + "lastName": "Johnson", + "streetAddress": "165 Caprice Court", + "streetAddress2": "Suite A", + "addressLocality": "Castle Rock", + "addressRegion": "Colorado", + "postalCode": "80109", + "addressCountry": "United States", + "latitude": 39.38363820960583, + "longitude": -104.86229586128452, + "timeZone": "Mountain Standard Time", + "language": "en", + "sendFormat": 1, + "optInUtc": "2022-08-28T17:37:26.6236851Z", + "optInSource": 6, + "optInIp": "0.0.0.0", + "confirmedUtc": "2022-08-29T17:37:26.6236972Z", + "confirmedIp": "0.0.0.0", + "optOutUtc": null, + "optOutSource": null, + "optOutIp": null, + "optOutReason": "", + "groups": [ + "359949389556096655", + "359949389556097786" + ], + "tags": [ + "TarventTest", + "TarventTest2", + "TarventTest3" + ], + "profileFields": null, + "createdUtc": "2022-08-28T17:37:26.6237782Z", + "modifiedUtc": "2022-09-17T17:37:26.6237797Z" + } + } + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/triggers/contact-unsubscribed.ts b/packages/pieces/community/tarvent/src/lib/triggers/contact-unsubscribed.ts new file mode 100644 index 0000000..d2ec895 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/contact-unsubscribed.ts @@ -0,0 +1,95 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; +import { CreateWebhookResponse } from '../common/types'; + +export const contactUnsubscribedTrigger = createTrigger({ + auth: tarventAuth, + name: 'tarvent_contact_unsubscribed', + displayName: 'Contact Unsubscribed', + description: 'Triggers when a contact unsubscribes from an audience.', + type: TriggerStrategy.WEBHOOK, + props: { + include: tarventCommon.include, + audienceId: tarventCommon.audienceId(false, 'If specified, the trigger will only fire if contact is in the selected audience.'), + groupId: tarventCommon.audienceGroupId(false, 'If specified, the trigger will only fire if contact is in the selected group.'), + tagId: tarventCommon.tagId(false, 'If specified, the trigger will only fire if contact has the selected tag.') + }, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.createWebhook(context, 'contactUnsubscribed'); + await context.store.put('tarvent_contact_unsubscribed', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'tarvent_contact_unsubscribed', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.deleteWebhook(webhook.data.createWebhook.id); + } + }, + sampleData: { + "id": "000000000000000000", + "dateUtc": "2022-09-27T17:37:26.482913Z", + "accountId": "000000000000000000", + "eventType": 1003, + "initiator": { + "source": 2, + "ip": "0.0.0.0", + "protocol": "IPv4", + "httpVerb": "POST", + "device": "Desktop", + "software": "Outlook", + "os": "Windows 11", + "referrer": "https://gmail.com" + }, + "payload": { + "audienceId": "000000000000000000", + "contact": { + "id": "000000000000000000", + "key": "Kayla@tarvent.com", + "email": "Kayla@tarvent.com", + "status": 1, + "rating": 3, + "firstName": "Kayla", + "lastName": "Johnson", + "streetAddress": "165 Caprice Court", + "streetAddress2": "Suite A", + "addressLocality": "Castle Rock", + "addressRegion": "Colorado", + "postalCode": "80109", + "addressCountry": "United States", + "latitude": 39.38363820960583, + "longitude": -104.86229586128452, + "timeZone": "Mountain Standard Time", + "language": "en", + "sendFormat": 1, + "optInUtc": "2022-08-28T17:37:26.6236851Z", + "optInSource": 6, + "optInIp": "0.0.0.0", + "confirmedUtc": "2022-08-29T17:37:26.6236972Z", + "confirmedIp": "0.0.0.0", + "optOutUtc": null, + "optOutSource": null, + "optOutIp": null, + "optOutReason": "", + "groups": [ + "359949389556096655", + "359949389556097786" + ], + "tags": [ + "TarventTest", + "TarventTest2", + "TarventTest3" + ], + "profileFields": null, + "createdUtc": "2022-08-28T17:37:26.6237782Z", + "modifiedUtc": "2022-09-17T17:37:26.6237797Z" + } + } + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/triggers/contact-updated.ts b/packages/pieces/community/tarvent/src/lib/triggers/contact-updated.ts new file mode 100644 index 0000000..2ca2912 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/contact-updated.ts @@ -0,0 +1,95 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; +import { CreateWebhookResponse } from '../common/types'; + +export const contactUpdatedTrigger = createTrigger({ + auth: tarventAuth, + name: 'tarvent_contact_updated', + displayName: 'Contact Profile Updated', + description: 'Triggers when a contact\'s profile is updated.', + type: TriggerStrategy.WEBHOOK, + props: { + include: tarventCommon.include, + audienceId: tarventCommon.audienceId(false, 'If specified, the trigger will only fire if contact is in the selected audience.'), + groupId: tarventCommon.audienceGroupId(false, 'If specified, the trigger will only fire if contact is in the selected group.'), + tagId: tarventCommon.tagId(false, 'If specified, the trigger will only fire if contact has the selected tag.') + }, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.createWebhook(context, 'contactUpdated'); + await context.store.put('tarvent_contact_updated', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'tarvent_contact_updated', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.deleteWebhook(webhook.data.createWebhook.id); + } + }, + sampleData: { + "id": "000000000000000000", + "dateUtc": "2022-09-27T17:37:26.482913Z", + "accountId": "000000000000000000", + "eventType": 1003, + "initiator": { + "source": 2, + "ip": "0.0.0.0", + "protocol": "IPv4", + "httpVerb": "POST", + "device": "Desktop", + "software": "Outlook", + "os": "Windows 11", + "referrer": "https://gmail.com" + }, + "payload": { + "audienceId": "000000000000000000", + "contact": { + "id": "000000000000000000", + "key": "Kayla@tarvent.com", + "email": "Kayla@tarvent.com", + "status": 1, + "rating": 3, + "firstName": "Kayla", + "lastName": "Johnson", + "streetAddress": "165 Caprice Court", + "streetAddress2": "Suite A", + "addressLocality": "Castle Rock", + "addressRegion": "Colorado", + "postalCode": "80109", + "addressCountry": "United States", + "latitude": 39.38363820960583, + "longitude": -104.86229586128452, + "timeZone": "Mountain Standard Time", + "language": "en", + "sendFormat": 1, + "optInUtc": "2022-08-28T17:37:26.6236851Z", + "optInSource": 6, + "optInIp": "0.0.0.0", + "confirmedUtc": "2022-08-29T17:37:26.6236972Z", + "confirmedIp": "0.0.0.0", + "optOutUtc": null, + "optOutSource": null, + "optOutIp": null, + "optOutReason": "", + "groups": [ + "359949389556096655", + "359949389556097786" + ], + "tags": [ + "TarventTest", + "TarventTest2", + "TarventTest3" + ], + "profileFields": null, + "createdUtc": "2022-08-28T17:37:26.6237782Z", + "modifiedUtc": "2022-09-17T17:37:26.6237797Z" + } + } + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/triggers/form-submitted.ts b/packages/pieces/community/tarvent/src/lib/triggers/form-submitted.ts new file mode 100644 index 0000000..2b96a86 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/form-submitted.ts @@ -0,0 +1,99 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; +import { CreateWebhookResponse } from '../common/types'; + +export const formSubmittedTrigger = createTrigger({ + auth: tarventAuth, + name: 'tarvent_form_submitted', + displayName: 'Form Submission Received', + description: 'Triggers when a known or unknown contact submits a sign up, profile update, or other form.', + type: TriggerStrategy.WEBHOOK, + props: { + include: tarventCommon.include, + audienceId: tarventCommon.audienceId(false, 'If specified, the trigger will only fire if contact is in the selected audience.'), + formId: tarventCommon.audienceFormId(false, 'If specified, the trigger will only fire if the selected form is submitted.'), + groupId: tarventCommon.audienceGroupId(false, 'If specified, the trigger will only fire if contact is in the selected group.'), + tagId: tarventCommon.tagId(false, 'If specified, the trigger will only fire if contact has the selected tag.') + }, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.createWebhook(context, 'formSubmitted'); + await context.store.put('tarvent_form_submitted', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'tarvent_form_submitted', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.deleteWebhook(webhook.data.createWebhook.id); + } + }, + sampleData: { + "id": "000000000000000000", + "dateUtc": "2022-09-27T17:37:26.482913Z", + "accountId": "000000000000000000", + "eventType": 1003, + "initiator": { + "source": 2, + "ip": "0.0.0.0", + "protocol": "IPv4", + "httpVerb": "POST", + "device": "Desktop", + "software": "Outlook", + "os": "Windows 11", + "referrer": "https://gmail.com" + }, + "payload": { + formId: '000000000000000000', + questionData: [{ + "questionText": "Question answer" + }], + "contact": { + "id": "000000000000000000", + "key": "Kayla@tarvent.com", + "email": "Kayla@tarvent.com", + "status": 1, + "rating": 3, + "firstName": "Kayla", + "lastName": "Johnson", + "streetAddress": "165 Caprice Court", + "streetAddress2": "Suite A", + "addressLocality": "Castle Rock", + "addressRegion": "Colorado", + "postalCode": "80109", + "addressCountry": "United States", + "latitude": 39.38363820960583, + "longitude": -104.86229586128452, + "timeZone": "Mountain Standard Time", + "language": "en", + "sendFormat": 1, + "optInUtc": "2022-08-28T17:37:26.6236851Z", + "optInSource": 6, + "optInIp": "0.0.0.0", + "confirmedUtc": "2022-08-29T17:37:26.6236972Z", + "confirmedIp": "0.0.0.0", + "optOutUtc": null, + "optOutSource": null, + "optOutIp": null, + "optOutReason": "", + "groups": [ + "359949389556096655", + "359949389556097786" + ], + "tags": [ + "TarventTest", + "TarventTest2", + "TarventTest3" + ], + "profileFields": null, + "createdUtc": "2022-08-28T17:37:26.6237782Z", + "modifiedUtc": "2022-09-17T17:37:26.6237797Z" + } + } + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/triggers/index.ts b/packages/pieces/community/tarvent/src/lib/triggers/index.ts new file mode 100644 index 0000000..ee63590 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/index.ts @@ -0,0 +1,17 @@ +export * from './contact-added'; +export * from './campaign-send-finished'; +export * from './contact-bounced'; +export * from './contact-clicked'; +export * from './contact-opened'; +export * from './contact-group-updated'; +export * from './contact-node-added'; +export * from './contact-replied'; +export * from './contact-status-updated'; +export * from './contact-tag-updated'; +export * from './contact-unsubscribed'; +export * from './contact-updated'; +export * from './form-submitted'; +export * from './page-performed'; +export * from './survey-submitted'; +export * from './transaction-created'; +export * from './transaction-sent'; diff --git a/packages/pieces/community/tarvent/src/lib/triggers/page-performed.ts b/packages/pieces/community/tarvent/src/lib/triggers/page-performed.ts new file mode 100644 index 0000000..b4bc301 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/page-performed.ts @@ -0,0 +1,96 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; +import { CreateWebhookResponse } from '../common/types'; + +export const pagePerformedTrigger = createTrigger({ + auth: tarventAuth, + name: 'tarvent_page_performed', + displayName: 'Landing Page CTA Performed', + description: 'Triggers when a known or unknown contact performs a Call-To-Action within a landing page.', + type: TriggerStrategy.WEBHOOK, + props: { + include: tarventCommon.include, + landingPageId: tarventCommon.landingPageId, + audienceId: tarventCommon.audienceId(false, 'If specified, the trigger will only fire if contact is in the selected audience.'), + groupId: tarventCommon.audienceGroupId(false, 'If specified, the trigger will only fire if contact is in the selected group.'), + tagId: tarventCommon.tagId(false, 'If specified, the trigger will only fire if contact has the selected tag.') + }, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.createWebhook(context, 'landingPageCtaPerformed'); + await context.store.put('tarvent_page_performed', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'tarvent_page_performed', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.deleteWebhook(webhook.data.createWebhook.id); + } + }, + sampleData: { + "id": "000000000000000000", + "dateUtc": "2022-09-27T17:37:26.482913Z", + "accountId": "000000000000000000", + "eventType": 1003, + "initiator": { + "source": 2, + "ip": "0.0.0.0", + "protocol": "IPv4", + "httpVerb": "POST", + "device": "Desktop", + "software": "Outlook", + "os": "Windows 11", + "referrer": "https://gmail.com" + }, + "payload": { + pageId: '000000000000000000', + "contact": { + "id": "000000000000000000", + "key": "Kayla@tarvent.com", + "email": "Kayla@tarvent.com", + "status": 1, + "rating": 3, + "firstName": "Kayla", + "lastName": "Johnson", + "streetAddress": "165 Caprice Court", + "streetAddress2": "Suite A", + "addressLocality": "Castle Rock", + "addressRegion": "Colorado", + "postalCode": "80109", + "addressCountry": "United States", + "latitude": 39.38363820960583, + "longitude": -104.86229586128452, + "timeZone": "Mountain Standard Time", + "language": "en", + "sendFormat": 1, + "optInUtc": "2022-08-28T17:37:26.6236851Z", + "optInSource": 6, + "optInIp": "0.0.0.0", + "confirmedUtc": "2022-08-29T17:37:26.6236972Z", + "confirmedIp": "0.0.0.0", + "optOutUtc": null, + "optOutSource": null, + "optOutIp": null, + "optOutReason": "", + "groups": [ + "359949389556096655", + "359949389556097786" + ], + "tags": [ + "TarventTest", + "TarventTest2", + "TarventTest3" + ], + "profileFields": null, + "createdUtc": "2022-08-28T17:37:26.6237782Z", + "modifiedUtc": "2022-09-17T17:37:26.6237797Z" + } + } + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/triggers/survey-submitted.ts b/packages/pieces/community/tarvent/src/lib/triggers/survey-submitted.ts new file mode 100644 index 0000000..ffc6e65 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/survey-submitted.ts @@ -0,0 +1,96 @@ +import { tarventAuth } from '../..'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { makeClient, tarventCommon } from '../common'; +import { CreateWebhookResponse } from '../common/types'; + +export const surveySubmittedTrigger = createTrigger({ + auth: tarventAuth, + name: 'tarvent_survey_submitted', + displayName: 'Survey Submission Received', + description: 'Triggers when a known or unknown contact submits a survey.', + type: TriggerStrategy.WEBHOOK, + props: { + include: tarventCommon.include, + surveyId: tarventCommon.surveyId, + audienceId: tarventCommon.audienceId(false, 'If specified, the trigger will only fire if contact is in the selected audience.'), + groupId: tarventCommon.audienceGroupId(false, 'If specified, the trigger will only fire if contact is in the selected group.'), + tagId: tarventCommon.tagId(false, 'If specified, the trigger will only fire if contact has the selected tag.') + }, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.createWebhook(context, 'surveySubmitted'); + await context.store.put('tarvent_survey_submitted', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'tarvent_survey_submitted', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.deleteWebhook(webhook.data.createWebhook.id); + } + }, + sampleData: { + "id": "000000000000000000", + "dateUtc": "2022-09-27T17:37:26.482913Z", + "accountId": "000000000000000000", + "eventType": 1003, + "initiator": { + "source": 2, + "ip": "0.0.0.0", + "protocol": "IPv4", + "httpVerb": "POST", + "device": "Desktop", + "software": "Outlook", + "os": "Windows 11", + "referrer": "https://gmail.com" + }, + "payload": { + pageId: '000000000000000000', + "contact": { + "id": "000000000000000000", + "key": "Kayla@tarvent.com", + "email": "Kayla@tarvent.com", + "status": 1, + "rating": 3, + "firstName": "Kayla", + "lastName": "Johnson", + "streetAddress": "165 Caprice Court", + "streetAddress2": "Suite A", + "addressLocality": "Castle Rock", + "addressRegion": "Colorado", + "postalCode": "80109", + "addressCountry": "United States", + "latitude": 39.38363820960583, + "longitude": -104.86229586128452, + "timeZone": "Mountain Standard Time", + "language": "en", + "sendFormat": 1, + "optInUtc": "2022-08-28T17:37:26.6236851Z", + "optInSource": 6, + "optInIp": "0.0.0.0", + "confirmedUtc": "2022-08-29T17:37:26.6236972Z", + "confirmedIp": "0.0.0.0", + "optOutUtc": null, + "optOutSource": null, + "optOutIp": null, + "optOutReason": "", + "groups": [ + "359949389556096655", + "359949389556097786" + ], + "tags": [ + "TarventTest", + "TarventTest2", + "TarventTest3" + ], + "profileFields": null, + "createdUtc": "2022-08-28T17:37:26.6237782Z", + "modifiedUtc": "2022-09-17T17:37:26.6237797Z" + } + } + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/triggers/transaction-created.ts b/packages/pieces/community/tarvent/src/lib/triggers/transaction-created.ts new file mode 100644 index 0000000..7939cc6 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/transaction-created.ts @@ -0,0 +1,95 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; +import { CreateWebhookResponse } from '../common/types'; + +export const transactionCreatedTrigger = createTrigger({ + auth: tarventAuth, + name: 'tarvent_transaction_created', + displayName: 'Transaction Created', + description: 'Triggers when a transactional email is created for a known or unknown contact.', + type: TriggerStrategy.WEBHOOK, + props: { + include: tarventCommon.include, + audienceId: tarventCommon.audienceId(false, 'If specified, the trigger will only fire if contact is in the selected audience.'), + groupId: tarventCommon.audienceGroupId(false, 'If specified, the trigger will only fire if contact is in the selected group.'), + tagId: tarventCommon.tagId(false, 'If specified, the trigger will only fire if contact has the selected tag.') + }, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.createWebhook(context, 'transactionCreated'); + await context.store.put('tarvent_transaction_created', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'tarvent_transaction_created', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.deleteWebhook(webhook.data.createWebhook.id); + } + }, + sampleData: { + "id": "000000000000000000", + "dateUtc": "2022-09-27T17:37:26.482913Z", + "accountId": "000000000000000000", + "eventType": 1003, + "initiator": { + "source": 2, + "ip": "0.0.0.0", + "protocol": "IPv4", + "httpVerb": "POST", + "device": "Desktop", + "software": "Outlook", + "os": "Windows 11", + "referrer": "https://gmail.com" + }, + "payload": { + "emailId": "000000000000000000", + "contact": { + "id": "000000000000000000", + "key": "Kayla@tarvent.com", + "email": "Kayla@tarvent.com", + "status": 1, + "rating": 3, + "firstName": "Kayla", + "lastName": "Johnson", + "streetAddress": "165 Caprice Court", + "streetAddress2": "Suite A", + "addressLocality": "Castle Rock", + "addressRegion": "Colorado", + "postalCode": "80109", + "addressCountry": "United States", + "latitude": 39.38363820960583, + "longitude": -104.86229586128452, + "timeZone": "Mountain Standard Time", + "language": "en", + "sendFormat": 1, + "optInUtc": "2022-08-28T17:37:26.6236851Z", + "optInSource": 6, + "optInIp": "0.0.0.0", + "confirmedUtc": "2022-08-29T17:37:26.6236972Z", + "confirmedIp": "0.0.0.0", + "optOutUtc": null, + "optOutSource": null, + "optOutIp": null, + "optOutReason": "", + "groups": [ + "359949389556096655", + "359949389556097786" + ], + "tags": [ + "TarventTest", + "TarventTest2", + "TarventTest3" + ], + "profileFields": null, + "createdUtc": "2022-08-28T17:37:26.6237782Z", + "modifiedUtc": "2022-09-17T17:37:26.6237797Z" + } + } + }, +}); diff --git a/packages/pieces/community/tarvent/src/lib/triggers/transaction-sent.ts b/packages/pieces/community/tarvent/src/lib/triggers/transaction-sent.ts new file mode 100644 index 0000000..1da4380 --- /dev/null +++ b/packages/pieces/community/tarvent/src/lib/triggers/transaction-sent.ts @@ -0,0 +1,95 @@ +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { tarventAuth } from '../..'; +import { makeClient, tarventCommon } from '../common'; +import { CreateWebhookResponse } from '../common/types'; + +export const transactionSentTrigger = createTrigger({ + auth: tarventAuth, + name: 'tarvent_transaction_sent', + displayName: 'Transaction Sent', + description: 'Triggers when a transactional email is processed and sent to one or more recipients.', + type: TriggerStrategy.WEBHOOK, + props: { + include: tarventCommon.include, + audienceId: tarventCommon.audienceId(false, 'If specified, the trigger will only fire if contact is in the selected audience.'), + groupId: tarventCommon.audienceGroupId(false, 'If specified, the trigger will only fire if contact is in the selected group.'), + tagId: tarventCommon.tagId(false, 'If specified, the trigger will only fire if contact has the selected tag.') + }, + async onEnable(context) { + const client = makeClient(context.auth); + const res = await client.createWebhook(context, 'transactionSent'); + await context.store.put('tarvent_transaction_sent', res); + }, + async run(context) { + return [context.payload.body]; + }, + async onDisable(context) { + const webhook = await context.store.get( + 'tarvent_transaction_sent', + ); + if (webhook != null) { + const client = makeClient(context.auth); + await client.deleteWebhook(webhook.data.createWebhook.id); + } + }, + sampleData: { + "id": "000000000000000000", + "dateUtc": "2022-09-27T17:37:26.482913Z", + "accountId": "000000000000000000", + "eventType": 1003, + "initiator": { + "source": 2, + "ip": "0.0.0.0", + "protocol": "IPv4", + "httpVerb": "POST", + "device": "Desktop", + "software": "Outlook", + "os": "Windows 11", + "referrer": "https://gmail.com" + }, + "payload": { + "emailId": "000000000000000000", + "contact": { + "id": "000000000000000000", + "key": "Kayla@tarvent.com", + "email": "Kayla@tarvent.com", + "status": 1, + "rating": 3, + "firstName": "Kayla", + "lastName": "Johnson", + "streetAddress": "165 Caprice Court", + "streetAddress2": "Suite A", + "addressLocality": "Castle Rock", + "addressRegion": "Colorado", + "postalCode": "80109", + "addressCountry": "United States", + "latitude": 39.38363820960583, + "longitude": -104.86229586128452, + "timeZone": "Mountain Standard Time", + "language": "en", + "sendFormat": 1, + "optInUtc": "2022-08-28T17:37:26.6236851Z", + "optInSource": 6, + "optInIp": "0.0.0.0", + "confirmedUtc": "2022-08-29T17:37:26.6236972Z", + "confirmedIp": "0.0.0.0", + "optOutUtc": null, + "optOutSource": null, + "optOutIp": null, + "optOutReason": "", + "groups": [ + "359949389556096655", + "359949389556097786" + ], + "tags": [ + "TarventTest", + "TarventTest2", + "TarventTest3" + ], + "profileFields": null, + "createdUtc": "2022-08-28T17:37:26.6237782Z", + "modifiedUtc": "2022-09-17T17:37:26.6237797Z" + } + } + }, +}); diff --git a/packages/pieces/community/tarvent/tsconfig.json b/packages/pieces/community/tarvent/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/tarvent/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/tarvent/tsconfig.lib.json b/packages/pieces/community/tarvent/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/tarvent/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/taskade/.eslintrc.json b/packages/pieces/community/taskade/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/taskade/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/taskade/README.md b/packages/pieces/community/taskade/README.md new file mode 100644 index 0000000..32ad3fc --- /dev/null +++ b/packages/pieces/community/taskade/README.md @@ -0,0 +1,7 @@ +# pieces-taskade + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-taskade` to build the library. diff --git a/packages/pieces/community/taskade/package.json b/packages/pieces/community/taskade/package.json new file mode 100644 index 0000000..f865af3 --- /dev/null +++ b/packages/pieces/community/taskade/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-taskade", + "version": "0.0.1" +} diff --git a/packages/pieces/community/taskade/project.json b/packages/pieces/community/taskade/project.json new file mode 100644 index 0000000..af9e210 --- /dev/null +++ b/packages/pieces/community/taskade/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-taskade", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/taskade/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/taskade", + "tsConfig": "packages/pieces/community/taskade/tsconfig.lib.json", + "packageJson": "packages/pieces/community/taskade/package.json", + "main": "packages/pieces/community/taskade/src/index.ts", + "assets": [ + "packages/pieces/community/taskade/*.md", + { + "input": "packages/pieces/community/taskade/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-taskade {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/taskade/src/index.ts b/packages/pieces/community/taskade/src/index.ts new file mode 100644 index 0000000..7b8d2c7 --- /dev/null +++ b/packages/pieces/community/taskade/src/index.ts @@ -0,0 +1,35 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { createTaskAction } from './lib/actions/create-task.action'; +import { PieceCategory } from '@activepieces/shared'; +import { completeTaskAction } from './lib/actions/complete-task.action'; +import { deleteTaskAction } from './lib/actions/delete-task.action'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; + +export const taskadeAuth = PieceAuth.SecretText({ + displayName: 'Personal Token', + required: true, + description: ` + 1. Navigate to https://taskade.com/settings/password and scroll down to Personal Access Tokens. + 2. Create your personal access token with any name.`, +}); + +export const taskade = createPiece({ + displayName: 'Taskade', + auth: taskadeAuth, + minimumSupportedRelease: '0.30.0', + categories: [PieceCategory.PRODUCTIVITY], + description: 'collaboration platform for remote teams to organize and manage projects', + logoUrl: 'https://cdn.activepieces.com/pieces/taskade.png', + authors: ['kishanprmr'], + actions: [ + createTaskAction, + completeTaskAction, + deleteTaskAction, + createCustomApiCallAction({ + baseUrl: () => 'https://www.taskade.com/api/v1', + auth: taskadeAuth, + authMapping: async (auth) => ({ Authorization: `Bearer ${auth as string}` }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/taskade/src/lib/actions/complete-task.action.ts b/packages/pieces/community/taskade/src/lib/actions/complete-task.action.ts new file mode 100644 index 0000000..e7155cd --- /dev/null +++ b/packages/pieces/community/taskade/src/lib/actions/complete-task.action.ts @@ -0,0 +1,24 @@ +import { taskadeAuth } from '../../'; +import { createAction } from '@activepieces/pieces-framework'; +import { taskadeProps } from '../common/props'; +import { TaskadeAPIClient } from '../common/client'; + +export const completeTaskAction = createAction({ + auth: taskadeAuth, + name: 'taskade-complete-task', + displayName: 'Complete Task', + description: 'Complete a task in a project.', + props: { + workspace_id: taskadeProps.workspace_id, + folder_id: taskadeProps.folder_id, + project_id: taskadeProps.project_id, + task_id: taskadeProps.task_id, + }, + async run(context) { + const { project_id, task_id } = context.propsValue; + + const client = new TaskadeAPIClient(context.auth); + + return await client.completeTask(project_id, task_id); + }, +}); diff --git a/packages/pieces/community/taskade/src/lib/actions/create-task.action.ts b/packages/pieces/community/taskade/src/lib/actions/create-task.action.ts new file mode 100644 index 0000000..6f9fe53 --- /dev/null +++ b/packages/pieces/community/taskade/src/lib/actions/create-task.action.ts @@ -0,0 +1,68 @@ +import { taskadeAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { taskadeProps } from '../common/props'; +import { TaskadeAPIClient } from '../common/client'; + +export const createTaskAction = createAction({ + auth: taskadeAuth, + name: 'taskade-create-task', + displayName: 'Create Task', + description: 'Creates a new task.', + props: { + workspace_id: taskadeProps.workspace_id, + folder_id: taskadeProps.folder_id, + project_id: taskadeProps.project_id, + content_type: Property.StaticDropdown({ + displayName: 'Content Type', + required: true, + defaultValue: 'text/markdown', + options: { + disabled: false, + options: [ + { + label: 'text/markdown', + value: 'text/markdown', + }, + { + label: 'text/plain', + value: 'text/plain', + }, + ], + }, + }), + content: Property.LongText({ + displayName: 'Task Content', + required: true, + }), + placement: Property.StaticDropdown({ + displayName: 'Placement', + description: 'Placement of task in block', + required: true, + defaultValue: 'afterbegin', + options: { + disabled: false, + options: [ + { + label: 'afterbegin', + value: 'afterbegin', + }, + { + label: 'beforeend', + value: 'beforeend', + }, + ], + }, + }), + }, + async run(context) { + const { project_id, content_type, content, placement } = context.propsValue; + + const client = new TaskadeAPIClient(context.auth); + + return await client.createTask(project_id, { + content, + contentType: content_type, + placement, + }); + }, +}); diff --git a/packages/pieces/community/taskade/src/lib/actions/delete-task.action.ts b/packages/pieces/community/taskade/src/lib/actions/delete-task.action.ts new file mode 100644 index 0000000..82361f7 --- /dev/null +++ b/packages/pieces/community/taskade/src/lib/actions/delete-task.action.ts @@ -0,0 +1,24 @@ +import { taskadeAuth } from '../../'; +import { createAction } from '@activepieces/pieces-framework'; +import { taskadeProps } from '../common/props'; +import { TaskadeAPIClient } from '../common/client'; + +export const deleteTaskAction = createAction({ + auth: taskadeAuth, + name: 'taskade-delete-task', + displayName: 'Delete Task', + description: 'Delete an existing task in a project.', + props: { + workspace_id: taskadeProps.workspace_id, + folder_id: taskadeProps.folder_id, + project_id: taskadeProps.project_id, + task_id: taskadeProps.task_id, + }, + async run(context) { + const { project_id, task_id } = context.propsValue; + + const client = new TaskadeAPIClient(context.auth); + + return await client.deleteTask(project_id, task_id); + }, +}); diff --git a/packages/pieces/community/taskade/src/lib/common/client.ts b/packages/pieces/community/taskade/src/lib/common/client.ts new file mode 100644 index 0000000..9cdeff7 --- /dev/null +++ b/packages/pieces/community/taskade/src/lib/common/client.ts @@ -0,0 +1,105 @@ +import { + HttpMessageBody, + HttpMethod, + QueryParams, + httpClient, + HttpRequest, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { + CreateTaskDateParams, + CreateTaskParams, + ListAPIResponse, + ProjectResponse, + CreateTaskResponse, + WorkspaceFolderResponse, + WorkspaceResponse, + TaskResponse, +} from './types'; + +type RequestParams = Record; + +export class TaskadeAPIClient { + constructor(private personalToken: string) {} + + async makeRequest( + method: HttpMethod, + resourceUri: string, + query?: RequestParams, + body: any | undefined = undefined, + ): Promise { + const baseUrl = 'https://www.taskade.com/api/v1'; + const qs: QueryParams = {}; + + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + qs[key] = String(value); + } + } + } + + const request: HttpRequest = { + method, + url: baseUrl + resourceUri, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: this.personalToken, + }, + queryParams: qs, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + } + + async listWorkspaces(): Promise> { + return await this.makeRequest(HttpMethod.GET, '/workspaces'); + } + + async listWorkspaceFolders( + workspace_id: string, + ): Promise> { + return await this.makeRequest(HttpMethod.GET, `/workspaces/${workspace_id}/folders`); + } + + async listProjects(folder_id: string): Promise> { + return await this.makeRequest(HttpMethod.GET, `/folders/${folder_id}/projects`); + } + + async createTask(projectId: string, params: CreateTaskParams): Promise { + return await this.makeRequest(HttpMethod.POST, `/projects/${projectId}/tasks`, undefined, { + tasks: [params], + }); + } + + async createTaskDate(projectId: string, taskId: string, params: CreateTaskDateParams) { + return await this.makeRequest( + HttpMethod.PUT, + `/projects/${projectId}/tasks/${taskId}/date`, + undefined, + params, + ); + } + + async listTasks( + projectId: string, + params: RequestParams, + ): Promise> { + return await this.makeRequest(HttpMethod.GET, `/projects/${projectId}/tasks`, params); + } + + async completeTask(projectId: string, taskId: string) { + return await this.makeRequest( + HttpMethod.POST, + `/projects/${projectId}/tasks/${taskId}/complete`, + undefined, + {}, + ); + } + + async deleteTask(projectId: string, taskId: string) { + return await this.makeRequest(HttpMethod.DELETE, `/projects/${projectId}/tasks/${taskId}`); + } +} diff --git a/packages/pieces/community/taskade/src/lib/common/props.ts b/packages/pieces/community/taskade/src/lib/common/props.ts new file mode 100644 index 0000000..25bb75d --- /dev/null +++ b/packages/pieces/community/taskade/src/lib/common/props.ts @@ -0,0 +1,128 @@ +import { DropdownOption, Property } from '@activepieces/pieces-framework'; +import { TaskadeAPIClient } from './client'; + +const createEmptyOptions = (placeholder: string) => { + return { + disabled: true, + options: [], + placeholder, + }; +}; + +export const taskadeProps = { + workspace_id: Property.Dropdown({ + displayName: 'Workspace', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return createEmptyOptions('Please connect account first.'); + } + + const client = new TaskadeAPIClient(auth as string); + const response = await client.listWorkspaces(); + + const options: DropdownOption[] = []; + + for (const workspace of response.items) { + options.push({ label: workspace.name, value: workspace.id }); + } + + return { + disabled: false, + options, + }; + }, + }), + folder_id: Property.Dropdown({ + displayName: 'Folder', + refreshers: ['workspace_id'], + required: false, + options: async ({ auth, workspace_id }) => { + if (!auth) { + return createEmptyOptions('Please connect account first.'); + } + if (!workspace_id) { + return createEmptyOptions('Please select workspace.'); + } + + const client = new TaskadeAPIClient(auth as string); + const response = await client.listWorkspaceFolders(workspace_id as string); + + const options: DropdownOption[] = []; + + for (const folder of response.items) { + options.push({ label: folder.name, value: folder.id }); + } + + return { + disabled: false, + options, + }; + }, + }), + project_id: Property.Dropdown({ + displayName: 'Project', + refreshers: ['workspace_id', 'folder_id'], + required: true, + options: async ({ auth, workspace_id, folder_id }) => { + if (!auth) { + return createEmptyOptions('Please connect account first.'); + } + if (!workspace_id) { + return createEmptyOptions('Please select workspace.'); + } + + const workspaceId = workspace_id as string; + const folderId = (folder_id as string) ?? workspaceId; + + const client = new TaskadeAPIClient(auth as string); + const response = await client.listProjects(folderId as string); + + const options: DropdownOption[] = []; + + for (const project of response.items) { + options.push({ label: project.name, value: project.id }); + } + + return { + disabled: false, + options, + }; + }, + }), + task_id: Property.Dropdown({ + displayName: 'Task', + refreshers: ['project_id'], + required: true, + options: async ({ auth, project_id }) => { + if (!auth) { + return createEmptyOptions('Please connect account first.'); + } + if (!project_id) { + return createEmptyOptions('Please select project.'); + } + + const client = new TaskadeAPIClient(auth as string); + const options: DropdownOption[] = []; + + let after; + let moreTasks = true; + while (moreTasks) { + const response = await client.listTasks(project_id as string, { limit: 100, after }); + if (response.items.length === 0) { + moreTasks = false; + } else { + after = response.items[response.items.length - 1].id; + for (const task of response.items) { + options.push({ label: task.text, value: task.id }); + } + } + } + return { + disabled: false, + options, + }; + }, + }), +}; diff --git a/packages/pieces/community/taskade/src/lib/common/types.ts b/packages/pieces/community/taskade/src/lib/common/types.ts new file mode 100644 index 0000000..2346350 --- /dev/null +++ b/packages/pieces/community/taskade/src/lib/common/types.ts @@ -0,0 +1,42 @@ +export interface ListAPIResponse { + ok: boolean; + items: Array; +} + +export interface BaseResponse { + id: string; + name: string; +} + +export type WorkspaceResponse = BaseResponse; +export type WorkspaceFolderResponse = BaseResponse; +export type ProjectResponse = BaseResponse; + +export interface CreateTaskParams { + contentType: string; + content: string; + placement: string; +} + +export interface TaskResponse { + id: string; + parentId: string; + text: string; + completed: boolean; +} + +export interface CreateTaskResponse { + ok: boolean; + item: TaskResponse[]; +} + +export interface CreateTaskDateParams { + start: { + date: string; + time: string; + }; + end?: { + date: string; + time: string; + }; +} diff --git a/packages/pieces/community/taskade/tsconfig.json b/packages/pieces/community/taskade/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/taskade/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/taskade/tsconfig.lib.json b/packages/pieces/community/taskade/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/taskade/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/tavily/.eslintrc.json b/packages/pieces/community/tavily/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/tavily/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/tavily/README.md b/packages/pieces/community/tavily/README.md new file mode 100644 index 0000000..e0e46df --- /dev/null +++ b/packages/pieces/community/tavily/README.md @@ -0,0 +1,25 @@ +# Tavily + +## Description +Tavily is an AI-powered search engine designed for accurate and real-time information retrieval. This piece enables integration with Tavily's API to perform searches and extract information. + +## Actions +1. **Search**: Perform a search query using Tavily's AI search engine + - Supports both basic and advanced search depths + - Optional AI-generated answer + - Configurable result limits + - Raw content inclusion option + +2. **Extract**: Extract structured data from a webpage using Tavily's extraction API + - Clean and formatted content extraction + - Supports various content types + - Removes ads and irrelevant content + +## Authentication +This piece requires an API key from Tavily. To obtain your API key: +1. Visit https://tavily.com/ and sign up for an account +2. Navigate to your dashboard +3. Copy your API key from the dashboard + +## Requirements +- Tavily API Key diff --git a/packages/pieces/community/tavily/package.json b/packages/pieces/community/tavily/package.json new file mode 100644 index 0000000..711f8f4 --- /dev/null +++ b/packages/pieces/community/tavily/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-tavily", + "version": "0.0.1" +} diff --git a/packages/pieces/community/tavily/project.json b/packages/pieces/community/tavily/project.json new file mode 100644 index 0000000..839ec3f --- /dev/null +++ b/packages/pieces/community/tavily/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-tavily", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/tavily/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/tavily", + "tsConfig": "packages/pieces/community/tavily/tsconfig.lib.json", + "packageJson": "packages/pieces/community/tavily/package.json", + "main": "packages/pieces/community/tavily/src/index.ts", + "assets": [ + "packages/pieces/community/tavily/*.md", + { + "input": "packages/pieces/community/tavily/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/tavily/src/index.ts b/packages/pieces/community/tavily/src/index.ts new file mode 100644 index 0000000..c450a94 --- /dev/null +++ b/packages/pieces/community/tavily/src/index.ts @@ -0,0 +1,55 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { searchAction } from './lib/actions/search'; +import { extractAction } from './lib/actions/extract'; + +const markdownDescription = ` +Follow these steps to obtain your Tavily API Key: + +1. Visit [tavily](https://tavily.com/) and create an account. +2. Log in and navigate to your dashboard. +3. Locate and copy your API key from the dashboard. +`; + +export const tavilyAuth = PieceAuth.SecretText({ + description: markdownDescription, + displayName: 'API Key', + required: true, + validate: async ({ auth }) => { + try { + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.tavily.com/search', + headers: { + 'Content-Type': 'application/json', + }, + body: { + api_key: auth, + query: 'test', + search_depth: 'basic', + }, + }); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API Key', + }; + } + }, +}); + +export const tavily = createPiece({ + displayName: 'Tavily', + description: 'Search engine tailored for AI agents.', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/tavily.jpg', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + authors: ['OsamaHaikal'], + auth: tavilyAuth, + actions: [searchAction, extractAction], + triggers: [], +}); diff --git a/packages/pieces/community/tavily/src/lib/actions/extract.ts b/packages/pieces/community/tavily/src/lib/actions/extract.ts new file mode 100644 index 0000000..86de58a --- /dev/null +++ b/packages/pieces/community/tavily/src/lib/actions/extract.ts @@ -0,0 +1,39 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { tavilyAuth } from '../../index'; + +export const extractAction = createAction({ + name: 'extract', + displayName: 'Extract Content', + description: 'Retrieve raw web content from specified URLs.', + auth: tavilyAuth, + props: { + urls: Property.Array({ + displayName: 'URLs', + description: 'The URLs you want to extract with Tavily.', + required: true, + }), + include_images: Property.Checkbox({ + displayName: 'Include Images', + description: 'Include a list of images extracted from the URLs in the response.', + required: false, + defaultValue: false, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.tavily.com/extract', + headers: { + 'Content-Type': 'application/json', + }, + body: { + api_key: auth, + urls: propsValue.urls, + include_images: propsValue.include_images, + }, + }); + + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/tavily/src/lib/actions/search.ts b/packages/pieces/community/tavily/src/lib/actions/search.ts new file mode 100644 index 0000000..570b219 --- /dev/null +++ b/packages/pieces/community/tavily/src/lib/actions/search.ts @@ -0,0 +1,130 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { tavilyAuth } from '../../index'; + +export const searchAction = createAction({ + name: 'search', + displayName: 'Search', + description: 'Search for data based on a query.', + auth: tavilyAuth, + props: { + query: Property.LongText({ + displayName: 'Search Query', + description: 'The search query you want to execute with Tavily.', + required: true, + }), + search_depth: Property.StaticDropdown({ + displayName: 'Search Depth', + description: 'The depth of the search. It can be "basic" or "advanced".', + required: false, + defaultValue: 'basic', + options: { + options: [ + { label: 'Basic', value: 'basic' }, + { label: 'Advanced', value: 'advanced' }, + ], + }, + }), + topic: Property.StaticDropdown({ + displayName: 'Topic', + description: 'The category of the search. This will determine which of our agents will be used for the search.', + required: false, + defaultValue: 'general', + options: { + options: [ + { label: 'General', value: 'general' }, + { label: 'News', value: 'news' }, + ], + }, + }), + days: Property.Number({ + displayName: 'Days', + description: 'The number of days back from the current date to include in the search results. Only available when using the "news" search topic. Default is 3.', + required: false, + defaultValue: 3, + }), + time_range: Property.StaticDropdown({ + displayName: 'Time Range', + description: 'The time range back from the current date to include in the search results.', + required: false, + options: { + options: [ + { label: 'Day', value: 'day' }, + { label: 'Week', value: 'week' }, + { label: 'Month', value: 'month' }, + { label: 'Year', value: 'year' }, + { label: 'Day (Short)', value: 'd' }, + { label: 'Week (Short)', value: 'w' }, + { label: 'Month (Short)', value: 'm' }, + { label: 'Year (Short)', value: 'y' }, + ], + }, + }), + max_results: Property.Number({ + displayName: 'Maximum Results', + description: 'The maximum number of search results to return.', + required: false, + defaultValue: 5, + }), + include_images: Property.Checkbox({ + displayName: 'Include Images', + description: 'Include a list of query-related images in the response.', + required: false, + defaultValue: false, + }), + include_image_descriptions: Property.Checkbox({ + displayName: 'Include Image Descriptions', + description: 'When include_images is set to True, this option adds descriptive text for each image.', + required: false, + defaultValue: false, + }), + include_answer: Property.Checkbox({ + displayName: 'Include Answer', + description: 'Include a short answer to original query, generated by an LLM based on Tavily\'s search results.', + required: false, + defaultValue: false, + }), + include_raw_content: Property.Checkbox({ + displayName: 'Include Raw Content', + description: 'Include the cleaned and parsed HTML content of each search result.', + required: false, + defaultValue: false, + }), + include_domains: Property.Array({ + displayName: 'Include Domains', + description: 'A list of domains to specifically include in the search results.', + required: false, + }), + exclude_domains: Property.Array({ + displayName: 'Exclude Domains', + description: 'A list of domains to specifically exclude from the search results.', + required: false, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.tavily.com/search', + headers: { + 'Content-Type': 'application/json', + }, + body: { + api_key: auth, + query: propsValue.query, + search_depth: propsValue.search_depth, + topic: propsValue.topic, + days: propsValue.days, + time_range: propsValue.time_range, + max_results: propsValue.max_results, + include_images: propsValue.include_images, + include_image_descriptions: propsValue.include_image_descriptions, + include_answer: propsValue.include_answer, + include_raw_content: propsValue.include_raw_content, + include_domains: propsValue.include_domains, + exclude_domains: propsValue.exclude_domains, + }, + }); + + return response.body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/tavily/tsconfig.json b/packages/pieces/community/tavily/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/tavily/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/tavily/tsconfig.lib.json b/packages/pieces/community/tavily/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/tavily/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/telegram-bot/.babelrc b/packages/pieces/community/telegram-bot/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/telegram-bot/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/telegram-bot/.eslintrc.json b/packages/pieces/community/telegram-bot/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/telegram-bot/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/telegram-bot/README.md b/packages/pieces/community/telegram-bot/README.md new file mode 100644 index 0000000..c18eb74 --- /dev/null +++ b/packages/pieces/community/telegram-bot/README.md @@ -0,0 +1,7 @@ +# pieces-telegram-bot + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-telegram-bot` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/telegram-bot/package.json b/packages/pieces/community/telegram-bot/package.json new file mode 100644 index 0000000..5c13673 --- /dev/null +++ b/packages/pieces/community/telegram-bot/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-telegram-bot", + "version": "0.3.14" +} \ No newline at end of file diff --git a/packages/pieces/community/telegram-bot/project.json b/packages/pieces/community/telegram-bot/project.json new file mode 100644 index 0000000..6cb4374 --- /dev/null +++ b/packages/pieces/community/telegram-bot/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-telegram-bot", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/telegram-bot/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/telegram-bot", + "tsConfig": "packages/pieces/community/telegram-bot/tsconfig.lib.json", + "packageJson": "packages/pieces/community/telegram-bot/package.json", + "main": "packages/pieces/community/telegram-bot/src/index.ts", + "assets": [ + "packages/pieces/community/telegram-bot/*.md", + { + "input": "packages/pieces/community/telegram-bot/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/telegram-bot/src/index.ts b/packages/pieces/community/telegram-bot/src/index.ts new file mode 100644 index 0000000..1e459cf --- /dev/null +++ b/packages/pieces/community/telegram-bot/src/index.ts @@ -0,0 +1,47 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { telegramCreateInviteLinkAction } from './lib/action/create-invite-link'; +import { telegramGetChatMemberAction } from './lib/action/get-chat-member'; +import { telegramSendMediaAction } from './lib/action/send-media.action'; +import { telegramSendMessageAction } from './lib/action/send-text-message.action'; +import { telegramCommons } from './lib/common'; +import { telegramNewMessage } from './lib/trigger/new-message'; + +const markdownDescription = ` +**Authentication**: + +1. Begin a conversation with the [Botfather](https://telegram.me/BotFather). +2. Type in "/newbot" +3. Choose a name for your bot +4. Choose a username for your bot. +5. Copy the token value from the Botfather and use it activepieces connection. +6. Congratulations! You can now use your new Telegram connection in your flows. +`; + +export const telegramBotAuth = PieceAuth.SecretText({ + displayName: 'Bot Token', + description: markdownDescription, + required: true, +}); + +export const telegramBot = createPiece({ + displayName: 'Telegram Bot', + description: 'Build chatbots for Telegram', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/telegram_bot.png', + categories: [PieceCategory.COMMUNICATION], + auth: telegramBotAuth, + actions: [ + telegramSendMessageAction, + telegramSendMediaAction, + telegramGetChatMemberAction, + telegramCreateInviteLinkAction, + createCustomApiCallAction({ + baseUrl: (auth) => telegramCommons.getApiUrl(auth as string, ''), + auth: telegramBotAuth, + }), + ], + authors: ["abdullahranginwala","tanoggy","alerdenisov","Abdallah-Alwarawreh","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + triggers: [telegramNewMessage], +}); diff --git a/packages/pieces/community/telegram-bot/src/lib/action/create-invite-link.ts b/packages/pieces/community/telegram-bot/src/lib/action/create-invite-link.ts new file mode 100644 index 0000000..70262bc --- /dev/null +++ b/packages/pieces/community/telegram-bot/src/lib/action/create-invite-link.ts @@ -0,0 +1,65 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { telegramBotAuth } from '../..'; +import { telegramCommons } from '../common'; + +const chatId = ` +**How to obtain Chat ID:** +1. Search for the bot "@getmyid_bot" in Telegram. +2. Start a conversation with the bot. +3. Send the command "/my_id" to the bot. +4. The bot will reply with your chat ID. + +**Note: Remember to initiate the chat with the bot, or you'll get an error for "chat not found.** +`; +const format = ` +[Link example](https://core.telegram.org/bots/api#formatting-options) +`; +export const telegramCreateInviteLinkAction = createAction({ + auth: telegramBotAuth, + name: 'create_invite_link', + description: 'Create an invite link for a chat', + displayName: 'Create Invite Link', + props: { + instructions: Property.MarkDown({ + value: chatId, + }), + chat_id: Property.ShortText({ + displayName: 'Chat Id', + required: true, + }), + name: Property.ShortText({ + displayName: 'Name', + description: 'Name of the invite link (max 32 chars)', + required: false, + }), + expire_date: Property.DateTime({ + displayName: 'Expire Date', + description: 'Point in time when the link will expire', + required: false, + }), + member_limit: Property.Number({ + displayName: 'Member Limit', + description: + 'Maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999', + required: false, + }), + }, + async run(ctx) { + return await httpClient + .sendRequest({ + method: HttpMethod.POST, + url: telegramCommons.getApiUrl(ctx.auth, 'createChatInviteLink'), + headers: {}, + body: { + chat_id: ctx.propsValue.chat_id, + name: ctx.propsValue.name ?? undefined, + expire_date: ctx.propsValue.expire_date + ? Math.floor(new Date(ctx.propsValue.expire_date).getTime() / 1000) + : undefined, + member_limit: ctx.propsValue.member_limit ?? undefined, + }, + }) + .then((res) => res.body); + }, +}); diff --git a/packages/pieces/community/telegram-bot/src/lib/action/get-chat-member.ts b/packages/pieces/community/telegram-bot/src/lib/action/get-chat-member.ts new file mode 100644 index 0000000..c9a14da --- /dev/null +++ b/packages/pieces/community/telegram-bot/src/lib/action/get-chat-member.ts @@ -0,0 +1,54 @@ +import { httpClient, HttpError, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { telegramBotAuth } from '../..'; +import { telegramCommons } from '../common'; + +const chatId = ` +**How to obtain Chat ID:** +1. Search for the bot "@getmyid_bot" in Telegram. +2. Start a conversation with the bot. +3. Send the command "/my_id" to the bot. +4. The bot will reply with your chat ID. + +**Note: Remember to initiate the chat with the bot, or you'll get an error for "chat not found.** +`; +const format = ` +[Link example](https://core.telegram.org/bots/api#formatting-options) +`; +export const telegramGetChatMemberAction = createAction({ + auth: telegramBotAuth, + name: 'get_chat_member', + description: 'Get member info (or null) for provided chat id and user id', + displayName: 'Get Chat Member', + props: { + instructions: Property.MarkDown({ + value: chatId, + }), + chat_id: Property.ShortText({ + displayName: 'Chat Id', + required: true, + }), + user_id: Property.ShortText({ + displayName: 'User Id', + description: 'Unique identifier for the user', + required: true, + }), + }, + async run(ctx) { + try { + return await httpClient + .sendRequest({ + method: HttpMethod.POST, + url: telegramCommons.getApiUrl(ctx.auth, 'getChatMember'), + headers: {}, + body: { + chat_id: ctx.propsValue.chat_id, + user_id: ctx.propsValue.user_id, + }, + }) + .then((res) => res.body); + } catch (error) { + return (error as HttpError).errorMessage().response.body; + } + }, +}); diff --git a/packages/pieces/community/telegram-bot/src/lib/action/send-media.action.ts b/packages/pieces/community/telegram-bot/src/lib/action/send-media.action.ts new file mode 100644 index 0000000..79ea933 --- /dev/null +++ b/packages/pieces/community/telegram-bot/src/lib/action/send-media.action.ts @@ -0,0 +1,265 @@ +import { + ApFile, + createAction, + DynamicPropsValue, + Property, +} from '@activepieces/pieces-framework'; +import { + HttpMessageBody, + HttpMethod, + QueryParams, + httpClient, +} from '@activepieces/pieces-common'; +import { telegramCommons } from '../common'; +import { telegramBotAuth } from '../..'; +import FormData from 'form-data'; + +const chatId = ` + +**How to obtain Chat ID:** +1. Search for the bot "@getmyid_bot" in Telegram. +2. Start a conversation with the bot. +3. Send the command "/my_id" to the bot. +4. The bot will reply with your chat ID. + +**Note: Remember to initiate the chat with the bot, or you'll get an error for "chat not found.** +`; +const format = ` +[Link example](https://core.telegram.org/bots/api#formatting-options) +`; +export const telegramSendMediaAction = createAction({ + auth: telegramBotAuth, + name: 'send_media', + description: 'Send a media message through a Telegram bot', + displayName: 'Send Media', + props: { + instructions: Property.MarkDown({ + value: chatId, + }), + chat_id: Property.ShortText({ + displayName: 'Chat Id', + required: true, + }), + message_thread_id: Property.ShortText({ + displayName: 'Message Thread Id', + description: + 'Unique identifier for the target message thread of the forums; for forums supergroups only', + required: false, + }), + media_type: Property.StaticDropdown({ + displayName: 'Media Type', + required: false, + options: { + disabled: false, + placeholder: 'Select media type', + options: [ + { label: 'Image', value: 'photo' }, + { label: 'Video', value: 'video' }, + { label: 'Sticker', value: 'sticker' }, + { label: 'GIF', value: 'animation' }, + ], + }, + }), + media: Property.DynamicProperties({ + displayName: 'Media Properties', + required: false, + refreshers: ['media_type'], + async props({ media_type }) { + const propsBuilders: Record DynamicPropsValue> = { + photo: () => ({ + photo: Property.File({ + displayName: 'Image', + description: 'The image to be uploaded as a file', + required: false, + }), + photoUrl: Property.ShortText({ + displayName: 'Image Url', + description: 'The image url to be downloaded by Telegram', + required: false, + }), + photoId: Property.ShortText({ + displayName: 'Image Id', + description: + "The image id previously uploaded to Telegram's servers", + required: false, + }), + }), + video: () => ({ + video: Property.File({ + displayName: 'Video', + description: 'The video to be uploaded as a file', + required: false, + }), + videoUrl: Property.ShortText({ + displayName: 'Video Url', + description: 'The video url to be downloaded by Telegram', + required: false, + }), + videoId: Property.ShortText({ + displayName: 'Video Id', + description: + "The video id previously uploaded to Telegram's servers", + required: false, + }), + }), + sticker: () => ({ + sticker: Property.File({ + displayName: 'Sticker', + description: + 'The sticker to be uploaded as a file (supports .WEBP files for static and .TGS for animated)', + required: false, + }), + emoji: Property.ShortText({ + displayName: 'Emoji', + description: + 'Emoji associated with the sticker. Only for just uploaded stickers', + required: false, + }), + stickerUrl: Property.ShortText({ + displayName: 'Sticker Url', + description: + 'The static sticker url to be downloaded by Telegram (supports only .WEBP files)', + required: false, + }), + stickerId: Property.ShortText({ + displayName: 'Sticker Id', + description: + "The sticker id previously uploaded to Telegram's servers", + required: false, + }), + }), + animation: () => ({ + animation: Property.File({ + displayName: 'GIF', + description: + 'The GIF or MPEG-4 without sound file to be uploaded as a auto-playing animation', + required: false, + }), + animationUrl: Property.ShortText({ + displayName: 'GIF Url', + description: + 'The GIF or MPEG-4 without sound url to be downloaded by Telegram', + required: false, + }), + animationId: Property.ShortText({ + displayName: 'GIF Id', + description: + "The GIF or MPEG-4 without sound id previously uploaded to Telegram's servers", + required: false, + }), + duration: Property.Number({ + displayName: 'Duration', + description: 'Duration of sent video in seconds', + required: false, + }), + }), + }; + return propsBuilders[media_type as unknown as string](); + }, + }), + format: Property.StaticDropdown({ + displayName: 'Format', + description: 'Choose format you want ', + required: false, + options: { + options: [ + { + label: 'Markdown', + value: 'MarkdownV2', + }, + { + label: 'HTML', + value: 'HTML', + }, + ], + }, + defaultValue: 'MarkdownV2', + }), + instructions_format: Property.MarkDown({ + value: format, + }), + message: Property.LongText({ + displayName: 'Message', + description: 'The message to be sent', + required: true, + }), + reply_markup: Property.Json({ + required: false, + displayName: 'Reply Markup', + description: + 'Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. Use special actions such as Build Inline Keyboard to generate this JSON object.', + }), + }, + async run(ctx) { + const mediaType = ctx.propsValue['media_type']; + const headers: Record = {}; + const queryParams: QueryParams = {}; + let body: HttpMessageBody | undefined = undefined; + let method = 'sendMessage'; + if (typeof mediaType !== 'undefined') { + // send media message + const [file, url, id] = [ + ctx.propsValue.media?.[mediaType] as ApFile, + ctx.propsValue.media?.[mediaType + 'Url'] as string, + ctx.propsValue.media?.[mediaType + 'Id'] as string, + ]; + + const methods: Partial> = { + photo: 'sendPhoto', + video: 'sendVideo', + sticker: 'sendSticker', + animation: 'sendAnimation', + }; + + const mediaMethod = methods[mediaType]; + + if (!mediaMethod) { + throw new Error('Unknown media type method (' + mediaType + ')'); + } + method = mediaMethod; + + if (typeof file !== 'undefined') { + // upload + headers['Content-Type'] = 'multipart/form-data'; + const form = new FormData(); + form.append('file', file.data, file.extension); + body = form; + queryParams.chat_id = ctx.propsValue['chat_id']; + queryParams.caption = ctx.propsValue['message']; + if (ctx.propsValue['message_thread_id']) + queryParams.message_thread_id = ctx.propsValue['message_thread_id']; + queryParams.parse_mode = ctx.propsValue['format'] ?? 'MarkdownV2'; + + // TODO: research how to + // if (ctx.propsValue['reply_markup']) + // queryParams.reply_markup = ctx.propsValue['reply_markup']; + } else if (typeof url !== 'undefined' || typeof id !== 'undefined') { + // download + body = body || {}; + body[mediaType] = url ?? id; + body.chat_id = ctx.propsValue['chat_id']; + body.caption = ctx.propsValue['message']; + body.message_thread_id = + ctx.propsValue['message_thread_id'] ?? undefined; + body.parse_mode = ctx.propsValue['format'] ?? 'MarkdownV2'; + body.reply_markup = ctx.propsValue['reply_markup'] ?? undefined; + } else { + throw new Error( + 'No media defined. Ensure you have setup file, url or id' + ); + } + } + + if (typeof body === 'undefined') { + throw new Error('No body defined'); + } + + return await httpClient.sendRequest({ + method: HttpMethod.POST, + url: telegramCommons.getApiUrl(ctx.auth, method), + headers, + body, + queryParams, + }); + }, +}); diff --git a/packages/pieces/community/telegram-bot/src/lib/action/send-text-message.action.ts b/packages/pieces/community/telegram-bot/src/lib/action/send-text-message.action.ts new file mode 100644 index 0000000..1472f9d --- /dev/null +++ b/packages/pieces/community/telegram-bot/src/lib/action/send-text-message.action.ts @@ -0,0 +1,91 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { telegramCommons } from '../common'; +import { telegramBotAuth } from '../..'; + +const chatId = ` + +**How to obtain Chat ID:** +1. Search for the bot "@getmyid_bot" in Telegram. +2. Start a conversation with the bot. +3. Send the command "/my_id" to the bot. +4. The bot will reply with your chat ID. + +**Note: Remember to initiate the chat with the bot, or you'll get an error for "chat not found.** +`; +const format = ` +[Link example](https://core.telegram.org/bots/api#formatting-options) +`; +export const telegramSendMessageAction = createAction({ + auth: telegramBotAuth, + name: 'send_text_message', + description: 'Send a message through a Telegram bot', + displayName: 'Send Text Message', + props: { + instructions: Property.MarkDown({ + value: chatId, + }), + chat_id: Property.ShortText({ + displayName: 'Chat Id', + required: true, + }), + message_thread_id: Property.ShortText({ + displayName: 'Message Thread Id', + description: + 'Unique identifier for the target message thread of the forums; for forums supergroups only', + required: false, + }), + format: Property.StaticDropdown({ + displayName: 'Format', + description: 'Choose format you want ', + required: false, + options: { + options: [ + { + label: 'Markdown', + value: 'MarkdownV2', + }, + { + label: 'HTML', + value: 'HTML', + }, + ], + }, + defaultValue: 'MarkdownV2', + }), + instructions_format: Property.MarkDown({ + value: format, + }), + web_page_preview: Property.Checkbox({ + displayName: 'Disable Web Page Preview', + description: 'Disable link previews for links in this message', + required: false, + defaultValue: false, + }), + message: Property.LongText({ + displayName: 'Message', + description: 'The message to be sent', + required: true, + }), + reply_markup: Property.Json({ + required: false, + displayName: 'Reply Markup', + description: + 'Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. Use special actions such as Build Inline Keyboard to generate this JSON object.', + }), + }, + async run(ctx) { + return await httpClient.sendRequest({ + method: HttpMethod.POST, + url: telegramCommons.getApiUrl(ctx.auth, 'sendMessage'), + body: { + chat_id: ctx.propsValue['chat_id'], + text: ctx.propsValue['message'], + message_thread_id: ctx.propsValue['message_thread_id'] ?? undefined, + parse_mode: ctx.propsValue['format'] ?? 'MarkdownV2', + reply_markup: ctx.propsValue['reply_markup'] ?? undefined, + disable_web_page_preview: ctx.propsValue['web_page_preview'] ?? false, + }, + }); + }, +}); diff --git a/packages/pieces/community/telegram-bot/src/lib/common/index.ts b/packages/pieces/community/telegram-bot/src/lib/common/index.ts new file mode 100644 index 0000000..5d13dd8 --- /dev/null +++ b/packages/pieces/community/telegram-bot/src/lib/common/index.ts @@ -0,0 +1,43 @@ +import { + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; + +export type SetWebhookRequest = { + ip_address: string; + max_connections: number; + allowed_updates: string[]; + drop_pending_updates: boolean; + secret_token: string; +}; + +export const telegramCommons = { + getApiUrl: (botToken: string, methodName: string) => { + return `https://api.telegram.org/bot${botToken}/${methodName}`; + }, + subscribeWebhook: async ( + botToken: string, + webhookUrl: string, + overrides?: Partial + ) => { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://api.telegram.org/bot${botToken}/setWebhook`, + body: { + allowed_updates: [], + url: webhookUrl, + ...overrides, + }, + }; + + await httpClient.sendRequest(request); + }, + unsubscribeWebhook: async (botToken: string) => { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `https://api.telegram.org/bot${botToken}/deleteWebhook`, + }; + return await httpClient.sendRequest(request); + }, +}; diff --git a/packages/pieces/community/telegram-bot/src/lib/trigger/new-message.ts b/packages/pieces/community/telegram-bot/src/lib/trigger/new-message.ts new file mode 100644 index 0000000..11bee88 --- /dev/null +++ b/packages/pieces/community/telegram-bot/src/lib/trigger/new-message.ts @@ -0,0 +1,63 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { telegramCommons } from '../common'; +import { telegramBotAuth } from '../..'; +import { httpClient, HttpMethod, HttpRequest } from '@activepieces/pieces-common'; + +export const telegramNewMessage = createTrigger({ + auth: telegramBotAuth, + name: 'new_telegram_message', + displayName: 'New message', + description: 'Triggers when Telegram receives a new message', + props: {}, + type: TriggerStrategy.WEBHOOK, + sampleData: { + body: { + message: { + chat: { + id: 55169542059, + type: 'private', + username: 'AbdallahAlwarawreh', + last_name: 'Alwarawreh', + first_name: 'Abdallah', + }, + date: 1686050152, + from: { + id: 55169542059, + is_bot: false, + username: 'AbdallahAlwarawreh', + last_name: 'Alwarawreh', + first_name: 'Abdallah', + language_code: 'en', + }, + parse_mode: 'MarkdownV2', + text: 'Hello world', + message_id: 21, + }, + update_id: 351114420, + }, + }, + async onEnable(context) { + await telegramCommons.subscribeWebhook(context.auth, context.webhookUrl, { + allowed_updates: [], + }); + }, + async onDisable(context) { + await telegramCommons.unsubscribeWebhook(context.auth); + }, + async run(context) { + return [context.payload.body]; + }, + async test(context) { + const messages = await getLastFiveMessages(context.auth) + return messages.result + }, +}); + +const getLastFiveMessages = async (botToken: string) => { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `https://api.telegram.org/bot${botToken}/getUpdates?offset=-5`, + }; + const response = await httpClient.sendRequest(request); + return response.body; +} \ No newline at end of file diff --git a/packages/pieces/community/telegram-bot/tsconfig.json b/packages/pieces/community/telegram-bot/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/telegram-bot/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/telegram-bot/tsconfig.lib.json b/packages/pieces/community/telegram-bot/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/telegram-bot/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/text-ai/.eslintrc.json b/packages/pieces/community/text-ai/.eslintrc.json new file mode 100644 index 0000000..eac708a --- /dev/null +++ b/packages/pieces/community/text-ai/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/text-ai/README.md b/packages/pieces/community/text-ai/README.md new file mode 100644 index 0000000..a3d1012 --- /dev/null +++ b/packages/pieces/community/text-ai/README.md @@ -0,0 +1,7 @@ +# pieces-text-ai + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-text-ai` to build the library. diff --git a/packages/pieces/community/text-ai/package.json b/packages/pieces/community/text-ai/package.json new file mode 100644 index 0000000..7648ef9 --- /dev/null +++ b/packages/pieces/community/text-ai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-text-ai", + "version": "0.2.2" +} diff --git a/packages/pieces/community/text-ai/project.json b/packages/pieces/community/text-ai/project.json new file mode 100644 index 0000000..ac49f95 --- /dev/null +++ b/packages/pieces/community/text-ai/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-text-ai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/text-ai/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/text-ai", + "tsConfig": "packages/pieces/community/text-ai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/text-ai/package.json", + "main": "packages/pieces/community/text-ai/src/index.ts", + "assets": [ + "packages/pieces/community/text-ai/*.md", + { + "input": "packages/pieces/community/text-ai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/text-ai/src/index.ts b/packages/pieces/community/text-ai/src/index.ts new file mode 100644 index 0000000..b0621bb --- /dev/null +++ b/packages/pieces/community/text-ai/src/index.ts @@ -0,0 +1,18 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { askAI } from './lib/actions/ask-ai'; +import { PieceCategory } from '@activepieces/shared'; +import { summarizeText } from './lib/actions/summarize-text'; + +export const activepiecesAi = createPiece({ + displayName: 'Text AI', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.63.0', + categories: [ + PieceCategory.ARTIFICIAL_INTELLIGENCE, + PieceCategory.UNIVERSAL_AI, + ], + logoUrl: 'https://cdn.activepieces.com/pieces/text-ai.svg', + authors: ['anasbarg', 'amrdb'], + actions: [askAI, summarizeText], + triggers: [], +}); diff --git a/packages/pieces/community/text-ai/src/lib/actions/ask-ai.ts b/packages/pieces/community/text-ai/src/lib/actions/ask-ai.ts new file mode 100644 index 0000000..69a9295 --- /dev/null +++ b/packages/pieces/community/text-ai/src/lib/actions/ask-ai.ts @@ -0,0 +1,99 @@ +import { aiProps } from '@activepieces/pieces-common'; +import { SUPPORTED_AI_PROVIDERS, createAIProvider } from '@activepieces/shared'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { CoreMessage, LanguageModel, generateText } from 'ai'; + +export const askAI = createAction({ + name: 'askAi', + displayName: 'Ask AI', + description: '', + props: { + provider: aiProps({ modelType: 'language' }).provider, + model: aiProps({ modelType: 'language' }).model, + prompt: Property.LongText({ + displayName: 'Prompt', + required: true, + }), + conversationKey: Property.ShortText({ + displayName: 'Conversation Key', + required: false, + }), + creativity: Property.Number({ + displayName: 'Creativity', + required: false, + defaultValue: 100, + description: + 'Controls the creativity of the AI response. A higher value will make the AI more creative and a lower value will make it more deterministic.', + }), + maxTokens: Property.Number({ + displayName: 'Max Tokens', + required: false, + defaultValue: 2000, + }), + }, + async run(context) { + const providerName = context.propsValue.provider as string; + const modelInstance = context.propsValue.model as LanguageModel; + const storage = context.store; + + const providerConfig = SUPPORTED_AI_PROVIDERS.find(p => p.provider === providerName); + if (!providerConfig) { + throw new Error(`Provider ${providerName} not found`); + } + + const baseURL = `${context.server.apiUrl}v1/ai-providers/proxy/${providerName}`; + const engineToken = context.server.token; + const provider = createAIProvider({ + providerName, + modelInstance, + apiKey: engineToken, + baseURL, + }); + + const conversationKey = context.propsValue.conversationKey + ? `ask-ai-conversation:${context.propsValue.conversationKey}` + : null; + + let conversation = null; + if (conversationKey) { + conversation = (await storage.get( + conversationKey + )) ?? []; + if (!conversation) { + await storage.put(conversationKey, { messages: [] }); + } + } + + const response = await generateText({ + model: provider, + messages: [ + ...(conversation ?? []), + { + role: 'user', + content: context.propsValue.prompt, + }, + ], + maxTokens: context.propsValue.maxTokens, + temperature: (context.propsValue.creativity ?? 100) / 100, + headers: { + 'Authorization': `Bearer ${engineToken}`, + }, + }); + + conversation?.push({ + role: 'user', + content: context.propsValue.prompt, + }); + + conversation?.push({ + role: 'assistant', + content: response.text ?? '', + }); + + if (conversationKey) { + await storage.put(conversationKey, conversation); + } + + return response.text ?? ''; + }, +}); diff --git a/packages/pieces/community/text-ai/src/lib/actions/summarize-text.ts b/packages/pieces/community/text-ai/src/lib/actions/summarize-text.ts new file mode 100644 index 0000000..77ab940 --- /dev/null +++ b/packages/pieces/community/text-ai/src/lib/actions/summarize-text.ts @@ -0,0 +1,63 @@ +import { aiProps } from '@activepieces/pieces-common'; +import { SUPPORTED_AI_PROVIDERS, createAIProvider } from '@activepieces/shared'; +import { createAction, Property, Action } from '@activepieces/pieces-framework'; +import { LanguageModel, generateText } from 'ai'; + +export const summarizeText: Action = createAction({ + name: 'summarizeText', + displayName: 'Summarize Text', + description: '', + props: { + provider: aiProps({ modelType: 'language' }).provider, + model: aiProps({ modelType: 'language' }).model, + text: Property.LongText({ + displayName: 'Text', + required: true, + }), + prompt: Property.ShortText({ + displayName: 'Prompt', + defaultValue: + 'Summarize the following text in a clear and concise manner, capturing the key points and main ideas while keeping the summary brief and informative.', + required: true, + }), + maxTokens: Property.Number({ + displayName: 'Max Tokens', + required: false, + defaultValue: 2000, + }), + }, + async run(context) { + const providerName = context.propsValue.provider as string; + const modelInstance = context.propsValue.model as LanguageModel; + + const providerConfig = SUPPORTED_AI_PROVIDERS.find(p => p.provider === providerName); + if (!providerConfig) { + throw new Error(`Provider ${providerName} not found`); + } + + const baseURL = `${context.server.apiUrl}v1/ai-providers/proxy/${providerName}`; + const engineToken = context.server.token; + const provider = createAIProvider({ + providerName, + modelInstance, + apiKey: engineToken, + baseURL, + }); + + const response = await generateText({ + model: provider, + messages: [ + { + role: 'user', + content: `${context.propsValue.prompt} Summarize the following text : ${context.propsValue.text}`, + }, + ], + maxTokens: context.propsValue.maxTokens, + headers: { + 'Authorization': `Bearer ${engineToken}`, + }, + }); + + return response.text ?? ''; + }, +}); diff --git a/packages/pieces/community/text-ai/tsconfig.json b/packages/pieces/community/text-ai/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/text-ai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/text-ai/tsconfig.lib.json b/packages/pieces/community/text-ai/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/text-ai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/text-helper/.eslintrc.json b/packages/pieces/community/text-helper/.eslintrc.json new file mode 100644 index 0000000..d7449bb --- /dev/null +++ b/packages/pieces/community/text-helper/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {}, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/text-helper/README.md b/packages/pieces/community/text-helper/README.md new file mode 100644 index 0000000..40795a1 --- /dev/null +++ b/packages/pieces/community/text-helper/README.md @@ -0,0 +1,7 @@ +# pieces-text-helper + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-text-helper` to build the library. diff --git a/packages/pieces/community/text-helper/package.json b/packages/pieces/community/text-helper/package.json new file mode 100644 index 0000000..d65fca0 --- /dev/null +++ b/packages/pieces/community/text-helper/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-text-helper", + "version": "0.4.0" +} diff --git a/packages/pieces/community/text-helper/project.json b/packages/pieces/community/text-helper/project.json new file mode 100644 index 0000000..2d872e0 --- /dev/null +++ b/packages/pieces/community/text-helper/project.json @@ -0,0 +1,42 @@ +{ + "name": "pieces-text-helper", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/text-helper/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/text-helper", + "tsConfig": "packages/pieces/community/text-helper/tsconfig.lib.json", + "packageJson": "packages/pieces/community/text-helper/package.json", + "main": "packages/pieces/community/text-helper/src/index.ts", + "assets": [ + "packages/pieces/community/text-helper/*.md", + { + "input": "packages/pieces/community/text-helper/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-string-utils {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/text-helper/src/index.ts b/packages/pieces/community/text-helper/src/index.ts new file mode 100644 index 0000000..f32276f --- /dev/null +++ b/packages/pieces/community/text-helper/src/index.ts @@ -0,0 +1,41 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { concat } from './lib/actions/concat'; +import { find } from './lib/actions/find'; +import { htmlToMarkdown } from './lib/actions/html-to-markdown'; +import { markdownToHTML } from './lib/actions/markdown-to-html'; +import { replace } from './lib/actions/replace'; +import { split } from './lib/actions/split'; +import { stripHtmlContent } from './lib/actions/strip-html'; +import { slugifyAction } from './lib/actions/slugify'; +import { defaultValue } from './lib/actions/default-value'; + +export const textHelper = createPiece({ + displayName: 'Text Helper', + description: 'Tools for text processing', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/text-helper.svg', + authors: [ + 'joeworkman', + 'kishanprmr', + 'MoShizzle', + 'AbdulTheActivePiecer', + 'abuaboud', + 'AdamSelene', + 'Anmol-Gup', + ], + categories: [PieceCategory.CORE], + actions: [ + concat, + replace, + split, + find, + markdownToHTML, + htmlToMarkdown, + stripHtmlContent, + slugifyAction, + defaultValue, + ], + triggers: [], +}); diff --git a/packages/pieces/community/text-helper/src/lib/actions/concat.ts b/packages/pieces/community/text-helper/src/lib/actions/concat.ts new file mode 100644 index 0000000..d6cd8d5 --- /dev/null +++ b/packages/pieces/community/text-helper/src/lib/actions/concat.ts @@ -0,0 +1,29 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; + +export const concat = createAction({ + description: 'Concatenate two or more texts', + displayName: 'Concatenate', + name: 'concat', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + texts: Property.Array({ + displayName: 'Texts', + required: true, + }), + separator: Property.ShortText({ + displayName: 'Separator', + description: 'The text that separates the texts you want to concatenate', + required: false, + }), + }, + run: async (ctx) => { + return (ctx.propsValue.texts ?? []).join(ctx.propsValue.separator ?? ''); + }, +}); diff --git a/packages/pieces/community/text-helper/src/lib/actions/default-value.ts b/packages/pieces/community/text-helper/src/lib/actions/default-value.ts new file mode 100644 index 0000000..20cab5e --- /dev/null +++ b/packages/pieces/community/text-helper/src/lib/actions/default-value.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { isEmpty } from '@activepieces/shared'; + +export const defaultValue = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'defaultValue', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + displayName: 'Use Default Value if Input is Empty', + description: + 'Checks your input and returns the default value, if the input is an empty text or list', + props: { + value: Property.ShortText({ + displayName: 'Enter value', + description: 'Enter value', + required: false, + }), + defaultString: Property.ShortText({ + displayName: 'Default Value', + required: true, + }), + }, + async run(context) { + // Action logic here + const { value, defaultString } = context.propsValue; + if (isEmpty(value)) { + return defaultString; + } + return value; + }, +}); diff --git a/packages/pieces/community/text-helper/src/lib/actions/find.ts b/packages/pieces/community/text-helper/src/lib/actions/find.ts new file mode 100644 index 0000000..623386a --- /dev/null +++ b/packages/pieces/community/text-helper/src/lib/actions/find.ts @@ -0,0 +1,30 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; + +export const find = createAction({ + description: 'Find substring (Regex or Text).', + displayName: 'Find', + name: 'find', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + text: Property.ShortText({ + displayName: 'text', + required: true, + }), + expression: Property.ShortText({ + displayName: 'Expression', + description: 'Regex or text to search for.', + required: true, + }), + }, + run: async (ctx): Promise => { + const expression = RegExp(ctx.propsValue.expression); + return ctx.propsValue.text.match(expression); + }, +}); diff --git a/packages/pieces/community/text-helper/src/lib/actions/html-to-markdown.ts b/packages/pieces/community/text-helper/src/lib/actions/html-to-markdown.ts new file mode 100644 index 0000000..1fe5b6c --- /dev/null +++ b/packages/pieces/community/text-helper/src/lib/actions/html-to-markdown.ts @@ -0,0 +1,29 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import TurndownService from 'turndown'; + +export const htmlToMarkdown = createAction({ + name: 'html_to_markdown', + displayName: 'HTML to Markdown', + description: 'Convert HTML to Markdown', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + html: Property.LongText({ + displayName: 'HTML Content', + description: 'The HTML to convert to markdown', + required: true, + }), + }, + run: async (context) => { + const html = context.propsValue.html; + const service = new TurndownService(); + service.remove('script'); + return service.turndown(html); + }, +}); diff --git a/packages/pieces/community/text-helper/src/lib/actions/markdown-to-html.ts b/packages/pieces/community/text-helper/src/lib/actions/markdown-to-html.ts new file mode 100644 index 0000000..d8a78f8 --- /dev/null +++ b/packages/pieces/community/text-helper/src/lib/actions/markdown-to-html.ts @@ -0,0 +1,85 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { Converter, Flavor } from 'showdown'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const markdownToHTML = createAction({ + name: 'markdown_to_html', + displayName: 'Markdown to HTML', + description: 'Convert markdown to HTML', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + markdown: Property.LongText({ + displayName: 'Markdown Content', + description: 'The markdown to convert to HTML', + required: true, + }), + flavor: Property.StaticDropdown({ + displayName: 'Flavor of Markdown', + description: 'The flavor of markdown use during conversion', + required: true, + defaultValue: 'github', + options: { + options: [ + { label: 'Default', value: 'vanilla' }, + { label: 'Original', value: 'original' }, + { label: 'GitHub', value: 'github' }, + ], + }, + }), + headerLevelStart: Property.Number({ + displayName: 'Minimum Header Level', + description: 'The minimum header level to use during conversion', + required: true, + defaultValue: 1, + }), + tables: Property.Checkbox({ + displayName: 'Support Tables', + description: 'Whether to support tables during conversion', + required: true, + defaultValue: true, + }), + noHeaderId: Property.Checkbox({ + displayName: 'No Header ID', + description: 'Whether to add an ID to headers during conversion', + required: true, + defaultValue: false, + }), + simpleLineBreaks: Property.Checkbox({ + displayName: 'Simple Line Breaks', + description: + 'Parses line breaks as <br>, without needing 2 spaces at the end of the line', + required: true, + defaultValue: false, + }), + openLinksInNewWindow: Property.Checkbox({ + displayName: 'Open Links in New Window', + required: true, + defaultValue: false, + }), + }, + run: async (context) => { + await propsValidation.validateZod(context.propsValue, { + headerLevelStart: z.number().min(1).max(6), + }); + + const converter = new Converter({ + headerLevelStart: context.propsValue.headerLevelStart, + omitExtraWLInCodeBlocks: true, + noHeaderId: context.propsValue.noHeaderId, + tables: context.propsValue.tables, + simpleLineBreaks: context.propsValue.simpleLineBreaks, + openLinksInNewWindow: context.propsValue.openLinksInNewWindow, + }); + console.log('noHeaderId', context.propsValue.noHeaderId); + converter.setFlavor(context.propsValue.flavor as Flavor); + return converter.makeHtml(context.propsValue.markdown); + }, +}); diff --git a/packages/pieces/community/text-helper/src/lib/actions/replace.ts b/packages/pieces/community/text-helper/src/lib/actions/replace.ts new file mode 100644 index 0000000..2521ffe --- /dev/null +++ b/packages/pieces/community/text-helper/src/lib/actions/replace.ts @@ -0,0 +1,51 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; + +export const replace = createAction({ + description: + 'Replaces all instances of any word, character or phrase in text, with another.', + displayName: 'Replace', + name: 'replace', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + text: Property.ShortText({ + displayName: 'Text', + required: true, + }), + searchValue: Property.ShortText({ + displayName: 'Search Value', + description: 'Can be plain text or a regex expression.', + required: true, + }), + replaceValue: Property.ShortText({ + displayName: 'Replace Value', + required: false, + description: 'Leave empty to delete found results.', + }), + replaceOnlyFirst: Property.Checkbox({ + displayName: 'Replace Only First Match', + required: false, + description: 'Only replaces the first instance of the search value.', + }), + }, + run: async (ctx) => { + if (ctx.propsValue.replaceOnlyFirst) { + const expression = RegExp(ctx.propsValue.searchValue); + return ctx.propsValue.text.replace( + expression, + ctx.propsValue.replaceValue || '' + ); + } + const expression = RegExp(ctx.propsValue.searchValue, 'g'); + return ctx.propsValue.text.replaceAll( + expression, + ctx.propsValue.replaceValue || '' + ); + }, +}); diff --git a/packages/pieces/community/text-helper/src/lib/actions/slugify.ts b/packages/pieces/community/text-helper/src/lib/actions/slugify.ts new file mode 100644 index 0000000..722a7b1 --- /dev/null +++ b/packages/pieces/community/text-helper/src/lib/actions/slugify.ts @@ -0,0 +1,17 @@ +import slugify from 'slugify'; +import { Property, createAction } from '@activepieces/pieces-framework'; + +export const slugifyAction = createAction({ + description: 'Slugifies strings.', + displayName: 'Slugify', + name: 'slugify', + props: { + text: Property.ShortText({ + displayName: 'Text', + required: true, + }), + }, + run: async ({ propsValue }) => { + return slugify(propsValue.text); + }, +}); diff --git a/packages/pieces/community/text-helper/src/lib/actions/split.ts b/packages/pieces/community/text-helper/src/lib/actions/split.ts new file mode 100644 index 0000000..08ce68c --- /dev/null +++ b/packages/pieces/community/text-helper/src/lib/actions/split.ts @@ -0,0 +1,28 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; + +export const split = createAction({ + description: 'Split a text by a delimiter', + displayName: 'Split', + name: 'split', + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + text: Property.ShortText({ + displayName: 'Text', + required: true, + }), + delimiter: Property.ShortText({ + displayName: 'Delimiter', + required: true, + }), + }, + run: async (ctx) => { + return ctx.propsValue.text.split(ctx.propsValue.delimiter); + }, +}); diff --git a/packages/pieces/community/text-helper/src/lib/actions/strip-html.ts b/packages/pieces/community/text-helper/src/lib/actions/strip-html.ts new file mode 100644 index 0000000..2ef8b72 --- /dev/null +++ b/packages/pieces/community/text-helper/src/lib/actions/strip-html.ts @@ -0,0 +1,17 @@ +import { stripHtml } from 'string-strip-html'; +import { createAction, Property } from '@activepieces/pieces-framework'; + +export const stripHtmlContent = createAction({ + name: 'stripHtml', + displayName: 'Remove HTML Tags', + description: 'Removes every HTML tag and returns plain text', + props: { + html: Property.LongText({ + displayName: 'HTML content', + required: true, + }), + }, + async run({ propsValue }) { + return stripHtml(propsValue.html).result; + }, +}); diff --git a/packages/pieces/community/text-helper/tsconfig.json b/packages/pieces/community/text-helper/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/text-helper/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/text-helper/tsconfig.lib.json b/packages/pieces/community/text-helper/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/text-helper/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/thankster/.eslintrc.json b/packages/pieces/community/thankster/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/thankster/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/thankster/README.md b/packages/pieces/community/thankster/README.md new file mode 100644 index 0000000..c94e37a --- /dev/null +++ b/packages/pieces/community/thankster/README.md @@ -0,0 +1,7 @@ +# pieces-thankster + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-thankster` to build the library. diff --git a/packages/pieces/community/thankster/package.json b/packages/pieces/community/thankster/package.json new file mode 100644 index 0000000..0bdbd9a --- /dev/null +++ b/packages/pieces/community/thankster/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-thankster", + "version": "0.0.1" +} diff --git a/packages/pieces/community/thankster/project.json b/packages/pieces/community/thankster/project.json new file mode 100644 index 0000000..d13871a --- /dev/null +++ b/packages/pieces/community/thankster/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-thankster", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/thankster/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/thankster", + "tsConfig": "packages/pieces/community/thankster/tsconfig.lib.json", + "packageJson": "packages/pieces/community/thankster/package.json", + "main": "packages/pieces/community/thankster/src/index.ts", + "assets": [ + "packages/pieces/community/thankster/*.md", + { + "input": "packages/pieces/community/thankster/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/thankster/src/index.ts b/packages/pieces/community/thankster/src/index.ts new file mode 100644 index 0000000..14442f2 --- /dev/null +++ b/packages/pieces/community/thankster/src/index.ts @@ -0,0 +1,20 @@ + + import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; + import { sendCards } from './lib/actions/send-cards'; + + export const thanksterAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'Go My Profile page to find your API Key at the bottom.', + }); + + export const thankster = createPiece({ + displayName: "Thankster", + auth: thanksterAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: "https://cdn.activepieces.com/pieces/thankster.png", + authors: [], + actions: [sendCards], + triggers: [], + }); + \ No newline at end of file diff --git a/packages/pieces/community/thankster/src/lib/actions/send-cards.ts b/packages/pieces/community/thankster/src/lib/actions/send-cards.ts new file mode 100644 index 0000000..52eec6b --- /dev/null +++ b/packages/pieces/community/thankster/src/lib/actions/send-cards.ts @@ -0,0 +1,235 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { thanksterAuth } from '../..'; + +export const sendCards = createAction({ + name: 'send_handwritten_cards', + displayName: 'Send Cards', + description: 'Automatically send handwritten cards.', + auth: thanksterAuth, + props: { + templateID: Property.Dropdown({ + displayName: 'Select a Thankster Template', + description: 'If you are passing text or images from prior steps in this step into the Thankster template chosen above, be sure to select a template that has corresponding text or images boxes to pass it into. Either way, the font and style of your cards will be taken from the template you select.', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please fill in API key first', + }; + } + + const res = await httpClient.sendRequest<{ id: number; name: string }[]>({ + method: HttpMethod.GET, + url: 'https://app.thankster.com/api/v1/api_projects/listUserProjects', + headers: { + partner: 'partner_active_pieces', + userApiKey: auth as string + }, + }); + + const opts = []; + + if (res.body) { + for(const template of res.body) { + opts.push({ + label: template.name, + value: template.id + }) + } + } + + return { + disabled: false, + options: opts + }; + }, + }), + fname: Property.ShortText({ + displayName: 'Sender Firstname', + description: 'Sender first name.', + required: true + }), + lname: Property.ShortText({ + displayName: 'Sender Lastname', + description: 'Sender last name.', + required: false + }), + company: Property.ShortText({ + displayName: 'Sender Company Name', + description: 'Enter sender company name.', + required: false + }), + address: Property.ShortText({ + displayName: 'Sender Address1', + description: 'Enter sender address1.', + required: true + }), + address2: Property.ShortText({ + displayName: 'Sender Address2', + description: 'Enter sender address2.', + required: false + }), + city: Property.ShortText({ + displayName: 'Sender City', + description: 'Enter sender city.', + required: true + }), + state: Property.ShortText({ + displayName: 'Sender State', + description: 'Enter sender state.', + required: false + }), + zip: Property.ShortText({ + displayName: 'Sender Zip', + description: 'Enter sender zip.', + required: true + }), + country: Property.ShortText({ + displayName: 'Sender Country', + description: 'If not US, type the two character country code.', + defaultValue: 'US', + required: false + }), + r_fname: Property.ShortText({ + displayName: 'Receiver Firstname', + description: 'Receiver first name.', + required: true + }), + r_lname: Property.ShortText({ + displayName: 'Receiver Lastname', + description: 'Receiver last name.', + required: false + }), + r_company: Property.ShortText({ + displayName: 'Receiver Company', + description: 'Receiver company name.', + required: false + }), + r_address: Property.ShortText({ + displayName: 'Receiver Address1', + description: 'Receiver address1.', + required: true + }), + r_address2: Property.ShortText({ + displayName: 'Receiver Address2', + description: 'Receiver address2.', + required: false + }), + r_city: Property.ShortText({ + displayName: 'Receiver City', + description: 'Receiver city.', + required: true + }), + r_state: Property.ShortText({ + displayName: 'Receiver State', + description: 'Receiver state.', + required: false + }), + r_zip: Property.ShortText({ + displayName: 'Receiver Zip', + description: 'Receiver zip.', + required: true + }), + r_country: Property.ShortText({ + displayName: 'Receiver Country', + description: 'If not US, type the two character country code.', + defaultValue: 'US', + required: false + }), + api_text_one: Property.LongText({ + displayName: 'Text One', + description: 'Optional - if left blank, it will take the message from your Thankster template. Otherwise, map this to an api text box in the Thankster template being in Thankster Template field, 200 character maximum suggested.', + required: false + }), + api_text_two: Property.LongText({ + displayName: 'Text Two', + description: 'Optional - if left blank, it will take the message from your Thankster template. Otherwise, map this to an api text box in the Thankster template being in Thankster Template field, 200 character maximum suggested.', + required: false + }), + api_text_three: Property.LongText({ + displayName: 'Text Three', + description: 'Optional - if left blank, it will take the message from your Thankster template. Otherwise, map this to an api text box in the Thankster template being in Thankster Template field, 200 character maximum suggested.', + required: false + }), + api_text_four: Property.LongText({ + displayName: 'Text Four', + description: 'Optional - if left blank, it will take the message from your Thankster template. Otherwise, map this to an api text box in the Thankster template being in Thankster Template field, 200 character maximum suggested.', + required: false + }), + api_image_one: Property.ShortText({ + displayName: 'Image One (URL)', + description: 'Optional image URL for image one. Be sure this image is at least 1500 x 1900 pixels and has that aspect ratio to avoid stretching or pixelated.', + required: false + }), + api_image_two: Property.ShortText({ + displayName: 'Image Two (URL)', + description: 'Optional image URL for image one. Be sure this image is at least 1500 x 1900 pixels and has that aspect ratio to avoid stretching or pixelated.', + required: false + }), + api_image_three: Property.ShortText({ + displayName: 'Image Three (URL)', + description: 'Optional image URL for image one. Be sure this image is at least 1500 x 1900 pixels and has that aspect ratio to avoid stretching or pixelated.', + required: false + }), + api_image_four: Property.ShortText({ + displayName: 'Image Four (URL)', + description: 'Optional image URL for image one. Be sure this image is at least 1500 x 1900 pixels and has that aspect ratio to avoid stretching or pixelated.', + required: false + }), + sender_image: Property.ShortText({ + displayName: 'Sender Image', + description: 'Optional image URL for return address (on the envelope). The recommended image size is 300x120 pixels or that aspect ratio to avoid stretching or pixelation. The image will be on top of the text and it will be downsized preserving the aspect ratio to fit the boundaries if necessary.', + required: false + }), + recipient_image: Property.ShortText({ + displayName: 'Recipient Image', + description: 'Optional image URL for return address (on the envelope). The recommended image size is 300x120 pixels or that aspect ratio to avoid stretching or pixelation. The image will be on top of the text and it will be downsized preserving the aspect ratio to fit the boundaries if necessary.', + required: false + }) + }, + async run(context) { + const res = await httpClient.sendRequest<{status: number, message: string}>({ + method: HttpMethod.POST, + url: 'https://app.thankster.com/api/v1/api_projects/createQuickProject', + headers: { + partner: 'partner_active_pieces', + userApiKey: context.auth as string + }, + body: { + templateID: context.propsValue.templateID, + fname: context.propsValue.fname, + lname: context.propsValue.lname, + company: context.propsValue.company, + address: context.propsValue.address, + address2: context.propsValue.address2, + city: context.propsValue.city, + state: context.propsValue.state, + zip: context.propsValue.zip, + country: context.propsValue.country, + r_fname: context.propsValue.r_fname, + r_lname: context.propsValue.r_lname, + r_address: context.propsValue.r_address, + r_address2: context.propsValue.r_address2, + r_city: context.propsValue.r_city, + r_state: context.propsValue.r_state, + r_zip: context.propsValue.r_zip, + r_country: context.propsValue.r_country, + api_text_one: context.propsValue.api_text_one, + api_text_two: context.propsValue.api_text_two, + api_text_three: context.propsValue.api_text_three, + api_text_four: context.propsValue.api_text_four, + api_image_one: context.propsValue.api_image_one, + api_image_two: context.propsValue.api_image_two, + api_image_three: context.propsValue.api_image_three, + api_image_four: context.propsValue.api_image_four, + sender_image: context.propsValue.sender_image, + recipient_image: context.propsValue.recipient_image + } + }); + return res.body; + }, +}); diff --git a/packages/pieces/community/thankster/tsconfig.json b/packages/pieces/community/thankster/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/thankster/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/thankster/tsconfig.lib.json b/packages/pieces/community/thankster/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/thankster/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/ticktick/.eslintrc.json b/packages/pieces/community/ticktick/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/ticktick/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/ticktick/README.md b/packages/pieces/community/ticktick/README.md new file mode 100644 index 0000000..33216d4 --- /dev/null +++ b/packages/pieces/community/ticktick/README.md @@ -0,0 +1,7 @@ +# pieces-ticktick + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-ticktick` to build the library. diff --git a/packages/pieces/community/ticktick/package.json b/packages/pieces/community/ticktick/package.json new file mode 100644 index 0000000..c1444f2 --- /dev/null +++ b/packages/pieces/community/ticktick/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-ticktick", + "version": "0.0.1" +} diff --git a/packages/pieces/community/ticktick/project.json b/packages/pieces/community/ticktick/project.json new file mode 100644 index 0000000..ef67636 --- /dev/null +++ b/packages/pieces/community/ticktick/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-ticktick", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/ticktick/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/ticktick", + "tsConfig": "packages/pieces/community/ticktick/tsconfig.lib.json", + "packageJson": "packages/pieces/community/ticktick/package.json", + "main": "packages/pieces/community/ticktick/src/index.ts", + "assets": [ + "packages/pieces/community/ticktick/*.md", + { + "input": "packages/pieces/community/ticktick/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/ticktick/src/index.ts b/packages/pieces/community/ticktick/src/index.ts new file mode 100644 index 0000000..86bc213 --- /dev/null +++ b/packages/pieces/community/ticktick/src/index.ts @@ -0,0 +1,43 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece, OAuth2PropertyValue, PieceAuth } from '@activepieces/pieces-framework'; +import { completeTaskAction } from './lib/actions/complete-task'; +import { createTaskAction } from './lib/actions/create-task'; +import { deleteTaskAction } from './lib/actions/delete-task'; +import { findTaskAction } from './lib/actions/find-task'; +import { getProjectAction } from './lib/actions/get-project-by-id'; +import { getTaskAction } from './lib/actions/get-task'; +import { updateTaskAction } from './lib/actions/update-task'; +import { newTaskCreatedTrigger } from './lib/triggers/new-task-created'; + +export const ticktickAuth = PieceAuth.OAuth2({ + authUrl: 'https://ticktick.com/oauth/authorize', + tokenUrl: 'https://ticktick.com/oauth/token', + required: true, + scope: ['tasks:read', 'tasks:write'], +}); + +export const ticktick = createPiece({ + displayName: 'TickTick', + logoUrl: 'https://cdn.activepieces.com/pieces/ticktick.png', + auth: ticktickAuth, + authors: ['onyedikachi-david', 'kishanprmr'], + actions: [ + createTaskAction, + updateTaskAction, + getTaskAction, + deleteTaskAction, + completeTaskAction, + findTaskAction, + getProjectAction, + createCustomApiCallAction({ + auth:ticktickAuth, + baseUrl:()=>'https://api.ticktick.com/open/v1', + authMapping:async (auth)=>{ + return { + Authorization:`Bearer ${(auth as OAuth2PropertyValue).access_token}` + } + } + }) + ], + triggers: [newTaskCreatedTrigger], +}); diff --git a/packages/pieces/community/ticktick/src/lib/actions/complete-task.ts b/packages/pieces/community/ticktick/src/lib/actions/complete-task.ts new file mode 100644 index 0000000..9ea39d2 --- /dev/null +++ b/packages/pieces/community/ticktick/src/lib/actions/complete-task.ts @@ -0,0 +1,34 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { ticktickAuth } from '../../index'; +import { tickTickApiCall } from '../common/client'; +import { projectId, taskId } from '../common/props'; + +export const completeTaskAction = createAction({ + auth: ticktickAuth, + name: 'complete_task', + displayName: 'Complete Task', + description: 'Marks an existing task as completed.', + props: { + projectId: projectId({ + displayName: 'List', + required: true, + }), + taskId: taskId({ + displayName: 'Task ID', + description: 'The ID of the task to complete.', + required: true, + }), + }, + async run(context) { + const { projectId, taskId } = context.propsValue; + + const response = await tickTickApiCall({ + accessToken: context.auth.access_token, + method: HttpMethod.POST, + resourceUri: `/project/${projectId}/task/${taskId}/complete`, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/ticktick/src/lib/actions/create-task.ts b/packages/pieces/community/ticktick/src/lib/actions/create-task.ts new file mode 100644 index 0000000..e9ee75e --- /dev/null +++ b/packages/pieces/community/ticktick/src/lib/actions/create-task.ts @@ -0,0 +1,83 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import { ticktickAuth } from '../../index'; +import { tickTickApiCall } from '../common/client'; +import { + TICKTICK_PRIORITY_HIGH, + TICKTICK_PRIORITY_LOW, + TICKTICK_PRIORITY_MEDIUM, + TICKTICK_PRIORITY_NONE, +} from '../common/constants'; +import { projectId } from '../common/props'; + +export const createTaskAction = createAction({ + auth: ticktickAuth, + name: 'create_task', + displayName: 'Create Task', + description: 'Creates a new in a specific list.', + props: { + projectId: projectId({ + displayName: 'List', + description: 'The list to create the task in.', + required: true, + }), + title: Property.ShortText({ + displayName: 'Task Title', + required: true, + }), + content: Property.LongText({ + displayName: 'Task Content', + required: false, + }), + desc: Property.LongText({ + displayName: 'Description (Checklist)', + description: 'Description of the checklist, often used with subtasks (items).', + required: false, + }), + startDate: Property.DateTime({ + displayName: 'Start Date', + required: false, + }), + dueDate: Property.DateTime({ + displayName: 'Due Date', + required: false, + }), + priority: Property.StaticDropdown({ + displayName: 'Priority', + required: false, + options: { + options: [ + { label: 'None', value: TICKTICK_PRIORITY_NONE }, + { label: 'Low', value: TICKTICK_PRIORITY_LOW }, + { label: 'Medium', value: TICKTICK_PRIORITY_MEDIUM }, + { label: 'High', value: TICKTICK_PRIORITY_HIGH }, + ], + }, + }), + }, + async run(context) { + const { projectId, title, content, desc, startDate, dueDate, priority } = context.propsValue; + + const createTaskParams: Record = { + title, + projectId: projectId as string, + }; + + if (content) createTaskParams['content'] = content; + if (desc) createTaskParams['desc'] = desc; + if (startDate) + createTaskParams['startDate'] = dayjs(startDate).format('YYYY-MM-DDTHH:mm:ssZZ'); + if (dueDate) createTaskParams['dueDate'] = dayjs(dueDate).format('YYYY-MM-DDTHH:mm:ssZZ'); + if (priority) createTaskParams['priority'] = priority; + + const response = await tickTickApiCall({ + accessToken: context.auth.access_token, + method: HttpMethod.POST, + resourceUri: '/task', + body: createTaskParams, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/ticktick/src/lib/actions/delete-task.ts b/packages/pieces/community/ticktick/src/lib/actions/delete-task.ts new file mode 100644 index 0000000..8761e29 --- /dev/null +++ b/packages/pieces/community/ticktick/src/lib/actions/delete-task.ts @@ -0,0 +1,35 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { ticktickAuth } from '../../index'; +import { tickTickApiCall } from '../common/client'; +import { projectId, taskId } from '../common/props'; + +export const deleteTaskAction = createAction({ + auth: ticktickAuth, + name: 'delete_task', + displayName: 'Delete Task', + description: 'Deletes an existing task.', + props: { + projectId: projectId({ + displayName: 'List', + description: 'The list the task belongs to.', + required: true, + }), + taskId: taskId({ + displayName: 'Task ID', + description: 'The ID of the task to delete.', + required: true, + }), + }, + async run(context) { + const { projectId, taskId } = context.propsValue; + + const response = await tickTickApiCall({ + accessToken: context.auth.access_token, + method: HttpMethod.DELETE, + resourceUri: `/project/${projectId}/task/${taskId}`, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/ticktick/src/lib/actions/find-task.ts b/packages/pieces/community/ticktick/src/lib/actions/find-task.ts new file mode 100644 index 0000000..0efd3f6 --- /dev/null +++ b/packages/pieces/community/ticktick/src/lib/actions/find-task.ts @@ -0,0 +1,69 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { ticktickAuth } from '../../index'; +import { tickTickApiCall } from '../common/client'; +import { projectId } from '../common/props'; + +export const findTaskAction = createAction({ + auth: ticktickAuth, + name: 'find_task', + displayName: 'Find Task', + description: 'Finds tasks in a specific project by their title.', + props: { + projectId: projectId({ + displayName: 'List', + description: 'The list to search within.', + required: true, + }), + title: Property.ShortText({ + displayName: 'Task Title', + required: true, + }), + matchType: Property.StaticDropdown({ + displayName: 'Match Type', + description: 'Select how the title should be matched.', + required: true, + options: { + options: [ + { label: 'Contains (case-insensitive)', value: 'contains' }, + { label: 'Exact Match (case-insensitive)', value: 'exact' }, + ], + }, + defaultValue: 'contains', + }), + }, + async run(context) { + const { projectId, title, matchType } = context.propsValue; + + if (!projectId || !title) { + return []; + } + + const response = await tickTickApiCall<{ + tasks: { id: string; title: string }[]; + }>({ + accessToken: context.auth.access_token, + method: HttpMethod.GET, + resourceUri: `/project/${projectId}/data`, + }); + + const foundTasks = []; + + for (const task of response.tasks) { + if (matchType === 'exact') { + if (task.title.toLowerCase() === title.toLowerCase()) { + foundTasks.push(task); + } + } else { + // Default to 'contains' + if (task.title.toLowerCase().includes(title.toLowerCase())) { + foundTasks.push(task); + } + } + } + return { + found: foundTasks.length > 0, + result: foundTasks, + }; + }, +}); diff --git a/packages/pieces/community/ticktick/src/lib/actions/get-project-by-id.ts b/packages/pieces/community/ticktick/src/lib/actions/get-project-by-id.ts new file mode 100644 index 0000000..23a80e0 --- /dev/null +++ b/packages/pieces/community/ticktick/src/lib/actions/get-project-by-id.ts @@ -0,0 +1,29 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { ticktickAuth } from '../../index'; +import { tickTickApiCall } from '../common/client'; + +export const getProjectAction = createAction({ + auth: ticktickAuth, + name: 'get_project', + displayName: 'Get Task List', + description: 'Retrieves the details of a specific task list by ID.', + props: { + projectId: Property.ShortText({ + displayName: 'List ID', + description: 'Select the list to retrieve details for.', + required: true, + }), + }, + async run(context) { + const { projectId } = context.propsValue; + + const response = await tickTickApiCall({ + accessToken: context.auth.access_token, + method: HttpMethod.GET, + resourceUri: `/project/${projectId}`, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/ticktick/src/lib/actions/get-task.ts b/packages/pieces/community/ticktick/src/lib/actions/get-task.ts new file mode 100644 index 0000000..1ed592d --- /dev/null +++ b/packages/pieces/community/ticktick/src/lib/actions/get-task.ts @@ -0,0 +1,35 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { ticktickAuth } from '../../index'; +import { tickTickApiCall } from '../common/client'; +import { projectId, taskId } from '../common/props'; + +export const getTaskAction = createAction({ + auth: ticktickAuth, + name: 'get_task', + displayName: 'Get Task', + description: 'Retrieves the details of a specific task.', + props: { + projectId: projectId({ + displayName: 'List', + description: 'The list the task belongs to.', + required: true, + }), + taskId: taskId({ + displayName: 'Task ID', + description: 'The ID of the task to retrieve.', + required: true, + }), + }, + async run(context) { + const { projectId, taskId } = context.propsValue; + + const response = await tickTickApiCall({ + accessToken: context.auth.access_token, + method: HttpMethod.GET, + resourceUri: `/project/${projectId}/task/${taskId}`, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/ticktick/src/lib/actions/update-task.ts b/packages/pieces/community/ticktick/src/lib/actions/update-task.ts new file mode 100644 index 0000000..83e8215 --- /dev/null +++ b/packages/pieces/community/ticktick/src/lib/actions/update-task.ts @@ -0,0 +1,91 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import { ticktickAuth } from '../../index'; +import { tickTickApiCall } from '../common/client'; +import { + TICKTICK_PRIORITY_HIGH, + TICKTICK_PRIORITY_LOW, + TICKTICK_PRIORITY_MEDIUM, + TICKTICK_PRIORITY_NONE, +} from '../common/constants'; +import { projectId, taskId } from '../common/props'; + + +export const updateTaskAction = createAction({ + auth: ticktickAuth, + name: 'update_task', + displayName: 'Update Task', + description: 'Update an existing task.', + props: { + projectId: projectId({ + displayName: 'List', + description: 'The list to update the task in.', + required: true, + }), + taskId: taskId({ + displayName: 'Task ID', + description: 'The ID of the task to update.', + required: true, + }), + title: Property.ShortText({ + displayName: 'Title', + required: false, + }), + content: Property.LongText({ + displayName: 'Content', + required: false, + }), + desc: Property.LongText({ + displayName: 'Description (Checklist)', + description: 'New description of the checklist items. Replaces existing.', + required: false, + }), + startDate: Property.DateTime({ + displayName: 'Start Date', + required: false, + }), + dueDate: Property.DateTime({ + displayName: 'Due Date', + required: false, + }), + priority: Property.StaticDropdown({ + displayName: 'Priority', + required: false, + options: { + options: [ + { label: 'None', value: TICKTICK_PRIORITY_NONE }, + { label: 'Low', value: TICKTICK_PRIORITY_LOW }, + { label: 'Medium', value: TICKTICK_PRIORITY_MEDIUM }, + { label: 'High', value: TICKTICK_PRIORITY_HIGH }, + ], + }, + }), + }, + async run(context) { + const { taskId, projectId, title, content, desc, startDate, dueDate, priority } = + context.propsValue; + + const updateTaskParams: Record = { + id: taskId, + projectId: projectId as string, + }; + + if (title) updateTaskParams['title'] = title; + if (content) updateTaskParams['content'] = content; + if (desc) updateTaskParams['desc'] = desc; + if (startDate) + updateTaskParams['startDate'] = dayjs(startDate).format('YYYY-MM-DDTHH:mm:ssZZ'); + if (dueDate) updateTaskParams['dueDate'] = dayjs(dueDate).format('YYYY-MM-DDTHH:mm:ssZZ'); + if (priority) updateTaskParams['priority'] = priority; + + const response = await tickTickApiCall({ + accessToken: context.auth.access_token, + method: HttpMethod.POST, + resourceUri: `/task/${taskId}`, + body: updateTaskParams, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/ticktick/src/lib/common/client.ts b/packages/pieces/community/ticktick/src/lib/common/client.ts new file mode 100644 index 0000000..4c07df8 --- /dev/null +++ b/packages/pieces/community/ticktick/src/lib/common/client.ts @@ -0,0 +1,49 @@ +import { + AuthenticationType, + httpClient, + HttpMessageBody, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; + +export type TickTickApiCallParams = { + accessToken: string; + method: HttpMethod; + resourceUri: string; + query?: Record; + body?: any; +}; + +export async function tickTickApiCall({ + accessToken, + method, + resourceUri, + query, + body, +}: TickTickApiCallParams): Promise { + const baseUrl = 'https://api.ticktick.com/open/v1'; + const qs: QueryParams = {}; + + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + qs[key] = String(value); + } + } + } + + const request: HttpRequest = { + method, + url: baseUrl + resourceUri, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + queryParams: qs, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} diff --git a/packages/pieces/community/ticktick/src/lib/common/constants.ts b/packages/pieces/community/ticktick/src/lib/common/constants.ts new file mode 100644 index 0000000..cc320d6 --- /dev/null +++ b/packages/pieces/community/ticktick/src/lib/common/constants.ts @@ -0,0 +1,14 @@ + +// Task Statuses +export const TICKTICK_TASK_STATUS_INCOMPLETE = 0; +export const TICKTICK_TASK_STATUS_COMPLETED = 2; + +// ChecklistItem (Subtask) Statuses +export const TICKTICK_SUBTASK_STATUS_INCOMPLETE = 0; +export const TICKTICK_SUBTASK_STATUS_COMPLETED = 1; + +// Task Priorities +export const TICKTICK_PRIORITY_NONE = 0; +export const TICKTICK_PRIORITY_LOW = 1; +export const TICKTICK_PRIORITY_MEDIUM = 3; +export const TICKTICK_PRIORITY_HIGH = 5; diff --git a/packages/pieces/community/ticktick/src/lib/common/props.ts b/packages/pieces/community/ticktick/src/lib/common/props.ts new file mode 100644 index 0000000..580002e --- /dev/null +++ b/packages/pieces/community/ticktick/src/lib/common/props.ts @@ -0,0 +1,79 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { tickTickApiCall } from './client'; + +interface DropdownParams { + displayName: string; + description?: string; + required: boolean; +} + +export const projectId = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + description: params.description, + required: params.required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please authenticate first', + options: [], + }; + } + + const authValue = auth as OAuth2PropertyValue; + const response = await tickTickApiCall<{ id: string; name: string }[]>({ + accessToken: authValue.access_token, + method: HttpMethod.GET, + resourceUri: '/project', + }); + + const projects = [...(response || []), { id: 'inbox', name: 'inbox' }]; + + return { + disabled: false, + options: projects.map((project) => { + return { + label: project.name, + value: project.id, + }; + }), + }; + }, + }); + +export const taskId = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + description: params.description, + required: params.required, + refreshers: ['projectId'], + options: async ({ auth, projectId }) => { + if (!auth || !projectId) { + return { + disabled: true, + placeholder: 'Please authenticate first and select list.', + options: [], + }; + } + + const authValue = auth as OAuth2PropertyValue; + const response = await tickTickApiCall<{ tasks: { id: string; title: string }[] }>({ + accessToken: authValue.access_token, + method: HttpMethod.GET, + resourceUri: `/project/${projectId}/data`, + }); + + return { + disabled: false, + options: response.tasks.map((task) => { + return { + label: task.title, + value: task.id, + }; + }), + }; + }, + }); diff --git a/packages/pieces/community/ticktick/src/lib/triggers/new-task-created.ts b/packages/pieces/community/ticktick/src/lib/triggers/new-task-created.ts new file mode 100644 index 0000000..0da5ac9 --- /dev/null +++ b/packages/pieces/community/ticktick/src/lib/triggers/new-task-created.ts @@ -0,0 +1,87 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { ticktickAuth } from '../../index'; +import { tickTickApiCall } from '../common/client'; +import { TICKTICK_TASK_STATUS_INCOMPLETE } from '../common/constants'; +import { projectId } from '../common/props'; + +const TRIGGER_KEY = 'ticktick_new_task_trigger'; + +export const newTaskCreatedTrigger = createTrigger({ + auth: ticktickAuth, + name: 'new_task_created', + displayName: 'New Task Created', + description: 'Triggers when a new task is created in a selected project.', + props: { + projectId: projectId({ + displayName: 'Project', + description: 'The project to monitor for new tasks.', + required: true, + }), + }, + type: TriggerStrategy.POLLING, + sampleData: { + id: '6247ee29630c800f064fd145', + projectId: '6226ff9877acee87727f6bca', + title: 'Sample New Task Title', + content: 'This is a sample task content.', + status: TICKTICK_TASK_STATUS_INCOMPLETE, // Corrected usage + }, + async onEnable(context) { + const { store, auth, propsValue } = context; + + const response = await tickTickApiCall<{ tasks: { id: string; title: string }[] }>({ + accessToken: auth.access_token, + method: HttpMethod.GET, + resourceUri: `/project/${propsValue.projectId}/data`, + }); + + const taskIds = response.tasks.map((task) => task.id); + + await store.put(TRIGGER_KEY, JSON.stringify(taskIds)); + }, + async onDisable(context) { + await context.store.delete(TRIGGER_KEY); + }, + async test(context) { + const { auth, propsValue } = context; + + const response = await tickTickApiCall<{ tasks: { id: string; title: string }[] }>({ + accessToken: auth.access_token, + method: HttpMethod.GET, + resourceUri: `/project/${propsValue.projectId}/data`, + }); + + return response.tasks.slice(0, 5); + }, + async run(context) { + const { store, auth, propsValue } = context; + + const existingIds = (await store.get(TRIGGER_KEY)) ?? '[]'; + const parsedExistingIds = JSON.parse(existingIds) as string[]; + + const { tasks: currentTasks } = await tickTickApiCall<{ + tasks: { id: string; title: string }[]; + }>({ + accessToken: auth.access_token, + method: HttpMethod.GET, + resourceUri: `/project/${propsValue.projectId}/data`, + }); + + if (currentTasks.length === 0) { + await store.put(TRIGGER_KEY, '[]'); + return []; + } + + const newTasks = currentTasks.filter((task) => !parsedExistingIds.includes(task.id)); + const allCurrentIds = currentTasks.map((task) => task.id); + + await store.put(TRIGGER_KEY, JSON.stringify(allCurrentIds)); + + if (newTasks.length === 0) { + return []; + } + + return newTasks; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/ticktick/tsconfig.json b/packages/pieces/community/ticktick/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/ticktick/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/ticktick/tsconfig.lib.json b/packages/pieces/community/ticktick/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/ticktick/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/tidycal/.eslintrc.json b/packages/pieces/community/tidycal/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/tidycal/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/tidycal/README.md b/packages/pieces/community/tidycal/README.md new file mode 100644 index 0000000..64c0635 --- /dev/null +++ b/packages/pieces/community/tidycal/README.md @@ -0,0 +1,7 @@ +# pieces-tidycal + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-tidycal` to build the library. diff --git a/packages/pieces/community/tidycal/package.json b/packages/pieces/community/tidycal/package.json new file mode 100644 index 0000000..a788f98 --- /dev/null +++ b/packages/pieces/community/tidycal/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-tidycal", + "version": "0.0.9" +} \ No newline at end of file diff --git a/packages/pieces/community/tidycal/project.json b/packages/pieces/community/tidycal/project.json new file mode 100644 index 0000000..462249b --- /dev/null +++ b/packages/pieces/community/tidycal/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-tidycal", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/tidycal/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/tidycal", + "tsConfig": "packages/pieces/community/tidycal/tsconfig.lib.json", + "packageJson": "packages/pieces/community/tidycal/package.json", + "main": "packages/pieces/community/tidycal/src/index.ts", + "assets": [ + "packages/pieces/community/tidycal/*.md", + { + "input": "packages/pieces/community/tidycal/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-tidycal {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/tidycal/src/index.ts b/packages/pieces/community/tidycal/src/index.ts new file mode 100644 index 0000000..8ae8c64 --- /dev/null +++ b/packages/pieces/community/tidycal/src/index.ts @@ -0,0 +1,54 @@ +import { + createCustomApiCallAction, + HttpMethod, +} from '@activepieces/pieces-common'; +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { calltidycalapi } from './lib/common'; +import { tidycalbookingcancelled } from './lib/trigger/cancelled-booking'; +import { tidycalnewbooking } from './lib/trigger/new-booking'; +import { tidycalnewcontact } from './lib/trigger/new-contacts'; + +const markdown = ` +# Personal Access Token +1- Visit https://tidycal.com/integrations/oauth and click on "Create a new token" +2- Enter a name for your token and click on "Create" +`; +export const tidyCalAuth = PieceAuth.SecretText({ + displayName: 'API Key', + description: markdown, + required: true, + validate: async ({ auth }) => { + try { + await calltidycalapi(HttpMethod.GET, 'bookings', auth, undefined); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API Key', + }; + } + }, +}); + +export const tidycal = createPiece({ + displayName: 'TidyCal', + description: 'Streamline your scheduling', + auth: tidyCalAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/tidycal.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: ["Salem-Alaa","kishanprmr","MoShizzle","abuaboud"], + actions: [ + createCustomApiCallAction({ + baseUrl: () => 'https://tidycal.com/api', + auth: tidyCalAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${auth}`, + }), + }), + ], + triggers: [tidycalbookingcancelled, tidycalnewbooking, tidycalnewcontact], +}); diff --git a/packages/pieces/community/tidycal/src/lib/common/index.ts b/packages/pieces/community/tidycal/src/lib/common/index.ts new file mode 100644 index 0000000..4de4911 --- /dev/null +++ b/packages/pieces/community/tidycal/src/lib/common/index.ts @@ -0,0 +1,24 @@ +import { + AuthenticationType, + HttpMessageBody, + HttpMethod, + HttpResponse, + httpClient, +} from '@activepieces/pieces-common'; + +export async function calltidycalapi( + method: HttpMethod, + apiUrl: string, + accessToken: string, + body: any | undefined +): Promise> { + return await httpClient.sendRequest({ + method: method, + url: `https://tidycal.com/api/${apiUrl}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + body: body, + }); +} diff --git a/packages/pieces/community/tidycal/src/lib/trigger/cancelled-booking.ts b/packages/pieces/community/tidycal/src/lib/trigger/cancelled-booking.ts new file mode 100644 index 0000000..191dfcf --- /dev/null +++ b/packages/pieces/community/tidycal/src/lib/trigger/cancelled-booking.ts @@ -0,0 +1,98 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + HttpMethod, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { calltidycalapi } from '../common'; +import { tidyCalAuth } from '../../'; +import dayjs from 'dayjs'; + +export const tidycalbookingcancelled = createTrigger({ + auth: tidyCalAuth, + name: 'booking_canceled', + displayName: 'Booking Canceled', + description: 'Triggers when a new booking is canceled', + props: {}, + sampleData: { + data: [ + { + id: 1, + contact_id: 1, + booking_type_id: 1, + starts_at: '2022-01-01T00:00:00.000000Z', + ends_at: '2022-02-01T00:00:00.000000Z', + cancelled_at: '2022-02-01T00:00:00.000000Z', + created_at: '2022-02-01T00:00:00.000000Z', + updated_at: '2022-02-01T00:00:00.000000Z', + timezone: 'America/Los_Angeles', + meeting_url: 'https://zoom.us/j/949494949494', + meeting_id: 'fw44lkj48fks', + questions: {}, + contact: { + id: '1', + email: 'john@doe.com', + name: 'John Doe', + created_at: '2022-01-01T00:00:00.000000Z', + updated_at: '2022-01-01T00:00:00.000000Z', + }, + }, + ], + }, + type: TriggerStrategy.POLLING, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, + test: async (context) => { + return await pollingHelper.test(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, +}); + +const polling: Polling> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, lastFetchEpochMS }) => { + const currentValues = await calltidycalapi<{ + data: { + id: string; + cancelled_at: string; + }[]; + }>(HttpMethod.GET, `bookings?cancelled=true`, auth, undefined); + + const cancelledBookings = currentValues.body; + const bookings = cancelledBookings.data.filter((item) => { + const cancelledAt = dayjs(item.cancelled_at); + return cancelledAt.isAfter(lastFetchEpochMS); + }); + return bookings.map((item) => { + return { + epochMilliSeconds: dayjs(item.cancelled_at).valueOf(), + data: item, + }; + }); + }, +}; diff --git a/packages/pieces/community/tidycal/src/lib/trigger/new-booking.ts b/packages/pieces/community/tidycal/src/lib/trigger/new-booking.ts new file mode 100644 index 0000000..6d6af84 --- /dev/null +++ b/packages/pieces/community/tidycal/src/lib/trigger/new-booking.ts @@ -0,0 +1,98 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + HttpMethod, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { calltidycalapi } from '../common'; +import { tidyCalAuth } from '../../'; +import dayjs from 'dayjs'; + +export const tidycalnewbooking = createTrigger({ + auth: tidyCalAuth, + name: 'new_booking', + displayName: 'New Booking', + description: 'Triggers when a new booking is created', + props: {}, + sampleData: { + data: [ + { + id: 1, + contact_id: 1, + booking_type_id: 1, + starts_at: '2022-01-01T00:00:00.000000Z', + ends_at: '2022-02-01T00:00:00.000000Z', + cancelled_at: '2022-02-01T00:00:00.000000Z', + created_at: '2022-02-01T00:00:00.000000Z', + updated_at: '2022-02-01T00:00:00.000000Z', + timezone: 'America/Los_Angeles', + meeting_url: 'https://zoom.us/j/949494949494', + meeting_id: 'fw44lkj48fks', + questions: {}, + contact: { + id: '1', + email: 'john@doe.com', + name: 'John Doe', + created_at: '2022-01-01T00:00:00.000000Z', + updated_at: '2022-01-01T00:00:00.000000Z', + }, + }, + ], + }, + type: TriggerStrategy.POLLING, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, + test: async (context) => { + return await pollingHelper.test(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, +}); + +const polling: Polling> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, lastFetchEpochMS }) => { + const currentValues = await calltidycalapi<{ + data: { + id: string; + created_at: string; + }[]; + }>(HttpMethod.GET, `bookings?cancelled=false`, auth, undefined); + + const createdBookings = currentValues.body; + const bookings = createdBookings.data.filter((item) => { + const created_at = dayjs(item.created_at); + return created_at.isAfter(lastFetchEpochMS); + }); + return bookings.map((item) => { + return { + epochMilliSeconds: dayjs(item.created_at).valueOf(), + data: item, + }; + }); + }, +}; diff --git a/packages/pieces/community/tidycal/src/lib/trigger/new-contacts.ts b/packages/pieces/community/tidycal/src/lib/trigger/new-contacts.ts new file mode 100644 index 0000000..cfeeede --- /dev/null +++ b/packages/pieces/community/tidycal/src/lib/trigger/new-contacts.ts @@ -0,0 +1,84 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + HttpMethod, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { calltidycalapi } from '../common'; +import { tidyCalAuth } from '../../'; +import dayjs from 'dayjs'; + +export const tidycalnewcontact = createTrigger({ + auth: tidyCalAuth, + name: 'new_contact', + displayName: 'New Contact', + description: 'Triggers when a new contact is created', + props: {}, + sampleData: { + data: [ + { + id: '1', + email: 'john@doe.com', + name: 'John Doe', + created_at: '2022-01-01T00:00:00.000000Z', + updated_at: '2022-01-01T00:00:00.000000Z', + }, + ], + }, + type: TriggerStrategy.POLLING, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, + test: async (context) => { + return await pollingHelper.test(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, +}); + +const polling: Polling> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, lastFetchEpochMS }) => { + const currentValues = await calltidycalapi<{ + data: { + id: string; + created_at: string; + }[]; + }>(HttpMethod.GET, `contacts`, auth, undefined); + + const createdcontacts = currentValues.body; + const contact = createdcontacts.data.filter((item) => { + const created_at = dayjs(item.created_at); + return created_at.isAfter(lastFetchEpochMS); + }); + return contact.map((item) => { + return { + epochMilliSeconds: dayjs(item.created_at).valueOf(), + data: item, + }; + }); + }, +}; diff --git a/packages/pieces/community/tidycal/tsconfig.json b/packages/pieces/community/tidycal/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/tidycal/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/tidycal/tsconfig.lib.json b/packages/pieces/community/tidycal/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/tidycal/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/todoist/.babelrc b/packages/pieces/community/todoist/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/todoist/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/todoist/.eslintrc.json b/packages/pieces/community/todoist/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/todoist/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/todoist/README.md b/packages/pieces/community/todoist/README.md new file mode 100644 index 0000000..ad8e874 --- /dev/null +++ b/packages/pieces/community/todoist/README.md @@ -0,0 +1,7 @@ +# pieces-todoist + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-todoist` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/todoist/package.json b/packages/pieces/community/todoist/package.json new file mode 100644 index 0000000..b49ac61 --- /dev/null +++ b/packages/pieces/community/todoist/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-todoist", + "version": "0.3.10" +} diff --git a/packages/pieces/community/todoist/project.json b/packages/pieces/community/todoist/project.json new file mode 100644 index 0000000..e1dccfe --- /dev/null +++ b/packages/pieces/community/todoist/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-todoist", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/todoist/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/todoist", + "tsConfig": "packages/pieces/community/todoist/tsconfig.lib.json", + "packageJson": "packages/pieces/community/todoist/package.json", + "main": "packages/pieces/community/todoist/src/index.ts", + "assets": [ + "packages/pieces/community/todoist/*.md", + { + "input": "packages/pieces/community/todoist/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/todoist/src/index.ts b/packages/pieces/community/todoist/src/index.ts new file mode 100644 index 0000000..ee69b97 --- /dev/null +++ b/packages/pieces/community/todoist/src/index.ts @@ -0,0 +1,39 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { OAuth2PropertyValue, PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { todoistCreateTaskAction } from './lib/actions/create-task-action'; +import { todoistTaskCompletedTrigger } from './lib/triggers/task-completed-trigger'; +import { todoistUpdateTaskAction } from './lib/actions/update-task.action'; +import { todoistFindTaskAction } from './lib/actions/find-task.action'; +import { todoistMarkTaskCompletedAction } from './lib/actions/mark-task-completed.action'; + +export const todoistAuth = PieceAuth.OAuth2({ + required: true, + authUrl: 'https://todoist.com/oauth/authorize', + tokenUrl: 'https://todoist.com/oauth/access_token', + scope: ['data:read_write'], +}); + +export const todoist = createPiece({ + displayName: 'Todoist', + description: 'To-do list and task manager', + minimumSupportedRelease: '0.5.0', + logoUrl: 'https://cdn.activepieces.com/pieces/todoist.png', + authors: ['MyWay', 'kishanprmr', 'MoShizzle', 'khaledmashaly', 'abuaboud'], + categories: [PieceCategory.PRODUCTIVITY], + auth: todoistAuth, + actions: [ + todoistCreateTaskAction, + todoistUpdateTaskAction, + todoistFindTaskAction, + todoistMarkTaskCompletedAction, + createCustomApiCallAction({ + baseUrl: () => 'https://api.todoist.com/rest/v2', + auth: todoistAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [todoistTaskCompletedTrigger], +}); diff --git a/packages/pieces/community/todoist/src/lib/actions/create-task-action.ts b/packages/pieces/community/todoist/src/lib/actions/create-task-action.ts new file mode 100644 index 0000000..45214d3 --- /dev/null +++ b/packages/pieces/community/todoist/src/lib/actions/create-task-action.ts @@ -0,0 +1,77 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { assertNotNullOrUndefined } from '@activepieces/shared'; +import { todoistRestClient } from '../common/client/rest-client'; +import { + todoistProjectIdDropdown, + todoistSectionIdDropdown, +} from '../common/props'; +import { TodoistCreateTaskRequest } from '../common/models'; +import { todoistAuth } from '../..'; + +export const todoistCreateTaskAction = createAction({ + auth: todoistAuth, + name: 'create_task', + displayName: 'Create Task', + description: 'Create task', + props: { + project_id: todoistProjectIdDropdown( + "Task project ID. If not set, task is put to user's Inbox." + ), + content: Property.LongText({ + displayName: 'content', + description: + "The task's content. It may contain some markdown-formatted text and hyperlinks", + required: true, + }), + description: Property.LongText({ + displayName: 'Description', + description: + 'A description for the task. This value may contain some markdown-formatted text and hyperlinks.', + required: false, + }), + labels: Property.Array({ + displayName: 'Labels', + required: false, + description: + "The task's labels (a list of names that may represent either personal or shared labels)", + }), + priority: Property.Number({ + displayName: 'Priority', + description: 'Task priority from 1 (normal) to 4 (urgent)', + required: false, + }), + due_date: Property.ShortText({ + displayName: 'Due date', + description: + "Can be either a specific date in YYYY-MM-DD format relative to user's timezone, a specific date and time in RFC3339 format, or a human defined date (e.g. 'next Monday') using local time", + required: false, + }), + section_id: todoistSectionIdDropdown, + }, + + async run({ auth, propsValue }) { + const token = auth.access_token; + const { + project_id, + content, + description, + labels, + priority, + due_date, + section_id, + } = propsValue as TodoistCreateTaskRequest; + + assertNotNullOrUndefined(token, 'token'); + assertNotNullOrUndefined(content, 'content'); + return await todoistRestClient.tasks.create({ + token, + project_id, + content, + description, + labels, + priority, + due_date, + section_id, + }); + }, +}); diff --git a/packages/pieces/community/todoist/src/lib/actions/find-task.action.ts b/packages/pieces/community/todoist/src/lib/actions/find-task.action.ts new file mode 100644 index 0000000..e7cf8a8 --- /dev/null +++ b/packages/pieces/community/todoist/src/lib/actions/find-task.action.ts @@ -0,0 +1,36 @@ +import { todoistAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { todoistProjectIdDropdown } from '../common/props'; +import { todoistRestClient } from '../common/client/rest-client'; +import { assertNotNullOrUndefined } from '@activepieces/shared'; + +export const todoistFindTaskAction = createAction({ + auth: todoistAuth, + name: 'find_task', + displayName: 'Find Task', + description: 'Finds a task by name.', + props: { + name: Property.ShortText({ + displayName: 'Name', + description: 'The name of the task to search for.', + required: true, + }), + project_id: todoistProjectIdDropdown( + 'Search for tasks within the selected project. If left blank, then all projects are searched.', + ), + }, + async run(context) { + const token = context.auth.access_token; + const { name, project_id } = context.propsValue; + + assertNotNullOrUndefined(token, 'token'); + const tasks = await todoistRestClient.tasks.list({ token, project_id }); + + const matchedTask = tasks.find((task) => task.content == name); + if (!matchedTask) { + throw new Error('Task not found'); + } else { + return matchedTask; + } + }, +}); diff --git a/packages/pieces/community/todoist/src/lib/actions/mark-task-completed.action.ts b/packages/pieces/community/todoist/src/lib/actions/mark-task-completed.action.ts new file mode 100644 index 0000000..307ef05 --- /dev/null +++ b/packages/pieces/community/todoist/src/lib/actions/mark-task-completed.action.ts @@ -0,0 +1,25 @@ +import { assertNotNullOrUndefined } from '@activepieces/shared'; +import { todoistAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { todoistRestClient } from '../common/client/rest-client'; + +export const todoistMarkTaskCompletedAction = createAction({ + auth: todoistAuth, + name: 'mark_task_completed', + displayName: 'Mark Task as Completed', + description: 'Marks a task as being completed.', + props: { + task_id: Property.ShortText({ + displayName: 'Task ID', + required: true, + }), + }, + async run(context) { + const token = context.auth.access_token; + const { task_id } = context.propsValue; + + assertNotNullOrUndefined(token, 'token'); + + return await todoistRestClient.tasks.close({ token, task_id }); + }, +}); diff --git a/packages/pieces/community/todoist/src/lib/actions/update-task.action.ts b/packages/pieces/community/todoist/src/lib/actions/update-task.action.ts new file mode 100644 index 0000000..aede34a --- /dev/null +++ b/packages/pieces/community/todoist/src/lib/actions/update-task.action.ts @@ -0,0 +1,63 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { assertNotNullOrUndefined } from '@activepieces/shared'; +import { todoistRestClient } from '../common/client/rest-client'; +import { todoistAuth } from '../..'; + +export const todoistUpdateTaskAction = createAction({ + auth: todoistAuth, + name: 'update_task', + displayName: 'Update Task', + description: 'Updates an existing task.', + props: { + task_id: Property.ShortText({ + displayName: 'Task ID', + required: true, + }), + content: Property.LongText({ + displayName: 'content', + description: + "The task's content. It may contain some markdown-formatted text and hyperlinks", + required: false, + }), + description: Property.LongText({ + displayName: 'Description', + description: + 'A description for the task. This value may contain some markdown-formatted text and hyperlinks.', + required: false, + }), + labels: Property.Array({ + displayName: 'Labels', + required: false, + description: + "The task's labels (a list of names that may represent either personal or shared labels)", + }), + priority: Property.Number({ + displayName: 'Priority', + description: 'Task priority from 1 (normal) to 4 (urgent)', + required: false, + }), + due_date: Property.ShortText({ + displayName: 'Due date', + description: + "Can be either a specific date in YYYY-MM-DD format relative to user's timezone, a specific date and time in RFC3339 format, or a human defined date (e.g. 'next Monday') using local time", + required: false, + }), + }, + + async run({ auth, propsValue }) { + const token = auth.access_token; + const { task_id, content, description, priority, due_date } = propsValue; + const labels = propsValue.labels as string[]; + + assertNotNullOrUndefined(token, 'token'); + return await todoistRestClient.tasks.update({ + token, + task_id, + content, + description, + labels, + priority, + due_date, + }); + }, +}); diff --git a/packages/pieces/community/todoist/src/lib/common/client/rest-client.ts b/packages/pieces/community/todoist/src/lib/common/client/rest-client.ts new file mode 100644 index 0000000..3390b73 --- /dev/null +++ b/packages/pieces/community/todoist/src/lib/common/client/rest-client.ts @@ -0,0 +1,189 @@ +import { + HttpRequest, + HttpMethod, + AuthenticationType, + httpClient, +} from '@activepieces/pieces-common'; +import { isNotUndefined, pickBy } from '@activepieces/shared'; +import { + TodoistCreateTaskRequest, + TodoistProject, + TodoistSection, + TodoistTask, + TodoistUpdateTaskRequest, +} from '../models'; + +const API = 'https://api.todoist.com/rest/v2'; + +export const todoistRestClient = { + projects: { + async list({ token }: ProjectsListParams): Promise { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${API}/projects`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, + }, + + sections: { + async list(params: SectionsListPrams): Promise { + const qs: Record = {}; + if (params.project_id) qs['project_id'] = params.project_id; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${API}/sections`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: params.token, + }, + queryParams: qs, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, + }, + + tasks: { + async create({ + token, + project_id, + content, + description, + labels, + priority, + due_date, + section_id, + }: TasksCreateParams): Promise { + const body: TodoistCreateTaskRequest = { + content, + project_id, + description, + labels, + priority, + section_id, + ...dueDateParams(due_date), + }; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${API}/tasks`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, + + async update(params: TasksUpdateParams): Promise { + const body: TodoistUpdateTaskRequest = { + content: params.content, + description: params.description, + labels: params.labels?.length === 0 ? undefined : params.labels, + priority: params.priority, + ...dueDateParams(params.due_date), + }; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${API}/tasks/${params.task_id}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: params.token, + }, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, + + async list({ + token, + project_id, + filter, + }: TasksListParams): Promise { + const queryParams = { + filter, + project_id, + }; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${API}/tasks`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + queryParams: pickBy(queryParams, isNotUndefined), + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, + + async close({ token, task_id }: { token: string; task_id: string }) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${API}/tasks/${task_id}/close`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, + }, +}; + +type ProjectsListParams = { + token: string; +}; + +type SectionsListPrams = { + token: string; + project_id?: string; +}; + +type TasksCreateParams = { + token: string; +} & TodoistCreateTaskRequest; + +type TasksUpdateParams = { + token: string; + task_id: string; +} & TodoistUpdateTaskRequest; + +type TasksListParams = { + token: string; + project_id?: string | undefined; + filter?: string | undefined; +}; + +const dueDateParams = (dueDate?: string) => { + if (dueDate) { + const parsedDate = Date.parse(dueDate); + if (isNaN(parsedDate)) { + return { due_string: dueDate }; + } else if (/\d{4}-\d{2}-\d{2}/.test(dueDate)) { + return { due_date: dueDate }; + } else { + return { due_datetime: new Date(parsedDate).toISOString() }; + } + } + return {}; +}; diff --git a/packages/pieces/community/todoist/src/lib/common/client/sync-client.ts b/packages/pieces/community/todoist/src/lib/common/client/sync-client.ts new file mode 100644 index 0000000..b84e21e --- /dev/null +++ b/packages/pieces/community/todoist/src/lib/common/client/sync-client.ts @@ -0,0 +1,49 @@ +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { isNotUndefined, pickBy } from '@activepieces/shared'; +import { TodoistCompletedListResponse } from '../models'; + +const API = 'https://api.todoist.com/sync/v9'; + +export const todoistSyncClient = { + completed: { + async list({ + token, + since, + project_id, + until, + }: CompletedListParams): Promise { + const queryParams = { + limit: '200', + since, + until, + project_id, + }; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${API}/completed/get_all`, + queryParams: pickBy(queryParams, isNotUndefined), + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + }; + + const response = + await httpClient.sendRequest(request); + return response.body; + }, + }, +}; + +type CompletedListParams = { + token: string; + since: string; + until: string; + project_id: string | undefined; +}; diff --git a/packages/pieces/community/todoist/src/lib/common/models.ts b/packages/pieces/community/todoist/src/lib/common/models.ts new file mode 100644 index 0000000..d4b7f6a --- /dev/null +++ b/packages/pieces/community/todoist/src/lib/common/models.ts @@ -0,0 +1,76 @@ +export type TodoistProject = { + id: string; + name: string; +}; + +export type TodoistSection = { + id: string; + name: string; + project_id: string; + order: number; +}; + +export type TodoistCreateTaskRequest = { + content: string; + project_id?: string | undefined; + description?: string | undefined; + labels?: Array | undefined; + priority?: number | undefined; + due_date?: string | undefined; + due_string?: string | undefined; + due_datetime?: string | undefined; + section_id?: string | undefined; +}; + +export type TodoistUpdateTaskRequest = { + content?: string; + description?: string; + labels?: Array; + priority?: number; + due_date?: string | undefined; + due_string?: string | undefined; + due_datetime?: string | undefined; +}; + +type TodoistTaskDue = { + string: string; + date: string; + is_recurring: boolean; + datetime?: string | undefined; + timezone?: string | undefined; +}; + +export type TodoistTask = { + id: string; + projectId: string | null; + sectionId: string | null; + content: string; + description?: string | undefined; + is_completed: boolean; + labels: string[]; + parent_id: string | null; + order: number; + priority: number; + due: TodoistTaskDue | null; + url: string; + comment_count: number; + created_at: string; + creator_id: string; + assignee_id: string | null; + assigner_id: string | null; +}; + +export type TodoistCompletedTask = { + id: string; + task_id: string; + user_id: string; + project_id: string; + section_id: string; + content: string; + completed_at: string; + note_count: number; +}; + +export type TodoistCompletedListResponse = { + items: TodoistCompletedTask[]; +}; diff --git a/packages/pieces/community/todoist/src/lib/common/props.ts b/packages/pieces/community/todoist/src/lib/common/props.ts new file mode 100644 index 0000000..0fc04a5 --- /dev/null +++ b/packages/pieces/community/todoist/src/lib/common/props.ts @@ -0,0 +1,77 @@ +import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { todoistRestClient } from './client/rest-client'; + +const buildEmptyList = ({ placeholder }: { placeholder: string }) => { + return { + disabled: true, + options: [], + placeholder, + }; +}; + +export const todoistProjectIdDropdown = (description: string) => + Property.Dropdown({ + displayName: 'Project', + refreshers: [], + description, + required: false, + options: async ({ auth }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please select an authentication', + }); + } + + const token = (auth as OAuth2PropertyValue).access_token; + const projects = await todoistRestClient.projects.list({ token }); + + if (projects.length === 0) { + return buildEmptyList({ + placeholder: 'No projects found! Please create a project.', + }); + } + + const options = projects.map((p) => ({ + label: p.name, + value: p.id, + })); + + return { + disabled: false, + options, + }; + }, + }); + +export const todoistSectionIdDropdown = Property.Dropdown({ + displayName: 'Section', + refreshers: ['project_id'], + required: false, + options: async ({ auth, project_id }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please select an authentication', + }); + } + + const token = (auth as OAuth2PropertyValue).access_token; + const projectId = project_id as string | undefined; + const sections = await todoistRestClient.sections.list({ token, project_id: projectId }); + + if (sections.length === 0) { + return buildEmptyList({ + placeholder: 'No sections found! Please create a section.', + }); + } + + const options = sections.map((p) => ({ + label: p.name, + value: p.id, + })); + + return { + disabled: false, + options, + }; + }, +}); diff --git a/packages/pieces/community/todoist/src/lib/triggers/task-completed-trigger.ts b/packages/pieces/community/todoist/src/lib/triggers/task-completed-trigger.ts new file mode 100644 index 0000000..2e17e23 --- /dev/null +++ b/packages/pieces/community/todoist/src/lib/triggers/task-completed-trigger.ts @@ -0,0 +1,118 @@ +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import { TodoistCompletedListResponse, TodoistCompletedTask } from '../common/models'; +import { todoistProjectIdDropdown } from '../common/props'; +import { todoistAuth } from '../..'; +import { + AuthenticationType, + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, + QueryParams, +} from '@activepieces/pieces-common'; + +const ISO_FORMAT = 'YYYY-MM-DDTHH:mm:ss'; + +const polling: Polling, { project_id?: string }> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const lastUpdatedTime = + lastFetchEpochMS === 0 + ? dayjs().subtract(5, 'minutes').format(ISO_FORMAT) + : dayjs(lastFetchEpochMS).format(ISO_FORMAT); + + const tasks: TodoistCompletedTask[] = []; + + let hasMore = true; + let offset = 0; + const limit = 200; + + do { + const qs: QueryParams = { + limit: limit.toString(), + offset: offset.toString(), + since: lastUpdatedTime, + }; + + if (propsValue.project_id) { + qs.project_id = propsValue.project_id; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.todoist.com/sync/v9/completed/get_all', + queryParams: qs, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }); + if (response.body.items.length > 0) { + tasks.push(...response.body.items); + offset += limit; + } else { + hasMore = false; + } + } while (hasMore); + + return tasks.map((task) => { + return { + epochMilliSeconds: dayjs(task.completed_at).valueOf(), + data: task, + }; + }); + }, +}; + +export const todoistTaskCompletedTrigger = createTrigger({ + auth: todoistAuth, + name: 'task_completed', + displayName: 'Task Completed', + description: 'Triggers when a new task is completed', + type: TriggerStrategy.POLLING, + + props: { + project_id: todoistProjectIdDropdown( + 'Leave it blank if you want to get completed tasks from all your projects.', + ), + }, + + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + content: 'Buy Milk', + meta_data: null, + user_id: '2671355', + task_id: '2995104339', + note_count: 0, + project_id: '2203306141', + section_id: '7025', + completed_at: '2015-02-17T15:40:41.000000Z', + id: '1899066186', + }, +}); diff --git a/packages/pieces/community/todoist/tsconfig.json b/packages/pieces/community/todoist/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/todoist/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/todoist/tsconfig.lib.json b/packages/pieces/community/todoist/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/todoist/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/todos/.eslintrc.json b/packages/pieces/community/todos/.eslintrc.json new file mode 100644 index 0000000..eac708a --- /dev/null +++ b/packages/pieces/community/todos/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/todos/README.md b/packages/pieces/community/todos/README.md new file mode 100644 index 0000000..f4ca03e --- /dev/null +++ b/packages/pieces/community/todos/README.md @@ -0,0 +1,7 @@ +# pieces-todos + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-todos` to build the library. diff --git a/packages/pieces/community/todos/package.json b/packages/pieces/community/todos/package.json new file mode 100644 index 0000000..fac85a3 --- /dev/null +++ b/packages/pieces/community/todos/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-todos", + "version": "0.0.6" +} diff --git a/packages/pieces/community/todos/project.json b/packages/pieces/community/todos/project.json new file mode 100644 index 0000000..f5c17a5 --- /dev/null +++ b/packages/pieces/community/todos/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-todos", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/todos/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/todos", + "tsConfig": "packages/pieces/community/todos/tsconfig.lib.json", + "packageJson": "packages/pieces/community/todos/package.json", + "main": "packages/pieces/community/todos/src/index.ts", + "assets": [ + "packages/pieces/community/todos/*.md", + { + "input": "packages/pieces/community/todos/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/todos/src/index.ts b/packages/pieces/community/todos/src/index.ts new file mode 100644 index 0000000..0f367b8 --- /dev/null +++ b/packages/pieces/community/todos/src/index.ts @@ -0,0 +1,18 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { createTodo } from './lib/actions/create-todo'; +import { PieceCategory } from '@activepieces/shared'; +import { waitForApproval } from './lib/actions/wait-for-approval'; +import { createTodoAndWait } from './lib/actions/create-todo-and-wait'; + +export const todos = createPiece({ + displayName: 'Todos', + description: + 'Create tasks for project members to take actions, useful for approvals, reviews, and manual actions performed by humans', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.49.0', + logoUrl: 'https://cdn.activepieces.com/pieces/manual-tasks.svg', + authors: ['hazemadelkhalel'], + categories: [PieceCategory.CORE, PieceCategory.FLOW_CONTROL], + actions: [createTodo, waitForApproval, createTodoAndWait], + triggers: [], +}); diff --git a/packages/pieces/community/todos/src/lib/actions/create-todo-and-wait.ts b/packages/pieces/community/todos/src/lib/actions/create-todo-and-wait.ts new file mode 100644 index 0000000..2335193 --- /dev/null +++ b/packages/pieces/community/todos/src/lib/actions/create-todo-and-wait.ts @@ -0,0 +1,56 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + ExecutionType, + PauseType, + CreateAndWaitTodoResult, +} from '@activepieces/shared'; +import { sendTodoApproval, createTodoProps } from '../utils/utils'; + +export const createTodoAndWait = createAction({ + name: 'createTodoAndWait', + displayName: 'Create Todo and Wait', + description: + 'Creates a todo for a user and wait for their response or take action.', + props: createTodoProps, + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + async test(context) { + if (context.executionType === ExecutionType.BEGIN) { + context.run.pause({ + pauseMetadata: { + type: PauseType.WEBHOOK, + response: {}, + }, + }); + const response = await sendTodoApproval(context, true); + return response.body; + } else { + return { + status: context.resumePayload.queryParams['status'], + }; + } + }, + async run(context) { + if (context.executionType === ExecutionType.BEGIN) { + context.run.pause({ + pauseMetadata: { + type: PauseType.WEBHOOK, + response: {}, + }, + }); + const response = await sendTodoApproval(context, false); + return response.body; + } else { + const result: CreateAndWaitTodoResult = { + status: context.resumePayload.queryParams['status'], + } + return result; + } + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/todos/src/lib/actions/create-todo.ts b/packages/pieces/community/todos/src/lib/actions/create-todo.ts new file mode 100644 index 0000000..97be5d3 --- /dev/null +++ b/packages/pieces/community/todos/src/lib/actions/create-todo.ts @@ -0,0 +1,44 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { sendTodoApproval, createTodoProps, constructTodoUrl } from '../utils/utils'; +import { CreateTodoResult } from '@activepieces/shared'; + +export const createTodo = createAction({ + name: 'createTodo', + displayName: 'Create Todo', + description: + 'Creates a todo for a user, requiring them to respond or take action.', + props: createTodoProps, + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + async test(context) { + const response = await sendTodoApproval(context, true); + const links = context.propsValue.statusOptions.map((option: any) => ({ + name: option.name, + url: constructTodoUrl(context.server.publicUrl, response.body.id, option.name, true), + })); + return { + id: response.body.id, + links, + }; + }, + async run(context) { + const response = await sendTodoApproval(context, false); + const links = context.propsValue.statusOptions.map((option: any) => ({ + name: option.name, + url: constructTodoUrl(context.server.publicUrl, response.body.id, option.name, false), + })); + const result: CreateTodoResult = { + id: response.body.id, + links, + } + return result; + }, +}); + + diff --git a/packages/pieces/community/todos/src/lib/actions/wait-for-approval.ts b/packages/pieces/community/todos/src/lib/actions/wait-for-approval.ts new file mode 100644 index 0000000..e81c983 --- /dev/null +++ b/packages/pieces/community/todos/src/lib/actions/wait-for-approval.ts @@ -0,0 +1,58 @@ +import { httpClient } from '@activepieces/pieces-common'; +import { AuthenticationType } from '@activepieces/pieces-common'; +import { HttpRequest } from '@activepieces/pieces-common'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { PauseType } from '@activepieces/shared'; +import { ExecutionType } from '@activepieces/shared'; + +export const waitForApproval = createAction({ + name: 'wait_for_approval', + displayName: 'Wait for Approval', + description: 'Pauses the flow and wait for the approval from the user', + props: { + taskId: Property.ShortText({ + displayName: 'Task ID', + description: 'The ID of the task to wait for approval', + required: true, + }), + }, + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + async test(ctx) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${ctx.server.publicUrl}v1/todos/${ctx.propsValue.taskId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: ctx.server.token, + }, + }; + const response = await httpClient.sendRequest(request); + return { + status: response.body.status.name, + }; + }, + async run(ctx) { + if (ctx.executionType === ExecutionType.BEGIN) { + ctx.run.pause({ + pauseMetadata: { + type: PauseType.WEBHOOK, + response: {} + }, + }); + + return undefined; + } else { + return { + status: ctx.resumePayload.queryParams['status'], + }; + } + }, +}); diff --git a/packages/pieces/community/todos/src/lib/utils/utils.ts b/packages/pieces/community/todos/src/lib/utils/utils.ts new file mode 100644 index 0000000..448be08 --- /dev/null +++ b/packages/pieces/community/todos/src/lib/utils/utils.ts @@ -0,0 +1,142 @@ +import { HttpMethod, AuthenticationType, HttpRequest, httpClient } from "@activepieces/pieces-common"; +import { Property } from "@activepieces/pieces-framework"; +import { CreateTodoRequestBody, PopulatedTodo, SeekPage, STATUS_VARIANT, UserWithMetaInformation } from "@activepieces/shared"; + + +export const createTodoProps = { + title: Property.ShortText({ + displayName: 'Title', + required: true, + }), + description: Property.LongText({ + displayName: 'Description', + description: + 'These details will be displayed for the assignee. Add the full context so they can take proper action, You can also use markdown formatting.', + required: false, + }), + assigneeId: Property.Dropdown({ + displayName: 'Assignee', + required: false, + options: async (_, context) => { + const baseApiUrl = context.server.publicUrl; + const apiKey = context.server.token; + const users = await listAssignee(baseApiUrl, apiKey); + return { + options: users.data.map((user) => ({ + value: user.id, + label: `${user.firstName} ${user.lastName}`, + })), + }; + }, + refreshers: [], + }), + statusOptions: Property.Array({ + displayName: 'Status Options', + required: true, + defaultValue: [ + { + name: 'Accepted', + variant: STATUS_VARIANT.POSITIVE, + continueFlow: true, + }, + { + name: 'Rejected', + variant: STATUS_VARIANT.NEGATIVE, + continueFlow: true, + }, + ], + properties: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + variant: Property.StaticDropdown({ + displayName: 'Variant', + required: true, + defaultValue: STATUS_VARIANT.POSITIVE, + options: { + options: Object.values(STATUS_VARIANT).map((variant) => ({ + value: variant, + label: variant, + })), + }, + }), + continueFlow: Property.Checkbox({ + displayName: 'Continue Flow', + required: true, + defaultValue: true, + }), + }, + }), +} + +export function constructTodoUrl(publicUrl: string, todoId: string, status: string, isTest: boolean) { + return `${publicUrl}v1/todos/${todoId}/resolve?status=${status}&isTest=${isTest}`; +} + +type ApprovalParms = { + propsValue: { + title: string; + description?: string; + statusOptions: unknown[]; + assigneeId?: string; + }; + flows: { + current: { + id: string; + }; + }; + run: { + id: string; + }; + server: { + publicUrl: string; + token: string; + }; + generateResumeUrl: (options: { queryParams: Record }) => string; +} +export async function sendTodoApproval(context: ApprovalParms, isTest: boolean) { + const requestBody: CreateTodoRequestBody = { + title: context.propsValue.title, + description: context.propsValue.description ?? '', + statusOptions: context.propsValue.statusOptions.map((option: any) => ({ + name: option.name, + description: option.description, + variant: option.variant, + continueFlow: option.continueFlow, + })), + flowId: context.flows.current.id, + runId: isTest ? undefined : context.run.id, + assigneeId: context.propsValue.assigneeId ?? undefined, + resolveUrl: context.generateResumeUrl({ + queryParams: {}, + }), + }; + return await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.server.publicUrl}v1/todos`, + body: requestBody, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.server.token, + }, + }); +} + +export async function listAssignee( + publicUrl: string, + token: string +): Promise> { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${publicUrl}v1/todos/assignees`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: token, + }, + }; + const res = await httpClient.sendRequest>( + request + ); + return res.body; +} diff --git a/packages/pieces/community/todos/tsconfig.json b/packages/pieces/community/todos/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/todos/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/todos/tsconfig.lib.json b/packages/pieces/community/todos/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/todos/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/totalcms/.eslintrc.json b/packages/pieces/community/totalcms/.eslintrc.json new file mode 100644 index 0000000..15ede17 --- /dev/null +++ b/packages/pieces/community/totalcms/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*", "package.json"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/totalcms/README.md b/packages/pieces/community/totalcms/README.md new file mode 100644 index 0000000..cd32ecd --- /dev/null +++ b/packages/pieces/community/totalcms/README.md @@ -0,0 +1,7 @@ +# pieces-totalcms + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-totalcms` to build the library. diff --git a/packages/pieces/community/totalcms/package.json b/packages/pieces/community/totalcms/package.json new file mode 100644 index 0000000..10b53fb --- /dev/null +++ b/packages/pieces/community/totalcms/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-totalcms", + "version": "0.0.5" +} \ No newline at end of file diff --git a/packages/pieces/community/totalcms/project.json b/packages/pieces/community/totalcms/project.json new file mode 100644 index 0000000..7a18cbf --- /dev/null +++ b/packages/pieces/community/totalcms/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-totalcms", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/totalcms/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/totalcms", + "tsConfig": "packages/pieces/community/totalcms/tsconfig.lib.json", + "packageJson": "packages/pieces/community/totalcms/package.json", + "main": "packages/pieces/community/totalcms/src/index.ts", + "assets": [ + "packages/pieces/community/totalcms/*.md", + { + "input": "packages/pieces/community/totalcms/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-totalcms {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/totalcms/src/index.ts b/packages/pieces/community/totalcms/src/index.ts new file mode 100644 index 0000000..b1b6f99 --- /dev/null +++ b/packages/pieces/community/totalcms/src/index.ts @@ -0,0 +1,53 @@ +import { createPiece } from '@activepieces/pieces-framework'; +import { cmsAuth } from './lib/auth'; + +import { getBlogPostAction } from './lib/actions/get-blog-post'; +import { getContentAction } from './lib/actions/get-content'; +import { saveBlogGalleryAction } from './lib/actions/save-blog-gallery'; +import { saveBlogImageAction } from './lib/actions/save-blog-image'; +import { saveBlogPostAction } from './lib/actions/save-blog-post'; +import { saveDateAction } from './lib/actions/save-date'; +import { saveDepotAction } from './lib/actions/save-depot'; +import { saveFileAction } from './lib/actions/save-file'; +import { saveGalleryAction } from './lib/actions/save-gallery'; +import { saveImageAction } from './lib/actions/save-image'; +import { saveTextAction } from './lib/actions/save-text'; +import { saveToggleAction } from './lib/actions/save-toggle'; +import { saveVideoAction } from './lib/actions/save-video'; + +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceCategory } from '@activepieces/shared'; +import { newBlogPost } from './lib/triggers/new-blog-post'; + +export const totalcms = createPiece({ + displayName: 'Total CMS', + description: 'Content management system for modern websites', + auth: cmsAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/totalcms.png', + categories: [PieceCategory.MARKETING], + authors: ["joeworkman","kishanprmr","MoShizzle","abuaboud"], + actions: [ + getContentAction, + getBlogPostAction, + saveBlogPostAction, + saveBlogGalleryAction, + saveBlogImageAction, + saveDateAction, + saveDepotAction, + saveFileAction, + saveGalleryAction, + saveImageAction, + saveTextAction, + saveToggleAction, + saveVideoAction, + createCustomApiCallAction({ + baseUrl: (auth) => (auth as { domain: string }).domain, + auth: cmsAuth, + authMapping: async (auth) => ({ + 'total-key': (auth as { license: string }).license, + }), + }), + ], + triggers: [newBlogPost], +}); diff --git a/packages/pieces/community/totalcms/src/lib/actions/get-blog-post.ts b/packages/pieces/community/totalcms/src/lib/actions/get-blog-post.ts new file mode 100644 index 0000000..d2a5536 --- /dev/null +++ b/packages/pieces/community/totalcms/src/lib/actions/get-blog-post.ts @@ -0,0 +1,27 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getBlogPost } from '../api'; +import { cmsAuth } from '../auth'; + +export const getBlogPostAction = createAction({ + name: 'get_blog_post', + auth: cmsAuth, + displayName: 'Get Blog Post', + description: 'Get a blog post from Total CMS', + props: { + slug: Property.ShortText({ + displayName: 'CMS ID', + description: 'The CMS ID of the content to retrieve', + required: true, + }), + permalink: Property.ShortText({ + displayName: 'Permalink', + description: 'The permalink of the post to retrieve', + required: true, + }), + }, + async run(context) { + const slug = context.propsValue.slug; + const permalink = context.propsValue.permalink; + return await getBlogPost(context.auth, slug, permalink); + }, +}); diff --git a/packages/pieces/community/totalcms/src/lib/actions/get-content.ts b/packages/pieces/community/totalcms/src/lib/actions/get-content.ts new file mode 100644 index 0000000..058c7fe --- /dev/null +++ b/packages/pieces/community/totalcms/src/lib/actions/get-content.ts @@ -0,0 +1,43 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getContent } from '../api'; +import { cmsAuth } from '../auth'; + +export const getContentAction = createAction({ + name: 'get_content', + auth: cmsAuth, + displayName: 'Get Content', + description: 'Get content from your Total CMS website', + props: { + type: Property.StaticDropdown({ + displayName: 'Data Type', + description: 'The type of data to return', + required: true, + options: { + options: [ + { label: 'Blog', value: 'blog' }, + { label: 'Datastore', value: 'datastore' }, + { label: 'Date', value: 'date' }, + { label: 'Depot', value: 'depot' }, + { label: 'Feed', value: 'feed' }, + { label: 'File', value: 'file' }, + { label: 'Gallery', value: 'gallery' }, + { label: 'Image', value: 'image' }, + { label: 'Ratings', value: 'ratings' }, + { label: 'Text', value: 'text' }, + { label: 'Toggle', value: 'toggle' }, + { label: 'Video', value: 'video' }, + ], + }, + }), + slug: Property.ShortText({ + displayName: 'CMS ID', + description: 'The CMS ID of the content to retrieve', + required: true, + }), + }, + async run(context) { + const type = context.propsValue.type; + const slug = context.propsValue.slug; + return await getContent(context.auth, type, slug); + }, +}); diff --git a/packages/pieces/community/totalcms/src/lib/actions/save-blog-gallery.ts b/packages/pieces/community/totalcms/src/lib/actions/save-blog-gallery.ts new file mode 100644 index 0000000..9a1d25f --- /dev/null +++ b/packages/pieces/community/totalcms/src/lib/actions/save-blog-gallery.ts @@ -0,0 +1,127 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { saveBlogGallery } from '../api'; +import { cmsAuth } from '../auth'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const saveBlogGalleryAction = createAction({ + name: 'save_blog_gallery', + auth: cmsAuth, + displayName: 'Save Blog Post Gallery Image', + description: 'Save image to Total CMS blog post gallery', + props: { + slug: Property.ShortText({ + displayName: 'CMS ID', + description: 'The CMS ID of the blog to save', + required: true, + }), + permalink: Property.ShortText({ + displayName: 'Permalink', + description: 'The permalink of the blog post to save', + required: true, + }), + image: Property.File({ + displayName: 'Image', + description: 'The image to save', + required: true, + }), + alt: Property.ShortText({ + displayName: 'Alt Text', + description: 'The alt text for the image', + required: true, + }), + quality: Property.Number({ + displayName: 'Thumbnail Quality', + description: 'The quality of the thumbnail', + required: true, + defaultValue: 85, + }), + scaleTh: Property.Number({ + displayName: 'Thumbnail Scale', + description: 'The scale of the thumbnail', + required: true, + defaultValue: 400, + }), + scaleSq: Property.Number({ + displayName: 'Thumbnail Square Scale', + description: 'The scale of the square thumbnail', + required: true, + defaultValue: 400, + }), + resize: Property.StaticDropdown({ + displayName: 'Thumbnail Resize Method', + description: 'The method to use when resizing the thumbnail', + required: true, + defaultValue: 'auto', + options: { + options: [ + { label: 'Auto', value: 'auto' }, + { label: 'Landscape', value: 'landscape' }, + { label: 'Portrait', value: 'portrait' }, + ], + }, + }), + lcrop: Property.StaticDropdown({ + displayName: 'Thumbnail Landscape Crop', + description: + 'The method to use when cropping the landscape thumbnail for the square thumbnail', + required: true, + defaultValue: 'center', + options: { + options: [ + { label: 'Left', value: 'left' }, + { label: 'Center', value: 'center' }, + { label: 'Right', value: 'right' }, + ], + }, + }), + pcrop: Property.StaticDropdown({ + displayName: 'Thumbnail Landscape Crop', + description: + 'The method to use when cropping the landscape thumbnail for the square thumbnail', + required: true, + defaultValue: 'middle', + options: { + options: [ + { label: 'Top', value: 'top' }, + { label: 'Middle', value: 'middle' }, + { label: 'Bottom', value: 'bottom' }, + ], + }, + }), + altMeta: Property.Checkbox({ + displayName: 'Pull Alt Text from Meta Data', + description: + 'Pull the alt text from the meta data of the image. If set, place placeholder text in the alt text field above.', + required: true, + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + quality: z.number().min(1).max(100), + scaleTh: z.number().min(1), + scaleSq: z.number().min(1), + }); + const slug = context.propsValue.slug; + const image = { + filename: context.propsValue.image.filename, + base64: context.propsValue.image.base64, + }; + return await saveBlogGallery(context.auth, slug, image, { + permalink: context.propsValue.permalink, + thumbs: 1, + optimize: 1, + alttype: context.propsValue.altMeta ? 'meta' : 'user', + alt: context.propsValue.alt, + quality: context.propsValue.quality, + scale_th: context.propsValue.scaleTh, + scale_sq: context.propsValue.scaleSq, + resize: context.propsValue.resize, + lcrop: context.propsValue.lcrop, + pcrop: context.propsValue.pcrop, + }); + }, +}); diff --git a/packages/pieces/community/totalcms/src/lib/actions/save-blog-image.ts b/packages/pieces/community/totalcms/src/lib/actions/save-blog-image.ts new file mode 100644 index 0000000..ac3ee0e --- /dev/null +++ b/packages/pieces/community/totalcms/src/lib/actions/save-blog-image.ts @@ -0,0 +1,131 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; +import { saveBlogImage } from '../api'; +import { cmsAuth } from '../auth'; + +export const saveBlogImageAction = createAction({ + name: 'save_blog_image', + auth: cmsAuth, + displayName: 'Save Blog Post Image', + description: 'Save image to Total CMS blog post', + props: { + slug: Property.ShortText({ + displayName: 'CMS ID', + description: 'The CMS ID of the content to save', + required: true, + }), + permalink: Property.ShortText({ + displayName: 'Permalink', + description: 'The permalink of the blog post to save', + required: true, + }), + image: Property.File({ + displayName: 'Image', + description: 'The image to save', + required: true, + }), + alt: Property.ShortText({ + displayName: 'Alt Text', + description: 'The alt text for the image', + required: true, + }), + quality: Property.Number({ + displayName: 'Thumbnail Quality', + description: 'The quality of the thumbnail', + required: true, + defaultValue: 85, + }), + scaleTh: Property.Number({ + displayName: 'Thumbnail Scale', + description: 'The scale of the thumbnail', + required: true, + defaultValue: 400, + }), + scaleSq: Property.Number({ + displayName: 'Thumbnail Square Scale', + description: 'The scale of the square thumbnail', + required: true, + defaultValue: 400, + }), + resize: Property.StaticDropdown({ + displayName: 'Thumbnail Resize Method', + description: 'The method to use when resizing the thumbnail', + required: true, + defaultValue: 'auto', + options: { + options: [ + { label: 'Auto', value: 'auto' }, + { label: 'Landscape', value: 'landscape' }, + { label: 'Portrait', value: 'portrait' }, + ], + }, + }), + lcrop: Property.StaticDropdown({ + displayName: 'Thumbnail Landscape Crop', + description: + 'The method to use when cropping the landscape thumbnail for the square thumbnail', + required: true, + defaultValue: 'center', + options: { + options: [ + { label: 'Left', value: 'left' }, + { label: 'Center', value: 'center' }, + { label: 'Right', value: 'right' }, + ], + }, + }), + pcrop: Property.StaticDropdown({ + displayName: 'Thumbnail Landscape Crop', + description: + 'The method to use when cropping the landscape thumbnail for the square thumbnail', + required: true, + defaultValue: 'middle', + options: { + options: [ + { label: 'Top', value: 'top' }, + { label: 'Middle', value: 'middle' }, + { label: 'Bottom', value: 'bottom' }, + ], + }, + }), + altMeta: Property.Checkbox({ + displayName: 'Pull Alt Text from Meta Data', + description: + 'Pull the alt text from the meta data of the image. If set, place placeholder text in the alt text field above.', + required: true, + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + quality: z.number().min(1).max(100), + scaleTh: z.number().min(1), + scaleSq: z.number().min(1), + }); + + const slug = context.propsValue.slug; + const image = { + filename: context.propsValue.image.filename, + base64: context.propsValue.image.base64, + }; + return await saveBlogImage(context.auth, slug, image, { + permalink: context.propsValue.permalink, + thumbs: 1, + optimize: 1, + alttype: context.propsValue.altMeta ? 'meta' : 'user', + alt: context.propsValue.alt, + quality: context.propsValue.quality, + scale_th: context.propsValue.scaleTh, + scale_sq: context.propsValue.scaleSq, + resize: context.propsValue.resize, + lcrop: context.propsValue.lcrop, + pcrop: context.propsValue.pcrop, + // Cannot add support for ext option with how this API is built. + // it would break the saving of the blog post since it would try + // to save the blog post JSON file with the extension of the ext option + }); + }, +}); diff --git a/packages/pieces/community/totalcms/src/lib/actions/save-blog-post.ts b/packages/pieces/community/totalcms/src/lib/actions/save-blog-post.ts new file mode 100644 index 0000000..b3040c2 --- /dev/null +++ b/packages/pieces/community/totalcms/src/lib/actions/save-blog-post.ts @@ -0,0 +1,132 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { saveContent } from '../api'; +import { cmsAuth } from '../auth'; + +export const saveBlogPostAction = createAction({ + name: 'save_blog_post', + auth: cmsAuth, + displayName: 'Save Blog Post', + description: 'Save blog content to Total CMS', + props: { + slug: Property.ShortText({ + displayName: 'CMS ID', + description: 'The CMS ID of the content to save', + required: true, + }), + permalink: Property.ShortText({ + displayName: 'Permalink', + description: + 'The permalink of the blog post. Ensure this is unique or it will overwrite the existing post.', + required: true, + }), + title: Property.ShortText({ + displayName: 'Title', + description: 'The title of the blog post', + required: false, + }), + timestamp: Property.Number({ + displayName: 'Date (Unix Timestamp)', + description: 'The date in unix timestamp format', + required: false, + }), + summary: Property.LongText({ + displayName: 'Summary', + description: 'The summary of the blog post', + required: false, + }), + content: Property.LongText({ + displayName: 'Content', + description: 'The content of the blog post', + required: false, + }), + extra: Property.LongText({ + displayName: 'Extra Content', + description: 'The extra content of the blog post', + required: false, + }), + extra2: Property.LongText({ + displayName: 'Extra Content 2', + description: 'The extra content 2 of the blog post', + required: false, + }), + media: Property.ShortText({ + displayName: 'Media', + description: 'The media of the blog post', + required: false, + }), + rssTitle: Property.ShortText({ + displayName: 'RSS Title', + description: 'The RSS title of the blog post', + required: false, + }), + rssDescription: Property.ShortText({ + displayName: 'RSS Description', + description: 'The RSS description of the blog post', + required: false, + }), + author: Property.ShortText({ + displayName: 'Author', + description: 'The author of the blog post', + required: false, + }), + genre: Property.ShortText({ + displayName: 'Genre', + description: 'The genre of the blog post', + required: false, + }), + categories: Property.ShortText({ + displayName: 'Categories', + description: 'A comma separated list of categories for the blog post', + required: false, + }), + tags: Property.ShortText({ + displayName: 'Tags', + description: 'A comma separated list of tags for the blog post', + required: false, + }), + labels: Property.ShortText({ + displayName: 'Labels', + description: 'A comma separated list of labels for the blog post', + required: false, + }), + draft: Property.Checkbox({ + displayName: 'Draft', + description: 'Set to true to save as a draft', + required: false, + }), + featured: Property.Checkbox({ + displayName: 'Featured', + description: 'Set to true to save as a featured post', + required: false, + }), + archived: Property.Checkbox({ + displayName: 'Archived', + description: 'Set to true to save as an archived post', + required: false, + }), + }, + async run(context) { + const slug = context.propsValue.slug; + return await saveContent(context.auth, 'blog', slug, { + nodecode: true, + permalink: context.propsValue.permalink, + title: context.propsValue.title, + timestamp: context.propsValue.timestamp?.toString(), + summary: context.propsValue.summary, + content: context.propsValue.content, + extra: context.propsValue.extra, + extra2: context.propsValue.extra2, + media: context.propsValue.media, + rss_title: context.propsValue.rssTitle, + rss_description: context.propsValue.rssDescription, + author: context.propsValue.author, + genre: context.propsValue.genre, + categories: context.propsValue.categories, + tags: context.propsValue.tags, + labels: context.propsValue.labels, + draft: context.propsValue.draft ? 'true' : 'false', + featured: context.propsValue.featured ? 'true' : 'false', + archived: context.propsValue.archived ? 'true' : 'false', + }); + }, +}); diff --git a/packages/pieces/community/totalcms/src/lib/actions/save-date.ts b/packages/pieces/community/totalcms/src/lib/actions/save-date.ts new file mode 100644 index 0000000..6b06557 --- /dev/null +++ b/packages/pieces/community/totalcms/src/lib/actions/save-date.ts @@ -0,0 +1,30 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { saveContent } from '../api'; +import { cmsAuth } from '../auth'; + +export const saveDateAction = createAction({ + name: 'save_date', + auth: cmsAuth, + displayName: 'Save Date Content', + description: 'Save date content to Total CMS', + props: { + slug: Property.ShortText({ + displayName: 'CMS ID', + description: 'The CMS ID of the content to save', + required: true, + }), + timestamp: Property.Number({ + displayName: 'Unix Timestamp', + description: 'The unix timestamp to save', + required: true, + }), + }, + async run(context) { + const slug = context.propsValue.slug; + const timestamp = context.propsValue.timestamp; + return await saveContent(context.auth, 'date', slug, { + nodecode: true, + timestamp: timestamp.toString(), + }); + }, +}); diff --git a/packages/pieces/community/totalcms/src/lib/actions/save-depot.ts b/packages/pieces/community/totalcms/src/lib/actions/save-depot.ts new file mode 100644 index 0000000..3e8effe --- /dev/null +++ b/packages/pieces/community/totalcms/src/lib/actions/save-depot.ts @@ -0,0 +1,30 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { saveDepot } from '../api'; +import { cmsAuth } from '../auth'; + +export const saveDepotAction = createAction({ + name: 'save_depot', + auth: cmsAuth, + displayName: 'Save Depot', + description: 'Save file to Total CMS depot', + props: { + slug: Property.ShortText({ + displayName: 'CMS ID', + description: 'The CMS ID of the depot to save', + required: true, + }), + file: Property.File({ + displayName: 'File', + description: 'The file to save', + required: true, + }), + }, + async run(context) { + const slug = context.propsValue.slug; + const file = { + filename: context.propsValue.file.filename, + base64: context.propsValue.file.base64, + }; + return await saveDepot(context.auth, slug, file); + }, +}); diff --git a/packages/pieces/community/totalcms/src/lib/actions/save-file.ts b/packages/pieces/community/totalcms/src/lib/actions/save-file.ts new file mode 100644 index 0000000..a4e918b --- /dev/null +++ b/packages/pieces/community/totalcms/src/lib/actions/save-file.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { saveFile } from '../api'; +import { cmsAuth } from '../auth'; + +export const saveFileAction = createAction({ + name: 'save_file', + auth: cmsAuth, + displayName: 'Save File', + description: 'Save file to Total CMS', + props: { + slug: Property.ShortText({ + displayName: 'CMS ID', + description: 'The CMS ID of the file to save', + required: true, + }), + ext: Property.ShortText({ + displayName: 'File Extension', + description: 'The file extension of the file', + required: true, + }), + file: Property.File({ + displayName: 'File', + description: 'The file to save', + required: true, + }), + }, + async run(context) { + const slug = context.propsValue.slug; + const file = { + filename: context.propsValue.file.filename, + base64: context.propsValue.file.base64, + }; + return await saveFile(context.auth, slug, file, { + ext: context.propsValue.ext, + }); + }, +}); diff --git a/packages/pieces/community/totalcms/src/lib/actions/save-gallery.ts b/packages/pieces/community/totalcms/src/lib/actions/save-gallery.ts new file mode 100644 index 0000000..11dc07c --- /dev/null +++ b/packages/pieces/community/totalcms/src/lib/actions/save-gallery.ts @@ -0,0 +1,122 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { saveGallery } from '../api'; +import { cmsAuth } from '../auth'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const saveGalleryAction = createAction({ + name: 'save_gallery', + auth: cmsAuth, + displayName: 'Save Gallery Image', + description: 'Save image to Total CMS gallery', + props: { + slug: Property.ShortText({ + displayName: 'CMS ID', + description: 'The CMS ID of the gallery to save', + required: true, + }), + image: Property.File({ + displayName: 'Image', + description: 'The image to save', + required: true, + }), + alt: Property.ShortText({ + displayName: 'Alt Text', + description: 'The alt text for the image', + required: true, + }), + quality: Property.Number({ + displayName: 'Thumbnail Quality', + description: 'The quality of the thumbnail', + required: true, + defaultValue: 85, + }), + scaleTh: Property.Number({ + displayName: 'Thumbnail Scale', + description: 'The scale of the thumbnail', + required: true, + defaultValue: 400, + }), + scaleSq: Property.Number({ + displayName: 'Thumbnail Square Scale', + description: 'The scale of the square thumbnail', + required: true, + defaultValue: 400, + }), + resize: Property.StaticDropdown({ + displayName: 'Thumbnail Resize Method', + description: 'The method to use when resizing the thumbnail', + required: true, + defaultValue: 'auto', + options: { + options: [ + { label: 'Auto', value: 'auto' }, + { label: 'Landscape', value: 'landscape' }, + { label: 'Portrait', value: 'portrait' }, + ], + }, + }), + lcrop: Property.StaticDropdown({ + displayName: 'Thumbnail Landscape Crop', + description: + 'The method to use when cropping the landscape thumbnail for the square thumbnail', + required: true, + defaultValue: 'center', + options: { + options: [ + { label: 'Left', value: 'left' }, + { label: 'Center', value: 'center' }, + { label: 'Right', value: 'right' }, + ], + }, + }), + pcrop: Property.StaticDropdown({ + displayName: 'Thumbnail Landscape Crop', + description: + 'The method to use when cropping the landscape thumbnail for the square thumbnail', + required: true, + defaultValue: 'middle', + options: { + options: [ + { label: 'Top', value: 'top' }, + { label: 'Middle', value: 'middle' }, + { label: 'Bottom', value: 'bottom' }, + ], + }, + }), + altMeta: Property.Checkbox({ + displayName: 'Pull Alt Text from Meta Data', + description: + 'Pull the alt text from the meta data of the image. If set, place placeholder text in the alt text field above.', + required: true, + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + quality: z.number().min(1).max(100), + scaleTh: z.number().min(1), + scaleSq: z.number().min(1), + }); + + const slug = context.propsValue.slug; + const image = { + filename: context.propsValue.image.filename, + base64: context.propsValue.image.base64, + }; + return await saveGallery(context.auth, slug, image, { + thumbs: 1, + optimize: 1, + alttype: context.propsValue.altMeta ? 'meta' : 'user', + alt: context.propsValue.alt, + quality: context.propsValue.quality, + scale_th: context.propsValue.scaleTh, + scale_sq: context.propsValue.scaleSq, + resize: context.propsValue.resize, + lcrop: context.propsValue.lcrop, + pcrop: context.propsValue.pcrop, + }); + }, +}); diff --git a/packages/pieces/community/totalcms/src/lib/actions/save-image.ts b/packages/pieces/community/totalcms/src/lib/actions/save-image.ts new file mode 100644 index 0000000..2378e2c --- /dev/null +++ b/packages/pieces/community/totalcms/src/lib/actions/save-image.ts @@ -0,0 +1,134 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { saveImage } from '../api'; +import { cmsAuth } from '../auth'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const saveImageAction = createAction({ + name: 'save_image', + auth: cmsAuth, + displayName: 'Save Image', + description: 'Save image to Total CMS', + props: { + slug: Property.ShortText({ + displayName: 'CMS ID', + description: 'The CMS ID of the content to save', + required: true, + }), + image: Property.File({ + displayName: 'Image', + description: 'The image to save', + required: true, + }), + alt: Property.ShortText({ + displayName: 'Alt Text', + description: 'The alt text for the image', + required: true, + }), + ext: Property.StaticDropdown({ + displayName: 'Extension', + description: 'The extension of the image', + required: true, + defaultValue: 'jpg', + options: { + options: [ + { label: 'jpg', value: 'jpg' }, + { label: 'png', value: 'png' }, + ], + }, + }), + quality: Property.Number({ + displayName: 'Thumbnail Quality', + description: 'The quality of the thumbnail', + required: true, + defaultValue: 85, + }), + scaleTh: Property.Number({ + displayName: 'Thumbnail Scale', + description: 'The scale of the thumbnail', + required: true, + defaultValue: 400, + }), + scaleSq: Property.Number({ + displayName: 'Thumbnail Square Scale', + description: 'The scale of the square thumbnail', + required: true, + defaultValue: 400, + }), + resize: Property.StaticDropdown({ + displayName: 'Thumbnail Resize Method', + description: 'The method to use when resizing the thumbnail', + required: true, + defaultValue: 'auto', + options: { + options: [ + { label: 'Auto', value: 'auto' }, + { label: 'Landscape', value: 'landscape' }, + { label: 'Portrait', value: 'portrait' }, + ], + }, + }), + lcrop: Property.StaticDropdown({ + displayName: 'Thumbnail Landscape Crop', + description: + 'The method to use when cropping the landscape thumbnail for the square thumbnail', + required: true, + defaultValue: 'center', + options: { + options: [ + { label: 'Left', value: 'left' }, + { label: 'Center', value: 'center' }, + { label: 'Right', value: 'right' }, + ], + }, + }), + pcrop: Property.StaticDropdown({ + displayName: 'Thumbnail Landscape Crop', + description: + 'The method to use when cropping the landscape thumbnail for the square thumbnail', + required: true, + defaultValue: 'middle', + options: { + options: [ + { label: 'Top', value: 'top' }, + { label: 'Middle', value: 'middle' }, + { label: 'Bottom', value: 'bottom' }, + ], + }, + }), + altMeta: Property.Checkbox({ + displayName: 'Pull Alt Text from Meta Data', + description: + 'Pull the alt text from the meta data of the image. If set, place placeholder text in the alt text field above.', + required: true, + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + quality: z.number().min(1).max(100), + scaleTh: z.number().min(1), + scaleSq: z.number().min(1), + }); + const slug = context.propsValue.slug; + const image = { + filename: context.propsValue.image.filename, + base64: context.propsValue.image.base64, + }; + return await saveImage(context.auth, slug, image, { + thumbs: 1, + optimize: 1, + alttype: context.propsValue.altMeta ? 'meta' : 'user', + alt: context.propsValue.alt, + ext: context.propsValue.ext, + quality: context.propsValue.quality, + scale_th: context.propsValue.scaleTh, + scale_sq: context.propsValue.scaleSq, + resize: context.propsValue.resize, + lcrop: context.propsValue.lcrop, + pcrop: context.propsValue.pcrop, + }); + }, +}); diff --git a/packages/pieces/community/totalcms/src/lib/actions/save-text.ts b/packages/pieces/community/totalcms/src/lib/actions/save-text.ts new file mode 100644 index 0000000..f63601e --- /dev/null +++ b/packages/pieces/community/totalcms/src/lib/actions/save-text.ts @@ -0,0 +1,30 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { saveContent } from '../api'; +import { cmsAuth } from '../auth'; + +export const saveTextAction = createAction({ + name: 'save_text', + auth: cmsAuth, + displayName: 'Save Text Content', + description: 'Save text content to Total CMS', + props: { + slug: Property.ShortText({ + displayName: 'CMS ID', + description: 'The CMS ID of the content to save', + required: true, + }), + text: Property.LongText({ + displayName: 'Text Content', + description: 'The text content to save', + required: true, + }), + }, + async run(context) { + const slug = context.propsValue.slug; + const text = context.propsValue.text; + return await saveContent(context.auth, 'text', slug, { + nodecode: true, + text: text, + }); + }, +}); diff --git a/packages/pieces/community/totalcms/src/lib/actions/save-toggle.ts b/packages/pieces/community/totalcms/src/lib/actions/save-toggle.ts new file mode 100644 index 0000000..af9d146 --- /dev/null +++ b/packages/pieces/community/totalcms/src/lib/actions/save-toggle.ts @@ -0,0 +1,30 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { saveContent } from '../api'; +import { cmsAuth } from '../auth'; + +export const saveToggleAction = createAction({ + name: 'save_toggle', + auth: cmsAuth, + displayName: 'Save Toggle', + description: 'Save toggle content to Total CMS', + props: { + slug: Property.ShortText({ + displayName: 'CMS ID', + description: 'The CMS ID of the content to save', + required: true, + }), + status: Property.Checkbox({ + displayName: 'Status', + description: 'The status of the toggle. "true" is on, "false" is off.', + required: true, + }), + }, + async run(context) { + const slug = context.propsValue.slug; + const status = context.propsValue.status ? 'true' : 'false'; + return await saveContent(context.auth, 'toggle', slug, { + nodecode: true, + state: status, + }); + }, +}); diff --git a/packages/pieces/community/totalcms/src/lib/actions/save-video.ts b/packages/pieces/community/totalcms/src/lib/actions/save-video.ts new file mode 100644 index 0000000..4c281c5 --- /dev/null +++ b/packages/pieces/community/totalcms/src/lib/actions/save-video.ts @@ -0,0 +1,39 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { saveContent } from '../api'; +import { cmsAuth } from '../auth'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const saveVideoAction = createAction({ + name: 'save_video', + auth: cmsAuth, + displayName: 'Save Video Content', + description: 'Save video content to Total CMS', + props: { + slug: Property.ShortText({ + displayName: 'CMS ID', + description: 'The CMS ID of the content to save', + required: true, + }), + video: Property.ShortText({ + displayName: 'Video URL', + description: 'The URL of the video to save', + required: true, + }), + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + video: z.string().url(), + }); + + const slug = context.propsValue.slug; + const video = context.propsValue.video; + return await saveContent(context.auth, 'video', slug, { + nodecode: true, + video: video, + }); + }, +}); diff --git a/packages/pieces/community/totalcms/src/lib/api.ts b/packages/pieces/community/totalcms/src/lib/api.ts new file mode 100644 index 0000000..4c1aeb8 --- /dev/null +++ b/packages/pieces/community/totalcms/src/lib/api.ts @@ -0,0 +1,173 @@ +import { + httpClient, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; +import { TotalCMSAuthType } from './auth'; +import FormData from 'form-data'; + +export type KeyValuePair = { + [key: string]: string | boolean | number | object | undefined; +}; +export type FileUpload = { filename: string; base64: string }; + +const totalcmsAPI = async ( + auth: TotalCMSAuthType, + type: string, + slug: string, + query: QueryParams = {}, + data: KeyValuePair = {}, + method: HttpMethod = HttpMethod.GET +) => { + if (method === HttpMethod.GET) { + query['slug'] = slug; + query['type'] = type; + } else { + data['slug'] = slug; + data['type'] = type; + } + + const request: HttpRequest = { + body: data, + queryParams: query, + method: method, + url: `${auth.domain}/rw_common/plugins/stacks/total-cms/totalapi.php`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'total-key': auth.license, + }, + }; + const response = await httpClient.sendRequest(request); + + if (response.status !== 200) { + throw new Error(`Total CMS API error: ${response.status} ${response.body}`); + } + + return { + success: true, + data: response.body['data'], + }; +}; + +const totalcmsUploadAPI = async ( + auth: TotalCMSAuthType, + type: string, + slug: string, + file: FileUpload, + data: KeyValuePair = {}, + fileName = 'file' +) => { + const formData = new FormData(); + formData.append('type', type); + formData.append('slug', slug); + + formData.append(fileName, Buffer.from(file.base64, 'base64'), file.filename); + + for (const key in data) { + if (fileName !== 'file') { + // blog post images use the format image[alt] or gallery[alt] + formData.append(`${fileName}[${key}]`, data[key]); + } + formData.append(key, data[key]); + } + + const request: HttpRequest = { + body: formData, + method: HttpMethod.POST, + url: `${auth.domain}/rw_common/plugins/stacks/total-cms/totalapi.php`, + headers: { + 'Content-Type': 'multipart/form-data', + 'total-key': auth.license, + }, + }; + const response = await httpClient.sendRequest(request); + + if (response.status !== 200) { + throw new Error(`Total CMS API error: ${response.status} ${response.body}`); + } + + return { + success: true, + data: response.body['data'], + }; +}; + +export async function saveFile( + auth: TotalCMSAuthType, + slug: string, + file: FileUpload, + data: KeyValuePair +) { + return totalcmsUploadAPI(auth, 'file', slug, file, data); +} + +export async function saveDepot( + auth: TotalCMSAuthType, + slug: string, + file: FileUpload +) { + return totalcmsUploadAPI(auth, 'depot', slug, file); +} + +export async function saveImage( + auth: TotalCMSAuthType, + slug: string, + file: FileUpload, + data: KeyValuePair +) { + return totalcmsUploadAPI(auth, 'image', slug, file, data); +} + +export async function saveGallery( + auth: TotalCMSAuthType, + slug: string, + file: FileUpload, + data: KeyValuePair +) { + return totalcmsUploadAPI(auth, 'gallery', slug, file, data); +} + +export async function saveBlogImage( + auth: TotalCMSAuthType, + slug: string, + file: FileUpload, + data: KeyValuePair +) { + return totalcmsUploadAPI(auth, 'blog', slug, file, data, 'image'); +} + +export async function saveBlogGallery( + auth: TotalCMSAuthType, + slug: string, + file: FileUpload, + data: KeyValuePair +) { + return totalcmsUploadAPI(auth, 'blog', slug, file, data, 'gallery'); +} + +export async function saveContent( + auth: TotalCMSAuthType, + type: string, + slug: string, + data: KeyValuePair +) { + return totalcmsAPI(auth, type, slug, {}, data, HttpMethod.POST); +} + +export async function getContent( + auth: TotalCMSAuthType, + type: string, + slug: string, + query: QueryParams = {} +) { + return totalcmsAPI(auth, type, slug, query); +} + +export async function getBlogPost( + auth: TotalCMSAuthType, + slug: string, + permalink: string +) { + return totalcmsAPI(auth, 'blog', slug, { permalink: permalink }); +} diff --git a/packages/pieces/community/totalcms/src/lib/auth.ts b/packages/pieces/community/totalcms/src/lib/auth.ts new file mode 100644 index 0000000..8d9fd6f --- /dev/null +++ b/packages/pieces/community/totalcms/src/lib/auth.ts @@ -0,0 +1,41 @@ +import { PieceAuth, Property } from '@activepieces/pieces-framework'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; +import { saveContent } from './api'; + +export type TotalCMSAuthType = { license: string; domain: string }; + +export const cmsAuth = PieceAuth.CustomAuth({ + description: 'Setup your Total CMS connection', + props: { + domain: Property.ShortText({ + displayName: 'Total CMS Domain', + description: 'The domain of your Total CMS website', + required: true, + }), + license: PieceAuth.SecretText({ + displayName: 'License Key', + description: 'The License key for your Total CMS domain', + required: true, + }), + }, + required: true, + async validate({ auth }) { + await propsValidation.validateZod(auth, { + domain: z.string().url(), + license: z.string(), + }); + + const response = await saveContent(auth, 'text', 'activepieces', { + text: 'verified', + }); + if (response.success !== true) { + throw new Error( + 'Authentication failed. Please check your domain and license key and try again.' + ); + } + return { + valid: true, + }; + }, +}); diff --git a/packages/pieces/community/totalcms/src/lib/triggers/new-blog-post.ts b/packages/pieces/community/totalcms/src/lib/triggers/new-blog-post.ts new file mode 100644 index 0000000..8ea9f0a --- /dev/null +++ b/packages/pieces/community/totalcms/src/lib/triggers/new-blog-post.ts @@ -0,0 +1,74 @@ +import { + TriggerStrategy, + createTrigger, + Property, + PiecePropValueSchema, +} from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { TotalCMSAuthType, cmsAuth } from '../auth'; +import { getContent } from '../api'; + +const polling: Polling< + PiecePropValueSchema, + { slug: string } +> = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ auth, propsValue }) => { + const slug = propsValue.slug; + const posts = await getContent(auth, 'blog', slug); + + return posts.data.map((post: { permalink: string }) => ({ + id: post.permalink, + data: post, + })); + }, +}; + +export const newBlogPost = createTrigger({ + name: 'new_blog_post', + displayName: 'New Blog Post', + description: 'Triggers when a new blog post is published', + type: TriggerStrategy.POLLING, + props: { + slug: Property.ShortText({ + displayName: 'CMS ID', + description: 'The CMS ID of the content to retrieve', + required: true, + }), + }, + sampleData: {}, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth as TotalCMSAuthType, + store: context.store, + propsValue: context.propsValue, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth as TotalCMSAuthType, + store: context.store, + propsValue: context.propsValue, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, { + auth: context.auth as TotalCMSAuthType, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, + test: async (context) => { + return await pollingHelper.test(polling, { + auth: context.auth as TotalCMSAuthType, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, +}); diff --git a/packages/pieces/community/totalcms/tsconfig.json b/packages/pieces/community/totalcms/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/totalcms/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/totalcms/tsconfig.lib.json b/packages/pieces/community/totalcms/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/totalcms/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/trello/.eslintrc.json b/packages/pieces/community/trello/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/trello/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/trello/README.md b/packages/pieces/community/trello/README.md new file mode 100644 index 0000000..1ae0cbe --- /dev/null +++ b/packages/pieces/community/trello/README.md @@ -0,0 +1,7 @@ +# pieces-trello + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-trello` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/trello/package.json b/packages/pieces/community/trello/package.json new file mode 100644 index 0000000..3bc6e27 --- /dev/null +++ b/packages/pieces/community/trello/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-trello", + "version": "0.3.8" +} diff --git a/packages/pieces/community/trello/project.json b/packages/pieces/community/trello/project.json new file mode 100644 index 0000000..95a87af --- /dev/null +++ b/packages/pieces/community/trello/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-trello", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/trello/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/trello", + "tsConfig": "packages/pieces/community/trello/tsconfig.lib.json", + "packageJson": "packages/pieces/community/trello/package.json", + "main": "packages/pieces/community/trello/src/index.ts", + "assets": [ + "packages/pieces/community/trello/*.md", + { + "input": "packages/pieces/community/trello/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/trello/src/index.ts b/packages/pieces/community/trello/src/index.ts new file mode 100644 index 0000000..03a9667 --- /dev/null +++ b/packages/pieces/community/trello/src/index.ts @@ -0,0 +1,77 @@ +import { + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createCard } from './lib/actions/create-card'; +import { getCard } from './lib/actions/get-card'; +import { cardMovedTrigger } from './lib/triggers/cardMoved'; +import { newCardTrigger } from './lib/triggers/newCard'; + +const markdownProperty = ` +To obtain your API key and token, follow these steps: + +1. Go to https://trello.com/power-ups/admin +2. Click **New** to create a new power-up +3. Enter power-up information, and click **Create** +4. From the API Key page, click **Generate a new API key** +5. Copy **API Key** and enter it into the Trello API Key connection +6. Click **manually generate a Token** next to the API key field +7. Copy the token and paste it into the Trello Token connection +8. Your connection should now work! +`; +export const trelloAuth = PieceAuth.BasicAuth({ + description: markdownProperty, + required: true, + username: { + displayName: 'API Key', + description: 'Trello API Key', + }, + password: { + displayName: 'Token', + description: 'Trello Token', + }, + validate: async ({ auth }) => { + const { username, password } = auth; + if (!username || !password) { + return { + valid: false, + error: 'Empty API Key or Token', + }; + } + try { + const request: HttpRequest = { + method: HttpMethod.GET, + url: + `https://api.trello.com/1/members/me/boards` + + `?key=` + + username + + `&token=` + + password, + }; + await httpClient.sendRequest(request); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API Key or Token', + }; + } + }, +}); + +export const trello = createPiece({ + displayName: 'Trello', + description: 'Project management tool for teams', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/trello.png', + authors: ["Salem-Alaa","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + categories: [PieceCategory.PRODUCTIVITY], + auth: trelloAuth, + actions: [createCard, getCard], + triggers: [cardMovedTrigger, newCardTrigger], +}); diff --git a/packages/pieces/community/trello/src/lib/actions/create-card.ts b/packages/pieces/community/trello/src/lib/actions/create-card.ts new file mode 100644 index 0000000..a9dd83f --- /dev/null +++ b/packages/pieces/community/trello/src/lib/actions/create-card.ts @@ -0,0 +1,69 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpRequest, + HttpMethod, +} from '@activepieces/pieces-common'; +import { trelloCommon } from '../common'; +import { TrelloCard } from '../common/props/card'; +import { trelloAuth } from '../..'; + +export const createCard = createAction({ + auth: trelloAuth, + name: 'create_card', + displayName: 'Create Card', + description: 'Create a new card in Trello', + props: { + board_id: trelloCommon.board_id, + list_id: trelloCommon.list_id, + name: Property.ShortText({ + description: 'The name of the card to create', + displayName: 'Task Name', + required: true, + }), + description: Property.LongText({ + description: 'The description of the card to create', + displayName: 'Task Description', + required: false, + }), + position: Property.StaticDropdown({ + description: 'Place the card on top or bottom of the list', + displayName: 'Position', + required: false, + options: { + options: [ + { label: 'Top', value: 'top' }, + { label: 'Bottom', value: 'bottom' }, + ], + }, + }), + labels: trelloCommon.board_labels, + }, + + async run(context) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: + `${trelloCommon.baseUrl}cards` + + `?idList=` + + context.propsValue['list_id'] + + `&key=` + + context.auth.username + + `&token=` + + context.auth.password, + headers: { + Accept: 'application/json', + }, + body: { + name: context.propsValue['name'], + desc: context.propsValue['description'], + pos: context.propsValue['position'], + idLabels: context.propsValue['labels'], + }, + queryParams: {}, + }; + const response = await httpClient.sendRequest(request); + + return response.body; + }, +}); diff --git a/packages/pieces/community/trello/src/lib/actions/get-card.ts b/packages/pieces/community/trello/src/lib/actions/get-card.ts new file mode 100644 index 0000000..10980e9 --- /dev/null +++ b/packages/pieces/community/trello/src/lib/actions/get-card.ts @@ -0,0 +1,40 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpRequest, + HttpMethod, +} from '@activepieces/pieces-common'; +import { trelloCommon } from '../common'; +import { TrelloCard } from '../common/props/card'; +import { trelloAuth } from '../..'; + +export const getCard = createAction({ + auth: trelloAuth, + name: 'get_card', + displayName: 'Get Card', + description: 'Get a card in Trello', + props: { + cardId: Property.ShortText({ + description: 'The card ID', + displayName: 'Card ID', + required: true, + }), + }, + + async run(context) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: + `${trelloCommon.baseUrl}cards/` + + context.propsValue['cardId'] + + `?key=` + + context.auth.username + + `&token=` + + context.auth.password, + headers: { + Accept: 'application/json', + }, + }; + return (await httpClient.sendRequest(request)).body; + }, +}); diff --git a/packages/pieces/community/trello/src/lib/common/index.ts b/packages/pieces/community/trello/src/lib/common/index.ts new file mode 100644 index 0000000..618c607 --- /dev/null +++ b/packages/pieces/community/trello/src/lib/common/index.ts @@ -0,0 +1,293 @@ +import { BasicAuthPropertyValue, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpRequest, HttpMethod } from '@activepieces/pieces-common'; + +export interface WebhookInformation { + id: string; + description: string; + idModel: string; + callbackURL: string; + active: boolean; + consecutiveFailures: number; + firstConsecutiveFailDate: string; +} + +export const trelloCommon = { + baseUrl: 'https://api.trello.com/1/', + board_id: Property.Dropdown({ + displayName: 'Boards', + description: 'List of boards', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + + const basicAuthProperty = auth as BasicAuthPropertyValue; + const user = await getAuthorisedUser(basicAuthProperty.username, basicAuthProperty.password); + const boards = await listBoards( + basicAuthProperty.username, + basicAuthProperty.password, + user['id'], + ); + + return { + options: boards.map((board: { id: string; name: string }) => ({ + value: board.id, + label: board.name, + })), + }; + }, + }), + list_id: Property.Dropdown({ + displayName: 'Lists', + description: 'Get lists from a board', + required: true, + refreshers: ['board_id'], + options: async ({ auth, board_id }) => { + if (!auth || !board_id) { + return { + disabled: true, + placeholder: 'connect your account first and select a board', + options: [], + }; + } + + const basicAuthProperty = auth as BasicAuthPropertyValue; + const lists = await listBoardLists( + basicAuthProperty.username, + basicAuthProperty.password, + board_id as string, + ); + + return { + options: lists.map((list: { id: string; name: string }) => ({ + value: list.id, + label: list.name, + })), + }; + }, + }), + list_id_opt: Property.Dropdown({ + displayName: 'Lists', + description: 'Get lists from a board', + required: false, + refreshers: ['board_id'], + options: async ({ auth, board_id }) => { + if (!auth || !board_id) { + return { + disabled: true, + placeholder: 'connect your account first and select a board', + options: [], + }; + } + const basicAuthProperty = auth as BasicAuthPropertyValue; + const lists = await listBoardLists( + basicAuthProperty.username, + basicAuthProperty.password, + board_id as string, + ); + + return { + options: lists.map((list: { id: string; name: string }) => ({ + value: list.id, + label: list.name, + })), + }; + }, + }), + board_labels: Property.MultiSelectDropdown({ + displayName: 'Labels', + description: 'Assign labels to the card', + required: false, + refreshers: ['board_id'], + options: async ({ auth, board_id }) => { + if (!auth || !board_id) { + return { + disabled: true, + placeholder: 'connect your account first and select a board', + options: [], + }; + } + + const basicAuthProperty = auth as BasicAuthPropertyValue; + const labels = await listBoardLabels( + basicAuthProperty.username, + basicAuthProperty.password, + board_id as string, + ); + + return { + options: labels.map((label: { id: string; name: string; color: string }) => ({ + label: label.name || label.color, + value: label.id, + })), + }; + }, + }), + create_webhook: async (auth: BasicAuthPropertyValue, list_id: string, webhookUrl: string) => { + const request: HttpRequest = { + method: HttpMethod.POST, + url: + `${trelloCommon.baseUrl}webhooks` + + `?key=` + + auth.username + + `&token=` + + auth.password + + `&callbackURL=` + + webhookUrl + + `&idModel=` + + list_id, + }; + const response = await httpClient.sendRequest(request); + + return response.body; + }, + delete_webhook: async (auth: BasicAuthPropertyValue, webhook_id: string) => { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: + `${trelloCommon.baseUrl}webhooks/${webhook_id}` + + `?key=` + + auth.username + + `&token=` + + auth.password, + }; + const response = await httpClient.sendRequest(request); + + return response.body; + }, + list_webhooks: async (auth: BasicAuthPropertyValue) => { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${trelloCommon.baseUrl}tokens/${auth.password}/webhooks` + `?key=` + auth.username, + }; + const response = await httpClient.sendRequest(request); + + return response.body; + }, +}; + +/** + * Gets the authenticated user via API and token + * @param apikey API Key + * @param token Token Key + * @returns JSON containing Trello user + */ +async function getAuthorisedUser(apikey: string, token: string) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${trelloCommon.baseUrl}members/me` + `?key=` + apikey + `&token=` + token, + headers: { + Accept: 'application/json', + }, + }; + const response = await httpClient.sendRequest(request); + + return response.body; +} + +/** + * Lists all boards a member has access to in Trello + * @param apikey API Key + * @param token API Token + * @param user_id ID of the user + * @returns JSON Array of boards user has access to + */ +async function listBoards(apikey: string, token: string, user_id: string) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${trelloCommon.baseUrl}members/${user_id}/boards` + `?key=` + apikey + `&token=` + token, + headers: { + Accept: 'application/json', + }, + }; + const response = await httpClient.sendRequest<{ id: string; name: string }[]>(request); + + return response.body; +} + +/** + * Gets all the lists inside of a board + * @param apikey API Key + * @param token API Token + * @param board_id Board to fetch lists from + * @returns JSON Array of lists + */ +async function listBoardLists(apikey: string, token: string, board_id: string) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${trelloCommon.baseUrl}boards/${board_id}/lists` + `?key=` + apikey + `&token=` + token, + headers: { + Accept: 'application/json', + }, + }; + const response = await httpClient.sendRequest<{ id: string; name: string }[]>(request); + + return response.body; +} + +/** + * Gets all the labels of a board + * @param apikey API Key + * @param token API Token + * @param board_id Board to fetch labels from + * @returns JSON Array of labels + */ +async function listBoardLabels(apikey: string, token: string, board_id: string) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${trelloCommon.baseUrl}boards/${board_id}/labels` + `?key=` + apikey + `&token=` + token, + headers: { + Accept: 'application/json', + }, + }; + const response = await httpClient.sendRequest<{ id: string; name: string; color: string }[]>( + request, + ); + + return response.body; +} + +export async function getCardDetail(apikey: string, token: string, card_id: string) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${trelloCommon.baseUrl}cards/${card_id}` + `?key=` + apikey + `&token=` + token, + headers: { + Accept: 'application/json', + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} + +export async function getCardsInBoard(apikey: string, token: string, board_id: string) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${trelloCommon.baseUrl}boards/${board_id}/cards` + `?key=` + apikey + `&token=` + token, + headers: { + Accept: 'application/json', + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} + +export async function getCardsInList(apikey: string, token: string, list_id: string) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${trelloCommon.baseUrl}lists/${list_id}/cards` + `?key=` + apikey + `&token=` + token, + headers: { + Accept: 'application/json', + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} diff --git a/packages/pieces/community/trello/src/lib/common/props/card.ts b/packages/pieces/community/trello/src/lib/common/props/card.ts new file mode 100644 index 0000000..24af5c6 --- /dev/null +++ b/packages/pieces/community/trello/src/lib/common/props/card.ts @@ -0,0 +1,113 @@ +export type TrelloCard = { + id: string; + address: string; + badges: { + attachmentsByType: { + trello: { + board: number; + card: number; + }; + }; + location: boolean; + votes: number; + viewingMemberVoted: boolean; + subscribed: boolean; + fogbugz: string; + checkItems: number; + checkItemsChecked: number; + comments: number; + attachments: number; + description: boolean; + due: string; + start: string; + dueComplete: boolean; + }; + checkItemStates: string[]; + closed: boolean; + coordinates: string; + creationMethod: string; + dateLastActivity: string; + desc: string; + due: string; + dueReminder: string; + email: string; + idBoard: string; + idChecklists: { id: string }[]; + idLabels: { + id: string; + idBoard: string; + name: string; + color: string; + }[]; + idList: string; + idMembers: string[]; + idMembersVoted: string[]; + idShort: number; + labels: string[]; + locationName: string; + manualCoverAttachment: boolean; + name: string; + pos: number; + shortLink: string; + shortUrl: string; + subscribed: boolean; + url: string; +}; +export type TrelloCardMoved = { + action: { + display: { + translationKey: string; + entities: { + card: { + type: string; + idList: string; + id: string; + shortLink: string; + text: string; + }; + listBefore: { + type: string; + id: string; + text: string; + }; + listAfter: { + type: string; + id: string; + text: string; + }; + memberCreator: { + type: string; + id: string; + username: string; + text: string; + }; + }; + }; + }; +}; +export type TrelloNewCard = { + action: { + display: { + translationKey: string; + entities: { + card: { + type: string; + id: string; + shortLink: string; + text: string; + }; + list: { + type: string; + id: string; + text: string; + }; + memberCreator: { + type: string; + id: string; + username: string; + text: string; + }; + }; + }; + }; +}; diff --git a/packages/pieces/community/trello/src/lib/trello.mdx b/packages/pieces/community/trello/src/lib/trello.mdx new file mode 100644 index 0000000..9524422 --- /dev/null +++ b/packages/pieces/community/trello/src/lib/trello.mdx @@ -0,0 +1,59 @@ +--- +title: 'Trello' +--- + +## Creating your first Trello card using Activepiece + +1. Create a new collection +2. Select trigger as 'schedule' and we will use its default value of 5 minutes +3. Click on + to add a new piece +4. Select the 'Trello' piece +5. Select the 'Create Card' Action on the right-hand sidebar +6. Select your Trello API Key connection you created above +7. Select your Trello Token Key connection above +8. Head over to the Trello board you wish to create a new card in +9. Create a new card, lets call it 'test' +10. Click into that card +11. In the URL, add '.json', this should return a wall of JSON text +12. Look for a value called 'idList', it should look something like this: `"idList":"5aea17e911c71fee96375d94"` +13. Copy the idList value, in our case `5aea17e911c71fee96375d94` and enter it into the idList field +14. Set the card name you'd like to create +15. Set a description to enter into the card (optional) +16. Lastly, lets test our flow by clicking 'Test Flow' +17. A new card should have been created in the selected list +18. Now click publish, and thats it! + +--- + +--- + +## Fetching a card via Activepieces + +1. Create a new collection +2. Select trigger as 'schedule' and we will use its default value of 5 minutes +3. Click on + to add a new piece +4. Select the 'Trello' piece +5. Select the 'Get Card' Action on the right-hand sidebar +6. Select your Trello API Key connection you created above +7. Select your Trello Token Key connection above +8. Head over to the Trello board you wish to create a new card in +9. Create a new card, lets call it 'test' +10. Click into that card +11. In the URL, add '.json', this should return a wall of JSON text +12. Look for a value called 'id', it should look something like this: `"id":"5aea17e911c71fee96375d94"` +13. Copy the idList value, in our case `5aea17e911c71fee96375d94` and enter it into the idList field +14. Lastly, lets test our flow by clicking 'Test Flow' +15. A new card should have been created in the selected list +16. Now click publish, and thats it! + +--- + +## Triggers + +TRIGGERS + +--- + +## Actions + +ACTIONS diff --git a/packages/pieces/community/trello/src/lib/triggers/cardMoved.ts b/packages/pieces/community/trello/src/lib/triggers/cardMoved.ts new file mode 100644 index 0000000..5160f38 --- /dev/null +++ b/packages/pieces/community/trello/src/lib/triggers/cardMoved.ts @@ -0,0 +1,143 @@ +import { trelloAuth } from '../..'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { getCardDetail, getCardsInList, trelloCommon } from '../common'; +import { TrelloCardMoved } from '../common/props/card'; +import { isNil } from '@activepieces/shared'; + +export const cardMovedTrigger = createTrigger({ + auth: trelloAuth, + name: 'card_moved_to_list', + displayName: 'Card Moved to list', + description: 'Trigger when a card is moved to the list specified', + props: { + board_id: trelloCommon.board_id, + list_id: trelloCommon.list_id, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const element_id = context.propsValue.list_id; + const webhooks = await trelloCommon.list_webhooks(context.auth); + const webhook = webhooks.find( + (webhook) => webhook.idModel === element_id && webhook.callbackURL === context.webhookUrl, + ); + if (webhook) { + context.webhookUrl = webhook.callbackURL; + return; + } + const response = await trelloCommon.create_webhook( + context.auth, + element_id, + context.webhookUrl, + ); + await context.store.put('webhook_id', response.id); + }, + async onDisable(context) { + const webhook_id = (await context.store.get('webhook_id')) as string; + if (isNil(webhook_id)) { + return; + } + const webhooks = await trelloCommon.list_webhooks(context.auth); + const webhook = webhooks.find((webhook) => webhook.callbackURL === context.webhookUrl); + if (!webhook) { + return; + } + await trelloCommon.delete_webhook(context.auth, webhook_id); + }, + async run(context) { + const response = context.payload.body as TrelloCardMoved; + const response_body = response.action.display; + if (response.action.display.translationKey !== 'action_move_card_from_list_to_list') { + return []; + } + if (response_body.entities.listAfter.id !== context.propsValue.list_id) { + return []; + } + if (response_body.entities.listBefore.id === context.propsValue.list_id) { + return []; + } + const card = await getCardDetail( + context.auth.username, + context.auth.password, + response_body.entities.card.id, + ); + return [card]; + }, + async test(context) { + let cards: Array> = []; + try { + cards = await getCardsInList( + context.auth.username, + context.auth.password, + context.propsValue.list_id, + ); + + cards.sort( + (a: any, b: any) => + new Date(b.dateLastActivity).getTime() - new Date(a.dateLastActivity).getTime(), + ); + + return cards.slice(0, 5); + } catch (error) { + console.error('An error occurred:', error); + return []; + } + }, + sampleData: { + id: '6651d6d6298164adb4a598b6', + badges: { + attachmentsByType: { trello: { board: 0, card: 0 } }, + externalSource: null, + location: false, + votes: 0, + viewingMemberVoted: false, + subscribed: false, + attachments: 0, + fogbugz: '', + checkItems: 0, + checkItemsChecked: 0, + checkItemsEarliestDue: null, + comments: 0, + description: false, + due: null, + dueComplete: false, + lastUpdatedByAi: false, + start: null, + }, + checkItemStates: [], + closed: false, + dueComplete: false, + dateLastActivity: '2024-05-25T12:17:26.372Z', + desc: '', + descData: { emoji: {} }, + due: null, + dueReminder: null, + email: null, + idBoard: '6639c3a2f9a2ecd2b53adcc4', + idChecklists: [], + idList: '6639c3a342f39d7f6ff9e9b0', + idMembers: [], + idMembersVoted: [], + idShort: 11, + idAttachmentCover: null, + labels: [], + idLabels: [], + manualCoverAttachment: false, + name: 'TEST CARDS', + pos: 196608, + shortLink: '57tgmppm', + shortUrl: 'https://trello.com/c/57tghpk', + start: null, + subscribed: false, + url: 'https://trello.com/c/57tkkppm/11-again-cards', + cover: { + idAttachment: null, + color: null, + idUploadedBackground: null, + size: 'normal', + brightness: 'dark', + idPlugin: null, + }, + isTemplate: false, + cardRole: null, + }, +}); diff --git a/packages/pieces/community/trello/src/lib/triggers/newCard.ts b/packages/pieces/community/trello/src/lib/triggers/newCard.ts new file mode 100644 index 0000000..0b6751b --- /dev/null +++ b/packages/pieces/community/trello/src/lib/triggers/newCard.ts @@ -0,0 +1,144 @@ +import { trelloAuth } from '../..'; +import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { getCardDetail, getCardsInBoard, getCardsInList, trelloCommon } from '../common'; +import { TrelloNewCard } from '../common/props/card'; +import { isNil } from '@activepieces/shared'; + +export const newCardTrigger = createTrigger({ + auth: trelloAuth, + name: 'new_card', + displayName: 'New Card', + description: 'Trigger when a new card is created', + props: { + board_id: trelloCommon.board_id, + list_id_opt: trelloCommon.list_id_opt, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const element_id = context.propsValue.list_id_opt || context.propsValue.board_id; + const webhooks = await trelloCommon.list_webhooks(context.auth); + const webhook = webhooks.find( + (webhook) => webhook.idModel === element_id && webhook.callbackURL === context.webhookUrl, + ); + if (webhook) { + context.webhookUrl = webhook.callbackURL; + return; + } + const response = await trelloCommon.create_webhook( + context.auth, + element_id, + context.webhookUrl, + ); + await context.store.put('webhook_id', response.id); + }, + async onDisable(context) { + const webhook_id = (await context.store.get('webhook_id')) as string; + if (isNil(webhook_id)) { + return; + } + const webhooks = await trelloCommon.list_webhooks(context.auth); + const webhook = webhooks.find((webhook) => webhook.callbackURL === context.webhookUrl); + if (!webhook) { + return; + } + await trelloCommon.delete_webhook(context.auth, webhook_id); + }, + async run(context) { + const body = context.payload.body as TrelloNewCard; + if (body.action.display.translationKey !== 'action_create_card') { + return []; + } + if (context.propsValue.list_id_opt) { + if (body.action.display.entities.list.id !== context.propsValue.list_id_opt) { + return []; + } + } + + const card = await getCardDetail( + context.auth.username, + context.auth.password, + body.action.display.entities.card.id, + ); + + return [card]; + }, + async test(context) { + let cards: Array> = []; + try { + const getListFunction = context.propsValue.list_id_opt ? getCardsInList : getCardsInBoard; + cards = await getListFunction( + context.auth.username, + context.auth.password, + context.propsValue.list_id_opt || context.propsValue.board_id, + ); + + cards.sort( + (a: any, b: any) => + new Date(b.dateLastActivity).getTime() - new Date(a.dateLastActivity).getTime(), + ); + + return cards.slice(0, 5); + } catch (error) { + console.error('An error occurred:', error); + return []; + } + }, + sampleData: { + id: '6651d6d6298164adb4a598b6', + badges: { + attachmentsByType: { trello: { board: 0, card: 0 } }, + externalSource: null, + location: false, + votes: 0, + viewingMemberVoted: false, + subscribed: false, + attachments: 0, + fogbugz: '', + checkItems: 0, + checkItemsChecked: 0, + checkItemsEarliestDue: null, + comments: 0, + description: false, + due: null, + dueComplete: false, + lastUpdatedByAi: false, + start: null, + }, + checkItemStates: [], + closed: false, + dueComplete: false, + dateLastActivity: '2024-05-25T12:17:26.372Z', + desc: '', + descData: { emoji: {} }, + due: null, + dueReminder: null, + email: null, + idBoard: '6639c3a2f9a2ecd2b53adcc4', + idChecklists: [], + idList: '6639c3a342f39d7f6ff9e9b0', + idMembers: [], + idMembersVoted: [], + idShort: 11, + idAttachmentCover: null, + labels: [], + idLabels: [], + manualCoverAttachment: false, + name: 'TEST CARDS', + pos: 196608, + shortLink: '57tgmppm', + shortUrl: 'https://trello.com/c/57tghpk', + start: null, + subscribed: false, + url: 'https://trello.com/c/57tkkppm/11-again-cards', + cover: { + idAttachment: null, + color: null, + idUploadedBackground: null, + size: 'normal', + brightness: 'dark', + idPlugin: null, + }, + isTemplate: false, + cardRole: null, + }, +}); diff --git a/packages/pieces/community/trello/tsconfig.json b/packages/pieces/community/trello/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/trello/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/trello/tsconfig.lib.json b/packages/pieces/community/trello/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/trello/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/truelayer/.eslintrc.json b/packages/pieces/community/truelayer/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/truelayer/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/truelayer/README.md b/packages/pieces/community/truelayer/README.md new file mode 100644 index 0000000..c5de492 --- /dev/null +++ b/packages/pieces/community/truelayer/README.md @@ -0,0 +1,7 @@ +# pieces-truelayer + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-truelayer` to build the library. diff --git a/packages/pieces/community/truelayer/package.json b/packages/pieces/community/truelayer/package.json new file mode 100644 index 0000000..7404c65 --- /dev/null +++ b/packages/pieces/community/truelayer/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-truelayer", + "version": "0.0.1" +} diff --git a/packages/pieces/community/truelayer/project.json b/packages/pieces/community/truelayer/project.json new file mode 100644 index 0000000..a2e4b29 --- /dev/null +++ b/packages/pieces/community/truelayer/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-truelayer", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/truelayer/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/truelayer", + "tsConfig": "packages/pieces/community/truelayer/tsconfig.lib.json", + "packageJson": "packages/pieces/community/truelayer/package.json", + "main": "packages/pieces/community/truelayer/src/index.ts", + "assets": [ + "packages/pieces/community/truelayer/*.md", + { + "input": "packages/pieces/community/truelayer/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/truelayer/src/index.ts b/packages/pieces/community/truelayer/src/index.ts new file mode 100644 index 0000000..cfa5609 --- /dev/null +++ b/packages/pieces/community/truelayer/src/index.ts @@ -0,0 +1,111 @@ +import { OAuth2PropertyValue, PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { trueLayerCommon } from './lib/common'; + +import { createPayout } from './lib/action/payouts/create-payout'; +import { getPayout } from './lib/action/payouts/get-payout'; +import { startPayoutAuthorizationFlow } from './lib/action/payouts/start-payout-authorization-flow'; + +import { submitPaymentsProviderReturnParameters } from './lib/action/general/submit-payments-provider-return-parameters'; + +import { createMandate } from './lib/action/mandates/create-mandate'; +import { listMandate } from './lib/action/mandates/list-mandate'; +import { getMandate } from './lib/action/mandates/get-mandate'; +import { startMandateAuthorizationFlow } from './lib/action/mandates/start-mandate-authorization-flow'; +import { submitConsentMandate } from './lib/action/mandates/submit-consent-mandate'; +import { submitMandateProviderSelection } from './lib/action/mandates/submit-mandate-provider-selection'; +import { revokeMandate } from './lib/action/mandates/revoke-mandate'; +import { confirmMandateFunds } from './lib/action/mandates/confirm-mandate-funds'; +import { getConstraints } from './lib/action/mandates/get-constraints'; + +import { listOperatingAccounts } from './lib/action/merchants/list-operating-accounts'; +import { getOperatingAccount } from './lib/action/merchants/get-operating-account'; +import { merchantAccountGetTransactions } from './lib/action/merchants/merchant-account-get-transactions'; +import { merchantAccountSetupSweeping } from './lib/action/merchants/merchant-account-setup-sweeping'; +import { merchantAccountDisableSweeping } from './lib/action/merchants/merchant-account-disable-sweeping'; +import { merchantAccountGetSweeping } from './lib/action/merchants/merchant-account-get-sweeping'; +import { getMerchantAccountPaymentSources } from './lib/action/merchants/get-merchant-account-payment-sources'; + +import { createPaymentLink } from './lib/action/payment-links/create-payment-link'; +import { getPaymentLink } from './lib/action/payment-links/get-payment-link'; +import { getPaymentLinkPayments } from './lib/action/payment-links/get-payment-link-payments'; + +import { createPayment } from './lib/action/payments/create-payment'; +import { startPaymentAuthorizationFlow } from './lib/action/payments/start-payment-authorization-flow'; +import { submitProviderSelection } from './lib/action/payments/submit-provider-selection'; +import { submitSchemeSelection } from './lib/action/payments/submit-scheme-selection'; +import { submitForm } from './lib/action/payments/submit-form'; +import { submitConsent } from './lib/action/payments/submit-consent'; +import { submitUserAccountSelection } from './lib/action/payments/submit-user-account-selection'; +import { cancelPayment } from './lib/action/payments/cancel-payment'; +import { saveUserAccountPayment } from './lib/action/payments/save-user-account-payment'; +import { getPayment } from './lib/action/payments/get-payment'; +import { createPaymentRefund } from './lib/action/payments/create-payment-refund'; +import { getPaymentRefunds } from './lib/action/payments/get-payment-refunds'; +import { getPaymentRefund } from './lib/action/payments/get-payment-refund'; + +import { searchPaymentProviders } from './lib/action/payments-providers/search-payment-providers'; +import { getPaymentProvider } from './lib/action/payments-providers/get-payment-provider'; +import { PieceCategory } from '@activepieces/shared'; + +export const paymentsApiV3Payments = createPiece({ + displayName: 'TrueLayer', + description: `Connect with TrueLayer to leverage secure open banking services. This integration allows seamless interaction with TrueLayer's API to manage various financial processes.`, + auth: trueLayerCommon.auth, + minimumSupportedRelease: '0.20.0', + categories: [PieceCategory.PAYMENT_PROCESSING], + logoUrl: 'https://cdn.activepieces.com/pieces/truelayer.png', + authors: ['ahmad-swanblocks'], + actions: [ + createPayout, + getPayout, + startPayoutAuthorizationFlow, + submitPaymentsProviderReturnParameters, + createMandate, + listMandate, + getMandate, + startMandateAuthorizationFlow, + submitConsentMandate, + submitMandateProviderSelection, + revokeMandate, + confirmMandateFunds, + getConstraints, + listOperatingAccounts, + getOperatingAccount, + merchantAccountGetTransactions, + merchantAccountSetupSweeping, + merchantAccountDisableSweeping, + merchantAccountGetSweeping, + getMerchantAccountPaymentSources, + createPaymentLink, + getPaymentLink, + getPaymentLinkPayments, + createPayment, + startPaymentAuthorizationFlow, + submitProviderSelection, + submitSchemeSelection, + submitForm, + submitConsent, + submitUserAccountSelection, + cancelPayment, + saveUserAccountPayment, + getPayment, + createPaymentRefund, + getPaymentRefunds, + getPaymentRefund, + searchPaymentProviders, + getPaymentProvider, + createCustomApiCallAction({ + baseUrl: () => { + return trueLayerCommon.baseUrl; + }, + auth: trueLayerCommon.auth, + authMapping: async (auth) => { + return { + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }; + }, + }), + ], + triggers: [], +}); \ No newline at end of file diff --git a/packages/pieces/community/truelayer/src/lib/action/general/submit-payments-provider-return-parameters.ts b/packages/pieces/community/truelayer/src/lib/action/general/submit-payments-provider-return-parameters.ts new file mode 100644 index 0000000..5d883a7 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/general/submit-payments-provider-return-parameters.ts @@ -0,0 +1,29 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const submitPaymentsProviderReturnParameters = createAction({ + auth: trueLayerCommon.auth, + name: 'submit-payments-provider-return-parameters', + displayName: 'Submit payments return parameters', + description: 'Submit direct return query and fragment parameters returned from the provider.', + props: { + IdempotencyKeyHeader: Property.ShortText({ + displayName: 'Idempotency Key Header', + description: 'Used to ensure idempotent requests', + required: false, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/payments-provider-return`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + 'Idempotency-Key': ctx.propsValue.IdempotencyKeyHeader, + }, + body: ctx.propsValue, + }) + return response.body + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/mandates/confirm-mandate-funds.ts b/packages/pieces/community/truelayer/src/lib/action/mandates/confirm-mandate-funds.ts new file mode 100644 index 0000000..cce369a --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/mandates/confirm-mandate-funds.ts @@ -0,0 +1,42 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const confirmMandateFunds = createAction({ + auth: trueLayerCommon.auth, + name: 'confirm-mandate-funds', + displayName: 'Confirm Mandate Funds', + description: 'Confirm that the PSU has the given funds. This API can be called using the mandate_token associated with the mandate or using a backend bearer token.', + props: { + id: Property.ShortText({ + displayName: 'Mandate ID', + description: 'ID of the Mandate to be confirmed.', + required: true, + }), + amount_in_minor: Property.ShortText({ + displayName: 'Amount in Minor Units', + description: 'A "cent" value representing the amount. For example, 100 == 1 GBP.', + required: true, + }), + currency: Property.ShortText({ + displayName: 'Currency', + description: 'Currency code (e.g., GBP, EUR).', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${trueLayerCommon.baseUrl}/v3/mandates/${ctx.propsValue.id}/funds`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + body: { + amount_in_minor: ctx.propsValue.amount_in_minor, + currency: ctx.propsValue.currency, + }, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/mandates/create-mandate.ts b/packages/pieces/community/truelayer/src/lib/action/mandates/create-mandate.ts new file mode 100644 index 0000000..e65c6a2 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/mandates/create-mandate.ts @@ -0,0 +1,36 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const createMandate = createAction({ + auth: trueLayerCommon.auth, + name: 'create-mandate', + displayName: 'Create Mandate', + description: 'Create a new mandate. This API must be called using a backend bearer token.', + props: { + IdempotencyKeyHeader: Property.ShortText({ + displayName: 'Idempotency Key Header', + description: 'Used to ensure idempotent requests', + required: false, + }), + SignatureHeader: Property.ShortText({ + displayName: 'Signature Header', + description: 'Used for request signature verification', + required: false, + }), + }, + run: async (ctx) => { + const response =await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/mandates`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + 'Idempotency-Key': ctx.propsValue.IdempotencyKeyHeader, + 'Signature': ctx.propsValue.SignatureHeader, + }, + body: ctx.propsValue, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/mandates/get-constraints.ts b/packages/pieces/community/truelayer/src/lib/action/mandates/get-constraints.ts new file mode 100644 index 0000000..8f4cff6 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/mandates/get-constraints.ts @@ -0,0 +1,28 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const getConstraints = createAction({ + auth: trueLayerCommon.auth, + name: 'get-constraints', + displayName: 'Get Mandate Constraints', + description: 'Retrieve the constraints defined on the mandate, as well as the current utilization of those constraints within the periods.', + props: { + id: Property.ShortText({ + displayName: 'Mandate ID', + description: 'The ID of the mandate to retrieve the constraints for.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${trueLayerCommon.baseUrl}/v3/mandates/${ctx.propsValue.id}/constraints`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + }) + return response.body + + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/mandates/get-mandate.ts b/packages/pieces/community/truelayer/src/lib/action/mandates/get-mandate.ts new file mode 100644 index 0000000..3b1bc3e --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/mandates/get-mandate.ts @@ -0,0 +1,28 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const getMandate = createAction({ + auth: trueLayerCommon.auth, + name: 'get-mandate', + displayName: 'Get Mandate', + description: 'Returns a mandate with the stated ID. This endpoint can be called either by the regular `backend token` or the `mandate token` for that mandate.', + props: { + id: Property.ShortText({ + displayName: 'Mandate ID', + description: 'The ID of the mandate to retrieve.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${trueLayerCommon.baseUrl}/v3/mandates/${ctx.propsValue.id}`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/mandates/list-mandate.ts b/packages/pieces/community/truelayer/src/lib/action/mandates/list-mandate.ts new file mode 100644 index 0000000..3f8dcbb --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/mandates/list-mandate.ts @@ -0,0 +1,43 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const listMandate = createAction({ + auth: trueLayerCommon.auth, + name: 'list-mandate', + displayName: 'List Mandates', + description: 'List all the mandates associated with the client. This API must be called using a backend bearer token.', + props: { + user_id: Property.ShortText({ + displayName: 'User ID', + description: 'Optional ID of the user whose mandates you want to list.', + required: false, + }), + cursor: Property.ShortText({ + displayName: 'Cursor', + description: 'Optional cursor for pagination.', + required: false, + }), + limit: Property.ShortText({ + displayName: 'Limit', + description: 'Optional limit on the number of mandates to return.', + required: false, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${trueLayerCommon.baseUrl}/v3/mandates`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + queryParams: { + user_id: ctx.propsValue.user_id || '', + cursor: ctx.propsValue.cursor || '', + limit: ctx.propsValue.limit || '', + }, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/mandates/revoke-mandate.ts b/packages/pieces/community/truelayer/src/lib/action/mandates/revoke-mandate.ts new file mode 100644 index 0000000..aa13e83 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/mandates/revoke-mandate.ts @@ -0,0 +1,28 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const revokeMandate = createAction({ + auth: trueLayerCommon.auth, + name: 'revoke-mandate', + displayName: 'Revoke Mandate', + description: 'Revoke a mandate. This API must be called using a backend bearer token.', + props: { + id: Property.ShortText({ + displayName: 'Mandate ID', + description: 'The ID of the mandate to revoke.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/mandates/${ctx.propsValue.id}/revoke`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/mandates/start-mandate-authorization-flow.ts b/packages/pieces/community/truelayer/src/lib/action/mandates/start-mandate-authorization-flow.ts new file mode 100644 index 0000000..40b4f29 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/mandates/start-mandate-authorization-flow.ts @@ -0,0 +1,29 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const startMandateAuthorizationFlow = createAction({ + auth: trueLayerCommon.auth, + name: 'start-mandate-authorization-flow', + displayName: 'Start Authorization Flow', + description: 'Start the authorization flow for a mandate. This API can be called using either the mandate_token associated with the mandate or a backend bearer token.', + props: { + id: Property.ShortText({ + displayName: 'Mandate ID', + description: 'The ID of the mandate to start the authorization flow for.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/mandates/${ctx.propsValue.id}/authorization-flow`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + body: ctx.propsValue, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/mandates/submit-consent-mandate.ts b/packages/pieces/community/truelayer/src/lib/action/mandates/submit-consent-mandate.ts new file mode 100644 index 0000000..b9a014c --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/mandates/submit-consent-mandate.ts @@ -0,0 +1,29 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const submitConsentMandate = createAction({ + auth: trueLayerCommon.auth, + name: 'submit-consent-mandate', + displayName: 'Submit consent', + description: 'Submit the consent given by the user. This API can be called using either the mandate_token associated with the mandate or a backend bearer token.', + props: { + id: Property.ShortText({ + displayName: 'Mandate ID', + description: 'The ID of the mandate for which consent is being submitted.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/mandates/${ctx.propsValue.id}/authorization-flow/actions/consent`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + body: ctx.propsValue, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/mandates/submit-mandate-provider-selection.ts b/packages/pieces/community/truelayer/src/lib/action/mandates/submit-mandate-provider-selection.ts new file mode 100644 index 0000000..f69bfa5 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/mandates/submit-mandate-provider-selection.ts @@ -0,0 +1,29 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const submitMandateProviderSelection = createAction({ + auth: trueLayerCommon.auth, + name: 'submit-mandate-provider-selection', + displayName: 'Submit provider selection', + description: 'Submit the provider details selected by the PSU. This API can be called using either the mandate_token associated with the mandate or a backend bearer token.', + props: { + id: Property.ShortText({ + displayName: 'Mandate ID', + description: 'The ID of the mandate for which provider selection is being submitted.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/mandates/${ctx.propsValue.id}/authorization-flow/actions/provider-selection`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + body: ctx.propsValue, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/merchants/get-merchant-account-payment-sources.ts b/packages/pieces/community/truelayer/src/lib/action/merchants/get-merchant-account-payment-sources.ts new file mode 100644 index 0000000..7d1de1b --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/merchants/get-merchant-account-payment-sources.ts @@ -0,0 +1,36 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const getMerchantAccountPaymentSources = createAction({ + auth: trueLayerCommon.auth, + name: 'get-merchant-account-payment-sources', + displayName: 'Get Payment Sources', + description: 'Get the payment sources from which the merchant account has received payments.', + props: { + id: Property.ShortText({ + displayName: 'Merchant Account ID', + description: 'The ID of the merchant account into which payments were made.', + required: true, + }), + user_id: Property.ShortText({ + displayName: 'User ID', + description: 'The ID of the user whose payment sources are being retrieved.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${trueLayerCommon.baseUrl}/v3/merchant-accounts/${ctx.propsValue.id}/payment-sources`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + queryParams: { + user_id: ctx.propsValue.user_id, + }, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/merchants/get-operating-account.ts b/packages/pieces/community/truelayer/src/lib/action/merchants/get-operating-account.ts new file mode 100644 index 0000000..78267d3 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/merchants/get-operating-account.ts @@ -0,0 +1,28 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const getOperatingAccount = createAction({ + auth: trueLayerCommon.auth, + name: 'get-operating-account', + displayName: 'Get Merchant Account', + description: 'Get the details of a single merchant account.', + props: { + id: Property.ShortText({ + displayName: 'Merchant Account ID', + description: 'The ID of the merchant account to be retrieved.', + required: true, + }), + }, + run: async (ctx) => { + const response =await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${trueLayerCommon.baseUrl}/v3/merchant-accounts/${ctx.propsValue.id}`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/merchants/list-operating-accounts.ts b/packages/pieces/community/truelayer/src/lib/action/merchants/list-operating-accounts.ts new file mode 100644 index 0000000..2acb7fb --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/merchants/list-operating-accounts.ts @@ -0,0 +1,22 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const listOperatingAccounts = createAction({ + auth: trueLayerCommon.auth, + name: 'list-operating-accounts', + displayName: 'List Merchant Accounts', + description: 'List all your TrueLayer merchant accounts. There might be more than one account per currency.', + props: {}, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${trueLayerCommon.baseUrl}/v3/merchant-accounts`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + }) + + return response.body + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/merchants/merchant-account-disable-sweeping.ts b/packages/pieces/community/truelayer/src/lib/action/merchants/merchant-account-disable-sweeping.ts new file mode 100644 index 0000000..1f4d094 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/merchants/merchant-account-disable-sweeping.ts @@ -0,0 +1,28 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const merchantAccountDisableSweeping = createAction({ + auth: trueLayerCommon.auth, + name: 'merchant-account-disable-sweeping', + displayName: 'Disable Sweeping', + description: 'Disable automatic sweeping for a merchant account.', + props: { + id: Property.ShortText({ + displayName: 'Merchant Account ID', + description: 'The ID of the merchant account to disable sweeping for.', + required: true, + }), + }, + run: async (ctx) => { + const response = await await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: `${trueLayerCommon.baseUrl}/v3/merchant-accounts/${ctx.propsValue.id}/sweeping`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + }) + + return response.body + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/merchants/merchant-account-get-sweeping.ts b/packages/pieces/community/truelayer/src/lib/action/merchants/merchant-account-get-sweeping.ts new file mode 100644 index 0000000..87c9297 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/merchants/merchant-account-get-sweeping.ts @@ -0,0 +1,28 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const merchantAccountGetSweeping = createAction({ + auth: trueLayerCommon.auth, + name: 'merchant-account-get-sweeping', + displayName: 'Get Sweeping Settings', + description: 'Get the automatic sweeping settings for a merchant account.', + props: { + id: Property.ShortText({ + displayName: 'Merchant Account ID', + description: 'The ID of the merchant account to fetch the sweeping settings for.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${trueLayerCommon.baseUrl}/v3/merchant-accounts/${ctx.propsValue.id}/sweeping`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + }) + + return response.body + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/merchants/merchant-account-get-transactions.ts b/packages/pieces/community/truelayer/src/lib/action/merchants/merchant-account-get-transactions.ts new file mode 100644 index 0000000..421bda2 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/merchants/merchant-account-get-transactions.ts @@ -0,0 +1,55 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const merchantAccountGetTransactions = createAction({ + auth: trueLayerCommon.auth, + name: 'merchant-account-get-transactions', + displayName: 'Get Transactions', + description: 'Get the transactions of a single merchant account. If pagination is missing, add a header `tl-enable-pagination: true` to enable pagination.', + props: { + id: Property.ShortText({ + displayName: 'Merchant Account ID', + description: 'The ID of the merchant account to return the transactions for.', + required: true, + }), + from: Property.ShortText({ + displayName: 'Start Timestamp', + description: 'Timestamp for the start of the range to query (inclusive). Uses the ISO-8601 format of YYYY-MM-DDTHH:MM:SS±HHMM.', + required: true, + }), + to: Property.ShortText({ + displayName: 'End Timestamp', + description: 'Timestamp for the end of the range to query (inclusive). Uses the ISO-8601 format of YYYY-MM-DDTHH:MM:SS±HHMM.', + required: true, + }), + cursor: Property.ShortText({ + displayName: 'Cursor', + description: 'Cursor used for pagination purposes, returned as `next_cursor` in the response payload of the initial request. Not required for the first page.', + required: false, + }), + type: Property.ShortText({ + displayName: 'Transaction Type', + description: 'Filters transactions by payments or payouts. If omitted, both are returned.', + required: false, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${trueLayerCommon.baseUrl}/v3/merchant-accounts/${ctx.propsValue.id}/transactions`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + 'tl-enable-pagination': 'true', + }, + queryParams: { + from: ctx.propsValue.from, + to: ctx.propsValue.to, + cursor: ctx.propsValue.cursor || '', + type: ctx.propsValue.type || '', + }, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/merchants/merchant-account-setup-sweeping.ts b/packages/pieces/community/truelayer/src/lib/action/merchants/merchant-account-setup-sweeping.ts new file mode 100644 index 0000000..7afa493 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/merchants/merchant-account-setup-sweeping.ts @@ -0,0 +1,48 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const merchantAccountSetupSweeping = createAction({ + auth: trueLayerCommon.auth, + name: 'merchant-account-setup-sweeping', + displayName: 'Set Up or Update Sweeping', + description: 'Set the automatic sweeping settings for a merchant account. At regular intervals, any available balance in excess of the configured `max_amount_in_minor` is withdrawn to a pre-configured IBAN.', + props: { + id: Property.ShortText({ + displayName: 'Merchant Account ID', + description: 'The ID of the merchant account to set or update sweeping settings for.', + required: true, + }), + max_amount_in_minor: Property.ShortText({ + displayName: 'Max Amount in Minor Units', + description: 'The amount above which sweeping will occur, expressed in minor units (e.g., 100 means 1 GBP).', + required: true, + }), + frequency: Property.ShortText({ + displayName: 'Sweeping Frequency', + description: 'The frequency of the sweeping operation (e.g., daily, weekly).', + required: true, + }), + iban: Property.ShortText({ + displayName: 'IBAN', + description: 'The IBAN to which sweeping funds will be transferred.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/merchant-accounts/${ctx.propsValue.id}/sweeping`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + body: { + max_amount_in_minor: ctx.propsValue.max_amount_in_minor, + frequency: ctx.propsValue.frequency, + iban: ctx.propsValue.iban, + }, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payment-links/create-payment-link.ts b/packages/pieces/community/truelayer/src/lib/action/payment-links/create-payment-link.ts new file mode 100644 index 0000000..882d74e --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payment-links/create-payment-link.ts @@ -0,0 +1,36 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const createPaymentLink = createAction({ + auth: trueLayerCommon.auth, + name: 'create-payment-link', + displayName: 'Create Payment Link', + description: 'Create a new payment link. This API must be called using a backend bearer token.', + props: { + IdempotencyKeyHeader: Property.ShortText({ + displayName: 'Idempotency Key Header', + description: 'Used to ensure idempotent requests.', + required: false, + }), + SignatureHeader: Property.ShortText({ + displayName: 'Signature Header', + description: 'Used for request signature verification.', + required: false, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/payment-links`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + 'Idempotency-Key': ctx.propsValue.IdempotencyKeyHeader, + 'Signature': ctx.propsValue.SignatureHeader, + }, + body: ctx.propsValue, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payment-links/get-payment-link-payments.ts b/packages/pieces/community/truelayer/src/lib/action/payment-links/get-payment-link-payments.ts new file mode 100644 index 0000000..0b045c4 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payment-links/get-payment-link-payments.ts @@ -0,0 +1,42 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const getPaymentLinkPayments = createAction({ + auth: trueLayerCommon.auth, + name: 'get-payment-link-payments', + displayName: 'Get Payments', + description: 'List all the payments associated with the payment link. This API must be called using a backend bearer token.', + props: { + id: Property.ShortText({ + displayName: 'Payment Link ID', + description: 'The ID of the payment link for which payments are being retrieved.', + required: true, + }), + cursor: Property.ShortText({ + displayName: 'Cursor', + description: 'Cursor used for pagination purposes, returned as `next_cursor` in the response payload of the initial request. Not required for the first page of items.', + required: false, + }), + limit: Property.ShortText({ + displayName: 'Limit', + description: 'Optional limit on the number of payments to return.', + required: false, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${trueLayerCommon.baseUrl}/v3/payment-links/${ctx.propsValue.id}/payments`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + queryParams: { + cursor: ctx.propsValue.cursor || '', + limit: ctx.propsValue.limit || '', + }, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payment-links/get-payment-link.ts b/packages/pieces/community/truelayer/src/lib/action/payment-links/get-payment-link.ts new file mode 100644 index 0000000..aa6c3fe --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payment-links/get-payment-link.ts @@ -0,0 +1,28 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const getPaymentLink = createAction({ + auth: trueLayerCommon.auth, + name: 'get-payment-link', + displayName: 'Get Payment Link', + description: 'Retrieves payment link details. This API must be called using a backend bearer token.', + props: { + id: Property.ShortText({ + displayName: 'Payment Link ID', + description: 'The ID of the payment link to retrieve.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${trueLayerCommon.baseUrl}/v3/payment-links/${ctx.propsValue.id}`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payments-providers/get-payment-provider.ts b/packages/pieces/community/truelayer/src/lib/action/payments-providers/get-payment-provider.ts new file mode 100644 index 0000000..ecba55e --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payments-providers/get-payment-provider.ts @@ -0,0 +1,44 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const getPaymentProvider = createAction({ + auth: trueLayerCommon.auth, + name: 'get-payment-provider', + displayName: 'Get Payment Provider', + description: 'Returns payment provider details. This API can be called without the need for authentication.', + props: { + id: Property.ShortText({ + displayName: 'Payment Provider ID', + description: 'The ID of the payment provider to retrieve details for.', + required: true, + }), + client_id: Property.ShortText({ + displayName: 'Client ID', + description: 'Optional client ID to retrieve specific provider details.', + required: false, + }), + icon_type: Property.ShortText({ + displayName: 'Icon Type', + description: `Optional configuration for the type of icon: + - \`default\`: Default icon with no background (SVG). + - \`extended\`: Extended to a square with an appropriate background color (SVG). + - \`extended_small\`: Extended icon with 192x192 px size (JPEG). + - \`extended_medium\`: Extended icon with 432x432 px size (JPEG). + - \`extended_large\`: Extended icon jpeg with 864x864 px size (JPEG).`, + required: false, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${trueLayerCommon.baseUrl}/v3/payments-providers/${ctx.propsValue.id}`, + queryParams: { + client_id: ctx.propsValue.client_id || '', + icon_type: ctx.propsValue.icon_type || '', + }, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payments-providers/search-payment-providers.ts b/packages/pieces/community/truelayer/src/lib/action/payments-providers/search-payment-providers.ts new file mode 100644 index 0000000..6c8fa35 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payments-providers/search-payment-providers.ts @@ -0,0 +1,20 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const searchPaymentProviders = createAction({ + auth: trueLayerCommon.auth, + name: 'search-payment-providers', + displayName: 'Search Payment Providers', + description: 'Returns a list of payment providers.', + props: {}, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/payments-providers/search`, + body: ctx.propsValue, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payments/cancel-payment.ts b/packages/pieces/community/truelayer/src/lib/action/payments/cancel-payment.ts new file mode 100644 index 0000000..c607485 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payments/cancel-payment.ts @@ -0,0 +1,28 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const cancelPayment = createAction({ + auth: trueLayerCommon.auth, + name: 'cancel-payment', + displayName: 'Cancel Payment', + description: 'Cancel a payment. This API can be called using the `resource_token` associated with the payment or a backend bearer token.', + props: { + id: Property.ShortText({ + displayName: 'Payment ID', + description: 'The ID of the payment to cancel.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/payments/${ctx.propsValue.id}/actions/cancel`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payments/create-payment-refund.ts b/packages/pieces/community/truelayer/src/lib/action/payments/create-payment-refund.ts new file mode 100644 index 0000000..126caca --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payments/create-payment-refund.ts @@ -0,0 +1,36 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const createPaymentRefund = createAction({ + auth: trueLayerCommon.auth, + name: 'create-payment-refund', + displayName: 'Create Payment Refund', + description: 'Refund a merchant account payment, either fully or partially.', + props: { + id: Property.ShortText({ + displayName: 'Payment ID', + description: 'The payment ID for the payment to be fully or partially refunded.', + required: true, + }), + amount_in_minor: Property.ShortText({ + displayName: 'Amount in Minor Units', + description: 'The amount to refund, expressed in minor units (e.g., 100 means 1 GBP).', + required: false, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/payments/${ctx.propsValue.id}/refunds`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + body: { + amount_in_minor: ctx.propsValue.amount_in_minor, + }, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payments/create-payment.ts b/packages/pieces/community/truelayer/src/lib/action/payments/create-payment.ts new file mode 100644 index 0000000..9dcc8cc --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payments/create-payment.ts @@ -0,0 +1,47 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const createPayment = createAction({ + auth: trueLayerCommon.auth, + name: 'create-payment', + displayName: 'Create Payment', + description: 'Create a new payment. This API must be called using a backend bearer token.', + props: { + IdempotencyKeyHeader: Property.ShortText({ + displayName: 'Idempotency Key', + description: 'A key that uniquely identifies the request. If the same key is sent in another request, the operation will have the same result as the first request.', + required: true, + }), + SignatureHeader: Property.ShortText({ + displayName: 'Signature Header', + description: 'Header containing the signature of the request payload for authentication purposes.', + required: true, + }), + PsuIpAddressHeader: Property.ShortText({ + displayName: 'PSU IP Address', + description: 'Used to collect and record the end-user\'s IP address. Only considered if the authorization_flow object in the request body is specified.', + required: false, + }), + DeviceUserAgentHeader: Property.ShortText({ + displayName: 'Device User Agent', + description: 'Used to improve the end-user\'s authentication experience based on the device type. If omitted, the `User-Agent` header will be used instead. Only considered if the authorization_flow object in the request body is specified.', + required: false, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/payments`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + 'Idempotency-Key': ctx.propsValue.IdempotencyKeyHeader, + 'Signature': ctx.propsValue.SignatureHeader, + 'PSU-IP-Address': ctx.propsValue.PsuIpAddressHeader, + 'Device-User-Agent': ctx.propsValue.DeviceUserAgentHeader, + }, + body: ctx.propsValue, + }); + + return response.body; +}}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payments/get-payment-refund.ts b/packages/pieces/community/truelayer/src/lib/action/payments/get-payment-refund.ts new file mode 100644 index 0000000..6e0b2c6 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payments/get-payment-refund.ts @@ -0,0 +1,33 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const getPaymentRefund = createAction({ + auth: trueLayerCommon.auth, + name: 'get-payment-refund', + displayName: 'Get Payment Refund', + description: 'Returns refund details for a specific payment.', + props: { + payment_id: Property.ShortText({ + displayName: 'Payment ID', + description: 'The ID of the payment for which the refund was made.', + required: true, + }), + refund_id: Property.ShortText({ + displayName: 'Refund ID', + description: 'The ID of the refund to retrieve details for.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${trueLayerCommon.baseUrl}/v3/payments/${ctx.propsValue.payment_id}/refunds/${ctx.propsValue.refund_id}`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payments/get-payment-refunds.ts b/packages/pieces/community/truelayer/src/lib/action/payments/get-payment-refunds.ts new file mode 100644 index 0000000..4aa136f --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payments/get-payment-refunds.ts @@ -0,0 +1,28 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const getPaymentRefunds = createAction({ + auth: trueLayerCommon.auth, + name: 'get-payment-refunds', + displayName: 'Get Payment Refunds', + description: 'Returns all refunds of a payment.', + props: { + id: Property.ShortText({ + displayName: 'Payment ID', + description: 'The ID of the merchant account payment to retrieve all refunds for.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${trueLayerCommon.baseUrl}/v3/payments/${ctx.propsValue.id}/refunds`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + }) + + return response.body + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payments/get-payment.ts b/packages/pieces/community/truelayer/src/lib/action/payments/get-payment.ts new file mode 100644 index 0000000..18e6e9d --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payments/get-payment.ts @@ -0,0 +1,28 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const getPayment = createAction({ + auth: trueLayerCommon.auth, + name: 'get-payment', + displayName: 'Get Payment', + description: 'Returns payment details. This API can be called using either the `resource_token` associated with the payment or a backend bearer token.', + props: { + id: Property.ShortText({ + displayName: 'Payment ID', + description: 'The ID of the payment to retrieve.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${trueLayerCommon.baseUrl}/v3/payments/${ctx.propsValue.id}`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payments/save-user-account-payment.ts b/packages/pieces/community/truelayer/src/lib/action/payments/save-user-account-payment.ts new file mode 100644 index 0000000..4c80f55 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payments/save-user-account-payment.ts @@ -0,0 +1,29 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const saveUserAccountPayment = createAction({ + auth: trueLayerCommon.auth, + name: 'save-user-account-payment', + displayName: 'Save Payment Account', + description: 'Save the account details associated with a payment for subsequent re-use. This API can be called using the `resource_token` associated with the payment or a backend bearer token.', + props: { + id: Property.ShortText({ + displayName: 'Payment ID', + description: 'The ID of the payment to save the account details for.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/payments/${ctx.propsValue.id}/actions/save-user-account`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + body: ctx.propsValue, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payments/start-payment-authorization-flow.ts b/packages/pieces/community/truelayer/src/lib/action/payments/start-payment-authorization-flow.ts new file mode 100644 index 0000000..d0d158c --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payments/start-payment-authorization-flow.ts @@ -0,0 +1,29 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const startPaymentAuthorizationFlow = createAction({ + auth: trueLayerCommon.auth, + name: 'start-payment-authorization-flow', + displayName: 'Start Payment Authorization Flow', + description: 'Start the authorization flow for a payment. This API can be called using the `resource_token` associated with the payment or a backend bearer token.', + props: { + id: Property.ShortText({ + displayName: 'Payment ID', + description: 'The ID of the payment for which to start the authorization flow.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/payments/${ctx.propsValue.id}/authorization-flow`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + body: ctx.propsValue, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payments/submit-consent.ts b/packages/pieces/community/truelayer/src/lib/action/payments/submit-consent.ts new file mode 100644 index 0000000..868c364 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payments/submit-consent.ts @@ -0,0 +1,29 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const submitConsent = createAction({ + auth: trueLayerCommon.auth, + name: 'submit-consent', + displayName: 'Submit Consent', + description: 'Submit the consent given by the user. This API can be called using the `resource_token` associated with the payment or a backend bearer token.', + props: { + id: Property.ShortText({ + displayName: 'Payment ID', + description: 'The ID of the payment for which consent is being submitted.', + required: true, + }), + }, + run: async (ctx) => { + const response = await await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/payments/${ctx.propsValue.id}/authorization-flow/actions/consent`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + body: ctx.propsValue, + }) + + return response.body + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payments/submit-form.ts b/packages/pieces/community/truelayer/src/lib/action/payments/submit-form.ts new file mode 100644 index 0000000..3b7e862 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payments/submit-form.ts @@ -0,0 +1,29 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const submitForm = createAction({ + auth: trueLayerCommon.auth, + name: 'submit-form', + displayName: 'Submit Form', + description: 'Submit form details filled by the PSU. This API can be called using the `resource_token` associated with the payment or a backend bearer token.', + props: { + id: Property.ShortText({ + displayName: 'Payment ID', + description: 'The ID of the payment for which the form is being submitted.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/payments/${ctx.propsValue.id}/authorization-flow/actions/form`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + body: ctx.propsValue, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payments/submit-provider-selection.ts b/packages/pieces/community/truelayer/src/lib/action/payments/submit-provider-selection.ts new file mode 100644 index 0000000..5643547 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payments/submit-provider-selection.ts @@ -0,0 +1,29 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const submitProviderSelection = createAction({ + auth: trueLayerCommon.auth, + name: 'submit-provider-selection', + displayName: 'Submit Provider Selection', + description: 'Submit the provider details selected by the PSU. This API can be called using the `resource_token` associated with the payment or a backend bearer token.', + props: { + id: Property.ShortText({ + displayName: 'Payment ID', + description: 'The ID of the payment for which provider selection is being submitted.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/payments/${ctx.propsValue.id}/authorization-flow/actions/provider-selection`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + body: ctx.propsValue, + }) + + return response.body + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payments/submit-scheme-selection.ts b/packages/pieces/community/truelayer/src/lib/action/payments/submit-scheme-selection.ts new file mode 100644 index 0000000..2778a4a --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payments/submit-scheme-selection.ts @@ -0,0 +1,29 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const submitSchemeSelection = createAction({ + auth: trueLayerCommon.auth, + name: 'submit-scheme-selection', + displayName: 'Submit Scheme Selection', + description: 'Submit the scheme details selected by the PSU. This API can be called using the `resource_token` associated with the payment or a backend bearer token.', + props: { + id: Property.ShortText({ + displayName: 'Payment ID', + description: 'The ID of the payment for which the scheme details are being submitted.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/payments/${ctx.propsValue.id}/authorization-flow/actions/scheme-selection`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + body: ctx.propsValue, + }) + + return response.body + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payments/submit-user-account-selection.ts b/packages/pieces/community/truelayer/src/lib/action/payments/submit-user-account-selection.ts new file mode 100644 index 0000000..029698d --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payments/submit-user-account-selection.ts @@ -0,0 +1,28 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const submitUserAccountSelection = createAction({ + auth: trueLayerCommon.auth, + name: 'submit-user-account-selection', + displayName: 'Submit User Account Selection', + description: 'Submit the user account selection option given by the user. This API can be called using the `resource_token` associated with the payment or a backend bearer token.', + props: { + id: Property.ShortText({ + displayName: 'Payment ID', + description: 'The ID of the payment for which the user account selection is being submitted.', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/payments/${ctx.propsValue.id}/authorization-flow/actions/user-account-selection`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + }, + body: ctx.propsValue, + }) + + return response.body;} +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payouts/create-payout.ts b/packages/pieces/community/truelayer/src/lib/action/payouts/create-payout.ts new file mode 100644 index 0000000..cdbfd9c --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payouts/create-payout.ts @@ -0,0 +1,35 @@ + +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const createPayout = createAction({ + auth: trueLayerCommon.auth, + name: 'create-payout', + displayName: 'Create payout', + description: 'Pay out from one of your merchant accounts. ', + props: { + IdempotencyKeyHeader: Property.ShortText({ + displayName: 'Used to ensure idempotent requests', + required: true, + }), + SignatureHeader: Property.ShortText({ + displayName: 'Used for request signature verification', + required: true, + }), + }, + run: async (ctx) => { + const response = await await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/payouts`, + headers: { + Authorization: `Bearer ${(ctx.auth as OAuth2PropertyValue).access_token}`, + 'Idempotency-Key': ctx.propsValue.IdempotencyKeyHeader, + 'Signature': ctx.propsValue.SignatureHeader, + }, + body: ctx.propsValue, + }) + + return response.body + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payouts/get-payout.ts b/packages/pieces/community/truelayer/src/lib/action/payouts/get-payout.ts new file mode 100644 index 0000000..e9fcdf3 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payouts/get-payout.ts @@ -0,0 +1,29 @@ + +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const getPayout = createAction({ + auth: trueLayerCommon.auth, + name: 'get-payout', + displayName: 'Get payout', + description: 'Returns payout details. ', + props: { + id: Property.ShortText({ + displayName: 'ID of the payout', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${trueLayerCommon.baseUrl}/v3/payouts/${ctx.propsValue.id}`, + headers: { + Authorization: `${ctx.auth}`, + } + }) + + return response.body; + + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/action/payouts/start-payout-authorization-flow.ts b/packages/pieces/community/truelayer/src/lib/action/payouts/start-payout-authorization-flow.ts new file mode 100644 index 0000000..219fd17 --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/action/payouts/start-payout-authorization-flow.ts @@ -0,0 +1,29 @@ + +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { trueLayerCommon } from '../../common'; + +export const startPayoutAuthorizationFlow = createAction({ + auth: trueLayerCommon.auth, + name: 'start-payout-authorization-flow', + displayName: 'Start authorization flow', + description: 'Start the authorization flow for a payout. This API can be called using the `resource_token` associated with the payout you are trying to fetch.', + props: { + id: Property.ShortText({ + displayName: 'ID of the payout', + required: true, + }), + }, + run: async (ctx) => { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${trueLayerCommon.baseUrl}/v3/payouts/${ctx.propsValue.id}/authorization-flow`, + headers: { + Authorization: `${ctx.auth}`, + }, + body: ctx.propsValue, + }) + + return response.body; + }, +}); diff --git a/packages/pieces/community/truelayer/src/lib/common/index.ts b/packages/pieces/community/truelayer/src/lib/common/index.ts new file mode 100644 index 0000000..1796f2d --- /dev/null +++ b/packages/pieces/community/truelayer/src/lib/common/index.ts @@ -0,0 +1,23 @@ +import { PieceAuth } from '@activepieces/pieces-framework'; + +export const trueLayerCommon = { + baseUrl: 'https://api.truelayer.com', + auth: PieceAuth.OAuth2({ + description: 'Authentication for TrueLayer API', + authUrl:'https://auth.truelayer.com', + tokenUrl: 'https://auth.truelayer.com/connect/token', + required: true, + scope: [ + 'info', + 'accounts', + 'balance', + 'cards', + 'transactions', + 'direct_debits', + 'standing_orders', + 'offline_access', + 'signupplus', + 'verification' + ], + }) +}; \ No newline at end of file diff --git a/packages/pieces/community/truelayer/tsconfig.json b/packages/pieces/community/truelayer/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/truelayer/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/truelayer/tsconfig.lib.json b/packages/pieces/community/truelayer/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/truelayer/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/twilio/.babelrc b/packages/pieces/community/twilio/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/twilio/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/twilio/.eslintrc.json b/packages/pieces/community/twilio/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/twilio/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/twilio/README.md b/packages/pieces/community/twilio/README.md new file mode 100644 index 0000000..c569c9f --- /dev/null +++ b/packages/pieces/community/twilio/README.md @@ -0,0 +1,7 @@ +# pieces-twilio + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-twilio` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/twilio/package.json b/packages/pieces/community/twilio/package.json new file mode 100644 index 0000000..dcd90b9 --- /dev/null +++ b/packages/pieces/community/twilio/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-twilio", + "version": "0.3.4" +} \ No newline at end of file diff --git a/packages/pieces/community/twilio/project.json b/packages/pieces/community/twilio/project.json new file mode 100644 index 0000000..a2e8a0a --- /dev/null +++ b/packages/pieces/community/twilio/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-twilio", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/twilio/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/twilio", + "tsConfig": "packages/pieces/community/twilio/tsconfig.lib.json", + "packageJson": "packages/pieces/community/twilio/package.json", + "main": "packages/pieces/community/twilio/src/index.ts", + "assets": [ + "packages/pieces/community/twilio/*.md", + { + "input": "packages/pieces/community/twilio/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/twilio/src/index.ts b/packages/pieces/community/twilio/src/index.ts new file mode 100644 index 0000000..a8572a2 --- /dev/null +++ b/packages/pieces/community/twilio/src/index.ts @@ -0,0 +1,46 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { twilioSendSms } from './lib/action/send-sms'; +import { twilioNewIncomingSms } from './lib/trigger/new-incoming-sms'; + +export const twilioAuth = PieceAuth.BasicAuth({ + description: 'The authentication to use to connect to Twilio', + + required: true, + username: { + displayName: 'Account SID', + description: 'The account SID to use to connect to Twilio', + }, + password: { + displayName: 'Auth token', + description: 'The auth token to use to connect to Twilio', + }, +}); + +export const twilio = createPiece({ + displayName: 'Twilio', + description: + 'Cloud communications platform for building SMS, Voice & Messaging applications', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/twilio.png', + auth: twilioAuth, + categories: [PieceCategory.COMMUNICATION], + actions: [ + twilioSendSms, + createCustomApiCallAction({ + baseUrl: () => 'https://api.twilio.com/2010-04-01', + auth: twilioAuth, + authMapping: async (auth) => ({ + Authorization: `Basic ${Buffer.from( + `${(auth as { username: string }).username}:${ + (auth as { password: string }).password + }` + ).toString('base64')}`, + }), + }), + ], + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + triggers: [twilioNewIncomingSms], +}); diff --git a/packages/pieces/community/twilio/src/lib/action/send-sms.ts b/packages/pieces/community/twilio/src/lib/action/send-sms.ts new file mode 100644 index 0000000..af0171e --- /dev/null +++ b/packages/pieces/community/twilio/src/lib/action/send-sms.ts @@ -0,0 +1,39 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { callTwilioApi, twilioCommon } from '../common'; +import { twilioAuth } from '../..'; + +export const twilioSendSms = createAction({ + auth: twilioAuth, + name: 'send_sms', + description: 'Send a new SMS message', + displayName: 'Send SMS', + props: { + from: twilioCommon.phone_number, + body: Property.ShortText({ + description: 'The body of the message to send', + displayName: 'Message Body', + required: true, + }), + to: Property.ShortText({ + description: 'The phone number to send the message to', + displayName: 'To', + required: true, + }), + }, + async run(context) { + const { body, to, from } = context.propsValue; + const account_sid = context.auth.username; + const auth_token = context.auth.password; + return await callTwilioApi( + HttpMethod.POST, + 'Messages.json', + { account_sid, auth_token }, + { + From: from, + Body: body, + To: to, + } + ); + }, +}); diff --git a/packages/pieces/community/twilio/src/lib/common/index.ts b/packages/pieces/community/twilio/src/lib/common/index.ts new file mode 100644 index 0000000..cbbd906 --- /dev/null +++ b/packages/pieces/community/twilio/src/lib/common/index.ts @@ -0,0 +1,67 @@ +import { + Property, + BasicAuthPropertyValue, +} from '@activepieces/pieces-framework'; +import { + HttpMethod, + HttpMessageBody, + httpClient, + AuthenticationType, +} from '@activepieces/pieces-common'; + +export const twilioCommon = { + phone_number: Property.Dropdown({ + description: 'The phone number to send the message from', + displayName: 'From', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'connect your account first', + options: [], + }; + } + + const basicAuthProperty = auth as BasicAuthPropertyValue; + const response = await callTwilioApi<{ + incoming_phone_numbers: { + phone_number: string; + friendly_name: string; + }[]; + }>(HttpMethod.GET, 'IncomingPhoneNumbers.json', { + account_sid: basicAuthProperty.username, + auth_token: basicAuthProperty.password, + }); + return { + disabled: false, + options: response.body.incoming_phone_numbers.map((number: any) => ({ + value: number.phone_number, + label: number.friendly_name, + })), + }; + }, + }), +}; + +export const callTwilioApi = async ( + method: HttpMethod, + path: string, + auth: { account_sid: string; auth_token: string }, + body?: any +) => { + return await httpClient.sendRequest({ + method, + url: `https://api.twilio.com/2010-04-01/Accounts/${auth.account_sid}/${path}`, + authentication: { + type: AuthenticationType.BASIC, + username: auth.account_sid, + password: auth.auth_token, + }, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: body, + }); +}; diff --git a/packages/pieces/community/twilio/src/lib/trigger/new-incoming-sms.ts b/packages/pieces/community/twilio/src/lib/trigger/new-incoming-sms.ts new file mode 100644 index 0000000..85f9f94 --- /dev/null +++ b/packages/pieces/community/twilio/src/lib/trigger/new-incoming-sms.ts @@ -0,0 +1,120 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { + HttpMethod, + HttpResponse, + httpClient, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { callTwilioApi, twilioCommon } from '../common'; +import { twilioAuth } from '../..'; + +export const twilioNewIncomingSms = createTrigger({ + auth: twilioAuth, + name: 'new_incoming_sms', + displayName: 'New Incoming SMS', + description: 'Triggers when a new SMS message is received', + props: { + phone_number: twilioCommon.phone_number, + }, + sampleData: { + body: 'Hello', + num_segments: '1', + direction: 'inbound', + from: '+12184191735', + date_updated: 'Wed, 08 Feb 2023 01:40:51 +0000', + price: null, + error_message: null, + uri: '/2010-04-01/Accounts/ACc0ea1238d61fe90d78a69a3de71d45619/Messages/SM8c3920d3f2ac481ba83e639a69dadd63.json', + account_sid: 'ACc0ea716d61fe90d78a123a3de71d45619', + num_media: '0', + to: '+12184191735', + date_created: 'Wed, 08 Feb 2023 01:40:50 +0000', + status: 'failed', + sid: 'SM8c3920d3f2ac481ba83e639a69dadd63', + date_sent: 'Wed, 08 Feb 2023 01:40:51 +0000', + messaging_service_sid: 'MG88e323e6a88ce67ba3bf12e1bcb7e0b8', + error_code: 21211, + price_unit: 'USD', + api_version: '2010-04-01', + subresource_uris: { + media: + '/2010-04-01/Accounts/ACc0ea716d61fe90d78a69a3de71d45619/Messages/SM8c3920d3f2ac481ba83e639a69dadd63/Media.json', + feedback: + '/2010-04-01/Accounts/ACc0ea716d61fe90d78a69a3de71d45619/Messages/SM8c3920d3f2ac481ba83e639a69dadd63/Feedback.json', + }, + }, + // Twilio API only allows one webhook per phone number, so we need to poll + type: TriggerStrategy.POLLING, + async onEnable(context) { + const { phone_number } = context.propsValue; + const account_sid = context.auth.username; + const auth_token = context.auth.password; + const response = await callTwilioApi( + HttpMethod.GET, + `Messages.json?PageSize=20&To=${phone_number}`, + { account_sid, auth_token }, + {} + ); + await context.store.put('_new_incoming_sms_trigger', { + lastMessageId: + response.body.messages.length === 0 + ? null + : response.body.messages[0].sid, + }); + }, + async onDisable(context) { + await context.store.put('_new_incoming_sms_trigger', null); + }, + async run(context) { + const account_sid = context.auth.username; + const auth_token = context.auth.password; + const newMessages: unknown[] = []; + const lastMessage = await context.store.get( + '_new_incoming_sms_trigger' + ); + let currentUri: + | string + | null = `2010-04-01/Accounts/${account_sid}/Messages.json?PageSize=20&To=${context.propsValue.phone_number}`; + let firstMessageId = undefined; + while (currentUri !== undefined && currentUri !== null) { + const res: HttpResponse = + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.twilio.com/${currentUri}`, + authentication: { + type: AuthenticationType.BASIC, + username: account_sid, + password: auth_token, + }, + }); + const messages = res.body.messages; + if (!firstMessageId && messages.length > 0) { + firstMessageId = messages[0].sid; + } + currentUri = res.body.next_page_uri; + for (let i = 0; i < messages.length; i++) { + const message = messages[i]; + if (message.sid === lastMessage?.lastMessageId) { + currentUri = null; + break; + } + if (message.direction === 'inbound') { + newMessages.push(message); + } + } + } + await context.store.put('_new_incoming_sms_trigger', { + lastMessageId: firstMessageId ?? lastMessage!.lastMessageId, + }); + return newMessages; + }, +}); + +interface LastMessage { + lastMessageId: string | null; +} + +interface MessagePaginationResponse { + messages: { sid: string; to: string; status: string; direction: string }[]; + next_page_uri: string; +} diff --git a/packages/pieces/community/twilio/tsconfig.json b/packages/pieces/community/twilio/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/twilio/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/twilio/tsconfig.lib.json b/packages/pieces/community/twilio/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/twilio/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/twin-labs/.eslintrc.json b/packages/pieces/community/twin-labs/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/twin-labs/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/twin-labs/README.md b/packages/pieces/community/twin-labs/README.md new file mode 100644 index 0000000..7610790 --- /dev/null +++ b/packages/pieces/community/twin-labs/README.md @@ -0,0 +1,7 @@ +# pieces-twin-labs + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-twin-labs` to build the library. diff --git a/packages/pieces/community/twin-labs/package.json b/packages/pieces/community/twin-labs/package.json new file mode 100644 index 0000000..e89636c --- /dev/null +++ b/packages/pieces/community/twin-labs/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-twin-labs", + "version": "0.0.3" +} diff --git a/packages/pieces/community/twin-labs/project.json b/packages/pieces/community/twin-labs/project.json new file mode 100644 index 0000000..00db64f --- /dev/null +++ b/packages/pieces/community/twin-labs/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-twin-labs", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/twin-labs/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/twin-labs", + "tsConfig": "packages/pieces/community/twin-labs/tsconfig.lib.json", + "packageJson": "packages/pieces/community/twin-labs/package.json", + "main": "packages/pieces/community/twin-labs/src/index.ts", + "assets": [ + "packages/pieces/community/twin-labs/*.md", + { + "input": "packages/pieces/community/twin-labs/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/twin-labs/src/index.ts b/packages/pieces/community/twin-labs/src/index.ts new file mode 100644 index 0000000..9c9ff38 --- /dev/null +++ b/packages/pieces/community/twin-labs/src/index.ts @@ -0,0 +1,20 @@ + + import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; + import { startBrowsingTask } from "./lib/actions/start-browsing-task"; + + export const twinLabsAuth = PieceAuth.SecretText({ + displayName:'API Key', + required:true, + description:"Please use ***your-twin-labs-api-key*** as value for API Key" + }); + + export const twinLabs = createPiece({ + displayName: "Twin Web Agent", + auth: twinLabsAuth, + minimumSupportedRelease: '0.20.0', + logoUrl: "https://cdn.activepieces.com/pieces/twin-labs.png", + authors: [], + actions: [startBrowsingTask], + triggers: [], + }); + \ No newline at end of file diff --git a/packages/pieces/community/twin-labs/src/lib/actions/start-browsing-task.ts b/packages/pieces/community/twin-labs/src/lib/actions/start-browsing-task.ts new file mode 100644 index 0000000..b1a8824 --- /dev/null +++ b/packages/pieces/community/twin-labs/src/lib/actions/start-browsing-task.ts @@ -0,0 +1,93 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { twinLabsAuth } from '../..'; + +// API BASE URL +const API_BASE_URL = 'https://paris.prod.api.twin.so'; + +export const startBrowsingTask = createAction({ + name: 'startBrowsingTask', + auth: twinLabsAuth, + displayName: 'Browse', + description: + 'Browse the internet with an AI web navigation agent that can find information for you', + props: { + + + startUrl: Property.ShortText({ + displayName: 'startUrl', + required: true, + description: 'The URL where the browsing task should begin', + defaultValue: '', + }), + + goal: Property.ShortText({ + displayName: 'Goal', + required: true, + description: 'The goal or objective of the browsing task', + }), + }, + + async run(context) { + // Interface for the initial /browse + interface BrowseStartResponse { + url: string; + universeId: string; + worldId: number; + unitId: number; + } + + // Interface for the GET polling + interface BrowseStatusResponse { + completed : boolean; + pending?: boolean; + output?: string; + + } + + // Start the browsing task + const startRes = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${API_BASE_URL}/browse`, + headers: { + 'x-api-key': context.auth, + 'Content-Type': 'application/json', + }, + body: { + goal: context.propsValue['goal'], + startUrl: context.propsValue['startUrl'], + outputType: 'string', + completionCallbackUrl: 'https://', + }, + }); + + const pollingUrl = startRes.body.url; + let statusResponse: BrowseStatusResponse = { + completed: false, + pending: true, + }; + const timeoutAt = Date.now() + 15 * 60 * 1000; // 15 minutes + + + // Poll for task completion every 5 seconds until timeout + while (!statusResponse.completed && Date.now() < timeoutAt) { + await new Promise((resolve) => setTimeout(resolve, 5_000)); // wait 5 seconds + + const pollRes = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: pollingUrl, + headers: { + 'x-api-key': context.auth, + 'Content-Type': 'application/json', + }, + }); + + statusResponse = pollRes.body; // update statusResponse with the latest response + + } + + + // Return the final statusResponse in all cases + return statusResponse; + }, +}); diff --git a/packages/pieces/community/twin-labs/tsconfig.json b/packages/pieces/community/twin-labs/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/twin-labs/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/twin-labs/tsconfig.lib.json b/packages/pieces/community/twin-labs/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/twin-labs/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/twitter/.eslintrc.json b/packages/pieces/community/twitter/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/twitter/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/twitter/README.md b/packages/pieces/community/twitter/README.md new file mode 100644 index 0000000..6822724 --- /dev/null +++ b/packages/pieces/community/twitter/README.md @@ -0,0 +1,7 @@ +# pieces-twitter + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-twitter` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/twitter/package.json b/packages/pieces/community/twitter/package.json new file mode 100644 index 0000000..22646fc --- /dev/null +++ b/packages/pieces/community/twitter/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-twitter", + "version": "0.2.10" +} \ No newline at end of file diff --git a/packages/pieces/community/twitter/project.json b/packages/pieces/community/twitter/project.json new file mode 100644 index 0000000..274f194 --- /dev/null +++ b/packages/pieces/community/twitter/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-twitter", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/twitter/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/twitter", + "tsConfig": "packages/pieces/community/twitter/tsconfig.lib.json", + "packageJson": "packages/pieces/community/twitter/package.json", + "main": "packages/pieces/community/twitter/src/index.ts", + "assets": [ + "packages/pieces/community/twitter/*.md", + { + "input": "packages/pieces/community/twitter/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/twitter/src/index.ts b/packages/pieces/community/twitter/src/index.ts new file mode 100644 index 0000000..e1cf5e7 --- /dev/null +++ b/packages/pieces/community/twitter/src/index.ts @@ -0,0 +1,94 @@ +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { TwitterApi } from 'twitter-api-v2'; +import { createTweet } from './lib/actions/create-tweet'; +import { createReply } from './lib/actions/create-reply'; + +const markdownDescription = ` +If you don't have the credentials down below, please follow these steps to obtain the required credentials: + +1. Go to [https://developer.twitter.com/en/portal/projects-and-apps](https://developer.twitter.com/en/portal/projects-and-apps) and click on your app settings. + +2. Under the **Settings** tab then under **User authentication settings** section, click "Set up". + +3. **This step must be completed before generating the keys**, check on **Read and write** for "App permissions" and **Native App** for "Type of App", fill in your website url and let the **Callback URI / Redirect URL** be **(your_website_url)/redirect** . + +4. Go back to your app settings page and click the **Keys and tokens** tab. + +5. Next to **API key and secret**, click "Regenerate" and copy the following values to the inputs below: + + **Api Key** + + **Api Key Secret** + +6. Next to **Access token and secret**, click "Regenerate" and copy the following values to the inputs below: + + **Access Token** + + **Access Token Secret** + + +`; + +export const twitterAuth = PieceAuth.CustomAuth({ + description: markdownDescription, + props: { + consumerKey: Property.ShortText({ + displayName: 'Api Key', + description: 'The api key', + required: true, + }), + consumerSecret: Property.ShortText({ + displayName: 'Api Key Secret', + description: 'The api key secret', + required: true, + }), + accessToken: Property.ShortText({ + displayName: 'Access Token', + description: 'The access token', + required: true, + }), + accessTokenSecret: Property.ShortText({ + displayName: 'Access Token Secret', + description: 'The access token secret', + required: true, + }), + }, + validate: async ({ auth }) => { + const { consumerKey, consumerSecret, accessToken, accessTokenSecret } = + auth; + const userClient = new TwitterApi({ + appKey: consumerKey, + appSecret: consumerSecret, + accessToken: accessToken, + accessSecret: accessTokenSecret, + }); + try { + await userClient.v2.me(); + return { valid: true }; + } catch (e) { + return { + valid: false, + error: + 'Please make sure you have followed steps carefully and that your app is placed in a project.', + }; + } + }, + required: true, +}); + +export const twitter = createPiece({ + displayName: 'Twitter', + description: 'Social media platform with over 500 million user', + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/twitter.png', + categories: [PieceCategory.COMMUNICATION], + authors: ["Abdallah-Alwarawreh","Salem-Alaa","kishanprmr","AbdulTheActivePiecer","khaledmashaly","abuaboud"], + auth: twitterAuth, + actions: [createTweet, createReply], + triggers: [], +}); diff --git a/packages/pieces/community/twitter/src/lib/actions/create-reply.ts b/packages/pieces/community/twitter/src/lib/actions/create-reply.ts new file mode 100644 index 0000000..96cdc50 --- /dev/null +++ b/packages/pieces/community/twitter/src/lib/actions/create-reply.ts @@ -0,0 +1,78 @@ +import { + ApFile, + Property, + createAction, + } from '@activepieces/pieces-framework'; +import { TwitterApi } from 'twitter-api-v2'; +import { twitterAuth } from '../..'; +import { twitterCommon } from '../common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const createReply = createAction({ + auth: twitterAuth, + + name: 'create-reply', + displayName: 'Create Reply', + description: 'Reply to a tweet.', + props: { + tweet_id: Property.LongText({ + displayName: 'Tweet ID', + description: 'The ID of the tweet to reply too.', + required: true, + }), + text: twitterCommon.text, + image_1: twitterCommon.image_1, + image_2: twitterCommon.image_2, + image_3: twitterCommon.image_3, + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + text: z.string().min(1), + }); + + const { consumerKey, consumerSecret, accessToken, accessTokenSecret } = + context.auth; + const userClient = new TwitterApi({ + appKey: consumerKey, + appSecret: consumerSecret, + accessToken: accessToken, + accessSecret: accessTokenSecret, + }); + + try { + const media: ApFile[] = [ + context.propsValue.image_1, + context.propsValue.image_2, + context.propsValue.image_3, + ].filter((m): m is ApFile => !!m); + const uploadedMedia: Promise[] = []; + media.forEach((m) => { + uploadedMedia.push( + userClient.v1.uploadMedia(Buffer.from(m.base64, 'base64'), { + mimeType: 'image/png', + target: 'tweet', + }) + ); + }); + const uploaded = await Promise.all(uploadedMedia); + + const response = + uploaded.length > 0 + ? await userClient.v2.reply(context.propsValue.text, context.propsValue.tweet_id, { + media: { + media_ids: [...uploaded], + }, + }) + : await userClient.v2.reply(context.propsValue.text, context.propsValue.tweet_id); + return response || { success: true }; + } catch (error: any) { + throw new Error( + JSON.stringify({ + code: error.code, + errors: error.errors, + }) + ); + } + }, + }); \ No newline at end of file diff --git a/packages/pieces/community/twitter/src/lib/actions/create-tweet.ts b/packages/pieces/community/twitter/src/lib/actions/create-tweet.ts new file mode 100644 index 0000000..3b5ea6c --- /dev/null +++ b/packages/pieces/community/twitter/src/lib/actions/create-tweet.ts @@ -0,0 +1,72 @@ +import { + ApFile, + createAction, +} from '@activepieces/pieces-framework'; +import { TwitterApi } from 'twitter-api-v2'; +import { twitterAuth } from '../..'; +import { twitterCommon } from '../common'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const createTweet = createAction({ + auth: twitterAuth, + + name: 'create-tweet', + displayName: 'Create Tweet', + description: 'Create a tweet', + props: { + text: twitterCommon.text, + image_1: twitterCommon.image_1, + image_2: twitterCommon.image_2, + image_3: twitterCommon.image_3, + }, + async run(context) { + await propsValidation.validateZod(context.propsValue, { + text: z.string().min(1), + }); + + const { consumerKey, consumerSecret, accessToken, accessTokenSecret } = + context.auth; + const userClient = new TwitterApi({ + appKey: consumerKey, + appSecret: consumerSecret, + accessToken: accessToken, + accessSecret: accessTokenSecret, + }); + + try { + const media: ApFile[] = [ + context.propsValue.image_1, + context.propsValue.image_2, + context.propsValue.image_3, + ].filter((m): m is ApFile => !!m); + const uploadedMedia: Promise[] = []; + media.forEach((m) => { + uploadedMedia.push( + userClient.v1.uploadMedia(Buffer.from(m.base64, 'base64'), { + mimeType: 'image/png', + target: 'tweet', + }) + ); + }); + const uploaded = await Promise.all(uploadedMedia); + + const response = + uploaded.length > 0 + ? await userClient.v2.tweet(context.propsValue.text, { + media: { + media_ids: [...uploaded], + }, + }) + : await userClient.v2.tweet(context.propsValue.text); + return response || { success: true }; + } catch (error: any) { + throw new Error( + JSON.stringify({ + code: error.code, + errors: error.errors, + }) + ); + } + }, +}); diff --git a/packages/pieces/community/twitter/src/lib/common/index.ts b/packages/pieces/community/twitter/src/lib/common/index.ts new file mode 100644 index 0000000..7054713 --- /dev/null +++ b/packages/pieces/community/twitter/src/lib/common/index.ts @@ -0,0 +1,27 @@ +import { Property } from '@activepieces/pieces-framework'; + +export const twitterCommon = { + text: Property.LongText({ + displayName: 'Text', + description: 'The text of the tweet', + required: true, + }), + image_1: Property.File({ + displayName: 'Media (1)', + description: + 'An image, video or GIF url or base64 to attach to the tweet', + required: false, + }), + image_2: Property.File({ + displayName: 'Media (2)', + description: + 'An image, video or GIF url or base64 to attach to the tweet', + required: false, + }), + image_3: Property.File({ + displayName: 'Media (3)', + description: + 'An image, video or GIF url or base64 to attach to the tweet', + required: false, + }), + }; \ No newline at end of file diff --git a/packages/pieces/community/twitter/tsconfig.json b/packages/pieces/community/twitter/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/twitter/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/twitter/tsconfig.lib.json b/packages/pieces/community/twitter/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/twitter/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/typeform/.babelrc b/packages/pieces/community/typeform/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/typeform/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/typeform/.eslintrc.json b/packages/pieces/community/typeform/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/typeform/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/typeform/README.md b/packages/pieces/community/typeform/README.md new file mode 100644 index 0000000..e7d54ae --- /dev/null +++ b/packages/pieces/community/typeform/README.md @@ -0,0 +1,7 @@ +# pieces-typeform + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-typeform` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/typeform/package.json b/packages/pieces/community/typeform/package.json new file mode 100644 index 0000000..545f315 --- /dev/null +++ b/packages/pieces/community/typeform/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-typeform", + "version": "0.3.5" +} \ No newline at end of file diff --git a/packages/pieces/community/typeform/project.json b/packages/pieces/community/typeform/project.json new file mode 100644 index 0000000..af4a011 --- /dev/null +++ b/packages/pieces/community/typeform/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-typeform", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/typeform/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/typeform", + "tsConfig": "packages/pieces/community/typeform/tsconfig.lib.json", + "packageJson": "packages/pieces/community/typeform/package.json", + "main": "packages/pieces/community/typeform/src/index.ts", + "assets": [ + "packages/pieces/community/typeform/*.md", + { + "input": "packages/pieces/community/typeform/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/typeform/src/index.ts b/packages/pieces/community/typeform/src/index.ts new file mode 100644 index 0000000..9691a95 --- /dev/null +++ b/packages/pieces/community/typeform/src/index.ts @@ -0,0 +1,36 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { typeformNewSubmission } from './lib/trigger/new-submission'; + +export const typeformAuth = PieceAuth.OAuth2({ + required: true, + tokenUrl: 'https://api.typeform.com/oauth/token', + authUrl: 'https://admin.typeform.com/oauth/authorize', + scope: ['webhooks:write', 'forms:read'], +}); + +export const typeform = createPiece({ + displayName: 'Typeform', + description: 'Create beautiful online forms and surveys', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/typeform.png', + categories: [PieceCategory.FORMS_AND_SURVEYS], + actions: [ + createCustomApiCallAction({ + baseUrl: () => 'https://api.typeform.com', + auth: typeformAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + auth: typeformAuth, + authors: ["ashrafsamhouri","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + triggers: [typeformNewSubmission], +}); diff --git a/packages/pieces/community/typeform/src/lib/common/index.ts b/packages/pieces/community/typeform/src/lib/common/index.ts new file mode 100644 index 0000000..e61fc75 --- /dev/null +++ b/packages/pieces/community/typeform/src/lib/common/index.ts @@ -0,0 +1,124 @@ +import { + Property, + OAuth2PropertyValue, + DropdownOption, +} from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + AuthenticationType, + httpClient, +} from '@activepieces/pieces-common'; + +type FormListResponse = { + page_count: number; + total_items: number; + items: { + id: string; + title: string; + }[]; +}; + +export const formsDropdown = Property.Dropdown({ + displayName: 'Form', + description: 'Form Name', + required: true, + refreshers: [], + async options({ auth: authentication }) { + const auth = authentication as OAuth2PropertyValue; + + if (!auth) { + return { + disabled: true, + placeholder: 'Connect typeform account', + options: [], + }; + } + + const accessToken = auth.access_token; + + const options: DropdownOption[] = []; + let hasMore = true; + let page = 1; + + do { + const request: HttpRequest = { + method: HttpMethod.GET, + url: 'https://api.typeform.com/forms', + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + queryParams: { + page: page.toString(), + page_size: '200', + }, + }; + + const response = await httpClient.sendRequest(request); + + for (const form of response.body.items) { + options.push({ label: form.title, value: form.id }); + } + + hasMore = + response.body.page_count != undefined && + page < response.body.page_count; + + page++; + } while (hasMore); + + return { + disabled: false, + placeholder: 'Select form', + options, + }; + }, +}); + +export const typeformCommon = { + baseUrl: 'https://api.typeform.com', + subscribeWebhook: async ( + formId: string, + tag: string, + webhookUrl: string, + accessToken: string + ) => { + const request: HttpRequest = { + method: HttpMethod.PUT, + url: `${typeformCommon.baseUrl}/forms/${formId}/webhooks/${tag}`, + headers: { + 'Content-Type': 'application/json', + }, + body: { + enabled: true, + url: webhookUrl, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + queryParams: {}, + }; + + await httpClient.sendRequest(request); + }, + unsubscribeWebhook: async ( + formId: string, + tag: string, + accessToken: string + ) => { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `${typeformCommon.baseUrl}/forms/${formId}/webhooks/${tag}`, + headers: { + 'Content-Type': 'application/json', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }; + return await httpClient.sendRequest(request); + }, +}; diff --git a/packages/pieces/community/typeform/src/lib/trigger/new-submission.ts b/packages/pieces/community/typeform/src/lib/trigger/new-submission.ts new file mode 100644 index 0000000..ad6325a --- /dev/null +++ b/packages/pieces/community/typeform/src/lib/trigger/new-submission.ts @@ -0,0 +1,91 @@ +import { typeformCommon, formsDropdown } from '../common'; +import { nanoid } from 'nanoid'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { typeformAuth } from '../..'; + +export const typeformNewSubmission = createTrigger({ + auth: typeformAuth, + name: 'new_submission', + displayName: 'New Submission', + description: 'Triggers when Typeform receives a new submission', + props: { + form_id: formsDropdown, + }, + type: TriggerStrategy.WEBHOOK, + sampleData: { + form_id: 'o3TT4IlE', + token: '9q3v9bp5alonta6239q3q1rfly2c2ukh', + landed_at: '2023-01-29T20:52:35Z', + submitted_at: '2023-01-29T20:52:37Z', + definition: { + id: 'o3TT4IlE', + title: 'test2', + fields: [ + { + id: 'r2NV4a7LSugq', + ref: '01GQZMFYAD53N13MC7G0AKFWC6', + type: 'multiple_choice', + title: '...', + properties: {}, + choices: [ + { + id: 'jNfSosecdD10', + label: 'Choice 1', + }, + { + id: 'pCyKAWMwEbZH', + label: 'Choice 2', + }, + ], + }, + ], + }, + answers: [ + { + type: 'choice', + choice: { + label: 'Choice 1', + }, + field: { + id: 'r2NV4a7LSugq', + type: 'multiple_choice', + ref: '01GQZMFYAD53N13MC7G0AKFWC6', + }, + }, + ], + thankyou_screen_ref: '01GQZMFYADET9MXFKPGFQG08T9', + }, + async onEnable(context) { + const randomTag = `ap_new_submission_${nanoid()}`; + await typeformCommon.subscribeWebhook( + context.propsValue['form_id']!, + randomTag, + context.webhookUrl, + getAccessTokenOrThrow(context.auth) + ); + await context.store?.put('_new_submission_trigger', { + tag: randomTag, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_new_submission_trigger' + ); + if (response !== null && response !== undefined) { + await typeformCommon.unsubscribeWebhook( + context.propsValue['form_id']!, + response.tag, + getAccessTokenOrThrow(context.auth) + ); + } + }, + async run(context) { + const body = context.payload.body as { form_response: unknown }; + return [body.form_response]; + }, +}); + +interface WebhookInformation { + tag: string; +} diff --git a/packages/pieces/community/typeform/tsconfig.json b/packages/pieces/community/typeform/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/typeform/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/typeform/tsconfig.lib.json b/packages/pieces/community/typeform/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/typeform/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/upgradechat/.eslintrc.json b/packages/pieces/community/upgradechat/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/upgradechat/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/upgradechat/README.md b/packages/pieces/community/upgradechat/README.md new file mode 100644 index 0000000..3b2b1a5 --- /dev/null +++ b/packages/pieces/community/upgradechat/README.md @@ -0,0 +1,7 @@ +# pieces-upgradechat + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-upgradechat` to build the library. diff --git a/packages/pieces/community/upgradechat/package.json b/packages/pieces/community/upgradechat/package.json new file mode 100644 index 0000000..d8980ff --- /dev/null +++ b/packages/pieces/community/upgradechat/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-upgradechat", + "version": "0.0.1" +} diff --git a/packages/pieces/community/upgradechat/project.json b/packages/pieces/community/upgradechat/project.json new file mode 100644 index 0000000..6829eab --- /dev/null +++ b/packages/pieces/community/upgradechat/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-upgradechat", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/upgradechat/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/upgradechat", + "tsConfig": "packages/pieces/community/upgradechat/tsconfig.lib.json", + "packageJson": "packages/pieces/community/upgradechat/package.json", + "main": "packages/pieces/community/upgradechat/src/index.ts", + "assets": [ + "packages/pieces/community/upgradechat/*.md", + { + "input": "packages/pieces/community/upgradechat/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/upgradechat/src/index.ts b/packages/pieces/community/upgradechat/src/index.ts new file mode 100644 index 0000000..0126ad0 --- /dev/null +++ b/packages/pieces/community/upgradechat/src/index.ts @@ -0,0 +1,72 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { addOrUpdateContactExtended } from './lib/actions/add-or-update-contact-extended'; +import { addOrUpdateContact } from './lib/actions/add-or-update-contact'; +import { addOrUpdateSubscription } from './lib/actions/add-or-update-subscription'; +import { createInvoice } from './lib/actions/create-invoice'; +import { createProduct } from './lib/actions/create-product'; +import { getContactDetails } from './lib/actions/get-contact-details'; +import { newLead } from './lib/triggers/new-lead'; +import { newPayment } from './lib/triggers/new-payment'; +import { newSubscription } from './lib/triggers/new-subscription'; +import { PieceCategory } from '@activepieces/shared'; + +const markdownDescription = ` + Follow these instructions to get your Upgrade.chat API Key: + + 1. Visit the following website: https://crm.upgrade.chat/ or the beta website: https://betacrm.upgrade.chat/ + 2. Once on the website, locate and click on the admin to obtain your Upgrade.chat API Key. +`; + +export const upgradechatAuth = PieceAuth.CustomAuth({ + description: markdownDescription, + required: true, + props: { + base_url: Property.StaticDropdown({ + displayName: 'Base URL', + description: 'Select the base environment URL', + required: true, + options: { + disabled: false, + options: [ + { + label: 'Upgrade.chat Live (crm.upgrade.chat)', + value: 'https://crm.upgrade.chat/', + }, + { + label: 'Upgrade.chat Beta (betacrm.upgrade.chat)', + value: 'https://betacrm.upgrade.chat/', + }, + ], + }, + }), + api_key: PieceAuth.SecretText({ + displayName: 'Secret API Key', + description: 'Enter the API Key', + required: true, + }), + }, +}); + +export const upgradechat = createPiece({ + displayName: 'Upgrade.chat', + description: + 'Supercharge your Discord or Telegram communities with subscription payments and membership tools.', + auth: upgradechatAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/upgradechat.png', + authors: ['Trayshmhirk', 'OmarSayed'], + categories: [PieceCategory.SALES_AND_CRM], + actions: [ + addOrUpdateContact, + addOrUpdateContactExtended, + addOrUpdateSubscription, + createInvoice, + createProduct, + getContactDetails, + ], + triggers: [newLead, newPayment, newSubscription], +}); diff --git a/packages/pieces/community/upgradechat/src/lib/actions/add-or-update-contact-extended.ts b/packages/pieces/community/upgradechat/src/lib/actions/add-or-update-contact-extended.ts new file mode 100644 index 0000000..9d5f70c --- /dev/null +++ b/packages/pieces/community/upgradechat/src/lib/actions/add-or-update-contact-extended.ts @@ -0,0 +1,1292 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { upgradechatAuth } from '../../'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const addOrUpdateContactExtended = createAction({ + name: 'addOrUpdateContactExtended', + displayName: 'Add or Update Contact (Extended)', + description: 'Adds or updates a contact (extended version)', + auth: upgradechatAuth, + props: { + matchExisting: Property.StaticDropdown({ + displayName: 'Match Existing Contact', + description: + 'If "Yes", will try to find an existing record using Email and Full Name and update it.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + overrideLists: Property.StaticDropdown({ + displayName: 'Override Lists', + description: + 'If "Yes", will override lists of contact details in update mode instead of merging them - lists, tags, emails, phones, links, addresses, photos.', + required: false, + defaultValue: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + ignoreInvalidValues: Property.StaticDropdown({ + displayName: 'Ignore Invalid Values', + description: + 'If "Yes", will save the record even if there are some validation errors.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + contactId: Property.Number({ + displayName: 'Contact ID', + description: 'Sperse Contact ID. Will be used for looking a client', + defaultValue: 0, + required: false, + }), + contactXref: Property.ShortText({ + displayName: 'Contact XREF', + description: + 'This is string external reference that can be passed during creation and then if sent again it will update the record.', + required: false, + }), + importType: Property.StaticDropdown({ + displayName: 'Contact Type', + required: true, + defaultValue: 'Lead', + options: { + disabled: false, + options: [ + { + label: 'Lead', + value: 'Lead', + }, + { + label: 'Client', + value: 'Client', + }, + { + label: 'Partner', + value: 'Partner', + }, + ], + }, + }), + // user info + createUser: Property.StaticDropdown({ + displayName: 'Create User', + description: + 'If "Yes" then User will be created. Personal email will be used as User Name.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + sendWelcomeEmail: Property.StaticDropdown({ + displayName: 'Send Welcome Email', + description: + 'If "Yes" then Welcome Email will be sent to the newly created user.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + userPassword: Property.ShortText({ + displayName: 'User Password', + description: + 'If password is not passed then it will be automatically generated.', + required: false, + }), + // fullname + namePrefix: Property.ShortText({ + displayName: 'Prefix', + description: 'The title used to address the contact.', + required: false, + }), + firstName: Property.ShortText({ + displayName: 'First Name', + description: 'Required if Last Name and Company Name fields are empty.', + required: true, + }), + middleName: Property.ShortText({ + displayName: 'Middle Name', + description: "The contact's middle name.", + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + description: 'Required if First Name and Company Name fields are empty.', + required: true, + }), + nickName: Property.ShortText({ + displayName: 'Nick Name', + description: "The contact's nick name.", + required: false, + }), + nameSuffix: Property.ShortText({ + displayName: 'Suffix', + description: 'Additional information about the contact e.g PhD.', + required: false, + }), + // personal info + gender: Property.StaticDropdown({ + displayName: 'Gender', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Male', + value: 'Male', + }, + { + label: 'Female', + value: 'Female', + }, + ], + }, + }), + dob: Property.ShortText({ + displayName: 'Date of Birth', + description: 'Valid date format YYYY-MM-DD or MM-DD-YYYY.', + required: false, + }), + bankCode: Property.ShortText({ + displayName: 'Bank Code', + description: "The contact's 4-letter personality code.", + required: false, + }), + ssn: Property.ShortText({ + displayName: 'SSN', + description: "The contact's social security number.", + required: false, + }), + drivingLicense: Property.ShortText({ + displayName: 'Driving License', + required: false, + }), + drivingLicenseState: Property.ShortText({ + displayName: 'Driving License State', + required: false, + }), + isActiveMilitaryDuty: Property.StaticDropdown({ + displayName: 'Is Active Military Duty', + description: + 'Possible values are: Yes, No. If nothing is chosen that means "Unknown".', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + isUSCitizen: Property.StaticDropdown({ + displayName: 'Is US Citizen', + description: + 'Possible values are: Yes, No. If nothing is chosen that means "Unknown".', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + preferredToD: Property.StaticDropdown({ + displayName: 'Preferred Time of Day', + description: 'Preferred Time of Day to contact with Client', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Morning', + value: 'Morning', + }, + { + label: 'Afternoon', + value: 'Afternoon', + }, + { + label: 'Evening', + value: 'Evening', + }, + { + label: 'Anytime', + value: 'Anytime', + }, + ], + }, + }), + personAffiliateCode: Property.ShortText({ + displayName: 'Person Affiliate Code', + description: + 'Affiliate Code is used for the current person detection as a source of new leads. Alphanumeric characters, underscore and hyphen are allowed.', + required: false, + }), + // email + email1: Property.LongText({ + displayName: 'Personal Email', + description: "The contact's personal email.", + required: false, + }), + email2: Property.LongText({ + displayName: 'Alternative Personal email', + description: "The contact's alternative personal email.", + required: false, + }), + email3: Property.LongText({ + displayName: 'Other Personal email', + description: "The contact's additional email.", + required: false, + }), + workEmail1: Property.LongText({ + displayName: 'Work Email', + description: "The contact's work email.", + required: false, + }), + workEmail2: Property.LongText({ + displayName: 'Alternative Work Email', + description: "The contact's alternative work email.", + required: false, + }), + workEmail3: Property.LongText({ + displayName: 'Other Work Email', + description: "The contact's other work email.", + required: false, + }), + // phone + mobilePhone: Property.ShortText({ + displayName: 'Mobile Phone', + description: "The contact's primary phone number.", + required: false, + }), + mobilePhoneExt: Property.ShortText({ + displayName: 'Mobile Phone Ext', + description: "The contact's primary phone number extension.", + required: false, + }), + homePhone: Property.ShortText({ + displayName: 'Home Phone', + description: "The contact's home phone number.", + required: false, + }), + homePhoneExt: Property.ShortText({ + displayName: 'Home Phone Ext', + description: "The contact's home phone number extension.", + required: false, + }), + workPhone1: Property.ShortText({ + displayName: 'Work Phone', + description: "The contact's work phone number.", + required: false, + }), + workPhone1Ext: Property.ShortText({ + displayName: 'Work Phone Ext', + description: "The contact's work phone number extension.", + required: false, + }), + workPhone2: Property.ShortText({ + displayName: 'Work Phone 2', + description: "The contact's alternative work phone number.", + required: false, + }), + workPhone2Ext: Property.ShortText({ + displayName: 'Work Phone 2 Ext', + description: "The contact's alternative work phone number extension.", + required: false, + }), + // home address + home_street: Property.LongText({ + displayName: 'Home Street', + description: + "The contact's full street address (can include apartment or unit number).", + required: false, + }), + home_addressLine2: Property.LongText({ + displayName: 'Address 2', + required: false, + }), + home_city: Property.LongText({ + displayName: 'City', + description: "The contact's city of residence.", + required: false, + }), + home_stateName: Property.ShortText({ + displayName: 'State Name', + description: "The contact's state of residence.", + required: false, + }), + home_stateId: Property.ShortText({ + displayName: 'State Code', + description: "The contact's state code.", + required: false, + }), + home_zip: Property.ShortText({ + displayName: 'ZIP Code', + description: "The contact's zip/postal code.", + required: false, + }), + home_countryName: Property.ShortText({ + displayName: 'Country Name', + description: "The contact's country of residence.", + required: false, + }), + home_countryId: Property.ShortText({ + displayName: 'Country Code', + description: "The contact's country code.", + required: false, + }), + // work address + work_street: Property.LongText({ + displayName: 'Work Street', + description: "The contact's work address.", + required: false, + }), + work_addressLine2: Property.LongText({ + displayName: 'Address 2', + required: false, + }), + work_city: Property.LongText({ + displayName: 'City', + description: "The contact's work city.", + required: false, + }), + work_stateName: Property.ShortText({ + displayName: 'State Name', + description: "The contact's work state.", + required: false, + }), + work_stateId: Property.ShortText({ + displayName: 'State Code', + description: "The contact's work state code.", + required: false, + }), + work_zip: Property.ShortText({ + displayName: 'ZIP Code', + description: "The contact's work zip code.", + required: false, + }), + work_countryName: Property.ShortText({ + displayName: 'Country Name', + description: "The contact's work country.", + required: false, + }), + work_countryId: Property.ShortText({ + displayName: 'Country Code', + description: "The contact's work country code.", + required: false, + }), + // links + webSiteUrl: Property.LongText({ + displayName: 'Website', + description: "The contact's company website URL.", + required: false, + }), + linkedInUrl: Property.LongText({ + displayName: 'LinkedIn', + description: "The contact's LinkedIn profile id.", + required: false, + }), + photoUrl: Property.LongText({ + displayName: 'Photo URL', + description: "The contact's person photo URL.", + required: false, + }), + facebookUrl: Property.LongText({ + displayName: 'Facebook', + description: "The contact's Facebook profile id.", + required: false, + }), + instagramUrl: Property.LongText({ + displayName: 'Instagram', + description: "The contact's Instagram profile id.", + required: false, + }), + twitterUrl: Property.LongText({ + displayName: 'Twitter', + description: "The contact's Twitter profile id.", + required: false, + }), + googlePlusUrl: Property.LongText({ + displayName: 'Google Reviews', + description: "The contact's Google reviews.", + required: false, + }), + angelListUrl: Property.LongText({ + displayName: 'AngelList', + description: "The contact's AngelList profile id.", + required: false, + }), + zoomUrl: Property.LongText({ + displayName: 'Zoom', + description: "The contact's Zoom id.", + required: false, + }), + // content + experience: Property.LongText({ + displayName: 'Content', + description: "The contact's professional experience.", + required: false, + }), + profileSummary: Property.LongText({ + displayName: 'Profile Summary', + description: "The contact's profile summary.", + required: false, + }), + notes: Property.LongText({ + displayName: 'Notes', + description: 'Additional notes about the contact', + required: false, + }), + followUpDate: Property.ShortText({ + displayName: 'Follow Up Date', + description: + 'Valid date format YYYY-MM-DD HH:MM:SS. If date is defined then new Follow Up Task will be created for this contact', + required: false, + }), + assignedUser: Property.ShortText({ + displayName: 'Assigned User', + description: + 'Preferably, Sperse User Email should be passed as it is unique within Sperse account but User Name can be also passed', + required: false, + }), + leadDealAmount: Property.Number({ + displayName: 'Deal Amount', + description: 'Estimated deal/opportunity amount.', + required: false, + }), + // custom fields + customField1: Property.LongText({ + displayName: 'Custom Field 1', + description: 'Additional custom data for the contact record.', + required: false, + }), + customField2: Property.LongText({ + displayName: 'Custom Field 2', + required: false, + }), + customField3: Property.LongText({ + displayName: 'Custom Field 3', + required: false, + }), + customField4: Property.LongText({ + displayName: 'Custom Field 4', + required: false, + }), + customField5: Property.LongText({ + displayName: 'Custom Field 5', + required: false, + }), + // business info + companyName: Property.LongText({ + displayName: 'Company Name', + description: + "Name of the contact's company (This field is mandatory if the First Name and Last Name fields are empty).", + required: false, + }), + organizationType: Property.StaticDropdown({ + displayName: 'Organization Type', + required: false, + options: { + disabled: false, + options: [ + { + label: 'LLP', + value: 'LLP', + }, + { + label: 'LLC', + value: 'LLC', + }, + { + label: 'Inc', + value: 'Inc', + }, + { + label: 'LP', + value: 'LP', + }, + { + label: 'Partnership', + value: 'Partnership', + }, + { + label: 'Sole Proprietership', + value: 'Sole Proprietership', + }, + { + label: 'Trust', + value: 'Trust', + }, + { + label: 'LLLP', + value: 'LLLP', + }, + { + label: 'Other', + value: 'Other', + }, + ], + }, + }), + isEmployed: Property.StaticDropdown({ + displayName: 'Is Employed', + description: 'Pass yes if the client is employed in this Organization.', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + jobTitle: Property.ShortText({ + displayName: 'Job Title', + description: "The contact's job title.", + required: false, + }), + employmentStartDate: Property.ShortText({ + displayName: 'Employment Start Date', + description: 'Valid date format YYYY-MM-DD or MM-DD-YYYY', + required: false, + }), + employeeCount: Property.ShortText({ + displayName: 'Employee Count', + required: false, + }), + dateFounded: Property.ShortText({ + displayName: 'Date Founded', + description: 'Valid date format YYYY-MM-DD or MM-DD-YYYY', + required: false, + }), + ein: Property.LongText({ + displayName: 'EIN', + required: false, + }), + annualRevenue: Property.Number({ + displayName: 'Annual Revenue', + required: false, + }), + industry: Property.ShortText({ + displayName: 'Industry', + description: "The company's industry.", + required: false, + }), + companyPhone: Property.ShortText({ + displayName: 'Company Phone', + required: false, + }), + companyPhoneExt: Property.ShortText({ + displayName: 'Company Phone Extension', + required: false, + }), + companyFaxNumber: Property.LongText({ + displayName: 'Company Fax Number', + required: false, + }), + companyEmail: Property.LongText({ + displayName: 'Company Email', + required: false, + }), + companyWebSiteUrl: Property.LongText({ + displayName: 'Company Website', + required: false, + }), + companyFacebookUrl: Property.LongText({ + displayName: 'Company Facebook', + required: false, + }), + companyLinkedInUrl: Property.LongText({ + displayName: 'Company LinkedIn', + required: false, + }), + companyInstagramUrl: Property.LongText({ + displayName: 'Company Instagram', + required: false, + }), + companyTwitterUrl: Property.LongText({ + displayName: 'Company Twitter', + required: false, + }), + companyGooglePlusUrl: Property.LongText({ + displayName: 'Company Google Reviews', + required: false, + }), + companyCrunchbaseUrl: Property.LongText({ + displayName: 'Company Crunchbase', + required: false, + }), + companyBBBUrl: Property.LongText({ + displayName: 'Company BBB URL', + required: false, + }), + companyZoomUrl: Property.LongText({ + displayName: 'Company Zoom', + required: false, + }), + companyCalendlyUrl: Property.LongText({ + displayName: 'Company Calendly', + required: false, + }), + companyLogoUrl: Property.LongText({ + displayName: 'Company Logo URL', + required: false, + }), + companyAffiliateCode: Property.ShortText({ + displayName: 'Company Affiliate Code', + required: false, + }), + // company full Address + company_street: Property.LongText({ + displayName: 'Company Street', + required: false, + }), + company_addressLine2: Property.LongText({ + displayName: 'Address 2', + required: false, + }), + company_city: Property.ShortText({ + displayName: 'City', + required: false, + }), + company_stateName: Property.ShortText({ + displayName: 'State Name', + required: false, + }), + company_stateId: Property.ShortText({ + displayName: 'State Code', + required: false, + }), + company_zip: Property.ShortText({ + displayName: 'Company Zip Code', + required: false, + }), + company_countryName: Property.LongText({ + displayName: 'Company Country Name', + required: false, + }), + company_countryId: Property.ShortText({ + displayName: 'Company Country Code', + required: false, + }), + // tracking info + leadSource: Property.ShortText({ + displayName: 'Source Code', + description: + 'The first known source the contact used to find your website. You can set this automatically and update manually later.', + required: false, + }), + channelId: Property.ShortText({ + displayName: 'Channel Code', + description: 'The channel/medium the contact used to find your website.', + required: false, + }), + affiliateCode: Property.ShortText({ + displayName: 'Affiliate Code', + description: + 'The affiliate/referer partner through which the contact signed up.', + required: false, + }), + campaignId: Property.ShortText({ + displayName: 'Campaign ID', + required: false, + }), + gclId: Property.ShortText({ + displayName: 'Google Click ID', + required: false, + }), + refererURL: Property.LongText({ + displayName: 'Referer URL', + description: + 'The webpage where the contact clicked a link that sent them to your website.', + required: false, + }), + applicantId: Property.ShortText({ + displayName: 'Applicant ID', + required: false, + }), + applicationId: Property.ShortText({ + displayName: 'Application ID', + required: false, + }), + ipAddress: Property.LongText({ + displayName: 'IP Address', + required: false, + }), + userAgent: Property.ShortText({ + displayName: 'User Agent', + required: false, + }), + siteId: Property.ShortText({ + displayName: 'Site ID', + required: false, + }), + siteUrl: Property.LongText({ + displayName: 'Site URL', + required: false, + }), + dateCreated: Property.ShortText({ + displayName: 'Date Created', + description: 'Valid date format YYYY-MM-DD HH:MM:SS', + required: false, + }), + entryUrl: Property.LongText({ + displayName: 'Entry URL', + description: + 'The first page of visit through which the contact visited your website.', + required: false, + }), + // contact Attributes + leadStageName: Property.ShortText({ + displayName: 'Lead Stage Name', + description: + 'The first page of visit through which the contact visited your website.', + required: false, + }), + star: Property.ShortText({ + displayName: 'Star', + description: + 'String Value. Supports the following items: Yellow, Blue, Green, Purple, Red. Other values will be skipped.', + required: false, + }), + rating: Property.ShortText({ + displayName: 'Rating', + description: 'This is the static array from 1 to 10', + required: false, + }), + // UtmParameters + utmSource: Property.LongText({ + displayName: 'UTM Source', + required: false, + }), + utmMedium: Property.LongText({ + displayName: 'UTM Medium', + required: false, + }), + utmCampaign: Property.LongText({ + displayName: 'UTM Campaign', + required: false, + }), + utmTerm: Property.LongText({ + displayName: 'UTM Term', + required: false, + }), + utmContent: Property.LongText({ + displayName: 'UTM Content', + required: false, + }), + utmKeyword: Property.LongText({ + displayName: 'UTM Keyword', + required: false, + }), + utmAdGroup: Property.LongText({ + displayName: 'UTM AdGroup', + required: false, + }), + utmName: Property.LongText({ + displayName: 'UTM Name', + required: false, + }), + // requestCustomInfo + requestCustomField1: Property.LongText({ + displayName: 'Request Custom Field 1', + required: false, + }), + requestCustomField2: Property.LongText({ + displayName: 'Request Custom Field 2', + required: false, + }), + requestCustomField3: Property.LongText({ + displayName: 'Request Custom Field 3', + required: false, + }), + requestCustomField4: Property.LongText({ + displayName: 'Request Custom Field 4', + required: false, + }), + requestCustomField5: Property.LongText({ + displayName: 'Request Custom Field 5', + required: false, + }), + // subscription1 + sub1_productCode: Property.ShortText({ + displayName: 'Product Code', + description: + 'Product Code from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 1.', + required: false, + }), + sub1_paymentPeriodType: Property.StaticDropdown({ + displayName: 'Product Payment Period Type', + description: + 'The chosen Period Type has to be set for the Product on Sperse side', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + ], + }, + }), + sub1_systemType: Property.ShortText({ + displayName: 'System Type', + description: + 'Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 1.', + required: false, + }), + sub1_code: Property.ShortText({ + displayName: 'Code', + description: + 'Code of subscription service from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 1.', + required: false, + }), + sub1_name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + sub1_level: Property.ShortText({ + displayName: 'Level', + description: 'Code of subscription service level from the Sperse CRM.', + required: false, + }), + sub1_startDate: Property.ShortText({ + displayName: 'Start Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub1_endDate: Property.ShortText({ + displayName: 'End Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub1_amount: Property.Number({ + displayName: 'Amount', + required: false, + }), + // subscription2 + sub2_productCode: Property.ShortText({ + displayName: 'Product Code', + description: + 'Product Code from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 2.', + required: false, + }), + sub2_paymentPeriodType: Property.StaticDropdown({ + displayName: 'Product Payment Period Type', + description: + 'The chosen Period Type has to be set for the Product on Sperse side', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + ], + }, + }), + sub2_systemType: Property.ShortText({ + displayName: 'System Type', + description: + 'Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 2.', + required: false, + }), + sub2_code: Property.ShortText({ + displayName: 'Code', + description: + 'Code of subscription service from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 2.', + required: false, + }), + sub2_name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + sub2_level: Property.ShortText({ + displayName: 'Level', + description: 'Code of subscription service level from the Sperse CRM.', + required: false, + }), + sub2_startDate: Property.ShortText({ + displayName: 'Start Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub2_endDate: Property.ShortText({ + displayName: 'End Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub2_amount: Property.Number({ + displayName: 'Amount', + required: false, + }), + // subscription3 + sub3_productCode: Property.ShortText({ + displayName: 'Product Code', + description: + 'Product Code from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 3.', + required: false, + }), + sub3_paymentPeriodType: Property.StaticDropdown({ + displayName: 'Product Payment Period Type', + description: + 'The chosen Period Type has to be set for the Product on Sperse side', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + ], + }, + }), + sub3_systemType: Property.ShortText({ + displayName: 'System Type', + description: + 'Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 3.', + required: false, + }), + sub3_code: Property.ShortText({ + displayName: 'Code', + description: + 'Code of subscription service from the Sperse CRM. Either Product Code and Payment Period Type or System Type and Code are required fields within Subscription 3.', + required: false, + }), + sub3_name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + sub3_level: Property.ShortText({ + displayName: 'Level', + description: 'Code of subscription service level from the Sperse CRM.', + required: false, + }), + sub3_startDate: Property.ShortText({ + displayName: 'Start Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub3_endDate: Property.ShortText({ + displayName: 'End Date', + description: 'Valid date format YYYY-MM-DD HH:MM:SS.', + required: false, + }), + sub3_amount: Property.Number({ + displayName: 'Amount', + required: false, + }), + }, + async run(context) { + const customer = { + importType: context.propsValue.importType, + ignoreInvalidValues: context.propsValue.ignoreInvalidValues, + matchExisting: context.propsValue.matchExisting, + overrideLists: context.propsValue.overrideLists, + createUser: context.propsValue.createUser, + sendWelcomeEmail: context.propsValue.sendWelcomeEmail, + contactId: context.propsValue.contactId, + contactXref: context.propsValue.contactXref, + userPassword: context.propsValue.userPassword, + personalInfo: { + fullName: { + namePrefix: context.propsValue.namePrefix, + firstName: context.propsValue.firstName, + middleName: context.propsValue.middleName, + lastName: context.propsValue.lastName, + nameSuffix: context.propsValue.nickName, + nickName: context.propsValue.nameSuffix, + }, + doB: context.propsValue.dob, + mobilePhone: context.propsValue.mobilePhone, + mobilePhoneExt: context.propsValue.mobilePhoneExt, + homePhone: context.propsValue.homePhone, + homePhoneExt: context.propsValue.homePhoneExt, + ssn: context.propsValue.ssn, + bankCode: context.propsValue.bankCode, + email1: context.propsValue.email1, + email2: context.propsValue.email2, + email3: context.propsValue.email3, + preferredToD: context.propsValue.preferredToD, + drivingLicense: context.propsValue.drivingLicense, + drivingLicenseState: context.propsValue.drivingLicenseState, + isActiveMilitaryDuty: context.propsValue.isActiveMilitaryDuty, + gender: context.propsValue.gender, + fullAddress: { + street: context.propsValue.home_street, + addressLine2: context.propsValue.home_addressLine2, + city: context.propsValue.home_city, + stateName: context.propsValue.home_stateName, + stateId: context.propsValue.home_stateId, + zip: context.propsValue.home_zip, + countryName: context.propsValue.home_countryName, + countryId: context.propsValue.home_countryId, + }, + isUSCitizen: context.propsValue.isUSCitizen, + webSiteUrl: context.propsValue.webSiteUrl, + facebookUrl: context.propsValue.facebookUrl, + linkedInUrl: context.propsValue.linkedInUrl, + instagramUrl: context.propsValue.instagramUrl, + twitterUrl: context.propsValue.twitterUrl, + googlePlusUrl: context.propsValue.googlePlusUrl, + angelListUrl: context.propsValue.angelListUrl, + zoomUrl: context.propsValue.zoomUrl, + photoUrl: context.propsValue.photoUrl, + experience: context.propsValue.experience, + profileSummary: context.propsValue.profileSummary, + affiliateCode: context.propsValue.personAffiliateCode, + customFields: { + customField1: context.propsValue.customField1, + customField2: context.propsValue.customField2, + customField3: context.propsValue.customField3, + customField4: context.propsValue.customField4, + customField5: context.propsValue.customField5, + }, + }, + businessInfo: { + companyName: context.propsValue.companyName, + organizationType: context.propsValue.organizationType, + jobTitle: context.propsValue.isEmployed, + isEmployed: context.propsValue.jobTitle, + employmentStartDate: context.propsValue.employmentStartDate, + employeeCount: context.propsValue.employeeCount, + dateFounded: context.propsValue.dateFounded, + ein: context.propsValue.ein, + annualRevenue: context.propsValue.annualRevenue, + industry: context.propsValue.industry, + companyPhone: context.propsValue.companyPhone, + companyPhoneExt: context.propsValue.companyPhoneExt, + companyFaxNumber: context.propsValue.companyFaxNumber, + companyEmail: context.propsValue.companyEmail, + companyFullAddress: { + street: context.propsValue.company_street, + addressLine2: context.propsValue.company_addressLine2, + city: context.propsValue.company_city, + stateName: context.propsValue.company_stateName, + stateId: context.propsValue.company_stateId, + zip: context.propsValue.company_zip, + countryName: context.propsValue.company_countryName, + countryId: context.propsValue.company_countryId, + }, + companyWebSiteUrl: context.propsValue.companyWebSiteUrl, + companyFacebookUrl: context.propsValue.companyFacebookUrl, + companyLinkedInUrl: context.propsValue.companyLinkedInUrl, + companyInstagramUrl: context.propsValue.companyInstagramUrl, + companyTwitterUrl: context.propsValue.companyTwitterUrl, + companyGooglePlusUrl: context.propsValue.companyGooglePlusUrl, + companyCrunchbaseUrl: context.propsValue.companyCrunchbaseUrl, + companyBBBUrl: context.propsValue.companyBBBUrl, + companyCalendlyUrl: context.propsValue.companyZoomUrl, + companyZoomUrl: context.propsValue.companyCalendlyUrl, + companyLogoUrl: context.propsValue.companyLogoUrl, + workPhone1: context.propsValue.workPhone1, + workPhone1Ext: context.propsValue.workPhone1Ext, + workPhone2: context.propsValue.workPhone2, + workPhone2Ext: context.propsValue.workPhone2Ext, + workEmail1: context.propsValue.workEmail1, + workEmail2: context.propsValue.workEmail2, + workEmail3: context.propsValue.workEmail3, + workFullAddress: { + street: context.propsValue.work_street, + addressLine2: context.propsValue.work_addressLine2, + city: context.propsValue.work_city, + stateName: context.propsValue.work_stateName, + stateId: context.propsValue.work_stateId, + zip: context.propsValue.work_zip, + countryName: context.propsValue.work_countryName, + countryId: context.propsValue.work_countryId, + }, + affiliateCode: context.propsValue.companyAffiliateCode, + }, + assignedUser: context.propsValue.assignedUser, + followUpDate: context.propsValue.followUpDate, + notes: context.propsValue.notes, + dateCreated: context.propsValue.dateCreated, + leadStageName: context.propsValue.leadStageName, + leadSource: context.propsValue.leadSource, + leadDealAmount: context.propsValue.leadDealAmount, + affiliateCode: context.propsValue.affiliateCode, + campaignId: context.propsValue.campaignId, + channelId: context.propsValue.channelId, + gclId: context.propsValue.gclId, + refererUrl: context.propsValue.refererURL, + entryUrl: context.propsValue.entryUrl, + applicantId: context.propsValue.applicantId, + applicationId: context.propsValue.applicationId, + ipAddress: context.propsValue.ipAddress, + userAgent: context.propsValue.userAgent, + siteId: context.propsValue.siteId, + siteUrl: context.propsValue.siteUrl, + utmSource: context.propsValue.utmSource, + utmMedium: context.propsValue.utmMedium, + utmCampaign: context.propsValue.utmCampaign, + utmTerm: context.propsValue.utmTerm, + utmContent: context.propsValue.utmContent, + utmKeyword: context.propsValue.utmKeyword, + utmAdGroup: context.propsValue.utmAdGroup, + utmName: context.propsValue.utmName, + requestCustomInfo: { + customField1: context.propsValue.requestCustomField1, + customField2: context.propsValue.requestCustomField2, + customField3: context.propsValue.requestCustomField3, + customField4: context.propsValue.requestCustomField4, + customField5: context.propsValue.requestCustomField5, + }, + subscription1: { + productCode: context.propsValue.sub1_productCode, + paymentPeriodType: context.propsValue.sub1_paymentPeriodType, + systemType: context.propsValue.sub1_systemType, + code: context.propsValue.sub1_code, + name: context.propsValue.sub1_name, + level: context.propsValue.sub1_level, + startDate: context.propsValue.sub1_startDate, + endDate: context.propsValue.sub1_endDate, + amount: context.propsValue.sub1_amount, + }, + subscription2: { + productCode: context.propsValue.sub2_productCode, + paymentPeriodType: context.propsValue.sub2_paymentPeriodType, + systemType: context.propsValue.sub2_systemType, + code: context.propsValue.sub2_code, + name: context.propsValue.sub2_name, + level: context.propsValue.sub2_level, + startDate: context.propsValue.sub2_startDate, + endDate: context.propsValue.sub2_endDate, + amount: context.propsValue.sub2_amount, + }, + subscription3: { + productCode: context.propsValue.sub3_productCode, + paymentPeriodType: context.propsValue.sub3_paymentPeriodType, + systemType: context.propsValue.sub3_systemType, + code: context.propsValue.sub3_code, + name: context.propsValue.sub3_name, + level: context.propsValue.sub3_level, + startDate: context.propsValue.sub3_startDate, + endDate: context.propsValue.sub3_endDate, + amount: context.propsValue.sub3_amount, + }, + classificationInfo: { + rating: context.propsValue.rating, + }, + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.base_url}/api/services/CRM/Import/ImportContact`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...customer, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/upgradechat/src/lib/actions/add-or-update-contact.ts b/packages/pieces/community/upgradechat/src/lib/actions/add-or-update-contact.ts new file mode 100644 index 0000000..29931e9 --- /dev/null +++ b/packages/pieces/community/upgradechat/src/lib/actions/add-or-update-contact.ts @@ -0,0 +1,375 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { upgradechatAuth } from '../../'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const addOrUpdateContact = createAction({ + name: 'addOrUpdateContact', + displayName: 'Add or Update Contact', + description: 'Creates a new contact.', + auth: upgradechatAuth, + props: { + importType: Property.StaticDropdown({ + displayName: 'Contact Type', + required: true, + defaultValue: 'Lead', + options: { + disabled: false, + options: [ + { + label: 'Lead', + value: 'Lead', + }, + { + label: 'Client', + value: 'Client', + }, + { + label: 'Partner', + value: 'Partner', + }, + ], + }, + }), + matchExisting: Property.StaticDropdown({ + displayName: 'Match Existing Contact', + description: + 'If "Yes", will try to find an existing record using Email and Full Name and update it.', + required: false, + defaultValue: true, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + contactId: Property.Number({ + displayName: 'Contact ID', + description: 'Sperse Contact ID. Will be used for looking a client', + defaultValue: 0, + required: false, + }), + // fullname + fullName: Property.ShortText({ + displayName: 'Full Name', + description: "The contact's full name.", + required: false, + }), + namePrefix: Property.ShortText({ + displayName: 'Prefix', + description: 'The title used to address the contact.', + required: false, + }), + firstName: Property.ShortText({ + displayName: 'First Name', + description: 'Required if Last Name and Company Name fields are empty.', + required: true, + }), + middleName: Property.ShortText({ + displayName: 'Middle Name', + description: "The contact's middle name.", + required: false, + }), + lastName: Property.ShortText({ + displayName: 'Last Name', + description: 'Required if First Name and Company Name fields are empty.', + required: true, + }), + nickName: Property.ShortText({ + displayName: 'Nick Name', + description: "The contact's nick name.", + required: false, + }), + nameSuffix: Property.ShortText({ + displayName: 'Suffix', + description: 'Additional information about the contact e.g PhD.', + required: false, + }), + // personal info + gender: Property.StaticDropdown({ + displayName: 'Gender', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Male', + value: 'Male', + }, + { + label: 'Female', + value: 'Female', + }, + ], + }, + }), + dob: Property.ShortText({ + displayName: 'Date of Birth', + description: 'Valid date format YYYY-MM-DD or MM-DD-YYYY.', + required: false, + }), + bankCode: Property.ShortText({ + displayName: 'Bank Code', + description: "The contact's 4-letter personality code.", + required: false, + }), + ssn: Property.ShortText({ + displayName: 'SSN', + description: "The contact's social security number.", + required: false, + }), + // business info + companyName: Property.ShortText({ + displayName: 'Company Name', + description: + "Name of the contact's company (This field is mandatory if the First Name and Last Name fields are empty).", + required: false, + }), + jobTitle: Property.ShortText({ + displayName: 'Job Title', + description: "The contact's job title.", + required: false, + }), + industry: Property.ShortText({ + displayName: 'Industry', + description: "The company's industry.", + required: false, + }), + // email + workEmail1: Property.LongText({ + displayName: 'Work Email', + description: "The contact's work email.", + required: false, + }), + email1: Property.LongText({ + displayName: 'Personal Email', + description: "The contact's personal email.", + required: false, + }), + email2: Property.LongText({ + displayName: 'Other email', + description: "The contact's additional email.", + required: false, + }), + // phone + workPhone1: Property.ShortText({ + displayName: 'Work Phone', + description: "The contact's work/primary phone number.", + required: false, + }), + homePhone: Property.ShortText({ + displayName: 'Home Phone', + description: "The contact's home phone number.", + required: false, + }), + mobilePhone: Property.ShortText({ + displayName: 'Mobile Phone', + description: "The contact's mobile phone number.", + required: false, + }), + // links + webSiteUrl: Property.ShortText({ + displayName: 'Website', + description: "The contact's company website URL.", + required: false, + }), + linkedInUrl: Property.ShortText({ + displayName: 'LinkedIn', + description: "The contact's LinkedIn profile id.", + required: false, + }), + photoUrl: Property.ShortText({ + displayName: 'Photo URL', + description: "The contact's person photo URL.", + required: false, + }), + // Full Address + street: Property.ShortText({ + displayName: 'Street', + description: + "The contact's full street address (can include apartment or unit number).", + required: false, + }), + addressLine2: Property.ShortText({ + displayName: 'Address 2', + required: false, + }), + city: Property.ShortText({ + displayName: 'City', + description: "The contact's city of residence.", + required: false, + }), + stateName: Property.ShortText({ + displayName: 'State Name', + description: "The contact's state of residence.", + required: false, + }), + stateId: Property.ShortText({ + displayName: 'State Code', + description: "The contact's state code.", + required: false, + }), + zip: Property.ShortText({ + displayName: 'Zip Code', + description: "The contact's zip/postal code.", + required: false, + }), + countryName: Property.ShortText({ + displayName: 'Country Name', + description: "The contact's country of residence.", + required: false, + }), + countryId: Property.ShortText({ + displayName: 'Country Code', + description: "The contact's country code.", + required: false, + }), + // content + experience: Property.LongText({ + displayName: 'Content', + description: "The contact's professional experience.", + required: false, + }), + profileSummary: Property.LongText({ + displayName: 'Profile Summary', + description: "The contact's profile summary.", + required: false, + }), + notes: Property.LongText({ + displayName: 'notes', + description: 'Additional notes about the contact', + required: false, + }), + followUpDate: Property.ShortText({ + displayName: 'Follow Up Date', + description: + 'Valid date format YYYY-MM-DD HH:MM:SS. If date is defined then new Follow Up Task will be created for this contact', + required: false, + }), + assignedUser: Property.ShortText({ + displayName: 'Assigned User', + description: + 'Preferably, Sperse User Email should be passed as it is unique within Sperse account but User Name can be also passed', + required: false, + }), + leadDealAmount: Property.Number({ + displayName: 'Deal Amount', + description: 'Estimated deal/opportunity amount.', + required: false, + }), + // tracking info + leadSource: Property.ShortText({ + displayName: 'Source Code', + description: + 'The first known source the contact used to find your website. You can set this automatically and update manually later.', + required: false, + }), + channelId: Property.ShortText({ + displayName: 'Channel Code', + description: 'The channel/medium the contact used to find your website.', + required: false, + }), + affiliateCode: Property.ShortText({ + displayName: 'Affiliate Code', + description: + 'The affiliate/referer partner through which the contact signed up.', + required: false, + }), + refererURL: Property.ShortText({ + displayName: 'Referer URL', + description: + 'The webpage where the contact clicked a link that sent them to your website.', + required: false, + }), + entryUrl: Property.ShortText({ + displayName: 'Entry URL', + description: + 'The first page of visit through which the contact visited your website.', + required: false, + }), + }, + async run(context) { + const contact = { + importType: context.propsValue.importType, + matchExisting: context.propsValue.matchExisting, + contactId: context.propsValue.contactId, + personalInfo: { + fullName: { + namePrefix: context.propsValue.namePrefix, + firstName: context.propsValue.firstName, + middleName: context.propsValue.middleName, + lastName: context.propsValue.lastName, + nameSuffix: context.propsValue.nameSuffix, + nickName: context.propsValue.nickName, + }, + doB: context.propsValue.dob, + mobilePhone: context.propsValue.mobilePhone, + homePhone: context.propsValue.homePhone, + ssn: context.propsValue.ssn, + bankCode: context.propsValue.bankCode, + email1: context.propsValue.email1, + email2: context.propsValue.email2, + gender: context.propsValue.gender, + fullAddress: { + street: context.propsValue.street, + addressLine2: context.propsValue.addressLine2, + city: context.propsValue.city, + stateName: context.propsValue.stateName, + stateId: context.propsValue.stateId, + zip: context.propsValue.zip, + countryName: context.propsValue.countryName, + countryId: context.propsValue.countryId, + }, + webSiteUrl: context.propsValue.webSiteUrl, + linkedInUrl: context.propsValue.linkedInUrl, + photoUrl: context.propsValue.photoUrl, + + experience: context.propsValue.experience, + profileSummary: context.propsValue.profileSummary, + }, + + businessInfo: { + companyName: context.propsValue.companyName, + jobTitle: context.propsValue.jobTitle, + industry: context.propsValue.industry, + workPhone1: context.propsValue.workPhone1, + workEmail1: context.propsValue.workEmail1, + }, + + assignedUser: context.propsValue.assignedUser, + followUpDate: context.propsValue.followUpDate, + notes: context.propsValue.notes, + leadDealAmount: context.propsValue.leadDealAmount, + + leadSource: context.propsValue.leadSource, + channelId: context.propsValue.channelId, + affiliateCode: context.propsValue.affiliateCode, + refererUrl: context.propsValue.refererURL, + entryUrl: context.propsValue.entryUrl, + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.base_url}/api/services/CRM/Import/ImportContact`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...contact, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/upgradechat/src/lib/actions/add-or-update-subscription.ts b/packages/pieces/community/upgradechat/src/lib/actions/add-or-update-subscription.ts new file mode 100644 index 0000000..b479844 --- /dev/null +++ b/packages/pieces/community/upgradechat/src/lib/actions/add-or-update-subscription.ts @@ -0,0 +1,110 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { upgradechatAuth } from '../..'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const addOrUpdateSubscription = createAction({ + name: 'addOrUpdateSubscription', + displayName: 'Add or Update Subscription', + description: 'Creates a new subscription.', + auth: upgradechatAuth, + props: { + contactId: Property.Number({ + displayName: 'Contact ID', + defaultValue: 0, + required: false, + }), + productId: Property.Number({ + displayName: 'Product ID', + required: false, + }), + contactXref: Property.ShortText({ + displayName: 'External Contact ID', + description: + 'ContactXref have to be specified and correct to look up the correct contact', + required: false, + }), + productCode: Property.ShortText({ + displayName: 'Product Code', + description: + 'Product Code (Unique product identifier). ProductCode have to be specified and correct to look up the correct product', + required: true, + }), + paymentPeriodType: Property.StaticDropdown({ + displayName: 'Payment Period Type', + description: + 'The chosen Period Type has to be set for the Product on Sperse side', + required: true, + defaultValue: 'Monthly', + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + ], + }, + }), + hasRecurringBilling: Property.StaticDropdown({ + displayName: 'Is it Recurring Billing', + required: false, + defaultValue: false, + options: { + disabled: false, + options: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ], + }, + }), + }, + async run(context) { + const subscription = { + contactId: context.propsValue.contactId, + contactXref: context.propsValue.contactXref, + products: [ + { + productId: context.propsValue.productId, + productCode: context.propsValue.productCode, + paymentPeriodType: context.propsValue.paymentPeriodType, + hasRecurringBilling: context.propsValue.hasRecurringBilling, + }, + ], + productId: context.propsValue.productId, + productCode: context.propsValue.productCode, + paymentPeriodType: context.propsValue.paymentPeriodType, + hasRecurringBilling: context.propsValue.hasRecurringBilling, + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: `${context.auth.base_url}/api/services/CRM/OrderSubscription/Update`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...subscription, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/upgradechat/src/lib/actions/create-invoice.ts b/packages/pieces/community/upgradechat/src/lib/actions/create-invoice.ts new file mode 100644 index 0000000..016334c --- /dev/null +++ b/packages/pieces/community/upgradechat/src/lib/actions/create-invoice.ts @@ -0,0 +1,474 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { upgradechatAuth } from '../../'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +// Helper function to get current date in the required format +const getCurrentDateInISOFormat = () => { + return new Date().toISOString(); // Returns current date in format: YYYY-MM-DDTHH:MM:SSZ +}; + +export const createInvoice = createAction({ + name: 'createInvoice', + displayName: 'Create Invoice', + description: 'Creates a new invoice in the CRM.', + auth: upgradechatAuth, + props: { + contactId: Property.Number({ + displayName: 'Contact ID', + description: 'Sperse Contact ID. Will be used for looking a client', + defaultValue: 0, + required: false, + }), + contactXref: Property.ShortText({ + displayName: 'External Contact ID', + description: + 'External Contact Reference (ID) . Will be used for looking a client', + required: false, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + required: true, + defaultValue: 'Draft', + options: { + disabled: false, + options: [ + { + label: 'Draft', + value: 'Draft', + }, + { + label: 'Final', + value: 'Final', + }, + { + label: 'Paid', + value: 'Paid', + }, + { + label: 'Sent', + value: 'Sent', + }, + ], + }, + }), + invoiceNo: Property.Number({ + displayName: 'Invoice No.', + defaultValue: 0, + required: true, + }), + date: Property.DateTime({ + displayName: 'Date of the Invoice', + description: 'should be like this: 2024-06-11T11:11:41Z', + defaultValue: getCurrentDateInISOFormat(), + required: true, + }), + dueDate: Property.DateTime({ + displayName: 'Due Date of the Invoice', + description: 'should be like this: 2024-06-11T11:11:41Z', + required: true, + }), + currencyId: Property.StaticDropdown({ + displayName: 'Currency Id', + required: true, + defaultValue: 'USD', + options: { + disabled: false, + options: [ + { + label: 'USD', + value: 'USD', + }, + { + label: 'JPY', + value: 'JPY', + }, + { + label: 'IND', + value: 'IND', + }, + { + label: 'EUR', + value: 'EUR', + }, + { + label: 'GBP', + value: 'GBP', + }, + { + label: 'AUD', + value: 'AUD', + }, + { + label: 'CAD', + value: 'CAD', + }, + { + label: 'CHF', + value: 'CHF', + }, + { + label: 'CNY', + value: 'CNY', + }, + { + label: 'SEK', + value: 'SEK', + }, + { + label: 'NZD', + value: 'NZD', + }, + ], + }, + }), + grandTotal: Property.Number({ + displayName: 'Grand Total', + defaultValue: 0, + required: false, + }), + discountTotal: Property.Number({ + displayName: 'Discount Total', + defaultValue: 0, + required: false, + }), + shippingTotal: Property.Number({ + displayName: 'Shipping Total', + defaultValue: 0, + required: false, + }), + taxTotal: Property.Number({ + displayName: 'Tax Total', + defaultValue: 0, + required: false, + }), + // billing + bCompany: Property.ShortText({ + displayName: 'Billing Company', + required: false, + }), + bFirstName: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + bLastName: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + bPhone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + bEmail: Property.ShortText({ + displayName: 'Email', + required: false, + }), + bCountryId: Property.ShortText({ + displayName: 'Country Id', + required: false, + }), + bStateId: Property.ShortText({ + displayName: 'State Id', + required: false, + }), + bStateName: Property.ShortText({ + displayName: 'State Name', + required: false, + }), + bCity: Property.ShortText({ + displayName: 'City', + required: false, + }), + bZip: Property.ShortText({ + displayName: 'Zip', + required: false, + }), + bAddress1: Property.LongText({ + displayName: 'Billing Address 1', + required: false, + }), + bAddress2: Property.LongText({ + displayName: 'Billing Address 2', + required: false, + }), + // shipping + sCompany: Property.ShortText({ + displayName: 'Shipping Company', + required: false, + }), + sFirstName: Property.ShortText({ + displayName: 'First Name', + required: false, + }), + sLastName: Property.ShortText({ + displayName: 'Last Name', + required: false, + }), + sPhone: Property.ShortText({ + displayName: 'Phone', + required: false, + }), + sEmail: Property.ShortText({ + displayName: 'Email', + required: false, + }), + sCountryId: Property.ShortText({ + displayName: 'Country Id', + required: false, + }), + sStateId: Property.ShortText({ + displayName: 'State Id', + required: false, + }), + sStateName: Property.ShortText({ + displayName: 'State Name', + required: false, + }), + sCity: Property.ShortText({ + displayName: 'City', + required: false, + }), + sZip: Property.ShortText({ + displayName: 'Zip', + required: false, + }), + sAddress1: Property.LongText({ + displayName: 'Shipping Address 1', + required: false, + }), + sAddress2: Property.LongText({ + displayName: 'Shipping Address 2', + required: false, + }), + // + note: Property.LongText({ + displayName: 'Invoice Note', + required: false, + }), + invoiceDescription: Property.LongText({ + displayName: 'Invoice Description', + required: true, + }), + // line + quantity: Property.Number({ + displayName: 'Quantity', + defaultValue: 0, + required: true, + }), + rate: Property.Number({ + displayName: 'Rate', + defaultValue: 0, + required: false, + }), + itemTotal: Property.Number({ + displayName: 'Total Item Price', + defaultValue: 0, + required: false, + }), + commissionableAmount: Property.Number({ + displayName: 'Commissionable Amount', + defaultValue: 0, + required: false, + }), + unitId: Property.StaticDropdown({ + displayName: 'Unit Id', + required: false, + defaultValue: 'Unit', + options: { + disabled: false, + options: [ + { + label: 'Unit', + value: 'Unit', + }, + { + label: 'Day', + value: 'Day', + }, + { + label: 'Month', + value: 'Month', + }, + { + label: 'Year', + value: 'Year', + }, + { + label: 'Hour', + value: 'Hour', + }, + { + label: 'Kilogram', + value: 'Kilogram', + }, + { + label: 'Zone', + value: 'Zone', + }, + { + label: 'Package', + value: 'Package', + }, + { + label: 'Pound', + value: 'Pound', + }, + { + label: 'Piece', + value: 'Piece', + }, + { + label: 'Feet', + value: 'Feet', + }, + { + label: 'Custom', + value: 'Custom', + }, + ], + }, + }), + productCode: Property.ShortText({ + displayName: 'Product Code', + description: 'Product Code. We will look up the product', + required: false, + }), + itemDescription: Property.ShortText({ + displayName: 'Description', + required: false, + }), + sortOrder: Property.Number({ + displayName: 'Sort Order', + defaultValue: 0, + required: false, + }), + // transactions + transactionDate: Property.DateTime({ + displayName: 'Transaction Date', + description: 'should be like this: 2024-06-11T11:11:41Z', + defaultValue: getCurrentDateInISOFormat(), + required: true, + }), + transactionDescription: Property.ShortText({ + displayName: 'Transaction Description', + required: false, + }), + amount: Property.Number({ + displayName: 'Amount', + defaultValue: 0, + required: false, + }), + gatewayName: Property.ShortText({ + displayName: 'Gateway Name', + required: false, + }), + gatewayTransactionId: Property.ShortText({ + displayName: 'Gateway Transaction Id', + required: false, + }), + historicalData: Property.StaticDropdown({ + displayName: 'Historical Data', + description: + 'Pass true if this is not actual transaction. Should be False by default', + required: true, + defaultValue: false, + options: { + disabled: false, + options: [ + { + label: 'true', + value: true, + }, + { + label: 'false', + value: false, + }, + ], + }, + }), + }, + async run(context) { + // construct + const invoice = { + contactId: context.propsValue.contactId, + contactXref: context.propsValue.contactXref, + status: context.propsValue.status, + number: context.propsValue.invoiceNo, + date: context.propsValue.date, + dueDate: context.propsValue.dueDate, + currencyId: context.propsValue.currencyId, + grandTotal: context.propsValue.grandTotal, + discountTotal: context.propsValue.discountTotal, + shippingTotal: context.propsValue.shippingTotal, + taxTotal: context.propsValue.taxTotal, + billingAddress: { + countryId: context.propsValue.bCountryId, + stateId: context.propsValue.bStateId, + stateName: context.propsValue.bStateName, + city: context.propsValue.bCity, + zip: context.propsValue.bZip, + address1: context.propsValue.bAddress1, + address2: context.propsValue.bAddress2, + firstName: context.propsValue.bFirstName, + lastName: context.propsValue.bLastName, + company: context.propsValue.bCompany, + email: context.propsValue.bEmail, + phone: context.propsValue.bPhone, + }, + shippingAddress: { + countryId: context.propsValue.sCountryId, + stateId: context.propsValue.sStateId, + stateName: context.propsValue.sStateName, + city: context.propsValue.sCity, + zip: context.propsValue.sZip, + address1: context.propsValue.sAddress1, + address2: context.propsValue.sAddress2, + firstName: context.propsValue.sFirstName, + lastName: context.propsValue.sLastName, + company: context.propsValue.sCompany, + email: context.propsValue.sEmail, + phone: context.propsValue.sPhone, + }, + description: context.propsValue.invoiceDescription, + note: context.propsValue.note, + lines: [ + { + quantity: context.propsValue.quantity, + rate: context.propsValue.rate, + total: context.propsValue.itemTotal, + commissionableAmount: context.propsValue.commissionableAmount, + unitId: context.propsValue.unitId, + productCode: context.propsValue.productCode, + description: context.propsValue.itemDescription, + sortOrder: context.propsValue.sortOrder, + }, + ], + transactions: [ + { + date: context.propsValue.transactionDate, + description: context.propsValue.transactionDescription, + amount: context.propsValue.amount, + gatewayName: context.propsValue.gatewayName, + gatewayTransactionId: context.propsValue.gatewayTransactionId, + }, + ], + historicalData: context.propsValue.historicalData, + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.base_url}/api/services/CRM/Import/ImportInvoice`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...invoice, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/upgradechat/src/lib/actions/create-product.ts b/packages/pieces/community/upgradechat/src/lib/actions/create-product.ts new file mode 100644 index 0000000..329b853 --- /dev/null +++ b/packages/pieces/community/upgradechat/src/lib/actions/create-product.ts @@ -0,0 +1,318 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { upgradechatAuth } from '../../'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const createProduct = createAction({ + name: 'createProduct', + displayName: 'Create Product', + description: 'Creates a new product in the CRM', + auth: upgradechatAuth, + props: { + productType: Property.StaticDropdown({ + displayName: 'Product Type', + required: true, + defaultValue: 'General', + options: { + disabled: false, + options: [ + { + label: 'General', + value: 'General', + }, + { + label: 'Event', + value: 'Event', + }, + { + label: 'Subscription', + value: 'Subscription', + }, + { + label: 'Digital', + value: 'Digital', + }, + ], + }, + }), + name: Property.ShortText({ + displayName: 'Product Name', + required: true, + }), + code: Property.ShortText({ + displayName: 'SKU', + required: true, + description: 'Sku is the product code', + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + descriptionHTML: Property.LongText({ + displayName: 'Description HTML', + required: false, + description: 'Javascript and media tags are not allowed', + }), + logoURL: Property.LongText({ + displayName: 'Logo Url', + required: false, + }), + groupName: Property.ShortText({ + displayName: 'Group Name', + required: false, + }), + price: Property.Number({ + displayName: 'Price', + required: false, + description: 'Required for General , Digital and Event Product Type', + defaultValue: 0, + }), + currencyId: Property.StaticDropdown({ + displayName: 'Currency Id', + required: false, + defaultValue: 'USD', + options: { + disabled: false, + options: [ + { + label: 'USD', + value: 'USD', + }, + { + label: 'JPY', + value: 'JPY', + }, + { + label: 'IND', + value: 'IND', + }, + { + label: 'EUR', + value: 'EUR', + }, + { + label: 'GBP', + value: 'GBP', + }, + { + label: 'AUD', + value: 'AUD', + }, + { + label: 'CAD', + value: 'CAD', + }, + { + label: 'CHF', + value: 'CHF', + }, + { + label: 'CNY', + value: 'CNY', + }, + { + label: 'SEK', + value: 'SEK', + }, + { + label: 'NZD', + value: 'NZD', + }, + ], + }, + }), + unit: Property.StaticDropdown({ + displayName: 'Unit', + required: false, + description: 'Required for General and Digital Product Type', + defaultValue: 'Day', + options: { + disabled: false, + options: [ + { + label: 'Day', + value: 'Day', + }, + { + label: 'Feet', + value: 'Feet', + }, + { + label: 'Hour', + value: 'Hour', + }, + { + label: 'Kilogram', + value: 'Kilogram', + }, + { + label: 'Pound', + value: 'Pound', + }, + { + label: 'Month', + value: 'Month', + }, + { + label: 'Package', + value: 'Package', + }, + { + label: 'Piece', + value: 'Piece', + }, + { + label: 'Unit', + value: 'Unit', + }, + { + label: 'Year', + value: 'Year', + }, + { + label: 'Zone', + value: 'Zone', + }, + { + label: 'Custom', + value: 'Custom', + }, + ], + }, + }), + frequency: Property.StaticDropdown({ + displayName: 'Payment Cycle', + required: false, + description: 'Required for General and Digital Product Type', + defaultValue: 'Monthly', + options: { + disabled: false, + options: [ + { + label: 'Monthly', + value: 'Monthly', + }, + { + label: 'Annual', + value: 'Annual', + }, + { + label: 'LifeTime', + value: 'LifeTime', + }, + { + label: 'OneTime', + value: 'OneTime', + }, + { + label: 'Custom', + value: 'Custom', + }, + ], + }, + }), + fee: Property.Number({ + displayName: 'Subscription fee', + required: false, + defaultValue: 0, + }), + cycles: Property.Number({ + displayName: 'No of cycles', + required: false, + description: 'Required for all except LifeTime or OneTime plan', + defaultValue: 0, + }), + signupFee: Property.Number({ + displayName: 'Signup fee', + required: false, + description: 'Required for all except LifeTime or OneTime plan', + defaultValue: 0, + }), + customPeriodType: Property.StaticDropdown({ + displayName: 'Custom Period Type', + required: false, + description: 'Required for Custom or OneTime Plan', + defaultValue: 'Days', + options: { + disabled: false, + options: [ + { + label: 'Days', + value: 'Days', + }, + { + label: 'Weeks', + value: 'Weeks', + }, + { + label: 'Months', + value: 'Months', + }, + { + label: 'Years', + value: 'Years', + }, + ], + }, + }), + customPeriodCount: Property.Number({ + displayName: 'Custom No of Period', + required: false, + description: 'Required for Custom or OneTime Plan', + defaultValue: 0, + }), + trialDayCount: Property.Number({ + displayName: 'Custom No of Period', + required: false, + description: 'Required for OneTime plan', + defaultValue: 0, + }), + gracePeriodDayCount: Property.Number({ + displayName: 'Grace Period Count', + required: false, + defaultValue: 0, + }), + }, + async run(context) { + const product = { + code: context.propsValue.code, + name: context.propsValue.name, + logoUrl: context.propsValue.logoURL, + description: context.propsValue.description, + descriptionHtml: context.propsValue.descriptionHTML, + groupName: context.propsValue.groupName, + type: context.propsValue.productType, + price: context.propsValue.price, + currencyId: context.propsValue.currencyId, + unit: context.propsValue.unit, + productSubscriptionOptions: [ + { + frequency: context.propsValue.frequency, + signupFee: context.propsValue.signupFee, + fee: context.propsValue.fee, + trialDayCount: context.propsValue.trialDayCount, + customPeriodCount: context.propsValue.customPeriodCount, + customPeriodType: context.propsValue.customPeriodType, + cycles: context.propsValue.cycles, + gracePeriodDayCount: context.propsValue.gracePeriodDayCount, + }, + ], + }; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.base_url}/api/services/CRM/Import/ImportProduct`, + headers: { + 'api-key': context.auth.api_key, // Pass API key in headers + 'Content-Type': 'application/json', + }, + body: { + ...product, + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/upgradechat/src/lib/actions/get-contact-details.ts b/packages/pieces/community/upgradechat/src/lib/actions/get-contact-details.ts new file mode 100644 index 0000000..11ae06a --- /dev/null +++ b/packages/pieces/community/upgradechat/src/lib/actions/get-contact-details.ts @@ -0,0 +1,73 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { upgradechatAuth } from '../../'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getContactDetails = createAction({ + name: 'getContactDetails', + displayName: 'Get Contact Details', + description: 'Get Contact Details', + auth: upgradechatAuth, + props: { + contactId: Property.Number({ + displayName: 'Contact Id', + required: false, + }), + xref: Property.ShortText({ + displayName: 'Contact Xref', + required: false, + }), + affiliateCode: Property.ShortText({ + displayName: 'Affiliate Code', + required: false, + }), + userId: Property.Number({ + displayName: 'User Id', + required: false, + description: 'Id of the logged in user (not contact id)', + }), + userEmail: Property.LongText({ + displayName: 'User Email', + required: false, + description: 'Email of the logged in user', + }), + }, + async run(context) { + const contact = { + contactId: context.propsValue.contactId, + xref: context.propsValue.xref, + affiliateCode: context.propsValue.affiliateCode, + userId: context.propsValue.userId, + userEmail: context.propsValue.userEmail, + }; + + // Filter out keys with undefined values + const filteredContact: Record = Object.fromEntries( + Object.entries(contact).filter(([, value]) => value !== undefined) + ) as Record; // Cast to ensure it's the correct type + + // Create query parameters from the filtered contact object + const queryParams = new URLSearchParams( + Object.entries(filteredContact).map(([key, value]) => [ + key, + String(value), + ]) + ); + + // Send GET request with query parameters in the URL + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${ + context.auth.base_url + }/api/services/CRM/Contact/GetContactData?${queryParams.toString()}`, + headers: { + 'api-key': context.auth.api_key, + 'Content-Type': 'application/json', + }, + }); + + return { + status: res.status, + body: res.body, + }; + }, +}); diff --git a/packages/pieces/community/upgradechat/src/lib/common/common.ts b/packages/pieces/community/upgradechat/src/lib/common/common.ts new file mode 100644 index 0000000..75a7935 --- /dev/null +++ b/packages/pieces/community/upgradechat/src/lib/common/common.ts @@ -0,0 +1,46 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +export const upgradechatCommon = { + subscribeWebhook: async ( + eventName: string, + baseUrl: string, + apiKey: string, + webhookUrl: string + ) => { + const request = { + method: HttpMethod.POST, + url: `${baseUrl}/api/services/Platform/Event/Subscribe`, + headers: { + 'api-key': apiKey, + 'Content-Type': 'application/json', + }, + body: { + eventName: eventName, + targetUrl: webhookUrl, + }, + }; + + const res = await httpClient.sendRequest(request); + + const { id: webhookId } = res.body.result; + + return webhookId; + }, + + unsubscribeWebhook: async ( + baseUrl: string, + apiKey: string, + webhookId: number + ) => { + const request = { + method: HttpMethod.POST, + url: `${baseUrl}/api/services/Platform/Event/Unsubscribe?id=${webhookId}`, + headers: { + 'api-key': apiKey, + 'Content-Type': 'application/json', + }, + }; + + return await httpClient.sendRequest(request); + }, +}; diff --git a/packages/pieces/community/upgradechat/src/lib/triggers/new-lead.ts b/packages/pieces/community/upgradechat/src/lib/triggers/new-lead.ts new file mode 100644 index 0000000..f09a5b6 --- /dev/null +++ b/packages/pieces/community/upgradechat/src/lib/triggers/new-lead.ts @@ -0,0 +1,232 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { upgradechatAuth } from '../..'; +import { upgradechatCommon } from '../common/common'; + +export const newLead = createTrigger({ + auth: upgradechatAuth, + name: 'newLead', + displayName: 'New Lead', + description: 'Triggers when a new lead is created', + props: {}, + type: TriggerStrategy.WEBHOOK, + sampleData: { + id: 100500, + group: 'P', + personalInfo: { + fullName: { + namePrefix: 'Mr', + firstName: 'John', + middleName: 'G', + lastName: 'Smith', + nameSuffix: 'Jr', + nickName: 'Johnny', + }, + doB: '1992-06-30T00:00:00Z', + mobilePhone: '+12057899877', + mobilePhoneExt: '123', + homePhone: '+12057985632', + homePhoneExt: '123', + phone1: null, + phoneExt1: null, + phone2: null, + phoneExt2: null, + preferredToD: 'Anytime', + timeZone: 'Pacific Standard Time', + ssn: '123456456', + bankCode: 'BANK', + email1: 'personalemail1@gmal.com', + email2: 'personalemail2@hotmail.com', + email3: 'personalemail3@yahoo.com', + email4: null, + email5: null, + drivingLicense: 'ASDF4566G', + drivingLicenseState: 'NY', + isActiveMilitaryDuty: true, + gender: 'Male', + fullAddress: { + street: '999-901 Emancipation Ave', + addressLine2: null, + neighborhood: null, + city: 'Houston', + stateName: 'Texas', + stateId: 'TX', + zip: '77003', + countryName: 'United States of America', + countryId: 'US', + startDate: '2018-04-30T00:00:00Z', + isOwner: true, + }, + fullAddress2: null, + fullAddress3: null, + isUSCitizen: true, + webSiteUrl: 'www.myprofile.com', + facebookUrl: 'www.fb.com/j.smith', + linkedInUrl: 'https://www.linkedin.com/j.smith', + instagramUrl: 'https://www.instagram.com/j.smith', + twitterUrl: 'https://twitter.com/j.smith', + googlePlusUrl: 'https://googleplus.com/j.smith', + angelListUrl: 'https://angel.co/', + zoomUrl: 'https://zoom.com', + otherLinkUrl: 'https://otherlink.com', + photoUrl: 'https://www.myprofile.com/profile-pictures/myphoto.png', + experience: + 'Improvements made to two existing products, designed five completely new products for four customers.', + profileSummary: + 'Highly skilled and results-oriented professional with solid academic preparation holding a Juris Doctor degree and extensive experience in intelligence and special operations seeks position in risk management.', + interests: ['World Economy', 'Baseball'], + affiliateCode: 'PA001', + isActive: null, + customFields: { + customField1: null, + customField2: null, + customField3: null, + customField4: null, + customField5: null, + }, + }, + businessInfo: { + companyName: 'MyCompany LLC', + organizationType: 'Inc', + jobTitle: 'Chief Executive Officer', + isEmployed: true, + employmentStartDate: '2021-12-30T00:00:00Z', + employeeCount: 500, + dateFounded: '2021-06-30T00:00:00Z', + ein: '35-8896524', + annualRevenue: 6000000.0, + industry: 'Sport and Entertainment', + companyPhone: '+12057784563', + companyPhoneExt: '123', + companyFaxNumber: '+12057324598', + companyEmail: 'companyemail1@company.com', + companyFullAddress: { + street: '1500 Canton st', + addressLine2: null, + neighborhood: null, + city: 'Dallas', + stateName: 'Texas', + stateId: 'TX', + zip: '75201', + countryName: 'United States of America', + countryId: 'US', + startDate: '2018-04-30T00:00:00Z', + isOwner: false, + }, + companyWebSiteUrl: 'www.mycompany.com', + companyFacebookUrl: 'www.fb.com/mycompany', + companyLinkedInUrl: 'https://www.linkedin.com/mycompany', + companyInstagramUrl: 'https://www.instagram.com/mycompany', + companyTwitterUrl: 'https://twitter.com/mycompany', + companyGooglePlusUrl: 'https://googleplus.com/mycompany', + companyCrunchbaseUrl: 'https://www.crunchbase.com/mycompany', + companyBBBUrl: 'https://www.bbb.org/en/us/overview-of-bbb-ratings', + companyPinterestUrl: 'https://www.pinterest.com/mycompany', + companyDomainUrl: 'https://www.domain.com/mycompany', + companyAlexaUrl: 'https://www.alexa.com/mycompany', + companyOpenCorporatesUrl: 'https://www.opencorporates.com/mycompany', + companyGlassDoorUrl: 'https://www.classdoor.com/mycompany', + companyTrustpilotUrl: 'https://www.trustpilot.com/mycompany', + companyFollowersUrl: 'https://www.followers.com/mycompany', + companyYoutubeUrl: 'https://www.youtube.com/mycompany', + companyYelpUrl: 'https://www.yelp.com/mycompany', + companyRSSUrl: 'https://www.rss.com/mycompany', + companyNavUrl: 'https://www.nav.com/mycompany', + companyAngelListUrl: 'https://www.angelist.com/mycompany', + companyCalendlyUrl: 'https://www.calendly.com/mycompany', + companyZoomUrl: 'https://zoom.com/mycompany', + companyOtherLinkUrl: 'https://www.otherlink.com/mycompany', + companyLogoUrl: 'https://www.mycompany.com/images/companylogo/logo.png', + workPhone1: '+12057412354', + workPhone1Ext: '123', + workPhone2: '+12057741236', + workPhone2Ext: '123', + workEmail1: 'workemail1@company.com', + workEmail2: 'workemail2@company.com', + workEmail3: 'workemail3@company.com', + workFullAddress: { + street: '1502-1702 Strawberry Rd', + addressLine2: null, + neighborhood: null, + city: 'Pasadena', + stateName: 'Texas', + stateId: 'TX', + zip: '77502', + countryName: 'United States of America', + countryId: 'US', + startDate: '2020-05-30T00:00:00Z', + isOwner: false, + }, + affiliateCode: 'CA0001', + }, + dateCreated: '2022-06-23T13:32:54.9451405Z', + ipAddress: '192.168.0.1', + trackingInfo: { + sourceCode: 'Code', + channelCode: 'ChannelCode', + affiliateCode: '414CODE', + refererUrl: 'http://www.refererurl.com/referpage.html', + entryUrl: 'https://entryurl.com/start-now/?ref=wow', + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36', + clientIp: '192.168.0.1', + }, + applicationInfo: { + applicationId: '771XYZ23234G', + applicantId: '771XYZSD', + applicantUserId: 10229, + clickId: '2B69283E-9433-4CE6-A24E-D8809C050B70', + requestedLoanAmount: 89.0, + incomeType: 'Benefits', + netMonthlyIncome: 899.0, + payFrequency: 'Monthly', + payNextDate: '2022-07-30T13:32:54.945358Z', + bankName: 'Bank Name', + bankAccountType: 'Checking', + monthsAtBank: 24, + bankAccountNumber: '26005325777412', + creditScoreRating: 'Excellent', + loanReason: 'AutoPurchase', + creditCardDebtAmount: 56898.0, + }, + classificationInfo: { + rating: '10', + lists: ['My List 1', 'My List 2', 'My List 3'], + tags: ['My Tag 1', 'My Tag 2', 'My Tag 3'], + partnerTypeName: 'My Partner Type', + }, + eventTime: '2022-06-30T13:32:54Z', + }, + + async onEnable(context) { + const webhookId = await upgradechatCommon.subscribeWebhook( + 'LeadCreated', + context.auth.base_url, + context.auth.api_key, + context.webhookUrl + ); + + await context.store?.put('_new_lead_trigger', { + webhookId: webhookId, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_new_lead_trigger' + ); + + if (response !== null && response !== undefined) { + await upgradechatCommon.unsubscribeWebhook( + context.auth.base_url, + context.auth.api_key, + response.webhookId + ); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); + +interface WebhookInformation { + webhookId: number; +} diff --git a/packages/pieces/community/upgradechat/src/lib/triggers/new-payment.ts b/packages/pieces/community/upgradechat/src/lib/triggers/new-payment.ts new file mode 100644 index 0000000..03b0d99 --- /dev/null +++ b/packages/pieces/community/upgradechat/src/lib/triggers/new-payment.ts @@ -0,0 +1,85 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { upgradechatAuth } from '../..'; +import { upgradechatCommon } from '../common/common'; + +export const newPayment = createTrigger({ + auth: upgradechatAuth, + name: 'newPayment', + displayName: 'New Payment', + description: 'Triggers when a new payment is created', + props: {}, + type: TriggerStrategy.WEBHOOK, + sampleData: { + contact: { + email: 's@gmail.com', + fullName: 'Test User', + id: 636, + phone: '98877676565', + }, + invoice: { + currencyId: 'USD', + date: '2024-06-10T00:00:00Z', + description: 'just a description', + discountTotal: 0, + grandTotal: 100, + id: 457, + number: 'INV - 20240610 - 746C', + shippingTotal: 0, + taxTotal: 0, + lines: [ + { + description: 'Me Spacial', + productId: 810, + quantity: 1, + rate: 100, + total: 100, + unitId: 'Month', + }, + ], + }, + transaction: { + amount: 100, + currencyId: 'USD', + date: '2024-06-10T09:36:01.832Z', + gatewayName: null, + gatewayTransactionId: null, + id: 403, + isSuccessful: true, + type: 'Sale', + }, + eventTime: '2024-06-10T09:36:08', + eventType: 'Payment.Created', + }, + async onEnable(context) { + const webhookId = await upgradechatCommon.subscribeWebhook( + 'Payment.Created', + context.auth.base_url, + context.auth.api_key, + context.webhookUrl + ); + + await context.store?.put('_new_payment_trigger', { + webhookId: webhookId, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_new_payment_trigger' + ); + + if (response !== null && response !== undefined) { + await upgradechatCommon.unsubscribeWebhook( + context.auth.base_url, + context.auth.api_key, + response.webhookId + ); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); + +interface WebhookInformation { + webhookId: number; +} diff --git a/packages/pieces/community/upgradechat/src/lib/triggers/new-subscription.ts b/packages/pieces/community/upgradechat/src/lib/triggers/new-subscription.ts new file mode 100644 index 0000000..e270b71 --- /dev/null +++ b/packages/pieces/community/upgradechat/src/lib/triggers/new-subscription.ts @@ -0,0 +1,61 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { upgradechatAuth } from '../..'; +import { upgradechatCommon } from '../common/common'; + +export const newSubscription = createTrigger({ + auth: upgradechatAuth, + name: 'newSubscription', + displayName: 'New Subscription', + description: 'triggers when a new subscription is created', + props: {}, + type: TriggerStrategy.WEBHOOK, + sampleData: { + eventType: 'Subscription.CreatedOrUpdated', + contactId: 3994, + fullname: 'Frank Micheal', + email: 'tray@sperse.com', + id: '536778', + name: 'Subscription_Name', + startDate: '2024-06-30T09:29:43.6271352Z', + endDate: '2025-06-30T09:29:43.6271352Z', + amount: 100, + frequency: 'Annual', + trialDayCount: '4', + gracePeriodCount: '10', + statusId: 'A', + cancelationReason: '', + eventTime: '2024-09-06T00:29:07', + }, + async onEnable(context) { + const webhookId = await upgradechatCommon.subscribeWebhook( + 'Subscription.CreatedOrUpdated', + context.auth.base_url, + context.auth.api_key, + context.webhookUrl + ); + + await context.store?.put('_new_subscription_trigger', { + webhookId: webhookId, + }); + }, + async onDisable(context) { + const response = await context.store?.get( + '_new_subscription_trigger' + ); + + if (response !== null && response !== undefined) { + await upgradechatCommon.unsubscribeWebhook( + context.auth.base_url, + context.auth.api_key, + response.webhookId + ); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); + +interface WebhookInformation { + webhookId: number; +} diff --git a/packages/pieces/community/upgradechat/tsconfig.json b/packages/pieces/community/upgradechat/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/upgradechat/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/upgradechat/tsconfig.lib.json b/packages/pieces/community/upgradechat/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/upgradechat/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/utility-ai/.eslintrc.json b/packages/pieces/community/utility-ai/.eslintrc.json new file mode 100644 index 0000000..eac708a --- /dev/null +++ b/packages/pieces/community/utility-ai/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/utility-ai/README.md b/packages/pieces/community/utility-ai/README.md new file mode 100644 index 0000000..263e79c --- /dev/null +++ b/packages/pieces/community/utility-ai/README.md @@ -0,0 +1,7 @@ +# pieces-utility-ai + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-utility-ai` to build the library. diff --git a/packages/pieces/community/utility-ai/package.json b/packages/pieces/community/utility-ai/package.json new file mode 100644 index 0000000..bc695c3 --- /dev/null +++ b/packages/pieces/community/utility-ai/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-utility-ai", + "version": "0.4.3" +} diff --git a/packages/pieces/community/utility-ai/project.json b/packages/pieces/community/utility-ai/project.json new file mode 100644 index 0000000..ee69ad2 --- /dev/null +++ b/packages/pieces/community/utility-ai/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-utility-ai", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/utility-ai/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/utility-ai", + "tsConfig": "packages/pieces/community/utility-ai/tsconfig.lib.json", + "packageJson": "packages/pieces/community/utility-ai/package.json", + "main": "packages/pieces/community/utility-ai/src/index.ts", + "assets": [ + "packages/pieces/community/utility-ai/*.md", + { + "input": "packages/pieces/community/utility-ai/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/utility-ai/src/index.ts b/packages/pieces/community/utility-ai/src/index.ts new file mode 100644 index 0000000..cba6ff9 --- /dev/null +++ b/packages/pieces/community/utility-ai/src/index.ts @@ -0,0 +1,19 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { extractStructuredData } from './lib/actions/extract-structured-data'; +import { classifyText } from './lib/actions/classify-text'; +import { checkModeration } from './lib/actions/check-moderation'; + +export const aiUtility = createPiece({ + displayName: 'Utility AI', + auth: PieceAuth.None(), + categories: [ + PieceCategory.ARTIFICIAL_INTELLIGENCE, + PieceCategory.UNIVERSAL_AI, + ], + minimumSupportedRelease: '0.63.0', + logoUrl: 'https://cdn.activepieces.com/pieces/ai-utility.svg', + authors: ['kishanprmr', 'amrdb'], + actions: [checkModeration, classifyText, extractStructuredData], + triggers: [], +}); diff --git a/packages/pieces/community/utility-ai/src/lib/actions/check-moderation.ts b/packages/pieces/community/utility-ai/src/lib/actions/check-moderation.ts new file mode 100644 index 0000000..1fd42e1 --- /dev/null +++ b/packages/pieces/community/utility-ai/src/lib/actions/check-moderation.ts @@ -0,0 +1,125 @@ +import { ApFile, createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { AIProviderWithoutSensitiveData, isNil, SeekPage } from '@activepieces/shared'; +import OpenAI from 'openai'; +import { ModerationMultiModalInput } from 'openai/resources/moderations'; + +export const checkModeration = createAction({ + name: 'checkModeration', + displayName: 'Check Moderation', + description: + 'Classifies if text or image contains hate, hate/threatening, self-harm, sexual, sexual/minors, violence, or violence/graphic content.', + props: { + provider: Property.Dropdown({ + displayName: 'Provider', + required: true, + refreshers: [], + options: async (_, ctx) => { + const { body: { data: supportedProviders } } = await httpClient.sendRequest< + SeekPage + >({ + method: HttpMethod.GET, + url: `${ctx.server.apiUrl}v1/ai-providers`, + headers: { + Authorization: `Bearer ${ctx.server.token}`, + }, + }); + + const openaiProvider = supportedProviders.find(provider => provider.provider === 'openai'); + + return { + placeholder: openaiProvider ? 'Select AI Provider' : `No OpenAI providers available for moderation`, + disabled: !openaiProvider, + options: openaiProvider ? [ + { + value: openaiProvider.provider, + label: openaiProvider.provider + } + ] : [], + }; + }, + }), + model: Property.Dropdown({ + displayName: 'Model', + required: true, + defaultValue: 'gpt-4o', + refreshers: ['provider'], + options: async (propsValue) => { + const provider = propsValue['provider'] as string; + if (isNil(provider)) { + return { + disabled: true, + options: [], + placeholder: 'Select AI Provider', + }; + } + + const openaiModerationModels = [ + { + label: 'omni-moderation-latest', + value: 'omni-moderation-latest', + }, + ]; + + return { + placeholder: 'Select AI Model', + disabled: false, + options: openaiModerationModels, + }; + }, + }), + text: Property.LongText({ + displayName: 'Text', + required: false, + }), + images: Property.Array({ + displayName: 'Images', + required: false, + properties: { + file: Property.File({ + displayName: 'Image File', + required: true, + }), + }, + }), + }, + async run(context) { + const providerName = context.propsValue.provider as string; + const text = context.propsValue.text; + const images = (context.propsValue.images as Array<{ file: ApFile }>) ?? []; + + const baseURL = `${context.server.apiUrl}v1/ai-providers/proxy/${providerName}/v1`; + const engineToken = context.server.token; + + if (!text && !images.length) { + throw new Error('Please provide text or images to check moderation'); + } + + const client = new OpenAI({ + apiKey: engineToken, + baseURL, + }); + + const input: ModerationMultiModalInput[] = []; + + if (text) { + input.push({ type: 'text', text }); + } + + for (const image of images) { + input.push({ + type: 'image_url', + image_url: { + url: `data:image/${image.file.extension};base64,${image.file.base64}`, + }, + }); + } + + const moderation = await client.moderations.create({ + input, + model: context.propsValue.model, + }); + + return moderation.results[0]; + }, +}); diff --git a/packages/pieces/community/utility-ai/src/lib/actions/classify-text.ts b/packages/pieces/community/utility-ai/src/lib/actions/classify-text.ts new file mode 100644 index 0000000..a52e2a3 --- /dev/null +++ b/packages/pieces/community/utility-ai/src/lib/actions/classify-text.ts @@ -0,0 +1,63 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { aiProps } from '@activepieces/pieces-common'; +import { generateText, LanguageModel } from 'ai'; +import { createAIProvider, SUPPORTED_AI_PROVIDERS } from '@activepieces/shared'; + +export const classifyText = createAction({ + name: 'classifyText', + displayName: 'Classify Text', + description: 'Classify your text into one of your provided categories.', + props: { + provider: aiProps({ modelType: 'language' }).provider, + model: aiProps({ modelType: 'language' }).model, + text: Property.LongText({ + displayName: 'Text to Classify', + required: true, + }), + categories: Property.Array({ + displayName: 'Categories', + description: 'Categories to classify text into.', + required: true, + }), + }, + async run(context) { + const categories = (context.propsValue.categories as string[]) ?? []; + + const providerName = context.propsValue.provider as string; + const modelInstance = context.propsValue.model as LanguageModel; + + const providerConfig = SUPPORTED_AI_PROVIDERS.find(p => p.provider === providerName); + if (!providerConfig) { + throw new Error(`Provider ${providerName} not found`); + } + + const baseURL = `${context.server.apiUrl}v1/ai-providers/proxy/${providerName}`; + const engineToken = context.server.token; + const provider = createAIProvider({ + providerName, + modelInstance, + apiKey: engineToken, + baseURL, + }); + + const response = await generateText({ + model: provider, + prompt: `As a text classifier, your task is to assign one of the following categories to the provided text: ${categories.join( + ', ' + )}. Please respond with only the selected category as a single word, and nothing else. + Text to classify: "${context.propsValue.text}"`, + headers: { + 'Authorization': `Bearer ${engineToken}`, + }, + }); + const result = response.text.trim(); + + if (!categories.includes(result)) { + throw new Error( + 'Unable to classify the text into the provided categories.' + ); + } + + return result; + }, +}); diff --git a/packages/pieces/community/utility-ai/src/lib/actions/extract-structured-data.ts b/packages/pieces/community/utility-ai/src/lib/actions/extract-structured-data.ts new file mode 100644 index 0000000..0a9310a --- /dev/null +++ b/packages/pieces/community/utility-ai/src/lib/actions/extract-structured-data.ts @@ -0,0 +1,269 @@ +import { ApFile, createAction, Property } from '@activepieces/pieces-framework'; +import { createAIProvider, MarkdownVariant } from '@activepieces/shared'; +import { aiProps } from '@activepieces/pieces-common'; +import { generateText, tool, LanguageModel, jsonSchema, CoreMessage, CoreUserMessage } from 'ai'; +import mime from 'mime-types'; +import Ajv from 'ajv'; + +export const extractStructuredData = createAction({ + name: 'extractStructuredData', + displayName: 'Extract Structured Data', + description: 'Extract structured data from provided text,image or PDF.', + props: { + provider: aiProps({ modelType: 'language', functionCalling: true }).provider, + model: aiProps({ modelType: 'language', functionCalling: true }).model, + text: Property.LongText({ + displayName: 'Text', + description: 'Text to extract structured data from.', + required: false, + }), + files: Property.Array({ + displayName: 'Files', + required: false, + properties: { + file: Property.File({ + displayName: 'Image/PDF', + description: 'Image or PDF to extract structured data from.', + required: false, + }), + }, + }), + prompt: Property.LongText({ + displayName: 'Guide Prompt', + description: 'Prompt to guide the AI.', + defaultValue: 'Extract the following data from the provided data.', + required: false, + }), + mode: Property.StaticDropdown<'simple' | 'advanced'>({ + displayName: 'Data Schema Type', + description: 'For complex schema, you can use advanced mode.', + required: true, + defaultValue: 'simple', + options: { + disabled: false, + options: [ + { label: 'Simple', value: 'simple' }, + { label: 'Advanced', value: 'advanced' }, + ], + }, + }), + schama: Property.DynamicProperties({ + displayName: 'Data Definition', + required: true, + refreshers: ['mode'], + props: async (propsValue) => { + const mode = propsValue['mode'] as unknown as 'simple' | 'advanced'; + if (mode === 'advanced') { + return { + fields: Property.Json({ + displayName: 'JSON Schema', + description: + 'Learn more about JSON Schema here: https://json-schema.org/learn/getting-started-step-by-step', + required: true, + defaultValue: { + type: 'object', + properties: { + name: { + type: 'string', + }, + age: { + type: 'number', + }, + }, + required: ['name'], + }, + }), + }; + } + return { + fields: Property.Array({ + displayName: 'Data Definition', + required: true, + properties: { + name: Property.ShortText({ + displayName: 'Name', + description: + 'Provide the name of the value you want to extract from the unstructured text. The name should be unique and short. ', + required: true, + }), + description: Property.LongText({ + displayName: 'Description', + description: + 'Brief description of the data, this hints for the AI on what to look for', + required: false, + }), + type: Property.StaticDropdown({ + displayName: 'Data Type', + description: 'Type of parameter.', + required: true, + defaultValue: 'string', + options: { + disabled: false, + options: [ + { label: 'Text', value: 'string' }, + { label: 'Number', value: 'number' }, + { label: 'Boolean', value: 'boolean' }, + ], + }, + }), + isRequired: Property.Checkbox({ + displayName: 'Fail if Not present?', + required: true, + defaultValue: false, + }), + }, + }), + }; + }, + }), + maxTokens: Property.Number({ + displayName: 'Max Tokens', + required: false, + defaultValue: 2000, + }), + }, + async run(context) { + const providerName = context.propsValue.provider as string; + const modelInstance = context.propsValue.model as LanguageModel; + const text = context.propsValue.text; + const files = (context.propsValue.files as Array<{ file: ApFile }>) ?? []; + const prompt = context.propsValue.prompt; + const schema = context.propsValue.schama; + const maxTokens = context.propsValue.maxTokens; + + if (!text && !files.length) { + throw new Error('Please provide text or image/PDF to extract data from.'); + } + + const baseURL = `${context.server.apiUrl}v1/ai-providers/proxy/${providerName}`; + const engineToken = context.server.token; + const provider = createAIProvider({ + providerName, + modelInstance, + apiKey: engineToken, + baseURL, + }); + + let schemaDefinition: any; + + if (context.propsValue.mode === 'advanced') { + const ajv = new Ajv(); + const isValidSchema = ajv.validateSchema(schema['fields']); + + if (!isValidSchema) { + throw new Error( + JSON.stringify({ + message: 'Invalid JSON schema', + errors: ajv.errors, + }), + ); + } + + schemaDefinition = jsonSchema(schema['fields'] as any); + } else { + const fields = schema['fields'] as Array<{ + name: string; + description?: string; + type: string; + isRequired: boolean; + }>; + + const properties: Record = {}; + const required: string[] = []; + + fields.forEach((field) => { + properties[field.name] = { + type: field.type, + description: field.description, + }; + + if (field.isRequired) { + required.push(field.name); + } + }); + + const jsonSchemaObject = { + type: 'object' as const, + properties, + required, + }; + + schemaDefinition = jsonSchema(jsonSchemaObject); + } + + const extractionTool = tool({ + description: 'Extract structured data from the provided content', + parameters: schemaDefinition, + execute: async (data) => { + return data; + }, + }); + + const messages: Array = []; + + // Prepare content parts array + const contentParts: CoreUserMessage['content']= []; + + // Add the main prompt message + let textContent = prompt || 'Extract the following data from the provided data.'; + if (text) { + textContent += `\n\nText to analyze:\n${text}`; + } + + contentParts.push({ + type: 'text', + text: textContent, + }); + + // Handle file processing similar to previous implementation + if (files.length > 0) { + for (const fileWrapper of files) { + const file = fileWrapper.file; + if (!file) { + continue; + } + const fileType = file.extension ? mime.lookup(file.extension) : 'image/jpeg'; + + if (fileType && fileType.startsWith('image') && file.base64) { + contentParts.push({ + type: 'image', + image: `data:${fileType};base64,${file.base64}`, + }); + } + } + } + + // Add the message with all content parts + messages.push({ + role: 'user', + content: contentParts, + }); + + try { + // Use Vercel AI SDK to generate text with tool calling + const result = await generateText({ + model: provider, + maxTokens, + tools: { + extractData: extractionTool, + }, + toolChoice: 'required', + messages, + }); + + // Extract the tool call result + const toolCalls = result.toolCalls; + if (!toolCalls || toolCalls.length === 0) { + throw new Error('No structured data could be extracted from the input.'); + } + + const extractedData = toolCalls[0].args; + return extractedData; + + } catch (error) { + throw new Error(`Failed to extract structured data: ${error instanceof Error ? error.message : 'Unknown error'}`); + } + }, +}); + + diff --git a/packages/pieces/community/utility-ai/tsconfig.json b/packages/pieces/community/utility-ai/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/utility-ai/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/utility-ai/tsconfig.lib.json b/packages/pieces/community/utility-ai/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/utility-ai/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/vbout/.eslintrc.json b/packages/pieces/community/vbout/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/vbout/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/vbout/README.md b/packages/pieces/community/vbout/README.md new file mode 100644 index 0000000..85915d9 --- /dev/null +++ b/packages/pieces/community/vbout/README.md @@ -0,0 +1,7 @@ +# pieces-vbout + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-vbout` to build the library. diff --git a/packages/pieces/community/vbout/package.json b/packages/pieces/community/vbout/package.json new file mode 100644 index 0000000..1976504 --- /dev/null +++ b/packages/pieces/community/vbout/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-vbout", + "version": "0.0.4" +} \ No newline at end of file diff --git a/packages/pieces/community/vbout/project.json b/packages/pieces/community/vbout/project.json new file mode 100644 index 0000000..0800068 --- /dev/null +++ b/packages/pieces/community/vbout/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-vbout", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/vbout/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/vbout", + "tsConfig": "packages/pieces/community/vbout/tsconfig.lib.json", + "packageJson": "packages/pieces/community/vbout/package.json", + "main": "packages/pieces/community/vbout/src/index.ts", + "assets": [ + "packages/pieces/community/vbout/*.md", + { + "input": "packages/pieces/community/vbout/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-vbout {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/vbout/src/index.ts b/packages/pieces/community/vbout/src/index.ts new file mode 100644 index 0000000..336dc28 --- /dev/null +++ b/packages/pieces/community/vbout/src/index.ts @@ -0,0 +1,64 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { addContactAction } from './lib/actions/add-contact'; +import { addTagToContactAction } from './lib/actions/add-tag-to-contact'; +import { createEmailMarketingCampaignAction } from './lib/actions/create-campaign'; +import { createEmailListAction } from './lib/actions/create-email-list'; +import { createSocialMediaMessageAction } from './lib/actions/create-social-media-message'; +import { getContactByEmailAction } from './lib/actions/get-contact-by-email'; +import { getEmailListAction } from './lib/actions/get-email-list'; +import { removeTagFromContactAction } from './lib/actions/remove-tag-from-contact'; +import { unsubscribeContactAction } from './lib/actions/unsubscribe-contact'; +import { updateContactAction } from './lib/actions/update-contact'; +import { makeClient } from './lib/common'; + +const markdown = ` +To obtain your API key, follow these steps: + +1.Go to **settings** by clicking your profile-pic (top-right).\n +2.Navigate to **API Integrations** section.\n +3.Under **API USER KEY** ,copy API key.\n +`; + +export const vboutAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: markdown, + async validate({ auth }) { + const client = makeClient(auth as string); + try { + await client.validateAuth(); + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid API key.', + }; + } + }, +}); + +export const vbout = createPiece({ + displayName: 'VBOUT', + description: 'Marketing automation platform for agencies', + auth: vboutAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/vbout.png', + authors: ["kishanprmr","abuaboud"], + categories: [PieceCategory.MARKETING], + actions: [ + addContactAction, + addTagToContactAction, + createEmailListAction, + createEmailMarketingCampaignAction, + createSocialMediaMessageAction, + getContactByEmailAction, + getEmailListAction, + removeTagFromContactAction, + unsubscribeContactAction, + updateContactAction, + ], + triggers: [], +}); diff --git a/packages/pieces/community/vbout/src/lib/actions/add-contact.ts b/packages/pieces/community/vbout/src/lib/actions/add-contact.ts new file mode 100644 index 0000000..acce58f --- /dev/null +++ b/packages/pieces/community/vbout/src/lib/actions/add-contact.ts @@ -0,0 +1,27 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { vboutAuth } from '../..'; +import { makeClient, vboutCommon } from '../common'; + +export const addContactAction = createAction({ + auth: vboutAuth, + name: 'vbout_add_contact', + displayName: 'Add Contact to List', + description: 'Adds a contact to a given email list.', + props: { + email: Property.ShortText({ + displayName: 'Email Address', + required: true, + }), + listid: vboutCommon.listid(true), + status: vboutCommon.contactStatus(true), + ipaddress: Property.ShortText({ + displayName: 'IP Address', + required: false, + }), + fields: vboutCommon.listFields, + }, + async run(context) { + const client = makeClient(context.auth as string); + return await client.addContact(context.propsValue); + }, +}); diff --git a/packages/pieces/community/vbout/src/lib/actions/add-tag-to-contact.ts b/packages/pieces/community/vbout/src/lib/actions/add-tag-to-contact.ts new file mode 100644 index 0000000..2796200 --- /dev/null +++ b/packages/pieces/community/vbout/src/lib/actions/add-tag-to-contact.ts @@ -0,0 +1,28 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { vboutAuth } from '../..'; +import { makeClient } from '../common'; + +export const addTagToContactAction = createAction({ + auth: vboutAuth, + name: 'vbout_add_tag', + displayName: 'Add Tag to Contact', + description: 'Adds the tag to the contact.', + props: { + email: Property.ShortText({ + displayName: 'Email Address', + required: true, + }), + tagname: Property.Array({ + displayName: 'Tag Name', + required: true, + }), + }, + async run(context) { + const { email, tagname } = context.propsValue; + const client = makeClient(context.auth as string); + return await client.addTagToContact({ + email: email, + tagname: tagname as string[], + }); + }, +}); diff --git a/packages/pieces/community/vbout/src/lib/actions/create-campaign.ts b/packages/pieces/community/vbout/src/lib/actions/create-campaign.ts new file mode 100644 index 0000000..e73744b --- /dev/null +++ b/packages/pieces/community/vbout/src/lib/actions/create-campaign.ts @@ -0,0 +1,94 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { vboutAuth } from '../..'; +import { makeClient } from '../common'; + +export const createEmailMarketingCampaignAction = createAction({ + auth: vboutAuth, + name: 'vbout_add_email_marketing_campaign', + displayName: 'Create Email Marketing Campaign', + description: 'Creates a new email campaign for specific list.', + props: { + lists: Property.MultiSelectDropdown({ + displayName: 'Campaign Recipients List', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + const client = makeClient(auth as string); + const res = await client.listEmailLists(); + return { + disabled: false, + options: res.lists.items.map((list) => { + return { + label: list.name, + value: list.id, + }; + }), + }; + }, + }), + name: Property.ShortText({ + displayName: 'Campaign Name', + required: true, + }), + subject: Property.ShortText({ + displayName: 'Campaign Subject', + required: true, + }), + type: Property.StaticDropdown({ + displayName: 'Campaign Type', + required: true, + defaultValue: 'standard', + options: { + disabled: false, + options: [ + { + label: 'Standard', + value: 'standard', + }, + { + label: 'Automated', + value: 'automated', + }, + ], + }, + }), + fromemail: Property.ShortText({ + displayName: 'From Email', + required: true, + }), + from_name: Property.ShortText({ + displayName: 'From Name', + required: true, + }), + reply_to: Property.ShortText({ + displayName: 'Reply To', + required: true, + }), + body: Property.LongText({ + displayName: 'Message Body', + required: true, + }), + }, + async run(context) { + const { lists, name, from_name, fromemail, reply_to, subject, body, type } = + context.propsValue; + const client = makeClient(context.auth as string); + return await client.addCampaign({ + lists: lists.join(','), + name, + from_name, + fromemail, + reply_to, + subject, + body, + type, + }); + }, +}); diff --git a/packages/pieces/community/vbout/src/lib/actions/create-email-list.ts b/packages/pieces/community/vbout/src/lib/actions/create-email-list.ts new file mode 100644 index 0000000..85fdf0b --- /dev/null +++ b/packages/pieces/community/vbout/src/lib/actions/create-email-list.ts @@ -0,0 +1,71 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { vboutAuth } from '../..'; +import { makeClient } from '../common'; + +export const createEmailListAction = createAction({ + auth: vboutAuth, + name: 'vbout_create_email_list', + displayName: 'Create Email List', + description: 'Creates a new email list.', + props: { + name: Property.ShortText({ + required: true, + displayName: 'Name', + description: 'The name of the list.', + }), + email_subject: Property.LongText({ + required: false, + displayName: 'Email Subject', + description: 'The default subscription subject.', + }), + reply_to: Property.ShortText({ + required: false, + displayName: 'Reply To', + description: 'The Reply to email of the list.', + }), + fromemail: Property.ShortText({ + required: false, + displayName: 'From Email', + description: 'The From email of the list.', + }), + from_name: Property.ShortText({ + required: false, + displayName: 'From Name', + description: 'The From name of the list.', + }), + notify_email: Property.ShortText({ + required: false, + displayName: 'Notify Email', + description: 'Notification Email.', + }), + success_email: Property.ShortText({ + required: false, + displayName: 'Success Email', + description: 'Subscription Success Email.', + }), + success_message: Property.ShortText({ + required: false, + displayName: 'Success Message', + description: 'Subscription Success Message.', + }), + error_message: Property.ShortText({ + required: false, + displayName: 'Error Message', + description: 'Subscription Error Message.', + }), + confirmation_email: Property.ShortText({ + required: false, + displayName: 'Confirmation Email', + description: 'Confirmation Email Message.', + }), + confirmation_message: Property.ShortText({ + required: false, + displayName: 'Confirmation Message', + }), + }, + + async run({ auth, propsValue }) { + const client = makeClient(auth as string); + return await client.createEmailList(propsValue); + }, +}); diff --git a/packages/pieces/community/vbout/src/lib/actions/create-social-media-message.ts b/packages/pieces/community/vbout/src/lib/actions/create-social-media-message.ts new file mode 100644 index 0000000..04365ed --- /dev/null +++ b/packages/pieces/community/vbout/src/lib/actions/create-social-media-message.ts @@ -0,0 +1,21 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { vboutAuth } from '../../'; +import { makeClient, vboutCommon } from '../common'; +export const createSocialMediaMessageAction = createAction({ + auth: vboutAuth, + name: 'vbout_create_social_media_message', + displayName: 'Create Social Media Message', + description: 'Post a message to one of your social media channel.', + props: { + message: Property.LongText({ + displayName: 'Message', + required: true, + }), + channel: vboutCommon.socialMediaChannel, + channelid: vboutCommon.socialMediaProfile, + }, + async run(context) { + const client = makeClient(context.auth as string); + return await client.createSocialMediaPost(context.propsValue); + }, +}); diff --git a/packages/pieces/community/vbout/src/lib/actions/get-contact-by-email.ts b/packages/pieces/community/vbout/src/lib/actions/get-contact-by-email.ts new file mode 100644 index 0000000..6164f28 --- /dev/null +++ b/packages/pieces/community/vbout/src/lib/actions/get-contact-by-email.ts @@ -0,0 +1,23 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { vboutAuth } from '../..'; +import { makeClient, vboutCommon } from '../common'; + +export const getContactByEmailAction = createAction({ + auth: vboutAuth, + name: 'vbout_get_contact_by_email', + displayName: 'Get Contact by Email', + description: 'Retrieves the contact by email.', + props: { + listid: vboutCommon.listid(false), + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth as string); + const { listid, email } = propsValue; + + return await client.getContactByEmail(email, listid); + }, +}); diff --git a/packages/pieces/community/vbout/src/lib/actions/get-email-list.ts b/packages/pieces/community/vbout/src/lib/actions/get-email-list.ts new file mode 100644 index 0000000..4b4394f --- /dev/null +++ b/packages/pieces/community/vbout/src/lib/actions/get-email-list.ts @@ -0,0 +1,19 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { vboutAuth } from '../..'; +import { makeClient, vboutCommon } from '../common'; + +export const getEmailListAction = createAction({ + auth: vboutAuth, + name: 'vbout_get_email_list', + displayName: 'Get List Details with Custom Fields', + description: 'Retrieves specific list details with custom fields.', + props: { + listid: vboutCommon.listid(true), + }, + async run({ auth, propsValue }) { + const client = makeClient(auth as string); + const listId = propsValue.listid!; + + return await client.getEmailList(listId); + }, +}); diff --git a/packages/pieces/community/vbout/src/lib/actions/remove-tag-from-contact.ts b/packages/pieces/community/vbout/src/lib/actions/remove-tag-from-contact.ts new file mode 100644 index 0000000..37a2505 --- /dev/null +++ b/packages/pieces/community/vbout/src/lib/actions/remove-tag-from-contact.ts @@ -0,0 +1,26 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { vboutAuth } from '../..'; +import { makeClient } from '../common'; + +export const removeTagFromContactAction = createAction({ + auth: vboutAuth, + name: 'vbout_remove_tag', + displayName: 'Remove Tags from Contact', + description: 'Removes tags from an existing contact.', + props: { + email: Property.ShortText({ + displayName: 'Email Address', + required: true, + }), + tagname: Property.ShortText({ + displayName: 'Tag Name', + required: true, + description: `use comma for multiple tag e.g. **tag1,tag2**.`, + }), + }, + async run(context) { + const { email, tagname } = context.propsValue; + const client = makeClient(context.auth as string); + return await client.removeTagFromContact(email, tagname); + }, +}); diff --git a/packages/pieces/community/vbout/src/lib/actions/unsubscribe-contact.ts b/packages/pieces/community/vbout/src/lib/actions/unsubscribe-contact.ts new file mode 100644 index 0000000..f7e3023 --- /dev/null +++ b/packages/pieces/community/vbout/src/lib/actions/unsubscribe-contact.ts @@ -0,0 +1,38 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { vboutAuth } from '../..'; +import { makeClient, vboutCommon } from '../common'; +import { ContactStatusValues } from '../common/models'; + +export const unsubscribeContactAction = createAction({ + auth: vboutAuth, + name: 'vbout_unsubscribe_contact', + displayName: 'Unsubscribe Contact', + description: 'Unsubscribes an existing contact in a given email list.', + props: { + email: Property.ShortText({ + displayName: 'Contact Email', + required: true, + description: 'Contact email for update.', + }), + listid: vboutCommon.listid(true), + }, + async run(context) { + const client = makeClient(context.auth as string); + const { email, listid } = context.propsValue; + const res = await client.getContactByEmail( + email as string, + listid as string + ); + const contact = res.response.data.contact; + + if ('errorCode' in contact) { + return res; + } else { + const contactId = contact[0].id; + return await client.updateContact({ + id: contactId, + status: ContactStatusValues.UNSUBSCRIBE, + }); + } + }, +}); diff --git a/packages/pieces/community/vbout/src/lib/actions/update-contact.ts b/packages/pieces/community/vbout/src/lib/actions/update-contact.ts new file mode 100644 index 0000000..b00f472 --- /dev/null +++ b/packages/pieces/community/vbout/src/lib/actions/update-contact.ts @@ -0,0 +1,40 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { vboutAuth } from '../..'; +import { makeClient, vboutCommon } from '../common'; + +export const updateContactAction = createAction({ + auth: vboutAuth, + name: 'vbout_update_contact', + displayName: 'Update Contact', + description: 'Updates a contact in a given email list.', + props: { + email: Property.ShortText({ + displayName: 'Contact Email', + required: true, + description: 'Contact email for update.', + }), + listid: vboutCommon.listid(true), + status: vboutCommon.contactStatus(false), + ipaddress: Property.ShortText({ + displayName: 'IP Address', + required: false, + }), + fields: vboutCommon.listFields, + }, + async run(context) { + const client = makeClient(context.auth as string); + const { email } = context.propsValue; + const res = await client.getContactByEmail(email as string); + const contact = res.response.data.contact; + + if ('errorCode' in contact) { + return res; + } else { + const contactId = contact[0].id; + return await client.updateContact({ + id: contactId, + ...context.propsValue, + }); + } + }, +}); diff --git a/packages/pieces/community/vbout/src/lib/common/client.ts b/packages/pieces/community/vbout/src/lib/common/client.ts new file mode 100644 index 0000000..c5956ec --- /dev/null +++ b/packages/pieces/community/vbout/src/lib/common/client.ts @@ -0,0 +1,170 @@ +import { + HttpMessageBody, + HttpMethod, + QueryParams, + httpClient, +} from '@activepieces/pieces-common'; +import { vboutCommon } from '.'; +import { + CampaignCreateRequest, + ContactCreateRequest, + ContactList, + ContactUpdateRequest, + EmailListCreateRequest, + SocialMediaChannelListResponse, + SocialMediaPostCreateRequest, + TagCreateRequest, + VboutResponseBody, +} from './models'; + +function emptyValueFilter( + accessor: (key: string) => any +): (key: string) => boolean { + return (key: string) => { + const val = accessor(key); + return ( + val !== null && + val !== undefined && + (typeof val != 'string' || val.length > 0) + ); + }; +} + +export function prepareQuery(request?: Record): QueryParams { + const params: QueryParams = {}; + if (!request) return params; + Object.keys(request) + .filter(emptyValueFilter((k) => request[k])) + .forEach((k: string) => { + params[k] = (request as Record)[k].toString(); + }); + return params; +} +export class VboutClient { + constructor(private apiKey: string) {} + + async makeRequest( + method: HttpMethod, + url: string, + query?: QueryParams, + body?: object + ): Promise { + const res = await httpClient.sendRequest({ + method, + url: vboutCommon.baseUrl + url, + queryParams: { key: this.apiKey, ...query }, + body, + }); + return res.body; + } + + async validateAuth() { + return await this.makeRequest(HttpMethod.GET, '/app/me'); + } + + async listEmailLists() { + return ( + await this.makeRequest< + VboutResponseBody<{ + lists: { + count: number; + items: ContactList[]; + }; + }> + >(HttpMethod.GET, '/emailmarketing/getlists') + ).response.data; + } + + async getContactByEmail(email: string, listId?: string) { + return await this.makeRequest< + VboutResponseBody<{ + contact: { + id: string; + email: string; + listid: string; + list_name: string; + [key: string]: any; + }[]; + }> + >( + HttpMethod.GET, + '/emailmarketing/getcontactbyemail', + prepareQuery({ email: email, listid: listId }) + ); + } + + async getEmailList(listId: string) { + return await this.makeRequest>( + HttpMethod.GET, + '/emailmarketing/getlist', + prepareQuery({ id: listId }) + ); + } + async createEmailList(request: EmailListCreateRequest) { + return await this.makeRequest( + HttpMethod.POST, + '/emailMarketing/AddList', + undefined, + request + ); + } + async addContact(request: ContactCreateRequest) { + return await this.makeRequest( + HttpMethod.POST, + '/emailMarketing/AddContact', + undefined, + request + ); + } + async updateContact(request: ContactUpdateRequest) { + return await this.makeRequest( + HttpMethod.POST, + '/emailMarketing/EditContact', + undefined, + request + ); + } + async addTagToContact(request: TagCreateRequest) { + return await this.makeRequest( + HttpMethod.POST, + '/emailMarketing/AddTag', + undefined, + request + ); + } + async removeTagFromContact(email: string, tagname: string) { + return await this.makeRequest( + HttpMethod.DELETE, + '/emailMarketing/RemoveTag', + prepareQuery({ + email, + tagname, + }) + ); + } + + async addCampaign(request: CampaignCreateRequest) { + return await this.makeRequest( + HttpMethod.POST, + '/emailMarketing/AddCampaign', + undefined, + request + ); + } + async listSocialMediaChannels() { + return ( + await this.makeRequest< + VboutResponseBody<{ channels: SocialMediaChannelListResponse }> + >(HttpMethod.GET, '/socialMedia/Channels') + ).response.data; + } + + async createSocialMediaPost(request: SocialMediaPostCreateRequest) { + return await this.makeRequest( + HttpMethod.POST, + '/socialMedia/AddPost', + undefined, + request + ); + } +} diff --git a/packages/pieces/community/vbout/src/lib/common/index.ts b/packages/pieces/community/vbout/src/lib/common/index.ts new file mode 100644 index 0000000..7d9b3cf --- /dev/null +++ b/packages/pieces/community/vbout/src/lib/common/index.ts @@ -0,0 +1,171 @@ +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { VboutClient } from './client'; +import { + ContactStatusValues, + SocialMediaChannelValues, + SocialMediaProfile, +} from './models'; +export function makeClient(apiKey: string): VboutClient { + return new VboutClient(apiKey); +} + +export const vboutCommon = { + baseUrl: 'https://api.vbout.com/1', + listid: (required = true) => + Property.Dropdown({ + displayName: 'List ID', + required: required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + const client = makeClient(auth as string); + const res = await client.listEmailLists(); + return { + disabled: false, + options: res.lists.items.map((list) => { + return { + label: list.name, + value: list.id, + }; + }), + }; + }, + }), + listFields: Property.DynamicProperties({ + displayName: 'Fields', + required: true, + refreshers: ['listid'], + props: async ({ auth, listid }) => { + if (!auth || !listid) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account and select Email List.', + }; + } + const fields: DynamicPropsValue = {}; + const client = makeClient(auth as unknown as string); + const contactList = await client.getEmailList( + listid as unknown as string + ); + const contactListFields = contactList.response.data.list.fields; + Object.keys(contactListFields).forEach((key) => { + fields[key] = Property.ShortText({ + displayName: contactListFields[key], + required: false, + }); + }); + return fields; + }, + }), + contactStatus: (required = true) => + Property.StaticDropdown({ + displayName: 'Contact Status', + required: required, + options: { + disabled: false, + options: [ + { + label: 'Unconfirmed', + value: ContactStatusValues.UNCONFIRMED, + }, + { + label: 'Active', + value: ContactStatusValues.ACTIVE, + }, + { + label: 'Unsubscribe', + value: ContactStatusValues.UNSUBSCRIBE, + }, + { + label: 'Bounced Email', + value: ContactStatusValues.BOUNCED_EMAIL, + }, + ], + }, + }), + socialMediaChannel: Property.StaticDropdown({ + displayName: 'Social Media Channel', + required: true, + options: { + disabled: false, + options: [ + { + label: 'Twitter', + value: SocialMediaChannelValues.TWITTER, + }, + { + label: 'LinkedIn', + value: SocialMediaChannelValues.LINKEDIN, + }, + { + label: 'Facebook', + value: SocialMediaChannelValues.FACEBOOK, + }, + ], + }, + }), + socialMediaProfile: Property.Dropdown({ + displayName: 'Social Media Account', + required: true, + refreshers: ['channel'], + options: async ({ auth, channel }) => { + if (!auth || !channel) { + return { + disabled: true, + options: [], + placeholder: + 'Please connect your account and select social media channel.', + }; + } + const client = makeClient(auth as string); + const { channels } = await client.listSocialMediaChannels(); + let options: { label: string; value: string }[] = []; + switch (channel as string) { + case SocialMediaChannelValues.TWITTER: { + options = [ + ...options, + ...mapSocialMediaProfile(channels.Twitter.profiles), + ]; + break; + } + case SocialMediaChannelValues.FACEBOOK: { + options = [ + ...options, + ...mapSocialMediaProfile(channels.Facebook.pages), + ]; + break; + } + case SocialMediaChannelValues.LINKEDIN: { + options = [ + ...options, + ...mapSocialMediaProfile(channels.Linkedin.companies), + ...mapSocialMediaProfile(channels.Linkedin.profiles), + ]; + break; + } + } + return { + disabled: false, + options: options, + }; + }, + }), +}; + +function mapSocialMediaProfile( + profiles: SocialMediaProfile[] +): { label: string; value: string }[] { + return profiles.map((profile) => { + return { + label: profile.name, + value: profile.id, + }; + }); +} diff --git a/packages/pieces/community/vbout/src/lib/common/models.ts b/packages/pieces/community/vbout/src/lib/common/models.ts new file mode 100644 index 0000000..0cb7004 --- /dev/null +++ b/packages/pieces/community/vbout/src/lib/common/models.ts @@ -0,0 +1,122 @@ +import { HttpMessageBody } from '@activepieces/pieces-common'; +export interface VboutResponseBody extends HttpMessageBody { + response: { + header: { + status: string; + dataType: string; + limit: string; + cached?: string; + }; + data: T; + }; +} +export interface EmailListCreateRequest { + name: string; + email_subject?: string; + reply_to?: string; + fromemail?: string; + from_name?: string; + doubleOption?: string; + notify?: string; + notify_email?: string; + success_email?: string; + success_message?: string; + error_message?: string; + confirmation_email?: string; + confirmation_message?: string; + communications?: boolean; +} +export interface ContactList { + id: string; + name: string; + form_title: string; + email_subject: string; + reply_to: string; + from_email: string; + from_name: string; + confirmation_email: string; + success_email: string; + confirmation_message: string; + success_message: string; + error_message: string; + doubleOption: string; + notify_email: string; + creation_date: string; + fields: { + [key: string]: string; + }; +} + +export interface ContactCreateRequest { + listid?: string; + status?: string; + email: string; + ipaddress?: string; + fields?: { + [key: string]: string; + }; +} + +export interface ContactUpdateRequest { + id: string; + listid?: string; + status?: string; + email?: string; + ipaddress?: string; + fields?: { + [key: string]: string; + }; +} + +export interface TagCreateRequest { + email: string; + tagname: string[]; +} + +export interface CampaignCreateRequest { + name: string; + subject: string; + fromemail: string; + from_name: string; + reply_to: string; + body: string; + type: string; + lists: string; +} +export interface SocialMediaProfile { + id: string; + name: string; +} +export interface SocialMediaChannelListResponse { + Facebook: { + count: number; + pages: SocialMediaProfile[]; + }; + Twitter: { + count: number; + profiles: SocialMediaProfile[]; + }; + Linkedin: { + count: number; + profiles: SocialMediaProfile[]; + companies: SocialMediaProfile[]; + }; +} + +export interface SocialMediaPostCreateRequest { + message: string; + channel: string; + channelid: string; +} + +export enum SocialMediaChannelValues { + TWITTER = 'twitter', + LINKEDIN = 'linkedin', + FACEBOOK = 'facebook', +} +export enum ContactStatusValues { + UNCONFIRMED = '0', + ACTIVE = '1', + UNSUBSCRIBE = '2', + BOUNCED_EMAIL = '3', +} diff --git a/packages/pieces/community/vbout/tsconfig.json b/packages/pieces/community/vbout/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/vbout/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/vbout/tsconfig.lib.json b/packages/pieces/community/vbout/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/vbout/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/village/.eslintrc.json b/packages/pieces/community/village/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/village/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/village/README.md b/packages/pieces/community/village/README.md new file mode 100644 index 0000000..029d460 --- /dev/null +++ b/packages/pieces/community/village/README.md @@ -0,0 +1,7 @@ +# pieces-village + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-village` to build the library. diff --git a/packages/pieces/community/village/package.json b/packages/pieces/community/village/package.json new file mode 100644 index 0000000..3df7e5f --- /dev/null +++ b/packages/pieces/community/village/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-village", + "version": "0.0.3" +} \ No newline at end of file diff --git a/packages/pieces/community/village/project.json b/packages/pieces/community/village/project.json new file mode 100644 index 0000000..8c1100e --- /dev/null +++ b/packages/pieces/community/village/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-village", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/village/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/village", + "tsConfig": "packages/pieces/community/village/tsconfig.lib.json", + "packageJson": "packages/pieces/community/village/package.json", + "main": "packages/pieces/community/village/src/index.ts", + "assets": [ + "packages/pieces/community/village/*.md", + { + "input": "packages/pieces/community/village/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/village/src/index.ts b/packages/pieces/community/village/src/index.ts new file mode 100644 index 0000000..4f34601 --- /dev/null +++ b/packages/pieces/community/village/src/index.ts @@ -0,0 +1,25 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { fetchPeoplePaths } from './lib/actions/fetch-people-paths'; +import { fetchCompaniesPaths } from './lib/actions/fetch-companies-paths'; + +export const villageAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'Your Village API Key', +}); + +export const village = createPiece({ + displayName: 'Village', + description: 'The Social Capital API', + auth: villageAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/village.png', + categories: [ + PieceCategory.PRODUCTIVITY, + PieceCategory.SALES_AND_CRM, + ], + authors: ['rafaelmuttoni'], + actions: [fetchPeoplePaths, fetchCompaniesPaths], + triggers: [], +}); diff --git a/packages/pieces/community/village/src/lib/actions/fetch-companies-paths.ts b/packages/pieces/community/village/src/lib/actions/fetch-companies-paths.ts new file mode 100644 index 0000000..80bf87c --- /dev/null +++ b/packages/pieces/community/village/src/lib/actions/fetch-companies-paths.ts @@ -0,0 +1,47 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { villageAuth } from '../..'; + +export const fetchCompaniesPaths = createAction({ + name: 'fetchCompaniesPaths', + auth: villageAuth, + displayName: 'Find Company Paths', + description: + 'Enrich a company record with all people that have available intro paths', + props: { + user_identifier: Property.LongText({ + displayName: 'User Identifier', + description: `If you're a Village Partner, use this field that identifies your user`, + required: false, + }), + company_domain_url: Property.LongText({ + displayName: 'Company Domain URL', + description: `The Company Domain URL of the company you're trying to find paths to. E.g. https://openai.com/`, + required: false, + }), + company_linkedin_url: Property.LongText({ + displayName: 'Company Linkedin URL', + description: `The Company Linkedin URL of the company you're trying to find paths to. E.g. https://www.linkedin.com/company/openai/`, + required: false, + }), + }, + async run(context) { + const { company_domain_url, company_linkedin_url, user_identifier } = + context.propsValue; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.village.do/v1/companies/paths', + headers: { + 'X-Village-Secret-Key': context.auth, // Pass API key in headers + }, + body: { + user_identifier, + company_domain_url, + company_linkedin_url, + }, + }); + return res.body; + }, +}); diff --git a/packages/pieces/community/village/src/lib/actions/fetch-people-paths.ts b/packages/pieces/community/village/src/lib/actions/fetch-people-paths.ts new file mode 100644 index 0000000..c77e3de --- /dev/null +++ b/packages/pieces/community/village/src/lib/actions/fetch-people-paths.ts @@ -0,0 +1,39 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { villageAuth } from '../..'; + +export const fetchPeoplePaths = createAction({ + name: 'fetchPeoplePaths', + auth: villageAuth, + displayName: 'Find Person Paths', + description: 'Enrich a person record with all available intro paths', + props: { + user_identifier: Property.LongText({ + displayName: 'User Identifier', + description: `If you're a Village Partner, use this field that identifies your user`, + required: false, + }), + person_linkedin_url: Property.LongText({ + displayName: 'Person Linkedin URL', + description: `The Linkedin URL of the person you're trying to find paths to.`, + required: true, + }), + }, + async run(context) { + const { person_linkedin_url, user_identifier } = context.propsValue; + + const res = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.village.do/v1/people/paths', + headers: { + 'X-Village-Secret-Key': context.auth, // Pass API key in headers + }, + body: { + person_linkedin_url, + user_identifier, + }, + }); + return res.body; + }, +}); diff --git a/packages/pieces/community/village/tsconfig.json b/packages/pieces/community/village/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/village/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/village/tsconfig.lib.json b/packages/pieces/community/village/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/village/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/vtex/.eslintrc.json b/packages/pieces/community/vtex/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/vtex/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/vtex/README.md b/packages/pieces/community/vtex/README.md new file mode 100644 index 0000000..8aae1be --- /dev/null +++ b/packages/pieces/community/vtex/README.md @@ -0,0 +1,7 @@ +# pieces-vtex + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-vtex` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/vtex/package.json b/packages/pieces/community/vtex/package.json new file mode 100644 index 0000000..75aaa71 --- /dev/null +++ b/packages/pieces/community/vtex/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-vtex", + "version": "0.1.3" +} \ No newline at end of file diff --git a/packages/pieces/community/vtex/project.json b/packages/pieces/community/vtex/project.json new file mode 100644 index 0000000..cf499a8 --- /dev/null +++ b/packages/pieces/community/vtex/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-vtex", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/vtex/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/vtex", + "tsConfig": "packages/pieces/community/vtex/tsconfig.lib.json", + "packageJson": "packages/pieces/community/vtex/package.json", + "main": "packages/pieces/community/vtex/src/index.ts", + "assets": [ + "packages/pieces/community/vtex/*.md", + { + "input": "packages/pieces/community/vtex/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/vtex/src/index.ts b/packages/pieces/community/vtex/src/index.ts new file mode 100644 index 0000000..ba910b6 --- /dev/null +++ b/packages/pieces/community/vtex/src/index.ts @@ -0,0 +1,90 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createBrand } from './lib/actions/Brand/create-brand'; +import { deleteBrand } from './lib/actions/Brand/delete-brand'; +import { getBrandById } from './lib/actions/Brand/get-brand-by-id'; +import { getBrandList } from './lib/actions/Brand/get-brand-list'; +import { updateBrand } from './lib/actions/Brand/update-brand'; +import { getCategoryById } from './lib/actions/Category/get-category-by-id'; +import { getClientById } from './lib/actions/Client/get-client-by-id'; +import { getClientList } from './lib/actions/Client/get-client-list'; +import { getOrderById } from './lib/actions/Order/get-order-by-id'; +import { getOrderList } from './lib/actions/Order/get-order-list'; +import { createProduct } from './lib/actions/Product/create-product'; +import { getProductById } from './lib/actions/Product/get-product-by-id'; +import { updateProduct } from './lib/actions/Product/update-product'; +import { createSkuFile } from './lib/actions/SKU-File/create-sku-file'; +import { createSku } from './lib/actions/SKU/create-sku'; +import { getSkuByProductId } from './lib/actions/SKU/get-sku-by-product-id'; + +const markdownDescription = ` +**Host Url**: The VTEX store host (e.g \`{{accountName}}.{{environment}}.com\`) +**App Key** and **App Token**: To get your app key and app token, follow the steps below: +1. Go to your vtex account on **Account Settings** -> **Account** -> **Security** +2. Click on **Generate access key and secret** +4. Copy the access key as your **App Key** and the secret is your **App Token**. +`; + +export const vtexAuth = PieceAuth.CustomAuth({ + description: markdownDescription, + props: { + hostUrl: Property.ShortText({ + displayName: 'Host Url', + description: '{accountName}.{environment}.com', + required: true, + }), + appKey: PieceAuth.SecretText({ + displayName: 'App Key', + description: 'VTEX App Key', + required: true, + }), + appToken: PieceAuth.SecretText({ + displayName: 'App Token', + description: 'VTEX App Token', + required: true, + }), + }, + required: true, +}); + +export const vtex = createPiece({ + displayName: 'VTEX', + description: 'Unified commerce platform', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/vtex.png', + categories: [PieceCategory.COMMERCE], + authors: ["Willianwg","kishanprmr","MoShizzle","AbdulTheActivePiecer","khaledmashaly","abuaboud"], + auth: vtexAuth, + actions: [ + getProductById, + createProduct, + updateProduct, + getBrandList, + getBrandById, + createBrand, + updateBrand, + deleteBrand, + getCategoryById, + getSkuByProductId, + createSku, + createSkuFile, + getClientList, + getClientById, + getOrderById, + getOrderList, + createCustomApiCallAction({ + baseUrl: (auth) => `https://${(auth as { hostUrl: string }).hostUrl}`, + auth: vtexAuth, + authMapping: async (auth) => ({ + 'X-VTEX-API-AppKey': (auth as { appKey: string }).appKey, + 'X-VTEX-API-AppToken': (auth as { appToken: string }).appToken, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/vtex/src/lib/actions/Brand/create-brand.ts b/packages/pieces/community/vtex/src/lib/actions/Brand/create-brand.ts new file mode 100644 index 0000000..46db667 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/actions/Brand/create-brand.ts @@ -0,0 +1,73 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Brand } from '../../common/Brand'; +import { Replace } from '../../common/types'; +import { vtexAuth } from '../../..'; + +export const createBrand = createAction({ + auth: vtexAuth, + name: 'create-brand', + displayName: 'Create new Brand', + description: 'Create a new Brand to your catalog', + props: { + Name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + SiteTitle: Property.ShortText({ + displayName: 'Site Title', + required: false, + }), + LinkId: Property.ShortText({ + displayName: 'Link ID', + required: false, + }), + Id: Property.Number({ + displayName: 'Brand ID', + description: 'Set the brand ID', + required: false, + }), + Text: Property.ShortText({ + displayName: 'Text', + required: false, + }), + MenuHome: Property.Checkbox({ + displayName: 'Menu Home', + required: false, + }), + Keywords: Property.ShortText({ + displayName: 'Keywords', + description: 'Similar words', + required: false, + }), + Active: Property.Checkbox({ + displayName: 'Active', + required: false, + }), + Score: Property.Number({ + displayName: 'Score', + required: false, + }), + }, + async run(context) { + const { hostUrl, appKey, appToken } = context.auth; + const brandData: Replace< + typeof context.propsValue, + { authentication?: typeof context.auth } + > = { ...context.propsValue }; + delete brandData.authentication; + + const brand = new Brand(hostUrl, appKey, appToken); + + return await brand.createBrand({ + Id: context.propsValue.Id, + Name: context.propsValue.Name, + Text: context.propsValue.Text, + Keywords: context.propsValue.Keywords, + SiteTitle: context.propsValue.SiteTitle, + Active: context.propsValue.Active, + MenuHome: context.propsValue.MenuHome, + Score: context.propsValue.Score, + LinkId: context.propsValue.LinkId, + }); + }, +}); diff --git a/packages/pieces/community/vtex/src/lib/actions/Brand/delete-brand.ts b/packages/pieces/community/vtex/src/lib/actions/Brand/delete-brand.ts new file mode 100644 index 0000000..02435b3 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/actions/Brand/delete-brand.ts @@ -0,0 +1,25 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Brand } from '../../common/Brand'; +import { vtexAuth } from '../../..'; + +export const deleteBrand = createAction({ + auth: vtexAuth, + name: 'delete-brand', + displayName: 'Delete Brand', + description: "Delete a Brand in your catalog by it's id", + props: { + brandId: Property.Number({ + displayName: 'Brand ID', + description: 'The Brand ID', + required: true, + }), + }, + async run(context) { + const { hostUrl, appKey, appToken } = context.auth; + const { brandId } = context.propsValue; + + const brand = new Brand(hostUrl, appKey, appToken); + + return await brand.deleteBrand(brandId); + }, +}); diff --git a/packages/pieces/community/vtex/src/lib/actions/Brand/get-brand-by-id.ts b/packages/pieces/community/vtex/src/lib/actions/Brand/get-brand-by-id.ts new file mode 100644 index 0000000..55f7265 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/actions/Brand/get-brand-by-id.ts @@ -0,0 +1,25 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Brand } from '../../common/Brand'; +import { vtexAuth } from '../../..'; + +export const getBrandById = createAction({ + auth: vtexAuth, + name: 'get-brand-by-id', + displayName: 'Get Brand By ID', + description: "Find a Brand in your catalog by it's id", + props: { + BrandId: Property.Number({ + displayName: 'Brand ID', + description: 'The Brand ID', + required: true, + }), + }, + async run(context) { + const { hostUrl, appKey, appToken } = context.auth; + const { BrandId } = context.propsValue; + + const brand = new Brand(hostUrl, appKey, appToken); + + return await brand.getBrandById(BrandId); + }, +}); diff --git a/packages/pieces/community/vtex/src/lib/actions/Brand/get-brand-list.ts b/packages/pieces/community/vtex/src/lib/actions/Brand/get-brand-list.ts new file mode 100644 index 0000000..691cfc5 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/actions/Brand/get-brand-list.ts @@ -0,0 +1,18 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Brand } from '../../common/Brand'; +import { vtexAuth } from '../../..'; + +export const getBrandList = createAction({ + auth: vtexAuth, + name: 'get-brand-list', + displayName: 'Get Brand List', + description: 'Find all Brands in your catalog', + props: {}, + async run(context) { + const { hostUrl, appKey, appToken } = context.auth; + + const brand = new Brand(hostUrl, appKey, appToken); + + return await brand.getBrandList(); + }, +}); diff --git a/packages/pieces/community/vtex/src/lib/actions/Brand/update-brand.ts b/packages/pieces/community/vtex/src/lib/actions/Brand/update-brand.ts new file mode 100644 index 0000000..20562e4 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/actions/Brand/update-brand.ts @@ -0,0 +1,81 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Brand } from '../../common/Brand'; +import { Replace } from '../../common/types'; +import { vtexAuth } from '../../..'; + +export const updateBrand = createAction({ + auth: vtexAuth, + name: 'update-brand', + displayName: 'Update Brand', + description: 'Update a Brand in your catalog', + props: { + Id: Property.Number({ + displayName: 'Brand ID', + description: 'Set the brand ID', + required: true, + }), + Name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + SiteTitle: Property.ShortText({ + displayName: 'Site Title', + required: true, + }), + LinkId: Property.ShortText({ + displayName: 'Link ID', + required: true, + }), + Text: Property.ShortText({ + displayName: 'Text', + required: true, + }), + MenuHome: Property.Checkbox({ + displayName: 'Menu Home', + required: true, + }), + Keywords: Property.ShortText({ + displayName: 'Keywords', + description: 'Similar words', + required: true, + }), + Active: Property.Checkbox({ + displayName: 'Active', + required: true, + }), + Score: Property.Number({ + displayName: 'Score', + required: true, + }), + }, + async run(context) { + const { hostUrl, appKey, appToken } = context.auth; + const { + Id, + Name, + SiteTitle, + LinkId, + Text, + MenuHome, + Keywords, + Active, + Score, + } = context.propsValue; + + const brandData = { + Id, + Name, + SiteTitle, + LinkId, + Text, + MenuHome, + Keywords, + Active, + Score, + }; + + const brand = new Brand(hostUrl, appKey, appToken); + + return await brand.updateBrand(Id, brandData); + }, +}); diff --git a/packages/pieces/community/vtex/src/lib/actions/Category/get-category-by-id.ts b/packages/pieces/community/vtex/src/lib/actions/Category/get-category-by-id.ts new file mode 100644 index 0000000..e656d0c --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/actions/Category/get-category-by-id.ts @@ -0,0 +1,25 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Category } from '../../common/Category'; +import { vtexAuth } from '../../..'; + +export const getCategoryById = createAction({ + auth: vtexAuth, + name: 'get-category-by-id', + displayName: 'Get Category', + description: "Find a Category in your catalog by it's id", + props: { + categoryId: Property.Number({ + displayName: 'Category ID', + description: 'The Category ID', + required: false, + }), + }, + async run(context) { + const { hostUrl, appKey, appToken } = context.auth; + const { categoryId } = context.propsValue; + + const category = new Category(hostUrl, appKey, appToken); + + return await category.getCategory(categoryId); + }, +}); diff --git a/packages/pieces/community/vtex/src/lib/actions/Client/get-client-by-id.ts b/packages/pieces/community/vtex/src/lib/actions/Client/get-client-by-id.ts new file mode 100644 index 0000000..a64b802 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/actions/Client/get-client-by-id.ts @@ -0,0 +1,25 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '../../common/Client'; +import { vtexAuth } from '../../..'; + +export const getClientById = createAction({ + auth: vtexAuth, + name: 'get-client-by-id', + displayName: 'Get Client By ID', + description: 'Find a Client by Id', + props: { + clientId: Property.Number({ + displayName: 'Client ID', + description: 'The Client ID', + required: true, + }), + }, + async run(context) { + const { hostUrl, appKey, appToken } = context.auth; + const { clientId } = context.propsValue; + + const client = new Client(hostUrl, appKey, appToken); + + return await client.getClientById(clientId); + }, +}); diff --git a/packages/pieces/community/vtex/src/lib/actions/Client/get-client-list.ts b/packages/pieces/community/vtex/src/lib/actions/Client/get-client-list.ts new file mode 100644 index 0000000..a45d397 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/actions/Client/get-client-list.ts @@ -0,0 +1,18 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { Client } from '../../common/Client'; +import { vtexAuth } from '../../..'; + +export const getClientList = createAction({ + auth: vtexAuth, + name: 'get-client-list', + displayName: 'Get Client List', + description: 'Find all Clients', + props: {}, + async run(context) { + const { hostUrl, appKey, appToken } = context.auth; + + const client = new Client(hostUrl, appKey, appToken); + + return await client.getClientList(); + }, +}); diff --git a/packages/pieces/community/vtex/src/lib/actions/Order/get-order-by-id.ts b/packages/pieces/community/vtex/src/lib/actions/Order/get-order-by-id.ts new file mode 100644 index 0000000..1777287 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/actions/Order/get-order-by-id.ts @@ -0,0 +1,25 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Order } from '../../common/Order'; +import { vtexAuth } from '../../..'; + +export const getOrderById = createAction({ + auth: vtexAuth, + name: 'get-order-by-id', + displayName: 'Get Order By ID', + description: 'Find a Order by Id', + props: { + OrderId: Property.Number({ + displayName: 'Order ID', + description: 'The Order ID', + required: true, + }), + }, + async run(context) { + const { hostUrl, appKey, appToken } = context.auth; + const { OrderId } = context.propsValue; + + const order = new Order(hostUrl, appKey, appToken); + + return await order.getOrderById(OrderId); + }, +}); diff --git a/packages/pieces/community/vtex/src/lib/actions/Order/get-order-list.ts b/packages/pieces/community/vtex/src/lib/actions/Order/get-order-list.ts new file mode 100644 index 0000000..9c5c9e8 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/actions/Order/get-order-list.ts @@ -0,0 +1,57 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { Order } from '../../common/Order'; +import { vtexAuth } from '../../..'; + +const year = new Date().getFullYear().toString(); + +export const getOrderList = createAction({ + auth: vtexAuth, + name: 'get-order-list', + displayName: 'Get Orders List', + description: 'Find Orders', + props: { + fromYear: Property.Number({ + displayName: 'From (Year)', + required: true, + }), + toYear: Property.ShortText({ + displayName: 'To (Year)', + defaultValue: year, + required: true, + }), + fromMonth: Property.ShortText({ + displayName: 'From (Month)', + defaultValue: '01', + required: false, + }), + toMonth: Property.ShortText({ + displayName: 'To (Month)', + defaultValue: '12', + required: false, + }), + fromDay: Property.ShortText({ + displayName: 'From (Day)', + defaultValue: '01', + required: false, + }), + toDay: Property.ShortText({ + displayName: 'To (Day)', + defaultValue: '28', + required: false, + }), + }, + async run(context) { + const { hostUrl, appKey, appToken } = context.auth; + const { fromYear, toYear, fromMonth, toMonth, fromDay, toDay } = + context.propsValue; + + const order = new Order(hostUrl, appKey, appToken); + + const fromDate = new Date( + `${fromYear}-${fromMonth || '01'}-${fromDay || '01'}` + ); + const toDate = new Date(`${toYear}-${toMonth || '12'}-${toDay || '28'}`); + + return await order.getOrderList(fromDate, toDate); + }, +}); diff --git a/packages/pieces/community/vtex/src/lib/actions/Product/create-product.ts b/packages/pieces/community/vtex/src/lib/actions/Product/create-product.ts new file mode 100644 index 0000000..128b0c7 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/actions/Product/create-product.ts @@ -0,0 +1,110 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Product } from '../../common/Product'; +import { vtexAuth } from '../../..'; + +export const createProduct = createAction({ + auth: vtexAuth, + name: 'create-product', + displayName: 'Create New Product', + description: 'Create a new product in your catalog', + props: { + Name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + Title: Property.ShortText({ + displayName: 'Title', + required: true, + }), + Description: Property.ShortText({ + displayName: 'Description', + required: true, + }), + BrandId: Property.Number({ + displayName: 'Brand ID', + required: true, + }), + CategoryId: Property.Number({ + displayName: 'Category ID', + required: true, + }), + LinkId: Property.ShortText({ + displayName: 'Link ID', + required: false, + }), + RefId: Property.ShortText({ + displayName: 'Ref ID', + required: false, + }), + Id: Property.Number({ + displayName: 'Product ID', + description: 'Set the product ID', + required: false, + }), + IsVisible: Property.Checkbox({ + displayName: 'Is Visible', + required: false, + }), + DescriptionShort: Property.ShortText({ + displayName: 'Short Description', + required: false, + }), + ReleaseDate: Property.ShortText({ + displayName: 'Release Date', + required: false, + }), + KeyWords: Property.ShortText({ + displayName: 'Key Words', + description: 'Similar words', + required: false, + }), + IsActive: Property.Checkbox({ + displayName: 'Is Active', + required: false, + }), + TaxCode: Property.ShortText({ + displayName: 'Tax Code', + required: false, + }), + MetaTagDescription: Property.ShortText({ + displayName: 'Metatag description', + required: false, + }), + ShowWithoutStock: Property.Checkbox({ + displayName: 'Show Without Stock', + required: false, + }), + Score: Property.Number({ + displayName: 'Score', + required: false, + }), + }, + async run(context) { + const { hostUrl, appKey, appToken } = context.auth; + + const product = new Product(hostUrl, appKey, appToken); + + const productData = { + Name: context.propsValue.Name, + Title: context.propsValue.Title, + Description: context.propsValue.Description, + BrandId: context.propsValue.BrandId, + CategoryId: context.propsValue.CategoryId, + LinkId: context.propsValue.LinkId, + RefId: context.propsValue.RefId, + Id: context.propsValue.Id, + IsVisible: context.propsValue.IsVisible, + DescriptionShort: context.propsValue.DescriptionShort, + ReleaseDate: context.propsValue.ReleaseDate, + KeyWords: context.propsValue.KeyWords, + IsActive: context.propsValue.IsActive, + TaxCode: context.propsValue.TaxCode, + MetaTagDescription: context.propsValue.MetaTagDescription, + ShowWithoutStock: context.propsValue.ShowWithoutStock, + Score: context.propsValue.Score, + }; + + const result = await product.createProduct(productData); + return result; + }, +}); diff --git a/packages/pieces/community/vtex/src/lib/actions/Product/get-product-by-id.ts b/packages/pieces/community/vtex/src/lib/actions/Product/get-product-by-id.ts new file mode 100644 index 0000000..6983e1e --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/actions/Product/get-product-by-id.ts @@ -0,0 +1,25 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Product } from '../../common/Product'; +import { vtexAuth } from '../../..'; + +export const getProductById = createAction({ + auth: vtexAuth, + name: 'get-product-by-id', + displayName: 'Get Product By ID', + description: "Find a product in your catalog by it's id", + props: { + productId: Property.Number({ + displayName: 'Product ID', + description: 'The product ID', + required: true, + }), + }, + async run(context) { + const { hostUrl, appKey, appToken } = context.auth; + const { productId } = context.propsValue; + + const product = new Product(hostUrl, appKey, appToken); + + return await product.getProductById(productId); + }, +}); diff --git a/packages/pieces/community/vtex/src/lib/actions/Product/update-product.ts b/packages/pieces/community/vtex/src/lib/actions/Product/update-product.ts new file mode 100644 index 0000000..e6e5805 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/actions/Product/update-product.ts @@ -0,0 +1,121 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Product } from '../../common/Product'; +import { Replace } from '../../common/types'; +import { vtexAuth } from '../../..'; + +export const updateProduct = createAction({ + auth: vtexAuth, + name: 'Update-product', + displayName: 'Update a Product', + description: 'Update a product in your catalog', + props: { + productId: Property.Number({ + displayName: 'Product ID', + description: 'Set the product ID', + required: true, + }), + Name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + Title: Property.ShortText({ + displayName: 'Title', + required: true, + }), + Description: Property.ShortText({ + displayName: 'Description', + required: true, + }), + BrandId: Property.Number({ + displayName: 'Brand ID', + required: true, + }), + CategoryId: Property.Number({ + displayName: 'Category ID', + required: true, + }), + DepartmentId: Property.Number({ + displayName: 'DepartmentId', + required: true, + }), + LinkId: Property.ShortText({ + displayName: 'Link ID', + required: false, + }), + RefId: Property.ShortText({ + displayName: 'Ref ID', + required: false, + }), + BrandName: Property.ShortText({ + displayName: 'Brand Name', + required: false, + }), + IsVisible: Property.Checkbox({ + displayName: 'Is Visible', + required: false, + }), + DescriptionShort: Property.ShortText({ + displayName: 'Short Description', + required: false, + }), + ReleaseDate: Property.ShortText({ + displayName: 'Release Date', + required: false, + }), + KeyWords: Property.ShortText({ + displayName: 'Key Words', + description: 'Similar words', + required: false, + }), + IsActive: Property.Checkbox({ + displayName: 'Is Active', + required: false, + }), + TaxCode: Property.ShortText({ + displayName: 'Tax Code', + required: false, + }), + MetaTagDescription: Property.ShortText({ + displayName: 'Metatag description', + required: false, + }), + ShowWithoutStock: Property.Checkbox({ + displayName: 'Show Without Stock', + required: false, + }), + Score: Property.Number({ + displayName: 'Score', + required: false, + }), + }, + async run(context) { + const { hostUrl, appKey, appToken } = context.auth; + const { productId, ...restProps } = context.propsValue; + + const updatedProduct = { + productId, + Name: restProps.Name, + Title: restProps.Title, + Description: restProps.Description, + BrandId: restProps.BrandId, + CategoryId: restProps.CategoryId, + DepartmentId: restProps.DepartmentId, + LinkId: restProps.LinkId, + RefId: restProps.RefId, + BrandName: restProps.BrandName, + IsVisible: restProps.IsVisible, + DescriptionShort: restProps.DescriptionShort, + ReleaseDate: restProps.ReleaseDate, + KeyWords: restProps.KeyWords, + IsActive: restProps.IsActive, + TaxCode: restProps.TaxCode, + MetaTagDescription: restProps.MetaTagDescription, + ShowWithoutStock: restProps.ShowWithoutStock, + Score: restProps.Score, + }; + + const product = new Product(hostUrl, appKey, appToken); + + return await product.updateProduct(productId, updatedProduct); + }, +}); diff --git a/packages/pieces/community/vtex/src/lib/actions/SKU-File/create-sku-file.ts b/packages/pieces/community/vtex/src/lib/actions/SKU-File/create-sku-file.ts new file mode 100644 index 0000000..a420ab8 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/actions/SKU-File/create-sku-file.ts @@ -0,0 +1,51 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { vtexAuth } from '../../..'; +import { SkuFile } from '../../common/SKU-File'; +import { Replace } from '../../common/types'; + +export const createSkuFile = createAction({ + auth: vtexAuth, + name: 'create-sku-file', + displayName: 'Create New Sku File', + description: 'Create a new SKU File to your catalog', + props: { + SkuId: Property.Number({ + displayName: 'Sku ID', + description: 'Set the Sku ID', + required: true, + }), + Url: Property.ShortText({ + displayName: 'Image Url', + required: true, + }), + Name: Property.ShortText({ + displayName: 'Image Name', + required: true, + }), + IsMain: Property.Checkbox({ + displayName: 'Is Main Image', + required: true, + }), + Label: Property.ShortText({ + displayName: 'Image Label', + required: false, + }), + Text: Property.ShortText({ + displayName: 'Image Name', + description: 'General text of the image', + required: false, + }), + }, + async run(context) { + const { hostUrl, appKey, appToken } = context.auth; + const skuFile = new SkuFile(hostUrl, appKey, appToken); + + return await skuFile.createSkuFile(context.propsValue.SkuId, { + Url: context.propsValue.Url, + Name: context.propsValue.Name, + IsMain: context.propsValue.IsMain, + Label: context.propsValue.Label, + Text: context.propsValue.Text, + }); + }, +}); diff --git a/packages/pieces/community/vtex/src/lib/actions/SKU/create-sku.ts b/packages/pieces/community/vtex/src/lib/actions/SKU/create-sku.ts new file mode 100644 index 0000000..096f079 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/actions/SKU/create-sku.ts @@ -0,0 +1,68 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Sku } from '../../common/SKU'; +import { Replace } from '../../common/types'; +import { vtexAuth } from '../../..'; + +export const createSku = createAction({ + auth: vtexAuth, + name: 'create-sku', + displayName: 'Create New Sku', + description: 'Create a new SKU to your catalog', + props: { + ProductId: Property.Number({ + displayName: 'Product ID', + description: 'ID of the product to be associated with this SKU', + required: true, + }), + Name: Property.ShortText({ + displayName: 'SKU Name', + description: 'Name the variation of the product', + required: true, + }), + PackagedHeight: Property.Number({ + displayName: 'Package Height', + required: true, + }), + PackagedLength: Property.Number({ + displayName: 'Package Length', + required: true, + }), + PackagedWidth: Property.Number({ + displayName: 'Package Width', + required: true, + }), + PackagedWeightKg: Property.Number({ + displayName: 'Package Weight (Kg)', + required: true, + }), + IsActive: Property.Checkbox({ + displayName: 'Is Active', + required: false, + }), + IsKit: Property.Checkbox({ + displayName: 'Is Kit', + required: false, + }), + Id: Property.Number({ + displayName: 'Sku ID', + description: 'Set the Sku ID', + required: false, + }), + }, + async run(context) { + const { hostUrl, appKey, appToken } = context.auth; + const sku = new Sku(hostUrl, appKey, appToken); + + return await sku.createSku({ + ProductId: context.propsValue.ProductId, + Name: context.propsValue.Name, + PackagedHeight: context.propsValue.PackagedHeight, + PackagedLength: context.propsValue.PackagedLength, + PackagedWidth: context.propsValue.PackagedWidth, + PackagedWeightKg: context.propsValue.PackagedWeightKg, + IsActive: context.propsValue.IsActive, + IsKit: context.propsValue.IsKit, + Id: context.propsValue.Id, + }); + }, +}); diff --git a/packages/pieces/community/vtex/src/lib/actions/SKU/get-sku-by-product-id.ts b/packages/pieces/community/vtex/src/lib/actions/SKU/get-sku-by-product-id.ts new file mode 100644 index 0000000..c66e99c --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/actions/SKU/get-sku-by-product-id.ts @@ -0,0 +1,25 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Sku } from '../../common/SKU'; +import { vtexAuth } from '../../..'; + +export const getSkuByProductId = createAction({ + auth: vtexAuth, + name: 'get-sku-by-product-id', + displayName: 'Get SKU By Product ID', + description: 'Find a Sku in your catalog by a Product id', + props: { + productId: Property.Number({ + displayName: 'Product ID', + description: 'The Product ID', + required: true, + }), + }, + async run(context) { + const { hostUrl, appKey, appToken } = context.auth; + const { productId } = context.propsValue; + + const sku = new Sku(hostUrl, appKey, appToken); + + return await sku.getSkuListByProductId(productId); + }, +}); diff --git a/packages/pieces/community/vtex/src/lib/common/Brand.ts b/packages/pieces/community/vtex/src/lib/common/Brand.ts new file mode 100644 index 0000000..bab7101 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/common/Brand.ts @@ -0,0 +1,46 @@ +import axios, { Axios } from 'axios'; +import { CreateBrandParams, UpdateBrandParams } from './types'; + +export class Brand { + api: Axios; + + constructor(host: string, appKey: string, appToken: string) { + this.api = axios.create({ + baseURL: 'https://' + host, + headers: { + 'X-VTEX-API-AppKey': appKey, + 'X-VTEX-API-AppToken': appToken, + }, + }); + } + + async getBrandById(brandID: number) { + const route = '/api/catalog_system/pvt/brand/'; + const response = await this.api.get(route + brandID); + return response.data; + } + + async getBrandList() { + const route = '/api/catalog_system/pvt/brand/list'; + const response = await this.api.get(route); + return response.data; + } + + async createBrand(newBrandData: CreateBrandParams) { + const route = '/api/catalog/pvt/brand'; + const response = await this.api.post(route, newBrandData); + return response.data; + } + + async updateBrand(brandID: number, updatedBrandData: UpdateBrandParams) { + const route = '/api/catalog/pvt/brand/'; + const response = await this.api.put(route + brandID, updatedBrandData); + return response.data; + } + + async deleteBrand(brandID: number) { + const route = '/api/catalog/pvt/brand/'; + const response = await this.api.delete(route + brandID); + return response.data; + } +} diff --git a/packages/pieces/community/vtex/src/lib/common/Category.ts b/packages/pieces/community/vtex/src/lib/common/Category.ts new file mode 100644 index 0000000..2cc7af7 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/common/Category.ts @@ -0,0 +1,46 @@ +import axios, { Axios } from 'axios'; +import { CreateCategoryParams, UpdateCategoryParams } from './types'; + +export class Category { + api: Axios; + + constructor(host: string, appKey: string, appToken: string) { + this.api = axios.create({ + baseURL: 'https://' + host, + headers: { + 'X-VTEX-API-AppKey': appKey, + 'X-VTEX-API-AppToken': appToken, + }, + }); + } + + async getCategory(CategoryID?: number) { + const route = '/api/catalog/pvt/category/'; + const response = await this.api.get(route + (CategoryID ? CategoryID : '')); + return response.data; + } + + async getCategoryTree(categoryLevels: number) { + const route = '/api/catalog_system/pub/category/tree/'; + const response = await this.api.get(route + categoryLevels); + return response.data; + } + + async createCategory(newCategoryData: CreateCategoryParams) { + const route = '/api/catalog/pvt/category'; + const response = await this.api.post(route, newCategoryData); + return response.data; + } + + async updateCategory( + CategoryID: number, + updatedCategoryData: UpdateCategoryParams + ) { + const route = '/api/catalog/pvt/category/'; + const response = await this.api.put( + route + CategoryID, + updatedCategoryData + ); + return response.data; + } +} diff --git a/packages/pieces/community/vtex/src/lib/common/Client.ts b/packages/pieces/community/vtex/src/lib/common/Client.ts new file mode 100644 index 0000000..50d8ffe --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/common/Client.ts @@ -0,0 +1,39 @@ +import axios, { Axios } from 'axios'; + +type GetClientByIdResponse = { + email: string; + id: string; + accountId: string; + accountName: string; + dataEntityId: string; +}; + +type GetClientListResponse = GetClientByIdResponse[]; + +export class Client { + api: Axios; + + constructor(host: string, appKey: string, appToken: string) { + this.api = axios.create({ + baseURL: 'https://' + host, + headers: { + 'X-VTEX-API-AppKey': appKey, + 'X-VTEX-API-AppToken': appToken, + }, + }); + } + + async getClientById(clientID: number): Promise { + const route = `/api/dataentities/CL/documents/`; + const response = await this.api.get( + route + clientID + ); + return response.data; + } + + async getClientList(): Promise { + const route = '/api/dataentities/CL/search'; + const response = await this.api.get(route); + return response.data; + } +} diff --git a/packages/pieces/community/vtex/src/lib/common/Order.ts b/packages/pieces/community/vtex/src/lib/common/Order.ts new file mode 100644 index 0000000..ee136f1 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/common/Order.ts @@ -0,0 +1,27 @@ +import axios, { Axios } from 'axios'; + +export class Order { + api: Axios; + + constructor(host: string, appKey: string, appToken: string) { + this.api = axios.create({ + baseURL: 'https://' + host, + headers: { + 'X-VTEX-API-AppKey': appKey, + 'X-VTEX-API-AppToken': appToken, + }, + }); + } + + async getOrderById(OrderID: number) { + const route = `/api/oms/pvt/orders/`; + const response = await this.api.get(route + OrderID); + return response.data; + } + + async getOrderList(from: Date, to: Date) { + const route = `/api/oms/pvt/orders?f_creationDate=creationDate:[${from.toISOString()} TO ${to.toISOString()}]`; + const response = await this.api.get(route); + return response.data; + } +} diff --git a/packages/pieces/community/vtex/src/lib/common/Product.ts b/packages/pieces/community/vtex/src/lib/common/Product.ts new file mode 100644 index 0000000..d102c25 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/common/Product.ts @@ -0,0 +1,41 @@ +import axios, { Axios } from 'axios'; +import { + CreateProductParams, + UpdateProductParams, + GetProductByIdResponse, +} from './types'; + +export class Product { + api: Axios; + + constructor(host: string, appKey: string, appToken: string) { + this.api = axios.create({ + baseURL: 'https://' + host, + headers: { + 'X-VTEX-API-AppKey': appKey, + 'X-VTEX-API-AppToken': appToken, + }, + }); + } + + async getProductById(productID: number): Promise { + const route = '/api/catalog/pvt/product/'; + const response = await this.api.get(route + productID); + return response.data; + } + + async createProduct(newProductData: CreateProductParams) { + const route = '/api/catalog/pvt/product'; + const response = await this.api.post(route, newProductData); + return response.data; + } + + async updateProduct( + productID: number, + updatedProductData: UpdateProductParams + ) { + const route = '/api/catalog/pvt/product/'; + const response = await this.api.put(route + productID, updatedProductData); + return response.data; + } +} diff --git a/packages/pieces/community/vtex/src/lib/common/SKU-File.ts b/packages/pieces/community/vtex/src/lib/common/SKU-File.ts new file mode 100644 index 0000000..c3683e6 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/common/SKU-File.ts @@ -0,0 +1,53 @@ +import axios, { Axios } from 'axios'; + +type CreateSkuFileParams = { + IsMain?: boolean; + Label?: string; + Name: string; + Text?: string; + Url: string; +}; + +type UpdateSkuFileParams = { + Id: number; + SkuId: number; + FieldId: number; + FieldValueId: number; + Text: string; +}; + +export class SkuFile { + api: Axios; + + constructor(host: string, appKey: string, appToken: string) { + this.api = axios.create({ + baseURL: 'https://' + host, + headers: { + 'X-VTEX-API-AppKey': appKey, + 'X-VTEX-API-AppToken': appToken, + }, + }); + } + + async getSkuFilesBySkuId(skuID: number) { + const route = `/api/catalog/pvt/stockkeepingunit/${skuID}/file/`; + const response = await this.api.get(route); + return response.data; + } + + async createSkuFile(skuID: number, newSkuFileData: CreateSkuFileParams) { + const route = `/api/catalog/pvt/stockkeepingunit/${skuID}/file`; + const response = await this.api.post(route, newSkuFileData); + return response.data; + } + + async updateSkuFile( + skuID: number, + skuFileID: number, + updatedSkuFileData: UpdateSkuFileParams + ) { + const route = `/api/catalog/pvt/stockkeepingunit/${skuID}/file/${skuFileID}`; + const response = await this.api.put(route, updatedSkuFileData); + return response.data; + } +} diff --git a/packages/pieces/community/vtex/src/lib/common/SKU.ts b/packages/pieces/community/vtex/src/lib/common/SKU.ts new file mode 100644 index 0000000..2796914 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/common/SKU.ts @@ -0,0 +1,40 @@ +import axios, { Axios } from 'axios'; +import { CreateSkuParams, UpdateSkuParams } from './types'; + +export class Sku { + api: Axios; + + constructor(host: string, appKey: string, appToken: string) { + this.api = axios.create({ + baseURL: 'https://' + host, + headers: { + 'X-VTEX-API-AppKey': appKey, + 'X-VTEX-API-AppToken': appToken, + }, + }); + } + + async getSkuById(skuID: number) { + const route = '/api/catalog/pvt/stockkeepingunit/'; + const response = await this.api.get(route + skuID); + return response.data; + } + + async getSkuListByProductId(productID: number) { + const route = '/api/catalog_system/pvt/sku/stockkeepingunitByProductId/'; + const response = await this.api.get(route + productID); + return response.data; + } + + async createSku(newSkuData: CreateSkuParams) { + const route = '/api/catalog/pvt/stockkeepingunit'; + const response = await this.api.post(route, newSkuData); + return response.data; + } + + async updateSku(skuID: number, newSkuData: UpdateSkuParams) { + const route = '/api/catalog/pvt/stockkeepingunit/'; + const response = await this.api.put(route + skuID, newSkuData); + return response.data; + } +} diff --git a/packages/pieces/community/vtex/src/lib/common/types/Brand.ts b/packages/pieces/community/vtex/src/lib/common/types/Brand.ts new file mode 100644 index 0000000..98357f8 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/common/types/Brand.ts @@ -0,0 +1,13 @@ +export type CreateBrandParams = { + Id?: number; + Name: string; + Text?: string; + Keywords?: string; + SiteTitle?: string; + Active?: boolean; + MenuHome?: boolean; + Score?: number; + LinkId?: string; +}; + +export type UpdateBrandParams = CreateBrandParams; diff --git a/packages/pieces/community/vtex/src/lib/common/types/Category.ts b/packages/pieces/community/vtex/src/lib/common/types/Category.ts new file mode 100644 index 0000000..50ed534 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/common/types/Category.ts @@ -0,0 +1,18 @@ +export type CreateCategoryParams = { + Name: string; + FatherCategoryId: null; + Title: string; + Description: string; + Keywords: string; + IsActive: boolean; + LomadeeCampaignCode: null; + AdWordsRemarketingCode: null; + ShowInStoreFront: boolean; + ShowBrandFilter: boolean; + ActiveStoreFrontLink: boolean; + GlobalCategoryId: number; + StockKeepingUnitSelectionMode: string; + Score: null; +}; + +export type UpdateCategoryParams = CreateCategoryParams; diff --git a/packages/pieces/community/vtex/src/lib/common/types/Product.ts b/packages/pieces/community/vtex/src/lib/common/types/Product.ts new file mode 100644 index 0000000..ff7ad55 --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/common/types/Product.ts @@ -0,0 +1,49 @@ +export type CreateProductParams = { + Id?: number; + Name?: string; + CategoryPath?: string; + CategoryId?: number; + BrandName?: string; + BrandId?: number; + LinkId?: string; + RefId?: string; + IsVisible?: boolean; + Description?: string; + DescriptionShort?: string; + ReleaseDate?: string; + KeyWords?: string; + Title?: string; + IsActive?: boolean; + TaxCode?: string; + MetaTagDescription?: string; + ShowWithoutStock?: boolean; + Score?: number; +}; + +export type UpdateProductParams = Omit & { + DepartmentId: number; +}; + +export type GetProductByIdResponse = { + Id: number; + Name: string; + DepartmentId: number; + CategoryId: number; + BrandId: number; + LinkId: string; + RefId: string; + IsVisible: boolean; + Description: string; + DescriptionShort: string; + ReleaseDate: string; + KeyWords: string; + Title: string; + IsActive: boolean; + TaxCode: string; + MetaTagDescription: string; + SupplierId: number | null; + ShowWithoutStock: boolean; + AdWordsRemarketingCode: string; + LomadeeCampaignCode: string; + Score: number; +}; diff --git a/packages/pieces/community/vtex/src/lib/common/types/SKU.ts b/packages/pieces/community/vtex/src/lib/common/types/SKU.ts new file mode 100644 index 0000000..f79d4cb --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/common/types/SKU.ts @@ -0,0 +1,31 @@ +export type CreateSkuParams = { + Id?: number; + ProductId: number; + IsActive?: boolean; + ActivateIfPossible?: boolean; + Name: string; + RefId?: string; + Ean?: string; + PackagedHeight: number; + PackagedLength: number; + PackagedWidth: number; + PackagedWeightKg: number; + Height?: number; + Length?: number; + Width?: number; + WeightKg?: number; + CubicWeight?: number; + IsKit?: boolean; + CreationDate?: string; + RewardValue?: number; + EstimatedDateArrival?: string; + ManufacturerCode?: string; + CommercialConditionId?: number; + MeasurementUnit?: string; + UnitMultiplier?: number; + ModalType?: string; + KitItemsSellApart?: boolean; + Videos?: string[]; +}; + +export type UpdateSkuParams = CreateSkuParams; diff --git a/packages/pieces/community/vtex/src/lib/common/types/index.ts b/packages/pieces/community/vtex/src/lib/common/types/index.ts new file mode 100644 index 0000000..a6c87be --- /dev/null +++ b/packages/pieces/community/vtex/src/lib/common/types/index.ts @@ -0,0 +1,6 @@ +export * from './Product'; +export * from './Brand'; +export * from './Category'; +export * from './SKU'; + +export type Replace = Omit & R; diff --git a/packages/pieces/community/vtex/tsconfig.json b/packages/pieces/community/vtex/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/vtex/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/vtex/tsconfig.lib.json b/packages/pieces/community/vtex/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/vtex/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/vtiger/.eslintrc.json b/packages/pieces/community/vtiger/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/vtiger/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/vtiger/README.md b/packages/pieces/community/vtiger/README.md new file mode 100644 index 0000000..e6dff99 --- /dev/null +++ b/packages/pieces/community/vtiger/README.md @@ -0,0 +1,7 @@ +# pieces-vtiger + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-vtiger` to build the library. diff --git a/packages/pieces/community/vtiger/package-lock.json b/packages/pieces/community/vtiger/package-lock.json new file mode 100644 index 0000000..478da63 --- /dev/null +++ b/packages/pieces/community/vtiger/package-lock.json @@ -0,0 +1,55 @@ +{ + "name": "@activepieces/piece-vtiger", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@activepieces/piece-vtiger", + "version": "0.0.1", + "dependencies": { + "@bowbridge/vtiger-js": "^1.17.0" + } + }, + "node_modules/@bowbridge/vtiger-js": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@bowbridge/vtiger-js/-/vtiger-js-1.17.0.tgz", + "integrity": "sha512-NTMxJ3rym5EPF7nidpVVNbghMYAugqKmeP6oCCmEfKBxV35CXuGquiOyXXbZNZfLf8xZfFZ+0rBY7mH5v5ZqMg==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "axios": "^0.24.0" + } + }, + "node_modules/axios": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", + "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "peer": true, + "dependencies": { + "follow-redirects": "^1.14.4" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "peer": true, + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + } + } +} diff --git a/packages/pieces/community/vtiger/package.json b/packages/pieces/community/vtiger/package.json new file mode 100644 index 0000000..37af56e --- /dev/null +++ b/packages/pieces/community/vtiger/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-vtiger", + "version": "1.1.10" +} \ No newline at end of file diff --git a/packages/pieces/community/vtiger/project.json b/packages/pieces/community/vtiger/project.json new file mode 100644 index 0000000..ef16371 --- /dev/null +++ b/packages/pieces/community/vtiger/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-vtiger", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/vtiger/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/vtiger", + "tsConfig": "packages/pieces/community/vtiger/tsconfig.lib.json", + "packageJson": "packages/pieces/community/vtiger/package.json", + "main": "packages/pieces/community/vtiger/src/index.ts", + "assets": [ + "packages/pieces/community/vtiger/*.md", + { + "input": "packages/pieces/community/vtiger/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-vtiger {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/vtiger/src/index.ts b/packages/pieces/community/vtiger/src/index.ts new file mode 100644 index 0000000..6c54b09 --- /dev/null +++ b/packages/pieces/community/vtiger/src/index.ts @@ -0,0 +1,120 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createRecord } from './lib/actions/create-record'; +import { deleteRecord } from './lib/actions/delete-record'; +import { getRecord } from './lib/actions/get-record'; +import { makeAPICall } from './lib/actions/make-api-call'; +import { searchRecords } from './lib/actions/search-record'; +import { updateRecord } from './lib/actions/update-record'; +import { instanceLogin, isBaseUrl } from './lib/common'; +import { newOrUpdatedRecord } from './lib/triggers/new-or-updated-record'; +import { queryRecords } from './lib/actions/query-records'; + +const markdownProperty = ` +To obtain your Access Key, follow these steps: + +1. Login to Vtiger CRM: +Open a web browser and log in to your Vtiger CRM instance. + +2. Navigate to User Profile: +In the top right corner, click on your profile name. +Select "My Preferences." + +3. The system will generate an access key for you. +Copy and securely store the access key. This key will be used for authentication when making API requests. +Note: + +Access keys are sensitive information, and they should be kept secure. +Treat the access key like a password. Do not share it publicly or expose it in an insecure manner. +`; + +export const vtigerAuth = PieceAuth.CustomAuth({ + description: markdownProperty, + props: { + instance_url: Property.ShortText({ + displayName: 'VTiger Instance URL', + description: + 'For the instance URL, add the url without the endpoint. For example enter https://.od2.vtiger.com instead of https://.od2.vtiger.com/restapi/v1/vtiger/default', + required: true, + }), + username: Property.ShortText({ + displayName: 'Username', + description: 'Enter your username/email', + required: true, + }), + password: PieceAuth.SecretText({ + displayName: 'Access Key', + description: 'Enter your access Key', + required: true, + }), + }, + validate: async ({ auth }) => { + const { instance_url, username, password } = auth; + + try { + if (!isBaseUrl(instance_url)) { + return { + valid: false, + error: + 'Please ensure that the website is valid and does not contain any paths, for example, https://.od2.vtiger.com ', + }; + } + if (instance_url.endsWith('/')) { + return { + valid: false, + error: + 'Please enter the URL without a trailing slash. E.g. https://.od2.vtiger.com instead of https://.od2.vtiger.com/', + }; + } + if (instance_url.includes('restapi/')) { + return { + valid: false, + error: + 'Add the url without the endpoint. For example add https://.od2.vtiger.com/ instead of https://.od2.vtiger.com/restapi/v1/vtiger/default', + }; + } + + const instance = await instanceLogin(instance_url, username, password); + if (!instance) { + return { + valid: false, + error: 'Invalid credentials, check and try again.', + }; + } + + return { + valid: true, + }; + } catch (err) { + return { + valid: false, + error: 'Unexpected error. Please check your credentials and try again.', + }; + } + }, + required: true, +}); + +export const vtiger = createPiece({ + displayName: 'Vtiger', + description: 'CRM software for sales, marketing, and support teams', + auth: vtigerAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/vtiger.png', + categories: [PieceCategory.SALES_AND_CRM], + authors: ["kanarelo","kishanprmr","abuaboud"], + actions: [ + createRecord, + getRecord, + updateRecord, + deleteRecord, + queryRecords, + searchRecords, + makeAPICall, + ], + triggers: [newOrUpdatedRecord], +}); diff --git a/packages/pieces/community/vtiger/src/lib/actions/create-record.ts b/packages/pieces/community/vtiger/src/lib/actions/create-record.ts new file mode 100644 index 0000000..b2e957d --- /dev/null +++ b/packages/pieces/community/vtiger/src/lib/actions/create-record.ts @@ -0,0 +1,53 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { vtigerAuth } from '../..'; +import { instanceLogin, recordProperty } from '../common'; +import { elementTypeProperty } from '../common'; + +//Docs: https://code.vtiger.com/vtiger/vtigercrm-manual/-/wikis/Webservice-Docs +//Extra: https://help.vtiger.com/article/147111249-Rest-API-Manual + +export const createRecord = createAction({ + name: 'create_record', + auth: vtigerAuth, + displayName: 'Create Record', + description: 'Create a Record', + props: { + elementType: elementTypeProperty, + record: recordProperty(), + }, + async run({ propsValue: { elementType, record }, auth }) { + const instance = await instanceLogin( + auth.instance_url, + auth.username, + auth.password + ); + + if (instance !== null) { + const response = await httpClient.sendRequest[]>({ + method: HttpMethod.POST, + url: `${auth.instance_url}/webservice.php`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: { + operation: 'create', + sessionName: instance.sessionId ?? instance.sessionName, + elementType: elementType, + element: JSON.stringify(record), + }, + }); + + console.debug({ + operation: 'create', + sessionName: instance.sessionId ?? instance.sessionName, + elementType: elementType, + element: JSON.stringify(record), + }); + + return response.body; + } + + return null; + }, +}); diff --git a/packages/pieces/community/vtiger/src/lib/actions/delete-record.ts b/packages/pieces/community/vtiger/src/lib/actions/delete-record.ts new file mode 100644 index 0000000..7ef4ff6 --- /dev/null +++ b/packages/pieces/community/vtiger/src/lib/actions/delete-record.ts @@ -0,0 +1,45 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { vtigerAuth } from '../..'; +import { instanceLogin, recordIdProperty } from '../common'; +import { elementTypeProperty } from '../common'; + +//Docs: https://code.vtiger.com/vtiger/vtigercrm-manual/-/wikis/Webservice-Docs +//Extra: https://help.vtiger.com/article/147111249-Rest-API-Manual + +export const deleteRecord = createAction({ + name: 'delete_record', + auth: vtigerAuth, + displayName: 'Delete Record', + description: 'Delete a Record', + props: { + elementType: elementTypeProperty, + record: recordIdProperty(), + }, + async run({ + propsValue: { elementType, record }, + auth: { instance_url, username, password }, + }) { + const instance = await instanceLogin(instance_url, username, password); + + if (instance !== null) { + const response = await httpClient.sendRequest[]>({ + method: HttpMethod.POST, + url: `${instance_url}/webservice.php`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: { + operation: 'delete', + sessionName: instance.sessionId ?? instance.sessionName, + elementType, + id: record['id'], + }, + }); + + return response.body; + } + + return null; + }, +}); diff --git a/packages/pieces/community/vtiger/src/lib/actions/get-record.ts b/packages/pieces/community/vtiger/src/lib/actions/get-record.ts new file mode 100644 index 0000000..f875596 --- /dev/null +++ b/packages/pieces/community/vtiger/src/lib/actions/get-record.ts @@ -0,0 +1,42 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { vtigerAuth } from '../..'; +import { instanceLogin, recordIdProperty } from '../common'; +import { elementTypeProperty } from '../common'; + +//Docs: https://code.vtiger.com/vtiger/vtigercrm-manual/-/wikis/Webservice-Docs +//Extra: https://help.vtiger.com/article/147111249-Rest-API-Manual + +export const getRecord = createAction({ + name: 'get_record', + auth: vtigerAuth, + displayName: 'Get Record', + description: 'Get a Record by value', + props: { + elementType: elementTypeProperty, + record: recordIdProperty(), + }, + async run({ + propsValue: { elementType, record }, + auth: { instance_url, username, password }, + }) { + const instance = await instanceLogin(instance_url, username, password); + + if (instance !== null) { + const response = await httpClient.sendRequest[]>({ + method: HttpMethod.GET, + url: `${instance_url}/webservice.php`, + queryParams: { + operation: 'retrieve', + sessionName: instance.sessionId ?? instance.sessionName, + elementType: elementType as unknown as string, + ...record, + }, + }); + + return response.body; + } + + return null; + }, +}); diff --git a/packages/pieces/community/vtiger/src/lib/actions/make-api-call.ts b/packages/pieces/community/vtiger/src/lib/actions/make-api-call.ts new file mode 100644 index 0000000..c39c7e6 --- /dev/null +++ b/packages/pieces/community/vtiger/src/lib/actions/make-api-call.ts @@ -0,0 +1,80 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { vtigerAuth } from '../..'; +import { instanceLogin } from '../common'; +import { + HttpMessageBody, + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; + +//Docs: https://code.vtiger.com/vtiger/vtigercrm-manual/-/wikis/Webservice-Docs +//Extra: https://help.vtiger.com/article/147111249-Rest-API-Manual + +export const makeAPICall = createAction({ + name: 'make_api_call', + auth: vtigerAuth, + displayName: 'Custom API Call', + description: 'Performs an arbitrary authorized API call. ', + props: { + method: Property.StaticDropdown({ + displayName: 'Http Method', + description: 'Select the HTTP method you want to use', + required: true, + options: { + options: [ + { label: 'GET', value: HttpMethod.GET }, + { label: 'POST', value: HttpMethod.POST }, + ], + }, + }), + headers: Property.Json({ + displayName: 'Headers', + description: `Enter the desired request headers. Skip the authorization headers`, + required: true, + defaultValue: {}, + }), + data: Property.Json({ + displayName: 'Data', + description: `Enter the data to pass. if its POST, it will be sent as body data, and if GET, as query string`, + required: true, + defaultValue: {}, + }), + }, + async run({ propsValue, auth }) { + const vtigerInstance = await instanceLogin( + auth.instance_url, + auth.username, + auth.password + ); + if (vtigerInstance === null) return; + + const data: Record = { + sessionName: vtigerInstance.sessionId ?? vtigerInstance.sessionName, + ...(propsValue.data ?? {}), + }; + + const httpRequest: HttpRequest = { + url: `${auth.instance_url}/webservice.php`, + method: propsValue.method ?? HttpMethod.GET, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + ...(propsValue.headers ?? {}), + }, + }; + httpRequest[propsValue.method === HttpMethod.GET ? 'queryParams' : 'body'] = + data; + + const response = await httpClient.sendRequest[]>( + httpRequest + ); + + if ([200, 201].includes(response.status)) { + return response.body; + } + + return { + error: 'Unexpected outcome!', + }; + }, +}); diff --git a/packages/pieces/community/vtiger/src/lib/actions/query-records.ts b/packages/pieces/community/vtiger/src/lib/actions/query-records.ts new file mode 100644 index 0000000..72f8750 --- /dev/null +++ b/packages/pieces/community/vtiger/src/lib/actions/query-records.ts @@ -0,0 +1,57 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { vtigerAuth } from '../..'; +import { + Operation, + elementTypeProperty, + instanceLogin, + prepareHttpRequest, +} from '../common'; +import { httpClient } from '@activepieces/pieces-common'; + +//Docs: https://code.vtiger.com/vtiger/vtigercrm-manual/-/wikis/Webservice-Docs +//Extra: https://help.vtiger.com/article/147111249-Rest-API-Manual + +export const queryRecords = createAction({ + name: 'query_records', + auth: vtigerAuth, + displayName: 'Query Records', + description: 'Query records by SQL statement.', + props: { + query: Property.LongText({ + displayName: 'Query', + description: + 'Enter the query statement, e.g. SELECT count(*) FROM Contacts;', + required: true, + }), + }, + async run({ propsValue, auth }) { + const vtigerInstance = await instanceLogin( + auth.instance_url, + auth.username, + auth.password + ); + if (vtigerInstance === null) return; + + const response = await httpClient.sendRequest<{ + success: boolean; + result: Record[]; + }>( + prepareHttpRequest( + auth.instance_url, + vtigerInstance.sessionId ?? vtigerInstance.sessionName, + 'query' as Operation, + { query: propsValue.query } + ) + ); + + if (response.body.success) { + return response.body.result; + } else { + console.debug(response); + + return { + error: 'Unexpected outcome!', + }; + } + }, +}); diff --git a/packages/pieces/community/vtiger/src/lib/actions/search-record.ts b/packages/pieces/community/vtiger/src/lib/actions/search-record.ts new file mode 100644 index 0000000..cc28d68 --- /dev/null +++ b/packages/pieces/community/vtiger/src/lib/actions/search-record.ts @@ -0,0 +1,94 @@ +import { + PiecePropValueSchema, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { vtigerAuth } from '../..'; +import { + VTigerAuthValue, + queryRecords, + countRecords, + elementTypeProperty, + generateElementFields, + instanceLogin, +} from '../common'; + +//Docs: https://code.vtiger.com/vtiger/vtigercrm-manual/-/wikis/Webservice-Docs +//Extra: https://help.vtiger.com/article/147111249-Rest-API-Manual + +export const searchRecords = createAction({ + name: 'search_records', + auth: vtigerAuth, + displayName: 'Search Records', + description: 'Search for a record.', + props: { + elementType: elementTypeProperty, + fields: Property.DynamicProperties({ + displayName: 'Search Fields', + description: 'Enter your filter criteria', + required: true, + refreshers: ['elementType'], + props: async ({ auth, elementType }) => { + if (!auth || !elementType) { + return {}; + } + + const instance = await instanceLogin( + (auth as PiecePropValueSchema).instance_url, + (auth as PiecePropValueSchema).username, + (auth as PiecePropValueSchema).password + ); + + if (instance === null) { + return {}; + } + + return generateElementFields( + auth as VTigerAuthValue, + elementType as unknown as string, + {}, + true + ); + }, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Enter the maximum number of records to return.', + required: false, + }), + }, + async run({ propsValue, auth }) { + const vtigerInstance = await instanceLogin( + auth.instance_url, + auth.username, + auth.password + ); + if (vtigerInstance === null) return; + + const count = await countRecords(auth, propsValue.elementType as string); + if (count > 0) { + const records: Record[] = await queryRecords( + auth, + propsValue.elementType as string, + 0, + count + ); + + const filtered = records.filter((record) => { + return Object.entries(propsValue['fields']).every(([key, value]) => { + if (typeof value === 'string') { + return (record[key] as unknown as string) + .toLowerCase() + .includes(value.toLowerCase()); + } else { + return record[key] === value.toLowerCase(); + } + }); + }); + + return propsValue.limit ? filtered.slice(0, propsValue.limit) : filtered; + } else { + return []; + } + }, +}); diff --git a/packages/pieces/community/vtiger/src/lib/actions/update-record.ts b/packages/pieces/community/vtiger/src/lib/actions/update-record.ts new file mode 100644 index 0000000..beeb012 --- /dev/null +++ b/packages/pieces/community/vtiger/src/lib/actions/update-record.ts @@ -0,0 +1,285 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { + DropdownState, + DynamicPropsValue, + PiecePropValueSchema, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { vtigerAuth } from '../..'; +import { + instanceLogin, + VTigerAuthValue, + Modules, + Field, + getRecordReference, +} from '../common'; +import { elementTypeProperty } from '../common'; + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export const updateRecord = createAction({ + name: 'update_record', + auth: vtigerAuth, + displayName: 'Update Record', + description: 'Update a Record', + props: { + elementType: elementTypeProperty, + id: Property.Dropdown({ + displayName: 'Id', + description: "The record's id", + required: true, + refreshers: ['elementType'], + options: async ({ auth, elementType }) => { + if (!auth || !elementType) { + return { + disabled: true, + options: [], + placeholder: + 'Please select the element type and setup authentication to continue.', + }; + } + + let c = 0; + let instance = null; + while (!instance && c < 3) { + instance = await instanceLogin( + (auth as VTigerAuthValue).instance_url, + (auth as VTigerAuthValue).username, + (auth as VTigerAuthValue).password + ); + await sleep(1500); + c++; + } + + if (!instance) { + return { + disabled: true, + options: [], + placeholder: 'Authentication failed.', + }; + } + + const response = await httpClient.sendRequest<{ + success: boolean; + result: Record[]; + }>({ + method: HttpMethod.GET, + url: `${(auth as VTigerAuthValue)['instance_url']}/webservice.php`, + queryParams: { + sessionName: instance.sessionId ?? instance.sessionName, + operation: 'query', + elementType: elementType as unknown as string, + query: `SELECT * FROM ${elementType} LIMIT 100;`, + }, + }); + + if (!response.body.success) + return { + disabled: true, + options: [], + placeholder: 'Request unsuccessful.', + }; + + const element: string = elementType as unknown as string; + + return { + options: response.body.result.map((r) => ({ + label: Modules[element](r), + value: r['id'], + })), + disabled: false, + }; + }, + }), + record: Property.DynamicProperties({ + displayName: 'Record Fields', + description: 'Add new fields to be created in the new record', + required: true, + refreshers: ['id', 'elementType'], + props: async ({ auth, id, elementType }) => { + if (!auth || !elementType) { + return {}; + } + + const instance = await instanceLogin( + auth['instance_url'], + auth['username'], + auth['password'] + ); + if (!instance) return {}; + + let defaultValue: Record; + if (id && 'id') { + const retrieve_response = await httpClient.sendRequest<{ + success: boolean; + result: Record; + }>({ + method: HttpMethod.GET, + url: `${auth['instance_url']}/webservice.php`, + queryParams: { + operation: 'retrieve', + sessionName: instance.sessionId ?? instance.sessionName, + elementType: elementType as unknown as string, + id: id as unknown as string, + }, + }); + if (retrieve_response.body.result) { + defaultValue = retrieve_response.body.result; + } else { + defaultValue = {}; + } + } else { + defaultValue = {}; + } + + const describe_response = await httpClient.sendRequest<{ + success: boolean; + result: { fields: Field[] }; + }>({ + method: HttpMethod.GET, + url: `${auth['instance_url']}/webservice.php`, + queryParams: { + sessionName: instance.sessionId ?? instance.sessionName, + operation: 'describe', + elementType: elementType as unknown as string, + }, + }); + + const fields: DynamicPropsValue = {}; + + if (describe_response.body.success) { + const generateField = async (field: Field) => { + const params = { + displayName: field.label, + description: `Field ${field.name} of object type ${elementType}`, + required: field.mandatory, + }; + + if ( + [ + 'string', + 'text', + 'mediumtext', + 'phone', + 'url', + 'email', + ].includes(field.type.name) + ) { + if (['mediumtext', 'url'].includes(field.type.name)) { + fields[field.name] = Property.LongText({ + ...params, + defaultValue: defaultValue?.[field.name] as string, + }); + } else { + fields[field.name] = Property.ShortText({ + ...params, + defaultValue: defaultValue?.[field.name] as string, + }); + } + } else if ( + ['picklist', 'reference', 'owner'].includes(field.type.name) + ) { + let options: DropdownState; + if (field.type.name === 'picklist') { + options = { + disabled: false, + options: field.type.picklistValues ?? [], + }; + } else if (field.type.name === 'owner') { + options = await getRecordReference( + auth as PiecePropValueSchema, + ['Users'] + ); + } else if (field.type.refersTo) { + options = await getRecordReference( + auth as PiecePropValueSchema, + field.type.refersTo ?? [] + ); + } else { + options = { disabled: false, options: [] }; + } + + fields[field.name] = Property.StaticDropdown({ + ...params, + defaultValue: defaultValue?.[field.name] as string, + options, + }); + } else if ( + ['double', 'integer', 'currency'].includes(field.type.name) + ) { + fields[field.name] = Property.Number({ + ...params, + defaultValue: defaultValue?.[field.name] as number, + }); + } else if (['boolean'].includes(field.type.name)) { + fields[field.name] = Property.Checkbox({ + displayName: field.label, + description: `The fields to fill in the object type ${elementType}`, + required: field.mandatory, + defaultValue: defaultValue?.[field.name] as boolean, + }); + } else if (['date', 'datetime', 'time'].includes(field.type.name)) { + fields[field.name] = Property.DateTime({ + displayName: field.label, + description: `The fields to fill in the object type ${elementType}`, + defaultValue: defaultValue?.[field.name] as string, + required: field.mandatory, + }); + } + }; + + for (const field of describe_response.body.result.fields) { + if (field.name === 'id') continue; + + await generateField(field); + } + } + + return fields; + }, + }), + }, + async run({ propsValue: { elementType, id, record }, auth }) { + const instance = await instanceLogin( + auth.instance_url, + auth.username, + auth.password + ); + + if (instance !== null) { + const response = await httpClient.sendRequest[]>({ + method: HttpMethod.POST, + url: `${auth.instance_url}/webservice.php`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: { + operation: 'update', + sessionName: instance.sessionId ?? instance.sessionName, + elementType: elementType, + element: JSON.stringify({ + id: id, + ...record, + }), + }, + }); + + console.debug({ + operation: 'update', + sessionName: instance.sessionId ?? instance.sessionName, + elementType: elementType, + element: JSON.stringify({ + id: id, + ...record, + }), + }); + + return response.body; + } + + return null; + }, +}); diff --git a/packages/pieces/community/vtiger/src/lib/common.ts b/packages/pieces/community/vtiger/src/lib/common.ts new file mode 100644 index 0000000..717742b --- /dev/null +++ b/packages/pieces/community/vtiger/src/lib/common.ts @@ -0,0 +1,616 @@ +import { + HttpHeaders, + HttpMessageBody, + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; +import { + DropdownState, + DynamicPropsValue, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import * as crypto from 'crypto-js'; +import { Challenge, Instance } from './models'; +import { vtigerAuth } from '..'; + +export const isBaseUrl = (urlString: string): boolean => { + try { + const url = new URL(urlString); + return !url.pathname || url.pathname === '/'; + } catch (error) { + // Handle invalid URLs here, e.g., return false or throw an error + return false; + } +}; + +export const md5 = (contents: string) => crypto.MD5(contents).toString(); +export const calculateAuthKey = ( + challengeToken: string, + accessKey: string +): string => crypto.MD5(challengeToken + accessKey).toString(crypto.enc.Hex); + +export const instanceLogin = async ( + instance_url: string, + username: string, + password: string, + debug = false +) => { + const endpoint = `${instance_url}/webservice.php`; + const challenge = await httpClient.sendRequest<{ + success: boolean; + result: Challenge; + }>({ + method: HttpMethod.GET, + url: `${endpoint}?operation=getchallenge&username=${username}`, + }); + + const accessKey = calculateAuthKey(challenge.body.result.token, password); + const response = await httpClient.sendRequest<{ + success: boolean; + result: Instance; + }>({ + method: HttpMethod.POST, + url: `${endpoint}`, + headers: { + 'Content-Type': 'multipart/form-data', + }, + body: { + operation: 'login', + username, + accessKey, + }, + }); + + if (debug) { + console.debug('>>>>>>>>>>>> LOGIN', response.body, { + method: HttpMethod.POST, + url: `${endpoint}`, + headers: { + 'Content-Type': 'multipart/form-data', + }, + body: { + operation: 'login', + username, + accessKey, + }, + }); + } + if (response.body.success) { + return response.body.result; + } + + return null; +}; + +export type Operation = + | 'create' + | 'retrieve' + | 'delete' + | 'update' + | 'query' + | 'listtypes'; + +export const Operations: Record = { + listtypes: { + method: HttpMethod.GET, + }, + create: { + method: HttpMethod.POST, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }, + retrieve: { + method: HttpMethod.GET, + }, + delete: { + method: HttpMethod.POST, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }, + update: { + method: HttpMethod.POST, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }, + query: { + method: HttpMethod.GET, + }, +}; + +export const prepareHttpRequest = ( + instanceUrl: string, + sessionName: string, + operation: Operation, + record: Record +) => { + const data: Record = { + operation, + sessionName, + ...record, + }; + if ('element' in record) data['element'] = JSON.stringify(record['element']); + + const httpRequest: HttpRequest = { + url: `${instanceUrl}/webservice.php`, + method: Operations[operation].method, + headers: Operations[operation].headers, + }; + + if (Operations[operation].method === HttpMethod.GET) { + httpRequest['queryParams'] = data; + } else if (Operations[operation].method === HttpMethod.POST) { + httpRequest['body'] = data; + } + + return httpRequest; +}; + +interface BodyParams { + method: HttpMethod; + headers?: HttpHeaders; +} + +export const Modules: Record = { + Accounts: (record: Record) => `${record['accountname']}`, + Assets: (record: Record) => `${record['assetname']}`, + CompanyDetails: (record: Record) => + `${record['organizationname']}`, + Contacts: (record: Record) => `${record['email']}`, + Currency: (record: Record) => `${record['currency_name']}`, + DocumentFolders: (record: Record) => + `${record['foldername']}`, + Documents: (record: Record) => `${record['notes_title']}`, + Emails: (record: Record) => `${record['subject']}`, + Events: (record: Record) => `${record['subject']}`, + Faq: (record: Record) => `${record['faq_no']}`, + Groups: (record: Record) => `${record['groupname']}`, + HelpDesk: (record: Record) => `${record['ticket_no']}`, + Invoice: (record: Record) => `${record['invoice_no']}`, + Leads: (record: Record) => + `${record['lead_no']}: ${record['firstname']} ${record['lastname']}`, + LineItem: (record: Record) => `${record['productid']}`, + ModComments: (record: Record) => + `${record['commentcontent']}`, + Potentials: (record: Record) => `${record['potentialname']}`, + PriceBooks: (record: Record) => `${record['bookname']}`, + Products: (record: Record) => `${record['productname']}`, + ProductTaxes: (record: Record) => + `#${record['taxid']} pid: ${record['productid']}`, + Project: (record: Record) => `${record['projectname']}`, + ProjectMilestone: (record: Record) => + `${record['projectmilestonename']}`, + ProjectTask: (record: Record) => + `${record['projecttaskname']}`, + PurchaseOrder: (record: Record) => `${record['subject']}`, + Quotes: (record: Record) => `${record['subject']}`, + SalesOrder: (record: Record) => `${record['salesorder_no']}`, + ServiceContracts: (record: Record) => `${record['subject']}`, + Services: (record: Record) => `${record['servicename']}`, + SLA: (record: Record) => `${record['policy_name']}`, + Tax: (record: Record) => `${record['taxname']}`, + Users: (record: Record) => `${record['user_name']}`, + Vendors: (record: Record) => `${record['vendorname']}`, +}; + +export const elementTypeProperty = Property.StaticDropdown({ + displayName: 'Module Type', + description: 'The module / element type', + required: true, + options: { + options: [ + { label: 'Accounts', value: 'Accounts' }, + { label: 'Assets', value: 'Assets' }, + { label: 'CompanyDetails', value: 'CompanyDetails' }, + { label: 'Contacts', value: 'Contacts' }, + { label: 'Currency', value: 'Currency' }, + { label: 'DocumentFolders', value: 'DocumentFolders' }, + { label: 'Documents', value: 'Documents' }, + { label: 'Emails', value: 'Emails' }, + { label: 'Events', value: 'Events' }, + { label: 'Faq', value: 'Faq' }, + { label: 'Groups', value: 'Groups' }, + { label: 'HelpDesk', value: 'HelpDesk' }, + { label: 'Invoice', value: 'Invoice' }, + { label: 'Leads', value: 'Leads' }, + { label: 'LineItem', value: 'LineItem' }, + { label: 'ModComments', value: 'ModComments' }, + { label: 'Potentials', value: 'Potentials' }, + { label: 'PriceBooks', value: 'PriceBooks' }, + { label: 'Products', value: 'Products' }, + { label: 'ProductTaxes', value: 'ProductTaxes' }, + { label: 'Project', value: 'Project' }, + { label: 'ProjectMilestone', value: 'ProjectMilestone' }, + { label: 'ProjectTask', value: 'ProjectTask' }, + { label: 'PurchaseOrder', value: 'PurchaseOrder' }, + { label: 'Quotes', value: 'Quotes' }, + { label: 'SalesOrder', value: 'SalesOrder' }, + { label: 'ServiceContracts', value: 'ServiceContracts' }, + { label: 'Services', value: 'Services' }, + { label: 'SLA', value: 'SLA' }, + { label: 'Tax', value: 'Tax' }, + { label: 'Users', value: 'Users' }, + { label: 'Vendors', value: 'Vendors' }, + ], + }, +}); + +export interface Field { + name: string; + dblabel: string; + label: string; + default: string; + mandatory: boolean; + type: { + name: string; + length?: string; + refersTo?: string[]; + picklistValues?: { + label: string; + value: string; + }[]; + }; +} + +export type VTigerAuthValue = PiecePropValueSchema; + +export const recordIdProperty = () => + Property.DynamicProperties({ + displayName: 'Record Fields', + description: 'Add new fields to be created in the new record', + required: true, + refreshers: ['elementType'], + props: async ({ auth, elementType }) => { + if (!auth || !elementType) { + return {}; + } + + const instance = await instanceLogin( + auth['instance_url'], + auth['username'], + auth['password'] + ); + if (!instance) return {}; + + const response = await httpClient.sendRequest<{ + success: boolean; + result: Record[]; + }>({ + method: HttpMethod.GET, + url: `${(auth as VTigerAuthValue)['instance_url']}/webservice.php`, + queryParams: { + sessionName: instance.sessionId ?? instance.sessionName, + operation: 'query', + elementType: elementType as unknown as string, + query: `SELECT * FROM ${elementType} LIMIT 100;`, + }, + }); + + if (!response.body.success) return {}; + + const fields: DynamicPropsValue = {}; + const _type: string = elementType as unknown as string; + const _module: CallableFunction = Modules[_type]; + + fields['id'] = Property.StaticDropdown({ + displayName: 'Id', + description: "The record's id", + required: true, + options: { + options: response.body.result.map((r) => ({ + label: _module(r), + value: r['id'], + })), + }, + }); + + return fields; + }, + }); + +export const FieldMapping = { + autogenerated: Property.ShortText, + string: Property.ShortText, + text: Property.ShortText, + double: Property.Number, + integer: Property.Number, + mediumtext: Property.LongText, + phone: Property.LongText, + url: Property.LongText, + email: Property.LongText, + picklist: Property.StaticDropdown, + reference: Property.StaticDropdown, + currency: Property.Number, + boolean: Property.Checkbox, + owner: Property.StaticDropdown, + date: Property.DateTime, + datetime: Property.DateTime, + file: Property.File, + time: Property.DateTime, +}; + +export async function getRecordReference( + auth: PiecePropValueSchema, + modules: string[] +): Promise> { + const module = modules[0]; //Limit to the first reference for now + const vtigerInstance = await instanceLogin( + auth['instance_url'], + auth['username'], + auth['password'] + ); + if (vtigerInstance === null) + return { + disabled: true, + options: [], + }; + + const httpRequest = prepareHttpRequest( + auth['instance_url'], + vtigerInstance.sessionId ?? vtigerInstance.sessionName, + 'query' as Operation, + { query: `SELECT * FROM ${module};` } + ); + + const response = await httpClient.sendRequest<{ + success: boolean; + result: Record[]; + }>(httpRequest); + + if (response.body.success) { + return { + disabled: false, + options: response.body.result.map((record) => { + return { + label: Modules[module](record), + value: record['id'] as string, + }; + }), + }; + } + + return { + disabled: true, + options: [], + }; +} + +export const recordProperty = (create = true) => + Property.DynamicProperties({ + displayName: 'Record Fields', + description: 'Add new fields to be created in the new record', + required: true, + refreshers: ['id', 'elementType'], + props: async ({ auth, id, elementType }) => { + if (!auth || !elementType) { + return {}; + } + + const instance = await instanceLogin( + auth['instance_url'], + auth['username'], + auth['password'] + ); + if (!instance) return {}; + + let defaultValue: Record; + + if (create) { + defaultValue = {}; + } else { + if (id && 'id' in id) { + const retrieve_response = await httpClient.sendRequest< + Record + >({ + method: HttpMethod.GET, + url: `${auth['instance_url']}/webservice.php`, + queryParams: { + operation: 'retrieve', + sessionName: instance.sessionId ?? instance.sessionName, + elementType: elementType as unknown as string, + id: id['id'] as unknown as string, + }, + }); + defaultValue = retrieve_response.body; + } else { + defaultValue = {}; + } + } + + return generateElementFields( + auth as VTigerAuthValue, + elementType as unknown as string, + defaultValue + ); + }, + }); + +export const queryRecords = async ( + auth: VTigerAuthValue, + elementType: string, + page = 0, + limit = 100 +) => { + const instance = await instanceLogin( + auth['instance_url'], + auth['username'], + auth['password'] + ); + if (!instance) return []; + + const response = await httpClient.sendRequest<{ + success: boolean; + result: Record[]; + }>({ + method: HttpMethod.GET, + url: `${(auth as VTigerAuthValue)['instance_url']}/webservice.php`, + queryParams: { + sessionName: instance.sessionId ?? instance.sessionName, + operation: 'query', + elementType: elementType as unknown as string, + query: `SELECT * FROM ${elementType} LIMIT ${page}, ${limit};`, + }, + }); + + if (response.body.success) { + return response.body.result; + } + + return []; +}; + +export const countRecords = async ( + auth: VTigerAuthValue, + elementType: string +) => { + const instance = await instanceLogin( + auth['instance_url'], + auth['username'], + auth['password'] + ); + if (!instance) return 0; + + const response = await httpClient.sendRequest<{ + success: boolean; + result: { count: string }[]; + }>({ + method: HttpMethod.GET, + url: `${(auth as VTigerAuthValue)['instance_url']}/webservice.php`, + queryParams: { + sessionName: instance.sessionId ?? instance.sessionName, + operation: 'query', + elementType: elementType as unknown as string, + query: `SELECT count(*) FROM ${elementType};`, + }, + }); + + if (response.body.success) { + return Number.parseInt(response.body.result[0].count); + } + + return 0; +}; + +export const generateElementFields = async ( + auth: VTigerAuthValue, + elementType: string, + defaultValue: Record, + skipMandatory = false +): Promise => { + const instance = await instanceLogin( + auth['instance_url'], + auth['username'], + auth['password'] + ); + if (!instance) return {}; + + const describe_response = await httpClient.sendRequest<{ + success: boolean; + result: { fields: Field[] }; + }>({ + method: HttpMethod.GET, + url: `${auth['instance_url']}/webservice.php`, + queryParams: { + sessionName: instance.sessionId ?? instance.sessionName, + operation: 'describe', + elementType: elementType, + }, + }); + + const fields: DynamicPropsValue = {}; + + if (describe_response.body.success) { + const generateField = async (field: Field) => { + const params = { + displayName: field.label, + description: `Field ${field.name} of object type ${elementType}`, + required: !skipMandatory ? field.mandatory : false, + }; + + if ( + ['string', 'text', 'mediumtext', 'phone', 'url', 'email'].includes( + field.type.name + ) + ) { + if (['mediumtext', 'url'].includes(field.type.name)) { + fields[field.name] = Property.LongText({ + ...params, + defaultValue: defaultValue?.[field.name] as string, + }); + } else { + fields[field.name] = Property.ShortText({ + ...params, + defaultValue: defaultValue?.[field.name] as string, + }); + } + } else if (['picklist', 'reference', 'owner'].includes(field.type.name)) { + let options: DropdownState; + if (field.type.name === 'picklist') { + options = { + disabled: false, + options: field.type.picklistValues ?? [], + }; + } else if (field.type.name === 'owner') { + options = await getRecordReference( + auth as PiecePropValueSchema, + ['Users'] + ); + } else if (field.type.refersTo) { + options = await getRecordReference( + auth as PiecePropValueSchema, + field.type.refersTo ?? [] + ); + } else { + options = { disabled: false, options: [] }; + } + + fields[field.name] = Property.StaticDropdown({ + ...params, + defaultValue: defaultValue?.[field.name] as string, + options, + }); + } else if (['double', 'integer', 'currency'].includes(field.type.name)) { + fields[field.name] = Property.Number({ + ...params, + defaultValue: defaultValue?.[field.name] as number, + }); + } else if (['boolean'].includes(field.type.name)) { + fields[field.name] = Property.Checkbox({ + displayName: field.label, + description: `The fields to fill in the object type ${elementType}`, + required: !skipMandatory ? field.mandatory : false, + defaultValue: defaultValue?.[field.name] as boolean, + }); + } else if (['date', 'datetime', 'time'].includes(field.type.name)) { + fields[field.name] = Property.DateTime({ + displayName: field.label, + description: `The fields to fill in the object type ${elementType}`, + defaultValue: defaultValue?.[field.name] as string, + required: !skipMandatory ? field.mandatory : false, + }); + } + }; + + for (const field of describe_response.body.result.fields) { + if ( + [ + 'id', + 'modifiedtime', + 'createdtime', + 'modifiedby', + 'created_user_id', + ].includes(field.name) + ) { + continue; + } + + await generateField(field); + } + } + + return fields; +}; diff --git a/packages/pieces/community/vtiger/src/lib/models.ts b/packages/pieces/community/vtiger/src/lib/models.ts new file mode 100644 index 0000000..aca2dce --- /dev/null +++ b/packages/pieces/community/vtiger/src/lib/models.ts @@ -0,0 +1,23 @@ +export interface Challenge { + token: string; // Challenge token to be used for login. + serverTime: string; // Current Server time + expireTime: string; +} + +export interface Instance { + sessionId: string; + sessionName: string; + userId: string; + version: string; + vtigerVersion: string; + username: string; + first_name: string; + last_name: string; + email: string; + time_zone: string; + hour_format: string; + date_format: string; + is_admin: string; + call_duration: string; + other_event_duration: string; +} diff --git a/packages/pieces/community/vtiger/src/lib/triggers/new-or-updated-record.ts b/packages/pieces/community/vtiger/src/lib/triggers/new-or-updated-record.ts new file mode 100644 index 0000000..1b1d7b7 --- /dev/null +++ b/packages/pieces/community/vtiger/src/lib/triggers/new-or-updated-record.ts @@ -0,0 +1,152 @@ +import { + DedupeStrategy, + Polling, + httpClient, + pollingHelper, +} from '@activepieces/pieces-common'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { vtigerAuth } from '../..'; +import { + elementTypeProperty, + instanceLogin, + prepareHttpRequest, +} from '../common'; +import dayjs from 'dayjs'; + +export const newOrUpdatedRecord = createTrigger({ + auth: vtigerAuth, + name: 'new_or_updated_record', + displayName: 'New or Updated Record', + description: + 'Triggers when a new record is introduced or a record is updated.', + props: { + elementType: elementTypeProperty, + watchBy: Property.StaticDropdown({ + displayName: 'Watch By', + description: 'Column to watch for trigger', + required: true, + options: { + options: [ + { value: 'createdtime', label: 'Created Time' }, + { value: 'modifiedtime', label: 'Modified Time' }, + ], + }, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Enter the maximum number of records to return.', + defaultValue: 100, + required: true, + }), + }, + sampleData: { + success: true, + result: [ + { + id: '3x291', + createdtime: '2020-07-22 12:46:55', + modifiedtime: '2020-07-22 12:46:55', + }, + ], + }, + type: TriggerStrategy.POLLING, + async test(ctx) { + return await pollingHelper.test(polling, { ...ctx }); + }, + async onEnable(ctx) { + await pollingHelper.onEnable(polling, { ...ctx }); + }, + async onDisable(ctx) { + await pollingHelper.onDisable(polling, { ...ctx }); + }, + async run(ctx) { + return await pollingHelper.poll(polling, { ...ctx }); + }, +}); + +const polling: Polling< + PiecePropValueSchema, + { elementType?: string; watchBy?: string; limit?: number } +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + const items = await fetchRecords({ auth, propsValue, lastFetchEpochMS }); + + return (items ?? []).map((item) => { + return { + epochMilliSeconds: dayjs( + propsValue.watchBy === 'createdtime' + ? item['createdtime'] + : item['modifiedtime'] + ).valueOf(), + data: item, + }; + }); + }, +}; + +const fetchRecords = async ({ + auth, + propsValue, + lastFetchEpochMS, +}: { + auth: Record; + propsValue: Record; + lastFetchEpochMS: number; +}) => { + const vtigerInstance = await instanceLogin( + auth['instance_url'], + auth['username'], + auth['password'] + ); + if (vtigerInstance === null) { + return []; + } + + const query = `SELECT * FROM ${propsValue['elementType']} ;`; + + const httpRequest = prepareHttpRequest( + auth['instance_url'], + vtigerInstance.sessionId ?? vtigerInstance.sessionName, + 'query', + { query } + ); + const response = await httpClient.sendRequest<{ + success: boolean; + result: Record[]; + }>(httpRequest); + + if (response.body.success) { + const lastFetch = dayjs(lastFetchEpochMS); + const records = response.body.result; + const limit = propsValue['limit'] as number; + + const newOrUpdatedRecords = records.filter((record) => { + const watchTime = dayjs(record[propsValue['watchBy'] as string] ?? 0); + return watchTime.diff(lastFetch) >= 0; + }); + const sortedRecords = newOrUpdatedRecords.sort((a, b) => { + const key = propsValue['watchBy'] as string; + if (a[key] < b[key]) { + return -1; + } + if (a[key] > b[key]) { + return 1; + } + return 0; + }); + + if (limit > 0) { + return sortedRecords.slice(0, limit); + } else { + return sortedRecords; + } + } + + return []; +}; diff --git a/packages/pieces/community/vtiger/tsconfig.json b/packages/pieces/community/vtiger/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/vtiger/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/vtiger/tsconfig.lib.json b/packages/pieces/community/vtiger/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/vtiger/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/webflow/.eslintrc.json b/packages/pieces/community/webflow/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/webflow/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/webflow/README.md b/packages/pieces/community/webflow/README.md new file mode 100644 index 0000000..ab13bfa --- /dev/null +++ b/packages/pieces/community/webflow/README.md @@ -0,0 +1,7 @@ +# pieces-webflow + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-webflow` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/webflow/package.json b/packages/pieces/community/webflow/package.json new file mode 100644 index 0000000..f725e96 --- /dev/null +++ b/packages/pieces/community/webflow/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-webflow", + "version": "0.1.5" +} \ No newline at end of file diff --git a/packages/pieces/community/webflow/project.json b/packages/pieces/community/webflow/project.json new file mode 100644 index 0000000..757a075 --- /dev/null +++ b/packages/pieces/community/webflow/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-webflow", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/webflow/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/webflow", + "tsConfig": "packages/pieces/community/webflow/tsconfig.lib.json", + "packageJson": "packages/pieces/community/webflow/package.json", + "main": "packages/pieces/community/webflow/src/index.ts", + "assets": [ + "packages/pieces/community/webflow/*.md", + { + "input": "packages/pieces/community/webflow/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/webflow/src/index.ts b/packages/pieces/community/webflow/src/index.ts new file mode 100644 index 0000000..0fd81db --- /dev/null +++ b/packages/pieces/community/webflow/src/index.ts @@ -0,0 +1,57 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { OAuth2PropertyValue, PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { webflowCreateCollectionItemAction } from './lib/actions/create-collection-item'; +import { webflowDeleteCollectionItem } from './lib/actions/delete-collection-item'; +import { webflowFindCollectionItem } from './lib/actions/find-collection-item'; +import { webflowFindOrder } from './lib/actions/find-order'; +import { webflowFulfillOrder } from './lib/actions/fulfill-order'; +import { webflowGetCollectionItem } from './lib/actions/get-collection-item'; +import { webflowRefundOrder } from './lib/actions/refund-order'; +import { webflowUnfulfillOrder } from './lib/actions/unfulfill-order'; +import { webflowUpdateCollectionItem } from './lib/actions/update-collection-item'; +import { webflowNewSubmission } from './lib/triggers/new-form-submitted'; + +export const webflowAuth = PieceAuth.OAuth2({ + description: '', + authUrl: 'https://webflow.com/oauth/authorize', + tokenUrl: 'https://api.webflow.com/oauth/access_token', + required: true, + scope: ['webhooks:write', 'forms:read'], +}); + +export const webflow = createPiece({ + displayName: 'Webflow', + description: 'Design, build, and launch responsive websites visually', + minimumSupportedRelease: '0.5.0', + logoUrl: 'https://cdn.activepieces.com/pieces/webflow.png', + categories: [PieceCategory.MARKETING], + authors: [ + 'Ahmad-AbuOsbeh', + 'TaskMagicKyle', + 'kishanprmr', + 'MoShizzle', + 'khaledmashaly', + 'abuaboud', + ], + auth: webflowAuth, + actions: [ + webflowCreateCollectionItemAction, + webflowDeleteCollectionItem, + webflowUpdateCollectionItem, + webflowFindCollectionItem, + webflowGetCollectionItem, + webflowFulfillOrder, + webflowUnfulfillOrder, + webflowRefundOrder, + webflowFindOrder, + createCustomApiCallAction({ + baseUrl: () => 'https://api.webflow.com', + auth: webflowAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [webflowNewSubmission], +}); diff --git a/packages/pieces/community/webflow/src/lib/actions/create-collection-item.ts b/packages/pieces/community/webflow/src/lib/actions/create-collection-item.ts new file mode 100644 index 0000000..41d9719 --- /dev/null +++ b/packages/pieces/community/webflow/src/lib/actions/create-collection-item.ts @@ -0,0 +1,62 @@ +import { createAction, DynamicPropsValue, Property } from '@activepieces/pieces-framework'; + +import { webflowAuth } from '../..'; +import { webflowProps } from '../common/props'; +import { WebflowApiClient } from '../common/client'; + +export const webflowCreateCollectionItemAction = createAction({ + auth: webflowAuth, + name: 'create_collection_item', + displayName: 'Create Collection Item', + description: 'Creates new collection item.', + props: { + site_id: webflowProps.site_id, + collection_id: webflowProps.collection_id, + collection_fields: webflowProps.collection_fields, + is_archived: Property.Checkbox({ + displayName: 'Is Archived', + description: 'Whether the item is archived or not', + required: false, + }), + is_draft: Property.Checkbox({ + displayName: 'Is Draft', + description: 'Whether the item is a draft or not', + required: false, + }), + }, + async run(context) { + const collectionId = context.propsValue.collection_id; + const isArchived = context.propsValue.is_archived; + const isDraft = context.propsValue.is_draft; + const collectionInputFields = context.propsValue.collection_fields; + + const client = new WebflowApiClient(context.auth.access_token); + const { fields: CollectionFields } = await client.getCollection(collectionId); + + const formattedCollectionFields: DynamicPropsValue = {}; + for (const field of CollectionFields) { + const fieldValue = collectionInputFields[field.slug]; + + if (fieldValue !== undefined && fieldValue !== '') { + switch (field.type) { + case 'ImageRef': + case 'FileRef': + formattedCollectionFields[field.slug] = { url: fieldValue }; + break; + case 'Set': + formattedCollectionFields[field.slug] = fieldValue.map((url: string) => ({ url: url })); + break; + case 'Number': + formattedCollectionFields[field.slug] = Number(fieldValue); + break; + default: + formattedCollectionFields[field.slug] = fieldValue; + } + } + } + + return await client.createCollectionItem(collectionId, { + fields: { ...formattedCollectionFields, _archived: isArchived, _draft: isDraft }, + }); + }, +}); diff --git a/packages/pieces/community/webflow/src/lib/actions/delete-collection-item.ts b/packages/pieces/community/webflow/src/lib/actions/delete-collection-item.ts new file mode 100644 index 0000000..ecf2dfb --- /dev/null +++ b/packages/pieces/community/webflow/src/lib/actions/delete-collection-item.ts @@ -0,0 +1,26 @@ +import { createAction } from '@activepieces/pieces-framework'; + +import { webflowAuth } from '../..'; +import { webflowProps } from '../common/props'; +import { WebflowApiClient } from '../common/client'; + +export const webflowDeleteCollectionItem = createAction({ + auth: webflowAuth, + name: 'delete_collection_item', + description: 'Delete collection item', + displayName: 'Delete an item in a collection', + props: { + site_id: webflowProps.site_id, + collection_id: webflowProps.collection_id, + collection_item_id: webflowProps.collection_item_id, + }, + + async run(context) { + const collectionId = context.propsValue.collection_id; + const collectionItemId = context.propsValue.collection_item_id; + + const client = new WebflowApiClient(context.auth.access_token); + + return await client.deleteCollectionItem(collectionId, collectionItemId); + }, +}); diff --git a/packages/pieces/community/webflow/src/lib/actions/find-collection-item.ts b/packages/pieces/community/webflow/src/lib/actions/find-collection-item.ts new file mode 100644 index 0000000..4fea1c0 --- /dev/null +++ b/packages/pieces/community/webflow/src/lib/actions/find-collection-item.ts @@ -0,0 +1,70 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { webflowAuth } from '../..'; +import { webflowProps } from '../common/props'; + +export const webflowFindCollectionItem = createAction({ + auth: webflowAuth, + name: 'find_collection_item', + description: 'Find collection item in a collection by field', + displayName: 'Find a Collection Item by Field', + props: { + site_id: webflowProps.site_id, + collection_id: webflowProps.collection_id, + field_name: Property.ShortText({ + displayName: 'Field Name', + description: 'The name of the field to search by', + required: true, + }), + field_value: Property.ShortText({ + displayName: 'Field Value', + description: 'The value of the field to search for', + required: true, + }), + max_results: Property.Number({ + displayName: 'Max Results', + description: 'The maximum number of results to return', + required: false, + }), + }, + + async run(configValue) { + const accessToken = configValue.auth['access_token']; + const collectionId = configValue.propsValue['collection_id']; + const fieldName = configValue.propsValue['field_name']; + const fieldValue = configValue.propsValue['field_value']; + const maxResults = configValue.propsValue['max_results']; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `https://api.webflow.com/collections/${collectionId}/items`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }; + + try { + const res = await httpClient.sendRequest(request); + if (res.status !== 200) { + throw new Error('Failed to fetch collection items'); + } + + const items = res.body.items; + const matches = items + .filter((item: any) => { + return item.fields[fieldName] === fieldValue; + }) + .slice(0, maxResults); + + return { success: true, result: matches }; + } catch (err) { + return { success: false, message: err }; + } + }, +}); diff --git a/packages/pieces/community/webflow/src/lib/actions/find-order.ts b/packages/pieces/community/webflow/src/lib/actions/find-order.ts new file mode 100644 index 0000000..873d867 --- /dev/null +++ b/packages/pieces/community/webflow/src/lib/actions/find-order.ts @@ -0,0 +1,25 @@ +import { createAction } from '@activepieces/pieces-framework'; + +import { webflowAuth } from '../..'; +import { webflowProps } from '../common/props'; +import { WebflowApiClient } from '../common/client'; + +export const webflowFindOrder = createAction({ + auth: webflowAuth, + name: 'find_order', + description: 'Find order', + displayName: 'Find an order', + props: { + site_id: webflowProps.site_id, + order_id: webflowProps.order_id, + }, + + async run(context) { + const orderId = context.propsValue.order_id; + const siteId = context.propsValue.site_id; + + const client = new WebflowApiClient(context.auth.access_token); + + return await client.getOrder(siteId, orderId); + }, +}); diff --git a/packages/pieces/community/webflow/src/lib/actions/fulfill-order.ts b/packages/pieces/community/webflow/src/lib/actions/fulfill-order.ts new file mode 100644 index 0000000..7b1c12f --- /dev/null +++ b/packages/pieces/community/webflow/src/lib/actions/fulfill-order.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { webflowAuth } from '../..'; +import { webflowProps } from '../common/props'; +import { WebflowApiClient } from '../common/client'; + +export const webflowFulfillOrder = createAction({ + auth: webflowAuth, + name: 'fulfill_order', + description: 'Fulfill order', + displayName: 'Fulfill an order', + props: { + site_id: webflowProps.site_id, + order_id: webflowProps.order_id, + send_order_fulfilled_email: Property.Checkbox({ + displayName: 'Send Order Fulfilled Email', + description: 'Send an email to the customer that their order has been fulfilled', + required: false, + }), + }, + + async run(context) { + const orderId = context.propsValue.order_id; + const siteId = context.propsValue.site_id; + const sendOrderFulfilledEmail = context.propsValue.send_order_fulfilled_email; + + const client = new WebflowApiClient(context.auth.access_token); + + return await client.fulfillOrder(siteId, orderId, { sendOrderFulfilledEmail }); + }, +}); diff --git a/packages/pieces/community/webflow/src/lib/actions/get-collection-item.ts b/packages/pieces/community/webflow/src/lib/actions/get-collection-item.ts new file mode 100644 index 0000000..5b737b3 --- /dev/null +++ b/packages/pieces/community/webflow/src/lib/actions/get-collection-item.ts @@ -0,0 +1,26 @@ +import { createAction } from '@activepieces/pieces-framework'; + +import { webflowAuth } from '../..'; +import { webflowProps } from '../common/props'; +import { WebflowApiClient } from '../common/client'; + +export const webflowGetCollectionItem = createAction({ + auth: webflowAuth, + name: 'get_collection_item', + description: 'Get collection item in a collection by ID', + displayName: 'Get a Collection Item by ID', + props: { + site_id: webflowProps.site_id, + collection_id: webflowProps.collection_id, + collection_item_id: webflowProps.collection_item_id, + }, + + async run(context) { + const collectionId = context.propsValue.collection_id; + const collectionItemId = context.propsValue.collection_item_id; + + const client = new WebflowApiClient(context.auth.access_token); + + return await client.getCollectionItem(collectionId, collectionItemId); + }, +}); diff --git a/packages/pieces/community/webflow/src/lib/actions/publish-collection-item.ts b/packages/pieces/community/webflow/src/lib/actions/publish-collection-item.ts new file mode 100644 index 0000000..b5632c8 --- /dev/null +++ b/packages/pieces/community/webflow/src/lib/actions/publish-collection-item.ts @@ -0,0 +1,26 @@ +import { createAction } from '@activepieces/pieces-framework'; + +import { webflowAuth } from '../..'; +import { webflowProps } from '../common/props'; +import { WebflowApiClient } from '../common/client'; + +export const webflowPublishCollectionItem = createAction({ + auth: webflowAuth, + name: 'publish_collection_item', + description: 'Publish collection item', + displayName: 'Publish a Collection Item', + props: { + site_id: webflowProps.site_id, + collection_id: webflowProps.collection_id, + collection_item_id: webflowProps.collection_item_id, + }, + + async run(context) { + const collectionId = context.propsValue.collection_id; + const collectionItemId = context.propsValue.collection_item_id; + + const client = new WebflowApiClient(context.auth.access_token); + + return await client.publishCollectionItem(collectionId, collectionItemId); + }, +}); diff --git a/packages/pieces/community/webflow/src/lib/actions/refund-order.ts b/packages/pieces/community/webflow/src/lib/actions/refund-order.ts new file mode 100644 index 0000000..2b211c1 --- /dev/null +++ b/packages/pieces/community/webflow/src/lib/actions/refund-order.ts @@ -0,0 +1,47 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { webflowAuth } from '../..'; +import { webflowProps } from '../common/props'; +import { WebflowApiClient } from '../common/client'; + +export const webflowRefundOrder = createAction({ + auth: webflowAuth, + name: 'refund_order', + description: 'Refund order', + displayName: 'Refund an order', + props: { + site_id: webflowProps.site_id, + order_id: webflowProps.order_id, + // reason: Property.StaticDropdown({ + // displayName: 'Reason', + // description: 'The reason for the refund', + // required: false, + // options: { + // disabled: false, + // options: [ + // { + // label: 'Duplicate', + // value: 'duplicate', + // }, + // { + // label: 'Fraudulent', + // value: 'fraudulent', + // }, + // { + // label: 'Requested', + // value: 'requested', + // }, + // ], + // }, + // }), + }, + + async run(context) { + const orderId = context.propsValue.order_id; + const siteId = context.propsValue.site_id; + + const client = new WebflowApiClient(context.auth.access_token); + + return await client.refundOrder(siteId, orderId); + }, +}); diff --git a/packages/pieces/community/webflow/src/lib/actions/unfulfill-order.ts b/packages/pieces/community/webflow/src/lib/actions/unfulfill-order.ts new file mode 100644 index 0000000..7cb5699 --- /dev/null +++ b/packages/pieces/community/webflow/src/lib/actions/unfulfill-order.ts @@ -0,0 +1,25 @@ +import { createAction } from '@activepieces/pieces-framework'; + +import { webflowAuth } from '../..'; +import { webflowProps } from '../common/props'; +import { WebflowApiClient } from '../common/client'; + +export const webflowUnfulfillOrder = createAction({ + auth: webflowAuth, + name: 'unfulfill_order', + description: 'Unfulfill order', + displayName: 'Unfulfill an order', + props: { + site_id: webflowProps.site_id, + order_id: webflowProps.order_id, + }, + + async run(context) { + const orderId = context.propsValue.order_id; + const siteId = context.propsValue.site_id; + + const client = new WebflowApiClient(context.auth.access_token); + + return await client.unfulfillOrder(siteId, orderId); + }, +}); diff --git a/packages/pieces/community/webflow/src/lib/actions/update-collection-item.ts b/packages/pieces/community/webflow/src/lib/actions/update-collection-item.ts new file mode 100644 index 0000000..f7f7003 --- /dev/null +++ b/packages/pieces/community/webflow/src/lib/actions/update-collection-item.ts @@ -0,0 +1,74 @@ +import { createAction, DynamicPropsValue, Property } from '@activepieces/pieces-framework'; + +import { webflowAuth } from '../..'; +import { webflowProps } from '../common/props'; +import { WebflowApiClient } from '../common/client'; + +export const webflowUpdateCollectionItem = createAction({ + auth: webflowAuth, + name: 'update_collection_item', + description: 'Update collection item', + displayName: 'Update an item in a collection', + props: { + site_id: webflowProps.site_id, + collection_id: webflowProps.collection_id, + collection_item_id: webflowProps.collection_item_id, + collection_fields: webflowProps.collection_fields, + is_archived: Property.Checkbox({ + displayName: 'Is Archived', + description: 'Whether the item is archived or not', + required: false, + }), + is_draft: Property.Checkbox({ + displayName: 'Is Draft', + description: 'Whether the item is a draft or not', + required: false, + }), + }, + + async run(context) { + const collectionId = context.propsValue.collection_id; + const collectionItemId = context.propsValue.collection_item_id; + const isArchived = context.propsValue.is_archived; + const isDraft = context.propsValue.is_draft; + const collectionInputFields = context.propsValue.collection_fields; + + const client = new WebflowApiClient(context.auth.access_token); + const { fields: CollectionFields } = await client.getCollection(collectionId); + + const formattedCollectionFields: DynamicPropsValue = {}; + for (const field of CollectionFields) { + const fieldValue = collectionInputFields[field.slug]; + + if (fieldValue !== undefined && fieldValue !== '') { + switch (field.type) { + case 'ImageRef': + case 'FileRef': + formattedCollectionFields[field.slug] = { url: fieldValue }; + break; + case 'Set': + if (fieldValue.length > 0) { + formattedCollectionFields[field.slug] = fieldValue.map((url: string) => ({ + url: url, + })); + } + break; + case 'ItemRefSet': + if (fieldValue.length > 0) { + formattedCollectionFields[field.slug] = fieldValue; + } + break; + case 'Number': + formattedCollectionFields[field.slug] = Number(fieldValue); + break; + default: + formattedCollectionFields[field.slug] = fieldValue; + } + } + } + + return await client.updateCollectionItem(collectionId, collectionItemId, { + fields: { ...formattedCollectionFields, _archived: isArchived, _draft: isDraft }, + }); + }, +}); diff --git a/packages/pieces/community/webflow/src/lib/common/client.ts b/packages/pieces/community/webflow/src/lib/common/client.ts new file mode 100644 index 0000000..c8440e0 --- /dev/null +++ b/packages/pieces/community/webflow/src/lib/common/client.ts @@ -0,0 +1,148 @@ +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; + +export class WebflowApiClient { + constructor(private accessToken: string) {} + + async makeRequest( + method: HttpMethod, + resourceUri: string, + query?: Record, + body: any | undefined = undefined, + ): Promise { + const apiUrl = 'https://api.webflow.com'; + const params: QueryParams = {}; + + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + params[key] = String(value); + } + } + } + + const request: HttpRequest = { + method: method, + url: apiUrl + resourceUri, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: this.accessToken, + }, + queryParams: params, + body: body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + } + + async listSites() { + return await this.makeRequest(HttpMethod.GET, '/sites'); + } + + async listCollections(siteId: string) { + return await this.makeRequest(HttpMethod.GET, `/sites/${siteId}/collections`); + } + + async getCollection(collectionId: string) { + return await this.makeRequest(HttpMethod.GET, `/collections/${collectionId}`); + } + + async createCollectionItem(collectionId: string, request: Record) { + return await this.makeRequest( + HttpMethod.POST, + `/collections/${collectionId}/items`, + undefined, + request, + ); + } + + async updateCollectionItem(collectionId: string, itemId: string, request: Record) { + return await this.makeRequest( + HttpMethod.PUT, + `/collections/${collectionId}/items/${itemId}`, + undefined, + request, + ); + } + + async getCollectionItem(collectionId: string, itemId: string) { + return await this.makeRequest(HttpMethod.GET, `/collections/${collectionId}/items/${itemId}`); + } + + async deleteCollectionItem(collectionId: string, itemId: string) { + return await this.makeRequest( + HttpMethod.DELETE, + `/collections/${collectionId}/items/${itemId}`, + ); + } + + async publishCollectionItem(collectionId: string, itemId: string) { + return await this.makeRequest( + HttpMethod.POST, + `/collections/${collectionId}/items/publish`, + undefined, + { itemIds: [itemId] }, + ); + } + + async listCollectionItems(collectionId: string, page: number, limit: number) { + return await this.makeRequest(HttpMethod.GET, `/collections/${collectionId}/items`, { + offset: page, + limit, + }); + } + + async getOrder(siteId: string, orderId: string) { + return await this.makeRequest(HttpMethod.GET, `/sites/${siteId}/orders/${orderId}`); + } + + async fulfillOrder(siteId: string, orderId: string, request: Record) { + return await this.makeRequest( + HttpMethod.POST, + `/sites/${siteId}/orders/${orderId}/fulfill`, + undefined, + request, + ); + } + + async unfulfillOrder(siteId: string, orderId: string) { + return await this.makeRequest( + HttpMethod.POST, + `/sites/${siteId}/orders/${orderId}/unfulfill`, + undefined, + ); + } + + async refundOrder(siteId: string, orderId: string) { + return await this.makeRequest(HttpMethod.POST, `/sites/${siteId}/orders/${orderId}/refund`); + } + + async listOrders(siteId: string, page: number, limit: number) { + return await this.makeRequest(HttpMethod.GET, `/sites/${siteId}/orders`, { + offset: page, + limit, + }); + } + + async createWebhook(siteId: string, triggerType: string, webhookUrl: string) { + return await this.makeRequest( + HttpMethod.POST, + `'/sites/${siteId}/webhooks`, + {}, + { + triggerType, + url: webhookUrl, + }, + ); + } + + async deleteWebhook(webhookId: string) { + return await this.makeRequest(HttpMethod.DELETE, `/webhooks/${webhookId}`); + } +} diff --git a/packages/pieces/community/webflow/src/lib/common/common.ts b/packages/pieces/community/webflow/src/lib/common/common.ts new file mode 100644 index 0000000..e9e592f --- /dev/null +++ b/packages/pieces/community/webflow/src/lib/common/common.ts @@ -0,0 +1,52 @@ +import { Property, OAuth2PropertyValue, DynamicPropsValue } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + AuthenticationType, + httpClient, +} from '@activepieces/pieces-common'; + +export const webflowCommon = { + baseUrl: 'https://api.webflow.com/', + subscribeWebhook: async ( + siteId: string, + tag: string, + webhookUrl: string, + accessToken: string, + ) => { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://api.webflow.com/sites/${siteId}/webhooks`, + headers: { + 'Content-Type': 'application/json', + }, + body: { + triggerType: tag, + url: webhookUrl, + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + queryParams: {}, + }; + + const res = await httpClient.sendRequest(request); + return res; + }, + unsubscribeWebhook: async (siteId: string, webhookId: string, accessToken: string) => { + const request: HttpRequest = { + method: HttpMethod.DELETE, + url: `https://api.webflow.com/sites/${siteId}/webhooks/${webhookId}`, + + headers: { + 'Content-Type': 'application/json', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }; + return await httpClient.sendRequest(request); + }, +}; diff --git a/packages/pieces/community/webflow/src/lib/common/props.ts b/packages/pieces/community/webflow/src/lib/common/props.ts new file mode 100644 index 0000000..3418087 --- /dev/null +++ b/packages/pieces/community/webflow/src/lib/common/props.ts @@ -0,0 +1,228 @@ +import { + DropdownOption, + DynamicPropsValue, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; + +import { WebflowApiClient } from './client'; +import { webflowAuth } from '../..'; + +export const webflowProps = { + site_id: Property.Dropdown({ + displayName: 'Site', + required: true, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect account first.', + }; + } + const authValue = auth as PiecePropValueSchema; + const client = new WebflowApiClient(authValue.access_token); + + const sites = await client.listSites(); + + const options: DropdownOption[] = []; + for (const site of sites) { + options.push({ label: site.name, value: site._id }); + } + + return { + disabled: false, + options, + }; + }, + }), + collection_id: Property.Dropdown({ + displayName: 'Collection', + required: true, + refreshers: ['site_id'], + options: async ({ auth, site_id }) => { + if (!auth || !site_id) { + return { + disabled: true, + options: [], + placeholder: 'Please connect account first.', + }; + } + const authValue = auth as PiecePropValueSchema; + const client = new WebflowApiClient(authValue.access_token); + + const collections = await client.listCollections(site_id as string); + + const options: DropdownOption[] = []; + for (const collection of collections) { + options.push({ label: collection.name, value: collection._id }); + } + + return { + disabled: false, + options, + }; + }, + }), + collection_fields: Property.DynamicProperties({ + displayName: 'Collection Fields', + required: true, + refreshers: ['collection_id'], + props: async ({ auth, collection_id }) => { + if (!auth) return {}; + if (!collection_id) return {}; + + const collectionFields: DynamicPropsValue = {}; + const authValue = auth as PiecePropValueSchema; + const client = new WebflowApiClient(authValue.access_token); + + const { fields } = await client.getCollection(collection_id as unknown as string); + + for (const field of fields) { + if (field.editable && field.slug !== '_archived' && field.slug !== '_draft') { + switch (field.type) { + case 'Option': + collectionFields[field.slug] = Property.StaticDropdown({ + displayName: field.name, + required: field.required, + options: { + disabled: false, + options: field.validations.options.map((option: { name: string }) => { + return { + label: option.name, + value: option.name, + }; + }), + }, + }); + break; + case 'RichText': + case 'Email': + case 'PlainText': + case 'Phone': + case 'Link': + case 'Video': + case 'Color': + case 'ItemRef': + case 'FileRef': + collectionFields[field.slug] = Property.ShortText({ + displayName: field.name, + required: field.required, + }); + break; + case 'ImageRef': + collectionFields[field.slug] = Property.ShortText({ + displayName: field.name, + required: field.required, + description: + 'Images must be hosted on a publicly accessible URL to be uploaded via the API.The maximum file size for images is 4MB.', + }); + break; + case 'Set': + collectionFields[field.slug] = Property.Array({ + displayName: field.name, + required: field.required, + description: + ' Images must be hosted on a publicly accessible URL to be uploaded via the API.The maximum file size for images is 4MB.', + }); + break; + case 'ItemRefSet': + collectionFields[field.slug] = Property.Array({ + displayName: field.name, + required: field.required, + }); + break; + case 'Number': + collectionFields[field.slug] = Property.Number({ + displayName: field.name, + required: field.required, + }); + break; + case 'Date': + collectionFields[field.slug] = Property.DateTime({ + displayName: field.name, + required: field.required, + }); + break; + case 'Bool': + collectionFields[field.slug] = Property.Checkbox({ + displayName: field.name, + required: false, + }); + break; + } + } + } + return collectionFields; + }, + }), + collection_item_id: Property.Dropdown({ + displayName: 'Collection Item', + required: true, + refreshers: ['collection_id'], + options: async ({ auth, collection_id }) => { + if (!auth || !collection_id) { + return { + disabled: true, + options: [], + placeholder: 'Please connect account first.', + }; + } + const authValue = auth as PiecePropValueSchema; + const client = new WebflowApiClient(authValue.access_token); + + const options: DropdownOption[] = []; + + let page = 0; + let response; + do { + response = await client.listCollectionItems(collection_id as string, page, 100); + page += 100; + + for (const item of response.items) { + options.push({ label: item.name, value: item._id }); + } + } while (response.items.length > 0); + + return { + disabled: false, + options, + }; + }, + }), + order_id: Property.Dropdown({ + displayName: 'Order', + required: true, + refreshers: ['site_id'], + options: async ({ auth, site_id }) => { + if (!auth || !site_id) { + return { + disabled: true, + options: [], + placeholder: 'Please connect account first.', + }; + } + const authValue = auth as PiecePropValueSchema; + const client = new WebflowApiClient(authValue.access_token); + + const options: DropdownOption[] = []; + + let page = 0; + let response; + do { + response = await client.listOrders(site_id as string, page, 100); + page += 100; + + for (const order of response) { + options.push({ label: order.orderId, value: order.orderId }); + } + } while (response.length > 0); + + return { + disabled: false, + options, + }; + }, + }), +}; diff --git a/packages/pieces/community/webflow/src/lib/triggers/new-form-submitted.ts b/packages/pieces/community/webflow/src/lib/triggers/new-form-submitted.ts new file mode 100644 index 0000000..4841a6c --- /dev/null +++ b/packages/pieces/community/webflow/src/lib/triggers/new-form-submitted.ts @@ -0,0 +1,79 @@ +import { webflowCommon } from '../common/common'; +import { createTrigger, TriggerStrategy, Property } from '@activepieces/pieces-framework'; +import { getAccessTokenOrThrow } from '@activepieces/pieces-common'; +import { webflowAuth } from '../..'; +import { webflowProps } from '../common/props'; + +const triggerNameInStore = 'webflow_created_form_submissions_trigger'; + +export const webflowNewSubmission = createTrigger({ + auth: webflowAuth, + + name: 'new_submission', + displayName: 'New Submission', + description: 'Triggers when Webflow Site receives a new submission', + props: { + site_id: webflowProps.site_id, + formName: Property.ShortText({ + displayName: 'Form Name', + required: false, + description: 'Copy from the form settings, or from one of the responses', + }), + }, + type: TriggerStrategy.WEBHOOK, + // TODO remove and force testing as the data can be custom. + sampleData: { + name: 'Sample Form', + site: '62749158efef318abc8d5a0f', + data: { + field_one: 'mock valued', + }, + d: '2022-09-14T12:35:16.117Z', + _id: '6321ca84df3949bfc6752327', + }, + async onEnable(context) { + const formSubmissionTag = 'form_submission'; + + const res = await webflowCommon.subscribeWebhook( + context.propsValue['site_id']!, + formSubmissionTag, + context.webhookUrl, + getAccessTokenOrThrow(context.auth), + ); + await context.store?.put(triggerNameInStore, { + webhookId: res.body._id, + }); + }, + async onDisable(context) { + const response = await context.store?.get(triggerNameInStore); + if (response !== null && response !== undefined) { + await webflowCommon.unsubscribeWebhook( + context.propsValue['site_id']!, + response.webhookId, + getAccessTokenOrThrow(context.auth), + ); + } + }, + async run(context) { + const body = context.payload.body as PayloadBody; + const { formName } = context.propsValue; + //if formName provided, trigger only required formName if it's matched; else trigger all forms in selected webflow site. + if (formName) { + if (body.name == formName) { + return [body]; + } else { + return []; + } + } else { + return [body]; + } + }, +}); + +interface WebhookInformation { + webhookId: string; +} + +type PayloadBody = { + name: string; +}; diff --git a/packages/pieces/community/webflow/tsconfig.json b/packages/pieces/community/webflow/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/webflow/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/webflow/tsconfig.lib.json b/packages/pieces/community/webflow/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/webflow/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/webhook/.eslintrc.json b/packages/pieces/community/webhook/.eslintrc.json new file mode 100644 index 0000000..359ff63 --- /dev/null +++ b/packages/pieces/community/webhook/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/README.md b/packages/pieces/community/webhook/README.md new file mode 100644 index 0000000..8b3dda2 --- /dev/null +++ b/packages/pieces/community/webhook/README.md @@ -0,0 +1,7 @@ +# pieces-webhook + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-webhook` to build the library. diff --git a/packages/pieces/community/webhook/package.json b/packages/pieces/community/webhook/package.json new file mode 100644 index 0000000..5faead1 --- /dev/null +++ b/packages/pieces/community/webhook/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-webhook", + "version": "0.1.15" +} diff --git a/packages/pieces/community/webhook/project.json b/packages/pieces/community/webhook/project.json new file mode 100644 index 0000000..5f7151c --- /dev/null +++ b/packages/pieces/community/webhook/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-webhook", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/webhook/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/webhook", + "tsConfig": "packages/pieces/community/webhook/tsconfig.lib.json", + "packageJson": "packages/pieces/community/webhook/package.json", + "main": "packages/pieces/community/webhook/src/index.ts", + "assets": [ + "packages/pieces/community/webhook/*.md", + { + "input": "packages/pieces/community/webhook/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-webhook {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/ar.json b/packages/pieces/community/webhook/src/i18n/ar.json new file mode 100644 index 0000000..3c0b266 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/ar.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Receive HTTP requests and trigger flows using unique URLs.", + "Return Response": "Return Response", + "Respond and Wait for Next Webhook": "Respond and Wait for Next Webhook", + "return a response": "return a response", + "return a response and wait for the next webhook to resume the flow": "return a response and wait for the next webhook to resume the flow", + "Response Type": "Response Type", + "Response": "Response", + "Flow Execution": "Flow Execution", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ", + "JSON": "JSON", + "Raw": "Raw", + "Redirect": "Redirect", + "Stop": "Stop", + "Respond and Continue": "Respond and Continue", + "Catch Webhook": "Catch Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.", + "Authentication": "Authentication", + "Authentication Fields": "Authentication Fields", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "None", + "Basic Auth": "Basic Auth", + "Header Auth": "Header Auth" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/bg.json b/packages/pieces/community/webhook/src/i18n/bg.json new file mode 100644 index 0000000..3c0b266 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/bg.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Receive HTTP requests and trigger flows using unique URLs.", + "Return Response": "Return Response", + "Respond and Wait for Next Webhook": "Respond and Wait for Next Webhook", + "return a response": "return a response", + "return a response and wait for the next webhook to resume the flow": "return a response and wait for the next webhook to resume the flow", + "Response Type": "Response Type", + "Response": "Response", + "Flow Execution": "Flow Execution", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ", + "JSON": "JSON", + "Raw": "Raw", + "Redirect": "Redirect", + "Stop": "Stop", + "Respond and Continue": "Respond and Continue", + "Catch Webhook": "Catch Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.", + "Authentication": "Authentication", + "Authentication Fields": "Authentication Fields", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "None", + "Basic Auth": "Basic Auth", + "Header Auth": "Header Auth" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/ca.json b/packages/pieces/community/webhook/src/i18n/ca.json new file mode 100644 index 0000000..3c0b266 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/ca.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Receive HTTP requests and trigger flows using unique URLs.", + "Return Response": "Return Response", + "Respond and Wait for Next Webhook": "Respond and Wait for Next Webhook", + "return a response": "return a response", + "return a response and wait for the next webhook to resume the flow": "return a response and wait for the next webhook to resume the flow", + "Response Type": "Response Type", + "Response": "Response", + "Flow Execution": "Flow Execution", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ", + "JSON": "JSON", + "Raw": "Raw", + "Redirect": "Redirect", + "Stop": "Stop", + "Respond and Continue": "Respond and Continue", + "Catch Webhook": "Catch Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.", + "Authentication": "Authentication", + "Authentication Fields": "Authentication Fields", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "None", + "Basic Auth": "Basic Auth", + "Header Auth": "Header Auth" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/de.json b/packages/pieces/community/webhook/src/i18n/de.json new file mode 100644 index 0000000..ff86230 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/de.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Erhalten Sie HTTP-Anfragen und lösen Sie Ströme mittels eindeutiger URLs.", + "Return Response": "Rückgabeantwort", + "Respond and Wait for Next Webhook": "Antworte und warte auf nächsten Webhook", + "return a response": "eine Antwort zurückgeben", + "return a response and wait for the next webhook to resume the flow": "eine Antwort zurückgeben und warten, bis der nächste Webhook den Fluss fortsetzt", + "Response Type": "Antworttyp", + "Response": "Antwort", + "Flow Execution": "Flussausführung", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Antworte und warte auf den nächsten Webhook**
\n Überprüfe den Antwort-Header (x-activepieces-resume-webhook-url) auf die nächste Webhook-URL und rufe ihn auf, um den Fluss fortzusetzen.
\n ", + "JSON": "JSON", + "Raw": "Rohe", + "Redirect": "Umleitung", + "Stop": "Stoppen", + "Respond and Continue": "Antworten und fortsetzen", + "Catch Webhook": "Webhook fangen", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Erhalten Sie eingehende HTTP/Webhooks mit beliebigen HTTP-Methoden wie GET, POST, PUT, DELETE, etc.", + "Authentication": "Authentifizierung", + "Authentication Fields": "Authentifizierungsfelder", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngeneriere Beispieldaten und löst den veröffentlichten Fluss aus.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronische Anfragen:**\n\nWenn du eine Antwort von diesem Webhook erwartest, füge `/sync` zum Ende der URL hinzu. \nWenn es länger als 30 Sekunden dauert, wird eine 408 Request Timeout Antwort zurückgegeben.\n\nUm Daten zurückzugeben, fügen Sie mit der Rückgabewert Aktion einen Webhook-Schritt in Ihren Fluss ein.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test-URL:**\n\nWenn Sie Sample-Daten erzeugen wollen, ohne den Fluss auszulösen, fügen Sie `/test` an Ihre Webhook-URL an.\n\n", + "None": "Keine", + "Basic Auth": "Einfacher Auth", + "Header Auth": "Kopfzeile Auth" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/es.json b/packages/pieces/community/webhook/src/i18n/es.json new file mode 100644 index 0000000..107d46a --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/es.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Recibir peticiones HTTP y disparar flujos usando URLs únicas.", + "Return Response": "Respuesta de retorno", + "Respond and Wait for Next Webhook": "Respuesta y espera para el próximo webhook", + "return a response": "devolver una respuesta", + "return a response and wait for the next webhook to resume the flow": "devolver una respuesta y esperar a que el próximo webhook reanude el flujo", + "Response Type": "Tipo de respuesta", + "Response": "Respuesta", + "Flow Execution": "Ejecutar Flow", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Responde y espere a Next Webhook**
\n Verifique la cabecera de respuesta (x-activepieces-resume-webhook-url) para la siguiente URL del webhook y llámelo para reanudar el flujo.
\n ", + "JSON": "JSON", + "Raw": "Rápido", + "Redirect": "Redirigir", + "Stop": "Parar", + "Respond and Continue": "Respuesta y continuar", + "Catch Webhook": "Capturar Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Recibir HTTP/webhooks entrantes usando cualquier método HTTP como GET, POST, PUT, DELETE, etc.", + "Authentication": "Autenticación", + "Authentication Fields": "Campos de autenticación", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**URL vivo:**\n```text\n{{webhookUrl}}\n```\ngenerar datos de ejemplo y desencadenadores de flujo publicado.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "Ninguna", + "Basic Auth": "Auth Básica", + "Header Auth": "Auth de cabecera" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/fr.json b/packages/pieces/community/webhook/src/i18n/fr.json new file mode 100644 index 0000000..e73e948 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/fr.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Recevoir des requêtes HTTP et déclencher des flux en utilisant des URL uniques.", + "Return Response": "Réponse de retour", + "Respond and Wait for Next Webhook": "Répondre et attendre le prochain Webhook", + "return a response": "renvoyer une réponse", + "return a response and wait for the next webhook to resume the flow": "retourner une réponse et attendre que le prochain webhook reprenne le flux", + "Response Type": "Type de réponse", + "Response": "Réponse", + "Flow Execution": "Exécution en flux", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Répondre et attendre le prochain Webhook**
\n Vérifiez l'en-tête de réponse (x-activepieces-resume-webhook-url) pour le prochain webhook URL et appelez-le pour reprendre le flux.
\n ", + "JSON": "JSON", + "Raw": "Brute", + "Redirect": "Rediriger", + "Stop": "Arrêter", + "Respond and Continue": "Répondre et continuer", + "Catch Webhook": "Capturer le Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Recevoir des HTTP/webhooks entrants en utilisant n'importe quelle méthode HTTP telle que GET, POST, PUT, DELETE, etc.", + "Authentication": "Authentification", + "Authentication Fields": "Champs d'authentification", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**URL en direct:**\n```text\n{{webhookUrl}}\n```\ngénérer des exemples de données et des triggers publiés flux.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "Aucun", + "Basic Auth": "Authentification basique", + "Header Auth": "Auth de l'en-tête" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/hi.json b/packages/pieces/community/webhook/src/i18n/hi.json new file mode 100644 index 0000000..3c0b266 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/hi.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Receive HTTP requests and trigger flows using unique URLs.", + "Return Response": "Return Response", + "Respond and Wait for Next Webhook": "Respond and Wait for Next Webhook", + "return a response": "return a response", + "return a response and wait for the next webhook to resume the flow": "return a response and wait for the next webhook to resume the flow", + "Response Type": "Response Type", + "Response": "Response", + "Flow Execution": "Flow Execution", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ", + "JSON": "JSON", + "Raw": "Raw", + "Redirect": "Redirect", + "Stop": "Stop", + "Respond and Continue": "Respond and Continue", + "Catch Webhook": "Catch Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.", + "Authentication": "Authentication", + "Authentication Fields": "Authentication Fields", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "None", + "Basic Auth": "Basic Auth", + "Header Auth": "Header Auth" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/hu.json b/packages/pieces/community/webhook/src/i18n/hu.json new file mode 100644 index 0000000..3c0b266 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/hu.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Receive HTTP requests and trigger flows using unique URLs.", + "Return Response": "Return Response", + "Respond and Wait for Next Webhook": "Respond and Wait for Next Webhook", + "return a response": "return a response", + "return a response and wait for the next webhook to resume the flow": "return a response and wait for the next webhook to resume the flow", + "Response Type": "Response Type", + "Response": "Response", + "Flow Execution": "Flow Execution", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ", + "JSON": "JSON", + "Raw": "Raw", + "Redirect": "Redirect", + "Stop": "Stop", + "Respond and Continue": "Respond and Continue", + "Catch Webhook": "Catch Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.", + "Authentication": "Authentication", + "Authentication Fields": "Authentication Fields", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "None", + "Basic Auth": "Basic Auth", + "Header Auth": "Header Auth" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/hy.json b/packages/pieces/community/webhook/src/i18n/hy.json new file mode 100644 index 0000000..3c0b266 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/hy.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Receive HTTP requests and trigger flows using unique URLs.", + "Return Response": "Return Response", + "Respond and Wait for Next Webhook": "Respond and Wait for Next Webhook", + "return a response": "return a response", + "return a response and wait for the next webhook to resume the flow": "return a response and wait for the next webhook to resume the flow", + "Response Type": "Response Type", + "Response": "Response", + "Flow Execution": "Flow Execution", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ", + "JSON": "JSON", + "Raw": "Raw", + "Redirect": "Redirect", + "Stop": "Stop", + "Respond and Continue": "Respond and Continue", + "Catch Webhook": "Catch Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.", + "Authentication": "Authentication", + "Authentication Fields": "Authentication Fields", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "None", + "Basic Auth": "Basic Auth", + "Header Auth": "Header Auth" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/id.json b/packages/pieces/community/webhook/src/i18n/id.json new file mode 100644 index 0000000..3c0b266 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/id.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Receive HTTP requests and trigger flows using unique URLs.", + "Return Response": "Return Response", + "Respond and Wait for Next Webhook": "Respond and Wait for Next Webhook", + "return a response": "return a response", + "return a response and wait for the next webhook to resume the flow": "return a response and wait for the next webhook to resume the flow", + "Response Type": "Response Type", + "Response": "Response", + "Flow Execution": "Flow Execution", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ", + "JSON": "JSON", + "Raw": "Raw", + "Redirect": "Redirect", + "Stop": "Stop", + "Respond and Continue": "Respond and Continue", + "Catch Webhook": "Catch Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.", + "Authentication": "Authentication", + "Authentication Fields": "Authentication Fields", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "None", + "Basic Auth": "Basic Auth", + "Header Auth": "Header Auth" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/it.json b/packages/pieces/community/webhook/src/i18n/it.json new file mode 100644 index 0000000..3c0b266 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/it.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Receive HTTP requests and trigger flows using unique URLs.", + "Return Response": "Return Response", + "Respond and Wait for Next Webhook": "Respond and Wait for Next Webhook", + "return a response": "return a response", + "return a response and wait for the next webhook to resume the flow": "return a response and wait for the next webhook to resume the flow", + "Response Type": "Response Type", + "Response": "Response", + "Flow Execution": "Flow Execution", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ", + "JSON": "JSON", + "Raw": "Raw", + "Redirect": "Redirect", + "Stop": "Stop", + "Respond and Continue": "Respond and Continue", + "Catch Webhook": "Catch Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.", + "Authentication": "Authentication", + "Authentication Fields": "Authentication Fields", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "None", + "Basic Auth": "Basic Auth", + "Header Auth": "Header Auth" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/ja.json b/packages/pieces/community/webhook/src/i18n/ja.json new file mode 100644 index 0000000..f1f41ac --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/ja.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "一意のURLを使用してHTTPリクエストとトリガーフローを受信します。", + "Return Response": "返品対応", + "Respond and Wait for Next Webhook": "応答して次のWebhookを待つ", + "return a response": "レスポンスを返す", + "return a response and wait for the next webhook to resume the flow": "レスポンスを返し、次の webhook がフローを再開するのを待ちます。", + "Response Type": "応答タイプ", + "Response": "回答", + "Flow Execution": "フロー実行", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ", + "JSON": "JSON", + "Raw": "Raw", + "Redirect": "リダイレクト", + "Stop": "停止", + "Respond and Continue": "応答して続ける", + "Catch Webhook": "Webhookをキャッチする", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "GET、POST、PUT、DELETEなどの任意のHTTPメソッドを使用して受信HTTP/Webhookを受信します。", + "Authentication": "認証", + "Authentication Fields": "認証フィールド", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**ライブURL:**\n```text\n{{webhookUrl}}\n```\n生成サンプル データと公開されたフローをトリガーします。\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "なし", + "Basic Auth": "ベーシック認証", + "Header Auth": "ヘッダー認証" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/ko.json b/packages/pieces/community/webhook/src/i18n/ko.json new file mode 100644 index 0000000..3c0b266 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/ko.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Receive HTTP requests and trigger flows using unique URLs.", + "Return Response": "Return Response", + "Respond and Wait for Next Webhook": "Respond and Wait for Next Webhook", + "return a response": "return a response", + "return a response and wait for the next webhook to resume the flow": "return a response and wait for the next webhook to resume the flow", + "Response Type": "Response Type", + "Response": "Response", + "Flow Execution": "Flow Execution", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ", + "JSON": "JSON", + "Raw": "Raw", + "Redirect": "Redirect", + "Stop": "Stop", + "Respond and Continue": "Respond and Continue", + "Catch Webhook": "Catch Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.", + "Authentication": "Authentication", + "Authentication Fields": "Authentication Fields", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "None", + "Basic Auth": "Basic Auth", + "Header Auth": "Header Auth" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/nl.json b/packages/pieces/community/webhook/src/i18n/nl.json new file mode 100644 index 0000000..68d272d --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/nl.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Ontvang HTTP verzoeken en trigger flows met behulp van unieke URL's.", + "Return Response": "Antwoord retour", + "Respond and Wait for Next Webhook": "Reageer en wacht op de volgende Webhook", + "return a response": "geef een antwoord terug", + "return a response and wait for the next webhook to resume the flow": "retourneert een antwoord en wacht tot de volgende webhook de stroom hervat", + "Response Type": "Type reactie", + "Response": "Antwoord", + "Flow Execution": "Flow Execution", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Reageer en wacht op de volgende Webhook**
\n Controleer de response header (x-activepieces-resume-webhook-url) voor de volgende webhook URL en bel het om de flow te hervatten.
\n ", + "JSON": "JSON", + "Raw": "Onbewerkte", + "Redirect": "Doorverwijzen", + "Stop": "Stoppen", + "Respond and Continue": "Reageren en doorgaan", + "Catch Webhook": "Vang Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Ontvang inkomende HTTP/webhooks met behulp van eender welke HTTP-methode zoals GET, POST, PUT, DELETE, etc.", + "Authentication": "Authenticatie", + "Authentication Fields": "Authenticatie velden", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenereer sample data & triggers gepubliceerde flow.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchrone verzoeken:**\n\nAls je een reactie verwacht van deze webhook, voeg `/sync` toe aan het einde van de URL. \nAls het langer dan 30 seconden duurt, zal het een time-out van 408 verzoek teruggeven.\n\nOm gegevens terug te geven, voeg een Webhook stap toe aan uw stroom met de Return Response actie.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nals je voorbeelddata wilt genereren zonder de flow te activeren, voeg `/test` toe aan je webhook URL.\n\n", + "None": "geen", + "Basic Auth": "Basis authenticatie", + "Header Auth": "Header Auth" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/pl.json b/packages/pieces/community/webhook/src/i18n/pl.json new file mode 100644 index 0000000..3c0b266 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/pl.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Receive HTTP requests and trigger flows using unique URLs.", + "Return Response": "Return Response", + "Respond and Wait for Next Webhook": "Respond and Wait for Next Webhook", + "return a response": "return a response", + "return a response and wait for the next webhook to resume the flow": "return a response and wait for the next webhook to resume the flow", + "Response Type": "Response Type", + "Response": "Response", + "Flow Execution": "Flow Execution", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ", + "JSON": "JSON", + "Raw": "Raw", + "Redirect": "Redirect", + "Stop": "Stop", + "Respond and Continue": "Respond and Continue", + "Catch Webhook": "Catch Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.", + "Authentication": "Authentication", + "Authentication Fields": "Authentication Fields", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "None", + "Basic Auth": "Basic Auth", + "Header Auth": "Header Auth" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/pt.json b/packages/pieces/community/webhook/src/i18n/pt.json new file mode 100644 index 0000000..89228b1 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/pt.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Receber solicitações HTTP e fluxos de gatilho usando URLs exclusivas.", + "Return Response": "Retornar Resposta", + "Respond and Wait for Next Webhook": "Responda e aguarde o próximo Webhook", + "return a response": "retornar uma resposta", + "return a response and wait for the next webhook to resume the flow": "retorne uma resposta e espere o próximo webhook retomar o fluxo", + "Response Type": "Tipo de resposta", + "Response": "Resposta", + "Flow Execution": "Execução de fluxo", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ", + "JSON": "JSON", + "Raw": "RAW", + "Redirect": "Redirecionamento", + "Stop": "Interromper", + "Respond and Continue": "Responder e Continuar", + "Catch Webhook": "Capturar Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Receber HTTP/webhooks de entrada usando qualquer método HTTP como GET, POST, PUT, DELETE, etc.", + "Authentication": "Autenticação", + "Authentication Fields": "Campos de autenticação", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**URL ao vivo:**\n```text\n{{webhookUrl}}\n```\ngera dados de amostra e aciona fluxo publicado.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "Nenhuma", + "Basic Auth": "Autenticação básica", + "Header Auth": "Autenticação de Cabeçalho" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/ru.json b/packages/pieces/community/webhook/src/i18n/ru.json new file mode 100644 index 0000000..23ca576 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/ru.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Вебхук", + "Receive HTTP requests and trigger flows using unique URLs.": "Получать HTTP-запросы и триггеры потоков, используя уникальные URL.", + "Return Response": "Ответ на возврат", + "Respond and Wait for Next Webhook": "Ответить и подождать следующего вебхука", + "return a response": "вернуть ответ", + "return a response and wait for the next webhook to resume the flow": "возвращать ответ и ждать следующего webhook, чтобы возобновить поток", + "Response Type": "Тип ответа", + "Response": "Замечание", + "Flow Execution": "Исполнение потока", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Ответьте и подождите следующего Webhook**
\n Проверьте заголовок ответа (x-activepieces-resume-webhook-url) для следующего URL-адреса webhook и вызовите его, чтобы возобновить поток.
\n ", + "JSON": "JSON", + "Raw": "Сырье", + "Redirect": "Перенаправление", + "Stop": "Остановить", + "Respond and Continue": "Ответить и продолжить", + "Catch Webhook": "Поймать вебхук", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Получать входящие HTTP/webhooks, используя любой метод HTTP, такой как GET, POST, PUT, DELETE, и т.д.", + "Authentication": "Проверка подлинности", + "Authentication Fields": "Поля аутентификации", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\nгенерирует примеры данных и триггеров опубликованного потока.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Синхронные запросы:**\n\nЕсли вы ожидаете ответа от этого вебхука, добавьте `/sync` в конец URL. \nЕсли это займет более 30 секунд, он возвращает ответ на запрос 408 раз.\n\nЧтобы вернуть данные, добавьте шаг Webhook к вашему потоку с действием возврата ответа.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Тестовый URL:**\n\nесли вы хотите сгенерировать образцы без срабатывания потока, добавьте `/test` в URL вашего вебхука.\n\n", + "None": "Нет", + "Basic Auth": "Базовая авторизация", + "Header Auth": "Авторизация заголовка" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/sv.json b/packages/pieces/community/webhook/src/i18n/sv.json new file mode 100644 index 0000000..3c0b266 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/sv.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Receive HTTP requests and trigger flows using unique URLs.", + "Return Response": "Return Response", + "Respond and Wait for Next Webhook": "Respond and Wait for Next Webhook", + "return a response": "return a response", + "return a response and wait for the next webhook to resume the flow": "return a response and wait for the next webhook to resume the flow", + "Response Type": "Response Type", + "Response": "Response", + "Flow Execution": "Flow Execution", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ", + "JSON": "JSON", + "Raw": "Raw", + "Redirect": "Redirect", + "Stop": "Stop", + "Respond and Continue": "Respond and Continue", + "Catch Webhook": "Catch Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.", + "Authentication": "Authentication", + "Authentication Fields": "Authentication Fields", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "None", + "Basic Auth": "Basic Auth", + "Header Auth": "Header Auth" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/translation.json b/packages/pieces/community/webhook/src/i18n/translation.json new file mode 100644 index 0000000..3c0b266 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/translation.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Receive HTTP requests and trigger flows using unique URLs.", + "Return Response": "Return Response", + "Respond and Wait for Next Webhook": "Respond and Wait for Next Webhook", + "return a response": "return a response", + "return a response and wait for the next webhook to resume the flow": "return a response and wait for the next webhook to resume the flow", + "Response Type": "Response Type", + "Response": "Response", + "Flow Execution": "Flow Execution", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ", + "JSON": "JSON", + "Raw": "Raw", + "Redirect": "Redirect", + "Stop": "Stop", + "Respond and Continue": "Respond and Continue", + "Catch Webhook": "Catch Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.", + "Authentication": "Authentication", + "Authentication Fields": "Authentication Fields", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "None", + "Basic Auth": "Basic Auth", + "Header Auth": "Header Auth" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/uk.json b/packages/pieces/community/webhook/src/i18n/uk.json new file mode 100644 index 0000000..3c0b266 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/uk.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Receive HTTP requests and trigger flows using unique URLs.", + "Return Response": "Return Response", + "Respond and Wait for Next Webhook": "Respond and Wait for Next Webhook", + "return a response": "return a response", + "return a response and wait for the next webhook to resume the flow": "return a response and wait for the next webhook to resume the flow", + "Response Type": "Response Type", + "Response": "Response", + "Flow Execution": "Flow Execution", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ", + "JSON": "JSON", + "Raw": "Raw", + "Redirect": "Redirect", + "Stop": "Stop", + "Respond and Continue": "Respond and Continue", + "Catch Webhook": "Catch Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.", + "Authentication": "Authentication", + "Authentication Fields": "Authentication Fields", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "None", + "Basic Auth": "Basic Auth", + "Header Auth": "Header Auth" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/vi.json b/packages/pieces/community/webhook/src/i18n/vi.json new file mode 100644 index 0000000..3c0b266 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/vi.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "Receive HTTP requests and trigger flows using unique URLs.", + "Return Response": "Return Response", + "Respond and Wait for Next Webhook": "Respond and Wait for Next Webhook", + "return a response": "return a response", + "return a response and wait for the next webhook to resume the flow": "return a response and wait for the next webhook to resume the flow", + "Response Type": "Response Type", + "Response": "Response", + "Flow Execution": "Flow Execution", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ", + "JSON": "JSON", + "Raw": "Raw", + "Redirect": "Redirect", + "Stop": "Stop", + "Respond and Continue": "Respond and Continue", + "Catch Webhook": "Catch Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.", + "Authentication": "Authentication", + "Authentication Fields": "Authentication Fields", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "None", + "Basic Auth": "Basic Auth", + "Header Auth": "Header Auth" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/i18n/zh.json b/packages/pieces/community/webhook/src/i18n/zh.json new file mode 100644 index 0000000..622d103 --- /dev/null +++ b/packages/pieces/community/webhook/src/i18n/zh.json @@ -0,0 +1,28 @@ +{ + "Webhook": "Webhook", + "Receive HTTP requests and trigger flows using unique URLs.": "使用唯一的 URL 接收HTTP 请求和触发流。", + "Return Response": "退货回复", + "Respond and Wait for Next Webhook": "响应并等待下一个 Webhook", + "return a response": "返回响应", + "return a response and wait for the next webhook to resume the flow": "返回响应并等待下一个 web 钩子以恢复流", + "Response Type": "响应类型", + "Response": "答复", + "Flow Execution": "流执行", + "Markdown": "Markdown", + "**Respond and Wait for Next Webhook**
\n Check the response header (x-activepieces-resume-webhook-url) for the next webhook URL and call it to resume the flow.
\n ": "**响应并等待下一个 Webhook**
\n 请检查下一个 webhook URL 的响应头(x-activethes-resume-webhook-url) 并调用它来恢复流程。
\n ", + "JSON": "JSON", + "Raw": "原始文件", + "Redirect": "重定向", + "Stop": "停止", + "Respond and Continue": "响应并继续", + "Catch Webhook": "捕获Webhook", + "Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.": "使用 GET、POST、PUT、DELETE等任何HTTP 方法接收传入的 HTTP/webhooks 。", + "Authentication": "认证", + "Authentication Fields": "身份验证字段", + "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n": "**Live URL:**\n```text\n{{webhookUrl}}\n```\ngenerate sample data & triggers published flow.\n\n", + "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n": "**Synchronous Requests:**\n\nIf you expect a response from this webhook, add `/sync` to the end of the URL. \nIf it takes more than 30 seconds, it will return a 408 Request Timeout response.\n\nTo return data, add an Webhook step to your flow with the Return Response action.\n", + "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n": "\n**Test URL:**\n\nif you want to generate sample data without triggering the flow, append `/test` to your webhook URL.\n\n", + "None": "无", + "Basic Auth": "基本认证", + "Header Auth": "头部认证" +} \ No newline at end of file diff --git a/packages/pieces/community/webhook/src/index.ts b/packages/pieces/community/webhook/src/index.ts new file mode 100644 index 0000000..ccb3b8d --- /dev/null +++ b/packages/pieces/community/webhook/src/index.ts @@ -0,0 +1,17 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { catchWebhook } from './lib/triggers/catch-hook'; +import { PieceCategory } from '@activepieces/shared'; +import { returnResponse } from './lib/actions/return-response'; +import { returnResponseAndWaitForNextWebhook } from './lib/actions/return-response-and-wait-for-next-webhook'; + +export const webhook = createPiece({ + displayName: 'Webhook', + description: 'Receive HTTP requests and trigger flows using unique URLs.', + auth: PieceAuth.None(), + categories: [PieceCategory.CORE], + minimumSupportedRelease: '0.52.0', + logoUrl: 'https://cdn.activepieces.com/pieces/webhook.svg', + authors: ['abuaboud', 'pfernandez98', 'kishanprmr','AbdulTheActivePiecer'], + actions: [returnResponse,returnResponseAndWaitForNextWebhook], + triggers: [catchWebhook], +}); diff --git a/packages/pieces/community/webhook/src/lib/actions/return-response-and-wait-for-next-webhook.ts b/packages/pieces/community/webhook/src/lib/actions/return-response-and-wait-for-next-webhook.ts new file mode 100644 index 0000000..b06a774 --- /dev/null +++ b/packages/pieces/community/webhook/src/lib/actions/return-response-and-wait-for-next-webhook.ts @@ -0,0 +1,163 @@ +import { + DynamicPropsValue, + Property, + createAction, + } from '@activepieces/pieces-framework'; + import { ExecutionType, PauseType, StopResponse } from '@activepieces/shared'; + import { StatusCodes } from 'http-status-codes'; + + enum ResponseType { + JSON = 'json', + RAW = 'raw', + REDIRECT = 'redirect', + } + + + const RESUME_WEBHOOK_HEADER = 'x-activepieces-resume-webhook-url'; + export const returnResponseAndWaitForNextWebhook = createAction({ + name: 'return_response_and_wait_for_next_webhook', + displayName: 'Respond and Wait for Next Webhook', + description: 'return a response and wait for the next webhook to resume the flow', + props: { + markdown: Property.MarkDown({ + value: `**Respond and Wait for Next Webhook**
+ Check the response header (${RESUME_WEBHOOK_HEADER}) for the next webhook URL and call it to resume the flow.
+ `, + }), + responseType: Property.StaticDropdown({ + displayName: 'Response Type', + required: false, + defaultValue: 'json', + options: { + disabled: false, + options: [ + { + label: 'JSON', + value: ResponseType.JSON, + }, + { + label: 'Raw', + value: ResponseType.RAW, + }, + { + label: 'Redirect', + value: ResponseType.REDIRECT, + }, + ], + }, + }), + fields: Property.DynamicProperties({ + displayName: 'Response', + refreshers: ['responseType'], + required: true, + props: async ({ responseType }) => { + if (!responseType) return {}; + + const bodyTypeInput = responseType as unknown as ResponseType; + + const fields: DynamicPropsValue = {}; + + if (bodyTypeInput !== ResponseType.REDIRECT) { + fields['status'] = Property.Number({ + displayName: 'Status', + required: false, + defaultValue: 200, + }); + fields['headers'] = Property.Object({ + displayName: 'Headers', + required: false, + }); + } + + switch (bodyTypeInput) { + case ResponseType.JSON: + fields['body'] = Property.Json({ + displayName: 'JSON Body', + required: true, + }); + break; + case ResponseType.RAW: + fields['body'] = Property.LongText({ + displayName: 'Raw Body', + required: true, + }); + break; + case ResponseType.REDIRECT: + fields['body'] = Property.LongText({ + displayName: 'Redirect URL', + required: true, + }); + break; + } + return fields; + }, + }), + }, + + async run(context) { + const { fields, responseType } = context.propsValue; + const bodyInput = fields ['body']; + const headers = fields['headers'] ?? {}; + headers[RESUME_WEBHOOK_HEADER] = context.generateResumeUrl({ + queryParams: { + created: new Date().toISOString(), + runId: context.run.id, + }, + sync:true + }); + const status = fields['status']; + const response: StopResponse = { + status: status ?? StatusCodes.OK, + headers, + }; + + switch (responseType) { + case ResponseType.JSON: + response.body = praseToJson(bodyInput); + break; + case ResponseType.RAW: + response.body = bodyInput; + break; + case ResponseType.REDIRECT: + response.status = StatusCodes.MOVED_PERMANENTLY; + response.headers = { ...response.headers, Location: ensureProtocol(bodyInput) }; + response.body = bodyInput; + break; + } + + + if(context.executionType === ExecutionType.BEGIN){ + context.run.pause({ + pauseMetadata: { + type: PauseType.WEBHOOK, + response + }, + }); + return { + nextWebhookUrl: headers[RESUME_WEBHOOK_HEADER], + }; + } + else { + return { + body: context.resumePayload.body, + headers: context.resumePayload.headers, + queryParams: context.resumePayload.queryParams, + } + } + }, + }); + + function praseToJson(body: unknown) { + if (typeof body === 'string') { + return JSON.parse(body); + } + return JSON.parse(JSON.stringify(body)); + } + + function ensureProtocol(url: string): string { + if (!url.startsWith('http://') && !url.startsWith('https://')) { + return `https://${url}`; + } + return url; + } + diff --git a/packages/pieces/community/webhook/src/lib/actions/return-response.ts b/packages/pieces/community/webhook/src/lib/actions/return-response.ts new file mode 100644 index 0000000..dc857c6 --- /dev/null +++ b/packages/pieces/community/webhook/src/lib/actions/return-response.ts @@ -0,0 +1,169 @@ +import { + DynamicPropsValue, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { StopResponse } from '@activepieces/shared'; +import { StatusCodes } from 'http-status-codes'; + +enum ResponseType { + JSON = 'json', + RAW = 'raw', + REDIRECT = 'redirect', +} + +enum FlowExecution { + STOP = 'stop', + RESPOND = 'respond', +} + +export const returnResponse = createAction({ + name: 'return_response', + displayName: 'Return Response', + description: 'return a response', + props: { + responseType: Property.StaticDropdown({ + displayName: 'Response Type', + required: false, + defaultValue: 'json', + options: { + disabled: false, + options: [ + { + label: 'JSON', + value: ResponseType.JSON, + }, + { + label: 'Raw', + value: ResponseType.RAW, + }, + { + label: 'Redirect', + value: ResponseType.REDIRECT, + }, + ], + }, + }), + fields: Property.DynamicProperties({ + displayName: 'Response', + refreshers: ['responseType'], + required: true, + props: async ({ responseType }) => { + if (!responseType) return {}; + + const bodyTypeInput = responseType as unknown as ResponseType; + + const fields: DynamicPropsValue = {}; + + if (bodyTypeInput !== ResponseType.REDIRECT) { + fields['status'] = Property.Number({ + displayName: 'Status', + required: false, + defaultValue: 200, + }); + fields['headers'] = Property.Object({ + displayName: 'Headers', + required: false, + }); + } + + switch (bodyTypeInput) { + case ResponseType.JSON: + fields['body'] = Property.Json({ + displayName: 'JSON Body', + required: true, + }); + break; + case ResponseType.RAW: + fields['body'] = Property.LongText({ + displayName: 'Raw Body', + required: true, + }); + break; + case ResponseType.REDIRECT: + fields['body'] = Property.LongText({ + displayName: 'Redirect URL', + required: true, + }); + break; + } + return fields; + }, + }), + respond: Property.StaticDropdown({ + displayName: 'Flow Execution', + required: false, + defaultValue: FlowExecution.STOP, + options: { + disabled: false, + options: [ + { label: 'Stop', value: FlowExecution.STOP }, + { label: 'Respond and Continue', value: FlowExecution.RESPOND }, + ], + }, + }), + }, + + async run(context) { + const { fields, responseType, respond } = context.propsValue; + const bodyInput = fields ['body']; + const headers = fields['headers']?? {}; + const status = fields['status']; + + + const response: StopResponse = { + status: status ?? StatusCodes.OK, + headers, + }; + + switch (responseType) { + case ResponseType.JSON: + response.body = praseToJson(bodyInput); + break; + case ResponseType.RAW: + response.body = bodyInput; + break; + case ResponseType.REDIRECT: + response.status = StatusCodes.MOVED_PERMANENTLY; + response.headers = { ...response.headers, Location: ensureProtocol(bodyInput) }; + response.body = bodyInput; + break; + } + + switch(respond){ + case FlowExecution.STOP: + { + context.run.stop({ + response, + }); + break; + } + case FlowExecution.RESPOND: + { + context.run.respond({ + response, + }); + break; + } + case undefined: + break; + } + + + return response; + }, +}); + +function praseToJson(body: unknown) { + if (typeof body === 'string') { + return JSON.parse(body); + } + return JSON.parse(JSON.stringify(body)); +} + +function ensureProtocol(url: string): string { + if (!url.startsWith('http://') && !url.startsWith('https://')) { + return `https://${url}`; + } + return url; +} diff --git a/packages/pieces/community/webhook/src/lib/triggers/catch-hook.ts b/packages/pieces/community/webhook/src/lib/triggers/catch-hook.ts new file mode 100644 index 0000000..3f67cc7 --- /dev/null +++ b/packages/pieces/community/webhook/src/lib/triggers/catch-hook.ts @@ -0,0 +1,190 @@ +import { + createTrigger, + DynamicPropsValue, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { assertNotNullOrUndefined, MarkdownVariant } from '@activepieces/shared'; + +const liveMarkdown = `**Live URL:** +\`\`\`text +{{webhookUrl}} +\`\`\` +generate sample data & triggers published flow. + +`; + +const testMarkdown = ` +**Test URL:** + +if you want to generate sample data without triggering the flow, append \`/test\` to your webhook URL. + +`; + +const syncMarkdown = `**Synchronous Requests:** + +If you expect a response from this webhook, add \`/sync\` to the end of the URL. +If it takes more than 30 seconds, it will return a 408 Request Timeout response. + +To return data, add an Webhook step to your flow with the Return Response action. +`; + +enum AuthType { + NONE = 'none', + BASIC = 'basic', + HEADER = 'header', +} +export const catchWebhook = createTrigger({ + name: 'catch_webhook', + displayName: 'Catch Webhook', + description: + 'Receive incoming HTTP/webhooks using any HTTP method such as GET, POST, PUT, DELETE, etc.', + props: { + liveMarkdown: Property.MarkDown({ + value: liveMarkdown, + variant: MarkdownVariant.BORDERLESS, + }), + syncMarkdown: Property.MarkDown({ + value: syncMarkdown, + variant: MarkdownVariant.INFO, + }), + testMarkdown: Property.MarkDown({ + value: testMarkdown, + variant: MarkdownVariant.INFO, + }), + authType: Property.StaticDropdown({ + displayName: 'Authentication', + required: true, + defaultValue: 'none', + options: { + disabled: false, + options: [ + { label: 'None', value: AuthType.NONE }, + { label: 'Basic Auth', value: AuthType.BASIC }, + { label: 'Header Auth', value: AuthType.HEADER }, + ], + }, + }), + authFields: Property.DynamicProperties({ + displayName: 'Authentication Fields', + required: false, + refreshers: ['authType'], + props: async ({ authType }) => { + if (!authType) { + return {}; + } + const authTypeEnum = authType.toString() as AuthType; + let fields: DynamicPropsValue = {}; + switch (authTypeEnum) { + case AuthType.NONE: + fields = {}; + break; + case AuthType.BASIC: + fields = { + username: Property.ShortText({ + displayName: 'Username', + description: 'The username to use for authentication.', + required: true, + }), + password: Property.ShortText({ + displayName: 'Password', + description: 'The password to use for authentication.', + required: true, + }), + }; + break; + case AuthType.HEADER: + fields = { + headerName: Property.ShortText({ + displayName: 'Header Name', + description: + 'The name of the header to use for authentication.', + required: true, + }), + headerValue: Property.ShortText({ + displayName: 'Header Value', + description: 'The value to check against the header.', + required: true, + }), + }; + break; + default: + throw new Error('Invalid authentication type'); + } + return fields; + }, + }), + }, + sampleData: null, + type: TriggerStrategy.WEBHOOK, + async onEnable() { + // ignore + }, + async onDisable() { + // ignore + }, + async run(context) { + const authenticationType = context.propsValue.authType; + assertNotNullOrUndefined( + authenticationType, + 'Authentication type is required' + ); + const verified = verifyAuth( + authenticationType, + context.propsValue.authFields ?? {}, + context.payload.headers + ); + if (!verified) { + return []; + } + return [context.payload]; + }, +}); + +function verifyAuth( + authenticationType: AuthType, + authFields: DynamicPropsValue, + headers: Record +): boolean { + switch (authenticationType) { + case AuthType.NONE: + return true; + case AuthType.BASIC: + return verifyBasicAuth( + headers['authorization'], + authFields['username'], + authFields['password'] + ); + case AuthType.HEADER: + return verifyHeaderAuth( + headers, + authFields['headerName'], + authFields['headerValue'] + ); + default: + throw new Error('Invalid authentication type'); + } +} + +function verifyHeaderAuth( + headers: Record, + headerName: string, + headerSecret: string +) { + const headerValue = headers[headerName.toLocaleLowerCase()]; + return headerValue === headerSecret; +} + +function verifyBasicAuth( + headerValue: string, + username: string, + password: string +) { + if (!headerValue.toLocaleLowerCase().startsWith('basic ')) { + return false; + } + const auth = headerValue.substring(6); + const decodedAuth = Buffer.from(auth, 'base64').toString(); + const [receivedUsername, receivedPassword] = decodedAuth.split(':'); + return receivedUsername === username && receivedPassword === password; +} diff --git a/packages/pieces/community/webhook/tsconfig.json b/packages/pieces/community/webhook/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/webhook/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/webhook/tsconfig.lib.json b/packages/pieces/community/webhook/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/webhook/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/webling/.eslintrc.json b/packages/pieces/community/webling/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/webling/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/webling/README.md b/packages/pieces/community/webling/README.md new file mode 100644 index 0000000..540c817 --- /dev/null +++ b/packages/pieces/community/webling/README.md @@ -0,0 +1,7 @@ +# pieces-webling + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-webling` to build the library. diff --git a/packages/pieces/community/webling/package.json b/packages/pieces/community/webling/package.json new file mode 100644 index 0000000..aaa3a2e --- /dev/null +++ b/packages/pieces/community/webling/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-webling", + "version": "0.0.1" +} diff --git a/packages/pieces/community/webling/project.json b/packages/pieces/community/webling/project.json new file mode 100644 index 0000000..6ee6f31 --- /dev/null +++ b/packages/pieces/community/webling/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-webling", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/webling/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/webling", + "tsConfig": "packages/pieces/community/webling/tsconfig.lib.json", + "packageJson": "packages/pieces/community/webling/package.json", + "main": "packages/pieces/community/webling/src/index.ts", + "assets": [ + "packages/pieces/community/webling/*.md", + { + "input": "packages/pieces/community/webling/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/webling/src/index.ts b/packages/pieces/community/webling/src/index.ts new file mode 100644 index 0000000..0b8eef9 --- /dev/null +++ b/packages/pieces/community/webling/src/index.ts @@ -0,0 +1,60 @@ +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { onEventChanged } from './lib/triggers/calendar-event'; +import { onChangedData } from './lib/triggers/on-changed-data'; +import { PieceCategory } from '@activepieces/shared'; +import { eventsById } from './lib/actions/get-events-by-id'; + +export const weblingAuth = PieceAuth.CustomAuth({ + required: true, + props: { + baseUrl: Property.ShortText({ + displayName: 'Base URL', + required: true, + defaultValue: 'example.webling.ch', + }), + apikey: PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + }), + }, + validate: async ({ auth }) => { + try { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `https://${auth.baseUrl}/api/1/member`, + headers: { + apikey: auth.apikey, + }, + }; + await httpClient.sendRequest(request); + return { + valid: true, + }; + } catch (e: any) { + return { + valid: false, + error: e?.message, + }; + } + }, +}); + +export const webling = createPiece({ + displayName: 'Webling', + auth: weblingAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/webling.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: ['felifluid'], + actions: [eventsById], + triggers: [onEventChanged, onChangedData], +}); diff --git a/packages/pieces/community/webling/src/lib/actions/get-events-by-id.ts b/packages/pieces/community/webling/src/lib/actions/get-events-by-id.ts new file mode 100644 index 0000000..aa3df9f --- /dev/null +++ b/packages/pieces/community/webling/src/lib/actions/get-events-by-id.ts @@ -0,0 +1,54 @@ +import { weblingAuth } from '../../index'; +import { createAction, PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { getCalendars, getEventsById } from '../common/helpers'; +import { z } from 'zod'; +import { propsValidation } from '@activepieces/pieces-common'; + +export const eventsById = createAction({ + auth: weblingAuth, + name: 'EventsById', + displayName: 'Get Events by ID', + description: + 'Gets event data by a list of event IDs and optional calendar ID to filter.', + props: { + eventIds: Property.ShortText({ + displayName: 'Event ID list', + required: true, + description: + "Comma separated list of event IDs (e.g. '536,525,506,535'). When at least one ID doesn't exist the whole query return a 404 error.", + }), + calendarId: Property.Dropdown({ + displayName: 'Calendar', + description: 'Calendar to filter the events by.', + refreshers: [], + required: false, + options: async ({ auth }) => { + const authProp = auth as PiecePropValueSchema; + const calendars = await getCalendars(authProp); + return { + disabled: false, + options: calendars.map((calendar) => { + return { + label: calendar.properties.title, + value: calendar.id, + }; + }), + }; + }, + }), + }, + async run(configValue) { + const { eventIds: eventIds, calendarId: calendarId } = + configValue.propsValue; + + await propsValidation.validateZod(configValue.propsValue, { + eventIds: z.string().regex(/^\d+(,\d+)*$/), + }); + + const events = await getEventsById(configValue.auth, eventIds); + if (calendarId) { + return events.filter((event) => event.parents.includes(calendarId)); + } + return events; + }, +}); diff --git a/packages/pieces/community/webling/src/lib/common/helpers.ts b/packages/pieces/community/webling/src/lib/common/helpers.ts new file mode 100644 index 0000000..eb9b6e8 --- /dev/null +++ b/packages/pieces/community/webling/src/lib/common/helpers.ts @@ -0,0 +1,109 @@ +import { weblingAuth } from '../../index'; +import { + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { PiecePropValueSchema } from '@activepieces/pieces-framework'; +import { CalendarObject, WeblingCalendarEvent, WeblingChanges } from './types'; + +export async function callApi( + authProp: PiecePropValueSchema, + request: string +) { + const httpRequest: HttpRequest = { + method: HttpMethod.GET, + url: `https://${authProp.baseUrl}/api/1/${request}`, + headers: { + apikey: authProp.apikey, + }, + }; + const response = await httpClient.sendRequest(httpRequest); + return response; +} + +export async function getChanges( + authProp: PiecePropValueSchema, + lastFetchEpochMS: number +): Promise { + // Webling API breaks if unix timestamp is too far in the past + if (lastFetchEpochMS === 0) { + const today = new Date(); + const minUpdated = new Date(); + minUpdated.setDate(today.getDate() - 7); + lastFetchEpochMS = minUpdated.getTime(); + } + + const response = await callApi( + authProp, + `/changes/${lastFetchEpochMS / 1000}` // webling uses seconds instead of milliseconds + ); + return response.body; +} + +export async function getCalendars( + authProp: PiecePropValueSchema +): Promise { + const response = await callApi( + authProp, + 'calendar?format=full' + ); + return response.body; +} + +export async function getAllEvents( + authProp: PiecePropValueSchema, + calendarId: string +): Promise { + const response = await callApi( + authProp, + `calendarevent?filter=$parents.$id=${calendarId}&format=full` + ); + return response.body; +} + +export async function getEventsById( + authProp: PiecePropValueSchema, + eventIds: string, +): Promise { + + const request = `calendarevent/${eventIds}` + + const response = await callApi( + authProp, + request + ); + + return response.body; +}; + +export async function getUpdatedOrNewEvents( + authProp: PiecePropValueSchema, + calendarId: string, + lastFetchEpochMS: number +): Promise { + // get changes since last call + const weblingChanges: WeblingChanges = await getChanges( + authProp, + lastFetchEpochMS / 1000 + ); + + // this will also include ids of deleted objects + const changedEvents: string[] = weblingChanges.objects.calendarevents ?? []; + + const deletedObjects: string[] = weblingChanges.deleted ?? []; + + // filter out already deleted objects to treat seperately + // including a deleted event in a query list will result in a 404 response for the whole query + const updatedOrNewEvents: string[] = changedEvents.filter( + (event) => !deletedObjects.includes(event) + ); + + const response = await callApi( + authProp, + `calendarevent/${updatedOrNewEvents.join( + ',' + )}?format=full&filter=$parents.$id=${calendarId}` + ); + return response.body; +} diff --git a/packages/pieces/community/webling/src/lib/common/index.ts b/packages/pieces/community/webling/src/lib/common/index.ts new file mode 100644 index 0000000..efcd14e --- /dev/null +++ b/packages/pieces/community/webling/src/lib/common/index.ts @@ -0,0 +1,26 @@ +import { PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { getCalendars } from './helpers'; +import { weblingAuth } from '../../index'; + +export const weblingCommon = { + calendarDropdown: () => { + return Property.Dropdown({ + displayName: 'Calendar', + refreshers: [], + required: true, + options: async ({ auth }) => { + const authProp = auth as PiecePropValueSchema; + const calendars = await getCalendars(authProp); + return { + disabled: false, + options: calendars.map((calendar) => { + return { + label: calendar.properties.title, + value: calendar.id, + }; + }), + }; + }, + }); + }, +}; diff --git a/packages/pieces/community/webling/src/lib/common/types.ts b/packages/pieces/community/webling/src/lib/common/types.ts new file mode 100644 index 0000000..f0d5e0a --- /dev/null +++ b/packages/pieces/community/webling/src/lib/common/types.ts @@ -0,0 +1,111 @@ +export interface MetaObject { + created: string; + createuser: { + label: string; + type: string; + }; + lastmodified: string; + lastmodifieduser: { + label: string; + type: string; + }; +} + +export interface CalendarObject { + type: string; + meta: MetaObject; + readonly: boolean; + properties: { + title: string; + color: string; + isPublic: boolean; + publicHash: string; + icsHash: string; + }; + parents: []; + children: { + calendarevent: string[]; + }; + links: object; + id: string; +} + +export interface CalendarList { + objects: CalendarObject[]; +} + +export interface WeblingCalendarEvent { + type: 'calendarevent'; + meta: MetaObject; + readonly: boolean; + properties: { + title: string; + description: string; + place: string; + begin: string; + end: string; + duration: string; + isAllDay: boolean; + isRecurring: boolean; + status: string; + recurrencePattern: string | null; + enableParticipantSignup: boolean; + enableParticipantMaybeState: boolean; + isSignupBinding: boolean; + maxParticipants: string | null; + signedupParticipants: string; + signupAllowedUntil: string | null; + doAutoAcceptParticipants: boolean; + questionSchema: string | null; + showParticipationsInPortal: boolean; + showAllAnswersInPortal: boolean; + }; + parents: string[]; + children: object; + links: object; +} + +export interface WeblingChanges { + objects: WeblingObjectTypes; + deleted: string[]; + context: object[]; + definitions: object[]; + settings: boolean; + quota: boolean; + subscription: boolean; + revision: string; + version: string; +} + +// this list might be incomplete +export interface WeblingObjectTypes { + account?: string[]; + accountgroup?: string[]; + accountgrouptemplate?: string[]; + accounttemplate?: string[]; + apikey?: string[]; + article?: string[]; + articlegroup?: string[]; + calendar?: string[]; + calendarevents?: string[]; + comment?: string[]; + debitor?: string[]; + debitorcategory?: string[]; + document?: string[]; + domain?: string[]; + email?: string[]; + entry?: string[]; + entrygroup?: string[]; + file?: string[]; + member?: string[]; + memberform?: string[]; + membergroup?: string[]; + page?: string[]; + participant?: string[]; + period?: string[]; + periodchain?: string[]; + periodgroup?: string[]; + settings?: string[]; + template?: string[]; + user?: string[]; +} diff --git a/packages/pieces/community/webling/src/lib/triggers/calendar-event.ts b/packages/pieces/community/webling/src/lib/triggers/calendar-event.ts new file mode 100644 index 0000000..90e9c4c --- /dev/null +++ b/packages/pieces/community/webling/src/lib/triggers/calendar-event.ts @@ -0,0 +1,86 @@ +import { + createTrigger, + TriggerStrategy, + PiecePropValueSchema, +} from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { weblingAuth } from '../../index'; +import { weblingCommon } from '../common'; +import { WeblingCalendarEvent } from '../common/types'; +import { getUpdatedOrNewEvents } from '../common/helpers'; + +const polling: Polling< + PiecePropValueSchema, + { calendarId?: string } +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue: { calendarId }, lastFetchEpochMS }) => { + // implement the logic to fetch the items + const items: WeblingCalendarEvent[] = + (await getUpdatedOrNewEvents(auth, calendarId!, lastFetchEpochMS)) ?? []; + return items.map((item) => ({ + epochMilliSeconds: new Date(item.meta.lastmodified).getTime(), + data: item, + })); + }, +}; + +export const onEventChanged = createTrigger({ + auth: weblingAuth, + name: 'onEventChanged', + displayName: 'New or Updated Event', + description: 'Triggers when an event is added or updated.', + props: { + calendarId: weblingCommon.calendarDropdown(), + }, + sampleData: {}, + type: TriggerStrategy.POLLING, + async test(context) { + const { store, auth, propsValue } = context; + return await pollingHelper.test(polling, { + store, + auth, + propsValue: { + calendarId: propsValue.calendarId, + }, + files: context.files, + }); + }, + async onEnable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onEnable(polling, { + store, + auth, + propsValue: { + calendarId: propsValue.calendarId, + }, + }); + }, + + async onDisable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onDisable(polling, { + store, + auth, + propsValue: { + calendarId: propsValue.calendarId, + }, + }); + }, + + async run(context) { + const { store, auth, propsValue } = context; + return await pollingHelper.poll(polling, { + store, + auth, + propsValue: { + calendarId: propsValue.calendarId, + }, + files: context.files, + }); + }, +}); diff --git a/packages/pieces/community/webling/src/lib/triggers/on-changed-data.ts b/packages/pieces/community/webling/src/lib/triggers/on-changed-data.ts new file mode 100644 index 0000000..a4fc70d --- /dev/null +++ b/packages/pieces/community/webling/src/lib/triggers/on-changed-data.ts @@ -0,0 +1,118 @@ +import { + createTrigger, + TriggerStrategy, + PiecePropValueSchema, +} from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { weblingAuth } from '../../index'; +import { WeblingChanges } from '../common/types'; +import { getChanges } from '../common/helpers'; + +const polling: Polling< + PiecePropValueSchema, + { calendarId?: string } +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + const changes: WeblingChanges = await getChanges(auth, lastFetchEpochMS); + const items = [ + { + epochMilliSeconds: Date.now(), // maybe use revision instead? + data: changes, + }, + ]; + return items; + }, +}; + +export const onChangedData = createTrigger({ + auth: weblingAuth, + name: 'onChangedData', + displayName: 'On Changed Data', + description: + 'Triggers when anything was added, updated or deleted since last request.', + props: {}, + sampleData: { + objects: { + account: [244, 246], + accountgroup: [242], + accountgrouptemplate: [241], + accounttemplate: [243, 245, 247, 248, 250], + apikey: [5241], + article: [4579, 4580], + articlegroup: [4578], + calendar: [235, 4428], + calendarevent: [4431, 4434, 4435, 4436], + comment: [4423], + debitor: [1205, 1208], + debitorcategory: [650], + document: [4506], + documentgroup: [114], + domain: [1771], + email: [5085], + entry: [321, 323, 338], + entrygroup: [320, 322, 337], + file: [4525], + member: [4270, 4271, 398, 399], + memberform: [4491], + membergroup: [100], + page: [4495], + participant: [4460], + period: [240], + periodchain: [239], + periodgroup: [238], + settings: [233], + template: [228], + user: [120, 343, 345], + }, + deleted: [235], + context: [], + definitions: ['member'], + settings: false, + quota: true, + subscription: true, + revision: 4758, + version: 2560, + }, + type: TriggerStrategy.POLLING, + async test(context) { + const { store, auth, propsValue } = context; + return await pollingHelper.test(polling, { + store, + auth, + propsValue, + files: context.files, + }); + }, + async onEnable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onEnable(polling, { + store, + auth, + propsValue, + }); + }, + + async onDisable(context) { + const { store, auth, propsValue } = context; + await pollingHelper.onDisable(polling, { + store, + auth, + propsValue, + }); + }, + + async run(context) { + const { store, auth, propsValue } = context; + return await pollingHelper.poll(polling, { + store, + auth, + propsValue, + files: context.files, + }); + }, +}); diff --git a/packages/pieces/community/webling/tsconfig.json b/packages/pieces/community/webling/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/webling/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/webling/tsconfig.lib.json b/packages/pieces/community/webling/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/webling/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/wedof/.eslintrc.json b/packages/pieces/community/wedof/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/wedof/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/wedof/README.md b/packages/pieces/community/wedof/README.md new file mode 100644 index 0000000..889e9c5 --- /dev/null +++ b/packages/pieces/community/wedof/README.md @@ -0,0 +1,7 @@ +# pieces-wedof + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-wedof` to build the library. diff --git a/packages/pieces/community/wedof/package.json b/packages/pieces/community/wedof/package.json new file mode 100644 index 0000000..3114e75 --- /dev/null +++ b/packages/pieces/community/wedof/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-wedof", + "version": "1.2.8" +} diff --git a/packages/pieces/community/wedof/project.json b/packages/pieces/community/wedof/project.json new file mode 100644 index 0000000..18e5d8f --- /dev/null +++ b/packages/pieces/community/wedof/project.json @@ -0,0 +1,65 @@ +{ + "name": "pieces-wedof", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/wedof/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/wedof", + "tsConfig": "packages/pieces/community/wedof/tsconfig.lib.json", + "packageJson": "packages/pieces/community/wedof/package.json", + "main": "packages/pieces/community/wedof/src/index.ts", + "assets": [ + "packages/pieces/community/wedof/*.md", + { + "input": "packages/pieces/community/wedof/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "build-with-deps": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "nx run pieces-wedof:build", + "nx run pieces-wedof:postbuild" + ], + "parallel": false + } + }, + "postbuild": { + "executor": "nx:run-commands", + "options": { + "cwd": "dist/packages/pieces/community/wedof", + "command": "npm install" + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-wedof {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ], + "options": { + "lintFilePatterns": [ + "packages/pieces/community/wedof/**/*.ts" + ] + } + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/wedof/src/index.ts b/packages/pieces/community/wedof/src/index.ts new file mode 100644 index 0000000..8608037 --- /dev/null +++ b/packages/pieces/community/wedof/src/index.ts @@ -0,0 +1,198 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { wedofCommon } from './lib/common/wedof'; +import { newRegistrationFolderNotProcessed } from './lib/triggers/registration-folders/new-registration-folder-created'; +import { registrationFolderUpdated } from './lib/triggers/registration-folders/registration-folder-updated'; +import { registrationFolderAccepted } from './lib/triggers/registration-folders/registration-folder-accepted'; +import { registrationFolderPaid } from './lib/triggers/registration-folders/registration-folder-paid'; +import { registrationFolderSelected } from './lib/triggers/registration-folders/registration-folder-selected'; +import { registrationFolderTobill } from './lib/triggers/registration-folders/registration-folder-tobill'; +import { validateRegistrationFolder } from './lib/actions/registration-folders/validate-registration-folder'; +import { updateRegistrationFolder } from './lib/actions/registration-folders/update-registration-folder'; +import { searchRegistrationFolder } from './lib/actions/registration-folders/search-registration-folder'; +import { declareRegistrationFolderTerminated } from './lib/actions/registration-folders/declare-registration-folder-terminated'; +import { declareRegistrationFolderServicedone } from './lib/actions/registration-folders/declare-registration-folder-servicedone'; +import { declareRegistrationFolderIntraining } from './lib/actions/registration-folders/declare-registration-folder-intraining'; +import { billRegistrationFolder } from './lib/actions/registration-folders/bill-registration-folder'; +import { registrationFolderInTraining } from './lib/triggers/registration-folders/registration-folder-inTraining'; +import { registrationFolderTerminated } from './lib/triggers/registration-folders/registration-folder-terminated'; +import { getRegistrationFolder } from './lib/actions/registration-folders/get-registration-folder'; +import { cancelRegistrationFolder } from './lib/actions/registration-folders/cancel-registration-folder'; +import { refuseRegistrationFolder } from './lib/actions/registration-folders/refuse-registration-folder'; +import { getMinimalSessionDates } from './lib/actions/registration-folders/get-minimal-session-dates'; +import { certificationFolderUpdated } from './lib/triggers/certification-folders/certification-folder-updated'; +import { certificationFolderSuccess } from './lib/triggers/certification-folders/certification-folder-success'; +import { newCertificationFolderCreated } from './lib/triggers/certification-folders/new-certification-folder-created'; +import { certificationFolderTotake } from './lib/triggers/certification-folders/certification-folder-totake'; +import { certificationFolderToretake } from './lib/triggers/certification-folders/certification-folder-toretake'; +import { certificationFolderRegistred } from './lib/triggers/certification-folders/certification-folder-registred'; +import { certificationFolderToControl } from './lib/triggers/certification-folders/certification-folder-tocontrol'; +import { certificationFolderSelected } from './lib/triggers/certification-folders/certification-folder-selected'; +import { declareCertificationFolderRegistred } from './lib/actions/certification-folders/declare-certification-folder-registred'; +import { declareCertificationFolderToTake } from './lib/actions/certification-folders/declare-certification-folder-totake'; +import { declareCertificationFolderToControl } from './lib/actions/certification-folders/declare-certification-folder-tocontrol'; +import { declareCertificationFolderSuccess } from './lib/actions/certification-folders/declare-certification-folder-success'; +import { declareCertificationFolderToRetake } from './lib/actions/certification-folders/declare-certification-folder-toretake'; +import { declareCertificationFolderFailed } from './lib/actions/certification-folders/declare-certification-folder-failed'; +import { refuseCertificationFolder } from './lib/actions/certification-folders/refuse-certification-folder'; +import { abortCertificationFolder } from './lib/actions/certification-folders/abort-certification-folder'; +import { getCertificationFolder } from './lib/actions/certification-folders/get-certification-folder'; +import { searchCertificationFolder } from './lib/actions/certification-folders/search-certification-folder'; +import { getCertificationFolderDocuments } from './lib/actions/certification-folders/list-certification-folder-documents'; +import { listActivitiesAndTasks } from './lib/actions/list-activities-and-tasks'; +import { createTask } from './lib/actions/create-task'; +import { createActivitie } from './lib/actions/create-activitie'; +import { sendFile } from './lib/actions/send-file'; +import {me} from "./lib/actions/me"; +import {myOrganism} from "./lib/actions/my-organism"; +import { getRegistrationFolderDocuments } from './lib/actions/registration-folders/list-registration-folder-documents'; +import {updateCertificationFolder} from "./lib/actions/certification-folders/update-certification-folder"; +import { updateCompletionRate } from './lib/actions/registration-folders/update-completion-rate'; +import { certificationFolderSurveyInitialExperienceAvailable } from './lib/triggers/certification-folder-survey/certification-folder-survey-initial-experience-available'; +import { certificationFolderSurveyInitialExperienceAnswered } from './lib/triggers/certification-folder-survey/certification-folder-survey-initial-experience-answered'; +import { certificationFolderSurveyLongTermExperienceAnswered } from './lib/triggers/certification-folder-survey/certification-folder-survey-long-experience-answered'; +import { certificationFolderSurveyLongTermExperienceAvailable } from './lib/triggers/certification-folder-survey/certification-folder-survey-long-experience-available'; +import { certificationFolderSurveySixMonthExperienceAnswered } from './lib/triggers/certification-folder-survey/certification-folder-survey-six-month-experience-answered'; +import { certificationFolderSurveySixMonthExperienceAvailable } from './lib/triggers/certification-folder-survey/certification-folder-survey-six-month-experience-available'; +import { getCertificationFolderSurvey } from './lib/actions/certification-folder-survey/get-certification-folder-survey'; +import { listCertificationFolderSurveys } from './lib/actions/certification-folder-survey/list-certification-folder-surveys'; +import { createCertificationPartnerAudit } from './lib/actions/certification-partner-audit/create-certification-partner-audit'; +import { createGeneralAudit } from './lib/actions/certification-partner-audit/create-general-audit'; +import { getPartnership } from './lib/actions/certification-partner/get-partnership'; +import { updatePartnership } from './lib/actions/certification-partner/update-partnership'; +import { deletePartnership } from './lib/actions/certification-partner/delete-partnership'; +import { listPartnerships } from './lib/actions/certification-partner/list-partnership'; +import { createPartnership } from './lib/actions/certification-partner/create-partnership'; +import { resetPartnership } from './lib/actions/certification-partner/reset-partnership'; +import { certificationPartnerProcessing } from './lib/triggers/certification-partner/certificationPartner-processing'; +import { certificationPartnerAborted } from './lib/triggers/certification-partner/certificationPartner-aborted'; +import { certificationPartnerActive } from './lib/triggers/certification-partner/certificationPartner-active'; +import { certificationPartnerRefused } from './lib/triggers/certification-partner/certificationPartner-refused'; +import { certificationPartnerRevoked } from './lib/triggers/certification-partner/certificationPartner-revoked'; +import { certificationPartnerSuspended } from './lib/triggers/certification-partner/certificationPartner-suspended'; +import { addExecutionTag } from './lib/actions/add-execution-tag'; + + +export const wedofAuth = PieceAuth.SecretText({ + displayName: 'Clé API', + required: true, + description: 'Veuillez saisir votre clé API fournie par wedof', + validate: async ({auth}) => { + try { + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: wedofCommon.baseUrl + '/users/me', + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': auth, + }, + }); + return {valid: true}; + } catch (error) { + return { + valid: false, + error: 'Clé Api invalide', + }; + } + }, +}); + +export const wedof = createPiece({ + displayName: 'Wedof', + auth: wedofAuth, + description: + 'Automatisez la gestion de vos dossiers de formations (CPF, EDOF, Kairos, AIF, OPCO et autres)', + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/wedof.svg', + categories: [ + PieceCategory.SALES_AND_CRM, + PieceCategory.CONTENT_AND_FILES, + PieceCategory.PRODUCTIVITY, + ], + authors: ['vbarrier','obenazouz'], + actions: [ + ////////////// registrationFolders //////////// + getRegistrationFolder, + searchRegistrationFolder, + updateRegistrationFolder, + validateRegistrationFolder, + declareRegistrationFolderTerminated, + declareRegistrationFolderServicedone, + declareRegistrationFolderIntraining, + billRegistrationFolder, + cancelRegistrationFolder, + refuseRegistrationFolder, + getMinimalSessionDates, + getRegistrationFolderDocuments, + updateCompletionRate, + ////////////// certificationFolders //////////// + getCertificationFolder, + searchCertificationFolder, + declareCertificationFolderRegistred, + declareCertificationFolderToTake, + declareCertificationFolderToControl, + declareCertificationFolderSuccess, + declareCertificationFolderToRetake, + declareCertificationFolderFailed, + refuseCertificationFolder, + abortCertificationFolder, + getCertificationFolderDocuments, + updateCertificationFolder, + ////////////// general //////////// + listActivitiesAndTasks, + createTask, + createActivitie, + sendFile, + me, + myOrganism, + addExecutionTag, + ///////////// certificationFoldersSurvey /////// + getCertificationFolderSurvey, + listCertificationFolderSurveys, + ///////////// certificationPartnerAudit //////// + createCertificationPartnerAudit, + createGeneralAudit, + //////////// certificationPartner ////////////// + getPartnership, + updatePartnership, + deletePartnership, + listPartnerships, + createPartnership, + resetPartnership + ], + triggers: [ + ////////////// registrationFolders //////////// + newRegistrationFolderNotProcessed, + registrationFolderUpdated, + registrationFolderAccepted, + registrationFolderInTraining, + registrationFolderTerminated, + registrationFolderPaid, + registrationFolderSelected, + registrationFolderTobill, + ////////////// certificationFolders //////////// + newCertificationFolderCreated, + certificationFolderUpdated, + certificationFolderRegistred, + certificationFolderTotake, + certificationFolderToControl, + certificationFolderSuccess, + certificationFolderToretake, + certificationFolderSelected, + ///////////// certificationFoldersSurvey /////// + certificationFolderSurveyInitialExperienceAvailable, + certificationFolderSurveyInitialExperienceAnswered, + certificationFolderSurveyLongTermExperienceAnswered, + certificationFolderSurveyLongTermExperienceAvailable, + certificationFolderSurveySixMonthExperienceAnswered, + certificationFolderSurveySixMonthExperienceAvailable, + //////////// certificationPartner ///////////////// + certificationPartnerAborted, + certificationPartnerProcessing, + certificationPartnerActive, + certificationPartnerRefused, + certificationPartnerRevoked, + certificationPartnerSuspended + ], +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/add-execution-tag.ts b/packages/pieces/community/wedof/src/lib/actions/add-execution-tag.ts new file mode 100644 index 0000000..7fb2bed --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/add-execution-tag.ts @@ -0,0 +1,36 @@ +import { wedofAuth } from '../../index'; +import { Property, createAction } from '@activepieces/pieces-framework'; + +export const addExecutionTag = createAction({ + auth: wedofAuth, + name: 'addExecutionTag', + displayName: "Associer l'exécution à wedof", + description: + "Permet d'associer une exécution de workflow à un ou plusieurs dossiers de (formations / certifications) dans wedof", + errorHandlingOptions: { + continueOnFailure: { + hide: true, + }, + retryOnFailure: { + hide: true, + }, + }, + props: { + externalId: Property.Array({ + displayName: 'Numéros de dossier', + description: + 'Entrez un ou plusieurs numéros de dossier à associer à cette exécution.', + required: true, + defaultValue: [], + }), + }, + async run(context) { + for (const id of context.propsValue.externalId as string[]) { + await context.tags.add({ name: id }); + } + return { + success: true, + tagsAjoutes: context.propsValue.externalId, + }; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-folder-survey/get-certification-folder-survey.ts b/packages/pieces/community/wedof/src/lib/actions/certification-folder-survey/get-certification-folder-survey.ts new file mode 100644 index 0000000..c0ee13b --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-folder-survey/get-certification-folder-survey.ts @@ -0,0 +1,34 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + import { wedofAuth } from '../../..'; + import { + createAction, + Property, + } from '@activepieces/pieces-framework'; + import { wedofCommon } from '../../common/wedof'; + + export const getCertificationFolderSurvey = createAction({ + auth: wedofAuth, + name: 'getCertificationFolderSurvey', + displayName: "Récupération d'une enquête", + description: "Permet de récupérer une enquête associée à un dossier de certification", + props: { + certificationFolderExternalId: Property.ShortText({ + displayName: 'N° de dossier de certification', + description: "Sélectionner la propriété {externalId} du dossier de certification", + required: true, + }), + }, + + async run(context) { + return ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: wedofCommon.baseUrl + '/surveys/'+ context.propsValue.certificationFolderExternalId, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, + }); \ No newline at end of file diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-folder-survey/list-certification-folder-surveys.ts b/packages/pieces/community/wedof/src/lib/actions/certification-folder-survey/list-certification-folder-surveys.ts new file mode 100644 index 0000000..af3e76a --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-folder-survey/list-certification-folder-surveys.ts @@ -0,0 +1,108 @@ +import { HttpMethod, QueryParams, httpClient } from '@activepieces/pieces-common'; + import { wedofAuth } from '../../..'; + import { + createAction, + Property, + } from '@activepieces/pieces-framework'; + import { wedofCommon } from '../../common/wedof'; + + export const listCertificationFolderSurveys = createAction({ + auth: wedofAuth, + name: 'listCertificationFolderSurveys', + displayName: 'Liste les enquêtes selon des critères', + description: "Récupérer l'ensemble des enquêtes de l'organisme de l'utilisateur connecté", + props: { + certifInfo: Property.ShortText({ + displayName: 'N° certifInfo', + description: "Permet de n'obtenir que les enquêtes liées à la certification considérée", + required: false, + }), + limit: Property.ShortText({ + displayName: "Nombre d'enquêtes", + description: "Nombre d'éléments retourné par requête - par défaut 100", + required: false, + }), + order: Property.StaticDropdown({ + displayName: "Ordre", + description: "Tri les résultats par ordre ascendant ou descendant", + required: false, + options: { + options: [ + { + value: 'asc', + label: 'Ascendant', + }, + { + value: 'desc', + label: 'Descendant', + }, + ], + disabled: false, + }, + }), + page: Property.Number({ + displayName: 'N° de page de la requête', + description: 'Par défaut : 1', + defaultValue: 1, + required: false, + }), + state: Property.StaticDropdown({ + displayName: "Etat", + description: "Permet de n'obtenir que les enquêtes en fonction de l'état considéré", + required: false, + options: { + options: [ + { + value: 'all', + label: 'Tous', + }, + { + value: 'created', + label: 'Créé', + }, + { + value: 'beforeCertificationSuccess', + label: 'Avant la réussite de la certification', + }, + { + value: 'afterSixMonthsCertificationSuccess', + label: 'Six mois après la réussite de la certification', + }, + { + value: 'finished', + label: 'Terminé', + }, + ], + disabled: false, + }, + }), + }, + + async run(context) { + const params = { + certifInfo: context.propsValue.certifInfo ?? null, + limit: context.propsValue.limit ?? null, + page: context.propsValue.page ?? null, + state: context.propsValue.state ?? null, + order:context.propsValue.order ?? null, + }; + const queryParams: QueryParams = {}; + Object.keys(params).forEach((value) => { + const key = value as keyof typeof params; + if (params[key] != null && params[key] != undefined) { + queryParams[value] = params[key] as string; + } + }); + return ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + queryParams: queryParams, + url: wedofCommon.baseUrl + '/surveys', + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, + }); \ No newline at end of file diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-folders/abort-certification-folder.ts b/packages/pieces/community/wedof/src/lib/actions/certification-folders/abort-certification-folder.ts new file mode 100644 index 0000000..7c9e906 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-folders/abort-certification-folder.ts @@ -0,0 +1,43 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const abortCertificationFolder = createAction({ + auth: wedofAuth, + name: 'abortCertificationFolder', + displayName: 'Passer un dossier de certification à l’état : Abandonné', + description: "Change l'état d'un dossier de certification vers : Abandonné", + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de certification', + description: + 'Sélectionner la propriété {externalId} du dossier de certification', + required: true, + }), + comment: Property.LongText({ + displayName: 'Commentaire', + required: false, + }), + }, + async run(context) { + const message = { + comment: context.propsValue.comment, + }; + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/certificationFolders/' + + context.propsValue.externalId + + '/abort', + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-failed.ts b/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-failed.ts new file mode 100644 index 0000000..68066d6 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-failed.ts @@ -0,0 +1,52 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const declareCertificationFolderFailed = createAction({ + auth: wedofAuth, + name: 'declareCertificationFolderFailed', + displayName: 'Passer un dossier de certification à l’état : Échoué', + description: "Change l'état d'un dossier de certification vers : Échoué", + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de certification', + description: + 'Sélectionner la propriété {externalId} du dossier de certification', + required: true, + }), + detailedResult: Property.ShortText({ + displayName: "Détail du résultat de l'examen", + required: false, + }), + europeanLanguageLevel: wedofCommon.europeanLanguageLevel, + comment: Property.LongText({ + displayName: 'Commentaire', + required: false, + }), + }, + + async run(context) { + const message = { + detailedResult: context.propsValue.detailedResult, + europeanLanguageLevel: context.propsValue.europeanLanguageLevel, + comment: context.propsValue.comment, + }; + + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/certificationFolders/' + + context.propsValue.externalId + + '/fail', + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-registred.ts b/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-registred.ts new file mode 100644 index 0000000..59af278 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-registred.ts @@ -0,0 +1,75 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { wedofCommon } from '../../common/wedof'; +import dayjs from 'dayjs'; + +export const declareCertificationFolderRegistred = createAction({ + auth: wedofAuth, + name: 'declareCertificationFolderRegistred', + displayName: "Passer un dossier de certification à l'état : Enregistré", + description: "Change l'état d'un dossier de certification vers : Enregistré", + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de certification', + description: + 'Sélectionner la propriété {externalId} du dossier de certification', + required: true, + }), + enrollmentDate: Property.DateTime({ + displayName: "Date d'inscription à la certification", + description: 'Date au format YYYY-MM-DD.', + required: false, + }), + examinationDate: Property.DateTime({ + displayName: "Date de passage de l'examen", + description: 'Date au format YYYY-MM-DD.', + required: false, + }), + examinationEndDate: Property.DateTime({ + displayName: "Date de fin de passage de l'examen", + description: 'Date au format YYYY-MM-DD.', + required: false, + }), + examinationType: wedofCommon.examinationType, + examinationPlace: Property.ShortText({ + displayName: "Lieu de passage de l'examen", + required: false, + }), + comment: Property.LongText({ + displayName: 'Commentaire', + required: false, + }), + }, + async run(context) { + const message = { + enrollmentDate: context.propsValue.enrollmentDate + ? dayjs(context.propsValue.enrollmentDate).format('YYYY-MM-DD') + : null, + examinationDate: context.propsValue.examinationDate + ? dayjs(context.propsValue.examinationDate).format('YYYY-MM-DD') + : null, + examinationEndDate: context.propsValue.examinationEndDate + ? dayjs(context.propsValue.examinationEndDate).format('YYYY-MM-DD') + : null, + examinationType: context.propsValue.examinationType, + examinationPlace: context.propsValue.examinationPlace, + comment: context.propsValue.comment, + }; + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/certificationFolders/' + + context.propsValue.externalId + + '/register', + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-success.ts b/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-success.ts new file mode 100644 index 0000000..6bdbe1c --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-success.ts @@ -0,0 +1,67 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { wedofCommon } from '../../common/wedof'; +import dayjs from 'dayjs'; + +export const declareCertificationFolderSuccess = createAction({ + auth: wedofAuth, + name: 'declareCertificationFolderSuccess', + displayName: "Passer un dossier de certification à l'état : Réussi", + description: "Change l'état d'un dossier de certification vers : Réussi", + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de certification', + description: + 'Sélectionner la propriété {externalId} du dossier de certification', + required: true, + }), + detailedResult: Property.ShortText({ + displayName: "Détail du résultat de l'examen", + required: false, + }), + europeanLanguageLevel: wedofCommon.europeanLanguageLevel, + issueDate: Property.DateTime({ + displayName: "Date d'obtention de la certification", + description: 'Date au format YYYY-MM-DD.', + required: true, + }), + digitalProofLink: Property.ShortText({ + displayName: + "Lien vers la preuve numérique de l'obtention de la certification", + required: false, + }), + gradePass: wedofCommon.gradePass, + comment: Property.LongText({ + displayName: 'Commentaire', + required: false, + }), + }, + async run(context) { + const message = { + detailedResult: context.propsValue.detailedResult, + issueDate: context.propsValue.issueDate + ? dayjs(context.propsValue.issueDate).format('YYYY-MM-DD') + : null, + digitalProofLink: context.propsValue.digitalProofLink, + europeanLanguageLevel: context.propsValue.europeanLanguageLevel, + gradePass: context.propsValue.gradePass, + comment: context.propsValue.comment, + }; + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/certificationFolders/' + + context.propsValue.externalId + + '/success', + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-tocontrol.ts b/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-tocontrol.ts new file mode 100644 index 0000000..43d842e --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-tocontrol.ts @@ -0,0 +1,100 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { wedofCommon } from '../../common/wedof'; +import dayjs from 'dayjs'; + +export const declareCertificationFolderToControl = createAction({ + auth: wedofAuth, + name: 'declareCertificationFolderToControl', + displayName: "Passer un dossier de certification à l'état : À contrôler", + description: "Change l'état d'un dossier de certification vers : À contrôler", + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de certification', + description: + 'Sélectionner la propriété {externalId} du dossier de certification', + required: true, + }), + enrollmentDate: Property.DateTime({ + displayName: "Date d'inscription à la certification", + description: 'Date au format YYYY-MM-DD.', + required: false, + }), + examinationDate: Property.DateTime({ + displayName: "Date de passage de l'examen", + description: 'Date au format YYYY-MM-DD.', + required: true, + }), + examinationEndDate: Property.DateTime({ + displayName: "Date de fin de passage de l'examen", + description: 'Date au format YYYY-MM-DD.', + required: false, + }), + examinationType: Property.StaticDropdown({ + displayName: "Type de passage de l'examen", + required: true, + defaultValue: { + value: 'A_DISTANCE', + label: 'À distance', + }, + options: { + options: [ + { + value: 'A_DISTANCE', + label: 'À distance', + }, + { + value: 'EN_PRESENTIEL', + label: 'En présentiel', + }, + { + value: 'MIXTE', + label: 'Mixte', + }, + ], + disabled: false, + }, + }), + examinationPlace: Property.ShortText({ + displayName: "Lieu de passage de l'examen", + required: false, + }), + comment: Property.LongText({ + displayName: 'Commentaire', + required: false, + }), + }, + + async run(context) { + const message = { + enrollmentDate: context.propsValue.enrollmentDate + ? dayjs(context.propsValue.enrollmentDate).format('YYYY-MM-DD') + : null, + examinationDate: context.propsValue.examinationDate + ? dayjs(context.propsValue.examinationDate).format('YYYY-MM-DD') + : null, + examinationEndDate: context.propsValue.examinationEndDate + ? dayjs(context.propsValue.examinationEndDate).format('YYYY-MM-DD') + : null, + examinationType: context.propsValue.examinationType, + examinationPlace: context.propsValue.examinationPlace, + comment: context.propsValue.comment, + }; + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/certificationFolders/' + + context.propsValue.externalId + + '/control', + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-toretake.ts b/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-toretake.ts new file mode 100644 index 0000000..cb62b93 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-toretake.ts @@ -0,0 +1,76 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; +import dayjs from 'dayjs'; + +export const declareCertificationFolderToRetake = createAction({ + auth: wedofAuth, + name: 'declareCertificationFolderToRetake', + displayName: 'Passer un dossier de certification à l’état : à repasser', + description: "Change l'état d'un dossier de certification vers : à repasser", + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de certification', + description: + 'Sélectionner la propriété {externalId} du dossier de certification', + required: true, + }), + detailedResult: Property.ShortText({ + displayName: "Détail du résultat de l'examen", + required: false, + }), + europeanLanguageLevel: wedofCommon.europeanLanguageLevel, + examinationDate: Property.DateTime({ + displayName: "Date de passage de l'examen", + description: 'Date au format YYYY-MM-DD.', + required: false, + }), + examinationEndDate: Property.DateTime({ + displayName: "Date de fin de passage de l'examen", + description: 'Date au format YYYY-MM-DD.', + required: false, + }), + examinationPlace: Property.ShortText({ + displayName: "Lieu de passage de l'examen", + required: false, + }), + examinationType: wedofCommon.examinationType, + comment: Property.LongText({ + displayName: 'Commentaire', + required: false, + }), + }, + + async run(context) { + const message = { + detailedResult: context.propsValue.detailedResult, + europeanLanguageLevel: context.propsValue.europeanLanguageLevel, + examinationDate: context.propsValue.examinationDate + ? dayjs(context.propsValue.examinationDate).format('YYYY-MM-DD') + : null, + examinationEndDate: context.propsValue.examinationEndDate + ? dayjs(context.propsValue.examinationEndDate).format('YYYY-MM-DD') + : null, + examinationPlace: context.propsValue.examinationPlace, + examinationType: context.propsValue.examinationType, + comment: context.propsValue.comment, + }; + + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/certificationFolders/' + + context.propsValue.externalId + + '/retake', + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-totake.ts b/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-totake.ts new file mode 100644 index 0000000..b6d05c1 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-folders/declare-certification-folder-totake.ts @@ -0,0 +1,95 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { wedofCommon } from '../../common/wedof'; +import dayjs from 'dayjs'; + +export const declareCertificationFolderToTake = createAction({ + auth: wedofAuth, + name: 'declareCertificationFolderToTake', + displayName: "Passer un dossier de certification à l'état : Prêt à passer", + description: + "Change l'état d'un dossier de certification vers : Prêt à passer", + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de certification', + description: + 'Sélectionner la propriété {externalId} du dossier de certification', + required: true, + }), + enrollmentDate: Property.DateTime({ + displayName: "Date d'inscription à la certification", + description: 'Date au format YYYY-MM-DD.', + required: false, + }), + examinationDate: Property.DateTime({ + displayName: "Date de passage de l'examen", + description: 'Date au format YYYY-MM-DD.', + required: false, + }), + examinationEndDate: Property.DateTime({ + displayName: "Date de fin de passage de l'examen", + description: 'Date au format YYYY-MM-DD.', + required: false, + }), + examinationType: wedofCommon.examinationType, + examinationPlace: Property.ShortText({ + displayName: "Lieu de passage de l'examen", + required: false, + }), + tiersTemps: Property.StaticDropdown({ + displayName: "Tiers temps", + description: "Indique si le candidat a besoin d'un tiers temps", + required: true, + options: { + disabled: false, + options: [ + { + label: "Non", + value: false, + }, + { + label: 'Oui', + value: true, + }, + ], + }, + }), + comment: Property.LongText({ + displayName: 'Commentaire', + required: false, + }), + }, + async run(context) { + const message = { + enrollmentDate: context.propsValue.enrollmentDate + ? dayjs(context.propsValue.enrollmentDate).format('YYYY-MM-DD') + : null, + examinationDate: context.propsValue.examinationDate + ? dayjs(context.propsValue.examinationDate).format('YYYY-MM-DD') + : null, + examinationEndDate: context.propsValue.examinationEndDate + ? dayjs(context.propsValue.examinationEndDate).format('YYYY-MM-DD') + : null, + examinationType: context.propsValue.examinationType, + examinationPlace: context.propsValue.examinationPlace, + tiersTemps: context.propsValue.tiersTemps, + comment: context.propsValue.comment, + }; + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/certificationFolders/' + + context.propsValue.externalId + + '/take', + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-folders/get-certification-folder.ts b/packages/pieces/community/wedof/src/lib/actions/certification-folders/get-certification-folder.ts new file mode 100644 index 0000000..a6d883b --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-folders/get-certification-folder.ts @@ -0,0 +1,35 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const getCertificationFolder = createAction({ + auth: wedofAuth, + name: 'getCertificationFolder', + displayName: 'Récupérer un dossier de certification', + description: + 'Récupérer un dossier de certification à partir de son n° de dossier', + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de certification', + description: + 'Sélectionner la propriété {externalId} du dossier de certification', + required: true, + }), + }, + async run(context) { + return ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: + wedofCommon.baseUrl + + '/certificationFolders/' + + context.propsValue.externalId, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-folders/list-certification-folder-documents.ts b/packages/pieces/community/wedof/src/lib/actions/certification-folders/list-certification-folder-documents.ts new file mode 100644 index 0000000..e29750e --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-folders/list-certification-folder-documents.ts @@ -0,0 +1,36 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const getCertificationFolderDocuments = createAction({ + auth: wedofAuth, + name: 'getCertificationFolderDocuments', + displayName: "Liste des documents d'un dossier de certification", + description: + "Récupérer la liste de documents d'un dossier de certification à partir de son n° de dossier", + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de certification', + description: + 'Sélectionner la propriété {externalId} du dossier de certification', + required: true, + }), + }, + async run(context) { + return ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: + wedofCommon.baseUrl + + '/certificationFolders/' + + context.propsValue.externalId + + '/files', + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-folders/refuse-certification-folder.ts b/packages/pieces/community/wedof/src/lib/actions/certification-folders/refuse-certification-folder.ts new file mode 100644 index 0000000..34fe15b --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-folders/refuse-certification-folder.ts @@ -0,0 +1,43 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const refuseCertificationFolder = createAction({ + auth: wedofAuth, + name: 'refuseCertificationFolder', + displayName: 'Passer un dossier de certification à l’état : Refuser', + description: "Change l'état d'un dossier de certification vers : Refuser", + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de certification', + description: + 'Sélectionner la propriété {externalId} du dossier de certification', + required: true, + }), + comment: Property.LongText({ + displayName: 'Commentaire', + required: false, + }), + }, + async run(context) { + const message = { + comment: context.propsValue.comment, + }; + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/certificationFolders/' + + context.propsValue.externalId + + '/refuse', + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-folders/search-certification-folder.ts b/packages/pieces/community/wedof/src/lib/actions/certification-folders/search-certification-folder.ts new file mode 100644 index 0000000..01115e8 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-folders/search-certification-folder.ts @@ -0,0 +1,177 @@ +import { HttpMethod, QueryParams, httpClient } from '@activepieces/pieces-common'; + import { wedofAuth } from '../../..'; + import { + createAction, + DynamicPropsValue, + Property, + } from '@activepieces/pieces-framework'; + import { wedofCommon } from '../../common/wedof'; + import dayjs from 'dayjs'; + + export const searchCertificationFolder = createAction({ + auth: wedofAuth, + name: 'searchCertificationFolder', + displayName: 'Rechercher un ou plusieurs dossiers de certifications', + description: + 'Liste les dossiers de certifications en fonction des critères sélectionnés', + props: { + query: Property.ShortText({ + displayName: 'Recherche', + description: 'Nom, prénom, N° de dossier, N° de certification etc..', + required: false, + }), + period: wedofCommon.period, + periodForm: Property.DynamicProperties({ + description: '', + displayName: 'ez', + required: true, + refreshers: ['period'], + props: async ({ period }) => { + const _period = period as unknown as string; + const props: DynamicPropsValue = {}; + if (_period === 'custom') { + props['since'] = Property.DateTime({ + displayName: '(Période) Entre le', + description: 'Date au format YYYY-MM-DD', + required: true, + }); + props['until'] = Property.DateTime({ + displayName: "(Période) et jusqu'au", + description: 'Date au format YYYY-MM-DD', + required: true, + }); + } else if ( + ['next', 'future', 'tomorrow'].some((v) => + _period.toLowerCase().includes(v) + ) + ) { + props['filterOnStateDate'] = wedofCommon.filterOnStateDateFuture; + } else if (_period) { + props['filterOnStateDate'] = wedofCommon.filterOnStateDate; + } + return props; + }, + }), + state: wedofCommon.state, + certificationFolderState: wedofCommon.certificationFolderState, + sort:wedofCommon.sort, + order:wedofCommon.order, + cdcState:wedofCommon.cdcState, + cdcExcluded: Property.StaticDropdown({ + displayName: "Exclus de l'accrochage", + description: "Permet de filtrer les dossiers de certification qui sont exclus de l'accrochage", + required: false, + options: { + options: [ + { + value: true, + label: 'Oui', + }, + { + value: false, + label: 'Non', + }, + ], + disabled: false, + }, + }), + cdcCompliant: Property.StaticDropdown({ + displayName: "Donnés apprenant complètes", + description: "Permet de filtrer les dossiers de certification selon le fait qu'ils contiennent les données de l'apprenant obligatoires pour l'accrochage en cas d'obtention de la certification", + required: false, + options: { + options: [ + { + value: true, + label: 'Oui', + }, + { + value: false, + label: 'Non', + }, + ], + disabled: false, + }, + }), + cdcToExport: Property.StaticDropdown({ + displayName: "Inclus dans les prochains accrochages", + description: "Permet de filtrer les dossiers de certification qui devront être inclus dans les prochains exports pour l'accrochage (par défaut oui, sauf si déjà accroché avec succès)", + required: false, + options: { + options: [ + { + value: true, + label: 'Oui', + }, + { + value: false, + label: 'Non', + }, + ], + disabled: false, + }, + }), + limit: Property.Number({ + displayName: 'Nombre de dossiers de formation', + description: + 'Nombre de dossiers de certification maximum qui seront retournés par requête', + defaultValue: 100, + required: false, + }), + page: Property.Number({ + displayName: 'N° de page de la requête', + description: 'Par défaut : 1', + defaultValue: 1, + required: false, + }), + }, + + async run(context) { + const params = { + query: context.propsValue.query ?? null, + limit: context.propsValue.limit ?? null, + page: context.propsValue.page ?? null, + state: context.propsValue.state ?? null, + sort:context.propsValue.state ?? null, + order:context.propsValue.order ?? null, + cdcExcluded:context.propsValue.cdcExcluded ?? null, + cdcCompliant:context.propsValue.cdcCompliant ?? null, + cdcToExport:context.propsValue.cdcToExport ?? null, + certificationFolderState: + context.propsValue.certificationFolderState ?? null, + cdcState:context.propsValue.cdcState ?? null, + period: context.propsValue.period ?? null, + since: context.propsValue.periodForm['since'] + ? dayjs(context.propsValue.periodForm['since']) + .startOf('day') + .format('YYYY-MM-DDTHH:mm:ssZ') + : null, + until: context.propsValue.periodForm['until'] + ? dayjs(context.propsValue.periodForm['until']) + .endOf('day') + .format('YYYY-MM-DDTHH:mm:ssZ') + : null, + filterOnStateDate: + context.propsValue.periodForm['filterOnStateDate'] ?? null, + }; + const queryParams: QueryParams = {}; + Object.keys(params).forEach((value) => { + const key = value as keyof typeof params; + if (params[key] != null && params[key] != undefined) { + queryParams[value] = params[key] as string; + } + }); + return ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + queryParams: queryParams, + url: wedofCommon.baseUrl + '/certificationFolders', + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, + }); + \ No newline at end of file diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-folders/update-certification-folder.ts b/packages/pieces/community/wedof/src/lib/actions/certification-folders/update-certification-folder.ts new file mode 100644 index 0000000..1532338 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-folders/update-certification-folder.ts @@ -0,0 +1,108 @@ +import {HttpMethod, httpClient} from '@activepieces/pieces-common'; +import {wedofAuth} from '../../..'; +import {createAction, Property} from '@activepieces/pieces-framework'; +import {wedofCommon} from '../../common/wedof'; +import dayjs from 'dayjs'; + +export const updateCertificationFolder = createAction({ + auth: wedofAuth, + name: 'updateCertificationFolder', + displayName: 'Mettre à jour un dossier de certification', + description: + "Met à jour certaines informations modifiables d'un dossier de certification", + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de certification', + description: + 'Sélectionner la propriété {externalId} du dossier de certification', + required: true, + }), + enrollmentDate: Property.DateTime({ + displayName: 'Date d\'inscription à la certification', + description: 'Date au format YYYY-MM-DD', + required: false, + }), + examinationDate: Property.DateTime({ + displayName: 'Date de début l\'examen de certification', + description: 'Date au format YYYY-MM-DD', + required: false, + }), + examinationEndDate: Property.DateTime({ + displayName: 'Date de fin l\'examen de certification', + description: 'Date au format YYYY-MM-DD', + required: false, + }), + examinationPlace: Property.ShortText({ + displayName: 'Lieu de l\'examen', + description: "Lieu de l'examen de certification (ou lien https)", + required: false, + }), + tiersTemps: Property.StaticDropdown({ + displayName: "Tiers temps", + description: "Indique si le candidat a besoin d'un tiers temps", + required: false, + options: { + disabled: false, + options: [ + { + label: "Non", + value: false, + }, + { + label: 'Oui', + value: true, + }, + ], + }, + }), + comment: Property.LongText({ + displayName: 'Commentaire', + description: "Commentaire (non-visible par l'apprenant)", + required: false, + }), + verbatim: Property.ShortText({ + displayName: 'Verbatim', + description: "Verbatim", + required: false, + }), + amountHt: Property.Number({ + displayName: 'Prix du passage de la certification', + description: 'Tarif en €', + required: false, + }), + tags: Property.Array({ + displayName: "Tags", + description: "Liste de tags associée au dossier de certification, si vous souhaitez garder vos précédents tags, il faut les réécrire dans le champ", + required: false, + }), + }, + async run(context) { + const message = { + enrollmentDate: context.propsValue.enrollmentDate + ? dayjs(context.propsValue.enrollmentDate).format('YYYY-MM-DD') + : null, + examinationDate: context.propsValue.examinationDate, + examinationEndDate: context.propsValue.examinationEndDate, + examinationPlace: context.propsValue.examinationPlace, + comment: context.propsValue.comment, + verbatim: context.propsValue.verbatim, + amountHt: context.propsValue.amountHt, + tags: context.propsValue.tags as string[], + tiersTemps: context.propsValue.tiersTemps, + }; + return ( + await httpClient.sendRequest({ + method: HttpMethod.PUT, + body: message, + url: + wedofCommon.baseUrl + + '/certificationFolders/' + + context.propsValue.externalId, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-partner-audit/create-certification-partner-audit.ts b/packages/pieces/community/wedof/src/lib/actions/certification-partner-audit/create-certification-partner-audit.ts new file mode 100644 index 0000000..2a9251d --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-partner-audit/create-certification-partner-audit.ts @@ -0,0 +1,85 @@ +import { wedofAuth } from '../../../index'; +import { createAction, DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofCommon } from '../../common/wedof'; + +export const createCertificationPartnerAudit = createAction({ + auth: wedofAuth, + name: 'createCertificationPartnerAudit', + displayName: "Créer un audit sur un partenariat de certification", + description: "Permet de créer un audit sur un partenariat de certification", + props: { + certifInfo: Property.ShortText({ + displayName: 'N° certifInfo', + description: "Permet de n'obtenir que les modèles liés à la certification considérée", + required: true, + }), + siret: Property.ShortText({ + displayName: 'N° de siret', + description: + 'Sélectionner le SIRET du partenaire', + required: true, + }), + templateId: Property.DynamicProperties({ + displayName: "Type du modèle d'audit", + refreshers: ['certifInfo'], + required: true, + props: async ({ auth, certifInfo }) => { + if (!certifInfo) { + console.error('certifInfo is undefined'); + return {}; + } + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${wedofCommon.baseUrl}/certificationPartnerAuditTemplates`, + queryParams: { certifInfo: certifInfo as unknown as string }, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': auth as unknown as string, + }, + }); + + const options = response.body.map((template: { id: string; name: string }) => ({ + label: template.name, + value: template.id, + })); + + return { + templateId: Property.StaticDropdown({ + displayName: "Modèle d'audit", + required: true, + options: { + options: options, + }, + }), + } as DynamicPropsValue; + + } catch (error) { + console.error('Error fetching templates:', error); + return {}; + } + }, + }), + }, + async run(context) { + const message = { + templateId: context.propsValue.templateId['templateId'] ?? null, + }; + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/certifications/' + + context.propsValue.certifInfo + + '/partners/'+ context.propsValue.siret + '/audits', + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-partner-audit/create-general-audit.ts b/packages/pieces/community/wedof/src/lib/actions/certification-partner-audit/create-general-audit.ts new file mode 100644 index 0000000..e9841f3 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-partner-audit/create-general-audit.ts @@ -0,0 +1,139 @@ +import { wedofAuth } from '../../../index'; +import { createAction, DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofCommon } from '../../common/wedof'; + +export const createGeneralAudit = createAction({ + auth: wedofAuth, + name: 'createGeneralAudit', + displayName: "Générer un audit général sur les partenaires d'une certification", + description: "Permet de générer et clôturer un audit pour chacun des partenariats (actifs) de certification", + props: { + certifInfo: Property.ShortText({ + displayName: 'N° certifInfo', + description: "Permet de n'obtenir que les partenariats liés à la certification considérée", + required: true, + }), + templateId: Property.DynamicProperties({ + displayName: "Type du modèle d'audit", + refreshers: ['certifInfo'], + required: true, + props: async ({ auth, certifInfo }) => { + if (!certifInfo) { + console.error('certifInfo is undefined'); + return {}; + } + try { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${wedofCommon.baseUrl}/certificationPartnerAuditTemplates`, + queryParams: { certifInfo: certifInfo as unknown as string }, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': auth as unknown as string, + }, + }); + + const options = response.body.map((template: { id: string; name: string }) => ({ + label: template.name, + value: template.id, + })); + + return { + templateId: Property.StaticDropdown({ + displayName: "Modèle d'audit", + required: true, + options: { + options: options, + }, + }), + } as DynamicPropsValue; + + } catch (error) { + console.error('Error fetching templates:', error); + return {}; + } + }, + }), + complete: Property.StaticDropdown({ + displayName: "Clôturer les audits automatiquement", + description: "Indique si l'audit doit être clôturer", + required: false, + defaultValue : true, + options: { + disabled: false, + options: [ + { + label: "Non", + value: false, + }, + { + label: 'Oui', + value: true, + }, + ], + }, + }), + updateCompliance: Property.StaticDropdown({ + displayName: "Mettre à jour la conformité du partenariat", + description: "Indique si il faut mettre à jour la conformité du partenariat", + required: false, + defaultValue : true, + options: { + disabled: false, + options: [ + { + label: "Non", + value: false, + }, + { + label: 'Oui', + value: true, + }, + ], + }, + }), + suspend: Property.StaticDropdown({ + displayName: "Suspendre automatiquement le partenariat en cas de non-conformité", + description: "Indique si le partenariat doit être suspendu en cas de non-conformité (ne s'applique que pour les certifications actives)", + required: false, + defaultValue : true, + options: { + disabled: false, + options: [ + { + label: "Non", + value: false, + }, + { + label: 'Oui', + value: true, + }, + ], + }, + }), + + }, + async run(context) { + const message = { + templateId: context.propsValue.templateId['templateId'] ?? null, + complete: context.propsValue.complete, + updateCompliance: context.propsValue.updateCompliance, + suspend: context.propsValue.suspend, + }; + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/certifications/' + + context.propsValue.certifInfo + '/partners/audits', + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-partner/create-partnership.ts b/packages/pieces/community/wedof/src/lib/actions/certification-partner/create-partnership.ts new file mode 100644 index 0000000..6ad9dc2 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-partner/create-partnership.ts @@ -0,0 +1,42 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const createPartnership = createAction({ + auth: wedofAuth, + name: 'createPartnership', + displayName: "Créer un partenariat", + description: "Permet de créer un nouveau partenariat avec le SIRET fourni", + + props: { + certifInfo: Property.ShortText({ + displayName: 'N° certifInfo', + description: + 'Sélectionner le {certifInfo} de la certification considérée', + required: true, + }), + siret: Property.ShortText({ + displayName: 'N° siret', + description: 'Le numéro SIRET du partenaire', + required: true, + }), + }, + + async run(context) { + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: wedofCommon.baseUrl + '/certifications/partners/' + context.propsValue.siret, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + body:{ + 'certifInfo': context.propsValue.certifInfo, + } + + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-partner/delete-partnership.ts b/packages/pieces/community/wedof/src/lib/actions/certification-partner/delete-partnership.ts new file mode 100644 index 0000000..1cdfff4 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-partner/delete-partnership.ts @@ -0,0 +1,40 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const deletePartnership = createAction({ + auth: wedofAuth, + name: 'deletePartnership', + displayName: "Supprimer un partenariat", + description: "Supprime un partenariat à l'état Demande à compléter", + props: { + certifInfo: Property.ShortText({ + displayName: 'N° certifInfo', + description: 'Sélectionner le {certifInfo} de la certification considérée', + required: true, + }), + siret: Property.ShortText({ + displayName: 'N° Siret', + description: 'Sélectionner le {siret} du partenaire', + required: true, + }), + }, + async run(context) { + return ( + await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: + wedofCommon.baseUrl + + '/certifications/' + + context.propsValue.certifInfo + + '/partners/' + + context.propsValue.siret, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-partner/get-partnership.ts b/packages/pieces/community/wedof/src/lib/actions/certification-partner/get-partnership.ts new file mode 100644 index 0000000..faf398a --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-partner/get-partnership.ts @@ -0,0 +1,43 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const getPartnership = createAction({ + auth: wedofAuth, + name: 'getPartnership', + displayName: "Récupération d'un partenariat", + description: + "Récupération d'un partenariat par le certifInfo de la certification et du siret du partenaire", + props: { + certifInfo: Property.ShortText({ + displayName: 'N° certifInfo', + description: + 'Sélectionner le {certifInfo} de la certification considérée', + required: true, + }), + siret: Property.ShortText({ + displayName: 'N° Siret', + description: + 'Sélectionner le {siret} du partenaire', + required: true, + }), + }, + async run(context) { + return ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: + wedofCommon.baseUrl + + '/certifications/' + + context.propsValue.certifInfo + + '/partners/' + + context.propsValue.siret, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-partner/list-partnership.ts b/packages/pieces/community/wedof/src/lib/actions/certification-partner/list-partnership.ts new file mode 100644 index 0000000..46c3c67 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-partner/list-partnership.ts @@ -0,0 +1,147 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const listPartnerships = createAction({ + auth: wedofAuth, + name: 'listPartnerships', + displayName: "Lister les partenariats", + description: "Récupère l'ensemble des partenariats d'une certification", + props: { + certifInfo: Property.ShortText({ + displayName: 'N° certifInfo', + description: 'Identifiant de la certification', + required: true, + }), + certifier: Property.ShortText({ + displayName: 'N° Siret Certificateur', + required: false, + }), + certifierAccessState: Property.StaticDropdown({ + displayName: 'État d\'accès du certificateur', + required: false, + options: { + options: [ + { label: 'Tous', value: 'all' }, + { label: 'En attente', value: 'waiting' }, + { label: 'Accepté', value: 'accepted' }, + { label: 'Refusé', value: 'refused' }, + { label: 'Terminé', value: 'terminated' }, + { label: 'Aucun', value: 'none' }, + ] + } + }), + compliance: Property.StaticDropdown({ + displayName: 'Conformité', + required: false, + options: { + options: [ + { label: 'Tous', value: 'all' }, + { label: 'Conforme', value: 'compliant' }, + { label: 'Partiellement conforme', value: 'partiallyCompliant' }, + { label: 'Non conforme', value: 'nonCompliant' }, + { label: 'En cours', value: 'inProgress' }, + ] + } + }), + connectionIssue: Property.Checkbox({ + displayName: 'Problème de connexion', + required: false, + }), + limit: Property.Number({ + displayName: 'Limite', + defaultValue: 100, + description: 'Nombre maximal de résultats à retourner - 100 par défault', + required: false, + }), + order: Property.StaticDropdown({ + displayName: 'Ordre', + required: false, + options: { + options: [ + { label: 'Ascendant', value: 'asc' }, + { label: 'Descendant', value: 'desc' }, + ] + } + }), + page: Property.Number({ + displayName: 'Page', + defaultValue: 1, + description: 'Numéro de la page de résultats - 1 par défault', + required: false, + }), + query: Property.ShortText({ + displayName: 'Requête de recherche', + required: false, + }), + sort: Property.StaticDropdown({ + displayName: 'Trier par', + required: false, + defaultValue:'name', + options: { + options: [ + { label: "Nom de l'organisme", value: 'name' }, + { label: 'État', value: 'state' }, + ] + } + }), + state: Property.StaticDropdown({ + displayName: 'État', + required: false, + defaultValue: 'all', + options: { + options: [ + { + value: 'processing', + label: 'Demande en traitement', + }, + { + value: 'active', + label: 'Partenariat actif', + }, + { + value: 'aborted', + label: 'Demande abondonnée', + }, + { + value: 'refused', + label: 'Demande refusée', + }, + { + value: 'suspended', + label: 'Partenariat suspendu', + }, + { + value: 'revoked', + label: 'Partenariat révoqué', + }, + { + value: 'all', + label: 'Tous', + }, + ], + } + }), + }, + async run(context) { + const queryParams = new URLSearchParams(); + + for (const [key, value] of Object.entries(context.propsValue)) { + if (value !== undefined) { + queryParams.append(key, String(value)); + } + } + + return ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: wedofCommon.baseUrl +'/certifications/'+ context.propsValue.certifInfo +`/partners?${queryParams.toString()}`, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-partner/reset-partnership.ts b/packages/pieces/community/wedof/src/lib/actions/certification-partner/reset-partnership.ts new file mode 100644 index 0000000..64090d2 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-partner/reset-partnership.ts @@ -0,0 +1,37 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const resetPartnership = createAction({ + auth: wedofAuth, + name: 'resetPartnership', + displayName: "Réinitialiser un partenariat", + description: "Permet de réinitialiser les données du partenariat en état 'Demande en traitement'", + + props: { + certifInfo: Property.ShortText({ + displayName: 'N° certifInfo', + description: 'Identifiant de la certification', + required: true, + }), + siret: Property.ShortText({ + displayName: 'N° siret', + description: 'Numéro SIRET du partenaire à réinitialiser', + required: true, + }), + }, + + async run(context) { + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: wedofCommon.baseUrl + '/certifications/'+ context.propsValue.certifInfo +'/partners/'+ context.propsValue.siret +'/reinitialize', + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/certification-partner/update-partnership.ts b/packages/pieces/community/wedof/src/lib/actions/certification-partner/update-partnership.ts new file mode 100644 index 0000000..46ca74b --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/certification-partner/update-partnership.ts @@ -0,0 +1,100 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const updatePartnership = createAction({ + auth: wedofAuth, + name: 'updatePartnership', + displayName: "Mettre à jour le partenariat", + description: "Permet de mettre à jour le partenariat", + props: { + certifInfo: Property.ShortText({ + displayName: 'N° certifInfo', + description: + 'Sélectionner le {certifInfo} de la certification considérée', + required: true, + }), + siret: Property.ShortText({ + displayName: 'N° Siret', + description: + 'Sélectionner le {siret} du partenaire', + required: true, + }), + state: wedofCommon.partnershipState, + habilitation : wedofCommon.habilitation, + comment: Property.LongText({ + displayName: 'Commentaire', + description: 'Informations complémentaires sur le partenariat', + required: false, + }), + pendingActivation: Property.Checkbox({ + displayName: 'En attente d’activation', + required: false, + }), + pendingRevocation: Property.Checkbox({ + displayName: 'En attente de révocation', + required: false, + }), + pendingSuspension: Property.Checkbox({ + displayName: 'En attente de suspension', + required: false, + }), + amountHt: Property.Number({ + displayName: 'Montant HT', + description: 'Prix de vente du passage de certification (Hors Taxe)', + required: false, + }), + compliance: wedofCommon.compliance, + tags: Property.Array({ + displayName: 'Tags', + description: 'Liste de tags associés au partenariat', + required: false, + }), + metadata: Property.Array({ + displayName: 'Méta-données', + description: 'Données supplémentaires liées au partenariat', + required: false, + }), + trainingsZone: Property.Array({ + displayName: 'Zone de formation', + required: false, + }), + skillSets: Property.Array({ + displayName: 'Blocs de compétences', + required: false, + }), + }, + async run(context) { + const message = { + state: context.propsValue.state, + habilitation: context.propsValue.habilitation, + comment: context.propsValue.comment, + pendingActivation: context.propsValue.pendingActivation, + pendingRevocation: context.propsValue.pendingRevocation, + pendingSuspension: context.propsValue.pendingSuspension, + amountHt: context.propsValue.amountHt, + compliance: context.propsValue.compliance, + tags: context.propsValue.tags, + metadata: context.propsValue.metadata, + trainingsZone: context.propsValue.trainingsZone, + skillSets: context.propsValue.skillSets, + }; + return ( + await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: + wedofCommon.baseUrl + + '/certifications/' + + context.propsValue.certifInfo + + '/partners/' + + context.propsValue.siret, + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/create-activitie.ts b/packages/pieces/community/wedof/src/lib/actions/create-activitie.ts new file mode 100644 index 0000000..18705a1 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/create-activitie.ts @@ -0,0 +1,90 @@ +import { wedofAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofCommon } from '../common/wedof'; +import dayjs from 'dayjs'; + +export const createActivitie = createAction({ + auth: wedofAuth, + name: 'createActivitie', + displayName: "Créer une activité", + description: "Permet de créer une activité d'un dossier (Dossier de formation / Dossier de certification)", + props: { + entityClass: Property.StaticDropdown({ + displayName: "Choisir le type de dossier", + description: "Permet de n'obtenir que les dossiers dans le type considéré - par défaut tous les types sont retournés", + required: true, + options: { + options: [ + {label: "Dossier de certification", value: "CertificationFolder"}, + {label: "Dossier de formation", value: "RegistrationFolder"}, + {label: "Proposition commerciale", value: "Proposal"} + ], + disabled: false, + }, + }), + externalId: Property.ShortText({ + displayName: 'N° du dossier', + description: + 'Sélectionner la propriété {externalId} du dossier', + required: true, + }), + title: Property.ShortText({ + displayName: "Titre de l'activité", + required: true, + }), + type:wedofCommon.tasks, + qualiopiIndicators:wedofCommon.qualiopiIndicators, + description: Property.ShortText({ + displayName: 'Description', + required: false, + }), + userEmail: Property.ShortText({ + displayName: "Responsable (email de l'utilisateur)", + required: true, + }), + eventTime: Property.DateTime({ + displayName: "Date de début", + description: 'Date au format YYYY-MM-DDTHH:mm:ssZ.', + required: true, + }), + eventEndTime: Property.DateTime({ + displayName: "Date d'échéance", + description: 'Date au format YYYY-MM-DDTHH:mm:ssZ.', + required: false, + }), + link: Property.ShortText({ + displayName: "Lien (url) vers la tâche", + required: false, + }), + + }, + async run(context) { + const message = { + title: context.propsValue.title ?? null, + eventEndTime: context.propsValue.eventEndTime ? dayjs(context.propsValue.eventEndTime) : null, + type: context.propsValue.type, + qualiopiIndicators: context.propsValue.qualiopiIndicators, + description: context.propsValue.description ?? null, + userEmail: context.propsValue.userEmail ?? null, + link: context.propsValue.link ?? null, + eventTime: context.propsValue.eventTime ? dayjs(context.propsValue.eventTime) : null, + origin: "manual", + }; + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/activities/' + + context.propsValue.entityClass + + '/'+ context.propsValue.externalId, + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/create-task.ts b/packages/pieces/community/wedof/src/lib/actions/create-task.ts new file mode 100644 index 0000000..e3de4de --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/create-task.ts @@ -0,0 +1,86 @@ +import { wedofAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofCommon } from '../common/wedof'; +import dayjs from 'dayjs'; + +export const createTask = createAction({ + auth: wedofAuth, + name: 'createTask', + displayName: "Créer une tâche", + description: "Permet de créer une tâche d'un dossier (Dossier de formation / Dossier de certification)", + props: { + entityClass: Property.StaticDropdown({ + displayName: "Choisir le type de dossier", + description: "Permet de n'obtenir que les dossiers dans le type considéré - par défaut tous les types sont retournés", + required: true, + options: { + options: [ + {label: "Dossier de certification", value: "CertificationFolder"}, + {label: "Dossier de formation", value: "RegistrationFolder"}, + {label: "Proposition commerciale", value: "Proposal"} + ], + disabled: false, + }, + }), + externalId: Property.ShortText({ + displayName: 'N° du dossier', + description: + 'Sélectionner la propriété {externalId} du dossier', + required: true, + }), + title: Property.ShortText({ + displayName: 'Titre de la tâche', + required: true, + }), + dueDate: Property.DateTime({ + displayName: "Date d'échéance", + description: 'Date au format YYYY-MM-DDTHH:mm:ssZ.', + required: false, + }), + type:wedofCommon.tasks, + qualiopiIndicators:wedofCommon.qualiopiIndicators, + description: Property.ShortText({ + displayName: 'Description', + required: false, + }), + userEmail: Property.ShortText({ + displayName: "Responsable (email de l'utilisateur)", + required: true, + }), + link: Property.ShortText({ + displayName: "Lien (url) vers la tâche", + required: false, + }), + + }, + async run(context) { + const message = { + title: context.propsValue.title ?? null, + dueDate: context.propsValue.dueDate ? dayjs(context.propsValue.dueDate) : null, + eventEndTime: null, + type: context.propsValue.type, + qualiopiIndicators: context.propsValue.qualiopiIndicators, + description: context.propsValue.description ?? null, + userEmail: context.propsValue.userEmail ?? null, + link: context.propsValue.link ?? null, + eventTime: null, + origin: "manual", + }; + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/activities/' + + context.propsValue.entityClass + + '/'+ context.propsValue.externalId, + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/list-activities-and-tasks.ts b/packages/pieces/community/wedof/src/lib/actions/list-activities-and-tasks.ts new file mode 100644 index 0000000..2f75fcf --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/list-activities-and-tasks.ts @@ -0,0 +1,52 @@ +import { wedofAuth } from '../../index'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofCommon } from '../common/wedof'; + +export const listActivitiesAndTasks = createAction({ + auth: wedofAuth, + name: 'listActivitiesAndTasks', + displayName: "Liste de toutes les activités et tâches d'un dossier", + description: "Liste de toutes les activités et tâches d'un dossier (Dossier de formation / Dossier de certification)", + props: { + entityClass: Property.StaticDropdown({ + displayName: "Choisir le type de dossier", + description: "Permet de n'obtenir que les dossiers dans le type considéré - par défaut tous les types sont retournés", + required: true, + options: { + options: [ + { + value: "certificationFolders", + label: 'Dossier de certification', + }, + { + value: "registrationFolders", + label: 'Dossier de formation', + }, + ], + disabled: false, + }, + }), + externalId: Property.ShortText({ + displayName: 'N° du dossier', + description: + 'Sélectionner la propriété {externalId} du dossier', + required: true, + }), + }, + async run(context) { + return ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: + wedofCommon.baseUrl + + '/activities/' +context.propsValue.entityClass+'/'+ + context.propsValue.externalId, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/me.ts b/packages/pieces/community/wedof/src/lib/actions/me.ts new file mode 100644 index 0000000..1cb9a29 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/me.ts @@ -0,0 +1,25 @@ +import {wedofAuth} from '../../index'; +import {createAction} from '@activepieces/pieces-framework'; +import {HttpMethod, httpClient} from '@activepieces/pieces-common'; +import {wedofCommon} from '../common/wedof'; + +export const me = createAction({ + auth: wedofAuth, + name: 'me', + displayName: "Récupérer mes informations", + description: "Récupérer mes informations et mes détails", + props: {}, + async run(context) { + return ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: + wedofCommon.baseUrl + '/users/me', + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/my-organism.ts b/packages/pieces/community/wedof/src/lib/actions/my-organism.ts new file mode 100644 index 0000000..d8d2cb4 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/my-organism.ts @@ -0,0 +1,25 @@ +import {wedofAuth} from '../../index'; +import {createAction} from '@activepieces/pieces-framework'; +import {HttpMethod, httpClient} from '@activepieces/pieces-common'; +import {wedofCommon} from '../common/wedof'; + +export const myOrganism = createAction({ + auth: wedofAuth, + name: 'myOrganism', + displayName: "Récupérer mon organisme", + description: "Récupérer mon organisme et afficher ses détails", + props: {}, + async run(context) { + return ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: + wedofCommon.baseUrl + '/organisms/me', + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/registration-folders/bill-registration-folder.ts b/packages/pieces/community/wedof/src/lib/actions/registration-folders/bill-registration-folder.ts new file mode 100644 index 0000000..3e2954a --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/registration-folders/bill-registration-folder.ts @@ -0,0 +1,53 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const billRegistrationFolder = createAction({ + auth: wedofAuth, + name: 'billRegistrationFolder', + displayName: 'Facturer le dossier de formation', + description: + 'Associe le dossier de formation à un n° de facture et transmets les informations de facturation au financeur (EDOF par exemple)', + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de formation', + description: + 'Sélectionner la propriété {externalId} du dossier de formation', + required: true, + }), + billNumber: Property.ShortText({ + displayName: 'N° de facture', + description: 'N° de la facture à associer', + required: true, + }), + vatRate: Property.Number({ + displayName: 'TVA', + description: + 'Permet de forcer un Taux de TVA en %. Par défaut la TVA est calculée à partir des données du dossier de formation', + required: false, + }), + }, + async run(context) { + const message = { + billNumber: context.propsValue.billNumber, + vatRate: context.propsValue.vatRate, + }; + + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/registrationFolders/' + + context.propsValue.externalId + + '/billing', + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/registration-folders/cancel-registration-folder.ts b/packages/pieces/community/wedof/src/lib/actions/registration-folders/cancel-registration-folder.ts new file mode 100644 index 0000000..826b65a --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/registration-folders/cancel-registration-folder.ts @@ -0,0 +1,81 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const cancelRegistrationFolder = createAction({ + auth: wedofAuth, + name: 'cancelRegistrationFolder', + displayName: 'Annuler le dossier de formation', + description: 'Annuler le dossier de formation', + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de formation', + description: + 'Sélectionner la propriété {externalId} du dossier de formation', + required: true, + }), + code: Property.Dropdown({ + displayName: "Raison de l'annulation du dossier de formation", + description: "Sélectionner la raison de l'annulation", + required: true, + refreshers: ['auth'], + refreshOnSearch: false, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + }; + } + const response = ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: + wedofCommon.baseUrl + '/registrationFoldersReasons?type=canceled', + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': auth as string, + }, + }) + ).body; + const reasons = response.map( + (reason: { label: string; code: string }) => { + return { label: reason.label, value: reason.code }; + } + ); + return { + disabled: false, + options: reasons, + }; + }, + }), + description: Property.LongText({ + displayName: 'Description', + description: " Texte expliquant les raisons de l'annulation", + required: false, + }), + }, + async run(context) { + const message = { + code: context.propsValue.code, + description: context.propsValue.description, + }; + + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/registrationFolders/' + + context.propsValue.externalId + + '/cancel', + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/registration-folders/declare-registration-folder-intraining.ts b/packages/pieces/community/wedof/src/lib/actions/registration-folders/declare-registration-folder-intraining.ts new file mode 100644 index 0000000..0e95d79 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/registration-folders/declare-registration-folder-intraining.ts @@ -0,0 +1,49 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; +import dayjs from 'dayjs'; + +export const declareRegistrationFolderIntraining = createAction({ + auth: wedofAuth, + name: 'declareRegistrationFolderIntraining', + displayName: "Passer un dossier de formation à l'état : En formation", + description: "Change l'état d'un dossier de formation vers : En formation", + + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de formation', + description: + 'Sélectionner la propriété {externalId} du dossier de formation', + required: true, + }), + date: Property.DateTime({ + displayName: 'Entrée en formation le', + description: 'Date au format YYYY-MM-DD.', + required: false, + }), + }, + async run(context) { + const message = { + date: context.propsValue.date + ? dayjs(context.propsValue.date).format('YYYY-MM-DD') + : null, + }; + + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/registrationFolders/' + + context.propsValue.externalId + + '/inTraining', + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/registration-folders/declare-registration-folder-servicedone.ts b/packages/pieces/community/wedof/src/lib/actions/registration-folders/declare-registration-folder-servicedone.ts new file mode 100644 index 0000000..7d2e9aa --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/registration-folders/declare-registration-folder-servicedone.ts @@ -0,0 +1,104 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; +import dayjs from 'dayjs'; + +export const declareRegistrationFolderServicedone = createAction({ + auth: wedofAuth, + name: 'declareRegistrationFolderServicedone', + displayName: "Passer un dossier de formation à l'état : Service fait déclaré", + description: + "Passe le dossier dans l'état 'service fait déclaré' s'il est dans l'état 'sortie de formation' ou dans l'état 'en formation'. Si depuis l'état 'en formation', le passage à l'état intermédiaire 'sortie de formation' se fera automatiquement.", + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de formation', + description: + 'Sélectionner la propriété {externalId} du dossier de formation', + required: true, + }), + absenceDuration: Property.Number({ + displayName: "durée d'absence", + description: + "La durée d'une éventuelle absence en heures. 0 si aucune absence.", + required: false, + defaultValue: 0, + }), + forceMajeureAbsence: wedofCommon.forceMajeureAbsence, + trainingDuration: Property.Number({ + displayName: 'Durée totale de la formation', + description: + "Précise la durée totale de la formation afin de calculer le % d'absence. Si rien n'est précisé, récupère la durée dans le trainingActionInfo/duration", + required: false, + defaultValue: 0, + }), + code: Property.Dropdown({ + displayName: 'Raison de la sortie de formation', + description: 'Sélectionner la raison de sortie de formation', + required: true, + refreshers: ['auth'], + refreshOnSearch: false, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + }; + } + const response = ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: + wedofCommon.baseUrl + + '/registrationFoldersReasons?type=terminated', + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': auth as string, + }, + }) + ).body; + const reasons = response.map( + (reason: { label: string; code: string }) => { + return { label: reason.label, value: reason.code }; + } + ); + return { + disabled: false, + options: reasons, + }; + }, + }), + date: Property.DateTime({ + displayName: 'Sortie de formation le', + description: "Date du sortie de formation au format YYYY-MM-DD. Par défaut, date du jour. Si la date a déjà été indiquée au moment du terminate, il n'est pas nécessaire de la repréciser", + required: false, + defaultValue: dayjs(new Date()).format('YYYY-MM-DD'), + }), + }, + + async run(context) { + const message = { + absenceDuration: context.propsValue.absenceDuration ?? null, + forceMajeureAbsence: context.propsValue.forceMajeureAbsence ?? null, + trainingDuration: context.propsValue.trainingDuration ?? null, + code: context.propsValue.code ?? null, + date: context.propsValue.date ? dayjs(context.propsValue.date).format('YYYY-MM-DD') : null, + }; + + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/registrationFolders/' + + context.propsValue.externalId + + '/serviceDone', + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/registration-folders/declare-registration-folder-terminated.ts b/packages/pieces/community/wedof/src/lib/actions/registration-folders/declare-registration-folder-terminated.ts new file mode 100644 index 0000000..71d2b10 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/registration-folders/declare-registration-folder-terminated.ts @@ -0,0 +1,95 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; +import dayjs from 'dayjs'; + +export const declareRegistrationFolderTerminated = createAction({ + auth: wedofAuth, + name: 'declareRegistrationFolderTerminated', + displayName: "Passer un dossier de formation à l'état : sortie de formation", + description: + "Change l'état d'un dossier de formation vers : sortie de formation", + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de formation', + description: + 'Sélectionner la propriété {externalId} du dossier de formation', + required: true, + }), + date: Property.DateTime({ + displayName: 'Sortie de formation le', + description: 'Date au format YYYY-MM-DD.', + required: false, + defaultValue: dayjs(new Date()).format('YYYY-MM-DD'), + }), + code: Property.Dropdown({ + displayName: 'Raison de la sortie de formation', + description: 'Sélectionner la raison de sortie de formation', + required: true, + refreshers: ['auth'], + refreshOnSearch: false, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + }; + } + const response = ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: + wedofCommon.baseUrl + + '/registrationFoldersReasons?type=terminated', + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': auth as string, + }, + }) + ).body; + const reasons = response.map( + (reason: { label: string; code: string }) => { + return { label: reason.label, value: reason.code }; + } + ); + return { + disabled: false, + options: reasons, + }; + }, + }), + absenceDuration: Property.Number({ + displayName: "durée d'absence", + description: + "La durée d'une éventuelle absence en heures. 0 si aucune absence.", + required: false, + defaultValue: 0, + }), + }, + async run(context) { + const message = { + date: context.propsValue.date + ? dayjs(context.propsValue.date).format('YYYY-MM-DD') + : null, + code: context.propsValue.code, + absenceDuration: context.propsValue.absenceDuration, + }; + + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/registrationFolders/' + + context.propsValue.externalId + + '/terminate', + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/registration-folders/get-minimal-session-dates.ts b/packages/pieces/community/wedof/src/lib/actions/registration-folders/get-minimal-session-dates.ts new file mode 100644 index 0000000..5fc79e7 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/registration-folders/get-minimal-session-dates.ts @@ -0,0 +1,26 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const getMinimalSessionDates = createAction({ + auth: wedofAuth, + name: 'getMinimalSessionsDates', + displayName: 'Date minimale de début de session de formation', + description: + 'Récupération des dates minimales de début de session de formation', + props: {}, + + async run(context) { + return ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: wedofCommon.baseUrl + '/registrationFolders/utils/sessionMinDates', + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/registration-folders/get-registration-folder.ts b/packages/pieces/community/wedof/src/lib/actions/registration-folders/get-registration-folder.ts new file mode 100644 index 0000000..d3aa1c7 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/registration-folders/get-registration-folder.ts @@ -0,0 +1,36 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const getRegistrationFolder = createAction({ + auth: wedofAuth, + name: 'getRegistrationFolder', + displayName: 'Récupérer un dossier de formation', + description: + 'Récupérer un dossier de formation à partir de son n° de dossier', + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de formation', + description: + 'Sélectionner la propriété {externalId} du dossier de formation', + required: true, + }), + }, + + async run(context) { + return ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: + wedofCommon.baseUrl + + '/registrationFolders/' + + context.propsValue.externalId, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/registration-folders/list-registration-folder-documents.ts b/packages/pieces/community/wedof/src/lib/actions/registration-folders/list-registration-folder-documents.ts new file mode 100644 index 0000000..6264b94 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/registration-folders/list-registration-folder-documents.ts @@ -0,0 +1,35 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const getRegistrationFolderDocuments = createAction({ + auth: wedofAuth, + name: 'getRegistrationFolderDocuments', + displayName: "Liste des documents d'un dossier de formation", + description: "Récupérer la liste de documents d'un dossier de formation à partir de son n° de dossier", + props: { + Id: Property.ShortText({ + displayName: 'N° du dossier de formation', + description: + 'Sélectionner la propriété {Id} du dossier de formation', + required: true, + }), + }, + + async run(context) { + return ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: + wedofCommon.baseUrl + + '/registrationFolders/' + + context.propsValue.Id +'/files', + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/registration-folders/refuse-registration-folder.ts b/packages/pieces/community/wedof/src/lib/actions/registration-folders/refuse-registration-folder.ts new file mode 100644 index 0000000..26f86c3 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/registration-folders/refuse-registration-folder.ts @@ -0,0 +1,81 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const refuseRegistrationFolder = createAction({ + auth: wedofAuth, + name: 'refuseRegistrationFolder', + displayName: 'Refuser le dossier de formation', + description: 'Refuser le dossier de formation', + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de formation', + description: + 'Sélectionner la propriété {externalId} du dossier de formation', + required: true, + }), + code: Property.Dropdown({ + displayName: 'Raison du refus du dossier de formation', + description: 'Sélectionner la raison du refus', + required: true, + refreshers: ['auth'], + refreshOnSearch: false, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + }; + } + const response = ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: + wedofCommon.baseUrl + '/registrationFoldersReasons?type=refused', + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': auth as string, + }, + }) + ).body; + const reasons = response.map( + (reason: { label: string; code: string }) => { + return { label: reason.label, value: reason.code }; + } + ); + return { + disabled: false, + options: reasons, + }; + }, + }), + description: Property.LongText({ + displayName: 'Description', + description: ' Texte expliquant les raisons du refus', + required: false, + }), + }, + async run(context) { + const message = { + code: context.propsValue.code, + description: context.propsValue.description, + }; + + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/registrationFolders/' + + context.propsValue.externalId + + '/refuse', + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/registration-folders/search-registration-folder.ts b/packages/pieces/community/wedof/src/lib/actions/registration-folders/search-registration-folder.ts new file mode 100644 index 0000000..134a4fb --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/registration-folders/search-registration-folder.ts @@ -0,0 +1,129 @@ +import { + httpClient, + HttpMethod, + QueryParams, +} from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { + createAction, + DynamicPropsValue, + Property, +} from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; +import dayjs from 'dayjs'; + +export const searchRegistrationFolder = createAction({ + auth: wedofAuth, + name: 'listRegistrationFolders', + displayName: 'Rechercher un ou plusieurs dossiers de formation', + description: + 'Liste les dossiers de formation en fonction des critères sélectionnés', + props: { + query: Property.ShortText({ + displayName: 'Recherche', + description: 'Nom, prénom, N° de dossier, N° de certification etc..', + required: false, + }), + period: wedofCommon.period, + periodForm: Property.DynamicProperties({ + description: '', + displayName: 'ez', + required: true, + refreshers: ['period'], + props: async ({ period }) => { + const _period = period as unknown as string; + const props: DynamicPropsValue = {}; + if (_period === 'custom') { + props['since'] = Property.DateTime({ + displayName: '(Période) Entre le', + description: 'Date au format YYYY-MM-DD', + required: true, + }); + props['until'] = Property.DateTime({ + displayName: "(Période) et jusqu'au", + description: 'Date au format YYYY-MM-DD', + required: true, + }); + } else if ( + ['next', 'future', 'tomorrow'].some((v) => + _period.toLowerCase().includes(v) + ) + ) { + props['filterOnStateDate'] = wedofCommon.filterOnStateDateFuture; + } else if (_period) { + props['filterOnStateDate'] = wedofCommon.filterOnStateDate; + } + return props; + }, + }), + type: wedofCommon.type, + state: wedofCommon.state, + billingState: wedofCommon.billingState, + controlState: wedofCommon.controlState, + certificationFolderState: wedofCommon.certificationFolderState, + proposalCode: Property.ShortText({ + displayName: 'Code de proposition commercial', + description: 'Code de la proposition commercial Wedof associé', + required: false, + }), + limit: Property.Number({ + displayName: 'Nombre de dossiers de formation', + description: + 'Nombre de dossiers de formation maximum qui seront retournés par requête', + defaultValue: 100, + required: false, + }), + page: Property.Number({ + displayName: 'N° de page de la requête', + description: 'Par défaut : 1', + defaultValue: 1, + required: false, + }), + }, + + async run(context) { + const params = { + query: context.propsValue.query ?? null, + limit: context.propsValue.limit ?? null, + page: context.propsValue.page ?? null, + controlState: context.propsValue.controlState ?? null, + state: context.propsValue.state ?? null, + certificationFolderState: + context.propsValue.certificationFolderState ?? null, + billingState: context.propsValue.billingState ?? null, + type: context.propsValue.type ?? null, + period: context.propsValue.period ?? null, + since: context.propsValue.periodForm['since'] + ? dayjs(context.propsValue.periodForm['since']) + .startOf('day') + .format('YYYY-MM-DDTHH:mm:ssZ') + : null, + until: context.propsValue.periodForm['until'] + ? dayjs(context.propsValue.periodForm['until']) + .endOf('day') + .format('YYYY-MM-DDTHH:mm:ssZ') + : null, + filterOnStateDate: + context.propsValue.periodForm['filterOnStateDate'] ?? null, + proposalCode: context.propsValue.proposalCode ?? null, + }; + const queryParams: QueryParams = {}; + Object.keys(params).forEach((value) => { + const key = value as keyof typeof params; + if (params[key] != null && params[key] != undefined) { + queryParams[value] = params[key] as string; + } + }); + return ( + await httpClient.sendRequest({ + method: HttpMethod.GET, + queryParams: queryParams, + url: wedofCommon.baseUrl + '/registrationFolders', + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/registration-folders/update-completion-rate.ts b/packages/pieces/community/wedof/src/lib/actions/registration-folders/update-completion-rate.ts new file mode 100644 index 0000000..009d9aa --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/registration-folders/update-completion-rate.ts @@ -0,0 +1,44 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const updateCompletionRate = createAction({ + auth: wedofAuth, + name: 'updateCompletionRate', + displayName: "Mettre à jour l'assiduité d'un apprenant", + description: + "Mettre à jour le taux d'avancement en % d'assiduité d'un apprenant pour un Dossier de formation donné.", + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de formation', + description: + 'Sélectionner la propriété {externalId} du dossier de formation', + required: true, + }), + completionRate: Property.Number({ + displayName: "Taux d'avancement", + description: "Taux d'avancement en % compris entre 0% et 100%. Uniquement sous format d'un entier. Uniquement possible à l'état En formation et Sortie de formation", + required: true, + }), + }, + async run(context) { + const message = { + completionRate: context.propsValue.completionRate, + }; + return ( + await httpClient.sendRequest({ + method: HttpMethod.PUT, + body: message, + url: + wedofCommon.baseUrl + + '/registrationFolders/' + + context.propsValue.externalId, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/registration-folders/update-registration-folder.ts b/packages/pieces/community/wedof/src/lib/actions/registration-folders/update-registration-folder.ts new file mode 100644 index 0000000..4132818 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/registration-folders/update-registration-folder.ts @@ -0,0 +1,101 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; +import dayjs from 'dayjs'; + +export const updateRegistrationFolder = createAction({ + auth: wedofAuth, + name: 'updateRegistrationFolder', + displayName: 'Mettre à jour un dossier de formation', + description: + "Met à jour certaines informations modifiables d'un dossier de formation", + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de formation', + description: + 'Sélectionner la propriété {externalId} du dossier de formation', + required: true, + }), + price: Property.Number({ + displayName: 'Prix de la formation', + description: 'Nouveau tarif en €', + required: false, + }), + sessionStartDate: Property.DateTime({ + displayName: 'Date de début de la session de formation', + description: 'Date au format YYYY-MM-DD', + required: false, + }), + sessionEndDate: Property.DateTime({ + displayName: 'Date de fin de la session de formation', + description: 'Date au format YYYY-MM-DD', + required: false, + }), + notes: Property.LongText({ + displayName: 'Notes', + description: "Notes privées (non-visible par l'apprenant)", + required: false, + }), + description: Property.LongText({ + displayName: 'Description', + description: "Note publique visible de l'apprenant", + required: false, + }), + completionRate: Property.Number({ + displayName: "Taux d'avancement", + description: "Taux d'avancement en % compris entre 0% et 100%. Uniquement sous format d'un entier. Uniquement possible à l'état En formation et Sortie de formation", + required: false, + }), + indicativeDuration: Property.Number({ + displayName: 'Durée moyenne de la formation', + description: 'En heures, durée moyenne de la formation', + required: false, + }), + weeklyDuration: Property.Number({ + displayName: 'Temps de formation par semaine', + description: 'En heures, ne peut pas être supérieur à 99', + required: false, + }), + tags: Property.Array({ + displayName: "Tags", + description: "Liste de tags associée au dossier de formation, si vous souhaitez garder vos précédents tags, il faut les réécrire dans le champ", + required: false, + }), + }, + async run(context) { + const message = { + notes: context.propsValue.notes ?? null, + description: context.propsValue.description ?? null, + priceChange: { + price: context.propsValue.price ?? null, + }, + trainingActionInfo: { + sessionStartDate: context.propsValue.sessionStartDate + ? dayjs(context.propsValue.sessionStartDate).format('YYYY-MM-DD') + : null, + sessionEndDate: context.propsValue.sessionEndDate + ? dayjs(context.propsValue.sessionEndDate).format('YYYY-MM-DD') + : null, + completionRate: context.propsValue.completionRate ?? null, + indicativeDuration: context.propsValue.indicativeDuration ?? null, + weeklyDuration: context.propsValue.weeklyDuration ?? null, + }, + tags: context.propsValue.tags as string[], + }; + return ( + await httpClient.sendRequest({ + method: HttpMethod.PUT, + body: message, + url: + wedofCommon.baseUrl + + '/registrationFolders/' + + context.propsValue.externalId, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/registration-folders/validate-registration-folder.ts b/packages/pieces/community/wedof/src/lib/actions/registration-folders/validate-registration-folder.ts new file mode 100644 index 0000000..cce3dc1 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/registration-folders/validate-registration-folder.ts @@ -0,0 +1,53 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { wedofAuth } from '../../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const validateRegistrationFolder = createAction({ + auth: wedofAuth, + name: 'validateRegistrationFolder', + displayName: 'Valider le dossier de formation', + description: "Passer l'état du dossier de formation à l'état validé", + props: { + externalId: Property.ShortText({ + displayName: 'N° du dossier de formation', + description: + 'Sélectionner la propriété {externalId} du dossier de formation', + required: true, + }), + indicativeDuration: Property.Number({ + displayName: 'Durée totale de la formation', + description: + "Obligatoire dans le cas d'un dossier de formation avec financement France Travail", + required: false, + }), + weeklyDuration: Property.Number({ + displayName: 'Intensité hebdomadaire', + description: + 'Intensité hebdomadaire de la formation, en heures par semaine', + required: false, + }), + }, + async run(context) { + const message = { + indicativeDuration: context.propsValue.indicativeDuration, + weeklyDuration: context.propsValue.weeklyDuration, + }; + + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/registrationFolders/' + + context.propsValue.externalId + + '/validate', + body: message, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/actions/send-file.ts b/packages/pieces/community/wedof/src/lib/actions/send-file.ts new file mode 100644 index 0000000..4a861ac --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/actions/send-file.ts @@ -0,0 +1,162 @@ +import { wedofAuth } from '../../index'; +import { createAction, DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { wedofCommon } from '../common/wedof'; + +export const sendFile = createAction({ + auth: wedofAuth, + name: 'sendFile', + displayName: "Envoyer un fichier", + description: "Permet d'envoyer un fichier pour un dossier (Dossier de formation / Dossier de certification)", + props: { + entityClass: Property.StaticDropdown({ + displayName: "Choisir le type de dossier", + description: "Permet de n'obtenir que les dossiers dans le type considéré - par défaut tous les types sont retournés", + required: true, + options: { + options: [ + { + value: "certificationFolders", + label: 'Dossier de certification', + }, + { + value: "registrationFolders", + label: 'Dossier de formation', + }, + ], + disabled: false, + }, + }), + externalId: Property.ShortText({ + displayName: 'N° du dossier', + description: + 'Sélectionner la propriété {externalId} du dossier', + required: true, + }), + title: Property.ShortText({ + displayName: 'Titre du fichier', + required: false, + }), + typeId: Property.DynamicProperties({ + displayName: 'Type du fichier', + refreshers: ['entityClass', 'externalId'], + required: true, + props: async ({ auth, entityClass, externalId }) => { + const fields: DynamicPropsValue = {}; + if (!entityClass) { + console.error('entityClass is undefined'); + return {}; + } + if (!externalId) { + console.error('externalId is undefined'); + return {}; + } + try { + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${wedofCommon.baseUrl}/${entityClass}/${externalId}/files`, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': auth as unknown as string, + }, + }); + const data = res.body; + if (Array.isArray(data)) { + data.forEach((field: { id: string | number; name: string; }) => { + fields[field.id] = Property.StaticDropdown({ + displayName: field.name, + options: { + options: data.map((option: { typeId: string; name: string; }) => ({ + value: option.typeId, + label: option.name, + })), + disabled: false, + }, + required: false, + }); + }); + } + } catch (error) { + console.error('Error fetching data:', error); + } + return fields; + }, + }), + typeFile: Property.StaticDropdown({ + displayName: "Choisir le format d'envoi du fichier", + description: "Permet de choisir la méthode d'envoi du fichier", + required: false, + defaultValue: "file", + options: { + options: [ + { + value: "file", + label: 'Attacher un document', + }, + { + value: "fileUrl", + label: 'Ajouter un lien', + }, + { + value: "url", + label: "Ajouter à partir d'un lien", + }, + ], + disabled : false, + }, + + }), + files: Property.DynamicProperties({ + description: '', + displayName: 'ez', + required: true, + refreshers: ['typeFile'], + props: async ({ typeFile }) => { + const _type = typeFile as unknown as string; + const props: DynamicPropsValue = {}; + if (_type === "url") { + props['fileToDownload'] = Property.LongText({ + displayName: "Lien vers le document", + description: 'URL du fichier à télécharger', + required: true, + }); + } else if (_type === "fileUrl") { + props['file'] = Property.LongText({ + displayName: "Lien a envoyer", + description: 'URL du fichier', + required: true, + }); + } else if (_type === "file") { + props['file'] = Property.File({ + displayName: "Fichier a envoyer", + required: true, + }); + } + return props; + }, + }), + }, + async run(context) { + const message = { + title: context.propsValue.title ?? null, + typeId: String(context.propsValue.typeId['undefined'])?? null, + file:context.propsValue.files['file'] ?? null, + fileToDownload: context.propsValue.files['fileToDownload'] ?? null, + }; + return ( + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: + wedofCommon.baseUrl + + '/' + + context.propsValue.entityClass + + '/'+ context.propsValue.externalId + '/files', + body: message, + headers: { + 'Content-Type': 'multipart/form-data', + 'X-Api-Key': context.auth as string, + }, + }) + ).body; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/wedof/src/lib/common/wedof.ts b/packages/pieces/community/wedof/src/lib/common/wedof.ts new file mode 100644 index 0000000..6bbd9d4 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/common/wedof.ts @@ -0,0 +1,1110 @@ +import { Property } from '@activepieces/pieces-framework'; +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; + +export const wedofCommon = { + baseUrl: 'https://www.wedof.fr/api', + host: 'https://www.wedof.fr/api', + + subscribeWebhook: async ( + events: string[], + webhookUrl: string, + apiKey: string, + name: string + ) => { + const request = { + method: HttpMethod.POST, + url: `${wedofCommon.baseUrl}/webhooks`, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': apiKey, + 'User-Agent': 'activepieces', + }, + body: { + url: webhookUrl, + events: events, + name: name, + secret: null, + enabled: true, + ignoreSsl: false, + }, + }; + const response = await httpClient.sendRequest(request); + if (response.status !== 201) { + let errorMessage = `Échec de la création du webhook. Code de statut reçu: ${response.status}`; + if (response.body && typeof response.body === 'object') { + const errorBody = response.body as any; + if (errorBody.detail) { + errorMessage += `. Erreur: ${errorBody.detail}`; + } + if (errorBody.violations && Array.isArray(errorBody.violations)) { + const violations = errorBody.violations + .map((v: any) => `${v.propertyPath}: ${v.title}`) + .join(', '); + errorMessage += `. Détails: ${violations}`; + } + if (!errorBody.detail && !errorBody.violations) { + errorMessage += `. Réponse: ${JSON.stringify(response.body)}`; + } + } + throw new Error(errorMessage); + } + return response.body.id; + }, + + handleWebhookSubscription: async ( + events: string[], + context: any, + name: string + ) => { + const id = await context.store.get('_webhookId'); + if (id === null) { + try { + const webhookId = await wedofCommon.subscribeWebhook( + events, + context.webhookUrl, + context.auth as string, + name + ); + await context.store.put('_webhookId', webhookId); + } catch (error) { + console.error('Erreur lors de la création du webhook:', error); + const errorMessage = + error instanceof Error ? error.message : 'Erreur inconnue'; + throw new Error(`Échec de la création du webhook: ${errorMessage}`); + } + } else { + console.log('/////////// webhook already exist ////'); + } + }, + + unsubscribeWebhook: async (webhookId: string, apiKey: string) => { + const request = { + method: HttpMethod.DELETE, + url: `${wedofCommon.baseUrl}/webhooks/${webhookId}`, + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': apiKey, + 'User-Agent': 'activepieces', + }, + }; + return await httpClient.sendRequest(request); + }, + + state: Property.StaticMultiSelectDropdown({ + displayName: 'Etat du dossier de formation', + required: false, + options: { + options: [ + { + value: 'notProcessed', + label: 'Non traité', + }, + { + value: 'validated', + label: 'Validé', + }, + { + value: 'waitingAcceptation', + label: "Validé (En cours d'instruction par France Travail)", + }, + { + value: 'accepted', + label: 'Accepté', + }, + { + value: 'inTraining', + label: 'En formation', + }, + { + value: 'terminated', + label: 'Sortie de formation', + }, + { + value: 'serviceDoneDeclared', + label: 'Service fait déclaré', + }, + { + value: 'serviceDoneValidated', + label: 'Service fait validé', + }, + { + value: 'canceledByAttendee', + label: 'Annulé (par le titulaire)', + }, + { + value: 'canceledByAttendeeNotRealized', + label: 'Annulation titulaire (non réalisé)', + }, + { + value: 'canceledByOrganism', + label: "Annulé (par l'organisme)", + }, + { + value: 'canceledByFinancer', + label: 'Annulé (par le financeur)', + }, + { + value: 'rejectedWithoutTitulaireSuite', + label: 'Annulé sans suite', + }, + { + value: 'refusedByAttendee', + label: 'Refus titulaire', + }, + { + value: 'refusedByOrganism', + label: "Refusé (par l'organisme)", + }, + { + value: 'refusedByFinancer', + label: 'Refusé (par le financeur)', + }, + ], + disabled: false, + }, + }), + + partnershipState: Property.StaticDropdown({ + displayName: 'État du partenariat de certification', + required: false, + options: { + disabled: false, + options: [ + { + value: 'processing', + label: 'Demande en traitement', + }, + { + value: 'active', + label: 'Partenariat actif', + }, + { + value: 'aborted', + label: 'Demande abondonnée', + }, + { + value: 'refused', + label: 'Demande refusée', + }, + { + value: 'suspended', + label: 'Partenariat suspendu', + }, + { + value: 'revoked', + label: 'Partenariat révoqué', + }, + ], + }, + }), + + habilitation: Property.StaticDropdown({ + displayName: 'Habilitation du partenaire', + required: false, + options: { + disabled: false, + options: [ + { + value: 'evaluate', + label: 'Habilitation pour organiser l’évaluation', + }, + { + value: 'train', + label: 'Habilitation pour former', + }, + { + value: 'train_evaluate', + label: 'Habilitation pour former et organiser l’évaluation', + }, + ], + }, + }), + + compliance: Property.StaticDropdown({ + displayName: 'Conformité', + required: false, + options: { + options: [ + { label: 'Conforme', value: 'compliant' }, + { label: 'Partiellement Conforme', value: 'partiallyCompliant' }, + { label: 'Non Conforme', value: 'nonCompliant' }, + ], + }, + }), + + events: Property.StaticMultiSelectDropdown({ + displayName: 'Événement sur le dossier de formation', + required: true, + options: { + options: [ + { + value: 'registrationFolder.created', + label: 'Créé', + }, + { + value: 'registrationFolder.updated', + label: 'Mis à jour', + }, + { + value: 'registrationFolder.notProcessed', + label: 'Non traité', + }, + { + value: 'registrationFolder.validated', + label: 'Validé', + }, + { + value: 'registrationFolder.waitingAcceptation', + label: "Validé (En cours d'instruction par France Travail)", + }, + { + value: 'registrationFolder.accepted', + label: 'Accepté', + }, + { + value: 'registrationFolder.inTraining', + label: 'En formation', + }, + { + value: 'registrationFolder.terminated', + label: 'Sortie de formation', + }, + { + value: 'registrationFolder.serviceDoneDeclared', + label: 'Service fait déclaré', + }, + { + value: 'registrationFolder.serviceDoneValidated', + label: 'Service fait validé', + }, + { + value: 'registrationFolderFile.added', + label: 'Document ajouté', + }, + { + value: 'registrationFolderFile.updated', + label: 'Document mis a jour', + }, + { + value: 'registrationFolderFile.deleted', + label: 'Document supprimé', + }, + { + value: 'registrationFolderFile.valid', + label: 'Document validé', + }, + { + value: 'registrationFolderFile.refused', + label: 'Document refusé', + }, + { + value: 'registrationFolderFile.toReview', + label: 'Document à vérifier', + }, + { + value: 'registrationFolder.canceledByAttendee', + label: 'Annulé (par le titulaire)', + }, + { + value: 'registrationFolder.canceledByAttendeeNotRealized', + label: 'Annulation titulaire (non réalisé)', + }, + { + value: 'registrationFolder.canceledByOrganism', + label: "Annulé (par l'organisme)", + }, + { + value: 'registrationFolder.canceledByFinancer', + label: 'Annulé (par le financeur)', + }, + { + value: 'registrationFolder.rejectedWithoutTitulaireSuite', + label: 'Annulé sans suite', + }, + { + value: 'registrationFolder.refusedByAttendee', + label: 'Refus titulaire', + }, + { + value: 'registrationFolder.refusedByOrganism', + label: "Refusé (par l'organisme)", + }, + { + value: 'registrationFolder.refusedByFinancer', + label: 'Refusé (par le financeur)', + }, + { + value: 'registrationFolder.refusedByFinancer', + label: 'Refusé (par le financeur)', + }, + { + value: 'registrationFolderBilling.notBillable', + label: 'Pas facturable', + }, + { + value: 'registrationFolderBilling.depositWait', + label: 'Acompte en attente de dépot', + }, + { + value: 'registrationFolderBilling.depositPaid', + label: 'Acompte déposé', + }, + { + value: 'registrationFolderBilling.toBill', + label: 'A facturer', + }, + { + value: 'registrationFolderBilling.billed', + label: 'Facturé', + }, + { + value: 'registrationFolderBilling.paid', + label: 'Payé', + }, + ], + disabled: false, + }, + }), + + certificationEvents: Property.StaticMultiSelectDropdown({ + displayName: 'Événement sur le dossier de certification', + required: true, + options: { + options: [ + { + value: 'certificationFolder.created', + label: 'Créé', + }, + { + value: 'certificationFolder.updated', + label: 'Mis à jour', + }, + { + value: 'certificationFolder.accrochageOk', + label: 'Accrochage réussi', + }, + { + value: 'certificationFolder.accrochageKo', + label: 'Accrochage en erreur', + }, + { + value: 'certificationFolder.toRegister', + label: 'À enregistrer', + }, + { + value: 'certificationFolder.registered', + label: 'Enregistré', + }, + { + value: 'certificationFolder.toTake', + label: 'Prêt à passer', + }, + { + value: 'certificationFolder.toControl', + label: 'À contrôler', + }, + { + value: 'certificationFolder.success', + label: 'Réussi', + }, + { + value: 'certificationFolder.refused', + label: 'Refusé', + }, + { + value: 'certificationFolder.failed', + label: 'Échoué', + }, + { + value: 'certificationFolder.aborted', + label: 'Abandonné', + }, + { + value: 'certificationFolder.inTrainingStarted', + label: 'Formation démarrée', + }, + { + value: 'certificationFolder.inTrainingEnded', + label: 'Formation terminée', + }, + ], + disabled: false, + }, + }), + + forceMajeureAbsence: Property.StaticDropdown({ + displayName: 'Absence pour raison de force majeure', + description: "Si absence pour raison de force majeure, 'Oui', sinon 'Non'", + required: false, + defaultValue: false, + options: { + options: [ + { + value: true, + label: 'Oui', + }, + { + value: false, + label: 'Non', + }, + ], + disabled: false, + }, + }), + + europeanLanguageLevel: Property.StaticDropdown({ + displayName: 'Nomenclature européeenne pour les certifications de langues', + required: false, + defaultValue: null, + options: { + options: [ + { label: 'C2', value: 'C2' }, + { label: 'C1', value: 'C1' }, + { label: 'B2', value: 'B2' }, + { label: 'B1', value: 'B1' }, + { label: 'A2', value: 'A2' }, + { label: 'A1', value: 'A1' }, + { label: 'INSUFFISANT', value: 'INSUFFISANT' }, + ], + disabled: false, + }, + }), + + gradePass: Property.StaticDropdown({ + displayName: 'Ajoute une mention au dossier de certification', + required: false, + defaultValue: null, + options: { + options: [ + { label: 'SANS MENTION', value: 'SANS_MENTION' }, + { label: 'MENTION ASSEZ BIEN', value: 'MENTION_ASSEZ_BIEN' }, + { label: 'MENTION BIEN', value: 'MENTION_BIEN' }, + { label: 'MENTION TRES BIEN', value: 'MENTION_TRES_BIEN' }, + { + label: 'MENTION TRES BIEN AVEC FELICITATIONS', + value: 'MENTION_TRES_BIEN_AVEC_FELICITATIONS_DU_JURY', + }, + ], + disabled: false, + }, + }), + + examinationType: Property.StaticDropdown({ + displayName: "Type de passage de l'examen", + required: false, + defaultValue: null, + options: { + options: [ + { + value: 'A_DISTANCE', + label: 'À distance', + }, + { + value: 'EN_PRESENTIEL', + label: 'En présentiel', + }, + { + value: 'MIXTE', + label: 'Mixte', + }, + ], + disabled: false, + }, + }), + + controlState: Property.StaticMultiSelectDropdown({ + displayName: 'Etat de controle', + description: + "Permet de n'obtenir que les dossiers dans l'état de contrôle considéré", + required: false, + options: { + options: [ + { + value: 'notInControl', + label: 'Aucun contrôle', + }, + { + value: 'inControl', + label: 'En cours de contrôle', + }, + { + value: 'released', + label: 'Contrôle terminé', + }, + ], + disabled: false, + }, + }), + + certificationFolderState: Property.StaticMultiSelectDropdown({ + displayName: 'État du dossier de certification', + required: false, + options: { + options: [ + { + label: 'Tous', + value: 'all', + }, + { + label: 'À enregistrer', + value: 'toRegister', + }, + { + label: 'Enregistré', + value: 'registered', + }, + { + label: 'Prêt à passer', + value: 'toTake', + }, + { + label: 'À contrôler', + value: 'toControl', + }, + { + label: 'Réussi', + value: 'success', + }, + { + label: 'À repasser', + value: 'toRetake', + }, + { + label: 'Échoué', + value: 'failed', + }, + { + label: 'Refusé', + value: 'refused', + }, + { + label: 'Abandonné', + value: 'aborted', + }, + ], + disabled: false, + }, + }), + + billingState: Property.StaticMultiSelectDropdown({ + displayName: 'État de facturation', + required: false, + options: { + options: [ + { + label: 'Tous', + value: 'all', + }, + { + label: 'Pas facturable', + value: 'notBillable', + }, + { + label: 'En attente du virement', + value: 'depositWait', + }, + { + label: 'Virement effectué', + value: 'depositPaid', + }, + { + label: 'A facturer', + value: 'toBill', + }, + { + label: 'Facturé', + value: 'billed', + }, + { + label: 'Payé', + value: 'paid', + }, + ], + disabled: false, + }, + }), + + type: Property.StaticMultiSelectDropdown({ + displayName: 'Financement', + required: false, + options: { + options: [ + { + label: 'Tous', + value: 'all', + }, + { + label: 'CPF', + value: 'cpf', + }, + { + label: 'Kairos (AIF)', + value: 'kairosAif', + }, + { + label: 'OPCO', + value: 'opco', + }, + { + label: 'Entreprise', + value: 'company', + }, + { + label: 'Autofinancement', + value: 'individual', + }, + { + label: 'Pôle Emploi (Autres)', + value: 'poleEmploi', + }, + ], + disabled: false, + }, + }), + + period: Property.StaticDropdown({ + displayName: 'Période', + required: false, + defaultValue: null, + options: { + options: [ + { + label: 'Aucune période', + value: null, + }, + { + label: 'Personnalisée', + value: 'custom', + }, + { + label: 'Demain', + value: 'tomorrow', + }, + { + label: "Aujourd'hui", + value: 'today', + }, + { + label: 'Hier', + value: 'yesterday', + }, + { + label: '7 derniers jours', + value: 'rollingWeek', + }, + { + label: '7 prochains jours', + value: 'rollingWeekFuture', + }, + { + label: 'Semaine prochaine', + value: 'nextWeek', + }, + { + label: 'Semaine précédente', + value: 'previousWeek', + }, + { + label: 'Semaine courante', + value: 'currentWeek', + }, + { + label: '30 derniers jours', + value: 'rollingMonth', + }, + { + label: '30 prochains jours', + value: 'rollingMonthFuture', + }, + { + label: 'Mois prochain', + value: 'nextMonth', + }, + { + label: 'Mois précédent', + value: 'previousMonth', + }, + { + label: 'Mois courant', + value: 'currentMonth', + }, + { + label: '12 derniers mois', + value: 'rollingYear', + }, + { + label: '12 prochains mois', + value: 'rollingYearFuture', + }, + { + label: 'Année prochaine', + value: 'nextYear', + }, + { + label: 'Année précédente', + value: 'previousYear', + }, + { + label: 'Année courante', + value: 'currentYear', + }, + { + label: 'Période de facturation Wedof en cours', + value: 'wedofInvoice', + }, + ], + disabled: false, + }, + }), + + filterOnStateDate: Property.StaticDropdown({ + displayName: '(Période) Basé sur la date de', + required: true, + defaultValue: 'lastUpdate', + options: { + disabled: false, + options: [ + { + label: 'Date de mise à jour', + value: 'lastUpdate', + }, + { + label: 'Date de Création', + value: 'createdOn', + }, + { + label: 'Passage à Non Traité', + value: 'notProcessedDate', + }, + { + label: 'Passage à Validé', + value: 'validatedDate', + }, + { + label: 'Passage à Accepter', + value: 'acceptedDate', + }, + { + label: 'Passage à Entrer en formation', + value: 'inTrainingDate', + }, + { + label: 'Passage à Sortie de formation', + value: 'terminatedDate', + }, + { + label: 'Passage à Service fait Déclaré', + value: 'serviceDoneDeclaredDate', + }, + { + label: 'Passage à Service fait Validé', + value: 'serviceDoneValidatedDate', + }, + { + label: 'Passage à Facturer', + value: 'billedDate', + }, + { + label: 'Passage à Refus titulaire', + value: 'refusedByAttendeeDate', + }, + { + label: "Passage à Refusé (par l'organisme)", + value: 'refusedByOrganismDate', + }, + { + label: 'Passage à Annulé (parle titulaire)', + value: 'canceledByAttendeeDate', + }, + { + label: "Passage à Annulé (par l'organisme)", + value: 'canceledByOrganismDate', + }, + { + label: 'Passage à Annulation titulaire (non réalisé)', + value: 'canceledByAttendeeNotRealizedDate', + }, + { + label: 'Passage à Annulé sans suite', + value: 'rejectedWithoutTitulaireSuiteDate', + }, + { + label: 'Date de début de session', + value: 'sessionStartDate', + }, + { + label: 'Date de fin de session', + value: 'sessionEndDate', + }, + ], + }, + }), + filterOnStateDateFuture: Property.StaticDropdown({ + displayName: '(Période) Basé sur la date de', + required: true, + defaultValue: 'sessionStartDate', + options: { + disabled: false, + options: [ + { + label: 'Date de début de session', + value: 'sessionStartDate', + }, + { + label: 'Date de fin de session', + value: 'sessionEndDate', + }, + ], + }, + }), + + sort: Property.StaticDropdown({ + displayName: 'Tri sur critère', + required: true, + options: { + disabled: false, + options: [ + { + label: "Date du dernier changement d'état", + value: 'stateLastUpdate', + }, + { + label: 'Date du dernier dossier mis à réussi', + value: 'successDate', + }, + ], + }, + }), + + order: Property.StaticDropdown({ + displayName: 'Ordre', + description: + 'Tri les résultats par ordre ascendant ou descendant - par défaut descendant', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Descendant', + value: 'desc', + }, + { + label: 'Ascendant', + value: 'asc', + }, + ], + }, + }), + + tasks: Property.StaticDropdown({ + displayName: 'Type de tâche', + required: true, + options: { + disabled: false, + options: [ + { + label: 'Téléphone', + value: 'phone', + }, + { + label: 'Email', + value: 'email', + }, + { + label: 'Meeting', + value: 'meeting', + }, + { + label: 'Chat', + value: 'chat', + }, + { + label: 'SMS', + value: 'sms', + }, + { + label: 'Formation', + value: 'training', + }, + { + label: 'Remarque', + value: 'remark', + }, + { + label: 'Document', + value: 'file', + }, + ], + }, + }), + + qualiopiIndicators: Property.StaticDropdown({ + displayName: 'Associée à Qualiopi', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Ind. 1 : Informations du public', + value: 1, + }, + { + label: 'Ind. 2 : Indicateurs de résultats', + value: 2, + }, + { + label: 'Ind. 3 : Obtentions des certifications', + value: 3, + }, + { + label: 'Ind. 4 : Analyse du besoin', + value: 4, + }, + { + label: 'Ind. 5 : Objectifs de la prestation', + value: 5, + }, + { + label: 'Ind. 6 : Mise en oeuvre de la prestation', + value: 6, + }, + { + label: 'Ind. 7 : Adéquation contenus / exigences', + value: 7, + }, + { + label: "Ind. 8 : Positionnement à l'entrée", + value: 8, + }, + { + label: 'Ind. 9 : Condition de déroulement', + value: 9, + }, + { + label: 'Ind. 10 : Adaptation de la prestation', + value: 10, + }, + { + label: 'Ind. 11 : Atteinte des objectifs', + value: 11, + }, + { + label: 'Ind. 12 : Engagement des bénéficiaires', + value: 12, + }, + { + label: 'Ind. 13 : Coordination des apprentis', + value: 13, + }, + { + label: 'Ind. 14 : Exercice de la citoyenneté', + value: 14, + }, + { + label: "Ind. 15 : Droits à devoirs de l'apprenti", + value: 15, + }, + { + label: 'Ind. 16 : Présentation à la certification', + value: 16, + }, + { + label: 'Ind. 17 : Moyens humains et techniques', + value: 17, + }, + { + label: 'Ind. 18 : Coordination des acteurs', + value: 18, + }, + { + label: 'Ind. 19 : Ressources pédagogiques', + value: 19, + }, + { + label: 'Ind. 20 : Personnels dédiés', + value: 20, + }, + { + label: 'Ind. 21 : Compétences des acteurs', + value: 21, + }, + { + label: 'Ind. 22 : Gestion des compétences', + value: 22, + }, + { + label: 'Ind. 23 : Veille légale et réglementaire', + value: 23, + }, + { + label: 'Ind. 24 : Veille emplois et métiers', + value: 24, + }, + { + label: 'Ind. 25 : Veille technologique', + value: 25, + }, + { + label: 'Ind. 26 : Public en situation de handicap', + value: 26, + }, + { + label: 'Ind. 27 : Sous-traitance et portage salarial', + value: 27, + }, + { + label: 'Ind. 28 : Formation Situation de travail', + value: 28, + }, + { + label: 'Ind. 29 : Insertion professionnelle', + value: 29, + }, + { + label: 'Ind. 30 : Recueil des appréciations', + value: 30, + }, + { + label: 'Ind. 31 : Traitement des réclamations', + value: 31, + }, + { + label: "Ind. 32 : Mesures d'amélioration continue", + value: 32, + }, + ], + }, + }), + + cdcState: Property.StaticDropdown({ + displayName: "État de l'accrochage", + description: + "Permet d'indiquer où en est le dossier de certification dans le processus d'accrochage auprès de la CDC", + required: false, + options: { + disabled: false, + options: [ + { + label: 'Tous', + value: 'all', + }, + { + label: 'Jamais accroché', + value: 'notExported', + }, + { + label: "Envoyé et en attente de l'accusé", + value: 'exported', + }, + { + label: 'Accrochage réussi', + value: 'processedOk', + }, + { + label: 'Accrochage en erreur', + value: 'processedKo', + }, + ], + }, + }), +}; diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-initial-experience-answered.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-initial-experience-answered.ts new file mode 100644 index 0000000..9aafa62 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-initial-experience-answered.ts @@ -0,0 +1,104 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationFolderSurveyInitialExperienceAnswered = createTrigger( + { + auth: wedofAuth, + name: 'certificationFolderSurveyInitialExperienceAnswered', + displayName: + 'Enquête "Situation professionnelle en début de cursus" répondue', + description: + "Se déclenche lorsqu'un une enquête de début de cursus est répondue", + type: TriggerStrategy.WEBHOOK, + props: {}, + sampleData: { + id: 0, + initialExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + initialExperienceAnsweredDate: '2019-08-24T14:15:22Z', + sixMonthExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + sixMonthExperienceAnsweredDate: '2019-08-24T14:15:22Z', + sixMonthExperienceStartDate: '2019-08-24T14:15:22Z', + longTermExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + longTermExperienceAnsweredDate: '2019-08-24T14:15:22Z', + longTermExperienceStartDate: '2019-08-24T14:15:22Z', + state: 'created', + }, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationFolderSurvey.initialExperienceAnswered'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, + } +); diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-initial-experience-available.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-initial-experience-available.ts new file mode 100644 index 0000000..9b25b06 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-initial-experience-available.ts @@ -0,0 +1,103 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationFolderSurveyInitialExperienceAvailable = + createTrigger({ + auth: wedofAuth, + name: 'certificationFolderSurveyInitialExperienceAvailable', + displayName: + 'Enquête "Situation professionnelle en début de cursus" disponible', + description: + "Se déclenche lorsqu'un une enquête de début de cursus est disponible", + type: TriggerStrategy.WEBHOOK, + props: {}, + sampleData: { + id: 0, + initialExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + initialExperienceAnsweredDate: '2019-08-24T14:15:22Z', + sixMonthExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + sixMonthExperienceAnsweredDate: '2019-08-24T14:15:22Z', + sixMonthExperienceStartDate: '2019-08-24T14:15:22Z', + longTermExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + longTermExperienceAnsweredDate: '2019-08-24T14:15:22Z', + longTermExperienceStartDate: '2019-08-24T14:15:22Z', + state: 'created', + }, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationFolderSurvey.created'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, + }); diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-long-experience-answered.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-long-experience-answered.ts new file mode 100644 index 0000000..ec9f4bb --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-long-experience-answered.ts @@ -0,0 +1,102 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationFolderSurveyLongTermExperienceAnswered = + createTrigger({ + auth: wedofAuth, + name: 'certificationFolderSurveyLongTermExperienceAnswered', + displayName: 'Enquête "Situation professionnelle au moins un an" répondue', + description: + "Se déclenche lorsqu'un une enquête de au moins un an de cursus est répondue", + type: TriggerStrategy.WEBHOOK, + props: {}, + sampleData: { + id: 0, + initialExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + initialExperienceAnsweredDate: '2019-08-24T14:15:22Z', + sixMonthExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + sixMonthExperienceAnsweredDate: '2019-08-24T14:15:22Z', + sixMonthExperienceStartDate: '2019-08-24T14:15:22Z', + longTermExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + longTermExperienceAnsweredDate: '2019-08-24T14:15:22Z', + longTermExperienceStartDate: '2019-08-24T14:15:22Z', + state: 'created', + }, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationFolderSurvey.longTermExperienceAnswered'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, + }); diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-long-experience-available.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-long-experience-available.ts new file mode 100644 index 0000000..0312712 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-long-experience-available.ts @@ -0,0 +1,103 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationFolderSurveyLongTermExperienceAvailable = + createTrigger({ + auth: wedofAuth, + name: 'certificationFolderSurveyLongTermExperienceAvailable', + displayName: + 'Enquête "Situation professionnelle au moins un an" disponible', + description: + "Se déclenche lorsqu'un une enquête de au moins un an de cursus est disponible", + type: TriggerStrategy.WEBHOOK, + props: {}, + sampleData: { + id: 0, + initialExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + initialExperienceAnsweredDate: '2019-08-24T14:15:22Z', + sixMonthExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + sixMonthExperienceAnsweredDate: '2019-08-24T14:15:22Z', + sixMonthExperienceStartDate: '2019-08-24T14:15:22Z', + longTermExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + longTermExperienceAnsweredDate: '2019-08-24T14:15:22Z', + longTermExperienceStartDate: '2019-08-24T14:15:22Z', + state: 'created', + }, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationFolderSurvey.longTermExperienceAvailable'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, + }); \ No newline at end of file diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-six-month-experience-answered.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-six-month-experience-answered.ts new file mode 100644 index 0000000..8a3cce7 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-six-month-experience-answered.ts @@ -0,0 +1,102 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationFolderSurveySixMonthExperienceAnswered = + createTrigger({ + auth: wedofAuth, + name: 'certificationFolderSurveySixMonthExperienceAnswered', + displayName: 'Enquête "Situation professionnelle de 6 mois" répondue', + description: + "Se déclenche lorsqu'un une enquête de 6 mois de cursus est répondue", + type: TriggerStrategy.WEBHOOK, + props: {}, + sampleData: { + id: 0, + initialExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + initialExperienceAnsweredDate: '2019-08-24T14:15:22Z', + sixMonthExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + sixMonthExperienceAnsweredDate: '2019-08-24T14:15:22Z', + sixMonthExperienceStartDate: '2019-08-24T14:15:22Z', + longTermExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + longTermExperienceAnsweredDate: '2019-08-24T14:15:22Z', + longTermExperienceStartDate: '2019-08-24T14:15:22Z', + state: 'created', + }, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationFolderSurvey.sixMonthExperienceAnswered'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, + }); diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-six-month-experience-available.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-six-month-experience-available.ts new file mode 100644 index 0000000..f1f91b8 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-folder-survey/certification-folder-survey-six-month-experience-available.ts @@ -0,0 +1,102 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationFolderSurveySixMonthExperienceAvailable = + createTrigger({ + auth: wedofAuth, + name: 'certificationFolderSurveySixMonthExperienceAvailable', + displayName: 'Enquête "Situation professionnelle de 6 mois" disponible', + description: + "Se déclenche lorsqu'un une enquête de 6 mois de cursus est disponible", + type: TriggerStrategy.WEBHOOK, + props: {}, + sampleData: { + id: 0, + initialExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + initialExperienceAnsweredDate: '2019-08-24T14:15:22Z', + sixMonthExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + sixMonthExperienceAnsweredDate: '2019-08-24T14:15:22Z', + sixMonthExperienceStartDate: '2019-08-24T14:15:22Z', + longTermExperience: { + id: 0, + qualification: 0, + certificationName: 'string', + job: 'string', + companyName: 'string', + salaryYearly: 0, + situation: 'string', + contractType: 'string', + executiveStatus: true, + startDate: '2019-08-24T14:15:22Z', + endDate: '2019-08-24T14:15:22Z', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + }, + longTermExperienceAnsweredDate: '2019-08-24T14:15:22Z', + longTermExperienceStartDate: '2019-08-24T14:15:22Z', + state: 'created', + }, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationFolderSurvey.sixMonthExperienceAvailable'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, + }); \ No newline at end of file diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-registred.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-registred.ts new file mode 100644 index 0000000..6f33437 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-registred.ts @@ -0,0 +1,224 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationFolderRegistred = createTrigger({ + auth: wedofAuth, + name: 'certificationFolderRegistred', + displayName: 'Dossier de certification enregistré', + description: "Se déclenche lorsqu'un dossier de certification est enregistré", + props: {}, + sampleData: { + attendeeLink: 'https://test.wedof.fr/candidat-1234-123456789', + permalink: 'https://test.wedof.fr/dossier-certification-1234', + addToPassportLink: + 'https://test.wedof.fr/candidat-1234-123456789-passeport', + id: 2024, + examinationDate: null, + examinationEndDate: null, + examinationPlace: null, + issueDate: null, + expirationDate: null, + detailedResult: null, + digitalProofLink: null, + state: 'registered', + files: [ + { + permalink: 'https://test.wedof.fr/candidat-1234-123456789-document-4', + id: 40, + typeId: 4, + fileName: 'OUHAHAH', + link: 'https://community.n8n.io/t/declarative-style-nodes-how-to-send-multipart-form-data/26416', + fileType: 'link', + state: 'valid', + comment: null, + generationState: 'notGenerated', + createdOn: '2024-06-20T13:48:12.000Z', + updatedOn: '2024-06-26T14:05:12.123Z', + _links: { + certificationFolder: { + href: '/api/certificationFolders/1234', + }, + }, + }, + ], + comment: '', + history: { + toTakeDate: null, + failedDate: null, + successDate: null, + toRegisterDate: '2024-06-18T12:31:12.000Z', + registeredDate: '2024-06-18T12:31:12.000Z', + abortedDate: null, + toControlDate: null, + refusedDate: null, + toRetakeDate: null, + inTrainingStartedDate: null, + inTrainingEndedDate: null, + }, + stateLastUpdate: '2024-06-18T12:31:12.000Z', + attendee: { + id: 2024, + lastName: 'doe', + firstName: 'john', + email: 'john.doe@gmail.com', + phoneNumber: '+1.112.666.0606', + phoneFixed: null, + degree: 7, + degreeTitle: 'BAC+5 : grade master, DEA, DESS, ingénieur... (NIVEAU 7)', + address: { + id: null, + city: 'string', + line4: null, + number: '9', + country: null, + postBox: null, + zipCode: 'string', + roadName: 'string', + roadType: 'ALL', + idAddress: null, + residence: null, + countryCode: null, + fullAddress: null, + trainingSite: null, + corporateName: 'M JOHN DOE', + roadTypeLabel: 'string', + informationSite: null, + repetitionIndex: null, + subscriptionSite: null, + additionalAddress: null, + repetitionIndexLabel: null, + reducedMobilityAccessCompliant: null, + reducedMobilityAccessModalities: null, + }, + dateOfBirth: null, + nameCityOfBirth: null, + gender: null, + birthName: null, + codeCountryOfBirth: null, + employmentStatus: null, + codeCityOfBirth: null, + firstName2: null, + firstName3: null, + nameCountryOfBirth: null, + readOnly: false, + externalId: null, + lastLogin: null, + cdcCompliant: false, + }, + certifiedData: true, + examinationType: 'A_DISTANCE', + type: 'OF', + gradePass: null, + examinationCenterZipCode: null, + europeanLanguageLevel: null, + accessModality: null, + verbatim: null, + optionName: null, + accessModalityVae: null, + cdcState: 'notExported', + cdcToExport: true, + cdcCompliant: false, + enrollmentDate: null, + amountHt: null, + cdcTechnicalId: null, + inTraining: false, + cdcExcluded: false, + externalId: '1234-1234567890', + certificateId: null, + createdOn: '2024-06-18T12:31:12.000Z', + updatedOn: '2024-06-26T14:05:12.121Z', + _links: { + self: { + href: '/api/certificationFolders/1234-1234567890', + }, + register: { + href: '/api/certificationFolders/1234-1234567890/register', + }, + refuse: { + href: '/api/certificationFolders/1234-1234567890/refuse', + }, + take: { + href: '/api/certificationFolders/1234-1234567890/take', + }, + control: { + href: '/api/certificationFolders/1234-1234567890/control', + }, + retake: { + href: '/api/certificationFolders/1234-1234567890/retake', + }, + fail: { + href: '/api/certificationFolders/1234-1234567890/fail', + }, + success: { + href: '/api/certificationFolders/1234-1234567890/success', + }, + abort: { + href: '/api/certificationFolders/1234-1234567890/abort', + }, + certification: { + href: '/api/certifications/123456', + name: 'titre certification', + certifInfo: '123456', + externalId: 'RS12345', + id: 2, + enabled: true, + }, + registrationFolder: { + href: '/api/registrationFolders/1234567890', + externalId: '1234567890', + type: 'individual', + state: 'accepted', + }, + partner: { + href: '/api/organisms/123456789', + name: 'organism', + siret: '1234567890', + }, + certifier: { + href: '/api/organisms/1234567890', + name: 'organism', + siret: '1234567890', + }, + activities: { + href: '/api/activities/CertificationFolder/1234', + }, + }, + tags: [], + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationFolder.registered'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-selected.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-selected.ts new file mode 100644 index 0000000..5e8cbd9 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-selected.ts @@ -0,0 +1,227 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationFolderSelected = createTrigger({ + auth: wedofAuth, + name: 'certificationFolderSelected', + displayName: 'Événement sur le dossier de certification', + description: + "Se déclenche lorsque l'événement choisi se produit sur un dossier de certification", + props: { + scope: wedofCommon.certificationEvents, + }, + sampleData: { + attendeeLink: 'https://test.wedof.fr/candidat-1234-123456789', + permalink: 'https://test.wedof.fr/dossier-certification-1234', + addToPassportLink: + 'https://test.wedof.fr/candidat-1234-123456789-passeport', + id: 2024, + examinationDate: null, + examinationEndDate: null, + examinationPlace: null, + issueDate: null, + expirationDate: null, + detailedResult: null, + digitalProofLink: null, + state: 'registered', + files: [ + { + permalink: 'https://test.wedof.fr/candidat-1234-123456789-document-4', + id: 40, + typeId: 4, + fileName: 'OUHAHAH', + link: 'https://community.n8n.io/t/declarative-style-nodes-how-to-send-multipart-form-data/26416', + fileType: 'link', + state: 'valid', + comment: null, + generationState: 'notGenerated', + createdOn: '2024-06-20T13:48:12.000Z', + updatedOn: '2024-06-26T14:05:12.123Z', + _links: { + certificationFolder: { + href: '/api/certificationFolders/1234', + }, + }, + }, + ], + comment: '', + history: { + toTakeDate: null, + failedDate: null, + successDate: null, + toRegisterDate: '2024-06-18T12:31:12.000Z', + registeredDate: '2024-06-18T12:31:12.000Z', + abortedDate: null, + toControlDate: null, + refusedDate: null, + toRetakeDate: null, + inTrainingStartedDate: null, + inTrainingEndedDate: null, + }, + stateLastUpdate: '2024-06-18T12:31:12.000Z', + attendee: { + id: 2024, + lastName: 'doe', + firstName: 'john', + email: 'john.doe@gmail.com', + phoneNumber: '+1.112.666.0606', + phoneFixed: null, + degree: 7, + degreeTitle: 'BAC+5 : grade master, DEA, DESS, ingénieur... (NIVEAU 7)', + address: { + id: null, + city: 'string', + line4: null, + number: '9', + country: null, + postBox: null, + zipCode: 'string', + roadName: 'string', + roadType: 'ALL', + idAddress: null, + residence: null, + countryCode: null, + fullAddress: null, + trainingSite: null, + corporateName: 'M JOHN DOE', + roadTypeLabel: 'string', + informationSite: null, + repetitionIndex: null, + subscriptionSite: null, + additionalAddress: null, + repetitionIndexLabel: null, + reducedMobilityAccessCompliant: null, + reducedMobilityAccessModalities: null, + }, + dateOfBirth: null, + nameCityOfBirth: null, + gender: null, + birthName: null, + codeCountryOfBirth: null, + employmentStatus: null, + codeCityOfBirth: null, + firstName2: null, + firstName3: null, + nameCountryOfBirth: null, + readOnly: false, + externalId: null, + lastLogin: null, + cdcCompliant: false, + }, + certifiedData: true, + examinationType: 'A_DISTANCE', + type: 'OF', + gradePass: null, + examinationCenterZipCode: null, + europeanLanguageLevel: null, + accessModality: null, + verbatim: null, + optionName: null, + accessModalityVae: null, + cdcState: 'notExported', + cdcToExport: true, + cdcCompliant: false, + enrollmentDate: null, + amountHt: null, + cdcTechnicalId: null, + inTraining: false, + cdcExcluded: false, + externalId: '1234-1234567890', + certificateId: null, + createdOn: '2024-06-18T12:31:12.000Z', + updatedOn: '2024-06-26T14:05:12.121Z', + _links: { + self: { + href: '/api/certificationFolders/1234-1234567890', + }, + register: { + href: '/api/certificationFolders/1234-1234567890/register', + }, + refuse: { + href: '/api/certificationFolders/1234-1234567890/refuse', + }, + take: { + href: '/api/certificationFolders/1234-1234567890/take', + }, + control: { + href: '/api/certificationFolders/1234-1234567890/control', + }, + retake: { + href: '/api/certificationFolders/1234-1234567890/retake', + }, + fail: { + href: '/api/certificationFolders/1234-1234567890/fail', + }, + success: { + href: '/api/certificationFolders/1234-1234567890/success', + }, + abort: { + href: '/api/certificationFolders/1234-1234567890/abort', + }, + certification: { + href: '/api/certifications/123456', + name: 'titre certification', + certifInfo: '123456', + externalId: 'RS12345', + id: 2, + enabled: true, + }, + registrationFolder: { + href: '/api/registrationFolders/1234567890', + externalId: '1234567890', + type: 'individual', + state: 'accepted', + }, + partner: { + href: '/api/organisms/123456789', + name: 'organism', + siret: '1234567890', + }, + certifier: { + href: '/api/organisms/1234567890', + name: 'organism', + siret: '1234567890', + }, + activities: { + href: '/api/activities/CertificationFolder/1234', + }, + }, + tags: [], + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + context.propsValue.scope, + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-success.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-success.ts new file mode 100644 index 0000000..81411d7 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-success.ts @@ -0,0 +1,225 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationFolderSuccess = createTrigger({ + auth: wedofAuth, + name: 'certificationfolderSuccess', + displayName: 'Dossier de certification réussi', + description: + "Se déclenche lorsqu'un dossier de formation passe à l'état réussi", + props: {}, + sampleData: { + attendeeLink: 'https://test.wedof.fr/candidat-1234-123456789', + permalink: 'https://test.wedof.fr/dossier-certification-1234', + addToPassportLink: + 'https://test.wedof.fr/candidat-1234-123456789-passeport', + id: 2024, + examinationDate: null, + examinationEndDate: null, + examinationPlace: null, + issueDate: null, + expirationDate: null, + detailedResult: null, + digitalProofLink: null, + state: 'registered', + files: [ + { + permalink: 'https://test.wedof.fr/candidat-1234-123456789-document-4', + id: 40, + typeId: 4, + fileName: 'OUHAHAH', + link: 'https://community.n8n.io/t/declarative-style-nodes-how-to-send-multipart-form-data/26416', + fileType: 'link', + state: 'valid', + comment: null, + generationState: 'notGenerated', + createdOn: '2024-06-20T13:48:12.000Z', + updatedOn: '2024-06-26T14:05:12.123Z', + _links: { + certificationFolder: { + href: '/api/certificationFolders/1234', + }, + }, + }, + ], + comment: '', + history: { + toTakeDate: null, + failedDate: null, + successDate: null, + toRegisterDate: '2024-06-18T12:31:12.000Z', + registeredDate: '2024-06-18T12:31:12.000Z', + abortedDate: null, + toControlDate: null, + refusedDate: null, + toRetakeDate: null, + inTrainingStartedDate: null, + inTrainingEndedDate: null, + }, + stateLastUpdate: '2024-06-18T12:31:12.000Z', + attendee: { + id: 2024, + lastName: 'doe', + firstName: 'john', + email: 'john.doe@gmail.com', + phoneNumber: '+1.112.666.0606', + phoneFixed: null, + degree: 7, + degreeTitle: 'BAC+5 : grade master, DEA, DESS, ingénieur... (NIVEAU 7)', + address: { + id: null, + city: 'string', + line4: null, + number: '9', + country: null, + postBox: null, + zipCode: 'string', + roadName: 'string', + roadType: 'ALL', + idAddress: null, + residence: null, + countryCode: null, + fullAddress: null, + trainingSite: null, + corporateName: 'M JOHN DOE', + roadTypeLabel: 'string', + informationSite: null, + repetitionIndex: null, + subscriptionSite: null, + additionalAddress: null, + repetitionIndexLabel: null, + reducedMobilityAccessCompliant: null, + reducedMobilityAccessModalities: null, + }, + dateOfBirth: null, + nameCityOfBirth: null, + gender: null, + birthName: null, + codeCountryOfBirth: null, + employmentStatus: null, + codeCityOfBirth: null, + firstName2: null, + firstName3: null, + nameCountryOfBirth: null, + readOnly: false, + externalId: null, + lastLogin: null, + cdcCompliant: false, + }, + certifiedData: true, + examinationType: 'A_DISTANCE', + type: 'OF', + gradePass: null, + examinationCenterZipCode: null, + europeanLanguageLevel: null, + accessModality: null, + verbatim: null, + optionName: null, + accessModalityVae: null, + cdcState: 'notExported', + cdcToExport: true, + cdcCompliant: false, + enrollmentDate: null, + amountHt: null, + cdcTechnicalId: null, + inTraining: false, + cdcExcluded: false, + externalId: '1234-1234567890', + certificateId: null, + createdOn: '2024-06-18T12:31:12.000Z', + updatedOn: '2024-06-26T14:05:12.121Z', + _links: { + self: { + href: '/api/certificationFolders/1234-1234567890', + }, + register: { + href: '/api/certificationFolders/1234-1234567890/register', + }, + refuse: { + href: '/api/certificationFolders/1234-1234567890/refuse', + }, + take: { + href: '/api/certificationFolders/1234-1234567890/take', + }, + control: { + href: '/api/certificationFolders/1234-1234567890/control', + }, + retake: { + href: '/api/certificationFolders/1234-1234567890/retake', + }, + fail: { + href: '/api/certificationFolders/1234-1234567890/fail', + }, + success: { + href: '/api/certificationFolders/1234-1234567890/success', + }, + abort: { + href: '/api/certificationFolders/1234-1234567890/abort', + }, + certification: { + href: '/api/certifications/123456', + name: 'titre certification', + certifInfo: '123456', + externalId: 'RS12345', + id: 2, + enabled: true, + }, + registrationFolder: { + href: '/api/registrationFolders/1234567890', + externalId: '1234567890', + type: 'individual', + state: 'accepted', + }, + partner: { + href: '/api/organisms/123456789', + name: 'organism', + siret: '1234567890', + }, + certifier: { + href: '/api/organisms/1234567890', + name: 'organism', + siret: '1234567890', + }, + activities: { + href: '/api/activities/CertificationFolder/1234', + }, + }, + tags: [], + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationFolder.success'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-tocontrol.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-tocontrol.ts new file mode 100644 index 0000000..048df3c --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-tocontrol.ts @@ -0,0 +1,225 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationFolderToControl = createTrigger({ + auth: wedofAuth, + name: 'certificationFolderToControl', + displayName: 'Dossier de certification à contrôler', + description: + "Se déclenche lorsqu'un dossier de certification passe à controler", + props: {}, + sampleData: { + attendeeLink: 'https://test.wedof.fr/candidat-1234-123456789', + permalink: 'https://test.wedof.fr/dossier-certification-1234', + addToPassportLink: + 'https://test.wedof.fr/candidat-1234-123456789-passeport', + id: 2024, + examinationDate: null, + examinationEndDate: null, + examinationPlace: null, + issueDate: null, + expirationDate: null, + detailedResult: null, + digitalProofLink: null, + state: 'registered', + files: [ + { + permalink: 'https://test.wedof.fr/candidat-1234-123456789-document-4', + id: 40, + typeId: 4, + fileName: 'OUHAHAH', + link: 'https://community.n8n.io/t/declarative-style-nodes-how-to-send-multipart-form-data/26416', + fileType: 'link', + state: 'valid', + comment: null, + generationState: 'notGenerated', + createdOn: '2024-06-20T13:48:12.000Z', + updatedOn: '2024-06-26T14:05:12.123Z', + _links: { + certificationFolder: { + href: '/api/certificationFolders/1234', + }, + }, + }, + ], + comment: '', + history: { + toTakeDate: null, + failedDate: null, + successDate: null, + toRegisterDate: '2024-06-18T12:31:12.000Z', + registeredDate: '2024-06-18T12:31:12.000Z', + abortedDate: null, + toControlDate: null, + refusedDate: null, + toRetakeDate: null, + inTrainingStartedDate: null, + inTrainingEndedDate: null, + }, + stateLastUpdate: '2024-06-18T12:31:12.000Z', + attendee: { + id: 2024, + lastName: 'doe', + firstName: 'john', + email: 'john.doe@gmail.com', + phoneNumber: '+1.112.666.0606', + phoneFixed: null, + degree: 7, + degreeTitle: 'BAC+5 : grade master, DEA, DESS, ingénieur... (NIVEAU 7)', + address: { + id: null, + city: 'string', + line4: null, + number: '9', + country: null, + postBox: null, + zipCode: 'string', + roadName: 'string', + roadType: 'ALL', + idAddress: null, + residence: null, + countryCode: null, + fullAddress: null, + trainingSite: null, + corporateName: 'M JOHN DOE', + roadTypeLabel: 'string', + informationSite: null, + repetitionIndex: null, + subscriptionSite: null, + additionalAddress: null, + repetitionIndexLabel: null, + reducedMobilityAccessCompliant: null, + reducedMobilityAccessModalities: null, + }, + dateOfBirth: null, + nameCityOfBirth: null, + gender: null, + birthName: null, + codeCountryOfBirth: null, + employmentStatus: null, + codeCityOfBirth: null, + firstName2: null, + firstName3: null, + nameCountryOfBirth: null, + readOnly: false, + externalId: null, + lastLogin: null, + cdcCompliant: false, + }, + certifiedData: true, + examinationType: 'A_DISTANCE', + type: 'OF', + gradePass: null, + examinationCenterZipCode: null, + europeanLanguageLevel: null, + accessModality: null, + verbatim: null, + optionName: null, + accessModalityVae: null, + cdcState: 'notExported', + cdcToExport: true, + cdcCompliant: false, + enrollmentDate: null, + amountHt: null, + cdcTechnicalId: null, + inTraining: false, + cdcExcluded: false, + externalId: '1234-1234567890', + certificateId: null, + createdOn: '2024-06-18T12:31:12.000Z', + updatedOn: '2024-06-26T14:05:12.121Z', + _links: { + self: { + href: '/api/certificationFolders/1234-1234567890', + }, + register: { + href: '/api/certificationFolders/1234-1234567890/register', + }, + refuse: { + href: '/api/certificationFolders/1234-1234567890/refuse', + }, + take: { + href: '/api/certificationFolders/1234-1234567890/take', + }, + control: { + href: '/api/certificationFolders/1234-1234567890/control', + }, + retake: { + href: '/api/certificationFolders/1234-1234567890/retake', + }, + fail: { + href: '/api/certificationFolders/1234-1234567890/fail', + }, + success: { + href: '/api/certificationFolders/1234-1234567890/success', + }, + abort: { + href: '/api/certificationFolders/1234-1234567890/abort', + }, + certification: { + href: '/api/certifications/123456', + name: 'titre certification', + certifInfo: '123456', + externalId: 'RS12345', + id: 2, + enabled: true, + }, + registrationFolder: { + href: '/api/registrationFolders/1234567890', + externalId: '1234567890', + type: 'individual', + state: 'accepted', + }, + partner: { + href: '/api/organisms/123456789', + name: 'organism', + siret: '1234567890', + }, + certifier: { + href: '/api/organisms/1234567890', + name: 'organism', + siret: '1234567890', + }, + activities: { + href: '/api/activities/CertificationFolder/1234', + }, + }, + tags: [], + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationFolder.toControl'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-toretake.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-toretake.ts new file mode 100644 index 0000000..6f0aa84 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-toretake.ts @@ -0,0 +1,225 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationFolderToretake = createTrigger({ + auth: wedofAuth, + name: 'certificationFolderToretake', + displayName: 'Dossier de certification à repasser', + description: + "Se déclanche lorsqu'un dossier de certification est prét à repasser", + props: {}, + sampleData: { + attendeeLink: 'https://test.wedof.fr/candidat-1234-123456789', + permalink: 'https://test.wedof.fr/dossier-certification-1234', + addToPassportLink: + 'https://test.wedof.fr/candidat-1234-123456789-passeport', + id: 2024, + examinationDate: null, + examinationEndDate: null, + examinationPlace: null, + issueDate: null, + expirationDate: null, + detailedResult: null, + digitalProofLink: null, + state: 'registered', + files: [ + { + permalink: 'https://test.wedof.fr/candidat-1234-123456789-document-4', + id: 40, + typeId: 4, + fileName: 'OUHAHAH', + link: 'https://community.n8n.io/t/declarative-style-nodes-how-to-send-multipart-form-data/26416', + fileType: 'link', + state: 'valid', + comment: null, + generationState: 'notGenerated', + createdOn: '2024-06-20T13:48:12.000Z', + updatedOn: '2024-06-26T14:05:12.123Z', + _links: { + certificationFolder: { + href: '/api/certificationFolders/1234', + }, + }, + }, + ], + comment: '', + history: { + toTakeDate: null, + failedDate: null, + successDate: null, + toRegisterDate: '2024-06-18T12:31:12.000Z', + registeredDate: '2024-06-18T12:31:12.000Z', + abortedDate: null, + toControlDate: null, + refusedDate: null, + toRetakeDate: null, + inTrainingStartedDate: null, + inTrainingEndedDate: null, + }, + stateLastUpdate: '2024-06-18T12:31:12.000Z', + attendee: { + id: 2024, + lastName: 'doe', + firstName: 'john', + email: 'john.doe@gmail.com', + phoneNumber: '+1.112.666.0606', + phoneFixed: null, + degree: 7, + degreeTitle: 'BAC+5 : grade master, DEA, DESS, ingénieur... (NIVEAU 7)', + address: { + id: null, + city: 'string', + line4: null, + number: '9', + country: null, + postBox: null, + zipCode: 'string', + roadName: 'string', + roadType: 'ALL', + idAddress: null, + residence: null, + countryCode: null, + fullAddress: null, + trainingSite: null, + corporateName: 'M JOHN DOE', + roadTypeLabel: 'string', + informationSite: null, + repetitionIndex: null, + subscriptionSite: null, + additionalAddress: null, + repetitionIndexLabel: null, + reducedMobilityAccessCompliant: null, + reducedMobilityAccessModalities: null, + }, + dateOfBirth: null, + nameCityOfBirth: null, + gender: null, + birthName: null, + codeCountryOfBirth: null, + employmentStatus: null, + codeCityOfBirth: null, + firstName2: null, + firstName3: null, + nameCountryOfBirth: null, + readOnly: false, + externalId: null, + lastLogin: null, + cdcCompliant: false, + }, + certifiedData: true, + examinationType: 'A_DISTANCE', + type: 'OF', + gradePass: null, + examinationCenterZipCode: null, + europeanLanguageLevel: null, + accessModality: null, + verbatim: null, + optionName: null, + accessModalityVae: null, + cdcState: 'notExported', + cdcToExport: true, + cdcCompliant: false, + enrollmentDate: null, + amountHt: null, + cdcTechnicalId: null, + inTraining: false, + cdcExcluded: false, + externalId: '1234-1234567890', + certificateId: null, + createdOn: '2024-06-18T12:31:12.000Z', + updatedOn: '2024-06-26T14:05:12.121Z', + _links: { + self: { + href: '/api/certificationFolders/1234-1234567890', + }, + register: { + href: '/api/certificationFolders/1234-1234567890/register', + }, + refuse: { + href: '/api/certificationFolders/1234-1234567890/refuse', + }, + take: { + href: '/api/certificationFolders/1234-1234567890/take', + }, + control: { + href: '/api/certificationFolders/1234-1234567890/control', + }, + retake: { + href: '/api/certificationFolders/1234-1234567890/retake', + }, + fail: { + href: '/api/certificationFolders/1234-1234567890/fail', + }, + success: { + href: '/api/certificationFolders/1234-1234567890/success', + }, + abort: { + href: '/api/certificationFolders/1234-1234567890/abort', + }, + certification: { + href: '/api/certifications/123456', + name: 'titre certification', + certifInfo: '123456', + externalId: 'RS12345', + id: 2, + enabled: true, + }, + registrationFolder: { + href: '/api/registrationFolders/1234567890', + externalId: '1234567890', + type: 'individual', + state: 'accepted', + }, + partner: { + href: '/api/organisms/123456789', + name: 'organism', + siret: '1234567890', + }, + certifier: { + href: '/api/organisms/1234567890', + name: 'organism', + siret: '1234567890', + }, + activities: { + href: '/api/activities/CertificationFolder/1234', + }, + }, + tags: [], + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationFolder.toRetake'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-totake.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-totake.ts new file mode 100644 index 0000000..6fd23df --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-totake.ts @@ -0,0 +1,225 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationFolderTotake = createTrigger({ + auth: wedofAuth, + name: 'certificationFolderTotake', + displayName: 'Dossier de certification prêt à passer', + description: + "Se déclenche lorsqu'un dossier de certification est prét à passer", + props: {}, + sampleData: { + attendeeLink: 'https://test.wedof.fr/candidat-1234-123456789', + permalink: 'https://test.wedof.fr/dossier-certification-1234', + addToPassportLink: + 'https://test.wedof.fr/candidat-1234-123456789-passeport', + id: 2024, + examinationDate: null, + examinationEndDate: null, + examinationPlace: null, + issueDate: null, + expirationDate: null, + detailedResult: null, + digitalProofLink: null, + state: 'registered', + files: [ + { + permalink: 'https://test.wedof.fr/candidat-1234-123456789-document-4', + id: 40, + typeId: 4, + fileName: 'OUHAHAH', + link: 'https://community.n8n.io/t/declarative-style-nodes-how-to-send-multipart-form-data/26416', + fileType: 'link', + state: 'valid', + comment: null, + generationState: 'notGenerated', + createdOn: '2024-06-20T13:48:12.000Z', + updatedOn: '2024-06-26T14:05:12.123Z', + _links: { + certificationFolder: { + href: '/api/certificationFolders/1234', + }, + }, + }, + ], + comment: '', + history: { + toTakeDate: null, + failedDate: null, + successDate: null, + toRegisterDate: '2024-06-18T12:31:12.000Z', + registeredDate: '2024-06-18T12:31:12.000Z', + abortedDate: null, + toControlDate: null, + refusedDate: null, + toRetakeDate: null, + inTrainingStartedDate: null, + inTrainingEndedDate: null, + }, + stateLastUpdate: '2024-06-18T12:31:12.000Z', + attendee: { + id: 2024, + lastName: 'doe', + firstName: 'john', + email: 'john.doe@gmail.com', + phoneNumber: '+1.112.666.0606', + phoneFixed: null, + degree: 7, + degreeTitle: 'BAC+5 : grade master, DEA, DESS, ingénieur... (NIVEAU 7)', + address: { + id: null, + city: 'string', + line4: null, + number: '9', + country: null, + postBox: null, + zipCode: 'string', + roadName: 'string', + roadType: 'ALL', + idAddress: null, + residence: null, + countryCode: null, + fullAddress: null, + trainingSite: null, + corporateName: 'M JOHN DOE', + roadTypeLabel: 'string', + informationSite: null, + repetitionIndex: null, + subscriptionSite: null, + additionalAddress: null, + repetitionIndexLabel: null, + reducedMobilityAccessCompliant: null, + reducedMobilityAccessModalities: null, + }, + dateOfBirth: null, + nameCityOfBirth: null, + gender: null, + birthName: null, + codeCountryOfBirth: null, + employmentStatus: null, + codeCityOfBirth: null, + firstName2: null, + firstName3: null, + nameCountryOfBirth: null, + readOnly: false, + externalId: null, + lastLogin: null, + cdcCompliant: false, + }, + certifiedData: true, + examinationType: 'A_DISTANCE', + type: 'OF', + gradePass: null, + examinationCenterZipCode: null, + europeanLanguageLevel: null, + accessModality: null, + verbatim: null, + optionName: null, + accessModalityVae: null, + cdcState: 'notExported', + cdcToExport: true, + cdcCompliant: false, + enrollmentDate: null, + amountHt: null, + cdcTechnicalId: null, + inTraining: false, + cdcExcluded: false, + externalId: '1234-1234567890', + certificateId: null, + createdOn: '2024-06-18T12:31:12.000Z', + updatedOn: '2024-06-26T14:05:12.121Z', + _links: { + self: { + href: '/api/certificationFolders/1234-1234567890', + }, + register: { + href: '/api/certificationFolders/1234-1234567890/register', + }, + refuse: { + href: '/api/certificationFolders/1234-1234567890/refuse', + }, + take: { + href: '/api/certificationFolders/1234-1234567890/take', + }, + control: { + href: '/api/certificationFolders/1234-1234567890/control', + }, + retake: { + href: '/api/certificationFolders/1234-1234567890/retake', + }, + fail: { + href: '/api/certificationFolders/1234-1234567890/fail', + }, + success: { + href: '/api/certificationFolders/1234-1234567890/success', + }, + abort: { + href: '/api/certificationFolders/1234-1234567890/abort', + }, + certification: { + href: '/api/certifications/123456', + name: 'titre certification', + certifInfo: '123456', + externalId: 'RS12345', + id: 2, + enabled: true, + }, + registrationFolder: { + href: '/api/registrationFolders/1234567890', + externalId: '1234567890', + type: 'individual', + state: 'accepted', + }, + partner: { + href: '/api/organisms/123456789', + name: 'organism', + siret: '1234567890', + }, + certifier: { + href: '/api/organisms/1234567890', + name: 'organism', + siret: '1234567890', + }, + activities: { + href: '/api/activities/CertificationFolder/1234', + }, + }, + tags: [], + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationFolder.toTake'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-updated.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-updated.ts new file mode 100644 index 0000000..aca1ce8 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-folders/certification-folder-updated.ts @@ -0,0 +1,224 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationFolderUpdated = createTrigger({ + auth: wedofAuth, + name: 'certificationFolderUpdated', + displayName: 'Dossier de certification mis à jour', + description: "Se déclenche lorsqu'un dossier de certification est mis à jour", + props: {}, + sampleData: { + attendeeLink: 'https://test.wedof.fr/candidat-1234-123456789', + permalink: 'https://test.wedof.fr/dossier-certification-1234', + addToPassportLink: + 'https://test.wedof.fr/candidat-1234-123456789-passeport', + id: 2024, + examinationDate: null, + examinationEndDate: null, + examinationPlace: null, + issueDate: null, + expirationDate: null, + detailedResult: null, + digitalProofLink: null, + state: 'registered', + files: [ + { + permalink: 'https://test.wedof.fr/candidat-1234-123456789-document-4', + id: 40, + typeId: 4, + fileName: 'OUHAHAH', + link: 'https://community.n8n.io/t/declarative-style-nodes-how-to-send-multipart-form-data/26416', + fileType: 'link', + state: 'valid', + comment: null, + generationState: 'notGenerated', + createdOn: '2024-06-20T13:48:12.000Z', + updatedOn: '2024-06-26T14:05:12.123Z', + _links: { + certificationFolder: { + href: '/api/certificationFolders/1234', + }, + }, + }, + ], + comment: '', + history: { + toTakeDate: null, + failedDate: null, + successDate: null, + toRegisterDate: '2024-06-18T12:31:12.000Z', + registeredDate: '2024-06-18T12:31:12.000Z', + abortedDate: null, + toControlDate: null, + refusedDate: null, + toRetakeDate: null, + inTrainingStartedDate: null, + inTrainingEndedDate: null, + }, + stateLastUpdate: '2024-06-18T12:31:12.000Z', + attendee: { + id: 2024, + lastName: 'doe', + firstName: 'john', + email: 'john.doe@gmail.com', + phoneNumber: '+1.112.666.0606', + phoneFixed: null, + degree: 7, + degreeTitle: 'BAC+5 : grade master, DEA, DESS, ingénieur... (NIVEAU 7)', + address: { + id: null, + city: 'string', + line4: null, + number: '9', + country: null, + postBox: null, + zipCode: 'string', + roadName: 'string', + roadType: 'ALL', + idAddress: null, + residence: null, + countryCode: null, + fullAddress: null, + trainingSite: null, + corporateName: 'M JOHN DOE', + roadTypeLabel: 'string', + informationSite: null, + repetitionIndex: null, + subscriptionSite: null, + additionalAddress: null, + repetitionIndexLabel: null, + reducedMobilityAccessCompliant: null, + reducedMobilityAccessModalities: null, + }, + dateOfBirth: null, + nameCityOfBirth: null, + gender: null, + birthName: null, + codeCountryOfBirth: null, + employmentStatus: null, + codeCityOfBirth: null, + firstName2: null, + firstName3: null, + nameCountryOfBirth: null, + readOnly: false, + externalId: null, + lastLogin: null, + cdcCompliant: false, + }, + certifiedData: true, + examinationType: 'A_DISTANCE', + type: 'OF', + gradePass: null, + examinationCenterZipCode: null, + europeanLanguageLevel: null, + accessModality: null, + verbatim: null, + optionName: null, + accessModalityVae: null, + cdcState: 'notExported', + cdcToExport: true, + cdcCompliant: false, + enrollmentDate: null, + amountHt: null, + cdcTechnicalId: null, + inTraining: false, + cdcExcluded: false, + externalId: '1234-1234567890', + certificateId: null, + createdOn: '2024-06-18T12:31:12.000Z', + updatedOn: '2024-06-26T14:05:12.121Z', + _links: { + self: { + href: '/api/certificationFolders/1234-1234567890', + }, + register: { + href: '/api/certificationFolders/1234-1234567890/register', + }, + refuse: { + href: '/api/certificationFolders/1234-1234567890/refuse', + }, + take: { + href: '/api/certificationFolders/1234-1234567890/take', + }, + control: { + href: '/api/certificationFolders/1234-1234567890/control', + }, + retake: { + href: '/api/certificationFolders/1234-1234567890/retake', + }, + fail: { + href: '/api/certificationFolders/1234-1234567890/fail', + }, + success: { + href: '/api/certificationFolders/1234-1234567890/success', + }, + abort: { + href: '/api/certificationFolders/1234-1234567890/abort', + }, + certification: { + href: '/api/certifications/123456', + name: 'titre certification', + certifInfo: '123456', + externalId: 'RS12345', + id: 2, + enabled: true, + }, + registrationFolder: { + href: '/api/registrationFolders/1234567890', + externalId: '1234567890', + type: 'individual', + state: 'accepted', + }, + partner: { + href: '/api/organisms/123456789', + name: 'organism', + siret: '1234567890', + }, + certifier: { + href: '/api/organisms/1234567890', + name: 'organism', + siret: '1234567890', + }, + activities: { + href: '/api/activities/CertificationFolder/1234', + }, + }, + tags: [], + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationFolder.updated'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + } +}); \ No newline at end of file diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-folders/new-certification-folder-created.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-folders/new-certification-folder-created.ts new file mode 100644 index 0000000..b32e67d --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-folders/new-certification-folder-created.ts @@ -0,0 +1,225 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const newCertificationFolderCreated = createTrigger({ + auth: wedofAuth, + name: 'newCertificationFolderCreated', + displayName: 'Nouveau dossier de certification', + description: + "Se déclenche lorsqu'un nouveau dossier de certification est créé", + type: TriggerStrategy.WEBHOOK, + props: {}, + sampleData: { + attendeeLink: 'https://test.wedof.fr/candidat-1234-123456789', + permalink: 'https://test.wedof.fr/dossier-certification-1234', + addToPassportLink: + 'https://test.wedof.fr/candidat-1234-123456789-passeport', + id: 2024, + examinationDate: null, + examinationEndDate: null, + examinationPlace: null, + issueDate: null, + expirationDate: null, + detailedResult: null, + digitalProofLink: null, + state: 'registered', + files: [ + { + permalink: 'https://test.wedof.fr/candidat-1234-123456789-document-4', + id: 40, + typeId: 4, + fileName: 'OUHAHAH', + link: 'https://community.n8n.io/t/declarative-style-nodes-how-to-send-multipart-form-data/26416', + fileType: 'link', + state: 'valid', + comment: null, + generationState: 'notGenerated', + createdOn: '2024-06-20T13:48:12.000Z', + updatedOn: '2024-06-26T14:05:12.123Z', + _links: { + certificationFolder: { + href: '/api/certificationFolders/1234', + }, + }, + }, + ], + comment: '', + history: { + toTakeDate: null, + failedDate: null, + successDate: null, + toRegisterDate: '2024-06-18T12:31:12.000Z', + registeredDate: '2024-06-18T12:31:12.000Z', + abortedDate: null, + toControlDate: null, + refusedDate: null, + toRetakeDate: null, + inTrainingStartedDate: null, + inTrainingEndedDate: null, + }, + stateLastUpdate: '2024-06-18T12:31:12.000Z', + attendee: { + id: 2024, + lastName: 'doe', + firstName: 'john', + email: 'john.doe@gmail.com', + phoneNumber: '+1.112.666.0606', + phoneFixed: null, + degree: 7, + degreeTitle: 'BAC+5 : grade master, DEA, DESS, ingénieur... (NIVEAU 7)', + address: { + id: null, + city: 'string', + line4: null, + number: '9', + country: null, + postBox: null, + zipCode: 'string', + roadName: 'string', + roadType: 'ALL', + idAddress: null, + residence: null, + countryCode: null, + fullAddress: null, + trainingSite: null, + corporateName: 'M JOHN DOE', + roadTypeLabel: 'string', + informationSite: null, + repetitionIndex: null, + subscriptionSite: null, + additionalAddress: null, + repetitionIndexLabel: null, + reducedMobilityAccessCompliant: null, + reducedMobilityAccessModalities: null, + }, + dateOfBirth: null, + nameCityOfBirth: null, + gender: null, + birthName: null, + codeCountryOfBirth: null, + employmentStatus: null, + codeCityOfBirth: null, + firstName2: null, + firstName3: null, + nameCountryOfBirth: null, + readOnly: false, + externalId: null, + lastLogin: null, + cdcCompliant: false, + }, + certifiedData: true, + examinationType: 'A_DISTANCE', + type: 'OF', + gradePass: null, + examinationCenterZipCode: null, + europeanLanguageLevel: null, + accessModality: null, + verbatim: null, + optionName: null, + accessModalityVae: null, + cdcState: 'notExported', + cdcToExport: true, + cdcCompliant: false, + enrollmentDate: null, + amountHt: null, + cdcTechnicalId: null, + inTraining: false, + cdcExcluded: false, + externalId: '1234-1234567890', + certificateId: null, + createdOn: '2024-06-18T12:31:12.000Z', + updatedOn: '2024-06-26T14:05:12.121Z', + _links: { + self: { + href: '/api/certificationFolders/1234-1234567890', + }, + register: { + href: '/api/certificationFolders/1234-1234567890/register', + }, + refuse: { + href: '/api/certificationFolders/1234-1234567890/refuse', + }, + take: { + href: '/api/certificationFolders/1234-1234567890/take', + }, + control: { + href: '/api/certificationFolders/1234-1234567890/control', + }, + retake: { + href: '/api/certificationFolders/1234-1234567890/retake', + }, + fail: { + href: '/api/certificationFolders/1234-1234567890/fail', + }, + success: { + href: '/api/certificationFolders/1234-1234567890/success', + }, + abort: { + href: '/api/certificationFolders/1234-1234567890/abort', + }, + certification: { + href: '/api/certifications/123456', + name: 'titre certification', + certifInfo: '123456', + externalId: 'RS12345', + id: 2, + enabled: true, + }, + registrationFolder: { + href: '/api/registrationFolders/1234567890', + externalId: '1234567890', + type: 'individual', + state: 'accepted', + }, + partner: { + href: '/api/organisms/123456789', + name: 'organism', + siret: '1234567890', + }, + certifier: { + href: '/api/organisms/1234567890', + name: 'organism', + siret: '1234567890', + }, + activities: { + href: '/api/activities/CertificationFolder/1234', + }, + }, + tags: [], + }, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationFolder.created'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-aborted.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-aborted.ts new file mode 100644 index 0000000..900d3e5 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-aborted.ts @@ -0,0 +1,68 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationPartnerAborted = createTrigger({ + auth: wedofAuth, + name: 'certificationPartnerAborted', + displayName: 'Demande de partenariat abandonnée', + description: "Se déclenche Lorsqu'une demande de partenariat estabandonnée", + props: {}, + sampleData: { + id: 0, + url: 'string', + secret: 'string', + type: 'string', + events: {}, + enabled: true, + ignoreSsl: true, + name: 'string', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + _links: { + self: { + href: 'string', + }, + organism: { + href: 'string', + name: null, + siret: null, + }, + }, + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationPartner.aborted'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-active.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-active.ts new file mode 100644 index 0000000..8f6a243 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-active.ts @@ -0,0 +1,68 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationPartnerActive = createTrigger({ + auth: wedofAuth, + name: 'certificationPartnerActive', + displayName: 'Partenariat actif', + description: "Se déclenche Lorsqu'une demande de partenariat est actif", + props: {}, + sampleData: { + id: 0, + url: 'string', + secret: 'string', + type: 'string', + events: {}, + enabled: true, + ignoreSsl: true, + name: 'string', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + _links: { + self: { + href: 'string', + }, + organism: { + href: 'string', + name: null, + siret: null, + }, + }, + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationPartner.active'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-processing.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-processing.ts new file mode 100644 index 0000000..b9368aa --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-processing.ts @@ -0,0 +1,68 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationPartnerProcessing = createTrigger({ + auth: wedofAuth, + name: 'certificationPartnerProcessing', + displayName: 'Demande de partenariat en traitement', + description: + "Se déclenche Lorsqu'une demande de partenariat est en traitement", + props: {}, + sampleData: { + id: 0, + url: 'string', + secret: 'string', + type: 'string', + events: {}, + enabled: true, + ignoreSsl: true, + name: 'string', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + _links: { + self: { + href: 'string', + }, + organism: { + href: 'string', + name: null, + siret: null, + }, + }, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationPartner.processing'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-refused.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-refused.ts new file mode 100644 index 0000000..a5425a9 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-refused.ts @@ -0,0 +1,68 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationPartnerRefused = createTrigger({ + auth: wedofAuth, + name: 'certificationPartnerRefused', + displayName: 'Demande de partenariat refusée', + description: "Se déclenche Lorsqu'une demande de partenariat est refusée", + props: {}, + sampleData: { + id: 0, + url: 'string', + secret: 'string', + type: 'string', + events: {}, + enabled: true, + ignoreSsl: true, + name: 'string', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + _links: { + self: { + href: 'string', + }, + organism: { + href: 'string', + name: null, + siret: null, + }, + }, + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationPartner.refused'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-revoked.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-revoked.ts new file mode 100644 index 0000000..24dc2a7 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-revoked.ts @@ -0,0 +1,68 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationPartnerRevoked = createTrigger({ + auth: wedofAuth, + name: 'certificationPartnerRevoked', + displayName: 'Partenariat révoqué', + description: "Se déclenche Lorsqu'un partenariat est révoqué", + props: {}, + sampleData: { + id: 0, + url: 'string', + secret: 'string', + type: 'string', + events: {}, + enabled: true, + ignoreSsl: true, + name: 'string', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + _links: { + self: { + href: 'string', + }, + organism: { + href: 'string', + name: null, + siret: null, + }, + }, + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationPartner.revoked'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-suspended.ts b/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-suspended.ts new file mode 100644 index 0000000..854218d --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/certification-partner/certificationPartner-suspended.ts @@ -0,0 +1,68 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const certificationPartnerSuspended = createTrigger({ + auth: wedofAuth, + name: 'certificationPartnerSuspended', + displayName: 'Partenariat suspendu', + description: "Se déclenche Lorsqu'un partenariat est suspendu", + props: {}, + sampleData: { + id: 0, + url: 'string', + secret: 'string', + type: 'string', + events: {}, + enabled: true, + ignoreSsl: true, + name: 'string', + createdOn: '2019-08-24T14:15:22Z', + updatedOn: '2019-08-24T14:15:22Z', + _links: { + self: { + href: 'string', + }, + organism: { + href: 'string', + name: null, + siret: null, + }, + }, + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['certificationPartner.suspended'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/triggers/registration-folders/new-registration-folder-created.ts b/packages/pieces/community/wedof/src/lib/triggers/registration-folders/new-registration-folder-created.ts new file mode 100644 index 0000000..8d1adc4 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/registration-folders/new-registration-folder-created.ts @@ -0,0 +1,232 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const newRegistrationFolderNotProcessed = createTrigger({ + auth: wedofAuth, + name: 'newRegistrationFolderNotProcessed', + displayName: 'Nouveau dossier de formation', + description: + "Se déclenche lorsqu'un nouveau dossier de formation est créé (non traité)", + type: TriggerStrategy.WEBHOOK, + props: {}, + sampleData: { + withPoleEmploi: false, + attendeeLink: 'https://test.wedof.fr/apprenant-12345678901234', + dataProviderId: null, + permalink: 'https://test.wedof.fr/dossier-formation-12345678901234', + isAllowActions: true, + type: 'individual', + lastUpdate: '2024-03-15T14:26:51.842Z', + attendee: { + id: 2024, + lastName: 'john', + firstName: 'Doe', + email: 'john.doe@gmail.com', + phoneNumber: '(323) 853-2456', + phoneFixed: '0666666666', + degree: 7, + degreeTitle: 'BAC+5 : grade master, DEA, DESS, ingénieur... (NIVEAU 7)', + address: { + id: null, + city: 'string', + line4: null, + number: '01', + country: null, + postBox: null, + zipCode: 'string', + roadName: 'string', + roadType: 'string', + idAddress: null, + residence: null, + countryCode: null, + fullAddress: null, + trainingSite: null, + corporateName: 'M JOHN DOE', + roadTypeLabel: 'string', + informationSite: null, + repetitionIndex: null, + subscriptionSite: null, + additionalAddress: null, + repetitionIndexLabel: null, + reducedMobilityAccessCompliant: null, + reducedMobilityAccessModalities: null, + }, + dateOfBirth: null, + nameCityOfBirth: null, + gender: null, + birthName: null, + codeCountryOfBirth: null, + poleEmploiId: null, + poleEmploiDpt: null, + codeCityOfBirth: null, + firstName2: null, + firstName3: null, + nameCountryOfBirth: null, + poleEmploiRegionCode: null, + readOnly: false, + cdcCompliant: false, + }, + state: 'notProcessed', + attendeeState: 'serviceDoneNotDeclared', + billingState: 'notBillable', + externalId: '12345678901234', + billId: null, + billNumber: null, + amountHtNet: null, + amountToInvoice: null, + amountCGU: null, + amountTtc: null, + amountHt: null, + vatHtAmount5: null, + vatAmount5: null, + vatHtAmount20: null, + vatAmount20: null, + history: { + serviceDoneDeclaredAttendeeDate: null, + billedDate: null, + paidDate: null, + acceptedDate: null, + rejectedWithoutTitulaireSuiteDate: null, + validatedDate: null, + inTrainingDate: null, + terminatedDate: null, + notProcessedDate: '2024-06-16T14:26:51.000Z', + refusedByAttendeeDate: null, + refusedByOrganismDate: null, + refusedByFinancerDate: null, + canceledByAttendeeDate: null, + canceledByOrganismDate: null, + serviceDoneDeclaredDate: null, + serviceDoneValidatedDate: null, + canceledByAttendeeNotRealizedDate: null, + canceledByFinancerDate: null, + inControlDate: null, + releasedDate: null, + completionRateLastUpdate: null, + }, + files: [], + notes: '', + description: '', + completionRate: null, + controlState: 'notInControl', + createdOn: '2024-03-15T14:26:51.000Z', + updatedOn: '2024-06-26T09:42:40.642Z', + _links: { + self: { + href: '/api/registrationFolders/12345678901234', + }, + validate: { + href: '/api/registrationFolders/12345678901234/validate', + }, + inTraining: { + href: '/api/registrationFolders/12345678901234/inTraining', + }, + terminate: { + href: '/api/registrationFolders/12345678901234/terminate', + }, + serviceDone: { + href: '/api/registrationFolders/12345678901234/serviceDone', + }, + refuse: { + href: '/api/registrationFolders/12345678901234/refuse', + }, + cancel: { + href: '/api/registrationFolders/12345678901234/cancel', + }, + billing: { + href: '/api/registrationFolders/12345678901234/billing', + }, + session: { + href: '/api/sessions/titre_action', + }, + organism: { + href: '/api/organisms/12345678901234', + name: 'Organism', + siret: '12345678901234', + }, + payments: { + href: '/api/payments?registrationFolderId=12345678901234', + }, + trainingAction: { + href: '/api/trainingActions/titre_action', + }, + certification: { + href: '/api/certifications/112713', + name: 'Gérer des projets avec la méthode Agile', + certifInfo: '112713', + externalId: 'RS5695', + id: 2, + enabled: true, + }, + activities: { + href: '/api/activities/RegistrationFolder/12345678901234', + }, + }, + tags: [], + trainingActionInfo: { + vat: null, + title: 'Titre formation', + address: { + id: null, + }, + content: 'string', + sessionId: 'Titre session', + totalExcl: 1075, + totalIncl: 1290, + quitReason: null, + vatExclTax5: null, + vatInclTax5: null, + externalLink: '', + trainingGoal: 'string', + vatExclTax20: 1075, + vatInclTax20: 1290, + trainingPaces: ['3', '1', '5'], + additionalFees: 0, + expectedResult: 'string', + sessionEndDate: '2024-03-29T00:00:00.000Z', + weeklyDuration: 14, + sessionStartDate: '2024-03-28T00:00:00.000Z', + indicativeDuration: 14, + teachingModalities: '2', + trainingCompletionRate: null, + externalId: '53222292400039_scrum-online-action-v2', + trainingActionId: '53222292400039_scrum-online-v2/titre_action', + }, + externalLink: '', + }, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['registrationFolder.notProcessed'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-accepted.ts b/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-accepted.ts new file mode 100644 index 0000000..5972bac --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-accepted.ts @@ -0,0 +1,232 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const registrationFolderAccepted = createTrigger({ + auth: wedofAuth, + name: 'registrationFolderAccepted', + displayName: 'Dossier de formation accepté', + description: + "Se déclenche lorsqu'un dossier de formation passe à l'état accepté", + props: {}, + sampleData: { + withPoleEmploi: false, + attendeeLink: 'https://test.wedof.fr/apprenant-12345678901234', + dataProviderId: null, + permalink: 'https://test.wedof.fr/dossier-formation-12345678901234', + isAllowActions: true, + type: 'individual', + lastUpdate: '2024-03-15T14:26:51.842Z', + attendee: { + id: 2024, + lastName: 'john', + firstName: 'Doe', + email: 'john.doe@gmail.com', + phoneNumber: '(323) 853-2456', + phoneFixed: '0666666666', + degree: 7, + degreeTitle: 'BAC+5 : grade master, DEA, DESS, ingénieur... (NIVEAU 7)', + address: { + id: null, + city: 'string', + line4: null, + number: '01', + country: null, + postBox: null, + zipCode: 'string', + roadName: 'string', + roadType: 'string', + idAddress: null, + residence: null, + countryCode: null, + fullAddress: null, + trainingSite: null, + corporateName: 'M JOHN DOE', + roadTypeLabel: 'string', + informationSite: null, + repetitionIndex: null, + subscriptionSite: null, + additionalAddress: null, + repetitionIndexLabel: null, + reducedMobilityAccessCompliant: null, + reducedMobilityAccessModalities: null, + }, + dateOfBirth: null, + nameCityOfBirth: null, + gender: null, + birthName: null, + codeCountryOfBirth: null, + poleEmploiId: null, + poleEmploiDpt: null, + codeCityOfBirth: null, + firstName2: null, + firstName3: null, + nameCountryOfBirth: null, + poleEmploiRegionCode: null, + readOnly: false, + cdcCompliant: false, + }, + state: 'accepted', + attendeeState: 'serviceDoneNotDeclared', + billingState: 'notBillable', + externalId: '12345678901234', + billId: null, + billNumber: null, + amountHtNet: null, + amountToInvoice: null, + amountCGU: null, + amountTtc: null, + amountHt: null, + vatHtAmount5: null, + vatAmount5: null, + vatHtAmount20: null, + vatAmount20: null, + history: { + serviceDoneDeclaredAttendeeDate: null, + billedDate: null, + paidDate: null, + acceptedDate: '2024-06-16T14:26:51.000Z', + rejectedWithoutTitulaireSuiteDate: null, + validatedDate: null, + inTrainingDate: null, + terminatedDate: null, + notProcessedDate: '2024-06-16T14:26:51.000Z', + refusedByAttendeeDate: null, + refusedByOrganismDate: null, + refusedByFinancerDate: null, + canceledByAttendeeDate: null, + canceledByOrganismDate: null, + serviceDoneDeclaredDate: null, + serviceDoneValidatedDate: null, + canceledByAttendeeNotRealizedDate: null, + canceledByFinancerDate: null, + inControlDate: null, + releasedDate: null, + completionRateLastUpdate: null, + }, + files: [], + notes: '', + description: '', + completionRate: null, + controlState: 'notInControl', + createdOn: '2024-03-15T14:26:51.000Z', + updatedOn: '2024-06-26T09:42:40.642Z', + _links: { + self: { + href: '/api/registrationFolders/12345678901234', + }, + validate: { + href: '/api/registrationFolders/12345678901234/validate', + }, + inTraining: { + href: '/api/registrationFolders/12345678901234/inTraining', + }, + terminate: { + href: '/api/registrationFolders/12345678901234/terminate', + }, + serviceDone: { + href: '/api/registrationFolders/12345678901234/serviceDone', + }, + refuse: { + href: '/api/registrationFolders/12345678901234/refuse', + }, + cancel: { + href: '/api/registrationFolders/12345678901234/cancel', + }, + billing: { + href: '/api/registrationFolders/12345678901234/billing', + }, + session: { + href: '/api/sessions/titre_action', + }, + organism: { + href: '/api/organisms/12345678901234', + name: 'Organism', + siret: '12345678901234', + }, + payments: { + href: '/api/payments?registrationFolderId=12345678901234', + }, + trainingAction: { + href: '/api/trainingActions/titre_action', + }, + certification: { + href: '/api/certifications/112713', + name: 'Gérer des projets avec la méthode Agile', + certifInfo: '112713', + externalId: 'RS5695', + id: 2, + enabled: true, + }, + activities: { + href: '/api/activities/RegistrationFolder/12345678901234', + }, + }, + tags: [], + trainingActionInfo: { + vat: null, + title: 'Titre formation', + address: { + id: null, + }, + content: 'string', + sessionId: 'Titre session', + totalExcl: 1075, + totalIncl: 1290, + quitReason: null, + vatExclTax5: null, + vatInclTax5: null, + externalLink: '', + trainingGoal: 'string', + vatExclTax20: 1075, + vatInclTax20: 1290, + trainingPaces: ['3', '1', '5'], + additionalFees: 0, + expectedResult: 'string', + sessionEndDate: '2024-03-29T00:00:00.000Z', + weeklyDuration: 14, + sessionStartDate: '2024-03-28T00:00:00.000Z', + indicativeDuration: 14, + teachingModalities: '2', + trainingCompletionRate: null, + externalId: '53222292400039_scrum-online-action-v2', + trainingActionId: '53222292400039_scrum-online-v2/titre_action', + }, + externalLink: '', + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['registrationFolder.accepted'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-inTraining.ts b/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-inTraining.ts new file mode 100644 index 0000000..2e7d3fc --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-inTraining.ts @@ -0,0 +1,232 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const registrationFolderInTraining = createTrigger({ + auth: wedofAuth, + name: 'registrationFolderInTraining', + displayName: 'Dossier de formation entre en formation', + description: + "Se déclenche lorsqu'un dossier de formation passe à l'état en formation", + props: {}, + sampleData: { + withPoleEmploi: false, + attendeeLink: 'https://test.wedof.fr/apprenant-12345678901234', + dataProviderId: null, + permalink: 'https://test.wedof.fr/dossier-formation-12345678901234', + isAllowActions: true, + type: 'individual', + lastUpdate: '2024-03-15T14:26:51.842Z', + attendee: { + id: 2024, + lastName: 'john', + firstName: 'Doe', + email: 'john.doe@gmail.com', + phoneNumber: '(323) 853-2456', + phoneFixed: '0666666666', + degree: 7, + degreeTitle: 'BAC+5 : grade master, DEA, DESS, ingénieur... (NIVEAU 7)', + address: { + id: null, + city: 'string', + line4: null, + number: '01', + country: null, + postBox: null, + zipCode: 'string', + roadName: 'string', + roadType: 'string', + idAddress: null, + residence: null, + countryCode: null, + fullAddress: null, + trainingSite: null, + corporateName: 'M JOHN DOE', + roadTypeLabel: 'string', + informationSite: null, + repetitionIndex: null, + subscriptionSite: null, + additionalAddress: null, + repetitionIndexLabel: null, + reducedMobilityAccessCompliant: null, + reducedMobilityAccessModalities: null, + }, + dateOfBirth: null, + nameCityOfBirth: null, + gender: null, + birthName: null, + codeCountryOfBirth: null, + poleEmploiId: null, + poleEmploiDpt: null, + codeCityOfBirth: null, + firstName2: null, + firstName3: null, + nameCountryOfBirth: null, + poleEmploiRegionCode: null, + readOnly: false, + cdcCompliant: false, + }, + state: 'accepted', + attendeeState: 'serviceDoneNotDeclared', + billingState: 'notBillable', + externalId: '12345678901234', + billId: null, + billNumber: null, + amountHtNet: null, + amountToInvoice: null, + amountCGU: null, + amountTtc: null, + amountHt: null, + vatHtAmount5: null, + vatAmount5: null, + vatHtAmount20: null, + vatAmount20: null, + history: { + serviceDoneDeclaredAttendeeDate: null, + billedDate: null, + paidDate: null, + acceptedDate: '2024-06-16T14:26:51.000Z', + rejectedWithoutTitulaireSuiteDate: null, + validatedDate: null, + inTrainingDate: null, + terminatedDate: null, + notProcessedDate: '2024-06-16T14:26:51.000Z', + refusedByAttendeeDate: null, + refusedByOrganismDate: null, + refusedByFinancerDate: null, + canceledByAttendeeDate: null, + canceledByOrganismDate: null, + serviceDoneDeclaredDate: null, + serviceDoneValidatedDate: null, + canceledByAttendeeNotRealizedDate: null, + canceledByFinancerDate: null, + inControlDate: null, + releasedDate: null, + completionRateLastUpdate: null, + }, + files: [], + notes: '', + description: '', + completionRate: null, + controlState: 'notInControl', + createdOn: '2024-03-15T14:26:51.000Z', + updatedOn: '2024-06-26T09:42:40.642Z', + _links: { + self: { + href: '/api/registrationFolders/12345678901234', + }, + validate: { + href: '/api/registrationFolders/12345678901234/validate', + }, + inTraining: { + href: '/api/registrationFolders/12345678901234/inTraining', + }, + terminate: { + href: '/api/registrationFolders/12345678901234/terminate', + }, + serviceDone: { + href: '/api/registrationFolders/12345678901234/serviceDone', + }, + refuse: { + href: '/api/registrationFolders/12345678901234/refuse', + }, + cancel: { + href: '/api/registrationFolders/12345678901234/cancel', + }, + billing: { + href: '/api/registrationFolders/12345678901234/billing', + }, + session: { + href: '/api/sessions/titre_action', + }, + organism: { + href: '/api/organisms/12345678901234', + name: 'Organism', + siret: '12345678901234', + }, + payments: { + href: '/api/payments?registrationFolderId=12345678901234', + }, + trainingAction: { + href: '/api/trainingActions/titre_action', + }, + certification: { + href: '/api/certifications/112713', + name: 'Gérer des projets avec la méthode Agile', + certifInfo: '112713', + externalId: 'RS5695', + id: 2, + enabled: true, + }, + activities: { + href: '/api/activities/RegistrationFolder/12345678901234', + }, + }, + tags: [], + trainingActionInfo: { + vat: null, + title: 'Titre formation', + address: { + id: null, + }, + content: 'string', + sessionId: 'Titre session', + totalExcl: 1075, + totalIncl: 1290, + quitReason: null, + vatExclTax5: null, + vatInclTax5: null, + externalLink: '', + trainingGoal: 'string', + vatExclTax20: 1075, + vatInclTax20: 1290, + trainingPaces: ['3', '1', '5'], + additionalFees: 0, + expectedResult: 'string', + sessionEndDate: '2024-03-29T00:00:00.000Z', + weeklyDuration: 14, + sessionStartDate: '2024-03-28T00:00:00.000Z', + indicativeDuration: 14, + teachingModalities: '2', + trainingCompletionRate: null, + externalId: '53222292400039_scrum-online-action-v2', + trainingActionId: '53222292400039_scrum-online-v2/titre_action', + }, + externalLink: '', + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['registrationFolder.inTraining'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-paid.ts b/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-paid.ts new file mode 100644 index 0000000..8ac0aee --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-paid.ts @@ -0,0 +1,231 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const registrationFolderPaid = createTrigger({ + auth: wedofAuth, + name: 'registrationFolderPaid', + displayName: 'Dossier de formation payé (acompte ou payé totalement)', + description: "Se déclenche lorsqu'un dossier de formation est payé", + props: {}, + sampleData: { + withPoleEmploi: false, + attendeeLink: 'https://test.wedof.fr/apprenant-12345678901234', + dataProviderId: null, + permalink: 'https://test.wedof.fr/dossier-formation-12345678901234', + isAllowActions: true, + type: 'individual', + lastUpdate: '2024-03-15T14:26:51.842Z', + attendee: { + id: 2024, + lastName: 'john', + firstName: 'Doe', + email: 'john.doe@gmail.com', + phoneNumber: '(323) 853-2456', + phoneFixed: '0666666666', + degree: 7, + degreeTitle: 'BAC+5 : grade master, DEA, DESS, ingénieur... (NIVEAU 7)', + address: { + id: null, + city: 'string', + line4: null, + number: '01', + country: null, + postBox: null, + zipCode: 'string', + roadName: 'string', + roadType: 'string', + idAddress: null, + residence: null, + countryCode: null, + fullAddress: null, + trainingSite: null, + corporateName: 'M JOHN DOE', + roadTypeLabel: 'string', + informationSite: null, + repetitionIndex: null, + subscriptionSite: null, + additionalAddress: null, + repetitionIndexLabel: null, + reducedMobilityAccessCompliant: null, + reducedMobilityAccessModalities: null, + }, + dateOfBirth: null, + nameCityOfBirth: null, + gender: null, + birthName: null, + codeCountryOfBirth: null, + poleEmploiId: null, + poleEmploiDpt: null, + codeCityOfBirth: null, + firstName2: null, + firstName3: null, + nameCountryOfBirth: null, + poleEmploiRegionCode: null, + readOnly: false, + cdcCompliant: false, + }, + state: 'accepted', + attendeeState: 'serviceDoneNotDeclared', + billingState: 'notBillable', + externalId: '12345678901234', + billId: null, + billNumber: null, + amountHtNet: null, + amountToInvoice: null, + amountCGU: null, + amountTtc: null, + amountHt: null, + vatHtAmount5: null, + vatAmount5: null, + vatHtAmount20: null, + vatAmount20: null, + history: { + serviceDoneDeclaredAttendeeDate: null, + billedDate: null, + paidDate: null, + acceptedDate: '2024-06-16T14:26:51.000Z', + rejectedWithoutTitulaireSuiteDate: null, + validatedDate: null, + inTrainingDate: null, + terminatedDate: null, + notProcessedDate: '2024-06-16T14:26:51.000Z', + refusedByAttendeeDate: null, + refusedByOrganismDate: null, + refusedByFinancerDate: null, + canceledByAttendeeDate: null, + canceledByOrganismDate: null, + serviceDoneDeclaredDate: null, + serviceDoneValidatedDate: null, + canceledByAttendeeNotRealizedDate: null, + canceledByFinancerDate: null, + inControlDate: null, + releasedDate: null, + completionRateLastUpdate: null, + }, + files: [], + notes: '', + description: '', + completionRate: null, + controlState: 'notInControl', + createdOn: '2024-03-15T14:26:51.000Z', + updatedOn: '2024-06-26T09:42:40.642Z', + _links: { + self: { + href: '/api/registrationFolders/12345678901234', + }, + validate: { + href: '/api/registrationFolders/12345678901234/validate', + }, + inTraining: { + href: '/api/registrationFolders/12345678901234/inTraining', + }, + terminate: { + href: '/api/registrationFolders/12345678901234/terminate', + }, + serviceDone: { + href: '/api/registrationFolders/12345678901234/serviceDone', + }, + refuse: { + href: '/api/registrationFolders/12345678901234/refuse', + }, + cancel: { + href: '/api/registrationFolders/12345678901234/cancel', + }, + billing: { + href: '/api/registrationFolders/12345678901234/billing', + }, + session: { + href: '/api/sessions/titre_action', + }, + organism: { + href: '/api/organisms/12345678901234', + name: 'Organism', + siret: '12345678901234', + }, + payments: { + href: '/api/payments?registrationFolderId=12345678901234', + }, + trainingAction: { + href: '/api/trainingActions/titre_action', + }, + certification: { + href: '/api/certifications/112713', + name: 'Gérer des projets avec la méthode Agile', + certifInfo: '112713', + externalId: 'RS5695', + id: 2, + enabled: true, + }, + activities: { + href: '/api/activities/RegistrationFolder/12345678901234', + }, + }, + tags: [], + trainingActionInfo: { + vat: null, + title: 'Titre formation', + address: { + id: null, + }, + content: 'string', + sessionId: 'Titre session', + totalExcl: 1075, + totalIncl: 1290, + quitReason: null, + vatExclTax5: null, + vatInclTax5: null, + externalLink: '', + trainingGoal: 'string', + vatExclTax20: 1075, + vatInclTax20: 1290, + trainingPaces: ['3', '1', '5'], + additionalFees: 0, + expectedResult: 'string', + sessionEndDate: '2024-03-29T00:00:00.000Z', + weeklyDuration: 14, + sessionStartDate: '2024-03-28T00:00:00.000Z', + indicativeDuration: 14, + teachingModalities: '2', + trainingCompletionRate: null, + externalId: '53222292400039_scrum-online-action-v2', + trainingActionId: '53222292400039_scrum-online-v2/titre_action', + }, + externalLink: '', + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['registrationFolderBilling.paid'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-selected.ts b/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-selected.ts new file mode 100644 index 0000000..1788a8a --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-selected.ts @@ -0,0 +1,234 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const registrationFolderSelected = createTrigger({ + auth: wedofAuth, + name: 'registrationFolderSelected', + displayName: 'Événement sur le dossier de formation', + description: + "Se déclenche lorsque l'événement choisi se produit sur un dossier de formation", + props: { + scope: wedofCommon.events, + }, + sampleData: { + withPoleEmploi: false, + attendeeLink: 'https://test.wedof.fr/apprenant-12345678901234', + dataProviderId: null, + permalink: 'https://test.wedof.fr/dossier-formation-12345678901234', + isAllowActions: true, + type: 'individual', + lastUpdate: '2024-03-15T14:26:51.842Z', + attendee: { + id: 2024, + lastName: 'john', + firstName: 'Doe', + email: 'john.doe@gmail.com', + phoneNumber: '(323) 853-2456', + phoneFixed: '0666666666', + degree: 7, + degreeTitle: 'BAC+5 : grade master, DEA, DESS, ingénieur... (NIVEAU 7)', + address: { + id: null, + city: 'string', + line4: null, + number: '01', + country: null, + postBox: null, + zipCode: 'string', + roadName: 'string', + roadType: 'string', + idAddress: null, + residence: null, + countryCode: null, + fullAddress: null, + trainingSite: null, + corporateName: 'M JOHN DOE', + roadTypeLabel: 'string', + informationSite: null, + repetitionIndex: null, + subscriptionSite: null, + additionalAddress: null, + repetitionIndexLabel: null, + reducedMobilityAccessCompliant: null, + reducedMobilityAccessModalities: null, + }, + dateOfBirth: null, + nameCityOfBirth: null, + gender: null, + birthName: null, + codeCountryOfBirth: null, + poleEmploiId: null, + poleEmploiDpt: null, + codeCityOfBirth: null, + firstName2: null, + firstName3: null, + nameCountryOfBirth: null, + poleEmploiRegionCode: null, + readOnly: false, + cdcCompliant: false, + }, + state: 'accepted', + attendeeState: 'serviceDoneNotDeclared', + billingState: 'notBillable', + externalId: '12345678901234', + billId: null, + billNumber: null, + amountHtNet: null, + amountToInvoice: null, + amountCGU: null, + amountTtc: null, + amountHt: null, + vatHtAmount5: null, + vatAmount5: null, + vatHtAmount20: null, + vatAmount20: null, + history: { + serviceDoneDeclaredAttendeeDate: null, + billedDate: null, + paidDate: null, + acceptedDate: '2024-06-16T14:26:51.000Z', + rejectedWithoutTitulaireSuiteDate: null, + validatedDate: null, + inTrainingDate: null, + terminatedDate: null, + notProcessedDate: '2024-06-16T14:26:51.000Z', + refusedByAttendeeDate: null, + refusedByOrganismDate: null, + refusedByFinancerDate: null, + canceledByAttendeeDate: null, + canceledByOrganismDate: null, + serviceDoneDeclaredDate: null, + serviceDoneValidatedDate: null, + canceledByAttendeeNotRealizedDate: null, + canceledByFinancerDate: null, + inControlDate: null, + releasedDate: null, + completionRateLastUpdate: null, + }, + files: [], + notes: '', + description: '', + completionRate: null, + controlState: 'notInControl', + createdOn: '2024-03-15T14:26:51.000Z', + updatedOn: '2024-06-26T09:42:40.642Z', + _links: { + self: { + href: '/api/registrationFolders/12345678901234', + }, + validate: { + href: '/api/registrationFolders/12345678901234/validate', + }, + inTraining: { + href: '/api/registrationFolders/12345678901234/inTraining', + }, + terminate: { + href: '/api/registrationFolders/12345678901234/terminate', + }, + serviceDone: { + href: '/api/registrationFolders/12345678901234/serviceDone', + }, + refuse: { + href: '/api/registrationFolders/12345678901234/refuse', + }, + cancel: { + href: '/api/registrationFolders/12345678901234/cancel', + }, + billing: { + href: '/api/registrationFolders/12345678901234/billing', + }, + session: { + href: '/api/sessions/titre_action', + }, + organism: { + href: '/api/organisms/12345678901234', + name: 'Organism', + siret: '12345678901234', + }, + payments: { + href: '/api/payments?registrationFolderId=12345678901234', + }, + trainingAction: { + href: '/api/trainingActions/titre_action', + }, + certification: { + href: '/api/certifications/112713', + name: 'Gérer des projets avec la méthode Agile', + certifInfo: '112713', + externalId: 'RS5695', + id: 2, + enabled: true, + }, + activities: { + href: '/api/activities/RegistrationFolder/12345678901234', + }, + }, + tags: [], + trainingActionInfo: { + vat: null, + title: 'Titre formation', + address: { + id: null, + }, + content: 'string', + sessionId: 'Titre session', + totalExcl: 1075, + totalIncl: 1290, + quitReason: null, + vatExclTax5: null, + vatInclTax5: null, + externalLink: '', + trainingGoal: 'string', + vatExclTax20: 1075, + vatInclTax20: 1290, + trainingPaces: ['3', '1', '5'], + additionalFees: 0, + expectedResult: 'string', + sessionEndDate: '2024-03-29T00:00:00.000Z', + weeklyDuration: 14, + sessionStartDate: '2024-03-28T00:00:00.000Z', + indicativeDuration: 14, + teachingModalities: '2', + trainingCompletionRate: null, + externalId: '53222292400039_scrum-online-action-v2', + trainingActionId: '53222292400039_scrum-online-v2/titre_action', + }, + externalLink: '', + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + context.propsValue.scope, + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-terminated.ts b/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-terminated.ts new file mode 100644 index 0000000..deb6ea6 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-terminated.ts @@ -0,0 +1,232 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const registrationFolderTerminated = createTrigger({ + auth: wedofAuth, + name: 'registrationFolderTerminated', + displayName: 'Dossier de formation sort de formation', + description: + "Se déclenche lorsqu'un dossier de formation passe à l'état sorti de formation", + props: {}, + sampleData: { + withPoleEmploi: false, + attendeeLink: 'https://test.wedof.fr/apprenant-12345678901234', + dataProviderId: null, + permalink: 'https://test.wedof.fr/dossier-formation-12345678901234', + isAllowActions: true, + type: 'individual', + lastUpdate: '2024-03-15T14:26:51.842Z', + attendee: { + id: 2024, + lastName: 'john', + firstName: 'Doe', + email: 'john.doe@gmail.com', + phoneNumber: '(323) 853-2456', + phoneFixed: '0666666666', + degree: 7, + degreeTitle: 'BAC+5 : grade master, DEA, DESS, ingénieur... (NIVEAU 7)', + address: { + id: null, + city: 'string', + line4: null, + number: '01', + country: null, + postBox: null, + zipCode: 'string', + roadName: 'string', + roadType: 'string', + idAddress: null, + residence: null, + countryCode: null, + fullAddress: null, + trainingSite: null, + corporateName: 'M JOHN DOE', + roadTypeLabel: 'string', + informationSite: null, + repetitionIndex: null, + subscriptionSite: null, + additionalAddress: null, + repetitionIndexLabel: null, + reducedMobilityAccessCompliant: null, + reducedMobilityAccessModalities: null, + }, + dateOfBirth: null, + nameCityOfBirth: null, + gender: null, + birthName: null, + codeCountryOfBirth: null, + poleEmploiId: null, + poleEmploiDpt: null, + codeCityOfBirth: null, + firstName2: null, + firstName3: null, + nameCountryOfBirth: null, + poleEmploiRegionCode: null, + readOnly: false, + cdcCompliant: false, + }, + state: 'accepted', + attendeeState: 'serviceDoneNotDeclared', + billingState: 'notBillable', + externalId: '12345678901234', + billId: null, + billNumber: null, + amountHtNet: null, + amountToInvoice: null, + amountCGU: null, + amountTtc: null, + amountHt: null, + vatHtAmount5: null, + vatAmount5: null, + vatHtAmount20: null, + vatAmount20: null, + history: { + serviceDoneDeclaredAttendeeDate: null, + billedDate: null, + paidDate: null, + acceptedDate: '2024-06-16T14:26:51.000Z', + rejectedWithoutTitulaireSuiteDate: null, + validatedDate: null, + inTrainingDate: null, + terminatedDate: null, + notProcessedDate: '2024-06-16T14:26:51.000Z', + refusedByAttendeeDate: null, + refusedByOrganismDate: null, + refusedByFinancerDate: null, + canceledByAttendeeDate: null, + canceledByOrganismDate: null, + serviceDoneDeclaredDate: null, + serviceDoneValidatedDate: null, + canceledByAttendeeNotRealizedDate: null, + canceledByFinancerDate: null, + inControlDate: null, + releasedDate: null, + completionRateLastUpdate: null, + }, + files: [], + notes: '', + description: '', + completionRate: null, + controlState: 'notInControl', + createdOn: '2024-03-15T14:26:51.000Z', + updatedOn: '2024-06-26T09:42:40.642Z', + _links: { + self: { + href: '/api/registrationFolders/12345678901234', + }, + validate: { + href: '/api/registrationFolders/12345678901234/validate', + }, + inTraining: { + href: '/api/registrationFolders/12345678901234/inTraining', + }, + terminate: { + href: '/api/registrationFolders/12345678901234/terminate', + }, + serviceDone: { + href: '/api/registrationFolders/12345678901234/serviceDone', + }, + refuse: { + href: '/api/registrationFolders/12345678901234/refuse', + }, + cancel: { + href: '/api/registrationFolders/12345678901234/cancel', + }, + billing: { + href: '/api/registrationFolders/12345678901234/billing', + }, + session: { + href: '/api/sessions/titre_action', + }, + organism: { + href: '/api/organisms/12345678901234', + name: 'Organism', + siret: '12345678901234', + }, + payments: { + href: '/api/payments?registrationFolderId=12345678901234', + }, + trainingAction: { + href: '/api/trainingActions/titre_action', + }, + certification: { + href: '/api/certifications/112713', + name: 'Gérer des projets avec la méthode Agile', + certifInfo: '112713', + externalId: 'RS5695', + id: 2, + enabled: true, + }, + activities: { + href: '/api/activities/RegistrationFolder/12345678901234', + }, + }, + tags: [], + trainingActionInfo: { + vat: null, + title: 'Titre formation', + address: { + id: null, + }, + content: 'string', + sessionId: 'Titre session', + totalExcl: 1075, + totalIncl: 1290, + quitReason: null, + vatExclTax5: null, + vatInclTax5: null, + externalLink: '', + trainingGoal: 'string', + vatExclTax20: 1075, + vatInclTax20: 1290, + trainingPaces: ['3', '1', '5'], + additionalFees: 0, + expectedResult: 'string', + sessionEndDate: '2024-03-29T00:00:00.000Z', + weeklyDuration: 14, + sessionStartDate: '2024-03-28T00:00:00.000Z', + indicativeDuration: 14, + teachingModalities: '2', + trainingCompletionRate: null, + externalId: '53222292400039_scrum-online-action-v2', + trainingActionId: '53222292400039_scrum-online-v2/titre_action', + }, + externalLink: '', + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['registrationFolder.terminated'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-tobill.ts b/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-tobill.ts new file mode 100644 index 0000000..a0e0ba9 --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-tobill.ts @@ -0,0 +1,232 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const registrationFolderTobill = createTrigger({ + auth: wedofAuth, + name: 'registrationFolderTobill', + displayName: 'Dossier de formation à facturer', + description: + "Se déclenche lorsqu'un dossier de formation est prêt à être facturé (service fait validé)", + props: {}, + sampleData: { + withPoleEmploi: false, + attendeeLink: 'https://test.wedof.fr/apprenant-12345678901234', + dataProviderId: null, + permalink: 'https://test.wedof.fr/dossier-formation-12345678901234', + isAllowActions: true, + type: 'individual', + lastUpdate: '2024-03-15T14:26:51.842Z', + attendee: { + id: 2024, + lastName: 'john', + firstName: 'Doe', + email: 'john.doe@gmail.com', + phoneNumber: '(323) 853-2456', + phoneFixed: '0666666666', + degree: 7, + degreeTitle: 'BAC+5 : grade master, DEA, DESS, ingénieur... (NIVEAU 7)', + address: { + id: null, + city: 'string', + line4: null, + number: '01', + country: null, + postBox: null, + zipCode: 'string', + roadName: 'string', + roadType: 'string', + idAddress: null, + residence: null, + countryCode: null, + fullAddress: null, + trainingSite: null, + corporateName: 'M JOHN DOE', + roadTypeLabel: 'string', + informationSite: null, + repetitionIndex: null, + subscriptionSite: null, + additionalAddress: null, + repetitionIndexLabel: null, + reducedMobilityAccessCompliant: null, + reducedMobilityAccessModalities: null, + }, + dateOfBirth: null, + nameCityOfBirth: null, + gender: null, + birthName: null, + codeCountryOfBirth: null, + poleEmploiId: null, + poleEmploiDpt: null, + codeCityOfBirth: null, + firstName2: null, + firstName3: null, + nameCountryOfBirth: null, + poleEmploiRegionCode: null, + readOnly: false, + cdcCompliant: false, + }, + state: 'accepted', + attendeeState: 'serviceDoneNotDeclared', + billingState: 'notBillable', + externalId: '12345678901234', + billId: null, + billNumber: null, + amountHtNet: null, + amountToInvoice: null, + amountCGU: null, + amountTtc: null, + amountHt: null, + vatHtAmount5: null, + vatAmount5: null, + vatHtAmount20: null, + vatAmount20: null, + history: { + serviceDoneDeclaredAttendeeDate: null, + billedDate: null, + paidDate: null, + acceptedDate: '2024-06-16T14:26:51.000Z', + rejectedWithoutTitulaireSuiteDate: null, + validatedDate: null, + inTrainingDate: null, + terminatedDate: null, + notProcessedDate: '2024-06-16T14:26:51.000Z', + refusedByAttendeeDate: null, + refusedByOrganismDate: null, + refusedByFinancerDate: null, + canceledByAttendeeDate: null, + canceledByOrganismDate: null, + serviceDoneDeclaredDate: null, + serviceDoneValidatedDate: null, + canceledByAttendeeNotRealizedDate: null, + canceledByFinancerDate: null, + inControlDate: null, + releasedDate: null, + completionRateLastUpdate: null, + }, + files: [], + notes: '', + description: '', + completionRate: null, + controlState: 'notInControl', + createdOn: '2024-03-15T14:26:51.000Z', + updatedOn: '2024-06-26T09:42:40.642Z', + _links: { + self: { + href: '/api/registrationFolders/12345678901234', + }, + validate: { + href: '/api/registrationFolders/12345678901234/validate', + }, + inTraining: { + href: '/api/registrationFolders/12345678901234/inTraining', + }, + terminate: { + href: '/api/registrationFolders/12345678901234/terminate', + }, + serviceDone: { + href: '/api/registrationFolders/12345678901234/serviceDone', + }, + refuse: { + href: '/api/registrationFolders/12345678901234/refuse', + }, + cancel: { + href: '/api/registrationFolders/12345678901234/cancel', + }, + billing: { + href: '/api/registrationFolders/12345678901234/billing', + }, + session: { + href: '/api/sessions/titre_action', + }, + organism: { + href: '/api/organisms/12345678901234', + name: 'Organism', + siret: '12345678901234', + }, + payments: { + href: '/api/payments?registrationFolderId=12345678901234', + }, + trainingAction: { + href: '/api/trainingActions/titre_action', + }, + certification: { + href: '/api/certifications/112713', + name: 'Gérer des projets avec la méthode Agile', + certifInfo: '112713', + externalId: 'RS5695', + id: 2, + enabled: true, + }, + activities: { + href: '/api/activities/RegistrationFolder/12345678901234', + }, + }, + tags: [], + trainingActionInfo: { + vat: null, + title: 'Titre formation', + address: { + id: null, + }, + content: 'string', + sessionId: 'Titre session', + totalExcl: 1075, + totalIncl: 1290, + quitReason: null, + vatExclTax5: null, + vatInclTax5: null, + externalLink: '', + trainingGoal: 'string', + vatExclTax20: 1075, + vatInclTax20: 1290, + trainingPaces: ['3', '1', '5'], + additionalFees: 0, + expectedResult: 'string', + sessionEndDate: '2024-03-29T00:00:00.000Z', + weeklyDuration: 14, + sessionStartDate: '2024-03-28T00:00:00.000Z', + indicativeDuration: 14, + teachingModalities: '2', + trainingCompletionRate: null, + externalId: '53222292400039_scrum-online-action-v2', + trainingActionId: '53222292400039_scrum-online-v2/titre_action', + }, + externalLink: '', + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['registrationFolderBilling.toBill'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-updated.ts b/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-updated.ts new file mode 100644 index 0000000..1beb9de --- /dev/null +++ b/packages/pieces/community/wedof/src/lib/triggers/registration-folders/registration-folder-updated.ts @@ -0,0 +1,231 @@ +import { wedofAuth } from '../../..'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { wedofCommon } from '../../common/wedof'; + +export const registrationFolderUpdated = createTrigger({ + auth: wedofAuth, + name: 'registrationFolderUpdated', + displayName: 'Dossier de formation mis à jour', + description: "Se déclenche lorsqu'un dossier de formation est mis à jour", + props: {}, + sampleData: { + withPoleEmploi: false, + attendeeLink: 'https://test.wedof.fr/apprenant-12345678901234', + dataProviderId: null, + permalink: 'https://test.wedof.fr/dossier-formation-12345678901234', + isAllowActions: true, + type: 'individual', + lastUpdate: '2024-03-15T14:26:51.842Z', + attendee: { + id: 2024, + lastName: 'john', + firstName: 'Doe', + email: 'john.doe@gmail.com', + phoneNumber: '(323) 853-2456', + phoneFixed: '0666666666', + degree: 7, + degreeTitle: 'BAC+5 : grade master, DEA, DESS, ingénieur... (NIVEAU 7)', + address: { + id: null, + city: 'string', + line4: null, + number: '01', + country: null, + postBox: null, + zipCode: 'string', + roadName: 'string', + roadType: 'string', + idAddress: null, + residence: null, + countryCode: null, + fullAddress: null, + trainingSite: null, + corporateName: 'M JOHN DOE', + roadTypeLabel: 'string', + informationSite: null, + repetitionIndex: null, + subscriptionSite: null, + additionalAddress: null, + repetitionIndexLabel: null, + reducedMobilityAccessCompliant: null, + reducedMobilityAccessModalities: null, + }, + dateOfBirth: null, + nameCityOfBirth: null, + gender: null, + birthName: null, + codeCountryOfBirth: null, + poleEmploiId: null, + poleEmploiDpt: null, + codeCityOfBirth: null, + firstName2: null, + firstName3: null, + nameCountryOfBirth: null, + poleEmploiRegionCode: null, + readOnly: false, + cdcCompliant: false, + }, + state: 'accepted', + attendeeState: 'serviceDoneNotDeclared', + billingState: 'notBillable', + externalId: '12345678901234', + billId: null, + billNumber: null, + amountHtNet: null, + amountToInvoice: null, + amountCGU: null, + amountTtc: null, + amountHt: null, + vatHtAmount5: null, + vatAmount5: null, + vatHtAmount20: null, + vatAmount20: null, + history: { + serviceDoneDeclaredAttendeeDate: null, + billedDate: null, + paidDate: null, + acceptedDate: '2024-06-16T14:26:51.000Z', + rejectedWithoutTitulaireSuiteDate: null, + validatedDate: null, + inTrainingDate: null, + terminatedDate: null, + notProcessedDate: '2024-06-16T14:26:51.000Z', + refusedByAttendeeDate: null, + refusedByOrganismDate: null, + refusedByFinancerDate: null, + canceledByAttendeeDate: null, + canceledByOrganismDate: null, + serviceDoneDeclaredDate: null, + serviceDoneValidatedDate: null, + canceledByAttendeeNotRealizedDate: null, + canceledByFinancerDate: null, + inControlDate: null, + releasedDate: null, + completionRateLastUpdate: null, + }, + files: [], + notes: '', + description: '', + completionRate: null, + controlState: 'notInControl', + createdOn: '2024-03-15T14:26:51.000Z', + updatedOn: '2024-06-26T09:42:40.642Z', + _links: { + self: { + href: '/api/registrationFolders/12345678901234', + }, + validate: { + href: '/api/registrationFolders/12345678901234/validate', + }, + inTraining: { + href: '/api/registrationFolders/12345678901234/inTraining', + }, + terminate: { + href: '/api/registrationFolders/12345678901234/terminate', + }, + serviceDone: { + href: '/api/registrationFolders/12345678901234/serviceDone', + }, + refuse: { + href: '/api/registrationFolders/12345678901234/refuse', + }, + cancel: { + href: '/api/registrationFolders/12345678901234/cancel', + }, + billing: { + href: '/api/registrationFolders/12345678901234/billing', + }, + session: { + href: '/api/sessions/titre_action', + }, + organism: { + href: '/api/organisms/12345678901234', + name: 'Organism', + siret: '12345678901234', + }, + payments: { + href: '/api/payments?registrationFolderId=12345678901234', + }, + trainingAction: { + href: '/api/trainingActions/titre_action', + }, + certification: { + href: '/api/certifications/112713', + name: 'Gérer des projets avec la méthode Agile', + certifInfo: '112713', + externalId: 'RS5695', + id: 2, + enabled: true, + }, + activities: { + href: '/api/activities/RegistrationFolder/12345678901234', + }, + }, + tags: [], + trainingActionInfo: { + vat: null, + title: 'Titre formation', + address: { + id: null, + }, + content: 'string', + sessionId: 'Titre session', + totalExcl: 1075, + totalIncl: 1290, + quitReason: null, + vatExclTax5: null, + vatInclTax5: null, + externalLink: '', + trainingGoal: 'string', + vatExclTax20: 1075, + vatInclTax20: 1290, + trainingPaces: ['3', '1', '5'], + additionalFees: 0, + expectedResult: 'string', + sessionEndDate: '2024-03-29T00:00:00.000Z', + weeklyDuration: 14, + sessionStartDate: '2024-03-28T00:00:00.000Z', + indicativeDuration: 14, + teachingModalities: '2', + trainingCompletionRate: null, + externalId: '53222292400039_scrum-online-action-v2', + trainingActionId: '53222292400039_scrum-online-v2/titre_action', + }, + externalLink: '', + }, + type: TriggerStrategy.WEBHOOK, + + async onEnable(context) { + const flows = await context.flows.list(); + const flow = flows.data.find( + (flow) => flow.id === context.flows.current.id + ); + const name = `${flow?.version.displayName}`; + + await wedofCommon.handleWebhookSubscription( + ['registrationFolder.updated'], + context, + name + ); + }, + + async onDisable(context) { + const id = await context.store.get('_webhookId'); + if (id !== null && id !== undefined) { + await wedofCommon.unsubscribeWebhook( + id as string, + context.auth as string + ); + await context.store.delete('_webhookId'); + } + }, + + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/wedof/tsconfig.json b/packages/pieces/community/wedof/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/wedof/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/wedof/tsconfig.lib.json b/packages/pieces/community/wedof/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/wedof/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/whatsable/.eslintrc.json b/packages/pieces/community/whatsable/.eslintrc.json new file mode 100644 index 0000000..359ff63 --- /dev/null +++ b/packages/pieces/community/whatsable/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/whatsable/README.md b/packages/pieces/community/whatsable/README.md new file mode 100644 index 0000000..270a58b --- /dev/null +++ b/packages/pieces/community/whatsable/README.md @@ -0,0 +1,7 @@ +# pieces-whatsable + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-whatsable` to build the library. diff --git a/packages/pieces/community/whatsable/package.json b/packages/pieces/community/whatsable/package.json new file mode 100644 index 0000000..af51c81 --- /dev/null +++ b/packages/pieces/community/whatsable/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-whatsable", + "version": "0.0.3" +} \ No newline at end of file diff --git a/packages/pieces/community/whatsable/project.json b/packages/pieces/community/whatsable/project.json new file mode 100644 index 0000000..727294f --- /dev/null +++ b/packages/pieces/community/whatsable/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-whatsable", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/whatsable/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/whatsable", + "tsConfig": "packages/pieces/community/whatsable/tsconfig.lib.json", + "packageJson": "packages/pieces/community/whatsable/package.json", + "main": "packages/pieces/community/whatsable/src/index.ts", + "assets": [ + "packages/pieces/community/whatsable/*.md", + { + "input": "packages/pieces/community/whatsable/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-whatsable {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/whatsable/src/index.ts b/packages/pieces/community/whatsable/src/index.ts new file mode 100644 index 0000000..7e7acc1 --- /dev/null +++ b/packages/pieces/community/whatsable/src/index.ts @@ -0,0 +1,19 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { sendMessage } from './lib/actions/send-message'; + +export const whatsableAuth = PieceAuth.SecretText({ + displayName: 'Whatsable Auth Token', + description: 'The auth token for Whatsable', + required: true, +}); + +export const whatsable = createPiece({ + displayName: 'Whatsable', + description: 'Manage your WhatsApp business account', + auth: whatsableAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/whatsable.png', + authors: ["abuaboud"], + actions: [sendMessage], + triggers: [], +}); diff --git a/packages/pieces/community/whatsable/src/lib/actions/send-message.ts b/packages/pieces/community/whatsable/src/lib/actions/send-message.ts new file mode 100644 index 0000000..921d8e6 --- /dev/null +++ b/packages/pieces/community/whatsable/src/lib/actions/send-message.ts @@ -0,0 +1,38 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, HttpMethod, +} from '@activepieces/pieces-common'; +import { whatsableAuth } from '../..'; + +export const sendMessage = createAction({ + name: 'sendMessage', + displayName: 'Send Message', + description: '', + auth: whatsableAuth, + props: { + to: Property.ShortText({ + displayName: 'To', + description: 'The recipient of the message', + required: true, + }), + text: Property.LongText({ + displayName: 'Message', + description: 'The message to send', + required: true, + }), + }, + async run(ctx) { + const { to, text } = ctx.propsValue; + return await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://dashboard.whatsable.app/api/whatsapp/messages/send', + headers: { + 'Authorization': ctx.auth, + }, + body: { + to, + text, + }, + }); + }, +}); diff --git a/packages/pieces/community/whatsable/tsconfig.json b/packages/pieces/community/whatsable/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/whatsable/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/whatsable/tsconfig.lib.json b/packages/pieces/community/whatsable/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/whatsable/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/whatsapp/.eslintrc.json b/packages/pieces/community/whatsapp/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/whatsapp/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/whatsapp/README.md b/packages/pieces/community/whatsapp/README.md new file mode 100644 index 0000000..85a4a45 --- /dev/null +++ b/packages/pieces/community/whatsapp/README.md @@ -0,0 +1,7 @@ +# pieces-whatsapp + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-whatsapp` to build the library. diff --git a/packages/pieces/community/whatsapp/package.json b/packages/pieces/community/whatsapp/package.json new file mode 100644 index 0000000..a81d700 --- /dev/null +++ b/packages/pieces/community/whatsapp/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-whatsapp", + "version": "0.1.0" +} diff --git a/packages/pieces/community/whatsapp/project.json b/packages/pieces/community/whatsapp/project.json new file mode 100644 index 0000000..ea29011 --- /dev/null +++ b/packages/pieces/community/whatsapp/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-whatsapp", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/whatsapp/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/whatsapp", + "tsConfig": "packages/pieces/community/whatsapp/tsconfig.lib.json", + "packageJson": "packages/pieces/community/whatsapp/package.json", + "main": "packages/pieces/community/whatsapp/src/index.ts", + "assets": [ + "packages/pieces/community/whatsapp/*.md", + { + "input": "packages/pieces/community/whatsapp/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-whatsapp {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/whatsapp/src/index.ts b/packages/pieces/community/whatsapp/src/index.ts new file mode 100644 index 0000000..a3827c2 --- /dev/null +++ b/packages/pieces/community/whatsapp/src/index.ts @@ -0,0 +1,46 @@ +import { createPiece, PieceAuth, Property } from '@activepieces/pieces-framework'; +import { sendMessage } from './lib/actions/send-message'; +import { sendMedia } from './lib/actions/send-media'; +import { sendTemplateMessageAction } from './lib/actions/send-from-template'; + +const markdown = ` +To Obtain a Phone Number ID and a Permanent System User Access Token, follow these steps: + +1. Go to https://developers.facebook.com/ +2. Make a new app, Select Other for usecase. +3. Choose Business as the type of app. +4. Add new Product -> WhatsApp. +5. Navigate to WhatsApp Settings > API Setup. +6. Copy the Business Account ID. +7. Login to your [Meta Business Manager](https://business.facebook.com/). +8. Click on Settings. +9. Create a new System User with access over the app and copy the access token. +`; + +export const whatsappAuth = PieceAuth.CustomAuth({ + required: true, + description: markdown, + props: { + access_token: PieceAuth.SecretText({ + displayName: 'System User Access Token', + description: 'The system user access token of your WhatsApp business account.', + required: true, + }), + businessAccountId: Property.ShortText({ + displayName: 'Business Account ID', + description: 'The business account ID of your WhatsApp business account.', + required: true, + }), + }, +}); + +export const whatsapp = createPiece({ + displayName: 'WhatsApp Business', + description: 'Manage your WhatsApp business account', + auth: whatsappAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/whatsapp.png', + authors: ['LevwTech', 'kishanprmr'], + actions: [sendMessage, sendMedia, sendTemplateMessageAction], + triggers: [], +}); diff --git a/packages/pieces/community/whatsapp/src/lib/actions/send-from-template.ts b/packages/pieces/community/whatsapp/src/lib/actions/send-from-template.ts new file mode 100644 index 0000000..6037b81 --- /dev/null +++ b/packages/pieces/community/whatsapp/src/lib/actions/send-from-template.ts @@ -0,0 +1,106 @@ +import { whatsappAuth } from '../..'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { commonProps } from '../common/utils'; + +export const sendTemplateMessageAction = createAction({ + auth: whatsappAuth, + name: 'send-template-message', + displayName: 'Send Template Message', + description: 'Sends a template message.', + props: { + phone_number_id: commonProps.phone_number_id, + to: Property.ShortText({ + displayName: 'To', + description: 'Recipient phone number.', + required: true, + }), + message_template_id: commonProps.message_template_id, + message_template_fields: commonProps.message_template_fields, + }, + async run(context) { + const phoneNumberId = context.propsValue.phone_number_id as string; + const recipientPhoneNumber = context.propsValue.to as string; + const templateId = context.propsValue.message_template_id as string; + const templateFields = context.propsValue.message_template_fields; + + // construct components object + // https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#components-object + const components = []; + const headerParameters = []; + const bodyParameters = []; + const buttonParameters = []; + + for (const [key, value] of Object.entries(templateFields)) { + if (key.startsWith('header_')) { + headerParameters.push({ type: 'text', text: value }); + } else if (key.startsWith('body_')) { + bodyParameters.push({ type: 'text', text: value }); + } else if (key.startsWith('button_')) { + buttonParameters.push({ type: 'text', text: value }); + } + } + + if (headerParameters.length) { + components.push({ + type: 'header', + parameters: headerParameters, + }); + } + if (bodyParameters.length) { + components.push({ + type: 'body', + parameters: bodyParameters, + }); + } + if (buttonParameters.length) { + components.push({ + type: 'button', + sub_type: 'url', + index: 0, + parameters: buttonParameters, + }); + } + + // fetch template language code + const templateData = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://graph.facebook.com/v20.0/${templateId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + queryParams: { + fields: 'id,name,language', + }, + }); + + const templateLanguage = templateData.body['language']; + const templateName = templateData.body['name']; + + // https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-message-templates/#text-based + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://graph.facebook.com/v20.0/${phoneNumberId}/messages`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + body: { + messaging_product: 'whatsapp', + recipient_type: 'individual', + to: recipientPhoneNumber, + type: 'template', + template: { + name: templateName, + language: { + code: templateLanguage, + }, + components, + }, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/whatsapp/src/lib/actions/send-media.ts b/packages/pieces/community/whatsapp/src/lib/actions/send-media.ts new file mode 100644 index 0000000..75ac0bf --- /dev/null +++ b/packages/pieces/community/whatsapp/src/lib/actions/send-media.ts @@ -0,0 +1,76 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { whatsappAuth } from '../..'; +import { + supportedMediaTypes, + capitalizeFirstLetter, + mediaTypeSupportsCaption, + commonProps, +} from '../common/utils'; + +export const sendMedia = createAction({ + auth: whatsappAuth, + name: 'sendMedia', + displayName: 'Send Media', + description: 'Send a media message through WhatsApp', + props: { + phone_number_id: commonProps.phone_number_id, + to: Property.ShortText({ + displayName: 'To', + description: 'The recipient of the message', + required: true, + }), + type: Property.Dropdown({ + displayName: 'Type', + description: 'The type of media to send', + required: true, + options: async () => { + return { + options: supportedMediaTypes.map((type) => ({ + label: capitalizeFirstLetter(type), + value: type, + })), + }; + }, + refreshers: [], + }), + media: Property.ShortText({ + displayName: 'Media URL', + description: 'The URL of the media to send', + required: true, + }), + caption: Property.LongText({ + displayName: 'Caption', + description: 'A caption for the media', + required: false, + }), + filename: Property.LongText({ + displayName: 'Filename', + description: 'Filename of the document to send', + required: false, + }), + }, + async run(context) { + const { to, caption, media, type, filename, phone_number_id } = context.propsValue; + const { access_token } = context.auth; + const body = { + messaging_product: 'whatsapp', + recipient_type: 'individual', + to, + type, + [type]: { + link: media, + }, + }; + if (caption && mediaTypeSupportsCaption(type)) (body[type] as any).caption = caption; + if (filename && type === 'document') (body[type] as any).filename = filename; + return await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://graph.facebook.com/v17.0/${phone_number_id}/messages`, + headers: { + Authorization: 'Bearer ' + access_token, + }, + body, + }); + }, +}); diff --git a/packages/pieces/community/whatsapp/src/lib/actions/send-message.ts b/packages/pieces/community/whatsapp/src/lib/actions/send-message.ts new file mode 100644 index 0000000..cca72fd --- /dev/null +++ b/packages/pieces/community/whatsapp/src/lib/actions/send-message.ts @@ -0,0 +1,44 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { whatsappAuth } from '../..'; +import { commonProps } from '../common/utils'; + +export const sendMessage = createAction({ + auth: whatsappAuth, + name: 'sendMessage', + displayName: 'Send Message', + description: 'Send a text message through WhatsApp', + props: { + phone_number_id: commonProps.phone_number_id, + to: Property.ShortText({ + displayName: 'To', + description: 'The recipient of the message', + required: true, + }), + text: Property.LongText({ + displayName: 'Message', + description: 'The message to send', + required: true, + }), + }, + async run(context) { + const { to, text, phone_number_id } = context.propsValue; + const { access_token } = context.auth; + return await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://graph.facebook.com/v17.0/${phone_number_id}/messages`, + headers: { + Authorization: 'Bearer ' + access_token, + }, + body: { + messaging_product: 'whatsapp', + recipient_type: 'individual', + to, + type: 'text', + text: { + body: text, + }, + }, + }); + }, +}); diff --git a/packages/pieces/community/whatsapp/src/lib/common/utils.ts b/packages/pieces/community/whatsapp/src/lib/common/utils.ts new file mode 100644 index 0000000..85680e9 --- /dev/null +++ b/packages/pieces/community/whatsapp/src/lib/common/utils.ts @@ -0,0 +1,222 @@ +import { + AuthenticationType, + httpClient, + HttpMethod, + QueryParams, +} from '@activepieces/pieces-common'; +import { whatsappAuth } from '../../'; +import { + Property, + PiecePropValueSchema, + DynamicPropsValue, + DropdownOption, +} from '@activepieces/pieces-framework'; + +export const supportedMediaTypes = ['image', 'audio', 'document', 'sticker', 'video']; +export const capitalizeFirstLetter = (word: string) => word.charAt(0).toUpperCase() + word.slice(1); +export const mediaTypeSupportsCaption = (type: string) => + ['image', 'video', 'document'].includes(type); + +export const commonProps = { + phone_number_id: Property.Dropdown({ + displayName: 'Phone Number ID', + description: 'Phone number ID that will be used to send the message.', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + placeholder: 'Please connect account first', + disabled: true, + options: [], + }; + } + + const authValue = auth as PiecePropValueSchema; + + const options: DropdownOption[] = []; + + let hasMore = false; + let cursor; + + do { + const qs: QueryParams = { + fields: 'verified_name,id,display_phone_number', + limit: '1', + }; + if (cursor) qs['after'] = cursor; + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://graph.facebook.com/v20.0/${authValue.businessAccountId}/phone_numbers`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authValue.access_token, + }, + queryParams: qs, + }); + + for (const phoneNumber of response.body.data) { + options.push({ + label: `${phoneNumber.verified_name as string} : ${ + phoneNumber.display_phone_number as string + }`, + value: phoneNumber.id as string, + }); + } + + if (response.body.paging.next) { + (hasMore = true), (cursor = response.body.paging.cursors.after); + } else { + hasMore = false; + } + } while (hasMore); + + return { + disabled: false, + options, + }; + }, + }), + message_template_id: Property.Dropdown({ + displayName: 'Message Template ID', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + placeholder: 'Please connect account first', + disabled: true, + options: [], + }; + } + + const authValue = auth as PiecePropValueSchema; + + const options: DropdownOption[] = []; + + let hasMore = false; + let cursor; + + do { + const qs: QueryParams = { + fields: 'id,name,language', + limit: '1', + }; + if (cursor) qs['after'] = cursor; + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://graph.facebook.com/v20.0/${authValue.businessAccountId}/message_templates`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authValue.access_token, + }, + queryParams: qs, + }); + + for (const template of response.body.data) { + options.push({ + label: `${template.name as string} (${template.language as string})`, + value: template.id as string, + }); + } + + if (response.body.paging.next) { + (hasMore = true), (cursor = response.body.paging.cursors.after); + } else { + hasMore = false; + } + } while (hasMore); + + return { + disabled: false, + options, + }; + }, + }), + + message_template_fields: Property.DynamicProperties({ + displayName: 'Template Fields', + refreshers: ['message_template_id'], + required: true, + props: async ({ auth, message_template_id }) => { + if (!auth) return {}; + if (!message_template_id) return {}; + + const authValue = auth as PiecePropValueSchema; + const templateId = message_template_id as unknown as string; + + const response = await httpClient.sendRequest({ + url: `https://graph.facebook.com/v20.0/${templateId}`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authValue.access_token, + }, + }); + + const bodyComponentFields: DynamicPropsValue = {}; + const headerComponentFields: DynamicPropsValue = {}; + const buttonComponentFields: DynamicPropsValue = {}; + + for (const component of response.body.components) { + if (component.type === 'BODY') { + // https://developers.facebook.com/docs/whatsapp/business-management-api/message-templates/components#syntax + bodyComponentFields['BODY_markdown'] = Property.MarkDown({ + value: ` + **Body :** + ${component.text}`, + }); + + const bodyTextVariables = component.text?.match(/{{(\d+)}}/g) ?? []; + + for (let i = 0; i < bodyTextVariables.length; i++) { + bodyComponentFields[`body_{{${i + 1}}}`] = Property.ShortText({ + displayName: `Body {{${i + 1}}}`, + required: false, + }); + } + } else if (component.type === 'HEADER' && component.format === 'TEXT') { + // https://developers.facebook.com/docs/whatsapp/business-management-api/message-templates/components#text-headers + headerComponentFields['HEADER_markdown'] = Property.MarkDown({ + value: ` + **Header :** + ${component.text}`, + }); + + const headerTextVariables = component.text?.match(/{{(\d+)}}/g) ?? []; + + for (let i = 0; i < headerTextVariables.length; i++) { + headerComponentFields[`header_{{${i + 1}}}`] = Property.ShortText({ + displayName: `Header {{${i + 1}}}`, + required: false, + }); + } + } else if (component.type === 'BUTTONS') { + // https://developers.facebook.com/docs/whatsapp/business-management-api/message-templates/components#url-buttons + for (const button of component.buttons) { + if (button.type === 'URL') { + const buttonURLTextVariables = button.url?.match(/{{(\d+)}}/g) ?? []; + + for (let i = 0; i < buttonURLTextVariables.length; i++) { + buttonComponentFields[`button_{{${i + 1}}}`] = Property.ShortText({ + displayName: button.text, + required: false, + }); + } + } + } + } + } + + const templateFields: DynamicPropsValue = { + ...headerComponentFields, + ...bodyComponentFields, + ...buttonComponentFields, + }; + + return templateFields; + }, + }), +}; diff --git a/packages/pieces/community/whatsapp/tsconfig.json b/packages/pieces/community/whatsapp/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/whatsapp/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/whatsapp/tsconfig.lib.json b/packages/pieces/community/whatsapp/tsconfig.lib.json new file mode 100644 index 0000000..0fe2205 --- /dev/null +++ b/packages/pieces/community/whatsapp/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts", "src/lib/actions/common/utils.ts"] +} diff --git a/packages/pieces/community/woocommerce/.eslintrc.json b/packages/pieces/community/woocommerce/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/woocommerce/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/woocommerce/README.md b/packages/pieces/community/woocommerce/README.md new file mode 100644 index 0000000..bba95fc --- /dev/null +++ b/packages/pieces/community/woocommerce/README.md @@ -0,0 +1,7 @@ +# pieces-woocommerce + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-woocommerce` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/woocommerce/package.json b/packages/pieces/community/woocommerce/package.json new file mode 100644 index 0000000..92471dd --- /dev/null +++ b/packages/pieces/community/woocommerce/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-woocommerce", + "version": "0.0.9" +} \ No newline at end of file diff --git a/packages/pieces/community/woocommerce/project.json b/packages/pieces/community/woocommerce/project.json new file mode 100644 index 0000000..442c2fb --- /dev/null +++ b/packages/pieces/community/woocommerce/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-woocommerce", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/woocommerce/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/woocommerce", + "tsConfig": "packages/pieces/community/woocommerce/tsconfig.lib.json", + "packageJson": "packages/pieces/community/woocommerce/package.json", + "main": "packages/pieces/community/woocommerce/src/index.ts", + "assets": [ + "packages/pieces/community/woocommerce/*.md", + { + "input": "packages/pieces/community/woocommerce/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/woocommerce/src/index.ts b/packages/pieces/community/woocommerce/src/index.ts new file mode 100644 index 0000000..1bdefb9 --- /dev/null +++ b/packages/pieces/community/woocommerce/src/index.ts @@ -0,0 +1,88 @@ +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; + +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { PieceCategory } from '@activepieces/shared'; +import { wooCreateCoupon } from './lib/actions/create-coupon'; +import { wooCreateCustomer } from './lib/actions/create-customer'; +import { wooCreateProduct } from './lib/actions/create-product'; +import { wooFindCustomer } from './lib/actions/find-customer'; +import { wooFindProduct } from './lib/actions/find-product'; +import { triggers } from './lib/triggers'; + +const authDescription = ` +To generate your API credentials, follow the steps below: +1. Go to WooCommerce -> Settings -> Advanced tab -> REST API. +2. Click on Add Key to create a new key. +3. Enter the key description and change the permissions to Read/Write. +4. Click Generate Key. +5. Copy the Consumer Key and Consumer Secret into the fields below. You will not be able to view the Consumer Secret after exiting the page. + +Note that the base URL of your WooCommerce instance needs to be on a secure (HTTPS) connection, or the piece will not work even on local instances on the same device. +`; + +export const wooAuth = PieceAuth.CustomAuth({ + description: authDescription, + required: true, + props: { + baseUrl: Property.ShortText({ + displayName: 'Base URL', + description: + 'The base URL of your app (e.g https://mystore.com) and it should start with HTTPS only', + required: true, + }), + consumerKey: Property.ShortText({ + displayName: 'Consumer Key', + description: 'The consumer key generated from your app', + required: true, + }), + consumerSecret: PieceAuth.SecretText({ + displayName: 'Consumer Secret', + description: 'The consumer secret generated from your app', + required: true, + }), + }, + async validate({ auth }) { + const baseUrl = auth.baseUrl; + if (!baseUrl.match(/^(https):\/\//)) { + return { + valid: false, + error: 'Base URL must start with https (e.g https://mystore.com)', + }; + } + return { valid: true }; + }, +}); + +export const woocommerce = createPiece({ + displayName: 'WooCommerce', + description: 'E-commerce platform built on WordPress', + + logoUrl: 'https://cdn.activepieces.com/pieces/woocommerce.png', + categories: [PieceCategory.COMMERCE], + auth: wooAuth, + minimumSupportedRelease: '0.30.0', + authors: ["TaskMagicKyle","kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + actions: [ + wooCreateCustomer, + wooCreateCoupon, + wooCreateProduct, + wooFindCustomer, + wooFindProduct, + createCustomApiCallAction({ + baseUrl: (auth) => (auth as { baseUrl: string }).baseUrl, + auth: wooAuth, + authMapping: async (auth) => ({ + Authorization: `Basic ${Buffer.from( + `${(auth as { consumerKey: string }).consumerKey}:${ + (auth as { consumerSecret: string }).consumerSecret + }` + ).toString('base64')}`, + }), + }), + ], + triggers: triggers, +}); diff --git a/packages/pieces/community/woocommerce/src/lib/actions/create-coupon.ts b/packages/pieces/community/woocommerce/src/lib/actions/create-coupon.ts new file mode 100644 index 0000000..7315c46 --- /dev/null +++ b/packages/pieces/community/woocommerce/src/lib/actions/create-coupon.ts @@ -0,0 +1,96 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, + AuthenticationType, + propsValidation, +} from '@activepieces/pieces-common'; +import { z } from 'zod'; + +import { wooAuth } from '../..'; + +export const wooCreateCoupon = createAction({ + name: 'Create Coupon', + displayName: 'Create Coupon', + description: 'Create a coupon', + auth: wooAuth, + props: { + code: Property.ShortText({ + displayName: 'Coupon code', + description: 'Enter the coupon code', + required: true, + }), + discount_type: Property.StaticDropdown({ + displayName: 'Discount type', + description: 'Select the discount type', + required: true, + options: { + options: [ + { + label: 'Fixed cart', + value: 'fixed_cart', + }, + { + label: 'Fixed product', + value: 'fixed_product', + }, + { + label: 'Percent', + value: 'percent', + }, + { + label: 'Percent product', + value: 'percent_product', + }, + ], + }, + }), + amount: Property.Number({ + displayName: 'Amount', + description: 'Enter the amount', + required: true, + }), + minimum_amount: Property.Number({ + displayName: 'Minimum amount', + description: 'Enter the minimum amount', + required: true, + }), + }, + async run(configValue) { + await propsValidation.validateZod(configValue.propsValue, { + minimum_amount: z.number().min(0), + }); + + const trimmedBaseUrl = configValue.auth.baseUrl.replace(/\/$/, ''); + const amount = configValue.propsValue['amount'] || 0; + const code = configValue.propsValue['code']; + const discount_type = configValue.propsValue['discount_type']; + const minimum_amount = configValue.propsValue['minimum_amount'] || 0; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${trimmedBaseUrl}/wp-json/wc/v3/coupons`, + authentication: { + type: AuthenticationType.BASIC, + username: configValue.auth.consumerKey, + password: configValue.auth.consumerSecret, + }, + body: { + code, + discount_type, + amount, + individual_use: true, + exclude_sale_items: true, + minimum_amount, + }, + }; + + const res = await httpClient.sendRequest(request); + + return res.body; + }, +}); diff --git a/packages/pieces/community/woocommerce/src/lib/actions/create-customer.ts b/packages/pieces/community/woocommerce/src/lib/actions/create-customer.ts new file mode 100644 index 0000000..e451815 --- /dev/null +++ b/packages/pieces/community/woocommerce/src/lib/actions/create-customer.ts @@ -0,0 +1,117 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, + AuthenticationType, +} from '@activepieces/pieces-common'; + +import { wooAuth } from '../..'; + +export const wooCreateCustomer = createAction({ + name: 'Create Customer', + displayName: 'Create Customer', + description: 'Create a Customer', + auth: wooAuth, + props: { + email: Property.ShortText({ + displayName: 'Email', + description: 'Enter the email', + required: true, + }), + first_name: Property.ShortText({ + displayName: 'First name', + description: 'Enter the first name', + required: true, + }), + last_name: Property.ShortText({ + displayName: 'Last name', + description: 'Enter the last name', + required: true, + }), + username: Property.ShortText({ + displayName: 'Username', + description: 'Enter the username', + required: true, + }), + password: Property.ShortText({ + displayName: 'Password', + description: 'Enter the password', + required: true, + }), + street_address: Property.ShortText({ + displayName: 'Address', + description: 'Enter the street address', + required: true, + }), + city: Property.ShortText({ + displayName: 'City', + description: 'Enter the city', + required: true, + }), + state: Property.ShortText({ + displayName: 'State', + description: 'Enter the state', + required: true, + }), + postcode: Property.ShortText({ + displayName: 'Postcode', + description: 'Enter the postcode', + required: true, + }), + country: Property.ShortText({ + displayName: 'Country', + description: 'Enter the country', + required: true, + }), + phone: Property.ShortText({ + displayName: 'Phone', + description: 'Enter the phone', + required: true, + }), + }, + async run(configValue) { + const trimmedBaseUrl = configValue.auth.baseUrl.replace(/\/$/, ''); + + const email = configValue.propsValue['email']; + const first_name = configValue.propsValue['first_name']; + const last_name = configValue.propsValue['last_name']; + const username = configValue.propsValue['username']; + const password = configValue.propsValue['password']; + + const billing = { + first_name, + last_name, + address_1: configValue.propsValue['street_address'], + city: configValue.propsValue['city'], + state: configValue.propsValue['state'], + postcode: configValue.propsValue['postcode'], + country: configValue.propsValue['country'], + email, + phone: configValue.propsValue['phone'], + }; + + const request: HttpRequest = { + url: `${trimmedBaseUrl}//wp-json/wc/v3/customers`, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BASIC, + username: configValue.auth.consumerKey, + password: configValue.auth.consumerSecret, + }, + body: { + email, + first_name, + last_name, + username, + password, + billing, + shipping: billing, + }, + }; + + const res = await httpClient.sendRequest(request); + + return res.body; + }, +}); diff --git a/packages/pieces/community/woocommerce/src/lib/actions/create-product.ts b/packages/pieces/community/woocommerce/src/lib/actions/create-product.ts new file mode 100644 index 0000000..fe5846a --- /dev/null +++ b/packages/pieces/community/woocommerce/src/lib/actions/create-product.ts @@ -0,0 +1,116 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + AuthenticationType, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; + +import { wooAuth } from '../..'; + +export const wooCreateProduct = createAction({ + name: 'Create Product', + displayName: 'Create Product', + description: 'Create a Product', + auth: wooAuth, + props: { + name: Property.ShortText({ + displayName: 'Name', + description: 'Enter the name', + required: true, + }), + type: Property.StaticDropdown({ + displayName: 'Type', + description: 'Select the type', + required: true, + options: { + options: [ + { + label: 'Simple', + value: 'simple', + }, + { + label: 'Grouped', + value: 'grouped', + }, + { + label: 'External', + value: 'external', + }, + { + label: 'Variable', + value: 'variable', + }, + ], + }, + }), + regular_price: Property.Number({ + displayName: 'Regular price', + description: 'Enter the regular price', + required: true, + }), + description: Property.LongText({ + displayName: 'Description', + description: 'Enter the description', + required: true, + }), + short_description: Property.LongText({ + displayName: 'Short description', + description: 'Enter the short description', + required: true, + }), + categories: Property.ShortText({ + displayName: 'Categories', + description: 'Enter the category IDs (comma separated)', + required: true, + }), + images: Property.LongText({ + displayName: 'Images', + description: + 'Enter the URLs of images you want to upload (comma separated)', + required: true, + }), + }, + async run(configValue) { + const trimmedBaseUrl = configValue.auth.baseUrl.replace(/\/$/, ''); + + const name = configValue.propsValue['name']; + const type = configValue.propsValue['type']; + const regular_price = configValue.propsValue['regular_price']; + const description = configValue.propsValue['description']; + const short_description = configValue.propsValue['short_description']; + const categories = + configValue.propsValue['categories'].split(',').map((id) => ({ + id, + })) || []; + const images = + configValue.propsValue['images'].split(',').map((url) => ({ + src: url, + })) || []; + + const body = { + name, + type, + regular_price, + description, + short_description, + categories, + images, + }; + + const request: HttpRequest = { + url: `${trimmedBaseUrl}/wp-json/wc/v3/products`, + method: HttpMethod.POST, + authentication: { + type: AuthenticationType.BASIC, + username: configValue.auth.consumerKey, + password: configValue.auth.consumerSecret, + }, + body, + }; + + const res = await httpClient.sendRequest(request); + + return res.body; + }, +}); diff --git a/packages/pieces/community/woocommerce/src/lib/actions/find-coupon.ts b/packages/pieces/community/woocommerce/src/lib/actions/find-coupon.ts new file mode 100644 index 0000000..5a98a69 --- /dev/null +++ b/packages/pieces/community/woocommerce/src/lib/actions/find-coupon.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, + AuthenticationType, +} from '@activepieces/pieces-common'; + +import { wooAuth } from '../..'; + +export const wooFindCoupon = createAction({ + name: 'Find Coupon', + displayName: 'Find Coupon', + description: 'Find a Coupon', + auth: wooAuth, + props: { + id: Property.ShortText({ + displayName: 'Coupon ID', + description: 'Enter the coupon ID', + required: true, + }), + }, + async run(configValue) { + const trimmedBaseUrl = configValue.auth.baseUrl.replace(/\/$/, ''); + const couponId = configValue.propsValue['id']; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${trimmedBaseUrl}/wp-json/wc/v3/coupons/${couponId}`, + authentication: { + type: AuthenticationType.BASIC, + username: configValue.auth.consumerKey, + password: configValue.auth.consumerSecret, + }, + }; + + const res = await httpClient.sendRequest(request); + + return res.body; + }, +}); diff --git a/packages/pieces/community/woocommerce/src/lib/actions/find-customer.ts b/packages/pieces/community/woocommerce/src/lib/actions/find-customer.ts new file mode 100644 index 0000000..6009a5d --- /dev/null +++ b/packages/pieces/community/woocommerce/src/lib/actions/find-customer.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + httpClient, + AuthenticationType, +} from '@activepieces/pieces-common'; + +import { wooAuth } from '../..'; + +export const wooFindCustomer = createAction({ + name: 'Find Customer', + displayName: 'Find Customer', + description: 'Find a Customer', + auth: wooAuth, + props: { + email: Property.ShortText({ + displayName: 'Email', + description: 'Enter the email', + required: true, + }), + }, + async run(configValue) { + const trimmedBaseUrl = configValue.auth.baseUrl.replace(/\/$/, ''); + const email = configValue.propsValue['email']; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${trimmedBaseUrl}/wp-json/wc/v3/customers?email=${email}`, + authentication: { + type: AuthenticationType.BASIC, + username: configValue.auth.consumerKey, + password: configValue.auth.consumerSecret, + }, + }; + + const res = await httpClient.sendRequest(request); + + return res.body; + }, +}); diff --git a/packages/pieces/community/woocommerce/src/lib/actions/find-product.ts b/packages/pieces/community/woocommerce/src/lib/actions/find-product.ts new file mode 100644 index 0000000..649821b --- /dev/null +++ b/packages/pieces/community/woocommerce/src/lib/actions/find-product.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + AuthenticationType, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; + +import { wooAuth } from '../..'; + +export const wooFindProduct = createAction({ + name: 'Find Product', + displayName: 'Find Product', + description: 'Find a Product', + auth: wooAuth, + props: { + id: Property.ShortText({ + displayName: 'Product ID', + description: 'Enter the product ID', + required: true, + }), + }, + async run(configValue) { + const trimmedBaseUrl = configValue.auth.baseUrl.replace(/\/$/, ''); + const productId = configValue.propsValue['id']; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${trimmedBaseUrl}/wp-json/wc/v3/products/${productId}`, + authentication: { + type: AuthenticationType.BASIC, + username: configValue.auth.consumerKey, + password: configValue.auth.consumerSecret, + }, + }; + + const res = await httpClient.sendRequest(request); + + return res.body; + }, +}); diff --git a/packages/pieces/community/woocommerce/src/lib/common/index.ts b/packages/pieces/community/woocommerce/src/lib/common/index.ts new file mode 100644 index 0000000..e3db9d2 --- /dev/null +++ b/packages/pieces/community/woocommerce/src/lib/common/index.ts @@ -0,0 +1,63 @@ +import { + AuthenticationType, + HttpMethod, + httpClient, +} from '@activepieces/pieces-common'; +import { PiecePropValueSchema } from '@activepieces/pieces-framework'; +import { wooAuth } from '../../'; + +export const wooCommon = { + async createWebhook( + name: string, + webhookUrl: string, + topic: string, + auth: PiecePropValueSchema + ) { + const trimmedBaseUrl = auth.baseUrl.replace(/\/$/, ''); + return await httpClient.sendRequest({ + url: `${trimmedBaseUrl}/wp-json/wc/v3/webhooks`, + method: HttpMethod.POST, + body: { + name: name, + topic: topic, + delivery_url: webhookUrl, + }, + authentication: { + type: AuthenticationType.BASIC, + username: auth.consumerKey, + password: auth.consumerSecret, + }, + }); + }, + async deleteWebhook( + webhookId: number, + auth: PiecePropValueSchema + ) { + const trimmedBaseUrl = auth.baseUrl.replace(/\/$/, ''); + return await httpClient.sendRequest({ + url: `${trimmedBaseUrl}/wp-json/wc/v3/webhooks/${webhookId}`, + method: HttpMethod.DELETE, + queryParams: { force: 'true' }, + authentication: { + type: AuthenticationType.BASIC, + username: auth.consumerKey, + password: auth.consumerSecret, + }, + }); + }, +}; + +export interface WebhookInformation { + id: number; + name: string; + status: string; + topic: string; + resource: string; + event: string; + hooks: string[]; + delivery_url: string; + date_created: string; + date_created_gmt: string; + date_modified: string; + date_modified_gmt: string; +} diff --git a/packages/pieces/community/woocommerce/src/lib/triggers/index.ts b/packages/pieces/community/woocommerce/src/lib/triggers/index.ts new file mode 100644 index 0000000..7f0e677 --- /dev/null +++ b/packages/pieces/community/woocommerce/src/lib/triggers/index.ts @@ -0,0 +1,408 @@ +import { woocommerceRegisterTrigger } from './register-trigger'; + +const sampleData = { + product: { + id: 20, + sku: '', + name: 'My Product', + slug: 'my-product', + tags: [], + type: 'simple', + price: '5', + _links: { + self: [ + { + href: 'https://myshop.com/index.php?rest_route=/wc/v3/products/20', + }, + ], + collection: [ + { + href: 'https://myshop.com/index.php?rest_route=/wc/v3/products', + }, + ], + }, + images: [], + description: '

Description bold

\n

New Line

\n', + date_created: '2023-07-06T14:51:45', + date_modified: '2023-07-06T14:51:45', + total_sales: 0, + stock_status: 'instock', + rating_count: 0, + status: 'publish', + weight: '', + on_sale: false, + virtual: false, + featured: false, + downloads: [], + meta_data: [], + parent_id: 0, + permalink: 'https://myshop.com/?product=my-product', + tax_class: '', + attributes: [], + backorders: 'no', + categories: [ + { + id: 15, + name: 'Uncategorized', + slug: 'uncategorized', + }, + ], + dimensions: { + width: '', + height: '', + length: '', + }, + menu_order: 0, + price_html: + '5,000 د.ا', + sale_price: '', + tax_status: 'taxable', + upsell_ids: [], + variations: [], + backordered: false, + button_text: '', + has_options: false, + purchasable: true, + related_ids: [12], + downloadable: false, + external_url: '', + manage_stock: false, + purchase_note: '', + regular_price: '5', + average_rating: '0.00', + cross_sell_ids: [], + download_limit: -1, + shipping_class: '', + stock_quantity: null, + date_on_sale_to: null, + download_expiry: -1, + reviews_allowed: true, + date_created_gmt: '2023-07-06T14:51:45', + grouped_products: [], + low_stock_amount: null, + shipping_taxable: true, + date_modified_gmt: '2023-07-06T14:51:45', + date_on_sale_from: null, + shipping_class_id: 0, + shipping_required: true, + short_description: '', + sold_individually: false, + backorders_allowed: false, + catalog_visibility: 'visible', + default_attributes: [], + date_on_sale_to_gmt: null, + date_on_sale_from_gmt: null, + }, + order: { + id: 17, + total: '2.000', + _links: { + self: [ + { + href: 'https://myshop.com/index.php?rest_route=/wc/v3/orders/17', + }, + ], + customer: [ + { + href: 'https://myshop.com/index.php?rest_route=/wc/v3/customers/1', + }, + ], + collection: [ + { + href: 'https://myshop.com/index.php?rest_route=/wc/v3/orders', + }, + ], + }, + number: '17', + status: 'pending', + shipping_total: '0.000', + currency_symbol: '$', + date_created_gmt: '2023-07-06T14:17:03', + payment_method_title: 'Cash on delivery', + billing: { + city: 'City', + email: 'email@gmail.com', + phone: '123123123', + state: 'State', + company: '', + country: 'CO', + postcode: '11111', + address_1: '1 Street', + address_2: '', + last_name: 'Last', + first_name: 'First', + }, + refunds: [], + version: '7.8.2', + cart_tax: '0.000', + currency: 'USD', + shipping: { + city: 'City', + phone: '', + state: 'State', + company: '', + country: 'CO', + postcode: '11111', + address_1: '1 Street', + address_2: '', + last_name: 'Last', + first_name: 'First', + }, + date_paid: null, + fee_lines: [], + meta_data: [ + { + id: 228, + key: 'is_vat_exempt', + value: 'no', + }, + ], + order_key: 'wc_order_C66uDC3RekAax', + parent_id: 0, + tax_lines: [], + total_tax: '0.000', + line_items: [ + { + id: 9, + sku: '', + name: 'First Product', + image: { + id: '', + src: '', + }, + price: 1, + taxes: [], + total: '2.000', + quantity: 2, + subtotal: '2.000', + meta_data: [], + tax_class: '', + total_tax: '0.000', + product_id: 12, + parent_name: null, + subtotal_tax: '0.000', + variation_id: 0, + }, + ], + created_via: 'checkout', + customer_id: 1, + is_editable: false, + payment_url: + 'https://myshop.com/?page_id=8&order-pay=17&pay_for_order=true&key=wc_order_C66uDC3RekAax', + coupon_lines: [], + date_created: '2023-07-06T14:17:03', + discount_tax: '0.000', + shipping_tax: '0.000', + customer_note: '', + date_modified: '2023-07-06T14:25:02', + date_paid_gmt: null, + needs_payment: true, + date_completed: null, + discount_total: '0.000', + payment_method: 'cod', + shipping_lines: [ + { + id: 10, + taxes: [], + total: '0.000', + meta_data: [ + { + id: 75, + key: 'Items', + value: 'First Product × 2', + display_key: 'Items', + display_value: 'First Product × 2', + }, + ], + method_id: 'free_shipping', + total_tax: '0.000', + instance_id: '1', + method_title: 'Free shipping', + }, + ], + transaction_id: '', + needs_processing: true, + date_modified_gmt: '2023-07-06T14:25:02', + prices_include_tax: false, + }, + coupon: { + id: 22, + code: '5dollars', + _links: { + self: [ + { + href: 'https://myshop.com/index.php?rest_route=/wc/v3/coupons/22', + }, + ], + collection: [ + { + href: 'https://myshop.com/index.php?rest_route=/wc/v3/coupons', + }, + ], + }, + amount: '5.00', + status: 'publish', + used_by: [], + meta_data: [], + description: '', + product_ids: [20], + usage_count: 0, + usage_limit: null, + date_created: '2023-07-09T15:10:14', + date_expires: '2023-07-31T00:00:00', + date_modified: '2023-07-09T15:23:03', + discount_type: 'fixed_cart', + free_shipping: true, + maximum_amount: '0.00', + minimum_amount: '0.00', + date_created_gmt: '2023-07-09T15:10:14', + date_expires_gmt: '2023-07-31T00:00:00', + date_modified_gmt: '2023-07-09T15:23:03', + usage_limit_per_user: 1, + individual_use: false, + email_restrictions: [], + exclude_sale_items: false, + product_categories: [], + excluded_product_ids: [], + limit_usage_to_x_items: null, + excluded_product_categories: [], + }, + customer: { + id: 1, + role: 'administrator', + email: 'email@gmail.com', + avatar_url: '', + username: 'username', + first_name: 'First', + last_name: 'Last', + date_created: '2023-07-05T14:13:10', + date_modified: '2023-07-06T14:58:43', + date_created_gmt: '2023-07-05T14:13:10', + date_modified_gmt: '2023-07-06T14:58:43', + is_paying_customer: false, + _links: { + self: [ + { + href: 'https://myshop.com/index.php?rest_route=/wc/v3/customers/1', + }, + ], + collection: [ + { + href: 'https://myshop.com/index.php?rest_route=/wc/v3/customers', + }, + ], + }, + billing: { + city: 'City', + email: 'email@gmail.com', + phone: '123123123', + state: 'State', + company: '', + country: 'CO', + postcode: '11111', + address_1: '# Street', + address_2: '', + last_name: 'Last', + first_name: 'First', + }, + shipping: { + city: 'City', + email: 'email@gmail.com', + phone: '123123123', + state: 'State', + company: '', + country: 'CO', + postcode: '11111', + address_1: '# Street', + address_2: '', + last_name: 'Last', + first_name: 'First', + }, + }, +}; +export const triggers = [ + { + name: 'product_created', + topic: 'product.created', + displayName: 'Product Created', + description: 'Triggers when new product is created.', + sampleData: sampleData.product, + }, + { + name: 'product_updated', + topic: 'product.updated', + displayName: 'Product Updated', + description: 'Triggers when an existing product is updated.', + sampleData: sampleData.product, + }, + { + name: 'product_deleted', + topic: 'product.deleted', + displayName: 'Product Deleted', + description: 'Triggers when an existing product is deleted.', + sampleData: sampleData.product, + }, + { + name: 'order_created', + topic: 'order.created', + displayName: 'Order Created', + description: 'Triggers when new order is created.', + sampleData: sampleData.order, + }, + { + name: 'order_updated', + topic: 'order.updated', + displayName: 'Order Updated', + description: 'Triggers when an existing order is updated.', + sampleData: sampleData.order, + }, + { + name: 'order_deleted', + topic: 'order.deleted', + displayName: 'Order Deleted', + description: 'Triggers when an existing order is deleted.', + sampleData: sampleData.order, + }, + { + name: 'coupon_created', + topic: 'coupon.created', + displayName: 'Coupon Created', + description: 'Triggers when new coupon is created.', + sampleData: sampleData.coupon, + }, + { + name: 'coupon_updated', + topic: 'coupon.updated', + displayName: 'Coupon Updated', + description: 'Triggers when an existing coupon is updated.', + sampleData: sampleData.coupon, + }, + { + name: 'coupon_deleted', + topic: 'coupon.deleted', + displayName: 'Coupon Deleted', + description: 'Triggers when an existing coupon is deleted.', + sampleData: sampleData.coupon, + }, + { + name: 'customer_created', + topic: 'customer.created', + displayName: 'Customer Created', + description: 'Triggers when new customer is created.', + sampleData: sampleData.customer, + }, + { + name: 'customer_updated', + topic: 'customer.updated', + displayName: 'Customer Updated', + description: 'Triggers when an existing customer is updated.', + sampleData: sampleData.customer, + }, + { + name: 'customer_deleted', + topic: 'customer.deleted', + displayName: 'Customer Deleted', + description: 'Triggers when an existing customer is deleted.', + sampleData: sampleData.customer, + }, +].map((trigger) => woocommerceRegisterTrigger(trigger)); diff --git a/packages/pieces/community/woocommerce/src/lib/triggers/register-trigger.ts b/packages/pieces/community/woocommerce/src/lib/triggers/register-trigger.ts new file mode 100644 index 0000000..af4433c --- /dev/null +++ b/packages/pieces/community/woocommerce/src/lib/triggers/register-trigger.ts @@ -0,0 +1,67 @@ +import { + PiecePropValueSchema, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { wooAuth } from '../../'; +import { WebhookInformation, wooCommon } from '../common'; +import { WebhookHandshakeStrategy } from '@activepieces/shared'; +export const woocommerceRegisterTrigger = ({ + name, + topic, + displayName, + description, + sampleData, +}: { + name: string; + topic: string; + displayName: string; + description: string; + sampleData: unknown; +}) => + createTrigger({ + auth: wooAuth, + name: `$woocommerce_trigger_${name}`, + displayName, + description, + props: {}, + sampleData, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const res = await wooCommon.createWebhook( + displayName, + context.webhookUrl, + topic, + context.auth as PiecePropValueSchema + ); + await context.store.put( + `$woocommerce_trigger_${name}`, + res.body + ); + }, + async onDisable(context) { + const webhook = await context.store.get( + `$woocommerce_trigger_${name}` + ); + if (webhook != null) { + await wooCommon.deleteWebhook( + webhook.id, + context.auth as PiecePropValueSchema + ); + } + }, + // WooCommerce sends a request verifying the webhook that contains only the webhook_id. + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.BODY_PARAM_PRESENT, + paramName: 'webhook_id', + }, + async onHandshake(context) { + return { + status: 200, + body: { webhook_id: (context.payload.body as any)['webhook_id'] }, + }; + }, + async run(context) { + return [context.payload.body]; + }, + }); diff --git a/packages/pieces/community/woocommerce/tsconfig.json b/packages/pieces/community/woocommerce/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/woocommerce/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/woocommerce/tsconfig.lib.json b/packages/pieces/community/woocommerce/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/woocommerce/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/wootric/.eslintrc.json b/packages/pieces/community/wootric/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/wootric/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/wootric/README.md b/packages/pieces/community/wootric/README.md new file mode 100644 index 0000000..1eae3c2 --- /dev/null +++ b/packages/pieces/community/wootric/README.md @@ -0,0 +1,7 @@ +# pieces-wootric + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-wootric` to build the library. diff --git a/packages/pieces/community/wootric/package.json b/packages/pieces/community/wootric/package.json new file mode 100644 index 0000000..b4c6c3d --- /dev/null +++ b/packages/pieces/community/wootric/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-wootric", + "version": "0.0.3" +} \ No newline at end of file diff --git a/packages/pieces/community/wootric/project.json b/packages/pieces/community/wootric/project.json new file mode 100644 index 0000000..72cbbbd --- /dev/null +++ b/packages/pieces/community/wootric/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-wootric", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/wootric/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/wootric", + "tsConfig": "packages/pieces/community/wootric/tsconfig.lib.json", + "packageJson": "packages/pieces/community/wootric/package.json", + "main": "packages/pieces/community/wootric/src/index.ts", + "assets": [ + "packages/pieces/community/wootric/*.md", + { + "input": "packages/pieces/community/wootric/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-community-wootric {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/wootric/src/index.ts b/packages/pieces/community/wootric/src/index.ts new file mode 100644 index 0000000..6a06dd5 --- /dev/null +++ b/packages/pieces/community/wootric/src/index.ts @@ -0,0 +1,27 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { createWootricSurvey } from './lib/actions/create-survey'; +import { OAuth2GrantType } from '@activepieces/shared'; + +export const WOOTRIC_API_URL = 'https://api.wootric.com'; +export const WOOTRIC_IMAGE_URL = + 'https://assets-production.wootric.com/assets/wootric-is-now-inmoment-250x108-85cb4900c62ff4d33200abafee7d63372d410abc5bf0cab90e80a07d4f4e5a31.png'; + +export const wootricAuth = PieceAuth.OAuth2({ + required: true, + grantType: OAuth2GrantType.CLIENT_CREDENTIALS, + authUrl: '', + tokenUrl: `${WOOTRIC_API_URL}/oauth/token`, + scope: [], +}); + +export const wootric = createPiece({ + displayName: 'Wootric', + description: 'Measure and boost customer happiness', + + auth: wootricAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: WOOTRIC_IMAGE_URL, + authors: ["abuaboud"], + actions: [createWootricSurvey], + triggers: [], +}); diff --git a/packages/pieces/community/wootric/src/lib/actions/create-survey.ts b/packages/pieces/community/wootric/src/lib/actions/create-survey.ts new file mode 100644 index 0000000..76474fd --- /dev/null +++ b/packages/pieces/community/wootric/src/lib/actions/create-survey.ts @@ -0,0 +1,47 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wootricAuth, WOOTRIC_API_URL } from '../../'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const sendSurvey = async (surveyRequestPayload: object) => { + const EMAIL_SURVEY = `${WOOTRIC_API_URL}/v1/email_survey`; + return await httpClient.sendRequest({ + method: HttpMethod.POST, + url: EMAIL_SURVEY, + body: surveyRequestPayload, + }); +}; + +export const createWootricSurvey = createAction({ + name: 'trigger_wootric_survey', + auth: wootricAuth, + displayName: 'Trigger Wootric Survey', + description: 'Trigger a survey from Wootric', + props: { + emails: Property.Array({ + displayName: 'Emails', + description: 'End user emails, where you want the survey to be received', + required: true, + defaultValue: [], + }), + surveyImmediately: Property.Checkbox({ + displayName: 'Survey Immediately', + description: + 'Enter "true" to survey immediately to bypass checks, otherwise "false"', + required: true, + }), + }, + async run(context) { + const { surveyImmediately, emails } = context.propsValue; + const { access_token } = context.auth; + + const surveyRequestPayload = { + emails: emails, + survey_immediately: surveyImmediately, + access_token: access_token, + }; + + const surveyResponse = await sendSurvey(surveyRequestPayload); + + return surveyResponse.body; + }, +}); diff --git a/packages/pieces/community/wootric/tsconfig.json b/packages/pieces/community/wootric/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/wootric/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/wootric/tsconfig.lib.json b/packages/pieces/community/wootric/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/wootric/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/wordpress/.babelrc b/packages/pieces/community/wordpress/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/wordpress/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/wordpress/.eslintrc.json b/packages/pieces/community/wordpress/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/wordpress/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/wordpress/README.md b/packages/pieces/community/wordpress/README.md new file mode 100644 index 0000000..63b74a9 --- /dev/null +++ b/packages/pieces/community/wordpress/README.md @@ -0,0 +1,7 @@ +# pieces-wordpress + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-wordpress` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/wordpress/package.json b/packages/pieces/community/wordpress/package.json new file mode 100644 index 0000000..d219e9f --- /dev/null +++ b/packages/pieces/community/wordpress/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-wordpress", + "version": "0.3.14" +} \ No newline at end of file diff --git a/packages/pieces/community/wordpress/project.json b/packages/pieces/community/wordpress/project.json new file mode 100644 index 0000000..728fc6d --- /dev/null +++ b/packages/pieces/community/wordpress/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-wordpress", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/wordpress/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/wordpress", + "tsConfig": "packages/pieces/community/wordpress/tsconfig.lib.json", + "packageJson": "packages/pieces/community/wordpress/package.json", + "main": "packages/pieces/community/wordpress/src/index.ts", + "assets": [ + "packages/pieces/community/wordpress/*.md", + { + "input": "packages/pieces/community/wordpress/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/wordpress/src/index.ts b/packages/pieces/community/wordpress/src/index.ts new file mode 100644 index 0000000..f3cbc08 --- /dev/null +++ b/packages/pieces/community/wordpress/src/index.ts @@ -0,0 +1,134 @@ +import { + AuthenticationType, + HttpMethod, + HttpRequest, + createCustomApiCallAction, + httpClient, +} from '@activepieces/pieces-common'; +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createWordPressPage } from './lib/actions/create-page.action'; +import { createWordPressPost } from './lib/actions/create-post.action'; +import { getWordPressPost } from './lib/actions/get-post.action'; +import { wordpressCommon } from './lib/common'; +import { wordpressNewPost } from './lib/trigger/new-post.trigger'; +import { updateWordPressPost } from './lib/actions/update-post.action'; + +const markdownPropertyDescription = ` +**Enable Basic Authentication:** + +1. Download the plugin from: https://github.com/WP-API/Basic-Auth (Click on Code -> Download Zip) +2. Log in to your WordPress dashboard. +3. Go to "Plugins" and click "Add New." +4. Choose "Upload Plugin" and select the downloaded file. +5. Install and activate the plugin. + +`; + +export const wordpressAuth = PieceAuth.CustomAuth({ + description: markdownPropertyDescription, + required: true, + props: { + username: Property.ShortText({ + displayName: 'Username', + required: true, + }), + password: PieceAuth.SecretText({ + displayName: 'Password', + required: true, + }), + website_url: Property.ShortText({ + displayName: 'Website URL', + required: true, + description: + 'URL of the wordpress url i.e https://www.example-website.com', + }), + }, + validate: async ({ auth }) => { + const { username, password, website_url } = auth; + if (!username || !password || !website_url) { + return { + valid: false, + error: 'please fill all the fields [username, password, website_url] ', + }; + } + if (!wordpressCommon.isBaseUrl(website_url.trim())) { + return { + valid: false, + error: + 'Please ensure that the website is valid and does not contain any paths, for example, https://example-website.com.', + }; + } + const apiEnabled = await wordpressCommon.urlExists( + website_url.trim() + '/wp-json' + ); + if (!apiEnabled) { + return { + valid: false, + error: `REST API is not reachable, visit ${website_url.trim()}/wp-json" \n make sure your settings (Settings -> Permalinks) are set to "Post name" (or any option other than "Plain") and disable any security plugins that might block the REST API `, + }; + } + try { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${website_url}/wp-json/wp/v2/categories`, + authentication: { + type: AuthenticationType.BASIC, + username: username, + password: password, + }, + }; + await httpClient.sendRequest(request); + return { + valid: true, + }; + } catch (e: any) { + return { + valid: false, + error: 'Credentials are invalid. ' + e?.message, + }; + } + }, +}); + +export const wordpress = createPiece({ + displayName: 'WordPress', + description: 'Open-source website creation software', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/wordpress.png', + categories: [PieceCategory.MARKETING], + auth: wordpressAuth, + authors: [ + 'pfernandez98', + 'Salem-Alaa', + 'kishanprmr', + 'MoShizzle', + 'AbdulTheActivePiecer', + 'khaledmashaly', + 'abuaboud', + ], + actions: [ + createWordPressPost, + createWordPressPage, + updateWordPressPost, + getWordPressPost, + createCustomApiCallAction({ + baseUrl: (auth) => + (auth as { website_url: string }).website_url.trim() + '/wp-json/wp/v2', + auth: wordpressAuth, + authMapping: async (auth) => ({ + Authorization: `Basic ${Buffer.from( + `${(auth as { username: string }).username}:${ + (auth as { password: string }).password + }` + ).toString('base64')}`, + }), + }), + ], + triggers: [wordpressNewPost], +}); diff --git a/packages/pieces/community/wordpress/src/lib/actions/create-page.action.ts b/packages/pieces/community/wordpress/src/lib/actions/create-page.action.ts new file mode 100644 index 0000000..84c89d8 --- /dev/null +++ b/packages/pieces/community/wordpress/src/lib/actions/create-page.action.ts @@ -0,0 +1,104 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wordpressCommon } from '../common'; +import { + httpClient, + HttpMethod, + HttpRequest, + AuthenticationType, +} from '@activepieces/pieces-common'; +import { wordpressAuth } from '../..'; + +export const createWordPressPage = createAction({ + auth: wordpressAuth, + name: 'create_page', + description: 'Create new page on WordPress', + displayName: 'Create Page', + props: { + title: Property.ShortText({ + description: 'Title of the page about to be added', + displayName: 'Title', + required: true, + }), + content: Property.LongText({ + description: 'Uses the WordPress Text Editor which supports HTML', + displayName: 'Content', + required: true, + }), + slug: Property.ShortText({ + displayName: 'Slug', + required: false, + }), + date: Property.ShortText({ + description: 'Page publish date (ISO-8601)', + displayName: 'Date', + required: false, + }), + status: Property.StaticDropdown({ + description: 'Choose status', + displayName: 'Status', + required: false, + options: { + disabled: false, + options: [ + { value: 'publish', label: 'Published' }, + { value: 'future', label: 'Scheduled' }, + { value: 'draft', label: 'Draft' }, + { value: 'pending', label: 'Pending' }, + { value: 'private', label: 'Private' }, + ], + }, + }), + excerpt: Property.LongText({ + description: 'Uses the WordPress Text Editor which supports HTML', + displayName: 'Excerpt', + required: false, + }), + comment_status: Property.Checkbox({ + displayName: 'Enable Comments', + required: false, + }), + ping_status: Property.Checkbox({ + displayName: 'Open to Pinging', + required: false, + }), + }, + async run(context) { + if (!(await wordpressCommon.urlExists(context.auth.website_url.trim()))) { + throw new Error('Website url is invalid: ' + context.auth.website_url); + } + const requestBody: Record = {}; + if (context.propsValue.date) { + requestBody['date'] = context.propsValue.date; + } + if (context.propsValue.comment_status) { + requestBody['comment_status'] = context.propsValue.comment_status + ? 'open' + : 'closed'; + } + if (context.propsValue.slug) { + requestBody['slug'] = context.propsValue.slug; + } + if (context.propsValue.excerpt) { + requestBody['excerpt'] = context.propsValue.excerpt; + } + if (context.propsValue.status) { + requestBody['status'] = context.propsValue.status; + } + requestBody['content'] = context.propsValue.content; + requestBody['title'] = context.propsValue.title; + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${context.auth.website_url.trim()}/wp-json/wp/v2/pages`, + authentication: { + type: AuthenticationType.BASIC, + username: context.auth.username, + password: context.auth.password, + }, + body: requestBody, + }; + const response = await httpClient.sendRequest< + { id: string; name: string }[] + >(request); + return response; + }, +}); diff --git a/packages/pieces/community/wordpress/src/lib/actions/create-post.action.ts b/packages/pieces/community/wordpress/src/lib/actions/create-post.action.ts new file mode 100644 index 0000000..d14ca4f --- /dev/null +++ b/packages/pieces/community/wordpress/src/lib/actions/create-post.action.ts @@ -0,0 +1,141 @@ +import { + createAction, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { wordpressCommon, WordPressMedia } from '../common'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import FormData from 'form-data'; +import { wordpressAuth } from '../..'; + +export const createWordPressPost = createAction({ + auth: wordpressAuth, + name: 'create_post', + description: 'Create new post on WordPress', + displayName: 'Create Post', + props: { + title: Property.ShortText({ + description: 'Title of the post about to be added', + displayName: 'Title', + required: true, + }), + content: Property.LongText({ + description: 'Uses the WordPress Text Editor which supports HTML', + displayName: 'Content', + required: true, + }), + slug: Property.ShortText({ + displayName: 'Slug', + required: false, + }), + date: Property.ShortText({ + description: 'Post publish date (ISO-8601)', + displayName: 'Date', + required: false, + }), + featured_media_file: wordpressCommon.featured_media_file, + tags: wordpressCommon.tags, + acfFields: Property.Object({ + displayName: 'Custom ACF fields', + description: + 'Provide field name with value.You can find out field name from ACF plugin menu.', + required: false, + }), + categories: wordpressCommon.categories, + featured_media: wordpressCommon.featured_media, + status: wordpressCommon.status, + excerpt: Property.LongText({ + description: 'Uses the WordPress Text Editor which supports HTML', + displayName: 'Excerpt', + required: false, + }), + comment_status: Property.Checkbox({ + displayName: 'Enable Comments', + required: false, + }), + ping_status: Property.Checkbox({ + displayName: 'Open to Pinging', + required: false, + }), + }, + async run(context) { + if (!(await wordpressCommon.urlExists(context.auth.website_url.trim()))) { + throw new Error('Website url is invalid: ' + context.auth.website_url); + } + const requestBody: Record = {}; + if (context.propsValue.date) { + requestBody['date'] = context.propsValue.date; + } + if (context.propsValue.comment_status) { + requestBody['comment_status'] = context.propsValue.comment_status + ? 'open' + : 'closed'; + } + if (context.propsValue.categories) { + requestBody['categories'] = context.propsValue.categories; + } + if (context.propsValue.slug) { + requestBody['slug'] = context.propsValue.slug; + } + if (context.propsValue.excerpt) { + requestBody['excerpt'] = context.propsValue.excerpt; + } + if (context.propsValue.tags) { + requestBody['tags'] = context.propsValue.tags; + } + if (context.propsValue.ping_status) { + requestBody['ping_status'] = context.propsValue.ping_status + ? 'open' + : 'closed'; + } + if (context.propsValue.status) { + requestBody['status'] = context.propsValue.status; + } + if (context.propsValue.featured_media) { + requestBody['featured_media'] = context.propsValue.featured_media; + } + + if ( + context.propsValue.acfFields && + Object.keys(context.propsValue.acfFields).length > 0 + ) { + requestBody['acf'] = context.propsValue.acfFields; + } + + if (context.propsValue.featured_media_file) { + const formData = new FormData(); + const { filename, base64 } = context.propsValue.featured_media_file; + formData.append('file', Buffer.from(base64, 'base64'), filename); + const uploadMediaResponse = await httpClient.sendRequest<{ id: string }>({ + method: HttpMethod.POST, + url: `${context.auth.website_url.trim()}/wp-json/wp/v2/media`, + body: formData, + headers: { + 'Content-Type': 'multipart/form-data', + }, + authentication: { + type: AuthenticationType.BASIC, + username: context.auth.username, + password: context.auth.password, + }, + }); + requestBody['featured_media'] = uploadMediaResponse.body.id; + } + requestBody['content'] = context.propsValue.content; + requestBody['title'] = context.propsValue.title; + return await httpClient.sendRequest<{ id: string; name: string }[]>({ + method: HttpMethod.POST, + url: `${context.auth.website_url.trim()}/wp-json/wp/v2/posts`, + authentication: { + type: AuthenticationType.BASIC, + username: context.auth.username, + password: context.auth.password, + }, + body: requestBody, + }); + }, +}); diff --git a/packages/pieces/community/wordpress/src/lib/actions/get-post.action.ts b/packages/pieces/community/wordpress/src/lib/actions/get-post.action.ts new file mode 100644 index 0000000..2267afb --- /dev/null +++ b/packages/pieces/community/wordpress/src/lib/actions/get-post.action.ts @@ -0,0 +1,44 @@ +import { + createAction, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { wordpressCommon, WordPressMedia } from '../common'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import FormData from 'form-data'; +import { wordpressAuth } from '../..'; + +export const getWordPressPost = createAction({ + auth: wordpressAuth, + name: 'get_post', + description: 'Get a post from WordPress', + displayName: 'Get Post Details', + props: { + id: Property.Number({ + description: 'The ID of the post to get', + displayName: 'Post ID', + required: true, + }), + }, + async run(context) { + if (!(await wordpressCommon.urlExists(context.auth.website_url.trim()))) { + throw new Error('Website url is invalid: ' + context.auth.website_url); + } + + return await httpClient.sendRequest<{ id: string; name: string }[]>({ + method: HttpMethod.GET, + url: `${context.auth.website_url.trim()}/wp-json/wp/v2/posts/${ + context.propsValue.id + }`, + authentication: { + type: AuthenticationType.BASIC, + username: context.auth.username, + password: context.auth.password, + }, + }); + }, +}); diff --git a/packages/pieces/community/wordpress/src/lib/actions/update-post.action.ts b/packages/pieces/community/wordpress/src/lib/actions/update-post.action.ts new file mode 100644 index 0000000..eeb89a5 --- /dev/null +++ b/packages/pieces/community/wordpress/src/lib/actions/update-post.action.ts @@ -0,0 +1,150 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { wordpressCommon } from '../common'; +import { + httpClient, + HttpMethod, + AuthenticationType, +} from '@activepieces/pieces-common'; +import FormData from 'form-data'; +import { wordpressAuth } from '../..'; + +export const updateWordPressPost = createAction({ + auth: wordpressAuth, + name: 'update_post', + description: 'Update an existing post on WordPress.', + displayName: 'Update Post', + props: { + post: wordpressCommon.post, + title: Property.ShortText({ + description: 'Title of the post about to be added', + displayName: 'Title', + required: false, + }), + content: Property.LongText({ + description: 'Uses the WordPress Text Editor which supports HTML', + displayName: 'Content', + required: false, + }), + slug: Property.ShortText({ + displayName: 'Slug', + required: false, + }), + date: Property.ShortText({ + description: 'Post publish date (ISO-8601)', + displayName: 'Date', + required: false, + }), + featured_media_file: wordpressCommon.featured_media_file, + tags: wordpressCommon.tags, + acfFields: Property.Object({ + displayName: 'Custom ACF fields', + description: + 'Provide field name with value.You can find out field name from ACF plugin menu.', + required: false, + }), + categories: wordpressCommon.categories, + featured_media: wordpressCommon.featured_media, + status: wordpressCommon.status, + excerpt: Property.LongText({ + description: 'Uses the WordPress Text Editor which supports HTML', + displayName: 'Excerpt', + required: false, + }), + comment_status: Property.Checkbox({ + displayName: 'Enable Comments', + required: false, + }), + ping_status: Property.Checkbox({ + displayName: 'Open to Pinging', + required: false, + }), + }, + async run(context) { + if (!(await wordpressCommon.urlExists(context.auth.website_url.trim()))) { + throw new Error('Website url is invalid: ' + context.auth.website_url); + } + const requestBody: Record = {}; + + if (context.propsValue.title) { + requestBody['title'] = context.propsValue.title; + } + + if (context.propsValue.content) { + requestBody['content'] = context.propsValue.content; + } + + if (context.propsValue.date) { + requestBody['date'] = context.propsValue.date; + } + if (context.propsValue.comment_status) { + requestBody['comment_status'] = context.propsValue.comment_status + ? 'open' + : 'closed'; + } + if (context.propsValue.categories) { + requestBody['categories'] = context.propsValue.categories; + } + if (context.propsValue.slug) { + requestBody['slug'] = context.propsValue.slug; + } + if (context.propsValue.excerpt) { + requestBody['excerpt'] = context.propsValue.excerpt; + } + if (context.propsValue.tags) { + requestBody['tags'] = context.propsValue.tags; + } + if (context.propsValue.ping_status) { + requestBody['ping_status'] = context.propsValue.ping_status + ? 'open' + : 'closed'; + } + if (context.propsValue.status) { + requestBody['status'] = context.propsValue.status; + } + if (context.propsValue.featured_media) { + requestBody['featured_media'] = context.propsValue.featured_media; + } + + if ( + context.propsValue.acfFields && + Object.keys(context.propsValue.acfFields).length > 0 + ) { + requestBody['acf'] = context.propsValue.acfFields; + } + + if (context.propsValue.featured_media_file) { + const formData = new FormData(); + const { filename, base64 } = context.propsValue.featured_media_file; + formData.append('file', Buffer.from(base64, 'base64'), filename); + const uploadMediaResponse = await httpClient.sendRequest<{ id: string }>({ + method: HttpMethod.POST, + url: `${context.auth.website_url.trim()}/wp-json/wp/v2/media`, + body: formData, + headers: { + 'Content-Type': 'multipart/form-data', + }, + authentication: { + type: AuthenticationType.BASIC, + username: context.auth.username, + password: context.auth.password, + }, + }); + requestBody['featured_media'] = uploadMediaResponse.body.id; + } + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `${context.auth.website_url.trim()}/wp-json/wp/v2/posts/${ + context.propsValue.post + }`, + authentication: { + type: AuthenticationType.BASIC, + username: context.auth.username, + password: context.auth.password, + }, + body: requestBody, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/wordpress/src/lib/common/index.ts b/packages/pieces/community/wordpress/src/lib/common/index.ts new file mode 100644 index 0000000..a89ef83 --- /dev/null +++ b/packages/pieces/community/wordpress/src/lib/common/index.ts @@ -0,0 +1,461 @@ +import { + DropdownOption, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { wordpressAuth } from '../..'; +export type WordPressMedia = { id: string; title: { rendered: string } }; + +const PAGE_HEADER = 'x-wp-totalpages'; + +export const wordpressCommon = { + featured_media_file: Property.File({ + displayName: 'Featured Media (URL)', + required: false, + description: 'URL of featured media', + }), + authors: Property.Dropdown({ + displayName: 'Authors', + required: false, + refreshers: [], + options: async ({ auth }) => { + const connection = auth as PiecePropValueSchema; + const websiteUrl = connection.website_url; + if (!connection?.username || !connection?.password || !websiteUrl) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${websiteUrl.trim()}/wp-json/wp/v2/users`, + authentication: { + type: AuthenticationType.BASIC, + username: connection.username, + password: connection.password, + }, + }; + const response = await httpClient.sendRequest< + { id: string; name: string }[] + >(request); + return { + options: response.body.map((usr) => { + return { value: usr.id, label: usr.name }; + }), + }; + }, + }), + tags: Property.MultiSelectDropdown({ + description: 'Post tags', + displayName: 'Tags', + required: false, + refreshers: [], + options: async ({ auth }) => { + const connection = auth as PiecePropValueSchema; + if (!connection) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first', + }; + } + if (!(await wordpressCommon.urlExists(connection.website_url.trim()))) { + return { + disabled: true, + placeholder: 'Incorrect website url', + options: [], + }; + } + + let pageCursor = 1; + const getTagsParams = { + websiteUrl: connection.website_url.trim(), + username: connection.username, + password: connection.password, + page: pageCursor, + }; + const result: { id: string; name: string }[] = []; + let hasNext = true; + let tags = await wordpressCommon.getTags(getTagsParams); + while (hasNext) { + result.push(...tags.tags); + hasNext = pageCursor <= tags.totalPages; + if (hasNext) { + pageCursor++; + tags = await wordpressCommon.getTags({ + ...getTagsParams, + page: pageCursor, + }); + } + } + if (result.length === 0) { + return { + disabled: true, + options: [], + placeholder: 'Please add tags from your admin dashboard', + }; + } + const options = result.map((res) => { + return { + label: res.name, + value: res.id, + }; + }); + return { + options: options, + disabled: false, + }; + }, + }), + categories: Property.MultiSelectDropdown({ + description: 'Post categories', + displayName: 'Categories', + required: false, + refreshers: [], + options: async ({ auth }) => { + const connection = auth as PiecePropValueSchema; + if (!connection) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first', + }; + } + if (!(await wordpressCommon.urlExists(connection.website_url.trim()))) { + return { + disabled: true, + placeholder: 'Incorrect website url', + options: [], + }; + } + + let pageCursor = 1; + const getTagsParams = { + websiteUrl: connection.website_url, + username: connection.username, + password: connection.password, + perPage: 10, + page: pageCursor, + }; + const result: { id: string; name: string }[] = []; + let categories = await wordpressCommon.getCategories(getTagsParams); + let hasNext = true; + while (hasNext) { + result.push(...categories.categories); + hasNext = pageCursor <= categories.totalPages; + if (hasNext) { + pageCursor++; + categories = await wordpressCommon.getCategories({ + ...getTagsParams, + page: pageCursor, + }); + } + } + if (result.length === 0) { + return { + disabled: true, + options: [], + placeholder: 'Please add categories from your admin dashboard', + }; + } + const options = result.map((res) => { + return { + label: res.name, + value: res.id, + }; + }); + return { + options: options, + disabled: false, + }; + }, + }), + featured_media: Property.Dropdown({ + description: 'Choose from one of your uploaded media files', + displayName: 'Featured Media (image)', + required: false, + refreshers: [], + options: async ({ auth }) => { + const connection = auth as PiecePropValueSchema; + if (!connection) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first', + }; + } + if (!(await wordpressCommon.urlExists(connection.website_url.trim()))) { + return { + disabled: true, + placeholder: 'Incorrect website url', + options: [], + }; + } + + let pageCursor = 1; + const getMediaParams = { + websiteUrl: connection.website_url, + username: connection.username, + password: connection.password, + page: pageCursor, + }; + const result: WordPressMedia[] = []; + let media = await wordpressCommon.getMedia(getMediaParams); + if (media.totalPages === 0) { + result.push(...media.media); + } + while (media.media.length > 0 && pageCursor <= media.totalPages) { + result.push(...media.media); + pageCursor++; + media = await wordpressCommon.getMedia(getMediaParams); + } + if (result.length === 0) { + return { + disabled: true, + options: [], + placeholder: + 'Please add an image to your media from your admin dashboard', + }; + } + const options = result.map((res) => { + return { + label: res.title.rendered, + value: res.id, + }; + }); + return { + options: options, + disabled: false, + }; + }, + }), + status: Property.StaticDropdown({ + description: 'Choose post status', + displayName: 'Status', + required: false, + options: { + disabled: false, + options: [ + { value: 'publish', label: 'Published' }, + { value: 'future', label: 'Scheduled' }, + { value: 'draft', label: 'Draft' }, + { value: 'pending', label: 'Pending' }, + { value: 'private', label: 'Private' }, + { value: 'trash', label: 'Trash' }, + ], + }, + }), + post: Property.Dropdown({ + displayName: 'Post', + required: true, + refreshers: [], + options: async ({ auth }) => { + const connection = auth as PiecePropValueSchema; + const websiteUrl = connection.website_url; + if (!connection?.username || !connection?.password || !websiteUrl) { + return { + disabled: true, + placeholder: 'Connect your account first', + options: [], + }; + } + const postOptions: DropdownOption[] = []; + let currentPage = 0; + let totalPage = 0; + + do { + currentPage += 1; + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${websiteUrl.trim()}/wp-json/wp/v2/posts`, + authentication: { + type: AuthenticationType.BASIC, + username: connection.username, + password: connection.password, + }, + queryParams: { + orderby: 'date', + order: 'desc', + per_page: '50', + page: currentPage.toString(), + }, + }; + + const response = await httpClient.sendRequest(request); + totalPage = parseInt( + response.headers?.['x-wp-totalpages'] as string, + 10 + ); + + postOptions.push( + ...response.body.map( + (post: { id: number; title: { rendered: string } }) => { + return { + label: post.title.rendered + ? post.title.rendered + : post.id.toString(), + value: post.id, + }; + } + ) + ); + } while (totalPage !== currentPage); + + return { + disabled: false, + options: postOptions, + }; + }, + }), + async getPosts(params: { + websiteUrl: string; + username: string; + password: string; + authors: string | undefined; + afterDate: string; + page: number; + }) { + const queryParams: Record = { + orderby: 'date', + order: 'desc', + before: new Date().toISOString(), + after: params.afterDate, + page: params.page.toString(), + }; + if (params.authors) { + queryParams['author'] = params.authors; + } + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${params.websiteUrl}/wp-json/wp/v2/posts`, + authentication: { + type: AuthenticationType.BASIC, + username: params.username, + password: params.password, + }, + queryParams: queryParams, + }; + const response = await httpClient.sendRequest<{ date: string }[]>(request); + return { + posts: response.body, + totalPages: + response.headers && response.headers[PAGE_HEADER] + ? Number(response.headers[PAGE_HEADER]) + : 0, + }; + }, + async getMedia(params: { + websiteUrl: string; + username: string; + password: string; + page: number; + }) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${params.websiteUrl}/wp-json/wp/v2/media`, + queryParams: { + page: params.page.toString(), + }, + authentication: { + type: AuthenticationType.BASIC, + username: params.username, + password: params.password, + }, + }; + const response = await httpClient.sendRequest(request); + return { + media: response.body, + totalPages: + response.headers && response.headers[PAGE_HEADER] + ? Number(response.headers[PAGE_HEADER]) + : 0, + }; + }, + async getTags(params: { + websiteUrl: string; + username: string; + password: string; + page: number; + }) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${params.websiteUrl}/wp-json/wp/v2/tags`, + queryParams: { + page: params.page.toString(), + }, + authentication: { + type: AuthenticationType.BASIC, + username: params.username, + password: params.password, + }, + }; + const response = await httpClient.sendRequest< + { id: string; name: string }[] + >(request); + return { + tags: response.body, + totalPages: + response.headers && response.headers[PAGE_HEADER] + ? Number(response.headers[PAGE_HEADER]) + : 0, + }; + }, + async getCategories(params: { + websiteUrl: string; + username: string; + password: string; + page: number; + }) { + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${params.websiteUrl}/wp-json/wp/v2/categories`, + authentication: { + type: AuthenticationType.BASIC, + username: params.username, + password: params.password, + }, + queryParams: { + page: params.page.toString(), + }, + }; + const response = await httpClient.sendRequest< + { id: string; name: string }[] + >(request); + return { + categories: response.body, + totalPages: + response.headers && response.headers[PAGE_HEADER] + ? Number(response.headers[PAGE_HEADER]) + : 0, + }; + }, + async urlExists(url: string) { + try { + const request: HttpRequest = { + method: HttpMethod.GET, + url: url, + }; + await httpClient.sendRequest(request); + return true; + } catch (e) { + return false; + } + }, + async isBaseUrl(urlString: string): Promise { + try { + const url = new URL(urlString); + return !url.pathname || url.pathname === '/'; + } catch (error) { + // Handle invalid URLs here, e.g., return false or throw an error + return false; + } + }, +}; diff --git a/packages/pieces/community/wordpress/src/lib/trigger/new-post.trigger.ts b/packages/pieces/community/wordpress/src/lib/trigger/new-post.trigger.ts new file mode 100644 index 0000000..5f3043b --- /dev/null +++ b/packages/pieces/community/wordpress/src/lib/trigger/new-post.trigger.ts @@ -0,0 +1,189 @@ +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { wordpressCommon } from '../common'; +import dayjs from 'dayjs'; +import { wordpressAuth } from '../..'; + +export const wordpressNewPost = createTrigger({ + auth: wordpressAuth, + name: 'new_post', + displayName: 'New Post', + sampleData: { + id: 60, + date: '2023-02-19T10:08:25', + date_gmt: '2023-02-19T10:08:25', + guid: { + rendered: 'https://yoursite.com/?p=60', + }, + modified: '2023-02-19T10:08:25', + modified_gmt: '2023-02-19T10:08:25', + slug: 'post-slug', + status: 'publish', + type: 'post', + link: '/post-slug/', + title: { + rendered: '

post title

', + }, + content: { + rendered: '\npost content\n', + protected: false, + }, + excerpt: { + rendered: 'xxx\n', + protected: false, + }, + author: 1, + featured_media: 0, + comment_status: 'open', + ping_status: 'open', + sticky: false, + template: '', + format: 'standard', + meta: [], + categories: [1], + tags: [], + _links: { + self: [ + { + href: '/wp-json/wp/v2/posts/60', + }, + ], + collection: [ + { + href: '/wp-json/wp/v2/posts', + }, + ], + about: [ + { + href: '/wp-json/wp/v2/types/post', + }, + ], + author: [ + { + embeddable: true, + href: '/wp-json/wp/v2/users/1', + }, + ], + replies: [ + { + embeddable: true, + href: '/wp-json/wp/v2/comments?post=60', + }, + ], + 'version-history': [ + { + count: 1, + href: '/wp-json/wp/v2/posts/60/revisions', + }, + ], + 'predecessor-version': [ + { + id: 61, + href: '/wp-json/wp/v2/posts/60/revisions/61', + }, + ], + 'wp:attachment': [ + { + href: '/wp-json/wp/v2/media?parent=60', + }, + ], + 'wp:term': [ + { + taxonomy: 'category', + embeddable: true, + href: '/wp-json/wp/v2/categories?post=60', + }, + { + taxonomy: 'post_tag', + embeddable: true, + href: '/wp-json/wp/v2/tags?post=60', + }, + ], + curies: [ + { + name: 'wp', + href: 'https://api.w.org/{rel}', + templated: true, + }, + ], + }, + }, + description: 'Triggers when a new post is published', + props: { + authors: wordpressCommon.authors, + }, + type: TriggerStrategy.POLLING, + async test(ctx) { + return await pollingHelper.test(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + files: ctx.files, + }); + }, + async onEnable(ctx) { + await pollingHelper.onEnable(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + }); + }, + async onDisable(ctx) { + await pollingHelper.onDisable(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + }); + }, + async run(ctx) { + return await pollingHelper.poll(polling, { + auth: ctx.auth, + store: ctx.store, + propsValue: ctx.propsValue, + files: ctx.files, + }); + }, +}); + +const polling: Polling< + PiecePropValueSchema, + { authors: string | undefined } +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + const items = await getPosts(auth, propsValue.authors!, lastFetchEpochMS); + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.date).valueOf(), + data: item, + })); + }, +}; + +const getPosts = async ( + auth: PiecePropValueSchema, + authors: string, + startDate: number +) => { + //WordPress accepts date only if they come after the start of the unix time stamp in 1970 + let afterDate = dayjs(startDate).toISOString(); + if (startDate === 0) { + afterDate = dayjs(startDate).add(1, 'day').toISOString(); + } + const getPostsParams = { + websiteUrl: auth.website_url.trim(), + username: auth.username, + password: auth.password, + authors: authors, + afterDate: afterDate, + page: 1, + }; + return (await wordpressCommon.getPosts(getPostsParams)).posts; +}; diff --git a/packages/pieces/community/wordpress/tsconfig.json b/packages/pieces/community/wordpress/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/wordpress/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/wordpress/tsconfig.lib.json b/packages/pieces/community/wordpress/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/wordpress/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/xero/.eslintrc.json b/packages/pieces/community/xero/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/xero/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/xero/README.md b/packages/pieces/community/xero/README.md new file mode 100644 index 0000000..633b69a --- /dev/null +++ b/packages/pieces/community/xero/README.md @@ -0,0 +1,7 @@ +# pieces-xero + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-xero` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/xero/package.json b/packages/pieces/community/xero/package.json new file mode 100644 index 0000000..b26e758 --- /dev/null +++ b/packages/pieces/community/xero/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-xero", + "version": "0.4.0" +} diff --git a/packages/pieces/community/xero/project.json b/packages/pieces/community/xero/project.json new file mode 100644 index 0000000..09203b9 --- /dev/null +++ b/packages/pieces/community/xero/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-xero", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/xero/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/xero", + "tsConfig": "packages/pieces/community/xero/tsconfig.lib.json", + "packageJson": "packages/pieces/community/xero/package.json", + "main": "packages/pieces/community/xero/src/index.ts", + "assets": [ + "packages/pieces/community/xero/*.md", + { + "input": "packages/pieces/community/xero/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/xero/src/index.ts b/packages/pieces/community/xero/src/index.ts new file mode 100644 index 0000000..f0fd0d6 --- /dev/null +++ b/packages/pieces/community/xero/src/index.ts @@ -0,0 +1,59 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { xeroCreateContact } from './lib/actions/create-contact'; +import { xeroCreateInvoice } from './lib/actions/create-invoice'; + +export const xeroAuth = PieceAuth.OAuth2({ + description: ` + 1. Log in to Xero. + 2. Go to [Developer portal](https://developer.xero.com/app/manage/). + 3. Click on the App you want to integrate. + 4. On the left, click on \`Configuration\`. + 5. Enter your \`redirect url\`. + 6. Copy the \`Client Id\` and \`Client Secret\`. + `, + authUrl: 'https://login.xero.com/identity/connect/authorize', + tokenUrl: 'https://identity.xero.com/connect/token', + required: true, + scope: [ + 'openid', + 'profile', + 'email', + 'offline_access', + 'accounting.contacts', + 'accounting.transactions', + 'accounting.reports.read', + 'accounting.journals.read', + 'accounting.budgets.read', + 'accounting.attachments', + 'accounting.settings', + ], +}); + +export const xero = createPiece({ + displayName: 'Xero', + description: 'Beautiful accounting software', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/xero.png', + authors: ['kanarelo', 'kishanprmr', 'MoShizzle', 'khaledmashaly', 'abuaboud'], + categories: [PieceCategory.ACCOUNTING], + auth: xeroAuth, + actions: [ + xeroCreateContact, + xeroCreateInvoice, + createCustomApiCallAction({ + baseUrl: () => 'https://api.xero.com/api.xro/2.0', + auth: xeroAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/xero/src/lib/actions/create-contact.ts b/packages/pieces/community/xero/src/lib/actions/create-contact.ts new file mode 100644 index 0000000..81a1097 --- /dev/null +++ b/packages/pieces/community/xero/src/lib/actions/create-contact.ts @@ -0,0 +1,56 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; +import { props } from '../common/props'; +import { xeroAuth } from '../..'; + +export const xeroCreateContact = createAction({ + auth: xeroAuth, + name: 'xero_create_contact', + description: 'Create Xero Contact', + displayName: 'Create or Update Contact', + props: { + tenant_id: props.tenant_id, + contact_id: props.contact_id(false), + name: props.contact_name(true), + email: props.contact_email(false), + }, + async run(context) { + const { name, email, contact_id, tenant_id } = context.propsValue; + const body = { + Contacts: [ + { + Name: name, + EmailAddress: email, + }, + ], + }; + const url = 'https://api.xero.com/api.xro/2.0/Contacts'; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: contact_id ? `${url}/${contact_id}` : url, + body, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + headers: { + 'Xero-Tenant-Id': tenant_id, + }, + }; + + const result = await httpClient.sendRequest(request); + console.debug('Contact creation response', result); + + if (result.status === 200) { + return result.body; + } + + return result; + }, +}); diff --git a/packages/pieces/community/xero/src/lib/actions/create-invoice.ts b/packages/pieces/community/xero/src/lib/actions/create-invoice.ts new file mode 100644 index 0000000..11904ed --- /dev/null +++ b/packages/pieces/community/xero/src/lib/actions/create-invoice.ts @@ -0,0 +1,113 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; + +import { props } from '../common/props'; +import dayjs from 'dayjs'; +import { xeroAuth } from '../..'; + +export const xeroCreateInvoice = createAction({ + auth: xeroAuth, + name: 'xero_create_invoice', + description: 'Create Xero Invoice', + displayName: 'Create or Update Invoice', + props: { + tenant_id: props.tenant_id, + invoice_id: props.invoice_id, + contact_id: props.contact_id(false), + name: props.contact_name(true), + email: props.contact_email(false), + line_item: Property.Object({ + displayName: 'Line Item', + description: 'Invoice line items', + required: true, + defaultValue: { + AccountCode: 200, + Quantity: 0, + UnitAmount: 0, + LineAmount: 0, + TaxType: 'NONE', + Description: 'description', + }, + }), + date: Property.ShortText({ + displayName: 'Date Prepared', + description: 'Date the invoice was created. Format example: 2019-03-11', + required: false, + }), + due_date: Property.ShortText({ + displayName: 'Due Date', + description: 'Due date of the invoice. Format example: 2019-03-11', + defaultValue: dayjs().format('YYYY-MM-DD'), + required: true, + }), + reference: Property.ShortText({ + displayName: 'Invoice Reference', + description: 'Reference number of the Invoice', + required: false, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + description: 'Invoice Status', + required: true, + options: { + options: [ + { label: 'Draft', value: 'DRAFT' }, + { label: 'Submitted', value: 'SUBMITTED' }, + { label: 'Authorised', value: 'AUTHORISED' }, + { label: 'Deleted', value: 'DELETED' }, + { label: 'Voided', value: 'VOIDED' }, + ], + }, + }), + }, + async run(context) { + const { invoice_id, contact_id, email, name, tenant_id, ...invoice } = + context.propsValue; + + const contact: Record = { Name: name }; + if (email) contact['EmailAddress'] = email; + if (contact_id) contact['ContactID'] = contact_id; + + const body = { + Invoices: [ + { + Type: 'ACCREC', + Contact: contact, + LineItems: invoice.line_item ? [invoice.line_item] : [], + Date: invoice.date, + DueDate: invoice.due_date, + Reference: invoice.reference, + Status: invoice.status, + }, + ], + }; + + const url = 'https://api.xero.com/api.xro/2.0/Invoices'; + const request: HttpRequest = { + method: HttpMethod.POST, + url: invoice_id ? `${url}/${invoice_id}` : url, + body, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + headers: { + 'Xero-Tenant-Id': tenant_id, + }, + }; + + const result = await httpClient.sendRequest(request); + console.debug('Invoice creation response', result); + + if (result.status === 200) { + return result.body; + } + + return result; + }, +}); diff --git a/packages/pieces/community/xero/src/lib/common/props.ts b/packages/pieces/community/xero/src/lib/common/props.ts new file mode 100644 index 0000000..555faf3 --- /dev/null +++ b/packages/pieces/community/xero/src/lib/common/props.ts @@ -0,0 +1,85 @@ +import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework'; +import { + AuthenticationType, + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; + +export const props = { + tenant_id: Property.Dropdown({ + displayName: 'Organization', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + + const request: HttpRequest = { + method: HttpMethod.GET, + url: 'https://api.xero.com/connections', + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: (auth as OAuth2PropertyValue).access_token, + }, + }; + + const result = await httpClient.sendRequest< + { + id: string; + authEventId: string; + tenantId: string; + tenantType: string; + tenantName: string; + createdDateUtc: string; + updatedDateUtc: string; + }[] + >(request); + + if (result.status === 200) { + return { + disabled: false, + options: [ + { + label: result.body?.[0].tenantName, + value: result.body?.[0].tenantId, + }, + ], + }; + } + + return { + disabled: true, + options: [], + placeholder: 'Error processing tenant_id', + }; + }, + }), + invoice_id: Property.ShortText({ + displayName: 'Invoice ID', + description: 'ID of the invoice to update', + required: false, + }), + contact_id: (required = false) => + Property.ShortText({ + displayName: 'Contact ID', + description: 'ID of the contact to create invoice for.', + required: required, + }), + contact_name: (required = false) => + Property.ShortText({ + displayName: 'Contact Name', + description: 'Contact name, in full.', + required: required, + }), + contact_email: (required = false) => + Property.ShortText({ + displayName: 'Contact Email', + description: 'Email address of the contact.', + required: required, + }), +}; diff --git a/packages/pieces/community/xero/tsconfig.json b/packages/pieces/community/xero/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/xero/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/xero/tsconfig.lib.json b/packages/pieces/community/xero/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/xero/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/xml/.eslintrc.json b/packages/pieces/community/xml/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/xml/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/xml/README.md b/packages/pieces/community/xml/README.md new file mode 100644 index 0000000..e238886 --- /dev/null +++ b/packages/pieces/community/xml/README.md @@ -0,0 +1,7 @@ +# pieces-xml + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-xml` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/xml/package.json b/packages/pieces/community/xml/package.json new file mode 100644 index 0000000..3c38ab4 --- /dev/null +++ b/packages/pieces/community/xml/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-xml", + "version": "0.1.3" +} \ No newline at end of file diff --git a/packages/pieces/community/xml/project.json b/packages/pieces/community/xml/project.json new file mode 100644 index 0000000..02ef92b --- /dev/null +++ b/packages/pieces/community/xml/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-xml", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/xml/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/xml", + "tsConfig": "packages/pieces/community/xml/tsconfig.lib.json", + "packageJson": "packages/pieces/community/xml/package.json", + "main": "packages/pieces/community/xml/src/index.ts", + "assets": [ + "packages/pieces/community/xml/*.md", + { + "input": "packages/pieces/community/xml/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/xml/src/index.ts b/packages/pieces/community/xml/src/index.ts new file mode 100644 index 0000000..4f5e924 --- /dev/null +++ b/packages/pieces/community/xml/src/index.ts @@ -0,0 +1,16 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { convertJsonToXml } from './lib/actions/convert-json-to-xml'; + +export const xml = createPiece({ + displayName: 'XML', + description: 'Extensible Markup Language for storing and transporting data', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/xml.png', + categories: [PieceCategory.CORE], + auth: PieceAuth.None(), + authors: ["Willianwg","kishanprmr","AbdulTheActivePiecer","khaledmashaly","abuaboud"], + actions: [convertJsonToXml], + triggers: [], +}); diff --git a/packages/pieces/community/xml/src/lib/actions/convert-json-to-xml.ts b/packages/pieces/community/xml/src/lib/actions/convert-json-to-xml.ts new file mode 100644 index 0000000..1f0ca09 --- /dev/null +++ b/packages/pieces/community/xml/src/lib/actions/convert-json-to-xml.ts @@ -0,0 +1,36 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import js2xml from 'json2xml'; + +export const convertJsonToXml = createAction({ + name: 'convert-json-to-xml', + displayName: 'Convert JSON to XML', + description: 'Convert JSON to XML', + props: { + json: Property.Json({ + displayName: 'JSON', + required: true, + }), + attributes_key: Property.ShortText({ + displayName: 'Attribute field', + description: "Field to add your tag's attributes", + required: false, + }), + header: Property.Checkbox({ + displayName: 'Header', + description: 'Add XML header', + required: false, + }), + }, + async run(context) { + const { json } = context.propsValue; + + const attributes_key = context.propsValue.attributes_key + ? context.propsValue.attributes_key + : 'attr'; + const header = context.propsValue.header + ? context.propsValue.header + : false; + + return js2xml(JSON.parse(JSON.stringify(json)), { attributes_key, header }); + }, +}); diff --git a/packages/pieces/community/xml/tsconfig.json b/packages/pieces/community/xml/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/xml/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/xml/tsconfig.lib.json b/packages/pieces/community/xml/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/xml/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/youtube/.eslintrc.json b/packages/pieces/community/youtube/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/youtube/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/youtube/README.md b/packages/pieces/community/youtube/README.md new file mode 100644 index 0000000..1470f23 --- /dev/null +++ b/packages/pieces/community/youtube/README.md @@ -0,0 +1,7 @@ +# pieces-youtube + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-youtube` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/youtube/package.json b/packages/pieces/community/youtube/package.json new file mode 100644 index 0000000..0cc11b0 --- /dev/null +++ b/packages/pieces/community/youtube/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-youtube", + "version": "0.3.8" +} \ No newline at end of file diff --git a/packages/pieces/community/youtube/project.json b/packages/pieces/community/youtube/project.json new file mode 100644 index 0000000..f2d2686 --- /dev/null +++ b/packages/pieces/community/youtube/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-youtube", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/youtube/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/youtube", + "tsConfig": "packages/pieces/community/youtube/tsconfig.lib.json", + "packageJson": "packages/pieces/community/youtube/package.json", + "main": "packages/pieces/community/youtube/src/index.ts", + "assets": [ + "packages/pieces/community/youtube/*.md", + { + "input": "packages/pieces/community/youtube/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/youtube/src/index.ts b/packages/pieces/community/youtube/src/index.ts new file mode 100644 index 0000000..5a4ebbf --- /dev/null +++ b/packages/pieces/community/youtube/src/index.ts @@ -0,0 +1,30 @@ +import { + createPiece, + OAuth2PropertyValue, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { youtubeNewVideoTrigger } from './lib/triggers/new-video.trigger'; +import { youtubeAuth } from './lib/common/auth'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; + +export const youtube = createPiece({ + displayName: 'YouTube', + description: + 'Enjoy the videos and music you love, upload original content, and share it all with friends, family, and the world on YouTube', + + minimumSupportedRelease: '0.33.0', + logoUrl: 'https://cdn.activepieces.com/pieces/youtube.png', + categories: [PieceCategory.CONTENT_AND_FILES], + auth: youtubeAuth, + authors: ['abaza738', 'kishanprmr', 'khaledmashaly', 'abuaboud'], + actions: [ + createCustomApiCallAction({ + baseUrl: () => 'https://www.googleapis.com/youtube/v3', + auth: youtubeAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [youtubeNewVideoTrigger], +}); diff --git a/packages/pieces/community/youtube/src/lib/common/auth.ts b/packages/pieces/community/youtube/src/lib/common/auth.ts new file mode 100644 index 0000000..c846056 --- /dev/null +++ b/packages/pieces/community/youtube/src/lib/common/auth.ts @@ -0,0 +1,30 @@ +import { PieceAuth } from '@activepieces/pieces-framework'; + +export const youtubeAuth = PieceAuth.OAuth2({ + description: ` + 1. Sign in to [Google Cloud Console](https://console.cloud.google.com/). + 2. Create a new project or you can use existing one. + 3. Go to **APIs & Services** and click **Enable APIs & Services**. + 4. Search for **YouTube API** in the search bar and enable it. + 5. Go to **OAuth consent screen** and select **External** type and click create. + 6. Fill App Name, User Support Email, and Developer Contact Information. Click on the Save and Continue button. + 7. Click on **Add or Remove Scopes** and add following scopes and click update. + - https://www.googleapis.com/auth/youtube + - https://www.googleapis.com/auth/youtube.readonly + - https://www.googleapis.com/auth/youtube.upload + 8. Click Save and Continue to finish the Scopes step. + 9. Click on the Add Users button and add a test email You can add your own email).Then finally click Save and Continue to finish the Test Users portion. + 10. Go to **Credentials**. Click on the **Create Credentials** button and select the **OAuth client ID** option. + 11. Select the application type as **Web Application** and fill the Name field. + 12. Add https://cloud.activepieces.com/redirect in **Authorized redirect URIs** field, and click on the Create button. + 13. Copy **Client ID** and **Client Secret**.`, + + authUrl: 'https://accounts.google.com/o/oauth2/auth', + tokenUrl: 'https://oauth2.googleapis.com/token', + required: true, + scope: [ + 'https://www.googleapis.com/auth/youtube', + 'https://www.googleapis.com/auth/youtube.readonly', + 'https://www.googleapis.com/auth/youtube.upload', + ], +}); diff --git a/packages/pieces/community/youtube/src/lib/common/props.ts b/packages/pieces/community/youtube/src/lib/common/props.ts new file mode 100644 index 0000000..bf053a4 --- /dev/null +++ b/packages/pieces/community/youtube/src/lib/common/props.ts @@ -0,0 +1,7 @@ +import { Property } from '@activepieces/pieces-framework'; + +export const channelIdentifier = Property.ShortText({ + displayName: 'Channel ID, URL, or handle', + description: "YouTube channel's ID, URL, or handle (e.g: @DutchPilotGirl)", + required: true, +}); diff --git a/packages/pieces/community/youtube/src/lib/triggers/new-video.trigger.ts b/packages/pieces/community/youtube/src/lib/triggers/new-video.trigger.ts new file mode 100644 index 0000000..e9c0b37 --- /dev/null +++ b/packages/pieces/community/youtube/src/lib/triggers/new-video.trigger.ts @@ -0,0 +1,347 @@ +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { channelIdentifier } from '../common/props'; +import dayjs from 'dayjs'; +import cheerio from 'cheerio'; +import FeedParser from 'feedparser'; +import axios from 'axios'; + +export const youtubeNewVideoTrigger = createTrigger({ + name: 'new-video', + displayName: 'New Video In Channel', + description: 'Runs when a new video is added to a YouTube channel', + type: TriggerStrategy.POLLING, + requireAuth: false, + props: { + channel_identifier: channelIdentifier, + }, + sampleData: { + title: 'Ap Flow Branching', + description: null, + summary: null, + date: '2023-03-09T01:23:10.000Z', + pubdate: '2023-03-01T21:31:36.000Z', + pubDate: '2023-03-01T21:31:36.000Z', + link: 'https://www.youtube.com/watch?v=C7MZkWxrtvM', + guid: 'yt:video:C7MZkWxrtvM', + author: 'Mohammad AbuAboud', + comments: null, + origlink: null, + image: { + url: 'https://i4.ytimg.com/vi/C7MZkWxrtvM/hqdefault.jpg', + }, + source: {}, + categories: [], + enclosures: [], + 'atom:@': {}, + 'atom:id': { + '@': {}, + '#': 'yt:video:C7MZkWxrtvM', + }, + 'yt:videoid': { + '@': {}, + '#': 'C7MZkWxrtvM', + }, + 'yt:channelid': { + '@': {}, + '#': 'UCgImnA993V_2IbQ9seYNEzA', + }, + 'atom:title': { + '@': {}, + '#': 'Ap Flow Branching', + }, + 'atom:link': { + '@': { + rel: 'alternate', + href: 'https://www.youtube.com/watch?v=C7MZkWxrtvM', + }, + }, + 'atom:author': { + '@': {}, + name: { + '@': {}, + '#': 'Mohammad AbuAboud', + }, + uri: { + '@': {}, + '#': 'https://www.youtube.com/channel/UCgImnA993V_2IbQ9seYNEzA', + }, + }, + 'atom:published': { + '@': {}, + '#': '2023-03-01T21:31:36+00:00', + }, + 'atom:updated': { + '@': {}, + '#': '2023-03-09T01:23:10+00:00', + }, + 'media:group': { + '@': {}, + 'media:title': { + '@': {}, + '#': 'Ap Flow Branching', + }, + 'media:content': { + '@': { + url: 'https://www.youtube.com/v/C7MZkWxrtvM?version=3', + type: 'application/x-shockwave-flash', + width: '640', + height: '390', + }, + }, + 'media:thumbnail': { + '@': { + url: 'https://i4.ytimg.com/vi/C7MZkWxrtvM/hqdefault.jpg', + width: '480', + height: '360', + }, + }, + 'media:description': { + '@': {}, + }, + 'media:community': { + '@': {}, + 'media:starrating': { + '@': { + count: '0', + average: '0.00', + min: '1', + max: '5', + }, + }, + 'media:statistics': { + '@': { + views: '9', + }, + }, + }, + }, + meta: { + '#ns': [ + { + 'xmlns:yt': 'http://www.youtube.com/xml/schemas/2015', + }, + { + 'xmlns:media': 'http://search.yahoo.com/mrss/', + }, + { + xmlns: 'http://www.w3.org/2005/Atom', + }, + ], + '@': [ + { + 'xmlns:yt': 'http://www.youtube.com/xml/schemas/2015', + }, + { + 'xmlns:media': 'http://search.yahoo.com/mrss/', + }, + { + xmlns: 'http://www.w3.org/2005/Atom', + }, + ], + '#xml': { + version: '1.0', + encoding: 'UTF-8', + }, + '#type': 'atom', + '#version': '1.0', + title: 'Mohammad AbuAboud', + description: null, + date: '2020-12-29T17:29:29.000Z', + pubdate: '2020-12-29T17:29:29.000Z', + pubDate: '2020-12-29T17:29:29.000Z', + link: 'https://www.youtube.com/channel/UCgImnA993V_2IbQ9seYNEzA', + xmlurl: + 'http://www.youtube.com/feeds/videos.xml?channel_id=UCgImnA993V_2IbQ9seYNEzA', + xmlUrl: + 'http://www.youtube.com/feeds/videos.xml?channel_id=UCgImnA993V_2IbQ9seYNEzA', + author: 'Mohammad AbuAboud', + language: null, + favicon: null, + copyright: null, + generator: null, + cloud: {}, + image: {}, + categories: [], + 'atom:@': { + 'xmlns:yt': 'http://www.youtube.com/xml/schemas/2015', + 'xmlns:media': 'http://search.yahoo.com/mrss/', + xmlns: 'http://www.w3.org/2005/Atom', + }, + 'atom:link': [ + { + '@': { + rel: 'self', + href: 'http://www.youtube.com/feeds/videos.xml?channel_id=UCgImnA993V_2IbQ9seYNEzA', + }, + }, + { + '@': { + rel: 'alternate', + href: 'https://www.youtube.com/channel/UCgImnA993V_2IbQ9seYNEzA', + }, + }, + ], + 'atom:id': { + '@': {}, + '#': 'yt:channel:', + }, + 'yt:channelid': { + '@': {}, + }, + 'atom:title': { + '@': {}, + '#': 'Mohammad AbuAboud', + }, + 'atom:author': { + '@': {}, + name: { + '@': {}, + '#': 'Mohammad AbuAboud', + }, + uri: { + '@': {}, + '#': 'https://www.youtube.com/channel/UCgImnA993V_2IbQ9seYNEzA', + }, + }, + 'atom:published': { + '@': {}, + '#': '2020-12-29T17:29:29+00:00', + }, + }, + }, + async test({ propsValue }): Promise { + const channelId = await getChannelId(propsValue.channel_identifier); + if (!channelId) { + return []; + } + return (await getRssItems(channelId)) || []; + }, + async onEnable({ propsValue, store }): Promise { + const channelId = await getChannelId(propsValue.channel_identifier); + + if (!channelId) { + throw new Error('Unable to get channel ID.'); + } + + await store.put('channelId', channelId); + const items = (await getRssItems(channelId)) || []; + await store.put('lastFetchedYoutubeVideo', items?.[0]?.guid); + await store.put('lastUpdatedYoutubeVideo', getUpdateDate(items?.[0])); + return; + }, + + async onDisable(): Promise { + return; + }, + async run({ store }): Promise { + const channelId = await store.get('channelId'); + + if (!channelId) return []; + + const items = (await getRssItems(channelId)) || []; + if (items.length === 0) { + return []; + } + const lastItemId = await store.get('lastFetchedYoutubeVideo'); + const storedLastUpdated = await store.get( + 'lastUpdatedYoutubeVideo' + ); + + /** + * If the new latest item's date is before the last saved date + * it means something got deleted, nothing else to do + * this happens when a live stream ends, the live stream entry is deleted and later + * is replaced by the stream's video. + */ + if ( + storedLastUpdated && + dayjs(getUpdateDate(items?.[0])).isBefore(dayjs(storedLastUpdated)) + ) { + return []; + } + + const newItems = []; + for (const item of items) { + if (item.guid === lastItemId) break; + if ( + storedLastUpdated && + dayjs(getUpdateDate(item)).isBefore(dayjs(storedLastUpdated)) + ) { + continue; + } + newItems.push(item); + } + + await store.put('lastFetchedYoutubeVideo', items?.[0]?.guid); + await store.put('lastUpdatedYoutubeVideo', getUpdateDate(items?.[0])); + + return newItems; + }, +}); + +function getUpdateDate(item: any) { + const updated = item['atom:updated']; + if (updated == undefined) { + return undefined; + } + return updated['#']; +} + +async function getChannelId(urlOrId: string) { + if (urlOrId.trim().startsWith('@')) { + urlOrId = 'https://www.youtube.com/' + urlOrId; + } + if (!urlOrId.includes('https')) { + return urlOrId; + } + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: urlOrId, + }); + const $ = cheerio.load(response.body); + + // Check if the URL is a channel ID itself + const channelUrl = $('link[rel="canonical"]').attr('href'); + if (channelUrl && channelUrl.includes('/channel/')) { + return channelUrl.split('/channel/')[1]; + } + + throw new Error('Invalid YouTube channel URL'); +} + +function getRssItems(channelId: string): Promise { + const url = `https://www.youtube.com/feeds/videos.xml?channel_id=${channelId}`; + return new Promise((resolve, reject) => { + axios + .get(url, { + responseType: 'stream', + }) + .then((response) => { + const feedparser = new FeedParser({ + addmeta: true, + }); + response.data.pipe(feedparser); + const items: any[] = []; + + feedparser.on('readable', () => { + let item = feedparser.read(); + while (item) { + items.push(item); + item = feedparser.read(); + } + }); + + feedparser.on('end', () => { + resolve(items); + }); + + feedparser.on('error', (error: any) => { + reject(error); + }); + }) + .catch((error) => { + reject(error); + }); + }); +} diff --git a/packages/pieces/community/youtube/tsconfig.json b/packages/pieces/community/youtube/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/youtube/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/youtube/tsconfig.lib.json b/packages/pieces/community/youtube/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/youtube/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/zagomail/.eslintrc.json b/packages/pieces/community/zagomail/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/zagomail/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/zagomail/README.md b/packages/pieces/community/zagomail/README.md new file mode 100644 index 0000000..180df2a --- /dev/null +++ b/packages/pieces/community/zagomail/README.md @@ -0,0 +1,7 @@ +# pieces-zagomail + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-zagomail` to build the library. diff --git a/packages/pieces/community/zagomail/package.json b/packages/pieces/community/zagomail/package.json new file mode 100644 index 0000000..f768b4e --- /dev/null +++ b/packages/pieces/community/zagomail/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-zagomail", + "version": "0.0.1" +} diff --git a/packages/pieces/community/zagomail/project.json b/packages/pieces/community/zagomail/project.json new file mode 100644 index 0000000..48f5e12 --- /dev/null +++ b/packages/pieces/community/zagomail/project.json @@ -0,0 +1,45 @@ +{ + "name": "pieces-zagomail", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/zagomail/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/zagomail", + "tsConfig": "packages/pieces/community/zagomail/tsconfig.lib.json", + "packageJson": "packages/pieces/community/zagomail/package.json", + "main": "packages/pieces/community/zagomail/src/index.ts", + "assets": [ + "packages/pieces/community/zagomail/*.md" + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/zagomail/src/index.ts b/packages/pieces/community/zagomail/src/index.ts new file mode 100644 index 0000000..aa17e2a --- /dev/null +++ b/packages/pieces/community/zagomail/src/index.ts @@ -0,0 +1,59 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { addedSubscriber } from './lib/triggers/added-subscriber'; +import { unsubscribedSubscriber } from './lib/triggers/unsubscribed-subscriber'; +import { taggedSubscriber } from './lib/triggers/tagged-subscriber'; +import { createSubscriber } from './lib/actions/create-subscriber'; +import { tagSubscriber } from './lib/actions/tag-subscriber'; +import { updateSubscriber } from './lib/actions/update-subscriber'; +import { searchSubscriberByEmail } from './lib/actions/search-subscriber-by-email'; +import { getSubscriberDetails } from './lib/actions/get-subscriber-details'; +import { getCampaignDetails } from './lib/actions/get-campaign-details'; +import { zagoMailApiService } from './lib/common/request'; + +export const zagomailAuth = PieceAuth.SecretText({ + displayName: 'Application Public Key', + required: true, + description: + 'Please provide your application public key by generating one in your zagomail account settings or by directly visiting https://app.zagomail.com/user/api-keys/index.', + validate: async ({ auth }) => { + try { + const response = await zagoMailApiService.getAllLists(auth); + + if (response.status !== 'success') { + return { + valid: false, + error: 'Invalid Public Key.', + }; + } + return { + valid: true, + }; + } catch (e) { + return { + valid: false, + error: 'Invalid Public Key.', + }; + } + }, +}); + +export const zagomail = createPiece({ + displayName: 'Zagomail', + description: 'All-in-one email marketing and automation platform', + logoUrl: 'https://cdn.activepieces.com/pieces/zagomail.png', + authors: ['gs03dev'], + auth: zagomailAuth, + actions: [ + createSubscriber, + tagSubscriber, + updateSubscriber, + + searchSubscriberByEmail, + + getSubscriberDetails, + getCampaignDetails, + ], + triggers: [addedSubscriber, unsubscribedSubscriber, taggedSubscriber], + categories: [PieceCategory.MARKETING], +}); diff --git a/packages/pieces/community/zagomail/src/lib/actions/create-subscriber.ts b/packages/pieces/community/zagomail/src/lib/actions/create-subscriber.ts new file mode 100644 index 0000000..ac44323 --- /dev/null +++ b/packages/pieces/community/zagomail/src/lib/actions/create-subscriber.ts @@ -0,0 +1,41 @@ +import { zagomailAuth } from '../../'; +import { createAction } from '@activepieces/pieces-framework'; +import { zagoMailApiService } from '../common/request'; +import { listFields, listUId } from '../common/props'; +import { isNil } from '@activepieces/shared'; +import dayjs from 'dayjs'; + +export const createSubscriber = createAction({ + auth: zagomailAuth, + name: 'createSubscriber', + displayName: 'Create Subscriber', + description: 'Creates a new subscriber in a list.', + props: { + listUId: listUId, + fields: listFields(true), + }, + async run({ propsValue, auth }) { + const listUId = propsValue.listUId; + const listFields = propsValue.fields ?? {}; + + const payload: Record = {}; + + for (const [key, value] of Object.entries(listFields)) { + if (isNil(value) || value === '') continue; + + const [field, type] = key.split(':::'); + + let formattedValue = value; + + if (type === 'Date') { + formattedValue = dayjs(value).format('YYYY-MM-DD'); + } else if (type === 'Datetime') { + formattedValue = dayjs(value).format('YYYY-MM-DD HH:mm:ss'); + } + + payload[field] = formattedValue; + } + + return await zagoMailApiService.createSubscriber(auth, listUId, payload); + }, +}); diff --git a/packages/pieces/community/zagomail/src/lib/actions/get-campaign-details.ts b/packages/pieces/community/zagomail/src/lib/actions/get-campaign-details.ts new file mode 100644 index 0000000..16425dd --- /dev/null +++ b/packages/pieces/community/zagomail/src/lib/actions/get-campaign-details.ts @@ -0,0 +1,19 @@ +import { zagomailAuth } from '../../'; +import { createAction } from '@activepieces/pieces-framework'; +import { zagoMailApiService } from '../common/request'; +import { campaignUid } from '../common/props'; + +export const getCampaignDetails = createAction({ + auth: zagomailAuth, + name: 'getCampaignDetails', + displayName: 'Get Campaign', + description: 'Gets the details of a campaign.', + props: { + campaignUid: campaignUid, + }, + async run({propsValue, auth}) { + const campaignUid = propsValue.campaignUid; + + return await zagoMailApiService.getCampaignDetails(auth, campaignUid) + }, +}); diff --git a/packages/pieces/community/zagomail/src/lib/actions/get-subscriber-details.ts b/packages/pieces/community/zagomail/src/lib/actions/get-subscriber-details.ts new file mode 100644 index 0000000..a94ba5a --- /dev/null +++ b/packages/pieces/community/zagomail/src/lib/actions/get-subscriber-details.ts @@ -0,0 +1,29 @@ +import { zagomailAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zagoMailApiService } from '../common/request'; +import { listUId } from '../common/props'; + +export const getSubscriberDetails = createAction({ + auth: zagomailAuth, + name: 'getSubscriberDetails', + displayName: 'Get Subscriber', + description: 'Gets the details of a subscriber.', + props: { + listUId: listUId, + subscriberUid: Property.ShortText({ + displayName: 'Subscriber ID', + description: 'The ID of the subscriber you want to get the details for.', + required: true, + }), + }, + async run({ propsValue, auth }) { + const listUId = propsValue.listUId; + const subsriberUid = propsValue.subscriberUid; + + return await zagoMailApiService.getSubscriberDetails( + auth, + listUId, + subsriberUid + ); + }, +}); diff --git a/packages/pieces/community/zagomail/src/lib/actions/search-subscriber-by-email.ts b/packages/pieces/community/zagomail/src/lib/actions/search-subscriber-by-email.ts new file mode 100644 index 0000000..1cc119c --- /dev/null +++ b/packages/pieces/community/zagomail/src/lib/actions/search-subscriber-by-email.ts @@ -0,0 +1,35 @@ +import { zagomailAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zagoMailApiService } from '../common/request'; +import { listUId } from '../common/props'; + +export const searchSubscriberByEmail = createAction({ + auth: zagomailAuth, + name: 'searchSubscriberByEmail', + displayName: 'Search Subscriber', + description: 'Finds a subscriber by their email address.', + props: { + listUId: listUId, + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + }, + async run({ propsValue, auth }) { + const listUId = propsValue.listUId; + const email = propsValue.email; + + const response = await zagoMailApiService.searchSubscriberByEmail( + auth, + listUId, + { + email, + } + ); + + return { + found: response.status === 'success', + result: response.data ?? null, + }; + }, +}); diff --git a/packages/pieces/community/zagomail/src/lib/actions/tag-subscriber.ts b/packages/pieces/community/zagomail/src/lib/actions/tag-subscriber.ts new file mode 100644 index 0000000..26522b1 --- /dev/null +++ b/packages/pieces/community/zagomail/src/lib/actions/tag-subscriber.ts @@ -0,0 +1,54 @@ +import { zagomailAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zagoMailApiService } from '../common/request'; +import { Tag } from '../common/constants'; +import { listUId } from '../common/props'; + +export const tagSubscriber = createAction({ + auth: zagomailAuth, + name: 'tagSubscriber', + displayName: 'Tag Subscriber', + description: 'Adds A Tag to A Subscriber.', + props: { + tags: Property.Array({ + displayName: 'Tags', + description: + 'Add one or more tags you would like to add to this subscriber.', + required: true, + }), + listUId:listUId, + subscriberUid: Property.ShortText({ + displayName: 'Subscriber ID', + description: 'The ID of the subscriber you want to add the tag to.', + required: true, + }), + }, + async run({ propsValue, auth }) { + const providedTags = propsValue.tags as string[]; + + if (providedTags.length < 1) + throw new Error('You must provide atleast one tag'); + + const tags = (await zagoMailApiService.getTags(auth)) as Tag[]; + + return await Promise.all( + providedTags.map(async (providedTag) => { + const tagExists = tags.find((t) => t.ztag_name === providedTag); + + let tag; + + if (tagExists) { + tag = tagExists; + } else { + tag = (await zagoMailApiService.createTag(auth, providedTag)) as Tag; + } + + return await zagoMailApiService.addTagToSubscriber(auth, { + listUid: propsValue.listUId, + subscriberUid: propsValue.subscriberUid, + tagId: tag.ztag_id, + }); + }) + ); + }, +}); diff --git a/packages/pieces/community/zagomail/src/lib/actions/unsubscribe-subscriber.ts b/packages/pieces/community/zagomail/src/lib/actions/unsubscribe-subscriber.ts new file mode 100644 index 0000000..f04c7f9 --- /dev/null +++ b/packages/pieces/community/zagomail/src/lib/actions/unsubscribe-subscriber.ts @@ -0,0 +1,29 @@ +import { zagomailAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zagoMailApiService } from '../common/request'; +import { listUId } from '../common/props'; + +export const unsubscribeSubscriber = createAction({ + auth: zagomailAuth, + name: 'unsubscribeSubscriber', + displayName: 'Unsubscribe Subscriber', + description: 'Unsubscribes a subscriber.', + props: { + listUId: listUId, + subscriberUid: Property.ShortText({ + displayName: 'Subscriber ID', + description: 'The ID of the subscriber you want to unsubscribe.', + required: true, + }), + }, + async run({ propsValue, auth }) { + const listUId = propsValue.listUId; + const subsriberUid = propsValue.subscriberUid; + + return await zagoMailApiService.unsubscribeSubscriber( + auth, + listUId, + subsriberUid + ); + }, +}); diff --git a/packages/pieces/community/zagomail/src/lib/actions/update-subscriber.ts b/packages/pieces/community/zagomail/src/lib/actions/update-subscriber.ts new file mode 100644 index 0000000..164de7f --- /dev/null +++ b/packages/pieces/community/zagomail/src/lib/actions/update-subscriber.ts @@ -0,0 +1,52 @@ +import { zagomailAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zagoMailApiService } from '../common/request'; +import { listFields, listUId } from '../common/props'; +import { isNil } from '@activepieces/shared'; +import dayjs from 'dayjs'; + +export const updateSubscriber = createAction({ + auth: zagomailAuth, + name: 'updateSubscriber', + displayName: 'Update Subscriber', + description: 'Updates an existing subscriber.', + props: { + listUId: listUId, + subsriberUid: Property.ShortText({ + displayName: 'Subscriber ID', + description: 'The ID of the subscriber you want to update.', + required: true, + }), + fields: listFields(false), + }, + async run({ propsValue, auth }) { + const listUId = propsValue.listUId; + const subsriberUid = propsValue.subsriberUid; + const listFields = propsValue.fields ?? {}; + + const payload: Record = {}; + + for (const [key, value] of Object.entries(listFields)) { + if (isNil(value) || value === '') continue; + + const [field, type] = key.split(':::'); + + let formattedValue = value; + + if (type === 'Date') { + formattedValue = dayjs(value).format('YYYY-MM-DD'); + } else if (type === 'Datetime') { + formattedValue = dayjs(value).format('YYYY-MM-DD HH:mm:ss'); + } + + payload[field] = formattedValue; + } + + return await zagoMailApiService.updateSubscriber( + auth, + listUId, + subsriberUid, + payload + ); + }, +}); diff --git a/packages/pieces/community/zagomail/src/lib/common/constants.ts b/packages/pieces/community/zagomail/src/lib/common/constants.ts new file mode 100644 index 0000000..0cb43d3 --- /dev/null +++ b/packages/pieces/community/zagomail/src/lib/common/constants.ts @@ -0,0 +1,38 @@ +export const BASE_URL = 'https://api.zagomail.com'; + +export const API_ENDPOINTS = { + CREATE_SUBSCRIBER: '/lists/subscriber-create', + UPDATE_SUBSCRIBER: '/lists/subscriber-update', + UNSUBSCRIBE_SUBSCRIBER: '/lists/unsubscribe-subscriber', + SEARCH_SUBSCRIBER_BY_EMAIL: '/lists/search-by-email', + GET_SUBSCRIBER: '/lists/get-subscriber', + GET_CAMPAIGNS: '/campaigns/get-stats', + ADD_TAG_TO_SUBSCRIBER: '/lists/add-tag', + GET_TAGS: '/tags/get-tags', + CREATE_TAG: '/tags/create-tag', + CREATE_WEBHOOK: '/webhooks/create', + DELETE_WEBHOOK: '/webhooks/delete', + LIST_ALL_LISTS : '/lists/all-lists', + GET_LIST_FIELDS:'/lists/get-fields' +}; + + +export type WebhookResponse = { + id: string; + event_type: string; + target_url: string; + form_id: string | null; + tag_id: string | null; + link_url: string | null; +}; + +export type StoredWebhookId = { + webhookId: string; +}; + +export interface Tag { + ztag_id: string; + ztag_name: string; + customer_id: number; + created_on: Date; +} \ No newline at end of file diff --git a/packages/pieces/community/zagomail/src/lib/common/props.ts b/packages/pieces/community/zagomail/src/lib/common/props.ts new file mode 100644 index 0000000..4195298 --- /dev/null +++ b/packages/pieces/community/zagomail/src/lib/common/props.ts @@ -0,0 +1,120 @@ +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import { zagoMailApiService } from './request'; + +export const listUId = Property.Dropdown({ + displayName: 'List', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first.', + options: [], + }; + } + + const response = await zagoMailApiService.getAllLists(auth as string); + + const lists = response.data as { + records: Array<{ general: { list_uid: string; name: string } }>; + }; + + return { + disabled: false, + options: lists.records.map((list) => ({ + label: list.general.name, + value: list.general.list_uid, + })), + }; + }, +}); + +export const campaignUid = Property.Dropdown({ + displayName: 'Campaign', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + placeholder: 'Please connect your account first.', + options: [], + }; + } + + const response = await zagoMailApiService.getCampaigns(auth as string); + + const campaigns = response as { + records: Array<{ campaign_uid: string; name: string }>; + }; + + return { + disabled: false, + options: campaigns.records.map((campaign) => ({ + label: campaign.name, + value: campaign.campaign_uid, + })), + }; + }, +}); + +export const listFields = (isCreate=false) => Property.DynamicProperties({ + displayName: 'List Fields', + refreshers: ['listUId'], + required: true, + props: async ({ auth, listUId }) => { + if (!auth || !listUId) return {}; + + const fields: DynamicPropsValue = {}; + + const response = await zagoMailApiService.getListFields( + auth as unknown as string, + listUId as unknown as string, + ); + + const customFields = response as { + records: { tag: string; label: string; required: string; type: { name: string } }[]; + }; + + for (const field of customFields.records) { + switch (field.type.name) { + case 'Text': + case 'Country': + case 'State': + fields[`${field.tag}:::${field.type.name}`] = Property.ShortText({ + displayName: field.label, + required: field.required === 'yes' && isCreate, + }); + break; + case 'Date': + case 'Datetime': + fields[`${field.tag}:::${field.type.name}`] = Property.DateTime({ + displayName: field.label, + required: field.required === 'yes' && isCreate, + }); + break; + case 'Textarea': + fields[`${field.tag}:::${field.type.name}`] = Property.LongText({ + displayName: field.label, + required: field.required === 'yes' && isCreate, + }); + break; + case 'Checkbox': + fields[`${field.tag}:::${field.type.name}`] = Property.StaticDropdown({ + displayName: field.label, + required: field.required === 'yes' && isCreate, + options: { + disabled: false, + options: [ + { label: 'Yes', value: '1' }, + { label: 'No', value: '0' }, + ], + }, + }); + break; + } + } + return fields; + }, +}); diff --git a/packages/pieces/community/zagomail/src/lib/common/request.ts b/packages/pieces/community/zagomail/src/lib/common/request.ts new file mode 100644 index 0000000..504876e --- /dev/null +++ b/packages/pieces/community/zagomail/src/lib/common/request.ts @@ -0,0 +1,225 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { API_ENDPOINTS, BASE_URL } from './constants'; + +async function fireHttpRequest({ + method, + path, + body, +}: { + method: HttpMethod; + path: string; + body?: unknown; +}) { + return await httpClient.sendRequest({ + method, + url: `${BASE_URL}${path}`, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body, + }).then(res => res.body) +} + +export const zagoMailApiService = { + createSubscriber: async ( + publicKey: string, + listUid: string, + body: any + ) => { + const response = await fireHttpRequest({ + method: HttpMethod.POST, + path: `${API_ENDPOINTS.CREATE_SUBSCRIBER}?list_uid=${listUid}`, + body: { + ...body, + publicKey, + }, + }); + + return response.data.record; + }, + updateSubscriber: async ( + publicKey: string, + listUid: string, + subscriberUid: string, + body: any + ) => { + const response = await fireHttpRequest({ + method: HttpMethod.POST, + path: `${API_ENDPOINTS.UPDATE_SUBSCRIBER}?list_uid=${listUid}&subscriber_uid=${subscriberUid}`, + body: { + ...body, + publicKey, + }, + }); + + return response.data.record; + }, + unsubscribeSubscriber: async ( + publicKey: string, + listUid: string, + subscriberUid: string + ) => { + const response = await fireHttpRequest({ + method: HttpMethod.POST, + path: `${API_ENDPOINTS.UNSUBSCRIBE_SUBSCRIBER}?list_uid=${listUid}&subscriber_uid=${subscriberUid}`, + body: { + publicKey, + }, + }); + + return response.data; + }, + searchSubscriberByEmail: async ( + publicKey: string, + listUid: string, + body: { + email: string; + } + ) => { + const response = await fireHttpRequest({ + method: HttpMethod.POST, + path: `${API_ENDPOINTS.SEARCH_SUBSCRIBER_BY_EMAIL}?list_uid=${listUid}`, + body: { + ...body, + publicKey, + }, + }); + + return response; + }, + getSubscriberDetails: async ( + publicKey: string, + listUid: string, + subscriberUid: string + ) => { + const response = await fireHttpRequest({ + method: HttpMethod.GET, + path: `${API_ENDPOINTS.GET_SUBSCRIBER}?list_uid=${listUid}&subscriber_uid=${subscriberUid}`, + body: { + publicKey, + }, + }); + + return response.data; + }, + getCampaignDetails: async ( + publicKey: string, + campaignUid: string, + ) => { + const response = await fireHttpRequest({ + method: HttpMethod.GET, + path: `${API_ENDPOINTS.GET_CAMPAIGNS}?campaign_uid=${campaignUid}`, + body: { + publicKey, + }, + }); + + return response.data; + }, + addTagToSubscriber: async ( + publicKey: string, + { + listUid, + tagId, + subscriberUid, + }: { listUid: string; subscriberUid: string; tagId: string } + ) => { + const response = await fireHttpRequest({ + method: HttpMethod.POST, + path: `${API_ENDPOINTS.ADD_TAG_TO_SUBSCRIBER}?ztag_id=${tagId}&list_uid=${listUid}&subscriber_uid=${subscriberUid}`, + body: { + publicKey, + }, + }); + + return response; + }, + createTag: async (publicKey: string, tagName: string) => { + const response = await fireHttpRequest({ + method: HttpMethod.POST, + path: `${API_ENDPOINTS.CREATE_TAG}?tag_name=${tagName}`, + body: { + publicKey, + }, + }); + + return response.tag; + }, + getTags: async (publicKey: string) => { + const response = await fireHttpRequest({ + method: HttpMethod.GET, + path: API_ENDPOINTS.GET_TAGS, + body: { + publicKey, + }, + }) + + return response.tags; + }, + createWebhook: async ( + publicKey: string, + webhookUrl: string, + event_type: 'subscriber-activate' | 'subscriber-unsubscribe' | 'tag-added', + extraParams?: { + formID?: string; + tagID?: string; + linkUrl?: string; + } + ) => { + const response = await fireHttpRequest({ + method: HttpMethod.POST, + path: API_ENDPOINTS.CREATE_WEBHOOK, + body: { + publicKey, + event_type, + target_url: webhookUrl, + ...extraParams, + }, + }); + + return response.webhook; + }, + deleteWebhook: async (publicKey: string, webhookId: string) => { + return fireHttpRequest({ + method: HttpMethod.POST, + path: `${API_ENDPOINTS.DELETE_WEBHOOK}?id=${webhookId}`, + body: { + publicKey, + }, + }); + }, + getAllLists:async (publicKey:string) =>{ + const response = await fireHttpRequest({ + method:HttpMethod.GET, + path:`${API_ENDPOINTS.LIST_ALL_LISTS}`, + body:{ + publicKey + } + }) + + return response; + }, + getListFields:async (publicKey:string,listUid:string)=>{ + const response = await fireHttpRequest({ + method:HttpMethod.GET, + path:`${API_ENDPOINTS.GET_LIST_FIELDS}?list_uid=${listUid}`, + body:{ + publicKey + } + }) + + return response.data + }, + getCampaigns:async (publicKey:string)=>{ + const response = await fireHttpRequest({ + method:HttpMethod.GET, + path:`${API_ENDPOINTS.GET_CAMPAIGNS}`, + body:{ + publicKey + } + }) + + return response.data + } +}; diff --git a/packages/pieces/community/zagomail/src/lib/triggers/added-subscriber.ts b/packages/pieces/community/zagomail/src/lib/triggers/added-subscriber.ts new file mode 100644 index 0000000..61a3dde --- /dev/null +++ b/packages/pieces/community/zagomail/src/lib/triggers/added-subscriber.ts @@ -0,0 +1,51 @@ +import { + createTrigger, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { isNil } from '@activepieces/shared'; +import { zagomailAuth } from '../../index'; +import { zagoMailApiService, } from '../common/request'; +import { StoredWebhookId, WebhookResponse } from '../common/constants'; + +const CACHE_KEY = 'zagomail_added_subscriber_trigger'; + +export const addedSubscriber = createTrigger({ + auth: zagomailAuth, + name: 'addedSubscriber', + displayName: 'Subscriber Added', + description: 'Triggers when subscriber is signed up or confirmed.', + props: {}, + type: TriggerStrategy.WEBHOOK, + sampleData: { + action: 'subscriber-activate', + subscriber_uid: 'dg307jyx044e1', + list_uid: 'or449cjkqqfb2', + email: 'gs03dev@gmail.com', + status: 'confirmed', + created_at: '2025-05-11 08:26:16', + custom_fields: { + FNAME: 'gs03', + LNAME: 'dev', + }, + }, + async onEnable(context) { + const response = (await zagoMailApiService.createWebhook( + context.auth, + context.webhookUrl, + 'subscriber-activate' + )) as WebhookResponse; + + await context.store.put(CACHE_KEY, { + webhookId: response.id, + }); + }, + async onDisable(context) { + const webhook = await context.store.get(CACHE_KEY); + if (!isNil(webhook) && !isNil(webhook.webhookId)) { + await zagoMailApiService.deleteWebhook(context.auth, webhook.webhookId); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/zagomail/src/lib/triggers/tagged-subscriber.ts b/packages/pieces/community/zagomail/src/lib/triggers/tagged-subscriber.ts new file mode 100644 index 0000000..6867884 --- /dev/null +++ b/packages/pieces/community/zagomail/src/lib/triggers/tagged-subscriber.ts @@ -0,0 +1,76 @@ +import { zagomailAuth } from '../../'; +import { + createTrigger, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { zagoMailApiService } from '../common/request'; +import { StoredWebhookId, Tag, WebhookResponse } from '../common/constants'; +import { isNil } from '@activepieces/shared'; + +const CACHE_KEY = 'zagomail_tagged_subscriber_trigger'; + +export const taggedSubscriber = createTrigger({ + auth: zagomailAuth, + name: 'taggedSubscriber', + displayName: 'Tagged Subscriber', + description: 'Trigers when subscriber is tagged with a tag.', + props: { + tagName: Property.ShortText({ + displayName: 'Tag Name', + description: 'An Arbitrary name you would like to call this tag.', + required: true, + }), + }, + sampleData: { + action: 'tag-added', + subscriber_uid: 'dg307jyx044e1', + list_uid: 'or449cjkqqfb2', + tagID: '38185', + email: 'gs03dev@gmail.com', + status: 'confirmed', + created_at: '2025-05-11 08:26:16', + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const tagName = context.propsValue.tagName; + + const tags = (await zagoMailApiService.getTags(context.auth)) as Tag[]; + + const tagExists = tags.find((t) => t.ztag_name === tagName); + + let tag; + + if (tagExists) { + tag = tagExists; + } else { + tag = (await zagoMailApiService.createTag(context.auth, tagName)) as Tag; + } + + try { + const response = (await zagoMailApiService.createWebhook( + context.auth, + context.webhookUrl, + 'tag-added', + { + tagID: tag.ztag_id, + } + )) as WebhookResponse; + + await context.store.put(CACHE_KEY, { + webhookId: response.id, + }); + } catch (err: any) { + throw new Error(err); + } + }, + async onDisable(context) { + const webhook = await context.store.get(CACHE_KEY); + if (!isNil(webhook) && !isNil(webhook.webhookId)) { + await zagoMailApiService.deleteWebhook(context.auth, webhook.webhookId); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); diff --git a/packages/pieces/community/zagomail/src/lib/triggers/unsubscribed-subscriber.ts b/packages/pieces/community/zagomail/src/lib/triggers/unsubscribed-subscriber.ts new file mode 100644 index 0000000..835ddc5 --- /dev/null +++ b/packages/pieces/community/zagomail/src/lib/triggers/unsubscribed-subscriber.ts @@ -0,0 +1,49 @@ + +import { zagomailAuth } from '../../'; +import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; +import { zagoMailApiService } from '../common/request'; +import { StoredWebhookId, WebhookResponse } from '../common/constants'; +import { isNil } from '@activepieces/shared'; + +const CACHE_KEY = 'zagomail_unsubscribed_subscriber_trigger_store'; + +export const unsubscribedSubscriber = createTrigger({ + auth: zagomailAuth, + name: 'unsubscribedSubscriber', + displayName: 'Unsubscribed Subscriber', + description: 'Triggers when subscriber is unsubscribed.', + props: {}, + sampleData: { + action: 'subscriber-unsubscribe', + subscriber_uid: 'dg307jyx044e1', + list_uid: 'or449cjkqqfb2', + email: 'gs03dev@gmail.com', + status: 'unsubscribed', + created_at: '2025-05-11 08:26:16', + custom_fields: { + FNAME: 'gs03', + LNAME: 'dev', + }, + }, + type: TriggerStrategy.WEBHOOK, + async onEnable(context) { + const response = (await zagoMailApiService.createWebhook( + context.auth, + context.webhookUrl, + 'subscriber-unsubscribe' + )) as WebhookResponse; + + await context.store.put(CACHE_KEY, { + webhookId: response.id, + }); + }, + async onDisable(context) { + const webhook = await context.store.get(CACHE_KEY); + if (!isNil(webhook) && !isNil(webhook.webhookId)) { + await zagoMailApiService.deleteWebhook(context.auth, webhook.webhookId); + } + }, + async run(context) { + return [context.payload.body]; + }, +}); \ No newline at end of file diff --git a/packages/pieces/community/zagomail/tsconfig.json b/packages/pieces/community/zagomail/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/zagomail/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/zagomail/tsconfig.lib.json b/packages/pieces/community/zagomail/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/zagomail/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/zendesk/.eslintrc.json b/packages/pieces/community/zendesk/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/zendesk/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/zendesk/README.md b/packages/pieces/community/zendesk/README.md new file mode 100644 index 0000000..04783ce --- /dev/null +++ b/packages/pieces/community/zendesk/README.md @@ -0,0 +1,7 @@ +# pieces-zendesk + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-zendesk` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/zendesk/package.json b/packages/pieces/community/zendesk/package.json new file mode 100644 index 0000000..b49b264 --- /dev/null +++ b/packages/pieces/community/zendesk/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-zendesk", + "version": "0.1.4" +} \ No newline at end of file diff --git a/packages/pieces/community/zendesk/project.json b/packages/pieces/community/zendesk/project.json new file mode 100644 index 0000000..49b3aa6 --- /dev/null +++ b/packages/pieces/community/zendesk/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-zendesk", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/zendesk/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/zendesk", + "tsConfig": "packages/pieces/community/zendesk/tsconfig.lib.json", + "packageJson": "packages/pieces/community/zendesk/package.json", + "main": "packages/pieces/community/zendesk/src/index.ts", + "assets": [ + "packages/pieces/community/zendesk/*.md", + { + "input": "packages/pieces/community/zendesk/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/zendesk/src/index.ts b/packages/pieces/community/zendesk/src/index.ts new file mode 100644 index 0000000..93649f3 --- /dev/null +++ b/packages/pieces/community/zendesk/src/index.ts @@ -0,0 +1,66 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { newTicketInView } from './lib/trigger/new-ticket-in-view'; + +const markdownProperty = ` +**Organization**: The organization name can be found in the URL (e.g https://ORGANIZATION_NAME.zendesk.com). + +**Agent Email**: The email you use to log in to Zendesk. + +**API Token**: You can find this in the Zendesk Admin Panel under Settings > APIs > Zendesk API. +`; + +export const zendeskAuth = PieceAuth.CustomAuth({ + description: markdownProperty, + props: { + email: Property.ShortText({ + displayName: 'Agent Email', + description: 'The email address you use to login to Zendesk', + required: true, + }), + token: Property.ShortText({ + displayName: 'Token', + description: 'The API token you can generate in Zendesk', + required: true, + }), + subdomain: Property.ShortText({ + displayName: 'Organization (e.g activepieceshelp)', + description: 'The subdomain of your Zendesk instance', + required: true, + }), + }, + required: true, +}); + +export const zendesk = createPiece({ + displayName: 'Zendesk', + description: 'Customer service software and support ticket system', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/zendesk.png', + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud"], + categories: [PieceCategory.CUSTOMER_SUPPORT], + auth: zendeskAuth, + actions: [ + createCustomApiCallAction({ + baseUrl: (auth) => + `https://${ + (auth as { subdomain: string }).subdomain + }.zendesk.com/api/v2`, + auth: zendeskAuth, + authMapping: async (auth) => ({ + Authorization: `Basic ${Buffer.from( + `${(auth as { email: string }).email}/token:${ + (auth as { token: string }).token + }` + ).toString('base64')}`, + }), + }), + ], + triggers: [newTicketInView], +}); diff --git a/packages/pieces/community/zendesk/src/lib/trigger/new-ticket-in-view.ts b/packages/pieces/community/zendesk/src/lib/trigger/new-ticket-in-view.ts new file mode 100644 index 0000000..537ec0d --- /dev/null +++ b/packages/pieces/community/zendesk/src/lib/trigger/new-ticket-in-view.ts @@ -0,0 +1,168 @@ +import { + TriggerStrategy, + createTrigger, + Property, +} from '@activepieces/pieces-framework'; +import { + AuthenticationType, + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { zendeskAuth } from '../..'; + +export const newTicketInView = createTrigger({ + auth: zendeskAuth, + name: 'new_ticket_in_view', + displayName: 'New ticket in view', + description: 'Triggers when a new ticket is created in a view', + type: TriggerStrategy.POLLING, + props: { + view_id: Property.Dropdown({ + displayName: 'View', + description: 'The view to monitor for new tickets', + refreshers: [], + required: true, + options: async ({ auth }) => { + const authentication = auth as AuthProps; + if ( + !authentication?.['email'] || + !authentication?.['subdomain'] || + !authentication?.['token'] + ) { + return { + placeholder: 'Fill your authentication first', + disabled: true, + options: [], + }; + } + const response = await httpClient.sendRequest<{ views: any[] }>({ + url: `https://${authentication.subdomain}.zendesk.com/api/v2/views.json`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BASIC, + username: authentication.email + '/token', + password: authentication.token, + }, + }); + return { + placeholder: 'Select a view', + options: response.body.views.map((view: any) => ({ + label: view.title, + value: view.id, + })), + }; + }, + }), + }, + sampleData: { + url: 'https://activepieceshelp.zendesk.com/api/v2/tickets/5.json', + id: 5, + external_id: null, + via: { + channel: 'web', + source: { + from: {}, + to: {}, + rel: null, + }, + }, + created_at: '2023-03-25T02:39:41Z', + updated_at: '2023-03-25T02:39:41Z', + type: null, + subject: 'Subject', + raw_subject: 'Raw Subject', + description: 'Description', + priority: null, + status: 'open', + recipient: null, + requester_id: 8193592318236, + submitter_id: 8193592318236, + assignee_id: 8193592318236, + organization_id: 8193599387420, + group_id: 8193569448092, + collaborator_ids: [], + follower_ids: [], + email_cc_ids: [], + forum_topic_id: null, + problem_id: null, + has_incidents: false, + is_public: true, + due_at: null, + tags: [], + custom_fields: [], + satisfaction_rating: null, + sharing_agreement_ids: [], + custom_status_id: 8193592472348, + fields: [], + followup_ids: [], + ticket_form_id: 8193569410076, + brand_id: 8193583542300, + allow_channelback: false, + allow_attachments: true, + from_messaging_channel: false, + }, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, + test: async (context) => { + return await pollingHelper.test(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, +}); + +type AuthProps = { + email: string; + token: string; + subdomain: string; +}; + +const polling: Polling = { + strategy: DedupeStrategy.LAST_ITEM, + items: async ({ auth, propsValue }) => { + const items = await getTickets(auth, propsValue.view_id); + return items.map((item) => ({ + id: item.id, + data: item, + })); + }, +}; + +async function getTickets(authentication: AuthProps, view_id: string) { + const { email, token, subdomain } = authentication; + const response = await httpClient.sendRequest<{ tickets: any[] }>({ + url: `https://${subdomain}.zendesk.com/api/v2/views/${view_id}/tickets.json?sort_order=desc&sort_by=created_at&per_page=200`, + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BASIC, + username: email + '/token', + password: token, + }, + }); + return response.body.tickets; +} diff --git a/packages/pieces/community/zendesk/tsconfig.json b/packages/pieces/community/zendesk/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/zendesk/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/zendesk/tsconfig.lib.json b/packages/pieces/community/zendesk/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/zendesk/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/zerobounce/.eslintrc.json b/packages/pieces/community/zerobounce/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/zerobounce/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/zerobounce/README.md b/packages/pieces/community/zerobounce/README.md new file mode 100644 index 0000000..facaf23 --- /dev/null +++ b/packages/pieces/community/zerobounce/README.md @@ -0,0 +1,7 @@ +# pieces-zerobounce + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-zerobounce` to build the library. diff --git a/packages/pieces/community/zerobounce/package.json b/packages/pieces/community/zerobounce/package.json new file mode 100644 index 0000000..54b2a6c --- /dev/null +++ b/packages/pieces/community/zerobounce/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-zerobounce", + "version": "0.0.1" +} diff --git a/packages/pieces/community/zerobounce/project.json b/packages/pieces/community/zerobounce/project.json new file mode 100644 index 0000000..3403e1f --- /dev/null +++ b/packages/pieces/community/zerobounce/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-zerobounce", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/zerobounce/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/zerobounce", + "tsConfig": "packages/pieces/community/zerobounce/tsconfig.lib.json", + "packageJson": "packages/pieces/community/zerobounce/package.json", + "main": "packages/pieces/community/zerobounce/src/index.ts", + "assets": [ + "packages/pieces/community/zerobounce/*.md", + { + "input": "packages/pieces/community/zerobounce/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-zerobounce {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/zerobounce/src/index.ts b/packages/pieces/community/zerobounce/src/index.ts new file mode 100644 index 0000000..5673859 --- /dev/null +++ b/packages/pieces/community/zerobounce/src/index.ts @@ -0,0 +1,19 @@ + +import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { validateEmail } from "./lib/actions/validate-email"; + +export const zerobounceAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, +}); + +export const zerobounce = createPiece({ + displayName: "ZeroBounce", + auth: zerobounceAuth, + description: "ZeroBounce is an email validation service that helps you reduce bounces, improve email deliverability and increase email marketing ROI.", + minimumSupportedRelease: '0.30.0', + logoUrl: "https://cdn.activepieces.com/pieces/zerobounce.png", + authors: ["abuaboud"], + actions: [validateEmail], + triggers: [], +}); diff --git a/packages/pieces/community/zerobounce/src/lib/actions/validate-email.ts b/packages/pieces/community/zerobounce/src/lib/actions/validate-email.ts new file mode 100644 index 0000000..015e759 --- /dev/null +++ b/packages/pieces/community/zerobounce/src/lib/actions/validate-email.ts @@ -0,0 +1,40 @@ +import { zerobounceAuth } from '../..'; +import { createAction, Property, StoreScope } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; + +export const validateEmail = createAction({ + name: 'validateEmail', + displayName: 'Validate Email', + description: '', + props: { + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + cacheResponse: Property.Checkbox({ + displayName: 'Cache Response', + description: 'Store the response in the project store for future use.', + required: false, + defaultValue: true, + }), + }, + auth: zerobounceAuth, + async run({ store, propsValue, auth }) { + const key = `_zerobounce_${propsValue.email}`; + if (propsValue.cacheResponse) { + const cachedResponse = await store.get(key, StoreScope.PROJECT); + if (!isNil(cachedResponse)) { + return cachedResponse + } + } + const res = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zerobounce.net/v2/validate?email=${propsValue.email}&api_key=${auth}`, + }); + if (propsValue.cacheResponse) { + await store.put(key, res.body, StoreScope.PROJECT); + } + return res.body; + }, +}); diff --git a/packages/pieces/community/zerobounce/tsconfig.json b/packages/pieces/community/zerobounce/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/zerobounce/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/zerobounce/tsconfig.lib.json b/packages/pieces/community/zerobounce/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/zerobounce/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/zoho-books/.eslintrc.json b/packages/pieces/community/zoho-books/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/zoho-books/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/zoho-books/README.md b/packages/pieces/community/zoho-books/README.md new file mode 100644 index 0000000..1066082 --- /dev/null +++ b/packages/pieces/community/zoho-books/README.md @@ -0,0 +1,7 @@ +# pieces-zoho-books + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-zoho-books` to build the library. diff --git a/packages/pieces/community/zoho-books/package.json b/packages/pieces/community/zoho-books/package.json new file mode 100644 index 0000000..e48bc98 --- /dev/null +++ b/packages/pieces/community/zoho-books/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-zoho-books", + "version": "0.0.2" +} diff --git a/packages/pieces/community/zoho-books/project.json b/packages/pieces/community/zoho-books/project.json new file mode 100644 index 0000000..c6dabd5 --- /dev/null +++ b/packages/pieces/community/zoho-books/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-zoho-books", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/zoho-books/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/zoho-books", + "tsConfig": "packages/pieces/community/zoho-books/tsconfig.lib.json", + "packageJson": "packages/pieces/community/zoho-books/package.json", + "main": "packages/pieces/community/zoho-books/src/index.ts", + "assets": [ + "packages/pieces/community/zoho-books/*.md", + { + "input": "packages/pieces/community/zoho-books/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-zoho-books {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/zoho-books/src/index.ts b/packages/pieces/community/zoho-books/src/index.ts new file mode 100644 index 0000000..8742baf --- /dev/null +++ b/packages/pieces/community/zoho-books/src/index.ts @@ -0,0 +1,76 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; + +export const zohoBooksAuth = PieceAuth.OAuth2({ + props: { + location: Property.StaticDropdown({ + displayName: 'Location', + description: 'The location of your Zoho Books account', + required: true, + options: { + options: [ + { + label: 'zoho.eu (Europe)', + value: 'zoho.eu', + }, + { + label: 'zoho.com (United States)', + value: 'zoho.com', + }, + { + label: 'zoho.com.au (Australia)', + value: 'zoho.com.au', + }, + { + label: 'zoho.jp (Japan)', + value: 'zoho.jp', + }, + { + label: 'zoho.in (India)', + value: 'zoho.in', + }, + { + label: 'zohocloud.ca (Canada)', + value: 'zohocloud.ca', + }, + ], + }, + }), + }, + description: 'Authentication for Zoho Books', + scope: ['ZohoBooks.fullaccess.all'], + authUrl: 'https://accounts.{location}/oauth/v2/auth', + tokenUrl: 'https://accounts.{location}/oauth/v2/token', + required: true, +}); + +export const zohoBooks = createPiece({ + displayName: "Zoho Books", + description: 'Comprehensive online accounting software for small businesses.', + logoUrl: "https://cdn.activepieces.com/pieces/zoho-books.png", + minimumSupportedRelease: '0.30.0', + categories: [PieceCategory.ACCOUNTING], + authors: ["ikus060"], + auth: zohoBooksAuth, + actions: [ + createCustomApiCallAction({ + baseUrl: (auth) => + { + const data = (auth as OAuth2PropertyValue).data; + return data && data['api_domain']? `${data['api_domain']}/books/v3` : '' + }, + auth: zohoBooksAuth, + authMapping: async (auth) => ({ + Authorization: `Zoho-oauthtoken ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [], +}); + \ No newline at end of file diff --git a/packages/pieces/community/zoho-books/tsconfig.json b/packages/pieces/community/zoho-books/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/zoho-books/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/zoho-books/tsconfig.lib.json b/packages/pieces/community/zoho-books/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/zoho-books/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/zoho-crm/.eslintrc.json b/packages/pieces/community/zoho-crm/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/zoho-crm/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/zoho-crm/README.md b/packages/pieces/community/zoho-crm/README.md new file mode 100644 index 0000000..7e9eaaf --- /dev/null +++ b/packages/pieces/community/zoho-crm/README.md @@ -0,0 +1,7 @@ +# pieces-zoho-crm + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-zoho-crm` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/zoho-crm/package.json b/packages/pieces/community/zoho-crm/package.json new file mode 100644 index 0000000..edda87d --- /dev/null +++ b/packages/pieces/community/zoho-crm/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-zoho-crm", + "version": "0.1.8" +} \ No newline at end of file diff --git a/packages/pieces/community/zoho-crm/project.json b/packages/pieces/community/zoho-crm/project.json new file mode 100644 index 0000000..1e63edf --- /dev/null +++ b/packages/pieces/community/zoho-crm/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-zoho-crm", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/zoho-crm/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/zoho-crm", + "tsConfig": "packages/pieces/community/zoho-crm/tsconfig.lib.json", + "packageJson": "packages/pieces/community/zoho-crm/package.json", + "main": "packages/pieces/community/zoho-crm/src/index.ts", + "assets": [ + "packages/pieces/community/zoho-crm/*.md", + { + "input": "packages/pieces/community/zoho-crm/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/zoho-crm/src/index.ts b/packages/pieces/community/zoho-crm/src/index.ts new file mode 100644 index 0000000..02ef5d9 --- /dev/null +++ b/packages/pieces/community/zoho-crm/src/index.ts @@ -0,0 +1,80 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + Property, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { newContact } from './lib/triggers/new-contact'; +import { readFile } from './lib/actions/read-file'; + +export const zohoCrmAuth = PieceAuth.OAuth2({ + props: { + location: Property.StaticDropdown({ + displayName: 'Location', + description: 'The location of your Zoho CRM account', + required: true, + options: { + options: [ + { + label: 'zoho.eu (Europe)', + value: 'zoho.eu', + }, + { + label: 'zoho.com (United States)', + value: 'zoho.com', + }, + { + label: 'zoho.com.au (Australia)', + value: 'zoho.com.au', + }, + { + label: 'zoho.jp (Japan)', + value: 'zoho.jp', + }, + { + label: 'zoho.in (India)', + value: 'zoho.in', + }, + { + label: 'zohocloud.ca (Canada)', + value: 'zohocloud.ca', + }, + ], + }, + }), + }, + description: 'Authentication for Zoho CRM', + scope: ['ZohoCRM.users.ALL','ZohoCRM.org.ALL', 'ZohoCRM.settings.ALL', 'ZohoCRM.modules.ALL', 'ZohoCRM.bulk.ALL', 'ZohoCRM.bulk.backup.ALL', 'ZohoFiles.files.ALL'], + authUrl: 'https://accounts.{location}/oauth/v2/auth', + tokenUrl: 'https://accounts.{location}/oauth/v2/token', + required: true, +}); + +export const zohoCrm = createPiece({ + displayName: 'Zoho CRM', + description: 'Customer relationship management software', + + logoUrl: 'https://cdn.activepieces.com/pieces/zoho-crm.png', + minimumSupportedRelease: '0.30.0', + categories: [PieceCategory.SALES_AND_CRM], + authors: ["kishanprmr","MoShizzle","khaledmashaly","abuaboud","ikus060"], + auth: zohoCrmAuth, + actions: [ + readFile, + createCustomApiCallAction({ + baseUrl: (auth) => + { + const data = (auth as OAuth2PropertyValue).data; + return data && data['api_domain']? `${data['api_domain']}/crm/v3` : '' + }, + + auth: zohoCrmAuth, + authMapping: async (auth) => ({ + Authorization: `Zoho-oauthtoken ${(auth as OAuth2PropertyValue).access_token}`, + }), + }), + ], + triggers: [newContact], +}); diff --git a/packages/pieces/community/zoho-crm/src/lib/actions/read-file.ts b/packages/pieces/community/zoho-crm/src/lib/actions/read-file.ts new file mode 100644 index 0000000..1bc8b05 --- /dev/null +++ b/packages/pieces/community/zoho-crm/src/lib/actions/read-file.ts @@ -0,0 +1,45 @@ +import { zohoCrmAuth } from '../../index'; +import { Property, createAction } from "@activepieces/pieces-framework"; + + +export const readFile = createAction({ + auth: zohoCrmAuth, + name: 'read-file', + displayName: 'Read file', + description: 'Download a file content from Zoho CRM. e.g.: a Backup File', + props: { + url: Property.ShortText({ + displayName: 'URL', + description: 'The full URL to use, including the base URL', + required: true, + defaultValue: '', + }) + }, + run: async ({ auth, propsValue, files }) => { + const url = propsValue['url']; + + const download = await fetch(url, { + headers: { + Authorization: `Bearer ${auth.access_token}`, + }, + }) + .then((response) => + response.ok ? response.blob() : Promise.reject(response) + ) + .catch((error) => + Promise.reject( + new Error( + `Error when download file:\n\tDownload file response: ${(error as Error).message ?? error + }` + ) + ) + ); + + const fileName = url.split('/').pop() ?? url; + + return files.write({ + fileName: fileName, + data: Buffer.from(await download.arrayBuffer()), + }); + }, +}); diff --git a/packages/pieces/community/zoho-crm/src/lib/triggers/new-contact.ts b/packages/pieces/community/zoho-crm/src/lib/triggers/new-contact.ts new file mode 100644 index 0000000..57f7d3b --- /dev/null +++ b/packages/pieces/community/zoho-crm/src/lib/triggers/new-contact.ts @@ -0,0 +1,196 @@ +import { + AuthenticationType, + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; +import { + createTrigger, + OAuth2PropertyValue, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; +import { zohoCrmAuth } from '../..'; + +export const newContact = createTrigger({ + auth: zohoCrmAuth, + + name: 'new_contact', + displayName: 'New Contact', + description: 'Triggers when a new contact is created', + sampleData: { + Owner: { + name: 'Activepieces Apps', + id: '560094000000343001', + email: 'apps@activepieces.com', + }, + Email: 'capla-paprocki@yahoo.com', + Description: null, + $currency_symbol: '$', + Vendor_Name: null, + Mailing_Zip: '99501', + $field_states: null, + Other_Phone: null, + Mailing_State: 'AK', + $review_process: { + approve: false, + reject: false, + resubmit: false, + }, + Twitter: 'lpaprocki_sample', + Other_Zip: null, + Mailing_Street: '639 Main St', + Other_State: null, + $sharing_permission: 'full_access', + Salutation: null, + Other_Country: null, + Last_Activity_Time: '2023-03-26T00:02:28+01:00', + First_Name: 'Capla', + Full_Name: 'Capla Paprocki (Sample)', + Asst_Phone: null, + Record_Image: + 'd7d6bec0cbbfd9f3b84ebcd2eba41e9fa432f48560f9ed267b2e5b26eb58a07f5451e24ca9042b39f05459c41291c005b0dea6b224d375a6030f4096eb631fa3d4dcabb97393f1dc2470eb1658164f05', + Department: 'Admin', + Modified_By: { + name: 'Activepieces Apps', + id: '560094000000343001', + email: 'apps@activepieces.com', + }, + $review: null, + $state: 'save', + Skype_ID: 'lpaprocki', + Unsubscribed_Mode: null, + $process_flow: false, + Assistant: null, + Phone: '555-555-5555', + Mailing_Country: 'United States', + id: '560094000000349199', + Reporting_To: null, + $approval: { + delegate: false, + approve: false, + reject: false, + resubmit: false, + }, + Enrich_Status__s: null, + Other_City: null, + Created_Time: '2023-03-26T00:01:56+01:00', + $wizard_connection_path: null, + $editable: true, + Home_Phone: null, + Created_By: { + name: 'Activepieces Apps', + id: '560094000000343001', + email: 'apps@activepieces.com', + }, + $zia_owner_assignment: 'owner_recommendation_unavailable', + Secondary_Email: null, + }, + type: TriggerStrategy.POLLING, + props: {}, + async run(context) { + return await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, + async test({ auth, propsValue, store, files }): Promise { + return await pollingHelper.test(polling, { + auth, + store: store, + propsValue: propsValue, + files: files, + }); + }, + async onEnable({ auth, propsValue, store }): Promise { + await pollingHelper.onEnable(polling, { + auth, + store: store, + propsValue: propsValue, + }); + }, + async onDisable({ auth, propsValue, store }): Promise { + await pollingHelper.onDisable(polling, { + auth, + store: store, + propsValue: propsValue, + }); + }, +}); + +const polling: Polling = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth }) => { + const response = await httpClient.sendRequest<{ + data: { Created_Time: string }[]; + }>({ + url: `${auth.data.api_domain}/crm/v4/Contacts`, + method: HttpMethod.GET, + queryParams: { + perPage: '200', + sort_order: 'desc', + sort_by: 'Created_Time', + fields: [ + 'Owner', + 'Email', + '$currency_symbol', + '$field_states', + 'Other_Phone', + 'Mailing_State', + 'Other_State', + '$sharing_permission', + 'Other_Country', + 'Last_Activity_Time', + 'Department', + '$state', + 'Unsubscribed_Mode', + '$process_flow', + 'Assistant', + 'Mailing_Country', + 'id', + 'Reporting_To', + '$approval', + 'Enrich_Status__s', + 'Other_City', + 'Created_Time', + '$wizard_connection_path', + '$editable', + 'Home_Phone', + 'Created_By', + '$zia_owner_assignment', + 'Secondary_Email', + 'Description', + 'Vendor_Name', + 'Mailing_Zip', + '$review_process', + 'Twitter', + 'Other_Zip', + 'Mailing_Street', + '$canvas_id', + 'Salutation', + 'First_Name', + 'Full_Name', + 'Asst_Phone', + 'Record_Image', + 'Modified_By', + '$review', + 'Skype_ID', + 'Phone', + 'Account_Name', + ].join(','), + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }); + return response.body.data.map((record) => ({ + epochMilliSeconds: dayjs(record.Created_Time).valueOf(), + data: record, + })); + }, +}; diff --git a/packages/pieces/community/zoho-crm/tsconfig.json b/packages/pieces/community/zoho-crm/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/zoho-crm/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/zoho-crm/tsconfig.lib.json b/packages/pieces/community/zoho-crm/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/zoho-crm/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/zoho-desk/.eslintrc.json b/packages/pieces/community/zoho-desk/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/zoho-desk/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/zoho-desk/README.md b/packages/pieces/community/zoho-desk/README.md new file mode 100644 index 0000000..b42ef31 --- /dev/null +++ b/packages/pieces/community/zoho-desk/README.md @@ -0,0 +1,7 @@ +# pieces-zoho-desk + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-zoho-desk` to build the library. diff --git a/packages/pieces/community/zoho-desk/package.json b/packages/pieces/community/zoho-desk/package.json new file mode 100644 index 0000000..4eae22b --- /dev/null +++ b/packages/pieces/community/zoho-desk/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-zoho-desk", + "version": "0.0.1" +} diff --git a/packages/pieces/community/zoho-desk/project.json b/packages/pieces/community/zoho-desk/project.json new file mode 100644 index 0000000..52b27e0 --- /dev/null +++ b/packages/pieces/community/zoho-desk/project.json @@ -0,0 +1,45 @@ +{ + "name": "pieces-zoho-desk", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/zoho-desk/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/zoho-desk", + "tsConfig": "packages/pieces/community/zoho-desk/tsconfig.lib.json", + "packageJson": "packages/pieces/community/zoho-desk/package.json", + "main": "packages/pieces/community/zoho-desk/src/index.ts", + "assets": [ + "packages/pieces/community/zoho-desk/*.md" + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/zoho-desk/src/index.ts b/packages/pieces/community/zoho-desk/src/index.ts new file mode 100644 index 0000000..9131d6a --- /dev/null +++ b/packages/pieces/community/zoho-desk/src/index.ts @@ -0,0 +1,46 @@ +import { + OAuth2PropertyValue, + createPiece, + PiecePropValueSchema, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { listTicketsAction } from './lib/actions/list-tickets'; +import { createTicketAction } from './lib/actions/create-ticket'; +import { organizationId } from './lib/common/props'; +import { findContactAction } from './lib/actions/find-contact'; +import { zohoDeskAuth } from './lib/common/auth'; + +export const piecesZohoDesk = createPiece({ + displayName: 'Zoho Desk', + description: 'Helpdesk management software', + auth: zohoDeskAuth, + categories: [PieceCategory.CUSTOMER_SUPPORT], + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/zoho-desk.png', + authors: ['volubile', 'kishanprmr'], + actions: [ + listTicketsAction, + createTicketAction, + findContactAction, + createCustomApiCallAction({ + baseUrl: (auth) => { + const authValue = auth as PiecePropValueSchema; + const location = authValue.props?.['location'] ?? 'zoho.com'; + return `https://desk.${location}/api/v1`; + }, + auth: zohoDeskAuth, + authMapping: async (auth) => ({ + Authorization: `Zoho-oauthtoken ${(auth as OAuth2PropertyValue).access_token}`, + }), + extraProps: { + orgId: organizationId({ + displayName: 'Organization ID', + description: 'Select organization ID to include in auth headers.', + required: false, + }), + }, + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/zoho-desk/src/lib/actions/create-ticket.ts b/packages/pieces/community/zoho-desk/src/lib/actions/create-ticket.ts new file mode 100644 index 0000000..4cc940b --- /dev/null +++ b/packages/pieces/community/zoho-desk/src/lib/actions/create-ticket.ts @@ -0,0 +1,185 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { zohoDeskAuth } from '../common/auth'; +import { zohoDeskApiCall } from '../common'; +import { departmentId, organizationId } from '../common/props'; + +export const createTicketAction = createAction({ + auth: zohoDeskAuth, + name: 'create_ticket', + displayName: 'Create Ticket', + description: 'Creates a new ticket.', + props: { + orgId: organizationId({ displayName: 'Organization', required: true }), + departmentId: departmentId({ displayName: 'Department', required: true }), + contactId: Property.ShortText({ + displayName: 'Contact ID', + required: false, + description: 'ID of the contact raising the ticket', + }), + subject: Property.ShortText({ + displayName: 'Subject', + required: true, + description: 'Subject of the ticket', + }), + description: Property.LongText({ + displayName: 'Description', + required: true, + description: 'Description of the issue in the ticket', + }), + email: Property.ShortText({ + displayName: 'Email', + required: false, + description: 'Email address of the contact raising the ticket', + }), + phone: Property.ShortText({ + displayName: 'Phone', + required: false, + description: 'Phone number of the contact raising the ticket', + }), + status: Property.StaticDropdown({ + displayName: 'Status', + required: false, + description: 'Status of the ticket', + options: { + options: [ + { label: 'Open', value: 'Open' }, + { label: 'On Hold', value: 'On Hold' }, + { label: 'Escalated', value: 'Escalated' }, + { label: 'Closed', value: 'Closed' }, + ], + }, + defaultValue: 'Open', + }), + priority: Property.StaticDropdown({ + displayName: 'Priority', + required: false, + description: 'Priority of the ticket', + options: { + options: [ + { label: 'Low', value: 'Low' }, + { label: 'Medium', value: 'Medium' }, + { label: 'High', value: 'High' }, + { label: 'Urgent', value: 'Urgent' }, + ], + }, + defaultValue: 'Medium', + }), + category: Property.ShortText({ + displayName: 'Category', + required: false, + description: 'Category of the ticket', + }), + subCategory: Property.ShortText({ + displayName: 'Sub Category', + required: false, + description: 'Sub-category of the ticket', + }), + dueDate: Property.DateTime({ + displayName: 'Due Date', + required: false, + description: 'Due date for the ticket (ISO format)', + }), + channel: Property.StaticDropdown({ + displayName: 'Channel', + required: false, + description: 'Channel through which the ticket is created', + options: { + options: [ + { label: 'Email', value: 'Email' }, + { label: 'Phone', value: 'Phone' }, + { label: 'Chat', value: 'Chat' }, + { label: 'Web', value: 'Web' }, + { label: 'Social', value: 'Social' }, + ], + }, + defaultValue: 'Web', + }), + assigneeId: Property.ShortText({ + displayName: 'Assignee ID', + required: false, + description: 'ID of the agent to whom the ticket is assigned', + }), + productId: Property.ShortText({ + displayName: 'Product ID', + required: false, + description: 'ID of the product to which the ticket belongs', + }), + classification: Property.ShortText({ + displayName: 'Classification', + required: false, + description: 'Classification of the ticket', + }), + language: Property.ShortText({ + displayName: 'Language', + required: false, + description: 'Language of the ticket', + defaultValue: 'English', + }), + entitySkills: Property.Array({ + displayName: 'Entity Skills', + required: false, + description: 'Array of skill IDs associated with the ticket', + }), + customFields: Property.Object({ + displayName: 'Custom Fields', + required: false, + description: 'Custom fields in the ticket', + }), + }, + async run({ propsValue, auth }) { + const { + orgId, + departmentId, + contactId, + subject, + description, + email, + phone, + status, + priority, + category, + subCategory, + dueDate, + channel, + assigneeId, + productId, + classification, + language, + entitySkills, + customFields, + } = propsValue; + + const requestBody: Record = { + departmentId, + subject, + description, + }; + + if (contactId) requestBody['contactId'] = contactId; + if (email) requestBody['email'] = email; + if (phone) requestBody['phone'] = phone; + if (status) requestBody['status'] = status; + if (priority) requestBody['priority'] = priority; + if (category) requestBody['category'] = category; + if (subCategory) requestBody['subCategory'] = subCategory; + if (dueDate) requestBody['dueDate'] = dueDate; + if (channel) requestBody['channel'] = channel; + if (assigneeId) requestBody['assigneeId'] = assigneeId; + if (productId) requestBody['productId'] = productId; + if (classification) requestBody['classification'] = classification; + if (language) requestBody['language'] = language; + if (entitySkills && entitySkills.length > 0) requestBody['entitySkills'] = entitySkills; + if (customFields) requestBody['cf'] = customFields; + + const response = await zohoDeskApiCall({ + auth, + method: HttpMethod.POST, + resourceUri: '/tickets', + orgId, + body: requestBody, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/zoho-desk/src/lib/actions/find-contact.ts b/packages/pieces/community/zoho-desk/src/lib/actions/find-contact.ts new file mode 100644 index 0000000..ad80f9d --- /dev/null +++ b/packages/pieces/community/zoho-desk/src/lib/actions/find-contact.ts @@ -0,0 +1,45 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zohoDeskApiCall } from '../common'; +import { zohoDeskAuth } from '../common/auth'; +import { organizationId } from '../common/props'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/shared'; + +export const findContactAction = createAction({ + auth: zohoDeskAuth, + name: 'find-contact', + displayName: 'Find Contact', + description: 'Finds an existing contact by email.', + props: { + orgId: organizationId({ displayName: 'Organization', required: true }), + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + }, + async run(context) { + const response = await zohoDeskApiCall({ + auth: context.auth, + method: HttpMethod.GET, + resourceUri: '/contacts/search', + orgId: context.propsValue.orgId, + query: { + email: context.propsValue.email, + }, + }); + + if (isNil(response) || response === '') { + return { + found: false, + result: [], + }; + } + + const contacts = response as { data: Record[] }; + + return { + found: contacts.data.length > 0, + result: contacts.data, + }; + }, +}); diff --git a/packages/pieces/community/zoho-desk/src/lib/actions/list-tickets.ts b/packages/pieces/community/zoho-desk/src/lib/actions/list-tickets.ts new file mode 100644 index 0000000..3a9d916 --- /dev/null +++ b/packages/pieces/community/zoho-desk/src/lib/actions/list-tickets.ts @@ -0,0 +1,46 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { zohoDeskAuth } from '../common/auth'; +import { zohoDeskApiCall } from '../common'; +import { organizationId } from '../common/props'; + +export const listTicketsAction = createAction({ + auth: zohoDeskAuth, + name: 'list_tickets', + description: 'List tickets', + displayName: 'List tickets.', + props: { + orgId: organizationId({ displayName: 'Organization', required: true }), + include: Property.StaticMultiSelectDropdown({ + displayName: 'include', + required: false, + description: 'Additional information related to the tickets.', + options: { + disabled: false, + options: [ + { label: 'contacts', value: 'contacts' }, + { label: 'products', value: 'products' }, + { label: 'departments', value: 'departments' }, + { label: 'team', value: 'team' }, + { label: 'isRead', value: 'isRead' }, + { label: 'assignee', value: 'assignee' }, + ], + }, + }), + }, + async run({ propsValue, auth }) { + const queryParams: Record = {}; + + if (propsValue.include) queryParams['include'] = propsValue.include.join(','); + + const response = await zohoDeskApiCall({ + auth, + method: HttpMethod.GET, + resourceUri: '/tickets', + orgId: propsValue.orgId, + query: queryParams, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/zoho-desk/src/lib/common/auth.ts b/packages/pieces/community/zoho-desk/src/lib/common/auth.ts new file mode 100644 index 0000000..f9b9944 --- /dev/null +++ b/packages/pieces/community/zoho-desk/src/lib/common/auth.ts @@ -0,0 +1,60 @@ +import { PieceAuth, Property } from '@activepieces/pieces-framework'; + +export const zohoDeskAuth = PieceAuth.OAuth2({ + props: { + location: Property.StaticDropdown({ + displayName: 'Location', + description: 'The location of your Zoho Desk account.', + required: true, + options: { + options: [ + { + label: 'zoho.eu (Europe)', + value: 'zoho.eu', + }, + { + label: 'zoho.com (United States)', + value: 'zoho.com', + }, + { + label: 'zoho.com.au (Australia)', + value: 'zoho.com.au', + }, + { + label: 'zoho.jp (Japan)', + value: 'zoho.jp', + }, + { + label: 'zoho.in (India)', + value: 'zoho.in', + }, + { + label: 'zohocloud.ca (Canada)', + value: 'zohocloud.ca', + }, + ], + }, + }), + }, + description: 'Authentication for Zoho Desk', + scope: [ + 'Desk.tickets.ALL', + 'Desk.tasks.ALL', + 'Desk.settings.ALL', + 'Desk.events.ALL', + 'Desk.contacts.READ', + 'Desk.contacts.WRITE', + 'Desk.contacts.UPDATE', + 'Desk.contacts.CREATE', + 'Desk.basic.READ', + 'Desk.basic.CREATE', + 'Desk.search.READ', + 'Desk.articles.READ', + 'Desk.articles.CREATE', + 'Desk.articles.UPDATE', + 'Desk.articles.DELETE', + ], + authUrl: 'https://accounts.{location}/oauth/v2/auth', + tokenUrl: 'https://accounts.{location}/oauth/v2/token', + required: true, +}); diff --git a/packages/pieces/community/zoho-desk/src/lib/common/index.ts b/packages/pieces/community/zoho-desk/src/lib/common/index.ts new file mode 100644 index 0000000..b8be518 --- /dev/null +++ b/packages/pieces/community/zoho-desk/src/lib/common/index.ts @@ -0,0 +1,59 @@ +import { + httpClient, + HttpHeaders, + HttpMessageBody, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; +import { PiecePropValueSchema } from '@activepieces/pieces-framework'; +import { zohoDeskAuth } from './auth'; + +export type ZohoDeskApiCallParams = { + auth: PiecePropValueSchema; + orgId?: string; + method: HttpMethod; + resourceUri: string; + query?: Record; + body?: any; +}; + +export async function zohoDeskApiCall({ + auth, + orgId, + method, + resourceUri, + query, + body, +}: ZohoDeskApiCallParams): Promise { + const location = auth.props?.['location'] ?? 'zoho.com'; + const baseUrl = `https://desk.${location}/api/v1`; + const qs: QueryParams = {}; + + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + qs[key] = String(value); + } + } + } + + const headers: HttpHeaders = { + Authorization: `Zoho-oauthtoken ${auth.access_token}`, + }; + + if (orgId) { + headers['orgId'] = orgId; + } + + const request: HttpRequest = { + method, + url: baseUrl + resourceUri, + headers, + queryParams: qs, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} diff --git a/packages/pieces/community/zoho-desk/src/lib/common/props.ts b/packages/pieces/community/zoho-desk/src/lib/common/props.ts new file mode 100644 index 0000000..1e228b8 --- /dev/null +++ b/packages/pieces/community/zoho-desk/src/lib/common/props.ts @@ -0,0 +1,88 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { zohoDeskApiCall } from '.'; +import { zohoDeskAuth } from './auth'; + +interface DropdownParams { + displayName: string; + description?: string; + required: boolean; +} + +export const organizationId = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + description: params.description, + refreshers: [], + required: params.required, + options: async ({ auth }) => { + if (!auth) { + return { + placeholder: 'Please connect your account first.', + options: [], + disabled: true, + }; + } + const authValue = auth as PiecePropValueSchema; + + const response = await zohoDeskApiCall<{ data: { id: string; companyName: string }[] }>({ + auth: authValue, + method: HttpMethod.GET, + resourceUri: '/organizations', + }); + + return { + disabled: false, + options: response.data.map((org) => { + return { + label: org.companyName, + value: org.id, + }; + }), + }; + }, + }); + +export const departmentId = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + description: params.description, + refreshers: ['orgId'], + required: params.required, + options: async ({ auth, orgId }) => { + if (!auth) { + return { + placeholder: 'Please connect your account first.', + options: [], + disabled: true, + }; + } + + if (!orgId) { + return { + placeholder: 'Please select organization first.', + options: [], + disabled: true, + }; + } + + const authValue = auth as PiecePropValueSchema; + + const response = await zohoDeskApiCall<{ data: { id: string; name: string }[] }>({ + auth: authValue, + method: HttpMethod.GET, + resourceUri: `/departments`, + orgId: orgId as string, + }); + + return { + disabled: false, + options: response.data.map((department) => { + return { + label: department.name, + value: department.id, + }; + }), + }; + }, + }); diff --git a/packages/pieces/community/zoho-desk/tsconfig.json b/packages/pieces/community/zoho-desk/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/zoho-desk/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/zoho-desk/tsconfig.lib.json b/packages/pieces/community/zoho-desk/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/zoho-desk/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/zoho-invoice/.eslintrc.json b/packages/pieces/community/zoho-invoice/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/zoho-invoice/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/zoho-invoice/README.md b/packages/pieces/community/zoho-invoice/README.md new file mode 100644 index 0000000..9426cae --- /dev/null +++ b/packages/pieces/community/zoho-invoice/README.md @@ -0,0 +1,7 @@ +# pieces-zoho-invoice + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-zoho-invoice` to build the library. diff --git a/packages/pieces/community/zoho-invoice/package.json b/packages/pieces/community/zoho-invoice/package.json new file mode 100644 index 0000000..178d3f7 --- /dev/null +++ b/packages/pieces/community/zoho-invoice/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-zoho-invoice", + "version": "0.0.5" +} \ No newline at end of file diff --git a/packages/pieces/community/zoho-invoice/project.json b/packages/pieces/community/zoho-invoice/project.json new file mode 100644 index 0000000..ccd0afb --- /dev/null +++ b/packages/pieces/community/zoho-invoice/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-zoho-invoice", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/zoho-invoice/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/zoho-invoice", + "tsConfig": "packages/pieces/community/zoho-invoice/tsconfig.lib.json", + "packageJson": "packages/pieces/community/zoho-invoice/package.json", + "main": "packages/pieces/community/zoho-invoice/src/index.ts", + "assets": [ + "packages/pieces/community/zoho-invoice/*.md", + { + "input": "packages/pieces/community/zoho-invoice/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-zoho-invoice {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/zoho-invoice/src/index.ts b/packages/pieces/community/zoho-invoice/src/index.ts new file mode 100644 index 0000000..dbc2fab --- /dev/null +++ b/packages/pieces/community/zoho-invoice/src/index.ts @@ -0,0 +1,69 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + createPiece, + OAuth2PropertyValue, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { common } from './lib/common'; +import { newInvoice } from './lib/triggers/new-invoice'; + +export const zohoAuth = PieceAuth.OAuth2({ + required: true, + authUrl: 'https://accounts.zoho.{region}/oauth/v2/auth', + tokenUrl: 'https://accounts.zoho.{region}/oauth/v2/token', + scope: ['ZohoInvoice.invoices.READ'], + props: { + region: Property.StaticDropdown({ + displayName: 'Region', + description: 'Select your account region', + required: true, + options: { + options: [ + { + label: 'US (.com)', + value: 'com', + }, + { + label: 'Europe (.eu)', + value: 'eu', + }, + { + label: 'India (.in)', + value: 'in', + }, + { + label: 'Australia (.com.au)', + value: 'com.au', + }, + { + label: 'Japan (.jp)', + value: 'jp', + }, + ], + }, + }), + }, +}); + +export const zohoInvoice = createPiece({ + displayName: 'Zoho Invoice', + description: 'Online invoicing software for businesses', + + auth: zohoAuth, + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/zoho-invoice.png', + categories: [PieceCategory.ACCOUNTING], + authors: ["kishanprmr","MoShizzle","abuaboud"], + actions: [ + createCustomApiCallAction({ + baseUrl: (auth) => + common.baseUrl((auth as OAuth2PropertyValue).props!['region']), + auth: zohoAuth, + authMapping: async (auth) => + common.authHeaders((auth as OAuth2PropertyValue).access_token), + }), + ], + triggers: [newInvoice], +}); diff --git a/packages/pieces/community/zoho-invoice/src/lib/common/index.ts b/packages/pieces/community/zoho-invoice/src/lib/common/index.ts new file mode 100644 index 0000000..969a3f7 --- /dev/null +++ b/packages/pieces/community/zoho-invoice/src/lib/common/index.ts @@ -0,0 +1,34 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { OAuth2PropertyValue } from '@activepieces/pieces-framework'; + +export const common = { + baseUrl: (region: string) => { + return `https://www.zohoapis.${region}/invoice/v3`; + }, + authHeaders: (accessToken: string) => { + return { + Authorization: `Zoho-oauthtoken ${accessToken}`, + }; + }, + + async getInvoices( + auth: OAuth2PropertyValue, + search?: { + createdSince?: string; + } + ) { + const q: { + last_modified_at?: string; + } = {}; + if (search?.createdSince) q.last_modified_at = search.createdSince; + + const response = await httpClient.sendRequest({ + url: `${common.baseUrl(auth.props!['region'])}/invoices`, + method: HttpMethod.GET, + headers: common.authHeaders(auth.access_token), + queryParams: q, + }); + + return response.body['invoices']; + }, +}; diff --git a/packages/pieces/community/zoho-invoice/src/lib/triggers/new-invoice.ts b/packages/pieces/community/zoho-invoice/src/lib/triggers/new-invoice.ts new file mode 100644 index 0000000..5e06bab --- /dev/null +++ b/packages/pieces/community/zoho-invoice/src/lib/triggers/new-invoice.ts @@ -0,0 +1,76 @@ +import { + PiecePropValueSchema, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; + +import { common } from '../common'; + +import dayjs from 'dayjs'; +import { zohoAuth } from '../..'; + +const polling: Polling< + PiecePropValueSchema, + Record +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, lastFetchEpochMS }) => { + const currentValues = + (await common.getInvoices(auth, { + createdSince: dayjs(lastFetchEpochMS).format('YYYY-MM-DD'), + })) ?? []; + const items = (currentValues as any[]).map( + (item: { created_time: string }) => ({ + epochMilliSeconds: dayjs(item.created_time).valueOf(), + data: item, + }) + ); + return items; + }, +}; + +export const newInvoice = createTrigger({ + auth: zohoAuth, + name: 'new_invoice', + displayName: 'New Invoice', + description: 'Trigger when a new invoice is received.', + props: {}, + type: TriggerStrategy.POLLING, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + run: async (context) => { + return await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, + test: async (context) => { + return await pollingHelper.test(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + files: context.files, + }); + }, + + sampleData: {}, +}); diff --git a/packages/pieces/community/zoho-invoice/tsconfig.json b/packages/pieces/community/zoho-invoice/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/zoho-invoice/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/zoho-invoice/tsconfig.lib.json b/packages/pieces/community/zoho-invoice/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/zoho-invoice/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/zoho-mail/.eslintrc.json b/packages/pieces/community/zoho-mail/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/zoho-mail/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/zoho-mail/README.md b/packages/pieces/community/zoho-mail/README.md new file mode 100644 index 0000000..28d0bbd --- /dev/null +++ b/packages/pieces/community/zoho-mail/README.md @@ -0,0 +1,7 @@ +# pieces-zoho-mail + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-zoho-mail` to build the library. diff --git a/packages/pieces/community/zoho-mail/package.json b/packages/pieces/community/zoho-mail/package.json new file mode 100644 index 0000000..96e190a --- /dev/null +++ b/packages/pieces/community/zoho-mail/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-zoho-mail", + "version": "0.0.1" +} diff --git a/packages/pieces/community/zoho-mail/project.json b/packages/pieces/community/zoho-mail/project.json new file mode 100644 index 0000000..58d4f6a --- /dev/null +++ b/packages/pieces/community/zoho-mail/project.json @@ -0,0 +1,45 @@ +{ + "name": "pieces-zoho-mail", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/zoho-mail/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/zoho-mail", + "tsConfig": "packages/pieces/community/zoho-mail/tsconfig.lib.json", + "packageJson": "packages/pieces/community/zoho-mail/package.json", + "main": "packages/pieces/community/zoho-mail/src/index.ts", + "assets": [ + "packages/pieces/community/zoho-mail/*.md" + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/zoho-mail/src/index.ts b/packages/pieces/community/zoho-mail/src/index.ts new file mode 100644 index 0000000..864e2c1 --- /dev/null +++ b/packages/pieces/community/zoho-mail/src/index.ts @@ -0,0 +1,46 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + createPiece, + OAuth2PropertyValue, + PiecePropValueSchema, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { getEmailDetailsAction } from './lib/actions/get-email-details'; +import { markEmailAsReadAction } from './lib/actions/mark-email-as-read'; +import { markEmailAsUnreadAction } from './lib/actions/mark-email-as-unread'; +import { moveEmailAction } from './lib/actions/move-email'; +import { sendEmailAction } from './lib/actions/send-email'; +import { zohoMailAuth } from './lib/common/auth'; +import { newEmailReceivedTrigger } from './lib/triggers/new-email-received-trigger'; + +export const zohoMail = createPiece({ + displayName: 'Zoho Mail', + logoUrl: 'https://cdn.activepieces.com/pieces/zoho-mail.png', + auth: zohoMailAuth, + authors: ['onyedikachi-david', 'kishanprmr'], + description: + 'Zoho Mail is a powerful email service that allows you to manage your email, contacts, and calendars efficiently.', + minimumSupportedRelease: '0.36.1', + categories: [PieceCategory.COMMUNICATION], + actions: [ + getEmailDetailsAction, + markEmailAsReadAction, + markEmailAsUnreadAction, + moveEmailAction, + sendEmailAction, + createCustomApiCallAction({ + auth: zohoMailAuth, + baseUrl: (auth) => { + const authValue = auth as PiecePropValueSchema; + const location = authValue.props?.['location'] ?? 'zoho.com'; + return `https://mail.${location}/api`; + }, + authMapping: async (auth) => { + return { + Authorization: `Zoho-oauthtoken ${(auth as OAuth2PropertyValue).access_token}`, + }; + }, + }), + ], + triggers: [newEmailReceivedTrigger], +}); diff --git a/packages/pieces/community/zoho-mail/src/lib/actions/get-email-details.ts b/packages/pieces/community/zoho-mail/src/lib/actions/get-email-details.ts new file mode 100644 index 0000000..9a4370f --- /dev/null +++ b/packages/pieces/community/zoho-mail/src/lib/actions/get-email-details.ts @@ -0,0 +1,38 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { convertAttachment, parseStream, zohoMailApiCall } from '../common'; +import { zohoMailAuth } from '../common/auth'; +import { accountId, folderId, messageId } from '../common/props'; + +export const getEmailDetailsAction = createAction({ + auth: zohoMailAuth, + name: 'get_email_details', + displayName: 'Get Email Details', + description: 'Retrieves full content and metadata of a specific email.', + props: { + accountId: accountId({ displayName: 'Account', required: true }), + folderId: folderId({ displayName: 'Folder', required: true }), + messageId: messageId({ + displayName: 'Message ID', + description: 'The ID of the email message to retrieve.', + required: true, + }), + }, + async run(context) { + const { accountId, messageId } = context.propsValue; + + const response = await zohoMailApiCall<{ data: { content: string; messageId: string } }>({ + auth: context.auth, + method: HttpMethod.GET, + resourceUri: `/accounts/${accountId}/messages/${messageId}/originalmessage`, + }); + + const parsedMailResponse = await parseStream(response.data.content); + + return { + ...parsedMailResponse, + attachments: await convertAttachment(parsedMailResponse.attachments, context.files), + id: response.data.messageId, + }; + }, +}); diff --git a/packages/pieces/community/zoho-mail/src/lib/actions/mark-email-as-read.ts b/packages/pieces/community/zoho-mail/src/lib/actions/mark-email-as-read.ts new file mode 100644 index 0000000..585a6a2 --- /dev/null +++ b/packages/pieces/community/zoho-mail/src/lib/actions/mark-email-as-read.ts @@ -0,0 +1,36 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { zohoMailApiCall } from '../common'; +import { zohoMailAuth } from '../common/auth'; +import { accountId, folderId, messageId } from '../common/props'; + +export const markEmailAsReadAction = createAction({ + auth: zohoMailAuth, + name: 'mark_email_as_read', + displayName: 'Mark Email as Read', + description: 'Marks an email as read.', + props: { + accountId: accountId({ displayName: 'Account', required: true }), + folderId: folderId({ displayName: 'Folder', required: true }), + messageId: messageId({ + displayName: 'Message ID', + description: 'The ID of the email message to mark as read.', + required: true, + }), + }, + async run(context) { + const { accountId, messageId } = context.propsValue; + + const response = await zohoMailApiCall({ + auth: context.auth, + method: HttpMethod.PUT, + resourceUri: `/accounts/${accountId}/updatemessage`, + body: { + mode: 'markAsRead', + messageId: [messageId], + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/zoho-mail/src/lib/actions/mark-email-as-unread.ts b/packages/pieces/community/zoho-mail/src/lib/actions/mark-email-as-unread.ts new file mode 100644 index 0000000..70e24a8 --- /dev/null +++ b/packages/pieces/community/zoho-mail/src/lib/actions/mark-email-as-unread.ts @@ -0,0 +1,36 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { zohoMailApiCall } from '../common'; +import { zohoMailAuth } from '../common/auth'; +import { accountId, folderId, messageId } from '../common/props'; + +export const markEmailAsUnreadAction = createAction({ + auth: zohoMailAuth, + name: 'mark_email_as_unread', + displayName: 'Mark Emai as Unread', + description: 'Marks an email as unread.', + props: { + accountId: accountId({ displayName: 'Account', required: true }), + folderId: folderId({ displayName: 'Folder', required: true }), + messageId: messageId({ + displayName: 'Message ID', + description: 'The ID of the email message to mark as unread.', + required: true, + }), + }, + async run(context) { + const { accountId, messageId } = context.propsValue; + + const response = await zohoMailApiCall({ + auth: context.auth, + method: HttpMethod.PUT, + resourceUri: `/accounts/${accountId}/updatemessage`, + body: { + mode: 'markAsUnread', + messageId: [messageId], + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/zoho-mail/src/lib/actions/move-email.ts b/packages/pieces/community/zoho-mail/src/lib/actions/move-email.ts new file mode 100644 index 0000000..b126a12 --- /dev/null +++ b/packages/pieces/community/zoho-mail/src/lib/actions/move-email.ts @@ -0,0 +1,45 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction } from '@activepieces/pieces-framework'; +import { zohoMailApiCall } from '../common'; +import { zohoMailAuth } from '../common/auth'; +import { accountId, folderId, messageId } from '../common/props'; + +export const moveEmailAction = createAction({ + auth: zohoMailAuth, + name: 'move_email', + displayName: 'Move Email to Folder', + description: 'Moves an email to a different folder.', + props: { + accountId: accountId({ displayName: 'Account', required: true }), + folderId: folderId({ displayName: 'Current Folder', required: true }), + messageId: messageId({ + displayName: 'Message ID', + description: 'The ID of the email message to move.', + required: true, + }), + destfolderId: folderId({ + displayName: 'Destination Folder', + description: 'Select the folder to move the email to.', + required: true, + }), + }, + async run(context) { + const { accountId, destfolderId, messageId, folderId } = context.propsValue; + + const response = await zohoMailApiCall({ + auth: context.auth, + method: HttpMethod.PUT, + resourceUri: `/accounts/${accountId}/updatemessage`, + + body: { + mode: 'moveMessage', + destfolderId: destfolderId, + messageId: [messageId], + isFolderSpecific: true, + folderId: folderId, + }, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/zoho-mail/src/lib/actions/send-email.ts b/packages/pieces/community/zoho-mail/src/lib/actions/send-email.ts new file mode 100644 index 0000000..9dbbd42 --- /dev/null +++ b/packages/pieces/community/zoho-mail/src/lib/actions/send-email.ts @@ -0,0 +1,136 @@ +import { HttpMethod, httpClient } from '@activepieces/pieces-common'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import FormData from 'form-data'; +import { zohoMailApiCall } from '../common'; +import { zohoMailAuth } from '../common/auth'; +import { accountId, fromAddress } from '../common/props'; + +export const sendEmailAction = createAction({ + auth: zohoMailAuth, + name: 'send_email', + displayName: 'Send Email', + description: 'Sends an email.', + props: { + accountId: accountId({ displayName: 'Account', required: true }), + fromAddress: fromAddress({ displayName: 'From Email Address', required: true }), + toAddress: Property.ShortText({ + displayName: 'To Email Address', + description: "Recipient's email address.", + required: true, + }), + + subject: Property.LongText({ + displayName: 'Subject', + required: true, + }), + + mailFormat: Property.StaticDropdown({ + displayName: 'Mail Format', + required: true, + options: { + options: [ + { label: 'HTML', value: 'html' }, + { label: 'Plain Text', value: 'plaintext' }, + ], + }, + defaultValue: 'html', + }), + content: Property.LongText({ + displayName: 'Content', + description: 'HTML or plain text content of the email.', + required: true, + }), + ccAddress: Property.ShortText({ + displayName: 'CC Email Address', + description: "CC recipient's email address.", + required: false, + }), + bccAddress: Property.ShortText({ + displayName: 'BCC Email Address', + description: "BCC recipient's email address.", + required: false, + }), + askReceipt: Property.StaticDropdown({ + displayName: 'Ask for Read Receipt', + required: false, + options: { + options: [ + { label: 'Yes', value: 'yes' }, + { label: 'No', value: 'no' }, + ], + }, + }), + attachment: Property.File({ + displayName: 'Attachment', + required: false, + }), + attachmentName: Property.ShortText({ + displayName: 'Attachment Name', + description: 'In case you want to change the name of the attachment.', + required: false, + }), + }, + async run(context) { + const { + accountId, + fromAddress, + toAddress, + ccAddress, + bccAddress, + subject, + content, + mailFormat, + askReceipt, + attachment, + attachmentName, + } = context.propsValue; + + const requestBody: Record = { + fromAddress, + toAddress, + subject, + content, + mailFormat: mailFormat ?? 'html', + }; + + if (ccAddress) requestBody['ccAddress'] = ccAddress; + if (bccAddress) requestBody['bccAddress'] = bccAddress; + if (askReceipt) requestBody['askReceipt'] = askReceipt; + + if (attachment) { + const formData = new FormData(); + + formData.append( + 'attach', + Buffer.from(attachment.base64, 'base64'), + attachmentName || attachment.filename, + ); + + const location = context.auth.props?.['location'] ?? 'zoho.com'; + const baseUrl = `https://mail.${location}/api`; + + const uploadResponse = await httpClient.sendRequest<{ + data: { storeName: string; attachmentName: string; attachmentPath: string }[]; + }>({ + url: baseUrl + `/accounts/${accountId}/messages/attachments?uploadType=multipart`, + method: HttpMethod.POST, + body: formData, + headers: { + ...formData.getHeaders(), + Authorization: `Zoho-oauthtoken ${context.auth.access_token}`, + }, + }); + + requestBody['attachments'] = uploadResponse.body.data; + } + + const response = await zohoMailApiCall({ + auth: context.auth, + method: HttpMethod.POST, + resourceUri: `/accounts/${accountId}/messages`, + body: requestBody, + }); + + return response; + }, +}); diff --git a/packages/pieces/community/zoho-mail/src/lib/common/auth.ts b/packages/pieces/community/zoho-mail/src/lib/common/auth.ts new file mode 100644 index 0000000..a204fd3 --- /dev/null +++ b/packages/pieces/community/zoho-mail/src/lib/common/auth.ts @@ -0,0 +1,49 @@ +import { PieceAuth, Property } from '@activepieces/pieces-framework'; + +export const zohoMailAuth = PieceAuth.OAuth2({ + props: { + location: Property.StaticDropdown({ + displayName: 'Location', + description: 'The location of your Zoho Mail account.', + required: true, + options: { + options: [ + { + label: 'zoho.eu (Europe)', + value: 'zoho.eu', + }, + { + label: 'zoho.com (United States)', + value: 'zoho.com', + }, + { + label: 'zoho.com.au (Australia)', + value: 'zoho.com.au', + }, + { + label: 'zoho.jp (Japan)', + value: 'zoho.jp', + }, + { + label: 'zoho.in (India)', + value: 'zoho.in', + }, + { + label: 'zohocloud.ca (Canada)', + value: 'zohocloud.ca', + }, + ], + }, + }), + }, + description: 'Authentication for Zoho Desk', + scope: [ + 'ZohoMail.accounts.READ', + 'ZohoMail.messages.ALL', + 'ZohoMail.folders.ALL', + 'ZohoMail.organization.accounts.READ', + ], + authUrl: 'https://accounts.{location}/oauth/v2/auth', + tokenUrl: 'https://accounts.{location}/oauth/v2/token', + required: true, +}); diff --git a/packages/pieces/community/zoho-mail/src/lib/common/index.ts b/packages/pieces/community/zoho-mail/src/lib/common/index.ts new file mode 100644 index 0000000..16f2ff6 --- /dev/null +++ b/packages/pieces/community/zoho-mail/src/lib/common/index.ts @@ -0,0 +1,85 @@ +import { + HttpMessageBody, + HttpMethod, + HttpRequest, + QueryParams, + httpClient, +} from '@activepieces/pieces-common'; +import { FilesService, PiecePropValueSchema } from '@activepieces/pieces-framework'; +import { Attachment, ParsedMail, simpleParser } from 'mailparser'; +import { zohoMailAuth } from './auth'; + +export type ZohoMailApiCallParams = { + auth: PiecePropValueSchema; + method: HttpMethod; + resourceUri: string; + query?: Record; + body?: any; +}; + +export async function zohoMailApiCall({ + auth, + method, + resourceUri, + query, + body, +}: ZohoMailApiCallParams): Promise { + const location = auth.props?.['location'] ?? 'zoho.com'; + const baseUrl = `https://mail.${location}/api`; + const qs: QueryParams = {}; + + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== null && value !== undefined) { + qs[key] = String(value); + } + } + } + + const request: HttpRequest = { + method, + url: baseUrl + resourceUri, + headers: { + Authorization: `Zoho-oauthtoken ${auth.access_token}`, + }, + queryParams: qs, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; +} + +export async function parseStream(stream: string | Buffer): Promise { + return new Promise((resolve, reject) => { + simpleParser(stream, (err, parsed) => { + if (err) { + reject(err); + } else { + resolve(parsed); + } + }); + }); +} + +export async function convertAttachment(attachments: Attachment[], files: FilesService) { + const promises = attachments.map(async (attachment) => { + try { + const fileName = attachment.filename ?? `attachment-${Date.now()}`; + return { + fileName, + mimeType: attachment.contentType, + size: attachment.size, + data: await files.write({ + fileName: fileName, + data: attachment.content, + }), + }; + } catch (error) { + console.error(`Failed to process attachment: ${attachment.filename}`, error); + return null; + } + }); + const results = await Promise.all(promises); + return results.filter((result) => result !== null); +} diff --git a/packages/pieces/community/zoho-mail/src/lib/common/props.ts b/packages/pieces/community/zoho-mail/src/lib/common/props.ts new file mode 100644 index 0000000..bede4be --- /dev/null +++ b/packages/pieces/community/zoho-mail/src/lib/common/props.ts @@ -0,0 +1,188 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { zohoMailApiCall } from '.'; +import { zohoMailAuth } from './auth'; + +interface DropdownParams { + displayName: string; + description?: string; + required: boolean; +} + +export const accountId = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + description: params.description, + refreshers: [], + required: params.required, + options: async ({ auth }) => { + if (!auth) { + return { + placeholder: 'Please connect your account first.', + options: [], + disabled: true, + }; + } + const authValue = auth as PiecePropValueSchema; + + const response = await zohoMailApiCall<{ + data: { accountId: string; displayName: string }[]; + }>({ + auth: authValue, + method: HttpMethod.GET, + resourceUri: '/accounts', + }); + + return { + disabled: false, + options: response.data.map((account) => { + return { + label: account.displayName || account.accountId, + value: account.accountId, + }; + }), + }; + }, + }); + +export const folderId = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + description: params.description, + refreshers: ['accountId'], + required: params.required, + options: async ({ auth, accountId }) => { + if (!auth) { + return { + placeholder: 'Please connect your account first.', + options: [], + disabled: true, + }; + } + if (!accountId) { + return { + placeholder: 'Please select Account first.', + options: [], + disabled: true, + }; + } + + const authValue = auth as PiecePropValueSchema; + + const response = await zohoMailApiCall<{ + data: { folderId: string; path: string }[]; + }>({ + auth: authValue, + method: HttpMethod.GET, + resourceUri: `/accounts/${accountId}/folders`, + }); + + return { + disabled: false, + options: response.data.map((folder) => { + return { + label: folder.path || folder.folderId, + value: folder.folderId, + }; + }), + }; + }, + }); + +export const messageId = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + description: params.description, + refreshers: ['accountId', 'folderId'], + required: params.required, + options: async ({ auth, accountId, folderId }) => { + if (!auth) { + return { + placeholder: 'Please connect your account first.', + options: [], + disabled: true, + }; + } + if (!accountId) { + return { + placeholder: 'Please select Account first.', + options: [], + disabled: true, + }; + } + if (!folderId) { + return { + placeholder: 'Please select Folder first.', + options: [], + disabled: true, + }; + } + + const authValue = auth as PiecePropValueSchema; + + const response = await zohoMailApiCall<{ + data: { messageId: string; subject: string }[]; + }>({ + auth: authValue, + method: HttpMethod.GET, + resourceUri: `/accounts/${accountId}/messages/view`, + query: { + folderId: folderId as string, + limit: 50, + }, + }); + + return { + disabled: false, + options: response.data.map((message) => { + return { + label: message.subject, + value: message.messageId, + }; + }), + }; + }, + }); + +export const fromAddress = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + description: params.description, + refreshers: ['accountId'], + required: params.required, + options: async ({ auth, accountId }) => { + if (!auth) { + return { + placeholder: 'Please connect your account first.', + options: [], + disabled: true, + }; + } + if (!accountId) { + return { + placeholder: 'Please select Account first.', + options: [], + disabled: true, + }; + } + const authValue = auth as PiecePropValueSchema; + + const response = await zohoMailApiCall<{ + data: { sendMailDetails: { fromAddress: string }[] }; + }>({ + auth: authValue, + method: HttpMethod.GET, + resourceUri: `/accounts/${accountId}`, + }); + + return { + disabled: false, + options: response.data.sendMailDetails.map((account) => { + return { + label: account.fromAddress, + value: account.fromAddress, + }; + }), + }; + }, + }); diff --git a/packages/pieces/community/zoho-mail/src/lib/triggers/new-email-received-trigger.ts b/packages/pieces/community/zoho-mail/src/lib/triggers/new-email-received-trigger.ts new file mode 100644 index 0000000..0feb9f0 --- /dev/null +++ b/packages/pieces/community/zoho-mail/src/lib/triggers/new-email-received-trigger.ts @@ -0,0 +1,137 @@ +import { + DedupeStrategy, + HttpMethod, + Polling, + QueryParams, + pollingHelper, +} from '@activepieces/pieces-common'; +import { + PiecePropValueSchema, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { zohoMailApiCall } from '../common'; +import { zohoMailAuth } from '../common/auth'; +import { accountId, folderId } from '../common/props'; + +type Props = { + accountId?: string; + folderId?: string; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const { accountId, folderId } = propsValue; + + let page = 1; + let hasMore = true; + const allMessages = []; + + do { + const queryParams: QueryParams = { + start: page.toString(), + limit: lastFetchEpochMS === 0 ? '10' : '200', + }; + + if (folderId) { + queryParams['folderId'] = folderId; + } + + const response = await zohoMailApiCall<{ data: { receivedTime: string }[] }>({ + auth, + resourceUri: `/accounts/${accountId}/messages/view`, + method: HttpMethod.GET, + query: queryParams, + }); + + const messages = response.data || []; + + if (messages.length === 0) { + break; + } + + for (const msg of messages) { + const receivedTime = Number(msg.receivedTime); + if (lastFetchEpochMS > 0 && receivedTime <= lastFetchEpochMS) { + hasMore = false; + break; // Stop processing this page + } + allMessages.push(msg); // Only add if it's newer + } + + // if it's test mode, only fetch first page + if (lastFetchEpochMS === 0) break; + + if (!hasMore) { + break; + } + page++; + } while (hasMore); + + return allMessages.map((msg) => { + return { + epochMilliSeconds: Number(msg.receivedTime), + data: msg, + }; + }); + }, +}; + +export const newEmailReceivedTrigger = createTrigger({ + auth: zohoMailAuth, + name: 'new_email_received', + displayName: 'New Email Received', + description: 'Triggers when a new email is received in a specified folder (or inbox).', + props: { + accountId: accountId({ displayName: 'Account', required: true }), + folderId: folderId({ + displayName: 'Folder', + description: + 'Select the folder to watch. If empty, watches the inbox/all messages based on API default.', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + summary: 'test mail', + sentDateInGMT: '1749273996000', + calendarType: 0, + subject: 'test mail', + messageId: '1749293811021114900', + flagid: 'flag_not_set', + status2: '0', + priority: '3', + hasInline: 'false', + toAddress: '', + folderId: '7723149000000002014', + ccAddress: 'Not Provided', + hasAttachment: '0', + size: '238', + sender: 'john.doe@gmail.com', + receivedTime: '1749293811018', + fromAddress: 'john.doe@gmail.com', + status: '0', + }, +}); diff --git a/packages/pieces/community/zoho-mail/tsconfig.json b/packages/pieces/community/zoho-mail/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/zoho-mail/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/zoho-mail/tsconfig.lib.json b/packages/pieces/community/zoho-mail/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/zoho-mail/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/zoo/.eslintrc.json b/packages/pieces/community/zoo/.eslintrc.json new file mode 100644 index 0000000..4a4e695 --- /dev/null +++ b/packages/pieces/community/zoo/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/zoo/README.md b/packages/pieces/community/zoo/README.md new file mode 100644 index 0000000..d12d7a0 --- /dev/null +++ b/packages/pieces/community/zoo/README.md @@ -0,0 +1,7 @@ +# pieces-zoo + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-zoo` to build the library. diff --git a/packages/pieces/community/zoo/package.json b/packages/pieces/community/zoo/package.json new file mode 100644 index 0000000..e50323f --- /dev/null +++ b/packages/pieces/community/zoo/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-zoo", + "version": "0.0.1" +} diff --git a/packages/pieces/community/zoo/project.json b/packages/pieces/community/zoo/project.json new file mode 100644 index 0000000..deeaec0 --- /dev/null +++ b/packages/pieces/community/zoo/project.json @@ -0,0 +1,50 @@ +{ + "name": "pieces-zoo", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/zoo/src", + "projectType": "library", + "release": { + "version": { + "generatorOptions": { + "packageRoot": "dist/{projectRoot}", + "currentVersionResolver": "git-tag" + } + } + }, + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/zoo", + "tsConfig": "packages/pieces/community/zoo/tsconfig.lib.json", + "packageJson": "packages/pieces/community/zoo/package.json", + "main": "packages/pieces/community/zoo/src/index.ts", + "assets": [ + "packages/pieces/community/zoo/*.md", + { + "input": "packages/pieces/community/zoo/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "nx-release-publish": { + "options": { + "packageRoot": "dist/{projectRoot}" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/zoo/src/index.ts b/packages/pieces/community/zoo/src/index.ts new file mode 100644 index 0000000..4c9cc83 --- /dev/null +++ b/packages/pieces/community/zoo/src/index.ts @@ -0,0 +1,34 @@ + +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { ML_ACTIONS } from './lib/actions'; +import { API_CALLS_ACTIONS } from './lib/actions/api-calls'; +import { API_TOKENS_ACTIONS } from './lib/actions/api-tokens'; +import { FILE_ACTIONS } from './lib/actions/file'; +import { META_ACTIONS } from './lib/actions/meta'; +import { MODELING_ACTIONS } from './lib/actions/modeling'; +import { ORGS_ACTIONS } from './lib/actions/orgs'; +import { PAYMENTS_ACTIONS } from './lib/actions/payments'; +import { SERVICE_ACCOUNTS_ACTIONS } from './lib/actions/service-accounts'; +import { SHORTLINKS_ACTIONS } from './lib/actions/shortlinks'; +import { UNIT_ACTIONS } from './lib/actions/unit'; +import { USER_ACTIONS } from './lib/actions/users'; + +export const zooAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: 'Your Zoo API Key (Bearer Token).', +}); + +export const textToCad = createPiece({ + displayName: 'Zoo', + description: 'Generate and iterate on 3D models from text descriptions using ML endpoints.', + logoUrl: 'https://cdn.activepieces.com/pieces/zoo.jpg', + auth: zooAuth, + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + minimumSupportedRelease: '0.20.0', + authors: ['ahmad-swanblocks'], + actions: [...ML_ACTIONS, ...API_CALLS_ACTIONS, ...API_TOKENS_ACTIONS, ...FILE_ACTIONS, ...META_ACTIONS, ...MODELING_ACTIONS, ...ORGS_ACTIONS, ...PAYMENTS_ACTIONS, ...SERVICE_ACCOUNTS_ACTIONS, ...SHORTLINKS_ACTIONS, ...UNIT_ACTIONS, ...USER_ACTIONS], + triggers: [], +}); + \ No newline at end of file diff --git a/packages/pieces/community/zoo/src/lib/actions/api-calls/get-async-operation.action.ts b/packages/pieces/community/zoo/src/lib/actions/api-calls/get-async-operation.action.ts new file mode 100644 index 0000000..111208e --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/api-calls/get-async-operation.action.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getAsyncOperationAction = createAction({ + name: 'get_async_operation', + displayName: 'Get Async Operation', + description: 'Retrieve details of an asynchronous operation', + auth: zooAuth, + // category: 'API Calls', + props: { + operationId: Property.ShortText({ + displayName: 'Operation ID', + required: true, + description: 'The ID of the async operation to retrieve', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/async/operations/${propsValue.operationId}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/api-calls/get-org-api-call.action.ts b/packages/pieces/community/zoo/src/lib/actions/api-calls/get-org-api-call.action.ts new file mode 100644 index 0000000..6b7fb33 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/api-calls/get-org-api-call.action.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getOrgApiCallAction = createAction({ + name: 'get_org_api_call', + displayName: 'Get Organization API Call', + description: 'Retrieve details of a specific API call made by your organization', + auth: zooAuth, + // category: 'API Calls', + props: { + callId: Property.ShortText({ + displayName: 'Call ID', + required: true, + description: 'The ID of the API call to retrieve', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/org/api-calls/${propsValue.callId}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/api-calls/get-user-api-call.action.ts b/packages/pieces/community/zoo/src/lib/actions/api-calls/get-user-api-call.action.ts new file mode 100644 index 0000000..1c45ada --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/api-calls/get-user-api-call.action.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getUserApiCallAction = createAction({ + name: 'get_user_api_call', + displayName: 'Get User API Call', + description: 'Retrieve details of a specific API call made by your user account', + auth: zooAuth, + // category: 'API Calls', + props: { + callId: Property.ShortText({ + displayName: 'Call ID', + required: true, + description: 'The ID of the API call to retrieve', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/user/api-calls/${propsValue.callId}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/api-calls/index.ts b/packages/pieces/community/zoo/src/lib/actions/api-calls/index.ts new file mode 100644 index 0000000..6588229 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/api-calls/index.ts @@ -0,0 +1,13 @@ +import { getAsyncOperationAction } from './get-async-operation.action'; +import { listOrgApiCallsAction } from './list-org-api-calls.action'; +import { getOrgApiCallAction } from './get-org-api-call.action'; +import { listUserApiCallsAction } from './list-user-api-calls.action'; +import { getUserApiCallAction } from './get-user-api-call.action'; + +export const API_CALLS_ACTIONS = [ + getAsyncOperationAction, + listOrgApiCallsAction, + getOrgApiCallAction, + listUserApiCallsAction, + getUserApiCallAction, +]; diff --git a/packages/pieces/community/zoo/src/lib/actions/api-calls/list-org-api-calls.action.ts b/packages/pieces/community/zoo/src/lib/actions/api-calls/list-org-api-calls.action.ts new file mode 100644 index 0000000..493925b --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/api-calls/list-org-api-calls.action.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const listOrgApiCallsAction = createAction({ + name: 'list_org_api_calls', + displayName: 'List Organization API Calls', + description: 'List API calls made by your organization', + auth: zooAuth, + // category: 'API Calls', + props: { + limit: Property.Number({ + displayName: 'Limit', + required: false, + description: 'Maximum number of API calls to return', + }), + offset: Property.Number({ + displayName: 'Offset', + required: false, + description: 'Number of API calls to skip', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/org/api-calls', + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + ...(propsValue.limit && { limit: propsValue.limit.toString() }), + ...(propsValue.offset && { offset: propsValue.offset.toString() }), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/api-calls/list-user-api-calls.action.ts b/packages/pieces/community/zoo/src/lib/actions/api-calls/list-user-api-calls.action.ts new file mode 100644 index 0000000..a3cb802 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/api-calls/list-user-api-calls.action.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const listUserApiCallsAction = createAction({ + name: 'list_user_api_calls', + displayName: 'List User API Calls', + description: 'List API calls made by your user account', + auth: zooAuth, + // category: 'API Calls', + props: { + limit: Property.Number({ + displayName: 'Limit', + required: false, + description: 'Maximum number of API calls to return', + }), + offset: Property.Number({ + displayName: 'Offset', + required: false, + description: 'Number of API calls to skip', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/user/api-calls', + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + ...(propsValue.limit && { limit: propsValue.limit.toString() }), + ...(propsValue.offset && { offset: propsValue.offset.toString() }), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/api-tokens/create-api-token.action.ts b/packages/pieces/community/zoo/src/lib/actions/api-tokens/create-api-token.action.ts new file mode 100644 index 0000000..24e522a --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/api-tokens/create-api-token.action.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const createApiTokenAction = createAction({ + name: 'create_api_token', + displayName: 'Create API Token', + description: 'Create a new API token for your user account', + auth: zooAuth, + // category: 'API Tokens', + props: { + name: Property.ShortText({ + displayName: 'Token Name', + required: true, + description: 'A name to identify this token', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.zoo.dev/user/api-tokens', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + name: propsValue.name, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/api-tokens/delete-api-token.action.ts b/packages/pieces/community/zoo/src/lib/actions/api-tokens/delete-api-token.action.ts new file mode 100644 index 0000000..c530687 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/api-tokens/delete-api-token.action.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const deleteApiTokenAction = createAction({ + name: 'delete_api_token', + displayName: 'Delete API Token', + description: 'Delete an API token from your user account', + auth: zooAuth, + // category: 'API Tokens', + props: { + token: Property.ShortText({ + displayName: 'Token', + required: true, + description: 'The token to delete', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: `https://api.zoo.dev/user/api-tokens/${propsValue.token}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/api-tokens/get-api-token.action.ts b/packages/pieces/community/zoo/src/lib/actions/api-tokens/get-api-token.action.ts new file mode 100644 index 0000000..3e4d9ee --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/api-tokens/get-api-token.action.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getApiTokenAction = createAction({ + name: 'get_api_token', + displayName: 'Get API Token', + description: 'Retrieve details of a specific API token', + auth: zooAuth, + // category: 'API Tokens', + props: { + token: Property.ShortText({ + displayName: 'Token', + required: true, + description: 'The token to retrieve details for', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/user/api-tokens/${propsValue.token}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/api-tokens/index.ts b/packages/pieces/community/zoo/src/lib/actions/api-tokens/index.ts new file mode 100644 index 0000000..2c36960 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/api-tokens/index.ts @@ -0,0 +1,11 @@ +import { listApiTokensAction } from './list-api-tokens.action'; +import { createApiTokenAction } from './create-api-token.action'; +import { getApiTokenAction } from './get-api-token.action'; +import { deleteApiTokenAction } from './delete-api-token.action'; + +export const API_TOKENS_ACTIONS = [ + listApiTokensAction, + createApiTokenAction, + getApiTokenAction, + deleteApiTokenAction, +]; diff --git a/packages/pieces/community/zoo/src/lib/actions/api-tokens/list-api-tokens.action.ts b/packages/pieces/community/zoo/src/lib/actions/api-tokens/list-api-tokens.action.ts new file mode 100644 index 0000000..e885351 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/api-tokens/list-api-tokens.action.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const listApiTokensAction = createAction({ + name: 'list_api_tokens', + displayName: 'List API Tokens', + description: 'List all API tokens for your user account', + auth: zooAuth, + // category: 'API Tokens', + props: { + limit: Property.Number({ + displayName: 'Limit', + required: false, + description: 'Maximum number of tokens to return', + }), + offset: Property.Number({ + displayName: 'Offset', + required: false, + description: 'Number of tokens to skip', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/user/api-tokens', + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + ...(propsValue.limit && { limit: propsValue.limit.toString() }), + ...(propsValue.offset && { offset: propsValue.offset.toString() }), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/file/convert-cad-file.action.ts b/packages/pieces/community/zoo/src/lib/actions/file/convert-cad-file.action.ts new file mode 100644 index 0000000..42f2a91 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/file/convert-cad-file.action.ts @@ -0,0 +1,62 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const convertCadFileAction = createAction({ + name: 'convert_cad_file', + displayName: 'Convert CAD File', + description: 'Convert a CAD file from one format to another', + auth: zooAuth, + // category: 'File Operations', + props: { + file: Property.File({ + displayName: 'CAD File', + required: true, + description: 'The CAD file to convert', + }), + sourceFormat: Property.StaticDropdown({ + displayName: 'Source Format', + required: true, + options: { + options: [ + { label: 'FBX', value: 'fbx' }, + { label: 'GLB', value: 'glb' }, + { label: 'GLTF', value: 'gltf' }, + { label: 'OBJ', value: 'obj' }, + { label: 'PLY', value: 'ply' }, + { label: 'STEP', value: 'step' }, + { label: 'STL', value: 'stl' }, + ], + }, + }), + outputFormat: Property.StaticDropdown({ + displayName: 'Output Format', + required: true, + options: { + options: [ + { label: 'FBX', value: 'fbx' }, + { label: 'GLB', value: 'glb' }, + { label: 'GLTF', value: 'gltf' }, + { label: 'OBJ', value: 'obj' }, + { label: 'PLY', value: 'ply' }, + { label: 'STEP', value: 'step' }, + { label: 'STL', value: 'stl' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const formData = new FormData(); + formData.append('file', new Blob([propsValue.file.data]), propsValue.file.filename); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.zoo.dev/file/conversion/${propsValue.sourceFormat}/${propsValue.outputFormat}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + body: formData, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/file/get-center-of-mass.action.ts b/packages/pieces/community/zoo/src/lib/actions/file/get-center-of-mass.action.ts new file mode 100644 index 0000000..496ff30 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/file/get-center-of-mass.action.ts @@ -0,0 +1,32 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getCenterOfMassAction = createAction({ + name: 'get_center_of_mass', + displayName: 'Get Center of Mass', + description: 'Calculate the center of mass of a CAD file', + auth: zooAuth, + // category: 'File Operations', + props: { + file: Property.File({ + displayName: 'CAD File', + required: true, + description: 'The CAD file to analyze', + }), + }, + async run({ auth, propsValue }) { + const formData = new FormData(); + formData.append('file', new Blob([propsValue.file.data]), propsValue.file.filename); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.zoo.dev/file/center-of-mass', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: formData, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/file/get-density.action.ts b/packages/pieces/community/zoo/src/lib/actions/file/get-density.action.ts new file mode 100644 index 0000000..66ab21f --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/file/get-density.action.ts @@ -0,0 +1,32 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getDensityAction = createAction({ + name: 'get_density', + displayName: 'Get Density', + description: 'Calculate the density of a CAD file', + auth: zooAuth, + // category: 'File Operations', + props: { + file: Property.File({ + displayName: 'CAD File', + required: true, + description: 'The CAD file to analyze', + }), + }, + async run({ auth, propsValue }) { + const formData = new FormData(); + formData.append('file', new Blob([propsValue.file.data]), propsValue.file.filename); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.zoo.dev/file/density', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: formData, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/file/get-mass.action.ts b/packages/pieces/community/zoo/src/lib/actions/file/get-mass.action.ts new file mode 100644 index 0000000..babe70d --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/file/get-mass.action.ts @@ -0,0 +1,32 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getMassAction = createAction({ + name: 'get_mass', + displayName: 'Get Mass', + description: 'Calculate the mass of a CAD file', + auth: zooAuth, + // category: 'File Operations', + props: { + file: Property.File({ + displayName: 'CAD File', + required: true, + description: 'The CAD file to analyze', + }), + }, + async run({ auth, propsValue }) { + const formData = new FormData(); + formData.append('file', new Blob([propsValue.file.data]), propsValue.file.filename); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.zoo.dev/file/mass', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: formData, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/file/get-surface-area.action.ts b/packages/pieces/community/zoo/src/lib/actions/file/get-surface-area.action.ts new file mode 100644 index 0000000..d7f2d9d --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/file/get-surface-area.action.ts @@ -0,0 +1,32 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getSurfaceAreaAction = createAction({ + name: 'get_surface_area', + displayName: 'Get Surface Area', + description: 'Calculate the surface area of a CAD file', + auth: zooAuth, + // category: 'File Operations', + props: { + file: Property.File({ + displayName: 'CAD File', + required: true, + description: 'The CAD file to analyze', + }), + }, + async run({ auth, propsValue }) { + const formData = new FormData(); + formData.append('file', new Blob([propsValue.file.data]), propsValue.file.filename); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.zoo.dev/file/surface-area', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: formData, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/file/get-volume.action.ts b/packages/pieces/community/zoo/src/lib/actions/file/get-volume.action.ts new file mode 100644 index 0000000..cd39431 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/file/get-volume.action.ts @@ -0,0 +1,32 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getVolumeAction = createAction({ + name: 'get_volume', + displayName: 'Get Volume', + description: 'Calculate the volume of a CAD file', + auth: zooAuth, + // category: 'File Operations', + props: { + file: Property.File({ + displayName: 'CAD File', + required: true, + description: 'The CAD file to analyze', + }), + }, + async run({ auth, propsValue }) { + const formData = new FormData(); + formData.append('file', new Blob([propsValue.file.data]), propsValue.file.filename); + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.zoo.dev/file/volume', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: formData, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/file/index.ts b/packages/pieces/community/zoo/src/lib/actions/file/index.ts new file mode 100644 index 0000000..e7831c7 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/file/index.ts @@ -0,0 +1,15 @@ +import { getCenterOfMassAction } from './get-center-of-mass.action'; +import { convertCadFileAction } from './convert-cad-file.action'; +import { getDensityAction } from './get-density.action'; +import { getMassAction } from './get-mass.action'; +import { getSurfaceAreaAction } from './get-surface-area.action'; +import { getVolumeAction } from './get-volume.action'; + +export const FILE_ACTIONS = [ + getCenterOfMassAction, + convertCadFileAction, + getDensityAction, + getMassAction, + getSurfaceAreaAction, + getVolumeAction, +]; diff --git a/packages/pieces/community/zoo/src/lib/actions/index.ts b/packages/pieces/community/zoo/src/lib/actions/index.ts new file mode 100644 index 0000000..ca87111 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/index.ts @@ -0,0 +1,15 @@ +import { generateCadModelAction } from './ml/generate-cad-model.action'; +import { kclCompletionsAction } from './ml/kcl-completions.action'; +import { textToCadIterationAction } from './ml/text-to-cad-iteration.action'; +import { listCadModelsAction } from './ml/list-cad-models.action'; +import { getCadModelAction } from './ml/get-cad-model.action'; +import { giveModelFeedbackAction } from './ml/give-model-feedback.action'; + +export const ML_ACTIONS = [ + generateCadModelAction, + kclCompletionsAction, + textToCadIterationAction, + listCadModelsAction, + getCadModelAction, + giveModelFeedbackAction, +]; diff --git a/packages/pieces/community/zoo/src/lib/actions/meta/get-openapi-schema.action.ts b/packages/pieces/community/zoo/src/lib/actions/meta/get-openapi-schema.action.ts new file mode 100644 index 0000000..a6a5ada --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/meta/get-openapi-schema.action.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getOpenApiSchemaAction = createAction({ + name: 'get_openapi_schema', + displayName: 'Get OpenAPI Schema', + description: 'Retrieve the OpenAPI schema for the Zoo API', + auth: zooAuth, + // category: 'Meta', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/meta/index.ts b/packages/pieces/community/zoo/src/lib/actions/meta/index.ts new file mode 100644 index 0000000..3986316 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/meta/index.ts @@ -0,0 +1,7 @@ +import { getOpenApiSchemaAction } from './get-openapi-schema.action'; +import { returnPongAction } from './return-pong.action'; + +export const META_ACTIONS = [ + getOpenApiSchemaAction, + returnPongAction, +]; diff --git a/packages/pieces/community/zoo/src/lib/actions/meta/return-pong.action.ts b/packages/pieces/community/zoo/src/lib/actions/meta/return-pong.action.ts new file mode 100644 index 0000000..48d8185 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/meta/return-pong.action.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const returnPongAction = createAction({ + name: 'return_pong', + displayName: 'Return Pong', + description: 'Health check endpoint that returns "pong"', + auth: zooAuth, + // category: 'Meta', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/ping', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/ml/generate-cad-model.action.ts b/packages/pieces/community/zoo/src/lib/actions/ml/generate-cad-model.action.ts new file mode 100644 index 0000000..ed20acc --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/ml/generate-cad-model.action.ts @@ -0,0 +1,50 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const generateCadModelAction = createAction({ + name: 'generate_cad_model', + displayName: 'Generate CAD Model', + description: 'Generate a 3D model from text prompt', + auth: zooAuth, + // category: 'Machine Learning (ML)', + props: { + outputFormat: Property.StaticDropdown({ + displayName: 'Output Format', + required: true, + options: { + options: [ + { label: 'FBX', value: 'fbx' }, + { label: 'GLB', value: 'glb' }, + { label: 'GLTF', value: 'gltf' }, + { label: 'OBJ', value: 'obj' }, + { label: 'PLY', value: 'ply' }, + { label: 'STEP', value: 'step' }, + { label: 'STL', value: 'stl' }, + ], + }, + }), + outputKcl: Property.Checkbox({ + displayName: 'Include KCL Output', + required: false, + }), + prompt: Property.ShortText({ + displayName: 'Prompt', + required: false, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.zoo.dev/ai/text-to-cad/${propsValue.outputFormat}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + output_kcl: propsValue.outputKcl, + prompt: propsValue.prompt, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/ml/get-cad-model.action.ts b/packages/pieces/community/zoo/src/lib/actions/ml/get-cad-model.action.ts new file mode 100644 index 0000000..55d1e4d --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/ml/get-cad-model.action.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getCadModelAction = createAction({ + name: 'get_cad_model', + displayName: 'Get CAD Model', + description: 'Retrieve details of a specific 3D model', + auth: zooAuth, + // category: 'Machine Learning (ML)', + props: { + modelId: Property.ShortText({ + displayName: 'Model ID', + required: true, + description: 'The ID of the model to retrieve', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/user/text-to-cad/${propsValue.modelId}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/ml/give-model-feedback.action.ts b/packages/pieces/community/zoo/src/lib/actions/ml/give-model-feedback.action.ts new file mode 100644 index 0000000..1a17c83 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/ml/give-model-feedback.action.ts @@ -0,0 +1,43 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const giveModelFeedbackAction = createAction({ + name: 'give_model_feedback', + displayName: 'Give Model Feedback', + description: 'Provide feedback on a generated 3D model', + auth: zooAuth, + // category: 'Machine Learning (ML)', + props: { + modelId: Property.ShortText({ + displayName: 'Model ID', + required: true, + description: 'The ID of the model to give feedback on', + }), + feedback: Property.StaticDropdown({ + displayName: 'Feedback Type', + required: true, + options: { + options: [ + { label: 'Thumbs Up', value: 'thumbs_up' }, + { label: 'Thumbs Down', value: 'thumbs_down' }, + { label: 'Accepted', value: 'accepted' }, + { label: 'Rejected', value: 'rejected' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.zoo.dev/user/text-to-cad/${propsValue.modelId}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + feedback: propsValue.feedback, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/ml/kcl-completions.action.ts b/packages/pieces/community/zoo/src/lib/actions/ml/kcl-completions.action.ts new file mode 100644 index 0000000..f9be5ad --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/ml/kcl-completions.action.ts @@ -0,0 +1,49 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const kclCompletionsAction = createAction({ + name: 'kcl_completions', + displayName: 'KCL Code Completions', + description: 'Get code completions for KCL (Kernel Configuration Language)', + auth: zooAuth, + // category: 'Machine Learning (ML)', + props: { + prompt: Property.LongText({ + displayName: 'Prompt', + required: true, + description: 'The KCL code prompt to get completions for', + }), + temperature: Property.Number({ + displayName: 'Temperature', + required: false, + description: 'Controls randomness in completion generation (0.0 to 1.0)', + }), + maxTokens: Property.Number({ + displayName: 'Max Tokens', + required: false, + description: 'Maximum number of tokens to generate', + }), + stop: Property.Array({ + displayName: 'Stop Sequences', + required: false, + description: 'Sequences where the API will stop generating further tokens', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.zoo.dev/ml/kcl/completions', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + prompt: propsValue.prompt, + temperature: propsValue.temperature, + max_tokens: propsValue.maxTokens, + stop: propsValue.stop, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/ml/list-cad-models.action.ts b/packages/pieces/community/zoo/src/lib/actions/ml/list-cad-models.action.ts new file mode 100644 index 0000000..ae109c3 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/ml/list-cad-models.action.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const listCadModelsAction = createAction({ + name: 'list_cad_models', + displayName: 'List CAD Models', + description: 'Retrieve a list of your generated 3D models', + auth: zooAuth, + // category: 'Machine Learning (ML)', + props: { + limit: Property.Number({ + displayName: 'Limit', + required: false, + description: 'Maximum number of models to return', + }), + offset: Property.Number({ + displayName: 'Offset', + required: false, + description: 'Number of models to skip', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/user/text-to-cad', + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + ...(propsValue.limit && { limit: propsValue.limit.toString() }), + ...(propsValue.offset && { offset: propsValue.offset.toString() }), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/ml/text-to-cad-iteration.action.ts b/packages/pieces/community/zoo/src/lib/actions/ml/text-to-cad-iteration.action.ts new file mode 100644 index 0000000..9de8f0a --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/ml/text-to-cad-iteration.action.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const textToCadIterationAction = createAction({ + name: 'text_to_cad_iteration', + displayName: 'Iterate CAD Model', + description: 'Create a new iteration of an existing 3D model', + auth: zooAuth, + // category: 'Machine Learning (ML)', + props: { + prompt: Property.ShortText({ + displayName: 'Prompt', + required: true, + description: 'The prompt describing desired changes', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.zoo.dev/ml/text-to-cad/iteration', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + prompt: propsValue.prompt, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/modeling/index.ts b/packages/pieces/community/zoo/src/lib/actions/modeling/index.ts new file mode 100644 index 0000000..654dc59 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/modeling/index.ts @@ -0,0 +1,5 @@ +import { sendModelingCommandAction } from './send-modeling-command.action'; + +export const MODELING_ACTIONS = [ + sendModelingCommandAction, +]; diff --git a/packages/pieces/community/zoo/src/lib/actions/modeling/send-modeling-command.action.ts b/packages/pieces/community/zoo/src/lib/actions/modeling/send-modeling-command.action.ts new file mode 100644 index 0000000..ccd77a6 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/modeling/send-modeling-command.action.ts @@ -0,0 +1,34 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const sendModelingCommandAction = createAction({ + name: 'send_modeling_command', + displayName: 'Send Modeling Command', + description: 'Send a command to the modeling WebSocket endpoint', + auth: zooAuth, + // category: 'Modeling', + props: { + command: Property.Object({ + displayName: 'Command', + required: true, + description: 'The modeling command to send', + }), + }, + async run({ auth, propsValue }) { + // First get the WebSocket URL + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/ws/modeling/commands', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + + // Return the WebSocket URL and command for the client to handle the connection + return { + websocketUrl: response.body.url, + command: propsValue.command, + }; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/orgs/add-org-member.action.ts b/packages/pieces/community/zoo/src/lib/actions/orgs/add-org-member.action.ts new file mode 100644 index 0000000..35a48d7 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/orgs/add-org-member.action.ts @@ -0,0 +1,42 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const addOrgMemberAction = createAction({ + name: 'add_org_member', + displayName: 'Add Organization Member', + description: 'Add a new member to your organization', + auth: zooAuth, + // category: 'Organizations', + props: { + email: Property.ShortText({ + displayName: 'Email', + required: true, + description: 'Email address of the user to add', + }), + role: Property.StaticDropdown({ + displayName: 'Role', + required: true, + options: { + options: [ + { label: 'Admin', value: 'admin' }, + { label: 'Member', value: 'member' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.zoo.dev/org/members', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + email: propsValue.email, + role: propsValue.role, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/orgs/create-org.action.ts b/packages/pieces/community/zoo/src/lib/actions/orgs/create-org.action.ts new file mode 100644 index 0000000..11a25cd --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/orgs/create-org.action.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const createOrgAction = createAction({ + name: 'create_org', + displayName: 'Create Organization', + description: 'Create a new organization', + auth: zooAuth, + // category: 'Organizations', + props: { + name: Property.ShortText({ + displayName: 'Organization Name', + required: true, + description: 'The name for the new organization', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.zoo.dev/org', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + name: propsValue.name, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/orgs/get-org-member.action.ts b/packages/pieces/community/zoo/src/lib/actions/orgs/get-org-member.action.ts new file mode 100644 index 0000000..dbcad97 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/orgs/get-org-member.action.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getOrgMemberAction = createAction({ + name: 'get_org_member', + displayName: 'Get Organization Member', + description: 'Get details of a specific organization member', + auth: zooAuth, + // category: 'Organizations', + props: { + userId: Property.ShortText({ + displayName: 'User ID', + required: true, + description: 'ID of the member to retrieve', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/org/members/${propsValue.userId}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/orgs/get-org.action.ts b/packages/pieces/community/zoo/src/lib/actions/orgs/get-org.action.ts new file mode 100644 index 0000000..b21d1fc --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/orgs/get-org.action.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getOrgAction = createAction({ + name: 'get_org', + displayName: 'Get Organization', + description: 'Retrieve details of your organization', + auth: zooAuth, + // category: 'Organizations', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/org', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/orgs/index.ts b/packages/pieces/community/zoo/src/lib/actions/orgs/index.ts new file mode 100644 index 0000000..4fb253a --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/orgs/index.ts @@ -0,0 +1,15 @@ +import { getOrgAction } from './get-org.action'; +import { updateOrgAction } from './update-org.action'; +import { createOrgAction } from './create-org.action'; +import { listOrgMembersAction } from './list-org-members.action'; +import { addOrgMemberAction } from './add-org-member.action'; +import { getOrgMemberAction } from './get-org-member.action'; + +export const ORGS_ACTIONS = [ + getOrgAction, + updateOrgAction, + createOrgAction, + listOrgMembersAction, + addOrgMemberAction, + getOrgMemberAction, +]; diff --git a/packages/pieces/community/zoo/src/lib/actions/orgs/list-org-members.action.ts b/packages/pieces/community/zoo/src/lib/actions/orgs/list-org-members.action.ts new file mode 100644 index 0000000..ac3ba26 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/orgs/list-org-members.action.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const listOrgMembersAction = createAction({ + name: 'list_org_members', + displayName: 'List Organization Members', + description: 'List all members of your organization', + auth: zooAuth, + // category: 'Organizations', + props: { + limit: Property.Number({ + displayName: 'Limit', + required: false, + description: 'Maximum number of members to return', + }), + offset: Property.Number({ + displayName: 'Offset', + required: false, + description: 'Number of members to skip', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/org/members', + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + ...(propsValue.limit && { limit: propsValue.limit.toString() }), + ...(propsValue.offset && { offset: propsValue.offset.toString() }), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/orgs/update-org.action.ts b/packages/pieces/community/zoo/src/lib/actions/orgs/update-org.action.ts new file mode 100644 index 0000000..be8f5b0 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/orgs/update-org.action.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const updateOrgAction = createAction({ + name: 'update_org', + displayName: 'Update Organization', + description: 'Update your organization details', + auth: zooAuth, + // category: 'Organizations', + props: { + name: Property.ShortText({ + displayName: 'Organization Name', + required: true, + description: 'The new name for your organization', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: 'https://api.zoo.dev/org', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + name: propsValue.name, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/create-org-payment.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/create-org-payment.action.ts new file mode 100644 index 0000000..c09e61a --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/create-org-payment.action.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const createOrgPaymentAction = createAction({ + name: 'create_org_payment', + displayName: 'Create Organization Payment Info', + description: 'Create payment information for your organization', + auth: zooAuth, + // category: 'Payments', + props: { + paymentMethodId: Property.ShortText({ + displayName: 'Payment Method ID', + required: true, + description: 'ID of the payment method to use', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.zoo.dev/org/payment', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + payment_method_id: propsValue.paymentMethodId, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/create-org-subscription.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/create-org-subscription.action.ts new file mode 100644 index 0000000..45c3060 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/create-org-subscription.action.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const createOrgSubscriptionAction = createAction({ + name: 'create_org_subscription', + displayName: 'Create Organization Subscription', + description: 'Create a new subscription for your organization', + auth: zooAuth, + // category: 'Payments', + props: { + planId: Property.ShortText({ + displayName: 'Plan ID', + required: true, + description: 'ID of the subscription plan', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.zoo.dev/org/payment/subscriptions', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + plan_id: propsValue.planId, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/create-user-payment.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/create-user-payment.action.ts new file mode 100644 index 0000000..f12787d --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/create-user-payment.action.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const createUserPaymentAction = createAction({ + name: 'create_user_payment', + displayName: 'Create User Payment Info', + description: 'Create payment information for your user account', + auth: zooAuth, + // category: 'Payments', + props: { + paymentMethodId: Property.ShortText({ + displayName: 'Payment Method ID', + required: true, + description: 'ID of the payment method to use', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.zoo.dev/user/payment', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + payment_method_id: propsValue.paymentMethodId, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/create-user-subscription.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/create-user-subscription.action.ts new file mode 100644 index 0000000..162430c --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/create-user-subscription.action.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const createUserSubscriptionAction = createAction({ + name: 'create_user_subscription', + displayName: 'Create User Subscription', + description: 'Create a new subscription for your user account', + auth: zooAuth, + // category: 'Payments', + props: { + planId: Property.ShortText({ + displayName: 'Plan ID', + required: true, + description: 'ID of the subscription plan', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.zoo.dev/user/payment/subscriptions', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + plan_id: propsValue.planId, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/delete-org-payment.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/delete-org-payment.action.ts new file mode 100644 index 0000000..c183026 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/delete-org-payment.action.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const deleteOrgPaymentAction = createAction({ + name: 'delete_org_payment', + displayName: 'Delete Organization Payment Info', + description: 'Delete payment information for your organization', + auth: zooAuth, + // category: 'Payments', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: 'https://api.zoo.dev/org/payment', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/delete-user-payment.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/delete-user-payment.action.ts new file mode 100644 index 0000000..1fe9179 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/delete-user-payment.action.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const deleteUserPaymentAction = createAction({ + name: 'delete_user_payment', + displayName: 'Delete User Payment Info', + description: 'Delete payment information for your user account', + auth: zooAuth, + // category: 'Payments', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: 'https://api.zoo.dev/user/payment', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/get-org-balance.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/get-org-balance.action.ts new file mode 100644 index 0000000..6d1c9e2 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/get-org-balance.action.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getOrgBalanceAction = createAction({ + name: 'get_org_balance', + displayName: 'Get Organization Balance', + description: 'Retrieve the current balance for your organization', + auth: zooAuth, + // category: 'Payments', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/org/payment/balance', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/get-org-payment.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/get-org-payment.action.ts new file mode 100644 index 0000000..c526c3f --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/get-org-payment.action.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getOrgPaymentAction = createAction({ + name: 'get_org_payment', + displayName: 'Get Organization Payment Info', + description: 'Retrieve payment information for your organization', + auth: zooAuth, + // category: 'Payments', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/org/payment', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/get-org-subscription.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/get-org-subscription.action.ts new file mode 100644 index 0000000..919a68f --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/get-org-subscription.action.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getOrgSubscriptionAction = createAction({ + name: 'get_org_subscription', + displayName: 'Get Organization Subscription', + description: 'Retrieve the current subscription for your organization', + auth: zooAuth, + // category: 'Payments', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/org/payment/subscriptions', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/get-user-balance.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/get-user-balance.action.ts new file mode 100644 index 0000000..9ed3aae --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/get-user-balance.action.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getUserBalanceAction = createAction({ + name: 'get_user_balance', + displayName: 'Get User Balance', + description: 'Retrieve the current balance for your user account', + auth: zooAuth, + // category: 'Payments', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/user/payment/balance', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/get-user-payment.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/get-user-payment.action.ts new file mode 100644 index 0000000..8b2c315 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/get-user-payment.action.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getUserPaymentAction = createAction({ + name: 'get_user_payment', + displayName: 'Get User Payment Info', + description: 'Retrieve payment information for your user account', + auth: zooAuth, + // category: 'Payments', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/user/payment', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/get-user-subscription.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/get-user-subscription.action.ts new file mode 100644 index 0000000..d2a79fe --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/get-user-subscription.action.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getUserSubscriptionAction = createAction({ + name: 'get_user_subscription', + displayName: 'Get User Subscription', + description: 'Retrieve the current subscription for your user account', + auth: zooAuth, + // category: 'Payments', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/user/payment/subscriptions', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/index.ts b/packages/pieces/community/zoo/src/lib/actions/payments/index.ts new file mode 100644 index 0000000..046ee9d --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/index.ts @@ -0,0 +1,43 @@ +import { getOrgPaymentAction } from './get-org-payment.action'; +import { updateOrgPaymentAction } from './update-org-payment.action'; +import { createOrgPaymentAction } from './create-org-payment.action'; +import { deleteOrgPaymentAction } from './delete-org-payment.action'; +import { getOrgBalanceAction } from './get-org-balance.action'; +import { listOrgInvoicesAction } from './list-org-invoices.action'; +import { listOrgPaymentMethodsAction } from './list-org-payment-methods.action'; +import { getOrgSubscriptionAction } from './get-org-subscription.action'; +import { updateOrgSubscriptionAction } from './update-org-subscription.action'; +import { createOrgSubscriptionAction } from './create-org-subscription.action'; +import { getUserPaymentAction } from './get-user-payment.action'; +import { updateUserPaymentAction } from './update-user-payment.action'; +import { createUserPaymentAction } from './create-user-payment.action'; +import { deleteUserPaymentAction } from './delete-user-payment.action'; +import { getUserBalanceAction } from './get-user-balance.action'; +import { listUserInvoicesAction } from './list-user-invoices.action'; +import { listUserPaymentMethodsAction } from './list-user-payment-methods.action'; +import { getUserSubscriptionAction } from './get-user-subscription.action'; +import { updateUserSubscriptionAction } from './update-user-subscription.action'; +import { createUserSubscriptionAction } from './create-user-subscription.action'; + +export const PAYMENTS_ACTIONS = [ + getOrgPaymentAction, + updateOrgPaymentAction, + createOrgPaymentAction, + deleteOrgPaymentAction, + getOrgBalanceAction, + listOrgInvoicesAction, + listOrgPaymentMethodsAction, + getOrgSubscriptionAction, + updateOrgSubscriptionAction, + createOrgSubscriptionAction, + getUserPaymentAction, + updateUserPaymentAction, + createUserPaymentAction, + deleteUserPaymentAction, + getUserBalanceAction, + listUserInvoicesAction, + listUserPaymentMethodsAction, + getUserSubscriptionAction, + updateUserSubscriptionAction, + createUserSubscriptionAction, +]; diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/list-org-invoices.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/list-org-invoices.action.ts new file mode 100644 index 0000000..cb7dc2e --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/list-org-invoices.action.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const listOrgInvoicesAction = createAction({ + name: 'list_org_invoices', + displayName: 'List Organization Invoices', + description: 'List all invoices for your organization', + auth: zooAuth, + // category: 'Payments', + props: { + limit: Property.Number({ + displayName: 'Limit', + required: false, + description: 'Maximum number of invoices to return', + }), + offset: Property.Number({ + displayName: 'Offset', + required: false, + description: 'Number of invoices to skip', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/org/payment/invoices', + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + ...(propsValue.limit && { limit: propsValue.limit.toString() }), + ...(propsValue.offset && { offset: propsValue.offset.toString() }), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/list-org-payment-methods.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/list-org-payment-methods.action.ts new file mode 100644 index 0000000..4309f36 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/list-org-payment-methods.action.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const listOrgPaymentMethodsAction = createAction({ + name: 'list_org_payment_methods', + displayName: 'List Organization Payment Methods', + description: 'List all payment methods for your organization', + auth: zooAuth, + // category: 'Payments', + props: { + limit: Property.Number({ + displayName: 'Limit', + required: false, + description: 'Maximum number of payment methods to return', + }), + offset: Property.Number({ + displayName: 'Offset', + required: false, + description: 'Number of payment methods to skip', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/org/payment/methods', + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + ...(propsValue.limit && { limit: propsValue.limit.toString() }), + ...(propsValue.offset && { offset: propsValue.offset.toString() }), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/list-user-invoices.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/list-user-invoices.action.ts new file mode 100644 index 0000000..d369ddb --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/list-user-invoices.action.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const listUserInvoicesAction = createAction({ + name: 'list_user_invoices', + displayName: 'List User Invoices', + description: 'List all invoices for your user account', + auth: zooAuth, + // category: 'Payments', + props: { + limit: Property.Number({ + displayName: 'Limit', + required: false, + description: 'Maximum number of invoices to return', + }), + offset: Property.Number({ + displayName: 'Offset', + required: false, + description: 'Number of invoices to skip', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/user/payment/invoices', + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + ...(propsValue.limit && { limit: propsValue.limit.toString() }), + ...(propsValue.offset && { offset: propsValue.offset.toString() }), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/list-user-payment-methods.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/list-user-payment-methods.action.ts new file mode 100644 index 0000000..5f34277 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/list-user-payment-methods.action.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const listUserPaymentMethodsAction = createAction({ + name: 'list_user_payment_methods', + displayName: 'List User Payment Methods', + description: 'List all payment methods for your user account', + auth: zooAuth, + // category: 'Payments', + props: { + limit: Property.Number({ + displayName: 'Limit', + required: false, + description: 'Maximum number of payment methods to return', + }), + offset: Property.Number({ + displayName: 'Offset', + required: false, + description: 'Number of payment methods to skip', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/user/payment/methods', + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + ...(propsValue.limit && { limit: propsValue.limit.toString() }), + ...(propsValue.offset && { offset: propsValue.offset.toString() }), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/update-org-payment.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/update-org-payment.action.ts new file mode 100644 index 0000000..cc7ea65 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/update-org-payment.action.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const updateOrgPaymentAction = createAction({ + name: 'update_org_payment', + displayName: 'Update Organization Payment Info', + description: 'Update payment information for your organization', + auth: zooAuth, + // category: 'Payments', + props: { + paymentMethodId: Property.ShortText({ + displayName: 'Payment Method ID', + required: true, + description: 'ID of the payment method to use', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: 'https://api.zoo.dev/org/payment', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + payment_method_id: propsValue.paymentMethodId, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/update-org-subscription.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/update-org-subscription.action.ts new file mode 100644 index 0000000..cfca090 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/update-org-subscription.action.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const updateOrgSubscriptionAction = createAction({ + name: 'update_org_subscription', + displayName: 'Update Organization Subscription', + description: 'Update the subscription for your organization', + auth: zooAuth, + // category: 'Payments', + props: { + planId: Property.ShortText({ + displayName: 'Plan ID', + required: true, + description: 'ID of the subscription plan', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: 'https://api.zoo.dev/org/payment/subscriptions', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + plan_id: propsValue.planId, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/update-user-payment.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/update-user-payment.action.ts new file mode 100644 index 0000000..5215982 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/update-user-payment.action.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const updateUserPaymentAction = createAction({ + name: 'update_user_payment', + displayName: 'Update User Payment Info', + description: 'Update payment information for your user account', + auth: zooAuth, + // category: 'Payments', + props: { + paymentMethodId: Property.ShortText({ + displayName: 'Payment Method ID', + required: true, + description: 'ID of the payment method to use', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: 'https://api.zoo.dev/user/payment', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + payment_method_id: propsValue.paymentMethodId, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/payments/update-user-subscription.action.ts b/packages/pieces/community/zoo/src/lib/actions/payments/update-user-subscription.action.ts new file mode 100644 index 0000000..d3dca5b --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/payments/update-user-subscription.action.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const updateUserSubscriptionAction = createAction({ + name: 'update_user_subscription', + displayName: 'Update User Subscription', + description: 'Update the subscription for your user account', + auth: zooAuth, + // category: 'Payments', + props: { + planId: Property.ShortText({ + displayName: 'Plan ID', + required: true, + description: 'ID of the subscription plan', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: 'https://api.zoo.dev/user/payment/subscriptions', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + plan_id: propsValue.planId, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/service-accounts/create-service-account.action.ts b/packages/pieces/community/zoo/src/lib/actions/service-accounts/create-service-account.action.ts new file mode 100644 index 0000000..689bfdd --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/service-accounts/create-service-account.action.ts @@ -0,0 +1,31 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const createServiceAccountAction = createAction({ + name: 'create_service_account', + displayName: 'Create Service Account', + description: 'Create a new service account for your organization', + auth: zooAuth, + // category: 'Service Accounts', + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + description: 'Name for the service account', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.zoo.dev/org/service-accounts', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + name: propsValue.name, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/service-accounts/delete-service-account.action.ts b/packages/pieces/community/zoo/src/lib/actions/service-accounts/delete-service-account.action.ts new file mode 100644 index 0000000..c3ad2d7 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/service-accounts/delete-service-account.action.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const deleteServiceAccountAction = createAction({ + name: 'delete_service_account', + displayName: 'Delete Service Account', + description: 'Delete a service account from your organization', + auth: zooAuth, + // category: 'Service Accounts', + props: { + token: Property.ShortText({ + displayName: 'Token', + required: true, + description: 'Token of the service account to delete', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: `https://api.zoo.dev/org/service-accounts/${propsValue.token}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/service-accounts/get-service-account.action.ts b/packages/pieces/community/zoo/src/lib/actions/service-accounts/get-service-account.action.ts new file mode 100644 index 0000000..de57711 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/service-accounts/get-service-account.action.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getServiceAccountAction = createAction({ + name: 'get_service_account', + displayName: 'Get Service Account', + description: 'Retrieve details of a specific service account', + auth: zooAuth, + // category: 'Service Accounts', + props: { + token: Property.ShortText({ + displayName: 'Token', + required: true, + description: 'Token of the service account to retrieve', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/org/service-accounts/${propsValue.token}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/service-accounts/index.ts b/packages/pieces/community/zoo/src/lib/actions/service-accounts/index.ts new file mode 100644 index 0000000..5742cca --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/service-accounts/index.ts @@ -0,0 +1,11 @@ +import { listServiceAccountsAction } from './list-service-accounts.action'; +import { createServiceAccountAction } from './create-service-account.action'; +import { getServiceAccountAction } from './get-service-account.action'; +import { deleteServiceAccountAction } from './delete-service-account.action'; + +export const SERVICE_ACCOUNTS_ACTIONS = [ + listServiceAccountsAction, + createServiceAccountAction, + getServiceAccountAction, + deleteServiceAccountAction, +]; diff --git a/packages/pieces/community/zoo/src/lib/actions/service-accounts/list-service-accounts.action.ts b/packages/pieces/community/zoo/src/lib/actions/service-accounts/list-service-accounts.action.ts new file mode 100644 index 0000000..9015763 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/service-accounts/list-service-accounts.action.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const listServiceAccountsAction = createAction({ + name: 'list_service_accounts', + displayName: 'List Service Accounts', + description: 'List all service accounts for your organization', + auth: zooAuth, + // category: 'Service Accounts', + props: { + limit: Property.Number({ + displayName: 'Limit', + required: false, + description: 'Maximum number of service accounts to return', + }), + offset: Property.Number({ + displayName: 'Offset', + required: false, + description: 'Number of service accounts to skip', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/org/service-accounts', + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + ...(propsValue.limit && { limit: propsValue.limit.toString() }), + ...(propsValue.offset && { offset: propsValue.offset.toString() }), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/shortlinks/create-shortlink.action.ts b/packages/pieces/community/zoo/src/lib/actions/shortlinks/create-shortlink.action.ts new file mode 100644 index 0000000..6fcff64 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/shortlinks/create-shortlink.action.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const createShortlinkAction = createAction({ + name: 'create_shortlink', + displayName: 'Create Shortlink', + description: 'Create a new shortlink for your user account', + auth: zooAuth, + // category: 'Shortlinks', + props: { + url: Property.ShortText({ + displayName: 'URL', + required: true, + description: 'The URL to shorten', + }), + key: Property.ShortText({ + displayName: 'Key', + required: false, + description: 'Custom key for the shortlink (optional)', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: 'https://api.zoo.dev/user/shortlinks', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + url: propsValue.url, + ...(propsValue.key && { key: propsValue.key }), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/shortlinks/delete-shortlink.action.ts b/packages/pieces/community/zoo/src/lib/actions/shortlinks/delete-shortlink.action.ts new file mode 100644 index 0000000..0377b26 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/shortlinks/delete-shortlink.action.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const deleteShortlinkAction = createAction({ + name: 'delete_shortlink', + displayName: 'Delete Shortlink', + description: 'Delete an existing shortlink', + auth: zooAuth, + // category: 'Shortlinks', + props: { + key: Property.ShortText({ + displayName: 'Key', + required: true, + description: 'The key of the shortlink to delete', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: `https://api.zoo.dev/user/shortlinks/${propsValue.key}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/shortlinks/index.ts b/packages/pieces/community/zoo/src/lib/actions/shortlinks/index.ts new file mode 100644 index 0000000..901e57d --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/shortlinks/index.ts @@ -0,0 +1,13 @@ +import { listOrgShortlinksAction } from './list-org-shortlinks.action'; +import { listUserShortlinksAction } from './list-user-shortlinks.action'; +import { createShortlinkAction } from './create-shortlink.action'; +import { updateShortlinkAction } from './update-shortlink.action'; +import { deleteShortlinkAction } from './delete-shortlink.action'; + +export const SHORTLINKS_ACTIONS = [ + listOrgShortlinksAction, + listUserShortlinksAction, + createShortlinkAction, + updateShortlinkAction, + deleteShortlinkAction, +]; diff --git a/packages/pieces/community/zoo/src/lib/actions/shortlinks/list-org-shortlinks.action.ts b/packages/pieces/community/zoo/src/lib/actions/shortlinks/list-org-shortlinks.action.ts new file mode 100644 index 0000000..3444777 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/shortlinks/list-org-shortlinks.action.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const listOrgShortlinksAction = createAction({ + name: 'list_org_shortlinks', + displayName: 'List Organization Shortlinks', + description: 'List all shortlinks for your organization', + auth: zooAuth, + // category: 'Shortlinks', + props: { + limit: Property.Number({ + displayName: 'Limit', + required: false, + description: 'Maximum number of shortlinks to return', + }), + offset: Property.Number({ + displayName: 'Offset', + required: false, + description: 'Number of shortlinks to skip', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/org/shortlinks', + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + ...(propsValue.limit && { limit: propsValue.limit.toString() }), + ...(propsValue.offset && { offset: propsValue.offset.toString() }), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/shortlinks/list-user-shortlinks.action.ts b/packages/pieces/community/zoo/src/lib/actions/shortlinks/list-user-shortlinks.action.ts new file mode 100644 index 0000000..db8b0cc --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/shortlinks/list-user-shortlinks.action.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const listUserShortlinksAction = createAction({ + name: 'list_user_shortlinks', + displayName: 'List User Shortlinks', + description: 'List all shortlinks for your user account', + auth: zooAuth, + // category: 'Shortlinks', + props: { + limit: Property.Number({ + displayName: 'Limit', + required: false, + description: 'Maximum number of shortlinks to return', + }), + offset: Property.Number({ + displayName: 'Offset', + required: false, + description: 'Number of shortlinks to skip', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/user/shortlinks', + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + ...(propsValue.limit && { limit: propsValue.limit.toString() }), + ...(propsValue.offset && { offset: propsValue.offset.toString() }), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/shortlinks/update-shortlink.action.ts b/packages/pieces/community/zoo/src/lib/actions/shortlinks/update-shortlink.action.ts new file mode 100644 index 0000000..d8178bd --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/shortlinks/update-shortlink.action.ts @@ -0,0 +1,36 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const updateShortlinkAction = createAction({ + name: 'update_shortlink', + displayName: 'Update Shortlink', + description: 'Update an existing shortlink', + auth: zooAuth, + // category: 'Shortlinks', + props: { + key: Property.ShortText({ + displayName: 'Key', + required: true, + description: 'The key of the shortlink to update', + }), + url: Property.ShortText({ + displayName: 'URL', + required: true, + description: 'The new URL for the shortlink', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: `https://api.zoo.dev/user/shortlinks/${propsValue.key}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + url: propsValue.url, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/unit/convert-angle.action.ts b/packages/pieces/community/zoo/src/lib/actions/unit/convert-angle.action.ts new file mode 100644 index 0000000..33047d9 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/unit/convert-angle.action.ts @@ -0,0 +1,53 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const convertAngleAction = createAction({ + name: 'convert_angle', + displayName: 'Convert Angle', + description: 'Convert angle measurements between different units', + auth: zooAuth, + // category: 'Unit Conversion', + props: { + value: Property.Number({ + displayName: 'Value', + required: true, + description: 'The angle value to convert', + }), + inputUnit: Property.StaticDropdown({ + displayName: 'Input Unit', + required: true, + options: { + options: [ + { label: 'Degrees', value: 'deg' }, + { label: 'Radians', value: 'rad' }, + { label: 'Gradians', value: 'grad' }, + ], + }, + }), + outputUnit: Property.StaticDropdown({ + displayName: 'Output Unit', + required: true, + options: { + options: [ + { label: 'Degrees', value: 'deg' }, + { label: 'Radians', value: 'rad' }, + { label: 'Gradians', value: 'grad' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/unit/conversion/angle/${propsValue.inputUnit}/${propsValue.outputUnit}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + value: propsValue.value.toString(), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/unit/convert-area.action.ts b/packages/pieces/community/zoo/src/lib/actions/unit/convert-area.action.ts new file mode 100644 index 0000000..c792866 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/unit/convert-area.action.ts @@ -0,0 +1,63 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const convertAreaAction = createAction({ + name: 'convert_area', + displayName: 'Convert Area', + description: 'Convert area measurements between different units', + auth: zooAuth, + // category: 'Unit Conversion', + props: { + value: Property.Number({ + displayName: 'Value', + required: true, + description: 'The area value to convert', + }), + inputUnit: Property.StaticDropdown({ + displayName: 'Input Unit', + required: true, + options: { + options: [ + { label: 'Square Meters', value: 'm2' }, + { label: 'Square Feet', value: 'ft2' }, + { label: 'Square Inches', value: 'in2' }, + { label: 'Square Yards', value: 'yd2' }, + { label: 'Square Kilometers', value: 'km2' }, + { label: 'Square Miles', value: 'mi2' }, + { label: 'Hectares', value: 'ha' }, + { label: 'Acres', value: 'ac' }, + ], + }, + }), + outputUnit: Property.StaticDropdown({ + displayName: 'Output Unit', + required: true, + options: { + options: [ + { label: 'Square Meters', value: 'm2' }, + { label: 'Square Feet', value: 'ft2' }, + { label: 'Square Inches', value: 'in2' }, + { label: 'Square Yards', value: 'yd2' }, + { label: 'Square Kilometers', value: 'km2' }, + { label: 'Square Miles', value: 'mi2' }, + { label: 'Hectares', value: 'ha' }, + { label: 'Acres', value: 'ac' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/unit/conversion/area/${propsValue.inputUnit}/${propsValue.outputUnit}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + value: propsValue.value.toString(), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/unit/convert-current.action.ts b/packages/pieces/community/zoo/src/lib/actions/unit/convert-current.action.ts new file mode 100644 index 0000000..0de39cf --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/unit/convert-current.action.ts @@ -0,0 +1,53 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const convertCurrentAction = createAction({ + name: 'convert_current', + displayName: 'Convert Current', + description: 'Convert electrical current measurements between different units', + auth: zooAuth, + // category: 'Unit Conversion', + props: { + value: Property.Number({ + displayName: 'Value', + required: true, + description: 'The current value to convert', + }), + inputUnit: Property.StaticDropdown({ + displayName: 'Input Unit', + required: true, + options: { + options: [ + { label: 'Amperes', value: 'A' }, + { label: 'Milliamperes', value: 'mA' }, + { label: 'Kiloamperes', value: 'kA' }, + ], + }, + }), + outputUnit: Property.StaticDropdown({ + displayName: 'Output Unit', + required: true, + options: { + options: [ + { label: 'Amperes', value: 'A' }, + { label: 'Milliamperes', value: 'mA' }, + { label: 'Kiloamperes', value: 'kA' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/unit/conversion/current/${propsValue.inputUnit}/${propsValue.outputUnit}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + value: propsValue.value.toString(), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/unit/convert-energy.action.ts b/packages/pieces/community/zoo/src/lib/actions/unit/convert-energy.action.ts new file mode 100644 index 0000000..5123457 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/unit/convert-energy.action.ts @@ -0,0 +1,61 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const convertEnergyAction = createAction({ + name: 'convert_energy', + displayName: 'Convert Energy', + description: 'Convert energy measurements between different units', + auth: zooAuth, + // category: 'Unit Conversion', + props: { + value: Property.Number({ + displayName: 'Value', + required: true, + description: 'The energy value to convert', + }), + inputUnit: Property.StaticDropdown({ + displayName: 'Input Unit', + required: true, + options: { + options: [ + { label: 'Joules', value: 'J' }, + { label: 'Kilojoules', value: 'kJ' }, + { label: 'Calories', value: 'cal' }, + { label: 'Kilocalories', value: 'kcal' }, + { label: 'Watt Hours', value: 'Wh' }, + { label: 'Kilowatt Hours', value: 'kWh' }, + { label: 'British Thermal Units', value: 'BTU' }, + ], + }, + }), + outputUnit: Property.StaticDropdown({ + displayName: 'Output Unit', + required: true, + options: { + options: [ + { label: 'Joules', value: 'J' }, + { label: 'Kilojoules', value: 'kJ' }, + { label: 'Calories', value: 'cal' }, + { label: 'Kilocalories', value: 'kcal' }, + { label: 'Watt Hours', value: 'Wh' }, + { label: 'Kilowatt Hours', value: 'kWh' }, + { label: 'British Thermal Units', value: 'BTU' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/unit/conversion/energy/${propsValue.inputUnit}/${propsValue.outputUnit}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + value: propsValue.value.toString(), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/unit/convert-force.action.ts b/packages/pieces/community/zoo/src/lib/actions/unit/convert-force.action.ts new file mode 100644 index 0000000..ad27a4e --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/unit/convert-force.action.ts @@ -0,0 +1,55 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const convertForceAction = createAction({ + name: 'convert_force', + displayName: 'Convert Force', + description: 'Convert force measurements between different units', + auth: zooAuth, + // category: 'Unit Conversion', + props: { + value: Property.Number({ + displayName: 'Value', + required: true, + description: 'The force value to convert', + }), + inputUnit: Property.StaticDropdown({ + displayName: 'Input Unit', + required: true, + options: { + options: [ + { label: 'Newtons', value: 'N' }, + { label: 'Kilonewtons', value: 'kN' }, + { label: 'Pound-force', value: 'lbf' }, + { label: 'Dynes', value: 'dyn' }, + ], + }, + }), + outputUnit: Property.StaticDropdown({ + displayName: 'Output Unit', + required: true, + options: { + options: [ + { label: 'Newtons', value: 'N' }, + { label: 'Kilonewtons', value: 'kN' }, + { label: 'Pound-force', value: 'lbf' }, + { label: 'Dynes', value: 'dyn' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/unit/conversion/force/${propsValue.inputUnit}/${propsValue.outputUnit}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + value: propsValue.value.toString(), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/unit/convert-frequency.action.ts b/packages/pieces/community/zoo/src/lib/actions/unit/convert-frequency.action.ts new file mode 100644 index 0000000..555142d --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/unit/convert-frequency.action.ts @@ -0,0 +1,55 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const convertFrequencyAction = createAction({ + name: 'convert_frequency', + displayName: 'Convert Frequency', + description: 'Convert frequency measurements between different units', + auth: zooAuth, + // category: 'Unit Conversion', + props: { + value: Property.Number({ + displayName: 'Value', + required: true, + description: 'The frequency value to convert', + }), + inputUnit: Property.StaticDropdown({ + displayName: 'Input Unit', + required: true, + options: { + options: [ + { label: 'Hertz', value: 'Hz' }, + { label: 'Kilohertz', value: 'kHz' }, + { label: 'Megahertz', value: 'MHz' }, + { label: 'Gigahertz', value: 'GHz' }, + ], + }, + }), + outputUnit: Property.StaticDropdown({ + displayName: 'Output Unit', + required: true, + options: { + options: [ + { label: 'Hertz', value: 'Hz' }, + { label: 'Kilohertz', value: 'kHz' }, + { label: 'Megahertz', value: 'MHz' }, + { label: 'Gigahertz', value: 'GHz' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/unit/conversion/frequency/${propsValue.inputUnit}/${propsValue.outputUnit}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + value: propsValue.value.toString(), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/unit/convert-length.action.ts b/packages/pieces/community/zoo/src/lib/actions/unit/convert-length.action.ts new file mode 100644 index 0000000..c21d1af --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/unit/convert-length.action.ts @@ -0,0 +1,63 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const convertLengthAction = createAction({ + name: 'convert_length', + displayName: 'Convert Length', + description: 'Convert length measurements between different units', + auth: zooAuth, + // category: 'Unit Conversion', + props: { + value: Property.Number({ + displayName: 'Value', + required: true, + description: 'The length value to convert', + }), + inputUnit: Property.StaticDropdown({ + displayName: 'Input Unit', + required: true, + options: { + options: [ + { label: 'Meters', value: 'm' }, + { label: 'Kilometers', value: 'km' }, + { label: 'Centimeters', value: 'cm' }, + { label: 'Millimeters', value: 'mm' }, + { label: 'Inches', value: 'in' }, + { label: 'Feet', value: 'ft' }, + { label: 'Yards', value: 'yd' }, + { label: 'Miles', value: 'mi' }, + ], + }, + }), + outputUnit: Property.StaticDropdown({ + displayName: 'Output Unit', + required: true, + options: { + options: [ + { label: 'Meters', value: 'm' }, + { label: 'Kilometers', value: 'km' }, + { label: 'Centimeters', value: 'cm' }, + { label: 'Millimeters', value: 'mm' }, + { label: 'Inches', value: 'in' }, + { label: 'Feet', value: 'ft' }, + { label: 'Yards', value: 'yd' }, + { label: 'Miles', value: 'mi' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/unit/conversion/length/${propsValue.inputUnit}/${propsValue.outputUnit}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + value: propsValue.value.toString(), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/unit/convert-mass.action.ts b/packages/pieces/community/zoo/src/lib/actions/unit/convert-mass.action.ts new file mode 100644 index 0000000..cdcda8a --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/unit/convert-mass.action.ts @@ -0,0 +1,59 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const convertMassAction = createAction({ + name: 'convert_mass', + displayName: 'Convert Mass', + description: 'Convert mass measurements between different units', + auth: zooAuth, + // category: 'Unit Conversion', + props: { + value: Property.Number({ + displayName: 'Value', + required: true, + description: 'The mass value to convert', + }), + inputUnit: Property.StaticDropdown({ + displayName: 'Input Unit', + required: true, + options: { + options: [ + { label: 'Kilograms', value: 'kg' }, + { label: 'Grams', value: 'g' }, + { label: 'Milligrams', value: 'mg' }, + { label: 'Pounds', value: 'lb' }, + { label: 'Ounces', value: 'oz' }, + { label: 'Metric Tons', value: 't' }, + ], + }, + }), + outputUnit: Property.StaticDropdown({ + displayName: 'Output Unit', + required: true, + options: { + options: [ + { label: 'Kilograms', value: 'kg' }, + { label: 'Grams', value: 'g' }, + { label: 'Milligrams', value: 'mg' }, + { label: 'Pounds', value: 'lb' }, + { label: 'Ounces', value: 'oz' }, + { label: 'Metric Tons', value: 't' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/unit/conversion/mass/${propsValue.inputUnit}/${propsValue.outputUnit}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + value: propsValue.value.toString(), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/unit/convert-power.action.ts b/packages/pieces/community/zoo/src/lib/actions/unit/convert-power.action.ts new file mode 100644 index 0000000..d415461 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/unit/convert-power.action.ts @@ -0,0 +1,55 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const convertPowerAction = createAction({ + name: 'convert_power', + displayName: 'Convert Power', + description: 'Convert power measurements between different units', + auth: zooAuth, + // category: 'Unit Conversion', + props: { + value: Property.Number({ + displayName: 'Value', + required: true, + description: 'The power value to convert', + }), + inputUnit: Property.StaticDropdown({ + displayName: 'Input Unit', + required: true, + options: { + options: [ + { label: 'Watts', value: 'W' }, + { label: 'Kilowatts', value: 'kW' }, + { label: 'Megawatts', value: 'MW' }, + { label: 'Horsepower', value: 'hp' }, + ], + }, + }), + outputUnit: Property.StaticDropdown({ + displayName: 'Output Unit', + required: true, + options: { + options: [ + { label: 'Watts', value: 'W' }, + { label: 'Kilowatts', value: 'kW' }, + { label: 'Megawatts', value: 'MW' }, + { label: 'Horsepower', value: 'hp' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/unit/conversion/power/${propsValue.inputUnit}/${propsValue.outputUnit}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + value: propsValue.value.toString(), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/unit/convert-pressure.action.ts b/packages/pieces/community/zoo/src/lib/actions/unit/convert-pressure.action.ts new file mode 100644 index 0000000..e127824 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/unit/convert-pressure.action.ts @@ -0,0 +1,57 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const convertPressureAction = createAction({ + name: 'convert_pressure', + displayName: 'Convert Pressure', + description: 'Convert pressure measurements between different units', + auth: zooAuth, + // category: 'Unit Conversion', + props: { + value: Property.Number({ + displayName: 'Value', + required: true, + description: 'The pressure value to convert', + }), + inputUnit: Property.StaticDropdown({ + displayName: 'Input Unit', + required: true, + options: { + options: [ + { label: 'Pascal', value: 'Pa' }, + { label: 'Kilopascal', value: 'kPa' }, + { label: 'Bar', value: 'bar' }, + { label: 'Atmosphere', value: 'atm' }, + { label: 'Pounds per Square Inch', value: 'psi' }, + ], + }, + }), + outputUnit: Property.StaticDropdown({ + displayName: 'Output Unit', + required: true, + options: { + options: [ + { label: 'Pascal', value: 'Pa' }, + { label: 'Kilopascal', value: 'kPa' }, + { label: 'Bar', value: 'bar' }, + { label: 'Atmosphere', value: 'atm' }, + { label: 'Pounds per Square Inch', value: 'psi' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/unit/conversion/pressure/${propsValue.inputUnit}/${propsValue.outputUnit}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + value: propsValue.value.toString(), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/unit/convert-temperature.action.ts b/packages/pieces/community/zoo/src/lib/actions/unit/convert-temperature.action.ts new file mode 100644 index 0000000..1089180 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/unit/convert-temperature.action.ts @@ -0,0 +1,53 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const convertTemperatureAction = createAction({ + name: 'convert_temperature', + displayName: 'Convert Temperature', + description: 'Convert temperature measurements between different units', + auth: zooAuth, + // category: 'Unit Conversion', + props: { + value: Property.Number({ + displayName: 'Value', + required: true, + description: 'The temperature value to convert', + }), + inputUnit: Property.StaticDropdown({ + displayName: 'Input Unit', + required: true, + options: { + options: [ + { label: 'Celsius', value: 'C' }, + { label: 'Fahrenheit', value: 'F' }, + { label: 'Kelvin', value: 'K' }, + ], + }, + }), + outputUnit: Property.StaticDropdown({ + displayName: 'Output Unit', + required: true, + options: { + options: [ + { label: 'Celsius', value: 'C' }, + { label: 'Fahrenheit', value: 'F' }, + { label: 'Kelvin', value: 'K' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/unit/conversion/temperature/${propsValue.inputUnit}/${propsValue.outputUnit}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + value: propsValue.value.toString(), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/unit/convert-torque.action.ts b/packages/pieces/community/zoo/src/lib/actions/unit/convert-torque.action.ts new file mode 100644 index 0000000..8dcfd25 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/unit/convert-torque.action.ts @@ -0,0 +1,53 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const convertTorqueAction = createAction({ + name: 'convert_torque', + displayName: 'Convert Torque', + description: 'Convert torque measurements between different units', + auth: zooAuth, + // category: 'Unit Conversion', + props: { + value: Property.Number({ + displayName: 'Value', + required: true, + description: 'The torque value to convert', + }), + inputUnit: Property.StaticDropdown({ + displayName: 'Input Unit', + required: true, + options: { + options: [ + { label: 'Newton Meter', value: 'Nm' }, + { label: 'Pound Foot', value: 'lbft' }, + { label: 'Kilogram Force Meter', value: 'kgfm' }, + ], + }, + }), + outputUnit: Property.StaticDropdown({ + displayName: 'Output Unit', + required: true, + options: { + options: [ + { label: 'Newton Meter', value: 'Nm' }, + { label: 'Pound Foot', value: 'lbft' }, + { label: 'Kilogram Force Meter', value: 'kgfm' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/unit/conversion/torque/${propsValue.inputUnit}/${propsValue.outputUnit}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + value: propsValue.value.toString(), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/unit/convert-volume.action.ts b/packages/pieces/community/zoo/src/lib/actions/unit/convert-volume.action.ts new file mode 100644 index 0000000..a886bfd --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/unit/convert-volume.action.ts @@ -0,0 +1,61 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const convertVolumeAction = createAction({ + name: 'convert_volume', + displayName: 'Convert Volume', + description: 'Convert volume measurements between different units', + auth: zooAuth, + // category: 'Unit Conversion', + props: { + value: Property.Number({ + displayName: 'Value', + required: true, + description: 'The volume value to convert', + }), + inputUnit: Property.StaticDropdown({ + displayName: 'Input Unit', + required: true, + options: { + options: [ + { label: 'Cubic Meters', value: 'm3' }, + { label: 'Cubic Feet', value: 'ft3' }, + { label: 'Cubic Inches', value: 'in3' }, + { label: 'Liters', value: 'L' }, + { label: 'Gallons', value: 'gal' }, + { label: 'Milliliters', value: 'mL' }, + { label: 'Fluid Ounces', value: 'fl_oz' }, + ], + }, + }), + outputUnit: Property.StaticDropdown({ + displayName: 'Output Unit', + required: true, + options: { + options: [ + { label: 'Cubic Meters', value: 'm3' }, + { label: 'Cubic Feet', value: 'ft3' }, + { label: 'Cubic Inches', value: 'in3' }, + { label: 'Liters', value: 'L' }, + { label: 'Gallons', value: 'gal' }, + { label: 'Milliliters', value: 'mL' }, + { label: 'Fluid Ounces', value: 'fl_oz' }, + ], + }, + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/unit/conversion/volume/${propsValue.inputUnit}/${propsValue.outputUnit}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + queryParams: { + value: propsValue.value.toString(), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/unit/index.ts b/packages/pieces/community/zoo/src/lib/actions/unit/index.ts new file mode 100644 index 0000000..9ab1856 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/unit/index.ts @@ -0,0 +1,29 @@ +import { convertAngleAction } from './convert-angle.action'; +import { convertAreaAction } from './convert-area.action'; +import { convertCurrentAction } from './convert-current.action'; +import { convertEnergyAction } from './convert-energy.action'; +import { convertForceAction } from './convert-force.action'; +import { convertFrequencyAction } from './convert-frequency.action'; +import { convertLengthAction } from './convert-length.action'; +import { convertMassAction } from './convert-mass.action'; +import { convertPowerAction } from './convert-power.action'; +import { convertPressureAction } from './convert-pressure.action'; +import { convertTemperatureAction } from './convert-temperature.action'; +import { convertTorqueAction } from './convert-torque.action'; +import { convertVolumeAction } from './convert-volume.action'; + +export const UNIT_ACTIONS = [ + convertAngleAction, + convertAreaAction, + convertCurrentAction, + convertEnergyAction, + convertForceAction, + convertFrequencyAction, + convertLengthAction, + convertMassAction, + convertPowerAction, + convertPressureAction, + convertTemperatureAction, + convertTorqueAction, + convertVolumeAction, +]; diff --git a/packages/pieces/community/zoo/src/lib/actions/users/delete-user.action.ts b/packages/pieces/community/zoo/src/lib/actions/users/delete-user.action.ts new file mode 100644 index 0000000..d5b337c --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/users/delete-user.action.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const deleteUserAction = createAction({ + name: 'delete_user', + displayName: 'Delete User', + description: 'Delete your user account', + auth: zooAuth, + // category: 'Users', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.DELETE, + url: 'https://api.zoo.dev/user', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/users/get-extended-user.action.ts b/packages/pieces/community/zoo/src/lib/actions/users/get-extended-user.action.ts new file mode 100644 index 0000000..740151a --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/users/get-extended-user.action.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getExtendedUserAction = createAction({ + name: 'get_extended_user', + displayName: 'Get Extended User Info', + description: 'Retrieve extended information about your user account', + auth: zooAuth, + // category: 'Users', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/user/extended', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/users/get-oauth2-providers.action.ts b/packages/pieces/community/zoo/src/lib/actions/users/get-oauth2-providers.action.ts new file mode 100644 index 0000000..6e32aad --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/users/get-oauth2-providers.action.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getOAuth2ProvidersAction = createAction({ + name: 'get_oauth2_providers', + displayName: 'Get OAuth2 Providers', + description: 'Get the OAuth2 providers available for your user account', + auth: zooAuth, + // category: 'Users', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/user/oauth2/providers', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/users/get-privacy-settings.action.ts b/packages/pieces/community/zoo/src/lib/actions/users/get-privacy-settings.action.ts new file mode 100644 index 0000000..99b95ae --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/users/get-privacy-settings.action.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getPrivacySettingsAction = createAction({ + name: 'get_privacy_settings', + displayName: 'Get Privacy Settings', + description: 'Get your user privacy settings', + auth: zooAuth, + // category: 'Users', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/user/privacy', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/users/get-user-org.action.ts b/packages/pieces/community/zoo/src/lib/actions/users/get-user-org.action.ts new file mode 100644 index 0000000..a91f9aa --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/users/get-user-org.action.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getUserOrgAction = createAction({ + name: 'get_user_org', + displayName: 'Get User Organization', + description: 'Get the organization associated with your user account', + auth: zooAuth, + // category: 'Users', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/user/org', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/users/get-user-session.action.ts b/packages/pieces/community/zoo/src/lib/actions/users/get-user-session.action.ts new file mode 100644 index 0000000..be18d5a --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/users/get-user-session.action.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getUserSessionAction = createAction({ + name: 'get_user_session', + displayName: 'Get User Session', + description: 'Get details about a specific user session', + auth: zooAuth, + // category: 'Users', + props: { + token: Property.ShortText({ + displayName: 'Session Token', + required: true, + description: 'The token of the session to retrieve', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.zoo.dev/user/session/${propsValue.token}`, + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/users/get-user.action.ts b/packages/pieces/community/zoo/src/lib/actions/users/get-user.action.ts new file mode 100644 index 0000000..81cc529 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/users/get-user.action.ts @@ -0,0 +1,22 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getUserAction = createAction({ + name: 'get_user', + displayName: 'Get User', + description: 'Retrieve your user information', + auth: zooAuth, + // category: 'Users', + props: {}, + async run({ auth }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.zoo.dev/user', + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/users/index.ts b/packages/pieces/community/zoo/src/lib/actions/users/index.ts new file mode 100644 index 0000000..59649c5 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/users/index.ts @@ -0,0 +1,21 @@ +import { getUserAction } from './get-user.action'; +import { updateUserAction } from './update-user.action'; +import { deleteUserAction } from './delete-user.action'; +import { getExtendedUserAction } from './get-extended-user.action'; +import { getOAuth2ProvidersAction } from './get-oauth2-providers.action'; +import { getUserOrgAction } from './get-user-org.action'; +import { getPrivacySettingsAction } from './get-privacy-settings.action'; +import { updatePrivacySettingsAction } from './update-privacy-settings.action'; +import { getUserSessionAction } from './get-user-session.action'; + +export const USER_ACTIONS = [ + getUserAction, + updateUserAction, + deleteUserAction, + getExtendedUserAction, + getOAuth2ProvidersAction, + getUserOrgAction, + getPrivacySettingsAction, + updatePrivacySettingsAction, + getUserSessionAction, +]; diff --git a/packages/pieces/community/zoo/src/lib/actions/users/update-privacy-settings.action.ts b/packages/pieces/community/zoo/src/lib/actions/users/update-privacy-settings.action.ts new file mode 100644 index 0000000..6cb634c --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/users/update-privacy-settings.action.ts @@ -0,0 +1,29 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const updatePrivacySettingsAction = createAction({ + name: 'update_privacy_settings', + displayName: 'Update Privacy Settings', + description: 'Update your user privacy settings', + auth: zooAuth, + // category: 'Users', + props: { + settings: Property.Object({ + displayName: 'Privacy Settings', + required: true, + description: 'The new privacy settings to apply', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: 'https://api.zoo.dev/user/privacy', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: propsValue.settings, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/src/lib/actions/users/update-user.action.ts b/packages/pieces/community/zoo/src/lib/actions/users/update-user.action.ts new file mode 100644 index 0000000..2ef5109 --- /dev/null +++ b/packages/pieces/community/zoo/src/lib/actions/users/update-user.action.ts @@ -0,0 +1,37 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { zooAuth } from '../../../index' +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const updateUserAction = createAction({ + name: 'update_user', + displayName: 'Update User', + description: 'Update your user information', + auth: zooAuth, + // category: 'Users', + props: { + name: Property.ShortText({ + displayName: 'Name', + required: false, + description: 'Your new display name', + }), + email: Property.ShortText({ + displayName: 'Email', + required: false, + description: 'Your new email address', + }), + }, + async run({ auth, propsValue }) { + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: 'https://api.zoo.dev/user', + headers: { + Authorization: `Bearer ${auth}`, + }, + body: { + ...(propsValue.name && { name: propsValue.name }), + ...(propsValue.email && { email: propsValue.email }), + }, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/zoo/tsconfig.json b/packages/pieces/community/zoo/tsconfig.json new file mode 100644 index 0000000..29c9dd1 --- /dev/null +++ b/packages/pieces/community/zoo/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/zoo/tsconfig.lib.json b/packages/pieces/community/zoo/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/zoo/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/zoom/.babelrc b/packages/pieces/community/zoom/.babelrc new file mode 100644 index 0000000..fc2ad0f --- /dev/null +++ b/packages/pieces/community/zoom/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/pieces/community/zoom/.eslintrc.json b/packages/pieces/community/zoom/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/packages/pieces/community/zoom/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/zoom/README.md b/packages/pieces/community/zoom/README.md new file mode 100644 index 0000000..634d25d --- /dev/null +++ b/packages/pieces/community/zoom/README.md @@ -0,0 +1,7 @@ +# pieces-zoom + +This library was generated with [Nx](https://nx.dev). + +## Running lint + +Run `nx lint pieces-zoom` to execute the lint via [ESLint](https://eslint.org/). diff --git a/packages/pieces/community/zoom/package.json b/packages/pieces/community/zoom/package.json new file mode 100644 index 0000000..5bd2294 --- /dev/null +++ b/packages/pieces/community/zoom/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-zoom", + "version": "0.4.0" +} \ No newline at end of file diff --git a/packages/pieces/community/zoom/project.json b/packages/pieces/community/zoom/project.json new file mode 100644 index 0000000..ec84727 --- /dev/null +++ b/packages/pieces/community/zoom/project.json @@ -0,0 +1,37 @@ +{ + "name": "pieces-zoom", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/zoom/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/zoom", + "tsConfig": "packages/pieces/community/zoom/tsconfig.lib.json", + "packageJson": "packages/pieces/community/zoom/package.json", + "main": "packages/pieces/community/zoom/src/index.ts", + "assets": [ + "packages/pieces/community/zoom/*.md", + { + "input": "packages/pieces/community/zoom/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/zoom/src/index.ts b/packages/pieces/community/zoom/src/index.ts new file mode 100644 index 0000000..9ee1d44 --- /dev/null +++ b/packages/pieces/community/zoom/src/index.ts @@ -0,0 +1,49 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { + OAuth2PropertyValue, + PieceAuth, + createPiece, +} from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { zoomCreateMeeting } from './lib/actions/create-meeting'; +import { zoomCreateMeetingRegistrant } from './lib/actions/create-meeting-registrant'; + +export const zoomAuth = PieceAuth.OAuth2({ + description: ` + 1. Go to [marketplace.zoom.us](https://marketplace.zoom.us/) and log in to your account. + 2. In the upper-right corner, click **Develop** then **Build App**. + 3. Select **General App**. + 4. Copy the Client ID and Client Secret.Add Redirect URL and press continue. + 5. Go to **Scopes** from left side bar and add **meeting:write:meeting** and **meeting:write:registrant** as scopes.`, + authUrl: 'https://zoom.us/oauth/authorize', + tokenUrl: 'https://zoom.us/oauth/token', + required: true, + // scope: ['meeting:write:admin', 'meeting:write'], + scope: [], +}); + +export const zoom = createPiece({ + displayName: 'Zoom', + description: 'Video conferencing, web conferencing, webinars, screen sharing', + + minimumSupportedRelease: '0.30.0', + logoUrl: 'https://cdn.activepieces.com/pieces/zoom.png', + categories: [PieceCategory.COMMUNICATION], + actions: [ + zoomCreateMeeting, + zoomCreateMeetingRegistrant, + createCustomApiCallAction({ + baseUrl: () => 'https://api.zoom.us/v2', + auth: zoomAuth, + authMapping: async (auth) => { + const typedAuth = auth as OAuth2PropertyValue; + return { + Authorization: `Bearer ${typedAuth.access_token}`, + }; + }, + }), + ], + auth: zoomAuth, + authors: ['kanarelo', 'kishanprmr', 'MoShizzle', 'khaledmashaly', 'abuaboud'], + triggers: [], +}); diff --git a/packages/pieces/community/zoom/src/lib/actions/create-meeting-registrant.ts b/packages/pieces/community/zoom/src/lib/actions/create-meeting-registrant.ts new file mode 100644 index 0000000..e345f87 --- /dev/null +++ b/packages/pieces/community/zoom/src/lib/actions/create-meeting-registrant.ts @@ -0,0 +1,67 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + AuthenticationType, + httpClient, +} from '@activepieces/pieces-common'; +import { RegistrationResponse } from '../common/models'; +import { getRegistarantProps } from '../common/props'; +import { zoomAuth } from '../..'; + +export const zoomCreateMeetingRegistrant = createAction({ + auth: zoomAuth, + name: 'zoom_create_meeting_registrant', + displayName: 'Create Zoom Meeting Registrant', + description: "Create and submit a user's registration to a meeting.", + props: getRegistarantProps(), + async run(context) { + const body: Record = { + first_name: context.propsValue.first_name, + last_name: context.propsValue.last_name, + email: context.propsValue.email, + address: context.propsValue.address, + city: context.propsValue.city, + state: context.propsValue.state, + zip: context.propsValue.zip, + country: context.propsValue.country, + phone: context.propsValue.phone, + comments: context.propsValue.comments, + industry: context.propsValue.industry, + job_title: context.propsValue.job_title, + no_of_employees: context.propsValue.no_of_employees, + org: context.propsValue.org, + purchasing_time_frame: context.propsValue.purchasing_time_frame, + role_in_purchase_process: context.propsValue.role_in_purchase_process, + }; + + if ( + context.propsValue.custom_questions && + Object.keys(context.propsValue.custom_questions).length > 0 + ) { + body.custom_questions = Object.entries( + context.propsValue.custom_questions + ).map(([key, value]) => ({ title: key, value: value })); + } + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://api.zoom.us/v2/meetings/${context.propsValue.meeting_id}/registrants`, + body, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + queryParams: {}, + }; + + const result = await httpClient.sendRequest(request); + console.debug('Meeting registration response', result); + + if (result.status === 201) { + return result.body; + } else { + return result; + } + }, +}); diff --git a/packages/pieces/community/zoom/src/lib/actions/create-meeting.ts b/packages/pieces/community/zoom/src/lib/actions/create-meeting.ts new file mode 100644 index 0000000..c1b2140 --- /dev/null +++ b/packages/pieces/community/zoom/src/lib/actions/create-meeting.ts @@ -0,0 +1,155 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + HttpRequest, + HttpMethod, + AuthenticationType, + httpClient, +} from '@activepieces/pieces-common'; +import { MeetingMessageBody, MeetingResponseBody } from '../common/models'; +import { zoomAuth } from '../..'; + +const defaults = { + agenda: 'My Meeting', + default_password: false, + duration: 30, + pre_schedule: false, + + settings: { + allow_multiple_devices: true, + approval_type: 2, + audio: 'telephony', + + calendar_type: 1, + close_registration: false, + + email_notification: true, + host_video: true, + join_before_host: false, + meeting_authentication: true, + mute_upon_entry: false, + participant_video: false, + private_meeting: false, + registrants_confirmation_email: true, + registrants_email_notification: true, + registration_type: 1, + show_share_button: true, + host_save_video_order: true, + }, + + timezone: 'UTC', + type: 2, +}; + +const action = () => { + return createAction({ + auth: zoomAuth, + name: 'zoom_create_meeting', // Must be a unique across the piece, this shouldn't be changed. + displayName: 'Create Zoom Meeting', + description: 'Create a new Zoom Meeting', + props: { + topic: Property.ShortText({ + displayName: "Meeting's topic", + description: "The meeting's topic", + required: true, + }), + start_time: Property.ShortText({ + displayName: 'Start Time', + description: 'Meeting start date-time', + required: false, + }), + duration: Property.Number({ + displayName: 'Duration (in Minutes)', + description: 'Duration of the meeting', + required: false, + }), + auto_recording: Property.StaticDropdown({ + displayName: 'Auto Recording', + required: false, + options: { + disabled: false, + options: [ + { label: 'Local', value: 'local' }, + { label: 'Cloud', value: 'cloud' }, + { label: 'None', value: 'none' }, + ], + }, + }), + audio: Property.StaticDropdown({ + displayName: 'Audio', + required: false, + options: { + disabled: false, + options: [ + { label: 'Both telephony and VoIP', value: 'both' }, + { label: 'Telephony only', value: 'telephony' }, + { label: 'VoIP only', value: 'voip' }, + { label: 'Third party audio conference', value: 'thirdParty' }, + ], + }, + }), + agenda: Property.LongText({ + displayName: 'Agenda', + description: "The meeting's agenda", + required: false, + }), + password: Property.ShortText({ + displayName: 'Password', + description: + 'The password required to join the meeting. By default, a password can only have a maximum length of 10 characters and only contain alphanumeric characters and the @, -, _, and * characters.', + required: false, + }), + pre_schedule: Property.Checkbox({ + displayName: 'Pre Schedule', + description: + 'Whether the prescheduled meeting was created via the GSuite app.', + required: false, + }), + schedule_for: Property.ShortText({ + displayName: 'Schedule for', + description: + 'The email address or user ID of the user to schedule a meeting for.', + required: false, + }), + join_url: Property.LongText({ + displayName: 'Join URL', + description: 'URL for participants to join the meeting.', + required: false, + }), + }, + async run(context) { + const body: MeetingMessageBody = { + ...defaults, + ...context.propsValue, + }; + + if (context.propsValue.auto_recording) { + body.settings.auto_recording = context.propsValue.auto_recording; + } + + if (context.propsValue.audio) { + body.settings.audio = context.propsValue.audio; + } + delete body['auth']; + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://api.zoom.us/v2/users/me/meetings`, + body: body, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + queryParams: {}, + }; + + const result = await httpClient.sendRequest(request); + + if (result.status === 201) { + return result.body; + } else { + return result; + } + }, + }); +}; + +export const zoomCreateMeeting = action(); diff --git a/packages/pieces/community/zoom/src/lib/common/models.ts b/packages/pieces/community/zoom/src/lib/common/models.ts new file mode 100644 index 0000000..ceb153c --- /dev/null +++ b/packages/pieces/community/zoom/src/lib/common/models.ts @@ -0,0 +1,252 @@ +import { HttpMessageBody } from '@activepieces/pieces-common'; + +export interface MeetingRegistrant { + first_name: string; + last_name?: string; + email: string; + address?: string; + city?: string; + state?: string; + zip?: string; + country?: string; + phone?: string; + comments?: string; + custom_questions?: { + title: string; + value: string; + }[]; + industry?: string; + job_title?: string; + no_of_employees?: string; + org?: string; + purchasing_time_frame?: string; + role_in_purchase_process?: string; + language?: string; + auto_approve?: boolean; +} + +export interface RegistrationResponse extends HttpMessageBody { + id: number; + join_url: string; + registrant_id: string; + start_time: string; + topic: string; + occurrences: { + duration: number; + occurrence_id: string; + start_time: string; + status: string; + }[]; + participant_pin_code: number; +} + +export interface MeetingResponseBody extends HttpMessageBody { + id?: number; + assistant_id?: string; + host_email?: string; + registration_url?: string; + agenda?: string; + created_at?: string; + duration?: number; + join_url?: string; + occurrences?: { + duration?: number; + occurrence_id?: string; + start_time?: string; + status?: string; + }[]; + password?: string; + pmi?: string; + pre_schedule?: boolean; + recurrence?: { + end_date_time?: string; + end_times?: number; + monthly_day?: number; + monthly_week?: number; + monthly_week_day?: number; + repeat_interval?: number; + type: number; + weekly_days?: string; + }; + settings: { + allow_multiple_devices: boolean; + alternative_hosts: number; + alternative_hosts_email_notification: boolean; + alternative_host_update_polls: boolean; + approval_type: number; + approved_or_denied_countries_or_regions: { + approved_list: string[]; + denied_list: string[]; + enable: boolean; + method: string; + }; + audio: string; + authentication_domains: string; + authentication_exception: { + email: string; + name: string; + join_url: string; + }[]; + authentication_name: string; + authentication_option: string; + auto_recording: string; + breakout_room: { + enable: boolean; + rooms: { + name: string; + participants: string[]; + }[]; + }; + calendar_type: number; + close_registration: boolean; + contact_email: string; + contact_name: string; + custom_keys: { + key: string; + value: string; + }[]; + email_notification: boolean; + encryption_type: string; + focus_mode: boolean; + global_dial_in_countries: string[]; + global_dial_in_numbers: { + city: string; + country: string; + country_name: string; + number: string; + type: string; + }[]; + host_video: boolean; + jbh_time: number; + join_before_host: boolean; + language_interpretation: { + enable: boolean; + interpreters: { + email: string; + languages: string; + }[]; + }; + meeting_authentication: boolean; + mute_upon_entry: boolean; + participant_video: boolean; + private_meeting: boolean; + registrants_confirmation_email: boolean; + registrants_email_notification: boolean; + registration_type: number; + show_share_button: boolean; + use_pmi: boolean; + waiting_room: boolean; + watermark: boolean; + host_save_video_order: boolean; + }; + + start_time: string; + start_url: string; + timezone: string; + topic: string; + type: number; + tracking_fields: { + field: string; + value: string; + visible: boolean; + }[]; +} + +export interface MeetingMessageBody extends HttpMessageBody { + agenda?: string; + password?: string; + duration?: number; + pre_schedule?: boolean; + recurrence?: { + end_date_time: string; + end_times: number; + monthly_day: number; + monthly_week: number; + monthly_week_day: number; + repeat_interval: number; + type: number; + weekly_days: string; + }; + settings: { + allow_multiple_devices?: boolean; + alternative_hosts?: number; + alternative_hosts_email_notification?: boolean; + alternative_host_update_polls?: boolean; + approval_type?: number; + approved_or_denied_countries_or_regions?: { + approved_list?: string[]; + denied_list?: string[]; + enable?: boolean; + method?: string; + }; + audio?: string; + authentication_domains?: string; + authentication_exception?: { + email?: string; + name?: string; + join_url?: string; + }[]; + authentication_name?: string; + authentication_option?: string; + auto_recording?: string; + breakout_room?: { + enable?: boolean; + rooms?: { + name: string; + participants: string[]; + }[]; + }; + calendar_type?: number; + close_registration?: boolean; + contact_email?: string; + contact_name?: string; + custom_keys?: { + key?: string; + value?: string; + }[]; + email_notification?: boolean; + encryption_type?: string; + focus_mode?: boolean; + global_dial_in_countries?: string[]; + global_dial_in_numbers?: { + city?: string; + country?: string; + country_name?: string; + number?: string; + type?: string; + }[]; + host_video?: boolean; + jbh_time?: number; + join_before_host?: boolean; + language_interpretation?: { + enable?: boolean; + interpreters?: { + email?: string; + languages?: string; + }[]; + }; + meeting_authentication?: boolean; + mute_upon_entry?: boolean; + participant_video?: boolean; + private_meeting?: boolean; + registrants_confirmation_email?: boolean; + registrants_email_notification?: boolean; + registration_type?: number; + show_share_button?: boolean; + use_pmi?: boolean; + waiting_room?: boolean; + watermark?: boolean; + host_save_video_order?: boolean; + }; + + start_time?: string; + start_url?: string; + timezone?: string; + topic?: string; + type?: number; + tracking_fields?: { + field: string; + value?: string; + visible?: boolean; + }[]; +} diff --git a/packages/pieces/community/zoom/src/lib/common/props.ts b/packages/pieces/community/zoom/src/lib/common/props.ts new file mode 100644 index 0000000..e30ba38 --- /dev/null +++ b/packages/pieces/community/zoom/src/lib/common/props.ts @@ -0,0 +1,126 @@ +import { Property } from '@activepieces/pieces-framework'; + +export const getRegistarantProps = () => ({ + meeting_id: Property.ShortText({ + displayName: 'Meeting ID', + description: 'The meeting ID.', + required: true, + }), + first_name: Property.ShortText({ + displayName: 'First name', + description: "The registrant's first name.", + required: true, + }), + last_name: Property.ShortText({ + displayName: 'Last name', + description: "The registrant's last name.", + required: false, + }), + email: Property.ShortText({ + displayName: 'Email', + description: "The registrant's email address.", + required: true, + }), + address: Property.ShortText({ + displayName: 'Address', + description: "The registrant's address", + required: false, + }), + city: Property.ShortText({ + displayName: 'City', + description: "The registrant's city", + required: false, + }), + state: Property.ShortText({ + displayName: 'State', + description: "The registrant's state or province.", + required: false, + }), + zip: Property.ShortText({ + displayName: 'Zip', + description: "The registrant's zip or postal code.", + required: false, + }), + country: Property.ShortText({ + displayName: 'Country', + description: "The registrant's two-letter country code.", + required: false, + }), + phone: Property.ShortText({ + displayName: 'Phone', + description: "The registrant's phone number.", + required: false, + }), + comments: Property.LongText({ + displayName: 'Comments', + description: "The registrant's questions and comments.", + required: false, + }), + custom_questions: Property.Object({ + displayName: 'Custom questions', + description: '', + required: false, + }), + industry: Property.ShortText({ + displayName: 'Industry', + description: "The registrant's industry.", + required: false, + }), + job_title: Property.ShortText({ + displayName: 'Job title', + description: "The registrant's job title.", + required: false, + }), + no_of_employees: Property.StaticDropdown({ + displayName: 'No of employees', + description: "The registrant's number of employees.", + required: false, + options: { + disabled: false, + options: [ + { label: '1-20', value: '1-20' }, + { label: '21-50', value: '21-50' }, + { label: '51-100', value: '51-100' }, + { label: '101-500', value: '101-500' }, + { label: '500-1,000', value: '500-1,000' }, + { label: '1,001-5,000', value: '1,001-5,000' }, + { label: '5,001-10,000', value: '5,001-10,000' }, + { label: 'More than 10,000', value: 'More than 10,000' }, + ], + }, + }), + org: Property.ShortText({ + displayName: 'Organization', + description: "The registrant's organization.", + required: false, + }), + purchasing_time_frame: Property.StaticDropdown({ + displayName: 'Purchasing time frame', + description: "The registrant's purchasing time frame.", + required: false, + options: { + disabled: false, + options: [ + { label: 'Within a month', value: 'Within a month' }, + { label: '1-3 months', value: '1-3 months' }, + { label: '4-6 months', value: '4-6 months' }, + { label: 'More than 6 months', value: 'More than 6 months' }, + { label: 'No timeframe', value: 'No timeframe' }, + ], + }, + }), + role_in_purchase_process: Property.StaticDropdown({ + displayName: 'Role in purchase process', + description: "The registrant's role in the purchase process.", + required: false, + options: { + disabled: false, + options: [ + { label: 'Decision Maker', value: 'Decision Maker' }, + { label: 'Evaluator/Recommender', value: 'Evaluator/Recommender' }, + { label: 'Influencer', value: 'Influencer' }, + { label: 'Not involved', value: 'Not involved' }, + ], + }, + }), +}); diff --git a/packages/pieces/community/zoom/tsconfig.json b/packages/pieces/community/zoom/tsconfig.json new file mode 100644 index 0000000..7bef1d1 --- /dev/null +++ b/packages/pieces/community/zoom/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/pieces/community/zoom/tsconfig.lib.json b/packages/pieces/community/zoom/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/zoom/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/zuora/.eslintrc.json b/packages/pieces/community/zuora/.eslintrc.json new file mode 100644 index 0000000..eac708a --- /dev/null +++ b/packages/pieces/community/zuora/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/zuora/README.md b/packages/pieces/community/zuora/README.md new file mode 100644 index 0000000..870395f --- /dev/null +++ b/packages/pieces/community/zuora/README.md @@ -0,0 +1 @@ +# pieces-zuora diff --git a/packages/pieces/community/zuora/package.json b/packages/pieces/community/zuora/package.json new file mode 100644 index 0000000..bb00717 --- /dev/null +++ b/packages/pieces/community/zuora/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-zuora", + "version": "0.0.6" +} diff --git a/packages/pieces/community/zuora/project.json b/packages/pieces/community/zuora/project.json new file mode 100644 index 0000000..e98f1d1 --- /dev/null +++ b/packages/pieces/community/zuora/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-zuora", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/zuora/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/zuora", + "tsConfig": "packages/pieces/community/zuora/tsconfig.lib.json", + "packageJson": "packages/pieces/community/zuora/package.json", + "main": "packages/pieces/community/zuora/src/index.ts", + "assets": [ + "packages/pieces/community/zuora/*.md", + { + "input": "packages/pieces/community/zuora/src/i18n", + "output": "./src/i18n", + "glob": "**/!(i18n.json)" + } + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-zuora {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + } + } +} \ No newline at end of file diff --git a/packages/pieces/community/zuora/src/index.ts b/packages/pieces/community/zuora/src/index.ts new file mode 100644 index 0000000..42b728a --- /dev/null +++ b/packages/pieces/community/zuora/src/index.ts @@ -0,0 +1,90 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { createInvoiceAction } from './lib/actions/create-invoice.action'; +import { PieceCategory } from '@activepieces/shared'; +import { findProductRatePlanAction } from './lib/actions/find-product-rate-plans.action'; +import { findAccountAction } from './lib/actions/find-account.action'; +import { findProductAction } from './lib/actions/find-product.action'; + +export const zuoraAuth = PieceAuth.CustomAuth({ + description: ` + - **Establish a Dedicated API User** : Zuora recommends creating a distinct API user specifically for OAuth authentication with write permissions. Refer to the [API User Creation Guide](https://knowledgecenter.zuora.com/Zuora_Central_Platform/Tenant_Management/A_Administrator_Settings/Manage_Users/Create_an_API_User) for detailed instructions on setting up this user. + - **Create OAuth Client** : After setting up the API user, it's essential to create an OAuth client tailored for this user.Detailed instructions can be found in the [OAuth Client Creation Guide](https://knowledgecenter.zuora.com/Zuora_Central_Platform/Tenant_Management/A_Administrator_Settings/Manage_Users). + - **Refer to Base API URL** : Utilize the [Base API URL](https://developer.zuora.com/v1-api-reference/introduction/#section/Introduction/Access-to-the-API) provided by Zuora for your environment. This URL serves as the entry point for accessing the Zuora API.`, + required: true, + props: { + clientId: Property.ShortText({ + displayName: 'Client ID', + required: true, + }), + clientSecret: Property.ShortText({ + displayName: 'Client Secret', + required: true, + }), + environment: Property.StaticDropdown({ + displayName: 'Environment', + required: true, + options: { + disabled: false, + options: [ + { + label: 'US Cloud 1 Production', + value: 'https://rest.na.zuora.com', + }, + { + label: 'US Cloud 1 API Sandbox', + value: 'https://rest.sandbox.na.zuora.com', + }, + { + label: 'US Cloud 2 Production', + value: 'https://rest.zuora.com', + }, + { + label: 'US Cloud 2 API Sandbox', + value: 'https://rest.apisandbox.zuora.com', + }, + { + label: 'US Central Sandbox', + value: 'https://rest.test.zuora.com', + }, + { + label: 'EU Production', + value: 'https://rest.eu.zuora.com', + }, + { + label: 'EU API Sandbox', + value: 'https://rest.sandbox.eu.zuora.com', + }, + { + label: 'EU Developer & Central Sandbox', + value: 'https://rest.test.eu.zuora.com', + }, + ], + }, + }), + }, +}); + +export const zuora = createPiece({ + displayName: 'Zuora', + auth: zuoraAuth, + minimumSupportedRelease: '0.27.1', + description: + 'Cloud-based subscription management platform that enables businesses to launch and monetize subscription services.', + logoUrl: 'https://cdn.activepieces.com/pieces/zuora.png', + categories: [ + PieceCategory.SALES_AND_CRM, + PieceCategory.PAYMENT_PROCESSING, + ], + authors: ['kishanprmr'], + actions: [ + createInvoiceAction, + findAccountAction, + findProductRatePlanAction, + findProductAction, + ], + triggers: [], +}); diff --git a/packages/pieces/community/zuora/src/lib/actions/create-invoice.action.ts b/packages/pieces/community/zuora/src/lib/actions/create-invoice.action.ts new file mode 100644 index 0000000..316d791 --- /dev/null +++ b/packages/pieces/community/zuora/src/lib/actions/create-invoice.action.ts @@ -0,0 +1,132 @@ +import { zuoraAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getAccessToken } from '../common'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; + +export const createInvoiceAction = createAction({ + auth: zuoraAuth, + name: 'create-invoice', + displayName: 'Create Invoice', + description: 'Create a standalone invoice.', + props: { + accountNumber: Property.ShortText({ + displayName: 'Customer Account Number', + description: + 'The number of the customer account associated with the invoice.', + required: true, + }), + autoPay: Property.Checkbox({ + displayName: 'Auto Pay?', + description: + 'Whether invoices are automatically picked up for processing in the corresponding payment run.', + required: false, + }), + comments: Property.LongText({ + displayName: 'Comments', + required: false, + }), + dueDate: Property.DateTime({ + displayName: 'Due Date', + description: 'Provide YYYY-MM-DD format.', + required: true, + }), + invoiceDate: Property.DateTime({ + displayName: 'Invoice Date', + required: true, + description: 'Provide YYYY-MM-DD format.', + }), + invoiceItems: Property.Array({ + displayName: 'Invoice Items', + required: false, + properties: { + productRatePlanChargeId: Property.ShortText({ + displayName: 'Product Rate Plan Charge ID', + description: + 'The ID of the product rate plan charge that the invoice item is created from.You can use `Find Product Rate Plan` action to search associate rate plan charge.', + required: true, + }), + amount: Property.Number({ + displayName: 'Amount', + required: true, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + purchaseOrderNumber: Property.ShortText({ + displayName: 'Purchase Order Number', + required: false, + }), + quantity: Property.ShortText({ + displayName: 'Quantity', + required: false, + }), + serviceStartDate: Property.ShortText({ + displayName: 'Service Start Date', + description: 'Provide YYYY-MM-DD format.', + required: true, + }), + serviceEndDate: Property.ShortText({ + displayName: 'Service End Date', + description: 'Provide YYYY-MM-DD format.', + required: false, + }), + }, + }), + status: Property.StaticDropdown({ + displayName: 'Status', + required: false, + defaultValue: 'Draft', + options: { + disabled: false, + options: [ + { label: 'Draft', value: 'Draft' }, + { label: 'Posted', value: 'Posted' }, + ], + }, + }), + }, + async run(context) { + const { accountNumber, autoPay, comments, dueDate, invoiceDate, status } = + context.propsValue; + + const invoiceItems = context.propsValue.invoiceItems as InvoiceItemInput[]; + + const token = await getAccessToken(context.auth); + + const body = { + accountNumber, + autoPay, + comments, + dueDate, + invoiceDate, + status, + invoiceItems, + }; + + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${context.auth.environment}/v1/invoices`, + authentication: { type: AuthenticationType.BEARER_TOKEN, token }, + body, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, +}); + +type InvoiceItemInput = { + productRatePlanChargeId: string; + amount: string; + description: string; + purchaseOrderNumber: string; + quantity: string; + serviceStartDate: string; + serviceEndDate: string; +}; diff --git a/packages/pieces/community/zuora/src/lib/actions/find-account.action.ts b/packages/pieces/community/zuora/src/lib/actions/find-account.action.ts new file mode 100644 index 0000000..12fa8b9 --- /dev/null +++ b/packages/pieces/community/zuora/src/lib/actions/find-account.action.ts @@ -0,0 +1,38 @@ +import { zuoraAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getAccessToken } from '../common'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; + +export const findAccountAction = createAction({ + auth: zuoraAuth, + name: 'find-account', + displayName: 'Find Customer Account', + description: 'Retrieves account based on name.', + props: { + name: Property.ShortText({ + displayName: 'Account Name', + required: true, + }), + }, + async run(context) { + const name = context.propsValue.name; + const token = await getAccessToken(context.auth); + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${context.auth.environment}/object-query/accounts`, + authentication: { type: AuthenticationType.BEARER_TOKEN, token }, + queryParams: { + 'filter[]': `name.EQ:${name}`, + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, +}); diff --git a/packages/pieces/community/zuora/src/lib/actions/find-product-rate-plans.action.ts b/packages/pieces/community/zuora/src/lib/actions/find-product-rate-plans.action.ts new file mode 100644 index 0000000..34b4d70 --- /dev/null +++ b/packages/pieces/community/zuora/src/lib/actions/find-product-rate-plans.action.ts @@ -0,0 +1,44 @@ +import { zuoraAuth } from '../..'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getAccessToken } from '../common'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; + +export const findProductRatePlanAction = createAction({ + auth: zuoraAuth, + name: 'find-product-rate-plan', + displayName: 'Find Product Rate Plan', + description: 'Retrieves product rate plan with charges.', + props: { + name: Property.ShortText({ + displayName: 'Product Rate Plan Name', + description: 'i.e. StealthCo Premium', + required: true, + }), + productid: Property.ShortText({ + displayName: 'Product ID', + required: true, + }), + }, + async run(context) { + const name = context.propsValue.name; + const productid = context.propsValue.productid; + const token = await getAccessToken(context.auth); + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${context.auth.environment}/object-query/product-rate-plans?filter[]=name.EQ:${name}&filter[]=productid.EQ:${productid}`, + authentication: { type: AuthenticationType.BEARER_TOKEN, token }, + queryParams: { + 'expand[]': 'productrateplancharges', + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, +}); diff --git a/packages/pieces/community/zuora/src/lib/actions/find-product.action.ts b/packages/pieces/community/zuora/src/lib/actions/find-product.action.ts new file mode 100644 index 0000000..224d3cc --- /dev/null +++ b/packages/pieces/community/zuora/src/lib/actions/find-product.action.ts @@ -0,0 +1,38 @@ +import { zuoraAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getAccessToken } from '../common'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; + +export const findProductAction = createAction({ + auth: zuoraAuth, + name: 'find-product', + displayName: 'Find Product', + description: 'Retrieves product based on sku.', + props: { + sku: Property.ShortText({ + displayName: 'Product SKU', + required: true, + }), + }, + async run(context) { + const sku = context.propsValue.sku; + const token = await getAccessToken(context.auth); + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${context.auth.environment}/object-query/products`, + authentication: { type: AuthenticationType.BEARER_TOKEN, token }, + queryParams: { + 'filter[]': `sku.EQ:${sku}`, + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body; + }, +}); diff --git a/packages/pieces/community/zuora/src/lib/common/index.ts b/packages/pieces/community/zuora/src/lib/common/index.ts new file mode 100644 index 0000000..f357bd1 --- /dev/null +++ b/packages/pieces/community/zuora/src/lib/common/index.ts @@ -0,0 +1,102 @@ +import { zuoraAuth } from '../../'; +import { + DropdownOption, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, + QueryParams, +} from '@activepieces/pieces-common'; + +export async function queryAccounts( + auth: PiecePropValueSchema +) { + const token = await getAccessToken(auth); + const result: Record[] = []; + let cursor; + do { + const qs: QueryParams = { + pageSize: '50', + 'fields[]': 'id,name', + 'sort[]': 'updated_time.desc', + }; + if (cursor) { + qs['cursor'] = cursor; + } + + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${auth.environment}/v2/accounts`, + queryParams: qs, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token, + }, + }; + + const response = await httpClient.sendRequest(request); + result.push(...response.body['data']); + cursor = response.body['nextPage']; + } while (cursor); + + return result; +} + +export async function getAccessToken( + auth: PiecePropValueSchema +): Promise { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${auth.environment}/oauth/token`, + body: new URLSearchParams({ + client_id: auth.clientId, + client_secret: auth.clientSecret, + grant_type: 'client_credentials', + }), + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }; + + const response = await httpClient.sendRequest(request); + return response.body['access_token']; +} + +export const zuoraCommonProps = { + account_id: (displayName: string, description: string, required: boolean) => + Property.Dropdown({ + displayName, + description, + required, + refreshers: [], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first', + }; + } + const authValue = auth as PiecePropValueSchema; + const accounts = await queryAccounts(authValue); + + const options: DropdownOption[] = []; + + for (const account of accounts) { + options.push({ + label: account['name'] ?? account['id'], + value: account['id'], + }); + } + + return { + disabled: false, + options, + }; + }, + }), +}; diff --git a/packages/pieces/community/zuora/tsconfig.json b/packages/pieces/community/zuora/tsconfig.json new file mode 100644 index 0000000..059cd81 --- /dev/null +++ b/packages/pieces/community/zuora/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/zuora/tsconfig.lib.json b/packages/pieces/community/zuora/tsconfig.lib.json new file mode 100644 index 0000000..28369ef --- /dev/null +++ b/packages/pieces/community/zuora/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/custom/README.md b/packages/pieces/custom/README.md new file mode 100644 index 0000000..e69de29 diff --git a/packages/react-ui/.eslintrc.json b/packages/react-ui/.eslintrc.json new file mode 100644 index 0000000..a67a2a0 --- /dev/null +++ b/packages/react-ui/.eslintrc.json @@ -0,0 +1,179 @@ +{ + "extends": [ + "plugin:@nx/react", + "../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "**/*.ts", + "**/*.tsx" + ], + "parser": "@typescript-eslint/parser", + "settings": { + "react": { + "version": "detect" + }, + "import/resolver": { + "typescript": {}, + "alias": { + "map": [ + [ + "@", + "./src" + ] + ], + "extensions": [ + ".ts", + ".tsx", + ".js", + ".jsx", + ".json" + ] + } + } + }, + "env": { + "browser": true, + "node": true, + "es6": true + }, + "extends": [ + "eslint:recommended", + "plugin:import/errors", + "plugin:import/warnings", + "plugin:import/typescript", + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended", + //"plugin:jsx-a11y/recommended", + "plugin:prettier/recommended", + "plugin:testing-library/react", + "plugin:jest-dom/recommended", + "plugin:vitest/legacy-recommended" + ], + "rules": { + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", + "import/no-restricted-paths": [ + "error", + { + "zones": [ + // Previous restrictions... + // enforce unidirectional codebase: + // e.g. src/app can import from src/features but not the other way around + { + "target": "./src/features", + "from": "./src/app" + }, + { + "target": [ + "./src/components", + "./src/hooks", + "./src/lib", + "./src/types", + "./src/utils" + ], + "from": [ + "./src/features", + "./src/app" + ] + } + ] + } + ], + "import/no-cycle": "off", + "linebreak-style": [ + "error", + "unix" + ], + "react/prop-types": "error", + "import/order": [ + "error", + { + "groups": [ + "builtin", + "external", + "internal", + "parent", + "sibling", + "index", + "object" + ], + "newlines-between": "always", + "alphabetize": { + "order": "asc", + "caseInsensitive": true + } + } + ], + "import/default": "off", + "import/no-named-as-default-member": "off", + "import/no-named-as-default": "off", + "react/react-in-jsx-scope": "off", + "jsx-a11y/anchor-is-valid": "off", + "@typescript-eslint/no-unused-vars": [ + "error" + ], + "@typescript-eslint/explicit-function-return-type": [ + "off" + ], + "@typescript-eslint/explicit-module-boundary-types": [ + "off" + ], + "@typescript-eslint/no-empty-function": [ + "off" + ], + "@typescript-eslint/no-explicit-any": [ + "off" + ], + "@typescript-eslint/indent": "off", + "prettier/prettier": [ + "error", + { + "singleQuote": true, + "trailingComma": "all", + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "jsxBracketSameLine": false + } + ] + } + }, + { + "files": [ + "./src/components/**/*.{ts,tsx}" + ], + "rules": { + "react/prop-types": "off" + } + }, + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/react-ui/.prettierignore b/packages/react-ui/.prettierignore new file mode 100644 index 0000000..5acb4c9 --- /dev/null +++ b/packages/react-ui/.prettierignore @@ -0,0 +1 @@ +*.hbs \ No newline at end of file diff --git a/packages/react-ui/i18next-parser.config.js b/packages/react-ui/i18next-parser.config.js new file mode 100644 index 0000000..a47b05c --- /dev/null +++ b/packages/react-ui/i18next-parser.config.js @@ -0,0 +1,16 @@ +module.exports = { + locales: ['en', 'fr', 'it', 'de', 'nl', 'ja', 'es', 'id', 'vi', 'zh', 'pt', 'hu', 'uk', 'bg'], // Your supported languages + output: 'packages/react-ui/public/locales/$LOCALE/$NAMESPACE.json', // Where to output the JSON files + input: ['src/**/*.{js,jsx,ts,tsx}'], // Where to find your React files + defaultNamespace: 'translation', // Default namespace if not specified + createOldCatalogs: false, // Don’t maintain the existing structure with old keys + lexers: { + js: ['JavascriptLexer'], + jsx: ['JavascriptLexer'], + ts: ['JavascriptLexer'], + tsx: ['JavascriptLexer'], + }, + keepRemoved: false, + keySeparator: false, // Disable key separator + namespaceSeparator: false, // Disable namespace separator +}; \ No newline at end of file diff --git a/packages/react-ui/index.html b/packages/react-ui/index.html new file mode 100644 index 0000000..e634d3d --- /dev/null +++ b/packages/react-ui/index.html @@ -0,0 +1,21 @@ + + + + + + Activepieces + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/packages/react-ui/jest.config.ts b/packages/react-ui/jest.config.ts new file mode 100644 index 0000000..4341600 --- /dev/null +++ b/packages/react-ui/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'react-ui', + preset: '../../jest.preset.js', + transform: { + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', + '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }], + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/packages/react-ui', +}; diff --git a/packages/react-ui/postcss.config.js b/packages/react-ui/postcss.config.js new file mode 100644 index 0000000..c72626d --- /dev/null +++ b/packages/react-ui/postcss.config.js @@ -0,0 +1,15 @@ +const { join } = require('path'); + +// Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build +// option from your application's configuration (i.e. project.json). +// +// See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries + +module.exports = { + plugins: { + tailwindcss: { + config: join(__dirname, 'tailwind.config.js'), + }, + autoprefixer: {}, + }, +}; diff --git a/packages/react-ui/project.json b/packages/react-ui/project.json new file mode 100644 index 0000000..906860d --- /dev/null +++ b/packages/react-ui/project.json @@ -0,0 +1,22 @@ +{ + "name": "react-ui", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/react-ui/src", + "projectType": "application", + "tags": [], + "targets": { + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "packages/react-ui/jest.config.ts" + } + }, + "vite-typecheck": { + "executor": "nx:run-commands", + "options": { + "command": "tsc --noEmit -p packages/react-ui/tsconfig.app.json" + } + } + } +} diff --git a/packages/react-ui/public/locales/ar/translation.json b/packages/react-ui/public/locales/ar/translation.json new file mode 100644 index 0000000..bacf14e --- /dev/null +++ b/packages/react-ui/public/locales/ar/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "نشر", + "Latest version is published": "تم نشر أحدث نسخة", + "Your flow has incomplete steps": "Your flow has incomplete steps", + "Edit Flow": "تعديل التدفق", + "View Draft": "View Draft", + "Uncategorized": "غير مصنّف", + "Go to folder": "انتقل إلى مجلد", + "Support": "دعم", + "Runs": "التشغيلات", + "Run Logs": "تشغيل السجلات", + "Versions": "Versions", + "Versions History": "Versions History", + "Error generating code": "خطأ في إنشاء كود", + "AI Copilot": "مساعد الذكاء الاصطناعي Copilot", + "i.e Calculate the sum of a list...": "i.e Calculate the sum of a list...", + "Send": "إرسال", + "Generating Code": "Generating Code", + "Hello there! I am here to generate code that helps with your flow": "Hello there! I am here to generate code that helps with your flow", + "Here are examples of what I am best used for: ": "Here are examples of what I am best used for: ", + "Text Processing": "Text Processing", + "Process strings, dates and data": "Process strings, dates and data", + "Data Operations": "Data Operations", + "Change data from one format to another": "Change data from one format to another", + "Calculations": "Calculations", + "Handle math and statistics": "Handle math and statistics", + "API Integration": "API Integration", + "Connect with external services. Best for simple integrations currently.": "Connect with external services. Best for simple integrations currently.", + "What would you like me to help you with?": "What would you like me to help you with?", + "Insert": "إدراج", + "Data Selector": "مُحدِّد البيانات", + "Search": "بحث", + "No matching data": "لا توجد بيانات مطابقة", + "Try adjusting your search": "حاول تعديل بحثك", + "This trigger needs to have data loaded from your account, to use as sample data.": "هذا المشغل يحتاج إلى تحميل البيانات من حسابك، لاستخدامها كعينات تجريبية.", + "This step needs to be tested in order to view its data.": " هذه الخطوة بحاجة إلى الاختبار من أجل عرض بياناتها. ", + "Go to Trigger": "اذهب إلى المشغل", + "Go to Step": "الذهاب إلى الخطوة", + "Select Mode": "Select Mode", + "Move Mode": "Move Mode", + "Reset Zoom": "العودة إلى الوضعية الافتراضية", + "Zoom In": "تكبير", + "Zoom Out": "التصغير", + "Fit to View": "ملائمة العرض", + "Replace": "Replace", + "Copy": "نسخ", + "Duplicate": "استنساخ", + "Paste After Last Step": "Paste After Last Step", + "Paste Inside Loop": "Paste Inside Loop", + "Paste After": "Paste After", + "Paste Inside...": "Paste Inside...", + "New Branch": "New Branch", + "Paste Inside Branch": "Paste Inside Branch", + "Delete": "حذف", + "Duplicate Branch": "Duplicate Branch", + "Delete Branch": "Delete Branch", + "Invalid Move": "حركة غير ممكنة", + "The destination location is a child of the dragged step": "The destination location is a child of the dragged step", + "End": "النهاية", + "Skipped": "Skipped", + "Incomplete settings": "إعدادات غير مكتملة", + "logo": "الشعار", + "Step Icon": "أيقونة الخطوة", + "Branch": "فرع", + "incompleteSteps": "{invalidSteps, plural, =0 {لا خطوات غير كاملة} =1 {اكمال خطوة واحدة} other {خطوات # مكتملة}}", + "Test Flow": "اختبار التدفق", + "Please test the trigger first": "الرجاء اختبار المُشغّل أولاً", + "View Only": "عرض فقط", + "Use as Draft": "استخدام كمسودة", + "Are you sure?": "هل أنت متأكد؟", + "Your current draft version will be overwritten with": "سيتم استبدال النسخة الحالية من المُسَوَّدَة مع", + "version #": "إصدار #", + "Cancel": "إلغاء", + "Confirm": "تأكيد", + "Version": "الإصدار", + "Viewing": "معاينة", + "View": "عرض", + "Version History": "تاريخ النسخ", + "Error, please try again.": "خطأ، يرجى المحاولة مرة أخرى!", + "Continue on Failure": "الاستمرار عند الفشل", + "Enable this option to skip this step and continue the flow normally if it fails.": "تمكين هذا الخِيار لتخطي هذه الخطوة ومواصلة التدفق بشكل طبيعي إذا فشل.", + "Retry on Failure": "إعادة المحاولة عند الفشل", + "Automatically retry up to four attempts when failed.": "إعادة المحاولة تلقائيًا حتى أربع محاولات عند الفشل.", + "Remove": "إزالة", + "Add Item": "إضافة عنصر", + "File Input": "File Input", + "Date Input": "Date Input", + "Dynamic value": "Dynamic value", + "Select an option": "حدد الخِيار", + "Unexpected error, please retry": "Unexpected error, please retry", + "Unexpected error, please refresh the page or contact support": "Unexpected error, please refresh the page or contact support", + "Name can only contain letters, numbers and underscores": "Name can only contain letters, numbers and underscores", + "Ask AI": "اسأل الذكاء الاصطناعي", + "Create Todo Guide": "Create Todo Guide", + "Where would you like the todo to be reviewed?": "Where would you like the todo to be reviewed?", + "Activepieces Todos": "Activepieces Todos", + "Users will manage tasks directly in Activepieces": "Users will manage tasks directly in Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.", + "External Channel (Slack, Teams, Email, ...)": "External Channel (Slack, Teams, Email, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.", + "Preview (Activepieces Todos)": "Preview (Activepieces Todos)", + "Preview (External channel)": "Preview (External channel)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "You can add the channel before the Wait Step, and configure the logic in the Router step", + "Add Steps": "Add Steps", + "All": "All", + "AI": "AI", + "Core": "Core", + "Apps": "Apps", + "Not available as trigger": "Not available as trigger", + "Not available as action": "Not available as action", + "Let our AI assistant help you out": "Let our AI assistant help you out", + "Or": "Or", + "Request Piece": "Request Piece", + "No pieces found": "لا توجد قطع", + "Please select a piece first": "Please select a piece first", + "All Iterations": "كافة التعديلات", + "Duration": "المدة", + "Input": "مُدخل", + "Output": "المخرج", + "There are no logs captured for this run.": "There are no logs captured for this run.", + "Logs are kept for {days} days after execution and then deleted.": "Logs are kept for {days} days after execution and then deleted.", + "Run Details": "تفاصيل التشغيل", + "Iteration": "تكرار", + "Done": "تمّ", + "Took": "استغرق", + "Running": "قيد التشغيل", + "on latest version": "on latest version", + "from failed step": "from failed step", + "Recent Runs": "مرات التشغيل الأخيرة", + "No runs found": "لم يتم العثور على أي تشغيلات", + "Close": "إغلاق", + "OR": "أو", + "And If": "و إذا", + "+ And": "+ و", + "+ Or": "+ أو", + "(Text) Contains": "(النص) يحتوي", + "(Text) Does not contain": "(النص) لا يحتوي على", + "(Text) Exactly matches": "(النص) مطابق تماما", + "(Text) Does not exactly match": "(النص) لا يتطابق مطلقا", + "(Text) Starts with": "(النص) يبدأ بـ", + "(Text) Does not start with": "(النص) لا يبدأ بـ", + "(Text) Ends with": "(النص) ينتهي بـ", + "(Text) Does not end with": "(النص) لا ينتهي بـ", + "(List) Contains": "(List) Contains", + "(List) Does not contain": "(List) Does not contain", + "(Number) Is greater than": "(عدد) أكبر من", + "(Number) Is less than": "(عدد) أقل من", + "(Number) Is equal to": "(عدد) يساوي", + "(Date/time) After": "(Date/time) After", + "(Date/time) Before": "(Date/time) Before", + "(Date/time) Equals": "(Date/time) Equals", + "(Boolean) Is true": "(Boolean) قيمة \"صح\"", + "(Boolean) Is false": "(Boolean) قيمة \"خطأ\"", + "(List) Is empty": "(List) Is empty", + "(List) Is not empty": "(List) Is not empty", + "Exists": "موجود", + "Does not exist": "غير موجود", + "Incomplete condition": "Incomplete condition", + "First value": "القيمة الأولى", + "Second value": "القيمة الثانية", + "Case sensitive": "حساسية حالة الأحرف", + "Execute If": "Execute If", + "The package name is required": "اسم الحُزْمَة مطلوب", + "Success": "نجاح", + "Package added successfully": "تمت إضافة الحُزْمَة بنجاح", + "Could not fetch package version": "تعذر جلب إصدار الحُزْمَة", + "Add NPM Package": "إضافة حُزْمَة NPM", + "Type the name of the npm package you want to add.": "اكتب اسم حُزْمَة npm التي تريد إضافتها.", + "Package Name": "أسم الحُزْمَة", + "The latest version will be fetched and added": "سيتم جلب أحدث إصدار وإضافته", + "Add": "إضافة", + "Code": "الكود", + "Dependencies": "(Dependencies) الاعتماديات", + "Use code": "Use code", + "Add package": "إضافة حُزْمَة", + "Inputs": "المدخلات", + "Edit Step Name": "تعديل اسم الخطوة", + "Edit Branch Name": "Edit Branch Name", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "اختر العناصر التي ترغب في التكرار عليها من الخطوة السابقة عن طريق النقر على **العناصر**، التي يجب أن تكون قائمة من العناصر.\n\nستقوم الحلقة بالتكرار مرورا بكل عنصر في القائمة وتنفيذ الخطوة التالية لكل منها.", + "Items": "العناصر", + "Select an array of items": "اختر مجموعة من العناصر", + "Reconnect": "Reconnect", + "Select a connection": "اختر اتصالاً", + "Create Connection": "إنشاء اتصال", + "Rename": "إعادة التّسمية", + "Move": "نقل", + "Add Branch": "Add Branch", + "Execute": "Execute", + "Only the first (left) matching branch": "Only the first (left) matching branch", + "All matching paths from left to right": "All matching paths from left to right", + "Branches": "Branches", + "{field} is required": "{field} is required", + "Tool Sample Data": "Tool Sample Data", + "Fill in the following fields to use them as sample data for the trigger.": "Fill in the following fields to use them as sample data for the trigger.", + "No input fields defined in the schema": "No input fields defined in the schema", + "Save": "حفظ", + "Test Environment": "Test Environment", + "Assigned to": "Assigned to", + "(Me)": "(Me)", + "Please select status to resolve the todo": "Please select status to resolve the todo", + "Resolve": "Resolve", + "Change status to resolved": "Change status to resolved", + "Send Sample Data to Webhook": "Send Sample Data to Webhook", + "Method": "Method", + "Query Params": "Query Params", + "Headers": "Headers", + "Body": "Body", + "Type": "Type", + "JSON": "JSON", + "Text": "Text", + "Form Data": "Form Data", + "Generate Sample Data": "أنشئ عينة بيانات", + "Test Step": "اختبار الخطوة", + "Retest": "إعادة الاختبار", + "Testing Failed": "فشل الاختبار", + "Tested Successfully": "تم الاختبار بنجاح", + "Logs": "Logs", + "There is no sample data available found for this trigger.": "لا توجد عينة بيانات متاحة لهذا المشغل.", + "Internal error, please try again later.": "Internal error, please try again later.", + "Failed to run test step and no error message was returned": "فشل في تشغيل خطوة الاختبار ولم يتم إرجاع أي رسالة خطأ", + "Please fix inputs first": "الرجاء إصلاح المدخلات أولاً", + "No sample data available": "No sample data available", + "Old results were removed, retest for new sample data": "Old results were removed, retest for new sample data", + "Result #": "نتيجة #", + "The sample data can be used in the next steps.": " يمكن استخدام بيانات العينة هذه في الخطوات التالية ", + "Testing Trigger": "اختبار المشغّل", + "Action Required": "Action Required", + "testPieceWebhookTriggerNote": "الرجاء الذَّهاب إلى {pieceName} وتشغيل {triggerName}.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Send Data to the webhook URL to generate sample data to use in the next steps", + "Test Trigger": "اختبر المشغّل ", + "Use Mock Data": "Use Mock Data", + "Load Sample Data": "تحميل عيّنة البيانات", + "Test Tool": "Test Tool", + "home": "الرئيسية", + "Home": "الرئيسية", + "Alerts": "التنبيهات", + "Releases": "Releases", + "Flows": "التدفقات", + "Products": "Products", + "MCP": "MCP", + "Tables": "Tables", + "Todos": "Todos", + "Push to Git": "ارفع إلى Git", + "Move To": "نقل إلى", + "Duplicating": "الاستنساخ", + "Import": "استيراد", + "Exporting": "جار التصدير", + "Export": "تصدير", + "Share": "مشاركة", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "هل أنت متأكد من أنك تريد حذف هذا التدفق؟ سيؤدي هذا إلى حذف التدفق، وجميع بياناته وأي عمليات تشغيل في الخلفية بشكل دائم.", + "You are on a development branch, this will also delete the flow from the remote repository.": "You are on a development branch, this will also delete the flow from the remote repository.", + "flow": "التدفق", + "Community Support": "Community Support", + "Overview": "Overview", + "Projects": "المشروعات", + "Users": "المستخدمون", + "Setup": "Setup", + "Branding": "العلامة", + "Global Connections": "Global Connections", + "Pieces": "القطع", + "Templates": "القوالب", + "License Key": "License Key", + "Security": "Security", + "Audit Logs": "سجل التدقيق", + "Single Sign On": "تسجيل دخول واحد", + "Signing Keys": "مفاتيح التوقيع", + "Project Roles": "Project Roles", + "API Keys": "مفاتيح API", + "Infrastructure": "Infrastructure", + "Workers": "Workers", + "Health": "Health", + "Billing": "Billing", + "Settings": "الإعدادات", + "Contact Sales": "Contact Sales", + "General": "عام", + "Appearance": "المظهر", + "Team": "الفريق", + "Environments": "Environments", + "Project Settings": "Project Settings", + "Exit Platform Admin": "Exit Platform Admin", + "Enter Platform Admin": "Enter Platform Admin", + "Logout": "Logout", + "Misc": "Misc", + "Connections": "الاتصالات", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "Today": "Today", + "Tasks": "المهام", + "AI Credits": "AI Credits", + "Usage resets in": "Usage resets in", + "Manage": "Manage", + "Unlimited": "Unlimited", + "Enabled": "Enabled", + "Disabled": "Disabled", + "Could not claim the authorization code, make sure you have correct settings and try again.": "لا يمكن طلب رمز التفويض، تأكد من الإعدادات وحاول مرة أخرى.", + "Connection failed with error {msg}": "فشل الاتصال مع خطأ {msg}", + "You don't have the permission to create a connection.": "You don't have the permission to create a connection.", + "Reconnect {displayName} Connection": "إعادة الاتصال {displayName}", + "Connect to {displayName}": "Connect to {displayName}", + "Connection Name": "اسم الاتصال", + "Connection name": "اسم الاتصال", + "New Connection": "اتصال جديد", + "Redirect URL": "Redirect URL", + "Client ID": "معرِّف العميل", + "Client Secret": "كلمة سر العميل", + "Connect": "توصيل", + "Disconnect": "قطع الاتصال", + "I would like to use my own App Credentials": "أرغب في استخدام بيانات اعتماد التطبيق الخاصة بي", + "I would like to use predefined App Credentials": "أرغب في استخدام بيانات اعتماد التطبيق المحددة مسبقًا", + "Permission needed": "Permission needed", + "Connections replaced successfully": "Connections replaced successfully", + "Error": "خطأ", + "Failed to replace connections": "Failed to replace connections", + "Failed to get affected flows": "Failed to get affected flows", + "Please select a piece": "Please select a piece", + "Please select a connection to replace": "Please select a connection to replace", + "Please select a connection to replace with": "Please select a connection to replace with", + "Replace Connections": "Replace Connections", + "Confirm Replacement": "Confirm Replacement", + "Replace one connection with another.": "Replace one connection with another.", + "This action requires ": "This action requires ", + "reconnecting": "reconnecting", + " any associated MCP pieces.": " any associated MCP pieces.", + "Piece": "قطعة", + "Select a piece": "Select a piece", + "Connection to Replace": "Connection to Replace", + "Choose connection to replace": "Choose connection to replace", + "Replaced With": "Replaced With", + "Choose connection to replace with": "Choose connection to replace with", + "All flows will be changed to use the replaced with connection": "All flows will be changed to use the replaced with connection", + "Next": "Next", + "No flows will be affected by this change": "No flows will be affected by this change", + "Back": "Back", + "Unnamed tool": "Unnamed tool", + "This flow is enabled": "This flow is enabled", + "Enable this flow to make it available": "Enable this flow to make it available", + "Piece is updated successfully": "Piece is updated successfully", + "Piece is added successfully": "Piece is added successfully", + "Failed to update piece": "Failed to update piece", + "Failed to add piece": "Failed to add piece", + "Please select a connection": "Please select a connection", + "Your MCP server already has this piece": "Your MCP server already has this piece", + "+ New Connection": "+ New Connection", + "Edit Piece": "Edit Piece", + "Add Piece": "Add Piece", + "Connection": "الاتصال", + "MCP piece": "MCP piece", + "Failed to update piece status": "Failed to update piece status", + "Connection required": "Connection required", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.", + "piece": "piece", + "Connect your AI assistant to external services": "Connect your AI assistant to external services", + "Collapse": "Collapse", + "Show All": "Show All", + "Server URL": "Server URL", + "Hide the token for security": "Hide the token for security", + "Show the token": "Show the token", + "Generate a new token for security. This will invalidate the current URL.": "Generate a new token for security. This will invalidate the current URL.", + "URL copied to clipboard": "URL copied to clipboard", + "Copy URL to clipboard": "Copy URL to clipboard", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.", + "Max tables reached": "Max tables reached", + "You can't create more than {maxTables} tables": "You can't create more than {maxTables} tables", + "Name": "الاسم", + "Created": "تم الإنشاء", + "Delete Tables": "Delete Tables", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Are you sure you want to delete the selected tables? This action cannot be undone.", + "table": "table", + "Create and manage your tables to store your automation data": "Create and manage your tables to store your automation data", + "New Table": "New Table", + "No tables have been created yet": "No tables have been created yet", + "Create a table to get started and start managing your automation data": "Create a table to get started and start managing your automation data", + "Error deleting connections": "Error deleting connections", + "Status": "الحالة", + "Display Name": "الاسم المعروض", + "Owner": "Owner", + "App": "التطبيق", + "This connection is global and can be managed in the platform admin": "This connection is global and can be managed in the platform admin", + "External ID": "External ID", + "Connected At": "Connected At", + "Confirm Deletion": "Confirm Deletion", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Are you sure you want to delete the selected connections? This action cannot be undone.", + "Deleting connections may cause your Flows or MCP tools to break.": "Deleting connections may cause your Flows or MCP tools to break.", + "Manage project connections to external systems.": "Manage project connections to external systems.", + "No connections found": "No connections found", + "Come back later when you create a automation to manage your connections": "Come back later when you create a automation to manage your connections", + "Steps": "الخطوات", + "Folder": "مجلد", + "Flow name": "اسم تدفق المهام", + "No flows found": "No flows found", + "Create a workflow to start automating": "Create a workflow to start automating", + "Create and manage your flows, run history and run issues": "Create and manage your flows, run history and run issues", + "Issues": "المشكلات", + "Untitled": "بلا عنوان", + "Create flow": "Create flow", + "From scratch": "من الصفر", + "Use a template": "استخدم نموذج", + "From local file": "From local file", + "Flow Name": "اسم التدفق", + "Count": "عدّ", + "First Seen": "تم رؤيته للمرة الأولى", + "Last Seen": "آخر مشاهدة", + "Issues in {flowDisplayName} is marked as resolved.": "Issues in {flowDisplayName} is marked as resolved.", + "Unlock Issues": "Unlock Issues", + "Track issues in your workflows and troubleshoot them.": "Track issues in your workflows and troubleshoot them.", + "No issues found": "No issues found", + "All your workflows are running smoothly.": "All your workflows are running smoothly.", + "Mark as Resolved": "علّمها ”حُلّت“", + "All Flows Are Turned Off": "All Flows Are Turned Off", + "Task Usage Exceeded": "Task Usage Exceeded", + "of the Allowed Limit.": "of the Allowed Limit.", + "When a project tasks limit is reached,": "When a project tasks limit is reached,", + "all flows will be turned off and you will not be able to run any flows.": "all flows will be turned off and you will not be able to run any flows.", + "Please visit": "Please visit", + "Your Plan": "Your Plan", + "and increase your task limit, which requires your payment details.": "and increase your task limit, which requires your payment details.", + "Please contact your admin to increase the project task limit.": "Please contact your admin to increase the project task limit.", + "and increase the project task limit.": "and increase the project task limit.", + "Dismiss": "Dismiss", + "Token rotated successfully": "Token rotated successfully", + "Failed to rotate token": "Failed to rotate token", + "Piece removed successfully": "Piece removed successfully", + "Failed to remove piece": "Failed to remove piece", + "Flow created successfully": "Flow created successfully", + "Failed to create flow": "Failed to create flow", + "Add Flow": "Add Flow", + "Let your AI assistant trigger automations": "Let your AI assistant trigger automations", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Connect to your hosted MCP Server using any MCP client to communicate with tools", + "MCP Server": "MCP Server", + "My Tools": "My Tools", + "Create Flow": "Create Flow", + "Note": "Note", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.", + "This URL grants access to your tools and data. Only share with trusted applications.": "This URL grants access to your tools and data. Only share with trusted applications.", + "Server Configuration": "Server Configuration", + "Hide sensitive data": "Hide sensitive data", + "Show sensitive data": "Show sensitive data", + "Create a new URL. The current one will stop working.": "Create a new URL. The current one will stop working.", + "Copy configuration": "Copy configuration", + "Configuration copied to clipboard": "Configuration copied to clipboard", + "Claude": "Claude", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "Server/Other", + "Note: MCPs only work with": "Note: MCPs only work with", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", not the web version.", + "Prerequisites:": "Prerequisites:", + "Install": "تثبيت", + "Node.js": "Node.js", + "and": "و", + "Open Settings:": "Open Settings:", + "Click the menu and select": "Click the menu and select", + "Developer": "Developer", + "Configure MCP:": "Configure MCP:", + "Click": "Click", + "Edit Config": "Edit Config", + "and paste the configuration below": "and paste the configuration below", + "Save and Restart:": "Save and Restart:", + "Save the config and restart Claude Desktop": "Save the config and restart Claude Desktop", + "Navigate to": "Navigate to", + "Cursor Settings": "Cursor Settings", + "Add Server:": "Add Server:", + "Add new global MCP server": "Add new global MCP server", + "Configure:": "Configure:", + "Paste the configuration below and save": "Paste the configuration below and save", + "Use either method:": "Use either method:", + "Go to": "Go to", + "Advanced Settings": "Advanced Settings", + "Open Command Palette and select": "Open Command Palette and select", + "Windsurf Settings Page": "Windsurf Settings Page", + "Navigate to Cascade:": "Navigate to Cascade:", + "Select": "Select", + "Cascade": "Cascade", + "in the sidebar": "in the sidebar", + "Add Server": "Add Server", + "Add custom server +": "Add custom server +", + "Copy URL": "Copy URL", + "Client Setup Instructions": "Client Setup Instructions", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "After changing connections or flows, reconnect your MCP server for changes to take effect.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.", + "icon": "icon", + "Unlock Analytics": "Unlock Analytics", + "Get insights into your platform usage and performance with our analytics dashboard": "Get insights into your platform usage and performance with our analytics dashboard", + "Active Flows": "Active Flows", + "The number of enabled flows in the platform": "The number of enabled flows in the platform", + "Active Projects": "Active Projects", + "The number of projects with at least one enabled flow": "The number of projects with at least one enabled flow", + "Active Users": "Active Users", + "The number of users logged in the last 30 days": "The number of users logged in the last 30 days", + "Out of {totalusers} total users": "Out of {totalusers} total users", + "Pieces Used": "Pieces Used", + "The number of unique pieces used across all flows": "The number of unique pieces used across all flows", + "Flows with AI": "Flows with AI", + "The number of enabled flows that use AI pieces": "The number of enabled flows that use AI pieces", + "Metrics": "Metrics", + "Executed Tasks": "Executed Tasks", + "Showing total executed tasks for specified time range": "Showing total executed tasks for specified time range", + "Tasks Usage Limit": "Tasks Usage Limit", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.", + "Number of monthly tasks": "Number of monthly tasks", + "Save changes": "Save changes", + "Limits updated successfully": "Limits updated successfully", + "Failed to update limits": "Failed to update limits", + "Failed to load billing information": "Failed to load billing information", + "Billing Amount": "Billing Amount", + "Manage Payment Details": "Manage Payment Details", + "Add Payment Details": "Add Payment Details", + "Current Task Usage": "Current Task Usage", + "Count of executed steps": "Count of executed steps", + "Billing Limit": "Billing Limit", + "Edit": "Edit", + "Add Limit": "Add Limit", + "Current Credit Usage": "Current Credit Usage", + "WebSocket Connection": "WebSocket Connection", + "Connected": "Connected", + "Disconnected": "Disconnected", + "No issues detected": "No issues detected", + "Check the status of your platform and its components": "Check the status of your platform and its components", + "System Health Status": "System Health Status", + "All systems are running smoothly": "All systems are running smoothly", + "Check the health of your worker machines": "Check the health of your worker machines", + "Workers Machine": "Workers Machine", + "This is demo data. In a real environment, this would show your actual worker machines.": "This is demo data. In a real environment, this would show your actual worker machines.", + "No workers found": "No workers found", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "You don't have any worker machines yet. Spin up new machines to execute your automations", + "IP Address": "IP Address", + "CPU Usage": "CPU Usage", + "Disk Usage": "Disk Usage", + "RAM Usage": "RAM Usage", + "Last Contact": "Last Contact", + "Configs": "Configs", + "Environment Variables": "Environment Variables", + "Websocket Connection Error": "Websocket Connection Error", + "Retry Connection": "Retry Connection", + "Update Available": "Update Available", + "Update Now": "Update Now", + "Your Universal AI needs a quick setup": "Your Universal AI needs a quick setup", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.", + "Configure": "Configure", + "Platform Alerts": "Platform Alerts", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.", + "View Alerts": "View Alerts", + "Used Tasks": "Used Tasks", + "Used AI Credits": "Used AI Credits", + "Cannot delete active project, switch to another project first": "Cannot delete active project, switch to another project first", + "Delete Projects": "Delete Projects", + "Are you sure you want to delete the selected projects?": "Are you sure you want to delete the selected projects?", + "New Project": "New Project", + "Validation error": "Validation error", + "Project has enabled flows. Please disable them first.": "Project has enabled flows. Please disable them first.", + "This project is active. Please switch to another project first.": "This project is active. Please switch to another project first.", + "Unlock Projects": "Unlock Projects", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Orchestrate your automation teams across projects with their own flows, connections and usage quotas", + "Manage your automation projects": "Manage your automation projects", + "No projects found": "No projects found", + "Start by creating projects to manage your automation teams": "Start by creating projects to manage your automation teams", + "Edit project": "Edit project", + "Name is required": "الإسم مطلوب", + "Create New Project": "Create New Project", + "Project Name": "إسم المشروع", + "Id": "Id", + "Enable API Keys": "Enable API Keys", + "Create and manage API keys to access Activepieces APIs.": "Create and manage API keys to access Activepieces APIs.", + "New Api Key": "New Api Key", + "No API keys found": "No API keys found", + "Start by creating an API key to communicate with Activepieces APIs": "Start by creating an API key to communicate with Activepieces APIs", + "Delete API Key": "Delete API Key", + "Are you sure you want to delete this API key?": "Are you sure you want to delete this API key?", + "API Key": "API Key", + "API Key Created": "API Key Created", + "Create New API Key": "Create New API Key", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Please save this secret key somewhere safe and accessible. For security reasons,", + "you won't be able to view it again after closing this dialog.": "you won't be able to view it again after closing this dialog.", + "API Key Name": "API Key Name", + "Action": "Action", + "Performed By": "Performed By", + "Project": "Project", + "Unlock Audit Logs": "Unlock Audit Logs", + "Comply with internal and external security policies by tracking activities done within your account": "Comply with internal and external security policies by tracking activities done within your account", + "Track activities done within your platform": "Track activities done within your platform", + "No audit logs found": "No audit logs found", + "Come back later when you have some activity to audit": "Come back later when you have some activity to audit", + "Resource": "Resource", + "Details": "Details", + "N/A": "N/A", + "Flow Run": "Flow Run", + "Flow": "التدفق", + "User": "User", + "Signing Key": "Signing Key", + "Project Role Management": "Project Role Management", + "Define custom roles and permissions to control what your team members can access and modify": "Define custom roles and permissions to control what your team members can access and modify", + "Define custom roles and permissions that can be assigned to your team members": "Define custom roles and permissions that can be assigned to your team members", + "New Role": "New Role", + "Contact sales to unlock custom roles": "Contact sales to unlock custom roles", + "Create New Role": "Create New Role", + "View ": "View ", + "Edit ": "Edit ", + "Role Name": "Role Name", + "Permissions": "Permissions", + "None": "None", + "Read": "Read", + "Write": "Write", + "Create": "إنشاء", + "Email": "البريد الإلكتروني ", + "First Name": "الاسم الأول", + "Last Name": "إسم العائلة", + "Roles": "Roles", + "View the users assigned to this role": "View the users assigned to this role", + "Role": "Role", + "No users found": "No users found", + "Starting by assigning users to this role": "Starting by assigning users to this role", + "Project Role entry deleted successfully": "Project Role entry deleted successfully", + "Updated": "تم التحديث", + "No project roles found": "No project roles found", + "Create custom project roles to manage permissions for platform users": "Create custom project roles to manage permissions for platform users", + "Show Users": "Show Users", + "View Role": "View Role", + "Edit Role": "Edit Role", + "Delete Role": "Delete Role", + "Project Role": "دور المشروع", + "Unlock Embedding Through JS SDK": "Unlock Embedding Through JS SDK", + "Enable signing keys to access embedding functionalities.": "Enable signing keys to access embedding functionalities.", + "New Signing Key": "New Signing Key", + "No signing keys found": "No signing keys found", + "Create a signing key to authenticate users with embedding": "Create a signing key to authenticate users with embedding", + "Delete Signing Key": "Delete Signing Key", + "Are you sure you want to delete this signing key?": "Are you sure you want to delete this signing key?", + "Signing Key Created": "Signing Key Created", + "Create New Signing Key": "Create New Signing Key", + "Signing Key Name": "Signing Key Name", + "Allowed domains updated": "Allowed domains updated", + "Update": "Update", + "Enable": "Enable", + "Configure Allowed Domains": "Configure Allowed Domains", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.", + "Add Domain": "Add Domain", + "Allow logins through {providerName}'s single sign-on functionality.": "Allow logins through {providerName}'s single sign-on functionality.", + "Email authentication updated": "Email authentication updated", + "Enable Single Sign On": "Enable Single Sign On", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Let your users sign in with your current SSO provider or give them self serve sign up access", + "Allowed Domains": "Allowed Domains", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Allow users to authenticate with specific domains. Leave empty to allow all domains.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Allowed Email Login", + "Allow logins through email and password.": "Allow logins through email and password.", + "Single sign on settings updated": "Single sign on settings updated", + "Disable": "Disable", + "Configure {provider} SSO": "Configure {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} Client ID", + "{provider} Client Secret": "{provider} Client Secret", + "Single sign-on settings updated": "Single sign-on settings updated", + "Configure SAML 2.0 SSO": "Configure SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDP Metadata", + "IDP Certificate": "IDP Certificate", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "Base URL", + "Resource Name": "Resource Name", + "Deployment Name": "Deployment Name", + "Saving": "Saving", + "Activepieces Copilot": "Activepieces Copilot", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot is configured and ready to help your users build flows faster using AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configure Activepieces Copilot to help your users build flows faster using AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Set provider credentials that will be used by universal AI pieces, i.e Text AI.", + "AI Providers": "AI Providers", + "Copilot": "Copilot", + "Configure credentials for {providerName} AI provider.": "Configure credentials for {providerName} AI provider.", + "Update AI Provider": "Update AI Provider", + "Enable AI Provider": "Enable AI Provider", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Please enter a valid domain", + "Your changes have been saved.": "تم حفظ التغييرات.", + "The domain is already added.": "The domain is already added.", + "Add Custom Domain": "Add Custom Domain", + "Enter a domain name without a protocol (e.g. example.com)": "Enter a domain name without a protocol (e.g. example.com)", + "Logo URL": "Logo URL", + "Icon URL": "Icon URL", + "Favicon URL": "Favicon URL", + "Default Language": "Default Language", + "Select Language": "Select Language", + "No Languages": "No Languages", + "Primary Color": "Primary Color", + "Custom Domains": "Custom Domains", + "No domains added yet.": "No domains added yet.", + "Verified": "Verified", + "Pending, please contact the support for dns verification.": "Pending, please contact the support for dns verification.", + "Are you sure you want to delete {domain}?": "Are you sure you want to delete {domain}?", + "Brand Activepieces": "القطع النشطة للعلامات التجارية", + "Give your users an experience that looks like you by customizing the color, logo and more": "Give your users an experience that looks like you by customizing the color, logo and more", + "Configure the appearance and SMTP settings for your platform.": "Configure the appearance and SMTP settings for your platform.", + "Invalid host": "Invalid host", + "Invalid username": "Invalid username", + "Invalid password": "Invalid password", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Invalid sender name", + "SMTP is configured": "SMTP is configured", + "Mail Server": "Mail Server", + "Set up your SMTP settings to send emails from your domain.": "Set up your SMTP settings to send emails from your domain.", + "Disable Mail Server": "Disable Mail Server", + "Are you sure you want to disable your mail server?": "Are you sure you want to disable your mail server?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "This will stop you from sending emails for issues, quota limits, invitations and forgot password.", + "mail server": "mail server", + "Host": "Host", + "Port": "Port", + "Username": "Username", + "Password": "كلمة المرور", + "Sender Email": "Sender Email", + "Sender Name": "Sender Name", + "Enable Global Connections": "Enable Global Connections", + "Manage platform-wide connections to external systems.": "Manage platform-wide connections to external systems.", + "No global connections found": "No global connections found", + "Create a global connection that can be shared to multiple projects": "Create a global connection that can be shared to multiple projects", + "License key is invalid": "License key is invalid", + "Invalid license key": "Invalid license key", + "License activated!": "License activated!", + "Activate License Key": "Activate License Key", + "Let the magic begin!": "Let the magic begin!", + "Activate": "Activate", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ", + "This feature is not available in your current edition. ": "This feature is not available in your current edition. ", + "Learn how to upgrade": "Learn how to upgrade", + "Activate your platform and unlock enterprise features": "Activate your platform and unlock enterprise features", + "Activate License": "Activate License", + "Expiration": "Expiration", + "Valid until": "Valid until", + "Expired": "Expired", + "Expires soon": "Expires soon", + "Features": "Features", + "Applying Tags...": "Applying Tags...", + "Tags applied.": "Tags applied.", + "Tag created": "Tag created", + "Tag": "Tag", + "Tags": "Tags", + "Control Pieces": "قطع التحكم", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Show the pieces that matter most to your users and hide the ones you don't like.", + "Manage the pieces that are available to your users": "Manage the pieces that are available to your users", + "Start by installing pieces that you want to use in your automations": "Start by installing pieces that you want to use in your automations", + "Piece Name": "اسم القطعة", + "Hide this piece from all projects": "Hide this piece from all projects", + "Show this piece for all projects": "Show this piece for all projects", + "Unpin this piece": "Unpin this piece", + "Pin this piece": "Pin this piece", + "Pieces synced": "Pieces synced", + "Pieces have been synced from the activepieces cloud.": "Pieces have been synced from the activepieces cloud.", + "OAuth2 Credentials Deleted": "OAuth2 Credentials Deleted", + "OAuth2 Credentials Updated": "OAuth2 Credentials Updated", + "Configure OAuth2 APP": "Configure OAuth2 APP", + "Delete OAuth2 APP": "Delete OAuth2 APP", + "Templates deleted successfully": "Templates deleted successfully", + "Delete Templates": "Delete Templates", + "Are you sure you want to delete the selected templates?": "Are you sure you want to delete the selected templates?", + "New Template": "New Template", + "Unlock Templates": "Unlock Templates", + "Convert the most common automations into reusable templates 1 click away from your users": "Convert the most common automations into reusable templates 1 click away from your users", + "Convert the most common automations into reusable templates": "Convert the most common automations into reusable templates", + "No templates found": "No templates found", + "Create a template for your user to inspire them": "Create a template for your user to inspire them", + "Edit template": "Edit template", + "Template is required": "Template is required", + "Update New Template": "Update New Template", + "Create New Template": "Create New Template", + "Template Name": "Template Name", + "Description": "الوصف", + "Template Description": "Template Description", + "Blog URL": "Blog URL", + "Template Blog URL": "Template Blog URL", + "Template": "Template", + "Invalid JSON": "Invalid JSON", + "User deleted successfully": "User deleted successfully", + "User activated successfully": "User activated successfully", + "User deactivated successfully": "User deactivated successfully", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Manage your users and their access to your projects", + "Start inviting users to your project": "Start inviting users to your project", + "External Id": "External Id", + "Admin": "مديــر", + "Member": "Member", + "Activated": "Activated", + "Deactivated": "Deactivated", + "Edit user": "Edit user", + "Admin cannot be deactivated": "Admin cannot be deactivated", + "Deactivate user": "Deactivate user", + "Activate user": "Activate user", + "Delete User": "Delete User", + "Are you sure you want to delete this user?": "Are you sure you want to delete this user?", + "Delete user": "Delete user", + "Update User Role": "Update User Role", + "Meeting Summary Flow": "Meeting Summary Flow", + "Added new features and fixed bugs": "Added new features and fixed bugs", + "Flows Changes": "Flows Changes", + "Connections Changes": "Connections Changes", + "New connections are placeholders and need to be reconnected again": "New connections are placeholders and need to be reconnected again", + "renamed to": "renamed to", + "Tables Changes": "Tables Changes", + "No changes to apply": "No changes to apply", + "Apply Changes": "Apply Changes", + "Create Git Release": "Create Git Release", + "Create Project Release": "Create Project Release", + "Create Rollback to": "Create Rollback to", + "Source": "Source", + "Rollback": "Rollback", + "Imported At": "Imported At", + "Imported By": "Imported By", + "Track and manage your project version history and deployments. ": "Track and manage your project version history and deployments. ", + "Environments & Releases": "Environments & Releases", + "Project Releases": "Project Releases", + "Create Release": "Create Release", + "From Git": "From Git", + "From Project": "From Project", + "No project releases found": "No project releases found", + "Create a project release to get started": "Create a project release to get started", + "Please select project": "Please select project", + "No Changes Found": "No Changes Found", + "There are no differences to apply": "There are no differences to apply", + "Please select a project": "Please select a project", + "Search projects...": "Search projects...", + "Review Changes": "مراجعة التغييرات ", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Imported by", + "from": "from", + "No description provided": "No description provided", + "Invitation only sign up": "Invitation only sign up", + "Please ask your administrator to add you to the organization.": "Please ask your administrator to add you to the organization.", + "Something went wrong, please try again.": "Something went wrong, please try again.", + "Please try again.": "Please try again.", + "Please enter a valid email address": "يرجى إدخال عنوان بريد إلكتروني صحيح", + "The email is already added.": "تمت إضافة البريد الإلكتروني.", + "Add email": "إضافة بريد إلكتروني", + "Only project admins can do this": "Only project admins can do this", + "Add Alert Email": "إضافة بريد إلكتروني للتنبيه", + "Enter the email address to receive alerts.": "أدخل عنوان البريد الإلكتروني لتلقي التنبيهات.", + "Add Email": "إضافة بريد إلكتروني", + "Emails": "رسائل البريد الإلكتروني", + "Add email addresses to receive alerts.": "إضافة عناوين بريد إلكتروني لتلقي التنبيهات.", + "No emails added yet.": "لا توجد رسائل بريد إلكتروني مضافة.", + "Choose what you want to be notified about.": "اختر ما تريد إعلامك عنه.", + "Project and alert permissions are required to change this setting.": "Project and alert permissions are required to change this setting.", + "Every Failed Run": "كل تشغيل فاشل", + "Get an email alert when a flow fails.": "احصل على تنبيه بالبريد الإلكتروني عند فشل التدفق.", + "Get an email alert when a new issue created.": "احصل على تنبيه بالبريد الإلكتروني عند إنشاء مشكلة جديدة.", + "Never": "أبدًا", + "Turn off email notifications.": "Turn off email notifications.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "تخصيص مظهر التطبيق. التبديل تلقائياً بين المظاهر النهارية والليلية.", + "Select the theme for the dashboard.": "حدد شكل لوحة التحكم.", + "Light": "نهاري", + "Dark": "داكن", + "Select the language that will be used in the dashboard.": "حدد اللغة التي سيتم استخدامها في لوحة التحكم.", + "Select language": "حدد اللغة", + "Search language...": "البحث عن لغة…", + "No language found.": "لم يُعثر على لغات.", + "Help us translate Activepieces to your language.": "ساعدنا على ترجمة Activepieces إلى لغتك.", + "Learn more": "معرفة المزيد", + "Git Connection Removed": "Git Connection Removed", + "Your Git repository has been successfully disconnected": "Your Git repository has been successfully disconnected", + "Enable Environments": "Enable Environments", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Deploy flows across development, staging and production environments with version control and team collaboration", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Connect to Git to enable version control, backup your flows, and manage multiple environments. ", + "Repository URL": "Repository URL", + "Not connected": "Not connected", + "Project Folder": "Project Folder", + "Releases Enabled": "Releases Enabled", + "You have successfully enabled releases": "You have successfully enabled releases", + "Enable releases to easily create and manage project releases.": "Enable releases to easily create and manage project releases.", + "The external ID is already taken.": "The external ID is already taken.", + "Manage general settings for your project.": "إدارة الإعدادات العامة لمشروعك.", + "Used to identify the project based on your SaaS ID": "Used to identify the project based on your SaaS ID", + "org-3412321": "org-3412321", + "Delete {name}": "حذف {name}", + "This will permanently delete this piece, all steps using it will fail.": "سيؤدي هذا إلى حذف هذه القطعة بشكل دائم، وستفشل جميع الخطوات التي تستخدمها.", + "Add a piece to your project that you want to use in your automations": "Add a piece to your project that you want to use in your automations", + "Pieces list updated": "Pieces list updated", + "Manage Pieces": "Manage Pieces", + "Choose which pieces you want to be available for your current project users": "Choose which pieces you want to be available for your current project users", + "Unlock Team Permissions": "Unlock Team Permissions", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial", + "Project Members": "أعضاء المشروع", + "Invite your team members to collaborate.": "قم بدعوة أعضاء فريقك للتعاون.", + "No members are added to this project.": "لم يتم إضافة أي عضو إلى هذا المشروع.", + "Pending Invitations": "الدعوات المعلقة", + "No pending invitation.": "لا توجد دعوات معلقة", + "templateId is missing": "معرّف القالب مفقود", + "Me Only": "Me Only", + "Unresolved": "Unresolved", + "Resolved": "Resolved", + "Title": "Title", + "Date Created": "Date Created", + "Manage todos for your project that are created by automations": "Manage todos for your project that are created by automations", + "No todos found": "No todos found", + "You do not have any pending todos. Great job!": "You do not have any pending todos. Great job!", + "Write a comment...": "Write a comment...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "This feature is still under testing and might be changed often", + "Failed to copy to clipboard": "فشل النسخ إلى الحافظة", + "{number} items selected": "{number} items selected", + "Select All": "Select All", + "No results found.": "لا توجد نتائج.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect يجب أن يستخدم داخل MultiSelectProserder", + "Unset": "Unset", + "Refresh": "Refresh", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} المزيد", + "Removed {entityName}": "تمت إزالة {entityName}", + "Download File": "تنزيل ملف", + "Copied to clipboard": "نُسِخَ إلى الحافظة", + "File is not available after execution.": "الملف غير متاح بعد التنفيذ.", + "Available for Projects": "Available for Projects", + "Select projects": "Select projects", + "No items": "No items", + "Previous": "Previous", + "to": "to", + "Last Week": "Last Week", + "Last Month": "Last Month", + "Last 3 Months": "Last 3 Months", + "Last 6 Months": "Last 6 Months", + "Next 7 days": "Next 7 days", + "Next 30 days": "Next 30 days", + "Next 90 days": "Next 90 days", + "Next 180 days": "Next 180 days", + "Select Time Range": "Select Time Range", + "Clear": "Clear", + "Download": "Download", + "Go to Dashboard": "Go to Dashboard", + "Select a file": "Select a file", + "Press space to separate values": "Press space to separate values", + "AM": "AM", + "PM": "PM", + "Already have an account?": "لديك حساب بالفعل؟", + "Sign in": "تسجيل الدخول", + "Don't have an account?": "ليس لديك حساب؟", + "Sign up": "إنشاء حساب", + "Welcome Back!": "أهلاً بعودتك!", + "Enter your email below to sign in to your account": "أدخل بريدك الإلكتروني أدناه لتسجيل الدخول إلى حسابك", + "Let's Get Started!": "لنبدأ!", + "Create your account and start flowing!": "أنشئ حسابك وابدأ في انشاء التدفقات!", + "Your password was changed successfully": "تم تغيير كلمة المرور بنجاح", + "Your password reset request has expired, please request a new one": "انتهت صلاحيّة طلب إعادة تعيين كلمة المرور الخاصة بك، يرجى تقديم طلب جديد", + "Reset Password": "إعادة تعيين كلمة المرور", + "Enter your new password": "أدخل كلمة مرور جديدة", + "Password is required": "كلمة المرور مطلوبة", + "Verification email resent, if previous one expired.": "Verification email resent, if previous one expired.", + "Password reset link resent, if previous one expired.": "Password reset link resent, if previous one expired.", + "We sent you a link to complete your registration to": "We sent you a link to complete your registration to", + "We sent you a link to reset your password to": "We sent you a link to reset your password to", + "Didn't receive an email or it expired?": "Didn't receive an email or it expired?", + "Resend": "إعادة الإرسال", + "Please enter your email": "الرجاء ادخال بريدك الالكتروني", + "Check Your Inbox": "تحقَّق من بريدك الوارِد", + "If the user exists we'll send you an email with a link to reset your password.": " إذا كان المستخدم موجودًا، فسنرسل إليك بريدًا إلكترونيًا يحتوي على رابط لإعادة تعيين كلمة المرور الخاصة بك. ", + "Send Password Reset Link": "إرسال رابط إعادة تعيين كلمة المرور", + "Back to sign in": "العودة لتسجيل الدخول", + "Email is invalid": "البريد الإلكتروني غير صحيح", + "Something went wrong, please try again later": "حدث خطأ ما، يُرجى إعادة المحاولة لاحقًا", + "Invalid email or password": "البريد الإلكتروني أو كلمة المرور غير صالحين", + "User has been deactivated": "User has been deactivated", + "Email domain is disallowed": "Email domain is disallowed", + "Email authentication has been disabled": "Email authentication has been disabled", + "Forgot your password?": "هل نسيت كلمة المرور؟", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "Sign up is restricted. You need an invitation to join. Please contact the administrator.", + "Email is already used": "البريد الإلكتروني مستخدم بالفعل", + "Email authentication is disabled": "Email authentication is disabled", + "First name is required": "الاسم الأول مطلوب", + "Last name is required": "اسم العائلة مطلوب", + "Email is required": "البريد الإلكتروني مطلوب", + "Receive updates and newsletters from activepieces": "احصل على التحديثات والنشرات الإخبارية من Activepieces", + "By creating an account, you agree to our": "بإنشاء حساب، فإنك توافق على", + "terms of service": "شُرُوط الخِدمَة", + "privacy policy": "سياسة الخصوصيّة", + "Sign up With": "Sign up With", + "Google": "Google", + "Sign in With": "Sign in With", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "Email has been verified. You will be redirected to sign in...", + "Verifying email...": "جارٍ التحقق من البريد الإلكتروني...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "invitation has expired, once you sign in again you will be able to resend the verification email.", + "Redirecting to sign in...": "Redirecting to sign in...", + "Password must contain at least one special character": "يجب أن تحتوي كلمة المرور على رمز خاص واحد على الأقل", + "Password must contain at least one lowercase letter": "يجب أن تحتوي كلمة المرور على حرف صغير واحد على الأقل.", + "Password must contain at least one uppercase letter": "يجب أن تحتوي كلمة المرور على حرف كبير واحد على الأقل.", + "Password must contain at least one number": "كلمة المرور يجب أن تحتوي على الأقل على رقم واحد", + "8-64 Characters": "8-64 رمز", + "Special Character": "رمز خاص", + "Lowercase": "أحرف صغيرة", + "Uppercase": "احرف كبيرة", + "Number": "رقم", + "Connection has been updated.": "Connection has been updated.", + "Edit Global Connection": "Edit Global Connection", + "Connection has been renamed.": "Connection has been renamed.", + "New Connection Name": "New Connection Name", + "Connection name already used": "Connection name already used", + "Please select at least one project": "Please select at least one project", + "Run Succeeded": "التشغيل نجح", + "Run Failed": "التشغيل فشل", + "Flow Run is paused": "تم إيقاف تشغيل التدفق مؤقتاً", + "Run Failed due to quota exceeded": "فشل التشغيل بسبب تجاوز الحصة", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Run failed due to exceeding the memory limit of {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "تجاوز تشغيل {timeout} ثانية، حاول تحسين الخطوات.", + "Run failed for an unknown reason, contact support.": "فشل التشغيل لسبب غير معروف، تواصل مع الدعم.", + "Unknown": "غير معروف", + "Exit Run": "الخروج من التشغيل", + "Select shown": "Select shown", + "Select all": "Select all", + "Start Time": "توقيت البداية", + "Runs replayed successfully": "Runs replayed successfully", + "Retry": "Retry", + "all except": "all except", + "all": "all", + "Only failed runs can be retried from failed step": "Only failed runs can be retried from failed step", + "No flow runs found": "No flow runs found", + "Come back later when your automations start running": "Come back later when your automations start running", + "Step running": "الخطوة قيد التشغيل", + "Step paused": "الخطوة متوقفة مؤقتاً", + "Step Stopped": "تم إيقاف الخطوة", + "Step Succeeded": "تمت الخطوة بنجاح", + "Step Failed": "فشلت الخطوة", + "Please publish flow first": "يرجى نشر التدفق أولاً", + "Flow is on": "التدفق قيد التشغيل", + "Flow is off": "التدفق متوقف", + "Permission Needed": "يلزم الحصول على إذن", + "Draft Version": "نسخة مسودة", + "Published Version": "Published Version", + "Locked Version": "نسخة مقفلة", + "flowsImported": "{flowsCount, plural, =0 {لا توجد تدفقات مستوردة} =1 {تم استيراد التدفق بنجاح} other {تم استيراد التدفقات بنجاح}}", + "Template file is invalid": "Template file is invalid", + "No valid templates found. The following files failed to import: ": "No valid templates found. The following files failed to import: ", + "Please select a file first": "Please select a file first", + "Unsupported file type": "Unsupported file type", + "Import Flow": "استيراد تدفق", + "Warning": "Warning", + "Importing a flow will overwrite your current one.": "Importing a flow will overwrite your current one.", + "Select a folder": "Select a folder", + "Folders": "المجلدات", + "Please select a folder": "الرجاء إختيار مجلد", + "Moved flows successfully": "Moved flows successfully", + "Move Selected Flows": "Move Selected Flows", + "Select Folder": "حدد الملف", + "No Folders": "لا توجد مجلدات", + "Flow has been renamed.": "تمت إعادة تسمية التدفق.", + "New Flow Name": "اسم تدفق جديد", + "Use Template": "استخدام القالب", + "Browse Templates": "تصفح القوالب", + "Search templates": "بحث في القوالب", + "No templates found, try adjusting your search": "No templates found, try adjusting your search", + "Read more about this template in": "اقرأ المزيد عن هذا القالب في", + "this blog!": "هذه المدونة!", + "Share Template": "مشاركة القالب", + "Generate or update a template link for the current flow to easily share it with others.": "إنشاء أو تحديث رابط قالب للتدفق الحالي لمشاركته بسهولة مع الآخرين.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "لن يحتوي القالب على أي بيانات اعتماد في حقول الاتصال، مع الحفاظ على أمن المعلومات الحساسة.", + "A short description of the template": "وصف موجز للنموذج", + "Flow Is In Use": "Flow Is In Use", + "Flow is being used by another user, please try again later.": "Flow is being used by another user, please try again later.", + "Flow has been published.": "لقد تم نشر التدفق.", + "Flows have been exported.": "Flows have been exported.", + "Run": "تشغيل", + "Real time flow": "تدفق في الوقت الفعلي", + "Flow can't be published with empty trigger {name}": "لا يمكن نشر التدفق مع مشغل فارغ {name}", + "Please contact support as your published flow has a problem": "يرجى الاتصال بالدعم لوجود مشكلة في التدفق المنشور", + "Actions": "الإجراءات", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.", + "You are on a development branch, this will not delete the flows from the remote repository.": "You are on a development branch, this will not delete the flows from the remote repository.", + "Please enter folder name": "أدخل اسم المجلد", + "Added folder successfully": "تم إضافة المجلد بنجاح", + "The folder name already exists.": "اسم المجلد موجود بالفعل.", + "New Folder": "مجلد جديد", + "Folder Name": "اسم المجلد", + "Loading...": "تحميل...", + "Delete {folderName}": "Delete {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "إذا قمت بحذف هذا المجلد، سوف نحتفظ بتدفقاته ونغير تصنيفها إلى \"غير مصنف\".", + "All flows": "كل التدفقات", + "Please enter a folder name": "يرجى إدخال اسم المجلد", + "Renamed flow successfully": "تم إعادة تسمية التدفق بنجاح", + "Folder name already used": "Folder name already used", + "New Folder Name": "اسم المجلد الجديد", + "Connected successfully": "تم الاتصال بنجاح", + "Connect Git": "توصيل Git", + "Remote URL": "رابط عن بعد", + "Folder name is the name of the folder where the project will be stored or fetched.": "اسم المجلد هو اسم المجلد حيث سيتم تخزين المشروع أو جلبه.", + "SSH Private Key": "مفتاح SSH الخاص", + "The SSH private key to use for authentication.": "مفتاح SSH الخاص لاستخدامه للمصادقة.", + "Only published flows can be pushed to Git": "Only published flows can be pushed to Git", + "Pushed successfully": "تم الرفع بنجاح", + "Invalid Operation": "Invalid Operation", + "Commit Message": "رسالة الالتزام", + "Enter a commit message to describe the changes you want to push.": "أدخل رسالة لوصف التغييرات التي تريد رفعها.", + "Push": "رفع", + "This field is required": "This field is required", + "Your submission was successfully received.": "تم استلام رسالتك بنجاح.", + "Flow not found": "لم يتم العثور على التدفق", + "The flow you are trying to submit to does not exist.": "التدفق الذي تحاول تسليمه غير موجود.", + "The flow failed to execute.": "فشل في تنفيذ التدفق.", + "Submit": "إرسال", + "issues-notification": "اشعارات المشاكل", + "Please select a package type": "الرجاء تحديد نوع الحزمة", + "package.json not found in archive": "package.json not found in archive", + "Error processing archive file": "Error processing archive file", + "Please upload a .tgz file": "Please upload a .tgz file", + "Piece name is required for NPM Registry": "Piece name is required for NPM Registry", + "Piece version is required for NPM Registry": "Piece version is required for NPM Registry", + "Piece installed": "تم تثبيت القطعة", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "A piece with this name and version is already installed. Please update the version number in package.json and try again.", + "Install Piece": "تثبيت القطعة", + "Install a piece": "تثبيت قطعة", + "Package Type": "نوع الحزمة", + "NPM Registry": "NPM Registry", + "Packed Archive (.tgz)": "أرشيف معبأ (.tgz)", + "Piece Version": "نسخة القطعة", + "Package Archive": "أرشيف الحزمة", + "Package archive": "أرشيف الحزمة", + "Powerful Node.js & TypeScript code with npm": "Powerful Node.js & TypeScript code with npm", + "Loop on Items": "Loop on Items", + "Split your flow into branches depending on condition(s)": "Split your flow into branches depending on condition(s)", + "Empty Trigger": "Empty Trigger", + "An internal error occurred while fetching data, please contact support": "An internal error occurred while fetching data, please contact support", + "An internal error occurred, please contact support": "An internal error occurred, please contact support", + "Custom Javascript Code": "Custom Javascript Code", + "Router": "Router", + "recordsCount": "{recordsCount, plural, =0 {0 Records} =1 {1 Record} other {# Records}}", + "selected": "selected", + "All records selected": "All records selected", + "fieldsCount": "{fieldsCount, plural, =0 {0 Fields} =1 {1 Field} other {# Fields}}", + "Saving...": "Saving...", + "Loading more...": "Loading more...", + "Export Table": "Export Table", + "Delete Records": "Delete Records", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Are you sure you want to delete the selected records? This action cannot be undone.", + "record": "record", + "records": "records", + "mm/dd/yyy": "mm/dd/yyy", + "Delete Field": "Delete Field", + "Are you sure you want to delete this field? This action cannot be undone.": "Are you sure you want to delete this field? This action cannot be undone.", + "field": "field", + "Ignored": "Ignored", + "Table": "Table", + "CSV": "CSV", + "Field": "Field", + "Please select a csv file": "Please select a csv file", + "Max file size is {maxFileSize}MB": "Max file size is {maxFileSize}MB", + "Import CSV": "Import CSV", + "Imported records will be added to the bottom of the table": "Imported records will be added to the bottom of the table", + "Any records after the limit ({maxRecords} records) will be ignored": "Any records after the limit ({maxRecords} records) will be ignored", + "CSV File": "CSV File", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support", + "Name must be unique": "Name must be unique", + "Type is required": "Type is required", + "Please add at least one option": "Please add at least one option", + "New Field": "New Field", + "Options": "Options", + "Name is already taken": "Name is already taken", + "Table renamed": "Table renamed", + "Table name": "Table name", + "Team Invitation Accepted": " تم قَبول دعوة الفريق ", + "Thank you for accepting the invitation. We are redirecting you right now...": " نشكرك على قَبول الدعوة. سيتم إعادة توجيهك الآن... ", + "Invalid invitation token. Please try again.": "رمز الدعوة غير صالح. يرجى المحاولة مرة أخرى.", + "Role updated successfully": "Role updated successfully", + "Error updating role": "Error updating role", + "Please try again later": "Please try again later", + "Edit Role for": "Edit Role for", + "Select Role": "Select Role", + "Avatar": "الصورة الرمزية", + "Remove {email}": "إزالة {email}", + "Are you sure you want to remove this invitation?": "هل أنت متأكد أنك تريد حذف هذه الدعوة؟", + "Please select invitation type": "الرجاء تحديد نوع الدعوة", + "Please select platform role": "الرجاء تحديد دور المنصة", + "Invitation sent successfully": "تم إرسال الدعوة بنجاح", + "Please select a project role": "Please select a project role", + "Invitation link copied successfully": "تم نسخ رابط الدعوة بنجاح", + "Invite User": "دعوة مستخدم", + "Invitation Link": "رابط الدعوة", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": " من فضلك قم بنسخ الرابط أدناه ومشاركته مع المستخدم الذي تريد دعوته، تنتهي صَلاحِيَة الدعوة خلال 24 ساعة. ", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": " اكتب عنوان البريد الإلكتروني للمستخدم الذي تريد دعوته، تنتهي صَلاحِيَة الدعوة خلال 24 ساعة. ", + "Invite To": "دعوة إلى", + "Entire Platform": "المنصة بأكملها", + "Select Project Role": "Select Project Role", + "Invite": "دعوة", + "Platform Role": "دور المنصة", + "Select a platform role": "اختر دور المنصة", + "Are you sure you want to remove this member?": "هل أنت متأكد من أنك تريد إزالة هذا العضو؟", + "Editor": "محرر", + "Operator": "مشغل", + "Viewer": "مشاهد", + "Select a project role": "اختر دور المشروع", + "Steps in this flow": "الخطوات في هذا التدفق", + "Invalid Access": "Invalid Access", + "Either the project does not exist or you do not have access to it.": "Either the project does not exist or you do not have access to it." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/bg/translation.json b/packages/react-ui/public/locales/bg/translation.json new file mode 100644 index 0000000..b2b2d88 --- /dev/null +++ b/packages/react-ui/public/locales/bg/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Publish", + "Latest version is published": "Latest version is published", + "Your flow has incomplete steps": "Your flow has incomplete steps", + "Edit Flow": "Edit Flow", + "View Draft": "View Draft", + "Uncategorized": "Uncategorized", + "Go to folder": "Go to folder", + "Support": "Support", + "Runs": "Runs", + "Run Logs": "Run Logs", + "Versions": "Versions", + "Versions History": "Versions History", + "Error generating code": "Error generating code", + "AI Copilot": "AI Copilot", + "i.e Calculate the sum of a list...": "i.e Calculate the sum of a list...", + "Send": "Send", + "Generating Code": "Generating Code", + "Hello there! I am here to generate code that helps with your flow": "Hello there! I am here to generate code that helps with your flow", + "Here are examples of what I am best used for: ": "Here are examples of what I am best used for: ", + "Text Processing": "Text Processing", + "Process strings, dates and data": "Process strings, dates and data", + "Data Operations": "Data Operations", + "Change data from one format to another": "Change data from one format to another", + "Calculations": "Calculations", + "Handle math and statistics": "Handle math and statistics", + "API Integration": "API Integration", + "Connect with external services. Best for simple integrations currently.": "Connect with external services. Best for simple integrations currently.", + "What would you like me to help you with?": "What would you like me to help you with?", + "Insert": "Insert", + "Data Selector": "Data Selector", + "Search": "Search", + "No matching data": "No matching data", + "Try adjusting your search": "Try adjusting your search", + "This trigger needs to have data loaded from your account, to use as sample data.": "This trigger needs to have data loaded from your account, to use as sample data.", + "This step needs to be tested in order to view its data.": "This step needs to be tested in order to view its data.", + "Go to Trigger": "Go to Trigger", + "Go to Step": "Go to Step", + "Select Mode": "Select Mode", + "Move Mode": "Move Mode", + "Reset Zoom": "Reset Zoom", + "Zoom In": "Zoom In", + "Zoom Out": "Zoom Out", + "Fit to View": "Fit to View", + "Replace": "Replace", + "Copy": "Copy", + "Duplicate": "Duplicate", + "Paste After Last Step": "Paste After Last Step", + "Paste Inside Loop": "Paste Inside Loop", + "Paste After": "Paste After", + "Paste Inside...": "Paste Inside...", + "New Branch": "New Branch", + "Paste Inside Branch": "Paste Inside Branch", + "Delete": "Delete", + "Duplicate Branch": "Duplicate Branch", + "Delete Branch": "Delete Branch", + "Invalid Move": "Invalid Move", + "The destination location is a child of the dragged step": "The destination location is a child of the dragged step", + "End": "End", + "Skipped": "Skipped", + "Incomplete settings": "Incomplete settings", + "logo": "logo", + "Step Icon": "Step Icon", + "Branch": "Branch", + "incompleteSteps": "{invalidSteps, plural, =0 {no incomplete steps} =1 {Complete 1 step} other {Complete # steps}}", + "Test Flow": "Test Flow", + "Please test the trigger first": "Please test the trigger first", + "View Only": "View Only", + "Use as Draft": "Use as Draft", + "Are you sure?": "Are you sure?", + "Your current draft version will be overwritten with": "Your current draft version will be overwritten with", + "version #": "version #", + "Cancel": "Cancel", + "Confirm": "Confirm", + "Version": "Version", + "Viewing": "Viewing", + "View": "View", + "Version History": "Version History", + "Error, please try again.": "Error, please try again.", + "Continue on Failure": "Continue on Failure", + "Enable this option to skip this step and continue the flow normally if it fails.": "Enable this option to skip this step and continue the flow normally if it fails.", + "Retry on Failure": "Retry on Failure", + "Automatically retry up to four attempts when failed.": "Automatically retry up to four attempts when failed.", + "Remove": "Remove", + "Add Item": "Add Item", + "File Input": "File Input", + "Date Input": "Date Input", + "Dynamic value": "Dynamic value", + "Select an option": "Select an option", + "Unexpected error, please retry": "Unexpected error, please retry", + "Unexpected error, please refresh the page or contact support": "Unexpected error, please refresh the page or contact support", + "Name can only contain letters, numbers and underscores": "Name can only contain letters, numbers and underscores", + "Ask AI": "Ask AI", + "Create Todo Guide": "Create Todo Guide", + "Where would you like the todo to be reviewed?": "Where would you like the todo to be reviewed?", + "Activepieces Todos": "Activepieces Todos", + "Users will manage tasks directly in Activepieces": "Users will manage tasks directly in Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.", + "External Channel (Slack, Teams, Email, ...)": "External Channel (Slack, Teams, Email, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.", + "Preview (Activepieces Todos)": "Preview (Activepieces Todos)", + "Preview (External channel)": "Preview (External channel)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "You can add the channel before the Wait Step, and configure the logic in the Router step", + "Add Steps": "Add Steps", + "All": "All", + "AI": "AI", + "Core": "Core", + "Apps": "Apps", + "Not available as trigger": "Not available as trigger", + "Not available as action": "Not available as action", + "Let our AI assistant help you out": "Let our AI assistant help you out", + "Or": "Or", + "Request Piece": "Request Piece", + "No pieces found": "No pieces found", + "Please select a piece first": "Please select a piece first", + "All Iterations": "All Iterations", + "Duration": "Duration", + "Input": "Input", + "Output": "Output", + "There are no logs captured for this run.": "There are no logs captured for this run.", + "Logs are kept for {days} days after execution and then deleted.": "Logs are kept for {days} days after execution and then deleted.", + "Run Details": "Run Details", + "Iteration": "Iteration", + "Done": "Done", + "Took": "Took", + "Running": "Running", + "on latest version": "on latest version", + "from failed step": "from failed step", + "Recent Runs": "Recent Runs", + "No runs found": "No runs found", + "Close": "Close", + "OR": "OR", + "And If": "And If", + "+ And": "+ And", + "+ Or": "+ Or", + "(Text) Contains": "(Text) Contains", + "(Text) Does not contain": "(Text) Does not contain", + "(Text) Exactly matches": "(Text) Exactly matches", + "(Text) Does not exactly match": "(Text) Does not exactly match", + "(Text) Starts with": "(Text) Starts with", + "(Text) Does not start with": "(Text) Does not start with", + "(Text) Ends with": "(Text) Ends with", + "(Text) Does not end with": "(Text) Does not end with", + "(List) Contains": "(List) Contains", + "(List) Does not contain": "(List) Does not contain", + "(Number) Is greater than": "(Number) Is greater than", + "(Number) Is less than": "(Number) Is less than", + "(Number) Is equal to": "(Number) Is equal to", + "(Date/time) After": "(Date/time) After", + "(Date/time) Before": "(Date/time) Before", + "(Date/time) Equals": "(Date/time) Equals", + "(Boolean) Is true": "(Boolean) Is true", + "(Boolean) Is false": "(Boolean) Is false", + "(List) Is empty": "(List) Is empty", + "(List) Is not empty": "(List) Is not empty", + "Exists": "Exists", + "Does not exist": "Does not exist", + "Incomplete condition": "Incomplete condition", + "First value": "First value", + "Second value": "Second value", + "Case sensitive": "Case sensitive", + "Execute If": "Execute If", + "The package name is required": "The package name is required", + "Success": "Success", + "Package added successfully": "Package added successfully", + "Could not fetch package version": "Could not fetch package version", + "Add NPM Package": "Add NPM Package", + "Type the name of the npm package you want to add.": "Type the name of the npm package you want to add.", + "Package Name": "Package Name", + "The latest version will be fetched and added": "The latest version will be fetched and added", + "Add": "Add", + "Code": "Code", + "Dependencies": "Dependencies", + "Use code": "Use code", + "Add package": "Add package", + "Inputs": "Inputs", + "Edit Step Name": "Edit Step Name", + "Edit Branch Name": "Edit Branch Name", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.", + "Items": "Items", + "Select an array of items": "Select an array of items", + "Reconnect": "Reconnect", + "Select a connection": "Select a connection", + "Create Connection": "Create Connection", + "Rename": "Rename", + "Move": "Move", + "Add Branch": "Add Branch", + "Execute": "Execute", + "Only the first (left) matching branch": "Only the first (left) matching branch", + "All matching paths from left to right": "All matching paths from left to right", + "Branches": "Branches", + "{field} is required": "{field} is required", + "Tool Sample Data": "Tool Sample Data", + "Fill in the following fields to use them as sample data for the trigger.": "Fill in the following fields to use them as sample data for the trigger.", + "No input fields defined in the schema": "No input fields defined in the schema", + "Save": "Save", + "Test Environment": "Test Environment", + "Assigned to": "Assigned to", + "(Me)": "(Me)", + "Please select status to resolve the todo": "Please select status to resolve the todo", + "Resolve": "Resolve", + "Change status to resolved": "Change status to resolved", + "Send Sample Data to Webhook": "Send Sample Data to Webhook", + "Method": "Method", + "Query Params": "Query Params", + "Headers": "Headers", + "Body": "Body", + "Type": "Type", + "JSON": "JSON", + "Text": "Text", + "Form Data": "Form Data", + "Generate Sample Data": "Generate Sample Data", + "Test Step": "Test Step", + "Retest": "Retest", + "Testing Failed": "Testing Failed", + "Tested Successfully": "Tested Successfully", + "Logs": "Logs", + "There is no sample data available found for this trigger.": "There is no sample data available found for this trigger.", + "Internal error, please try again later.": "Internal error, please try again later.", + "Failed to run test step and no error message was returned": "Failed to run test step and no error message was returned", + "Please fix inputs first": "Please fix inputs first", + "No sample data available": "No sample data available", + "Old results were removed, retest for new sample data": "Old results were removed, retest for new sample data", + "Result #": "Result #", + "The sample data can be used in the next steps.": "The sample data can be used in the next steps.", + "Testing Trigger": "Testing Trigger", + "Action Required": "Action Required", + "testPieceWebhookTriggerNote": "Please go to {pieceName} and trigger {triggerName}.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Send Data to the webhook URL to generate sample data to use in the next steps", + "Test Trigger": "Test Trigger", + "Use Mock Data": "Use Mock Data", + "Load Sample Data": "Load Sample Data", + "Test Tool": "Test Tool", + "home": "home", + "Home": "Home", + "Alerts": "Alerts", + "Releases": "Releases", + "Flows": "Flows", + "Products": "Products", + "MCP": "MCP", + "Tables": "Tables", + "Todos": "Todos", + "Push to Git": "Push to Git", + "Move To": "Move To", + "Duplicating": "Duplicating", + "Import": "Import", + "Exporting": "Exporting", + "Export": "Export", + "Share": "Share", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.", + "You are on a development branch, this will also delete the flow from the remote repository.": "You are on a development branch, this will also delete the flow from the remote repository.", + "flow": "flow", + "Community Support": "Community Support", + "Overview": "Overview", + "Projects": "Projects", + "Users": "Users", + "Setup": "Setup", + "Branding": "Branding", + "Global Connections": "Global Connections", + "Pieces": "Pieces", + "Templates": "Templates", + "License Key": "License Key", + "Security": "Security", + "Audit Logs": "Audit Logs", + "Single Sign On": "Single Sign On", + "Signing Keys": "Signing Keys", + "Project Roles": "Project Roles", + "API Keys": "API Keys", + "Infrastructure": "Infrastructure", + "Workers": "Workers", + "Health": "Health", + "Billing": "Billing", + "Settings": "Settings", + "Contact Sales": "Contact Sales", + "General": "General", + "Appearance": "Appearance", + "Team": "Team", + "Environments": "Environments", + "Project Settings": "Project Settings", + "Exit Platform Admin": "Exit Platform Admin", + "Enter Platform Admin": "Enter Platform Admin", + "Logout": "Logout", + "Misc": "Misc", + "Connections": "Connections", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "Today": "Today", + "Tasks": "Tasks", + "AI Credits": "AI Credits", + "Usage resets in": "Usage resets in", + "Manage": "Manage", + "Unlimited": "Unlimited", + "Enabled": "Enabled", + "Disabled": "Disabled", + "Could not claim the authorization code, make sure you have correct settings and try again.": "Could not claim the authorization code, make sure you have correct settings and try again.", + "Connection failed with error {msg}": "Connection failed with error {msg}", + "You don't have the permission to create a connection.": "You don't have the permission to create a connection.", + "Reconnect {displayName} Connection": "Reconnect {displayName}", + "Connect to {displayName}": "Connect to {displayName}", + "Connection Name": "Connection Name", + "Connection name": "Connection name", + "New Connection": "New Connection", + "Redirect URL": "Redirect URL", + "Client ID": "Client ID", + "Client Secret": "Client Secret", + "Connect": "Connect", + "Disconnect": "Disconnect", + "I would like to use my own App Credentials": "I would like to use my own App Credentials", + "I would like to use predefined App Credentials": "I would like to use predefined App Credentials", + "Permission needed": "Permission needed", + "Connections replaced successfully": "Connections replaced successfully", + "Error": "Error", + "Failed to replace connections": "Failed to replace connections", + "Failed to get affected flows": "Failed to get affected flows", + "Please select a piece": "Please select a piece", + "Please select a connection to replace": "Please select a connection to replace", + "Please select a connection to replace with": "Please select a connection to replace with", + "Replace Connections": "Replace Connections", + "Confirm Replacement": "Confirm Replacement", + "Replace one connection with another.": "Replace one connection with another.", + "This action requires ": "This action requires ", + "reconnecting": "reconnecting", + " any associated MCP pieces.": " any associated MCP pieces.", + "Piece": "Piece", + "Select a piece": "Select a piece", + "Connection to Replace": "Connection to Replace", + "Choose connection to replace": "Choose connection to replace", + "Replaced With": "Replaced With", + "Choose connection to replace with": "Choose connection to replace with", + "All flows will be changed to use the replaced with connection": "All flows will be changed to use the replaced with connection", + "Next": "Next", + "No flows will be affected by this change": "No flows will be affected by this change", + "Back": "Back", + "Unnamed tool": "Unnamed tool", + "This flow is enabled": "This flow is enabled", + "Enable this flow to make it available": "Enable this flow to make it available", + "Piece is updated successfully": "Piece is updated successfully", + "Piece is added successfully": "Piece is added successfully", + "Failed to update piece": "Failed to update piece", + "Failed to add piece": "Failed to add piece", + "Please select a connection": "Please select a connection", + "Your MCP server already has this piece": "Your MCP server already has this piece", + "+ New Connection": "+ New Connection", + "Edit Piece": "Edit Piece", + "Add Piece": "Add Piece", + "Connection": "Connection", + "MCP piece": "MCP piece", + "Failed to update piece status": "Failed to update piece status", + "Connection required": "Connection required", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.", + "piece": "piece", + "Connect your AI assistant to external services": "Connect your AI assistant to external services", + "Collapse": "Collapse", + "Show All": "Show All", + "Server URL": "Server URL", + "Hide the token for security": "Hide the token for security", + "Show the token": "Show the token", + "Generate a new token for security. This will invalidate the current URL.": "Generate a new token for security. This will invalidate the current URL.", + "URL copied to clipboard": "URL copied to clipboard", + "Copy URL to clipboard": "Copy URL to clipboard", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.", + "Max tables reached": "Max tables reached", + "You can't create more than {maxTables} tables": "You can't create more than {maxTables} tables", + "Name": "Name", + "Created": "Created", + "Delete Tables": "Delete Tables", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Are you sure you want to delete the selected tables? This action cannot be undone.", + "table": "table", + "Create and manage your tables to store your automation data": "Create and manage your tables to store your automation data", + "New Table": "New Table", + "No tables have been created yet": "No tables have been created yet", + "Create a table to get started and start managing your automation data": "Create a table to get started and start managing your automation data", + "Error deleting connections": "Error deleting connections", + "Status": "Status", + "Display Name": "Display Name", + "Owner": "Owner", + "App": "App", + "This connection is global and can be managed in the platform admin": "This connection is global and can be managed in the platform admin", + "External ID": "External ID", + "Connected At": "Connected At", + "Confirm Deletion": "Confirm Deletion", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Are you sure you want to delete the selected connections? This action cannot be undone.", + "Deleting connections may cause your Flows or MCP tools to break.": "Deleting connections may cause your Flows or MCP tools to break.", + "Manage project connections to external systems.": "Manage project connections to external systems.", + "No connections found": "No connections found", + "Come back later when you create a automation to manage your connections": "Come back later when you create a automation to manage your connections", + "Steps": "Steps", + "Folder": "Folder", + "Flow name": "Flow name", + "No flows found": "No flows found", + "Create a workflow to start automating": "Create a workflow to start automating", + "Create and manage your flows, run history and run issues": "Create and manage your flows, run history and run issues", + "Issues": "Issues", + "Untitled": "Untitled", + "Create flow": "Create flow", + "From scratch": "From scratch", + "Use a template": "Use a template", + "From local file": "From local file", + "Flow Name": "Flow Name", + "Count": "Count", + "First Seen": "First Seen", + "Last Seen": "Last Seen", + "Issues in {flowDisplayName} is marked as resolved.": "Issues in {flowDisplayName} is marked as resolved.", + "Unlock Issues": "Unlock Issues", + "Track issues in your workflows and troubleshoot them.": "Track issues in your workflows and troubleshoot them.", + "No issues found": "No issues found", + "All your workflows are running smoothly.": "All your workflows are running smoothly.", + "Mark as Resolved": "Mark as Resolved", + "All Flows Are Turned Off": "All Flows Are Turned Off", + "Task Usage Exceeded": "Task Usage Exceeded", + "of the Allowed Limit.": "of the Allowed Limit.", + "When a project tasks limit is reached,": "When a project tasks limit is reached,", + "all flows will be turned off and you will not be able to run any flows.": "all flows will be turned off and you will not be able to run any flows.", + "Please visit": "Please visit", + "Your Plan": "Your Plan", + "and increase your task limit, which requires your payment details.": "and increase your task limit, which requires your payment details.", + "Please contact your admin to increase the project task limit.": "Please contact your admin to increase the project task limit.", + "and increase the project task limit.": "and increase the project task limit.", + "Dismiss": "Dismiss", + "Token rotated successfully": "Token rotated successfully", + "Failed to rotate token": "Failed to rotate token", + "Piece removed successfully": "Piece removed successfully", + "Failed to remove piece": "Failed to remove piece", + "Flow created successfully": "Flow created successfully", + "Failed to create flow": "Failed to create flow", + "Add Flow": "Add Flow", + "Let your AI assistant trigger automations": "Let your AI assistant trigger automations", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Connect to your hosted MCP Server using any MCP client to communicate with tools", + "MCP Server": "MCP Server", + "My Tools": "My Tools", + "Create Flow": "Create Flow", + "Note": "Note", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.", + "This URL grants access to your tools and data. Only share with trusted applications.": "This URL grants access to your tools and data. Only share with trusted applications.", + "Server Configuration": "Server Configuration", + "Hide sensitive data": "Hide sensitive data", + "Show sensitive data": "Show sensitive data", + "Create a new URL. The current one will stop working.": "Create a new URL. The current one will stop working.", + "Copy configuration": "Copy configuration", + "Configuration copied to clipboard": "Configuration copied to clipboard", + "Claude": "Claude", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "Server/Other", + "Note: MCPs only work with": "Note: MCPs only work with", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", not the web version.", + "Prerequisites:": "Prerequisites:", + "Install": "Install", + "Node.js": "Node.js", + "and": "and", + "Open Settings:": "Open Settings:", + "Click the menu and select": "Click the menu and select", + "Developer": "Developer", + "Configure MCP:": "Configure MCP:", + "Click": "Click", + "Edit Config": "Edit Config", + "and paste the configuration below": "and paste the configuration below", + "Save and Restart:": "Save and Restart:", + "Save the config and restart Claude Desktop": "Save the config and restart Claude Desktop", + "Navigate to": "Navigate to", + "Cursor Settings": "Cursor Settings", + "Add Server:": "Add Server:", + "Add new global MCP server": "Add new global MCP server", + "Configure:": "Configure:", + "Paste the configuration below and save": "Paste the configuration below and save", + "Use either method:": "Use either method:", + "Go to": "Go to", + "Advanced Settings": "Advanced Settings", + "Open Command Palette and select": "Open Command Palette and select", + "Windsurf Settings Page": "Windsurf Settings Page", + "Navigate to Cascade:": "Navigate to Cascade:", + "Select": "Select", + "Cascade": "Cascade", + "in the sidebar": "in the sidebar", + "Add Server": "Add Server", + "Add custom server +": "Add custom server +", + "Copy URL": "Copy URL", + "Client Setup Instructions": "Client Setup Instructions", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "After changing connections or flows, reconnect your MCP server for changes to take effect.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.", + "icon": "icon", + "Unlock Analytics": "Unlock Analytics", + "Get insights into your platform usage and performance with our analytics dashboard": "Get insights into your platform usage and performance with our analytics dashboard", + "Active Flows": "Active Flows", + "The number of enabled flows in the platform": "The number of enabled flows in the platform", + "Active Projects": "Active Projects", + "The number of projects with at least one enabled flow": "The number of projects with at least one enabled flow", + "Active Users": "Active Users", + "The number of users logged in the last 30 days": "The number of users logged in the last 30 days", + "Out of {totalusers} total users": "Out of {totalusers} total users", + "Pieces Used": "Pieces Used", + "The number of unique pieces used across all flows": "The number of unique pieces used across all flows", + "Flows with AI": "Flows with AI", + "The number of enabled flows that use AI pieces": "The number of enabled flows that use AI pieces", + "Metrics": "Metrics", + "Executed Tasks": "Executed Tasks", + "Showing total executed tasks for specified time range": "Showing total executed tasks for specified time range", + "Tasks Usage Limit": "Tasks Usage Limit", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.", + "Number of monthly tasks": "Number of monthly tasks", + "Save changes": "Save changes", + "Limits updated successfully": "Limits updated successfully", + "Failed to update limits": "Failed to update limits", + "Failed to load billing information": "Failed to load billing information", + "Billing Amount": "Billing Amount", + "Manage Payment Details": "Manage Payment Details", + "Add Payment Details": "Add Payment Details", + "Current Task Usage": "Current Task Usage", + "Count of executed steps": "Count of executed steps", + "Billing Limit": "Billing Limit", + "Edit": "Edit", + "Add Limit": "Add Limit", + "Current Credit Usage": "Current Credit Usage", + "WebSocket Connection": "WebSocket Connection", + "Connected": "Connected", + "Disconnected": "Disconnected", + "No issues detected": "No issues detected", + "Check the status of your platform and its components": "Check the status of your platform and its components", + "System Health Status": "System Health Status", + "All systems are running smoothly": "All systems are running smoothly", + "Check the health of your worker machines": "Check the health of your worker machines", + "Workers Machine": "Workers Machine", + "This is demo data. In a real environment, this would show your actual worker machines.": "This is demo data. In a real environment, this would show your actual worker machines.", + "No workers found": "No workers found", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "You don't have any worker machines yet. Spin up new machines to execute your automations", + "IP Address": "IP Address", + "CPU Usage": "CPU Usage", + "Disk Usage": "Disk Usage", + "RAM Usage": "RAM Usage", + "Last Contact": "Last Contact", + "Configs": "Configs", + "Environment Variables": "Environment Variables", + "Websocket Connection Error": "Websocket Connection Error", + "Retry Connection": "Retry Connection", + "Update Available": "Update Available", + "Update Now": "Update Now", + "Your Universal AI needs a quick setup": "Your Universal AI needs a quick setup", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.", + "Configure": "Configure", + "Platform Alerts": "Platform Alerts", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.", + "View Alerts": "View Alerts", + "Used Tasks": "Used Tasks", + "Used AI Credits": "Used AI Credits", + "Cannot delete active project, switch to another project first": "Cannot delete active project, switch to another project first", + "Delete Projects": "Delete Projects", + "Are you sure you want to delete the selected projects?": "Are you sure you want to delete the selected projects?", + "New Project": "New Project", + "Validation error": "Validation error", + "Project has enabled flows. Please disable them first.": "Project has enabled flows. Please disable them first.", + "This project is active. Please switch to another project first.": "This project is active. Please switch to another project first.", + "Unlock Projects": "Unlock Projects", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Orchestrate your automation teams across projects with their own flows, connections and usage quotas", + "Manage your automation projects": "Manage your automation projects", + "No projects found": "No projects found", + "Start by creating projects to manage your automation teams": "Start by creating projects to manage your automation teams", + "Edit project": "Edit project", + "Name is required": "Name is required", + "Create New Project": "Create New Project", + "Project Name": "Project Name", + "Id": "Id", + "Enable API Keys": "Enable API Keys", + "Create and manage API keys to access Activepieces APIs.": "Create and manage API keys to access Activepieces APIs.", + "New Api Key": "New Api Key", + "No API keys found": "No API keys found", + "Start by creating an API key to communicate with Activepieces APIs": "Start by creating an API key to communicate with Activepieces APIs", + "Delete API Key": "Delete API Key", + "Are you sure you want to delete this API key?": "Are you sure you want to delete this API key?", + "API Key": "API Key", + "API Key Created": "API Key Created", + "Create New API Key": "Create New API Key", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Please save this secret key somewhere safe and accessible. For security reasons,", + "you won't be able to view it again after closing this dialog.": "you won't be able to view it again after closing this dialog.", + "API Key Name": "API Key Name", + "Action": "Action", + "Performed By": "Performed By", + "Project": "Project", + "Unlock Audit Logs": "Unlock Audit Logs", + "Comply with internal and external security policies by tracking activities done within your account": "Comply with internal and external security policies by tracking activities done within your account", + "Track activities done within your platform": "Track activities done within your platform", + "No audit logs found": "No audit logs found", + "Come back later when you have some activity to audit": "Come back later when you have some activity to audit", + "Resource": "Resource", + "Details": "Details", + "N/A": "N/A", + "Flow Run": "Flow Run", + "Flow": "Flow", + "User": "User", + "Signing Key": "Signing Key", + "Project Role Management": "Project Role Management", + "Define custom roles and permissions to control what your team members can access and modify": "Define custom roles and permissions to control what your team members can access and modify", + "Define custom roles and permissions that can be assigned to your team members": "Define custom roles and permissions that can be assigned to your team members", + "New Role": "New Role", + "Contact sales to unlock custom roles": "Contact sales to unlock custom roles", + "Create New Role": "Create New Role", + "View ": "View ", + "Edit ": "Edit ", + "Role Name": "Role Name", + "Permissions": "Permissions", + "None": "None", + "Read": "Read", + "Write": "Write", + "Create": "Create", + "Email": "Email", + "First Name": "First Name", + "Last Name": "Last Name", + "Roles": "Roles", + "View the users assigned to this role": "View the users assigned to this role", + "Role": "Role", + "No users found": "No users found", + "Starting by assigning users to this role": "Starting by assigning users to this role", + "Project Role entry deleted successfully": "Project Role entry deleted successfully", + "Updated": "Updated", + "No project roles found": "No project roles found", + "Create custom project roles to manage permissions for platform users": "Create custom project roles to manage permissions for platform users", + "Show Users": "Show Users", + "View Role": "View Role", + "Edit Role": "Edit Role", + "Delete Role": "Delete Role", + "Project Role": "Project Role", + "Unlock Embedding Through JS SDK": "Unlock Embedding Through JS SDK", + "Enable signing keys to access embedding functionalities.": "Enable signing keys to access embedding functionalities.", + "New Signing Key": "New Signing Key", + "No signing keys found": "No signing keys found", + "Create a signing key to authenticate users with embedding": "Create a signing key to authenticate users with embedding", + "Delete Signing Key": "Delete Signing Key", + "Are you sure you want to delete this signing key?": "Are you sure you want to delete this signing key?", + "Signing Key Created": "Signing Key Created", + "Create New Signing Key": "Create New Signing Key", + "Signing Key Name": "Signing Key Name", + "Allowed domains updated": "Allowed domains updated", + "Update": "Update", + "Enable": "Enable", + "Configure Allowed Domains": "Configure Allowed Domains", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.", + "Add Domain": "Add Domain", + "Allow logins through {providerName}'s single sign-on functionality.": "Allow logins through {providerName}'s single sign-on functionality.", + "Email authentication updated": "Email authentication updated", + "Enable Single Sign On": "Enable Single Sign On", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Let your users sign in with your current SSO provider or give them self serve sign up access", + "Allowed Domains": "Allowed Domains", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Allow users to authenticate with specific domains. Leave empty to allow all domains.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Allowed Email Login", + "Allow logins through email and password.": "Allow logins through email and password.", + "Single sign on settings updated": "Single sign on settings updated", + "Disable": "Disable", + "Configure {provider} SSO": "Configure {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} Client ID", + "{provider} Client Secret": "{provider} Client Secret", + "Single sign-on settings updated": "Single sign-on settings updated", + "Configure SAML 2.0 SSO": "Configure SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDP Metadata", + "IDP Certificate": "IDP Certificate", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "Base URL", + "Resource Name": "Resource Name", + "Deployment Name": "Deployment Name", + "Saving": "Saving", + "Activepieces Copilot": "Activepieces Copilot", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot is configured and ready to help your users build flows faster using AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configure Activepieces Copilot to help your users build flows faster using AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Set provider credentials that will be used by universal AI pieces, i.e Text AI.", + "AI Providers": "AI Providers", + "Copilot": "Copilot", + "Configure credentials for {providerName} AI provider.": "Configure credentials for {providerName} AI provider.", + "Update AI Provider": "Update AI Provider", + "Enable AI Provider": "Enable AI Provider", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Please enter a valid domain", + "Your changes have been saved.": "Your changes have been saved.", + "The domain is already added.": "The domain is already added.", + "Add Custom Domain": "Add Custom Domain", + "Enter a domain name without a protocol (e.g. example.com)": "Enter a domain name without a protocol (e.g. example.com)", + "Logo URL": "Logo URL", + "Icon URL": "Icon URL", + "Favicon URL": "Favicon URL", + "Default Language": "Default Language", + "Select Language": "Select Language", + "No Languages": "No Languages", + "Primary Color": "Primary Color", + "Custom Domains": "Custom Domains", + "No domains added yet.": "No domains added yet.", + "Verified": "Verified", + "Pending, please contact the support for dns verification.": "Pending, please contact the support for dns verification.", + "Are you sure you want to delete {domain}?": "Are you sure you want to delete {domain}?", + "Brand Activepieces": "Brand Activepieces", + "Give your users an experience that looks like you by customizing the color, logo and more": "Give your users an experience that looks like you by customizing the color, logo and more", + "Configure the appearance and SMTP settings for your platform.": "Configure the appearance and SMTP settings for your platform.", + "Invalid host": "Invalid host", + "Invalid username": "Invalid username", + "Invalid password": "Invalid password", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Invalid sender name", + "SMTP is configured": "SMTP is configured", + "Mail Server": "Mail Server", + "Set up your SMTP settings to send emails from your domain.": "Set up your SMTP settings to send emails from your domain.", + "Disable Mail Server": "Disable Mail Server", + "Are you sure you want to disable your mail server?": "Are you sure you want to disable your mail server?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "This will stop you from sending emails for issues, quota limits, invitations and forgot password.", + "mail server": "mail server", + "Host": "Host", + "Port": "Port", + "Username": "Username", + "Password": "Password", + "Sender Email": "Sender Email", + "Sender Name": "Sender Name", + "Enable Global Connections": "Enable Global Connections", + "Manage platform-wide connections to external systems.": "Manage platform-wide connections to external systems.", + "No global connections found": "No global connections found", + "Create a global connection that can be shared to multiple projects": "Create a global connection that can be shared to multiple projects", + "License key is invalid": "License key is invalid", + "Invalid license key": "Invalid license key", + "License activated!": "License activated!", + "Activate License Key": "Activate License Key", + "Let the magic begin!": "Let the magic begin!", + "Activate": "Activate", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ", + "This feature is not available in your current edition. ": "This feature is not available in your current edition. ", + "Learn how to upgrade": "Learn how to upgrade", + "Activate your platform and unlock enterprise features": "Activate your platform and unlock enterprise features", + "Activate License": "Activate License", + "Expiration": "Expiration", + "Valid until": "Valid until", + "Expired": "Expired", + "Expires soon": "Expires soon", + "Features": "Features", + "Applying Tags...": "Applying Tags...", + "Tags applied.": "Tags applied.", + "Tag created": "Tag created", + "Tag": "Tag", + "Tags": "Tags", + "Control Pieces": "Control Pieces", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Show the pieces that matter most to your users and hide the ones you don't like.", + "Manage the pieces that are available to your users": "Manage the pieces that are available to your users", + "Start by installing pieces that you want to use in your automations": "Start by installing pieces that you want to use in your automations", + "Piece Name": "Piece Name", + "Hide this piece from all projects": "Hide this piece from all projects", + "Show this piece for all projects": "Show this piece for all projects", + "Unpin this piece": "Unpin this piece", + "Pin this piece": "Pin this piece", + "Pieces synced": "Pieces synced", + "Pieces have been synced from the activepieces cloud.": "Pieces have been synced from the activepieces cloud.", + "OAuth2 Credentials Deleted": "OAuth2 Credentials Deleted", + "OAuth2 Credentials Updated": "OAuth2 Credentials Updated", + "Configure OAuth2 APP": "Configure OAuth2 APP", + "Delete OAuth2 APP": "Delete OAuth2 APP", + "Templates deleted successfully": "Templates deleted successfully", + "Delete Templates": "Delete Templates", + "Are you sure you want to delete the selected templates?": "Are you sure you want to delete the selected templates?", + "New Template": "New Template", + "Unlock Templates": "Unlock Templates", + "Convert the most common automations into reusable templates 1 click away from your users": "Convert the most common automations into reusable templates 1 click away from your users", + "Convert the most common automations into reusable templates": "Convert the most common automations into reusable templates", + "No templates found": "No templates found", + "Create a template for your user to inspire them": "Create a template for your user to inspire them", + "Edit template": "Edit template", + "Template is required": "Template is required", + "Update New Template": "Update New Template", + "Create New Template": "Create New Template", + "Template Name": "Template Name", + "Description": "Description", + "Template Description": "Template Description", + "Blog URL": "Blog URL", + "Template Blog URL": "Template Blog URL", + "Template": "Template", + "Invalid JSON": "Invalid JSON", + "User deleted successfully": "User deleted successfully", + "User activated successfully": "User activated successfully", + "User deactivated successfully": "User deactivated successfully", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Manage your users and their access to your projects", + "Start inviting users to your project": "Start inviting users to your project", + "External Id": "External Id", + "Admin": "Admin", + "Member": "Member", + "Activated": "Activated", + "Deactivated": "Deactivated", + "Edit user": "Edit user", + "Admin cannot be deactivated": "Admin cannot be deactivated", + "Deactivate user": "Deactivate user", + "Activate user": "Activate user", + "Delete User": "Delete User", + "Are you sure you want to delete this user?": "Are you sure you want to delete this user?", + "Delete user": "Delete user", + "Update User Role": "Update User Role", + "Meeting Summary Flow": "Meeting Summary Flow", + "Added new features and fixed bugs": "Added new features and fixed bugs", + "Flows Changes": "Flows Changes", + "Connections Changes": "Connections Changes", + "New connections are placeholders and need to be reconnected again": "New connections are placeholders and need to be reconnected again", + "renamed to": "renamed to", + "Tables Changes": "Tables Changes", + "No changes to apply": "No changes to apply", + "Apply Changes": "Apply Changes", + "Create Git Release": "Create Git Release", + "Create Project Release": "Create Project Release", + "Create Rollback to": "Create Rollback to", + "Source": "Source", + "Rollback": "Rollback", + "Imported At": "Imported At", + "Imported By": "Imported By", + "Track and manage your project version history and deployments. ": "Track and manage your project version history and deployments. ", + "Environments & Releases": "Environments & Releases", + "Project Releases": "Project Releases", + "Create Release": "Create Release", + "From Git": "From Git", + "From Project": "From Project", + "No project releases found": "No project releases found", + "Create a project release to get started": "Create a project release to get started", + "Please select project": "Please select project", + "No Changes Found": "No Changes Found", + "There are no differences to apply": "There are no differences to apply", + "Please select a project": "Please select a project", + "Search projects...": "Search projects...", + "Review Changes": "Review Changes", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Imported by", + "from": "from", + "No description provided": "No description provided", + "Invitation only sign up": "Invitation only sign up", + "Please ask your administrator to add you to the organization.": "Please ask your administrator to add you to the organization.", + "Something went wrong, please try again.": "Something went wrong, please try again.", + "Please try again.": "Please try again.", + "Please enter a valid email address": "Please enter a valid email address", + "The email is already added.": "The email is already added.", + "Add email": "Add email", + "Only project admins can do this": "Only project admins can do this", + "Add Alert Email": "Add Alert Email", + "Enter the email address to receive alerts.": "Enter the email address to receive alerts.", + "Add Email": "Add Email", + "Emails": "Emails", + "Add email addresses to receive alerts.": "Add email addresses to receive alerts.", + "No emails added yet.": "No emails added yet.", + "Choose what you want to be notified about.": "Choose what you want to be notified about.", + "Project and alert permissions are required to change this setting.": "Project and alert permissions are required to change this setting.", + "Every Failed Run": "Every Failed Run", + "Get an email alert when a flow fails.": "Get an email alert when a flow fails.", + "Get an email alert when a new issue created.": "Get an email alert when a new issue created.", + "Never": "Never", + "Turn off email notifications.": "Turn off email notifications.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Customize the appearance of the app. Automatically switch between day and night themes.", + "Select the theme for the dashboard.": "Select the theme for the dashboard.", + "Light": "Light", + "Dark": "Dark", + "Select the language that will be used in the dashboard.": "Select the language that will be used in the dashboard.", + "Select language": "Select language", + "Search language...": "Search language...", + "No language found.": "No language found.", + "Help us translate Activepieces to your language.": "Help us translate Activepieces to your language.", + "Learn more": "Learn more", + "Git Connection Removed": "Git Connection Removed", + "Your Git repository has been successfully disconnected": "Your Git repository has been successfully disconnected", + "Enable Environments": "Enable Environments", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Deploy flows across development, staging and production environments with version control and team collaboration", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Connect to Git to enable version control, backup your flows, and manage multiple environments. ", + "Repository URL": "Repository URL", + "Not connected": "Not connected", + "Project Folder": "Project Folder", + "Releases Enabled": "Releases Enabled", + "You have successfully enabled releases": "You have successfully enabled releases", + "Enable releases to easily create and manage project releases.": "Enable releases to easily create and manage project releases.", + "The external ID is already taken.": "The external ID is already taken.", + "Manage general settings for your project.": "Manage general settings for your project.", + "Used to identify the project based on your SaaS ID": "Used to identify the project based on your SaaS ID", + "org-3412321": "org-3412321", + "Delete {name}": "Delete {name}", + "This will permanently delete this piece, all steps using it will fail.": "This will permanently delete this piece, all steps using it will fail.", + "Add a piece to your project that you want to use in your automations": "Add a piece to your project that you want to use in your automations", + "Pieces list updated": "Pieces list updated", + "Manage Pieces": "Manage Pieces", + "Choose which pieces you want to be available for your current project users": "Choose which pieces you want to be available for your current project users", + "Unlock Team Permissions": "Unlock Team Permissions", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial", + "Project Members": "Project Members", + "Invite your team members to collaborate.": "Invite your team members to collaborate.", + "No members are added to this project.": "No members are added to this project.", + "Pending Invitations": "Pending Invitations", + "No pending invitation.": "No pending invitation.", + "templateId is missing": "templateId is missing", + "Me Only": "Me Only", + "Unresolved": "Unresolved", + "Resolved": "Resolved", + "Title": "Title", + "Date Created": "Date Created", + "Manage todos for your project that are created by automations": "Manage todos for your project that are created by automations", + "No todos found": "No todos found", + "You do not have any pending todos. Great job!": "You do not have any pending todos. Great job!", + "Write a comment...": "Write a comment...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "This feature is still under testing and might be changed often", + "Failed to copy to clipboard": "Failed to copy to clipboard", + "{number} items selected": "{number} items selected", + "Select All": "Select All", + "No results found.": "No results found.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect must be used within MultiSelectProvider", + "Unset": "Unset", + "Refresh": "Refresh", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} more", + "Removed {entityName}": "Removed {entityName}", + "Download File": "Download File", + "Copied to clipboard": "Copied to clipboard", + "File is not available after execution.": "File is not available after execution.", + "Available for Projects": "Available for Projects", + "Select projects": "Select projects", + "No items": "No items", + "Previous": "Previous", + "to": "to", + "Last Week": "Last Week", + "Last Month": "Last Month", + "Last 3 Months": "Last 3 Months", + "Last 6 Months": "Last 6 Months", + "Next 7 days": "Next 7 days", + "Next 30 days": "Next 30 days", + "Next 90 days": "Next 90 days", + "Next 180 days": "Next 180 days", + "Select Time Range": "Select Time Range", + "Clear": "Clear", + "Download": "Download", + "Go to Dashboard": "Go to Dashboard", + "Select a file": "Select a file", + "Press space to separate values": "Press space to separate values", + "AM": "AM", + "PM": "PM", + "Already have an account?": "Already have an account?", + "Sign in": "Sign in", + "Don't have an account?": "Don't have an account?", + "Sign up": "Sign up", + "Welcome Back!": "Welcome Back!", + "Enter your email below to sign in to your account": "Enter your email below to sign in to your account", + "Let's Get Started!": "Let's Get Started!", + "Create your account and start flowing!": "Create your account and start flowing!", + "Your password was changed successfully": "Your password was changed successfully", + "Your password reset request has expired, please request a new one": "Your password reset request has expired, please request a new one", + "Reset Password": "Reset Password", + "Enter your new password": "Enter your new password", + "Password is required": "Password is required", + "Verification email resent, if previous one expired.": "Verification email resent, if previous one expired.", + "Password reset link resent, if previous one expired.": "Password reset link resent, if previous one expired.", + "We sent you a link to complete your registration to": "We sent you a link to complete your registration to", + "We sent you a link to reset your password to": "We sent you a link to reset your password to", + "Didn't receive an email or it expired?": "Didn't receive an email or it expired?", + "Resend": "Resend", + "Please enter your email": "Please enter your email", + "Check Your Inbox": "Check Your Inbox", + "If the user exists we'll send you an email with a link to reset your password.": "If the user exists we'll send you an email with a link to reset your password.", + "Send Password Reset Link": "Send Password Reset Link", + "Back to sign in": "Back to sign in", + "Email is invalid": "Email is invalid", + "Something went wrong, please try again later": "Something went wrong, please try again later", + "Invalid email or password": "Invalid email or password", + "User has been deactivated": "User has been deactivated", + "Email domain is disallowed": "Email domain is disallowed", + "Email authentication has been disabled": "Email authentication has been disabled", + "Forgot your password?": "Forgot your password?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "Sign up is restricted. You need an invitation to join. Please contact the administrator.", + "Email is already used": "Email is already used", + "Email authentication is disabled": "Email authentication is disabled", + "First name is required": "First name is required", + "Last name is required": "Last name is required", + "Email is required": "Email is required", + "Receive updates and newsletters from activepieces": "Receive updates and newsletters from activepieces", + "By creating an account, you agree to our": "By creating an account, you agree to our", + "terms of service": "terms of service", + "privacy policy": "privacy policy", + "Sign up With": "Sign up With", + "Google": "Google", + "Sign in With": "Sign in With", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "Email has been verified. You will be redirected to sign in...", + "Verifying email...": "Verifying email...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "invitation has expired, once you sign in again you will be able to resend the verification email.", + "Redirecting to sign in...": "Redirecting to sign in...", + "Password must contain at least one special character": "Password must contain at least one special character", + "Password must contain at least one lowercase letter": "Password must contain at least one lowercase letter", + "Password must contain at least one uppercase letter": "Password must contain at least one uppercase letter", + "Password must contain at least one number": "Password must contain at least one number", + "8-64 Characters": "8-64 Characters", + "Special Character": "Special Character", + "Lowercase": "Lowercase", + "Uppercase": "Uppercase", + "Number": "Number", + "Connection has been updated.": "Connection has been updated.", + "Edit Global Connection": "Edit Global Connection", + "Connection has been renamed.": "Connection has been renamed.", + "New Connection Name": "New Connection Name", + "Connection name already used": "Connection name already used", + "Please select at least one project": "Please select at least one project", + "Run Succeeded": "Run Succeeded", + "Run Failed": "Run Failed", + "Flow Run is paused": "Flow Run is paused", + "Run Failed due to quota exceeded": "Run Failed due to quota exceeded", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Run failed due to exceeding the memory limit of {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "Run exceeded {timeout} seconds, try to optimize your steps.", + "Run failed for an unknown reason, contact support.": "Run failed for an unknown reason, contact support.", + "Unknown": "Unknown", + "Exit Run": "Exit Run", + "Select shown": "Select shown", + "Select all": "Select all", + "Start Time": "Start Time", + "Runs replayed successfully": "Runs replayed successfully", + "Retry": "Retry", + "all except": "all except", + "all": "all", + "Only failed runs can be retried from failed step": "Only failed runs can be retried from failed step", + "No flow runs found": "No flow runs found", + "Come back later when your automations start running": "Come back later when your automations start running", + "Step running": "Step running", + "Step paused": "Step paused", + "Step Stopped": "Step Stopped", + "Step Succeeded": "Step Succeeded", + "Step Failed": "Step Failed", + "Please publish flow first": "Please publish flow first", + "Flow is on": "Flow is on", + "Flow is off": "Flow is off", + "Permission Needed": "Permission Needed", + "Draft Version": "Draft Version", + "Published Version": "Published Version", + "Locked Version": "Locked Version", + "flowsImported": "{flowsCount, plural, =0 {No Flows Imported} =1 {Flow Imported Successfully} other {Flows Imported Successfully}}", + "Template file is invalid": "Template file is invalid", + "No valid templates found. The following files failed to import: ": "No valid templates found. The following files failed to import: ", + "Please select a file first": "Please select a file first", + "Unsupported file type": "Unsupported file type", + "Import Flow": "Import Flow", + "Warning": "Warning", + "Importing a flow will overwrite your current one.": "Importing a flow will overwrite your current one.", + "Select a folder": "Select a folder", + "Folders": "Folders", + "Please select a folder": "Please select a folder", + "Moved flows successfully": "Moved flows successfully", + "Move Selected Flows": "Move Selected Flows", + "Select Folder": "Select Folder", + "No Folders": "No Folders", + "Flow has been renamed.": "Flow has been renamed.", + "New Flow Name": "New Flow Name", + "Use Template": "Use Template", + "Browse Templates": "Browse Templates", + "Search templates": "Search templates", + "No templates found, try adjusting your search": "No templates found, try adjusting your search", + "Read more about this template in": "Read more about this template in", + "this blog!": "this blog!", + "Share Template": "Share Template", + "Generate or update a template link for the current flow to easily share it with others.": "Generate or update a template link for the current flow to easily share it with others.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "The template will not have any credentials in connection fields, keeping sensitive information secure.", + "A short description of the template": "A short description of the template", + "Flow Is In Use": "Flow Is In Use", + "Flow is being used by another user, please try again later.": "Flow is being used by another user, please try again later.", + "Flow has been published.": "Flow has been published.", + "Flows have been exported.": "Flows have been exported.", + "Run": "Run", + "Real time flow": "Real time flow", + "Flow can't be published with empty trigger {name}": "Flow can't be published with empty trigger {name}", + "Please contact support as your published flow has a problem": "Please contact support as your published flow has a problem", + "Actions": "Actions", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.", + "You are on a development branch, this will not delete the flows from the remote repository.": "You are on a development branch, this will not delete the flows from the remote repository.", + "Please enter folder name": "Please enter folder name", + "Added folder successfully": "Added folder successfully", + "The folder name already exists.": "The folder name already exists.", + "New Folder": "New Folder", + "Folder Name": "Folder Name", + "Loading...": "Loading...", + "Delete {folderName}": "Delete {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "If you delete this folder, we will keep its flows and move them to Uncategorized.", + "All flows": "All flows", + "Please enter a folder name": "Please enter a folder name", + "Renamed flow successfully": "Renamed flow successfully", + "Folder name already used": "Folder name already used", + "New Folder Name": "New Folder Name", + "Connected successfully": "Connected successfully", + "Connect Git": "Connect Git", + "Remote URL": "Remote URL", + "Folder name is the name of the folder where the project will be stored or fetched.": "Folder name is the name of the folder where the project will be stored or fetched.", + "SSH Private Key": "SSH Private Key", + "The SSH private key to use for authentication.": "The SSH private key to use for authentication.", + "Only published flows can be pushed to Git": "Only published flows can be pushed to Git", + "Pushed successfully": "Pushed successfully", + "Invalid Operation": "Invalid Operation", + "Commit Message": "Commit Message", + "Enter a commit message to describe the changes you want to push.": "Enter a commit message to describe the changes you want to push.", + "Push": "Push", + "This field is required": "This field is required", + "Your submission was successfully received.": "Your submission was successfully received.", + "Flow not found": "Flow not found", + "The flow you are trying to submit to does not exist.": "The flow you are trying to submit to does not exist.", + "The flow failed to execute.": "The flow failed to execute.", + "Submit": "Submit", + "issues-notification": "issues-notification", + "Please select a package type": "Please select a package type", + "package.json not found in archive": "package.json not found in archive", + "Error processing archive file": "Error processing archive file", + "Please upload a .tgz file": "Please upload a .tgz file", + "Piece name is required for NPM Registry": "Piece name is required for NPM Registry", + "Piece version is required for NPM Registry": "Piece version is required for NPM Registry", + "Piece installed": "Piece installed", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "A piece with this name and version is already installed. Please update the version number in package.json and try again.", + "Install Piece": "Install Piece", + "Install a piece": "Install a piece", + "Package Type": "Package Type", + "NPM Registry": "NPM Registry", + "Packed Archive (.tgz)": "Packed Archive (.tgz)", + "Piece Version": "Piece Version", + "Package Archive": "Package Archive", + "Package archive": "Package archive", + "Powerful Node.js & TypeScript code with npm": "Powerful Node.js & TypeScript code with npm", + "Loop on Items": "Loop on Items", + "Split your flow into branches depending on condition(s)": "Split your flow into branches depending on condition(s)", + "Empty Trigger": "Empty Trigger", + "An internal error occurred while fetching data, please contact support": "An internal error occurred while fetching data, please contact support", + "An internal error occurred, please contact support": "An internal error occurred, please contact support", + "Custom Javascript Code": "Custom Javascript Code", + "Router": "Router", + "recordsCount": "{recordsCount, plural, =0 {0 Records} =1 {1 Record} other {# Records}}", + "selected": "selected", + "All records selected": "All records selected", + "fieldsCount": "{fieldsCount, plural, =0 {0 Fields} =1 {1 Field} other {# Fields}}", + "Saving...": "Saving...", + "Loading more...": "Loading more...", + "Export Table": "Export Table", + "Delete Records": "Delete Records", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Are you sure you want to delete the selected records? This action cannot be undone.", + "record": "record", + "records": "records", + "mm/dd/yyy": "mm/dd/yyy", + "Delete Field": "Delete Field", + "Are you sure you want to delete this field? This action cannot be undone.": "Are you sure you want to delete this field? This action cannot be undone.", + "field": "field", + "Ignored": "Ignored", + "Table": "Table", + "CSV": "CSV", + "Field": "Field", + "Please select a csv file": "Please select a csv file", + "Max file size is {maxFileSize}MB": "Max file size is {maxFileSize}MB", + "Import CSV": "Import CSV", + "Imported records will be added to the bottom of the table": "Imported records will be added to the bottom of the table", + "Any records after the limit ({maxRecords} records) will be ignored": "Any records after the limit ({maxRecords} records) will be ignored", + "CSV File": "CSV File", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support", + "Name must be unique": "Name must be unique", + "Type is required": "Type is required", + "Please add at least one option": "Please add at least one option", + "New Field": "New Field", + "Options": "Options", + "Name is already taken": "Name is already taken", + "Table renamed": "Table renamed", + "Table name": "Table name", + "Team Invitation Accepted": "Team Invitation Accepted", + "Thank you for accepting the invitation. We are redirecting you right now...": "Thank you for accepting the invitation. We are redirecting you right now...", + "Invalid invitation token. Please try again.": "Invalid invitation token. Please try again.", + "Role updated successfully": "Role updated successfully", + "Error updating role": "Error updating role", + "Please try again later": "Please try again later", + "Edit Role for": "Edit Role for", + "Select Role": "Select Role", + "Avatar": "Avatar", + "Remove {email}": "Remove {email}", + "Are you sure you want to remove this invitation?": "Are you sure you want to remove this invitation?", + "Please select invitation type": "Please select invitation type", + "Please select platform role": "Please select platform role", + "Invitation sent successfully": "Invitation sent successfully", + "Please select a project role": "Please select a project role", + "Invitation link copied successfully": "Invitation link copied successfully", + "Invite User": "Invite User", + "Invitation Link": "Invitation Link", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Type the email address of the user you want to invite, the invitation expires in 24 hours.", + "Invite To": "Invite To", + "Entire Platform": "Entire Platform", + "Select Project Role": "Select Project Role", + "Invite": "Invite", + "Platform Role": "Platform Role", + "Select a platform role": "Select a platform role", + "Are you sure you want to remove this member?": "Are you sure you want to remove this member?", + "Editor": "Editor", + "Operator": "Operator", + "Viewer": "Viewer", + "Select a project role": "Select a project role", + "Steps in this flow": "Steps in this flow", + "Invalid Access": "Invalid Access", + "Either the project does not exist or you do not have access to it.": "Either the project does not exist or you do not have access to it." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/ca/translation.json b/packages/react-ui/public/locales/ca/translation.json new file mode 100644 index 0000000..2f8230d --- /dev/null +++ b/packages/react-ui/public/locales/ca/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Publicar", + "Latest version is published": "S'ha publicat la versió més recent", + "Your flow has incomplete steps": "Your flow has incomplete steps", + "Edit Flow": "Editar flux", + "View Draft": "View Draft", + "Uncategorized": "Sense categoria", + "Go to folder": "Anar a la carpeta", + "Support": "Suport", + "Runs": "Execucions", + "Run Logs": "Registres d'execució", + "Versions": "Versions", + "Versions History": "Versions History", + "Error generating code": "Error en generar el codi", + "AI Copilot": "Copilot IA", + "i.e Calculate the sum of a list...": "i.e Calculate the sum of a list...", + "Send": "Enviar", + "Generating Code": "Generating Code", + "Hello there! I am here to generate code that helps with your flow": "Hello there! I am here to generate code that helps with your flow", + "Here are examples of what I am best used for: ": "Here are examples of what I am best used for: ", + "Text Processing": "Text Processing", + "Process strings, dates and data": "Process strings, dates and data", + "Data Operations": "Data Operations", + "Change data from one format to another": "Change data from one format to another", + "Calculations": "Calculations", + "Handle math and statistics": "Handle math and statistics", + "API Integration": "API Integration", + "Connect with external services. Best for simple integrations currently.": "Connect with external services. Best for simple integrations currently.", + "What would you like me to help you with?": "What would you like me to help you with?", + "Insert": "Inserir", + "Data Selector": "Selector de dades", + "Search": "Cercar", + "No matching data": "No s'han trobat dades coincidents", + "Try adjusting your search": "Prova d'ajustar la cerca", + "This trigger needs to have data loaded from your account, to use as sample data.": "Aquest desencadenant necessita carregar dades del teu compte per utilitzar-les com a dades de mostra.", + "This step needs to be tested in order to view its data.": "Aquest pas s'ha de provar per veure les seves dades.", + "Go to Trigger": "Anar al desencadenant", + "Go to Step": "Anar al pas", + "Select Mode": "Select Mode", + "Move Mode": "Move Mode", + "Reset Zoom": "Restablir zoom", + "Zoom In": "Ampliar", + "Zoom Out": "Reduir", + "Fit to View": "Ajustar a la vista", + "Replace": "Replace", + "Copy": "Copiar", + "Duplicate": "Duplicar", + "Paste After Last Step": "Paste After Last Step", + "Paste Inside Loop": "Paste Inside Loop", + "Paste After": "Paste After", + "Paste Inside...": "Paste Inside...", + "New Branch": "New Branch", + "Paste Inside Branch": "Paste Inside Branch", + "Delete": "Eliminar", + "Duplicate Branch": "Duplicate Branch", + "Delete Branch": "Delete Branch", + "Invalid Move": "Moviment invàlid", + "The destination location is a child of the dragged step": "The destination location is a child of the dragged step", + "End": "Final", + "Skipped": "Skipped", + "Incomplete settings": "Configuració incompleta", + "logo": "logo", + "Step Icon": "Icona del pas", + "Branch": "Branca", + "incompleteSteps": "{invalidSteps, plural, one {}=0 {no hi ha passos incomplets} =1 {Completa 1 pas} other {Completa # passos}}", + "Test Flow": "Provar flux", + "Please test the trigger first": "Si us plau, prova primer el desencadenant", + "View Only": "Només lectura", + "Use as Draft": "Utilitzar com a esborrany", + "Are you sure?": "Estàs segur?", + "Your current draft version will be overwritten with": "La teva versió d'esborrany actual serà sobreescrita amb", + "version #": "versió #", + "Cancel": "Cancel·lar", + "Confirm": "Confirmar", + "Version": "Versió", + "Viewing": "Visualitzant", + "View": "Veure", + "Version History": "Historial de versions", + "Error, please try again.": "Error, si us plau, torna-ho a provar.", + "Continue on Failure": "Continuar en cas de fallada", + "Enable this option to skip this step and continue the flow normally if it fails.": "Activa aquesta opció per ometre aquest pas i continuar el flux normalment si falla.", + "Retry on Failure": "Reintentar en cas de fallada", + "Automatically retry up to four attempts when failed.": "Reintentar automàticament fins a quatre intents quan falli.", + "Remove": "Eliminar", + "Add Item": "Afegir element", + "File Input": "File Input", + "Date Input": "Date Input", + "Dynamic value": "Dynamic value", + "Select an option": "Selecciona una opció", + "Unexpected error, please retry": "Unexpected error, please retry", + "Unexpected error, please refresh the page or contact support": "Unexpected error, please refresh the page or contact support", + "Name can only contain letters, numbers and underscores": "Name can only contain letters, numbers and underscores", + "Ask AI": "Preguntar a la IA", + "Create Todo Guide": "Create Todo Guide", + "Where would you like the todo to be reviewed?": "Where would you like the todo to be reviewed?", + "Activepieces Todos": "Activepieces Todos", + "Users will manage tasks directly in Activepieces": "Users will manage tasks directly in Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.", + "External Channel (Slack, Teams, Email, ...)": "External Channel (Slack, Teams, Email, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.", + "Preview (Activepieces Todos)": "Preview (Activepieces Todos)", + "Preview (External channel)": "Preview (External channel)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "You can add the channel before the Wait Step, and configure the logic in the Router step", + "Add Steps": "Add Steps", + "All": "All", + "AI": "AI", + "Core": "Core", + "Apps": "Apps", + "Not available as trigger": "Not available as trigger", + "Not available as action": "Not available as action", + "Let our AI assistant help you out": "Let our AI assistant help you out", + "Or": "Or", + "Request Piece": "Request Piece", + "No pieces found": "No s'han trobat peces", + "Please select a piece first": "Please select a piece first", + "All Iterations": "Totes les iteracions", + "Duration": "Durada", + "Input": "Entrada", + "Output": "Sortida", + "There are no logs captured for this run.": "There are no logs captured for this run.", + "Logs are kept for {days} days after execution and then deleted.": "Logs are kept for {days} days after execution and then deleted.", + "Run Details": "Detalls d'execució", + "Iteration": "Iteració", + "Done": "Fet", + "Took": "Duració", + "Running": "Executant", + "on latest version": "on latest version", + "from failed step": "from failed step", + "Recent Runs": "Execucions recents", + "No runs found": "No s'han trobat execucions", + "Close": "Tancar", + "OR": "O", + "And If": "I si", + "+ And": "+ I", + "+ Or": "+ O", + "(Text) Contains": "(Text) Conté", + "(Text) Does not contain": "(Text) No conté", + "(Text) Exactly matches": "(Text) Coincideix exactament", + "(Text) Does not exactly match": "(Text) No coincideix exactament", + "(Text) Starts with": "(Text) Comença amb", + "(Text) Does not start with": "(Text) No comença amb", + "(Text) Ends with": "(Text) Acaba amb", + "(Text) Does not end with": "(Text) No acaba amb", + "(List) Contains": "(List) Contains", + "(List) Does not contain": "(List) Does not contain", + "(Number) Is greater than": "(Nombre) És més gran que", + "(Number) Is less than": "(Nombre) És més petit que", + "(Number) Is equal to": "(Nombre) És igual a", + "(Date/time) After": "(Date/time) After", + "(Date/time) Before": "(Date/time) Before", + "(Date/time) Equals": "(Date/time) Equals", + "(Boolean) Is true": "(Booleà) És cert", + "(Boolean) Is false": "(Booleà) És fals", + "(List) Is empty": "(List) Is empty", + "(List) Is not empty": "(List) Is not empty", + "Exists": "Existeix", + "Does not exist": "No existeix", + "Incomplete condition": "Incomplete condition", + "First value": "Primer valor", + "Second value": "Segon valor", + "Case sensitive": "Distingeix majúscules i minúscules", + "Execute If": "Execute If", + "The package name is required": "El nom del paquet és obligatori", + "Success": "Èxit", + "Package added successfully": "Paquet afegit correctament", + "Could not fetch package version": "No s'ha pogut obtenir la versió del paquet", + "Add NPM Package": "Afegir paquet NPM", + "Type the name of the npm package you want to add.": "Escriu el nom del paquet npm que vols afegir.", + "Package Name": "Nom del paquet", + "The latest version will be fetched and added": "S'obtindrà i s'afegirà la versió més recent", + "Add": "Afegir", + "Code": "Codi", + "Dependencies": "Dependències", + "Use code": "Use code", + "Add package": "Afegir paquet", + "Inputs": "Entrades", + "Edit Step Name": "Editar el nom del pas", + "Edit Branch Name": "Edit Branch Name", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Selecciona els elements per iterar des del pas anterior fent clic a l'entrada **Elements**, que hauria de ser una **llista** d'elements.\n\nEl bucle iterarà sobre cada element de la llista i executarà el següent pas per a cada element.", + "Items": "Elements", + "Select an array of items": "Selecciona una matriu d'elements", + "Reconnect": "Reconnect", + "Select a connection": "Selecciona una connexió", + "Create Connection": "Crear connexió", + "Rename": "Reanomenar", + "Move": "Moure", + "Add Branch": "Add Branch", + "Execute": "Execute", + "Only the first (left) matching branch": "Only the first (left) matching branch", + "All matching paths from left to right": "All matching paths from left to right", + "Branches": "Branches", + "{field} is required": "{field} is required", + "Tool Sample Data": "Tool Sample Data", + "Fill in the following fields to use them as sample data for the trigger.": "Fill in the following fields to use them as sample data for the trigger.", + "No input fields defined in the schema": "No input fields defined in the schema", + "Save": "Desar", + "Test Environment": "Test Environment", + "Assigned to": "Assigned to", + "(Me)": "(Me)", + "Please select status to resolve the todo": "Please select status to resolve the todo", + "Resolve": "Resolve", + "Change status to resolved": "Change status to resolved", + "Send Sample Data to Webhook": "Send Sample Data to Webhook", + "Method": "Method", + "Query Params": "Query Params", + "Headers": "Headers", + "Body": "Body", + "Type": "Type", + "JSON": "JSON", + "Text": "Text", + "Form Data": "Form Data", + "Generate Sample Data": "Generar dades de mostra", + "Test Step": "Pas de prova", + "Retest": "Tornar a provar", + "Testing Failed": "La prova ha fallat", + "Tested Successfully": "Provat amb èxit", + "Logs": "Logs", + "There is no sample data available found for this trigger.": "No hi ha dades de mostra disponibles per a aquest desencadenant.", + "Internal error, please try again later.": "Internal error, please try again later.", + "Failed to run test step and no error message was returned": "No s'ha pogut executar el pas de prova i no s'ha retornat cap missatge d'error", + "Please fix inputs first": "Si us plau, corregeix les entrades primer", + "No sample data available": "No sample data available", + "Old results were removed, retest for new sample data": "Old results were removed, retest for new sample data", + "Result #": "Resultat #", + "The sample data can be used in the next steps.": "Les dades de mostra es poden utilitzar en els següents passos.", + "Testing Trigger": "Provant desencadenant", + "Action Required": "Action Required", + "testPieceWebhookTriggerNote": "Si us plau, ves a {pieceName} i activa {triggerName}.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Send Data to the webhook URL to generate sample data to use in the next steps", + "Test Trigger": "Provar desencadenant", + "Use Mock Data": "Use Mock Data", + "Load Sample Data": "Carregar dades de mostra", + "Test Tool": "Test Tool", + "home": "inici", + "Home": "Inici", + "Alerts": "Alertes", + "Releases": "Releases", + "Flows": "Fluxos", + "Products": "Products", + "MCP": "MCP", + "Tables": "Tables", + "Todos": "Todos", + "Push to Git": "Pujar a Git", + "Move To": "Moure a", + "Duplicating": "Duplicant", + "Import": "Importar", + "Exporting": "Exportant", + "Export": "Exportar", + "Share": "Compartir", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Estàs segur que vols eliminar aquest flux? Això eliminarà permanentment el flux, totes les seves dades i qualsevol execució en segon pla.", + "You are on a development branch, this will also delete the flow from the remote repository.": "You are on a development branch, this will also delete the flow from the remote repository.", + "flow": "flux", + "Community Support": "Community Support", + "Overview": "Overview", + "Projects": "Projectes", + "Users": "Usuaris", + "Setup": "Setup", + "Branding": "Marca", + "Global Connections": "Global Connections", + "Pieces": "Peces", + "Templates": "Plantilles", + "License Key": "License Key", + "Security": "Security", + "Audit Logs": "Registres d'auditoria", + "Single Sign On": "Inici de sessió únic", + "Signing Keys": "Claus de signatura", + "Project Roles": "Project Roles", + "API Keys": "Claus API", + "Infrastructure": "Infrastructure", + "Workers": "Workers", + "Health": "Health", + "Billing": "Billing", + "Settings": "Configuració", + "Contact Sales": "Contact Sales", + "General": "General", + "Appearance": "Aparença", + "Team": "Equip", + "Environments": "Environments", + "Project Settings": "Project Settings", + "Exit Platform Admin": "Exit Platform Admin", + "Enter Platform Admin": "Enter Platform Admin", + "Logout": "Logout", + "Misc": "Misc", + "Connections": "Connexions", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "Today": "Today", + "Tasks": "Tasques", + "AI Credits": "AI Credits", + "Usage resets in": "Usage resets in", + "Manage": "Manage", + "Unlimited": "Unlimited", + "Enabled": "Enabled", + "Disabled": "Disabled", + "Could not claim the authorization code, make sure you have correct settings and try again.": "No s'ha pogut reclamar el codi d'autorització, assegura't que tens la configuració correcta i torna-ho a intentar.", + "Connection failed with error {msg}": "La connexió ha fallat amb l'error {msg}", + "You don't have the permission to create a connection.": "You don't have the permission to create a connection.", + "Reconnect {displayName} Connection": "Reconnectar {displayName}", + "Connect to {displayName}": "Connect to {displayName}", + "Connection Name": "Nom de la connexió", + "Connection name": "Nom de la connexió", + "New Connection": "Nova connexió", + "Redirect URL": "Redirect URL", + "Client ID": "ID de client", + "Client Secret": "Secret de client", + "Connect": "Connectar", + "Disconnect": "Desconnectar", + "I would like to use my own App Credentials": "M'agradaria utilitzar les meves pròpies credencials d'aplicació", + "I would like to use predefined App Credentials": "M'agradaria utilitzar credencials d'aplicació predefinides", + "Permission needed": "Permission needed", + "Connections replaced successfully": "Connections replaced successfully", + "Error": "Error", + "Failed to replace connections": "Failed to replace connections", + "Failed to get affected flows": "Failed to get affected flows", + "Please select a piece": "Please select a piece", + "Please select a connection to replace": "Please select a connection to replace", + "Please select a connection to replace with": "Please select a connection to replace with", + "Replace Connections": "Replace Connections", + "Confirm Replacement": "Confirm Replacement", + "Replace one connection with another.": "Replace one connection with another.", + "This action requires ": "This action requires ", + "reconnecting": "reconnecting", + " any associated MCP pieces.": " any associated MCP pieces.", + "Piece": "Peça", + "Select a piece": "Select a piece", + "Connection to Replace": "Connection to Replace", + "Choose connection to replace": "Choose connection to replace", + "Replaced With": "Replaced With", + "Choose connection to replace with": "Choose connection to replace with", + "All flows will be changed to use the replaced with connection": "All flows will be changed to use the replaced with connection", + "Next": "Next", + "No flows will be affected by this change": "No flows will be affected by this change", + "Back": "Back", + "Unnamed tool": "Unnamed tool", + "This flow is enabled": "This flow is enabled", + "Enable this flow to make it available": "Enable this flow to make it available", + "Piece is updated successfully": "Piece is updated successfully", + "Piece is added successfully": "Piece is added successfully", + "Failed to update piece": "Failed to update piece", + "Failed to add piece": "Failed to add piece", + "Please select a connection": "Please select a connection", + "Your MCP server already has this piece": "Your MCP server already has this piece", + "+ New Connection": "+ New Connection", + "Edit Piece": "Edit Piece", + "Add Piece": "Add Piece", + "Connection": "Connexió", + "MCP piece": "MCP piece", + "Failed to update piece status": "Failed to update piece status", + "Connection required": "Connection required", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.", + "piece": "piece", + "Connect your AI assistant to external services": "Connect your AI assistant to external services", + "Collapse": "Collapse", + "Show All": "Show All", + "Server URL": "Server URL", + "Hide the token for security": "Hide the token for security", + "Show the token": "Show the token", + "Generate a new token for security. This will invalidate the current URL.": "Generate a new token for security. This will invalidate the current URL.", + "URL copied to clipboard": "URL copied to clipboard", + "Copy URL to clipboard": "Copy URL to clipboard", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.", + "Max tables reached": "Max tables reached", + "You can't create more than {maxTables} tables": "You can't create more than {maxTables} tables", + "Name": "Nom", + "Created": "Creat", + "Delete Tables": "Delete Tables", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Are you sure you want to delete the selected tables? This action cannot be undone.", + "table": "table", + "Create and manage your tables to store your automation data": "Create and manage your tables to store your automation data", + "New Table": "New Table", + "No tables have been created yet": "No tables have been created yet", + "Create a table to get started and start managing your automation data": "Create a table to get started and start managing your automation data", + "Error deleting connections": "Error deleting connections", + "Status": "Estat", + "Display Name": "Nom visible", + "Owner": "Owner", + "App": "Aplicació", + "This connection is global and can be managed in the platform admin": "This connection is global and can be managed in the platform admin", + "External ID": "External ID", + "Connected At": "Connected At", + "Confirm Deletion": "Confirm Deletion", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Are you sure you want to delete the selected connections? This action cannot be undone.", + "Deleting connections may cause your Flows or MCP tools to break.": "Deleting connections may cause your Flows or MCP tools to break.", + "Manage project connections to external systems.": "Manage project connections to external systems.", + "No connections found": "No connections found", + "Come back later when you create a automation to manage your connections": "Come back later when you create a automation to manage your connections", + "Steps": "Passos", + "Folder": "Carpeta", + "Flow name": "Nom del flux", + "No flows found": "No flows found", + "Create a workflow to start automating": "Create a workflow to start automating", + "Create and manage your flows, run history and run issues": "Create and manage your flows, run history and run issues", + "Issues": "Problemes", + "Untitled": "Sense títol", + "Create flow": "Create flow", + "From scratch": "Des de zero", + "Use a template": "Utilitzar una plantilla", + "From local file": "From local file", + "Flow Name": "Nom del flux", + "Count": "Quantitat", + "First Seen": "Primera aparició", + "Last Seen": "Última visualització", + "Issues in {flowDisplayName} is marked as resolved.": "Issues in {flowDisplayName} is marked as resolved.", + "Unlock Issues": "Unlock Issues", + "Track issues in your workflows and troubleshoot them.": "Track issues in your workflows and troubleshoot them.", + "No issues found": "No issues found", + "All your workflows are running smoothly.": "All your workflows are running smoothly.", + "Mark as Resolved": "Marcar com a resolt", + "All Flows Are Turned Off": "All Flows Are Turned Off", + "Task Usage Exceeded": "Task Usage Exceeded", + "of the Allowed Limit.": "of the Allowed Limit.", + "When a project tasks limit is reached,": "When a project tasks limit is reached,", + "all flows will be turned off and you will not be able to run any flows.": "all flows will be turned off and you will not be able to run any flows.", + "Please visit": "Please visit", + "Your Plan": "Your Plan", + "and increase your task limit, which requires your payment details.": "and increase your task limit, which requires your payment details.", + "Please contact your admin to increase the project task limit.": "Please contact your admin to increase the project task limit.", + "and increase the project task limit.": "and increase the project task limit.", + "Dismiss": "Dismiss", + "Token rotated successfully": "Token rotated successfully", + "Failed to rotate token": "Failed to rotate token", + "Piece removed successfully": "Piece removed successfully", + "Failed to remove piece": "Failed to remove piece", + "Flow created successfully": "Flow created successfully", + "Failed to create flow": "Failed to create flow", + "Add Flow": "Add Flow", + "Let your AI assistant trigger automations": "Let your AI assistant trigger automations", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Connect to your hosted MCP Server using any MCP client to communicate with tools", + "MCP Server": "MCP Server", + "My Tools": "My Tools", + "Create Flow": "Create Flow", + "Note": "Note", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.", + "This URL grants access to your tools and data. Only share with trusted applications.": "This URL grants access to your tools and data. Only share with trusted applications.", + "Server Configuration": "Server Configuration", + "Hide sensitive data": "Hide sensitive data", + "Show sensitive data": "Show sensitive data", + "Create a new URL. The current one will stop working.": "Create a new URL. The current one will stop working.", + "Copy configuration": "Copy configuration", + "Configuration copied to clipboard": "Configuration copied to clipboard", + "Claude": "Claude", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "Server/Other", + "Note: MCPs only work with": "Note: MCPs only work with", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", not the web version.", + "Prerequisites:": "Prerequisites:", + "Install": "Instal·lar", + "Node.js": "Node.js", + "and": "i", + "Open Settings:": "Open Settings:", + "Click the menu and select": "Click the menu and select", + "Developer": "Developer", + "Configure MCP:": "Configure MCP:", + "Click": "Click", + "Edit Config": "Edit Config", + "and paste the configuration below": "and paste the configuration below", + "Save and Restart:": "Save and Restart:", + "Save the config and restart Claude Desktop": "Save the config and restart Claude Desktop", + "Navigate to": "Navigate to", + "Cursor Settings": "Cursor Settings", + "Add Server:": "Add Server:", + "Add new global MCP server": "Add new global MCP server", + "Configure:": "Configure:", + "Paste the configuration below and save": "Paste the configuration below and save", + "Use either method:": "Use either method:", + "Go to": "Go to", + "Advanced Settings": "Advanced Settings", + "Open Command Palette and select": "Open Command Palette and select", + "Windsurf Settings Page": "Windsurf Settings Page", + "Navigate to Cascade:": "Navigate to Cascade:", + "Select": "Select", + "Cascade": "Cascade", + "in the sidebar": "in the sidebar", + "Add Server": "Add Server", + "Add custom server +": "Add custom server +", + "Copy URL": "Copy URL", + "Client Setup Instructions": "Client Setup Instructions", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "After changing connections or flows, reconnect your MCP server for changes to take effect.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.", + "icon": "icon", + "Unlock Analytics": "Unlock Analytics", + "Get insights into your platform usage and performance with our analytics dashboard": "Get insights into your platform usage and performance with our analytics dashboard", + "Active Flows": "Active Flows", + "The number of enabled flows in the platform": "The number of enabled flows in the platform", + "Active Projects": "Active Projects", + "The number of projects with at least one enabled flow": "The number of projects with at least one enabled flow", + "Active Users": "Active Users", + "The number of users logged in the last 30 days": "The number of users logged in the last 30 days", + "Out of {totalusers} total users": "Out of {totalusers} total users", + "Pieces Used": "Pieces Used", + "The number of unique pieces used across all flows": "The number of unique pieces used across all flows", + "Flows with AI": "Flows with AI", + "The number of enabled flows that use AI pieces": "The number of enabled flows that use AI pieces", + "Metrics": "Metrics", + "Executed Tasks": "Executed Tasks", + "Showing total executed tasks for specified time range": "Showing total executed tasks for specified time range", + "Tasks Usage Limit": "Tasks Usage Limit", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.", + "Number of monthly tasks": "Number of monthly tasks", + "Save changes": "Save changes", + "Limits updated successfully": "Limits updated successfully", + "Failed to update limits": "Failed to update limits", + "Failed to load billing information": "Failed to load billing information", + "Billing Amount": "Billing Amount", + "Manage Payment Details": "Manage Payment Details", + "Add Payment Details": "Add Payment Details", + "Current Task Usage": "Current Task Usage", + "Count of executed steps": "Count of executed steps", + "Billing Limit": "Billing Limit", + "Edit": "Edit", + "Add Limit": "Add Limit", + "Current Credit Usage": "Current Credit Usage", + "WebSocket Connection": "WebSocket Connection", + "Connected": "Connected", + "Disconnected": "Disconnected", + "No issues detected": "No issues detected", + "Check the status of your platform and its components": "Check the status of your platform and its components", + "System Health Status": "System Health Status", + "All systems are running smoothly": "All systems are running smoothly", + "Check the health of your worker machines": "Check the health of your worker machines", + "Workers Machine": "Workers Machine", + "This is demo data. In a real environment, this would show your actual worker machines.": "This is demo data. In a real environment, this would show your actual worker machines.", + "No workers found": "No workers found", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "You don't have any worker machines yet. Spin up new machines to execute your automations", + "IP Address": "IP Address", + "CPU Usage": "CPU Usage", + "Disk Usage": "Disk Usage", + "RAM Usage": "RAM Usage", + "Last Contact": "Last Contact", + "Configs": "Configs", + "Environment Variables": "Environment Variables", + "Websocket Connection Error": "Websocket Connection Error", + "Retry Connection": "Retry Connection", + "Update Available": "Update Available", + "Update Now": "Update Now", + "Your Universal AI needs a quick setup": "Your Universal AI needs a quick setup", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.", + "Configure": "Configure", + "Platform Alerts": "Platform Alerts", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.", + "View Alerts": "View Alerts", + "Used Tasks": "Used Tasks", + "Used AI Credits": "Used AI Credits", + "Cannot delete active project, switch to another project first": "Cannot delete active project, switch to another project first", + "Delete Projects": "Delete Projects", + "Are you sure you want to delete the selected projects?": "Are you sure you want to delete the selected projects?", + "New Project": "New Project", + "Validation error": "Validation error", + "Project has enabled flows. Please disable them first.": "Project has enabled flows. Please disable them first.", + "This project is active. Please switch to another project first.": "This project is active. Please switch to another project first.", + "Unlock Projects": "Unlock Projects", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Orchestrate your automation teams across projects with their own flows, connections and usage quotas", + "Manage your automation projects": "Manage your automation projects", + "No projects found": "No projects found", + "Start by creating projects to manage your automation teams": "Start by creating projects to manage your automation teams", + "Edit project": "Edit project", + "Name is required": "El nom és obligatori", + "Create New Project": "Create New Project", + "Project Name": "Nom del projecte", + "Id": "Id", + "Enable API Keys": "Enable API Keys", + "Create and manage API keys to access Activepieces APIs.": "Create and manage API keys to access Activepieces APIs.", + "New Api Key": "New Api Key", + "No API keys found": "No API keys found", + "Start by creating an API key to communicate with Activepieces APIs": "Start by creating an API key to communicate with Activepieces APIs", + "Delete API Key": "Delete API Key", + "Are you sure you want to delete this API key?": "Are you sure you want to delete this API key?", + "API Key": "API Key", + "API Key Created": "API Key Created", + "Create New API Key": "Create New API Key", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Please save this secret key somewhere safe and accessible. For security reasons,", + "you won't be able to view it again after closing this dialog.": "you won't be able to view it again after closing this dialog.", + "API Key Name": "API Key Name", + "Action": "Action", + "Performed By": "Performed By", + "Project": "Project", + "Unlock Audit Logs": "Unlock Audit Logs", + "Comply with internal and external security policies by tracking activities done within your account": "Comply with internal and external security policies by tracking activities done within your account", + "Track activities done within your platform": "Track activities done within your platform", + "No audit logs found": "No audit logs found", + "Come back later when you have some activity to audit": "Come back later when you have some activity to audit", + "Resource": "Resource", + "Details": "Details", + "N/A": "N/A", + "Flow Run": "Flow Run", + "Flow": "Flux", + "User": "User", + "Signing Key": "Signing Key", + "Project Role Management": "Project Role Management", + "Define custom roles and permissions to control what your team members can access and modify": "Define custom roles and permissions to control what your team members can access and modify", + "Define custom roles and permissions that can be assigned to your team members": "Define custom roles and permissions that can be assigned to your team members", + "New Role": "New Role", + "Contact sales to unlock custom roles": "Contact sales to unlock custom roles", + "Create New Role": "Create New Role", + "View ": "View ", + "Edit ": "Edit ", + "Role Name": "Role Name", + "Permissions": "Permissions", + "None": "None", + "Read": "Read", + "Write": "Write", + "Create": "Crear", + "Email": "Correu electrònic", + "First Name": "Nom", + "Last Name": "Cognom", + "Roles": "Roles", + "View the users assigned to this role": "View the users assigned to this role", + "Role": "Role", + "No users found": "No users found", + "Starting by assigning users to this role": "Starting by assigning users to this role", + "Project Role entry deleted successfully": "Project Role entry deleted successfully", + "Updated": "Actualitzat", + "No project roles found": "No project roles found", + "Create custom project roles to manage permissions for platform users": "Create custom project roles to manage permissions for platform users", + "Show Users": "Show Users", + "View Role": "View Role", + "Edit Role": "Edit Role", + "Delete Role": "Delete Role", + "Project Role": "Rol del projecte", + "Unlock Embedding Through JS SDK": "Unlock Embedding Through JS SDK", + "Enable signing keys to access embedding functionalities.": "Enable signing keys to access embedding functionalities.", + "New Signing Key": "New Signing Key", + "No signing keys found": "No signing keys found", + "Create a signing key to authenticate users with embedding": "Create a signing key to authenticate users with embedding", + "Delete Signing Key": "Delete Signing Key", + "Are you sure you want to delete this signing key?": "Are you sure you want to delete this signing key?", + "Signing Key Created": "Signing Key Created", + "Create New Signing Key": "Create New Signing Key", + "Signing Key Name": "Signing Key Name", + "Allowed domains updated": "Allowed domains updated", + "Update": "Update", + "Enable": "Enable", + "Configure Allowed Domains": "Configure Allowed Domains", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.", + "Add Domain": "Add Domain", + "Allow logins through {providerName}'s single sign-on functionality.": "Allow logins through {providerName}'s single sign-on functionality.", + "Email authentication updated": "Email authentication updated", + "Enable Single Sign On": "Enable Single Sign On", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Let your users sign in with your current SSO provider or give them self serve sign up access", + "Allowed Domains": "Allowed Domains", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Allow users to authenticate with specific domains. Leave empty to allow all domains.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Allowed Email Login", + "Allow logins through email and password.": "Allow logins through email and password.", + "Single sign on settings updated": "Single sign on settings updated", + "Disable": "Disable", + "Configure {provider} SSO": "Configure {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} Client ID", + "{provider} Client Secret": "{provider} Client Secret", + "Single sign-on settings updated": "Single sign-on settings updated", + "Configure SAML 2.0 SSO": "Configure SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDP Metadata", + "IDP Certificate": "IDP Certificate", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "Base URL", + "Resource Name": "Resource Name", + "Deployment Name": "Deployment Name", + "Saving": "Saving", + "Activepieces Copilot": "Activepieces Copilot", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot is configured and ready to help your users build flows faster using AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configure Activepieces Copilot to help your users build flows faster using AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Set provider credentials that will be used by universal AI pieces, i.e Text AI.", + "AI Providers": "AI Providers", + "Copilot": "Copilot", + "Configure credentials for {providerName} AI provider.": "Configure credentials for {providerName} AI provider.", + "Update AI Provider": "Update AI Provider", + "Enable AI Provider": "Enable AI Provider", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Please enter a valid domain", + "Your changes have been saved.": "Els teus canvis s'han guardat.", + "The domain is already added.": "The domain is already added.", + "Add Custom Domain": "Add Custom Domain", + "Enter a domain name without a protocol (e.g. example.com)": "Enter a domain name without a protocol (e.g. example.com)", + "Logo URL": "Logo URL", + "Icon URL": "Icon URL", + "Favicon URL": "Favicon URL", + "Default Language": "Default Language", + "Select Language": "Select Language", + "No Languages": "No Languages", + "Primary Color": "Primary Color", + "Custom Domains": "Custom Domains", + "No domains added yet.": "No domains added yet.", + "Verified": "Verified", + "Pending, please contact the support for dns verification.": "Pending, please contact the support for dns verification.", + "Are you sure you want to delete {domain}?": "Are you sure you want to delete {domain}?", + "Brand Activepieces": "Marca Activepieces", + "Give your users an experience that looks like you by customizing the color, logo and more": "Give your users an experience that looks like you by customizing the color, logo and more", + "Configure the appearance and SMTP settings for your platform.": "Configure the appearance and SMTP settings for your platform.", + "Invalid host": "Invalid host", + "Invalid username": "Invalid username", + "Invalid password": "Invalid password", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Invalid sender name", + "SMTP is configured": "SMTP is configured", + "Mail Server": "Mail Server", + "Set up your SMTP settings to send emails from your domain.": "Set up your SMTP settings to send emails from your domain.", + "Disable Mail Server": "Disable Mail Server", + "Are you sure you want to disable your mail server?": "Are you sure you want to disable your mail server?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "This will stop you from sending emails for issues, quota limits, invitations and forgot password.", + "mail server": "mail server", + "Host": "Host", + "Port": "Port", + "Username": "Username", + "Password": "Contrasenya", + "Sender Email": "Sender Email", + "Sender Name": "Sender Name", + "Enable Global Connections": "Enable Global Connections", + "Manage platform-wide connections to external systems.": "Manage platform-wide connections to external systems.", + "No global connections found": "No global connections found", + "Create a global connection that can be shared to multiple projects": "Create a global connection that can be shared to multiple projects", + "License key is invalid": "License key is invalid", + "Invalid license key": "Invalid license key", + "License activated!": "License activated!", + "Activate License Key": "Activate License Key", + "Let the magic begin!": "Let the magic begin!", + "Activate": "Activate", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ", + "This feature is not available in your current edition. ": "This feature is not available in your current edition. ", + "Learn how to upgrade": "Learn how to upgrade", + "Activate your platform and unlock enterprise features": "Activate your platform and unlock enterprise features", + "Activate License": "Activate License", + "Expiration": "Expiration", + "Valid until": "Valid until", + "Expired": "Expired", + "Expires soon": "Expires soon", + "Features": "Features", + "Applying Tags...": "Applying Tags...", + "Tags applied.": "Tags applied.", + "Tag created": "Tag created", + "Tag": "Tag", + "Tags": "Tags", + "Control Pieces": "Controlar peces", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Show the pieces that matter most to your users and hide the ones you don't like.", + "Manage the pieces that are available to your users": "Manage the pieces that are available to your users", + "Start by installing pieces that you want to use in your automations": "Start by installing pieces that you want to use in your automations", + "Piece Name": "Nom de la peça", + "Hide this piece from all projects": "Hide this piece from all projects", + "Show this piece for all projects": "Show this piece for all projects", + "Unpin this piece": "Unpin this piece", + "Pin this piece": "Pin this piece", + "Pieces synced": "Pieces synced", + "Pieces have been synced from the activepieces cloud.": "Pieces have been synced from the activepieces cloud.", + "OAuth2 Credentials Deleted": "OAuth2 Credentials Deleted", + "OAuth2 Credentials Updated": "OAuth2 Credentials Updated", + "Configure OAuth2 APP": "Configure OAuth2 APP", + "Delete OAuth2 APP": "Delete OAuth2 APP", + "Templates deleted successfully": "Templates deleted successfully", + "Delete Templates": "Delete Templates", + "Are you sure you want to delete the selected templates?": "Are you sure you want to delete the selected templates?", + "New Template": "New Template", + "Unlock Templates": "Unlock Templates", + "Convert the most common automations into reusable templates 1 click away from your users": "Convert the most common automations into reusable templates 1 click away from your users", + "Convert the most common automations into reusable templates": "Convert the most common automations into reusable templates", + "No templates found": "No templates found", + "Create a template for your user to inspire them": "Create a template for your user to inspire them", + "Edit template": "Edit template", + "Template is required": "Template is required", + "Update New Template": "Update New Template", + "Create New Template": "Create New Template", + "Template Name": "Template Name", + "Description": "Descripció", + "Template Description": "Template Description", + "Blog URL": "Blog URL", + "Template Blog URL": "Template Blog URL", + "Template": "Template", + "Invalid JSON": "Invalid JSON", + "User deleted successfully": "User deleted successfully", + "User activated successfully": "User activated successfully", + "User deactivated successfully": "User deactivated successfully", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Manage your users and their access to your projects", + "Start inviting users to your project": "Start inviting users to your project", + "External Id": "External Id", + "Admin": "Administrador", + "Member": "Member", + "Activated": "Activated", + "Deactivated": "Deactivated", + "Edit user": "Edit user", + "Admin cannot be deactivated": "Admin cannot be deactivated", + "Deactivate user": "Deactivate user", + "Activate user": "Activate user", + "Delete User": "Delete User", + "Are you sure you want to delete this user?": "Are you sure you want to delete this user?", + "Delete user": "Delete user", + "Update User Role": "Update User Role", + "Meeting Summary Flow": "Meeting Summary Flow", + "Added new features and fixed bugs": "Added new features and fixed bugs", + "Flows Changes": "Flows Changes", + "Connections Changes": "Connections Changes", + "New connections are placeholders and need to be reconnected again": "New connections are placeholders and need to be reconnected again", + "renamed to": "renamed to", + "Tables Changes": "Tables Changes", + "No changes to apply": "No changes to apply", + "Apply Changes": "Apply Changes", + "Create Git Release": "Create Git Release", + "Create Project Release": "Create Project Release", + "Create Rollback to": "Create Rollback to", + "Source": "Source", + "Rollback": "Rollback", + "Imported At": "Imported At", + "Imported By": "Imported By", + "Track and manage your project version history and deployments. ": "Track and manage your project version history and deployments. ", + "Environments & Releases": "Environments & Releases", + "Project Releases": "Project Releases", + "Create Release": "Create Release", + "From Git": "From Git", + "From Project": "From Project", + "No project releases found": "No project releases found", + "Create a project release to get started": "Create a project release to get started", + "Please select project": "Please select project", + "No Changes Found": "No Changes Found", + "There are no differences to apply": "There are no differences to apply", + "Please select a project": "Please select a project", + "Search projects...": "Search projects...", + "Review Changes": "Revisar canvis", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Imported by", + "from": "from", + "No description provided": "No description provided", + "Invitation only sign up": "Invitation only sign up", + "Please ask your administrator to add you to the organization.": "Please ask your administrator to add you to the organization.", + "Something went wrong, please try again.": "Something went wrong, please try again.", + "Please try again.": "Please try again.", + "Please enter a valid email address": "Si us plau, introdueix una adreça de correu electrònic vàlida", + "The email is already added.": "El correu electrònic ja s'ha afegit.", + "Add email": "Afegir correu electrònic", + "Only project admins can do this": "Only project admins can do this", + "Add Alert Email": "Afegir correu electrònic d'alerta", + "Enter the email address to receive alerts.": "Introdueix l'adreça de correu electrònic per rebre alertes.", + "Add Email": "Afegir correu electrònic", + "Emails": "Correus electrònics", + "Add email addresses to receive alerts.": "Afegeix adreces de correu electrònic per rebre alertes.", + "No emails added yet.": "Encara no s'ha afegit cap correu electrònic.", + "Choose what you want to be notified about.": "Tria sobre què vols rebre notificacions.", + "Project and alert permissions are required to change this setting.": "Project and alert permissions are required to change this setting.", + "Every Failed Run": "Cada execució fallida", + "Get an email alert when a flow fails.": "Rep una alerta per correu electrònic quan un flux falli.", + "Get an email alert when a new issue created.": "Rep una alerta per correu electrònic quan es creï un nou problema.", + "Never": "Mai", + "Turn off email notifications.": "Desactiva les notificacions per correu electrònic.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Personalitza l'aparença de l'aplicació. Canvia automàticament entre els temes de dia i de nit.", + "Select the theme for the dashboard.": "Selecciona el tema per al tauler de control.", + "Light": "Clar", + "Dark": "Fosc", + "Select the language that will be used in the dashboard.": "Selecciona l'idioma que s'utilitzarà al tauler de control.", + "Select language": "Selecciona l'idioma", + "Search language...": "Cerca idioma...", + "No language found.": "No s'ha trobat cap idioma.", + "Help us translate Activepieces to your language.": "Ajuda'ns a traduir Activepieces al teu idioma.", + "Learn more": "Més informació", + "Git Connection Removed": "Git Connection Removed", + "Your Git repository has been successfully disconnected": "Your Git repository has been successfully disconnected", + "Enable Environments": "Enable Environments", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Deploy flows across development, staging and production environments with version control and team collaboration", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Connect to Git to enable version control, backup your flows, and manage multiple environments. ", + "Repository URL": "Repository URL", + "Not connected": "Not connected", + "Project Folder": "Project Folder", + "Releases Enabled": "Releases Enabled", + "You have successfully enabled releases": "You have successfully enabled releases", + "Enable releases to easily create and manage project releases.": "Enable releases to easily create and manage project releases.", + "The external ID is already taken.": "The external ID is already taken.", + "Manage general settings for your project.": "Gestiona la configuració general del teu projecte.", + "Used to identify the project based on your SaaS ID": "Used to identify the project based on your SaaS ID", + "org-3412321": "org-3412321", + "Delete {name}": "Eliminar {name}", + "This will permanently delete this piece, all steps using it will fail.": "Això eliminarà permanentment aquesta peça i tots els passos que la utilitzin fallaran.", + "Add a piece to your project that you want to use in your automations": "Add a piece to your project that you want to use in your automations", + "Pieces list updated": "Pieces list updated", + "Manage Pieces": "Manage Pieces", + "Choose which pieces you want to be available for your current project users": "Choose which pieces you want to be available for your current project users", + "Unlock Team Permissions": "Unlock Team Permissions", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial", + "Project Members": "Membres del projecte", + "Invite your team members to collaborate.": "Convida els membres del teu equip a col·laborar.", + "No members are added to this project.": "No s'han afegit membres a aquest projecte.", + "Pending Invitations": "Invitacions pendents", + "No pending invitation.": "Cap invitació pendent.", + "templateId is missing": "Falta el templateId", + "Me Only": "Me Only", + "Unresolved": "Unresolved", + "Resolved": "Resolved", + "Title": "Title", + "Date Created": "Date Created", + "Manage todos for your project that are created by automations": "Manage todos for your project that are created by automations", + "No todos found": "No todos found", + "You do not have any pending todos. Great job!": "You do not have any pending todos. Great job!", + "Write a comment...": "Write a comment...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "This feature is still under testing and might be changed often", + "Failed to copy to clipboard": "No s'ha pogut copiar al porta-retalls", + "{number} items selected": "{number} items selected", + "Select All": "Select All", + "No results found.": "No s'han trobat resultats.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect s'ha d'utilitzar dins de MultiSelectProvider", + "Unset": "Unset", + "Refresh": "Refresh", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} més", + "Removed {entityName}": "S'ha eliminat {entityName}", + "Download File": "Descarregar fitxer", + "Copied to clipboard": "Copiat al porta-retalls", + "File is not available after execution.": "El fitxer no està disponible després de l'execució.", + "Available for Projects": "Available for Projects", + "Select projects": "Select projects", + "No items": "No items", + "Previous": "Previous", + "to": "to", + "Last Week": "Last Week", + "Last Month": "Last Month", + "Last 3 Months": "Last 3 Months", + "Last 6 Months": "Last 6 Months", + "Next 7 days": "Next 7 days", + "Next 30 days": "Next 30 days", + "Next 90 days": "Next 90 days", + "Next 180 days": "Next 180 days", + "Select Time Range": "Select Time Range", + "Clear": "Clear", + "Download": "Download", + "Go to Dashboard": "Go to Dashboard", + "Select a file": "Select a file", + "Press space to separate values": "Press space to separate values", + "AM": "AM", + "PM": "PM", + "Already have an account?": "Ja tens un compte?", + "Sign in": "Iniciar sessió", + "Don't have an account?": "No tens un compte?", + "Sign up": "Registra't", + "Welcome Back!": "Hola de nou!", + "Enter your email below to sign in to your account": "Introdueix el teu correu electrònic a continuació per iniciar sessió al teu compte", + "Let's Get Started!": "Comencem!", + "Create your account and start flowing!": "Crea el teu compte i comença a treballar!", + "Your password was changed successfully": "La teva contrasenya s'ha canviat correctament", + "Your password reset request has expired, please request a new one": "La teva sol·licitud de restabliment de contrasenya ha expirat, si us plau, sol·licita'n una de nova", + "Reset Password": "Restablir contrasenya", + "Enter your new password": "Introdueix la teva nova contrasenya", + "Password is required": "Es requereix una contrasenya", + "Verification email resent, if previous one expired.": "Verification email resent, if previous one expired.", + "Password reset link resent, if previous one expired.": "Password reset link resent, if previous one expired.", + "We sent you a link to complete your registration to": "We sent you a link to complete your registration to", + "We sent you a link to reset your password to": "We sent you a link to reset your password to", + "Didn't receive an email or it expired?": "Didn't receive an email or it expired?", + "Resend": "Reenviar", + "Please enter your email": "Si us plau, introdueix el teu correu electrònic", + "Check Your Inbox": "Revisa la teva safata d'entrada", + "If the user exists we'll send you an email with a link to reset your password.": "Si l'usuari existeix, t'enviarem un correu electrònic amb un enllaç per restablir la teva contrasenya.", + "Send Password Reset Link": "Enviar enllaç de restabliment de contrasenya", + "Back to sign in": "Tornar a iniciar sessió", + "Email is invalid": "El correu electrònic no és vàlid", + "Something went wrong, please try again later": "Alguna cosa ha fallat, si us plau, torna-ho a intentar més tard", + "Invalid email or password": "Correu electrònic o contrasenya no vàlids", + "User has been deactivated": "User has been deactivated", + "Email domain is disallowed": "Email domain is disallowed", + "Email authentication has been disabled": "Email authentication has been disabled", + "Forgot your password?": "Has oblidat la teva contrasenya?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "Sign up is restricted. You need an invitation to join. Please contact the administrator.", + "Email is already used": "El correu electrònic ja està en ús", + "Email authentication is disabled": "Email authentication is disabled", + "First name is required": "El nom és obligatori", + "Last name is required": "El cognom és obligatori", + "Email is required": "El correu electrònic és obligatori", + "Receive updates and newsletters from activepieces": "Rep actualitzacions i butlletins d'Activepieces", + "By creating an account, you agree to our": "En crear un compte, acceptes els nostres", + "terms of service": "termes del servei", + "privacy policy": "política de privacitat", + "Sign up With": "Sign up With", + "Google": "Google", + "Sign in With": "Sign in With", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "Email has been verified. You will be redirected to sign in...", + "Verifying email...": "Verificant correu electrònic...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "invitation has expired, once you sign in again you will be able to resend the verification email.", + "Redirecting to sign in...": "Redirecting to sign in...", + "Password must contain at least one special character": "La contrasenya ha de contenir almenys un caràcter especial", + "Password must contain at least one lowercase letter": "La contrasenya ha de contenir almenys una lletra minúscula", + "Password must contain at least one uppercase letter": "La contrasenya ha de contenir almenys una lletra majúscula", + "Password must contain at least one number": "La contrasenya ha de contenir almenys un número", + "8-64 Characters": "8-64 Caràcters", + "Special Character": "Caràcter especial", + "Lowercase": "Minúscula", + "Uppercase": "Majúscula", + "Number": "Número", + "Connection has been updated.": "Connection has been updated.", + "Edit Global Connection": "Edit Global Connection", + "Connection has been renamed.": "Connection has been renamed.", + "New Connection Name": "New Connection Name", + "Connection name already used": "Connection name already used", + "Please select at least one project": "Please select at least one project", + "Run Succeeded": "Execució completada amb èxit", + "Run Failed": "Execució fallida", + "Flow Run is paused": "L'execució del flux està en pausa", + "Run Failed due to quota exceeded": "Execució fallida per límit de quota superat", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Run failed due to exceeding the memory limit of {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "L'execució ha superat {timeout} segons, intenta optimitzar els teus passos.", + "Run failed for an unknown reason, contact support.": "L'execució ha fallat per una raó desconeguda, contacta amb el suport.", + "Unknown": "Desconegut", + "Exit Run": "Sortir de l'execució", + "Select shown": "Select shown", + "Select all": "Select all", + "Start Time": "Hora d'inici", + "Runs replayed successfully": "Runs replayed successfully", + "Retry": "Retry", + "all except": "all except", + "all": "all", + "Only failed runs can be retried from failed step": "Only failed runs can be retried from failed step", + "No flow runs found": "No flow runs found", + "Come back later when your automations start running": "Come back later when your automations start running", + "Step running": "Pas en execució", + "Step paused": "Pas en pausa", + "Step Stopped": "Pas aturat", + "Step Succeeded": "Pas completat amb èxit", + "Step Failed": "Pas fallit", + "Please publish flow first": "Si us plau, publica el flux primer", + "Flow is on": "El flux està activat", + "Flow is off": "El flux està desactivat", + "Permission Needed": "Permís necessari", + "Draft Version": "Versió d'esborrany", + "Published Version": "Published Version", + "Locked Version": "Versió bloquejada", + "flowsImported": "{flowsCount, plural, =0 {No hi ha fluxos importats} =1 {Flux importat correctament} other {Fluxos importats correctament}}", + "Template file is invalid": "Template file is invalid", + "No valid templates found. The following files failed to import: ": "No valid templates found. The following files failed to import: ", + "Please select a file first": "Please select a file first", + "Unsupported file type": "Unsupported file type", + "Import Flow": "Importar flux", + "Warning": "Warning", + "Importing a flow will overwrite your current one.": "Importing a flow will overwrite your current one.", + "Select a folder": "Select a folder", + "Folders": "Carpetes", + "Please select a folder": "Si us plau, selecciona una carpeta", + "Moved flows successfully": "Moved flows successfully", + "Move Selected Flows": "Move Selected Flows", + "Select Folder": "Selecciona una carpeta", + "No Folders": "No hi ha carpetes", + "Flow has been renamed.": "El flux ha estat reanomenat.", + "New Flow Name": "Nou nom del flux", + "Use Template": "Utilitzar plantilla", + "Browse Templates": "Explorar plantilles", + "Search templates": "Cercar plantilles", + "No templates found, try adjusting your search": "No templates found, try adjusting your search", + "Read more about this template in": "Més informació sobre aquesta plantilla a", + "this blog!": "aquest blog!", + "Share Template": "Compartir plantilla", + "Generate or update a template link for the current flow to easily share it with others.": "Genera o actualitza un enllaç de plantilla per al flux actual per compartir-lo fàcilment amb altres.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "La plantilla no inclourà credencials en els camps de connexió, mantenint la informació sensible segura.", + "A short description of the template": "Una descripció breu de la plantilla", + "Flow Is In Use": "Flow Is In Use", + "Flow is being used by another user, please try again later.": "Flow is being used by another user, please try again later.", + "Flow has been published.": "El flux ha estat publicat.", + "Flows have been exported.": "Flows have been exported.", + "Run": "Executar", + "Real time flow": "Flux en temps real", + "Flow can't be published with empty trigger {name}": "No es pot publicar el flux amb el desencadenant buit {name}", + "Please contact support as your published flow has a problem": "Si us plau, contacta amb el suport perquè el teu flux publicat té un problema", + "Actions": "Accions", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.", + "You are on a development branch, this will not delete the flows from the remote repository.": "You are on a development branch, this will not delete the flows from the remote repository.", + "Please enter folder name": "Si us plau, introdueix el nom de la carpeta", + "Added folder successfully": "Carpeta afegida correctament", + "The folder name already exists.": "El nom de la carpeta ja existeix.", + "New Folder": "Nova carpeta", + "Folder Name": "Nom de la carpeta", + "Loading...": "Carregant...", + "Delete {folderName}": "Delete {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "Si elimines aquesta carpeta, conservarem els seus fluxos i els traslladarem a Sense categoria.", + "All flows": "Tots els fluxos", + "Please enter a folder name": "Si us plau, introdueix un nom de carpeta", + "Renamed flow successfully": "Flux reanomenat correctament", + "Folder name already used": "Folder name already used", + "New Folder Name": "Nou nom de la carpeta", + "Connected successfully": "Connectat correctament", + "Connect Git": "Connectar amb Git", + "Remote URL": "URL remota", + "Folder name is the name of the folder where the project will be stored or fetched.": "El nom de la carpeta és el lloc on es guardarà o recuperarà el projecte.", + "SSH Private Key": "Clau privada SSH", + "The SSH private key to use for authentication.": "La clau privada SSH per utilitzar en l'autenticació.", + "Only published flows can be pushed to Git": "Only published flows can be pushed to Git", + "Pushed successfully": "Pujat correctament", + "Invalid Operation": "Invalid Operation", + "Commit Message": "Missatge de commit", + "Enter a commit message to describe the changes you want to push.": "Introdueix un missatge de commit per descriure els canvis que vols pujar.", + "Push": "Pujar", + "This field is required": "This field is required", + "Your submission was successfully received.": "La teva sol·licitud s'ha rebut correctament.", + "Flow not found": "Flux no trobat", + "The flow you are trying to submit to does not exist.": "El flux al qual intentes enviar no existeix.", + "The flow failed to execute.": "L'execució del flux ha fallat.", + "Submit": "Enviar", + "issues-notification": "notificació de problemes", + "Please select a package type": "Si us plau, selecciona un tipus de paquet", + "package.json not found in archive": "package.json not found in archive", + "Error processing archive file": "Error processing archive file", + "Please upload a .tgz file": "Please upload a .tgz file", + "Piece name is required for NPM Registry": "Piece name is required for NPM Registry", + "Piece version is required for NPM Registry": "Piece version is required for NPM Registry", + "Piece installed": "Peça instal·lada", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "A piece with this name and version is already installed. Please update the version number in package.json and try again.", + "Install Piece": "Instal·lar peça", + "Install a piece": "Instal·lar una peça", + "Package Type": "Tipus de paquet", + "NPM Registry": "Registre NPM", + "Packed Archive (.tgz)": "Arxiu comprimit (.tgz)", + "Piece Version": "Versió de la peça", + "Package Archive": "Arxiu del paquet", + "Package archive": "Arxiu del paquet", + "Powerful Node.js & TypeScript code with npm": "Powerful Node.js & TypeScript code with npm", + "Loop on Items": "Loop on Items", + "Split your flow into branches depending on condition(s)": "Split your flow into branches depending on condition(s)", + "Empty Trigger": "Empty Trigger", + "An internal error occurred while fetching data, please contact support": "An internal error occurred while fetching data, please contact support", + "An internal error occurred, please contact support": "An internal error occurred, please contact support", + "Custom Javascript Code": "Custom Javascript Code", + "Router": "Router", + "recordsCount": "{recordsCount, plural, =0 {0 Records} =1 {1 Record} other {# Records}}", + "selected": "selected", + "All records selected": "All records selected", + "fieldsCount": "{fieldsCount, plural, =0 {0 Fields} =1 {1 Field} other {# Fields}}", + "Saving...": "Saving...", + "Loading more...": "Loading more...", + "Export Table": "Export Table", + "Delete Records": "Delete Records", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Are you sure you want to delete the selected records? This action cannot be undone.", + "record": "record", + "records": "records", + "mm/dd/yyy": "mm/dd/yyy", + "Delete Field": "Delete Field", + "Are you sure you want to delete this field? This action cannot be undone.": "Are you sure you want to delete this field? This action cannot be undone.", + "field": "field", + "Ignored": "Ignored", + "Table": "Table", + "CSV": "CSV", + "Field": "Field", + "Please select a csv file": "Please select a csv file", + "Max file size is {maxFileSize}MB": "Max file size is {maxFileSize}MB", + "Import CSV": "Import CSV", + "Imported records will be added to the bottom of the table": "Imported records will be added to the bottom of the table", + "Any records after the limit ({maxRecords} records) will be ignored": "Any records after the limit ({maxRecords} records) will be ignored", + "CSV File": "CSV File", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support", + "Name must be unique": "Name must be unique", + "Type is required": "Type is required", + "Please add at least one option": "Please add at least one option", + "New Field": "New Field", + "Options": "Options", + "Name is already taken": "Name is already taken", + "Table renamed": "Table renamed", + "Table name": "Table name", + "Team Invitation Accepted": "Invitació d'equip acceptada", + "Thank you for accepting the invitation. We are redirecting you right now...": "Gràcies per acceptar la invitació. Et redirigim ara mateix...", + "Invalid invitation token. Please try again.": "Token d'invitació no vàlid. Si us plau, intenta-ho de nou.", + "Role updated successfully": "Role updated successfully", + "Error updating role": "Error updating role", + "Please try again later": "Please try again later", + "Edit Role for": "Edit Role for", + "Select Role": "Select Role", + "Avatar": "Avatar", + "Remove {email}": "Eliminar {email}", + "Are you sure you want to remove this invitation?": "Estàs segur que vols eliminar aquesta invitació?", + "Please select invitation type": "Si us plau, selecciona el tipus d'invitació", + "Please select platform role": "Si us plau, selecciona un rol de plataforma", + "Invitation sent successfully": "Invitació enviada correctament", + "Please select a project role": "Please select a project role", + "Invitation link copied successfully": "Enllaç d'invitació copiat correctament", + "Invite User": "Convidar usuari", + "Invitation Link": "Enllaç d'invitació", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Copia l'enllaç següent i comparteix-lo amb l'usuari que vols convidar, la invitació expira en 24 hores.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Escriu l'adreça de correu electrònic de l'usuari que vols convidar, la invitació expira en 24 hores.", + "Invite To": "Convidar a", + "Entire Platform": "Tota la plataforma", + "Select Project Role": "Select Project Role", + "Invite": "Convidar", + "Platform Role": "Rol de la plataforma", + "Select a platform role": "Selecciona un rol de plataforma", + "Are you sure you want to remove this member?": "Estàs segur que vols eliminar aquest membre?", + "Editor": "Editor", + "Operator": "Operador", + "Viewer": "Visualitzador", + "Select a project role": "Selecciona un rol de projecte", + "Steps in this flow": "Passos en aquest flux", + "Invalid Access": "Invalid Access", + "Either the project does not exist or you do not have access to it.": "Either the project does not exist or you do not have access to it." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/de/translation.json b/packages/react-ui/public/locales/de/translation.json new file mode 100644 index 0000000..666f305 --- /dev/null +++ b/packages/react-ui/public/locales/de/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Veröffentlichen", + "Latest version is published": "Neueste Version ist veröffentlicht", + "Your flow has incomplete steps": "Dein Fluss hat unvollständige Schritte", + "Edit Flow": "Ablauf bearbeiten", + "View Draft": "Entwurf anzeigen", + "Uncategorized": "Nicht kategorisiert", + "Go to folder": "Gehe zum Ordner", + "Support": "Unterstützung", + "Runs": "Ausführungen", + "Run Logs": "Ausführungsprotokolle", + "Versions": "Versionen", + "Versions History": "Versionshistorie", + "Error generating code": "Fehler bei der Codegenerierung", + "AI Copilot": "KI Copilot", + "i.e Calculate the sum of a list...": "D.h. Berechne die Summe einer Liste...", + "Send": "Senden", + "Generating Code": "Generiere Code", + "Hello there! I am here to generate code that helps with your flow": "Hallo da! Ich bin hier, um Code zu generieren, der mit deinem Fluss hilft", + "Here are examples of what I am best used for: ": "Hier sind Beispiele dafür, wofür ich am besten verwendet werde: ", + "Text Processing": "Textverarbeitung", + "Process strings, dates and data": "Zeichenketten, Datum und Daten verarbeiten", + "Data Operations": "Datenvorgänge", + "Change data from one format to another": "Daten von einem Format zum anderen ändern", + "Calculations": "Berechnungen", + "Handle math and statistics": "Mathematik und Statistik bearbeiten", + "API Integration": "API-Integration", + "Connect with external services. Best for simple integrations currently.": "Verbinden Sie sich mit externen Diensten. Am besten für einfache Integrationen heute.", + "What would you like me to help you with?": "Was möchtest du dir dabei helfen?", + "Insert": "Einfügen", + "Data Selector": "Daten-Auswahl", + "Search": "Suche", + "No matching data": "Keine übereinstimmenden Daten", + "Try adjusting your search": "Versuchen Sie Ihre Suche anzupassen.", + "This trigger needs to have data loaded from your account, to use as sample data.": "Für diesen Auslöser müssen Daten aus Ihrem Konto geladen werden, die als Beispieldaten dienen.", + "This step needs to be tested in order to view its data.": "Dieser Schritt muss getestet werden, um seine Daten anzeigen zu können.", + "Go to Trigger": "Gehe zum Auslöser", + "Go to Step": "Gehe zum Schritt", + "Select Mode": "Wähle Modus", + "Move Mode": "Verschiebe-Modus", + "Reset Zoom": "Vergrößerung zurücksetzen", + "Zoom In": "Vergrößern", + "Zoom Out": "Verkleinern", + "Fit to View": "An Ansicht anpassen", + "Replace": "Ersetzen", + "Copy": "Kopieren", + "Duplicate": "Duplizieren", + "Paste After Last Step": "Nach dem letzten Schritt einfügen", + "Paste Inside Loop": "In Schleife einfügen", + "Paste After": "Einfügen nach", + "Paste Inside...": "Inside einfügen...", + "New Branch": "Neuer Zweig", + "Paste Inside Branch": "Inside Branch einfügen", + "Delete": "Löschen", + "Duplicate Branch": "Zweig duplizieren", + "Delete Branch": "Branch löschen", + "Invalid Move": "Ungültiger Zug", + "The destination location is a child of the dragged step": "Die Zielposition ist ein untergeordneter Schritt", + "End": "Ende", + "Skipped": "Übersprungen", + "Incomplete settings": "Unvollständige Einstellungen", + "logo": "Logo", + "Step Icon": "Schritt-Symbol", + "Branch": "Zweig", + "incompleteSteps": "{invalidSteps, plural, =0 {Keine unvollständigen Schritte} =1 {Erledige 1 Schritt} other {Erledige # Schritte}}", + "Test Flow": "Testablauf", + "Please test the trigger first": "Bitte testen Sie den Auslöser zuerst", + "View Only": "Nur Ansicht", + "Use as Draft": "Als Entwurf verwenden", + "Are you sure?": "Sind Sie sicher?", + "Your current draft version will be overwritten with": "Dein aktueller Entwurf wird überschrieben mit ", + "version #": "version #", + "Cancel": "Abbrechen", + "Confirm": "Bestätigen", + "Version": "Version", + "Viewing": "Ansehen", + "View": "Ansicht", + "Version History": "Versionsverlauf", + "Error, please try again.": "Fehler, bitte erneut versuchen.", + "Continue on Failure": "Bei Fehler fortfahren", + "Enable this option to skip this step and continue the flow normally if it fails.": "Aktivieren Sie diese Option, um diesen Schritt zu überspringen und den Ablauf normal fortzusetzen, wenn er fehlschlägt.", + "Retry on Failure": "Bei Fehler wiederholen", + "Automatically retry up to four attempts when failed.": "Versuchen Sie bei Fehlschlag automatisch bis zu vier Versuche.", + "Remove": "Entfernen", + "Add Item": "Element hinzufügen", + "File Input": "Dateieingabe", + "Date Input": "Date Input", + "Dynamic value": "Dynamischer Wert", + "Select an option": "Wähle eine Option aus", + "Unexpected error, please retry": "Unerwarteter Fehler, bitte erneut versuchen", + "Unexpected error, please refresh the page or contact support": "Unerwarteter Fehler, bitte aktualisieren Sie die Seite oder kontaktieren Sie den Support", + "Name can only contain letters, numbers and underscores": "Name darf nur Buchstaben, Zahlen und Unterstriche enthalten", + "Ask AI": "KI fragen", + "Create Todo Guide": "Todo-Anleitung erstellen", + "Where would you like the todo to be reviewed?": "Wo soll das ToDo überprüft werden?", + "Activepieces Todos": "Todos aktivieren", + "Users will manage tasks directly in Activepieces": "Benutzer werden Aufgaben direkt in Activepieces verwalten", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Benutzer verwalten und reagieren auf Todos direkt innerhalb der Activepieces Schnittstelle. Ideal für interne Teams.", + "External Channel (Slack, Teams, Email, ...)": "Externer Kanal (Slack, Teams, Email, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Senden Sie Benachrichtigungen mit Genehmigungslinks über externe Kanäle wie Slack, Teams oder Email. Am besten für die Zusammenarbeit mit externen Stakeholdern.", + "Preview (Activepieces Todos)": "Vorschau (Activepieces Todos)", + "Preview (External channel)": "Vorschau (externer Kanal)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "Das Activepieces Todo erlaubt es Benutzern, Aufgaben direkt im Activepieces Interface zu überprüfen und zu erledigen", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "Sie können den Kanal vor dem Warteschritt hinzufügen und die Logik im Router-Schritt konfigurieren", + "Add Steps": "Füge Schritte hinzu", + "All": "Alle", + "AI": "AI", + "Core": "Kern", + "Apps": "Apps", + "Not available as trigger": "Nicht als Auslöser verfügbar", + "Not available as action": "Nicht verfügbar als Aktion", + "Let our AI assistant help you out": "Lassen Sie sich von unserem KI-Assistenten helfen", + "Or": "Oder", + "Request Piece": "Stück anfordern", + "No pieces found": "Keine Bausteine gefunden", + "Please select a piece first": "Bitte wählen Sie zuerst ein Stück", + "All Iterations": "Alle Iterationen", + "Duration": "Dauer", + "Input": "Eingabe", + "Output": "Ausgabe", + "There are no logs captured for this run.": "Es wurden keine Logs für diesen Run erfasst.", + "Logs are kept for {days} days after execution and then deleted.": "Protokolle werden für {days} Tage nach der Ausführung aufbewahrt und dann gelöscht.", + "Run Details": "Ausführungsdetails", + "Iteration": "Iteration", + "Done": "Fertig", + "Took": "Anhalten", + "Running": "Läuft", + "on latest version": "auf neueste Version", + "from failed step": "vom fehlgeschlagenen Schritt", + "Recent Runs": "Letzte Ausführungen", + "No runs found": "Keine Ausführungen gefunden", + "Close": "Schließen", + "OR": "ODER", + "And If": "Und wenn", + "+ And": "+ Und", + "+ Or": "+ Oder", + "(Text) Contains": "(Text) Enthält", + "(Text) Does not contain": "(Text) Enthält nicht", + "(Text) Exactly matches": "(Text) Genaue Übereinstimmung", + "(Text) Does not exactly match": "(Text) Stimmt nicht genau überein", + "(Text) Starts with": "(Text) Beginnt mit", + "(Text) Does not start with": "(Text) Beginnt nicht mit", + "(Text) Ends with": "(Text) Endet mit", + "(Text) Does not end with": "(Text) Endet nicht mit", + "(List) Contains": "(List) Enthält", + "(List) Does not contain": "(List) enthält nicht", + "(Number) Is greater than": "(Zahl) Ist größer als", + "(Number) Is less than": "(Zahl) Ist kleiner als", + "(Number) Is equal to": "(Zahl) Ist gleich", + "(Date/time) After": "(Datum/Uhrzeit) nach", + "(Date/time) Before": "(Datum/Uhrzeit) vorher", + "(Date/time) Equals": "(Datum/Uhrzeit) Gleich", + "(Boolean) Is true": "(Boolean) Ist wahr", + "(Boolean) Is false": "(Boolean) Ist falsch", + "(List) Is empty": "(List) ist leer", + "(List) Is not empty": "(List) ist nicht leer", + "Exists": "Existiert", + "Does not exist": "Existiert nicht", + "Incomplete condition": "Unvollständige Bedingung", + "First value": "Erster Wert", + "Second value": "Zweiter Wert", + "Case sensitive": "Groß- und Kleinschreibung beachten", + "Execute If": "Ausführen wenn", + "The package name is required": "Der Paketname ist erforderlich", + "Success": "Erfolg", + "Package added successfully": "Paket erfolgreich hinzugefügt", + "Could not fetch package version": "Paketversion konnte nicht abgerufen werden", + "Add NPM Package": "NPM-Paket hinzufügen", + "Type the name of the npm package you want to add.": "Geben Sie den Namen des NPM-Paketes ein, welches Sie hinzufügen möchten.", + "Package Name": "Paketname", + "The latest version will be fetched and added": "Die neueste Version wird abgerufen und hinzugefügt", + "Add": "Hinzufügen", + "Code": "Code", + "Dependencies": "Abhängigkeiten", + "Use code": "Code verwenden", + "Add package": "Paket hinzufügen", + "Inputs": "Eingaben", + "Edit Step Name": "Schrittname bearbeiten", + "Edit Branch Name": "Branchname bearbeiten", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Wählen Sie die Elemente aus, die im vorherigen Schritt wiederholt werden sollen, indem Sie auf die **Elemente** Eingabe klicken, die eine **Liste** der Elemente sein soll.\n\nDie Schleife wird über jedes Element in der Liste iterieren und den nächsten Schritt für jedes Element ausführen.", + "Items": "Elemente", + "Select an array of items": "Wählen Sie ein Array von Elementen", + "Reconnect": "Neu verbinden", + "Select a connection": "Verbindung auswählen", + "Create Connection": "Verbindung erstellen", + "Rename": "Umbenennen", + "Move": "Verschieben", + "Add Branch": "Zweig hinzufügen", + "Execute": "Ausführen", + "Only the first (left) matching branch": "Nur der erste (links) passende Branch", + "All matching paths from left to right": "Alle passenden Pfade von links nach rechts", + "Branches": "Äste", + "{field} is required": "{field} ist erforderlich", + "Tool Sample Data": "Beispieldaten", + "Fill in the following fields to use them as sample data for the trigger.": "Füllen Sie die folgenden Felder aus, um sie als Beispieldaten für den Trigger zu verwenden.", + "No input fields defined in the schema": "Keine Eingabefelder im Schema definiert", + "Save": "Speichern", + "Test Environment": "Testumgebung", + "Assigned to": "Zugewiesen an", + "(Me)": "(Ich)", + "Please select status to resolve the todo": "Bitte wählen Sie den Status aus, um das To-Do zu lösen", + "Resolve": "Lösen", + "Change status to resolved": "Status auf gelöst ändern", + "Send Sample Data to Webhook": "Beispieldaten an Webhook senden", + "Method": "Methode", + "Query Params": "Abfrageparameter", + "Headers": "Kopfzeilen", + "Body": "Körper", + "Type": "Typ", + "JSON": "JSON", + "Text": "Text", + "Form Data": "Formulardaten", + "Generate Sample Data": "Beispieldaten generieren", + "Test Step": "Testschritt", + "Retest": "Retten", + "Testing Failed": "Test fehlgeschlagen", + "Tested Successfully": "Erfolgreich getestet", + "Logs": "Logs", + "There is no sample data available found for this trigger.": "Für diesen Auslöser sind keine Beispieldaten verfügbar.", + "Internal error, please try again later.": "Interner Fehler, bitte versuchen Sie es später erneut.", + "Failed to run test step and no error message was returned": "Testschritt konnte nicht ausgeführt werden und es wurde keine Fehlermeldung zurückgegeben", + "Please fix inputs first": "Bitte zuerst die Eingaben korrigieren", + "No sample data available": "Keine Beispieldaten verfügbar", + "Old results were removed, retest for new sample data": "Alte Ergebnisse wurden entfernt, Test für neue Beispieldaten", + "Result #": "Ergebnis #", + "The sample data can be used in the next steps.": "Die Beispieldaten können in den nächsten Schritten verwendet werden.", + "Testing Trigger": "Teste Auslöser", + "Action Required": "Aktion erforderlich", + "testPieceWebhookTriggerNote": "Bitte gehen Sie zu {pieceName} und lösen {triggerName} aus.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Sende Daten an die Webhook-URL, um Beispieldaten zu generieren, die in den nächsten Schritten verwendet werden sollen", + "Test Trigger": "Auslöser Testen", + "Use Mock Data": "Verwende Mock Daten", + "Load Sample Data": "Beispieldaten laden", + "Test Tool": "Test-Werkzeug", + "home": "home", + "Home": "Startseite", + "Alerts": "Benachrichtigungen", + "Releases": "Veröffentlichungen", + "Flows": "Abläufe", + "Products": "Produkte", + "MCP": "MCP", + "Tables": "Tabellen", + "Todos": "Todos", + "Push to Git": "In Git schieben", + "Move To": "Bewegen zu", + "Duplicating": "Duplizieren", + "Import": "Importieren", + "Exporting": "Exportieren", + "Export": "Exportieren", + "Share": "Teilen", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Sind Sie sicher, dass Sie diesen Ablauf löschen wollen? Dadurch wird der Ablauf, alle zugehörigen Daten und alle Hintergrundausführungen dauerhaft gelöscht.", + "You are on a development branch, this will also delete the flow from the remote repository.": "Sie befinden sich in einem Entwicklungszweig, dies löscht auch den Fluss aus dem Remote-Repository.", + "flow": "Ablauf", + "Community Support": "Community-Unterstützung", + "Overview": "Übersicht", + "Projects": "Projekte", + "Users": "Benutzer", + "Setup": "Setup", + "Branding": "Branding", + "Global Connections": "Globale Verbindungen", + "Pieces": "Bausteine", + "Templates": "Vorlagen", + "License Key": "Lizenzschlüssel", + "Security": "Sicherheit", + "Audit Logs": "Audit-Protokolle", + "Single Sign On": "Einmal anmelden", + "Signing Keys": "Signierschlüssel", + "Project Roles": "Projektrollen", + "API Keys": "API-Schlüssel", + "Infrastructure": "Infrastruktur", + "Workers": "Arbeiter", + "Health": "Gesundheit", + "Billing": "Abrechnung", + "Settings": "Einstellungen", + "Contact Sales": "Kontakt Verkäufe", + "General": "Allgemein", + "Appearance": "Erscheinungsbild", + "Team": "Team", + "Environments": "Umgebungen", + "Project Settings": "Projekteinstellungen", + "Exit Platform Admin": "Plattform-Admin verlassen", + "Enter Platform Admin": "Plattform-Admin eingeben", + "Logout": "Abmelden", + "Misc": "Sonst", + "Connections": "Verbindungen", + "days": "tage", + "hours": "std", + "minutes": "minuten", + "Today": "Heute", + "Tasks": "Aufgaben", + "AI Credits": "AI Credits", + "Usage resets in": "Nutzung zurückgesetzt in", + "Manage": "Verwalten", + "Unlimited": "Unbegrenzt", + "Enabled": "Aktiviert", + "Disabled": "Deaktiviert", + "Could not claim the authorization code, make sure you have correct settings and try again.": "Der Autorisierungscode konnte nicht beansprucht werden. Stellen Sie sicher, dass Sie die richtigen Einstellungen haben und versuchen Sie es erneut.", + "Connection failed with error {msg}": "Verbindung fehlgeschlagen mit Fehler {msg}", + "You don't have the permission to create a connection.": "Ihnen fehlt die Berechtigung, eine Verbindung herzustellen.", + "Reconnect {displayName} Connection": "Reconnect {displayName}", + "Connect to {displayName}": "Mit {displayName} verbinden", + "Connection Name": "Verbindungsname", + "Connection name": "Verbindungsname", + "New Connection": "Neue Verbindung", + "Redirect URL": "Weiterleitungs-URL", + "Client ID": "Kunden-ID", + "Client Secret": "Kundengeheimnis", + "Connect": "Verbinden", + "Disconnect": "Trennen", + "I would like to use my own App Credentials": "Ich möchte meine eigenen App-Zugangsdaten verwenden", + "I would like to use predefined App Credentials": "Ich möchte vordefinierte App-Zugangsdaten verwenden", + "Permission needed": "Berechtigung benötigt", + "Connections replaced successfully": "Verbindungen erfolgreich ersetzt", + "Error": "Fehler", + "Failed to replace connections": "Fehler beim Ersetzen der Verbindungen", + "Failed to get affected flows": "Fehler beim Abrufen der betroffenen Ströme", + "Please select a piece": "Bitte wählen Sie ein Stück aus", + "Please select a connection to replace": "Bitte wählen Sie eine zu ersetzende Verbindung", + "Please select a connection to replace with": "Bitte wählen Sie eine Verbindung zum Ersetzen durch", + "Replace Connections": "Verbindungen ersetzen", + "Confirm Replacement": "Ersatz bestätigen", + "Replace one connection with another.": "Ersetzen Sie eine Verbindung mit einer anderen.", + "This action requires ": "Diese Aktion erfordert ", + "reconnecting": "erneut verbinden", + " any associated MCP pieces.": " alle zugehörigen MCP-Stücke.", + "Piece": "Baustein", + "Select a piece": "Wähle ein Stück", + "Connection to Replace": "Verbindung ersetzen", + "Choose connection to replace": "Verbindung zum Ersetzen auswählen", + "Replaced With": "Ersetzt durch", + "Choose connection to replace with": "Wählen Sie eine Verbindung zum ersetzen durch", + "All flows will be changed to use the replaced with connection": "Alle Ströme werden geändert, um die ersetzte Verbindung zu verwenden", + "Next": "Nächste", + "No flows will be affected by this change": "Keine Ströme werden von dieser Änderung beeinflusst", + "Back": "Zurück", + "Unnamed tool": "Unbenanntes Werkzeug", + "This flow is enabled": "Dieser Fluss ist aktiviert", + "Enable this flow to make it available": "Aktiviere diesen Flow um ihn verfügbar zu machen", + "Piece is updated successfully": "Teil wurde erfolgreich aktualisiert", + "Piece is added successfully": "Teil wurde erfolgreich hinzugefügt", + "Failed to update piece": "Fehler beim Aktualisieren des Stücks", + "Failed to add piece": "Fehler beim Hinzufügen des Stücks", + "Please select a connection": "Bitte wählen Sie eine Verbindung", + "Your MCP server already has this piece": "Ihr MCP-Server hat dieses Stück bereits", + "+ New Connection": "+ Neue Verbindung", + "Edit Piece": "Teil bearbeiten", + "Add Piece": "Stück hinzufügen", + "Connection": "Verbindung", + "MCP piece": "MCP-Stück", + "Failed to update piece status": "Fehler beim Aktualisieren des Stückstatus", + "Connection required": "Verbindung erforderlich", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Sind Sie sicher, dass Sie dieses Tool aus Ihrem MCP löschen möchten? wenn Sie es löschen, können Sie es nicht in Ihrem MCP-Client verwenden.", + "piece": "stück", + "Connect your AI assistant to external services": "Verbinden Sie Ihren KI-Assistenten mit externen Diensten", + "Collapse": "Einklappen", + "Show All": "Alle anzeigen", + "Server URL": "Server-URL", + "Hide the token for security": "Token für Sicherheit ausblenden", + "Show the token": "Token anzeigen", + "Generate a new token for security. This will invalidate the current URL.": "Ein neues Token für die Sicherheit generieren. Dies wird die aktuelle URL ungültig machen.", + "URL copied to clipboard": "URL in Zwischenablage kopiert", + "Copy URL to clipboard": "URL in Zwischenablage kopieren", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "Diese URL enthält einen sensiblen Sicherheits-Token. Teilen Sie ihn nur mit vertrauenswürdigen Anwendungen und Diensten. Drehen Sie den Token wenn Sie vermuten, dass er kompromittiert wurde.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "Nachdem Sie irgendwelche Änderungen an Verbindungen oder Strömen vorgenommen haben, müssen Sie Ihren MCP-Server erneut verbinden, damit die Änderungen wirksam werden.", + "Max tables reached": "Max. Tabellen erreicht", + "You can't create more than {maxTables} tables": "Sie können nicht mehr als {maxTables} Tabellen erstellen", + "Name": "Name", + "Created": "Erstellt", + "Delete Tables": "Tabellen löschen", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Sind Sie sicher, dass Sie die ausgewählten Tabellen löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "table": "tabelle", + "Create and manage your tables to store your automation data": "Erstellen und verwalten Sie Ihre Tabellen, um Ihre Automatisierungsdaten zu speichern", + "New Table": "Neue Tabelle", + "No tables have been created yet": "Es wurden noch keine Tabellen erstellt", + "Create a table to get started and start managing your automation data": "Erstellen Sie eine Tabelle, um loszulegen und Ihre Automatisierungsdaten zu verwalten", + "Error deleting connections": "Fehler beim Löschen der Verbindungen", + "Status": "Status", + "Display Name": "Anzeigename", + "Owner": "Besitzer", + "App": "App", + "This connection is global and can be managed in the platform admin": "Diese Verbindung ist global und kann im Plattform-Admin verwaltet werden", + "External ID": "Externe ID", + "Connected At": "Verbunden am", + "Confirm Deletion": "Löschen bestätigen", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Sind Sie sicher, dass Sie die ausgewählten Verbindungen löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "Deleting connections may cause your Flows or MCP tools to break.": "Das Löschen von Verbindungen kann dazu führen, dass Ihre Flows oder MCP-Tools kaputt gehen.", + "Manage project connections to external systems.": "Projektverbindungen zu externen Systemen verwalten.", + "No connections found": "Keine Verbindungen gefunden", + "Come back later when you create a automation to manage your connections": "Kommen Sie später zurück, wenn Sie eine Automatisierung zur Verwaltung Ihrer Verbindungen erstellen", + "Steps": "Schritte", + "Folder": "Ordner", + "Flow name": "Ablaufname", + "No flows found": "Keine Ströme gefunden", + "Create a workflow to start automating": "Erstellen Sie einen Workflow, um mit der Automatisierung zu beginnen", + "Create and manage your flows, run history and run issues": "Erstellen und verwalten Sie Ihre Flows, führen Sie den Verlauf aus und führen Sie Probleme aus", + "Issues": "Probleme", + "Untitled": "Ohne Titel", + "Create flow": "Flow erstellen", + "From scratch": "Leere Vorlage", + "Use a template": "Eine Vorlage verwenden", + "From local file": "Von lokaler Datei", + "Flow Name": "Ablaufname", + "Count": "Anzahl", + "First Seen": "Erster Blick", + "Last Seen": "Zuletzt gesehen", + "Issues in {flowDisplayName} is marked as resolved.": "Probleme in {flowDisplayName} werden als gelöst markiert.", + "Unlock Issues": "Probleme entsperren", + "Track issues in your workflows and troubleshoot them.": "Verfolgen Sie Probleme in Ihren Workflows und beheben Sie sie.", + "No issues found": "Keine Tickets gefunden", + "All your workflows are running smoothly.": "Alle Ihre Arbeitsabläufe laufen reibungslos.", + "Mark as Resolved": "Als gelöst markieren", + "All Flows Are Turned Off": "Alle Flows sind ausgeschaltet", + "Task Usage Exceeded": "Aufgabennutzung überschritten", + "of the Allowed Limit.": "des erlaubten Limit.", + "When a project tasks limit is reached,": "Wenn ein Projektaufgabenlimit erreicht ist,", + "all flows will be turned off and you will not be able to run any flows.": "alle Ströme werden ausgeschaltet und Sie werden keine Ströme ausführen können.", + "Please visit": "Bitte besuchen", + "Your Plan": "Ihr Plan", + "and increase your task limit, which requires your payment details.": "und erhöhen Sie Ihr Aufgabenlimit, was Ihre Zahlungsinformationen erfordert.", + "Please contact your admin to increase the project task limit.": "Bitte kontaktieren Sie Ihren Administrator, um das Projektaufgabenlimit zu erhöhen.", + "and increase the project task limit.": "und erhöhen Sie das Projektaufgabenlimit.", + "Dismiss": "Verwerfen", + "Token rotated successfully": "Token erfolgreich gedreht", + "Failed to rotate token": "Fehler beim Drehen des Token", + "Piece removed successfully": "Teil erfolgreich entfernt", + "Failed to remove piece": "Fehler beim Entfernen des Stücks", + "Flow created successfully": "Flow erfolgreich erstellt", + "Failed to create flow": "Fehler beim Erstellen des Fusses", + "Add Flow": "Flow hinzufügen", + "Let your AI assistant trigger automations": "Lassen Sie Ihren KI-Assistenten Automatisierungen auslösen", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Verbinden Sie sich mit Ihrem gehosteten MCP Server mit jedem MCP Client um mit Tools zu kommunizieren", + "MCP Server": "MCP-Server", + "My Tools": "Meine Tools", + "Create Flow": "Flow erstellen", + "Note": "Notiz", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "Wenn Sie Ihren MCP-Server dem Internet aussetzen möchten, setzen Sie die Umgebungsvariable AP_FRONTEND_URL auf die öffentliche URL Ihrer Activepieces Instanz.", + "This URL grants access to your tools and data. Only share with trusted applications.": "Diese URL gewährt Zugriff auf Ihre Tools und Daten. Teilen Sie nur mit vertrauenswürdigen Anwendungen.", + "Server Configuration": "Serverkonfiguration", + "Hide sensitive data": "Empfindliche Daten ausblenden", + "Show sensitive data": "Empfindliche Daten anzeigen", + "Create a new URL. The current one will stop working.": "Erstellen Sie eine neue URL. Die aktuelle URL wird nicht mehr funktionieren.", + "Copy configuration": "Konfiguration kopieren", + "Configuration copied to clipboard": "Konfiguration in Zwischenablage kopiert", + "Claude": "Jörg", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "Server/Andere", + "Note: MCPs only work with": "Hinweis: MCPs funktionieren nur mit", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", nicht die Web-Version.", + "Prerequisites:": "Voraussetzungen:", + "Install": "Installieren", + "Node.js": "Node.js", + "and": "und", + "Open Settings:": "Einstellungen öffnen:", + "Click the menu and select": "Klicken Sie auf das Menü und wählen Sie", + "Developer": "Entwickler", + "Configure MCP:": "MCP konfigurieren:", + "Click": "Click", + "Edit Config": "Konfiguration bearbeiten", + "and paste the configuration below": "und fügen Sie die Konfiguration unten ein", + "Save and Restart:": "Speichern und Neustarten:", + "Save the config and restart Claude Desktop": "Speichere die Konfiguration und starte Claude Desktop neu", + "Navigate to": "Navigieren zu", + "Cursor Settings": "Cursor-Einstellungen", + "Add Server:": "Server hinzufügen:", + "Add new global MCP server": "Neuen globalen MCP-Server hinzufügen", + "Configure:": "Konfigurieren:", + "Paste the configuration below and save": "Fügen Sie die Konfiguration unten ein und speichern", + "Use either method:": "Verwende jede Methode:", + "Go to": "Gehe zu", + "Advanced Settings": "Erweiterte Einstellungen", + "Open Command Palette and select": "Befehlspalette öffnen und auswählen", + "Windsurf Settings Page": "Windsurf-Einstellungen Seite", + "Navigate to Cascade:": "Navigiere zu Kaskade:", + "Select": "Auswählen", + "Cascade": "Cascade", + "in the sidebar": "in der Sidebar", + "Add Server": "Server hinzufügen", + "Add custom server +": "Eigenen Server hinzufügen +", + "Copy URL": "URL kopieren", + "Client Setup Instructions": "Client-Setup Anweisungen", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "Nach dem Wechsel von Verbindungen oder Strömen verbinden Sie Ihren MCP-Server neu, damit die Änderungen wirksam werden.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Folgen Sie diesen Schritten, um MCP in Ihrem bevorzugten Client einzurichten. Dies ermöglicht Ihrem KI-Assistenten den Zugriff auf Ihre Werkzeuge.", + "icon": "symbol", + "Unlock Analytics": "Analytik freischalten", + "Get insights into your platform usage and performance with our analytics dashboard": "Erhalten Sie Einblicke in die Nutzung und Leistung Ihrer Plattform mit unserem AnalyseDashboard", + "Active Flows": "Aktive Ströme", + "The number of enabled flows in the platform": "Die Anzahl der aktivierten Ströme in der Plattform", + "Active Projects": "Aktive Projekte", + "The number of projects with at least one enabled flow": "Die Anzahl der Projekte mit mindestens einem aktivierten Fluss", + "Active Users": "Aktive Benutzer", + "The number of users logged in the last 30 days": "Die Anzahl der angemeldeten Benutzer in den letzten 30 Tagen", + "Out of {totalusers} total users": "Keine {totalusers} Benutzer", + "Pieces Used": "Verwendete Teile", + "The number of unique pieces used across all flows": "Die Anzahl der Einzelstücke, die über alle Ströme hinweg verwendet werden", + "Flows with AI": "Fließt mit KI", + "The number of enabled flows that use AI pieces": "Die Anzahl der aktivierten Ströme, die KI-Teile verwenden", + "Metrics": "Metriken", + "Executed Tasks": "Ausgeführte Aufgaben", + "Showing total executed tasks for specified time range": "Gesamte ausgeführte Aufgaben für den angegebenen Zeitraum anzeigen", + "Tasks Usage Limit": "Aufgabenlimit", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Geben Sie ein monatliches Limit für Aufgaben an, um eine übermäßige Nutzung zu vermeiden. Ihre Abläufe werden nicht mehr ausgeführt, wenn dieses Limit erreicht wurde.", + "Number of monthly tasks": "Anzahl der monatlichen Aufgaben", + "Save changes": "Änderungen speichern", + "Limits updated successfully": "Limits erfolgreich aktualisiert", + "Failed to update limits": "Limits konnten nicht aktualisiert werden", + "Failed to load billing information": "Fehler beim Laden der Rechnungsinformationen", + "Billing Amount": "Rechnungsbetrag", + "Manage Payment Details": "Zahlungsdetails verwalten", + "Add Payment Details": "Zahlungsdetails hinzufügen", + "Current Task Usage": "Aktuelle Aufgaben-Nutzung", + "Count of executed steps": "Anzahl der ausgeführten Schritte", + "Billing Limit": "Rechnungslimit", + "Edit": "Bearbeiten", + "Add Limit": "Neue Grenze", + "Current Credit Usage": "Aktuelle Kreditnutzung", + "WebSocket Connection": "WebSocket-Verbindung", + "Connected": "Verbunden", + "Disconnected": "Verbindung getrennt", + "No issues detected": "Keine Probleme gefunden", + "Check the status of your platform and its components": "Überprüfen Sie den Status Ihrer Plattform und deren Komponenten", + "System Health Status": "System Gesundheitsstatus", + "All systems are running smoothly": "Alle Systeme laufen reibungslos", + "Check the health of your worker machines": "Prüfen Sie die Gesundheit Ihrer Arbeitermaschinen", + "Workers Machine": "Arbeiter Maschine", + "This is demo data. In a real environment, this would show your actual worker machines.": "Dies sind Demo-Daten. In einer realen Umgebung würde dies Ihre tatsächlichen Arbeitsmaschinen zeigen.", + "No workers found": "Keine Arbeiter gefunden", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "Sie haben noch keine Arbeitsmaschinen. Neue Maschinen zum Ausführen Ihrer Automatisierung aufbauen", + "IP Address": "IP-Adresse", + "CPU Usage": "CPU Auslastung", + "Disk Usage": "Plattennutzung", + "RAM Usage": "RAM Usage", + "Last Contact": "Letzter Kontakt", + "Configs": "Einstellungen", + "Environment Variables": "Umgebungsvariablen", + "Websocket Connection Error": "Websocket Verbindungsfehler", + "Retry Connection": "Verbindung wiederholen", + "Update Available": "Update verfügbar", + "Update Now": "Jetzt aktualisieren", + "Your Universal AI needs a quick setup": "Ihre universelle KI benötigt ein schnelles Setup", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "Wir haben festgestellt, dass Sie noch keine KI-Anbieter eingerichtet haben. Um universelle KI-Teile für Ihr Team freizuschalten, müssen Sie zuerst einige Zugangsdaten für Anbieter konfigurieren.", + "Configure": "Konfigurieren", + "Platform Alerts": "Plattform-Alarme", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "Es gibt wichtige Plattformwarnungen, die Ihre Aufmerksamkeit erfordern. Bitte überprüfen Sie den Bereich \"Benachrichtigungen\" im Plattform-Administrator.", + "View Alerts": "Alarme anzeigen", + "Used Tasks": "Verwendete Aufgaben", + "Used AI Credits": "Verwendete KI-Credits", + "Cannot delete active project, switch to another project first": "Aktives Projekt kann nicht gelöscht werden, zuerst zu einem anderen Projekt wechseln", + "Delete Projects": "Projekte löschen", + "Are you sure you want to delete the selected projects?": "Sind Sie sicher, dass Sie die ausgewählten Projekte löschen möchten?", + "New Project": "Neues Projekt", + "Validation error": "Validierungsfehler", + "Project has enabled flows. Please disable them first.": "Projekt hat Ströme aktiviert. Bitte deaktivieren Sie diese zuerst.", + "This project is active. Please switch to another project first.": "Dieses Projekt ist aktiv. Bitte wechseln Sie zuerst zu einem anderen Projekt.", + "Unlock Projects": "Entsperren von Projekten", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Organisieren Sie Ihre Automatisierungsteams über Projekte hinweg mit eigenen Strömen, Verbindungen und Nutzungsquoten", + "Manage your automation projects": "Automatisierungsprojekte verwalten", + "No projects found": "Keine Projekte gefunden", + "Start by creating projects to manage your automation teams": "Beginnen Sie mit der Erstellung von Projekten, um Ihre Automatisierungsteams zu verwalten", + "Edit project": "Projekt bearbeiten", + "Name is required": "Name ist erforderlich", + "Create New Project": "Neues Projekt erstellen", + "Project Name": "Projekt Name", + "Id": "Id", + "Enable API Keys": "API-Schlüssel aktivieren", + "Create and manage API keys to access Activepieces APIs.": "Erstellen und verwalten Sie API-Schlüssel, um auf Activepieces APIs zuzugreifen.", + "New Api Key": "Neuer Api-Schlüssel", + "No API keys found": "Keine API-Schlüssel gefunden", + "Start by creating an API key to communicate with Activepieces APIs": "Beginnen Sie mit dem Erstellen eines API-Schlüssels für die Kommunikation mit Activepieces APIs", + "Delete API Key": "API Key löschen", + "Are you sure you want to delete this API key?": "Sind Sie sicher, dass Sie diesen API-Schlüssel löschen möchten?", + "API Key": "API-Schlüssel", + "API Key Created": "API-Schlüssel erstellt", + "Create New API Key": "Neuen API-Schlüssel erstellen", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Bitte speichern Sie diesen geheimen Schlüssel irgendwo sicher und zugänglich. Aus Sicherheitsgründen", + "you won't be able to view it again after closing this dialog.": "Sie können ihn nach dem Schließen dieses Dialoges nicht mehr anzeigen.", + "API Key Name": "API-Schlüsselname", + "Action": "Aktion", + "Performed By": "Ausgeführt von", + "Project": "Projekt", + "Unlock Audit Logs": "Audit-Protokolle freischalten", + "Comply with internal and external security policies by tracking activities done within your account": "Befolgen Sie die internen und externen Sicherheitsrichtlinien durch die Verfolgung von Aktivitäten innerhalb Ihres Kontos", + "Track activities done within your platform": "Verfolgen Sie Aktivitäten innerhalb Ihrer Plattform", + "No audit logs found": "Keine Audit-Logs gefunden", + "Come back later when you have some activity to audit": "Kommen Sie später zurück, wenn Sie eine Aktivität zum Audit haben", + "Resource": "Ressource", + "Details": "Details", + "N/A": "N/A", + "Flow Run": "Flusslauf", + "Flow": "Ablauf", + "User": "Benutzer", + "Signing Key": "Signaturschlüssel", + "Project Role Management": "Projektrolle Management", + "Define custom roles and permissions to control what your team members can access and modify": "Definieren Sie benutzerdefinierte Rollen und Berechtigungen um zu kontrollieren, was Ihre Teammitglieder zugreifen und ändern können", + "Define custom roles and permissions that can be assigned to your team members": "Definieren Sie benutzerdefinierte Rollen und Berechtigungen, die Ihren Teammitgliedern zugewiesen werden können", + "New Role": "Neue Rolle", + "Contact sales to unlock custom roles": "Verkäufe kontaktieren, um benutzerdefinierte Rollen freizuschalten", + "Create New Role": "Neue Rolle erstellen", + "View ": "Ansicht ", + "Edit ": "Bearbeiten ", + "Role Name": "Rollenname", + "Permissions": "Berechtigungen", + "None": "Keine", + "Read": "Lesen", + "Write": "Schreiben", + "Create": "Erstellen", + "Email": "E-Mail", + "First Name": "Vorname", + "Last Name": "Nachname", + "Roles": "Rollen", + "View the users assigned to this role": "Benutzer anzeigen, die dieser Rolle zugewiesen sind", + "Role": "Rolle", + "No users found": "Keine Benutzer gefunden", + "Starting by assigning users to this role": "Beginnend mit der Zuweisung von Benutzern zu dieser Rolle", + "Project Role entry deleted successfully": "Projektrolle erfolgreich gelöscht", + "Updated": "Aktualisiert", + "No project roles found": "Keine Projektrollen gefunden", + "Create custom project roles to manage permissions for platform users": "Erstellen Sie benutzerdefinierte Projektrollen, um Berechtigungen für Plattformnutzer zu verwalten", + "Show Users": "Benutzer anzeigen", + "View Role": "Rolle anzeigen", + "Edit Role": "Rolle bearbeiten", + "Delete Role": "Rolle löschen", + "Project Role": "Projektrolle", + "Unlock Embedding Through JS SDK": "Einbetten durch JS SDK entsperren", + "Enable signing keys to access embedding functionalities.": "Signaturschlüssel aktivieren, um auf Einbettungsfunktionen zuzugreifen.", + "New Signing Key": "Neuer Signaturschlüssel", + "No signing keys found": "Keine Signaturschlüssel gefunden", + "Create a signing key to authenticate users with embedding": "Erstellen Sie einen Signaturschlüssel, um Benutzer mit Einbettung zu authentifizieren", + "Delete Signing Key": "Signaturschlüssel löschen", + "Are you sure you want to delete this signing key?": "Sind Sie sicher, dass Sie diesen Signaturschlüssel löschen möchten?", + "Signing Key Created": "Signierungsschlüssel erstellt", + "Create New Signing Key": "Neuen Signaturschlüssel erstellen", + "Signing Key Name": "Signierungsschlüssel Name", + "Allowed domains updated": "Erlaubte Domains aktualisiert", + "Update": "Aktualisieren", + "Enable": "Aktivieren", + "Configure Allowed Domains": "Erlaubte Domains konfigurieren", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Geben Sie die erlaubten Domains für die Benutzer zu authentifizieren, Leere Liste erlaubt alle Domains.", + "Add Domain": "Domain hinzufügen", + "Allow logins through {providerName}'s single sign-on functionality.": "Erlaube Logins über {providerName}s Single-Sign-On Funktionalität.", + "Email authentication updated": "E-Mail-Authentifizierung aktualisiert", + "Enable Single Sign On": "Single Sign On aktivieren", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Lassen Sie Ihre Benutzer sich bei Ihrem aktuellen SSO-Anbieter anmelden oder geben Sie ihnen selbst Zugang zur Anmeldung", + "Allowed Domains": "Erlaubte Domains", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Benutzern erlauben, sich mit bestimmten Domains zu authentifizieren. Leer lassen um alle Domains zuzulassen.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Erlaubte E-Mail-Anmeldung", + "Allow logins through email and password.": "Erlaube Anmeldungen per E-Mail und Passwort.", + "Single sign on settings updated": "Einzelanmeldung auf Einstellungen aktualisiert", + "Disable": "Deaktivieren", + "Configure {provider} SSO": "{provider} SSO konfigurieren", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Lesen Sie mehr Informationen darüber, wie Sie {provider} SSO [here]konfigurieren (https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} Client-ID", + "{provider} Client Secret": "{provider} Client Geheimnis", + "Single sign-on settings updated": "Einzelne Anmeldeeinstellungen aktualisiert", + "Configure SAML 2.0 SSO": "SAML 2.0 SSO konfigurieren", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nBitte überprüfen Sie die folgende Dokumentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDP Metadaten", + "IDP Certificate": "IDP-Zertifikat", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "Basis-URL", + "Resource Name": "Ressourcenname", + "Deployment Name": "Einsatzname", + "Saving": "Speichern", + "Activepieces Copilot": "Copilot Activepieces", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot ist konfiguriert und bereit, Ihren Benutzern zu helfen schneller Ströme mit Hilfe von AI zu bauen.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Konfigurieren Sie Activepieces Copilot so, dass Ihre Benutzer schneller Ströme mit Hilfe von AI erstellen können.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Legen Sie Ihre KI-Anbieter & Copilot-Einstellungen fest, damit Ihre Nutzer ein nahtloses Bauerlebnis mit unseren universellen KI-Stücken genießen", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Legen Sie die Zugangsdaten des Anbieters fest, die von universellen KI-Stücken verwendet werden, also Text-KI.", + "AI Providers": "AI Providers", + "Copilot": "Copilot", + "Configure credentials for {providerName} AI provider.": "Konfigurieren Sie Anmeldedaten für {providerName} KI-Provider.", + "Update AI Provider": "KI-Anbieter aktualisieren", + "Enable AI Provider": "KI-Anbieter aktivieren", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Bitte geben Sie eine gültige Domain ein", + "Your changes have been saved.": "Ihre Änderungen wurden gespeichert.", + "The domain is already added.": "Die Domain ist bereits hinzugefügt.", + "Add Custom Domain": "Eigene Domain hinzufügen", + "Enter a domain name without a protocol (e.g. example.com)": "Geben Sie einen Domänennamen ohne Protokoll ein (z.B. beispiel.com)", + "Logo URL": "Logo-URL", + "Icon URL": "Icon-URL", + "Favicon URL": "Favicon URL", + "Default Language": "Standardsprache", + "Select Language": "Sprache auswählen", + "No Languages": "Keine Sprachen", + "Primary Color": "Primäre Farbe", + "Custom Domains": "Eigene Domains", + "No domains added yet.": "Noch keine Domains hinzugefügt.", + "Verified": "Verifiziert", + "Pending, please contact the support for dns verification.": "Ausstehend, kontaktieren Sie bitte den Support für die DNS-Verifikation.", + "Are you sure you want to delete {domain}?": "Sind Sie sicher, dass Sie {domain} löschen möchten?", + "Brand Activepieces": "Marke Activepieces", + "Give your users an experience that looks like you by customizing the color, logo and more": "Geben Sie Ihren Benutzern ein Erlebnis, das wie Sie aussieht, indem Sie die Farbe, das Logo und mehr anpassen", + "Configure the appearance and SMTP settings for your platform.": "Konfigurieren Sie das Aussehen und die SMTP-Einstellungen für Ihre Plattform.", + "Invalid host": "Ungültiger Host", + "Invalid username": "Ungültiger Benutzername", + "Invalid password": "Ungültiges Passwort", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Ungültiger Absendername", + "SMTP is configured": "SMTP ist konfiguriert", + "Mail Server": "Mail-Server", + "Set up your SMTP settings to send emails from your domain.": "Richten Sie Ihre SMTP-Einstellungen ein, um E-Mails von Ihrer Domain zu senden.", + "Disable Mail Server": "Mail-Server deaktivieren", + "Are you sure you want to disable your mail server?": "Sind Sie sicher, dass Sie Ihren Mailserver deaktivieren möchten?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "Dies hindert Sie daran, E-Mails für Fragen, Quotenbeschränkungen, Einladungen und Passwort zu versenden.", + "mail server": "mailserver", + "Host": "Host", + "Port": "Port", + "Username": "Benutzername", + "Password": "Kennwort", + "Sender Email": "Absender-E-Mail", + "Sender Name": "Absendername", + "Enable Global Connections": "Globale Verbindungen aktivieren", + "Manage platform-wide connections to external systems.": "Verwalten von plattformweiten Verbindungen zu externen Systemen.", + "No global connections found": "Keine globalen Verbindungen gefunden", + "Create a global connection that can be shared to multiple projects": "Erstelle eine globale Verbindung, die mit mehreren Projekten geteilt werden kann", + "License key is invalid": "Lizenzschlüssel ist ungültig", + "Invalid license key": "Ungültiger Lizenzschlüssel", + "License activated!": "Lizenz aktiviert!", + "Activate License Key": "Lizenzschlüssel aktivieren", + "Let the magic begin!": "Lassen Sie die Magie beginnen!", + "Activate": "Aktivieren", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "Diese Funktion ist noch nicht in der Cloud bedient, bitte kontaktieren Sie sales@activepieces.com. ", + "This feature is not available in your current edition. ": "Diese Funktion ist in Ihrer aktuellen Ausgabe nicht verfügbar. ", + "Learn how to upgrade": "Lernen Sie, wie Sie upgraden", + "Activate your platform and unlock enterprise features": "Aktivieren Sie Ihre Plattform und entsperren Sie die Unternehmensfunktionen", + "Activate License": "Lizenz aktivieren", + "Expiration": "Ablaufdatum", + "Valid until": "Gültig bis", + "Expired": "Abgelaufen", + "Expires soon": "Läuft bald ab", + "Features": "Eigenschaften", + "Applying Tags...": "Tags werden angewendet...", + "Tags applied.": "Tags angewandt.", + "Tag created": "Tag erstellt", + "Tag": "Markierung", + "Tags": "Tags", + "Control Pieces": "Steuerbausteine", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Zeigen Sie die Stücke an, die Ihren Benutzern am meisten wichtig sind, und verstecken Sie diejenigen, die Ihnen nicht gefallen.", + "Manage the pieces that are available to your users": "Verwalten Sie die Teile, die Ihren Benutzern zur Verfügung stehen", + "Start by installing pieces that you want to use in your automations": "Beginnen Sie mit der Installation der Teile, die Sie in Ihren Automatisierungen verwenden möchten", + "Piece Name": "Bausteinname", + "Hide this piece from all projects": "Dieses Stück aus allen Projekten ausblenden", + "Show this piece for all projects": "Dieses Stück für alle Projekte anzeigen", + "Unpin this piece": "Dieses Stück lösen", + "Pin this piece": "Dieses Stück anheften", + "Pieces synced": "Teile synchronisiert", + "Pieces have been synced from the activepieces cloud.": "Teile wurden aus der Aktivepieces Cloud synchronisiert.", + "OAuth2 Credentials Deleted": "OAuth2 Zugangsdaten gelöscht", + "OAuth2 Credentials Updated": "OAuth2 Zugangsdaten aktualisiert", + "Configure OAuth2 APP": "OAuth2 APP konfigurieren", + "Delete OAuth2 APP": "OAuth2 App löschen", + "Templates deleted successfully": "Vorlagen erfolgreich gelöscht", + "Delete Templates": "Vorlagen löschen", + "Are you sure you want to delete the selected templates?": "Sind Sie sicher, dass Sie die ausgewählten Vorlagen löschen möchten?", + "New Template": "Neue Vorlage", + "Unlock Templates": "Vorlagen entsperren", + "Convert the most common automations into reusable templates 1 click away from your users": "Konvertieren Sie die gängigsten Automatisierungen in wiederverwendbare Vorlagen 1 Klick weg von Ihren Benutzern", + "Convert the most common automations into reusable templates": "Konvertieren Sie die gängigsten Automatisierungen in wiederverwendbare Vorlagen", + "No templates found": "Keine Vorlagen gefunden", + "Create a template for your user to inspire them": "Erstellen Sie eine Vorlage für Ihren Benutzer, um sie zu inspirieren", + "Edit template": "Vorlage bearbeiten", + "Template is required": "Vorlage ist erforderlich", + "Update New Template": "Neue Vorlage aktualisieren", + "Create New Template": "Neue Vorlage erstellen", + "Template Name": "Vorlagenname", + "Description": "Beschreibung", + "Template Description": "Vorlagenbeschreibung", + "Blog URL": "Blog-URL", + "Template Blog URL": "Template-Blog-URL", + "Template": "Vorlage", + "Invalid JSON": "Ungültiges JSON", + "User deleted successfully": "Benutzer erfolgreich gelöscht", + "User activated successfully": "Benutzer erfolgreich aktiviert", + "User deactivated successfully": "Benutzer erfolgreich deaktiviert", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Verwalten Sie Ihre Benutzer und deren Zugriff auf Ihre Projekte", + "Start inviting users to your project": "Beginne Benutzer in dein Projekt einzuladen", + "External Id": "Externe Id", + "Admin": "Admin", + "Member": "Mitglied", + "Activated": "Aktiviert", + "Deactivated": "Deaktiviert", + "Edit user": "Benutzer bearbeiten", + "Admin cannot be deactivated": "Admin kann nicht deaktiviert werden", + "Deactivate user": "Benutzer deaktivieren", + "Activate user": "Benutzer aktivieren", + "Delete User": "Benutzer löschen", + "Are you sure you want to delete this user?": "Sind Sie sicher, dass Sie diesen Benutzer löschen möchten?", + "Delete user": "Benutzer löschen", + "Update User Role": "Benutzerrolle aktualisieren", + "Meeting Summary Flow": "Meeting Zusammenfassung Flow", + "Added new features and fixed bugs": "Neue Funktionen und behobene Fehler hinzugefügt", + "Flows Changes": "Strömungsänderungen", + "Connections Changes": "Verbindungsänderungen", + "New connections are placeholders and need to be reconnected again": "Neue Verbindungen sind Platzhalter und müssen erneut verbunden werden", + "renamed to": "umbenannt in", + "Tables Changes": "Tabellen-Änderungen", + "No changes to apply": "Keine Änderungen anzuwenden", + "Apply Changes": "Änderungen anwenden", + "Create Git Release": "Git Release erstellen", + "Create Project Release": "Projektversion erstellen", + "Create Rollback to": "Rollback erstellen zu", + "Source": "Quelle", + "Rollback": "Rollback", + "Imported At": "Importiert am", + "Imported By": "Importiert von", + "Track and manage your project version history and deployments. ": "Verfolgen und verwalten Sie Ihre Projektversionsgeschichte und -verteilungen. ", + "Environments & Releases": "Umgebungen & Releases", + "Project Releases": "Projektveröffentlichungen", + "Create Release": "Release erstellen", + "From Git": "Von Git", + "From Project": "Von Projekt", + "No project releases found": "Keine Projektveröffentlichungen gefunden", + "Create a project release to get started": "Erstellen Sie eine Projektversion um loszulegen", + "Please select project": "Bitte Projekt auswählen", + "No Changes Found": "Keine Änderungen gefunden", + "There are no differences to apply": "Es gibt keine Unterschiede zum Anwenden", + "Please select a project": "Bitte wählen Sie ein Projekt", + "Search projects...": "Projekte suchen...", + "Review Changes": "Änderungen überprüfen", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Importiert von", + "from": "von", + "No description provided": "Keine Beschreibung angegeben", + "Invitation only sign up": "Nur Einladung anmelden", + "Please ask your administrator to add you to the organization.": "Bitten Sie Ihren Administrator, Sie zur Organisation hinzuzufügen.", + "Something went wrong, please try again.": "Etwas ist schief gelaufen, bitte versuche es erneut.", + "Please try again.": "Bitte versuchen Sie es erneut.", + "Please enter a valid email address": "Bitte geben Sie eine gültige E-Mail-Adresse ein", + "The email is already added.": "Die E-Mail wurde bereits hinzugefügt.", + "Add email": "E-Mail hinzufügen", + "Only project admins can do this": "Nur Projektadministratoren können dies tun", + "Add Alert Email": "E-Mail-Benachrichtigung hinzufügen", + "Enter the email address to receive alerts.": "Zum Erhalt von Benachrichtigungen E-Mail-Adressen hinzufügen.", + "Add Email": "E-Mail hinzufügen", + "Emails": "E-Mails", + "Add email addresses to receive alerts.": "Zum Erhalt von Benachrichtigungen E-Mail-Adressen hinzufügen.", + "No emails added yet.": "Es wurden noch keine E-Mail-Adressen hinzugefügt.", + "Choose what you want to be notified about.": "Wählen Sie aus, worüber Sie benachrichtigt werden möchten.", + "Project and alert permissions are required to change this setting.": "Projekt- und Alarmberechtigungen werden benötigt, um diese Einstellung zu ändern.", + "Every Failed Run": "Jeder fehlgeschlagene Start", + "Get an email alert when a flow fails.": "Erhalten Sie eine E-Mail-Benachrichtigung, wenn ein Ablauf fehlschlägt.", + "Get an email alert when a new issue created.": "Erhalte eine E-Mail-Benachrichtigung, wenn ein neues Problem erstellt wird.", + "Never": "Nie", + "Turn off email notifications.": "E-Mail-Benachrichtigungen deaktivieren.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Passen Sie das Erscheinungsbild der App an. Automatisch zwischen Tages- und Nachtthemen wechseln.", + "Select the theme for the dashboard.": "Wählen Sie das Theme für das Dashboard.", + "Light": "Hell", + "Dark": "Dunkel", + "Select the language that will be used in the dashboard.": "Wählen Sie die Sprache aus, die im Dashboard verwendet wird.", + "Select language": "Sprache auswählen", + "Search language...": "Sprache suchen...", + "No language found.": "Keine Sprache gefunden.", + "Help us translate Activepieces to your language.": "Helfen Sie uns Activepieces in Ihre Sprache zu übersetzen.", + "Learn more": "Mehr erfahren", + "Git Connection Removed": "Git-Verbindung entfernt", + "Your Git repository has been successfully disconnected": "Ihr Git-Repository wurde erfolgreich getrennt", + "Enable Environments": "Umgebungen aktivieren", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Versionskontrolle und Team-Kollaboration über Entwicklungs-, Staging- und Produktionsumgebungen hinweg", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Verbinden Sie sich mit Git, um die Versionskontrolle zu aktivieren, Ihre Ströme zu sichern und mehrere Umgebungen zu verwalten. ", + "Repository URL": "Repository-URL", + "Not connected": "Nicht verbunden", + "Project Folder": "Projektordner", + "Releases Enabled": "Releases aktiviert", + "You have successfully enabled releases": "Sie haben die Veröffentlichungen erfolgreich aktiviert", + "Enable releases to easily create and manage project releases.": "Aktivieren Sie Veröffentlichungen, um Projektversionen zu erstellen und zu verwalten.", + "The external ID is already taken.": "Die externe ID ist bereits vergeben.", + "Manage general settings for your project.": "Verwalten Sie allgemeine Einstellungen für Ihr Projekt.", + "Used to identify the project based on your SaaS ID": "Wird verwendet, um das Projekt basierend auf deiner SaaS-ID zu identifizieren", + "org-3412321": "org-3412321", + "Delete {name}": "Lösche {name}", + "This will permanently delete this piece, all steps using it will fail.": "Dies wird diesen Baustein dauerhaft löschen, alle Schritte, die ihn verwenden, werden fehlschlagen.", + "Add a piece to your project that you want to use in your automations": "Fügen Sie Ihrem Projekt ein Stück hinzu, das Sie in Ihren Automatisierungen verwenden möchten", + "Pieces list updated": "Stückliste aktualisiert", + "Manage Pieces": "Teile verwalten", + "Choose which pieces you want to be available for your current project users": "Wählen Sie aus, welche Teile für Ihre aktuellen Projektbenutzer verfügbar sein sollen", + "Unlock Team Permissions": "Team-Berechtigungen freischalten", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "Sie können Nutzer kostenlos in der Community-Edition auf Ihre Plattform einladen. Für erweiterte Rollen und Berechtigungen fordern Sie eine Testversion an", + "Project Members": "Projektmitglieder", + "Invite your team members to collaborate.": "Laden Sie Ihre Teammitglieder zur Zusammenarbeit ein.", + "No members are added to this project.": "Zu diesem Projekt wurden keine Mitglieder hinzugefügt.", + "Pending Invitations": "Ausstehende Einladungen", + "No pending invitation.": "Keine ausstehende Einladung.", + "templateId is missing": "Vorlagen-ID fehlt", + "Me Only": "Nur mich", + "Unresolved": "Unerledigt", + "Resolved": "Gelöst", + "Title": "Titel", + "Date Created": "Erstellungsdatum", + "Manage todos for your project that are created by automations": "Verwalten Sie Todos für Ihr Projekt, die von Automatisierungen erstellt werden", + "No todos found": "Keine Todos gefunden", + "You do not have any pending todos. Great job!": "Sie haben keine ausstehenden Todos. Großartiger Job!", + "Write a comment...": "Kommentar schreiben...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "Diese Funktion wird noch getestet und kann oft geändert werden", + "Failed to copy to clipboard": "Fehler beim Kopieren in die Zwischenablage", + "{number} items selected": "{number} Elemente ausgewählt", + "Select All": "Alle auswählen", + "No results found.": "Keine Ergebnisse gefunden.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect muss innerhalb des MultiSelectProviders verwendet werden", + "Unset": "Entfernen", + "Refresh": "Aktualisieren", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} mehr", + "Removed {entityName}": "Entfernte {entityName}", + "Download File": "Datei herunterladen", + "Copied to clipboard": "In die Zwischenablage kopiert", + "File is not available after execution.": "Datei ist nach der Ausführung nicht verfügbar.", + "Available for Projects": "Für Projekte verfügbar", + "Select projects": "Projekte auswählen", + "No items": "Keine Elemente", + "Previous": "Vorherige", + "to": "zu", + "Last Week": "Letzte Woche", + "Last Month": "Letzten Monat", + "Last 3 Months": "Letzten 3 Monate", + "Last 6 Months": "Letzte 6 Monate", + "Next 7 days": "Nächste 7 Tage", + "Next 30 days": "Nächste 30 Tage", + "Next 90 days": "Nächste 90 Tage", + "Next 180 days": "Nächste 180 Tage", + "Select Time Range": "Zeitbereich auswählen", + "Clear": "Leeren", + "Download": "Download", + "Go to Dashboard": "Zum Dashboard gehen", + "Select a file": "Datei auswählen", + "Press space to separate values": "Drücken Sie Leertaste um Werte zu trennen", + "AM": "MT", + "PM": "PN", + "Already have an account?": "Sie haben bereits ein Konto?", + "Sign in": "Anmelden", + "Don't have an account?": "Sie besitzen noch kein Konto?", + "Sign up": "Registrieren", + "Welcome Back!": "Willkommen zurück!", + "Enter your email below to sign in to your account": "Bitte geben Sie Ihre E-Mail-Adresse unten ein, um sich in Ihrem Konto anzumelden", + "Let's Get Started!": "Lassen Sie uns beginnen!", + "Create your account and start flowing!": "Erstellen Sie Ihr Konto und fangen Sie an zu fließen!", + "Your password was changed successfully": "Ihr Kennwort wurde erfolgreich geändert", + "Your password reset request has expired, please request a new one": "Ihre Anfrage zum Zurücksetzen des Kennwortes ist abgelaufen, bitte fordern Sie eine neue an", + "Reset Password": "Kennwort zurücksetzen", + "Enter your new password": "Geben Sie ein neues Kennwort ein", + "Password is required": "Kennwort erforderlich", + "Verification email resent, if previous one expired.": "Bestätigungs-E-Mail gesendet, wenn die vorherige abgelaufen ist.", + "Password reset link resent, if previous one expired.": "Link zum Zurücksetzen des Passworts gesendet, wenn der vorherige abgelaufen ist.", + "We sent you a link to complete your registration to": "Wir haben dir einen Link geschickt, um deine Registrierung abzuschließen an", + "We sent you a link to reset your password to": "Wir haben Ihnen einen Link zum Zurücksetzen Ihres Passworts gesendet auf", + "Didn't receive an email or it expired?": "Wurde keine E-Mail erhalten oder es ist abgelaufen?", + "Resend": "Erneut senden", + "Please enter your email": "Bitte geben Sie Ihre E-Mail-Adresse ein", + "Check Your Inbox": "Überprüfen Sie Ihren Posteingang", + "If the user exists we'll send you an email with a link to reset your password.": "Wenn der Benutzer existiert, senden wir Ihnen eine E-Mail mit einem Link zum Zurücksetzen des Kennworts.", + "Send Password Reset Link": "E-Mail zum Zurücksetzen des Kennworts senden", + "Back to sign in": "Zurück zur Anmeldung", + "Email is invalid": "E-Mail-Adresse ist ungültig", + "Something went wrong, please try again later": "Etwas ist schiefgelaufen, bitte versuchen Sie es später noch einmal", + "Invalid email or password": "Ungültige E-Mail oder Kennwort", + "User has been deactivated": "Benutzer wurde deaktiviert", + "Email domain is disallowed": "E-Mail-Domain ist nicht erlaubt", + "Email authentication has been disabled": "E-Mail-Authentifizierung wurde deaktiviert", + "Forgot your password?": "Kennwort vergessen?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "Anmeldung ist beschränkt. Sie benötigen eine Einladung, um beizutreten. Bitte wenden Sie sich an den Administrator.", + "Email is already used": "E-Mail wird bereits verwendet", + "Email authentication is disabled": "E-Mail-Authentifizierung ist deaktiviert", + "First name is required": "Vorname erforderlich", + "Last name is required": "Nachname erforderlich", + "Email is required": "E-Mail ist erforderlich", + "Receive updates and newsletters from activepieces": "Erhalten Sie Mitteilungen und Newsletter von activepieces", + "By creating an account, you agree to our": "Mit der Erstellung eines Kontos, stimmen Sie zu, zu unseren", + "terms of service": "Nutzungsbedingungen", + "privacy policy": "Datenschutzrichtlinien", + "Sign up With": "Registrieren mit", + "Google": "Google", + "Sign in With": "Anmelden mit", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "E-Mail wurde verifiziert. Sie werden weitergeleitet, um sich anzumelden...", + "Verifying email...": "E-Mail-Adresse wird bestätigt...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "Einladung ist abgelaufen, sobald Sie sich erneut anmelden, können Sie die Bestätigungs-E-Mail erneut senden.", + "Redirecting to sign in...": "Weiterleitung zum Anmelden...", + "Password must contain at least one special character": "Das Kennwort muss mindestens ein Sonderzeichen enthalten", + "Password must contain at least one lowercase letter": "Das Kennwort muss mindestens einen Kleinbuchstaben enthalten", + "Password must contain at least one uppercase letter": "Das Kennwort muss mindestens einen Großbuchstaben enthalten", + "Password must contain at least one number": "Das Kennwort muss mindestens eine Ziffer enthalten", + "8-64 Characters": "8-64 Zeichen", + "Special Character": "Sonderzeichen", + "Lowercase": "Kleinbuchstaben", + "Uppercase": "Großbuchstaben", + "Number": "Zahl", + "Connection has been updated.": "Verbindung wurde aktualisiert.", + "Edit Global Connection": "Globale Verbindung bearbeiten", + "Connection has been renamed.": "Verbindung wurde umbenannt.", + "New Connection Name": "Neuer Verbindungsname", + "Connection name already used": "Verbindungsname bereits verwendet", + "Please select at least one project": "Bitte wählen Sie mindestens ein Projekt aus", + "Run Succeeded": "Ausführung erfolgreich", + "Run Failed": "Ausführung fehlgeschlagen", + "Flow Run is paused": "Ausführung ist pausiert", + "Run Failed due to quota exceeded": "Ausführung fehlgeschlagen wegen Quota überschritten", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Ausführen fehlgeschlagen, da das Speicherlimit von {memoryLimit} MB überschritten wurde", + "Run exceeded {timeout} seconds, try to optimize your steps.": "Ausführung überschritten {timeout} Sekunden, versuchen Sie Ihre Schritte zu optimieren.", + "Run failed for an unknown reason, contact support.": "Ausführung aus unbekanntem Grund fehlgeschlagen, bitte kontaktieren Sie den Support.", + "Unknown": "Unbekannt", + "Exit Run": "Ausführung beenden", + "Select shown": "Angezeigte auswählen", + "Select all": "Alles auswählen", + "Start Time": "Startzeit", + "Runs replayed successfully": "Replays erfolgreich wiedergegeben", + "Retry": "Wiederholen", + "all except": "alle außer", + "all": "alle", + "Only failed runs can be retried from failed step": "Nur fehlgeschlagene Abläufe können aus fehlgeschlagenen Schritt erneut ausprobiert werden", + "No flow runs found": "Keine Abläufe gefunden", + "Come back later when your automations start running": "Kommen Sie später, wenn Ihre Automatisierungen starten", + "Step running": "Schritt läuft", + "Step paused": "Schritt pausiert", + "Step Stopped": "Schritt angehalten", + "Step Succeeded": "Schritt erfolgreich", + "Step Failed": "Schritt fehlgeschlagen", + "Please publish flow first": "Bitte den Ablauf zuerst veröffentlichen", + "Flow is on": "Ablauf ist aktiv", + "Flow is off": "Ablauf ist inaktiv", + "Permission Needed": "Berechtigung benötigt", + "Draft Version": "Entwurfsversion", + "Published Version": "Veröffentlichte Version", + "Locked Version": "Gesperrte Version", + "flowsImported": "{flowsCount, plural, =0 {Keine Ströme importiert} =1 {Flow erfolgreich importiert} other {Flows erfolgreich importiert}}", + "Template file is invalid": "Vorlagendatei ist ungültig", + "No valid templates found. The following files failed to import: ": "Keine gültigen Vorlagen gefunden. Die folgenden Dateien konnten nicht importiert werden: ", + "Please select a file first": "Bitte wählen Sie zuerst eine Datei", + "Unsupported file type": "Nicht unterstützter Dateityp", + "Import Flow": "Ablauf importieren", + "Warning": "Warnung", + "Importing a flow will overwrite your current one.": "Durch das Importieren eines Flow wird dein aktueller Flow überschrieben.", + "Select a folder": "Ordner auswählen", + "Folders": "Ordner", + "Please select a folder": "Bitte wählen Sie einen Ordner aus", + "Moved flows successfully": "Verschobene Ströme erfolgreich", + "Move Selected Flows": "Ausgewählte Flows verschieben", + "Select Folder": "Ordner auswählen", + "No Folders": "Keine Ordner", + "Flow has been renamed.": "Der Ablauf wurde umbenannt.", + "New Flow Name": "Neuer Ablaufname", + "Use Template": "Vorlage verwenden", + "Browse Templates": "Vorlagen durchsuchen", + "Search templates": "Vorlagen suchen", + "No templates found, try adjusting your search": "Keine Vorlagen gefunden, versuche deine Suche anzupassen", + "Read more about this template in": "Lesen Sie mehr über diese Vorlage in", + "this blog!": "dieser Blog!", + "Share Template": "Vorlage teilen", + "Generate or update a template link for the current flow to easily share it with others.": "Generieren oder aktualisieren Sie einen Vorlage-Link für den aktuellen Ablauf, um ihn einfach mit anderen zu teilen.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "Die Vorlage wird keine Zugangsdaten in Verbindungsfeldern haben, um sensible Informationen sicher zu halten.", + "A short description of the template": "Eine kurze Beschreibung der Vorlage", + "Flow Is In Use": "Flow ist in Gebrauch", + "Flow is being used by another user, please try again later.": "Flow wird von einem anderen Benutzer genutzt, bitte versuchen Sie es später erneut.", + "Flow has been published.": "Der Ablauf wurde veröffentlicht.", + "Flows have been exported.": "Ströme wurden exportiert.", + "Run": "Ausführung", + "Real time flow": "Echtzeitablauf", + "Flow can't be published with empty trigger {name}": "Ablauf kann nicht mit leerem Auslöser {name} veröffentlicht werden", + "Please contact support as your published flow has a problem": "Bitte kontaktiere den Support, da dein veröffentlichter Fluss ein Problem hat", + "Actions": "Aktionen", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Sind Sie sicher, dass Sie diese Ströme löschen möchten? Dies wird die Ströme, alle ihre Daten und alle Hintergrundausläufe dauerhaft löschen.", + "You are on a development branch, this will not delete the flows from the remote repository.": "Sie befinden sich in einem Entwicklungszweig, dies löscht nicht die Ströme aus dem Remote-Repository.", + "Please enter folder name": "Bitte Ordnernamen eingeben", + "Added folder successfully": "Ordner erfolgreich hinzugefügt", + "The folder name already exists.": "Der Ordnername existiert bereits.", + "New Folder": "Neuer Ordner", + "Folder Name": "Ordnername", + "Loading...": "Lädt...", + "Delete {folderName}": "{folderName} löschen", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "Wenn Sie diesen Ordner löschen, werden die Abläufe behalten und in den Ordner \"Nicht kategorisiert\" verschoben.", + "All flows": "Alle Abläufe", + "Please enter a folder name": "Bitte geben Sie einen Ordnernamen ein", + "Renamed flow successfully": "Umbenennung des Ablaufs erfolgreich", + "Folder name already used": "Ordnername bereits verwendet", + "New Folder Name": "Neuer Ordnername", + "Connected successfully": "Erfolgreich verbunden", + "Connect Git": "Git verbinden", + "Remote URL": "Remote-URL", + "Folder name is the name of the folder where the project will be stored or fetched.": "Ordnername ist der Name des Ordners, in dem das Projekt gespeichert oder abgerufen werden soll.", + "SSH Private Key": "SSH Privatschlüssel", + "The SSH private key to use for authentication.": "Der private SSH-Schlüssel, der zur Authentifizierung verwendet wird.", + "Only published flows can be pushed to Git": "Nur veröffentlichte Ströme können nach Git gedrückt werden", + "Pushed successfully": "Erfolgreich gepumpt", + "Invalid Operation": "Ungültige Operation", + "Commit Message": "Commit Nachricht", + "Enter a commit message to describe the changes you want to push.": "Geben Sie eine Commit-Nachricht ein, um die Änderungen zu beschreiben, die Sie hochladen möchten.", + "Push": "Push", + "This field is required": "Dieses Feld ist erforderlich", + "Your submission was successfully received.": "Ihre Einreichung wurde erfolgreich empfangen.", + "Flow not found": "Ablauf nicht gefunden", + "The flow you are trying to submit to does not exist.": "Der Fließ, zu dem Sie zu übermitteln versuchen, existiert nicht.", + "The flow failed to execute.": "Der Ablauf konnte nicht ausgeführt werden.", + "Submit": "Absenden", + "issues-notification": "Problembenachrichtigung", + "Please select a package type": "Bitte wählen Sie einen Pakettyp", + "package.json not found in archive": "package.json im Archiv nicht gefunden", + "Error processing archive file": "Fehler beim Verarbeiten der Archivdatei", + "Please upload a .tgz file": "Bitte laden Sie eine .tgz Datei hoch", + "Piece name is required for NPM Registry": "Stückname ist erforderlich für die NPM Registry", + "Piece version is required for NPM Registry": "Piece-Version ist erforderlich für die NPM Registry", + "Piece installed": "Baustein installiert", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "Ein Stück mit diesem Namen und dieser Version ist bereits installiert. Bitte aktualisieren Sie die Versionsnummer in package.json und versuchen Sie es erneut.", + "Install Piece": "Baustein installieren", + "Install a piece": "Baustein installieren", + "Package Type": "Pakettyp", + "NPM Registry": "NPM Registry", + "Packed Archive (.tgz)": "Gepacktes Archiv (.tgz)", + "Piece Version": "Bausteinversion", + "Package Archive": "Paketarchiv", + "Package archive": "Paketarchiv", + "Powerful Node.js & TypeScript code with npm": "Leistungsstarker Node.js & TypeScript Code mit npm", + "Loop on Items": "Schleife auf Items", + "Split your flow into branches depending on condition(s)": "Teile deinen Fluss in Äste je nach Bedingung(en)", + "Empty Trigger": "Leerer Auslöser", + "An internal error occurred while fetching data, please contact support": "Beim Abrufen der Daten ist ein interner Fehler aufgetreten. Bitte kontaktieren Sie den Support", + "An internal error occurred, please contact support": "Ein interner Fehler ist aufgetreten, bitte kontaktieren Sie den Support", + "Custom Javascript Code": "Eigener Javascript-Code", + "Router": "Router", + "recordsCount": "aufnahmeanzahl", + "selected": "ausgewählt", + "All records selected": "Alle Datensätze ausgewählt", + "fieldsCount": "feldanzahl", + "Saving...": "Speichern...", + "Loading more...": "Lade mehr...", + "Export Table": "Tabelle exportieren", + "Delete Records": "Datensätze löschen", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Sind Sie sicher, dass Sie die ausgewählten Einträge löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "record": "aufzeichnen", + "records": "einträge", + "mm/dd/yyy": "mm/tt/jjj", + "Delete Field": "Lösche Feld", + "Are you sure you want to delete this field? This action cannot be undone.": "Sind Sie sicher, dass Sie dieses Feld löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "field": "feld", + "Ignored": "Ignoriert", + "Table": "Tisch", + "CSV": "CSV", + "Field": "Feld", + "Please select a csv file": "Bitte wählen Sie eine csv-Datei", + "Max file size is {maxFileSize}MB": "Maximale Dateigröße beträgt {maxFileSize}MB", + "Import CSV": "CSV importieren", + "Imported records will be added to the bottom of the table": "Importierte Datensätze werden am Ende der Tabelle hinzugefügt", + "Any records after the limit ({maxRecords} records) will be ignored": "Alle Datensätze nach dem Limit ({maxRecords} Datensätze) werden ignoriert", + "CSV File": "CSV-Datei", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "Beim Importieren der csv-Datei ist ein unerwarteter Fehler aufgetreten. Bitte drücken Sie den Kopierfehler und senden Sie ihn an den Support", + "Name must be unique": "Name muss eindeutig sein", + "Type is required": "Typ ist erforderlich", + "Please add at least one option": "Bitte fügen Sie mindestens eine Option hinzu", + "New Field": "Neues Feld", + "Options": "Optionen", + "Name is already taken": "Name ist bereits vergeben", + "Table renamed": "Tisch umbenannt", + "Table name": "Tabellenname", + "Team Invitation Accepted": "Teameinladung angenommen", + "Thank you for accepting the invitation. We are redirecting you right now...": "Vielen Dank, dass Sie die Einladung angenommen haben. Wir leiten Sie gerade weiter...", + "Invalid invitation token. Please try again.": "Ungültiger Einladungstoken. Bitte versuchen Sie es erneut.", + "Role updated successfully": "Rolle erfolgreich aktualisiert", + "Error updating role": "Fehler beim Aktualisieren der Rolle", + "Please try again later": "Bitte später erneut versuchen", + "Edit Role for": "Rolle bearbeiten für", + "Select Role": "Rolle auswählen", + "Avatar": "Avatar", + "Remove {email}": "Entferne {email}", + "Are you sure you want to remove this invitation?": "Sind Sie sicher, dass Sie diese Einladung entfernen möchten?", + "Please select invitation type": "Bitte wählen Sie einen Einladungstyp", + "Please select platform role": "Bitte wählen Sie die Plattformrolle", + "Invitation sent successfully": "Einladung erfolgreich gesendet", + "Please select a project role": "Bitte wählen Sie eine Projektrolle aus", + "Invitation link copied successfully": "Einladungslink erfolgreich kopiert", + "Invite User": "Benutzer einladen", + "Invitation Link": "Einladungslink", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Bitte kopieren Sie den Link unten und teilen Sie ihn mit dem Benutzer, den Sie einladen möchten, die Einladung läuft in 24 Stunden ab.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Geben Sie die E-Mail-Adresse des Benutzers ein, den Sie einladen möchten, die Einladung läuft in 24 Stunden ab.", + "Invite To": "Einladen zu", + "Entire Platform": "Gesamte Plattform", + "Select Project Role": "Projektrolle auswählen", + "Invite": "Einladen", + "Platform Role": "Plattformrolle", + "Select a platform role": "Wählen Sie eine Plattformrolle", + "Are you sure you want to remove this member?": "Sind Sie sicher, dass Sie dieses Mitglied entfernen möchten?", + "Editor": "Redakteur", + "Operator": "Operator", + "Viewer": "Betrachter", + "Select a project role": "Projektrolle auswählen", + "Steps in this flow": "Schritte in diesem Ablauf", + "Invalid Access": "Ungültiger Zugriff", + "Either the project does not exist or you do not have access to it.": "Entweder existiert das Projekt nicht oder Sie haben keinen Zugriff darauf." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/en/translation.json b/packages/react-ui/public/locales/en/translation.json new file mode 100644 index 0000000..e2bee95 --- /dev/null +++ b/packages/react-ui/public/locales/en/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Publish", + "Latest version is published": "Latest version is published", + "Your flow has incomplete steps": "Your flow has incomplete steps", + "Edit Flow": "Edit Flow", + "View Draft": "View Draft", + "Uncategorized": "Uncategorized", + "Go to folder": "Go to folder", + "Support": "Support", + "Runs": "Runs", + "Run Logs": "Run Logs", + "Versions": "Versions", + "Versions History": "Versions History", + "Error generating code": "Error generating code", + "AI Copilot": "AI Copilot", + "i.e Calculate the sum of a list...": "i.e Calculate the sum of a list...", + "Send": "Send", + "Generating Code": "Generating Code", + "Hello there! I am here to generate code that helps with your flow": "Hello there! I am here to generate code that helps with your flow", + "Here are examples of what I am best used for: ": "Here are examples of what I am best used for: ", + "Text Processing": "Text Processing", + "Process strings, dates and data": "Process strings, dates and data", + "Data Operations": "Data Operations", + "Change data from one format to another": "Change data from one format to another", + "Calculations": "Calculations", + "Handle math and statistics": "Handle math and statistics", + "API Integration": "API Integration", + "Connect with external services. Best for simple integrations currently.": "Connect with external services. Best for simple integrations currently.", + "What would you like me to help you with?": "What would you like me to help you with?", + "Insert": "Insert", + "Data Selector": "Data Selector", + "Search": "Search", + "No matching data": "No matching data", + "Try adjusting your search": "Try adjusting your search", + "This trigger needs to have data loaded from your account, to use as sample data.": "This trigger needs to have data loaded from your account, to use as sample data.", + "This step needs to be tested in order to view its data.": "This step needs to be tested in order to view its data.", + "Go to Trigger": "Go to Trigger", + "Go to Step": "Go to Step", + "Select Mode": "Select Mode", + "Move Mode": "Move Mode", + "Reset Zoom": "Reset Zoom", + "Zoom In": "Zoom In", + "Zoom Out": "Zoom Out", + "Fit to View": "Fit to View", + "Replace": "Replace", + "Copy": "Copy", + "Duplicate": "Duplicate", + "Paste After Last Step": "Paste After Last Step", + "Paste Inside Loop": "Paste Inside Loop", + "Paste After": "Paste After", + "Paste Inside...": "Paste Inside...", + "New Branch": "New Branch", + "Paste Inside Branch": "Paste Inside Branch", + "Delete": "Delete", + "Duplicate Branch": "Duplicate Branch", + "Delete Branch": "Delete Branch", + "Invalid Move": "Invalid Move", + "The destination location is a child of the dragged step": "The destination location is a child of the dragged step", + "End": "End", + "Skipped": "Skipped", + "Incomplete settings": "Incomplete settings", + "logo": "logo", + "Step Icon": "Step Icon", + "Branch": "Branch", + "incompleteSteps": "{invalidSteps, plural, =0 {no incomplete steps} =1 {Complete 1 step} other {Complete # steps}}", + "Test Flow": "Test Flow", + "Please test the trigger first": "Please test the trigger first", + "View Only": "View Only", + "Use as Draft": "Use as Draft", + "Are you sure?": "Are you sure?", + "Your current draft version will be overwritten with": "Your current draft version will be overwritten with", + "version #": "version #", + "Cancel": "Cancel", + "Confirm": "Confirm", + "Version": "Version", + "Viewing": "Viewing", + "View": "View", + "Version History": "Version History", + "Error, please try again.": "Error, please try again.", + "Continue on Failure": "Continue on Failure", + "Enable this option to skip this step and continue the flow normally if it fails.": "Enable this option to skip this step and continue the flow normally if it fails.", + "Retry on Failure": "Retry on Failure", + "Automatically retry up to four attempts when failed.": "Automatically retry up to four attempts when failed.", + "Remove": "Remove", + "Add Item": "Add Item", + "File Input": "File Input", + "Date Input": "Date Input", + "Dynamic value": "Dynamic value", + "Select an option": "Select an option", + "Unexpected error, please retry": "Unexpected error, please retry", + "Unexpected error, please refresh the page or contact support": "Unexpected error, please refresh the page or contact support", + "Name can only contain letters, numbers and underscores": "Name can only contain letters, numbers and underscores", + "Ask AI": "Ask AI", + "Create Todo Guide": "Create Todo Guide", + "Where would you like the todo to be reviewed?": "Where would you like the todo to be reviewed?", + "Activepieces Todos": "Activepieces Todos", + "Users will manage tasks directly in Activepieces": "Users will manage tasks directly in Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.", + "External Channel (Slack, Teams, Email, ...)": "External Channel (Slack, Teams, Email, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.", + "Preview (Activepieces Todos)": "Preview (Activepieces Todos)", + "Preview (External channel)": "Preview (External channel)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "You can add the channel before the Wait Step, and configure the logic in the Router step", + "Add Steps": "Add Steps", + "All": "All", + "AI": "AI", + "Core": "Core", + "Apps": "Apps", + "Not available as trigger": "Not available as trigger", + "Not available as action": "Not available as action", + "Let our AI assistant help you out": "Let our AI assistant help you out", + "Or": "Or", + "Request Piece": "Request Piece", + "No pieces found": "No pieces found", + "Please select a piece first": "Please select a piece first", + "All Iterations": "All Iterations", + "Duration": "Duration", + "Input": "Input", + "Output": "Output", + "There are no logs captured for this run.": "There are no logs captured for this run.", + "Logs are kept for {days} days after execution and then deleted.": "Logs are kept for {days} days after execution and then deleted.", + "Run Details": "Run Details", + "Iteration": "Iteration", + "Done": "Done", + "Took": "Took", + "Running": "Running", + "on latest version": "on latest version", + "from failed step": "from failed step", + "Recent Runs": "Recent Runs", + "No runs found": "No runs found", + "Close": "Close", + "OR": "OR", + "And If": "And If", + "+ And": "+ And", + "+ Or": "+ Or", + "(Text) Contains": "(Text) Contains", + "(Text) Does not contain": "(Text) Does not contain", + "(Text) Exactly matches": "(Text) Exactly matches", + "(Text) Does not exactly match": "(Text) Does not exactly match", + "(Text) Starts with": "(Text) Starts with", + "(Text) Does not start with": "(Text) Does not start with", + "(Text) Ends with": "(Text) Ends with", + "(Text) Does not end with": "(Text) Does not end with", + "(List) Contains": "(List) Contains", + "(List) Does not contain": "(List) Does not contain", + "(Number) Is greater than": "(Number) Is greater than", + "(Number) Is less than": "(Number) Is less than", + "(Number) Is equal to": "(Number) Is equal to", + "(Date/time) After": "(Date/time) After", + "(Date/time) Before": "(Date/time) Before", + "(Date/time) Equals": "(Date/time) Equals", + "(Boolean) Is true": "(Boolean) Is true", + "(Boolean) Is false": "(Boolean) Is false", + "(List) Is empty": "(List) Is empty", + "(List) Is not empty": "(List) Is not empty", + "Exists": "Exists", + "Does not exist": "Does not exist", + "Incomplete condition": "Incomplete condition", + "First value": "First value", + "Second value": "Second value", + "Case sensitive": "Case sensitive", + "Execute If": "Execute If", + "The package name is required": "The package name is required", + "Success": "Success", + "Package added successfully": "Package added successfully", + "Could not fetch package version": "Could not fetch package version", + "Add NPM Package": "Add NPM Package", + "Type the name of the npm package you want to add.": "Type the name of the npm package you want to add.", + "Package Name": "Package Name", + "The latest version will be fetched and added": "The latest version will be fetched and added", + "Add": "Add", + "Code": "Code", + "Dependencies": "Dependencies", + "Use code": "Use code", + "Add package": "Add package", + "Inputs": "Inputs", + "Edit Step Name": "Edit Step Name", + "Edit Branch Name": "Edit Branch Name", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.", + "Items": "Items", + "Select an array of items": "Select an array of items", + "Reconnect": "Reconnect", + "Select a connection": "Select a connection", + "Create Connection": "Create Connection", + "Rename": "Rename", + "Move": "Move", + "Add Branch": "Add Branch", + "Execute": "Execute", + "Only the first (left) matching branch": "Only the first (left) matching branch", + "All matching paths from left to right": "All matching paths from left to right", + "Branches": "Branches", + "{field} is required": "{field} is required", + "Tool Sample Data": "Tool Sample Data", + "Fill in the following fields to use them as sample data for the trigger.": "Fill in the following fields to use them as sample data for the trigger.", + "No input fields defined in the schema": "No input fields defined in the schema", + "Save": "Save", + "Test Environment": "Test Environment", + "Assigned to": "Assigned to", + "(Me)": "(Me)", + "Please select status to resolve the todo": "Please select status to resolve the todo", + "Resolve": "Resolve", + "Change status to resolved": "Change status to resolved", + "Send Sample Data to Webhook": "Send Sample Data to Webhook", + "Method": "Method", + "Query Params": "Query Params", + "Headers": "Headers", + "Body": "Body", + "Type": "Type", + "JSON": "JSON", + "Text": "Text", + "Form Data": "Form Data", + "Generate Sample Data": "Generate Sample Data", + "Test Step": "Test Step", + "Retest": "Retest", + "Testing Failed": "Testing Failed", + "Tested Successfully": "Tested Successfully", + "Logs": "Logs", + "There is no sample data available found for this trigger.": "There is no sample data available found for this trigger.", + "Internal error, please try again later.": "Internal error, please try again later.", + "Failed to run test step and no error message was returned": "Failed to run test step and no error message was returned", + "Please fix inputs first": "Please fix inputs first", + "No sample data available": "No sample data available", + "Old results were removed, retest for new sample data": "Old results were removed, retest for new sample data", + "Result #": "Result #", + "The sample data can be used in the next steps.": "The sample data can be used in the next steps.", + "Testing Trigger": "Testing Trigger", + "Action Required": "Action Required", + "testPieceWebhookTriggerNote": "Please go to {pieceName} and trigger {triggerName}.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Send Data to the webhook URL to generate sample data to use in the next steps", + "Test Trigger": "Test Trigger", + "Use Mock Data": "Use Mock Data", + "Load Sample Data": "Load Sample Data", + "Test Tool": "Test Tool", + "home": "home", + "Home": "Home", + "Alerts": "Alerts", + "Releases": "Releases", + "Flows": "Flows", + "Products": "Products", + "MCP": "MCP", + "Tables": "Tables", + "Todos": "Todos", + "Push to Git": "Push to Git", + "Move To": "Move To", + "Duplicating": "Duplicating", + "Import": "Import", + "Exporting": "Exporting", + "Export": "Export", + "Share": "Share", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.", + "You are on a development branch, this will also delete the flow from the remote repository.": "You are on a development branch, this will also delete the flow from the remote repository.", + "flow": "flow", + "Community Support": "Community Support", + "Overview": "Overview", + "Projects": "Projects", + "Users": "Users", + "Setup": "Setup", + "Branding": "Branding", + "Global Connections": "Global Connections", + "Pieces": "Pieces", + "Templates": "Templates", + "License Key": "License Key", + "Security": "Security", + "Audit Logs": "Audit Logs", + "Single Sign On": "Single Sign On", + "Signing Keys": "Signing Keys", + "Project Roles": "Project Roles", + "API Keys": "API Keys", + "Infrastructure": "Infrastructure", + "Workers": "Workers", + "Health": "Health", + "Billing": "Billing", + "Settings": "Settings", + "Contact Sales": "Contact Sales", + "General": "General", + "Appearance": "Appearance", + "Team": "Team", + "Environments": "Environments", + "Project Settings": "Project Settings", + "Exit Platform Admin": "Exit Platform Admin", + "Enter Platform Admin": "Enter Platform Admin", + "Logout": "Logout", + "Misc": "Misc", + "Connections": "Connections", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "Today": "Today", + "Tasks": "Tasks", + "AI Credits": "AI Credits", + "Usage resets in": "Usage resets in", + "Manage": "Manage", + "Unlimited": "Unlimited", + "Enabled": "Enabled", + "Disabled": "Disabled", + "Could not claim the authorization code, make sure you have correct settings and try again.": "Could not claim the authorization code, make sure you have correct settings and try again.", + "Connection failed with error {msg}": "Connection failed with error {msg}", + "You don't have the permission to create a connection.": "You don't have the permission to create a connection.", + "Reconnect {displayName} Connection": "Reconnect {displayName} Connection", + "Connect to {displayName}": "Connect to {displayName}", + "Connection Name": "Connection Name", + "Connection name": "Connection name", + "New Connection": "New Connection", + "Redirect URL": "Redirect URL", + "Client ID": "Client ID", + "Client Secret": "Client Secret", + "Connect": "Connect", + "Disconnect": "Disconnect", + "I would like to use my own App Credentials": "I would like to use my own App Credentials", + "I would like to use predefined App Credentials": "I would like to use predefined App Credentials", + "Permission needed": "Permission needed", + "Connections replaced successfully": "Connections replaced successfully", + "Error": "Error", + "Failed to replace connections": "Failed to replace connections", + "Failed to get affected flows": "Failed to get affected flows", + "Please select a piece": "Please select a piece", + "Please select a connection to replace": "Please select a connection to replace", + "Please select a connection to replace with": "Please select a connection to replace with", + "Replace Connections": "Replace Connections", + "Confirm Replacement": "Confirm Replacement", + "Replace one connection with another.": "Replace one connection with another.", + "This action requires ": "This action requires ", + "reconnecting": "reconnecting", + " any associated MCP pieces.": " any associated MCP pieces.", + "Piece": "Piece", + "Select a piece": "Select a piece", + "Connection to Replace": "Connection to Replace", + "Choose connection to replace": "Choose connection to replace", + "Replaced With": "Replaced With", + "Choose connection to replace with": "Choose connection to replace with", + "All flows will be changed to use the replaced with connection": "All flows will be changed to use the replaced with connection", + "Next": "Next", + "No flows will be affected by this change": "No flows will be affected by this change", + "Back": "Back", + "Unnamed tool": "Unnamed tool", + "This flow is enabled": "This flow is enabled", + "Enable this flow to make it available": "Enable this flow to make it available", + "Piece is updated successfully": "Piece is updated successfully", + "Piece is added successfully": "Piece is added successfully", + "Failed to update piece": "Failed to update piece", + "Failed to add piece": "Failed to add piece", + "Please select a connection": "Please select a connection", + "Your MCP server already has this piece": "Your MCP server already has this piece", + "+ New Connection": "+ New Connection", + "Edit Piece": "Edit Piece", + "Add Piece": "Add Piece", + "Connection": "Connection", + "MCP piece": "MCP piece", + "Failed to update piece status": "Failed to update piece status", + "Connection required": "Connection required", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.", + "piece": "piece", + "Connect your AI assistant to external services": "Connect your AI assistant to external services", + "Collapse": "Collapse", + "Show All": "Show All", + "Server URL": "Server URL", + "Hide the token for security": "Hide the token for security", + "Show the token": "Show the token", + "Generate a new token for security. This will invalidate the current URL.": "Generate a new token for security. This will invalidate the current URL.", + "URL copied to clipboard": "URL copied to clipboard", + "Copy URL to clipboard": "Copy URL to clipboard", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.", + "Max tables reached": "Max tables reached", + "You can't create more than {maxTables} tables": "You can't create more than {maxTables} tables", + "Name": "Name", + "Created": "Created", + "Delete Tables": "Delete Tables", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Are you sure you want to delete the selected tables? This action cannot be undone.", + "table": "table", + "Create and manage your tables to store your automation data": "Create and manage your tables to store your automation data", + "New Table": "New Table", + "No tables have been created yet": "No tables have been created yet", + "Create a table to get started and start managing your automation data": "Create a table to get started and start managing your automation data", + "Error deleting connections": "Error deleting connections", + "Status": "Status", + "Display Name": "Display Name", + "Owner": "Owner", + "App": "App", + "This connection is global and can be managed in the platform admin": "This connection is global and can be managed in the platform admin", + "External ID": "External ID", + "Connected At": "Connected At", + "Confirm Deletion": "Confirm Deletion", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Are you sure you want to delete the selected connections? This action cannot be undone.", + "Deleting connections may cause your Flows or MCP tools to break.": "Deleting connections may cause your Flows or MCP tools to break.", + "Manage project connections to external systems.": "Manage project connections to external systems.", + "No connections found": "No connections found", + "Come back later when you create a automation to manage your connections": "Come back later when you create a automation to manage your connections", + "Steps": "Steps", + "Folder": "Folder", + "Flow name": "Flow name", + "No flows found": "No flows found", + "Create a workflow to start automating": "Create a workflow to start automating", + "Create and manage your flows, run history and run issues": "Create and manage your flows, run history and run issues", + "Issues": "Issues", + "Untitled": "Untitled", + "Create flow": "Create flow", + "From scratch": "From scratch", + "Use a template": "Use a template", + "From local file": "From local file", + "Flow Name": "Flow Name", + "Count": "Count", + "First Seen": "First Seen", + "Last Seen": "Last Seen", + "Issues in {flowDisplayName} is marked as resolved.": "Issues in {flowDisplayName} is marked as resolved.", + "Unlock Issues": "Unlock Issues", + "Track issues in your workflows and troubleshoot them.": "Track issues in your workflows and troubleshoot them.", + "No issues found": "No issues found", + "All your workflows are running smoothly.": "All your workflows are running smoothly.", + "Mark as Resolved": "Mark as Resolved", + "All Flows Are Turned Off": "All Flows Are Turned Off", + "Task Usage Exceeded": "Task Usage Exceeded", + "of the Allowed Limit.": "of the Allowed Limit.", + "When a project tasks limit is reached,": "When a project tasks limit is reached,", + "all flows will be turned off and you will not be able to run any flows.": "all flows will be turned off and you will not be able to run any flows.", + "Please visit": "Please visit", + "Your Plan": "Your Plan", + "and increase your task limit, which requires your payment details.": "and increase your task limit, which requires your payment details.", + "Please contact your admin to increase the project task limit.": "Please contact your admin to increase the project task limit.", + "and increase the project task limit.": "and increase the project task limit.", + "Dismiss": "Dismiss", + "Token rotated successfully": "Token rotated successfully", + "Failed to rotate token": "Failed to rotate token", + "Piece removed successfully": "Piece removed successfully", + "Failed to remove piece": "Failed to remove piece", + "Flow created successfully": "Flow created successfully", + "Failed to create flow": "Failed to create flow", + "Add Flow": "Add Flow", + "Let your AI assistant trigger automations": "Let your AI assistant trigger automations", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Connect to your hosted MCP Server using any MCP client to communicate with tools", + "MCP Server": "MCP Server", + "My Tools": "My Tools", + "Create Flow": "Create Flow", + "Note": "Note", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.", + "This URL grants access to your tools and data. Only share with trusted applications.": "This URL grants access to your tools and data. Only share with trusted applications.", + "Server Configuration": "Server Configuration", + "Hide sensitive data": "Hide sensitive data", + "Show sensitive data": "Show sensitive data", + "Create a new URL. The current one will stop working.": "Create a new URL. The current one will stop working.", + "Copy configuration": "Copy configuration", + "Configuration copied to clipboard": "Configuration copied to clipboard", + "Claude": "Claude", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "Server/Other", + "Note: MCPs only work with": "Note: MCPs only work with", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", not the web version.", + "Prerequisites:": "Prerequisites:", + "Install": "Install", + "Node.js": "Node.js", + "and": "and", + "Open Settings:": "Open Settings:", + "Click the menu and select": "Click the menu and select", + "Developer": "Developer", + "Configure MCP:": "Configure MCP:", + "Click": "Click", + "Edit Config": "Edit Config", + "and paste the configuration below": "and paste the configuration below", + "Save and Restart:": "Save and Restart:", + "Save the config and restart Claude Desktop": "Save the config and restart Claude Desktop", + "Navigate to": "Navigate to", + "Cursor Settings": "Cursor Settings", + "Add Server:": "Add Server:", + "Add new global MCP server": "Add new global MCP server", + "Configure:": "Configure:", + "Paste the configuration below and save": "Paste the configuration below and save", + "Use either method:": "Use either method:", + "Go to": "Go to", + "Advanced Settings": "Advanced Settings", + "Open Command Palette and select": "Open Command Palette and select", + "Windsurf Settings Page": "Windsurf Settings Page", + "Navigate to Cascade:": "Navigate to Cascade:", + "Select": "Select", + "Cascade": "Cascade", + "in the sidebar": "in the sidebar", + "Add Server": "Add Server", + "Add custom server +": "Add custom server +", + "Copy URL": "Copy URL", + "Client Setup Instructions": "Client Setup Instructions", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "After changing connections or flows, reconnect your MCP server for changes to take effect.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.", + "icon": "icon", + "Unlock Analytics": "Unlock Analytics", + "Get insights into your platform usage and performance with our analytics dashboard": "Get insights into your platform usage and performance with our analytics dashboard", + "Active Flows": "Active Flows", + "The number of enabled flows in the platform": "The number of enabled flows in the platform", + "Active Projects": "Active Projects", + "The number of projects with at least one enabled flow": "The number of projects with at least one enabled flow", + "Active Users": "Active Users", + "The number of users logged in the last 30 days": "The number of users logged in the last 30 days", + "Out of {totalusers} total users": "Out of {totalusers} total users", + "Pieces Used": "Pieces Used", + "The number of unique pieces used across all flows": "The number of unique pieces used across all flows", + "Flows with AI": "Flows with AI", + "The number of enabled flows that use AI pieces": "The number of enabled flows that use AI pieces", + "Metrics": "Metrics", + "Executed Tasks": "Executed Tasks", + "Showing total executed tasks for specified time range": "Showing total executed tasks for specified time range", + "Tasks Usage Limit": "Tasks Usage Limit", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.", + "Number of monthly tasks": "Number of monthly tasks", + "Save changes": "Save changes", + "Limits updated successfully": "Limits updated successfully", + "Failed to update limits": "Failed to update limits", + "Failed to load billing information": "Failed to load billing information", + "Billing Amount": "Billing Amount", + "Manage Payment Details": "Manage Payment Details", + "Add Payment Details": "Add Payment Details", + "Current Task Usage": "Current Task Usage", + "Count of executed steps": "Count of executed steps", + "Billing Limit": "Billing Limit", + "Edit": "Edit", + "Add Limit": "Add Limit", + "Current Credit Usage": "Current Credit Usage", + "WebSocket Connection": "WebSocket Connection", + "Connected": "Connected", + "Disconnected": "Disconnected", + "No issues detected": "No issues detected", + "Check the status of your platform and its components": "Check the status of your platform and its components", + "System Health Status": "System Health Status", + "All systems are running smoothly": "All systems are running smoothly", + "Check the health of your worker machines": "Check the health of your worker machines", + "Workers Machine": "Workers Machine", + "This is demo data. In a real environment, this would show your actual worker machines.": "This is demo data. In a real environment, this would show your actual worker machines.", + "No workers found": "No workers found", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "You don't have any worker machines yet. Spin up new machines to execute your automations", + "IP Address": "IP Address", + "CPU Usage": "CPU Usage", + "Disk Usage": "Disk Usage", + "RAM Usage": "RAM Usage", + "Last Contact": "Last Contact", + "Configs": "Configs", + "Environment Variables": "Environment Variables", + "Websocket Connection Error": "Websocket Connection Error", + "Retry Connection": "Retry Connection", + "Update Available": "Update Available", + "Update Now": "Update Now", + "Your Universal AI needs a quick setup": "Your Universal AI needs a quick setup", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.", + "Configure": "Configure", + "Platform Alerts": "Platform Alerts", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.", + "View Alerts": "View Alerts", + "Used Tasks": "Used Tasks", + "Used AI Credits": "Used AI Credits", + "Cannot delete active project, switch to another project first": "Cannot delete active project, switch to another project first", + "Delete Projects": "Delete Projects", + "Are you sure you want to delete the selected projects?": "Are you sure you want to delete the selected projects?", + "New Project": "New Project", + "Validation error": "Validation error", + "Project has enabled flows. Please disable them first.": "Project has enabled flows. Please disable them first.", + "This project is active. Please switch to another project first.": "This project is active. Please switch to another project first.", + "Unlock Projects": "Unlock Projects", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Orchestrate your automation teams across projects with their own flows, connections and usage quotas", + "Manage your automation projects": "Manage your automation projects", + "No projects found": "No projects found", + "Start by creating projects to manage your automation teams": "Start by creating projects to manage your automation teams", + "Edit project": "Edit project", + "Name is required": "Name is required", + "Create New Project": "Create New Project", + "Project Name": "Project Name", + "Id": "Id", + "Enable API Keys": "Enable API Keys", + "Create and manage API keys to access Activepieces APIs.": "Create and manage API keys to access Activepieces APIs.", + "New Api Key": "New Api Key", + "No API keys found": "No API keys found", + "Start by creating an API key to communicate with Activepieces APIs": "Start by creating an API key to communicate with Activepieces APIs", + "Delete API Key": "Delete API Key", + "Are you sure you want to delete this API key?": "Are you sure you want to delete this API key?", + "API Key": "API Key", + "API Key Created": "API Key Created", + "Create New API Key": "Create New API Key", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Please save this secret key somewhere safe and accessible. For security reasons,", + "you won't be able to view it again after closing this dialog.": "you won't be able to view it again after closing this dialog.", + "API Key Name": "API Key Name", + "Action": "Action", + "Performed By": "Performed By", + "Project": "Project", + "Unlock Audit Logs": "Unlock Audit Logs", + "Comply with internal and external security policies by tracking activities done within your account": "Comply with internal and external security policies by tracking activities done within your account", + "Track activities done within your platform": "Track activities done within your platform", + "No audit logs found": "No audit logs found", + "Come back later when you have some activity to audit": "Come back later when you have some activity to audit", + "Resource": "Resource", + "Details": "Details", + "N/A": "N/A", + "Flow Run": "Flow Run", + "Flow": "Flow", + "User": "User", + "Signing Key": "Signing Key", + "Project Role Management": "Project Role Management", + "Define custom roles and permissions to control what your team members can access and modify": "Define custom roles and permissions to control what your team members can access and modify", + "Define custom roles and permissions that can be assigned to your team members": "Define custom roles and permissions that can be assigned to your team members", + "New Role": "New Role", + "Contact sales to unlock custom roles": "Contact sales to unlock custom roles", + "Create New Role": "Create New Role", + "View ": "View ", + "Edit ": "Edit ", + "Role Name": "Role Name", + "Permissions": "Permissions", + "None": "None", + "Read": "Read", + "Write": "Write", + "Create": "Create", + "Email": "Email", + "First Name": "First Name", + "Last Name": "Last Name", + "Roles": "Roles", + "View the users assigned to this role": "View the users assigned to this role", + "Role": "Role", + "No users found": "No users found", + "Starting by assigning users to this role": "Starting by assigning users to this role", + "Project Role entry deleted successfully": "Project Role entry deleted successfully", + "Updated": "Updated", + "No project roles found": "No project roles found", + "Create custom project roles to manage permissions for platform users": "Create custom project roles to manage permissions for platform users", + "Show Users": "Show Users", + "View Role": "View Role", + "Edit Role": "Edit Role", + "Delete Role": "Delete Role", + "Project Role": "Project Role", + "Unlock Embedding Through JS SDK": "Unlock Embedding Through JS SDK", + "Enable signing keys to access embedding functionalities.": "Enable signing keys to access embedding functionalities.", + "New Signing Key": "New Signing Key", + "No signing keys found": "No signing keys found", + "Create a signing key to authenticate users with embedding": "Create a signing key to authenticate users with embedding", + "Delete Signing Key": "Delete Signing Key", + "Are you sure you want to delete this signing key?": "Are you sure you want to delete this signing key?", + "Signing Key Created": "Signing Key Created", + "Create New Signing Key": "Create New Signing Key", + "Signing Key Name": "Signing Key Name", + "Allowed domains updated": "Allowed domains updated", + "Update": "Update", + "Enable": "Enable", + "Configure Allowed Domains": "Configure Allowed Domains", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.", + "Add Domain": "Add Domain", + "Allow logins through {providerName}'s single sign-on functionality.": "Allow logins through {providerName}'s single sign-on functionality.", + "Email authentication updated": "Email authentication updated", + "Enable Single Sign On": "Enable Single Sign On", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Let your users sign in with your current SSO provider or give them self serve sign up access", + "Allowed Domains": "Allowed Domains", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Allow users to authenticate with specific domains. Leave empty to allow all domains.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Allowed Email Login", + "Allow logins through email and password.": "Allow logins through email and password.", + "Single sign on settings updated": "Single sign on settings updated", + "Disable": "Disable", + "Configure {provider} SSO": "Configure {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} Client ID", + "{provider} Client Secret": "{provider} Client Secret", + "Single sign-on settings updated": "Single sign-on settings updated", + "Configure SAML 2.0 SSO": "Configure SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDP Metadata", + "IDP Certificate": "IDP Certificate", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "Base URL", + "Resource Name": "Resource Name", + "Deployment Name": "Deployment Name", + "Saving": "Saving", + "Activepieces Copilot": "Activepieces Copilot", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot is configured and ready to help your users build flows faster using AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configure Activepieces Copilot to help your users build flows faster using AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Set provider credentials that will be used by universal AI pieces, i.e Text AI.", + "AI Providers": "AI Providers", + "Copilot": "Copilot", + "Configure credentials for {providerName} AI provider.": "Configure credentials for {providerName} AI provider.", + "Update AI Provider": "Update AI Provider", + "Enable AI Provider": "Enable AI Provider", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Please enter a valid domain", + "Your changes have been saved.": "Your changes have been saved.", + "The domain is already added.": "The domain is already added.", + "Add Custom Domain": "Add Custom Domain", + "Enter a domain name without a protocol (e.g. example.com)": "Enter a domain name without a protocol (e.g. example.com)", + "Logo URL": "Logo URL", + "Icon URL": "Icon URL", + "Favicon URL": "Favicon URL", + "Default Language": "Default Language", + "Select Language": "Select Language", + "No Languages": "No Languages", + "Primary Color": "Primary Color", + "Custom Domains": "Custom Domains", + "No domains added yet.": "No domains added yet.", + "Verified": "Verified", + "Pending, please contact the support for dns verification.": "Pending, please contact the support for dns verification.", + "Are you sure you want to delete {domain}?": "Are you sure you want to delete {domain}?", + "Brand Activepieces": "Brand Activepieces", + "Give your users an experience that looks like you by customizing the color, logo and more": "Give your users an experience that looks like you by customizing the color, logo and more", + "Configure the appearance and SMTP settings for your platform.": "Configure the appearance and SMTP settings for your platform.", + "Invalid host": "Invalid host", + "Invalid username": "Invalid username", + "Invalid password": "Invalid password", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Invalid sender name", + "SMTP is configured": "SMTP is configured", + "Mail Server": "Mail Server", + "Set up your SMTP settings to send emails from your domain.": "Set up your SMTP settings to send emails from your domain.", + "Disable Mail Server": "Disable Mail Server", + "Are you sure you want to disable your mail server?": "Are you sure you want to disable your mail server?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "This will stop you from sending emails for issues, quota limits, invitations and forgot password.", + "mail server": "mail server", + "Host": "Host", + "Port": "Port", + "Username": "Username", + "Password": "Password", + "Sender Email": "Sender Email", + "Sender Name": "Sender Name", + "Enable Global Connections": "Enable Global Connections", + "Manage platform-wide connections to external systems.": "Manage platform-wide connections to external systems.", + "No global connections found": "No global connections found", + "Create a global connection that can be shared to multiple projects": "Create a global connection that can be shared to multiple projects", + "License key is invalid": "License key is invalid", + "Invalid license key": "Invalid license key", + "License activated!": "License activated!", + "Activate License Key": "Activate License Key", + "Let the magic begin!": "Let the magic begin!", + "Activate": "Activate", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ", + "This feature is not available in your current edition. ": "This feature is not available in your current edition. ", + "Learn how to upgrade": "Learn how to upgrade", + "Activate your platform and unlock enterprise features": "Activate your platform and unlock enterprise features", + "Activate License": "Activate License", + "Expiration": "Expiration", + "Valid until": "Valid until", + "Expired": "Expired", + "Expires soon": "Expires soon", + "Features": "Features", + "Applying Tags...": "Applying Tags...", + "Tags applied.": "Tags applied.", + "Tag created": "Tag created", + "Tag": "Tag", + "Tags": "Tags", + "Control Pieces": "Control Pieces", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Show the pieces that matter most to your users and hide the ones you don't like.", + "Manage the pieces that are available to your users": "Manage the pieces that are available to your users", + "Start by installing pieces that you want to use in your automations": "Start by installing pieces that you want to use in your automations", + "Piece Name": "Piece Name", + "Hide this piece from all projects": "Hide this piece from all projects", + "Show this piece for all projects": "Show this piece for all projects", + "Unpin this piece": "Unpin this piece", + "Pin this piece": "Pin this piece", + "Pieces synced": "Pieces synced", + "Pieces have been synced from the activepieces cloud.": "Pieces have been synced from the activepieces cloud.", + "OAuth2 Credentials Deleted": "OAuth2 Credentials Deleted", + "OAuth2 Credentials Updated": "OAuth2 Credentials Updated", + "Configure OAuth2 APP": "Configure OAuth2 APP", + "Delete OAuth2 APP": "Delete OAuth2 APP", + "Templates deleted successfully": "Templates deleted successfully", + "Delete Templates": "Delete Templates", + "Are you sure you want to delete the selected templates?": "Are you sure you want to delete the selected templates?", + "New Template": "New Template", + "Unlock Templates": "Unlock Templates", + "Convert the most common automations into reusable templates 1 click away from your users": "Convert the most common automations into reusable templates 1 click away from your users", + "Convert the most common automations into reusable templates": "Convert the most common automations into reusable templates", + "No templates found": "No templates found", + "Create a template for your user to inspire them": "Create a template for your user to inspire them", + "Edit template": "Edit template", + "Template is required": "Template is required", + "Update New Template": "Update New Template", + "Create New Template": "Create New Template", + "Template Name": "Template Name", + "Description": "Description", + "Template Description": "Template Description", + "Blog URL": "Blog URL", + "Template Blog URL": "Template Blog URL", + "Template": "Template", + "Invalid JSON": "Invalid JSON", + "User deleted successfully": "User deleted successfully", + "User activated successfully": "User activated successfully", + "User deactivated successfully": "User deactivated successfully", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Manage your users and their access to your projects", + "Start inviting users to your project": "Start inviting users to your project", + "External Id": "External Id", + "Admin": "Admin", + "Member": "Member", + "Activated": "Activated", + "Deactivated": "Deactivated", + "Edit user": "Edit user", + "Admin cannot be deactivated": "Admin cannot be deactivated", + "Deactivate user": "Deactivate user", + "Activate user": "Activate user", + "Delete User": "Delete User", + "Are you sure you want to delete this user?": "Are you sure you want to delete this user?", + "Delete user": "Delete user", + "Update User Role": "Update User Role", + "Meeting Summary Flow": "Meeting Summary Flow", + "Added new features and fixed bugs": "Added new features and fixed bugs", + "Flows Changes": "Flows Changes", + "Connections Changes": "Connections Changes", + "New connections are placeholders and need to be reconnected again": "New connections are placeholders and need to be reconnected again", + "renamed to": "renamed to", + "Tables Changes": "Tables Changes", + "No changes to apply": "No changes to apply", + "Apply Changes": "Apply Changes", + "Create Git Release": "Create Git Release", + "Create Project Release": "Create Project Release", + "Create Rollback to": "Create Rollback to", + "Source": "Source", + "Rollback": "Rollback", + "Imported At": "Imported At", + "Imported By": "Imported By", + "Track and manage your project version history and deployments. ": "Track and manage your project version history and deployments. ", + "Environments & Releases": "Environments & Releases", + "Project Releases": "Project Releases", + "Create Release": "Create Release", + "From Git": "From Git", + "From Project": "From Project", + "No project releases found": "No project releases found", + "Create a project release to get started": "Create a project release to get started", + "Please select project": "Please select project", + "No Changes Found": "No Changes Found", + "There are no differences to apply": "There are no differences to apply", + "Please select a project": "Please select a project", + "Search projects...": "Search projects...", + "Review Changes": "Review Changes", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Imported by", + "from": "from", + "No description provided": "No description provided", + "Invitation only sign up": "Invitation only sign up", + "Please ask your administrator to add you to the organization.": "Please ask your administrator to add you to the organization.", + "Something went wrong, please try again.": "Something went wrong, please try again.", + "Please try again.": "Please try again.", + "Please enter a valid email address": "Please enter a valid email address", + "The email is already added.": "The email is already added.", + "Add email": "Add email", + "Only project admins can do this": "Only project admins can do this", + "Add Alert Email": "Add Alert Email", + "Enter the email address to receive alerts.": "Enter the email address to receive alerts.", + "Add Email": "Add Email", + "Emails": "Emails", + "Add email addresses to receive alerts.": "Add email addresses to receive alerts.", + "No emails added yet.": "No emails added yet.", + "Choose what you want to be notified about.": "Choose what you want to be notified about.", + "Project and alert permissions are required to change this setting.": "Project and alert permissions are required to change this setting.", + "Every Failed Run": "Every Failed Run", + "Get an email alert when a flow fails.": "Get an email alert when a flow fails.", + "Get an email alert when a new issue created.": "Get an email alert when a new issue created.", + "Never": "Never", + "Turn off email notifications.": "Turn off email notifications.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Customize the appearance of the app. Automatically switch between day and night themes.", + "Select the theme for the dashboard.": "Select the theme for the dashboard.", + "Light": "Light", + "Dark": "Dark", + "Select the language that will be used in the dashboard.": "Select the language that will be used in the dashboard.", + "Select language": "Select language", + "Search language...": "Search language...", + "No language found.": "No language found.", + "Help us translate Activepieces to your language.": "Help us translate Activepieces to your language.", + "Learn more": "Learn more", + "Git Connection Removed": "Git Connection Removed", + "Your Git repository has been successfully disconnected": "Your Git repository has been successfully disconnected", + "Enable Environments": "Enable Environments", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Deploy flows across development, staging and production environments with version control and team collaboration", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Connect to Git to enable version control, backup your flows, and manage multiple environments. ", + "Repository URL": "Repository URL", + "Not connected": "Not connected", + "Project Folder": "Project Folder", + "Releases Enabled": "Releases Enabled", + "You have successfully enabled releases": "You have successfully enabled releases", + "Enable releases to easily create and manage project releases.": "Enable releases to easily create and manage project releases.", + "The external ID is already taken.": "The external ID is already taken.", + "Manage general settings for your project.": "Manage general settings for your project.", + "Used to identify the project based on your SaaS ID": "Used to identify the project based on your SaaS ID", + "org-3412321": "org-3412321", + "Delete {name}": "Delete {name}", + "This will permanently delete this piece, all steps using it will fail.": "This will permanently delete this piece, all steps using it will fail.", + "Add a piece to your project that you want to use in your automations": "Add a piece to your project that you want to use in your automations", + "Pieces list updated": "Pieces list updated", + "Manage Pieces": "Manage Pieces", + "Choose which pieces you want to be available for your current project users": "Choose which pieces you want to be available for your current project users", + "Unlock Team Permissions": "Unlock Team Permissions", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial", + "Project Members": "Project Members", + "Invite your team members to collaborate.": "Invite your team members to collaborate.", + "No members are added to this project.": "No members are added to this project.", + "Pending Invitations": "Pending Invitations", + "No pending invitation.": "No pending invitation.", + "templateId is missing": "templateId is missing", + "Me Only": "Me Only", + "Unresolved": "Unresolved", + "Resolved": "Resolved", + "Title": "Title", + "Date Created": "Date Created", + "Manage todos for your project that are created by automations": "Manage todos for your project that are created by automations", + "No todos found": "No todos found", + "You do not have any pending todos. Great job!": "You do not have any pending todos. Great job!", + "Write a comment...": "Write a comment...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "This feature is still under testing and might be changed often", + "Failed to copy to clipboard": "Failed to copy to clipboard", + "{number} items selected": "{number} items selected", + "Select All": "Select All", + "No results found.": "No results found.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect must be used within MultiSelectProvider", + "Unset": "Unset", + "Refresh": "Refresh", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} more", + "Removed {entityName}": "Removed {entityName}", + "Download File": "Download File", + "Copied to clipboard": "Copied to clipboard", + "File is not available after execution.": "File is not available after execution.", + "Available for Projects": "Available for Projects", + "Select projects": "Select projects", + "No items": "No items", + "Previous": "Previous", + "to": "to", + "Last Week": "Last Week", + "Last Month": "Last Month", + "Last 3 Months": "Last 3 Months", + "Last 6 Months": "Last 6 Months", + "Next 7 days": "Next 7 days", + "Next 30 days": "Next 30 days", + "Next 90 days": "Next 90 days", + "Next 180 days": "Next 180 days", + "Select Time Range": "Select Time Range", + "Clear": "Clear", + "Download": "Download", + "Go to Dashboard": "Go to Dashboard", + "Select a file": "Select a file", + "Press space to separate values": "Press space to separate values", + "AM": "AM", + "PM": "PM", + "Already have an account?": "Already have an account?", + "Sign in": "Sign in", + "Don't have an account?": "Don't have an account?", + "Sign up": "Sign up", + "Welcome Back!": "Welcome Back!", + "Enter your email below to sign in to your account": "Enter your email below to sign in to your account", + "Let's Get Started!": "Let's Get Started!", + "Create your account and start flowing!": "Create your account and start flowing!", + "Your password was changed successfully": "Your password was changed successfully", + "Your password reset request has expired, please request a new one": "Your password reset request has expired, please request a new one", + "Reset Password": "Reset Password", + "Enter your new password": "Enter your new password", + "Password is required": "Password is required", + "Verification email resent, if previous one expired.": "Verification email resent, if previous one expired.", + "Password reset link resent, if previous one expired.": "Password reset link resent, if previous one expired.", + "We sent you a link to complete your registration to": "We sent you a link to complete your registration to", + "We sent you a link to reset your password to": "We sent you a link to reset your password to", + "Didn't receive an email or it expired?": "Didn't receive an email or it expired?", + "Resend": "Resend", + "Please enter your email": "Please enter your email", + "Check Your Inbox": "Check Your Inbox", + "If the user exists we'll send you an email with a link to reset your password.": "If the user exists we'll send you an email with a link to reset your password.", + "Send Password Reset Link": "Send Password Reset Link", + "Back to sign in": "Back to sign in", + "Email is invalid": "Email is invalid", + "Something went wrong, please try again later": "Something went wrong, please try again later", + "Invalid email or password": "Invalid email or password", + "User has been deactivated": "User has been deactivated", + "Email domain is disallowed": "Email domain is disallowed", + "Email authentication has been disabled": "Email authentication has been disabled", + "Forgot your password?": "Forgot your password?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "Sign up is restricted. You need an invitation to join. Please contact the administrator.", + "Email is already used": "Email is already used", + "Email authentication is disabled": "Email authentication is disabled", + "First name is required": "First name is required", + "Last name is required": "Last name is required", + "Email is required": "Email is required", + "Receive updates and newsletters from activepieces": "Receive updates and newsletters from activepieces", + "By creating an account, you agree to our": "By creating an account, you agree to our", + "terms of service": "terms of service", + "privacy policy": "privacy policy", + "Sign up With": "Sign up With", + "Google": "Google", + "Sign in With": "Sign in With", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "Email has been verified. You will be redirected to sign in...", + "Verifying email...": "Verifying email...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "invitation has expired, once you sign in again you will be able to resend the verification email.", + "Redirecting to sign in...": "Redirecting to sign in...", + "Password must contain at least one special character": "Password must contain at least one special character", + "Password must contain at least one lowercase letter": "Password must contain at least one lowercase letter", + "Password must contain at least one uppercase letter": "Password must contain at least one uppercase letter", + "Password must contain at least one number": "Password must contain at least one number", + "8-64 Characters": "8-64 Characters", + "Special Character": "Special Character", + "Lowercase": "Lowercase", + "Uppercase": "Uppercase", + "Number": "Number", + "Connection has been updated.": "Connection has been updated.", + "Edit Global Connection": "Edit Global Connection", + "Connection has been renamed.": "Connection has been renamed.", + "New Connection Name": "New Connection Name", + "Connection name already used": "Connection name already used", + "Please select at least one project": "Please select at least one project", + "Run Succeeded": "Run Succeeded", + "Run Failed": "Run Failed", + "Flow Run is paused": "Flow Run is paused", + "Run Failed due to quota exceeded": "Run Failed due to quota exceeded", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Run failed due to exceeding the memory limit of {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "Run exceeded {timeout} seconds, try to optimize your steps.", + "Run failed for an unknown reason, contact support.": "Run failed for an unknown reason, contact support.", + "Unknown": "Unknown", + "Exit Run": "Exit Run", + "Select shown": "Select shown", + "Select all": "Select all", + "Start Time": "Start Time", + "Runs replayed successfully": "Runs replayed successfully", + "Retry": "Retry", + "all except": "all except", + "all": "all", + "Only failed runs can be retried from failed step": "Only failed runs can be retried from failed step", + "No flow runs found": "No flow runs found", + "Come back later when your automations start running": "Come back later when your automations start running", + "Step running": "Step running", + "Step paused": "Step paused", + "Step Stopped": "Step Stopped", + "Step Succeeded": "Step Succeeded", + "Step Failed": "Step Failed", + "Please publish flow first": "Please publish flow first", + "Flow is on": "Flow is on", + "Flow is off": "Flow is off", + "Permission Needed": "Permission Needed", + "Draft Version": "Draft Version", + "Published Version": "Published Version", + "Locked Version": "Locked Version", + "flowsImported": "{flowsCount, plural, =0 {No Flows Imported} =1 {Flow Imported Successfully} other {Flows Imported Successfully}}", + "Template file is invalid": "Template file is invalid", + "No valid templates found. The following files failed to import: ": "No valid templates found. The following files failed to import: ", + "Please select a file first": "Please select a file first", + "Unsupported file type": "Unsupported file type", + "Import Flow": "Import Flow", + "Warning": "Warning", + "Importing a flow will overwrite your current one.": "Importing a flow will overwrite your current one.", + "Select a folder": "Select a folder", + "Folders": "Folders", + "Please select a folder": "Please select a folder", + "Moved flows successfully": "Moved flows successfully", + "Move Selected Flows": "Move Selected Flows", + "Select Folder": "Select Folder", + "No Folders": "No Folders", + "Flow has been renamed.": "Flow has been renamed.", + "New Flow Name": "New Flow Name", + "Use Template": "Use Template", + "Browse Templates": "Browse Templates", + "Search templates": "Search templates", + "No templates found, try adjusting your search": "No templates found, try adjusting your search", + "Read more about this template in": "Read more about this template in", + "this blog!": "this blog!", + "Share Template": "Share Template", + "Generate or update a template link for the current flow to easily share it with others.": "Generate or update a template link for the current flow to easily share it with others.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "The template will not have any credentials in connection fields, keeping sensitive information secure.", + "A short description of the template": "A short description of the template", + "Flow Is In Use": "Flow Is In Use", + "Flow is being used by another user, please try again later.": "Flow is being used by another user, please try again later.", + "Flow has been published.": "Flow has been published.", + "Flows have been exported.": "Flows have been exported.", + "Run": "Run", + "Real time flow": "Real time flow", + "Flow can't be published with empty trigger {name}": "Flow can't be published with empty trigger {name}", + "Please contact support as your published flow has a problem": "Please contact support as your published flow has a problem", + "Actions": "Actions", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.", + "You are on a development branch, this will not delete the flows from the remote repository.": "You are on a development branch, this will not delete the flows from the remote repository.", + "Please enter folder name": "Please enter folder name", + "Added folder successfully": "Added folder successfully", + "The folder name already exists.": "The folder name already exists.", + "New Folder": "New Folder", + "Folder Name": "Folder Name", + "Loading...": "Loading...", + "Delete {folderName}": "Delete {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "If you delete this folder, we will keep its flows and move them to Uncategorized.", + "All flows": "All flows", + "Please enter a folder name": "Please enter a folder name", + "Renamed flow successfully": "Renamed flow successfully", + "Folder name already used": "Folder name already used", + "New Folder Name": "New Folder Name", + "Connected successfully": "Connected successfully", + "Connect Git": "Connect Git", + "Remote URL": "Remote URL", + "Folder name is the name of the folder where the project will be stored or fetched.": "Folder name is the name of the folder where the project will be stored or fetched.", + "SSH Private Key": "SSH Private Key", + "The SSH private key to use for authentication.": "The SSH private key to use for authentication.", + "Only published flows can be pushed to Git": "Only published flows can be pushed to Git", + "Pushed successfully": "Pushed successfully", + "Invalid Operation": "Invalid Operation", + "Commit Message": "Commit Message", + "Enter a commit message to describe the changes you want to push.": "Enter a commit message to describe the changes you want to push.", + "Push": "Push", + "This field is required": "This field is required", + "Your submission was successfully received.": "Your submission was successfully received.", + "Flow not found": "Flow not found", + "The flow you are trying to submit to does not exist.": "The flow you are trying to submit to does not exist.", + "The flow failed to execute.": "The flow failed to execute.", + "Submit": "Submit", + "issues-notification": "issues-notification", + "Please select a package type": "Please select a package type", + "package.json not found in archive": "package.json not found in archive", + "Error processing archive file": "Error processing archive file", + "Please upload a .tgz file": "Please upload a .tgz file", + "Piece name is required for NPM Registry": "Piece name is required for NPM Registry", + "Piece version is required for NPM Registry": "Piece version is required for NPM Registry", + "Piece installed": "Piece installed", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "A piece with this name and version is already installed. Please update the version number in package.json and try again.", + "Install Piece": "Install Piece", + "Install a piece": "Install a piece", + "Package Type": "Package Type", + "NPM Registry": "NPM Registry", + "Packed Archive (.tgz)": "Packed Archive (.tgz)", + "Piece Version": "Piece Version", + "Package Archive": "Package Archive", + "Package archive": "Package archive", + "Powerful Node.js & TypeScript code with npm": "Powerful Node.js & TypeScript code with npm", + "Loop on Items": "Loop on Items", + "Split your flow into branches depending on condition(s)": "Split your flow into branches depending on condition(s)", + "Empty Trigger": "Empty Trigger", + "An internal error occurred while fetching data, please contact support": "An internal error occurred while fetching data, please contact support", + "An internal error occurred, please contact support": "An internal error occurred, please contact support", + "Custom Javascript Code": "Custom Javascript Code", + "Router": "Router", + "recordsCount": "{recordsCount, plural, =0 {0 Records} =1 {1 Record} other {# Records}}", + "selected": "selected", + "All records selected": "All records selected", + "fieldsCount": "{fieldsCount, plural, =0 {0 Fields} =1 {1 Field} other {# Fields}}", + "Saving...": "Saving...", + "Loading more...": "Loading more...", + "Export Table": "Export Table", + "Delete Records": "Delete Records", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Are you sure you want to delete the selected records? This action cannot be undone.", + "record": "record", + "records": "records", + "mm/dd/yyy": "mm/dd/yyy", + "Delete Field": "Delete Field", + "Are you sure you want to delete this field? This action cannot be undone.": "Are you sure you want to delete this field? This action cannot be undone.", + "field": "field", + "Ignored": "Ignored", + "Table": "Table", + "CSV": "CSV", + "Field": "Field", + "Please select a csv file": "Please select a csv file", + "Max file size is {maxFileSize}MB": "Max file size is {maxFileSize}MB", + "Import CSV": "Import CSV", + "Imported records will be added to the bottom of the table": "Imported records will be added to the bottom of the table", + "Any records after the limit ({maxRecords} records) will be ignored": "Any records after the limit ({maxRecords} records) will be ignored", + "CSV File": "CSV File", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support", + "Name must be unique": "Name must be unique", + "Type is required": "Type is required", + "Please add at least one option": "Please add at least one option", + "New Field": "New Field", + "Options": "Options", + "Name is already taken": "Name is already taken", + "Table renamed": "Table renamed", + "Table name": "Table name", + "Team Invitation Accepted": "Team Invitation Accepted", + "Thank you for accepting the invitation. We are redirecting you right now...": "Thank you for accepting the invitation. We are redirecting you right now...", + "Invalid invitation token. Please try again.": "Invalid invitation token. Please try again.", + "Role updated successfully": "Role updated successfully", + "Error updating role": "Error updating role", + "Please try again later": "Please try again later", + "Edit Role for": "Edit Role for", + "Select Role": "Select Role", + "Avatar": "Avatar", + "Remove {email}": "Remove {email}", + "Are you sure you want to remove this invitation?": "Are you sure you want to remove this invitation?", + "Please select invitation type": "Please select invitation type", + "Please select platform role": "Please select platform role", + "Invitation sent successfully": "Invitation sent successfully", + "Please select a project role": "Please select a project role", + "Invitation link copied successfully": "Invitation link copied successfully", + "Invite User": "Invite User", + "Invitation Link": "Invitation Link", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Type the email address of the user you want to invite, the invitation expires in 24 hours.", + "Invite To": "Invite To", + "Entire Platform": "Entire Platform", + "Select Project Role": "Select Project Role", + "Invite": "Invite", + "Platform Role": "Platform Role", + "Select a platform role": "Select a platform role", + "Are you sure you want to remove this member?": "Are you sure you want to remove this member?", + "Editor": "Editor", + "Operator": "Operator", + "Viewer": "Viewer", + "Select a project role": "Select a project role", + "Steps in this flow": "Steps in this flow", + "Invalid Access": "Invalid Access", + "Either the project does not exist or you do not have access to it.": "Either the project does not exist or you do not have access to it." +} diff --git a/packages/react-ui/public/locales/es/translation.json b/packages/react-ui/public/locales/es/translation.json new file mode 100644 index 0000000..516b3ca --- /dev/null +++ b/packages/react-ui/public/locales/es/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Publicar", + "Latest version is published": "La última versión está publicada", + "Your flow has incomplete steps": "Tu flujo tiene pasos incompletos", + "Edit Flow": "Editar flujo", + "View Draft": "Ver borrador", + "Uncategorized": "Sin categorizar", + "Go to folder": "Ir a la carpeta", + "Support": "Soporte", + "Runs": "Ejecutar", + "Run Logs": "Ejecutar registros", + "Versions": "Versiones", + "Versions History": "Historial de versiones", + "Error generating code": "Error al generar código", + "AI Copilot": "Copilota IA", + "i.e Calculate the sum of a list...": "es decir, calcular la suma de una lista...", + "Send": "Enviar", + "Generating Code": "Generando código", + "Hello there! I am here to generate code that helps with your flow": "¡Hola! Estoy aquí para generar código que ayuda con tu flujo", + "Here are examples of what I am best used for: ": "He aquí algunos ejemplos de lo que más me utilizan: ", + "Text Processing": "Procesamiento de texto", + "Process strings, dates and data": "Cadenas, fechas y datos de proceso", + "Data Operations": "Operaciones de datos", + "Change data from one format to another": "Cambiar datos de un formato a otro", + "Calculations": "Calculaciones", + "Handle math and statistics": "Manejar matemáticas y estadísticas", + "API Integration": "Integración API", + "Connect with external services. Best for simple integrations currently.": "Conéctate con servicios externos. Lo mejor para integraciones simples actualmente.", + "What would you like me to help you with?": "¿Qué te gustaría que te ayudara?", + "Insert": "Insert", + "Data Selector": "Selector de datos", + "Search": "Buscar", + "No matching data": "No hay datos coincidentes", + "Try adjusting your search": "Intenta ajustar tu búsqueda", + "This trigger needs to have data loaded from your account, to use as sample data.": "Este disparador necesita tener datos cargados desde su cuenta, para usarlos como datos de muestra.", + "This step needs to be tested in order to view its data.": "Este paso debe ser probado para poder ver sus datos.", + "Go to Trigger": "Ir al disparador", + "Go to Step": "Ir a paso", + "Select Mode": "Seleccionar modo", + "Move Mode": "Modo Mover", + "Reset Zoom": "Reset Zoom", + "Zoom In": "Acercar en", + "Zoom Out": "Apagar", + "Fit to View": "Ajustar a la vista", + "Replace": "Reemplazar", + "Copy": "Copiar", + "Duplicate": "Duplicate", + "Paste After Last Step": "Pegar después del último paso", + "Paste Inside Loop": "Pegar dentro del bucle", + "Paste After": "Pegar después de", + "Paste Inside...": "Pegar dentro...", + "New Branch": "Nueva rama", + "Paste Inside Branch": "Pegar en la rama", + "Delete": "Eliminar", + "Duplicate Branch": "Duplicar rama", + "Delete Branch": "Eliminar rama", + "Invalid Move": "Movimiento inválido", + "The destination location is a child of the dragged step": "La ubicación de destino es un hijo del paso arrastrado", + "End": "Fin", + "Skipped": "Omitido", + "Incomplete settings": "Ajustes incompletos", + "logo": "logotipo", + "Step Icon": "Icono de Paso", + "Branch": "Rama", + "incompleteSteps": "{invalidSteps, plural, =0 {No hay pasos incompletos} =1 {Completa 1 paso} other {Completa # pasos}}", + "Test Flow": "Prueba de flujo", + "Please test the trigger first": "Por favor pruebe el gatillo primero", + "View Only": "Ver solo", + "Use as Draft": "Usar como borrador", + "Are you sure?": "¿Estás seguro?", + "Your current draft version will be overwritten with": "Su versión actual de borrador será sobrescrita con", + "version #": "versión #", + "Cancel": "Cancelar", + "Confirm": "Confirmar", + "Version": "Versión", + "Viewing": "Viendo", + "View": "Ver", + "Version History": "Historial de versiones", + "Error, please try again.": "Error, por favor inténtelo de nuevo.", + "Continue on Failure": "Continuar con el fracaso", + "Enable this option to skip this step and continue the flow normally if it fails.": "Active esta opción para omitir este paso y continuar el flujo normalmente si falla.", + "Retry on Failure": "Reintentar en Fallo", + "Automatically retry up to four attempts when failed.": "Reintente automáticamente hasta cuatro intentos cuando falle.", + "Remove": "Eliminar", + "Add Item": "Añadir ítem", + "File Input": "Entrada de archivo", + "Date Input": "Date Input", + "Dynamic value": "Valor dinámico", + "Select an option": "Seleccione una opción", + "Unexpected error, please retry": "Error inesperado, vuelva a intentarlo", + "Unexpected error, please refresh the page or contact support": "Error inesperado, por favor actualiza la página o contacta con el soporte técnico", + "Name can only contain letters, numbers and underscores": "El nombre sólo puede contener letras, números y guiones bajos", + "Ask AI": "Preguntar IA", + "Create Todo Guide": "Crear guía de tareas", + "Where would you like the todo to be reviewed?": "¿Dónde te gustaría que se revisara la tarea?", + "Activepieces Todos": "Todos de piezas activas", + "Users will manage tasks directly in Activepieces": "Los usuarios gestionarán tareas directamente en Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Los usuarios gestionarán y responderán a todos directamente dentro de la interfaz Activepieces ideal para equipos internos.", + "External Channel (Slack, Teams, Email, ...)": "Canal externo (Slack, equipos, correo electrónico, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Enviar notificaciones con enlaces de aprobación a través de canales externos como Slack, Equipos o Email. Lo mejor para colaborar con grupos de interés externos.", + "Preview (Activepieces Todos)": "Vista previa (Todos)", + "Preview (External channel)": "Vista previa (canal externo)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "El Activepieces Todo permite a los usuarios revisar y resolver tareas directamente en la interfaz Activepieces", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "Puede añadir el canal antes del paso de espera, y configurar la lógica en el paso de enrutamiento", + "Add Steps": "Añadir pasos", + "All": "Todos", + "AI": "AI", + "Core": "Núcleo", + "Apps": "Aplicaciones", + "Not available as trigger": "No disponible como disparador", + "Not available as action": "No disponible como acción", + "Let our AI assistant help you out": "Deja que nuestro asistente de IA te ayude", + "Or": "O", + "Request Piece": "Solicitar pieza", + "No pieces found": "Piezas no encontradas", + "Please select a piece first": "Por favor seleccione una pieza primero", + "All Iterations": "Todas las iteraciones", + "Duration": "Duración", + "Input": "Input", + "Output": "Salida", + "There are no logs captured for this run.": "No hay registros capturados para esta ejecución.", + "Logs are kept for {days} days after execution and then deleted.": "Los registros se guardan durante {days} días después de la ejecución y luego se eliminan.", + "Run Details": "Detalles de ejecución", + "Iteration": "Iteración", + "Done": "Hecho", + "Took": "Gancho", + "Running": "Ejecutando", + "on latest version": "en la última versión", + "from failed step": "del paso fallido", + "Recent Runs": "Ejecuciones recientes", + "No runs found": "No hay ejecuciones", + "Close": "Cerrar", + "OR": "O", + "And If": "Y si", + "+ And": "+ Y", + "+ Or": "+ O", + "(Text) Contains": "(Texto) Contiene", + "(Text) Does not contain": "(Texto) No contiene", + "(Text) Exactly matches": "(Texto) Coincide Exactamente", + "(Text) Does not exactly match": "(Texto) No coincide exactamente", + "(Text) Starts with": "(Texto) Comienza por", + "(Text) Does not start with": "(Texto) No comienza con", + "(Text) Ends with": "(Texto) Termina con", + "(Text) Does not end with": "(Texto) No termina con", + "(List) Contains": "(Lista) Contiene", + "(List) Does not contain": "(Listar) No contiene", + "(Number) Is greater than": "(Número) Es mayor que", + "(Number) Is less than": "(Número) Es menor que", + "(Number) Is equal to": "(Número) es igual a", + "(Date/time) After": "(Fecha/hora) Después", + "(Date/time) Before": "(Fecha/hora) Antes", + "(Date/time) Equals": "(Fecha/hora) Iguales", + "(Boolean) Is true": "(Booleano) Es verdad", + "(Boolean) Is false": "(Booleano) Es falso", + "(List) Is empty": "(Lista) Está vacía", + "(List) Is not empty": "(Lista) No está vacío", + "Exists": "Existe", + "Does not exist": "No existe", + "Incomplete condition": "Condición incompleta", + "First value": "Primer valor", + "Second value": "Segundo valor", + "Case sensitive": "Distingue mayúsculas", + "Execute If": "Ejecutar si", + "The package name is required": "El nombre del paquete es requerido", + "Success": "Éxito", + "Package added successfully": "Paquete añadido correctamente", + "Could not fetch package version": "No se pudo obtener la versión del paquete", + "Add NPM Package": "Añadir paquete NPM", + "Type the name of the npm package you want to add.": "Escriba el nombre del paquete npm que desea añadir.", + "Package Name": "Nombre del paquete", + "The latest version will be fetched and added": "La última versión será obtenida y añadida", + "Add": "Añadir", + "Code": "Código", + "Dependencies": "Dependencias", + "Use code": "Usar código", + "Add package": "Añadir paquete", + "Inputs": "Inputs", + "Edit Step Name": "Editar nombre de paso", + "Edit Branch Name": "Editar nombre de rama", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.", + "Items": "Elementos", + "Select an array of items": "Seleccione una matriz de elementos", + "Reconnect": "Volver a conectar", + "Select a connection": "Seleccione una conexión", + "Create Connection": "Crear conexión", + "Rename": "Renombrar", + "Move": "Mover", + "Add Branch": "Añadir rama", + "Execute": "Ejecutar", + "Only the first (left) matching branch": "Sólo la primera rama de coincidencia (izquierda)", + "All matching paths from left to right": "Todas las rutas coincidentes de izquierda a derecha", + "Branches": "Ramas", + "{field} is required": "Se requiere {field}", + "Tool Sample Data": "Datos de muestra de herramienta", + "Fill in the following fields to use them as sample data for the trigger.": "Rellene los siguientes campos para utilizarlos como datos de muestra para el activador.", + "No input fields defined in the schema": "No hay campos de entrada definidos en el esquema", + "Save": "Guardar", + "Test Environment": "Entorno de prueba", + "Assigned to": "Asignado a", + "(Me)": "(Yo)", + "Please select status to resolve the todo": "Por favor, seleccione el estado para resolver la tarea", + "Resolve": "Resolver", + "Change status to resolved": "Cambiar estado a resuelto", + "Send Sample Data to Webhook": "Enviar datos de ejemplo a Webhook", + "Method": "Método", + "Query Params": "Parámetros de consulta", + "Headers": "Encabezados", + "Body": "Cuerpo", + "Type": "Tipo", + "JSON": "JSON", + "Text": "Texto", + "Form Data": "Datos de Formulario", + "Generate Sample Data": "Generar datos de ejemplo", + "Test Step": "Probar paso", + "Retest": "Retorno", + "Testing Failed": "Prueba fallida", + "Tested Successfully": "Probado con éxito", + "Logs": "Registros", + "There is no sample data available found for this trigger.": "No hay datos de muestra disponibles para este disparador.", + "Internal error, please try again later.": "Error interno, por favor inténtalo de nuevo más tarde.", + "Failed to run test step and no error message was returned": "Error al ejecutar el paso de prueba y no se devolvió ningún mensaje de error", + "Please fix inputs first": "Por favor arregla primero las entradas", + "No sample data available": "No hay datos de ejemplo disponibles", + "Old results were removed, retest for new sample data": "Se eliminaron los resultados antiguos, reprueba los nuevos datos de muestra", + "Result #": "Resultado #", + "The sample data can be used in the next steps.": "Los datos de muestra se pueden utilizar en los siguientes pasos.", + "Testing Trigger": "Probando disparador", + "Action Required": "Acción requerida", + "testPieceWebhookTriggerNote": "Por favor, ve a {pieceName} y activa {triggerName}.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Enviar datos a la URL del webhook para generar datos de ejemplo a usar en los siguientes pasos", + "Test Trigger": "Probar activación", + "Use Mock Data": "Usar datos simulados", + "Load Sample Data": "Cargar datos de ejemplo", + "Test Tool": "Probar herramienta", + "home": "casa", + "Home": "Inicio", + "Alerts": "Alertas", + "Releases": "Publicaciones", + "Flows": "Flujos", + "Products": "Productos", + "MCP": "MCP", + "Tables": "Tablas", + "Todos": "Todos", + "Push to Git": "Enviar a Git", + "Move To": "Mover a", + "Duplicating": "Duplicando", + "Import": "Importar", + "Exporting": "Exportando", + "Export": "Exportar", + "Share": "Compartir", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "¿Está seguro que desea eliminar este flujo? Esto eliminará permanentemente el flujo, todos sus datos y cualquier ejecución en segundo plano.", + "You are on a development branch, this will also delete the flow from the remote repository.": "Estás en una rama de desarrollo, esto también eliminará el flujo del repositorio remoto.", + "flow": "flujo", + "Community Support": "Soporte de la comunidad", + "Overview": "Resumen", + "Projects": "Proyectos", + "Users": "Usuarios", + "Setup": "Configurar", + "Branding": "Marca", + "Global Connections": "Conexiones globales", + "Pieces": "Piezas", + "Templates": "Plantillas", + "License Key": "Clave de licencia", + "Security": "Seguridad", + "Audit Logs": "Auditoría", + "Single Sign On": "Firmar único", + "Signing Keys": "Firmando claves", + "Project Roles": "Roles del proyecto", + "API Keys": "Claves API", + "Infrastructure": "Infraestructura", + "Workers": "Trabajadores", + "Health": "Salud", + "Billing": "Facturación", + "Settings": "Ajustes", + "Contact Sales": "Contacto Ventas", + "General": "General", + "Appearance": "Apariencia", + "Team": "Equipo", + "Environments": "Entornos", + "Project Settings": "Configuración del proyecto", + "Exit Platform Admin": "Salir de Platform Admin", + "Enter Platform Admin": "Entrar Administrador de Plataforma", + "Logout": "Cerrar sesión", + "Misc": "Misc", + "Connections": "Conexiones", + "days": "días", + "hours": "horas", + "minutes": "minutos", + "Today": "Hoy", + "Tasks": "Tareas", + "AI Credits": "AI Credits", + "Usage resets in": "El uso se reinicia en", + "Manage": "Gestionar", + "Unlimited": "Ilimitado", + "Enabled": "Activado", + "Disabled": "Deshabilitado", + "Could not claim the authorization code, make sure you have correct settings and try again.": "No se pudo reclamar el código de autorización, asegúrese de que tiene la configuración correcta y vuelva a intentarlo.", + "Connection failed with error {msg}": "Error de conexión con error {msg}", + "You don't have the permission to create a connection.": "No tienes permiso para crear una conexión.", + "Reconnect {displayName} Connection": "Reconnect {displayName}", + "Connect to {displayName}": "Conectar a {displayName}", + "Connection Name": "Nombre de la conexión", + "Connection name": "Nombre de la conexión", + "New Connection": "Nueva conexión", + "Redirect URL": "URL de redirección", + "Client ID": "ID de cliente", + "Client Secret": "Cliente secreto", + "Connect": "Conectar", + "Disconnect": "Desconectar", + "I would like to use my own App Credentials": "Me gustaría usar mis propias credenciales de aplicación", + "I would like to use predefined App Credentials": "Me gustaría usar credenciales de aplicación predefinidas", + "Permission needed": "Se necesita permiso", + "Connections replaced successfully": "Conexiones reemplazadas con éxito", + "Error": "Error", + "Failed to replace connections": "Error al reemplazar las conexiones", + "Failed to get affected flows": "Error al obtener los flujos afectados", + "Please select a piece": "Por favor, seleccione una pieza", + "Please select a connection to replace": "Por favor, seleccione una conexión para reemplazar", + "Please select a connection to replace with": "Por favor, seleccione una conexión con la que reemplazar", + "Replace Connections": "Reemplazar conexiones", + "Confirm Replacement": "Confirmar reemplazo", + "Replace one connection with another.": "Reemplazar una conexión con otra.", + "This action requires ": "Esta acción requiere ", + "reconnecting": "reconectando", + " any associated MCP pieces.": " cualquier pieza MCP asociada.", + "Piece": "Pieza", + "Select a piece": "Seleccione una pieza", + "Connection to Replace": "Conexión a reemplazar", + "Choose connection to replace": "Elija la conexión para reemplazar", + "Replaced With": "Reemplazado con", + "Choose connection to replace with": "Elija la conexión con la que reemplazar", + "All flows will be changed to use the replaced with connection": "Todos los flujos se cambiarán para usar el reemplazo por conexión", + "Next": "Siguiente", + "No flows will be affected by this change": "Ningún flujo será afectado por este cambio", + "Back": "Atrás", + "Unnamed tool": "Herramienta sin nombre", + "This flow is enabled": "Este flujo está activado", + "Enable this flow to make it available": "Habilita este flujo para que esté disponible", + "Piece is updated successfully": "La pieza se ha actualizado correctamente", + "Piece is added successfully": "La pieza se ha añadido correctamente", + "Failed to update piece": "Error al actualizar la pieza", + "Failed to add piece": "Error al agregar la pieza", + "Please select a connection": "Por favor, seleccione una conexión", + "Your MCP server already has this piece": "Tu servidor MCP ya tiene esta pieza", + "+ New Connection": "+ Nueva conexión", + "Edit Piece": "Editar pieza", + "Add Piece": "Añadir pieza", + "Connection": "Conexión", + "MCP piece": "Pieza MCP", + "Failed to update piece status": "Error al actualizar el estado de la pieza", + "Connection required": "Conexión requerida", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "¿Está seguro que desea eliminar esta herramienta de su MCP? si lo eliminas no podrás usarlo en tu cliente MCP.", + "piece": "pieza", + "Connect your AI assistant to external services": "Conecte su asistente de IA a servicios externos", + "Collapse": "Colapso", + "Show All": "Mostrar todo", + "Server URL": "URL del servidor", + "Hide the token for security": "Ocultar el token de seguridad", + "Show the token": "Mostrar el token", + "Generate a new token for security. This will invalidate the current URL.": "Genera un nuevo token para seguridad. Esto invalidará la URL actual.", + "URL copied to clipboard": "URL copiada al portapapeles", + "Copy URL to clipboard": "Copiar URL al portapapeles", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "Esta URL contiene un token de seguridad confidencial. Únicamente compártelo con aplicaciones y servicios de confianza. Rota el token si sospechas que ha sido comprometido.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "Después de realizar cualquier cambio en conexiones o flujos, necesitará volver a conectar su servidor MCP para que los cambios surtan efecto.", + "Max tables reached": "Mesas máximas alcanzadas", + "You can't create more than {maxTables} tables": "No puede crear más de tablas {maxTables}", + "Name": "Nombre", + "Created": "Creado", + "Delete Tables": "Eliminar Tablas", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "¿Está seguro que desea eliminar las tablas seleccionadas? Esta acción no se puede deshacer.", + "table": "tabla", + "Create and manage your tables to store your automation data": "Crea y gestiona tus tablas para almacenar tus datos de automatización", + "New Table": "Nueva tabla", + "No tables have been created yet": "Aún no se han creado tablas", + "Create a table to get started and start managing your automation data": "Crea una tabla para empezar y empezar a administrar tus datos de automatización", + "Error deleting connections": "Error al eliminar las conexiones", + "Status": "Estado", + "Display Name": "Mostrar nombre", + "Owner": "Propietario", + "App": "App", + "This connection is global and can be managed in the platform admin": "Esta conexión es global y se puede administrar en el administrador de la plataforma", + "External ID": "ID Externo", + "Connected At": "Conectado en", + "Confirm Deletion": "Confirmar eliminación", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "¿Está seguro que desea eliminar las conexiones seleccionadas? Esta acción no se puede deshacer.", + "Deleting connections may cause your Flows or MCP tools to break.": "Eliminar conexiones puede causar que sus Flows o herramientas MCP se rompan.", + "Manage project connections to external systems.": "Administrar conexiones de proyecto a sistemas externos.", + "No connections found": "No se encontraron conexiones", + "Come back later when you create a automation to manage your connections": "Vuelve más tarde cuando creas una automatización para administrar tus conexiones", + "Steps": "Pasos", + "Folder": "Carpeta", + "Flow name": "Nombre de flujo", + "No flows found": "No hay flujos", + "Create a workflow to start automating": "Crear un flujo de trabajo para empezar a automatizar", + "Create and manage your flows, run history and run issues": "Crea y gestiona tus flujos, ejecuta el historial y ejecuta problemas", + "Issues": "Problemas", + "Untitled": "Sin título", + "Create flow": "Crear flujo", + "From scratch": "Desde cero", + "Use a template": "Usar una plantilla", + "From local file": "Desde archivo local", + "Flow Name": "Nombre de Flow", + "Count": "Contador", + "First Seen": "Primer detectado", + "Last Seen": "Último Visto", + "Issues in {flowDisplayName} is marked as resolved.": "Los problemas en {flowDisplayName} están marcados como resueltos.", + "Unlock Issues": "Desbloquear incidencias", + "Track issues in your workflows and troubleshoot them.": "Seguimiento de problemas en sus flujos de trabajo y solución de problemas.", + "No issues found": "No se encontraron peticiones", + "All your workflows are running smoothly.": "Todos sus flujos de trabajo se están ejecutando sin problemas.", + "Mark as Resolved": "Marcar como resuelto", + "All Flows Are Turned Off": "Todos los Flows están desactivados", + "Task Usage Exceeded": "Uso de la tarea excedido", + "of the Allowed Limit.": "del límite permitido.", + "When a project tasks limit is reached,": "Cuando se alcanza el límite de tareas de un proyecto,", + "all flows will be turned off and you will not be able to run any flows.": "todos los flujos se apagarán y no podrá ejecutar ningún flujo.", + "Please visit": "Por favor visita", + "Your Plan": "Su plan", + "and increase your task limit, which requires your payment details.": "y aumentar su límite de tarea, lo que requiere sus detalles de pago.", + "Please contact your admin to increase the project task limit.": "Póngase en contacto con su administrador para aumentar el límite de tareas del proyecto.", + "and increase the project task limit.": "y aumentar el límite de tareas del proyecto.", + "Dismiss": "Descartar", + "Token rotated successfully": "Token rotado con éxito", + "Failed to rotate token": "Error al girar el token", + "Piece removed successfully": "Pieza eliminada correctamente", + "Failed to remove piece": "Error al eliminar la pieza", + "Flow created successfully": "Flow creado con éxito", + "Failed to create flow": "Error al crear flujo", + "Add Flow": "Añadir flujo", + "Let your AI assistant trigger automations": "Deja que tu asistente de IA active automatizaciones", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Conéctate a tu servidor MCP alojado usando cualquier cliente MCP para comunicarte con herramientas", + "MCP Server": "Servidor MCP", + "My Tools": "Mis herramientas", + "Create Flow": "Crear flujo", + "Note": "Nota", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "Si desea exponer su servidor MCP a Internet, por favor establezca la variable de entorno AP_FRONTEND_URL a la URL pública de su instancia Activepieces .", + "This URL grants access to your tools and data. Only share with trusted applications.": "Esta URL permite el acceso a tus herramientas y datos. Solo compartir con aplicaciones de confianza.", + "Server Configuration": "Configuración del servidor", + "Hide sensitive data": "Ocultar datos sensibles", + "Show sensitive data": "Mostrar datos sensibles", + "Create a new URL. The current one will stop working.": "Crear una nueva URL. La actual dejará de funcionar.", + "Copy configuration": "Copiar configuración", + "Configuration copied to clipboard": "Configuración copiada al portapapeles", + "Claude": "Claude", + "Cursor": "Cursor", + "Windsurf": "Navegación", + "Server/Other": "Servidor/Otro", + "Note: MCPs only work with": "Nota: Los MCs sólo funcionan con", + "Claude Desktop": "Escritorio Claude", + ", not the web version.": ", no la versión web.", + "Prerequisites:": "Requisitos previos:", + "Install": "Instalar", + "Node.js": "Node.js", + "and": "y", + "Open Settings:": "Abrir Configuración:", + "Click the menu and select": "Haga clic en el menú y seleccione", + "Developer": "Desarrollador", + "Configure MCP:": "Configurar MCP:", + "Click": "Click", + "Edit Config": "Editar configuración", + "and paste the configuration below": "y pega la configuración de abajo", + "Save and Restart:": "Guardar y reiniciar:", + "Save the config and restart Claude Desktop": "Guardar la configuración y reiniciar Claude Desktop", + "Navigate to": "Navegar a", + "Cursor Settings": "Ajustes del cursor", + "Add Server:": "Añadir servidor:", + "Add new global MCP server": "Añadir nuevo servidor global MCP", + "Configure:": "Configuración:", + "Paste the configuration below and save": "Pegue la configuración de abajo y guarde", + "Use either method:": "Usar cualquiera de los métodos:", + "Go to": "Ir a", + "Advanced Settings": "Configuración Avanzada", + "Open Command Palette and select": "Abrir paleta de comandos y seleccionar", + "Windsurf Settings Page": "Página de ajustes de Windsurf", + "Navigate to Cascade:": "Navegar a Cascada:", + "Select": "Seleccionar", + "Cascade": "Cascade", + "in the sidebar": "en la barra lateral", + "Add Server": "Añadir Servidor", + "Add custom server +": "Añadir servidor personalizado +", + "Copy URL": "Copiar URL", + "Client Setup Instructions": "Instrucciones de configuración del cliente", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "Después de cambiar conexiones o flujos, vuelva a conectar su servidor MCP para que los cambios surtan efecto.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Siga estos pasos para configurar MCP en su cliente preferido. Esto permite a su asistente de IA acceder a sus herramientas.", + "icon": "icono", + "Unlock Analytics": "Desbloquear análisis", + "Get insights into your platform usage and performance with our analytics dashboard": "Obtén información sobre el uso y rendimiento de tu plataforma con nuestro panel de análisis", + "Active Flows": "Flujos activos", + "The number of enabled flows in the platform": "El número de flujos habilitados en la plataforma", + "Active Projects": "Proyectos activos", + "The number of projects with at least one enabled flow": "El número de proyectos con al menos un flujo habilitado", + "Active Users": "Usuarios Activos", + "The number of users logged in the last 30 days": "El número de usuarios registrados en los últimos 30 días", + "Out of {totalusers} total users": "De un total de usuarios {totalusers}", + "Pieces Used": "Piezas Usadas", + "The number of unique pieces used across all flows": "El número de piezas únicas utilizadas a través de todos los flujos", + "Flows with AI": "Fluye con IA", + "The number of enabled flows that use AI pieces": "El número de flujos habilitados que utilizan piezas de IA", + "Metrics": "Métricas", + "Executed Tasks": "Tareas ejecutadas", + "Showing total executed tasks for specified time range": "Mostrar el total de tareas ejecutadas para el rango de tiempo especificado", + "Tasks Usage Limit": "Límite de uso de tareas", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Especifique un límite mensual para las tareas para evitar un uso excesivo. Sus flujos ya no se ejecutarán si se alcanzó este límite.", + "Number of monthly tasks": "Número de tareas mensuales", + "Save changes": "Guardar cambios", + "Limits updated successfully": "Límites actualizados correctamente", + "Failed to update limits": "Error al actualizar los límites", + "Failed to load billing information": "Error al cargar la información de facturación", + "Billing Amount": "Cantidad de facturación", + "Manage Payment Details": "Administrar detalles de pago", + "Add Payment Details": "Añadir detalles de pago", + "Current Task Usage": "Uso actual de tareas", + "Count of executed steps": "Número de pasos ejecutados", + "Billing Limit": "Límite de facturación", + "Edit": "Editar", + "Add Limit": "Añadir límite", + "Current Credit Usage": "Uso actual de crédito", + "WebSocket Connection": "Conexión WebSocket", + "Connected": "Conectado", + "Disconnected": "Desconectado", + "No issues detected": "No hay problemas detectados", + "Check the status of your platform and its components": "Comprueba el estado de tu plataforma y sus componentes", + "System Health Status": "Estado de Salud del Sistema", + "All systems are running smoothly": "Todos los sistemas están funcionando sin problemas", + "Check the health of your worker machines": "Compruebe el estado de salud de sus máquinas trabajadoras", + "Workers Machine": "Máquina de Trabajadores", + "This is demo data. In a real environment, this would show your actual worker machines.": "Esto son datos de demostración. En un entorno real, esto mostraría sus máquinas trabajadoras reales.", + "No workers found": "No se encontraron trabajadores", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "Aún no tienes ninguna máquina operadora. Ponga nuevas máquinas para ejecutar sus automatizaciones", + "IP Address": "Dirección IP", + "CPU Usage": "Uso de CPU", + "Disk Usage": "Uso del disco", + "RAM Usage": "RAM Usage", + "Last Contact": "Último contacto", + "Configs": "Configurar", + "Environment Variables": "Variables de entorno", + "Websocket Connection Error": "Error de conexión de Websocket", + "Retry Connection": "Reintentar conexión", + "Update Available": "Actualización disponible", + "Update Now": "Actualizar ahora", + "Your Universal AI needs a quick setup": "Tu IA Universal necesita una configuración rápida", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "Nos hemos dado cuenta de que aún no has configurado ningún proveedor de IA. Para desbloquear piezas de IA Universal para tu equipo, primero tendrás que configurar algunas credenciales de proveedor.", + "Configure": "Configurar", + "Platform Alerts": "Alertas de Plataforma", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "Hay alertas importantes de la plataforma que requieren tu atención. Por favor revisa la sección de alertas en Plataforma Admin.", + "View Alerts": "Ver alertas", + "Used Tasks": "Tareas Usadas", + "Used AI Credits": "Créditos IA usados", + "Cannot delete active project, switch to another project first": "No se puede eliminar el proyecto activo, cambiar a otro proyecto primero", + "Delete Projects": "Eliminar proyectos", + "Are you sure you want to delete the selected projects?": "¿Está seguro que desea eliminar los proyectos seleccionados?", + "New Project": "Nuevo proyecto", + "Validation error": "Error de validación", + "Project has enabled flows. Please disable them first.": "El proyecto ha activado flujos. Por favor deshabilítelos primero.", + "This project is active. Please switch to another project first.": "Este proyecto está activo. Por favor, cambie a otro proyecto primero.", + "Unlock Projects": "Desbloquear proyectos", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Organiza tus equipos de automatización a través de proyectos con sus propios flujos, conexiones y cuotas de uso", + "Manage your automation projects": "Gestiona tus proyectos de automatización", + "No projects found": "No se encontraron proyectos", + "Start by creating projects to manage your automation teams": "Empieza creando proyectos para administrar tus equipos de automatización", + "Edit project": "Editar proyecto", + "Name is required": "Se requiere nombre", + "Create New Project": "Crear nuevo proyecto", + "Project Name": "Nombre del proyecto", + "Id": "Id", + "Enable API Keys": "Habilitar claves API", + "Create and manage API keys to access Activepieces APIs.": "Crear y administrar claves API para acceder a APIs Activepieces .", + "New Api Key": "Nueva Clave Api", + "No API keys found": "No se encontraron claves API", + "Start by creating an API key to communicate with Activepieces APIs": "Empezar por crear una clave API para comunicarse con las APIs Activepieces", + "Delete API Key": "Borrar Clave API", + "Are you sure you want to delete this API key?": "¿Estás seguro de que quieres eliminar esta clave API?", + "API Key": "Clave API", + "API Key Created": "Clave API creada", + "Create New API Key": "Crear nueva clave API", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Por favor, guarde esta clave secreta en algún lugar seguro y accesible. Por razones de seguridad,", + "you won't be able to view it again after closing this dialog.": "no podrás verlo de nuevo después de cerrar este diálogo.", + "API Key Name": "Nombre clave API", + "Action": "Accin", + "Performed By": "Realizado por", + "Project": "Projekt", + "Unlock Audit Logs": "Desbloquear registros de auditoría", + "Comply with internal and external security policies by tracking activities done within your account": "Completa las políticas de seguridad internas y externas rastreando las actividades realizadas dentro de tu cuenta", + "Track activities done within your platform": "Rastrear las actividades realizadas dentro de tu plataforma", + "No audit logs found": "No se encontraron registros de auditoría", + "Come back later when you have some activity to audit": "Vuelve más tarde cuando tengas alguna actividad para auditar", + "Resource": "Recurso", + "Details": "Detalles", + "N/A": "N/A", + "Flow Run": "Flow Run", + "Flow": "Flujo", + "User": "Usuario", + "Signing Key": "Clave de firma", + "Project Role Management": "Gestión de Rol de Proyecto", + "Define custom roles and permissions to control what your team members can access and modify": "Definir roles y permisos personalizados para controlar lo que los miembros de tu equipo pueden acceder y modificar", + "Define custom roles and permissions that can be assigned to your team members": "Definir roles y permisos personalizados que pueden ser asignados a los miembros de tu equipo", + "New Role": "Nuevo rol", + "Contact sales to unlock custom roles": "Contacte con ventas para desbloquear roles personalizados", + "Create New Role": "Crear nuevo rol", + "View ": "Ver ", + "Edit ": "Editar ", + "Role Name": "Nombre del Rol", + "Permissions": "Permisos", + "None": "Ninguna", + "Read": "Leer", + "Write": "Escribir", + "Create": "Crear", + "Email": "E-mail", + "First Name": "Nombre", + "Last Name": "Apellido", + "Roles": "Roles", + "View the users assigned to this role": "Ver los usuarios asignados a este rol", + "Role": "Rol", + "No users found": "No hay usuarios", + "Starting by assigning users to this role": "Comenzando asignando usuarios a este rol", + "Project Role entry deleted successfully": "Entrada de rol de proyecto eliminada correctamente", + "Updated": "Actualizado", + "No project roles found": "No se encontraron roles de proyecto", + "Create custom project roles to manage permissions for platform users": "Crear roles de proyecto personalizados para administrar permisos para usuarios de la plataforma", + "Show Users": "Mostrar usuarios", + "View Role": "Ver rol", + "Edit Role": "Editar rol", + "Delete Role": "Eliminar rol", + "Project Role": "Rol del proyecto", + "Unlock Embedding Through JS SDK": "Desbloquear incrustación a través de JS SDK", + "Enable signing keys to access embedding functionalities.": "Habilitar la firma de claves para acceder a funciones de incrustación.", + "New Signing Key": "Nueva Clave de Firma", + "No signing keys found": "No se encontraron claves de firma", + "Create a signing key to authenticate users with embedding": "Crear una clave de firma para autenticar usuarios con incrustación", + "Delete Signing Key": "Borrar Clave de Firma", + "Are you sure you want to delete this signing key?": "¿Está seguro de que desea eliminar esta clave de firma?", + "Signing Key Created": "Clave de firma creada", + "Create New Signing Key": "Crear nueva clave de firma", + "Signing Key Name": "Nombre de clave de firma", + "Allowed domains updated": "Dominios permitidos actualizados", + "Update": "Actualizar", + "Enable": "Activar", + "Configure Allowed Domains": "Configurar dominios permitidos", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Introduzca los dominios permitidos para que los usuarios se autenticen, la lista vacía permitirá todos los dominios.", + "Add Domain": "Añadir dominio", + "Allow logins through {providerName}'s single sign-on functionality.": "Permitir iniciar sesión a través de la funcionalidad de inicio de sesión único de {providerName}.", + "Email authentication updated": "Autenticación de correo actualizada", + "Enable Single Sign On": "Activar inicio de sesión único", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Deja que tus usuarios inicien sesión con tu actual proveedor de SSO o déles acceso de registro auto servido", + "Allowed Domains": "Dominios permitidos", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Permitir a los usuarios autenticarse con dominios específicos. Dejar en blanco para permitir todos los dominios.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Acceso a Email permitido", + "Allow logins through email and password.": "Permitir inicio de sesión a través de correo electrónico y contraseña.", + "Single sign on settings updated": "Configuración de inicio único actualizada", + "Disable": "Desactivar", + "Configure {provider} SSO": "Configurar {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Lea más información sobre cómo configurar {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "ID de cliente {provider}", + "{provider} Client Secret": "{provider} secreto de cliente", + "Single sign-on settings updated": "Configuración de inicio de sesión único actualizada", + "Configure SAML 2.0 SSO": "Configurar SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "Metadatos IDP", + "IDP Certificate": "Certificado IDP", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "URL base", + "Resource Name": "Nombre del recurso", + "Deployment Name": "Nombre de despliegue", + "Saving": "Guardando", + "Activepieces Copilot": "Copilot de piezas activas", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot está configurado y listo para ayudar a tus usuarios a construir más rápido usando AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configure Activepieces Copilot para ayudar a sus usuarios a construir flujos más rápido usando AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Configura tus proveedores de IA y ajustes de copilot para que tus usuarios disfruten de una experiencia de construcción perfecta con nuestras piezas de IA universales", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Establece las credenciales de proveedor que serán utilizadas por piezas universales de IA, por ejemplo, texto IA de texto.", + "AI Providers": "AI Providers", + "Copilot": "Copilote", + "Configure credentials for {providerName} AI provider.": "Configurar credenciales para el proveedor IA {providerName}.", + "Update AI Provider": "Actualizar proveedor de IA", + "Enable AI Provider": "Habilitar proveedor de IA", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Por favor, introduzca un dominio válido", + "Your changes have been saved.": "Sus cambios han sido guardados.", + "The domain is already added.": "El dominio ya ha sido añadido.", + "Add Custom Domain": "Añadir dominio personalizado", + "Enter a domain name without a protocol (e.g. example.com)": "Introduzca un nombre de dominio sin un protocolo (por ejemplo, ejemplo.com)", + "Logo URL": "URL del Logo", + "Icon URL": "URL del icono", + "Favicon URL": "URL de Favicon", + "Default Language": "Idioma por defecto", + "Select Language": "Seleccionar Idioma", + "No Languages": "No hay idiomas", + "Primary Color": "Color principal", + "Custom Domains": "Dominios personalizados", + "No domains added yet.": "No hay dominios añadidos aún.", + "Verified": "Verificado", + "Pending, please contact the support for dns verification.": "Pendiente, póngase en contacto con el soporte para la verificación de dns.", + "Are you sure you want to delete {domain}?": "¿Está seguro que desea eliminar {domain}?", + "Brand Activepieces": "Piezas activas de marca", + "Give your users an experience that looks like you by customizing the color, logo and more": "Dale a tus usuarios una experiencia que se vea como tú personalizando el color, el logotipo y más", + "Configure the appearance and SMTP settings for your platform.": "Configure la apariencia y la configuración SMTP de su plataforma.", + "Invalid host": "Servidor inválido", + "Invalid username": "Nombre de usuario inválido", + "Invalid password": "Contraseña no válida", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Nombre de remitente no válido", + "SMTP is configured": "SMTP está configurado", + "Mail Server": "Servidor de Correo", + "Set up your SMTP settings to send emails from your domain.": "Configura tu configuración SMTP para enviar correos desde tu dominio.", + "Disable Mail Server": "Desactivar el servidor de correo", + "Are you sure you want to disable your mail server?": "¿Está seguro de que desea desactivar su servidor de correo?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "Esto le impedirá enviar correos electrónicos para problemas, límites de cuotas, invitaciones y contraseña olvidada.", + "mail server": "servidor de correo", + "Host": "Anfitrión", + "Port": "Puerto", + "Username": "Usuario", + "Password": "Contraseña", + "Sender Email": "Email del remitente", + "Sender Name": "Nombre del remitente", + "Enable Global Connections": "Habilitar conexiones globales", + "Manage platform-wide connections to external systems.": "Administrar conexiones de toda la plataforma a sistemas externos.", + "No global connections found": "No se encontraron conexiones globales", + "Create a global connection that can be shared to multiple projects": "Crear una conexión global que pueda ser compartida con varios proyectos", + "License key is invalid": "La clave de licencia no es válida", + "Invalid license key": "Clave de licencia no válida", + "License activated!": "¡Licencia activada!", + "Activate License Key": "Activar clave de licencia", + "Let the magic begin!": "¡Que comience la magia!", + "Activate": "Activar", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "Esta característica no es auto-servido en la nube todavía, por favor contacta a sales@activepieces.com. ", + "This feature is not available in your current edition. ": "Esta característica no está disponible en su edición actual. ", + "Learn how to upgrade": "Aprende cómo actualizar", + "Activate your platform and unlock enterprise features": "Activa tu plataforma y desbloquea las funciones empresariales", + "Activate License": "Activar licencia", + "Expiration": "Caducidad", + "Valid until": "Válido hasta", + "Expired": "Caducó", + "Expires soon": "Expira pronto", + "Features": "Características", + "Applying Tags...": "Aplicando etiquetas...", + "Tags applied.": "Etiquetas aplicadas.", + "Tag created": "Etiqueta creada", + "Tag": "Etiqueta", + "Tags": "Etiquetas", + "Control Pieces": "Controlar Piezas", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Muestra las piezas que más importan a tus usuarios y oculta las que no te gustan.", + "Manage the pieces that are available to your users": "Administra las piezas que están disponibles para tus usuarios", + "Start by installing pieces that you want to use in your automations": "Empieza por instalar piezas que quieras usar en tus automatizaciones", + "Piece Name": "Nombre de la pieza", + "Hide this piece from all projects": "Ocultar esta pieza de todos los proyectos", + "Show this piece for all projects": "Mostrar esta pieza para todos los proyectos", + "Unpin this piece": "Desanclar esta pieza", + "Pin this piece": "Fijar esta pieza", + "Pieces synced": "Piezas sincronizadas", + "Pieces have been synced from the activepieces cloud.": "Las piezas se han sincronizado desde la nube de piezas activas.", + "OAuth2 Credentials Deleted": "Credenciales de OAuth2 eliminadas", + "OAuth2 Credentials Updated": "Credenciales de OAuth2 actualizadas", + "Configure OAuth2 APP": "Configurar la aplicación OAuth2", + "Delete OAuth2 APP": "Eliminar API de OAuth2", + "Templates deleted successfully": "Plantillas eliminadas correctamente", + "Delete Templates": "Eliminar plantillas", + "Are you sure you want to delete the selected templates?": "¿Está seguro que desea eliminar las plantillas seleccionadas?", + "New Template": "Nueva plantilla", + "Unlock Templates": "Desbloquear plantillas", + "Convert the most common automations into reusable templates 1 click away from your users": "Convierte las automaciones más comunes en plantillas reutilizables a 1 clic lejos de tus usuarios", + "Convert the most common automations into reusable templates": "Convierte las automaciones más comunes en plantillas reutilizables", + "No templates found": "No se encontraron plantillas", + "Create a template for your user to inspire them": "Crear una plantilla para su usuario para que la inspire", + "Edit template": "Editar plantilla", + "Template is required": "Plantilla es necesaria", + "Update New Template": "Actualizar nueva plantilla", + "Create New Template": "Crear nueva plantilla", + "Template Name": "Nombre de Plantilla", + "Description": "Descripción", + "Template Description": "Descripción de la plantilla", + "Blog URL": "URL del blog", + "Template Blog URL": "URL del blog de plantillas", + "Template": "Plantilla", + "Invalid JSON": "JSON inválido", + "User deleted successfully": "Usuario eliminado correctamente", + "User activated successfully": "Usuario activado correctamente", + "User deactivated successfully": "Usuario desactivado correctamente", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Administra tus usuarios y su acceso a tus proyectos", + "Start inviting users to your project": "Empieza a invitar usuarios a tu proyecto", + "External Id": "Id Externo", + "Admin": "Admin", + "Member": "Miembro", + "Activated": "Activado", + "Deactivated": "Desactivado", + "Edit user": "Editar usuario", + "Admin cannot be deactivated": "El administrador no puede desactivarse", + "Deactivate user": "Desactivar usuario", + "Activate user": "Activar usuario", + "Delete User": "Eliminar usuario", + "Are you sure you want to delete this user?": "¿Está seguro que desea eliminar este usuario?", + "Delete user": "Eliminar usuario", + "Update User Role": "Actualizar rol de usuario", + "Meeting Summary Flow": "Resumen de reuniones", + "Added new features and fixed bugs": "Añadidas nuevas características y errores corregidos", + "Flows Changes": "Cambios de Flujo", + "Connections Changes": "Cambios de conexiones", + "New connections are placeholders and need to be reconnected again": "Las nuevas conexiones son marcadores de posición y necesitan ser conectadas de nuevo", + "renamed to": "renombrado a", + "Tables Changes": "Cambios de Tablas", + "No changes to apply": "No hay cambios para aplicar", + "Apply Changes": "Aplicar cambios", + "Create Git Release": "Crear Git Release", + "Create Project Release": "Crear versión del proyecto", + "Create Rollback to": "Crear Rollback a", + "Source": "Fuente", + "Rollback": "Rollback", + "Imported At": "Importado en", + "Imported By": "Importado por", + "Track and manage your project version history and deployments. ": "Sigue y administra el historial de versiones de tu proyecto y sus implementaciones. ", + "Environments & Releases": "Entornos y Versiones", + "Project Releases": "Lanzamientos del proyecto", + "Create Release": "Crear Versión", + "From Git": "Desde Git", + "From Project": "Desde proyecto", + "No project releases found": "No se encontraron versiones de proyecto", + "Create a project release to get started": "Crear una versión del proyecto para empezar", + "Please select project": "Por favor seleccione proyecto", + "No Changes Found": "No se encontraron cambios", + "There are no differences to apply": "No hay diferencias para aplicar", + "Please select a project": "Por favor, seleccione un proyecto", + "Search projects...": "Buscar proyectos...", + "Review Changes": "Revisar cambios", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Importado por", + "from": "de", + "No description provided": "No se proporcionó descripción", + "Invitation only sign up": "Invitación sólo registrarse", + "Please ask your administrator to add you to the organization.": "Por favor, pida a su administrador que le agregue a la organización.", + "Something went wrong, please try again.": "Algo salió mal, por favor inténtalo de nuevo.", + "Please try again.": "Vuelva a intentarlo.", + "Please enter a valid email address": "Por favor, introduzca una dirección de correo válida", + "The email is already added.": "El correo electrónico ya ha sido añadido.", + "Add email": "Añadir email", + "Only project admins can do this": "Solo los administradores del proyecto pueden hacer esto", + "Add Alert Email": "Añadir correo de alerta", + "Enter the email address to receive alerts.": "Introduzca la dirección de correo electrónico para recibir alertas.", + "Add Email": "Añadir Email", + "Emails": "Correos", + "Add email addresses to receive alerts.": "Añadir direcciones de correo electrónico para recibir alertas.", + "No emails added yet.": "Aún no se han añadido correos electrónicos.", + "Choose what you want to be notified about.": "Elige de qué quieres ser notificado.", + "Project and alert permissions are required to change this setting.": "Permisos de proyecto y alerta son necesarios para cambiar esta configuración.", + "Every Failed Run": "Cada ejecución fallida", + "Get an email alert when a flow fails.": "Obtener una alerta de correo electrónico cuando un flujo falla.", + "Get an email alert when a new issue created.": "Recibe una alerta de correo electrónico cuando se crea un nuevo problema.", + "Never": "Nunca", + "Turn off email notifications.": "Desactivar notificaciones por correo.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Personaliza la apariencia de la aplicación. Cambia automáticamente entre temas de día y noche.", + "Select the theme for the dashboard.": "Seleccione el tema para el tablero.", + "Light": "Claro", + "Dark": "Oscuro", + "Select the language that will be used in the dashboard.": "Seleccione el idioma que se utilizará en el panel.", + "Select language": "Seleccionar idioma", + "Search language...": "Buscar idioma...", + "No language found.": "Idioma no encontrado.", + "Help us translate Activepieces to your language.": "Ayúdanos a traducir Activepieces a tu idioma.", + "Learn more": "Aprende más", + "Git Connection Removed": "Conexión de Git eliminada", + "Your Git repository has been successfully disconnected": "Su repositorio Git se ha desconectado correctamente", + "Enable Environments": "Habilitar entornos", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Desplegar flujos a través de entornos de desarrollo, puesta en escena y producción con control de versiones y colaboración en equipo", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Conéctate a Git para habilitar el control de versiones, hacer copias de seguridad de tus flujos y administrar múltiples entornos. ", + "Repository URL": "URL del repositorio", + "Not connected": "No conectado", + "Project Folder": "Carpeta del proyecto", + "Releases Enabled": "Publicaciones habilitadas", + "You have successfully enabled releases": "Has habilitado correctamente las versiones", + "Enable releases to easily create and manage project releases.": "Habilitar lanzamientos para crear y administrar fácilmente lanzamientos de proyectos.", + "The external ID is already taken.": "El ID externo ya está en uso.", + "Manage general settings for your project.": "Administrar la configuración general de su proyecto.", + "Used to identify the project based on your SaaS ID": "Utilizado para identificar el proyecto basado en su ID de SaaS", + "org-3412321": "org-3412321", + "Delete {name}": "Eliminar {name}", + "This will permanently delete this piece, all steps using it will fail.": "Esto eliminará permanentemente este pieto, todos los pasos que lo usen fallarán.", + "Add a piece to your project that you want to use in your automations": "Añade una pieza a tu proyecto que quieras usar en tus automatizaciones", + "Pieces list updated": "Lista de piezas actualizada", + "Manage Pieces": "Administrar Piezas", + "Choose which pieces you want to be available for your current project users": "Elige qué piezas quieres estar disponible para los usuarios actuales del proyecto", + "Unlock Team Permissions": "Desbloquear permisos del equipo", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "Puedes invitar usuarios a tu Platform de forma gratuita en la edición de la comunidad. Para roles avanzados y permisos solicita la prueba", + "Project Members": "Miembros del proyecto", + "Invite your team members to collaborate.": "Invita a los miembros de tu equipo a colaborar.", + "No members are added to this project.": "No hay miembros agregados a este proyecto.", + "Pending Invitations": "Invitaciones pendientes", + "No pending invitation.": "No hay invitaciones pendientes.", + "templateId is missing": "plantilla Id no se encuentra", + "Me Only": "Sólo yo", + "Unresolved": "Sin resolver", + "Resolved": "Resuelto", + "Title": "Título", + "Date Created": "Fecha de creación", + "Manage todos for your project that are created by automations": "Administrar tareas para tu proyecto que son creadas por automatizaciones", + "No todos found": "No hay tareas", + "You do not have any pending todos. Great job!": "No tienes ninguna tarea pendiente. ¡Buen trabajo!", + "Write a comment...": "Escribe un comentario...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "Esta característica todavía está en prueba y puede ser cambiada a menudo", + "Failed to copy to clipboard": "Error al copiar al portapapeles", + "{number} items selected": "{number} elementos seleccionados", + "Select All": "Seleccionar todo", + "No results found.": "No se han encontrado resultados.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect debe ser utilizado en MultiSelectProvider", + "Unset": "Quitar", + "Refresh": "Refrescar", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} más", + "Removed {entityName}": "Eliminado {entityName}", + "Download File": "Descargar archivo", + "Copied to clipboard": "Copiado al portapapeles", + "File is not available after execution.": "El archivo no está disponible después de la ejecución.", + "Available for Projects": "Disponible para proyectos", + "Select projects": "Seleccionar proyectos", + "No items": "No hay elementos", + "Previous": "Anterior", + "to": "a", + "Last Week": "Última semana", + "Last Month": "Mes pasado", + "Last 3 Months": "Últimos 3 meses", + "Last 6 Months": "Últimos 6 meses", + "Next 7 days": "Próximos 7 días", + "Next 30 days": "Próximos 30 días", + "Next 90 days": "Próximos 90 días", + "Next 180 days": "Próximos 180 días", + "Select Time Range": "Seleccionar rango de tiempo", + "Clear": "Claro", + "Download": "Descargar", + "Go to Dashboard": "Ir al Tablero", + "Select a file": "Seleccione un archivo", + "Press space to separate values": "Pulse el espacio para separar los valores", + "AM": "M", + "PM": "MP", + "Already have an account?": "¿Ya tienes una cuenta?", + "Sign in": "Iniciar sesión", + "Don't have an account?": "¿No tienes una cuenta?", + "Sign up": "Regístrate", + "Welcome Back!": "¡Bienvenido!", + "Enter your email below to sign in to your account": "Introduce tu correo electrónico a continuación para iniciar sesión en tu cuenta", + "Let's Get Started!": "¡Comencemos!", + "Create your account and start flowing!": "¡Crea tu cuenta y empieza a fluir!", + "Your password was changed successfully": "Tu contraseña ha sido cambiada correctamente", + "Your password reset request has expired, please request a new one": "Su solicitud de restablecimiento de contraseña ha caducado, por favor solicite una nueva", + "Reset Password": "Restablecer contraseña", + "Enter your new password": "Introduce tu nueva contraseña", + "Password is required": "Se requiere contraseña", + "Verification email resent, if previous one expired.": "Correo de verificación reenviado, si el anterior ha caducado.", + "Password reset link resent, if previous one expired.": "Enlace de restablecimiento de contraseña reenviado, si el anterior ha caducado.", + "We sent you a link to complete your registration to": "Te hemos enviado un enlace para completar tu registro", + "We sent you a link to reset your password to": "Te hemos enviado un enlace para restablecer tu contraseña a", + "Didn't receive an email or it expired?": "¿No ha recibido un correo electrónico o ha caducado?", + "Resend": "Reenviar", + "Please enter your email": "Por favor ingrese su email", + "Check Your Inbox": "Marque su bandeja de entrada", + "If the user exists we'll send you an email with a link to reset your password.": "Si el usuario existe, te enviaremos un correo electrónico con un enlace para restablecer tu contraseña.", + "Send Password Reset Link": "Enviar enlace de restablecimiento de contraseña", + "Back to sign in": "Volver a iniciar sesión", + "Email is invalid": "El correo no es válido", + "Something went wrong, please try again later": "Algo salió mal, por favor inténtalo de nuevo más tarde", + "Invalid email or password": "Email o contraseña no válidos", + "User has been deactivated": "El usuario ha sido desactivado", + "Email domain is disallowed": "Dominio de correo no permitido", + "Email authentication has been disabled": "La autenticación de correo electrónico ha sido deshabilitada", + "Forgot your password?": "¿Olvidaste tu contraseña?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "El registro está restringido. Necesita una invitación para unirse. Por favor, póngase en contacto con el administrador.", + "Email is already used": "El correo electrónico ya está en uso", + "Email authentication is disabled": "Autenticación de correo está desactivada", + "First name is required": "El nombre es obligatorio", + "Last name is required": "Se requiere apellido", + "Email is required": "Se requiere correo electrónico", + "Receive updates and newsletters from activepieces": "Recibir actualizaciones y boletines de noticias de piezas activas", + "By creating an account, you agree to our": "Al crear una cuenta, aceptas nuestra", + "terms of service": "términos de servicio", + "privacy policy": "política de privacidad", + "Sign up With": "Regístrate con", + "Google": "Google", + "Sign in With": "Iniciar sesión con", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "El correo electrónico ha sido verificado. Será redirigido para iniciar sesión...", + "Verifying email...": "Verificando email...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "la invitación ha caducado, una vez que inicie sesión de nuevo podrá reenviar el correo electrónico de verificación.", + "Redirecting to sign in...": "Redirigiendo para iniciar sesión...", + "Password must contain at least one special character": "La contraseña debe contener al menos un carácter especial", + "Password must contain at least one lowercase letter": "La contraseña debe contener al menos una letra minúscula", + "Password must contain at least one uppercase letter": "La contraseña debe contener al menos una letra mayúscula", + "Password must contain at least one number": "La contraseña debe contener al menos un número", + "8-64 Characters": "8-64 caracteres", + "Special Character": "Carácter especial", + "Lowercase": "Minúsculas", + "Uppercase": "Mayúsculas", + "Number": "Número", + "Connection has been updated.": "La conexión ha sido actualizada.", + "Edit Global Connection": "Editar conexión global", + "Connection has been renamed.": "La conexión ha sido renombrada.", + "New Connection Name": "Nuevo nombre de conexión", + "Connection name already used": "Nombre de la conexión ya utilizado", + "Please select at least one project": "Por favor, seleccione al menos un proyecto", + "Run Succeeded": "Ejecutar con éxito", + "Run Failed": "Error al ejecutar", + "Flow Run is paused": "Flow Run está en pausa", + "Run Failed due to quota exceeded": "Falló debido a que la cuota superó", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Error al ejecutar debido a que se ha superado el límite de memoria de {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "La ejecución ha superado los {timeout} segundos, trate de optimizar sus pasos.", + "Run failed for an unknown reason, contact support.": "No se pudo ejecutar por una razón desconocida, póngase en contacto con soporte.", + "Unknown": "Desconocido", + "Exit Run": "Salir de Run", + "Select shown": "Seleccionar mostrado", + "Select all": "Seleccionar todo", + "Start Time": "Hora de inicio", + "Runs replayed successfully": "Ejecutar repetidas con éxito", + "Retry": "Reintentar", + "all except": "todo excepto", + "all": "todo", + "Only failed runs can be retried from failed step": "Sólo las ejecuciones fallidas pueden ser reintentadas desde el paso fallido", + "No flow runs found": "No hay ejecuciones de flujo", + "Come back later when your automations start running": "Vuelve más tarde cuando empiecen a funcionar tus automatizaciones", + "Step running": "Paso corriendo", + "Step paused": "Paso pausado", + "Step Stopped": "Paso detenido", + "Step Succeeded": "Paso exitoso", + "Step Failed": "Paso fallido", + "Please publish flow first": "Por favor publique primero", + "Flow is on": "Flujo activado", + "Flow is off": "Flow está apagado", + "Permission Needed": "Se necesita permiso", + "Draft Version": "Versión de borrador", + "Published Version": "Versión publicada", + "Locked Version": "Versión bloqueada", + "flowsImported": "{flowsCount, plural, =0 {No Flows Imported} =1 {Flow Imported Successfully} other {Flows Imported Successfully}}", + "Template file is invalid": "El archivo de plantilla no es válido", + "No valid templates found. The following files failed to import: ": "No se encontraron plantillas válidas. Los siguientes archivos fallaron al importar: ", + "Please select a file first": "Por favor, seleccione un archivo primero", + "Unsupported file type": "Tipo de archivo no soportado", + "Import Flow": "Importar flujo", + "Warning": "Advertencia", + "Importing a flow will overwrite your current one.": "La importación de un flujo sobreescribirá el actual.", + "Select a folder": "Seleccione una carpeta", + "Folders": "Carpetas", + "Please select a folder": "Por favor, seleccione una carpeta", + "Moved flows successfully": "Flujos movidos con éxito", + "Move Selected Flows": "Mover flujos seleccionados", + "Select Folder": "Seleccionar carpeta", + "No Folders": "Sin carpetas", + "Flow has been renamed.": "Flow ha sido renombrado.", + "New Flow Name": "Nuevo nombre de flujo", + "Use Template": "Usar Plantilla", + "Browse Templates": "Ver plantillas", + "Search templates": "Buscar plantillas", + "No templates found, try adjusting your search": "No se encontraron plantillas, intente ajustar su búsqueda", + "Read more about this template in": "Leer más sobre esta plantilla en", + "this blog!": "este blog!", + "Share Template": "Compartir plantilla", + "Generate or update a template link for the current flow to easily share it with others.": "Generar o actualizar un enlace de plantilla para el flujo actual para compartirlo fácilmente con otros.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "La plantilla no tendrá ninguna credencial en los campos de conexión, manteniendo segura la información confidencial.", + "A short description of the template": "Breve descripción de la plantilla", + "Flow Is In Use": "Flow está en uso", + "Flow is being used by another user, please try again later.": "Flow está siendo utilizado por otro usuario, por favor inténtalo de nuevo más tarde.", + "Flow has been published.": "Flow ha sido publicado.", + "Flows have been exported.": "Los flujos han sido exportados.", + "Run": "Ejecutar", + "Real time flow": "Flujo en tiempo real", + "Flow can't be published with empty trigger {name}": "Flow no puede publicarse con el disparador vacío {name}", + "Please contact support as your published flow has a problem": "Póngase en contacto con soporte técnico ya que su flujo publicado tiene un problema", + "Actions": "Acciones", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "¿Está seguro que desea eliminar estos flujos? Esto eliminará permanentemente los flujos, todos sus datos y cualquier ejecución en segundo plano.", + "You are on a development branch, this will not delete the flows from the remote repository.": "Estás en una rama de desarrollo, esto no eliminará los flujos del repositorio remoto.", + "Please enter folder name": "Por favor, introduzca el nombre de la carpeta", + "Added folder successfully": "Carpeta añadida correctamente", + "The folder name already exists.": "El nombre de la carpeta ya existe.", + "New Folder": "Nueva carpeta", + "Folder Name": "Nombre de carpeta", + "Loading...": "Cargando...", + "Delete {folderName}": "Eliminar {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "Si elimina esta carpeta, mantendremos sus flujos y los moveremos a Sin categorizar.", + "All flows": "Todos los flujos", + "Please enter a folder name": "Por favor, introduzca un nombre de carpeta", + "Renamed flow successfully": "Flujo renombrado correctamente", + "Folder name already used": "Nombre de carpeta ya utilizado", + "New Folder Name": "Nuevo nombre de carpeta", + "Connected successfully": "Conectado correctamente", + "Connect Git": "Conectar Git", + "Remote URL": "URL remota", + "Folder name is the name of the folder where the project will be stored or fetched.": "El nombre de la carpeta es el nombre de la carpeta donde se almacenará o obtendrá el proyecto.", + "SSH Private Key": "Clave privada SSH", + "The SSH private key to use for authentication.": "La clave privada SSH a usar para la autenticación.", + "Only published flows can be pushed to Git": "Sólo los flujos publicados pueden ser empujados a Git", + "Pushed successfully": "Enviado con éxito", + "Invalid Operation": "Operación no válida", + "Commit Message": "Mensaje de Commit", + "Enter a commit message to describe the changes you want to push.": "Introduzca un mensaje de confirmación para describir los cambios que desea push.", + "Push": "Empujar", + "This field is required": "Este campo es obligatorio", + "Your submission was successfully received.": "Su envío ha sido recibido con éxito.", + "Flow not found": "Flujo no encontrado", + "The flow you are trying to submit to does not exist.": "El flujo al que intentas enviar no existe.", + "The flow failed to execute.": "El flujo no se pudo ejecutar.", + "Submit": "Enviar", + "issues-notification": "notificación-problemas", + "Please select a package type": "Por favor, seleccione un tipo de paquete", + "package.json not found in archive": "package.json no encontrado en el archivo", + "Error processing archive file": "Error al procesar el archivo", + "Please upload a .tgz file": "Por favor, sube un archivo .tgz", + "Piece name is required for NPM Registry": "El nombre de la pieza es obligatorio para el registro NPM", + "Piece version is required for NPM Registry": "La versión de la pieza es necesaria para el registro NPM", + "Piece installed": "Pieza instalada", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "Ya está instalada una pieza con este nombre y versión. Por favor, actualice el número de versión en package.json y vuelva a intentarlo.", + "Install Piece": "Instalar Piece", + "Install a piece": "Instalar una pieza", + "Package Type": "Tipo de paquete", + "NPM Registry": "Registro NPM", + "Packed Archive (.tgz)": "Archivo empaquetado (.tgz)", + "Piece Version": "Versión de la pieza", + "Package Archive": "Archivo de paquetes", + "Package archive": "Archivo de paquetes", + "Powerful Node.js & TypeScript code with npm": "Código de Node.js y TypeScript con npm", + "Loop on Items": "Bucle en artículos", + "Split your flow into branches depending on condition(s)": "Dividir tu flujo en ramas dependiendo de condición(es)", + "Empty Trigger": "Disparador vacío", + "An internal error occurred while fetching data, please contact support": "Se ha producido un error interno al recuperar los datos, por favor póngase en contacto con el soporte técnico", + "An internal error occurred, please contact support": "Se ha producido un error interno, póngase en contacto con el servicio de asistencia", + "Custom Javascript Code": "Código JavaScript personalizado", + "Router": "Enrutador", + "recordsCount": "contador de registros", + "selected": "seleccionado", + "All records selected": "Todos los registros seleccionados", + "fieldsCount": "contador de campos", + "Saving...": "Guardando...", + "Loading more...": "Cargando más...", + "Export Table": "Exportar tabla", + "Delete Records": "Borrar registros", + "Are you sure you want to delete the selected records? This action cannot be undone.": "¿Está seguro que desea eliminar los registros seleccionados? Esta acción no se puede deshacer.", + "record": "grabar", + "records": "registros", + "mm/dd/yyy": "mm/dd/aaaa", + "Delete Field": "Eliminar campo", + "Are you sure you want to delete this field? This action cannot be undone.": "¿Está seguro que desea eliminar este campo? Esta acción no se puede deshacer.", + "field": "campo", + "Ignored": "Ignorado", + "Table": "Tabla", + "CSV": "CSV", + "Field": "Campo", + "Please select a csv file": "Por favor, seleccione un archivo csv", + "Max file size is {maxFileSize}MB": "El tamaño máximo del archivo es {maxFileSize}MB", + "Import CSV": "Importar CSV", + "Imported records will be added to the bottom of the table": "Los registros importados se añadirán a la parte inferior de la tabla", + "Any records after the limit ({maxRecords} records) will be ignored": "Cualquier registro después del límite (registros{maxRecords} ) será ignorado", + "CSV File": "Archivo CSV", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "Ocurrió un error inesperado mientras se importaba el archivo csv, por favor pulse el error de copia y envíelo al soporte", + "Name must be unique": "El nombre debe ser único", + "Type is required": "Tipo es requerido", + "Please add at least one option": "Por favor, añade al menos una opción", + "New Field": "Nuevo campo", + "Options": "Opciones", + "Name is already taken": "El nombre ya está en uso", + "Table renamed": "Tabla renombrada", + "Table name": "Nombre de tabla", + "Team Invitation Accepted": "Invitación de equipo aceptada", + "Thank you for accepting the invitation. We are redirecting you right now...": "Gracias por aceptar la invitación. Te estamos redirigiendo ahora mismo...", + "Invalid invitation token. Please try again.": "Token de invitación inválido. Vuelve a intentarlo.", + "Role updated successfully": "Rol actualizado correctamente", + "Error updating role": "Error al actualizar el rol", + "Please try again later": "Inténtalo de nuevo más tarde", + "Edit Role for": "Editar rol para", + "Select Role": "Seleccionar rol", + "Avatar": "Avatar", + "Remove {email}": "Eliminar {email}", + "Are you sure you want to remove this invitation?": "¿Está seguro de que desea eliminar esta invitación?", + "Please select invitation type": "Por favor, seleccione el tipo de invitación", + "Please select platform role": "Por favor, seleccione el rol de la plataforma", + "Invitation sent successfully": "Invitación enviada con éxito", + "Please select a project role": "Por favor, seleccione un rol de proyecto", + "Invitation link copied successfully": "Enlace de invitación copiado correctamente", + "Invite User": "Invitar usuario", + "Invitation Link": "Enlace de invitación", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Por favor, copia el siguiente enlace y compártelo con el usuario que quieres invitar, la invitación caduca en 24 horas.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Escriba la dirección de correo electrónico del usuario al que desea invitar, la invitación expira en 24 horas.", + "Invite To": "Invitar a", + "Entire Platform": "Plataforma completa", + "Select Project Role": "Seleccionar rol de proyecto", + "Invite": "Invitar", + "Platform Role": "Rol de Plataforma", + "Select a platform role": "Seleccione un rol de plataforma", + "Are you sure you want to remove this member?": "¿Está seguro que desea eliminar este miembro?", + "Editor": "Editor", + "Operator": "Operador", + "Viewer": "Visor", + "Select a project role": "Seleccione un rol de proyecto", + "Steps in this flow": "Pasos en este flujo", + "Invalid Access": "Acceso no válido", + "Either the project does not exist or you do not have access to it.": "O el proyecto no existe o no tienes acceso a él." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/fr/translation.json b/packages/react-ui/public/locales/fr/translation.json new file mode 100644 index 0000000..7c3a139 --- /dev/null +++ b/packages/react-ui/public/locales/fr/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Publier", + "Latest version is published": "La dernière version est publiée", + "Your flow has incomplete steps": "Votre flux a des étapes incomplètes", + "Edit Flow": "Modifier le process", + "View Draft": "Voir le brouillon", + "Uncategorized": "Non catégorisé", + "Go to folder": "Aller au dossier", + "Support": "Soutien", + "Runs": "Exécutions", + "Run Logs": "Journaux d'exécution", + "Versions": "Versions", + "Versions History": "Historique des versions", + "Error generating code": "Erreur lors de la génération du code", + "AI Copilot": "Copilote IA", + "i.e Calculate the sum of a list...": "i.e Calculer la somme d'une liste...", + "Send": "Envoyer", + "Generating Code": "Génération du code", + "Hello there! I am here to generate code that helps with your flow": "Bonjour ! Je suis ici pour générer du code qui aide à votre flux", + "Here are examples of what I am best used for: ": "Voici des exemples de ce pour quoi je suis le mieux utilisé : ", + "Text Processing": "Traitement du texte", + "Process strings, dates and data": "Traiter les chaînes, dates et données", + "Data Operations": "Opérations de données", + "Change data from one format to another": "Changer les données d'un format à un autre", + "Calculations": "Calculs", + "Handle math and statistics": "Gérer les mathématiques et les statistiques", + "API Integration": "Intégration API", + "Connect with external services. Best for simple integrations currently.": "Se connecter avec des services externes. Meilleur pour des intégrations simples actuellement.", + "What would you like me to help you with?": "« Qu'est-ce que vous aimeriez que je vous aide? »", + "Insert": "Insérer", + "Data Selector": "Sélecteur de données", + "Search": "Rechercher", + "No matching data": "Aucune donnée correspondante", + "Try adjusting your search": "Essayez d'ajuster votre recherche", + "This trigger needs to have data loaded from your account, to use as sample data.": "Ce déclencheur doit contenir des données chargées à partir de votre compte, afin de les utiliser comme données d'exemple.", + "This step needs to be tested in order to view its data.": " Cette étape doit être testée pour pouvoir visualiser ses données.", + "Go to Trigger": "Aller au déclencheur", + "Go to Step": "Allez à l'étape", + "Select Mode": "Sélectionner le mode", + "Move Mode": "Mode Déplacement", + "Reset Zoom": "Réinitialiser le zoom", + "Zoom In": "Zoom avant", + "Zoom Out": "Zoom arrière", + "Fit to View": "Ajuster à la vue", + "Replace": "Remplacer", + "Copy": "Copier", + "Duplicate": "Dupliquer", + "Paste After Last Step": "Coller après la dernière étape", + "Paste Inside Loop": "Coller la boucle intérieure", + "Paste After": "Coller après", + "Paste Inside...": "Coller à l'intérieur...", + "New Branch": "Nouvelle branche", + "Paste Inside Branch": "Coller la branche intérieure", + "Delete": "Supprimer", + "Duplicate Branch": "Dupliquer la branche", + "Delete Branch": "Supprimer la branche", + "Invalid Move": "Déplacement invalide", + "The destination location is a child of the dragged step": "L'emplacement de destination est un enfant de l'étape traînée", + "End": "Fin", + "Skipped": "Ignoré", + "Incomplete settings": "Paramètres incomplets", + "logo": "Logo", + "Step Icon": "Icône d'étape", + "Branch": "Branche", + "incompleteSteps": "{invalidSteps, plural, =0 {aucune étape incomplète} =1 {Complétez 1 étape} other {Complétez # étapes}}", + "Test Flow": "Tester le process", + "Please test the trigger first": "Veuillez d'abord tester le déclencheur", + "View Only": "Lecture seul", + "Use as Draft": "Utiliser comme brouillon", + "Are you sure?": "Êtes-vous sûr ?", + "Your current draft version will be overwritten with": "Votre brouillon actuel sera écrasé par", + "version #": "version #", + "Cancel": "Annuler", + "Confirm": "Confirmer", + "Version": "Version", + "Viewing": "Visualisation", + "View": "Vue", + "Version History": "Historique des versions", + "Error, please try again.": "Erreur, veuillez réessayer!", + "Continue on Failure": "Poursuivre en cas d'échec", + "Enable this option to skip this step and continue the flow normally if it fails.": "Activez cette option pour sauter cette étape et continuer le process normalement en cas d'échec.", + "Retry on Failure": "Réessayer en cas d'échec", + "Automatically retry up to four attempts when failed.": "Réessayer automatiquement jusqu'à quatre tentatives en cas d'échec.", + "Remove": "Retirer", + "Add Item": "Ajouter un élément", + "File Input": "Entrée de fichier", + "Date Input": "Date Input", + "Dynamic value": "Valeur dynamique", + "Select an option": "Choisir une option", + "Unexpected error, please retry": "Erreur inattendue, veuillez réessayer", + "Unexpected error, please refresh the page or contact support": "Erreur inattendue, veuillez actualiser la page ou contacter le support", + "Name can only contain letters, numbers and underscores": "Le nom ne peut contenir que des lettres, des chiffres et des tirets bas", + "Ask AI": "Demander à l'IA", + "Create Todo Guide": "Créer un tutoriel de Todo", + "Where would you like the todo to be reviewed?": "Où souhaitez-vous que la todo soit examinée ?", + "Activepieces Todos": "Todos Activepieces", + "Users will manage tasks directly in Activepieces": "Les utilisateurs géreront les tâches directement dans Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Les utilisateurs géreront et répondront aux tâches directement dans l'interface Activepieces. Idéal pour les équipes internes.", + "External Channel (Slack, Teams, Email, ...)": "Canal Externe (Slack, Teams, Email, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Envoyer des notifications avec des liens d'approbation via des canaux externes comme Slack, Teams ou Email. Meilleur pour collaborer avec des parties prenantes externes.", + "Preview (Activepieces Todos)": "Aperçu (Todos actifs)", + "Preview (External channel)": "Aperçu (canal externe)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "La Todo Activepieces permet aux utilisateurs d'examiner et de résoudre les tâches directement dans l'interface Activepieces", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "Vous pouvez ajouter le canal avant l'étape d'attente, et configurer la logique à l'étape du routeur", + "Add Steps": "Ajouter des étapes", + "All": "Tous", + "AI": "AI", + "Core": "Noyau", + "Apps": "Applications", + "Not available as trigger": "Non disponible comme déclencheur", + "Not available as action": "Non disponible en tant qu'action", + "Let our AI assistant help you out": "Laissez notre assistant IA vous aider", + "Or": "Ou", + "Request Piece": "Demander une pièce", + "No pieces found": "Aucune pièce trouvée", + "Please select a piece first": "Veuillez d'abord sélectionner une pièce", + "All Iterations": "Toutes les itérations", + "Duration": "Durée", + "Input": "Entrée", + "Output": "Sortie", + "There are no logs captured for this run.": "Il n'y a pas de logs capturés pour cette course.", + "Logs are kept for {days} days after execution and then deleted.": "Les journaux sont conservés pendant {days} jours après leur exécution, puis supprimés.", + "Run Details": "Détails de l'exécution", + "Iteration": "Itération", + "Done": "Terminé", + "Took": "Reçu", + "Running": "En cours d'exécution", + "on latest version": "sur la dernière version", + "from failed step": "à partir de l'étape échouée", + "Recent Runs": "Exécutions récentes", + "No runs found": "Aucune exécutions trouvées", + "Close": "Fermer", + "OR": "OU", + "And If": "Et si", + "+ And": "+ Et", + "+ Or": "+ Ou", + "(Text) Contains": "(Texte) Contient", + "(Text) Does not contain": "(Texte) Ne contient pas", + "(Text) Exactly matches": "(Texte) Correspond exactement", + "(Text) Does not exactly match": "(Texte) Ne correspond pas exactement", + "(Text) Starts with": "(Texte) Commence par", + "(Text) Does not start with": "(Texte) Ne commence pas par", + "(Text) Ends with": "(Texte) Se termine par", + "(Text) Does not end with": "(Texte) Ne se termine pas par", + "(List) Contains": "(List) Contient", + "(List) Does not contain": "(List) Ne contient pas", + "(Number) Is greater than": "(Nombre) Est plus grand que", + "(Number) Is less than": "(Nombre) Est inférieur à", + "(Number) Is equal to": "(Nombre) Est égal à", + "(Date/time) After": "(Date/Heure) Après", + "(Date/time) Before": "(Date/Heure) Avant", + "(Date/time) Equals": "(Date/Heure) Equals", + "(Boolean) Is true": "(Booléen) Est vrai", + "(Boolean) Is false": "(Booléen) Est faux", + "(List) Is empty": "(List) Est vide", + "(List) Is not empty": "(List) N'est pas vide", + "Exists": "Existe", + "Does not exist": "N'existe pas", + "Incomplete condition": "Condition incomplète", + "First value": "Première valeur", + "Second value": "Seconde valeur", + "Case sensitive": "Sensible à la casse", + "Execute If": "Exécuter si", + "The package name is required": "Le nom du paquet est requis", + "Success": "Succès", + "Package added successfully": "Paquet ajouté avec succès", + "Could not fetch package version": "Impossible de récupérer la version du paquet", + "Add NPM Package": "Ajouter un paquet NPM", + "Type the name of the npm package you want to add.": "Tapez le nom du paquet npm que vous souhaitez ajouter.", + "Package Name": "Nom du paquet", + "The latest version will be fetched and added": "La dernière version sera récupérée et ajoutée", + "Add": "Ajouter", + "Code": "Code", + "Dependencies": "Dépendances", + "Use code": "Utiliser le code", + "Add package": "Ajouter un paquet", + "Inputs": "Entrées", + "Edit Step Name": "Modifier le nom de l'étape", + "Edit Branch Name": "Modifier le nom de la branche", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Sélectionnez les éléments à itérer à partir de l'étape précédente en cliquant sur l'entrée **Éléments**, qui doit être une **liste** d'éléments.\n\nLa boucle parcourt chaque élément de la liste et exécute l'étape suivante pour chaque élément.", + "Items": "Éléments", + "Select an array of items": "Sélectionner un tableau d'éléments", + "Reconnect": "Reconnecter", + "Select a connection": "Sélectionner une connexion", + "Create Connection": "Créer une connexion", + "Rename": "Renommer", + "Move": "Déplacer", + "Add Branch": "Ajouter une branche", + "Execute": "Exécuter", + "Only the first (left) matching branch": "Seulement la première branche correspondante (à gauche)", + "All matching paths from left to right": "Tous les chemins correspondants de gauche à droite", + "Branches": "Branches", + "{field} is required": "{field} est requis", + "Tool Sample Data": "Données d'exemple d'outil", + "Fill in the following fields to use them as sample data for the trigger.": "Remplissez les champs suivants pour les utiliser comme données d'exemple pour le déclencheur.", + "No input fields defined in the schema": "Aucun champ de saisie défini dans le schéma", + "Save": "Enregistrer", + "Test Environment": "Environnement de test", + "Assigned to": "Assigné à", + "(Me)": "(Moi)", + "Please select status to resolve the todo": "Veuillez sélectionner le statut pour résoudre la tâche", + "Resolve": "Résoudre", + "Change status to resolved": "Changer le statut en statut résolu", + "Send Sample Data to Webhook": "Envoyer des exemples de données au Webhook", + "Method": "Méthode", + "Query Params": "Paramètres de requête", + "Headers": "En-têtes", + "Body": "Corps", + "Type": "Type de texte", + "JSON": "JSON", + "Text": "Texte du texte", + "Form Data": "Données du formulaire", + "Generate Sample Data": "Générer un échantillon de données", + "Test Step": "Étape de test", + "Retest": "Retester", + "Testing Failed": "Échec du test", + "Tested Successfully": "Testé avec succès", + "Logs": "Journaux", + "There is no sample data available found for this trigger.": "Il n'y a pas d'échantillon de données disponibles pour ce déclencheur.", + "Internal error, please try again later.": "Erreur interne, veuillez réessayer plus tard.", + "Failed to run test step and no error message was returned": "Échec de l'exécution de l'étape de test et aucun message d'erreur n'a été retourné", + "Please fix inputs first": "Veuillez d'abord corriger les entrées", + "No sample data available": "Aucune donnée d'échantillon disponible", + "Old results were removed, retest for new sample data": "Les anciens résultats ont été supprimés, réessayez pour les nouvelles données d'échantillonnage", + "Result #": "Résultat #", + "The sample data can be used in the next steps.": "Les données de tests peuvent être utilisées lors des prochaines étapes.", + "Testing Trigger": "Déclencheur de test", + "Action Required": "Action requise", + "testPieceWebhookTriggerNote": "Veuillez vous rendre dans {pieceName} et déclencher {triggerName}.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Envoyer des données à l'URL du webhook pour générer des exemples de données à utiliser lors des prochaines étapes", + "Test Trigger": "Tester le déclencheur", + "Use Mock Data": "Utiliser les données fictives", + "Load Sample Data": "Charger un échantillon de données", + "Test Tool": "Outil de test", + "home": "accueil", + "Home": "Accueil", + "Alerts": "Alertes", + "Releases": "Versions", + "Flows": "Process", + "Products": "Produits", + "MCP": "MCP", + "Tables": "Tables", + "Todos": "Todos", + "Push to Git": "Pousser vers Git", + "Move To": "Déplacer vers", + "Duplicating": "Duplication en cours", + "Import": "Importer", + "Exporting": "Exportation en cours", + "Export": "Exporter", + "Share": "Partager", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Êtes-vous sûr de vouloir supprimer ce process ? Cela supprimera définitivement le flux, toutes ses données et toutes les exécutions en arrière-plan.", + "You are on a development branch, this will also delete the flow from the remote repository.": "Vous êtes sur une branche de développement, cela supprimera également le flux du référentiel distant.", + "flow": "process", + "Community Support": "Support de la communauté", + "Overview": "Aperçu", + "Projects": "Projets", + "Users": "Utilisateurs", + "Setup": "Configuration", + "Branding": "Image de marque", + "Global Connections": "Connexions Globales", + "Pieces": "Éléments", + "Templates": "Modèles", + "License Key": "Clé de licence", + "Security": "Sécurité", + "Audit Logs": "Journaux d'audit", + "Single Sign On": "Authentification unique (Single Sign On)", + "Signing Keys": "Clés de signature", + "Project Roles": "Rôles du projet", + "API Keys": "Clés API", + "Infrastructure": "Infrastructure", + "Workers": "Collaborateurs-trices", + "Health": "Santé", + "Billing": "Facturation", + "Settings": "Paramètres", + "Contact Sales": "Contacter les ventes", + "General": "Général", + "Appearance": "Apparence", + "Team": "Équipe", + "Environments": "Environnements", + "Project Settings": "Paramètres du projet", + "Exit Platform Admin": "Quitter l'administrateur de la plateforme", + "Enter Platform Admin": "Entrer l'administrateur de la plateforme", + "Logout": "Déconnexion", + "Misc": "Divers", + "Connections": "Connexions", + "days": "Jours", + "hours": "heures", + "minutes": "minutes", + "Today": "Aujourd'hui", + "Tasks": "Tâches", + "AI Credits": "AI Credits", + "Usage resets in": "Utilisation réinitialisée dans", + "Manage": "Gérer", + "Unlimited": "Illimité", + "Enabled": "Activé", + "Disabled": "Désactivé", + "Could not claim the authorization code, make sure you have correct settings and try again.": "Impossible de réclamer le code d'autorisation, assurez-vous que vous avez des paramètres corrects et réessayez.", + "Connection failed with error {msg}": "La connexion a échoué avec l'erreur {msg}", + "You don't have the permission to create a connection.": "Vous n'avez pas la permission de créer une connexion.", + "Reconnect {displayName} Connection": "Reconnecter {displayName}", + "Connect to {displayName}": "Se connecter à {displayName}", + "Connection Name": "Nom de la connexion", + "Connection name": "Nom Connexion", + "New Connection": "Nouvelle connexion", + "Redirect URL": "URL de redirection", + "Client ID": "ID client", + "Client Secret": "Clé secrète du client", + "Connect": "Connecter", + "Disconnect": "Déconnecter", + "I would like to use my own App Credentials": "Je voudrais utiliser mes propres identifiants d'application", + "I would like to use predefined App Credentials": "Je voudrais utiliser mes propres identifiants d'application", + "Permission needed": "Autorisation requise", + "Connections replaced successfully": "Connexions remplacées avec succès", + "Error": "Erreur", + "Failed to replace connections": "Impossible de remplacer les connexions", + "Failed to get affected flows": "Impossible d'obtenir les flux affectés", + "Please select a piece": "Veuillez sélectionner une pièce", + "Please select a connection to replace": "Veuillez sélectionner une connexion à remplacer", + "Please select a connection to replace with": "Veuillez sélectionner une connexion à remplacer par", + "Replace Connections": "Remplacer les connexions", + "Confirm Replacement": "Confirmer le remplacement", + "Replace one connection with another.": "Remplacer une connexion par une autre.", + "This action requires ": "Cette action nécessite ", + "reconnecting": "reconnexion", + " any associated MCP pieces.": " toutes les pièces MCP associées.", + "Piece": "Widget", + "Select a piece": "Sélectionnez une pièce", + "Connection to Replace": "Connexion à Remplacer", + "Choose connection to replace": "Choisir la connexion à remplacer", + "Replaced With": "Remplacé par", + "Choose connection to replace with": "Choisissez la connexion à remplacer par", + "All flows will be changed to use the replaced with connection": "Tous les flux seront modifiés pour utiliser le remplacement avec la connexion", + "Next": "Suivant", + "No flows will be affected by this change": "Aucun flux ne sera affecté par ce changement", + "Back": "Précédent", + "Unnamed tool": "Outil sans nom", + "This flow is enabled": "Ce flux est activé", + "Enable this flow to make it available": "Activer ce flux pour le rendre disponible", + "Piece is updated successfully": "Pièce mise à jour avec succès", + "Piece is added successfully": "Pièce ajoutée avec succès", + "Failed to update piece": "Échec de la mise à jour de la pièce", + "Failed to add piece": "Impossible d'ajouter la pièce", + "Please select a connection": "Veuillez sélectionner une connexion", + "Your MCP server already has this piece": "Votre serveur MCP a déjà cette pièce", + "+ New Connection": "+ Nouvelle connexion", + "Edit Piece": "Modifier la pièce", + "Add Piece": "Ajouter une pièce", + "Connection": "Connexion", + "MCP piece": "Pièce MCP", + "Failed to update piece status": "Impossible de mettre à jour le statut de la pièce", + "Connection required": "Connexion requise", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Êtes-vous sûr de vouloir supprimer cet outil de votre MCP ? Si vous le supprimez, vous ne pourrez pas l'utiliser dans votre client MCP.", + "piece": "pièce", + "Connect your AI assistant to external services": "Connectez votre assistant IA aux services externes", + "Collapse": "Réduire", + "Show All": "Tout afficher", + "Server URL": "URL du serveur", + "Hide the token for security": "Masquer le jeton pour la sécurité", + "Show the token": "Afficher le jeton", + "Generate a new token for security. This will invalidate the current URL.": "Générer un nouveau jeton pour la sécurité. Cela invalidera l'URL actuelle.", + "URL copied to clipboard": "URL copiée dans le presse-papiers", + "Copy URL to clipboard": "Copier l'URL dans le presse-papiers", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "Cette URL contient un jeton de sécurité sensible. Partagez-le uniquement avec des applications et des services de confiance. Tournez le jeton si vous soupçonnez qu'il a été compromis.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "Après avoir modifié les connexions ou les flux, vous devrez reconnecter votre serveur MCP pour que les changements prennent effet.", + "Max tables reached": "Nombre maximum de tables atteint", + "You can't create more than {maxTables} tables": "Vous ne pouvez pas créer plus de tables {maxTables}", + "Name": "Nom", + "Created": "Créé", + "Delete Tables": "Supprimer les tables", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Êtes-vous sûr de vouloir supprimer les tables sélectionnées ? Cette action ne peut pas être annulée.", + "table": "table", + "Create and manage your tables to store your automation data": "Créez et gérez vos tables pour stocker vos données d'automatisation", + "New Table": "Nouvelle table", + "No tables have been created yet": "Aucune table n'a encore été créée", + "Create a table to get started and start managing your automation data": "Créez une table pour commencer et commencer à gérer vos données d'automatisation", + "Error deleting connections": "Erreur lors de la suppression des connexions", + "Status": "Statut", + "Display Name": "Afficher nom", + "Owner": "Propriétaire", + "App": "Application", + "This connection is global and can be managed in the platform admin": "Cette connexion est globale et peut être gérée dans l'administrateur de la plateforme", + "External ID": "ID externe", + "Connected At": "Connecté à", + "Confirm Deletion": "Confirmer la suppression", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Êtes-vous sûr de vouloir supprimer les connexions sélectionnées ? Cette action ne peut pas être annulée.", + "Deleting connections may cause your Flows or MCP tools to break.": "La suppression des connexions peut causer des ruptures de vos flux ou de vos outils MCP.", + "Manage project connections to external systems.": "Gérer les connexions de projets à des systèmes externes.", + "No connections found": "Aucune connexion trouvée", + "Come back later when you create a automation to manage your connections": "Revenez plus tard lorsque vous créez une automatisation pour gérer vos connexions", + "Steps": "Étapes", + "Folder": "Répertoire", + "Flow name": "Nom du flux", + "No flows found": "Aucun flux trouvé", + "Create a workflow to start automating": "Créer un workflow pour démarrer l'automatisation", + "Create and manage your flows, run history and run issues": "Créez et gérez vos flux, exécutez l'historique et exécutez des problèmes", + "Issues": "Problèmes", + "Untitled": "Sans titre", + "Create flow": "Créer un flux", + "From scratch": "À partir de zéro", + "Use a template": "Utiliser un modèle", + "From local file": "Depuis un fichier local", + "Flow Name": "Nom du flux", + "Count": "Nombre", + "First Seen": "Vu pour la première fois", + "Last Seen": "Vu pour la dernière fois", + "Issues in {flowDisplayName} is marked as resolved.": "Les demandes dans {flowDisplayName} sont marquées comme résolues.", + "Unlock Issues": "Débloquer les problèmes", + "Track issues in your workflows and troubleshoot them.": "Suivre les problèmes de vos workflows et les dépanner.", + "No issues found": "Aucun problème trouvé", + "All your workflows are running smoothly.": "Tous vos flux de travail fonctionnent correctement.", + "Mark as Resolved": "Marqué comme résolu", + "All Flows Are Turned Off": "Tous les flux sont désactivés", + "Task Usage Exceeded": "Utilisation de la tâche dépassée", + "of the Allowed Limit.": "de la limite autorisée.", + "When a project tasks limit is reached,": "Quand une limite de tâches de projet est atteinte,", + "all flows will be turned off and you will not be able to run any flows.": "tous les flux seront désactivés et vous ne pourrez pas exécuter de flux.", + "Please visit": "Veuillez visiter", + "Your Plan": "Votre plan", + "and increase your task limit, which requires your payment details.": "et augmentez votre limite de tâches, ce qui nécessite vos informations de paiement.", + "Please contact your admin to increase the project task limit.": "Veuillez contacter votre administrateur pour augmenter la limite de tâches du projet.", + "and increase the project task limit.": "et augmenter la limite de tâches du projet.", + "Dismiss": "Refuser", + "Token rotated successfully": "Jeton tourné avec succès", + "Failed to rotate token": "Impossible de pivoter le jeton", + "Piece removed successfully": "Pièce supprimée avec succès", + "Failed to remove piece": "Impossible de supprimer la pièce", + "Flow created successfully": "Flux créé avec succès", + "Failed to create flow": "Impossible de créer le flux", + "Add Flow": "Ajouter Flux", + "Let your AI assistant trigger automations": "Laisser les automatisations déclenchées par votre assistant IA", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Connectez-vous à votre serveur MCP hébergé en utilisant n'importe quel client MCP pour communiquer avec les outils", + "MCP Server": "Serveur MCP", + "My Tools": "Mes outils", + "Create Flow": "Créer un flux", + "Note": "Note", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "Si vous souhaitez exposer votre serveur MCP à Internet, veuillez définir la variable d'environnement AP_FRONTEND_URL sur l'URL publique de votre instance Activepieces .", + "This URL grants access to your tools and data. Only share with trusted applications.": "Cette URL vous donne accès à vos outils et à vos données. Seulement partager avec des applications de confiance.", + "Server Configuration": "Configuration du serveur", + "Hide sensitive data": "Masquer les données sensibles", + "Show sensitive data": "Afficher les données sensibles", + "Create a new URL. The current one will stop working.": "Créer une nouvelle URL. L'actuelle cessera de fonctionner.", + "Copy configuration": "Copier la configuration", + "Configuration copied to clipboard": "Configuration copiée dans le presse-papiers", + "Claude": "Famille Claude", + "Cursor": "Cursor", + "Windsurf": "Planche à voile", + "Server/Other": "Serveur/Autre", + "Note: MCPs only work with": "Note: Les MCPs ne fonctionnent qu'avec", + "Claude Desktop": "Bureau Claude", + ", not the web version.": ", pas la version web.", + "Prerequisites:": "Pré-requis :", + "Install": "Installer", + "Node.js": "Node.js", + "and": "et", + "Open Settings:": "Ouvrir les paramètres:", + "Click the menu and select": "Cliquez sur le menu et sélectionnez", + "Developer": "Développeur", + "Configure MCP:": "Configurer MCP :", + "Click": "Click", + "Edit Config": "Modifier la configuration", + "and paste the configuration below": "et collez la configuration ci-dessous", + "Save and Restart:": "Enregistrer et redémarrer :", + "Save the config and restart Claude Desktop": "Enregistrez la configuration et redémarrez Claude Desktop", + "Navigate to": "Naviguer vers", + "Cursor Settings": "Paramètres du curseur", + "Add Server:": "Ajouter un serveur:", + "Add new global MCP server": "Ajouter un nouveau serveur MCP global", + "Configure:": "Configurer :", + "Paste the configuration below and save": "Collez la configuration ci-dessous et enregistrez", + "Use either method:": "Utiliser l'une ou l'autre des méthodes:", + "Go to": "Aller à", + "Advanced Settings": "Paramètres avancés", + "Open Command Palette and select": "Ouvrez la palette de commandes et sélectionnez", + "Windsurf Settings Page": "Page des paramètres du Windsurf", + "Navigate to Cascade:": "Naviguez vers la Cascade:", + "Select": "Sélectionner", + "Cascade": "Cascade", + "in the sidebar": "dans la barre latérale", + "Add Server": "Ajouter un serveur", + "Add custom server +": "Ajouter un serveur personnalisé +", + "Copy URL": "Copier l'URL", + "Client Setup Instructions": "Instructions de configuration du client", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "Après avoir changé de connexions ou de flux, reconnectez votre serveur MCP pour que les changements prennent effet.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Suivez ces étapes pour configurer MCP dans votre client préféré. Cela permet à votre assistant AI d'accéder à vos outils.", + "icon": "icône", + "Unlock Analytics": "Débloquer Analyses", + "Get insights into your platform usage and performance with our analytics dashboard": "Obtenez un aperçu de l'utilisation et des performances de votre plateforme avec notre tableau de bord analytique", + "Active Flows": "Flux Actifs", + "The number of enabled flows in the platform": "Le nombre de flux activés sur la plateforme", + "Active Projects": "Projets Actifs", + "The number of projects with at least one enabled flow": "Le nombre de projets avec au moins un flux activé", + "Active Users": "Utilisateurs actifs", + "The number of users logged in the last 30 days": "Le nombre d'utilisateurs connectés dans les 30 derniers jours", + "Out of {totalusers} total users": "Out of {totalusers} total users", + "Pieces Used": "Pièces utilisées", + "The number of unique pieces used across all flows": "Le nombre de pièces uniques utilisées sur tous les flux", + "Flows with AI": "Flux avec l'IA", + "The number of enabled flows that use AI pieces": "Le nombre de flux activés qui utilisent les pièces IA", + "Metrics": "Métriques", + "Executed Tasks": "Tâches exécutées", + "Showing total executed tasks for specified time range": "Affichage du total des tâches exécutées pour la plage de temps spécifiée", + "Tasks Usage Limit": "Limite d'utilisation des tâches", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Spécifiez une limite mensuelle pour les tâches afin d'éviter une utilisation excessive. Vos flux ne s'exécuteront plus si cette limite a été atteinte.", + "Number of monthly tasks": "Nombre de tâches mensuelles", + "Save changes": "Enregistrer les modifications", + "Limits updated successfully": "Limites mises à jour avec succès", + "Failed to update limits": "Échec de la mise à jour des limites", + "Failed to load billing information": "Impossible de charger les informations de facturation", + "Billing Amount": "Montant de la facturation", + "Manage Payment Details": "Gérer les détails de paiement", + "Add Payment Details": "Ajouter des détails de paiement", + "Current Task Usage": "Utilisation de la tâche actuelle", + "Count of executed steps": "Nombre d'étapes exécutées", + "Billing Limit": "Limite de facturation", + "Edit": "Editer", + "Add Limit": "Ajouter une limite", + "Current Credit Usage": "Utilisation actuelle du crédit", + "WebSocket Connection": "Connexion WebSocket", + "Connected": "Connecté", + "Disconnected": "Déconnecté", + "No issues detected": "Aucun problème détecté", + "Check the status of your platform and its components": "Vérifiez l'état de votre plateforme et de ses composants", + "System Health Status": "Statut de santé du système", + "All systems are running smoothly": "Tous les systèmes fonctionnent en douceur", + "Check the health of your worker machines": "Contrôler la santé de vos machines de travail", + "Workers Machine": "Machine de travail", + "This is demo data. In a real environment, this would show your actual worker machines.": "Il s'agit de données de démonstration. Dans un environnement réel, cela montrerait vos machines de travail.", + "No workers found": "Aucun travailleur trouvé", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "Vous n'avez pas encore de machines de travail. Faites tourner de nouvelles machines pour exécuter vos automatisations", + "IP Address": "Adresse IP", + "CPU Usage": "Usage du CPU", + "Disk Usage": "Utilisation du disque", + "RAM Usage": "RAM Usage", + "Last Contact": "Dernier contact", + "Configs": "Configurations", + "Environment Variables": "Variables d'environnement", + "Websocket Connection Error": "Erreur de connexion Websocket", + "Retry Connection": "Réessayer la connexion", + "Update Available": "Mise à jour disponible", + "Update Now": "Mettre à jour maintenant", + "Your Universal AI needs a quick setup": "Votre Universal AI a besoin d'une installation rapide", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "Nous avons remarqué que vous n'avez pas encore configuré de fournisseur IA. Pour débloquer des pièces Universal AI pour votre équipe, vous devez d'abord configurer certains identifiants du fournisseur.", + "Configure": "Configurer", + "Platform Alerts": "Alertes de la plateforme", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "Il y a des alertes importantes sur la plateforme qui nécessitent votre attention. Veuillez consulter la section alertes dans l'administrateur de la plate-forme.", + "View Alerts": "Voir les alertes", + "Used Tasks": "Tâches utilisées", + "Used AI Credits": "Crédits AI utilisés", + "Cannot delete active project, switch to another project first": "Impossible de supprimer le projet actif, basculez d'abord vers un autre projet", + "Delete Projects": "Supprimer des projets", + "Are you sure you want to delete the selected projects?": "Êtes-vous sûr de vouloir supprimer les projets sélectionnés ?", + "New Project": "Nouveau projet", + "Validation error": "Erreur de validation", + "Project has enabled flows. Please disable them first.": "Le projet a activé les flux. Veuillez d'abord les désactiver.", + "This project is active. Please switch to another project first.": "Ce projet est actif. Veuillez d'abord passer à un autre projet.", + "Unlock Projects": "Débloquer les projets", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Organisez vos équipes d'automatisation à travers vos projets avec leurs propres flux, connexions et quotas d'utilisation", + "Manage your automation projects": "Gérez vos projets d'automatisation", + "No projects found": "Aucun projet trouvé", + "Start by creating projects to manage your automation teams": "Commencez par créer des projets pour gérer vos équipes d'automatisation", + "Edit project": "Modifier le projet", + "Name is required": "Le nom est nécessaire", + "Create New Project": "Créer un nouveau projet", + "Project Name": "Nom du projet", + "Id": "Id", + "Enable API Keys": "Activer les clés API", + "Create and manage API keys to access Activepieces APIs.": "Créez et gérez des clés API pour accéder aux API Activepieces .", + "New Api Key": "Nouvelle clé Api", + "No API keys found": "Aucune clé API trouvée", + "Start by creating an API key to communicate with Activepieces APIs": "Commencez par créer une clé API pour communiquer avec les API Activepieces", + "Delete API Key": "Supprimer la clé API", + "Are you sure you want to delete this API key?": "Êtes-vous sûr de vouloir supprimer cette clé API ?", + "API Key": "Clé API", + "API Key Created": "Clé API créée", + "Create New API Key": "Créer une nouvelle clé API", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Veuillez enregistrer cette clé secrète dans un endroit sûr et accessible. Pour des raisons de sécurité,", + "you won't be able to view it again after closing this dialog.": "vous ne pourrez plus le voir après la fermeture de cette boîte de dialogue.", + "API Key Name": "Nom de la clé API", + "Action": "Action", + "Performed By": "Réalisé par", + "Project": "Projet", + "Unlock Audit Logs": "Débloquer les journaux d'audit", + "Comply with internal and external security policies by tracking activities done within your account": "Se conformer aux politiques de sécurité internes et externes en suivant les activités effectuées dans votre compte", + "Track activities done within your platform": "Suivre les activités effectuées sur votre plateforme", + "No audit logs found": "Aucun journal d'audit trouvé", + "Come back later when you have some activity to audit": "Revenez plus tard lorsque vous aurez une activité à auditer", + "Resource": "Ressource", + "Details": "Détails du produit", + "N/A": "N/A", + "Flow Run": "Course de flux", + "Flow": "Flux", + "User": "Utilisateur", + "Signing Key": "Clé de signature", + "Project Role Management": "Gestion des rôles du projet", + "Define custom roles and permissions to control what your team members can access and modify": "Définir les rôles personnalisés et les autorisations pour contrôler ce que les membres de votre équipe peuvent accéder et modifier", + "Define custom roles and permissions that can be assigned to your team members": "Définir les rôles personnalisés et les permissions qui peuvent être assignés aux membres de votre équipe", + "New Role": "Nouveau rôle", + "Contact sales to unlock custom roles": "Contactez les ventes pour débloquer des rôles personnalisés", + "Create New Role": "Créer un nouveau rôle", + "View ": "Voir ", + "Edit ": "Editer ", + "Role Name": "Nom du rôle", + "Permissions": "Permissions", + "None": "Aucun", + "Read": "Lu", + "Write": "Écrire", + "Create": "Créer", + "Email": "Courriel", + "First Name": "Prénom", + "Last Name": "Nom", + "Roles": "Rôles", + "View the users assigned to this role": "Voir les utilisateurs assignés à ce rôle", + "Role": "Rôle", + "No users found": "Aucun utilisateur trouvé", + "Starting by assigning users to this role": "Commencer par assigner des utilisateurs à ce rôle", + "Project Role entry deleted successfully": "Entrée de rôle de projet supprimée avec succès", + "Updated": "Mis à jour", + "No project roles found": "Aucun rôle de projet trouvé", + "Create custom project roles to manage permissions for platform users": "Créer des rôles personnalisés de projet pour gérer les autorisations des utilisateurs de la plateforme", + "Show Users": "Afficher les utilisateurs", + "View Role": "Voir le rôle", + "Edit Role": "Modifier le rôle", + "Delete Role": "Supprimer le rôle", + "Project Role": "Rôle du projet", + "Unlock Embedding Through JS SDK": "Débloquer l'intégration dans le SDK JS", + "Enable signing keys to access embedding functionalities.": "Activer la signature de clés pour accéder aux fonctionnalités d'intégration.", + "New Signing Key": "Nouvelle clé de signature", + "No signing keys found": "Aucune clé de signature trouvée", + "Create a signing key to authenticate users with embedding": "Créer une clé de signature pour authentifier les utilisateurs avec intégration", + "Delete Signing Key": "Supprimer la clé de signature", + "Are you sure you want to delete this signing key?": "Êtes-vous sûr de vouloir supprimer cette clé de signature ?", + "Signing Key Created": "Clé de signature créée", + "Create New Signing Key": "Créer une nouvelle clé de signature", + "Signing Key Name": "Nom de la clé de signature", + "Allowed domains updated": "Domaines autorisés mis à jour", + "Update": "Mise à jour", + "Enable": "Activer", + "Configure Allowed Domains": "Configurer les domaines autorisés", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Entrez les domaines autorisés pour les utilisateurs avec lesquels s'authentifier, la liste vide autorisera tous les domaines.", + "Add Domain": "Ajouter un domaine", + "Allow logins through {providerName}'s single sign-on functionality.": "Autoriser les connexions grâce à la fonctionnalité de connexion unique de {providerName}.", + "Email authentication updated": "Authentification par e-mail mise à jour", + "Enable Single Sign On": "Activer le Single Sign On", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Laissez vos utilisateurs se connecter avec votre fournisseur SSO actuel ou leur donner un accès à l'inscription en libre-service", + "Allowed Domains": "Domaines autorisés", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Permettre aux utilisateurs de s'authentifier avec des domaines spécifiques. Laisser vide pour autoriser tous les domaines.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Connexion à l'e-mail autorisée", + "Allow logins through email and password.": "Autoriser les connexions par e-mail et mot de passe.", + "Single sign on settings updated": "Paramètres de l'authentification unique mis à jour", + "Disable": "Désactiver", + "Configure {provider} SSO": "Configurer SSO {provider}", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "En savoir plus sur la façon de configurer {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "ID client {provider}", + "{provider} Client Secret": "Secret client {provider}", + "Single sign-on settings updated": "Paramètres d'authentification unique mis à jour", + "Configure SAML 2.0 SSO": "Configurer SSO SAML 2.0", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Instructions de configuration** :\nVeuillez vérifier la documentation suivante : [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**URL de signature unique** :\n```text\n{samlAcs}\n```\n**URI d'audience (SP Entity ID)** :\n```text\nActivepieces\n```\n", + "IDP Metadata": "Métadonnées IDP", + "IDP Certificate": "Certificat IDP", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "URL de base", + "Resource Name": "Nom de la ressource", + "Deployment Name": "Nom du déploiement", + "Saving": "Sauvegarde", + "Activepieces Copilot": "Copilote de pièces actives", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot est configuré et prêt à aider vos utilisateurs à construire des flux plus rapidement en utilisant AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configurer le Copilot Activepieces pour aider vos utilisateurs à construire des flux plus rapidement en utilisant AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Définissez les paramètres de vos fournisseurs d'IA et de votre pilote afin que vos utilisateurs puissent profiter d'une expérience de construction transparente avec nos pièces IA universelles", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Définissez les informations d'identification du fournisseur qui seront utilisées par les pièces IA universelles, c.-à-d. l'IA. de texte.", + "AI Providers": "AI Providers", + "Copilot": "Copilote", + "Configure credentials for {providerName} AI provider.": "Configurer les identifiants pour le fournisseur IA {providerName}.", + "Update AI Provider": "Mettre à jour le fournisseur AI", + "Enable AI Provider": "Activer le fournisseur IA", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Veuillez entrer un domaine valide", + "Your changes have been saved.": "Vos changements ont été enregistrés.", + "The domain is already added.": "Le domaine est déjà ajouté.", + "Add Custom Domain": "Ajouter un domaine personnalisé", + "Enter a domain name without a protocol (e.g. example.com)": "Entrez un nom de domaine sans protocole (par exemple exemple.com)", + "Logo URL": "URL du logo", + "Icon URL": "URL de l'icône", + "Favicon URL": "URL du favicon", + "Default Language": "Langue par défaut", + "Select Language": "Sélectionner la langue", + "No Languages": "Aucune langue", + "Primary Color": "Couleur principale", + "Custom Domains": "Domaines personnalisés", + "No domains added yet.": "Aucun domaine n'a été ajouté.", + "Verified": "Vérifié", + "Pending, please contact the support for dns verification.": "En attente, veuillez contacter le support pour la vérification dns.", + "Are you sure you want to delete {domain}?": "Êtes-vous sûr de vouloir supprimer {domain}?", + "Brand Activepieces": "Marque Activepieces", + "Give your users an experience that looks like you by customizing the color, logo and more": "Donnez à vos utilisateurs une expérience qui vous ressemble en personnalisant la couleur, le logo et plus encore", + "Configure the appearance and SMTP settings for your platform.": "Configurez l'apparence et les paramètres SMTP de votre plateforme.", + "Invalid host": "Hôte invalide", + "Invalid username": "Nom d'utilisateur non valide", + "Invalid password": "Mot de passe invalide", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Nom de l'expéditeur invalide", + "SMTP is configured": "SMTP est configuré", + "Mail Server": "Serveur de messagerie", + "Set up your SMTP settings to send emails from your domain.": "Configurez vos paramètres SMTP pour envoyer des emails depuis votre domaine.", + "Disable Mail Server": "Désactiver le serveur de messagerie", + "Are you sure you want to disable your mail server?": "Êtes-vous sûr de vouloir désactiver votre serveur de messagerie ?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "Cela vous empêchera d'envoyer des e-mails pour les tickets, les limites de quotas, les invitations et les mots de passe oubliés.", + "mail server": "Serveur de messagerie", + "Host": "Hôte", + "Port": "Port", + "Username": "Nom d'utilisateur", + "Password": "Mot de passe", + "Sender Email": "E-mail de l'expéditeur", + "Sender Name": "Nom de l'expéditeur", + "Enable Global Connections": "Activer les connexions globales", + "Manage platform-wide connections to external systems.": "Gérer les connexions à des systèmes externes à l'échelle de la plate-forme.", + "No global connections found": "Aucune connexion globale trouvée", + "Create a global connection that can be shared to multiple projects": "Créer une connexion globale qui peut être partagée sur plusieurs projets", + "License key is invalid": "La clé de licence n'est pas valide", + "Invalid license key": "Clé de licence invalide", + "License activated!": "Licence activée !", + "Activate License Key": "Activer la clé de licence", + "Let the magic begin!": "« Que la magie commence! »", + "Activate": "Activer", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "Cette fonctionnalité n'est pas encore autonome dans le cloud, veuillez contacter sales@activepieces.com. ", + "This feature is not available in your current edition. ": "Cette fonctionnalité n'est pas disponible dans votre édition actuelle. ", + "Learn how to upgrade": "Apprendre à améliorer", + "Activate your platform and unlock enterprise features": "Activez votre plateforme et débloquez les fonctionnalités d'entreprise", + "Activate License": "Activer la licence", + "Expiration": "Expiration", + "Valid until": "Valable jusqu'au", + "Expired": "Expiré", + "Expires soon": "Expire bientôt", + "Features": "Fonctionnalités", + "Applying Tags...": "Application des balises...", + "Tags applied.": "Tags appliqués.", + "Tag created": "Étiquette créée", + "Tag": "Étiquette", + "Tags": "Tags", + "Control Pieces": "Éléments de contrôle", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Montrez les pièces qui comptent le plus pour vos utilisateurs et masquez celles que vous n'aimez pas.", + "Manage the pieces that are available to your users": "Gérez les pièces disponibles pour vos utilisateurs", + "Start by installing pieces that you want to use in your automations": "Commencez par installer des pièces que vous souhaitez utiliser dans vos automatisations", + "Piece Name": "Nom du widget", + "Hide this piece from all projects": "Cacher cette pièce de tous les projets", + "Show this piece for all projects": "Afficher cette pièce pour tous les projets", + "Unpin this piece": "Désépingler cette pièce", + "Pin this piece": "Épingler cette pièce", + "Pieces synced": "Pièces synchronisées", + "Pieces have been synced from the activepieces cloud.": "Des morceaux ont été synchronisés à partir du nuage de pièces actives.", + "OAuth2 Credentials Deleted": "Les identifiants OAuth2 ont été supprimés", + "OAuth2 Credentials Updated": "Informations d’identification OAuth2 mises à jour", + "Configure OAuth2 APP": "Configurer l'application OAuth2", + "Delete OAuth2 APP": "Supprimer l'application OAuth2", + "Templates deleted successfully": "Modèles supprimés avec succès", + "Delete Templates": "Supprimer les modèles", + "Are you sure you want to delete the selected templates?": "Êtes-vous sûr de vouloir supprimer les modèles sélectionnés ?", + "New Template": "Nouveau modèle", + "Unlock Templates": "Débloquer les Modèles", + "Convert the most common automations into reusable templates 1 click away from your users": "Convertir les automatisations les plus courantes en modèles réutilisables en 1 clic loin de vos utilisateurs", + "Convert the most common automations into reusable templates": "Convertir les automations les plus courantes en modèles réutilisables", + "No templates found": "Aucun modèle trouvé", + "Create a template for your user to inspire them": "Créez un modèle pour que votre utilisateur puisse les inspirer", + "Edit template": "Modifier le modèle", + "Template is required": "Le modèle est requis", + "Update New Template": "Mettre à jour le nouveau modèle", + "Create New Template": "Créer un nouveau modèle", + "Template Name": "Nom du modèle", + "Description": "Libellé", + "Template Description": "Description du modèle", + "Blog URL": "URL du blog", + "Template Blog URL": "URL du blog du modèle", + "Template": "Gabarit", + "Invalid JSON": "JSON invalide", + "User deleted successfully": "Utilisateur supprimé avec succès", + "User activated successfully": "Utilisateur activé avec succès", + "User deactivated successfully": "Utilisateur désactivé avec succès", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Gérez vos utilisateurs et leur accès à vos projets", + "Start inviting users to your project": "Commencer à inviter des utilisateurs à rejoindre votre projet", + "External Id": "Id externe", + "Admin": "Administrateur", + "Member": "Membre", + "Activated": "Activé", + "Deactivated": "Désactivé", + "Edit user": "Modifier l'utilisateur", + "Admin cannot be deactivated": "L'administrateur ne peut pas être désactivé", + "Deactivate user": "Désactiver l'utilisateur", + "Activate user": "Activer l'utilisateur", + "Delete User": "Supprimer l'utilisateur", + "Are you sure you want to delete this user?": "Êtes-vous sûr de vouloir supprimer cet utilisateur ?", + "Delete user": "Supprimer l'utilisateur", + "Update User Role": "Mettre à jour le rôle de l'utilisateur", + "Meeting Summary Flow": "Flux Résumé de la réunion", + "Added new features and fixed bugs": "Ajout de nouvelles fonctionnalités et correction de bugs", + "Flows Changes": "Changements de flux", + "Connections Changes": "Changements des connexions", + "New connections are placeholders and need to be reconnected again": "Les nouvelles connexions sont des espaces réservés et doivent être reconnectées", + "renamed to": "renommé en", + "Tables Changes": "Changements des tables", + "No changes to apply": "Aucune modification à appliquer", + "Apply Changes": "Appliquer les modifications", + "Create Git Release": "Créer une version Git", + "Create Project Release": "Créer une version de projet", + "Create Rollback to": "Créer un retour à", + "Source": "Source", + "Rollback": "Rollback", + "Imported At": "Importé le", + "Imported By": "Importé par", + "Track and manage your project version history and deployments. ": "Suivez et gérez l'historique et les déploiements de vos versions de projet. ", + "Environments & Releases": "Environnements & Versions", + "Project Releases": "Versions de projet", + "Create Release": "Créer une version", + "From Git": "De Git", + "From Project": "Depuis le projet", + "No project releases found": "Aucune version de projet trouvée", + "Create a project release to get started": "Créer une version de projet pour commencer", + "Please select project": "Veuillez sélectionner un projet", + "No Changes Found": "Aucun changement trouvé", + "There are no differences to apply": "Il n'y a aucune différence à appliquer", + "Please select a project": "Veuillez sélectionner un projet", + "Search projects...": "Rechercher des projets...", + "Review Changes": "Examiner les changements", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Importé par", + "from": "de", + "No description provided": "Aucune description fournie", + "Invitation only sign up": "Inscription uniquement à l'invitation", + "Please ask your administrator to add you to the organization.": "Veuillez demander à votre administrateur de vous ajouter à l'organisation.", + "Something went wrong, please try again.": "Une erreur est survenue, veuillez réessayer.", + "Please try again.": "Veuillez réessayer.", + "Please enter a valid email address": "Veuillez entrer une adresse e-mail valide", + "The email is already added.": "Cette adresse e-mail est déjà ajoutée.", + "Add email": "Ajouter un e-mail", + "Only project admins can do this": "Seuls les administrateurs du projet peuvent le faire", + "Add Alert Email": "Ajouter un e-mail d'alerte", + "Enter the email address to receive alerts.": "Entrez l'adresse e-mail pour recevoir des alertes.", + "Add Email": "Ajouter un e-mail", + "Emails": "E-mails", + "Add email addresses to receive alerts.": "Ajoutez des adresses e-mail pour recevoir des alertes.", + "No emails added yet.": "Aucun e-mail n'a encore été ajouté.", + "Choose what you want to be notified about.": "Choisissez ce dont vous voulez être averti.", + "Project and alert permissions are required to change this setting.": "Les autorisations de projet et d'alerte sont requises pour modifier ce paramètre.", + "Every Failed Run": "Toutes les exécutions échouées", + "Get an email alert when a flow fails.": "Recevoir une alerte par courriel lorsqu'un flux échoue.", + "Get an email alert when a new issue created.": "Recevoir une alerte par courriel lorsqu'un nouveau problème a été créé.", + "Never": "Jamais", + "Turn off email notifications.": "Désactiver les notifications email.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Personnalisez l'apparence de l'application. Basculez automatiquement entre les thèmes jour et nuit.", + "Select the theme for the dashboard.": "Sélectionnez le thème du tableau de bord.", + "Light": "Clair", + "Dark": "Sombre", + "Select the language that will be used in the dashboard.": "Sélectionnez la langue qui sera utilisée dans le tableau de bord.", + "Select language": "Sélectionner une langue", + "Search language...": "Rechercher les langues...", + "No language found.": "Aucune langue trouvée.", + "Help us translate Activepieces to your language.": "Aidez-nous à traduire dans votre langue.", + "Learn more": "En savoir plus", + "Git Connection Removed": "Connexion Git supprimée", + "Your Git repository has been successfully disconnected": "Votre dépôt Git a été déconnecté avec succès", + "Enable Environments": "Activer les Environnements", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Déployer les flux entre les environnements de développement, de mise en scène et de production avec le contrôle de version et la collaboration en équipe", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Connectez-vous à Git pour activer le contrôle de version, sauvegarder vos flux et gérer plusieurs environnements. ", + "Repository URL": "URL du dépôt", + "Not connected": "Non connecté", + "Project Folder": "Dossier du projet", + "Releases Enabled": "Versions activées", + "You have successfully enabled releases": "Vous avez activé avec succès les versions", + "Enable releases to easily create and manage project releases.": "Activez les versions pour créer et gérer facilement les versions du projet.", + "The external ID is already taken.": "L'ID externe est déjà pris.", + "Manage general settings for your project.": "Gérer les paramètres généraux de votre projet.", + "Used to identify the project based on your SaaS ID": "Utilisé pour identifier le projet en fonction de votre ID SaaS", + "org-3412321": "org-3412321", + "Delete {name}": "Supprimer {name}", + "This will permanently delete this piece, all steps using it will fail.": "Cela supprimera définitivement ce widget, toutes les étapes l'utilisant échoueront.", + "Add a piece to your project that you want to use in your automations": "Ajoutez une pièce à votre projet que vous souhaitez utiliser dans vos automatisations", + "Pieces list updated": "Liste des pièces mise à jour", + "Manage Pieces": "Gérer les pièces", + "Choose which pieces you want to be available for your current project users": "Choisissez les pièces que vous voulez être disponibles pour les utilisateurs de votre projet actuel", + "Unlock Team Permissions": "Débloquer les permissions de l'équipe", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "Vous pouvez inviter des utilisateurs sur votre plateforme gratuitement dans l'édition communautaire. Pour des rôles avancés et des autorisations demander une version d'essai", + "Project Members": "Membres du projet", + "Invite your team members to collaborate.": "Invitez les membres de votre équipe à collaborer.", + "No members are added to this project.": "Aucun membre n'est ajouté à ce projet.", + "Pending Invitations": "Invitations en attente", + "No pending invitation.": "Aucune invitation en attente.", + "templateId is missing": "templateId est manquant", + "Me Only": "Moi seulement", + "Unresolved": "Non résolu", + "Resolved": "Résolu", + "Title": "Titre de la page", + "Date Created": "Date de création", + "Manage todos for your project that are created by automations": "Gérer les todos de votre projet qui sont créées par les automatisations", + "No todos found": "Aucune todo trouvée", + "You do not have any pending todos. Great job!": "Vous n'avez aucune todo en attente. Bravo !", + "Write a comment...": "Écrire un commentaire...", + "Beta": "Bêta", + "This feature is still under testing and might be changed often": "Cette fonctionnalité est toujours en cours de test et pourrait être modifiée souvent", + "Failed to copy to clipboard": "Échec de la copie dans le Presse-papiers", + "{number} items selected": "{number} éléments sélectionnés", + "Select All": "Tout sélectionner", + "No results found.": "Aucun résultat trouvé.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect doit être utilisé dans MultiSelectPro...", + "Unset": "Retirer", + "Refresh": "Rafraîchir", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} de plus", + "Removed {entityName}": "Suppression de {entityName}", + "Download File": "Télécharger le fichier", + "Copied to clipboard": "Copié dans le presse-papiers", + "File is not available after execution.": "Le fichier n'est pas disponible après l'exécution.", + "Available for Projects": "Disponible pour les projets", + "Select projects": "Sélectionner des projets", + "No items": "Aucun élément", + "Previous": "Précédent", + "to": "à", + "Last Week": "Semaine dernière", + "Last Month": "Le mois dernier", + "Last 3 Months": "Les 3 derniers mois", + "Last 6 Months": "6 derniers mois", + "Next 7 days": "7 prochains jours", + "Next 30 days": "30 prochains jours", + "Next 90 days": "Prochains 90 jours", + "Next 180 days": "180 prochains jours", + "Select Time Range": "Sélectionnez la plage de temps", + "Clear": "Nettoyer", + "Download": "Télécharger", + "Go to Dashboard": "Aller au tableau de bord", + "Select a file": "Sélectionnez un fichier", + "Press space to separate values": "Appuyez sur l'espace pour séparer les valeurs", + "AM": "Am", + "PM": "MM", + "Already have an account?": "Vous avez déjà un compte ?", + "Sign in": "Connexion", + "Don't have an account?": "Vous n'avez pas de compte ?", + "Sign up": "Inscription", + "Welcome Back!": "Heureux de vous revoir !", + "Enter your email below to sign in to your account": "Entrez votre email ci-dessous pour vous connecter à votre compte", + "Let's Get Started!": "Commençons !", + "Create your account and start flowing!": "Créez votre compte et commencez maintenant !", + "Your password was changed successfully": "Votre mot de passe a été changé avec succès", + "Your password reset request has expired, please request a new one": "La demande de réinitialisation de votre mot de passe a expiré, merci de réaliser une nouvelle demande", + "Reset Password": "Réinitialiser le mot de passe", + "Enter your new password": "Entrez votre nouveau mot de passe", + "Password is required": "Mot de passe obligatoire", + "Verification email resent, if previous one expired.": "E-mail de vérification renvoyé, si le précédent a expiré.", + "Password reset link resent, if previous one expired.": "Le lien de réinitialisation de mot de passe a été renvoyé, si le précédent a expiré.", + "We sent you a link to complete your registration to": "Nous vous avons envoyé un lien pour terminer votre inscription à", + "We sent you a link to reset your password to": "Nous vous avons envoyé un lien pour réinitialiser votre mot de passe à", + "Didn't receive an email or it expired?": "Vous n'avez pas reçu d'e-mail ou il a expiré?", + "Resend": "Renvoyer", + "Please enter your email": "Merci d'entrer votre email", + "Check Your Inbox": "Vérifier votre boite de réception", + "If the user exists we'll send you an email with a link to reset your password.": "Si l'utilisateur existe, nous vous enverrons un e-mail avec un lien pour réinitialiser votre mot de passe.", + "Send Password Reset Link": "Envoyer le lien de réinitialisation du mot de passe", + "Back to sign in": "Retour à la connexion", + "Email is invalid": "Email invalide", + "Something went wrong, please try again later": "Une erreur s'est produite, veuillez réessayer plus tard", + "Invalid email or password": "Email ou mot de passe invalide", + "User has been deactivated": "L'utilisateur a été désactivé", + "Email domain is disallowed": "Le domaine de messagerie n'est pas autorisé", + "Email authentication has been disabled": "L'authentification par e-mail a été désactivée", + "Forgot your password?": "Mot de passe oublié ?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "L'inscription est restreinte. Vous avez besoin d'une invitation pour vous inscrire. Veuillez contacter l'administrateur.", + "Email is already used": "Email déjà utilisé", + "Email authentication is disabled": "L'authentification par e-mail est désactivée", + "First name is required": "Prénom obligatoire", + "Last name is required": "Nom obligatoire", + "Email is required": "L'email est nécessaire", + "Receive updates and newsletters from activepieces": "Recevoir la newsletter d'activepieces", + "By creating an account, you agree to our": "En créant un compte, vous acceptez nos", + "terms of service": "conditions d'utilisation", + "privacy policy": "politique de confidentialité", + "Sign up With": "S'inscrire avec", + "Google": "Google", + "Sign in With": "Se connecter avec", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "L'e-mail a été vérifié. Vous allez être redirigé pour vous connecter...", + "Verifying email...": "Vérification email ...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "l'invitation a expiré, une fois que vous vous reconnecterez, vous pourrez renvoyer l'e-mail de vérification.", + "Redirecting to sign in...": "Redirection vers la connexion...", + "Password must contain at least one special character": "Le mot de passe doit contenir au moins un caractère spécial", + "Password must contain at least one lowercase letter": "Le mot de passe doit contenir au moins une minuscule", + "Password must contain at least one uppercase letter": "Le mot de passe doit contenir au moins une majuscule", + "Password must contain at least one number": "Le mot de passe doit contenir au moins un chiffre", + "8-64 Characters": "8-64 caractères", + "Special Character": "Caractère spécial", + "Lowercase": "Minuscule", + "Uppercase": "Majuscule", + "Number": "Nombre", + "Connection has been updated.": "La connexion a été mise à jour.", + "Edit Global Connection": "Modifier la connexion globale", + "Connection has been renamed.": "La connexion a été renommée.", + "New Connection Name": "Nouveau nom de connexion", + "Connection name already used": "Nom de connexion déjà utilisé", + "Please select at least one project": "Veuillez sélectionner au moins un projet", + "Run Succeeded": "Exécution réussie", + "Run Failed": "Échec de l'exécution", + "Flow Run is paused": "Le process est interrompu", + "Run Failed due to quota exceeded": "Échec de l'exécution en raison du quota dépassé", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "L'exécution a échoué en raison du dépassement de la limite de mémoire {memoryLimit} Mo", + "Run exceeded {timeout} seconds, try to optimize your steps.": "L'exécution dépasse {timeout} secondes, essayez d'optimiser vos étapes.", + "Run failed for an unknown reason, contact support.": "L'exécution a échoué pour une raison inconnue, contactez l'assistance.", + "Unknown": "Inconnu", + "Exit Run": "Quitter l'exécution", + "Select shown": "Sélectionner affiché", + "Select all": "Tout sélectionner", + "Start Time": "Heure de début", + "Runs replayed successfully": "Exécution rejouée avec succès", + "Retry": "Réessayer", + "all except": "Tous sauf", + "all": "Tous", + "Only failed runs can be retried from failed step": "Seuls les exécutions échouées peuvent être ré-essayées à partir de l'étape échouée", + "No flow runs found": "Aucun flux d'exécutions trouvé", + "Come back later when your automations start running": "Revenez plus tard lorsque vos automatisations commenceront à fonctionner", + "Step running": "Étape en cours d'exécution", + "Step paused": "Étape en pause", + "Step Stopped": "Étape arrêtée", + "Step Succeeded": "Étape réussie", + "Step Failed": "Étape échouée", + "Please publish flow first": "Veuillez d'abord publier le flux", + "Flow is on": "Flux activé", + "Flow is off": "Flux désactivé", + "Permission Needed": "Permission requise", + "Draft Version": "Version du brouillon", + "Published Version": "Version publiée", + "Locked Version": "Version Verrouillée", + "flowsImported": "{flowsCount, plural, =0 {Aucun flux importé} =1 {Flux importé avec succès} other {Flux importés avec succès}}", + "Template file is invalid": "Le fichier de modèle n'est pas valide", + "No valid templates found. The following files failed to import: ": "Aucun modèle valide trouvé. Les fichiers suivants n'ont pas pu être importés : ", + "Please select a file first": "Veuillez d'abord sélectionner un fichier", + "Unsupported file type": "Type de fichier non pris en charge", + "Import Flow": "Importer Flux", + "Warning": "Avertissement", + "Importing a flow will overwrite your current one.": "Importer un flux écrasera votre flux actuel.", + "Select a folder": "Sélectionnez un dossier", + "Folders": "Dossiers", + "Please select a folder": "Sélectionnez un dossier", + "Moved flows successfully": "Flux déplacés avec succès", + "Move Selected Flows": "Déplacer les flux sélectionnés", + "Select Folder": "Sélectionner le dossier", + "No Folders": "Aucun dossier", + "Flow has been renamed.": "Le flux a été renommé.", + "New Flow Name": "Nouveau nom de flux", + "Use Template": "Utiliser modèle", + "Browse Templates": "Explorer modèle", + "Search templates": "Recherche modèle", + "No templates found, try adjusting your search": "Aucun modèle trouvé, essayez d'ajuster votre recherche", + "Read more about this template in": "Générez ou mettez à jour un lien de modèle pour le flux actuel afin de le partager facilement avec d'autres.", + "this blog!": "Ce blog !", + "Share Template": "Partager modèle", + "Generate or update a template link for the current flow to easily share it with others.": "Générez ou mettez à jour un lien de modèle pour le flux actuel afin de le partager facilement avec d'autres.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "Le modèle ne contiendra aucune information d'identification dans les champs de connexion, préservant ainsi la sécurité des informations sensibles.", + "A short description of the template": "Courte description du modèle", + "Flow Is In Use": "Flux en cours d'utilisation", + "Flow is being used by another user, please try again later.": "Flow est utilisé par un autre utilisateur, veuillez réessayer plus tard.", + "Flow has been published.": "Le process a été publié.", + "Flows have been exported.": "Les flux ont été exportés.", + "Run": "Exécuter", + "Real time flow": "Flux en temps réel", + "Flow can't be published with empty trigger {name}": "Le flux ne peut pas être publié avec le déclencheur vide {name}", + "Please contact support as your published flow has a problem": "Veuillez contacter le support car votre flux publié présente un problème", + "Actions": "Actions", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Êtes-vous sûr de vouloir supprimer ces flux ? Cela supprimera définitivement les flux, toutes leurs données et toutes les exécutions en arrière-plan.", + "You are on a development branch, this will not delete the flows from the remote repository.": "Vous êtes sur une branche de développement, cela ne supprimera pas les flux du référentiel distant.", + "Please enter folder name": "Merci d'entrer le nom du dossier", + "Added folder successfully": "Dossier ajouté avec succès", + "The folder name already exists.": "Le nom du dossier existe déjà.", + "New Folder": "Nouveau dossier", + "Folder Name": "Nom du dossier", + "Loading...": "Chargement ...", + "Delete {folderName}": "Supprimer {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "Si vous supprimez ce dossier, nous conserverons ses flux et les déplacerons vers \"Non catégorisé\".", + "All flows": "Tous les flux", + "Please enter a folder name": "Merci d'entrer un nom de dossier", + "Renamed flow successfully": "Flux renommé avec succès", + "Folder name already used": "Nom de dossier déjà utilisé", + "New Folder Name": "Nouveau nom de dossier", + "Connected successfully": "Connecté avec succès", + "Connect Git": "Connecté à Git", + "Remote URL": "URL distante", + "Folder name is the name of the folder where the project will be stored or fetched.": "Le nom du dossier est le nom du dossier dans lequel le projet sera stocké ou récupéré.", + "SSH Private Key": "Clef privée SSH", + "The SSH private key to use for authentication.": "La clé privée SSH à utiliser pour l'authentification.", + "Only published flows can be pushed to Git": "Seuls les flux publiés peuvent être poussés vers Git", + "Pushed successfully": "Poussé avec succès", + "Invalid Operation": "Opération non valide", + "Commit Message": "Message de commit", + "Enter a commit message to describe the changes you want to push.": "Saisissez un message de validation pour décrire les modifications que vous souhaitez appliquer.", + "Push": "Pousser", + "This field is required": "Ce champ est obligatoire", + "Your submission was successfully received.": "Votre demande a été reçue avec succès.", + "Flow not found": "Flux non trouvé", + "The flow you are trying to submit to does not exist.": "Le flux auquel vous essayez de vous soumettre n'existe pas.", + "The flow failed to execute.": "Le flux n'a pas pu s'exécuter", + "Submit": "Soumettre", + "issues-notification": "notification de problèmes", + "Please select a package type": "Merci de sélectionner le type de paquet", + "package.json not found in archive": "package.json introuvable dans l'archive", + "Error processing archive file": "Erreur de traitement du fichier d'archive", + "Please upload a .tgz file": "Veuillez télécharger un fichier .tgz", + "Piece name is required for NPM Registry": "Le nom de la pièce est requis pour le Registre du NPM", + "Piece version is required for NPM Registry": "La version de la pièce est requise pour le Registre du NPM", + "Piece installed": "Widget installé", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "Une pièce portant ce nom et version est déjà installée. Veuillez mettre à jour le numéro de version dans package.json et réessayer.", + "Install Piece": "Installer widget", + "Install a piece": "Installer un widget", + "Package Type": "Type de paquet", + "NPM Registry": "Registre NPM", + "Packed Archive (.tgz)": "Archive compressée (.tgz)", + "Piece Version": "Version du widget", + "Package Archive": "Archive compressée", + "Package archive": "Archive compressée", + "Powerful Node.js & TypeScript code with npm": "Puissant code Node.js & TypeScript avec npm", + "Loop on Items": "Boucler sur les objets", + "Split your flow into branches depending on condition(s)": "Diviser votre flux en branches selon les condition(s)", + "Empty Trigger": "Déclencheur vide", + "An internal error occurred while fetching data, please contact support": "Une erreur interne s'est produite lors de la récupération des données, veuillez contacter le support", + "An internal error occurred, please contact support": "Une erreur interne est survenue, veuillez contacter le support", + "Custom Javascript Code": "Code Javascript personnalisé", + "Router": "Routeur", + "recordsCount": "nombre d'enregistrements", + "selected": "slectionns", + "All records selected": "Tous les enregistrements sélectionnés", + "fieldsCount": "Nombre de champs", + "Saving...": "Sauvegarde en cours...", + "Loading more...": "Chargement plus...", + "Export Table": "Exporter la table", + "Delete Records": "Supprimer les enregistrements", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Êtes-vous sûr de vouloir supprimer les enregistrements sélectionnés ? Cette action ne peut pas être annulée.", + "record": "enregistrement", + "records": "enregistrements", + "mm/dd/yyy": "jj/mm/aaaa", + "Delete Field": "Supprimer le champ", + "Are you sure you want to delete this field? This action cannot be undone.": "Êtes-vous sûr de vouloir supprimer ce champ ? Cette action ne peut pas être annulée.", + "field": "champ", + "Ignored": "Ignoré", + "Table": "Tableau", + "CSV": "CSV", + "Field": "Champ", + "Please select a csv file": "Veuillez sélectionner un fichier csv", + "Max file size is {maxFileSize}MB": "La taille maximale du fichier est {maxFileSize}Mo", + "Import CSV": "Importer CSV", + "Imported records will be added to the bottom of the table": "Les enregistrements importés seront ajoutés en bas de la table", + "Any records after the limit ({maxRecords} records) will be ignored": "Tous les enregistrements après la limite (enregistrements{maxRecords} ) seront ignorés", + "CSV File": "Fichier CSV", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "Une erreur inattendue s'est produite lors de l'importation du fichier csv, veuillez appuyer sur l'erreur de copie et l'envoyer au support", + "Name must be unique": "Le nom doit être unique", + "Type is required": "Le type est requis", + "Please add at least one option": "Veuillez ajouter au moins une option", + "New Field": "Nouveau champ", + "Options": "Options", + "Name is already taken": "Le nom est déjà pris", + "Table renamed": "Table renommée", + "Table name": "Nom du tableau", + "Team Invitation Accepted": "Invitation d'équipe acceptée", + "Thank you for accepting the invitation. We are redirecting you right now...": "Merci d'avoir accepté l'invitation. Nous vous redirigeons immédiatement…", + "Invalid invitation token. Please try again.": "Token d'invitation non valide. Veuillez réessayer.", + "Role updated successfully": "Rôle mis à jour avec succès", + "Error updating role": "Erreur lors de la mise à jour du rôle", + "Please try again later": "Veuillez réessayer plus tard", + "Edit Role for": "Modifier le rôle pour", + "Select Role": "Sélectionner un rôle", + "Avatar": "Avatars", + "Remove {email}": "Supprimer {email}", + "Are you sure you want to remove this invitation?": "Etes-vous sûr de vouloir supprimer cette invitation ?", + "Please select invitation type": "Merci de sélectionner un type d'invitation", + "Please select platform role": "Merci de sélectionner un rôle de plateforme", + "Invitation sent successfully": "Invitation envoyée avec succès", + "Please select a project role": "Veuillez sélectionner un rôle de projet", + "Invitation link copied successfully": "Lien d'invitation copié avec succès", + "Invite User": "Inviter utilisateur", + "Invitation Link": "Lien d'invitation", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Merci de copier le lien ci-dessous et le partager avec l'utilisateur que vous souhaitez inviter, l'invitation expire dans 24 heures.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Taper l'email de l'utilisateur que vous souhaitez inviter, l'invitation expire dans 24 heures.", + "Invite To": "Inviter à", + "Entire Platform": "Plateforme entière", + "Select Project Role": "Sélectionner un rôle de projet", + "Invite": "Inviter", + "Platform Role": "Rôle de la plateforme", + "Select a platform role": "Sélectionner un rôle de plateforme", + "Are you sure you want to remove this member?": "Êtes-vous sûr de vouloir supprimer ce membre ?", + "Editor": "Editeur", + "Operator": "Opérateur", + "Viewer": "Lecteur", + "Select a project role": "Sélectionner un rôle de projet", + "Steps in this flow": "Étapes dans ce flux", + "Invalid Access": "Accès non valide", + "Either the project does not exist or you do not have access to it.": "Soit le projet n'existe pas, soit vous n'y avez pas accès." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/hi/translation.json b/packages/react-ui/public/locales/hi/translation.json new file mode 100644 index 0000000..b364322 --- /dev/null +++ b/packages/react-ui/public/locales/hi/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "प्रकाशित करें", + "Latest version is published": "नवीनतम संस्करण प्रकाशित हो चुका है", + "Your flow has incomplete steps": "आपके फ़्लो में कुछ चरण अधूरे हैं", + "Edit Flow": "Edit Flow", + "View Draft": "View Draft", + "Uncategorized": "Uncategorized", + "Go to folder": "Go to folder", + "Support": "Support", + "Runs": "Runs", + "Run Logs": "Run Logs", + "Versions": "Versions", + "Versions History": "Versions History", + "Error generating code": "Error generating code", + "AI Copilot": "AI Copilot", + "i.e Calculate the sum of a list...": "i.e Calculate the sum of a list...", + "Send": "Send", + "Generating Code": "Generating Code", + "Hello there! I am here to generate code that helps with your flow": "Hello there! I am here to generate code that helps with your flow", + "Here are examples of what I am best used for: ": "Here are examples of what I am best used for: ", + "Text Processing": "Text Processing", + "Process strings, dates and data": "Process strings, dates and data", + "Data Operations": "Data Operations", + "Change data from one format to another": "Change data from one format to another", + "Calculations": "Calculations", + "Handle math and statistics": "Handle math and statistics", + "API Integration": "API Integration", + "Connect with external services. Best for simple integrations currently.": "Connect with external services. Best for simple integrations currently.", + "What would you like me to help you with?": "What would you like me to help you with?", + "Insert": "Insert", + "Data Selector": "Data Selector", + "Search": "Search", + "No matching data": "No matching data", + "Try adjusting your search": "Try adjusting your search", + "This trigger needs to have data loaded from your account, to use as sample data.": "This trigger needs to have data loaded from your account, to use as sample data.", + "This step needs to be tested in order to view its data.": "This step needs to be tested in order to view its data.", + "Go to Trigger": "Go to Trigger", + "Go to Step": "Go to Step", + "Select Mode": "Select Mode", + "Move Mode": "Move Mode", + "Reset Zoom": "Reset Zoom", + "Zoom In": "Zoom In", + "Zoom Out": "Zoom Out", + "Fit to View": "Fit to View", + "Replace": "Replace", + "Copy": "Copy", + "Duplicate": "Duplicate", + "Paste After Last Step": "Paste After Last Step", + "Paste Inside Loop": "Paste Inside Loop", + "Paste After": "Paste After", + "Paste Inside...": "Paste Inside...", + "New Branch": "New Branch", + "Paste Inside Branch": "Paste Inside Branch", + "Delete": "Delete", + "Duplicate Branch": "Duplicate Branch", + "Delete Branch": "Delete Branch", + "Invalid Move": "Invalid Move", + "The destination location is a child of the dragged step": "The destination location is a child of the dragged step", + "End": "End", + "Skipped": "Skipped", + "Incomplete settings": "Incomplete settings", + "logo": "logo", + "Step Icon": "Step Icon", + "Branch": "Branch", + "incompleteSteps": "{invalidSteps, plural, =0 {no incomplete steps} =1 {Complete 1 step} other {Complete # steps}}", + "Test Flow": "Test Flow", + "Please test the trigger first": "Please test the trigger first", + "View Only": "View Only", + "Use as Draft": "Use as Draft", + "Are you sure?": "Are you sure?", + "Your current draft version will be overwritten with": "Your current draft version will be overwritten with", + "version #": "version #", + "Cancel": "Cancel", + "Confirm": "Confirm", + "Version": "Version", + "Viewing": "Viewing", + "View": "View", + "Version History": "Version History", + "Error, please try again.": "Error, please try again.", + "Continue on Failure": "Continue on Failure", + "Enable this option to skip this step and continue the flow normally if it fails.": "Enable this option to skip this step and continue the flow normally if it fails.", + "Retry on Failure": "Retry on Failure", + "Automatically retry up to four attempts when failed.": "Automatically retry up to four attempts when failed.", + "Remove": "Remove", + "Add Item": "Add Item", + "File Input": "File Input", + "Date Input": "Date Input", + "Dynamic value": "Dynamic value", + "Select an option": "Select an option", + "Unexpected error, please retry": "Unexpected error, please retry", + "Unexpected error, please refresh the page or contact support": "Unexpected error, please refresh the page or contact support", + "Name can only contain letters, numbers and underscores": "Name can only contain letters, numbers and underscores", + "Ask AI": "Ask AI", + "Create Todo Guide": "Create Todo Guide", + "Where would you like the todo to be reviewed?": "Where would you like the todo to be reviewed?", + "Activepieces Todos": "Activepieces Todos", + "Users will manage tasks directly in Activepieces": "Users will manage tasks directly in Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.", + "External Channel (Slack, Teams, Email, ...)": "External Channel (Slack, Teams, Email, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.", + "Preview (Activepieces Todos)": "Preview (Activepieces Todos)", + "Preview (External channel)": "Preview (External channel)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "You can add the channel before the Wait Step, and configure the logic in the Router step", + "Add Steps": "Add Steps", + "All": "All", + "AI": "AI", + "Core": "Core", + "Apps": "Apps", + "Not available as trigger": "Not available as trigger", + "Not available as action": "Not available as action", + "Let our AI assistant help you out": "Let our AI assistant help you out", + "Or": "Or", + "Request Piece": "Request Piece", + "No pieces found": "No pieces found", + "Please select a piece first": "Please select a piece first", + "All Iterations": "All Iterations", + "Duration": "Duration", + "Input": "Input", + "Output": "Output", + "There are no logs captured for this run.": "There are no logs captured for this run.", + "Logs are kept for {days} days after execution and then deleted.": "Logs are kept for {days} days after execution and then deleted.", + "Run Details": "Run Details", + "Iteration": "Iteration", + "Done": "Done", + "Took": "Took", + "Running": "Running", + "on latest version": "on latest version", + "from failed step": "from failed step", + "Recent Runs": "Recent Runs", + "No runs found": "No runs found", + "Close": "Close", + "OR": "OR", + "And If": "And If", + "+ And": "+ And", + "+ Or": "+ Or", + "(Text) Contains": "(Text) Contains", + "(Text) Does not contain": "(Text) Does not contain", + "(Text) Exactly matches": "(Text) Exactly matches", + "(Text) Does not exactly match": "(Text) Does not exactly match", + "(Text) Starts with": "(Text) Starts with", + "(Text) Does not start with": "(Text) Does not start with", + "(Text) Ends with": "(Text) Ends with", + "(Text) Does not end with": "(Text) Does not end with", + "(List) Contains": "(List) Contains", + "(List) Does not contain": "(List) Does not contain", + "(Number) Is greater than": "(Number) Is greater than", + "(Number) Is less than": "(Number) Is less than", + "(Number) Is equal to": "(Number) Is equal to", + "(Date/time) After": "(Date/time) After", + "(Date/time) Before": "(Date/time) Before", + "(Date/time) Equals": "(Date/time) Equals", + "(Boolean) Is true": "(Boolean) Is true", + "(Boolean) Is false": "(Boolean) Is false", + "(List) Is empty": "(List) Is empty", + "(List) Is not empty": "(List) Is not empty", + "Exists": "Exists", + "Does not exist": "Does not exist", + "Incomplete condition": "Incomplete condition", + "First value": "First value", + "Second value": "Second value", + "Case sensitive": "Case sensitive", + "Execute If": "Execute If", + "The package name is required": "The package name is required", + "Success": "Success", + "Package added successfully": "Package added successfully", + "Could not fetch package version": "Could not fetch package version", + "Add NPM Package": "Add NPM Package", + "Type the name of the npm package you want to add.": "Type the name of the npm package you want to add.", + "Package Name": "Package Name", + "The latest version will be fetched and added": "The latest version will be fetched and added", + "Add": "Add", + "Code": "Code", + "Dependencies": "Dependencies", + "Use code": "Use code", + "Add package": "Add package", + "Inputs": "Inputs", + "Edit Step Name": "Edit Step Name", + "Edit Branch Name": "Edit Branch Name", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.", + "Items": "Items", + "Select an array of items": "Select an array of items", + "Reconnect": "Reconnect", + "Select a connection": "Select a connection", + "Create Connection": "Create Connection", + "Rename": "Rename", + "Move": "Move", + "Add Branch": "Add Branch", + "Execute": "Execute", + "Only the first (left) matching branch": "Only the first (left) matching branch", + "All matching paths from left to right": "All matching paths from left to right", + "Branches": "Branches", + "{field} is required": "{field} is required", + "Tool Sample Data": "Tool Sample Data", + "Fill in the following fields to use them as sample data for the trigger.": "Fill in the following fields to use them as sample data for the trigger.", + "No input fields defined in the schema": "No input fields defined in the schema", + "Save": "Save", + "Test Environment": "Test Environment", + "Assigned to": "Assigned to", + "(Me)": "(Me)", + "Please select status to resolve the todo": "Please select status to resolve the todo", + "Resolve": "Resolve", + "Change status to resolved": "Change status to resolved", + "Send Sample Data to Webhook": "Send Sample Data to Webhook", + "Method": "Method", + "Query Params": "Query Params", + "Headers": "Headers", + "Body": "Body", + "Type": "Type", + "JSON": "JSON", + "Text": "Text", + "Form Data": "Form Data", + "Generate Sample Data": "Generate Sample Data", + "Test Step": "Test Step", + "Retest": "Retest", + "Testing Failed": "Testing Failed", + "Tested Successfully": "Tested Successfully", + "Logs": "Logs", + "There is no sample data available found for this trigger.": "There is no sample data available found for this trigger.", + "Internal error, please try again later.": "Internal error, please try again later.", + "Failed to run test step and no error message was returned": "Failed to run test step and no error message was returned", + "Please fix inputs first": "Please fix inputs first", + "No sample data available": "No sample data available", + "Old results were removed, retest for new sample data": "Old results were removed, retest for new sample data", + "Result #": "Result #", + "The sample data can be used in the next steps.": "The sample data can be used in the next steps.", + "Testing Trigger": "Testing Trigger", + "Action Required": "Action Required", + "testPieceWebhookTriggerNote": "Please go to {pieceName} and trigger {triggerName}.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Send Data to the webhook URL to generate sample data to use in the next steps", + "Test Trigger": "Test Trigger", + "Use Mock Data": "Use Mock Data", + "Load Sample Data": "Load Sample Data", + "Test Tool": "Test Tool", + "home": "home", + "Home": "Home", + "Alerts": "Alerts", + "Releases": "Releases", + "Flows": "Flows", + "Products": "Products", + "MCP": "MCP", + "Tables": "Tables", + "Todos": "Todos", + "Push to Git": "Push to Git", + "Move To": "Move To", + "Duplicating": "Duplicating", + "Import": "Import", + "Exporting": "Exporting", + "Export": "Export", + "Share": "Share", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.", + "You are on a development branch, this will also delete the flow from the remote repository.": "You are on a development branch, this will also delete the flow from the remote repository.", + "flow": "flow", + "Community Support": "Community Support", + "Overview": "Overview", + "Projects": "Projects", + "Users": "Users", + "Setup": "Setup", + "Branding": "Branding", + "Global Connections": "Global Connections", + "Pieces": "Pieces", + "Templates": "Templates", + "License Key": "License Key", + "Security": "Security", + "Audit Logs": "Audit Logs", + "Single Sign On": "Single Sign On", + "Signing Keys": "Signing Keys", + "Project Roles": "Project Roles", + "API Keys": "API Keys", + "Infrastructure": "Infrastructure", + "Workers": "Workers", + "Health": "Health", + "Billing": "Billing", + "Settings": "Settings", + "Contact Sales": "Contact Sales", + "General": "General", + "Appearance": "Appearance", + "Team": "Team", + "Environments": "Environments", + "Project Settings": "Project Settings", + "Exit Platform Admin": "Exit Platform Admin", + "Enter Platform Admin": "Enter Platform Admin", + "Logout": "Logout", + "Misc": "Misc", + "Connections": "Connections", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "Today": "Today", + "Tasks": "Tasks", + "AI Credits": "AI Credits", + "Usage resets in": "Usage resets in", + "Manage": "Manage", + "Unlimited": "Unlimited", + "Enabled": "Enabled", + "Disabled": "Disabled", + "Could not claim the authorization code, make sure you have correct settings and try again.": "Could not claim the authorization code, make sure you have correct settings and try again.", + "Connection failed with error {msg}": "Connection failed with error {msg}", + "You don't have the permission to create a connection.": "You don't have the permission to create a connection.", + "Reconnect {displayName} Connection": "Reconnect {displayName}", + "Connect to {displayName}": "Connect to {displayName}", + "Connection Name": "Connection Name", + "Connection name": "Connection name", + "New Connection": "New Connection", + "Redirect URL": "Redirect URL", + "Client ID": "Client ID", + "Client Secret": "Client Secret", + "Connect": "Connect", + "Disconnect": "Disconnect", + "I would like to use my own App Credentials": "I would like to use my own App Credentials", + "I would like to use predefined App Credentials": "I would like to use predefined App Credentials", + "Permission needed": "Permission needed", + "Connections replaced successfully": "Connections replaced successfully", + "Error": "Error", + "Failed to replace connections": "Failed to replace connections", + "Failed to get affected flows": "Failed to get affected flows", + "Please select a piece": "Please select a piece", + "Please select a connection to replace": "Please select a connection to replace", + "Please select a connection to replace with": "Please select a connection to replace with", + "Replace Connections": "Replace Connections", + "Confirm Replacement": "Confirm Replacement", + "Replace one connection with another.": "Replace one connection with another.", + "This action requires ": "This action requires ", + "reconnecting": "reconnecting", + " any associated MCP pieces.": " any associated MCP pieces.", + "Piece": "Piece", + "Select a piece": "Select a piece", + "Connection to Replace": "Connection to Replace", + "Choose connection to replace": "Choose connection to replace", + "Replaced With": "Replaced With", + "Choose connection to replace with": "Choose connection to replace with", + "All flows will be changed to use the replaced with connection": "All flows will be changed to use the replaced with connection", + "Next": "Next", + "No flows will be affected by this change": "No flows will be affected by this change", + "Back": "Back", + "Unnamed tool": "Unnamed tool", + "This flow is enabled": "This flow is enabled", + "Enable this flow to make it available": "Enable this flow to make it available", + "Piece is updated successfully": "Piece is updated successfully", + "Piece is added successfully": "Piece is added successfully", + "Failed to update piece": "Failed to update piece", + "Failed to add piece": "Failed to add piece", + "Please select a connection": "Please select a connection", + "Your MCP server already has this piece": "Your MCP server already has this piece", + "+ New Connection": "+ New Connection", + "Edit Piece": "Edit Piece", + "Add Piece": "Add Piece", + "Connection": "Connection", + "MCP piece": "MCP piece", + "Failed to update piece status": "Failed to update piece status", + "Connection required": "Connection required", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.", + "piece": "piece", + "Connect your AI assistant to external services": "Connect your AI assistant to external services", + "Collapse": "Collapse", + "Show All": "Show All", + "Server URL": "Server URL", + "Hide the token for security": "Hide the token for security", + "Show the token": "Show the token", + "Generate a new token for security. This will invalidate the current URL.": "Generate a new token for security. This will invalidate the current URL.", + "URL copied to clipboard": "URL copied to clipboard", + "Copy URL to clipboard": "Copy URL to clipboard", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.", + "Max tables reached": "Max tables reached", + "You can't create more than {maxTables} tables": "You can't create more than {maxTables} tables", + "Name": "Name", + "Created": "Created", + "Delete Tables": "Delete Tables", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Are you sure you want to delete the selected tables? This action cannot be undone.", + "table": "table", + "Create and manage your tables to store your automation data": "Create and manage your tables to store your automation data", + "New Table": "New Table", + "No tables have been created yet": "No tables have been created yet", + "Create a table to get started and start managing your automation data": "Create a table to get started and start managing your automation data", + "Error deleting connections": "Error deleting connections", + "Status": "Status", + "Display Name": "Display Name", + "Owner": "Owner", + "App": "App", + "This connection is global and can be managed in the platform admin": "This connection is global and can be managed in the platform admin", + "External ID": "External ID", + "Connected At": "Connected At", + "Confirm Deletion": "Confirm Deletion", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Are you sure you want to delete the selected connections? This action cannot be undone.", + "Deleting connections may cause your Flows or MCP tools to break.": "Deleting connections may cause your Flows or MCP tools to break.", + "Manage project connections to external systems.": "Manage project connections to external systems.", + "No connections found": "No connections found", + "Come back later when you create a automation to manage your connections": "Come back later when you create a automation to manage your connections", + "Steps": "Steps", + "Folder": "Folder", + "Flow name": "Flow name", + "No flows found": "No flows found", + "Create a workflow to start automating": "Create a workflow to start automating", + "Create and manage your flows, run history and run issues": "Create and manage your flows, run history and run issues", + "Issues": "Issues", + "Untitled": "Untitled", + "Create flow": "Create flow", + "From scratch": "From scratch", + "Use a template": "Use a template", + "From local file": "From local file", + "Flow Name": "Flow Name", + "Count": "Count", + "First Seen": "First Seen", + "Last Seen": "Last Seen", + "Issues in {flowDisplayName} is marked as resolved.": "Issues in {flowDisplayName} is marked as resolved.", + "Unlock Issues": "Unlock Issues", + "Track issues in your workflows and troubleshoot them.": "Track issues in your workflows and troubleshoot them.", + "No issues found": "No issues found", + "All your workflows are running smoothly.": "All your workflows are running smoothly.", + "Mark as Resolved": "Mark as Resolved", + "All Flows Are Turned Off": "All Flows Are Turned Off", + "Task Usage Exceeded": "Task Usage Exceeded", + "of the Allowed Limit.": "of the Allowed Limit.", + "When a project tasks limit is reached,": "When a project tasks limit is reached,", + "all flows will be turned off and you will not be able to run any flows.": "all flows will be turned off and you will not be able to run any flows.", + "Please visit": "Please visit", + "Your Plan": "Your Plan", + "and increase your task limit, which requires your payment details.": "and increase your task limit, which requires your payment details.", + "Please contact your admin to increase the project task limit.": "Please contact your admin to increase the project task limit.", + "and increase the project task limit.": "and increase the project task limit.", + "Dismiss": "Dismiss", + "Token rotated successfully": "Token rotated successfully", + "Failed to rotate token": "Failed to rotate token", + "Piece removed successfully": "Piece removed successfully", + "Failed to remove piece": "Failed to remove piece", + "Flow created successfully": "Flow created successfully", + "Failed to create flow": "Failed to create flow", + "Add Flow": "Add Flow", + "Let your AI assistant trigger automations": "Let your AI assistant trigger automations", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Connect to your hosted MCP Server using any MCP client to communicate with tools", + "MCP Server": "MCP Server", + "My Tools": "My Tools", + "Create Flow": "Create Flow", + "Note": "Note", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.", + "This URL grants access to your tools and data. Only share with trusted applications.": "This URL grants access to your tools and data. Only share with trusted applications.", + "Server Configuration": "Server Configuration", + "Hide sensitive data": "Hide sensitive data", + "Show sensitive data": "Show sensitive data", + "Create a new URL. The current one will stop working.": "Create a new URL. The current one will stop working.", + "Copy configuration": "Copy configuration", + "Configuration copied to clipboard": "Configuration copied to clipboard", + "Claude": "Claude", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "Server/Other", + "Note: MCPs only work with": "Note: MCPs only work with", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", not the web version.", + "Prerequisites:": "Prerequisites:", + "Install": "Install", + "Node.js": "Node.js", + "and": "and", + "Open Settings:": "Open Settings:", + "Click the menu and select": "Click the menu and select", + "Developer": "Developer", + "Configure MCP:": "Configure MCP:", + "Click": "Click", + "Edit Config": "Edit Config", + "and paste the configuration below": "and paste the configuration below", + "Save and Restart:": "Save and Restart:", + "Save the config and restart Claude Desktop": "Save the config and restart Claude Desktop", + "Navigate to": "Navigate to", + "Cursor Settings": "Cursor Settings", + "Add Server:": "Add Server:", + "Add new global MCP server": "Add new global MCP server", + "Configure:": "Configure:", + "Paste the configuration below and save": "Paste the configuration below and save", + "Use either method:": "Use either method:", + "Go to": "Go to", + "Advanced Settings": "Advanced Settings", + "Open Command Palette and select": "Open Command Palette and select", + "Windsurf Settings Page": "Windsurf Settings Page", + "Navigate to Cascade:": "Navigate to Cascade:", + "Select": "Select", + "Cascade": "Cascade", + "in the sidebar": "in the sidebar", + "Add Server": "Add Server", + "Add custom server +": "Add custom server +", + "Copy URL": "Copy URL", + "Client Setup Instructions": "Client Setup Instructions", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "After changing connections or flows, reconnect your MCP server for changes to take effect.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.", + "icon": "icon", + "Unlock Analytics": "Unlock Analytics", + "Get insights into your platform usage and performance with our analytics dashboard": "Get insights into your platform usage and performance with our analytics dashboard", + "Active Flows": "Active Flows", + "The number of enabled flows in the platform": "The number of enabled flows in the platform", + "Active Projects": "Active Projects", + "The number of projects with at least one enabled flow": "The number of projects with at least one enabled flow", + "Active Users": "Active Users", + "The number of users logged in the last 30 days": "The number of users logged in the last 30 days", + "Out of {totalusers} total users": "Out of {totalusers} total users", + "Pieces Used": "Pieces Used", + "The number of unique pieces used across all flows": "The number of unique pieces used across all flows", + "Flows with AI": "Flows with AI", + "The number of enabled flows that use AI pieces": "The number of enabled flows that use AI pieces", + "Metrics": "Metrics", + "Executed Tasks": "Executed Tasks", + "Showing total executed tasks for specified time range": "Showing total executed tasks for specified time range", + "Tasks Usage Limit": "Tasks Usage Limit", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.", + "Number of monthly tasks": "Number of monthly tasks", + "Save changes": "Save changes", + "Limits updated successfully": "Limits updated successfully", + "Failed to update limits": "Failed to update limits", + "Failed to load billing information": "Failed to load billing information", + "Billing Amount": "Billing Amount", + "Manage Payment Details": "Manage Payment Details", + "Add Payment Details": "Add Payment Details", + "Current Task Usage": "Current Task Usage", + "Count of executed steps": "Count of executed steps", + "Billing Limit": "Billing Limit", + "Edit": "Edit", + "Add Limit": "Add Limit", + "Current Credit Usage": "Current Credit Usage", + "WebSocket Connection": "WebSocket Connection", + "Connected": "Connected", + "Disconnected": "Disconnected", + "No issues detected": "No issues detected", + "Check the status of your platform and its components": "Check the status of your platform and its components", + "System Health Status": "System Health Status", + "All systems are running smoothly": "All systems are running smoothly", + "Check the health of your worker machines": "Check the health of your worker machines", + "Workers Machine": "Workers Machine", + "This is demo data. In a real environment, this would show your actual worker machines.": "This is demo data. In a real environment, this would show your actual worker machines.", + "No workers found": "No workers found", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "You don't have any worker machines yet. Spin up new machines to execute your automations", + "IP Address": "IP Address", + "CPU Usage": "CPU Usage", + "Disk Usage": "Disk Usage", + "RAM Usage": "RAM Usage", + "Last Contact": "Last Contact", + "Configs": "Configs", + "Environment Variables": "Environment Variables", + "Websocket Connection Error": "Websocket Connection Error", + "Retry Connection": "Retry Connection", + "Update Available": "Update Available", + "Update Now": "Update Now", + "Your Universal AI needs a quick setup": "Your Universal AI needs a quick setup", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.", + "Configure": "Configure", + "Platform Alerts": "Platform Alerts", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.", + "View Alerts": "View Alerts", + "Used Tasks": "Used Tasks", + "Used AI Credits": "Used AI Credits", + "Cannot delete active project, switch to another project first": "Cannot delete active project, switch to another project first", + "Delete Projects": "Delete Projects", + "Are you sure you want to delete the selected projects?": "Are you sure you want to delete the selected projects?", + "New Project": "New Project", + "Validation error": "Validation error", + "Project has enabled flows. Please disable them first.": "Project has enabled flows. Please disable them first.", + "This project is active. Please switch to another project first.": "This project is active. Please switch to another project first.", + "Unlock Projects": "Unlock Projects", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Orchestrate your automation teams across projects with their own flows, connections and usage quotas", + "Manage your automation projects": "Manage your automation projects", + "No projects found": "No projects found", + "Start by creating projects to manage your automation teams": "Start by creating projects to manage your automation teams", + "Edit project": "Edit project", + "Name is required": "Name is required", + "Create New Project": "Create New Project", + "Project Name": "Project Name", + "Id": "Id", + "Enable API Keys": "Enable API Keys", + "Create and manage API keys to access Activepieces APIs.": "Create and manage API keys to access Activepieces APIs.", + "New Api Key": "New Api Key", + "No API keys found": "No API keys found", + "Start by creating an API key to communicate with Activepieces APIs": "Start by creating an API key to communicate with Activepieces APIs", + "Delete API Key": "Delete API Key", + "Are you sure you want to delete this API key?": "Are you sure you want to delete this API key?", + "API Key": "API Key", + "API Key Created": "API Key Created", + "Create New API Key": "Create New API Key", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Please save this secret key somewhere safe and accessible. For security reasons,", + "you won't be able to view it again after closing this dialog.": "you won't be able to view it again after closing this dialog.", + "API Key Name": "API Key Name", + "Action": "Action", + "Performed By": "Performed By", + "Project": "Project", + "Unlock Audit Logs": "Unlock Audit Logs", + "Comply with internal and external security policies by tracking activities done within your account": "Comply with internal and external security policies by tracking activities done within your account", + "Track activities done within your platform": "Track activities done within your platform", + "No audit logs found": "No audit logs found", + "Come back later when you have some activity to audit": "Come back later when you have some activity to audit", + "Resource": "Resource", + "Details": "Details", + "N/A": "N/A", + "Flow Run": "Flow Run", + "Flow": "Flow", + "User": "User", + "Signing Key": "Signing Key", + "Project Role Management": "Project Role Management", + "Define custom roles and permissions to control what your team members can access and modify": "Define custom roles and permissions to control what your team members can access and modify", + "Define custom roles and permissions that can be assigned to your team members": "Define custom roles and permissions that can be assigned to your team members", + "New Role": "New Role", + "Contact sales to unlock custom roles": "Contact sales to unlock custom roles", + "Create New Role": "Create New Role", + "View ": "View ", + "Edit ": "Edit ", + "Role Name": "Role Name", + "Permissions": "Permissions", + "None": "None", + "Read": "Read", + "Write": "Write", + "Create": "Create", + "Email": "Email", + "First Name": "First Name", + "Last Name": "Last Name", + "Roles": "Roles", + "View the users assigned to this role": "View the users assigned to this role", + "Role": "Role", + "No users found": "No users found", + "Starting by assigning users to this role": "Starting by assigning users to this role", + "Project Role entry deleted successfully": "Project Role entry deleted successfully", + "Updated": "Updated", + "No project roles found": "No project roles found", + "Create custom project roles to manage permissions for platform users": "Create custom project roles to manage permissions for platform users", + "Show Users": "Show Users", + "View Role": "View Role", + "Edit Role": "Edit Role", + "Delete Role": "Delete Role", + "Project Role": "Project Role", + "Unlock Embedding Through JS SDK": "Unlock Embedding Through JS SDK", + "Enable signing keys to access embedding functionalities.": "Enable signing keys to access embedding functionalities.", + "New Signing Key": "New Signing Key", + "No signing keys found": "No signing keys found", + "Create a signing key to authenticate users with embedding": "Create a signing key to authenticate users with embedding", + "Delete Signing Key": "Delete Signing Key", + "Are you sure you want to delete this signing key?": "Are you sure you want to delete this signing key?", + "Signing Key Created": "Signing Key Created", + "Create New Signing Key": "Create New Signing Key", + "Signing Key Name": "Signing Key Name", + "Allowed domains updated": "Allowed domains updated", + "Update": "Update", + "Enable": "Enable", + "Configure Allowed Domains": "Configure Allowed Domains", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.", + "Add Domain": "Add Domain", + "Allow logins through {providerName}'s single sign-on functionality.": "Allow logins through {providerName}'s single sign-on functionality.", + "Email authentication updated": "Email authentication updated", + "Enable Single Sign On": "Enable Single Sign On", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Let your users sign in with your current SSO provider or give them self serve sign up access", + "Allowed Domains": "Allowed Domains", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Allow users to authenticate with specific domains. Leave empty to allow all domains.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Allowed Email Login", + "Allow logins through email and password.": "Allow logins through email and password.", + "Single sign on settings updated": "Single sign on settings updated", + "Disable": "Disable", + "Configure {provider} SSO": "Configure {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} Client ID", + "{provider} Client Secret": "{provider} Client Secret", + "Single sign-on settings updated": "Single sign-on settings updated", + "Configure SAML 2.0 SSO": "Configure SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDP Metadata", + "IDP Certificate": "IDP Certificate", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "Base URL", + "Resource Name": "Resource Name", + "Deployment Name": "Deployment Name", + "Saving": "Saving", + "Activepieces Copilot": "Activepieces Copilot", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot is configured and ready to help your users build flows faster using AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configure Activepieces Copilot to help your users build flows faster using AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Set provider credentials that will be used by universal AI pieces, i.e Text AI.", + "AI Providers": "AI Providers", + "Copilot": "Copilot", + "Configure credentials for {providerName} AI provider.": "Configure credentials for {providerName} AI provider.", + "Update AI Provider": "Update AI Provider", + "Enable AI Provider": "Enable AI Provider", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Please enter a valid domain", + "Your changes have been saved.": "Your changes have been saved.", + "The domain is already added.": "The domain is already added.", + "Add Custom Domain": "Add Custom Domain", + "Enter a domain name without a protocol (e.g. example.com)": "Enter a domain name without a protocol (e.g. example.com)", + "Logo URL": "Logo URL", + "Icon URL": "Icon URL", + "Favicon URL": "Favicon URL", + "Default Language": "Default Language", + "Select Language": "Select Language", + "No Languages": "No Languages", + "Primary Color": "Primary Color", + "Custom Domains": "Custom Domains", + "No domains added yet.": "No domains added yet.", + "Verified": "Verified", + "Pending, please contact the support for dns verification.": "Pending, please contact the support for dns verification.", + "Are you sure you want to delete {domain}?": "Are you sure you want to delete {domain}?", + "Brand Activepieces": "Brand Activepieces", + "Give your users an experience that looks like you by customizing the color, logo and more": "Give your users an experience that looks like you by customizing the color, logo and more", + "Configure the appearance and SMTP settings for your platform.": "Configure the appearance and SMTP settings for your platform.", + "Invalid host": "Invalid host", + "Invalid username": "Invalid username", + "Invalid password": "Invalid password", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Invalid sender name", + "SMTP is configured": "SMTP is configured", + "Mail Server": "Mail Server", + "Set up your SMTP settings to send emails from your domain.": "Set up your SMTP settings to send emails from your domain.", + "Disable Mail Server": "Disable Mail Server", + "Are you sure you want to disable your mail server?": "Are you sure you want to disable your mail server?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "This will stop you from sending emails for issues, quota limits, invitations and forgot password.", + "mail server": "mail server", + "Host": "Host", + "Port": "Port", + "Username": "Username", + "Password": "Password", + "Sender Email": "Sender Email", + "Sender Name": "Sender Name", + "Enable Global Connections": "Enable Global Connections", + "Manage platform-wide connections to external systems.": "Manage platform-wide connections to external systems.", + "No global connections found": "No global connections found", + "Create a global connection that can be shared to multiple projects": "Create a global connection that can be shared to multiple projects", + "License key is invalid": "License key is invalid", + "Invalid license key": "Invalid license key", + "License activated!": "License activated!", + "Activate License Key": "Activate License Key", + "Let the magic begin!": "Let the magic begin!", + "Activate": "Activate", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ", + "This feature is not available in your current edition. ": "This feature is not available in your current edition. ", + "Learn how to upgrade": "Learn how to upgrade", + "Activate your platform and unlock enterprise features": "Activate your platform and unlock enterprise features", + "Activate License": "Activate License", + "Expiration": "Expiration", + "Valid until": "Valid until", + "Expired": "Expired", + "Expires soon": "Expires soon", + "Features": "Features", + "Applying Tags...": "Applying Tags...", + "Tags applied.": "Tags applied.", + "Tag created": "Tag created", + "Tag": "Tag", + "Tags": "Tags", + "Control Pieces": "Control Pieces", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Show the pieces that matter most to your users and hide the ones you don't like.", + "Manage the pieces that are available to your users": "Manage the pieces that are available to your users", + "Start by installing pieces that you want to use in your automations": "Start by installing pieces that you want to use in your automations", + "Piece Name": "Piece Name", + "Hide this piece from all projects": "Hide this piece from all projects", + "Show this piece for all projects": "Show this piece for all projects", + "Unpin this piece": "Unpin this piece", + "Pin this piece": "Pin this piece", + "Pieces synced": "Pieces synced", + "Pieces have been synced from the activepieces cloud.": "Pieces have been synced from the activepieces cloud.", + "OAuth2 Credentials Deleted": "OAuth2 Credentials Deleted", + "OAuth2 Credentials Updated": "OAuth2 Credentials Updated", + "Configure OAuth2 APP": "Configure OAuth2 APP", + "Delete OAuth2 APP": "Delete OAuth2 APP", + "Templates deleted successfully": "Templates deleted successfully", + "Delete Templates": "Delete Templates", + "Are you sure you want to delete the selected templates?": "Are you sure you want to delete the selected templates?", + "New Template": "New Template", + "Unlock Templates": "Unlock Templates", + "Convert the most common automations into reusable templates 1 click away from your users": "Convert the most common automations into reusable templates 1 click away from your users", + "Convert the most common automations into reusable templates": "Convert the most common automations into reusable templates", + "No templates found": "No templates found", + "Create a template for your user to inspire them": "Create a template for your user to inspire them", + "Edit template": "Edit template", + "Template is required": "Template is required", + "Update New Template": "Update New Template", + "Create New Template": "Create New Template", + "Template Name": "Template Name", + "Description": "Description", + "Template Description": "Template Description", + "Blog URL": "Blog URL", + "Template Blog URL": "Template Blog URL", + "Template": "Template", + "Invalid JSON": "Invalid JSON", + "User deleted successfully": "User deleted successfully", + "User activated successfully": "User activated successfully", + "User deactivated successfully": "User deactivated successfully", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Manage your users and their access to your projects", + "Start inviting users to your project": "Start inviting users to your project", + "External Id": "External Id", + "Admin": "Admin", + "Member": "Member", + "Activated": "Activated", + "Deactivated": "Deactivated", + "Edit user": "Edit user", + "Admin cannot be deactivated": "Admin cannot be deactivated", + "Deactivate user": "Deactivate user", + "Activate user": "Activate user", + "Delete User": "Delete User", + "Are you sure you want to delete this user?": "Are you sure you want to delete this user?", + "Delete user": "Delete user", + "Update User Role": "Update User Role", + "Meeting Summary Flow": "Meeting Summary Flow", + "Added new features and fixed bugs": "Added new features and fixed bugs", + "Flows Changes": "Flows Changes", + "Connections Changes": "Connections Changes", + "New connections are placeholders and need to be reconnected again": "New connections are placeholders and need to be reconnected again", + "renamed to": "renamed to", + "Tables Changes": "Tables Changes", + "No changes to apply": "No changes to apply", + "Apply Changes": "Apply Changes", + "Create Git Release": "Create Git Release", + "Create Project Release": "Create Project Release", + "Create Rollback to": "Create Rollback to", + "Source": "Source", + "Rollback": "Rollback", + "Imported At": "Imported At", + "Imported By": "Imported By", + "Track and manage your project version history and deployments. ": "Track and manage your project version history and deployments. ", + "Environments & Releases": "Environments & Releases", + "Project Releases": "Project Releases", + "Create Release": "Create Release", + "From Git": "From Git", + "From Project": "From Project", + "No project releases found": "No project releases found", + "Create a project release to get started": "Create a project release to get started", + "Please select project": "Please select project", + "No Changes Found": "No Changes Found", + "There are no differences to apply": "There are no differences to apply", + "Please select a project": "Please select a project", + "Search projects...": "Search projects...", + "Review Changes": "Review Changes", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Imported by", + "from": "from", + "No description provided": "No description provided", + "Invitation only sign up": "Invitation only sign up", + "Please ask your administrator to add you to the organization.": "Please ask your administrator to add you to the organization.", + "Something went wrong, please try again.": "Something went wrong, please try again.", + "Please try again.": "Please try again.", + "Please enter a valid email address": "Please enter a valid email address", + "The email is already added.": "The email is already added.", + "Add email": "Add email", + "Only project admins can do this": "Only project admins can do this", + "Add Alert Email": "Add Alert Email", + "Enter the email address to receive alerts.": "Enter the email address to receive alerts.", + "Add Email": "Add Email", + "Emails": "Emails", + "Add email addresses to receive alerts.": "Add email addresses to receive alerts.", + "No emails added yet.": "No emails added yet.", + "Choose what you want to be notified about.": "Choose what you want to be notified about.", + "Project and alert permissions are required to change this setting.": "Project and alert permissions are required to change this setting.", + "Every Failed Run": "Every Failed Run", + "Get an email alert when a flow fails.": "Get an email alert when a flow fails.", + "Get an email alert when a new issue created.": "Get an email alert when a new issue created.", + "Never": "Never", + "Turn off email notifications.": "Turn off email notifications.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Customize the appearance of the app. Automatically switch between day and night themes.", + "Select the theme for the dashboard.": "Select the theme for the dashboard.", + "Light": "Light", + "Dark": "Dark", + "Select the language that will be used in the dashboard.": "Select the language that will be used in the dashboard.", + "Select language": "Select language", + "Search language...": "Search language...", + "No language found.": "No language found.", + "Help us translate Activepieces to your language.": "Help us translate Activepieces to your language.", + "Learn more": "Learn more", + "Git Connection Removed": "Git Connection Removed", + "Your Git repository has been successfully disconnected": "Your Git repository has been successfully disconnected", + "Enable Environments": "Enable Environments", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Deploy flows across development, staging and production environments with version control and team collaboration", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Connect to Git to enable version control, backup your flows, and manage multiple environments. ", + "Repository URL": "Repository URL", + "Not connected": "Not connected", + "Project Folder": "Project Folder", + "Releases Enabled": "Releases Enabled", + "You have successfully enabled releases": "You have successfully enabled releases", + "Enable releases to easily create and manage project releases.": "Enable releases to easily create and manage project releases.", + "The external ID is already taken.": "The external ID is already taken.", + "Manage general settings for your project.": "Manage general settings for your project.", + "Used to identify the project based on your SaaS ID": "Used to identify the project based on your SaaS ID", + "org-3412321": "org-3412321", + "Delete {name}": "Delete {name}", + "This will permanently delete this piece, all steps using it will fail.": "This will permanently delete this piece, all steps using it will fail.", + "Add a piece to your project that you want to use in your automations": "Add a piece to your project that you want to use in your automations", + "Pieces list updated": "Pieces list updated", + "Manage Pieces": "Manage Pieces", + "Choose which pieces you want to be available for your current project users": "Choose which pieces you want to be available for your current project users", + "Unlock Team Permissions": "Unlock Team Permissions", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial", + "Project Members": "Project Members", + "Invite your team members to collaborate.": "Invite your team members to collaborate.", + "No members are added to this project.": "No members are added to this project.", + "Pending Invitations": "Pending Invitations", + "No pending invitation.": "No pending invitation.", + "templateId is missing": "templateId is missing", + "Me Only": "Me Only", + "Unresolved": "Unresolved", + "Resolved": "Resolved", + "Title": "Title", + "Date Created": "Date Created", + "Manage todos for your project that are created by automations": "Manage todos for your project that are created by automations", + "No todos found": "No todos found", + "You do not have any pending todos. Great job!": "You do not have any pending todos. Great job!", + "Write a comment...": "Write a comment...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "This feature is still under testing and might be changed often", + "Failed to copy to clipboard": "Failed to copy to clipboard", + "{number} items selected": "{number} items selected", + "Select All": "Select All", + "No results found.": "No results found.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect must be used within MultiSelectProvider", + "Unset": "Unset", + "Refresh": "Refresh", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} more", + "Removed {entityName}": "Removed {entityName}", + "Download File": "Download File", + "Copied to clipboard": "Copied to clipboard", + "File is not available after execution.": "File is not available after execution.", + "Available for Projects": "Available for Projects", + "Select projects": "Select projects", + "No items": "No items", + "Previous": "Previous", + "to": "to", + "Last Week": "Last Week", + "Last Month": "Last Month", + "Last 3 Months": "Last 3 Months", + "Last 6 Months": "Last 6 Months", + "Next 7 days": "Next 7 days", + "Next 30 days": "Next 30 days", + "Next 90 days": "Next 90 days", + "Next 180 days": "Next 180 days", + "Select Time Range": "Select Time Range", + "Clear": "Clear", + "Download": "Download", + "Go to Dashboard": "Go to Dashboard", + "Select a file": "Select a file", + "Press space to separate values": "Press space to separate values", + "AM": "AM", + "PM": "PM", + "Already have an account?": "Already have an account?", + "Sign in": "Sign in", + "Don't have an account?": "Don't have an account?", + "Sign up": "Sign up", + "Welcome Back!": "Welcome Back!", + "Enter your email below to sign in to your account": "Enter your email below to sign in to your account", + "Let's Get Started!": "Let's Get Started!", + "Create your account and start flowing!": "Create your account and start flowing!", + "Your password was changed successfully": "Your password was changed successfully", + "Your password reset request has expired, please request a new one": "Your password reset request has expired, please request a new one", + "Reset Password": "Reset Password", + "Enter your new password": "Enter your new password", + "Password is required": "Password is required", + "Verification email resent, if previous one expired.": "Verification email resent, if previous one expired.", + "Password reset link resent, if previous one expired.": "Password reset link resent, if previous one expired.", + "We sent you a link to complete your registration to": "We sent you a link to complete your registration to", + "We sent you a link to reset your password to": "We sent you a link to reset your password to", + "Didn't receive an email or it expired?": "Didn't receive an email or it expired?", + "Resend": "Resend", + "Please enter your email": "Please enter your email", + "Check Your Inbox": "Check Your Inbox", + "If the user exists we'll send you an email with a link to reset your password.": "If the user exists we'll send you an email with a link to reset your password.", + "Send Password Reset Link": "Send Password Reset Link", + "Back to sign in": "Back to sign in", + "Email is invalid": "Email is invalid", + "Something went wrong, please try again later": "Something went wrong, please try again later", + "Invalid email or password": "Invalid email or password", + "User has been deactivated": "User has been deactivated", + "Email domain is disallowed": "Email domain is disallowed", + "Email authentication has been disabled": "Email authentication has been disabled", + "Forgot your password?": "Forgot your password?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "Sign up is restricted. You need an invitation to join. Please contact the administrator.", + "Email is already used": "Email is already used", + "Email authentication is disabled": "Email authentication is disabled", + "First name is required": "First name is required", + "Last name is required": "Last name is required", + "Email is required": "Email is required", + "Receive updates and newsletters from activepieces": "Receive updates and newsletters from activepieces", + "By creating an account, you agree to our": "By creating an account, you agree to our", + "terms of service": "terms of service", + "privacy policy": "privacy policy", + "Sign up With": "Sign up With", + "Google": "Google", + "Sign in With": "Sign in With", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "Email has been verified. You will be redirected to sign in...", + "Verifying email...": "Verifying email...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "invitation has expired, once you sign in again you will be able to resend the verification email.", + "Redirecting to sign in...": "Redirecting to sign in...", + "Password must contain at least one special character": "Password must contain at least one special character", + "Password must contain at least one lowercase letter": "Password must contain at least one lowercase letter", + "Password must contain at least one uppercase letter": "Password must contain at least one uppercase letter", + "Password must contain at least one number": "Password must contain at least one number", + "8-64 Characters": "8-64 Characters", + "Special Character": "Special Character", + "Lowercase": "Lowercase", + "Uppercase": "Uppercase", + "Number": "Number", + "Connection has been updated.": "Connection has been updated.", + "Edit Global Connection": "Edit Global Connection", + "Connection has been renamed.": "Connection has been renamed.", + "New Connection Name": "New Connection Name", + "Connection name already used": "Connection name already used", + "Please select at least one project": "Please select at least one project", + "Run Succeeded": "Run Succeeded", + "Run Failed": "Run Failed", + "Flow Run is paused": "Flow Run is paused", + "Run Failed due to quota exceeded": "Run Failed due to quota exceeded", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Run failed due to exceeding the memory limit of {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "Run exceeded {timeout} seconds, try to optimize your steps.", + "Run failed for an unknown reason, contact support.": "Run failed for an unknown reason, contact support.", + "Unknown": "Unknown", + "Exit Run": "Exit Run", + "Select shown": "Select shown", + "Select all": "Select all", + "Start Time": "Start Time", + "Runs replayed successfully": "Runs replayed successfully", + "Retry": "Retry", + "all except": "all except", + "all": "all", + "Only failed runs can be retried from failed step": "Only failed runs can be retried from failed step", + "No flow runs found": "No flow runs found", + "Come back later when your automations start running": "Come back later when your automations start running", + "Step running": "Step running", + "Step paused": "Step paused", + "Step Stopped": "Step Stopped", + "Step Succeeded": "Step Succeeded", + "Step Failed": "Step Failed", + "Please publish flow first": "Please publish flow first", + "Flow is on": "Flow is on", + "Flow is off": "Flow is off", + "Permission Needed": "Permission Needed", + "Draft Version": "Draft Version", + "Published Version": "Published Version", + "Locked Version": "Locked Version", + "flowsImported": "{flowsCount, plural, =0 {No Flows Imported} =1 {Flow Imported Successfully} other {Flows Imported Successfully}}", + "Template file is invalid": "Template file is invalid", + "No valid templates found. The following files failed to import: ": "No valid templates found. The following files failed to import: ", + "Please select a file first": "Please select a file first", + "Unsupported file type": "Unsupported file type", + "Import Flow": "Import Flow", + "Warning": "Warning", + "Importing a flow will overwrite your current one.": "Importing a flow will overwrite your current one.", + "Select a folder": "Select a folder", + "Folders": "Folders", + "Please select a folder": "Please select a folder", + "Moved flows successfully": "Moved flows successfully", + "Move Selected Flows": "Move Selected Flows", + "Select Folder": "Select Folder", + "No Folders": "No Folders", + "Flow has been renamed.": "Flow has been renamed.", + "New Flow Name": "New Flow Name", + "Use Template": "Use Template", + "Browse Templates": "Browse Templates", + "Search templates": "Search templates", + "No templates found, try adjusting your search": "No templates found, try adjusting your search", + "Read more about this template in": "Read more about this template in", + "this blog!": "this blog!", + "Share Template": "Share Template", + "Generate or update a template link for the current flow to easily share it with others.": "Generate or update a template link for the current flow to easily share it with others.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "The template will not have any credentials in connection fields, keeping sensitive information secure.", + "A short description of the template": "A short description of the template", + "Flow Is In Use": "Flow Is In Use", + "Flow is being used by another user, please try again later.": "Flow is being used by another user, please try again later.", + "Flow has been published.": "Flow has been published.", + "Flows have been exported.": "Flows have been exported.", + "Run": "Run", + "Real time flow": "Real time flow", + "Flow can't be published with empty trigger {name}": "Flow can't be published with empty trigger {name}", + "Please contact support as your published flow has a problem": "Please contact support as your published flow has a problem", + "Actions": "Actions", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.", + "You are on a development branch, this will not delete the flows from the remote repository.": "You are on a development branch, this will not delete the flows from the remote repository.", + "Please enter folder name": "Please enter folder name", + "Added folder successfully": "Added folder successfully", + "The folder name already exists.": "The folder name already exists.", + "New Folder": "New Folder", + "Folder Name": "Folder Name", + "Loading...": "Loading...", + "Delete {folderName}": "Delete {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "If you delete this folder, we will keep its flows and move them to Uncategorized.", + "All flows": "All flows", + "Please enter a folder name": "Please enter a folder name", + "Renamed flow successfully": "Renamed flow successfully", + "Folder name already used": "Folder name already used", + "New Folder Name": "New Folder Name", + "Connected successfully": "Connected successfully", + "Connect Git": "Connect Git", + "Remote URL": "Remote URL", + "Folder name is the name of the folder where the project will be stored or fetched.": "Folder name is the name of the folder where the project will be stored or fetched.", + "SSH Private Key": "SSH Private Key", + "The SSH private key to use for authentication.": "The SSH private key to use for authentication.", + "Only published flows can be pushed to Git": "Only published flows can be pushed to Git", + "Pushed successfully": "Pushed successfully", + "Invalid Operation": "Invalid Operation", + "Commit Message": "Commit Message", + "Enter a commit message to describe the changes you want to push.": "Enter a commit message to describe the changes you want to push.", + "Push": "Push", + "This field is required": "This field is required", + "Your submission was successfully received.": "Your submission was successfully received.", + "Flow not found": "Flow not found", + "The flow you are trying to submit to does not exist.": "The flow you are trying to submit to does not exist.", + "The flow failed to execute.": "The flow failed to execute.", + "Submit": "Submit", + "issues-notification": "issues-notification", + "Please select a package type": "Please select a package type", + "package.json not found in archive": "package.json not found in archive", + "Error processing archive file": "Error processing archive file", + "Please upload a .tgz file": "Please upload a .tgz file", + "Piece name is required for NPM Registry": "Piece name is required for NPM Registry", + "Piece version is required for NPM Registry": "Piece version is required for NPM Registry", + "Piece installed": "Piece installed", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "A piece with this name and version is already installed. Please update the version number in package.json and try again.", + "Install Piece": "Install Piece", + "Install a piece": "Install a piece", + "Package Type": "Package Type", + "NPM Registry": "NPM Registry", + "Packed Archive (.tgz)": "Packed Archive (.tgz)", + "Piece Version": "Piece Version", + "Package Archive": "Package Archive", + "Package archive": "Package archive", + "Powerful Node.js & TypeScript code with npm": "Powerful Node.js & TypeScript code with npm", + "Loop on Items": "Loop on Items", + "Split your flow into branches depending on condition(s)": "Split your flow into branches depending on condition(s)", + "Empty Trigger": "Empty Trigger", + "An internal error occurred while fetching data, please contact support": "An internal error occurred while fetching data, please contact support", + "An internal error occurred, please contact support": "An internal error occurred, please contact support", + "Custom Javascript Code": "Custom Javascript Code", + "Router": "Router", + "recordsCount": "{recordsCount, plural, =0 {0 Records} =1 {1 Record} other {# Records}}", + "selected": "selected", + "All records selected": "All records selected", + "fieldsCount": "{fieldsCount, plural, =0 {0 Fields} =1 {1 Field} other {# Fields}}", + "Saving...": "Saving...", + "Loading more...": "Loading more...", + "Export Table": "Export Table", + "Delete Records": "Delete Records", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Are you sure you want to delete the selected records? This action cannot be undone.", + "record": "record", + "records": "records", + "mm/dd/yyy": "mm/dd/yyy", + "Delete Field": "Delete Field", + "Are you sure you want to delete this field? This action cannot be undone.": "Are you sure you want to delete this field? This action cannot be undone.", + "field": "field", + "Ignored": "Ignored", + "Table": "Table", + "CSV": "CSV", + "Field": "Field", + "Please select a csv file": "Please select a csv file", + "Max file size is {maxFileSize}MB": "Max file size is {maxFileSize}MB", + "Import CSV": "Import CSV", + "Imported records will be added to the bottom of the table": "Imported records will be added to the bottom of the table", + "Any records after the limit ({maxRecords} records) will be ignored": "Any records after the limit ({maxRecords} records) will be ignored", + "CSV File": "CSV File", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support", + "Name must be unique": "Name must be unique", + "Type is required": "Type is required", + "Please add at least one option": "Please add at least one option", + "New Field": "New Field", + "Options": "Options", + "Name is already taken": "Name is already taken", + "Table renamed": "Table renamed", + "Table name": "Table name", + "Team Invitation Accepted": "Team Invitation Accepted", + "Thank you for accepting the invitation. We are redirecting you right now...": "Thank you for accepting the invitation. We are redirecting you right now...", + "Invalid invitation token. Please try again.": "Invalid invitation token. Please try again.", + "Role updated successfully": "Role updated successfully", + "Error updating role": "Error updating role", + "Please try again later": "Please try again later", + "Edit Role for": "Edit Role for", + "Select Role": "Select Role", + "Avatar": "Avatar", + "Remove {email}": "Remove {email}", + "Are you sure you want to remove this invitation?": "Are you sure you want to remove this invitation?", + "Please select invitation type": "Please select invitation type", + "Please select platform role": "Please select platform role", + "Invitation sent successfully": "Invitation sent successfully", + "Please select a project role": "Please select a project role", + "Invitation link copied successfully": "Invitation link copied successfully", + "Invite User": "Invite User", + "Invitation Link": "Invitation Link", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Type the email address of the user you want to invite, the invitation expires in 24 hours.", + "Invite To": "Invite To", + "Entire Platform": "Entire Platform", + "Select Project Role": "Select Project Role", + "Invite": "Invite", + "Platform Role": "Platform Role", + "Select a platform role": "Select a platform role", + "Are you sure you want to remove this member?": "Are you sure you want to remove this member?", + "Editor": "Editor", + "Operator": "Operator", + "Viewer": "Viewer", + "Select a project role": "Select a project role", + "Steps in this flow": "Steps in this flow", + "Invalid Access": "Invalid Access", + "Either the project does not exist or you do not have access to it.": "Either the project does not exist or you do not have access to it." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/hu/translation.json b/packages/react-ui/public/locales/hu/translation.json new file mode 100644 index 0000000..b5c8925 --- /dev/null +++ b/packages/react-ui/public/locales/hu/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Közzététel", + "Latest version is published": "A legújabb verzió közzétéve", + "Your flow has incomplete steps": "Your flow has incomplete steps", + "Edit Flow": "Folyamat szerkesztése", + "View Draft": "View Draft", + "Uncategorized": "Kategorizálatlan", + "Go to folder": "Mappához megy", + "Support": "Támogatás", + "Runs": "Futtatások", + "Run Logs": "Futtatási naplók", + "Versions": "Versions", + "Versions History": "Versions History", + "Error generating code": "Hiba a kód generálásában", + "AI Copilot": "AI Társ", + "i.e Calculate the sum of a list...": "i.e Calculate the sum of a list...", + "Send": "Küldés", + "Generating Code": "Generating Code", + "Hello there! I am here to generate code that helps with your flow": "Hello there! I am here to generate code that helps with your flow", + "Here are examples of what I am best used for: ": "Here are examples of what I am best used for: ", + "Text Processing": "Text Processing", + "Process strings, dates and data": "Process strings, dates and data", + "Data Operations": "Data Operations", + "Change data from one format to another": "Change data from one format to another", + "Calculations": "Calculations", + "Handle math and statistics": "Handle math and statistics", + "API Integration": "API Integration", + "Connect with external services. Best for simple integrations currently.": "Connect with external services. Best for simple integrations currently.", + "What would you like me to help you with?": "What would you like me to help you with?", + "Insert": "Beillesztés", + "Data Selector": "Adatválasztó", + "Search": "Keresés", + "No matching data": "Nincs egyező adat", + "Try adjusting your search": "Próbáld meg módosítani a keresést", + "This trigger needs to have data loaded from your account, to use as sample data.": "Ehhez a triggerhez adatokat kell betölteni a fiókodból, hogy mintaadatként használhasd.", + "This step needs to be tested in order to view its data.": "Ezt a lépést tesztelni kell ahhoz, hogy megtekinthesd az adatait.", + "Go to Trigger": "Menj a Triggerhez", + "Go to Step": "Menj a Lépéshez", + "Select Mode": "Select Mode", + "Move Mode": "Move Mode", + "Reset Zoom": "Zoom visszaállítása", + "Zoom In": "Nagyítás", + "Zoom Out": "Kicsinyítés", + "Fit to View": "Alkalmazkodás a nézethez", + "Replace": "Replace", + "Copy": "Másolás", + "Duplicate": "Másolat", + "Paste After Last Step": "Paste After Last Step", + "Paste Inside Loop": "Paste Inside Loop", + "Paste After": "Paste After", + "Paste Inside...": "Paste Inside...", + "New Branch": "New Branch", + "Paste Inside Branch": "Paste Inside Branch", + "Delete": "Törlés", + "Duplicate Branch": "Duplicate Branch", + "Delete Branch": "Delete Branch", + "Invalid Move": "Érvénytelen mozdulat", + "The destination location is a child of the dragged step": "The destination location is a child of the dragged step", + "End": "Vége", + "Skipped": "Skipped", + "Incomplete settings": "Hiányos beállítások", + "logo": "logó", + "Step Icon": "Lépés ikonja", + "Branch": "Ág", + "incompleteSteps": "{invalidSteps, plural, =0 {nincs befejezett lépés} =1 {Befejezett 1 lépés} other {Befejezett # lépés}}", + "Test Flow": "Folyamat tesztelése", + "Please test the trigger first": "Kérlek először teszteld le a triggert", + "View Only": "Csak megtekintés", + "Use as Draft": "Használat vázlatként", + "Are you sure?": "Biztos vagy benne?", + "Your current draft version will be overwritten with": "A jelenlegi vázlat verziód felül lesz írva ezzel:", + "version #": "verzió #", + "Cancel": "Mégse", + "Confirm": "Megerősítés", + "Version": "Verzió", + "Viewing": "Megtekintés", + "View": "Megtekintés", + "Version History": "Verziótörténet", + "Error, please try again.": "Hiba, kérlek próbáld újra.", + "Continue on Failure": "Folytatás hiba esetén", + "Enable this option to skip this step and continue the flow normally if it fails.": "Kapcsold be ezt az opciót, hogy átugorj ezen a lépésen, és a folyamatot normál módon folytasd, ha hiba történik.", + "Retry on Failure": "Újrapróbálkozás hiba esetén", + "Automatically retry up to four attempts when failed.": "Automatikusan próbálkozzon meg négy próbálkozással, ha hiba történik.", + "Remove": "Eltávolítás", + "Add Item": "Elem hozzáadása", + "File Input": "File Input", + "Date Input": "Date Input", + "Dynamic value": "Dynamic value", + "Select an option": "Válassz egy opciót", + "Unexpected error, please retry": "Unexpected error, please retry", + "Unexpected error, please refresh the page or contact support": "Unexpected error, please refresh the page or contact support", + "Name can only contain letters, numbers and underscores": "Name can only contain letters, numbers and underscores", + "Ask AI": "Kérdezd meg az AI-t", + "Create Todo Guide": "Create Todo Guide", + "Where would you like the todo to be reviewed?": "Where would you like the todo to be reviewed?", + "Activepieces Todos": "Activepieces Todos", + "Users will manage tasks directly in Activepieces": "Users will manage tasks directly in Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.", + "External Channel (Slack, Teams, Email, ...)": "External Channel (Slack, Teams, Email, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.", + "Preview (Activepieces Todos)": "Preview (Activepieces Todos)", + "Preview (External channel)": "Preview (External channel)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "You can add the channel before the Wait Step, and configure the logic in the Router step", + "Add Steps": "Add Steps", + "All": "All", + "AI": "AI", + "Core": "Core", + "Apps": "Apps", + "Not available as trigger": "Not available as trigger", + "Not available as action": "Not available as action", + "Let our AI assistant help you out": "Let our AI assistant help you out", + "Or": "Or", + "Request Piece": "Request Piece", + "No pieces found": "Nincs darab találva", + "Please select a piece first": "Please select a piece first", + "All Iterations": "Összes iteráció", + "Duration": "Időtartam", + "Input": "Bemenet", + "Output": "Kimenet", + "There are no logs captured for this run.": "There are no logs captured for this run.", + "Logs are kept for {days} days after execution and then deleted.": "Logs are kept for {days} days after execution and then deleted.", + "Run Details": "Futtatás részletei", + "Iteration": "Iteráció", + "Done": "Kész", + "Took": "Eltelt", + "Running": "Fut", + "on latest version": "on latest version", + "from failed step": "from failed step", + "Recent Runs": "Legutóbbi futtatások", + "No runs found": "Nincs futtatás találva", + "Close": "Bezárás", + "OR": "VAGY", + "And If": "És ha", + "+ And": "+ És", + "+ Or": "+ Vagy", + "(Text) Contains": "(Szöveg) Tartalmazza", + "(Text) Does not contain": "(Szöveg) Nem tartalmazza", + "(Text) Exactly matches": "(Szöveg) Pontosan illeszkedik", + "(Text) Does not exactly match": "(Szöveg) Nem illeszkedik pontosan", + "(Text) Starts with": "(Szöveg) Ezzel kezdődik", + "(Text) Does not start with": "(Szöveg) Nem ezzel kezdődik", + "(Text) Ends with": "(Szöveg) Ezzel végződik", + "(Text) Does not end with": "(Szöveg) Nem ezzel végződik", + "(List) Contains": "(List) Contains", + "(List) Does not contain": "(List) Does not contain", + "(Number) Is greater than": "(Szám) Nagyobb mint", + "(Number) Is less than": "(Szám) Kisebb mint", + "(Number) Is equal to": "(Szám) Egyenlő", + "(Date/time) After": "(Date/time) After", + "(Date/time) Before": "(Date/time) Before", + "(Date/time) Equals": "(Date/time) Equals", + "(Boolean) Is true": "(Logikai érték) Igaz", + "(Boolean) Is false": "(Logikai érték) Hamis", + "(List) Is empty": "(List) Is empty", + "(List) Is not empty": "(List) Is not empty", + "Exists": "Létezik", + "Does not exist": "Nem létezik", + "Incomplete condition": "Incomplete condition", + "First value": "Első érték", + "Second value": "Második érték", + "Case sensitive": "Kis- és nagybetű érzékeny", + "Execute If": "Execute If", + "The package name is required": "A csomag neve szükséges", + "Success": "Siker", + "Package added successfully": "Csomag sikeresen hozzáadva", + "Could not fetch package version": "Nem sikerült lekérni a csomag verzióját", + "Add NPM Package": "NPM Csomag hozzáadása", + "Type the name of the npm package you want to add.": "Írd be az npm csomag nevét, amit hozzá szeretnél adni.", + "Package Name": "Csomag neve", + "The latest version will be fetched and added": "A legújabb verzió lesz lekérve és hozzáadva", + "Add": "Hozzáadás", + "Code": "Kód", + "Dependencies": "Függőségek", + "Use code": "Use code", + "Add package": "Csomag hozzáadása", + "Inputs": "Bemenetek", + "Edit Step Name": "Lépés név szerkesztése", + "Edit Branch Name": "Edit Branch Name", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Válaszd ki azokat az elemeket, amiken az előző lépésben iterálni szeretnél, kattintva az **Items** bemenetre, amelynek egy **lista** elemeinek kell lennie.\n\nA ciklus minden egyes elemet végig fog iterálni és végrehajtja a következő lépést minden egyes elemre.", + "Items": "Elemek", + "Select an array of items": "Válassz egy elemekből álló tömböt", + "Reconnect": "Reconnect", + "Select a connection": "Válassz egy kapcsolatot", + "Create Connection": "Kapcsolat létrehozása", + "Rename": "Átnevezés", + "Move": "Mozgatás", + "Add Branch": "Add Branch", + "Execute": "Execute", + "Only the first (left) matching branch": "Only the first (left) matching branch", + "All matching paths from left to right": "All matching paths from left to right", + "Branches": "Branches", + "{field} is required": "{field} is required", + "Tool Sample Data": "Tool Sample Data", + "Fill in the following fields to use them as sample data for the trigger.": "Fill in the following fields to use them as sample data for the trigger.", + "No input fields defined in the schema": "No input fields defined in the schema", + "Save": "Mentés", + "Test Environment": "Test Environment", + "Assigned to": "Assigned to", + "(Me)": "(Me)", + "Please select status to resolve the todo": "Please select status to resolve the todo", + "Resolve": "Resolve", + "Change status to resolved": "Change status to resolved", + "Send Sample Data to Webhook": "Send Sample Data to Webhook", + "Method": "Method", + "Query Params": "Query Params", + "Headers": "Headers", + "Body": "Body", + "Type": "Type", + "JSON": "JSON", + "Text": "Text", + "Form Data": "Form Data", + "Generate Sample Data": "Minta adat generálása", + "Test Step": "Lépés tesztelése", + "Retest": "Újra tesztelés", + "Testing Failed": "Tesztelés nem sikerült", + "Tested Successfully": "Sikeres tesztelés", + "Logs": "Logs", + "There is no sample data available found for this trigger.": "Nincs elérhető minta adat ehhez a triggerhez.", + "Internal error, please try again later.": "Internal error, please try again later.", + "Failed to run test step and no error message was returned": "Nem sikerült tesztelni a lépést, és nem jött vissza hibaüzenet", + "Please fix inputs first": "Kérlek először javítsd a bemeneteket", + "No sample data available": "No sample data available", + "Old results were removed, retest for new sample data": "Old results were removed, retest for new sample data", + "Result #": "Eredmény #", + "The sample data can be used in the next steps.": "A minta adat felhasználható a következő lépésekben.", + "Testing Trigger": "Trigger tesztelése", + "Action Required": "Action Required", + "testPieceWebhookTriggerNote": "Kérlek menj a {pieceName}-hez és triggereld a {triggerName}-t.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Send Data to the webhook URL to generate sample data to use in the next steps", + "Test Trigger": "Trigger tesztelése", + "Use Mock Data": "Use Mock Data", + "Load Sample Data": "Minta adat betöltése", + "Test Tool": "Test Tool", + "home": "otthon", + "Home": "Kezdőlap", + "Alerts": "Riasztások", + "Releases": "Releases", + "Flows": "Folyamatok", + "Products": "Products", + "MCP": "MCP", + "Tables": "Tables", + "Todos": "Todos", + "Push to Git": "Push a Git-re", + "Move To": "Mozgatás ide", + "Duplicating": "Másolás", + "Import": "Importálás", + "Exporting": "Exportálás", + "Export": "Exportálás", + "Share": "Megosztás", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Biztosan törölni akarod ezt a folyamatot? Ez véglegesen törli a folyamatot, minden adatát és a háttérfuttatásokat.", + "You are on a development branch, this will also delete the flow from the remote repository.": "You are on a development branch, this will also delete the flow from the remote repository.", + "flow": "folyamat", + "Community Support": "Community Support", + "Overview": "Overview", + "Projects": "Projektek", + "Users": "Felhasználók", + "Setup": "Setup", + "Branding": "Márkázás", + "Global Connections": "Global Connections", + "Pieces": "Darabok", + "Templates": "Sablonok", + "License Key": "License Key", + "Security": "Security", + "Audit Logs": "Audit naplók", + "Single Sign On": "Egyszeri bejelentkezés", + "Signing Keys": "Aláíró kulcsok", + "Project Roles": "Project Roles", + "API Keys": "API Kulcsok", + "Infrastructure": "Infrastructure", + "Workers": "Workers", + "Health": "Health", + "Billing": "Billing", + "Settings": "Beállítások", + "Contact Sales": "Contact Sales", + "General": "Általános", + "Appearance": "Megjelenés", + "Team": "Csapat", + "Environments": "Environments", + "Project Settings": "Project Settings", + "Exit Platform Admin": "Exit Platform Admin", + "Enter Platform Admin": "Enter Platform Admin", + "Logout": "Logout", + "Misc": "Misc", + "Connections": "Kapcsolatok", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "Today": "Today", + "Tasks": "Feladatok", + "AI Credits": "AI Credits", + "Usage resets in": "Usage resets in", + "Manage": "Manage", + "Unlimited": "Unlimited", + "Enabled": "Enabled", + "Disabled": "Disabled", + "Could not claim the authorization code, make sure you have correct settings and try again.": "Nem sikerült megszerezni az engedélyezési kódot, győződj meg arról, hogy helyesek a beállítások, és próbáld újra.", + "Connection failed with error {msg}": "A kapcsolat hiba történt {msg}", + "You don't have the permission to create a connection.": "You don't have the permission to create a connection.", + "Reconnect {displayName} Connection": "Kapcsolódás újra {displayName} kapcsolat", + "Connect to {displayName}": "Connect to {displayName}", + "Connection Name": "Kapcsolat neve", + "Connection name": "Kapcsolat neve", + "New Connection": "Új kapcsolat", + "Redirect URL": "Redirect URL", + "Client ID": "Ügyfél azonosító", + "Client Secret": "Ügyféltitok", + "Connect": "Csatlakozás", + "Disconnect": "Lecsatlakozás", + "I would like to use my own App Credentials": "Saját alkalmazás hitelesítő adatokat szeretnék használni", + "I would like to use predefined App Credentials": "Előre definiált alkalmazás hitelesítő adatokat szeretnék használni", + "Permission needed": "Permission needed", + "Connections replaced successfully": "Connections replaced successfully", + "Error": "Hiba", + "Failed to replace connections": "Failed to replace connections", + "Failed to get affected flows": "Failed to get affected flows", + "Please select a piece": "Please select a piece", + "Please select a connection to replace": "Please select a connection to replace", + "Please select a connection to replace with": "Please select a connection to replace with", + "Replace Connections": "Replace Connections", + "Confirm Replacement": "Confirm Replacement", + "Replace one connection with another.": "Replace one connection with another.", + "This action requires ": "This action requires ", + "reconnecting": "reconnecting", + " any associated MCP pieces.": " any associated MCP pieces.", + "Piece": "Darab", + "Select a piece": "Select a piece", + "Connection to Replace": "Connection to Replace", + "Choose connection to replace": "Choose connection to replace", + "Replaced With": "Replaced With", + "Choose connection to replace with": "Choose connection to replace with", + "All flows will be changed to use the replaced with connection": "All flows will be changed to use the replaced with connection", + "Next": "Next", + "No flows will be affected by this change": "No flows will be affected by this change", + "Back": "Back", + "Unnamed tool": "Unnamed tool", + "This flow is enabled": "This flow is enabled", + "Enable this flow to make it available": "Enable this flow to make it available", + "Piece is updated successfully": "Piece is updated successfully", + "Piece is added successfully": "Piece is added successfully", + "Failed to update piece": "Failed to update piece", + "Failed to add piece": "Failed to add piece", + "Please select a connection": "Please select a connection", + "Your MCP server already has this piece": "Your MCP server already has this piece", + "+ New Connection": "+ New Connection", + "Edit Piece": "Edit Piece", + "Add Piece": "Add Piece", + "Connection": "Kapcsolat", + "MCP piece": "MCP piece", + "Failed to update piece status": "Failed to update piece status", + "Connection required": "Connection required", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.", + "piece": "piece", + "Connect your AI assistant to external services": "Connect your AI assistant to external services", + "Collapse": "Collapse", + "Show All": "Show All", + "Server URL": "Server URL", + "Hide the token for security": "Hide the token for security", + "Show the token": "Show the token", + "Generate a new token for security. This will invalidate the current URL.": "Generate a new token for security. This will invalidate the current URL.", + "URL copied to clipboard": "URL copied to clipboard", + "Copy URL to clipboard": "Copy URL to clipboard", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.", + "Max tables reached": "Max tables reached", + "You can't create more than {maxTables} tables": "You can't create more than {maxTables} tables", + "Name": "Név", + "Created": "Létrehozva", + "Delete Tables": "Delete Tables", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Are you sure you want to delete the selected tables? This action cannot be undone.", + "table": "table", + "Create and manage your tables to store your automation data": "Create and manage your tables to store your automation data", + "New Table": "New Table", + "No tables have been created yet": "No tables have been created yet", + "Create a table to get started and start managing your automation data": "Create a table to get started and start managing your automation data", + "Error deleting connections": "Error deleting connections", + "Status": "Állapot", + "Display Name": "Megjelenítési név", + "Owner": "Owner", + "App": "Alkalmazás", + "This connection is global and can be managed in the platform admin": "This connection is global and can be managed in the platform admin", + "External ID": "External ID", + "Connected At": "Connected At", + "Confirm Deletion": "Confirm Deletion", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Are you sure you want to delete the selected connections? This action cannot be undone.", + "Deleting connections may cause your Flows or MCP tools to break.": "Deleting connections may cause your Flows or MCP tools to break.", + "Manage project connections to external systems.": "Manage project connections to external systems.", + "No connections found": "No connections found", + "Come back later when you create a automation to manage your connections": "Come back later when you create a automation to manage your connections", + "Steps": "Lépések", + "Folder": "Mappa", + "Flow name": "Folyamat neve", + "No flows found": "No flows found", + "Create a workflow to start automating": "Create a workflow to start automating", + "Create and manage your flows, run history and run issues": "Create and manage your flows, run history and run issues", + "Issues": "Problémák", + "Untitled": "Cím nélküli", + "Create flow": "Create flow", + "From scratch": "Nulláról", + "Use a template": "Használj sablont", + "From local file": "From local file", + "Flow Name": "Folyamat neve", + "Count": "Szám", + "First Seen": "Első megtekintés", + "Last Seen": "Utolsó megtekintés", + "Issues in {flowDisplayName} is marked as resolved.": "Issues in {flowDisplayName} is marked as resolved.", + "Unlock Issues": "Unlock Issues", + "Track issues in your workflows and troubleshoot them.": "Track issues in your workflows and troubleshoot them.", + "No issues found": "No issues found", + "All your workflows are running smoothly.": "All your workflows are running smoothly.", + "Mark as Resolved": "Megoldottként jelölés", + "All Flows Are Turned Off": "All Flows Are Turned Off", + "Task Usage Exceeded": "Task Usage Exceeded", + "of the Allowed Limit.": "of the Allowed Limit.", + "When a project tasks limit is reached,": "When a project tasks limit is reached,", + "all flows will be turned off and you will not be able to run any flows.": "all flows will be turned off and you will not be able to run any flows.", + "Please visit": "Please visit", + "Your Plan": "Your Plan", + "and increase your task limit, which requires your payment details.": "and increase your task limit, which requires your payment details.", + "Please contact your admin to increase the project task limit.": "Please contact your admin to increase the project task limit.", + "and increase the project task limit.": "and increase the project task limit.", + "Dismiss": "Dismiss", + "Token rotated successfully": "Token rotated successfully", + "Failed to rotate token": "Failed to rotate token", + "Piece removed successfully": "Piece removed successfully", + "Failed to remove piece": "Failed to remove piece", + "Flow created successfully": "Flow created successfully", + "Failed to create flow": "Failed to create flow", + "Add Flow": "Add Flow", + "Let your AI assistant trigger automations": "Let your AI assistant trigger automations", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Connect to your hosted MCP Server using any MCP client to communicate with tools", + "MCP Server": "MCP Server", + "My Tools": "My Tools", + "Create Flow": "Create Flow", + "Note": "Note", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.", + "This URL grants access to your tools and data. Only share with trusted applications.": "This URL grants access to your tools and data. Only share with trusted applications.", + "Server Configuration": "Server Configuration", + "Hide sensitive data": "Hide sensitive data", + "Show sensitive data": "Show sensitive data", + "Create a new URL. The current one will stop working.": "Create a new URL. The current one will stop working.", + "Copy configuration": "Copy configuration", + "Configuration copied to clipboard": "Configuration copied to clipboard", + "Claude": "Claude", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "Server/Other", + "Note: MCPs only work with": "Note: MCPs only work with", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", not the web version.", + "Prerequisites:": "Prerequisites:", + "Install": "Telepítés", + "Node.js": "Node.js", + "and": "és", + "Open Settings:": "Open Settings:", + "Click the menu and select": "Click the menu and select", + "Developer": "Developer", + "Configure MCP:": "Configure MCP:", + "Click": "Click", + "Edit Config": "Edit Config", + "and paste the configuration below": "and paste the configuration below", + "Save and Restart:": "Save and Restart:", + "Save the config and restart Claude Desktop": "Save the config and restart Claude Desktop", + "Navigate to": "Navigate to", + "Cursor Settings": "Cursor Settings", + "Add Server:": "Add Server:", + "Add new global MCP server": "Add new global MCP server", + "Configure:": "Configure:", + "Paste the configuration below and save": "Paste the configuration below and save", + "Use either method:": "Use either method:", + "Go to": "Go to", + "Advanced Settings": "Advanced Settings", + "Open Command Palette and select": "Open Command Palette and select", + "Windsurf Settings Page": "Windsurf Settings Page", + "Navigate to Cascade:": "Navigate to Cascade:", + "Select": "Select", + "Cascade": "Cascade", + "in the sidebar": "in the sidebar", + "Add Server": "Add Server", + "Add custom server +": "Add custom server +", + "Copy URL": "Copy URL", + "Client Setup Instructions": "Client Setup Instructions", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "After changing connections or flows, reconnect your MCP server for changes to take effect.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.", + "icon": "icon", + "Unlock Analytics": "Unlock Analytics", + "Get insights into your platform usage and performance with our analytics dashboard": "Get insights into your platform usage and performance with our analytics dashboard", + "Active Flows": "Active Flows", + "The number of enabled flows in the platform": "The number of enabled flows in the platform", + "Active Projects": "Active Projects", + "The number of projects with at least one enabled flow": "The number of projects with at least one enabled flow", + "Active Users": "Active Users", + "The number of users logged in the last 30 days": "The number of users logged in the last 30 days", + "Out of {totalusers} total users": "Out of {totalusers} total users", + "Pieces Used": "Pieces Used", + "The number of unique pieces used across all flows": "The number of unique pieces used across all flows", + "Flows with AI": "Flows with AI", + "The number of enabled flows that use AI pieces": "The number of enabled flows that use AI pieces", + "Metrics": "Metrics", + "Executed Tasks": "Executed Tasks", + "Showing total executed tasks for specified time range": "Showing total executed tasks for specified time range", + "Tasks Usage Limit": "Tasks Usage Limit", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.", + "Number of monthly tasks": "Number of monthly tasks", + "Save changes": "Save changes", + "Limits updated successfully": "Limits updated successfully", + "Failed to update limits": "Failed to update limits", + "Failed to load billing information": "Failed to load billing information", + "Billing Amount": "Billing Amount", + "Manage Payment Details": "Manage Payment Details", + "Add Payment Details": "Add Payment Details", + "Current Task Usage": "Current Task Usage", + "Count of executed steps": "Count of executed steps", + "Billing Limit": "Billing Limit", + "Edit": "Edit", + "Add Limit": "Add Limit", + "Current Credit Usage": "Current Credit Usage", + "WebSocket Connection": "WebSocket Connection", + "Connected": "Connected", + "Disconnected": "Disconnected", + "No issues detected": "No issues detected", + "Check the status of your platform and its components": "Check the status of your platform and its components", + "System Health Status": "System Health Status", + "All systems are running smoothly": "All systems are running smoothly", + "Check the health of your worker machines": "Check the health of your worker machines", + "Workers Machine": "Workers Machine", + "This is demo data. In a real environment, this would show your actual worker machines.": "This is demo data. In a real environment, this would show your actual worker machines.", + "No workers found": "No workers found", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "You don't have any worker machines yet. Spin up new machines to execute your automations", + "IP Address": "IP Address", + "CPU Usage": "CPU Usage", + "Disk Usage": "Disk Usage", + "RAM Usage": "RAM Usage", + "Last Contact": "Last Contact", + "Configs": "Configs", + "Environment Variables": "Environment Variables", + "Websocket Connection Error": "Websocket Connection Error", + "Retry Connection": "Retry Connection", + "Update Available": "Update Available", + "Update Now": "Update Now", + "Your Universal AI needs a quick setup": "Your Universal AI needs a quick setup", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.", + "Configure": "Configure", + "Platform Alerts": "Platform Alerts", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.", + "View Alerts": "View Alerts", + "Used Tasks": "Used Tasks", + "Used AI Credits": "Used AI Credits", + "Cannot delete active project, switch to another project first": "Cannot delete active project, switch to another project first", + "Delete Projects": "Delete Projects", + "Are you sure you want to delete the selected projects?": "Are you sure you want to delete the selected projects?", + "New Project": "New Project", + "Validation error": "Validation error", + "Project has enabled flows. Please disable them first.": "Project has enabled flows. Please disable them first.", + "This project is active. Please switch to another project first.": "This project is active. Please switch to another project first.", + "Unlock Projects": "Unlock Projects", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Orchestrate your automation teams across projects with their own flows, connections and usage quotas", + "Manage your automation projects": "Manage your automation projects", + "No projects found": "No projects found", + "Start by creating projects to manage your automation teams": "Start by creating projects to manage your automation teams", + "Edit project": "Edit project", + "Name is required": "A név megadása kötelező", + "Create New Project": "Create New Project", + "Project Name": "Projekt neve", + "Id": "Id", + "Enable API Keys": "Enable API Keys", + "Create and manage API keys to access Activepieces APIs.": "Create and manage API keys to access Activepieces APIs.", + "New Api Key": "New Api Key", + "No API keys found": "No API keys found", + "Start by creating an API key to communicate with Activepieces APIs": "Start by creating an API key to communicate with Activepieces APIs", + "Delete API Key": "Delete API Key", + "Are you sure you want to delete this API key?": "Are you sure you want to delete this API key?", + "API Key": "API Key", + "API Key Created": "API Key Created", + "Create New API Key": "Create New API Key", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Please save this secret key somewhere safe and accessible. For security reasons,", + "you won't be able to view it again after closing this dialog.": "you won't be able to view it again after closing this dialog.", + "API Key Name": "API Key Name", + "Action": "Action", + "Performed By": "Performed By", + "Project": "Project", + "Unlock Audit Logs": "Unlock Audit Logs", + "Comply with internal and external security policies by tracking activities done within your account": "Comply with internal and external security policies by tracking activities done within your account", + "Track activities done within your platform": "Track activities done within your platform", + "No audit logs found": "No audit logs found", + "Come back later when you have some activity to audit": "Come back later when you have some activity to audit", + "Resource": "Resource", + "Details": "Details", + "N/A": "N/A", + "Flow Run": "Flow Run", + "Flow": "Folyamat", + "User": "User", + "Signing Key": "Signing Key", + "Project Role Management": "Project Role Management", + "Define custom roles and permissions to control what your team members can access and modify": "Define custom roles and permissions to control what your team members can access and modify", + "Define custom roles and permissions that can be assigned to your team members": "Define custom roles and permissions that can be assigned to your team members", + "New Role": "New Role", + "Contact sales to unlock custom roles": "Contact sales to unlock custom roles", + "Create New Role": "Create New Role", + "View ": "View ", + "Edit ": "Edit ", + "Role Name": "Role Name", + "Permissions": "Permissions", + "None": "None", + "Read": "Read", + "Write": "Write", + "Create": "Létrehozás", + "Email": "E-mail", + "First Name": "Keresztnév", + "Last Name": "Vezetéknév", + "Roles": "Roles", + "View the users assigned to this role": "View the users assigned to this role", + "Role": "Role", + "No users found": "No users found", + "Starting by assigning users to this role": "Starting by assigning users to this role", + "Project Role entry deleted successfully": "Project Role entry deleted successfully", + "Updated": "Frissítve", + "No project roles found": "No project roles found", + "Create custom project roles to manage permissions for platform users": "Create custom project roles to manage permissions for platform users", + "Show Users": "Show Users", + "View Role": "View Role", + "Edit Role": "Edit Role", + "Delete Role": "Delete Role", + "Project Role": "Projekt szerep", + "Unlock Embedding Through JS SDK": "Unlock Embedding Through JS SDK", + "Enable signing keys to access embedding functionalities.": "Enable signing keys to access embedding functionalities.", + "New Signing Key": "New Signing Key", + "No signing keys found": "No signing keys found", + "Create a signing key to authenticate users with embedding": "Create a signing key to authenticate users with embedding", + "Delete Signing Key": "Delete Signing Key", + "Are you sure you want to delete this signing key?": "Are you sure you want to delete this signing key?", + "Signing Key Created": "Signing Key Created", + "Create New Signing Key": "Create New Signing Key", + "Signing Key Name": "Signing Key Name", + "Allowed domains updated": "Allowed domains updated", + "Update": "Update", + "Enable": "Enable", + "Configure Allowed Domains": "Configure Allowed Domains", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.", + "Add Domain": "Add Domain", + "Allow logins through {providerName}'s single sign-on functionality.": "Allow logins through {providerName}'s single sign-on functionality.", + "Email authentication updated": "Email authentication updated", + "Enable Single Sign On": "Enable Single Sign On", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Let your users sign in with your current SSO provider or give them self serve sign up access", + "Allowed Domains": "Allowed Domains", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Allow users to authenticate with specific domains. Leave empty to allow all domains.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Allowed Email Login", + "Allow logins through email and password.": "Allow logins through email and password.", + "Single sign on settings updated": "Single sign on settings updated", + "Disable": "Disable", + "Configure {provider} SSO": "Configure {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} Client ID", + "{provider} Client Secret": "{provider} Client Secret", + "Single sign-on settings updated": "Single sign-on settings updated", + "Configure SAML 2.0 SSO": "Configure SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDP Metadata", + "IDP Certificate": "IDP Certificate", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "Base URL", + "Resource Name": "Resource Name", + "Deployment Name": "Deployment Name", + "Saving": "Saving", + "Activepieces Copilot": "Activepieces Copilot", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot is configured and ready to help your users build flows faster using AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configure Activepieces Copilot to help your users build flows faster using AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Set provider credentials that will be used by universal AI pieces, i.e Text AI.", + "AI Providers": "AI Providers", + "Copilot": "Copilot", + "Configure credentials for {providerName} AI provider.": "Configure credentials for {providerName} AI provider.", + "Update AI Provider": "Update AI Provider", + "Enable AI Provider": "Enable AI Provider", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Please enter a valid domain", + "Your changes have been saved.": "A változtatásaid mentve lettek.", + "The domain is already added.": "The domain is already added.", + "Add Custom Domain": "Add Custom Domain", + "Enter a domain name without a protocol (e.g. example.com)": "Enter a domain name without a protocol (e.g. example.com)", + "Logo URL": "Logo URL", + "Icon URL": "Icon URL", + "Favicon URL": "Favicon URL", + "Default Language": "Default Language", + "Select Language": "Select Language", + "No Languages": "No Languages", + "Primary Color": "Primary Color", + "Custom Domains": "Custom Domains", + "No domains added yet.": "No domains added yet.", + "Verified": "Verified", + "Pending, please contact the support for dns verification.": "Pending, please contact the support for dns verification.", + "Are you sure you want to delete {domain}?": "Are you sure you want to delete {domain}?", + "Brand Activepieces": "Márkázott Activepieces", + "Give your users an experience that looks like you by customizing the color, logo and more": "Give your users an experience that looks like you by customizing the color, logo and more", + "Configure the appearance and SMTP settings for your platform.": "Configure the appearance and SMTP settings for your platform.", + "Invalid host": "Invalid host", + "Invalid username": "Invalid username", + "Invalid password": "Invalid password", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Invalid sender name", + "SMTP is configured": "SMTP is configured", + "Mail Server": "Mail Server", + "Set up your SMTP settings to send emails from your domain.": "Set up your SMTP settings to send emails from your domain.", + "Disable Mail Server": "Disable Mail Server", + "Are you sure you want to disable your mail server?": "Are you sure you want to disable your mail server?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "This will stop you from sending emails for issues, quota limits, invitations and forgot password.", + "mail server": "mail server", + "Host": "Host", + "Port": "Port", + "Username": "Username", + "Password": "Jelszó", + "Sender Email": "Sender Email", + "Sender Name": "Sender Name", + "Enable Global Connections": "Enable Global Connections", + "Manage platform-wide connections to external systems.": "Manage platform-wide connections to external systems.", + "No global connections found": "No global connections found", + "Create a global connection that can be shared to multiple projects": "Create a global connection that can be shared to multiple projects", + "License key is invalid": "License key is invalid", + "Invalid license key": "Invalid license key", + "License activated!": "License activated!", + "Activate License Key": "Activate License Key", + "Let the magic begin!": "Let the magic begin!", + "Activate": "Activate", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ", + "This feature is not available in your current edition. ": "This feature is not available in your current edition. ", + "Learn how to upgrade": "Learn how to upgrade", + "Activate your platform and unlock enterprise features": "Activate your platform and unlock enterprise features", + "Activate License": "Activate License", + "Expiration": "Expiration", + "Valid until": "Valid until", + "Expired": "Expired", + "Expires soon": "Expires soon", + "Features": "Features", + "Applying Tags...": "Applying Tags...", + "Tags applied.": "Tags applied.", + "Tag created": "Tag created", + "Tag": "Tag", + "Tags": "Tags", + "Control Pieces": "Kontroll darabok", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Show the pieces that matter most to your users and hide the ones you don't like.", + "Manage the pieces that are available to your users": "Manage the pieces that are available to your users", + "Start by installing pieces that you want to use in your automations": "Start by installing pieces that you want to use in your automations", + "Piece Name": "Darab neve", + "Hide this piece from all projects": "Hide this piece from all projects", + "Show this piece for all projects": "Show this piece for all projects", + "Unpin this piece": "Unpin this piece", + "Pin this piece": "Pin this piece", + "Pieces synced": "Pieces synced", + "Pieces have been synced from the activepieces cloud.": "Pieces have been synced from the activepieces cloud.", + "OAuth2 Credentials Deleted": "OAuth2 Credentials Deleted", + "OAuth2 Credentials Updated": "OAuth2 Credentials Updated", + "Configure OAuth2 APP": "Configure OAuth2 APP", + "Delete OAuth2 APP": "Delete OAuth2 APP", + "Templates deleted successfully": "Templates deleted successfully", + "Delete Templates": "Delete Templates", + "Are you sure you want to delete the selected templates?": "Are you sure you want to delete the selected templates?", + "New Template": "New Template", + "Unlock Templates": "Unlock Templates", + "Convert the most common automations into reusable templates 1 click away from your users": "Convert the most common automations into reusable templates 1 click away from your users", + "Convert the most common automations into reusable templates": "Convert the most common automations into reusable templates", + "No templates found": "No templates found", + "Create a template for your user to inspire them": "Create a template for your user to inspire them", + "Edit template": "Edit template", + "Template is required": "Template is required", + "Update New Template": "Update New Template", + "Create New Template": "Create New Template", + "Template Name": "Template Name", + "Description": "Leírás", + "Template Description": "Template Description", + "Blog URL": "Blog URL", + "Template Blog URL": "Template Blog URL", + "Template": "Template", + "Invalid JSON": "Invalid JSON", + "User deleted successfully": "User deleted successfully", + "User activated successfully": "User activated successfully", + "User deactivated successfully": "User deactivated successfully", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Manage your users and their access to your projects", + "Start inviting users to your project": "Start inviting users to your project", + "External Id": "External Id", + "Admin": "Adminisztrátor", + "Member": "Member", + "Activated": "Activated", + "Deactivated": "Deactivated", + "Edit user": "Edit user", + "Admin cannot be deactivated": "Admin cannot be deactivated", + "Deactivate user": "Deactivate user", + "Activate user": "Activate user", + "Delete User": "Delete User", + "Are you sure you want to delete this user?": "Are you sure you want to delete this user?", + "Delete user": "Delete user", + "Update User Role": "Update User Role", + "Meeting Summary Flow": "Meeting Summary Flow", + "Added new features and fixed bugs": "Added new features and fixed bugs", + "Flows Changes": "Flows Changes", + "Connections Changes": "Connections Changes", + "New connections are placeholders and need to be reconnected again": "New connections are placeholders and need to be reconnected again", + "renamed to": "renamed to", + "Tables Changes": "Tables Changes", + "No changes to apply": "No changes to apply", + "Apply Changes": "Apply Changes", + "Create Git Release": "Create Git Release", + "Create Project Release": "Create Project Release", + "Create Rollback to": "Create Rollback to", + "Source": "Source", + "Rollback": "Rollback", + "Imported At": "Imported At", + "Imported By": "Imported By", + "Track and manage your project version history and deployments. ": "Track and manage your project version history and deployments. ", + "Environments & Releases": "Environments & Releases", + "Project Releases": "Project Releases", + "Create Release": "Create Release", + "From Git": "From Git", + "From Project": "From Project", + "No project releases found": "No project releases found", + "Create a project release to get started": "Create a project release to get started", + "Please select project": "Please select project", + "No Changes Found": "No Changes Found", + "There are no differences to apply": "There are no differences to apply", + "Please select a project": "Please select a project", + "Search projects...": "Search projects...", + "Review Changes": "Változtatások átnézése", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Imported by", + "from": "from", + "No description provided": "No description provided", + "Invitation only sign up": "Invitation only sign up", + "Please ask your administrator to add you to the organization.": "Please ask your administrator to add you to the organization.", + "Something went wrong, please try again.": "Something went wrong, please try again.", + "Please try again.": "Please try again.", + "Please enter a valid email address": "Kérlek, adj meg egy érvényes e-mail címet", + "The email is already added.": "Az e-mail már hozzá lett adva.", + "Add email": "E-mail hozzáadása", + "Only project admins can do this": "Only project admins can do this", + "Add Alert Email": "Értesítési e-mail hozzáadása", + "Enter the email address to receive alerts.": "Add meg az értesítési e-mail címet.", + "Add Email": "E-mail hozzáadása", + "Emails": "E-mailek", + "Add email addresses to receive alerts.": "Add hozzá az e-mail címeket az értesítések fogadásához.", + "No emails added yet.": "Még nincs hozzáadva e-mail cím.", + "Choose what you want to be notified about.": "Válaszd ki, hogy miről szeretnél értesítést kapni.", + "Project and alert permissions are required to change this setting.": "Project and alert permissions are required to change this setting.", + "Every Failed Run": "Minden sikertelen futtatás", + "Get an email alert when a flow fails.": "Értesítést kapsz e-mailben, ha a folyamat nem sikerül.", + "Get an email alert when a new issue created.": "Értesítést kapsz e-mailben, amikor új probléma keletkezik.", + "Never": "Soha", + "Turn off email notifications.": "Kapcsolja ki az e-mail értesítéseket.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Testre szabhatod az alkalmazás megjelenését. Automatikusan válts a nappali és éjjeli téma között.", + "Select the theme for the dashboard.": "Válaszd ki a téma típusát a vezérlőpulthoz.", + "Light": "Világos", + "Dark": "Sötét", + "Select the language that will be used in the dashboard.": "Válaszd ki a nyelvet, amely a vezérlőpulton lesz használva.", + "Select language": "Válassz nyelvet", + "Search language...": "Nyelv keresése...", + "No language found.": "Nincs nyelv találva.", + "Help us translate Activepieces to your language.": "Segíts lefordítani az Activepieces-t a te nyelvedre.", + "Learn more": "Tudj meg többet", + "Git Connection Removed": "Git Connection Removed", + "Your Git repository has been successfully disconnected": "Your Git repository has been successfully disconnected", + "Enable Environments": "Enable Environments", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Deploy flows across development, staging and production environments with version control and team collaboration", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Connect to Git to enable version control, backup your flows, and manage multiple environments. ", + "Repository URL": "Repository URL", + "Not connected": "Not connected", + "Project Folder": "Project Folder", + "Releases Enabled": "Releases Enabled", + "You have successfully enabled releases": "You have successfully enabled releases", + "Enable releases to easily create and manage project releases.": "Enable releases to easily create and manage project releases.", + "The external ID is already taken.": "The external ID is already taken.", + "Manage general settings for your project.": "Általános beállítások kezelése a projektedhez.", + "Used to identify the project based on your SaaS ID": "Used to identify the project based on your SaaS ID", + "org-3412321": "org-3412321", + "Delete {name}": "Törlés {name}", + "This will permanently delete this piece, all steps using it will fail.": "Ez véglegesen törli ezt a darabot, és minden lépés, amely ezt használja, hibás lesz.", + "Add a piece to your project that you want to use in your automations": "Add a piece to your project that you want to use in your automations", + "Pieces list updated": "Pieces list updated", + "Manage Pieces": "Manage Pieces", + "Choose which pieces you want to be available for your current project users": "Choose which pieces you want to be available for your current project users", + "Unlock Team Permissions": "Unlock Team Permissions", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial", + "Project Members": "Projekt tagjai", + "Invite your team members to collaborate.": "Hívd meg csapattagjaidat az együttműködésre.", + "No members are added to this project.": "Nincs tag hozzáadva ehhez a projekthez.", + "Pending Invitations": "Függő meghívók", + "No pending invitation.": "Nincs függő meghívó.", + "templateId is missing": "A sablon azonosító hiányzik", + "Me Only": "Me Only", + "Unresolved": "Unresolved", + "Resolved": "Resolved", + "Title": "Title", + "Date Created": "Date Created", + "Manage todos for your project that are created by automations": "Manage todos for your project that are created by automations", + "No todos found": "No todos found", + "You do not have any pending todos. Great job!": "You do not have any pending todos. Great job!", + "Write a comment...": "Write a comment...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "This feature is still under testing and might be changed often", + "Failed to copy to clipboard": "Nem sikerült másolni a vágólapra", + "{number} items selected": "{number} items selected", + "Select All": "Select All", + "No results found.": "Nincs találat.", + "useMultiSelect must be used within MultiSelectProvider": "A useMultiSelect csak a MultiSelectProvider-en belül használható", + "Unset": "Unset", + "Refresh": "Refresh", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} további", + "Removed {entityName}": "Eltávolított {entityName}", + "Download File": "Fájl letöltése", + "Copied to clipboard": "Másolva a vágólapra", + "File is not available after execution.": "A fájl végrehajtás után nem érhető el.", + "Available for Projects": "Available for Projects", + "Select projects": "Select projects", + "No items": "No items", + "Previous": "Previous", + "to": "to", + "Last Week": "Last Week", + "Last Month": "Last Month", + "Last 3 Months": "Last 3 Months", + "Last 6 Months": "Last 6 Months", + "Next 7 days": "Next 7 days", + "Next 30 days": "Next 30 days", + "Next 90 days": "Next 90 days", + "Next 180 days": "Next 180 days", + "Select Time Range": "Select Time Range", + "Clear": "Clear", + "Download": "Download", + "Go to Dashboard": "Go to Dashboard", + "Select a file": "Select a file", + "Press space to separate values": "Press space to separate values", + "AM": "AM", + "PM": "PM", + "Already have an account?": "Már van fiókod?", + "Sign in": "Bejelentkezés", + "Don't have an account?": "Nincs fiókod?", + "Sign up": "Regisztrálj", + "Welcome Back!": "Üdv újra!", + "Enter your email below to sign in to your account": "Add meg az e-mailedet alul, hogy bejelentkezhess a fiókodra", + "Let's Get Started!": "Kezdjük el!", + "Create your account and start flowing!": "Hozd létre a fiókodat és kezdj el dolgozni!", + "Your password was changed successfully": "A jelszavad sikeresen meg lett változtatva", + "Your password reset request has expired, please request a new one": "A jelszó visszaállítási kérelmed lejárt, kérj újat", + "Reset Password": "Jelszó visszaállítása", + "Enter your new password": "Add meg az új jelszavad", + "Password is required": "Jelszó megadása szükséges", + "Verification email resent, if previous one expired.": "Verification email resent, if previous one expired.", + "Password reset link resent, if previous one expired.": "Password reset link resent, if previous one expired.", + "We sent you a link to complete your registration to": "We sent you a link to complete your registration to", + "We sent you a link to reset your password to": "We sent you a link to reset your password to", + "Didn't receive an email or it expired?": "Didn't receive an email or it expired?", + "Resend": "Újraküldés", + "Please enter your email": "Kérlek, add meg az e-mailedet", + "Check Your Inbox": "Ellenőrizd a Beérkező leveleidet", + "If the user exists we'll send you an email with a link to reset your password.": "Ha a felhasználó létezik, küldünk egy e-mailt a jelszó visszaállító linkjével.", + "Send Password Reset Link": "Jelszó visszaállító link küldése", + "Back to sign in": "Vissza a bejelentkezéshez", + "Email is invalid": "Az e-mail érvénytelen", + "Something went wrong, please try again later": "Valami hiba történt, kérlek próbáld később", + "Invalid email or password": "Érvénytelen e-mail vagy jelszó", + "User has been deactivated": "User has been deactivated", + "Email domain is disallowed": "Email domain is disallowed", + "Email authentication has been disabled": "Email authentication has been disabled", + "Forgot your password?": "Elfelejtetted a jelszavad?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "Sign up is restricted. You need an invitation to join. Please contact the administrator.", + "Email is already used": "Az e-mail már használatban van", + "Email authentication is disabled": "Email authentication is disabled", + "First name is required": "A keresztnevet megadni szükséges", + "Last name is required": "A vezetéknév megadása szükséges", + "Email is required": "Az e-mail megadása kötelező", + "Receive updates and newsletters from activepieces": "Frissítések és hírlevelek fogadása az activepieces-től", + "By creating an account, you agree to our": "Fiók létrehozásával elfogadod a következőket", + "terms of service": "szolgáltatási feltételek", + "privacy policy": "adatvédelmi szabályzat", + "Sign up With": "Sign up With", + "Google": "Google", + "Sign in With": "Sign in With", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "Email has been verified. You will be redirected to sign in...", + "Verifying email...": "E-mail ellenőrzés folyamatban...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "invitation has expired, once you sign in again you will be able to resend the verification email.", + "Redirecting to sign in...": "Redirecting to sign in...", + "Password must contain at least one special character": "A jelszónak legalább egy speciális karaktert kell tartalmaznia", + "Password must contain at least one lowercase letter": "A jelszónak legalább egy kisbetűt kell tartalmaznia", + "Password must contain at least one uppercase letter": "A jelszónak legalább egy nagybetűt kell tartalmaznia", + "Password must contain at least one number": "A jelszónak legalább egy számot kell tartalmaznia", + "8-64 Characters": "8-64 karakter", + "Special Character": "Speciális karakter", + "Lowercase": "Kisbetű", + "Uppercase": "Nagybetű", + "Number": "Szám", + "Connection has been updated.": "Connection has been updated.", + "Edit Global Connection": "Edit Global Connection", + "Connection has been renamed.": "Connection has been renamed.", + "New Connection Name": "New Connection Name", + "Connection name already used": "Connection name already used", + "Please select at least one project": "Please select at least one project", + "Run Succeeded": "Futtatás sikeres", + "Run Failed": "Futtatás sikertelen", + "Flow Run is paused": "A folyamat futtatása szüneteltetve van", + "Run Failed due to quota exceeded": "Futtatás sikertelen a kvóta túllépése miatt", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Run failed due to exceeding the memory limit of {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "A futtatás meghaladta a {timeout} másodpercet, próbáld meg optimalizálni a lépéseidet.", + "Run failed for an unknown reason, contact support.": "A futtatás ismeretlen okból hibás lett, lépj kapcsolatba a támogatással.", + "Unknown": "Ismeretlen", + "Exit Run": "Kilépés a futtatásból", + "Select shown": "Select shown", + "Select all": "Select all", + "Start Time": "Kezdés ideje", + "Runs replayed successfully": "Runs replayed successfully", + "Retry": "Retry", + "all except": "all except", + "all": "all", + "Only failed runs can be retried from failed step": "Only failed runs can be retried from failed step", + "No flow runs found": "No flow runs found", + "Come back later when your automations start running": "Come back later when your automations start running", + "Step running": "Lépés futtatása", + "Step paused": "Lépés szüneteltetve", + "Step Stopped": "Lépés leállítva", + "Step Succeeded": "Lépés sikeres", + "Step Failed": "Lépés sikertelen", + "Please publish flow first": "Kérlek először publikáld a folyamatot", + "Flow is on": "A folyamat be van kapcsolva", + "Flow is off": "A folyamat ki van kapcsolva", + "Permission Needed": "Engedély szükséges", + "Draft Version": "Vázlat verzió", + "Published Version": "Published Version", + "Locked Version": "Zárolt verzió", + "flowsImported": "{flowsCount, plural, =0 {Nincs importált folyamat} =1 {1 folyamat sikeresen importálva} other {Folyamatok sikeresen importálva}}", + "Template file is invalid": "Template file is invalid", + "No valid templates found. The following files failed to import: ": "No valid templates found. The following files failed to import: ", + "Please select a file first": "Please select a file first", + "Unsupported file type": "Unsupported file type", + "Import Flow": "Folyamat importálása", + "Warning": "Warning", + "Importing a flow will overwrite your current one.": "Importing a flow will overwrite your current one.", + "Select a folder": "Select a folder", + "Folders": "Mappák", + "Please select a folder": "Kérlek válassz egy mappát", + "Moved flows successfully": "Moved flows successfully", + "Move Selected Flows": "Move Selected Flows", + "Select Folder": "Válassz mappát", + "No Folders": "Nincsenek mappák", + "Flow has been renamed.": "A folyamat át lett nevezve.", + "New Flow Name": "Új folyamat név", + "Use Template": "Sablon használata", + "Browse Templates": "Sablonok böngészése", + "Search templates": "Sablonok keresése", + "No templates found, try adjusting your search": "No templates found, try adjusting your search", + "Read more about this template in": "Tudj meg többet erről a sablonról itt:", + "this blog!": "ebben a blogban!", + "Share Template": "Sablon megosztása", + "Generate or update a template link for the current flow to easily share it with others.": "Generálj vagy frissíts egy sablon linket a jelenlegi folyamathoz, hogy könnyedén megoszthasd másokkal.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "A sablon nem tartalmaz hitelesítő adatokat a kapcsolat mezőiben, így biztosítva van az érzékeny információk védelme.", + "A short description of the template": "A sablon rövid leírása", + "Flow Is In Use": "Flow Is In Use", + "Flow is being used by another user, please try again later.": "Flow is being used by another user, please try again later.", + "Flow has been published.": "A folyamat közzétéve lett.", + "Flows have been exported.": "Flows have been exported.", + "Run": "Futtatás", + "Real time flow": "Valós idejű folyamat", + "Flow can't be published with empty trigger {name}": "A folyamat nem publikálható üres triggerrel {name}", + "Please contact support as your published flow has a problem": "Kérjük vedd fel a kapcsolatot a támogatással, mert a közzétett folyamatban probléma van", + "Actions": "Műveletek", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.", + "You are on a development branch, this will not delete the flows from the remote repository.": "You are on a development branch, this will not delete the flows from the remote repository.", + "Please enter folder name": "Kérlek, add meg a mappa nevét", + "Added folder successfully": "Mappa sikeresen hozzáadva", + "The folder name already exists.": "A mappa neve már létezik.", + "New Folder": "Új mappa", + "Folder Name": "Mappa neve", + "Loading...": "Betöltés...", + "Delete {folderName}": "Delete {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "Ha törlöd ezt a mappát, a folyamatait megőrizzük és Kategorizálatlan mappába helyezzük őket.", + "All flows": "Összes folyamat", + "Please enter a folder name": "Kérlek add meg a mappa nevét", + "Renamed flow successfully": "A folyamat átnevezése sikerült", + "Folder name already used": "Folder name already used", + "New Folder Name": "Új mappa név", + "Connected successfully": "Sikeres csatlakozás", + "Connect Git": "Git csatlakoztatása", + "Remote URL": "Távoli URL", + "Folder name is the name of the folder where the project will be stored or fetched.": "A mappa neve az a mappa, ahol a projekt tárolódik vagy ahonnan betöltődik.", + "SSH Private Key": "SSH Privát kulcs", + "The SSH private key to use for authentication.": "Az SSH privát kulcs, amelyet hitelesítéshez használhatsz.", + "Only published flows can be pushed to Git": "Only published flows can be pushed to Git", + "Pushed successfully": "Sikeres push", + "Invalid Operation": "Invalid Operation", + "Commit Message": "Commit üzenet", + "Enter a commit message to describe the changes you want to push.": "Írj egy commit üzenetet, hogy leírd a változtatásokat, amelyeket pusholni szeretnél.", + "Push": "Push", + "This field is required": "This field is required", + "Your submission was successfully received.": "A bejegyzésed sikeresen megérkezett.", + "Flow not found": "Folyamat nem található", + "The flow you are trying to submit to does not exist.": "A folyamat, amelyhez be akarsz küldeni, nem létezik.", + "The flow failed to execute.": "A folyamat végrehajtása nem sikerült.", + "Submit": "Beküldés", + "issues-notification": "problémák-értesítés", + "Please select a package type": "Kérlek válassz egy csomag típust", + "package.json not found in archive": "package.json not found in archive", + "Error processing archive file": "Error processing archive file", + "Please upload a .tgz file": "Please upload a .tgz file", + "Piece name is required for NPM Registry": "Piece name is required for NPM Registry", + "Piece version is required for NPM Registry": "Piece version is required for NPM Registry", + "Piece installed": "Darab telepítve", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "A piece with this name and version is already installed. Please update the version number in package.json and try again.", + "Install Piece": "Darab telepítése", + "Install a piece": "Darab telepítése", + "Package Type": "Csomag típus", + "NPM Registry": "NPM regisztráció", + "Packed Archive (.tgz)": "Csomagolt archívum (.tgz)", + "Piece Version": "Darab verzió", + "Package Archive": "Csomag archívum", + "Package archive": "Csomag archívum", + "Powerful Node.js & TypeScript code with npm": "Powerful Node.js & TypeScript code with npm", + "Loop on Items": "Loop on Items", + "Split your flow into branches depending on condition(s)": "Split your flow into branches depending on condition(s)", + "Empty Trigger": "Empty Trigger", + "An internal error occurred while fetching data, please contact support": "An internal error occurred while fetching data, please contact support", + "An internal error occurred, please contact support": "An internal error occurred, please contact support", + "Custom Javascript Code": "Custom Javascript Code", + "Router": "Router", + "recordsCount": "{recordsCount, plural, =0 {0 Records} =1 {1 Record} other {# Records}}", + "selected": "selected", + "All records selected": "All records selected", + "fieldsCount": "{fieldsCount, plural, =0 {0 Fields} =1 {1 Field} other {# Fields}}", + "Saving...": "Saving...", + "Loading more...": "Loading more...", + "Export Table": "Export Table", + "Delete Records": "Delete Records", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Are you sure you want to delete the selected records? This action cannot be undone.", + "record": "record", + "records": "records", + "mm/dd/yyy": "mm/dd/yyy", + "Delete Field": "Delete Field", + "Are you sure you want to delete this field? This action cannot be undone.": "Are you sure you want to delete this field? This action cannot be undone.", + "field": "field", + "Ignored": "Ignored", + "Table": "Table", + "CSV": "CSV", + "Field": "Field", + "Please select a csv file": "Please select a csv file", + "Max file size is {maxFileSize}MB": "Max file size is {maxFileSize}MB", + "Import CSV": "Import CSV", + "Imported records will be added to the bottom of the table": "Imported records will be added to the bottom of the table", + "Any records after the limit ({maxRecords} records) will be ignored": "Any records after the limit ({maxRecords} records) will be ignored", + "CSV File": "CSV File", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support", + "Name must be unique": "Name must be unique", + "Type is required": "Type is required", + "Please add at least one option": "Please add at least one option", + "New Field": "New Field", + "Options": "Options", + "Name is already taken": "Name is already taken", + "Table renamed": "Table renamed", + "Table name": "Table name", + "Team Invitation Accepted": "Csapat meghívó elfogadva", + "Thank you for accepting the invitation. We are redirecting you right now...": "Köszönjük, hogy elfogadtad a meghívót. Most átirányítunk...", + "Invalid invitation token. Please try again.": "Érvénytelen meghívó token. Kérlek próbáld újra.", + "Role updated successfully": "Role updated successfully", + "Error updating role": "Error updating role", + "Please try again later": "Please try again later", + "Edit Role for": "Edit Role for", + "Select Role": "Select Role", + "Avatar": "Avatar", + "Remove {email}": "Eltávolítás {email}", + "Are you sure you want to remove this invitation?": "Biztosan el szeretnéd távolítani ezt a meghívót?", + "Please select invitation type": "Kérlek válassz meghívó típusát", + "Please select platform role": "Kérlek válassz platform szerepet", + "Invitation sent successfully": "Meghívó sikeresen elküldve", + "Please select a project role": "Please select a project role", + "Invitation link copied successfully": "Meghívó link sikeresen másolva", + "Invite User": "Felhasználó meghívása", + "Invitation Link": "Meghívó link", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Kérlek másold ki az alábbi linket, és oszd meg a felhasználóval, akit meg szeretnél hívni, a meghívó 24 órán belül lejár.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Add meg a felhasználó e-mail címét, akit meg szeretnél hívni, a meghívó 24 órán belül lejár.", + "Invite To": "Meghívás ide", + "Entire Platform": "Az egész platform", + "Select Project Role": "Select Project Role", + "Invite": "Meghívás", + "Platform Role": "Platform szerep", + "Select a platform role": "Válassz egy platform szerepet", + "Are you sure you want to remove this member?": "Biztosan el akarod távolítani ezt a tagot?", + "Editor": "Szerkesztő", + "Operator": "Működtető", + "Viewer": "Néző", + "Select a project role": "Válassz egy projekt szerepet", + "Steps in this flow": "Lépések ebben a folyamatban", + "Invalid Access": "Invalid Access", + "Either the project does not exist or you do not have access to it.": "Either the project does not exist or you do not have access to it." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/hy/translation.json b/packages/react-ui/public/locales/hy/translation.json new file mode 100644 index 0000000..7f9225f --- /dev/null +++ b/packages/react-ui/public/locales/hy/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Հրապարակել", + "Latest version is published": "Վերջին տարբերակը հրապարակվել է", + "Your flow has incomplete steps": "Your flow has incomplete steps", + "Edit Flow": "Խմբագրել", + "View Draft": "View Draft", + "Uncategorized": "Uncategorized", + "Go to folder": "Գնալ թղթապանակ", + "Support": "Աջակցություն", + "Runs": "Runs", + "Run Logs": "Գործարկման Log-եր", + "Versions": "Versions", + "Versions History": "Versions History", + "Error generating code": "Սխալ կոդի գեներացման ժամանակ", + "AI Copilot": "AI Copilot", + "i.e Calculate the sum of a list...": "i.e Calculate the sum of a list...", + "Send": "Ուղարկել", + "Generating Code": "Generating Code", + "Hello there! I am here to generate code that helps with your flow": "Hello there! I am here to generate code that helps with your flow", + "Here are examples of what I am best used for: ": "Here are examples of what I am best used for: ", + "Text Processing": "Text Processing", + "Process strings, dates and data": "Process strings, dates and data", + "Data Operations": "Data Operations", + "Change data from one format to another": "Change data from one format to another", + "Calculations": "Calculations", + "Handle math and statistics": "Handle math and statistics", + "API Integration": "API Integration", + "Connect with external services. Best for simple integrations currently.": "Connect with external services. Best for simple integrations currently.", + "What would you like me to help you with?": "What would you like me to help you with?", + "Insert": "Տեղադրել", + "Data Selector": "Data Selector", + "Search": "Որոնել", + "No matching data": "Համապատասխան տվյալներ չկան", + "Try adjusting your search": "Try adjusting your search", + "This trigger needs to have data loaded from your account, to use as sample data.": "This trigger needs to have data loaded from your account, to use as sample data.", + "This step needs to be tested in order to view its data.": "This step needs to be tested in order to view its data.", + "Go to Trigger": "Go to Trigger", + "Go to Step": "Go to Step", + "Select Mode": "Select Mode", + "Move Mode": "Move Mode", + "Reset Zoom": "Reset Zoom", + "Zoom In": "Zoom In", + "Zoom Out": "Zoom Out", + "Fit to View": "Fit to View", + "Replace": "Replace", + "Copy": "Copy", + "Duplicate": "Duplicate", + "Paste After Last Step": "Paste After Last Step", + "Paste Inside Loop": "Paste Inside Loop", + "Paste After": "Paste After", + "Paste Inside...": "Paste Inside...", + "New Branch": "New Branch", + "Paste Inside Branch": "Paste Inside Branch", + "Delete": "Delete", + "Duplicate Branch": "Duplicate Branch", + "Delete Branch": "Delete Branch", + "Invalid Move": "Invalid Move", + "The destination location is a child of the dragged step": "The destination location is a child of the dragged step", + "End": "End", + "Skipped": "Skipped", + "Incomplete settings": "Incomplete settings", + "logo": "logo", + "Step Icon": "Step Icon", + "Branch": "Branch", + "incompleteSteps": "{invalidSteps, plural, =0 {no incomplete steps} =1 {Complete 1 step} other {Complete # steps}}", + "Test Flow": "Test Flow", + "Please test the trigger first": "Please test the trigger first", + "View Only": "View Only", + "Use as Draft": "Use as Draft", + "Are you sure?": "Are you sure?", + "Your current draft version will be overwritten with": "Your current draft version will be overwritten with", + "version #": "version #", + "Cancel": "Cancel", + "Confirm": "Confirm", + "Version": "Version", + "Viewing": "Viewing", + "View": "View", + "Version History": "Տարբերակների պատմություն", + "Error, please try again.": "Error, please try again.", + "Continue on Failure": "Continue on Failure", + "Enable this option to skip this step and continue the flow normally if it fails.": "Enable this option to skip this step and continue the flow normally if it fails.", + "Retry on Failure": "Retry on Failure", + "Automatically retry up to four attempts when failed.": "Automatically retry up to four attempts when failed.", + "Remove": "Remove", + "Add Item": "Add Item", + "File Input": "File Input", + "Date Input": "Date Input", + "Dynamic value": "Dynamic value", + "Select an option": "Select an option", + "Unexpected error, please retry": "Unexpected error, please retry", + "Unexpected error, please refresh the page or contact support": "Unexpected error, please refresh the page or contact support", + "Name can only contain letters, numbers and underscores": "Name can only contain letters, numbers and underscores", + "Ask AI": "Ask AI", + "Create Todo Guide": "Create Todo Guide", + "Where would you like the todo to be reviewed?": "Where would you like the todo to be reviewed?", + "Activepieces Todos": "Activepieces Todos", + "Users will manage tasks directly in Activepieces": "Users will manage tasks directly in Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.", + "External Channel (Slack, Teams, Email, ...)": "External Channel (Slack, Teams, Email, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.", + "Preview (Activepieces Todos)": "Preview (Activepieces Todos)", + "Preview (External channel)": "Preview (External channel)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "You can add the channel before the Wait Step, and configure the logic in the Router step", + "Add Steps": "Add Steps", + "All": "All", + "AI": "AI", + "Core": "Core", + "Apps": "Apps", + "Not available as trigger": "Not available as trigger", + "Not available as action": "Not available as action", + "Let our AI assistant help you out": "Let our AI assistant help you out", + "Or": "Or", + "Request Piece": "Request Piece", + "No pieces found": "No pieces found", + "Please select a piece first": "Please select a piece first", + "All Iterations": "All Iterations", + "Duration": "Duration", + "Input": "Input", + "Output": "Output", + "There are no logs captured for this run.": "There are no logs captured for this run.", + "Logs are kept for {days} days after execution and then deleted.": "Logs are kept for {days} days after execution and then deleted.", + "Run Details": "Run Details", + "Iteration": "Iteration", + "Done": "Done", + "Took": "Took", + "Running": "Running", + "on latest version": "on latest version", + "from failed step": "from failed step", + "Recent Runs": "Recent Runs", + "No runs found": "No runs found", + "Close": "Close", + "OR": "OR", + "And If": "And If", + "+ And": "+ And", + "+ Or": "+ Or", + "(Text) Contains": "(Text) Contains", + "(Text) Does not contain": "(Text) Does not contain", + "(Text) Exactly matches": "(Text) Exactly matches", + "(Text) Does not exactly match": "(Text) Does not exactly match", + "(Text) Starts with": "(Text) Starts with", + "(Text) Does not start with": "(Text) Does not start with", + "(Text) Ends with": "(Text) Ends with", + "(Text) Does not end with": "(Text) Does not end with", + "(List) Contains": "(List) Contains", + "(List) Does not contain": "(List) Does not contain", + "(Number) Is greater than": "(Number) Is greater than", + "(Number) Is less than": "(Number) Is less than", + "(Number) Is equal to": "(Number) Is equal to", + "(Date/time) After": "(Date/time) After", + "(Date/time) Before": "(Date/time) Before", + "(Date/time) Equals": "(Date/time) Equals", + "(Boolean) Is true": "(Boolean) Is true", + "(Boolean) Is false": "(Boolean) Is false", + "(List) Is empty": "(List) Is empty", + "(List) Is not empty": "(List) Is not empty", + "Exists": "Exists", + "Does not exist": "Does not exist", + "Incomplete condition": "Incomplete condition", + "First value": "First value", + "Second value": "Second value", + "Case sensitive": "Case sensitive", + "Execute If": "Execute If", + "The package name is required": "The package name is required", + "Success": "Հաջողված է", + "Package added successfully": "Package added successfully", + "Could not fetch package version": "Could not fetch package version", + "Add NPM Package": "Add NPM Package", + "Type the name of the npm package you want to add.": "Type the name of the npm package you want to add.", + "Package Name": "Package Name", + "The latest version will be fetched and added": "The latest version will be fetched and added", + "Add": "Add", + "Code": "Code", + "Dependencies": "Dependencies", + "Use code": "Use code", + "Add package": "Add package", + "Inputs": "Inputs", + "Edit Step Name": "Edit Step Name", + "Edit Branch Name": "Edit Branch Name", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.", + "Items": "Items", + "Select an array of items": "Select an array of items", + "Reconnect": "Reconnect", + "Select a connection": "Select a connection", + "Create Connection": "Create Connection", + "Rename": "Rename", + "Move": "Move", + "Add Branch": "Add Branch", + "Execute": "Execute", + "Only the first (left) matching branch": "Only the first (left) matching branch", + "All matching paths from left to right": "All matching paths from left to right", + "Branches": "Branches", + "{field} is required": "{field} is required", + "Tool Sample Data": "Tool Sample Data", + "Fill in the following fields to use them as sample data for the trigger.": "Fill in the following fields to use them as sample data for the trigger.", + "No input fields defined in the schema": "No input fields defined in the schema", + "Save": "Save", + "Test Environment": "Test Environment", + "Assigned to": "Assigned to", + "(Me)": "(Me)", + "Please select status to resolve the todo": "Please select status to resolve the todo", + "Resolve": "Resolve", + "Change status to resolved": "Change status to resolved", + "Send Sample Data to Webhook": "Send Sample Data to Webhook", + "Method": "Method", + "Query Params": "Query Params", + "Headers": "Headers", + "Body": "Body", + "Type": "Type", + "JSON": "JSON", + "Text": "Text", + "Form Data": "Form Data", + "Generate Sample Data": "Generate Sample Data", + "Test Step": "Test Step", + "Retest": "Retest", + "Testing Failed": "Testing Failed", + "Tested Successfully": "Tested Successfully", + "Logs": "Logs", + "There is no sample data available found for this trigger.": "There is no sample data available found for this trigger.", + "Internal error, please try again later.": "Internal error, please try again later.", + "Failed to run test step and no error message was returned": "Failed to run test step and no error message was returned", + "Please fix inputs first": "Please fix inputs first", + "No sample data available": "No sample data available", + "Old results were removed, retest for new sample data": "Old results were removed, retest for new sample data", + "Result #": "Result #", + "The sample data can be used in the next steps.": "The sample data can be used in the next steps.", + "Testing Trigger": "Testing Trigger", + "Action Required": "Action Required", + "testPieceWebhookTriggerNote": "Please go to {pieceName} and trigger {triggerName}.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Send Data to the webhook URL to generate sample data to use in the next steps", + "Test Trigger": "Test Trigger", + "Use Mock Data": "Use Mock Data", + "Load Sample Data": "Load Sample Data", + "Test Tool": "Test Tool", + "home": "home", + "Home": "Գլխավոր", + "Alerts": "Alerts", + "Releases": "Releases", + "Flows": "Flows", + "Products": "Products", + "MCP": "MCP", + "Tables": "Tables", + "Todos": "Todos", + "Push to Git": "Push to Git", + "Move To": "Move To", + "Duplicating": "Duplicating", + "Import": "Import", + "Exporting": "Exporting", + "Export": "Export", + "Share": "Share", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.", + "You are on a development branch, this will also delete the flow from the remote repository.": "You are on a development branch, this will also delete the flow from the remote repository.", + "flow": "flow", + "Community Support": "Community Support", + "Overview": "Overview", + "Projects": "Projects", + "Users": "Users", + "Setup": "Setup", + "Branding": "Branding", + "Global Connections": "Global Connections", + "Pieces": "Pieces", + "Templates": "Templates", + "License Key": "License Key", + "Security": "Security", + "Audit Logs": "Audit Logs", + "Single Sign On": "Single Sign On", + "Signing Keys": "Signing Keys", + "Project Roles": "Project Roles", + "API Keys": "API Keys", + "Infrastructure": "Infrastructure", + "Workers": "Workers", + "Health": "Health", + "Billing": "Billing", + "Settings": "Settings", + "Contact Sales": "Contact Sales", + "General": "General", + "Appearance": "Appearance", + "Team": "Team", + "Environments": "Environments", + "Project Settings": "Project Settings", + "Exit Platform Admin": "Exit Platform Admin", + "Enter Platform Admin": "Enter Platform Admin", + "Logout": "Logout", + "Misc": "Misc", + "Connections": "Connections", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "Today": "Today", + "Tasks": "Tasks", + "AI Credits": "AI Credits", + "Usage resets in": "Usage resets in", + "Manage": "Manage", + "Unlimited": "Unlimited", + "Enabled": "Enabled", + "Disabled": "Disabled", + "Could not claim the authorization code, make sure you have correct settings and try again.": "Could not claim the authorization code, make sure you have correct settings and try again.", + "Connection failed with error {msg}": "Connection failed with error {msg}", + "You don't have the permission to create a connection.": "You don't have the permission to create a connection.", + "Reconnect {displayName} Connection": "Reconnect {displayName}", + "Connect to {displayName}": "Connect to {displayName}", + "Connection Name": "Connection Name", + "Connection name": "Connection name", + "New Connection": "New Connection", + "Redirect URL": "Redirect URL", + "Client ID": "Client ID", + "Client Secret": "Client Secret", + "Connect": "Connect", + "Disconnect": "Disconnect", + "I would like to use my own App Credentials": "I would like to use my own App Credentials", + "I would like to use predefined App Credentials": "I would like to use predefined App Credentials", + "Permission needed": "Permission needed", + "Connections replaced successfully": "Connections replaced successfully", + "Error": "Error", + "Failed to replace connections": "Failed to replace connections", + "Failed to get affected flows": "Failed to get affected flows", + "Please select a piece": "Please select a piece", + "Please select a connection to replace": "Please select a connection to replace", + "Please select a connection to replace with": "Please select a connection to replace with", + "Replace Connections": "Replace Connections", + "Confirm Replacement": "Confirm Replacement", + "Replace one connection with another.": "Replace one connection with another.", + "This action requires ": "This action requires ", + "reconnecting": "reconnecting", + " any associated MCP pieces.": " any associated MCP pieces.", + "Piece": "Piece", + "Select a piece": "Select a piece", + "Connection to Replace": "Connection to Replace", + "Choose connection to replace": "Choose connection to replace", + "Replaced With": "Replaced With", + "Choose connection to replace with": "Choose connection to replace with", + "All flows will be changed to use the replaced with connection": "All flows will be changed to use the replaced with connection", + "Next": "Next", + "No flows will be affected by this change": "No flows will be affected by this change", + "Back": "Back", + "Unnamed tool": "Unnamed tool", + "This flow is enabled": "This flow is enabled", + "Enable this flow to make it available": "Enable this flow to make it available", + "Piece is updated successfully": "Piece is updated successfully", + "Piece is added successfully": "Piece is added successfully", + "Failed to update piece": "Failed to update piece", + "Failed to add piece": "Failed to add piece", + "Please select a connection": "Please select a connection", + "Your MCP server already has this piece": "Your MCP server already has this piece", + "+ New Connection": "+ New Connection", + "Edit Piece": "Edit Piece", + "Add Piece": "Add Piece", + "Connection": "Connection", + "MCP piece": "MCP piece", + "Failed to update piece status": "Failed to update piece status", + "Connection required": "Connection required", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.", + "piece": "piece", + "Connect your AI assistant to external services": "Connect your AI assistant to external services", + "Collapse": "Collapse", + "Show All": "Show All", + "Server URL": "Server URL", + "Hide the token for security": "Hide the token for security", + "Show the token": "Show the token", + "Generate a new token for security. This will invalidate the current URL.": "Generate a new token for security. This will invalidate the current URL.", + "URL copied to clipboard": "URL copied to clipboard", + "Copy URL to clipboard": "Copy URL to clipboard", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.", + "Max tables reached": "Max tables reached", + "You can't create more than {maxTables} tables": "You can't create more than {maxTables} tables", + "Name": "Name", + "Created": "Created", + "Delete Tables": "Delete Tables", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Are you sure you want to delete the selected tables? This action cannot be undone.", + "table": "table", + "Create and manage your tables to store your automation data": "Create and manage your tables to store your automation data", + "New Table": "New Table", + "No tables have been created yet": "No tables have been created yet", + "Create a table to get started and start managing your automation data": "Create a table to get started and start managing your automation data", + "Error deleting connections": "Error deleting connections", + "Status": "Status", + "Display Name": "Display Name", + "Owner": "Owner", + "App": "App", + "This connection is global and can be managed in the platform admin": "This connection is global and can be managed in the platform admin", + "External ID": "External ID", + "Connected At": "Connected At", + "Confirm Deletion": "Confirm Deletion", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Are you sure you want to delete the selected connections? This action cannot be undone.", + "Deleting connections may cause your Flows or MCP tools to break.": "Deleting connections may cause your Flows or MCP tools to break.", + "Manage project connections to external systems.": "Manage project connections to external systems.", + "No connections found": "No connections found", + "Come back later when you create a automation to manage your connections": "Come back later when you create a automation to manage your connections", + "Steps": "Steps", + "Folder": "Folder", + "Flow name": "Flow name", + "No flows found": "No flows found", + "Create a workflow to start automating": "Create a workflow to start automating", + "Create and manage your flows, run history and run issues": "Create and manage your flows, run history and run issues", + "Issues": "Issues", + "Untitled": "Untitled", + "Create flow": "Create flow", + "From scratch": "From scratch", + "Use a template": "Use a template", + "From local file": "From local file", + "Flow Name": "Flow Name", + "Count": "Count", + "First Seen": "First Seen", + "Last Seen": "Last Seen", + "Issues in {flowDisplayName} is marked as resolved.": "Issues in {flowDisplayName} is marked as resolved.", + "Unlock Issues": "Unlock Issues", + "Track issues in your workflows and troubleshoot them.": "Track issues in your workflows and troubleshoot them.", + "No issues found": "No issues found", + "All your workflows are running smoothly.": "All your workflows are running smoothly.", + "Mark as Resolved": "Mark as Resolved", + "All Flows Are Turned Off": "All Flows Are Turned Off", + "Task Usage Exceeded": "Task Usage Exceeded", + "of the Allowed Limit.": "of the Allowed Limit.", + "When a project tasks limit is reached,": "When a project tasks limit is reached,", + "all flows will be turned off and you will not be able to run any flows.": "all flows will be turned off and you will not be able to run any flows.", + "Please visit": "Please visit", + "Your Plan": "Your Plan", + "and increase your task limit, which requires your payment details.": "and increase your task limit, which requires your payment details.", + "Please contact your admin to increase the project task limit.": "Please contact your admin to increase the project task limit.", + "and increase the project task limit.": "and increase the project task limit.", + "Dismiss": "Dismiss", + "Token rotated successfully": "Token rotated successfully", + "Failed to rotate token": "Failed to rotate token", + "Piece removed successfully": "Piece removed successfully", + "Failed to remove piece": "Failed to remove piece", + "Flow created successfully": "Flow created successfully", + "Failed to create flow": "Failed to create flow", + "Add Flow": "Add Flow", + "Let your AI assistant trigger automations": "Let your AI assistant trigger automations", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Connect to your hosted MCP Server using any MCP client to communicate with tools", + "MCP Server": "MCP Server", + "My Tools": "My Tools", + "Create Flow": "Create Flow", + "Note": "Note", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.", + "This URL grants access to your tools and data. Only share with trusted applications.": "This URL grants access to your tools and data. Only share with trusted applications.", + "Server Configuration": "Server Configuration", + "Hide sensitive data": "Hide sensitive data", + "Show sensitive data": "Show sensitive data", + "Create a new URL. The current one will stop working.": "Create a new URL. The current one will stop working.", + "Copy configuration": "Copy configuration", + "Configuration copied to clipboard": "Configuration copied to clipboard", + "Claude": "Claude", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "Server/Other", + "Note: MCPs only work with": "Note: MCPs only work with", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", not the web version.", + "Prerequisites:": "Prerequisites:", + "Install": "Install", + "Node.js": "Node.js", + "and": "and", + "Open Settings:": "Open Settings:", + "Click the menu and select": "Click the menu and select", + "Developer": "Developer", + "Configure MCP:": "Configure MCP:", + "Click": "Click", + "Edit Config": "Edit Config", + "and paste the configuration below": "and paste the configuration below", + "Save and Restart:": "Save and Restart:", + "Save the config and restart Claude Desktop": "Save the config and restart Claude Desktop", + "Navigate to": "Navigate to", + "Cursor Settings": "Cursor Settings", + "Add Server:": "Add Server:", + "Add new global MCP server": "Add new global MCP server", + "Configure:": "Configure:", + "Paste the configuration below and save": "Paste the configuration below and save", + "Use either method:": "Use either method:", + "Go to": "Go to", + "Advanced Settings": "Advanced Settings", + "Open Command Palette and select": "Open Command Palette and select", + "Windsurf Settings Page": "Windsurf Settings Page", + "Navigate to Cascade:": "Navigate to Cascade:", + "Select": "Select", + "Cascade": "Cascade", + "in the sidebar": "in the sidebar", + "Add Server": "Add Server", + "Add custom server +": "Add custom server +", + "Copy URL": "Copy URL", + "Client Setup Instructions": "Client Setup Instructions", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "After changing connections or flows, reconnect your MCP server for changes to take effect.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.", + "icon": "icon", + "Unlock Analytics": "Unlock Analytics", + "Get insights into your platform usage and performance with our analytics dashboard": "Get insights into your platform usage and performance with our analytics dashboard", + "Active Flows": "Active Flows", + "The number of enabled flows in the platform": "The number of enabled flows in the platform", + "Active Projects": "Active Projects", + "The number of projects with at least one enabled flow": "The number of projects with at least one enabled flow", + "Active Users": "Active Users", + "The number of users logged in the last 30 days": "The number of users logged in the last 30 days", + "Out of {totalusers} total users": "Out of {totalusers} total users", + "Pieces Used": "Pieces Used", + "The number of unique pieces used across all flows": "The number of unique pieces used across all flows", + "Flows with AI": "Flows with AI", + "The number of enabled flows that use AI pieces": "The number of enabled flows that use AI pieces", + "Metrics": "Metrics", + "Executed Tasks": "Executed Tasks", + "Showing total executed tasks for specified time range": "Showing total executed tasks for specified time range", + "Tasks Usage Limit": "Tasks Usage Limit", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.", + "Number of monthly tasks": "Number of monthly tasks", + "Save changes": "Save changes", + "Limits updated successfully": "Limits updated successfully", + "Failed to update limits": "Failed to update limits", + "Failed to load billing information": "Failed to load billing information", + "Billing Amount": "Billing Amount", + "Manage Payment Details": "Manage Payment Details", + "Add Payment Details": "Add Payment Details", + "Current Task Usage": "Current Task Usage", + "Count of executed steps": "Count of executed steps", + "Billing Limit": "Billing Limit", + "Edit": "Edit", + "Add Limit": "Add Limit", + "Current Credit Usage": "Current Credit Usage", + "WebSocket Connection": "WebSocket Connection", + "Connected": "Connected", + "Disconnected": "Disconnected", + "No issues detected": "No issues detected", + "Check the status of your platform and its components": "Check the status of your platform and its components", + "System Health Status": "System Health Status", + "All systems are running smoothly": "All systems are running smoothly", + "Check the health of your worker machines": "Check the health of your worker machines", + "Workers Machine": "Workers Machine", + "This is demo data. In a real environment, this would show your actual worker machines.": "This is demo data. In a real environment, this would show your actual worker machines.", + "No workers found": "No workers found", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "You don't have any worker machines yet. Spin up new machines to execute your automations", + "IP Address": "IP Address", + "CPU Usage": "CPU Usage", + "Disk Usage": "Disk Usage", + "RAM Usage": "RAM Usage", + "Last Contact": "Last Contact", + "Configs": "Configs", + "Environment Variables": "Environment Variables", + "Websocket Connection Error": "Websocket Connection Error", + "Retry Connection": "Retry Connection", + "Update Available": "Update Available", + "Update Now": "Update Now", + "Your Universal AI needs a quick setup": "Your Universal AI needs a quick setup", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.", + "Configure": "Configure", + "Platform Alerts": "Platform Alerts", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.", + "View Alerts": "View Alerts", + "Used Tasks": "Used Tasks", + "Used AI Credits": "Used AI Credits", + "Cannot delete active project, switch to another project first": "Cannot delete active project, switch to another project first", + "Delete Projects": "Delete Projects", + "Are you sure you want to delete the selected projects?": "Are you sure you want to delete the selected projects?", + "New Project": "New Project", + "Validation error": "Validation error", + "Project has enabled flows. Please disable them first.": "Project has enabled flows. Please disable them first.", + "This project is active. Please switch to another project first.": "This project is active. Please switch to another project first.", + "Unlock Projects": "Unlock Projects", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Orchestrate your automation teams across projects with their own flows, connections and usage quotas", + "Manage your automation projects": "Manage your automation projects", + "No projects found": "No projects found", + "Start by creating projects to manage your automation teams": "Start by creating projects to manage your automation teams", + "Edit project": "Edit project", + "Name is required": "Name is required", + "Create New Project": "Create New Project", + "Project Name": "Project Name", + "Id": "Id", + "Enable API Keys": "Enable API Keys", + "Create and manage API keys to access Activepieces APIs.": "Create and manage API keys to access Activepieces APIs.", + "New Api Key": "New Api Key", + "No API keys found": "No API keys found", + "Start by creating an API key to communicate with Activepieces APIs": "Start by creating an API key to communicate with Activepieces APIs", + "Delete API Key": "Delete API Key", + "Are you sure you want to delete this API key?": "Are you sure you want to delete this API key?", + "API Key": "API Key", + "API Key Created": "API Key Created", + "Create New API Key": "Create New API Key", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Please save this secret key somewhere safe and accessible. For security reasons,", + "you won't be able to view it again after closing this dialog.": "you won't be able to view it again after closing this dialog.", + "API Key Name": "API Key Name", + "Action": "Action", + "Performed By": "Performed By", + "Project": "Project", + "Unlock Audit Logs": "Unlock Audit Logs", + "Comply with internal and external security policies by tracking activities done within your account": "Comply with internal and external security policies by tracking activities done within your account", + "Track activities done within your platform": "Track activities done within your platform", + "No audit logs found": "No audit logs found", + "Come back later when you have some activity to audit": "Come back later when you have some activity to audit", + "Resource": "Resource", + "Details": "Details", + "N/A": "N/A", + "Flow Run": "Flow Run", + "Flow": "Flow", + "User": "User", + "Signing Key": "Signing Key", + "Project Role Management": "Project Role Management", + "Define custom roles and permissions to control what your team members can access and modify": "Define custom roles and permissions to control what your team members can access and modify", + "Define custom roles and permissions that can be assigned to your team members": "Define custom roles and permissions that can be assigned to your team members", + "New Role": "New Role", + "Contact sales to unlock custom roles": "Contact sales to unlock custom roles", + "Create New Role": "Create New Role", + "View ": "View ", + "Edit ": "Edit ", + "Role Name": "Role Name", + "Permissions": "Permissions", + "None": "None", + "Read": "Read", + "Write": "Write", + "Create": "Create", + "Email": "Email", + "First Name": "First Name", + "Last Name": "Last Name", + "Roles": "Roles", + "View the users assigned to this role": "View the users assigned to this role", + "Role": "Role", + "No users found": "No users found", + "Starting by assigning users to this role": "Starting by assigning users to this role", + "Project Role entry deleted successfully": "Project Role entry deleted successfully", + "Updated": "Updated", + "No project roles found": "No project roles found", + "Create custom project roles to manage permissions for platform users": "Create custom project roles to manage permissions for platform users", + "Show Users": "Show Users", + "View Role": "View Role", + "Edit Role": "Edit Role", + "Delete Role": "Delete Role", + "Project Role": "Project Role", + "Unlock Embedding Through JS SDK": "Unlock Embedding Through JS SDK", + "Enable signing keys to access embedding functionalities.": "Enable signing keys to access embedding functionalities.", + "New Signing Key": "New Signing Key", + "No signing keys found": "No signing keys found", + "Create a signing key to authenticate users with embedding": "Create a signing key to authenticate users with embedding", + "Delete Signing Key": "Delete Signing Key", + "Are you sure you want to delete this signing key?": "Are you sure you want to delete this signing key?", + "Signing Key Created": "Signing Key Created", + "Create New Signing Key": "Create New Signing Key", + "Signing Key Name": "Signing Key Name", + "Allowed domains updated": "Allowed domains updated", + "Update": "Update", + "Enable": "Enable", + "Configure Allowed Domains": "Configure Allowed Domains", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.", + "Add Domain": "Add Domain", + "Allow logins through {providerName}'s single sign-on functionality.": "Allow logins through {providerName}'s single sign-on functionality.", + "Email authentication updated": "Email authentication updated", + "Enable Single Sign On": "Enable Single Sign On", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Let your users sign in with your current SSO provider or give them self serve sign up access", + "Allowed Domains": "Allowed Domains", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Allow users to authenticate with specific domains. Leave empty to allow all domains.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Allowed Email Login", + "Allow logins through email and password.": "Allow logins through email and password.", + "Single sign on settings updated": "Single sign on settings updated", + "Disable": "Disable", + "Configure {provider} SSO": "Configure {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} Client ID", + "{provider} Client Secret": "{provider} Client Secret", + "Single sign-on settings updated": "Single sign-on settings updated", + "Configure SAML 2.0 SSO": "Configure SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDP Metadata", + "IDP Certificate": "IDP Certificate", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "Base URL", + "Resource Name": "Resource Name", + "Deployment Name": "Deployment Name", + "Saving": "Saving", + "Activepieces Copilot": "Activepieces Copilot", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot is configured and ready to help your users build flows faster using AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configure Activepieces Copilot to help your users build flows faster using AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Set provider credentials that will be used by universal AI pieces, i.e Text AI.", + "AI Providers": "AI Providers", + "Copilot": "Copilot", + "Configure credentials for {providerName} AI provider.": "Configure credentials for {providerName} AI provider.", + "Update AI Provider": "Update AI Provider", + "Enable AI Provider": "Enable AI Provider", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Please enter a valid domain", + "Your changes have been saved.": "Your changes have been saved.", + "The domain is already added.": "The domain is already added.", + "Add Custom Domain": "Add Custom Domain", + "Enter a domain name without a protocol (e.g. example.com)": "Enter a domain name without a protocol (e.g. example.com)", + "Logo URL": "Logo URL", + "Icon URL": "Icon URL", + "Favicon URL": "Favicon URL", + "Default Language": "Default Language", + "Select Language": "Select Language", + "No Languages": "No Languages", + "Primary Color": "Primary Color", + "Custom Domains": "Custom Domains", + "No domains added yet.": "No domains added yet.", + "Verified": "Verified", + "Pending, please contact the support for dns verification.": "Pending, please contact the support for dns verification.", + "Are you sure you want to delete {domain}?": "Are you sure you want to delete {domain}?", + "Brand Activepieces": "Brand Activepieces", + "Give your users an experience that looks like you by customizing the color, logo and more": "Give your users an experience that looks like you by customizing the color, logo and more", + "Configure the appearance and SMTP settings for your platform.": "Configure the appearance and SMTP settings for your platform.", + "Invalid host": "Invalid host", + "Invalid username": "Invalid username", + "Invalid password": "Invalid password", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Invalid sender name", + "SMTP is configured": "SMTP is configured", + "Mail Server": "Mail Server", + "Set up your SMTP settings to send emails from your domain.": "Set up your SMTP settings to send emails from your domain.", + "Disable Mail Server": "Disable Mail Server", + "Are you sure you want to disable your mail server?": "Are you sure you want to disable your mail server?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "This will stop you from sending emails for issues, quota limits, invitations and forgot password.", + "mail server": "mail server", + "Host": "Host", + "Port": "Port", + "Username": "Username", + "Password": "Password", + "Sender Email": "Sender Email", + "Sender Name": "Sender Name", + "Enable Global Connections": "Enable Global Connections", + "Manage platform-wide connections to external systems.": "Manage platform-wide connections to external systems.", + "No global connections found": "No global connections found", + "Create a global connection that can be shared to multiple projects": "Create a global connection that can be shared to multiple projects", + "License key is invalid": "License key is invalid", + "Invalid license key": "Invalid license key", + "License activated!": "License activated!", + "Activate License Key": "Activate License Key", + "Let the magic begin!": "Let the magic begin!", + "Activate": "Activate", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ", + "This feature is not available in your current edition. ": "This feature is not available in your current edition. ", + "Learn how to upgrade": "Learn how to upgrade", + "Activate your platform and unlock enterprise features": "Activate your platform and unlock enterprise features", + "Activate License": "Activate License", + "Expiration": "Expiration", + "Valid until": "Valid until", + "Expired": "Expired", + "Expires soon": "Expires soon", + "Features": "Features", + "Applying Tags...": "Applying Tags...", + "Tags applied.": "Tags applied.", + "Tag created": "Tag created", + "Tag": "Tag", + "Tags": "Tags", + "Control Pieces": "Control Pieces", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Show the pieces that matter most to your users and hide the ones you don't like.", + "Manage the pieces that are available to your users": "Manage the pieces that are available to your users", + "Start by installing pieces that you want to use in your automations": "Start by installing pieces that you want to use in your automations", + "Piece Name": "Piece Name", + "Hide this piece from all projects": "Hide this piece from all projects", + "Show this piece for all projects": "Show this piece for all projects", + "Unpin this piece": "Unpin this piece", + "Pin this piece": "Pin this piece", + "Pieces synced": "Pieces synced", + "Pieces have been synced from the activepieces cloud.": "Pieces have been synced from the activepieces cloud.", + "OAuth2 Credentials Deleted": "OAuth2 Credentials Deleted", + "OAuth2 Credentials Updated": "OAuth2 Credentials Updated", + "Configure OAuth2 APP": "Configure OAuth2 APP", + "Delete OAuth2 APP": "Delete OAuth2 APP", + "Templates deleted successfully": "Templates deleted successfully", + "Delete Templates": "Delete Templates", + "Are you sure you want to delete the selected templates?": "Are you sure you want to delete the selected templates?", + "New Template": "New Template", + "Unlock Templates": "Unlock Templates", + "Convert the most common automations into reusable templates 1 click away from your users": "Convert the most common automations into reusable templates 1 click away from your users", + "Convert the most common automations into reusable templates": "Convert the most common automations into reusable templates", + "No templates found": "No templates found", + "Create a template for your user to inspire them": "Create a template for your user to inspire them", + "Edit template": "Edit template", + "Template is required": "Template is required", + "Update New Template": "Update New Template", + "Create New Template": "Create New Template", + "Template Name": "Template Name", + "Description": "Description", + "Template Description": "Template Description", + "Blog URL": "Blog URL", + "Template Blog URL": "Template Blog URL", + "Template": "Template", + "Invalid JSON": "Invalid JSON", + "User deleted successfully": "User deleted successfully", + "User activated successfully": "User activated successfully", + "User deactivated successfully": "User deactivated successfully", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Manage your users and their access to your projects", + "Start inviting users to your project": "Start inviting users to your project", + "External Id": "External Id", + "Admin": "Admin", + "Member": "Member", + "Activated": "Activated", + "Deactivated": "Deactivated", + "Edit user": "Edit user", + "Admin cannot be deactivated": "Admin cannot be deactivated", + "Deactivate user": "Deactivate user", + "Activate user": "Activate user", + "Delete User": "Delete User", + "Are you sure you want to delete this user?": "Are you sure you want to delete this user?", + "Delete user": "Delete user", + "Update User Role": "Update User Role", + "Meeting Summary Flow": "Meeting Summary Flow", + "Added new features and fixed bugs": "Added new features and fixed bugs", + "Flows Changes": "Flows Changes", + "Connections Changes": "Connections Changes", + "New connections are placeholders and need to be reconnected again": "New connections are placeholders and need to be reconnected again", + "renamed to": "renamed to", + "Tables Changes": "Tables Changes", + "No changes to apply": "No changes to apply", + "Apply Changes": "Apply Changes", + "Create Git Release": "Create Git Release", + "Create Project Release": "Create Project Release", + "Create Rollback to": "Create Rollback to", + "Source": "Source", + "Rollback": "Rollback", + "Imported At": "Imported At", + "Imported By": "Imported By", + "Track and manage your project version history and deployments. ": "Track and manage your project version history and deployments. ", + "Environments & Releases": "Environments & Releases", + "Project Releases": "Project Releases", + "Create Release": "Create Release", + "From Git": "From Git", + "From Project": "From Project", + "No project releases found": "No project releases found", + "Create a project release to get started": "Create a project release to get started", + "Please select project": "Please select project", + "No Changes Found": "No Changes Found", + "There are no differences to apply": "There are no differences to apply", + "Please select a project": "Please select a project", + "Search projects...": "Search projects...", + "Review Changes": "Review Changes", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Imported by", + "from": "from", + "No description provided": "No description provided", + "Invitation only sign up": "Invitation only sign up", + "Please ask your administrator to add you to the organization.": "Please ask your administrator to add you to the organization.", + "Something went wrong, please try again.": "Something went wrong, please try again.", + "Please try again.": "Please try again.", + "Please enter a valid email address": "Please enter a valid email address", + "The email is already added.": "The email is already added.", + "Add email": "Add email", + "Only project admins can do this": "Only project admins can do this", + "Add Alert Email": "Add Alert Email", + "Enter the email address to receive alerts.": "Enter the email address to receive alerts.", + "Add Email": "Add Email", + "Emails": "Emails", + "Add email addresses to receive alerts.": "Add email addresses to receive alerts.", + "No emails added yet.": "No emails added yet.", + "Choose what you want to be notified about.": "Choose what you want to be notified about.", + "Project and alert permissions are required to change this setting.": "Project and alert permissions are required to change this setting.", + "Every Failed Run": "Every Failed Run", + "Get an email alert when a flow fails.": "Get an email alert when a flow fails.", + "Get an email alert when a new issue created.": "Get an email alert when a new issue created.", + "Never": "Never", + "Turn off email notifications.": "Turn off email notifications.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Customize the appearance of the app. Automatically switch between day and night themes.", + "Select the theme for the dashboard.": "Select the theme for the dashboard.", + "Light": "Light", + "Dark": "Dark", + "Select the language that will be used in the dashboard.": "Select the language that will be used in the dashboard.", + "Select language": "Select language", + "Search language...": "Search language...", + "No language found.": "No language found.", + "Help us translate Activepieces to your language.": "Help us translate Activepieces to your language.", + "Learn more": "Learn more", + "Git Connection Removed": "Git Connection Removed", + "Your Git repository has been successfully disconnected": "Your Git repository has been successfully disconnected", + "Enable Environments": "Enable Environments", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Deploy flows across development, staging and production environments with version control and team collaboration", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Connect to Git to enable version control, backup your flows, and manage multiple environments. ", + "Repository URL": "Repository URL", + "Not connected": "Not connected", + "Project Folder": "Project Folder", + "Releases Enabled": "Releases Enabled", + "You have successfully enabled releases": "You have successfully enabled releases", + "Enable releases to easily create and manage project releases.": "Enable releases to easily create and manage project releases.", + "The external ID is already taken.": "The external ID is already taken.", + "Manage general settings for your project.": "Manage general settings for your project.", + "Used to identify the project based on your SaaS ID": "Used to identify the project based on your SaaS ID", + "org-3412321": "org-3412321", + "Delete {name}": "Delete {name}", + "This will permanently delete this piece, all steps using it will fail.": "This will permanently delete this piece, all steps using it will fail.", + "Add a piece to your project that you want to use in your automations": "Add a piece to your project that you want to use in your automations", + "Pieces list updated": "Pieces list updated", + "Manage Pieces": "Manage Pieces", + "Choose which pieces you want to be available for your current project users": "Choose which pieces you want to be available for your current project users", + "Unlock Team Permissions": "Unlock Team Permissions", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial", + "Project Members": "Project Members", + "Invite your team members to collaborate.": "Invite your team members to collaborate.", + "No members are added to this project.": "No members are added to this project.", + "Pending Invitations": "Pending Invitations", + "No pending invitation.": "No pending invitation.", + "templateId is missing": "templateId is missing", + "Me Only": "Me Only", + "Unresolved": "Unresolved", + "Resolved": "Resolved", + "Title": "Title", + "Date Created": "Date Created", + "Manage todos for your project that are created by automations": "Manage todos for your project that are created by automations", + "No todos found": "No todos found", + "You do not have any pending todos. Great job!": "You do not have any pending todos. Great job!", + "Write a comment...": "Write a comment...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "This feature is still under testing and might be changed often", + "Failed to copy to clipboard": "Failed to copy to clipboard", + "{number} items selected": "{number} items selected", + "Select All": "Select All", + "No results found.": "No results found.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect must be used within MultiSelectProvider", + "Unset": "Unset", + "Refresh": "Refresh", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} more", + "Removed {entityName}": "Removed {entityName}", + "Download File": "Download File", + "Copied to clipboard": "Copied to clipboard", + "File is not available after execution.": "File is not available after execution.", + "Available for Projects": "Available for Projects", + "Select projects": "Select projects", + "No items": "No items", + "Previous": "Previous", + "to": "to", + "Last Week": "Last Week", + "Last Month": "Last Month", + "Last 3 Months": "Last 3 Months", + "Last 6 Months": "Last 6 Months", + "Next 7 days": "Next 7 days", + "Next 30 days": "Next 30 days", + "Next 90 days": "Next 90 days", + "Next 180 days": "Next 180 days", + "Select Time Range": "Select Time Range", + "Clear": "Clear", + "Download": "Download", + "Go to Dashboard": "Go to Dashboard", + "Select a file": "Select a file", + "Press space to separate values": "Press space to separate values", + "AM": "AM", + "PM": "PM", + "Already have an account?": "Already have an account?", + "Sign in": "Sign in", + "Don't have an account?": "Don't have an account?", + "Sign up": "Sign up", + "Welcome Back!": "Welcome Back!", + "Enter your email below to sign in to your account": "Enter your email below to sign in to your account", + "Let's Get Started!": "Let's Get Started!", + "Create your account and start flowing!": "Create your account and start flowing!", + "Your password was changed successfully": "Your password was changed successfully", + "Your password reset request has expired, please request a new one": "Your password reset request has expired, please request a new one", + "Reset Password": "Reset Password", + "Enter your new password": "Enter your new password", + "Password is required": "Password is required", + "Verification email resent, if previous one expired.": "Verification email resent, if previous one expired.", + "Password reset link resent, if previous one expired.": "Password reset link resent, if previous one expired.", + "We sent you a link to complete your registration to": "We sent you a link to complete your registration to", + "We sent you a link to reset your password to": "We sent you a link to reset your password to", + "Didn't receive an email or it expired?": "Didn't receive an email or it expired?", + "Resend": "Resend", + "Please enter your email": "Please enter your email", + "Check Your Inbox": "Check Your Inbox", + "If the user exists we'll send you an email with a link to reset your password.": "If the user exists we'll send you an email with a link to reset your password.", + "Send Password Reset Link": "Send Password Reset Link", + "Back to sign in": "Back to sign in", + "Email is invalid": "Email is invalid", + "Something went wrong, please try again later": "Something went wrong, please try again later", + "Invalid email or password": "Invalid email or password", + "User has been deactivated": "User has been deactivated", + "Email domain is disallowed": "Email domain is disallowed", + "Email authentication has been disabled": "Email authentication has been disabled", + "Forgot your password?": "Forgot your password?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "Sign up is restricted. You need an invitation to join. Please contact the administrator.", + "Email is already used": "Email is already used", + "Email authentication is disabled": "Email authentication is disabled", + "First name is required": "First name is required", + "Last name is required": "Last name is required", + "Email is required": "Email is required", + "Receive updates and newsletters from activepieces": "Receive updates and newsletters from activepieces", + "By creating an account, you agree to our": "By creating an account, you agree to our", + "terms of service": "terms of service", + "privacy policy": "privacy policy", + "Sign up With": "Sign up With", + "Google": "Google", + "Sign in With": "Sign in With", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "Email has been verified. You will be redirected to sign in...", + "Verifying email...": "Verifying email...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "invitation has expired, once you sign in again you will be able to resend the verification email.", + "Redirecting to sign in...": "Redirecting to sign in...", + "Password must contain at least one special character": "Password must contain at least one special character", + "Password must contain at least one lowercase letter": "Password must contain at least one lowercase letter", + "Password must contain at least one uppercase letter": "Password must contain at least one uppercase letter", + "Password must contain at least one number": "Password must contain at least one number", + "8-64 Characters": "8-64 Characters", + "Special Character": "Special Character", + "Lowercase": "Lowercase", + "Uppercase": "Uppercase", + "Number": "Number", + "Connection has been updated.": "Connection has been updated.", + "Edit Global Connection": "Edit Global Connection", + "Connection has been renamed.": "Connection has been renamed.", + "New Connection Name": "New Connection Name", + "Connection name already used": "Connection name already used", + "Please select at least one project": "Please select at least one project", + "Run Succeeded": "Run Succeeded", + "Run Failed": "Run Failed", + "Flow Run is paused": "Flow Run is paused", + "Run Failed due to quota exceeded": "Run Failed due to quota exceeded", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Run failed due to exceeding the memory limit of {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "Run exceeded {timeout} seconds, try to optimize your steps.", + "Run failed for an unknown reason, contact support.": "Run failed for an unknown reason, contact support.", + "Unknown": "Unknown", + "Exit Run": "Exit Run", + "Select shown": "Select shown", + "Select all": "Select all", + "Start Time": "Start Time", + "Runs replayed successfully": "Runs replayed successfully", + "Retry": "Retry", + "all except": "all except", + "all": "all", + "Only failed runs can be retried from failed step": "Only failed runs can be retried from failed step", + "No flow runs found": "No flow runs found", + "Come back later when your automations start running": "Come back later when your automations start running", + "Step running": "Step running", + "Step paused": "Step paused", + "Step Stopped": "Step Stopped", + "Step Succeeded": "Step Succeeded", + "Step Failed": "Step Failed", + "Please publish flow first": "Please publish flow first", + "Flow is on": "Flow is on", + "Flow is off": "Flow is off", + "Permission Needed": "Permission Needed", + "Draft Version": "Draft Version", + "Published Version": "Published Version", + "Locked Version": "Locked Version", + "flowsImported": "{flowsCount, plural, =0 {No Flows Imported} =1 {Flow Imported Successfully} other {Flows Imported Successfully}}", + "Template file is invalid": "Template file is invalid", + "No valid templates found. The following files failed to import: ": "No valid templates found. The following files failed to import: ", + "Please select a file first": "Please select a file first", + "Unsupported file type": "Unsupported file type", + "Import Flow": "Import Flow", + "Warning": "Warning", + "Importing a flow will overwrite your current one.": "Importing a flow will overwrite your current one.", + "Select a folder": "Select a folder", + "Folders": "Folders", + "Please select a folder": "Please select a folder", + "Moved flows successfully": "Moved flows successfully", + "Move Selected Flows": "Move Selected Flows", + "Select Folder": "Select Folder", + "No Folders": "No Folders", + "Flow has been renamed.": "Flow has been renamed.", + "New Flow Name": "New Flow Name", + "Use Template": "Use Template", + "Browse Templates": "Browse Templates", + "Search templates": "Search templates", + "No templates found, try adjusting your search": "No templates found, try adjusting your search", + "Read more about this template in": "Read more about this template in", + "this blog!": "this blog!", + "Share Template": "Share Template", + "Generate or update a template link for the current flow to easily share it with others.": "Generate or update a template link for the current flow to easily share it with others.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "The template will not have any credentials in connection fields, keeping sensitive information secure.", + "A short description of the template": "A short description of the template", + "Flow Is In Use": "Flow Is In Use", + "Flow is being used by another user, please try again later.": "Flow is being used by another user, please try again later.", + "Flow has been published.": "Flow-ն հրապարակվել է", + "Flows have been exported.": "Flows have been exported.", + "Run": "Run", + "Real time flow": "Real time flow", + "Flow can't be published with empty trigger {name}": "Flow can't be published with empty trigger {name}", + "Please contact support as your published flow has a problem": "Please contact support as your published flow has a problem", + "Actions": "Actions", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.", + "You are on a development branch, this will not delete the flows from the remote repository.": "You are on a development branch, this will not delete the flows from the remote repository.", + "Please enter folder name": "Please enter folder name", + "Added folder successfully": "Added folder successfully", + "The folder name already exists.": "The folder name already exists.", + "New Folder": "New Folder", + "Folder Name": "Folder Name", + "Loading...": "Loading...", + "Delete {folderName}": "Delete {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "If you delete this folder, we will keep its flows and move them to Uncategorized.", + "All flows": "All flows", + "Please enter a folder name": "Please enter a folder name", + "Renamed flow successfully": "Renamed flow successfully", + "Folder name already used": "Folder name already used", + "New Folder Name": "New Folder Name", + "Connected successfully": "Connected successfully", + "Connect Git": "Connect Git", + "Remote URL": "Remote URL", + "Folder name is the name of the folder where the project will be stored or fetched.": "Folder name is the name of the folder where the project will be stored or fetched.", + "SSH Private Key": "SSH Private Key", + "The SSH private key to use for authentication.": "The SSH private key to use for authentication.", + "Only published flows can be pushed to Git": "Only published flows can be pushed to Git", + "Pushed successfully": "Pushed successfully", + "Invalid Operation": "Invalid Operation", + "Commit Message": "Commit Message", + "Enter a commit message to describe the changes you want to push.": "Enter a commit message to describe the changes you want to push.", + "Push": "Push", + "This field is required": "This field is required", + "Your submission was successfully received.": "Your submission was successfully received.", + "Flow not found": "Flow not found", + "The flow you are trying to submit to does not exist.": "The flow you are trying to submit to does not exist.", + "The flow failed to execute.": "The flow failed to execute.", + "Submit": "Submit", + "issues-notification": "issues-notification", + "Please select a package type": "Please select a package type", + "package.json not found in archive": "package.json not found in archive", + "Error processing archive file": "Error processing archive file", + "Please upload a .tgz file": "Please upload a .tgz file", + "Piece name is required for NPM Registry": "Piece name is required for NPM Registry", + "Piece version is required for NPM Registry": "Piece version is required for NPM Registry", + "Piece installed": "Piece installed", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "A piece with this name and version is already installed. Please update the version number in package.json and try again.", + "Install Piece": "Install Piece", + "Install a piece": "Install a piece", + "Package Type": "Package Type", + "NPM Registry": "NPM Registry", + "Packed Archive (.tgz)": "Packed Archive (.tgz)", + "Piece Version": "Piece Version", + "Package Archive": "Package Archive", + "Package archive": "Package archive", + "Powerful Node.js & TypeScript code with npm": "Powerful Node.js & TypeScript code with npm", + "Loop on Items": "Loop on Items", + "Split your flow into branches depending on condition(s)": "Split your flow into branches depending on condition(s)", + "Empty Trigger": "Empty Trigger", + "An internal error occurred while fetching data, please contact support": "An internal error occurred while fetching data, please contact support", + "An internal error occurred, please contact support": "An internal error occurred, please contact support", + "Custom Javascript Code": "Custom Javascript Code", + "Router": "Router", + "recordsCount": "{recordsCount, plural, =0 {0 Records} =1 {1 Record} other {# Records}}", + "selected": "selected", + "All records selected": "All records selected", + "fieldsCount": "{fieldsCount, plural, =0 {0 Fields} =1 {1 Field} other {# Fields}}", + "Saving...": "Saving...", + "Loading more...": "Loading more...", + "Export Table": "Export Table", + "Delete Records": "Delete Records", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Are you sure you want to delete the selected records? This action cannot be undone.", + "record": "record", + "records": "records", + "mm/dd/yyy": "mm/dd/yyy", + "Delete Field": "Delete Field", + "Are you sure you want to delete this field? This action cannot be undone.": "Are you sure you want to delete this field? This action cannot be undone.", + "field": "field", + "Ignored": "Ignored", + "Table": "Table", + "CSV": "CSV", + "Field": "Field", + "Please select a csv file": "Please select a csv file", + "Max file size is {maxFileSize}MB": "Max file size is {maxFileSize}MB", + "Import CSV": "Import CSV", + "Imported records will be added to the bottom of the table": "Imported records will be added to the bottom of the table", + "Any records after the limit ({maxRecords} records) will be ignored": "Any records after the limit ({maxRecords} records) will be ignored", + "CSV File": "CSV File", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support", + "Name must be unique": "Name must be unique", + "Type is required": "Type is required", + "Please add at least one option": "Please add at least one option", + "New Field": "New Field", + "Options": "Options", + "Name is already taken": "Name is already taken", + "Table renamed": "Table renamed", + "Table name": "Table name", + "Team Invitation Accepted": "Team Invitation Accepted", + "Thank you for accepting the invitation. We are redirecting you right now...": "Thank you for accepting the invitation. We are redirecting you right now...", + "Invalid invitation token. Please try again.": "Invalid invitation token. Please try again.", + "Role updated successfully": "Role updated successfully", + "Error updating role": "Error updating role", + "Please try again later": "Please try again later", + "Edit Role for": "Edit Role for", + "Select Role": "Select Role", + "Avatar": "Avatar", + "Remove {email}": "Remove {email}", + "Are you sure you want to remove this invitation?": "Are you sure you want to remove this invitation?", + "Please select invitation type": "Please select invitation type", + "Please select platform role": "Please select platform role", + "Invitation sent successfully": "Invitation sent successfully", + "Please select a project role": "Please select a project role", + "Invitation link copied successfully": "Invitation link copied successfully", + "Invite User": "Invite User", + "Invitation Link": "Invitation Link", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Type the email address of the user you want to invite, the invitation expires in 24 hours.", + "Invite To": "Invite To", + "Entire Platform": "Entire Platform", + "Select Project Role": "Select Project Role", + "Invite": "Invite", + "Platform Role": "Platform Role", + "Select a platform role": "Select a platform role", + "Are you sure you want to remove this member?": "Are you sure you want to remove this member?", + "Editor": "Editor", + "Operator": "Operator", + "Viewer": "Viewer", + "Select a project role": "Select a project role", + "Steps in this flow": "Steps in this flow", + "Invalid Access": "Invalid Access", + "Either the project does not exist or you do not have access to it.": "Either the project does not exist or you do not have access to it." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/id/translation.json b/packages/react-ui/public/locales/id/translation.json new file mode 100644 index 0000000..2bea5f9 --- /dev/null +++ b/packages/react-ui/public/locales/id/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Terbitkan", + "Latest version is published": "Versi terbaru telah diterbitkan", + "Your flow has incomplete steps": "Your flow has incomplete steps", + "Edit Flow": "Sunting Alur", + "View Draft": "View Draft", + "Uncategorized": "Tidak Dikategorikan", + "Go to folder": "Buka Folder", + "Support": "Dukungan", + "Runs": "Runs", + "Run Logs": "Catatan Eksekusi", + "Versions": "Versions", + "Versions History": "Versions History", + "Error generating code": "Terjadi kesalahan saat menghasilkan kode", + "AI Copilot": "AI Copilot", + "i.e Calculate the sum of a list...": "i.e Calculate the sum of a list...", + "Send": "Kirim", + "Generating Code": "Generating Code", + "Hello there! I am here to generate code that helps with your flow": "Hello there! I am here to generate code that helps with your flow", + "Here are examples of what I am best used for: ": "Here are examples of what I am best used for: ", + "Text Processing": "Text Processing", + "Process strings, dates and data": "Process strings, dates and data", + "Data Operations": "Data Operations", + "Change data from one format to another": "Change data from one format to another", + "Calculations": "Calculations", + "Handle math and statistics": "Handle math and statistics", + "API Integration": "API Integration", + "Connect with external services. Best for simple integrations currently.": "Connect with external services. Best for simple integrations currently.", + "What would you like me to help you with?": "What would you like me to help you with?", + "Insert": "Sisipkan", + "Data Selector": "Pemilih Data", + "Search": "Cari", + "No matching data": "Tidak ada data yang cocok", + "Try adjusting your search": "Coba sesuaikan pencarian Anda", + "This trigger needs to have data loaded from your account, to use as sample data.": "Pemicu ini memerlukan data yang dimuat dari akun Anda, untuk digunakan sebagai data contoh.", + "This step needs to be tested in order to view its data.": "Langkah ini perlu diuji agar dapat melihat datanya.", + "Go to Trigger": "Buka Pemicu", + "Go to Step": "Buka Langkah", + "Select Mode": "Select Mode", + "Move Mode": "Move Mode", + "Reset Zoom": "Atur Ulang Pembesaran", + "Zoom In": "Perbesar", + "Zoom Out": "Perkecil", + "Fit to View": "Sesuaikan Tampilan", + "Replace": "Replace", + "Copy": "Copy", + "Duplicate": "Duplicate", + "Paste After Last Step": "Paste After Last Step", + "Paste Inside Loop": "Paste Inside Loop", + "Paste After": "Paste After", + "Paste Inside...": "Paste Inside...", + "New Branch": "New Branch", + "Paste Inside Branch": "Paste Inside Branch", + "Delete": "Delete", + "Duplicate Branch": "Duplicate Branch", + "Delete Branch": "Delete Branch", + "Invalid Move": "Langkah Tidak Valid", + "The destination location is a child of the dragged step": "The destination location is a child of the dragged step", + "End": "Selesai", + "Skipped": "Skipped", + "Incomplete settings": "Pengaturan tidak lengkap", + "logo": "logo", + "Step Icon": "Ikon Langkah", + "Branch": "Branch", + "incompleteSteps": "{invalidSteps, plural, =0 {tidak ada langkah yang belum lengkap} =1 {Selesaikan 1 langkah} other {Selesaikan # langkah}}", + "Test Flow": "Uji Alur", + "Please test the trigger first": "Harap uji pemicu terlebih dahulu", + "View Only": "Hanya Lihat", + "Use as Draft": "Gunakan sebagai Draf", + "Are you sure?": "Apakah Anda yakin?", + "Your current draft version will be overwritten with": "Draf versi Anda saat ini akan ditimpa dengan", + "version #": "versi #", + "Cancel": "Batalkan", + "Confirm": "Konfirmasi", + "Version": "Versi", + "Viewing": "Melihat", + "View": "Lihat", + "Version History": "Riwayat Versi", + "Error, please try again.": "Terjadi kesalahan, silakan coba lagi.", + "Continue on Failure": "Lanjutkan walau Gagal", + "Enable this option to skip this step and continue the flow normally if it fails.": "Aktifkan opsi ini untuk melewati langkah ini dan melanjutkan alur secara normal jika gagal.", + "Retry on Failure": "Coba Ulang jika Gagal", + "Automatically retry up to four attempts when failed.": "Secara otomatis mencoba ulang hingga empat kali percobaan ketika gagal.", + "Remove": "Hapus", + "Add Item": "Tambahkan Item", + "File Input": "File Input", + "Date Input": "Date Input", + "Dynamic value": "Dynamic value", + "Select an option": "Pilih opsi", + "Unexpected error, please retry": "Unexpected error, please retry", + "Unexpected error, please refresh the page or contact support": "Unexpected error, please refresh the page or contact support", + "Name can only contain letters, numbers and underscores": "Name can only contain letters, numbers and underscores", + "Ask AI": "Tanya AI", + "Create Todo Guide": "Create Todo Guide", + "Where would you like the todo to be reviewed?": "Where would you like the todo to be reviewed?", + "Activepieces Todos": "Activepieces Todos", + "Users will manage tasks directly in Activepieces": "Users will manage tasks directly in Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.", + "External Channel (Slack, Teams, Email, ...)": "External Channel (Slack, Teams, Email, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.", + "Preview (Activepieces Todos)": "Preview (Activepieces Todos)", + "Preview (External channel)": "Preview (External channel)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "You can add the channel before the Wait Step, and configure the logic in the Router step", + "Add Steps": "Add Steps", + "All": "All", + "AI": "AI", + "Core": "Core", + "Apps": "Apps", + "Not available as trigger": "Not available as trigger", + "Not available as action": "Not available as action", + "Let our AI assistant help you out": "Let our AI assistant help you out", + "Or": "Or", + "Request Piece": "Request Piece", + "No pieces found": "No pieces found", + "Please select a piece first": "Please select a piece first", + "All Iterations": "Semua Iterasi", + "Duration": "Durasi", + "Input": "Masukan", + "Output": "Keluaran", + "There are no logs captured for this run.": "There are no logs captured for this run.", + "Logs are kept for {days} days after execution and then deleted.": "Logs are kept for {days} days after execution and then deleted.", + "Run Details": "Detail Eksekusi", + "Iteration": "Iterasi", + "Done": "Selesai", + "Took": "Waktu yang dibutuhkan", + "Running": "Running", + "on latest version": "on latest version", + "from failed step": "from failed step", + "Recent Runs": "Eksekusi Terbaru", + "No runs found": "Tidak ada eksekusi yang ditemukan", + "Close": "Tutup", + "OR": "ATAU", + "And If": "Dan Jika", + "+ And": "Dan", + "+ Or": "Atau", + "(Text) Contains": "(Teks) Mengandung", + "(Text) Does not contain": "(Teks) Tidak Mengandung", + "(Text) Exactly matches": "(Teks) Sama Persis", + "(Text) Does not exactly match": "(Teks) Tidak Sama Persis", + "(Text) Starts with": "(Teks) Diawali dengan", + "(Text) Does not start with": "(Teks) Tidak Diawali dengan", + "(Text) Ends with": "(Teks) Diakhiri dengan", + "(Text) Does not end with": "(Teks) Tidak Diakhiri dengan", + "(List) Contains": "(List) Contains", + "(List) Does not contain": "(List) Does not contain", + "(Number) Is greater than": "(Angka) Lebih besar dari", + "(Number) Is less than": "(Angka) Lebih kecil dari", + "(Number) Is equal to": "(Angka) Sama dengan", + "(Date/time) After": "(Date/time) After", + "(Date/time) Before": "(Date/time) Before", + "(Date/time) Equals": "(Date/time) Equals", + "(Boolean) Is true": "(Boolean) Bernilai benar", + "(Boolean) Is false": "(Boolean) Bernilai salah", + "(List) Is empty": "(List) Is empty", + "(List) Is not empty": "(List) Is not empty", + "Exists": "Ada", + "Does not exist": "Tidak ada", + "Incomplete condition": "Incomplete condition", + "First value": "Nilai pertama", + "Second value": "Nilai kedua", + "Case sensitive": "Peka Huruf Besar/Kecil", + "Execute If": "Execute If", + "The package name is required": "Nama paket dibutuhkan", + "Success": "Berhasil", + "Package added successfully": "Paket berhasil ditambahkan", + "Could not fetch package version": "Tidak dapat mengambil versi paket", + "Add NPM Package": "Tambahkan Paket NPM", + "Type the name of the npm package you want to add.": "Ketik nama paket npm yang ingin Anda tambahkan.", + "Package Name": "Nama Paket", + "The latest version will be fetched and added": "Versi terbaru akan diambil dan ditambahkan", + "Add": "Tambah", + "Code": "Kode", + "Dependencies": "Dependensi", + "Use code": "Use code", + "Add package": "Tambahkan paket", + "Inputs": "Masukan", + "Edit Step Name": "Sunting Nama Langkah", + "Edit Branch Name": "Edit Branch Name", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Pilih item yang akan diiterasi dari langkah sebelumnya dengan mengklik masukan **items**, yang seharusnya berupa **daftar** item.\n\nPerulangan akan mengiterasi setiap item dalam daftar dan mengeksekusi langkah berikutnya untuk setiap item.", + "Items": "Item", + "Select an array of items": "Pilih array item", + "Reconnect": "Reconnect", + "Select a connection": "Pilih koneksi", + "Create Connection": "Buat Koneksi", + "Rename": "Rename", + "Move": "Move", + "Add Branch": "Add Branch", + "Execute": "Execute", + "Only the first (left) matching branch": "Only the first (left) matching branch", + "All matching paths from left to right": "All matching paths from left to right", + "Branches": "Branches", + "{field} is required": "{field} is required", + "Tool Sample Data": "Tool Sample Data", + "Fill in the following fields to use them as sample data for the trigger.": "Fill in the following fields to use them as sample data for the trigger.", + "No input fields defined in the schema": "No input fields defined in the schema", + "Save": "Save", + "Test Environment": "Test Environment", + "Assigned to": "Assigned to", + "(Me)": "(Me)", + "Please select status to resolve the todo": "Please select status to resolve the todo", + "Resolve": "Resolve", + "Change status to resolved": "Change status to resolved", + "Send Sample Data to Webhook": "Send Sample Data to Webhook", + "Method": "Method", + "Query Params": "Query Params", + "Headers": "Headers", + "Body": "Body", + "Type": "Type", + "JSON": "JSON", + "Text": "Text", + "Form Data": "Form Data", + "Generate Sample Data": "Hasilkan Data Contoh", + "Test Step": "Uji Langkah", + "Retest": "Uji Ulang", + "Testing Failed": "Pengujian Gagal", + "Tested Successfully": "Pengujian Berhasil", + "Logs": "Logs", + "There is no sample data available found for this trigger.": "There is no sample data available found for this trigger.", + "Internal error, please try again later.": "Internal error, please try again later.", + "Failed to run test step and no error message was returned": "Gagal menjalankan langkah uji dan tidak ada pesan kesalahan yang dikembalikan.", + "Please fix inputs first": "Mohon perbaiki masukan terlebih dahulu", + "No sample data available": "No sample data available", + "Old results were removed, retest for new sample data": "Old results were removed, retest for new sample data", + "Result #": "Hasil #", + "The sample data can be used in the next steps.": "The sample data can be used in the next steps.", + "Testing Trigger": "Menguji Pemicu", + "Action Required": "Action Required", + "testPieceWebhookTriggerNote": "Please go to {pieceName} and trigger {triggerName}.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Send Data to the webhook URL to generate sample data to use in the next steps", + "Test Trigger": "Test Trigger", + "Use Mock Data": "Use Mock Data", + "Load Sample Data": "Load Sample Data", + "Test Tool": "Test Tool", + "home": "home", + "Home": "Beranda", + "Alerts": "Alerts", + "Releases": "Releases", + "Flows": "Flows", + "Products": "Products", + "MCP": "MCP", + "Tables": "Tables", + "Todos": "Todos", + "Push to Git": "Push to Git", + "Move To": "Move To", + "Duplicating": "Duplicating", + "Import": "Import", + "Exporting": "Exporting", + "Export": "Export", + "Share": "Share", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.", + "You are on a development branch, this will also delete the flow from the remote repository.": "You are on a development branch, this will also delete the flow from the remote repository.", + "flow": "flow", + "Community Support": "Community Support", + "Overview": "Overview", + "Projects": "Projects", + "Users": "Users", + "Setup": "Setup", + "Branding": "Branding", + "Global Connections": "Global Connections", + "Pieces": "Pieces", + "Templates": "Templates", + "License Key": "License Key", + "Security": "Security", + "Audit Logs": "Audit Logs", + "Single Sign On": "Single Sign On", + "Signing Keys": "Signing Keys", + "Project Roles": "Project Roles", + "API Keys": "API Keys", + "Infrastructure": "Infrastructure", + "Workers": "Workers", + "Health": "Health", + "Billing": "Billing", + "Settings": "Settings", + "Contact Sales": "Contact Sales", + "General": "General", + "Appearance": "Appearance", + "Team": "Team", + "Environments": "Environments", + "Project Settings": "Project Settings", + "Exit Platform Admin": "Exit Platform Admin", + "Enter Platform Admin": "Enter Platform Admin", + "Logout": "Logout", + "Misc": "Misc", + "Connections": "Connections", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "Today": "Today", + "Tasks": "Tasks", + "AI Credits": "AI Credits", + "Usage resets in": "Usage resets in", + "Manage": "Manage", + "Unlimited": "Unlimited", + "Enabled": "Enabled", + "Disabled": "Disabled", + "Could not claim the authorization code, make sure you have correct settings and try again.": "Could not claim the authorization code, make sure you have correct settings and try again.", + "Connection failed with error {msg}": "Connection failed with error {msg}", + "You don't have the permission to create a connection.": "You don't have the permission to create a connection.", + "Reconnect {displayName} Connection": "Reconnect {displayName}", + "Connect to {displayName}": "Connect to {displayName}", + "Connection Name": "Connection Name", + "Connection name": "Connection name", + "New Connection": "New Connection", + "Redirect URL": "Redirect URL", + "Client ID": "Client ID", + "Client Secret": "Client Secret", + "Connect": "Connect", + "Disconnect": "Disconnect", + "I would like to use my own App Credentials": "I would like to use my own App Credentials", + "I would like to use predefined App Credentials": "I would like to use predefined App Credentials", + "Permission needed": "Permission needed", + "Connections replaced successfully": "Connections replaced successfully", + "Error": "Error", + "Failed to replace connections": "Failed to replace connections", + "Failed to get affected flows": "Failed to get affected flows", + "Please select a piece": "Please select a piece", + "Please select a connection to replace": "Please select a connection to replace", + "Please select a connection to replace with": "Please select a connection to replace with", + "Replace Connections": "Replace Connections", + "Confirm Replacement": "Confirm Replacement", + "Replace one connection with another.": "Replace one connection with another.", + "This action requires ": "This action requires ", + "reconnecting": "reconnecting", + " any associated MCP pieces.": " any associated MCP pieces.", + "Piece": "Piece", + "Select a piece": "Select a piece", + "Connection to Replace": "Connection to Replace", + "Choose connection to replace": "Choose connection to replace", + "Replaced With": "Replaced With", + "Choose connection to replace with": "Choose connection to replace with", + "All flows will be changed to use the replaced with connection": "All flows will be changed to use the replaced with connection", + "Next": "Next", + "No flows will be affected by this change": "No flows will be affected by this change", + "Back": "Back", + "Unnamed tool": "Unnamed tool", + "This flow is enabled": "This flow is enabled", + "Enable this flow to make it available": "Enable this flow to make it available", + "Piece is updated successfully": "Piece is updated successfully", + "Piece is added successfully": "Piece is added successfully", + "Failed to update piece": "Failed to update piece", + "Failed to add piece": "Failed to add piece", + "Please select a connection": "Please select a connection", + "Your MCP server already has this piece": "Your MCP server already has this piece", + "+ New Connection": "+ New Connection", + "Edit Piece": "Edit Piece", + "Add Piece": "Add Piece", + "Connection": "Koneksi", + "MCP piece": "MCP piece", + "Failed to update piece status": "Failed to update piece status", + "Connection required": "Connection required", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.", + "piece": "piece", + "Connect your AI assistant to external services": "Connect your AI assistant to external services", + "Collapse": "Collapse", + "Show All": "Show All", + "Server URL": "Server URL", + "Hide the token for security": "Hide the token for security", + "Show the token": "Show the token", + "Generate a new token for security. This will invalidate the current URL.": "Generate a new token for security. This will invalidate the current URL.", + "URL copied to clipboard": "URL copied to clipboard", + "Copy URL to clipboard": "Copy URL to clipboard", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.", + "Max tables reached": "Max tables reached", + "You can't create more than {maxTables} tables": "You can't create more than {maxTables} tables", + "Name": "Name", + "Created": "Created", + "Delete Tables": "Delete Tables", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Are you sure you want to delete the selected tables? This action cannot be undone.", + "table": "table", + "Create and manage your tables to store your automation data": "Create and manage your tables to store your automation data", + "New Table": "New Table", + "No tables have been created yet": "No tables have been created yet", + "Create a table to get started and start managing your automation data": "Create a table to get started and start managing your automation data", + "Error deleting connections": "Error deleting connections", + "Status": "Status", + "Display Name": "Display Name", + "Owner": "Owner", + "App": "App", + "This connection is global and can be managed in the platform admin": "This connection is global and can be managed in the platform admin", + "External ID": "External ID", + "Connected At": "Connected At", + "Confirm Deletion": "Confirm Deletion", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Are you sure you want to delete the selected connections? This action cannot be undone.", + "Deleting connections may cause your Flows or MCP tools to break.": "Deleting connections may cause your Flows or MCP tools to break.", + "Manage project connections to external systems.": "Manage project connections to external systems.", + "No connections found": "No connections found", + "Come back later when you create a automation to manage your connections": "Come back later when you create a automation to manage your connections", + "Steps": "Steps", + "Folder": "Folder", + "Flow name": "Flow name", + "No flows found": "No flows found", + "Create a workflow to start automating": "Create a workflow to start automating", + "Create and manage your flows, run history and run issues": "Create and manage your flows, run history and run issues", + "Issues": "Issues", + "Untitled": "Untitled", + "Create flow": "Create flow", + "From scratch": "From scratch", + "Use a template": "Use a template", + "From local file": "From local file", + "Flow Name": "Flow Name", + "Count": "Count", + "First Seen": "First Seen", + "Last Seen": "Last Seen", + "Issues in {flowDisplayName} is marked as resolved.": "Issues in {flowDisplayName} is marked as resolved.", + "Unlock Issues": "Unlock Issues", + "Track issues in your workflows and troubleshoot them.": "Track issues in your workflows and troubleshoot them.", + "No issues found": "No issues found", + "All your workflows are running smoothly.": "All your workflows are running smoothly.", + "Mark as Resolved": "Mark as Resolved", + "All Flows Are Turned Off": "All Flows Are Turned Off", + "Task Usage Exceeded": "Task Usage Exceeded", + "of the Allowed Limit.": "of the Allowed Limit.", + "When a project tasks limit is reached,": "When a project tasks limit is reached,", + "all flows will be turned off and you will not be able to run any flows.": "all flows will be turned off and you will not be able to run any flows.", + "Please visit": "Please visit", + "Your Plan": "Your Plan", + "and increase your task limit, which requires your payment details.": "and increase your task limit, which requires your payment details.", + "Please contact your admin to increase the project task limit.": "Please contact your admin to increase the project task limit.", + "and increase the project task limit.": "and increase the project task limit.", + "Dismiss": "Dismiss", + "Token rotated successfully": "Token rotated successfully", + "Failed to rotate token": "Failed to rotate token", + "Piece removed successfully": "Piece removed successfully", + "Failed to remove piece": "Failed to remove piece", + "Flow created successfully": "Flow created successfully", + "Failed to create flow": "Failed to create flow", + "Add Flow": "Add Flow", + "Let your AI assistant trigger automations": "Let your AI assistant trigger automations", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Connect to your hosted MCP Server using any MCP client to communicate with tools", + "MCP Server": "MCP Server", + "My Tools": "My Tools", + "Create Flow": "Create Flow", + "Note": "Note", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.", + "This URL grants access to your tools and data. Only share with trusted applications.": "This URL grants access to your tools and data. Only share with trusted applications.", + "Server Configuration": "Server Configuration", + "Hide sensitive data": "Hide sensitive data", + "Show sensitive data": "Show sensitive data", + "Create a new URL. The current one will stop working.": "Create a new URL. The current one will stop working.", + "Copy configuration": "Copy configuration", + "Configuration copied to clipboard": "Configuration copied to clipboard", + "Claude": "Claude", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "Server/Other", + "Note: MCPs only work with": "Note: MCPs only work with", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", not the web version.", + "Prerequisites:": "Prerequisites:", + "Install": "Install", + "Node.js": "Node.js", + "and": "and", + "Open Settings:": "Open Settings:", + "Click the menu and select": "Click the menu and select", + "Developer": "Developer", + "Configure MCP:": "Configure MCP:", + "Click": "Click", + "Edit Config": "Edit Config", + "and paste the configuration below": "and paste the configuration below", + "Save and Restart:": "Save and Restart:", + "Save the config and restart Claude Desktop": "Save the config and restart Claude Desktop", + "Navigate to": "Navigate to", + "Cursor Settings": "Cursor Settings", + "Add Server:": "Add Server:", + "Add new global MCP server": "Add new global MCP server", + "Configure:": "Configure:", + "Paste the configuration below and save": "Paste the configuration below and save", + "Use either method:": "Use either method:", + "Go to": "Go to", + "Advanced Settings": "Advanced Settings", + "Open Command Palette and select": "Open Command Palette and select", + "Windsurf Settings Page": "Windsurf Settings Page", + "Navigate to Cascade:": "Navigate to Cascade:", + "Select": "Select", + "Cascade": "Cascade", + "in the sidebar": "in the sidebar", + "Add Server": "Add Server", + "Add custom server +": "Add custom server +", + "Copy URL": "Copy URL", + "Client Setup Instructions": "Client Setup Instructions", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "After changing connections or flows, reconnect your MCP server for changes to take effect.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.", + "icon": "icon", + "Unlock Analytics": "Unlock Analytics", + "Get insights into your platform usage and performance with our analytics dashboard": "Get insights into your platform usage and performance with our analytics dashboard", + "Active Flows": "Active Flows", + "The number of enabled flows in the platform": "The number of enabled flows in the platform", + "Active Projects": "Active Projects", + "The number of projects with at least one enabled flow": "The number of projects with at least one enabled flow", + "Active Users": "Active Users", + "The number of users logged in the last 30 days": "The number of users logged in the last 30 days", + "Out of {totalusers} total users": "Out of {totalusers} total users", + "Pieces Used": "Pieces Used", + "The number of unique pieces used across all flows": "The number of unique pieces used across all flows", + "Flows with AI": "Flows with AI", + "The number of enabled flows that use AI pieces": "The number of enabled flows that use AI pieces", + "Metrics": "Metrics", + "Executed Tasks": "Executed Tasks", + "Showing total executed tasks for specified time range": "Showing total executed tasks for specified time range", + "Tasks Usage Limit": "Tasks Usage Limit", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.", + "Number of monthly tasks": "Number of monthly tasks", + "Save changes": "Save changes", + "Limits updated successfully": "Limits updated successfully", + "Failed to update limits": "Failed to update limits", + "Failed to load billing information": "Failed to load billing information", + "Billing Amount": "Billing Amount", + "Manage Payment Details": "Manage Payment Details", + "Add Payment Details": "Add Payment Details", + "Current Task Usage": "Current Task Usage", + "Count of executed steps": "Count of executed steps", + "Billing Limit": "Billing Limit", + "Edit": "Edit", + "Add Limit": "Add Limit", + "Current Credit Usage": "Current Credit Usage", + "WebSocket Connection": "WebSocket Connection", + "Connected": "Connected", + "Disconnected": "Disconnected", + "No issues detected": "No issues detected", + "Check the status of your platform and its components": "Check the status of your platform and its components", + "System Health Status": "System Health Status", + "All systems are running smoothly": "All systems are running smoothly", + "Check the health of your worker machines": "Check the health of your worker machines", + "Workers Machine": "Workers Machine", + "This is demo data. In a real environment, this would show your actual worker machines.": "This is demo data. In a real environment, this would show your actual worker machines.", + "No workers found": "No workers found", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "You don't have any worker machines yet. Spin up new machines to execute your automations", + "IP Address": "IP Address", + "CPU Usage": "CPU Usage", + "Disk Usage": "Disk Usage", + "RAM Usage": "RAM Usage", + "Last Contact": "Last Contact", + "Configs": "Configs", + "Environment Variables": "Environment Variables", + "Websocket Connection Error": "Websocket Connection Error", + "Retry Connection": "Retry Connection", + "Update Available": "Update Available", + "Update Now": "Update Now", + "Your Universal AI needs a quick setup": "Your Universal AI needs a quick setup", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.", + "Configure": "Configure", + "Platform Alerts": "Platform Alerts", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.", + "View Alerts": "View Alerts", + "Used Tasks": "Used Tasks", + "Used AI Credits": "Used AI Credits", + "Cannot delete active project, switch to another project first": "Cannot delete active project, switch to another project first", + "Delete Projects": "Delete Projects", + "Are you sure you want to delete the selected projects?": "Are you sure you want to delete the selected projects?", + "New Project": "New Project", + "Validation error": "Validation error", + "Project has enabled flows. Please disable them first.": "Project has enabled flows. Please disable them first.", + "This project is active. Please switch to another project first.": "This project is active. Please switch to another project first.", + "Unlock Projects": "Unlock Projects", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Orchestrate your automation teams across projects with their own flows, connections and usage quotas", + "Manage your automation projects": "Manage your automation projects", + "No projects found": "No projects found", + "Start by creating projects to manage your automation teams": "Start by creating projects to manage your automation teams", + "Edit project": "Edit project", + "Name is required": "Name is required", + "Create New Project": "Create New Project", + "Project Name": "Project Name", + "Id": "Id", + "Enable API Keys": "Enable API Keys", + "Create and manage API keys to access Activepieces APIs.": "Create and manage API keys to access Activepieces APIs.", + "New Api Key": "New Api Key", + "No API keys found": "No API keys found", + "Start by creating an API key to communicate with Activepieces APIs": "Start by creating an API key to communicate with Activepieces APIs", + "Delete API Key": "Delete API Key", + "Are you sure you want to delete this API key?": "Are you sure you want to delete this API key?", + "API Key": "API Key", + "API Key Created": "API Key Created", + "Create New API Key": "Create New API Key", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Please save this secret key somewhere safe and accessible. For security reasons,", + "you won't be able to view it again after closing this dialog.": "you won't be able to view it again after closing this dialog.", + "API Key Name": "API Key Name", + "Action": "Action", + "Performed By": "Performed By", + "Project": "Project", + "Unlock Audit Logs": "Unlock Audit Logs", + "Comply with internal and external security policies by tracking activities done within your account": "Comply with internal and external security policies by tracking activities done within your account", + "Track activities done within your platform": "Track activities done within your platform", + "No audit logs found": "No audit logs found", + "Come back later when you have some activity to audit": "Come back later when you have some activity to audit", + "Resource": "Resource", + "Details": "Details", + "N/A": "N/A", + "Flow Run": "Flow Run", + "Flow": "Flow", + "User": "User", + "Signing Key": "Signing Key", + "Project Role Management": "Project Role Management", + "Define custom roles and permissions to control what your team members can access and modify": "Define custom roles and permissions to control what your team members can access and modify", + "Define custom roles and permissions that can be assigned to your team members": "Define custom roles and permissions that can be assigned to your team members", + "New Role": "New Role", + "Contact sales to unlock custom roles": "Contact sales to unlock custom roles", + "Create New Role": "Create New Role", + "View ": "View ", + "Edit ": "Edit ", + "Role Name": "Role Name", + "Permissions": "Permissions", + "None": "None", + "Read": "Read", + "Write": "Write", + "Create": "Create", + "Email": "Email", + "First Name": "First Name", + "Last Name": "Last Name", + "Roles": "Roles", + "View the users assigned to this role": "View the users assigned to this role", + "Role": "Role", + "No users found": "No users found", + "Starting by assigning users to this role": "Starting by assigning users to this role", + "Project Role entry deleted successfully": "Project Role entry deleted successfully", + "Updated": "Updated", + "No project roles found": "No project roles found", + "Create custom project roles to manage permissions for platform users": "Create custom project roles to manage permissions for platform users", + "Show Users": "Show Users", + "View Role": "View Role", + "Edit Role": "Edit Role", + "Delete Role": "Delete Role", + "Project Role": "Project Role", + "Unlock Embedding Through JS SDK": "Unlock Embedding Through JS SDK", + "Enable signing keys to access embedding functionalities.": "Enable signing keys to access embedding functionalities.", + "New Signing Key": "New Signing Key", + "No signing keys found": "No signing keys found", + "Create a signing key to authenticate users with embedding": "Create a signing key to authenticate users with embedding", + "Delete Signing Key": "Delete Signing Key", + "Are you sure you want to delete this signing key?": "Are you sure you want to delete this signing key?", + "Signing Key Created": "Signing Key Created", + "Create New Signing Key": "Create New Signing Key", + "Signing Key Name": "Signing Key Name", + "Allowed domains updated": "Allowed domains updated", + "Update": "Update", + "Enable": "Enable", + "Configure Allowed Domains": "Configure Allowed Domains", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.", + "Add Domain": "Add Domain", + "Allow logins through {providerName}'s single sign-on functionality.": "Allow logins through {providerName}'s single sign-on functionality.", + "Email authentication updated": "Email authentication updated", + "Enable Single Sign On": "Enable Single Sign On", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Let your users sign in with your current SSO provider or give them self serve sign up access", + "Allowed Domains": "Allowed Domains", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Allow users to authenticate with specific domains. Leave empty to allow all domains.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Allowed Email Login", + "Allow logins through email and password.": "Allow logins through email and password.", + "Single sign on settings updated": "Single sign on settings updated", + "Disable": "Disable", + "Configure {provider} SSO": "Configure {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} Client ID", + "{provider} Client Secret": "{provider} Client Secret", + "Single sign-on settings updated": "Single sign-on settings updated", + "Configure SAML 2.0 SSO": "Configure SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDP Metadata", + "IDP Certificate": "IDP Certificate", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "Base URL", + "Resource Name": "Resource Name", + "Deployment Name": "Deployment Name", + "Saving": "Saving", + "Activepieces Copilot": "Activepieces Copilot", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot is configured and ready to help your users build flows faster using AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configure Activepieces Copilot to help your users build flows faster using AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Set provider credentials that will be used by universal AI pieces, i.e Text AI.", + "AI Providers": "AI Providers", + "Copilot": "Copilot", + "Configure credentials for {providerName} AI provider.": "Configure credentials for {providerName} AI provider.", + "Update AI Provider": "Update AI Provider", + "Enable AI Provider": "Enable AI Provider", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Please enter a valid domain", + "Your changes have been saved.": "Your changes have been saved.", + "The domain is already added.": "The domain is already added.", + "Add Custom Domain": "Add Custom Domain", + "Enter a domain name without a protocol (e.g. example.com)": "Enter a domain name without a protocol (e.g. example.com)", + "Logo URL": "Logo URL", + "Icon URL": "Icon URL", + "Favicon URL": "Favicon URL", + "Default Language": "Default Language", + "Select Language": "Select Language", + "No Languages": "No Languages", + "Primary Color": "Primary Color", + "Custom Domains": "Custom Domains", + "No domains added yet.": "No domains added yet.", + "Verified": "Verified", + "Pending, please contact the support for dns verification.": "Pending, please contact the support for dns verification.", + "Are you sure you want to delete {domain}?": "Are you sure you want to delete {domain}?", + "Brand Activepieces": "Brand Activepieces", + "Give your users an experience that looks like you by customizing the color, logo and more": "Give your users an experience that looks like you by customizing the color, logo and more", + "Configure the appearance and SMTP settings for your platform.": "Configure the appearance and SMTP settings for your platform.", + "Invalid host": "Invalid host", + "Invalid username": "Invalid username", + "Invalid password": "Invalid password", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Invalid sender name", + "SMTP is configured": "SMTP is configured", + "Mail Server": "Mail Server", + "Set up your SMTP settings to send emails from your domain.": "Set up your SMTP settings to send emails from your domain.", + "Disable Mail Server": "Disable Mail Server", + "Are you sure you want to disable your mail server?": "Are you sure you want to disable your mail server?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "This will stop you from sending emails for issues, quota limits, invitations and forgot password.", + "mail server": "mail server", + "Host": "Host", + "Port": "Port", + "Username": "Username", + "Password": "Password", + "Sender Email": "Sender Email", + "Sender Name": "Sender Name", + "Enable Global Connections": "Enable Global Connections", + "Manage platform-wide connections to external systems.": "Manage platform-wide connections to external systems.", + "No global connections found": "No global connections found", + "Create a global connection that can be shared to multiple projects": "Create a global connection that can be shared to multiple projects", + "License key is invalid": "License key is invalid", + "Invalid license key": "Invalid license key", + "License activated!": "License activated!", + "Activate License Key": "Activate License Key", + "Let the magic begin!": "Let the magic begin!", + "Activate": "Activate", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ", + "This feature is not available in your current edition. ": "This feature is not available in your current edition. ", + "Learn how to upgrade": "Learn how to upgrade", + "Activate your platform and unlock enterprise features": "Activate your platform and unlock enterprise features", + "Activate License": "Activate License", + "Expiration": "Expiration", + "Valid until": "Valid until", + "Expired": "Expired", + "Expires soon": "Expires soon", + "Features": "Features", + "Applying Tags...": "Applying Tags...", + "Tags applied.": "Tags applied.", + "Tag created": "Tag created", + "Tag": "Tag", + "Tags": "Tags", + "Control Pieces": "Control Pieces", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Show the pieces that matter most to your users and hide the ones you don't like.", + "Manage the pieces that are available to your users": "Manage the pieces that are available to your users", + "Start by installing pieces that you want to use in your automations": "Start by installing pieces that you want to use in your automations", + "Piece Name": "Piece Name", + "Hide this piece from all projects": "Hide this piece from all projects", + "Show this piece for all projects": "Show this piece for all projects", + "Unpin this piece": "Unpin this piece", + "Pin this piece": "Pin this piece", + "Pieces synced": "Pieces synced", + "Pieces have been synced from the activepieces cloud.": "Pieces have been synced from the activepieces cloud.", + "OAuth2 Credentials Deleted": "OAuth2 Credentials Deleted", + "OAuth2 Credentials Updated": "OAuth2 Credentials Updated", + "Configure OAuth2 APP": "Configure OAuth2 APP", + "Delete OAuth2 APP": "Delete OAuth2 APP", + "Templates deleted successfully": "Templates deleted successfully", + "Delete Templates": "Delete Templates", + "Are you sure you want to delete the selected templates?": "Are you sure you want to delete the selected templates?", + "New Template": "New Template", + "Unlock Templates": "Unlock Templates", + "Convert the most common automations into reusable templates 1 click away from your users": "Convert the most common automations into reusable templates 1 click away from your users", + "Convert the most common automations into reusable templates": "Convert the most common automations into reusable templates", + "No templates found": "No templates found", + "Create a template for your user to inspire them": "Create a template for your user to inspire them", + "Edit template": "Edit template", + "Template is required": "Template is required", + "Update New Template": "Update New Template", + "Create New Template": "Create New Template", + "Template Name": "Template Name", + "Description": "Description", + "Template Description": "Template Description", + "Blog URL": "Blog URL", + "Template Blog URL": "Template Blog URL", + "Template": "Template", + "Invalid JSON": "Invalid JSON", + "User deleted successfully": "User deleted successfully", + "User activated successfully": "User activated successfully", + "User deactivated successfully": "User deactivated successfully", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Manage your users and their access to your projects", + "Start inviting users to your project": "Start inviting users to your project", + "External Id": "External Id", + "Admin": "Admin", + "Member": "Member", + "Activated": "Activated", + "Deactivated": "Deactivated", + "Edit user": "Edit user", + "Admin cannot be deactivated": "Admin cannot be deactivated", + "Deactivate user": "Deactivate user", + "Activate user": "Activate user", + "Delete User": "Delete User", + "Are you sure you want to delete this user?": "Are you sure you want to delete this user?", + "Delete user": "Delete user", + "Update User Role": "Update User Role", + "Meeting Summary Flow": "Meeting Summary Flow", + "Added new features and fixed bugs": "Added new features and fixed bugs", + "Flows Changes": "Flows Changes", + "Connections Changes": "Connections Changes", + "New connections are placeholders and need to be reconnected again": "New connections are placeholders and need to be reconnected again", + "renamed to": "renamed to", + "Tables Changes": "Tables Changes", + "No changes to apply": "No changes to apply", + "Apply Changes": "Apply Changes", + "Create Git Release": "Create Git Release", + "Create Project Release": "Create Project Release", + "Create Rollback to": "Create Rollback to", + "Source": "Source", + "Rollback": "Rollback", + "Imported At": "Imported At", + "Imported By": "Imported By", + "Track and manage your project version history and deployments. ": "Track and manage your project version history and deployments. ", + "Environments & Releases": "Environments & Releases", + "Project Releases": "Project Releases", + "Create Release": "Create Release", + "From Git": "From Git", + "From Project": "From Project", + "No project releases found": "No project releases found", + "Create a project release to get started": "Create a project release to get started", + "Please select project": "Please select project", + "No Changes Found": "No Changes Found", + "There are no differences to apply": "There are no differences to apply", + "Please select a project": "Please select a project", + "Search projects...": "Search projects...", + "Review Changes": "Review Changes", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Imported by", + "from": "from", + "No description provided": "No description provided", + "Invitation only sign up": "Invitation only sign up", + "Please ask your administrator to add you to the organization.": "Please ask your administrator to add you to the organization.", + "Something went wrong, please try again.": "Something went wrong, please try again.", + "Please try again.": "Please try again.", + "Please enter a valid email address": "Please enter a valid email address", + "The email is already added.": "The email is already added.", + "Add email": "Add email", + "Only project admins can do this": "Only project admins can do this", + "Add Alert Email": "Add Alert Email", + "Enter the email address to receive alerts.": "Enter the email address to receive alerts.", + "Add Email": "Add Email", + "Emails": "Emails", + "Add email addresses to receive alerts.": "Add email addresses to receive alerts.", + "No emails added yet.": "No emails added yet.", + "Choose what you want to be notified about.": "Choose what you want to be notified about.", + "Project and alert permissions are required to change this setting.": "Project and alert permissions are required to change this setting.", + "Every Failed Run": "Every Failed Run", + "Get an email alert when a flow fails.": "Get an email alert when a flow fails.", + "Get an email alert when a new issue created.": "Get an email alert when a new issue created.", + "Never": "Never", + "Turn off email notifications.": "Turn off email notifications.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Customize the appearance of the app. Automatically switch between day and night themes.", + "Select the theme for the dashboard.": "Select the theme for the dashboard.", + "Light": "Light", + "Dark": "Dark", + "Select the language that will be used in the dashboard.": "Select the language that will be used in the dashboard.", + "Select language": "Select language", + "Search language...": "Search language...", + "No language found.": "No language found.", + "Help us translate Activepieces to your language.": "Help us translate Activepieces to your language.", + "Learn more": "Learn more", + "Git Connection Removed": "Git Connection Removed", + "Your Git repository has been successfully disconnected": "Your Git repository has been successfully disconnected", + "Enable Environments": "Enable Environments", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Deploy flows across development, staging and production environments with version control and team collaboration", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Connect to Git to enable version control, backup your flows, and manage multiple environments. ", + "Repository URL": "Repository URL", + "Not connected": "Not connected", + "Project Folder": "Project Folder", + "Releases Enabled": "Releases Enabled", + "You have successfully enabled releases": "You have successfully enabled releases", + "Enable releases to easily create and manage project releases.": "Enable releases to easily create and manage project releases.", + "The external ID is already taken.": "The external ID is already taken.", + "Manage general settings for your project.": "Manage general settings for your project.", + "Used to identify the project based on your SaaS ID": "Used to identify the project based on your SaaS ID", + "org-3412321": "org-3412321", + "Delete {name}": "Delete {name}", + "This will permanently delete this piece, all steps using it will fail.": "This will permanently delete this piece, all steps using it will fail.", + "Add a piece to your project that you want to use in your automations": "Add a piece to your project that you want to use in your automations", + "Pieces list updated": "Pieces list updated", + "Manage Pieces": "Manage Pieces", + "Choose which pieces you want to be available for your current project users": "Choose which pieces you want to be available for your current project users", + "Unlock Team Permissions": "Unlock Team Permissions", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial", + "Project Members": "Project Members", + "Invite your team members to collaborate.": "Invite your team members to collaborate.", + "No members are added to this project.": "No members are added to this project.", + "Pending Invitations": "Pending Invitations", + "No pending invitation.": "No pending invitation.", + "templateId is missing": "templateId is missing", + "Me Only": "Me Only", + "Unresolved": "Unresolved", + "Resolved": "Resolved", + "Title": "Title", + "Date Created": "Date Created", + "Manage todos for your project that are created by automations": "Manage todos for your project that are created by automations", + "No todos found": "No todos found", + "You do not have any pending todos. Great job!": "You do not have any pending todos. Great job!", + "Write a comment...": "Write a comment...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "This feature is still under testing and might be changed often", + "Failed to copy to clipboard": "Failed to copy to clipboard", + "{number} items selected": "{number} items selected", + "Select All": "Select All", + "No results found.": "Tidak ada hasil yang ditemukan.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect must be used within MultiSelectProvider", + "Unset": "Unset", + "Refresh": "Refresh", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} more", + "Removed {entityName}": "Removed {entityName}", + "Download File": "Download File", + "Copied to clipboard": "Copied to clipboard", + "File is not available after execution.": "File is not available after execution.", + "Available for Projects": "Available for Projects", + "Select projects": "Select projects", + "No items": "No items", + "Previous": "Previous", + "to": "to", + "Last Week": "Last Week", + "Last Month": "Last Month", + "Last 3 Months": "Last 3 Months", + "Last 6 Months": "Last 6 Months", + "Next 7 days": "Next 7 days", + "Next 30 days": "Next 30 days", + "Next 90 days": "Next 90 days", + "Next 180 days": "Next 180 days", + "Select Time Range": "Select Time Range", + "Clear": "Clear", + "Download": "Download", + "Go to Dashboard": "Go to Dashboard", + "Select a file": "Select a file", + "Press space to separate values": "Press space to separate values", + "AM": "AM", + "PM": "PM", + "Already have an account?": "Already have an account?", + "Sign in": "Sign in", + "Don't have an account?": "Don't have an account?", + "Sign up": "Sign up", + "Welcome Back!": "Welcome Back!", + "Enter your email below to sign in to your account": "Enter your email below to sign in to your account", + "Let's Get Started!": "Let's Get Started!", + "Create your account and start flowing!": "Create your account and start flowing!", + "Your password was changed successfully": "Your password was changed successfully", + "Your password reset request has expired, please request a new one": "Your password reset request has expired, please request a new one", + "Reset Password": "Reset Password", + "Enter your new password": "Enter your new password", + "Password is required": "Password is required", + "Verification email resent, if previous one expired.": "Verification email resent, if previous one expired.", + "Password reset link resent, if previous one expired.": "Password reset link resent, if previous one expired.", + "We sent you a link to complete your registration to": "We sent you a link to complete your registration to", + "We sent you a link to reset your password to": "We sent you a link to reset your password to", + "Didn't receive an email or it expired?": "Didn't receive an email or it expired?", + "Resend": "Resend", + "Please enter your email": "Please enter your email", + "Check Your Inbox": "Check Your Inbox", + "If the user exists we'll send you an email with a link to reset your password.": "If the user exists we'll send you an email with a link to reset your password.", + "Send Password Reset Link": "Send Password Reset Link", + "Back to sign in": "Back to sign in", + "Email is invalid": "Email is invalid", + "Something went wrong, please try again later": "Something went wrong, please try again later", + "Invalid email or password": "Invalid email or password", + "User has been deactivated": "User has been deactivated", + "Email domain is disallowed": "Email domain is disallowed", + "Email authentication has been disabled": "Email authentication has been disabled", + "Forgot your password?": "Forgot your password?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "Sign up is restricted. You need an invitation to join. Please contact the administrator.", + "Email is already used": "Email is already used", + "Email authentication is disabled": "Email authentication is disabled", + "First name is required": "First name is required", + "Last name is required": "Last name is required", + "Email is required": "Email is required", + "Receive updates and newsletters from activepieces": "Receive updates and newsletters from activepieces", + "By creating an account, you agree to our": "By creating an account, you agree to our", + "terms of service": "terms of service", + "privacy policy": "privacy policy", + "Sign up With": "Sign up With", + "Google": "Google", + "Sign in With": "Sign in With", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "Email has been verified. You will be redirected to sign in...", + "Verifying email...": "Verifying email...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "invitation has expired, once you sign in again you will be able to resend the verification email.", + "Redirecting to sign in...": "Redirecting to sign in...", + "Password must contain at least one special character": "Password must contain at least one special character", + "Password must contain at least one lowercase letter": "Password must contain at least one lowercase letter", + "Password must contain at least one uppercase letter": "Password must contain at least one uppercase letter", + "Password must contain at least one number": "Password must contain at least one number", + "8-64 Characters": "8-64 Characters", + "Special Character": "Special Character", + "Lowercase": "Lowercase", + "Uppercase": "Uppercase", + "Number": "Number", + "Connection has been updated.": "Connection has been updated.", + "Edit Global Connection": "Edit Global Connection", + "Connection has been renamed.": "Connection has been renamed.", + "New Connection Name": "New Connection Name", + "Connection name already used": "Connection name already used", + "Please select at least one project": "Please select at least one project", + "Run Succeeded": "Run Succeeded", + "Run Failed": "Run Failed", + "Flow Run is paused": "Flow Run is paused", + "Run Failed due to quota exceeded": "Run Failed due to quota exceeded", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Run failed due to exceeding the memory limit of {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "Run exceeded {timeout} seconds, try to optimize your steps.", + "Run failed for an unknown reason, contact support.": "Run failed for an unknown reason, contact support.", + "Unknown": "Unknown", + "Exit Run": "Exit Run", + "Select shown": "Select shown", + "Select all": "Select all", + "Start Time": "Start Time", + "Runs replayed successfully": "Runs replayed successfully", + "Retry": "Retry", + "all except": "all except", + "all": "all", + "Only failed runs can be retried from failed step": "Only failed runs can be retried from failed step", + "No flow runs found": "No flow runs found", + "Come back later when your automations start running": "Come back later when your automations start running", + "Step running": "Step running", + "Step paused": "Step paused", + "Step Stopped": "Step Stopped", + "Step Succeeded": "Step Succeeded", + "Step Failed": "Step Failed", + "Please publish flow first": "Please publish flow first", + "Flow is on": "Flow is on", + "Flow is off": "Flow is off", + "Permission Needed": "Permission Needed", + "Draft Version": "Draft Version", + "Published Version": "Published Version", + "Locked Version": "Locked Version", + "flowsImported": "{flowsCount, plural, =0 {No Flows Imported} =1 {Flow Imported Successfully} other {Flows Imported Successfully}}", + "Template file is invalid": "Template file is invalid", + "No valid templates found. The following files failed to import: ": "No valid templates found. The following files failed to import: ", + "Please select a file first": "Please select a file first", + "Unsupported file type": "Unsupported file type", + "Import Flow": "Import Flow", + "Warning": "Warning", + "Importing a flow will overwrite your current one.": "Importing a flow will overwrite your current one.", + "Select a folder": "Select a folder", + "Folders": "Folders", + "Please select a folder": "Please select a folder", + "Moved flows successfully": "Moved flows successfully", + "Move Selected Flows": "Move Selected Flows", + "Select Folder": "Select Folder", + "No Folders": "No Folders", + "Flow has been renamed.": "Flow has been renamed.", + "New Flow Name": "New Flow Name", + "Use Template": "Use Template", + "Browse Templates": "Browse Templates", + "Search templates": "Search templates", + "No templates found, try adjusting your search": "No templates found, try adjusting your search", + "Read more about this template in": "Read more about this template in", + "this blog!": "this blog!", + "Share Template": "Share Template", + "Generate or update a template link for the current flow to easily share it with others.": "Generate or update a template link for the current flow to easily share it with others.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "The template will not have any credentials in connection fields, keeping sensitive information secure.", + "A short description of the template": "A short description of the template", + "Flow Is In Use": "Flow Is In Use", + "Flow is being used by another user, please try again later.": "Flow is being used by another user, please try again later.", + "Flow has been published.": "Alur telah diterbitkan.", + "Flows have been exported.": "Flows have been exported.", + "Run": "Run", + "Real time flow": "Real time flow", + "Flow can't be published with empty trigger {name}": "Flow can't be published with empty trigger {name}", + "Please contact support as your published flow has a problem": "Please contact support as your published flow has a problem", + "Actions": "Tindakan", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.", + "You are on a development branch, this will not delete the flows from the remote repository.": "You are on a development branch, this will not delete the flows from the remote repository.", + "Please enter folder name": "Please enter folder name", + "Added folder successfully": "Added folder successfully", + "The folder name already exists.": "The folder name already exists.", + "New Folder": "New Folder", + "Folder Name": "Folder Name", + "Loading...": "Loading...", + "Delete {folderName}": "Delete {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "If you delete this folder, we will keep its flows and move them to Uncategorized.", + "All flows": "All flows", + "Please enter a folder name": "Please enter a folder name", + "Renamed flow successfully": "Renamed flow successfully", + "Folder name already used": "Folder name already used", + "New Folder Name": "New Folder Name", + "Connected successfully": "Connected successfully", + "Connect Git": "Connect Git", + "Remote URL": "Remote URL", + "Folder name is the name of the folder where the project will be stored or fetched.": "Folder name is the name of the folder where the project will be stored or fetched.", + "SSH Private Key": "SSH Private Key", + "The SSH private key to use for authentication.": "The SSH private key to use for authentication.", + "Only published flows can be pushed to Git": "Only published flows can be pushed to Git", + "Pushed successfully": "Pushed successfully", + "Invalid Operation": "Invalid Operation", + "Commit Message": "Commit Message", + "Enter a commit message to describe the changes you want to push.": "Enter a commit message to describe the changes you want to push.", + "Push": "Push", + "This field is required": "This field is required", + "Your submission was successfully received.": "Your submission was successfully received.", + "Flow not found": "Flow not found", + "The flow you are trying to submit to does not exist.": "The flow you are trying to submit to does not exist.", + "The flow failed to execute.": "The flow failed to execute.", + "Submit": "Submit", + "issues-notification": "issues-notification", + "Please select a package type": "Please select a package type", + "package.json not found in archive": "package.json not found in archive", + "Error processing archive file": "Error processing archive file", + "Please upload a .tgz file": "Please upload a .tgz file", + "Piece name is required for NPM Registry": "Piece name is required for NPM Registry", + "Piece version is required for NPM Registry": "Piece version is required for NPM Registry", + "Piece installed": "Piece installed", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "A piece with this name and version is already installed. Please update the version number in package.json and try again.", + "Install Piece": "Install Piece", + "Install a piece": "Install a piece", + "Package Type": "Package Type", + "NPM Registry": "NPM Registry", + "Packed Archive (.tgz)": "Packed Archive (.tgz)", + "Piece Version": "Piece Version", + "Package Archive": "Package Archive", + "Package archive": "Package archive", + "Powerful Node.js & TypeScript code with npm": "Powerful Node.js & TypeScript code with npm", + "Loop on Items": "Loop on Items", + "Split your flow into branches depending on condition(s)": "Split your flow into branches depending on condition(s)", + "Empty Trigger": "Empty Trigger", + "An internal error occurred while fetching data, please contact support": "An internal error occurred while fetching data, please contact support", + "An internal error occurred, please contact support": "An internal error occurred, please contact support", + "Custom Javascript Code": "Custom Javascript Code", + "Router": "Router", + "recordsCount": "{recordsCount, plural, =0 {0 Records} =1 {1 Record} other {# Records}}", + "selected": "selected", + "All records selected": "All records selected", + "fieldsCount": "{fieldsCount, plural, =0 {0 Fields} =1 {1 Field} other {# Fields}}", + "Saving...": "Saving...", + "Loading more...": "Loading more...", + "Export Table": "Export Table", + "Delete Records": "Delete Records", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Are you sure you want to delete the selected records? This action cannot be undone.", + "record": "record", + "records": "records", + "mm/dd/yyy": "mm/dd/yyy", + "Delete Field": "Delete Field", + "Are you sure you want to delete this field? This action cannot be undone.": "Are you sure you want to delete this field? This action cannot be undone.", + "field": "field", + "Ignored": "Ignored", + "Table": "Table", + "CSV": "CSV", + "Field": "Field", + "Please select a csv file": "Please select a csv file", + "Max file size is {maxFileSize}MB": "Max file size is {maxFileSize}MB", + "Import CSV": "Import CSV", + "Imported records will be added to the bottom of the table": "Imported records will be added to the bottom of the table", + "Any records after the limit ({maxRecords} records) will be ignored": "Any records after the limit ({maxRecords} records) will be ignored", + "CSV File": "CSV File", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support", + "Name must be unique": "Name must be unique", + "Type is required": "Type is required", + "Please add at least one option": "Please add at least one option", + "New Field": "New Field", + "Options": "Options", + "Name is already taken": "Name is already taken", + "Table renamed": "Table renamed", + "Table name": "Table name", + "Team Invitation Accepted": "Team Invitation Accepted", + "Thank you for accepting the invitation. We are redirecting you right now...": "Thank you for accepting the invitation. We are redirecting you right now...", + "Invalid invitation token. Please try again.": "Invalid invitation token. Please try again.", + "Role updated successfully": "Role updated successfully", + "Error updating role": "Error updating role", + "Please try again later": "Please try again later", + "Edit Role for": "Edit Role for", + "Select Role": "Select Role", + "Avatar": "Avatar", + "Remove {email}": "Remove {email}", + "Are you sure you want to remove this invitation?": "Are you sure you want to remove this invitation?", + "Please select invitation type": "Please select invitation type", + "Please select platform role": "Please select platform role", + "Invitation sent successfully": "Invitation sent successfully", + "Please select a project role": "Please select a project role", + "Invitation link copied successfully": "Invitation link copied successfully", + "Invite User": "Invite User", + "Invitation Link": "Invitation Link", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Type the email address of the user you want to invite, the invitation expires in 24 hours.", + "Invite To": "Invite To", + "Entire Platform": "Entire Platform", + "Select Project Role": "Select Project Role", + "Invite": "Invite", + "Platform Role": "Platform Role", + "Select a platform role": "Select a platform role", + "Are you sure you want to remove this member?": "Are you sure you want to remove this member?", + "Editor": "Editor", + "Operator": "Operator", + "Viewer": "Viewer", + "Select a project role": "Select a project role", + "Steps in this flow": "Steps in this flow", + "Invalid Access": "Invalid Access", + "Either the project does not exist or you do not have access to it.": "Either the project does not exist or you do not have access to it." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/it/translation.json b/packages/react-ui/public/locales/it/translation.json new file mode 100644 index 0000000..35313ed --- /dev/null +++ b/packages/react-ui/public/locales/it/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Publish", + "Latest version is published": "Latest version is published", + "Your flow has incomplete steps": "Your flow has incomplete steps", + "Edit Flow": "Edit Flow", + "View Draft": "View Draft", + "Uncategorized": "Uncategorized", + "Go to folder": "Go to folder", + "Support": "Support", + "Runs": "Runs", + "Run Logs": "Run Logs", + "Versions": "Versions", + "Versions History": "Versions History", + "Error generating code": "Error generating code", + "AI Copilot": "AI Copilot", + "i.e Calculate the sum of a list...": "i.e Calculate the sum of a list...", + "Send": "Send", + "Generating Code": "Generating Code", + "Hello there! I am here to generate code that helps with your flow": "Hello there! I am here to generate code that helps with your flow", + "Here are examples of what I am best used for: ": "Here are examples of what I am best used for: ", + "Text Processing": "Text Processing", + "Process strings, dates and data": "Process strings, dates and data", + "Data Operations": "Data Operations", + "Change data from one format to another": "Change data from one format to another", + "Calculations": "Calculations", + "Handle math and statistics": "Handle math and statistics", + "API Integration": "API Integration", + "Connect with external services. Best for simple integrations currently.": "Connect with external services. Best for simple integrations currently.", + "What would you like me to help you with?": "What would you like me to help you with?", + "Insert": "Insert", + "Data Selector": "Data Selector", + "Search": "Search", + "No matching data": "No matching data", + "Try adjusting your search": "Try adjusting your search", + "This trigger needs to have data loaded from your account, to use as sample data.": "This trigger needs to have data loaded from your account, to use as sample data.", + "This step needs to be tested in order to view its data.": "Questo step deve essere testato per visualizzare i suoi dati", + "Go to Trigger": "Go to Trigger", + "Go to Step": "Go to Step", + "Select Mode": "Select Mode", + "Move Mode": "Move Mode", + "Reset Zoom": "Reset Zoom", + "Zoom In": "Zoom In", + "Zoom Out": "Zoom Out", + "Fit to View": "Fit to View", + "Replace": "Replace", + "Copy": "Copy", + "Duplicate": "Duplicate", + "Paste After Last Step": "Paste After Last Step", + "Paste Inside Loop": "Paste Inside Loop", + "Paste After": "Paste After", + "Paste Inside...": "Paste Inside...", + "New Branch": "New Branch", + "Paste Inside Branch": "Paste Inside Branch", + "Delete": "Delete", + "Duplicate Branch": "Duplicate Branch", + "Delete Branch": "Delete Branch", + "Invalid Move": "Invalid Move", + "The destination location is a child of the dragged step": "The destination location is a child of the dragged step", + "End": "End", + "Skipped": "Skipped", + "Incomplete settings": "Impostazioni incomplete", + "logo": "logo", + "Step Icon": "Step Icon", + "Branch": "Branch", + "incompleteSteps": "{invalidSteps, plural, =0 {no incomplete steps} =1 {Complete 1 step} other {Complete # steps}}", + "Test Flow": "Test Flow", + "Please test the trigger first": "Please test the trigger first", + "View Only": "View Only", + "Use as Draft": "Use as Draft", + "Are you sure?": "Are you sure?", + "Your current draft version will be overwritten with": "Your current draft version will be overwritten with", + "version #": "version #", + "Cancel": "Annulla", + "Confirm": "Confirm", + "Version": "Versione", + "Viewing": "Viewing", + "View": "Visualizza", + "Version History": "Version History", + "Error, please try again.": "Error, please try again.", + "Continue on Failure": "Continue on Failure", + "Enable this option to skip this step and continue the flow normally if it fails.": "Enable this option to skip this step and continue the flow normally if it fails.", + "Retry on Failure": "Retry on Failure", + "Automatically retry up to four attempts when failed.": "Automatically retry up to four attempts when failed.", + "Remove": "Remove", + "Add Item": "Add Item", + "File Input": "File Input", + "Date Input": "Date Input", + "Dynamic value": "Dynamic value", + "Select an option": "Select an option", + "Unexpected error, please retry": "Unexpected error, please retry", + "Unexpected error, please refresh the page or contact support": "Unexpected error, please refresh the page or contact support", + "Name can only contain letters, numbers and underscores": "Name can only contain letters, numbers and underscores", + "Ask AI": "Ask AI", + "Create Todo Guide": "Create Todo Guide", + "Where would you like the todo to be reviewed?": "Where would you like the todo to be reviewed?", + "Activepieces Todos": "Activepieces Todos", + "Users will manage tasks directly in Activepieces": "Users will manage tasks directly in Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.", + "External Channel (Slack, Teams, Email, ...)": "External Channel (Slack, Teams, Email, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.", + "Preview (Activepieces Todos)": "Preview (Activepieces Todos)", + "Preview (External channel)": "Preview (External channel)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "You can add the channel before the Wait Step, and configure the logic in the Router step", + "Add Steps": "Add Steps", + "All": "All", + "AI": "AI", + "Core": "Core", + "Apps": "Apps", + "Not available as trigger": "Not available as trigger", + "Not available as action": "Not available as action", + "Let our AI assistant help you out": "Let our AI assistant help you out", + "Or": "Or", + "Request Piece": "Request Piece", + "No pieces found": "No pieces found", + "Please select a piece first": "Please select a piece first", + "All Iterations": "All Iterations", + "Duration": "Duration", + "Input": "Input", + "Output": "Output", + "There are no logs captured for this run.": "There are no logs captured for this run.", + "Logs are kept for {days} days after execution and then deleted.": "Logs are kept for {days} days after execution and then deleted.", + "Run Details": "Run Details", + "Iteration": "Iteration", + "Done": "Done", + "Took": "Took", + "Running": "Running", + "on latest version": "on latest version", + "from failed step": "from failed step", + "Recent Runs": "Recent Runs", + "No runs found": "No runs found", + "Close": "Close", + "OR": "OR", + "And If": "And If", + "+ And": "+ And", + "+ Or": "+ Or", + "(Text) Contains": "(Text) Contains", + "(Text) Does not contain": "(Text) Does not contain", + "(Text) Exactly matches": "(Text) Exactly matches", + "(Text) Does not exactly match": "(Text) Does not exactly match", + "(Text) Starts with": "(Text) Starts with", + "(Text) Does not start with": "(Text) Does not start with", + "(Text) Ends with": "(Text) Ends with", + "(Text) Does not end with": "(Text) Does not end with", + "(List) Contains": "(List) Contains", + "(List) Does not contain": "(List) Does not contain", + "(Number) Is greater than": "(Number) Is greater than", + "(Number) Is less than": "(Number) Is less than", + "(Number) Is equal to": "(Number) Is equal to", + "(Date/time) After": "(Date/time) After", + "(Date/time) Before": "(Date/time) Before", + "(Date/time) Equals": "(Date/time) Equals", + "(Boolean) Is true": "(Boolean) Is true", + "(Boolean) Is false": "(Boolean) Is false", + "(List) Is empty": "(List) Is empty", + "(List) Is not empty": "(List) Is not empty", + "Exists": "Exists", + "Does not exist": "Does not exist", + "Incomplete condition": "Incomplete condition", + "First value": "First value", + "Second value": "Second value", + "Case sensitive": "Case sensitive", + "Execute If": "Execute If", + "The package name is required": "The package name is required", + "Success": "Success", + "Package added successfully": "Package added successfully", + "Could not fetch package version": "Could not fetch package version", + "Add NPM Package": "Add NPM Package", + "Type the name of the npm package you want to add.": "Type the name of the npm package you want to add.", + "Package Name": "Package Name", + "The latest version will be fetched and added": "The latest version will be fetched and added", + "Add": "Add", + "Code": "Code", + "Dependencies": "Dependencies", + "Use code": "Use code", + "Add package": "Add package", + "Inputs": "Inputs", + "Edit Step Name": "Edit Step Name", + "Edit Branch Name": "Edit Branch Name", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.", + "Items": "Items", + "Select an array of items": "Select an array of items", + "Reconnect": "Reconnect", + "Select a connection": "Select a connection", + "Create Connection": "Create Connection", + "Rename": "Rename", + "Move": "Move", + "Add Branch": "Add Branch", + "Execute": "Execute", + "Only the first (left) matching branch": "Only the first (left) matching branch", + "All matching paths from left to right": "All matching paths from left to right", + "Branches": "Branches", + "{field} is required": "{field} is required", + "Tool Sample Data": "Tool Sample Data", + "Fill in the following fields to use them as sample data for the trigger.": "Fill in the following fields to use them as sample data for the trigger.", + "No input fields defined in the schema": "No input fields defined in the schema", + "Save": "Save", + "Test Environment": "Test Environment", + "Assigned to": "Assigned to", + "(Me)": "(Me)", + "Please select status to resolve the todo": "Please select status to resolve the todo", + "Resolve": "Resolve", + "Change status to resolved": "Change status to resolved", + "Send Sample Data to Webhook": "Send Sample Data to Webhook", + "Method": "Method", + "Query Params": "Query Params", + "Headers": "Headers", + "Body": "Body", + "Type": "Type", + "JSON": "JSON", + "Text": "Text", + "Form Data": "Form Data", + "Generate Sample Data": "Generate Sample Data", + "Test Step": "Test Step", + "Retest": "Retest", + "Testing Failed": "Testing Failed", + "Tested Successfully": "Tested Successfully", + "Logs": "Logs", + "There is no sample data available found for this trigger.": "There is no sample data available found for this trigger.", + "Internal error, please try again later.": "Internal error, please try again later.", + "Failed to run test step and no error message was returned": "Failed to run test step and no error message was returned", + "Please fix inputs first": "Please fix inputs first", + "No sample data available": "No sample data available", + "Old results were removed, retest for new sample data": "Old results were removed, retest for new sample data", + "Result #": "Result #", + "The sample data can be used in the next steps.": "The sample data can be used in the next steps.", + "Testing Trigger": "Testing Trigger", + "Action Required": "Action Required", + "testPieceWebhookTriggerNote": "Please go to {pieceName} and trigger {triggerName}.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Send Data to the webhook URL to generate sample data to use in the next steps", + "Test Trigger": "Test Trigger", + "Use Mock Data": "Use Mock Data", + "Load Sample Data": "Load Sample Data", + "Test Tool": "Test Tool", + "home": "home", + "Home": "Home", + "Alerts": "Alerts", + "Releases": "Releases", + "Flows": "Flows", + "Products": "Products", + "MCP": "MCP", + "Tables": "Tables", + "Todos": "Todos", + "Push to Git": "Push to Git", + "Move To": "Move To", + "Duplicating": "Duplicating", + "Import": "Import", + "Exporting": "Exporting", + "Export": "Export", + "Share": "Share", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.", + "You are on a development branch, this will also delete the flow from the remote repository.": "You are on a development branch, this will also delete the flow from the remote repository.", + "flow": "flow", + "Community Support": "Community Support", + "Overview": "Overview", + "Projects": "Projects", + "Users": "Users", + "Setup": "Setup", + "Branding": "Branding", + "Global Connections": "Global Connections", + "Pieces": "Pieces", + "Templates": "Templates", + "License Key": "License Key", + "Security": "Security", + "Audit Logs": "Audit Logs", + "Single Sign On": "Single Sign On", + "Signing Keys": "Signing Keys", + "Project Roles": "Project Roles", + "API Keys": "API Keys", + "Infrastructure": "Infrastructure", + "Workers": "Workers", + "Health": "Health", + "Billing": "Billing", + "Settings": "Settings", + "Contact Sales": "Contact Sales", + "General": "General", + "Appearance": "Appearance", + "Team": "Team", + "Environments": "Environments", + "Project Settings": "Project Settings", + "Exit Platform Admin": "Exit Platform Admin", + "Enter Platform Admin": "Enter Platform Admin", + "Logout": "Logout", + "Misc": "Misc", + "Connections": "Connections", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "Today": "Today", + "Tasks": "Tasks", + "AI Credits": "AI Credits", + "Usage resets in": "Usage resets in", + "Manage": "Manage", + "Unlimited": "Unlimited", + "Enabled": "Enabled", + "Disabled": "Disabled", + "Could not claim the authorization code, make sure you have correct settings and try again.": "Could not claim the authorization code, make sure you have correct settings and try again.", + "Connection failed with error {msg}": "Connection failed with error {msg}", + "You don't have the permission to create a connection.": "You don't have the permission to create a connection.", + "Reconnect {displayName} Connection": "Reconnect {displayName}", + "Connect to {displayName}": "Connect to {displayName}", + "Connection Name": "Connection Name", + "Connection name": "Connection name", + "New Connection": "New Connection", + "Redirect URL": "Redirect URL", + "Client ID": "Client ID", + "Client Secret": "Client Secret", + "Connect": "Connect", + "Disconnect": "Disconnect", + "I would like to use my own App Credentials": "I would like to use my own App Credentials", + "I would like to use predefined App Credentials": "I would like to use predefined App Credentials", + "Permission needed": "Permission needed", + "Connections replaced successfully": "Connections replaced successfully", + "Error": "Error", + "Failed to replace connections": "Failed to replace connections", + "Failed to get affected flows": "Failed to get affected flows", + "Please select a piece": "Please select a piece", + "Please select a connection to replace": "Please select a connection to replace", + "Please select a connection to replace with": "Please select a connection to replace with", + "Replace Connections": "Replace Connections", + "Confirm Replacement": "Confirm Replacement", + "Replace one connection with another.": "Replace one connection with another.", + "This action requires ": "This action requires ", + "reconnecting": "reconnecting", + " any associated MCP pieces.": " any associated MCP pieces.", + "Piece": "Piece", + "Select a piece": "Select a piece", + "Connection to Replace": "Connection to Replace", + "Choose connection to replace": "Choose connection to replace", + "Replaced With": "Replaced With", + "Choose connection to replace with": "Choose connection to replace with", + "All flows will be changed to use the replaced with connection": "All flows will be changed to use the replaced with connection", + "Next": "Next", + "No flows will be affected by this change": "No flows will be affected by this change", + "Back": "Back", + "Unnamed tool": "Unnamed tool", + "This flow is enabled": "This flow is enabled", + "Enable this flow to make it available": "Enable this flow to make it available", + "Piece is updated successfully": "Piece is updated successfully", + "Piece is added successfully": "Piece is added successfully", + "Failed to update piece": "Failed to update piece", + "Failed to add piece": "Failed to add piece", + "Please select a connection": "Please select a connection", + "Your MCP server already has this piece": "Your MCP server already has this piece", + "+ New Connection": "+ New Connection", + "Edit Piece": "Edit Piece", + "Add Piece": "Add Piece", + "Connection": "Connection", + "MCP piece": "MCP piece", + "Failed to update piece status": "Failed to update piece status", + "Connection required": "Connection required", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.", + "piece": "piece", + "Connect your AI assistant to external services": "Connect your AI assistant to external services", + "Collapse": "Collapse", + "Show All": "Show All", + "Server URL": "Server URL", + "Hide the token for security": "Hide the token for security", + "Show the token": "Show the token", + "Generate a new token for security. This will invalidate the current URL.": "Generate a new token for security. This will invalidate the current URL.", + "URL copied to clipboard": "URL copied to clipboard", + "Copy URL to clipboard": "Copy URL to clipboard", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.", + "Max tables reached": "Max tables reached", + "You can't create more than {maxTables} tables": "You can't create more than {maxTables} tables", + "Name": "Name", + "Created": "Created", + "Delete Tables": "Delete Tables", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Are you sure you want to delete the selected tables? This action cannot be undone.", + "table": "table", + "Create and manage your tables to store your automation data": "Create and manage your tables to store your automation data", + "New Table": "New Table", + "No tables have been created yet": "No tables have been created yet", + "Create a table to get started and start managing your automation data": "Create a table to get started and start managing your automation data", + "Error deleting connections": "Error deleting connections", + "Status": "Status", + "Display Name": "Display Name", + "Owner": "Owner", + "App": "App", + "This connection is global and can be managed in the platform admin": "This connection is global and can be managed in the platform admin", + "External ID": "External ID", + "Connected At": "Connected At", + "Confirm Deletion": "Confirm Deletion", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Are you sure you want to delete the selected connections? This action cannot be undone.", + "Deleting connections may cause your Flows or MCP tools to break.": "Deleting connections may cause your Flows or MCP tools to break.", + "Manage project connections to external systems.": "Manage project connections to external systems.", + "No connections found": "No connections found", + "Come back later when you create a automation to manage your connections": "Come back later when you create a automation to manage your connections", + "Steps": "Steps", + "Folder": "Folder", + "Flow name": "Flow name", + "No flows found": "No flows found", + "Create a workflow to start automating": "Create a workflow to start automating", + "Create and manage your flows, run history and run issues": "Create and manage your flows, run history and run issues", + "Issues": "Issues", + "Untitled": "Untitled", + "Create flow": "Create flow", + "From scratch": "From scratch", + "Use a template": "Use a template", + "From local file": "From local file", + "Flow Name": "Flow Name", + "Count": "Count", + "First Seen": "First Seen", + "Last Seen": "Last Seen", + "Issues in {flowDisplayName} is marked as resolved.": "Issues in {flowDisplayName} is marked as resolved.", + "Unlock Issues": "Unlock Issues", + "Track issues in your workflows and troubleshoot them.": "Track issues in your workflows and troubleshoot them.", + "No issues found": "No issues found", + "All your workflows are running smoothly.": "All your workflows are running smoothly.", + "Mark as Resolved": "Mark as Resolved", + "All Flows Are Turned Off": "All Flows Are Turned Off", + "Task Usage Exceeded": "Task Usage Exceeded", + "of the Allowed Limit.": "of the Allowed Limit.", + "When a project tasks limit is reached,": "When a project tasks limit is reached,", + "all flows will be turned off and you will not be able to run any flows.": "all flows will be turned off and you will not be able to run any flows.", + "Please visit": "Please visit", + "Your Plan": "Your Plan", + "and increase your task limit, which requires your payment details.": "and increase your task limit, which requires your payment details.", + "Please contact your admin to increase the project task limit.": "Please contact your admin to increase the project task limit.", + "and increase the project task limit.": "and increase the project task limit.", + "Dismiss": "Dismiss", + "Token rotated successfully": "Token rotated successfully", + "Failed to rotate token": "Failed to rotate token", + "Piece removed successfully": "Piece removed successfully", + "Failed to remove piece": "Failed to remove piece", + "Flow created successfully": "Flow created successfully", + "Failed to create flow": "Failed to create flow", + "Add Flow": "Add Flow", + "Let your AI assistant trigger automations": "Let your AI assistant trigger automations", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Connect to your hosted MCP Server using any MCP client to communicate with tools", + "MCP Server": "MCP Server", + "My Tools": "My Tools", + "Create Flow": "Create Flow", + "Note": "Note", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.", + "This URL grants access to your tools and data. Only share with trusted applications.": "This URL grants access to your tools and data. Only share with trusted applications.", + "Server Configuration": "Server Configuration", + "Hide sensitive data": "Hide sensitive data", + "Show sensitive data": "Show sensitive data", + "Create a new URL. The current one will stop working.": "Create a new URL. The current one will stop working.", + "Copy configuration": "Copy configuration", + "Configuration copied to clipboard": "Configuration copied to clipboard", + "Claude": "Claude", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "Server/Other", + "Note: MCPs only work with": "Note: MCPs only work with", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", not the web version.", + "Prerequisites:": "Prerequisites:", + "Install": "Install", + "Node.js": "Node.js", + "and": "and", + "Open Settings:": "Open Settings:", + "Click the menu and select": "Click the menu and select", + "Developer": "Developer", + "Configure MCP:": "Configure MCP:", + "Click": "Click", + "Edit Config": "Edit Config", + "and paste the configuration below": "and paste the configuration below", + "Save and Restart:": "Save and Restart:", + "Save the config and restart Claude Desktop": "Save the config and restart Claude Desktop", + "Navigate to": "Navigate to", + "Cursor Settings": "Cursor Settings", + "Add Server:": "Add Server:", + "Add new global MCP server": "Add new global MCP server", + "Configure:": "Configure:", + "Paste the configuration below and save": "Paste the configuration below and save", + "Use either method:": "Use either method:", + "Go to": "Go to", + "Advanced Settings": "Advanced Settings", + "Open Command Palette and select": "Open Command Palette and select", + "Windsurf Settings Page": "Windsurf Settings Page", + "Navigate to Cascade:": "Navigate to Cascade:", + "Select": "Select", + "Cascade": "Cascade", + "in the sidebar": "in the sidebar", + "Add Server": "Add Server", + "Add custom server +": "Add custom server +", + "Copy URL": "Copy URL", + "Client Setup Instructions": "Client Setup Instructions", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "After changing connections or flows, reconnect your MCP server for changes to take effect.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.", + "icon": "icon", + "Unlock Analytics": "Unlock Analytics", + "Get insights into your platform usage and performance with our analytics dashboard": "Get insights into your platform usage and performance with our analytics dashboard", + "Active Flows": "Active Flows", + "The number of enabled flows in the platform": "The number of enabled flows in the platform", + "Active Projects": "Active Projects", + "The number of projects with at least one enabled flow": "The number of projects with at least one enabled flow", + "Active Users": "Active Users", + "The number of users logged in the last 30 days": "The number of users logged in the last 30 days", + "Out of {totalusers} total users": "Out of {totalusers} total users", + "Pieces Used": "Pieces Used", + "The number of unique pieces used across all flows": "The number of unique pieces used across all flows", + "Flows with AI": "Flows with AI", + "The number of enabled flows that use AI pieces": "The number of enabled flows that use AI pieces", + "Metrics": "Metrics", + "Executed Tasks": "Executed Tasks", + "Showing total executed tasks for specified time range": "Showing total executed tasks for specified time range", + "Tasks Usage Limit": "Tasks Usage Limit", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.", + "Number of monthly tasks": "Number of monthly tasks", + "Save changes": "Save changes", + "Limits updated successfully": "Limits updated successfully", + "Failed to update limits": "Failed to update limits", + "Failed to load billing information": "Failed to load billing information", + "Billing Amount": "Billing Amount", + "Manage Payment Details": "Manage Payment Details", + "Add Payment Details": "Add Payment Details", + "Current Task Usage": "Current Task Usage", + "Count of executed steps": "Count of executed steps", + "Billing Limit": "Billing Limit", + "Edit": "Edit", + "Add Limit": "Add Limit", + "Current Credit Usage": "Current Credit Usage", + "WebSocket Connection": "WebSocket Connection", + "Connected": "Connected", + "Disconnected": "Disconnected", + "No issues detected": "No issues detected", + "Check the status of your platform and its components": "Check the status of your platform and its components", + "System Health Status": "System Health Status", + "All systems are running smoothly": "All systems are running smoothly", + "Check the health of your worker machines": "Check the health of your worker machines", + "Workers Machine": "Workers Machine", + "This is demo data. In a real environment, this would show your actual worker machines.": "This is demo data. In a real environment, this would show your actual worker machines.", + "No workers found": "No workers found", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "You don't have any worker machines yet. Spin up new machines to execute your automations", + "IP Address": "IP Address", + "CPU Usage": "CPU Usage", + "Disk Usage": "Disk Usage", + "RAM Usage": "RAM Usage", + "Last Contact": "Last Contact", + "Configs": "Configs", + "Environment Variables": "Environment Variables", + "Websocket Connection Error": "Websocket Connection Error", + "Retry Connection": "Retry Connection", + "Update Available": "Update Available", + "Update Now": "Update Now", + "Your Universal AI needs a quick setup": "Your Universal AI needs a quick setup", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.", + "Configure": "Configure", + "Platform Alerts": "Platform Alerts", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.", + "View Alerts": "View Alerts", + "Used Tasks": "Used Tasks", + "Used AI Credits": "Used AI Credits", + "Cannot delete active project, switch to another project first": "Cannot delete active project, switch to another project first", + "Delete Projects": "Delete Projects", + "Are you sure you want to delete the selected projects?": "Are you sure you want to delete the selected projects?", + "New Project": "New Project", + "Validation error": "Validation error", + "Project has enabled flows. Please disable them first.": "Project has enabled flows. Please disable them first.", + "This project is active. Please switch to another project first.": "This project is active. Please switch to another project first.", + "Unlock Projects": "Unlock Projects", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Orchestrate your automation teams across projects with their own flows, connections and usage quotas", + "Manage your automation projects": "Manage your automation projects", + "No projects found": "No projects found", + "Start by creating projects to manage your automation teams": "Start by creating projects to manage your automation teams", + "Edit project": "Edit project", + "Name is required": "Name is required", + "Create New Project": "Create New Project", + "Project Name": "Project Name", + "Id": "Id", + "Enable API Keys": "Enable API Keys", + "Create and manage API keys to access Activepieces APIs.": "Create and manage API keys to access Activepieces APIs.", + "New Api Key": "New Api Key", + "No API keys found": "No API keys found", + "Start by creating an API key to communicate with Activepieces APIs": "Start by creating an API key to communicate with Activepieces APIs", + "Delete API Key": "Delete API Key", + "Are you sure you want to delete this API key?": "Are you sure you want to delete this API key?", + "API Key": "API Key", + "API Key Created": "API Key Created", + "Create New API Key": "Create New API Key", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Please save this secret key somewhere safe and accessible. For security reasons,", + "you won't be able to view it again after closing this dialog.": "you won't be able to view it again after closing this dialog.", + "API Key Name": "API Key Name", + "Action": "Action", + "Performed By": "Performed By", + "Project": "Project", + "Unlock Audit Logs": "Unlock Audit Logs", + "Comply with internal and external security policies by tracking activities done within your account": "Comply with internal and external security policies by tracking activities done within your account", + "Track activities done within your platform": "Track activities done within your platform", + "No audit logs found": "No audit logs found", + "Come back later when you have some activity to audit": "Come back later when you have some activity to audit", + "Resource": "Resource", + "Details": "Details", + "N/A": "N/A", + "Flow Run": "Flow Run", + "Flow": "Flow", + "User": "User", + "Signing Key": "Signing Key", + "Project Role Management": "Project Role Management", + "Define custom roles and permissions to control what your team members can access and modify": "Define custom roles and permissions to control what your team members can access and modify", + "Define custom roles and permissions that can be assigned to your team members": "Define custom roles and permissions that can be assigned to your team members", + "New Role": "New Role", + "Contact sales to unlock custom roles": "Contact sales to unlock custom roles", + "Create New Role": "Create New Role", + "View ": "View ", + "Edit ": "Edit ", + "Role Name": "Role Name", + "Permissions": "Permissions", + "None": "None", + "Read": "Read", + "Write": "Write", + "Create": "Create", + "Email": "Email", + "First Name": "First Name", + "Last Name": "Last Name", + "Roles": "Roles", + "View the users assigned to this role": "View the users assigned to this role", + "Role": "Role", + "No users found": "No users found", + "Starting by assigning users to this role": "Starting by assigning users to this role", + "Project Role entry deleted successfully": "Project Role entry deleted successfully", + "Updated": "Updated", + "No project roles found": "No project roles found", + "Create custom project roles to manage permissions for platform users": "Create custom project roles to manage permissions for platform users", + "Show Users": "Show Users", + "View Role": "View Role", + "Edit Role": "Edit Role", + "Delete Role": "Delete Role", + "Project Role": "Project Role", + "Unlock Embedding Through JS SDK": "Unlock Embedding Through JS SDK", + "Enable signing keys to access embedding functionalities.": "Enable signing keys to access embedding functionalities.", + "New Signing Key": "New Signing Key", + "No signing keys found": "No signing keys found", + "Create a signing key to authenticate users with embedding": "Create a signing key to authenticate users with embedding", + "Delete Signing Key": "Delete Signing Key", + "Are you sure you want to delete this signing key?": "Are you sure you want to delete this signing key?", + "Signing Key Created": "Signing Key Created", + "Create New Signing Key": "Create New Signing Key", + "Signing Key Name": "Signing Key Name", + "Allowed domains updated": "Allowed domains updated", + "Update": "Update", + "Enable": "Enable", + "Configure Allowed Domains": "Configure Allowed Domains", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.", + "Add Domain": "Add Domain", + "Allow logins through {providerName}'s single sign-on functionality.": "Allow logins through {providerName}'s single sign-on functionality.", + "Email authentication updated": "Email authentication updated", + "Enable Single Sign On": "Enable Single Sign On", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Let your users sign in with your current SSO provider or give them self serve sign up access", + "Allowed Domains": "Allowed Domains", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Allow users to authenticate with specific domains. Leave empty to allow all domains.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Allowed Email Login", + "Allow logins through email and password.": "Allow logins through email and password.", + "Single sign on settings updated": "Single sign on settings updated", + "Disable": "Disable", + "Configure {provider} SSO": "Configure {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} Client ID", + "{provider} Client Secret": "{provider} Client Secret", + "Single sign-on settings updated": "Single sign-on settings updated", + "Configure SAML 2.0 SSO": "Configure SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDP Metadata", + "IDP Certificate": "IDP Certificate", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "Base URL", + "Resource Name": "Resource Name", + "Deployment Name": "Deployment Name", + "Saving": "Saving", + "Activepieces Copilot": "Activepieces Copilot", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot is configured and ready to help your users build flows faster using AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configure Activepieces Copilot to help your users build flows faster using AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Set provider credentials that will be used by universal AI pieces, i.e Text AI.", + "AI Providers": "AI Providers", + "Copilot": "Copilot", + "Configure credentials for {providerName} AI provider.": "Configure credentials for {providerName} AI provider.", + "Update AI Provider": "Update AI Provider", + "Enable AI Provider": "Enable AI Provider", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Please enter a valid domain", + "Your changes have been saved.": "Your changes have been saved.", + "The domain is already added.": "The domain is already added.", + "Add Custom Domain": "Add Custom Domain", + "Enter a domain name without a protocol (e.g. example.com)": "Enter a domain name without a protocol (e.g. example.com)", + "Logo URL": "Logo URL", + "Icon URL": "Icon URL", + "Favicon URL": "Favicon URL", + "Default Language": "Default Language", + "Select Language": "Select Language", + "No Languages": "No Languages", + "Primary Color": "Primary Color", + "Custom Domains": "Custom Domains", + "No domains added yet.": "No domains added yet.", + "Verified": "Verified", + "Pending, please contact the support for dns verification.": "Pending, please contact the support for dns verification.", + "Are you sure you want to delete {domain}?": "Are you sure you want to delete {domain}?", + "Brand Activepieces": "Brand Activepieces", + "Give your users an experience that looks like you by customizing the color, logo and more": "Give your users an experience that looks like you by customizing the color, logo and more", + "Configure the appearance and SMTP settings for your platform.": "Configure the appearance and SMTP settings for your platform.", + "Invalid host": "Invalid host", + "Invalid username": "Invalid username", + "Invalid password": "Invalid password", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Invalid sender name", + "SMTP is configured": "SMTP is configured", + "Mail Server": "Mail Server", + "Set up your SMTP settings to send emails from your domain.": "Set up your SMTP settings to send emails from your domain.", + "Disable Mail Server": "Disable Mail Server", + "Are you sure you want to disable your mail server?": "Are you sure you want to disable your mail server?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "This will stop you from sending emails for issues, quota limits, invitations and forgot password.", + "mail server": "mail server", + "Host": "Host", + "Port": "Port", + "Username": "Username", + "Password": "Password", + "Sender Email": "Sender Email", + "Sender Name": "Sender Name", + "Enable Global Connections": "Enable Global Connections", + "Manage platform-wide connections to external systems.": "Manage platform-wide connections to external systems.", + "No global connections found": "No global connections found", + "Create a global connection that can be shared to multiple projects": "Create a global connection that can be shared to multiple projects", + "License key is invalid": "License key is invalid", + "Invalid license key": "Invalid license key", + "License activated!": "License activated!", + "Activate License Key": "Activate License Key", + "Let the magic begin!": "Let the magic begin!", + "Activate": "Activate", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ", + "This feature is not available in your current edition. ": "This feature is not available in your current edition. ", + "Learn how to upgrade": "Learn how to upgrade", + "Activate your platform and unlock enterprise features": "Activate your platform and unlock enterprise features", + "Activate License": "Activate License", + "Expiration": "Expiration", + "Valid until": "Valid until", + "Expired": "Expired", + "Expires soon": "Expires soon", + "Features": "Features", + "Applying Tags...": "Applying Tags...", + "Tags applied.": "Tags applied.", + "Tag created": "Tag created", + "Tag": "Tag", + "Tags": "Tags", + "Control Pieces": "Control Pieces", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Show the pieces that matter most to your users and hide the ones you don't like.", + "Manage the pieces that are available to your users": "Manage the pieces that are available to your users", + "Start by installing pieces that you want to use in your automations": "Start by installing pieces that you want to use in your automations", + "Piece Name": "Piece Name", + "Hide this piece from all projects": "Hide this piece from all projects", + "Show this piece for all projects": "Show this piece for all projects", + "Unpin this piece": "Unpin this piece", + "Pin this piece": "Pin this piece", + "Pieces synced": "Pieces synced", + "Pieces have been synced from the activepieces cloud.": "Pieces have been synced from the activepieces cloud.", + "OAuth2 Credentials Deleted": "OAuth2 Credentials Deleted", + "OAuth2 Credentials Updated": "OAuth2 Credentials Updated", + "Configure OAuth2 APP": "Configure OAuth2 APP", + "Delete OAuth2 APP": "Delete OAuth2 APP", + "Templates deleted successfully": "Templates deleted successfully", + "Delete Templates": "Delete Templates", + "Are you sure you want to delete the selected templates?": "Are you sure you want to delete the selected templates?", + "New Template": "New Template", + "Unlock Templates": "Unlock Templates", + "Convert the most common automations into reusable templates 1 click away from your users": "Convert the most common automations into reusable templates 1 click away from your users", + "Convert the most common automations into reusable templates": "Convert the most common automations into reusable templates", + "No templates found": "No templates found", + "Create a template for your user to inspire them": "Create a template for your user to inspire them", + "Edit template": "Edit template", + "Template is required": "Template is required", + "Update New Template": "Update New Template", + "Create New Template": "Create New Template", + "Template Name": "Template Name", + "Description": "Description", + "Template Description": "Template Description", + "Blog URL": "Blog URL", + "Template Blog URL": "Template Blog URL", + "Template": "Template", + "Invalid JSON": "Invalid JSON", + "User deleted successfully": "User deleted successfully", + "User activated successfully": "User activated successfully", + "User deactivated successfully": "User deactivated successfully", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Manage your users and their access to your projects", + "Start inviting users to your project": "Start inviting users to your project", + "External Id": "External Id", + "Admin": "Admin", + "Member": "Member", + "Activated": "Activated", + "Deactivated": "Deactivated", + "Edit user": "Edit user", + "Admin cannot be deactivated": "Admin cannot be deactivated", + "Deactivate user": "Deactivate user", + "Activate user": "Activate user", + "Delete User": "Delete User", + "Are you sure you want to delete this user?": "Are you sure you want to delete this user?", + "Delete user": "Delete user", + "Update User Role": "Update User Role", + "Meeting Summary Flow": "Meeting Summary Flow", + "Added new features and fixed bugs": "Added new features and fixed bugs", + "Flows Changes": "Flows Changes", + "Connections Changes": "Connections Changes", + "New connections are placeholders and need to be reconnected again": "New connections are placeholders and need to be reconnected again", + "renamed to": "renamed to", + "Tables Changes": "Tables Changes", + "No changes to apply": "No changes to apply", + "Apply Changes": "Apply Changes", + "Create Git Release": "Create Git Release", + "Create Project Release": "Create Project Release", + "Create Rollback to": "Create Rollback to", + "Source": "Source", + "Rollback": "Rollback", + "Imported At": "Imported At", + "Imported By": "Imported By", + "Track and manage your project version history and deployments. ": "Track and manage your project version history and deployments. ", + "Environments & Releases": "Environments & Releases", + "Project Releases": "Project Releases", + "Create Release": "Create Release", + "From Git": "From Git", + "From Project": "From Project", + "No project releases found": "No project releases found", + "Create a project release to get started": "Create a project release to get started", + "Please select project": "Please select project", + "No Changes Found": "No Changes Found", + "There are no differences to apply": "There are no differences to apply", + "Please select a project": "Please select a project", + "Search projects...": "Search projects...", + "Review Changes": "Review Changes", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Imported by", + "from": "from", + "No description provided": "No description provided", + "Invitation only sign up": "Invitation only sign up", + "Please ask your administrator to add you to the organization.": "Please ask your administrator to add you to the organization.", + "Something went wrong, please try again.": "Something went wrong, please try again.", + "Please try again.": "Please try again.", + "Please enter a valid email address": "Please enter a valid email address", + "The email is already added.": "The email is already added.", + "Add email": "Add email", + "Only project admins can do this": "Only project admins can do this", + "Add Alert Email": "Add Alert Email", + "Enter the email address to receive alerts.": "Enter the email address to receive alerts.", + "Add Email": "Add Email", + "Emails": "Emails", + "Add email addresses to receive alerts.": "Add email addresses to receive alerts.", + "No emails added yet.": "No emails added yet.", + "Choose what you want to be notified about.": "Choose what you want to be notified about.", + "Project and alert permissions are required to change this setting.": "Project and alert permissions are required to change this setting.", + "Every Failed Run": "Every Failed Run", + "Get an email alert when a flow fails.": "Get an email alert when a flow fails.", + "Get an email alert when a new issue created.": "Get an email alert when a new issue created.", + "Never": "Never", + "Turn off email notifications.": "Turn off email notifications.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Customize the appearance of the app. Automatically switch between day and night themes.", + "Select the theme for the dashboard.": "Select the theme for the dashboard.", + "Light": "Light", + "Dark": "Dark", + "Select the language that will be used in the dashboard.": "Select the language that will be used in the dashboard.", + "Select language": "Select language", + "Search language...": "Search language...", + "No language found.": "No language found.", + "Help us translate Activepieces to your language.": "Help us translate Activepieces to your language.", + "Learn more": "Learn more", + "Git Connection Removed": "Git Connection Removed", + "Your Git repository has been successfully disconnected": "Your Git repository has been successfully disconnected", + "Enable Environments": "Enable Environments", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Deploy flows across development, staging and production environments with version control and team collaboration", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Connect to Git to enable version control, backup your flows, and manage multiple environments. ", + "Repository URL": "Repository URL", + "Not connected": "Not connected", + "Project Folder": "Project Folder", + "Releases Enabled": "Releases Enabled", + "You have successfully enabled releases": "You have successfully enabled releases", + "Enable releases to easily create and manage project releases.": "Enable releases to easily create and manage project releases.", + "The external ID is already taken.": "The external ID is already taken.", + "Manage general settings for your project.": "Manage general settings for your project.", + "Used to identify the project based on your SaaS ID": "Used to identify the project based on your SaaS ID", + "org-3412321": "org-3412321", + "Delete {name}": "Delete {name}", + "This will permanently delete this piece, all steps using it will fail.": "This will permanently delete this piece, all steps using it will fail.", + "Add a piece to your project that you want to use in your automations": "Add a piece to your project that you want to use in your automations", + "Pieces list updated": "Pieces list updated", + "Manage Pieces": "Manage Pieces", + "Choose which pieces you want to be available for your current project users": "Choose which pieces you want to be available for your current project users", + "Unlock Team Permissions": "Unlock Team Permissions", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial", + "Project Members": "Project Members", + "Invite your team members to collaborate.": "Invite your team members to collaborate.", + "No members are added to this project.": "No members are added to this project.", + "Pending Invitations": "Pending Invitations", + "No pending invitation.": "No pending invitation.", + "templateId is missing": "templateId is missing", + "Me Only": "Me Only", + "Unresolved": "Unresolved", + "Resolved": "Resolved", + "Title": "Title", + "Date Created": "Date Created", + "Manage todos for your project that are created by automations": "Manage todos for your project that are created by automations", + "No todos found": "No todos found", + "You do not have any pending todos. Great job!": "You do not have any pending todos. Great job!", + "Write a comment...": "Write a comment...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "This feature is still under testing and might be changed often", + "Failed to copy to clipboard": "Failed to copy to clipboard", + "{number} items selected": "{number} items selected", + "Select All": "Select All", + "No results found.": "No results found.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect must be used within MultiSelectProvider", + "Unset": "Unset", + "Refresh": "Refresh", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} more", + "Removed {entityName}": "Removed {entityName}", + "Download File": "Download File", + "Copied to clipboard": "Copied to clipboard", + "File is not available after execution.": "File is not available after execution.", + "Available for Projects": "Available for Projects", + "Select projects": "Select projects", + "No items": "No items", + "Previous": "Previous", + "to": "to", + "Last Week": "Last Week", + "Last Month": "Last Month", + "Last 3 Months": "Last 3 Months", + "Last 6 Months": "Last 6 Months", + "Next 7 days": "Next 7 days", + "Next 30 days": "Next 30 days", + "Next 90 days": "Next 90 days", + "Next 180 days": "Next 180 days", + "Select Time Range": "Select Time Range", + "Clear": "Clear", + "Download": "Download", + "Go to Dashboard": "Go to Dashboard", + "Select a file": "Select a file", + "Press space to separate values": "Press space to separate values", + "AM": "AM", + "PM": "PM", + "Already have an account?": "Already have an account?", + "Sign in": "Sign in", + "Don't have an account?": "Don't have an account?", + "Sign up": "Sign up", + "Welcome Back!": "Welcome Back!", + "Enter your email below to sign in to your account": "Enter your email below to sign in to your account", + "Let's Get Started!": "Let's Get Started!", + "Create your account and start flowing!": "Create your account and start flowing!", + "Your password was changed successfully": "Your password was changed successfully", + "Your password reset request has expired, please request a new one": "Your password reset request has expired, please request a new one", + "Reset Password": "Reset Password", + "Enter your new password": "Enter your new password", + "Password is required": "Password is required", + "Verification email resent, if previous one expired.": "Verification email resent, if previous one expired.", + "Password reset link resent, if previous one expired.": "Password reset link resent, if previous one expired.", + "We sent you a link to complete your registration to": "We sent you a link to complete your registration to", + "We sent you a link to reset your password to": "We sent you a link to reset your password to", + "Didn't receive an email or it expired?": "Didn't receive an email or it expired?", + "Resend": "Resend", + "Please enter your email": "Please enter your email", + "Check Your Inbox": "Check Your Inbox", + "If the user exists we'll send you an email with a link to reset your password.": "If the user exists we'll send you an email with a link to reset your password.", + "Send Password Reset Link": "Send Password Reset Link", + "Back to sign in": "Back to sign in", + "Email is invalid": "Email is invalid", + "Something went wrong, please try again later": "Something went wrong, please try again later", + "Invalid email or password": "Invalid email or password", + "User has been deactivated": "User has been deactivated", + "Email domain is disallowed": "Email domain is disallowed", + "Email authentication has been disabled": "Email authentication has been disabled", + "Forgot your password?": "Forgot your password?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "Sign up is restricted. You need an invitation to join. Please contact the administrator.", + "Email is already used": "Email is already used", + "Email authentication is disabled": "Email authentication is disabled", + "First name is required": "First name is required", + "Last name is required": "Last name is required", + "Email is required": "Email is required", + "Receive updates and newsletters from activepieces": "Receive updates and newsletters from activepieces", + "By creating an account, you agree to our": "By creating an account, you agree to our", + "terms of service": "terms of service", + "privacy policy": "privacy policy", + "Sign up With": "Sign up With", + "Google": "Google", + "Sign in With": "Sign in With", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "Email has been verified. You will be redirected to sign in...", + "Verifying email...": "Verifying email...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "invitation has expired, once you sign in again you will be able to resend the verification email.", + "Redirecting to sign in...": "Redirecting to sign in...", + "Password must contain at least one special character": "Password must contain at least one special character", + "Password must contain at least one lowercase letter": "Password must contain at least one lowercase letter", + "Password must contain at least one uppercase letter": "Password must contain at least one uppercase letter", + "Password must contain at least one number": "Password must contain at least one number", + "8-64 Characters": "8-64 Characters", + "Special Character": "Special Character", + "Lowercase": "Lowercase", + "Uppercase": "Uppercase", + "Number": "Number", + "Connection has been updated.": "Connection has been updated.", + "Edit Global Connection": "Edit Global Connection", + "Connection has been renamed.": "Connection has been renamed.", + "New Connection Name": "New Connection Name", + "Connection name already used": "Connection name already used", + "Please select at least one project": "Please select at least one project", + "Run Succeeded": "Run Succeeded", + "Run Failed": "Run Failed", + "Flow Run is paused": "Flow Run is paused", + "Run Failed due to quota exceeded": "Run Failed due to quota exceeded", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Run failed due to exceeding the memory limit of {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "Run exceeded {timeout} seconds, try to optimize your steps.", + "Run failed for an unknown reason, contact support.": "Run failed for an unknown reason, contact support.", + "Unknown": "Unknown", + "Exit Run": "Exit Run", + "Select shown": "Select shown", + "Select all": "Select all", + "Start Time": "Start Time", + "Runs replayed successfully": "Runs replayed successfully", + "Retry": "Retry", + "all except": "all except", + "all": "all", + "Only failed runs can be retried from failed step": "Only failed runs can be retried from failed step", + "No flow runs found": "No flow runs found", + "Come back later when your automations start running": "Come back later when your automations start running", + "Step running": "Step running", + "Step paused": "Step paused", + "Step Stopped": "Step Stopped", + "Step Succeeded": "Step Succeeded", + "Step Failed": "Step Failed", + "Please publish flow first": "Please publish flow first", + "Flow is on": "Flow is on", + "Flow is off": "Flow is off", + "Permission Needed": "Permission Needed", + "Draft Version": "Draft Version", + "Published Version": "Published Version", + "Locked Version": "Locked Version", + "flowsImported": "{flowsCount, plural, =0 {No Flows Imported} =1 {Flow Imported Successfully} other {Flows Imported Successfully}}", + "Template file is invalid": "Template file is invalid", + "No valid templates found. The following files failed to import: ": "No valid templates found. The following files failed to import: ", + "Please select a file first": "Please select a file first", + "Unsupported file type": "Unsupported file type", + "Import Flow": "Import Flow", + "Warning": "Warning", + "Importing a flow will overwrite your current one.": "Importing a flow will overwrite your current one.", + "Select a folder": "Select a folder", + "Folders": "Folders", + "Please select a folder": "Please select a folder", + "Moved flows successfully": "Moved flows successfully", + "Move Selected Flows": "Move Selected Flows", + "Select Folder": "Select Folder", + "No Folders": "No Folders", + "Flow has been renamed.": "Flow has been renamed.", + "New Flow Name": "New Flow Name", + "Use Template": "Use Template", + "Browse Templates": "Browse Templates", + "Search templates": "Search templates", + "No templates found, try adjusting your search": "No templates found, try adjusting your search", + "Read more about this template in": "Read more about this template in", + "this blog!": "this blog!", + "Share Template": "Share Template", + "Generate or update a template link for the current flow to easily share it with others.": "Generate or update a template link for the current flow to easily share it with others.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "The template will not have any credentials in connection fields, keeping sensitive information secure.", + "A short description of the template": "A short description of the template", + "Flow Is In Use": "Flow Is In Use", + "Flow is being used by another user, please try again later.": "Flow is being used by another user, please try again later.", + "Flow has been published.": "Flow has been published.", + "Flows have been exported.": "Flows have been exported.", + "Run": "Run", + "Real time flow": "Real time flow", + "Flow can't be published with empty trigger {name}": "Flow can't be published with empty trigger {name}", + "Please contact support as your published flow has a problem": "Please contact support as your published flow has a problem", + "Actions": "Actions", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.", + "You are on a development branch, this will not delete the flows from the remote repository.": "You are on a development branch, this will not delete the flows from the remote repository.", + "Please enter folder name": "Please enter folder name", + "Added folder successfully": "Added folder successfully", + "The folder name already exists.": "The folder name already exists.", + "New Folder": "New Folder", + "Folder Name": "Folder Name", + "Loading...": "Loading...", + "Delete {folderName}": "Delete {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "If you delete this folder, we will keep its flows and move them to Uncategorized.", + "All flows": "All flows", + "Please enter a folder name": "Please enter a folder name", + "Renamed flow successfully": "Renamed flow successfully", + "Folder name already used": "Folder name already used", + "New Folder Name": "New Folder Name", + "Connected successfully": "Connected successfully", + "Connect Git": "Connect Git", + "Remote URL": "Remote URL", + "Folder name is the name of the folder where the project will be stored or fetched.": "Folder name is the name of the folder where the project will be stored or fetched.", + "SSH Private Key": "SSH Private Key", + "The SSH private key to use for authentication.": "The SSH private key to use for authentication.", + "Only published flows can be pushed to Git": "Only published flows can be pushed to Git", + "Pushed successfully": "Pushed successfully", + "Invalid Operation": "Invalid Operation", + "Commit Message": "Commit Message", + "Enter a commit message to describe the changes you want to push.": "Enter a commit message to describe the changes you want to push.", + "Push": "Push", + "This field is required": "This field is required", + "Your submission was successfully received.": "Your submission was successfully received.", + "Flow not found": "Flow not found", + "The flow you are trying to submit to does not exist.": "The flow you are trying to submit to does not exist.", + "The flow failed to execute.": "The flow failed to execute.", + "Submit": "Submit", + "issues-notification": "issues-notification", + "Please select a package type": "Please select a package type", + "package.json not found in archive": "package.json not found in archive", + "Error processing archive file": "Error processing archive file", + "Please upload a .tgz file": "Please upload a .tgz file", + "Piece name is required for NPM Registry": "Piece name is required for NPM Registry", + "Piece version is required for NPM Registry": "Piece version is required for NPM Registry", + "Piece installed": "Piece installed", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "A piece with this name and version is already installed. Please update the version number in package.json and try again.", + "Install Piece": "Install Piece", + "Install a piece": "Install a piece", + "Package Type": "Package Type", + "NPM Registry": "NPM Registry", + "Packed Archive (.tgz)": "Packed Archive (.tgz)", + "Piece Version": "Piece Version", + "Package Archive": "Package Archive", + "Package archive": "Package archive", + "Powerful Node.js & TypeScript code with npm": "Powerful Node.js & TypeScript code with npm", + "Loop on Items": "Loop on Items", + "Split your flow into branches depending on condition(s)": "Split your flow into branches depending on condition(s)", + "Empty Trigger": "Empty Trigger", + "An internal error occurred while fetching data, please contact support": "An internal error occurred while fetching data, please contact support", + "An internal error occurred, please contact support": "An internal error occurred, please contact support", + "Custom Javascript Code": "Custom Javascript Code", + "Router": "Router", + "recordsCount": "{recordsCount, plural, =0 {0 Records} =1 {1 Record} other {# Records}}", + "selected": "selected", + "All records selected": "All records selected", + "fieldsCount": "{fieldsCount, plural, =0 {0 Fields} =1 {1 Field} other {# Fields}}", + "Saving...": "Saving...", + "Loading more...": "Loading more...", + "Export Table": "Export Table", + "Delete Records": "Delete Records", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Are you sure you want to delete the selected records? This action cannot be undone.", + "record": "record", + "records": "records", + "mm/dd/yyy": "mm/dd/yyy", + "Delete Field": "Delete Field", + "Are you sure you want to delete this field? This action cannot be undone.": "Are you sure you want to delete this field? This action cannot be undone.", + "field": "field", + "Ignored": "Ignored", + "Table": "Table", + "CSV": "CSV", + "Field": "Field", + "Please select a csv file": "Please select a csv file", + "Max file size is {maxFileSize}MB": "Max file size is {maxFileSize}MB", + "Import CSV": "Import CSV", + "Imported records will be added to the bottom of the table": "Imported records will be added to the bottom of the table", + "Any records after the limit ({maxRecords} records) will be ignored": "Any records after the limit ({maxRecords} records) will be ignored", + "CSV File": "CSV File", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support", + "Name must be unique": "Name must be unique", + "Type is required": "Type is required", + "Please add at least one option": "Please add at least one option", + "New Field": "New Field", + "Options": "Options", + "Name is already taken": "Name is already taken", + "Table renamed": "Table renamed", + "Table name": "Table name", + "Team Invitation Accepted": "Team Invitation Accepted", + "Thank you for accepting the invitation. We are redirecting you right now...": "Thank you for accepting the invitation. We are redirecting you right now...", + "Invalid invitation token. Please try again.": "Invalid invitation token. Please try again.", + "Role updated successfully": "Role updated successfully", + "Error updating role": "Error updating role", + "Please try again later": "Please try again later", + "Edit Role for": "Edit Role for", + "Select Role": "Select Role", + "Avatar": "Avatar", + "Remove {email}": "Remove {email}", + "Are you sure you want to remove this invitation?": "Are you sure you want to remove this invitation?", + "Please select invitation type": "Please select invitation type", + "Please select platform role": "Please select platform role", + "Invitation sent successfully": "Invitation sent successfully", + "Please select a project role": "Please select a project role", + "Invitation link copied successfully": "Invitation link copied successfully", + "Invite User": "Invite User", + "Invitation Link": "Invitation Link", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Type the email address of the user you want to invite, the invitation expires in 24 hours.", + "Invite To": "Invite To", + "Entire Platform": "Entire Platform", + "Select Project Role": "Select Project Role", + "Invite": "Invite", + "Platform Role": "Platform Role", + "Select a platform role": "Select a platform role", + "Are you sure you want to remove this member?": "Are you sure you want to remove this member?", + "Editor": "Editor", + "Operator": "Operator", + "Viewer": "Viewer", + "Select a project role": "Select a project role", + "Steps in this flow": "Steps in this flow", + "Invalid Access": "Invalid Access", + "Either the project does not exist or you do not have access to it.": "Either the project does not exist or you do not have access to it." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/ja/translation.json b/packages/react-ui/public/locales/ja/translation.json new file mode 100644 index 0000000..08c573e --- /dev/null +++ b/packages/react-ui/public/locales/ja/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "公開する", + "Latest version is published": "最新バージョンが公開されました", + "Your flow has incomplete steps": "フローに不完全なステップがあります", + "Edit Flow": "フローを編集", + "View Draft": "下書きを表示", + "Uncategorized": "未分類", + "Go to folder": "フォルダへ移動", + "Support": "サポート", + "Runs": "実行", + "Run Logs": "ログ実行", + "Versions": "バージョン", + "Versions History": "バージョン履歴", + "Error generating code": "コードの生成中にエラー", + "AI Copilot": "AI コパイロット", + "i.e Calculate the sum of a list...": "i.e リストの合計を計算...", + "Send": "送信", + "Generating Code": "コードを生成中", + "Hello there! I am here to generate code that helps with your flow": "こんにちは!フローに役立つコードを生成するためにここにいます", + "Here are examples of what I am best used for: ": "私が最もよく使われているものは次のとおりです: ", + "Text Processing": "テキスト処理", + "Process strings, dates and data": "処理文字列、日付とデータ", + "Data Operations": "データ操作", + "Change data from one format to another": "あるフォーマットから別のフォーマットにデータを変更", + "Calculations": "計算", + "Handle math and statistics": "数学と統計の処理", + "API Integration": "API連携", + "Connect with external services. Best for simple integrations currently.": "外部サービスと接続します。現在シンプルな統合に最適です。", + "What would you like me to help you with?": "何をお手伝いしましょうか。", + "Insert": "挿入", + "Data Selector": "データセレクター", + "Search": "検索", + "No matching data": "一致するデータがありません", + "Try adjusting your search": "検索を調整してみてください", + "This trigger needs to have data loaded from your account, to use as sample data.": "サンプルデータとして使用するには、アカウントからデータを読み込む必要があります。", + "This step needs to be tested in order to view its data.": "このステップは、データを表示するためにテストする必要があります。", + "Go to Trigger": "トリガーへ移動", + "Go to Step": "ステップに進む", + "Select Mode": "モードを選択", + "Move Mode": "移動モード", + "Reset Zoom": "Reset Zoom", + "Zoom In": "拡大", + "Zoom Out": "ズームアウト", + "Fit to View": "表示に合わせる", + "Replace": "置換", + "Copy": "コピー", + "Duplicate": "Duplicate", + "Paste After Last Step": "最後のステップの後に貼り付け", + "Paste Inside Loop": "ループ内に貼り付け", + "Paste After": "後に貼り付け", + "Paste Inside...": "内部に貼り付け...", + "New Branch": "新しいブランチ", + "Paste Inside Branch": "ブランチ内に貼り付け", + "Delete": "削除", + "Duplicate Branch": "ブランチを複製", + "Delete Branch": "ブランチを削除", + "Invalid Move": "無効な移動です", + "The destination location is a child of the dragged step": "移動先の場所はドラッグされたステップの子です", + "End": "終了", + "Skipped": "スキップ", + "Incomplete settings": "不完全な設定", + "logo": "ロゴ", + "Step Icon": "ステップアイコン", + "Branch": "ブランチ", + "incompleteSteps": "{invalidSteps, plural, =0 {未完成のステップはありません} =1 {1ステップ完了} other {#ステップ完了}}", + "Test Flow": "テストフロー", + "Please test the trigger first": "最初にトリガーをテストしてください", + "View Only": "表示のみ", + "Use as Draft": "下書きとして使用", + "Are you sure?": "よろしいですか?", + "Your current draft version will be overwritten with": "現在の下書きバージョンは上書きされます", + "version #": "version #", + "Cancel": "キャンセル", + "Confirm": "確認する", + "Version": "バージョン", + "Viewing": "表示", + "View": "表示", + "Version History": "バージョン履歴", + "Error, please try again.": "エラーが発生しました。もう一度やり直してください。", + "Continue on Failure": "失敗時に続ける", + "Enable this option to skip this step and continue the flow normally if it fails.": "このオプションを有効にすると、このステップをスキップし、失敗した場合は正常にフローを継続します。", + "Retry on Failure": "失敗時に再試行", + "Automatically retry up to four attempts when failed.": "失敗したときに自動的に最大 4 回の試行を再試行します。", + "Remove": "削除", + "Add Item": "アイテムを追加", + "File Input": "ファイル入力", + "Date Input": "Date Input", + "Dynamic value": "動的値", + "Select an option": "オプションを選択", + "Unexpected error, please retry": "予期しないエラーが発生しました。再試行してください。", + "Unexpected error, please refresh the page or contact support": "予期しないエラー。ページを更新するか、サポートにお問い合わせください。", + "Name can only contain letters, numbers and underscores": "名前には文字、数字、アンダースコアのみを含めることができます", + "Ask AI": "AIに聞く", + "Create Todo Guide": "Todo ガイドを作成", + "Where would you like the todo to be reviewed?": "どこでタスクをレビューしますか?", + "Activepieces Todos": "Activeepieces Todos", + "Users will manage tasks directly in Activepieces": "ユーザーはActivepiecsで直接タスクを管理します", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "ユーザーはActivepiecesインターフェイス内で直接タスクを管理し、対応します。社内チームに最適です。", + "External Channel (Slack, Teams, Email, ...)": "外部チャンネル (Slack, Teams, Email, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Slack、Teams、Emailなどの外部チャンネルを通じて承認リンクで通知を送信します。外部のステークホルダーとのコラボレーションに最適です。", + "Preview (Activepieces Todos)": "プレビュー(アクティブなトドス)", + "Preview (External channel)": "プレビュー (外部チャンネル)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "Activeepieces Todoを使用すると、ユーザーはActiveepiecesインターフェイスで直接タスクを確認し、解決することができます。", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "待機ステップの前にチャネルを追加し、ルーターステップでロジックを設定できます。", + "Add Steps": "ステップを追加", + "All": "すべて", + "AI": "AI", + "Core": "コア", + "Apps": "アプリ", + "Not available as trigger": "トリガーとして利用できません", + "Not available as action": "アクションとして利用できません", + "Let our AI assistant help you out": "AIアシスタントがあなたを助けましょう", + "Or": "または", + "Request Piece": "ピースをリクエスト", + "No pieces found": "ピースが見つかりません", + "Please select a piece first": "最初にピースを選択してください", + "All Iterations": "すべてのイテレーション", + "Duration": "期間", + "Input": "Input", + "Output": "出力", + "There are no logs captured for this run.": "この実行のためにキャプチャされたログはありません。", + "Logs are kept for {days} days after execution and then deleted.": "Logs are kept for {days} days after execution and then deleted.", + "Run Details": "実行の詳細", + "Iteration": "イテレーション", + "Done": "完了", + "Took": "取得済み", + "Running": "実行中", + "on latest version": "最新バージョンで", + "from failed step": "失敗したステップから", + "Recent Runs": "最近の実行", + "No runs found": "実行が見つかりません", + "Close": "閉じる", + "OR": "OR", + "And If": "と", + "+ And": "+", + "+ Or": "+ または", + "(Text) Contains": "(テキスト) 含む", + "(Text) Does not contain": "(テキスト)を含まない", + "(Text) Exactly matches": "(テキスト)正確に一致", + "(Text) Does not exactly match": "(テキスト)完全に一致しません", + "(Text) Starts with": "(テキスト)で始まる", + "(Text) Does not start with": "(テキスト)で始まらない場合", + "(Text) Ends with": "(テキスト) で終了します。", + "(Text) Does not end with": "(テキスト)で終了しない", + "(List) Contains": "(リスト) 以下を含む", + "(List) Does not contain": "(リスト) を含まない", + "(Number) Is greater than": "(数値) より大きい", + "(Number) Is less than": "(数値) より小さい", + "(Number) Is equal to": "(数値) が等しい場合", + "(Date/time) After": "(日付/時間) 後", + "(Date/time) Before": "(日付/時刻) 前", + "(Date/time) Equals": "(日付/時間) 等しい", + "(Boolean) Is true": "(Boolean) true", + "(Boolean) Is false": "(Boolean) はfalse", + "(List) Is empty": "(リスト) が空です", + "(List) Is not empty": "(リスト) 空ではありません", + "Exists": "存在する", + "Does not exist": "存在しません", + "Incomplete condition": "不完全な条件", + "First value": "最初の値", + "Second value": "2番目の値", + "Case sensitive": "大文字と小文字を区別する", + "Execute If": "実行する場合", + "The package name is required": "パッケージ名が必要です", + "Success": "成功", + "Package added successfully": "パッケージの追加に成功しました", + "Could not fetch package version": "パッケージのバージョンを取得できませんでした", + "Add NPM Package": "NPMパッケージを追加", + "Type the name of the npm package you want to add.": "追加する npm パッケージの名前を入力します。", + "Package Name": "パッケージ名", + "The latest version will be fetched and added": "最新バージョンを取得して追加します", + "Add": "追加", + "Code": "コード", + "Dependencies": "依存関係", + "Use code": "コードを使用", + "Add package": "パッケージを追加", + "Inputs": "Inputs", + "Edit Step Name": "ステップ名を編集", + "Edit Branch Name": "ブランチ名を編集", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "前のステップから繰り返すアイテムを選択するには、**アイテム**のリスト**をクリックします。\n\nループはリスト内の各項目を反復処理し、各項目の次のステップを実行します。", + "Items": "アイテム", + "Select an array of items": "アイテムの配列を選択", + "Reconnect": "再接続", + "Select a connection": "コネクションを選択", + "Create Connection": "接続を作成", + "Rename": "名前の変更", + "Move": "移動", + "Add Branch": "ブランチを追加", + "Execute": "実行", + "Only the first (left) matching branch": "最初に一致するブランチのみ(左)", + "All matching paths from left to right": "左から右に一致するすべてのパス", + "Branches": "ブランチ", + "{field} is required": "{field} が必要です", + "Tool Sample Data": "ツールサンプルデータ", + "Fill in the following fields to use them as sample data for the trigger.": "トリガーのサンプルデータとして使用するには、次のフィールドを入力します。", + "No input fields defined in the schema": "スキーマに定義されている入力フィールドがありません", + "Save": "保存", + "Test Environment": "テスト環境", + "Assigned to": "割り当て先", + "(Me)": "(私)", + "Please select status to resolve the todo": "タスクを解決するための状態を選択してください", + "Resolve": "解決", + "Change status to resolved": "状態を解決に変更する", + "Send Sample Data to Webhook": "サンプルデータをWebhookに送信", + "Method": "方法", + "Query Params": "パラメータをクエリする", + "Headers": "ヘッダー", + "Body": "本文", + "Type": "タイプ", + "JSON": "JSON", + "Text": "テキスト", + "Form Data": "フォームデータ", + "Generate Sample Data": "サンプルデータの生成", + "Test Step": "ステップをテスト", + "Retest": "再試行する", + "Testing Failed": "テスト失敗", + "Tested Successfully": "テスト成功", + "Logs": "ログ", + "There is no sample data available found for this trigger.": "このトリガーに利用可能なサンプルデータはありません。", + "Internal error, please try again later.": "内部エラーが発生しました。後でもう一度お試しください。", + "Failed to run test step and no error message was returned": "テストステップの実行に失敗し、エラーメッセージが返されませんでした", + "Please fix inputs first": "入力を最初に修正してください", + "No sample data available": "利用可能なサンプルデータがありません", + "Old results were removed, retest for new sample data": "古い結果が削除されました。新しいサンプルデータを再テストします。", + "Result #": "結果 #", + "The sample data can be used in the next steps.": "サンプルデータは次のステップで使用できます。", + "Testing Trigger": "テストトリガー", + "Action Required": "アクションが必要です", + "testPieceWebhookTriggerNote": "{pieceName} に行き、 {triggerName} をトリガーしてください。", + "Send Data to the webhook URL to generate sample data to use in the next steps": "次のステップで使用するサンプルデータを生成するために、WebhookのURLにデータを送信します", + "Test Trigger": "テストトリガー", + "Use Mock Data": "モックデータを使用", + "Load Sample Data": "サンプルデータを読み込む", + "Test Tool": "テストツール", + "home": "ホーム", + "Home": "ホーム", + "Alerts": "アラート", + "Releases": "リリース", + "Flows": "フロー", + "Products": "商品", + "MCP": "MCP", + "Tables": "テーブル", + "Todos": "Todos", + "Push to Git": "Git にプッシュする", + "Move To": "移動先:", + "Duplicating": "複製中", + "Import": "インポート", + "Exporting": "エクスポート中", + "Export": "エクスポート", + "Share": "共有", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "このフローを削除してもよろしいですか?フロー、すべてのデータとバックグラウンド実行が完全に削除されます。", + "You are on a development branch, this will also delete the flow from the remote repository.": "開発ブランチにあります。これはリモートリポジトリからフローも削除されます。", + "flow": "flow", + "Community Support": "コミュニティサポート", + "Overview": "概要", + "Projects": "プロジェクト", + "Users": "ユーザー", + "Setup": "セットアップ", + "Branding": "Branding", + "Global Connections": "グローバルコネクション", + "Pieces": "小品", + "Templates": "テンプレート", + "License Key": "ライセンス キー", + "Security": "セキュリティ", + "Audit Logs": "監査ログ", + "Single Sign On": "シングルサインイン", + "Signing Keys": "署名キー", + "Project Roles": "プロジェクトの役割", + "API Keys": "API キー", + "Infrastructure": "インフラストラクチャ", + "Workers": "Worker", + "Health": "健康", + "Billing": "請求", + "Settings": "設定", + "Contact Sales": "営業担当に連絡", + "General": "全般", + "Appearance": "外観", + "Team": "チーム", + "Environments": "環境", + "Project Settings": "プロジェクトの設定", + "Exit Platform Admin": "Exit Platform Admin", + "Enter Platform Admin": "プラットフォーム管理者を入力してください", + "Logout": "ログアウト", + "Misc": "その他", + "Connections": "コネクション", + "days": "日", + "hours": "時間", + "minutes": "分", + "Today": "今日", + "Tasks": "タスク", + "AI Credits": "AI Credits", + "Usage resets in": "使用状況がリセットされます", + "Manage": "管理", + "Unlimited": "無制限です", + "Enabled": "有効", + "Disabled": "無効", + "Could not claim the authorization code, make sure you have correct settings and try again.": "認証コードを取得できませんでした。正しい設定をして再度お試し下さい。", + "Connection failed with error {msg}": "{msg} エラーで接続に失敗しました", + "You don't have the permission to create a connection.": "接続を作成する権限がありません。", + "Reconnect {displayName} Connection": "Reconnect {displayName}", + "Connect to {displayName}": "{displayName} に接続", + "Connection Name": "接続名", + "Connection name": "接続名", + "New Connection": "新しい接続", + "Redirect URL": "リダイレクトURL", + "Client ID": "クライアント ID", + "Client Secret": "クライアントシークレット", + "Connect": "接続する", + "Disconnect": "接続を解除", + "I would like to use my own App Credentials": "自分のアプリの資格情報を使用したい", + "I would like to use predefined App Credentials": "事前定義されたアプリの認証情報を使用したい", + "Permission needed": "権限が必要です", + "Connections replaced successfully": "コネクションが正常に置き換えられました", + "Error": "エラー", + "Failed to replace connections": "接続の置き換えに失敗しました", + "Failed to get affected flows": "影響を受けるフローを取得できませんでした", + "Please select a piece": "作品を選択してください", + "Please select a connection to replace": "交換する接続を選択してください", + "Please select a connection to replace with": "交換する接続を選択してください", + "Replace Connections": "コネクションを置き換え", + "Confirm Replacement": "交換の確認", + "Replace one connection with another.": "1 つの接続を別の接続に置き換えます。", + "This action requires ": "この操作には必要です ", + "reconnecting": "再接続中", + " any associated MCP pieces.": " 関連付けられたMCPのピースです", + "Piece": "ピース", + "Select a piece": "ピースを選択", + "Connection to Replace": "置き換える接続", + "Choose connection to replace": "交換する接続を選択", + "Replaced With": "置き換え", + "Choose connection to replace with": "置換する接続を選択してください", + "All flows will be changed to use the replaced with connection": "すべてのフローは、置き換えられた接続を使用するように変更されます", + "Next": "次へ", + "No flows will be affected by this change": "この変更の影響を受けるフローはありません", + "Back": "戻る", + "Unnamed tool": "名前のないツール", + "This flow is enabled": "このフローは有効です", + "Enable this flow to make it available": "このフローを有効にすると利用可能になります", + "Piece is updated successfully": "ピースは正常に更新されました", + "Piece is added successfully": "ピースが正常に追加されました", + "Failed to update piece": "ピースの更新に失敗しました", + "Failed to add piece": "ピースを追加できませんでした", + "Please select a connection": "接続を選択してください", + "Your MCP server already has this piece": "あなたのMCPサーバーはすでにこの作品を持っています", + "+ New Connection": "+ 新しい接続", + "Edit Piece": "ピースを編集", + "Add Piece": "ピースを追加", + "Connection": "接続", + "MCP piece": "MCP ピース", + "Failed to update piece status": "ピース状態の更新に失敗しました", + "Connection required": "接続が必要です", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "このツールをMCPから削除してもよろしいですか? 削除すると、MCP クライアントでは使用できなくなります。", + "piece": "ピース", + "Connect your AI assistant to external services": "AIアシスタントを外部サービスに接続する", + "Collapse": "折りたたむ", + "Show All": "すべて表示", + "Server URL": "サーバー URL", + "Hide the token for security": "セキュリティのためにトークンを非表示にする", + "Show the token": "トークンを表示", + "Generate a new token for security. This will invalidate the current URL.": "セキュリティのために新しいトークンを生成します。これは現在の URL を無効にします。", + "URL copied to clipboard": "URL をクリップボードにコピーしました", + "Copy URL to clipboard": "URL をクリップボードにコピー", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "この URL には機密性の高いセキュリティトークンが含まれています。信頼できるアプリケーションやサービスとのみ共有してください。トークンが危険にさらされたと思われる場合は、トークンを回転させてください。", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "接続やフローに変更を加えた後は、変更を有効にするために MCP サーバーを再接続する必要があります。", + "Max tables reached": "テーブルの最大数に達しました", + "You can't create more than {maxTables} tables": "{maxTables} テーブル以上を作成することはできません", + "Name": "名前", + "Created": "作成済み", + "Delete Tables": "テーブルの削除", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "選択したテーブルを削除してもよろしいですか?この操作は元に戻せません。", + "table": "表", + "Create and manage your tables to store your automation data": "自動化データを保存するためのテーブルの作成と管理", + "New Table": "新しいテーブル", + "No tables have been created yet": "テーブルがまだ作成されていません", + "Create a table to get started and start managing your automation data": "テーブルを作成して、オートメーションデータの管理を開始します", + "Error deleting connections": "コネクション削除エラー", + "Status": "ステータス", + "Display Name": "表示名", + "Owner": "所有者", + "App": "アプリ", + "This connection is global and can be managed in the platform admin": "この接続はグローバルで、プラットフォーム管理者で管理できます", + "External ID": "外部 ID", + "Connected At": "接続日時", + "Confirm Deletion": "削除の確認", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "選択した接続を削除してもよろしいですか?この操作は元に戻せません。", + "Deleting connections may cause your Flows or MCP tools to break.": "接続を削除すると、フローや MCP ツールが壊れる可能性があります。", + "Manage project connections to external systems.": "外部システムへのプロジェクト接続を管理します。", + "No connections found": "コネクションが見つかりません", + "Come back later when you create a automation to manage your connections": "接続を管理するオートメーションを作成したら、後で戻ってきてください。", + "Steps": "歩数", + "Folder": "フォルダ", + "Flow name": "フロー名", + "No flows found": "フローが見つかりません", + "Create a workflow to start automating": "自動化を開始するワークフローを作成します", + "Create and manage your flows, run history and run issues": "フローの作成と管理、履歴の実行、問題の実行", + "Issues": "課題", + "Untitled": "無題のタイトル", + "Create flow": "フローを作成", + "From scratch": "スクラッチから", + "Use a template": "テンプレートを使用", + "From local file": "ローカルファイルから", + "Flow Name": "フロー名", + "Count": "カウント", + "First Seen": "最初に見る", + "Last Seen": "最後に見た", + "Issues in {flowDisplayName} is marked as resolved.": "{flowDisplayName} の問題は解決済みとマークされています。", + "Unlock Issues": "課題のロックを解除", + "Track issues in your workflows and troubleshoot them.": "ワークフローの問題を追跡し、トラブルシューティングします。", + "No issues found": "問題は見つかりませんでした", + "All your workflows are running smoothly.": "すべてのワークフローがスムーズに実行されます。", + "Mark as Resolved": "解決済みとしてマーク", + "All Flows Are Turned Off": "すべてのフローをオフにする", + "Task Usage Exceeded": "タスク使用回数を超えました", + "of the Allowed Limit.": "制限されています", + "When a project tasks limit is reached,": "プロジェクトタスクの上限に達したとき", + "all flows will be turned off and you will not be able to run any flows.": "すべてのフローがオフになり、フローを実行することはできません。", + "Please visit": "ご覧ください", + "Your Plan": "あなたのプラン", + "and increase your task limit, which requires your payment details.": "そして、タスクの上限を増やし、支払いの詳細を必要とします。", + "Please contact your admin to increase the project task limit.": "プロジェクトタスクの上限を上げるには管理者に連絡してください。", + "and increase the project task limit.": "プロジェクト・タスクの上限を上げることができます", + "Dismiss": "却下する", + "Token rotated successfully": "トークンが正常に回転しました", + "Failed to rotate token": "トークンの回転に失敗しました", + "Piece removed successfully": "ピースを削除しました", + "Failed to remove piece": "ピースの削除に失敗しました", + "Flow created successfully": "フローを正常に作成しました", + "Failed to create flow": "フローの作成に失敗しました", + "Add Flow": "フローを追加", + "Let your AI assistant trigger automations": "AIアシスタントによる自動トリガーを許可する", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "任意の MCP クライアントを使用して、ホストされた MCP サーバーに接続してツールと通信します。", + "MCP Server": "MCP サーバー", + "My Tools": "私のツール", + "Create Flow": "フローを作成", + "Note": "メモ", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "MCPサーバーをインターネットに公開したい場合。 AP_FRONTEND_URL 環境変数を Activepieces インスタンスのパブリック URL に設定してください。", + "This URL grants access to your tools and data. Only share with trusted applications.": "このURLはツールとデータへのアクセスを許可します。信頼できるアプリケーションとのみ共有します。", + "Server Configuration": "サーバーの設定", + "Hide sensitive data": "機密データを隠す", + "Show sensitive data": "機密データを表示", + "Create a new URL. The current one will stop working.": "新しいURLを作成します。現在のURLは機能しなくなります。", + "Copy configuration": "設定をコピー", + "Configuration copied to clipboard": "設定をクリップボードにコピーしました", + "Claude": "クロード語", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "サーバー/その他", + "Note: MCPs only work with": "注意:MCPは動作しません。", + "Claude Desktop": "クロードデスクトップ", + ", not the web version.": "をクリックします。", + "Prerequisites:": "前提条件:", + "Install": "インストール", + "Node.js": "Node.js", + "and": "と", + "Open Settings:": "設定を開く:", + "Click the menu and select": "メニューをクリックして、", + "Developer": "開発者", + "Configure MCP:": "MCPの設定:", + "Click": "Click", + "Edit Config": "設定を編集", + "and paste the configuration below": "以下の設定を貼り付けてください", + "Save and Restart:": "保存して再起動:", + "Save the config and restart Claude Desktop": "設定を保存し、Claude Desktopを再起動します", + "Navigate to": "移動先:", + "Cursor Settings": "カーソル設定", + "Add Server:": "サーバーを追加:", + "Add new global MCP server": "新しいグローバル MCP サーバーを追加", + "Configure:": "設定:", + "Paste the configuration below and save": "以下の設定を貼り付けて保存", + "Use either method:": "いずれかの方法を使用してください:", + "Go to": "移動", + "Advanced Settings": "詳細設定", + "Open Command Palette and select": "コマンドパレットを開いて選択", + "Windsurf Settings Page": "Windsurf Settings Page", + "Navigate to Cascade:": "カスケードへの移動:", + "Select": "選択", + "Cascade": "Cascade", + "in the sidebar": "サイドバーで", + "Add Server": "サーバーを追加", + "Add custom server +": "カスタムサーバーを追加 +", + "Copy URL": "URLをコピー", + "Client Setup Instructions": "クライアント設定の手順", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "接続またはフローを変更した後、MCP サーバーを再接続して変更を反映します。", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "ご希望のクライアントにMCPを設定するには、以下の手順に従ってください。これにより、AIアシスタントがツールにアクセスできるようになります。", + "icon": "アイコン", + "Unlock Analytics": "解析のロックを解除", + "Get insights into your platform usage and performance with our analytics dashboard": "アナリティクスダッシュボードを使って、プラットフォームの使用状況とパフォーマンスに関する洞察を得ましょう", + "Active Flows": "有効なフロー", + "The number of enabled flows in the platform": "プラットフォームで有効になっているフローの数", + "Active Projects": "アクティブなプロジェクト", + "The number of projects with at least one enabled flow": "少なくとも 1 つの有効なフローを持つプロジェクトの数", + "Active Users": "アクティブなユーザー", + "The number of users logged in the last 30 days": "過去30日間にログインしたユーザーの数", + "Out of {totalusers} total users": "{totalusers} の合計ユーザー", + "Pieces Used": "使用された欠片", + "The number of unique pieces used across all flows": "すべてのフローで使用されるユニークなピースの数", + "Flows with AI": "AIでフローする", + "The number of enabled flows that use AI pieces": "AIピースを使用する有効なフローの数", + "Metrics": "メトリック", + "Executed Tasks": "実行されたタスク", + "Showing total executed tasks for specified time range": "指定された時間範囲で実行されたタスクの合計を表示", + "Tasks Usage Limit": "タスク使用限界値", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "過剰な使用を避けるためにタスクの月間制限を指定します。この制限に達した場合、フローは実行されなくなります。", + "Number of monthly tasks": "毎月のタスク数", + "Save changes": "変更を保存", + "Limits updated successfully": "制限が正常に更新されました", + "Failed to update limits": "制限の更新に失敗しました", + "Failed to load billing information": "請求情報の読み込みに失敗しました", + "Billing Amount": "請求額", + "Manage Payment Details": "支払い詳細の管理", + "Add Payment Details": "支払いの詳細を追加", + "Current Task Usage": "現在のタスク使用率", + "Count of executed steps": "実行されたステップの数", + "Billing Limit": "請求限度額", + "Edit": "編集", + "Add Limit": "上限を追加", + "Current Credit Usage": "現在のクレジット使用率", + "WebSocket Connection": "WebSocket 接続", + "Connected": "接続しました", + "Disconnected": "切断されました", + "No issues detected": "課題は検出されませんでした", + "Check the status of your platform and its components": "プラットフォームとそのコンポーネントの状態を確認してください", + "System Health Status": "システムの状態を確認する", + "All systems are running smoothly": "すべてのシステムがスムーズに動作しています", + "Check the health of your worker machines": "ワーカーマシンの状態を確認する", + "Workers Machine": "ワーカーマシン", + "This is demo data. In a real environment, this would show your actual worker machines.": "これはデモデータです。実際の環境では、実際のワーカーマシンが表示されます。", + "No workers found": "ワーカーが見つかりません", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "あなたはまだワーカーマシンを持っていません。自動化を実行するために新しいマシンをスピンアップしてください", + "IP Address": "IP アドレス", + "CPU Usage": "CPU 使用率", + "Disk Usage": "ディスクの使用量", + "RAM Usage": "RAM Usage", + "Last Contact": "前回の連絡先", + "Configs": "構成", + "Environment Variables": "環境変数", + "Websocket Connection Error": "Websocket 接続エラー", + "Retry Connection": "接続を再試行", + "Update Available": "アップデートがあります", + "Update Now": "今すぐ更新", + "Your Universal AI needs a quick setup": "ユニバーサルAIにはクイックセットアップが必要です", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "まだAIプロバイダーを設定していないことがわかりました。 チームのユニバーサルAIピースのロックを解除するには、まずプロバイダーの資格情報を設定する必要があります。", + "Configure": "設定", + "Platform Alerts": "プラットフォームアラート", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "注意が必要な重要なプラットフォームアラートがあります。プラットフォーム管理者のアラートセクションを確認してください。", + "View Alerts": "アラートを表示", + "Used Tasks": "使用済みタスク", + "Used AI Credits": "使用済みAIクレジット", + "Cannot delete active project, switch to another project first": "アクティブなプロジェクトを削除できません。別のプロジェクトに切り替えてください", + "Delete Projects": "プロジェクトを削除", + "Are you sure you want to delete the selected projects?": "選択したプロジェクトを削除してもよろしいですか?", + "New Project": "新規プロジェクト", + "Validation error": "検証エラー", + "Project has enabled flows. Please disable them first.": "プロジェクトはフローを有効にしました。最初にフローを無効にしてください。", + "This project is active. Please switch to another project first.": "このプロジェクトはアクティブです。まず別のプロジェクトに切り替えてください。", + "Unlock Projects": "プロジェクトのロックを解除", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "独自のフロー、接続、使用量の割り当てを使用して、プロジェクト全体の自動化チームをオーケストレーションします", + "Manage your automation projects": "自動化プロジェクトの管理", + "No projects found": "プロジェクトが見つかりません", + "Start by creating projects to manage your automation teams": "オートメーションチームを管理するプロジェクトを作成することから始めましょう", + "Edit project": "プロジェクトを編集", + "Name is required": "名前が必要です", + "Create New Project": "新規プロジェクトを作成", + "Project Name": "プロジェクト名", + "Id": "Id", + "Enable API Keys": "API キーを有効にする", + "Create and manage API keys to access Activepieces APIs.": "Activepieces APIにアクセスするためのAPIキーを作成および管理します。", + "New Api Key": "新しいAPIキー", + "No API keys found": "API キーが見つかりません", + "Start by creating an API key to communicate with Activepieces APIs": "Activepieces APIと通信するためのAPIキーを作成することから始めましょう", + "Delete API Key": "API キーを削除", + "Are you sure you want to delete this API key?": "この API キーを削除してもよろしいですか?", + "API Key": "API キー", + "API Key Created": "API キーが作成されました", + "Create New API Key": "新しいAPIキーを作成", + "Please save this secret key somewhere safe and accessible. For security reasons,": "この秘密鍵を安全でアクセス可能な場所に保存してください。セキュリティ上の理由から。", + "you won't be able to view it again after closing this dialog.": "このダイアログを閉じても二度と見ることはできません。", + "API Key Name": "API キー名", + "Action": "アクション", + "Performed By": "遂行者:", + "Project": "プロジェクト", + "Unlock Audit Logs": "監査ログのロックを解除", + "Comply with internal and external security policies by tracking activities done within your account": "アカウント内で行われたアクティビティを追跡することで、内部および外部のセキュリティポリシーを遵守する", + "Track activities done within your platform": "プラットフォーム内で行われたアクティビティを追跡する", + "No audit logs found": "監査ログが見つかりません", + "Come back later when you have some activity to audit": "監査するアクティビティがある場合は、後でまた来てください。", + "Resource": "リソース", + "Details": "詳細", + "N/A": "該当なし", + "Flow Run": "フローラン", + "Flow": "フロー", + "User": "ユーザー", + "Signing Key": "署名キー", + "Project Role Management": "プロジェクトのロール管理", + "Define custom roles and permissions to control what your team members can access and modify": "チームメンバーがアクセスおよび変更できるものを制御するためのカスタムロールと権限を定義します", + "Define custom roles and permissions that can be assigned to your team members": "チームメンバーに割り当てられるカスタムロールと権限を定義します", + "New Role": "新しいロール", + "Contact sales to unlock custom roles": "営業担当者に連絡してカスタムの役割をアンロックします", + "Create New Role": "新しいロールを作成", + "View ": "表示 ", + "Edit ": "編集 ", + "Role Name": "役割名", + "Permissions": "アクセス許可", + "None": "なし", + "Read": "既読にする", + "Write": "書き込み", + "Create": "作成", + "Email": "Eメールアドレス", + "First Name": "名", + "Last Name": "姓", + "Roles": "ロール", + "View the users assigned to this role": "この役割に割り当てられたユーザーを表示", + "Role": "ロール", + "No users found": "ユーザーが見つかりませんでした", + "Starting by assigning users to this role": "この役割にユーザーを割り当てることから開始", + "Project Role entry deleted successfully": "プロジェクトのロールエントリが正常に削除されました", + "Updated": "更新日時", + "No project roles found": "プロジェクトロールが見つかりません", + "Create custom project roles to manage permissions for platform users": "プラットフォームユーザーの権限を管理するためのカスタムプロジェクトロールを作成します", + "Show Users": "ユーザーを表示", + "View Role": "ロールを表示", + "Edit Role": "役割を編集", + "Delete Role": "ロールを削除", + "Project Role": "プロジェクトの役割", + "Unlock Embedding Through JS SDK": "JS SDKを通じての埋め込みをロック解除", + "Enable signing keys to access embedding functionalities.": "埋め込み機能にアクセスするには署名キーを有効にしてください。", + "New Signing Key": "新しい署名キー", + "No signing keys found": "署名キーが見つかりません", + "Create a signing key to authenticate users with embedding": "埋め込みでユーザーを認証するための署名キーを作成します", + "Delete Signing Key": "署名キーを削除", + "Are you sure you want to delete this signing key?": "この署名キーを削除してもよろしいですか?", + "Signing Key Created": "署名キーが作成されました", + "Create New Signing Key": "新規署名キーの作成", + "Signing Key Name": "署名キー名", + "Allowed domains updated": "許可されたドメインが更新されました", + "Update": "更新", + "Enable": "有効にする", + "Configure Allowed Domains": "許可されたドメインの設定", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "ユーザーが認証できるように許可されているドメインを入力します。Empty リストはすべてのドメインを許可します。", + "Add Domain": "ドメインを追加", + "Allow logins through {providerName}'s single sign-on functionality.": "{providerName}のシングルサインオン機能を通じてログインを許可します。", + "Email authentication updated": "メール認証が更新されました", + "Enable Single Sign On": "シングルサインオンを有効にする", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "ユーザに現在の SSO プロバイダにサインインするか、セルフサーブのサインアップアクセスを許可します", + "Allowed Domains": "許可されたドメイン", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "ユーザーが特定のドメインで認証できるようにします。すべてのドメインを許可するには空のままにしてください。", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "許可された電子メールログイン", + "Allow logins through email and password.": "メールアドレスとパスワードでのログインを許可します。", + "Single sign on settings updated": "シングルサインオンの設定が更新されました", + "Disable": "無効", + "Configure {provider} SSO": "{provider} SSOを設定", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "{provider} SSO [here](https://www.activepieces.com/docs/security/sso )の設定方法についての詳細をお読みください。", + "{provider} Client ID": "{provider} クライアントID", + "{provider} Client Secret": "{provider} クライアントシークレット", + "Single sign-on settings updated": "シングルサインオンの設定が更新されました", + "Configure SAML 2.0 SSO": "SAML 2.0 SSO の設定", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDPメタデータ", + "IDP Certificate": "IDP証明書", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "ベースURL", + "Resource Name": "リソース名", + "Deployment Name": "配備名", + "Saving": "保存中", + "Activepieces Copilot": "Activepieces Copilot", + "Copilot is configured and ready to help your users build flows faster using AI.": "コピロットが設定され、AIを使用してユーザーがフローを高速に構築できるようになります。", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Activepieces Copilotを設定すると、AIを使用してユーザーがフローを高速に構築できるようになります。", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "AIプロバイダーと副操縦士の設定を行い、ユーザーが当社のユニバーサルAIピースでシームレスな建物体験を楽しめるようにしましょう", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "汎用AIピース、すなわちテキストAIで使用されるプロバイダ資格情報を設定します。", + "AI Providers": "AI Providers", + "Copilot": "コピロット", + "Configure credentials for {providerName} AI provider.": "{providerName} AI プロバイダーの資格情報を設定します。", + "Update AI Provider": "AI プロバイダーを更新", + "Enable AI Provider": "AI プロバイダーを有効にする", + "sk_************************": "sk_************************", + "Please enter a valid domain": "有効なドメインを入力してください", + "Your changes have been saved.": "変更が保存されました。", + "The domain is already added.": "ドメインは既に追加されています。", + "Add Custom Domain": "カスタムドメインを追加", + "Enter a domain name without a protocol (e.g. example.com)": "プロトコルを持たないドメイン名を入力してください(例:example.com)", + "Logo URL": "ロゴ URL", + "Icon URL": "アイコン URL", + "Favicon URL": "ファビコンの URL", + "Default Language": "デフォルトの言語", + "Select Language": "言語の選択", + "No Languages": "言語なし", + "Primary Color": "プライマリ色", + "Custom Domains": "カスタムドメイン", + "No domains added yet.": "まだ追加されたドメインはありません。", + "Verified": "確認済み", + "Pending, please contact the support for dns verification.": "保留中です。dns認証のサポートにお問い合わせください。", + "Are you sure you want to delete {domain}?": "{domain} を削除してもよろしいですか?", + "Brand Activepieces": "ブランドのアクティブ時計", + "Give your users an experience that looks like you by customizing the color, logo and more": "色、ロゴなどをカスタマイズすることで、ユーザーにそっくりな体験を提供できます。", + "Configure the appearance and SMTP settings for your platform.": "プラットフォームの外観と SMTP 設定を構成します。", + "Invalid host": "無効なホスト", + "Invalid username": "無効なユーザー名", + "Invalid password": "無効なパスワード", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "不正な送信者名", + "SMTP is configured": "SMTPが設定されています", + "Mail Server": "メールサーバー", + "Set up your SMTP settings to send emails from your domain.": "ドメインからメールを送信するためにSMTP設定を設定します。", + "Disable Mail Server": "メールサーバーを無効にする", + "Are you sure you want to disable your mail server?": "メールサーバーを無効にしてもよろしいですか?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "これにより、課題、クォータ制限、招待状、パスワードを忘れたメールを送信できなくなります。", + "mail server": "メールサーバー", + "Host": "ホスト", + "Port": "ポート", + "Username": "ユーザー名", + "Password": "パスワード", + "Sender Email": "送信者メールアドレス", + "Sender Name": "送信者名", + "Enable Global Connections": "グローバル接続を有効にする", + "Manage platform-wide connections to external systems.": "外部システムへのプラットフォーム全体の接続を管理します。", + "No global connections found": "グローバル接続が見つかりません", + "Create a global connection that can be shared to multiple projects": "複数のプロジェクトと共有できるグローバル接続を作成します", + "License key is invalid": "ライセンスキーが無効です", + "Invalid license key": "無効なライセンスキーです。", + "License activated!": "ライセンスが有効になりました!", + "Activate License Key": "ライセンスキーを有効にする", + "Let the magic begin!": "魔法を始めよう!", + "Activate": "有効にする", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "この機能はまだクラウドでセルフサービスではありません。sales@activeeces.comまでご連絡ください。 ", + "This feature is not available in your current edition. ": "この機能は現在のエディションでは使用できません。 ", + "Learn how to upgrade": "アップグレード方法を学ぶ", + "Activate your platform and unlock enterprise features": "プラットフォームをアクティベートし、エンタープライズ機能をアンロックします", + "Activate License": "ライセンスの有効化", + "Expiration": "有効期限", + "Valid until": "有効期限", + "Expired": "期限切れ", + "Expires soon": "まもなく期限切れ", + "Features": "特徴", + "Applying Tags...": "タグを適用しています...", + "Tags applied.": "タグを適用しました。", + "Tag created": "タグが作成されました", + "Tag": "タグ", + "Tags": "タグ", + "Control Pieces": "部品の制御", + "Show the pieces that matter most to your users and hide the ones you don't like.": "ユーザーに最も重要なピースを表示し、好きではないピースを非表示にします。", + "Manage the pieces that are available to your users": "ユーザーが利用できるピースを管理します", + "Start by installing pieces that you want to use in your automations": "オートメーションで使用したいピースをインストールすることから始めましょう", + "Piece Name": "ピース名", + "Hide this piece from all projects": "すべてのプロジェクトからこのピースを非表示にする", + "Show this piece for all projects": "すべてのプロジェクトにこのピースを表示", + "Unpin this piece": "このピースの固定を解除する", + "Pin this piece": "このピースをピン留めする", + "Pieces synced": "作品を同期しました", + "Pieces have been synced from the activepieces cloud.": "ピースはアクティブピースクラウドから同期されています。", + "OAuth2 Credentials Deleted": "OAuth2認証情報が削除されました", + "OAuth2 Credentials Updated": "OAuth2 資格情報が更新されました", + "Configure OAuth2 APP": "OAuth2 APPを設定", + "Delete OAuth2 APP": "OAuth2 アプリを削除", + "Templates deleted successfully": "テンプレートが正常に削除されました", + "Delete Templates": "テンプレートを削除", + "Are you sure you want to delete the selected templates?": "選択したテンプレートを削除してもよろしいですか?", + "New Template": "新規テンプレート", + "Unlock Templates": "テンプレートのロックを解除", + "Convert the most common automations into reusable templates 1 click away from your users": "最も一般的なオートメーションを再利用可能なテンプレートに変換する 1クリック離れたユーザーから", + "Convert the most common automations into reusable templates": "最も一般的なオートメーションを再利用可能なテンプレートに変換します", + "No templates found": "テンプレートが見つかりません", + "Create a template for your user to inspire them": "ユーザーがそれらを刺激するためのテンプレートを作成する", + "Edit template": "テンプレート編集", + "Template is required": "テンプレートが必要です", + "Update New Template": "新しいテンプレートを更新", + "Create New Template": "新規テンプレートを作成", + "Template Name": "テンプレート名", + "Description": "説明", + "Template Description": "テンプレートの説明", + "Blog URL": "ブログURL", + "Template Blog URL": "テンプレートブログURL", + "Template": "テンプレート", + "Invalid JSON": "無効なJSON", + "User deleted successfully": "ユーザーが正常に削除されました", + "User activated successfully": "ユーザーが正常にアクティブ化されました", + "User deactivated successfully": "ユーザーの無効化に成功しました", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "ユーザーとプロジェクトへのアクセスを管理します", + "Start inviting users to your project": "ユーザーのプロジェクトへの招待を開始", + "External Id": "外部ID", + "Admin": "管理者", + "Member": "メンバー", + "Activated": "アクティベート済み", + "Deactivated": "無効化されました", + "Edit user": "ユーザーを編集", + "Admin cannot be deactivated": "管理者を無効にすることはできません", + "Deactivate user": "ユーザーを無効にする", + "Activate user": "利用者を有効にする", + "Delete User": "ユーザーを削除", + "Are you sure you want to delete this user?": "このユーザーを削除してもよろしいですか?", + "Delete user": "ユーザーを削除", + "Update User Role": "ユーザーロールの更新", + "Meeting Summary Flow": "ミーティングの概要フロー", + "Added new features and fixed bugs": "新機能と修正バグを追加", + "Flows Changes": "フローの変更", + "Connections Changes": "コネクションの変更", + "New connections are placeholders and need to be reconnected again": "新しい接続はプレースホルダであり、再接続する必要があります", + "renamed to": "名前を変更", + "Tables Changes": "テーブルの変更", + "No changes to apply": "適用する変更はありません", + "Apply Changes": "変更を適用", + "Create Git Release": "Git リリースの作成", + "Create Project Release": "プロジェクトリリースを作成", + "Create Rollback to": "ロールバックを作成", + "Source": "ソース", + "Rollback": "Rollback", + "Imported At": "インポート日時", + "Imported By": "インポート者", + "Track and manage your project version history and deployments. ": "プロジェクトのバージョン履歴とデプロイメントを追跡および管理します。 ", + "Environments & Releases": "環境とリリース", + "Project Releases": "プロジェクトリリース", + "Create Release": "リリースを作成", + "From Git": "Git から", + "From Project": "プロジェクトから", + "No project releases found": "プロジェクトのリリースが見つかりません", + "Create a project release to get started": "プロジェクトリリースを作成して始めましょう", + "Please select project": "プロジェクトを選択してください", + "No Changes Found": "変更が見つかりませんでした", + "There are no differences to apply": "適用する違いはありません", + "Please select a project": "プロジェクトを選択してください", + "Search projects...": "プロジェクトを検索...", + "Review Changes": "変更を確認", + "Git": "Git", + "Summary": "Summary", + "Imported by": "インポート者:", + "from": "from", + "No description provided": "説明が提供されていません", + "Invitation only sign up": "招待のみサインアップ", + "Please ask your administrator to add you to the organization.": "管理者に組織に追加するよう依頼してください。", + "Something went wrong, please try again.": "問題が発生しました。もう一度やり直してください。", + "Please try again.": "もう一度やり直してください。", + "Please enter a valid email address": "有効なメールアドレスを入力してください", + "The email is already added.": "メールアドレスは既に追加されています。", + "Add email": "メールアドレスを追加", + "Only project admins can do this": "プロジェクト管理者のみがこれを行うことができます", + "Add Alert Email": "アラートメールを追加", + "Enter the email address to receive alerts.": "アラートを受信するメールアドレスを入力します。", + "Add Email": "メールアドレスを追加", + "Emails": "E-mail", + "Add email addresses to receive alerts.": "アラートを受信するメールアドレスを追加します。", + "No emails added yet.": "メールはまだ追加されていません。", + "Choose what you want to be notified about.": "通知する内容を選択します。", + "Project and alert permissions are required to change this setting.": "この設定を変更するには、プロジェクトとアラートの権限が必要です。", + "Every Failed Run": "すべての失敗した実行", + "Get an email alert when a flow fails.": "フローが失敗したときに電子メールアラートを取得します。", + "Get an email alert when a new issue created.": "新しい課題が作成されたときに電子メールアラートを取得します。", + "Never": "一切なし", + "Turn off email notifications.": "メール通知をオフにします。", + "Customize the appearance of the app. Automatically switch between day and night themes.": "アプリの外観をカスタマイズします。昼と夜のテーマを自動的に切り替えます。", + "Select the theme for the dashboard.": "ダッシュボードのテーマを選択します。", + "Light": "ライト", + "Dark": "ダーク", + "Select the language that will be used in the dashboard.": "ダッシュボードで使用する言語を選択します。", + "Select language": "言語を選択", + "Search language...": "言語を検索...", + "No language found.": "言語が見つかりません。", + "Help us translate Activepieces to your language.": "Activepiecesをあなたの言語に翻訳するのを手伝ってください。", + "Learn more": "もっと詳しく", + "Git Connection Removed": "Git 接続が削除されました", + "Your Git repository has been successfully disconnected": "Git リポジトリの接続が正常に切断されました", + "Enable Environments": "環境を有効にする", + "Deploy flows across development, staging and production environments with version control and team collaboration": "バージョン管理とチームのコラボレーションを使用して、開発、ステージング、本番環境全体のフローをデプロイします。", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Gitに接続してバージョン管理を有効にし、フローをバックアップし、複数の環境を管理します。 ", + "Repository URL": "リポジトリURL", + "Not connected": "接続していません", + "Project Folder": "プロジェクトフォルダ", + "Releases Enabled": "リリースが有効になっています", + "You have successfully enabled releases": "リリースを有効にしました", + "Enable releases to easily create and manage project releases.": "リリースを有効にして、プロジェクトのリリースを簡単に作成および管理できます。", + "The external ID is already taken.": "外部 ID は既に使用されています。", + "Manage general settings for your project.": "プロジェクトの一般的な設定を管理します。", + "Used to identify the project based on your SaaS ID": "SaaS ID に基づいてプロジェクトを識別するために使用", + "org-3412321": "org-3412321", + "Delete {name}": "{name} を削除", + "This will permanently delete this piece, all steps using it will fail.": "このピースは永久に削除され、使用しているすべてのステップは失敗します。", + "Add a piece to your project that you want to use in your automations": "自動化で使用したい作品をプロジェクトに追加する", + "Pieces list updated": "作品リストが更新されました", + "Manage Pieces": "駒を管理", + "Choose which pieces you want to be available for your current project users": "現在のプロジェクトユーザーに利用したい作品を選択してください", + "Unlock Team Permissions": "チーム権限を解放する", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "コミュニティ版では、無料でユーザーをあなたのプラットフォームに招待することができます。詳細なロールと権限のリクエストトライアルが可能です。", + "Project Members": "プロジェクトメンバー", + "Invite your team members to collaborate.": "チームメンバーにコラボレーションを招待します。", + "No members are added to this project.": "このプロジェクトにメンバーが追加されていません。", + "Pending Invitations": "保留中の招待", + "No pending invitation.": "保留中の招待はありません。", + "templateId is missing": "templateId がありません", + "Me Only": "自分のみ", + "Unresolved": "未解決です", + "Resolved": "解決済み", + "Title": "タイトル", + "Date Created": "作成日", + "Manage todos for your project that are created by automations": "自動化によって作成されたプロジェクトのタスクを管理する", + "No todos found": "タスクが見つかりません", + "You do not have any pending todos. Great job!": "保留中のタスクはありません。よくできました!", + "Write a comment...": "コメントを書く...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "この機能はまだテスト中で、頻繁に変更される可能性があります", + "Failed to copy to clipboard": "クリップボードにコピーできませんでした", + "{number} items selected": "{number} 個のアイテムを選択しました", + "Select All": "すべて選択", + "No results found.": "結果が見つかりませんでした。", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelectはMultiSelectProvider内で使用する必要があります", + "Unset": "未設定", + "Refresh": "更新", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} 以上", + "Removed {entityName}": "{entityName} を削除しました", + "Download File": "ファイルをダウンロード", + "Copied to clipboard": "クリップボードにコピーしました", + "File is not available after execution.": "ファイルは実行後に使用できません。", + "Available for Projects": "プロジェクトで利用可能", + "Select projects": "プロジェクトを選択", + "No items": "アイテムがありません", + "Previous": "前", + "to": "to", + "Last Week": "前週", + "Last Month": "前月", + "Last 3 Months": "過去 3 ヶ月", + "Last 6 Months": "過去 6 ヶ月", + "Next 7 days": "次の7日", + "Next 30 days": "次の30日間", + "Next 90 days": "次の90日間", + "Next 180 days": "次の180日間", + "Select Time Range": "時間範囲の選択", + "Clear": "クリア", + "Download": "ダウンロード", + "Go to Dashboard": "ダッシュボードに移動", + "Select a file": "ファイルを選択", + "Press space to separate values": "スペースを押して値を分離します", + "AM": "午前", + "PM": "PM", + "Already have an account?": "既にアカウントをお持ちですか?", + "Sign in": "サインイン", + "Don't have an account?": "アカウントをお持ちでないですか?", + "Sign up": "新規登録", + "Welcome Back!": "おかえりなさい!", + "Enter your email below to sign in to your account": "アカウントにサインインするには、メールアドレスを入力してください。", + "Let's Get Started!": "始めましょう!", + "Create your account and start flowing!": "アカウントを作成し、フローを開始します!", + "Your password was changed successfully": "パスワードが正常に変更されました", + "Your password reset request has expired, please request a new one": "パスワードリセットリクエストの有効期限が切れています。新しいものをリクエストしてください。", + "Reset Password": "パスワードのリセット", + "Enter your new password": "新しいパスワードを入力してください", + "Password is required": "パスワードが必要です", + "Verification email resent, if previous one expired.": "以前のメールの有効期限が切れている場合、確認メールが再送信されます。", + "Password reset link resent, if previous one expired.": "以前のパスワードリセットリンクが期限切れの場合、再送信されます。", + "We sent you a link to complete your registration to": "登録を完了するためのリンクを送信しました", + "We sent you a link to reset your password to": "パスワードをリセットするためのリンクを送信しました", + "Didn't receive an email or it expired?": "メールが届かなかった、または期限切れですか?", + "Resend": "再送信する", + "Please enter your email": "メールアドレスを入力してください", + "Check Your Inbox": "受信トレイを確認する", + "If the user exists we'll send you an email with a link to reset your password.": "ユーザーが存在する場合、パスワードをリセットするためのリンクが記載されたメールを送信します。", + "Send Password Reset Link": "パスワードリセットリンクを送信", + "Back to sign in": "サインインに戻る", + "Email is invalid": "メールアドレスが無効です", + "Something went wrong, please try again later": "問題が発生しました。後でもう一度お試しください。", + "Invalid email or password": "メールアドレスまたはパスワードが正しくありません", + "User has been deactivated": "ユーザーが無効化されました", + "Email domain is disallowed": "メールドメインは許可されていません", + "Email authentication has been disabled": "メール認証が無効になっています", + "Forgot your password?": "パスワードをお忘れですか?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "サインアップが制限されています。参加するには招待状が必要です。管理者に連絡してください。", + "Email is already used": "メールアドレスは既に使用されています", + "Email authentication is disabled": "メール認証が無効になっています", + "First name is required": "名は必須です", + "Last name is required": "姓が必要です", + "Email is required": "メールアドレスが必要です", + "Receive updates and newsletters from activepieces": "アクティベーションピースから更新情報やニュースレターを受け取る", + "By creating an account, you agree to our": "アカウントを作成することで、以下に同意したことになります。", + "terms of service": "利用規約", + "privacy policy": "プライバシー ポリシー", + "Sign up With": "でサインアップ", + "Google": "Google", + "Sign in With": "次でログイン", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "メールアドレスが認証されました。ログイン画面にリダイレクトされます。", + "Verifying email...": "メールアドレスを確認しています...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "招待の有効期限が切れました。再度サインインすると、確認メールを再送信することができます。", + "Redirecting to sign in...": "サインインするためにリダイレクト中...", + "Password must contain at least one special character": "パスワードには少なくとも1つの特殊文字が含まれている必要があります", + "Password must contain at least one lowercase letter": "パスワードには少なくとも1つの小文字が含まれている必要があります", + "Password must contain at least one uppercase letter": "パスワードには少なくとも1つの大文字が含まれている必要があります", + "Password must contain at least one number": "パスワードには少なくとも1つの番号が必要です", + "8-64 Characters": "8-64 文字", + "Special Character": "特殊文字", + "Lowercase": "小文字", + "Uppercase": "大文字・小文字", + "Number": "数値", + "Connection has been updated.": "接続を更新しました。", + "Edit Global Connection": "グローバル接続を編集", + "Connection has been renamed.": "接続の名前が変更されました。", + "New Connection Name": "新しい接続名", + "Connection name already used": "接続名は既に使用されています", + "Please select at least one project": "少なくとも1つのプロジェクトを選択してください", + "Run Succeeded": "実行に成功", + "Run Failed": "実行に失敗しました", + "Flow Run is paused": "フローランは一時停止されています", + "Run Failed due to quota exceeded": "クォータを超えたため実行に失敗しました", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "{memoryLimit} MBのメモリ制限を超えているため実行に失敗しました", + "Run exceeded {timeout} seconds, try to optimize your steps.": "実行が {timeout} 秒を超えました。ステップを最適化してみてください。", + "Run failed for an unknown reason, contact support.": "不明な理由で実行に失敗しました, 連絡先のサポート.", + "Unknown": "不明", + "Exit Run": "実行を終了", + "Select shown": "表示されたものを選択", + "Select all": "すべて選択", + "Start Time": "開始時刻", + "Runs replayed successfully": "実行が正常にリプレイされました", + "Retry": "再試行する", + "all except": "それ以外は", + "all": "すべて", + "Only failed runs can be retried from failed step": "失敗した実行のみが失敗したステップから再試行することができます", + "No flow runs found": "フローの実行が見つかりません", + "Come back later when your automations start running": "オートメーションが動作し始めたらまた後でお戻りください。", + "Step running": "ステップ実行", + "Step paused": "一時停止中", + "Step Stopped": "ステップ停止", + "Step Succeeded": "ステップ成功", + "Step Failed": "手順に失敗しました", + "Please publish flow first": "最初にフローを公開してください", + "Flow is on": "フローがオン", + "Flow is off": "フローがオフです", + "Permission Needed": "権限が必要です", + "Draft Version": "下書きバージョン", + "Published Version": "公開されたバージョン", + "Locked Version": "ロックされたバージョン", + "flowsImported": "{flowsCount, plural, =0 {フローはインポートされません} =1 {フローは正常にインポートされました} other {フローは正常にインポートされました}}", + "Template file is invalid": "テンプレートファイルが無効です", + "No valid templates found. The following files failed to import: ": "有効なテンプレートが見つかりませんでした。次のファイルのインポートに失敗しました: ", + "Please select a file first": "最初にファイルを選択してください", + "Unsupported file type": "サポートされていないファイルタイプ", + "Import Flow": "フローをインポート", + "Warning": "警告", + "Importing a flow will overwrite your current one.": "フローをインポートすると現在のフローが上書きされます。", + "Select a folder": "フォルダを選択", + "Folders": "フォルダ", + "Please select a folder": "フォルダを選択してください", + "Moved flows successfully": "フローを移動しました", + "Move Selected Flows": "選択したフローを移動", + "Select Folder": "フォルダを選択", + "No Folders": "フォルダがありません", + "Flow has been renamed.": "フローの名前が変更されました。", + "New Flow Name": "新しいフロー名", + "Use Template": "テンプレートを使用", + "Browse Templates": "テンプレートを参照", + "Search templates": "テンプレートを検索", + "No templates found, try adjusting your search": "テンプレートが見つかりませんでした。検索を調整してみてください", + "Read more about this template in": "このテンプレートについてもっと読む", + "this blog!": "このブログ!", + "Share Template": "テンプレートを共有", + "Generate or update a template link for the current flow to easily share it with others.": "現在のフローのテンプレートリンクを生成または更新して、他の人と簡単に共有できます。", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "テンプレートは接続フィールドに資格情報を持たず、機密情報を安全に保ちます。", + "A short description of the template": "テンプレートの簡単な説明", + "Flow Is In Use": "フローは使用中です", + "Flow is being used by another user, please try again later.": "Flow は他のユーザーによって使用されています。後でもう一度お試しください。", + "Flow has been published.": "Flow が公開されました。", + "Flows have been exported.": "フローがエクスポートされました。", + "Run": "実行", + "Real time flow": "リアルタイム フロー", + "Flow can't be published with empty trigger {name}": "空のトリガー {name}でフローを公開できません", + "Please contact support as your published flow has a problem": "公開フローに問題があるため、サポートに連絡してください。", + "Actions": "アクション", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "これらのフローを削除してもよろしいですか?フロー、すべてのデータ、およびバックグラウンド実行が完全に削除されます。", + "You are on a development branch, this will not delete the flows from the remote repository.": "開発ブランチにあります。これはリモートリポジトリからフローを削除しません。", + "Please enter folder name": "フォルダ名を入力してください", + "Added folder successfully": "フォルダを追加しました", + "The folder name already exists.": "フォルダ名は既に存在します。", + "New Folder": "新規フォルダ", + "Folder Name": "フォルダ名", + "Loading...": "読み込み中...", + "Delete {folderName}": "{folderName} を削除", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "このフォルダを削除すると、フローを保持し、それらを未分類に移動します。", + "All flows": "すべてのフロー", + "Please enter a folder name": "フォルダ名を入力してください", + "Renamed flow successfully": "フローの名前を変更しました", + "Folder name already used": "フォルダ名は既に使用されています", + "New Folder Name": "新規フォルダ名", + "Connected successfully": "接続に成功しました", + "Connect Git": "Git に接続", + "Remote URL": "リモート URL", + "Folder name is the name of the folder where the project will be stored or fetched.": "フォルダ名は、プロジェクトが保存または取得されるフォルダ名です。", + "SSH Private Key": "SSHプライベートキー", + "The SSH private key to use for authentication.": "認証に使用する SSH 秘密鍵。", + "Only published flows can be pushed to Git": "公開されたフローのみGitにプッシュできます", + "Pushed successfully": "プッシュに成功しました", + "Invalid Operation": "無効な操作", + "Commit Message": "コミットメッセージ", + "Enter a commit message to describe the changes you want to push.": "プッシュする変更を記述するコミットメッセージを入力します。", + "Push": "Push", + "This field is required": "このフィールドは必須項目です", + "Your submission was successfully received.": "提出物が正常に受信されました。", + "Flow not found": "フローが見つかりません", + "The flow you are trying to submit to does not exist.": "送信しようとしているフローは存在しません。", + "The flow failed to execute.": "フローの実行に失敗しました。", + "Submit": "送信", + "issues-notification": "issues-notification", + "Please select a package type": "パッケージの種類を選択してください", + "package.json not found in archive": "package.json がアーカイブに見つかりません", + "Error processing archive file": "アーカイブファイルの処理中にエラーが発生しました", + "Please upload a .tgz file": ".tgzファイルをアップロードしてください", + "Piece name is required for NPM Registry": "NPMレジストリにはピース名が必要です", + "Piece version is required for NPM Registry": "NPMレジストリにはピースのバージョンが必要です", + "Piece installed": "ピースがインストールされました", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "この名前とバージョンが既にインストールされています。package.json 内のバージョン番号を更新して、もう一度やり直してください。", + "Install Piece": "ピースをインストール", + "Install a piece": "ピースをインストール", + "Package Type": "パッケージタイプ", + "NPM Registry": "NPM レジストリ", + "Packed Archive (.tgz)": "パックされたアーカイブ (.tgz)", + "Piece Version": "ピースバージョン", + "Package Archive": "パッケージアーカイブ", + "Package archive": "パッケージアーカイブ", + "Powerful Node.js & TypeScript code with npm": "npm の強力な Node.js & TypeScript コード", + "Loop on Items": "アイテムでループ", + "Split your flow into branches depending on condition(s)": "条件に応じてフローを分岐します", + "Empty Trigger": "空のトリガー", + "An internal error occurred while fetching data, please contact support": "データの取得中に内部エラーが発生しました。サポートにお問い合わせください。", + "An internal error occurred, please contact support": "内部エラーが発生しました。サポートにお問い合わせください。", + "Custom Javascript Code": "カスタムJavascriptコード", + "Router": "Router", + "recordsCount": "{recordsCount, plural, =0 {0 Records} =1 {1 Record} other {# Records}}", + "selected": "選択済み", + "All records selected": "すべてのレコードが選択されました", + "fieldsCount": "{fieldsCount, plural, =0 {0 Fields} =1 {1 Field} other {# Fields}}", + "Saving...": "保存中...", + "Loading more...": "さらに読み込み中...", + "Export Table": "テーブルをエクスポート", + "Delete Records": "レコードを削除", + "Are you sure you want to delete the selected records? This action cannot be undone.": "選択したレコードを削除してもよろしいですか?この操作は元に戻せません。", + "record": "レコード", + "records": "レコード", + "mm/dd/yyy": "yyyy/mm/dd", + "Delete Field": "フィールドを削除", + "Are you sure you want to delete this field? This action cannot be undone.": "このフィールドを削除してもよろしいですか?この操作は元に戻せません。", + "field": "フィールド", + "Ignored": "無視", + "Table": "表", + "CSV": "CSV", + "Field": "フィールド", + "Please select a csv file": "CSVファイルを選択してください", + "Max file size is {maxFileSize}MB": "最大ファイルサイズは {maxFileSize}MB です", + "Import CSV": "CSVをインポート", + "Imported records will be added to the bottom of the table": "インポートされたレコードがテーブルの下部に追加されます", + "Any records after the limit ({maxRecords} records) will be ignored": "制限後のすべてのレコード({maxRecords} レコード)は無視されます", + "CSV File": "CSVファイル", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "CSVファイルのインポート中に予期しないエラーが発生しました。copyエラーを押してサポートに送信してください。", + "Name must be unique": "名前は一意でなければなりません", + "Type is required": "タイプは必須です", + "Please add at least one option": "少なくとも1つのオプションを追加してください", + "New Field": "新しいフィールド", + "Options": "オプション", + "Name is already taken": "名前は既に使用されています", + "Table renamed": "テーブル名を変更しました", + "Table name": "テーブル名", + "Team Invitation Accepted": "チーム招待が承認されました", + "Thank you for accepting the invitation. We are redirecting you right now...": "招待を承認していただき、ありがとうございます。現在あなたをリダイレクトしています...", + "Invalid invitation token. Please try again.": "招待トークンが無効です。もう一度やり直してください。", + "Role updated successfully": "ロールの更新に成功しました", + "Error updating role": "ロールの更新中にエラーが発生しました", + "Please try again later": "後でもう一度お試しください", + "Edit Role for": "ロールの編集", + "Select Role": "ロールを選択", + "Avatar": "アバター", + "Remove {email}": "{email} を削除", + "Are you sure you want to remove this invitation?": "この招待状を削除してもよろしいですか?", + "Please select invitation type": "招待タイプを選択してください", + "Please select platform role": "プラットフォームの役割を選択してください", + "Invitation sent successfully": "招待状が送信されました", + "Please select a project role": "プロジェクトの役割を選択してください", + "Invitation link copied successfully": "招待リンクが正常にコピーされました", + "Invite User": "ユーザーを招待", + "Invitation Link": "招待リンク", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "以下のリンクをコピーし、招待したいユーザーと共有してください。招待の有効期限は24時間後です。", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "招待したいユーザーのメールアドレスを入力します。招待の有効期限は24時間後です。", + "Invite To": "招待先", + "Entire Platform": "プラットフォーム全体", + "Select Project Role": "プロジェクトのロールを選択", + "Invite": "招待", + "Platform Role": "プラットフォームロール", + "Select a platform role": "プラットフォームの役割を選択", + "Are you sure you want to remove this member?": "このメンバーを削除してもよろしいですか?", + "Editor": "エディター", + "Operator": "演算子", + "Viewer": "ビューアー", + "Select a project role": "プロジェクトの役割を選択", + "Steps in this flow": "この流れのステップ", + "Invalid Access": "不正なアクセス", + "Either the project does not exist or you do not have access to it.": "プロジェクトが存在しないか、アクセスできません。" +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/ko/translation.json b/packages/react-ui/public/locales/ko/translation.json new file mode 100644 index 0000000..b2b2d88 --- /dev/null +++ b/packages/react-ui/public/locales/ko/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Publish", + "Latest version is published": "Latest version is published", + "Your flow has incomplete steps": "Your flow has incomplete steps", + "Edit Flow": "Edit Flow", + "View Draft": "View Draft", + "Uncategorized": "Uncategorized", + "Go to folder": "Go to folder", + "Support": "Support", + "Runs": "Runs", + "Run Logs": "Run Logs", + "Versions": "Versions", + "Versions History": "Versions History", + "Error generating code": "Error generating code", + "AI Copilot": "AI Copilot", + "i.e Calculate the sum of a list...": "i.e Calculate the sum of a list...", + "Send": "Send", + "Generating Code": "Generating Code", + "Hello there! I am here to generate code that helps with your flow": "Hello there! I am here to generate code that helps with your flow", + "Here are examples of what I am best used for: ": "Here are examples of what I am best used for: ", + "Text Processing": "Text Processing", + "Process strings, dates and data": "Process strings, dates and data", + "Data Operations": "Data Operations", + "Change data from one format to another": "Change data from one format to another", + "Calculations": "Calculations", + "Handle math and statistics": "Handle math and statistics", + "API Integration": "API Integration", + "Connect with external services. Best for simple integrations currently.": "Connect with external services. Best for simple integrations currently.", + "What would you like me to help you with?": "What would you like me to help you with?", + "Insert": "Insert", + "Data Selector": "Data Selector", + "Search": "Search", + "No matching data": "No matching data", + "Try adjusting your search": "Try adjusting your search", + "This trigger needs to have data loaded from your account, to use as sample data.": "This trigger needs to have data loaded from your account, to use as sample data.", + "This step needs to be tested in order to view its data.": "This step needs to be tested in order to view its data.", + "Go to Trigger": "Go to Trigger", + "Go to Step": "Go to Step", + "Select Mode": "Select Mode", + "Move Mode": "Move Mode", + "Reset Zoom": "Reset Zoom", + "Zoom In": "Zoom In", + "Zoom Out": "Zoom Out", + "Fit to View": "Fit to View", + "Replace": "Replace", + "Copy": "Copy", + "Duplicate": "Duplicate", + "Paste After Last Step": "Paste After Last Step", + "Paste Inside Loop": "Paste Inside Loop", + "Paste After": "Paste After", + "Paste Inside...": "Paste Inside...", + "New Branch": "New Branch", + "Paste Inside Branch": "Paste Inside Branch", + "Delete": "Delete", + "Duplicate Branch": "Duplicate Branch", + "Delete Branch": "Delete Branch", + "Invalid Move": "Invalid Move", + "The destination location is a child of the dragged step": "The destination location is a child of the dragged step", + "End": "End", + "Skipped": "Skipped", + "Incomplete settings": "Incomplete settings", + "logo": "logo", + "Step Icon": "Step Icon", + "Branch": "Branch", + "incompleteSteps": "{invalidSteps, plural, =0 {no incomplete steps} =1 {Complete 1 step} other {Complete # steps}}", + "Test Flow": "Test Flow", + "Please test the trigger first": "Please test the trigger first", + "View Only": "View Only", + "Use as Draft": "Use as Draft", + "Are you sure?": "Are you sure?", + "Your current draft version will be overwritten with": "Your current draft version will be overwritten with", + "version #": "version #", + "Cancel": "Cancel", + "Confirm": "Confirm", + "Version": "Version", + "Viewing": "Viewing", + "View": "View", + "Version History": "Version History", + "Error, please try again.": "Error, please try again.", + "Continue on Failure": "Continue on Failure", + "Enable this option to skip this step and continue the flow normally if it fails.": "Enable this option to skip this step and continue the flow normally if it fails.", + "Retry on Failure": "Retry on Failure", + "Automatically retry up to four attempts when failed.": "Automatically retry up to four attempts when failed.", + "Remove": "Remove", + "Add Item": "Add Item", + "File Input": "File Input", + "Date Input": "Date Input", + "Dynamic value": "Dynamic value", + "Select an option": "Select an option", + "Unexpected error, please retry": "Unexpected error, please retry", + "Unexpected error, please refresh the page or contact support": "Unexpected error, please refresh the page or contact support", + "Name can only contain letters, numbers and underscores": "Name can only contain letters, numbers and underscores", + "Ask AI": "Ask AI", + "Create Todo Guide": "Create Todo Guide", + "Where would you like the todo to be reviewed?": "Where would you like the todo to be reviewed?", + "Activepieces Todos": "Activepieces Todos", + "Users will manage tasks directly in Activepieces": "Users will manage tasks directly in Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.", + "External Channel (Slack, Teams, Email, ...)": "External Channel (Slack, Teams, Email, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.", + "Preview (Activepieces Todos)": "Preview (Activepieces Todos)", + "Preview (External channel)": "Preview (External channel)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "You can add the channel before the Wait Step, and configure the logic in the Router step", + "Add Steps": "Add Steps", + "All": "All", + "AI": "AI", + "Core": "Core", + "Apps": "Apps", + "Not available as trigger": "Not available as trigger", + "Not available as action": "Not available as action", + "Let our AI assistant help you out": "Let our AI assistant help you out", + "Or": "Or", + "Request Piece": "Request Piece", + "No pieces found": "No pieces found", + "Please select a piece first": "Please select a piece first", + "All Iterations": "All Iterations", + "Duration": "Duration", + "Input": "Input", + "Output": "Output", + "There are no logs captured for this run.": "There are no logs captured for this run.", + "Logs are kept for {days} days after execution and then deleted.": "Logs are kept for {days} days after execution and then deleted.", + "Run Details": "Run Details", + "Iteration": "Iteration", + "Done": "Done", + "Took": "Took", + "Running": "Running", + "on latest version": "on latest version", + "from failed step": "from failed step", + "Recent Runs": "Recent Runs", + "No runs found": "No runs found", + "Close": "Close", + "OR": "OR", + "And If": "And If", + "+ And": "+ And", + "+ Or": "+ Or", + "(Text) Contains": "(Text) Contains", + "(Text) Does not contain": "(Text) Does not contain", + "(Text) Exactly matches": "(Text) Exactly matches", + "(Text) Does not exactly match": "(Text) Does not exactly match", + "(Text) Starts with": "(Text) Starts with", + "(Text) Does not start with": "(Text) Does not start with", + "(Text) Ends with": "(Text) Ends with", + "(Text) Does not end with": "(Text) Does not end with", + "(List) Contains": "(List) Contains", + "(List) Does not contain": "(List) Does not contain", + "(Number) Is greater than": "(Number) Is greater than", + "(Number) Is less than": "(Number) Is less than", + "(Number) Is equal to": "(Number) Is equal to", + "(Date/time) After": "(Date/time) After", + "(Date/time) Before": "(Date/time) Before", + "(Date/time) Equals": "(Date/time) Equals", + "(Boolean) Is true": "(Boolean) Is true", + "(Boolean) Is false": "(Boolean) Is false", + "(List) Is empty": "(List) Is empty", + "(List) Is not empty": "(List) Is not empty", + "Exists": "Exists", + "Does not exist": "Does not exist", + "Incomplete condition": "Incomplete condition", + "First value": "First value", + "Second value": "Second value", + "Case sensitive": "Case sensitive", + "Execute If": "Execute If", + "The package name is required": "The package name is required", + "Success": "Success", + "Package added successfully": "Package added successfully", + "Could not fetch package version": "Could not fetch package version", + "Add NPM Package": "Add NPM Package", + "Type the name of the npm package you want to add.": "Type the name of the npm package you want to add.", + "Package Name": "Package Name", + "The latest version will be fetched and added": "The latest version will be fetched and added", + "Add": "Add", + "Code": "Code", + "Dependencies": "Dependencies", + "Use code": "Use code", + "Add package": "Add package", + "Inputs": "Inputs", + "Edit Step Name": "Edit Step Name", + "Edit Branch Name": "Edit Branch Name", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.", + "Items": "Items", + "Select an array of items": "Select an array of items", + "Reconnect": "Reconnect", + "Select a connection": "Select a connection", + "Create Connection": "Create Connection", + "Rename": "Rename", + "Move": "Move", + "Add Branch": "Add Branch", + "Execute": "Execute", + "Only the first (left) matching branch": "Only the first (left) matching branch", + "All matching paths from left to right": "All matching paths from left to right", + "Branches": "Branches", + "{field} is required": "{field} is required", + "Tool Sample Data": "Tool Sample Data", + "Fill in the following fields to use them as sample data for the trigger.": "Fill in the following fields to use them as sample data for the trigger.", + "No input fields defined in the schema": "No input fields defined in the schema", + "Save": "Save", + "Test Environment": "Test Environment", + "Assigned to": "Assigned to", + "(Me)": "(Me)", + "Please select status to resolve the todo": "Please select status to resolve the todo", + "Resolve": "Resolve", + "Change status to resolved": "Change status to resolved", + "Send Sample Data to Webhook": "Send Sample Data to Webhook", + "Method": "Method", + "Query Params": "Query Params", + "Headers": "Headers", + "Body": "Body", + "Type": "Type", + "JSON": "JSON", + "Text": "Text", + "Form Data": "Form Data", + "Generate Sample Data": "Generate Sample Data", + "Test Step": "Test Step", + "Retest": "Retest", + "Testing Failed": "Testing Failed", + "Tested Successfully": "Tested Successfully", + "Logs": "Logs", + "There is no sample data available found for this trigger.": "There is no sample data available found for this trigger.", + "Internal error, please try again later.": "Internal error, please try again later.", + "Failed to run test step and no error message was returned": "Failed to run test step and no error message was returned", + "Please fix inputs first": "Please fix inputs first", + "No sample data available": "No sample data available", + "Old results were removed, retest for new sample data": "Old results were removed, retest for new sample data", + "Result #": "Result #", + "The sample data can be used in the next steps.": "The sample data can be used in the next steps.", + "Testing Trigger": "Testing Trigger", + "Action Required": "Action Required", + "testPieceWebhookTriggerNote": "Please go to {pieceName} and trigger {triggerName}.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Send Data to the webhook URL to generate sample data to use in the next steps", + "Test Trigger": "Test Trigger", + "Use Mock Data": "Use Mock Data", + "Load Sample Data": "Load Sample Data", + "Test Tool": "Test Tool", + "home": "home", + "Home": "Home", + "Alerts": "Alerts", + "Releases": "Releases", + "Flows": "Flows", + "Products": "Products", + "MCP": "MCP", + "Tables": "Tables", + "Todos": "Todos", + "Push to Git": "Push to Git", + "Move To": "Move To", + "Duplicating": "Duplicating", + "Import": "Import", + "Exporting": "Exporting", + "Export": "Export", + "Share": "Share", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.", + "You are on a development branch, this will also delete the flow from the remote repository.": "You are on a development branch, this will also delete the flow from the remote repository.", + "flow": "flow", + "Community Support": "Community Support", + "Overview": "Overview", + "Projects": "Projects", + "Users": "Users", + "Setup": "Setup", + "Branding": "Branding", + "Global Connections": "Global Connections", + "Pieces": "Pieces", + "Templates": "Templates", + "License Key": "License Key", + "Security": "Security", + "Audit Logs": "Audit Logs", + "Single Sign On": "Single Sign On", + "Signing Keys": "Signing Keys", + "Project Roles": "Project Roles", + "API Keys": "API Keys", + "Infrastructure": "Infrastructure", + "Workers": "Workers", + "Health": "Health", + "Billing": "Billing", + "Settings": "Settings", + "Contact Sales": "Contact Sales", + "General": "General", + "Appearance": "Appearance", + "Team": "Team", + "Environments": "Environments", + "Project Settings": "Project Settings", + "Exit Platform Admin": "Exit Platform Admin", + "Enter Platform Admin": "Enter Platform Admin", + "Logout": "Logout", + "Misc": "Misc", + "Connections": "Connections", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "Today": "Today", + "Tasks": "Tasks", + "AI Credits": "AI Credits", + "Usage resets in": "Usage resets in", + "Manage": "Manage", + "Unlimited": "Unlimited", + "Enabled": "Enabled", + "Disabled": "Disabled", + "Could not claim the authorization code, make sure you have correct settings and try again.": "Could not claim the authorization code, make sure you have correct settings and try again.", + "Connection failed with error {msg}": "Connection failed with error {msg}", + "You don't have the permission to create a connection.": "You don't have the permission to create a connection.", + "Reconnect {displayName} Connection": "Reconnect {displayName}", + "Connect to {displayName}": "Connect to {displayName}", + "Connection Name": "Connection Name", + "Connection name": "Connection name", + "New Connection": "New Connection", + "Redirect URL": "Redirect URL", + "Client ID": "Client ID", + "Client Secret": "Client Secret", + "Connect": "Connect", + "Disconnect": "Disconnect", + "I would like to use my own App Credentials": "I would like to use my own App Credentials", + "I would like to use predefined App Credentials": "I would like to use predefined App Credentials", + "Permission needed": "Permission needed", + "Connections replaced successfully": "Connections replaced successfully", + "Error": "Error", + "Failed to replace connections": "Failed to replace connections", + "Failed to get affected flows": "Failed to get affected flows", + "Please select a piece": "Please select a piece", + "Please select a connection to replace": "Please select a connection to replace", + "Please select a connection to replace with": "Please select a connection to replace with", + "Replace Connections": "Replace Connections", + "Confirm Replacement": "Confirm Replacement", + "Replace one connection with another.": "Replace one connection with another.", + "This action requires ": "This action requires ", + "reconnecting": "reconnecting", + " any associated MCP pieces.": " any associated MCP pieces.", + "Piece": "Piece", + "Select a piece": "Select a piece", + "Connection to Replace": "Connection to Replace", + "Choose connection to replace": "Choose connection to replace", + "Replaced With": "Replaced With", + "Choose connection to replace with": "Choose connection to replace with", + "All flows will be changed to use the replaced with connection": "All flows will be changed to use the replaced with connection", + "Next": "Next", + "No flows will be affected by this change": "No flows will be affected by this change", + "Back": "Back", + "Unnamed tool": "Unnamed tool", + "This flow is enabled": "This flow is enabled", + "Enable this flow to make it available": "Enable this flow to make it available", + "Piece is updated successfully": "Piece is updated successfully", + "Piece is added successfully": "Piece is added successfully", + "Failed to update piece": "Failed to update piece", + "Failed to add piece": "Failed to add piece", + "Please select a connection": "Please select a connection", + "Your MCP server already has this piece": "Your MCP server already has this piece", + "+ New Connection": "+ New Connection", + "Edit Piece": "Edit Piece", + "Add Piece": "Add Piece", + "Connection": "Connection", + "MCP piece": "MCP piece", + "Failed to update piece status": "Failed to update piece status", + "Connection required": "Connection required", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.", + "piece": "piece", + "Connect your AI assistant to external services": "Connect your AI assistant to external services", + "Collapse": "Collapse", + "Show All": "Show All", + "Server URL": "Server URL", + "Hide the token for security": "Hide the token for security", + "Show the token": "Show the token", + "Generate a new token for security. This will invalidate the current URL.": "Generate a new token for security. This will invalidate the current URL.", + "URL copied to clipboard": "URL copied to clipboard", + "Copy URL to clipboard": "Copy URL to clipboard", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.", + "Max tables reached": "Max tables reached", + "You can't create more than {maxTables} tables": "You can't create more than {maxTables} tables", + "Name": "Name", + "Created": "Created", + "Delete Tables": "Delete Tables", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Are you sure you want to delete the selected tables? This action cannot be undone.", + "table": "table", + "Create and manage your tables to store your automation data": "Create and manage your tables to store your automation data", + "New Table": "New Table", + "No tables have been created yet": "No tables have been created yet", + "Create a table to get started and start managing your automation data": "Create a table to get started and start managing your automation data", + "Error deleting connections": "Error deleting connections", + "Status": "Status", + "Display Name": "Display Name", + "Owner": "Owner", + "App": "App", + "This connection is global and can be managed in the platform admin": "This connection is global and can be managed in the platform admin", + "External ID": "External ID", + "Connected At": "Connected At", + "Confirm Deletion": "Confirm Deletion", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Are you sure you want to delete the selected connections? This action cannot be undone.", + "Deleting connections may cause your Flows or MCP tools to break.": "Deleting connections may cause your Flows or MCP tools to break.", + "Manage project connections to external systems.": "Manage project connections to external systems.", + "No connections found": "No connections found", + "Come back later when you create a automation to manage your connections": "Come back later when you create a automation to manage your connections", + "Steps": "Steps", + "Folder": "Folder", + "Flow name": "Flow name", + "No flows found": "No flows found", + "Create a workflow to start automating": "Create a workflow to start automating", + "Create and manage your flows, run history and run issues": "Create and manage your flows, run history and run issues", + "Issues": "Issues", + "Untitled": "Untitled", + "Create flow": "Create flow", + "From scratch": "From scratch", + "Use a template": "Use a template", + "From local file": "From local file", + "Flow Name": "Flow Name", + "Count": "Count", + "First Seen": "First Seen", + "Last Seen": "Last Seen", + "Issues in {flowDisplayName} is marked as resolved.": "Issues in {flowDisplayName} is marked as resolved.", + "Unlock Issues": "Unlock Issues", + "Track issues in your workflows and troubleshoot them.": "Track issues in your workflows and troubleshoot them.", + "No issues found": "No issues found", + "All your workflows are running smoothly.": "All your workflows are running smoothly.", + "Mark as Resolved": "Mark as Resolved", + "All Flows Are Turned Off": "All Flows Are Turned Off", + "Task Usage Exceeded": "Task Usage Exceeded", + "of the Allowed Limit.": "of the Allowed Limit.", + "When a project tasks limit is reached,": "When a project tasks limit is reached,", + "all flows will be turned off and you will not be able to run any flows.": "all flows will be turned off and you will not be able to run any flows.", + "Please visit": "Please visit", + "Your Plan": "Your Plan", + "and increase your task limit, which requires your payment details.": "and increase your task limit, which requires your payment details.", + "Please contact your admin to increase the project task limit.": "Please contact your admin to increase the project task limit.", + "and increase the project task limit.": "and increase the project task limit.", + "Dismiss": "Dismiss", + "Token rotated successfully": "Token rotated successfully", + "Failed to rotate token": "Failed to rotate token", + "Piece removed successfully": "Piece removed successfully", + "Failed to remove piece": "Failed to remove piece", + "Flow created successfully": "Flow created successfully", + "Failed to create flow": "Failed to create flow", + "Add Flow": "Add Flow", + "Let your AI assistant trigger automations": "Let your AI assistant trigger automations", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Connect to your hosted MCP Server using any MCP client to communicate with tools", + "MCP Server": "MCP Server", + "My Tools": "My Tools", + "Create Flow": "Create Flow", + "Note": "Note", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.", + "This URL grants access to your tools and data. Only share with trusted applications.": "This URL grants access to your tools and data. Only share with trusted applications.", + "Server Configuration": "Server Configuration", + "Hide sensitive data": "Hide sensitive data", + "Show sensitive data": "Show sensitive data", + "Create a new URL. The current one will stop working.": "Create a new URL. The current one will stop working.", + "Copy configuration": "Copy configuration", + "Configuration copied to clipboard": "Configuration copied to clipboard", + "Claude": "Claude", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "Server/Other", + "Note: MCPs only work with": "Note: MCPs only work with", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", not the web version.", + "Prerequisites:": "Prerequisites:", + "Install": "Install", + "Node.js": "Node.js", + "and": "and", + "Open Settings:": "Open Settings:", + "Click the menu and select": "Click the menu and select", + "Developer": "Developer", + "Configure MCP:": "Configure MCP:", + "Click": "Click", + "Edit Config": "Edit Config", + "and paste the configuration below": "and paste the configuration below", + "Save and Restart:": "Save and Restart:", + "Save the config and restart Claude Desktop": "Save the config and restart Claude Desktop", + "Navigate to": "Navigate to", + "Cursor Settings": "Cursor Settings", + "Add Server:": "Add Server:", + "Add new global MCP server": "Add new global MCP server", + "Configure:": "Configure:", + "Paste the configuration below and save": "Paste the configuration below and save", + "Use either method:": "Use either method:", + "Go to": "Go to", + "Advanced Settings": "Advanced Settings", + "Open Command Palette and select": "Open Command Palette and select", + "Windsurf Settings Page": "Windsurf Settings Page", + "Navigate to Cascade:": "Navigate to Cascade:", + "Select": "Select", + "Cascade": "Cascade", + "in the sidebar": "in the sidebar", + "Add Server": "Add Server", + "Add custom server +": "Add custom server +", + "Copy URL": "Copy URL", + "Client Setup Instructions": "Client Setup Instructions", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "After changing connections or flows, reconnect your MCP server for changes to take effect.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.", + "icon": "icon", + "Unlock Analytics": "Unlock Analytics", + "Get insights into your platform usage and performance with our analytics dashboard": "Get insights into your platform usage and performance with our analytics dashboard", + "Active Flows": "Active Flows", + "The number of enabled flows in the platform": "The number of enabled flows in the platform", + "Active Projects": "Active Projects", + "The number of projects with at least one enabled flow": "The number of projects with at least one enabled flow", + "Active Users": "Active Users", + "The number of users logged in the last 30 days": "The number of users logged in the last 30 days", + "Out of {totalusers} total users": "Out of {totalusers} total users", + "Pieces Used": "Pieces Used", + "The number of unique pieces used across all flows": "The number of unique pieces used across all flows", + "Flows with AI": "Flows with AI", + "The number of enabled flows that use AI pieces": "The number of enabled flows that use AI pieces", + "Metrics": "Metrics", + "Executed Tasks": "Executed Tasks", + "Showing total executed tasks for specified time range": "Showing total executed tasks for specified time range", + "Tasks Usage Limit": "Tasks Usage Limit", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.", + "Number of monthly tasks": "Number of monthly tasks", + "Save changes": "Save changes", + "Limits updated successfully": "Limits updated successfully", + "Failed to update limits": "Failed to update limits", + "Failed to load billing information": "Failed to load billing information", + "Billing Amount": "Billing Amount", + "Manage Payment Details": "Manage Payment Details", + "Add Payment Details": "Add Payment Details", + "Current Task Usage": "Current Task Usage", + "Count of executed steps": "Count of executed steps", + "Billing Limit": "Billing Limit", + "Edit": "Edit", + "Add Limit": "Add Limit", + "Current Credit Usage": "Current Credit Usage", + "WebSocket Connection": "WebSocket Connection", + "Connected": "Connected", + "Disconnected": "Disconnected", + "No issues detected": "No issues detected", + "Check the status of your platform and its components": "Check the status of your platform and its components", + "System Health Status": "System Health Status", + "All systems are running smoothly": "All systems are running smoothly", + "Check the health of your worker machines": "Check the health of your worker machines", + "Workers Machine": "Workers Machine", + "This is demo data. In a real environment, this would show your actual worker machines.": "This is demo data. In a real environment, this would show your actual worker machines.", + "No workers found": "No workers found", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "You don't have any worker machines yet. Spin up new machines to execute your automations", + "IP Address": "IP Address", + "CPU Usage": "CPU Usage", + "Disk Usage": "Disk Usage", + "RAM Usage": "RAM Usage", + "Last Contact": "Last Contact", + "Configs": "Configs", + "Environment Variables": "Environment Variables", + "Websocket Connection Error": "Websocket Connection Error", + "Retry Connection": "Retry Connection", + "Update Available": "Update Available", + "Update Now": "Update Now", + "Your Universal AI needs a quick setup": "Your Universal AI needs a quick setup", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.", + "Configure": "Configure", + "Platform Alerts": "Platform Alerts", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.", + "View Alerts": "View Alerts", + "Used Tasks": "Used Tasks", + "Used AI Credits": "Used AI Credits", + "Cannot delete active project, switch to another project first": "Cannot delete active project, switch to another project first", + "Delete Projects": "Delete Projects", + "Are you sure you want to delete the selected projects?": "Are you sure you want to delete the selected projects?", + "New Project": "New Project", + "Validation error": "Validation error", + "Project has enabled flows. Please disable them first.": "Project has enabled flows. Please disable them first.", + "This project is active. Please switch to another project first.": "This project is active. Please switch to another project first.", + "Unlock Projects": "Unlock Projects", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Orchestrate your automation teams across projects with their own flows, connections and usage quotas", + "Manage your automation projects": "Manage your automation projects", + "No projects found": "No projects found", + "Start by creating projects to manage your automation teams": "Start by creating projects to manage your automation teams", + "Edit project": "Edit project", + "Name is required": "Name is required", + "Create New Project": "Create New Project", + "Project Name": "Project Name", + "Id": "Id", + "Enable API Keys": "Enable API Keys", + "Create and manage API keys to access Activepieces APIs.": "Create and manage API keys to access Activepieces APIs.", + "New Api Key": "New Api Key", + "No API keys found": "No API keys found", + "Start by creating an API key to communicate with Activepieces APIs": "Start by creating an API key to communicate with Activepieces APIs", + "Delete API Key": "Delete API Key", + "Are you sure you want to delete this API key?": "Are you sure you want to delete this API key?", + "API Key": "API Key", + "API Key Created": "API Key Created", + "Create New API Key": "Create New API Key", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Please save this secret key somewhere safe and accessible. For security reasons,", + "you won't be able to view it again after closing this dialog.": "you won't be able to view it again after closing this dialog.", + "API Key Name": "API Key Name", + "Action": "Action", + "Performed By": "Performed By", + "Project": "Project", + "Unlock Audit Logs": "Unlock Audit Logs", + "Comply with internal and external security policies by tracking activities done within your account": "Comply with internal and external security policies by tracking activities done within your account", + "Track activities done within your platform": "Track activities done within your platform", + "No audit logs found": "No audit logs found", + "Come back later when you have some activity to audit": "Come back later when you have some activity to audit", + "Resource": "Resource", + "Details": "Details", + "N/A": "N/A", + "Flow Run": "Flow Run", + "Flow": "Flow", + "User": "User", + "Signing Key": "Signing Key", + "Project Role Management": "Project Role Management", + "Define custom roles and permissions to control what your team members can access and modify": "Define custom roles and permissions to control what your team members can access and modify", + "Define custom roles and permissions that can be assigned to your team members": "Define custom roles and permissions that can be assigned to your team members", + "New Role": "New Role", + "Contact sales to unlock custom roles": "Contact sales to unlock custom roles", + "Create New Role": "Create New Role", + "View ": "View ", + "Edit ": "Edit ", + "Role Name": "Role Name", + "Permissions": "Permissions", + "None": "None", + "Read": "Read", + "Write": "Write", + "Create": "Create", + "Email": "Email", + "First Name": "First Name", + "Last Name": "Last Name", + "Roles": "Roles", + "View the users assigned to this role": "View the users assigned to this role", + "Role": "Role", + "No users found": "No users found", + "Starting by assigning users to this role": "Starting by assigning users to this role", + "Project Role entry deleted successfully": "Project Role entry deleted successfully", + "Updated": "Updated", + "No project roles found": "No project roles found", + "Create custom project roles to manage permissions for platform users": "Create custom project roles to manage permissions for platform users", + "Show Users": "Show Users", + "View Role": "View Role", + "Edit Role": "Edit Role", + "Delete Role": "Delete Role", + "Project Role": "Project Role", + "Unlock Embedding Through JS SDK": "Unlock Embedding Through JS SDK", + "Enable signing keys to access embedding functionalities.": "Enable signing keys to access embedding functionalities.", + "New Signing Key": "New Signing Key", + "No signing keys found": "No signing keys found", + "Create a signing key to authenticate users with embedding": "Create a signing key to authenticate users with embedding", + "Delete Signing Key": "Delete Signing Key", + "Are you sure you want to delete this signing key?": "Are you sure you want to delete this signing key?", + "Signing Key Created": "Signing Key Created", + "Create New Signing Key": "Create New Signing Key", + "Signing Key Name": "Signing Key Name", + "Allowed domains updated": "Allowed domains updated", + "Update": "Update", + "Enable": "Enable", + "Configure Allowed Domains": "Configure Allowed Domains", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.", + "Add Domain": "Add Domain", + "Allow logins through {providerName}'s single sign-on functionality.": "Allow logins through {providerName}'s single sign-on functionality.", + "Email authentication updated": "Email authentication updated", + "Enable Single Sign On": "Enable Single Sign On", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Let your users sign in with your current SSO provider or give them self serve sign up access", + "Allowed Domains": "Allowed Domains", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Allow users to authenticate with specific domains. Leave empty to allow all domains.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Allowed Email Login", + "Allow logins through email and password.": "Allow logins through email and password.", + "Single sign on settings updated": "Single sign on settings updated", + "Disable": "Disable", + "Configure {provider} SSO": "Configure {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} Client ID", + "{provider} Client Secret": "{provider} Client Secret", + "Single sign-on settings updated": "Single sign-on settings updated", + "Configure SAML 2.0 SSO": "Configure SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDP Metadata", + "IDP Certificate": "IDP Certificate", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "Base URL", + "Resource Name": "Resource Name", + "Deployment Name": "Deployment Name", + "Saving": "Saving", + "Activepieces Copilot": "Activepieces Copilot", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot is configured and ready to help your users build flows faster using AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configure Activepieces Copilot to help your users build flows faster using AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Set provider credentials that will be used by universal AI pieces, i.e Text AI.", + "AI Providers": "AI Providers", + "Copilot": "Copilot", + "Configure credentials for {providerName} AI provider.": "Configure credentials for {providerName} AI provider.", + "Update AI Provider": "Update AI Provider", + "Enable AI Provider": "Enable AI Provider", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Please enter a valid domain", + "Your changes have been saved.": "Your changes have been saved.", + "The domain is already added.": "The domain is already added.", + "Add Custom Domain": "Add Custom Domain", + "Enter a domain name without a protocol (e.g. example.com)": "Enter a domain name without a protocol (e.g. example.com)", + "Logo URL": "Logo URL", + "Icon URL": "Icon URL", + "Favicon URL": "Favicon URL", + "Default Language": "Default Language", + "Select Language": "Select Language", + "No Languages": "No Languages", + "Primary Color": "Primary Color", + "Custom Domains": "Custom Domains", + "No domains added yet.": "No domains added yet.", + "Verified": "Verified", + "Pending, please contact the support for dns verification.": "Pending, please contact the support for dns verification.", + "Are you sure you want to delete {domain}?": "Are you sure you want to delete {domain}?", + "Brand Activepieces": "Brand Activepieces", + "Give your users an experience that looks like you by customizing the color, logo and more": "Give your users an experience that looks like you by customizing the color, logo and more", + "Configure the appearance and SMTP settings for your platform.": "Configure the appearance and SMTP settings for your platform.", + "Invalid host": "Invalid host", + "Invalid username": "Invalid username", + "Invalid password": "Invalid password", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Invalid sender name", + "SMTP is configured": "SMTP is configured", + "Mail Server": "Mail Server", + "Set up your SMTP settings to send emails from your domain.": "Set up your SMTP settings to send emails from your domain.", + "Disable Mail Server": "Disable Mail Server", + "Are you sure you want to disable your mail server?": "Are you sure you want to disable your mail server?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "This will stop you from sending emails for issues, quota limits, invitations and forgot password.", + "mail server": "mail server", + "Host": "Host", + "Port": "Port", + "Username": "Username", + "Password": "Password", + "Sender Email": "Sender Email", + "Sender Name": "Sender Name", + "Enable Global Connections": "Enable Global Connections", + "Manage platform-wide connections to external systems.": "Manage platform-wide connections to external systems.", + "No global connections found": "No global connections found", + "Create a global connection that can be shared to multiple projects": "Create a global connection that can be shared to multiple projects", + "License key is invalid": "License key is invalid", + "Invalid license key": "Invalid license key", + "License activated!": "License activated!", + "Activate License Key": "Activate License Key", + "Let the magic begin!": "Let the magic begin!", + "Activate": "Activate", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ", + "This feature is not available in your current edition. ": "This feature is not available in your current edition. ", + "Learn how to upgrade": "Learn how to upgrade", + "Activate your platform and unlock enterprise features": "Activate your platform and unlock enterprise features", + "Activate License": "Activate License", + "Expiration": "Expiration", + "Valid until": "Valid until", + "Expired": "Expired", + "Expires soon": "Expires soon", + "Features": "Features", + "Applying Tags...": "Applying Tags...", + "Tags applied.": "Tags applied.", + "Tag created": "Tag created", + "Tag": "Tag", + "Tags": "Tags", + "Control Pieces": "Control Pieces", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Show the pieces that matter most to your users and hide the ones you don't like.", + "Manage the pieces that are available to your users": "Manage the pieces that are available to your users", + "Start by installing pieces that you want to use in your automations": "Start by installing pieces that you want to use in your automations", + "Piece Name": "Piece Name", + "Hide this piece from all projects": "Hide this piece from all projects", + "Show this piece for all projects": "Show this piece for all projects", + "Unpin this piece": "Unpin this piece", + "Pin this piece": "Pin this piece", + "Pieces synced": "Pieces synced", + "Pieces have been synced from the activepieces cloud.": "Pieces have been synced from the activepieces cloud.", + "OAuth2 Credentials Deleted": "OAuth2 Credentials Deleted", + "OAuth2 Credentials Updated": "OAuth2 Credentials Updated", + "Configure OAuth2 APP": "Configure OAuth2 APP", + "Delete OAuth2 APP": "Delete OAuth2 APP", + "Templates deleted successfully": "Templates deleted successfully", + "Delete Templates": "Delete Templates", + "Are you sure you want to delete the selected templates?": "Are you sure you want to delete the selected templates?", + "New Template": "New Template", + "Unlock Templates": "Unlock Templates", + "Convert the most common automations into reusable templates 1 click away from your users": "Convert the most common automations into reusable templates 1 click away from your users", + "Convert the most common automations into reusable templates": "Convert the most common automations into reusable templates", + "No templates found": "No templates found", + "Create a template for your user to inspire them": "Create a template for your user to inspire them", + "Edit template": "Edit template", + "Template is required": "Template is required", + "Update New Template": "Update New Template", + "Create New Template": "Create New Template", + "Template Name": "Template Name", + "Description": "Description", + "Template Description": "Template Description", + "Blog URL": "Blog URL", + "Template Blog URL": "Template Blog URL", + "Template": "Template", + "Invalid JSON": "Invalid JSON", + "User deleted successfully": "User deleted successfully", + "User activated successfully": "User activated successfully", + "User deactivated successfully": "User deactivated successfully", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Manage your users and their access to your projects", + "Start inviting users to your project": "Start inviting users to your project", + "External Id": "External Id", + "Admin": "Admin", + "Member": "Member", + "Activated": "Activated", + "Deactivated": "Deactivated", + "Edit user": "Edit user", + "Admin cannot be deactivated": "Admin cannot be deactivated", + "Deactivate user": "Deactivate user", + "Activate user": "Activate user", + "Delete User": "Delete User", + "Are you sure you want to delete this user?": "Are you sure you want to delete this user?", + "Delete user": "Delete user", + "Update User Role": "Update User Role", + "Meeting Summary Flow": "Meeting Summary Flow", + "Added new features and fixed bugs": "Added new features and fixed bugs", + "Flows Changes": "Flows Changes", + "Connections Changes": "Connections Changes", + "New connections are placeholders and need to be reconnected again": "New connections are placeholders and need to be reconnected again", + "renamed to": "renamed to", + "Tables Changes": "Tables Changes", + "No changes to apply": "No changes to apply", + "Apply Changes": "Apply Changes", + "Create Git Release": "Create Git Release", + "Create Project Release": "Create Project Release", + "Create Rollback to": "Create Rollback to", + "Source": "Source", + "Rollback": "Rollback", + "Imported At": "Imported At", + "Imported By": "Imported By", + "Track and manage your project version history and deployments. ": "Track and manage your project version history and deployments. ", + "Environments & Releases": "Environments & Releases", + "Project Releases": "Project Releases", + "Create Release": "Create Release", + "From Git": "From Git", + "From Project": "From Project", + "No project releases found": "No project releases found", + "Create a project release to get started": "Create a project release to get started", + "Please select project": "Please select project", + "No Changes Found": "No Changes Found", + "There are no differences to apply": "There are no differences to apply", + "Please select a project": "Please select a project", + "Search projects...": "Search projects...", + "Review Changes": "Review Changes", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Imported by", + "from": "from", + "No description provided": "No description provided", + "Invitation only sign up": "Invitation only sign up", + "Please ask your administrator to add you to the organization.": "Please ask your administrator to add you to the organization.", + "Something went wrong, please try again.": "Something went wrong, please try again.", + "Please try again.": "Please try again.", + "Please enter a valid email address": "Please enter a valid email address", + "The email is already added.": "The email is already added.", + "Add email": "Add email", + "Only project admins can do this": "Only project admins can do this", + "Add Alert Email": "Add Alert Email", + "Enter the email address to receive alerts.": "Enter the email address to receive alerts.", + "Add Email": "Add Email", + "Emails": "Emails", + "Add email addresses to receive alerts.": "Add email addresses to receive alerts.", + "No emails added yet.": "No emails added yet.", + "Choose what you want to be notified about.": "Choose what you want to be notified about.", + "Project and alert permissions are required to change this setting.": "Project and alert permissions are required to change this setting.", + "Every Failed Run": "Every Failed Run", + "Get an email alert when a flow fails.": "Get an email alert when a flow fails.", + "Get an email alert when a new issue created.": "Get an email alert when a new issue created.", + "Never": "Never", + "Turn off email notifications.": "Turn off email notifications.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Customize the appearance of the app. Automatically switch between day and night themes.", + "Select the theme for the dashboard.": "Select the theme for the dashboard.", + "Light": "Light", + "Dark": "Dark", + "Select the language that will be used in the dashboard.": "Select the language that will be used in the dashboard.", + "Select language": "Select language", + "Search language...": "Search language...", + "No language found.": "No language found.", + "Help us translate Activepieces to your language.": "Help us translate Activepieces to your language.", + "Learn more": "Learn more", + "Git Connection Removed": "Git Connection Removed", + "Your Git repository has been successfully disconnected": "Your Git repository has been successfully disconnected", + "Enable Environments": "Enable Environments", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Deploy flows across development, staging and production environments with version control and team collaboration", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Connect to Git to enable version control, backup your flows, and manage multiple environments. ", + "Repository URL": "Repository URL", + "Not connected": "Not connected", + "Project Folder": "Project Folder", + "Releases Enabled": "Releases Enabled", + "You have successfully enabled releases": "You have successfully enabled releases", + "Enable releases to easily create and manage project releases.": "Enable releases to easily create and manage project releases.", + "The external ID is already taken.": "The external ID is already taken.", + "Manage general settings for your project.": "Manage general settings for your project.", + "Used to identify the project based on your SaaS ID": "Used to identify the project based on your SaaS ID", + "org-3412321": "org-3412321", + "Delete {name}": "Delete {name}", + "This will permanently delete this piece, all steps using it will fail.": "This will permanently delete this piece, all steps using it will fail.", + "Add a piece to your project that you want to use in your automations": "Add a piece to your project that you want to use in your automations", + "Pieces list updated": "Pieces list updated", + "Manage Pieces": "Manage Pieces", + "Choose which pieces you want to be available for your current project users": "Choose which pieces you want to be available for your current project users", + "Unlock Team Permissions": "Unlock Team Permissions", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial", + "Project Members": "Project Members", + "Invite your team members to collaborate.": "Invite your team members to collaborate.", + "No members are added to this project.": "No members are added to this project.", + "Pending Invitations": "Pending Invitations", + "No pending invitation.": "No pending invitation.", + "templateId is missing": "templateId is missing", + "Me Only": "Me Only", + "Unresolved": "Unresolved", + "Resolved": "Resolved", + "Title": "Title", + "Date Created": "Date Created", + "Manage todos for your project that are created by automations": "Manage todos for your project that are created by automations", + "No todos found": "No todos found", + "You do not have any pending todos. Great job!": "You do not have any pending todos. Great job!", + "Write a comment...": "Write a comment...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "This feature is still under testing and might be changed often", + "Failed to copy to clipboard": "Failed to copy to clipboard", + "{number} items selected": "{number} items selected", + "Select All": "Select All", + "No results found.": "No results found.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect must be used within MultiSelectProvider", + "Unset": "Unset", + "Refresh": "Refresh", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} more", + "Removed {entityName}": "Removed {entityName}", + "Download File": "Download File", + "Copied to clipboard": "Copied to clipboard", + "File is not available after execution.": "File is not available after execution.", + "Available for Projects": "Available for Projects", + "Select projects": "Select projects", + "No items": "No items", + "Previous": "Previous", + "to": "to", + "Last Week": "Last Week", + "Last Month": "Last Month", + "Last 3 Months": "Last 3 Months", + "Last 6 Months": "Last 6 Months", + "Next 7 days": "Next 7 days", + "Next 30 days": "Next 30 days", + "Next 90 days": "Next 90 days", + "Next 180 days": "Next 180 days", + "Select Time Range": "Select Time Range", + "Clear": "Clear", + "Download": "Download", + "Go to Dashboard": "Go to Dashboard", + "Select a file": "Select a file", + "Press space to separate values": "Press space to separate values", + "AM": "AM", + "PM": "PM", + "Already have an account?": "Already have an account?", + "Sign in": "Sign in", + "Don't have an account?": "Don't have an account?", + "Sign up": "Sign up", + "Welcome Back!": "Welcome Back!", + "Enter your email below to sign in to your account": "Enter your email below to sign in to your account", + "Let's Get Started!": "Let's Get Started!", + "Create your account and start flowing!": "Create your account and start flowing!", + "Your password was changed successfully": "Your password was changed successfully", + "Your password reset request has expired, please request a new one": "Your password reset request has expired, please request a new one", + "Reset Password": "Reset Password", + "Enter your new password": "Enter your new password", + "Password is required": "Password is required", + "Verification email resent, if previous one expired.": "Verification email resent, if previous one expired.", + "Password reset link resent, if previous one expired.": "Password reset link resent, if previous one expired.", + "We sent you a link to complete your registration to": "We sent you a link to complete your registration to", + "We sent you a link to reset your password to": "We sent you a link to reset your password to", + "Didn't receive an email or it expired?": "Didn't receive an email or it expired?", + "Resend": "Resend", + "Please enter your email": "Please enter your email", + "Check Your Inbox": "Check Your Inbox", + "If the user exists we'll send you an email with a link to reset your password.": "If the user exists we'll send you an email with a link to reset your password.", + "Send Password Reset Link": "Send Password Reset Link", + "Back to sign in": "Back to sign in", + "Email is invalid": "Email is invalid", + "Something went wrong, please try again later": "Something went wrong, please try again later", + "Invalid email or password": "Invalid email or password", + "User has been deactivated": "User has been deactivated", + "Email domain is disallowed": "Email domain is disallowed", + "Email authentication has been disabled": "Email authentication has been disabled", + "Forgot your password?": "Forgot your password?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "Sign up is restricted. You need an invitation to join. Please contact the administrator.", + "Email is already used": "Email is already used", + "Email authentication is disabled": "Email authentication is disabled", + "First name is required": "First name is required", + "Last name is required": "Last name is required", + "Email is required": "Email is required", + "Receive updates and newsletters from activepieces": "Receive updates and newsletters from activepieces", + "By creating an account, you agree to our": "By creating an account, you agree to our", + "terms of service": "terms of service", + "privacy policy": "privacy policy", + "Sign up With": "Sign up With", + "Google": "Google", + "Sign in With": "Sign in With", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "Email has been verified. You will be redirected to sign in...", + "Verifying email...": "Verifying email...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "invitation has expired, once you sign in again you will be able to resend the verification email.", + "Redirecting to sign in...": "Redirecting to sign in...", + "Password must contain at least one special character": "Password must contain at least one special character", + "Password must contain at least one lowercase letter": "Password must contain at least one lowercase letter", + "Password must contain at least one uppercase letter": "Password must contain at least one uppercase letter", + "Password must contain at least one number": "Password must contain at least one number", + "8-64 Characters": "8-64 Characters", + "Special Character": "Special Character", + "Lowercase": "Lowercase", + "Uppercase": "Uppercase", + "Number": "Number", + "Connection has been updated.": "Connection has been updated.", + "Edit Global Connection": "Edit Global Connection", + "Connection has been renamed.": "Connection has been renamed.", + "New Connection Name": "New Connection Name", + "Connection name already used": "Connection name already used", + "Please select at least one project": "Please select at least one project", + "Run Succeeded": "Run Succeeded", + "Run Failed": "Run Failed", + "Flow Run is paused": "Flow Run is paused", + "Run Failed due to quota exceeded": "Run Failed due to quota exceeded", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Run failed due to exceeding the memory limit of {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "Run exceeded {timeout} seconds, try to optimize your steps.", + "Run failed for an unknown reason, contact support.": "Run failed for an unknown reason, contact support.", + "Unknown": "Unknown", + "Exit Run": "Exit Run", + "Select shown": "Select shown", + "Select all": "Select all", + "Start Time": "Start Time", + "Runs replayed successfully": "Runs replayed successfully", + "Retry": "Retry", + "all except": "all except", + "all": "all", + "Only failed runs can be retried from failed step": "Only failed runs can be retried from failed step", + "No flow runs found": "No flow runs found", + "Come back later when your automations start running": "Come back later when your automations start running", + "Step running": "Step running", + "Step paused": "Step paused", + "Step Stopped": "Step Stopped", + "Step Succeeded": "Step Succeeded", + "Step Failed": "Step Failed", + "Please publish flow first": "Please publish flow first", + "Flow is on": "Flow is on", + "Flow is off": "Flow is off", + "Permission Needed": "Permission Needed", + "Draft Version": "Draft Version", + "Published Version": "Published Version", + "Locked Version": "Locked Version", + "flowsImported": "{flowsCount, plural, =0 {No Flows Imported} =1 {Flow Imported Successfully} other {Flows Imported Successfully}}", + "Template file is invalid": "Template file is invalid", + "No valid templates found. The following files failed to import: ": "No valid templates found. The following files failed to import: ", + "Please select a file first": "Please select a file first", + "Unsupported file type": "Unsupported file type", + "Import Flow": "Import Flow", + "Warning": "Warning", + "Importing a flow will overwrite your current one.": "Importing a flow will overwrite your current one.", + "Select a folder": "Select a folder", + "Folders": "Folders", + "Please select a folder": "Please select a folder", + "Moved flows successfully": "Moved flows successfully", + "Move Selected Flows": "Move Selected Flows", + "Select Folder": "Select Folder", + "No Folders": "No Folders", + "Flow has been renamed.": "Flow has been renamed.", + "New Flow Name": "New Flow Name", + "Use Template": "Use Template", + "Browse Templates": "Browse Templates", + "Search templates": "Search templates", + "No templates found, try adjusting your search": "No templates found, try adjusting your search", + "Read more about this template in": "Read more about this template in", + "this blog!": "this blog!", + "Share Template": "Share Template", + "Generate or update a template link for the current flow to easily share it with others.": "Generate or update a template link for the current flow to easily share it with others.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "The template will not have any credentials in connection fields, keeping sensitive information secure.", + "A short description of the template": "A short description of the template", + "Flow Is In Use": "Flow Is In Use", + "Flow is being used by another user, please try again later.": "Flow is being used by another user, please try again later.", + "Flow has been published.": "Flow has been published.", + "Flows have been exported.": "Flows have been exported.", + "Run": "Run", + "Real time flow": "Real time flow", + "Flow can't be published with empty trigger {name}": "Flow can't be published with empty trigger {name}", + "Please contact support as your published flow has a problem": "Please contact support as your published flow has a problem", + "Actions": "Actions", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.", + "You are on a development branch, this will not delete the flows from the remote repository.": "You are on a development branch, this will not delete the flows from the remote repository.", + "Please enter folder name": "Please enter folder name", + "Added folder successfully": "Added folder successfully", + "The folder name already exists.": "The folder name already exists.", + "New Folder": "New Folder", + "Folder Name": "Folder Name", + "Loading...": "Loading...", + "Delete {folderName}": "Delete {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "If you delete this folder, we will keep its flows and move them to Uncategorized.", + "All flows": "All flows", + "Please enter a folder name": "Please enter a folder name", + "Renamed flow successfully": "Renamed flow successfully", + "Folder name already used": "Folder name already used", + "New Folder Name": "New Folder Name", + "Connected successfully": "Connected successfully", + "Connect Git": "Connect Git", + "Remote URL": "Remote URL", + "Folder name is the name of the folder where the project will be stored or fetched.": "Folder name is the name of the folder where the project will be stored or fetched.", + "SSH Private Key": "SSH Private Key", + "The SSH private key to use for authentication.": "The SSH private key to use for authentication.", + "Only published flows can be pushed to Git": "Only published flows can be pushed to Git", + "Pushed successfully": "Pushed successfully", + "Invalid Operation": "Invalid Operation", + "Commit Message": "Commit Message", + "Enter a commit message to describe the changes you want to push.": "Enter a commit message to describe the changes you want to push.", + "Push": "Push", + "This field is required": "This field is required", + "Your submission was successfully received.": "Your submission was successfully received.", + "Flow not found": "Flow not found", + "The flow you are trying to submit to does not exist.": "The flow you are trying to submit to does not exist.", + "The flow failed to execute.": "The flow failed to execute.", + "Submit": "Submit", + "issues-notification": "issues-notification", + "Please select a package type": "Please select a package type", + "package.json not found in archive": "package.json not found in archive", + "Error processing archive file": "Error processing archive file", + "Please upload a .tgz file": "Please upload a .tgz file", + "Piece name is required for NPM Registry": "Piece name is required for NPM Registry", + "Piece version is required for NPM Registry": "Piece version is required for NPM Registry", + "Piece installed": "Piece installed", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "A piece with this name and version is already installed. Please update the version number in package.json and try again.", + "Install Piece": "Install Piece", + "Install a piece": "Install a piece", + "Package Type": "Package Type", + "NPM Registry": "NPM Registry", + "Packed Archive (.tgz)": "Packed Archive (.tgz)", + "Piece Version": "Piece Version", + "Package Archive": "Package Archive", + "Package archive": "Package archive", + "Powerful Node.js & TypeScript code with npm": "Powerful Node.js & TypeScript code with npm", + "Loop on Items": "Loop on Items", + "Split your flow into branches depending on condition(s)": "Split your flow into branches depending on condition(s)", + "Empty Trigger": "Empty Trigger", + "An internal error occurred while fetching data, please contact support": "An internal error occurred while fetching data, please contact support", + "An internal error occurred, please contact support": "An internal error occurred, please contact support", + "Custom Javascript Code": "Custom Javascript Code", + "Router": "Router", + "recordsCount": "{recordsCount, plural, =0 {0 Records} =1 {1 Record} other {# Records}}", + "selected": "selected", + "All records selected": "All records selected", + "fieldsCount": "{fieldsCount, plural, =0 {0 Fields} =1 {1 Field} other {# Fields}}", + "Saving...": "Saving...", + "Loading more...": "Loading more...", + "Export Table": "Export Table", + "Delete Records": "Delete Records", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Are you sure you want to delete the selected records? This action cannot be undone.", + "record": "record", + "records": "records", + "mm/dd/yyy": "mm/dd/yyy", + "Delete Field": "Delete Field", + "Are you sure you want to delete this field? This action cannot be undone.": "Are you sure you want to delete this field? This action cannot be undone.", + "field": "field", + "Ignored": "Ignored", + "Table": "Table", + "CSV": "CSV", + "Field": "Field", + "Please select a csv file": "Please select a csv file", + "Max file size is {maxFileSize}MB": "Max file size is {maxFileSize}MB", + "Import CSV": "Import CSV", + "Imported records will be added to the bottom of the table": "Imported records will be added to the bottom of the table", + "Any records after the limit ({maxRecords} records) will be ignored": "Any records after the limit ({maxRecords} records) will be ignored", + "CSV File": "CSV File", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support", + "Name must be unique": "Name must be unique", + "Type is required": "Type is required", + "Please add at least one option": "Please add at least one option", + "New Field": "New Field", + "Options": "Options", + "Name is already taken": "Name is already taken", + "Table renamed": "Table renamed", + "Table name": "Table name", + "Team Invitation Accepted": "Team Invitation Accepted", + "Thank you for accepting the invitation. We are redirecting you right now...": "Thank you for accepting the invitation. We are redirecting you right now...", + "Invalid invitation token. Please try again.": "Invalid invitation token. Please try again.", + "Role updated successfully": "Role updated successfully", + "Error updating role": "Error updating role", + "Please try again later": "Please try again later", + "Edit Role for": "Edit Role for", + "Select Role": "Select Role", + "Avatar": "Avatar", + "Remove {email}": "Remove {email}", + "Are you sure you want to remove this invitation?": "Are you sure you want to remove this invitation?", + "Please select invitation type": "Please select invitation type", + "Please select platform role": "Please select platform role", + "Invitation sent successfully": "Invitation sent successfully", + "Please select a project role": "Please select a project role", + "Invitation link copied successfully": "Invitation link copied successfully", + "Invite User": "Invite User", + "Invitation Link": "Invitation Link", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Type the email address of the user you want to invite, the invitation expires in 24 hours.", + "Invite To": "Invite To", + "Entire Platform": "Entire Platform", + "Select Project Role": "Select Project Role", + "Invite": "Invite", + "Platform Role": "Platform Role", + "Select a platform role": "Select a platform role", + "Are you sure you want to remove this member?": "Are you sure you want to remove this member?", + "Editor": "Editor", + "Operator": "Operator", + "Viewer": "Viewer", + "Select a project role": "Select a project role", + "Steps in this flow": "Steps in this flow", + "Invalid Access": "Invalid Access", + "Either the project does not exist or you do not have access to it.": "Either the project does not exist or you do not have access to it." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/nl/translation.json b/packages/react-ui/public/locales/nl/translation.json new file mode 100644 index 0000000..8f1d555 --- /dev/null +++ b/packages/react-ui/public/locales/nl/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Publiceer", + "Latest version is published": "Laatste versie is gepubliceerd", + "Your flow has incomplete steps": "Je stroom heeft onvolledige stappen", + "Edit Flow": "Flow bewerken", + "View Draft": "Concept bekijken", + "Uncategorized": "Geen categorie", + "Go to folder": "Ga naar map", + "Support": "Ondersteuning", + "Runs": "Uitvoeren", + "Run Logs": "Logbestanden", + "Versions": "versies", + "Versions History": "Versies geschiedenis", + "Error generating code": "Fout bij genereren code", + "AI Copilot": "AI Kooilot", + "i.e Calculate the sum of a list...": "d.w.z. de som van een lijst berekenen...", + "Send": "Verzenden", + "Generating Code": "Genereren code", + "Hello there! I am here to generate code that helps with your flow": "Hallo daar! Ik ben hier om code te genereren die helpt met je stroom", + "Here are examples of what I am best used for: ": "Hier zijn voorbeelden van waar ik het best voor kan gebruiken: ", + "Text Processing": "Tekst verwerking", + "Process strings, dates and data": "Verwerk tekenreeksen, datums en gegevens", + "Data Operations": "Data Bewerkingen", + "Change data from one format to another": "Wijzig gegevens van het ene formaat naar het andere", + "Calculations": "Berekeningen", + "Handle math and statistics": "Afhandelen van wiskunde en statistieken", + "API Integration": "Integratie met API", + "Connect with external services. Best for simple integrations currently.": "Maak verbinding met externe diensten. Het beste voor eenvoudige integraties op dit moment.", + "What would you like me to help you with?": "Waar wil je dat ik je mee helpen?", + "Insert": "Invoegen", + "Data Selector": "Data Selectiemenu", + "Search": "Zoeken", + "No matching data": "Geen overeenkomende gegevens", + "Try adjusting your search": "Probeer je zoekopdracht aan te passen", + "This trigger needs to have data loaded from your account, to use as sample data.": "Deze trigger moet gegevens geladen hebben van uw account, om te gebruiken als voorbeelddata.", + "This step needs to be tested in order to view its data.": "Deze stap moet worden getest om de gegevens ervan te bekijken.", + "Go to Trigger": "Ga naar trigger", + "Go to Step": "Ga naar stap", + "Select Mode": "Selecteer modus", + "Move Mode": "Modus verplaatsen", + "Reset Zoom": "Reset Zoom", + "Zoom In": "Zoom in", + "Zoom Out": "Uitzoomen", + "Fit to View": "Aanpassen aan weergave", + "Replace": "Vervangen", + "Copy": "Kopiëer", + "Duplicate": "Dupliceer", + "Paste After Last Step": "Plakken na laatste stap", + "Paste Inside Loop": "Plak binnenste lus", + "Paste After": "Plakken na", + "Paste Inside...": "Plak binnen...", + "New Branch": "Nieuwe branch", + "Paste Inside Branch": "Plak binnen branche", + "Delete": "Verwijderen", + "Duplicate Branch": "Dupliceer branch", + "Delete Branch": "Aftakking verwijderen", + "Invalid Move": "Ongeldige Actie", + "The destination location is a child of the dragged step": "De bestemmingslocatie is een kind van de geslepen stap", + "End": "Einde", + "Skipped": "Overgeslagen", + "Incomplete settings": "Instellingen onvolledig", + "logo": "logo", + "Step Icon": "Stap icoon", + "Branch": "Filiaal", + "incompleteSteps": "{invalidSteps, plural, one {}=0 {Geen onvolledige stappen} =1 {Voltooi 1 stap} other {Voltooi # stappen}}", + "Test Flow": "Test flow", + "Please test the trigger first": "Test eerst de trigger", + "View Only": "Alleen weergeven", + "Use as Draft": "Gebruiken als concept", + "Are you sure?": "Weet je het zeker?", + "Your current draft version will be overwritten with": "Uw huidige conceptversie zal worden overschreven door", + "version #": "versie #", + "Cancel": "Annuleren", + "Confirm": "Bevestig", + "Version": "Versie", + "Viewing": "Bekijkt", + "View": "Weergave", + "Version History": "Versiegeschiedenis", + "Error, please try again.": "Fout, probeer het opnieuw.", + "Continue on Failure": "Ga door bij fout(en)", + "Enable this option to skip this step and continue the flow normally if it fails.": "Schakel deze optie in om deze stap over te slaan en door te gaan met de flow als deze mislukt.", + "Retry on Failure": "Opnieuw proberen bij fout", + "Automatically retry up to four attempts when failed.": "Automatisch opnieuw proberen tot vier pogingen wanneer mislukt.", + "Remove": "Verwijderen", + "Add Item": "Item toevoegen", + "File Input": "Bestand Invoer", + "Date Input": "Date Input", + "Dynamic value": "Dynamische waarde", + "Select an option": "Selecteer een optie", + "Unexpected error, please retry": "Onverwachte fout, probeer opnieuw", + "Unexpected error, please refresh the page or contact support": "Onverwachte fout, ververs de pagina of neem contact op met support", + "Name can only contain letters, numbers and underscores": "De naam mag alleen letters, cijfers en onderstrepingstekens bevatten", + "Ask AI": "Vraag het AI", + "Create Todo Guide": "Todo gids aanmaken", + "Where would you like the todo to be reviewed?": "Waar wilt u dat de todo wordt herzien?", + "Activepieces Todos": "Todo actieve epieces", + "Users will manage tasks directly in Activepieces": "Gebruikers beheren taken direct in Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Gebruikers beheren en reageren op todos rechtstreeks binnen de Activepieces interface. Ideaal voor interne teams.", + "External Channel (Slack, Teams, Email, ...)": "Extern kanaal (Slack, Teams, E-mail, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Stuur meldingen met goedkeuringslinks via externe kanalen zoals Slack, Teams of E-mail. Het beste is om samen te werken met externe stakeholders.", + "Preview (Activepieces Todos)": "Voorbeeld (Activepieces todos)", + "Preview (External channel)": "Voorbeeld (eeuwig kanaal)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "Met de Activepieces Todo kunnen gebruikers taken direct in de Activepieces interface bekijken en oplossen", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "Je kunt het kanaal toevoegen voor de Wait Stap, en de logica configureren in de Router stap", + "Add Steps": "Stappen toevoegen", + "All": "Allemaal", + "AI": "AI", + "Core": "Kern", + "Apps": "Applicaties", + "Not available as trigger": "Niet beschikbaar als trigger", + "Not available as action": "Niet beschikbaar als actie", + "Let our AI assistant help you out": "Laat onze AI-assistent je helpen", + "Or": "OF", + "Request Piece": "Stuk aanvragen", + "No pieces found": "Geen pieces gevonden", + "Please select a piece first": "Selecteer eerst een stuk", + "All Iterations": "Alle iteraties", + "Duration": "Tijdsduur", + "Input": "Input", + "Output": "Uitvoer", + "There are no logs captured for this run.": "Er zijn geen logs vastgelegd voor deze run.", + "Logs are kept for {days} days after execution and then deleted.": "Logboeken worden {days} dagen na uitvoering bewaard en daarna verwijderd.", + "Run Details": "Uitvoeren details", + "Iteration": "Herhalingen (iteratie)", + "Done": "Voltooid", + "Took": "Nam", + "Running": "Lopend", + "on latest version": "op de laatste versie", + "from failed step": "van mislukte stap", + "Recent Runs": "Recente runs", + "No runs found": "Geen resultaten gevonden", + "Close": "Sluiten", + "OR": "OF", + "And If": "En als", + "+ And": "+ En", + "+ Or": "+ Of", + "(Text) Contains": "(Tekst) Bevat", + "(Text) Does not contain": "(Tekst) Bevat niet", + "(Text) Exactly matches": "(Tekst) Matched exact", + "(Text) Does not exactly match": "(Tekst) Matched niet exact", + "(Text) Starts with": "(Tekst) Begint met", + "(Text) Does not start with": "(Volgende) Begint niet met", + "(Text) Ends with": "(Tekst) Eindigt met", + "(Text) Does not end with": "(Tekst) Eindigt niet met", + "(List) Contains": "(List) Bevat", + "(List) Does not contain": "(List) Bevat geen", + "(Number) Is greater than": "(Getal) Is groter dan", + "(Number) Is less than": "(Getal) Is kleiner dan", + "(Number) Is equal to": "(Getal) Is gelijk aan", + "(Date/time) After": "(Datum/tijd) Na", + "(Date/time) Before": "(Datum/tijd) Voor", + "(Date/time) Equals": "(Datum/tijd) gelijk aan", + "(Boolean) Is true": "(Boolean) Is waar", + "(Boolean) Is false": "(Boolean) Is niet waar", + "(List) Is empty": "(List) Is leeg", + "(List) Is not empty": "(List) Is niet leeg", + "Exists": "Bestaat", + "Does not exist": "Bestaat niet", + "Incomplete condition": "Onvolledige voorwaarde", + "First value": "Eerste waarde", + "Second value": "Tweede waarde", + "Case sensitive": "Hoofdlettergevoelig", + "Execute If": "Uitvoeren als", + "The package name is required": "De package name is vereist", + "Success": "Gelukt", + "Package added successfully": "Package succesvol toegevoegd", + "Could not fetch package version": "Kan de package versie niet ophalen", + "Add NPM Package": "NPM package toevoegen", + "Type the name of the npm package you want to add.": "Typ de naam van het NPM package dat je wilt toevoegen.", + "Package Name": "Package Naam", + "The latest version will be fetched and added": "De laatste versie zal worden opgehaald en toegevoegd", + "Add": "Toevoegen", + "Code": "Code", + "Dependencies": "Afhankelijkheden", + "Use code": "Gebruik code", + "Add package": "Package toevoegen", + "Inputs": "Invoer", + "Edit Step Name": "Bewerk stapnaam", + "Edit Branch Name": "Aftakkenaam bewerken", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Selecteer de items om te herhalen uit de vorige stap door te klikken op de **Items** invoer, die een **lijst** van items moet zijn.\n\nDe loop zal over elk item in de lijst herhalen en de volgende stap voor elk item uitvoeren.", + "Items": "Artikelen", + "Select an array of items": "Selecteer een reeks van items", + "Reconnect": "Opnieuw verbinden", + "Select a connection": "Selecteer een koppeling", + "Create Connection": "Maak een Koppeling", + "Rename": "Hernoemen", + "Move": "Verplaatsen", + "Add Branch": "Aftakking toevoegen", + "Execute": "Uitvoeren", + "Only the first (left) matching branch": "Alleen de eerste (links) overeenkomende branch", + "All matching paths from left to right": "Alle overeenkomende paden van links naar rechts", + "Branches": "Filialen", + "{field} is required": "{field} is vereist", + "Tool Sample Data": "Tool voorbeelddata", + "Fill in the following fields to use them as sample data for the trigger.": "Vul de volgende velden in om ze te gebruiken als voorbeeldgegevens voor de trigger.", + "No input fields defined in the schema": "Geen invoervelden gedefinieerd in het schema", + "Save": "Opslaan", + "Test Environment": "Test Omgeving", + "Assigned to": "Toegewezen aan", + "(Me)": "(mij)", + "Please select status to resolve the todo": "Selecteer een status om de todo op te lossen", + "Resolve": "Oplossen", + "Change status to resolved": "Status te wijzigen naar opgelost", + "Send Sample Data to Webhook": "Stuur voorbeelddata naar de Webhook", + "Method": "Methode", + "Query Params": "Query parameters", + "Headers": "Kopteksten", + "Body": "Lichaam", + "Type": "Type", + "JSON": "JSON", + "Text": "Tekstveld", + "Form Data": "Formulieren gegevens", + "Generate Sample Data": "Voorbeeldgegevens genereren", + "Test Step": "Test stap", + "Retest": "Opnieuw testen", + "Testing Failed": "Test mislukt", + "Tested Successfully": "Test Succesvol", + "Logs": "Logboeken", + "There is no sample data available found for this trigger.": "Er zijn geen voorbeeldgegevens aanwezig voor deze trigger.", + "Internal error, please try again later.": "Interne fout, probeer het later opnieuw.", + "Failed to run test step and no error message was returned": "Test stap mislukt en er is geen foutbericht teruggestuurd", + "Please fix inputs first": "Corrigeer eerst de invoer", + "No sample data available": "Geen voorbeeldgegevens beschikbaar", + "Old results were removed, retest for new sample data": "Oude resultaten zijn verwijderd, test opnieuw voor nieuwe voorbeeldgegevens", + "Result #": "Resultaat #", + "The sample data can be used in the next steps.": "Deze voorbeeldgegevens kunnen worden gebruikt in de volgende stappen.", + "Testing Trigger": "Testen trigger", + "Action Required": "Actie vereist", + "testPieceWebhookTriggerNote": "Ga naar {pieceName} en trigger {triggerName}.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Stuur data naar de webhook URL om voorbeeldgegevens te genereren in de volgende stappen", + "Test Trigger": "Test Trigger", + "Use Mock Data": "Gebruik nepgegevens", + "Load Sample Data": "Voorbeeldgegevens laden", + "Test Tool": "Test Gereedschap", + "home": "huis", + "Home": "Startpagina", + "Alerts": "Waarschuwingen", + "Releases": "Releases", + "Flows": "Stromen", + "Products": "Product(en)", + "MCP": "MCP", + "Tables": "Tabellen", + "Todos": "Todos", + "Push to Git": "Push naar Git", + "Move To": "Verplaats Naar", + "Duplicating": "Dupliceren", + "Import": "Importeer", + "Exporting": "Bezig met exporteren", + "Export": "Exporteer", + "Share": "Delen", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Weet je zeker dat je deze flow wilt verwijderen? Dit zal de flow, alle gegevens en achtergrondgegevens permanent verwijderen.", + "You are on a development branch, this will also delete the flow from the remote repository.": "U bevindt zich in een ontwikkelingsbranch, dit verwijdert ook de stroom uit de externe repository.", + "flow": "stroom", + "Community Support": "Community ondersteuning", + "Overview": "Overzicht.", + "Projects": "Projecten", + "Users": "Gebruikers", + "Setup": "Instellen", + "Branding": "Merk", + "Global Connections": "Globale verbindingen", + "Pieces": "Stuks", + "Templates": "Sjablonen", + "License Key": "Licentie sleutel", + "Security": "Beveiliging", + "Audit Logs": "Auditlogboek", + "Single Sign On": "Single Sign On", + "Signing Keys": "Sleutels voor ondertekening", + "Project Roles": "Project rollen", + "API Keys": "API sleutels", + "Infrastructure": "Infrastructuur", + "Workers": "Werkers", + "Health": "Gezondheid", + "Billing": "Facturering", + "Settings": "Instellingen", + "Contact Sales": "Contact met Verkoop", + "General": "Algemeen", + "Appearance": "Weergave", + "Team": "Team", + "Environments": "Omgevingen", + "Project Settings": "Project Instellingen", + "Exit Platform Admin": "Verlaat Platform Admin", + "Enter Platform Admin": "Voer Platform Admin in", + "Logout": "Afmelden", + "Misc": "Diversen", + "Connections": "Koppelingen", + "days": "Dagen", + "hours": "Uren", + "minutes": "minuten", + "Today": "vandaag", + "Tasks": "Taken", + "AI Credits": "AI Credits", + "Usage resets in": "Opnieuw instellen gebruik in", + "Manage": "Beheren", + "Unlimited": "Onbeperkt", + "Enabled": "Ingeschakeld", + "Disabled": "Uitgeschakeld", + "Could not claim the authorization code, make sure you have correct settings and try again.": "Kan de autorisatiecode niet claimen, zorg ervoor dat u de juiste instellingen heeft en probeer het opnieuw.", + "Connection failed with error {msg}": "Verbinding mislukt met foutmelding {msg}", + "You don't have the permission to create a connection.": "Je hebt geen toestemming om een verbinding te maken.", + "Reconnect {displayName} Connection": "{displayName} opnieuw verbinden", + "Connect to {displayName}": "Verbinden met {displayName}", + "Connection Name": "Naam van de koppeling", + "Connection name": "Naam van de koppeling", + "New Connection": "Nieuwe Koppeling", + "Redirect URL": "Omleidings URL", + "Client ID": "Klant ID", + "Client Secret": "Client Secret", + "Connect": "Verbinden", + "Disconnect": "Verbreken", + "I would like to use my own App Credentials": "Ik wil graag mijn eigen app inloggegevens gebruiken", + "I would like to use predefined App Credentials": "Ik wil graag vooraf gedefinieerde app referenties gebruiken", + "Permission needed": "Toestemming vereist", + "Connections replaced successfully": "Connecties succesvol vervangen", + "Error": "Foutmelding", + "Failed to replace connections": "Verbindingen vervangen mislukt", + "Failed to get affected flows": "Betrokken stromen ophalen mislukt", + "Please select a piece": "Selecteer een stuk", + "Please select a connection to replace": "Selecteer een verbinding om te vervangen", + "Please select a connection to replace with": "Selecteer een verbinding om mee te vervangen", + "Replace Connections": "Vervang verbindingen", + "Confirm Replacement": "Vervanging bevestigen", + "Replace one connection with another.": "Vervang één verbinding met een andere.", + "This action requires ": "Deze actie vereist ", + "reconnecting": "opnieuw verbinden", + " any associated MCP pieces.": " alle bijbehorende MCP-stukken.", + "Piece": "Stuk", + "Select a piece": "Selecteer een stuk", + "Connection to Replace": "Verbinding om te vervangen", + "Choose connection to replace": "Kies te vervangen verbinding", + "Replaced With": "Vervangen door", + "Choose connection to replace with": "Kies een verbinding om te vervangen door", + "All flows will be changed to use the replaced with connection": "Alle flows zullen worden gewijzigd om de vervangen met verbinding te gebruiken", + "Next": "Volgende", + "No flows will be affected by this change": "Geen stromen zullen worden beïnvloed door deze verandering", + "Back": "Achterzijde", + "Unnamed tool": "Naamloos gereedschap", + "This flow is enabled": "Deze flow is ingeschakeld", + "Enable this flow to make it available": "Schakel deze flow in om deze beschikbaar te maken", + "Piece is updated successfully": "Stuk is succesvol bijgewerkt", + "Piece is added successfully": "Stuk is succesvol toegevoegd", + "Failed to update piece": "Update item is mislukt", + "Failed to add piece": "Element toevoegen mislukt", + "Please select a connection": "Selecteer een verbinding", + "Your MCP server already has this piece": "Uw MCP-server heeft dit stuk al", + "+ New Connection": "+ Nieuwe Verbinding", + "Edit Piece": "Bewerk stukken", + "Add Piece": "Stuk toevoegen", + "Connection": "Koppeling", + "MCP piece": "MCP stuk", + "Failed to update piece status": "Bijwerken van stukstatus mislukt", + "Connection required": "Verbinding vereist", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Weet u zeker dat u deze tool wilt verwijderen uit uw MCP? Als u het verwijdert, kunt u het niet gebruiken in uw MCP-client.", + "piece": "stuk", + "Connect your AI assistant to external services": "Verbind uw AI-assistent met externe diensten", + "Collapse": "Samenvouwen", + "Show All": "Alles weergeven", + "Server URL": "Server URL", + "Hide the token for security": "Verberg het token voor veiligheid", + "Show the token": "Toon de token", + "Generate a new token for security. This will invalidate the current URL.": "Genereer een nieuw token voor beveiliging. Dit zal de huidige URL ongeldig maken.", + "URL copied to clipboard": "URL gekopieerd naar klembord", + "Copy URL to clipboard": "Kopieer URL naar klembord", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "Deze URL bevat een gevoelige beveiligingstoken. Deel deze alleen met vertrouwde applicaties en diensten. Draai het token als u vermoedt dat het is aangetast.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "Na wijzigingen in verbindingen of flows, moet u uw MCP-server opnieuw koppelen om de wijzigingen van kracht te laten worden.", + "Max tables reached": "Max. tabellen bereikt", + "You can't create more than {maxTables} tables": "U kunt niet meer dan {maxTables} tabellen maken", + "Name": "Naam", + "Created": "Aangemaakt", + "Delete Tables": "Verwijder tabellen", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Weet u zeker dat u de geselecteerde tabellen wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.", + "table": "Tabel", + "Create and manage your tables to store your automation data": "Maak en beheer je tabellen om automatiseringsgegevens op te slaan", + "New Table": "Nieuwe tafel", + "No tables have been created yet": "Er zijn nog geen tabellen aangemaakt", + "Create a table to get started and start managing your automation data": "Maak een tabel aan om te beginnen met het beheren van uw automatiseringsgegevens", + "Error deleting connections": "Fout bij verwijderen contacten", + "Status": "status", + "Display Name": "Weergavenaam", + "Owner": "Eigenaar", + "App": "App", + "This connection is global and can be managed in the platform admin": "Deze verbinding is globaal en kan worden beheerd in de platformbeheerder", + "External ID": "Extern ID", + "Connected At": "Verbonden op", + "Confirm Deletion": "Verwijdering bevestigen", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Weet u zeker dat u de geselecteerde verbindingen wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.", + "Deleting connections may cause your Flows or MCP tools to break.": "Het verwijderen van verbindingen kan ervoor zorgen dat uw Stroom- of MCP-gereedschappen breken.", + "Manage project connections to external systems.": "Beheer projectverbindingen naar externe systemen.", + "No connections found": "Geen verbindingen gevonden", + "Come back later when you create a automation to manage your connections": "Kom later terug wanneer je een automatisering aanmaakt om je verbindingen te beheren", + "Steps": "Stappen", + "Folder": "Map", + "Flow name": "Flow naam", + "No flows found": "Geen stromen gevonden", + "Create a workflow to start automating": "Maak een workflow aan om te beginnen met automatiseren", + "Create and manage your flows, run history and run issues": "Maak en beheer uw stroom, voer geschiedenis uit en voer problemen uit", + "Issues": "Kwesties", + "Untitled": "Ongetiteld", + "Create flow": "Flow aanmaken", + "From scratch": "Zonder template", + "Use a template": "Gebruik een template", + "From local file": "Van lokaal bestand", + "Flow Name": "Flow naam", + "Count": "Aantal", + "First Seen": "Voor het eerst gezien op", + "Last Seen": "Laatst Gezien", + "Issues in {flowDisplayName} is marked as resolved.": "Problemen in {flowDisplayName} zijn gemarkeerd als opgelost.", + "Unlock Issues": "Ontgrendel problemen", + "Track issues in your workflows and troubleshoot them.": "Volg problemen in je werkstromen en maak er problemen mee.", + "No issues found": "Geen problemen gevonden", + "All your workflows are running smoothly.": "Al je workflows werken soepel.", + "Mark as Resolved": "Markeren als opgelost", + "All Flows Are Turned Off": "Alle waterstromen zijn uitgeschakeld", + "Task Usage Exceeded": "Taakgebruik overschreden", + "of the Allowed Limit.": "van de toegestane limiet.", + "When a project tasks limit is reached,": "Wanneer een project taak limiet is bereikt,", + "all flows will be turned off and you will not be able to run any flows.": "alle stromen zullen worden uitgeschakeld en je zal geen stromen kunnen runnen.", + "Please visit": "Bezoek a.u.b.", + "Your Plan": "Uw abonnement", + "and increase your task limit, which requires your payment details.": "en verhoog je taaklimiet, waarvoor je betalingsgegevens nodig zijn.", + "Please contact your admin to increase the project task limit.": "Neem contact op met uw beheerder om de projecttaaklimiet te verhogen.", + "and increase the project task limit.": "en de limiet van de project taak verhogen.", + "Dismiss": "Uitschakelen", + "Token rotated successfully": "Token met succes geroteerd", + "Failed to rotate token": "Token roteren mislukt", + "Piece removed successfully": "Stuk succesvol verwijderd", + "Failed to remove piece": "Item verwijderen mislukt", + "Flow created successfully": "Flow succesvol aangemaakt", + "Failed to create flow": "Fout bij het maken van een flow", + "Add Flow": "Voeg Flow toe", + "Let your AI assistant trigger automations": "Laat jouw AI assistent automaties activeren", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Maak verbinding met uw gehoste MCP-Server met behulp van een MCP-client om te communiceren met tools", + "MCP Server": "MCP Server", + "My Tools": "Mijn gereedschappen", + "Create Flow": "Maak Stroom", + "Note": "Notitie", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "Als u uw MCP-server aan het internet wilt blootstellen stel de AP_FRONTEND_URL omgevingsvariabele in op de openbare URL van uw Activepiëles instantie.", + "This URL grants access to your tools and data. Only share with trusted applications.": "Deze URL verleent toegang tot uw tools en gegevens. Deel alleen met vertrouwde applicaties.", + "Server Configuration": "Server Configuratie", + "Hide sensitive data": "Gevoelige gegevens verbergen", + "Show sensitive data": "Gevoelige gegevens weergeven", + "Create a new URL. The current one will stop working.": "Maak een nieuwe URL aan. De huidige zal niet meer werken.", + "Copy configuration": "Kopieer configuratie", + "Configuration copied to clipboard": "Configuratie gekopieerd naar klembord", + "Claude": "Claude", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "Server/Overig", + "Note: MCPs only work with": "Opmerking: MCPs werken alleen met", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", niet de webversie.", + "Prerequisites:": "Vereisten:", + "Install": "Installeren", + "Node.js": "Node.js", + "and": "en", + "Open Settings:": "Open instellingen:", + "Click the menu and select": "Klik op het menu en selecteer", + "Developer": "Ontwikkelaar", + "Configure MCP:": "MCP:", + "Click": "Click", + "Edit Config": "Configuratie bewerken", + "and paste the configuration below": "en plak de configuratie hieronder", + "Save and Restart:": "Opslaan en herstarten:", + "Save the config and restart Claude Desktop": "Sla de configuratie op en start Claude Desktop opnieuw op", + "Navigate to": "Navigeer naar", + "Cursor Settings": "Cursor instellingen", + "Add Server:": "Server toevoegen:", + "Add new global MCP server": "Nieuwe globale MCP-server toevoegen", + "Configure:": "Instellingen:", + "Paste the configuration below and save": "De configuratie hieronder plakken en opslaan", + "Use either method:": "Gebruik een van beide methoden:", + "Go to": "Ga naar", + "Advanced Settings": "Geavanceerde instellingen", + "Open Command Palette and select": "Open het opdrachtpalet en selecteer", + "Windsurf Settings Page": "Windsurf instellingen pagina", + "Navigate to Cascade:": "Navigeer naar Cascade:", + "Select": "Selecteren", + "Cascade": "Cascade", + "in the sidebar": "in de zijbalk", + "Add Server": "Server toevoegen", + "Add custom server +": "Aangepaste server toevoegen +", + "Copy URL": "URL kopiëren", + "Client Setup Instructions": "Client installatie-instructies", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "Na het veranderen van verbindingen of stromen, koppel uw MCP-server opnieuw om wijzigingen van kracht te laten worden.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Volg deze stappen om MCP in te stellen in uw gewenste client. Hiermee kunt uw AI-assistent toegang krijgen tot uw hulpmiddelen.", + "icon": "pictogram", + "Unlock Analytics": "Ontgrendel Analytics", + "Get insights into your platform usage and performance with our analytics dashboard": "Krijg inzicht in uw platformgebruik en prestaties met ons analytics dashboard", + "Active Flows": "Actieve Stromen", + "The number of enabled flows in the platform": "Het aantal ingeschakelde stromen in het platform", + "Active Projects": "Actieve projecten", + "The number of projects with at least one enabled flow": "Het aantal projecten met ten minste één ingeschakeld stroom", + "Active Users": "Actieve gebruikers", + "The number of users logged in the last 30 days": "Het aantal ingelogde gebruikers van de laatste 30 dagen", + "Out of {totalusers} total users": "Van {totalusers} gebruikers in totaal", + "Pieces Used": "Gebruikte stukjes", + "The number of unique pieces used across all flows": "Het aantal unieke stuks dat gebruikt wordt over alle stromen", + "Flows with AI": "Stroomt met AI", + "The number of enabled flows that use AI pieces": "Het aantal ingeschakelde stromen die AI stukjes gebruiken", + "Metrics": "Statistieken", + "Executed Tasks": "Uitgevoerde taken", + "Showing total executed tasks for specified time range": "Totaal uit te voeren taken voor opgegeven tijdbereik worden weergegeven", + "Tasks Usage Limit": "Taken Gebruikslimiet", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Specificeer een maandelijkse limiet voor taken om overmatig gebruik te voorkomen. Uw stromen zullen niet langer worden uitgevoerd als deze limiet is bereikt.", + "Number of monthly tasks": "Aantal maandelijkse taken", + "Save changes": "Wijzigingen opslaan", + "Limits updated successfully": "Limieten met succes bijgewerkt", + "Failed to update limits": "Bijwerken van limieten mislukt", + "Failed to load billing information": "Niet gelukt om factuurgegevens te laden", + "Billing Amount": "Facturering bedrag", + "Manage Payment Details": "Betalingsgegevens beheren", + "Add Payment Details": "Voeg Betalingsgegevens toe", + "Current Task Usage": "Huidig gebruik taak", + "Count of executed steps": "Aantal uitgevoerde stappen", + "Billing Limit": "Facturering limiet", + "Edit": "Bewerken", + "Add Limit": "Limiet toevoegen", + "Current Credit Usage": "Huidig kredietgebruik", + "WebSocket Connection": "WebSocket verbinding", + "Connected": "Verbonden", + "Disconnected": "Losgekoppeld", + "No issues detected": "Geen problemen gedetecteerd", + "Check the status of your platform and its components": "Controleer de status van je platform en de bijbehorende componenten", + "System Health Status": "Systeem gezondheidsstatus", + "All systems are running smoothly": "Alle systemen draaien soepel", + "Check the health of your worker machines": "Controleer de gezondheid van uw werknemers machines", + "Workers Machine": "Arbeiders Machine", + "This is demo data. In a real environment, this would show your actual worker machines.": "Dit zijn demogegevens. In een echte omgeving zou dit uw arbeidersmachines tonen.", + "No workers found": "Geen werknemers gevonden", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "U hebt nog geen werktuigmachines. Gebruik nieuwe machines om uw automaties uit te voeren", + "IP Address": "IP adres", + "CPU Usage": "CPU gebruik", + "Disk Usage": "Schijf gebruik", + "RAM Usage": "RAM Usage", + "Last Contact": "Laatste contactpersoon", + "Configs": "Configuraties", + "Environment Variables": "Omgeving variabelen", + "Websocket Connection Error": "Websocket verbindingsfout", + "Retry Connection": "Verbinding opnieuw proberen", + "Update Available": "Update beschikbaar", + "Update Now": "Nu bijwerken", + "Your Universal AI needs a quick setup": "Uw Universal AI heeft een snelle instelling nodig", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "We hebben gemerkt dat je nog geen AI providers hebt ingesteld. Om Universal AI stukjes voor uw team te ontgrendelen, moet u eerst enkele providergegevens configureren.", + "Configure": "Configureren", + "Platform Alerts": "Platform waarschuwingen", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "Er zijn belangrijke platformwaarschuwingen die uw aandacht vereisen. Controleer de waarschuwingssectie in de Platform Admin.", + "View Alerts": "Bekijk waarschuwingen", + "Used Tasks": "Gebruikte taken", + "Used AI Credits": "Gebruikte AI Credits", + "Cannot delete active project, switch to another project first": "Kan het actieve project niet verwijderen, eerst overschakelen naar een ander project", + "Delete Projects": "Verwijder projecten", + "Are you sure you want to delete the selected projects?": "Weet u zeker dat u de geselecteerde projecten wilt verwijderen?", + "New Project": "Nieuw project", + "Validation error": "Validatie fout", + "Project has enabled flows. Please disable them first.": "Project heeft flows ingeschakeld. Gelieve deze eerst uit te schakelen.", + "This project is active. Please switch to another project first.": "Dit project is actief. Schakel eerst over naar een ander project.", + "Unlock Projects": "Ontgrendelen van projecten", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Orchestrate je automatiseringsteams in verschillende projecten met hun eigen stromen, verbindingen en gebruikquota", + "Manage your automation projects": "Beheer je automatiseringsprojecten", + "No projects found": "Geen projecten gevonden", + "Start by creating projects to manage your automation teams": "Begin met het maken van projecten om uw automatiseringsteams te beheren", + "Edit project": "Bewerk project", + "Name is required": "Naam is vereist", + "Create New Project": "Nieuw project aanmaken", + "Project Name": "Projectnaam", + "Id": "Id", + "Enable API Keys": "API-sleutels inschakelen", + "Create and manage API keys to access Activepieces APIs.": "Maak en beheer API-sleutels voor toegang tot de Activepieces API's.", + "New Api Key": "Nieuwe Api-sleutel", + "No API keys found": "Geen API sleutels gevonden", + "Start by creating an API key to communicate with Activepieces APIs": "Begin met het maken van een API-sleutel om te communiceren met de Activepieces API's", + "Delete API Key": "API-sleutel verwijderen", + "Are you sure you want to delete this API key?": "Weet u zeker dat u deze API-sleutel wilt verwijderen?", + "API Key": "API Sleutel", + "API Key Created": "API-sleutel aangemaakt", + "Create New API Key": "Nieuwe API-sleutel maken", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Sla deze geheime sleutel ergens veilig en toegankelijk op. Om veiligheidsredenen", + "you won't be able to view it again after closing this dialog.": "je zal het niet opnieuw kunnen bekijken nadat je dit dialoogvenster hebt gesloten.", + "API Key Name": "API Key Naam", + "Action": "actie", + "Performed By": "Uitgevoerd door", + "Project": "Project", + "Unlock Audit Logs": "Ontgrendel Audit Logs", + "Comply with internal and external security policies by tracking activities done within your account": "Voldoen met intern en extern beveiligingsbeleid door activiteiten die binnen uw account worden uitgevoerd te traceren", + "Track activities done within your platform": "Volg activiteiten uitgevoerd op uw platform", + "No audit logs found": "Geen auditlogboeken gevonden", + "Come back later when you have some activity to audit": "Kom later terug als u wat activiteit heeft om te controleren", + "Resource": "Bron", + "Details": "Beschrijving", + "N/A": "N/B", + "Flow Run": "Flow Run", + "Flow": "Stroom", + "User": "Gebruiker", + "Signing Key": "Onderteken Sleutel", + "Project Role Management": "Project Rol Beheer", + "Define custom roles and permissions to control what your team members can access and modify": "Definieer aangepaste rollen en permissies om te bepalen welke toegang uw teamleden hebben tot en wijzigingen", + "Define custom roles and permissions that can be assigned to your team members": "Definieer aangepaste rollen en permissies die aan uw teamleden kunnen worden toegewezen", + "New Role": "Nieuwe rol", + "Contact sales to unlock custom roles": "Neem contact op met verkoop om aangepaste rollen te ontgrendelen", + "Create New Role": "Nieuwe rol maken", + "View ": "Bekijken ", + "Edit ": "Bewerken ", + "Role Name": "Rol naam", + "Permissions": "Machtigingen", + "None": "geen", + "Read": "Lezen", + "Write": "Schrijven", + "Create": "Maak", + "Email": "E-mail", + "First Name": "Voornaam", + "Last Name": "Achternaam", + "Roles": "Rollen", + "View the users assigned to this role": "Bekijk de gebruikers die zijn toegewezen aan deze rol", + "Role": "Functie", + "No users found": "Geen gebruikers gevonden", + "Starting by assigning users to this role": "Begin met het toewijzen van gebruikers aan deze rol", + "Project Role entry deleted successfully": "Project Rol item succesvol verwijderd", + "Updated": "Gewijzigd", + "No project roles found": "Geen project rollen gevonden", + "Create custom project roles to manage permissions for platform users": "Maak aangepaste projectrollen om machtigingen voor platformgebruikers te beheren", + "Show Users": "Toon gebruikers", + "View Role": "Rol bekijken", + "Edit Role": "Rol bewerken", + "Delete Role": "Verwijder rol", + "Project Role": "Project rol", + "Unlock Embedding Through JS SDK": "Ontgrendel insluiten via JS SDK", + "Enable signing keys to access embedding functionalities.": "Sleutels tekenen om toegang te krijgen tot embedding functionaliteiten.", + "New Signing Key": "Nieuwe ondertekeningssleutel", + "No signing keys found": "Geen ondertekeningssleutels gevonden", + "Create a signing key to authenticate users with embedding": "Maak een ondertekensleutel om gebruikers te verifiëren met insluiten", + "Delete Signing Key": "Ondertekensleutel verwijderen", + "Are you sure you want to delete this signing key?": "Weet je zeker dat je deze ondertekeningssleutel wilt verwijderen?", + "Signing Key Created": "Ondertekensleutel aangemaakt", + "Create New Signing Key": "Maak een nieuwe ondertekeningssleutel", + "Signing Key Name": "Sleutelnaam Ondertekening", + "Allowed domains updated": "Toegestane domeinen bijgewerkt", + "Update": "Vernieuwen", + "Enable": "Inschakelen", + "Configure Allowed Domains": "Toegestane domeinen configureren", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Voer de toegestane domeinen in waarmee de gebruikers kunnen verifiëren, met een lege lijst zullen alle domeinen worden toegestaan.", + "Add Domain": "Domein toevoegen", + "Allow logins through {providerName}'s single sign-on functionality.": "Sta logins toe via {providerName}zijn enkele sign-on functionaliteit.", + "Email authentication updated": "E-mail authenticatie bijgewerkt", + "Enable Single Sign On": "Single Sign On inschakelen", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Laat uw gebruikers inloggen met uw huidige SSO provider of geef hen zelf-servup toegang", + "Allowed Domains": "Toegestane domeinen", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Sta gebruikers toe om te verifiëren met specifieke domeinen. Laat leeg om alle domeinen toe te staan.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Toegestane e-mail login", + "Allow logins through email and password.": "Sta logins via e-mail en wachtwoord toe.", + "Single sign on settings updated": "Instellingen voor eenmalige aanmelding bijgewerkt", + "Disable": "Uitschakelen", + "Configure {provider} SSO": "Configureer {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Lees meer informatie over het configureren van {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} Client-ID", + "{provider} Client Secret": "{provider} adverteerder geheim", + "Single sign-on settings updated": "Single sign-on instellingen bijgewerkt", + "Configure SAML 2.0 SSO": "SAML 2.0 SSO configureren", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDP Metadata", + "IDP Certificate": "IDP Certificaat", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "Basis URL", + "Resource Name": "Resource naam", + "Deployment Name": "Deployment Naam", + "Saving": "Besparing", + "Activepieces Copilot": "Activepieces Copilot", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot is geconfigureerd en klaar om uw gebruikers te helpen sneller stromen te bouwen met behulp van AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configureer Activepieces Copilot om uw gebruikers te helpen stromen sneller te bouwen met behulp van AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Stel uw AI providers & copilot instellingen in, zodat uw gebruikers genieten van een naadloze bouwervaring met onze universele AI stukken", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Stel de provider-referenties in die worden gebruikt door universele AI-stukken, d.w.z. tekst AI.", + "AI Providers": "AI Providers", + "Copilot": "Kopiër", + "Configure credentials for {providerName} AI provider.": "Configureer inloggegevens voor {providerName} AI provider.", + "Update AI Provider": "Update AI Provider", + "Enable AI Provider": "AI Provider inschakelen", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Voer een geldig domein in", + "Your changes have been saved.": "Uw wijzigingen zijn opgeslagen.", + "The domain is already added.": "Het domein is al toegevoegd.", + "Add Custom Domain": "Eigen domein toevoegen", + "Enter a domain name without a protocol (e.g. example.com)": "Voer een domeinnaam zonder protocol in (bijv. voorbeeld.com)", + "Logo URL": "Logo URL", + "Icon URL": "Icoon URL", + "Favicon URL": "Favicon URL", + "Default Language": "Standaard taal", + "Select Language": "Selecteer Taal", + "No Languages": "Geen Talen", + "Primary Color": "Primaire kleur", + "Custom Domains": "Eigen domeinen", + "No domains added yet.": "Nog geen domeinen toegevoegd.", + "Verified": "Geverifieerd", + "Pending, please contact the support for dns verification.": "In behandeling, neem contact op met ondersteuning voor dns verificatie.", + "Are you sure you want to delete {domain}?": "Weet u zeker dat u {domain} wilt verwijderen?", + "Brand Activepieces": "Merk Activepieces", + "Give your users an experience that looks like you by customizing the color, logo and more": "Geef je gebruikers een ervaring die lijkt op je door de kleur, logo en meer aan te passen", + "Configure the appearance and SMTP settings for your platform.": "Configureer de weergave en SMTP-instellingen voor uw platform.", + "Invalid host": "Ongeldige host", + "Invalid username": "Ongeldige gebruikersnaam", + "Invalid password": "Ongeldig wachtwoord", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Ongeldige afzendernaam", + "SMTP is configured": "SMTP is geconfigureerd", + "Mail Server": "E-mail server", + "Set up your SMTP settings to send emails from your domain.": "Stel uw SMTP-instellingen in om e-mails vanuit uw domein te verzenden.", + "Disable Mail Server": "Mailserver uitschakelen", + "Are you sure you want to disable your mail server?": "Weet u zeker dat u uw mailserver wilt uitschakelen?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "Dit zorgt ervoor dat u geen e-mails kunt verzenden voor kwesties, quota beperkingen, uitnodigingen en wachtwoord vergeten.", + "mail server": "mail server", + "Host": "Hostnaam", + "Port": "Poort", + "Username": "Gebruikersnaam", + "Password": "Wachtwoord", + "Sender Email": "E-mailadres afzender", + "Sender Name": "Naam afzender", + "Enable Global Connections": "Globale verbindingen inschakelen", + "Manage platform-wide connections to external systems.": "Beheer platformbrede verbindingen met externe systemen.", + "No global connections found": "Geen globale verbindingen gevonden", + "Create a global connection that can be shared to multiple projects": "Maak een globale verbinding die gedeeld kan worden met meerdere projecten", + "License key is invalid": "Licentiesleutel is ongeldig", + "Invalid license key": "Ongeldige licentiesleutel", + "License activated!": "Licentie geactiveerd!", + "Activate License Key": "Activeer licentiesleutel", + "Let the magic begin!": "Laat de magie beginnen!", + "Activate": "Activeren", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "Deze functie wordt nog niet zelf in de cloud gebruikt, neem contact op met sales@activepieces.com. ", + "This feature is not available in your current edition. ": "Deze functie is niet beschikbaar in je huidige editie. ", + "Learn how to upgrade": "Leer hoe te upgraden", + "Activate your platform and unlock enterprise features": "Activeer uw platform en ontgrendel bedrijfs-functies", + "Activate License": "Licentie activeren", + "Expiration": "Vervaldatum", + "Valid until": "Geldig tot", + "Expired": "Verlopen", + "Expires soon": "Verloopt binnenkort", + "Features": "Eigenschappen", + "Applying Tags...": "Labels toepassen...", + "Tags applied.": "Tags toegepast.", + "Tag created": "Tag aangemaakt", + "Tag": "Tagnaam", + "Tags": "Labels", + "Control Pieces": "Controle Stuks", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Toon de stukken die het belangrijkst zijn voor je gebruikers en verberg de stukken die je niet leuk vindt.", + "Manage the pieces that are available to your users": "Beheer de stukken die beschikbaar zijn voor je gebruikers", + "Start by installing pieces that you want to use in your automations": "Start met het installeren van stukken die je wilt gebruiken in je automatiseringen", + "Piece Name": "Piece naam", + "Hide this piece from all projects": "Dit stuk verbergen voor alle projecten", + "Show this piece for all projects": "Toon dit stuk voor alle projecten", + "Unpin this piece": "Dit stuk losmaken", + "Pin this piece": "Dit stuk vastmaken", + "Pieces synced": "Stuks gesynchroniseerd", + "Pieces have been synced from the activepieces cloud.": "Stukken zijn gesynchroniseerd vanuit de activepieces cloud.", + "OAuth2 Credentials Deleted": "OAuth2 aanmeldgegevens verwijderd", + "OAuth2 Credentials Updated": "OAuth2 Aanmeldgegevens bijgewerkt", + "Configure OAuth2 APP": "Configureer OAuth2 APP", + "Delete OAuth2 APP": "Verwijder OAuth2 APP", + "Templates deleted successfully": "Templates succesvol verwijderd", + "Delete Templates": "Sjablonen verwijderen", + "Are you sure you want to delete the selected templates?": "Weet u zeker dat u de geselecteerde sjablonen wilt verwijderen?", + "New Template": "Nieuwe sjabloon", + "Unlock Templates": "Sjablonen ontgrendelen", + "Convert the most common automations into reusable templates 1 click away from your users": "Zet de meest voorkomende automaties om in herbruikbare templates 1 klik weg van uw gebruikers", + "Convert the most common automations into reusable templates": "De meest voorkomende automaties omzetten in herbruikbare templates", + "No templates found": "Geen sjablonen gevonden", + "Create a template for your user to inspire them": "Maak een sjabloon voor uw gebruiker om hen te inspireren", + "Edit template": "Sjabloon bewerken", + "Template is required": "Template is verplicht", + "Update New Template": "Nieuwe sjabloon bijwerken", + "Create New Template": "Nieuwe sjabloon maken", + "Template Name": "Template naam", + "Description": "Beschrijving", + "Template Description": "Beschrijving sjabloon", + "Blog URL": "Blog URL", + "Template Blog URL": "Template Blog URL", + "Template": "Sjabloon", + "Invalid JSON": "Ongeldige JSON", + "User deleted successfully": "Gebruiker succesvol verwijderd", + "User activated successfully": "Gebruiker succesvol geactiveerd", + "User deactivated successfully": "Gebruiker met succes gedeactiveerd", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Beheer uw gebruikers en hun toegang tot uw projecten", + "Start inviting users to your project": "Nodig gebruikers uit voor je project", + "External Id": "Extern ID", + "Admin": "Beheerder", + "Member": "Lid", + "Activated": "Geactiveerd", + "Deactivated": "Gedeactiveerd", + "Edit user": "Gebruiker bewerken", + "Admin cannot be deactivated": "Beheerder kan niet gedeactiveerd worden", + "Deactivate user": "Gebruiker deactiveren", + "Activate user": "Gebruiker activeren", + "Delete User": "Gebruiker verwijderen", + "Are you sure you want to delete this user?": "Weet u zeker dat u deze gebruiker wilt verwijderen?", + "Delete user": "Gebruiker verwijderen", + "Update User Role": "Gebruikersrol bijwerken", + "Meeting Summary Flow": "Vergadering Samenvatting Flow", + "Added new features and fixed bugs": "Nieuwe functies en bugs toegevoegd", + "Flows Changes": "Veranderingen aanstouwen", + "Connections Changes": "Wijzigingen van verbindingen", + "New connections are placeholders and need to be reconnected again": "Nieuwe verbindingen zijn tijdelijke aanduidingen en moeten opnieuw verbonden worden", + "renamed to": "hernoemd naar", + "Tables Changes": "Tabellen Wijzigingen", + "No changes to apply": "Geen wijzigingen om toe te passen", + "Apply Changes": "Wijzigingen toepassen", + "Create Git Release": "Maak Git Release", + "Create Project Release": "Maak Project Release", + "Create Rollback to": "Maak een Rollback naar", + "Source": "Bron", + "Rollback": "Rollback", + "Imported At": "Geïmporteerd op", + "Imported By": "Geïmporteerd door", + "Track and manage your project version history and deployments. ": "Volg en beheer uw projectversiegeschiedenis en implementaties. ", + "Environments & Releases": "Omgevingen & Releases", + "Project Releases": "Project releases", + "Create Release": "Maak release", + "From Git": "Van Git", + "From Project": "Van Project", + "No project releases found": "Geen project releases gevonden", + "Create a project release to get started": "Maak een project release aan om te beginnen.", + "Please select project": "Selecteer een project", + "No Changes Found": "Geen wijzigingen gevonden", + "There are no differences to apply": "Er zijn geen verschillen om toe te passen", + "Please select a project": "Selecteer een project", + "Search projects...": "Zoek projecten...", + "Review Changes": "Bekijk wijzigingen", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Geïmport door", + "from": "van", + "No description provided": "Geen beschrijving opgegeven", + "Invitation only sign up": "Alleen uitnodiging aanmelden", + "Please ask your administrator to add you to the organization.": "Vraag uw beheerder om u toe te voegen aan de organisatie.", + "Something went wrong, please try again.": "Er is iets fout gegaan, probeer het opnieuw.", + "Please try again.": "Probeer het opnieuw.", + "Please enter a valid email address": "Voer een geldig e-mailadres in", + "The email is already added.": "Dit e-mailadres is al toegevoegd.", + "Add email": "E-mailadres toevoegen", + "Only project admins can do this": "Alleen projectbeheerders kunnen dit doen", + "Add Alert Email": "E-mail bij waarschuwing toevoegen", + "Enter the email address to receive alerts.": "Voer het e-mailadres in om meldingen te ontvangen.", + "Add Email": "E-mail toevoegen", + "Emails": "E-mails", + "Add email addresses to receive alerts.": "Voeg e-mailadressen toe om meldingen te ontvangen.", + "No emails added yet.": "Nog geen e-mails toegevoegd.", + "Choose what you want to be notified about.": "Kies welke notificaties jij wilt ontvangen.", + "Project and alert permissions are required to change this setting.": "Project- en waarschuwingsrechten zijn vereist om deze instelling te wijzigen.", + "Every Failed Run": "Elke mislukte Run", + "Get an email alert when a flow fails.": "Krijg een e-mailwaarschuwing wanneer een flow mislukt.", + "Get an email alert when a new issue created.": "Ontvang een e-mailmelding wanneer een nieuw probleem is aangemaakt.", + "Never": "Nooit", + "Turn off email notifications.": "Schakel e-mailmeldingen uit.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Pas het uiterlijk van de app aan. Schakel automatisch tussen dag- en nachtthema's.", + "Select the theme for the dashboard.": "Selecteer het thema voor het dashboard.", + "Light": "Licht", + "Dark": "Donker", + "Select the language that will be used in the dashboard.": "Selecteer de taal die zal worden gebruikt in het dashboard.", + "Select language": "Taal selecteren", + "Search language...": "Zoek taal...", + "No language found.": "Geen taal gevonden.", + "Help us translate Activepieces to your language.": "Help ons Activepieces te vertalen naar jouw taal.", + "Learn more": "Meer informatie", + "Git Connection Removed": "Git verbinding verwijderd", + "Your Git repository has been successfully disconnected": "Uw Git-repository is met succes losgekoppeld", + "Enable Environments": "Omgevingen inschakelen", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Implementeer over ontwikkeling, staging en productie omgevingen met versie controle en teamsamenwerking", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Maak verbinding met Git om versiemanagement in te schakelen, maak een backup van je flows en beheer meerdere omgevingen. ", + "Repository URL": "Repository URL", + "Not connected": "Niet verbonden", + "Project Folder": "Project Map", + "Releases Enabled": "Releases ingeschakeld", + "You have successfully enabled releases": "U hebt met succes releases ingeschakeld", + "Enable releases to easily create and manage project releases.": "Schakel releases in om eenvoudig projectreleases te maken en te beheren.", + "The external ID is already taken.": "De externe ID is al in gebruik.", + "Manage general settings for your project.": "Algemene instellingen voor uw project beheren.", + "Used to identify the project based on your SaaS ID": "Gebruikt om het project te identificeren op basis van uw SaaS ID", + "org-3412321": "org-3412321", + "Delete {name}": "Verwijder {name}", + "This will permanently delete this piece, all steps using it will fail.": "Dit verwijdert dit deze piece, alle stappen die het gebruiken zullen mislukken.", + "Add a piece to your project that you want to use in your automations": "Voeg een stuk toe aan je project dat je wilt gebruiken in je automatiseringen", + "Pieces list updated": "Lijst met artikelen bijgewerkt", + "Manage Pieces": "Artikelen beheren", + "Choose which pieces you want to be available for your current project users": "Kies welke stukken je beschikbaar wilt zijn voor je huidige projectgebruikers", + "Unlock Team Permissions": "Ontgrendel teamrechten", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "U kunt gratis gebruikers uitnodigen voor uw Platform in de community editie. Voor geavanceerde rollen en toestemmingsverzoek proefperiode", + "Project Members": "Projectleden", + "Invite your team members to collaborate.": "Nodig uw teamleden uit om samen te werken.", + "No members are added to this project.": "Er zijn geen leden toegevoegd aan dit project.", + "Pending Invitations": "Openstaande uitnodigingen", + "No pending invitation.": "Geen openstaande uitnodigingen.", + "templateId is missing": "templateId ontbreekt", + "Me Only": "Alleen ikzelf", + "Unresolved": "Onopgelost", + "Resolved": "Opgelost", + "Title": "Aanspreektitel", + "Date Created": "Datum aangemaakt", + "Manage todos for your project that are created by automations": "Beheer todos voor je project die zijn aangemaakt door automatiseringen", + "No todos found": "Geen todos gevonden", + "You do not have any pending todos. Great job!": "U heeft geen lopende todos. Goed gedaan!", + "Write a comment...": "Schrijf een reactie...", + "Beta": "Bèta", + "This feature is still under testing and might be changed often": "Deze functie is nog steeds in de testfase en kan vaak worden gewijzigd", + "Failed to copy to clipboard": "Kon niet naar klembord kopiëren", + "{number} items selected": "{number} items geselecteerd", + "Select All": "Alles selecteren", + "No results found.": "Geen resultaten gevonden.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect moet worden gebruikt binnen MultiSelectProvider", + "Unset": "Uitzetten", + "Refresh": "Vernieuwen", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} meer", + "Removed {entityName}": "Verwijderd {entityName}", + "Download File": "Bestand downloaden", + "Copied to clipboard": "Gekopiëerd naar klembord", + "File is not available after execution.": "Bestand is niet beschikbaar na uitvoering.", + "Available for Projects": "Beschikbaar voor projecten", + "Select projects": "Selecteer projecten", + "No items": "Geen artikelen", + "Previous": "named@@0", + "to": "naar", + "Last Week": "Vorige week", + "Last Month": "Afgelopen maand", + "Last 3 Months": "Laatste 3 maanden", + "Last 6 Months": "Laatste 6 maanden", + "Next 7 days": "Komende 7 dagen", + "Next 30 days": "Komende 30 dagen", + "Next 90 days": "Komende 90 dagen", + "Next 180 days": "Komende 180 dagen", + "Select Time Range": "Selecteer tijdbereik", + "Clear": "Verwijderen", + "Download": "downloaden", + "Go to Dashboard": "Ga naar Dashboard", + "Select a file": "Selecteer een bestand", + "Press space to separate values": "Druk op spatie om waarden te scheiden", + "AM": "VK", + "PM": "PM", + "Already have an account?": "Heb je al een account?", + "Sign in": "Inloggen", + "Don't have an account?": "Heb je geen account?", + "Sign up": "Registreren", + "Welcome Back!": "Welkom terug!", + "Enter your email below to sign in to your account": "Voer hieronder uw e-mailadres in om in te loggen op uw account", + "Let's Get Started!": "Laten we beginnen!", + "Create your account and start flowing!": "Maak je account aan en begin met je Flows!", + "Your password was changed successfully": "Jouw wachtwoord is met succes gewijzigd", + "Your password reset request has expired, please request a new one": "Uw aanvraag voor het opnieuw instellen van het wachtwoord is verlopen, vraag een nieuwe aan", + "Reset Password": "Wachtwoord resetten", + "Enter your new password": "Voer nieuw wachtwoord in", + "Password is required": "Wachtwoord is vereist", + "Verification email resent, if previous one expired.": "Verificatie e-mail verzonden, indien de vorige verlopen.", + "Password reset link resent, if previous one expired.": "Wachtwoord reset link opnieuw verstuurd, als de vorige verlopen is.", + "We sent you a link to complete your registration to": "We hebben u een link gestuurd om uw registratie te voltooien naar", + "We sent you a link to reset your password to": "We hebben u een link gestuurd om uw wachtwoord opnieuw in te stellen op", + "Didn't receive an email or it expired?": "Geen e-mail ontvangen of het is verlopen?", + "Resend": "Opnieuw versturen", + "Please enter your email": "Vul jouw e-mailadres in", + "Check Your Inbox": "Controleer je inbox", + "If the user exists we'll send you an email with a link to reset your password.": "Als de gebruiker bestaat sturen we je een e-mail met een link om je wachtwoord te resetten.", + "Send Password Reset Link": "Stuur een link om mijn wachtwoord te resetten", + "Back to sign in": "Terug naar aanmelden", + "Email is invalid": "Ongeldig e-mailadres", + "Something went wrong, please try again later": "Iets ging fout, probeer het later opnieuw", + "Invalid email or password": "Ongeldig e-mailadres of wachtwoord", + "User has been deactivated": "Gebruiker is gedeactiveerd", + "Email domain is disallowed": "E-mail domein is niet toegestaan", + "Email authentication has been disabled": "E-mail authenticatie is uitgeschakeld", + "Forgot your password?": "Wachtwoord vergeten?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "Aanmelden is beperkt. U heeft een uitnodiging nodig om deel te nemen. Neem contact op met de beheerder.", + "Email is already used": "Dit e-mailadres wordt al gebruikt", + "Email authentication is disabled": "E-mail authenticatie is uitgeschakeld", + "First name is required": "Voornaam is vereist", + "Last name is required": "Achternaam is verplicht", + "Email is required": "E-mailadres is vereist", + "Receive updates and newsletters from activepieces": "Ontvang updates en nieuwsbrieven van activepieces", + "By creating an account, you agree to our": "Door een account aan te maken, gaat je akkoord met onze", + "terms of service": "algemene voorwaarden", + "privacy policy": "privacybeleid", + "Sign up With": "Registreer met", + "Google": "Google", + "Sign in With": "Log in met", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "E-mail is geverifieerd. U wordt omgeleid naar inloggen...", + "Verifying email...": "E-mailadres verifiëren...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "De uitnodiging is verlopen, nadat u opnieuw bent aangemeld, kunt u de verificatie e-mail opnieuw verzenden.", + "Redirecting to sign in...": "Omleiden om in te loggen...", + "Password must contain at least one special character": "Het wachtwoord moet bestaan uit ten minste één speciaal teken", + "Password must contain at least one lowercase letter": "Wachtwoord moet ten minste één kleine letter bevatten", + "Password must contain at least one uppercase letter": "Wachtwoord moet ten minste één hoofdletter bevatten", + "Password must contain at least one number": "Wachtwoord moet minimaal één getal bevatten", + "8-64 Characters": "8-64 karakters", + "Special Character": "Speciaal teken", + "Lowercase": "Kleine letter", + "Uppercase": "Hoofdletter", + "Number": "Getal", + "Connection has been updated.": "Verbinding is bijgewerkt.", + "Edit Global Connection": "Algemene verbinding bewerken", + "Connection has been renamed.": "Verbinding is hernoemd.", + "New Connection Name": "Nieuwe Connectie Naam", + "Connection name already used": "Connectienaam is al in gebruik", + "Please select at least one project": "Selecteer ten minste één project", + "Run Succeeded": "Run Succesvol", + "Run Failed": "Run Mislukt", + "Flow Run is paused": "Flow is gepauzeerd", + "Run Failed due to quota exceeded": "Run mislukt vanwege het overschrijden van het quotum", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "De uitvoering is mislukt als gevolg van het overschrijden van de geheugenlimiet van {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "Run overschrijdt {timeout} seconden, probeer je stappen te optimaliseren.", + "Run failed for an unknown reason, contact support.": "Het uitvoeren is mislukt om onbekende reden, contact support.", + "Unknown": "Onbekend", + "Exit Run": "Run afsluiten", + "Select shown": "Selecteer getoond", + "Select all": "Alles selecteren", + "Start Time": "Starttijd", + "Runs replayed successfully": "Runs met succes afgespeeld", + "Retry": "Opnieuw", + "all except": "alles behalve", + "all": "alle", + "Only failed runs can be retried from failed step": "Alleen mislukte uitvoeringen kunnen opnieuw worden geprobeerd vanaf de mislukte stap", + "No flow runs found": "Geen stroom uitvoeringen gevonden", + "Come back later when your automations start running": "Kom later terug wanneer jouw automatiseringen beginnen te werken", + "Step running": "Stap wordt uitgevoerd", + "Step paused": "Stap is gepauzeerd", + "Step Stopped": "Stap gestopt", + "Step Succeeded": "Stap voltooid", + "Step Failed": "Stap mislukt", + "Please publish flow first": "Publiceer de flow eerst", + "Flow is on": "Flow staat aan", + "Flow is off": "Flow staat uit", + "Permission Needed": "Toestemming nodig", + "Draft Version": "Concept versie", + "Published Version": "Gepubliceerde versie", + "Locked Version": "Vergrendelde versie", + "flowsImported": "{flowsCount, plural, one {}=0 {Geen Flows geïmporteerd} =1 {Flow Succesvol geïmporteerd} other {Flows succesvol geïmporteerd}}", + "Template file is invalid": "Sjabloonbestand is ongeldig", + "No valid templates found. The following files failed to import: ": "Geen geldige sjablonen gevonden. De volgende bestanden kunnen niet importeren: ", + "Please select a file first": "Selecteer eerst een bestand", + "Unsupported file type": "Niet-ondersteund bestandstype", + "Import Flow": "Flow importeren", + "Warning": "Waarschuwing", + "Importing a flow will overwrite your current one.": "Het importeren van een stroom overschrijft uw huidige versie.", + "Select a folder": "Selecteer een map", + "Folders": "Mappen", + "Please select a folder": "Selecteer een map", + "Moved flows successfully": "Verplaatste stromen succesvol", + "Move Selected Flows": "Geselecteerde Stromen verplaatsen", + "Select Folder": "Selecteer map", + "No Folders": "Geen mappen", + "Flow has been renamed.": "Flow is hernoemd.", + "New Flow Name": "Nieuwe Flow naam", + "Use Template": "Gebruik template", + "Browse Templates": "Blader door de templates", + "Search templates": "Zoek een template", + "No templates found, try adjusting your search": "Geen sjablonen gevonden, probeer uw zoekopdracht aan te passen", + "Read more about this template in": "Lees meer over dit template", + "this blog!": "dit blog!", + "Share Template": "Deel template", + "Generate or update a template link for the current flow to easily share it with others.": "Genereer of update een templatelink voor de huidige flow om deze gemakkelijk te delen.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "Het template zal geen gegevens hebben in de connectievelden en gevoelige informatie veilig houden.", + "A short description of the template": "Een korte beschrijving van de template", + "Flow Is In Use": "Flow Is in gebruik", + "Flow is being used by another user, please try again later.": "Flow wordt gebruikt door een andere gebruiker, probeer het later opnieuw.", + "Flow has been published.": "Flow is gepubliceerd.", + "Flows have been exported.": "Er zijn stromen geëxporteerd.", + "Run": "Uitvoeren", + "Real time flow": "Real-time flow", + "Flow can't be published with empty trigger {name}": "Flow kan niet worden gepubliceerd met lege trigger {name}", + "Please contact support as your published flow has a problem": "Neem contact op met support omdat je gepubliceerde flow een probleem heeft", + "Actions": "Acties", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Weet je zeker dat je deze flows wilt verwijderen? Dit zal de flows, al hun gegevens en achtergrondgegevens permanent verwijderen.", + "You are on a development branch, this will not delete the flows from the remote repository.": "U zit in een ontwikkelingsbranch, dit zal de stromen niet verwijderen uit de externe repository.", + "Please enter folder name": "Vul de naam van de map in", + "Added folder successfully": "Map succesvol toegevoegd", + "The folder name already exists.": "De mapnaam bestaat al.", + "New Folder": "Nieuwe map", + "Folder Name": "Naam van de map", + "Loading...": "Laden...", + "Delete {folderName}": "{folderName} verwijderen", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "Als u deze map verwijdert, behouden we de flows en verplaatsen we ze naar niet gecategoriseerd", + "All flows": "Alle flows", + "Please enter a folder name": "Vul de naam van de map in", + "Renamed flow successfully": "Succesvol de flow hernoemd", + "Folder name already used": "Mapnaam is al in gebruik", + "New Folder Name": "Nieuwe mapnaam", + "Connected successfully": "Succesvol verbonden", + "Connect Git": "Git koppelen", + "Remote URL": "Externe URL", + "Folder name is the name of the folder where the project will be stored or fetched.": "Mapnaam is de naam van de map waar het project wordt opgeslagen of opgehaald.", + "SSH Private Key": "SSH persoonlijke sleutel", + "The SSH private key to use for authentication.": "De SSH private key moet worden gebruikt voor authenticatie.", + "Only published flows can be pushed to Git": "Alleen gepubliceerde flows kunnen naar Git worden gepusht", + "Pushed successfully": "Pushed succesvol", + "Invalid Operation": "Ongeldige bewerking", + "Commit Message": "Commit Bericht", + "Enter a commit message to describe the changes you want to push.": "Voer een commit message in om de veranderingen te beschrijven die u wilt pushen.", + "Push": "Push", + "This field is required": "Dit veld is verplicht", + "Your submission was successfully received.": "Uw aanvraag is met succes ontvangen.", + "Flow not found": "Flow niet gevonden", + "The flow you are trying to submit to does not exist.": "De flow die jij probeert in te dienen bestaat niet.", + "The flow failed to execute.": "Het uitvoeren van de flow is mislukt.", + "Submit": "Versturen", + "issues-notification": "issues-melding", + "Please select a package type": "Selecteer een package type", + "package.json not found in archive": "package.json niet gevonden in archief", + "Error processing archive file": "Fout bij verwerken archiefbestand", + "Please upload a .tgz file": "Upload een .tgz bestand", + "Piece name is required for NPM Registry": "Naam van stukjes is vereist voor NPM register", + "Piece version is required for NPM Registry": "Piece versie is vereist voor NPM register", + "Piece installed": "Piece geïnstalleerd", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "Een stuk met deze naam en versie is al geïnstalleerd. Update het versienummer in package.json en probeer het opnieuw.", + "Install Piece": "Installeer Piece", + "Install a piece": "Installeer een piece", + "Package Type": "Pakket Type", + "NPM Registry": "NPM register", + "Packed Archive (.tgz)": "Verpakt archief (.tgz)", + "Piece Version": "Versie van Piece", + "Package Archive": "Pakket Archief", + "Package archive": "Pakket archief", + "Powerful Node.js & TypeScript code with npm": "Krachtige Node.js & TypeScript code met npm", + "Loop on Items": "Herhaal op Voorwerpen", + "Split your flow into branches depending on condition(s)": "Splits je stroom in branches afhankelijk van conditie(s)", + "Empty Trigger": "Leeg Trigger", + "An internal error occurred while fetching data, please contact support": "Er is een interne fout opgetreden tijdens het ophalen van gegevens, neem contact op met support", + "An internal error occurred, please contact support": "Er is een interne fout opgetreden. Neem contact op met support", + "Custom Javascript Code": "Aangepaste Javascript Code", + "Router": "Router", + "recordsCount": "aantal records", + "selected": "Geselecteerd", + "All records selected": "Alle records geselecteerd", + "fieldsCount": "veldaantal", + "Saving...": "Opslaan...", + "Loading more...": "Meer laden...", + "Export Table": "Export tabel", + "Delete Records": "Records verwijderen", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Weet u zeker dat u de geselecteerde records wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.", + "record": "opnemen", + "records": "records", + "mm/dd/yyy": "dd-mm-jjjj", + "Delete Field": "Veld verwijderen", + "Are you sure you want to delete this field? This action cannot be undone.": "Weet je zeker dat je dit veld wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.", + "field": "veld", + "Ignored": "Genegeerd", + "Table": "Tabel", + "CSV": "csv", + "Field": "Veld", + "Please select a csv file": "Selecteer een CSV-bestand", + "Max file size is {maxFileSize}MB": "Maximale bestandsgrootte is {maxFileSize}MB", + "Import CSV": "Importeer CSV", + "Imported records will be added to the bottom of the table": "Geïmporteerde records zullen worden toegevoegd aan de onderkant van de tabel", + "Any records after the limit ({maxRecords} records) will be ignored": "Alle records na de limiet ({maxRecords} records) zullen worden genegeerd", + "CSV File": "CSV-bestand", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "Er is een onverwachte fout opgetreden tijdens het importeren van het csv-bestand, klik op de kopie en verzend het naar ondersteuning", + "Name must be unique": "Naam moet uniek zijn", + "Type is required": "Type is verplicht", + "Please add at least one option": "Voeg ten minste één optie toe", + "New Field": "Nieuw veld", + "Options": "Instellingen", + "Name is already taken": "Naam is al in gebruik", + "Table renamed": "Tabel hernoemd", + "Table name": "Tabel naam", + "Team Invitation Accepted": "Team uitnodiging geaccepteerd", + "Thank you for accepting the invitation. We are redirecting you right now...": "Bedankt voor het accepteren van de uitnodiging. We sturen je nu door...", + "Invalid invitation token. Please try again.": "Ongeldige uitnodigingstoken. Probeer het opnieuw.", + "Role updated successfully": "Rol succesvol bijgewerkt", + "Error updating role": "Fout bij bijwerken rol", + "Please try again later": "Probeer het later opnieuw", + "Edit Role for": "Rol bewerken voor", + "Select Role": "Selecteer lidmaatschap", + "Avatar": "Profielfoto", + "Remove {email}": "Verwijder {email}", + "Are you sure you want to remove this invitation?": "Weet je zeker dat je deze uitnodiging wilt verwijderen?", + "Please select invitation type": "Selecteer het type uitnodiging", + "Please select platform role": "Selecteer een platform rol", + "Invitation sent successfully": "Uitnodiging succesvol verzonden", + "Please select a project role": "Selecteer een project rol", + "Invitation link copied successfully": "Uitnodigingslink succesvol gekopieerd", + "Invite User": "Gebruiker uitnodigen", + "Invitation Link": "Uitnodigingslink", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Kopieer de link hieronder en deel deze met de gebruiker die je wilt uitnodigen. De uitnodiging vervalt binnen 24 uur.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Typ het e-mailadres van de gebruiker die je wilt uitnodigen, de uitnodiging verloopt binnen 24 uur.", + "Invite To": "Uitnodigen voor", + "Entire Platform": "Hele Platform", + "Select Project Role": "Selecteer Project Rol", + "Invite": "Uitnodigen", + "Platform Role": "Platform rol", + "Select a platform role": "Selecteer een platform rol", + "Are you sure you want to remove this member?": "Weet je zeker dat je dit lid wilt verwijderen?", + "Editor": "Bewerker", + "Operator": "Operator", + "Viewer": "Bekijker", + "Select a project role": "Selecteer een project rol", + "Steps in this flow": "Stappen in deze flow", + "Invalid Access": "Ongeldige toegang", + "Either the project does not exist or you do not have access to it.": "Het project bestaat niet of je hebt er geen toegang toe." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/pl/translation.json b/packages/react-ui/public/locales/pl/translation.json new file mode 100644 index 0000000..b2b2d88 --- /dev/null +++ b/packages/react-ui/public/locales/pl/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Publish", + "Latest version is published": "Latest version is published", + "Your flow has incomplete steps": "Your flow has incomplete steps", + "Edit Flow": "Edit Flow", + "View Draft": "View Draft", + "Uncategorized": "Uncategorized", + "Go to folder": "Go to folder", + "Support": "Support", + "Runs": "Runs", + "Run Logs": "Run Logs", + "Versions": "Versions", + "Versions History": "Versions History", + "Error generating code": "Error generating code", + "AI Copilot": "AI Copilot", + "i.e Calculate the sum of a list...": "i.e Calculate the sum of a list...", + "Send": "Send", + "Generating Code": "Generating Code", + "Hello there! I am here to generate code that helps with your flow": "Hello there! I am here to generate code that helps with your flow", + "Here are examples of what I am best used for: ": "Here are examples of what I am best used for: ", + "Text Processing": "Text Processing", + "Process strings, dates and data": "Process strings, dates and data", + "Data Operations": "Data Operations", + "Change data from one format to another": "Change data from one format to another", + "Calculations": "Calculations", + "Handle math and statistics": "Handle math and statistics", + "API Integration": "API Integration", + "Connect with external services. Best for simple integrations currently.": "Connect with external services. Best for simple integrations currently.", + "What would you like me to help you with?": "What would you like me to help you with?", + "Insert": "Insert", + "Data Selector": "Data Selector", + "Search": "Search", + "No matching data": "No matching data", + "Try adjusting your search": "Try adjusting your search", + "This trigger needs to have data loaded from your account, to use as sample data.": "This trigger needs to have data loaded from your account, to use as sample data.", + "This step needs to be tested in order to view its data.": "This step needs to be tested in order to view its data.", + "Go to Trigger": "Go to Trigger", + "Go to Step": "Go to Step", + "Select Mode": "Select Mode", + "Move Mode": "Move Mode", + "Reset Zoom": "Reset Zoom", + "Zoom In": "Zoom In", + "Zoom Out": "Zoom Out", + "Fit to View": "Fit to View", + "Replace": "Replace", + "Copy": "Copy", + "Duplicate": "Duplicate", + "Paste After Last Step": "Paste After Last Step", + "Paste Inside Loop": "Paste Inside Loop", + "Paste After": "Paste After", + "Paste Inside...": "Paste Inside...", + "New Branch": "New Branch", + "Paste Inside Branch": "Paste Inside Branch", + "Delete": "Delete", + "Duplicate Branch": "Duplicate Branch", + "Delete Branch": "Delete Branch", + "Invalid Move": "Invalid Move", + "The destination location is a child of the dragged step": "The destination location is a child of the dragged step", + "End": "End", + "Skipped": "Skipped", + "Incomplete settings": "Incomplete settings", + "logo": "logo", + "Step Icon": "Step Icon", + "Branch": "Branch", + "incompleteSteps": "{invalidSteps, plural, =0 {no incomplete steps} =1 {Complete 1 step} other {Complete # steps}}", + "Test Flow": "Test Flow", + "Please test the trigger first": "Please test the trigger first", + "View Only": "View Only", + "Use as Draft": "Use as Draft", + "Are you sure?": "Are you sure?", + "Your current draft version will be overwritten with": "Your current draft version will be overwritten with", + "version #": "version #", + "Cancel": "Cancel", + "Confirm": "Confirm", + "Version": "Version", + "Viewing": "Viewing", + "View": "View", + "Version History": "Version History", + "Error, please try again.": "Error, please try again.", + "Continue on Failure": "Continue on Failure", + "Enable this option to skip this step and continue the flow normally if it fails.": "Enable this option to skip this step and continue the flow normally if it fails.", + "Retry on Failure": "Retry on Failure", + "Automatically retry up to four attempts when failed.": "Automatically retry up to four attempts when failed.", + "Remove": "Remove", + "Add Item": "Add Item", + "File Input": "File Input", + "Date Input": "Date Input", + "Dynamic value": "Dynamic value", + "Select an option": "Select an option", + "Unexpected error, please retry": "Unexpected error, please retry", + "Unexpected error, please refresh the page or contact support": "Unexpected error, please refresh the page or contact support", + "Name can only contain letters, numbers and underscores": "Name can only contain letters, numbers and underscores", + "Ask AI": "Ask AI", + "Create Todo Guide": "Create Todo Guide", + "Where would you like the todo to be reviewed?": "Where would you like the todo to be reviewed?", + "Activepieces Todos": "Activepieces Todos", + "Users will manage tasks directly in Activepieces": "Users will manage tasks directly in Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.", + "External Channel (Slack, Teams, Email, ...)": "External Channel (Slack, Teams, Email, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.", + "Preview (Activepieces Todos)": "Preview (Activepieces Todos)", + "Preview (External channel)": "Preview (External channel)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "You can add the channel before the Wait Step, and configure the logic in the Router step", + "Add Steps": "Add Steps", + "All": "All", + "AI": "AI", + "Core": "Core", + "Apps": "Apps", + "Not available as trigger": "Not available as trigger", + "Not available as action": "Not available as action", + "Let our AI assistant help you out": "Let our AI assistant help you out", + "Or": "Or", + "Request Piece": "Request Piece", + "No pieces found": "No pieces found", + "Please select a piece first": "Please select a piece first", + "All Iterations": "All Iterations", + "Duration": "Duration", + "Input": "Input", + "Output": "Output", + "There are no logs captured for this run.": "There are no logs captured for this run.", + "Logs are kept for {days} days after execution and then deleted.": "Logs are kept for {days} days after execution and then deleted.", + "Run Details": "Run Details", + "Iteration": "Iteration", + "Done": "Done", + "Took": "Took", + "Running": "Running", + "on latest version": "on latest version", + "from failed step": "from failed step", + "Recent Runs": "Recent Runs", + "No runs found": "No runs found", + "Close": "Close", + "OR": "OR", + "And If": "And If", + "+ And": "+ And", + "+ Or": "+ Or", + "(Text) Contains": "(Text) Contains", + "(Text) Does not contain": "(Text) Does not contain", + "(Text) Exactly matches": "(Text) Exactly matches", + "(Text) Does not exactly match": "(Text) Does not exactly match", + "(Text) Starts with": "(Text) Starts with", + "(Text) Does not start with": "(Text) Does not start with", + "(Text) Ends with": "(Text) Ends with", + "(Text) Does not end with": "(Text) Does not end with", + "(List) Contains": "(List) Contains", + "(List) Does not contain": "(List) Does not contain", + "(Number) Is greater than": "(Number) Is greater than", + "(Number) Is less than": "(Number) Is less than", + "(Number) Is equal to": "(Number) Is equal to", + "(Date/time) After": "(Date/time) After", + "(Date/time) Before": "(Date/time) Before", + "(Date/time) Equals": "(Date/time) Equals", + "(Boolean) Is true": "(Boolean) Is true", + "(Boolean) Is false": "(Boolean) Is false", + "(List) Is empty": "(List) Is empty", + "(List) Is not empty": "(List) Is not empty", + "Exists": "Exists", + "Does not exist": "Does not exist", + "Incomplete condition": "Incomplete condition", + "First value": "First value", + "Second value": "Second value", + "Case sensitive": "Case sensitive", + "Execute If": "Execute If", + "The package name is required": "The package name is required", + "Success": "Success", + "Package added successfully": "Package added successfully", + "Could not fetch package version": "Could not fetch package version", + "Add NPM Package": "Add NPM Package", + "Type the name of the npm package you want to add.": "Type the name of the npm package you want to add.", + "Package Name": "Package Name", + "The latest version will be fetched and added": "The latest version will be fetched and added", + "Add": "Add", + "Code": "Code", + "Dependencies": "Dependencies", + "Use code": "Use code", + "Add package": "Add package", + "Inputs": "Inputs", + "Edit Step Name": "Edit Step Name", + "Edit Branch Name": "Edit Branch Name", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.", + "Items": "Items", + "Select an array of items": "Select an array of items", + "Reconnect": "Reconnect", + "Select a connection": "Select a connection", + "Create Connection": "Create Connection", + "Rename": "Rename", + "Move": "Move", + "Add Branch": "Add Branch", + "Execute": "Execute", + "Only the first (left) matching branch": "Only the first (left) matching branch", + "All matching paths from left to right": "All matching paths from left to right", + "Branches": "Branches", + "{field} is required": "{field} is required", + "Tool Sample Data": "Tool Sample Data", + "Fill in the following fields to use them as sample data for the trigger.": "Fill in the following fields to use them as sample data for the trigger.", + "No input fields defined in the schema": "No input fields defined in the schema", + "Save": "Save", + "Test Environment": "Test Environment", + "Assigned to": "Assigned to", + "(Me)": "(Me)", + "Please select status to resolve the todo": "Please select status to resolve the todo", + "Resolve": "Resolve", + "Change status to resolved": "Change status to resolved", + "Send Sample Data to Webhook": "Send Sample Data to Webhook", + "Method": "Method", + "Query Params": "Query Params", + "Headers": "Headers", + "Body": "Body", + "Type": "Type", + "JSON": "JSON", + "Text": "Text", + "Form Data": "Form Data", + "Generate Sample Data": "Generate Sample Data", + "Test Step": "Test Step", + "Retest": "Retest", + "Testing Failed": "Testing Failed", + "Tested Successfully": "Tested Successfully", + "Logs": "Logs", + "There is no sample data available found for this trigger.": "There is no sample data available found for this trigger.", + "Internal error, please try again later.": "Internal error, please try again later.", + "Failed to run test step and no error message was returned": "Failed to run test step and no error message was returned", + "Please fix inputs first": "Please fix inputs first", + "No sample data available": "No sample data available", + "Old results were removed, retest for new sample data": "Old results were removed, retest for new sample data", + "Result #": "Result #", + "The sample data can be used in the next steps.": "The sample data can be used in the next steps.", + "Testing Trigger": "Testing Trigger", + "Action Required": "Action Required", + "testPieceWebhookTriggerNote": "Please go to {pieceName} and trigger {triggerName}.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Send Data to the webhook URL to generate sample data to use in the next steps", + "Test Trigger": "Test Trigger", + "Use Mock Data": "Use Mock Data", + "Load Sample Data": "Load Sample Data", + "Test Tool": "Test Tool", + "home": "home", + "Home": "Home", + "Alerts": "Alerts", + "Releases": "Releases", + "Flows": "Flows", + "Products": "Products", + "MCP": "MCP", + "Tables": "Tables", + "Todos": "Todos", + "Push to Git": "Push to Git", + "Move To": "Move To", + "Duplicating": "Duplicating", + "Import": "Import", + "Exporting": "Exporting", + "Export": "Export", + "Share": "Share", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.", + "You are on a development branch, this will also delete the flow from the remote repository.": "You are on a development branch, this will also delete the flow from the remote repository.", + "flow": "flow", + "Community Support": "Community Support", + "Overview": "Overview", + "Projects": "Projects", + "Users": "Users", + "Setup": "Setup", + "Branding": "Branding", + "Global Connections": "Global Connections", + "Pieces": "Pieces", + "Templates": "Templates", + "License Key": "License Key", + "Security": "Security", + "Audit Logs": "Audit Logs", + "Single Sign On": "Single Sign On", + "Signing Keys": "Signing Keys", + "Project Roles": "Project Roles", + "API Keys": "API Keys", + "Infrastructure": "Infrastructure", + "Workers": "Workers", + "Health": "Health", + "Billing": "Billing", + "Settings": "Settings", + "Contact Sales": "Contact Sales", + "General": "General", + "Appearance": "Appearance", + "Team": "Team", + "Environments": "Environments", + "Project Settings": "Project Settings", + "Exit Platform Admin": "Exit Platform Admin", + "Enter Platform Admin": "Enter Platform Admin", + "Logout": "Logout", + "Misc": "Misc", + "Connections": "Connections", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "Today": "Today", + "Tasks": "Tasks", + "AI Credits": "AI Credits", + "Usage resets in": "Usage resets in", + "Manage": "Manage", + "Unlimited": "Unlimited", + "Enabled": "Enabled", + "Disabled": "Disabled", + "Could not claim the authorization code, make sure you have correct settings and try again.": "Could not claim the authorization code, make sure you have correct settings and try again.", + "Connection failed with error {msg}": "Connection failed with error {msg}", + "You don't have the permission to create a connection.": "You don't have the permission to create a connection.", + "Reconnect {displayName} Connection": "Reconnect {displayName}", + "Connect to {displayName}": "Connect to {displayName}", + "Connection Name": "Connection Name", + "Connection name": "Connection name", + "New Connection": "New Connection", + "Redirect URL": "Redirect URL", + "Client ID": "Client ID", + "Client Secret": "Client Secret", + "Connect": "Connect", + "Disconnect": "Disconnect", + "I would like to use my own App Credentials": "I would like to use my own App Credentials", + "I would like to use predefined App Credentials": "I would like to use predefined App Credentials", + "Permission needed": "Permission needed", + "Connections replaced successfully": "Connections replaced successfully", + "Error": "Error", + "Failed to replace connections": "Failed to replace connections", + "Failed to get affected flows": "Failed to get affected flows", + "Please select a piece": "Please select a piece", + "Please select a connection to replace": "Please select a connection to replace", + "Please select a connection to replace with": "Please select a connection to replace with", + "Replace Connections": "Replace Connections", + "Confirm Replacement": "Confirm Replacement", + "Replace one connection with another.": "Replace one connection with another.", + "This action requires ": "This action requires ", + "reconnecting": "reconnecting", + " any associated MCP pieces.": " any associated MCP pieces.", + "Piece": "Piece", + "Select a piece": "Select a piece", + "Connection to Replace": "Connection to Replace", + "Choose connection to replace": "Choose connection to replace", + "Replaced With": "Replaced With", + "Choose connection to replace with": "Choose connection to replace with", + "All flows will be changed to use the replaced with connection": "All flows will be changed to use the replaced with connection", + "Next": "Next", + "No flows will be affected by this change": "No flows will be affected by this change", + "Back": "Back", + "Unnamed tool": "Unnamed tool", + "This flow is enabled": "This flow is enabled", + "Enable this flow to make it available": "Enable this flow to make it available", + "Piece is updated successfully": "Piece is updated successfully", + "Piece is added successfully": "Piece is added successfully", + "Failed to update piece": "Failed to update piece", + "Failed to add piece": "Failed to add piece", + "Please select a connection": "Please select a connection", + "Your MCP server already has this piece": "Your MCP server already has this piece", + "+ New Connection": "+ New Connection", + "Edit Piece": "Edit Piece", + "Add Piece": "Add Piece", + "Connection": "Connection", + "MCP piece": "MCP piece", + "Failed to update piece status": "Failed to update piece status", + "Connection required": "Connection required", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.", + "piece": "piece", + "Connect your AI assistant to external services": "Connect your AI assistant to external services", + "Collapse": "Collapse", + "Show All": "Show All", + "Server URL": "Server URL", + "Hide the token for security": "Hide the token for security", + "Show the token": "Show the token", + "Generate a new token for security. This will invalidate the current URL.": "Generate a new token for security. This will invalidate the current URL.", + "URL copied to clipboard": "URL copied to clipboard", + "Copy URL to clipboard": "Copy URL to clipboard", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.", + "Max tables reached": "Max tables reached", + "You can't create more than {maxTables} tables": "You can't create more than {maxTables} tables", + "Name": "Name", + "Created": "Created", + "Delete Tables": "Delete Tables", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Are you sure you want to delete the selected tables? This action cannot be undone.", + "table": "table", + "Create and manage your tables to store your automation data": "Create and manage your tables to store your automation data", + "New Table": "New Table", + "No tables have been created yet": "No tables have been created yet", + "Create a table to get started and start managing your automation data": "Create a table to get started and start managing your automation data", + "Error deleting connections": "Error deleting connections", + "Status": "Status", + "Display Name": "Display Name", + "Owner": "Owner", + "App": "App", + "This connection is global and can be managed in the platform admin": "This connection is global and can be managed in the platform admin", + "External ID": "External ID", + "Connected At": "Connected At", + "Confirm Deletion": "Confirm Deletion", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Are you sure you want to delete the selected connections? This action cannot be undone.", + "Deleting connections may cause your Flows or MCP tools to break.": "Deleting connections may cause your Flows or MCP tools to break.", + "Manage project connections to external systems.": "Manage project connections to external systems.", + "No connections found": "No connections found", + "Come back later when you create a automation to manage your connections": "Come back later when you create a automation to manage your connections", + "Steps": "Steps", + "Folder": "Folder", + "Flow name": "Flow name", + "No flows found": "No flows found", + "Create a workflow to start automating": "Create a workflow to start automating", + "Create and manage your flows, run history and run issues": "Create and manage your flows, run history and run issues", + "Issues": "Issues", + "Untitled": "Untitled", + "Create flow": "Create flow", + "From scratch": "From scratch", + "Use a template": "Use a template", + "From local file": "From local file", + "Flow Name": "Flow Name", + "Count": "Count", + "First Seen": "First Seen", + "Last Seen": "Last Seen", + "Issues in {flowDisplayName} is marked as resolved.": "Issues in {flowDisplayName} is marked as resolved.", + "Unlock Issues": "Unlock Issues", + "Track issues in your workflows and troubleshoot them.": "Track issues in your workflows and troubleshoot them.", + "No issues found": "No issues found", + "All your workflows are running smoothly.": "All your workflows are running smoothly.", + "Mark as Resolved": "Mark as Resolved", + "All Flows Are Turned Off": "All Flows Are Turned Off", + "Task Usage Exceeded": "Task Usage Exceeded", + "of the Allowed Limit.": "of the Allowed Limit.", + "When a project tasks limit is reached,": "When a project tasks limit is reached,", + "all flows will be turned off and you will not be able to run any flows.": "all flows will be turned off and you will not be able to run any flows.", + "Please visit": "Please visit", + "Your Plan": "Your Plan", + "and increase your task limit, which requires your payment details.": "and increase your task limit, which requires your payment details.", + "Please contact your admin to increase the project task limit.": "Please contact your admin to increase the project task limit.", + "and increase the project task limit.": "and increase the project task limit.", + "Dismiss": "Dismiss", + "Token rotated successfully": "Token rotated successfully", + "Failed to rotate token": "Failed to rotate token", + "Piece removed successfully": "Piece removed successfully", + "Failed to remove piece": "Failed to remove piece", + "Flow created successfully": "Flow created successfully", + "Failed to create flow": "Failed to create flow", + "Add Flow": "Add Flow", + "Let your AI assistant trigger automations": "Let your AI assistant trigger automations", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Connect to your hosted MCP Server using any MCP client to communicate with tools", + "MCP Server": "MCP Server", + "My Tools": "My Tools", + "Create Flow": "Create Flow", + "Note": "Note", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.", + "This URL grants access to your tools and data. Only share with trusted applications.": "This URL grants access to your tools and data. Only share with trusted applications.", + "Server Configuration": "Server Configuration", + "Hide sensitive data": "Hide sensitive data", + "Show sensitive data": "Show sensitive data", + "Create a new URL. The current one will stop working.": "Create a new URL. The current one will stop working.", + "Copy configuration": "Copy configuration", + "Configuration copied to clipboard": "Configuration copied to clipboard", + "Claude": "Claude", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "Server/Other", + "Note: MCPs only work with": "Note: MCPs only work with", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", not the web version.", + "Prerequisites:": "Prerequisites:", + "Install": "Install", + "Node.js": "Node.js", + "and": "and", + "Open Settings:": "Open Settings:", + "Click the menu and select": "Click the menu and select", + "Developer": "Developer", + "Configure MCP:": "Configure MCP:", + "Click": "Click", + "Edit Config": "Edit Config", + "and paste the configuration below": "and paste the configuration below", + "Save and Restart:": "Save and Restart:", + "Save the config and restart Claude Desktop": "Save the config and restart Claude Desktop", + "Navigate to": "Navigate to", + "Cursor Settings": "Cursor Settings", + "Add Server:": "Add Server:", + "Add new global MCP server": "Add new global MCP server", + "Configure:": "Configure:", + "Paste the configuration below and save": "Paste the configuration below and save", + "Use either method:": "Use either method:", + "Go to": "Go to", + "Advanced Settings": "Advanced Settings", + "Open Command Palette and select": "Open Command Palette and select", + "Windsurf Settings Page": "Windsurf Settings Page", + "Navigate to Cascade:": "Navigate to Cascade:", + "Select": "Select", + "Cascade": "Cascade", + "in the sidebar": "in the sidebar", + "Add Server": "Add Server", + "Add custom server +": "Add custom server +", + "Copy URL": "Copy URL", + "Client Setup Instructions": "Client Setup Instructions", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "After changing connections or flows, reconnect your MCP server for changes to take effect.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.", + "icon": "icon", + "Unlock Analytics": "Unlock Analytics", + "Get insights into your platform usage and performance with our analytics dashboard": "Get insights into your platform usage and performance with our analytics dashboard", + "Active Flows": "Active Flows", + "The number of enabled flows in the platform": "The number of enabled flows in the platform", + "Active Projects": "Active Projects", + "The number of projects with at least one enabled flow": "The number of projects with at least one enabled flow", + "Active Users": "Active Users", + "The number of users logged in the last 30 days": "The number of users logged in the last 30 days", + "Out of {totalusers} total users": "Out of {totalusers} total users", + "Pieces Used": "Pieces Used", + "The number of unique pieces used across all flows": "The number of unique pieces used across all flows", + "Flows with AI": "Flows with AI", + "The number of enabled flows that use AI pieces": "The number of enabled flows that use AI pieces", + "Metrics": "Metrics", + "Executed Tasks": "Executed Tasks", + "Showing total executed tasks for specified time range": "Showing total executed tasks for specified time range", + "Tasks Usage Limit": "Tasks Usage Limit", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.", + "Number of monthly tasks": "Number of monthly tasks", + "Save changes": "Save changes", + "Limits updated successfully": "Limits updated successfully", + "Failed to update limits": "Failed to update limits", + "Failed to load billing information": "Failed to load billing information", + "Billing Amount": "Billing Amount", + "Manage Payment Details": "Manage Payment Details", + "Add Payment Details": "Add Payment Details", + "Current Task Usage": "Current Task Usage", + "Count of executed steps": "Count of executed steps", + "Billing Limit": "Billing Limit", + "Edit": "Edit", + "Add Limit": "Add Limit", + "Current Credit Usage": "Current Credit Usage", + "WebSocket Connection": "WebSocket Connection", + "Connected": "Connected", + "Disconnected": "Disconnected", + "No issues detected": "No issues detected", + "Check the status of your platform and its components": "Check the status of your platform and its components", + "System Health Status": "System Health Status", + "All systems are running smoothly": "All systems are running smoothly", + "Check the health of your worker machines": "Check the health of your worker machines", + "Workers Machine": "Workers Machine", + "This is demo data. In a real environment, this would show your actual worker machines.": "This is demo data. In a real environment, this would show your actual worker machines.", + "No workers found": "No workers found", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "You don't have any worker machines yet. Spin up new machines to execute your automations", + "IP Address": "IP Address", + "CPU Usage": "CPU Usage", + "Disk Usage": "Disk Usage", + "RAM Usage": "RAM Usage", + "Last Contact": "Last Contact", + "Configs": "Configs", + "Environment Variables": "Environment Variables", + "Websocket Connection Error": "Websocket Connection Error", + "Retry Connection": "Retry Connection", + "Update Available": "Update Available", + "Update Now": "Update Now", + "Your Universal AI needs a quick setup": "Your Universal AI needs a quick setup", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.", + "Configure": "Configure", + "Platform Alerts": "Platform Alerts", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.", + "View Alerts": "View Alerts", + "Used Tasks": "Used Tasks", + "Used AI Credits": "Used AI Credits", + "Cannot delete active project, switch to another project first": "Cannot delete active project, switch to another project first", + "Delete Projects": "Delete Projects", + "Are you sure you want to delete the selected projects?": "Are you sure you want to delete the selected projects?", + "New Project": "New Project", + "Validation error": "Validation error", + "Project has enabled flows. Please disable them first.": "Project has enabled flows. Please disable them first.", + "This project is active. Please switch to another project first.": "This project is active. Please switch to another project first.", + "Unlock Projects": "Unlock Projects", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Orchestrate your automation teams across projects with their own flows, connections and usage quotas", + "Manage your automation projects": "Manage your automation projects", + "No projects found": "No projects found", + "Start by creating projects to manage your automation teams": "Start by creating projects to manage your automation teams", + "Edit project": "Edit project", + "Name is required": "Name is required", + "Create New Project": "Create New Project", + "Project Name": "Project Name", + "Id": "Id", + "Enable API Keys": "Enable API Keys", + "Create and manage API keys to access Activepieces APIs.": "Create and manage API keys to access Activepieces APIs.", + "New Api Key": "New Api Key", + "No API keys found": "No API keys found", + "Start by creating an API key to communicate with Activepieces APIs": "Start by creating an API key to communicate with Activepieces APIs", + "Delete API Key": "Delete API Key", + "Are you sure you want to delete this API key?": "Are you sure you want to delete this API key?", + "API Key": "API Key", + "API Key Created": "API Key Created", + "Create New API Key": "Create New API Key", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Please save this secret key somewhere safe and accessible. For security reasons,", + "you won't be able to view it again after closing this dialog.": "you won't be able to view it again after closing this dialog.", + "API Key Name": "API Key Name", + "Action": "Action", + "Performed By": "Performed By", + "Project": "Project", + "Unlock Audit Logs": "Unlock Audit Logs", + "Comply with internal and external security policies by tracking activities done within your account": "Comply with internal and external security policies by tracking activities done within your account", + "Track activities done within your platform": "Track activities done within your platform", + "No audit logs found": "No audit logs found", + "Come back later when you have some activity to audit": "Come back later when you have some activity to audit", + "Resource": "Resource", + "Details": "Details", + "N/A": "N/A", + "Flow Run": "Flow Run", + "Flow": "Flow", + "User": "User", + "Signing Key": "Signing Key", + "Project Role Management": "Project Role Management", + "Define custom roles and permissions to control what your team members can access and modify": "Define custom roles and permissions to control what your team members can access and modify", + "Define custom roles and permissions that can be assigned to your team members": "Define custom roles and permissions that can be assigned to your team members", + "New Role": "New Role", + "Contact sales to unlock custom roles": "Contact sales to unlock custom roles", + "Create New Role": "Create New Role", + "View ": "View ", + "Edit ": "Edit ", + "Role Name": "Role Name", + "Permissions": "Permissions", + "None": "None", + "Read": "Read", + "Write": "Write", + "Create": "Create", + "Email": "Email", + "First Name": "First Name", + "Last Name": "Last Name", + "Roles": "Roles", + "View the users assigned to this role": "View the users assigned to this role", + "Role": "Role", + "No users found": "No users found", + "Starting by assigning users to this role": "Starting by assigning users to this role", + "Project Role entry deleted successfully": "Project Role entry deleted successfully", + "Updated": "Updated", + "No project roles found": "No project roles found", + "Create custom project roles to manage permissions for platform users": "Create custom project roles to manage permissions for platform users", + "Show Users": "Show Users", + "View Role": "View Role", + "Edit Role": "Edit Role", + "Delete Role": "Delete Role", + "Project Role": "Project Role", + "Unlock Embedding Through JS SDK": "Unlock Embedding Through JS SDK", + "Enable signing keys to access embedding functionalities.": "Enable signing keys to access embedding functionalities.", + "New Signing Key": "New Signing Key", + "No signing keys found": "No signing keys found", + "Create a signing key to authenticate users with embedding": "Create a signing key to authenticate users with embedding", + "Delete Signing Key": "Delete Signing Key", + "Are you sure you want to delete this signing key?": "Are you sure you want to delete this signing key?", + "Signing Key Created": "Signing Key Created", + "Create New Signing Key": "Create New Signing Key", + "Signing Key Name": "Signing Key Name", + "Allowed domains updated": "Allowed domains updated", + "Update": "Update", + "Enable": "Enable", + "Configure Allowed Domains": "Configure Allowed Domains", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.", + "Add Domain": "Add Domain", + "Allow logins through {providerName}'s single sign-on functionality.": "Allow logins through {providerName}'s single sign-on functionality.", + "Email authentication updated": "Email authentication updated", + "Enable Single Sign On": "Enable Single Sign On", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Let your users sign in with your current SSO provider or give them self serve sign up access", + "Allowed Domains": "Allowed Domains", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Allow users to authenticate with specific domains. Leave empty to allow all domains.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Allowed Email Login", + "Allow logins through email and password.": "Allow logins through email and password.", + "Single sign on settings updated": "Single sign on settings updated", + "Disable": "Disable", + "Configure {provider} SSO": "Configure {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} Client ID", + "{provider} Client Secret": "{provider} Client Secret", + "Single sign-on settings updated": "Single sign-on settings updated", + "Configure SAML 2.0 SSO": "Configure SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDP Metadata", + "IDP Certificate": "IDP Certificate", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "Base URL", + "Resource Name": "Resource Name", + "Deployment Name": "Deployment Name", + "Saving": "Saving", + "Activepieces Copilot": "Activepieces Copilot", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot is configured and ready to help your users build flows faster using AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configure Activepieces Copilot to help your users build flows faster using AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Set provider credentials that will be used by universal AI pieces, i.e Text AI.", + "AI Providers": "AI Providers", + "Copilot": "Copilot", + "Configure credentials for {providerName} AI provider.": "Configure credentials for {providerName} AI provider.", + "Update AI Provider": "Update AI Provider", + "Enable AI Provider": "Enable AI Provider", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Please enter a valid domain", + "Your changes have been saved.": "Your changes have been saved.", + "The domain is already added.": "The domain is already added.", + "Add Custom Domain": "Add Custom Domain", + "Enter a domain name without a protocol (e.g. example.com)": "Enter a domain name without a protocol (e.g. example.com)", + "Logo URL": "Logo URL", + "Icon URL": "Icon URL", + "Favicon URL": "Favicon URL", + "Default Language": "Default Language", + "Select Language": "Select Language", + "No Languages": "No Languages", + "Primary Color": "Primary Color", + "Custom Domains": "Custom Domains", + "No domains added yet.": "No domains added yet.", + "Verified": "Verified", + "Pending, please contact the support for dns verification.": "Pending, please contact the support for dns verification.", + "Are you sure you want to delete {domain}?": "Are you sure you want to delete {domain}?", + "Brand Activepieces": "Brand Activepieces", + "Give your users an experience that looks like you by customizing the color, logo and more": "Give your users an experience that looks like you by customizing the color, logo and more", + "Configure the appearance and SMTP settings for your platform.": "Configure the appearance and SMTP settings for your platform.", + "Invalid host": "Invalid host", + "Invalid username": "Invalid username", + "Invalid password": "Invalid password", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Invalid sender name", + "SMTP is configured": "SMTP is configured", + "Mail Server": "Mail Server", + "Set up your SMTP settings to send emails from your domain.": "Set up your SMTP settings to send emails from your domain.", + "Disable Mail Server": "Disable Mail Server", + "Are you sure you want to disable your mail server?": "Are you sure you want to disable your mail server?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "This will stop you from sending emails for issues, quota limits, invitations and forgot password.", + "mail server": "mail server", + "Host": "Host", + "Port": "Port", + "Username": "Username", + "Password": "Password", + "Sender Email": "Sender Email", + "Sender Name": "Sender Name", + "Enable Global Connections": "Enable Global Connections", + "Manage platform-wide connections to external systems.": "Manage platform-wide connections to external systems.", + "No global connections found": "No global connections found", + "Create a global connection that can be shared to multiple projects": "Create a global connection that can be shared to multiple projects", + "License key is invalid": "License key is invalid", + "Invalid license key": "Invalid license key", + "License activated!": "License activated!", + "Activate License Key": "Activate License Key", + "Let the magic begin!": "Let the magic begin!", + "Activate": "Activate", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ", + "This feature is not available in your current edition. ": "This feature is not available in your current edition. ", + "Learn how to upgrade": "Learn how to upgrade", + "Activate your platform and unlock enterprise features": "Activate your platform and unlock enterprise features", + "Activate License": "Activate License", + "Expiration": "Expiration", + "Valid until": "Valid until", + "Expired": "Expired", + "Expires soon": "Expires soon", + "Features": "Features", + "Applying Tags...": "Applying Tags...", + "Tags applied.": "Tags applied.", + "Tag created": "Tag created", + "Tag": "Tag", + "Tags": "Tags", + "Control Pieces": "Control Pieces", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Show the pieces that matter most to your users and hide the ones you don't like.", + "Manage the pieces that are available to your users": "Manage the pieces that are available to your users", + "Start by installing pieces that you want to use in your automations": "Start by installing pieces that you want to use in your automations", + "Piece Name": "Piece Name", + "Hide this piece from all projects": "Hide this piece from all projects", + "Show this piece for all projects": "Show this piece for all projects", + "Unpin this piece": "Unpin this piece", + "Pin this piece": "Pin this piece", + "Pieces synced": "Pieces synced", + "Pieces have been synced from the activepieces cloud.": "Pieces have been synced from the activepieces cloud.", + "OAuth2 Credentials Deleted": "OAuth2 Credentials Deleted", + "OAuth2 Credentials Updated": "OAuth2 Credentials Updated", + "Configure OAuth2 APP": "Configure OAuth2 APP", + "Delete OAuth2 APP": "Delete OAuth2 APP", + "Templates deleted successfully": "Templates deleted successfully", + "Delete Templates": "Delete Templates", + "Are you sure you want to delete the selected templates?": "Are you sure you want to delete the selected templates?", + "New Template": "New Template", + "Unlock Templates": "Unlock Templates", + "Convert the most common automations into reusable templates 1 click away from your users": "Convert the most common automations into reusable templates 1 click away from your users", + "Convert the most common automations into reusable templates": "Convert the most common automations into reusable templates", + "No templates found": "No templates found", + "Create a template for your user to inspire them": "Create a template for your user to inspire them", + "Edit template": "Edit template", + "Template is required": "Template is required", + "Update New Template": "Update New Template", + "Create New Template": "Create New Template", + "Template Name": "Template Name", + "Description": "Description", + "Template Description": "Template Description", + "Blog URL": "Blog URL", + "Template Blog URL": "Template Blog URL", + "Template": "Template", + "Invalid JSON": "Invalid JSON", + "User deleted successfully": "User deleted successfully", + "User activated successfully": "User activated successfully", + "User deactivated successfully": "User deactivated successfully", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Manage your users and their access to your projects", + "Start inviting users to your project": "Start inviting users to your project", + "External Id": "External Id", + "Admin": "Admin", + "Member": "Member", + "Activated": "Activated", + "Deactivated": "Deactivated", + "Edit user": "Edit user", + "Admin cannot be deactivated": "Admin cannot be deactivated", + "Deactivate user": "Deactivate user", + "Activate user": "Activate user", + "Delete User": "Delete User", + "Are you sure you want to delete this user?": "Are you sure you want to delete this user?", + "Delete user": "Delete user", + "Update User Role": "Update User Role", + "Meeting Summary Flow": "Meeting Summary Flow", + "Added new features and fixed bugs": "Added new features and fixed bugs", + "Flows Changes": "Flows Changes", + "Connections Changes": "Connections Changes", + "New connections are placeholders and need to be reconnected again": "New connections are placeholders and need to be reconnected again", + "renamed to": "renamed to", + "Tables Changes": "Tables Changes", + "No changes to apply": "No changes to apply", + "Apply Changes": "Apply Changes", + "Create Git Release": "Create Git Release", + "Create Project Release": "Create Project Release", + "Create Rollback to": "Create Rollback to", + "Source": "Source", + "Rollback": "Rollback", + "Imported At": "Imported At", + "Imported By": "Imported By", + "Track and manage your project version history and deployments. ": "Track and manage your project version history and deployments. ", + "Environments & Releases": "Environments & Releases", + "Project Releases": "Project Releases", + "Create Release": "Create Release", + "From Git": "From Git", + "From Project": "From Project", + "No project releases found": "No project releases found", + "Create a project release to get started": "Create a project release to get started", + "Please select project": "Please select project", + "No Changes Found": "No Changes Found", + "There are no differences to apply": "There are no differences to apply", + "Please select a project": "Please select a project", + "Search projects...": "Search projects...", + "Review Changes": "Review Changes", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Imported by", + "from": "from", + "No description provided": "No description provided", + "Invitation only sign up": "Invitation only sign up", + "Please ask your administrator to add you to the organization.": "Please ask your administrator to add you to the organization.", + "Something went wrong, please try again.": "Something went wrong, please try again.", + "Please try again.": "Please try again.", + "Please enter a valid email address": "Please enter a valid email address", + "The email is already added.": "The email is already added.", + "Add email": "Add email", + "Only project admins can do this": "Only project admins can do this", + "Add Alert Email": "Add Alert Email", + "Enter the email address to receive alerts.": "Enter the email address to receive alerts.", + "Add Email": "Add Email", + "Emails": "Emails", + "Add email addresses to receive alerts.": "Add email addresses to receive alerts.", + "No emails added yet.": "No emails added yet.", + "Choose what you want to be notified about.": "Choose what you want to be notified about.", + "Project and alert permissions are required to change this setting.": "Project and alert permissions are required to change this setting.", + "Every Failed Run": "Every Failed Run", + "Get an email alert when a flow fails.": "Get an email alert when a flow fails.", + "Get an email alert when a new issue created.": "Get an email alert when a new issue created.", + "Never": "Never", + "Turn off email notifications.": "Turn off email notifications.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Customize the appearance of the app. Automatically switch between day and night themes.", + "Select the theme for the dashboard.": "Select the theme for the dashboard.", + "Light": "Light", + "Dark": "Dark", + "Select the language that will be used in the dashboard.": "Select the language that will be used in the dashboard.", + "Select language": "Select language", + "Search language...": "Search language...", + "No language found.": "No language found.", + "Help us translate Activepieces to your language.": "Help us translate Activepieces to your language.", + "Learn more": "Learn more", + "Git Connection Removed": "Git Connection Removed", + "Your Git repository has been successfully disconnected": "Your Git repository has been successfully disconnected", + "Enable Environments": "Enable Environments", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Deploy flows across development, staging and production environments with version control and team collaboration", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Connect to Git to enable version control, backup your flows, and manage multiple environments. ", + "Repository URL": "Repository URL", + "Not connected": "Not connected", + "Project Folder": "Project Folder", + "Releases Enabled": "Releases Enabled", + "You have successfully enabled releases": "You have successfully enabled releases", + "Enable releases to easily create and manage project releases.": "Enable releases to easily create and manage project releases.", + "The external ID is already taken.": "The external ID is already taken.", + "Manage general settings for your project.": "Manage general settings for your project.", + "Used to identify the project based on your SaaS ID": "Used to identify the project based on your SaaS ID", + "org-3412321": "org-3412321", + "Delete {name}": "Delete {name}", + "This will permanently delete this piece, all steps using it will fail.": "This will permanently delete this piece, all steps using it will fail.", + "Add a piece to your project that you want to use in your automations": "Add a piece to your project that you want to use in your automations", + "Pieces list updated": "Pieces list updated", + "Manage Pieces": "Manage Pieces", + "Choose which pieces you want to be available for your current project users": "Choose which pieces you want to be available for your current project users", + "Unlock Team Permissions": "Unlock Team Permissions", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial", + "Project Members": "Project Members", + "Invite your team members to collaborate.": "Invite your team members to collaborate.", + "No members are added to this project.": "No members are added to this project.", + "Pending Invitations": "Pending Invitations", + "No pending invitation.": "No pending invitation.", + "templateId is missing": "templateId is missing", + "Me Only": "Me Only", + "Unresolved": "Unresolved", + "Resolved": "Resolved", + "Title": "Title", + "Date Created": "Date Created", + "Manage todos for your project that are created by automations": "Manage todos for your project that are created by automations", + "No todos found": "No todos found", + "You do not have any pending todos. Great job!": "You do not have any pending todos. Great job!", + "Write a comment...": "Write a comment...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "This feature is still under testing and might be changed often", + "Failed to copy to clipboard": "Failed to copy to clipboard", + "{number} items selected": "{number} items selected", + "Select All": "Select All", + "No results found.": "No results found.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect must be used within MultiSelectProvider", + "Unset": "Unset", + "Refresh": "Refresh", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} more", + "Removed {entityName}": "Removed {entityName}", + "Download File": "Download File", + "Copied to clipboard": "Copied to clipboard", + "File is not available after execution.": "File is not available after execution.", + "Available for Projects": "Available for Projects", + "Select projects": "Select projects", + "No items": "No items", + "Previous": "Previous", + "to": "to", + "Last Week": "Last Week", + "Last Month": "Last Month", + "Last 3 Months": "Last 3 Months", + "Last 6 Months": "Last 6 Months", + "Next 7 days": "Next 7 days", + "Next 30 days": "Next 30 days", + "Next 90 days": "Next 90 days", + "Next 180 days": "Next 180 days", + "Select Time Range": "Select Time Range", + "Clear": "Clear", + "Download": "Download", + "Go to Dashboard": "Go to Dashboard", + "Select a file": "Select a file", + "Press space to separate values": "Press space to separate values", + "AM": "AM", + "PM": "PM", + "Already have an account?": "Already have an account?", + "Sign in": "Sign in", + "Don't have an account?": "Don't have an account?", + "Sign up": "Sign up", + "Welcome Back!": "Welcome Back!", + "Enter your email below to sign in to your account": "Enter your email below to sign in to your account", + "Let's Get Started!": "Let's Get Started!", + "Create your account and start flowing!": "Create your account and start flowing!", + "Your password was changed successfully": "Your password was changed successfully", + "Your password reset request has expired, please request a new one": "Your password reset request has expired, please request a new one", + "Reset Password": "Reset Password", + "Enter your new password": "Enter your new password", + "Password is required": "Password is required", + "Verification email resent, if previous one expired.": "Verification email resent, if previous one expired.", + "Password reset link resent, if previous one expired.": "Password reset link resent, if previous one expired.", + "We sent you a link to complete your registration to": "We sent you a link to complete your registration to", + "We sent you a link to reset your password to": "We sent you a link to reset your password to", + "Didn't receive an email or it expired?": "Didn't receive an email or it expired?", + "Resend": "Resend", + "Please enter your email": "Please enter your email", + "Check Your Inbox": "Check Your Inbox", + "If the user exists we'll send you an email with a link to reset your password.": "If the user exists we'll send you an email with a link to reset your password.", + "Send Password Reset Link": "Send Password Reset Link", + "Back to sign in": "Back to sign in", + "Email is invalid": "Email is invalid", + "Something went wrong, please try again later": "Something went wrong, please try again later", + "Invalid email or password": "Invalid email or password", + "User has been deactivated": "User has been deactivated", + "Email domain is disallowed": "Email domain is disallowed", + "Email authentication has been disabled": "Email authentication has been disabled", + "Forgot your password?": "Forgot your password?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "Sign up is restricted. You need an invitation to join. Please contact the administrator.", + "Email is already used": "Email is already used", + "Email authentication is disabled": "Email authentication is disabled", + "First name is required": "First name is required", + "Last name is required": "Last name is required", + "Email is required": "Email is required", + "Receive updates and newsletters from activepieces": "Receive updates and newsletters from activepieces", + "By creating an account, you agree to our": "By creating an account, you agree to our", + "terms of service": "terms of service", + "privacy policy": "privacy policy", + "Sign up With": "Sign up With", + "Google": "Google", + "Sign in With": "Sign in With", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "Email has been verified. You will be redirected to sign in...", + "Verifying email...": "Verifying email...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "invitation has expired, once you sign in again you will be able to resend the verification email.", + "Redirecting to sign in...": "Redirecting to sign in...", + "Password must contain at least one special character": "Password must contain at least one special character", + "Password must contain at least one lowercase letter": "Password must contain at least one lowercase letter", + "Password must contain at least one uppercase letter": "Password must contain at least one uppercase letter", + "Password must contain at least one number": "Password must contain at least one number", + "8-64 Characters": "8-64 Characters", + "Special Character": "Special Character", + "Lowercase": "Lowercase", + "Uppercase": "Uppercase", + "Number": "Number", + "Connection has been updated.": "Connection has been updated.", + "Edit Global Connection": "Edit Global Connection", + "Connection has been renamed.": "Connection has been renamed.", + "New Connection Name": "New Connection Name", + "Connection name already used": "Connection name already used", + "Please select at least one project": "Please select at least one project", + "Run Succeeded": "Run Succeeded", + "Run Failed": "Run Failed", + "Flow Run is paused": "Flow Run is paused", + "Run Failed due to quota exceeded": "Run Failed due to quota exceeded", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Run failed due to exceeding the memory limit of {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "Run exceeded {timeout} seconds, try to optimize your steps.", + "Run failed for an unknown reason, contact support.": "Run failed for an unknown reason, contact support.", + "Unknown": "Unknown", + "Exit Run": "Exit Run", + "Select shown": "Select shown", + "Select all": "Select all", + "Start Time": "Start Time", + "Runs replayed successfully": "Runs replayed successfully", + "Retry": "Retry", + "all except": "all except", + "all": "all", + "Only failed runs can be retried from failed step": "Only failed runs can be retried from failed step", + "No flow runs found": "No flow runs found", + "Come back later when your automations start running": "Come back later when your automations start running", + "Step running": "Step running", + "Step paused": "Step paused", + "Step Stopped": "Step Stopped", + "Step Succeeded": "Step Succeeded", + "Step Failed": "Step Failed", + "Please publish flow first": "Please publish flow first", + "Flow is on": "Flow is on", + "Flow is off": "Flow is off", + "Permission Needed": "Permission Needed", + "Draft Version": "Draft Version", + "Published Version": "Published Version", + "Locked Version": "Locked Version", + "flowsImported": "{flowsCount, plural, =0 {No Flows Imported} =1 {Flow Imported Successfully} other {Flows Imported Successfully}}", + "Template file is invalid": "Template file is invalid", + "No valid templates found. The following files failed to import: ": "No valid templates found. The following files failed to import: ", + "Please select a file first": "Please select a file first", + "Unsupported file type": "Unsupported file type", + "Import Flow": "Import Flow", + "Warning": "Warning", + "Importing a flow will overwrite your current one.": "Importing a flow will overwrite your current one.", + "Select a folder": "Select a folder", + "Folders": "Folders", + "Please select a folder": "Please select a folder", + "Moved flows successfully": "Moved flows successfully", + "Move Selected Flows": "Move Selected Flows", + "Select Folder": "Select Folder", + "No Folders": "No Folders", + "Flow has been renamed.": "Flow has been renamed.", + "New Flow Name": "New Flow Name", + "Use Template": "Use Template", + "Browse Templates": "Browse Templates", + "Search templates": "Search templates", + "No templates found, try adjusting your search": "No templates found, try adjusting your search", + "Read more about this template in": "Read more about this template in", + "this blog!": "this blog!", + "Share Template": "Share Template", + "Generate or update a template link for the current flow to easily share it with others.": "Generate or update a template link for the current flow to easily share it with others.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "The template will not have any credentials in connection fields, keeping sensitive information secure.", + "A short description of the template": "A short description of the template", + "Flow Is In Use": "Flow Is In Use", + "Flow is being used by another user, please try again later.": "Flow is being used by another user, please try again later.", + "Flow has been published.": "Flow has been published.", + "Flows have been exported.": "Flows have been exported.", + "Run": "Run", + "Real time flow": "Real time flow", + "Flow can't be published with empty trigger {name}": "Flow can't be published with empty trigger {name}", + "Please contact support as your published flow has a problem": "Please contact support as your published flow has a problem", + "Actions": "Actions", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.", + "You are on a development branch, this will not delete the flows from the remote repository.": "You are on a development branch, this will not delete the flows from the remote repository.", + "Please enter folder name": "Please enter folder name", + "Added folder successfully": "Added folder successfully", + "The folder name already exists.": "The folder name already exists.", + "New Folder": "New Folder", + "Folder Name": "Folder Name", + "Loading...": "Loading...", + "Delete {folderName}": "Delete {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "If you delete this folder, we will keep its flows and move them to Uncategorized.", + "All flows": "All flows", + "Please enter a folder name": "Please enter a folder name", + "Renamed flow successfully": "Renamed flow successfully", + "Folder name already used": "Folder name already used", + "New Folder Name": "New Folder Name", + "Connected successfully": "Connected successfully", + "Connect Git": "Connect Git", + "Remote URL": "Remote URL", + "Folder name is the name of the folder where the project will be stored or fetched.": "Folder name is the name of the folder where the project will be stored or fetched.", + "SSH Private Key": "SSH Private Key", + "The SSH private key to use for authentication.": "The SSH private key to use for authentication.", + "Only published flows can be pushed to Git": "Only published flows can be pushed to Git", + "Pushed successfully": "Pushed successfully", + "Invalid Operation": "Invalid Operation", + "Commit Message": "Commit Message", + "Enter a commit message to describe the changes you want to push.": "Enter a commit message to describe the changes you want to push.", + "Push": "Push", + "This field is required": "This field is required", + "Your submission was successfully received.": "Your submission was successfully received.", + "Flow not found": "Flow not found", + "The flow you are trying to submit to does not exist.": "The flow you are trying to submit to does not exist.", + "The flow failed to execute.": "The flow failed to execute.", + "Submit": "Submit", + "issues-notification": "issues-notification", + "Please select a package type": "Please select a package type", + "package.json not found in archive": "package.json not found in archive", + "Error processing archive file": "Error processing archive file", + "Please upload a .tgz file": "Please upload a .tgz file", + "Piece name is required for NPM Registry": "Piece name is required for NPM Registry", + "Piece version is required for NPM Registry": "Piece version is required for NPM Registry", + "Piece installed": "Piece installed", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "A piece with this name and version is already installed. Please update the version number in package.json and try again.", + "Install Piece": "Install Piece", + "Install a piece": "Install a piece", + "Package Type": "Package Type", + "NPM Registry": "NPM Registry", + "Packed Archive (.tgz)": "Packed Archive (.tgz)", + "Piece Version": "Piece Version", + "Package Archive": "Package Archive", + "Package archive": "Package archive", + "Powerful Node.js & TypeScript code with npm": "Powerful Node.js & TypeScript code with npm", + "Loop on Items": "Loop on Items", + "Split your flow into branches depending on condition(s)": "Split your flow into branches depending on condition(s)", + "Empty Trigger": "Empty Trigger", + "An internal error occurred while fetching data, please contact support": "An internal error occurred while fetching data, please contact support", + "An internal error occurred, please contact support": "An internal error occurred, please contact support", + "Custom Javascript Code": "Custom Javascript Code", + "Router": "Router", + "recordsCount": "{recordsCount, plural, =0 {0 Records} =1 {1 Record} other {# Records}}", + "selected": "selected", + "All records selected": "All records selected", + "fieldsCount": "{fieldsCount, plural, =0 {0 Fields} =1 {1 Field} other {# Fields}}", + "Saving...": "Saving...", + "Loading more...": "Loading more...", + "Export Table": "Export Table", + "Delete Records": "Delete Records", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Are you sure you want to delete the selected records? This action cannot be undone.", + "record": "record", + "records": "records", + "mm/dd/yyy": "mm/dd/yyy", + "Delete Field": "Delete Field", + "Are you sure you want to delete this field? This action cannot be undone.": "Are you sure you want to delete this field? This action cannot be undone.", + "field": "field", + "Ignored": "Ignored", + "Table": "Table", + "CSV": "CSV", + "Field": "Field", + "Please select a csv file": "Please select a csv file", + "Max file size is {maxFileSize}MB": "Max file size is {maxFileSize}MB", + "Import CSV": "Import CSV", + "Imported records will be added to the bottom of the table": "Imported records will be added to the bottom of the table", + "Any records after the limit ({maxRecords} records) will be ignored": "Any records after the limit ({maxRecords} records) will be ignored", + "CSV File": "CSV File", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support", + "Name must be unique": "Name must be unique", + "Type is required": "Type is required", + "Please add at least one option": "Please add at least one option", + "New Field": "New Field", + "Options": "Options", + "Name is already taken": "Name is already taken", + "Table renamed": "Table renamed", + "Table name": "Table name", + "Team Invitation Accepted": "Team Invitation Accepted", + "Thank you for accepting the invitation. We are redirecting you right now...": "Thank you for accepting the invitation. We are redirecting you right now...", + "Invalid invitation token. Please try again.": "Invalid invitation token. Please try again.", + "Role updated successfully": "Role updated successfully", + "Error updating role": "Error updating role", + "Please try again later": "Please try again later", + "Edit Role for": "Edit Role for", + "Select Role": "Select Role", + "Avatar": "Avatar", + "Remove {email}": "Remove {email}", + "Are you sure you want to remove this invitation?": "Are you sure you want to remove this invitation?", + "Please select invitation type": "Please select invitation type", + "Please select platform role": "Please select platform role", + "Invitation sent successfully": "Invitation sent successfully", + "Please select a project role": "Please select a project role", + "Invitation link copied successfully": "Invitation link copied successfully", + "Invite User": "Invite User", + "Invitation Link": "Invitation Link", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Type the email address of the user you want to invite, the invitation expires in 24 hours.", + "Invite To": "Invite To", + "Entire Platform": "Entire Platform", + "Select Project Role": "Select Project Role", + "Invite": "Invite", + "Platform Role": "Platform Role", + "Select a platform role": "Select a platform role", + "Are you sure you want to remove this member?": "Are you sure you want to remove this member?", + "Editor": "Editor", + "Operator": "Operator", + "Viewer": "Viewer", + "Select a project role": "Select a project role", + "Steps in this flow": "Steps in this flow", + "Invalid Access": "Invalid Access", + "Either the project does not exist or you do not have access to it.": "Either the project does not exist or you do not have access to it." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/pt/translation.json b/packages/react-ui/public/locales/pt/translation.json new file mode 100644 index 0000000..785b833 --- /dev/null +++ b/packages/react-ui/public/locales/pt/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Publicar", + "Latest version is published": "A última versão está publicada", + "Your flow has incomplete steps": "Seu fluxo tem passos incompletos", + "Edit Flow": "Editar Fluxo", + "View Draft": "Ver Rascunho", + "Uncategorized": "Sem categoria", + "Go to folder": "Ir para a pasta", + "Support": "Suporte", + "Runs": "Execuções", + "Run Logs": "Executar Logs", + "Versions": "Versões", + "Versions History": "Histórico de Versões", + "Error generating code": "Erro ao gerar código", + "AI Copilot": "Copiloto de IA", + "i.e Calculate the sum of a list...": "i.e., Calcule a soma de uma lista...", + "Send": "Enviar", + "Generating Code": "Generating code:", + "Hello there! I am here to generate code that helps with your flow": "Olá! Estou aqui para gerar um código que ajuda com o seu fluxo", + "Here are examples of what I am best used for: ": "Aqui estão exemplos do que eu sou mais usado para: ", + "Text Processing": "Processamento de Texto", + "Process strings, dates and data": "Processando sequências de caracteres, datas e dados", + "Data Operations": "Operações de Dados", + "Change data from one format to another": "Alterar dados de um formato para outro", + "Calculations": "Cálculos", + "Handle math and statistics": "Tratar matemática e estatísticas", + "API Integration": "Integração de API", + "Connect with external services. Best for simple integrations currently.": "Conecte-se a serviços externos. O melhor para integrações simples atualmente.", + "What would you like me to help you with?": "No que você gostaria que eu te ajudasse?", + "Insert": "Inserir", + "Data Selector": "Seletor de Dados", + "Search": "Pesquisar", + "No matching data": "Sem dados correspondentes", + "Try adjusting your search": "Tente ajustar sua busca", + "This trigger needs to have data loaded from your account, to use as sample data.": "Este disparador precisa ter dados da sua conta carregados, para usá-los como exemplo.", + "This step needs to be tested in order to view its data.": "Este passo precisa ser testado para se visualizar seus dados.", + "Go to Trigger": "Ir para Disparador", + "Go to Step": "Ir para o Passo", + "Select Mode": "Selecione o Modo", + "Move Mode": "Modo de Movimento", + "Reset Zoom": "Redefinir Zoom", + "Zoom In": "Aproximar", + "Zoom Out": "Afastar", + "Fit to View": "Ajustar à Área de Visualização", + "Replace": "Substituir", + "Copy": "Copiar", + "Duplicate": "Duplicar", + "Paste After Last Step": "Colar Após o Último Passo", + "Paste Inside Loop": "Colar dentro do loop", + "Paste After": "Colar Depois", + "Paste Inside...": "Colar dentro...", + "New Branch": "Nova filial", + "Paste Inside Branch": "Colar dentro do Branch", + "Delete": "Apagar", + "Duplicate Branch": "Duplicar filial", + "Delete Branch": "Excluir branch", + "Invalid Move": "Movimento Inválido", + "The destination location is a child of the dragged step": "O local de destino é filho do passo arrastado", + "End": "Finalizar", + "Skipped": "Ignorado", + "Incomplete settings": "Configurações incompletas", + "logo": "logotipo", + "Step Icon": "Ícone do passo", + "Branch": "Ramificação", + "incompleteSteps": "{invalidSteps, plural, =0 {Não há passos incompletos} =1 {Complete 1 passo} other {Complete # passos}}", + "Test Flow": "Fluxo de teste", + "Please test the trigger first": "Por favor, teste o disparador primeiro", + "View Only": "Apenas visualizar", + "Use as Draft": "Usar como Rascunho", + "Are you sure?": "Você tem certeza?", + "Your current draft version will be overwritten with": "A sua versão atual de rascunho será substituída por", + "version #": "# da versão", + "Cancel": "Cancelar", + "Confirm": "Confirmar", + "Version": "Versão", + "Viewing": "Visualizando", + "View": "Visualizar", + "Version History": "Histórico de versões", + "Error, please try again.": "Ocorreu um erro, por favor tente novamente.", + "Continue on Failure": "Continuar ao falhar", + "Enable this option to skip this step and continue the flow normally if it fails.": "Habilite esta opção para pular este passo e continuar o fluxo normalmente se ele falhar.", + "Retry on Failure": "Tentar novamente ao falhar", + "Automatically retry up to four attempts when failed.": "Repetir automaticamente até quatro tentativas quando falhar.", + "Remove": "Remover", + "Add Item": "Adicionar Item", + "File Input": "Entrada de arquivo", + "Date Input": "Date Input", + "Dynamic value": "Valor dinâmico", + "Select an option": "Selecionar uma opção", + "Unexpected error, please retry": "Erro inesperado, por favor tente novamente", + "Unexpected error, please refresh the page or contact support": "Erro inesperado, por favor atualize a página ou contacte o suporte", + "Name can only contain letters, numbers and underscores": "O nome só pode conter letras, números e sublinhados", + "Ask AI": "Perguntar à IA", + "Create Todo Guide": "Criar Guia de Tarefas", + "Where would you like the todo to be reviewed?": "Onde você gostaria que a tarefa fosse revisada?", + "Activepieces Todos": "Peças ativas", + "Users will manage tasks directly in Activepieces": "Os usuários gerenciarão as tarefas diretamente em peças ativas", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Os usuários gerenciarão e responderão a tarefas diretamente dentro da interface de peças ativas. Ideal para equipes internas.", + "External Channel (Slack, Teams, Email, ...)": "Canal Externo (Slack, Teams, E-mail, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Enviar notificações com links de aprovação através de canais externos, como Slack, Equipes ou E-mail. Melhor para colaborar com partes interessadas externas.", + "Preview (Activepieces Todos)": "Pré-visualização (Ativepieces Todos)", + "Preview (External channel)": "Pré-visualizar (canal externo)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "O Activepieces Todo permite que os usuários revisem e resolvam tarefas diretamente na interface Activepieces", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "Você pode adicionar o canal antes do Passo de Espera e configurar a lógica na etapa do roteador", + "Add Steps": "Adicionar passos", + "All": "TODOS", + "AI": "AI", + "Core": "Principal", + "Apps": "Aplicativos", + "Not available as trigger": "Não disponível como gatilho", + "Not available as action": "Não disponível como ação", + "Let our AI assistant help you out": "Deixe o nosso assistente de IA ajudar você", + "Or": "ou", + "Request Piece": "Pedir peça", + "No pieces found": "Nenhuma peça encontrada", + "Please select a piece first": "Por favor, selecione um pedaço primeiro", + "All Iterations": "Todas as Iterações", + "Duration": "Duração", + "Input": "Entrada", + "Output": "Saída", + "There are no logs captured for this run.": "Não há logs capturados para esta corrida.", + "Logs are kept for {days} days after execution and then deleted.": "Os logs são mantidos por {days} dias após a execução e depois são excluídos.", + "Run Details": "Executar Detalhes", + "Iteration": "Interação", + "Done": "Concluído", + "Took": "OK", + "Running": "Executando", + "on latest version": "na versão mais recente", + "from failed step": "do passo que falhou", + "Recent Runs": "Execuções Recentes", + "No runs found": "Nenhuma execução encontrada", + "Close": "Fechar", + "OR": "OU", + "And If": "E Se", + "+ And": "+ E", + "+ Or": "+ Ou", + "(Text) Contains": "(Texto) Contém", + "(Text) Does not contain": "(Texto) Não contém", + "(Text) Exactly matches": "(Texto) Corresponde exatamente", + "(Text) Does not exactly match": "(Texto) Não corresponde exatamente", + "(Text) Starts with": "(Texto) Inicia com", + "(Text) Does not start with": "(Texto) Não inicia com", + "(Text) Ends with": "(Texto) Termina com", + "(Text) Does not end with": "(Texto) Não termina com", + "(List) Contains": "(Negro) Contém", + "(List) Does not contain": "(Listão) Não contém", + "(Number) Is greater than": "(Número) É maior que", + "(Number) Is less than": "(Número) É menor que", + "(Number) Is equal to": "(Número) É igual a", + "(Date/time) After": "(Date/time) Depois", + "(Date/time) Before": "(Date/time) Antes", + "(Date/time) Equals": "(Date/tempo) Igual", + "(Boolean) Is true": "(booleano) É verdadeiro", + "(Boolean) Is false": "(Booleano) É falso", + "(List) Is empty": "(Listão) Está vazio", + "(List) Is not empty": "(Listão) Não está vazio", + "Exists": "Existe", + "Does not exist": "Não existe", + "Incomplete condition": "Condição incompleta", + "First value": "Primeiro valor", + "Second value": "Segundo valor", + "Case sensitive": "Diferenciar Maiúsculas e Minúsculas", + "Execute If": "Executar se", + "The package name is required": "O nome do pacote é obrigatório", + "Success": "Bem-sucedido", + "Package added successfully": "Pacote adicionado com sucesso", + "Could not fetch package version": "Não foi possível obter a versão do pacote", + "Add NPM Package": "Adicionar Pacote NPM", + "Type the name of the npm package you want to add.": "Digite o nome do pacote npm que você deseja adicionar.", + "Package Name": "Nome do Pacote", + "The latest version will be fetched and added": "A versão mais recente será obtida e adicionada", + "Add": "Adicionar", + "Code": "Código", + "Dependencies": "Dependências", + "Use code": "Use o código", + "Add package": "Adicionar pacote", + "Inputs": "Entradas", + "Edit Step Name": "Editar Nome do Passo", + "Edit Branch Name": "Editar nome de filial", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Selecione os itens a serem iterados no passo anterior clicando no input 'Itens', que deve ser uma Lista de itens.\n\nO loop irá iterar sobre cada item na lista e executará a próxima etapa para cada item.", + "Items": "Itens", + "Select an array of items": "Selecione uma array de itens", + "Reconnect": "Reconectar", + "Select a connection": "Selecione uma conexão", + "Create Connection": "Criar Conexão", + "Rename": "Renomear", + "Move": "Mover", + "Add Branch": "Adicionar filial", + "Execute": "Executar", + "Only the first (left) matching branch": "Apenas o primeiro (restante) branch correspondente", + "All matching paths from left to right": "Todos os caminhos correspondentes da esquerda para a direita", + "Branches": "Filiais", + "{field} is required": "{field} é necessário", + "Tool Sample Data": "Dados de exemplo de ferramenta", + "Fill in the following fields to use them as sample data for the trigger.": "Preencha os seguintes campos para usá-los como dados de exemplo para o gatilho.", + "No input fields defined in the schema": "Nenhum campo de entrada definido no esquema", + "Save": "Salvar", + "Test Environment": "Ambiente de teste", + "Assigned to": "Atribuído a", + "(Me)": "(Eu)", + "Please select status to resolve the todo": "Por favor, selecione o status para resolver a tarefa", + "Resolve": "Resolver", + "Change status to resolved": "Alterar status para resolvido", + "Send Sample Data to Webhook": "Enviar dados de exemplo para o Webhook", + "Method": "Método", + "Query Params": "Parâmetros de consulta", + "Headers": "Cabeçalhos", + "Body": "Conteúdo", + "Type": "tipo", + "JSON": "JSON", + "Text": "texto", + "Form Data": "Dados de Formulário", + "Generate Sample Data": "Gerar Dados de Exemplo", + "Test Step": "Testar Passo", + "Retest": "Retestar", + "Testing Failed": "Falha no Teste", + "Tested Successfully": "Teste bem-sucedido", + "Logs": "Registros", + "There is no sample data available found for this trigger.": "Não há dados de amostragem disponíveis para este gatilho.", + "Internal error, please try again later.": "Erro interno, por favor, tente novamente mais tarde.", + "Failed to run test step and no error message was returned": "Falha ao executar o passo de teste e nenhuma mensagem de erro foi retornada", + "Please fix inputs first": "Por favor, corrija as entradas primeiro", + "No sample data available": "Nenhum dado de amostra disponível", + "Old results were removed, retest for new sample data": "Os antigos resultados foram removidos, retome os novos dados de exemplo", + "Result #": "Resultado #", + "The sample data can be used in the next steps.": "Os dados de exemplo podem ser usados nos passos seguintes.", + "Testing Trigger": "Testando Gatilho", + "Action Required": "Ação necessária", + "testPieceWebhookTriggerNote": "Por favor, vá para {pieceName} e gatilho {triggerName}.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Enviar dados para o URL do webhook para gerar dados de exemplo a serem usados nos próximos passos", + "Test Trigger": "Teste de Gatilho", + "Use Mock Data": "Usar dados fictícios", + "Load Sample Data": "Carregar dados de exemplo", + "Test Tool": "Ferramenta de teste", + "home": "início", + "Home": "Página Inicial", + "Alerts": "Alertas", + "Releases": "Versões", + "Flows": "Fluxos", + "Products": "produtos", + "MCP": "MCP", + "Tables": "Tabelas", + "Todos": "Todos", + "Push to Git": "Enviar para o Git", + "Move To": "Mover Para", + "Duplicating": "Duplicando", + "Import": "Importar", + "Exporting": "Exportando", + "Export": "Exportar", + "Share": "Compartilhar", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Tem certeza que deseja excluir este fluxo? Isto irá excluir permanentemente o fluxo, todos os seus dados e qualquer execução em segundo plano.", + "You are on a development branch, this will also delete the flow from the remote repository.": "Você está em um ramo de desenvolvimento, isso também vai excluir o fluxo do repositório remoto.", + "flow": "fluxo", + "Community Support": "Apoio da Comunidade", + "Overview": "Geral", + "Projects": "Projetos", + "Users": "Usuários", + "Setup": "Configuração", + "Branding": "Marca", + "Global Connections": "Conexões Globais", + "Pieces": "Peças", + "Templates": "Modelos", + "License Key": "Chave de Licença", + "Security": "Segurança", + "Audit Logs": "Logs de Auditoria", + "Single Sign On": "Logon único", + "Signing Keys": "Chaves de assinatura", + "Project Roles": "Papéis do Projeto", + "API Keys": "Chaves da API", + "Infrastructure": "Infraestrutura", + "Workers": "Trabalhadores", + "Health": "Vida", + "Billing": "Faturamento", + "Settings": "Configurações", + "Contact Sales": "Contatar Vendas", + "General": "Geral", + "Appearance": "Aparência", + "Team": "Equipe", + "Environments": "Ambientes", + "Project Settings": "Configurações do projeto", + "Exit Platform Admin": "Administrador da Plataforma de Saída", + "Enter Platform Admin": "Entrar administrador da plataforma", + "Logout": "Desconectar", + "Misc": "Diversos", + "Connections": "Conexões", + "days": "Dias", + "hours": "horas", + "minutes": "Minutos", + "Today": "hoje", + "Tasks": "Tarefas", + "AI Credits": "AI Credits", + "Usage resets in": "Redefinições de uso em", + "Manage": "Administrar", + "Unlimited": "Ilimitado", + "Enabled": "Ativado", + "Disabled": "Desabilitado", + "Could not claim the authorization code, make sure you have correct settings and try again.": "Não foi possível solicitar o código de autorização, certifique-se de estar com as configurações corretas e tente novamente.", + "Connection failed with error {msg}": "Falha na conexão com o erro {msg}", + "You don't have the permission to create a connection.": "Você não tem permissão para criar uma conexão.", + "Reconnect {displayName} Connection": "Reconnect {displayName}", + "Connect to {displayName}": "Conectar a {displayName}", + "Connection Name": "Nome da Conexão", + "Connection name": "Nome da conexão", + "New Connection": "Nova Conexão", + "Redirect URL": "URL de redirecionamento", + "Client ID": "ID do Cliente", + "Client Secret": "Chave secreta", + "Connect": "Conectar", + "Disconnect": "Desconectar", + "I would like to use my own App Credentials": "Eu gostaria de usar minhas próprias Credenciais do App", + "I would like to use predefined App Credentials": "Eu gostaria de usar Credenciais do App predefinidas", + "Permission needed": "Necessário permissão", + "Connections replaced successfully": "Conexões substituídas com sucesso", + "Error": "Erro", + "Failed to replace connections": "Falha ao substituir conexões", + "Failed to get affected flows": "Falha ao obter os fluxos afetados", + "Please select a piece": "Por favor, selecione um pedaço", + "Please select a connection to replace": "Por favor, selecione uma conexão para substituir", + "Please select a connection to replace with": "Por favor, selecione uma conexão para substituir com", + "Replace Connections": "Substituir conexões", + "Confirm Replacement": "Confirmar a substituição", + "Replace one connection with another.": "Substitua uma conexão com outra.", + "This action requires ": "Esta ação requer ", + "reconnecting": "reconectando", + " any associated MCP pieces.": " quaisquer peças MCP associadas.", + "Piece": "Peça", + "Select a piece": "Selecione um pedaço", + "Connection to Replace": "Conexão para substituir", + "Choose connection to replace": "Escolher conexão para substituir", + "Replaced With": "Substituído com", + "Choose connection to replace with": "Escolha a conexão para substituir por", + "All flows will be changed to use the replaced with connection": "Todos os fluxos serão alterados para usar a substituição com conexão", + "Next": "Próximo", + "No flows will be affected by this change": "Nenhum fluxo será afetado por esta alteração", + "Back": "Anterior", + "Unnamed tool": "Ferramenta sem nome", + "This flow is enabled": "Este fluxo está ativado", + "Enable this flow to make it available": "Habilite este fluxo para torná-lo disponível", + "Piece is updated successfully": "Pedaço atualizado com sucesso", + "Piece is added successfully": "Pedaço adicionado com sucesso", + "Failed to update piece": "Falha ao atualizar a peça", + "Failed to add piece": "Falha ao adicionar um pedaço", + "Please select a connection": "Por favor, selecione uma conexão", + "Your MCP server already has this piece": "Seu servidor MCP já possui esta peça", + "+ New Connection": "+ Nova conexão", + "Edit Piece": "Editar Pedaço", + "Add Piece": "Adicionar Pedaços", + "Connection": "Conexão", + "MCP piece": "peça MCP", + "Failed to update piece status": "Não foi possível atualizar o status do pedaço", + "Connection required": "Conexão necessária", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Tem certeza que deseja excluir esta ferramenta do seu MCP? se você excluí-lo você não poderá usá-lo em seu cliente MCP.", + "piece": "pedaço", + "Connect your AI assistant to external services": "Conecte seu assistente de IA a serviços externos", + "Collapse": "Recolher", + "Show All": "Mostrar todos", + "Server URL": "URL do servidor", + "Hide the token for security": "Ocultar o token de segurança", + "Show the token": "Mostrar o token", + "Generate a new token for security. This will invalidate the current URL.": "Gerar um novo token para segurança. Isto invalidará o URL atual.", + "URL copied to clipboard": "URL copiado para área de transferência", + "Copy URL to clipboard": "Copiar URL para área de transferência", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "Esta URL contém um token de segurança confidencial. Compartilhe-o apenas com aplicativos e serviços confiáveis. Rotacione o token se suspeitar que ele foi comprometido.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "Depois de fazer quaisquer alterações em conexões ou fluxos, você precisará reconectar o seu servidor MCP para que as alterações tenham efeito.", + "Max tables reached": "Máximo de tabelas alcançadas", + "You can't create more than {maxTables} tables": "Você não pode criar mais do que tabelas {maxTables}", + "Name": "Nome", + "Created": "Criado", + "Delete Tables": "Excluir Tabelas", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Você tem certeza que deseja excluir as tabelas selecionadas? Esta ação não pode ser desfeita.", + "table": "Tabela", + "Create and manage your tables to store your automation data": "Criar e gerenciar suas tabelas para armazenar seus dados de automação", + "New Table": "Nova Tabela", + "No tables have been created yet": "Nenhuma tabela foi criada ainda", + "Create a table to get started and start managing your automation data": "Crie uma tabela para começar e comece a gerenciar seus dados de automação", + "Error deleting connections": "Erro ao excluir conexões", + "Status": "Estado", + "Display Name": "Nome de Exibição", + "Owner": "Proprietário", + "App": "Aplicativo", + "This connection is global and can be managed in the platform admin": "Essa conexão é global e pode ser gerenciada no administrador da plataforma", + "External ID": "ID Externo", + "Connected At": "Conectado em", + "Confirm Deletion": "Confirmar Exclusão", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Você tem certeza que deseja excluir as conexões selecionadas? Esta ação não pode ser desfeita.", + "Deleting connections may cause your Flows or MCP tools to break.": "Excluir conexões pode fazer com que suas ferramentas MCP quebrem.", + "Manage project connections to external systems.": "Gerenciar conexões de projetos para sistemas externos.", + "No connections found": "Nenhuma conexão encontrada", + "Come back later when you create a automation to manage your connections": "Volte mais tarde, quando você criar uma automação para gerenciar suas conexões", + "Steps": "Passos", + "Folder": "Pasta", + "Flow name": "Nome do fluxo", + "No flows found": "Nenhum fluxo encontrado", + "Create a workflow to start automating": "Criar um workflow para iniciar a automação", + "Create and manage your flows, run history and run issues": "Crie e gerencie seus fluxos, execute o histórico e problemas", + "Issues": "Problemas", + "Untitled": "Sem Título", + "Create flow": "Criar fluxo", + "From scratch": "A partir do zero", + "Use a template": "Usar um modelo", + "From local file": "Do arquivo local", + "Flow Name": "Nome do Fluxo", + "Count": "Contagem", + "First Seen": "Visto pela primeira vez", + "Last Seen": "Visto pela última vez", + "Issues in {flowDisplayName} is marked as resolved.": "Os problemas em {flowDisplayName} estão marcados como resolvidos.", + "Unlock Issues": "Desbloquear Problemas", + "Track issues in your workflows and troubleshoot them.": "Acompanhe problemas em seus fluxos de trabalho e resolva-os.", + "No issues found": "Nenhuma issue encontrada", + "All your workflows are running smoothly.": "Todos os seus fluxos de trabalho estão funcionando sem problemas.", + "Mark as Resolved": "Marcar como resolvido", + "All Flows Are Turned Off": "Todos os fluxos são desativados", + "Task Usage Exceeded": "Uso da tarefa excedido", + "of the Allowed Limit.": "do limite permitido.", + "When a project tasks limit is reached,": "Quando o limite de tarefas do projeto é atingido,", + "all flows will be turned off and you will not be able to run any flows.": "todos os fluxos serão desativados e você não será capaz de executar quaisquer fluxos.", + "Please visit": "Por favor, visite", + "Your Plan": "Seu plano", + "and increase your task limit, which requires your payment details.": "e aumente o seu limite de tarefas, o que requer os seus dados de pagamento.", + "Please contact your admin to increase the project task limit.": "Entre em contato com o administrador para aumentar o limite das tarefas do projeto.", + "and increase the project task limit.": "e aumente o limite das tarefas do projeto.", + "Dismiss": "Descartar", + "Token rotated successfully": "Token rotacionado com sucesso", + "Failed to rotate token": "Falha ao girar token", + "Piece removed successfully": "Pedaço removido com sucesso", + "Failed to remove piece": "Falha ao remover o pedaço", + "Flow created successfully": "Fluxo criado com sucesso", + "Failed to create flow": "Falha ao criar fluxo", + "Add Flow": "Adicionar Fluxo", + "Let your AI assistant trigger automations": "Deixe seu assistente de I.A. acionar automações", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Conecte-se ao seu servidor MCP hospedado usando qualquer cliente MCP para se comunicar com as ferramentas", + "MCP Server": "Servidor MCP", + "My Tools": "Minhas Ferramentas", + "Create Flow": "Criar Fluxo", + "Note": "Observação", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "Se você gostaria de expor seu servidor MCP à internet, por favor defina a variável de ambiente AP_FRONTEND_URL pública da instância de suas peças de atividade.", + "This URL grants access to your tools and data. Only share with trusted applications.": "Essa URL concede acesso a suas ferramentas e dados. Só compartilha com aplicativos confiáveis.", + "Server Configuration": "Configuração Servidor", + "Hide sensitive data": "Ocultar dados confidenciais", + "Show sensitive data": "Mostrar dados confidenciais", + "Create a new URL. The current one will stop working.": "Criar um novo URL. O atual deixará de funcionar.", + "Copy configuration": "Copiar configuração", + "Configuration copied to clipboard": "Configuração copiada para área de transferência", + "Claude": "Mortal", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "Servidor/Outro", + "Note: MCPs only work with": "Nota: MCPs funcionam somente com", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", não a versão web.", + "Prerequisites:": "Pré-requisitos:", + "Install": "Instalar", + "Node.js": "Node.js", + "and": "e", + "Open Settings:": "Abrir configurações:", + "Click the menu and select": "Clique no menu e selecione", + "Developer": "Desenvolvedores", + "Configure MCP:": "Configurar MCP:", + "Click": "Click", + "Edit Config": "Editar Configuração", + "and paste the configuration below": "e cole a configuração abaixo", + "Save and Restart:": "Salvar e Reiniciar:", + "Save the config and restart Claude Desktop": "Salvar as configurações e reiniciar o Claude Desktop", + "Navigate to": "Navegar para", + "Cursor Settings": "Configurações do Cursor", + "Add Server:": "Adicionar Servidor:", + "Add new global MCP server": "Adicionar novo servidor MCP global", + "Configure:": "Configurar:", + "Paste the configuration below and save": "Cole a configuração abaixo e salve", + "Use either method:": "Use qualquer método:", + "Go to": "Ir para", + "Advanced Settings": "Configurações Avançadas", + "Open Command Palette and select": "Abrir Paleta de Comando e selecionar", + "Windsurf Settings Page": "Página de Configurações Windsurf", + "Navigate to Cascade:": "Navegar para o Cascade:", + "Select": "Selecionar", + "Cascade": "Cascade", + "in the sidebar": "na barra lateral", + "Add Server": "Adicionar Servidor", + "Add custom server +": "Adicionar servidor personalizado +", + "Copy URL": "Copiar URL", + "Client Setup Instructions": "Instruções de Configuração do Cliente", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "Após alterar conexões ou fluxos, reconecte seu servidor MCP para que as alterações tenham efeito.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Siga estas etapas para configurar o MCP em seu cliente preferido. Isso permite que seu assistente de IA acesse suas ferramentas.", + "icon": "ícone", + "Unlock Analytics": "Desbloquear Análises", + "Get insights into your platform usage and performance with our analytics dashboard": "Obtenha informações sobre o uso e desempenho da sua plataforma com o nosso painel de análise", + "Active Flows": "Fluxos ativos", + "The number of enabled flows in the platform": "O número de fluxos ativados na plataforma", + "Active Projects": "Projetos ativos", + "The number of projects with at least one enabled flow": "O número de projetos com pelo menos um fluxo permitido", + "Active Users": "Usuários ativos", + "The number of users logged in the last 30 days": "O número de usuários logados nos últimos 30 dias", + "Out of {totalusers} total users": "Fora do total de {totalusers} usuários", + "Pieces Used": "Pedaços Utilizados", + "The number of unique pieces used across all flows": "O número de peças únicas usadas em todos os fluxos", + "Flows with AI": "Fluxos com IA", + "The number of enabled flows that use AI pieces": "O número de fluxos ativados que usam peças IA", + "Metrics": "Métricas", + "Executed Tasks": "Tarefas executadas", + "Showing total executed tasks for specified time range": "Mostrando o total de tarefas executadas para um intervalo de tempo especificado", + "Tasks Usage Limit": "Limite de uso de tarefas", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Especifique um limite mensal para evitar uso excessivo. Seus fluxos não serão mais executados se este limite for atingido.", + "Number of monthly tasks": "Número de tarefas mensais", + "Save changes": "Salvar as alterações", + "Limits updated successfully": "Limites atualizados com sucesso", + "Failed to update limits": "Falha ao atualizar limites", + "Failed to load billing information": "Não foi possível carregar as informações de faturamento", + "Billing Amount": "Valor de Faturamento", + "Manage Payment Details": "Gerenciar detalhes de pagamento", + "Add Payment Details": "Adicionar detalhes de pagamento", + "Current Task Usage": "Uso da tarefa atual", + "Count of executed steps": "Contagem de etapas executadas", + "Billing Limit": "Limite de cobrança", + "Edit": "Alterar", + "Add Limit": "Adicionar limite", + "Current Credit Usage": "Uso atual de crédito", + "WebSocket Connection": "Conexão de WebSocket", + "Connected": "Conectado", + "Disconnected": "Desconectado", + "No issues detected": "Nenhum problema detectado", + "Check the status of your platform and its components": "Verifique o status da sua plataforma e de seus componentes", + "System Health Status": "Status de Saúde do Sistema", + "All systems are running smoothly": "Todos os sistemas estão funcionando bem", + "Check the health of your worker machines": "Verifique a saúde das máquinas do seu trabalhador", + "Workers Machine": "Máquina dos Trabalhadores", + "This is demo data. In a real environment, this would show your actual worker machines.": "Estes são dados de demonstração. Em um ambiente real, isso mostraria as suas máquinas reais.", + "No workers found": "Nenhum trabalhador encontrado", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "Você não tem nenhuma máquina de trabalhador ainda. Gire novas máquinas para executar suas automações", + "IP Address": "Endereço IP", + "CPU Usage": "Uso da CPU", + "Disk Usage": "Uso do disco", + "RAM Usage": "RAM Usage", + "Last Contact": "Último contato", + "Configs": "Configurações", + "Environment Variables": "Variáveis de Ambiente", + "Websocket Connection Error": "Erro de conexão Websocket", + "Retry Connection": "Tentar Conexão novamente", + "Update Available": "Atualização disponível", + "Update Now": "Atualizar agora", + "Your Universal AI needs a quick setup": "Sua IA Universal precisa de uma configuração rápida", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "Notamos que você ainda não configurou nenhum fornecedor de Inteligência Artificial. Para desbloquear peças de IA universal para sua equipe, você precisará configurar algumas credenciais de provedor primeiro.", + "Configure": "Configurar", + "Platform Alerts": "Alertas de Plataforma", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "Existem importantes alertas de plataformas que requerem sua atenção. Por favor, verifique a seção de alertas no Administrador da Plataforma.", + "View Alerts": "Visualizar alertas", + "Used Tasks": "Tarefas utilizadas", + "Used AI Credits": "Créditos IA utilizados", + "Cannot delete active project, switch to another project first": "Não é possível excluir o projeto ativo, mude para outro projeto primeiro", + "Delete Projects": "Excluir Projetos", + "Are you sure you want to delete the selected projects?": "Tem certeza que deseja excluir os projetos selecionados?", + "New Project": "Novo Projeto", + "Validation error": "Erro de validação", + "Project has enabled flows. Please disable them first.": "O projeto tem fluxos ativados. Por favor desative-os primeiro.", + "This project is active. Please switch to another project first.": "Este projeto está ativo. Por favor, mude para outro projeto primeiro.", + "Unlock Projects": "Desbloquear Projetos", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Orquestre suas equipes de automação em projetos com seus próprios fluxos, conexões e quotas de uso", + "Manage your automation projects": "Gerenciar seus projetos de automação", + "No projects found": "Nenhum projeto encontrado", + "Start by creating projects to manage your automation teams": "Comece criando projetos para gerenciar seus times de automação", + "Edit project": "Editar projeto", + "Name is required": "Nome é obrigatório", + "Create New Project": "Criar Novo Projeto", + "Project Name": "Nome do Projeto", + "Id": "Id", + "Enable API Keys": "Habilitar chaves API", + "Create and manage API keys to access Activepieces APIs.": "Crie e gerencie chaves de API para acessar APIs do Activepieces .", + "New Api Key": "Nova Chave de API", + "No API keys found": "Nenhuma chave de API encontrada", + "Start by creating an API key to communicate with Activepieces APIs": "Comece criando uma chave de API para se comunicar com APIs do Activepieces", + "Delete API Key": "Apagar Chave de API", + "Are you sure you want to delete this API key?": "Tem certeza que deseja excluir esta chave de API?", + "API Key": "Chave de API", + "API Key Created": "Chave de API Criada", + "Create New API Key": "Criar nova chave de API", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Salve esta chave secreta em algum lugar seguro e acessível. Por razões de segurança,", + "you won't be able to view it again after closing this dialog.": "você não será capaz de vê-lo novamente após fechar esta caixa de diálogo.", + "API Key Name": "Nome da Chave API", + "Action": "Acão", + "Performed By": "Realizado Por", + "Project": "Projecto", + "Unlock Audit Logs": "Desbloquear Logs de Auditoria", + "Comply with internal and external security policies by tracking activities done within your account": "Cumprir com políticas de segurança internas e externas rastreando atividades realizadas na sua conta", + "Track activities done within your platform": "Acompanhe as atividades feitas em sua plataforma", + "No audit logs found": "Nenhum log de auditoria encontrado", + "Come back later when you have some activity to audit": "Volte mais tarde quando tiver alguma atividade para auditoria", + "Resource": "Recurso", + "Details": "detalhes", + "N/A": "N/D", + "Flow Run": "Corrida de fluxo", + "Flow": "Fluxo", + "User": "Usuário", + "Signing Key": "Chave de Assinatura", + "Project Role Management": "Gerenciamento de Funções", + "Define custom roles and permissions to control what your team members can access and modify": "Definir funções e permissões personalizadas para controlar o que os membros da sua equipe podem acessar e modificar", + "Define custom roles and permissions that can be assigned to your team members": "Definir funções e permissões personalizadas que podem ser atribuídas aos membros da sua equipe", + "New Role": "Nova Permissão", + "Contact sales to unlock custom roles": "Contate vendas para desbloquear funções personalizadas", + "Create New Role": "Criar Novo Papel", + "View ": "Visualizar ", + "Edit ": "Alterar ", + "Role Name": "Nome da Função", + "Permissions": "Permissões", + "None": "Nenhuma", + "Read": "Lido", + "Write": "Salvar", + "Create": "Criar", + "Email": "e-mail", + "First Name": "Nome", + "Last Name": "Sobrenome", + "Roles": "Papéis", + "View the users assigned to this role": "Visualizar os usuários atribuídos a esta função", + "Role": "Funções", + "No users found": "Nenhum usuário encontrado", + "Starting by assigning users to this role": "Começando por atribuir os usuários a esta função", + "Project Role entry deleted successfully": "Entrada de Papel do Projeto excluída com sucesso", + "Updated": "Atualizado", + "No project roles found": "Nenhuma função do projeto encontrada", + "Create custom project roles to manage permissions for platform users": "Criar papéis personalizados para gerenciar permissões para usuários da plataforma", + "Show Users": "Mostrar Usuários", + "View Role": "Ver Papel", + "Edit Role": "Editar Permissão", + "Delete Role": "Excluir Papel", + "Project Role": "Papel no Projeto", + "Unlock Embedding Through JS SDK": "Desbloquear Incorporação através do SDK JS", + "Enable signing keys to access embedding functionalities.": "Habilite chaves de assinatura para acessar as funcionalidades de incorporação.", + "New Signing Key": "Nova Chave de Assinatura", + "No signing keys found": "Nenhuma chave de assinatura encontrada", + "Create a signing key to authenticate users with embedding": "Crie uma chave de assinatura para autenticar usuários com incorporação", + "Delete Signing Key": "Excluir Chave de Assinatura", + "Are you sure you want to delete this signing key?": "Você tem certeza que deseja excluir esta chave de assinatura?", + "Signing Key Created": "Chave de assinatura criada", + "Create New Signing Key": "Criar Nova Chave de Assinatura", + "Signing Key Name": "Nome da chave assinante", + "Allowed domains updated": "Domínios permitidos atualizados", + "Update": "Atualização", + "Enable": "Habilitado", + "Configure Allowed Domains": "Configurar domínios permitidos", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Digite os domínios permitidos para que os usuários possam autenticar com, lista vazia permitirá todos os domínios.", + "Add Domain": "Adicionar domínio", + "Allow logins through {providerName}'s single sign-on functionality.": "Permitir logins através da funcionalidade de login único do {providerName}.", + "Email authentication updated": "Autenticação de e-mail atualizada", + "Enable Single Sign On": "Ativar logon único", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Deixe seus usuários entrarem com seu provedor SSO atual ou dar-lhes acesso de inscrição auto serve", + "Allowed Domains": "Domínios permitidos", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Permitir que os usuários se autentiquem com domínios específicos. Deixe em branco para permitir todos os domínios.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Login de Email Permitido", + "Allow logins through email and password.": "Permitir logins por email e senha.", + "Single sign on settings updated": "Configurações de sign on simples atualizadas", + "Disable": "Desligado", + "Configure {provider} SSO": "Configurar o {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Leia mais informações sobre como configurar o {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "ID do Cliente {provider}", + "{provider} Client Secret": "Segredo do Cliente {provider}", + "Single sign-on settings updated": "Configurações de logon único atualizadas", + "Configure SAML 2.0 SSO": "Configurar SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Instruções de instalação**:\nPor favor, verifique a seguinte documentação: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**URL de sinal único**:\n```text\n{samlAcs}\n```\n**ID de entidade de audiência (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "Metadados IDP", + "IDP Certificate": "Certificado IDP", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "URL Base", + "Resource Name": "Nome do Recurso", + "Deployment Name": "Nome de implantação", + "Saving": "Salvando", + "Activepieces Copilot": "Copilot activepieces", + "Copilot is configured and ready to help your users build flows faster using AI.": "O Copilot está configurado e pronto para ajudar seus usuários a criar fluxos mais rapidamente usando a IA.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configure o Activepieces Copilot para ajudar seus usuários a construir fluxos mais rapidamente usando a IA.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Defina seus provedores de IA e configurações de copiloto para que os seus usuários desfrutem de uma experiência de construção ininterrupta com as nossas peças de IA universais", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Configura credenciais do provedor que serão usadas por peças AI universais, ou seja, I.A. Texto", + "AI Providers": "AI Providers", + "Copilot": "Cobre", + "Configure credentials for {providerName} AI provider.": "Configurar credenciais para o provedor {providerName} AI.", + "Update AI Provider": "Atualizar Provedor de I.A.", + "Enable AI Provider": "Ativar provedor de IA", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Por favor, insira um domínio válido", + "Your changes have been saved.": "Suas mudanças foram salvas.", + "The domain is already added.": "O domínio já foi adicionado.", + "Add Custom Domain": "Adicionar domínio personalizado", + "Enter a domain name without a protocol (e.g. example.com)": "Insira um nome de domínio sem um protocolo (por exemplo, exemplo.com)", + "Logo URL": "URL do logotipo", + "Icon URL": "URL do ícone:", + "Favicon URL": "URL do Favicon", + "Default Language": "Idioma padrão", + "Select Language": "Selecione o idioma", + "No Languages": "Sem idiomas", + "Primary Color": "Cor primária", + "Custom Domains": "Domínios personalizados", + "No domains added yet.": "Nenhum domínio adicionado ainda.", + "Verified": "Verificada", + "Pending, please contact the support for dns verification.": "Pendente, entre em contato com o suporte para a verificação de dns.", + "Are you sure you want to delete {domain}?": "Tem certeza que deseja excluir {domain}?", + "Brand Activepieces": "Marca Activepieces", + "Give your users an experience that looks like you by customizing the color, logo and more": "Dê aos seus usuários uma experiência que se pareça com você personalizando a cor, o logotipo e muito mais", + "Configure the appearance and SMTP settings for your platform.": "Configure a aparência e as configurações SMTP da sua plataforma.", + "Invalid host": "Host inválido", + "Invalid username": "Usuário inválido", + "Invalid password": "Senha inválida", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Nome de remetente inválido", + "SMTP is configured": "SMTP está configurado", + "Mail Server": "Servidor de email", + "Set up your SMTP settings to send emails from your domain.": "Configure as suas configurações de SMTP para enviar e-mails a partir do seu domínio.", + "Disable Mail Server": "Desabilitar Servidor de Email", + "Are you sure you want to disable your mail server?": "Tem certeza que deseja desativar o seu servidor de email?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "Isso impedirá que você envie e-mails para issues, limites de cota, convites e senha esquecida.", + "mail server": "servidor de email", + "Host": "Servidor", + "Port": "Porta", + "Username": "Usuário:", + "Password": "Senha", + "Sender Email": "E-mail do remetente", + "Sender Name": "Nome do Remetente", + "Enable Global Connections": "Ativar conexões globais", + "Manage platform-wide connections to external systems.": "Gerenciar conexões de plataforma para sistemas externos.", + "No global connections found": "Nenhuma conexão global encontrada", + "Create a global connection that can be shared to multiple projects": "Criar uma conexão global que pode ser compartilhada para vários projetos", + "License key is invalid": "A chave da licença é inválida", + "Invalid license key": "Chave de licença inválida", + "License activated!": "Licença ativada!", + "Activate License Key": "Ativar Chave de Licença", + "Let the magic begin!": "Que comece a mágica!", + "Activate": "Ativar", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "Este recurso ainda não se serve na nuvem, entre em contato com sales@activepieces.com. ", + "This feature is not available in your current edition. ": "Este recurso não está disponível na sua edição atual. ", + "Learn how to upgrade": "Saiba como atualizar", + "Activate your platform and unlock enterprise features": "Ative sua plataforma e desbloqueie os recursos empresariais", + "Activate License": "Ativar Licença", + "Expiration": "Vencimento", + "Valid until": "Valido ate", + "Expired": "Expirado", + "Expires soon": "Expira em breve", + "Features": "Funcionalidades", + "Applying Tags...": "Aplicando Tags...", + "Tags applied.": "Tags aplicadas.", + "Tag created": "Etiqueta criada", + "Tag": "Etiqueta", + "Tags": "Etiquetas", + "Control Pieces": "Peças de controle", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Mostre as peças que mais importam para os seus usuários e oculte as que você não gosta.", + "Manage the pieces that are available to your users": "Gerencie as peças que estão disponíveis para seus usuários", + "Start by installing pieces that you want to use in your automations": "Comece instalando peças que você deseja usar em suas automações", + "Piece Name": "Nome da Peça", + "Hide this piece from all projects": "Ocultar esta peça de todos os projetos", + "Show this piece for all projects": "Mostrar esta peça para todos os projetos", + "Unpin this piece": "Desafixar este pedaço", + "Pin this piece": "Fixar esta peça", + "Pieces synced": "Pedaços sincronizados", + "Pieces have been synced from the activepieces cloud.": "Pedaços foram sincronizados a partir da nuvem de peças de atividade.", + "OAuth2 Credentials Deleted": "Credenciais OAuth2 excluídas", + "OAuth2 Credentials Updated": "Credenciais OAuth2 Atualizadas", + "Configure OAuth2 APP": "Configurar aplicativo OAuth2", + "Delete OAuth2 APP": "Excluir aplicativo OAuth2", + "Templates deleted successfully": "Modelos excluídos com sucesso", + "Delete Templates": "Excluir Modelos", + "Are you sure you want to delete the selected templates?": "Você tem certeza que deseja excluir os modelos selecionados?", + "New Template": "Novo Modelo", + "Unlock Templates": "Desbloquear Modelos", + "Convert the most common automations into reusable templates 1 click away from your users": "Converta as automações mais comuns em modelos reutilizáveis a um clique de seus usuários", + "Convert the most common automations into reusable templates": "Converter as automações mais comuns em modelos reutilizáveis", + "No templates found": "Nenhum modelo encontrado", + "Create a template for your user to inspire them": "Crie um modelo para o seu usuário para inspirá-lo", + "Edit template": "Editar modelo", + "Template is required": "Modelo é obrigatório", + "Update New Template": "Atualizar Novo Modelo", + "Create New Template": "Criar Novo Modelo", + "Template Name": "Nome do modelo", + "Description": "Descrição", + "Template Description": "Descrição do Modelo", + "Blog URL": "URL do Blog", + "Template Blog URL": "URL do Modelo Blog", + "Template": "Modelo", + "Invalid JSON": "JSON inválido", + "User deleted successfully": "Usuário excluído com sucesso", + "User activated successfully": "Usuário ativado com sucesso", + "User deactivated successfully": "Usuário desativado com sucesso", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Gerencie seus usuários e seu acesso a seus projetos", + "Start inviting users to your project": "Comece a convidar usuários para o seu projeto", + "External Id": "ID Externo", + "Admin": "Administrador", + "Member": "Membro", + "Activated": "Ativado", + "Deactivated": "Desativado", + "Edit user": "Atualizar usuário", + "Admin cannot be deactivated": "O administrador não pode ser desativado", + "Deactivate user": "Desativar usuário", + "Activate user": "Ativar usuário", + "Delete User": "Excluir Usuário", + "Are you sure you want to delete this user?": "Tem certeza de que deseja excluir este usuário?", + "Delete user": "Excluir usuário", + "Update User Role": "Atualizar Função do Usuário", + "Meeting Summary Flow": "Resumo do fluxo da reunião", + "Added new features and fixed bugs": "Adicionados novos recursos e erros corrigidos", + "Flows Changes": "Alterações de fluxos", + "Connections Changes": "Mudanças nas Conexões", + "New connections are placeholders and need to be reconnected again": "Novas conexões são espaços reservados e precisam ser reconectadas", + "renamed to": "renomeado para", + "Tables Changes": "Alterações de tabelas", + "No changes to apply": "Nenhuma alteração para aplicar", + "Apply Changes": "Aplicar alterações", + "Create Git Release": "Criar lançamento Git", + "Create Project Release": "Criar lançamento de projeto", + "Create Rollback to": "Criar reversão para", + "Source": "fonte", + "Rollback": "Rollback", + "Imported At": "Importado em", + "Imported By": "Importado por", + "Track and manage your project version history and deployments. ": "Rastreie e gerencie seu histórico de versão do projeto e implantações. ", + "Environments & Releases": "Ambientes e Lançamentos", + "Project Releases": "Versões do Projeto", + "Create Release": "Criar versão", + "From Git": "Do Git", + "From Project": "Do projeto", + "No project releases found": "Nenhum lançamento de projeto encontrado", + "Create a project release to get started": "Crie uma versão do projeto para começar", + "Please select project": "Selecione o projeto", + "No Changes Found": "Nenhuma alteração encontrada", + "There are no differences to apply": "Não há diferenças para aplicar", + "Please select a project": "Por favor, selecione um projeto", + "Search projects...": "Pesquisar projetos...", + "Review Changes": "Revisar Mudanças", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Importado por", + "from": "De", + "No description provided": "Descrição não fornecida", + "Invitation only sign up": "Inscrição somente por convite", + "Please ask your administrator to add you to the organization.": "Por favor, peça ao seu administrador para adicioná-lo à organização.", + "Something went wrong, please try again.": "Algo deu errado, por favor, tente novamente.", + "Please try again.": "Por favor, tente novamente.", + "Please enter a valid email address": "Por favor insira um endereço de email válido", + "The email is already added.": "Este email já foi adicionado.", + "Add email": "Adicionar email", + "Only project admins can do this": "Somente administradores do projeto podem fazer isso", + "Add Alert Email": "Adicionar Email de Alerta", + "Enter the email address to receive alerts.": "Insira o endereço de email para receber alertas.", + "Add Email": "Adicionar Email", + "Emails": "e-mails", + "Add email addresses to receive alerts.": "Adicionar endereços de email para receber alertas.", + "No emails added yet.": "Nenhum email adicionado ainda.", + "Choose what you want to be notified about.": "Escolha sobre o que você quer ser notificado.", + "Project and alert permissions are required to change this setting.": "Permissões de projeto e alerta são necessárias para alterar esta configuração.", + "Every Failed Run": "Cada Execução com Falha", + "Get an email alert when a flow fails.": "Receba um alerta por e-mail quando um fluxo falhar.", + "Get an email alert when a new issue created.": "Receba um alerta de e-mail quando um novo problema for criado.", + "Never": "Nunca", + "Turn off email notifications.": "Desative as notificações por email.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Personalize a aparência do app. Altere automaticamente entre os temas claro e escuro.", + "Select the theme for the dashboard.": "Selecione o tema para o painel.", + "Light": "Claro", + "Dark": "Escuro", + "Select the language that will be used in the dashboard.": "Selecione o idioma que será utilizado no painel.", + "Select language": "Selecionar idioma", + "Search language...": "Buscar idioma...", + "No language found.": "Nenhum idioma encontrado.", + "Help us translate Activepieces to your language.": "Ajude-nos a traduzir o Activepieces para seu idioma.", + "Learn more": "Saiba mais", + "Git Connection Removed": "Conexão Git removida", + "Your Git repository has been successfully disconnected": "Seu repositório Git foi desconectado com sucesso", + "Enable Environments": "Habilitar Ambientes", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Implantar fluxos em ambientes de desenvolvimento, preparação e produção com controle de versão e colaboração de equipe", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Conecte o Git para ativar o controle de versão, fazer backup de seus fluxos, e gerenciar vários ambientes. ", + "Repository URL": "URL do Repositório", + "Not connected": "Não conectado", + "Project Folder": "Pasta do Projeto", + "Releases Enabled": "Versões Habilitadas", + "You have successfully enabled releases": "Você ativou os lançamentos com sucesso", + "Enable releases to easily create and manage project releases.": "Habilite versões para criar e gerenciar facilmente lançamentos de projetos.", + "The external ID is already taken.": "O ID externo já está em uso.", + "Manage general settings for your project.": "Gerencie as configurações gerais para o seu projeto.", + "Used to identify the project based on your SaaS ID": "Usado para identificar o projeto com base na sua ID SaaS", + "org-3412321": "org-3412321", + "Delete {name}": "Excluir {name}", + "This will permanently delete this piece, all steps using it will fail.": "Isto excluirá permanentemente esta peça, todos os passos que a estiverem usando falharão.", + "Add a piece to your project that you want to use in your automations": "Adicione uma peça ao seu projeto que você deseja usar em suas automações", + "Pieces list updated": "Lista de peças atualizada", + "Manage Pieces": "Gerenciar peças", + "Choose which pieces you want to be available for your current project users": "Escolha quais peças você quer estar disponível para os usuários do seu projeto atual", + "Unlock Team Permissions": "Desbloquear Permissões da Equipe", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "Você pode convidar usuários para a sua Plataforma gratuitamente na edição da comunidade. Para funções avançadas e solicitação de permissões", + "Project Members": "Membros do Projeto", + "Invite your team members to collaborate.": "Convide seus colegas de equipe para colaborar.", + "No members are added to this project.": "Nenhum membro foi adicionado a este projeto.", + "Pending Invitations": "Convites Pendentes", + "No pending invitation.": "Nenhum convite pendente", + "templateId is missing": "templateId está faltando", + "Me Only": "Eu Somente", + "Unresolved": "Não resolvido", + "Resolved": "Resolvido", + "Title": "Título", + "Date Created": "Data de Criação", + "Manage todos for your project that are created by automations": "Gerencie tarefas para o seu projeto que é criado por automações", + "No todos found": "Nenhuma tarefa encontrada", + "You do not have any pending todos. Great job!": "Você não possui nenhuma tarefa pendente. Ótimo trabalho!", + "Write a comment...": "Escreva um comentário...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "Esse recurso ainda está em teste e pode ser alterado frequentemente", + "Failed to copy to clipboard": "Erro ao copiar para área de transferência", + "{number} items selected": "Itens {number} selecionados", + "Select All": "Selecionar Todos", + "No results found.": "Nenhum resultado encontrado.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect deve ser usado dentro de MultiSelectProvider", + "Unset": "Desmarcar", + "Refresh": "atualizar", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} mais", + "Removed {entityName}": "Removido {entityName}", + "Download File": "Baixar Arquivo", + "Copied to clipboard": "Copiado para a área de transferência", + "File is not available after execution.": "O arquivo não está disponível após a execução.", + "Available for Projects": "Disponível para projetos", + "Select projects": "Selecionar projetos", + "No items": "Nenhum item", + "Previous": "Anterior", + "to": "para", + "Last Week": "A semana passada", + "Last Month": "Mês passado", + "Last 3 Months": "Últimos 3 Meses", + "Last 6 Months": "Últimos 6 Meses", + "Next 7 days": "Próximos 7 dias", + "Next 30 days": "Próximos 30 Dias", + "Next 90 days": "Próximos 90 Dias", + "Next 180 days": "Próximos 180 dias", + "Select Time Range": "Selecione Intervalo de Tempo", + "Clear": "Limpar", + "Download": "BAIXAR", + "Go to Dashboard": "Ir para Painel", + "Select a file": "Selecione um arquivo", + "Press space to separate values": "Pressione espaço para separar valores", + "AM": "SOU", + "PM": "Escolha", + "Already have an account?": "Já possui uma conta?", + "Sign in": "Entrar", + "Don't have an account?": "Não tem uma conta?", + "Sign up": "Cadastre-se", + "Welcome Back!": "Bem-vindo de Volta!", + "Enter your email below to sign in to your account": "Digite seu email abaixo para acessar sua conta", + "Let's Get Started!": "Vamos Começar!", + "Create your account and start flowing!": "Crie sua conta e comece a fluir!", + "Your password was changed successfully": "A sua senha foi alterada com sucesso", + "Your password reset request has expired, please request a new one": "A sua solicitação de redefinição de senha expirou, por favor, solicite uma nova", + "Reset Password": "Redefinir Senha", + "Enter your new password": "Digite sua nova senha", + "Password is required": "Senha é obrigatória", + "Verification email resent, if previous one expired.": "E-mail de verificação reenviado, se o anterior expirou.", + "Password reset link resent, if previous one expired.": "Link para redefinir senha, se o anterior expirar.", + "We sent you a link to complete your registration to": "Enviamos um link para completar o seu cadastro", + "We sent you a link to reset your password to": "Enviamos um link para redefinir sua senha para", + "Didn't receive an email or it expired?": "Não recebeu um e-mail ou expirou?", + "Resend": "Reenviar", + "Please enter your email": "Por favor insira seu email", + "Check Your Inbox": "Verifique sua Caixa de Entrada", + "If the user exists we'll send you an email with a link to reset your password.": "Se o usuário existir, enviaremos um email com um link para redefinir sua senha.", + "Send Password Reset Link": "Enviar Link de Redefinição de Senha", + "Back to sign in": "Voltar para login", + "Email is invalid": "Email inválido", + "Something went wrong, please try again later": "Algo deu errado, por favor tente novamente mais tarde", + "Invalid email or password": "Email ou senha inválidos", + "User has been deactivated": "O usuário foi desativado", + "Email domain is disallowed": "Domínio do e-mail não permitido", + "Email authentication has been disabled": "A autenticação de email foi desativada", + "Forgot your password?": "Esqueceu sua senha?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "O cadastro é restrito. Você precisa de um convite para participar. Por favor, contate o administrador.", + "Email is already used": "Email já está em uso", + "Email authentication is disabled": "A autenticação de e-mail está desativada", + "First name is required": "Nome é obrigatório", + "Last name is required": "Sobrenome é obrigatório", + "Email is required": "Email é obrigatório", + "Receive updates and newsletters from activepieces": "Receba atualizações e boletins informativos do activepieces", + "By creating an account, you agree to our": "Ao criar uma conta, você concorda com nossos", + "terms of service": "termos de serviço", + "privacy policy": "política de privacidade", + "Sign up With": "Cadastre-se com", + "Google": "Google", + "Sign in With": "Acessar com", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "E-mail foi verificado. Você será redirecionado para entrar...", + "Verifying email...": "Verificando email...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "o convite expirou, assim que você entrar novamente, você poderá reenviar o e-mail de verificação.", + "Redirecting to sign in...": "Redirecionando para iniciar sessão...", + "Password must contain at least one special character": "A senha deve conter pelo menos um caractere especial", + "Password must contain at least one lowercase letter": "A senha deve conter pelo menos uma letra minúscula", + "Password must contain at least one uppercase letter": "A senha deve conter pelo menos uma letra maiúscula", + "Password must contain at least one number": "A senha deve conter pelo menos um número", + "8-64 Characters": "8-64 Caracteres", + "Special Character": "Caractere Especial", + "Lowercase": "Minúscula", + "Uppercase": "Maiúscula", + "Number": "Número", + "Connection has been updated.": "A conexão foi atualizada.", + "Edit Global Connection": "Editar conexão global", + "Connection has been renamed.": "A conexão foi renomeada.", + "New Connection Name": "Novo nome da conexão", + "Connection name already used": "Nome de conexão já utilizado", + "Please select at least one project": "Por favor, selecione pelo menos um projeto", + "Run Succeeded": "Execução Bem-sucedida", + "Run Failed": "Execução Falhou", + "Flow Run is paused": "Execução do Fluxo está pausada", + "Run Failed due to quota exceeded": "Execução Falhou devido ao limite excedido", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Falha ao executar devido a exceder o limite de memória de {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "Execução ultrapassou {timeout} segundos, tente otimizar seus passos.", + "Run failed for an unknown reason, contact support.": "Execução falhou por uma razão desconhecida, contate o suporte.", + "Unknown": "Desconhecido", + "Exit Run": "Sair da Execução", + "Select shown": "Selecionar mostrada", + "Select all": "Selecionar todos", + "Start Time": "Hora de início", + "Runs replayed successfully": "Rodadas reproduzidas com sucesso", + "Retry": "Repetir", + "all except": "todos, exceto", + "all": "Todos", + "Only failed runs can be retried from failed step": "Somente execuções falhadas podem ser retentadas da etapa fracassada", + "No flow runs found": "Nenhum fluxo de execução encontrado", + "Come back later when your automations start running": "Volte mais tarde quando suas automações começarem a rodar", + "Step running": "Passo em execução", + "Step paused": "Passo pausado", + "Step Stopped": "Passo Parado", + "Step Succeeded": "Passo Bem-sucedido", + "Step Failed": "Passo Falhou", + "Please publish flow first": "Por favor publique o fluxo primeiro", + "Flow is on": "Fluxo está ligado", + "Flow is off": "Fluxo está desligado", + "Permission Needed": "Permissão Necessária", + "Draft Version": "Versão de Rascunho", + "Published Version": "Versão publicada", + "Locked Version": "Versão Bloqueada", + "flowsImported": "{flowsCount, plural, one {}=0 {Nenhum fluxo importado} =1 {Fluxo importado com sucesso} other {Fluxos importados com sucesso}}", + "Template file is invalid": "Arquivo modelo é inválido", + "No valid templates found. The following files failed to import: ": "Nenhum modelo válido encontrado. Os seguintes arquivos falharam ao importar: ", + "Please select a file first": "Por favor, selecione um arquivo primeiro", + "Unsupported file type": "Tipo de arquivo não suportado", + "Import Flow": "Importar Fluxo", + "Warning": "ATENÇÃO", + "Importing a flow will overwrite your current one.": "A importação de um fluxo sobrescreverá o atual.", + "Select a folder": "Selecionar uma pasta", + "Folders": "Pastas", + "Please select a folder": "Por favor, selecione uma pasta", + "Moved flows successfully": "Fluxos movidos com sucesso", + "Move Selected Flows": "Mover Fluxos Selecionados", + "Select Folder": "Selecionar Pasta", + "No Folders": "Sem Pastas", + "Flow has been renamed.": "Fluxo foi renomeado.", + "New Flow Name": "Novo Nome de Fluxo", + "Use Template": "Usar Modelo", + "Browse Templates": "Procurar Modelos", + "Search templates": "Buscar modelos", + "No templates found, try adjusting your search": "Nenhum modelo encontrado, tente ajustar sua pesquisa", + "Read more about this template in": "Leia mais sobre este modelo em", + "this blog!": "este blog!", + "Share Template": "Compartilhar Modelo", + "Generate or update a template link for the current flow to easily share it with others.": "Gere ou atualize um link de modelo para o fluxo atual e compartilhe facilmente com outros.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "O modelo não terá nenhuma credencial nos campos de conexão, mantendo as informações sensíveis seguras.", + "A short description of the template": "Uma breve descrição do modelo", + "Flow Is In Use": "Fluxo está em uso", + "Flow is being used by another user, please try again later.": "Fluxo está sendo usado por outro usuário. Por favor, tente novamente mais tarde.", + "Flow has been published.": "O Fluxo foi publicado.", + "Flows have been exported.": "Flores foram exportados.", + "Run": "Executar", + "Real time flow": "Fluxo em tempo real", + "Flow can't be published with empty trigger {name}": "O fluxo não pode ser publicado com o disparador vazio {name}", + "Please contact support as your published flow has a problem": "Por favor contate o suporte pois seu fluxo publicado tem um problema", + "Actions": "Ações", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Tem certeza que deseja excluir estes fluxos? Isto irá excluir permanentemente os fluxos, todos os seus dados e quaisquer execuções em segundo plano.", + "You are on a development branch, this will not delete the flows from the remote repository.": "Você está em um ramo de desenvolvimento, isso não irá excluir os fluxos do repositório remoto.", + "Please enter folder name": "Por favor, insira o nome da pasta", + "Added folder successfully": "Pasta adicionada com sucesso", + "The folder name already exists.": "O nome da pasta já existe.", + "New Folder": "Nova Pasta", + "Folder Name": "Nome da Pasta", + "Loading...": "Carregando...", + "Delete {folderName}": "Excluir {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "Se você excluir esta pasta, manteremos seus fluxos e os moveremos para Sem Categoria.", + "All flows": "Todos os Fluxos", + "Please enter a folder name": "Por favor, insira o nome da pasta", + "Renamed flow successfully": "Fluxo renomeado com sucesso", + "Folder name already used": "Nome da pasta já utilizado", + "New Folder Name": "Novo Nome da Pasta", + "Connected successfully": "Conectado com sucesso", + "Connect Git": "Conectar Git", + "Remote URL": "URL Remoto", + "Folder name is the name of the folder where the project will be stored or fetched.": "O nome da pasta é o nome da pasta onde o projeto será armazenado ou recuperado.", + "SSH Private Key": "Chave Privada SSH", + "The SSH private key to use for authentication.": "A chave privada SSH para usar na autenticação.", + "Only published flows can be pushed to Git": "Somente fluxos publicados podem ser enviados para o Git", + "Pushed successfully": "Enviado com sucesso", + "Invalid Operation": "Operação Inválida", + "Commit Message": "Mensagem de Confirmação", + "Enter a commit message to describe the changes you want to push.": "Digite uma mensagem de confirmação para descrever as mudanças que deseja enviar.", + "Push": "Enviar", + "This field is required": "Este campo é obrigatório", + "Your submission was successfully received.": "Seu envio foi recebido com sucesso.", + "Flow not found": "Fluxo não encontrado", + "The flow you are trying to submit to does not exist.": "O fluxo que você está tentando enviar não existe.", + "The flow failed to execute.": "Não foi possível executar o fluxo.", + "Submit": "Enviar", + "issues-notification": "issues-notificação", + "Please select a package type": "Por favor, selecione o tipo de pacote", + "package.json not found in archive": "package.json não encontrado no arquivo", + "Error processing archive file": "Erro ao processar arquivo", + "Please upload a .tgz file": "Por favor, carregue um arquivo .tgz", + "Piece name is required for NPM Registry": "É necessário um nome para o Registro NPM", + "Piece version is required for NPM Registry": "Versão do Pedaço é necessária para o NPM Registry", + "Piece installed": "Peça instalada", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "Um pedaço com este nome e versão já está instalado. Por favor, atualize o número da versão em package.json e tente novamente.", + "Install Piece": "Instalar Peça", + "Install a piece": "Instalar uma peça", + "Package Type": "Tipo de pacote", + "NPM Registry": "Registro NPM", + "Packed Archive (.tgz)": "Arquivo Empacotado (.tgz)", + "Piece Version": "Versão da Peça", + "Package Archive": "Pacote de Arquivo", + "Package archive": "Pacote de Arquivo", + "Powerful Node.js & TypeScript code with npm": "Código de Node.js & TypeScript poderoso com npm", + "Loop on Items": "Fazer loop em itens", + "Split your flow into branches depending on condition(s)": "Dividir seu fluxo em branches dependendo de condição(ões)", + "Empty Trigger": "Gatilho Vazio", + "An internal error occurred while fetching data, please contact support": "Ocorreu um erro interno ao obter dados, por favor, contate o suporte", + "An internal error occurred, please contact support": "Ocorreu um erro interno, entre em contato com o suporte", + "Custom Javascript Code": "Código Javascript personalizado", + "Router": "Roteador", + "recordsCount": "Contagem de registros", + "selected": "Selecionado", + "All records selected": "Todos os registros selecionados", + "fieldsCount": "Contagem", + "Saving...": "Salvando...", + "Loading more...": "Carregando mais...", + "Export Table": "Exportar tabela", + "Delete Records": "Excluir registros", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Você tem certeza que deseja excluir os registros selecionados? Esta ação não pode ser desfeita.", + "record": "gravar", + "records": "registros", + "mm/dd/yyy": "dd/mm/yyyy", + "Delete Field": "Excluir Campo", + "Are you sure you want to delete this field? This action cannot be undone.": "Tem certeza que deseja excluir este campo? Esta ação não pode ser desfeita.", + "field": "Campo", + "Ignored": "Ignorado", + "Table": "Classificações", + "CSV": "Csv", + "Field": "Campo", + "Please select a csv file": "Por favor, selecione um arquivo csv", + "Max file size is {maxFileSize}MB": "Tamanho máximo de arquivo é {maxFileSize}MB", + "Import CSV": "Importar CSV", + "Imported records will be added to the bottom of the table": "Registros importados serão adicionados ao final da tabela", + "Any records after the limit ({maxRecords} records) will be ignored": "Quaisquer registros após o limite ({maxRecords} registros) serão ignorados", + "CSV File": "Arquivo CSV", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "Ocorreu um erro inesperado ao importar o arquivo csv. Por favor, clique em copiar o erro e envie-o para o suporte", + "Name must be unique": "Nome deve ser único", + "Type is required": "Tipo é obrigatório", + "Please add at least one option": "Por favor, adicione pelo menos uma opção", + "New Field": "Novo campo", + "Options": "Opções", + "Name is already taken": "O nome já está em uso", + "Table renamed": "Tabela renomeada", + "Table name": "Nome da tabela", + "Team Invitation Accepted": "Convite de Equipe Aceito", + "Thank you for accepting the invitation. We are redirecting you right now...": "Obrigado por aceitar o convite. Estamos redirecionando você agora...", + "Invalid invitation token. Please try again.": "Token de convite inválido. Por favor, tente novamente.", + "Role updated successfully": "Função atualizada com sucesso", + "Error updating role": "Erro ao atualizar função", + "Please try again later": "Por favor, tente novamente mais tarde", + "Edit Role for": "Editar Papel para", + "Select Role": "Selecione a função", + "Avatar": "Avatar", + "Remove {email}": "Remover {email}", + "Are you sure you want to remove this invitation?": "Tem certeza de que deseja excluir este convite?", + "Please select invitation type": "Por favor, selecione o tipo de convite", + "Please select platform role": "Por favor, selecione o papel na plataforma", + "Invitation sent successfully": "Convite enviado com sucesso", + "Please select a project role": "Por favor, selecione uma função de projeto", + "Invitation link copied successfully": "Link de convite copiado com sucesso", + "Invite User": "Convidar Usuário", + "Invitation Link": "Link de Convite", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Por favor, copie o link abaixo e compartilhe com o usuário que você deseja convidar, o convite expira em 24 horas.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Digite o endereço de email do usuário que você deseja convidar, o convite expira em 24 horas.", + "Invite To": "Convidar para", + "Entire Platform": "Plataforma Inteira", + "Select Project Role": "Selecionar função do projeto", + "Invite": "Convidar", + "Platform Role": "Papel na Plataforma", + "Select a platform role": "Selecione um papel na plataforma", + "Are you sure you want to remove this member?": "Tem certeza que deseja remover este membro?", + "Editor": "Editores", + "Operator": "Operador", + "Viewer": "Visualizador", + "Select a project role": "Selecione um papel no projeto", + "Steps in this flow": "Passos neste fluxo", + "Invalid Access": "Acesso Inválido", + "Either the project does not exist or you do not have access to it.": "Ou o projeto não existe ou você não tem acesso a ele." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/ru/translation.json b/packages/react-ui/public/locales/ru/translation.json new file mode 100644 index 0000000..1d63885 --- /dev/null +++ b/packages/react-ui/public/locales/ru/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Опубликовать", + "Latest version is published": "Последняя версия опубликована", + "Your flow has incomplete steps": "У вашего потока есть незавершенные шаги", + "Edit Flow": "Изменить поток", + "View Draft": "Просмотр черновика", + "Uncategorized": "Без категории", + "Go to folder": "Перейти в папку", + "Support": "Поддержка", + "Runs": "Выполненные запуски", + "Run Logs": "Журналы выполнения", + "Versions": "Версии", + "Versions History": "История версий", + "Error generating code": "Ошибка генерации кода", + "AI Copilot": "ИИ ассистент", + "i.e Calculate the sum of a list...": "То есть вычислить сумму списка...", + "Send": "Отправить", + "Generating Code": "Генерация кода", + "Hello there! I am here to generate code that helps with your flow": "Привет! Я здесь, чтобы генерировать код, который помогает с вашим потоком", + "Here are examples of what I am best used for: ": "Вот примеры того, что я использовал для: ", + "Text Processing": "Обработка текста", + "Process strings, dates and data": "Строки обработки, даты и данные", + "Data Operations": "Операции с данными", + "Change data from one format to another": "Изменить данные из одного формата на другой", + "Calculations": "Расчеты", + "Handle math and statistics": "Обработка математики и статистики", + "API Integration": "Интеграция API", + "Connect with external services. Best for simple integrations currently.": "Подключаться к внешним сервисам. Лучше всего для простых интеграций в настоящее время.", + "What would you like me to help you with?": "Что бы вы хотели мне помочь?", + "Insert": "Вставить", + "Data Selector": "Выбор данных", + "Search": "Поиск", + "No matching data": "Нет подходящих данных", + "Try adjusting your search": "Попробуйте изменить условия поиска", + "This trigger needs to have data loaded from your account, to use as sample data.": "Этот триггер должен иметь загруженные данные с вашего аккаунта, чтобы использовать в качестве демонстрационных данных.", + "This step needs to be tested in order to view its data.": "Этот шаг необходимо протестировать для просмотра его данных.", + "Go to Trigger": "Перейти к триггеру", + "Go to Step": "Перейти к шагу", + "Select Mode": "Выбор режима", + "Move Mode": "Режим перемещения", + "Reset Zoom": "Сбросить масштабирование", + "Zoom In": "Приблизить", + "Zoom Out": "Уменьшить", + "Fit to View": "Подогнать по размеру", + "Replace": "Заменить", + "Copy": "Копировать", + "Duplicate": "Дублировать", + "Paste After Last Step": "Вставить после последнего шага", + "Paste Inside Loop": "Вставить внутренний цикл", + "Paste After": "Вставить после", + "Paste Inside...": "Вставить внутрь...", + "New Branch": "Новая ветка", + "Paste Inside Branch": "Вставить внутрь ветки", + "Delete": "Удалить", + "Duplicate Branch": "Дублировать ветку", + "Delete Branch": "Удалить ветку", + "Invalid Move": "Неверный ход", + "The destination location is a child of the dragged step": "Местоположение назначения является потомком перетаскиваемого шага", + "End": "Конец", + "Skipped": "Пропущено", + "Incomplete settings": "Незавершенные настройки", + "logo": "логотип", + "Step Icon": "Значок шага", + "Branch": "Ветка", + "incompleteSteps": "{invalidSteps, plural, =0 {нет незавершенных шагов} =1 {Завершите 1 шаг} other {Завершите # шага(ов)}}", + "Test Flow": "Тест потока", + "Please test the trigger first": "Пожалуйста, проверьте сначала триггер", + "View Only": "Только просмотр", + "Use as Draft": "Использовать как черновик", + "Are you sure?": "Вы уверены?", + "Your current draft version will be overwritten with": "Текущая версия черновика будет перезаписана", + "version #": "версия №", + "Cancel": "Отмена", + "Confirm": "Подтвердить", + "Version": "Версия", + "Viewing": "Просмотр", + "View": "Смотреть", + "Version History": "История версий", + "Error, please try again.": "Произошла ошибка! Пожалуйста, повторите попытку.", + "Continue on Failure": "Продолжить при ошибке", + "Enable this option to skip this step and continue the flow normally if it fails.": "Включите эту опцию, чтобы пропустить этот шаг и продолжить выполнение в обычном режиме в случае ошибки.", + "Retry on Failure": "Повторить при ошибке", + "Automatically retry up to four attempts when failed.": "Автоматически повторять до четырех попыток в случае ошибки.", + "Remove": "Удалить", + "Add Item": "Добавить элемент", + "File Input": "Ввод файла", + "Date Input": "Date Input", + "Dynamic value": "Динамическое значение", + "Select an option": "Выберите опцию", + "Unexpected error, please retry": "Непредвиденная ошибка, повторите попытку", + "Unexpected error, please refresh the page or contact support": "Непредвиденная ошибка, обновите страницу или обратитесь в службу поддержки", + "Name can only contain letters, numbers and underscores": "Имя может содержать только буквы, цифры и подчеркивания", + "Ask AI": "Спросить AI", + "Create Todo Guide": "Создать руководство по задаче", + "Where would you like the todo to be reviewed?": "Где бы вы хотели просмотреть задачу?", + "Activepieces Todos": "Задачи Activepieces", + "Users will manage tasks directly in Activepieces": "Пользователи будут управлять задачами непосредственно в Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Пользователи будут управлять и отвечать на задачи непосредственно в интерфейсе Activepieces — идеально подходит для внутренних команд.", + "External Channel (Slack, Teams, Email, ...)": "Внешний канал (Slack, команды, E-mail, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Отправлять уведомления со ссылками одобрения через внешние каналы, такие как Slack, Teams или Email. Лучшее сотрудничество с внешними заинтересованными сторонами.", + "Preview (Activepieces Todos)": "Предпросмотр (активные задачи)", + "Preview (External channel)": "Предпросмотр (внешний канал)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "Activepieces Todo позволяет пользователям просматривать и решать задачи непосредственно в меню Activepieces", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "Вы можете добавить канал перед шагом ожидания и настроить логику на шаге маршрутизатора", + "Add Steps": "Добавить шаги", + "All": "Все", + "AI": "AI", + "Core": "Основа", + "Apps": "Приложения", + "Not available as trigger": "Недоступно как триггер", + "Not available as action": "Недоступно как действие", + "Let our AI assistant help you out": "Позвольте нашему помощнику ИИ помочь вам", + "Or": "Или", + "Request Piece": "Запросить часть", + "No pieces found": "Нет частей", + "Please select a piece first": "Пожалуйста, выберите первую часть", + "All Iterations": "Все Итерации", + "Duration": "Длительность", + "Input": "Ввод", + "Output": "Вывод", + "There are no logs captured for this run.": "Нет журналов для этого запуска.", + "Logs are kept for {days} days after execution and then deleted.": "Журналы хранятся в течение {days} дней после выполнения, а затем удаляются.", + "Run Details": "Запустить подробно", + "Iteration": "Итерация", + "Done": "Готово", + "Took": "Принять", + "Running": "Выполняется", + "on latest version": "последняя версия", + "from failed step": "от неудачного шага", + "Recent Runs": "Недавние вызовы", + "No runs found": "Вызовов не найдено", + "Close": "Закрыть", + "OR": "ИЛИ", + "And If": "И ЕСЛИ", + "+ And": "+ И", + "+ Or": "+ ИЛИ", + "(Text) Contains": "(Текст) Содержит", + "(Text) Does not contain": "(Текст) Не содержит", + "(Text) Exactly matches": "(Текст) Точно соответствует", + "(Text) Does not exactly match": "(Текст) Не точно соответствует", + "(Text) Starts with": "(Текст) Начинается с", + "(Text) Does not start with": "(Текст) Не начинается с", + "(Text) Ends with": "(Текст) Заканчивается на", + "(Text) Does not end with": "(Текст) Не заканчивается на", + "(List) Contains": "(Список) Содержит", + "(List) Does not contain": "(Список) не содержит", + "(Number) Is greater than": "(Число) Больше чем", + "(Number) Is less than": "(Число) Меньше чем", + "(Number) Is equal to": "(Число) Равно", + "(Date/time) After": "Дата/время) после", + "(Date/time) Before": "Дата/время) перед", + "(Date/time) Equals": "Дата/Время) Равно", + "(Boolean) Is true": "(Булево) Истина", + "(Boolean) Is false": "(Булево) Ложь", + "(List) Is empty": "(Список) пуст", + "(List) Is not empty": "(Список) не пустой", + "Exists": "Существует", + "Does not exist": "Не существует", + "Incomplete condition": "Неполное условие", + "First value": "Первое значение", + "Second value": "Второе значение", + "Case sensitive": "Регистрозависимый", + "Execute If": "Выполнить если", + "The package name is required": " Необходимо указать имя пакета ", + "Success": "Успешно", + "Package added successfully": "Пакет успешно добавлен", + "Could not fetch package version": "Не удалось получить версию пакета", + "Add NPM Package": "Добавить NPM-пакет", + "Type the name of the npm package you want to add.": "Введите имя npm пакета, который вы хотите добавить.", + "Package Name": "Имя пакета", + "The latest version will be fetched and added": "Последняя версия будет загружена и добавлена", + "Add": "Добавить", + "Code": "Код", + "Dependencies": "Зависимости", + "Use code": "Использовать код", + "Add package": "Добавить пакет", + "Inputs": "Вводы", + "Edit Step Name": "Введите имя шага", + "Edit Branch Name": "Изменить имя ветки", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Выберите элементы для итерации с предыдущего шага, нажав на ввод **элементов**, который должен быть **списком** элементов.\n\nЦикл будет повторяться по каждому элементу в списке и выполнять следующий шаг для каждого элемента.", + "Items": "Элементы", + "Select an array of items": "Выберите массив элементов", + "Reconnect": "Переподключить", + "Select a connection": "Выберите подключение", + "Create Connection": "Создать подключение", + "Rename": "Переименовать", + "Move": "Переместить", + "Add Branch": "Добавить ветку", + "Execute": "Выполнить", + "Only the first (left) matching branch": "Только первая (слева), соответствующая ветке", + "All matching paths from left to right": "Все подходящие пути слева направо", + "Branches": "Ветки", + "{field} is required": "Требуется {field}", + "Tool Sample Data": "Образцы данных инструмента", + "Fill in the following fields to use them as sample data for the trigger.": "Заполните следующие поля, чтобы использовать их в качестве образца данных для триггера.", + "No input fields defined in the schema": "Не заданы поля ввода, определенные в схеме", + "Save": "Сохранить", + "Test Environment": "Тестовая среда", + "Assigned to": "Назначено", + "(Me)": "(Ме)", + "Please select status to resolve the todo": "Пожалуйста, выберите статус для разрешения задачи", + "Resolve": "Решить", + "Change status to resolved": "Изменить статус на разрешен", + "Send Sample Data to Webhook": "Отправить примеры данных в Webhook", + "Method": "Метод", + "Query Params": "Параметры запроса", + "Headers": "Заголовки", + "Body": "Тело", + "Type": "Тип", + "JSON": "JSON", + "Text": "Текст", + "Form Data": "Данные формы", + "Generate Sample Data": "Сгенерировать пример данных", + "Test Step": "Тестовый шаг", + "Retest": "Повторить тест", + "Testing Failed": "Тест не пройден\n", + "Tested Successfully": "Тест пройден", + "Logs": "Логи", + "There is no sample data available found for this trigger.": "Примеры данных для этого триггера не найдены.", + "Internal error, please try again later.": "Внутренняя ошибка, повторите попытку позже.", + "Failed to run test step and no error message was returned": "Не удалось запустить тестовый шаг и не было возвращено сообщение об ошибке", + "Please fix inputs first": "Пожалуйста, исправьте входные данные", + "No sample data available": "Образец данных не доступен", + "Old results were removed, retest for new sample data": "Старые результаты были удалены, перетестируйте новые данные образца", + "Result #": "Результат #", + "The sample data can be used in the next steps.": "Примеры данных можно использовать на следующих этапах.", + "Testing Trigger": "Тестирование триггера", + "Action Required": "Требуется действие", + "testPieceWebhookTriggerNote": "Пожалуйста, перейдите на {pieceName} и триггер {triggerName}", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Отправить данные на URL-адрес webhook для генерации образцов данных для использования в следующих шагах", + "Test Trigger": "Тест триггера", + "Use Mock Data": "Использовать данные док-станции", + "Load Sample Data": "Загрузка примера данных", + "Test Tool": "Тестовый инструмент", + "home": "домой", + "Home": "Главная", + "Alerts": "Оповещения", + "Releases": "Релизы", + "Flows": "Потоки", + "Products": "Товары", + "MCP": "MCP", + "Tables": "Таблицы", + "Todos": "Todos", + "Push to Git": "Отправить (push) в Git", + "Move To": "Переместить в", + "Duplicating": "Дублирование", + "Import": "Импорт", + "Exporting": "Экспортирование", + "Export": "Экспорт", + "Share": "Поделиться", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Вы уверены, что хотите удалить этот поток? Это навсегда удалит поток, все данные и фоновые выполнения.", + "You are on a development branch, this will also delete the flow from the remote repository.": "Вы находитесь в ветке разработки, это также удалит поток из удаленного репозитория.", + "flow": "поток", + "Community Support": "Поддержка сообщества", + "Overview": "Общий обзор", + "Projects": "Проекты", + "Users": "Пользователи", + "Setup": "Настройка", + "Branding": "Брендинг", + "Global Connections": "Глобальные соединения", + "Pieces": "Части", + "Templates": "Шаблоны", + "License Key": "Лицензионный ключ", + "Security": "Безопасность", + "Audit Logs": "Журнал аудита", + "Single Sign On": "Одиночный вход", + "Signing Keys": "Подписание ключей", + "Project Roles": "Роли проекта", + "API Keys": "Ключи API", + "Infrastructure": "Инфраструктура", + "Workers": "Рабочие", + "Health": "Здоровье", + "Billing": "Биллинг", + "Settings": "Настройки", + "Contact Sales": "Связаться с продажами", + "General": "Общие положения", + "Appearance": "Внешний вид", + "Team": "Команда", + "Environments": "Окружения", + "Project Settings": "Настройки проекта", + "Exit Platform Admin": "Выйти из админа платформы", + "Enter Platform Admin": "Введите администратора платформы", + "Logout": "Выйти", + "Misc": "Прочие", + "Connections": "Соединения", + "days": "дней", + "hours": "часов", + "minutes": "минуты", + "Today": "Сегодня", + "Tasks": "Задачи", + "AI Credits": "AI Credits", + "Usage resets in": "Использование сбрасывается в", + "Manage": "Управлять", + "Unlimited": "Неограниченный", + "Enabled": "Включено", + "Disabled": "Отключено", + "Could not claim the authorization code, make sure you have correct settings and try again.": "Не удалось получить код авторизации, убедитесь, что у вас есть правильные настройки и повторите попытку.", + "Connection failed with error {msg}": "Ошибка соединения с ошибкой {msg}", + "You don't have the permission to create a connection.": "У вас нет разрешения на создание соединения.", + "Reconnect {displayName} Connection": "Reconnect {displayName}", + "Connect to {displayName}": "Подключиться к {displayName}", + "Connection Name": "Имя подключения", + "Connection name": "Имя соединения", + "New Connection": "Новое соединение", + "Redirect URL": "URL перенаправления", + "Client ID": "ID клиента", + "Client Secret": "Секрет клиента", + "Connect": "Подключиться", + "Disconnect": "Отключиться", + "I would like to use my own App Credentials": "Я хотел бы использовать свои учётные данные приложения", + "I would like to use predefined App Credentials": "Я хотел бы использовать предопределенные учетные данные приложений", + "Permission needed": "Требуется разрешение", + "Connections replaced successfully": "Соединения успешно заменены", + "Error": "Ошибка", + "Failed to replace connections": "Не удалось заменить соединения", + "Failed to get affected flows": "Не удалось получить затронутые потоки", + "Please select a piece": "Пожалуйста, выберите фигуру", + "Please select a connection to replace": "Пожалуйста, выберите соединение для замены", + "Please select a connection to replace with": "Пожалуйста, выберите соединение для замены", + "Replace Connections": "Заменить соединения", + "Confirm Replacement": "Подтвердить замену", + "Replace one connection with another.": "Замените одно соединение с другом.", + "This action requires ": "Это действие требует ", + "reconnecting": "переподключение", + " any associated MCP pieces.": " любые связанные детали MCP.", + "Piece": "Часть", + "Select a piece": "Выберите фигуру", + "Connection to Replace": "Подключение к Замена", + "Choose connection to replace": "Выберите соединение для замены", + "Replaced With": "Заменено на", + "Choose connection to replace with": "Выберите соединение для замены на", + "All flows will be changed to use the replaced with connection": "Все потоки будут изменены, чтобы использовать замененные соединения", + "Next": "Следующий", + "No flows will be affected by this change": "Это изменение не повлияет на потоки", + "Back": "Назад", + "Unnamed tool": "Безымянный инструмент", + "This flow is enabled": "Этот поток включен", + "Enable this flow to make it available": "Включите этот поток, чтобы сделать его доступным", + "Piece is updated successfully": "Часть успешно обновлена", + "Piece is added successfully": "Часть успешно добавлена", + "Failed to update piece": "Не удалось обновить часть", + "Failed to add piece": "Не удалось добавить фигуру", + "Please select a connection": "Пожалуйста, выберите соединение", + "Your MCP server already has this piece": "У вашего MCP сервера уже есть этот фрагмент", + "+ New Connection": "+ Новое соединение", + "Edit Piece": "Изменить часть", + "Add Piece": "Добавить фигуру", + "Connection": "Подключение", + "MCP piece": "MCP штучка", + "Failed to update piece status": "Не удалось обновить статус части", + "Connection required": "Требуется подключение", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Вы уверены, что хотите удалить этот инструмент из MCP? если вы удалите его, вы не сможете использовать его в своем клиенте MCP.", + "piece": "кусок", + "Connect your AI assistant to external services": "Подключите своего помощника ИИ к внешним сервисам", + "Collapse": "Свернуть", + "Show All": "Показать все", + "Server URL": "URL сервера", + "Hide the token for security": "Скрыть токен для безопасности", + "Show the token": "Показать токен", + "Generate a new token for security. This will invalidate the current URL.": "Генерировать новый токен для безопасности. Это приведет к недействию текущего URL.", + "URL copied to clipboard": "URL скопирован в буфер обмена", + "Copy URL to clipboard": "Скопировать URL в буфер обмена", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "Этот URL содержит секретный токен безопасности. Поделитесь им только с доверенными приложениями и сервисами. Повернуть токен, если вы подозреваете, что он был скомпрометирован.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "После внесения изменений в соединения или потоки, вам нужно будет переподключить ваш MCP сервер, чтобы изменения вступили в силу.", + "Max tables reached": "Достигнуто максимальное количество таблиц", + "You can't create more than {maxTables} tables": "Вы не можете создать более чем таблицы {maxTables}", + "Name": "Наименование", + "Created": "Создано", + "Delete Tables": "Удалить таблицы", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Вы уверены, что хотите удалить выбранные таблицы? Это действие не может быть отменено.", + "table": "таблица", + "Create and manage your tables to store your automation data": "Создавайте и управляйте таблицами для хранения данных автоматизации", + "New Table": "Новая таблица", + "No tables have been created yet": "Таблицы еще не созданы", + "Create a table to get started and start managing your automation data": "Создайте таблицу, чтобы начать управление данными автоматизации", + "Error deleting connections": "Ошибка удаления подключений", + "Status": "Статус", + "Display Name": "Показать имя", + "Owner": "Владелец", + "App": "Приложение", + "This connection is global and can be managed in the platform admin": "Это соединение является глобальным и может управляться администратором платформы", + "External ID": "Внешний ID", + "Connected At": "Подключено в", + "Confirm Deletion": "Подтвердите удаление", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Вы уверены, что хотите удалить выбранные соединения? Это действие нельзя отменить.", + "Deleting connections may cause your Flows or MCP tools to break.": "Удаление соединений может привести к разрыву ваших Flows или MCP инструментов.", + "Manage project connections to external systems.": "Управление подключением проекта к внешним системам.", + "No connections found": "Соединений не найдено", + "Come back later when you create a automation to manage your connections": "Возвращайтесь позже, когда вы создаете автоматизацию для управления подключениями", + "Steps": "Шаги", + "Folder": "Папка", + "Flow name": "Имя потока", + "No flows found": "Потоков не найдено", + "Create a workflow to start automating": "Создать рабочий процесс для запуска автоматизации", + "Create and manage your flows, run history and run issues": "Создавайте и управляйте процессами, запускайте историю и задачи", + "Issues": "Проблемы", + "Untitled": "Без названия", + "Create flow": "Создать поток", + "From scratch": "С нуля", + "Use a template": "Использовать шаблон", + "From local file": "Из локального файла", + "Flow Name": "Имя потока", + "Count": "Счетчик", + "First Seen": "Первый раз виден", + "Last Seen": "Последнее посещение", + "Issues in {flowDisplayName} is marked as resolved.": "Замечания в {flowDisplayName} помечены как решенные.", + "Unlock Issues": "Разблокировать задачи", + "Track issues in your workflows and troubleshoot them.": "Отслеживайте проблемы в рабочих процессах и их устранение.", + "No issues found": "Замечаний не найдено", + "All your workflows are running smoothly.": "Все ваши рабочие процессы работают гладко.", + "Mark as Resolved": "Пометить как решенные", + "All Flows Are Turned Off": "Все Потоки выключены", + "Task Usage Exceeded": "Превышен лимит использования задания", + "of the Allowed Limit.": "разрешенный лимит.", + "When a project tasks limit is reached,": "По достижении лимита задач проекта,", + "all flows will be turned off and you will not be able to run any flows.": "все потоки будут отключены, и вы не сможете запускать потоки.", + "Please visit": "Пожалуйста, посетите", + "Your Plan": "Ваш план", + "and increase your task limit, which requires your payment details.": "и увеличите лимит заданий, который требует ваших платежных реквизитов.", + "Please contact your admin to increase the project task limit.": "Пожалуйста, свяжитесь с вашим администратором, чтобы увеличить лимит задач проекта.", + "and increase the project task limit.": "и увеличить лимит задач проекта.", + "Dismiss": "Отклонить", + "Token rotated successfully": "Токен успешно повернут", + "Failed to rotate token": "Не удалось повернуть токен", + "Piece removed successfully": "Часть успешно удалена", + "Failed to remove piece": "Не удалось удалить часть", + "Flow created successfully": "Поток успешно создан", + "Failed to create flow": "Не удалось создать поток", + "Add Flow": "Добавить поток", + "Let your AI assistant trigger automations": "Пусть ваш помощник ИИ запустит автоматизацию", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Подключитесь к серверу MCP с помощью любого клиента MCP для общения с инструментами", + "MCP Server": "MCP сервер", + "My Tools": "Мои инструменты", + "Create Flow": "Создать поток", + "Note": "Примечание", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "Если вы хотите показать свой MCP сервер в интернете, пожалуйста, установите переменную окружения AP_FRONTEND_URL на публичный URL экземпляра Activepieces .", + "This URL grants access to your tools and data. Only share with trusted applications.": "Этот URL-адрес предоставляет доступ к вашим инструментам и данным. Только для доверенных приложений.", + "Server Configuration": "Конфигурация сервера", + "Hide sensitive data": "Скрыть конфиденциальные данные", + "Show sensitive data": "Показать конфиденциальные данные", + "Create a new URL. The current one will stop working.": "Создайте новый URL. Текущий URL перестанет работать.", + "Copy configuration": "Копировать конфигурацию", + "Configuration copied to clipboard": "Конфигурация скопирована в буфер обмена", + "Claude": "Клод", + "Cursor": "Cursor", + "Windsurf": "Виндсерф", + "Server/Other": "Сервер/Другая", + "Note: MCPs only work with": "Примечание: MCPs работают только с", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", а не веб-версии.", + "Prerequisites:": "Требования:", + "Install": "Установить", + "Node.js": "Node.js", + "and": "и", + "Open Settings:": "Открыть настройки:", + "Click the menu and select": "Нажмите на меню и выберите", + "Developer": "Разработчик", + "Configure MCP:": "Настройка MCP:", + "Click": "Click", + "Edit Config": "Изменить конфигурацию", + "and paste the configuration below": "и вставьте конфигурацию ниже", + "Save and Restart:": "Сохранить и перезапустить:", + "Save the config and restart Claude Desktop": "Сохранить конфигурацию и перезапустить Claude Desktop", + "Navigate to": "Перейти к", + "Cursor Settings": "Настройки курсора", + "Add Server:": "Добавить сервер:", + "Add new global MCP server": "Добавить новый глобальный MCP сервер", + "Configure:": "Конфигурация", + "Paste the configuration below and save": "Вставьте конфигурацию ниже и сохраните", + "Use either method:": "Использовать любой метод:", + "Go to": "Перейти к", + "Advanced Settings": "Расширенные настройки", + "Open Command Palette and select": "Открыть Палитру команд и выбрать", + "Windsurf Settings Page": "Страница настроек Windsurf", + "Navigate to Cascade:": "Перейти в Cascade:", + "Select": "Выбрать", + "Cascade": "Cascade", + "in the sidebar": "в боковой панели", + "Add Server": "Добавить сервер", + "Add custom server +": "Добавить пользовательский сервер +", + "Copy URL": "Копировать URL", + "Client Setup Instructions": "Инструкции по настройке клиента", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "После изменения соединений или потоков переподключите сервер MCP, чтобы изменения вступили в силу.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Выполните следующие шаги, чтобы настроить MCP в вашем любимом клиенте. Это позволит ассистенту AI получить доступ к вашим инструментам.", + "icon": "иконка", + "Unlock Analytics": "Разблокировать аналитику", + "Get insights into your platform usage and performance with our analytics dashboard": "Получите информацию об использовании и производительности вашей платформы с нашей панели аналитики", + "Active Flows": "Активные Потоки", + "The number of enabled flows in the platform": "Количество включенных потоков в платформе", + "Active Projects": "Активные проекты", + "The number of projects with at least one enabled flow": "Количество проектов с включенным по крайней мере одним потоком", + "Active Users": "Активные пользователи", + "The number of users logged in the last 30 days": "Количество пользователей, вошедших в систему за последние 30 дней", + "Out of {totalusers} total users": "Из всего {totalusers} пользователей", + "Pieces Used": "Использовано частей", + "The number of unique pieces used across all flows": "Количество уникальных частей, используемых во всех потоках", + "Flows with AI": "Потоки с ИИ", + "The number of enabled flows that use AI pieces": "Количество включенных потоков, использующих части ИИ", + "Metrics": "Метрики", + "Executed Tasks": "Выполненные задачи", + "Showing total executed tasks for specified time range": "Показано общее количество выполненных задач за указанный промежуток времени", + "Tasks Usage Limit": "Лимит использования задач", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Укажите месячный лимит для задач, чтобы избежать чрезмерного использования. Ваши потоки больше не будут выполняться, если этот лимит был достигнут.", + "Number of monthly tasks": "Количество ежемесячных задач", + "Save changes": "Сохранить изменения", + "Limits updated successfully": "Лимиты успешно обновлены", + "Failed to update limits": "Не удалось обновить ограничения", + "Failed to load billing information": "Не удалось загрузить платежную информацию", + "Billing Amount": "Сумма плательщика", + "Manage Payment Details": "Управление платежными реквизитами", + "Add Payment Details": "Добавить платежные реквизиты", + "Current Task Usage": "Текущее использование задания", + "Count of executed steps": "Количество выполненных шагов", + "Billing Limit": "Лимит оплаты", + "Edit": "Редактирование", + "Add Limit": "Добавить предел", + "Current Credit Usage": "Текущее использование кредита", + "WebSocket Connection": "Соединение с WebSocket", + "Connected": "Подключено", + "Disconnected": "Отключено", + "No issues detected": "Проблемы не обнаружены", + "Check the status of your platform and its components": "Проверьте состояние вашей платформы и ее компонентов", + "System Health Status": "Состояние здоровья системы", + "All systems are running smoothly": "Все системы работают плавно", + "Check the health of your worker machines": "Проверьте здоровье ваших машин", + "Workers Machine": "Рабочий Машина", + "This is demo data. In a real environment, this would show your actual worker machines.": "Это демо данные. В реальном окружении это покажет ваши фактические рабочие машины.", + "No workers found": "Работников не найдено", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "У вас пока нет рабочих машин. Попробуйте новые машины, чтобы выполнить автоматизацию", + "IP Address": "IP-адрес", + "CPU Usage": "Загрузка ЦП", + "Disk Usage": "Использование диска", + "RAM Usage": "RAM Usage", + "Last Contact": "Последний контакт", + "Configs": "Конфигурации", + "Environment Variables": "Переменные окружения", + "Websocket Connection Error": "Ошибка соединения с Websocket", + "Retry Connection": "Повторить подключение", + "Update Available": "Доступно обновление", + "Update Now": "Обновить сейчас", + "Your Universal AI needs a quick setup": "Ваш универсальный AI необходим быстрый набор", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "Мы заметили, что вы еще не создали ни одного AI провайдера. Чтобы разблокировать Universal AI части для вашей команды, вам нужно сначала настроить некоторые учетные данные провайдера.", + "Configure": "Настроить", + "Platform Alerts": "Предупреждения платформы", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "Есть важные оповещения, которые требуют вашего внимания. Пожалуйста, проверьте раздел оповещения в разделе \"Платформа администратор\".", + "View Alerts": "Просмотр оповещений", + "Used Tasks": "Используемые задачи", + "Used AI Credits": "Использованы ИИ Кредиты", + "Cannot delete active project, switch to another project first": "Не удается удалить активный проект, сначала переключитесь на другой проект", + "Delete Projects": "Удалить проекты", + "Are you sure you want to delete the selected projects?": "Вы уверены, что хотите удалить выбранные проекты?", + "New Project": "Новый проект", + "Validation error": "Ошибка проверки", + "Project has enabled flows. Please disable them first.": "Проект включил потоки. Пожалуйста, сначала отключите их.", + "This project is active. Please switch to another project first.": "Этот проект активен. Пожалуйста, сначала переключитесь на другой проект.", + "Unlock Projects": "Разблокировать Проекты", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Организуйте свои команды автоматизации по проектам своими потоками, соединениями и квотами использования", + "Manage your automation projects": "Управление вашими проектами автоматизации", + "No projects found": "Проектов не найдено", + "Start by creating projects to manage your automation teams": "Начните с создания проектов для управления вашей командой автоматизации", + "Edit project": "Редактировать проект", + "Name is required": "Требуется имя", + "Create New Project": "Создать новый проект", + "Project Name": "Название проекта", + "Id": "Id", + "Enable API Keys": "Включить ключи API", + "Create and manage API keys to access Activepieces APIs.": "Создание и управление ключами API для доступа к Activepieces API.", + "New Api Key": "Новый ключ Api", + "No API keys found": "Ключи API не найдены", + "Start by creating an API key to communicate with Activepieces APIs": "Начните с создания ключа API для общения с Activepieces API", + "Delete API Key": "Удалить ключ API", + "Are you sure you want to delete this API key?": "Вы уверены, что хотите удалить этот ключ API?", + "API Key": "Ключ API", + "API Key Created": "Ключ API создан", + "Create New API Key": "Создать новый ключ API", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Пожалуйста, сохраните этот секретный ключ где-то в безопасном месте и доступном месте по соображениям безопасности", + "you won't be able to view it again after closing this dialog.": "вы не сможете увидеть его снова после закрытия этого диалогового окна.", + "API Key Name": "Имя ключа API", + "Action": "Действие", + "Performed By": "Выполнено", + "Project": "Проект", + "Unlock Audit Logs": "Разблокировать журналы аудита", + "Comply with internal and external security policies by tracking activities done within your account": "Соблюдайте внутреннюю и внешнюю политику безопасности, отслеживая действия, сделанные в вашем аккаунте", + "Track activities done within your platform": "Отслеживайте действия, выполняемые в вашей платформе", + "No audit logs found": "Журналы аудита не найдены", + "Come back later when you have some activity to audit": "Возвращайтесь позже, когда у вас есть какие-то действия для аудита", + "Resource": "Ресурс", + "Details": "Детали", + "N/A": "Н/Д", + "Flow Run": "Запуск потока", + "Flow": "Поток", + "User": "Пользователь", + "Signing Key": "Подписание ключа", + "Project Role Management": "Управление ролями проекта", + "Define custom roles and permissions to control what your team members can access and modify": "Определите пользовательские роли и права для управления тем, что члены вашей команды могут получить доступ и изменить", + "Define custom roles and permissions that can be assigned to your team members": "Определите пользовательские роли и права доступа, которые могут быть назначены членам вашей команды", + "New Role": "Новая роль", + "Contact sales to unlock custom roles": "Связаться с продажами для разблокировки пользовательских ролей", + "Create New Role": "Создать новую роль", + "View ": "Вид ", + "Edit ": "Редактирование ", + "Role Name": "Название роли", + "Permissions": "Права доступа", + "None": "Нет", + "Read": "Чтение", + "Write": "Написать", + "Create": "Создать", + "Email": "Почта", + "First Name": "First Name", + "Last Name": "Фамилия", + "Roles": "Роли", + "View the users assigned to this role": "Просмотр пользователей, назначенных этой роли", + "Role": "Роль", + "No users found": "Пользователей не найдено", + "Starting by assigning users to this role": "Начиная с назначения пользователей этой роли", + "Project Role entry deleted successfully": "Запись роли проекта успешно удалена", + "Updated": "Обновлено", + "No project roles found": "Роли проекта не найдены", + "Create custom project roles to manage permissions for platform users": "Создать пользовательские роли проекта для управления правами доступа пользователей платформы", + "Show Users": "Показать пользователей", + "View Role": "Просмотр роли", + "Edit Role": "Изменить роль", + "Delete Role": "Удалить роль", + "Project Role": "Роль проекта", + "Unlock Embedding Through JS SDK": "Разблокировать встраивание через JS SDK", + "Enable signing keys to access embedding functionalities.": "Разрешить подписывать ключи для доступа к встроенным функциям.", + "New Signing Key": "Новый ключ подписи", + "No signing keys found": "Не найдено ни одного ключа подписи", + "Create a signing key to authenticate users with embedding": "Создать ключ подписи для аутентификации пользователей встраиванием", + "Delete Signing Key": "Удалить ключ подписи", + "Are you sure you want to delete this signing key?": "Вы уверены, что хотите удалить этот подписывающий ключ?", + "Signing Key Created": "Ключ подписи создан", + "Create New Signing Key": "Создать новый ключ подписи", + "Signing Key Name": "Название подписи ключа", + "Allowed domains updated": "Разрешенные домены обновлены", + "Update": "Обновить", + "Enable": "Включить", + "Configure Allowed Domains": "Настройка разрешенных доменов", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Введите разрешенные домены для аутентификации пользователей, пустой список разрешит все домены.", + "Add Domain": "Добавить домен", + "Allow logins through {providerName}'s single sign-on functionality.": "Разрешить вход с помощью функции единого входа {providerName}.", + "Email authentication updated": "Проверка подлинности электронной почты обновлена", + "Enable Single Sign On": "Включить единый вход", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Позвольте вашим пользователям войти с помощью вашего текущего SSO провайдера или предоставить им доступ для регистрации", + "Allowed Domains": "Разрешенные домены", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Разрешить пользователям аутентифицироваться с определенными доменами. Оставьте пустым, чтобы разрешить все домены.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Допустимый Логин Email", + "Allow logins through email and password.": "Разрешить вход по электронной почте и паролю.", + "Single sign on settings updated": "Настройки единого входа обновлены", + "Disable": "Отключено", + "Configure {provider} SSO": "Настроить {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Подробнее о том, как настроить {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} ID клиента", + "{provider} Client Secret": "Секрет клиента {provider}", + "Single sign-on settings updated": "Настройки единого входа обновлены", + "Configure SAML 2.0 SSO": "Настройка SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Инструкции по установке**:\nПожалуйста, проверьте следующую документацию: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**URL-адрес единого сигнала**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "Метаданные IDP", + "IDP Certificate": "Сертификат IDP", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "Базовый URL", + "Resource Name": "Название ресурса", + "Deployment Name": "Название развертывания", + "Saving": "Сохранение", + "Activepieces Copilot": "Активные копилот", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot сконфигурирован и готов помочь вашим пользователям быстрее строить потоки с помощью AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Настройте Activepieces Copilot, чтобы помочь вашим пользователям быстрее создавать потоки с помощью AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Установите настройки AI провайдеров и копилотов, чтобы ваши пользователи наслаждались бесшовным опытом в строительстве с нашими универсальными единицами AI", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Установите учетные данные провайдера, которые будут использоваться универсальными ИИ, т.е. текстовым AI.", + "AI Providers": "AI Providers", + "Copilot": "Копилот", + "Configure credentials for {providerName} AI provider.": "Configure credentials for {providerName} AI provider.", + "Update AI Provider": "Обновить поставщика AI", + "Enable AI Provider": "Включить поставщика AI", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Пожалуйста, введите правильный домен", + "Your changes have been saved.": "Ваши изменения были сохранены.", + "The domain is already added.": "Домен уже добавлен.", + "Add Custom Domain": "Добавить пользовательский домен", + "Enter a domain name without a protocol (e.g. example.com)": "Введите имя домена без протокола (например, example.com)", + "Logo URL": "URL логотипа", + "Icon URL": "URL значка", + "Favicon URL": "URL значка", + "Default Language": "Язык по умолчанию", + "Select Language": "Выберите язык", + "No Languages": "Нет языков", + "Primary Color": "Основной цвет", + "Custom Domains": "Пользовательские домены", + "No domains added yet.": "Домены еще не добавлены.", + "Verified": "Подтверждено", + "Pending, please contact the support for dns verification.": "В ожидании, обратитесь в службу поддержки для верификации dns.", + "Are you sure you want to delete {domain}?": "Вы уверены, что хотите удалить {domain}?", + "Brand Activepieces": "Активные образцы бренда", + "Give your users an experience that looks like you by customizing the color, logo and more": "Дайте вашим пользователям опыт, который похож на вас, настраивая цвет, логотип и многое другое", + "Configure the appearance and SMTP settings for your platform.": "Настройка внешнего вида и параметров SMTP для вашей платформы.", + "Invalid host": "Неверный хост", + "Invalid username": "Неверное имя пользователя", + "Invalid password": "Неверный пароль", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Неверное имя отправителя", + "SMTP is configured": "SMTP настроен", + "Mail Server": "Почтовый сервер", + "Set up your SMTP settings to send emails from your domain.": "Настройте параметры SMTP для отправки писем с вашего домена.", + "Disable Mail Server": "Отключить почтовый сервер", + "Are you sure you want to disable your mail server?": "Вы уверены, что хотите отключить почтовый сервер?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "Это не позволит вам отправлять письма с ошибками, лимитами квот, приглашениями и забыть пароль.", + "mail server": "почтовый сервер", + "Host": "Хост", + "Port": "Порт", + "Username": "Имя пользователя", + "Password": "Пароль", + "Sender Email": "Email отправителя", + "Sender Name": "Имя отправителя", + "Enable Global Connections": "Включить глобальные соединения", + "Manage platform-wide connections to external systems.": "Управление подключениями на платформе к внешним системам.", + "No global connections found": "Глобальные соединения не найдены", + "Create a global connection that can be shared to multiple projects": "Создать глобальное соединение, которое может быть доступно для нескольких проектов", + "License key is invalid": "Неверный лицензионный ключ", + "Invalid license key": "Неверный лицензионный ключ", + "License activated!": "Лицензия активирована!", + "Activate License Key": "Активировать лицензионный ключ", + "Let the magic begin!": "Пусть магия началась!", + "Activate": "Активировать", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "Эта функция еще не используется в облаке, пожалуйста, напишите на sales@activepieces.com. ", + "This feature is not available in your current edition. ": "Эта функция недоступна в текущей редакции. ", + "Learn how to upgrade": "Узнайте, как обновить", + "Activate your platform and unlock enterprise features": "Активируйте свою платформу и разблокируйте возможности предприятия", + "Activate License": "Активировать лицензию", + "Expiration": "Истечение", + "Valid until": "Действителен до", + "Expired": "Истёк", + "Expires soon": "Скоро истекает", + "Features": "Возможности", + "Applying Tags...": "Применение тегов...", + "Tags applied.": "Теги применены.", + "Tag created": "Тег создан", + "Tag": "Тег", + "Tags": "Теги", + "Control Pieces": "Контрольные части", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Покажите детали, которые важны для ваших пользователей и скрыть те, что вам не нравится.", + "Manage the pieces that are available to your users": "Управление частями, доступными вашим пользователям", + "Start by installing pieces that you want to use in your automations": "Начните с установки частей, которые вы хотите использовать в автоматизации", + "Piece Name": "Имя части", + "Hide this piece from all projects": "Скрыть эту часть из всех проектов", + "Show this piece for all projects": "Показать эту часть для всех проектов", + "Unpin this piece": "Открепить эту часть", + "Pin this piece": "Закрепить эту часть", + "Pieces synced": "Части синхронизированы", + "Pieces have been synced from the activepieces cloud.": "Части были синхронизированы из облака активных элементов.", + "OAuth2 Credentials Deleted": "Учётные данные OAuth2 удалены", + "OAuth2 Credentials Updated": "Учетные данные OAuth2 обновлены", + "Configure OAuth2 APP": "Настроить приложение OAuth2", + "Delete OAuth2 APP": "Удалить приложение OAuth2", + "Templates deleted successfully": "Шаблоны успешно удалены", + "Delete Templates": "Удалить шаблоны", + "Are you sure you want to delete the selected templates?": "Вы уверены, что хотите удалить выбранные шаблоны?", + "New Template": "Новый шаблон", + "Unlock Templates": "Открыть шаблоны", + "Convert the most common automations into reusable templates 1 click away from your users": "Конвертируйте наиболее распространенные автоматизации в повторно используемые шаблоны 1 клик от ваших пользователей", + "Convert the most common automations into reusable templates": "Конвертировать наиболее распространенные автоматизации в повторно используемые шаблоны", + "No templates found": "Шаблоны не найдены", + "Create a template for your user to inspire them": "Создайте шаблон для вашего пользователя, чтобы вдохновить его", + "Edit template": "Изменить шаблон", + "Template is required": "Требуется шаблон", + "Update New Template": "Обновить новый шаблон", + "Create New Template": "Создать новый шаблон", + "Template Name": "Название шаблона", + "Description": "Описание", + "Template Description": "Описание шаблона", + "Blog URL": "URL блога", + "Template Blog URL": "URL блога шаблона", + "Template": "Шаблон", + "Invalid JSON": "Неверный JSON", + "User deleted successfully": "Пользователь успешно удален", + "User activated successfully": "Пользователь успешно активирован", + "User deactivated successfully": "Пользователь успешно деактивирован", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Управление вашими пользователями и их доступ к вашим проектам", + "Start inviting users to your project": "Начните приглашать пользователей в ваш проект", + "External Id": "Внешний ID", + "Admin": "Админ", + "Member": "Участник", + "Activated": "Активирован", + "Deactivated": "Отключено", + "Edit user": "Изменить пользователя", + "Admin cannot be deactivated": "Администратор не может быть деактивирован", + "Deactivate user": "Деактивировать пользователя", + "Activate user": "Активировать пользователя", + "Delete User": "Удалить пользователя", + "Are you sure you want to delete this user?": "Вы уверены, что хотите удалить этого пользователя?", + "Delete user": "Удалить пользователя", + "Update User Role": "Изменить роль пользователя", + "Meeting Summary Flow": "Сводный поток встречи", + "Added new features and fixed bugs": "Добавлены новые возможности и исправлены ошибки", + "Flows Changes": "Изменения в динамике", + "Connections Changes": "Изменения подключений", + "New connections are placeholders and need to be reconnected again": "Новые соединения являются заполнителями и должны быть снова подключены", + "renamed to": "переименовано в", + "Tables Changes": "Изменения в таблицах", + "No changes to apply": "Нет изменений для применения", + "Apply Changes": "Применить изменения", + "Create Git Release": "Создать Git релиз", + "Create Project Release": "Создать выпуск проекта", + "Create Rollback to": "Создать откат", + "Source": "Источник", + "Rollback": "Rollback", + "Imported At": "Импортировано", + "Imported By": "Импортирован", + "Track and manage your project version history and deployments. ": "Отслеживайте и управляйте историей версий проекта. ", + "Environments & Releases": "Окружения и релизы", + "Project Releases": "Релизы проекта", + "Create Release": "Создать релиз", + "From Git": "Из Git", + "From Project": "Из Проекта", + "No project releases found": "Релизы проекта не найдены", + "Create a project release to get started": "Создайте релиз проекта, чтобы начать", + "Please select project": "Пожалуйста, выберите проект", + "No Changes Found": "Изменения не найдены", + "There are no differences to apply": "Нет различий для применения", + "Please select a project": "Пожалуйста, выберите проект", + "Search projects...": "Поиск проектов...", + "Review Changes": "Просмотреть изменения", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Импортировано", + "from": "от", + "No description provided": "Описание не указано", + "Invitation only sign up": "Регистрация только по приглашению", + "Please ask your administrator to add you to the organization.": "Попросите администратора добавить вас в организацию.", + "Something went wrong, please try again.": "Что-то пошло не так, попробуйте еще раз.", + "Please try again.": "Пожалуйста, попробуйте еще раз.", + "Please enter a valid email address": "Пожалуйста, введите действительный адрес электронной почты", + "The email is already added.": "Адрес электронной почты уже добавлен.", + "Add email": "Добавить email", + "Only project admins can do this": "Только администраторы проекта могут сделать это", + "Add Alert Email": "Добавить уведомление по электронной почте", + "Enter the email address to receive alerts.": "Введите адрес электронной почты для получения оповещений.", + "Add Email": "Добавить Email", + "Emails": "Письма", + "Add email addresses to receive alerts.": "Добавить email адреса для получения оповещений.", + "No emails added yet.": "Нет писем добавлено.", + "Choose what you want to be notified about.": "Выберите, о чем вы хотите получать уведомления.", + "Project and alert permissions are required to change this setting.": "Права доступа для проекта и оповещения необходимы для изменения этой настройки.", + "Every Failed Run": "Каждый неудачный запуск", + "Get an email alert when a flow fails.": "Получить уведомление по электронной почте при сбое процесса.", + "Get an email alert when a new issue created.": "Получить уведомление по электронной почте при создании новой проблемы.", + "Never": "Никогда", + "Turn off email notifications.": "Отключить уведомления по электронной почте.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Настройте внешний вид приложения. Автоматическое переключение между дневными и ночными темами.", + "Select the theme for the dashboard.": "Выберите шаблон панели управления.", + "Light": "Светлая", + "Dark": "Тёмная", + "Select the language that will be used in the dashboard.": "Выберите язык, который будет использоваться в панели управления.", + "Select language": "Выберите язык", + "Search language...": "Поиск языка...", + "No language found.": "Язык не найден.", + "Help us translate Activepieces to your language.": "Помогите нам перевести Activepieces на ваш язык.", + "Learn more": "Узнать больше", + "Git Connection Removed": "Соединение Git удалено", + "Your Git repository has been successfully disconnected": "Репозиторий Git был успешно отключен", + "Enable Environments": "Включить окружения", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Развертывание потоков в разных средах разработки, постановка и производство с контролем версий и командным сотрудничеством", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Подключитесь к Git, чтобы включить управление версиями, резервное копирование ваших потоков и управление множеством окружений. ", + "Repository URL": "URL репозитория", + "Not connected": "Не подключен", + "Project Folder": "Папка проекта", + "Releases Enabled": "Релизы включены", + "You have successfully enabled releases": "Вы успешно включили релизы", + "Enable releases to easily create and manage project releases.": "Включите релизы для создания и управления проектными релизами.", + "The external ID is already taken.": "Внешний ID уже занят.", + "Manage general settings for your project.": "Управление общими настройками для вашего проекта.", + "Used to identify the project based on your SaaS ID": "Используется для идентификации проекта на основе вашего SaaS ID", + "org-3412321": "org-3412321", + "Delete {name}": "Удалить {name}", + "This will permanently delete this piece, all steps using it will fail.": "Это навсегда удалит эту фигуру, все шаги, использующие её, не будут выполнены.", + "Add a piece to your project that you want to use in your automations": "Добавьте часть в ваш проект, который вы хотите использовать в автоматизации", + "Pieces list updated": "Список частей обновлён", + "Manage Pieces": "Управление частями", + "Choose which pieces you want to be available for your current project users": "Выберите, какие части вы хотите быть доступными для пользователей вашего проекта", + "Unlock Team Permissions": "Разблокировать разрешения команды", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "Вы можете бесплатно приглашать пользователей на вашу платформу в разделе сообщества. Для опытных ролей и разрешений требуется пробная версия", + "Project Members": "Участники проекта", + "Invite your team members to collaborate.": "Пригласите членов вашей команды к сотрудничеству.", + "No members are added to this project.": "В этот проект не добавлено ни одного участника.", + "Pending Invitations": "Приглашения в ожидании", + "No pending invitation.": "Нет приглашений, ожидающих подтверждения.", + "templateId is missing": "ID шаблона отсутствует", + "Me Only": "Только я", + "Unresolved": "Нерешенные", + "Resolved": "Решена", + "Title": "Заголовок", + "Date Created": "Дата создания", + "Manage todos for your project that are created by automations": "Управление задачами для вашего проекта, которые создаются по автоматизации", + "No todos found": "Задач не найдено", + "You do not have any pending todos. Great job!": "У вас нет ожидающих заданий. Отличная работа!", + "Write a comment...": "Написать комментарий...", + "Beta": "Бета", + "This feature is still under testing and might be changed often": "Эта функция все еще находится в стадии тестирования и может быть изменена часто", + "Failed to copy to clipboard": "Не удалось скопировать в буфер обмена", + "{number} items selected": "{number} выбранных элементов", + "Select All": "Выделить все", + "No results found.": "Результаты не найдены.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiMultiSelect должен использоваться в MultiSelectProvider", + "Unset": "Сбросить", + "Refresh": "Обновить", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} больше", + "Removed {entityName}": "Удалено {entityName}", + "Download File": "Скачать файл", + "Copied to clipboard": "Скопировано в буфер обмена", + "File is not available after execution.": "Файл не доступен после выполнения.", + "Available for Projects": "Доступно для проектов", + "Select projects": "Выберите проекты", + "No items": "Нет элементов", + "Previous": "Предыдущий", + "to": "до", + "Last Week": "Прошлая неделя", + "Last Month": "Прошлый месяц", + "Last 3 Months": "Последние 3 месяца", + "Last 6 Months": "Последние 6 месяцев", + "Next 7 days": "Следующие 7 дней", + "Next 30 days": "Следующие 30 дней", + "Next 90 days": "Следующие 90 дней", + "Next 180 days": "Следующие 180 дней", + "Select Time Range": "Выберите диапазон времени", + "Clear": "Очистить", + "Download": "Скачать", + "Go to Dashboard": "Перейти к панели управления", + "Select a file": "Выберите файл", + "Press space to separate values": "Нажмите пробел, чтобы разделить значения", + "AM": "АР", + "PM": "ЛС", + "Already have an account?": "Уже зарегистрированы?", + "Sign in": "Войти", + "Don't have an account?": "У вас нет учетной записи?", + "Sign up": "Регистрация", + "Welcome Back!": "С возвращением!", + "Enter your email below to sign in to your account": "Введите адрес электронной почты, чтобы войти в свою учетную запись", + "Let's Get Started!": "Давайте начнём!", + "Create your account and start flowing!": "Создайте свой аккаунт и начните работать!", + "Your password was changed successfully": "Ваш пароль был успешно изменен", + "Your password reset request has expired, please request a new one": "Срок действия вашего запроса на сброс пароля истек, пожалуйста, запросите новый запрос", + "Reset Password": "Сбросить пароль", + "Enter your new password": "Введите новый пароль", + "Password is required": "Требуется пароль", + "Verification email resent, if previous one expired.": "Проверка электронной почты отправлена, если предыдущий срок истек.", + "Password reset link resent, if previous one expired.": "Ссылка для сброса пароля отправлена, если предыдущий срок истек.", + "We sent you a link to complete your registration to": "Мы отправили вам ссылку для завершения вашей регистрации на", + "We sent you a link to reset your password to": "Мы отправили вам ссылку для сброса пароля на", + "Didn't receive an email or it expired?": "Не получили письмо или оно истекло?", + "Resend": "Переслать", + "Please enter your email": "Пожалуйста, введите ваш email", + "Check Your Inbox": "Проверьте папку \"Входящие\"", + "If the user exists we'll send you an email with a link to reset your password.": "Если пользователь уже существует, мы вышлем вам письмо со ссылкой для сброса пароля.", + "Send Password Reset Link": "Отправить ссылку для сброса пароля", + "Back to sign in": "Вернуться к входу", + "Email is invalid": "Неверный адрес эл. почты", + "Something went wrong, please try again later": "Что-то пошло не так, попробуйте позже", + "Invalid email or password": "Неверный email или пароль", + "User has been deactivated": "Пользователь деактивирован", + "Email domain is disallowed": "Домен электронной почты запрещен", + "Email authentication has been disabled": "Аутентификация по электронной почте отключена", + "Forgot your password?": "Забыли пароль?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "Регистрация ограничена. Вам необходимо приглашение для входа. Пожалуйста, свяжитесь с администратором.", + "Email is already used": "Адрес электронной почты уже используется", + "Email authentication is disabled": "Проверка подлинности электронной почты отключена", + "First name is required": "Требуется имя", + "Last name is required": "Требуется фамилия", + "Email is required": "Требуется адрес электронной почты", + "Receive updates and newsletters from activepieces": "Получать обновления и бюллетени от активных частей", + "By creating an account, you agree to our": "Создавая учетную запись, вы соглашаетесь с нашими", + "terms of service": "условия предоставления услуг", + "privacy policy": "политика конфиденциальности", + "Sign up With": "Регистрация с помощью", + "Google": "Google", + "Sign in With": "Войти с помощью", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "Email был подтвержден. Вы будете перенаправлены на вход...", + "Verifying email...": "Проверка электронной почты...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "срок действия приглашения истек, как только вы войдете снова, вы сможете отправить письмо с подтверждением.", + "Redirecting to sign in...": "Перенаправление на вход...", + "Password must contain at least one special character": "Пароль должен содержать хотя бы один специальный символ", + "Password must contain at least one lowercase letter": "Пароль должен содержать хотя бы одну строчную букву", + "Password must contain at least one uppercase letter": "Пароль должен содержать хотя бы одну заглавную букву", + "Password must contain at least one number": "Пароль должен содержать хотя бы одно число", + "8-64 Characters": "8-64 знака", + "Special Character": "Специальный символ", + "Lowercase": "Строчные буквы", + "Uppercase": "Прописные", + "Number": "Номер", + "Connection has been updated.": "Соединение обновлено.", + "Edit Global Connection": "Изменить глобальное подключение", + "Connection has been renamed.": "Соединение было переименовано.", + "New Connection Name": "Новое имя подключения", + "Connection name already used": "Имя подключения уже используется", + "Please select at least one project": "Пожалуйста, выберите хотя бы один проект", + "Run Succeeded": "Успешный запуск", + "Run Failed": "Неудачный запуск", + "Flow Run is paused": "Выполнение потока приостановлено", + "Run Failed due to quota exceeded": "Не удалось выполнить из-за превышения квоты", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Выполнение не удалось из-за превышения лимита памяти {memoryLimit} МБ", + "Run exceeded {timeout} seconds, try to optimize your steps.": "Превышен предел {timeout} секунд, попробуйте оптимизировать свои шаги.", + "Run failed for an unknown reason, contact support.": "Не удалось запустить по неизвестной причине, обратитесь в службу поддержки.", + "Unknown": "Неизвестен", + "Exit Run": "Выход из выполнения", + "Select shown": "Выделить показано", + "Select all": "Выбрать все", + "Start Time": "Время начала", + "Runs replayed successfully": "Выполняется успешно повторено", + "Retry": "Повторить", + "all except": "все кроме", + "all": "все", + "Only failed runs can be retried from failed step": "Только неудачные запуска можно повторить из неудачного шага", + "No flow runs found": "Запуск потока не найден", + "Come back later when your automations start running": "Возвращайтесь позже, когда ваши автоматические процессы запущены", + "Step running": "Шаг запущен", + "Step paused": "Шаг приостановлен", + "Step Stopped": "Шаг остановлен", + "Step Succeeded": "Шаг успешно завершен", + "Step Failed": "Шаг не удался", + "Please publish flow first": "Пожалуйста, сначала опубликуйте поток", + "Flow is on": "Flow включен", + "Flow is off": "Поток выключен", + "Permission Needed": "Требуется разрешение", + "Draft Version": "Версия черновика", + "Published Version": "Опубликованная версия", + "Locked Version": "Заблокированная версия", + "flowsImported": "{flowsCount, plural, =0 {Импортировано} =1 {Успешно импортировано их} other {Успешно импортировано их}}", + "Template file is invalid": "Недопустимый файл шаблона", + "No valid templates found. The following files failed to import: ": "Не найдено допустимых шаблонов. Импортировать не удалось следующие файлы: ", + "Please select a file first": "Сначала выберите файл", + "Unsupported file type": "Неподдерживаемый тип файла", + "Import Flow": "Импортировать поток", + "Warning": "Предупреждение", + "Importing a flow will overwrite your current one.": "Импорт потока перезапишет ваш текущий поток.", + "Select a folder": "Выберите папку", + "Folders": "Папки", + "Please select a folder": "Пожалуйста, выберите папку", + "Moved flows successfully": "Перемещенные потоки успешно", + "Move Selected Flows": "Переместить выбранные потоки", + "Select Folder": "Выберите папку", + "No Folders": "Нет папок", + "Flow has been renamed.": "Поток был переименован.", + "New Flow Name": "Новое имя потока", + "Use Template": "Использовать шаблон", + "Browse Templates": "Просмотр шаблонов", + "Search templates": "Поиск шаблонов", + "No templates found, try adjusting your search": "Шаблоны не найдены, попробуйте изменить ваш поиск", + "Read more about this template in": "Подробнее об этом шаблоне в", + "this blog!": "этот блог!", + "Share Template": "Поделиться шаблоном", + "Generate or update a template link for the current flow to easily share it with others.": "Сгенерировать или обновить ссылку на шаблон для текущего потока, чтобы легко поделиться ею с другими.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "Шаблон не будет иметь никаких учетных данных в полях подключения, сохраняя конфиденциальную информацию.", + "A short description of the template": "Краткое описание шаблона", + "Flow Is In Use": "Поток используется", + "Flow is being used by another user, please try again later.": "Поток используется другим пользователем, пожалуйста, повторите попытку позже.", + "Flow has been published.": "Поток опубликован.", + "Flows have been exported.": "Экспортированы потоки.", + "Run": "Запустить", + "Real time flow": "Поток реального времени", + "Flow can't be published with empty trigger {name}": "Поток не может быть опубликован с пустым триггером {name}", + "Please contact support as your published flow has a problem": "Пожалуйста, обратитесь в службу поддержки, так как у вас есть проблемы с опубликованным процессом", + "Actions": "Действия", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Вы уверены, что хотите удалить эти потоки? Это приведет к окончательному удалению потоков, всех их данных и фонового режима.", + "You are on a development branch, this will not delete the flows from the remote repository.": "Вы находитесь в ветке разработки, это не удалит потоки из удаленного репозитория.", + "Please enter folder name": "Пожалуйста, введите имя папки", + "Added folder successfully": "Папка успешно добавлена", + "The folder name already exists.": "Такое имя папки уже существует.", + "New Folder": "Новая папка", + "Folder Name": "Имя папки", + "Loading...": "Загрузка...", + "Delete {folderName}": "Удалить {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "Если вы удалите эту папку, мы сохраним ее потоки и переместим их в \"Без категории\".", + "All flows": "Все потоки", + "Please enter a folder name": "Пожалуйста, введите имя папки", + "Renamed flow successfully": "Успешное переименование потока", + "Folder name already used": "Имя папки уже используется", + "New Folder Name": "Имя новой папки", + "Connected successfully": "Подключено успешно", + "Connect Git": "Подключить Git", + "Remote URL": "Удаленный URL", + "Folder name is the name of the folder where the project will be stored or fetched.": "Имя папки - это имя папки, в которой проект будет храниться или извлечен.", + "SSH Private Key": "SSH закрытый ключ", + "The SSH private key to use for authentication.": "Закрытый ключ SSH для аутентификации.", + "Only published flows can be pushed to Git": "Только опубликованные потоки могут быть загружены в Git", + "Pushed successfully": "Отправлено успешно", + "Invalid Operation": "Неверная операция", + "Commit Message": "Сообщение об изменении", + "Enter a commit message to describe the changes you want to push.": "Введите сообщение о коммите, чтобы описать изменения, которые вы хотите push.", + "Push": "Нагрузка", + "This field is required": "Это поле является обязательным", + "Your submission was successfully received.": "Ваша работа была успешно получена.", + "Flow not found": "Поток не найден", + "The flow you are trying to submit to does not exist.": "Поток, к которому вы пытаетесь отправить, не существует.", + "The flow failed to execute.": "Не удалось выполнить поток.", + "Submit": "Отправить", + "issues-notification": "уведомление об ошибках", + "Please select a package type": "Пожалуйста, выберите тип пакета", + "package.json not found in archive": "package.json не найден в архиве", + "Error processing archive file": "Ошибка обработки файла архива", + "Please upload a .tgz file": "Пожалуйста, загрузите .tgz файл", + "Piece name is required for NPM Registry": "Имя части необходимо для Реестра Музея", + "Piece version is required for NPM Registry": "Для Реестра Музея требуется версия запчастей", + "Piece installed": "Часть установлена", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "Часть с таким именем и версией уже установлена. Пожалуйста, обновите номер версии в package.json и повторите попытку.", + "Install Piece": "Установить часть", + "Install a piece": "Установить фигуру", + "Package Type": "Тип пакета", + "NPM Registry": "Реестр Музея", + "Packed Archive (.tgz)": "Упакованный архив (.tgz)", + "Piece Version": "Версия части", + "Package Archive": "Архив пакетов", + "Package archive": "Архив пакетов", + "Powerful Node.js & TypeScript code with npm": "Мощный код Node.js и TypeScript с помощью npm", + "Loop on Items": "Цикл на элементы", + "Split your flow into branches depending on condition(s)": "Разделите поток на ветви в зависимости от условия(ов)", + "Empty Trigger": "Пустой триггер", + "An internal error occurred while fetching data, please contact support": "Произошла внутренняя ошибка при получении данных, пожалуйста, обратитесь в службу поддержки", + "An internal error occurred, please contact support": "Произошла внутренняя ошибка, обратитесь в службу поддержки", + "Custom Javascript Code": "Пользовательский Javascript-код", + "Router": "Роутер", + "recordsCount": "счетчик записей", + "selected": "выбрано", + "All records selected": "Все записи выбраны", + "fieldsCount": "количество полей", + "Saving...": "Сохранение...", + "Loading more...": "Загрузка еще...", + "Export Table": "Экспорт таблицы", + "Delete Records": "Удалить записи", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Вы уверены, что хотите удалить выбранные записи? Это действие нельзя отменить.", + "record": "запись", + "records": "записи", + "mm/dd/yyy": "мм/дд/ггг", + "Delete Field": "Удалить поле", + "Are you sure you want to delete this field? This action cannot be undone.": "Вы уверены, что хотите удалить это поле? Это действие не может быть отменено.", + "field": "поле", + "Ignored": "Игнорировать", + "Table": "Таблица", + "CSV": "CSV", + "Field": "Поле", + "Please select a csv file": "Пожалуйста, выберите csv файл", + "Max file size is {maxFileSize}MB": "Максимальный размер файла {maxFileSize}МБ", + "Import CSV": "Импорт CSV", + "Imported records will be added to the bottom of the table": "Импортированные записи будут добавлены в нижнюю часть таблицы", + "Any records after the limit ({maxRecords} records) will be ignored": "Любые записи после предела ({maxRecords} записи) будут игнорироваться", + "CSV File": "CSV файл", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "Произошла непредвиденная ошибка при импорте csv файла, пожалуйста, нажмите на ошибку копирования и отправьте ее в поддержку", + "Name must be unique": "Имя должно быть уникальным", + "Type is required": "Требуется тип", + "Please add at least one option": "Пожалуйста, добавьте хотя бы один вариант", + "New Field": "Новое поле", + "Options": "Варианты", + "Name is already taken": "Имя уже занято", + "Table renamed": "Таблица переименована", + "Table name": "Название таблицы", + "Team Invitation Accepted": "Приглашение в команду принято", + "Thank you for accepting the invitation. We are redirecting you right now...": "Спасибо, что приняли приглашение. Мы перенаправим вас прямо сейчас...", + "Invalid invitation token. Please try again.": "Неверный токен приглашения. Пожалуйста, попробуйте еще раз.", + "Role updated successfully": "Роль успешно обновлена", + "Error updating role": "Ошибка обновления роли", + "Please try again later": "Повторите попытку позже", + "Edit Role for": "Изменить роль для", + "Select Role": "Выберите роль", + "Avatar": "Аватар", + "Remove {email}": "Удалить {email}", + "Are you sure you want to remove this invitation?": "Вы уверены, что хотите удалить это приглашение?", + "Please select invitation type": "Пожалуйста, выберите тип приглашения", + "Please select platform role": "Пожалуйста, выберите роль платформы", + "Invitation sent successfully": "Приглашение успешно отправлено", + "Please select a project role": "Пожалуйста, выберите роль проекта", + "Invitation link copied successfully": "Ссылка для приглашения успешно скопирована", + "Invite User": "Пригласить пользователя", + "Invitation Link": "Ссылка на приглашение", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Пожалуйста, скопируйте ссылку ниже и поделитесь ею с пользователем, которого вы хотите пригласить, срок приглашения истекает через 24 часа.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Введите адрес электронной почты пользователя, которого вы хотите пригласить, срок приглашения истекает через 24 часа.", + "Invite To": "Пригласить в", + "Entire Platform": "Вся платформа", + "Select Project Role": "Выберите роль проекта", + "Invite": "Пригласить", + "Platform Role": "Роль платформы", + "Select a platform role": "Выберите роль платформы", + "Are you sure you want to remove this member?": "Вы уверены, что хотите удалить этого участника?", + "Editor": "Редактор", + "Operator": "Оператор", + "Viewer": "Просмотрщик", + "Select a project role": "Выберите роль проекта", + "Steps in this flow": "Шаги в этом потоке", + "Invalid Access": "Неправильный доступ", + "Either the project does not exist or you do not have access to it.": "Проект не существует или у вас нет доступа к нему." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/sv/translation.json b/packages/react-ui/public/locales/sv/translation.json new file mode 100644 index 0000000..07f1acf --- /dev/null +++ b/packages/react-ui/public/locales/sv/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Publish", + "Latest version is published": "Latest version is published", + "Your flow has incomplete steps": "Your flow has incomplete steps", + "Edit Flow": "Edit Flow", + "View Draft": "View Draft", + "Uncategorized": "Uncategorized", + "Go to folder": "Go to folder", + "Support": "Support", + "Runs": "Runs", + "Run Logs": "Run Logs", + "Versions": "Versions", + "Versions History": "Versions History", + "Error generating code": "Error generating code", + "AI Copilot": "AI Copilot", + "i.e Calculate the sum of a list...": "i.e Calculate the sum of a list...", + "Send": "Skicka", + "Generating Code": "Generating Code", + "Hello there! I am here to generate code that helps with your flow": "Hello there! I am here to generate code that helps with your flow", + "Here are examples of what I am best used for: ": "Here are examples of what I am best used for: ", + "Text Processing": "Text Processing", + "Process strings, dates and data": "Process strings, dates and data", + "Data Operations": "Data Operations", + "Change data from one format to another": "Change data from one format to another", + "Calculations": "Calculations", + "Handle math and statistics": "Handle math and statistics", + "API Integration": "API Integration", + "Connect with external services. Best for simple integrations currently.": "Connect with external services. Best for simple integrations currently.", + "What would you like me to help you with?": "What would you like me to help you with?", + "Insert": "Insert", + "Data Selector": "Data Selector", + "Search": "Sök", + "No matching data": "No matching data", + "Try adjusting your search": "Try adjusting your search", + "This trigger needs to have data loaded from your account, to use as sample data.": "This trigger needs to have data loaded from your account, to use as sample data.", + "This step needs to be tested in order to view its data.": "This step needs to be tested in order to view its data.", + "Go to Trigger": "Go to Trigger", + "Go to Step": "Go to Step", + "Select Mode": "Select Mode", + "Move Mode": "Move Mode", + "Reset Zoom": "Reset Zoom", + "Zoom In": "Zooma in", + "Zoom Out": "Zoom Ut", + "Fit to View": "Fit to View", + "Replace": "Replace", + "Copy": "Kopiera", + "Duplicate": "Duplicate", + "Paste After Last Step": "Paste After Last Step", + "Paste Inside Loop": "Paste Inside Loop", + "Paste After": "Paste After", + "Paste Inside...": "Paste Inside...", + "New Branch": "New Branch", + "Paste Inside Branch": "Paste Inside Branch", + "Delete": "Radera", + "Duplicate Branch": "Duplicate Branch", + "Delete Branch": "Delete Branch", + "Invalid Move": "Invalid Move", + "The destination location is a child of the dragged step": "The destination location is a child of the dragged step", + "End": "End", + "Skipped": "Skipped", + "Incomplete settings": "Incomplete settings", + "logo": "logo", + "Step Icon": "Step Icon", + "Branch": "Branch", + "incompleteSteps": "{invalidSteps, plural, =0 {no incomplete steps} =1 {Complete 1 step} other {Complete # steps}}", + "Test Flow": "Test Flow", + "Please test the trigger first": "Please test the trigger first", + "View Only": "View Only", + "Use as Draft": "Använd som utkast", + "Are you sure?": "Är du säker?", + "Your current draft version will be overwritten with": "Your current draft version will be overwritten with", + "version #": "version #", + "Cancel": "Avbryt", + "Confirm": "Bekräfta", + "Version": "Version", + "Viewing": "Viewing", + "View": "View", + "Version History": "Versionshistorik", + "Error, please try again.": "Error, please try again.", + "Continue on Failure": "Continue on Failure", + "Enable this option to skip this step and continue the flow normally if it fails.": "Enable this option to skip this step and continue the flow normally if it fails.", + "Retry on Failure": "Retry on Failure", + "Automatically retry up to four attempts when failed.": "Automatically retry up to four attempts when failed.", + "Remove": "Remove", + "Add Item": "Add Item", + "File Input": "File Input", + "Date Input": "Date Input", + "Dynamic value": "Dynamic value", + "Select an option": "Select an option", + "Unexpected error, please retry": "Unexpected error, please retry", + "Unexpected error, please refresh the page or contact support": "Unexpected error, please refresh the page or contact support", + "Name can only contain letters, numbers and underscores": "Name can only contain letters, numbers and underscores", + "Ask AI": "Ask AI", + "Create Todo Guide": "Create Todo Guide", + "Where would you like the todo to be reviewed?": "Where would you like the todo to be reviewed?", + "Activepieces Todos": "Activepieces Todos", + "Users will manage tasks directly in Activepieces": "Users will manage tasks directly in Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.", + "External Channel (Slack, Teams, Email, ...)": "External Channel (Slack, Teams, Email, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.", + "Preview (Activepieces Todos)": "Preview (Activepieces Todos)", + "Preview (External channel)": "Preview (External channel)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "You can add the channel before the Wait Step, and configure the logic in the Router step", + "Add Steps": "Add Steps", + "All": "All", + "AI": "AI", + "Core": "Core", + "Apps": "Apps", + "Not available as trigger": "Not available as trigger", + "Not available as action": "Not available as action", + "Let our AI assistant help you out": "Let our AI assistant help you out", + "Or": "Or", + "Request Piece": "Request Piece", + "No pieces found": "No pieces found", + "Please select a piece first": "Please select a piece first", + "All Iterations": "All Iterations", + "Duration": "Duration", + "Input": "Input", + "Output": "Output", + "There are no logs captured for this run.": "There are no logs captured for this run.", + "Logs are kept for {days} days after execution and then deleted.": "Logs are kept for {days} days after execution and then deleted.", + "Run Details": "Run Details", + "Iteration": "Iteration", + "Done": "Done", + "Took": "Took", + "Running": "Running", + "on latest version": "on latest version", + "from failed step": "from failed step", + "Recent Runs": "Recent Runs", + "No runs found": "No runs found", + "Close": "Stäng", + "OR": "ELLER", + "And If": "And If", + "+ And": "+ And", + "+ Or": "+ Or", + "(Text) Contains": "(Text) Contains", + "(Text) Does not contain": "(Text) Does not contain", + "(Text) Exactly matches": "(Text) Exactly matches", + "(Text) Does not exactly match": "(Text) Does not exactly match", + "(Text) Starts with": "(Text) Starts with", + "(Text) Does not start with": "(Text) Does not start with", + "(Text) Ends with": "(Text) Ends with", + "(Text) Does not end with": "(Text) Does not end with", + "(List) Contains": "(List) Contains", + "(List) Does not contain": "(List) Does not contain", + "(Number) Is greater than": "(Number) Is greater than", + "(Number) Is less than": "(Number) Is less than", + "(Number) Is equal to": "(Number) Is equal to", + "(Date/time) After": "(Date/time) After", + "(Date/time) Before": "(Date/time) Before", + "(Date/time) Equals": "(Date/time) Equals", + "(Boolean) Is true": "(Boolean) Is true", + "(Boolean) Is false": "(Boolean) Is false", + "(List) Is empty": "(List) Is empty", + "(List) Is not empty": "(List) Is not empty", + "Exists": "Exists", + "Does not exist": "Does not exist", + "Incomplete condition": "Incomplete condition", + "First value": "First value", + "Second value": "Second value", + "Case sensitive": "Case sensitive", + "Execute If": "Execute If", + "The package name is required": "The package name is required", + "Success": "Success", + "Package added successfully": "Package added successfully", + "Could not fetch package version": "Could not fetch package version", + "Add NPM Package": "Add NPM Package", + "Type the name of the npm package you want to add.": "Type the name of the npm package you want to add.", + "Package Name": "Package Name", + "The latest version will be fetched and added": "The latest version will be fetched and added", + "Add": "Lägg till", + "Code": "Kod", + "Dependencies": "Dependencies", + "Use code": "Use code", + "Add package": "Add package", + "Inputs": "Inputs", + "Edit Step Name": "Edit Step Name", + "Edit Branch Name": "Edit Branch Name", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.", + "Items": "Items", + "Select an array of items": "Select an array of items", + "Reconnect": "Reconnect", + "Select a connection": "Select a connection", + "Create Connection": "Create Connection", + "Rename": "Rename", + "Move": "Move", + "Add Branch": "Add Branch", + "Execute": "Execute", + "Only the first (left) matching branch": "Only the first (left) matching branch", + "All matching paths from left to right": "All matching paths from left to right", + "Branches": "Branches", + "{field} is required": "{field} is required", + "Tool Sample Data": "Tool Sample Data", + "Fill in the following fields to use them as sample data for the trigger.": "Fill in the following fields to use them as sample data for the trigger.", + "No input fields defined in the schema": "No input fields defined in the schema", + "Save": "Spara", + "Test Environment": "Test Environment", + "Assigned to": "Assigned to", + "(Me)": "(Me)", + "Please select status to resolve the todo": "Please select status to resolve the todo", + "Resolve": "Resolve", + "Change status to resolved": "Change status to resolved", + "Send Sample Data to Webhook": "Send Sample Data to Webhook", + "Method": "Method", + "Query Params": "Query Params", + "Headers": "Headers", + "Body": "Body", + "Type": "Type", + "JSON": "JSON", + "Text": "Text", + "Form Data": "Form Data", + "Generate Sample Data": "Generate Sample Data", + "Test Step": "Test Step", + "Retest": "Retest", + "Testing Failed": "Testing Failed", + "Tested Successfully": "Tested Successfully", + "Logs": "Logs", + "There is no sample data available found for this trigger.": "There is no sample data available found for this trigger.", + "Internal error, please try again later.": "Internal error, please try again later.", + "Failed to run test step and no error message was returned": "Failed to run test step and no error message was returned", + "Please fix inputs first": "Please fix inputs first", + "No sample data available": "No sample data available", + "Old results were removed, retest for new sample data": "Old results were removed, retest for new sample data", + "Result #": "Result #", + "The sample data can be used in the next steps.": "The sample data can be used in the next steps.", + "Testing Trigger": "Testing Trigger", + "Action Required": "Action Required", + "testPieceWebhookTriggerNote": "Please go to {pieceName} and trigger {triggerName}.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Send Data to the webhook URL to generate sample data to use in the next steps", + "Test Trigger": "Test Trigger", + "Use Mock Data": "Use Mock Data", + "Load Sample Data": "Load Sample Data", + "Test Tool": "Test Tool", + "home": "home", + "Home": "Home", + "Alerts": "Alerts", + "Releases": "Releases", + "Flows": "Flows", + "Products": "Products", + "MCP": "MCP", + "Tables": "Tables", + "Todos": "Todos", + "Push to Git": "Push to Git", + "Move To": "Move To", + "Duplicating": "Duplicating", + "Import": "Import", + "Exporting": "Exporting", + "Export": "Export", + "Share": "Dela", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.", + "You are on a development branch, this will also delete the flow from the remote repository.": "You are on a development branch, this will also delete the flow from the remote repository.", + "flow": "flow", + "Community Support": "Community Support", + "Overview": "Overview", + "Projects": "Projects", + "Users": "Användare", + "Setup": "Setup", + "Branding": "Branding", + "Global Connections": "Global Connections", + "Pieces": "Pieces", + "Templates": "Mallar", + "License Key": "License Key", + "Security": "Security", + "Audit Logs": "Audit Logs", + "Single Sign On": "Single Sign On", + "Signing Keys": "Signing Keys", + "Project Roles": "Project Roles", + "API Keys": "API-nycklar", + "Infrastructure": "Infrastructure", + "Workers": "Workers", + "Health": "Health", + "Billing": "Billing", + "Settings": "Inställningar", + "Contact Sales": "Contact Sales", + "General": "General", + "Appearance": "Utseende", + "Team": "Team", + "Environments": "Environments", + "Project Settings": "Project Settings", + "Exit Platform Admin": "Exit Platform Admin", + "Enter Platform Admin": "Enter Platform Admin", + "Logout": "Logout", + "Misc": "Misc", + "Connections": "Connections", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "Today": "Today", + "Tasks": "Uppgifter", + "AI Credits": "AI Credits", + "Usage resets in": "Usage resets in", + "Manage": "Manage", + "Unlimited": "Unlimited", + "Enabled": "Enabled", + "Disabled": "Disabled", + "Could not claim the authorization code, make sure you have correct settings and try again.": "Could not claim the authorization code, make sure you have correct settings and try again.", + "Connection failed with error {msg}": "Connection failed with error {msg}", + "You don't have the permission to create a connection.": "You don't have the permission to create a connection.", + "Reconnect {displayName} Connection": "Reconnect {displayName}", + "Connect to {displayName}": "Connect to {displayName}", + "Connection Name": "Connection Name", + "Connection name": "Connection name", + "New Connection": "New Connection", + "Redirect URL": "Redirect URL", + "Client ID": "Klient-ID", + "Client Secret": "Client Secret", + "Connect": "Anslut", + "Disconnect": "Disconnect", + "I would like to use my own App Credentials": "I would like to use my own App Credentials", + "I would like to use predefined App Credentials": "I would like to use predefined App Credentials", + "Permission needed": "Permission needed", + "Connections replaced successfully": "Connections replaced successfully", + "Error": "Error", + "Failed to replace connections": "Failed to replace connections", + "Failed to get affected flows": "Failed to get affected flows", + "Please select a piece": "Please select a piece", + "Please select a connection to replace": "Please select a connection to replace", + "Please select a connection to replace with": "Please select a connection to replace with", + "Replace Connections": "Replace Connections", + "Confirm Replacement": "Confirm Replacement", + "Replace one connection with another.": "Replace one connection with another.", + "This action requires ": "This action requires ", + "reconnecting": "reconnecting", + " any associated MCP pieces.": " any associated MCP pieces.", + "Piece": "Piece", + "Select a piece": "Select a piece", + "Connection to Replace": "Connection to Replace", + "Choose connection to replace": "Choose connection to replace", + "Replaced With": "Replaced With", + "Choose connection to replace with": "Choose connection to replace with", + "All flows will be changed to use the replaced with connection": "All flows will be changed to use the replaced with connection", + "Next": "Next", + "No flows will be affected by this change": "No flows will be affected by this change", + "Back": "Back", + "Unnamed tool": "Unnamed tool", + "This flow is enabled": "This flow is enabled", + "Enable this flow to make it available": "Enable this flow to make it available", + "Piece is updated successfully": "Piece is updated successfully", + "Piece is added successfully": "Piece is added successfully", + "Failed to update piece": "Failed to update piece", + "Failed to add piece": "Failed to add piece", + "Please select a connection": "Please select a connection", + "Your MCP server already has this piece": "Your MCP server already has this piece", + "+ New Connection": "+ New Connection", + "Edit Piece": "Edit Piece", + "Add Piece": "Add Piece", + "Connection": "Connection", + "MCP piece": "MCP piece", + "Failed to update piece status": "Failed to update piece status", + "Connection required": "Connection required", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.", + "piece": "piece", + "Connect your AI assistant to external services": "Connect your AI assistant to external services", + "Collapse": "Collapse", + "Show All": "Show All", + "Server URL": "Server URL", + "Hide the token for security": "Hide the token for security", + "Show the token": "Show the token", + "Generate a new token for security. This will invalidate the current URL.": "Generate a new token for security. This will invalidate the current URL.", + "URL copied to clipboard": "URL copied to clipboard", + "Copy URL to clipboard": "Copy URL to clipboard", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.", + "Max tables reached": "Max tables reached", + "You can't create more than {maxTables} tables": "You can't create more than {maxTables} tables", + "Name": "Name", + "Created": "Created", + "Delete Tables": "Delete Tables", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Are you sure you want to delete the selected tables? This action cannot be undone.", + "table": "table", + "Create and manage your tables to store your automation data": "Create and manage your tables to store your automation data", + "New Table": "New Table", + "No tables have been created yet": "No tables have been created yet", + "Create a table to get started and start managing your automation data": "Create a table to get started and start managing your automation data", + "Error deleting connections": "Error deleting connections", + "Status": "Status", + "Display Name": "Visningsnamn", + "Owner": "Owner", + "App": "App", + "This connection is global and can be managed in the platform admin": "This connection is global and can be managed in the platform admin", + "External ID": "External ID", + "Connected At": "Connected At", + "Confirm Deletion": "Confirm Deletion", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Are you sure you want to delete the selected connections? This action cannot be undone.", + "Deleting connections may cause your Flows or MCP tools to break.": "Deleting connections may cause your Flows or MCP tools to break.", + "Manage project connections to external systems.": "Manage project connections to external systems.", + "No connections found": "No connections found", + "Come back later when you create a automation to manage your connections": "Come back later when you create a automation to manage your connections", + "Steps": "Steps", + "Folder": "Mapp", + "Flow name": "Flow name", + "No flows found": "No flows found", + "Create a workflow to start automating": "Create a workflow to start automating", + "Create and manage your flows, run history and run issues": "Create and manage your flows, run history and run issues", + "Issues": "Issues", + "Untitled": "Untitled", + "Create flow": "Create flow", + "From scratch": "From scratch", + "Use a template": "Use a template", + "From local file": "From local file", + "Flow Name": "Flow Name", + "Count": "Count", + "First Seen": "First Seen", + "Last Seen": "Last Seen", + "Issues in {flowDisplayName} is marked as resolved.": "Issues in {flowDisplayName} is marked as resolved.", + "Unlock Issues": "Unlock Issues", + "Track issues in your workflows and troubleshoot them.": "Track issues in your workflows and troubleshoot them.", + "No issues found": "No issues found", + "All your workflows are running smoothly.": "All your workflows are running smoothly.", + "Mark as Resolved": "Mark as Resolved", + "All Flows Are Turned Off": "All Flows Are Turned Off", + "Task Usage Exceeded": "Task Usage Exceeded", + "of the Allowed Limit.": "of the Allowed Limit.", + "When a project tasks limit is reached,": "When a project tasks limit is reached,", + "all flows will be turned off and you will not be able to run any flows.": "all flows will be turned off and you will not be able to run any flows.", + "Please visit": "Please visit", + "Your Plan": "Your Plan", + "and increase your task limit, which requires your payment details.": "and increase your task limit, which requires your payment details.", + "Please contact your admin to increase the project task limit.": "Please contact your admin to increase the project task limit.", + "and increase the project task limit.": "and increase the project task limit.", + "Dismiss": "Dismiss", + "Token rotated successfully": "Token rotated successfully", + "Failed to rotate token": "Failed to rotate token", + "Piece removed successfully": "Piece removed successfully", + "Failed to remove piece": "Failed to remove piece", + "Flow created successfully": "Flow created successfully", + "Failed to create flow": "Failed to create flow", + "Add Flow": "Add Flow", + "Let your AI assistant trigger automations": "Let your AI assistant trigger automations", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Connect to your hosted MCP Server using any MCP client to communicate with tools", + "MCP Server": "MCP Server", + "My Tools": "My Tools", + "Create Flow": "Create Flow", + "Note": "Note", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.", + "This URL grants access to your tools and data. Only share with trusted applications.": "This URL grants access to your tools and data. Only share with trusted applications.", + "Server Configuration": "Server Configuration", + "Hide sensitive data": "Hide sensitive data", + "Show sensitive data": "Show sensitive data", + "Create a new URL. The current one will stop working.": "Create a new URL. The current one will stop working.", + "Copy configuration": "Copy configuration", + "Configuration copied to clipboard": "Configuration copied to clipboard", + "Claude": "Claude", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "Server/Other", + "Note: MCPs only work with": "Note: MCPs only work with", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", not the web version.", + "Prerequisites:": "Prerequisites:", + "Install": "Install", + "Node.js": "Node.js", + "and": "and", + "Open Settings:": "Open Settings:", + "Click the menu and select": "Click the menu and select", + "Developer": "Developer", + "Configure MCP:": "Configure MCP:", + "Click": "Click", + "Edit Config": "Edit Config", + "and paste the configuration below": "and paste the configuration below", + "Save and Restart:": "Save and Restart:", + "Save the config and restart Claude Desktop": "Save the config and restart Claude Desktop", + "Navigate to": "Navigate to", + "Cursor Settings": "Cursor Settings", + "Add Server:": "Add Server:", + "Add new global MCP server": "Add new global MCP server", + "Configure:": "Configure:", + "Paste the configuration below and save": "Paste the configuration below and save", + "Use either method:": "Use either method:", + "Go to": "Go to", + "Advanced Settings": "Advanced Settings", + "Open Command Palette and select": "Open Command Palette and select", + "Windsurf Settings Page": "Windsurf Settings Page", + "Navigate to Cascade:": "Navigate to Cascade:", + "Select": "Select", + "Cascade": "Cascade", + "in the sidebar": "in the sidebar", + "Add Server": "Add Server", + "Add custom server +": "Add custom server +", + "Copy URL": "Copy URL", + "Client Setup Instructions": "Client Setup Instructions", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "After changing connections or flows, reconnect your MCP server for changes to take effect.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.", + "icon": "icon", + "Unlock Analytics": "Unlock Analytics", + "Get insights into your platform usage and performance with our analytics dashboard": "Get insights into your platform usage and performance with our analytics dashboard", + "Active Flows": "Active Flows", + "The number of enabled flows in the platform": "The number of enabled flows in the platform", + "Active Projects": "Active Projects", + "The number of projects with at least one enabled flow": "The number of projects with at least one enabled flow", + "Active Users": "Active Users", + "The number of users logged in the last 30 days": "The number of users logged in the last 30 days", + "Out of {totalusers} total users": "Out of {totalusers} total users", + "Pieces Used": "Pieces Used", + "The number of unique pieces used across all flows": "The number of unique pieces used across all flows", + "Flows with AI": "Flows with AI", + "The number of enabled flows that use AI pieces": "The number of enabled flows that use AI pieces", + "Metrics": "Metrics", + "Executed Tasks": "Executed Tasks", + "Showing total executed tasks for specified time range": "Showing total executed tasks for specified time range", + "Tasks Usage Limit": "Tasks Usage Limit", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.", + "Number of monthly tasks": "Number of monthly tasks", + "Save changes": "Save changes", + "Limits updated successfully": "Limits updated successfully", + "Failed to update limits": "Failed to update limits", + "Failed to load billing information": "Failed to load billing information", + "Billing Amount": "Billing Amount", + "Manage Payment Details": "Manage Payment Details", + "Add Payment Details": "Add Payment Details", + "Current Task Usage": "Current Task Usage", + "Count of executed steps": "Count of executed steps", + "Billing Limit": "Billing Limit", + "Edit": "Edit", + "Add Limit": "Add Limit", + "Current Credit Usage": "Current Credit Usage", + "WebSocket Connection": "WebSocket Connection", + "Connected": "Connected", + "Disconnected": "Disconnected", + "No issues detected": "No issues detected", + "Check the status of your platform and its components": "Check the status of your platform and its components", + "System Health Status": "System Health Status", + "All systems are running smoothly": "All systems are running smoothly", + "Check the health of your worker machines": "Check the health of your worker machines", + "Workers Machine": "Workers Machine", + "This is demo data. In a real environment, this would show your actual worker machines.": "This is demo data. In a real environment, this would show your actual worker machines.", + "No workers found": "No workers found", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "You don't have any worker machines yet. Spin up new machines to execute your automations", + "IP Address": "IP Address", + "CPU Usage": "CPU Usage", + "Disk Usage": "Disk Usage", + "RAM Usage": "RAM Usage", + "Last Contact": "Last Contact", + "Configs": "Configs", + "Environment Variables": "Environment Variables", + "Websocket Connection Error": "Websocket Connection Error", + "Retry Connection": "Retry Connection", + "Update Available": "Update Available", + "Update Now": "Update Now", + "Your Universal AI needs a quick setup": "Your Universal AI needs a quick setup", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.", + "Configure": "Configure", + "Platform Alerts": "Platform Alerts", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.", + "View Alerts": "View Alerts", + "Used Tasks": "Used Tasks", + "Used AI Credits": "Used AI Credits", + "Cannot delete active project, switch to another project first": "Cannot delete active project, switch to another project first", + "Delete Projects": "Delete Projects", + "Are you sure you want to delete the selected projects?": "Are you sure you want to delete the selected projects?", + "New Project": "New Project", + "Validation error": "Validation error", + "Project has enabled flows. Please disable them first.": "Project has enabled flows. Please disable them first.", + "This project is active. Please switch to another project first.": "This project is active. Please switch to another project first.", + "Unlock Projects": "Unlock Projects", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Orchestrate your automation teams across projects with their own flows, connections and usage quotas", + "Manage your automation projects": "Manage your automation projects", + "No projects found": "No projects found", + "Start by creating projects to manage your automation teams": "Start by creating projects to manage your automation teams", + "Edit project": "Edit project", + "Name is required": "Name is required", + "Create New Project": "Create New Project", + "Project Name": "Projektnamn", + "Id": "Id", + "Enable API Keys": "Enable API Keys", + "Create and manage API keys to access Activepieces APIs.": "Create and manage API keys to access Activepieces APIs.", + "New Api Key": "New Api Key", + "No API keys found": "No API keys found", + "Start by creating an API key to communicate with Activepieces APIs": "Start by creating an API key to communicate with Activepieces APIs", + "Delete API Key": "Delete API Key", + "Are you sure you want to delete this API key?": "Are you sure you want to delete this API key?", + "API Key": "API Key", + "API Key Created": "API Key Created", + "Create New API Key": "Create New API Key", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Please save this secret key somewhere safe and accessible. For security reasons,", + "you won't be able to view it again after closing this dialog.": "you won't be able to view it again after closing this dialog.", + "API Key Name": "API Key Name", + "Action": "Action", + "Performed By": "Performed By", + "Project": "Project", + "Unlock Audit Logs": "Unlock Audit Logs", + "Comply with internal and external security policies by tracking activities done within your account": "Comply with internal and external security policies by tracking activities done within your account", + "Track activities done within your platform": "Track activities done within your platform", + "No audit logs found": "No audit logs found", + "Come back later when you have some activity to audit": "Come back later when you have some activity to audit", + "Resource": "Resource", + "Details": "Details", + "N/A": "N/A", + "Flow Run": "Flow Run", + "Flow": "Flow", + "User": "User", + "Signing Key": "Signing Key", + "Project Role Management": "Project Role Management", + "Define custom roles and permissions to control what your team members can access and modify": "Define custom roles and permissions to control what your team members can access and modify", + "Define custom roles and permissions that can be assigned to your team members": "Define custom roles and permissions that can be assigned to your team members", + "New Role": "New Role", + "Contact sales to unlock custom roles": "Contact sales to unlock custom roles", + "Create New Role": "Create New Role", + "View ": "View ", + "Edit ": "Edit ", + "Role Name": "Role Name", + "Permissions": "Permissions", + "None": "None", + "Read": "Read", + "Write": "Write", + "Create": "Skapa", + "Email": "Email", + "First Name": "Förnamn", + "Last Name": "Efternamn", + "Roles": "Roles", + "View the users assigned to this role": "View the users assigned to this role", + "Role": "Role", + "No users found": "No users found", + "Starting by assigning users to this role": "Starting by assigning users to this role", + "Project Role entry deleted successfully": "Project Role entry deleted successfully", + "Updated": "Updated", + "No project roles found": "No project roles found", + "Create custom project roles to manage permissions for platform users": "Create custom project roles to manage permissions for platform users", + "Show Users": "Show Users", + "View Role": "View Role", + "Edit Role": "Edit Role", + "Delete Role": "Delete Role", + "Project Role": "Projektroll", + "Unlock Embedding Through JS SDK": "Unlock Embedding Through JS SDK", + "Enable signing keys to access embedding functionalities.": "Enable signing keys to access embedding functionalities.", + "New Signing Key": "New Signing Key", + "No signing keys found": "No signing keys found", + "Create a signing key to authenticate users with embedding": "Create a signing key to authenticate users with embedding", + "Delete Signing Key": "Delete Signing Key", + "Are you sure you want to delete this signing key?": "Are you sure you want to delete this signing key?", + "Signing Key Created": "Signing Key Created", + "Create New Signing Key": "Create New Signing Key", + "Signing Key Name": "Signing Key Name", + "Allowed domains updated": "Allowed domains updated", + "Update": "Update", + "Enable": "Enable", + "Configure Allowed Domains": "Configure Allowed Domains", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.", + "Add Domain": "Add Domain", + "Allow logins through {providerName}'s single sign-on functionality.": "Allow logins through {providerName}'s single sign-on functionality.", + "Email authentication updated": "Email authentication updated", + "Enable Single Sign On": "Enable Single Sign On", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Let your users sign in with your current SSO provider or give them self serve sign up access", + "Allowed Domains": "Allowed Domains", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Allow users to authenticate with specific domains. Leave empty to allow all domains.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Allowed Email Login", + "Allow logins through email and password.": "Allow logins through email and password.", + "Single sign on settings updated": "Single sign on settings updated", + "Disable": "Disable", + "Configure {provider} SSO": "Configure {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} Client ID", + "{provider} Client Secret": "{provider} Client Secret", + "Single sign-on settings updated": "Single sign-on settings updated", + "Configure SAML 2.0 SSO": "Configure SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDP Metadata", + "IDP Certificate": "IDP Certificate", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "Base URL", + "Resource Name": "Resource Name", + "Deployment Name": "Deployment Name", + "Saving": "Saving", + "Activepieces Copilot": "Activepieces Copilot", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot is configured and ready to help your users build flows faster using AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configure Activepieces Copilot to help your users build flows faster using AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Set provider credentials that will be used by universal AI pieces, i.e Text AI.", + "AI Providers": "AI Providers", + "Copilot": "Copilot", + "Configure credentials for {providerName} AI provider.": "Configure credentials for {providerName} AI provider.", + "Update AI Provider": "Update AI Provider", + "Enable AI Provider": "Enable AI Provider", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Please enter a valid domain", + "Your changes have been saved.": "Your changes have been saved.", + "The domain is already added.": "The domain is already added.", + "Add Custom Domain": "Add Custom Domain", + "Enter a domain name without a protocol (e.g. example.com)": "Enter a domain name without a protocol (e.g. example.com)", + "Logo URL": "Logo URL", + "Icon URL": "Icon URL", + "Favicon URL": "Favicon URL", + "Default Language": "Default Language", + "Select Language": "Select Language", + "No Languages": "No Languages", + "Primary Color": "Primary Color", + "Custom Domains": "Custom Domains", + "No domains added yet.": "No domains added yet.", + "Verified": "Verified", + "Pending, please contact the support for dns verification.": "Pending, please contact the support for dns verification.", + "Are you sure you want to delete {domain}?": "Are you sure you want to delete {domain}?", + "Brand Activepieces": "Brand Activepieces", + "Give your users an experience that looks like you by customizing the color, logo and more": "Give your users an experience that looks like you by customizing the color, logo and more", + "Configure the appearance and SMTP settings for your platform.": "Configure the appearance and SMTP settings for your platform.", + "Invalid host": "Invalid host", + "Invalid username": "Invalid username", + "Invalid password": "Invalid password", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Invalid sender name", + "SMTP is configured": "SMTP is configured", + "Mail Server": "Mail Server", + "Set up your SMTP settings to send emails from your domain.": "Set up your SMTP settings to send emails from your domain.", + "Disable Mail Server": "Disable Mail Server", + "Are you sure you want to disable your mail server?": "Are you sure you want to disable your mail server?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "This will stop you from sending emails for issues, quota limits, invitations and forgot password.", + "mail server": "mail server", + "Host": "Host", + "Port": "Port", + "Username": "Username", + "Password": "Lösenord", + "Sender Email": "Sender Email", + "Sender Name": "Sender Name", + "Enable Global Connections": "Enable Global Connections", + "Manage platform-wide connections to external systems.": "Manage platform-wide connections to external systems.", + "No global connections found": "No global connections found", + "Create a global connection that can be shared to multiple projects": "Create a global connection that can be shared to multiple projects", + "License key is invalid": "License key is invalid", + "Invalid license key": "Invalid license key", + "License activated!": "License activated!", + "Activate License Key": "Activate License Key", + "Let the magic begin!": "Let the magic begin!", + "Activate": "Activate", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ", + "This feature is not available in your current edition. ": "This feature is not available in your current edition. ", + "Learn how to upgrade": "Learn how to upgrade", + "Activate your platform and unlock enterprise features": "Activate your platform and unlock enterprise features", + "Activate License": "Activate License", + "Expiration": "Expiration", + "Valid until": "Valid until", + "Expired": "Expired", + "Expires soon": "Expires soon", + "Features": "Features", + "Applying Tags...": "Applying Tags...", + "Tags applied.": "Tags applied.", + "Tag created": "Tag created", + "Tag": "Tag", + "Tags": "Tags", + "Control Pieces": "Control Pieces", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Show the pieces that matter most to your users and hide the ones you don't like.", + "Manage the pieces that are available to your users": "Manage the pieces that are available to your users", + "Start by installing pieces that you want to use in your automations": "Start by installing pieces that you want to use in your automations", + "Piece Name": "Piece Name", + "Hide this piece from all projects": "Hide this piece from all projects", + "Show this piece for all projects": "Show this piece for all projects", + "Unpin this piece": "Unpin this piece", + "Pin this piece": "Pin this piece", + "Pieces synced": "Pieces synced", + "Pieces have been synced from the activepieces cloud.": "Pieces have been synced from the activepieces cloud.", + "OAuth2 Credentials Deleted": "OAuth2 Credentials Deleted", + "OAuth2 Credentials Updated": "OAuth2 Credentials Updated", + "Configure OAuth2 APP": "Configure OAuth2 APP", + "Delete OAuth2 APP": "Delete OAuth2 APP", + "Templates deleted successfully": "Templates deleted successfully", + "Delete Templates": "Delete Templates", + "Are you sure you want to delete the selected templates?": "Are you sure you want to delete the selected templates?", + "New Template": "New Template", + "Unlock Templates": "Unlock Templates", + "Convert the most common automations into reusable templates 1 click away from your users": "Convert the most common automations into reusable templates 1 click away from your users", + "Convert the most common automations into reusable templates": "Convert the most common automations into reusable templates", + "No templates found": "No templates found", + "Create a template for your user to inspire them": "Create a template for your user to inspire them", + "Edit template": "Edit template", + "Template is required": "Template is required", + "Update New Template": "Update New Template", + "Create New Template": "Create New Template", + "Template Name": "Template Name", + "Description": "Description", + "Template Description": "Template Description", + "Blog URL": "Blog URL", + "Template Blog URL": "Template Blog URL", + "Template": "Template", + "Invalid JSON": "Invalid JSON", + "User deleted successfully": "User deleted successfully", + "User activated successfully": "User activated successfully", + "User deactivated successfully": "User deactivated successfully", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Manage your users and their access to your projects", + "Start inviting users to your project": "Start inviting users to your project", + "External Id": "External Id", + "Admin": "Admin", + "Member": "Member", + "Activated": "Activated", + "Deactivated": "Deactivated", + "Edit user": "Edit user", + "Admin cannot be deactivated": "Admin cannot be deactivated", + "Deactivate user": "Deactivate user", + "Activate user": "Activate user", + "Delete User": "Delete User", + "Are you sure you want to delete this user?": "Are you sure you want to delete this user?", + "Delete user": "Delete user", + "Update User Role": "Update User Role", + "Meeting Summary Flow": "Meeting Summary Flow", + "Added new features and fixed bugs": "Added new features and fixed bugs", + "Flows Changes": "Flows Changes", + "Connections Changes": "Connections Changes", + "New connections are placeholders and need to be reconnected again": "New connections are placeholders and need to be reconnected again", + "renamed to": "renamed to", + "Tables Changes": "Tables Changes", + "No changes to apply": "No changes to apply", + "Apply Changes": "Apply Changes", + "Create Git Release": "Create Git Release", + "Create Project Release": "Create Project Release", + "Create Rollback to": "Create Rollback to", + "Source": "Source", + "Rollback": "Rollback", + "Imported At": "Imported At", + "Imported By": "Imported By", + "Track and manage your project version history and deployments. ": "Track and manage your project version history and deployments. ", + "Environments & Releases": "Environments & Releases", + "Project Releases": "Project Releases", + "Create Release": "Create Release", + "From Git": "From Git", + "From Project": "From Project", + "No project releases found": "No project releases found", + "Create a project release to get started": "Create a project release to get started", + "Please select project": "Please select project", + "No Changes Found": "No Changes Found", + "There are no differences to apply": "There are no differences to apply", + "Please select a project": "Please select a project", + "Search projects...": "Search projects...", + "Review Changes": "Review Changes", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Imported by", + "from": "from", + "No description provided": "No description provided", + "Invitation only sign up": "Invitation only sign up", + "Please ask your administrator to add you to the organization.": "Please ask your administrator to add you to the organization.", + "Something went wrong, please try again.": "Something went wrong, please try again.", + "Please try again.": "Please try again.", + "Please enter a valid email address": "Please enter a valid email address", + "The email is already added.": "The email is already added.", + "Add email": "Add email", + "Only project admins can do this": "Only project admins can do this", + "Add Alert Email": "Add Alert Email", + "Enter the email address to receive alerts.": "Enter the email address to receive alerts.", + "Add Email": "Add Email", + "Emails": "Emails", + "Add email addresses to receive alerts.": "Add email addresses to receive alerts.", + "No emails added yet.": "No emails added yet.", + "Choose what you want to be notified about.": "Choose what you want to be notified about.", + "Project and alert permissions are required to change this setting.": "Project and alert permissions are required to change this setting.", + "Every Failed Run": "Every Failed Run", + "Get an email alert when a flow fails.": "Get an email alert when a flow fails.", + "Get an email alert when a new issue created.": "Get an email alert when a new issue created.", + "Never": "Aldrig", + "Turn off email notifications.": "Turn off email notifications.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Customize the appearance of the app. Automatically switch between day and night themes.", + "Select the theme for the dashboard.": "Select the theme for the dashboard.", + "Light": "Light", + "Dark": "Dark", + "Select the language that will be used in the dashboard.": "Select the language that will be used in the dashboard.", + "Select language": "Select language", + "Search language...": "Search language...", + "No language found.": "No language found.", + "Help us translate Activepieces to your language.": "Help us translate Activepieces to your language.", + "Learn more": "Learn more", + "Git Connection Removed": "Git Connection Removed", + "Your Git repository has been successfully disconnected": "Your Git repository has been successfully disconnected", + "Enable Environments": "Enable Environments", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Deploy flows across development, staging and production environments with version control and team collaboration", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Connect to Git to enable version control, backup your flows, and manage multiple environments. ", + "Repository URL": "Repository URL", + "Not connected": "Not connected", + "Project Folder": "Project Folder", + "Releases Enabled": "Releases Enabled", + "You have successfully enabled releases": "You have successfully enabled releases", + "Enable releases to easily create and manage project releases.": "Enable releases to easily create and manage project releases.", + "The external ID is already taken.": "The external ID is already taken.", + "Manage general settings for your project.": "Manage general settings for your project.", + "Used to identify the project based on your SaaS ID": "Used to identify the project based on your SaaS ID", + "org-3412321": "org-3412321", + "Delete {name}": "Radera {name}", + "This will permanently delete this piece, all steps using it will fail.": "This will permanently delete this piece, all steps using it will fail.", + "Add a piece to your project that you want to use in your automations": "Add a piece to your project that you want to use in your automations", + "Pieces list updated": "Pieces list updated", + "Manage Pieces": "Manage Pieces", + "Choose which pieces you want to be available for your current project users": "Choose which pieces you want to be available for your current project users", + "Unlock Team Permissions": "Unlock Team Permissions", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial", + "Project Members": "Projektmedlemmar", + "Invite your team members to collaborate.": "Invite your team members to collaborate.", + "No members are added to this project.": "No members are added to this project.", + "Pending Invitations": "Pending Invitations", + "No pending invitation.": "No pending invitation.", + "templateId is missing": "templateId is missing", + "Me Only": "Me Only", + "Unresolved": "Unresolved", + "Resolved": "Resolved", + "Title": "Title", + "Date Created": "Date Created", + "Manage todos for your project that are created by automations": "Manage todos for your project that are created by automations", + "No todos found": "No todos found", + "You do not have any pending todos. Great job!": "You do not have any pending todos. Great job!", + "Write a comment...": "Write a comment...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "This feature is still under testing and might be changed often", + "Failed to copy to clipboard": "Failed to copy to clipboard", + "{number} items selected": "{number} items selected", + "Select All": "Select All", + "No results found.": "No results found.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect must be used within MultiSelectProvider", + "Unset": "Unset", + "Refresh": "Refresh", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} more", + "Removed {entityName}": "Removed {entityName}", + "Download File": "Download File", + "Copied to clipboard": "Copied to clipboard", + "File is not available after execution.": "File is not available after execution.", + "Available for Projects": "Available for Projects", + "Select projects": "Select projects", + "No items": "No items", + "Previous": "Previous", + "to": "to", + "Last Week": "Last Week", + "Last Month": "Last Month", + "Last 3 Months": "Last 3 Months", + "Last 6 Months": "Last 6 Months", + "Next 7 days": "Next 7 days", + "Next 30 days": "Next 30 days", + "Next 90 days": "Next 90 days", + "Next 180 days": "Next 180 days", + "Select Time Range": "Select Time Range", + "Clear": "Clear", + "Download": "Download", + "Go to Dashboard": "Go to Dashboard", + "Select a file": "Select a file", + "Press space to separate values": "Press space to separate values", + "AM": "AM", + "PM": "PM", + "Already have an account?": "Already have an account?", + "Sign in": "Logga in", + "Don't have an account?": "Har du inget konto?", + "Sign up": "Registrera dig", + "Welcome Back!": "Welcome Back!", + "Enter your email below to sign in to your account": "Enter your email below to sign in to your account", + "Let's Get Started!": "Let's Get Started!", + "Create your account and start flowing!": "Create your account and start flowing!", + "Your password was changed successfully": "Your password was changed successfully", + "Your password reset request has expired, please request a new one": "Your password reset request has expired, please request a new one", + "Reset Password": "Återställ lösenord", + "Enter your new password": "Ange ditt nya lösenord", + "Password is required": "Password is required", + "Verification email resent, if previous one expired.": "Verification email resent, if previous one expired.", + "Password reset link resent, if previous one expired.": "Password reset link resent, if previous one expired.", + "We sent you a link to complete your registration to": "We sent you a link to complete your registration to", + "We sent you a link to reset your password to": "We sent you a link to reset your password to", + "Didn't receive an email or it expired?": "Didn't receive an email or it expired?", + "Resend": "Resend", + "Please enter your email": "Please enter your email", + "Check Your Inbox": "Check Your Inbox", + "If the user exists we'll send you an email with a link to reset your password.": "If the user exists we'll send you an email with a link to reset your password.", + "Send Password Reset Link": "Send Password Reset Link", + "Back to sign in": "Back to sign in", + "Email is invalid": "Email is invalid", + "Something went wrong, please try again later": "Something went wrong, please try again later", + "Invalid email or password": "Invalid email or password", + "User has been deactivated": "User has been deactivated", + "Email domain is disallowed": "Email domain is disallowed", + "Email authentication has been disabled": "Email authentication has been disabled", + "Forgot your password?": "Glömt ditt lösenord?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "Sign up is restricted. You need an invitation to join. Please contact the administrator.", + "Email is already used": "Email is already used", + "Email authentication is disabled": "Email authentication is disabled", + "First name is required": "First name is required", + "Last name is required": "Last name is required", + "Email is required": "Email is required", + "Receive updates and newsletters from activepieces": "Receive updates and newsletters from activepieces", + "By creating an account, you agree to our": "By creating an account, you agree to our", + "terms of service": "terms of service", + "privacy policy": "privacy policy", + "Sign up With": "Sign up With", + "Google": "Google", + "Sign in With": "Sign in With", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "Email has been verified. You will be redirected to sign in...", + "Verifying email...": "Verifying email...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "invitation has expired, once you sign in again you will be able to resend the verification email.", + "Redirecting to sign in...": "Redirecting to sign in...", + "Password must contain at least one special character": "Password must contain at least one special character", + "Password must contain at least one lowercase letter": "Password must contain at least one lowercase letter", + "Password must contain at least one uppercase letter": "Password must contain at least one uppercase letter", + "Password must contain at least one number": "Password must contain at least one number", + "8-64 Characters": "8-64 Characters", + "Special Character": "Special Character", + "Lowercase": "Lowercase", + "Uppercase": "Uppercase", + "Number": "Number", + "Connection has been updated.": "Connection has been updated.", + "Edit Global Connection": "Edit Global Connection", + "Connection has been renamed.": "Connection has been renamed.", + "New Connection Name": "New Connection Name", + "Connection name already used": "Connection name already used", + "Please select at least one project": "Please select at least one project", + "Run Succeeded": "Run Succeeded", + "Run Failed": "Run Failed", + "Flow Run is paused": "Flow Run is paused", + "Run Failed due to quota exceeded": "Run Failed due to quota exceeded", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Run failed due to exceeding the memory limit of {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "Run exceeded {timeout} seconds, try to optimize your steps.", + "Run failed for an unknown reason, contact support.": "Run failed for an unknown reason, contact support.", + "Unknown": "Unknown", + "Exit Run": "Exit Run", + "Select shown": "Select shown", + "Select all": "Select all", + "Start Time": "Start Time", + "Runs replayed successfully": "Runs replayed successfully", + "Retry": "Retry", + "all except": "all except", + "all": "all", + "Only failed runs can be retried from failed step": "Only failed runs can be retried from failed step", + "No flow runs found": "No flow runs found", + "Come back later when your automations start running": "Come back later when your automations start running", + "Step running": "Step running", + "Step paused": "Step paused", + "Step Stopped": "Step Stopped", + "Step Succeeded": "Step Succeeded", + "Step Failed": "Step Failed", + "Please publish flow first": "Please publish flow first", + "Flow is on": "Flow is on", + "Flow is off": "Flow is off", + "Permission Needed": "Permission Needed", + "Draft Version": "Draft Version", + "Published Version": "Published Version", + "Locked Version": "Locked Version", + "flowsImported": "{flowsCount, plural, =0 {No Flows Imported} =1 {Flow Imported Successfully} other {Flows Imported Successfully}}", + "Template file is invalid": "Template file is invalid", + "No valid templates found. The following files failed to import: ": "No valid templates found. The following files failed to import: ", + "Please select a file first": "Please select a file first", + "Unsupported file type": "Unsupported file type", + "Import Flow": "Import Flow", + "Warning": "Warning", + "Importing a flow will overwrite your current one.": "Importing a flow will overwrite your current one.", + "Select a folder": "Select a folder", + "Folders": "Mappar", + "Please select a folder": "Please select a folder", + "Moved flows successfully": "Moved flows successfully", + "Move Selected Flows": "Move Selected Flows", + "Select Folder": "Select Folder", + "No Folders": "No Folders", + "Flow has been renamed.": "Flow has been renamed.", + "New Flow Name": "New Flow Name", + "Use Template": "Use Template", + "Browse Templates": "Browse Templates", + "Search templates": "Search templates", + "No templates found, try adjusting your search": "No templates found, try adjusting your search", + "Read more about this template in": "Read more about this template in", + "this blog!": "this blog!", + "Share Template": "Share Template", + "Generate or update a template link for the current flow to easily share it with others.": "Generate or update a template link for the current flow to easily share it with others.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "The template will not have any credentials in connection fields, keeping sensitive information secure.", + "A short description of the template": "A short description of the template", + "Flow Is In Use": "Flow Is In Use", + "Flow is being used by another user, please try again later.": "Flow is being used by another user, please try again later.", + "Flow has been published.": "Flow has been published.", + "Flows have been exported.": "Flows have been exported.", + "Run": "Run", + "Real time flow": "Real time flow", + "Flow can't be published with empty trigger {name}": "Flow can't be published with empty trigger {name}", + "Please contact support as your published flow has a problem": "Please contact support as your published flow has a problem", + "Actions": "Åtgärder", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.", + "You are on a development branch, this will not delete the flows from the remote repository.": "You are on a development branch, this will not delete the flows from the remote repository.", + "Please enter folder name": "Please enter folder name", + "Added folder successfully": "Added folder successfully", + "The folder name already exists.": "The folder name already exists.", + "New Folder": "Ny mapp", + "Folder Name": "Mappnamn", + "Loading...": "Loading...", + "Delete {folderName}": "Delete {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "If you delete this folder, we will keep its flows and move them to Uncategorized.", + "All flows": "All flows", + "Please enter a folder name": "Please enter a folder name", + "Renamed flow successfully": "Renamed flow successfully", + "Folder name already used": "Folder name already used", + "New Folder Name": "New Folder Name", + "Connected successfully": "Connected successfully", + "Connect Git": "Connect Git", + "Remote URL": "Remote URL", + "Folder name is the name of the folder where the project will be stored or fetched.": "Folder name is the name of the folder where the project will be stored or fetched.", + "SSH Private Key": "SSH Private Key", + "The SSH private key to use for authentication.": "The SSH private key to use for authentication.", + "Only published flows can be pushed to Git": "Only published flows can be pushed to Git", + "Pushed successfully": "Pushed successfully", + "Invalid Operation": "Invalid Operation", + "Commit Message": "Commit Message", + "Enter a commit message to describe the changes you want to push.": "Enter a commit message to describe the changes you want to push.", + "Push": "Push", + "This field is required": "This field is required", + "Your submission was successfully received.": "Your submission was successfully received.", + "Flow not found": "Flow not found", + "The flow you are trying to submit to does not exist.": "The flow you are trying to submit to does not exist.", + "The flow failed to execute.": "The flow failed to execute.", + "Submit": "Skicka", + "issues-notification": "issues-notification", + "Please select a package type": "Please select a package type", + "package.json not found in archive": "package.json not found in archive", + "Error processing archive file": "Error processing archive file", + "Please upload a .tgz file": "Please upload a .tgz file", + "Piece name is required for NPM Registry": "Piece name is required for NPM Registry", + "Piece version is required for NPM Registry": "Piece version is required for NPM Registry", + "Piece installed": "Piece installed", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "A piece with this name and version is already installed. Please update the version number in package.json and try again.", + "Install Piece": "Install Piece", + "Install a piece": "Install a piece", + "Package Type": "Package Type", + "NPM Registry": "NPM Registry", + "Packed Archive (.tgz)": "Packed Archive (.tgz)", + "Piece Version": "Piece Version", + "Package Archive": "Package Archive", + "Package archive": "Package archive", + "Powerful Node.js & TypeScript code with npm": "Powerful Node.js & TypeScript code with npm", + "Loop on Items": "Loop on Items", + "Split your flow into branches depending on condition(s)": "Split your flow into branches depending on condition(s)", + "Empty Trigger": "Empty Trigger", + "An internal error occurred while fetching data, please contact support": "An internal error occurred while fetching data, please contact support", + "An internal error occurred, please contact support": "An internal error occurred, please contact support", + "Custom Javascript Code": "Custom Javascript Code", + "Router": "Router", + "recordsCount": "{recordsCount, plural, =0 {0 Records} =1 {1 Record} other {# Records}}", + "selected": "selected", + "All records selected": "All records selected", + "fieldsCount": "{fieldsCount, plural, =0 {0 Fields} =1 {1 Field} other {# Fields}}", + "Saving...": "Saving...", + "Loading more...": "Loading more...", + "Export Table": "Export Table", + "Delete Records": "Delete Records", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Are you sure you want to delete the selected records? This action cannot be undone.", + "record": "record", + "records": "records", + "mm/dd/yyy": "mm/dd/yyy", + "Delete Field": "Delete Field", + "Are you sure you want to delete this field? This action cannot be undone.": "Are you sure you want to delete this field? This action cannot be undone.", + "field": "field", + "Ignored": "Ignored", + "Table": "Table", + "CSV": "CSV", + "Field": "Field", + "Please select a csv file": "Please select a csv file", + "Max file size is {maxFileSize}MB": "Max file size is {maxFileSize}MB", + "Import CSV": "Import CSV", + "Imported records will be added to the bottom of the table": "Imported records will be added to the bottom of the table", + "Any records after the limit ({maxRecords} records) will be ignored": "Any records after the limit ({maxRecords} records) will be ignored", + "CSV File": "CSV File", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support", + "Name must be unique": "Name must be unique", + "Type is required": "Type is required", + "Please add at least one option": "Please add at least one option", + "New Field": "New Field", + "Options": "Options", + "Name is already taken": "Name is already taken", + "Table renamed": "Table renamed", + "Table name": "Table name", + "Team Invitation Accepted": "Team Invitation Accepted", + "Thank you for accepting the invitation. We are redirecting you right now...": "Thank you for accepting the invitation. We are redirecting you right now...", + "Invalid invitation token. Please try again.": "Invalid invitation token. Please try again.", + "Role updated successfully": "Role updated successfully", + "Error updating role": "Error updating role", + "Please try again later": "Please try again later", + "Edit Role for": "Edit Role for", + "Select Role": "Select Role", + "Avatar": "Avatar", + "Remove {email}": "Remove {email}", + "Are you sure you want to remove this invitation?": "Are you sure you want to remove this invitation?", + "Please select invitation type": "Please select invitation type", + "Please select platform role": "Please select platform role", + "Invitation sent successfully": "Invitation sent successfully", + "Please select a project role": "Please select a project role", + "Invitation link copied successfully": "Invitation link copied successfully", + "Invite User": "Invite User", + "Invitation Link": "Invitation Link", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Type the email address of the user you want to invite, the invitation expires in 24 hours.", + "Invite To": "Invite To", + "Entire Platform": "Entire Platform", + "Select Project Role": "Select Project Role", + "Invite": "Invite", + "Platform Role": "Platform Role", + "Select a platform role": "Select a platform role", + "Are you sure you want to remove this member?": "Are you sure you want to remove this member?", + "Editor": "Editor", + "Operator": "Operator", + "Viewer": "Viewer", + "Select a project role": "Select a project role", + "Steps in this flow": "Steps in this flow", + "Invalid Access": "Invalid Access", + "Either the project does not exist or you do not have access to it.": "Either the project does not exist or you do not have access to it." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/uk/translation.json b/packages/react-ui/public/locales/uk/translation.json new file mode 100644 index 0000000..82e6525 --- /dev/null +++ b/packages/react-ui/public/locales/uk/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Опублікувати", + "Latest version is published": "Остання версія опублікована", + "Your flow has incomplete steps": "Your flow has incomplete steps", + "Edit Flow": "Редагувати потік", + "View Draft": "View Draft", + "Uncategorized": "Без категорії", + "Go to folder": "Перейти до теки", + "Support": "Підтримка", + "Runs": "Runs", + "Run Logs": "Журнали запуску", + "Versions": "Versions", + "Versions History": "Versions History", + "Error generating code": "Помилка створення коду", + "AI Copilot": "AI Copilot", + "i.e Calculate the sum of a list...": "i.e Calculate the sum of a list...", + "Send": "Send", + "Generating Code": "Generating Code", + "Hello there! I am here to generate code that helps with your flow": "Hello there! I am here to generate code that helps with your flow", + "Here are examples of what I am best used for: ": "Here are examples of what I am best used for: ", + "Text Processing": "Text Processing", + "Process strings, dates and data": "Process strings, dates and data", + "Data Operations": "Data Operations", + "Change data from one format to another": "Change data from one format to another", + "Calculations": "Calculations", + "Handle math and statistics": "Handle math and statistics", + "API Integration": "API Integration", + "Connect with external services. Best for simple integrations currently.": "Connect with external services. Best for simple integrations currently.", + "What would you like me to help you with?": "What would you like me to help you with?", + "Insert": "Insert", + "Data Selector": "Data Selector", + "Search": "Search", + "No matching data": "No matching data", + "Try adjusting your search": "Try adjusting your search", + "This trigger needs to have data loaded from your account, to use as sample data.": "This trigger needs to have data loaded from your account, to use as sample data.", + "This step needs to be tested in order to view its data.": "This step needs to be tested in order to view its data.", + "Go to Trigger": "Go to Trigger", + "Go to Step": "Go to Step", + "Select Mode": "Select Mode", + "Move Mode": "Move Mode", + "Reset Zoom": "Reset Zoom", + "Zoom In": "Zoom In", + "Zoom Out": "Zoom Out", + "Fit to View": "Fit to View", + "Replace": "Replace", + "Copy": "Copy", + "Duplicate": "Duplicate", + "Paste After Last Step": "Paste After Last Step", + "Paste Inside Loop": "Paste Inside Loop", + "Paste After": "Paste After", + "Paste Inside...": "Paste Inside...", + "New Branch": "New Branch", + "Paste Inside Branch": "Paste Inside Branch", + "Delete": "Delete", + "Duplicate Branch": "Duplicate Branch", + "Delete Branch": "Delete Branch", + "Invalid Move": "Invalid Move", + "The destination location is a child of the dragged step": "The destination location is a child of the dragged step", + "End": "End", + "Skipped": "Skipped", + "Incomplete settings": "Incomplete settings", + "logo": "logo", + "Step Icon": "Step Icon", + "Branch": "Branch", + "incompleteSteps": "{invalidSteps, plural, =0 {no incomplete steps} =1 {Complete 1 step} other {Complete # steps}}", + "Test Flow": "Test Flow", + "Please test the trigger first": "Please test the trigger first", + "View Only": "View Only", + "Use as Draft": "Use as Draft", + "Are you sure?": "Are you sure?", + "Your current draft version will be overwritten with": "Your current draft version will be overwritten with", + "version #": "version #", + "Cancel": "Cancel", + "Confirm": "Confirm", + "Version": "Version", + "Viewing": "Viewing", + "View": "View", + "Version History": "Історія версій", + "Error, please try again.": "Error, please try again.", + "Continue on Failure": "Continue on Failure", + "Enable this option to skip this step and continue the flow normally if it fails.": "Enable this option to skip this step and continue the flow normally if it fails.", + "Retry on Failure": "Retry on Failure", + "Automatically retry up to four attempts when failed.": "Automatically retry up to four attempts when failed.", + "Remove": "Remove", + "Add Item": "Add Item", + "File Input": "File Input", + "Date Input": "Date Input", + "Dynamic value": "Dynamic value", + "Select an option": "Select an option", + "Unexpected error, please retry": "Unexpected error, please retry", + "Unexpected error, please refresh the page or contact support": "Unexpected error, please refresh the page or contact support", + "Name can only contain letters, numbers and underscores": "Name can only contain letters, numbers and underscores", + "Ask AI": "Ask AI", + "Create Todo Guide": "Create Todo Guide", + "Where would you like the todo to be reviewed?": "Where would you like the todo to be reviewed?", + "Activepieces Todos": "Activepieces Todos", + "Users will manage tasks directly in Activepieces": "Users will manage tasks directly in Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.", + "External Channel (Slack, Teams, Email, ...)": "External Channel (Slack, Teams, Email, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.", + "Preview (Activepieces Todos)": "Preview (Activepieces Todos)", + "Preview (External channel)": "Preview (External channel)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "You can add the channel before the Wait Step, and configure the logic in the Router step", + "Add Steps": "Add Steps", + "All": "All", + "AI": "AI", + "Core": "Core", + "Apps": "Apps", + "Not available as trigger": "Not available as trigger", + "Not available as action": "Not available as action", + "Let our AI assistant help you out": "Let our AI assistant help you out", + "Or": "Or", + "Request Piece": "Request Piece", + "No pieces found": "No pieces found", + "Please select a piece first": "Please select a piece first", + "All Iterations": "All Iterations", + "Duration": "Duration", + "Input": "Input", + "Output": "Output", + "There are no logs captured for this run.": "There are no logs captured for this run.", + "Logs are kept for {days} days after execution and then deleted.": "Logs are kept for {days} days after execution and then deleted.", + "Run Details": "Run Details", + "Iteration": "Iteration", + "Done": "Done", + "Took": "Took", + "Running": "Running", + "on latest version": "on latest version", + "from failed step": "from failed step", + "Recent Runs": "Recent Runs", + "No runs found": "No runs found", + "Close": "Close", + "OR": "OR", + "And If": "And If", + "+ And": "+ And", + "+ Or": "+ Or", + "(Text) Contains": "(Text) Contains", + "(Text) Does not contain": "(Text) Does not contain", + "(Text) Exactly matches": "(Text) Exactly matches", + "(Text) Does not exactly match": "(Text) Does not exactly match", + "(Text) Starts with": "(Text) Starts with", + "(Text) Does not start with": "(Text) Does not start with", + "(Text) Ends with": "(Text) Ends with", + "(Text) Does not end with": "(Text) Does not end with", + "(List) Contains": "(List) Contains", + "(List) Does not contain": "(List) Does not contain", + "(Number) Is greater than": "(Number) Is greater than", + "(Number) Is less than": "(Number) Is less than", + "(Number) Is equal to": "(Number) Is equal to", + "(Date/time) After": "(Date/time) After", + "(Date/time) Before": "(Date/time) Before", + "(Date/time) Equals": "(Date/time) Equals", + "(Boolean) Is true": "(Boolean) Is true", + "(Boolean) Is false": "(Boolean) Is false", + "(List) Is empty": "(List) Is empty", + "(List) Is not empty": "(List) Is not empty", + "Exists": "Exists", + "Does not exist": "Does not exist", + "Incomplete condition": "Incomplete condition", + "First value": "First value", + "Second value": "Second value", + "Case sensitive": "Case sensitive", + "Execute If": "Execute If", + "The package name is required": "The package name is required", + "Success": "Успішно", + "Package added successfully": "Package added successfully", + "Could not fetch package version": "Could not fetch package version", + "Add NPM Package": "Add NPM Package", + "Type the name of the npm package you want to add.": "Type the name of the npm package you want to add.", + "Package Name": "Package Name", + "The latest version will be fetched and added": "The latest version will be fetched and added", + "Add": "Add", + "Code": "Code", + "Dependencies": "Dependencies", + "Use code": "Use code", + "Add package": "Add package", + "Inputs": "Inputs", + "Edit Step Name": "Edit Step Name", + "Edit Branch Name": "Edit Branch Name", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.", + "Items": "Items", + "Select an array of items": "Select an array of items", + "Reconnect": "Reconnect", + "Select a connection": "Select a connection", + "Create Connection": "Create Connection", + "Rename": "Rename", + "Move": "Move", + "Add Branch": "Add Branch", + "Execute": "Execute", + "Only the first (left) matching branch": "Only the first (left) matching branch", + "All matching paths from left to right": "All matching paths from left to right", + "Branches": "Branches", + "{field} is required": "{field} is required", + "Tool Sample Data": "Tool Sample Data", + "Fill in the following fields to use them as sample data for the trigger.": "Fill in the following fields to use them as sample data for the trigger.", + "No input fields defined in the schema": "No input fields defined in the schema", + "Save": "Save", + "Test Environment": "Test Environment", + "Assigned to": "Assigned to", + "(Me)": "(Me)", + "Please select status to resolve the todo": "Please select status to resolve the todo", + "Resolve": "Resolve", + "Change status to resolved": "Change status to resolved", + "Send Sample Data to Webhook": "Send Sample Data to Webhook", + "Method": "Method", + "Query Params": "Query Params", + "Headers": "Headers", + "Body": "Body", + "Type": "Type", + "JSON": "JSON", + "Text": "Text", + "Form Data": "Form Data", + "Generate Sample Data": "Generate Sample Data", + "Test Step": "Test Step", + "Retest": "Retest", + "Testing Failed": "Testing Failed", + "Tested Successfully": "Tested Successfully", + "Logs": "Logs", + "There is no sample data available found for this trigger.": "There is no sample data available found for this trigger.", + "Internal error, please try again later.": "Internal error, please try again later.", + "Failed to run test step and no error message was returned": "Failed to run test step and no error message was returned", + "Please fix inputs first": "Please fix inputs first", + "No sample data available": "No sample data available", + "Old results were removed, retest for new sample data": "Old results were removed, retest for new sample data", + "Result #": "Result #", + "The sample data can be used in the next steps.": "The sample data can be used in the next steps.", + "Testing Trigger": "Testing Trigger", + "Action Required": "Action Required", + "testPieceWebhookTriggerNote": "Please go to {pieceName} and trigger {triggerName}.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Send Data to the webhook URL to generate sample data to use in the next steps", + "Test Trigger": "Test Trigger", + "Use Mock Data": "Use Mock Data", + "Load Sample Data": "Load Sample Data", + "Test Tool": "Test Tool", + "home": "home", + "Home": "Головна", + "Alerts": "Alerts", + "Releases": "Releases", + "Flows": "Flows", + "Products": "Products", + "MCP": "MCP", + "Tables": "Tables", + "Todos": "Todos", + "Push to Git": "Push to Git", + "Move To": "Move To", + "Duplicating": "Duplicating", + "Import": "Import", + "Exporting": "Exporting", + "Export": "Export", + "Share": "Share", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.", + "You are on a development branch, this will also delete the flow from the remote repository.": "You are on a development branch, this will also delete the flow from the remote repository.", + "flow": "flow", + "Community Support": "Community Support", + "Overview": "Overview", + "Projects": "Projects", + "Users": "Users", + "Setup": "Setup", + "Branding": "Branding", + "Global Connections": "Global Connections", + "Pieces": "Pieces", + "Templates": "Templates", + "License Key": "License Key", + "Security": "Security", + "Audit Logs": "Audit Logs", + "Single Sign On": "Single Sign On", + "Signing Keys": "Signing Keys", + "Project Roles": "Project Roles", + "API Keys": "API Keys", + "Infrastructure": "Infrastructure", + "Workers": "Workers", + "Health": "Health", + "Billing": "Billing", + "Settings": "Settings", + "Contact Sales": "Contact Sales", + "General": "General", + "Appearance": "Appearance", + "Team": "Team", + "Environments": "Environments", + "Project Settings": "Project Settings", + "Exit Platform Admin": "Exit Platform Admin", + "Enter Platform Admin": "Enter Platform Admin", + "Logout": "Logout", + "Misc": "Misc", + "Connections": "Connections", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "Today": "Today", + "Tasks": "Tasks", + "AI Credits": "AI Credits", + "Usage resets in": "Usage resets in", + "Manage": "Manage", + "Unlimited": "Unlimited", + "Enabled": "Enabled", + "Disabled": "Disabled", + "Could not claim the authorization code, make sure you have correct settings and try again.": "Could not claim the authorization code, make sure you have correct settings and try again.", + "Connection failed with error {msg}": "Connection failed with error {msg}", + "You don't have the permission to create a connection.": "You don't have the permission to create a connection.", + "Reconnect {displayName} Connection": "Reconnect {displayName}", + "Connect to {displayName}": "Connect to {displayName}", + "Connection Name": "Connection Name", + "Connection name": "Connection name", + "New Connection": "New Connection", + "Redirect URL": "Redirect URL", + "Client ID": "Client ID", + "Client Secret": "Client Secret", + "Connect": "Connect", + "Disconnect": "Disconnect", + "I would like to use my own App Credentials": "I would like to use my own App Credentials", + "I would like to use predefined App Credentials": "I would like to use predefined App Credentials", + "Permission needed": "Permission needed", + "Connections replaced successfully": "Connections replaced successfully", + "Error": "Error", + "Failed to replace connections": "Failed to replace connections", + "Failed to get affected flows": "Failed to get affected flows", + "Please select a piece": "Please select a piece", + "Please select a connection to replace": "Please select a connection to replace", + "Please select a connection to replace with": "Please select a connection to replace with", + "Replace Connections": "Replace Connections", + "Confirm Replacement": "Confirm Replacement", + "Replace one connection with another.": "Replace one connection with another.", + "This action requires ": "This action requires ", + "reconnecting": "reconnecting", + " any associated MCP pieces.": " any associated MCP pieces.", + "Piece": "Piece", + "Select a piece": "Select a piece", + "Connection to Replace": "Connection to Replace", + "Choose connection to replace": "Choose connection to replace", + "Replaced With": "Replaced With", + "Choose connection to replace with": "Choose connection to replace with", + "All flows will be changed to use the replaced with connection": "All flows will be changed to use the replaced with connection", + "Next": "Next", + "No flows will be affected by this change": "No flows will be affected by this change", + "Back": "Back", + "Unnamed tool": "Unnamed tool", + "This flow is enabled": "This flow is enabled", + "Enable this flow to make it available": "Enable this flow to make it available", + "Piece is updated successfully": "Piece is updated successfully", + "Piece is added successfully": "Piece is added successfully", + "Failed to update piece": "Failed to update piece", + "Failed to add piece": "Failed to add piece", + "Please select a connection": "Please select a connection", + "Your MCP server already has this piece": "Your MCP server already has this piece", + "+ New Connection": "+ New Connection", + "Edit Piece": "Edit Piece", + "Add Piece": "Add Piece", + "Connection": "Connection", + "MCP piece": "MCP piece", + "Failed to update piece status": "Failed to update piece status", + "Connection required": "Connection required", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.", + "piece": "piece", + "Connect your AI assistant to external services": "Connect your AI assistant to external services", + "Collapse": "Collapse", + "Show All": "Show All", + "Server URL": "Server URL", + "Hide the token for security": "Hide the token for security", + "Show the token": "Show the token", + "Generate a new token for security. This will invalidate the current URL.": "Generate a new token for security. This will invalidate the current URL.", + "URL copied to clipboard": "URL copied to clipboard", + "Copy URL to clipboard": "Copy URL to clipboard", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.", + "Max tables reached": "Max tables reached", + "You can't create more than {maxTables} tables": "You can't create more than {maxTables} tables", + "Name": "Name", + "Created": "Created", + "Delete Tables": "Delete Tables", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Are you sure you want to delete the selected tables? This action cannot be undone.", + "table": "table", + "Create and manage your tables to store your automation data": "Create and manage your tables to store your automation data", + "New Table": "New Table", + "No tables have been created yet": "No tables have been created yet", + "Create a table to get started and start managing your automation data": "Create a table to get started and start managing your automation data", + "Error deleting connections": "Error deleting connections", + "Status": "Status", + "Display Name": "Display Name", + "Owner": "Owner", + "App": "App", + "This connection is global and can be managed in the platform admin": "This connection is global and can be managed in the platform admin", + "External ID": "External ID", + "Connected At": "Connected At", + "Confirm Deletion": "Confirm Deletion", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Are you sure you want to delete the selected connections? This action cannot be undone.", + "Deleting connections may cause your Flows or MCP tools to break.": "Deleting connections may cause your Flows or MCP tools to break.", + "Manage project connections to external systems.": "Manage project connections to external systems.", + "No connections found": "No connections found", + "Come back later when you create a automation to manage your connections": "Come back later when you create a automation to manage your connections", + "Steps": "Steps", + "Folder": "Folder", + "Flow name": "Flow name", + "No flows found": "No flows found", + "Create a workflow to start automating": "Create a workflow to start automating", + "Create and manage your flows, run history and run issues": "Create and manage your flows, run history and run issues", + "Issues": "Issues", + "Untitled": "Untitled", + "Create flow": "Create flow", + "From scratch": "From scratch", + "Use a template": "Use a template", + "From local file": "From local file", + "Flow Name": "Flow Name", + "Count": "Count", + "First Seen": "First Seen", + "Last Seen": "Last Seen", + "Issues in {flowDisplayName} is marked as resolved.": "Issues in {flowDisplayName} is marked as resolved.", + "Unlock Issues": "Unlock Issues", + "Track issues in your workflows and troubleshoot them.": "Track issues in your workflows and troubleshoot them.", + "No issues found": "No issues found", + "All your workflows are running smoothly.": "All your workflows are running smoothly.", + "Mark as Resolved": "Mark as Resolved", + "All Flows Are Turned Off": "All Flows Are Turned Off", + "Task Usage Exceeded": "Task Usage Exceeded", + "of the Allowed Limit.": "of the Allowed Limit.", + "When a project tasks limit is reached,": "When a project tasks limit is reached,", + "all flows will be turned off and you will not be able to run any flows.": "all flows will be turned off and you will not be able to run any flows.", + "Please visit": "Please visit", + "Your Plan": "Your Plan", + "and increase your task limit, which requires your payment details.": "and increase your task limit, which requires your payment details.", + "Please contact your admin to increase the project task limit.": "Please contact your admin to increase the project task limit.", + "and increase the project task limit.": "and increase the project task limit.", + "Dismiss": "Dismiss", + "Token rotated successfully": "Token rotated successfully", + "Failed to rotate token": "Failed to rotate token", + "Piece removed successfully": "Piece removed successfully", + "Failed to remove piece": "Failed to remove piece", + "Flow created successfully": "Flow created successfully", + "Failed to create flow": "Failed to create flow", + "Add Flow": "Add Flow", + "Let your AI assistant trigger automations": "Let your AI assistant trigger automations", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Connect to your hosted MCP Server using any MCP client to communicate with tools", + "MCP Server": "MCP Server", + "My Tools": "My Tools", + "Create Flow": "Create Flow", + "Note": "Note", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.", + "This URL grants access to your tools and data. Only share with trusted applications.": "This URL grants access to your tools and data. Only share with trusted applications.", + "Server Configuration": "Server Configuration", + "Hide sensitive data": "Hide sensitive data", + "Show sensitive data": "Show sensitive data", + "Create a new URL. The current one will stop working.": "Create a new URL. The current one will stop working.", + "Copy configuration": "Copy configuration", + "Configuration copied to clipboard": "Configuration copied to clipboard", + "Claude": "Claude", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "Server/Other", + "Note: MCPs only work with": "Note: MCPs only work with", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", not the web version.", + "Prerequisites:": "Prerequisites:", + "Install": "Install", + "Node.js": "Node.js", + "and": "and", + "Open Settings:": "Open Settings:", + "Click the menu and select": "Click the menu and select", + "Developer": "Developer", + "Configure MCP:": "Configure MCP:", + "Click": "Click", + "Edit Config": "Edit Config", + "and paste the configuration below": "and paste the configuration below", + "Save and Restart:": "Save and Restart:", + "Save the config and restart Claude Desktop": "Save the config and restart Claude Desktop", + "Navigate to": "Navigate to", + "Cursor Settings": "Cursor Settings", + "Add Server:": "Add Server:", + "Add new global MCP server": "Add new global MCP server", + "Configure:": "Configure:", + "Paste the configuration below and save": "Paste the configuration below and save", + "Use either method:": "Use either method:", + "Go to": "Go to", + "Advanced Settings": "Advanced Settings", + "Open Command Palette and select": "Open Command Palette and select", + "Windsurf Settings Page": "Windsurf Settings Page", + "Navigate to Cascade:": "Navigate to Cascade:", + "Select": "Select", + "Cascade": "Cascade", + "in the sidebar": "in the sidebar", + "Add Server": "Add Server", + "Add custom server +": "Add custom server +", + "Copy URL": "Copy URL", + "Client Setup Instructions": "Client Setup Instructions", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "After changing connections or flows, reconnect your MCP server for changes to take effect.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.", + "icon": "icon", + "Unlock Analytics": "Unlock Analytics", + "Get insights into your platform usage and performance with our analytics dashboard": "Get insights into your platform usage and performance with our analytics dashboard", + "Active Flows": "Active Flows", + "The number of enabled flows in the platform": "The number of enabled flows in the platform", + "Active Projects": "Active Projects", + "The number of projects with at least one enabled flow": "The number of projects with at least one enabled flow", + "Active Users": "Active Users", + "The number of users logged in the last 30 days": "The number of users logged in the last 30 days", + "Out of {totalusers} total users": "Out of {totalusers} total users", + "Pieces Used": "Pieces Used", + "The number of unique pieces used across all flows": "The number of unique pieces used across all flows", + "Flows with AI": "Flows with AI", + "The number of enabled flows that use AI pieces": "The number of enabled flows that use AI pieces", + "Metrics": "Metrics", + "Executed Tasks": "Executed Tasks", + "Showing total executed tasks for specified time range": "Showing total executed tasks for specified time range", + "Tasks Usage Limit": "Tasks Usage Limit", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.", + "Number of monthly tasks": "Number of monthly tasks", + "Save changes": "Save changes", + "Limits updated successfully": "Limits updated successfully", + "Failed to update limits": "Failed to update limits", + "Failed to load billing information": "Failed to load billing information", + "Billing Amount": "Billing Amount", + "Manage Payment Details": "Manage Payment Details", + "Add Payment Details": "Add Payment Details", + "Current Task Usage": "Current Task Usage", + "Count of executed steps": "Count of executed steps", + "Billing Limit": "Billing Limit", + "Edit": "Edit", + "Add Limit": "Add Limit", + "Current Credit Usage": "Current Credit Usage", + "WebSocket Connection": "WebSocket Connection", + "Connected": "Connected", + "Disconnected": "Disconnected", + "No issues detected": "No issues detected", + "Check the status of your platform and its components": "Check the status of your platform and its components", + "System Health Status": "System Health Status", + "All systems are running smoothly": "All systems are running smoothly", + "Check the health of your worker machines": "Check the health of your worker machines", + "Workers Machine": "Workers Machine", + "This is demo data. In a real environment, this would show your actual worker machines.": "This is demo data. In a real environment, this would show your actual worker machines.", + "No workers found": "No workers found", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "You don't have any worker machines yet. Spin up new machines to execute your automations", + "IP Address": "IP Address", + "CPU Usage": "CPU Usage", + "Disk Usage": "Disk Usage", + "RAM Usage": "RAM Usage", + "Last Contact": "Last Contact", + "Configs": "Configs", + "Environment Variables": "Environment Variables", + "Websocket Connection Error": "Websocket Connection Error", + "Retry Connection": "Retry Connection", + "Update Available": "Update Available", + "Update Now": "Update Now", + "Your Universal AI needs a quick setup": "Your Universal AI needs a quick setup", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.", + "Configure": "Configure", + "Platform Alerts": "Platform Alerts", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.", + "View Alerts": "View Alerts", + "Used Tasks": "Used Tasks", + "Used AI Credits": "Used AI Credits", + "Cannot delete active project, switch to another project first": "Cannot delete active project, switch to another project first", + "Delete Projects": "Delete Projects", + "Are you sure you want to delete the selected projects?": "Are you sure you want to delete the selected projects?", + "New Project": "New Project", + "Validation error": "Validation error", + "Project has enabled flows. Please disable them first.": "Project has enabled flows. Please disable them first.", + "This project is active. Please switch to another project first.": "This project is active. Please switch to another project first.", + "Unlock Projects": "Unlock Projects", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Orchestrate your automation teams across projects with their own flows, connections and usage quotas", + "Manage your automation projects": "Manage your automation projects", + "No projects found": "No projects found", + "Start by creating projects to manage your automation teams": "Start by creating projects to manage your automation teams", + "Edit project": "Edit project", + "Name is required": "Name is required", + "Create New Project": "Create New Project", + "Project Name": "Project Name", + "Id": "Id", + "Enable API Keys": "Enable API Keys", + "Create and manage API keys to access Activepieces APIs.": "Create and manage API keys to access Activepieces APIs.", + "New Api Key": "New Api Key", + "No API keys found": "No API keys found", + "Start by creating an API key to communicate with Activepieces APIs": "Start by creating an API key to communicate with Activepieces APIs", + "Delete API Key": "Delete API Key", + "Are you sure you want to delete this API key?": "Are you sure you want to delete this API key?", + "API Key": "API Key", + "API Key Created": "API Key Created", + "Create New API Key": "Create New API Key", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Please save this secret key somewhere safe and accessible. For security reasons,", + "you won't be able to view it again after closing this dialog.": "you won't be able to view it again after closing this dialog.", + "API Key Name": "API Key Name", + "Action": "Action", + "Performed By": "Performed By", + "Project": "Project", + "Unlock Audit Logs": "Unlock Audit Logs", + "Comply with internal and external security policies by tracking activities done within your account": "Comply with internal and external security policies by tracking activities done within your account", + "Track activities done within your platform": "Track activities done within your platform", + "No audit logs found": "No audit logs found", + "Come back later when you have some activity to audit": "Come back later when you have some activity to audit", + "Resource": "Resource", + "Details": "Details", + "N/A": "N/A", + "Flow Run": "Flow Run", + "Flow": "Flow", + "User": "User", + "Signing Key": "Signing Key", + "Project Role Management": "Project Role Management", + "Define custom roles and permissions to control what your team members can access and modify": "Define custom roles and permissions to control what your team members can access and modify", + "Define custom roles and permissions that can be assigned to your team members": "Define custom roles and permissions that can be assigned to your team members", + "New Role": "New Role", + "Contact sales to unlock custom roles": "Contact sales to unlock custom roles", + "Create New Role": "Create New Role", + "View ": "View ", + "Edit ": "Edit ", + "Role Name": "Role Name", + "Permissions": "Permissions", + "None": "None", + "Read": "Read", + "Write": "Write", + "Create": "Create", + "Email": "Email", + "First Name": "First Name", + "Last Name": "Last Name", + "Roles": "Roles", + "View the users assigned to this role": "View the users assigned to this role", + "Role": "Role", + "No users found": "No users found", + "Starting by assigning users to this role": "Starting by assigning users to this role", + "Project Role entry deleted successfully": "Project Role entry deleted successfully", + "Updated": "Updated", + "No project roles found": "No project roles found", + "Create custom project roles to manage permissions for platform users": "Create custom project roles to manage permissions for platform users", + "Show Users": "Show Users", + "View Role": "View Role", + "Edit Role": "Edit Role", + "Delete Role": "Delete Role", + "Project Role": "Project Role", + "Unlock Embedding Through JS SDK": "Unlock Embedding Through JS SDK", + "Enable signing keys to access embedding functionalities.": "Enable signing keys to access embedding functionalities.", + "New Signing Key": "New Signing Key", + "No signing keys found": "No signing keys found", + "Create a signing key to authenticate users with embedding": "Create a signing key to authenticate users with embedding", + "Delete Signing Key": "Delete Signing Key", + "Are you sure you want to delete this signing key?": "Are you sure you want to delete this signing key?", + "Signing Key Created": "Signing Key Created", + "Create New Signing Key": "Create New Signing Key", + "Signing Key Name": "Signing Key Name", + "Allowed domains updated": "Allowed domains updated", + "Update": "Update", + "Enable": "Enable", + "Configure Allowed Domains": "Configure Allowed Domains", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.", + "Add Domain": "Add Domain", + "Allow logins through {providerName}'s single sign-on functionality.": "Allow logins through {providerName}'s single sign-on functionality.", + "Email authentication updated": "Email authentication updated", + "Enable Single Sign On": "Enable Single Sign On", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Let your users sign in with your current SSO provider or give them self serve sign up access", + "Allowed Domains": "Allowed Domains", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Allow users to authenticate with specific domains. Leave empty to allow all domains.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Allowed Email Login", + "Allow logins through email and password.": "Allow logins through email and password.", + "Single sign on settings updated": "Single sign on settings updated", + "Disable": "Disable", + "Configure {provider} SSO": "Configure {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} Client ID", + "{provider} Client Secret": "{provider} Client Secret", + "Single sign-on settings updated": "Single sign-on settings updated", + "Configure SAML 2.0 SSO": "Configure SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDP Metadata", + "IDP Certificate": "IDP Certificate", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "Base URL", + "Resource Name": "Resource Name", + "Deployment Name": "Deployment Name", + "Saving": "Saving", + "Activepieces Copilot": "Activepieces Copilot", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot is configured and ready to help your users build flows faster using AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configure Activepieces Copilot to help your users build flows faster using AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Set provider credentials that will be used by universal AI pieces, i.e Text AI.", + "AI Providers": "AI Providers", + "Copilot": "Copilot", + "Configure credentials for {providerName} AI provider.": "Configure credentials for {providerName} AI provider.", + "Update AI Provider": "Update AI Provider", + "Enable AI Provider": "Enable AI Provider", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Please enter a valid domain", + "Your changes have been saved.": "Your changes have been saved.", + "The domain is already added.": "The domain is already added.", + "Add Custom Domain": "Add Custom Domain", + "Enter a domain name without a protocol (e.g. example.com)": "Enter a domain name without a protocol (e.g. example.com)", + "Logo URL": "Logo URL", + "Icon URL": "Icon URL", + "Favicon URL": "Favicon URL", + "Default Language": "Default Language", + "Select Language": "Select Language", + "No Languages": "No Languages", + "Primary Color": "Primary Color", + "Custom Domains": "Custom Domains", + "No domains added yet.": "No domains added yet.", + "Verified": "Verified", + "Pending, please contact the support for dns verification.": "Pending, please contact the support for dns verification.", + "Are you sure you want to delete {domain}?": "Are you sure you want to delete {domain}?", + "Brand Activepieces": "Brand Activepieces", + "Give your users an experience that looks like you by customizing the color, logo and more": "Give your users an experience that looks like you by customizing the color, logo and more", + "Configure the appearance and SMTP settings for your platform.": "Configure the appearance and SMTP settings for your platform.", + "Invalid host": "Invalid host", + "Invalid username": "Invalid username", + "Invalid password": "Invalid password", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Invalid sender name", + "SMTP is configured": "SMTP is configured", + "Mail Server": "Mail Server", + "Set up your SMTP settings to send emails from your domain.": "Set up your SMTP settings to send emails from your domain.", + "Disable Mail Server": "Disable Mail Server", + "Are you sure you want to disable your mail server?": "Are you sure you want to disable your mail server?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "This will stop you from sending emails for issues, quota limits, invitations and forgot password.", + "mail server": "mail server", + "Host": "Host", + "Port": "Port", + "Username": "Username", + "Password": "Password", + "Sender Email": "Sender Email", + "Sender Name": "Sender Name", + "Enable Global Connections": "Enable Global Connections", + "Manage platform-wide connections to external systems.": "Manage platform-wide connections to external systems.", + "No global connections found": "No global connections found", + "Create a global connection that can be shared to multiple projects": "Create a global connection that can be shared to multiple projects", + "License key is invalid": "License key is invalid", + "Invalid license key": "Invalid license key", + "License activated!": "License activated!", + "Activate License Key": "Activate License Key", + "Let the magic begin!": "Let the magic begin!", + "Activate": "Activate", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ", + "This feature is not available in your current edition. ": "This feature is not available in your current edition. ", + "Learn how to upgrade": "Learn how to upgrade", + "Activate your platform and unlock enterprise features": "Activate your platform and unlock enterprise features", + "Activate License": "Activate License", + "Expiration": "Expiration", + "Valid until": "Valid until", + "Expired": "Expired", + "Expires soon": "Expires soon", + "Features": "Features", + "Applying Tags...": "Applying Tags...", + "Tags applied.": "Tags applied.", + "Tag created": "Tag created", + "Tag": "Tag", + "Tags": "Tags", + "Control Pieces": "Control Pieces", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Show the pieces that matter most to your users and hide the ones you don't like.", + "Manage the pieces that are available to your users": "Manage the pieces that are available to your users", + "Start by installing pieces that you want to use in your automations": "Start by installing pieces that you want to use in your automations", + "Piece Name": "Piece Name", + "Hide this piece from all projects": "Hide this piece from all projects", + "Show this piece for all projects": "Show this piece for all projects", + "Unpin this piece": "Unpin this piece", + "Pin this piece": "Pin this piece", + "Pieces synced": "Pieces synced", + "Pieces have been synced from the activepieces cloud.": "Pieces have been synced from the activepieces cloud.", + "OAuth2 Credentials Deleted": "OAuth2 Credentials Deleted", + "OAuth2 Credentials Updated": "OAuth2 Credentials Updated", + "Configure OAuth2 APP": "Configure OAuth2 APP", + "Delete OAuth2 APP": "Delete OAuth2 APP", + "Templates deleted successfully": "Templates deleted successfully", + "Delete Templates": "Delete Templates", + "Are you sure you want to delete the selected templates?": "Are you sure you want to delete the selected templates?", + "New Template": "New Template", + "Unlock Templates": "Unlock Templates", + "Convert the most common automations into reusable templates 1 click away from your users": "Convert the most common automations into reusable templates 1 click away from your users", + "Convert the most common automations into reusable templates": "Convert the most common automations into reusable templates", + "No templates found": "No templates found", + "Create a template for your user to inspire them": "Create a template for your user to inspire them", + "Edit template": "Edit template", + "Template is required": "Template is required", + "Update New Template": "Update New Template", + "Create New Template": "Create New Template", + "Template Name": "Template Name", + "Description": "Description", + "Template Description": "Template Description", + "Blog URL": "Blog URL", + "Template Blog URL": "Template Blog URL", + "Template": "Template", + "Invalid JSON": "Invalid JSON", + "User deleted successfully": "User deleted successfully", + "User activated successfully": "User activated successfully", + "User deactivated successfully": "User deactivated successfully", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Manage your users and their access to your projects", + "Start inviting users to your project": "Start inviting users to your project", + "External Id": "External Id", + "Admin": "Admin", + "Member": "Member", + "Activated": "Activated", + "Deactivated": "Deactivated", + "Edit user": "Edit user", + "Admin cannot be deactivated": "Admin cannot be deactivated", + "Deactivate user": "Deactivate user", + "Activate user": "Activate user", + "Delete User": "Delete User", + "Are you sure you want to delete this user?": "Are you sure you want to delete this user?", + "Delete user": "Delete user", + "Update User Role": "Update User Role", + "Meeting Summary Flow": "Meeting Summary Flow", + "Added new features and fixed bugs": "Added new features and fixed bugs", + "Flows Changes": "Flows Changes", + "Connections Changes": "Connections Changes", + "New connections are placeholders and need to be reconnected again": "New connections are placeholders and need to be reconnected again", + "renamed to": "renamed to", + "Tables Changes": "Tables Changes", + "No changes to apply": "No changes to apply", + "Apply Changes": "Apply Changes", + "Create Git Release": "Create Git Release", + "Create Project Release": "Create Project Release", + "Create Rollback to": "Create Rollback to", + "Source": "Source", + "Rollback": "Rollback", + "Imported At": "Imported At", + "Imported By": "Imported By", + "Track and manage your project version history and deployments. ": "Track and manage your project version history and deployments. ", + "Environments & Releases": "Environments & Releases", + "Project Releases": "Project Releases", + "Create Release": "Create Release", + "From Git": "From Git", + "From Project": "From Project", + "No project releases found": "No project releases found", + "Create a project release to get started": "Create a project release to get started", + "Please select project": "Please select project", + "No Changes Found": "No Changes Found", + "There are no differences to apply": "There are no differences to apply", + "Please select a project": "Please select a project", + "Search projects...": "Search projects...", + "Review Changes": "Review Changes", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Imported by", + "from": "from", + "No description provided": "No description provided", + "Invitation only sign up": "Invitation only sign up", + "Please ask your administrator to add you to the organization.": "Please ask your administrator to add you to the organization.", + "Something went wrong, please try again.": "Something went wrong, please try again.", + "Please try again.": "Please try again.", + "Please enter a valid email address": "Please enter a valid email address", + "The email is already added.": "The email is already added.", + "Add email": "Add email", + "Only project admins can do this": "Only project admins can do this", + "Add Alert Email": "Add Alert Email", + "Enter the email address to receive alerts.": "Enter the email address to receive alerts.", + "Add Email": "Add Email", + "Emails": "Emails", + "Add email addresses to receive alerts.": "Add email addresses to receive alerts.", + "No emails added yet.": "No emails added yet.", + "Choose what you want to be notified about.": "Choose what you want to be notified about.", + "Project and alert permissions are required to change this setting.": "Project and alert permissions are required to change this setting.", + "Every Failed Run": "Every Failed Run", + "Get an email alert when a flow fails.": "Get an email alert when a flow fails.", + "Get an email alert when a new issue created.": "Get an email alert when a new issue created.", + "Never": "Never", + "Turn off email notifications.": "Turn off email notifications.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Customize the appearance of the app. Automatically switch between day and night themes.", + "Select the theme for the dashboard.": "Select the theme for the dashboard.", + "Light": "Light", + "Dark": "Dark", + "Select the language that will be used in the dashboard.": "Select the language that will be used in the dashboard.", + "Select language": "Select language", + "Search language...": "Search language...", + "No language found.": "No language found.", + "Help us translate Activepieces to your language.": "Help us translate Activepieces to your language.", + "Learn more": "Learn more", + "Git Connection Removed": "Git Connection Removed", + "Your Git repository has been successfully disconnected": "Your Git repository has been successfully disconnected", + "Enable Environments": "Enable Environments", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Deploy flows across development, staging and production environments with version control and team collaboration", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Connect to Git to enable version control, backup your flows, and manage multiple environments. ", + "Repository URL": "Repository URL", + "Not connected": "Not connected", + "Project Folder": "Project Folder", + "Releases Enabled": "Releases Enabled", + "You have successfully enabled releases": "You have successfully enabled releases", + "Enable releases to easily create and manage project releases.": "Enable releases to easily create and manage project releases.", + "The external ID is already taken.": "The external ID is already taken.", + "Manage general settings for your project.": "Manage general settings for your project.", + "Used to identify the project based on your SaaS ID": "Used to identify the project based on your SaaS ID", + "org-3412321": "org-3412321", + "Delete {name}": "Delete {name}", + "This will permanently delete this piece, all steps using it will fail.": "This will permanently delete this piece, all steps using it will fail.", + "Add a piece to your project that you want to use in your automations": "Add a piece to your project that you want to use in your automations", + "Pieces list updated": "Pieces list updated", + "Manage Pieces": "Manage Pieces", + "Choose which pieces you want to be available for your current project users": "Choose which pieces you want to be available for your current project users", + "Unlock Team Permissions": "Unlock Team Permissions", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial", + "Project Members": "Project Members", + "Invite your team members to collaborate.": "Invite your team members to collaborate.", + "No members are added to this project.": "No members are added to this project.", + "Pending Invitations": "Pending Invitations", + "No pending invitation.": "No pending invitation.", + "templateId is missing": "templateId is missing", + "Me Only": "Me Only", + "Unresolved": "Unresolved", + "Resolved": "Resolved", + "Title": "Title", + "Date Created": "Date Created", + "Manage todos for your project that are created by automations": "Manage todos for your project that are created by automations", + "No todos found": "No todos found", + "You do not have any pending todos. Great job!": "You do not have any pending todos. Great job!", + "Write a comment...": "Write a comment...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "This feature is still under testing and might be changed often", + "Failed to copy to clipboard": "Failed to copy to clipboard", + "{number} items selected": "{number} items selected", + "Select All": "Select All", + "No results found.": "No results found.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect must be used within MultiSelectProvider", + "Unset": "Unset", + "Refresh": "Refresh", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} more", + "Removed {entityName}": "Removed {entityName}", + "Download File": "Download File", + "Copied to clipboard": "Copied to clipboard", + "File is not available after execution.": "File is not available after execution.", + "Available for Projects": "Available for Projects", + "Select projects": "Select projects", + "No items": "No items", + "Previous": "Previous", + "to": "to", + "Last Week": "Last Week", + "Last Month": "Last Month", + "Last 3 Months": "Last 3 Months", + "Last 6 Months": "Last 6 Months", + "Next 7 days": "Next 7 days", + "Next 30 days": "Next 30 days", + "Next 90 days": "Next 90 days", + "Next 180 days": "Next 180 days", + "Select Time Range": "Select Time Range", + "Clear": "Clear", + "Download": "Download", + "Go to Dashboard": "Go to Dashboard", + "Select a file": "Select a file", + "Press space to separate values": "Press space to separate values", + "AM": "AM", + "PM": "PM", + "Already have an account?": "Already have an account?", + "Sign in": "Sign in", + "Don't have an account?": "Don't have an account?", + "Sign up": "Sign up", + "Welcome Back!": "Welcome Back!", + "Enter your email below to sign in to your account": "Enter your email below to sign in to your account", + "Let's Get Started!": "Let's Get Started!", + "Create your account and start flowing!": "Create your account and start flowing!", + "Your password was changed successfully": "Your password was changed successfully", + "Your password reset request has expired, please request a new one": "Your password reset request has expired, please request a new one", + "Reset Password": "Reset Password", + "Enter your new password": "Enter your new password", + "Password is required": "Password is required", + "Verification email resent, if previous one expired.": "Verification email resent, if previous one expired.", + "Password reset link resent, if previous one expired.": "Password reset link resent, if previous one expired.", + "We sent you a link to complete your registration to": "We sent you a link to complete your registration to", + "We sent you a link to reset your password to": "We sent you a link to reset your password to", + "Didn't receive an email or it expired?": "Didn't receive an email or it expired?", + "Resend": "Resend", + "Please enter your email": "Please enter your email", + "Check Your Inbox": "Check Your Inbox", + "If the user exists we'll send you an email with a link to reset your password.": "If the user exists we'll send you an email with a link to reset your password.", + "Send Password Reset Link": "Send Password Reset Link", + "Back to sign in": "Back to sign in", + "Email is invalid": "Email is invalid", + "Something went wrong, please try again later": "Something went wrong, please try again later", + "Invalid email or password": "Invalid email or password", + "User has been deactivated": "User has been deactivated", + "Email domain is disallowed": "Email domain is disallowed", + "Email authentication has been disabled": "Email authentication has been disabled", + "Forgot your password?": "Forgot your password?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "Sign up is restricted. You need an invitation to join. Please contact the administrator.", + "Email is already used": "Email is already used", + "Email authentication is disabled": "Email authentication is disabled", + "First name is required": "First name is required", + "Last name is required": "Last name is required", + "Email is required": "Email is required", + "Receive updates and newsletters from activepieces": "Receive updates and newsletters from activepieces", + "By creating an account, you agree to our": "By creating an account, you agree to our", + "terms of service": "terms of service", + "privacy policy": "privacy policy", + "Sign up With": "Sign up With", + "Google": "Google", + "Sign in With": "Sign in With", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "Email has been verified. You will be redirected to sign in...", + "Verifying email...": "Verifying email...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "invitation has expired, once you sign in again you will be able to resend the verification email.", + "Redirecting to sign in...": "Redirecting to sign in...", + "Password must contain at least one special character": "Password must contain at least one special character", + "Password must contain at least one lowercase letter": "Password must contain at least one lowercase letter", + "Password must contain at least one uppercase letter": "Password must contain at least one uppercase letter", + "Password must contain at least one number": "Password must contain at least one number", + "8-64 Characters": "8-64 Characters", + "Special Character": "Special Character", + "Lowercase": "Lowercase", + "Uppercase": "Uppercase", + "Number": "Number", + "Connection has been updated.": "Connection has been updated.", + "Edit Global Connection": "Edit Global Connection", + "Connection has been renamed.": "Connection has been renamed.", + "New Connection Name": "New Connection Name", + "Connection name already used": "Connection name already used", + "Please select at least one project": "Please select at least one project", + "Run Succeeded": "Run Succeeded", + "Run Failed": "Run Failed", + "Flow Run is paused": "Flow Run is paused", + "Run Failed due to quota exceeded": "Run Failed due to quota exceeded", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Run failed due to exceeding the memory limit of {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "Run exceeded {timeout} seconds, try to optimize your steps.", + "Run failed for an unknown reason, contact support.": "Run failed for an unknown reason, contact support.", + "Unknown": "Unknown", + "Exit Run": "Exit Run", + "Select shown": "Select shown", + "Select all": "Select all", + "Start Time": "Start Time", + "Runs replayed successfully": "Runs replayed successfully", + "Retry": "Retry", + "all except": "all except", + "all": "all", + "Only failed runs can be retried from failed step": "Only failed runs can be retried from failed step", + "No flow runs found": "No flow runs found", + "Come back later when your automations start running": "Come back later when your automations start running", + "Step running": "Step running", + "Step paused": "Step paused", + "Step Stopped": "Step Stopped", + "Step Succeeded": "Step Succeeded", + "Step Failed": "Step Failed", + "Please publish flow first": "Please publish flow first", + "Flow is on": "Flow is on", + "Flow is off": "Flow is off", + "Permission Needed": "Permission Needed", + "Draft Version": "Draft Version", + "Published Version": "Published Version", + "Locked Version": "Locked Version", + "flowsImported": "{flowsCount, plural, =0 {No Flows Imported} =1 {Flow Imported Successfully} other {Flows Imported Successfully}}", + "Template file is invalid": "Template file is invalid", + "No valid templates found. The following files failed to import: ": "No valid templates found. The following files failed to import: ", + "Please select a file first": "Please select a file first", + "Unsupported file type": "Unsupported file type", + "Import Flow": "Import Flow", + "Warning": "Warning", + "Importing a flow will overwrite your current one.": "Importing a flow will overwrite your current one.", + "Select a folder": "Select a folder", + "Folders": "Folders", + "Please select a folder": "Please select a folder", + "Moved flows successfully": "Moved flows successfully", + "Move Selected Flows": "Move Selected Flows", + "Select Folder": "Select Folder", + "No Folders": "No Folders", + "Flow has been renamed.": "Flow has been renamed.", + "New Flow Name": "New Flow Name", + "Use Template": "Use Template", + "Browse Templates": "Browse Templates", + "Search templates": "Search templates", + "No templates found, try adjusting your search": "No templates found, try adjusting your search", + "Read more about this template in": "Read more about this template in", + "this blog!": "this blog!", + "Share Template": "Share Template", + "Generate or update a template link for the current flow to easily share it with others.": "Generate or update a template link for the current flow to easily share it with others.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "The template will not have any credentials in connection fields, keeping sensitive information secure.", + "A short description of the template": "A short description of the template", + "Flow Is In Use": "Flow Is In Use", + "Flow is being used by another user, please try again later.": "Flow is being used by another user, please try again later.", + "Flow has been published.": "Потік було опубліковано.", + "Flows have been exported.": "Flows have been exported.", + "Run": "Run", + "Real time flow": "Real time flow", + "Flow can't be published with empty trigger {name}": "Flow can't be published with empty trigger {name}", + "Please contact support as your published flow has a problem": "Please contact support as your published flow has a problem", + "Actions": "Actions", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.", + "You are on a development branch, this will not delete the flows from the remote repository.": "You are on a development branch, this will not delete the flows from the remote repository.", + "Please enter folder name": "Please enter folder name", + "Added folder successfully": "Added folder successfully", + "The folder name already exists.": "The folder name already exists.", + "New Folder": "New Folder", + "Folder Name": "Folder Name", + "Loading...": "Loading...", + "Delete {folderName}": "Delete {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "If you delete this folder, we will keep its flows and move them to Uncategorized.", + "All flows": "All flows", + "Please enter a folder name": "Please enter a folder name", + "Renamed flow successfully": "Renamed flow successfully", + "Folder name already used": "Folder name already used", + "New Folder Name": "New Folder Name", + "Connected successfully": "Connected successfully", + "Connect Git": "Connect Git", + "Remote URL": "Remote URL", + "Folder name is the name of the folder where the project will be stored or fetched.": "Folder name is the name of the folder where the project will be stored or fetched.", + "SSH Private Key": "SSH Private Key", + "The SSH private key to use for authentication.": "The SSH private key to use for authentication.", + "Only published flows can be pushed to Git": "Only published flows can be pushed to Git", + "Pushed successfully": "Pushed successfully", + "Invalid Operation": "Invalid Operation", + "Commit Message": "Commit Message", + "Enter a commit message to describe the changes you want to push.": "Enter a commit message to describe the changes you want to push.", + "Push": "Push", + "This field is required": "This field is required", + "Your submission was successfully received.": "Your submission was successfully received.", + "Flow not found": "Flow not found", + "The flow you are trying to submit to does not exist.": "The flow you are trying to submit to does not exist.", + "The flow failed to execute.": "The flow failed to execute.", + "Submit": "Submit", + "issues-notification": "issues-notification", + "Please select a package type": "Please select a package type", + "package.json not found in archive": "package.json not found in archive", + "Error processing archive file": "Error processing archive file", + "Please upload a .tgz file": "Please upload a .tgz file", + "Piece name is required for NPM Registry": "Piece name is required for NPM Registry", + "Piece version is required for NPM Registry": "Piece version is required for NPM Registry", + "Piece installed": "Piece installed", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "A piece with this name and version is already installed. Please update the version number in package.json and try again.", + "Install Piece": "Install Piece", + "Install a piece": "Install a piece", + "Package Type": "Package Type", + "NPM Registry": "NPM Registry", + "Packed Archive (.tgz)": "Packed Archive (.tgz)", + "Piece Version": "Piece Version", + "Package Archive": "Package Archive", + "Package archive": "Package archive", + "Powerful Node.js & TypeScript code with npm": "Powerful Node.js & TypeScript code with npm", + "Loop on Items": "Loop on Items", + "Split your flow into branches depending on condition(s)": "Split your flow into branches depending on condition(s)", + "Empty Trigger": "Empty Trigger", + "An internal error occurred while fetching data, please contact support": "An internal error occurred while fetching data, please contact support", + "An internal error occurred, please contact support": "An internal error occurred, please contact support", + "Custom Javascript Code": "Custom Javascript Code", + "Router": "Router", + "recordsCount": "{recordsCount, plural, =0 {0 Records} =1 {1 Record} other {# Records}}", + "selected": "selected", + "All records selected": "All records selected", + "fieldsCount": "{fieldsCount, plural, =0 {0 Fields} =1 {1 Field} other {# Fields}}", + "Saving...": "Saving...", + "Loading more...": "Loading more...", + "Export Table": "Export Table", + "Delete Records": "Delete Records", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Are you sure you want to delete the selected records? This action cannot be undone.", + "record": "record", + "records": "records", + "mm/dd/yyy": "mm/dd/yyy", + "Delete Field": "Delete Field", + "Are you sure you want to delete this field? This action cannot be undone.": "Are you sure you want to delete this field? This action cannot be undone.", + "field": "field", + "Ignored": "Ignored", + "Table": "Table", + "CSV": "CSV", + "Field": "Field", + "Please select a csv file": "Please select a csv file", + "Max file size is {maxFileSize}MB": "Max file size is {maxFileSize}MB", + "Import CSV": "Import CSV", + "Imported records will be added to the bottom of the table": "Imported records will be added to the bottom of the table", + "Any records after the limit ({maxRecords} records) will be ignored": "Any records after the limit ({maxRecords} records) will be ignored", + "CSV File": "CSV File", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support", + "Name must be unique": "Name must be unique", + "Type is required": "Type is required", + "Please add at least one option": "Please add at least one option", + "New Field": "New Field", + "Options": "Options", + "Name is already taken": "Name is already taken", + "Table renamed": "Table renamed", + "Table name": "Table name", + "Team Invitation Accepted": "Team Invitation Accepted", + "Thank you for accepting the invitation. We are redirecting you right now...": "Thank you for accepting the invitation. We are redirecting you right now...", + "Invalid invitation token. Please try again.": "Invalid invitation token. Please try again.", + "Role updated successfully": "Role updated successfully", + "Error updating role": "Error updating role", + "Please try again later": "Please try again later", + "Edit Role for": "Edit Role for", + "Select Role": "Select Role", + "Avatar": "Avatar", + "Remove {email}": "Remove {email}", + "Are you sure you want to remove this invitation?": "Are you sure you want to remove this invitation?", + "Please select invitation type": "Please select invitation type", + "Please select platform role": "Please select platform role", + "Invitation sent successfully": "Invitation sent successfully", + "Please select a project role": "Please select a project role", + "Invitation link copied successfully": "Invitation link copied successfully", + "Invite User": "Invite User", + "Invitation Link": "Invitation Link", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Type the email address of the user you want to invite, the invitation expires in 24 hours.", + "Invite To": "Invite To", + "Entire Platform": "Entire Platform", + "Select Project Role": "Select Project Role", + "Invite": "Invite", + "Platform Role": "Platform Role", + "Select a platform role": "Select a platform role", + "Are you sure you want to remove this member?": "Are you sure you want to remove this member?", + "Editor": "Editor", + "Operator": "Operator", + "Viewer": "Viewer", + "Select a project role": "Select a project role", + "Steps in this flow": "Steps in this flow", + "Invalid Access": "Invalid Access", + "Either the project does not exist or you do not have access to it.": "Either the project does not exist or you do not have access to it." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/vi/translation.json b/packages/react-ui/public/locales/vi/translation.json new file mode 100644 index 0000000..96f26ee --- /dev/null +++ b/packages/react-ui/public/locales/vi/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "Xuất bản", + "Latest version is published": "Phiên bản mới nhất được xuất bản", + "Your flow has incomplete steps": "Your flow has incomplete steps", + "Edit Flow": "Chỉnh sửa luồng", + "View Draft": "View Draft", + "Uncategorized": "Chưa phân loại", + "Go to folder": "Đi tới thư mục", + "Support": "Hổ trợ", + "Runs": "Chạy", + "Run Logs": "Chạy nhật ký", + "Versions": "Versions", + "Versions History": "Versions History", + "Error generating code": "Lỗi tạo mã", + "AI Copilot": "AI Copilot", + "i.e Calculate the sum of a list...": "i.e Calculate the sum of a list...", + "Send": "Gửi", + "Generating Code": "Generating Code", + "Hello there! I am here to generate code that helps with your flow": "Hello there! I am here to generate code that helps with your flow", + "Here are examples of what I am best used for: ": "Here are examples of what I am best used for: ", + "Text Processing": "Text Processing", + "Process strings, dates and data": "Process strings, dates and data", + "Data Operations": "Data Operations", + "Change data from one format to another": "Change data from one format to another", + "Calculations": "Calculations", + "Handle math and statistics": "Handle math and statistics", + "API Integration": "API Integration", + "Connect with external services. Best for simple integrations currently.": "Connect with external services. Best for simple integrations currently.", + "What would you like me to help you with?": "What would you like me to help you with?", + "Insert": "Chèn", + "Data Selector": "Bộ chọn dữ liệu", + "Search": "Tìm kiếm", + "No matching data": "Không có dữ liệu phù hợp", + "Try adjusting your search": "Hãy thử điều chỉnh tìm kiếm của bạn", + "This trigger needs to have data loaded from your account, to use as sample data.": "Trình kích hoạt này cần phải tải dữ liệu từ tài khoản của bạn để sử dụng làm dữ liệu mẫu.", + "This step needs to be tested in order to view its data.": "Bước này cần phải được kiểm tra để xem dữ liệu.", + "Go to Trigger": "Đi tới trình kích hoạt", + "Go to Step": "Đi tới Bước", + "Select Mode": "Select Mode", + "Move Mode": "Move Mode", + "Reset Zoom": "Đặt lại thu phóng", + "Zoom In": "Phóng to", + "Zoom Out": "Thu nhỏ", + "Fit to View": "Phù hợp để xem", + "Replace": "Replace", + "Copy": "Sao chép", + "Duplicate": "Nhân bản", + "Paste After Last Step": "Paste After Last Step", + "Paste Inside Loop": "Paste Inside Loop", + "Paste After": "Paste After", + "Paste Inside...": "Paste Inside...", + "New Branch": "New Branch", + "Paste Inside Branch": "Paste Inside Branch", + "Delete": "Xóa", + "Duplicate Branch": "Duplicate Branch", + "Delete Branch": "Delete Branch", + "Invalid Move": "Di chuyển không hợp lệ", + "The destination location is a child of the dragged step": "The destination location is a child of the dragged step", + "End": "Kết thúc", + "Skipped": "Skipped", + "Incomplete settings": "Cài đặt chưa hoàn chỉnh", + "logo": "logo", + "Step Icon": "Biểu tượng bước", + "Branch": "Nhánh", + "incompleteSteps": "{invalidSteps, plural, =0 {không có bước nào chưa hoàn thành} =1 {Hoàn thành 1 bước} other {Hoàn thành # bước}}", + "Test Flow": "Luồng thử nghiệm", + "Please test the trigger first": "Vui lòng kiểm tra trình kích hoạt trước", + "View Only": "Chỉ xem", + "Use as Draft": "Sử dụng làm bản nháp", + "Are you sure?": "Bạn có chắc không?", + "Your current draft version will be overwritten with": "Phiên bản nháp hiện tại của bạn sẽ bị ghi đè bằng", + "version #": "phiên bản #", + "Cancel": "Hủy bỏ", + "Confirm": "Xác nhận", + "Version": "Phiên bản", + "Viewing": "Đang xem", + "View": "Xem", + "Version History": "Lịch sử phiên bản", + "Error, please try again.": "Lỗi, vui lòng thử lại.", + "Continue on Failure": "Tiếp tục thất bại", + "Enable this option to skip this step and continue the flow normally if it fails.": "Bật tùy chọn này để bỏ qua bước này và tiếp tục quy trình bình thường nếu không thành công.", + "Retry on Failure": "Thử lại khi thất bại", + "Automatically retry up to four attempts when failed.": "Tự động thử lại tối đa bốn lần khi thất bại.", + "Remove": "Di dời", + "Add Item": "Thêm mục", + "File Input": "File Input", + "Date Input": "Date Input", + "Dynamic value": "Dynamic value", + "Select an option": "Chọn một tùy chọn", + "Unexpected error, please retry": "Unexpected error, please retry", + "Unexpected error, please refresh the page or contact support": "Unexpected error, please refresh the page or contact support", + "Name can only contain letters, numbers and underscores": "Name can only contain letters, numbers and underscores", + "Ask AI": "Hỏi AI", + "Create Todo Guide": "Create Todo Guide", + "Where would you like the todo to be reviewed?": "Where would you like the todo to be reviewed?", + "Activepieces Todos": "Activepieces Todos", + "Users will manage tasks directly in Activepieces": "Users will manage tasks directly in Activepieces", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.", + "External Channel (Slack, Teams, Email, ...)": "External Channel (Slack, Teams, Email, ...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.", + "Preview (Activepieces Todos)": "Preview (Activepieces Todos)", + "Preview (External channel)": "Preview (External channel)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "You can add the channel before the Wait Step, and configure the logic in the Router step", + "Add Steps": "Add Steps", + "All": "All", + "AI": "AI", + "Core": "Core", + "Apps": "Apps", + "Not available as trigger": "Not available as trigger", + "Not available as action": "Not available as action", + "Let our AI assistant help you out": "Let our AI assistant help you out", + "Or": "Or", + "Request Piece": "Request Piece", + "No pieces found": "Không tìm thấy mảnh nào", + "Please select a piece first": "Please select a piece first", + "All Iterations": "Tất cả các lần lặp lại", + "Duration": "Khoảng thời gian", + "Input": "đầu vào", + "Output": "đầu ra", + "There are no logs captured for this run.": "There are no logs captured for this run.", + "Logs are kept for {days} days after execution and then deleted.": "Logs are kept for {days} days after execution and then deleted.", + "Run Details": "Chi tiết chạy", + "Iteration": "Lặp lại", + "Done": "Xong", + "Took": "Lấy đi", + "Running": "Đang chạy", + "on latest version": "on latest version", + "from failed step": "from failed step", + "Recent Runs": "Lần chạy gần đây", + "No runs found": "Không tìm thấy lượt chạy nào", + "Close": "Đóng", + "OR": "HOẶC", + "And If": "Và Nếu", + "+ And": "+ Và", + "+ Or": "+ Hoặc", + "(Text) Contains": "(Chữ) Có chứa ", + "(Text) Does not contain": "(Chữ) Không chứa", + "(Text) Exactly matches": "(Chữ) Trùng khớp chính xác", + "(Text) Does not exactly match": "(Chữ) Không khớp chính xác", + "(Text) Starts with": "(Chữ) Bắt đầu bằng", + "(Text) Does not start with": "(Chữ) Không bắt đầu bằng", + "(Text) Ends with": "(Chữ) Kết thúc bằng", + "(Text) Does not end with": "(Chữ) Không kết thúc bằng", + "(List) Contains": "(List) Contains", + "(List) Does not contain": "(List) Does not contain", + "(Number) Is greater than": "(Số) Lớn hơn", + "(Number) Is less than": "(Số) Nhỏ hơn", + "(Number) Is equal to": "(Số) Bằng", + "(Date/time) After": "(Date/time) After", + "(Date/time) Before": "(Date/time) Before", + "(Date/time) Equals": "(Date/time) Equals", + "(Boolean) Is true": "(Boolean) Đúng", + "(Boolean) Is false": "(Boolean) là sai", + "(List) Is empty": "(List) Is empty", + "(List) Is not empty": "(List) Is not empty", + "Exists": "tồn tại", + "Does not exist": "Không tồn tại", + "Incomplete condition": "Incomplete condition", + "First value": "Giá trị đầu tiên", + "Second value": "Giá trị thứ hai", + "Case sensitive": "Phân biệt chữ hoa chữ thường", + "Execute If": "Execute If", + "The package name is required": "Tên gói là bắt buộc", + "Success": "Thành công", + "Package added successfully": "Đã thêm gói thành công", + "Could not fetch package version": "Không thể tìm nạp phiên bản gói", + "Add NPM Package": "Thêm gói NPM", + "Type the name of the npm package you want to add.": "Nhập tên của gói npm bạn muốn thêm.", + "Package Name": "Tên gói", + "The latest version will be fetched and added": "Phiên bản mới nhất sẽ được tìm nạp và thêm vào", + "Add": "Thêm vào", + "Code": "Mã", + "Dependencies": "phụ thuộc", + "Use code": "Use code", + "Add package": "Thêm gói", + "Inputs": "Đầu vào", + "Edit Step Name": "Chỉnh sửa tên bước", + "Edit Branch Name": "Edit Branch Name", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "Chọn các mục để lặp lại từ bước trước bằng cách nhấp vào đầu vào **các mục**, đây phải là **danh sách** các mục.\n\nVòng lặp sẽ lặp qua từng mục trong danh sách và thực hiện bước tiếp theo cho mỗi mục.", + "Items": "Các mục", + "Select an array of items": "Chọn một loạt các mục", + "Reconnect": "Reconnect", + "Select a connection": "Chọn kết nối", + "Create Connection": "Tạo kết nối", + "Rename": "Đổi tên", + "Move": "Di chuyển", + "Add Branch": "Add Branch", + "Execute": "Execute", + "Only the first (left) matching branch": "Only the first (left) matching branch", + "All matching paths from left to right": "All matching paths from left to right", + "Branches": "Branches", + "{field} is required": "{field} is required", + "Tool Sample Data": "Tool Sample Data", + "Fill in the following fields to use them as sample data for the trigger.": "Fill in the following fields to use them as sample data for the trigger.", + "No input fields defined in the schema": "No input fields defined in the schema", + "Save": "Lưu", + "Test Environment": "Test Environment", + "Assigned to": "Assigned to", + "(Me)": "(Me)", + "Please select status to resolve the todo": "Please select status to resolve the todo", + "Resolve": "Resolve", + "Change status to resolved": "Change status to resolved", + "Send Sample Data to Webhook": "Send Sample Data to Webhook", + "Method": "Method", + "Query Params": "Query Params", + "Headers": "Headers", + "Body": "Body", + "Type": "Type", + "JSON": "JSON", + "Text": "Text", + "Form Data": "Form Data", + "Generate Sample Data": "Tạo dữ liệu mẫu", + "Test Step": "Kiểm tra Bước ", + "Retest": "Kiểm tra lại", + "Testing Failed": "Kiểm tra thất bại", + "Tested Successfully": "Đã thử nghiệm thành công", + "Logs": "Logs", + "There is no sample data available found for this trigger.": "Không tìm thấy dữ liệu mẫu nào cho trình kích hoạt này.", + "Internal error, please try again later.": "Internal error, please try again later.", + "Failed to run test step and no error message was returned": "Không thể chạy bước kiểm tra và không có thông báo lỗi nào được trả về", + "Please fix inputs first": "Vui lòng sửa đầu vào trước", + "No sample data available": "No sample data available", + "Old results were removed, retest for new sample data": "Old results were removed, retest for new sample data", + "Result #": "Kết quả #", + "The sample data can be used in the next steps.": "Dữ liệu mẫu có thể được sử dụng trong các bước tiếp theo.", + "Testing Trigger": "Trình kích hoạt thử nghiệm", + "Action Required": "Action Required", + "testPieceWebhookTriggerNote": "Vui lòng truy cập { PieceName} và kích hoạt {triggerName}.", + "Send Data to the webhook URL to generate sample data to use in the next steps": "Send Data to the webhook URL to generate sample data to use in the next steps", + "Test Trigger": "Trình kích hoạt thử nghiệm", + "Use Mock Data": "Use Mock Data", + "Load Sample Data": "Tải dữ liệu mẫu", + "Test Tool": "Test Tool", + "home": "trang chủ", + "Home": "Trang chủ", + "Alerts": "Cảnh báo", + "Releases": "Releases", + "Flows": "Luồng", + "Products": "Products", + "MCP": "MCP", + "Tables": "Tables", + "Todos": "Todos", + "Push to Git": "Đẩy tới Git", + "Move To": "Chuyển đi", + "Duplicating": "Đang nhân bản", + "Import": "Nhập", + "Exporting": "Đang xuất", + "Export": "Xuất", + "Share": "Chia sẻ", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "Bạn có chắc chắn muốn xóa luồng này không? Thao tác này sẽ xóa vĩnh viễn luồng, tất cả dữ liệu của luồng và mọi hoạt động chạy nền.", + "You are on a development branch, this will also delete the flow from the remote repository.": "You are on a development branch, this will also delete the flow from the remote repository.", + "flow": "luồng", + "Community Support": "Community Support", + "Overview": "Overview", + "Projects": "Dự án", + "Users": "Người dùng", + "Setup": "Setup", + "Branding": "Xây dựng thương hiệu", + "Global Connections": "Global Connections", + "Pieces": "Số mảnh", + "Templates": "Các Mẫu", + "License Key": "License Key", + "Security": "Security", + "Audit Logs": "Nhật ký kiểm toán", + "Single Sign On": "Đăng nhập một lần", + "Signing Keys": "Signing Keys", + "Project Roles": "Project Roles", + "API Keys": "API Keys", + "Infrastructure": "Infrastructure", + "Workers": "Workers", + "Health": "Health", + "Billing": "Billing", + "Settings": "Cài đặt", + "Contact Sales": "Contact Sales", + "General": "Tổng thể", + "Appearance": "Biểu mẫu", + "Team": "Nhóm", + "Environments": "Environments", + "Project Settings": "Project Settings", + "Exit Platform Admin": "Exit Platform Admin", + "Enter Platform Admin": "Enter Platform Admin", + "Logout": "Logout", + "Misc": "Misc", + "Connections": "Kết nối", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "Today": "Today", + "Tasks": "Nhiệm vụ", + "AI Credits": "AI Credits", + "Usage resets in": "Usage resets in", + "Manage": "Manage", + "Unlimited": "Unlimited", + "Enabled": "Enabled", + "Disabled": "Disabled", + "Could not claim the authorization code, make sure you have correct settings and try again.": "Không thể xác nhận mã ủy quyền. Hãy đảm bảo bạn có cài đặt chính xác và thử lại.", + "Connection failed with error {msg}": "Kết nối không thành công với lỗi {msg}", + "You don't have the permission to create a connection.": "You don't have the permission to create a connection.", + "Reconnect {displayName} Connection": "Kết nối lại {displayName}", + "Connect to {displayName}": "Connect to {displayName}", + "Connection Name": "Tên kết nối", + "Connection name": "Tên kết nối", + "New Connection": "Kết nối mới", + "Redirect URL": "Redirect URL", + "Client ID": "ID khách hàng", + "Client Secret": "Client Secret", + "Connect": "Kết nối", + "Disconnect": "Ngắt kết nối", + "I would like to use my own App Credentials": "Tôi muốn sử dụng thông tin đăng nhập ứng dụng của riêng tôi", + "I would like to use predefined App Credentials": "Tôi muốn sử dụng thông tin đăng nhập ứng dụng được xác định trước", + "Permission needed": "Permission needed", + "Connections replaced successfully": "Connections replaced successfully", + "Error": "Lỗi", + "Failed to replace connections": "Failed to replace connections", + "Failed to get affected flows": "Failed to get affected flows", + "Please select a piece": "Please select a piece", + "Please select a connection to replace": "Please select a connection to replace", + "Please select a connection to replace with": "Please select a connection to replace with", + "Replace Connections": "Replace Connections", + "Confirm Replacement": "Confirm Replacement", + "Replace one connection with another.": "Replace one connection with another.", + "This action requires ": "This action requires ", + "reconnecting": "reconnecting", + " any associated MCP pieces.": " any associated MCP pieces.", + "Piece": "Mảnh", + "Select a piece": "Select a piece", + "Connection to Replace": "Connection to Replace", + "Choose connection to replace": "Choose connection to replace", + "Replaced With": "Replaced With", + "Choose connection to replace with": "Choose connection to replace with", + "All flows will be changed to use the replaced with connection": "All flows will be changed to use the replaced with connection", + "Next": "Next", + "No flows will be affected by this change": "No flows will be affected by this change", + "Back": "Back", + "Unnamed tool": "Unnamed tool", + "This flow is enabled": "This flow is enabled", + "Enable this flow to make it available": "Enable this flow to make it available", + "Piece is updated successfully": "Piece is updated successfully", + "Piece is added successfully": "Piece is added successfully", + "Failed to update piece": "Failed to update piece", + "Failed to add piece": "Failed to add piece", + "Please select a connection": "Please select a connection", + "Your MCP server already has this piece": "Your MCP server already has this piece", + "+ New Connection": "+ New Connection", + "Edit Piece": "Edit Piece", + "Add Piece": "Add Piece", + "Connection": "Kết nối", + "MCP piece": "MCP piece", + "Failed to update piece status": "Failed to update piece status", + "Connection required": "Connection required", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.", + "piece": "piece", + "Connect your AI assistant to external services": "Connect your AI assistant to external services", + "Collapse": "Collapse", + "Show All": "Show All", + "Server URL": "Server URL", + "Hide the token for security": "Hide the token for security", + "Show the token": "Show the token", + "Generate a new token for security. This will invalidate the current URL.": "Generate a new token for security. This will invalidate the current URL.", + "URL copied to clipboard": "URL copied to clipboard", + "Copy URL to clipboard": "Copy URL to clipboard", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.", + "Max tables reached": "Max tables reached", + "You can't create more than {maxTables} tables": "You can't create more than {maxTables} tables", + "Name": "Tên", + "Created": "Đã tạo", + "Delete Tables": "Delete Tables", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "Are you sure you want to delete the selected tables? This action cannot be undone.", + "table": "table", + "Create and manage your tables to store your automation data": "Create and manage your tables to store your automation data", + "New Table": "New Table", + "No tables have been created yet": "No tables have been created yet", + "Create a table to get started and start managing your automation data": "Create a table to get started and start managing your automation data", + "Error deleting connections": "Error deleting connections", + "Status": "Trạng thái", + "Display Name": "Tên hiển thị", + "Owner": "Owner", + "App": "Ứng dụng", + "This connection is global and can be managed in the platform admin": "This connection is global and can be managed in the platform admin", + "External ID": "External ID", + "Connected At": "Connected At", + "Confirm Deletion": "Confirm Deletion", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "Are you sure you want to delete the selected connections? This action cannot be undone.", + "Deleting connections may cause your Flows or MCP tools to break.": "Deleting connections may cause your Flows or MCP tools to break.", + "Manage project connections to external systems.": "Manage project connections to external systems.", + "No connections found": "No connections found", + "Come back later when you create a automation to manage your connections": "Come back later when you create a automation to manage your connections", + "Steps": "Bước", + "Folder": "Thư mục", + "Flow name": "Tên luồng", + "No flows found": "No flows found", + "Create a workflow to start automating": "Create a workflow to start automating", + "Create and manage your flows, run history and run issues": "Create and manage your flows, run history and run issues", + "Issues": "Vấn đề", + "Untitled": "Chưa Đặt Tên", + "Create flow": "Create flow", + "From scratch": "Từ scratch", + "Use a template": "Sử dụng một mẫu", + "From local file": "From local file", + "Flow Name": "Tên luồng", + "Count": "Tổng số", + "First Seen": "Nhìn thấy lần đầu", + "Last Seen": "Lần cuối", + "Issues in {flowDisplayName} is marked as resolved.": "Issues in {flowDisplayName} is marked as resolved.", + "Unlock Issues": "Unlock Issues", + "Track issues in your workflows and troubleshoot them.": "Track issues in your workflows and troubleshoot them.", + "No issues found": "No issues found", + "All your workflows are running smoothly.": "All your workflows are running smoothly.", + "Mark as Resolved": "Đánh dấu là đã giải quyết", + "All Flows Are Turned Off": "All Flows Are Turned Off", + "Task Usage Exceeded": "Task Usage Exceeded", + "of the Allowed Limit.": "of the Allowed Limit.", + "When a project tasks limit is reached,": "When a project tasks limit is reached,", + "all flows will be turned off and you will not be able to run any flows.": "all flows will be turned off and you will not be able to run any flows.", + "Please visit": "Please visit", + "Your Plan": "Your Plan", + "and increase your task limit, which requires your payment details.": "and increase your task limit, which requires your payment details.", + "Please contact your admin to increase the project task limit.": "Please contact your admin to increase the project task limit.", + "and increase the project task limit.": "and increase the project task limit.", + "Dismiss": "Dismiss", + "Token rotated successfully": "Token rotated successfully", + "Failed to rotate token": "Failed to rotate token", + "Piece removed successfully": "Piece removed successfully", + "Failed to remove piece": "Failed to remove piece", + "Flow created successfully": "Flow created successfully", + "Failed to create flow": "Failed to create flow", + "Add Flow": "Add Flow", + "Let your AI assistant trigger automations": "Let your AI assistant trigger automations", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "Connect to your hosted MCP Server using any MCP client to communicate with tools", + "MCP Server": "MCP Server", + "My Tools": "My Tools", + "Create Flow": "Create Flow", + "Note": "Note", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.", + "This URL grants access to your tools and data. Only share with trusted applications.": "This URL grants access to your tools and data. Only share with trusted applications.", + "Server Configuration": "Server Configuration", + "Hide sensitive data": "Hide sensitive data", + "Show sensitive data": "Show sensitive data", + "Create a new URL. The current one will stop working.": "Create a new URL. The current one will stop working.", + "Copy configuration": "Copy configuration", + "Configuration copied to clipboard": "Configuration copied to clipboard", + "Claude": "Claude", + "Cursor": "Cursor", + "Windsurf": "Windsurf", + "Server/Other": "Server/Other", + "Note: MCPs only work with": "Note: MCPs only work with", + "Claude Desktop": "Claude Desktop", + ", not the web version.": ", not the web version.", + "Prerequisites:": "Prerequisites:", + "Install": "Cài đặt", + "Node.js": "Node.js", + "and": "và", + "Open Settings:": "Open Settings:", + "Click the menu and select": "Click the menu and select", + "Developer": "Developer", + "Configure MCP:": "Configure MCP:", + "Click": "Click", + "Edit Config": "Edit Config", + "and paste the configuration below": "and paste the configuration below", + "Save and Restart:": "Save and Restart:", + "Save the config and restart Claude Desktop": "Save the config and restart Claude Desktop", + "Navigate to": "Navigate to", + "Cursor Settings": "Cursor Settings", + "Add Server:": "Add Server:", + "Add new global MCP server": "Add new global MCP server", + "Configure:": "Configure:", + "Paste the configuration below and save": "Paste the configuration below and save", + "Use either method:": "Use either method:", + "Go to": "Go to", + "Advanced Settings": "Advanced Settings", + "Open Command Palette and select": "Open Command Palette and select", + "Windsurf Settings Page": "Windsurf Settings Page", + "Navigate to Cascade:": "Navigate to Cascade:", + "Select": "Select", + "Cascade": "Cascade", + "in the sidebar": "in the sidebar", + "Add Server": "Add Server", + "Add custom server +": "Add custom server +", + "Copy URL": "Copy URL", + "Client Setup Instructions": "Client Setup Instructions", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "After changing connections or flows, reconnect your MCP server for changes to take effect.", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.", + "icon": "icon", + "Unlock Analytics": "Unlock Analytics", + "Get insights into your platform usage and performance with our analytics dashboard": "Get insights into your platform usage and performance with our analytics dashboard", + "Active Flows": "Active Flows", + "The number of enabled flows in the platform": "The number of enabled flows in the platform", + "Active Projects": "Active Projects", + "The number of projects with at least one enabled flow": "The number of projects with at least one enabled flow", + "Active Users": "Active Users", + "The number of users logged in the last 30 days": "The number of users logged in the last 30 days", + "Out of {totalusers} total users": "Out of {totalusers} total users", + "Pieces Used": "Pieces Used", + "The number of unique pieces used across all flows": "The number of unique pieces used across all flows", + "Flows with AI": "Flows with AI", + "The number of enabled flows that use AI pieces": "The number of enabled flows that use AI pieces", + "Metrics": "Metrics", + "Executed Tasks": "Executed Tasks", + "Showing total executed tasks for specified time range": "Showing total executed tasks for specified time range", + "Tasks Usage Limit": "Tasks Usage Limit", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.", + "Number of monthly tasks": "Number of monthly tasks", + "Save changes": "Save changes", + "Limits updated successfully": "Limits updated successfully", + "Failed to update limits": "Failed to update limits", + "Failed to load billing information": "Failed to load billing information", + "Billing Amount": "Billing Amount", + "Manage Payment Details": "Manage Payment Details", + "Add Payment Details": "Add Payment Details", + "Current Task Usage": "Current Task Usage", + "Count of executed steps": "Count of executed steps", + "Billing Limit": "Billing Limit", + "Edit": "Edit", + "Add Limit": "Add Limit", + "Current Credit Usage": "Current Credit Usage", + "WebSocket Connection": "WebSocket Connection", + "Connected": "Connected", + "Disconnected": "Disconnected", + "No issues detected": "No issues detected", + "Check the status of your platform and its components": "Check the status of your platform and its components", + "System Health Status": "System Health Status", + "All systems are running smoothly": "All systems are running smoothly", + "Check the health of your worker machines": "Check the health of your worker machines", + "Workers Machine": "Workers Machine", + "This is demo data. In a real environment, this would show your actual worker machines.": "This is demo data. In a real environment, this would show your actual worker machines.", + "No workers found": "No workers found", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "You don't have any worker machines yet. Spin up new machines to execute your automations", + "IP Address": "IP Address", + "CPU Usage": "CPU Usage", + "Disk Usage": "Disk Usage", + "RAM Usage": "RAM Usage", + "Last Contact": "Last Contact", + "Configs": "Configs", + "Environment Variables": "Environment Variables", + "Websocket Connection Error": "Websocket Connection Error", + "Retry Connection": "Retry Connection", + "Update Available": "Update Available", + "Update Now": "Update Now", + "Your Universal AI needs a quick setup": "Your Universal AI needs a quick setup", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.", + "Configure": "Configure", + "Platform Alerts": "Platform Alerts", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.", + "View Alerts": "View Alerts", + "Used Tasks": "Used Tasks", + "Used AI Credits": "Used AI Credits", + "Cannot delete active project, switch to another project first": "Cannot delete active project, switch to another project first", + "Delete Projects": "Delete Projects", + "Are you sure you want to delete the selected projects?": "Are you sure you want to delete the selected projects?", + "New Project": "New Project", + "Validation error": "Validation error", + "Project has enabled flows. Please disable them first.": "Project has enabled flows. Please disable them first.", + "This project is active. Please switch to another project first.": "This project is active. Please switch to another project first.", + "Unlock Projects": "Unlock Projects", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "Orchestrate your automation teams across projects with their own flows, connections and usage quotas", + "Manage your automation projects": "Manage your automation projects", + "No projects found": "No projects found", + "Start by creating projects to manage your automation teams": "Start by creating projects to manage your automation teams", + "Edit project": "Edit project", + "Name is required": "Tên là bắt buộc", + "Create New Project": "Create New Project", + "Project Name": "Tên dự án", + "Id": "Id", + "Enable API Keys": "Enable API Keys", + "Create and manage API keys to access Activepieces APIs.": "Create and manage API keys to access Activepieces APIs.", + "New Api Key": "New Api Key", + "No API keys found": "No API keys found", + "Start by creating an API key to communicate with Activepieces APIs": "Start by creating an API key to communicate with Activepieces APIs", + "Delete API Key": "Delete API Key", + "Are you sure you want to delete this API key?": "Are you sure you want to delete this API key?", + "API Key": "API Key", + "API Key Created": "API Key Created", + "Create New API Key": "Create New API Key", + "Please save this secret key somewhere safe and accessible. For security reasons,": "Please save this secret key somewhere safe and accessible. For security reasons,", + "you won't be able to view it again after closing this dialog.": "you won't be able to view it again after closing this dialog.", + "API Key Name": "API Key Name", + "Action": "Action", + "Performed By": "Performed By", + "Project": "Project", + "Unlock Audit Logs": "Unlock Audit Logs", + "Comply with internal and external security policies by tracking activities done within your account": "Comply with internal and external security policies by tracking activities done within your account", + "Track activities done within your platform": "Track activities done within your platform", + "No audit logs found": "No audit logs found", + "Come back later when you have some activity to audit": "Come back later when you have some activity to audit", + "Resource": "Resource", + "Details": "Details", + "N/A": "N/A", + "Flow Run": "Flow Run", + "Flow": "Luồng", + "User": "User", + "Signing Key": "Signing Key", + "Project Role Management": "Project Role Management", + "Define custom roles and permissions to control what your team members can access and modify": "Define custom roles and permissions to control what your team members can access and modify", + "Define custom roles and permissions that can be assigned to your team members": "Define custom roles and permissions that can be assigned to your team members", + "New Role": "New Role", + "Contact sales to unlock custom roles": "Contact sales to unlock custom roles", + "Create New Role": "Create New Role", + "View ": "View ", + "Edit ": "Edit ", + "Role Name": "Role Name", + "Permissions": "Permissions", + "None": "None", + "Read": "Read", + "Write": "Write", + "Create": "Tạo", + "Email": "Email", + "First Name": "Họ", + "Last Name": "Họ", + "Roles": "Roles", + "View the users assigned to this role": "View the users assigned to this role", + "Role": "Role", + "No users found": "No users found", + "Starting by assigning users to this role": "Starting by assigning users to this role", + "Project Role entry deleted successfully": "Project Role entry deleted successfully", + "Updated": "Đã cập nhật", + "No project roles found": "No project roles found", + "Create custom project roles to manage permissions for platform users": "Create custom project roles to manage permissions for platform users", + "Show Users": "Show Users", + "View Role": "View Role", + "Edit Role": "Edit Role", + "Delete Role": "Delete Role", + "Project Role": "Vai trò dự án", + "Unlock Embedding Through JS SDK": "Unlock Embedding Through JS SDK", + "Enable signing keys to access embedding functionalities.": "Enable signing keys to access embedding functionalities.", + "New Signing Key": "New Signing Key", + "No signing keys found": "No signing keys found", + "Create a signing key to authenticate users with embedding": "Create a signing key to authenticate users with embedding", + "Delete Signing Key": "Delete Signing Key", + "Are you sure you want to delete this signing key?": "Are you sure you want to delete this signing key?", + "Signing Key Created": "Signing Key Created", + "Create New Signing Key": "Create New Signing Key", + "Signing Key Name": "Signing Key Name", + "Allowed domains updated": "Allowed domains updated", + "Update": "Update", + "Enable": "Enable", + "Configure Allowed Domains": "Configure Allowed Domains", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.", + "Add Domain": "Add Domain", + "Allow logins through {providerName}'s single sign-on functionality.": "Allow logins through {providerName}'s single sign-on functionality.", + "Email authentication updated": "Email authentication updated", + "Enable Single Sign On": "Enable Single Sign On", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "Let your users sign in with your current SSO provider or give them self serve sign up access", + "Allowed Domains": "Allowed Domains", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "Allow users to authenticate with specific domains. Leave empty to allow all domains.", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "Allowed Email Login", + "Allow logins through email and password.": "Allow logins through email and password.", + "Single sign on settings updated": "Single sign on settings updated", + "Disable": "Disable", + "Configure {provider} SSO": "Configure {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} Client ID", + "{provider} Client Secret": "{provider} Client Secret", + "Single sign-on settings updated": "Single sign-on settings updated", + "Configure SAML 2.0 SSO": "Configure SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDP Metadata", + "IDP Certificate": "IDP Certificate", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "Base URL", + "Resource Name": "Resource Name", + "Deployment Name": "Deployment Name", + "Saving": "Saving", + "Activepieces Copilot": "Activepieces Copilot", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot is configured and ready to help your users build flows faster using AI.", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "Configure Activepieces Copilot to help your users build flows faster using AI.", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "Set provider credentials that will be used by universal AI pieces, i.e Text AI.", + "AI Providers": "AI Providers", + "Copilot": "Copilot", + "Configure credentials for {providerName} AI provider.": "Configure credentials for {providerName} AI provider.", + "Update AI Provider": "Update AI Provider", + "Enable AI Provider": "Enable AI Provider", + "sk_************************": "sk_************************", + "Please enter a valid domain": "Please enter a valid domain", + "Your changes have been saved.": "Các thay đổi đã được lưu.", + "The domain is already added.": "The domain is already added.", + "Add Custom Domain": "Add Custom Domain", + "Enter a domain name without a protocol (e.g. example.com)": "Enter a domain name without a protocol (e.g. example.com)", + "Logo URL": "Logo URL", + "Icon URL": "Icon URL", + "Favicon URL": "Favicon URL", + "Default Language": "Default Language", + "Select Language": "Select Language", + "No Languages": "No Languages", + "Primary Color": "Primary Color", + "Custom Domains": "Custom Domains", + "No domains added yet.": "No domains added yet.", + "Verified": "Verified", + "Pending, please contact the support for dns verification.": "Pending, please contact the support for dns verification.", + "Are you sure you want to delete {domain}?": "Are you sure you want to delete {domain}?", + "Brand Activepieces": "Brand Activepieces", + "Give your users an experience that looks like you by customizing the color, logo and more": "Give your users an experience that looks like you by customizing the color, logo and more", + "Configure the appearance and SMTP settings for your platform.": "Configure the appearance and SMTP settings for your platform.", + "Invalid host": "Invalid host", + "Invalid username": "Invalid username", + "Invalid password": "Invalid password", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "Invalid sender name", + "SMTP is configured": "SMTP is configured", + "Mail Server": "Mail Server", + "Set up your SMTP settings to send emails from your domain.": "Set up your SMTP settings to send emails from your domain.", + "Disable Mail Server": "Disable Mail Server", + "Are you sure you want to disable your mail server?": "Are you sure you want to disable your mail server?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "This will stop you from sending emails for issues, quota limits, invitations and forgot password.", + "mail server": "mail server", + "Host": "Host", + "Port": "Port", + "Username": "Username", + "Password": "Mật khẩu", + "Sender Email": "Sender Email", + "Sender Name": "Sender Name", + "Enable Global Connections": "Enable Global Connections", + "Manage platform-wide connections to external systems.": "Manage platform-wide connections to external systems.", + "No global connections found": "No global connections found", + "Create a global connection that can be shared to multiple projects": "Create a global connection that can be shared to multiple projects", + "License key is invalid": "License key is invalid", + "Invalid license key": "Invalid license key", + "License activated!": "License activated!", + "Activate License Key": "Activate License Key", + "Let the magic begin!": "Let the magic begin!", + "Activate": "Activate", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ", + "This feature is not available in your current edition. ": "This feature is not available in your current edition. ", + "Learn how to upgrade": "Learn how to upgrade", + "Activate your platform and unlock enterprise features": "Activate your platform and unlock enterprise features", + "Activate License": "Activate License", + "Expiration": "Expiration", + "Valid until": "Valid until", + "Expired": "Expired", + "Expires soon": "Expires soon", + "Features": "Features", + "Applying Tags...": "Applying Tags...", + "Tags applied.": "Tags applied.", + "Tag created": "Tag created", + "Tag": "Tag", + "Tags": "Tags", + "Control Pieces": "Điều khiển Mảnh", + "Show the pieces that matter most to your users and hide the ones you don't like.": "Show the pieces that matter most to your users and hide the ones you don't like.", + "Manage the pieces that are available to your users": "Manage the pieces that are available to your users", + "Start by installing pieces that you want to use in your automations": "Start by installing pieces that you want to use in your automations", + "Piece Name": "Tên mảnh", + "Hide this piece from all projects": "Hide this piece from all projects", + "Show this piece for all projects": "Show this piece for all projects", + "Unpin this piece": "Unpin this piece", + "Pin this piece": "Pin this piece", + "Pieces synced": "Pieces synced", + "Pieces have been synced from the activepieces cloud.": "Pieces have been synced from the activepieces cloud.", + "OAuth2 Credentials Deleted": "OAuth2 Credentials Deleted", + "OAuth2 Credentials Updated": "OAuth2 Credentials Updated", + "Configure OAuth2 APP": "Configure OAuth2 APP", + "Delete OAuth2 APP": "Delete OAuth2 APP", + "Templates deleted successfully": "Templates deleted successfully", + "Delete Templates": "Delete Templates", + "Are you sure you want to delete the selected templates?": "Are you sure you want to delete the selected templates?", + "New Template": "New Template", + "Unlock Templates": "Unlock Templates", + "Convert the most common automations into reusable templates 1 click away from your users": "Convert the most common automations into reusable templates 1 click away from your users", + "Convert the most common automations into reusable templates": "Convert the most common automations into reusable templates", + "No templates found": "No templates found", + "Create a template for your user to inspire them": "Create a template for your user to inspire them", + "Edit template": "Edit template", + "Template is required": "Template is required", + "Update New Template": "Update New Template", + "Create New Template": "Create New Template", + "Template Name": "Template Name", + "Description": "Mô tả", + "Template Description": "Template Description", + "Blog URL": "Blog URL", + "Template Blog URL": "Template Blog URL", + "Template": "Template", + "Invalid JSON": "Invalid JSON", + "User deleted successfully": "User deleted successfully", + "User activated successfully": "User activated successfully", + "User deactivated successfully": "User deactivated successfully", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "Manage your users and their access to your projects", + "Start inviting users to your project": "Start inviting users to your project", + "External Id": "External Id", + "Admin": "Quản trị viên", + "Member": "Member", + "Activated": "Activated", + "Deactivated": "Deactivated", + "Edit user": "Edit user", + "Admin cannot be deactivated": "Admin cannot be deactivated", + "Deactivate user": "Deactivate user", + "Activate user": "Activate user", + "Delete User": "Delete User", + "Are you sure you want to delete this user?": "Are you sure you want to delete this user?", + "Delete user": "Delete user", + "Update User Role": "Update User Role", + "Meeting Summary Flow": "Meeting Summary Flow", + "Added new features and fixed bugs": "Added new features and fixed bugs", + "Flows Changes": "Flows Changes", + "Connections Changes": "Connections Changes", + "New connections are placeholders and need to be reconnected again": "New connections are placeholders and need to be reconnected again", + "renamed to": "renamed to", + "Tables Changes": "Tables Changes", + "No changes to apply": "No changes to apply", + "Apply Changes": "Apply Changes", + "Create Git Release": "Create Git Release", + "Create Project Release": "Create Project Release", + "Create Rollback to": "Create Rollback to", + "Source": "Source", + "Rollback": "Rollback", + "Imported At": "Imported At", + "Imported By": "Imported By", + "Track and manage your project version history and deployments. ": "Track and manage your project version history and deployments. ", + "Environments & Releases": "Environments & Releases", + "Project Releases": "Project Releases", + "Create Release": "Create Release", + "From Git": "From Git", + "From Project": "From Project", + "No project releases found": "No project releases found", + "Create a project release to get started": "Create a project release to get started", + "Please select project": "Please select project", + "No Changes Found": "No Changes Found", + "There are no differences to apply": "There are no differences to apply", + "Please select a project": "Please select a project", + "Search projects...": "Search projects...", + "Review Changes": "Xem trước thay đổi", + "Git": "Git", + "Summary": "Summary", + "Imported by": "Imported by", + "from": "from", + "No description provided": "No description provided", + "Invitation only sign up": "Invitation only sign up", + "Please ask your administrator to add you to the organization.": "Please ask your administrator to add you to the organization.", + "Something went wrong, please try again.": "Something went wrong, please try again.", + "Please try again.": "Please try again.", + "Please enter a valid email address": "Vui lòng nhập một địa chỉ email hợp lệ", + "The email is already added.": "Email đã được sử dụng.", + "Add email": "Thêm email", + "Only project admins can do this": "Only project admins can do this", + "Add Alert Email": "Thêm email thông báo", + "Enter the email address to receive alerts.": "Nhập địa chỉ email để nhận thông báo.", + "Add Email": "Thêm Email", + "Emails": "Email", + "Add email addresses to receive alerts.": "Thêm địa chỉ email để nhận thông báo.", + "No emails added yet.": "Chưa có email nào được thêm vào.", + "Choose what you want to be notified about.": "Chọn nội dung bạn muốn được thông báo.", + "Project and alert permissions are required to change this setting.": "Project and alert permissions are required to change this setting.", + "Every Failed Run": "Tất cả lần chạy thất bại", + "Get an email alert when a flow fails.": "Nhận thông báo qua email khi một luồng không thành công.", + "Get an email alert when a new issue created.": "Nhận thông báo qua email khi có vấn đề mới được tạo.", + "Never": "Không bao giờ", + "Turn off email notifications.": "Tắt thông báo qua email.", + "Customize the appearance of the app. Automatically switch between day and night themes.": "Tùy chỉnh giao diện của ứng dụng. Tự động chuyển đổi giữa các chủ đề ngày và đêm.", + "Select the theme for the dashboard.": "Chọn chủ đề cho bảng thông tin.", + "Light": "Sáng", + "Dark": "Tối", + "Select the language that will be used in the dashboard.": "Chọn ngôn ngữ sẽ được sử dụng trong trang tổng quan.", + "Select language": "Chọn ngôn ngữ", + "Search language...": "Tìm ngôn ngữ...", + "No language found.": "Không tìm thấy ngôn ngữ.", + "Help us translate Activepieces to your language.": "Hãy giúp chúng tôi dịch Activepieces sang ngôn ngữ của bạn.", + "Learn more": "Tìm hiểu thêm", + "Git Connection Removed": "Git Connection Removed", + "Your Git repository has been successfully disconnected": "Your Git repository has been successfully disconnected", + "Enable Environments": "Enable Environments", + "Deploy flows across development, staging and production environments with version control and team collaboration": "Deploy flows across development, staging and production environments with version control and team collaboration", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "Connect to Git to enable version control, backup your flows, and manage multiple environments. ", + "Repository URL": "Repository URL", + "Not connected": "Not connected", + "Project Folder": "Project Folder", + "Releases Enabled": "Releases Enabled", + "You have successfully enabled releases": "You have successfully enabled releases", + "Enable releases to easily create and manage project releases.": "Enable releases to easily create and manage project releases.", + "The external ID is already taken.": "The external ID is already taken.", + "Manage general settings for your project.": "Quản lý cài đặt chung cho dự án của bạn.", + "Used to identify the project based on your SaaS ID": "Used to identify the project based on your SaaS ID", + "org-3412321": "org-3412321", + "Delete {name}": "Xoá {name}", + "This will permanently delete this piece, all steps using it will fail.": "Thao tác này sẽ xóa vĩnh viễn mảnh này, tất cả các bước sử dụng nó sẽ thất bại.", + "Add a piece to your project that you want to use in your automations": "Add a piece to your project that you want to use in your automations", + "Pieces list updated": "Pieces list updated", + "Manage Pieces": "Manage Pieces", + "Choose which pieces you want to be available for your current project users": "Choose which pieces you want to be available for your current project users", + "Unlock Team Permissions": "Unlock Team Permissions", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial", + "Project Members": "Thành viên dự án", + "Invite your team members to collaborate.": "Mời các thành viên trong nhóm của bạn cộng tác.", + "No members are added to this project.": "Không có thành viên nào được thêm vào dự án này.", + "Pending Invitations": "Lời mời đang chờ xử lý", + "No pending invitation.": "Không có lời mời đang chờ xử lý.", + "templateId is missing": "templateId bị thiếu", + "Me Only": "Me Only", + "Unresolved": "Unresolved", + "Resolved": "Resolved", + "Title": "Title", + "Date Created": "Date Created", + "Manage todos for your project that are created by automations": "Manage todos for your project that are created by automations", + "No todos found": "No todos found", + "You do not have any pending todos. Great job!": "You do not have any pending todos. Great job!", + "Write a comment...": "Write a comment...", + "Beta": "Beta", + "This feature is still under testing and might be changed often": "This feature is still under testing and might be changed often", + "Failed to copy to clipboard": "Không thể sao chép vào bộ nhớ tạm", + "{number} items selected": "{number} items selected", + "Select All": "Select All", + "No results found.": "Không tìm thấy kết quả nào.", + "useMultiSelect must be used within MultiSelectProvider": "useMultiSelect phải được sử dụng trong MultiSelectProvider", + "Unset": "Unset", + "Refresh": "Refresh", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} khác", + "Removed {entityName}": "Đã xóa {entityName}", + "Download File": "Tải tập tin xuống", + "Copied to clipboard": "Sao chép vào clipboard", + "File is not available after execution.": "Tệp không có sẵn sau khi thực thi.", + "Available for Projects": "Available for Projects", + "Select projects": "Select projects", + "No items": "No items", + "Previous": "Previous", + "to": "to", + "Last Week": "Last Week", + "Last Month": "Last Month", + "Last 3 Months": "Last 3 Months", + "Last 6 Months": "Last 6 Months", + "Next 7 days": "Next 7 days", + "Next 30 days": "Next 30 days", + "Next 90 days": "Next 90 days", + "Next 180 days": "Next 180 days", + "Select Time Range": "Select Time Range", + "Clear": "Clear", + "Download": "Download", + "Go to Dashboard": "Go to Dashboard", + "Select a file": "Select a file", + "Press space to separate values": "Press space to separate values", + "AM": "AM", + "PM": "PM", + "Already have an account?": "Đã có một tài khoản?", + "Sign in": "Đăng nhập", + "Don't have an account?": "Bạn không có tài khoản?", + "Sign up": "Đăng ký", + "Welcome Back!": "Chào mừng trở lại!", + "Enter your email below to sign in to your account": "Nhập email của bạn dưới đây để đăng nhập vào tài khoản của bạn", + "Let's Get Started!": "Bắt đầu nào!", + "Create your account and start flowing!": "Tạo tài khoản của bạn và bắt đầu luồng!", + "Your password was changed successfully": "Mật khẩu của bạn đã được thay đổi thành công.", + "Your password reset request has expired, please request a new one": "Yêu cầu đặt lại mật khẩu của bạn đã hết hạn, vui lòng gửi yêu mới", + "Reset Password": "Đặt lại Mật khẩu", + "Enter your new password": "Nhập mật khẩu mới", + "Password is required": "Mật khẩu là bắt buộc", + "Verification email resent, if previous one expired.": "Verification email resent, if previous one expired.", + "Password reset link resent, if previous one expired.": "Password reset link resent, if previous one expired.", + "We sent you a link to complete your registration to": "We sent you a link to complete your registration to", + "We sent you a link to reset your password to": "We sent you a link to reset your password to", + "Didn't receive an email or it expired?": "Didn't receive an email or it expired?", + "Resend": "Gửi lại", + "Please enter your email": "Vui lòng nhập email của bạn", + "Check Your Inbox": "Kiểm tra hộp thư của bạn", + "If the user exists we'll send you an email with a link to reset your password.": "Nếu người dùng tồn tại, chúng tôi sẽ gửi cho bạn một liên kết tới email để đặt lại mật khẩu.", + "Send Password Reset Link": "Gửi Liên kết tạo lại mật khẩu", + "Back to sign in": "Quay lại đăng nhập", + "Email is invalid": "Email không hợp lệ", + "Something went wrong, please try again later": "Đã xảy ra lỗi, hãy thử lại", + "Invalid email or password": "Email hoặc mật khẩu không hợp lệ", + "User has been deactivated": "User has been deactivated", + "Email domain is disallowed": "Email domain is disallowed", + "Email authentication has been disabled": "Email authentication has been disabled", + "Forgot your password?": "Quên mật khẩu?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "Sign up is restricted. You need an invitation to join. Please contact the administrator.", + "Email is already used": "Tên đã được sử dụng", + "Email authentication is disabled": "Email authentication is disabled", + "First name is required": "Tên là bắt buộc", + "Last name is required": "Họ là bắt buộc", + "Email is required": "Email là bắt buộc", + "Receive updates and newsletters from activepieces": "Nhận thông tin cập nhật và thông báo về các hoạt động", + "By creating an account, you agree to our": "Bằng cách tạo tài khoản, bạn đồng ý với chúng tôi", + "terms of service": "điều khoản sử dụng", + "privacy policy": "chính sách về quyền riêng tư", + "Sign up With": "Sign up With", + "Google": "Google", + "Sign in With": "Sign in With", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "Email has been verified. You will be redirected to sign in...", + "Verifying email...": "Đang xác minh email...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "invitation has expired, once you sign in again you will be able to resend the verification email.", + "Redirecting to sign in...": "Redirecting to sign in...", + "Password must contain at least one special character": "Mật khẩu phải chứa ít nhất một ký tự đặc biệt.", + "Password must contain at least one lowercase letter": "Mật khẩu phải chứa ít nhất một chữ cái viết thường", + "Password must contain at least one uppercase letter": "Mật khẩu phải chứa ít nhất một chữ cái viết hoa", + "Password must contain at least one number": "Mật khẩu phải chứa ít nhất một chữ số.", + "8-64 Characters": "8-64 ký tự", + "Special Character": "Ký tự đặc biệt", + "Lowercase": "Chữ thường", + "Uppercase": "Chữ in hoa", + "Number": "Số", + "Connection has been updated.": "Connection has been updated.", + "Edit Global Connection": "Edit Global Connection", + "Connection has been renamed.": "Connection has been renamed.", + "New Connection Name": "New Connection Name", + "Connection name already used": "Connection name already used", + "Please select at least one project": "Please select at least one project", + "Run Succeeded": "Chạy thành công", + "Run Failed": "Chạy không thành công", + "Flow Run is paused": "Luồng chạy bị tạm dừng", + "Run Failed due to quota exceeded": "Chạy không thành công do vượt quá hạn ngạch", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Run failed due to exceeding the memory limit of {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "Chạy đã vượt quá {timeout} giây, hãy cố gắng tối ưu hóa các bước của bạn.", + "Run failed for an unknown reason, contact support.": "Chạy không thành công vì lý do không xác định, hãy liên hệ với bộ phận hỗ trợ.", + "Unknown": "Không rõ", + "Exit Run": "Thoát chạy", + "Select shown": "Select shown", + "Select all": "Select all", + "Start Time": "Thời gian bắt đầu", + "Runs replayed successfully": "Runs replayed successfully", + "Retry": "Retry", + "all except": "all except", + "all": "all", + "Only failed runs can be retried from failed step": "Only failed runs can be retried from failed step", + "No flow runs found": "No flow runs found", + "Come back later when your automations start running": "Come back later when your automations start running", + "Step running": "Bước đang chạy", + "Step paused": "Bước bị tạm dừng", + "Step Stopped": "Bước đã dừng", + "Step Succeeded": "Bước thành công", + "Step Failed": "Bước thất bại", + "Please publish flow first": "Vui lòng xuất bản luồng trước", + "Flow is on": "Luồng được kích hoạt", + "Flow is off": "Luồng này đã bị tắt", + "Permission Needed": "Cần quyền truy cập", + "Draft Version": "Phiên bản nháp", + "Published Version": "Published Version", + "Locked Version": "Phiên bản bị khóa", + "flowsImported": "{flowsCount, plural, =0 {Không có luồng nào được nhập} =1 {Đã nhập luồng thành công} other {Luồng được nhập thành công}}", + "Template file is invalid": "Template file is invalid", + "No valid templates found. The following files failed to import: ": "No valid templates found. The following files failed to import: ", + "Please select a file first": "Please select a file first", + "Unsupported file type": "Unsupported file type", + "Import Flow": "nhập luồng", + "Warning": "Warning", + "Importing a flow will overwrite your current one.": "Importing a flow will overwrite your current one.", + "Select a folder": "Select a folder", + "Folders": "Thư mục", + "Please select a folder": "Hãy chọn thư mục", + "Moved flows successfully": "Moved flows successfully", + "Move Selected Flows": "Move Selected Flows", + "Select Folder": "Chọn thư mục", + "No Folders": "Không có thư mục", + "Flow has been renamed.": "Luồng đã được đổi tên.", + "New Flow Name": "Tên luồng mới", + "Use Template": "Sử dụng mẫu", + "Browse Templates": "Duyệt mẫu", + "Search templates": "Tìm kiếm mẫu", + "No templates found, try adjusting your search": "No templates found, try adjusting your search", + "Read more about this template in": "Đọc thêm về mẫu này trong", + "this blog!": "blog này!", + "Share Template": "Chia sẻ mẫu", + "Generate or update a template link for the current flow to easily share it with others.": "Tạo hoặc cập nhật liên kết mẫu cho luồng hiện tại để dễ dàng chia sẻ với người khác.", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "Mẫu sẽ không có bất kỳ thông tin xác thực nào trong các trường kết nối, giúp bảo mật thông tin nhạy cảm.", + "A short description of the template": "Mô tả ngắn gọn về mẫu", + "Flow Is In Use": "Flow Is In Use", + "Flow is being used by another user, please try again later.": "Flow is being used by another user, please try again later.", + "Flow has been published.": "Luồng đã được xuất bản.", + "Flows have been exported.": "Flows have been exported.", + "Run": "Chạy", + "Real time flow": "Luồng thời gian thực", + "Flow can't be published with empty trigger {name}": "Không thể xuất bản luồng khi trình kích hoạt trống {name}", + "Please contact support as your published flow has a problem": "Vui lòng liên hệ với bộ phận hỗ trợ vì luồng đã xuất bản của bạn có vấn đề", + "Actions": "hành động", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.", + "You are on a development branch, this will not delete the flows from the remote repository.": "You are on a development branch, this will not delete the flows from the remote repository.", + "Please enter folder name": "Hãy nhập tên thư mục", + "Added folder successfully": "Đã thêm thư mục thành công", + "The folder name already exists.": "Tên thư mục đã tồn tại.", + "New Folder": "Tạo thư mục", + "Folder Name": "Tên thư mục", + "Loading...": "Đang tải...", + "Delete {folderName}": "Delete {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "Nếu bạn xóa thư mục này, chúng tôi sẽ giữ nguyên các luồng của nó và chuyển chúng sang Chưa được phân loại.", + "All flows": "Tất cả các luồng", + "Please enter a folder name": "Vui lòng nhập tên thư mục", + "Renamed flow successfully": "Đã đổi tên luồng thành công", + "Folder name already used": "Folder name already used", + "New Folder Name": "Tên thư mục mới", + "Connected successfully": "Đã kết nối thành công", + "Connect Git": "Kết nối Git", + "Remote URL": "Remote URL", + "Folder name is the name of the folder where the project will be stored or fetched.": "Tên thư mục là tên của thư mục nơi dự án sẽ được lưu trữ hoặc tìm nạp.", + "SSH Private Key": "SSH Private Key", + "The SSH private key to use for authentication.": "SSH Private Key được sử dụng để xác thực.", + "Only published flows can be pushed to Git": "Only published flows can be pushed to Git", + "Pushed successfully": "Đã đẩy thành công", + "Invalid Operation": "Invalid Operation", + "Commit Message": "Tin nhắn cam kết", + "Enter a commit message to describe the changes you want to push.": "Nhập thông báo cam kết để mô tả những thay đổi bạn muốn thực hiện.", + "Push": "Đẩy", + "This field is required": "This field is required", + "Your submission was successfully received.": "Nội dung gửi của bạn đã được nhận thành công.", + "Flow not found": "Không tìm thấy luồng", + "The flow you are trying to submit to does not exist.": "Luồng bạn đang cố gửi tới không tồn tại.", + "The flow failed to execute.": "Luồng không thể thực thi được.", + "Submit": "Gửi", + "issues-notification": "vấn đề-thông báo", + "Please select a package type": "Vui lòng chọn loại gói", + "package.json not found in archive": "package.json not found in archive", + "Error processing archive file": "Error processing archive file", + "Please upload a .tgz file": "Please upload a .tgz file", + "Piece name is required for NPM Registry": "Piece name is required for NPM Registry", + "Piece version is required for NPM Registry": "Piece version is required for NPM Registry", + "Piece installed": "Mảnh được cài đặt", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "A piece with this name and version is already installed. Please update the version number in package.json and try again.", + "Install Piece": "Cài đặt mảnh", + "Install a piece": "Cài đặt một mảnh", + "Package Type": "Loại gói", + "NPM Registry": "Tập thông số NPM", + "Packed Archive (.tgz)": "Mảnh đóng gói (.tgz)", + "Piece Version": "phiên bản mảnh", + "Package Archive": "Mảnh đóng gói", + "Package archive": "Mảnh đóng gói", + "Powerful Node.js & TypeScript code with npm": "Powerful Node.js & TypeScript code with npm", + "Loop on Items": "Loop on Items", + "Split your flow into branches depending on condition(s)": "Split your flow into branches depending on condition(s)", + "Empty Trigger": "Empty Trigger", + "An internal error occurred while fetching data, please contact support": "An internal error occurred while fetching data, please contact support", + "An internal error occurred, please contact support": "An internal error occurred, please contact support", + "Custom Javascript Code": "Custom Javascript Code", + "Router": "Router", + "recordsCount": "{recordsCount, plural, =0 {0 Records} =1 {1 Record} other {# Records}}", + "selected": "selected", + "All records selected": "All records selected", + "fieldsCount": "{fieldsCount, plural, =0 {0 Fields} =1 {1 Field} other {# Fields}}", + "Saving...": "Saving...", + "Loading more...": "Loading more...", + "Export Table": "Export Table", + "Delete Records": "Delete Records", + "Are you sure you want to delete the selected records? This action cannot be undone.": "Are you sure you want to delete the selected records? This action cannot be undone.", + "record": "record", + "records": "records", + "mm/dd/yyy": "mm/dd/yyy", + "Delete Field": "Delete Field", + "Are you sure you want to delete this field? This action cannot be undone.": "Are you sure you want to delete this field? This action cannot be undone.", + "field": "field", + "Ignored": "Ignored", + "Table": "Table", + "CSV": "CSV", + "Field": "Field", + "Please select a csv file": "Please select a csv file", + "Max file size is {maxFileSize}MB": "Max file size is {maxFileSize}MB", + "Import CSV": "Import CSV", + "Imported records will be added to the bottom of the table": "Imported records will be added to the bottom of the table", + "Any records after the limit ({maxRecords} records) will be ignored": "Any records after the limit ({maxRecords} records) will be ignored", + "CSV File": "CSV File", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support", + "Name must be unique": "Name must be unique", + "Type is required": "Type is required", + "Please add at least one option": "Please add at least one option", + "New Field": "New Field", + "Options": "Options", + "Name is already taken": "Name is already taken", + "Table renamed": "Table renamed", + "Table name": "Table name", + "Team Invitation Accepted": "Lời mời của nhóm đã được chấp nhận", + "Thank you for accepting the invitation. We are redirecting you right now...": "Cảm ơn bạn đã chấp nhận lời mời. Chúng tôi đang chuyển hướng...", + "Invalid invitation token. Please try again.": "Mã thông báo lời mời không hợp lệ, vui lòng thử lại.", + "Role updated successfully": "Role updated successfully", + "Error updating role": "Error updating role", + "Please try again later": "Please try again later", + "Edit Role for": "Edit Role for", + "Select Role": "Select Role", + "Avatar": "Ảnh đại diện", + "Remove {email}": "Xóa {email}", + "Are you sure you want to remove this invitation?": "Bạn có chắc muốn xóa lời mời này?", + "Please select invitation type": "Vui lòng chọn loại tổ chức", + "Please select platform role": "Vui lòng chọn vai trò nền tảng", + "Invitation sent successfully": "Lời mời đã được gửi thành công", + "Please select a project role": "Please select a project role", + "Invitation link copied successfully": "Đã sao chép thành công liên kết lời mời", + "Invite User": "Mời Người Dùng", + "Invitation Link": "Liên kết Mời", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "Vui lòng sao chép liên kết bên dưới và chia sẻ nó với người dùng bạn muốn mời, lời mời sẽ hết hạn sau 24 giờ.", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "Type the email address of the user you want to invite, the invitation expires in 24 hours.", + "Invite To": "Được mời lúc", + "Entire Platform": "Toàn bộ nền tảng", + "Select Project Role": "Select Project Role", + "Invite": "Mời", + "Platform Role": "Vai trò nền tảng", + "Select a platform role": "Chọn vai trò nền tảng", + "Are you sure you want to remove this member?": "Bạn có chắc chắn muốn xóa thành viên này?", + "Editor": "Chỉnh sửa", + "Operator": "Điều phối viên", + "Viewer": "Người xem", + "Select a project role": "Chọn vai trò dự án", + "Steps in this flow": "Các step của luồng này", + "Invalid Access": "Invalid Access", + "Either the project does not exist or you do not have access to it.": "Either the project does not exist or you do not have access to it." +} \ No newline at end of file diff --git a/packages/react-ui/public/locales/zh/translation.json b/packages/react-ui/public/locales/zh/translation.json new file mode 100644 index 0000000..a94d244 --- /dev/null +++ b/packages/react-ui/public/locales/zh/translation.json @@ -0,0 +1,1191 @@ +{ + "Publish": "重新上架", + "Latest version is published": "最新版本已发布", + "Your flow has incomplete steps": "您的流程存在无效的步骤", + "Edit Flow": "编辑流程", + "View Draft": "查看草稿", + "Uncategorized": "未分类的", + "Go to folder": "转到文件夹", + "Support": "支持", + "Runs": "运行", + "Run Logs": "运行日志", + "Versions": "版本", + "Versions History": "版本历史", + "Error generating code": "生成代码出错", + "AI Copilot": "AI 复制器", + "i.e Calculate the sum of a list...": "i.e 计算列表的总和...", + "Send": "发送", + "Generating Code": "生成代码", + "Hello there! I am here to generate code that helps with your flow": "您好!我来这里生成帮助您流量的代码", + "Here are examples of what I am best used for: ": "下面是我最好使用的例子: ", + "Text Processing": "文本处理", + "Process strings, dates and data": "处理字符串、日期和数据", + "Data Operations": "数据操作", + "Change data from one format to another": "将数据从一个格式更改为另一个格式", + "Calculations": "计算", + "Handle math and statistics": "处理数学和统计", + "API Integration": "API 集成", + "Connect with external services. Best for simple integrations currently.": "与外部服务连接。目前最适合简单的集成。", + "What would you like me to help you with?": "你想让我帮助你们什么?", + "Insert": "Insert", + "Data Selector": "数据选择器", + "Search": "搜索", + "No matching data": "没有匹配的数据", + "Try adjusting your search": "尝试调整您的搜索", + "This trigger needs to have data loaded from your account, to use as sample data.": "此触发器需要从您的帐户加载数据作为示例数据。", + "This step needs to be tested in order to view its data.": "需要测试这一步骤以便查看其数据。", + "Go to Trigger": "转到触发器", + "Go to Step": "转到步骤", + "Select Mode": "选择模式", + "Move Mode": "移动模式", + "Reset Zoom": "Reset Zoom", + "Zoom In": "放大区域", + "Zoom Out": "缩放", + "Fit to View": "适合查看", + "Replace": "替换", + "Copy": "复制", + "Duplicate": "Duplicate", + "Paste After Last Step": "在最后一步之后粘贴", + "Paste Inside Loop": "粘贴到循环中", + "Paste After": "粘贴后", + "Paste Inside...": "粘贴到内部...", + "New Branch": "新分支", + "Paste Inside Branch": "粘贴到分支中", + "Delete": "删除", + "Duplicate Branch": "复制分支", + "Delete Branch": "删除分支", + "Invalid Move": "无效移动", + "The destination location is a child of the dragged step": "目标位置是拖动步骤的子节点", + "End": "结束", + "Skipped": "跳过", + "Incomplete settings": "不完整的设置", + "logo": "徽标", + "Step Icon": "步骤图标", + "Branch": "分支", + "incompleteSteps": "{invalidSteps, plural, =0 {没有不完整的步骤} =1 {完成步骤} other {完成# 步骤}}", + "Test Flow": "测试流", + "Please test the trigger first": "请先测试触发器", + "View Only": "仅查看", + "Use as Draft": "用作草稿", + "Are you sure?": "您确定吗?", + "Your current draft version will be overwritten with": "您当前的草稿将被覆盖为", + "version #": "版本 #", + "Cancel": "取消", + "Confirm": "确认", + "Version": "版本", + "Viewing": "查看", + "View": "查看", + "Version History": "版本历史记录", + "Error, please try again.": "错误,请重试。", + "Continue on Failure": "失败时继续", + "Enable this option to skip this step and continue the flow normally if it fails.": "启用此选项以跳过此步骤并在其失败时继续正常的流程。", + "Retry on Failure": "失败后重试", + "Automatically retry up to four attempts when failed.": "失败时自动重试最多四次尝试.", + "Remove": "删除", + "Add Item": "添加项目", + "File Input": "文件输入", + "Date Input": "Date Input", + "Dynamic value": "动态值", + "Select an option": "选择一个选项", + "Unexpected error, please retry": "意外错误,请重试", + "Unexpected error, please refresh the page or contact support": "意外错误,请刷新页面或联系客服。", + "Name can only contain letters, numbers and underscores": "名称只能包含字母、数字和下划线", + "Ask AI": "询问AI", + "Create Todo Guide": "创建待办事项指南", + "Where would you like the todo to be reviewed?": "您希望在哪里审查待办事项?", + "Activepieces Todos": "活动任务", + "Users will manage tasks directly in Activepieces": "用户将在活动中直接管理任务", + "Users will manage and respond to todos directly within the Activepieces interface. Ideal for internal teams.": "用户将直接在 Activefs 界面内管理和响应土豆。内部团队的理想状态。", + "External Channel (Slack, Teams, Email, ...)": "外部频道 (黑色,团队,电子邮件,...)", + "Send notifications with approval links via external channels like Slack, Teams or Email. Best for collaborating with external stakeholders.": "通过外部渠道(例如Slack、团队或电子邮件)发送带有批准链接的通知。最适合与外部利益攸关方合作。", + "Preview (Activepieces Todos)": "预览 (激活待办)", + "Preview (External channel)": "预览(外部频道)", + "The Activepieces Todo allows users to review and resolve tasks directly in the Activepieces interface": "Activeches Todo允许用户直接在 Activefs 界面审阅和解决任务", + "You can add the channel before the Wait Step, and configure the logic in the Router step": "您可以在等待步骤之前添加频道,并在路由器步骤中配置逻辑。", + "Add Steps": "添加步骤", + "All": "所有的", + "AI": "AI", + "Core": "核心文件", + "Apps": "应用程序", + "Not available as trigger": "不可以作为触发器", + "Not available as action": "不可以作为操作", + "Let our AI assistant help you out": "让我们的 AI 助手来帮助你", + "Or": "或", + "Request Piece": "请求块", + "No pieces found": "未找到块", + "Please select a piece first": "请先选择一块。", + "All Iterations": "所有迭代数", + "Duration": "期限", + "Input": "Input", + "Output": "产出", + "There are no logs captured for this run.": "这次运行没有抓取日志。", + "Logs are kept for {days} days after execution and then deleted.": "Logs are kept for {days} days after execution and then deleted.", + "Run Details": "运行详情", + "Iteration": "迭代率", + "Done": "完成", + "Took": "取得:", + "Running": "正在运行", + "on latest version": "最新版本", + "from failed step": "从失败步骤", + "Recent Runs": "最近运行的", + "No runs found": "未找到任何运行", + "Close": "关闭", + "OR": "或", + "And If": "如果:", + "+ And": "+", + "+ Or": "+ 或", + "(Text) Contains": "(文字) 包含", + "(Text) Does not contain": "(Text) 不包含", + "(Text) Exactly matches": "(ext) 完全匹配", + "(Text) Does not exactly match": "(ext) 不完全匹配", + "(Text) Starts with": "(Text) 启动", + "(Text) Does not start with": "(Text) 不以", + "(Text) Ends with": "(Text) 结束", + "(Text) Does not end with": "(Text) 不以", + "(List) Contains": "(列表) 包含", + "(List) Does not contain": "(List) 不包含", + "(Number) Is greater than": "(数字) 大于", + "(Number) Is less than": "(数字) 小于", + "(Number) Is equal to": "(数字) 等于", + "(Date/time) After": "(日期/时间) 后", + "(Date/time) Before": "(日期/时间) 前", + "(Date/time) Equals": "(日期/时间) 等于", + "(Boolean) Is true": "(布尔) 为 true", + "(Boolean) Is false": "(布尔) 为 false", + "(List) Is empty": "(List) 为空", + "(List) Is not empty": "(List) 不是空的", + "Exists": "存在", + "Does not exist": "不存在", + "Incomplete condition": "不完整的条件", + "First value": "第一个值", + "Second value": "第二个值", + "Case sensitive": "区分大小写", + "Execute If": "如果执行", + "The package name is required": "软件包名称是必需的", + "Success": "成功", + "Package added successfully": "包添加成功", + "Could not fetch package version": "无法获取软件包版本", + "Add NPM Package": "添加 NPM 包", + "Type the name of the npm package you want to add.": "输入您想要添加的 npm 软件包的名称。", + "Package Name": "软件包名称", + "The latest version will be fetched and added": "将获取并添加最新版本", + "Add": "添加", + "Code": "代码", + "Dependencies": "依赖关系", + "Use code": "使用代码", + "Add package": "添加软件包", + "Inputs": "Inputs", + "Edit Step Name": "编辑步骤名称", + "Edit Branch Name": "编辑分支名称", + "Select the items to iterate over from the previous step by clicking on the **Items** input, which should be a **list** of items.\n\nThe loop will iterate over each item in the list and execute the next step for every item.": "选择要从前一步重复的项目,点击**项目**输入**项目**,这应该是一个项目**列表**。\n\n循环会在列表中的每个项目上重复并执行每个项目的下一步。", + "Items": "项目", + "Select an array of items": "选择一个项目数组", + "Reconnect": "重新连接", + "Select a connection": "选择连接", + "Create Connection": "创建连接", + "Rename": "重命名:", + "Move": "移动", + "Add Branch": "添加分支", + "Execute": "执行", + "Only the first (left) matching branch": "仅第一个(左) 匹配分支", + "All matching paths from left to right": "从左到右的所有匹配路径", + "Branches": "分支", + "{field} is required": "{field} is required", + "Tool Sample Data": "工具示例数据", + "Fill in the following fields to use them as sample data for the trigger.": "填写以下字段作为触发器的示例数据。", + "No input fields defined in the schema": "在schema中没有定义输入字段", + "Save": "保存", + "Test Environment": "测试环境", + "Assigned to": "分配给", + "(Me)": "(我)", + "Please select status to resolve the todo": "请选择状态以解析任务", + "Resolve": "决定", + "Change status to resolved": "将状态更改为已解决", + "Send Sample Data to Webhook": "发送示例数据到 Webhook", + "Method": "方法", + "Query Params": "查询参数", + "Headers": "信头", + "Body": "正文内容", + "Type": "类型", + "JSON": "JSON", + "Text": "文本", + "Form Data": "表单数据", + "Generate Sample Data": "生成示例数据", + "Test Step": "测试步骤", + "Retest": "重新测试", + "Testing Failed": "测试失败", + "Tested Successfully": "测试成功", + "Logs": "日志", + "There is no sample data available found for this trigger.": "没有找到此触发器的示例数据。", + "Internal error, please try again later.": "内部错误,请稍后再试。", + "Failed to run test step and no error message was returned": "运行测试步骤失败,没有返回错误消息", + "Please fix inputs first": "请先修复输入", + "No sample data available": "没有可用的样本数据", + "Old results were removed, retest for new sample data": "旧的结果被删除,重新测试新的样本数据", + "Result #": "结果 #", + "The sample data can be used in the next steps.": "样本数据可以在下一步中使用。", + "Testing Trigger": "测试触发器", + "Action Required": "需要操作", + "testPieceWebhookTriggerNote": "请到 {pieceName} 并触发 {triggerName}。", + "Send Data to the webhook URL to generate sample data to use in the next steps": "发送数据到 webhook URL 来生成样本数据以供下一步使用", + "Test Trigger": "测试触发器", + "Use Mock Data": "使用模拟数据", + "Load Sample Data": "加载示例数据", + "Test Tool": "测试工具", + "home": "首页", + "Home": "首页", + "Alerts": "提醒", + "Releases": "发布", + "Flows": "流动", + "Products": "产品", + "MCP": "MCP", + "Tables": "表", + "Todos": "Todos", + "Push to Git": "推送到Git", + "Move To": "移动到", + "Duplicating": "复制中", + "Import": "导入", + "Exporting": "导出中", + "Export": "导出", + "Share": "分享", + "Are you sure you want to delete this flow? This will permanently delete the flow, all its data and any background runs.": "您确定要删除此流程吗?这将永久删除流程、所有数据和任何后台运行。", + "You are on a development branch, this will also delete the flow from the remote repository.": "您处于开发分支,这也将删除远程仓库中的流程。", + "flow": "流", + "Community Support": "社区支持", + "Overview": "概览", + "Projects": "项目", + "Users": "用户", + "Setup": "设置", + "Branding": "品牌化", + "Global Connections": "全局连接", + "Pieces": "块数", + "Templates": "模板", + "License Key": "许可证密钥", + "Security": "安全", + "Audit Logs": "审核日志", + "Single Sign On": "单点登录", + "Signing Keys": "签名密钥", + "Project Roles": "项目角色", + "API Keys": "API 密钥", + "Infrastructure": "B. 基础设施", + "Workers": "A. 工 人", + "Health": "保健", + "Billing": "计费", + "Settings": "设置", + "Contact Sales": "联系销售", + "General": "A. 概况", + "Appearance": "外观", + "Team": "团队", + "Environments": "环境", + "Project Settings": "项目设置", + "Exit Platform Admin": "退出平台管理", + "Enter Platform Admin": "输入平台管理", + "Logout": "注销", + "Misc": "其他", + "Connections": "连接", + "days": "天", + "hours": "小时", + "minutes": "分钟", + "Today": "今日:", + "Tasks": "任务", + "AI Credits": "AI Credits", + "Usage resets in": "使用重置于", + "Manage": "管理", + "Unlimited": "无限制", + "Enabled": "已启用", + "Disabled": "已禁用", + "Could not claim the authorization code, make sure you have correct settings and try again.": "无法认领授权码,请确认您有正确的设置并重试。", + "Connection failed with error {msg}": "Connection failed with error {msg}", + "You don't have the permission to create a connection.": "您没有创建连接的权限。", + "Reconnect {displayName} Connection": "Reconnect {displayName}", + "Connect to {displayName}": "Connect to {displayName}", + "Connection Name": "连接名称", + "Connection name": "连接名称", + "New Connection": "新建连接", + "Redirect URL": "重定向网址", + "Client ID": "客户端ID", + "Client Secret": "客户端密钥", + "Connect": "连接", + "Disconnect": "断开连接", + "I would like to use my own App Credentials": "我想使用我自己的应用程序凭据", + "I would like to use predefined App Credentials": "我想使用预定义的应用凭据", + "Permission needed": "需要权限", + "Connections replaced successfully": "连接已成功替换", + "Error": "错误", + "Failed to replace connections": "替换连接失败", + "Failed to get affected flows": "无法获得受影响的流程", + "Please select a piece": "请选择一个块", + "Please select a connection to replace": "请选择要替换的连接", + "Please select a connection to replace with": "请选择要替换的连接", + "Replace Connections": "替换连接", + "Confirm Replacement": "确认替换", + "Replace one connection with another.": "将一个连接替换为另一个连接。", + "This action requires ": "此操作需要 ", + "reconnecting": "重新连接", + " any associated MCP pieces.": " 任何关联的 MCP 片段。", + "Piece": "块", + "Select a piece": "选择一块块", + "Connection to Replace": "替换连接", + "Choose connection to replace": "选择要替换的连接", + "Replaced With": "替换为", + "Choose connection to replace with": "选择要替换的连接", + "All flows will be changed to use the replaced with connection": "所有流量都将被更改为使用被替换的连接", + "Next": "下一个", + "No flows will be affected by this change": "此更改将不会影响流量。", + "Back": "后退", + "Unnamed tool": "未命名工具", + "This flow is enabled": "此流已启用", + "Enable this flow to make it available": "启用此流以使其可用", + "Piece is updated successfully": "块已成功更新", + "Piece is added successfully": "块添加成功", + "Failed to update piece": "更新片段失败", + "Failed to add piece": "添加片件失败", + "Please select a connection": "请选择一个连接", + "Your MCP server already has this piece": "您的 MCP 服务器已经有这篇文章", + "+ New Connection": "+ 新建连接", + "Edit Piece": "编辑块", + "Add Piece": "添加块", + "Connection": "连接", + "MCP piece": "MCP 片段", + "Failed to update piece status": "更新片段状态失败", + "Connection required": "需要连接", + "Are you sure you want to delete this tool from your MCP? if you delete it you won't be able to use it in your MCP client.": "您确定要从MCP中删除此工具吗? 如果您删除它,您将无法在 MCP 客户端中使用它。", + "piece": "片段", + "Connect your AI assistant to external services": "将您的 AI 助手连接到外部服务", + "Collapse": "收起", + "Show All": "显示全部", + "Server URL": "服务器 URL", + "Hide the token for security": "隐藏安全令牌", + "Show the token": "显示令牌", + "Generate a new token for security. This will invalidate the current URL.": "生成一个新的安全令牌。这将使当前的 URL 无效。", + "URL copied to clipboard": "URL 已复制到剪贴板", + "Copy URL to clipboard": "复制 URL 到剪贴板", + "This URL contains a sensitive security token. Only share it with trusted applications and services. Rotate the token if you suspect it has been compromised.": "此 URL 包含一个敏感的安全令牌。只有与受信任的应用程序和服务共享它。如果您怀疑它已经被损坏,请旋转令牌。", + "After making any changes to connections or flows, you will need to reconnect your MCP server for the changes to take effect.": "在对连接或流量进行任何更改后,您需要重新连接您的 MCP 服务器才能使更改生效。", + "Max tables reached": "已达到最大数据表", + "You can't create more than {maxTables} tables": "You can't create more than {maxTables} tables", + "Name": "名称", + "Created": "已创建", + "Delete Tables": "删除表", + "Are you sure you want to delete the selected tables? This action cannot be undone.": "您确定要删除选定的表吗?此操作无法撤消。", + "table": "表", + "Create and manage your tables to store your automation data": "创建和管理您的表格以存储您的自动化数据", + "New Table": "新建表", + "No tables have been created yet": "尚未创建表", + "Create a table to get started and start managing your automation data": "创建一个表以开始并开始管理您的自动化数据", + "Error deleting connections": "删除连接时出错", + "Status": "状态", + "Display Name": "显示名称", + "Owner": "所有者", + "App": "应用", + "This connection is global and can be managed in the platform admin": "此连接是全局的,可以在平台管理器中管理", + "External ID": "外部ID", + "Connected At": "连接于", + "Confirm Deletion": "确认删除", + "Are you sure you want to delete the selected connections? This action cannot be undone.": "您确定要删除选定的连接吗?此操作无法撤消。", + "Deleting connections may cause your Flows or MCP tools to break.": "删除连接可能导致您的流程或 MCP 工具中断。", + "Manage project connections to external systems.": "管理到外部系统的项目连接。", + "No connections found": "未找到连接", + "Come back later when you create a automation to manage your connections": "稍后在您创建自动化来管理您的连接", + "Steps": "步骤", + "Folder": "文件夹", + "Flow name": "流程名称", + "No flows found": "没有找到流程", + "Create a workflow to start automating": "创建工作流以启动自动化", + "Create and manage your flows, run history and run issues": "创建和管理您的流程,运行历史和运行问题", + "Issues": "议 题", + "Untitled": "无标题", + "Create flow": "创建流程", + "From scratch": "从头开始", + "Use a template": "使用模板", + "From local file": "从本地文件", + "Flow Name": "流名称", + "Count": "计数", + "First Seen": "第一次查看", + "Last Seen": "上次查看时间", + "Issues in {flowDisplayName} is marked as resolved.": "Issues in {flowDisplayName} is marked as resolved.", + "Unlock Issues": "解锁问题", + "Track issues in your workflows and troubleshoot them.": "跟踪您的工作流中的问题并解决这些问题。", + "No issues found": "未发现问题", + "All your workflows are running smoothly.": "您的所有工作流都在顺利运行。", + "Mark as Resolved": "标记为已解决", + "All Flows Are Turned Off": "所有流量都已关闭", + "Task Usage Exceeded": "任务使用率已过期", + "of the Allowed Limit.": "允许的限制.", + "When a project tasks limit is reached,": "当达到项目任务限制", + "all flows will be turned off and you will not be able to run any flows.": "所有流量将被关闭,您将无法运行任何流量。", + "Please visit": "请访问", + "Your Plan": "您的计划", + "and increase your task limit, which requires your payment details.": "并提高您的任务限制,这需要您的付款详细信息。", + "Please contact your admin to increase the project task limit.": "请联系您的管理员以增加项目任务限制。", + "and increase the project task limit.": "并增加项目任务限制。", + "Dismiss": "关闭", + "Token rotated successfully": "令牌旋转成功", + "Failed to rotate token": "旋转令牌失败", + "Piece removed successfully": "方块删除成功", + "Failed to remove piece": "删除块失败", + "Flow created successfully": "流创建成功", + "Failed to create flow": "创建流失败", + "Add Flow": "添加流", + "Let your AI assistant trigger automations": "让您的 AI 助手触发自动化", + "Connect to your hosted MCP Server using any MCP client to communicate with tools": "使用任何 MCP 客户端连接到您托管的 MCP 服务器与工具通信", + "MCP Server": "MCP 服务器", + "My Tools": "我的工具", + "Create Flow": "创建流", + "Note": "说明", + "If you would like to expose your MCP server to the internet, please set the AP_FRONTEND_URL environment variable to the public URL of your Activepieces instance.": "如果您想要将您的 MCP 服务器曝光到互联网, 请将 AP_FRONTEND_URL 环境变量设置为 Actives 实例的公开URL。", + "This URL grants access to your tools and data. Only share with trusted applications.": "此 URL 允许访问您的工具和数据。只能分享到信任的应用程序。", + "Server Configuration": "服务器配置", + "Hide sensitive data": "隐藏敏感数据", + "Show sensitive data": "显示敏感数据", + "Create a new URL. The current one will stop working.": "创建一个新的 URL。当前的URL将停止工作。", + "Copy configuration": "复制配置", + "Configuration copied to clipboard": "配置已复制到剪贴板", + "Claude": "克洛德", + "Cursor": "Cursor", + "Windsurf": "风雪", + "Server/Other": "服务器/其他", + "Note: MCPs only work with": "注意:MCP只能使用", + "Claude Desktop": "Claude 桌面", + ", not the web version.": ", 而不是 web 版本。", + "Prerequisites:": "前提条件:", + "Install": "安装", + "Node.js": "Node.js", + "and": "和", + "Open Settings:": "打开设置:", + "Click the menu and select": "单击菜单并选择", + "Developer": "开发者", + "Configure MCP:": "配置 MCP:", + "Click": "Click", + "Edit Config": "编辑配置", + "and paste the configuration below": "粘贴下面的配置", + "Save and Restart:": "保存并重启:", + "Save the config and restart Claude Desktop": "保存配置并重启Claude 桌面", + "Navigate to": "导航到", + "Cursor Settings": "光标设置", + "Add Server:": "添加服务器:", + "Add new global MCP server": "添加全局MCP 服务器", + "Configure:": "配置:", + "Paste the configuration below and save": "粘贴下面的配置并保存", + "Use either method:": "使用任一方法:", + "Go to": "转至", + "Advanced Settings": "高级设置", + "Open Command Palette and select": "打开命令调色板并选择", + "Windsurf Settings Page": "风暴设置页面", + "Navigate to Cascade:": "导航到Cassca:", + "Select": "选择", + "Cascade": "Cascade", + "in the sidebar": "在侧边栏", + "Add Server": "添加服务器", + "Add custom server +": "添加自定义服务器 +", + "Copy URL": "复制 URL", + "Client Setup Instructions": "客户端安装说明", + "After changing connections or flows, reconnect your MCP server for changes to take effect.": "更改连接或流量后,重新连接您的 MCP 服务器以使更改生效。", + "Follow these steps to set up MCP in your preferred client. This enables your AI assistant to access your tools.": "按照这些步骤在您的首选客户端中设置 MCP。这使您的 AI 助手能够访问您的工具。", + "icon": "图标", + "Unlock Analytics": "解锁分析", + "Get insights into your platform usage and performance with our analytics dashboard": "通过我们的分析仪表板了解您的平台使用情况和性能。", + "Active Flows": "活动流动", + "The number of enabled flows in the platform": "平台中启用的流数", + "Active Projects": "活动项目", + "The number of projects with at least one enabled flow": "至少启用了一个流量的项目数", + "Active Users": "活跃用户", + "The number of users logged in the last 30 days": "在过去 30 天登录的用户数", + "Out of {totalusers} total users": "Out of {totalusers} total users", + "Pieces Used": "已使用的块", + "The number of unique pieces used across all flows": "所有流量中使用的唯一块数量", + "Flows with AI": "使用 AI 的流动", + "The number of enabled flows that use AI pieces": "使用 AI 块的已启用流的数量", + "Metrics": "指标", + "Executed Tasks": "已执行的任务", + "Showing total executed tasks for specified time range": "显示指定时间范围内已执行的任务总数", + "Tasks Usage Limit": "任务使用限制", + "Specify a monthly limit for tasks to avoid excessive usage. Your flows will no longer execute if this limit was reached.": "为避免过多使用指定每月限制。如果达到此限制,您的流量将不再执行。", + "Number of monthly tasks": "每月任务数", + "Save changes": "保存更改", + "Limits updated successfully": "限制已成功更新", + "Failed to update limits": "更新限制失败", + "Failed to load billing information": "加载账单信息失败", + "Billing Amount": "计费金额", + "Manage Payment Details": "管理付款详细信息", + "Add Payment Details": "添加付款详细信息", + "Current Task Usage": "当前任务使用情况", + "Count of executed steps": "已执行步骤计数", + "Billing Limit": "计费限制", + "Edit": "编辑", + "Add Limit": "添加限制", + "Current Credit Usage": "当前信用使用情况", + "WebSocket Connection": "WebSocket 连接", + "Connected": "已连接", + "Disconnected": "断开连接", + "No issues detected": "未检测到问题", + "Check the status of your platform and its components": "检查您的平台及其组件的状态", + "System Health Status": "系统健康状况", + "All systems are running smoothly": "所有系统正在顺利运行", + "Check the health of your worker machines": "检查您的工人机器的健康", + "Workers Machine": "工人机", + "This is demo data. In a real environment, this would show your actual worker machines.": "这是演示数据。在实际环境中,这将显示您的实际工作机器。", + "No workers found": "找不到工人", + "You don't have any worker machines yet. Spin up new machines to execute your automations": "您还没有任何劳动者机器。请使用新机器来执行您的自动化", + "IP Address": "IP 地址", + "CPU Usage": "CPU 使用", + "Disk Usage": "磁盘使用", + "RAM Usage": "RAM Usage", + "Last Contact": "上次联系人", + "Configs": "配置", + "Environment Variables": "环境变量", + "Websocket Connection Error": "Websocket 连接错误", + "Retry Connection": "重试连接", + "Update Available": "可用更新", + "Update Now": "立即更新", + "Your Universal AI needs a quick setup": "您的 Universal AI 需要快速设置", + "We noticed you haven't set up any AI providers yet. To unlock Universal AI pieces for your team, you'll need to configure some provider credentials first.": "我们注意到您尚未设置任何的 AI 提供商。 为了解锁您的团队的 Universal AI 块,您需要先配置一些提供者凭据。", + "Configure": "配置", + "Platform Alerts": "平台提醒", + "There are important platform alerts that require your attention. Please check the alerts section in Platform Admin.": "有重要的平台警报需要您注意。请检查平台管理员中的警报部分。", + "View Alerts": "查看提醒", + "Used Tasks": "已使用的任务", + "Used AI Credits": "使用 AI 点数", + "Cannot delete active project, switch to another project first": "无法删除活动项目,请先切换到另一个项目", + "Delete Projects": "删除项目", + "Are you sure you want to delete the selected projects?": "您确定要删除选定的项目吗?", + "New Project": "新建项目", + "Validation error": "验证错误", + "Project has enabled flows. Please disable them first.": "项目已启用流。请先禁用它们。", + "This project is active. Please switch to another project first.": "此项目已激活。请先切换到另一个项目。", + "Unlock Projects": "解锁项目", + "Orchestrate your automation teams across projects with their own flows, connections and usage quotas": "通过自己的流程、连接和使用配额来管理您的自动化团队", + "Manage your automation projects": "管理您的自动化项目", + "No projects found": "未找到项目", + "Start by creating projects to manage your automation teams": "创建项目来管理您的自动化团队", + "Edit project": "编辑项目", + "Name is required": "名称是必填项", + "Create New Project": "创建新项目", + "Project Name": "项目名称", + "Id": "Id", + "Enable API Keys": "启用 API 密钥", + "Create and manage API keys to access Activepieces APIs.": "创建和管理 API 密钥以访问 Activess API。", + "New Api Key": "新 Api 密钥", + "No API keys found": "未找到 API 密钥", + "Start by creating an API key to communicate with Activepieces APIs": "首先创建一个 API 密钥以与 Actives API进行通信", + "Delete API Key": "删除 API 密钥", + "Are you sure you want to delete this API key?": "您确定要删除此 API 密钥吗?", + "API Key": "API 密钥", + "API Key Created": "API 密钥已创建", + "Create New API Key": "创建新 API 密钥", + "Please save this secret key somewhere safe and accessible. For security reasons,": "请保存此密钥的安全性和可访问性。出于安全原因。", + "you won't be able to view it again after closing this dialog.": "在关闭此对话框后,您将无法再查看它。", + "API Key Name": "API 密钥名称", + "Action": "行 动", + "Performed By": "执行者", + "Project": "项目", + "Unlock Audit Logs": "解锁审核日志", + "Comply with internal and external security policies by tracking activities done within your account": "通过跟踪在您的帐户中完成的活动来遵守内部和外部安全政策", + "Track activities done within your platform": "跟踪在您的平台内完成的活动", + "No audit logs found": "没有找到审核日志", + "Come back later when you have some activity to audit": "稍后在您有一些活动进行审计", + "Resource": "资源", + "Details": "详细信息", + "N/A": "无", + "Flow Run": "流运行", + "Flow": "流", + "User": "用户", + "Signing Key": "签名密钥", + "Project Role Management": "项目角色管理", + "Define custom roles and permissions to control what your team members can access and modify": "定义自定义角色和权限来控制您的团队成员可以访问和修改的内容", + "Define custom roles and permissions that can be assigned to your team members": "定义可以分配给您的团队成员的自定义角色和权限", + "New Role": "新建角色", + "Contact sales to unlock custom roles": "联系销售以解锁自定义角色", + "Create New Role": "创建新角色", + "View ": "查看 ", + "Edit ": "编辑 ", + "Role Name": "角色名称", + "Permissions": "权限", + "None": "无", + "Read": "已读", + "Write": "写入", + "Create": "创建", + "Email": "电子邮件地址", + "First Name": "名字", + "Last Name": "名字", + "Roles": "角色", + "View the users assigned to this role": "查看分配给此角色的用户", + "Role": "作用", + "No users found": "未找到用户", + "Starting by assigning users to this role": "从分配用户到此角色开始", + "Project Role entry deleted successfully": "项目角色条目删除成功", + "Updated": "已更新", + "No project roles found": "没有找到项目角色", + "Create custom project roles to manage permissions for platform users": "创建自定义项目角色来管理平台用户的权限", + "Show Users": "显示用户", + "View Role": "查看角色", + "Edit Role": "编辑角色", + "Delete Role": "删除角色", + "Project Role": "项目角色", + "Unlock Embedding Through JS SDK": "解锁通过JSSDK 嵌入", + "Enable signing keys to access embedding functionalities.": "启用签名密钥访问嵌入功能。", + "New Signing Key": "新建签名密钥", + "No signing keys found": "未找到签名密钥", + "Create a signing key to authenticate users with embedding": "创建签名密钥以验证嵌入用户", + "Delete Signing Key": "删除签名密钥", + "Are you sure you want to delete this signing key?": "您确定要删除此签名密钥吗?", + "Signing Key Created": "签名密钥已创建", + "Create New Signing Key": "创建新签名密钥", + "Signing Key Name": "签名密钥名称", + "Allowed domains updated": "允许的域已更新", + "Update": "更新", + "Enable": "启用", + "Configure Allowed Domains": "配置允许的域", + "Enter the allowed domains for the users to authenticate with, Empty list will allow all domains.": "输入允许用户认证的域名。空列表将允许所有域名。", + "Add Domain": "添加域", + "Allow logins through {providerName}'s single sign-on functionality.": "Allow logins through {providerName}'s single sign-on functionality.", + "Email authentication updated": "电子邮件身份验证已更新", + "Enable Single Sign On": "启用单点登录", + "Let your users sign in with your current SSO provider or give them self serve sign up access": "让您的用户使用您当前的 SSO 提供商登录或让他们自己注册访问权限", + "Allowed Domains": "允许的域", + "Allow users to authenticate with specific domains. Leave empty to allow all domains.": "允许用户使用特定域名进行身份验证。留空以允许所有域名。", + "SAML 2.0": "SAML 2.0", + "Allowed Email Login": "允许的电子邮件登录", + "Allow logins through email and password.": "允许通过电子邮件和密码登录。", + "Single sign on settings updated": "单点登录设置已更新", + "Disable": "禁用", + "Configure {provider} SSO": "Configure {provider} SSO", + "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).": "Read more information about how to configure {provider} SSO [here](https://www.activepieces.com/docs/security/sso).", + "{provider} Client ID": "{provider} Client ID", + "{provider} Client Secret": "{provider} Client Secret", + "Single sign-on settings updated": "单点登录设置已更新", + "Configure SAML 2.0 SSO": "配置 SAML 2.0 SSO", + "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n": "\n**Setup Instructions**:\nPlease check the following documentation: [SAML SSO](https://activepieces.com/docs/security/sso)\n\n**Single sign-on URL**:\n```text\n{samlAcs}\n```\n**Audience URI (SP Entity ID)**:\n```text\nActivepieces\n```\n", + "IDP Metadata": "IDP 元数据", + "IDP Certificate": "IDP 证书", + "Configure AI Provider": "Configure AI Provider", + "Base URL": "基本网址", + "Resource Name": "资源名称", + "Deployment Name": "部署名称", + "Saving": "正在保存", + "Activepieces Copilot": "Activets 复制", + "Copilot is configured and ready to help your users build flows faster using AI.": "Copilot 已配置好并准备好使用AI来帮助您的用户建立更快的流程。", + "Configure Activepieces Copilot to help your users build flows faster using AI.": "配置Activescs Copilot,帮助您的用户使用AI建立更快的流程。", + "Unlock AI": "Unlock AI", + "Set your AI providers & copilot settings so your users enjoy a seamless building experience with our universal AI pieces": "设置您的 AI 提供商和copilot 设置,让您的用户享受无缝建筑体验,与我们的通用AI 片段", + "Set provider credentials that will be used by universal AI pieces, i.e Text AI.": "设置将被通用的 AI 小块使用的提供者凭据,即文本AI。", + "AI Providers": "AI Providers", + "Copilot": "科皮洛特", + "Configure credentials for {providerName} AI provider.": "Configure credentials for {providerName} AI provider.", + "Update AI Provider": "更新 AI 提供商", + "Enable AI Provider": "启用 AI 提供商", + "sk_************************": "sk_************************", + "Please enter a valid domain": "请输入一个有效的域", + "Your changes have been saved.": "您的更改已保存。", + "The domain is already added.": "该域已被添加。", + "Add Custom Domain": "添加自定义域", + "Enter a domain name without a protocol (e.g. example.com)": "输入没有协议的域名(例如,example.com)", + "Logo URL": "徽标网址", + "Icon URL": "图标网址", + "Favicon URL": "Favicon URL", + "Default Language": "默认语言", + "Select Language": "选择语言", + "No Languages": "没有语言", + "Primary Color": "主颜色", + "Custom Domains": "自定义域", + "No domains added yet.": "尚未添加域名。", + "Verified": "已验证", + "Pending, please contact the support for dns verification.": "Pending,请联系客服来验证。", + "Are you sure you want to delete {domain}?": "Are you sure you want to delete {domain}?", + "Brand Activepieces": "品牌激活", + "Give your users an experience that looks like you by customizing the color, logo and more": "通过自定义颜色、标志和更多来给您的用户一个看起来像样的体验", + "Configure the appearance and SMTP settings for your platform.": "配置您平台的外观和SMTP设置。", + "Invalid host": "无效主机", + "Invalid username": "用户名无效", + "Invalid password": "无效密码", + "Invalid sender email": "Invalid sender email", + "Invalid sender name": "无效的发件人名称", + "SMTP is configured": "SMTP 已配置", + "Mail Server": "邮件服务器", + "Set up your SMTP settings to send emails from your domain.": "设置您的 SMTP 设置,从您的域发送电子邮件。", + "Disable Mail Server": "禁用邮件服务器", + "Are you sure you want to disable your mail server?": "您确定要禁用您的邮件服务器吗?", + "This will stop you from sending emails for issues, quota limits, invitations and forgot password.": "这将阻止您发送电子邮件以获取问题、配额限制、邀请和忘记密码。", + "mail server": "邮件服务器", + "Host": "主机", + "Port": "端口", + "Username": "用户名", + "Password": "密码", + "Sender Email": "发件人电子邮件", + "Sender Name": "发件人姓名", + "Enable Global Connections": "启用全局连接", + "Manage platform-wide connections to external systems.": "管理到外部系统的全平台连接。", + "No global connections found": "未找到全局连接", + "Create a global connection that can be shared to multiple projects": "创建一个可以共享到多个项目的全局连接", + "License key is invalid": "许可证密钥无效", + "Invalid license key": "无效的许可证密钥", + "License activated!": "许可证已激活!", + "Activate License Key": "激活许可证密钥", + "Let the magic begin!": "让魔法开始!", + "Activate": "激活", + "This feature is not self serve in the cloud yet, please contact sales@activepieces.com. ": "此功能尚未在云端服务。请联系 sales@activeteres.com。 ", + "This feature is not available in your current edition. ": "此功能在您当前的版本中不可用。 ", + "Learn how to upgrade": "学习如何升级", + "Activate your platform and unlock enterprise features": "激活您的平台并解锁企业功能", + "Activate License": "激活许可证", + "Expiration": "过期时间", + "Valid until": "有效期至", + "Expired": "已过期", + "Expires soon": "即将过期", + "Features": "功能", + "Applying Tags...": "正在应用标签...", + "Tags applied.": "已应用标签。", + "Tag created": "标签已创建", + "Tag": "标签", + "Tags": "标签", + "Control Pieces": "控制块", + "Show the pieces that matter most to your users and hide the ones you don't like.": "显示对您的用户最重要的块,并隐藏您不喜欢的块。", + "Manage the pieces that are available to your users": "管理您的用户可以使用的片段", + "Start by installing pieces that you want to use in your automations": "从安装你想要在自动化中使用的块开始", + "Piece Name": "块名称", + "Hide this piece from all projects": "从所有项目中隐藏这篇文章", + "Show this piece for all projects": "显示所有项目的这篇文章", + "Unpin this piece": "取消固定这个片段", + "Pin this piece": "固定这个片段", + "Pieces synced": "片段已同步", + "Pieces have been synced from the activepieces cloud.": "已经从活动云同步块。", + "OAuth2 Credentials Deleted": "OAuth2 证书已删除", + "OAuth2 Credentials Updated": "OAuth2 凭据已更新", + "Configure OAuth2 APP": "配置 OAuth2 APP", + "Delete OAuth2 APP": "删除 OAuth2 APP", + "Templates deleted successfully": "模板删除成功", + "Delete Templates": "删除模板", + "Are you sure you want to delete the selected templates?": "您确定要删除选定的模板吗?", + "New Template": "新模板", + "Unlock Templates": "解锁模板", + "Convert the most common automations into reusable templates 1 click away from your users": "将最常用的自动转换为可重复使用的模板 1 单击您的用户", + "Convert the most common automations into reusable templates": "将最常用的自动转换为可重复使用的模板", + "No templates found": "没有找到模板", + "Create a template for your user to inspire them": "为您的用户创建一个模板以启发他们的", + "Edit template": "编辑模板", + "Template is required": "模板是必填项", + "Update New Template": "更新新模板", + "Create New Template": "创建新模板", + "Template Name": "模板名称", + "Description": "描述", + "Template Description": "模板描述", + "Blog URL": "博客网址", + "Template Blog URL": "模板博客 URL", + "Template": "模板", + "Invalid JSON": "无效的 JSON", + "User deleted successfully": "用户删除成功", + "User activated successfully": "用户激活成功", + "User deactivated successfully": "用户停用成功", + "Unlock Users": "Unlock Users", + "Manage your users and their access to your projects": "管理您的用户和他们对您项目的访问", + "Start inviting users to your project": "开始邀请用户加入您的项目", + "External Id": "外部ID", + "Admin": "管理员", + "Member": "成员", + "Activated": "已激活", + "Deactivated": "已停用", + "Edit user": "编辑用户", + "Admin cannot be deactivated": "管理员无法停用", + "Deactivate user": "停用用户", + "Activate user": "激活用户", + "Delete User": "删除用户", + "Are you sure you want to delete this user?": "您确定要删除此用户吗?", + "Delete user": "删除用户", + "Update User Role": "更新用户角色", + "Meeting Summary Flow": "会议简流", + "Added new features and fixed bugs": "添加新功能和修复错误", + "Flows Changes": "流量变化", + "Connections Changes": "连接更改", + "New connections are placeholders and need to be reconnected again": "新连接是占位符,需要重新连接", + "renamed to": "重命名为", + "Tables Changes": "表格更改", + "No changes to apply": "没有要应用的更改", + "Apply Changes": "应用更改", + "Create Git Release": "创建 Git 发布", + "Create Project Release": "创建工程发布", + "Create Rollback to": "创建回滚到", + "Source": "来源", + "Rollback": "Rollback", + "Imported At": "导入于", + "Imported By": "导入者", + "Track and manage your project version history and deployments. ": "跟踪和管理您的项目版本历史和部署。 ", + "Environments & Releases": "环境与发布", + "Project Releases": "项目发布", + "Create Release": "创建版本", + "From Git": "来自Git", + "From Project": "从项目", + "No project releases found": "没有找到工程版本", + "Create a project release to get started": "创建一个工程版本以开始", + "Please select project": "请选择项目", + "No Changes Found": "未找到更改", + "There are no differences to apply": "没有可应用的差异", + "Please select a project": "请选择一个项目", + "Search projects...": "搜索项目...", + "Review Changes": "审核更改", + "Git": "Git", + "Summary": "Summary", + "Imported by": "导入者", + "from": "来自", + "No description provided": "未提供描述", + "Invitation only sign up": "仅邀请注册", + "Please ask your administrator to add you to the organization.": "请请求您的管理员将您添加到组织。", + "Something went wrong, please try again.": "出了错,请重试。", + "Please try again.": "请再试一次。", + "Please enter a valid email address": "请输入一个有效的电子邮件地址", + "The email is already added.": "电子邮件已被添加。", + "Add email": "添加电子邮件", + "Only project admins can do this": "只有项目管理员可以做到这一点。", + "Add Alert Email": "添加警报邮件", + "Enter the email address to receive alerts.": "输入电子邮件地址以接收警报。", + "Add Email": "添加电子邮件", + "Emails": "电子邮件", + "Add email addresses to receive alerts.": "添加电子邮件地址以接收警报。", + "No emails added yet.": "尚未添加电子邮件。", + "Choose what you want to be notified about.": "选择您想要通知的信息。", + "Project and alert permissions are required to change this setting.": "更改此设置需要项目和警报权限。", + "Every Failed Run": "每次运行失败", + "Get an email alert when a flow fails.": "当流失败时获取电子邮件警报。", + "Get an email alert when a new issue created.": "创建新问题时获取电子邮件警报。", + "Never": "从不使用", + "Turn off email notifications.": "关闭电子邮件通知。", + "Customize the appearance of the app. Automatically switch between day and night themes.": "自定义应用程序外观。在白天和夜晚主题之间自动切换。", + "Select the theme for the dashboard.": "选择仪表板的主题。", + "Light": "亮色的", + "Dark": "深色", + "Select the language that will be used in the dashboard.": "选择将在仪表盘中使用的语言。", + "Select language": "选择语言", + "Search language...": "搜索语言...", + "No language found.": "未找到语言。", + "Help us translate Activepieces to your language.": "帮助我们将 Actives 翻译成你的语言。", + "Learn more": "了解更多", + "Git Connection Removed": "Git 连接已删除", + "Your Git repository has been successfully disconnected": "您的 Git 仓库已成功断开连接", + "Enable Environments": "启用环境", + "Deploy flows across development, staging and production environments with version control and team collaboration": "通过版本控制和团队协作,在开发、分阶段和生产环境中部署流动", + "Connect to Git to enable version control, backup your flows, and manage multiple environments. ": "连接到 Git 以启用版本控制,备份您的流量并管理多个环境。 ", + "Repository URL": "存储库 URL", + "Not connected": "未连接", + "Project Folder": "项目文件夹", + "Releases Enabled": "启用发布", + "You have successfully enabled releases": "您已成功启用发布", + "Enable releases to easily create and manage project releases.": "启用发布以轻松创建和管理项目发布版本。", + "The external ID is already taken.": "外部ID已被占用。", + "Manage general settings for your project.": "管理您的项目的一般设置。", + "Used to identify the project based on your SaaS ID": "用于根据您的 SaaS ID识别项目", + "org-3412321": "org-3412321", + "Delete {name}": "Delete {name}", + "This will permanently delete this piece, all steps using it will fail.": "这将永久删除这件事,使用它的所有步骤都将失败。", + "Add a piece to your project that you want to use in your automations": "在你的项目中添加一个你想要在你的自动化中使用的内容", + "Pieces list updated": "块列表已更新", + "Manage Pieces": "管理块", + "Choose which pieces you want to be available for your current project users": "选择您想要为当前项目用户提供的内容", + "Unlock Team Permissions": "解锁团队权限", + "You can invite users to your Platform for free in the community edition. For advanced roles and permissions request trial": "您可以在社区版免费邀请用户加入您的平台。对于高级角色和权限请求测试", + "Project Members": "项目成员", + "Invite your team members to collaborate.": "邀请您的团队成员进行合作。", + "No members are added to this project.": "没有成员被添加到此项目。", + "Pending Invitations": "待定邀请", + "No pending invitation.": "没有待处理的邀请。", + "templateId is missing": "模板ID丢失", + "Me Only": "仅我", + "Unresolved": "未解决", + "Resolved": "已解决", + "Title": "标题", + "Date Created": "创建日期", + "Manage todos for your project that are created by automations": "管理由自动创建的项目任务", + "No todos found": "未找到待办事项", + "You do not have any pending todos. Great job!": "你没有任何待处理的土豆。干得好!", + "Write a comment...": "撰写评论...", + "Beta": "测试版", + "This feature is still under testing and might be changed often": "此功能仍在测试中,可能经常被更改", + "Failed to copy to clipboard": "复制到剪贴板失败", + "{number} items selected": "{number} items selected", + "Select All": "选择所有", + "No results found.": "未找到任何结果。", + "useMultiSelect must be used within MultiSelectProvider": "使用 MultiselectProvider 必须使用", + "Unset": "取消设置", + "Refresh": "刷新", + "+{remainingPiecesCount} more": "+{remainingPiecesCount} more", + "Removed {entityName}": "Removed {entityName}", + "Download File": "下载文件", + "Copied to clipboard": "复制到剪贴板", + "File is not available after execution.": "执行后文件不可用。", + "Available for Projects": "可用于项目", + "Select projects": "选择项目", + "No items": "没有项目", + "Previous": "上一个", + "to": "到", + "Last Week": "最后一周", + "Last Month": "上月", + "Last 3 Months": "过去 3 个月", + "Last 6 Months": "过去 6 个月", + "Next 7 days": "接下来7天", + "Next 30 days": "30天后", + "Next 90 days": "今后90天", + "Next 180 days": "今后180天", + "Select Time Range": "选择时间范围", + "Clear": "清空", + "Download": "下载", + "Go to Dashboard": "转到仪表板", + "Select a file": "选择一个文件", + "Press space to separate values": "按空格分隔值", + "AM": "凌晨5时", + "PM": "私信", + "Already have an account?": "已经有一个帐户?", + "Sign in": "登录", + "Don't have an account?": "没有账户?", + "Sign up": "注册", + "Welcome Back!": "欢迎回来!", + "Enter your email below to sign in to your account": "请在下面输入您的电子邮件以登录您的帐户", + "Let's Get Started!": "让我们开始吧!", + "Create your account and start flowing!": "创建您的帐户并开始流程!", + "Your password was changed successfully": "您的密码已成功更改", + "Your password reset request has expired, please request a new one": "您的密码重置请求已过期,请申请一个新的请求", + "Reset Password": "重置密码", + "Enter your new password": "输入新密码", + "Password is required": "密码是必需的", + "Verification email resent, if previous one expired.": "验证邮件重新发送,如果前一封已过期。", + "Password reset link resent, if previous one expired.": "密码重置链接重新发送,如果前一个链接已过期。", + "We sent you a link to complete your registration to": "我们向您发送了一个链接来完成您的注册到", + "We sent you a link to reset your password to": "我们向您发送了一个链接来重置您的密码到", + "Didn't receive an email or it expired?": "没有收到邮件或邮件已过期?", + "Resend": "重新发送", + "Please enter your email": "请输入您的电子邮件", + "Check Your Inbox": "检查您的收件箱", + "If the user exists we'll send you an email with a link to reset your password.": "如果用户存在,我们会向您发送一封包含重置密码链接的电子邮件。", + "Send Password Reset Link": "发送密码重置链接", + "Back to sign in": "返回登录", + "Email is invalid": "电子邮件无效", + "Something went wrong, please try again later": "出了错,请稍后再试", + "Invalid email or password": "无效的电子邮件或密码", + "User has been deactivated": "用户已停用", + "Email domain is disallowed": "不允许电子邮件域", + "Email authentication has been disabled": "电子邮件身份验证已禁用", + "Forgot your password?": "忘记密码?", + "Sign up is restricted. You need an invitation to join. Please contact the administrator.": "注册受到限制。您需要邀请加入。请与管理员联系。", + "Email is already used": "电子邮件已被使用", + "Email authentication is disabled": "电子邮件身份验证已禁用", + "First name is required": "必填名", + "Last name is required": "姓氏是必填项", + "Email is required": "电子邮件是必填项", + "Receive updates and newsletters from activepieces": "从活动中接收更新和新闻消息", + "By creating an account, you agree to our": "通过创建帐户,您同意我们", + "terms of service": "服务条款", + "privacy policy": "隐私政策", + "Sign up With": "注册", + "Google": "Google", + "Sign in With": "登录使用", + "SAML": "SAML", + "Email has been verified. You will be redirected to sign in...": "电子邮件已验证。您将被重定向到登录...", + "Verifying email...": "正在验证电子邮件...", + "invitation has expired, once you sign in again you will be able to resend the verification email.": "邀请已过期,一旦您再次登录,您将能够重新发送验证电子邮件。", + "Redirecting to sign in...": "重定向到登录...", + "Password must contain at least one special character": "密码必须包含至少一个特殊字符", + "Password must contain at least one lowercase letter": "密码必须包含至少一个小写字母", + "Password must contain at least one uppercase letter": "密码必须包含至少一个大写字母", + "Password must contain at least one number": "密码必须包含至少一个数字", + "8-64 Characters": "8-64 字符", + "Special Character": "特殊字符", + "Lowercase": "小写", + "Uppercase": "大写", + "Number": "号码", + "Connection has been updated.": "连接已更新。", + "Edit Global Connection": "编辑全局连接", + "Connection has been renamed.": "连接已重命名。", + "New Connection Name": "新连接名称", + "Connection name already used": "连接名称已被使用", + "Please select at least one project": "请选择至少一个项目", + "Run Succeeded": "运行成功", + "Run Failed": "运行失败", + "Flow Run is paused": "流运行已暂停", + "Run Failed due to quota exceeded": "由于超出了配额,运行失败", + "Run failed due to exceeding the memory limit of {memoryLimit} MB": "Run failed due to exceeding the memory limit of {memoryLimit} MB", + "Run exceeded {timeout} seconds, try to optimize your steps.": "Run exceeded {timeout} seconds, try to optimize your steps.", + "Run failed for an unknown reason, contact support.": "因未知原因运行失败,请联系支持。", + "Unknown": "未知的", + "Exit Run": "退出运行", + "Select shown": "选择显示", + "Select all": "选择所有", + "Start Time": "开始时间", + "Runs replayed successfully": "运行重播成功", + "Retry": "重试", + "all except": "除非所有的", + "all": "所有的", + "Only failed runs can be retried from failed step": "只有失败的运行才能从失败的步骤中重试", + "No flow runs found": "找不到流流", + "Come back later when your automations start running": "稍后在自动运行后再回来吧", + "Step running": "步骤运行", + "Step paused": "步骤已暂停", + "Step Stopped": "步骤已停止", + "Step Succeeded": "步骤成功", + "Step Failed": "步骤失败", + "Please publish flow first": "请先发布流程", + "Flow is on": "流量已开启", + "Flow is off": "流量已关闭", + "Permission Needed": "需要权限", + "Draft Version": "草稿版本", + "Published Version": "已发布的版本", + "Locked Version": "锁定版本", + "flowsImported": "{flowsCount, plural, =0 {没有导入流程} =1 {导入流程成功} other {导入流程成功}}", + "Template file is invalid": "模板文件无效", + "No valid templates found. The following files failed to import: ": "未找到有效的模板。以下文件导入失败: ", + "Please select a file first": "请先选择一个文件", + "Unsupported file type": "不支持的文件类型", + "Import Flow": "导入流", + "Warning": "警告", + "Importing a flow will overwrite your current one.": "导入流程将覆盖您当前的流程。", + "Select a folder": "选择一个文件夹", + "Folders": "文件夹", + "Please select a folder": "请选择一个文件夹", + "Moved flows successfully": "移动流成功", + "Move Selected Flows": "移动选中的流动", + "Select Folder": "选择文件夹", + "No Folders": "没有文件夹", + "Flow has been renamed.": "流已重命名。", + "New Flow Name": "新流名称", + "Use Template": "使用模板", + "Browse Templates": "浏览模板", + "Search templates": "搜索模板", + "No templates found, try adjusting your search": "没有找到模板,请尝试调整您的搜索", + "Read more about this template in": "阅读更多关于此模板的信息", + "this blog!": "这个博客!", + "Share Template": "共享模板", + "Generate or update a template link for the current flow to easily share it with others.": "生成或更新当前流的模板链接以方便与他人分享。", + "The template will not have any credentials in connection fields, keeping sensitive information secure.": "模板在连接字段没有任何凭据,保持敏感信息安全。", + "A short description of the template": "模板的简短描述", + "Flow Is In Use": "流量正在使用", + "Flow is being used by another user, please try again later.": "流正被其他用户使用,请稍后再试。", + "Flow has been published.": "流已发布。", + "Flows have been exported.": "流量已经出口。", + "Run": "运行", + "Real time flow": "实时流程", + "Flow can't be published with empty trigger {name}": "Flow can't be published with empty trigger {name}", + "Please contact support as your published flow has a problem": "请联系支持,因为您发布的流程有问题", + "Actions": "行动", + "Are you sure you want to delete these flows? This will permanently delete the flows, all their data and any background runs.": "您确定要删除这些流程吗?这将永久删除流程、所有数据和任何后台运行。", + "You are on a development branch, this will not delete the flows from the remote repository.": "您处于开发分支,这将不会删除远程仓库中的流量。", + "Please enter folder name": "请输入文件夹名称", + "Added folder successfully": "添加文件夹成功", + "The folder name already exists.": "文件夹名称已存在。", + "New Folder": "新建文件夹", + "Folder Name": "文件夹名称", + "Loading...": "加载中...", + "Delete {folderName}": "Delete {folderName}", + "If you delete this folder, we will keep its flows and move them to Uncategorized.": "如果您删除此文件夹,我们将保持其流量并将其移动到取消分类。", + "All flows": "所有流程", + "Please enter a folder name": "请输入文件夹名称", + "Renamed flow successfully": "重命名流成功", + "Folder name already used": "文件夹名称已被使用", + "New Folder Name": "新文件夹名称", + "Connected successfully": "连接成功", + "Connect Git": "连接 Git", + "Remote URL": "远程 URL", + "Folder name is the name of the folder where the project will be stored or fetched.": "文件夹名称是存储或获取项目的文件夹名称。", + "SSH Private Key": "SSH 私钥", + "The SSH private key to use for authentication.": "用于身份验证的 SSH 私钥。", + "Only published flows can be pushed to Git": "只能将发布的流程推送到 Git", + "Pushed successfully": "推送成功", + "Invalid Operation": "无效操作", + "Commit Message": "提交消息", + "Enter a commit message to describe the changes you want to push.": "输入一个提交消息来描述您想要推出的更改。", + "Push": "推送", + "This field is required": "此字段是必填项", + "Your submission was successfully received.": "您的提交已被成功接收。", + "Flow not found": "找不到流", + "The flow you are trying to submit to does not exist.": "您要提交的流程不存在。", + "The flow failed to execute.": "流程执行失败。", + "Submit": "提交", + "issues-notification": "问题-通知", + "Please select a package type": "请选择包类型", + "package.json not found in archive": "在归档中未找到 package.json", + "Error processing archive file": "处理归档文件时出错", + "Please upload a .tgz file": "请上传一个 .tgz 文件", + "Piece name is required for NPM Registry": "NPM 注册表需要方块名称", + "Piece version is required for NPM Registry": "NPM 注册表需要区块版本", + "Piece installed": "已安装块", + "A piece with this name and version is already installed. Please update the version number in package.json and try again.": "已经安装了一个带有此名称和版本的部分。请在 package.json 中更新版本号并重试。", + "Install Piece": "安装块", + "Install a piece": "安装一个块", + "Package Type": "包类型", + "NPM Registry": "NPM 注册表", + "Packed Archive (.tgz)": "封装的存档(.tgz)", + "Piece Version": "区块版本", + "Package Archive": "软件包存档", + "Package archive": "包存档", + "Powerful Node.js & TypeScript code with npm": "强大的 Node.js 和 TypeScript 代码与 npm", + "Loop on Items": "循环项目", + "Split your flow into branches depending on condition(s)": "根据条件(s)拆分流到分支", + "Empty Trigger": "空触发器", + "An internal error occurred while fetching data, please contact support": "获取数据时发生内部错误,请联系客服。", + "An internal error occurred, please contact support": "发生内部错误,请联系支持", + "Custom Javascript Code": "自定义 Javascript 代码", + "Router": "路由器", + "recordsCount": "记录计数", + "selected": "已选择", + "All records selected": "已选择所有记录", + "fieldsCount": "字段计数", + "Saving...": "保存中...", + "Loading more...": "加载更多...", + "Export Table": "导出表", + "Delete Records": "删除记录", + "Are you sure you want to delete the selected records? This action cannot be undone.": "您确定要删除选定的记录吗?此操作无法撤消。", + "record": "记录", + "records": "记录", + "mm/dd/yyy": "月/日/年", + "Delete Field": "删除字段", + "Are you sure you want to delete this field? This action cannot be undone.": "您确定要删除此字段吗?此操作无法撤消。", + "field": "字段", + "Ignored": "忽略", + "Table": "表", + "CSV": "CSV", + "Field": "字段", + "Please select a csv file": "请选择一个 csv 文件", + "Max file size is {maxFileSize}MB": "Max file size is {maxFileSize}MB", + "Import CSV": "导入 CSV", + "Imported records will be added to the bottom of the table": "导入的记录将被添加到表格底部", + "Any records after the limit ({maxRecords} records) will be ignored": "Any records after the limit ({maxRecords} records) will be ignored", + "CSV File": "CSV 文件", + "An unexpected error occurred while importing the csv file, please hit the copy error and send it to support": "导入 csv 文件时发生意外错误,请点击错误并发送以支持", + "Name must be unique": "名称必须是唯一的", + "Type is required": "类型是必需的", + "Please add at least one option": "请至少添加一个选项", + "New Field": "新建字段", + "Options": "备选方案", + "Name is already taken": "名称已被使用", + "Table renamed": "表已重命名", + "Table name": "表格名称", + "Team Invitation Accepted": "团队邀请已接受", + "Thank you for accepting the invitation. We are redirecting you right now...": "感谢您接受邀请。我们现在正在重定向您...", + "Invalid invitation token. Please try again.": "无效的邀请令牌。请重试。", + "Role updated successfully": "角色更新成功", + "Error updating role": "更新角色时出错", + "Please try again later": "请稍后再试", + "Edit Role for": "编辑角色", + "Select Role": "选择角色", + "Avatar": "头像", + "Remove {email}": "Remove {email}", + "Are you sure you want to remove this invitation?": "您确定要删除此邀请吗?", + "Please select invitation type": "请选择邀请类型", + "Please select platform role": "请选择平台角色", + "Invitation sent successfully": "邀请已成功发送", + "Please select a project role": "请选择一个项目角色", + "Invitation link copied successfully": "成功复制邀请链接", + "Invite User": "邀请用户", + "Invitation Link": "邀请链接", + "Please copy the link below and share it with the user you want to invite, the invitation expires in 24 hours.": "请复制下面的链接并与您想要邀请的用户分享,邀请将在24小时内到期。", + "Type the email address of the user you want to invite, the invitation expires in 24 hours.": "输入您想要邀请的用户的电子邮件地址,邀请将在 24 小时内到期。", + "Invite To": "邀请到", + "Entire Platform": "整个平台", + "Select Project Role": "选择项目角色", + "Invite": "邀请", + "Platform Role": "平台角色", + "Select a platform role": "选择一个平台角色", + "Are you sure you want to remove this member?": "您确定要删除这个成员吗?", + "Editor": "编辑器", + "Operator": "运算符", + "Viewer": "查看器", + "Select a project role": "选择一个项目角色", + "Steps in this flow": "此流中的步骤", + "Invalid Access": "无效访问", + "Either the project does not exist or you do not have access to it.": "项目不存在或您没有访问权限。" +} \ No newline at end of file diff --git a/packages/react-ui/src/app/app.tsx b/packages/react-ui/src/app/app.tsx new file mode 100644 index 0000000..b935762 --- /dev/null +++ b/packages/react-ui/src/app/app.tsx @@ -0,0 +1,58 @@ +import { + DefaultErrorFunction, + SetErrorFunction, +} from '@sinclair/typebox/errors'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { EmbeddingProvider } from '@/components/embed-provider'; +import TelemetryProvider from '@/components/telemetry-provider'; +import { ThemeProvider } from '@/components/theme-provider'; +import { SidebarProvider } from '@/components/ui/sidebar-shadcn'; +import { Toaster } from '@/components/ui/toaster'; +import { TooltipProvider } from '@/components/ui/tooltip'; + +import { ChangelogProvider } from './components/changelog-provider'; +import { EmbeddingFontLoader } from './components/embedding-font-loader'; +import { InitialDataGuard } from './components/initial-data-guard'; +import { ApRouter } from './router'; + +const queryClient = new QueryClient(); +let typesFormatsAdded = false; + +if (!typesFormatsAdded) { + SetErrorFunction((error) => { + return error?.schema?.errorMessage ?? DefaultErrorFunction(error); + }); + typesFormatsAdded = true; +} + +export function App() { + const { i18n } = useTranslation(); + return ( + + + + + + + + + + + + + + + + + + + + + + ); +} + +export default App; diff --git a/packages/react-ui/src/app/builder/builder-flow-status-section/index.tsx b/packages/react-ui/src/app/builder/builder-flow-status-section/index.tsx new file mode 100644 index 0000000..572fc2a --- /dev/null +++ b/packages/react-ui/src/app/builder/builder-flow-status-section/index.tsx @@ -0,0 +1,53 @@ +import React from 'react'; + +import { useBuilderStateContext } from '@/app/builder/builder-hooks'; +import { FlowStatusToggle } from '@/features/flows/components/flow-status-toggle'; +import { FlowVersionStateDot } from '@/features/flows/components/flow-version-state-dot'; +import { useAuthorization } from '@/hooks/authorization-hooks'; +import { FlowVersionState, Permission } from '@activepieces/shared'; + +import { PublishButton } from './publish-button'; +import { EditFlowOrViewDraftButton } from './view-draft-or-edit-flow-button'; +const BuilderFlowStatusSection = React.memo(() => { + const { checkAccess } = useAuthorization(); + const userHasPermissionToUpdateFlowStatus = checkAccess( + Permission.UPDATE_FLOW_STATUS, + ); + const [flowVersion, flow, readonly] = useBuilderStateContext((state) => [ + state.flowVersion, + state.flow, + state.readonly, + ]); + const showFlowStatusSection = + (!readonly || flow.publishedVersionId === flowVersion.id) && + userHasPermissionToUpdateFlowStatus; + return ( + <> + {showFlowStatusSection && ( + <> + {flow.publishedVersionId && ( +
+ + {(flow.publishedVersionId === flowVersion.id || + flowVersion.state === FlowVersionState.DRAFT) && ( + + )} +
+ )} + + )} + + + + ); +}); + +BuilderFlowStatusSection.displayName = 'BuilderFlowStatusSection'; +export { BuilderFlowStatusSection }; diff --git a/packages/react-ui/src/app/builder/builder-flow-status-section/publish-button.tsx b/packages/react-ui/src/app/builder/builder-flow-status-section/publish-button.tsx new file mode 100644 index 0000000..49209f9 --- /dev/null +++ b/packages/react-ui/src/app/builder/builder-flow-status-section/publish-button.tsx @@ -0,0 +1,88 @@ +import { t } from 'i18next'; +import { useState } from 'react'; + +import { Button } from '@/components/ui/button'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip'; +import { UpgradeHookDialog } from '@/features/billing/components/upgrade-hook'; +import { flowsHooks } from '@/features/flows/lib/flows-hooks'; +import { useAuthorization } from '@/hooks/authorization-hooks'; +import { FlowVersionState, Permission } from '@activepieces/shared'; + +import { useBuilderStateContext } from '../builder-hooks'; + +const PublishButton = () => { + const { checkAccess } = useAuthorization(); + const [ + flowVersion, + flow, + setFlow, + setVersion, + isSaving, + readonly, + setIsPublishing, + isPublishing, + ] = useBuilderStateContext((state) => [ + state.flowVersion, + state.flow, + state.setFlow, + state.setVersion, + state.saving, + state.readonly, + state.setIsPublishing, + state.isPublishing, + ]); + const isViewingDraft = + flowVersion.state === FlowVersionState.DRAFT || + flowVersion.id === flow.publishedVersionId; + const permissionToEditFlow = checkAccess(Permission.WRITE_FLOW); + const isPublishedVersion = flowVersion.id === flow.publishedVersionId; + const [upgradeDialogOpen, setUpgradeDialogOpen] = useState(false); + const { mutate: publish } = flowsHooks.usePublishFlow({ + flowId: flow.id, + setFlow, + setVersion, + setIsPublishing, + showUpgradeDialog: () => setUpgradeDialogOpen(true), + }); + if (!permissionToEditFlow || !isViewingDraft || (readonly && !isPublishing)) { + return null; + } + return ( + <> + + + + + + + + {isPublishedVersion + ? t('Latest version is published') + : !flowVersion.valid + ? t('Your flow has incomplete steps') + : t('Publish')} + + + + + ); +}; + +PublishButton.displayName = 'PublishButton'; +export { PublishButton }; diff --git a/packages/react-ui/src/app/builder/builder-flow-status-section/view-draft-or-edit-flow-button.tsx b/packages/react-ui/src/app/builder/builder-flow-status-section/view-draft-or-edit-flow-button.tsx new file mode 100644 index 0000000..daa1f88 --- /dev/null +++ b/packages/react-ui/src/app/builder/builder-flow-status-section/view-draft-or-edit-flow-button.tsx @@ -0,0 +1,43 @@ +import { t } from 'i18next'; +import { useNavigate } from 'react-router-dom'; +import { useLocation } from 'react-use'; + +import { Button } from '@/components/ui/button'; +import { useAuthorization } from '@/hooks/authorization-hooks'; +import { FlowVersionState, Permission } from '@activepieces/shared'; + +import { useBuilderStateContext, useSwitchToDraft } from '../builder-hooks'; + +const EditFlowOrViewDraftButton = () => { + const location = useLocation(); + const navigate = useNavigate(); + const { checkAccess } = useAuthorization(); + const { switchToDraft, isSwitchingToDraftPending } = useSwitchToDraft(); + const [flowVersion, flowId, readonly, run] = useBuilderStateContext( + (state) => [state.flowVersion, state.flow.id, state.readonly, state.run], + ); + const isViewingDraft = flowVersion.state === FlowVersionState.DRAFT; + const permissionToEditFlow = checkAccess(Permission.WRITE_FLOW); + if (!readonly || (isViewingDraft && !run)) { + return null; + } + + return ( + + ); +}; +EditFlowOrViewDraftButton.displayName = 'EditFlowOrViewDraftButton'; +export { EditFlowOrViewDraftButton }; diff --git a/packages/react-ui/src/app/builder/builder-header/builder-header.tsx b/packages/react-ui/src/app/builder/builder-header/builder-header.tsx new file mode 100644 index 0000000..a285fe6 --- /dev/null +++ b/packages/react-ui/src/app/builder/builder-header/builder-header.tsx @@ -0,0 +1,209 @@ +import { QuestionMarkCircledIcon } from '@radix-ui/react-icons'; +import { t } from 'i18next'; +import { ChevronDown, History, Logs } from 'lucide-react'; +import { useEffect, useMemo, useState } from 'react'; +import { + createSearchParams, + useLocation, + useNavigate, + useSearchParams, +} from 'react-router-dom'; + +import { + LeftSideBarType, + useBuilderStateContext, +} from '@/app/builder/builder-hooks'; +import { useEmbedding } from '@/components/embed-provider'; +import { Button } from '@/components/ui/button'; +import EditableText from '@/components/ui/editable-text'; +import { HomeButton } from '@/components/ui/home-button'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip'; +import { foldersHooks } from '@/features/folders/lib/folders-hooks'; +import { useAuthorization } from '@/hooks/authorization-hooks'; +import { flagsHooks } from '@/hooks/flags-hooks'; +import { authenticationSession } from '@/lib/authentication-session'; +import { useNewWindow } from '@/lib/navigation-utils'; +import { NEW_FLOW_QUERY_PARAM } from '@/lib/utils'; +import { + ApFlagId, + FlowOperationType, + FlowVersionState, + Permission, + supportUrl, +} from '@activepieces/shared'; + +import FlowActionMenu from '../../components/flow-actions-menu'; +import { BuilderFlowStatusSection } from '../builder-flow-status-section'; + +import { UserAvatarMenu } from './user-avatar-menu'; + +export const BuilderHeader = () => { + const [queryParams] = useSearchParams(); + const navigate = useNavigate(); + const location = useLocation(); + const openNewWindow = useNewWindow(); + const { data: showSupport } = flagsHooks.useFlag( + ApFlagId.SHOW_COMMUNITY, + ); + const isInRunsPage = useMemo( + () => location.pathname.includes('/runs'), + [location.pathname], + ); + const hasPermissionToReadRuns = useAuthorization().checkAccess( + Permission.READ_FLOW, + ); + const [ + flow, + flowVersion, + setLeftSidebar, + moveToFolderClientSide, + applyOperation, + ] = useBuilderStateContext((state) => [ + state.flow, + state.flowVersion, + state.setLeftSidebar, + state.moveToFolderClientSide, + state.applyOperation, + ]); + + const { embedState } = useEmbedding(); + + const { data: folderData } = foldersHooks.useFolder(flow.folderId ?? 'NULL'); + + const isLatestVersion = + flowVersion.state === FlowVersionState.DRAFT || + flowVersion.id === flow.publishedVersionId; + const folderName = folderData?.displayName ?? t('Uncategorized'); + const [isEditingFlowName, setIsEditingFlowName] = useState(false); + useEffect(() => { + setIsEditingFlowName(queryParams.get(NEW_FLOW_QUERY_PARAM) === 'true'); + }, []); + + return ( +
+
+
+ +
+ {!embedState.hideFolders && + !embedState.disableNavigationInBuilder && ( + <> + + + + navigate({ + pathname: + authenticationSession.appendProjectRoutePrefix( + '/flows', + ), + search: createSearchParams({ + folderId: folderData?.id ?? 'NULL', + }).toString(), + }) + } + > + {folderName} + + + + {t('Go to folder')} {folderName} + + + + + {' / '} + + )} + {!embedState.hideFlowNameInBuilder && ( + + applyOperation({ + type: FlowOperationType.CHANGE_NAME, + request: { + displayName: value, + }, + }) + } + isEditing={isEditingFlowName} + setIsEditing={setIsEditingFlowName} + tooltipContent={isLatestVersion ? t('Edit Flow Name') : ''} + /> + )} +
+ {!embedState.hideFlowNameInBuilder && ( + { + navigate( + authenticationSession.appendProjectRoutePrefix('/flows'), + ); + }} + onRename={() => { + setIsEditingFlowName(true); + }} + onMoveTo={(folderId) => moveToFolderClientSide(folderId)} + onDuplicate={() => {}} + > + + + )} +
+ +
+
+ {showSupport && ( + + )} + {hasPermissionToReadRuns && ( + + )} + + {!isInRunsPage && ( + + )} + + + +
+
+
+ ); +}; diff --git a/packages/react-ui/src/app/builder/builder-header/user-avatar-menu.tsx b/packages/react-ui/src/app/builder/builder-header/user-avatar-menu.tsx new file mode 100644 index 0000000..709d46d --- /dev/null +++ b/packages/react-ui/src/app/builder/builder-header/user-avatar-menu.tsx @@ -0,0 +1,62 @@ +import { useQueryClient } from '@tanstack/react-query'; +import { t } from 'i18next'; +import { LogOut } from 'lucide-react'; + +import { useEmbedding } from '@/components/embed-provider'; +import { useTelemetry } from '@/components/telemetry-provider'; +import { UserAvatar } from '@/components/ui/user-avatar'; +import { userHooks } from '@/hooks/user-hooks'; +import { authenticationSession } from '@/lib/authentication-session'; + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, + DropdownMenuLabel, + DropdownMenuItem, +} from '../../../components/ui/dropdown-menu'; +import { TextWithIcon } from '../../../components/ui/text-with-icon'; + +export function UserAvatarMenu() { + const { reset } = useTelemetry(); + const { embedState } = useEmbedding(); + const { data: user } = userHooks.useCurrentUser(); + const queryClient = useQueryClient(); + if (!user || embedState.isEmbedded) { + return null; + } + + return ( + + + + + + +
+
{user.email}
+
+
+ { + userHooks.invalidateCurrentUser(queryClient); + authenticationSession.logOut(); + reset(); + }} + className="cursor-pointer" + > + } + text={{t('Logout')}} + className="cursor-pointer" + /> + +
+
+ ); +} diff --git a/packages/react-ui/src/app/builder/builder-hooks.ts b/packages/react-ui/src/app/builder/builder-hooks.ts new file mode 100644 index 0000000..96d28cd --- /dev/null +++ b/packages/react-ui/src/app/builder/builder-hooks.ts @@ -0,0 +1,999 @@ +import { useMutation } from '@tanstack/react-query'; +import { useReactFlow } from '@xyflow/react'; +import { + createContext, + useContext, + useCallback, + useEffect, + useRef, +} from 'react'; +import { usePrevious } from 'react-use'; +import { create, useStore } from 'zustand'; + +import { Messages } from '@/components/ui/chat/chat-message-list'; +import { INTERNAL_ERROR_TOAST, toast } from '@/components/ui/use-toast'; +import { flowsApi } from '@/features/flows/lib/flows-api'; +import { PromiseQueue } from '@/lib/promise-queue'; +import { NEW_FLOW_QUERY_PARAM } from '@/lib/utils'; +import { + FlowOperationRequest, + FlowOperationType, + FlowRun, + FlowVersion, + FlowVersionState, + Permission, + PopulatedFlow, + TriggerType, + flowOperations, + flowStructureUtil, + isNil, + StepLocationRelativeToParent, + FlowRunStatus, + apId, + StepSettings, +} from '@activepieces/shared'; + +import { flowRunUtils } from '../../features/flow-runs/lib/flow-run-utils'; +import { pieceSelectorUtils } from '../../features/pieces/lib/piece-selector-utils'; +import { useAuthorization } from '../../hooks/authorization-hooks'; +import { + AskAiButtonOperations, + PieceSelectorItem, + PieceSelectorOperation, + StepMetadataWithSuggestions, +} from '../../lib/types'; + +import { + copySelectedNodes, + deleteSelectedNodes, + getActionsInClipboard, + pasteNodes, + toggleSkipSelectedNodes, +} from './flow-canvas/bulk-actions'; +import { + CanvasShortcuts, + CanvasShortcutsProps, +} from './flow-canvas/context-menu/canvas-context-menu'; +import { STEP_CONTEXT_MENU_ATTRIBUTE } from './flow-canvas/utils/consts'; +import { flowCanvasUtils } from './flow-canvas/utils/flow-canvas-utils'; +import { textMentionUtils } from './piece-properties/text-input-with-mentions/text-input-utils'; +const flowUpdatesQueue = new PromiseQueue(); +export const BuilderStateContext = createContext(null); + +export function useBuilderStateContext( + selector: (state: BuilderState) => T, +): T { + const store = useContext(BuilderStateContext); + if (!store) + throw new Error('Missing BuilderStateContext.Provider in the tree'); + return useStore(store, selector); +} + +export enum LeftSideBarType { + RUNS = 'runs', + VERSIONS = 'versions', + RUN_DETAILS = 'run-details', + AI_COPILOT = 'chat', + NONE = 'none', +} + +export enum RightSideBarType { + NONE = 'none', + PIECE_SETTINGS = 'piece-settings', +} + +export enum ChatDrawerSource { + TEST_FLOW = 'test-flow', + TEST_STEP = 'test-step', +} + +type InsertMentionHandler = (propertyPath: string) => void; +export type BuilderState = { + flow: PopulatedFlow; + flowVersion: FlowVersion; + readonly: boolean; + sampleData: Record; + sampleDataInput: Record; + loopsIndexes: Record; + run: FlowRun | null; + leftSidebar: LeftSideBarType; + rightSidebar: RightSideBarType; + selectedStep: string | null; + canExitRun: boolean; + activeDraggingStep: string | null; + saving: boolean; + /** change this value to trigger the step form to set its values from the step */ + refreshStepFormSettingsToggle: boolean; + selectedBranchIndex: number | null; + chatDrawerOpenSource: ChatDrawerSource | null; + chatSessionMessages: Messages; + chatSessionId: string | null; + setChatDrawerOpenSource: (source: ChatDrawerSource | null) => void; + setChatSessionMessages: (messages: Messages) => void; + addChatMessage: (message: Messages[0]) => void; + clearChatSession: () => void; + setChatSessionId: (sessionId: string | null) => void; + refreshSettings: () => void; + setSelectedBranchIndex: (index: number | null) => void; + clearRun: (userHasPermissionToEditFlow: boolean) => void; + exitStepSettings: () => void; + renameFlowClientSide: (newName: string) => void; + moveToFolderClientSide: (folderId: string) => void; + setRun: (run: FlowRun, flowVersion: FlowVersion) => void; + setLeftSidebar: (leftSidebar: LeftSideBarType) => void; + setRightSidebar: (rightSidebar: RightSideBarType) => void; + applyOperation: (operation: FlowOperationRequest) => void; + removeStepSelection: () => void; + selectStepByName: (stepName: string) => void; + setActiveDraggingStep: (stepName: string | null) => void; + setFlow: (flow: PopulatedFlow) => void; + setSampleData: (stepName: string, payload: unknown) => void; + setSampleDataInput: (stepName: string, payload: unknown) => void; + setVersion: (flowVersion: FlowVersion) => void; + insertMention: InsertMentionHandler | null; + setReadOnly: (readOnly: boolean) => void; + setInsertMentionHandler: (handler: InsertMentionHandler | null) => void; + setLoopIndex: (stepName: string, index: number) => void; + operationListeners: Array< + (flowVersion: FlowVersion, operation: FlowOperationRequest) => void + >; + addOperationListener: ( + listener: ( + flowVersion: FlowVersion, + operation: FlowOperationRequest, + ) => void, + ) => void; + removeOperationListener: ( + listener: ( + flowVersion: FlowVersion, + operation: FlowOperationRequest, + ) => void, + ) => void; + askAiButtonProps: AskAiButtonOperations | null; + setAskAiButtonProps: (props: AskAiButtonOperations | null) => void; + selectedNodes: string[]; + setSelectedNodes: (nodes: string[]) => void; + panningMode: 'grab' | 'pan'; + setPanningMode: (mode: 'grab' | 'pan') => void; + isFocusInsideListMapperModeInput: boolean; + setIsFocusInsideListMapperModeInput: ( + isFocusInsideListMapperModeInput: boolean, + ) => void; + isPublishing: boolean; + setIsPublishing: (isPublishing: boolean) => void; + handleAddingOrUpdatingStep: (props: { + pieceSelectorItem: PieceSelectorItem; + operation: PieceSelectorOperation; + overrideSettings?: StepSettings; + selectStepAfter: boolean; + customLogoUrl?: string; + }) => string; + deselectStep: () => void; + //Piece selector state + openedPieceSelectorStepNameOrAddButtonId: string | null; + setOpenedPieceSelectorStepNameOrAddButtonId: ( + stepNameOrAddButtonId: string | null, + ) => void; + selectedPieceMetadataInPieceSelector: StepMetadataWithSuggestions | null; + setSelectedPieceMetadataInPieceSelector: ( + metadata: StepMetadataWithSuggestions | null, + ) => void; + /**Need this to re-render the piece settings form on replace step or updating agent */ + lastRerenderPieceSettingsTimeStamp: number | null; + setLastRerenderPieceSettingsTimeStamp: (timestamp: number) => void; +}; +const DEFAULT_PANNING_MODE_KEY_IN_LOCAL_STORAGE = 'defaultPanningMode'; +export type BuilderInitialState = Pick< + BuilderState, + | 'flow' + | 'flowVersion' + | 'readonly' + | 'run' + | 'canExitRun' + | 'sampleData' + | 'sampleDataInput' +>; + +export type BuilderStore = ReturnType; + +export const createBuilderStore = (initialState: BuilderInitialState) => + create((set, get) => { + const failedStepNameInRun = initialState.run?.steps + ? flowRunUtils.findLastStepWithStatus( + initialState.run.status, + initialState.run.steps, + ) + : null; + const initiallySelectedStep = determineInitiallySelectedStep( + failedStepNameInRun, + initialState.flowVersion, + ); + const isEmptyTriggerInitiallySelected = + initiallySelectedStep === 'trigger' && + initialState.flowVersion.trigger.type === TriggerType.EMPTY; + return { + loopsIndexes: + initialState.run && initialState.run.steps + ? flowRunUtils.findLoopsState( + initialState.flowVersion, + initialState.run, + {}, + ) + : {}, + sampleData: initialState.sampleData, + sampleDataInput: initialState.sampleDataInput, + flow: initialState.flow, + flowVersion: initialState.flowVersion, + leftSidebar: initialState.run + ? LeftSideBarType.RUN_DETAILS + : LeftSideBarType.NONE, + readonly: initialState.readonly, + run: initialState.run, + saving: false, + selectedStep: initiallySelectedStep, + canExitRun: initialState.canExitRun, + activeDraggingStep: null, + rightSidebar: + initiallySelectedStep && !isEmptyTriggerInitiallySelected + ? RightSideBarType.PIECE_SETTINGS + : RightSideBarType.NONE, + refreshStepFormSettingsToggle: false, + chatDrawerOpenSource: null, + chatSessionMessages: [], + chatSessionId: apId(), + setChatDrawerOpenSource: (source: ChatDrawerSource | null) => + set({ chatDrawerOpenSource: source }), + setChatSessionMessages: (messages: Messages) => + set({ chatSessionMessages: messages }), + addChatMessage: (message: Messages[0]) => + set((state) => ({ + chatSessionMessages: [...state.chatSessionMessages, message], + })), + clearChatSession: () => + set({ chatSessionMessages: [], chatSessionId: null }), + setChatSessionId: (sessionId: string | null) => + set({ chatSessionId: sessionId }), + removeStepSelection: () => + set({ + selectedStep: null, + rightSidebar: RightSideBarType.NONE, + selectedBranchIndex: null, + }), + + setActiveDraggingStep: (stepName: string | null) => + set({ + activeDraggingStep: stepName, + }), + setSelectedBranchIndex: (branchIndex: number | null) => + set({ + selectedBranchIndex: branchIndex, + }), + setReadOnly: (readonly: boolean) => set({ readonly }), + renameFlowClientSide: (newName: string) => { + set((state) => { + return { + flowVersion: { + ...state.flowVersion, + displayName: newName, + }, + }; + }); + }, + selectStepByName: (selectedStep: string) => { + set((state) => { + if (selectedStep === state.selectedStep) { + return state; + } + const selectedNodes = + isNil(selectedStep) || selectedStep === 'trigger' + ? [] + : [selectedStep]; + + const rightSidebar = + selectedStep === 'trigger' && + state.flowVersion.trigger.type === TriggerType.EMPTY + ? RightSideBarType.NONE + : RightSideBarType.PIECE_SETTINGS; + + const leftSidebar = !isNil(state.run) + ? LeftSideBarType.RUN_DETAILS + : LeftSideBarType.NONE; + + const isEmptyTrigger = + selectedStep === 'trigger' && + state.flowVersion.trigger.type === TriggerType.EMPTY; + + return { + openedPieceSelectorStepNameOrAddButtonId: isEmptyTrigger + ? 'trigger' + : null, + selectedStep, + rightSidebar, + leftSidebar, + selectedBranchIndex: null, + askAiButtonProps: null, + selectedNodes, + }; + }); + }, + moveToFolderClientSide: (folderId: string) => { + set((state) => { + return { + flow: { + ...state.flow, + folderId, + }, + }; + }); + }, + setFlow: (flow: PopulatedFlow) => set({ flow, selectedStep: null }), + setSampleData: (stepName: string, payload: unknown) => + set((state) => { + return { + sampleData: { + ...state.sampleData, + [stepName]: payload, + }, + }; + }), + setSampleDataInput: (stepName: string, payload: unknown) => + set((state) => { + return { + sampleDataInput: { + ...state.sampleDataInput, + [stepName]: payload, + }, + }; + }), + clearRun: (userHasPermissionToEditFlow: boolean) => + set({ + run: null, + readonly: !userHasPermissionToEditFlow, + loopsIndexes: {}, + leftSidebar: LeftSideBarType.NONE, + selectedBranchIndex: null, + }), + exitStepSettings: () => + set((state) => ({ + rightSidebar: RightSideBarType.NONE, + leftSidebar: + state.leftSidebar === LeftSideBarType.AI_COPILOT + ? LeftSideBarType.NONE + : state.leftSidebar, + selectedStep: null, + selectedBranchIndex: null, + askAiButtonProps: null, + })), + setRightSidebar: (rightSidebar: RightSideBarType) => + set({ rightSidebar }), + setLeftSidebar: (leftSidebar: LeftSideBarType) => + set({ leftSidebar, askAiButtonProps: null }), + setRun: async (run: FlowRun, flowVersion: FlowVersion) => + set((state) => { + const lastStepWithStatus = flowRunUtils.findLastStepWithStatus( + run.status, + run.steps, + ); + const initiallySelectedStep = run.steps + ? determineInitiallySelectedStep(lastStepWithStatus, flowVersion) + : state.selectedStep ?? 'trigger'; + return { + loopsIndexes: flowRunUtils.findLoopsState( + flowVersion, + run, + state.loopsIndexes, + ), + run, + flowVersion, + leftSidebar: LeftSideBarType.RUN_DETAILS, + rightSidebar: initiallySelectedStep + ? RightSideBarType.PIECE_SETTINGS + : RightSideBarType.NONE, + selectedStep: initiallySelectedStep, + readonly: true, + }; + }), + setIsPublishing: (isPublishing: boolean) => + set((state) => { + if (isPublishing) { + state.removeStepSelection(); + state.setReadOnly(true); + } else { + state.setReadOnly(false); + } + return { + isPublishing, + }; + }), + isPublishing: false, + setLoopIndex: (stepName: string, index: number) => { + set((state) => { + return { + loopsIndexes: { + ...state.loopsIndexes, + [stepName]: index, + }, + }; + }); + }, + applyOperation: (operation: FlowOperationRequest) => + set((state) => { + if (state.readonly) { + console.warn('Cannot apply operation while readonly'); + return state; + } + const newFlowVersion = flowOperations.apply( + state.flowVersion, + operation, + ); + + state.operationListeners.forEach((listener) => { + listener(state.flowVersion, operation); + }); + + const updateRequest = async () => { + set({ saving: true }); + try { + const updatedFlowVersion = await flowsApi.update( + state.flow.id, + operation, + true, + ); + set((state) => { + return { + flowVersion: { + ...state.flowVersion, + id: updatedFlowVersion.version.id, + state: updatedFlowVersion.version.state, + }, + saving: flowUpdatesQueue.size() !== 0, + }; + }); + } catch (error) { + console.error(error); + flowUpdatesQueue.halt(); + } + }; + flowUpdatesQueue.add(updateRequest); + return { flowVersion: newFlowVersion }; + }), + setVersion: (flowVersion: FlowVersion) => { + const initiallySelectedStep = determineInitiallySelectedStep( + null, + flowVersion, + ); + const isEmptyTriggerInitiallySelected = + initiallySelectedStep === 'trigger' && + flowVersion.trigger.type === TriggerType.EMPTY; + set((state) => ({ + flowVersion, + run: null, + selectedStep: initiallySelectedStep, + readonly: + state.flow.publishedVersionId !== flowVersion.id && + flowVersion.state === FlowVersionState.LOCKED, + leftSidebar: LeftSideBarType.NONE, + rightSidebar: + initiallySelectedStep && !isEmptyTriggerInitiallySelected + ? RightSideBarType.PIECE_SETTINGS + : RightSideBarType.NONE, + selectedBranchIndex: null, + })); + }, + insertMention: null, + setInsertMentionHandler: (insertMention: InsertMentionHandler | null) => { + set({ insertMention }); + }, + refreshSettings: () => + set((state) => ({ + refreshStepFormSettingsToggle: !state.refreshStepFormSettingsToggle, + })), + selectedBranchIndex: null, + operationListeners: [], + addOperationListener: ( + listener: ( + flowVersion: FlowVersion, + operation: FlowOperationRequest, + ) => void, + ) => + set((state) => ({ + operationListeners: [...state.operationListeners, listener], + })), + removeOperationListener: ( + listener: ( + flowVersion: FlowVersion, + operation: FlowOperationRequest, + ) => void, + ) => + set((state) => ({ + operationListeners: state.operationListeners.filter( + (l) => l !== listener, + ), + })), + askAiButtonProps: null, + setAskAiButtonProps: (props) => { + return set((state) => { + let leftSidebar = state.leftSidebar; + if (props) { + leftSidebar = LeftSideBarType.AI_COPILOT; + } else if (state.leftSidebar === LeftSideBarType.AI_COPILOT) { + leftSidebar = LeftSideBarType.NONE; + } + + let rightSidebar = state.rightSidebar; + if (props && props.type === FlowOperationType.UPDATE_ACTION) { + rightSidebar = RightSideBarType.PIECE_SETTINGS; + } else if (props) { + rightSidebar = RightSideBarType.NONE; + } + + let selectedStep = state.selectedStep; + if (props && props.type === FlowOperationType.UPDATE_ACTION) { + selectedStep = props.stepName; + } else if (props) { + selectedStep = null; + } + + return { + askAiButtonProps: props, + leftSidebar, + rightSidebar, + selectedStep, + }; + }); + }, + selectedNodes: [], + setSelectedNodes: (nodes) => { + return set(() => ({ + selectedNodes: nodes, + })); + }, + deselectStep: () => { + return set(() => ({ + rightSidebar: RightSideBarType.NONE, + selectedBranchIndex: null, + selectedStep: null, + })); + }, + panningMode: getPanningModeFromLocalStorage(), + setPanningMode: (mode: 'grab' | 'pan') => { + localStorage.setItem(DEFAULT_PANNING_MODE_KEY_IN_LOCAL_STORAGE, mode); + return set(() => ({ + panningMode: mode, + })); + }, + isFocusInsideListMapperModeInput: false, + setIsFocusInsideListMapperModeInput: ( + isFocusInsideListMapperModeInput: boolean, + ) => { + return set(() => ({ + isFocusInsideListMapperModeInput, + })); + }, + handleAddingOrUpdatingStep: ({ + pieceSelectorItem, + operation, + overrideSettings, + selectStepAfter, + customLogoUrl, + }): string => { + const { + applyOperation, + selectStepByName, + flowVersion, + setOpenedPieceSelectorStepNameOrAddButtonId, + } = get(); + const defaultValues = pieceSelectorUtils.getDefaultStepValues({ + stepName: getStepNameFromOperationType(operation, flowVersion), + pieceSelectorItem, + overrideDefaultSettings: overrideSettings, + }); + const isTrigger = + defaultValues.type === TriggerType.PIECE || + defaultValues.type === TriggerType.EMPTY; + switch (operation.type) { + case FlowOperationType.UPDATE_TRIGGER: { + if (!isTrigger) { + break; + } + if (flowVersion.trigger.type === TriggerType.EMPTY) { + set(() => { + return { + rightSidebar: RightSideBarType.PIECE_SETTINGS, + }; + }); + } + applyOperation({ + type: FlowOperationType.UPDATE_TRIGGER, + request: defaultValues, + }); + selectStepByName('trigger'); + set(() => ({ + lastRerenderPieceSettingsTimeStamp: Date.now(), + })); + break; + } + case FlowOperationType.ADD_ACTION: { + if (isTrigger) { + break; + } + applyOperation({ + type: FlowOperationType.ADD_ACTION, + request: { + ...operation.actionLocation, + action: { + ...defaultValues, + customLogoUrl, + }, + }, + }); + if (selectStepAfter) { + selectStepByName(defaultValues.name); + } + break; + } + case FlowOperationType.UPDATE_ACTION: { + const currentAction = flowStructureUtil.getStep( + operation.stepName, + flowVersion.trigger, + ); + if (isNil(currentAction)) { + console.error( + "Trying to update an action that's not in the displayed flow version", + ); + break; + } + if ( + !flowStructureUtil.isAction(currentAction.type) || + !flowStructureUtil.isAction(defaultValues.type) + ) { + break; + } + applyOperation({ + type: FlowOperationType.UPDATE_ACTION, + request: { + type: defaultValues.type, + displayName: defaultValues.displayName, + name: operation.stepName, + settings: { + ...defaultValues.settings, + }, + valid: defaultValues.valid, + customLogoUrl, + }, + }); + set(() => ({ + lastRerenderPieceSettingsTimeStamp: Date.now(), + })); + break; + } + } + setOpenedPieceSelectorStepNameOrAddButtonId(null); + return defaultValues.name; + }, + selectedPieceMetadataInPieceSelector: null, + setSelectedPieceMetadataInPieceSelector: ( + metadata: StepMetadataWithSuggestions | null, + ) => { + return set(() => ({ + selectedPieceMetadataInPieceSelector: metadata, + })); + }, + openedPieceSelectorStepNameOrAddButtonId: isEmptyTriggerInitiallySelected + ? 'trigger' + : null, + setOpenedPieceSelectorStepNameOrAddButtonId: ( + stepNameOrAddButtonId: string | null, + ) => { + return set((state) => { + const isReplacingEmptyTrigger = + state.flowVersion.trigger.type === TriggerType.EMPTY && + stepNameOrAddButtonId === 'trigger'; + return { + openedPieceSelectorStepNameOrAddButtonId: stepNameOrAddButtonId, + rightSidebar: isReplacingEmptyTrigger + ? RightSideBarType.NONE + : state.rightSidebar, + }; + }); + }, + lastRerenderPieceSettingsTimeStamp: null, + setLastRerenderPieceSettingsTimeStamp: (timestamp: number) => { + return set(() => ({ + lastRerenderPieceSettingsTimeStamp: timestamp, + })); + }, + }; + }); + +export function getPanningModeFromLocalStorage(): 'grab' | 'pan' { + return localStorage.getItem(DEFAULT_PANNING_MODE_KEY_IN_LOCAL_STORAGE) === + 'grab' + ? 'grab' + : 'pan'; +} + +const shortcutHandler = ( + event: KeyboardEvent, + handlers: Record void>, +) => { + const shortcutActivated = Object.entries(CanvasShortcuts).find( + ([_, shortcut]) => + shortcut.shortcutKey?.toLowerCase() === event.key.toLowerCase() && + !!( + shortcut.withCtrl === event.ctrlKey || + shortcut.withCtrl === event.metaKey + ) && + !!shortcut.withShift === event.shiftKey, + ); + if (shortcutActivated) { + if ( + isNil(shortcutActivated[1].shouldNotPreventDefault) || + !shortcutActivated[1].shouldNotPreventDefault + ) { + event.preventDefault(); + } + event.stopPropagation(); + handlers[shortcutActivated[0] as keyof CanvasShortcutsProps](); + } +}; + +export const NODE_SELECTION_RECT_CLASS_NAME = 'react-flow__nodesselection-rect'; +export const doesSelectionRectangleExist = () => { + return document.querySelector(`.${NODE_SELECTION_RECT_CLASS_NAME}`) !== null; +}; +export const useHandleKeyPressOnCanvas = () => { + const [ + selectedNodes, + flowVersion, + selectedStep, + exitStepSettings, + applyOperation, + readonly, + ] = useBuilderStateContext((state) => [ + state.selectedNodes, + state.flowVersion, + state.selectedStep, + state.exitStepSettings, + state.applyOperation, + state.readonly, + ]); + + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if ( + e.target instanceof HTMLElement && + (e.target === document.body || + e.target.classList.contains(NODE_SELECTION_RECT_CLASS_NAME) || + e.target.closest(`[data-${STEP_CONTEXT_MENU_ATTRIBUTE}]`)) && + !readonly + ) { + const selectedNodesWithoutTrigger = selectedNodes.filter( + (node) => node !== flowVersion.trigger.name, + ); + shortcutHandler(e, { + Copy: () => { + if ( + selectedNodesWithoutTrigger.length > 0 && + document.getSelection()?.toString() === '' + ) { + copySelectedNodes({ + selectedNodes: selectedNodesWithoutTrigger, + flowVersion, + }); + } + }, + Delete: () => { + if (selectedNodes.length > 0) { + deleteSelectedNodes({ + exitStepSettings, + selectedStep, + selectedNodes, + applyOperation, + }); + } + }, + Skip: () => { + if (selectedNodesWithoutTrigger.length > 0) { + toggleSkipSelectedNodes({ + selectedNodes: selectedNodesWithoutTrigger, + flowVersion, + applyOperation, + }); + } + }, + Paste: () => { + getActionsInClipboard().then((actions) => { + if (actions.length > 0) { + const lastStep = [ + flowVersion.trigger, + ...flowStructureUtil.getAllNextActionsWithoutChildren( + flowVersion.trigger, + ), + ].at(-1)!.name; + const lastSelectedNode = + selectedNodes.length === 1 ? selectedNodes[0] : null; + pasteNodes( + flowVersion, + { + parentStepName: lastSelectedNode ?? lastStep, + stepLocationRelativeToParent: + StepLocationRelativeToParent.AFTER, + }, + applyOperation, + ); + } + }); + }, + }); + } + }, + [ + selectedNodes, + flowVersion, + applyOperation, + selectedStep, + exitStepSettings, + readonly, + ], + ); + + useEffect(() => { + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + }, [handleKeyDown]); +}; + +export const useSwitchToDraft = () => { + const [flowVersion, setVersion, clearRun, setFlow] = useBuilderStateContext( + (state) => [ + state.flowVersion, + state.setVersion, + state.clearRun, + state.setFlow, + ], + ); + const { checkAccess } = useAuthorization(); + const userHasPermissionToEditFlow = checkAccess(Permission.WRITE_FLOW); + const { mutate: switchToDraft, isPending: isSwitchingToDraftPending } = + useMutation({ + mutationFn: async () => { + const flow = await flowsApi.get(flowVersion.flowId); + return flow; + }, + onSuccess: (flow) => { + setFlow(flow); + setVersion(flow.version); + clearRun(userHasPermissionToEditFlow); + }, + onError: () => { + toast(INTERNAL_ERROR_TOAST); + }, + }); + return { + switchToDraft, + isSwitchingToDraftPending, + }; +}; + +export const useIsFocusInsideListMapperModeInput = ({ + containerRef, + setIsFocusInsideListMapperModeInput, + isFocusInsideListMapperModeInput, +}: { + containerRef: React.RefObject; + setIsFocusInsideListMapperModeInput: ( + isFocusInsideListMapperModeInput: boolean, + ) => void; + isFocusInsideListMapperModeInput: boolean; +}) => { + useEffect(() => { + const focusInListener = () => { + const focusedElement = document.activeElement; + const isFocusedInside = !!containerRef.current?.contains(focusedElement); + const isFocusedInsideDataSelector = + !isNil(document.activeElement) && + document.activeElement instanceof HTMLElement && + textMentionUtils.isDataSelectorOrChildOfDataSelector( + document.activeElement, + ); + setIsFocusInsideListMapperModeInput( + isFocusedInside || + (isFocusedInsideDataSelector && isFocusInsideListMapperModeInput), + ); + }; + document.addEventListener('focusin', focusInListener); + return () => { + document.removeEventListener('focusin', focusInListener); + }; + }, [setIsFocusInsideListMapperModeInput, isFocusInsideListMapperModeInput]); +}; +export const useFocusOnStep = () => { + const [currentRun, selectStep] = useBuilderStateContext((state) => [ + state.run, + state.selectStepByName, + ]); + + const previousStatus = usePrevious(currentRun?.status); + const currentStep = flowRunUtils.findLastStepWithStatus( + previousStatus ?? FlowRunStatus.RUNNING, + currentRun?.steps ?? {}, + ); + const lastStep = usePrevious(currentStep); + + const { fitView } = useReactFlow(); + useEffect(() => { + if (!isNil(lastStep) && lastStep !== currentStep && !isNil(currentStep)) { + setTimeout(() => { + console.log('focusing on step', currentStep); + fitView(flowCanvasUtils.createFocusStepInGraphParams(currentStep)); + selectStep(currentStep); + }); + } + }, [lastStep, currentStep, selectStep, fitView]); +}; + +export const useResizeCanvas = ( + containerRef: React.RefObject, + setHasCanvasBeenInitialised: (hasCanvasBeenInitialised: boolean) => void, +) => { + const containerSizeRef = useRef({ + width: 0, + height: 0, + }); + const { getViewport, setViewport } = useReactFlow(); + + useEffect(() => { + if (!containerRef.current) return; + const resizeObserver = new ResizeObserver((entries) => { + const { width, height } = entries[0].contentRect; + setHasCanvasBeenInitialised(true); + const { x, y, zoom } = getViewport(); + if (containerRef.current && width !== containerSizeRef.current.width) { + const newX = x + (width - containerSizeRef.current.width) / 2; + // Update the viewport to keep content centered without affecting zoom + setViewport({ x: newX, y, zoom }); + } + // Adjust x/y values based on the new size and keep the same zoom level + containerSizeRef.current = { + width, + height, + }; + }); + resizeObserver.observe(containerRef.current); + return () => { + resizeObserver.disconnect(); + }; + }, [setViewport, getViewport]); +}; + +const getStepNameFromOperationType = ( + operation: PieceSelectorOperation, + flowVersion: FlowVersion, +) => { + switch (operation.type) { + case FlowOperationType.UPDATE_ACTION: + return operation.stepName; + case FlowOperationType.ADD_ACTION: + return flowStructureUtil.findUnusedName(flowVersion.trigger); + case FlowOperationType.UPDATE_TRIGGER: + return 'trigger'; + } +}; +function determineInitiallySelectedStep( + failedStepNameInRun: string | null, + flowVersion: FlowVersion, +): string | null { + if (failedStepNameInRun) { + return failedStepNameInRun; + } + const firstInvalidStep = flowStructureUtil + .getAllSteps(flowVersion.trigger) + .find((s) => !s.valid); + // eslint-disable-next-line no-restricted-globals + const isNewFlow = location.search.includes(NEW_FLOW_QUERY_PARAM); + if (isNewFlow) { + return null; + } + return firstInvalidStep?.name ?? 'trigger'; +} diff --git a/packages/react-ui/src/app/builder/builder-state-provider.tsx b/packages/react-ui/src/app/builder/builder-state-provider.tsx new file mode 100644 index 0000000..f9a3545 --- /dev/null +++ b/packages/react-ui/src/app/builder/builder-state-provider.tsx @@ -0,0 +1,39 @@ +import { useRef } from 'react'; + +import { + BuilderInitialState, + BuilderStateContext, + BuilderStore, + createBuilderStore, +} from '@/app/builder/builder-hooks'; +import { useAuthorization } from '@/hooks/authorization-hooks'; +import { projectHooks } from '@/hooks/project-hooks'; +import { Permission } from '@activepieces/shared'; + +type BuilderStateProviderProps = React.PropsWithChildren; + +export function BuilderStateProvider({ + children, + sampleData, + sampleDataInput, + ...props +}: BuilderStateProviderProps) { + const storeRef = useRef(); + const { checkAccess } = useAuthorization(); + const readonly = !checkAccess(Permission.WRITE_FLOW) || props.readonly; + projectHooks.useReloadPageIfProjectIdChanged(props.flow.projectId); + if (!storeRef.current) { + storeRef.current = createBuilderStore({ + ...props, + readonly, + sampleData, + sampleDataInput, + }); + } + + return ( + + {children} + + ); +} diff --git a/packages/react-ui/src/app/builder/copilot/chat-message.tsx b/packages/react-ui/src/app/builder/copilot/chat-message.tsx new file mode 100644 index 0000000..013c8fd --- /dev/null +++ b/packages/react-ui/src/app/builder/copilot/chat-message.tsx @@ -0,0 +1,105 @@ +import { Static, Type } from '@sinclair/typebox'; +import { Bot } from 'lucide-react'; +import React, { forwardRef } from 'react'; + +import { CodeEditor } from '../step-settings/code-settings/code-editor'; + +import { WelcomeMessage } from './welcome-message'; + +export const CopilotMessage = Type.Union([ + Type.Object({ + messageType: Type.Literal('code'), + userType: Type.Literal('bot'), + content: Type.Object({ + packages: Type.Object({ + dependencies: Type.Record(Type.String(), Type.String()), + }), + code: Type.String(), + inputs: Type.Record(Type.String(), Type.String()), + title: Type.String(), + icon: Type.String(), + }), + }), + Type.Object({ + messageType: Type.Literal('text'), + userType: Type.Union([Type.Literal('user'), Type.Literal('bot')]), + content: Type.String(), + }), +]); +export type CopilotMessage = Static; + +const ChatBox = ({ children }: { children: React.ReactNode }) => ( +
+ {children} +
+); + +interface ChatMessageProps { + message: CopilotMessage; + onApplyCode: (message: CopilotMessage) => void; +} + +export const ChatMessage = forwardRef( + ({ message, onApplyCode }, ref) => { + const isUser = message.userType === 'user'; + const isBot = message.userType === 'bot'; + const isCode = message.messageType === 'code'; + const isWelcome = + message.messageType === 'text' && message.content === 'welcome'; + + return ( +
+ {isWelcome ? ( + + ) : ( + <> + {isBot && ( + <> +
+ +
+
+ {!isCode ? ( + +

{message.content}

+
+ ) : ( + {}} + applyCodeToCurrentStep={() => onApplyCode(message)} + > + )} +
+ + )} + {isUser && ( + +

{message.content}

+
+ )} + + )} +
+ ); + }, +); + +ChatMessage.displayName = 'ChatMessage'; diff --git a/packages/react-ui/src/app/builder/copilot/index.tsx b/packages/react-ui/src/app/builder/copilot/index.tsx new file mode 100644 index 0000000..0e59494 --- /dev/null +++ b/packages/react-ui/src/app/builder/copilot/index.tsx @@ -0,0 +1,288 @@ +import { useMutation } from '@tanstack/react-query'; +import { t } from 'i18next'; +import { ArrowUp, LoaderCircle } from 'lucide-react'; +import { nanoid } from 'nanoid'; +import { useState, useRef, useEffect } from 'react'; +import { Socket } from 'socket.io-client'; + +import { CardList } from '@/components/custom/card-list'; +import { useSocket } from '@/components/socket-provider'; +import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'; +import { INTERNAL_ERROR_TOAST, toast } from '@/components/ui/use-toast'; +import { CORE_STEP_METADATA } from '@/features/pieces/lib/step-utils'; +import { + ActionType, + CodeAction, + FlowOperationType, + flowStructureUtil, + AskCopilotCodeResponse, + AskCopilotRequest, + WebsocketClientEvent, + WebsocketServerEvent, + AskCopilotTool, +} from '@activepieces/shared'; + +import { Textarea } from '../../../components/ui/textarea'; +import { pieceSelectorUtils } from '../../../features/pieces/lib/piece-selector-utils'; +import { LeftSideBarType, useBuilderStateContext } from '../builder-hooks'; +import { SidebarHeader } from '../sidebar-header'; + +import { ChatMessage, CopilotMessage } from './chat-message'; +import { LoadingMessage } from './loading-message'; + +interface DefaultEventsMap { + [event: string]: (...args: any[]) => void; +} + +const COPILOT_WELCOME_MESSAGES: CopilotMessage[] = [ + { + messageType: 'text', + content: 'welcome', + userType: 'bot', + }, +]; + +async function getCodeResponse( + socket: Socket, + request: AskCopilotRequest, +): Promise { + const id = nanoid(); + + socket.emit(WebsocketServerEvent.ASK_COPILOT, { + ...request, + id, + }); + return new Promise((resolve, reject) => { + socket.on( + WebsocketClientEvent.ASK_COPILOT_FINISHED, + (response: AskCopilotCodeResponse) => { + resolve(response); + }, + ); + socket.on('error', (error: any) => { + reject(error); + }); + }); +} + +export const CopilotSidebar = () => { + const [messages, setMessages] = useState( + COPILOT_WELCOME_MESSAGES, + ); + const [inputMessage, setInputMessage] = useState(''); + const [ + flowVersion, + refreshSettings, + applyOperation, + setLeftSidebar, + askAiButtonProps, + selectStepByName, + setAskAiButtonProps, + selectedStep, + ] = useBuilderStateContext((state) => [ + state.flowVersion, + state.refreshSettings, + state.applyOperation, + state.setLeftSidebar, + state.askAiButtonProps, + state.selectStepByName, + state.setAskAiButtonProps, + state.selectedStep, + ]); + const lastMessageRef = useRef(null); + const socket = useSocket(); + const scrollToLastMessage = () => { + setTimeout(() => { + lastMessageRef.current?.scrollIntoView({ + behavior: 'smooth', + }); + }, 1); + }; + const { isPending, mutate } = useMutation({ + mutationFn: (request: AskCopilotRequest) => + getCodeResponse(socket, request), + onSuccess: (response: AskCopilotCodeResponse) => { + console.log(response); + setMessages((prevMessages) => [ + ...prevMessages, + { + content: { + code: response.code, + packages: response.packageJson, + inputs: response.inputs, + icon: response.icon ?? '', + title: response.title, + }, + messageType: 'code', + userType: 'bot', + }, + ]); + scrollToLastMessage(); + }, + onError: (error: any) => { + toast({ + title: t('Error generating code'), + description: error.message, + }); + }, + }); + + const handleSendMessage = () => { + const trimmedInputMessage = inputMessage.trim(); + if (trimmedInputMessage === '') { + return; + } + mutate({ + prompt: inputMessage, + context: messages.map((message) => ({ + role: message.userType === 'user' ? 'user' : 'assistant', + content: JSON.stringify(message.content), + })), + tools: [AskCopilotTool.GENERATE_CODE], + flowId: flowVersion.flowId, + flowVersionId: flowVersion.id, + selectedStepName: selectedStep ?? undefined, + }); + + setMessages([ + ...messages, + { content: inputMessage, userType: 'user', messageType: 'text' }, + ]); + setInputMessage(''); + scrollToLastMessage(); + }; + const textAreaRef = useRef(null); + const applyCodeToCurrentStep = (message: CopilotMessage) => { + if (!askAiButtonProps) { + console.log('no ask ai button props'); + toast(INTERNAL_ERROR_TOAST); + return; + } + if (message.messageType !== 'code') { + return; + } + if (askAiButtonProps) { + const stepName = + askAiButtonProps.type === FlowOperationType.UPDATE_ACTION + ? askAiButtonProps.stepName + : flowStructureUtil.findUnusedName(flowVersion.trigger); + const codeAction = pieceSelectorUtils.getDefaultStepValues({ + stepName, + pieceSelectorItem: CORE_STEP_METADATA[ActionType.CODE], + overrideDefaultSettings: { + input: message.content.inputs, + sourceCode: { + code: message.content.code, + packageJson: JSON.stringify(message.content.packages, null, 2), + }, + }, + }) as CodeAction; + + codeAction.displayName = message.content.title; + codeAction.customLogoUrl = message.content.icon; + if (askAiButtonProps.type === FlowOperationType.ADD_ACTION) { + applyOperation({ + type: FlowOperationType.ADD_ACTION, + request: { + action: codeAction, + ...askAiButtonProps.actionLocation, + }, + }); + selectStepByName(stepName); + setAskAiButtonProps({ + type: FlowOperationType.UPDATE_ACTION, + stepName: codeAction.name, + }); + } else { + const step = flowStructureUtil.getStep( + askAiButtonProps.stepName, + flowVersion.trigger, + ); + if (step) { + const errorHandlingOptions = + step.type === ActionType.CODE || step.type === ActionType.PIECE + ? step.settings.errorHandlingOptions + : codeAction.settings.errorHandlingOptions; + + applyOperation({ + type: FlowOperationType.UPDATE_ACTION, + request: { + displayName: message.content.title, + name: step.name, + customLogoUrl: message.content.icon, + settings: { + ...codeAction.settings, + input: message.content.inputs, + errorHandlingOptions, + }, + type: ActionType.CODE, + valid: true, + }, + }); + } + } + } + refreshSettings(); + }; + useEffect(() => { + if (textAreaRef.current) { + textAreaRef.current.focus(); + } + }, []); + + return ( +
+ setLeftSidebar(LeftSideBarType.NONE)}> + {t('AI Copilot')} + +
+ + + {messages.map((message, index) => ( + applyCodeToCurrentStep(message)} + /> + ))} + {isPending && } + + + +
+